аргументов и тип возвращаемого значения), но лишь определение создает тело функции (выполняемые инструкции). Обратите внимание на то, что тело функции хранится в памяти как часть программы, поэтому правильно будет сказать, что определения функций и переменных выделяют память, а объявления — нет.
Разница между объявлением и определением позволяет разделить программу на части и компилировать их по отдельности. Объявления обеспечивают связь между разными частями программы, не беспокоясь об определениях. Поскольку все объявления должны быть согласованы друг с другом и с единственным объявлением, использование имен во всей программе должно быть непротиворечивым. Мы обсудим этот вопрос в разделе 8.3. А здесь мы лишь напомним о грамматическом анализаторе выражений из главы 6: функция expression() вызывает функцию term(), которая, в свою очередь, вызывает функцию primary(), которая вызывает функцию expression(). Поскольку любое имя в программе на языке С++ должно быть объявлено до того, как будет использовано, мы вынуждены объявить эти три функции.
double expression(); // это лишь объявление, но не определение
double primary()
{
// ...
expression();
// ...
}
double term()
{
// ...
primary();
// ...
}
double expression()
{
// ...
term();
// ...
}
Мы можем расположить эти четыре функции в любом порядке, потому что вызов одной из функций всегда будет предшествовать ее определению. Таким образом, необходимо предварительное объявление. По этой причине мы объявили функцию expression() до определения функции primary(), и все было в порядке. Такие циклические вызовы весьма типичны.
Почему имя должно быть определено до его использования? Не могли бы мы просто потребовать, чтобы компилятор читал программу (как это делаем мы), находил определение и выяснял, какую функцию следует вызвать? Можно, но это приведет к “интересным” техническим проблемам, поэтому мы решили этого не делать. Спецификация языка С++ требует, чтобы определение предшествовало использованию имени (за исключением членов класса; см. раздел 9.4.4).
Помимо всего прочего, существует обычная практика (не программирования): когда вы читаете учебники, то ожидаете, что автор определит понятия и обозначения прежде, чем станет их использовать, в противном случае читатели будут вынуждены постоянно догадываться об их смысле. Правило “объявления для использования” упрощает чтение как для людей, так и для компилятора. В программировании существует и вторая причина, по которой это правило имеет большую важность. Программа может состоять из тысяч строк (а то и сотен тысяч), и большинство функций, которые мы хотим вызвать, определены “где-то”. Это “где-то” часто является местом, куда мы даже не собираемся заглядывать. Объявления, которые описывают только способ использования переменной или функции, позволяет нам (и компилятору) не просматривать огромные тексты программ.
8.2.1. Виды объявлений
Программист может объявить множество сущностей в языке С++. Среди них наиболее интересными являются следующие.
• Переменные.
• Константы.
• Функции (см. раздел 8.5).
• Пространства имен (см. раздел 8.7).
• Типы (классы и перечисления; см. главу 9).
• Шаблоны (см. главу 19).
8.2.2. Объявления переменных и констант
Объявление переменной или константы задает ее имя, тип и (необязательно) начальное значение. Рассмотрим пример.
int a; // без инициализации
double d = 7; // инициализация с помощью синтаксической конструкции =
vector<int> vi(10); // инициализация с помощью синтаксической
// конструкции ()
Полная грамматика языка описана в книге Язык программирования С++ Страуструпа и в стандарте ISO C++.
Константы объявляются так же, как переменные, за исключением ключевого слова const и требования инициализации.
const int x = 7; // инициализация с помощью синтаксической
// конструкции =
const int x2(9); // инициализация с помощью синтаксической
// конструкции ()
const int y; // ошибка: нет инициализации
Причина, по которой константа требует инициализации, очевидна: после объявления константы она уже не может изменить свое значение. Как правило, целесообразно инициализировать и переменные; переменная, не имеющая начального значения, способна вызвать недоразумения. Рассмотрим пример.
void f(int z)
{
int x; // неинициализированная переменная
// ...здесь нет присваивания значений переменной x...
x = 7; // присваивание значения переменной x
// ...
}
Этот код выглядит вполне невинно, но что будет, если в первом пропущенном фрагменте, отмеченном многоточием, будет использована переменная x? Рассмотрим пример.
void f(int z)
{
int x; // неинициализированная переменная
// ...здесь нет присваивания значений переменной x...
if (z>x) {
// ...
}
// ...
x = 7; // присваивание значения переменной x
// ...
}
Поскольку переменная x не инициализирована, выполнение оператора z>x может привести к неопределенным последствиям. Сравнение z>x приведет к разным результатам на разных компьютерах и даже на одном и том же компьютере в разных сеансах работы. В принципе оператор z>x может вызвать прекращение работы программы из-за машинной ошибки, но чаще всего ничего не происходит, и мы получаем непредсказуемые результаты.
Естественно, такое непредсказуемое поведение программы нас не устраивает, но если мы не проинициализируем переменные, то в итоге произойдет ошибка.
Напомним, что “глупые ошибки” (которые происходят при использовании неинициализированных переменных) происходят из-за спешки или усталости. Как правило, компиляторы пытаются предупредить программистов, но в сложных программах — в которых такие ошибки и появляются чаще всего — они не могут выловить все такие ошибки. Существуют люди, не привыкшие инициализировать переменные. Часто это происходит потому, что они учили языки, в которых этого не требовалось; вы можете встретить такие примеры в будущем. Пожалуйста, не усложняйте себе жизнь, забывая инициализировать переменные при их определении.
8.2.3. Инициализация по умолчанию
Возможно, вы заметили, что мы часто не инициализируем объекты классов string, vector и т.д. Рассмотрим пример.
vector<string> v;
string s;
while (cin>>s) v.push_back(s);
Это не противоречит правилу, утверждающему, что переменные перед их использованием должны быть проинициализированы. В данном случае, если мы не задаем начальные значения, происходит инициализация строк и векторов по умолчанию. Таким образом, вектор v пуст (т.е. не содержит элементов), и строка s перед входом