C++ для начинающих - Липпман 19 стр.


Текущий свободный элемент называют вершиной стека. Операция push присваивает этому элементу новое значение , после чего вершина смещается вверх (становится на 1 больше). Пусть наш стек использует для хранения элементов вектор. Какую из форм операции увеличения следует применить? Сначала мы используем текущее значение, потом увеличиваем его. Это постфиксная форма:

stack[ top++ ] = value;

Что делает операция pop? Уменьшает значение вершины (текущая вершина показывает на пустой элемент), затем извлекает значение. Это префиксная форма операции уменьшения:

int value = stack[ --top ];

(Реализация класса stack приведена в конце этой главы. Стандартный класс stack рассматривается в разделе 6.16.)

Упражнение 4.8

Как вы думаете, почему язык программирования получил название С++, а не ++С?

4.6. Операции с комплексными числами

Класс комплексных чисел стандартной библиотеки С++ представляет собой хороший пример использования объектной модели. Благодаря перегруженным арифметическим операциям объекты этого класса используются так, как будто они принадлежат одному из встроенных типов данных. Более того, в подобных операциях могут одновременно принимать участие и переменные встроенного арифметического типа, и комплексные числа. (Отметим, что здесь мы не рассматриваем общие вопросы математики комплексных чисел. См. [PERSON68] или любую книгу по математике.) Например, можно написать:

#inc1ude complex

comp1ex double a;

comp1ex double b;

// ...

complex double с = a * b + a / b;

Комплексные и арифметические типы разрешается смешивать в одном выражении:

complex double complex_obj = a + 3.14159;

Аналогично комплексные числа инициализируются арифметическим типом, и им может быть присвоено такое значение:

double dval = 3.14159;

complex_obj = dval;

Или

int ival = 3;

complex_obj = ival;

Однако обратное неверно. Например, следующее выражение вызовет ошибку компиляции:

// ошибка: нет неявного преобразования

// в арифметический тип

double dval = complex_obj;

Нужно явно указать, какую часть комплексного числа – вещественную или мнимую – мы хотим присвоить обычному числу. Класс комплексных чисел имеет две функции, возвращающих соответственно вещественную и мнимую части. Мы можем обращаться к ним, используя синтаксис доступа к членам класса:

double re = complex_obj.real();

double im = complex_obj.imag();

или эквивалентный синтаксис вызова функции:

double re = real(complex_obj);

double im = imag(complex_obj);

Класс комплексных чисел поддерживает четыре составных оператора присваивания: +=, -=, *= и /=. Таким образом,

complex_obj += second_complex_obj;

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

complex double complex0( 3.14159, -2.171 );

comp1ex double complex1( complexO.real() );

cout complexO " " complex1 endl;

выглядит так:

( 3.14159, -2.171 ) ( 3.14159, 0.0 )

Оператор ввода понимает любой из следующих форматов:

// допустимые форматы для ввода комплексного числа

// 3.14159 == comp1ex( 3.14159 );

// ( 3.14159 ) == comp1ex( 3.14159 );

// ( 3.14, -1.0 ) == comp1ex( 3.14, -1.0 );

// может быть считано как

// cin a b с

// где a, b, с - комплексные числа

3.14159 ( 3.14159 ) ( 3.14, -1.0 )

Кроме этих операций, класс комплексных чисел имеет следующие функции-члены: sqrt(), abs(), polar(), sin(), cos(), tan(), exp(), log(), log10() и pow().

Упражнение 4.9

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

complex_obj += 1;

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

#include complex

operator+=( complexdouble cval, double dval )

{

return cval += complexdouble( dval );

}

(Это пример перегрузки оператора для определенного типа данных, детально рассмотренной в главе 15.)

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

#include iostream

#include complex

// определения операций...

int main() {

complex double cval ( 4.0, 1.0 );

cout cval endl;

cval += 1;

cout cval endl;

cval -= 1;

cout cval endl;

cval *= 2;

cout cval endl;

cout /= 2;

cout cval endl;

}

Упражнение 4.10

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

cval += 1;

что означает увеличение на 1 вещественной части cval, то и операция инкремента выглядела бы вполне законно. Реализуйте эти операции для типа complexdouble и выполните следующую программу:

#include iostream

#include complex

// определения операций...

int main() {

complex double cval( 4.0, 1.0 );

cout cval endl;

++cva1;

cout cval endl;

}

4.7. Условное выражение

Условное выражение, или оператор выбора, предоставляет возможность более компактной записи текстов, включающих инструкцию if-else. Например, вместо:

bool is_equal;

if (!strcmp(str1,str2)) is_equal = true;

else is_equal = false;

можно употребить более компактную запись:

bool is_equa1 = !strcmp( strl, str2 ) ? true : false;

Условный оператор имеет следующий синтаксис:

expr11 ? expr2 : expr3;

Вычисляется выражение expr1. Если его значением является true, оценивается expr2, если false, то expr3. Данный фрагмент кода:

int min( int ia, int ib )

{ return ( ia ib ) ? ia : ib; }

эквивалентен

int min(int ia, int ib) {

if (ia ib)

return ia;

else

return ib;

}

Приведенная ниже программа иллюстрирует использование условного оператора:

#include iostream

int main()

{

int i = 10, j = 20, k = 30;

cout "Большим из "

i " и " j " является "

( i j ? i : j ) end1;

cout "Значение " i

( i % 2 ? " нечетно." : " четно." )

endl;

/* условный оператор может быть вложенным,

* но глубокая вложенность трудна для восприятия.

* В данном примере max получает значение

* максимальной из трех величин

*/

int max = ( (i j)

? (( i k) ? i : k)

: ( j k ) ? j : k);

cout "Большим из "

i ", " j " и " k

" является " max endl;

}

Результатом работы программы будет:

Большим из 10 и 20 является 20

Значение 10 четно.

4.8. Оператор sizeof

Оператор sizeof возвращает размер в байтах объекта или типа данных. Синтаксис его таков:

sizeof ( type name );

sizeof ( object );

sizeof object;

Результат имеет специальный тип size_t, который определен как typedef в заголовочном файле cstddef. Вот пример использования обеих форм оператора sizeof:

#include cstddef

int ia[] = { 0, 1, 2 };

// sizeof возвращает размер всего массива

size_t array_size = sizeof ia;

// sizeof возвращает размер типа int

size_t element_size = array_size / sizeof( int );

Применение sizeof к массиву дает количество байтов, занимаемых массивом, а не количество его элементов и не размер в байтах каждого из них. Так, например, в системах, где int хранится в 4 байтах, значением array_size будет 12. Применение sizeof к указателю дает размер самого указателя, а не объекта, на который он указывает:

int *pi = new int[ 3 ];

size_t pointer_size = sizeof ( pi );

Здесь значением pointer_size будет память под указатель в байтах (4 в 32-битных системах), а не массива ia.

Вот пример программы, использующей оператор sizeof:

#include string

#include iostream

#include cstddef

int main() {

size_t ia;

ia = sizeof( ia ); // правильно

ia = sizeof ia; // правильно

// ia = sizeof int; // ошибка

ia = sizeof( int ); // правильно

int *pi = new int[ 12 ];

cout "pi: " sizeof( pi )

" *pi: " sizeof( pi )

endl;

// sizeof строки не зависит от

// ее реальной длины

string stl( "foobar" );

string st2( "a mighty oak" );

string *ps = stl;

cout " st1: " sizeof( st1 )

" st2: " sizeof( st2 )

" ps: sizeof( ps )

" *ps: " sizeof( *ps )

endl;

cout "short :\t" sizeof(short) endl;

cout "shorf" :\t" sizeof(short*) endl;

cout "short :\t" sizeof(short) endl;

cout "short[3] :\t" sizeof(short[3]) endl;

}

Результатом работы программы будет:

pi: 4 *pi: 4

st1: 12 st2: 12 ps: 4 *ps:12

short : 2

short* : 4

short : 2

short[3] : 6

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

Гарантируется, что в любой реализации С++ размер типа char равен 1.

// char_size == 1

size_t char_size = sizeof( char );

Значение оператора sizeof вычисляется во время компиляции и считается константой. Оно может быть использовано везде, где требуется константное значение, в том числе в качестве размера встроенного массива. Например:

// правильно: константное выражение

int array[ sizeof( some_type_T )];

4.9. Операторы new и delete

Каждая программа во время работы получает определенное количество памяти, которую можно использовать. Такое выделение памяти под объекты во время выполнения называется динамическим, а сама память выделяется из хипа (heap). (Мы уже касались вопроса о динамическом выделении памяти в главе 1.) Напомним, что выделение памяти объекту производится с помощью оператора new, возвращающего указатель на вновь созданный объект того типа, который был ему задан. Например:

int *pi = new int;

размещает объект типа int в памяти и инициализирует указатель pi адресом этого объекта. Сам объект в таком случае не инициализируется, но это легко изменить:

int *pi = new int( 1024 );

Можно динамически выделить память под массив:

int *pia = new int[ 10 ];

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

string *ps = new string;

размещает в памяти один объект типа string, инициализирует ps его адресом и вызывает конструктор по умолчанию для вновь созданного объекта типа string. Аналогично

string *psa = new string[10];

размещает в памяти массив из десяти элементов типа string, инициализирует psa его адресом и вызывает конструктор по умолчанию для каждого элемента массива.

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

Назад Дальше