Указатель this. Inline-функции.
Лекция №4
УКАЗАТЕЛЬ THIS. INLINE-ФУНКЦИИ.
КОНСТАНТНЫЕ И СТАТИЧЕСКИЕ ДАННЫЕ И ФУНКЦИИ.
Указатель this
Каждый объект в C++ содержит специальный указатель с именем this, который автоматически создается самим компилятором и указывает на текущий объект. Типом this является Т*, где Т – тип класса текущего объекта. Поскольку указатель this определен в классе, область его действия – класс, в котором он определен. Фактически this является скрытым параметром класса, добавляемым самим компилятором к его определению. При вызове обычной функции-члена класса ей передается указатель this так, как если бы он был первым аргументом. Таким образом, вызов функции-члена
ObjName.FuncName(par1, par2);
компилятор трактует так:
ObjName.FuncName(&ObjName, parl, par2);
Но, поскольку аргументы помещаются в стек справа налево, указатель this помещается в него последним. В теле функции-члена адрес объекта доступен как указатель this. Дружественным функциям и статическим функциям-членам класса указатель this не передается. Нижеследующие примеры демонстрируют использование этого указателя:
Пример 1.
#include<iostream.h>
#include<string.h>
class Prim
{
public:
Prim(char*);
void Privet();
char metka[20];
};
#include "Prim.h"
Prim::Prim(char* name)
{
strcpy(metka, name);
Privet(); // Все три
this->Privet(); // оператора
(*this).Privet(); // эквивалентны
}
void Prim::Privet()
{
cout<<"Hello, "<<metka<<endl; // Оба оператора
cout<<"Hello, "<<this->metka<<endl; // эквивалентны
}
int main()
{
Prim ob("dear!");
}
Пример 2.
// функция возвращает точку − середину отрезка, концы которого заданы
Dot& Dot::Middle(Dot A, Dot B)
{
x=(A.x+B.x)/2.0; y=(A.y+B.y)/2.0; // вычисляет середину отрезка
Print(); // выводит на экран координаты текущей точки
this->Print(); // все три оператора эквивалентны
(*this).Print();
return *this; // возвращает ссылку на текущую точку
}
Как можно видеть, внутри функции-члена Middle(Dot A, Dot B) обращения к данным-членам класса и функциям-членам могут осуществляться как непосредственно по имени, так и с помощью указателя this.
На практике такое употребление указателя this встречается крайне редко. В основном указатель this используется для возврата указателя (в форме: return this;) или ссылки (в форме: return *this;) на соответствующий объект. Этот указатель находит широкое применение при перегрузке операторов.
Встраиваемые (inline) функции
В C++ можно задать функцию, которая, фактически, не вызывается, а ее тело встраивается в программу в месте ее вызова. Она действует почти так же, как макроопределение с параметрами. По сравнению с обычными функциями встраиваемые (inline) функции обладают тем преимуществом, что их вызов не связан с передачей аргументов и возвратом результатов через стек и, следовательно, они выполняются быстрее обычных. Недостатком встраиваемых функций является то, что если они слишком большие и вызываются слишком часто, объем программы сильно возрастает. Из-за этого применение встраиваемых функций обычно ограничивается только очень простыми функциями.
Объявление встраиваемой функции осуществляется с помощью спецификатора inline, который вписывается перед определением функции.
Следует иметь в виду, что спецификатор inline только формулирует требование компилятору сформировать встроенную функцию. Если компилятор не в состоянии выполнить это требование, функция компилируется как обычная.
Компилятор не может сгенерировать функцию как встраиваемую, если она:
§ содержит оператор цикла (for, while, do-while);
§ содержит оператор switch или goto;
§ содержит статическую переменную (static);
§ если функция является рекурсивной;
§ имеет возвращаемый тип, отличный от void, и не содержит оператора return;
§ содержит встроенный код ассемблера.
Компилятор может налагать и другие ограничения на использование inline-функции, которые можно уточнить в описании конкретного компилятора. Ниже приведен пример использования встраиваемой функции:
Важно отметить, что встраиваемая функция должна быть не только объявлена, но и определена до ее первого вызова. Поэтому определение inline-функции обычно размещается в заголовочном файле.
Пример 3.
inline int chet(int x) {return !(x%2);}
int main()
{
int n;
cin>>n;
if(chet(n)) cout<<"Chetnoe\n";
else cout<<"Nechetnoe\n";
}
В этом примере используется встраиваемая функция для проверки числа на четность.
Встроенными могут быть объявлены не только обычные функции, но и функции-члены. Для этого достаточно перед определением функции вставить ключевое слово inline или включить ее определение в объявление класса (в этом случае ключевое слово inline больше не нужно).
Пример 4.
Файл Dot.h
class Dot // класс точки
{
double x, y;
public:
// объявление и определение встраиваемых функций
inline double GetX(){return x;}
inline double GetY(){return y;}
voidGet(double X, double Y) {x=X; y=Y;}
// объявление функций
void SetX(double X);
void SetY(double Y);
};
// определение функций вне тела класса, как встраиваемых
inline void Dot::SetX(double X) {x=X;}
inline void Dot::SetY(double Y) {x=Y;}
Пример 5.
class Any
{
public:
Any();
static intcount;
};
intAny::count=0;
К статическим данным-членам, объявленным в разделе public класса, рекомендуется обращаться с помощью следующей конструкции:
<имя_класса>::<данное_член>
Эта форма обращения отражает тот факт, что соответствующее данное-член является единственным для всего класса.
Если статические данные-члены объявлены как закрытые, то доступ к ним можно получить с помощью обычных функций-членов. Доступ к статическим данным-членам с помощью обычных функций-членов ничем не отличается от доступа к другим данным-членам, но для этого необходимо создать хотя бы один объект данного класса. В связи со сказанным выше, можно дать следующие рекомендации:
1. Применяйте статические данные-члены для совместного использования данных несколькими объектами класса;
2. Ограничьте доступ к статическим данным-членам, объявив их в разделе protected или private.
Пример 6.
Файл Dot.h
class Dot
{
static int count;// объявление статического данного-члена – счётчика объектов
charname;
int x, y;
public:
Dot (char Name) {name=Name; x=0; y=0; count++;}
~Dot() {count--;}
void PrintCount(); // функция выводит количество существующих объектов
};
Файл Dot.cpp
#include "Dot.h"
voidDot::PrintCount()
{
charS[30];
CharToOem("В памяти существует ", S);
cout<<S<<count;
CharToOem(" объекта типа Dot\n", S);
cout<<S;
}
Файл Main.cpp
#include "Dot.h"
int Dot::count=0; // инициализация статического данного-члена
intmain()
{
Dot A('A'), B('B'), C('C');
A.PrintCount();
cout<<Dot::count<<'\n'; // ошибка: данное-член count недоступен
}
В этом примере статическое данное-член count содержит количество существующих объектов. Инициализация данного-члена count осуществляется перед выполнением программы и созданием объектов в файле основной программы с помощью оператора: void Dot::PrintCount()
Обратите внимание, что обращение к этому члену класса в функции main() в операторе cout<<Dot::count<<'\n'; приводит к ошибке, поскольку данное-член класса count объявлено как закрытое и является недоступным за пределами класса.
При выполнении программа выводит на экран:
Пример 7.
Файл Dot.h
class Dot
{
intx,y;
static int count;
public:
Dot () {x=0; y=0; count++;}
~Dot() {count--;}
static int GetCount() {return count;}
void PrintCount();
};
Файл Dot.cpp
#include "Dot.h"
voidDot::PrintCount()
{
charT[30];
CharToOem("В памяти существует ", T);
cout<<T<<count;
CharToOem(" объекта типа Dot\n", T);
cout<<T;
}
Файл Main.cpp
#include "Dot.h"
int Dot::count=0; // инициализация статического данного-члена
intmain()
{
charS[25];
CharToOem("Сейчас есть объектов: ", S);
cout<<S<<Dot:: GetCount()<<'\n';
Dot A, B, C;
A.PrintCount();
cout<<Dot:: GetCount()<<'\n';
}
Пример 8.
class Dot // класс точки
{
const char name; // имя точки – константное данное-член
double x, y; // координаты точки
public: // открытые члены класса
Dot(char Name, double X, double Y): name(Name), x(X), y(Y) {}
// или так Dot(char Name, double X, double Y): name(Name) { x=X; y=Y;}
Dot(char Name): name(Name) {x=0; y=0;}
};
Пример 9.
class Coord // базовый класс координат
{
double x, y;
public:
Coord() {x=0; y=0;}
double GetX() const {return x;} // константная функция
double GetY() const {return y;} // константная функция
void GetVal(double X, double Y) const; // константная функция
void SetX(double X) {x=X;}
void SetY(double Y) {y=Y;}
};
Константные объекты
Можно также создавать константные объекты. Для этого их объявления предваряют модификатором const. Например,
const Coord A(3, 5);
Ключевое слово const информирует компилятор, что состояние данного объекта не должно изменяться. В связи с этим компилятор генерирует сообщение об ошибке, если для константного объекта вызывается функция-член (которая может изменить его данные-члены, изменив тем самым его состояние). Исключением из этого правила являются константные функции-члены, которые в силу своего определения не изменяют состояние объекта.
Пример 10.
class Coord // базовый класс координат
{
int x, y;
public:
Coord(int X, int Y) {x=X; y=Y;}
void SetVal(int X, int Y) {x=X; y=Y;}
void GetVal(double &X, double &Y) const; // константная функция
};
Coord::GetVal(double &X, double &Y) const
{
X=x; Y=y;
}
int main()
{
Coord p(3, 8);
const Coord p1(6, 9); // константный объект
int a, b;
cin>>a>>b;
p.GetVal(a, b);
p1.SetVal(a, b); // Ошибка!!! Вызов неконстантной функции-члена
p1.GetVal(a, b);
}
Чтобы обойти указанные ограничения на использование константных функций-членов класса, в стандарт языка С++ было введено новое ключевое слово mutable. Это ключевое слово позволяет указать, какие данные-члены класса могут быть модифицированы константными функциями-членами. Ключевое слово mutable нельзя использовать для статических и константных членов-данных; оно используется как модификатор типа, то есть синтаксис его использования имеет вид:
mutable <тип_данных> <имя_переменной-члена>;
Пример 11.
class AnyClass
{
mutable int count;
mutable const int *iptr;
public:
int func(int i=0) const
{
count=i++;
iptr=&i;
cout<<iptr;
return count;
}
};
Здесь в операторе mutable const int*iptr; модификатор mutable допустим, так как iptr является указателем на целое число, которое есть константа, хотя сам указатель константой не является.
Массивы объектов класса
Из объектов класса, как и из обычных переменных, можно строить массивы. Синтаксис объявления массива объектов аналогичен синтаксису объявления массивов обычных переменных. Например, следующее объявление создает массив из 10 элементов, которые являются объектами класса AnyClass:
AnyClass obArr[10];
Однако для того, чтобы компилятор смог создать этот массив, он должен использовать конструктор по умолчанию объектов класса. В отношении конструктора по умолчанию действуют перечисленные нами ранее правила. Рекомендуем не полагаться на то, что компилятор сам создаст конструктор по умолчанию, и всегда при объявлении массива объектов некоторого класса включать в этот класс конструктор по умолчанию.
Доступ к элементам массива также аналогичен доступу к массивам переменных любого другого типа. Например:
#include <iostream.h>
class AnyClass
{
int a;
public:
AnyClass(int n){a=n;}
int GetA(){return a;}
};
int main()
{
//Объявление и инициализация массива объектов
AnyClass obArr[5]={13, 17, 21, 23, 27};
int i;
for(i=0;i<5;i++)
{
//Обращение к элементам массива
cout<<obArr[i].GetA()<<' ';
}
}
Эта программа выводит на экран проинициализированные значения массива объектов obArr. Фактически, примененный здесь синтаксис инициализации массива является сокращенной формой следующей конструкции:
AnyClass obArr[5] = {AnyClass(13), AnyClass(17), AnyClass(21),
AnyClass(23), AnyClass(27)};
К сожалению, ею можно воспользоваться только при инициализации массивов объектов, конструктор которых содержит только один параметр. При инициализации массивов объектов с конструктором, содержащим несколько параметров, приходится использовать полную (или длинную) форму конструкции.
В следующем примере создается двумерный массив объектов, конструктор которого содержит два параметра.
#include <iostream.h>
class Coord
{
int x, у;
public:
Coord(int X, int Y){x=X; у=Y;}
int GetX(){return x;}
int GetY(){return y;}
};
int main()
{
Coord coordArr[4][2] ={Coord(3,4), Coord(5,6),
Coord(7,8), Coord(9,10),
Coord(ll,12), Coord(13,14),
Coord(15,16), Coord(17,18)};
int i, j;
for(i=0; i<4;i++)
{
for(j=0; j<2; j++)
{
cout<<coordArr[i][j].GetX()<<' ';
cout<<coordArr[i][j].GetY()<<' ';
}
cout<<endl;
}
}
Использование указателей для доступа к объектам массива совершенно аналогично их использованию для обычных переменных и структур. Арифметика указателей также аналогична. Инкрементирование указателя приводит к тому, что он указывает на следующий объект массива, декрементирование − к тому, что он указывает на предыдущий объект массива. Рассмотрим пример:
#include <iostream.h>
class Coord
{
int x, у;
public:
Coord(int X, int Y){x=X; у=Y;}
int GetX(){return x;}
int GetY(){return y;}
}
int main()
{
Coord obArr[4][2]={Coord(3,4), Coord(5,6),
Coord(7,8), Coord(9,10),
Coord(11,12), Coord(13,14),
Coord(15,16), Coord(17,18)};
int i;
Coord *ptr;
ptr=obArr; //Инициализация указателя адресом массива
for(i=0; i<4; i++)
{
cout<<ptr->GetX()<<' ';
cout<<ptr->GetY()<<"\n";
ptr++; //Переход на следующий объект
}
}
Эта программа выводит на экран в каждой строке значения переменных х и у текущего элемента массива объектов.
Массивы объектов классов могут располагаться в куче. Например:
#include <iostream.h>
class Coord
{
int x, у;
public:
Coord(int X, int Y){x=X; у=Y;}
Coord(){x=0; у=0;}
int GetX() {return x;}
int GetY() {return y;}
};
intmain()
{
int i;
Coord *ptr;
ptr = new Coord[6]; //Создание массива объектов в куче
for (i=0; i<6; i++ )
{
cout<<ptr->GetX()<<' ';
cout<<ptr->GetY()<<"\n";
ptr++ ; //Переход на следующий объект
}
cout<<"\n";
delete[] ptr; //Удаление массива объектов из кучи
}
Следует обратить внимание, что для удаления массива объектов используется форма оператора deleteс квадратными скобками.
Лекция №4
УКАЗАТЕЛЬ THIS. INLINE-ФУНКЦИИ.