Классы. Объектно-ориентированное программирование. Создание объектов

Создание объектов

Для объявления объекта произвольного типа используется следующая конструкция:

<тип класса> имя переменной = new <тип класса>();

Например:

InfoUser myinfo = new InfoUser();

Эта строка объявления выполняет три функции. Во-первых, объявляется переменная myinfo, относящаяся к типу класса InfoUser. Сама эта переменная не является объектом, а лишь переменной, которая может ссылаться на объект. Во-вторых, создается конкретная, физическая, копия объекта. Это делается с помощью оператора new . И наконец, переменной myinfo присваивается ссылка на данный объект. Таким образом, после выполнения анализируемой строки объявленная переменная myinfo ссылается на объект типа InfoUser.

Оператор new динамически (т.е. во время выполнения) распределяет память для объекта и возвращает ссылку на него, которая затем сохраняется в переменной. Следовательно, в C# для объектов всех классов должна быть динамически распределена память.

То обстоятельство, что объекты классов доступны по ссылке, объясняет, почему классы называются ссылочными типами . Главное отличие типов значений от ссылочных типов заключается в том, что именно содержит переменная каждого из этих типов. Так, переменная типа значения содержит конкретное значение, а ссылочная переменная содержит не сам объект, а лишь ссылку на него.

Переменные ссылочного типа и присваивание

В операции присваивания переменные ссылочного типа действуют иначе, чем переменные типа значения, например типа int. Когда одна переменная типа значения присваивается другой, ситуация оказывается довольно простой. Переменная, находящаяся в левой части оператора присваивания, получает копию значения переменной, находящейся в правой части этого оператора.

Когда же одна переменная ссылки на объект присваивается другой, то ситуация несколько усложняется, поскольку такое присваивание приводит к тому, что переменная, находящаяся в левой части оператора присваивания, ссылается на тот же самый объект, на который ссылается переменная, находящаяся в правой части этого оператора. Сам же объект не копируется. В силу этого отличия присваивание переменных ссылочного типа может привести к несколько неожиданным результатам.

В качестве примера рассмотрим следующий фрагмент кода:

Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class autoCar { public string marka; } class Program { static void Main(string args) { autoCar Car1 = new autoCar(); autoCar Car2 = Car1; Car1.marka = "Renault"; Console.WriteLine(Car1.marka); Console.WriteLine(Car2.marka); Console.ReadLine(); } } }

Когда переменная Car1 присваивается переменой Car2, то в конечном итоге переменная Car2 просто ссылается на тот же самый объект, что и переменная Car1. Следовательно, этим объектом можно оперировать с помощью переменной Car1 или Car2. Несмотря на то что обе переменные, Car1 и Car2, ссылаются на один и тот же объект, они никак иначе не связаны друг с другом.

Инициализаторы объектов

Инициализаторы объектов предоставляют способ создания объекта и инициализации его полей и свойств. Если используются инициализаторы объектов, то вместо обычного вызова конструктора класса указываются имена полей или свойств, инициализируемых первоначально задаваемым значением. Следовательно, синтаксис инициализатора объекта предоставляет альтернативу явному вызову конструктора класса. Синтаксис инициализатора объекта используется главным образом при создании анонимных типов в LINQ-выражениях. Но поскольку инициализаторы объектов можно, а иногда и нужно использовать в именованном классе, то ниже представлены основные положения об инициализации объектов.

Ниже приведена общая форма синтаксиса инициализации объектов:

new имя_класса {имя = выражение, имя = выражение, ...}

где имя обозначает имя поля или свойства, т.е. доступного члена класса, на который указывает имя_класса. А выражение обозначает инициализирующее выражение, тип которого, конечно, должен соответствовать типу поля или свойства.

Классы и объекты в С++ являются основными концепциями объектно-ориентированного программирования — ООП. Объектно-ориентированное программирование — расширение структурного программирования, в котором основными концепциями являются понятия классов и объектов. Основное отличие языка программирования С++ от С состоит в том, что в С нет классов, а следовательно язык С не поддерживает ООП, в отличие от С++.

Чтобы понять, для чего же в действительности нужны классы, проведём аналогию с каким-нибудь объектом из повседневной жизни, например, с велосипедом. Велосипед — это объект, который был построен согласно чертежам. Так вот, эти самые чертежи играют роль классов в ООП. Таким образом классы — это некоторые описания, схемы, чертежи по которым создаются объекты. Теперь ясно, что для создания объекта в ООП необходимо сначала составить чертежи, то есть классы. Классы имеют свои функции, которые называются методами класса. Передвижение велосипеда осуществляется за счёт вращения педалей, если рассматривать велосипед с точки зрения ООП, то механизм вращения педалей — это метод класса. Каждый велосипед имеет свой цвет, вес, различные составляющие — всё это свойства. Причём у каждого созданного объекта свойства могут различаться. Имея один класс, можно создать неограниченно количество объектов (велосипедов), каждый из которых будет обладать одинаковым набором методов, при этом можно не задумываться о внутренней реализации механизма вращения педалей, колёс, срабатывания системы торможения, так как всё это уже будет определено в классе. Разобравшись с назначением класса, дадим ему грамотное определение.

Классы в С++ — это абстракция описывающая методы, свойства, ещё не существующих объектов. Объекты — конкретное представление абстракции, имеющее свои свойства и методы. Созданные объекты на основе одного класса называются экземплярами этого класса. Эти объекты могут иметь различное поведение, свойства, но все равно будут являться объектами одного класса. В ООП существует три основных принципа построения классов:

  1. Инкапсуляция — это свойство, позволяющее объединить в классе и данные, и методы, работающие с ними и скрыть детали реализации от пользователя.
  2. Наследование — это свойство, позволяющее создать новый класс-потомок на основе уже существующего, при этом все характеристики класса родителя присваиваются классу-потомку.
  3. Полиморфизм — свойство классов, позволяющее использовать объекты классов с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.

Каждое свойство построения классов мы рассмотрим подробно по мере необходимости, а пока просто запомните эти три. А теперь вернёмся к классам, для начала рассмотрим структуру объявления классов.

// объявление классов в С++ class /*имя класса*/ { private: /* список свойств и методов для использования внутри класса */ public: /* список методов доступных другим функциям и объектам программы */ protected: /*список средств, доступных при наследовании*/ };

Объявление класса начинается с зарезервированного ключевого слова class ,послекоторого пишется имя класса. В фигурных скобочках, строки 3 — 10 объявляется тело класса, причём после закрывающейся скобочки обязательно нужно ставить точку с запятой, строка 10 . В теле класса объявляются три метки спецификации доступа, строки 4, 6, 8, после каждой метки нужно обязательно ставить двоеточие. В строке 4 объявлена метка спецификатора доступа private . Все методы и свойства класса, объявленные после спецификатор доступа private будут доступны только внутри класса. В строке 6 объявлен спецификатор доступа public , все методы и свойства класса, объявленные после спецификатора доступа public будут доступны другим функциям и объектам в программе. Пока на этом остановимся, спецификатор доступа protected разбирать сейчас не будем, просто запомните, что он есть. При объявлении класса, не обязательно объявлять три спецификатора доступа, и не обязательно их объявлять в таком порядке. Но лучше сразу определиться с порядком объявления спецификаторов доступа, и стараться его придерживаться. Разработаем программу, в которой объявим простейший класс, в котором будет объявлена одна функция, печатающая сообщение.

using namespace std; // начало объявления класса class CppStudio // имя класса { public: // спецификатор доступа void message() // функция (метод класса) выводящая сообщение на экран { cout << "website: сайт\ntheme: Classes and Objects in C + +\n"; } }; // конец объявления класса CppStudio int main(int argc, char* argv) { CppStudio objMessage; // объявление объекта objMessage.message(); // вызов функции класса message system("pause"); return 0; }

В строках 7 — 14 мы определили класс с именем CppStudio . Имя класса принято начинать с большой буквы, последующие слова в имени также должны начинаться с большой буквы. Такое сочетание букв называют верблюжьим регистром, так как чередование больших и маленьких букв напоминает силуэт верблюда. В теле класса объявлен спецификатор доступа public , который позволяет вызывать другим функциям методы класса, объявленные после public . Вот именно поэтому в главной функции, в строке 19 мы смогли вызвать функцию message() . В классе CppStudio объявлена всего одна функция, которая не имеет параметров и выводит сообщение на экран, строка 12 . Методы класса — это те же функции, только объявлены они внутри класса, поэтому всё что относится к функциям актуально и для методов классов. Объявление классов выполняется аналогично объявлению функций, то есть класс можно объявлять в отдельном файле или в главном файле, позже посмотрим как это делается. В строке 18 объявлена переменная objMessage типа CppStudio , так вот, переменная objMessage — это объект класса CppStudio . Таким образом, класс является сложным типом данных. После того как объект класса объявлен, можно воспользоваться его методами. Метод всего один — функция message() . Для этого обращаемся к методу объекта objMessage через точку, как показано в строке 19 , в результате программа выдаст текстовое сообщение (см. Рисунок 1).

Website: сайт theme: Classes and Objects in C + +

Рисунок 1 — Классы в С++

set — функции и get — функции классов

Каждый объект имеет какие-то свои свойства или атрибуты, которые характеризуют его на протяжении всей жизни. Атрибуты объекта хранятся в переменных, объявленных внутри класса, которому принадлежит данный объект. Причём, объявление переменных должно выполняться со спецификатором доступа private . Такие переменные называются элементами данных. Так как элементы данных объявлены в private , то и доступ к ним могут получить только методы класса, внешний доступ к элементам данных запрещён. Поэтому принято объявлять в классах специальные методы — так называемые set и get функции, с помощью которых можно манипулировать элементами данных. set-функции инициализируют элементы данных, get-функции позволяют просмотреть значения элементов данных. Доработаем класс CppStudio так, чтобы в нём можно было хранить дату в формате дд.мм.гг . Для изменения и просмотра даты реализуем соответственно set и get функции.

// classes.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include using namespace std; class CppStudio // имя класса { private: // спецификатор доступа private int day, // день month, // месяц year; // год public: // спецификатор доступа public void message() // функция (метод класса) выводящая сообщение на экран { cout << "\nwebsite: сайтntheme: Classes and Objects in C + +\n"; } void setDate(int date_day, int date_month, int date_year) // установка даты в формате дд.мм.гг { day = date_day; // инициализация день month = date_month; // инициализация месяц year = date_year; // инициализация год } void getDate() // отобразить текущую дату { cout << "Date: " << day << "." << month << "." << year << endl; } }; // конец объявления класса CppStudio int main(int argc, char* argv) { setlocale(LC_ALL, "rus"); // установка локали int day, month, year; cout << "Введите текущий день месяц и год!\n"; cout << "день: "; cin >> day; cout << "месяц: "; cin >> month; cout << "год: "; cin >> year; CppStudio objCppstudio; // объявление объекта objCppstudio.message(); // вызов функции класса message objCppstudio.setDate(day, month, year); // инициализация даты objCppstudio.getDate(); // отобразить дату system("pause"); return 0; }

В определении класса появился новый спецификатор доступа private , строка 9 . Данный спецификатор доступа ограничивает доступ к переменным, которые объявлены после него и до начала спецификатора доступа public , строки 9 — 12 . Таким образом к переменным day, month, year , могут получить доступ только методы класса. Функции не принадлежащие классу, не могут обращаться к этим переменным. Элементы данных или методы класса, объявленные после спецификатора доступа private , но до начала следующего спецификатора доступа называются закрытыми элементами данных и закрытыми методами класса. Следуя принципу наименьших привилегий и принципу хорошего программирования, целесообразно объявлять элементы данных после спецификатора доступа private , а методы класса — после спецификатора доступа public . Тогда, для манипулирования элементами данных, объявляются специальные функции — get и set . В класс CppStudio мы добавили два метода setDate() и getDate() , подробно рассмотрим каждый метод. Метод setDate() определён с 18 по 23 строки . Как уже ранее упоминалось, set — функции инициализируют элементы данных, поэтому метод setDate() выполняет именно такую функцию. То есть метод setDate() инициализирует переменные day, month, year . Чтобы просмотреть, значения в закрытых элементах данных объявлена функция getDate() ,которая возвращает значения из переменных day, month, year в виде даты.На этом определение класса закончено, в main(), как и всегда, создаем объект класса, и через объект вызываем его методы,строки 39 — 41 . Если бы элементы данных были объявлены после спецификатора public мы бы смогли к ним обратиться точно также, как и к методам класса. Результат работы программы показан на рисунке 2.

Введите текущий день месяц и год! день: 10 месяц: 11 год: 2011 website: сайтntheme: Classes and Objects in C + + Date: 10.11.2011

Рисунок 2 — Классы в С++

Конструкторы

В предыдущей программе, у класса CppStudio были объявлены элементы данных, которые могут хранить информацию о дате. Когда был создан объект класса, мы сначала вызвали set — функцию , для того, чтобы задать текущую дату (тем самым проинициализировать элементы данных), а потом — вызвали get — функцию и увидели соответствующую дату на экране. Если бы мы сначала вызвали get — функцию , то вместо даты мы бы увидели какие-то числа — мусор. Так вот, при создании объектов, можно сразу же проинициализировать элементы данных класса, выполняет эту функцию конструктор. Конструктор — специальная функция, которая выполняет начальную инициализацию элементов данных, причём имя конструктора обязательно должно совпадать с именем класса. Важным отличием конструктора от остальных функций является то, что он не возвращает значений вообще никаких, в том числе и void . В любом классе должен быть конструктор,даже если явным образом конструктор не объявлен (как в предыдущем классе), то компилятор предоставляет конструктор по умолчанию, без параметров. Доработаем класс CppStudio , добавив к нему конструктор.

// classes.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" #include << "\nwebsite: сайт\ntheme: Classes and Objects in C + +\n"; } void setDate(int date_day, int date_month, int date_year) // установка даты в формате дд.мм.гг { day = date_day; // инициализация день month = date_month; // инициализация месяц year = date_year; // инициализация год } void getDate() // отобразить текущую дату { cout << "date: " << day << "." << month << "." << year << endl; } }; // конец объявления класса CppStudio int main(int argc, char* argv) { CppStudio objCppstudio(11,11,2011); // объявление объекта и инициализация элементов данных objCppstudio.message(); // вызов функции message objCppstudio.getDate(); // отобразить дату system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// classes.cpp: определяет точку входа для консольного приложения. #include using namespace std; class CppStudio // имя класса { private: // спецификатор доступа private int day, // день month, // месяц year; // год public: // спецификатор доступа public CppStudio(int date_day, int date_month, int date_year) // конструктор класса { setDate(date_day, date_month, date_year); // вызов функции установки даты } void message() // функция (метод класса) выводящая сообщение на экран { cout << "\nwebsite: сайт\ntheme: Classes and Objects in C + +\n"; } void setDate(int date_day, int date_month, int date_year) // установка даты в формате дд.мм.гг { day = date_day; // инициализация день month = date_month; // инициализация месяц year = date_year; // инициализация год } void getDate() // отобразить текущую дату { cout << "date: " << day << "." << month << "." << year << endl; } }; // конец объявления класса CppStudio int main(int argc, char* argv) { CppStudio objCppstudio(11,11,2011); // объявление объекта и инициализация элементов данных objCppstudio.message(); // вызов функции message objCppstudio.getDate(); // отобразить дату return 0; }

Конструктор объявлен в строках 13 — 16 . Конструктор имеет три параметра, через которые он получает информацию о дате, в теле конструктора вызывается set — функция для установки даты. Можно было реализовать начальную инициализацию элементов данных класса и без set — функции , но так как эта функция была предусмотрена, то правильнее будет использовать именно эту функцию, строка 15 . В строке 35 объявляем объект класса, причём после имени объекта в круглых скобочках передаём три аргумента. Вот так с помощью конструктора выполняется начальная инициализация элементов данных (см. Рисунок 3).

Website: сайт theme: Classes and Objects in C + + date: 11.11.2011

Рисунок 3 — Классы в С++

Объявление класса в отдельном файле

До сих пор объявление класса выполнялось в файле с главной функцией и всё работало. Предположим, необходимо написать какую-то программу, для этого необходимо воспользоваться классом CppStudio — разработанный ранее нами класс. Чтобы воспользоваться этим классом, необходимо подключить файл, в котором он объявлен. Как мы уже говорили, подключение файлов выполняется с помощью препроцессорной директивы #include . Но даже, если мы сможем подключить файл с классом, появится новая проблема — так как в файле с классом уже есть функция main() , то при построении проекта компилятор выдаст ошибку. Суть ошибки: «В проекте найдено несколько main() — функций .» Именно поэтому класс необходимо объявлять в отдельном файле, чтобы его можно было неоднократно использовать. Ранее мы объявляли в отдельном файле функции, таким же образом размещается класс в отдельном файле. Для этого необходимо выполнить 3 шага:

  1. в заголовочном файле объявить пользовательский класс, в нашем случае — CppStudio ;
  2. подключить заголовочный файл к программе, в нашем случае — #include "CppStudio.h" .

В зависимости от среды разработки, способы добавления файлов в проект могут отличаться, но суть задачи от этого не меняется. В MVS2010 заголовочный файл можно добавить, вызвав контекстное меню(клик правой кнопкой мыши) в «обозревателе решений «, выбрав пункт «создать новый элемент «. В появившемся диалоговом окне выбираем нужный нам тип файлаэто *.h и заполняем поле «Имя файла «. Как и прежде, имя выбираем осмысленное, как правило такое же как и имя класса. Теперь к нашему проекту добавлен новый заголовочный файл — CppStudio.h .

В только что созданном заголовочном файле объявляем класс и, если необходимо, подключаем дополнительные заголовочные. Вот, что должно было получиться:

// заголовочный файл CppStudio.h #include using namespace std; // объявление класса class CppStudio // имя класса { private: // спецификатор доступа private int day, // день month, // месяц year; // год public: // спецификатор доступа public CppStudio(int date_day, int date_month, int date_year) // конструктор класса { setDate(date_day, date_month, date_year); // вызов функции установки даты } void message() // функция (метод класса) выводящая сообщение на экран { cout << "nwebsite: сайтntheme: Classes and Objects in C + +n"; } void setDate(int date_day, int date_month, int date_year) // установка даты в формате дд.мм.гг { day = date_day; // инициализация день month = date_month; // инициализация месяц year = date_year; // инициализация год } void getDate() // отобразить текущую дату { cout << "date: " << day << "." << month << "." << year << endl; } }; // конец объявления класса CppStudio

Чтобы главная функция увидела созданный нами класс и смогла его использовать, необходимо включить определение класса в исполняемом файле, с функцией main() . Делается это так:

// код Code::Blocks

// код Dev-C++

// classes.cpp: определяет точку входа для консольного приложения. // подключаем класс CppStudio #include "CppStudio.h" int main(int argc, char*argv) { CppStudio objCppstudio(11,11,2011); // объявление объекта и инициализвция элементов данных objCppstudio.message(); // вызов функции message objCppstudio.getDate(); // отобразить дату system("pause"); return 0; }

В строке 5 подключеноопределение класса CppStudio , только после этого можно создавать объекты класса, использовать его методы и т. д. Результат работы программы точно такой же как и раньше, изменилась лишь структура проекта.

Отделение интерфейса от реализации

Интерфейс класса — конструкция, определяющая методы и свойства, предоставляемые классом. Реализация класса — это способ осуществления работоспособности класса. До этого мы не отделяли интерфейс класса от его реализации, то есть реализация методов осуществлялась внутри класса. Отделение интерфейса от реализации класса выполняется для того, чтобы скрыть способ осуществления работоспособности класса. Отделение интерфейса от реализации выполняется за 5 шагов:

  1. добавить в проект заголовочный файл *.h ;
  2. определить интерфейс класса в заголовочном файле
  3. добавить в проект исполняемый файл *.cpp ;
  4. в исполняемом файле выполнить реализацию класса;
  5. подключить заголовочный файл к программе.

Как добавить заголовочный файл в проект мы уже знаем, точно также добавляется и исполняемый файл, имена этим файлам даются, как правило, одинаковые. Интерфейс класса должен выглядеть так:

// заголовочный файл класса СppStudio.h // интерфейс класса // объявление класса class CppStudio // имя класса { private: // спецификатор доступа private int day, // день month, // месяц year; // год public: // спецификатор доступа public CppStudio(int, int, int); // конструктор класса void message(); // функция (метод класса) выводящая сообщение на экран void setDate(int, int, int); // установка даты в формате дд.мм.гг void getDate(); // отобразить текущую дату }; // конец объявления класса CppStudio

В интерфейсе класса остались объявленные переменные и прототипы методов класса. Теперь рассмотрим содержимое файла реализации методов класса.

// файл реализации класса CppStudio.cpp #include using namespace std; // подключаем интерфейс класса к файлу его реализации #include "CppStudio.h" CppStudio::CppStudio(int date_day, int date_month, int date_year) // конструктор класса { setDate(date_day, date_month, date_year); // вызов функции установки даты } void CppStudio::message() // функция (метод класса) выводящая сообщение на экран { cout << "nwebsite: сайтntheme: Classes and Objects in C + +n"; } void CppStudio::setDate(int date_day, int date_month, int date_year) // установка даты в формате дд.мм.гг { day = date_day; // инициализация день month = date_month; // инициализация месяц year = date_year; // инициализация год } void CppStudio::getDate() // отобразить текущую дату { cout << "date: " << day << "." << month << "." << year << endl; }

Чтобы связать интерфейс класса и его реализацию, необходимо в файле реализации подключить заголовочный файл с определением класса, строка 6 (выделенная строка). После этого можно объявлять методы класса. Методы класса объявляются точно так же как и функции, только перед именем метода необходимо написать имя класса и поставить унарную операцию разрешения области действия «:: «.

// синтаксис объявления методов класса вне тела класса /*возвращаемый тип данных*/ /*имя класса*/::/*имя метода*/(/*параметры метода*/) { // операторы }

Так как методы класса объявляются вне тела класса, то необходимо связать реализацию метода с классом, а для этого воспользуемся бинарной операцией разрешения области действия. Бинарная операция разрешения области действия привязывает метод, объявленный извне, к классу, имя которого совпадает с именем в объявлении метода. Вот именно поэтому в объявлении метода класса необходимо добавлять имя класса и операцию разрешения области действия.

Итак, интерфейс класса определён, методы класса объявлены, осталось подключить заголовочный файл в исполняемом файле с main() функцией и программа готова.

// classes.cpp: определяет точку входа для консольного приложения. #include "stdafx.h" // подключаем класс CppStudio #include "CppStudio.h" int main(int argc, char*argv) { CppStudio objCppstudio(11,11,2011); // объявление объекта и инициализвция элементов данных objCppstudio.message(); // вызов функции message objCppstudio.getDate(); // отобразить дату system("pause"); return 0; }

// код Code::Blocks

// код Dev-C++

// classes.cpp: определяет точку входа для консольного приложения. // подключаем класс CppStudio #include "CppStudio.h" int main(int argc, char*argv) { CppStudio objCppstudio(11,11,2011); // объявление объекта и инициализвция элементов данных objCppstudio.message(); // вызов функции message objCppstudio.getDate(); // отобразить дату return 0; }

В строке 5 подключаем заголовочный файл класса, после чего можно создавать объекты этого класса.

Несколько советов о том, как следует формировать интерфейс будущего класса и что для этого надо или не надо делать.

Наконец-то мы добрались до самой важной темы во вступительном курсе. Сегодня мы будем говорить о классах и объектах. Выпуск небольшой и не сложный. Что есть хорошо.

Класс - не что иное, как структура, к которой добавили функции. А объект - это структурная переменная.

Данный материал будет более понятным, если вы хорошо освоились со структурами.
Структурное программирование

Все примеры, рассмотренные до сих пор относятся к структурному программированию. Его отличительной чертой является разбиение программ на блоки кода, выполняющие какую-то определённую задачу. При этом структурными единицами являются функции и модули (которые могут выноситься в отдельные файлы).
Объектно-ориентированное программирование (ООП)

Более современным является объектно-ориентированное программирование (в дальнейшем ООП). Понятие ООП больше относится к организации данных. ООП делает жизнь программиста легче, это без сомнений. Основной отличительной особенностью является объединение данных и функций.

Ключевыми понятиями в ООП являются объекты и классы. Классы - это структуры, в которые добавили функции. А объекты - это структурные переменные.
Определение класса

Определение класса должно располагаться до main.

Начнём с простых примеров:

код на языке c++ class soldier { public: int x,y; int ammo; };

В данном примере определение класса почти идентично определению структур. Есть только два отличия: в заголовке вместо ключевого слово struct стоит class. Второе - в первой строке определения класса стоит public с двоеточием. Вот с public мы разберёмся совсем скоро. Сначала же создадим переменную типа soldier.

soldier a;

a.x = 3;
a.y = 4;
a.ammo = 5;

Здесь мы создали объект a класса soldier. Смотрите, совсем никаких отличий от структурных переменных. В данном случае объекты (переменные классов) можно использовать также как и структурные переменные.
Спецификаторы доступа public и private

По умолчанию в структурах используется спецификатор доступа public, а в классах private. Рассмотрим примеры без спецификаторов:

код на языке c++ struct soldier { int x,y; }; class soldier { int x,y; };

Здесь между структурами и классами есть важное различие. Вот как на самом деле выглядят предыдущие определения:

код на языке c++ struct soldier { public: int x,y; }; class soldier { private: int x,y; };

Компилятор автоматически вставляет public и private. В структурах по умолчанию используется public, в классах по умолчанию используется private.
Спецификатор доступа public

Данный спецификатор по умолчанию используется в структурах. Чтобы использовать его в классах нужно объявлять его явно.

Спецификатор public позволяет переменным/объектам созданным на основе данной структуры/класса получить доступ к полям.

код на языке c++ class soldier { public: int x,y; }; soldier a; a.x = 3; cout << a.x << "\n";

В данном случае использование классов ничем не отличается от структур.
Спецификатор доступа private

Мы всегда будем объявлять спецификаторы доступа явно: и private и public.

код на языке c++ class soldier { private: int ammo; public: int x,y; } soldier a; a.x = 3; a.y = 4; a.ammo = 5; // !!! Данный код некорректен

При выполнении этого примера, компилятор выдаст ошибку. Мы не можем обращаться к полю ammo за пределами класса.

За пределами класса - значит, в любом месте за фигурными скобками определения класса (есть исключение, но об этом ниже). Если объект класса пытается получить доступ к переменной класса (a.ammo), это тоже считается "за пределами класса".

Спецификаторы доступа (и public, и private) действуют с того момента, как они появляются в классе и до того момента, когда встречается другой спецификатор или определение класса не заканчивается.

Для чего же всё-таки определять переменные в блоке со спецификатором public? Дело в том, что классы помимо переменных, могут содержать и функции.

Функции определённые внутри класса называются методами этого класса.
Методы классов

Рассмотрим пример: допустим мы хотим научить нашего солдата ходить. Вот как это будет выглядеть при использовании структур:

код на языке c++ struct soldier { int x, y; }; soldier a = {0,0}; a.x += 1; // боец переместился вправо

Конечно же лучше для передвижения написать функцию:

void move (int x, int y) {}

А как должно выглядеть тело функции? Мы можем просто записать:

a.x += x;
a.y += y;

Но тогда, если у нас будет несколько переменных типа soldier, мы не сможем применить функцию move к ним. Поэтому нам необходимо передавать ещё один аргумент - структурную переменную чьи поля будут изменены. Итак:

код на языке c++ soldier move (soldier a, int x, int y) { a.x += x; a.y += y; return a; }

Теперь посмотрим, как рабоать с этой функцией:

код на языке c++ soldier b = {0, 0}; soldier c = {1, 1}; b = move(b, 0, 1); // двигаем b вверх c = move(c, 0, -1); // двигаем c вниз

В функцию передаётся переменная soldier, но её нужно и вернуть из функции. В функцию дополнительно передаётся 8 байт (поля x,y переменной soldier) и из функции возвращается восемь байт. Лучше конечно передавать soldier по ссылке.

Смотрите сколько сложностей! С классами всё намного проще:

код на языке c++ class soldier { public: int x,y; void move (int dx, int dy) { x += dx; y += dy; } };

Внутри функции мы определили функцию move. Заметьте, в неё не нужно передавать объекты класса - метод сам знает, какой объект его вызвал. dx и dy - значения на которые изменятся поля класса.

Как всё это работает:

soldier a = {0,0};
soldier b = {5,6};
a.move(1,1);
b.move(-2,3);

Методы класса вызываются почти также как и обычные функции. При этом, метод знает, какой объект его вызвал и меняться будут поля нужного объекта.
Сокрытие данных

Теперь давайте всё-таки разберёмся, для чего нужен спецификатор доступа private.

Рассмотрим пример:

код на языке c++ class soldier { private: int x,y; public: void move(int dx, int dy) { x += dx; y += dy; } };

У нас нет доступа к переменным x,y. Но они доступны внутри класса. Метод move также имеет к ним доступ, так как он является методом класса soldier.

Теперь, изменить x и y можно только с помощью метода move. x и y скрыты. И это очень правильно. Понять, почему это правильно, мы сможем не скоро - только когда начнём работать с большими классами.
Конструктор класса

Теперь, когда нашы данные надёжно спрятаны с помощью спецификатора privatе, попробуем их проинициализировать:

soldier a = {0, 0};

Но тут компилятор выдаст ошибку: дескать, чтож это ты друг пытаешься получить доступ к скрытым данным?

Что же делать? Получается что у нас есть доступ к этип переменным через move, а как же задать этим переменным начальные значения?

Если вы попытаетесь следующий способ:

код на языке c++ class soldier { private int x = 0; int y = 0; };

То конечно же компилятор и тут выдаст ошибку, так как при определении класса память не выделяется.

Итак, в каждом классе есть специальная функция, которая вызывается каждый раз когда создаётся объект класса. Это функция называется конструктором класса. Если вы явно не создаёте конструктор, то компилятор вызывает конструктор по умолчанию.

Конструктор - это обычная функция, только в ней нет возвращаемого значения. Имя конструктора совпадает с именем класса. Конструктор должен располагаться в блоке public.

код на языке c++ class soldier { private: int x,y; public: soldier () // конструктор { x = 0; // инициализация переменных y = 0; } void move (int dx, int dy) { x += dx; y += dy; } };

Конструктор вызывается при создании объектов класса:

soldier a, b;

Здесь для обоих объектов a и b вызывается конструктор.

Возможна и такая форма записи:

soldier a(), b();

Здесь как бы намекается, что при создании a и b вызывается функция (конструктор).

Кстати! Конструкторы вызываются не только для пользовательских типов данных, но и для стандартных. Например, при определении переменной типа char вызывается соответствующий конструктор. При этом данный конструктор выделяет память под переменную - один байт.
Список инициализации конструктора

Определение конструктора класса soldier совершенно корректно. Но обычно в теле конструкторов не инициализируют переменные класса. Вместо этого, для инициализации переменных класса используются списки иницицализации. Вот как выглядит определение конструктора класса soldier со списком инициализации:

soldier () : x(0), y(0), ammo(100)
{}

Список инициализации записывается после круглых скобок в заголовке конструктора и отделён двоеточием. В списке, через запятую указываются переменные класса, а в скобках пишется значение, которым должны быть проинициализированы переменные.
Перегруженные функции

Прежде чем продолжать разговаривать о классах, нам нужно знать, что такое перегруженные функции.

Допустим, нам нужны две одинаковые функции, работающие с разными типами данных. В одну функцию передаётся аргумент типа int, а в другую - типа float. В данном случае для двух функций можно использовать одно имя. Такие функции и называются перегруженными:

код на языке c++ int function (int x) { return 0; } int function (float x) { return 0; }

Имейте в виду, что здесь, на самом деле, определены две функции, но с одним именем. Дело в том, что компилятор смотрит не только на имя, но и на количество и типы аргументов функции. При этом тип возвращаемого значения не учитывается и при попытке определить следующие функции, компилятор выдаст ошибку:

код на языке c++ int function (int x) { return 0; } float function (int x) { return 0; }

Перегруженные конструкторы

Теперь рассмотрим применение перегруженных функций на практике:
Так как конструкторы - это не что иное, как функции, то соответственно их можно перегружать. Для этого в определении класса нужно определить два конструктора:

код на языке c++ soldier() : x(0), y(0),ammo(0) {} soldier (int x_init, int y_init, int ammo_init) : x(x_init), y(y_init), ammo(ammo_init) {}

Заголовок второго конструктора получился довольно длинным, но это компенсируется появившимися у нас возможностями:

soldier s1;
soldier s1(3,4,10);

При создании первой переменной вызывается конструктор без аргументов. Во втором случае - конструктор с аргументами. Посмотрите насколько легко теперь можно инициализировать новые переменные. При этом мы используем список инициализации, только в скобки подставляем не константные значения, а значения переданных в конструктор параметров.
Определение методов вне класса

Методы класса можно определять не только внутри классов. При этом в классе должно быть объявление (прототип) метода:

код на языке c++ class soldier { public: int attack(); }; int soldier::attack() { return 0; }

Внутри класса в блоке public мы объявляем метод attack. Определение метода находится за пределами класса, но метод всё равно может пользоваться сокрытыми даннами своего класса. Для того, чтобы было понятно, что данная функция является методом класса, используется операция глобального разрешения::. Сначала мы записываем имя класса, знак операции глобального разрешения и затем - имя метода. Во всём остальном определение функции совпадает с тем, что мы уже видели.

Вот как будет выглядить класс soldier если мы вынесем метод move за пределы класса:

код на языке c++ class soldier { private: int x, y; int ammo; public: soldier() : x(0), y(0), ammo(0) {} soldier(int x_init, int y_init, int ammo_init) : x(x_init), y(y_init), ammo(ammo_init) {} void move(int, int); }; void soldier::move(int dx, int dy) { x = dx; y = dy; }

Методы выносятся за пределы определения класса при создании крупных классов.
Статические данные класса

Для каждого объекта данного класса создаётся свой набор переменных. Но у нас есть возможность для всех объектов создать одну переменную. В ней, например, можно хранить количество уже созданных объектов данного класса.

код на языке c++ class soldier { public: static int counter; soldier() { counter++; } } int soldier::counter = 0;

Переменную counter мы объявили с ключевым словом static. Кроме того за пределами класса мы инициализировали эту переменную. При инициализации мы также как и в классах использовали операцию глобального разрешения.

В конструкторе мы увеличиваем переменную на единицу.

Теперь каждый раз, когда в программе будет создаваться объект типа soldier, переменная counter будет инкрементироваться.
Упражнения:

Изучите тему Стеки и очереди и выполните к ней упражнения: доработайте представленные там классы. Если будут вопросы, пишите на e-mail.

Последнее обновление: 27.07.2018

C# является полноценным объектно-ориентированным языком. Это значит, что программу на C# можно представить в виде взаимосвязанных взаимодействующих между собой объектов.

Описанием объекта является класс , а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке, у которого есть имя, возраст, какие-то другие характеристики. То есть некоторый шаблон - этот шаблон можно назвать классом. Конкретное воплощение этого шаблона может отличаться, например, одни люди имеют одно имя, другие - другое имя. И реально существующий человек (фактически экземпляр данного класса) будет представлять объект этого класса.

По умолчанию проект консольного приложения уже по умолчанию содержит один класс Program, с которого и начинается выполнение программы.

По сути класс представляет новый тип, который определяется пользователем. Класс определяется с помощью ключевого слова сlass :

Class Person { }

Где определяется класс? Класс можно определять внутри пространства имен, вне пространства имен, внутри другого класса. Как правило, классы помещаются в отдельные файлы. Но в данном случае поместим новый класс в файле, где располагается класс Program. То есть файл Program.cs будет выглядеть следующим образом:

Using System; namespace HelloApp { class Person { } class Program { static void Main(string args) { } } }

Вся функциональность класса представлена его членами - полями (полями называются переменные класса), свойствами, методами, событиями. Например, определим в классе Person поля и метод:

Using System; namespace HelloApp { class Person { public string name; // имя public int age; // возраст public void GetInfo() { Console.WriteLine($"Имя: {name} Возраст: {age}"); } } class Program { static void Main(string args) { Person tom; Console.ReadKey(); } } }

В данном случае класс Person представляет человека. Поле name хранит имя, а поле age - возраст человека. А метод GetInfo выводит все данные на консоль. Чтобы все данные были доступны вне класса Person переменные и метод определены с модификатором public.

Поскольку класс представляет собой новый тип, то в программе мы можем определять переменные, которые представляют данный тип. Так, здесь в методе Main определена переменная tom, которая представляет класс Person. Но пока эта переменная не указывает ни на какой объект и по умолчанию она имеет значение null . Поэтому вначале необходимо создать объект класса Person.

Конструкторы

Кроме обычных методов в классах используются также и специальные методы, которые называются конструкторами . Конструкторы вызываются при создании нового объекта данного класса. Конструкторы выполняют инициализацию объекта.

Конструктор по умолчанию

Если в классе не определено ни одного конструктора, то для этого класса автоматически создается конструктор по умолчанию. Такой конструктор не имеет парамтров и не имеет тела.

Выше класс Person не имеет никаких конструкторов. Поэтому для него автоматически создается конструктор по умолчанию. И мы можем использовать этот конструктор. В частности, создадим один объект класса Person:

Class Program { static void Main(string args) { Person tom = new Person(); tom.GetInfo(); // Имя: Возраст: 0 tom.name = "Tom"; tom.age = 34; tom.GetInfo(); // Имя: Tom Возраст: 34 Console.Read(); } }

Для создания объекта Person используется выражение new Person() . Оператор new выделяет память для объекта Person. И затем вызывается конструктор по умолчанию, который не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person. А переменная tom получит ссылку на созданный объект.

Если конструктор не инициализирует значения переменных объекта, то они получают значения по умолчанию. Для переменных числовых типов это число 0, а для типа string и классов - это значение null (то есть фактически отсутствие значения).

После создания объекта мы можем обратиться к переменным объекта Person через переменную tom и установить или получить их значения, например, tom.name = "Tom"; .

Имя: Возраст: 0 Имя: Tom Возраст: 34

Создание конструкторов

Выше для инициализации объекта использовался конструктор по умолчанию. Однако мы сами можем определить свои конструкторы:

Class Person { public string name; public int age; public Person() { name = "Неизвестно"; age = 18; } // 1 конструктор public Person(string n) { name = n; age = 18; } // 2 конструктор public Person(string n, int a) { name = n; age = a; } // 3 конструктор public void GetInfo() { Console.WriteLine($"Имя: {name} Возраст: {age}"); } }

Теперь в классе определено три конструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса. Используем эти конструкторы:

Static void Main(string args) { Person tom = new Person(); // вызов 1-ого конструктора без параметров Person bob = new Person("Bob"); //вызов 2-ого конструктора с одним параметром Person sam = new Person("Sam", 25); // вызов 3-его конструктора с двумя параметрами bob.GetInfo(); // Имя: Bob Возраст: 18 tom.GetInfo(); // Имя: Неизвестно Возраст: 18 sam.GetInfo(); // Имя: Sam Возраст: 25 Console.ReadKey(); }

Консольный вывод данной программы:

Имя: Неизвестно Возраст: 18 Имя: Bob Возраст: 18 Имя: Sam Возраст: 25

При этом если в классе определены конструкторы, то при создании объекта необходимо использовать один из этих конструкторов.

Ключевое слово this

Ключевое слово this представляет ссылку на текущий экземпляр класса. В каких ситуациях оно нам может пригодиться? В примере выше определены три конструктора. Все три конструктора выполняют однотипные действия - устанавливают значения полей name и age. Но этих повторяющихся действий могло быть больше. И мы можем не дублировать функциональность конструкторов, а просто обращаться из одного конструктора к другому через ключевое слово this, передавая нужные значения для параметров:

Class Person { public string name; public int age; public Person() : this("Неизвестно") { } public Person(string name) : this(name, 18) { } public Person(string name, int age) { this.name = name; this.age = age; } public void GetInfo() { Console.WriteLine($"Имя: {name} Возраст: {age}"); } }

В данном случае первый конструктор вызывает второй, а второй конструктор вызывает третий. По количеству и типу параметров компилятор узнает, какой именно конструктор вызывается. Например, во втором конструкторе:

Public Person(string name) : this(name, 18) { }

идет обращение к третьему конструктору, которому передаются два значения. Причем в начале будет выполняться именно третий конструктор, и только потом код второго конструктора.

Также стоит отметить, что в третьем конструкторе параметры называются также, как и поля класса.

Public Person(string name, int age) { this.name = name; this.age = age; }

И чтобы разграничить параметры и поля класса, к полям класса обращение идет через ключевое слово this. Так, в выражении this.name = name; первая часть this.name означает, что name - это поле текущего класса, а не название параметра name. Если бы у нас параметры и поля назывались по-разному, то использовать слово this было бы необязательно. Также через ключевое слово this можно обращаться к любому полю или методу.

Инициализаторы объектов

Для инициализации объектов классов можно применять инициализаторы . Инициализаторы представляют передачу в фигурных скобках значений доступным полям и свойствам объекта:

Person tom = new Person { name = "Tom", age=31 }; tom.GetInfo(); // Имя: Tom Возраст: 31

С помощью инициализатора объектов можно присваивать значения всем доступным полям и свойствам объекта в момент создания без явного вызова конструктора.

При использовании инициализаторов следует учитывать следующие моменты:

    С помощью инициализатора мы можем установить значения только доступных из внешнего кода полей и свойств объекта. Например, в примере выше поля name и age имеют модификатор доступа public, поэтому они доступны из любой части программы.

    Инициализатор выполняется после конструктора, поэтому если и в конструкторе, и в инициализаторе устанавливаются значения одних и тех же полей и свойств, то значения, устанавливаемые в конструкторе, заменяются значениями из инициализатора.



Есть вопросы?

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: