С.Б. Липпман, Ж. Лажойе - Язык программирования С++ Вводный курс (1114944), страница 23
Текст из файла (страница 23)
В случае разного размераконтейнеров “хвост” более длинного в расчет не принимается. Понятно, что, если всесравниваемые элементы равны, функция возвращает true, если отличается хотя быодин – false. Используйте итератор для перебора элементов. Напишите функциюmain(), обращающуюся к is_equal().3.11. Класс complexКласс комплексных чисел complex – еще один класс из стандартной библиотеки. Какобычно, для его использования нужно включить заголовочный файл:122С++ для начинающих#include <comp1ex>Комплексное число состоит из двух частей – вещественной и мнимой.
Мнимая частьпредставляет собой квадратный корень из отрицательного числа. Комплексное числопринято записывать в виде2 + 3iгде 2 – действительная часть, а 3i – мнимая. Вот примеры определений объектов типа// чисто мнимое число: 0 + 7-icomp1ex< double > purei( 0, 7 );// мнимая часть равна 0: 3 + Oicomp1ex< float > rea1_num( 3 );// и вещественная, и мнимая часть равны 0: 0 + 0-icomp1ex< long double > zero;// инициализация одного комплексного числа другимcomplex:comp1ex< double > purei2( purei );Поскольку complex, как и vector, является шаблоном, мы можем конкретизировать еготипами float, double и long double, как в приведенных примерах.
Можно такжеcomplex< double > conjugate[ 2 ] = {complex< double >( 2, 3 ),complex< double >( 2, -3 )определить массив элементов типа complex:};complex< double > *ptr = &conjugate[0];Вот как определяются указатель и ссылка на комплексное число:complex< double > &ref = *ptr;Комплексные числа можно складывать, вычитать, умножать, делить, сравнивать,получать значения вещественной и мнимой части.
(Более подробно мы будем говорить оклассе complex в разделе 4.6.)3.12. Директива typedefДиректива typedef позволяет задать синоним для встроенного либо пользовательскоготипа данных. Например:123С++ для начинающихtypedeftypedeftypedeftypedefdoublevector<int>vec_intbooltypedef int124wages;vec_int;test_scores;in_attendance;*Pint;Имена, определенные с помощью директивы typedef, можно использовать точно так же,// double hourly, weekly;wages hourly, weekly;// vector<int> vecl( 10 );vec_int vecl( 10 );// vector<int> test0( c1ass_size );const int c1ass_size = 34;test_scores test0( c1ass_size );// vector< bool > attendance;vector< in_attendance > attendance( c1ass_size );// int *table[ 10 ];как спецификаторы типов:Pint table [ 10 ];Эта директива начинается с ключевого слова typedef, за которым идет спецификатортипа, и заканчивается идентификатором, который становится синонимом для указанноготипа.Для чего используются имена, определенные с помощью директивы typedef? Применяямнемонические имена для типов данных, можно сделать программу более легкой длявосприятия.
Кроме того, принято употреблять такие имена для сложных составныхтипов, в противном случае воспринимаемых с трудом (см. пример в разделе 3.14), дляобъявления указателей на функции и функции-члены класса (см. раздел 13.6).Ниже приводится пример вопроса, на который почти все дают неверный ответ. Ошибкавызвана непониманием директивы typedef как простой текстовой макроподстановки.Дано определение:typedef char *cstring;Каков тип переменной cstr в следующем объявлении:extern const cstring cstr;Ответ, который кажется очевидным:const char *cstrОднако это неверно. Спецификатор const относится к cstr, поэтому правильный ответ –константный указатель на char:char *const cstr;С++ для начинающих3.13.
Спецификатор volatileОбъект объявляется как volatile (неустойчивый, асинхронно изменяемый), если егозначение может быть изменено незаметно для компилятора, например переменная,обновляемая значением системных часов. Этот спецификатор сообщает компилятору, чтоне нужно производить оптимизацию кода для работы с данным объектом.volatile int disp1ay_register;volatile Task *curr_task;volatile int ixa[ max_size ];Спецификатор volatile используется подобно спецификатору const:volatile Screen bitmap_buf;display_register – неустойчивый объект типа int.
curr_task – указатель нанеустойчивый объект класса Task. ixa – неустойчивый массив целых, причем каждыйэлемент такого массива считается неустойчивым. bitmap_buf – неустойчивый объекткласса Screen, каждый его член данных также считается неустойчивым.Единственная цель использования спецификатора volatile – сообщить компилятору,что тот не может определить, кто и как может изменить значение данного объекта.Поэтому компилятор не должен выполнять оптимизацию кода, использующего данныйобъект.3.14.
Класс pairКласс pair (пара) стандартной библиотеки С++ позволяет нам определить однимобъектом пару значений, если между ними есть какая-либо семантическая связь. Этизначения могут быть одинакового или разного типа. Для использования данного классанеобходимо включить заголовочный файл:#inc1ude <uti1ity>Например, инструкцияpair< string, string > author( "James", "Joyce" );создает объект author типа pair, состоящий из двух строковых значений.string firstBook;if ( Joyce.first == "James" &&Joyce.second == "Joyce" )Отдельные части пары могут быть получены с помощью членов first и second:firstBook = "Stephen Hero";Если нужно определить несколько однотипных объектов этого класса, удобноиспользовать директиву typedef:125С++ для начинающихtypedef pair< string, string > Authors;Authors proust( "marcel", "proust" );Authors joyce( "James", "Joyce" );Authors musil( "robert", "musi1" );Вот другой пример употребления пары.
Первое значение содержит имя некоторогоclass EntrySlot;extern EntrySlot* 1ook_up( string );typedef pair< string, EntrySlot* > SymbolEntry;SymbolEntry current_entry( "author", 1ook_up( "author" ));// ...if ( EntrySlot *it = 1ook_up( "editor" )){current_entry.first = "editor";current_entry.second = it;объекта, второе – указатель на соответствующий этому объекту элемент таблицы.}(Мы вернемся к рассмотрению класса pair в разговоре о контейнерных типах в главе 6 иоб обобщенных алгоритмах в главе 12.)3.15. Типы классовМеханизм классов позволяет создавать новые типы данных; с его помощью введенытипы string, vector, complex и pair, рассмотренные выше.
В главе 2 мы рассказывалио концепциях и механизмах, поддерживающих объектный и объектно-ориентированныйподход, на примере реализации класса Array. Здесь мы, основываясь на объектномподходе, создадим простой класс String, реализация которого поможет понять, вчастности, перегрузку операций – мы говорили о ней в разделе 2.3. (Классы подробнорассматриваются в главах 13, 14 и 15.
Мы дали краткое описание класса для того, чтобыприводить более интересные примеры. Читатель, только начинающий изучение С++,может пропустить этот раздел и подождать более систематического описания классов вследующих главах.)Наш класс String должен поддерживать инициализацию объектом класса String,строковым литералом и встроенным строковым типом, равно как и операциюприсваивания ему значений этих типов.
Мы используем для этого конструкторы класса иперегруженную операцию присваивания. Доступ к отдельным символам String будетреализован как перегруженная операция взятия индекса. Кроме того, нам понадобятся:функция size() для получения информации о длине строки; операция сравненияобъектов типа String и объекта String со строкой встроенного типа; а также операцииввода/вывода нашего объекта. В заключение мы реализуем возможность доступа квнутреннему представлению нашей строки в виде строки встроенного типа.Определение класса начинается ключевым словом class, за которым следуетидентификатор – имя класса, или типа.
В общем случае класс состоит из секций,предваряемых словами public (открытая) и private (закрытая). Открытая секция, как126С++ для начинающихправило, содержит набор операций, поддерживаемых классом и называемых методамиили функциями-членами класса. Эти функции-члены определяют открытый интерфейскласса, другими словами, набор действий, которые можно совершать с объектамиданного класса. В закрытую секцию обычно включают данные-члены, обеспечивающиевнутреннюю реализацию. В нашем случае к внутренним членам относятся _string –указатель на char, а также _size типа int.
_size будет хранить информацию о длинестроки, а _string – динамически выделенный массив символов. Вот как выглядит#inc1ude <iostream>class String;istream& operator>>( istream&, String& );ostream& operator<<( ostream&, const String& );class String {public:// набор конструкторов// для автоматической инициализации// String strl;// String()// String str2( "literal" ); // String( const char* );// String str3( str2 );// String( const String& );String();String( const char* );String( const String& );// деструктор~String();// операторы присваивания// strl = str2// str3 = "a string literal"String& operator=( const String& );String& operator=( const char* );// операторы проверки на равенство// strl == str2;// str3 == "a string literal";bool operator==( const String& );bool operator==( const char* );// перегрузка оператора доступа по индексу// strl[ 0 ] = str2[ 0 ];char& operator[]( int );// доступ к членам классаintsize() { return _size; }char* c_str() { return _string; }private:int _size;char *_string;определение класса:}127С++ для начинающихКласс String имеет три конструктора.
Как было сказано в разделе 2.3, механизмперегрузки позволяет определять несколько реализаций функций с одним именем, есливсе они различаются количеством и/или типами своих параметров. Первый конструкторString();является конструктором по умолчанию, потому что не требует явного указанияначального значения.
Когда мы пишем:String str1;для str1 вызывается такой конструктор.Два оставшихся конструктора имеют по одному параметру. Так, дляString str2("строка символов");вызывается конструкторString(const char*);а дляString str3(str2);конструкторString(const String&);Тип вызываемого конструктора определяется типом фактического аргумента. Последнийиз конструкторов, String(const String&), называется копирующим, так как онинициализирует объект копией другого объекта.Если же написать:String str4(1024);то это вызовет ошибку компиляции, потому что нет ни одного конструктора с параметромтипа int.Объявление перегруженного оператора имеет следующий формат:return_type operator op (parameter_list);где operator – ключевое слово, а op – один из предопределенных операторов: +, =, ==, []и так далее. (Точное определение синтаксиса см. в главе 15.) Вот объявлениеперегруженного оператора взятия индекса:char& operator[] (int);Этот оператор имеет единственный параметр типа int и возвращает ссылку на char.Перегруженный оператор сам может быть перегружен, если списки параметров128С++ для начинающихотдельных конкретизаций различаются.
Для нашего класса String мы создадим по дваразличных оператора присваивания и проверки на равенство.Для вызова функции-члена применяются операторы доступа к членам – точка (.) илиString object("Danny");String *ptr = new String ("Anna");стрелка (->). Пусть мы имеем объявления объектов типа String:String array[2];vector<int> sizes( 3 );// доступ к члену для objects (.);// objects имеет размер 5sizes[ 0 ] = object.size();// доступ к члену для pointers (->)// ptr имеет размер 4sizes[ 1 ] = ptr->size();// доступ к члену (.)// array[0] имеет размер 0Вот как выглядит вызов функции size() для этих объектов:sizes[ 2 ] = array[0].size();Она возвращает соответственно 5, 4 и 0.String namel( "Yadie" );String name2( "Yodie" );// bool operator==(const String&)if ( namel == name2 )return;else// String& operator=( const String& )Перегруженные операторы применяются к объекту так же, как обычные:namel = name2;Объявление функции-члена должно находиться внутри определения класса, аопределение функции может стоять как внутри определения класса, так и вне его.