    Глава 12

    Введение в C++ 

    Многие программисты считают язык C++ объектно-ориентированной версией 
языка С. Это не совсем так, хотя C++ внес значительный вклад в понятие класса 
— особой структуры, членами которой могут быть как данные, так и функции. В 
главе 13 вы узнаете, что такое классы и как они используются в 
объектно-ориентированном программировании. В этой главе вы ознакомитесь с 
менее существенными, но не менее важными особенностями C++. 
    В названии языка C++ ("Си плюс плюс") обыгрывается оператор инкремента 
языка С. Дословно C++ — "следующий шаг за С". Ранее он был известен как "С с 
классами", но название C++ быстро завоевало популярность и стало фактически 
настоящим именем улучшенной версии С. Изобретатель C++, Бьярн .Страуструп, 
назвал этот язык "улучшенный С". Кроме классов, C++ добавил к ANSI С также и 
несколько новых ключевых слов и операторов, встраиваемые и перегружаемые 
функции, перегрузку операторов, новую технику управления памятью и другие 
средства. В любом случае, эти свойства C++ были разработаны для коррекции 
ошибок и несовместимостей в С, а также для расширения возможностей создания и 
обеспечения большей надежности кода. Если С сравнить со сладкой булочкой, то 
C++ — это свежий пломбир с орехами и фруктами. 
    С тех пор как C++ начал создаваться в 80-х годах в AT&T Bell Laboratories, 
некоторые его особенности вернулись в ANSI С, затушевав таким образом отличия 
C++ от его непосредственного донора — классического С. Сегодня ANSI С и C++ 
близки, как двоюродные братья, и большинство программ, написанных на ANSI С, 
совместимы с C++. Однако многие наиболее передовые свойства C++ (такие, как 
классы) не поддерживаются в ANSI С. 

    Анатомия программ на C++ 

    Многое из того, что вы узнали о программировании на ANSI С, применимо и к 
C++. Программы на ANSI С и C++ очень похожи. У них приблизительно один и тот 
же синтаксис, одни и те же виды циклов, типы данных, указатели и пр. 
    Поскольку компилятор Borland C++ может воспринимать программы, написанные 
как на ANSI С, так и на C++, необходимо указать, какой из языков он должен 
иметь в виду. Самый простой способ выполнить это требование для программ на 
C++ — это дать имена вашим исходным файлам с расширением .СРР вместо .С. 
Команда DOS bcc file.cpp компилирует FILE.СРР как программу на C++. Поскольку 
.СРР — расширение по умолчанию файла Borland C++, команды bcc file и bcc 
file.cpp дадут один и тот же результат. Чтобы компилировать программы на C++ 
из интегрированной среды, сохраните их с расширением .СРР и далее компилируйте 
как обычно. 


    ЗАМЕЧАНИЕ 

    Вы можете также использовать опцию -Р, чтобы указать Borland C++, что 
компилируются программы на C++. Использовать для исходных файлов расширение 
,СРР, конечно, проще, кроме того зто поможет вам различать программы на ANSI С 
и на C++. Некоторые программисты также дают своим заголовочным файлам на C++ 
расширение .НРР вместо .H. Компилятор не делает различай между .НРР и .Н, 
поэтому в данной книге все заголовочные файлы заканчивайте на .Н, 
Зквивалентного параметра в интегрированной среде не существует. 

    В главе 5 вы уже скомпилировали и запустили простую программу WELCOME.С. 
Листинг 12.1 — аналогичная версия на C++ с похожим кодом. Для компиляции и 
запуска программы в DOS последовательно введите команды bcc welcome и welcome. 
Для компиляции и запуска программы в интегрированной среде установите 
TargetExport равным EasyWin и нажмите <Ctrl+F9> 

    Листинг 12.1 WELCOME.CPP (программа на C++) 

    #include <iostream.h> 

    main() { 
       cout << "Welcome to Borland C++ programming!  \n" 
        return 0 ; 
    } 

    Существует два основных различия между листингом 12.1 и листингом 5.1. 
Во-первых, в первой строке включается IOSTREAM.H вместо привычного STDIO.H 
(при необходимости вы можете включить оба этих заголовочных файла). Во-вторых, 
в строке 5 используется стандартный поток вывода объектов cout ("character 
out", или "вывод символов") библиотеки потоков ввода-вывода для вывода текста 
на дисплей. Для того чтобы направить информацию по месту назначения (в данном 
случае, на экран), библиотека потоков ввода-вывода использует оператор C++ « 
("направить в"). Можно представить себе << в виде стрелок, перенаправляющих 
такие данные, как строка символов, из WELCOME, в такой потоковый объект 
ввода-вывода, как cout. 
    Вы можете также использовать составной оператор потоков ввода-вывода. Если 
firstName и lastName имеют тип указателей на строку char *, то выражение 

    cout << lastName << "," << firstName << '\n'; 

выводит два имени (lastName выводится первым), разделенных запятой и 
заканчивающихся переводом строки. Вы можете написать строку "\п" или символ 
'\п' в качестве начала новой строки. Некоторые программисты предпочитают 
писать каждую часть составного оператора iostream с новой строки. Независимо 
от внешнего вида, следующее выражение — один оператор, а не четыре: 

    cout << lastName 
    << "," 
    << firstName 
    << '\n' 

    Библиотека потоков ввода-вывода впечатляет своей способностью выводить 
различные типы данных, включая встроенные (такие, как int, long, double), а 
также .типы, которые вы создали сами. Если count имеет тип long, то выражение 

    cout << "Count == " << count <<  '\n'; 

выведет строку, завершающуюся значением count и переводом строки. 
    Вы изучите библиотеку потоков ввода-вывода более подробно далее в этой 
главе. А пока не обращайте особого внимания на различие в типах ее операторов 
в листингах части III. 

    ЗАМЕЧАНИЕ 

    Вы можете использовать методы stdio и библиотека потоков ввода-вывода 
вперемежку, а также использовать printf() и аналогичные функции в программах 
на C++. Конечно, как вы узнаете в главе 15 библиотеку потоков ввода-вывода 
можно расширить для обработки новых типов данных. Функция prinf() может 
манипулировать только встроенными типами данных и своими собственными 
форматами вывода, и, хотя этих форматов огромное количество их нельзя 
запрограммировать. 


    Сравнивая С и C++ 

    До того как мы приступим к изучению возможностей C++, необходимо обратить 
внимание на основные отличия ANSI С от C++. 


    Глава 12. Введение в C++     279 

    ANSI С и C++ в основном используют один и тот же синтаксис, операторы, 
выражения, встроенные типы данных, структуры, массивы, объединения, циклы, 
функции и указатели. 
    C++ имеет несколько дополнительных ключевых слов, приведенных в табл. 12.1 
вместе со словами (отмечены звездочкой), характерными для C++. В C++ также 
имеется несколько новых операторов, которые будут приведены позже. 

    Таблица 12.1. Ключевые слова ANSI С и C++ 

    asm 		default  	friend* 	protected* 	switch 	 void
    auto 		delete* 	goto 		public*  	template 	volatile 
    break		do  		if  		register	this* 	 while    
    case		double	inline*	return	throw* 
    catch* 		else 		int 		short 	try*  
    char 		enum 		long		signed 	typedef 	
    class*		extern	new*		sizeof	union 
    const		float		operator*	static	unsigned
    continue	for		private*	struct	virtual* 


    *Только для C++ 

    C++ требует, чтобы прототипы всех функций были определены до их вызова.  В 
ANSI С определение прототипов желательно, но не обязательно. 

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

    В C++ сильно ослаблена потребность в typedef-объявлениях. Вы можете 
объявить структуру типа 

    struct mystruct { 
    } ;

и затем объявлять переменные как mystruct x;. Чтобы проделать нечто 
подобное в ANSI С, вы должны написать struct mystruct x; или использовать 
typedef для структуры mystruct. 

    В C++ тип char — это байт из 8 бит. В ANSI С размер этого типа не 
определен, хотя в Borland C++ он всегда длиной 8 бит. 

    В ANSI С символьные константы имеют тип int, и выражение sizeof('X') 
эквивалентно sizeof(int). В C++ символьные константы имеют тип char, и 
выражение sizeof('X') соответствует sizeof(char). 

    ЗАМЕЧАНИЕ 

    Приведенный список не является полным сравнением ANSI C и C'++. Прочие 
отличия отличия будут приведены в соответствующих разделах. 
    

    Комментарии C++ 

    C++ дополнил комментарии ANSI С новым стилем, воскрешенным из BCPL — 
предшественника классического С. В программах C++ двойная косая начинает 
комментарий. Компилятор игнорирует все символы после / / до конца строки. В 
ANSI С комментарий 

    /* Это комментарий */ 

также может быть написан на C++ как 

    // Это комментарий 

    Комментарий ANSI С, который можно использовать и в программах на C++, 
требует двух символов: /*, чтобы начать комментарий, и */, чтобы закончить 
его. Комментарий ANSI С может продолжаться на двух и более строках, а 
комментарий C++ занимает только одну строку. Любой из них может продолжаться 
до конца строки. В ANSI С вы можете добавить комментарий к оператору следующим 
образом: 

    count++; /* Увеличить count на единицу */	.      . . 


    280     Часть III. Программирование на ANSI C++ 


    В C++ эту;строку можно записать и так:

    count++;  // Увеличить count на единицу

    В оператор можно вставить только комментарий ANSI С. Приведем правильный
(хотя и приводящий в недоумение) оператор на ANSI С и на C++:

    _result = count /* Вложенный комментарий */ + 100;

Конечно же, выражение

    result = count // Вложенный комментарий??? + 100;

    вызовет сообщение об ошибке, поскольку комментарий в C++ распространяетс
до конца строки и никак не может быть вложенным внутри оператора.

    Смешение стилей комментари

    В одной и той же программе одновременно могут употребляться комментарии
ANSI С и C++. В листинге 12.2 приводится несколько таких примеров.

    Листинг 12.2.  COMMENTS.CPP (сравнение типов комментариев ANSI С и C++)

    1:     #include <iostream.h>
    2:
    3:     // ----------------------------------------------------------------------------------
    4:     // Автор : Том Сван
    5:     // Версия : V1.0
    6:     // Назначение :  Демонстрация комментариев в C++
    7:     // ----------------------------------------------------------------------------------
    8:
    9:     main()
    10:    {
    11:    cout « "A Brief C++ Commentary\n"; // Вывести заголовок
    12:    cout « "\n"; // Вывести пустую строку после заголовка
    13:
    14:    /* Этот абзац демонстрирует, что комментарий на языке С может
    15:    занимать более одной строки.
    16:    Комментарий на C++ ограничен одной строкой. */
    17:
    18:    cout « "// This is not a comment.\n\n"; // Это комментарий
    19:    cout « "/* This also is not comment.*/ \n\n";
    20:    cout /* Это комментарий. */ « "This text is displayed.\n";
    21:    return 0;
    22:    }


    ЗАМЕЧАНИЕ

    В интегрированной среде при выделении синтаксиса цвет комментариев очень
похож на цвет строчек текста, что делает COMMENTTS.CPP трудным для чтения. Дл
изменения цветов выберите в Options\\Environment раздел Syntax
Higklighting\Customize, затем Comment из, списка Element к щелкните кнопкой
мыши на альтернативном цвете переднего плана CFG). (Я использую темно-еерьй.)

    В строках 3-7 используется комментарий C++ для небольшого информационного
заголовка, описывающего программу. Многие программисты любят "помечать" свои
исходные файлы своеобразно стилизованными заголовками, которые могут содержать
информацию о программе, исправлениях, дате модификаций и т.п.
    Строки 11-12 демонстрируют классический способ завершать операторы на C++
поясняющими комментариями. Строки 14-16 содержат многострочный комментарий на
ANSI С. Для написания аналогичного комментария на C++ каждую строку следовало
бы начинать с / /.
    Строки 18-19 иллюстрируют важный принцип: комментарии любого стиля не
должны оказаться в середине символьной строки. Строка 20 показывает, что
только комментарий стиля ANSI С может быть вложен внутрь оператора — прием,
приносящий скорее вред, чем пользу, и лучше его избегать.

    Отладка с комментариями

    Вы можете помещать комментарии C++ внутрь комментариев на ANSI С — это
факт, на котором основан полезный отладочный трюк. Поразмышляйте над таким фрагментом:

    char buffer[128];	// Буфер для ввода
    gets(buffer);	// Записать строку в буфер
    cout « '\n';	// Начать новую строку
    cout « buffer «	'\n';             // Вывести буфер и перейти на новую строку


    Глава 12. Введение в C++     281 


    Предположим, эти операторы не дают ожидаемого вывода на дисплей, и вы 
приняли решение проверить, что случится, если вы удалите оператор cout <<
'\n'; ,  который переводит строку перед отображением буфера. Вместо того чтобы 
удалить строку, вы можете добиться нужного эффекта, превратив ее в комментарий: 

    char buffer[128];	// Буфер для ввода 
    gets(buffer);           // Записать строку в буфер 
    /• 
    cout « '\n';	// Начать новую строку для отображения 
    •/ 
    cout « buffer «  '\n';	// Вывести буфер и перейти на новую строку 

    Скобки /* и */ "комментируют" заключенный в них оператор. Компилятор 
игнорирует все символы между скобками комментария, включая и комментарий на 
C++ // Начать новую строку для отображения. Теперь вы можете перекомпилировать 
и запустить временно измененную программу. Если вы решили восстановить 
первоначальный оператор, необходимо лишь удалить скобки комментария ANSI С. 

    Введение в потоки ввода-вывода 

    Несколько примеров программ демонстрируют мощь библиотеки потоков 
ввода-вывода в C++ (см. главу 15). Как и в ANSI С ввод и вывод в C++ не 
являются частью языка, а обеспечиваются внешними библиотечными модулями. Это 
означает, что программисты, пишущие программы для встроенных систем или для 
компьютеров, предназначенных для особых целей, вольны придумывать свои функции 
и способы ввода-вывода — это одна из причин популярности С и C++ в задачах 
программирования низкого уровня. Большинство программистов, конечно, могут 
воспользоваться стандартными библиотеками, разработанными специалистами 
высокого класса в области программного обеспечения, которых нелегко превзойти. 
    Поведение объекта потока ввода-вывода очень похоже на стандартный 
ввод-вывод потоков файлов, введенный в части II и подробно описанный в главе 
11. Потоки cout (character out) и cin (character in) имеют множество 
разнообразных возможностей. Чтобы отобразить символ с, вы можете написать 

    cout << с; 

или 

    cout.put(c); 

    Функция put() — член cout, это новое понятие, с которым вы будете часто 
встречаться в следующих разделах. Для вызова функции-члена, отделите его 
точкой от cout, как в записи, используемой для доступа к члену структуры. 
Поток cout — пример класса, который может иметь относящиеся к нему функции, 
такие, как put(). (В следующей главе эта возможность будет рассмотрена 
подробнее.) Вы должны вызывать функцию-член только со ссылкой на объект, 
подобный cout. Без этого выражение 

    put(c);  // ??? 

попытается вызвать независимую функцию put(). Оператор 

    cout.put(с); 

вызовет функцию put(), которая принадлежит cout. 
    Для того чтобы прочитать символ из стандартного ввода, следует 
использовать поток ввода cin, стоящий перед оператором » (взять из). Оператор 
    cin >> с; 

читает один символ из стандартного ввода и присваивает его переменной с. 
    Вы также можете вызвать функцию-член get из cin, сделав это следующим образом: 

    cin.get(c); 

    Как и в стандартном вводе-выводе, библиотека потоков использует системные 
стандартные файлы ввода и вывода и может использоваться для написания 
программ-фильтров. Листинг 12.3 демонстрирует простую программу фильтра. 
Поскольку Windows не поддерживает понятие фильтра, скомпилируйте FILTER. CPP 
из среды DOS, введя команду bcc filter. 

    Листинг 12.3.  FILTER.CPP (простая программа-фильтр на C++) 

    1:	#include <iostream.h> 
    2: 
    3:	main() 
    4:	{ 
    5:	  char с; 
    6: 
    7:	  while (cin.get(c)) 
    8:	     cout.put(c); 
    9:	  return 0; 
    10:	} 


    282	Часть III. Программирование на ANSI C++ 

    Запустите FILTER по аналогии с другими программами-фильтрами, переназначив 
ввод-вывод командой DOS. Например, чтобы FILTER отобразила свой исходный 
текст, введите команду DOS filter <filter.cpp. Для переназначения консольного 
вывода на принтер следует ввести filter >prn. Для того чтобы распечатать файл, 
введите filter <filename.ext >prn. 
    Строка 7 в FILTER.СРР вызывает cin.get(c) для ввода символа. Выражение 
cin.get(c) возвращает значение типа int или нуль при достижении конца 
источника ввода. Строка 8 передает все символы из источника в стандартный вывод. 

    Чтение данных встроенных типов 

    Самая привлекательная черта библиотеки потоков ввода-вывода C++ — чтение и 
запись данных самых различных типов. Листинг 12.4 демонстрирует чтение данных 
целых и вещественных типов с помощью операторов библиотеки потоков. 
Скомпилируйте и запустите программу как Easy Win-приложение для Windows или 
как DOS-программу и введите запрашиваемые значения. Попробуйте ввести ошибки 
(например, XXX вместо вещественного значения) для того, чтобы увидеть, как 
программа их обрабатывает. 

    Листинг 12.4.    GETVAL.CPP (использование библиотеки потоков ввода-вывода 
для чтения данных встроенных типов) 

    1:	#include <iostream.h> 
    2:	#include <stdlib.h> 
    3: 
    4:	void test(void); 
    5: 
    6:	main() 
    7:	{ 
    8:	  double fp;  // Вещественное значение 
    9:	  long k;  // Длинное целое 10: 
    11:	  cout « "Enter a floating-point value:   "; 
    12:	  cin » fp; 
    13:	  test(); 
    14:	  cout « "Value entered is:   " « fp «  '\n'; 
    15:	  cout « "Enter an integer value:   "; 
    16:	  cin » k; 
    17:	  test(); 
    18:	  cout « "Value entered is:   " « k «  '\n'; 
    19:	  return 0; 
    20:	} 
    21: 
    22:	void test(void) 
    23:	{ 
    24:	  if (!cin.good()) { 
    25:	    cout « "Error detected"; 
    26:	    exit(1); 
    27:	  } 
    28:	}

    Строка 11 запрашивает вещественное значение, читая его оператором 
потокового ввода в переменную fp типа double в строке 12. Аналогичный оператор 
ввода в строке 16 читает целое значение. В отличие от стандартного 
ввода-вывода, который требует точно задавать типы данных в функции scanfO или 
вызывать функции, которые могут читать и преобразовывать нестандартные типы, 
потоки ввода-вывода автоматически определяют тип переменных, используемых в 
операциях ввода-вывода. Поскольку вам не обязательно точно указывать типы 
данных, вы не сделаете ошибку в указании типа, как это могло бы быть при 
использовании стандартного ввода-вывода. В C++ очень сложно ввести в 
переменную данные неверного типа! 
    Строки 13 и 17 демонстрируют еще одно важное достоинство потоков 
ввода-вывода. Эти строки вызывают локальную функцию testO, приведенную в 
строках 22-28. 
    Функция проверяет результат вызова cin.good() — функции-члена, которая 
возвращает TRUE, если не было неисправимых ошибок для указанного потока. Можно 
использовать cout. good() точно так же и для выходного потока. В приведенном 
примере программы, если cin.good() возвращает FALSE, строки 25-26 выводят 
сообщение и завершают выполнение программы. 
    Вы не должны останавливать программу при обнаружении ошибки. Для сброса 
состояния ошибки следует использовать оператор 

    cin.clear(); 

    В главе 15 будут приведены другие функции-члены библиотеки потоков ввода-вывода. 


    Глава 12. Введение в C++	283 


    Чтение символьных строк 

    Вы уже видели, как библиотека потоков ввода-вывода может выводить 
символьные строки. Например, следующее объявление присваивает указателю на 
строку адрес строки символов 

    char *s = "Gently down the iostream"; 

Оператор 

    cout « s; 

отправит данную строку по назначению — в системный стандартный поток 
вывода, обычно на дисплей. Для чтения строки обратите процесс и используйте 
идентификатор потока ввода cin и оператор >> (поместить в) 

    char buffer[128];         // Объявление буфера строки 
    cin » buffer;	    // ??? 

    Эти строки сработают, но, если пользователь введет более 127 символов, 
оператор ввода продолжит запись за пределами буфера, при этом, возможно, 
данные и код, располагающиеся за буфером, будут уничтожены. Такая операция 
может привести к краху системы. 
    К счастью, существует более безопасный способ ввести строку, как это 
показано в листинге 12.5. Скомпилируйте и запустите эту программу, затем 
введите строку длиной более 24 символов. При этом она будет благополучно 
усечена до длины буфера, объявленного в строке 5. 

    Листинг 12.5.  GETSTR.CPP (использование потоковой библиотеки для 
безопасного чтения строк) 

    1:	#include <iostream.h> 
    2: 
    3:	main() 
    4:	{ 
    5:	  char s[25]; 
    6:	  char c; 
    7: 
    8: 	  cout « "Enter a 24-char string safely: \n"; 
    9:	  cin.get(s, 25, '\n'); 
    10:	  cout « "You entered:	" « s « '\n'; 
    11:	  if (cin.get(c) && с != \n') 
    12:	    cout « "Maximum line length reached\n"; 
    13:	  return(); 
    14:	}
  
    В строке 9 демонстрируется безопасный способ чтения строк с помощью 
оператора библиотеки потоков ввода-вывода. Функция-член cin.get() вызывается 
с тремя аргументами. 

    •   Адрес   результирующего   символьного   массива.   Строка-результат   
будет   прочитана   в   этот   массив   и завершена нулевым символом. 
    •   Размер массива в байтах. 
    •   Символ, завершающий ввод. Если не указан, по умолчанию он равен '\п'. 

    До тех пор пока вы корректно задаете размер буфера результата, чтение 
ввода, приведенное в строке 9, безопасно. Одна проблема все же остается. 
Символ новой строки или другой символ, завершающий ввод, остается в потоке и 
должен быть прочитан еще одним оператором cin.get(), как это показано в строках 
11—12. Если cin.get() не читает символ новой строки, ввод будет усечен. Этот 
факт необходимо учитывать при написании программ. 
    Проблему непрочитанного символа новой строки можно решить по-другому: с 
помощью вызова функции-члена cin.getline(). Для чтения 24-символьной строки в 
25-байтовый буфер (один байт резервируется для нулевого символа — признака 
конца строки) следует использовать операторы: 

    char buffer[25]; 
    cin.getline(buffer, 25); 


    Запись данных 

    В целом, вы можете использовать данные любого типа в операторе вывода в 
поток. Для переменной v, независимо от ее типа, выражение 

    cout « v; 

выведет нечто осмысленное, в системный стандартный поток вывода. Результат 
вывода данных некоторых типов может оказаться не совсем таким, каким вы его 
ожидали, и лучше не пытаться выводить подобным образом структуры и массивы. 
Конечно, вы можете отобразить значение указателей. Если указатель объявлен как 
int *p (или как указатель другого типа), оператор 

    cout « р; 


    284	Часть III. Программирование на ANSI C++ 

выведет значение р в виде длинного целого. Это может принести пользу при
отладке, но, вероятно, не все компиляторы C++ и операционные системы дадут
аналогичный результат.

    Форматный вывод с помощью потоков ввода-вывода

    Библиотека потоков ввода-вывода понимает множество команд форматного
вывода (некоторые из них обсуждались в главе 15). Листинг 12.6 демонстрирует
возможность использования трех идентификаторов выходного потока — dec, hex, и
oct — для вывода целых значений в десятичном, шестнадцатеричном и восьмеричном
форматах.

    Листинг 12.6. CONVERT.CPP (использование форматного вывода в потоках ввода-вывода)

    1:	#include <iostream.h>
    2:	#include <stdlib.h>
    3:
    4:	#define SIZE 35
    5:
    6:	main()
    7:	{
    8:	  int value;
    9:	  char s[SIZE];
    10:
    11:	  cout « "Value? ";
    12:	  cin.get(s, SIZE, '\n')
    13:	  value = atoi(s);
    14:	  cout « "Decimal=" << dec << value
    15:	  << "Hexadecimal=0x" << hex << value
    16:	  << "Octal=0" <<  oct << value « '\n' ;
    17:	  return 0;
    18:	}

    Строки 14-16 выводят в один и тот же поток различные данные. Одни из них —
строки символов. Другие — целые числа. Идентификаторы dec, hex, и oct,
выведенные в поток непосредственно перед выводом значения, изменяют текущий
формат вывода выходного потока.
    Кроме того, вы можете указать и ширину вывода с помощью вызова
cout.width(n), где п — нужное число позиций. Например, можно вывести значение
в 15 позициях с выравниванием по правому краю, написав следующий участок кода:

    int value = 1234;	// Форматируемое значение
    cout.width(15);	// Указать вывод в 15 позициях
    cout << value;	// Вывод значения в 15 позициях

    Иногда бывает неудобно пользоваться форматным выводом библиотеки потоков.
Ее сложные операторы могут быть трудными для чтения. Судя по обнародованным
исходным текстам, программисты-эксперты в C++ предпочитают смесь методов
форматирования вывода ANSI С и C++. Листинг 12.7 — типичный случай такого
подхода, он полностью аналогичен CONVERT. СPP.


    Листинг 12.7.  CONVERT2.CPP (альтернативный вариант форматного вывода)

    1:    #include <iostream.h>
    2:    #include <stdio.h>
    3:    #include <stdlib.h>
    4:
    5:    #define SIZE 35
    6:
    7:    main()
    8:    {
    9        int value;
    10:      char s[SIZE];
    11:      char buffer[80];
    12:
    13:      cout « "Value? ";
    14:      cin.get(s, SIZE, '\n');
    15:      value = atoi(s);
    16:      sprintf(buffer,"Decimal=%d Hexadecimal=%#x Octal=%#o\n"
    17:      value, value, value);
    18:      cout « buffer;
    19:      return 0;
    20:   }

Сложный оператор вывода библиотеки потоков из CONVERT. CPP заменен вызовом
sprintf() в строках 16-17. Функция sprintf(), описанная в STDLIB.H, готовит
символьную строку согласно тем же правилам, ко-


    Глава 12. Введение в C++     285 

торые используются в printf() и других подобных функциях. Строка 16
указывает форматы %d (десятичный), %#х (шестнадцатеричный), %#о (восьмеричный)
для вставки данных в символьный буфер в соответствующем порядке. Простой
оператор вывода в строке 18 выводит готовую строку в системный стандартный вывод.

    Область видимости и объявление переменных

    Неоднозначности области видимости переменных могут легко возникать в
программах как на ANSI С, так и на C++. Например, если существует глобальна
переменная int count, функция может объявить локальную переменную с тем же
именем, не вызвав при этом сообщения компилятора об ошибке:

    int count;	// Глобальная переменная void AnyFunction()
    {
      int count;       // Локальная переменна
     .....
    }

    Область видимости локальной переменной count действует в пределах
объявленной функции. В этой функции локальная переменная count делает
невозможным доступ к глобальной переменной с тем же именем. Для решения этой
проблемы в C++ можно использовать оператор разрешения области видимости ::.
Перепишите приведенную выше функцию следующим образом:

    int count;     // Глобальная переменна
    void AnyFunctionO
    {
      int count;                // Локальная переменна
      count = 1234;       // Присвоить значение локальной переменной
      ::count = 4321;     // Присвоить значение глобальной переменной count
    }

    Теперь она присваивает значение 1234 локальной переменной count и 4321  —
глобальной. Выражение ::count указывает C++, что используется внешн
переменная count, а не локальная (рис. 12.1).


    Рис. 12.1. Оператор разрешения области видимости может открыть доступ к
скрытой глобальной переменной


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

    Листинг 12.8. SCOPE.CPP (использование оператора разрешения видимости)

    1:    #include <iostream.h>
    2:
    3:    int k = 100; // Глобальная переменна
    4:
    5:    main()
    6:    {
    7:
    8:      int i = 200; // Локальная переменна
    9:      cout « "Global k == " « k « '\n';
    10:     cout « "Local i == " « i « '\n';
    11:     {

    286     Часть III. Программирование на ANSI C++ 


    12:	    int k = 300; 
    13	    cout << "Local k == " << k	<< '\n'; 
    14:	    cout << "Global k == " << ::k << '\n'; 
    15:	  } 
    16:	  return 0; 
    17:	} 

    В строке 3 объявляется и инициализируется значением 100 глобальная 
переменная k; а в строке 7 объявляется переменная i, локальная в функции 
main(). Строки 9-10 выводят значения k и i. Нет необходимости использовать 
оператор разрешения видимости в строке 9, так как была объявлена только одна 
переменная k. 
    Строки 11-15 содержат новый блок-оператор, заключенный внутри функции 
main(). Хотя это и необычно для практики программирования, блок может быть 
размещен в другом блоке (строки 11 и 15). Конечно, на практике подобные 
вложенные блоки принадлежат операторам if, while и им подобным. Внутри 
вложенного блока в строке 12 объявляется новая переменная с именем k, 
инициализированная значением 300. В отличие от ANSI С, C++ позволяет объявлять 
и инициализировать переменные в любом месте блока-оператора. Теперь мы имеем 
две переменных k: глобальную, объявленную в строке 3, и локальную, объявленную 
в строке 12. Поскольку новая локальная k скрывает глобальную переменную с тем 
же именем, в строке 13 отображается значение локальной переменной. Чтобы 
получить доступ к глобальной переменной, используется оператор разрешения 
области видимости, как показано в строке 14. 
    Переменные, объявленные внутри блока-оператора, существуют только в 
пределах этого блока. Другими словами, область их видимости ограничена местом 
их объявления. Следующий код вызовет сообщение об ошибке: 

    if (expression) { 
       int count = 0;	// Объявление переменной в области действия 
                                   // оператора if 
       ......
   } 
   cout « "count == " « count « '\n';   // ??? 

    При компиляции последняя строка вызовет сообщение об ошибке, поскольку 
переменная count объявлена вне области видимости оператора if, она не 
существует вне ее. 
    Бывает полезно одновременно объявлять и инициализировать переменную, 
управляющую циклом for. В ANSI С типичный цикл выглядит так: 

    void f() 
    { 
       int i; 
       for (i=0;  i<MAX; !++ ) 
            .......
    } 

    В C++ можно объявить переменную для цикла непосредственно внутри него, 
сократив код до следующего: 

     void f() 
    { 
       for (int i=0;   i<MAX;  i++) 
          .....
     }

    Однако такое объявление действует не только в пределах видимости цикла 
for. Инициализация управляющей переменной (в данном случае i=0;) выполняется 
до того, как начнется цикл, таким образом, i в данном примере попадает в 
область видимости f() и может использоваться любыми операторами, следующими за for. 

    Константы 

    Константы — новшество языка C++, заимствованное языком ANSI С. Объявление 
переменных с ключевым словом const создает вокруг них "силовое поле", 
предохраняющее их от изменения во время выполнения программы. Если вы объявили 
целую переменную count следующим образом: 

    const int count =1234; 

 Компилятор выдаст сообщение об ошибке в операторе 

    count++;	// ??? 

    Некоторые программисты рекомендуют использовать объявления const вместо 
символьных констант, созданных с помощью #define. Символьная константа 

    #define MAX 100 

определяет макрос с именем МАХ, связанный с текстом 100 (не целым 
значением 100). Если МАХ используется следующим образом: 

    for (int i=0;  i<MAX;  i++) 


    Глава 12. Введение в C++     287 

 то компилятор заменит МАХ на текстовые цифры 100 и скомпилирует оператор 
так, как будто вы ввели эти цифры в этом месте. 
    Некоторые эксперты по C++ утверждают, что легко сделать ошибку при 
использовании символьных констант, подобных МАХ. Сторонники объявления const 
скорее предложат вам следующее: 

    const int MAX = 100; 

    Цикл for останется таким же, а объявление МАХ истинной константой даст 
определенные преимущества. 

    •   Компилятор сможет выполнить проверку типов с МАХ более строго. C++ 
распознает, что константа МАХ имеет тип int, в то время как компилятор ничего 
не знает о типе текстового макроса МАХ. 

    •  Turbo Debugger распознает истинную константу МАХ. Отладчик не 
распознает символьные константы, созданные с помощью #define. 

    Все это необходимо иметь в виду. К несчастью, истинная константа МАХ 
нуждается в постоянном месте хранения в сегменте данных программы. 
Многочисленные программные константы могут занимать сотни тысяч байтов, 
расходуя тем самым память. Более того, использование таких значений может 
потребовать отнимающих время обращений к памяти. Обычно символьные константы 
используются как эффективные команды, загружающие значения прямо в регистры. 
    Истинные константы полезны в том случае, если они используются как 
переменные, значения которых в процессе выполнения программы не подлежат 
изменению. Конечно, их преимущества не настолько велики, чтобы совсем 
отказываться от #define. 

    Встраиваемые функции 

    Одной из печальных истин программирования на языках, подобных С и C++, 
является то, что на вызов функций расходуется время. Хотя вызов функции 
происходит быстрее, чем вы успеете глазом моргнуть, многочисленные вызовы 
могут ухудшить общее быстродействие программы. Полный отказ от функций — 
неприемлемое решение этой проблемы! Функции делают программу модульной и более 
легкой для поддержки. Без функций исключительно сложно, может быть, невозможно 
написать даже среднюю по размерам корректно работающую программу. 
    С другой стороны, разворачивание циклов и устранение многочисленных 
вызовов функций — стиль, сберегающий время и улучшающий общее быстродействие 
программы. Рассмотрим следующий гипотетический цикл for: 

    for (int i = 0;   i < MAX;   i++ ) { 
       AnyFunction(); 
    } 

   Если значение MAX слишком велико, многократные вызовы Any Function() могут занять 
достаточно много драгоценного времени вдобавок к общему времени выполнения программы. 
Предположим, AnyFunction() написана следующим образом: 

    void AnyFunction(void) { 
       cout « AnyValue « '\n';
       AnyValue++; 
    }

    Все это, конечно, предположительно, но один факт не вызывает сомнений: 
вставка тела AnyFunctionO непосредственно в цикл for ускорит выполнение программы: 

    for (int i = 0; i < MAX; i++) 
          cout « AnyValue « '\n'; 
          AnyValue++; 
    }

    Сбереженное время эквивалентно времени выполнения МАХ вызовов функции. Еще 
лучше полностью развернуть цикл, убрав оператор цикла for: 

    cout « AnyValue « '\n'; 
    AnyValue++; 
    cout « AnyValue « '\n'; 
    AnyValue++; 
    cout « AnyValue « '\n'; 
    AnyValue++; 


    288	Часть III. Программирование на ANSI C++ 


    Неважно, что придется ввести МАХ*2 операторов, и, если МАХ достаточно 
велико, скомпилированный код приобретет слоновьи пропорции. Не имеет значения, 
насколько вы улучшите быстродействие программы — на одну или две миллисекунды! 
    Нелепо? Да, несмотря на важность проблемы сбережения времени отказ от 
функций и циклов — слишком высокая цена за такое небольшое "улучшеньице". К 
счастью, C++ предлагает альтернативное решение этой проблемы. Вместо 
уничтожения вызовов функций вы можете объявить функции inline (встраиваемыми), 
вставляя тем самым их тела в поток кода. Как это сделать, показано в листинге 12.9. 

    Листинг 12.9.  INLINE.CPP (демонстрация встраиваемых функций) 

    1:   #include <iostream.h> 
    2:
    3:  inline int max(int a,   int b) 
    4:         { 
    5:            if (a >= b) 
    6:                 return a; 
    7:             else 
    8:                return b; 
    9:          } 
    10: 
    11:	main() 
    12:	{ 
    13:	  int х, у, z; 
    14: 
    15:	  cout « "X? "; 
    16:	  cin » x; 
    17:	  cout « "Y? "; 
    18:	  cin » y;                                            
    19:	  z = max(x, У); 
    20:	  cout « "max(a, b) == " « z « '\r\'; 
    21:	  return 0; 
    22:	}

    Для того чтобы объявить функцию встраиваемой, напишите перед ней ключевое 
слово inline, как показано в строке 3. Пишется функция как обычно (строки 
4-9). Ничего необычного в ее содержимом нет — все, что выполняется в любой 
функции, выполнится и для объявленной встраиваемой. Обычно встраиваемые 
функции, "подобные той, что определена в строках 3-9, объявляются в 
заголовочных файлах и включаются в каждый модуль, где необходимо их 
использование. Встраиваемую функцию необходимо полностью определить до того, 
как можно будет ее использовать, и включение ее текста в заголовочный файл — 
самый простой способ удовлетворения этого требования. 
    Используйте встраиваемые функции в точности так же, как вы используете 
обычные. Например, в строке 19 вызывается мах() для определения большего из 
двух целых чисел с присвоением этого значения переменной z. Конечно, в 
скомпилированном коде Borland C++ не вызывает функцию mах() в строке 19. 
Вместо этого компилятор вставляет тело функции непосредственно в программу, 
компилируя строку 19, как если бы она была написана следующим образом: 

    if (x >= у) 
      z = х; 
   else 
      z = у; 

    Время, сэкономленное в этом примере, минимально. Однако, если max() 
вызывается тысячи раз в цикле, быстродействие программы может существенно улучшиться. 

    ЗАМЕЧАНИЕ 

    Встраиваемые функции сродни регистровым переменным. Когда вы объявляете 
встраиваемую функцию, вы лишь даете указание компилятору, и, если возможно, 
тело функции будет вставлено прямо туда, откуда эта функция вызывается. 
Разумеется, компилятор — не золотая рыбка, и нет гарантия, что так будет с 
каждой вашей командой. Если встраиваемый код, к примеру, слишком велик, то 
компилятор может отказаться вставить функцию в поток кода, и вместо этого 
сгенерирует обычный вызов. Также при компиляция программ для Turbo Debugger 
все встраиваемые функции будут преобразованы в обычные вызываемые функции, 
которые можно будет отлаживать. Хорошие программы на C++ работают корректно 
вне зависимости от того, как скомпилированы в них встраиваемые функции. 


    Глава 12. Введение в C++         289 
    10 Освоение Borland® C++4.5 

    Управление памятью с помощью new и delete 

    Вы можете использовать одинаковую технику управления памятью в программах 
на ANSI С и C++. Функции malloc(), farmalloc(), farcalloc(), free(), farfree() и 
другие одинаково доступны для всех программ на С и C++. Однако C++ предлагает 
альтернативные операторы управления памятью new и delete, которые могут делать 
то же самое, что и стандартные функции, и более того. 
    Используйте new для выделения памяти динамическим переменным. Используйте 
free (?!), чтобы освободить память, выделенную new, для ее использования в 
последующих операторах new. Имейте в виду, что new и delete — унарные 
операторы, а не функции. Этот факт важен, поскольку, как вы узнаете в 
следующем разделе, C++ дает возможность переопределять большинство операторов, 
включая new и delete. Использование этих операторов вместо стандартных функций 
управления памятью дает возможность при необходимости самостоятельно 
обрабатывать ситуации управления памятью. Эти операторы также играют важную 
роль в объектно-ориентированном программировании, которому посвящена глава 13. 
    Чтобы выделить память для переменной, имеющей тип double, и присвоить ее 
адрес указателю, вы можете написать 

    double *dp = new double; 

    Вы можете часто видеть схожее объявление, делающее вызов new похожим на 
вызов функции: 

    double *dp = new(double); 

    Это не так, и лишние круглые скобки просто игнорируются. Используйте dp 
так же, как и любой другой указатель на память, выделенную malloc() или 
подобной функцией. Для присваивания значения содержимому памяти, адрес которой 
хранится в dp, напишите 

    •dp = 3.14159; 

    Закончав использование динамической переменной, удалите ее следующим образом: 

    delete dp; 

    Удаление указателя означает освобождение выделенной ранее памяти, адрес 
которой хранится в dp, для последующего использования. Для освобождения 
памяти, выделенной с помощью new, необходимо использовать только delete. 
Динамические переменные, созданные функцией malloc(), следует уничтожать с 
помощью функции free() (или, возможно, farfree(). Вы можете использовать 
malloc() и new в одной и той же программе, но вы не можете смешивать способы 
управления памятью ANSI С и C++. 

    Обнаружение ошибок, связанных с нехваткой памяти 

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

    •   new возбуждает исключительную ситуацию; 
    •   new возвращает нуль. 

    В старых версиях C++ оператор new в любом случае возвращал нуль в случае 
неудачной попытки выделения памяти, обычно вследствие нехватки свободного 
пространства. В новейших версиях ANSI C++ спецификации языка позволяют new 
возбуждать исключительную ситуацию — прием, который будет объяснен позже, в 
главе 15. Пока вы не узнаете больше о C++, преждевременно говорить об 
исключительных ситуациях, и лучше использовать new как и ранее, 
перепрограммировав его так, чтобы в случае ошибки выделения памяти возвращался нуль. 
    Для этого включите заголовочный файл NEW.H в вашу программу и добавьте 
следующий оператор в main() или в любое другое место до первого использования new: 

    set_new_handleг(0); 

    Функция set_new_handler() устанавливает адрес обработчика (подпрограммы 
времени исполнения), используемой new. Обработчик по умолчанию заставляет new 
возбуждать исключительную ситуацию для ошибок. 

    СОВЕТ

    При переносе имеющихся программ на C++ в среду Borland C++ 4.3, возможно, 
будет необходимо вызвать set_new_haadler( 0 ) в начале программы для обеспечения 
ее правильной работы. Это изменение следует внести в программу в том случае, 
если она непредвиденно завершится с сообщением "abnormal termination" 
(аварийное завершение), которое может быть вызвано возбуждением new 
исключительной ситуации в случае ошибки выделения памяти. 
    

    290	Часть III. Программирование на ANSI C++ 
	

    Листинг 12.10 поясняет, как использовать new для выделения памяти для 
структур, а также демонстрирует, что происходит, когда программе не хватает 
свободной памяти. Вы можете скомпилировать и запустить программу как 
EasyWin-приложение из интегрированной оболочки или же из командной строки DOS. 
В обоих случаях компилятор выдаст предупреждение "Unreachable code" 
(Недостижимый код). (В следующем разделе разъяснено, в каких случаях выдается 
это предупреждение.) 

    Листинг 12.10.  MEHERR.CPP (демонстрация new и ошибок,  связанных с 
нехваткой памяти) 

    1:	#include	<iostream.h> 
    2:	#include	<new.h> 
    3:	#include	<stdlib.h> 
    4: 
    5:	struct q	{ 
    6:	  int ia[1024]; // 2048-байтовая структура 
    7: 	};
    8.	 
    9:	main() 
    10:	{ 
    11:	struct q	*qp; // Указатель на структуру q 
    12: 
    13:	set new handler(O); // Указание new вернуть О в случае неудачи 
    14:	for (;;)  { // Повторять следующий оператор "без конца" 
    15:	   qp = new q; // Выделить новую структуру типа q 
    16:	   cout «	qp, « ' \n ' ; //, Вывести адрес структуры 
    17:	   if (qp == 0) { // В случае возврата new 0 следует: 
    18:	      cout « "Out of memory\n"; // Вывести сообщение об ошибке 
    19:	     exit(1);	// Прервать программу с кодом завершения 1 
    20:	  } 
    21:	} 
    22:	return 0	;// В этой строке выдается предупреждение "unreachable code" 
    23:	}
 
    В качестве программных данных в строках 5-7 объявляется структура с 
массивом 1,024 целых значений. При обработке этой структуры new выделяет 
2,048-байтовый блок (учитывая тот факт, что целое значение занимает два 
байта). Повторяя эту операцию до бесконечности, программа исчерпывает запасы 
свободной памяти, демонстрируя, что случается, когда new не может выделить ее достаточно. 
    Чтобы new возвращал нуль в случае ошибки, программа передает 0 функции 
set_new_handler(). После этого "бесконечный" цикл for выполняет несколько 
операторов. Первый из них вызывает new для выделения 2048-байтовой структуры и 
присваивает ее адрес указателю qp. Далее оператор вывода отображает адрес, 
хранящийся в qp. Если qp — нуль, то это означает, что new не сумел выделить 
достаточно памяти, программа выводит сообщение об ошибке и затем аварийно 
завершает выполнение с помощью вызова функции exit(). Вот что отобразилось на 
дисплее при запуске этой программы (адреса могут отличаться от тех, что будут 
выведены на вашем экране): 

    0x1366 
    0x1Ь8а 
    ...........
    0x0000 
    Out of memory 

    Если программа не вызывает set_new_handler(), new возбуждает 
исключительную ситуацию вместо того, чтобы вернуть нуль. Для демонстрации 
этого отличия, закомментируйте или удалите оператор set_new_handler(), 
скомпилируйте программу заново и запустите ее. Теперь программа выведет: 

    0x1366 
    0x1Ь8а 
    .......
    0x0000 
    Abnormal program termination 

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

    ЗАМЕЧАНИЕ 

    Поскольку по умолчанию new вызывает исключительную ситуацию, программы в 
этом и последующих разделах не будут осуществлять проверку на правильность 
возвращаемого new значения, как это было в большинстве программ, приведенных в 
первом дадании этой книги. Все ошибки, связанные с нехваткой амяти, вызывают 
аварийное завершение с выводом сообщения "Abnormal program tenainatjon" (Аварийное 


    Глава 12. Введение в C++     291 


завершение). Это удобно для небольших демонстрационных программ, но 
неприемлемо для коммерческих приложений. Для того чтобы узнать, как 
обрабатывать эти и другие исключительные ситуации без аварийных завершений, 
следует обратиться к главе 15. 

    Использование простых динамических переменных 

    Хотя MEMERR.CPP демонстрирует, что происходит при нехватке памяти, в 
обычных обстоятельствах эта программа — не лучший пример использования new. 
Например, использование "бесконечного" цикла for — плохой стиль 
программирования, хотя в данном случае необходимо было вызвать ошибку 
выделения памяти независимо от того, сколько оперативной памяти у вашего 
компьютера. Из-за этого цикла компилятор выдает предупреждение о том, что 
оператор return в строке 22 "недостижим". Это предупреждение означает, что 
компилятор обнаружил отсутствие пути, ведущего к выполнению одного или 
нескольких операторов в программе, что указывает на возможно неправильный 
оператор в каком-либо месте программы. 
    В обычных условиях следует использовать new для выделения памяти 
переменным, структурам или массивам, как показано ниже. Например, вы можете 
объявить структуру как в MEMERR: 

    struct q { 
    int ia[1024];	// 2048-байтовая структура 
    }; 

Затем использовать new для выделения памяти для переменной типа q: 

    struct q *qp;	// Объявление указателя на переменную типа структуры q 
    qp =     new q;                    // Выделение памяти для переменной типа структуры q 

    Если new завершился успешно, то qp содержит адрес переменной типа 
структуры q. Вы можете использовать переменную точно так же, как и обычную 
такого же типа. Когда вы завершили использование динамической переменной, 
следует освободить занимаемую ею память с помощью оператора delete. 
Освобожденная память в дальнейшем будет доступна для будущих использований new: 

    delete qp;  // Удаление и освобождение памяти,  адресованной qp 

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

    ЗАМЕЧАНИЕ 

    При завершении Программы вся наделенная память автоматически 
освобождается. Тем не менее, считается хорошим тоном удалять динамические 
переменные по окончании их использования. 

    Между прочим, всегда неплохо удалять (delete) указатель, даже если он 
равен нулю. Конечно, после удаления указателя память, адресовавшаяся им 
прежде, более не будет зарезервирована для программного использования. 
Удаление того же самого указателя повторно или использование адресованной им 
памяти может вызвать серьезные ошибки. Никогда не делайте так, как показано ниже: 

    double *dp;	// Объявление указателя на вещественное значение 
    dp = new double;	// Выделение памяти и присвоение ее адреса dp 
    *dp = 3.14159;	// Присваивание значения выделенной памяти 
    delete dp;	// Удаление выделенной памяти. Пока все хорошо 
    *dp = 9.9999;	// ??? Серьезная ошибка!  Не делайте этого! 
    delete dp;               // ??? Серьезная ошибка!  Разрушает кучу 

    Приведенный фрагмент корректен, исключая два последних оператора. После 
удаления dp нельзя повторно использовать указатель, поскольку удаленная память 
может быть зарезервирована для другой переменной. Повторное удаление 
указателя, как это произошло в последнем операторе, — также серьезная ошибка, 
так как это может повредить кучу, дважды добавив один и тот же блок памяти в 
список свободного пространства в этой области. Обязательное правило — удалив 
указатель, никогда не используйте его повторно ни для каких целей. Конечно, вы 
можете повторно использовать указатель для адресации памяти заново выделенной new: 

    dp = new double;	// Выделение памяти для динамической переменной 
    *dp = 3.14159;	// Присваивание значения динамической переменной 
    delete dp;	// Удаление памяти.  Не используйте dp после этого! 
    ....................
    dp = new double;  // Конечно,  вы можете выделить память для еще одной 
	                 // переменной 
    *dp = 9.9999;       // Присваивание значения заново выделенной переменной 
    delete dp;            // Удаление памяти.  Опять же,   не используйте затем dp! 


    292     Часть III. Программирование на ANSI C++ 

    Использование динамических строк

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

    Листинг 12.11. NEWSTR.CPP (объявление строк с помощью new)

    1:	#include <iostream.h>
    2:
    3:	#define SIZE 80
    4:
    5:	main()
    6:	{
    7:
    8:	  char *sp
    9:	  sp = new char[SIZE];
    10;	  cout «	"String?";
    11:	  cin.getlinefsp, SIZE);
    12:	  cout « "You entered:" « sp « '\n';
    13:	  delete[]	sp;
    14:	  return 0
    15:	}

    Строка 9 выделяет символьный массив размером SIZE в байтах и присваивает
адрес первого байта этого массива sp. Строки 10-12 демонстрируют использование
динамических строк, запрашивая и выводя строку текста.
    Строка 13 удаляет динамический символьный массив. Borland C++ разрешает
написать этот оператор без скобок следующим образом:

    delete sp; // ???, но безопасно в Borland C++

    Следуя более строгому протоколу C++, необходимо использовать пустые скобки
в выражении delete[], указывающие компилятору, что удаляется массив, а не
простая переменная. (В Borland C++ скобки необходимы только в том случае, если
удаляется массив классовых объектов, но об этом позже.)
    В ранних версиях C++ при удалении массива нужно было указывать количество
элементов. В C++ версии 2.0, например, строка 15 имела бы вид delete(SIZE]
sp;. Borland C++ игнорирует SIZE в таком случае и выдает предупреждение о
попытке использовать устаревшую форму delete.

    Использование динамических массивов

    Конечно, строки — это те же символьные массивы, и тот факт, что new и
delete также могут использоваться для создания и освобождения динамических
массивов любого типа, не будет для вас сюрпризом. Листинг 12.12 демонстрирует это.

    Листинг 12.12. NEWARRAY.CPP (объявление массивов с помощью new)

     1:     #include <iostream.h>
     2:
     3:     #define COUNT 100 // Количество целых значений
     4:
     5:     main()
     6:     {
     7:     int *array; // Массив целых значений
     8:     int i; // Индекс массива
     9:
    10:     array = new int[COUNT];
    11:
    12:     // Заполнение массива
    13:     for (i = 0;  i < COUNT;  i++)
    14:     array[i] = i;
    15:
    16:     // Вывод содержимого массива
    17:     for (i = 0;  i < COUNT;  i-н-) {
    18:       cout.width(e);
    19:       cout « arrayfi];
    20:     }
    21:     delete[] array;
    22:     return 0;
    23:     }

    В этом примере в строке 7 объявляется указатель на массив целых значений.
В строке 10 используется new для выделения места для 100 целых значений. Затем
массив заполняется значениями от 0 до 99 и выводится в


    Глава 12. Введение в C++     293 


    8-символьных столбцах с помощью цикла for в строках 17-20. Строка 21 
удаляет массив, используя пустые скобки в выражении delete[] для того, чтобы, 
как и в предыдущем листинге, указать компилятору, что удаляется массив, а не 
одно целое значение. 

    Многомерные динамические массивы 

    Для объявления указателя на двух- или трехмерный массив (или большей 
размерности) укажите количество элементов во второй и последующей позициях. 
Например, вы хотите выделить пространство для массива 10x20 вещественных 
значений двойной точности. Вы начинаете с объявления 

    int (*matrix)[20]; 

    Оно определяет matrix, как указатель на массив из 20 целых. Вы должны 
заключить в круглые скобки (*matrix), поскольку квадратные скобки массива 
имеют больший приоритет, чем символ указателя *. Целых значений еще даже не 
существует; все что вы сделали — только дали указание компилятору, что matrix 
— указатель на массив определенного размера. Чтобы выделить память для матрицы 
размером 10x20 и присвоить ее адрес переменной matrix, следует написать такой оператор: 

    matrix = new int[10][20]; 

    Оператор new выделяет память для массива. Адрес, получившийся в 
результате, присваивается matrix. Для создания куба 8x8x8 следует написать следующее: 

    int (*matrix)[8][8]; 
    matrix = new int[8][8][8]; 

    Такое объявление matrix определяет вторую и третью размерность массива, 
указывая компилятору, что matrix адресует массив целых значений размерностью 
8x8. Выражение new определяет все три измерения, выделяя пространство для 
указанного числа массивов 8x8, другими словами — трехмерного куба. 
    При выделении пространства для многомерного массива первый индекс может 
быть переменной, а второй и последующие должны быть константами. Например, 
если целая переменная v равна 40, то следующие строки 

    int (*matrix)[20]; 
    matrix = new int[v][20]; 

выделяют пространство для двухмерного массива размерностью 40x20 элементов 
целых значений, адресуемого matrix. Удаление многомерных массивов любой 
размерности происходит так, как будто они имеют лишь одно измерение: 

    delete[] matrix; 


    Перегрузка функций 

    Даже если вы только начали программировать, наверняка вы столкнулись с 
проблемой придумывания новых имен для функций. Конечно, вы можете придумать 
любое имя для функции — компилятор воспримет с одинаковым энтузиазмом функцию 
как с именем f29q(), так и названную BattingAverage(). Многие, конечно, 
предпочтут последний вариант. 
    В больших программах с этим шутить не стоит. Представим себе программу, 
которая должна рисовать несколько различных фигур. Каждая функция отрисовки 
имеет свое собственное имя, что делает код "усыпанным" функциями типа 
DrawEllipse, DrawCircle, DrawSquare, DrawLine и т.д. Код, вероятно, будет 
содержать также много переменных, названных ellipse, circle, square и line, и, 
в конце концов, мы получим программу, словно написанную заикой: 

    DrawEllipse(ellipse); 
    DrawCircle(circle);
    DrawSquare(square); 
    OrawLine(line); 

    Не будет ли лучше, если вы используете одно и то же имя функции (назовем 
ее Draw) для вывода всех фигур? Тогда вы сможете просто написать: 

    Draw(ellipse); 
    Draw(circle); 
    Draw(square); 
    Draw(line); 

    Подобной ясности в программах можно добиться, используя перегрузку 
функций. В C++ многочисленные функции могут иметь одинаковое имя до тех пор, 
пока они отличаются хотя бы одним параметром. Функции "перегружены", поскольку 
их имена одинаковы, а выполняемая ими работа — различна. Многие функции Draw в 
нашей гипотетической графической программе остаются раздельными и различными, 
и написаны точно так же, как и другие не перегруженные функции. Но компилятор 
C++ распознает их по тому, как они используются, а не только по их именам. 


    294	Часть III. Программирование на ANSI C++ 

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

    Листинг 12.13.  OVERLOAD.CPP (перегрузка функций)

    1:      #include <iostream.h>
    2:
    3:      int square(int a);
    4:      double square(double a);
    5:      long double square(long double a);
    6:
    7:      main()
    8:      {
    9:      int x = 10;
    10:     double у = 20.5;
    11:     long double z = 30.75;
    12:
    13:     cout « square(x) « '\n';
    14:     cout « square(y) « '\n';
    15:     cout « square(z) « '\n';
    16:     return 0;
    17:     }
    18:
    19:     int squarefint a)
    20:     {
    21:     return a * a;
    22:     }
    23:
    24:     double square(double a)
    25:     {
    26:     return a * a;
    27:     }
    28:
    29:     long double square(long double a)
    30:     {
    31:     return a * a;
    32:     }

    Прототипы трех перегруженных функции square() определены в строках 3-5. Все
функции имеют одинаковое имя, но, тем не менее, они различны, поскольку кажда
из них отличается хотя бы одним параметром от другой. Сами функции реализованы
в строках 19-32 как раздельные, т.е. имеющие различные имена.
    В строках 13-15 показано, как перегруженные функции могут прояснять суть
программы. Несмотря на различия в типах данных, очевидно, что каждый оператор
выводит квадрат аргумента. Это куда более приятно, чем различать функции по
именам типа squarelnt(), squareDouble() и squareLongDouble(). Конечно, решать,
стоит ли перегружать функции, которые выполняют более или менее схожую работу,
придется вам. Если вы назовете все функции в вашей программе одним и тем же
именем (с учетом того, что они должны отличаться хотя бы одним своим
аргументом), то ваш код может стать совершенно непостижимым, как новелла,
представляющая собой многократное повторение одного и того же слова.

    Аргументы функций по умолчанию

    Важное свойство C++ — возможность передачи аргумента в функцию по
умолчанию. Оно используется, если при вызове функции необходима только часть
параметров, а не все. Предположим, вам необходима функция, которая возвращает
сумму четырех целых значений, подобная следующей:

    int sum(int a,  int b,  int c,  int d)
{
    return a + b + с + d;
}

    Для вызова функции только с двумя аргументами (назовем их vl и v2) следует
дополнить нулями два недостающих параметра:

    cout « sum(v1,  v2,  0,  0);

    Это ограничение невелико, но оно заставляет вас разбираться в документации
по функции sum() для того, чтобы определить, какими значениями дополнить
неиспользуемые параметры. Ошибочное употребление -1 или другого значени
вместо параметров с и d, что вполне может допустить невнимательный
программист, заставит функцию вернуть неправильную сумму.


    Глава 12. Введение в C++     295 

    Аргументы функций по умолчанию придуманы для защиты от таких случаев, они
дополняют необходимые значения для незаданных аргументов. Для объявлени
значения аргумента по умолчанию в прототипе функции завершите параметр функции
знаком равенства и необходимым значением:

    int sum(int a,  int b,  int с = 0,  int d = 0);

    Подобное объявление sum() позволяет вызывать ее не только с двумя, но и с
большим числом аргументов — до четырех. Значения по умолчанию должны идти
последними в списке параметров функции и могут появляться только в прототипе
функции. Если не указаны аргументы для с и d, то они замещаются значениями по
умолчанию, указанными в прототипе функции. Теперь допустимы следующие операторы:

    cout « sum(1,  2);		// а == 1,  b == 2,   с == 0,  d == О
    cout « sum(1.  2,  3);	// а == 1,  b == 2,  с == 3,  d == О
    cout « sum(1,  2, 3, 4);	// а == 1, b == 2, с == 3, d == 4

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

    «*** Error: You goofed big time! **** «<«« Press any key to continue >»»»

    Скомпилируйте CENTER. CPP из листинга 12.14 как Easy Win- или
DOS-приложение. Запустите программу и введите строку, выводящуюся несколькими
способами функцией CenterO, которая заодно демонстрирует использование
аргументов по умолчанию.

    Листинг 12.14.  CENTER.CPP (использование аргументов функции по умолчанию)

    1:      #include <iostream.h>
    2:      #include <string.h>
    3:
    4:      #define SIZE 128
    5:
    6:      void Center(const char *instr, char «outstr,
    7:                           int width = 0, char IFill = '-', char rFill = '-');
    8:
    9:      main()
    10:     {
    11:     char instr[SIZE]; // Вводимая строка
    12:     char outstr[SIZEJ; // Выводимая строка
    13:     int length; // Длина строки в символах
    14:
    15:     cout « "Enter a string:";
    16:     cin.getline(instr, SIZE / 2);
    17:     length = strlen(instr);
    18:
    19:     Center(instr, outstr);
    20:     cout « outstr « '\n';
    21:     Center(instr, outstr, length + 8);
    22:     cout « outstr « '\n';
    23:     Center(instr, outstr, length + 16, '*', '*');
    24:     cout « outstr « '\n';
    25:     Center(instr, outstr, 78, '<', '>');
    26:     cout « outstr « '\n';
    27:
    28:     return 0;
    29:     }
    30:
    31:     // instr: Указатель на входную строку
    32:     // outstr: Указатель на выходную строку
    33:     // width: Окончательная желаемая длина выходной строки
    34:     // fill: Символ для заполнения выходной строки
    35:
    36:     void Center(const char *instr,  char *outstr,
    37:                           int width, char IFill, char rFill)
    38:     {
    39:     if (outstr == 0) // Выходная строка не должна быть нулевой
    40:        return;           // Если нулевая - немедленное возвращение
    41:
    42:     if (instr == 0) { // Если входная строка нулевая,
    43:          *outstr = 0; // Выводим пустую строку
    44:                             // и возвращаемся. }
    45:          return;
    46:     }
    47:     // Вычисление .переменных для заполнения выходной строки



    296     Часть III. Программирование на ANSI C++ 


    48:	int len = strlen(instr); 
    49:	if (width < len) 
    50:	   width = len; 
    51:	int wd2 =	(width - len) / 2; 
    52: 
    53:	int i; //	Индекс строки и	управляющая переменная для цикла for 
    54: 
    55:	for ( i = 0; i < wd2; i++) 
    56:	   outstr[i]	= IFill; 
    57:	for ( /*i= i*/; i < wd2 +	len; i++) 
    58:	   outstr[ij	= instr[i - wd2]	• 
    59:	for (/*i	= i*/; i < width;	i++) 
    60:	   outstr[i]	= rFill; 
    61:	outstr[i]	= 0; // Завершаем строку нулем 
    62:	} 

    В программе объявляется прототип функции Center() с тремя аргументами по умолчанию: 

    void Center(const char *instr, char *outstr, int width = 0,  char IFill = '-',  char rFill = '-'); 

    Первые два параметра — адреса входной и выходной строки. Входная строка 
объявлена с ключевым словом const для предохранения ее от изменения в теле 
функции. Последние три параметра определены со значениями по умолчанию, 
которые будут их инициализировать при пропуске соответствующих аргументов в 
операторе, в котором вызывается функция Center(). Как показано в программе, 
первый вызов функции должен быть только с двумя аргументами: 

    Center(instr,  outstr); 

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

    Ссылки 

    В главе 9 вы узнали, как создавать указатели, являющиеся альтернативными 
способами доступа к другим переменным путем их адресации. В следующих строках 

    int i = 1234; 
    int *p = &i; 

объявляется целая переменная i, инициализированная значением 1234, и 
указатель на целое р, адресующий i. В соответствии с этими объявлениями 
следующие два оператора выводят значение i: 

    cout « "i == " « i « '\n'; 
    cout « "i == " « *p «'\n'; 

    Выражение *р указывает компилятору, что следует использовать значение, 
адресуемое р. 
    Использование указателей в качестве альтернативного способа доступа к 
переменным таит в себе опасность — если оператор изменил адрес, хранящийся в 
указателе, то этот указатель более не ссылается на то же значение. Если такое 
изменение было внесено непреднамеренно, результатом его может быть серьезная 
ошибка. Кроме того, использование указателей ведет к написанию программ, 
пестрящих символами-звездочками. 
    C++ предлагает альтернативу для более безопасного доступа к переменным 
через указатели. Объявив ссылочную переменную, вы создадите объект, который, 
как указатель, ссылается на другое значение, но, в отличие от указателя, 
постоянно привязан к своему alter ego ("второму я"). Ссылка на значение всегда 
ссылается на это значение.
    Ссылки, в основном, используются в качестве переменных, параметров функций 
и результатов функций. 

    Ссылки в качестве переменных 

    Как переменная ссылка ссылается на другое значение. Вероятно, на практике 
вы не захотите использовать ссылки таким образом, но приведенная ниже 
программа демонстрирует работу со ссылками. Скомпилируйте и запустите листинг 
12.15 как Easy Win- или DOS-приложение. В программе приводятся четыре способа 
адресации и вывода целого значения. 

    Листинг 12.15. REFVAR.CPP (демонстрация ссылочных переменных) 

    1:     #include <iostream.h> 
    2: 
    3:     main() 
    

    Глава 12. Введение в С++     297 


    4:	{ 
    5:	int ivar = 1234;	// Переменной присвоено значение 
    6:	int *iptr = &ivar	;    // Указателю присвоен адрес ivar 
    7:	int &iref = ivar;	// Ссылка ассоциирована с ivar 
    8:	int *p = &iref;	// Указателю присвоен адрес iref 
    9: 
    10:	cout « "ivar ==	" « ivar « '\n' ; 
    11:	cout « "*iptr ==	" « *iptr « '\n' ; 
    12:	cout « "iref ==	" « iref « '\n'; 
    13:	cout « "*p == "	« *p « '\n' ; 
    14:	return 0; 
    15:	} 

    В строках 5-8 объявляются четыре переменные. Первая — простое целое ivar, 
инициализированное значением 1234. Следующая. — указатель на целое с именем 
iptr, которой присвоен адрес ivar. Третья объявлена как ссылочная переменная. Строка 

    int &iref = ivar; 

объявляет iref как ссылку на целое значение. Что касается iref, то ей 
присваивается ivar не как значение переменной, а как адрес в памяти. Следуя 
объявлению, iref указывает на ivar подобно указателю. Как показано в строке 
12, оператор 

    cout « "iref == " « iref « '\n'; 

выводит значение ivar — 1234. Это происходит благодаря использованию iref 
в качестве ссылки на местоположение переменной ivar в памяти. 
    Четвертое объявление в строке 8 создает еще один указатель — р, которому 
присваивается адрес, хранящийся в iref. Строки 6 и 8 дают одинаковый 
результат. В обеих строках создаются указатели, ссылающиеся на ivar. Как 
показано в строке 13, употребление указателя в выражении *р дает доступ к 
значению ivar. На рис. 12.2 проиллюстрирована взаимосвязь переменных из 
листинга 12.15. Как видно из иллюстрации, использование iref очень похоже на 
использование разыменования указателя *р. В обоих выражениях осуществляется 
доступ к значению ivar — 1234 


    Рис. 12.2. Взаимосвязь переменных в листинге 12.15, REFVALCPP 


    При использовании ссылок следует помнить одно правило: однажды 
инициализировав ссылку, ей нельзя присвоить другое значение. В REFVAL.CPP вы 
не можете объявить новую целочисленную переменную и присвоить ее адрес iref: 

    int anotherlnt;        
    iref = anotherlnt;       // ??? 

    Это присваивание не заставит iref ссылаться на anotherlnt. Это выражение 
присвоит значение anotherlnt объекту, на который iref ссылается, другими 
словами — ivar. Так же вы можете присваивать новые значения объекту, к 
которому относится ссылка. Например, выражение 

    iref = 4321; 

присвоит ivar значение 4321. Эти правила будет легче запомнить, если 
представить себе ссылки как неизменяемые альтернативные имена, похожие на 
указатели (но не идентичные им!). 
    Как следует из вышесказанного, ссылки должны быть инициализированы при их 
объявлении. Строка 7, к примеру, не может быть написана Как 

    int &iref;       // ??? 

    В отличие от указателей, которые могут быть объявлены в 
неинициализированном состоянии или установлены в нуль (если указатель не 
ссылается на необходимые данные), ссылки всегда ссылаются на объект. Для 
ссылок не существует аналога нулевого указателя. 

    ЗАМЕЧАНИЕ 
    
    Ссылки нельзя инициализировать значениями в следующих четырех особых 
случаях: при их объявления о ключевым словом extern, при использовании в 
качестве параметров функции, при использовании в качестве типа возвращаемого 
значения функции или в объявлениях классов (см. главу 13). 


    298     Часть III. Программирование на ANSI C++ 


    Не существует операторов, непосредственно производящих действия над 
ссылками. Все операции совершаются только над соответствующими объектами. 
Добавьте между строками 13 и 14 листинга 12.15 следующий оператор: 

    iref++ ; 

    Компилятор сгенерирует код, инкрементирующий ivar - переменную, на которую 
ссылается iref. Оператор не совершит никаких действий непосредственно над iref. 
    Ссылки также могут ссылаться на константы. Разумеется, вы не можете 
присвоить значение непосредственно ссылке. Объявление 

     int &iref = 1234;	// ??? 

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

    int х = 1234;	// Объявление и инициализация целочисленного объекта
   const   int &iref = x;           // Объявление iref как константной ссылки на х 

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

    cout « "iref = " « iref « '\п';        // Все в порядке 

    Вы также можете изменить значение переменной х: 

    х = 4321;        // Тоже годится 

    Поскольку iref — константа, то вы, конечно, не сможете присвоить новое 
значение х через ссылку: 

    iref = 4321;   // ??? Нельзя 

    Попытка скомпилировать последнее выражение приведет к выдаче сообщения об 
ошибке "Cannot modify a const object in function main()" (Нельзя изменить 
константный объект в функции mainO). 

    Ссылки в качестве параметров функций 

    Несмотря на интригующие свойства, сами по себе ссылочные переменные 
используются довольно редко. Вместо того чтобы использовать ссылку для доступа 
к другой переменной, легче использовать саму эту переменную. 
    Конечно, в качестве параметров функции ссылки имеют более широкое 
применение и предлагают полезную альтернативу доступа к параметрам. Ссылки 
особенно полезны в функциях, возвращающих несколько объектов, например два или 
более вводимых значения. Листинг 12.16 — измененная версия программы TAX. С из 
главы 5 — демонстрирует этот трюк. Скомпилируйте и запустите программу как 
Easy Win- или DOS-приложение, затем введите сумму платежей и налогов после их 
запроса. Программа выведет цену и сумму налога на введенные вами значения. 

    Листинг 12.16. ТАХ.СРР (использование ссылок в качестве параметров функции) 

    1: #include <iostream.h> 
    2: 
    3: #include <stdlib.h> 
    4: #include <stdio.h> 
    5; 
    6: double GetDouble(const char *prompt);             
    7: void GetData(double Spaid, double irate);   
    8: void Calculate(const double paid, const double rate, 
    9;  double &list, double &tax); 
    10: 
    11: main ()
    12: { 
    13:  double paid, rate, list, tax; 
    14:  GetData(paid, rate); 
    15:  Calculate(paid, rate, list, tax); 
    16:  printf('List pride = $%8.2f\n", list); 
    17:  printf("Tax paid = $%8.2f\n", tax); 
    18:  return 0; 
    19: } 
    20: 
    21: // Возвращает одно вещественное значение двойной точности 
    22: double GetDouble(const char «prompt) 
    23: { 
    24:  char s[20]; // Строка для ввода 
    25:  printf (prompt); // Отображение строки запроса 
    26:  scanf ("%20s", s); // Ввести данные как строку 
    27:  return atof(s); // Возвратить введенные данные 
    28:  } 
    

        Глава 12. Введение в C++     299 


    29: 
    30:	// Ввод цены и ставки налога вещественными значениями двойной точности 
    31:	// Замечание: изменяет значение paid и rate 
    32:	void GetData(double &paid, double &rate) 
    33:	{ 
    34:	paid = GetDouble("Price paid?"); 
    35:	rate = GetDouble("Tax rate (ex: .06)?"); 
    36:	} 
    37: 
    38:	// Вычисляет цену и налог по сумме и ставке 
    39:	// Замечание: изменяет значение list и tax                             
    40:	void Calculate(const double paid, const double rate, 
    41:	double &list, double &tax) 
    42:	{ 
    43:	list = paid / (1 + rate); 
    44:	tax = paid - list; 
    45:	}

    В программе определены три функции. Первая функция запрашивает и 
возвращает вещественное значение двойной точности — обычное описание функции, 
возвращающей некоторое значение: 

    double GetDouble(const char *prompt); 

    Вторая функция демонстрирует использование ссылочных параметров для 
возврата двух или более значений. Заметьте, что обоим параметрам функции 
предшествует символ &, означающий, что эти параметры передаются по ссылке 
(т.е. в GetData передаются их адреса вместо значений): 

    void GetData (double &paid, double &rate); 

    Рассмотрим тело функции GetData. В нем дважды вызывается GetDouble с 
аргументом-строкой, которая должна отображаться, и полученные значения 
присваиваются ссылочным параметрам paid и rate: 

    paid - GetDouble("Price paid?"); 
    rate = GetDouble("Tax rate (ex: .06)?"); 

    Поскольку paid и rate — ссылки, значения присваиваются объектам, 
переданным функции GetDouble(). Например, в функции main() следующий оператор 
вводит значения в две переменные, также названные paid и rate: 

    GetData(paid, rate); 


    ЗАМЕЧАНИЕ 

    Некоторые критикуют передачу параметров функций gj ссылке, верно замечая, 
что такие вызовы выглядят обманчиво, поскольку аргументы в них передаются по 
ссылке неявно. Ключ к решению этой проблемы и эффективному использованию 
ссылочных параметров — хорошее документироваяие программ. Обязательно должен 
присутствовать комментарий, в котором указывается, что вызов GetData изменяет 
значения paid и rate 

    Третья, и последняя, функция в примере вычисляет цену и налог к уплате, 
основываясь на действительной цене и ставке налога. Первых два параметра, paid 
и rate, — входные параметры функции. Поскольку функция не изменяет их, они 
объявлены константами, и передаются только их значения (точнее, копии их 
значений). Вторых два параметра, list и tax, — это выходные значения функции. 
Эти значения изменяются функцией и, следовательно, передаются по ссылке: 

    void Calculate(const double paid, const double rate, double &list,  double &tax) 

    Функция Calculate присваивает новые значения list и tax в следующих операторах: 

    list = paid / (1 + rate);	
    tax = paid - list;                     

    Поскольку параметры — ссылки, в действительности используются переменные, 
переданные функции Calculate(). Операция присваивания происходит по ссылке, 
вследствие чего изменяются действительные переменные, когда в функции main 
происходит следующий вызов: 

    Calculate(paid, rate, list, tax); 

    Аргументы, передающиеся по значению, невозможно отличить от аргументов, 
передающихся по ссылке. Хорошее документирование функции Calculate уменьшит 
опасность подобных ошибок. 
    Ссылки и указатели в качестве параметров тесно связаны. В самом деле, 
ссылки могут интерпретироваться компилятором C++ как указатели. Для того чтобы 
вникнуть в суть этой идеи, рассмотрим следующую небольшую функцию: 

    void f(int *ip) 
    *ip = 1234; 


    300     Часть III. Программирование на ANSI C++ 


    Внутри этой функции осуществляется доступ к переданному аргументу, адрес 
которого хранится в указателе ip, с помощью оператора вида 

    f(&ivar);   // Передача адреса ivar 

    Внутри функции выражение *ip = 1234; присваивает значение 1234 переменной 
ivar, адрес которой передается функции f(). Теперь рассмотрим аналогичную 
функцию, использующую ссылочные параметры: 

    void f(int &ir) 
    ir = 1234; 

    Указатель ip заменен ссылкой ir, которой функция присваивает 1234. Выражение 

    f(ivar);   // Передача ivar по ссылке 

присваивает значение ссылочному объекту: передает ivar no ссылке функции 
f(), и поскольку ir ссылается на ivar, то ivar присваивается 1234. 

    Ссылки в качестве результатов функций 

    Функции могут возвращать ссылки на объекты при условии, что эти объекты 
существуют, когда функция неактивна. (Другими словами, функции не могут 
возвращать ссылки на локальные автоматические переменные). Для функции, 
объявленной как 

    double &ref(double d); 

необходим один вещественный аргумент двойной точности, и она возвращает 
ссылку на объект того же типа, предположительно объявленный где-то в другом месте. 
    Листинг 12.17 демонстрирует использование ссылочных функций для скрытия 
внутреннего представления структуры данных. Это используется при реализации 
больших проектов, особенно когда структуры данных могут изменяться, что, в 
свою очередь, может послужить причиной коренной переделки программ, написанных 
на обычном ANSI С. Ссылочные функции в C++ могут облегчить этот рутинный 
процесс, обеспечив доступ к структурам данных, подобным массивам, без привязки 
кода к физическим свойствам данных. Скомпилируйте и запустите программу, по 
приглашению введите индексное значение от 0 до 9, затем — вещественное 
значение. Для завершения работы программы нажмите по приглашению <Enter>. 

    Листинг 12.17.   REFFUNC.CPP (ссылочные функции)

    1:	&include <iostream.h> 
    2:	&include <stdlib.h> 
    3:	&include <string.h> 
    5:	&define FALSE 0 
    6:	&define TRUE 1 
    7:	&define SIZE 10 
    8:	&define BUFLEN 20 
    9: 
    10:	double &ref(int index); 
    11:	void ShowArray(void); 12: 
    13:	double array[SIZE]; 14: 
    15:	main() 
    16:	{ 
    17:	int done = FALSE, index; 
    18:	char buffer[BUFLEN]; 
    19: 
    20:	for (index = 0; index < SIZE; index++) 
    21:	   ref(index) = index; // Присваивание ссылочной функции! 
    22:	while (Idone) { 
    23:	  ShowArray(); 
    24:	  cout « "\nEnter index from 0 to 9, Enter to quit:"; 
    25:	  cin.getline(buffer, BUFLEN); 
    26:	  done = (strlen(buffer) ==0); 
    27:	  if (Idone) { 
    28:	    index = atoi(buffer); 
    29:	    cout « "Enter floating-point value:"; 
    30:	    cin,getline(buffer, BUFLEN); 
    31:	    ref(index) = atof(buffer); 
    32:	  } 
    33:	} 
    34:	return 0; 
    35:	} 


    Глава 12. Введение в C++     301 

    36:
    37:	double &ref(int index) 
    38:	{ 
    39:	  if ((index < 0) | | (index >= SIZE)) 
    40:	    index = 0; 
    41:	  return array[index]; //	Вернуть ссылку на элемент массива 
    42:	} 
    43: 
    44:	void ShowArray(void) 
    45:	{ 
    46:	  cout « "\nArray :\n"; 
    47:	  for (int i = 0; i < SIZE ; i++) 
    48:	     cout << "[" « i « "] == " « array[i] « '\n'; 
    49:	} 

    Глобальный массив вещественных значений двойной точности объявляется в 
строке 13. В больших по размерам программах он может находиться в отдельном 
модуле. Хотя программа отображает значения и вставляет их в этот массив, это 
происходит без непосредственных ссылок на глобальную структуру данных в 
функции main(). Благодаря ссылочным функциям можно изменить внутреннее 
содержимое вещественных значений двойной точности (возможно, используя связный 
список вместо массива), и программа останется рабочей без малейших изменений в 
функции main().
    В строке 10 объявляется ссылочная функция с именем ref(). Она возвращает 
ссылку на вещественный объект двойной точности, идентифицированный 
целочисленным параметром-индексом. Поскольку функция возвращает ссылку на 
вещественный объект двойной точности, имя функции может оказаться слева от 
оператора присваивания, как показано в строке 21. Вероятно, это самое частое 
употребление ссылочных функций. Оператор в строке 21 присваивает значение 
вещественному объекту двойной точности, на который ссылается ref(index). 
Конкретное внутреннее представление этого объекта несущественно. Ссылочная 
функция скрывает внутреннее представление данных. 
    В строке 31 приводится еще один способ использования ссылочных функций. 
Вещественный результат двойной точности из функции atof() присваивается функции 
ref(index). Если ref — массив, этот оператор может быть написан так: 

    ref(index) = atof(buffer); 

    Изменения в механизме хранения данных потребуют соответствующих изменений 
в коде, которые, в свою очередь, потребуют дополнительных проверок, отладки и 
т.д. Ссылочные функции могут облегчить подобные нежелательные изменения. 
    Ссылочная функция приводится в строках 37-42. Оператор if обеспечивает 
значения индекса в области допустимых значений, после чего оператор return в 
строке 41 возвращает ссылку на одно из вещественных значений двойной точности 
в глобальном массиве. Важно разобраться, что в строке 41 не возвращается 
вещественное значение двойной точности. Возвращается ссылка на объект этого 
типа. Если бы ref() возвращала вещественное значение двойной точности, то она 
не могла бы использоваться в левой части оператора присваивания, как это 
произошло в main(). 

    Резюме 

    •   Язык C++ — нечто большее, чем объектно-ориентированная версия языка С. 
C++ имеет много возможностей, улучшающих и расширяющих С. Многое из того, что 
вы узнали о ANSI С, применимо и к C++, и большинство программ, написанных на 
ANSI С, совместимы с C++. Конечно, самые передовые возможности C++ не 
поддерживаются в ANSI С. 
    •   При компиляции файлов, содержащих исходные тексты программ, расширение 
.СРР указывает Borland C++, что эти программы написаны на языке C++. 
Расширение заголовочных файлов в данном случае не имеет значения, хотя 
некоторые программисты, пишущие на C++, предпочитают называть свои 
заголовочные файлы с расширением .НРР. В этой книге используются .Н. 
    •   C++ распознает все ключевые слова, операторы и другие элементы языка 
ANSI С. В C++ также имеется несколько новых ключевых слов и операторов в 
дополнение к ANSI С. 
    •   В C++ необходимо указывать прототипы всех функций. Прототип функции 
должен предшествовать ее вызову. 
    •   Язык C++ имеет следующие особенности: проверка типов осуществляется 
более строго, сильно ослаблена потребность в typedef-объявлениях, определение 
char означает выделение 8-битового байта. В C++ такие литеральные символы, как 
'X', имеют тип char. В ANSI С литеральные константы имеют тип int. 
    •   Символ комментария // следует использовать в C++ для того, чтобы 
начать комментарий, продолжающийся до конца строки. Комментарии C++ и ANSI С 
(скобки /* и */) могут использоваться вперемежку в одной программе. 
Комментарии C++ могут быть вложены внутрь комментариев ANSI С. 
    
    
    302	Часть III. Программирование на ANSI C++ 


   •  Потоки ввода-вывода исполъзуют операторы » (направить в) и « (взять из) 
для записи и чтения данных. Выходной поток cout связан со стандартным 
системным выводом. Входной поток cin связан со стандартным вводом. Для вывода 
сообщений об ошибках на дисплей следует использовать выходной поток сегг. 
Наиболее безопасный способ чтения символьной строки в буфер — вызов 
функции-члена входного потока get(). Для чтения строки из системного 
стандартного ввода следует использовать cin.get() или cin.getlineO. 
Функции-члены принадлежат классам. Классы — один из главных вкладов C++ в 
программирование (см. главу 13). 
   •  Для установки ширины вывода в последующих операторах вывода в поток 
следует использовать такие функции-члены, как cout.width(). Для указания 
десятичного, шестнадцатеричного и восьмеричного формата вывода следует 
использовать модификаторы dec, hex и oct. Вы можете также использовать 
стандартные функции ( такие, как sprintf ) для подготовки строки к записи в 
выходной поток. 
   •  В C++ объявления могут появляться в любом месте любого блока операторов. 
Например, вы можете объявить переменную внутри скобок цикла for. Область 
видимости переменной ограничена операторным блоком, в котором она определена. 
Разумеется, вы должны объявлять переменные до их использования. Если имя 
локальной переменной совпадает с именем глобальной, локальное имя скрывает имя 
глобальной переменной. Вы можете использовать оператор разрешения области 
видимости ::, чтобы получить доступ к глобальной переменной. Если локальная 
переменная названа count, то выражение ::count ссылается на глобальную переменную. 
   •  Некоторые эксперты в программировании на C++ рекомендуют использовать 
идентификатор const вместо #define для объявления символьных констант. В этом 
случае компилятор сможет осуществить проверку соответствия типов данных 
константных объектов в выражениях. Кроме того, Turbo Debugger распознает 
имена, определенные с помощью const, а не #define. Глобальные константные 
значения, конечно, занимают пространство в сегменте данных, а их использование 
требует времени для обращения к памяти. На практике, несмотря на 
опубликованные советы, программисты на C++, напротив, используют #define так 
же успешно, как и программисты на С. 
   •  Встраиваемые функции вставляются непосредственно в скомпилированный код 
вместо вызова функции.. Для того чтобы избавиться от вызовов функций, 
расходующих время внутри циклов, которые должны выполняться наиболее быстро, 
следует использовать встраиваемые функции. Учтите, что ключевое слово inline, 
подобно register, является лишь рекомендацией компилятору, а не жестким 
указанием. Встраиваемые функции могут встраиваться, а могут и нет. 
(Встраиваемые функции никогда не компилируются встроенными, когда генерируется 
код для отладки.) 
    • Для выделения и удаления динамических переменных в куче следует 
использовать операторы new и delete. В программах на C++ операторы new и 
delete используются вместо обычных функций распределения памяти, подобных 
malloc(), calloc(), free() и др. Вы можете продолжать использовать эти функции в 
программах на C++. Память, выделенная new, конечно, должна быть освобождена 
delete. Память, выделенная с помощью функции malloc(), должна освобождаться 
функцией free(). 
    • Имена функций могут быть перегружены. В программе могут использоваться 
несколько функций с одинаковыми именами в том случае, если они отличаются хотя 
бы одним своим аргументом. Перегрузка функций помогает сокращать потребность в 
таких уникальных именах функций, как DrawCircle и DrawEllipse. Однако на вас 
ложится ответственность за рациональность использования перегрузки имен. 
Наверное, было бы неразумно назвать все функции одним и тем же именем! 
   •  Не указанные в вызове параметры снабжаются значениями с помощью аргументов 
функций по умолчанию. Объявлять значения по умолчанию следует после всех 
других параметров, приведенных в прототипе функции. 
   • Ссылка, объявленная с помощью &, ссылается на объект. В отличие от 
указателей, которые также могут ссылаться на объект, ссылка должна быть 
инициализирована при объявлении (за исключением отдельных случаев), и не может 
ссылаться в дальнейшем ни на какой другой объект. 
   • Ссылки обычно используются для объявления параметров функции, которые 
ссылаются на фактические аргументы, переданные в эту функцию. Таким образом, 
ссылки похожи на параметры-указатели, но они проще в использовании. Кроме 
того, иногда ссылки применяются в качестве возвращаемого значения функции. Это 
можно использовать при скрытии деталей реализации внутренней структуры данных, 
что позволяет проводить в ней изменения без соответствующих исправлений в коде. 

    Упражнения 

    12.1. Используя потоки ввода-вывода, напишите программу-фильтр, 
переводящую строчные буквы в заглавные. 
    12.2. Исправьте листинг 12.4 так, чтобы запрос повторялся до тех пор, пока 
не будет обнаружена ошибка во входном потоке. 


    Глава 12. Введение в C++     303 