8.2.3. Инициализация по умолчанию
Возможно, вы заметили, что мы часто не инициализируем объекты классов
string
,
vector
и т.д. Рассмотрим пример.
vector<string> v;
string s;
while (cin>>s) v.push_back(s);
Это не противоречит правилу, утверждающему, что переменные перед их использованием должны быть проинициализированы. В данном случае, если мы не задаем начальные значения, происходит инициализация строк и векторов по умолчанию. Таким образом, вектор
v
пуст (т.е. не содержит элементов), и строка
s
перед входом в цикл также пуста (
""
). Механизм, гарантирующий инициализацию по умолчанию, называется
конструктором по умолчанию (default constructor).
К сожалению, язык С++ не предусматривает инициализацию по умолчанию для встроенных типов. Лишь глобальные переменные (см. раздел 8.4) по умолчанию инициализируются нулем, но их использование следует ограничивать. Большинство полезных переменных, к которым относятся локальные переменные и члены классов, не инициализируются, пока не указано их начальное значение (или не задан конструктор по умолчанию).
Не говорите, что вас не предупреждали!
8.3. Заголовочные файлы
Как управлять объявлениями и определениями? Они должны быть согласованными. В реальных программах могут быть десятки тысяч объявлений; программы с сотнями тысяч объявлений тоже не редкость. Как правило, когда вы пишете программу, большинство используемых определений написано не вами. Например, реализации потока
cout
и функции
sqrt()
были написаны много лет назад кем-то другим. Мы просто используем их. Главным средством управления сущностями, определенными где-то в другом месте, в языке С++ являются заголовки. В принципе
заголовок (header) — это коллекция объявлений, записанных в файле, поэтому заголовок часто называют
заголовочным файлом (header file). Такие заголовки подставляются в исходные файлы с помощью директивы
#include
. Например, вы можете решить улучшить организацию исходного кода нашего калькулятора (см. главы 6 и 7), выделив объявления лексем в отдельный файл. Таким образом, можно определить заголовочный файл
token.h
, содержащий объявления, необходимые для использования классов
Token
и
Token_stream
.
Объявления классов
Token
и
Token_stream
находятся в заголовке
token.h
. Их определения находятся в файле
token.cpp
. В языке C++ расширение
.h
относится к заголовочным файлам, а расширение
.cpp
чаще всего используется для исходных файлов. На самом деле в языке С++ расширение файла не имеет значения, но некоторые компиляторы и большинство интегрированных сред разработки программ настаивают на использовании определенных соглашений относительно расширений файлов.
В принципе директива
#include "file.h"
просто копирует объявления из файла
file.h
в ваш файл в точку, отмеченную директивой
#include
. Например, мы можем написать заголовочный файл
f.h
.
// f.h
int f(int);
А затем можем включить его в файл
user.cpp
.
// user.cpp
#include "f.h"
int g(int i)
{
return f(i);
}
При компиляции файла
user.cpp
компилятор выполнит подстановку заголовочного файла и скомпилирует следующий текст:
int f(int);
int g(int i)
{
return f(i);
}
Поскольку директива
#include
выполняется компилятором в самом начале, выполняющая ее часть компилятора называется
препроцессором (preprocessing) (раздел A.17).
Для упрощения проверки согласованности заголовок следует включать как в исходные файлы, использующие объявления, так и в исходные файлы, содержащие определения, соответствующие этим объявлениям. Это позволяет компилятору находить ошибки на самых ранних этапах. Например, представьте себе, что разработчик функции
Token_stream::putback()
сделал ошибки.
Token Token_stream::putback(Token t)
{
buffer.push_back(t);
return t;
}
Этот фрагмент выглядит вполне невинно. К счастью, компилятор перехватывает ошибки, потому что он видит (благодаря директиве
#include
) объявление функции
Token_stream::putback()
. Сравнивая это объявление с соответствующим определением, компилятор выясняет, что функция
putback()
не должна возвращать объект класса
Token
, а переменная
buffer
имеет тип
Token
, а не
vector<Token>
, так что мы не можем использовать функцию
push_back()
. Такие ошибки возникают, когда мы работаем над улучшением кода и вносим изменения, забывая о необходимости согласовывать их с остальной частью программы.
Рассмотрим следующие ошибки:
Token t = ts.gett(); // ошибка: нет члена gett
// ...
ts.putback(); // ошибка: отсутствует аргумент
Компилятор немедленно выдаст ошибку; заголовок
token.h
предоставляет ему всю информацию, необходимую для проверки.
Заголовочный файл
std_lib_facilities.h
содержит объявления стандартных библиотечных средств, таких как
cout
,
vector
и
sqrt()
, а также множества простых вспомогательных функций, таких как
error()
, не являющихся частью стандартной библиотеки. В разделе 12.8 мы продемонстрируем непосредственное использование заголовочных файлов стандартной библиотеки.