Справочное руководство по C++ - Бьярн Страустрап
Шрифт:
Интервал:
Закладка:
Из всего этого следует, что массивы в C++ хранятся по строкам (последний индекс изменяется быстрее всего), а значение первого индекса из описания позволяет вычислить размер памяти, необходимой для массива, однако при вычислении индексного выражения первый индекс роли не играет.
R.8.2.5 Функции
В описании T D, в котором D имеет вид
D1 ( список-описаний-параметров ) список-спецификаций-cv opt
описываемый идентификатор имеет тип "…список-спецификаций-cv функция с параметрами типа список-описаний-параметров возвращающая T".
список-описаний-параметров:
список-описаний-парам opt … opt
список-описаний-парам , …
список-описаний-парам:
описание-параметра
список-описаний-парам , описание-параметра
описание-параметра:
спецификации-описания описатель
спецификации-описания описатель = выражение
спецификации-описания абстрактный-описатель opt
спецификации-описания абстрактный-описатель opt = выражение
Если список-описаний-параметров завершается эллипсисом (…), про число параметров известно только то, что оно больше или равно числа заданных параметров, если список параметров пуст, то функция параметров не имеет. Список параметров void эквивалентен пустому списку параметров. Не считая этого случая, void не может быть типом параметра (хотя типы, получаемые из void, такие как void*, допустимы).
R.8.3 Определения функций
Определения функций имеют вид
определение-функции:
спецификации-описания opt описатель инициализатор-ctor тело-функции
тело-функции:
составной-оператор
Конструкция описатель из определения-функции должна содержать описатель вида
D1 ( список-описаний-параметров ) список-спецификаций-cv opt
в соответствии с определениями из §R.8.2.5.
Формальные параметры относятся к области видимости самого большого блока тела-функции.
Приведем пример полного определения функции.
int max(int a, int b, int c)
{
int m = (a › b) ? a : b;
return (m › c) ? m : c;
}
Здесь int представляет спецификации-описания, max(int a, int b, int c) - описатель, а {/*… */} - тело-функции.
Конструкция инициализатор-ctor используется только в конструкторах, см. §R.9.3.1 и §R.12.6.
Конструкция список-спецификаций-cv может участвовать: в описании нестатической функции-члена, в определении нестатической функции-члена или в описании указателя на функцию-член, см. §R.9.3.1. Она относится к типу функции.
Отметим, что неиспользуемым формальным параметрам имена можно не давать, например,
void print(int a, int)
{
printf("a = %dn",a);
}
R.8.4 Инициализаторы
За описателем может идти начальное значение описываемого идентификатора.
инициализатор:
= выражение-присваивания
= { список-инициализаторов , opt }
( список-выражений )
список-инициализаторов:
выражение-присваивания
список-инициализаторов, выражение-присваивания
{ список-инициализаторов , opt }
Автоматические, регистровые, статические и внешние переменные можно инициализировать произвольными выражениями, содержащими константы и описанные ранее переменные и функции.
int f(int);
int a = 2;
int b = f(a);
int c(b);
Указатель типа const T*, т.е. указатель на константу T, может инициализироваться указателем типа T*, но инициализация для указателей в обратном порядке незаконна. Объекты типа T можно инициализировать объектами типа T независимо от использования спецификаций const или volatile в типах инициализируемой переменной или инициализатора, например,
int a;
const int b = a;
int c = b;
const int* p0 = &a;
const int* p1 =&b;
int* p2 = &b; // ошибка: указатель без const
// настраивается на объект const
int *const p3 = p2;
int *const p4 = p1; // ошибка: указатель без const
// настраивается на объект const
const int* p5 = p1;
Здесь причина обеих ошибок одна: если допустить подобную инициализацию, она позволит изменять с помощью указателя без соответствующей спецификации значение чего-то, что было описано как const.
На выражения для стандартных значений параметров накладывается больше ограничений, см. §R.8.2.6.
Инициализация объектов классов с помощью конструкторов описывается в §R.12.6.1. Копирование объектов классов описывается в §R.12.8. Порядок инициализации статических объектов определяется в §R.3.4 и §R.6.7.
Гарантируется, что переменные статического класса памяти (§R.3.5), которые не были инициализированы, в качестве начального значения получат 0, приведенный к нужному типу. То же справедливо для статических членов объектов класса. Начальные значения автоматических и регистровых переменных, которые не были инициализированы, неопределены.
Если инициализатор относится к указателю или объекту арифметического типа, он состоит из одного выражения (возможно в скобках). В качестве начального значения объекта берется значение выражения, происходят такие же преобразования типа, как и в случае присваивания.
Заметим, что поскольку () не является инициализатором, описание
X a();
задает не объект a типа класс X, а является описанием функции без параметров, возвращающей X.
Инициализатор для статического члена принадлежит области видимости члена класса, например,
int a;
struct X {
static int a;
static int b;
};
int X::a = 1;
int X::b = a; // X::b = X::a
R.8.4.1 Агрегат
Агрегатом называется массив или объект типа класс (§R.9), не имеющий конструкторов (§R.12.1), частных или защищенных членов (§R.11), базовых классов (§R.10) и виртуальных функций (§R.10.2). Если агрегат инициализируется, то инициализатором должен быть список-инициализаторов, который состоит из заключенного в фигурные скобки списка, разделенного запятыми, инициализаторов для членов агрегата. Инициализаторы идут в возрастающем порядке индексов или членов агрегата. Если агрегат содержит вложенные агрегаты, это правило применяется рекурсивно для членов вложенных агрегатов. Если инициализаторов в списке меньше, чем членов агрегата, то он дополняется нулевыми значениями соответствующих типов.
Например, в следующем фрагменте
struct S {int a; char* b; int c;}
S ss = {1, "asdf"};
ss.a инициализируется значением 1, ss.b - "asdf", а ss.c - 0.
Кроме того, агрегат, являющийся классом, можно инициализировать объектом этого класса или класса, являющегося общим производным от него (§R.12.8).
Фигурные скобки разбираются следующим образом. Если список-инициализаторов начинается левой фигурной скобкой, то список инициализаторов, разделенных запятыми, задает значения членам агрегата, причем считается ошибкой, если инициализаторов больше, чем членов. Иначе, если список-инициализаторов или вложенный агрегат не начинается левой фигурной скобкой, то из списка используется такое число элементов, которое нужно для инициализации членов текущего агрегата; все оставшиеся элементы используются для инициализации членов следующего агрегата, в который вложен текущий агрегат.
Например, в определении
int x[] = {1, 3, 5};
массив x инициализируется как одномерный массив из трех элементов, поскольку размер массива не указан, и приведено три инициализатора.
Приведем пример инициализации с полной скобочной структурой.
float y[4][3] = {
{1, 3, 5},
{2, 4, 6},
{3, 5, 7},
};
Здесь значения 1, 3, 5 инициализируют первую строку массива y[0], т.е. y[0][0], y[0][1] и y[0][2]. Аналогично, следующие две строки инициализируют y[1] и y[2]. Инициализаторы приведены не полностью, поэтому y[3] инициализируется нулями. Точно такого же результата можно достичь с помощью такой инициализации:
float y[4][3] = {
1, 3, 5, 2, 4, 6, 3, 5, 7,
};
Последний (самый правый) индекс изменяется быстрее всего.
В последнем примере инициализатор для y начинается левой фигурной скобкой, но для y[0] скобки не задано, поэтому из списка используется три элемента, также по три последовательных элемента используется для y[1] и y[2]. В следующем примере
float y[4][3] = {
{1}, {2}, {3}, {4}
};
инициализируется первый столбец y (который рассматривается как двумерный массив), а остальные столбцы принимают значение 0.