notes

C++

Table Of Content

Указатели

  1. Указатель - это переменная, хранящая адрес некоторой ячейки памяти
  2. Указатели являются типизированными
     int i = 3; // переменная типа int
     int *p = 0; // указатель на переменную типа int
    
  3. Нулевому указателю (которому присовено значение 0) не соостветствует никакая ячейка памяти
  4. & - оператор взятия адреса переменной
  5. * - оператор разыменования (получает значение по адресу)
     p = &i; \\ указатель р указывает на переменную i
     *p = 10; \\ изменяется ячейка по адресу p, т.е. i
    

Массивы

  1. Массив - набор однотипных элементов, расположенных в памяти друг за другом, доступ к которым осуществляется по индексу
  2. С++ позволяет определять массивы на стеке
     // array 1 2 3 4 5 0 0 0 0 0
     int m[10] = { 1, 2, 3, 4, 5};
    
  3. Переменная массива хранит указатель на первый элемент этого массива.
  4. Получение значения по индексу работает следующим образом
     m[i] => *(m + i)
    
  5. Арифметика указателей
     int m[10] = {1, 2, 3, 4, 5};
     int *p = &m[0]; // адрес начала массива
     int *q = &m[9]; // адрес последнего элемента массива
    
    • (p + k) - сдвиг на k ячеек типа int вправо
    • (p - k) - сдвиг на k ячеек типа int влево
    • (q - p) - количество ячеек между указателями
    • p[k] - эквивалентно *(p + k)
  6. Заполнениие массива по указателю
     int m[10] = {};
     for (int *p = m; p < m+9; ++p)
     {
         *p = (p - m) + 1;
     }
     // массив заполнен числами от 1 до 10
    
  7. Передача массива в функцию
     int max_element (int *m, int size) {
         int max = *m;
         for (int i = 1; i < size; ++i) {
             if (m[i] > max) {
                 max = m[i];
             }
         }
         return max
     }
    

Использование указателей

  1. Для передачи массива в фунцию - передаём указатель на начало и указатель на следующий за последним элемен (тем самым избегая лишней операции - ` m[i] => *(m + i)`)
     bool contains(int *p, int *q, int value) {
         for (;, p!=q, ++p) {
             if (*p == value) {
                 return true;
             }
         }
         return false;
     }
    
  2. Возврат указателя из функции
     int * max_element (int *p, int *q) {
         int* pmax = p;
         for(; p != q; ++p) {
             if (*p > *pmax) {
                 pmax = p;
             }
         }
    
         return pmax;
     }
    
  3. Возврат значения через укзатель
     bool max_element (int *p, int *q, int *res) {
         if (p == q) {
             return false;
         }
    
         *res = *p;
         for(; p!=q; ++p) {
             if (*p > *res) {
                 *res = *p;
             }
         }
         return true;
     }
    

Ссылки

  1. Недостатки указателей
    • Использование указателей синтатически загрязняет код и усложняет его понимание (Приходится использовать операторы * и &).
    • Указатели могут быть неинициализированными (некорректный код).
    • Указатель может быть нулевым (корректный код), а значит указатель нужно проверять на равенство нулю.
    • Арифметика указателей может сделать из корректного указателя некорректный (легко промахнуться)
  2. Ссылки
    • Для того, чтобы исправить некоторые недостатки указателей, в С++ введены ссылки
    • Ссылки являются “красивой обёрткой” над указателями
        void swap (int & a, int & b) {
            int t = b;
            b = a;
            a = t;
        }
      
    • Простыми словами ссылка это псевдоним переменной
  3. Различия ссылок и указателей
    • Ссылка не может быть неиницилазированной
        int *p; // ok
        int &l; // error
      
    • У ссылки нет нулевого значения
        int *p = 0; // ok
        int &l = 0; // error
      
    • Ссылку нельзя переинициализировать
        int a = 10;
        int b = 20;
        int *p = &a; // р указывает на а
        p = &b; // p указывает на b
        int &l = a; // l ссылается на а
        l = b; // a присваивается значение b
      
    • Нельзя создавать массивы ссылок
    • У ссылок нет арифметики
  4. Следует следить за временем жизни переменных (чтобы время жизни указателей и ссылок всегда соответствовало времени жизни тех данных, на которые они указывают):
     int * foo() {
         int a = 10;
         return &a;
     }
    
     int & bar() {
         int b = 20;
         return b;
     }
    
     int *p = foo(); // runtime error
     int &l = bar(); // runtime error
    
  5. С помощью ссылки сделать параметром функции массив заданной длинны

     void foo(int (&a)[3]) { /* ... */ }
    
  6. Ссылки можно использовать не только для передачи изменяемых параметров в функции, но и для передачи больших параметров копировать которые дорого. Если при этом параметр не должен изменяться можно объявить его с ключевым словом const.
     int foo(string const &s) { /* нельзя менять значение s */ }