ptr — атрибут
Атрибут [ptr] обозначает указатель как полный указатель.
pointer_default(ptr) typedef [ ptr [ , type-attribute-list ] ] type-specifier declarator-list; typedef [ struct | union ] < [ ptr [ , field-attribute-list ] ] type-specifier declarator-list; . >[ ptr [ , function-attribute-list ] ] type-specifier ptr-decl function-name( [ [ parameter-attribute-list ] ] type-specifier [standard-declarator] , . ); [[ [ function-attribute-list ] ]] type-specifier [[ptr-decl]] function-name( [ ptr [[ , parameter-attribute-list ]] ] type-specifier [[standard-declarator]] , . );
Параметры
Указывает один или несколько атрибутов, применяемых к типу. Допустимые атрибуты типа: [handle], [switch_type], [transmit_as]; атрибут указателя [ref], [unique или [ptr]; и атрибуты использования [context_handle], [string] и [ignore]. Разделяйте несколько атрибутов запятыми.
Указывает базовый тип, структуру, объединение, тип перечисления или идентификатор типа. Необязательная спецификация хранилища может предшествовать описательу типа.
Указывает стандартный декларатор C, например идентификатор, декларатор указателя или декларатор массива. Дополнительные сведения см. в разделе Атрибуты массива и Sized-Pointer, массивы и массивы и указатели.
Задает стандартные деклараторы C, такие как идентификаторы, деклараторы указателей и деклараторы массива. Дополнительные сведения см. в разделе Атрибуты массива и Sized-Pointer, массивы и массивы и указатели. Список деклараторов состоит из одного или нескольких деклараторов, разделенных запятыми. Идентификатор parameter-name в деклараторе функции является необязательным.
Указывает ноль или несколько атрибутов поля, которые применяются к структуре, члену объединения или параметру функции. Допустимые атрибуты полей: [first_is], [last_is], [length_is], [max_is], [size_is]; атрибуты использования [string], [ignore] и [context_handle]; атрибут указателя [ref], [unique], или [ptr]; и атрибут union [switch_type]. Разделяйте несколько атрибутов полей запятыми.
Указывает ноль или несколько атрибутов, применяемых к функции. Допустимые атрибуты функции: [callback], [local]; атрибут указателя [ref], [unique], или [ptr]; и атрибуты использования [string], [ignore] и [context_handle].
Указывает по крайней мере один декларатор указателя, к которому применяется атрибут [ptr] . Декларатор указателя совпадает с декларатором указателя, используемым в C; он создается из конструктора *, модификаторов, таких как far, и const квалификатора.
Указывает имя удаленной процедуры.
Состоит из нуля или нескольких атрибутов, соответствующих указанному типу параметра. Атрибуты параметров могут принимать атрибуты направления в ииз него; атрибуты полей first_is, last_is, length_is, max_is, size_is и switch_type; ссылка на атрибут указателя, уникальный или [ptr]; и атрибуты использования context_handle и string. Атрибут использования ignore нельзя использовать в качестве атрибута параметра. Разделяйте несколько атрибутов запятыми.
Комментарии
Полный указатель, обозначенный атрибутом [ptr] , приближается к полной функциональности указателя языка C. Полный указатель может иметь значение NULL и может изменяться во время вызова с NULL на значение, отличное от NULL. Хранилище, на которое указывают полные указатели, можно получить с помощью других имен в приложении, поддерживающих псевдонимы и циклы. Эта функция требует дополнительных затрат во время удаленного вызова процедуры, чтобы определить данные, на которые ссылается указатель, определить, равно ли значение NULL, и определить, указывают ли два указателя на одни и те же данные.
Используйте полные указатели для:
- Удаленные возвращаемые значения.
- Двойные указатели, если размер выходного параметра неизвестен.
- Указатели NULL .
Полные (и уникальные) указатели нельзя использовать для описания размера массива или объединения, так как эти указатели могут иметь значение NULL. Это ограничение midl предотвращает ошибку, которая может возникнуть при использовании значения NULL в качестве размера.
Предполагается, что ссылочные и уникальные указатели не вызывают псевдонимов данных. Направленный граф, полученный путем запуска из уникального указателя или указателя ссылки и следующих только уникальных или ссылочных указателей, не содержит ни рекогвергенции, ни циклов.
Чтобы избежать псевдонимов, все значения указателя должны быть получены из входного указателя одного класса указателя. Если несколько указателей указывают на одно и то же расположение памяти, все такие указатели должны быть полными.
В некоторых случаях полные и уникальные указатели могут быть смешаны. Полному указателю можно присвоить значение уникального указателя, если назначение не нарушает ограничения на изменение значения уникального указателя. Однако при присвоении уникальному указателю значения полного указателя может возникнуть псевдоним.
Сочетание полных и уникальных указателей может привести к созданию псевдонимов, как показано в следующем примере:
typedef struct < [ptr] short * pdata; // full pointer >GRAPH_NODE_TYPE; typedef struct < [unique] graph_node * left; // unique pointer [unique] graph_node * right; // unique pointer >TREE_NODE_TYPE; // application code: short a = 5; TREE_NODE_TYPE * t; GRAPH_NODE_TYPE g, h; g.pdata = h.pdata = &a; t->left = &g; t->right = &h; // t->left->pdata == t->right->pdata == &a
Хотя «t-left>» и «t-right>» указывают на уникальные расположения памяти, «t-left-pdata>>» и «t-right-pdata>>» являются псевдонимами. По этой причине алгоритмы поддержки псевдонимов должны следовать всем указателям (включая уникальные и ссылочные), которые в конечном итоге могут достичь полного указателя.
Примеры
pointer_default(ptr) typedef [ptr, string] unsigned char * MY_STRING_TYPE; [ptr] char * MyFunction([in, out, unique] long * plNumber);
Указатели в 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++: Указатели
Когда в инструменты добавлены оператор адреса и оператор косвенного обращения, можно поговорить об указателях. В этом уроке мы узнаем что такое указатели, как их объявлять и какие существуют базовые операции.
Объявление указателя
Указатель — это переменная, которая в качестве значения хранит адрес памяти.
Переменные-указатели объявляются так же, как обычные переменные. Только в этом случае ставится звездочка между типом данных и именем переменной. Эта звездочка не является косвенным обращением. Это часть синтаксиса объявления указателя:
int *i_ptr <>; // указатель на значение типа int double *d_ptr <>; // указатель на значение типа double int* i_ptr2 <>; // тоже допустимый синтаксис int * iPtr3<>; // тоже допустимый синтаксис (но не делайте так, это похоже на умножение)Синтаксически C++ принимает звездочку рядом с типом данных, рядом с именем переменной или даже в середине.
При объявлении переменной-указателя нужно ставить звездочку рядом с типом, чтобы его было легче отличить от косвенного обращения.
Как и обычные переменные, указатели не инициализируются при объявлении. Если они не инициализированы значением, они будут содержать мусор.
Указатель X (где X – какой-либо тип) — это обычно используемое сокращение для «указателя на X». Поэтому, когда мы говорим «указатель int», мы на самом деле имеем в виду «указатель на значение типа int».
Хорошей практикой считается инициализировать указатель значением.
Присвоение значения указателю
Поскольку указатели содержат только адреса, когда мы присваиваем значение указателю, это значение должно быть адресом. Одна из самых распространенных вещей, которые делают с указателями, – это хранение в них адреса другой переменной.
Чтобы получить адрес переменной, мы используем оператор адреса:
#include int main() < int num < 5 >; int* ptr < &num >; // инициализируем ptr адресом переменной num std::coutЭта программа создает следующий вывод:
0x7ffc5d336fc8 0x7ffc5d336fc8ptr содержит адрес значения переменной, поэтому мы говорим, что ptr «указывает на» num .
Тип указателя должен соответствовать типу переменной, на которую он указывает:
int i_value < 5 >; double d_value < 7.0 >; int* i_ptr < &iValue >; // ok double* d_ptr < &dValue >; // ok i_ptr = &d_value; // ошибкаТип double не может указывать на адрес переменной типа int . Следующее также некорректно:
int* ptr < 5 >;Это связано с тем, что указатели могут содержать только адреса, а целочисленный литерал 5 не имеет адреса памяти. Если попробовать сделать это, компилятор сообщит, что он не может преобразовать int в указатель int .
Вопрос на засыпку: Можно ли инициализировать указатель, явно указав адрес ячейки памяти?
double* d_ptr< 0x0012FF7C >;Ответ - нет, компиляция этого кода завершится с ошибкой! Хотя казалось бы, почему, ведь оператор адреса & , так же возвращает адрес? Тут есть отличие - оператор & возвращает тоже указатель.
Возвращение указателя оператором адреса
Оператор адреса (&) не возвращает адрес своего операнда в виде литерала. Вместо этого он возвращает указатель, содержащий адрес операнда, тип которого является производным от аргумента. Например, взятие адреса значения int вернет адрес в указателе int .
Мы можем увидеть это в следующем примере:
#include #include int main() < int num < 4 >; std::coutВ Visual Studio этот код напечатал:
При компиляции gcc вместо этого выводит "pi" («pointer to int», указатель на int).
Одной из основных операций является получение значения переменной через указатель - косвенное обращение.
Косвенное обращение через указатели
У нас есть переменная-указатель, которая указывает на что-то. Значит, другая распространенная вещь, которую мы делаем с ней, — это косвенное обращение через указатель. Это нужно, чтобы получить значение того, на что он указывает.
Косвенное обращение через указатель вычисляет содержимое адреса, на который он указывает:
int value < 5 >; std::cout ; // ptr указывает на value std::coutЭта программа создает следующий вывод:
0x7ffcc0b6824c 5 0x7ffcc0b6824c 5Без типа при косвенном обращении через указатель он не знал бы, как интерпретировать содержимое, на которое он указывает. По этой же причине тип указателя и тип переменной, адрес которой ему присваивается, должны совпадать. Если бы это было не так, косвенное обращение через указатель неверно интерпретировало бы биты как другой тип.
После присваивания значению указателя можно присвоить другое значение:
int value1< 5 >; int value2< 7 >; int* ptr<>; ptr = &value1; // ptr указывает на value1 std::coutКогда адрес переменной value присваивается указателю ptr , верно следующее:
- ptr равен &value
- *ptr обрабатывается так же, как value
Поскольку *ptr обрабатывается так же, как value , можно присваивать ему значения, как если бы это была переменная value .
Следующая программа напечатает 7:
int value < 5 >; int* ptr < &value >; // ptr указывает на value *ptr = 7; // *ptr - это то же, что и value, которому присвоено 7 std::coutОбратите внимание, через указатель мы можем работать с переменной value - получить значение, и даже изменить его.
Такой мощный механизм имеет свои минусы.
Предупреждение о косвенном обращении через недействительные указатели
Указатели в C++ по своей сути небезопасны. Неправильное использование указателей — один из лучших способов вывести приложение из строя.
Во время косвенного обращения через указатель приложение пытается перейти в ячейку памяти, которая хранится в указателе, и получить содержимое памяти. В целях безопасности современные операционные системы используют приложения-песочницы. Они предотвращают неправильное взаимодействие операционной системы с другими приложениями и защитить стабильность самой операционной системы.
Если приложение пытается получить доступ к области памяти, не выделенной ему операционной системой, операционная система может завершить работу приложения.
Следующая программа иллюстрирует это и вероятнее всего упадет с ошибкой:
#include // Мы рассмотрим & позже. Пока не беспокойтесь об этом. Мы используем его только для того, // чтобы заставить компилятор думать, что p имеет значение. void foo(int*&p) < // p — ссылка на указатель. Мы рассмотрим ссылки (и ссылки на указатели) позже в этой главе. // Мы используем ее, чтобы заставить компилятор думать, что p мог быть изменен, // поэтому он не будет жаловаться на то, что p неинициализирован. >int main() < int* p; // Создаем неинициализированный указатель (указывающий на мусор) foo(p); // Обманываем компилятор, заставляя его думать, что мы собираемся присвоить указателю допустимое значение std::coutДля хранения указателей так же как и для обычных приманных выделяется память.
Размер указателей
Размер указателя зависит от архитектуры, для которой скомпилирован исполняемый файл — 32-битный исполняемый файл использует 32-битные адреса памяти. Следовательно, указатель на 32-битной машине занимает 32 бита (4 байта). С 64-битным исполняемым файлом указатель будет 64-битным (8 байтов). Это независимо от размера объекта, на который он указывает:
char* ch_ptr <>; // char равен 1 байту int* i_ptr <>; // int обычно равен 4 байтам std::coutРазмер указателя всегда один и тот же. Это связано с тем, что указатель — это просто адрес памяти, а количество битов, необходимых для доступа к адресу памяти на данной машине, всегда постоянно.
Что хорошего в указателях:
- Массивы реализованы с помощью указателей. Указатели могут использоваться для итерации по массиву
- Указатели в C++ — это единственный способ динамического выделения памяти
- Их можно использовать для передачи функции в качестве параметра другой функци
- Их можно использовать для достижения полиморфизма при работе с наследованием
- Их можно использовать, чтобы иметь указатель на одну структуру/класс в другой структуре/классе, чтобы сформировать цепочку. Это полезно в некоторых более сложных структурах данных, таких как связанные списки и деревья
В этом уроке мы познакомились с указателями, узнали как их объявлять, как присваивать им значения и как безопасно работать с ними.
Задание
Поменяйте значения переменных first_num и second_num местами. Попробуйте это сделать с помощью уже созданных указателей.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Указатель в языке Си
Указатель — переменная, содержащая адрес объекта. Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.
Указатели широко используются в программировании на языке Си. Указатели часто используются при работе с массивами.
Память компьютера можно представить в виде последовательности пронумерованных однобайтовых ячеек, с которыми можно работать по отдельности или блоками.
Каждая переменная в памяти имеет
- свой адрес — номер первой ячейки, где она расположена;
- свое значение.
Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной. Переменная, объявленная как указатель, занимает 4 байта в оперативной памяти (в случае 32-битной версии компилятора).
Указатель, как и любая переменная, должен быть объявлен. Общая форма объявления указателя
тип *ИмяОбъекта;
Тип указателя — это тип переменной, адрес которой он содержит.
Для работы с указателями в Си определены две операции:
- операция * (звездочка) — позволяет получить значение объекта по его адресу — определяет значение переменной, которое содержится по адресу, содержащемуся в указателе;
- операция & (амперсанд) — позволяет определить адрес переменной.