Nickolay.info. Алгоритмы. Примеры на работу с классами в C++ |
Эти примеры, точней, своего рода образцы трёх лабораторных работ по C++, показывают работу с классами на этом языке. Проверено на Borland C++ 3.1, до сих пор часто используемом в учебных целях, возможно, код неоптимален, так как делался в спешке...
В первом примере требуется создать класс "Служащий" со свойствами "Имя", "Возраст" и "Рабочий стаж", а также
всеми необходимыми методами. Все описания сделаем в заголовочном файле Lab1.h
Во-первых, определим условную директиву для предотвращения повторного включения заголовочного файла в проект:
#ifndef EMPLOYEEH #define EMPLOYEEH //здесь будет всё описание класса #endif
Теперь определим поля класса, их всего три, причём поле name
для хранения имени будет указателем. Это
значит, что в конструкторе или другом методе класса мы предусмотрим выделение оперативной памяти под строку,
адресованную этим указателем:
class EMPLOYEE { private: char *name; int age; int job;
В секции public
разместим заголовки нескольких конструкторов и деструктора. Первый конструктор встроен, а
конструктор копирования (реализация присваивания объектов класса EMPLOYEE
) будет задан явно. Это нужно
потому, что в противном случае EMPLOYEE A=B
присвоит свойству A.name
только указатель на B.name
, и,
например, при разрушении объекта B
, строка, содержащая его имя, будет потеряна.
public: EMPLOYEE(): name (NULL), age (0), job (0) {} // конструктор без параметров, тело конструктора встроено EMPLOYEE (char*,int,int); // конструктор с параметрами EMPLOYEE (const EMPLOYEE&); // конструктор копирования ~EMPLOYEE(); //деструктор
Поскольку все основные свойства класса - приватные, понадобятся методы для их получения:
//методы для получения свойств класса: char * getname(); int const getage(); int const getjob();
а также методы для их установки:
//методы для установки свойств класса: void setname (char*); void setage (int); void setjob (int); void set (char*,int,int);
наконец, методы вывода будут печатать значения свойств без заголовка и с заголовком, переданным как параметр:
void show(); void show(char *); };
В файле Lab1.cpp
реализуем перечисленные свойства и методы. Для этого подключим заголовки функций
стандартных библиотек и файл Lab1.h
с описанием класса:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Lab1.h"
Оба конструктора будут делать, в сущности, одно и то же, только конструктор копирования будет брать данные из объекта-параметра, а обычному конструктору они будут переданы как отдельные формальные параметры:
EMPLOYEE :: EMPLOYEE (char *name, int age, int job) { setname (name); setage (age); setjob (job); } EMPLOYEE :: EMPLOYEE (const EMPLOYEE &src) { setname (src.name); setage (src.age); setjob (src.job); }
Деструктор просто освобождает память, выделенную под строку name
:
EMPLOYEE :: ~EMPLOYEE () { free (name); }
Методы получения свойств текущего объекта (в C++ он доступен через специальный указатель this
) также очень
просты:
char * EMPLOYEE :: getname () { return this->name; } int EMPLOYEE :: getage () { return this->age; } int EMPLOYEE :: getjob () { return this->job; }
Метод setname
выделит память под имя, переданное параметром:
void EMPLOYEE :: setname (char *name) { int n = strlen (name); if (n>0) { if (this->name) free (this->name); this->name = (char *) calloc (strlen(name),sizeof(char)); if (this->name) { strcpy (this->name,name); } } }
А остальными методам установки свойств класса достаточно простого присваивания:
void EMPLOYEE :: setage (int age) { this->age=age; } void EMPLOYEE :: setjob (int job) { this->job=job; }
Метод set
, не создавая нового объекта, может поменять свойства у объекта существующего:
void EMPLOYEE :: set (char *name,int age,int job) { setname (name); setage (age); setjob (job); }
Оба метода show
, думаю, также вполне понятны:
void EMPLOYEE :: show () { printf ("Name=%s,Age=%d,Job=%d\n",name,age,job); } void EMPLOYEE :: show (char *hdr) { puts (hdr); show(); }
Наконец, напишем файл lab1demo.cpp
, который продемонстрирует работу созданного класса.
Чтобы не зависеть от формата файла проекта, принятого в том или ином конкретном компиляторе C++,
подключим файл Lab1.cpp
также директивой #include
:
#include <stdio.h> #include "Lab1.cpp"
Основные возможные действия с объектами класса EMPLOYEE
иллюстрируются далее.
Например, конструктор копирования явно или неявно вызывается:
EMPLOYEE Popov2 = Popov;
view (Popov);
EMPLOYEE Noname = temp (Popov2);
Объекты могут размещаться как в статической, так и в динамической памяти, также из них можно создавать массивы. Наконец, бывает нужна и такая экзотика, как вызов метода объекта через указатель на компонентную функцию.
void view (EMPLOYEE e) { e.show ("Передача объекта функции по значению:"); } EMPLOYEE temp (EMPLOYEE &e) { EMPLOYEE Noname(e); Noname.setname ("Noname"); return Noname; } int main () { EMPLOYEE Popov("Popov",30,5); Popov.show("Запись \"студент Попов\":"); EMPLOYEE Popov2 = Popov; Popov2.show("Копия для записи \"Попов\":"); view (Popov); EMPLOYEE Noname = temp (Popov2); Noname.show ("Построение временного объекта как возвращаемого значения:"); EMPLOYEE firm[3]; char *Names [] = { "Иванов", "Петров", "Сидоров" }; char buf[80]; for (int i=0; i<3; i++) { firm[i].set (Names[i],20+i*5,1+i*2); sprintf(buf,"Элемент статического массива %d",i); firm[i].show (buf); } EMPLOYEE *firm2 = new EMPLOYEE [3]; for (i=0; i<3; i++) { firm2[i].set (Names[i],20+i*5,1+i*2); sprintf(buf,"Элемент динамического массива %d",i); firm2[i].show (buf); } void (EMPLOYEE::*pf)(char *)=&EMPLOYEE::show; (firm2[2].*pf)("Вызов через указатель на компонентную функцию"); return 0; }
Скачать код примера можно по ссылке:
Файлы примера 1 в архиве ZIP (12 Кб)
Второй пример показывает иерархию классов. Например, напишем класс "Животное" (ANIMAL
) с дочерними от него
"Млекопитающим" (MAMMAL
) и "Птицей" (BIRD
), а у млекопитающего будет ещё класс-наследник "Парнокопытное"
(HOOFED
). Класс ANIMAL
будет абстрактным, то есть, содержащим виртуальный метод show
. Все классы-наследники
будут использовать собственные реализации этого метода. Кроме того, почти всегда делают виртуальным
деструктор базового класса - иначе можно потерять оперативную память при освобождении её от классов-потомков.
Впрочем, в ООП всегда есть где потерять оперативку и без иерархии деструкторов. В нашем примере будет также
поддерживаться список из объектов разных классов, для чего в базовом абстрактном классе мы определим
несколько статических (static
, единых для всех объектов класса) свойств, которые помогут нам поддерживать список из
объектов разных классов. Будет в классе ANIMAL
и статическая (также единственная для всех экземпляров
класса) функция print
, чтобы показать этот список. Всё остальное - из синтаксиса C++ и предыдущего примера.
Файл Lab2.h
- описание классов:
#ifndef ANIMALH #define ANIMALH class ANIMAL { protected: char *name; public: ANIMAL(): name (NULL) {} ANIMAL (char *); ANIMAL (const ANIMAL&); virtual ~ANIMAL() {}; char * getname(); void setname (char *); virtual void show (void) = 0; static void print(void); // просмотр списка void add (); static int count; static ANIMAL **animals; static ANIMAL *begin; // указатель на начало списка }; class MAMMAL : public ANIMAL { public: MAMMAL(): ANIMAL(NULL) {} MAMMAL (char *); ~MAMMAL() { free (name); } void show (void); }; class BIRD : public ANIMAL { public: BIRD(): ANIMAL(NULL) {} BIRD (char *); ~BIRD() { free (name); } void show (void); }; class HOOFED : public MAMMAL { public: HOOFED(): MAMMAL(NULL) {} HOOFED (char *); ~HOOFED() { free (name); } void show (void); }; //Все статические данные инициализируем вне описания класса int ANIMAL :: count = 0; const int size = 4; //Предельный размер списка ANIMAL ** ANIMAL::animals = new ANIMAL * [size]; ANIMAL * ANIMAL ::begin = ANIMAL ::animals[0]; //Не используется, просто для иллюстрации #endif
Файл Lab2.cpp
- реализации:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Lab2.h" ANIMAL :: ANIMAL (char *name) { setname (name); } ANIMAL :: ANIMAL (const ANIMAL &src) { setname (src.name); } char * ANIMAL :: getname () { return this->name; } void ANIMAL :: setname (char *name) { int n = strlen (name); if (n>0) { //Вот тут-то можно и потерять память, как обычно в ООП this->name = (char *) calloc (strlen(name),sizeof(char)); if (this->name!=NULL) strcpy (this->name,name); } } void ANIMAL :: add () { if (count<size) { animals[count++] = this; if (count<size) animals[count] = NULL; } } void ANIMAL :: print () { printf ("Всего: %d\n",count); for (int i=0; i<count; i++) if (animals[i]!=NULL) animals[i]->show(); } MAMMAL :: MAMMAL (char *name) : ANIMAL (name) {} void MAMMAL :: show () { printf ("MAMMAL: %s\n",name); } BIRD :: BIRD (char *name) : ANIMAL (name) {} void BIRD :: show () { printf ("BIRD: %s\n",name); } HOOFED :: HOOFED (char *name) : MAMMAL (name) {} void HOOFED :: show () { printf ("HOOFED: %s\n",name); }
Наконец, демка - файл Lab2demo.cpp
. Здесь мы создаём список из кота, вороны и лося, показываем его, потом всех удаляем :)
#include <stdio.h> #include <stdlib.h> #include <string.h> #include "Lab2.cpp" void main () { MAMMAL *cat = new MAMMAL ("Cat"); cat->show(); BIRD *crow = new BIRD ("Crow"); crow->show(); HOOFED *elk = new HOOFED ("Elk"); elk->show(); cat->add(); crow->add(); elk->add(); ANIMAL::print(); //Печатает весь список delete elk; delete crow; delete cat; }
Файлы примера 2 в архиве ZIP (10 Кб)
Ещё одна полезная возможность ООП в C++ - переопределение операторов. Действительно, неплохо писать C=A*B
,
если A
и B
- матрицы. Только предварительно, конечно, придётся сделать реализацию этого оператора умножения.
Но сначала, чтобы примеры не были слишком большими, напишем что-нибудь простенькое. Скажем, определим класс
"точка на плоскости" и в нём - постфиксный и префиксный инкремент и декремент. Исключительно в
иллюстративных целях префиксные операторы сделаны не членами, но "друзьями" (friend
) класса - то есть, они
не являются функциями класса, но имеют доступ к его свойствам. На практике использовать "друзей" не
рекомендуется вообще - если уж следовать странной концепции ООП, предполагающей максимальную изолированность
классов, то зачем эту изолированность так грубо нарушать?
В программе также определены бинарные "+
" и "-
", то есть, обычное сложение и вычитание координат точек, и унарный "-
"
(смена знака). Путаницы не возникает, так как для одного оператора может быть несколько определений,
отличающихся формальными параметрами. Кстати, согласно документации по C++, определение постфиксных операций
++
и --
должно иметь последним параметром функции-оператора целое значение (int
). Так как это значение нигде
не используется, мы его и не именовали. На этот раз всё описано в одном файле.
#include <stdio.h> class Point { public: double x,y; Point(double x0 = 0.0, double y0 =0.0) : x (x0), y (y0) {}; //Так можно встроить конструктор + сразу задать значения параметров по умолчанию + //присвоить параметры свойствам класса Point operator ++ (int); Point operator -- (int); //постфиксные friend Point operator ++ (Point &); friend Point operator -- (Point &); //префиксные Point operator + (Point &); //бинарные Point operator - (Point &); Point operator - (); //унарный void show (char *); }; Point Point::operator - () { return Point(-x,-y); } Point Point::operator ++ (int) { return Point(x++,y++); } Point Point::operator -- (int) { return Point(x--,y--); } Point operator ++ (Point &p) { return Point(++p.x,++p.y); } Point operator -- (Point &p) { return Point(--p.x,--p.y); } Point Point::operator+ (Point &p) { return Point (x + p.x, y + p.y); } Point Point::operator- (Point &p) { return Point (x - p.x, y - p.y); } void Point:: show (char *s) { puts(s); printf ("x=%.2lf,y=%.2lf\n",x,y); } void main () { Point origin; //Получилось (0,0) - очевидно, почему :) Point p1= Point (1,2); p1.show ("p1"); Point p2=origin + p1++; p2.show("0+p1++"); p1.show ("p1 изменилось"); p1--; p2= origin + ++p1; p2.show("0+ ++p1"); p1 = Point (1,2); p1.show ("New p1"); p2=origin - p1--; p2.show("0-p1--"); p1++; p2= origin - --p1; p2.show("0- --p1"); Point p3=-Point(1,-4); p3.show("-(1,-4)"); }
Для класса целочисленных матриц Matrix
тоже сделаем несколько определений операторов, позволяющих
складывать, вычитать и умножать матрицы с другими матрицами и с числами. Выделение и освобождение памяти на
этот раз сделаем операторами C++ new
и delete
, а не взятыми из классического Си функциями calloc
и free
. В
остальном идея выделения памяти под матрицу та же, что в
этой главе учебника по Си
(п. 8.5).
Для простоты, как и в других примерах, не проверяется корректность данных. Унарные операторы имеют тип void
, так как непосредственно изменяют свой объект, бинарные операторы получают и возвращают новый объект Matrix
.
Не обойтись в такой программе и без явного конструктора копирования плюс деструктора, грамотно (в порядке,
обратном выделению памяти) удаляющего динамические данные объекта. В противном случае рискуем получить "Null pointer assignment" (следствие работы с указателем, как с массивом данных - например, когда указатель не проверен на NULL
или объект, к которому мы обращаемся, был уже разрушен).
#include <stdio.h> class Matrix { public: int **c; int n,m; Matrix () : n(0), m(0) {}; Matrix (int r=2); Matrix (int n0=2, int m0=2); Matrix (const Matrix &); ~Matrix(); void operator = (Matrix &); void operator += (Matrix &); void operator -= (Matrix &); void operator *= (Matrix &); void operator += (int); void operator -= (int); void operator *= (int); Matrix operator + (Matrix &); Matrix operator - (Matrix &); Matrix operator * (Matrix &); Matrix operator + (int); Matrix operator - (int); Matrix operator * (int); int * operator [] (int i) { return c[i]; } void show (char *); }; Matrix::Matrix (int r) { new Matrix (r,r); } Matrix::Matrix (int n0,int m0) { n=n0; m=m0; c= new int * [n]; for (int i=0; i<n; i++) c[i]=new int [m]; } Matrix::~Matrix () { for (int i=n-1; i>-1; i--) delete c[i]; delete c; } void Matrix::operator = (Matrix &p) { n=p.n; m=p.m; for (int i=0; i<n; i++) for (int j=0; j<m; j++) c[i][j]=p.c[i][j]; } Matrix::Matrix (const Matrix &p) { n=p.n; m=p.m; c= new int * [n]; for (int i=0; i<n; i++) c[i]=new int [m]; for (i=0; i<n; i++) for (int j=0; j<m; j++) c[i][j]=p.c[i][j]; } Matrix Matrix::operator + (Matrix &p) { Matrix r=Matrix(n,m); int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) r.c[i][j]=c[i][j]+p.c[i][j]; return r; } Matrix Matrix::operator - (Matrix &p) { Matrix r=Matrix(n,m); int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) r.c[i][j]=c[i][j]-p.c[i][j]; return r; } Matrix Matrix::operator * (Matrix &p) { Matrix r=Matrix(n,p.m); int i,j,k; for (i=0; i<n; i++) for (j=0; j<p.m; j++) { r.c[i][j]=0; for (k=0; k<m; k++) r.c[i][j]+=c[i][k]*p.c[k][j]; } return r; } Matrix Matrix::operator + (int p) { Matrix r=Matrix(n,m); int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) r.c[i][j]=c[i][j]+p; return r; } Matrix Matrix::operator - (int p) { Matrix r=Matrix(n,m); int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) r.c[i][j]=c[i][j]-p; return r; } Matrix Matrix::operator * (int p) { Matrix r=Matrix(n,m); int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) r.c[i][j]=c[i][j]*p; return r; } void Matrix::operator += (Matrix &p) { int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) c[i][j]+=p.c[i][j]; } void Matrix::operator -= (Matrix &p) { int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) c[i][j]-=p.c[i][j]; } void Matrix::operator *= (Matrix &p) { Matrix r=Matrix(p.n,p.m); for (int i=0; i<p.n; i++) for (int j=0; j<p.m; j++) { r.c[i][j]=0; for (int k=0; k<m; k++) r.c[i][j]+=c[i][k]*p.c[k][j]; } for (i=0; i<p.n; i++) for (int j=0; j<p.m; j++) c[i][j]=r.c[i][j]; } void Matrix::operator += (int p) { int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) c[i][j]+=p; } void Matrix::operator -= (int p) { int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) c[i][j]-=p; } void Matrix::operator *= (int p) { int i,j; for (i=0; i<n; i++) for (j=0; j<m; j++) c[i][j]*=p; } void Matrix::show (char *s) { puts (s); int i,j; for (i=0; i<n; i++) { for (j=0; j<m; j++) printf ("%d ",c[i][j]); printf ("\n"); } } void main () { Matrix a(2,2); a[0][0]=1; a[0][1]=2; a[1][0]=3; a[1][1]=4; a.show ("Заполнение матрицы A" ); Matrix b(2,2); b[0][0]=2; b[0][1]=0; b[1][0]=1; b[1][1]=3; b.show ("Заполнение матрицы B" ); Matrix c=b+a; c.show ("C=B+A"); Matrix d = a + 1; d.show ("D=A+1"); Matrix e = a * b; e.show ("E=A*B"); a*=2; a.show ("A*=2"); b+=b; b.show ("B+=B"); b-=b*2; b.show ("B-=B*2"); d*=d; d.show ("D*=D"); }
Файлы примера 3 в архиве ZIP (28 Кб)
P.S. Для операции *=
(домножение на матрицу) реализация сомнительна, если размерности матриц - разные. В самом деле, что означает "домножить матрицу F[3][2]
на матрицу T[2][3]
? Если это означает "получить новую матрицу размером 3 на 3 и потом переписать в F
ту часть, что переписывается", тогда будет такая изменённая реализация функции:
void Matrix::operator *= (Matrix &p) { Matrix r=Matrix(n,p.m); for (int i=0; i<n; i++) for (int j=0; j<p.m; j++) { r.c[i][j]=0; for (int k=0; k<m; k++) r.c[i][j]+=c[i][k]*p.c[k][j]; } for (i=0; i<n; i++) for (int j=0; j<m; j++) c[i][j]=r.c[i][j]; }
Пример вызова:
Matrix f(3,2); int i,j; for (i=0; i<3; i++) for (j=0; j<2; j++) f[i][j]=i+j; f.show ("F"); Matrix t(2,3); for (i=0; i<2; i++) for (j=0; j<3; j++) t[i][j]=j; t.show ("T"); f*=t; f.show ("F*=T");
гостевая; E-mail |