Шаг 18.
Адресная арифметика

    Этот шаг посвящен изложению действий, которые можно выполнять над указателями.

    Вначале заметим, что ни один "правильный" указатель не может иметь значения 0, поэтому равенство нулю значения указателя может служить сигналом о ненормальном завершении выполнения функции.

    Над указателями можно выполнять следующие операции:

  1. Присваивание значения указателя другому указателю того же типа.
  2. "Операция" инициализации указателя. Операторы: char a; char *pa=&a; описывают символьную переменную a и указатель pa на объект типа char, а также инициализируют pa так, чтобы он указывал на a.
  3. Операция вычитания указателей одного и того же типа.
  4. Операции сложения и вычитания указателя и целого.

    Приведем несколько примеров.


    Пусть p - указатель на объект любого типа. Тогда оператор p++; увеличивает p так, что он указывает на следующий объект того же типа.
    Пусть i - переменная целого типа. Оператор p += i; увеличивает указатель p так, чтобы он указывал на объект, отстоящий на i "единиц" памяти, занимаемых объектом данного типа, от объекта, на который указывает p.

  1. Операции сравнения указателей одного и того же типа. Если p и q - указатели на объекты одного типа, то к ним применимы операции отношения (<, >=, >, <=, !=, ==).

    Например:

  1. Присваивание указателю нуля (NULL) и сравнение указателя с нулем (NULL). Например, отношение p!=NULL истинно, если указатель p отличен от NULL.

    Проиллюстрируем использование этих операций конкретными примерами.


    Пример 1. Проиллюстрируем выбор данных из памяти с помощью различных указателей.
#include <iostream.h>
void main()
{
  unsigned long L=0x12345678L;
  char *cp=(char *)&L;
  int *ip=(int *)&L;
  long *lp=(long *)&L;
  cout << hex;        //Шестнадцатеричное представление выводимых значений.
  cout << "\n Адрес L, &L=" << &L;
  cout << "\ncp = " << (void*)cp  << "\t*cp = 0x" << (int)*cp;
  cout << "\nip = " << (void *)ip << "\t*ip=0x" << *ip;
  cout << "\nlp = " << (void *)lp << "\t*lp=0x" << *lp;
}
Текст этой программы можно взять здесь.

Результат работы программы:

Адрес L, &L=0x243f2256
cp = 0x243f2256  *cp= 0x78
ip = 0x243f2256  *ip= 0x5678
lp = 0x243f2256  *lp= 0x12345678

    В программе используется явное приведение типов. Так как адрес &L имеет тип unsigned long *, то при инициализации указателей его значение явно преобразуется соответственно к типам char *, int *, long *. При выводе значений указателей они преобразуются к типу void *, так как требуется вывод значений, а не длин участков памяти, связанных со значениями указателей.

    При выводе значения *cp использовано явное преобразование типа (int), так как при его отсутствии будет выведен не код, а соответствующий ему символ ASCII-кода. Особенность размещения чисел в памяти компьютера заключается в том, что сначала размещаются младшие байты числа, а затем старшие (рис.1):


Рис.1. Схема размещения в памяти значения переменной L


    Пример 2. Печать значений адресов и длин некоторых указателей.
#include <iostream.h>
void main()
{
  char *pac,*pbc;
  long *pal,*pbl;
  cout << hex;
  cout << "\n Адреса указателей:";
  cout << "\n &pac = " << &pac << " &pbc = " <<&pbc;
  cout << "\n &pal = " << &pal << " &pbl = " <<&pbl;
  cout << "\n Длины указателей некоторых типов:";
  cout << "\n sizeof (void *) = " << sizeof(void *);
  cout << "\n sizeof (char *) = " << sizeof(char *);
  cout << "\n sizeof (int *) = " << sizeof(int *);
  cout << "\n sizeof (long *) = " << sizeof(long *);
  cout << "\n sizeof (float *) = " << sizeof(float *);
  cout << "\n sizeof (double *) = " << sizeof(double *);
  cout << "\n sizeof (long double *) = " << sizeof(long double *);
}
Текст этой программы можно взять здесь.

Результат работы программы:

  Адреса указателей:
  &pac = 0x5cff2314 &pbc = 0x5cff2310
  &pal = 0x5cff230c &pbl = 0x5cff2308
  Длины указателей некоторых типов:
  sizeof (void *) = 4
  sizeof (char *) = 4
  sizeof (int *) = 4
  sizeof (long *) = 4
  sizeof (float *) = 4
  sizeof (double *) = 4
  sizeof (long double *) = 4


    Примечание. На разных компьютерах могут получаться другие значения!


    Пример 3. Приоритеты унарных операций.
#include <iostream.h>
void main()
{
   int i1=10, i2=20, i3=30;
   int *p=&i2;
   //Значение i2.
   cout << "\n *&i2 = " << *&i2;
   //Значение i2 сначала увеличенное на 1.
   cout << "\n *&++i2 = " << *&++i2;
   //Значение i2.
   cout << "\n *p = " << *p;
   //Значение i2,  p увеличивается на 1.
   cout << "\n *p++ = " << *p++;
   //Значение i1.
   cout << "\n *p = " << *p;
   //Значение i1 сначала увеличенное на 1.
   cout << "\n ++*p = " << ++*p;
   //Значение i2, сначала уменьшается p.
   cout << "\n*--p = " << *--p;
   //Значение i3, сначала уменьшается p, затем полученное значение i3
   //увеличивается.
   cout << "\n++*--p = " << ++*--p;
}
Текст этой программы можно взять здесь.

Результат работы программы:

   *&i2 = 20
   *&++i2 = 21
   *p = 21
   *p++ = 21
   *p = 10
   ++*p = 11
   *--p = 21
   ++*--p = 31

    Выражение *p++ вычисляется в таком порядке: вначале выполняется именование (обращение по адресу), и полученное значение (21) служит значением выражения в целом. Затем выполняется операция ++ и значение указателя увеличивается на 1. Тем самым он "устанавливается" на переменную i1. (В памяти компьютера переменные i1,i2, i3 располагаются в обратном порядке).

    На следующем шаге мы поговорим об операции sizeof.


Предыдущий шаг Содержание Следующий шаг