Главная страница

Язык программирования C++. Вводный курс. С для начинающих


Скачать 5.41 Mb.
НазваниеС для начинающих
Дата24.08.2022
Размер5.41 Mb.
Формат файлаpdf
Имя файлаЯзык программирования C++. Вводный курс.pdf
ТипДокументы
#652350
страница15 из 93
1   ...   11   12   13   14   15   16   17   18   ...   93
146
Неверна и инструкция pia + 2=1; // ошибка
Хотя pia+2 дает адрес ia[2], присвоить ему значение нельзя. Если мы хотим изменить элемент ia[2], то нужно воспользоваться операцией разыменования. Корректной будет следующая запись:
*(pia + 2) = 1; // правильно
Операция присваивания имеет результат – значение, которое было присвоено самому левому операнду. Например, результатом такой операции ival = 0; является 0, а результат ival = 3.14159; равен 3. Тип результата – int в обоих случаях. Это свойство операции присваивания можно использовать в подвыражениях. Например, следующий цикл
} может быть переписан так:
}
Заметим, что вокруг выражения присваивания необходимы скобки, поскольку приоритет этой операции ниже, чем операции сравнения. Без скобок первым выполняется сравнение: next_char() != '\n' extern char next_char(); int main()
{ char ch = next_char(); while ( ch != '\n' ) {
// сделать что-то ... ch = next_char();
}
// ... extern char next_char(); int main()
{ char ch; while (( ch = next_char() ) != '\n' ) {
// сделать что-то ...
}
// ...

С++ для начинающих
147
и его результат, true или false, присваивается переменной ch. (Приоритеты операций будут рассмотрены в разделе 4.13.)
Аналогично несколько операций присваивания могут быть объединены, если это позволяют типы операндов. Например:
// ...
}
Обеим переменным ival и jval присваивается значение 0. Следующий пример неправилен, потому что типы pval и ival различны, и неявное преобразование типов невозможно. Отметим, что 0 является допустимым значением для обеих переменных:
// ...
}
Верен или нет приведенный ниже пример, мы сказать не можем, , поскольку определение jval в нем отсутствует:
}
Это правильно только в том случае, если переменная jval определена в программе ранее и имеет тип, приводимый к int. Обратите внимание: в этом случае мы присваиваем 0 значение jval и инициализируем ival. Для того чтобы инициализировать нулем обе переменные, мы должны написать:
}
В практике программирования часты случаи, когда к объекту применяется некоторая операция, а результат этой операции присваивается тому же объекту. Например: int main ()
{ int ival, jval; ival = jval = 0; // правильно: присваивание 0 обеим переменным int main ()
{ int ival; int *pval; ival = pval = 0; // ошибка: разные типы int main()
{
// ... int ival = jval = 0; // верно или нет?
// ... int main()
{
// правильно: определение и инициализация int ival = 0, jval = 0;
// ...

С++ для начинающих
148
}
Для более компактной записи С и С++ предоставляют составные операции присваивания.
С использованием такого оператора данный пример можно переписать следующим образом:
}
Общий синтаксис составного оператора присваивания таков: a op= b; где op= является одним из десяти операторов:
+=
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
Запись a op= b в точности эквивалентна записи a = a op b.
Упражнение 4.6
Найдите ошибку в данном примере. Исправьте запись.
}
Упражнение 4.7
Следующие выражения синтаксически правильны, однако скорее всего работают не так, как предполагал программист. Почему? Как их изменить? int arraySum( int ia[], int sz )
{ int sum = 0; for ( int i = 0; i < sz; ++i ) sum = sum + ia[ i ]; return sum; int arraySum( int ia[], int sz )
{ int sum = 0; for ( int i =0; i < sz; ++i )
// эквивалентно: sum = sum + ia[ i ]; sum += ia[ i ]; return sum; int main() { float fval; int ival; int *pi; fval = ival = pi = 0;

С++ для начинающих
149
(c) ival += ival + 1;
4.5.
Операции инкремента и декремента
Операции инкремента (++) и декремента (--) дают возможность компактной и удобной записи для изменения значения переменной на единицу. Чаще всего они используются при работе с массивами и коллекциями – для изменения величины индекса, указателя или итератора:
}
Выражение ix_vec++ является постфиксной формой оператора инкремента. Значение переменной ix_vec увеличивается после того, как ее текущее значение употреблено в качестве индекса.
Например, на первой итерации цикла значение ix_vec равно 0. Именно это значение применяется как индекс массива ivec, после чего ix_vec увеличивается и становится равным 1, однако новое значение используется только на следующей итерации.
Постфиксная форма операции декремента работает точно так же: текущее значение ix_ia берется в качестве индекса для ia, затем ix_ia уменьшается на 1.
Существует и префиксная форма этих операторов. При использовании такой формы текущее значение сначала уменьшается или увеличивается, а затем используется новое значение. Если мы пишем:
(a) if ( ptr = retrieve_pointer() != 0 )
(b) if ( ival = 1024 )
#include
#include int main()
{ int ia[10] = {0,1,2,3,4,5,6,7,8,9}; vector ivec( 10 ); int ix_vec = 0, ix_ia = 9; while ( ix_vec < 10 ) ivec[ ix_vec++ ] = ia[ ix_ia-- ]; int *pia = &ia[9]; vector::iterator iter = ivec.begin(); while ( iter != ivec.end() ) assert( *iter++ == *pia-- );
// неверно: ошибки с границами индексов в
// обоих случаях int ix_vec = 0, ix_ia = 9; while ( ix_vec < 10 )

С++ для начинающих
150
ivec[ ++ix_vec ] = ia[ --ix_ia ]; значение ix_vec увеличивается на единицу и становится равным 1 до первого использования в качестве индекса. Аналогично ix_ia получает значение 8 при первом использовании. Для того чтобы наша программа работала правильно, мы должны скорректировать начальные значения переменных ix_ivec и ix_ia: ivec[ ++ix_vec ] = ia[ --ix_ia ];
В качестве последнего примера рассмотрим понятие стека. Это фундаментальная абстракция компьютерного мира, позволяющая помещать и извлекать элементы в последовательности LIFO (last in, fist out – последним вошел, первым вышел). Стек реализует две основные операции – поместить (push) и извлечь (pop).
Текущий свободный элемент называют вершиной стека. Операция push присваивает этому элементу новое значение , после чего вершина смещается вверх (становится на 1 больше). Пусть наш стек использует для хранения элементов вектор. Какую из форм операции увеличения следует применить? Сначала мы используем текущее значение, потом увеличиваем его. Это постфиксная форма: stack[ top++ ] = value;
Что делает операция pop? Уменьшает значение вершины (текущая вершина показывает на пустой элемент), затем извлекает значение. Это префиксная форма операции уменьшения: int value = stack[ --top ];
(Реализация класса stack приведена в конце этой главы. Стандартный класс stack рассматривается в разделе 6.16.)
Упражнение 4.8
Как вы думаете, почему язык программирования получил название С++, а не ++С?
4.6.
Операции с комплексными числами
Класс комплексных чисел стандартной библиотеки С++ представляет собой хороший пример использования объектной модели. Благодаря перегруженным арифметическим операциям объекты этого класса используются так, как будто они принадлежат одному из встроенных типов данных. Более того, в подобных операциях могут одновременно принимать участие и переменные встроенного арифметического типа, и комплексные числа. (Отметим, что здесь мы не рассматриваем общие вопросы математики комплексных чисел. См. [PERSON68] или любую книгу по математике.) Например, можно написать:
// правильно int ix_vec = -1, ix_ia = 8; while ( ix_vec < 10 )

С++ для начинающих
151
complex< double > с = a * b + a / b;
Комплексные и арифметические типы разрешается смешивать в одном выражении: complex< double > complex_obj = a + 3.14159;
Аналогично комплексные числа инициализируются арифметическим типом, и им может быть присвоено такое значение: complex_obj = dval;
Или complex_obj = ival;
Однако обратное неверно. Например, следующее выражение вызовет ошибку компиляции: double dval = complex_obj;
Нужно явно указать, какую часть комплексного числа – вещественную или мнимую – мы хотим присвоить обычному числу. Класс комплексных чисел имеет две функции, возвращающих соответственно вещественную и мнимую части. Мы можем обращаться к ним, используя синтаксис доступа к членам класса: double im = complex_obj.imag(); или эквивалентный синтаксис вызова функции:
#inc1ude comp1ex< double > a; comp1ex< double > b;
// ... double dval = 3.14159; int ival = 3;
// ошибка: нет неявного преобразования
// в арифметический тип double re = complex_obj.real(); double re = real(complex_obj);

С++ для начинающих
152
double im = imag(complex_obj);
Класс комплексных чисел поддерживает четыре составных оператора присваивания: +=,
-=
, *= и /=. Таким образом, complex_obj += second_complex_obj;
Поддерживается и ввод/вывод комплексных чисел. Оператор вывода печатает вещественную и мнимую части через запятую, в круглых скобках. Например, результат выполнения операторов вывода cout << complexO << " " << complex1 << endl; выглядит так:
( 3.14159, -2.171 ) ( 3.14159, 0.0 )
Оператор ввода понимает любой из следующих форматов:
3.14159 ( 3.14159 ) ( 3.14, -1.0 )
Кроме этих операций, класс комплексных чисел имеет следующие функции-члены: sqrt()
, abs(), polar(), sin(), cos(), tan(), exp(), log(), log10() и pow().
Упражнение 4.9
Реализация стандартной библиотеки С++, доступная нам в момент написания книги, не поддерживает составных операций присваивания, если правый операнд не является комплексным числом. Например, подобная запись недопустима: complex_obj += 1;
(Хотя согласно стандарту С++ такое выражение должно быть корректно, производители часто не успевают за стандартом.) Мы можем определить свой собственный оператор для реализации такой операции. Вот вариант функции, реализующий оператор сложения для complex
: complex< double > complex0( 3.14159, -2.171 ); comp1ex< double > complex1( complexO.real() );
// допустимые форматы для ввода комплексного числа
// 3.14159 ==> comp1ex( 3.14159 );
// ( 3.14159 ) ==> comp1ex( 3.14159 );
// ( 3.14, -1.0 ) ==> comp1ex( 3.14, -1.0 );
// может быть считано как
// cin >> a >> b >> с
// где a, b, с - комплексные числа

С++ для начинающих
153
}
(Это пример перегрузки оператора для определенного типа данных, детально рассмотренной в главе 15.)
Используя этот пример, реализуйте три других составных оператора присваивания для типа complex. Добавьте свою реализацию к программе, приведенной ниже, и запустите ее для проверки.
}
Упражнение 4.10
Стандарт С++ не специфицирует реализацию операций инкремента и декремента для комплексного числа. Однако их семантика вполне понятна: если уж мы можем написать: cval += 1; что означает увеличение на 1 вещественной части cval, то и операция инкремента выглядела бы вполне законно. Реализуйте эти операции для типа complex и выполните следующую программу:
#include inline complex& operator+=( complex &cval, double dval )
{ return cval += complex( dval );
#include
#include
// определения операций... 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;

С++ для начинающих
154
}
4.7.
Условное выражение
Условное выражение, или оператор выбора, предоставляет возможность более компактной записи текстов, включающих инструкцию if-else. Например, вместо: else is_equal = false; можно употребить более компактную запись: bool is_equa1 = !strcmp( strl, str2 ) ? true : false;
Условный оператор имеет следующий синтаксис: expr11 ? expr2 : expr3;
Вычисляется выражение expr1. Если его значением является true, оценивается expr2, если false, то expr3. Данный фрагмент кода:
{ return ( ia < ib ) ? ia : ib; } эквивалентен
}
Приведенная ниже программа иллюстрирует использование условного оператора:
#include
#include
// определения операций... int main() { complex< double > cval( 4.0, 1.0 ); cout << cval << endl;
++cva1; cout << cval << endl; bool is_equal; if (!strcmp(str1,str2)) is_equal = true; int min( int ia, int ib ) int min(int ia, int ib) { if (ia < ib) return ia; else return ib;

С++ для начинающих
155
}
Результатом работы программы будет:
Большим из 10 и 20 является 20
Значение 10 четно.
4.8.
Оператор sizeof
Оператор sizeof возвращает размер в байтах объекта или типа данных. Синтаксис его таков: sizeof object;
Результат имеет специальный тип size_t, который определен как typedef в заголовочном файле cstddef. Вот пример использования обеих форм оператора sizeof:
#include 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; sizeof ( type name ); sizeof ( object );

С++ для начинающих
156
size_t element_size = array_size / sizeof( int );
Применение sizeof к массиву дает количество байтов, занимаемых массивом, а не количество его элементов и не размер в байтах каждого из них. Так, например, в системах, где int хранится в 4 байтах, значением array_size будет 12. Применение sizeof к указателю дает размер самого указателя, а не объекта, на который он указывает: size_t pointer_size = sizeof ( pi );
Здесь значением pointer_size будет память под указатель в байтах (4 в 32-битных системах), а не массива ia.
Вот пример программы, использующей оператор sizeof:
#include int ia[] = { 0, 1, 2 };
// sizeof возвращает размер всего массива size_t array_size = sizeof ia;
// sizeof возвращает размер типа int int *pi = new int[ 3 ];

С++ для начинающих
157
}
Результатом работы программы будет: 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. size_t char_size = sizeof( char );
#include
#include
#include 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;
// char_size == 1

С++ для начинающих
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 svec( 10 );
(b) vector *pvecl = new vector(10);
(c) vector **pvec2 = new vector[10];
(d) vector *pvl = &svec;
(e) vector *pv2 = pvecl;
(f) delete svec;
(g) delete pvecl;
(h) delete [] pvec2;
(i) delete pvl;
(j) delete pv2;
4.10.
Оператор “запятая”
Одно выражение может состоять из набора подвыражений, разделенных запятыми; такие подвыражения вычисляются слева направо. Конечным результатом будет результат самого правого из них. В следующем примере каждое из подвыражений условного оператора представляет собой список. Результатом первого подвыражения условного оператора является ix, второго – 0.

С++ для начинающих
1   ...   11   12   13   14   15   16   17   18   ...   93


написать администратору сайта