Лекция 6

Ссылки на константы

    int i = 5;
    int& ci = i;
    const int& cci = i; // Здесь все будет нормально
    const int n = 10;
    int& cn = n; // Такое компилятор запретит
    int& cn = const_cast<int&>(n);
    const int& ccn = n; 

Указатели и массивы

В C++ указатели и массивы тесно связаны

int a[10];

int* p = &a[0]; // адрес первого элемента
*p = 5;
a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
5
*p

Операции при работе с указателями

В Pascal таких операций нет т.к он является более высокоуровневым языком.

Выполним следующую операцию p++;. Теперь указатель p указывает на следующий(второй) элемент массива.

a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9]
5
*p

Операция p++ увеличивает адрес в зависимости от типа указателя т.е. p++ ~ p += sizeof(int)

p += 1;     // Переход на следующий элемент массива
p += n;     // Увеличение на n элементов массива
p+1     // Адрес следующего элемента  
p+n = p1;   // Записать в `p1` адрес n-го элемента
p1 - p = n; // Количество между указателями

А вот складывать указатели нельзя!!!

*(p+0) и a[0] - являются ссылками на первый элемент массива
*(p+1) и a[1] - являются ссылками на второй элемент массива
*(p+2) и a[2] - являются ссылками на третий элемент массива

т.е a[n] == *(p+n) - связь массивов и указателей

Имя массива a может быть неявно преобразовано к указателю на свой первый элемент. Т.е на самом деле а это указатель на первый элемент массива. Значит мы можем написать проще:

int* p = a;

a является константным указателем на свой первый элемент т.е он как-бы описан таким образом int* const a;

Отсюда становится понятно, почему нельзя писать a = a1. Т.к имя массива константный указатель , то нельзя присваивать один массив другому.

Вообще говоря, более строгая связь массивов и указателей выглядит следующим образом: a[n] == *(a+n)

Отсюда следует вывод(крамольная истина): В языке C массивов нет - есть только указатели!!!

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

Следствие 2. Понятно почему массивы индексируются с нуля. Это самое эффективное по этой формуле.

Следствие 3. a[n] == *(a+n) == *(n+a) == n[a]

Идиома *p++

Идиома - устойчивое выражение, которое воспринимается как единое целое.

Теперь допустим, нам необходимо сделать следующее:

int a[10];
int* p = a;

*p = 3;
p++;

Для краткости это хочется заменить на *(p++) = 3; А что, если записать *p++? Это можно воспринимать как *(p++) или как *(p)++. В C/C++ унарные операции ассоциируются справа налево, поэтому в данном случае ++ относится к указателю, следовательно *p++ ~ *(p++).

Пример 1. Заполнить массив a нулями

int a[10];
//int* p = a; // Однако это можно перенести в раздел инициализации for(;;)

for(int* p = a; p != a+10; *p++ = 0;);

Передача массива в функцию с помощью указателя

void InitZero(int* a, int n)
{
  for(int* p = a; p != a+n;) *p++ = 0;
}

// int* a ~ int a[] 

Пример 2. Даны 3 массива int a[10], b[10], c[10]. Необходимо заполнить массив c[10] суммой элементов массивов a[10] и b[10].

int *pa = a, *pb = b, *pc = c;

//for(; pa != a + 10;)
while(pa != a + 10)
  *pc++ = *pa++ + *pb++;

С-строки и указатели

Пример 1.

char s[10] = "Hello";
s[0] s[1] s[2] s[3] s[4] s[5] s[6] s[7] s[8] s[9]
H e l l o \0 \0 \0 \0 \0
char* pc = s;
//while(*pc != '\0') // Но можно короче 
while(*pc)
  cout << *pc++ <<  ' ';

Пример 2. Копирование строк

char s[10] = "Hello";
char s1[10];

s1 = s; // Нельзя т.к. s1 объявлен как char* const s1

char* mysrtcpy(char* p, const char* q)
{
  while(*q)
    *p++ = *q++;
  *p = 0;
  return p-1;

  // Или можно сделать короче, но менее понятно

  while(*p++ = *q++);
  return p-1;
}
  1. На главную