Указатели и ссылки — различия между версиями

Материал из SEWiki
Перейти к: навигация, поиск
(Новая страница: «==Указатели== Указатель - это переменная, содержащая адрес другой переменной. Указатели оче…»)
 
(Указатели на указатель)
 
(не показаны 3 промежуточные версии этого же участника)
Строка 13: Строка 13:
 
double *pd = &d;
 
double *pd = &d;
  
pa = pd ''//нельзя! т.к. указатели на данные разного типа''
+
pa = pd //нельзя! т.к. указатели на данные разного типа
  
pa = (int*)pd; ''//компилируется''
+
pa = (int*)pd; //компилируется
 
</source>
 
</source>
  
Строка 53: Строка 53:
 
swap (&amp;k, &amp;m);
 
swap (&amp;k, &amp;m);
 
</source>
 
</source>
Теперь значения k и m изменились, так как при передаче в функцию произошло копирование адресов переменных, а адреса мы менять не хотели.  
+
Теперь значения k и m изменились, так как при передаче в функцию произошло копирование адресов переменных, а адреса мы менять не хотели.
  
 
==Встроенные массивы==
 
==Встроенные массивы==
Строка 60: Строка 60:
  
 
<source lang="cpp">
 
<source lang="cpp">
int a[5] = {1, 2, 3}; ''//в памяти лежат 1,2,3,0,0''
+
int a[5] = {1, 2, 3}; //в памяти лежат 1,2,3,0,0
 
</source>
 
</source>
 
  _ _ _  ___________________ _ _
 
  _ _ _  ___________________ _ _
Строка 68: Строка 68:
 
указатель (на первый элемент массива):
 
указатель (на первый элемент массива):
 
<source lang="cpp">
 
<source lang="cpp">
int *p = 0; ''//"нулевой" указатель, nil. Означает, что он не указывает ни на какую ячейку памяти.''
+
int *p = 0; //"нулевой" указатель, nil. Означает, что он не указывает ни на какую ячейку памяти.
p = a; ''// &amp;a[0]''
+
p = a; // &amp;a[0]
 
+
a = p; // обратное (р = а) неверно
a = p; ''// обратное (р = а) неверно''
+
 
</source>
 
</source>
  
Строка 87: Строка 86:
 
{
 
{
 
int max = *m;  
 
int max = *m;  
for (int i=1; i &amp;lt size; ++i)
+
for (int i=1; i != size; ++i)
if (m[i] &amp;lt max)
+
if (m[i] >= max)
 
max = m[i];
 
max = m[i];
 
return max;
 
return max;
Строка 97: Строка 96:
  
 
Проблемы такой реализации:
 
Проблемы такой реализации:
* функция не работает с массивами нулевой длины;
+
* функция не работает с массивами нулевой длины;
* не отследить случаи выхода за границы массива.
+
* не отследить случаи выхода за границы массива.
  
 
===Арифметика указателей===
 
===Арифметика указателей===
 
Пусть q, p - указатели, а i - число. Возможные операции:
 
Пусть q, p - указатели, а i - число. Возможные операции:
* p+i, p-i сдвиг по массиву на i элементов вправо/влево
+
* p+i, p-i сдвиг по массиву на i элементов вправо/влево
* p[i] &lt;=&gt; *(p+i)
+
* p[i] &lt;=&gt; *(p+i)
* q-p количество элементов массива между p и q. Разность указателей имеет тип size_t.
+
* q-p количество элементов массива между p и q. Разность указателей имеет тип size_t.
  
 
Рассмотрим две реализации функции подсчета длины символов в строке:
 
Рассмотрим две реализации функции подсчета длины символов в строке:
Строка 142: Строка 141:
 
int *max = m;
 
int *max = m;
 
for (; m != last; ++m)
 
for (; m != last; ++m)
if (*m &gt; *max)
+
if (*m >= *max)
 
max = m;
 
max = m;
 
return max;
 
return max;
Строка 210: Строка 209:
 
<source lang="cpp">
 
<source lang="cpp">
 
int a = 5;
 
int a = 5;
int &amp;b = a; ''//b - ссылка на а''
+
int &amp;b = a; //b - ссылка на а
  
b = 10; ''//в а тоже стало 10''
+
b = 10; //в а тоже стало 10
 
</source>
 
</source>
 
Функция swap с использованием ссылок будет выглядеть так:
 
Функция swap с использованием ссылок будет выглядеть так:
Строка 232: Строка 231:
  
 
===Различия ссылок и указателей===
 
===Различия ссылок и указателей===
* Ссылку нельзя не проинициализировать (ссылка не может быть неинициализированной); ссылку нельзя переинициализировать.
+
* Ссылку нельзя не проинициализировать (ссылка не может быть неинициализированной); ссылку нельзя переинициализировать.
* Нельзя получить адрес ссылки или ссылку на ссылку.
+
* Нельзя получить адрес ссылки или ссылку на ссылку.
* Нельзя создавать массивы ссылок.
+
* Нельзя создавать массивы ссылок.
  
 
''Замечание:''
 
''Замечание:''
* &amp;q взятие адреса переменной q
+
* &amp;q взятие адреса переменной q
* *pa   разименование
+
* *pa   разименование
* int &amp;a передача переменной а по ссылке
+
* int &amp;a передача переменной а по ссылке
* int *pa  передача переменной по адресу.
+
* int *pa  передача переменной по адресу.

Текущая версия на 11:00, 30 марта 2011

Указатели

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

Пример создания указателя на int и на double:

int a=5;
int *pa = &amp;a;
double d = 1.5;
double *pd = &amp;d;

pa = pd //нельзя! т.к. указатели на данные разного типа

pa = (int*)pd; //компилируется

Последняя операция лишена смысла, т.к. элемент типа double занимает 8 байт, а элемент типа int - 4 байта (в х32). Поэтому указатель pa будет указывать на первые 4 байта данных, расположенных по адресу, хранящемуся в pd.

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

void swap (int a, int b)
{
	int t = a;
	a = b;
	b = t;
}

Вызов swap:

int k=10, m=20;
swap (k, m);

Несмотря на то, что все написано правильно, после возврата из функции swap значения k и m не изменились. Это обусловлено тем, что при передаче в функцию происходит копирование параметров. Таким образом, изменились скопированные значения, а исходные остались прежними. Чтобы иметь возможность менять параметры их следует передавать по указателю:

void swap (int *a, int *b)
{
	int t = *a;
	*a = *b;
	*b = *t;
}

Вызов swap:

int k=10, m=20;
swap (&amp;k, &amp;m);

Теперь значения k и m изменились, так как при передаче в функцию произошло копирование адресов переменных, а адреса мы менять не хотели.

Встроенные массивы

Опр. Массив - это непрерывная область памяти, в которой расположены однотипные элементы.

int a[5] = {1, 2, 3}; //в памяти лежат 1,2,3,0,0
_ _ _  ___________________ _ _
_ _ _ |_1_|_2_|_3_|_0_|_0_|_ _

Связь массивов и указателей: имя встроенного массива преобразуется в указатель (на первый элемент массива):

int *p = 0; //"нулевой" указатель, nil. Означает, что он не указывает ни на какую ячейку памяти.
p = a; // &amp;a[0]
a = p; // обратное (р = а) неверно

Передача массива в функцию в качестве параметра:

int max_element (int m[10]) {}

Массив передается по ссылке (не копируется). Вызов: max_element(a); Функция max_element будет работать только с массивами из 10 элементов (с другими - не скомпилируется). Правильнее делать так:

int max_element (int *m, int size) 
{
	int max = *m; 
	for (int i=1; i != size; ++i)
		if (m[i] >= max)
			max = m[i];
	return max;
}

Вызов: max_element(a, 5);

Проблемы такой реализации:

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

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

Пусть q, p - указатели, а i - число. Возможные операции:

  • p+i, p-i сдвиг по массиву на i элементов вправо/влево
  • p[i] <=> *(p+i)
  • q-p количество элементов массива между p и q. Разность указателей имеет тип size_t.

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

int strlen1 (char *s)
{
	int len=0;
	while (s[len]!=0)
		++len;
	return len;
}

int strlen2 (char *s)
{
	char *p = s;
	while (*p!=0)
		++p;
	return p-s;
}

Первая реализация более трудоемка, так как s[len] <=> *(s+len) то есть смещению по массиву и разименованию (2 операции), в то время, как во второй реализации мы работаем с указателями напрямую. Но второй вариант менее читабелен. Второй способ (работу с указателями напрямую) следует использовать и при передаче массива в функцию.

Рекомендуется следующая сигнатура:

int foo (int *first, int *last);

Где first является указателем на первый элемент массивва (a[0]), а last указателем на область памяти сразу за массивом ("a[n]"). Такой выбор указателя last обеспечивает работу с массивами нулевой длины. Перепишем функцию max_element:

int* max_element (int *m, int *last)
{
	int *max = m;
	for (; m != last; ++m)
		if (*m >= *max)
			max = m;
	return max;
}

Вызов: max_element(a, a+5); Следует отметить, что теперь функция max_element возвращает не значение максимального элемента массива, а указатель на него.

Указатели на указатель

Рассмотрим функцию поиска символа в строке, возвращающую позицию искомого элемента:

int findch (char *s, char p)
{
	for (int i = 0; s[i] != 0; ++i)
		if (s[i] == p)
			return i;
	return -1;
}

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

bool findch (char *s, char p, int *n=0)
{
	for (int i = 0; s[i] != 0; ++i)
		if (s[i] == p)
		{
			*n = i;
			return true;
		}
	return false;
}

Вызов:

int idx = 0;
if (findch(s, ' ', &amp;idx)) {...}

Чтобы возвращать не индекс элемента, а его позицию в массиве используют указатель на указатель:

bool findch (char *s, char p, char **pos=0)
{
	while (*s!=0)
	{
		if (*s==p)
		{
			if (pos)
				*pos=s;
			return true;
		}
		++s;
	}
	return false;
}

Вызов:

char *pos = 0;
if (findch(s, ' ', &amp;pos) {...}

Работа с указательями напрямую синтаксически неудобна. Для достижения удобства используют ссылки:

int a = 5;
int &amp;b = a; //b - ссылка на а

b = 10; //в а тоже стало 10

Функция swap с использованием ссылок будет выглядеть так:

void swap (int &amp;a, int &amp;b)
{
	int t=b;
	b=a;
	a=t;
}

Вызов:

int c = 30;
swap (a, c);

При передаче параметров в функцию по ссылке копирования данных не происходит.

Различия ссылок и указателей

  • Ссылку нельзя не проинициализировать (ссылка не может быть неинициализированной); ссылку нельзя переинициализировать.
  • Нельзя получить адрес ссылки или ссылку на ссылку.
  • Нельзя создавать массивы ссылок.

Замечание:

  • &q взятие адреса переменной q
  • *pa разименование
  • int &a передача переменной а по ссылке
  • int *pa передача переменной по адресу.