Указатели

 

Проблема

Мы очень часто в программировании используем обмен двух переменных местами. Было бы удобно написать функцию swap(int x, int y), использовать для обмена двух переменных значениями, писать вместо трех строк одну swap(a, b).   Функцию нельзя определить так:
void swap(int х, int у) /* НЕВЕРНО */
{
int temp;
temp = х;
x = y;
у = temp;
}
  При вызове с параметрами a, b внутри  функции создаются новые переменные x и y, они получают значения a и b, далее они обмениваются этими значениями и…уничтожаются при выходе из функции. Значения собственно a и b остаются без изменений!  

Объявление указателей

  Упрощенно память компьютера можно представить как массив где хранятся переменные. Переменная - как бы ячейка в этом массиве. Таким образом у каждой переменной есть индекс – адрес. В языке Си (и Си++, разумеется) можно завести переменные, которые будут содержать в себе адреса других переменных. Для указания типа указатель используют звездочку:  
int a; //переменная типа int
int *pa; //переменная типа «указатель на int»
Создавать неинициализированные переменные плохо. Неинициализированные указатели – вдвойне. Лучше, создавая переменную указатель поместить в нее какой-нибудь конкретный адрес. Адрес переменной можно получить при помощи «операции получения адреса», для этого используют амперсанд:  
int *pa = &a; //указатель pa будет указывать на a

Операция разыменования

Главной операцией с указателем является «операция разыменования», для этого используют звездочку перед переменной-указателем:  
*pa = 5 //положить 5 в переменную, на которую указывает pa
*pa++   //увеличить на единицу переменную, на которую указывает pa
      Теперь можно правильно написать функцию swap:
void swap(int *х, int *у)
{
int temp;
temp = *х;
 *x = *y;
*у = temp;
}
  Работа производится с указателями, вернее с ячейками, на которые они указывают. Использовать функцию следует так  
swap(&a, &b)
  В функции. Передаются не значения a и b, а их адреса. Благодаря операции разыменования именно a и b меняются своими значениями!  

Это же происходит в функции scanf.

        int a = 0;
        int b = 0;
        scanf("%d%d", &a, &b);
    
Ей надо передать именно адрес  переменной, а не ее значение, чтобы она смогла заложить результат в правильный адрес памяти (в нашу переменную). Именно этим объясняется наличие амперсанда перед переменными при вызове scanf.  

Указатели и массивы

  Указатели имеют тесную связь с массивами. Имя массива – это константный указатель на его нулевой элемент. Если переменная а – массив, то значения собственно a и &a[0] совпадают. При этом а нельзя изменить, но можно использовать. Например, команда
*a = 5; 
положит 5 в нулевую ячейку массива a.  

Другие операции с указателями

  К указателю можно прибавить (отнять) целое число (или единицу при помощи ++ и --). Это будет указатель на ячейку на это число ячеек «правее» (или «левее») той, на которую указывает указатель.   Например, напечатать массив a из десяти элементов без использования индексации []:  
int *p = a;
for (int i = 0; i < 10; i++)
{
printf(“%d ”, *p);
p++;
}
  Указатели можно вычитать друг из друга. Получим «расстояние» между ячейками, на которые они указывают. Других операции, например, деление, умножение на число, сложение указателей в языке не предусмотрено. h2>Строки char* Строки Си - это массивы символов
        char *s = "abc"
    
Строка состоит из четырех ячеек. Последний символ с кодом 0. Читать Си-строки можно разными способами, например
        # include 
        fgets(s, 100, stdin); // будет прочитано максимально 100 символов
    

Динамические массивы

Для выделения динамического массива и работы с ним используются new и delete: new[] и delete[].
    #include 

    int main()
    {
    std::cout << "Enter a positive integer: ";
    int length;
    std::cin >> length;

    int *array = new int[length]; // используем оператор new[] для выделения массива. Обратите внимание, переменная length не обязательно должна быть константой!

    std::cout << "I just allocated an array of integers of length " << length << '\n';

    array[0] = 7; // присваиваем элементу под индексом 0 значение 7

    delete[] array; // используем оператор delete[] для освобождения выделенной массиву памяти
    array = NULL; // используйте nullptr вместо 0 в C++11

    return 0;
    }
    
Для строк все то же самое
        char *s = new char[1000];
        detete[] s;
    

Утечка памяти, деструкторы

Если не делать delete[] и постоянно выделять память под указатель
        char s = NULL;
        while(true) {
           s = new char[1000];
        }
    

Рано или поздно программа переполнит память. Такая ситуация называется утечкой памяти.

В ООП при удалении объекта вызывается деструктор, функция, имя которой совпадает с именем класса, с тильдой в начале. В деструкторе нужно обязательно нужно освобождать память при помощи delete[].

Например:

        struct MyClass {
           char *t;
           MyClass();
           ~MyClass();
        };

        MyClass::MyClass() {
           std::cout << "Constructor called" << endl;
           // выделяем память
           t = new char[4];
           t[0] = 'a';
           t[1] = 'b';
           t[2] = 'c';
           t[3] = '\0';
        }
        MyClass::~MyClass() {
           std::cout << "Destructor called" << endl;
           // освобождаем память
           delete[] t;
        }