Поиск…


Вступление

Указатель - это адрес, который относится к местоположению в памяти. Они обычно используются, чтобы позволить функциям или структурам данных знать и изменять память без необходимости копировать упомянутую память. Указатели можно использовать как с примитивными (встроенными), так и с пользовательскими типами.

Указатели используют «разыменование» * , «адрес операторов« & и« arrow » -> . Операторы '*' и '->' используются для доступа к выделенной памяти, а оператор & используется для получения адреса в памяти.

Синтаксис

  • <Тип данных> * <Имя переменной>;
  • <Тип данных> * <Имя переменной> = & <Переменное имя одного и того же типа данных>;
  • <Тип данных> * <Имя переменной> = <Значение одного и того же типа данных>;
  • int * foo; // Указатель, который указывает на целочисленное значение
  • int * bar = & myIntVar;
  • long * bar [2];
  • long * bar [] = {& myLongVar1, & myLongVar2}; // Равно: long * bar [2]

замечания

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

int* a, b, c; //Only a is a pointer, the others are regular ints.

int* a, *b, *c; //These are three pointers!

int *foo[2]; //Both *foo[0] and *foo[1] are pointers.

Основы указателя

C ++ 11

Примечание: во всех следующих случаях предполагается существование константы nullptr C ++ 11 ++. Для более ранних версий замените nullptr на NULL , константу, которая использовалась для аналогичной роли.

Создание указательной переменной

Переменная указателя может быть создана с использованием специального синтаксиса * , например int *pointer_to_int; ,
Когда переменная имеет тип указателя ( int * ), она просто содержит адрес памяти. Адрес памяти - это местоположение, в котором хранятся данные базового типа ( int ).

Разница очевидна при сравнении размера переменной с размером указателя на один и тот же тип:

// Declare a struct type `big_struct` that contains
// three long long ints.
typedef struct {
    long long int foo1;
    long long int foo2;
    long long int foo3;
} big_struct;

// Create a variable `bar` of type `big_struct`
big_struct bar;
// Create a variable `p_bar` of type `pointer to big_struct`.
// Initialize it to `nullptr` (a null pointer).
big_struct *p_bar0 = nullptr;

// Print the size of `bar`
std::cout << "sizeof(bar) = " << sizeof(bar) << std::endl;
// Print the size of `p_bar`.
std::cout << "sizeof(p_bar0) = " << sizeof(p_bar0) << std::endl;

/* Produces:
    sizeof(bar) = 24
    sizeof(p_bar0) = 8
*/

Принимая адрес другой переменной

Указатели могут быть назначены между собой так же, как и обычные переменные; в этом случае это адрес памяти, который копируется из одного указателя в другой, а не фактические данные , на которые указывает указатель.
Более того, они могут принимать значение nullptr которое представляет собой нулевую ячейку памяти. Указатель, равный nullptr содержит недопустимую ячейку памяти и, следовательно, не относится к действительным данным.

Вы можете получить адрес памяти переменной данного типа, префиксной переменной с адресом оператора & . Значение, возвращаемое символом & является указателем на базовый тип, который содержит адрес памяти переменной (который является допустимыми данными до тех пор, пока переменная не выходит за рамки ).

// Copy `p_bar0` into `p_bar_1`.
big_struct *p_bar1 = p_bar0;

// Take the address of `bar` into `p_bar_2`
big_struct *p_bar2 = &bar;

// p_bar1 is now nullptr, p_bar2 is &bar.

p_bar0 = p_bar2;

// p_bar0 is now &bar.

p_bar2 = nullptr;

// p_bar0 == &bar
// p_bar1 == nullptr
// p_bar2 == nullptr

В отличие от ссылок:

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

Доступ к контенту указателя

Поскольку для принятия адреса требуется & , а для доступа к контенту требуется использование оператора разыменования * в качестве префикса. Когда указатель разыменовывается, он становится переменной базового типа (фактически, ссылкой на него). Затем он может быть прочитан и изменен, если не const .

(*p_bar0).foo1 = 5;

// `p_bar0` points to `bar`. This prints 5.
std::cout << "bar.foo1 = " << bar.foo1 << std::endl;

// Assign the value pointed to by `p_bar0` to `baz`.
big_struct baz;
baz = *p_bar0;

// Now `baz` contains a copy of the data pointed to by `p_bar0`.
// Indeed, it contains a copy of `bar`.

// Prints 5 as well
std::cout << "baz.foo1 = " << baz.foo1 << std::endl;

Сочетание * и оператора . сокращенно -> :

std::cout << "bar.foo1 = " << (*p_bar0).foo1 << std::endl; // Prints 5
std::cout << "bar.foo1 = " <<  p_bar0->foo1  << std::endl; // Prints 5

Выделение недействительных указателей

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

big_struct *never_do_this() {
   // This is a local variable. Outside `never_do_this` it doesn't exist.
   big_struct retval;
   retval.foo1 = 11;
   // This returns the address of `retval`.
   return &retval;
   // `retval` is destroyed and any code using the value returned
   // by `never_do_this` has a pointer to a memory location that
   // contains garbage data (or is inaccessible).
}

В таком сценарии g++ и clang++ корректно выдают предупреждения:

(Clang) warning: address of stack memory associated with local variable 'retval' returned [-Wreturn-stack-address]
(Gcc)   warning: address of local variable ‘retval’ returned [-Wreturn-local-addr]

Следовательно, следует проявлять осторожность, когда указатели являются аргументами функций, поскольку они могут быть нулевыми:

void naive_code(big_struct *ptr_big_struct) {
    // ... some code which doesn't check if `ptr_big_struct` is valid.
    ptr_big_struct->foo1 = 12;
}

// Segmentation fault.
naive_code(nullptr);

Операции указателя

Для указателей есть два оператора: Address-of operator (&): возвращает адрес памяти своего операнда. Оператор Contents-of (Dereference) (*): Возвращает значение переменной, расположенную по адресу, указанному его оператором.

int var = 20;
int *ptr;
ptr = &var;

cout << var << endl;
//Outputs 20 (The value of var)

cout << ptr << endl;
//Outputs 0x234f119 (var's memory location)

cout << *ptr << endl;
//Outputs 20(The value of the variable stored in the pointer ptr

Звездочка (*) используется при объявлении указателя для простой цели указывать, что это указатель. Не путайте это с оператором разыменования , который используется для получения значения, расположенного по указанному адресу. Это просто две разные вещи, представленные одним и тем же знаком.

Арифметика указателей

Приращение / уменьшение

Указатель может быть увеличен или уменьшен (префикс и постфикс). Приращение указателя продвигает значение указателя к элементу в массиве одним элементом за текущий элемент, указанный в данный момент. Уменьшение указателя перемещает его в предыдущий элемент массива.

Арифметика указателя не допускается, если тип, на который указывает указатель, не завершен. void всегда является неполным типом.

char* str = new char[10]; // str = 0x010
++str;                    // str = 0x011  in this case sizeof(char) = 1 byte

int* arr = new int[10];   // arr = 0x00100
++arr;                    // arr = 0x00104 if sizeof(int) = 4 bytes

void* ptr = (void*)new char[10];
++ptr;    // void is incomplete.

Если указатель на конечный элемент увеличивается, то указатель указывает на один элемент за конец массива. Такой указатель не может быть разыменован, но он может быть уменьшен.

Увеличение указателя на один из прошедших элементов в массиве или уменьшение указателя на первый элемент в массиве приводит к неопределенному поведению.

Указатель на объект без массива можно обрабатывать для арифметики указателя, как если бы это был массив размером 1.

Добавление / вычитание

Целочисленные значения могут быть добавлены к указателям; они действуют как приращение, а на определенное число, а не на 1. Целочисленные значения также можно вычесть из указателей, действуя как декремент указателя. Как и при добавлении / уменьшении, указатель должен указывать на полный тип.

char* str = new char[10];  // str = 0x010
str += 2;                  // str = 0x010 + 2 * sizeof(char) = 0x012

int* arr = new int[10];    // arr = 0x100
arr += 2;                  // arr = 0x100 + 2 * sizeof(int) = 0x108, assuming sizeof(int) == 4.

Различие в указателях

Можно вычислить разницу между двумя указателями на один и тот же тип. Два указателя должны быть в пределах одного объекта массива; в противном случае результаты неопределенного поведения.

Учитывая два указателя P и Q в одном массиве, если P - i й элемент в массиве, а Q - j й элемент, то P - Q будет i - j . Тип результата: std::ptrdiff_t , from <cstddef> .

char* start = new char[10];  // str = 0x010
char* test = &start[5];
std::ptrdiff_t diff = test - start; //Equal to 5.
std::ptrdiff_t diff = start - test; //Equal to -5; ptrdiff_t is signed.


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow