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

Язык Программирования C++. Вводный курс [rus]

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

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

132

this является указателем, адресующим объект name1 внутри тела функции операции присваивания.

ptr->size();

this всегда указывает на объект класса, через который происходит вызов функции. Если obj[ 1024 ];

то внутри size() значением this будет адрес, хранящийся в ptr. Внутри операции взятия индекса this содержит адрес obj. Разыменовывая this (использованием *this),

inline String&

String::operator=( const char *s )

{

if ( ! s ) { _size = 0;

delete [] _string; _string = 0;

}

else {

_size = str1en( s ); delete [] _string;

_string = new char[ _size + 1 ]; strcpy( _string, s );

}

мы получаем сам объект. (Указатель this детально описан в разделе 13.4.)

return *this;

}

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

inline String&

String::operator=( const String &rhs )

{

//в выражении

//namel = *pointer_to_string

//this представляет собой name1,

//rhs - *pointer_to_string.

this:

if ( this != &rhs ) {

Вот полный текст операции присваивания объекту String объекта того же типа:

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

133

inline String&

String::operator=( const String &rhs )

{

if ( this != &rhs ) { delete [] _string; _size = rhs._size;

if ( ! rhs._string ) _string = 0;

else {

_string = new char[ _size + 1 ]; strcpy( _string, rhs._string );

}

}

return *this;

}

Операция взятия индекса практически совпадает с ее реализацией для массива Array,

#include <cassert>

inline char& String::operator[] ( int elem )

{

assert( elem >= 0 && elem < _size ); return _string[ elem ];

который мы создали в разделе 2.3:

}

Операторы ввода и вывода реализуются как отдельные функции, а не члены класса. (О причинах этого мы поговорим в разделе 15.2. В разделах 20.4 и 20.5 рассказывается о перегрузке операторов ввода и вывода библиотеки iostream.) Наш оператор ввода может прочесть не более 4095 символов. setw() предопределенный манипулятор, он читает из входного потока заданное число символов минус 1, гарантируя тем самым, что мы не переполним наш внутренний буфер inBuf. (В главе 20 манипулятор setw() рассматривается детально.) Для использования манипуляторов нужно включить

#include <iomanip>

inline istream&

operator>>( istream &io, String &s )

{

// искусственное ограничение: 4096 символов const int 1imit_string_size = 4096;

char inBuf[ limit_string_size ];

//setw() входит в библиотеку iostream

//он ограничивает размер читаемого блока до 1imit_string_size-l

io >> setw( 1imit_string_size ) >> inBuf;

s = mBuf; // String::operator=( const char* ); return io;

соответствующий заголовочный файл:

}

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

134

Оператору вывода необходим доступ к внутреннему представлению строки String. Так как operator<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при

inline ostream&

operator<<( ostream& os, const String &s )

{

return os << s.c_str();

реализации операции вывода:

}

Ниже приводится пример программы, использующей класс String. Эта программа берет слова из входного потока и подсчитывает их общее число, а также количество слов "the" и "it" и регистрирует встретившиеся гласные.

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

135

#include <iostream> #inc1ude "String.h" int main() {

int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0;

// Слова "The" и "It"

// будем проверять с помощью operator==( const char* ) String but, the( "the" ), it( "it" );

// operator>>( ostream&, String& ) while ( cin >> buf ) {

++wdCnt;

// operator<<( ostream&, const String& ) cout << buf << ' ';

if ( wdCnt % 12 == 0 ) cout << endl;

// String::operator==( const String& ) and // String::operator==( const char* );

if ( buf == the | | buf == "The" ) ++theCnt;

else

if ( buf == it || buf == "It" ) ++itCnt;

// invokes String::s-ize()

for ( int ix =0; ix < buf.sizeO; ++ix )

{

// invokes String:: operator [] (int) switch( buf[ ix ] )

{

case 'a': case 'A': ++aCnt; break; case 'e': case 'E': ++eCnt; break; case 'i': case 'I': ++iCnt; break; case 'o': case '0': ++oCnt; break; case 'u': case 'U': ++uCnt; break; default: ++notVowe1; break;

}

}

}

// operator<<( ostream&, const String& ) cout << "\n\n"

<<"Слов: " << wdCnt << "\n\n"

<<"the/The: " << theCnt << '\n'

<<"it/It: " << itCnt << "\n\n"

<<"согласных: " < <notVowel << "\n\n"

<<"a: " << aCnt << '\n'

<<"e: " << eCnt << '\n'

<<"i: " << ICnt << '\n'

<<"o: " << oCnt << '\n'

<<"u: " << uCnt << endl;

}

Протестируем программу: предложим ей абзац из детского рассказа, написанного одним из авторов этой книги (мы еще встретимся с этим рассказом в главе 6). Вот результат работы программы:

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

136

Alice Emma has long flowing red hair. Her Daddy says when the wind blows through her hair, it looks almost alive, 1ike a fiery bird in flight. A beautiful fiery bird, he tells her, magical but

untamed. "Daddy, shush, there is no such thing," she tells him, at the same time wanting him to tell her more. Shyly, she asks,

"I mean, Daddy, is there?"

Слов: 65 the/The: 2 it/It: 1

согласных: 190 a: 22

e: 30 i: 24 о: 10 u: 7

Упражнение 3.26

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

Упражнение 3.27

Модифицируйте тестовую программу так, чтобы она подсчитывала и согласные b, d, f, s, t.

Упражнение 3.28

Напишите функцию-член, подсчитывающую количество вхождений символа в строку

class String {

public:

// ...

int count( char ch ) const;

// ...

String, используя следующее объявление:

};

Упражнение 3.29

Реализуйте оператор конкатенации строк (+) так, чтобы он конкатенировал две строки и

class String {

public:

// ...

String operator+( const String &rhs ) const;

// ...

возвращал результат в новом объекте String. Вот объявление функции:

};

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

137

4

4. Выражения

В главе 3 мы рассмотрели типы данных как встроенные, так и предоставленные стандартной библиотекой. Здесь мы разберем предопределенные операции, такие, как сложение, вычитание, сравнение и т.п., рассмотрим их приоритеты. Скажем, результатом выражения 3+4*5 является 23, а не 35 потому, что операция умножения (*) имеет более высокий приоритет, чем операция сложения (+). Кроме того, мы обсудим вопросы преобразований типов данных и явных, и неявных. Например, в выражении 3+0.7 целое значение 3 станет вещественным перед выполнением операции сложения.

4.1. Что такое выражение?

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

void mumble() { 3.14159; "melancholia"; upperBound;

Например:

}

Результатом вычисления выражения 3.14159 станет 3.14159 типа double, выражения "melancholia" адрес первого элемента строки типа const char*. Значение выражения upperBound это значение объекта upperBound, а его типом будет тип самого объекта.

Более общим случаем выражения является один или более операндов и некоторая

salary + raise

ivec[ size/2 ] * delta

операция, применяемая к ним: first_name + " " + 1ast_name

Операции обозначаются соответствующими знаками. В первом примере сложение применяется к salary и raise. Во втором выражении size делится на 2. Частное используется как индекс для массива ivec. Получившийся в результате операции взятия индекса элемент массива умножается на delta. В третьем примере два строковых объекта конкатенируются между собой и со строковым литералом, создавая новый строковый объект.

Операции, применяемые к одному операнду, называются унарными (например, взятие адреса (&) и разыменование (*)), а применяемые к двум операндам бинарными. Один и

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

138

тот же символ может обозначать разные операции в зависимости от того, унарна она или бинарна. Так, в выражении

*ptr

* представляет собой унарную операцию разыменования. Значением этого выражения является значение объекта, адрес которого содержится в ptr. Если же написать:

var1 * var2

то звездочка будет обозначать бинарную операцию умножения.

Результатом вычисления выражения всегда, если не оговорено противное, является r- значение. Тип результата арифметического выражения определяется типами операндов. Если операнды имеют разные типы, производится преобразование типов в соответствии с предопределенным набором правил. (Мы детально рассмотрим эти правила в разделе

4.14.)

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

иобъект, на который он указывает (если он на что-то указывает)7: ptr != 0 && *ptr != 0

Выражение состоит из трех подвыражений: проверку указателя ptr, разыменования ptr

int ival = 1024;

ипроверку результата разыменования. Если ptr определен как int *ptr = &ival;

то результатом разыменования будет 1024 и оба сравнения дадут истину. Результатом всего выражения также будет истина (оператор && обозначает логическое И).

Если посмотреть на этот пример внимательно, можно заметить, что порядок выполнения операций очень важен. Скажем, если бы операция разыменования ptr производилась до его сравнения с 0, в случае нулевого значения ptr это скорее всего вызвало бы крах программы. В случае операции И порядок действий строго определен: сначала оценивается левый операнд, и если его значение равно false, правый операнд не вычисляется вовсе. Порядок выполнения операций определяется их приоритетами, не всегда очевидными, что вызывает у начинающих программистов на С и С++ множество ошибок. Приоритеты будут приведены в разделе 4.13, а пока мы расскажем обо всех операциях, определенных в С++, начиная с наиболее привычных.

4.2. Арифметические операции

Таблица 4.1. Арифметические операции

7 Проверку на неравенство 0 можно опустить. Полностью эквивалентна приведенной и более употребима следующая запись: ptr && *ptr.

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

 

139

 

 

 

 

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

Значение

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

 

 

 

 

 

*

Умножение

expr * expr

 

/

Деление

expr / expr

 

%

Остаток от деления

expr % expr

 

+

Сложение

expr + expr

 

-

Вычитание

expr – expr

 

 

 

 

 

Деление целых чисел дает в результате целое число. Дробная часть результата, если она

int ivall = 21 / 6;

есть, отбрасывается: int iva12 = 21 / 7;

И ival1, и ival2 в итоге получат значение 3.

Операция остаток (%), называемая также делением по модулю, возвращает остаток от деления первого операнда на второй, но применяется только к операндам целого типа (char, short, int, long). Результат положителен, если оба операнда положительны. Если же один или оба операнда отрицательны, результат зависит от реализации, то есть машинно-зависим. Вот примеры правильного и неправильного использования деления по

3.14 % 3; // ошибка: операнд типа double

21 % 6; // правильно: 3

21 % 7; // правильно: 0 21 % -5; // машинно-зависимо: -1 или 1

int iva1 = 1024; double dval = 3.14159;

iva1 % 12; // правильно:

модулю:

iva1 % dval; // ошибка: операнд типа double

Иногда результат вычисления арифметического выражения может быть неправильным либо не определенным. В этих случаях говорят об арифметических исключениях (хотя они не вызывают возбуждения исключения в программе). Арифметические исключения могут иметь чисто математическую природу (скажем, деление на 0) или происходить от представления чисел в компьютере как переполнение (когда значение превышает величину, которая может быть выражена объектом данного типа). Например, тип char содержит 8 бит и способен хранить значения от 0 до 255 либо от -128 до 127 в зависимости от того, знаковый он или беззнаковый. В следующем примере попытка присвоить объекту типа char значение 256 вызывает переполнение:

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

140

#include <iostream>

 

int main() {

 

char byte_value = 32;

 

int ival = 8;

 

// переполнение памяти, отведенной под byte_value byte_value = ival * byte_value;

cout << "byte_value: " <<static_cast<int>(byte_value) << endl;

}

Для представления числа 256 необходимы 9 бит. Переменная byte_value получает некоторое неопределенное (машинно-зависимое) значение. Допустим, на нашей рабочей станции SGI мы получили 0. Первая попытка напечатать это значение с помощью:

cout << "byte_va1ue: " << byte_va1ue << endl;

привела к результату:

byte_value:

После некоторого замешательства мы поняли, что значение 0 это нулевой символ ASCII, который не имеет представления при печати. Чтобы напечатать не представление символа, а его значение, нам пришлось использовать весьма странно выглядящее выражение:

static_cast<int>(byte_value)

которое называется явным приведением типа. Оно преобразует тип объекта или выражения в другой тип, явно заданный программистом. В нашем случае мы изменили byte_value на int. Теперь программа выдает более осмысленный результат:

byte_value: 0

На самом деле нужно было изменить не значение, соответствующее byte_value, а поведение операции вывода, которая действует по-разному для разных типов. Объекты типа char представляются ASCII-символами (а не кодами), в то время как для объектов типа int мы увидим содержащиеся в них значения. (Преобразования типов рассмотрены в разделе 4.14.)

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

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

141

Стандартная библиотека С++ имеет заголовочный файл limits, содержащий различную информацию о встроенных типах данных, в том числе и диапазоны значений для каждого типа. Заголовочные файлы climits и cfloat также содержат эту информацию. (Об использовании этих заголовочных файлов для того, чтобы избежать переполнения и потери значимости, см. главы 4 и 6 [PLAUGER92]).

Арифметика вещественных чисел создает еще одну проблему, связанную с округлением. Вещественное число представляется фиксированным количеством разрядов (разным для разных типов float, double и long double), и точность значения зависит от используемого типа данных. Но даже самый точный тип long double не может устранить ошибку округления. Вещественная величина в любом случае представляется с некоторой ограниченной точностью. (См. [SHAMPINE97] о проблемах округления вещественных чисел.)

Упражнение 4.1

double dvall = 10.0, dva12 = 3.0; int ivall = 10, iva12 = 3;

dvall / dva12;

Вчем разница между приведенными выражениями с операцией деления? ivall / iva12;

Упражнение 4.2

Напишите выражение, определяющее, четным или нечетным является данное целое число.

Упражнение 4.3

Найдите заголовочные файлы limits, climits и cfloat и посмотрите, что они содержат.

4.3. Операции сравнения и логические операции

Таблица 4.2. Операции сравнения и логические операции

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

Значение

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

 

 

 

!

Логическое НЕ

!expr

<

Меньше

expr1 < expr2

<=

Меньше или равно

expr1 <= expr2

>

Больше

expr1 > expr2

>=

Больше или равно

expr1 >= expr2

==

Равно

expr1 == expr2

!=

Не равно

expr1 != expr2

&&

Логическое И

expr1 && expr2

||

Логическое ИЛИ

expr1 || expr2

Примечание. Все операции в результате дают значение типа bool

Примечание [O.A.1]: Как должны быть оформлены ссылки на книги, указанные в библиографии? Пришлите ваши пожелания.

Соседние файлы в предмете Программирование на C++