straustrup2 (852740), страница 14
Текст из файла (страница 14)
В вызове pow(2,i)транслятор преобразует целую константу (целое 2) в число с плавающей точкой (float), как того требуетфункция. Функция pow может быть определена следующим образом:float pow ( float x, int n ){if ( n < 0 )error ( "ошибка: для pow () задан отрицательный показатель");switch ( n ){case 0: return 1;case 1: return x;default: return x * pow ( x, n-1 );}}Первая часть определения функции задает ее имя, тип возвращаемого значения (если оно есть), атакже типы и имена формальных параметров (если они существуют).
Значение возвращается изфункции с помощью оператора return.32Бьерн Страуструп.Язык программирования С++Разные функции обычно имеют разные имена, но функциям, выполняющим сходные операции надобъектами разных типов, лучше дать одно имя. Если типы параметров таких функций различны, тотранслятор всегда может разобраться, какую функцию нужно вызывать. Например, можно иметь двефункции возведения в степень: одну - для целых чисел, а другую - для чисел с плавающей точкой:int pow ( int, int );double pow ( double, double );//...x = pow ( 2,10 );// вызов pow ( int, int )y = pow ( 2.0, 10.0 );// вызов pow ( double, double )Такое многократное использование имени называется перегрузкой имени функции или простоперегрузкой; перегрузка рассматривается особо в главе 7.Параметры функции могут передаваться либо "по значению", либо "по ссылке".
Рассмотримопределение функции, которая осуществляет взаимообмен значений двух целых переменных. Еслииспользуется стандартный способ передачи параметров по значению, то придется передаватьуказатели:void swap ( int * p, int * q ){int t = * p;* p = * q;* q = t;}Унарная операция * называется косвенностью (или операцией разыменования), она выбирает значениеобъекта, на который настроен указатель.
Функцию можно вызывать следующим образом:void f ( int i, int j ){swap ( & i, & j );}Если использовать передачу параметра по ссылке, можно обойтись без явных операций с указателем:void swap (int & r1, int & r2 ){int t = r1;r1 = r2;r2 = t;}void g ( int i, int j ){swap ( i, j );}Для любого типа T запись T& означает "ссылка на T". Ссылка служит синонимом той переменной,которой она инициализировалась. Отметим, что перегрузка допускает сосуществование двух функций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; }// ...};Таково определение шаблона типа. Он задает способ получения семейства сходных классов.