Б. Страуструп - Язык программирования С++ (1119446), страница 14
Текст из файла (страница 14)
Ссылка служит синонимом той переменной,которой она инициализировалась. Отметим, что перегрузка допускает сосуществование двух функцийswap в одной программе.1.3.6 МодулиПрограмма С++ почти всегда состоит из нескольких раздельно транслируемых "модулей". Каждый"модуль" обычно называется исходным файлом, но иногда - единицей трансляции.
Он состоит изпоследовательности описаний типов, функций, переменных и констант. Описание extern позволяет изодного исходного файла ссылаться на функцию или объект, определенные в другом исходном файле.Например:extern "C" double sqrt ( double );extern ostream cout;Самый распространенный способ обеспечить согласованность описаний внешних во всех исходных33Бьерн Страуструп.Язык программирования С++файлах - поместить такие описания в специальные файлы, называемые заголовочными. Заголовочныефайлы можно включать во все исходные файлы, в которых требуются описания внешних.
Например,описание функции sqrt хранится в заголовочном файле стандартных математических функций с именемmath.h, поэтому, если нужно извлечь квадратный корень из 4, можно написать:#include <math.h>//...x = sqrt ( 4 );Поскольку стандартные заголовочные файлы могут включаться во многие исходные файлы, в них нетописаний, дублирование которых могло бы вызвать ошибки. Так, тело функции присутствует в такихфайлах, если только это функция-подстановка, а инициализаторы указаны только для констант ($$4.3).Не считая таких случаев, заголовочный файл обычно служит хранилищем для типов, он предоставляетинтерфейс между раздельно транслируемыми частями программы.В команде включения заключенное в угловые скобки имя файла (в нашем примере - <math.h>)ссылается на файл, находящийся в стандартном каталоге включаемых файлов.
Часто это - каталог/usr/include/CC. Файлы, находящиеся в других каталогах, обозначаются своими путевыми именами,взятыми в кавычки. Поэтому в следующих командах:#include "math1.h"#include "/usr/bs/math2.h"включаются файл math1.h из текущего каталога пользователя и файл math2.h из каталога /usr/bs.Приведем небольшой законченный пример, в котором строка определяется в одном файле, апечатается в другом. В файле header.h определяются нужные типы:// header.hextern char * prog_name;extern void f();Файл main.c является основной программой:// main.c#include "header.h"char * prog_name = "примитивный, но законченный пример";int main (){f();}а строка печатается функцией из файла f.c:// f.c#include <stream.h>#include "header.h"void f (){cout << prog_name << '\n';}При запуске транслятора С++ и передаче ему необходимых файлов-параметров в различныхреализациях могут использоваться разные расширения имен для программ на С++.
На машине авторатрансляция и запуск программы выглядит так:$ CC main.c f.c -o silly$ sillyпримитивный, но законченный пример$Кроме раздельной трансляции концепцию модульности в С++ поддерживают классы ($$5.4).34Бьерн Страуструп.Язык программирования С++1.4 Поддержка абстракции данныхПоддержка программирования с абстракцией данных в основном сводится к возможности определитьнабор операций (функции и операции) над типом. Все обращения к объектам этого типаограничиваются операциями из заданного набора.
Однако, имея такие возможности, программист скорообнаруживает, что для удобства определения и использования новых типов нужны еще некоторыерасширения языка. Хорошим примером такого расширения является перегрузка операций.1.4.1 Инициализация и удалениеКогда представление типа скрыто, необходимо дать пользователю средства для инициализациипеременных этого типа.
Простейшее решение – до использования переменной вызывать некоторуюфункцию для ее инициализации.Например:class vector{// ...public:void init ( init size );// вызов init () перед первым// использованием объекта vector// ...};void f (){vector v;// пока v нельзя использоватьv.init ( 10 );// теперь можно}Но это некрасивое и чреватое ошибками решение. Будет лучше, если создатель типа определит дляинициализации переменных некоторую специальную функцию. Если такая функция есть, то двенезависимые операции размещения и инициализации переменной совмещаются в одной (иногда ееназывают инсталляцией или просто построением). Функция инициализации называется конструктором.Конструктор выделяется среди всех прочих функций данного класса тем, что имеет такое же имя, как исам класс.
Если объекты некоторого типа строятся нетривиально, то нужна еще одна дополнительнаяоперация для удаления их после последнего использования. Функция удаления в С++ называетсядеструктором. Деструктор имеет то же имя, что и его класс, но перед ним стоит символ ~ (в С++ этотсимвол используется для операции дополнения). Приведем пример:class vector{int sz;int * v;public:vector ( int );~vector ();int& operator [] ( int index );};// число элементов// указатель на целые// конструктор// деструктор// операция индексацииКонструктор класса vector можно использовать для контроля над ошибками и выделения памяти:vector::vector ( int s ){if ( s <= 0 )error ( "недопустимый размер вектора" );sz = s;v = new int [ s ];// разместить массив из s целых}35Бьерн Страуструп.Язык программирования С++Деструктор класса vector освобождает использовавшуюся память:vector::~vector (){delete [] v;// освободить массив, на который// настроен указатель v}От реализации С++ не требуется освобождения выделенной с помощью new памяти, если на неебольше не ссылается ни один указатель (иными словами, не требуется автоматическая "сборкамусора").
В замен этого можно без вмешательства пользователя определить в классе собственныефункции управления памятью. Это типичный способ применения конструкторов и деструкторов, хотяесть много не связанных с управлением памятью применений этих функций (см., например, $$9.4).1.4.2 Присваивание и инициализацияДля многих типов задача управления ими сводится к построению и уничтожению связанных с нимиобъектов, но есть типы, для которых этого мало.
Иногда необходимо управлять всеми операциямикопирования. Вернемся к классу vector:void f (){vector v1 ( 100 );vector v2 = v1;v1 = v2;// ...// построение нового вектора v2,// инициализируемого v1// v2 присваивается v1}Должна быть возможность определить интерпретацию операций инициализации v2 и присваивания v1.Например, в описании:class vector{int * v;int sz;public:// ...void operator = ( const vector & ); // присваиваниеvector ( const vector & );// инициализация};указывается, что присваивание и инициализация объектов типа vector должны выполняться с помощьюопределенных пользователем операций.
Присваивание можно определить так:void vector::operator = ( const vector & a )// контроль размера и копирование элементов{if ( sz != a.sz )error ( "недопустимый размер вектора для =" );for ( int i = 0; i < sz; i++ ) v [ i ] = a.v [ i ];}Поскольку эта операция использует для присваивания "старое значение" вектора, операцияинициализации должна задаваться другой функцией, например, такой:vector::vector ( const vector & a )// инициализация вектора значением другого вектора{sz = a.sz;// размер тот жеv = new int [ sz ];// выделить память для массиваfor ( int i = 0; i < sz; i++ )//копирование элементовv [ i ] = a.v [ i ];36Бьерн Страуструп.Язык программирования С++}В языке С++ конструктор вида T(const T&) называется конструктором копирования для типа T. Любуюинициализацию объектов типа T он выполняет с помощью значения некоторого другого объекта типа T.Помимо явной инициализации конструкторы вида T(const T&) используются для передачи параметровпо значению и получения возвращаемого функцией значения.1.4.3 Шаблоны типаЗачем программисту может понадобиться определить такой тип, как вектор целых чисел? Как правило,ему нужен вектор из элементов, тип которых неизвестен создателю класса Vector.
Следовательно, надосуметь определить тип вектора так, чтобы тип элементов в этом определении участвовал как параметр,обозначающий "реальные" типы элементов:template < class T > class Vector{ // вектор элементов типа TT * v;int sz;public:Vector ( int s ){if ( s <= 0 )error ( "недопустимый для Vector размер" );v = new T [ sz = s ];// выделить память для массива s типа T}T & operator [] ( int i );int size () { return sz; }// ...};Таково определение шаблона типа.
Он задает способ получения семейства сходных классов. В нашемпримере шаблон типа Vector показывает, как можно получить класс вектор для заданного типа егоэлементов. Это описание отличается от обычного описания класса наличием начальной конструкцииtemplate<class T>, которая и показывает, что описывается не класс, а шаблон типа с заданнымпараметром-типом (здесь он используется как тип элементов).Теперь можно определять ииспользовать вектора разных типов:void f (){Vector < int > v1 ( 100 );Vector < complex > v2 ( 200 );// вектор из 100 целых// вектор из 200// комплексных чиселv2 [ i ] = complex ( v1 [ x ], v1 [ y ] );// ...}Возможности, которые реализует шаблон типа, иногда называются параметрическими типами илигенерическими объектами.Оно сходно с возможностями, имеющимися в языках Clu и Ада.Использование шаблона типа не влечет за собой каких-либо дополнительных расходов времени посравнению с использованием класса, в котором все типы указаны непосредственно.1.4.4 Обработка особых ситуацийПо мере роста программ, а особенно при активном использовании библиотек появляетсянеобходимость стандартной обработки ошибок (или, в более широком смысле, "особых ситуаций").Языки Ада, Алгол-68 и Clu поддерживают стандартный способ обработки особых ситуаций.Снова вернемся к классу vector.
Что нужно делать, когда операции индексации передано значениеиндекса, выходящее за границы массива? Создатель класса vector не знает, на что рассчитываетпользователь в таком случае, а пользователь не может обнаружить подобную ошибку (если бы мог, то37Бьерн Страуструп.Язык программирования С++эта ошибка вообще не возникла бы). Выход такой: создатель класса обнаруживает ошибку выхода заграницу массива, но только сообщает о ней неизвестному пользователю. Пользователь сам принимаетнеобходимые меры.Например:class vector {// определение типа возможных особых ситуацийclass range { };// ...};Вместо вызова функции ошибки в функции vector::operator[]() можно перейти на ту часть программы, вкоторой обрабатываются особые ситуации. Это называется "запустить особую ситуацию" ("throw theexception"):int & vector::operator [] ( int i ){if ( i < 0 || sz <= i ) throw range ();return v [ i ];}В результате из стека будет выбираться информация, помещаемая туда при вызовах функций, до техпор, пока не будет обнаружен обработчик особой ситуации с типом range для класса вектор(vector::range); он и будет выполняться.Обработчик особых ситуаций можно определить только для специального блока:void f ( int i ){try{// в этом блоке обрабатываются особые ситуации// с помощью определенного ниже обработчикаvector v ( i );// ...v [ i + 1 ] = 7;// приводит к особой ситуации range// ...g (); // может привести к особой ситуации range// на некоторых векторах}catch ( vector::range ){error ( "f (): vector range error" );return;}}Использование особых ситуаций делает обработку ошибок более упорядоченной и понятной.Обсуждение и подробности отложим до главы 9.1.4.5 Преобразования типовОпределяемые пользователем преобразования типа, например, такие, как преобразование числа сплавающей точкой в комплексное, которое необходимо для конструктора complex(double), оказалисьочень полезными в С++.