Перейти к содержимому

Pointer to c что это

  • автор:

Указатели (C++)

Указатель — это переменная, в которой хранится адрес памяти объекта. Указатели широко используются как в C, так и в C++ для трех основных целей:

  • для выделения новых объектов в куче,
  • передача функций другим функциям
  • для итерации элементов в массивах или других структурах данных.

В программировании в стиле C необработанные указатели используются для всех этих сценариев. Однако необработанные указатели являются источником многих серьезных ошибок программирования. Поэтому их использование настоятельно не рекомендуется, за исключением случаев, когда они обеспечивают значительное преимущество производительности, и нет неоднозначности в отношении того, какой указатель является владельцем указателя, который отвечает за удаление объекта. Современный C++ предоставляет интеллектуальные указатели для выделения объектов, итераторов для обхода структур данных и лямбда-выражений для передачи функций. Используя эти средства языка и библиотеки вместо необработанных указателей, вы сделаете программу более безопасной, проще отладить и упростить понимание и обслуживание. Дополнительные сведения см . в смарт-указателях, итераторах и лямбда-выражениях .

В этом разделе

  • Необработанные указатели
  • Константные и переменные указатели
  • новые и удаленные операторы
  • Интеллектуальные указатели
  • Практическое руководство. Создание и использование экземпляров unique_ptr
  • Практическое руководство. Создание и использование экземпляров shared_ptr
  • Практическое руководство. Создание и использование экземпляров weak_ptr
  • Практическое руководство. Создание и использование экземпляров CComPtr и CComQIPtr

Необработанные указатели (C++)

Указатель — это тип переменной. Он хранит адрес объекта в памяти и используется для доступа к нему. Необработанный указатель — это указатель, время существования которого не контролируется инкапсулирующим объектом, например умным указателем. Необработанный указатель можно назначить адрес другой переменной, отличной от указателя, или его можно назначить значение nullptr . Указатель, который не был назначен значению, содержит случайные данные.

Указатель также можно разыменовать , чтобы получить значение объекта, на который он указывает. Оператор доступа к членам объекта предоставляет доступ к членам объекта.

 int* p = nullptr; // declare pointer and initialize it // so that it doesn't store a random address int i = 5; p = &i; // assign pointer to address of object int j = *p; // dereference p to retrieve the value at its address 

Указатель может указывать на типизированный объект или на void . Когда программа выделяет объект в куче в памяти, он получает адрес этого объекта в виде указателя. Такие указатели называются указателями владения. Указатель на владение (или копию) должен использоваться для явного освобождения выделенного кучи объекта, если он больше не нужен. Сбой освобождения памяти приводит к утечке памяти и отрисовывает, что расположение памяти недоступно для любой другой программы на компьютере. Выделение new памяти должно быть освобождено с помощью delete (или delete[] ). Дополнительные сведения см. в разделе new и delete операторах.

 MyClass* mc = new MyClass(); // allocate object on the heap mc->print(); // access class member delete mc; // delete object (please don't forget!) 

Указатель (если он не объявлен как const ) может быть добавочным или уменьшаться, чтобы указать другое расположение в памяти. Эта операция называется арифметикой указателя. Он используется в программировании в стиле C для итерации элементов в массивах или других структурах данных. const Указатель не может указывать на другое расположение памяти, и в этом смысле аналогичен ссылке . Дополнительные сведения см. в разделе const и volatile указателях.

 // declare a C-style string. Compiler adds terminating '\0'. const char* str = "Hello world"; const int c = 1; const int* pconst = &c; // declare a non-const pointer to const int const int c2 = 2; pconst = &c2; // OK pconst itself isn't const const int* const pconst2 = &c; // pconst2 = &c2; // Error! pconst2 is const. 

В 64-разрядных операционных системах указатель имеет размер 64 бита. Размер указателя системы определяет, сколько адресной памяти она может иметь. Все копии указателя указывают на одно расположение памяти. Указатели (а также ссылки) широко используются в C++ для передачи больших объектов в функции и из них. Зачастую это более эффективно для копирования адреса объекта, чем для копирования всего объекта. При определении функции укажите параметры указателя, const если не планируется изменять объект. Как правило, ссылки являются предпочтительным способом передачи объектов в функции, const если не может быть nullptr значение объекта.

Указатели на функции позволяют передавать функции другим функциям. Они используются для «обратных вызовов» в программировании в стиле C. Современный C++ использует лямбда-выражения для этой цели.

Инициализация и доступ к члену

В следующем примере показано, как объявить, инициализировать и использовать необработанный указатель. Он инициализирован с помощью new указания объекта, выделенного в куче, который необходимо явно delete . В примере также показано несколько опасностей, связанных с необработанными указателями. (Помните, что этот пример — программирование в стиле C, а не современное C++!)

#include #include class MyClass < public: int num; std::string name; void print() < std::cout >; // Accepts a MyClass pointer void func_A(MyClass* mc) < // Modify the object that mc points to. // All copies of the pointer will point to // the same modified object. mc->num = 3; > // Accepts a MyClass object void func_B(MyClass mc) < // mc here is a regular object, not a pointer. // Use the "." operator to access members. // This statement modifies only the local copy of mc. mc.num = 21; std::cout int main() < // Use the * operator to declare a pointer type // Use new to allocate and initialize memory MyClass* pmc = new MyClass< 108, "Nick" >; // Prints the memory address. Usually not what you want. std:: cout operator to access the object's public members pmc->print(); // "Nick, 108" // Copy the pointer. Now pmc and pmc2 point to same object! MyClass* pmc2 = pmc; // Use copied pointer to modify the original object pmc2->name = "Erika"; pmc->print(); // "Erika, 108" pmc2->print(); // "Erika, 108" // Pass the pointer to a function. func_A(pmc); pmc->print(); // "Erika, 3" pmc2->print(); // "Erika, 3" // Dereference the pointer and pass a copy // of the pointed-to object to a function func_B(*pmc); pmc->print(); // "Erika, 3" (original not modified by function) delete(pmc); // don't forget to give memory back to operating system! // delete(pmc2); //crash! memory location was already deleted > 

Арифметические указатели и массивы

Указатели и массивы тесно связаны. Когда массив передается по значению функции, он передается в качестве указателя на первый элемент. В следующем примере показаны следующие важные свойства указателей и массивов:

  • Оператор sizeof возвращает общий размер в байтах массива
  • Чтобы определить количество элементов, разделите общее число байтов по размеру одного элемента
  • Когда массив передается функции, он распадается на тип указателя.
  • sizeof Если оператор применяется к указателю, он возвращает размер указателя, например 4 байта в x86 или 8 байтах на x64
#include void func(int arr[], int length) < // returns pointer size. not useful here. size_t test = sizeof(arr); for(int i = 0; i < length; ++i) < std::cout > int main() < int i[5]< 1,2,3,4,5 >; // sizeof(i) = total bytes int j = sizeof(i) / sizeof(i[0]); func(i,j); > 

Некоторые арифметические операции можно использовать на не-указателях const , чтобы они указывали на другое расположение памяти. Указатели увеличивается и уменьшается с помощью ++ операторов , += -= а также операторов. Этот метод можно использовать в массивах и особенно полезен в буферах нетипизированных данных. Значение void* увеличивается по размеру char (1 байта). Типизированный указатель увеличивается по размеру типа, на который он указывает.

В следующем примере показано, как арифметический указатель можно использовать для доступа к отдельным пикселям в растровом рисунке в Windows. Обратите внимание на использование new и delete оператор разыменования.

#include #include using namespace std; int main() < BITMAPINFOHEADER header; header.biHeight = 100; // Multiple of 4 for simplicity. header.biWidth = 100; header.biBitCount = 24; header.biPlanes = 1; header.biCompression = BI_RGB; header.biSize = sizeof(BITMAPINFOHEADER); constexpr int bufferSize = 30000; unsigned char* buffer = new unsigned char[bufferSize]; BITMAPFILEHEADER bf; bf.bfType = 0x4D42; bf.bfSize = header.biSize + 14 + bufferSize; bf.bfReserved1 = 0; bf.bfReserved2 = 0; bf.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //54 // Create a gray square with a 2-pixel wide outline. unsigned char* begin = &buffer[0]; unsigned char* end = &buffer[0] + bufferSize; unsigned char* p = begin; constexpr int pixelWidth = 3; constexpr int borderWidth = 2; while (p < end) < // Is top or bottom edge? if ((p < begin + header.biWidth * pixelWidth * borderWidth) || (p >end - header.biWidth * pixelWidth * borderWidth) // Is left or right edge? || (p - begin) % (header.biWidth * pixelWidth) < (borderWidth * pixelWidth) || (p - begin) % (header.biWidth * pixelWidth) >((header.biWidth - borderWidth) * pixelWidth)) < *p = 0x0; // Black >else < *p = 0xC3; // Gray >p++; // Increment one byte sizeof(unsigned char). > ofstream wf(R"(box.bmp)", ios::out | ios::binary); wf.write(reinterpret_cast(&bf), sizeof(bf)); wf.write(reinterpret_cast(&header), sizeof(header)); wf.write(reinterpret_cast(begin), bufferSize); delete[] buffer; // Return memory to the OS. wf.close(); > 

void* Указатели

Указатель на void простое расположение необработанной памяти. Иногда необходимо использовать void* указатели, например при передаче между кодом C++ и функциями C.

Если типизированный указатель приведение к void указателю, содержимое расположения памяти не изменяется. Однако сведения о типе теряются, поэтому нельзя выполнять операции добавочного или уменьшения. Расположение памяти может быть приведение, например, из MyClass* и void* обратно в MyClass* . Такие операции по сути подвержены ошибкам и требуют большогоvoid ухода за ошибками. Современный C++ не рекомендует использовать указатели void практически во всех обстоятельствах.

//func.c void func(void* data, int length) < char* c = (char*)(data); // fill in the buffer with data for (int i = 0; i < length; ++i) < *c = 0x41; ++c; >> // main.cpp #include extern "C" < void func(void* data, int length); >class MyClass < public: int num; std::string name; void print() < std::cout >; int main() < MyClass* mc = new MyClass; void* p = static_cast(mc); MyClass* mc2 = static_cast(p); std::cout name (pvoid); for(char* c = pchar; c < pchar + 1000; ++c) < *c = 0x00; >func(pvoid, 1000); char ch = static_cast(pvoid)[0]; std::cout 

Указатели на функции

В программировании в стиле C указатели функций используются в основном для передачи функций другим функциям. Этот метод позволяет вызывающему объекту настраивать поведение функции, не изменяя ее. В современных выражениях C++лямбда-выражения обеспечивают ту же возможность с большей безопасностью типов и другими преимуществами.

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

// Declare pointer to any function that. // . accepts a string and returns a string string (*g)(string a); // has no return value and no parameters void (*x)(); // . returns an int and takes three parameters // of the specified types int (*i)(int i, string s, double d); 

В следующем примере показана функция, которая принимает в качестве параметра любую функцию combine , которая принимает и std::string возвращает значение std::string . В зависимости от передаваемой combine функции он либо добавляет строку, либо добавляет строку.

#include #include using namespace std; string base ; string append(string s) < return base.append(" ").append(s); >string prepend(string s) < return s.append(" ").append(base); >string combine(string s, string(*g)(string a)) < return (*g)(s); >int main()

Указатели в C++

Указатель (пойнтер, англ. pointer) — это переменная, содержащая адрес другой переменной. Тип данных pointer равен 4 байта. Указатели очень широко используются в языке C. Это происходит отчасти потому, что иногда они дают единственную возможность выразить нужное действие, а отчасти потому, что они обычно ведут к более компактным и эффективным программам, чем те, которые могут быть получены другими способами.

Следует четко понимать, что компилятору абсолютно безразлично, как написано объявление int *p или int* p. Программист может выбрать свой стиль. Однако символы & и * лучше связывать с переменными, а не типом. Так как в соответствии с правилами языка C++ символ * (как и символ &) связывается с отдельной переменной, а не ее типом.

Адрес переменной — это адрес первого байта переменной.
Указатель должен быть равен NULL или указывать на адрес переменной.

Так как указатель содержит адрес объекта, это дает возможность «косвенного» доступа к этому объекту через указатель.

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

int * pi = NULL;

Знак & используется для получения адреса переменной.

pi = &i; cout
Разыменование - изменение значения переменной на который указывает указатель.
*pi=48;

#include using namespace std; /* возвращает длину строки s */ int strlen(char *s) < int n; for (n = 0; *s != '\0'; s++) n++; return(n); >int main() < int i; int *pi = NULL; pi = &i; cout<<&i<Указатели и массивы

В языке C существует сильная взаимосвязь между указателями и массивами , настолько сильная, что указатели и массивы действительно следует рассматривать одновременно. Любую операцию, которую можно выполнить с помощью индексов массива, можно сделать и с помощью указателей. Вариант с указателями обычно оказывается более быстрым, но и несколько более трудным для непосредственного понимания. Описание

int a[10];

определяет массив размера 10, т.е. набор из 10 последовательных объектов, называемых a[0], a[1], …, a[9]. Запись a[i] соответствует элементу массива через i позиций от начала. Если pa - указатель целого, описанный как

int *pa;

приводит к тому, что pa указывает на нулевой элемент массива a. Это означает, что pa содержит адрес элемента a[0]. Теперь присваивание

x = *pa

будет копировать содержимое a[0] в x.

Если ра указывает на некоторый определенный элемент массива a, то по определению pa+1 указывает на следующий элемент, и вообще pa-i указывает на элемент, стоящий на i позиций до элемента, указываемого pa, а pa+i на элемент, стоящий на i позиций после. Таким образом, если pa указывает на a[0], то *(pa+1)

ссылается на содержимое a[1], pa+i - адрес a[i], а *(pa+i) - содержимое a[i].

Эти замечания справедливы независимо от типа переменных в массиве a. Суть определения "добавления 1 к указателю", а также его распространения на всю арифметику указателей, состоит в том, что приращение масштабируется размером памяти, занимаемой объектом, на который указывает указатель. Таким образом, i в pa+i перед прибавлением умножается на размер объектов, на которые указывает pa.

Очевидно существует очень тесное соответствие между индексацией и арифметикой указателей. В действительности компилятор преобразует ссылку на массив в указатель на начало массива. В результате этого имя массива является указательным выражением. Отсюда вытекает несколько весьма полезных следствий. Так как имя массива является синонимом местоположения его нулевого элемента, то присваивание pa = &a[0]

можно записать как pa = a.

Еще более удивительным, по крайней мере на первый взгляд, кажется тот факт, что ссылку на a[i] можно записать в виде *(a+i). При анализировании выражения a[i] в языке C оно немедленно преобразуется к виду *(a+i); эти две формы совершенно эквивалентны. Если применить операцию & к обеим частям такого соотношения эквивалентности, то мы получим, что &a[i] и a+i тоже идентичны: a+i - адрес i-го элемента от начала a. С другой стороны, если pa является указателем, то в выражениях его можно использовать с индексом: pa[i] идентично *(pa+i). Короче, любое выражение, включающее массивы и индексы, может быть записано через указатели и смещения и наоборот, причем даже в одном и том же утверждении.

Имеется одно различие между именем массива и указателем, которое необходимо иметь в виду. Указатель является переменной, так что операции pa=a и pa++ имеют смысл. Но имя массива является константой, а не переменной: конструкции типа a=pa или a++,или p=&a будут незаконными.

Когда имя массива передается функции, то на самом деле ей передается местоположение начала этого массива. Внутри вызванной функции такой аргумент является точно такой же переменной, как и любая другая, так что имя массива в качестве аргумента действительно является указателем, т.е. переменной, содержащей адрес. Мы можем использовать это обстоятельство для написания нового варианта функции strlen, вычисляющей длину строки: /* возвращает длину строки s */

int strlen(char *s)

Операция увеличения s совершенно законна, поскольку эта переменная является указателем, s++ никак не влияет на символьную строку в обратившейся к strlen функции, а только увеличивает локальную для функции strlen копию адреса.

Описания формальных параметров в определении функции в виде char s[];

совершенно эквивалентны; какой вид описания следует предпочесть, определяется в значительной степени тем, какие выражения будут использованы при написании функции. Если функции передается имя массива, то в зависимости от того, что удобнее, можно полагать, что функция оперирует либо с массивом, либо с указателем, и действовать далее соответвующим образом. Можно даже использовать оба вида операций, если это кажется уместным и ясным.

Имя массива - это константа, представляющая собой указатель на 0-ой элемент массива. Этот указатель отличается от обычных тем, что его нельзя изменить (установить на другую переменную), поскольку он сам хранится не в переменной, а является просто некоторым постоянным адресом.

массив указатель ____________ _____ array: | array[0] | ptr:| * | | array[1] | | | array[2] |

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

ptr = array; или ptr = &array[0]; но не ptr = &array;

Указатели - не целые числа! Хотя физически это и номера байтов, адресная арифметика отличается от обычной.

Как описывать ссылки (указатели) на двумерные массивы?

Как описывать ссылки (указатели) на двумерные массивы? Рассмотрим такую программу:

#include #define First 3 #define Second 5 char arr[First][Second] = < "ABC.", < 'D', 'E', 'F', '?', '\0' >, < 'G', 'H', 'Z', '!', '\0' >>; char (*ptr)[Second]; main()< int i; ptr = arr; /* arr и ptr теперь взаимозаменимы */ for(i=0; i

Указателем здесь является ptr. Отметим, что у него задана размерность по второму измерению: Second, именно для того, чтобы компилятор мог правильно вычислить двумерные индексы.

Попробуйте сами объявить

char (*ptr)[4]; char (*ptr)[6]; char **ptr;

и увидеть, к каким невеселым эффектам это приведет (компилятор, кстати, будет ругаться; но есть вероятность, что он все же странслирует это для вас. Но работать оно будет плачевно). Попробуйте также использовать ptr[x][y].

Обратите также внимание на инициализацию строк в нашем примере. Строка "ABC." равносильна объявлению

Указатели на функции

Например, используется в функции: Быстрая сортировка (англ. quicksort), часто называемая qsort. В функции qsort указатель на функцию применяется для указания способа сортировки.

Прежде чем вводить указатель на функцию, напомним, что каждая функция характеризуется типом возвращаемого значения, именем и сигнатурой. Напомним, что сигнатура определяется количеством, порядком следования и типами параметров. Иногда говорят, что сигнатурой функции называется список типов ее параметров.

А теперь путём последовательности утверждений придем к обсуждению темы данного раздела урока.

1. При использовании имени функции без последующих скобок и параметров имя функции выступает в качестве указателя на эту функцию, и его значением служит адрес размещения функции в памяти.

2. Это значение адреса может быть присвоено некоторому указателю, и затем уже этот новый указатель можно применять для вызова функции.

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

4. Указатель на функцию определяется следующим образом:

тип_функции (*имя_указателя)(спецификация_параметров);

Например: int (*func1Ptr) (char); - определение указателя func1Ptr на функцию с параметром типа char, возвращающую значение типа int.

Примечание: Будьте внимательны. Если приведенную синтаксическую конструкцию записать без первых круглых скобок, т.е. в виде int *fun (char); то компилятор воспримет ее как прототип некой функции с именем fun и параметром типа char, возвращающей значение указателя типа int *. Второй пример: char * (*func2Ptr) (char * ,int); - определение указателя func2Ptr на функцию с параметрами типа указатель на char и типа int, возвращающую значение типа указатель на char.

Иллюстрируем на практике. В определении указателя на функцию тип возвращаемого значения и сигнатура (типы, количество и последовательность параметров) должны совпадать с соответствующими типами и сигнатурами тех функций, адреса которых предполагается присваивать вводимому указателю при инициализации или с помощью оператора присваивания. В качестве простейшей иллюстрации сказанного приведем программу с указателем на функцию:

#include using namespace std; void f1(void) // Определение f1. < cout void f2(void) // Определение f2. < cout void main() < void (*ptr)(void); // ptr - указатель на функцию. ptr = f2; // Присваивается адрес f2(). (*ptr)(); // Вызов f2() по ее адресу. ptr = f1; // Присваивается адрес f1(). (*ptr)(); // Вызов f1() по ее адресу. ptr(); // Вызов эквивалентен (*ptr)(); >

Результат выполнения программы:

Load f2() Load f1() Load f1() Press any key to continue

Здесь значением имени_указателя служит адрес функции, а с помощью операции разыменования * обеспечивается обращение по адресу к этой функции. Однако будет ошибкой записать вызов функции без скобок в виде *ptr();. Дело в том, что операция () имеет более высокий приоритет, нежели операция обращения по адресу *. Следовательно, в соответствии с синтаксисом будет вначале сделана попытка обратиться к функции ptr(). И уже к результату будет отнесена операция разыменования, что будет воспринято как синтаксическая ошибка.

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

При присваивании указателей на функции также необходимо соблюдать соответствие типов возвращаемых значений функций и сигнатур для указателей правой и левой частей оператора присваивания. То же справедливо и при последующем вызове функций с помощью указателей, т.е. типы и количество фактических параметров, используемых при обращении к функции по адресу, должны соответствовать формальным параметрам вызываемой функции. Например, только некоторые из следующих операторов будут допустимы:

char f1(char) // Определение функции. char f2(int) // Определение функции. void f3(float) // Определение функции. int* f4(char *) // Определение функции. char (*pt1)(int); // Указатель на функцию. char (*pt2)(int); // Указатель на функцию. void (*ptr3)(float) = f3; // Инициализированный указатель. void main() < pt1 = f1; // Ошибка - несоответствие сигнатур. pt2 = f3; // Ошибка - несоответствие типов (значений и сигнатур). pt1 = f4; // Ошибка - несоответствие типов. pt1 = f2; // Правильно. pt2 = pt1; // Правильно. char с = (*pt1)(44); // Правильно. с = (*pt2)('\t'); // Ошибка - несоответствие сигнатур. >

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

#include using namespace std; // Функции одного типа с одинаковыми сигнатурами: int add(int n, int m) < return n + m; >int division(int n, int m) < return n/m; >int mult(int n, int m) < return n * m; >int subt(int n, int m) < return n - m; >void main() < int (*par)(int, int); // Указатель на функцию. int a = 6, b = 2; char c = '+'; while (c != ' ') < cout cout >

Результаты выполнения программы:

Arguments: a = 6, b = 2. Result for c = '+':8 Arguments: a = 8, b = 2. Result for c = '/':4 Arguments: a = 4, b = 2. Result for c = '*':8 Arguments: a = 8, b = 2. Result for c = '-':6 Press any key to continue

Цикл продолжается, пока значением переменной c не станет пробел. В каждой итерации указатель par получает адрес одной из функций, и изменяется значение c. По результатам программы легко проследить порядок выполнения ее операторов.

Массивы указателей на функции. Указатели на функции могут быть объединены в массивы. Например, float (*ptrArray[4]) (char) ; - описание массива с именем ptrArray из четырех указателей на функции, каждая из которых имеет параметр типа char и возвращает значение типа float. Чтобы обратиться, например, к третьей из этих функций, потребуется такой оператор:

float а = (*ptrArray[2])('f');

Как обычно, индексация массива начинается с 0, и поэтому третий элемент массива имеет индекс 2.

Массивы указателей на функции удобно использовать при разработке всевозможных меню, точнее программ, управление которыми выполняется с помощью меню. Для этого действия, предлагаемые на выбор будущему пользователю программы, оформляются в виде функций, адреса которых помещаются в массив указателей на функции. Пользователю предлагается выбрать из меню нужный ему пункт (в простейшем случае он вводит номер выбираемого пункта) и по номеру пункта, как по индексу, из массива выбирается соответствующий адрес функции. Обращение к функции по этому адресу обеспечивает выполнение требуемых действий. Самую общую схему реализации такого подхода иллюстрирует следующая программа для "обработки файлов":

#include using namespace std; /* Определение функций для обработки меню (функции фиктивны т. е. реальных действий не выполняют):*/ void act1 (char* name) < cout void act2 (char* name) < cout void act3 (char* name) < cout void act4 (char* name) < cout void act5 (char* name) < cout void main() < // Создание и инициализация массива указателей void (*MenuAct[5])(char*) = ; int number; // Номер выбранного пункта меню. char FileName[30]; // Строка для имени файла. // Реализация меню cout > number; if (number>>= 1 && number if (number != 5) < cout > FileName; // Читать имя файла. > else break; // Вызов функции по указателю на нее: (*MenuAct[number-1])(FileName); > // Конец бесконечного цикла. >

Пункты меню повторяются, пока не будет введен номер 5 - закрытие.

Указатели в C++

В этой статье мы с вами рассмотрим понятие указателя в языке C++. Указатель (англ. pointer) в C++ - это переменная, которая содержит адрес памяти другой переменной. Как и любая другая переменная, указатель должен быть сначала объявлен перед тем, как вы начнёте его использовать. Для объявления переменной-указателя используется символ "звёздочки" ( * ), идущий после того типа данных, на который и будет указывать переменная-указатель.

Давайте сразу посмотрим на небольшой пример использования указателя в программе на C++, чтобы прояснить всё вышесказанное:

#include using namespace std; int main() < // объявляем три переменные целого типа с именами a, b и c int a = 10, b = 20, c = 30; // объявляем переменную-указатель с именем p int* p; // выводим на экран консоли значения всех трёх переменных cout 

Я специально снабдил код примера комментариями, чтобы каждая строка была полностью понятна, но всё же остановлюсь на ключевых моментах этого примера. Вначале мы объявили три целочисленных переменных с именами a, b и c, проинициализировав их значениями 10, 20 и 30, соответственно. Дальше мы объявили переменную-указатель с именем p. Как видите, в её объявлении указан тип int*, что означает "указатель на значение типа int". Как можно видеть, в объявлении переменной p мы сначала не присваиваем ей никакого конкретного значения, это значит, что p будет содержать какое-то непредсказуемое значение, т.е. какой-то адрес, который не указывает на что-то конкретное. Обращаться к переменной-указателю на этой стадии нельзя: если вы попытаетесь использовать переменную-указатель до её инициализации, компилятор C++ не позволит этого сделать и выдаст ошибку, а такая программа у вас просто не скомпилируется.

Дальше мы просто выводим значения наших переменных a, b и c на экран консоли. Это нужно для последующей части нашей программы-примера, чтобы сравнить результаты доступа к переменным с помощью нашей переменной-указателя p. После этого мы присваиваем нашей переменной p значение адреса переменной a:

Этим присвоением мы как бы "настраиваем" переменную-указатель p на то, чтобы она указывала на переменную a. Дальше идут две интересных строки с выводом на консоль, где мы вначале выводим содержимое самой переменной p, а после этого обращаемся к содержимому ячейки памяти, на которую указывает p посредством указания символа-звёздочки перед p, т.е. *p:

 cout 

Такая запись *p называется разыменованием указателя. Как вы уже, наверное, поняли, разыменование необходимо для того, чтобы обратиться к самим данным, на которые указывает в текущий момент времени переменная-указатель.

Оставшаяся часть программы делает аналогичную "перенастройку" переменной-указателя p на две другие переменные b и c и также выводит на экран консоли значения адресов этих переменных и значения самих переменных.

После запуска этой программы у меня на экране консоли отобразилось следующее:

The value of 'a' is: 10
The value of 'b' is: 20
The value of 'c' is: 30
Pointer address is currently 00CFF91C
Pointer value is currently 10
Pointer address is currently 00CFF910
Pointer value is currently 20
Pointer address is currently 00CFF904
Pointer value is currently 30

Обратите внимание, в моём случае адреса памяти для переменных a, b и c равны 00CFF91C, 00CFF910 и 00CFF904, соответственно (у вас они могут и скорее всего будут отличаться, это непринципиально). Также мы видим, что после каждой перенастройки переменной-указателя при разыменовании указателя p мы получаем доступ к самим значениям этих переменных, которые и выводятся на экран консоли.

Напоследок запомните следующие важные моменты про указатели в языке C++:

  • Переменная-указатель всегда хранит адрес памяти, на который она указывает, а не сами данные.
  • Для объявления переменной-указателя необходимо указать тип данных, на которые будет указывать указатель, затем символ-звёздочку ( * ) и после этого имя самой переменной. Например, чтобы объявить переменную-указатель с именем x на тип данных char, используется запись: char* x;
  • Для разыменования указателя применяется символ-звёздочка ( * ), идущий перед именем переменной-указателя. Например, если ваша переменная-указатель имеет имя x, то её разыменование записывается как *x
  • Для инициализации переменной-указателя используется ссылка ( с помощью & ) на какую-то объявленную переменную в Вашей программе. К примеру, если у вас уже была объявлена другая переменная с именем y, и её тип данных char, то можно проинициализировать указатель x следующим образом: x = &y;
  • Не пытайтесь произвести разыменование переменной-указателя перед тем, как присвоите переменной корректное значение адреса какой-то объявленной переменной соответствующего типа.

Попробуйте выполнить следующие упражнения для закрепления темы указателей в языке C++:

  • Объявите в своей программе три переменных x, y и z с типом double. Проинициализируйте их какими-то значениями на свой вкус. Также объявите три переменных-указателя с именами px, py, pz и проинициализируйте их таким образом, чтобы они указывали на x, y и z, соответственно. Выведите значения указателей px, py, pz (адреса памяти) на экран консоли. Также выведите на консоль значения самих переменных x, y и z с помощью доступа к ним через переменные-указатели px, py и pz и их разыменование. После этого присвойте указателю pz адрес переменной x, а указателю px присвойте адрес переменной z. Выведите на экран консоли результат разыменования для px и pz.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *