Материал: lectures_3

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

/*

// указателя

.............................*/

38.6.Операции с указателями

Вязыке Си указатели наделены большими возможностями. Об этом свидетельствует размер перечня тех операций, которые можно выполнять с указателями:

Разыменование,

Взятие адреса,

Присваивание,

Индексирование указателей

Арифметические действия с указателями,

Сравнение указателей.

Дополнительные сведения о разыменовании указателей

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

ккоторым нельзя применять разыменование:

Недействительный указатель.

Нетипизированный указатель.

Нулевой указатель

Приведем примеры недопустимых разыменований.

/* Примеры недопустимых разыменований указателей */

int* p = NULL;

/* Недопустимо разыменовывать нулевой указатель*/

*p = 10;

int n = 5;

 

void*

p3 = &n;

/* Недопустимо разыменовывать нетипизированный

*p3 = 10;

 

 

указатель p3*/

char* p4;

/* Недопустимо разыменовывать

*p4 = ’A’;

/*

 

неинициализированный указатель p4 */

. . . . . . . . . . . . . . . . . . . . . . . . . . . */

Заметим, что к категории операторов разыменования относятся еще два оператора:

Оператор [], который используется при работе с массивами.

Оператор ->, который используется при работе со структурами.

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

Одному указателю можно присвоить значение другого указателя. Однако некоторые присваивания, которые допускает язык Си, могут привести к

96

некорректной работе программы. Остановимся на этом вопросе подробнее. Рассмотрим ряд частных случаев. Начнем с присваивания типизированных указателей. Корректность такой операции не вызывает сомнений только в том случае, когда совпадают базовые типы обоих указателей. В противном случае такую операцию следует считать недопустимой. Приведем пример программного кода, в котором будут встречаться оба вида присваиваний.

/*

Корректные и некорректные присваивания типизированных

int n = 10;

указателей

*/

 

 

double x = 20.3;

 

int *p1, p2;

 

 

double* p3;

 

 

p1

= &n;

/* допустимое присваивание

*/

p2

= p1;

/* допустимое присваивание

*/

p3

= p2;

/* недопустимое присваивание, т. к. не

совпадают базовые типы указателей

*/

/* конец программного кода примера

*/

 

Отметим, что, несмотря на

семантическую недопустимость

присваивания p3 = p1 в приведенном выше примере компилятор языка Си ограничивается выводом предупреждения (Компилятор языка С++ в этой ситуации выводит сообщение об ошибке). Работа языка Си с указателями свидетельствует о его пониженной типизации. Это обстоятельство следует рассматривать как крайне опасным последствиям. Одна из рекомендаций по преодолению этих неприятностей заключается в предварительной компиляции программного кода компилятором языка С++.

Одному указателю можно присвоить значение другого указателя. Однако некоторые присваивания, которые допускает язык Си, могут привести к некорректной работе программы. Остановимся на этом вопросе подробнее. Рассмотрим ряд частных случаев. Начнем с присваивания типизированных указателей. Корректность такой операции не вызывает сомнений только в том случае, когда совпадают базовые типы обоих указателей. В противном случае такую операцию следует считать недопустимой. Приведем пример программного кода, в котором будут встречаться оба вида присваиваний.

/*

Корректные и некорректные присваивания типизированных

int n = 10;

указателей

*/

 

 

double x = 20.3;

 

int *p1, p2;

 

 

double* p3;

 

 

p1

= &n;

/* допустимое присваивание

*/

p2

= p1;

/* допустимое присваивание

*/

p3

= p2;

/* недопустимое присваивание, т. к. не совпадают

 

 

базовые типы указателей

*/

/* конец программного кода примера

*/

97

Отметим, что, несмотря на недопустимость присваивания p3 = p1 в приведенном выше примере компилятор языка Си ограничивается выводом предупреждения (Компилятор языка С++ в этой ситуации выводит сообщение об ошибке).

Операция взятия адреса для указателя. Указатели на указатели

Пусть имеется следующий фрагмент программы

//..

int n = 5; int *p = &n; int **pp = &p;

Переменная pp является указателем на указатель или двухуровневым указателем. Эта переменная получена применением оператора взятия адреса & к указателю p. Указатели на указателиприменяются на практике. Например, в качестве параметров функций (см. прототип функции strtok).

Указатели и операция индексирования

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

#define MSIZE 5 #include <stdio.h> int main(void)

{

int x[MSIZE] = {1, 3, 5, 7, 9}; int *p = &x[1];

printf(“p[1] = %d”, p[1]); // ..

}

В рассматриваемом фрагменте программы объявлен указатель p, который инициализирован адресом второго элемента массива x. Затем функции printf() к указателю p применен оператор индексные скобки []. В результате выполнения этого фрагмента программы на экране получим p[1] = 5.

Арифметические операции с указателями

Арифметические операции применяются к укзателям, которые работают с массивами. К числу допустимых арифметических операций к таким указателям относятся:

++ (инкремент),

--(декремент),

сложение с целым числом,

вычитание целого числа.

вычисление разности двух указателей. Рассмотрим слеющий программный код:

#define MSIZE 5 #include <stdio.h>

98

int main(void)

{

int x[MSIZE] = {1, 3, 5, 7, 9}; int *p1 = &x[1];

int *p2 = &x[3]; // ..

}

Вэтом программном коде объявлены указатели p1 и p2, инциализированныt адресами элементов массива x. Допустимы следующие выражения с этими указателями:

p1++ и ++p1,

p1-- и p1--,

p1 + 2 и p2 – 2

p2 – p1.

Врезультате вычисления всех выражений, исключая последнее выражение (p2 – p1), получается адрес. Результатом вычисления разности p2

p1 будет целое число. Рассмотрим выражение p1++. При вычислении этого выражения следует учитывать возвращаемое значение и побочный эффект. Возращаемым значения выражения p++ является исходное значение указателя p. Побочный эффект при вычислении выражения p++ состоит в увеличении операнда на единицу. Однако адрес, хранящийся в указателе p, увеличивается таким образом, чтобы указатель был установлен на следующий элемент массива. При работе в 32 разрядной среды адрес, хранящийся в указатель, увеличиться на 4 байта. Таким образом, арифметические операции, выполняемые с указателями, оказываются масштабированными. В качесте масштаба используется объем памяти, которую занимает один элемент массива, с которым работает указатель.

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

Указатели можно сравнивать. Сравнение имеет смысл только для указателей на один и тот же массив. При соблюдении этого условия операции ==, !=, <, >, <=, >= будут выполняться корректно.

38.7.Указатели и динамическая память

Как отмечалось выше, одной из основных областей применения указателей – работа с динамической памятью. Использование динамической памяти позволяет повысить эффективность использования оперативной памяти компьютера. При этом возрастают трудности программирования. В первую очередь это связано с тем, что программист должен следить за выделением и освобождением память самостоятельно.

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

malloc().

calloc(),

realloc(),

99

free().

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

Функция malloc

#include<stdlib.h>

void* malloc(size_t size);

Эта функция предпринимает попытку динамически выделить непрерывный блок памяти, размер которого равен size_t байт. В случае успеха функция возвращает указатель на начало выделенной памяти. В случае неуспеха функция вернет указатель NULL. Следует учитывать, что после завершения работы с памятью, выделенной функцией malloc(), ее следует освободить вызовом функции free().

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

#include<stdlib.h> #define MAXSIZE 129 int main(void)

{

char* buf;

if(NULL ==(buf = malloc(MAXSIZE)))

 

error();

/*

..................*/

}

Функция calloc

Объявление этой функции имеет следующий вид

#include<stdlib.h>

void* calloc(size_t nmemb, size_t size);

Делает попытку выделить память для массива из nmemb объектов, размер каждого из которых равен size байт. В случае успеха функция calloc() возвращает указатель на начало выделенной памяти. В случае неуспеха возвращается указатель NULL. Выделенная память заполняется двоичными нулями.

#include<stdlib.h> #define MAXSIZE 129 int main(void)

{

double* arr; int n;

if(NULL ==(arr = сalloc(MAXSIZE))) error();

100

Источник: https://studfile.net/preview/16544272/