1.3. Определение данных

Передача параметров по ссылке. Ссылочной переменной называется переменная, которая служит псевдонимом для другой переменной. Например,

int   a = 3;

int &x = a; // x – псевдоним для а

x = a * x;

 — в результате х = а = 9.

Тип ссылочной переменной должен совпадать с типом переменной, для которой она служит псевдонимом, например, объявления

float a = 3;

int &x = a; // ошибка

содержат ошибку, ибо тип переменной х – целый, а тип а – с плавающей точкой. Запрещаются также двойные ссылки, например,

int &&x = a; //ошибка,

указатель-ссылка, например,

int &*p = &b; // ошибка,

массив-ссылка, а также ссылка-битовое поле, например,

int &x : 1; //ошибка.

Пример

float &Pi = 3.14;

- объявлена ссылочная переменная Pi, представляющая неявную вспомогательную переменную типа float, которой присвоено начальное значение 3.14.

Пример

 int  a[] = {-1, 0, 1};

 int *&p = a;

- объявлена ссылочная переменная p, как  псевдоним имени массива.

Ссылочные переменные применяются для изменения значений аргументов подпрограмм. Известно, что аргументы при вызове подпрограмм передаются через стек – перед вызовом они записываются в системный стек, а при возврате из подпрограммы восстанавливаются из стека, например, если вызвать подпрограмму

void swap(int a, int b)

{

int t = a; a = b; b = t;

}

с помощью оператора swap(x, y), то значения переменных х и у не изменятся. Если же аргументы определить как ссылочные:

void swap(int &a, int &b)

{

int t = a; a = b; b = t;

}

то вызов подпрограммы с помощью swap(x, y) приводит к перестановке переменных х и у, ибо через стек теперь будут передаваться не сами переменные, а их адреса.

Модификатор константы. Переменная, описанная как const, недоступна в других модулях программы, ее нельзя изменять во время выполнения программы. Единственная возможность присвоить ей значение – это инициализация при определении. Указатель, определенный с модификатором const нельзя изменить, однако, может быть изменен объект, который адресуется этим указателем. Например, при объявлении и выполнении оператора:

char* const p = buffer;

*p = ’x’;

по адресу buffer будет записан символ «х». А если объявить р как указатель на строку констант

const char *p = buffer;

то аналогичная операция *p =’x; будет ошибкой. Таким образом, модификатор const означает, что объект этого типа не может изменяться ни непосредственно, ни через указатель на него.

Адрес объекта, объявленного как const, не может быть присвоен указателю на переменные, которые могут изменяться, ибо в этом случае объект постоянного типа можно изменять через указатель. Например,

const char space = ’A’;

const char *p = &space; // верная запись

char *q = &space;       // ошибка

Упражнение. Учитывая объявления

char  c;                 const char cc=’a’;

char  *pc;               const char *pcc;

char  *const cpc=&c;     const char *const cpcc=&cc;

char  *const *pcpc;

определить, какие из приведенных ниже присваиваний верные, а какие нет:

c=cc;                    cc=c;             pcc=&c;          

pcc=&cc;                 pc=&c;            pc=&cc;

pc=pcc;                        pc=cpc;           pc=cpcc;

cpc=pc;                        *cpc=*pc;         pc=*pcpc;

**pcpc=*pc;              *pc=**pcpc;

Ответ: Неверны присваивания cc=c; pc=&cc; pc=pcc; pc=cpcc; cpc=pc; (остальные присваивания верны).

Модификатор const применяется в тех случаях, когда аргументы функции – ссылочные переменные, используемые для того, чтобы избежать копирования аргументов (которые могут быть достаточно большими), не предназначенных для модификации. Например, операция умножения объявляется как

complex operator*(const complex& z, const complex& w);

что приводит к передаче адресов объектов в подпрограмму умножения с сохранением всех остальных свойств передачи параметров как в Си.

Модификатор volatile сообщает компилятору, что переменная может быть изменена некоторым фоновым процессом, например, подпрограммой обработки прерываний или портом ввода – вывода. Это ключевое слово запрещает компилятору делать

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

Volatile int ticks;

Void timer()

{

ticks++;

}

void wait(int intervat)

{

ticks=0;

while(ticks<interval);

}

Предположим, что обработчик прерываний timer() надлежащим образом связан с аппаратным прерыванием от часов реального времени. Процедура wait() реализует цикл ожидания, пока значение ticks не станет равным интервалу времени, заданному параметром. Компилятор Си++ обязан перезагружать значение целой переменной ticks типа volatile перед каждым сравнением внутри цикла, несмотря на то, что значение переменной внутри цикла не изменяется.