Nickolay.info. Обучение. Лекции по Си. Глава 13

13. Классы

 

Формально описание класса на С++ выглядит следующим образом:

class ИмяКласса {

 private:

  Список членов класса;

 protected:

  Список членов класса;

 public:

  Список членов класса;

};

Список членов класса включает описание типов и имен как данных, так и функций. Переменные, перечисляемые в классе, по умолчанию имеют область видимости в пределах класса (private), и доступ к ним имеют только функциичлены данного класса. Обычно функциичлены имеют тип доступа public, т.е., видимы вне класса, к ним может осуществляться доступ извне. Атрибут protected  назначается тем членам класса, которые могут использоваться методами данного и производных от него классов.

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

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

#include <stdio.h>

#include <stdlib.h>

class cList { //Класс для описания списка

 unsigned int item;

 cList *next;

  // Указатель на следующий элемент

public:

 void show(); // Просмотр списка

 cList();//Конструктор по умолчанию -

         //сгенерировать item случайно

 cList (FILE *);     

         //Другой конструктор - прочитать

         //элемент из файла

 cList (unsigned int);

         //Третий конструктор -

         //ввести значение с клавиатуры

 ~cList();   // Деструктор

 void Start (); //Прототипы

 void End();    //методов класса

};

cList *first=NULL;  //начало списка

void cList::show() {

 cList *p; int i=1;

 for (p=first; p !=NULL; p=p->next) {

  printf ("\n Элемент %d) %u",i++,p->item);

 }

}

cList::cList() {

 randomize();

 item=random(32000);

 next=this;

}

cList::cList(FILE *f) {

 unsigned int n=0;

 fscanf (f,"%u",&n);

 fclose (f);

 item=n;

 next=this;

}

cList::cList(unsigned int n) {

 item = n;

 next = this;

}

void cList::Start () {

 this->next = first;

 first = this;

}

void cList::End () {

 cList *sled,*pred;

 for (sled=first,pred=NULL;

  sled !=NULL; pred=sled,sled=sled->next);

 pred->next = this;

 this->next = NULL;

 sled = this;

}

void main () {

 cList *List1 = new cList(3);

 List1->Start();

 cList *List2 = new cList ();

 List2->End();

 List1->show();

 // ...

}

Оператор . (точка), как и в Delphi, позволяет связать функцию с конкретным экземпляром класса. При использовании указателей, как и со структурами, конструкция (*указатель).поле заменяется на указатель->поле.

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

В Си++ разрешено описание прототипов функций с формальными параметрами, имеющими значение по умолчанию. Такие параметры должны быть последними в списке при объявлении функции:

int f (int i,int k=5) {

 //тело функции

}

Вызвать функцию f() можно, например, так:

f(i,j); //формальный параметр

        //i=фактическому i, k=j

f(1);   //i=1, k=5

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

Конструктор копирования для класса X имеет 1 аргумент типа X и, возможно, параметры по умолчанию, например

class X {

 public:

  X() { ... } //конструктор по умолчанию

  X(const &X) { ... }

   //конструктор копирования

  X(const &X,int=4) { ... }

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

   //с параметром по умолчанию

}

Конструктор копирования вызывается, когда происходит копирование объекта, обычно при объявлении с инициализацией:

X one;    //вызван конструктор по умолчанию

X two=one; //вызван конструктор копирования

Функциядеструктор разрушает объект данного класса и вызывается явно или неявно. Неявный вызов деструктора связан с прекращением существования объекта из‑за завершения области его определения. Явное уничтожение объекта выполняет оператор delete. Деструктор имеет то же имя, что класс, но предваренное символом ~. Деструкторы не могут получать аргументы и быть перегружены. Класс может объявить только один общедоступный деструктор. Если класс не содержит объявления деструктора, компилятор автоматически создаст его.

Как и для обычных функций, для членов класса поддерживается "перегрузка", то есть, использование одинаковых имен для функций, отличающихся числом и типом параметров. Функции не могут перегружаться, если они отличаются только типом возвращаемого значения, но не списком параметров, а также, если их типы аргументов неявно совпадают (int и int &).

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

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

+ - * / = < > += -= *= /= << >> <<= >>= == != <= >= ++ -- % & ^ ! | ~ &= ^= != && || %= [] () new delete

Смысл перегрузки оператора в том, что создается функция, выполняемая каждый раз, когда в контексте класса встречается этот оператор. Синтаксис определения перегрузки оператора таков:

ИмяТипа operator СимволОперации

 (СписокПараметров)

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

class ИмяПроизводногоКласса : БазовыйСписок {

 СписокЧленов;

}

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

Производный класс наследует все члены перечисленных базовых классов, но может использовать только члены с атрибутом public или protected.

Эти и другие особенности классов на Си++ рассмотрим на примере. Код ниже представляет класс Person и расширение класса Student, а также демонстрирует создание экземпляров этих классов.

#include <stdio.h>

#include <string.h>

#include <alloc.h>

class Person {

 private: //Данные объекта; атрибут,

          //применяемый по умолчанию

  char *Name;

  int Age;

  friend Person *upcase (Person *p);

   //эта функция - не член класса, но его

   //друг - она имеет полный доступ

   //к его членам

 protected: //Данные и методы могут

     //использоваться функциями-членами и

     //друзьями класса, для которого данный

     //класс является базовым

  char *putName (char *name);

 public:   

      //Данные и методы, доступные извне

  static int Count;

    //Статический член класса -

    //один для всех его экземпляров!

  Person () : Name (NULL), Age (0) {

   //Конструктор без аргументов

   //аргументы установлены по умолчанию

   Name = NULL;

    //Тело конструктора встроено

   Count++;

  }

  Person (char *name, int age);

   //Прототип конструктора с аргументами

  ~Person ();

   //У класса есть деструктор,

   //освобождающий память, занятую объектом

  Person operator + (char *);

   //Переопределение оператора +

   //для сложения строк -

   //действует в этом классе

   //на входе - параметр-указатель,

   //на выходе - новый объект Person

  //Методы класса:

  void Print (); //Перегружаемая функция,

  Person Print (Person *p);

   //т.е. у нас нес-ко функций с 1 именем

   //и разными аргументами

  void Input ();

  inline char * getName () {

   return Name;   //Встроенная функция

  }; //ее код каждый раз подставляется

     //в точку вызова

  inline int getAge () { return Age; };

};

//Описание производного класса:

class Student : public Person {

  // : - оператор наследования

  //Класс, порожденный от Person,

  //наследует его члены и может

  //использовать члены с атрибутом public

 protected:

  int Group;

 public:

  Student (char *name=NULL, int age=0,

   int group=0) :

   Group(group),Person(name,age) {}

   //Конструктор со значениями аргументов

   //по умолчанию и вызовом конструктора

   //родительского класса Person

  void Print ();

   // Метод производного класса

};

 int Person::Count=0;

 //один раз инициализировали статический

 //член вне описания класса!

// Оператор Класс :: показывает, что

//объект относится к классу:

Person :: Person (char *name, int age) {

  //Тело конструктора с аргументами

 this->Age = age;

  //this - указатель на текущий объект

  //из метода класса

  //неявно передается в любую нестатическую

  //функцию класса

 this->Name=putName (name);

 Count++;

}

Person :: ~Person () {

 //Деструктор всегда без аргументов,

 //не м.б. перегружен

 free (Name);

}

//Служебная функция:

char * Person :: putName (char *name) {

 Name = (char *) calloc

  (strlen(name),sizeof(char));

 if (Name!=NULL) strcpy (Name, name);

 return Name;

}

inline void Person :: Print () {

 //Эта функция - встроенная в класс

 printf ("\n%s (%d)",Name,Age);

}

Person Person :: Print (Person *p) {

 printf ("\nOther print: %s, %d",

  p->Name, p->Age);

 return *p;

}

//Перегрузка оператора

Person Person :: operator + (char *s) {

 Person temp; char name[80];

 strcpy(name,strcat (this->Name,s));

 temp.Name = putName(name);

 temp.Age=this->Age;

 return temp;

}

void Person :: Input () {

 //Создаем объект и возвращаем его

 char s[80];

 printf ("\nName? ");

 scanf ("%s",s);

 strcpy (Name,s);

 printf ("\nAge? ");

 scanf ("%d",&Age);

 putName (s);

}

Person *upcase (Person *p) {

 //функция-друг класса

 strupr (p->Name);

 return p;

}

void Student :: Print () {

 printf ("\n%s (%d,%d)",

  getName(),getAge(),Group);

}

void main () {

 Person *First = new Person ();

  //new вернет указатель на новый объект

  //происходит вызов конструктора

 First->Print ();

 delete First; //удаляем объект, неявно

               //вызвав деструктор

 Person *Second = new Person ("Ivanov",20);

 Second->Print ();

 Person Third; Third.Input ();

 upcase (&Third); Third.Print (&Third);

 Person Next = Third + " .N";

 Next.Print ();

 printf ("\nAll: %d",Person::Count);

 delete &Next;

 Student *Test = new Student ("Newer",

  Second->getAge(),319);

 Test->Print (); }

 

Рейтинг@Mail.ru
вверх гостевая; E-mail