Я:
Результат
Архив

МЕТА - Украина. Рейтинг сайтов Webalta Уровень доверия



Союз образовательных сайтов
Главная / Учебники / Учебники на русском языке / Компьютерные науки / C++


Компьютерные науки - Учебники на русском языке - Скачать бесплатно


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

1=sizeof(char)<=sizeof(short) <= sizeof(int) <= sizeof(long)
sizeof(float) <= sizeof(double)

В целом, предполагать что-либо еще относительно основных
типов неразумно. В частности, то, что целое достаточно для
хранения указателя, верно не для всех машин.

К основному типу можно применять прилагательное const.
Это дает тип, имеющий те же свойства, что и исходный тип, за
исключением того, что значение переменных типа const не может
изменяться после инициализации.

const float pi = 3.14;
const char plus = '+';

Символ, заключенный в одинарные кавычки, является сим-
вольной константой. Заметьте, что часто константа, определен-
ная таким образом, не занимает память. Просто там, где требу-
ется, ее значение может использоваться непосредственно.
Константа должна инициализироваться при описании. Для пере-
менных инициализация необязательна, но настоятельно рекомен-
дуется. Оснований для введения локальной переменной без ее
инициализации очень немного.

К любой комбинации этих типов могут применяться арифме-
тические операции:

+ (плюс, унарный и бинарный)
- (минус, унарный и бинарный)
* (умножение)
/ (деление)


- 16 -


А также операции сравнения:
== (равно)
!= (не равно)
< (меньше)
> (больше)
<= (меньше или равно)
>= (больше или равно)

Заметьте, что целое деление дает целый результат: 7/2
есть 3. Над целыми может выполняться операция % получения ос-
татка: 7%2 равно 1.

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

double d = 1;
int i = 1;
d = d + i;
i = d + i;

1.3.2 Производные Типы

Вот операции, создающие из основных типов новые типы:

* указатель на
*const константный указатель на
& ссылка на
[] вектор*
() функция, возвращающая

--------------------
* одномерный массив. Это принятый термин (например, век-
тора прерываний), и мы сочли, что стандартный перевод его как
"массив" затуманит изложение. (прим. перев.)

Например:

char* p // указатель на символ
char *const q // константный указатель на символ
char v[10] // вектор из 10 символов

Все вектора в качестве нижней границы индекса имеют
ноль, поэтому в v десять элементов: v[0]..v[9]. Функции объ-
ясняются в #1.5, ссылки в #1.9. Переменная указатель может
содержать адрес объекта соответствующего типа:

char c;
// ...
p = &c; // p указывает на c

Унарное & является операцией взятия адреса.

1.4 Выражения и Операторы

В С++ имеется богатый набор операций, с помощью которых
в выражениях образуются новые значения и изменяются значения
переменных. Поток управления в программе задается с помощью
операторов, а описания используются для введения в программе
имен переменных, констант и т.д. Заметьте, что описания явля-
ются операторами, поэтому они свободно могут сочетаться с
другими операторами.





- 17 -

1.4.1 Выражения
В С++ имеется большое число операций, и они будут объяс-
няться там, где (и если) это потребуется. Следует учесть, что
операции

~ (дополнение)
& (И)
^ (исключающее ИЛИ)
! (включающее ИЛИ)
<< (логический сдвиг влево)
>> (логический сдвиг вправо)

применяются к целым, и что нет отдельного типа данных для
логических действий.

Смысл операции зависит от числа операндов. Унарное & яв-
ляется операцией взятия адреса, а бинарное & - это операция
логического И. Смысл операции зависит также от типа ее опе-
рандов: + в выражении a+b означает сложение с плавающей точ-
кой, если операнды имеют тип float, но целое сложение, если
они типа int. В #1.8 объясняется, как можно определить опера-
цию для типа, определяемого пользователем, без потери ее зна-
чения, предопределенного для основных и производных типов.

В С++ есть операция присваивания =, а не оператор прис-
ваивания, как в некоторых языках. Таким образом, присваивание
может встречаться в неожиданном контексте, например, x=sqrt(a
=3*x). Это бывает полезно. a=b=c означает присвоение c объек-
ту b, а затем объекту a. Другим свойством операции присваива-
ния является то, что она может совмещаться с большинством би-
нарных операций. Например, x[i+3]*=4 означает
x[i+3]=x[i+3]*4, за исключением того факта, что выражение x[i
+3] вычисляется только один раз. Это дает привлекательную
степень эффективности без необходимости обращения к оптимизи-
рующим компиляторам. К тому же это более кратко.

В большинстве программ на С++ широко применяются указатели.
Унарная операция * разыменовывает* указатель, т.е. *p есть
объект, на который указывает p. Эта операция также называется
косвенной адресацией. Например, если имеется char* p, то *p
есть символ, на который указывает p. Часто при работе с ука-
зателями бывают полезны операция увеличения ++ и операция
уменьшения --. Предположим, p указывает на элемент вектора v,
тогда p++ делает p указывающим на следующий элемент.

--------------------
* англ. dereference - получить значение объекта, на ко-
торый указывает данный указатель. (прим. перев.)

1.4.2 Операторы Выражения

Самый обычный вид оператора - выражение;. Он состоит из
выражения, за которым следует точка с запятой. Например:

a = b*3+c;
cout << "go go go";
lseek(fd,0,2);

1.4.3 Пустой оператор

Простейшей формой оператора является оператор:

;

Он не делает ничего. Однако он может быть полезен в тех
случаях, когда синтаксис требует наличие оператора, а вам


- 18 -
оператор не нужен.
1.4.4 Блоки
Блок - это возможно пустой список операторов, заключен-
ный в фигурные скобки:

(* a=b+2; b++; *)

Блок позволяет рассматривать несколько операторов как
один. Область видимости имени, описанного в блоке, простира-
ется до конца блока. Имя можно сделать невидимым с помощью
описаний такого же имени во внутренних блоках.

1.4.5 Оператор if

Программа в следующем примере осуществляет преобразова-
ние дюймов в сантиметры и сантиметров в дюймы. Предполагает-
ся, что вы укажете единицы измерения вводимых данных, добав-
ляя i для дюймов и c для сантиметров:

#include

main()
(*
const float fac = 2.54;
float x, in, cm;
char ch = 0;

cout << "введите длину: ";
cin >> x >> ch;

if (ch == 'i') (* // inch - дюймы
in = x;
cm = x*fac;
*)
else if (ch == 'c') // cm - сантиметры
in = x/fac;
cm = x;
*)
else
in = cm = 0;

cout << in << " in = " << cm << " cm\n";
*)

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

1.4.6 Операторы switch

Оператор switch производит сопоставление значения с мно-
жеством констант. Проверки в предыдущем примере можно запи-
сать так:

switch (ch) (*
case 'i':
in = x;
cm = x*fac;
break;
case 'c':
in = x/fac;
cm = x;
break;
default:
in = cm = 0;
break;
*)
Операторы break применяются для выхода из оператора


- 19 -
switch. Константы в вариантах case должны быть различными, и
если проверяемое значение не совпадает ни с одной из конс-
тант, выбирается вариант default. Программисту не обязательно
предусматривать default.

1.4.7 Оператор while

Рассмотрим копирование строки, когда заданы указатель p
на ее первый символ и указатель q на целевую строку. По сог-
лашению строка оканчивается символом с целым значением 0.

while (p != 0) (*
*q = *p; // скопировать символ
q = q+1;
p = p+1;
*)
*q = 0; // завершающий символ 0 скопирован не был

Следующее после while условие должно быть заключено в
круглые скобки. Условие вычисляется, и если его значение не
ноль, выполняется непосредственно следующий за ним оператор.
Это повторяется до тех пор, пока вычисление условия не даст
ноль.

Этот пример слишком пространен. Можно использовать опе-
рацию ++ для непосредственного указания увеличения, и провер-
ка упростится:

while (*p) *q++ = *p++;
*q = 0;

где конструкция *p++ означает: "взять символ, на который
указывает p, затем увеличить p."

Пример можно еще упростить, так как указатель p разыме-
новывается дважды за каждый цикл. Копирование символа можно
делать тогда же, когда производится проверка условия:

while (*q++ = *p++) ;

Здесь берется символ, на который указывает p, p увеличи-
вается, этот символ копируется туда, куда указывает q, и q
увеличивается. Если символ ненулевой, цикл повторяется. Пос-
кольку вся работа выполняется в условии, не требуется ни од-
ного оператора. Чтобы указать на это, используется пустой
оператор. С++ (как и C) одновременно любят и ненавидят за
возможность такого чрезвычайно краткого ориентированного на
выразительность программирования*.

--------------------
* в оригинале expression-oriented (expression - выразитель-
ность и выражение). (прим. перев.)

1.4.8 Оператор for

Рассмотрим копирование десяти элементов одного вектора в
другой:

for (int i=0; i<10; i++) q[i]=p[i];










- 20 -

Это эквивалентно
int i = 0;
while (i<10) (*
q[i] = p[i];
i++;
*)
но более удобочитаемо, поскольку вся информация, управ-
ляющая циклом, локализована. При применении операции ++ к це-
лой переменной к ней просто добавляется единица. Первая часть
оператора for не обязательно должна быть описанием, она может
быть любым оператором. Например:

for (i=0; i<10; i++) q[i]=p[i];

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

1.4.9 Описания

Описание - это оператор, вводящий имя в программе. Оно
может также инициализировать объект с этим именем. Выполнение
описания означает, что когда поток управления доходит до опи-
сания, вычисляется инициализирующее выражение (инициализатор)
и производится инициализация. Например:

for (int i = 1; i
int t = v[i-1];
v[i-1] = v[i];
v[i] = t;
*)

При каждом выполнении оператора for i будет инициализи-
роваться один раз, а t MAX-1 раз.

1.5 Функции

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

extern float pow(float, int); //pow() определена в другом
месте

main()
(*
for (int i=0; i<10; i++) cout << pow(2,i) << "\n";
*)

Первая строка функции - ее описание, указывающее, что
pow - функция, получающая параметры типа float и int и возв-
ращающая float. Описание функции используется для того, чтобы
сделать определенными обращения к функции в других местах.

При вызове функции тип каждого параметра сопоставляется
с ожидаемым типом точно так же, как если бы инициализирова-
лась переменная описанного типа. Это гарантирует надлежащую
проверку и преобразование типов. Например, обращение
pow(12.3,"abcd") вызовет недовольство компилятора, поскольку
"abcd" является строкой, а не int. При вызове pow(2,i) компи-
лятор преобразует 2 к типу float, как того требует функция.
Функция pow может быть определена например так:



- 21 -


float pow(float x, int n)
(*
if (n < 0) error("sorry, negative exponent to pow()");
// извините, отрицательный показатель для pow()
switch (n) (*
case 0: return 1;
case 1: return x;
default: return x*pow(x,n-1);
*)
*)
Первая часть определения функции задает имя функции, тип
возвращаемого ею значения (если таковое имеется) и типы и
имена ее параметров (если они есть). Значение возвращается из
функции с помощью оператора return.

Разные функции, обычно имеют разные имена, но функциям,
выполняющим сходные действия над объектами различных типов,
иногда лучше дать возможность иметь одинаковые имена. Если
типы их параметров различны, то компилятор всегда может раз-
личить их и выбрать для вызова нужную функцию. Может, напри-
мер, иметься одна функция возведения в степень для целых пе-
ременных и другая для переменных с плавающей точкой:

overload pow;
int pow(int, int);
double pow(double, double);
//...
x=pow(2,10);
y=pow(2.0,10.0);

Описание

overload pow;

сообщает компилятору, что использование имени pow более
чем для одной функции является умышленным.

Если функция не возвращает значения, то ее следует опи-
сать как void:

void swap(int* p, int* q) // поменять местами
(*
int t = *p;
*p = *q;
*q = t;
*)

1.6 Структура программы

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

extern double sqrt(double);
extern instream cin;

Самый обычный способ обеспечить согласованность исходных
файлов - это поместить такие описания в отдельные файлы, на-
зываемые заголовочными (или хэдер) файлами, а затем включить,
то есть скопировать, эти заголовочные файлы во все файлы, где
нужны эти описания. Например, если описание sqrt хранится в
заголовочном файле для стандартных математических функций
math.h, и вы хотите извлечь квадратный корень из 4, можно на-


- 22 -
писать:

#include
//...
x = sqrt(4);

Поскольку обычные заголовочные файлы включаются во мно-
гие исходные файлы, они не содержат описаний, которые не
должны повторяться. Например, тела функций даются только для
inline-подставляемых функций (#1.12) и инициализаторы даются
только для констант (#1.3.1). За исключением этих случаев,
заголовочный файл является хранилищем информации о типах. Он
обеспечивает интерфейс между отдельно компилируемыми частями
программы.

В команде включения include имя файла, заключенное в уг-
ловые скобки, например , относится к файлу с этим
именем в стандартном каталоге (часто это /usr/include/CC), на
файлы, находящиеся в каких-либо других местах ссылаются с по-
мощью имен, заключенных в двойные кавычки. Например:

#include "math1.h"
#include "/usr/bs/math2.h"

включит math1.h из текущего пользовательского каталога,
а math2.h из каталога /usr/bs.

Здесь приводится очень маленький законченный пример
программы, в котором строка определяется в одном файле, а ее
печать производится в другом. Файл header.h определяет необ-
ходимые типы:

// header.h

extern char* prog_name;
extern void f();

В файле main.c находится главная программа:

// main.c

#include "header.h"
char* prog_name = "дурацкий, но полный";
main()
(*
f();
*)

а файл f.c печатает строку:

// f.c

#include
#include "header.h"
void f()
(*
cout << prog_name << "\n";
*)

Скомпилировать и запустить программу вы можете например
так:

$ CC main.c f.c -o silly
$ silly
дурацкий, но полный
$



- 23 -


1.7 Классы

Давайте посмотрим, как мы могли бы определить тип потока
вывода ostream. Чтобы упростить задачу, предположим, что для
буферизации определен тип streambuf. Тип streambuf на самом
деле определен в , где также находится и настоящее
определение ostream.

Пожалуйста, не испытывайте примеры, определяющие ostream
в этом и последующих разделах. Пока вы не сможете полностью
избежать использования , компилятор будет возражать
против переопределений.

Определение типа, определяемого пользователем (который в
С++ называется class, т.е. класс), специфицирует данные, не-
обходимые для представления объекта этого типа, и множество
операций для работы с этими объектами. Определение имеет две
части: закрытую (private) часть, содержащую информацию, кото-
рой может пользоваться только его разработчик, и открытую
(public) часть, представляющую интерфейс типа с пользовате-
лем:

class ostream (*
streambuf* buf;
int state;
public:
void put(char*);
void put(long);
void put(double);
*)

Описания после метки public: задают интерфейс: пользова-
тель может обращаться только к трем функциям put(). Описания
перед меткой public задают представление объекта класса
ostream. Имена buf и state могут использоваться только функ-
циями put(), описанными в открытой части.

class определяет тип, а не объект данных, поэтому чтобы
использовать ostream, мы должны один такой объект описать
(так же, как мы описываем переменные типа int):

ostream my_out;

Считая, что my_out был соответствующим образом проиници-
ализирован (как, объясняется в #1.10), его можно использовать
например так:

my_out.put("Hello, world\n");

С помощью операции точка выбирается член класса для дан-
ного объекта этого класса. Здесь для объекта my_out вызывает-
ся член функция put().

Функция может определяться так:

void ostream::put(char* p)
(*
while (*p) buf.sputc(*p++);
*)

где sputc() - функция, которая помещает символ в
streambuf. Префикс ostream необходим, чтобы отличить put()
ostream'а от других функций с именем put().

Для обращения к функции члену должен быть указан объект


- 24 -
класса. В функции члене можно ссылаться на этот объект неяв-
но, как это делалось выше в ostream::put(): в каждом вызове
buf относится к члену buf объекта, для которого функция выз-
вана.
Можно также ссылаться на этот объект явно посредством
указателя с именем this. В функции члене класса X this неявно
описан как X* (указатель на X) и инициализирован указателем
на тот объект, для которого эта функция вызвана. Определение
ostream::put() можно также записать в виде:

void ostream::put(char* p)
(*
while (*p) this->buf.sputc(*p++);
*)
Операция -> применяется для выбора члена объекта, задан-
ного указателем.

1.8 Перегрузка операций

Настоящий класс ostream определяет операцию <<, чтобы
сделать удобным вывод нескольких объектов одним оператором.
Давайте посмотрим, как это сделано.

Чтобы определить @, где @ - некоторая операция языка
С++, для каждого определяемого пользователем типа вы опреде-
ляете функцию с именем operator@, которая получает параметры
соответствующего типа. Например:

class ostream (*
//...
ostream operator<<(char*);
*);

ostream ostream::operator<<(char* p)
(*
while (*p) buf.sputc(*p++);
return *this;
*)

определяет операцию << как член класса ostream, поэтому
s<

<<(p), когда s является
ostream и p - указатель на символ. Операция << бинарна, а
функция operator<<(char*) на первый взгляд имеет только один
параметр. Однако, помимо этого она имеет свой стандартный па-
раметр this.

То, что в качестве возвращаемого значения возвращается
ostream, позволяет применять << к результату операции вывода.
Например, s<


(s.operator<<(p)).operator<<(q). Так задаются операции вывода
для встроенных типов.

С помощью множества операций, заданных как открытые чле-
ны класса ostream, вы можете теперь определить << для такого
определяемого типа, как complex, не изменяя описание класса
ostream:

ostream operator<<(ostream s, complex z)
// у complex две части: действительная real и мнимая imag
// печатает complex как (real,imag)
(*
return s << "(" << z.real << "," << z.imag << ")';
*)

Поскольку operator<<(ostream,complex) не является функ-
цией членом, для бинарности необходимо два явных параметра.
Вывод значений будет производиться в правильном порядке, по-


- 25 -
тому что <<, как и большинство операций С++, группирует слева
направо, то есть f<<<
ции операций компилятору известна разница между функциями
членами и функциями не членами. Например, если z - комплекс-
ная переменная, то s<
стандартной функции (не члена) operator<<(s,z).

1.9 Ссылки

К сожалению, последняя версия ostream содержит серьезную
ошибку и к тому же очень неэффективна. Сложность состоит в
том, что ostream копируется дважды при каждом использовании
<<: один раз как параметр и один раз как возвращаемое значе-
ние. Это оставляет state неизмененным после каждого вызова.
Необходима возможность передачи указателя на ostream вместо
передачи самого ostream.

Это можно сделать с помощью ссылок. Ссылка действует как
имя для объекта. T& означает ссылку на T. Ссылка должна быть
инициализирована, и она становится другим именем того объек-
та, которым она инициализирована. Например:

ostream& s1 = my_out;
ostream& s2 = cout;

Теперь можно использовать ссылку s1 и my_out одинаково,
и они будут иметь одинаковые значения. Например, присваивание

s1 = s2;

копирует объект, на который ссылается s2 (то есть,
cout), в объект, на который ссылается s1 (то есть, my_out).
Члены берутся с помощью операции точка

s1.put("не надо использовать ->");

а если применить операцию взятия адреса, то вы получите
адрес объекта, на который ссылается ссылка:

&s1 == &my_out

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

ostream& operator<<(ostream& s, complex z) (*
return s << "(" << z.real << "," << z.imag << ")";
*)

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

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






- 26 -


class istream (*
//...
int state;
public:
istream& operator>>(char&);
istream& operator>>(char*);
istream& operator>>(int&);
istream& operator>>(long&);
//...
*);

Заметьте, что для чтения long и int используются разные
функции, тогда как для их печати требовалась только одна. Это
вполне обычно, и причина в том, что int может быть преобразо-
вано в long по стандартным правилам неявного преобразования
(#с.6.6), избавляя таким образом программиста от беспокойства
по поводу написания обеих функций ввода.

1.10 Конструкторы

Определение ostream как класса сделало члены данные зак-
рытыми. Только функция член имеет доступ к закрытым членам,
поэтому надо предусмотреть функцию для инициализации. Такая
функция называется конструктором и отличается тем, что имеет
то же имя, что и ее класс:

class ostream (*
//...
ostream(streambuf*);
ostream(int size, char* s);
*);

Здесь задано два конструктора. Один получает вышеупомя-
нутый streambuf для реального вывода, другой получает размер
и указатель на символ для форматирования строки. В описании
необходимый для конструктора список параметров присоединяется
к имени. Теперь вы можете, например, описать такие потоки:

ostream my_out(&some_stream_buffer);
char xx[256];
ostream xx_stream(256,xx);

Описание my_out не только задает соответствующий объем
памяти где-то в другом месте, оно также вызывает конструктор
ostream::ostream(streambuf*), чтобы инициализировать его па-
раметром &some_stream_buffer, предположительно указателем на
подходящий объект класса streambuf. Описание конструкторов
для класса не только дает способ инициализации объектов, но
также обеспечивает то, что все объекты этого класса будут
проинициализированы. Если для класса были описаны конструкто-
ры, то невозможно описать переменную этого класса так, чтобы
конструктор не был вызван. Если класс имеет конструктор, не
получающий параметров, то этот конструктор будет вызываться в
том случае, если в описании нет ни одного параметра.

1.11 Вектора

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


- 27 -
запрограммировать это сами." Давайте посмотрим, действительно
ли оправдан такой ответ. Другими словами, проверим средства
абстракции языка С++, попытавшись реализовать эти возможности
для векторных типов, которые мы создадим сами, и посмотрим,
какие с этим связаны трудности, каких это требует затрат, и
насколько получившиеся векторные типы удобны в обращении.
class vector (*
int* v;
int sz;
public:
vector(int); // конструктор
~vector(); // деструктор
int size() (* return sz; *)
void set_size(int);
int& operator[](int);
int& elem(int i) (* return v[i]; *)
*);
Функция size возвращает число элементов вектора, таким
образом индексы должны лежать в диапазоне 0 ... size()-1.
Функция set_size сделана для изменения этого размера, elem
обеспечивает доступ к элементам без проверки индекса, а
operator[] дает доступ с проверкой границ.

Идея состоит в том, чтобы класс сам был структурой фик-
сированного размера, управляющей доступом к фактической памя-
ти вектора, которая выделяется конструктором вектора с по-
мощью распределителя свободной памяти new:

vector::vector(int s)
(*
if (s<=0) error("bad vector size");
// плохой размер вектора
sz = s;
v = new int[s];
*)

Теперь вы можете описывать вектора типа vector почти
столь же элегантно, как и вектора, встроенные в сам язык:

vector v1(100);
vector v2(nelem*2-4);

Операцию доступа можно определить как

int& vector::operator[](int i)
(*
if(i<0 !! sz<=i) error("vector index out of range");
// индекс выходит за границы вектора
return v[i];
*)

Операция !! (ИЛИИЛИ) - это логическая операция ИЛИ. Ее
правый операнд вычисляется только тогда, когда это необходи-
мо, то есть если вычисление левого операнда дало ноль. Возв-
ращение ссылки обеспечивает то, что запись [] может использо-
ваться с любой стороны операции присваивания:

v1[x] = v2[y];

Функция со странным именем ~vector - это деструктор, то
есть функция, описанная для того, чтобы она неявно вызыва-
лась, когда объект класса выходит из области видимости. Дест-
руктор класса C имеет имя ~C. Если его определить как






- 28 -


vector::~vector()
(*
delete v;
*)

то он будет, с помощью операции delete, освобождать
пространство, выделенное конструктором, поэтому когда vector
выходит из области видимости, все его пространство возвраща-
ется обратно в память для дальнейшего использования.

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

vector s(100);
//...
i = s.size();
x = elem(i-1);

порождает код, эквивалентный

//...
i = 100;
x = s.v[i-1];

С++ компилятор обычно достаточно разумен, чтобы генери-
ровать настолько хороший код, насколько вы можете получить в
результате прямого макрорасширения. Разумеется, компилятор
иногда вынужден использовать временные переменные и другие
уловки, чтобы сохранить семантику.

Вы можете указать, что вы хотите, чтобы функция была
inline-подставляемой, поставив ключевое слово inline, или,
для функции члена, просто включив определение функции в опи-
сание класса, как это сделано в предыдущем примере для size()
и elem().

При хорошем использовании inline-функции резко повышают
скорость выполнения и уменьшают размер объектного кода. Одна-
ко, inlineфункции запутывают описания и могут замедлить ком-
пиляцию, поэтому, если они не необходимы, то их желательно
избегать. Чтобы inlineфункция давала существенный выигрыш по
сравнению с обычной функцией, она должна быть очень малень-
кой.

1.13 Производные классы

Теперь давайте определим вектор, для которого пользова-
тель может задавать границы изменения индекса.

class vec: public vector (*
int low, high;
public:
vec(int,int);


- 29 -
int& elem(int);
int& operator[](int);
*);

Определение vec как
:public vector

означает, в первую очередь, что vec - это vector. То
есть, тип vec имеет (наследует) все свойства типа vector до-
полнительно к тем, что описаны специально для него. Говорят,
что vector является базовым классом для vec, а о vec говорит-
ся, что он производный класс от vector.
Класс vec модифицирует класс vector тем, что в нем зада-
ется другой конструктор, который требует от пользователя ука-
зывать две границы изменения индекса, а не длину, и имеются
свои собственные функции доступа elem(int) и operator[](int).
Функция elem() класса vec легко выражается через elem() клас-
са vector:
int& vec::elem(int i)
(*
return vector::elem(i-low);
*)

Операция разрешения области видимости :: используется
для того, чтобы не было бесконечной рекурсии обращения к
vec::elem() из нее самой. с помощью унарной операции :: можно
ссылаться на нелокальные имена. Было бы разумно описать vec::
elem() как inline, поскольку, скорее всего, эффективность су-
щественна, но необязательно, неразумно и невозможно написать
ее так, чтобы она непосредственно использовала закрытый член
v класса vector. Функции производного класса не имеют специ-
ального доступа к закрытым членам его базового класса.

Конструктор можно написать так:

vec::vec(int lb, int hb) : (hb-lb+1)
(*
if (hb-lb<0) hb = lb;
low = lb;
high = hb;
*)

Запись: (hb-lb+1) используется для определения списка
параметров конструктора базового класса vector::vector().
Этот конструктор вызывается перед телом vec::vec(). Вот не-
большой пример, который можно запустить, если скомпилировать
его вместе с остальными описаниями vector:




Назад


Новые поступления

Украинский Зеленый Портал Рефератик создан с целью поуляризации украинской культуры и облегчения поиска учебных материалов для украинских школьников, а также студентов и аспирантов украинских ВУЗов. Все материалы, опубликованные на сайте взяты из открытых источников. Однако, следует помнить, что тексты, опубликованных работ в первую очередь принадлежат их авторам. Используя материалы, размещенные на сайте, пожалуйста, давайте ссылку на название публикации и ее автора.

281311062 © insoft.com.ua,2007г. © il.lusion,2007г.
Карта сайта