Компоненты-функции 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 адрес1 j = 0x адрес2 значение i = 1 j = 1 адрес переменных i = 0x адрес1 j = 0x адрес2 значение i =5 j = 5 В этом случае компилятор создает временный объект j, которому присваивается адрес ранее созданного объекта i. Далее j может быть использовано как второе имя i.
Воспользуйтесь поиском по сайту: ©2015 - 2024 megalektsii.ru Все авторские права принадлежат авторам лекционных материалов. Обратная связь с нами...
|