Указатели 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 способами:
- *(e+i)
- e[i]
В структуре можно обратиться к членам через указатель 2 способами:
- (*a2).a
- a2->a
Все это справедливо для языка программирования Си++
Комментарии
Иван
Написать комментарий