Главная | Обратная связь | Поможем написать вашу работу!
МегаЛекции

Пеpегpузка опеpаций ( синтаксис operator)




Объявление и определение класса.

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

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

class Complex{ // 1
double re, im; // 2
public: // 3
Complex(double r, double i){re=r; im=i;} // 4
Complex(){re=0; im=0;} // 5
double getR(){ return re;} // 6
double getI(){ return im;} // 7
void setC(double a, double b){re=a; im=b;} // 8
}; // 9
void main(){
Complex m1(4,6), m2, m3[6]; // 10
m2.setC(5, 8); // 11
cout<<m2.getR()<<m2.getI()<<endl; // 12
}

Кратко рассмотрим составляющие программы. Со строки 1 начинается определение класса. В строке 2 объявлены два поля – данные класса. Ключевое слово public в строке 3 определяет соответствующий доступ к полям класса. В строках 4 и 5 приведены конструкторы класса – функции, создающие поля класса. В строках 6 – 8 записаны методы: первые два возвращают значения реальной и мнимой частей комплексного числа соответственно, третий устанавливает их значения. Девятой строкой заканчивается определение класса. В 10 строке объявляются 3 объекта (экземпляра) класса: переменные m1 и m2 (m1 инициализируется при объявлении) и массив m3. Оператор в 11 строке присваивает полям переменной m2 соответствующие значения, в 12 строке производится вывод этих значений на экран.

Определение класса имеет следующий вид:

тип_класса имя_класса { поля, методы };

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

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

[тип] имя_класса:: имя_компоненты,

где символ:: называется оператором принадлежности. Если перед этим оператором нет имени класса, то задаваемое имя определяется как глобальное, известное за пределами классов.

В программе наряду с определением класса допускается его объявление вида:

тип_класса имя_класса;

Примеры

class X; // Объявление класса Х

class Y { // Определение класса Y

X m1;

...

}

class X{ // Определение класса Х

...

}

Один класс можно объявить в другом классе. Первый при этом называется вложенным.

 

2 Конструкторы и деструкторы (типы конструкторов и деструкторов)

Конструктор – функция, создающая поля класса и, возможно, их инициализирующая. Приведем пример достаточно сложного класса String.

class String{ // 1
char *charPtr; // 2
int length; // 3
public: // 4
String(int size=80); // 5
String(char *text); // 6
String(String& Other_String); // 7
~String(){delete charPtr;}; // 8
int GetLen(void); // 9
void Show(void); //10
};

String::String (int size){
length=size;
charPtr=new char[length + 1];
*charPtr =‘\0’;
}

String::String(char *text) {
length=strlen(text);
char_ptr=new char [length+1];
strcpy(charPtr, text);
}

String::String(String& Other_String) {
length=Other_String.length;
char_ptr=new char[length+1];
strcpy(charPtr, Other_String.charPtr);
}

Первый конструктор класса String (строка 5) создает строку с заданной длиной (по умолчанию 80). Второй конструктор создает поле для символьной строки и инициализирует его текстом, передаваемым в качестве аргумента. Начальное значение для поля класса здесь задается как аргумент конструктора. Наряду с этим начальные значения могут задаваться в виде списка инициализации, который отделяется от списка аргументов конструктора двоеточием. Второй способ инициализации иногда предпочтительнее, т. к. в этот список можно включать компоненты другого класса. Третий конструктор (строка 7) копирует уже имеющуюся строку, во вновь создаваемую, и называется конструктором копирования.

Пример использования всех трех конструкторов следует ниже:

void main() {
String Astring(" Инкапсуляция");
String Bstring(Astring);
String Cstring(25);
...
}

В результате выполнения программы получаем три объекта: 2 строки с одинаковым содержимым и одну пустую строку длиной 25 символов.

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

конструктор имеет то же имя, что и класс, в котором он объявляется;

конструктор не имеет типа возвращаемого значения;

конструктор может иметь аргументы, заданные по умолчанию; эти аргументы не перечисляются при вызове конструктора;

если конструктор не задан в программе, то компилятор автоматически создает конструктор без аргументов;

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

конструктор класса X может иметь аргумент типа X& (ссылку на объект типа X), но не аргумент типа X.

конструктор, заданный в виде X::X(X&), называется конструктором копирования; он подразумевает использование свободной памяти.

Деструктор разрушает поля класса; его можно узнать по символу ~. Деструктор не имеет аргументов и не возвращает значения. В классе может быть только один деструктор. Если деструктор не задан в программе, то он автоматически генерируется компилятором. Деструктор автоматически вызывается при разрушении объекта за исключением случая, когда массив объектов создается оператором new. В последнем случае для разрушения объектов должен быть использован оператор delete для каждого элемента массива. (В классах Complex и String деструкторы вызываются автоматически).

 

Дружественные функции.

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

friend тип имя (параметры);

Дружественные функции рекомендуется использовать в следующих случаях:

при необходимости упростить форму обращения к данным класса: не будучи компонентом класса, дружественная функция не требует соответствующей формы вызова. Например, для класса Complex можно определить функции getR и getI как дружественные:

friend double getR(Сomplex a){ return a.re;}
friend double getI(Сomplex a){ return a.im;}

Обращение к ним следующее:

cin>>getR(m2);
cin>>getI(m2);

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

class Y {

...

void fy (){...} //Определение функции класса Y

...

};

class X {

...

void fx(){...}

friend void Y::fy();// Объявление функции fy дружественной классу X

...

};

Пример

#include <iostream.h>

class X; // Объявление класса Х

class Y {

int a;

public:

Y(int n):a(n){}; // Форма записи аналогична {a=n;}

void display(X* pX); //Функция-компонент класса Y

};

class X{

int a;

public:

X(int n):a(n){};

friend void Y::display(X* pX);.// Функция объявляется дружественной классу Х и

}; // имеет доступ к полю а как класса Х, так и класса Y

void Y::display(X* pX){

cout<<pX->a<<'\t'<<a<<endl;

}

void main(){

X mx(100);

Y my(200);

my.display(&mx);

}

Результат на дисплее: 100 200

Все функции одного класса могут быть дружественными другому классу.

 

Пеpегpузка опеpаций (синтаксис operator)

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

тип operator знак_опеpации(типы аpгументов){... }.

Перегруженная операция может быть определена как компонент класса; в этом случае она имеет один параметр или вообще не имеет параметров. У дружественной перегруженной операции может быть один или два параметра. Поэтому бинарные операции следует перегружать как дружественные.

Пpимеp для класса Сomplex:

class Complex {
double re,im;
public:
.............
friend Complex operator+(Complex, Complex);
};
Complex operator+(Complex l, Complex m) {
return Complex (l.re+m.re, l.im + m.im);}

main(){
Complex m1(5,2),m2(6,6),m3(0);
m3=m1+m2;
return 0;
}

Операция ‘+‘ вне класса Complex действует как обычно.

(Функция operator+(a,b) позволяет использовать краткую форму записи a+b. Мы могли бы без перегрузки записать m3=operator+(m1,m2); это относится и к другим операциям)

Перегружаться могут практически все операции (исключение – оператор принадлежности::, операторы..*?: sizeof и символ #). При этом функция operator не изменяет приоритеты операций и число операндов, определенных в языке.

Функция operator для переопределения бинарных операций может быть описана как функция-компонент класса с одним аргументом (например, для операции сложения a.operator+(b)) или как дружественная функция с двумя аргументами operator+(a,b)); унарная операция – как функция-компонент без аргументов или как дружественная функция с одним аргументом. Четыре функции operator=, operator[], operator() и operator-> должны быть нестатическими компонентами-функциями класса.

Все перегруженные операции, кроме ‘=’ наследуются. Относительно последней нужно отметить, что по умолчанию при ее использовании выполняется почленное копирование одного объекта в другой; например, если для класса String запишем:

String s1(10), s2(10);
s1=s2;

то в результате операции присваивания s1 будет иметь тот же указатель, что и s2; при изменении s2 изменяется и s1. Поэтому операцию присваивания целесообразно перегружать с выделением памяти для первого массива по типу:

X&x:: operator=(X&)

Для класса String можно записать:

void String::operator=(String& a) {
if (this==&a) return; //для предотвращения s1=s1;
delete charPtr; //чистка памяти

length=a.length;
charPtr= new char[length+1];
strcpy(charPtr,a.charPtr);
}

Перегруженные функции имеют тот же смысл, что и операции: функция с одним и тем же именем работает по-pазному с различными типами аргументов. Примером является набор конструкторов в классе String.

Функции new и delete перегружаются c помощью функции operator следующим образом:

void* operator new(size_t t [,список аргументов]),

где size_t – первый и единственный обязательный аргумент.

Например, перегрузка

void* operator new(size_t t, int n){
return new char[t*n]; }

позволяет добавить к стандартной функции new еще один аргумент – количество блоков памяти размером size_t каждый. Предположим, что функция вызывается с n=5 и t=double:

double *d=new(5) double;

Значение t в теле функции станет равным sizeof(double)=8 (для персональных компьютеров); в результате под переменную будет выделено 5*8=40 байтов памяти.

 

Поделиться:





Воспользуйтесь поиском по сайту:



©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...