Модераторы: bsa

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> [FAQ] Указатели и ссылки 
:(
    Опции темы
bsa
Дата 6.9.2009, 13:29 (ссылка) |    (голосов:13) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196



Любой новичок, который решил изучить язык Си или С++, почти сразу же сталкивается с непониманием смысла "указателей" и "ссылок".

Указатель - это переменная или константа, содержащая адрес памяти, начиная с которого могут располагаться данные определенного типа (достаточно представить бумажку, на которой написан домашний адрес). Указатель не несет в себе информации о количестве данных (возьмите бумажку и попытайтесь по ней определить, сколько домов находится на той же улице). Можно получить адрес любой переменной (у всех домов есть адрес, не так ли?) и присвоить его подходящему указателю (номер телефона - на страницу телефонной книги, адрес - в адресной, email - в список контактов... - каждому свое место). Операция по получению доступа к данным, на которые указывает указатель, называется разыменованием. Пример:
Код
int *p; /* указатель на переменную типа int */
int x; /* переменная типа int */
p = &x; /* указателю p присваиваем адрес переменной x */
*p = 0; /* разыменование указателя p и присваивание переменной x значения 0 */
double r;
p = &r; /* ошибка компиляции - нельзя присваивать указателю на целое адрес вещественной переменной */

Есть понятие дикий или подвисший указатель - это указатель непонятно куда. Он получается при создании переменной-указателя или после освобождения области памяти, в которую он указывал. Данными, на которые он указывает, пользоваться нельзя. В противном случае поведение программы непредсказуемо.
Есть понятие нулевой указатель - это указатель в никуда. Обычно, он используется для информирования, что требуемая память не выделена, данные не найдены и пр. При попытке использовать данные, на которые он указывает, программа аварийно завершится (ошибка: "segmentation fault" или "access violation"). Пример:
Код
int *p; /* дикий указатель*/
p = NULL; /* нулевой указатель */
*p = 0; /* вызовет аварийное завершение программы */

Есть типизированные указатели (аналоги: адрес квартиры, кабинета, номер телефона) и нетипизированные (широта, долгота и высота над уровнем моря). Типизированные указатели используются в основном для работы с массивами однотипных данных (например, положить в почтовый ящик каждого жильца дома №10 листовку с рекламой). Поэтому для таких указателей определены операторы: сложения/вычитания целых чисел и вычитание указателей того же типа (квартира №10 находится через 6 квартир от квартиры №4: №10 - №4 = 6, №4 + 6 = №10...). Например, у нас есть массив вещественных чисел. А так же есть типизированный указатель, на первый элемент этого массива. Увеличение на 1 этого указателя приведет к тому, что он станет указывать на следующий элемент массива. Уменьшение - на предыдущий. А если взять два указателя, которые указывают на данные в одном массиве, то можно узнать, на какое количество элементов нужно переместить первый, чтобы получить второй - это делается вычитанием указателей (если взять указатели на разные массивы, то результатом будет какое-то относительно случайное число). Пример:
Код
int a[100]; /* массив на 100 целых чисел */
int *p1; /* указатель на целое */
int *p2; /* второй указатель на целое */
int *p3; /* еще один указатель на целое */
int d; /* некое целое число */
p1 = &a[10]; /* p1 присваиваем адрес 10-го элемента массива a */
p2 = &a[64];
a[10] = 1; /* разыменование - присваивание 10-му элементу массива a значения 1 */
p1[0] = 1; /* тоже самое */
*(p1+ 0) = 1; /* тоже самое */
p3 = p1 + 10; /* p3 теперь указывает на 20-й элемент массива, аналогично: p3 = &a[20] */
p3 = p3 - 1; /* p3 == &a[19] */
++p3; /* p3 == &a[20] */
d = p2 - p1; /* разность указателей: d == 64 - 10 == 54 */
p3 += d; /* p3 == &a[74] */

Нетипизированные указатели используются для работы с целыми областями памяти, когда тип лежащих там данных не имеет значения. Например, используется функцией копирования блока памяти (memcpy). Для подобных указателей не определены ни арифметические операции, ни операция разыменования (нельзя получить непосредственный доступ к данным). Пример:
Код
void *p; /* нетипизированный указатель */
int x;
int *pi; /* типизированный указатель */
pi = &x; /* pi - указатель на x */
p = pi; /* p указывает на область памяти, в которой хранится переменная x */
*p = 0; /* ошибка - разыменование нетипизированного указателя */
Стандарт языка С++ гарантирует, что любой указатель может быть присвоен нетипизированному без потери информации. Именно поэтому, если по какой-то причине, нужно конвертировать указатель на один тип в указатель на другой, то делать это надо в 2 стадии, сначала сконвертировать в нетипизированный, а затем "типизировать" его в требуемый.

Можно создать указатель на любой тип. Для этого необходимо при его объявлении поставить звездочку перед именем переменной:
Код
int x;
int *px; /* указатель на int */
struct MyStruct;
struct MyStruct *ps; /* указатель на структуру MyStruct (для создания указателя достаточно объявления структуры!) */
int* *px; /* указатель на int* = указатель на указатель на int */
int a[10]; /* массив из 10 int */
int* ap[10]; /* массив из 10 указателей на int */
int (*pa)[10]; /* указатель на массив из 10 int */
int (*func)(void); /* указатель на функцию вида int myFunc(void) */
Немного остановимся на указателе на указатель. Многие не понимают, зачем это вообще нужно. Есть очень простой пример, иллюстрирующий их незаменимость. Когда функция принимает какие-то данные по значению, то создается их копия, и после выхода из функции, данные, ей переданные, останутся неизменными. Чтобы данные функция могла менять - ей нужно передать указатель на них. А что если функция должна менять именно указатель (например, функция добавления данных в массив с возможностью его расширения)? В этом случае и используется указатель на указатель.
Если нужно сделать typedef, то тут все очень просто - просто вместо имени переменной указываете название типа:
Код
typedef int *MyPointer;
typedef int (*MyArray)[10];
typedef int (*MyFunc)(void);
Только стоит иметь в виду, что:
Код
typedef char * string;
const string s;
s = "xxx";
Вызовет ошибку компиляции, в отличие от:
Код
const char *s;
s = "xxx";
Причина в том, что компилятор раскладывает string так:
Код
char * const s; //const string s;
Так как const это модификатор относящийся к переменной, а не к типу. На эти грабли наступил, например, разработчик библиотеки DevIL.

Стандартные библиотеки языков Си и С++ поддерживают перевод значений указателей в строковый вид, удобный для восприятия человеком. В принципе, это не особо нужно, разве что для отладки. Со времен языка Си указатели на char используются в качестве указателей на строки, именно поэтому стандартным поведением оператора вывода в поток (С++, std::ostream) на подобный указатель является отображение строки.  Чтобы это избежать, указатель нужно привести к указателю на void: static_cast<const void*>(p)

Преобразование указателей (и не только) в С и С++ немного отличается. В первом существует только один способ: Type *x = (Type*)y; Более того, преобразование любого указателя к указателю на void и обратно происходит молча - без лишних телодвижений. В С++ немного по другому - существует аж 4 вида преобразований (плюс, сохранен вариант из С): static_cast (преобразование совместимых типов), dynamic_cast (преобразование указателя на базовый класс к указателю на потомок), const_cast (снятие модификатора const с указателя) и reinterpret_cast (любое преобразование без проверки корректности). Эти преобразования расположены в порядке убывания предпочтительности использования. Последний из них аналогичен варианту из языка С. К тому же автоматическое преобразования указателя на void в указатель на другой тип отменено. Для этого необходимо использовать: Type *x = static_cast<Type*>(y);

Ссылка была введена в С++ для упрощения передачи сложных структур в функции, чтобы избежать их копирования, которое сильно ухудшит скоростные показатели программы. Чтобы представить себе как она работает достаточно одного слова - псевдоним. Т.е. если у вас есть переменная x, то вы можете сделать ей псевдоним с именем y. Если вы измените y, то изменится и x, а если измените x, то аналогично изменится и y. Но нельзя сменить объект, с которым ассоциирована ссылка. Т.е. если вы сделали ссылку, как псевдоним для x, то вы не сможете уже сделать ее псевдонимом для z. Операция взятия адреса от ссылки приведет к тому, что будет возвращен адрес ассоциированного с ней объекта.
Пример использования ссылок:
Код
int x;
int &y = x; /* ссылка на x */
y = 0; /* теперь x == 0 */
++x; /* теперь y == 1 */
--y; /* теперь x == 0 */
int z = 10;
y = z; /* теперь x = 10 */
int *p;
p = &y; /* p == &x */
double &r; /* ошибка - нельзя объявлять ссылки без указания "цели" */
double &r = x; /* ошибка - нельзя ссылаться на неподходящий тип */
...
int func(double &r) /* правильно */
{
...
}
...
double d;
func(d); /* правильно */
func(x); /* ошибка, нельзя ссылаться на неподходящий тип */
...
int func2(const double &r)
{
...
}
...
func2(x); /* правильно, так как ссылка идет на константный объект, который будет создан путем конвертации int в double */
Скорость работы со ссылкой не ниже, чем с указателем (так как на машинном уровне реализация похожая, а зачастую вообще одинаковая - машинный код совпадает).

Назад к FAQ

Это сообщение отредактировал(а) bsa - 26.7.2011, 11:01
PM   Вверх
EgoBrain
Дата 22.9.2009, 13:05 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 537
Регистрация: 23.3.2008
Где: Комната

Репутация: нет
Всего: 2



Можно продолжение по данной теме? Не затронуты такие аспекты как: указатель на указатель, передача в функцию указателя/указателя на указатель (пример был лишь с ссылкой), и чем отличается ссылка от указателя на уровне компилятора а не на логическом (если можно так выразиться).
Вот это, только Вашими словами:
Цитата(maxim1000 @ 22.9.2005,  00:56)
ссылка ближе к указателю, чем к пременной
в общем-то именно так она и реализована на уровне компилятора
просто везде, где она используется, компилятор дорисовывает звездочку smile
Код

int x;
int &y=x;
y++;
x=y;

при компиляции будет сначала переделано в такое:
Код

int x;
int *z=&x;
(*z)++;
x=(*z);

поэтому, когда мы пишем
Код

y=x;

на самом деле получается
Код

(*z)=x;

получается, что изменить значение адреса невозможно

единственное, чем отличается ссылка от указателя - инициализация:
Код

int &y=x;

означает занесение адреса x в y
и это навсегда (пока не выйдем из этого блока)...

И еще хотелось бы доп. объяснений по всяким синтаксическим хитростям типа (*z)++ и другое изащренное использование скобок и звездочек.
PM MAIL ICQ Skype   Вверх
zim22
Дата 22.9.2009, 13:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


depict1
****


Профиль
Группа: Завсегдатай
Сообщений: 2682
Регистрация: 15.1.2009
Где: Украина

Репутация: 29
Всего: 69



Цитата(EgoBrain @  22.9.2009,  13:05 Найти цитируемый пост)
И еще хотелось бы доп. объяснений по всяким синтаксическим хитростям типа (*z)++ и другое изащренное использование скобок и звездочек.

все подобные хитрости очень легко "парсятся" человеком, если знать(посмотреть) таблицу С++ операторов, а именно их приоритет и ассоциативность (лево/право)


--------------------
PM MAIL   Вверх
bsa
Дата 22.9.2009, 13:54 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196



Цитата(EgoBrain @ 22.9.2009,  13:05)
Не затронуты такие аспекты как: указатель на указатель, передача в функцию указателя/указателя на указатель (пример был лишь с ссылкой), и чем отличается ссылка от указателя на уровне компилятора а не на логическом (если можно так выразиться).

подумаю, скорее всего напишу кратенько про указатель на указатель.
А вот на счет необходимости рассмотрения ссылки с точки зрения компилятора я не уверен - зачем новичку забивать голову лишним? Но если будет народ настаивать, то напишу.
PM   Вверх
zim22
Дата 22.9.2009, 14:08 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


depict1
****


Профиль
Группа: Завсегдатай
Сообщений: 2682
Регистрация: 15.1.2009
Где: Украина

Репутация: 29
Всего: 69



Цитата(bsa @  22.9.2009,  13:54 Найти цитируемый пост)
Но если будет народ настаивать, то напишу.

я против. новичку это ни к чему.

Это сообщение отредактировал(а) zim22 - 22.9.2009, 14:08


--------------------
PM MAIL   Вверх
ller
Дата 22.9.2009, 23:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 325
Регистрация: 4.8.2008
Где: г. Таганрог

Репутация: 2
Всего: 4



zim22, а не совсем новичкам? Тем кто уже общие аспекты знает, я думаю такая информация пригодится. Только указать  smile пионерам вход воспрещен )))
PM MAIL   Вверх
EgoBrain
Дата 24.9.2009, 00:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 537
Регистрация: 23.3.2008
Где: Комната

Репутация: нет
Всего: 2



Если кто-то посчитает для себя эту информацию лишней, то не будет ее читать. Что касается глубины изучения на первых этапах, то я скажу, что на самом деле изучая такую точную математическую науку как программирования очень сложно балансировать между конкретикой и абстракционизмом, иной раз приходится сильно углублятся чтобы что-то понять, просто вот мне например зачастую очень сложно изучать на абстрактном уровне, так как на этом остаются вопросы.
PM MAIL ICQ Skype   Вверх
wrathchildtoo
Дата 24.9.2009, 14:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Новичок



Профиль
Группа: Участник
Сообщений: 17
Регистрация: 6.4.2009

Репутация: нет
Всего: нет



Цитата(bsa @  22.9.2009,  13:54 Найти цитируемый пост)
А вот на счет необходимости рассмотрения ссылки с точки зрения компилятора я не уверен - зачем новичку забивать голову лишним? Но если будет народ настаивать, то напишу.

Было бы интересно узнать. Ну или хотя бы киньте ссылочку где почитать.
PM MAIL   Вверх
bsa
Дата 24.9.2009, 21:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196



Цитата(wrathchildtoo @  24.9.2009,  14:44 Найти цитируемый пост)
Было бы интересно узнать. Ну или хотя бы киньте ссылочку где почитать.

Почитать можно в книжках по С++ или в гугле.
PM   Вверх
EgoBrain
Дата 25.9.2009, 00:45 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 537
Регистрация: 23.3.2008
Где: Комната

Репутация: нет
Всего: 2



Цитата(wrathchildtoo @ 24.9.2009,  14:44)
Цитата(bsa @  22.9.2009,  13:54 Найти цитируемый пост)
А вот на счет необходимости рассмотрения ссылки с точки зрения компилятора я не уверен - зачем новичку забивать голову лишним? Но если будет народ настаивать, то напишу.

Было бы интересно узнать. Ну или хотя бы киньте ссылочку где почитать.

Читайте закрепленную тему на этом же форуме! http://forum.vingrad.ru/articles/topic-60932.html

Добавлено через 1 минуту и 15 секунд
Просто я еще хотел услышать на этот счет мнение 
bsa
PM MAIL ICQ Skype   Вверх
bsa
Дата 25.9.2009, 13:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196



EgoBrain, немного подправил про ссылки. Более глубоко рассказывать тут желания у меня нет.
PM   Вверх
EgoBrain
Дата 27.9.2009, 03:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 537
Регистрация: 23.3.2008
Где: Комната

Репутация: нет
Всего: 2



Цитата(zim22 @ 22.9.2009,  13:24)
все подобные хитрости очень легко "парсятся" человеком, если знать(посмотреть) таблицу С++ операторов, а именно их приоритет и ассоциативность (лево/право)

Очень интересно, дайте таблицу на русском.
PM MAIL ICQ Skype   Вверх
zim22
Дата 27.9.2009, 08:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


depict1
****


Профиль
Группа: Завсегдатай
Сообщений: 2682
Регистрация: 15.1.2009
Где: Украина

Репутация: 29
Всего: 69



Цитата(EgoBrain @  27.9.2009,  03:11 Найти цитируемый пост)
Очень интересно, дайте таблицу на русском.

http://www.cplusplus.com/doc/tutorial/operators/


--------------------
PM MAIL   Вверх
bsa
Дата 27.9.2009, 17:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Модератор
Сообщений: 9185
Регистрация: 6.4.2006
Где: Москва, Россия

Репутация: 85
Всего: 196




M
bsa
zim22EgoBrain, завязываем с оффтопиком.

PM   Вверх
unicuum
Дата 1.12.2009, 12:12 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 830
Регистрация: 16.3.2005
Где: Рашка

Репутация: нет
Всего: 8



Цитата(bsa @  6.9.2009,  13:29 Найти цитируемый пост)

Можно создать указатель на любой тип. Для этого необходимо при его объявлении поставить звездочку перед именем переменной:

Вопрос в том, ставим ли мы звёздочку перед именем переменной или после указываемого типа. Я вот лично столкнулся с тем, что значки указателя (звёздочка) и ссылки (амперсанд) красивее выглядят присоединённых к типу, то есть не так:
Код

int *p;
а вот так
Код

int* p;

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


--------------------
user posted image
обычный день на винграде
PM   Вверх
Страницы: (3) Все [1] 2 3 
Ответ в темуСоздание новой темы Создание опроса
Правила форума "C/C++: Для новичков"
JackYF
bsa

Запрещается!

1. Публиковать ссылки на вскрытые компоненты

2. Обсуждать взлом компонентов и делиться вскрытыми компонентами

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


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, JackYF, bsa.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | C/C++: Для новичков | Следующая тема »


 




[ Время генерации скрипта: 0.0937 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.