Указатели на компоненты класса

В С++ имеется возможность обращаться к компонентам класса используя указатель на эти компоненты (не следует путать с указателем на объект, рассматривается позже).

Class Cls

{ int i;

. . .

public:

int j;

void f(){...}

. . .

};

main()

{ Cls obj; //

int Cls:: *p; // указатель на int компоненту класса Cls

void (Cls::*pf)(); // указатель на компоненту ф-цию класса Сls

pf=(void (Cls::*)()) Cls::fun;

. . .

obj.*p=1; // модификация значения int компоненты класса

(obj.*pf)(); // вызов функции f() класса Cls

}

Встроенные функции (спецификатор inline)

В ряде случаев в качестве компонент класса используются достаточно простые функции. Вызов функции означает переход к памяти, в которой расположен выполняемый код функции. Команда перехода занимает память и требует времени на ее выполнение, что иногда существенно превышает затраты памяти на хранение кода самой функции. Для таких функций целесообразно поместить код функции вместо выполнения перехода к функции. В этом случае при выполнении функции (обращении к ней) выполняется сразу ее код. Функции с такими свойствами являются встроенными. Иначе говоря, если описание компоненты функции включается в класс, то такая функция называется встроенной. Например:

Class stroka

{ char str[100];

public:

……..

int size()

{ for(int i=0; *(str+i); i++);

return i;

}

};

В описанном примере функция size() – встроенная. Если функция, объявленная в классе, а описанная за его пределами, должна быть встроенной, то она описывается со спецификатором inline:

Class stroka

{ char str[100];

public:

……..

int size();

};

inline int stroka ::size()

{ for(int i=0; str[i]; i++);

return i;

}

Спецификация inline может быть проигнорирована компилятором, поскольку иногда введение встроенных функций оказывается невозможным или нежелательным.

Организация внешнего доступа к локальным компонентам класса (спецификатор friend)

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

Для реализации этого в С++ введен спецификатор friend. Если некоторая функция определена как friend функция для некоторого класса, то она:

- не является компонентой-функцией этого класса;

- имеет доступ ко всем компонентам этого класса (private, public и protected).

Ниже рассматривается пример когда внешняя функция получает доступ к внутренним данным класса.

#include <iostream.h>

Class kls

{ int i,j;

public:

kls(int i,int J) : i(I),j(J) {} // конструктор

int max() {return i>j? i : j;} // функция-компонента класса kls

friend double fun(int, kls&); // friend-объявление внешней функции fun

};

double fun(int i, kls &x) // внешняя функция

{ return (double)i/x.i;

}

main()

{ kls obj(2,3);

cout << obj.max() << endl;

cout << fun(3,obj) << endl;

return 1;

}

Функции со спецификатором friend, не являясь компонентами класса, не имеют и, следовательно, не могут использовать this указатель (будет рассмотрен несколько позже). Следует также отметить ошибочность следующей заголовочной записи функции double kls :: fun(int i,int j), так как fun не является компонентой-функцией класса kls.

В общем случае friend-функция является глобальной независимо от секции, в которой она объявлена (public, protected, private), при условии, что она не объявлена ни в одном другом классе без спецификатора friend. Функция friend, объявленная в классе, может рассматриваться как часть интерфейса класса с внешней средой.

Вызов компоненты-функции класса осуществляется с использованием операции доступа к компоненте (.) или(->). Вызов же friend-функции производится по ее имени (так как friend-функции не являются его компонентами). Следовательно, как это будет показано далее, в friend-функции не передается this-указатель и доступ к компонентам класса выполняется либо явно (.), либо косвенно (->).

Компонента-функция одного класса может быть объявлена со спецификатором friend для другого класса:

class X{ ……….

void fun (….);

};

class Y{ ……….

friend void X:: fun (….);

};

В приведенном фрагменте функция fun() имеет доступ к локальным компонентам класса Y. Запись вида friend void X:: fun (….) говорит о том, что функция fun принадлежит классу Х, а спецификатор friend разрешает доступ к локальным компонентам класса Y (так как она объявлена со спецификатором в классе Y).

Ниже приведен пример программы расчета суммы двух налогов на зарплату.

#include "iostream.h"

#include "string.h"

#include "iomanip.h"

class nalogi; // неполное объявление класса nalogi

Class work

{ char s[20]; // фамилия работника

int zp; // зарплата

public:

float raschet(nalogi); // компонента-функция класса work

void inpt()

{ cout << "вводите фамилию и зарплату" << endl;

cin >> s >>zp;

}

work(){}

~work(){};

};

Class nalogi

{ float pd, // подоходный налог

st; // налог на соцстрахование

friend float work::raschet(nalogi); // friend-функция класса nalogi

public:

nalogi(float f1,float f2) : pd(f1),st(f2){};

~nalogi(void){};

};

float work::raschet(nalogi nl)

{ cout << s << setw(6) << zp <<endl; // доступ к данным класса work

cout << nl.pd << setw(8) << nl.st <<endl; // доступ к данным класса nalogi

return zp*nl.pd/100+zp*nl.st/100;

}

int main()

{ nalogi nlg((float)12,(float)2.3); // описание и инициализация объекта

work wr[2]; // описание массива объектов

for(int i=0;i<2;i++) wr[i].inpt(); // инициализация массива объктов

cout<<setiosflags(ios::fixed)<<setprecision(3)<<endl;

cout << wr[0].raschet(nlg) << endl; // расчет налога для объекта wr[0]

cout << wr[1].raschet(nlg) << endl; // расчет налога для объекта wr[1]

return 0;

}

Следует отметить необходимость выполнения неполного (предварительного) объявления класса nalogi, так как в прототипе функции raschet класса work используется объект класса nalogi, объявляемого далее. В то же время полное объявление класса nalogi не может быть выполнено ранее (до объявления класса work), так как в нем содержится friend-функция, описание которой должно быть выполнено до объявления friend-функции. В противном случае компилятор выдаст ошибку.

Если функция raschet в классе work также будет использоваться со спецификатором friend, то приведенная выше программа будет выглядеть следующим образом:

#include "iostream.h"

#include "string.h"

#include "iomanip.h"

class nalogi; // неполное объявление класса nalogi

Class work

{ char s[20]; // фамилия работника

int zp; // зарплата

public:

friend float raschet(work,nalogi); // friend-функция класса work

void inpt()

{ cout << "вводите фамилию и зарплату" << endl;

cin >> s >>zp;

}

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

~work(){} // деструктор по умолчанию

};

Class nalogi

{ float pd, // подоходный налог

st; // налог на соцстрахование

friend float raschet(work,nalogi); // friend-функция класса nalogi

public:

nalogi(float f1,float f2) : pd(f1),st(f2){};

~nalogi(void){};

};

float raschet(work wr,nalogi nl)

{ cout << wr.s << setw(6) << wr.zp <<endl;// доступ к данным класса work

cout << nl.pd << setw(8) << nl.st <<endl;// доступ к данным класса nalogi

return wr.zp*nl.pd/100+wr.zp*nl.st/100;

}

int main()

{ nalogi nlg((float)12,(float)2.3); // описание и инициализация объекта

work wr[2];

for(int i=0;i<2;i++) wr[i].inpt(); // инициализация массива объктов

cout<<setiosflags(ios::fixed)<<setprecision(3)<<endl;

cout << raschet(wr[0],nlg) << endl; // расчет налога для объекта wr[0]

cout << raschet(wr[1],nlg) << endl; // расчет налога для объекта wr[1]

return 0;

}

Все функции одного класса можно объявить со спецификатором friend по отношению к другому классу следующим образом:

class X{ ………..

friend class Y;

………..

};

class Y{ ……….

};

В этом случае все компоненты-функции класса Y имеют спецификатор friend для класса Х (имеют доступ к компонентам класса Х). В приведенном ниже примере оба класса являются друзьями.

#include <iostream.h>

Class A

{ int i; // компонента-данное класса А

public:

friend class B; // объявление класса В другом класса А

A():i(1){} // конструктор

void f1_A(B &); // объявление метода, оперирующего данными

// обоих классов

};

Class B

{ int j; // компонента-данное класса В

public:

friend A; // объявление класса А другом класса В

B():j(2){} // конструктор

void f1_B(A &a){ cout<<a.i+j<<endl;} // описание метода,

// оперирующего данными обоих классов

};

void A :: f1_A(B &b)

{ cout<<i<<' '<<b.j<<endl;}

void main()

{ A aa;

B bb;

aa.f1_A(bb);

bb.f1_B(aa);

}

Результат выполнения программы:

1 2

В объявлении класса А содержится инструкция friend class B, являющаяся и предварительным объявлением класса В, и объявлением класса В дружественным классу А. Отметим также необходимость описания функции f1_A после явного объявления класса В (в противном случае не может быть создан объект b еще не объявленного типа).

Отметим основные свойства и правила использования спецификатора friend:

- friend-функции не являются компонентами класса, но имеют доступ ко всем его компонентам независимо от их атрибута доступа;

- friend-функции не имеют указателя this;

- friend-функции не наследуются в производных классах;

- отношение friend не является ни симметричным (то есть если класс А есть friend классу В, то это не означает, что В также является friend классу А), ни транзитивным (то есть если A friend B и B friend C, то не следует, что A friend C);

- друзьями класса можно определить перегруженные функции. Каждая перегруженная функция, используемая как friend для некоторого класса, должна быть явно объявлена в классе со спецификатором friend.

Вложенные классы

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

class ext_class

{ class int_cls

{ · · ·

};

public:

· · ·

};

Класс int_class является вложенным по отношению к классу ext_class (внешний).

Доступ к компонентам вложенного класса, имеющим атрибут private, возможен только из функций вложенного класса и из функций внешнего класса, объявленных со спецификатором friend во вложенном классе.

#include "iostream.h"

class cls1// внешний класс

{ class cls2 // вложенный класс

{ int b; // все компоненты private для cls1 и cls2

cls2(int bb) : b(bb){} // конструктор класса cls2

};

public: // public секция для cls1

int a;

class cls3 // вложенный класс

{ int c; // private для cls1 и cls3

public: // public-секция для класса cls3

cls3(int cc): c(cc) {} // конструктор класса cls3

};

cls1(int aa): a(aa) {} // конструктор класса cls

};

void main()

{ cls1 aa(1980); // верно

cls1::cls2 bb(456); // ошибка cls2 cannot access private

cls1::cls3 cc(789); // верно

cout << aa.a << endl; // верно

cout << cc.c << endl; // ошибка 'c' : cannot access private member

// declared in class 'cls1::cls3'

}

В приведенном тексте программы инструкция cls1::cls2 bb(456) является ошибочной, так как объявление класса cls2 и его компонента находятся в секции private. Для устранения ошибки при описании объекта bb необходимо изменить атрибуты доступа для класса cls2 следующим образом:

public:

Class cls2

{ int b; // private-компонента

public:

cls2(int bb) : b(bb){}

};

Пример доступа к private-компонентам вложенного класса из функций внешнего класса, объявленных со спецификатором friend, приводится ниже.

#include "iostream.h"

class cls1// внешний класс

{ int a;

public:

cls1(int aa): a(aa) {}

class cls2; // неполное объявление класса

void fun(cls1::cls2); // функция получает объект класса cls2

class cls2 // вложенный класс

{ int c;

public:

cls2(int cc) : c(cc) {}

friend void cls1::fun(cls1::cls2); // ф-ция, дружественная классу cls1

int pp(){return c;}

};

};

void cls1::fun(cls1::cls2 dd) {cout << dd.c << endl << a;}

void main()

{ cls1 aa(123);

cls1:: cls2 cc(789);

aa.fun(cc);

}

Внешний класс cls1 содержит public-функцию fun(cls1::cls2 dd), где dd есть объект, соответствующий классу cls2, вложенному в класс cls1. В свою очередь в классе cls2 имеется friend-функция friend void cls1::fun(cls1::cls2 dd), обеспечивающая доступ функции fun класса cls1 к локальной компоненте с класса cls2.

#include "iostream.h"

#include "string.h"

class ext_cls // внешний класс

{ int gd; // год рождения

double zp; // з/плата

class int_cls1 // первый вложенный класс

{ char *s; // фамилия

public:

int_cls1(char *ss) // конструктор 1-го вложенного класса

{ s=new char[20];

strcpy(s,ss);

}

~int_cls1() // деструктор 1-го вложенного класса

{ delete [] s;}

void disp_int1() {cout << s<<endl;}

};

public:

class int_cls2 // второй вложенный класс

{ char *s; // фамилия

public:

int_cls2(char *ss) //конструктор 2-го вложенного класса

{ s=new char[20];

strcpy(s,ss);

}

~int_cls2() // деструктор 2-го вложенного класса

{ delete [] s;}

void disp_int2() {cout << s << endl;}

};

// public функции класса ext_cls

ext_cls(int god,double zpl):gd(god),zp(zpl){}// конструктор внешнего

// класса

int_cls1 *addr(int_cls1 &obj){return &obj;} // возврат адреса объекта obj

void disp_ext()

{ int_cls1 ob("Иванов"); // создание объекта вложенного класса

addr(ob)->disp_int1(); // вывод на экран содержимого объекта ob

cout <<gd<<’ ’<< zp<<endl;

}

};

void main()

{ ext_cls ext(1980,230.5);

// ext_cls::int_cls1 in1("Петров"); // неверно, т.к. int_cls1 имеет

// атрибут private

ext_cls::int_cls2 in2("Сидоров");

in2.disp_int2();

ext.disp_ext(); //

}

В результате выполнения программы получим:

Сидоров

Иванов

1980 230.5

В строке in2.disp_int2() (main функции) для объекта вложенного класса вызывается компонента-функция ext_cls::int_cls2::disp_int2() для вывода содержимого объекта in2 на экран. В следующей строке ext.disp_ext() вызывается функция ext_cls::disp_ext(), в которой создается объект вложенного класса int_cls1. Затем, используя косвенную адресацию, вызывается функция ext_cls::int_cls1::disp_int1(). Далее выводятся данные, содержащиеся в объекте внешнего класса.

Наши рекомендации