С++ больше нет
На днях один мой знакомый побывал на самом крупном столичном книжном рынке. Там очаровательная блондинка потрясла его известием: «С++ больше нет, теперь вместо него С#»
На днях один мой знакомый побывал на самом крупном столичном книжном рынке. Там очаровательная блондинка потрясла его известием: «С++ больше нет, теперь вместо него С#»
В ближайших планах заметок для публикации на блоге:
В комментариях голосуйте, какие из этих тем наиболее востребованы – они появятся в первую очередь. Также можно предлагать свои, альтернативные, темы интересных заметок.
Спасибо за то что вы читаете это.
Содержание:
Начнём с того, что указатели бывают на данные либо на функции. Так как 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-ах. Надо заметить, что данный код может выводить на экран разные числа в зависимости от:
Кроме вычитания (вычисления разницы указателей), к указателям можно применять операции сложения и вычитания с целым числом. Если в приведённом выше примере сложить &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;
То двигать указатель мы по-прежнему не сможем, а менять содержимое памяти по указателю сможем.
Есть простое правило, как определить, какой указатель перед нами: нужно прочитать его тип справа налево и послушать, что получается:
Указатели на функции в стиле С
Кроме данных и указателей на них в языке существуют функции и указатели на функции. Принцип работы с указателями на функции довольно понятный, а синтаксис приведён в следующем примере:
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++ In-Depth» под редакцией создателя языка – Бьярна Страуструпа. Книга выдержана в традиционной уже для этой серии книг манере задач для самостоятельного обдумывания с последующим подробным объяснением. Причём вопросы делятся на вопросы для новичков в C++ и вопросы для профессионалов. Не удивляйтесь, если прочитав её вы увидите, что-то знакомое – некоторые материалы на блоге используют информацию подчерпнутую и в ней, равно как и в любых других источниках. Темы задач, предложенных в книге довольно обширны – это: обобщённое программирование и стандартная библиотека, безопасность исключений, дизайн классов, наследование и полиморфизм, оптимизация и эффективность, головоломки и отдельно рассмотрены ошибки при проектировании класса std::string.
Тема довольно избита, однако, я всё-таки решил начать с неё. Думаю, новичкам будет полезно.
Итак, public, private и protected – это модификаторы доступа, а не видимости, как ошибочно думают некоторые. Private члены видны снаружи класса, но не доступны.
Теперь кратко, кому какой доступ они предоставляют.
Далее приведены примеры доступа с указанием какие поля в каких местах программы доступны.
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 }
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++. Он может быть использован также и как справочник. Надеюсь, время проведённое на этом блоге не будет потрачено впустую.