Копирующий конструктор
Инициализация объекта другим объектом того же класса называется почленной инициализацией по умолчанию. Копирование одного объекта в другой выполняется путем последовательного копирования каждого нестатического члена и осуществляется конструктором копирования. Вместе с конструктором умолчания, конструктор копирования входит в обязательный набор конструкторов для любого класса. Реализация механизма копирования значений для транслятора не является неразрешимой задачей. Конструктор копирования всего лишь создает копии объектов. Этот процесс реализуется при помощи стандартного программного кода. И построить такой код транслятор способен самостоятельно.
class x { //………};x a;x b=a; x c(a); x d=x(a);Проектировщик класса может изменить такое поведение, предоставив специальный копирующий конструктор. Если он определен, то вызывается всякий раз, когда один объект инициализируется другим объектом того же класса.
Часто почленная инициализация не обеспечивает корректного поведения класса. Поэтому мы явно определяем копирующий конструктор. Копирующий конструктор принимает в качестве формального параметра ссылку на объект класса (традиционно объявляемую со спецификатором const).
class x { //………public: x(const x &t); //………};Пример:
#include <stdio.h>class A{public: int* a, N; A(int); void print();};A::A(int n){ a=new int[n]; N=n; for(int i=0;i<N;i++) a[i]=i;}void A::print(){ for(int i=0;i<N;i++) printf("%d ", a[i]); printf("\n");}class B{public: int* a, N; B(int); B(const B&); void print();};B::B(int n){ a=new int[n]; N=n; for(int i=0;i<N;i++) a[i]=i;}B::B(const B &v){ N=v.N; a=new int[N]; for(int i=0;i<N;i++) a[i]=v.a[i];}void B::print(){ for(int i=0;i<N;i++) printf("%d ", a[i]); printf("\n");}int main(int argc, char* argv[]){ A a1(3); A a2=a1; a2.a[1]=8; printf("Object a1 - "); a1.print(); printf("Object a2 - "); a2.print(); B b1(3); B b2=b1; b2.a[1]=8; printf("Object b1 - "); b1.print(); printf("Object b2 - "); b2.print(); getchar(); return 0;}Вывод:
Object a1 - 0 8 2Object a2 - 0 8 2Object b1 - 0 1 2Object b2 - 0 8 2Очистка
Определяемый пользователем тип чаще имеет, чем не имеет, конструктор, который обеспечивает надлежащую инициализацию. Для многих типов также требуется обратное действие, деструктор, чтобы обеспечить соответствующую очистку объектов этого типа. Имя деструктора для класса X есть ~X() («дополнение конструктора»). В частности, многие типы используют некоторый объем динамической памяти, который выделяется конструктором и освобождается деструктором. Вот, например, традиционный стековый тип, из которого для краткости полностью выброшена обработка ошибок:
class char_stack {
int size;
char* top;
char* s;
public:
char_stack(int sz) { top=s=new char[size=sz]; }
~char_stack() { delete []s; } // деструктор
void push(char c) { *top++ = c; }
char pop() { return *--top;}
}
Когда char_stack выходит из области видимости, вызывается деструктор:
void f()
{
char_stack s1(100);
char_stack s2(200);
s1.push('a');
s2.push(s1.pop());
char ch = s2.pop();
printf("%c\n", ch;
}
Когда вызывается f(), конструктор char_stack вызывается для s1, чтобы выделить вектор из 100 символов, и для s2, чтобы выделить вектор из 200 символов. При возврате из f() эти два вектора будут освобождены.
Inline
При программировании с использованием классов очень часто используется много маленьких функций. По сути, везде, где в программе традиционной структуры стояло бы просто какое-нибудь обычное использование структуры данных, дается функция. То, что было соглашением, стало стандартом, который распознает компилятор. Это может страшно понизить эффективность, потому что стоимость вызова функции (хотя и вовсе не высокая по сравнению с другими языками) все равно намного выше, чем пара ссылок по памяти, необходимая для тела функции.
Чтобы справиться с этой проблемой, был разработан аппарат inline-функций. Функция член, определенная (а не просто описанная) в описании класса, считается inline. Это значит, например, что в функциях, которые используют приведенные выше char_stack, нет никаких вызовов функций кроме тех, которые используются для реализации операций вывода! Другими словами, нет никаких затрат времени выполнения, которые стоит принимать во внимание при разработке класса. Любое, даже самое маленькое действие, можно задать эффективно. Это утверждение снимает аргумент, который чаще всего приводят чаще всего в пользу открытых членов данных.
Функцию член можно также описать как inline вне описания класса. Например:
char char_stack {
int size;
char* top;
char* s;
public:
char pop();
// ...
};
inline char char_stack::pop()
{
return *--top;
}
Законченный Класс
Программирование без скрытия данных (с применением структур) требует меньшей продуманности, чем программирование со скрытием данных (с использованием классов). Структуру можно определить, не слишком задумываясь о том, как ее предполагается использовать. А когда определяется класс, все внимание сосредотачивается на обеспечении нового типа полным множеством операций; это важное смещение акцента. Время, потраченное на разработку нового типа, обычно многократно окупается при разработке и тестировании программы.
Вот пример законченного типа PascalSet, который реализует понятие «битовое множество целых однобайтовых беззнаковых»:
Файл pascalset.h
#ifndef _PS_H
#define _PS_H
#include <mem.h>
class PasSet {
unsigned char data[32];
unsigned char GetMask(unsigned char x)
{return 1<<x;}
unsigned char GetNumByte(unsigned char x)
{return x/8;}
unsigned char GetNumBit(unsigned char x)
{return x%8;}
public:
//Конструктор
PasSet()
{memset(data, 0, sizeof(data));}
//Проверка вхождения
int InSet(unsigned char x);
//Включение элементаvoid include(unsigned char x);
//Исключение элементаvoid exclude(unsigned char x);
//Вывод
void print();
};
#endif
Файл pascalset.cpp
include <stdio.h>
#include "pascalset.h"
int PasSet::InSet(unsigned char x)
{
unsigned char nbyte=GetNumByte(x);
unsigned char nbit=GetNumBit(x);
unsigned char mask=GetMask(nbit);
return data[nbyte] & mask;
}
void PasSet::include(unsigned char x)
{
unsigned char nbyte=GetNumByte(x);
unsigned char nbit=GetNumBit(x);
unsigned char mask=GetMask(nbit);
data[nbyte]|=mask;
}
void PasSet::exclude(unsigned char x)
{
unsigned char nbyte=GetNumByte(x);
unsigned char nbit=GetNumBit(x);
unsigned char mask=GetMask(nbit);
if (InSet(x)) data[nbyte]^=mask;
}
void PasSet::print()
{
for (int i=0; i<256; i++)
if (InSet((unsigned char)i)) printf("%d ", i);
printf("\n");
}
Файл mainset.cpp
#include <stdio.h>
#include "pascalset.h"
int main(int argc, char* argv[])
{
PasSet s;
s.include(8);s.include(3);s.include(5);
s.print();
s.exclude(3);
s.print();
if (s.InSet(5)) printf("5 is in set\n");
else printf("5 is not in set\n");
getchar();
return 0;
}
Результат работы
3 5 85 85 is in setДоступ к членам
Сокрытие информации — это формальный механизм, предотвращающий прямой доступ к внутреннему представлению типа класса из функций программы. Ограничение доступа к членам задается с помощью секций тела класса, помеченных ключевыми словами public, private и protected — спецификаторами доступа. Члены, объявленные в секции public, называются открытыми, а объявленные в секциях private и protected соответственно закрытыми или защищенными.
· открытый член доступен из любого места программы. Класс, скрывающий информацию, оставляет открытыми только функции-члены, определяющие операции, с помощью которых внешняя программа может манипулировать его объектами;
· закрытый член доступен только функциям-членам и друзьям класса. Класс, который хочет скрыть информацию, объявляет свои данные-члены закрытыми;
· защищенный член ведет себя как открытый по отношению к производному классу и как закрытый по отношению к остальной части программы.
В теле класса может быть несколько секций public, protected и private. Каждая секция продолжается либо до метки следующей секции, либо до закрывающей фигурной скобки. Если спецификатор доступа не указан, то секция, непосредственно следующая за открывающей скобкой, по умолчанию считается private.
Друзья
Предположим, вы определили два класса, vector и matrix (вектор и матрица). Каждый скрывает свое представление и предоставляет полный набор действий для манипуляции объектами его типа. Теперь определим функцию, умножающую матрицу на вектор. Для простоты допустим, что в векторе четыре элемента, которые индексируются 0...3, и что матрица состоит из четырех векторов, индексированных 0...3. Допустим также, что доступ к элементам вектора осуществляется через функцию elem(), которая осуществляет проверку индекса, и что в matrix имеется аналогичная функция. Один подход состоит в определении глобальной функции multiply() (перемножить) примерно следующим образом:
vector multiply(matrix& m, vector& v);{ vector r; for (int i = 0; i<3; i++) { // r[i] = m[i] * v; r.elem(i) = 0; for (int j = 0; j<3; j++) r.elem(i) += m.elem(i,j) * v.elem(j); }return r;}
Это своего рода «естественный» способ, но он очень неэффективен. При каждом обращении к multiply() elem() будет вызываться 4*(1+4*3) раза. Теперь, если мы сделаем multiply() членом класса vector, мы сможем обойтись без проверки индексов при обращении к элементу вектора, а если мы сделаем multiply() членом класса matrix, то мы сможем обойтись без проверки индексов при обращении к элементу матрицы. Однако членом двух классов функция быть не может. Нам нужно средство языка, предоставляющее функции право доступа к закрытой части класса. Функция не член, получившая право доступа к закрытой части класса, называется другом класса (friend). Функция становится другом класса после описания как friend. Например:
class vector { float v[4]; // ... friend vector multiply(matrix&, vector&);};class matrix { vector v[4]; // ... friend vector multiply(matrix&, vector&);};Функция друг не имеет никаких особенностей, помимо права доступа к закрытой части класса. В частности, friend функция не имеет указателя this (если только она не является полноправным членом функцией). Описание friend — настоящее описание. Оно вводит имя функции в самой внешней области видимости программы и сопоставляется с другими описаниями этого имени. Описание друга может располагаться или в закрытой, или в открытой части описания класса; где именно, значения не имеет.
Теперь можно написать функцию умножения, которая использует элементы векторов и матрицы непосредственно:
Функция член одного класса может быть другом другого. Например:
class x { // ... void f();};class y { // ... friend void x::f();};Нет ничего необычного в том, что все функции члены одного класса являются друзьями другого. Для этого есть даже более краткая запись:
class x { friend class y; // ...};Такое описание friend делает все функции члены класса y друзьями x.
Статические Члены
Класс — это тип, а не объект данных, и в каждом объекте класса имеется своя собственная копия данных, членов этого класса. Однако некоторые типы наиболее элегантно реализуются, если все объекты этого типа могут совместно использовать (разделять) некоторые данные. Предпочтительно, чтобы такие разделяемые данные были описаны как часть класса. Например, для управления задачами в операционной системе или в ее модели часто бывает полезен список всех задач:
class task { // ... task* next; static task* task_chain; void shedule(int); void wait(event); // ...};Описание члена task_chain (цепочка задач) как static обеспечивает, что он будет всего лишь один, а не по одной копии на каждый объект task. Он все равно остается в области видимости класса task, и «извне» доступ к нему можно получить, только если он был описан как public. В этом случае его имя должно уточняться именем его класса:
task::task_chainВ функции члене на него можно ссылаться просто task_chain. Использование статических членов класса может заметно снизить потребность в глобальных переменных.
Дата добавления: 2016-07-27; просмотров: 1836;