Классы и объекты являются строительными блоками объектно-ориентированного программирования на C++. Любая сущность, живая или неживая, может быть представлена как объект и соответствующим образом запрограммирована с помощью C++. Таким образом, такие объекты, как автомобиль, письменный стол, человек, птица, животное и т. д., могут быть представлены как объекты.
Класс является уровнем выше объекта и представляет собой категорию объектов. Таким образом, класс действует как план, который описывает дизайн объекта и его детали. Сюда входят данные, которые используются для описания объекта, а также различные методы или функции, которые могут воздействовать на данные объекта.
В этой статье мы обсудим все детали классов и объектов в C++ вместе с их программным представлением.
Классы
Класс в C++ можно рассматривать как план или скелет конкретной сущности. Класс — это определяемый пользователем тип данных. Он содержит общую информацию или данные для этого конкретного объекта и функций, которые работают с этим объектом.
В синтаксисе C++ мы определяем класс с помощью ключевого слова «class», за которым следует имя класса.
За именем класса следуют сведения о классе, заключенные в фигурные скобки, и заканчивается это все точкой с запятой.
В следующем блоке показан общий синтаксис определения класса:
Как показано в приведенном выше представлении, класс может иметь спецификаторы доступа, такие как частный/публичный/защищенный. Они могут иметь данные-члены и функции-члены. Данные и функции называются членами класса. По умолчанию члены являются закрытыми для класса, поэтому никакой внешний объект не имеет доступа к этим членам.
Например, транспортное средство может быть обобщенным классом, имеющим такие свойства, как модель, цвет, номер шасси, средняя скорость и т. д. Она может иметь такие функции, как ускорение, замедление и т.д., которые выполняют действия над элементами данных. Мы можем определить класс с именем «транспортное средство», который будет включать в себя все эти элементы данных и функции.
Как уже упоминалось, класс — это просто план для сущностей. Он не занимает места в памяти, когда он определен. Чтобы класс был функциональным, мы должны определить объекты, которые могут использовать члены класса.
Объекты
Чтобы использовать функциональность класса, нам нужно создать экземпляр класса для создания объекта. Объект является экземпляром класса. Простыми словами можно сказать, что объект является переменной типа класса.
Общий синтаксис для создания объекта:
classname object_name; |
После создания объекта его можно использовать для доступа к членам данных и функциям этого класса.
Доступ к членам класса (данным и функциям) осуществляется с помощью оператора точки (.), который также называется оператором доступа к членам.
Если obj — имя объекта и в классе есть функция «display()», то к функции можно обращаться как «obj.display()».
Однако в приведенном выше утверждении есть загвоздка. Мы можем получить доступ к функции display() с помощью объекта и оператора точки, если функция является «общедоступной» (public).
Спецификаторы доступа
В C++ доступ к элементам данных и функциям в классе зависит от доступа, предоставленного этому конкретному элементу данных или функции с использованием спецификатора доступа.
C++ поддерживает следующие спецификаторы доступа:
1) Private (частный)
Это спецификатор доступа по умолчанию для класса в C++. Это означает, что если для членов класса не указан спецификатор доступа, он считается закрытым.
Когда член является закрытым, к нему нельзя получить доступ за пределами класса. Даже не используя объект и оператор точки. Доступ к закрытым членам данных возможен только с помощью функций-членов класса.
Однако из этого правила есть исключение, которое мы обсудим в наших следующих темах.
2) Public (публичный)
Член данных или функция, которая определена как общедоступная в классе, доступна всем за пределами класса. Доступ к этим членам можно получить с помощью объекта и оператора точки.
3) Protected (защищенный)
Защищенный член класса доступен для самого класса и дочерних классов этого класса.
Этот спецификатор доступа особенно используется в случае наследования, и мы подробно обсудим это при обсуждении темы наследования.
Давайте рассмотрим пример, чтобы лучше понять работу спецификаторов доступа:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
#include <iostream> #include <string> using namespace std; class ABC{ int var1 = 10; public: string name; void display() { cout<<"var1 ="<<var1<<endl; cout<<"name ="<<name<<endl; } }; int main(); { ABC abc; //abc.var1 = 20; abc.name = "sth"; abc.display(); } |
Вывод данных:
var1 =10 name =sth |
В этой программе у нас есть два члена данных, из которых var1 типа int является закрытым (спецификатор доступа не указан. По умолчанию является закрытым). Другим элементом является имя строки, которое объявлено как общедоступное. У нас есть еще одна функция display, которая отображает значение обоих этих членов.
В основной функции мы объявляем объект abc класса ABC. Затем мы устанавливаем значения для элементов данных, а также отображаем функцию вызова, используя объект «abc».
Однако когда компилятор встречает строку abc.var1 = 20; он выдаст ошибку, что «var1 является частной переменной».
Это связано с тем, что мы не можем получить доступ к закрытым членам данных класса вне класса. Таким образом возникает ошибка. Но мы можем получить к нему доступ внутри функции и, поэтому, когда мы выводим значение var1 в функции отображения; он не выдает никакой ошибки.
Следовательно, вывод данных программы отображает начальное значение, с которым объявлена переменная var1.
А теперь, чтобы лучше понять вышесказанное рассмотрим еще один пример учебного класса. Этот класс имеет элементы данных: student_id, student_name и student_age. Он также имеет функции-члены для чтения информации о учащемся и отображения информации об учащемся.
Чтобы упростить задачу читателям, мы объявили всех членов класса общедоступными.
Данная программа показывает полную реализацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#include <iostream> #include <string> using namespace std; class student{ public: int student_id; string student_name; int student_age; void read_studentInfo(); void print_studentInfo() { cout<<"\nStudent ID : "<<student_id<<endl; cout<<"Student name : "<<student_name<<endl; cout<<"Student Age : "<<student_age; } }; void student::read_studentInfo(){ cout<<"Enter Student Id :"; cin>>student_id; cout<<"\nEnter student_name :"; cin>>student_name; cout<<"\nEnter student_age :"; cin>>student_age; } int main() { student s1; s1.read_studentInfo(); s1.print_studentInfo(); } |
Вывод данных:
Enter Student Id :1 Enter student_name :abc Enter student_age :12 |
ID учащегося: 1
Имя учащегося: abc
Возраст учащегося: 12
Таким образом, у нас есть полный класс, определенный выше. Единственное заметное отличие состоит в том, что мы определили одну функцию «print_studentInfo» внутри класса, тогда как другая функция «read_studentinfo» определена вне класса. Это два способа определения функций-членов для класса.
Обратите внимание, что функция, определенная снаружи, по-прежнему имеет объявление/прототип внутри класса. Кроме того, она определяется вне класса с помощью оператора разрешения области видимости (::) . Затем в основной функции мы создаем объект класса ученика, а затем вызываем функции для чтения и отображения данных.
Конструкторы
В этом разделе мы рассмотрим специальную функцию, которая используется для инициализации объекта при его создании. Эта специальная функция называется конструктором.
Конструктор — это функция-член класса, но она отличается от обычной функции-члена следующим образом:
- Конструктор не имеет возвращаемого значения, т.е. конструктор никогда не возвращает значение.
- Конструктор — общедоступная функция-член класса.
- Конструктор используется для инициализации элементов данных и создания объекта класса.
- Конструктор автоматически вызывается компилятором при создании объекта.
Типы конструкторов
C++ поддерживает следующие типы конструкторов:
1) Конструктор по умолчанию
Конструктор по умолчанию является базовым конструктором и не имеет параметров. Мы можем создать простой объект без каких-либо параметров, используя конструктор по умолчанию.
Конструктор по умолчанию имеет следующий синтаксис:
classname() { //код конструктора } |
Если у класса нет конструктора по умолчанию, компилятор создаст его сам.
2) Параметризованный конструктор
Параметризованный конструктор имеет список параметров, с помощью которых мы можем инициализировать члены класса. Когда мы объявляем объект в параметризованном конструкторе, нам нужно передать начальные значения в функцию-конструктор в качестве параметров.
Функция параметризованного конструктора выглядит так:
classname(argument list){ //код конструктора } |
Параметризованный конструктор используется для перегрузки конструкторов. Параметризованный конструктор используется для инициализации элементов данных различных объектов. При этом мы можем передавать разные значения членов данных разным объектам.
3) Конструкторы копирования
C++ поддерживает третий тип конструктора, известный как конструктор копирования. Вот его общая форма:
classname (const classname& obj); |
Как показано в объявлении выше, в конструкторе копирования новый объект создается с использованием значений другого объекта того же класса. Параметр, который передается конструктору, является постоянной ссылкой на объект, значения которого будут использоваться для построения нового объекта.
Конструктор копирования обычно вызывается в следующих ситуациях:
- Когда объект класса возвращается по значению.
- Когда объект передается функции в качестве аргумента и передается по значению.
- Когда объект создается из другого объекта того же класса.
- Когда временный объект создается компилятором.
Однако мы не можем гарантировать, что конструктор копирования обязательно будет вызван во всех вышеперечисленных случаях, поскольку у компилятора C++ есть способ оптимизировать операции копирования.
Конструктор копирования выполняет почленное копирование между объектами. Как и конструктор по умолчанию, компилятор C++ создает конструктор копирования по умолчанию, если мы не предоставляем его в нашей программе. Но когда у класса есть определенные элементы данных, такие как указатели, ссылки или любое распределение ресурсов во время выполнения, то нам понадобится собственный определяемый пользователем конструктор копирования.
Причина в том, что конструктор копирования по умолчанию выполняет только поверхностное копирование элементов данных, т. е. оба объекта будут совместно использовать одно и то же место в памяти. Это нормально для простых элементов данных без указателей.
Однако, когда дело доходит до указателей или любых других элементов динамических данных, мы хотели бы, чтобы данные указывали на новое место в памяти. Это глубокая копия, и ее можно получить только с помощью пользовательского конструктора копирования.
Ниже приведена полная программа на C++, которая реализует все три типа конструкторов и их использование при построении объекта:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
#include <iostream> #include <string> using namespace std; class student{ public: int student_id; string student_name; int student_age; //конструктор по умолчанию student(){ student_id = 1; student_name = "abc"; student_age = 10; } //параметризованный конструктор student(int id,string name,int age){ student_id = id; student_name = name; student_age = age; } //конструктор копирования student(const student& st){ student_id = st.student_id; student_name = st.student_name; student_age = st.student_age; } void print_studentInfo() { cout<<"\nStudent ID : "<<student_id<<endl; cout<<"Student name : "<<student_name<<endl; cout<<"Student Age : "<<student_age<<endl; } }; int main() { student s; cout<<"********** s **********"; s.print_studentInfo(); student s1(2,"xyz",12); cout<<endl; cout<<"********** s2 **********"; student s2 = s1; //copy constructor s2.print_studentInfo(); } |
Вывод данных:
********** s ********** Student ID : 1 Student name : abc Student Age : 10 ********** s2 ********** Student ID : 2 Student name : xyz Student Age : 12 |
В этой программе мы определили ученика класса, аналогичного тому, который был определен в предыдущей программе. Разница в том, что вместо чтения значений элементов данных из стандартного ввода через функцию мы определяем три конструктора.
Вполне возможно, что у класса может быть более одного конструктора. У нас есть конструктор по умолчанию, который инициализирует члены данных начальными значениями. Затем мы определяем параметризованный конструктор, который передает конструктору начальные значения в качестве параметров.
Затем мы определяем конструктор копирования, которому мы передаем постоянную ссылку на объект класса ученика.
В основной функции мы создаем три объекта отдельно, используя три конструктора. Первый объект s создается с помощью конструктора по умолчанию. Второй объект s1 создается с помощью параметризованного конструктора, а третий объект s2 создается с помощью конструктора копирования.
Обратите внимание на создание третьего объекта s2. Здесь мы присваиваем уже созданный объект s1 новому объекту s2. Таким образом, когда мы создаем новый объект, используя уже существующий объект, компилятор вызывает конструктор копирования.
Оператор присваивания
Мы также можем присвоить значения одного объекта другому, используя оператор присваивания (=). В этом случае у нас будет утверждение вроде s1 = s.
Разница между конструктором копирования и оператором присваивания заключается в том, что в то время как конструктор копирования конструирует совершенно новый объект, оператор присваивания просто присваивает значения члена объекта в правой части объекту в левой части.
Деструкторы
Деструктор также является специальной функцией, как и конструктор, но он реализует функции, прямо противоположные конструктору. В то время как конструктор используется для создания объекта, деструктор используется для уничтожения или удаления объекта.
Вот некоторые из характеристик деструктора :
- Имя деструктора совпадает с именем класса, но начинается со знака тильды (~).
- Деструктор не имеет возвращаемого типа.
- Деструктор не имеет аргументов.
- В классе может быть только один деструктор.
- Компилятор всегда создает деструктор по умолчанию, если мы не можем предоставить его для класса.
Общий синтаксис деструктора:
~classname(){ //код удаления } |
Деструктор класса обычно вызывается в следующих ситуациях:
- Когда объект выходит из области видимости, автоматически вызывается деструктор класса.
- Точно так же деструктор вызывается, когда программа завершает выполнение. Это означает, что все объекты также перестают существовать. Следовательно, будет вызываться деструктор каждого объекта.
- Деструктор класса также вызывается при выполнении оператора «delete» для удаления объекта.
- Мы также можем вызвать деструктор для выполнения любых действий по очистке после того, как мы закончим с функциональностью объекта.
Пример, приведенный ниже, демонстрирует работу деструктора:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> using namespace std; class sample{ public: sample(){ cout<<"Constructor::sample called"<<endl<<endl; } ~sample(){ cout<<"Destructor::~sample called"<<endl; } void display(){ cout<<"This is sample class"<<endl<<endl; } }; int main(){ sample obj; obj.display(); return 0; } |
Вывод данных:
Constructor::sample called This is sample class Destructor::~sample called |
Мы определили образец класса, в котором мы определили конструктор, деструктор и функцию отображения. В основной функции мы создаем объект obj образца класса, а затем вызываем функцию отображения на этом объекте.
После этого выполняется возврат 0. В выводе мы видим, что в тот момент, когда функция отображения возвращается, а управление программой приходит к оператору return 0, выполняется деструктор. Это означает, что он выполняется в тот момент, когда объект выходит из области видимости.
Указатель «this»
C++ использует специальную концепцию, относящуюся к объектам, известную как указатель this. Указатель this всегда указывает на текущий объект. Таким образом, в зависимости от ситуации, всякий раз, когда нам нужно обратиться к текущему объекту, мы используем указатель «this».
Вы должны помнить, что каждый раз, когда создается экземпляр класса, т.е. объект, для объекта создается отдельная копия элементов данных класса. Но когда дело доходит до функций-членов класса, все объекты имеют одну и ту же копию.
Итак, когда один или несколько объектов одновременно обращаются к функциям-членам, то как мы можем гарантировать, что функции-члены обращаются к нужным членам данных и изменяют их?
Это место, где вступает в действие указатель this. Компилятор передает неявный указатель с именем функции «this».
Указатель this передается в качестве скрытого аргумента всем вызовам функций-членов. Обычно это локальная переменная. Следовательно, указатель «this» является константным указателем, а его содержимое — адресом памяти текущего объекта.
Обратите внимание, что этот указатель доступен только для нестатических функций-членов, а не для статических функций. Это связано с тем, что к статическим функциям не нужно обращаться с помощью объекта. К ним можно получить прямой доступ, используя имя класса.
Обычно мы используем указатель «this» в ситуациях, когда переменные-члены и параметры передаются для инициализации переменных-членов с одинаковыми именами. Мы также используем его, когда нам нужно вернуть текущий объект из функции.
Давайте посмотрим на программу с использованием указателя «this» ниже:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#include <iostream> using namespace std; class Sample { private: int num; char ch; public: Sample &setParam(int num, char ch){ this->num =num; this->ch = ch; return *this; } void printValues(){ cout<<"num = "<<num<<endl; cout<<"ch = "<<ch; } }; int main(){ Sample obj; obj.setParam(100, 'A'); obj.printValues(); return 0; } |
Вывод данных:
num = 100 ch = A |
В приведенной выше программе у нас есть класс Sample с двумя элементами данных num и ch. У нас есть функция-член setParam, которая передает параметры с теми же именами, num и ch, чтобы установить значения переменных-членов.
Внутри функции мы присваиваем эти значения текущим переменным-членам объекта, указанным этим указателем. Как только значения установлены, текущий объект «this» возвращается из функции.
В основной функции мы сначала создаем объект класса Sample, obj и вызываем функцию setParam для установки значений, а затем вызываем функцию printValues для печати значений.
Итог
Понимание классов и объектов является первичным требованием для начала ООП в C++. Мы также, как можно подробно, рассказали о конструкторах и деструкторах на примерах.
В нашей следующей статье мы расскажем вам о списках инициализаторов в C++, и приведем конкретные примеры.
С Уважением, МониторБанк