Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
шпоры теория 113019.docx
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
379.66 Кб
Скачать

49. Операции с указателями: присваивание адреса, определение значения по адресу, присваивание указателей.

Указатель – это переменная, которая может содержать адрес некоторого объекта. Простейшая декларация указателя имеет формат

тип * ID_указателя;

Например: int *a; double *f; char *w.

Присваивание указателей

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

#include <stdio.h>

int main(void)

{ int x = 99;

int *p1, *p2;

p1 = &x;

p2 = p1; /* печать значение x дважды */

printf("Значение по адресу p1 и p2: %d %d\n", *p1, *p2); /* печать адреса x дважды */

printf("Значение указателей p1 и p2: %p %p", p1, p2);

return 0; }

после присваивания:

p1 = &x;

p2 = p1;

оба указателя (p1 и р2) ссылаются на х. То есть, оба указателя ссылаются на один и тот же объект. Программа выводит на экран следующее:

Значения по адресу p1 и р2 : 99 99

Значения указателей p1 и р2: 0063FDF0 0063FDF0

Допускается присваивание указателя одного типа указателю другого типа. Однако для этого необходимо выполнить явное преобразование типа указателя (операция приведения типов), которая рассматривается в следующем разделе.

Присваивание адреса и определение значения по адресу

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

&p – получение адреса, где p – идентификатор переменной. Результатом операции является адрес переменной p.

Пример программы:

# include <stdio.h>

int main()

{ int x=2,*p;

p=&x;

printf("\n x=%d address of x=%u",x,p);

return 0; }

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

Операторы ptr=&a; и val=*ptr; равнозначны оператору val=a;

Например,

n=32;

p=&n; /* p–адрес ячейки, куда записано n */

v=*p;

В результате выполнения этих действий в переменную v будет помещено число 32.

50. Операции с указателями: сложение и вычитание, инкремент и декремент, сравнение указателей

Помимо уже рассмотренных операций, с указателями можно выполнять арифметические операции сложения, инкремента (++), вычитания, декремента (--) и операции сравнения.

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

Инкремент перемещает указатель к следующему элементу массива, декремент – к предыдущему.

Указатель, таким образом, может использоваться в выражениях вида

p # iv, ## p, p ##, p # = iv,

p – указатель, iv – целочисленное выражение, # – символ операции '+' или '–'.

Результатом таких выражений является увеличенное или уменьшенное значение указателя на величину iv * sizeof(*p), т.е. если указатель на определенный тип увеличивается или уменьшается на константу, его значение изменяется на величину этой константы, умножен­ную на размер объекта данного типа.

Текущее значение указателя всегда ссылается на позицию некоторого объекта в памяти с учетом правил выравнивания для соответствующего типа данных. Таким образом, значение p # iv указывает на объект того же типа, расположенный в памяти со смещением на iv позиций.

При сравнении указателей могут использоваться отношения любого вида («>», «<» и т.д.), но наиболее важными видами проверок являются отношения равенства и неравенства («==», «!=»).

Отношения порядка имеют смысл только для указателей на последовательно размещенные объекты (элементы одного массива).

Разность двух указателей дает число объектов адресуемого ими типа в соответствующем диапазоне адресов, т.е. в применении к массивам разность указателей, например, на третий и шестой элементы равна 3.

Очевидно, что уменьшаемый и вычитаемый указатели должны принадлежать одному массиву, иначе результат операции не имеет практической ценности и может привести к непредсказуемому результату. То же можно сказать и о суммировании указателей.

Значение указателя можно вывести на экран с помощью функции printf, используя спецификацию %p (pointer), результат выводится в шестнадцатеричном виде.

Рассмотрим фрагмент программы:

int a = 5, *p, *p1, *p2;

p = &a;

p2 = p1 = p;

++p1;

p2 += 2;

printf(“a = %d , p = %d , p = %p , p1 = %p , p2 = %p .\n”, a, *p, p, p1, p2);

Результат может быть следующим:

a = 5 , *p = 5 , p = FFF4 , p1 = FFF6, p2 = FFF8 .

Графически это выглядит следующим образом (в 16-разрядном процессоре на тип int отводится 2 байта):

FFF5

FFF7

FFF9

FFF4

р

FFF6

p1

FFF8

p2

FFF10

p = FFF4,

p1 = FFF6 = ( FFF4 + 1*sizeof(*p)) FFF4 + 2 (int)

р2 = FFF8 = ( FFF4 + 2*sizeof(*p)) FFF4 + 2*2

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

При смешивании в выражении указателей разных типов явное преобразование типов требуется для всех указателей, кроме void*.

Явное приведение типов указателей позволяет получить адрес объекта любого типа:

type *p;

p = (type*) &object;

Значение указателя p позволяет работать с переменной object как объектом типа type.

51. Работа с динамической памятью. Динамические массивы.

В языке Си размерность массива при объявлении должна задавать-

ся константным выражением. При необходимости работы с массивами переменной размерности вместо массива достаточно объявить указатель требуемого типа и присвоить ему адрес свободной области памяти (захватить память). После обработки массива занятую память надо освободить. Библиотечные функции работы с памятью описаны в файле alloc.h. Пример создания динамического массива:

float *x;

int n;

printf("\nРазмерность - "); scanf(" %d",&n);

if ((x = calloc(n, sizeof(*x)))==NULL) { // Захват памяти

printf("\n Предел размерности “);

exit(1); }

else {

printf("\n Массив создан !");

...

for (i=0; i<n; i++)

printf("\n%f",x[i]);

...

free(x); // Освобождение памяти

}

В С++ введены две операции: захват памяти - new и освобождение захваченной ранее памяти - delete.

Общий формат записи: указатель = new тип (значение);

Пример создания одномерного динамического массива

Массив объявляем указателем.

...

double *x;

int i, n;

...

puts(" Введите размер массива: ");

scanf(“%d”, &n);

x = new double [n] ;

if (x == NULL) {

puts(" Предел размерности ! ");

return; }

for (i=0; i<n; i++) // Ввод элементов массива

scanf(“%lf”, &x[i]);

...

delete [ ]x; // Освобождение памяти