Pers.narod.ru. Алгоритмы. Примеры на работу с классами в 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");
|
|