Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

37.ИНФОРМАТИКА Book си

.pdf
Скачиваний:
37
Добавлен:
23.03.2016
Размер:
1.14 Mб
Скачать

Часть 2. Язык Си++

61

 

 

либо по ссылке, либо по значению. В первом случае программа работает непосредственно с переменной, переданной ей в качестве параметра, во втором же ей доступна не сама переменная, а только ее значение. Различие здесь очевидно: переменную, передаваемую по ссылке, функция может модифицировать, а переданную по значению — нет.

В языке Си параметры передаются только по значению. Если же требуется передать переменную, значение которой функция должна бы изменить, требовалось передавать адрес (указатель) данной переменной. Такая явная работа с адресами неудобна, и часто она служит источником ошибок. При работе на Си++ нет необходимости прибегать к подобным ухищрениям — в нем реализованы оба способа передачи параметров. С появлением ссылок работа с адресами возлагается на компилятор. Приведенные ниже два фрагмента программы позволяют сравнить передачу параметра по адресу и по ссылке:

// Пример передачи параметров по адресу: void PolarToDecart (

float radius, float angle, float *x, float *y)

{

*x=radius*cos(angle); *y=radius*sin(angle);

}

. . .

float x,y;

PolarToDecart(5, M_PI/2, &x, &y );

//M_PI — число пи ( )

//Пример передачи параметров по ссылке: void PolarToDecart (

float radius, float angle,

float &x, float &y)

{

x=radius*cos(angle); y=radius*sin(angle);

}

. . .

float x,y;

PolarToDecart(5, M_PI/2, x, y );

61

62 Си и Си++

// M_PI — число пи

Передача параметров по ссылке также иногда применяется, если аргументом является структура или другой подобный объект, значения которого не изменяются при выполнении операций функции (отсутствует побочный эффект). Это делается для того, чтобы сократить время передачи значений, т.к. при непосредственной передаче структуры программе требуется скопировать всю структуру в стек, а при передаче по ссылке копируется только адрес структуры, что значительно быстрее и требует меньшего размера стека.

Доступ к глобальной переменной, скрытой локальной переменной с тем же именем

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

int

i=0;

// глобальная переменная

. . .

 

int

f() { . . .

int i=0; // локальная переменная

i++;

// увеличение локальной

переменной

i

::i++;

// увеличение глобальной

переменной

i

. . .

 

 

 

}

 

 

 

Операторы динамического распределения памяти

Так как занятие и освобождение блоков памяти является очень распространенной операцией, в Си++ введены два оператора: new и delete. В языке Си для этих целей использовались стандартные библиотечные функции malloc, calloc и free, однако их использование более трудоемкое, чем использование операторов new и delete.

Оператор new выделяет блок памяти, необходимый для размещения переменной или массива (необходимо указать тип и, в случае массива, размерность), и при этом можно присвоить вновь созданной переменной начальное значение. Оператор delete освобождает ранее выделенную память. Размер занятого блока, необходимый для того, чтобы обеспечить правильную работу delete, автоматически записывается в его начало и обычно занимает дополнительно четыре байта. Следует также помнить, что реальный размер занятого блока

62

Часть 2. Язык Си++

63

 

 

не произволен, а кратен определенному числу байт (в Borland C++ 3.0 кратен 16 байтам), поэтому с точки зрения расхода памяти невыгодно резервировать много блоков под небольшие объекты.

В случае успешного выполнения оператор new возвращает адрес созданной переменной или адрес нулевого элемента созданного массива. В ситуации, когда new не может выделить требуемую память, он возвращает NULL, на что программа должна правильно отреагировать, однако такие случаи редки, и программисты иногда позволяют себе не обрабатывать в программе подобные результаты.

Приведем пример, в котором выделяется память под переменные типа int и float и под строку символов. Конечно, в таких случаях использование динамического распределения памяти нерационально, но данная программа проста для понимания:

#include <iostream.h> void main() {

int *ip; float *fp; char *str;

int str_len=256; ip=new int;

fp=new float (3.1415); str=new char[str_len];

if (!(ip && fp && str)) {

cout << "Не хватает памяти!\n"; return; // выход из программы

}

cout << "Введите число, а затем текст.\n"; cin >> *ip >> str;

cout << "Вы ввели строку: " << str << "\n"

<<"Вы ввели число : " << *ip << "\n"

<<" Число пи : " << *fp << endl; delete ip; delete fp;

delete str; // или delete[str_len] str;

}

Поскольку оператор new возвращает указатель, то для использования переменных, под которые он выделит место, надо вводить указатель на эту переменную. Переменная fp сразу при выделении под нее памяти принимает значение 3.1415. Обратите внимание, что для ис-

63

64

Си и Си++

 

 

пользования массива символов (строки) вводится указатель на переменную символьного типа, то есть на нулевой элемент массива. Для создания обычных массивов процедура аналогична.

Не забывайте освобождать память от уже не используемых переменных. При написании программ, работающих с большими объемами памяти, надо обязательно проверять возвращаемое значение операции new на NULL. Иногда встречаются неправильно написанные программы (без освобождения памяти и проверки на переполнение), которые после долгой работы программы заполняют всю доступную память и ―вылетают‖ или ―подвешивают‖ оперативную систему.

2.2 Классы

Классы и структуры

Конструкция описания класса объектов является тем элементом языка Си++, благодаря которому этот язык может быть использован для объектно-ориентированного программирования. Класс представляет собой расширение структуры языка Си. Внешне описание простейшего класса схоже со описанием структуры данных. Так, простейший пример класса объектов ―Точка‖ выглядит следующим образом:

//

Описание

класса

//

Описание

структуры

class Point

{

struct Point {

public:

 

 

float x,

y;

 

float x,

y;

};

 

 

};

 

 

 

 

 

Отличие описания простейших класса и структуры в данном примере состоит лишь в том, что ключевое слово struct заменено на ключевое слово class. В описании класса появилось новое ключевое слово public перед описанием переменных, означающее возможность доступа к элементам x и y объекта класса Point с помощью оператора доступа ―точка‖, как это можно было делать ранее с любыми элементами структур. Элементы класса доступны (видимы) лишь после явного указания этого, а элементы структуры видимы по умолчанию. Все свойства структур, которые были описаны ранее, относятся также и к классам.

Функции и переменные — члены класса

В отличие от структуры языка Си, в языке Си++ класс может включать в себя не только переменные, определяющие новый тип

64

Часть 2. Язык Си++

65

 

 

данных, но и функции, реализующие операции над этим типом данных. Таким образом, в класс входят как переменные-члены класса, так и функции-члены класса. Например, в класс объектов Point можно включить также функцию length:

class Point { public:

float x, y; // переменная-член класса float length() // функция-член класса

{ return sqrt(x*x+y*y); }

};

Как и в случае со структурами языка Си, описание класса объектов представляет собой не какой-либо конкретный объект, а является лишь образом (шаблоном) для создания объектов данного типа. Во время выполнения программы создаются экземпляры данного класса. Для них выделяется достаточно памяти, и инициализируются пере- менные-члены этих экземпляров класса. Экземпляры класса могут существовать различное время, а их переменные могут принимать различные значения.

Доступ к членам класса, как это было и для структур в языке Си, возможен с помощью операторов доступа ―точка‖ и ―->‖. Оператор ―точка‖ используется при работе с самим объектом, а оператор ―->‖ — для работы со ссылками на объект.

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

class ListElem {

ListElem *next; // Указатель на этот же класс

. . . // Элементы класса

};

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

class Point; // Предварительное описание класса. class Rectangle {

public:

65

66 Си и Си++

float left, right, top, bottom; void InRect(Point &LeftTop,

Point &RightBottom)

{ . . . }

. . .

};

описание класса Point может текстуально находиться после описания класса Rectangle или даже в другом файле. Однако благодаря предварительному объявлению класса Point в описании других классов (в нашем примере — в описании класса Rectangle) можно использовать ссылки на переменные класса Point.

Конструкторы и деструкторы

Очень часто ошибки при программировании возникают потому, что программист не инициализирует соответствующим образом переменные в своей программе. Классы объектов языка Си++ могут включать довольно большое число переменных и структур данных, для которых необходимы соответствующее выделение памяти и инициализация. Для этого в языке Си++ в состав функций-членов класса входят специальные функции-конструкторы. В отличие от других функций класса эти функции вызываются только один раз при создании класса. Имена таких функций совпадают с именем класса. Функ- ция-конструктор не вырабатывает результата.

Вызов конструктора Point возможен двумя способами: при описании объекта типа Point и при инициализации указателя на объект этого типа. Приведем фрагмент программы, работающей с классом

Point:

#include <iostream.h> #include <math.h> class Point {

public:

float x, y; // переменные

Point (float aX, float aY) // конструктор { x=aX; y=aY; }

float length() // функция (длина)

{ return sqrt(x*x+y*y); }

//функция (вектор)

void Vect(Point &A, Point &B)

66

Часть 2. Язык Си++

67

{ x=B.x-A.x; y=B.y-A.y; }

};

main() {

Point Center(40,30); // Point Center={40,30}; Point *pPoint1,*pPoint2;

cout << "Center: x=" << Center.x

<<", y=" << Center.y

<<", r=" << Center.length() << endl; pPoint1 = new Point(100,50);

pPoint2 = new Point;

if (!(pPoint1 && pPoint2)) {

cout << "Не хватает памяти!\n"; return 1;

}

cout << "Point: x=" << pPoint1->x

<<", y=" << pPoint1->y

<<", r=" << pPoint1->length() << endl; cout << "Point: Введите x=";

cin >> pPoint1->x;

cout << "Point: Введите y="; cin >> pPoint1->y;

cout << "Point: x=" << pPoint1->x

<<", y=" << pPoint1->y

<<", r=" << pPoint1->length() << endl;

pPoint2->Vect(Center,*pPoint1); delete pPoint1;

cout << "Вектор (Center,Point): x="

<<pPoint2->x << ", y=" << pPoint2->y

<<", r=" << pPoint2->length() << endl; delete pPoint2;

}

Класс состоит из двух переменных x, y и трех функций: Point — конструктор класса, length — длина радиус-вектора, Vect — вычисляет вектор, проведенный из точки A в точку B. Все элементы данного класса доступны для использования.

В примере объект Center непосредственно создается как переменная, а еще два объекта типа Point создаются через указатели

67

68

Си и Си++

 

 

pPoint1 и pPoint2. В первом случае выделенная для объекта память автоматически освобождается при завершении работы блока, т.к. она выделяется в стеке. Во втором случае память для объекта выделяется в области памяти ―куча‖. Такой объект может существовать и после завершения работы функции, в которой он был создан. Для освобождения занимаемой объектом памяти необходимо использовать оператор delete. Заметим еще одну разницу: элементы Center используются через операцию ―точка‖, а элементы объектов, созданных с помощью указателей, — через ―->‖.

Как и все функции Си++, конструкторы могут иметь параметры по умолчанию:

Point (float aX=0, float aY=0) { x=aX; y=aY; }

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

class Rectangle { public:

Point a,b;

Rectangle

(Point

&A, Point &B) { a=A; b=B; }

Rectangle

(float

x1,

float

y1,

 

float

x2,

float

y2)

{ a.x=x1;

a.y=y1; b.x=x2; b.y=y2; }

};

Заметим, что классы, как и структуры, можно присваивать друг другу, если они одного типа.

Деструкторы класса предназначены для выполнения действий, которые необходимо выполнить перед корректным уничтожением объекта данного класса. Таким действием, в частности, может быть освобождение памяти, выделенной для объекта в области памяти ―куча‖.

Имя деструктора совпадает с именем класса, но дополнительно перед именем класса стоит символ ~. Если память для объекта была выделена в стеке, то деструктор будет вызван неявно при выходе из того блока, в котором этот объект был описан. Для объектов, созданных с помощью оператора new, необходимо явное удаление объекта с применением оператора delete. Если оператор delete не был использован для удаления объекта, то объект существует до завершения функции main.

68

Часть 2. Язык Си++

69

 

 

Вслучае если вы не описали деструктор класса, он создается неявно и освобождает память, занимаемую непосредственно переменны- ми-членами класса. Если в объекте хранились указатели на некоторую область памяти, то в этом случае она не освобождается.

Взаключение приведем пример использования деструктора:

class TArray { public:

int *p; // указатель (будет массив int) // конструктор

TArray(int n) { p=new int[n]; } // деструктор

~TArray() {delete p; }

};

Ссылка класса на самого себя

При написании функции-члена класса у вас может возникнуть необходимость получить указатель на объект, к которому принадлежит данная функция, например, если вам нужно использовать указатель на этот объект в качестве параметра при вызове некоторой другой функции. Это можно сделать, поскольку в функциях-членах любого класса присутствует скрытый аргумент функции — указатель на текущий объект, который имеет имя this. В качестве примера приведем конструктор класса Point, который может описывать следующую функцию:

Point (float aX=0, float aY=0) { this->x=aX; this->y=aY; }

Понятно, что таким образом никто не пишет, однако смысл функции остается прежним, и данный пример достаточно наглядно демонстрирует работу указателя this.

Скрытые и видимые элементы класса

По умолчанию все члены класса являются скрытыми. Для того чтобы члены класса стали видимыми, в интерфейс класса вставляется уже знакомое вам ключевое слово public (―видимые‖). Все члены класса, описываемые далее в интерфейсе класса, становятся видимыми вне класса, и к ним возможен доступ с помощью операторов ―точка‖ и ―->‖. Вставив в интерфейс ключевое слово private (―скрытые‖), вы можете вновь сделать описываемые далее члены класса не-

69

70

Си и Си++

 

 

видимыми:

 

class . . .

 

{

// Скрытая часть

. . .

 

public:

// Видимая часть

. . .

 

private: // Скрытая часть

. . .

 

public:

// Видимая часть

. . .

 

}

 

Существует еще один способ управления доступом с помощью ключевого слова protected (―защищенные‖). Однако реально этот способ управления доступом к членам класса проявляет свою особенность лишь при наследовании классов. Пока же защищенные члены класса аналогичны скрытым членам класса, а ключевое слово protected идентично ключевому слову private.

Интерфейс класса и реализация класса

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

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

70