С++ больше нет

На днях один мой знакомый побывал на самом крупном столичном книжном рынке. Там очаровательная блондинка потрясла его известием: «С++ больше нет, теперь вместо него С#»

Отсюда

Ближайшие планы

В ближайших планах заметок для публикации на блоге:

  • std::for_each – одноимённый блогу алгоритм стандартной библиотеки
  • CRTP – статическая виртуализация
  • RAII – наверное, половина мощи C++ в этой аббревиатуре.
  • std::auto_ptr – за что любить и ненавидеть умный указатель из стандартной библиотеки шаблонов

В комментариях голосуйте, какие из этих тем наиболее востребованы – они появятся в первую очередь. Также можно предлагать свои, альтернативные, темы интересных заметок.

Спасибо за то что вы читаете это.

Синтаксис указателей в C++

Содержание:

Начнём с того, что указатели бывают на данные либо на функции. Так как 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 }

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

Must Read: Герб Саттер "Новые сложные задачи на C++"

Эта книга из серии Must Read. Вот ссылка на неё на сайте самого автора. Книга вышла в серии под названием «C++ In-Depth» под редакцией создателя языка – Бьярна Страуструпа. Книга выдержана в традиционной уже для этой серии книг манере задач для самостоятельного обдумывания с последующим подробным объяснением. Причём вопросы делятся на вопросы для новичков в C++ и вопросы для профессионалов. Не удивляйтесь, если прочитав её вы увидите, что-то знакомое – некоторые материалы на блоге используют информацию подчерпнутую и в ней, равно как и в любых других источниках. Темы задач, предложенных в книге довольно обширны – это: обобщённое программирование и стандартная библиотека, безопасность исключений, дизайн классов, наследование и полиморфизм, оптимизация и эффективность, головоломки и отдельно рассмотрены ошибки при проектировании класса std::string.

OZON.ru

Вокруг public, private и protected

Тема довольно избита, однако, я всё-таки решил начать с неё. Думаю, новичкам будет полезно.

Итак, public, private и protected – это модификаторы доступа, а не видимости, как ошибочно думают некоторые. Private члены видны снаружи класса, но не доступны.

Теперь кратко, кому какой доступ они предоставляют.

  • Public – доступ открыт всем, кто видит определение данного класса.
  • Private – доступ открыт самому классу (т.е. функциям-членам данного класса) и друзьям (friend) данного класса, как функциям, так и классам.
  • Protected – доступ открыт классам, производным от данного.

Далее приведены примеры доступа с указанием какие поля в каких местах программы доступны.

1 class some {

2   friend void f(some&);

3 public:

4   int a_;

5 protected:

6   int b_;

7 private:

8   int c_;

9 };

10

11 void f(some& obj) {

12   obj.a_ = 0; // ok

13   obj.b_ = 0; // ok

14   obj.c_ = 0; // ok

15 }

16

17 void g(some& obj) {

18   obj.a_ = 0; // ok

19   obj.b_ = 0; // CT error

20   obj.c_ = 0; // CT error

21 }

22

23 class derived : public some {

24   derived() {

25     a_ = 0; // ok

26     b_ = 0; // ok

27     c_ = 0; // CT error

28   }

29 };

В C++ существует public-наследование, private-наследование и protected-наследование. В зависимости от того, какой тип используется, изменяется доступ к членам базового класса для клиентов производного. В таблице сведена информация об этом изменении.

Исходный модификатор доступа
public private protected
public-наследование public private protected
private-наследование private private private
protected-наследование protected private protected

Следует добавить, что производный класс может изменить модификатор доступа с protected на public, разместив using объявление в соответствующей секции класса:

1 class some {

2 public:

3   int a;

4 protected:

5   int b;

6 private:

7   int c;

8 };

9

10 class derived : public some {

11 public:

12   using some::b;

13 };

14

15 void f(derived& obj) {

16   obj.b = 0; // ok

17 }

Напоследок приведу несколько приёмов, с помощью которых можно «достучаться» до закрытых функций или данных. Допустим, у нас есть класс some и нам нужно обнулить закрытую переменную c:

  • Модифицировать определение класса, добавив друга (функцию или класс)

    1 class some {

    2   friend class some_friend;

    3 public:

    4   int a;

    5 protected:

    6   int b;

    7 private:

    8   int c;

    9 };

    10

    11 class some_friend {

    12 public:

    13   static void hack(some& obj) {

    14     obj.c = 0;

    15   }

    16 };

  • Воспользоваться препроцессором:

    1 #define private public

    2

    3 class some {

    4 public:

    5   int a;

    6 protected:

    7   int b;

    8 private:

    9   int c;

    10 };

    11

    12 void hack(some& obj) {

    13   obj.c = 0;

    14 }

  • Создать класс с таким же расположением в памяти и воспользоваться reinterpret_cast для преобразования указателей:

    1 class some {

    2 public:

    3   int a;

    4 protected:

    5   int b;

    6 private:

    7   int c;

    8 };

    9

    10 class hack_some {

    11 public:

    12   int a;

    13   int b;

    14   int c;

    15 };

    16

    17 void h(some& obj) {

    18   reinterpret_cast<hack_some*>(&obj)->c = 0;

    19 }

  • Если у «взламываемого» класса есть шаблонная функция, можно её специализировать своим типом:

    1 class some {

    2 public:

    3   int a;

    4   template<class T> void func(void) {

    5     a = b + c;

    6   }

    7 protected:

    8   int b;

    9 private:

    10   int c;

    11 };

    12

    13 class hack_template_param{};

    14

    15 template<>

    16 void some::func<hack_template_param>(void) {

    17   c = 0;

    18 }

    19

    20 void hack(void) {

    21   some o;

    22   o.func<hack_template_param>();

    23 };

Очевидно, что способ с reinterpret_cast работает только для доступа к закрытым членам данных или вызова виртуальных функций. Остальные же способы позволяют как модифицировать закрытые данные, так и вызывать закрытые невиртуальные методы.

На сегодня всё.

C++ Для Всех. Полетели!

Приветствую всех читателей!

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

Follow

Get every new post delivered to your Inbox.