ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 14.05.2021
Просмотров: 578
Скачиваний: 3
const int ival = 1024;
//
правильно
int
*const &piref = &ival;
Между ссылкой и указателем существуют два основных отличия. Во-первых, ссылка обязательно должна быть инициализирована в месте своего определения. Во-вторых, всякое изменение ссылки преобразует не ее, а тот объект, на который она ссылается. Рассмотрим на примерах. Если мы пишем:
int *pi = 0;
мы инициализируем указатель pi нулевым значением, а это значит, что pi не указывает ни на какой объект. В то же время запись
const
int &ri = 0;
означает примерно
следующее:
int temp = 0;
const int &ri = temp;
Что касается операции присваивания, то в следующем примере:
int ival = 1024, ival2 = 2048;
int *pi = &ival, *pi2 = &ival2;
pi = pi2;
переменная
ival, на которую указывает pi, остается
неизменной, а pi получает значение адреса
переменной ival2. И pi, и pi2 и теперь указывают
на один и тот же объект ival2.
Если же
мы работаем со ссылками:
int &ri = ival, &ri2 = ival2;
ri = ri2;
то
само значение ival меняется, но ссылка ri
по-прежнему адресует ival.
В реальных
С++ программах ссылки редко используются
как самостоятельные объекты, обычно
они употребляются в качестве формальных
параметров функций. Например:
// пример использования ссылок
//
Значение возвращается в параметре
next_value
bool get_next_value( int &next_value );
// перегруженный оператор
Matrix operator+( const Matrix&, const Matrix& );
Как соотносятся самостоятельные объекты-ссылки и ссылки-параметры? Если мы пишем:
int ival;
while (get_next_value( ival )) ...
это равносильно следующему определению ссылки внутри функции:
int &next_value = ival;
Упражнение
Есть ли ошибки в данных определениях? Поясните. Как бы вы их исправили?
(a) int ival = 1.01; (b) int &rval1 = 1.01;
(c) int &rval2 = ival; (d) int &rval3 = &ival;
(e) int *pi = &ival; (f) int &rval4 = pi;
(g) int &rval5 = pi*; (h) int &*prval1 = pi;
(i) const int &ival2 = 1; (j) const int &*prval2 = &ival;
Упражнение
Если ли среди нижеследующих операций присваивания ошибочные (используются определения из предыдущего упражнения)?
(a) rval1 = 3.14159;
(b) prval1 = prval2;
(c) prval2 = rval1;
(d) *prval2 = ival2;
Упражнение
Найдите ошибки в приведенных инструкциях:
(a) int ival = 0;
const int *pi = 0;
const int &ri = 0;
(b)
pi = &ival;
ri = &ival;
pi = &rval;
Взаимосвязь массивов и указателей
Если мы имеем определение массива:
int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 };
то что означает простое указание его имени в программе?
ia;
Использование идентификатора массива в программе эквивалентно указанию адреса его первого элемента:
ia;
&ia[0]
Аналогично обратиться к значению первого элемента массива можно двумя способами:
// оба выражения возвращают первый элемент
*ia;
ia[0];
Чтобы взять адрес второго элемента массива, мы должны написать:
&ia[1];
Как мы уже упоминали раньше, выражение
ia+1;
также дает адрес второго элемента массива. Соответственно, его значение дают нам следующие два способа:
*(ia+1);
ia[1];
Отметим разницу в выражениях:
*ia+1
и
*(ia+1);
Операция разыменования имеет более высокий приоритет, чем операция сложения. Поэтому первое выражение сначала разыменовывает переменную ia и получает первый элемент массива, а затем прибавляет к нему 1. Второе же выражение доставляет значение второго элемента.
Проход по массиву можно осуществлять с помощью индекса, как мы делали это в предыдущем разделе, или с помощью указателей. Например:
#include <iostream>
int main()
{
int ia[9] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 };
int *pbegin = ia;
int *pend = ia + 9;
while ( pbegin != pend ) {
cout << *pbegin <<;
++pbegin;
}
}
Указатель pbegin инициализируется адресом первого элемента массива. Каждый проход по циклу увеличивает этот указатель на 1, что означает смещение его на следующий элемент. Как понять, где остановиться? В нашем примере мы определили второй указатель pend и инициализировали его адресом, следующим за последним элементом массива ia. Как только значение pbegin станет равным pend, мы узнаем, что массив кончился. Перепишем эту программу так, чтобы начало и конец массива передавались параметрами в некую обобщенную функцию, которая умеет печатать массив любого размера:
#inc1ude <iostream>
void ia_print( int *pbegin, int *pend )
{
while ( pbegin != pend ) {
cout << *pbegin <<
' ';
++pbegin;
}
}
int
main()
{
int ia[9] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 };
ia_print( ia, ia + 9 );
}
Наша функция стала более универсальной, однако, она умеет работать только с массивами типа int. Есть способ снять и это ограничение: преобразовать данную функцию в шаблон
#inc1ude <iostream>
template <c1ass e1emType>
void print( elemType *pbegin, elemType *pend )
{
while ( pbegin != pend ) {
cout << *pbegin << ' ';
++pbegin;
}
}
Теперь мы можем вызывать нашу функцию print() для печати массивов любого типа:
int main()
{
int ia[9] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 };
double da[4] = { 3.14, 6.28, 12.56, 25.12 };
string sa[3] = { "piglet", "eeyore", "pooh" };
print(
ia, ia+9 );
print( da, da+4 );
print( sa, sa+3 );
}
Мы написали обобщенную функцию. Параметрами таких функций являются указатели на начало и конец массива, с которым они производят определенные действия. Вот, например, как выглядят вызовы обобщенного алгоритма сортировки:
#include <a1gorithm>
int main()
{
int ia[6] = { 107, 28, 3, 47, 104, 76 };
string sa[3] = { "piglet", "eeyore", "pooh" };
sort(
ia, ia+6 );
sort( sa, sa+3 );
};