Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Философия программирования на C++ указатели 2


Здравствуйте, уважаемые подписчики!

Сегодня мы продолжим прежнюю тему об указателях и слегка разовьем её.
Начнем с примера:

#include "iostream"
using namespace std;

int main() {
  int i = 10;
  int k = 20;
  int* pi = &i;
  int* pk = &k;
  cout << "pk - pi = " << pk - pi << endl;
  int d = pk - pi;  //Запоминаем расстояние от pi до pk
  *(pi+d) = 30; //Запись в переменную k
  cout << "k = " << k << endl;
  *(pk-d) = 40;
  cout << "i = " << i << endl;
}


Как видите, над указателями можно выполнять некоторые арифметические операции. Можно вычитать один указатель из другого, и тогда мы получим разность между элементами, на которые они указывают. Эта разница измеряется не в байтах, она указывает, на сколько элементов такого типа, на который указывает указатель (в данном случае int, размер 4 которого байта), один указатель отстоит от другого. То есть память как бы предстает не в виде последовательности байтов, а в виде последовательности элементов типа int (по 4 байта, также называемых двойными словами).
Поэтому находить разность можно только для 2-х элементов одинакового типа (кроме типа void, о котором позже).
Результат программы выше может быть примерно такой:
pk - pi = -3
Это означает, что pk находится на 3*4=12 байт "левее" в памяти (ближе к младшим адресам).
Кроме того к указателю можно прибавлять и вычитать из него целое число. Это будет означать что-то примерно "получить указатель на участок памяти, находящийся на столько-то элементов правее (или левее) данного".
В нашем примере, прибавляя к pi значение d (расстояние от pi до pk), мы фактически получим указатель pk. Дальше мы его разыменовываем. Скобки нужны для того, чтобы указать приоритеты операторов. Дело в том, что оператор разыменования * имеет больший приоритет, и запись вида
*pi+d
будет означать, что необходимо сначала разыменовать переменную pi, а потом к ней прибавить значение d (в итоге получим ошибку компиляции, поскольку слева от оператора = нельзя использовать выражения).
И еще раз повторю важное: pi+1 означает не получение указателя на 1 байт старше, а на столько байт, сколько занимает элемент, на который указывает pi (в данном случае на 4 байта). Вот пример, чтобы это лучше понять:

#include "iostream"
using namespace std;

int main() {
  int i = 10;
  int* pi = &i;
  cout << "pi = " << (long)pi << endl;
  cout << "pi+1 = " << (long)(pi+1) << endl;
}


В результате должно получиться, что pi+1 будет на 4 больше, чем pi.

Отмечу, что сложение двух указателей, умножение и деление на число не определены (что вполне логично).

А теперь чуть более сложно. Все вышеописанное хорошо работает, если между 2-мя элементами всегда кратное 4-м число байтов (между переменными i и k). А что будет, если мы заставим компилятор вторую переменную указывать на 2 или 3 байта правее, чем первая переменная? Чтобы не гадать, приведу программу:

#include "iostream"
using namespace std;

int main() {
  int i = 10;
  cout << "&i = " << (long)&i << endl;
  int d = 3;
  char* a = (char*)&i + d;
  int* b = (int*)a;
  cout << "a = " << (long)a << endl;
  cout << "b = " << (long)b << endl;
  cout << "b - &i = " << (long) (b - &i) << endl;
}


Здесь я использовал оператор приведения типов. Указатели тоже можно приводить друг к различным типам. В данной программе мы заставляем компилятор интерпретировать адрес переменной i (фактически указатель int*) как указатель на char. После этого мы к нему прибавляем число d равное 3. Т.е. в a у нас будет храниться значение, на 3 байта смещенное относительно адреса переменной i (char занимает 1 байт). Дальше мы заставляем компилятор интерпретировать указатель a как указатель на int. В итоге в строчке
cout << "b - &i = " << (long) (b - &i) << endl;
получаем в результате 0. Из этого делаем вывод: разность между двумя указателями возвращает число элементов, полностью уместившихся в промежуток между этими указателями (или если по-другому, целочисленное деление числа байт между данными адресами на размер элемента).

Пожалуй на этом стоит остановиться. Желаю удачно поэкспериментировать с указателями (эта тема очень важна в программировании и в ней желательно хорошенько разобраться).
Мы всегда рады ответить вам на ваши письма, наш e-mail: thinkingc@qt-prog.ru

В избранное