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

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

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

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

 

72

#include <vector>

 

 

#include <algorithm>

 

 

#include <iostream>

 

 

int ia[ 10 ] = {

 

 

51, 23, 7, 88, 41, 98, 12, 103, 37, 6

 

};

 

 

int main()

 

 

{

 

 

vector< int > vec( ia, ia+10 );

end_it = vec.end();

vector<int>::iterator

it = vec.begin(),

cout << "Начальный массив: ";

 

for ( ; it != end_it; ++ it ) cout << *it << ' ';

cout << "\n";

 

 

// сортировка массива

 

 

sort( vec.begin(), vec.end() );

 

cout << "упорядоченный массив: ";

 

it = vec.begin(); end_it = vec.end();

 

for ( ; it != end_it; ++ it ) cout << *it << ' ';

cout << "\n\n";

 

 

int search_value;

cout << "Введите значение для поиска: "; cin >> search_value;

// поиск элемента vector<int>::iterator found;

found = find( vec.begin(), vec.end(), search_value );

if ( found != vec.end() )

cout << "значение найдено!\n\n"; else cout << "значение найдено!\n\n";

// инвертирование массива reverse( vec.begin(), vec.end() );

cout << "инвертированный массив: "; it = vec.begin(); end_it = vec.end();

for ( ; it != end_it; ++ it ) cout << *it << ' '; cout << endl;

}

Стандартная библиотека С++ поддерживает и ассоциативные массивы. Ассоциативный массив это массив, элементы которого можно индексировать не только целыми числами, но и значениями любого типа. В терминологии стандартной библиотеки ассоциативный массив называется отображением (map). Например, телефонный справочник может быть представлен в виде ассоциативного массива, где индексами

#include <map>

служат фамилии абонентов, а значениями элементов телефонные номера:

#include <string>

#include "TelephoneNumber.h"

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

73

map<string, telephoneNum> telephone_directory;

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

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

Упражнение 2.22

string pals[] = {

"pooh", "tiger", "piglet", "eeyore", "kanga" };

(a)vector<string> svec1(pals,pals+5);

(b)vector<int> ivec1(10);

(c)vector<int> ivec2(10,10);

(d)vector<string> svec2(svec1);

(e)vector<double> dvec;

Поясните результаты каждого из следующих определений вектора:

Упражнение 2.23

Напишите две реализации функции min(), объявление которой приведено ниже. Функция должна возвращать минимальный элемент массива. Используйте цикл for и

перебор элементов с помощью

индекса

template <class elemType>

итератора

elemType min (const vector<elemType> &vec);

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

74

Часть II

Основы языка

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

00011011011100010110010000111011 ...

Очень трудно придать такой последовательности смысл, но иногда нам приходится манипулировать и подобными неструктурированными данными (обычно нужда в этом возникает при программировании драйверов аппаратных устройств). С++ предоставляет набор операций для работы с битовыми данными. (Мы поговорим об этом в главе 4.)

Как правило, на последовательность битов накладывают какую-либо структуру, группируя биты в байты и слова. Байт содержит 8 бит, а слово – 4 байта, или 32 бита. Однако определение слова может быть разным в разных операционных системах. Сейчас начинается переход к 64-битным системам, а еще недавно были распространены системы с 16-битными словами. Хотя в подавляющем большинстве систем размер байта одинаков, мы все равно будем называть эти величины машинно-зависимыми.

Так выглядит наша последовательность битов, организованная в байты.

Рис 1.

Адресуемая машинная память

Теперь мы можем говорить, например, о байте с адресом 1040 или о слове с адресом 1024 и утверждать, что байт с адресом 1032 не равен байту с адресом 1040.

Однако мы не знаем, что же представляет собой какой-либо байт, какое-либо машинное слово. Как понять смысл тех или иных 8 бит? Для того чтобы однозначно интерпретировать значение этого байта (или слова, или другого набора битов), мы должны знать тип данных, представляемых данным байтом.

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

Глава 3 содержит обзор встроенных и расширенных типов, а также механизмов, с помощью которых можно создавать новые типы. В основном это, конечно, механизм классов, представленный в разделе 2.3. В главе 4 рассматриваются выражения, встроенные операции и их приоритеты, преобразования типов. В главе 5 рассказывается об инструкциях языка. И наконец глава 6 представляет стандартную библиотеку С++ и контейнерные типы вектор и ассоциативный массив.

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

75

 

3. Типы данных С++

В этой главе приводится обзор встроенных, или элементарных, типов данных языка С++. Она начинается с определения литералов, таких, как 3.14159 или pi, а затем вводится понятие переменной, или объекта, который должен принадлежать к одному из типов данных. Оставшаяся часть главы посвящена подробному описанию каждого встроенного типа. Кроме того, приводятся производные типы данных для строк и массивов, предоставляемые стандартной библиотекой С++. Хотя эти типы не являются элементарными, они очень важны для написания настоящих программ на С++, и нам хочется познакомить с ними читателя как можно раньше. Мы будем называть такие типы данных расширением базовых типов С++.

3.1. Литералы

В С++ имеется набор встроенных типов данных для представления целых и вещественных чисел, символов, а также тип данных символьный массив”, который служит для хранения символьных строк. Тип char служит для хранения отдельных символов и небольших целых чисел. Он занимает один машинный байт. Типы short, int и long предназначены для представления целых чисел. Эти типы различаются только диапазоном значений, которые могут принимать числа, а конкретные размеры перечисленных типов зависят от реализации. Обычно short занимает половину машинного слова, int одно слово, long одно или два слова. В 32-битных системах int и long, как правило, одного размера.

Типы float, double и long double предназначены для чисел с плавающей точкой и различаются точностью представления (количеством значащих разрядов) и диапазоном. Обычно float (одинарная точность) занимает одно машинное слово, double (двойная точность) – два, а long double (расширенная точность) – три.

char, short, int и long вместе составляют целые типы, которые, в свою очередь, могут быть знаковыми (signed) и беззнаковыми (unsigned). В знаковых типах самый левый бит служит для хранения знака (0 плюс, 1 минус), а оставшиеся биты содержат значение. В беззнаковых типах все биты используются для значения. 8-битовый тип signed char может представлять значения от -128 до 127, а unsigned char от 0 до

255.

Когда в программе встречается некоторое число, например 1, то это число называется литералом, или литеральной константой. Константой, потому что мы не можем изменить его значение, и литералом, потому что его значение фигурирует в тексте программы. Литерал является неадресуемой величиной: хотя реально он, конечно, хранится в памяти машины, нет никакого способа узнать его адрес. Каждый литерал имеет определенный тип. Так, 0 имеет тип int, 3.14159 тип double.

Литералы целых типов можно записать в десятичном, восьмеричном и шестнадцатеричном виде. Вот как выглядит число 20, представленное десятичным, восьмеричным и шестнадцатеричным литералами:

20 // десятичный

024 // восьмеричный

0х14 // шестнадцатеричный

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

76

Если литерал начинается с 0, он трактуется как восьмеричный, если с или , то как шестнадцатеричный. Привычная запись рассматривается как десятичное число.

По умолчанию все целые литералы имеют тип signed int. Можно явно определить целый литерал как имеющий тип long, приписав в конце числа букву L (используется как прописная L, так и строчная l, однако для удобства чтения не следует употреблять строчную: ее легко перепутать с 1). Буква U (или u) в конце определяет литерал как unsigned int, а две буквы UL или LU как тип unsigned long. Например:

128u 1024UL 1L 8Lu

Литералы, представляющие действительные числа, могут быть записаны как с десятичной точкой, так и в научной (экспоненциальной) нотации. По умолчанию они имеют тип double. Для явного указания типа float нужно использовать суффикс F или f, а для long double - L или l, но только в случае записи с десятичной точкой. Например:

3.14159F

0/1f

12.345L 0.0

3el

1.0E-3E 2.

1.0L

Слова true и false являются литералами типа bool.

Представимые литеральные символьные константы записываются как символы в одинарных кавычках. Например:

'a' '2' ',' ' ' (пробел)

Специальные символы (табуляция, возврат каретки) записываются как escape- последовательности . Определены следующие такие последовательности (они начинаются с символа обратной косой черты):

новая строка \n горизонтальная табуляция \t

забой

\b

вертикальная табуляция

\v

возврат каретки

\r

прогон листа

\f

звонок

\a

обратная косая черта

\\

вопрос

\?

одиночная кавычка

\'

двойная кавычка

\"

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

\7

(звонок)

\14 (новая строка)

\0

(null)

\062 ('2')

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

77

Символьный литерал может иметь префикс L (например, L'a'), что означает специальный тип wchar_t двухбайтовый символьный тип, который применяется для хранения символов национальных алфавитов, если они не могут быть представлены обычным типом char, как, например, китайские или японские буквы.

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

"" (пустая строка) "a"

"\nCC\toptions\tfile.[cC]\n" "a multi-line \

string literal signals its \ continuation with a backslash"

Фактически строковый литерал представляет собой массив символьных констант, где по соглашению языков С и С++ последним элементом всегда является специальный символ с кодом 0 (\0).

Литерал 'A' задает единственный символ А, а строковый литерал "А" массив из двух элементов: 'А' и \0 (пустого символа).

Раз существует тип wchar_t, существуют и литералы этого типа, обозначаемые, как и в случае с отдельными символами, префиксом L:

L"a wide string literal"

Строковый литерал типа wchar_t это массив символов того же типа, завершенный нулем.

Если в тесте программы идут подряд два или несколько строковых литералов (типа char или wchar_t), компилятор соединяет их в одну строку. Например, следующий текст

"two" "some"

породит массив из восьми символов twosome и завершающий нулевой символ.

// this is not a good idea

Результат конкатенации строк разного типа не определен. Если написать:

"two" L"some"

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

Упражнение 3.1

Объясните разницу в определениях следующих литералов:

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

78

(a)'a', L'a', "a", L"a"

(b)10, 10u, 10L, 10uL, 012, 0*C

(c)3.14, 3.14f, 3.14L

Упражнение 3.2

(a)"Who goes with F\144rgus?\014"

(b)3.14e1L

(c)"two" L"some"

(d)1024f

(e)3.14UL

(f)"multiple line

Какие ошибки допущены в приведенных ниже примерах? comment"

3.2. Переменные

#include <iostream>

Представим себе, что мы решаем задачу возведения 2 в степень 10. Пишем:

int main() {

// a first solution

cout << "2 raised to the power of 10: ";

cout << 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2 * 2; cout << endl;

return 0;

}

Задача решена, хотя нам и пришлось неоднократно проверять, действительно ли 10 раз повторяется литерал 2. Мы не ошиблись в написании этой длинной последовательности двоек, и программа выдала правильный результат – 1024.

Но теперь нас попросили возвести 2 в 17 степень, а потом в 23. Чрезвычайно неудобно каждый раз модифицировать текст программы! И, что еще хуже, очень просто ошибиться, написав лишнюю двойку или пропустив ее... А что делать, если нужно напечатать таблицу степеней двойки от 0 до 15? 16 раз повторить две строки, имеющие

cout << "2 в степени X\t";

общий вид:

cout << 2 * ... * 2;

где Х последовательно увеличивается на 1, а вместо отточия подставляется нужное число литералов?

Да, мы справились с задачей. Заказчик вряд ли будет вникать в детали, удовлетворившись полученным результатом. В реальной жизни такой подход достаточно часто срабатывает, более того, бывает оправдан: задача решена далеко не самым изящным способом, зато в желаемый срок. Искать более красивый и грамотный вариант может оказаться непрактичной тратой времени.

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

79

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

Привлечение более сложных механизмов для той же задачи, как правило, значительно увеличивает время подготовительного этапа. Кроме того, чем более сложные механизмы применяются, тем больше вероятность ошибок. Но даже несмотря на неизбежные ошибки и неверные ходы, применение высоких технологийможет принести выигрыш в скорости разработки, не говоря уже о том, что эти технологии значительно расширяют наши возможности. И что интересно! – сам процесс решения может стать привлекательным.

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

#include <iostream>

int main()

{

// objects of type int int value = 2;

int pow = 10;

cout << value << " в степени " << pow << ": \t";

int res = 1;

//оператор цикла:

//повторить вычисление res

//до тех пор пока cnt не станет больше pow for ( int cnt=1; cnt <= pow; ++cnt )

res = res * value;

cout << res << endl;

последовательности литералов применим оператор цикла. Вот как это будет выглядеть:

}

value, pow, res и cnt это переменные, которые позволяют хранить, модифицировать и извлекать значения. Оператор цикла for повторяет строку вычисления результата pow раз.

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

int pow( int val, int exp )

конкретные значения задать параметрами.

{

for ( int res = 1; exp > 0; --exp ) res = res * val;

return res;

}

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

80

Теперь получить любую степень нужного числа не составит никакого труда. Вот как

#include <iostream> extern int pow(int,int);

реализуется последняя наша задача напечатать таблицу степеней двойки от 0 до 15:

int main()

{

int val = 2; int exp = 15;

cout << "Степени 2\n";

for ( int cnt=0; cnt <= exp; ++cnt ) cout << cnt << ": "

<< pow( val, cnt ) << endl;

return 0;

}

Конечно, наша функция pow() все еще недостаточно обобщена и недостаточно надежна. Она не может оперировать вещественными числами, неправильно возводит числа в отрицательную степень всегда возвращает 1. Результат возведения большого числа в большую степень может не поместиться в переменную типа int, и тогда будет возвращено некоторое случайное неправильное значение. Видите, как непросто, оказывается, писать функции, рассчитанные на широкое применение? Гораздо сложнее, чем реализовать конкретный алгоритм, направленный на решение конкретной задачи.

3.2.1. Что такое переменная

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

int student_count; double salary;

bool on_loan; strins street_address;

применимых к этой переменной. Вот пример определения пяти объектов разных типов: char delimiter;

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

собственно значение, или r-значение (от read value – значение для чтения), которое хранится в этой области памяти и присуще как переменной, так и литералу;

значение адреса области памяти, ассоциированной с переменной, или l- значение (от location value – значение местоположения) – место, где хранится r- значение; присуще только объекту.

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

81

В выражении

ch = ch - '0';

переменная ch находится и слева и справа от символа операции присваивания. Справа расположено значение для чтения (ch и символьный литерал '0'): ассоциированные с переменной данные считываются из соответствующей области памяти. Слева значение местоположения: в область памяти, соотнесенную с переменной ch, помещается результат вычитания. В общем случае левый операнд операции присваивания должен быть l-

//ошибки компиляции: значения слева не являются l-значениями

//ошибка: литерал - не l-значение

0 = 1;

// ошибка: арифметическое выражение - не l-значение

значением. Мы не можем написать следующие выражения: salary + salary * 0.10 = new_salary;

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

// файл module0.C

должна быть использована в другом, появляются проблемы. Например:

//определяет объект fileName string fileName;

//... присвоить fileName значение

//файл module1.C

//использует объект fileName

//увы, не компилируется:

//fileName не определен в module1.C ifstream input_file( fileName );

С++ требует, чтобы объект был известен до первого обращения к нему. Это вызвано

необходимостью гарантировать правильность использования объекта в соответствии с его типом. В нашем примере модуль module1.C вызовет ошибку компиляции, поскольку переменная fileName не определена в нем. Чтобы избежать этой ошибки, мы должны сообщить компилятору об уже определенной переменной fileName. Это делается с

// файл module1.C

помощью инструкции объявления переменной:

//использует объект fileName

//fileName объявляется, то есть программа получает

//информацию об этом объекте без вторичного его определения extern string fileName;