Лабораторные / Лр №7. Указатели / Теор свед. Лр№7.Указатели
.odtЛабораторная работа по теме «Указатели".
Теоретические сведения.
Указатель – это переменная, содержащая адрес другой переменной.
Получение адреса
px = &x;
Теперь px «указывает» на x.
Операция получения адреса & применима только к переменным и элементам массива.
Адрес временных значений, таких как &(x + 1), получить нельзя.
Разыменование
Унарная операция * позволяет обратиться к значению по его адресу.
Выражение *px означает то же, что любая целая переменная, и с ним можно поступать, как с целой переменной, например,
*px = 5;
(*px)++;
printf("%d", *px);
Типы указателей
Типов указателей столько, сколько типов переменных, и указатель может указывать только на переменную своего типа. Исключение составляет указатель void*, который может указывать на переменную любого типа, но к нему нельзя применять операцию разыменования.
Описание указателей имеет мнемонический характер.
int i; // целая переменная
int *p; // указатель на целое
int i, *p; // совместное объявление
Любое описание (не только указателей) состоит из декларации и одного или более деклараторов.
Мнемоника в том, что выражения для деклараторов в контексте программного кода должны иметь тип декларации.
double d, *pd, m[10], F();
Указатели и аргументы функций
Передача аргументов в Си происходит по значению, но при помощи указателей можно имитировать передачу по ссылке.
void Swap(int *px, int *py)
{
int t = *px;
*px = *py;
*py = t;
}
void main()
{
int a = 5, b = 6;
Swap(&a, &b);
}
Указатели и массивы
Любые действия, которые можно выполнить с помощью индексов массива, можно сделать и с помощью указателей.
int m[10], *pm;
pm = &m[0];
int x = *pm; // x = m[0]
int y = *(pm + 1); // y = m[1]
При разборе выражения a[i] компилятор Си преобразует его к виду *(a+i).
Различие между именем массива и указателем в том, что указатель является переменной, а имя массива – константой. Т.е.
pm = &x; // можно
m = &x; // нельзя!
Указатели – аргументы функций
Когда массив передается в функцию, то на самом деле передается указатель.
Два равноценных способа объявить функцию для обработки массива.
void F(int m[], int n);
void F(int *m, int n);
Арифметика указателей
Пример. Сложить элементы массива, используя арифметику указателей.
int Sum(int *m, int n) {
int s = 0, *end = m + n;
while (m < end)
s += *(m++);
return s;
}
void main()
{
int a[] = {1,2,3};
printf("%d\n", Sum(a, 3));
}
Указатель может быть инициализирован, как любая другая переменная. Осмысленными значениями для указателя могут быть адреса переменных или 0.
#define NULL 0;
Добавление единицы к указателю означает, что он начнет указывать на соседнее в памяти значение базового типа.
Допустимые операции
p + i – смещение вперед
p – i – смещение назад
p1 – p2 – расстояние
p1 == p2 – равенство
p1 != p2 – неравенство
За исключением перечисленных операций, вся остальная арифметика указателей является незаконной. Запрещено складывать два указателя, умножать, делить, сдвигать или маскировать их, а также прибавлять к ним переменные типа float или double.
Многомерные массивы и массивы указателей
Многомерные – аналог прямоугольных в С#.
char m[2][3] = { {'1','2','3'}, {'4','5','6'} };
Массивы указателей – аналог невыровненных в С#.
char *a[2];
a[0] = (char*)malloc(sizeof(int) * 3);
a[1] = (char*)malloc(sizeof(int) * 33);
Указатели на функции
Пример. Функция foreach.
int m[] = {1,2,3,4,5,6};
void foreach(int m[], int n, int (*f)(int)) {
for (int i = 0; i < n; i++)
m[i] = f(m[i]);
}
int F(int x) {
return x * x;
}
void main()
{
foreach(m, 6, &F);
}
Операции разыменования и взятия адреса для функций можно задать неявно.
int m[] = {1,2,3,4,5,6};
void foreach(int m[], int n, int f(int)) {
for (int i = 0; i < n; i++)
m[i] = f(m[i]);
}
int F(int x) {
return x * x;
}
void main()
{
foreach(m, 6, F);
}