Компоненты-функции static и const

В С++ компоненты-функции могут использоваться с модификатором static и const. Обычная компонента-функция, вызываемая

object . function(a,b);

имеет явный список параметров a и b и неявный список параметров, состоящий из компонент данных переменной object. Неявные параметры можно представить как список параметров, доступных через указатель this. Статическая (static) компонента-функция не может обращаться к любой из компонент посредством указателя this. Компонента-функция const не может изменять неявные параметры.

#include "iostream.h"

Class cls

{ int kl; // количество изделий

double zp; // зарплата на производство 1 изделия

double nl1,nl2; // два налога на з/пл

double sr; // кол-во сырья на изделие

static double cs; // цена сырья на 1 изделие

public:

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

~cls(){} // деструктор

void inpt(int);

static void vvod_cn(double);

double seb() const;

};

double cls::cs; // явное определение static-члена в контексте файла

void cls::inpt(int k)

{ kl=k;

cout << "Введите з/пл и 2 налога";

cin >> nl1 >> nl2 >> zp;

}

void cls::vvod_cn(double c)

{ cs=c; // можно обращаться в функции только к static-компонентам;

}

double cls::seb() const

{return kl*(zp+zp*nl1+zp*nl2+sr*cs);//в функции нельзя изменить ни один

} // неявный параметр (kl zp nl1 nl2 sr)

void main()

{ cls c1,c2;

c1.inpt(100); // инициализация первого объекта

c2.inpt(200); // инициализация второго объекта

cls::vvod_cn(500.); //

cout << "\nc1" << c1.seb() << "\nc2" << c2.seb() << endl;

}

Ключевое слово static не должно быть включено в описание объекта статической компоненты класса. Так, в описании функции vvod_cn отсутствует ключевое слово static. В противном случае возможно противоречие между static- компонентами класса и внешними static-функциями и переменными.

Следующий далее пример демонстрирует доступ к static данным класса из различных функций.

#include "iostream.h"

Class cls

{ static int i;

int j;

public:

static int k; // объявление static-члена в объявлении класса

void f1();

static void f2();

};

int cls::k=0; // явное определение static-члена в контексте файла

int cls::i=0;

void cls::f1() // из функции класса возможен доступ

{ cout << ++k<<' '<<++i<< endl;}// к private и public static данным

void cls::f2() // из static функции класса возможен

{ cout <<++k<<' '<<++i<<endl;} // доступ к private и public static данным

void f3() // из внешней функции возможен

{cout<<++cls::k<<endl;} // доступ только к public static данным

void main()

{ cls obj;

cout << cls::k<<endl; // возможен доступ только к public static данным

obj.f1();

cls::f2();

f3();

}

Результат работы программы:

1 1

2 2

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

const cls c1;

cls c2;

c1.inpt(100); // неверный вызов

c2.inpt(100); // правильный вызов функции

c1.seb(); // правильный вызов функции

Для функций со спецификатором const указатель this имеет следующий тип:

const имя_класса * const this;

Следовательно, нельзя изменить значение компоненты объекта через указатель this без явной записи. Рассмотрим это на примере функции seb.

double cls::seb() const

{ ((cls *)this)->zp--; // возможная модификация неявного параметра

// zp посредством явной записи this-указателя

return kl*(zp+zp*nl1+zp*nl2+sr*cs);

}

Если функция, объявленная в классе, описывается отдельно (вне класса), то спецификатор const должен присутствовать как в объявлении, так и в описании этой функции.

Основные свойства и правила использования static- и const- функций:

- статические компоненты-функции не имеют указателя this, поэтому обращаться к нестатическим компонентам класса можно только с использованием . или ->;

- не могут быть объявлены две одинаковые функции с одинаковыми именами и типами аргументов, при этом одна статическая, а другая нет;

- статические компоненты-функции не могут быть виртуальными.

Proxi-классы

Реализация скрытия данных и интерфейса некоторого класса может быть выполнена посредством использования proxi-класса. Proxi-класс позволяет клиентам исходного класса использовать этот класс не имея доступа к деталям его реализации. Реализация proxi-класса предполагает следующую общую структуру:

- реализация исходного класса, компоненты которого требуется скрыть;

- реализация proxi-класса для доступа к компонентам исходного класса;

- функция, в которой вызываются компоненты proxi-класса

// заголовочный файл cls.h для класса cls

Class cls

{ int val;

public:

cls(int);

set(int);

int get() const;

};

// файл реализации для класса cls

#include "cls.h"

cls :: cls(int v) {val=v;}

cls :: set(int v){val=v;}

int cls :: get() const {return val;}

// заголовочный файл prox.h для proxi-класса prox

class cls; // предварительное объявление класса cls

Class prox

{ cls *pr; // для этого и требуется предварительное объявление

public:

prox(int);

set(int);

int get() const;

~prox();

};

// файл реализации для proxi-класса prox

#include "prox.h"

#include "cls.cpp"

prox :: prox(int vv) {pr=new cls(vv);}

prox :: set(int vv){pr->set(vv);}

int prox :: get() const {return pr->get();}

prox :: ~prox(){delete pr;}

// программа скрытия данных класса cls посредством proxi-класса prox

#include "iostream.h"

#include "prox.h"

void main()

{ prox obj(1);

cout<<” Значение val класса cls = ”<<obj.get()<<endl;

obj.set(2);

cout<<” Значение val класса cls = ”<<obj.get()<<endl;

}

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

Значение val класса cls = 1

Значение val класса cls = 2

Исходный класс cls содержит один private компонент val и методы, которые требуется скрыть от клиента взаимодействующего с main() функцией. В тоже время клиент должен иметь возможность взаимодействовать с классом cls. Для этого используется класс prox, реализация которого содержит public-интерфейс, аналогичный интерфейсу класса cls, и единственным private-компонентом – указателем на объект класса cls. Класс prox является proxi-классом для класса cls. Так как в определении класса prox используется только указатель на объект класса cls, то включение заголовочного файла класса cls посредством инструкции #include не обязательно. Достаточно объявить класс как тип данных путем предварительного объявления.

Файл реализации cls.cpp включает заголовочный файл cls.h с объявлением класса cls. Файл реализации prox.h класса prox является единственным файлом, включающим файл реализации cls.cpp, а, следовательно, и cls.h. Файл prox.cpp является доступным клиенту только как скомпилированный объектный код и, следовательно, клиент не может видеть взаимодействия между proxy-классом и классом cls.

В main() функцию включается только файл реализации prox.cpp, при этом отсутствует указание на существование класса cls. Следовательно private-данные класса cls скрыты от клиента.

Ссылки

В С(С++) известны три способа передачи данных в функцию: по значению, посредством указателя и используя ссылки.

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

Формат объявления ссылки имеет вид:

тип & имя_ссылки = инициализатор.

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

#include "iostream.h"

#include "string"

Class A

{ char s[80];

int i;

public :

A(char *S,int I):i(I) { strcpy(s,S);}

~A(){}

void see() {cout<<s<<" "<<i<<endl;}

};

void main()

{ A a("aaaaa",3),aa("bbbb",7);

A &b=a; // ссылка на объект класса А инициализирована значением а

cout<<"компоненты объекта :"; a.see();

cout<<"компоненты ссылки :"; b.see();

cout <<"адрес a="<<&a << " адрес &b= "<< &b << endl;

b=aa; // присвоение значений объекта aa ссылке b (и объекту a)

cout<<"компоненты объекта :"; a.see();

cout<<"компоненты ссылки :"; b.see();

int i=4,j=2;

int &ii=i; // ссылка на переменную i типа int

cout << "значение i= "<<i<<" значение ii= "<<ii<<endl;

ii++; // увеличение значения переменной i

cout << "значение i= "<<i<<" значение ii= "<<ii<<endl;

ii=j; // инициализация переменной i значением j

cout << "значение i= "<<i<<" значение ii= "<<ii<<endl;

}

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

компоненты объекта : aaaaa 3

компоненты ссылки : aaaaa 3

адрес a= 0x________ адрес &b= 0х________

компоненты объекта : bbbbb 7

компоненты ссылки : bbbbb 7

значение i= 4 значение ii= 4

значение i= 5 значение ii= 5

значение i= 2 значение ii= 2

Из примера следует, что переменная и ссылка на нее имеют один и тот же адрес в памяти. Изменение значения по ссылке приводит к изменению значения переменной и наоборот.

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

const int &j=4; // j инициализируется const-значением 4

j++; // ошибка l-value specifies const object

int k=j; // переменная k инициализируется значением

// временного объекта

Если тип инициализатора не совпадает с типом ссылки, то могут возникнуть проблемы с преобразованием данных одного типа к другому, например:

double f=2.5;

int &n=(int &)f;

cout<<”f=”<<f<<” n=”<<n; // результат f= 2.5 n=2

Адрес переменной f и ссылки n совпадают, но значения различаются, так как структуры данных плавающего и целочисленного типов различны.

Можно создать ссылку на ссылку, например:

int k=1;

int &n=k; // n – ссылка на k (равно k)

n++; // значение k равно 2

int &nn=n; // nn – ссылка на ссылку n (переменную k)

nn++; // значение k равно 3

Значения адреса переменной k, ссылок n и nn совпадают, следовательно, для ссылки nn не создается временный объект.

На применение переменных ссылочного типа накладываются некоторые ограничения:

- ссылки не являются указателями;

- можно взять ссылку от переменной ссылочного типа;

- можно создать указатель на ссылку;

- нельзя создать массив ссылок;

- ссылки на битовые поля не допускаются.

Параметры ссылки

Если требуется предоставить возможность функции изменять значения передаваемых в нее параметров, то в языке С они должны быть объявлены либо глобально, либо работа с ними в функции осуществляется через передаваемые в нее указатели на эти переменные. В С++ аргументы в функцию можно передавать также и через ссылку. Для этого при объявлении функции перед параметром ставится знак &.

#include <iostream.h>

void fun1(int,int);

void fun2(int &,int &);

void main()

{ int i=1,j=2; // i и j – локальные параметры

cout << "\n адрес переменных в main() i = "<<&i<<" j = "<<&j;

cout << "\n i = "<<i<<" j = "<<j;

fun1(i,j);

cout << "\n значение i = "<<i<<" j = "<<j;

fun2(i,j);

cout << "\n значение i = "<<i<<" j = "<<j;

}

void fun1(int i,int j)

{ cout << "\n адрес переменных в fun1() i = "<<&i<<" j = "<<&j;

int a; // при вызове fun1 i и j из main() копируются

a=i; i=j; j=a; // в стек в переменные i и j при возврате в main()

} // они просто теряются

void fun2(int &i,int &j)

{ cout << "\n адрес переменных в fun2() i = "<<&i<<" j = "<<&j;

int a; // здесь используются ссылки на переменные i и j

a=i; i=j; j=a; // из main() (вторые их имена) и т.о. действия в

// функции производятся с теми же переменными i и j

}

В функции fun2 в инструкции

a=i;

не используется операция *. При объявлении параметра-ссылки компилятор С++ определяет его как неявный указатель (ссылку) и обрабатывает соответствующим образом. При вызове функции fun2 ей автоматически передаются адреса переменных i и j. Таким образом, в функцию передаются не значения переменных, а их адреса, благодаря чему функция может модифицировать значения этих переменных. При вызове функции fun2 знак & перед переменными i и j ставить нельзя.

Независимые ссылки

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

#include <iostream.h>

void main()

{ int i=1;

int &j=i; // j – ссылка (второе имя) переменной i

cout << "\n адрес переменных i = "<<&i<<" j = "<<&j;

cout << "\n значение i = "<<i<<" j = "<<j;

j=5; //

cout << "\n адрес переменных i = "<<&i<<" j = "<<&j;

cout << "\n значение i = "<<i<<" j = "<<j;

}

В результате работы программы будет получено:

адрес переменных i = 0xадрес1j = 0xадрес2

значение i = 1 j = 1

адрес переменных i = 0xадрес1j = 0xадрес2

значение i =5 j = 5

В этом случае компилятор создает временный объект j, которому присваивается адрес ранее созданного объекта i. Далее j может быть использовано как второе имя i.

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