Классы. первое знакомство

Простейшее определение класса без наследования имеет вид:

class имя_класса {

// по умолчанию раздел private – частные члены класса

public: //открытые функции и переменные класса

};

Определение класса соответствует введению нового типа данных, а понятие переменной данного типа – понятию объекта (экземпляра) класса. Список членов класса включает определение данных и функций, те из них, чьи объявления находятся в описании класса, называются функциями-членами класса. В ООП для таких функций используется термин “методы класса”. Классы могут также содержать определения функций, тело которых помещается в определение класса (inline-функции). Для инициализации/уничтожения объектов используются специальные функции-конструкторы с тем же именем, что и класс, и деструкторы, с именем класса, перед которым стоит символ “~”.

Переменные, объявленные в разделе класса по умолчанию как частные (private), имеют область видимости в пределах класса. Их можно сделать видимыми вне класса, если поставить перед началом объявления слово public.

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

Классами в С++ являются также структуры (struct) и объединения (union).Отличием структуры и объединения от класса является то, что их члены по умолчанию открытые (public), а не закрытые, как у класса. Это обеспечивает преемственность с языком C. Кроме того, структуры и объединения не могут наследоваться и наследовать.

В следующей программе определяется простейший класс Strtype, членами которого являются массив strтипа char и функции set(),show(), get().

#include <iostream.h> //пример 10

#include <string.h>

#include <conio.h>

class Strtype{

char str[80]; //private

public:

void set (char *);//задать str

void show(); //вывести str

char* get(); //вернуть str

}; //конец определения класса

void Strtype::set(char *s) {// определение метода set()

strcpy(str,s);}//копирование s в str

void Strtype::show() {// определение метода show()

cout<<str<<endl;}

char * Strtype::get() {// определение метода get()

return str;}

int main() {

Strtype obstr; //объявление объекта

obstr.set("bel.university ");//вызов метода set()

obstr.show();//вызов метода show()

cout<<obstr.get()<<endl;

while(!kbhit());//задержка выхода до нажатия клавиши

return 0;

}

Вывод: bel.university

bel.university

Массив член-класса str – является частным (private), доступ к нему возможен только через функции-члены класса. Такое объединение в классе сокрытых данных и открытых функций и есть инкапсуляция. Здесь obstr – объект данного класса. Вызов функции осуществляется из объекта добавлением к имени объекта имени функции, через точку или –>, если используется указатель на объект.

Приведем примеры нескольких объявлений объектов:

Strtype a; //объект a

Strtype x[100]; //массив объектов

Strtype *p; //указатель на объект

p=new Strtype; // создание динамического объекта

a.set("строка"); // вызов функций

x[i].set("строка");

p->set("строка");

Оператор расширения области видимости “::”указываетдоступ к элементам класса. Например, Strtype::set(char *s) означает принадлежность функции set(char *s) области видимости класса Strtype. Кроме этого оператор “::”используется для доступа к данным класса (оператор вида Strtype::count), для указания внешней или глобальной области видимости переменной, скрытой локальным контекстом (оператор вида ::globalName).

Следующая программа демонстрирует создание класса Stack на основе динамического массива. Для инициализации объекта класса используется метод Stack() –конструктор класса.

#include <iostream.h> // пример 11

#include <conio.h>

#define SIZE 10

// объявление класса Stack для символов

class Stack {

char *stck; // содержит стек

int tos; // индекс вершины стека

public:

Stack(); //конструктор

~Stack(){delete [] stck;}//деструктор

void push(char ch); // помещает в стек символ

char pop(); // выталкивает из стека символ

};

// инициализация стека

Stack::Stack(){

stck=new char[SIZE];//динамический массив

tos=0;

cout << "работа конструктора … \n";

}

// помещение символа в стек

void Stack::push(char ch){

if (tos==SIZE) {cout << "стек полон";return; }

stck[tos]=ch;

tos++;

}

// выталкивание символа из стека

char Stack::pop(){

if (tos==0) {cout << "стек пуст";return 0;}

tos--;

return stck[tos];

}

int main(){

/*образование двух автоматически инициализируемых стеков */

Stack s1, s2; //вызов конструктора для s1 и s2

int i;

s1.push('a');s2.push('x');

s1.push('b');s2.push('y');

s1.push('c');s2.push('z');

for(i=0;i<3;i++) cout<<"символ из s1:"<<s1.pop() << "\n";

for(i=0;i<3;i++) cout<<"символ из s2:"<<s2.pop() << "\n";

while(!kbhit());//задержка

return 0;

}

Вывод:

Работа конструктора …

Работа конструктора …

Символ из s1:c

Символ из s1:b

Символ из s1:a

Символ из s2:z

Символ из s1:y

Символ из s1:x

Конструктор Stack() вызывается автоматически при создании объектов класса s1,s2и выполняет инициализацию объектов, состоящую из выделения памяти для динамического массива и установки указателя на вершину стека в нуль. Конструктор может иметь аргументы, но не имеет возвращаемого значения и может быть перегружаемым.

Деструктор ~Stack() выполняет действия необходимые для корректного завершения работы с объектом, а именно, в деструкторе может освобождаться динамически выделенная память, закрываться соединения с файлами, и др. Он не имеет аргументов. Именем деструктора является имя класса, перед которым стоит знак “~” – тильда.

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

#include <iostream.h>// пример 12

#include <conio.h>

struct Node{// объявление класса Node

int info; //информационное поле

Node* next; // указатель на следующий элемент

};

class List {// объявление класса List

Node* top; // указатель на начало списка

public:

List(){ // конструктор

top=0;

cout<<"\nkonstructor:\n";};

~List(){// деструктор

release();

cout<<"\ndestructor:\n";}

void push(int);// добавление элемента

void del() { // удаление элемента

Node* temp = top;

top = top->next;

delete temp; }

void show();

void release();// удаление всех элементов

};

void List::push(int i){

Node* temp = new Node;

temp->info = i;

temp->next =top;

top=temp; }

void List::show() {

Node* temp=top;

while (temp!=0) {

cout<<temp->info<<"->";

temp=temp->next;}

cout<<endl;}

void List::release() {

while (top!=0)del();}

int main(){

List *p;//объявление указателя на LIst

List st;//объявление объекта класса List

int n = 0;

cout << "Input an integer until 999: ";

do {//добавление в список, пока не введено 999

cin >> n; st.push(n);

} while(n != 999);

st.show();

st.del();

p=&st;

p->show();

while(!kbhit());

return 0;

}

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

class ClassA;

Важнейшим принципом ООП является наследование. Класс, который наследуется, называется базовым, а наследуемый – производным. В следующем примере класс Derived наследует компоненты класса Base, точнее компоненты раздела public, которые остаются открытыми, и компоненты раздела protected(защищенный), которые остаются закрытыми. Компоненты раздела private, также наследуются, но являются не доступными напрямую для производного класса. Производному классу доступны все данные и методы базового класса, наследуемые из разделов public и protected. Для переопределенных методов в производном классе действует принцип полиморфизма, который будет рассмотрен ниже. Объекту базового класса можно присвоить объект производного, указателю на базовый класс – значение указателя на производный класс. В этом случае через указатель на базовый класс можно получить доступ только к полям и функциям базового класса. Для доступа к полям и функциям порожденного класса следует привести (преобразовать) ссылку на базовый класс к ссылке на порожденный класс.

#include <iostream.h> // пример 12

#include <conio.h>

class Base { // определение базового класса

int i; //private по умолчанию

protected:

int k;

public:

Base(){i=0; k=1;}

void set_i(int n); // установка i

int get_i(){ // возврат i

return i;}

void show(){

cout<<i<<" "<<k<<endl;}

}; //конец Base

class Derived : public Base { // производный класс

int j;

public:

void set_j(int n);

int mul(); //умножение i на к базового класса и на j производного

}; //конец Derived

//установка значения i в базовом классе

void Base::set_i(int n){

i = n; }

//установка значения j в производном классе

void Derived::set_j(int n){

j = n;}

//возврат i*k из Base умноженного на j из Derived

int Derived::mul(){

/*производный класс наследует функции-члены базового класса*/

return j * get_i()*k;//вызов get_i() базового класса

}

int main(){

Derived ob;

ob.set_i(10); //загрузка i в Base

ob.set_j(4); // загрузка j в Derived

cout << ob.mul()<<endl; //вывод числа 40

ob.show(); //вывод i и k, 10 1

Base bob=ob;//присваивание объекта ссылке на базовой тип

cout<<bob.get_i(); // вывод i

while (!kbhit());

return 0;

}

Переменная i недоступна в производном классе, переменная k доступна, поскольку находится в разделе protected. В производном классе наследуются также функции get_i(), set_i() и show() классаBaseиз раздела public. Функция show() позволяет получить доступ из производного класса к закрытой переменной i производного класса. В результате выводится 40 10 1 10.

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

2. КЛАССЫ И ФУНКЦИИ

При реализации класса используются функции-члены класса (методы класса), функции-“друзья” класса, конструкторы, деструкторы, функции-операторы. Функции-члены класса объявляются внутри класса. Определение функции обычно помещается вне класса. При этом перед именем функции помещается операция доступа к области видимости имя_класса::. Таким образом, определение функции-члена класса имеет вид:

тип имя_класса:: имя_функции (список аргументов) { }

Определение функции может находиться и внутри класса вслед за его объявлением. Такие функции называются inline-функциями. Рассмотрим пример:

#include <iostream.h> // пример 20

#include <string.h>

#include <stdlib.h>

#include <conio.h>

#define SIZE 255

class Strtype {

char *p;

int len;

public:

Strtype(){// инициализация объекта, inline конструктор

p=new char [SIZE];

if(!p) {cout << "ошибка выделения памяти\n";

exit(1);}

*p='\0';

len=0;

}

~Strtype(); //деструктор

void set(char *ptr);

char *get(){return p;}

void show();

};

// освобождение памяти при удалении объекта строка

inline Strtype::~Strtype(){ //inline-деструктор

cout << "освобождение p\n";

delrete [] p;

}

void Strtype::set(char *ptr) {

if(strlen(ptr) >= SIZE) {

cout << "строка слишком велика \n";

return;

}

strcpy(p, ptr);

len = strlen(p);

}

void Strtype::show(){

cout << p << " длина : " << len<< "\n";

}

int main(){

{Strtype s1,s2;

s1.set("This is a test");

s2.set("I love C++");

s1.show();

s2.show();

cout<<s2.get()<<endl; }

while (!kbhit());

return 0;

}

В результате будет выведено:

This is a test длина: 14

I love C++ длина: 10

I love C++

Освобождение р

Освобождение р

Конструктор Strtype() и функция get() являются inline-функциями. Деструктор ~Strtype() также является inline – функцией, хотя и определяется вне класса. Ключевое слово inlineсодержит указание компилятору создать код функции, подставляемый в точку вызова. При этом время вызова сокращается, хотя код может увеличиться.

Вызов функций-членов класса осуществляется одним из двух способов:

имя_объекта.имя_функции(аргументы);

указатель_на_объект -> имя_функции(аргументы);

Еще раз обращаем внимание на то, что при определении функции- члена класса перед именем функции стоит имя класса, а при вызове –имя объекта класса.

Наши рекомендации