Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП_Лекции 2010.doc
Скачиваний:
69
Добавлен:
17.03.2015
Размер:
954.37 Кб
Скачать
    1. Определение класса

В C++ так же, как и в других языках программирования, класс - это структурный тип, используемый для описания некоторого множества объектов предметной области, имеющих общие свойства и поведение. Он объявляется следующим образом:

class <имя класса>{

private: <внутренние (недоступные) компоненты класса>

protected: <защищенные компоненты класса>

public: <общие (доступные) компоненты класса>

};

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

Компоненты класса, объявленные в секции private, называются внутренними. Они доступны только компонентным функциям того же класса и функциям, объявленным дружественными (см. § 3.5) описываемому классу.

Компоненты класса, объявленные в секции protected, называются защищенными. Они доступны компонентным функциям не только данного класса, но и его потомков. При отсутствии наследования — интерпретируются как внутренние.

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

Если при описании секции класса тип доступа к компонентам не указан, то по умолчанию принимается тип private.

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

<тип функции>.<имя класса>::<имя функции>(<список параметров>) {<тело компонентной функции>}

По правилам C++, если тело компонентной функции размещено в описании класса, то эта функция по умолчанию считается встраиваемой (inline). Коды таких функций компилятор помещает непосредственно в место вызова, что значительно ускоряет работу. Так, компонентные функции класса, описанного ниже, являются встраиваемыми по умолчанию:

#include <stdio.h>

#include <conio.h>

class X {

private: char c;

public: int x,y;

/* встраиваемые компонентные функции, определенные внутри класса */

void print (void)

{ clrscr(); gotoxy(x,y); printf ("%c", c);

x=x+10; y=y+5; gotoxy(x,y); printf ("%c", c); }

void set_X(char ach,int ax,int ay) { c=ach; x=ax; y=ay; }

};

Встраиваемую компонентную функцию можно описать и вне определения класса, добавив к заголовку функции описатель inline.

При определении компонентных функций следует иметь в виду, что не разрешается объявлять встраиваемыми:

функции, содержащие циклы, ассемблерные вставки или переключатели;

рекурсивные функции;

виртуальные функции.

Тела таких функций обязательно размещают вне определения класса. Например:

class Y{

int х,у;

public: int k; char l;

void print(void); // прототипы компонентных функций

void set_Y(char al,int ax,int ay,int ak);

};

// описание встраиваемой компонентной функции вне определения класса

inline void Y::set_Y(char al,int ax=40,int ay=15,int ak=15)

{ x=ax; y=ay; k=ak; l=al;}

/* описание компонентной функции print() содержит цикл, значит

использовать режим inline нельзя */

void Y::print() {clrscr(); gotoxy(x,y); for(int i=0;i<k;i++) printf(" %c",l); }

Рассмотрим пример определения класса.

        1. Определение класса (класс Строка). Пусть требуется описать класс, который обеспечит инициализацию, хранение и вывод строки:

#include <iostream.h>

#include <string.h>

class String { // начало описания класса

private: char str[25]; // поле класса - строка из 25 символов

public :

// прототипы компонентных функций (методов)

void set_str (char *); // инициализация строки

void display_str(void); // вывод строки на экран

char * return_str(void); // получение содержимого строки

};

// описание компонентных функций вне класса

void String::set_str(char * s) { strcpy(str,s);}

void String::display_str(void) { cout << str << endl;}

char * String::return_str(void) {return str;}

Определение класса можно поместить перед текстом программы или записать в отдельный файл, который подключают к программе с помощью директив компилятора include:

если файл находится в текущем каталоге - #include "имя файла";

если файл находится в каталогах автоматического поиска - #include <имя файлах

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

<имя класса> <список объектов или указателей на объект>;

Например:

String a, *b, c[6]; /*определяет: объект a класса String, указатель b на объект класса String и массив c из шести объектов класса String*/

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

<имя объекта>.<имя класса>::<имя поля или функции>;

Например:

a.String::str;

b-> String::set_str(st1);

с[i].String::display_str();

Однако чаще доступ к компонентам объекта обеспечивается с помощью укороченного имени, в котором имя класса и двойное двоеточие опускают. В этом случае доступ к полям и методам осуществляется по аналогии с обращением к полям структур:

<имя объекта>.<имя поля или функции>

<имя указателя на объект> -> <имя поля или функции>

<имя объекта>[<индекс>].<имя поля или функции>

Например:

a.str

b->str

c[i].str

a.display_str()

b-> display_str()

c[i].display_str()

Первая строка демонстрирует обращение к полям простого объекта, объекта, описанного как указатель на объект, и элемента массива объектов. Вторая строка - обращение к методам соответствующих объектов.

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

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

глобальные и локальные статические объекты создаются до вызова функции main и уничтожаются по завершении программы;

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

объект, память под который выделяется динамически, создается функцией new и уничтожается функцией delete.

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

Значение может заноситься в поле объекта во время выполнения программы несколькими способами:

непосредственным присваиванием значения полю объекта;

внутри любой компонентной функции используемого класса;

согласно общим правилам C++ с использованием оператора инициализации.

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

Инициализация полей, описанных в секциях private и protected, возможна только с помощью компонентной функции.

        1. Различные способы инициализации полей объекта

#include <string.h>

#include <iostream.h>

#include <conio.h>

class sstro {

public: char str1[40]; int x,y;

void set_str(char *vs) // метод инициализации полей

{strcpy(strl,vs); x=0; y=0; }

void print(void) // метод вывода содержимого полей

{ cout<<" "<<х<<" "<<у<<" "<<str1<<endl;}

void main() {

sstro aa={" пример 1 ", 200, 400 };

/* использование оператора инициализации при создании объекта */

sstro bb,cc; // создание объектов с неинициализированными полями

bb.x=200; bb.y=150; // инициализация общедоступных компонентов

strcpy(bb.str1, "пример 2"); // при прямом обращении из программы

cc.set_str(" пример 3"); // использование компонентной функции

aa.print(); bb.print(); cc.print();

}

Однако применение указанных способов является не очень удобным и часто приводит к ошибкам. Существует способ инициализации объекта с помощью специальной функции - конструктора, которая автоматически вызывается при объявлении объекта (см. § 3.2).

Значения полям объекта некоторого класса можно задать и с помощью операции присваивания ему значений полей другого, уже инициализированного, объекта того же класса. В этом случае автоматически вызывается копирующий конструктор (см. § 3.2).

Неявный параметр this. Когда компонентную функцию вызывают для конкретного объекта, этой функции автоматически (неявно) передается в качестве параметра указатель на поля того объекта, для которого она вызывается. Этот указатель имеет специальное имя this и неявно определен как константный в каждой функции класса:

<имя класса> *const this = <адрес объекта>;

В соответствии с описанием этот указатель изменять нельзя, однако в каждой принадлежащей классу функции он указывает именно на тот объект, для которого вызывают функцию. Иными словами, указатель this является дополнительным (скрытым) параметром каждой нестатической (см. далее) компонентной функции. Этот параметр используется для доступа к полям конкретного объекта. Фактически обращение к тому или иному полю объекта или его методу выглядит следующим образом:

this->pole this->str this->fun().

Причем, при объявлении некоторого объекта A выполняется операция this=&A, а при объявлении указателя на объект b - операция this=b.

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

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

        1. Использование параметра this.

#include <iostream.h>

#include <conio.h>

class ТА {

int x,y; public:

void set (int ax, int ay) { x=ax; y=ay;}

void print(void) { cout<<x<< "\t"<<y<<endl;}

ТА *fun1() // возвращает указатель на объект, для которого вызывается

{ х=у=100; return this;}

TA fun2(TA M) // возвращает объект, для которого вызывается

{ х+=М.х; у+=М.у; return *this;}

};

void main(){

clrscr();

ТА aa,bb;

aa.set( 10,20);

bb.fun1()->print(); // выводит: 100 100

aa.print(); // выводит: 1020

aa.fun2(bb).print(); // выводит: 110 120

getch();

}

Кроме того, указатель this явно используют для формирования результата при переопределении операций (см. § 3.6), так как операция переопределяется для конкретного, вызывающего ее объекта.

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

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

Инициализацию статических полей класса осуществляют обязательно вне определения класса, но с указанием описателя видимости <имя классах:. Например:

class point {

int x,y;

static int obj_count; /* статическое поле (счетчик обращений),

инициализация в этом месте не возможна */

public:

point () { x=0; y=0; obj_count++; } // обращение к статическому полю

};

int point::obj_count=0; // инициализация статического поля

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

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

<имя объекта>.<имя нестатического поля класса>

При обращении к статическим полям класса такой проблемы не возникает:

class point

{ int x,y, color; // нестатические поля

static int obj_count; // статическое поле - счетчик обращений

public:

point (){ х=0; у=0; со1оr=5; }

static void draw_pount(point &p); // статическая функция

};

int point::obj_count=0; // инициализация статического поля

void point::draw point(point &p) // имя объекта передано через параметр

{ putpixel(р.х, р.у, p.color); // обращение к нестатическому полю

obj_count++; } // обращение к статическому полю

Обращаться к статическим компонентам класса, являющимся принадлежностью всех объектов данного класса, можно, указав вместо имени объекта имя класса:

<класс>::<компонент>.

        1. Класс со статическими компонентами. В качестве примера, иллюстрирующего полезные свойства статических компонентов, рассмотрим класс, использующий статические компоненты для построения списка своих объектов (Рис. 1.2.).