Перейти к публикации
Forum-Tech - Разработчиков и Администраторов

Вся активность

Эта лента обновляется автоматически     

  1. Ранее
  2. Вслед за презентацией консоли Xbox Series X компания Microsoft представила свежее обновление своего API DirectX — DirectX 12 Ultimate. Если кратко, то в новой версии Microsoft объединила DirectX 12 и DirectX Raytracing (DXR) 1.1, а также добавила к официальным спецификациям некоторые расширения и функции. А ещё был обновлён логотип — Microsoft вернулась к римским цифрам. А теперь давайте подробнее пройдёмся по каждому пункту. В состав DirectX 12 Ultimate вошло всё то, что уже было в составе DirectX 12_1, а также все составляющие DXR. Поэтому на данный момент отнюдь не каждая видеокарта сможет похвастаться поддержкой DirectX 12 Ultimate. Фактически, это могут себе позволить только видеокарты NVIDIA GeForce RTX 20-й серии. А вот у AMD поддержка новой версии API появится лишь в следующем поколении графических процессоров на архитектуре RDNA 2. DirectX Raytracing 1.1 включает небольшие обновления изначального API DXR. Новая версия включает такие новые методы, как Inline Raytracing и непрямое выполнение лучей. Кажется, здесь разработчикам предлагается фактически тоже самое, что и в вышедшем недавно Vulkan RT, то есть возможность запуска расчёта трассировки одного луча из шейдера, а не большой группы лучей. По сути, решение трассировки лучей здесь происходит прямо во время её выполнения, а не по заранее заданным направлениям, что требует меньше ресурсов. Также частью DirectX 12 Ultimate стали такие функции, как Variable Rate Shading 2 (VRS 2), Mesh Shaders и Sampler Feedback. Вкратце напомним, что технология VRS обеспечивает прирост производительности за счёт выполнения более простых затенений в менее важных областях сцены. Mesh Shaders раньше было расширением NVIDIA, которое позволяло более эффективно обрабатывать большие объёмы данных. Наконец, Sampler Feedback позволяет более эффективно расходовать видеопамять — потенциально, «потребление» памяти игрой можно сократить до 10 % от обычного значения. Ключевая цель DirectX 12 Ultimate заключается в том, чтобы предоставить разработчикам единую платформу для ПК и будущей Xbox Series X. Последняя, напомним, как раз станет носителем графического процессора с архитектурой AMD RDNA 2, так что уже довольно скоро не только NVIDIA сможет похвастаться графическими процессорами с поддержкой DirectX 12 Ultimate. Для пользователей появление DirectX 12 Ultimate в первую очередь должно упростить выбор видеокарт. Если ускоритель поддерживает новую версию API, это означает, что он поддерживает полный набор современных функций, включая трассировку лучей, VRS и так далее. К тому же, пользователь будет знать, что видеокарта с DirectX 12 Ultimate будет иметь как минимум те же возможности, что и Xbox Series X. И хотя пока игр с поддержкой всех возможностей новой версии API нет, со временем они появятся, особенно с выходом новой консоли. Кстати, хотя PlayStation 5 тоже получит графику RDNA 2, у неё поддержки нового API не будет.
  3. Корпорация Microsoft рассказала о спецификациях своей новой консоли Xbox Series X и показала её в действии. Подробности опубликованы в блоге на сайте компании. Консоль будет работать на процессоре AMD с архитектурой Zen 2 и восемью ядрами с тактовой частотой 3,8 ГГц каждое. Графический процессор будет представлен архитектурой AMD RNDA 2 и будет выдавать 12 терафлопс. Оперативная память консоли будет составлять 16 Гб. Также устройство оснастят SSD NVME на один терабайт, при этом память можно булет расширить с помощью проприетарной карты, что добавит ещё один терабайт. Утверждается, что этот девайс будет работать так же быстро, как и основной модуль — в качестве примера компания показала проприетарную карту от Seagate. Создатели консоли сообщают, что при её создании в первую очередь думали о высоких фреймрейтах, а не только о качестве картинки и разрешении. Приставка будет нацелена на 4К и 60 fps в большинстве синглплеерных игр и на 120 fps в соревновательных шутерах. При этом станет возможным создавать большие миры в играх более цельными и уменьшать время загрузки между локациями, что сыграет на руку разработчикам игр с открытым миром. Кроме того, Xbox Series X будет выгружать полный дамп памяти на SSD, что позволит продолжить геймплей с той же точки даже в случае полной перезагрузки приставки. В то же время игры с консоли предыдущего поколения — Xbox One — будут работать на Series X в более высоком разрешении. Microsoft представила новую систему менеджмента памяти консоли — Xbox Velocity Architecture. Утверждается, что благодаря ней разработчики смогут мгновенно получать доступ к 100 Гб данных. Принцип работы основан на том, что консоль будет использовать часть SSD в качестве виртуальной памяти — данные будут быстро передаваться в ОЗУ с помощью специального чипа. Компания использовала в консоли сразу несколько технологий, которые снижают задержку передачи картинки на экран — Dynamic Latency Input (DLI), Variable Refresh Rate (VRR) и Auto Low Latency Mode (ALLM). Кроме того, Microsoft утверждает, что уже два года сотрудничает с ведущими производителями телевизоров, чтобы «обеспечить готовность экосистемы к функциям Xbox Series X». Приставка получит поддержку аппаратного ускорения DirectX Raytracing — с помощью трассировки лучей разработчики хотя добиться более реалистичного распространения света в играх. Использование технологии показали на примере Minecraft. Кроме того, Microsoft опубликовала скриншоты из игры Gears 5, запущенной на Xbox Series X, отметив, что разработчики смогли получить 100 кадров в секунду для отдельных режимов и сейчас работают над увеличением этого параметра, а при 4K и 60 fps количество частиц в кадре на 50% больше, чем смогли бы себе позволить самые продвинутые ПК. Геймпад к консоли претерпел незначительные изменения — он сохранил свой форм-фактор, но получил USB-C, кнопку Share и видоизменённый D-pad. В Microsoft говорят, что новый контроллер работает на более высокой частоте и расходует энергию экономичнее, работая по-прежнему на двух АА-батарейках. Microsoft подтвердила, что консоль будет поддерживать технологию обратной совместимости Smart Delivery. Её внедрение означает, что игрокам не потребуется покупать игры компании дважды — все они запустятся на новом поколении консолей. Пока это касается только продуктов, принадлежащих самой корпорации — из сторонних разработчиков о доступности своей новой игры на Xbox Series X объявила CD Projekt RED, которая планирует выпустить в сентябре Cyberpunk 2077. Microsoft по-прежнему собирается начать продажи консоли в конце 2020 года несмотря на вспышку коронавируса. Корпорация анонсировала Xbox Series X в декабре 2019 года. В конце февраля Microsoft показала внешний вид консоли и поделилась некоторыми подробностями о ней. Так, дизайн Xbox Series X будет кардинально отличаться от предыдущих поколений, став квадратным в форме «монолитного кирпича» — такой форм-фактор позволит ставить приставку как вертикально, так и горизонтально. Представители корпорации настаивают, что новую консоль можно описать тремя словами: «производительность, скорость и совместимость».
  4. Файловые системы Windows – это одна из тех вещей, о которых вы не задумываетесь, пока не приходится сделать выбор между ними. Что изменится если вы выберите NTFS вместо FAT32? Какие преимущества у exFAT и зачем вообще нам нужны различные файловые системы? Что такое файловая система? Представьте себе, что каждый файл на вашем компьютере – это одна из книг в библиотеке. Когда в библиотеке книгу ставят на какую-нибудь полку, то непременно записывают информацию о ней (название, автор, год) в указатель, который сможет подсказать посетителям где нужно искать эту книгу. Поиск в едином указателе заметно проще, чем беспорядочный перебор всех книг. Вместе с тем в библиотеке вам могут ограничить доступ к определенным книгам. Например, к служебным, где содержаться записи о том, кто и когда брал определенную литературу. Подобные записи также помогают отслеживать изменения статуса книг. Это базовый принцип работы файловых систем – они ведут учет файлов, их расположение и метаданные, что позволяет компьютеру понимать куда необходимо обратиться для выполнения определенного действия. Это очень важная базовая функция, поэтому неудивительно, что для ее выполнения существует несколько подходов. Конечно существуют не только FAT, exFAT и NTFS – на Mac используются HFS+ и APFS, а в Linux самыми распространенными являются ext3, ext4 и btrfs. Файловые системы, предназначенные для Windows и разрабатываемые Microsoft, стали стандартом для множества устройств, поэтому вы можете встретить их чаще, чем менее известные HFS+ или ext4. FAT32: чрезвычайно совместимая, но без поддержки больших файлов Файловая система FAT (File Allocation Table, таблица размещения файлов) пережила несколько вариаций со времени создания в 1977 году и используется по сей день. Поскольку она ограничена всего 32 битами для адресации кластеров, то максимальный объем тома может составлять до 8 ТиБ (Тебибайт), хотя на самом деле размер всего диска не может быть более 2 ТиБ. Однако самым большим ограничением можно считать невозможность хранения файлов объемом более 4 Гб. Во времена Windows 95 это казалось вполне нормальным, но не сейчас. В дополнение к этим ограничениям, FAT32 лишена некоторых современных функций, таких как установка разрешений на доступ к файлам и журналирование. Тем не менее эта файловая система по-прежнему используется для множества USB накопителей и SD карт, поскольку их заметная часть имеет небольшой объем и не требует наличия более продвинутых возможностей. На самом деле FAT32 до сих пор является файловой системой по умолчанию для большинства переносных накопителей объемом до 32 Гб, потому что за столь продолжительный период существования она стала совместима практически со всем. Windows, MacOS, Linux, Android и множество других систем могут читать и записывать на накопители с FAT32, что делает эту ФС прекрасным выбором для работы на любых устройствах. exFAT: совместимая со множеством устройств и с поддержкой больших файлов Переносные накопители объемом свыше 32 Гб, как правило форматируются в exFAT (Extended FAT, расширенная FAT). Эту файловую систему можно считать ответом Microsoft на требования изменения FAT под современные накопители. Благодаря 64 битной адресации, максимальный теоретический лимит на размер файла составляет 16 эксабайт, что с лихвой перекрывает любые современные требования. Как и FAT32, exFAT не обладает большим количеством функций, но она также вполне совместима со множеством устройств. Windows, Mac и Android поддерживают чтение и запись на разделы с exFAT, а в Linux нативная поддержка этой ФС появилась только в недавнем ядре 5.4, однако для более ранних систем беспроблемную работу можно организовать установив всего несколько пакетов. В целом, exFAT – это хороший выбор для переносных накопителей, если вы не планируете использовать их со старыми устройствами или не очень свежими Linux дистрибутивами. NTFS: лучшая для Windows систем Здесь все становится действительно по-другому: NTFS (New Technology File System, файловая система новой технологии) – это файловая система, которая широко применяется со времен Windows XP. Она обладает множеством функций, которые позволяют использовать ее в качестве ФС для системных дисков. Среди плюсов можно отметить поддержку файлов и разделов огромного размера, управление разрешениями, журналирование, поддержку шифрования, теневое копирование и еще ряд функций, которые позволяют ОС оставаться безопасной и защищённой. С другой стороны, важно сказать, что NTFS – это закрытая файловая система, предназначенная для Windows. Поэтому в других операционных системах ее поддержка довольна ограничена. MacOS по умолчанию умеет только читать файлы в NTFS разделах, в Linux дистрибутивах как правило организованы функции чтения и записи, но сторонними средствами. Однако, если говорить не о компьютерах, то у большинства устройств как правило поддержка либо отсутствует вовсе, либо минимальна. Так какую же файловую систему использовать? На самом деле ответ на этот вопрос очень прост: Используйте FAT32 для максимальной совместимости переносных накопителей и файлов объемом меньше 4 Гб. Используйте exFAT при необходимости хранить на съемных дисках большого объема файлы размером свыше 4 Гб. Используйте NTFS для системных дисков Windows и устройств где есть полная совместимость.
  5. Ваш ник: Yastreb Ссылка на профиль: Yastreb Получить награду(медали): Медаль Благодарности. Xeon, вручи какую нибудь медаль пожалуйста =)))
  6. Для получение награды или медали, вы должны оформить анкету, пример: Ваш ник: Xeon Ссылка на профиль: Xeon Получить награду(медали): Медаль за активность на Форуме Вы должны написать зачем вам данная награда и предоставить доказательства, что вы соответствуете требованиям. Со списком можете Медали
  7. Xeon

    Наследование в java - урок 9

    Пример наследования Рассмотрим класс под названием Shape (Форма). Shape является базовым классом, от которого наследуются другие формы, таких как прямоугольник, квадрат, круг и т.д. 1 2 3 4 5 6 public class Shape { public double area () { return 0; } } Поскольку это просто общая «форма», метод вычисления площади area() будет возвращать ноль. Чтобы узнать площадь конкретной фигуры, нужно создать подкласс, унаследованный от класса Shape, и в нем переопределить метод area(). От класса Shape наследуется класс Circle, который тоже представляет собой форму. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 class Circle extends Shape { // ключевое слово "extends" означает наследование private static final double PI = Math.PI; // константа private double diameter; //любое число, представляющее диаметр этого круга public Circle(double diameter) { // конструктор this.diameter = diameter; } public double area(){ double radius = diameter / 2.0; return PI * radius * radius; } } Метод area() базового класса наследуется классом Circle и становится доступен в нем, но нам нужно переопределить метод area() в классе Circle, таким образом, чтобы он вычислял площадь круга. Преимущество использования наследования в том, что вы можете написать код, который можно применить к ряду классов, расширяющих более общий класс. Создадим класс Main, и в нем напишем метод, который вычисляет большую площадь двух фигур: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Main { public static void main(String[] args) { Shape s1 = new Circle (5.0); Shape s2 = new Rectangle (5.0, 4.0); Shape larger = getLargerShape(s1,s2); System.out.println("The area of the larger shape is: "+larger.area()); } public static Shape getLargerShape(Shape s1, Shape s2) { if(s1.area() > s2.area()) return s1; else return s2; } } Как вы можете видеть, метод getLargerShape() не требует указания определенного типа фигуры для его двух параметров. В качестве параметров для этого метода можно использовать экземпляр любого класса, который наследует тип Shape. Можно использовать экземпляр класса круг, прямоугольник, треугольник, трапеция, и т.д. — до тех пор, как они наследуют класс формы. Резюме: Наследование классов ( inheritance ) один из существенных атрибутов ООП (объектно-ориентированного программирования). Оно позволяет строить новые классы на базе существующих, добавляя в них новые возможности или переопределяя существующие. Унаследованные поля могут быть использованы непосредственно, как и любые другие поля. Вы можете объявить поле в дочернем классе с тем же именем, что и в суперклассе, скрывая его таким образом (но это не рекомендуется). Вы можете объявить новые поля в подклассе, которых нет в суперклассе. Унаследованные методы тоже можно использовать непосредственно. Вы можете написать в подклассе новый метод, который имеет ту же сигнатуру, что и метод суперкласса, это называется переопределением метода. Вы можете написать новый статический метод в подклассе, который имеет ту же сигнатуру, что и метод суперкласса, таким образом, скрывая его (то есть метод суперкласа будет виден внутри подкласса только через ключевое слово super). Вы можете объявить новые методы в подклассе, которых нет в суперклассе. Вы можете написать конструктор подкласса, который вызывает конструктор суперкласса, неявно или с помощью ключевого слова super. Подкласс не наследует закрытые члены родительского класса, например, обозначенные модификатором private. Упражнение: Создайте недостающий класс Rectangle, который наследует класс Shape и находит площадь.
  8. После создания простого приложения, которое выводит что-то на экран, вы должны компилировать ваш код и запустить его. Независимо от того, какую операционную систему вы используете, Linux, Mac или Windows, если на вашем компьютере установлен JDK (Java Development Kit), вы можете в консоли набрать следующие команды чтобы скомпилировать и запустить программу: javac (или javac.exe) java (или java.exe) В первом случае будет вызван компилятор javac.exe, а во втором случае — запускалка java.exe, которая стартует нашу программу. Эти файлы лежат в папке bin вашего JDK. Рассмотрим на примере. Вспомним код из первого урока — создадим файл с названием Main.java. 1 2 3 4 5 public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } } Идем в папку, куда среда разработки сохранила проект. Я работаю в IntelliJIDEA, и мой проект лежит в C:\Users\имя пользователя\IdeaProjects\название проекта\src. Находим там наш файл Main.java. Консоль вызывается так: щелкаем правой клавишей мыши с зажатой клавишей Shift на пустом месте в папке, где лежит файл нашей программы, и выбираем пункт контекстного меню «Открыть окно команд». Для того, чтобы скомпилировать его нужно набрать в консоли команду javac и в качестве параметра передать имя нашего файла: 1 javac Main.java Эта команда вызовет компилятор, который создаст файл Main.class, содержащий скомпилированный код нашей java программы. Чтобы запустить ее, нужно ввести команду java с именем класса (не файла!) в качестве параметра: 1 java Main.class //так неправильно 1 java Main //так правильно Аргументы В главном классе нашей программы есть метод public static void main(...), который в качестве аргумента принимает массив String[] args. Массив строк в качестве аргумента можно передать в программу при запуске из командной строки . Любой массив в Java имеет переменную длину, это число элементов в этом массиве. Добавим такой код в класс Main.java: 1 2 3 4 5 6 7 8 public class Main { public static void main(String[] args) { for (int i = 0; i < args.length; i++) { System.out.println(args); } } } И чтобы скомпилировать и запустить программу с аргументами, пишем в консоль: 1 2 javac Main.java java Main arg0 arg1 arg2 Упражнение Создайте программу, которая выводит аргументы, переданные при запуске, в одну строку.
  9. Почти весь код, написанный на языке Java — это классы и объекты, т.к. java — объектно ориентированный язык. Java-объекты хранят свое состояние в переменных, эти переменные еще называют полями или членами экземпляра класса. Давайте начнем с примера: 1 2 3 4 5 class Point { int x; int y; } Этот класс определяет точку с координатами X и Y. Для того, чтобы создать экземпляр этого класса, мы должны использовать ключевое слово new. 1 2 Point p = new Point(); При этом используется так называемый конструктор по умолчанию (или конструктор без параметров) — это специальный метод класса, мы его не определяли явно, но даже если его не определить, он создаётся автоматически, выполняется при создании каждого нового объекта и присваивает первоначальные значения его свойствам (инициализирует их). От методов в java конструктор отличается тем, что имеет то же самое имя, что и класс, в котором он определен, а также не имеет типа возвращаемого значения. Конструктор в java возвращает новый объект — экземпляр родительского класса). Мы можем определить наш собственный конструктор. Поскольку методы можно перегружать, а конструктор является методом, то с помощью перегрузки можно создать дополнительные варианты конструкторов. Например, удобно иметь конструктор, который позволит при создании объекта Point явно указывать его координаты: 1 2 3 4 5 6 7 8 9 10 class Point { int x; int y; Point(int x, int y) { this.x = x; this.y = y; } } Это означает, что мы не можем больше использовать конструктор по умолчанию new Point(). Теперь мы можем пользоваться только перегруженным конструктором с указанием начальных значений new Point(4, 1). Мы можете определить более чем один конструктор, так что объект класса Point может быть создан несколькими способами. Давайте создадим еще один перегруженный конструктор: 1 2 3 4 5 6 7 8 9 10 11 12 13 class Point { int x; int y; Point() { this(0, 0); } Point(int x, int y) { this.x = x; this.y = y; } } Обратите внимание на ключевое слово this. Мы можем использовать его внутри конструктора для вызова другого конструктора (для того, чтобы избежать дублирования кода). Мы также используем ключевое слово this в качестве ссылки на текущий объект. После того, как мы определили объект р мы можем получить доступ к X и Y. 1 2 p.x = 3; p.y = 6; Методы Теперь мы можем определить методы класса Point. 1 2 3 4 5 6 7 8 9 10 11 12 class Point { ... // Наш код ранее void printPoint() { System.out.println("(" + x + "," + y + ")"); } Point center(Point other) { // Возвращает центр между этой и другой точками // Заметьте, мы используем целое число, поэтому не получим точное значение return new Point((x + other.x) / 2, (y + other.y) / 2); } } Public и Private На прошлом уроке мы уже рассматривали модификаторы доступа. Ключевое слово private перед переменной или методом означает, что только сам класс может получить доступ к переменной или методу, а когда мы используем public это значит, что любой объект может получить к нему доступ. Конструкторы обычно определяют как public, переменные определяют как private, а методы разделяются, некоторые public, а некоторые private, в зависимости от задач. Упражнение Напишите новый метод класса Point с названием scale, который сделает точку наполовину ближе к началу координат (0,0). Например, точка с координатами (8, 4) после выполнения метода scale будет иметь координаты (4, 2).
  10. Xeon

    Методы java - урок 6

    Методы в Java — это законченная последовательность действий (инструкций), направленных на решение отдельной задачи. По сути, это функции (они же процедуры, подпрограммы) более ранних, не ООП языков. Только эти функции являются членами классов и для различия с обычными функциями, согласно терминологии объектно-ориентированного программирования, называются методами. Методы определяются всегда внутри классов: 1 2 3 4 5 public class Main { public static void foo() { // Тело метода } } foo - это метод, который мы определили в классе Main, давайте его рассмотрим. public — тип доступа (метод может вызываться из другого класса). Существуют и другие типы доступа, к примеру private (метод доступен только внутри класса) и protected (о нем мы будем говорить позже). static означает что метод статический, он принадлежит классу Main, а не конкретному экземпляру класса Main. Мы можем вызвать этот метод из другого класса так: Main.foo(). void значит, что этот метод не возвращает значение. Методы могут возвращать значение в Java и оно должно быть определено при объявлении метода. Однако, вы можете использовать return просто для выхода из метода. Этот метод не получает никаких аргументов, но методы java могут получать аргументы, как мы увидим далее на примерах. Если тип возвращаемого значения не void, в теле метода должен быть хотя бы один оператор return выражение; где тип выражения должен совпадать с типом возвращаемого значения. Этот оператор возвращает результат вычисления выражения в точку вызова метода. Если тип возвращаемого значения – void, возврат из метода выполняется либо после выполнения последнего оператора тела метода, либо в результате выполнения оператора return; (таких операторов в теле метода может быть несколько). Пример объявления метода, возвращающего значение типа int – сумму двух своих параметров типа int: 1 2 3 4 5 int sum(int a, int b){ int x; x = a + b; return x; } При вызове метода, например, sum(5, 3), параметры 5 и 3 передаются в метод, как значения соответственно a и b, и оператор вызова метода sum(5, 3)– заменяется значением, возвращаемым методом (8). В отличие от языка C, в котором тип параметра, задаваемого при вызове, приводится к типу параметра в объявлении функции, тип задаваемого параметра в Java должен строго соответствовать типу параметра в объявлении метода, поэтому вызов метода sum(1.5, приведет к ошибке при компиляции программы. Не статические методы Не статические методы в Java используются чаще, чем статические методы. Эти методы могут принадлежать любому объекту, экземпляру класса, а не всему классу. Не статические методы могут получать доступ и изменять поля объекта. 1 2 3 4 5 6 7 8 9 public class Student { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } Вызов методов требует экземпляра класса Student. 1 2 3 4 5 6 Student s = new Student(); s.setName("Danielle"); String name = s.getName(); Student.setName("Bob"); // Не будет работать! Student.getName(); // Не будет работать! Перегруженные методы В языке Java в пределах одного класса можно определить два или более ме­тодов, которые совместно используют одно и то же имя, но имеют разное количество параметров. Когда это имеет место, методы называют перегру­женными, а о процессе говорят как о перегрузке метода (method overloading). Когда метод вызывается, то по количеству параметров и/или их типам среда выполнения Java определяет, какую именно версию перегруженного метода надо вызывать (тип возвращаемого значения во внимание не принимается, хотя, в принципе, он тоже может отличаться у разных версий перегруженных методов). Например, метод 1 2 3 4 5 double sum(double a, double b) { double x; x = a + b; return x; } вместе с объявленным ранее методом int sum(int a, int b)составляют пару перегруженных методов и при вызове sum(5, будет вызван первый метод, а при вызове sum(5.0, 8.0) будет вызван второй метод. По умолчанию метод, как и переменная, доступен только классам в том же пакете (наборе классов), что и исходный класс. Если перед возвращаемым типом задан модификатор доступа public, то метод является глобальным и доступен любым объектам, а модификатор private означает, что метод доступен в том классе, в котором он был объявлен, т.е. метод инкапсулирован в данном классе. Переопределение методов Кроме перегрузки существует также замещение, или переопределение методов (англ. overriding). Замещение происходит, когда класс потомок (подкласс) определяет некоторый метод, который уже есть в родительском классе(суперклассе), таким образом новый метод заменяет метод суперкласса. У нового метода подкласса должны быть те же параметры или сигнатура, тип возвращаемого результата, что и у метода родительского класса. 1 2 3 4 5 6 7 8 9 10 11 12 public class Thought { public void message() { System.out.println("Я себя чувствую как стрекоза, попавшая в параллельную вселенную."); } } public class Advice extends Thought { @Override // Аннотация @Override с Java 5 является необязательной, но весьма полезной public void message() { System.out.println("Внимание: Даты в календаре ближе, чем кажутся."); } } Класс Thought представляет собой суперкласс и обеспечивает вызов метода message(). Подкласс, называемый Advice, наследует каждый метод класса Thought. Однако, класс Advice переопределяет метод message(), замещая функционал, описанный в классе Thought. В Java, когда подкласс содержит метод, переопределяющий метод суперкласса, то он может помимо своего метода вызывать и метод суперкласса при помощи ключевого слова super. Например, нижеследующий вариант выводит оба сообщения при вызове метода подкласса: 1 2 3 4 5 6 7 public class Advice extends Thought { @Override public void message() { System.out.println("Внимание: Даты в календаре ближе, чем кажутся."); super.message(); // Вызов версии метода родительского класса } } Существуют методы, которые подкласс не может переопределять. Например, в Java метод, объявленный с ключевым словом final, не может быть переопределён. Методы, объявленные как private или static не могут быть переопределены, поскольку это соответствует неявному использованию final. Резюме Каждый java-метод должен быть внутри класса Статические методы принадлежат классу, а не статические методы принадлежат объектам, экземплярам класса В пределах одного класса может быть два и более методов с одинаковыми именами, но разным набором параметров (перегрузка метода) Класс-потомок может обеспечивать свою реализацию метода, уже реализованного в одном из родительских классов (переопределение метода) Упражнение Написать метод printFullName класса Student, который выводит полное ФИО студента.
  11. Xeon

    Циклы (Loops) java - урок 5

    Циклы в Java Есть два вида циклов в Java, for и while. For Цикл for состоит из трех секций: 1 for (int i = 0; i < 3; i++) {} Первая секция выполняется один раз, когда мы входим в цикл. В нашем примере здесь задается начальное значение переменной i. Вторая секция проверяет логическое условие, если оно возвращает true, выполняются операторы в цикле, если false, выход из цикла. Вторая секция в первый раз запускается сразу после первой секции, и выполняется каждый раз, пока условие верно, вызывая третью секцию. Третья секция — заключительный оператор, его действие выполняется каждый раз при выполнении цикла. В нашем примере это инкремент, который при каждом выполнении увеличивает значение переменной на единицу. Таким образом, цикл будет работать 3 раза. Вот порядок команд: 1 2 3 4 5 6 7 8 9 10 11 12 int i = 0; i < 3 // 0 < 3 = true // Inside of loop i++ // i is now 1 i < 3 // 1 < 3 = true // Inside of loop i++ // i is now 2 i < 3 // 2 < 3 = true // Inside of loop i++ // i is now 3 i < 3 // 3 < 3 = false // Loop is done... Мы можем опустить первую и третью секции цикла (как бы странно это ни выглядело), и цикл все еще будет работать: 1 for (;i < 5;) {} Для случаев, где нужно использовать цикл схожих повторяющихся действий, мы используем цикл while While Синтаксис похож на предыдущий: 1 while (condition) {} Условие будет работать впервые при вводе и каждый раз, когда вызывается цикл. Если условие возвратит false, то цикл не будет работать. Если мы хотим, чтобы цикл всегда выполнял по крайней мере одно действие, мы можем использовать do-while: 1 2 3 do { } while(condition); Не забудьте точку с запятой в конце. Foreach Другая версия for, это foreach. Но в Java решили не добавлять новое ключевое слово each. Ключевое слово, которое мы используем, все еще for, но когда мы хотим выполнить действия над элементами массива, делаем так: 1 2 3 4 int[] arr = {2, 0, 1, 3}; for (int el : arr) { System.out.println(el); } Это была короткая версия, эквивалентная следующей записи: 1 2 3 4 5 int[] arr = {1, 9, 9, 5}; for (int i = 0; i < arr.length; i++) { int el = arr; System.out.println(el); } Заметьте, что, если вы хотите использовать индекс элемента в цикле, Вы должны использовать более длинную версию и не можете использовать foreach. break and continue Эти два ключевых слова помогают нам управлять циклом из него. Оператор break останавливает цикл и переходит к оператору, следующему за ним: 1 2 3 4 5 6 7 8 9 10 11 12 int i; for (i = 0; i < 5; i++) { if (i >= 2) { break; } System.out.println("Yuhu"); } System.out.println(i); // Output: // Yuhu // Yuhu // 2 Оператор continue остановит текущую итерацию и переместится в следующую. Заметьте, что в цикле for действие в третьей секции будет выполнено при этом. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int i; for (i = 0; i < 5; i++) { if (i >= 3) { break; } System.out.println("Yuhu"); if (i >= 1) { continue; } System.out.println("Tata"); } System.out.println(i); // Output // Yuhu // Tata // Yuhu // Yuhu // 3 Упражнение Используя цикл, выведите на экран все четные числа из списка чисел в порядке получения. Не выводите числа, идущие после числа 237 в последовательности.
  12. Xeon

    Массивы java - урок 4

    Массив (англ. Array) это объект, хранящий в себе фиксированное количество значений одного типа. Другими словами, массив — это нумерованный набор переменных. Переменная в массиве называется элементом массива, а ее позиция в массиве задается индексом. Массивы в Java тоже представляют собой объекты. Они должны быть объявлены, а затем созданы. Чтобы объявить переменную, которая будет содержать массив целых чисел, мы используем следующий синтаксис: 1 int[] arr; Обратите внимание, размер не указан, так что мы еще не создали массив. 1 arr = new int[10]; Теперь мы создали новый массив размером 10. Мы можем проверить размер массива, выведя на экран его длину: 1 System.out.println(arr.length); Так же мы можем получить доступ к массиву и установить значения: 1 2 arr[0] = 4; arr[1] = arr[0] + 5; Счет элементов массива в Java начинается с нуля, то есть доступ к первому элементу можно получить по индексу 0 (например, arr[0]). Кроме того, как показано на примере, массив размером 5 будет заканчиваться на индексе 4, так как счет начинается с нуля. 1 2 int[] arr = new int[5] arr[4] = 4; // Получение доступа и присвоение значения последнему элементу Есть также возможность создать массив с указанием значений в одну строку: 1 int[] arr = {1, 2, 3, 4, 5}; Кстати, если вы попытаетесь вывести массив целиком на экран, получите что-то в этом роде: [I@f7e6a96. Для вывода всех значений массива используйте метод Arrays.toString(), преобразующий массив в строку. 1 System.out.println(Arrays.toString(arr)); Или напишите цикл, выводящий последовательно элементы массива. 1 2 3 for(int i=0; i<arr.length; i++) { System.out.println(arr); }
  13. Условные операторы в Java Java использует булевские (логические) переменные, чтобы оценивать условия. Значение true или false возвратится, после того как выражение будет оценено. Например: 1 2 3 4 5 int a = 4; boolean b = a == 4; if (b) { System.out.println("It's true!"); } Конечно, мы обычно не присваиваем условное выражение булевской переменной, мы просто используем короткую версию: 1 2 3 4 int a = 4; if (a == 4) { System.out.println("Ohhh! So a is 4!"); } Логические операторы Есть не так много операторов, которые можно использовать в условиях. Вот они: 1 2 3 4 5 6 7 8 9 10 int a = 4; int b = 5; boolean result; result = a < b; // истина result = a > b; // ложь result = a <= 4; // меньше или равно - истина result = b >= 6; // больше или равно - ложь result = a == b; // равно - ложь result = a != b; // неравно - истина result = a > b || a < b; // логическое ИЛИ - истина result = 3 < a && a < 6; // логическое И - истина result = !result; // Логическое НЕ - ложь Оператор if — else Синтаксис оператора if — else довольно прост: 1 2 3 if (a == b) { // Тело метода. Выполняется если a и b равны. } Так же мы можем добавить еще одно выражение, на случай, если условие не выполняется: 1 2 3 4 5 if (a == b) { // Мы уже знаем эту часть } else { // a и b не равны... } Если тело метода можно разместить в одну строку, можно не использовать { } 1 2 if (a == b) System.out.println("Yeah!"); else System.out.println("Ohhh..."); Или 1 2 3 4 if (a == b) System.out.println("Another line Wow!"); else System.out.println("Double rainbow!"); Несмотря на то, что такой метод мог бы сделать ваш код короче, мы строго рекомендуем новичкам не использовать короткую версию условного оператора Другая сторона if Есть еще один способ записать if — else в одну строку — с помощью оператора ? : 1 2 3 4 5 6 7 8 9 10 int a = 4; int result = a == 4 ? 1 : 8; // result будет равен 1 // Или обычная форма записи: int result; if (a == 4) { result = 1; } else { result = 8; } Опять же, мы не рекомендуем новичкам использовать эту версию if. Операторы == и equals Оператор == работает немного по-другому на объектах, нежели на примитивах. Когда вы используем объекты и хотите проверить, равны ли они, оператор == скажет что они равны, только если объекты одинаковы, но если вы хотите проверить их на логическое соответствие, используйте метод equals. Например: 1 2 3 4 5 6 7 String a = new String("Wow"); String b = new String("Wow"); String sameA = a; boolean r1 = a == b; // Ложь, так как a и b не один и тот же объект boolean r2 = a.equals(b); // Истина, так как a и b логически равны boolean r3 = a == sameA; // Истина, так как a и sameA действительно один и тот же объект
  14. Примитивные типы в Java Несмотря на то, что язык Java объектно-ориентирован, не все типы — объекты. Существуют так называемые примитивы (primitives). Вот список всех примитивов в Java: byte (число, 1 байт) short (число, 2 байта) int (число, 4 байта) long (число, 8 байтов) float (число с плавающей точкой, 4 байта) double (число с плавающей точкой, 8 байтов) char (символ, 2 байта) boolean (true (истина) или false (ложь), 1 байт) Java — строго типизированный язык, это означает, что мы должны объявить переменные , прежде чем будем их использовать. Числа в Java Чтобы объявить и присвоить число используйте следующий синтаксис: 1 2 int myNumber; myNumber = 5; = это оператор присваивания. Вы можете объединить эти операции: 1 int myNumber = 5; Чтобы объявить число с плавающей точкой, используйте следующий синтаксис: 1 2 double d = 4.5; d = 3.0; Если вы хотите использовать float, то: 1 float f = (float) 4.5; Или: 1 float f = 4.5f (f — более короткий способ объявить float) Символы и строки в Java В Java символ — свой собственный тип, и это не просто число. Синтаксис: 1 char c = 'g'; String — не примитив. Это реальный тип. Вот несколько способов использования строки: Создание строки с помощью конструктора 1 String s1 = new String("Who let the dogs out?"); С помощью двойных кавычек (» «) 1 String s2 = "Who who who who!"; В Java присутсвует конкатенация (объединение) строк при помощи оператора +. 1 String s3 = s1 + s2; В Java нет перегрузки операторов! Оператор + определен только для строк, вы никогда не увидите его с другими объектами, только с примитивами. 1 2 int num = 5; String s = "I have " + num + " cookies"; Заметьте, что кавычки с примитивами не используются. Логический тип boolean в Java Каждый оператор сравнения в Java возвращает булевскую переменную (boolean), которая может принять только два значения: true (истина) или false (ложь). 1 2 3 4 5 6 7 8 boolean b = false; b = true; boolean toBe = false; b = toBe || !toBe; if (b) { System.out.println(toBe); } Оператор || это логическое «или». А например, такой код не будет работать по причине несовместимости типов: 1 2 3 4 5 int children = 0; b = children; // Не будет работать, требуется boolean, а найден int if (children) { // Не будет работать, требуется boolean, а найден int // Не будет работать, требуется boolean, а найден int }
  15. Java — объектно-ориентированный язык программирования. В нем существуют классы (class) и объекты (object). Объекты в Java представляют собой экземпляры класса. Например, модель android смартфона Samsung Galaxy s6 вообще — это класс, а экземпляр Galaxy s6, котрый вы закажете на Ebay и он придет к вам по почте — это конкретный объект, экземпляр класса, и вы можете делать с ним все, что хотите. ООП — объектно-ориентированное программирование — одно из основных направлений в создании программ. Мы еще вернемся к этой теме в последующих уроках. Среда разработки на Java Для написания кода и работы с примерами требуется среда разработки. Это набор программ, утилит и других инструментов, без которых сейчас не обходится ни один программист. Среда разработки делает процесс программирования более быстрым и понятным благодаря автоматической проверке правильности кода и подсказкам. Рекомендуем для этого курса использовать среду разработки IntelliJ IDEA — скачайте бесплатную версию Community Edition и установите ее. Первая программа на Java Запустите среду разработки и нажмите Создать проект (Create new project). Далее в окне создания проекта слева вверху выберите язык проекта Java и нажмите внизу кнопку Next. Дальнейшие настройки можно оставить по умолчанию. На последнем экране можно изменить имя и местоположение проекта, и создать проект нажатием кнопки Finish. Далее откроется пустое окно проекта в среде разработки. Слева откройте вкладку Project дерева проекта. Раскройте проект и найдите внутри папку src. В этой папке всегда размещается код, написанный программистами. Пока она пуста, веди мы еще ничего не писали. Давайте это исправим. Правой клавишей мыши вызовите контекстное меню папки src и выберите New> Java Class. Далее укажите имя класса — Main. Это будет главный класс нашего проекта, в нем мы будем писать наш код. Давайте напишем программу Hello World, которая просто выводит на экран надпись «Hello, World!». 1 2 3 4 5 public class Main { public static void main(String[] args) { System.out.println("Hello, World!"); } } Первая строка объявляет класс под названием Main. 1 public class Main { В Java каждая строка кода, которая может выполняться должна находиться внутри класса. Эта строка объявляет класс Main, модификатор доступа public означает что класс общедоступен и любой другой класс может получить доступ к нему. На данный момент это не важно, так что не волнуйтесь. Для начала просто напишем наш код в классе Main, а про объекты поговорим позже. Обратите внимание, что, когда мы объявляем общедоступный класс (public), мы должны объявить его в файле с тем же именем (Main.java), иначе мы получим ошибку при компиляции. Следующая строка: 1 public static void main(String[] args) { Это точка входа нашей Java программы. Метод main должен иметь точно такую же сигнатуру, как показано, иначе программа не будет работать. public снова же означает, что метод общедоступен static означает что вы можете выполнить этот метод без создания экземпляра класса Main void означает, что метод не возвращает никакого значения main — имя метода При помощи этой строки мы выводим на экран «Hello, World!». 1 System.out.println("Hello, World!"); Это массив строк. Мы будем использовать его в следующем уроке, так что не волнуйтесь, если сейчас вы не все понимаете. Пока потренируйтесь выводить различный текст, ибо только практика сделает из вас программиста!
  16. В C++ можно определять пользовательские операторы для собственных типов данных. Оператор определяется, как обычная функция-член класса, только после определения возвращаемого типа ставится ключевое слово operator. Пример определения оператора сложения: int operator+ (int value) { return number + value; } Оператор может быть унарным или бинарным. Унарный оператор не принимает никаких аргументов. Например, оператор отрицания — «!». Бинарный оператор принимает дополнительный параметр. Например, в случае со сложением, принимается второе слагаемое. Чтобы прояснить картину, попробуем написать класс simple_fraction, который будет описывать простую дробь с целыми числителем и знаменателем. И определим операторы сложения, вычитания, умножения и деления для этого класса. /* * Класс, описывающий простую дробь */ class simple_fraction { public: simple_fraction(int numerator, int denominator) { if (denominator == 0) // Ошибка деления на ноль throw std::runtime_error("zero division error"); this->numerator = numerator; this->denominator = denominator; } // Определение основных математических операций для простой дроби double operator+ (int val) { return number() + val; } // Сложение double operator- (int val) { return number() - val; } // Вычитание double operator* (int val) { return number() * val; } // Умножение double operator/ (int val) // Деление { if (val == 0) { throw std::runtime_error("zero division error"); } return number() / val; } // Получение значения дроби в виде обычного double-числа double number() { return numerator / (double) denominator; } private: int numerator; // Числитель int denominator; // Знаменатель }; Для операции деления, мы также сделали проверку деления на ноль. Пример использования класса simple_fraction: // Простая дробь 2/3 simple_fraction fr(2, 3); double sum = fr + 10; // сумма double diff = fr - 10; // разность double factor = fr * 10; // произведение double div = fr / 10; // частное Операторы можно перегружать так же, как и обычные функции-члены класса. Например, можно перегрузить оператор сложения для двух простых дробей, который будет возвращать новую простую дробь. Тогда, нам придется привести дроби к общему знаменателю и вернуть другую простую дробь. Задание: усовершенствуйте класс simple_fraction. Перегрузите операторы сложения, вычитания, умножения и деления так, чтобы можно было производить операции над двумя простыми дробями и получать новую простую дробь. Реализуйте приведение двух дробей к общему знаменателю. Пример использования будущего класса: simple_fraction fr1(2, 3); simple_fraction fr2(3, 4); // 2/3 + 3/4 — это 17/12 simple_fraction sum = fr1 + fr2;
  17. Методы класса можно перегружать также, как и обычные функции. Особенно это удобно, когда нужно сделать несколько конструкторов, которые будут принимать разные параметры. Например, попробуем создать основу класса decimal, который реализует длинную арифметику для чисел произвольной точности. В таких случаях, обычно хранят число внутри строки, а логика математических операций реализуется через написание соответствующих операторов класса. Сделаем так, чтобы в конструктор этого класса можно было передавать и строку и число типа double. // Передача в конструктор строки decimal num1("10000000.999999"); // Передача числа decimal num2(10000.0); Для того, чтобы класс поддерживал такую универсальность, мы сделаем два разных конструктора для строки и числа: /** * Представим, что этот класс реализует длинную арифметику для чисел любой * точности */ class decimal { public: /* * Конструктор, принимающий в качестве аргумента строку, содержащую число */ decimal(string number) { clog << "First constructor called\n"; } /** * Конструктор принимат число типа double */ decimal(double number) { clog << "Second constructor called\n"; } private: string number; }; При передаче строки будет вызван первый конструктор, а при передаче числа — второй. Полный текст программы: #include <iostream> #include <string> using namespace std; /** * Представим, что этот класс реализует длинную арифметику для чисел любой точности */ class decimal { public: /* * Конструктор, принимающий в качестве аргумента строку, * содержащее число */ decimal(string number) { clog << "First constructor called\n"; this->number = number; } /** * Конструктор принимает число типа double */ decimal(double number) { /** * преобразуем double в строку с максимально возможной точностью * и записываем полученное значение в this->number */ clog << "Second constructor called\n"; } private: string number; }; int main() { // Будет вызван первый конструктор decimal num1("10000000.999999"); // Будет вызван второй конструктор decimal num2(10000.0); cin.get(); return 0; } Конечно, наш класс пока ничего не делает, потому что реализация длинной арифметики выходит за рамки данной статьи. Но на этом примере можно понять, когда может быть полезно использовать перегрузку методов класса. Задание: попробуйте написать класс student, в конструктор которого можно будет передавать либо его имя и фамилию, либо имя и год рождения. При передаче года рождения, должен считаться примерный возраст студента. Пример использования класса: student stud1("Иван", "Иванов"); student stud2("Иван", 1990);
  18. Xeon

    Перегрузка функций в C++

    Перегрузка функций в C++ используется, когда нужно сделать одно и то же действие с разными типами данных. Для примера, создадим простую функцию max, которая будет определять максимальное из двух целых чисел. /* Функция max для целых чисел */ int max(int num1, int num2) { if (num1 > num2) return num1; return num2; } В эту функцию мы можем передавать только целочисленные параметры. Для того, чтобы сделать аналог этой функции для чисел с плавающей запятой, выполним перегрузку этой функции: /* Функция max для чисел с плавающей запятой */ double max(double num1, double num2) { if (num1 > num2) return num1; return num2; } Теперь, когда мы будет вызывать функцию max с целыми параметрами, то вызовется первая функция. А если с дробными — то вторая. Например: // Здесь будет использоваться первый вариант функции max int imax = max(1, 10); // А здесь - второй double dmax = max(1.0, 20.0);
  19. Наследование позволяет избежать дублирования лишнего кода при написании классов. Пусть в базе данных ВУЗа должна храниться информация о всех студентах и преподавателях. Представлять все данные в одном классе не получится, поскольку для преподавателей нам понадобится хранить данные, которые для студента не применимы, и наоборот. Создание базового класса Для решения этой задачи создадим базовый класс human, который будет описывать модель человека. В нем будут храниться имя, фамилия и отчество. Создайте файл human.h: // human.h #ifndef HUMAN_H_INCLUDED #define HUMAN_H_INCLUDED #include <string> #include <sstream> class human { public: // Конструктор класса human human(std::string last_name, std::string name, std::string second_name) { this->last_name = last_name; this->name = name; this->second_name = second_name; } // Получение ФИО человека std::string get_full_name() { std::ostringstream full_name; full_name << this->last_name << " " << this->name << " " << this->second_name; return full_name.str(); } private: std::string name; // имя std::string last_name; // фамилия std::string second_name; // отчество }; #endif // HUMAN_H_INCLUDED Наследование от базового класса Теперь создайте новый класс student, который будет наследником класса human. Поместите его в файл student.h. // student.h #ifndef STUDENT_H_INCLUDED #define STUDENT_H_INCLUDED #include "human.h" #include <string> #include <vector> class student : public human { public: // Конструктор класса Student student( std::string last_name, std::string name, std::string second_name, std::vector<int> scores ) : human( last_name, name, second_name ) { this->scores = scores; } // Получение среднего балла студента float get_average_score() { // Общее количество оценок unsigned int count_scores = this->scores.size(); // Сумма всех оценок студента unsigned int sum_scores = 0; // Средний балл float average_score; for (unsigned int i = 0; i < count_scores; ++i) { sum_scores += this->scores[i]; } average_score = (float) sum_scores / (float) count_scores; return average_score; } private: // Оценки студента std::vector<int> scores; }; #endif // STUDENT_H_INCLUDED Функция get_average_score вычисляет среднее арифметическое всех оценок студента. Все публичные свойства и методы класса human будут доступны в классе student. Конструктор базового класса Для того, чтобы инициализировать конструктор родительского класса (в нашем случае — это сохранение имени, фамилии и отчества ученика), используется следующий синтаксис: // Конструктор класса Student student( // аргументы конструктора текущего класса ) : human( // инициализация конструктора родительского класса ) { // инициализация конструктора текущего класса } В конструктор класса human мы передаем инициалы человека, которые сохраняются в экземпляре класса. Для класса students, нам необходимо задать еще и список оценок студента. Поэтому конструктор students принимает все аргументы конструктора базового класса, а также дополнительные аргументы для расширения функционала: // Конструктор класса Student student( std::string last_name, std::string name, std::string second_name, std::vector<int> scores ) : human( last_name, name, second_name ) { this->scores = scores; } Список оценок студента хранится в векторе. Создание объекта класса student Реализуем пользовательский интерфейс для работы с классом student. // main.cpp #include <iostream> #include <vector> #include "human.h" #include "student.h" int main(int argc, char* argv[]) { // Оценки студента std::vector<int> scores; // Добавление оценок студента в вектор scores.push_back(5); scores.push_back(3); scores.push_back(2); scores.push_back(2); scores.push_back(5); scores.push_back(3); scores.push_back(3); scores.push_back(3); scores.push_back(3); // Создание объекта класса student student *stud = new student("Петров", "Иван", "Алексеевич", scores); // Вывод полного имени студента (используется унаследованный метод класса human) std::cout << stud->get_full_name() << std::endl; // Вывод среднего балла студента std::cout << "Средний балл: " << stud->get_average_score() << std::endl; return 0; } В этом примере мы написали программу, которая создает объект класса student, сохраняя в нем его имя, фамилию, отчество и список оценок. После инициализации объекта, происходит вывод полного имени студента с помощью функции get_full_name. Эта функция была унаследована от базового класса human. Затем программа вычислияет средний балл студента и выводит его на экран. Этим занимается функция get_average_score, которую мы описали внутри класса student. Мы реализовали часть функционала для нашей базы данных института (я конечно утрирую, когда оперирую столь серьезными высказываниями про настоящую базу данных Создание класса-наследника teacher Нужно создать еще один класс, в котором будут храниться данные преподавателей. Дадим ему название — teacher. Как вы уже поняли, мы не будем описывать все методы этого класса с нуля, а просто унаследуем его от класса human. Тогда, не нужно будет реализовывать хранение имени, фамилии и отчества препода. Это уже есть в базовом классе human. Создайте файл teacher.h: // teacher.h #ifndef TEACHER_H_INCLUDED #define TEACHER_H_INCLUDED #include "human.h" #include <string> class teacher : public human { // Конструктор класса teacher public: teacher( std::string last_name, std::string name, std::string second_name, // Количество учебных часов за семетр у преподавателя unsigned int work_time ) : human( last_name, name, second_name ) { this->work_time = work_time; } // Получение количества учебных часов unsigned int get_work_time() { return this->work_time; } private: // Учебные часы unsigned int work_time; }; #endif // TEACHER_H_INCLUDED У класса teacher появилось новое свойство — количество учебных часов, отведенное преподавателю на единицу времени (семестр). Весь остальной функционал наследуется от базового класса human. Если бы мы писали все с нуля, то одинакового кода бы получилось в разы больше, и его поддержка усложнилась бы на порядок. Создание объекта класса teacher Изменим содержимое файла main.cpp, чтобы проверить работу класса teacher. #include <iostream> #include "human.h" #include "teacher.h" int main(int argc, char* argv[]) { // Количество учебных часов преподавателя unsigned int teacher_work_time = 40; teacher *tch = new teacher("Васильков", "Петр", "Сергеевич", teacher_work_time); std::cout << tch->get_full_name() << std::endl; std::cout << "Количество часов: " << tch->get_work_time() << std::endl; return 0; } Если сборка программы прошла без ошибок, то результат работы программы будет таким: Можно таким же образом создать класс, в котором будут храниться данные обслуживающего персонала или руководящего состава. Наследование используют, когда у каждой группы объектов есть общие параметры, но для каждой из этих групп нужно хранить более кастомные данные. Также, мы можем создать класс, который будет описывыть студента заочной формы обучения. Его мы унаследовали бы от класса student, добавив какие-либо дополнительные данные. В класс human можно добавить еще больше свойств, которые будут описывать данные, имеющиеся у любого человека. Например, номер паспорта, дату рождения, прописку и место проживания. Подобный подход позволяет в разы уменьшить дублирование кода в реальных проектах, и упросить его поддержку. Когда нужно использовать конструктор Если у класса много свойств — их совсем не обязательно задавать в конструкторе. Для сохранения отдельных свойств класса используют set-функции. Например, для сохранения номера паспорта, можно создать публичный метод set_passport_number(std::string number), который будет принимать значение свойства и сохранять его в объекте, через переменную this.
  20. Xeon

    Векторы в C++ — урок 11

    Разработчики языка рекомендуют в использовать именно vector вместо ручного выделения памяти для массива. Это позволяет избежать утечек памяти и облегчает работу программисту. Пример создания вектора #include <iostream> #include <vector> int main() { // Вектор из 10 элементов типа int std::vector<int> v1(10); // Вектор из элементов типа float // С неопределенным размером std::vector<float> v2; // Вектор, состоящий из 10 элементов типа int // По умолчанию все элементы заполняются нулями std::vector<int> v3(10, 0); return 0; } Управление элементами вектора Создадим вектор, в котором будет содержаться произвольное количество фамилий студентов. #include <iostream> #include <vector> #include <string> int main() { // Поддержка кириллицы в консоли Windows setlocale(LC_ALL, ""); // Создание вектора из строк std::vector<std::string> students; // Буфер для ввода фамилии студента std::string buffer = ""; std::cout << "Вводите фамилии студентов. " << "По окончание ввода введите пустую строку" << std::endl; do { std::getline(std::cin, buffer); if (buffer.size() > 0) { // Добавление элемента в конец вектора students.push_back(buffer); } } while (buffer != ""); // Сохраняем количество элементов вектора unsigned int vector_size = students.size(); // Вывод заполненного вектора на экран std::cout << "Ваш вектор." << std::endl; for (int i = 0; i < vector_size; i++) { std::cout << students[i] << std::endl; } return 0; } Результат работы программы: Методы класса vector Для добавления нового элемента в конец вектора используется метод push_back(). Количество элементов определяется методом size(). Для доступа к элементам вектора можно использовать квадратные скобки [], также, как и для обычных массивов. pop_back() — удалить последний элемент clear() — удалить все элементы вектора empty() — проверить вектор на пустоту
  21. В этом уроке мы более детально познакомимся с конструкторами и деструкторами класса, а также научимся работать с файлами в потоковом режиме, с помощью библиотеки fstream. Продолжим написание программы учета оценок. Конструктор Students Добавим в класс Students конструктор, который будет принимать имя и фамилию ученика, и сохранять эти значения в соответствующих переменных класса. // Конструктор Students Students::Students(std::string name, std::string last_name) { Students::set_name(name); Students::set_last_name(last_name); } При создании нового объекта, мы должны передать конструктору имя и фамилию студента. Иначе компиляция программы завершится с ошибкой. std::string name = "Василий"; std::string last_name = "Пупкин"; Students *student = new Students(name, last_name); Теперь добавим прототип конструктора в файл students.h. /* students.h */ #pragma once /* Защита от двойного подключения заголовочного файла */ #include <string> class Students { public: // Конструктор класса Students Students(std::string, std::string); // Установка имени студента void set_name(std::string); // Получение имени студента std::string get_name(); // Установка фамилии студента void set_last_name(std::string); // Получение фамилии студента std::string get_last_name(); // Установка промежуточных оценок void set_scores(int []); // Установка среднего балла void set_average_ball(float); // Получение среднего балла float get_average_ball(); private: // Промежуточные оценки int scores[5]; // Средний балл float average_ball; // Имя std::string name; // Фамилия std::string last_name; }; В файле students.cpp определим сам конструктор. /* students.cpp */ #include <string> #include <fstream> #include "students.h" // Конструктор Students Students::Students(std::string name, std::string last_name) { Students::set_name(name); Students::set_last_name(last_name); } // Установка имени студента void Students::set_name(std::string student_name) { Students::name = student_name; } // Получение имени студента std::string Students::get_name() { return Students::name; } // Установка фамилии студента void Students::set_last_name(std::string student_last_name) { Students::last_name = student_last_name; } // Получение фамилии студента std::string Students::get_last_name() { return Students::last_name; } // Установка промежуточных оценок void Students::set_scores(int scores[]) { int sum = 0; for (int i = 0; i < 5; ++i) { Students::scores[i] = scores[i]; sum += scores[i]; } } // Установка среднего балла void Students::set_average_ball(float ball) { Students::average_ball = ball; } // Получение среднего балла float Students::get_average_ball() { return Students::average_ball; } В main() мы принимаем от пользователя имя и фамилию ученика, и сохраняем их во временных локальных переменных. После этого создаем новый объект класса Students, передавая его конструктору эти переменные. /* main.cpp */ #include <iostream> #include "students.h" int main(int argc, char *argv[]) { // Локальная переменная, хранящая имя ученика std::string name; // И его фамилию std::string last_name; // Ввод имени std::cout << "Name: "; getline(std::cin, name); // И фамилии std::cout << "Last name: "; getline(std::cin, last_name); // Передача параметров конструктору Students *student = new Students(name, last_name); // Оценки int scores[5]; // Сумма всех оценок int sum = 0; // Ввод промежуточных оценок for (int i = 0; i < 5; ++i) { std::cout << "Score " << i+1 << ": "; std::cin >> scores[i]; // суммирование sum += scores[i]; } // Сохраняем промежуточные оценки в объект класса Student student->set_scores(scores); // Считаем средний балл float average_ball = sum / 5.0; // Сохраняем средний балл student->set_average_ball(average_ball); // Выводим данные по студенту std::cout << "Average ball for " << student->get_name() << " " << student->get_last_name() << " is " << student->get_average_ball() << std::endl; // Удаление объекта student из памяти delete student; return 0; } Сохранение оценок в файл Чтобы после завершения работы с программой, все данные сохранялись, мы будем записывать их в текстовый файл. Оценки каждого студента будут находится в отдельной строке. Имя и фамилии будут разделяться пробелами. После имени и фамилии ученика ставится еще один пробел, а затем перечисляются все его оценки. Пример файла с оценками: Василий Пупкин 5 4 5 3 3 Иван Сидоров 5 5 3 4 5 Андрей Иванов 5 3 3 3 3 Для работы с файлами мы воспользуемся библиотекой fstream, которая подключается в заголовочном файле с таким же именем. #include <fstream> // Запись данных о студенте в файл void Students::save() { std::ofstream fout("students.txt", std::ios::app); fout << Students::get_name() << " " << Students::get_last_name() << " "; for (int i = 0; i < 5; ++i) { fout << Students::scores[i] << " "; } fout << std::endl; fout.close(); } Переменная fout — это объект класса ofstream, который находится внутри библиотеки fstream. Класс ofstream используется для записи каких-либо данных во внешний файл. Кстати, у него тоже есть конструктор. Он принимает в качестве параметров имя выходного файла и режим записи. В данном случае, мы используем режим добавления — std::ios:app (англ. append). После завершения работы с файлом, необходимо вызвать метод close() для того, чтобы закрыть файловый дескриптор. Чтобы сохранить оценки студента, мы будем вызывать только что созданный метод save(). Students student = new Students("Василий", "Пупкин"); student->save(); Деструктор Students Логично было бы сохранять все оценки после того, как работа со студентом закончена. Для этого создадим деструктор класса Students, который будет вызывать метод save() перед уничтожением объекта. // Деструктор Students Students::~Students() { Students::save(); } Добавим прототипы деструктора и метода save() в students.h. /* students.h */ #pragma once /* Защита от двойного подключения заголовочного файла */ #include <string> class Students { public: // Запись данных о студенте в файл void save(); // Деструктор класса Students ~Students(); // Конструктор класса Students Students(std::string, std::string); // Установка имени студента void set_name(std::string); // Получение имени студента std::string get_name(); // Установка фамилии студента void set_last_name(std::string); // Получение фамилии студента std::string get_last_name(); // Установка промежуточных оценок void set_scores(int []); // Получение массива с промежуточными оценками int *get_scores(); // Получение строки с промежуточными оценками std::string get_scores_str(char); // Установка среднего балла void set_average_ball(float); // Получение среднего балла float get_average_ball(); private: // Промежуточные оценки int scores[5]; // Средний балл float average_ball; // Имя std::string name; // Фамилия std::string last_name; }; И определим эти функции в students.cpp. /* students.cpp */ #include <string> #include <fstream> #include "students.h" // Деструктор Students Students::~Students() { Students::save(); } // Запись данных о студенте в файл void Students::save() { std::ofstream fout("students.txt", std::ios::app); fout << Students::get_name() << " " << Students::get_last_name() << " "; for (int i = 0; i < 5; ++i) { fout << Students::scores[i] << " "; } fout << std::endl; fout.close(); } // Конструктор Students Students::Students(std::string name, std::string last_name) { Students::set_name(name); Students::set_last_name(last_name); } // Установка имени студента void Students::set_name(std::string student_name) { Students::name = student_name; } // Получение имени студента std::string Students::get_name() { return Students::name; } // Установка фамилии студента void Students::set_last_name(std::string student_last_name) { Students::last_name = student_last_name; } // Получение фамилии студента std::string Students::get_last_name() { return Students::last_name; } // Установка промежуточных оценок void Students::set_scores(int scores[]) { int sum = 0; for (int i = 0; i < 5; ++i) { Students::scores[i] = scores[i]; sum += scores[i]; } } // Получение массива с промежуточными оценками int *Students::get_scores() { return Students::scores; } // Установка среднего балла void Students::set_average_ball(float ball) { Students::average_ball = ball; } // Получение среднего балла float Students::get_average_ball() { return Students::average_ball; } Содержимое main.cpp останется прежним. Скомпилируйте и запустите программу. Перед тем, как приложение завершит свою работу, в директории с исполняемым файлом будет создан новый текстовый файл с оценками — students.txt.
  22. Xeon

    Классы в C++ — урок 9

    Практически любой материальный предмет можно представить в виде совокупности объектов, из которых он состоит. Допустим, что нам нужно написать программу для учета успеваемости студентов. Можно представить группу студентов, как класс языка C++. Назовем его Students. class Students { // Имя студента std::string name; // Фамилия std::string last_name; // Пять промежуточных оценок студента int scores[5]; // Итоговая оценка за семестр float average_ball; }; Основные понятия Классы в программировании состоят из свойств и методов. Свойства — это любые данные, которыми можно характеризовать объект класса. В нашем случае, объектом класса является студент, а его свойствами — имя, фамилия, оценки и средний балл. У каждого студента есть имя — name и фамилия last_name . Также, у него есть промежуточные оценки за весь семестр. Эти оценки мы будем записывать в целочисленный массив из пяти элементов. После того, как все пять оценок будут проставлены, определим средний балл успеваемости студента за весь семестр — свойство average_ball. Методы — это функции, которые могут выполнять какие-либо действия над данными (свойствами) класса. Добавим в наш класс функцию calculate_average_ball(), которая будет определять средний балл успеваемости ученика. Методы класса — это его функции. Свойства класса — его переменные. class Students { public: // Функция, считающая средний балл void calculate_average_ball() { int sum = 0; // Сумма всех оценок for (int i = 0; i < 5; ++i) { sum += scores[i]; } // считаем среднее арифметическое average_ball = sum / 5.0; } // Имя студента std::string name; // Фамилия std::string last_name; // Пять промежуточных оценок студента int scores[5]; private: // Итоговая оценка за семестр float average_ball; }; Функция calculate_average_ball() просто делит сумму всех промежуточных оценок на их количество. Модификаторы доступа public и private Все свойства и методы классов имеют права доступа. По умолчанию, все содержимое класса является доступным для чтения и записи только для него самого. Для того, чтобы разрешить доступ к данным класса извне, используют модификатор доступа public. Все функции и переменные, которые находятся после модификатора public, становятся доступными из всех частей программы. Закрытые данные класса размещаются после модификатора доступа private. Если отсутствует модификатор public, то все функции и переменные, по умолчанию являются закрытыми (как в первом примере). Обычно, приватными делают все свойства класса, а публичными — его методы. Все действия с закрытыми свойствами класса реализуются через его методы. Рассмотрим следующий код. class Students { public: // Установка среднего балла void set_average_ball(float ball) { average_ball = ball; } // Получение среднего балла float get_average_ball() { return average_ball; } std::string name; std::string last_name; int scores[5]; private: float average_ball; }; Мы не можем напрямую обращаться к закрытым данными класса. Работать с этими данными можно только посредством методов этого класса. В примере выше, мы используем функцию get_average_ball() для получения средней оценки студента, и set_average_ball() для выставления этой оценки. Функция set_average_ball() принимает средний балл в качестве параметра и присваивает его значение закрытой переменной average_ball. Функция get_average_ball() просто возвращает значение этой переменной. Программа учета успеваемости студентов Создадим программу, которая будет заниматься учетом успеваемости студентов в группе. Создайте заголовочный файл students.h, в котором будет находиться класс Students. /* students.h */ #include <string> class Students { public: // Установка имени студента void set_name(std::string student_name) { name = student_name; } // Получение имени студента std::string get_name() { return name; } // Установка фамилии студента void set_last_name(std::string student_last_name) { last_name = student_last_name; } // Получение фамилии студента std::string get_last_name() { return last_name; } // Установка промежуточных оценок void set_scores(int student_scores[]) { for (int i = 0; i < 5; ++i) { scores[i] = student_scores[i]; } } // Установка среднего балла void set_average_ball(float ball) { average_ball = ball; } // Получение среднего балла float get_average_ball() { return average_ball; } private: // Промежуточные оценки int scores[5]; // Средний балл float average_ball; // Имя std::string name; // Фамилия std::string last_name; }; Мы добавили в наш класс новые методы, а также сделали приватными все его свойства. Функция set_name() сохраняет имя студента в переменной name, а get_name() возвращает значение этой переменной. Принцип работы функций set_last_name() и get_last_name() аналогичен. Функция set_scores() принимает массив с промежуточными оценками и сохраняет их в приватную переменную int scores[5]. Теперь создайте файл main.cpp со следующим содержимым. /* main.cpp */ #include <iostream> #include "students.h" int main() { // Создание объекта класса Student Students student; std::string name; std::string last_name; // Ввод имени с клавиатуры std::cout << "Name: "; getline(std::cin, name); // Ввод фамилии std::cout << "Last name: "; getline(std::cin, last_name); // Сохранение имени и фамилии в объект класса Students student.set_name(name); student.set_last_name(last_name); // Оценки int scores[5]; // Сумма всех оценок int sum = 0; // Ввод промежуточных оценок for (int i = 0; i < 5; ++i) { std::cout << "Score " << i+1 << ": "; std::cin >> scores[i]; // суммирование sum += scores[i]; } // Сохраняем промежуточные оценки в объект класса Student student.set_scores(scores); // Считаем средний балл float average_ball = sum / 5.0; // Сохраняем средний балл в объект класса Students student.set_average_ball(average_ball); // Выводим данные по студенту std::cout << "Average ball for " << student.get_name() << " " << student.get_last_name() << " is " << student.get_average_ball() << std::endl; return 0; } В самом начале программы создается объект класса Students. Дело в том, что сам класс является только описанием его объекта. Класс Students является описанием любого из студентов, у которого есть имя, фамилия и возможность получения оценок. Объект класса Students характеризует конкретного студента. Если мы захотим выставить оценки всем ученикам в группе, то будем создавать новый объект для каждого из них. Использование классов очень хорошо подходит для описания объектов реального мира. После создания объекта student, мы вводим с клавиатуры фамилию, имя и промежуточные оценки для конкретного ученика. Пускай это будет Вася Пупкин, у которого есть пять оценок за семестр — две тройки, две четверки и одна пятерка. Введенные данные мы передаем set-функциям, которые присваивают их закрытым переменным класса. После того, как были введены промежуточные оценки, мы высчитываем средний балл на основе этих оценок, а затем сохраняем это значение в закрытом свойстве average_ball, с помощью функции set_average_ball(). Скомпилируйте и запустите программу. Отделение данных от логики Вынесем реализацию всех методов класса в отдельный файл students.cpp. /* students.cpp */ #include <string> #include "students.h" // Установка имени студента void Students::set_name(std::string student_name) { Students::name = student_name; } // Получение имени студента std::string Students::get_name() { return Students::name; } // Установка фамилии студента void Students::set_last_name(std::string student_last_name) { Students::last_name = student_last_name; } // Получение фамилии студента std::string Students::get_last_name() { return Students::last_name; } // Установка промежуточных оценок void Students::set_scores(int scores[]) { for (int i = 0; i < 5; ++i) { Students::scores[i] = scores[i]; } } // Установка среднего балла void Students::set_average_ball(float ball) { Students::average_ball = ball; } // Получение среднего балла float Students::get_average_ball() { return Students::average_ball; } А в заголовочном файле students.h оставим только прототипы этих методов. /* students.h */ #pragma once /* Защита от двойного подключения заголовочного файла */ #include <string> class Students { public: // Установка имени студента void set_name(std::string); // Получение имени студента std::string get_name(); // Установка фамилии студента void set_last_name(std::string); // Получение фамилии студента std::string get_last_name(); // Установка промежуточных оценок void set_scores(int []); // Установка среднего балла void set_average_ball(float); // Получение среднего балла float get_average_ball(); private: // Промежуточные оценки int scores[5]; // Средний балл float average_ball; // Имя std::string name; // Фамилия std::string last_name; }; Такой подход называется абстракцией данных — одного из фундаментальных принципов объектно-ориентированного программирования. К примеру, если кто-то другой захочет использовать наш класс в своем коде, ему не обязательно знать, как именно высчитывается средний балл. Он просто будет использовать функцию calculate_average_ball() из второго примера, не вникая в алгоритм ее работы. Над крупными проектами обычно работает несколько программистов. Каждый из них занимается написанием определенной части продукта. В таких масштабах кода, одному человеку практически нереально запомнить, как работает каждая из внутренних функций проекта. В нашей программе, мы используем оператор потокового вывода cout, не задумываясь о том, как он реализован на низком уровне. Кроме того, отделение данных от логики является хорошим тоном программирования. Каждый класс в C++ использует свое пространство имен. Это сделано для того, чтобы избежать конфликтов при именовании переменных и функций. В файле students.cpp мы используем оператор принадлежности :: перед именем каждой функции. Это делается для того, чтобы указать компилятору, что эти функции принадлежат классу Students. Создание объекта через указатель Реализуем это в нашей программе, немного изменив содержимое файла main.cpp. /* main.cpp */ #include <iostream> #include "students.h" int main() { // Выделение памяти для объекта Students Students *student = new Students; std::string name; std::string last_name; // Ввод имени с клавиатуры std::cout << "Name: "; getline(std::cin, name); // Ввод фамилии std::cout << "Last name: "; getline(std::cin, last_name); // Сохранение имени и фамилии в объект класса Students student->set_name(name); student->set_last_name(last_name); // Оценки int scores[5]; // Сумма всех оценок int sum = 0; // Ввод промежуточных оценок for (int i = 0; i < 5; ++i) { std::cout << "Score " << i+1 << ": "; std::cin >> scores[i]; // суммирование sum += scores[i]; } // Сохраняем промежуточные оценки в объект класса Student student->set_scores(scores); // Считаем средний балл float average_ball = sum / 5.0; // Сохраняем средний балл в объект класса Students student->set_average_ball(average_ball); // Выводим данные по студенту std::cout << "Average ball for " << student->get_name() << " " << student->get_last_name() << " is " << student->get_average_ball() << std::endl; // Удаление объекта student из памяти delete student; return 0; } При создании статического объекта, для доступа к его методам и свойствам, используют операция прямого обращения — «.» (символ точки). Если же память для объекта выделяется посредством указателя, то для доступа к его методам и свойствам используется оператор косвенного обращения — «->». Конструктор и деструктор класса Конструктор класса — это специальная функция, которая автоматически вызывается сразу после создания объекта этого класса. Он не имеет типа возвращаемого значения и должен называться также, как класс, в котором он находится. По умолчанию, заполним двойками массив с промежуточными оценками студента. class Students { public: // Конструктор класса Students Students(int default_score) { for (int i = 0; i < 5; ++i) { scores[i] = default_score; } } private: int scores[5]; }; int main() { // Передаем двойку в конструктор Students *student = new Students(2); return 0; } Мы можем исправить двойки, если ученик будет хорошо себя вести, и вовремя сдавать домашние задания. А на «нет» и суда нет Деструктор класса вызывается при уничтожении объекта. Имя деструктора аналогично имени конструктора, только в начале ставится знак тильды ~. Деструктор не имеет входных параметров. #include <iostream> class Students { public: // Деструктор ~Students() { std::cout << "Memory has been cleaned. Good bye." << std::endl; } }; int main() { Students *student = new Students; // Уничтожение объекта delete student; return 0; }
  23. При объявлении, мы задавали массиву определенный постоянный размер. Возможно, кто-то из читателей пробовал делать так: int n = 10; int arr[n]; Но, как уже было сказано — при объявлении статического массива, его размером должна являться числовая константа, а не переменная. В большинстве случаев, целесообразно выделять определенное количество памяти для массива, значение которого изначально неизвестно. Например, необходимо создать динамический массив из N элементов, где значение N задается пользователем. Выделение памяти для динамического массива имеет аналогичный принцип. Создание динамического массива #include <iostream> using namespace std; int main() { int num; // размер массива cout << "Enter integer value: "; cin >> num; // получение от пользователя размера массива int *p_darr = new int[num]; // Выделение памяти для массива for (int i = 0; i < num; i++) { // Заполнение массива и вывод значений его элементов p_darr[i] = i; cout << "Value of " << i << " element is " << p_darr[i] << endl; } delete [] p_darr; // очистка памяти return 0; } Синтаксис выделения памяти для массива имеет вид указатель = new тип[размер]. В качестве размера массива может выступать любое целое положительное значение.
  24. Xeon

    Указатели в C++ — урок 7

    При выполнении любой программы, все необходимые для ее работы данные должныбыть загружены в оперативную память компьютера. Для обращения к переменным, находящимся в памяти, используются специальные адреса, которые записываются в шестнадцатеричном виде, например 0x100 или 0x200. Если переменных в памяти потребуется слишком большое количество, которое не сможет вместить в себя сама аппаратная часть, произойдет перегрузка системы или её зависание. Если мы объявляем переменные статично, так как мы делали в предыдущих уроках, они остаются в памяти до того момента, как программа завершит свою работу, а после чего уничтожаются. Такой подход может быть приемлем в простых примерах и несложных программах, которые не требуют большого количества ресурсов. Если же наш проект является огромным программным комплексом с высоким функционалом, объявлять таким образом переменные, естественно, было бы довольно не умно. Можете себе представить, если бы небезызвестная Battlefield 3 использовала такой метод работы с данными? В таком случае, самым заядлым геймерам пришлось бы перезагружать свои высоконагруженные системы кнопкой reset после нескольких секунд работы игры. Дело в том, что играя в тот же Battlefield, геймер в каждый новый момент времени видит различные объекты на экране монитора, например сейчас я стреляю во врага, а через долю секунды он уже падает убитым, создавая вокруг себя множество спецэффектов, таких как пыль, тени, и т.п. Естественно, все это занимает какое-то место в оперативной памяти компьютера. Если не уничтожать неиспользуемые объекты, очень скоро они заполнят весь объем ресурсов ПК. По этим причинам, в большинстве языков, в том числе и C/C++, имеется понятие указателя. Указатель — это переменная, хранящая в себе адрес ячейки оперативной памяти, например 0x100. Мы можем обращаться, например к массиву данных через указатель, который будет содержать адрес начала диапазона ячеек памяти, хранящих этот массив. После того, как этот массив станет не нужен для выполнения остальной части программы, мы просто освободим память по адресу этого указателя, и она вновь станет доступно для других переменных. Ниже приведен конкретный пример обращения к переменным через указатель и напрямую. Пример использования статических переменных #include <iostream> using namespace std; int main() { int a; // Объявление статической переменной int b = 5; // Инициализация статической переменной b a = 10; b = a + b; cout << "b is " << b << endl; return 0; } Пример использования динамических переменных #include <iostream> using namespace std; int main() { int *a = new int; // Объявление указателя для переменной типа int int *b = new int(5); // Инициализация указателя *a = 10; *b = *a + *b; cout << "b is " << *b << endl; delete b; delete a; return 0; } Синтаксис первого примера вам уже должен быть знаком. Мы объявляем/инициализируем статичные переменные a и b, после чего выполняем различные операции напрямую с ними. Во втором примере мы оперируем динамическими переменными посредством указателей. Рассмотрим общий синтаксис указателей в C++. Выделение памяти осуществляется с помощью оператора new и имеет вид: тип_данных *имя_указателя = new тип_данных;, например int *a = new int;. После удачного выполнения такой операции, в оперативной памяти компьютера происходит выделение диапазона ячеек, необходимого для хранения переменной типа int. Логично предположить, что для разных типов данных выделяется разное количество памяти. Следует быть особенно осторожным при работе с памятью, потому что именно ошибки программы, вызванные утечкой памяти, являются одними из самых трудно находимых. На отладку программы в поисках одной ничтожной ошибки, может уйти час, день, неделя, в зависимости от упорности разработчика и объема кода. Инициализация значения, находящегося по адресу указателя выполняется схожим образом, только в конце ставятся круглые скобки с нужным значением: тип данных *имя_указателя = new тип_данных(значение). В нашем примере это int *b = new int(5). Для того, чтобы получить адрес в памяти, на который ссылается указатель, используется имя переменной-указателя с префиксом &. перед ним (не путать со знаком ссылки в C++). Например, чтобы вывести на экран адрес ячейки памяти, на который ссылается указатель b во втором примере, мы пишем cout << "Address of b is " << &b << endl;. В моей системе, я получил значение 0x1aba030. У вас оно может быть другим, потому что адреса в оперативной памяти распределяются таким образом, чтобы максимально уменьшить фрагментацию. Поскольку, в любой системе список запущенных процессов, а также объем и разрядность памяти могут отличаться, система сама распределяет данные для обеспечения минимальной фрагментации. Для того, чтобы получить значение, которое находится по адресу, на который ссылается указатель, используется префикс *. Данная операция называется разыменованием указателя. Во втором примере мы выводим на экран значение, которое находится в ячейке памяти (у меня это 0x1aba030cout << "b is " << *b << endl; . В этом случае необходимо использовать знак *. Чтобы изменить значение, находящееся по адресу, на который ссылается указатель, нужно также использовать звездочку, например, как во втором примере — *b = *a + *b;. Когда мы оперируем данными, то используем знак * Когда мы оперируем адресами, то используем знак & В этих вещах очень часто возникают недопонимания, и кстати, не только у новичков. Многие из тех, кто начинал программировать с того же php, также часто испытывают подобную путаницу при работе с памятью. Для того, чтобы освободить память, выделенную оператором new, используется оператор delete. Пример освобождения памяти #include <iostream> using namespace std; int main() { // Выделение памяти int *a = new int; int *b = new int; float *c = new float; // ... Любые действия программы // Освобождение выделенной памяти delete c; delete b; delete a; return 0; } При использовании оператора delete для указателя, знак * не используется.
  25. Xeon

    Функции в C++ — урок 6

    Очень часто в программировании необходимо выполнять одни и те же действия. Например, мы хотим выводить пользователю сообщения об ошибке в разных местах программы, если он ввел неверное значение. без функций это выглядело бы так: include <iostream> #include <string> using namespace std; int main() { string valid_pass = "qwerty123"; string user_pass; cout << "Введите пароль: "; getline(cin, user_pass); if (user_pass == valid_pass) { cout << "Доступ разрешен." << endl; } else { cout << "Неверный пароль!" << endl; } return 0; } А вот аналогичный пример с функцией: #include <iostream> #include <string> using namespace std; void check_pass (string password) { string valid_pass = "qwerty123"; if (password == valid_pass) { cout << "Доступ разрешен." << endl; } else { cout << "Неверный пароль!" << endl; } } int main() { string user_pass; cout << "Введите пароль: "; getline (cin, user_pass); check_pass (user_pass); return 0; } По сути, после компиляции не будет никакой разницы для процессора, как для первого кода, так и для второго. Но ведь такую проверку пароля мы можем делать в нашей программе довольно много раз. И тогда получается копипаста и код становится нечитаемым. Функции — один из самых важных компонентов языка C++. Любая функция имеет тип, также, как и любая переменная. Функция может возвращать значение, тип которого в большинстве случаев аналогично типу самой функции. Если функция не возвращает никакого значения, то она должна иметь тип void (такие функции иногда называют процедурами) При объявлении функции, после ее типа должно находиться имя функции и две круглые скобки — открывающая и закрывающая, внутри которых могут находиться один или несколько аргументов функции, которых также может не быть вообще. после списка аргументов функции ставится открывающая фигурная скобка, после которой находится само тело функции. В конце тела функции обязательно ставится закрывающая фигурная скобка. Пример построения функции #include <iostream> using namespace std; void function_name () { cout << "Hello, world" << endl; } int main() { function_name(); // Вызов функции return 0; } Перед вами тривиальная программа, Hello, world, только реализованная с использованием функций. Если мы хотим вывести «Hello, world» где-то еще, нам просто нужно вызвать соответствующую функцию. В данном случае это делается так: function_name();. Вызов функции имеет вид имени функции с последующими круглыми скобками. Эти скобки могут быть пустыми, если функция не имеет аргументов. Если же аргументы в самой функции есть, их необходимо указать в круглых скобках. Также существует такое понятие, как параметры функции по умолчанию. Такие параметры можно не указывать при вызове функции, т.к. они примут значение по умолчанию, указанно после знака присваивания после данного параметра и списке всех параметров функции. В предыдущих примерах мы использовали функции типа void, которые не возвращают никакого значения. Как многие уже догадались, оператор return используется для возвращения вычисляемого функцией значения. Рассмотрим пример функции, возвращающей значение на примере проверки пароля. #include <iostream> #include <string> using namespace std; string check_pass (string password) { string valid_pass = "qwerty123"; string error_message; if (password == valid_pass) { error_message = "Доступ разрешен."; } else { error_message = "Неверный пароль!"; } return error_message; } int main() { string user_pass; cout << "Введите пароль: "; getline (cin, user_pass); string error_msg = check_pass (user_pass); cout << error_msg << endl; return 0; } В данном случае функция check_pass имеет тип string, следовательно она будет возвращать только значение типа string, иными словами говоря строку. Давайте рассмотрим алгоритм работы этой программы. Самой первой выполняется функция main(), которая должна присутствовать в каждой программе. Теперь мы объявляем переменную user_pass типа string, затем выводим пользователю сообщение «Введите пароль», который после ввода попадает в строку user_pass. А вот дальше начинает работать наша собственная функция check_pass(). В качестве аргумента этой функции передается строка, введенная пользователем. Аргумент функции — это, если сказать простым языком переменные или константы вызывающей функции, которые будет использовать вызываемая функция. При объявлении функций создается формальный параметр, имя которого может отличаться от параметра, передаваемого при вызове этой функции. Но типы формальных параметров и передаваемых функии аргументов в большинстве случаев должны быть аналогичны. После того, как произошел вызов функции check_pass(), начинает работать данная функция. Если функцию нигде не вызвать, то этот код будет проигнорирован программой. Итак, мы передали в качестве аргумента строку, которую ввел пользователь. Теперь эта строка в полном распоряжении функции (хочу обратить Ваше внимание на то, что переменные и константы, объявленные в разных функциях независимы друг от друга, они даже могут иметь одинаковые имена. В следующих уроках я расскажу о том, что такое область видимости, локальные и глобальные переменные). Теперь мы проверяем, правильный ли пароль ввел пользователь или нет. если пользователь ввел правильный пароль, присваиваем переменной error_message соответствующее значение. если нет, то сообщение об ошибке. После этой проверки мы возвращаем переменную error_message. На этом работа нашей функции закончена. А теперь, в функции main(), то значение, которое возвратила наша функция мы присваиваем переменной error_msg и выводим это значение (строку) на экран терминала. Если объяснять вкратце, рекурсия — это когда функция вызывает сама себя. Смотрите еще один пример: #include <iostream> #include <string> using namespace std; bool password_is_valid (string password) { string valid_pass = "qwerty123"; if (valid_pass == password) return true; else return false; } void get_pass () { string user_pass; cout << "Введите пароль: "; getline(cin, user_pass); if (!password_is_valid(user_pass)) { cout << "Неверный пароль!" << endl; get_pass (); // Здесь делаем рекурсию } else { cout << "Доступ разрешен." << endl; } } int main() { get_pass (); return 0; } Функции очень сильно облегчают работу программисту и намного повышают читаемость и понятность кода, в том числе и для самого разработчика (не удивляйтесь этому, т. к. если вы откроете код, написанный вами полгода назад,не сразу поймете соль, поверьте на слово). Не расстраивайтесь, если не сразу поймете все аспекты функций в C++, т. к. это довольно сложная тема и мы еще будем разбирать примеры с функциями в следующих уроках.
  1. Загрузить больше активности
×
×
  • Создать...