Содержание:

Начнём с того, что указатели бывают на данные либо на функции. Так как C++ обратно совместим с C, то рассмотрим сперва указатели в языке C.

Указатели на данные в стиле C

 

Начинающий изучать язык C знакомится сперва с указателями на данные. Всё просто, для взятия адреса используется operator &, для разыменования — operator *. Например, такой код:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 int main(void) {

    6   int a = 0;

    7   cout << a << ‘\n’;

    8   int* pa;

    9   pa = &a;

   10   ++(*pa);

   11   cout << a << ‘\n’;

   12   return 0;

   13 }

Выведет:

0

1

Указатель может указывать на переменную типа структура (или класс), в таком случае, для доступа к членам структуры применяется operator->. Пример:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 struct cpp_for_each {

    6   int a;

    7   float b;

    8 };

    9

   10 int main(void) {

   11   cpp_for_each obj;

   12   cpp_for_each* ptr;

   13   ptr = &obj;

   14   ptr->a = 1;

   15   ptr->b = 2.5f — ptr->a;

   16   cout << obj.b << ‘\n’;

   17   return 0;

   18 }

Арифметика указателей

 

С указателями на данные возможны арифметические операции сложения и вычитания. Пример:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 int main(void) {

    6   int a = 0;

    7   int b = 0;

    8   ptrdiff_t inInts = &a — &b;

    9   ptrdiff_t inChars =

   10     reinterpret_cast<char*>(&a)

   11     — reinterpret_cast<char*>(&b);

   12   cout

   13     << inChars << ‘ ‘

   14     << inInts << ‘\n’;

   15   return 0;

   16 }

Единица измерения разницы между указателями — это количество элементов указываемого типа. В приведённом примере в переменной inInts будет разница в int-ах, а в inChars — в char-ах. Надо заметить, что данный код может выводить на экран разные числа в зависимости от:

  • компилятора
  • размера int
  • вида сборки (debug/release)

Кроме вычитания (вычисления разницы указателей), к указателям можно применять операции сложения и вычитания с целым числом. Если в приведённом выше примере сложить &b и inInts, то мы получим &a:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 int main(void) {

    6   int a = 0;

    7   int b = 0;

    8   ptrdiff_t inInts = &a — &b;

    9   int* c = &b + inInts;

   10   cout <<

   11     ((&a == c)

   12       ? «Equal\n»

   13       : «Not equal\n»);

   14   return 0;

   15 }

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

—  ==  !=  <  >  <=  >=

а с одним указателем и числом такие:

+  —  ++  —  +=  -=

С арифметикой указателей связана операция доступа к элементу массива по индексу. В приведённом ниже примере, все переменные получат значение элемента массива с индексом 3, т.е. это всё эквивалентные записи одной и той же идеи:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 int main(void) {

    6   int a[5] = {1,2,3,4,5};

    7   int v1 = *(a + 3);

    8   int v2 = *(3 + a);

    9   int v3 = a[3];

   10   int v4 = 3[a];

   11   int v5 = *(&a[0] + 3);

   12   cout << v1 << ‘\n’;

   13   cout << v2 << ‘\n’;

   14   cout << v3 << ‘\n’;

   15   cout << v4 << ‘\n’;

   16   cout << v5 << ‘\n’;

   17   return 0;

   18 }

Константность

 

Надо заметить, что если мы перепишем самый первый пример и заменим там строку 8 таким образом:

    8   const int* pa;

то компилятор откажется компилировать строку 10, аргументируя тем, что мы пытаемся изменить данные используя указатель на константные, то есть неизменяемые, данные.

d:\vc\cppforeach\cppforeach\cppforeach.cpp(10) : error C3892: ‘pa’ : you cannot assign to a variable that is const

Кроме этого, мы можем узакать, что сам указатель является неизменным, т.е. что его нельзя передвинуть, чтобы он указывал на другое место в памяти:

    8   const int* const pa;

А если мы напишем так:

    8   int* const pa;

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

Есть простое правило, как определить, какой указатель перед нами: нужно прочитать его тип справа налево и послушать, что получается:

  • int * — указатель на целое
  • const int * — указатель на целую константу
  • int * const — константный указатель на целое
  • const int * const — константный указатель на целую константу

Указатели на функции в стиле С

Кроме данных и указателей на них в языке существуют функции и указатели на функции. Принцип работы с указателями на функции довольно понятный, а синтаксис приведён в следующем примере:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 void f(void) {

    6   cout << «Do something\n»;

    7 }

    8

    9 int main(void) {

   10   // обычный вызов

   11   f();

   12

   13   // вызов через взятие указателя

   14   // и его разыменование

   15   (*&f)();

   16

   17   // сохранить указатель

   18   // в переменную

   19   void (*pf)(void) = &f;

   20   // вызов функции по указателю

   21   (*pf)();

   22

   23   // тип для данных указателей

   24   typedef void (*funcType)(void);

   25   // переменная этого типа

   26   funcType pf2 = &f;

   27   (*pf2)();

   28   return 0;

   29 }

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

Указатели на члены данных (C++)

 

Если класс или структура содержит несколько членов данных одного типа, мы можем создать указатель на такие члены данных и обращаться к ним.

    1 #include <iostream>

    2

    3 using std::cout;

    4 using std::ostream;

    5

    6 // наш тестовый класс

    7 struct test {

    8   int first;

    9   int second;

   10   void out(ostream& o) const ;

   11 };

   12

   13 ostream& operator<<(

   14   ostream& out, const test& obj) {

   15   out << obj.first << ‘ ‘

   16       << obj.second;

   17   return out;

   18 };

   19

   20 void test::out(ostream& o) const {

   21   o << *this << ‘\n’;

   22 }

   23

   24 void zero(

   25   test* p, int test::* v) {

   26   p->*v = 0;

   27 }

   28

   29 int main(void) {

   30   // создаём и инициализируем

   31   // объект

   32   test obj;

   33   obj.first = 1;

   34   obj.second = 1;

   35   obj.out(cout);

   36

   37   // обнуляем первый член

   38   zero(&obj, &test::first);

   39   obj.out(cout);

   40

   41   // обнуляем второй член

   42   zero(&obj, &test::second);

   43   obj.out(cout);

   44

   45   // создаём ссылку на объект

   46   test& ref = obj;

   47   // тип — указатель на int

   48   // — член данных test

   49   typedef int test::* int_in_test;

   50

   51   // манипулируем первым членом

   52   // через ссылку

   53   int_in_test v = &test::first;

   54   ref.*v = 2;

   55   obj.out(cout);

   56

   57   // аналогично со вторым

   58   v = &test::second;

   59   ref.*v = 3;

   60   obj.out(cout);

   61

   62   return 0;

   63 }

Указатели на функции

 

С ними всё по аналогии:

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 struct test {

    6   void do1(void) {cout <<«1\n»;}

    7   void do2(void) {cout <<«2\n»;}

    8 };

    9

   10 typedef void (test::*vtv)(void);

   11

   12 void zero(

   13   test* p, vtv v) {

   14   (p->*v)();

   15 }

   16

   17

   18 int main(void) {

   19   test obj;

   20   zero(&obj, &test::do1);

   21   zero(&obj, &test::do2);

   22   return 0;

   23 }

Надо заметить, что метод класса может возвращать указатель на свои закрытые члены-данных или методы. Аналогия может быть такая: у класса есть ключи к закрытым членам, он открывает их и отдаёт  вовне. Пользователям же такого класса ключи не нужны. Таким образом, компилятор проверяет права на доступ при взятии адреса на член, после этого, если права подтверждены, код может передать этот указатель в другие места программы, где прав на взятие указателя может не быть, но тем не менее уже взятый указатель будет доступен.

    1 #include <iostream>

    2

    3 using std::cout;

    4

    5 struct test;

    6

    7 typedef void (test::*vtv)(void);

    8

    9 struct test {

   10  static vtv getFunc(char c) {

   11   if(c == ‘1’) return &test::do1;

   12   if(c == ‘2’) return &test::do2;

   13   return 0;

   14  }

   15 private:

   16   void do1(void) {cout <<«1\n»;}

   17   void do2(void) {cout <<«2\n»;}

   18 };

   19

   20 int main(void) {

   21   test obj;

   22   vtv vtv1 = test::getFunc(‘1’);

   23   vtv vtv2 = test::getFunc(‘2’);

   24   (obj.*vtv1)();

   25   (obj.*vtv2)();

   26   return 0;

   27 }

Вот и всё на сегодня.

Advertisements