Компоненты-функции 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.