Указатели C++, на массив и структуру, ссылки, разыменование, примеры

Итак, небольшая статья на эту страшную и малопонятную новичкам С++ тему.

Допустим, что вы уже знаете, что такое переменная в языке Си/Си++.

Просто напомним, в компьютере есть память - громадный ряд маленьких пронумерованных ячеек(по 1Байту). От 1 до ... 8млрд, к примеру(у меня оперативная память 8Гб)

1 2 3 4 ... 3221225472 ... 8589934592

Так уж сложилось, что такие громадные величины лучше удобнее кодировать в 16ричной системе исчисления. 0х1 до 0х200000000(в моем случае, 8ГБ памяти же)

1 2 3 4 ... 3221225472 ... 8589934592
0х1 0х2 0х3 0х4 ... 0хC0000000 ... 0х200000000

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

1 2 3 4 ... 3221225472 ... 8589934592
0х1 0х2 0х3 0х4 ... 0хC0000000 ... 0х200000000
0x00 char a; 0хFF char b; ... 0xFF ... 0x31

Ячейки с номерами 0х2, 0х4 имеют символьные имена a и b. Это называются переменные. При чем размер этих переменных всего 1 Байт - из-за типа данных char. А могут занимать и 4 байта (к примеру тип данных int (integer))

Давайте для удобства превратим ряд в таблицу, стеллаж.

Переменная - имя для ячейки памяти.
Удобное программисту.

0 1 2 3 4 5 6 7 8 9 a b c d e f
0x0000 a
0x0010 b
0x0020
0x0030 c
0x0040
0x0050
0x0060

char a; // переменная находится по адресу 0х0004, не проинициализирована
char b; // переменная находится по адресу 0х0018, не проинициализирована
int c=344; // переменная находится по адресу 0х0030, проинициализирована значением 344

Объявляя переменную, мы по сути выделяем ей память определенного размера(1,4,8 байт). Размер зависит от типа данных.

После объявления переменной, в этой занятой ячейке памяти ничего не находится, точнее, там мусор от прошлых использований.

При инициализации переменной, в эту занятую ячейку памяти заносится заданное программой значение.

Процессору без разницы как вы назовете переменную. Он все равно оперирует адресами.

Указатель - тоже переменная.
В ней хранится АДРЕС ячейки.

0 1 2 3 4 5 6 7 8 9 a b c d e f
0x0000 a
0x0010 b
0x0020
0x0030 c=344
0x0040 *d
0x0050 *e=&c
0x0060

char *d; // переменная находится по адресу 0х004c, не проинициализирована(значение указателя неизвестно)
int *e=&c; // переменная находится по адресу 0х0052, проинициализирована адресом переменной с(значение указателя содержит адрес переменной с - 0х0030)
printf("%d",e);//это выведет адрес переменной с 0х0030, но в 10тичном формате = 48 (из-за %d)
printf("%d",*e);//а это выведет значение по адресу 0х0030 -> 344

Объявляя переменную-указатель, мы по сути выделяем ей память всегда одинакового размера, необходимого для адресации любой ячейки памяти (4 байта для 32битных систем, 8 байт для 64 битных). Размер зависит от разрядности системы и задается при компиляции программы.

Чтобы узнать адрес любой переменной, используется символ & перед именем.

Чтобы узнать что лежит по адресу в указателе, нужно "разыменовать" его, используя символ * (звездочка).

Если еще непонятно, предлагаю перейти к примерам.

*имя - вначале создаем указатель (объявление указателя)
&имя1 - узнать адрес абсолютно любой переменной (взятие адреса)
*имя - узнать что находится по адресу в указателе (разыменование)

Вот небольшой пример указателей на языке Си++. Желательно самому все проверить(DevCpp)

#include <stdio.h>
struct proba
{
    int a;
    int b;
    char c;
};
int main()
{
    proba a1;
    proba *a2= new proba;
    a2=&a1;
    (a1).a=100;
    (a1).b=200;
    int a=-255;
    int c[5]={56,12,87,-12,255};
    int *d=&a;
    int *e=c;	
    printf("d= %d\n",*d);//-255
    printf("e[0]= %d\n",*e);//56
    printf("e[1]= %d\n",*(e+1));//12
    printf("e[1]= %d\n",e[1]);//12
    printf("a2.a= %d\n",a2->a);//100
    printf("a2.a= %d\n",(*a2).a);//100

    printf("size char* %d\n",sizeof(char *));//8
    printf("size int* %d\n",sizeof(int *));//8
    printf("size void* %d\n",sizeof(void *));//8
    printf("size a1 %d\n",sizeof(a1));//12
    printf("size a2 %d\n",sizeof(a2));//8
    return 0;
}
            

Кое-что интересно есть в примере.

*(e+1) и e[1] дают идентичный результат.

*(e+1) - к указателю е прибавляется 1 int смещение (4байта). и затем этот смещенный указатель разыменовывается. Получилось сдвинулись на 1 ячейку в массиве.

e[1] - тоже задаем смещение указателя на 1 (на 4 байта). По сути вся работа с массивами осуществляется через указатели. Будь то обычные численные массивы или строки. Все втихаря ведется указателями.

Есть еще кое-что интересно в примере на C/C++.

a2->a при работе напрямую с указателем на структуру используется стрелка "->"

(*a2).a - можно разыменовать указатель на структуру и обратиться к самой структуре по адресу. Тогда доступ к членам структуры осуществляется через точку "."

Указатели на массив.
И на структуру.
Особенности использования в C++.

В массиве можно обратиться к элементам через указатель 2 способами:

  1. *(e+i)
  2. e[i]

В структуре можно обратиться к членам через указатель 2 способами:

  1. (*a2).a
  2. a2->a

Все это справедливо для языка программирования Си++


Прочитали вы статью до конца или просто пролистали

Но вы крупно нам поможете, если прокомментируете ее (ВНИЗУ), выскажете свою долю критики или негатива, а потом еще и лайкнете (СПРАВА) в любимой вашей соц.сети. Зачем вам это? Да ни зачем, вам это сделать 2 секунды, на одно зевание вы потратите больше ;) А вашим знакомым это может пригодиться. Мы кстати есть Вконташе:)

comments powered by HyperComments