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

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
235
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

152

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;

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

С++ для начинающих

153

#include <complex> inline complex<double>&

operator+=( complex<double> &cval, double dval )

{

return cval += complex<double>( dval );

}

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

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

#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, то и операция инкремента выглядела бы вполне законно. Реализуйте эти операции для типа complex<double> и выполните следующую программу:

С++ для начинающих

154

#include <iostream> #include <complex>

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

int main() {

complex< double > cval( 4.0, 1.0 );

cout << cval << endl; ++cva1;

cout << cval << endl;

}

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

Условное выражение, или оператор выбора, предоставляет возможность более

bool is_equal;

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

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

else

is_equal = false;

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

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

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

expr11 ? expr2 : expr3;

Вычисляется выражение expr1. Если его значением является true, оценивается expr2,

int min( int ia, int ib )

если false, то expr3. Данный фрагмент кода:

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

int min(int ia, int ib) { if (ia < ib)

return ia; else

return ib;

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

}

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

С++ для начинающих

155

#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:

С++ для начинающих

156

#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. Применение

int *pi = new int[ 3 ];

sizeof к указателю дает размер самого указателя, а не объекта, на который он указывает: size_t pointer_size = sizeof ( pi );

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

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

С++ для начинающих

157

#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_size == 1

Гарантируется, что в любой реализации С++ размер типа char равен 1. size_t char_size = sizeof( char );

С++ для начинающих

158

Значение оператора 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, не имеют собственного имени. Вместо этого возвращается указатель на безымянный объект, и все действия с этим объектом производятся посредством косвенной адресации.

После использования объекта, созданного таким образом, мы должны явно освободить память, применив оператор delete к указателю на этот объект. (Попытка применить

С++ для начинающих

159

оператор delete к указателю, не содержащему адрес объекта, полученного описанным способом, вызовет ошибку времени выполнения.) Например:

delete pi;

освобождает память, на которую указывает объект типа int, на который указывает pi.

Аналогично

delete ps;

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

delete [] pia;

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

(Об операциях new и delete мы еще поговорим в главе 8.)

Упражнение 4.11

Какие из следующих выражений ошибочны?

(a)vector<string> svec( 10 );

(b)vector<string> *pvecl = new vector<string>(10);

(c)vector<string> **pvec2 = new vector<string>[10];

(d)vector<string> *pvl = &svec;

(e)vector<string> *pv2 = pvecl;

(f)delete svec;

(g)delete pvecl;

(h)delete [] pvec2;

(i)delete pvl;

(j)delete pv2;

4.10. Оператор запятая

Одно выражение может состоять из набора подвыражений, разделенных запятыми; такие подвыражения вычисляются слева направо. Конечным результатом будет результат самого правого из них. В следующем примере каждое из подвыражений условного оператора представляет собой список. Результатом первого подвыражения условного оператора является ix, второго 0.

С++ для начинающих

160

int main()

{

//примеры оператора "запятая"

//переменные ia, sz и index определены в другом месте ...

int ival = (ia != 0)

? ix=get_va1ue(), ia[index]=ix : ia=new int[sz], ia[index]=0;

// ...

}

4.11. Побитовые операторы

Таблица 4.3. Побитовые операторы

Символ операции

Значение

 

 

Использование

 

 

 

 

~

Побитовое НЕ

 

~expr

<<

Сдвиг влево

 

 

expr1 << expr2

>>

Сдвиг вправо

 

expr1 >> expr2

&

Побитовое И

 

 

expr1 & expr2

^

Побитовое

 

 

expr1 ^ expr2

 

ИСКЛЮЧАЮЩЕЕ ИЛИ

 

 

|

Побитовое ИЛИ

 

expr1 | expr2

&=

Побитовое

И

с

expr1 &= expr2

 

присваиванием

 

 

^=

Побитовое

 

 

expr1 ^= expr2

 

ИСКЛЮЧАЮЩЕЕ ИЛИ с

 

 

присваиванием

 

 

|=

Побитовое

ИЛИ

с

expr1 |= expr2

 

присваиванием

 

 

<<=

Сдвиг

влево

с

expr1 <<= expr2

 

присваиванием

 

 

>>=

Сдвиг

вправо

с

expr1 >>= expr2

 

присваиванием

 

 

Побитовые операции рассматривают операнды как упорядоченные наборы битов, каждый бит может иметь одно из двух значений 0 или 1. Такие операции позволяют программисту манипулировать значениями отдельных битов. Объект, содержащий набор битов, иногда называют битовым вектором. Он позволяет компактно хранить набор флагов переменных, принимающих значение да” “нет”. Например, компиляторы зачастую помещают в битовые векторы спецификаторы типов, такие, как const и volatile. Библиотека iostream использует эти векторы для хранения состояния формата вывода.

Как мы видели, в С++ существуют два способа работы со строками: использование C- строк и объектов типа string стандартной библиотеки и два подхода к массивам: массивы встроенного типа и объект vector. При работе с битовыми векторами также

С++ для начинающих

161

можно применять подход, заимствованный из С, – использовать для представления такого вектора объект встроенного целого типа, обычно unsigned int, или класс bitset стандартной библиотеки С++. Этот класс инкапсулирует семантику вектора, предоставляя операции для манипулирования отдельными битами. Кроме того, он позволяет ответить на вопросы типа: есть ли взведенныебиты (со значением 1) в векторе? Сколько битов взведено”?

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

разделе мы рассмотрим применение встроенных типов для представления битовых векторов, а в следующем класс bitset.

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

Побитовое НЕ (~) меняет значение каждого бита операнда. Бит, установленный в 1, меняет значение на 0 и наоборот.

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

Побитовое И (&) применяет операцию И ко всем битам своих операндов. Каждый бит левого операнда сравнивается с битом правого, находящимся в той же позиции. Если оба бита равны 1, то бит в данной позиции получает значение 1, в любом другом случае 0. (Побитовое И (&) не надо путать с логическим И (&&),но, к сожалению, каждый программист хоть раз в жизни совершал подобную ошибку.)

Побитовое ИСКЛЮЧАЮЩЕЕ ИЛИ (^) сравнивает биты операндов. Соответствующий бит результата равен 1, если операнды различны (один равен 0, а другой 1). Если же оба операнда равны, результата равен 0.

Побитовое ИЛИ (|) применяет операцию логического сложения к каждому биту операндов. Бит в позиции результата получает значение 1, если хотя бы один из соответствующих битов операндов равен 1, и 0, если биты обоих операндов равны 0. (Побитовое ИЛИ не нужно смешивать с логическим ИЛИ.)

Рассмотрим простой пример. Пусть у нас есть класс из 30 студентов. Каждую неделю преподаватель проводит зачет, результат которого сдал/не сдал. Итоги можно представить в виде битового вектора. (Заметим, что нумерация битов начинается с нуля, первый бит на самом деле является вторым по счету. Однако для удобства мы не будем использовать нулевой бит; таким образом, студенту номер 1 соответствует бит номер 1. В конце концов, наш преподаватель не специалист в области программирования.)

unsigned int quiz1 = 0;

Нам нужно иметь возможность менять значение каждого бита и проверять это значение. Предположим, студент 27 сдал зачет. Бит 27 необходимо выставить в 1, не меняя значения других битов. Это можно сделать за два шага. Сначала нужно начать с числа, содержащего 1 в 27-м бите и 0 в остальных. Для этого используем операцию сдвига: