Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лаб_практикум_ч2_2008_1.DOC
Скачиваний:
12
Добавлен:
10.11.2019
Размер:
155.14 Кб
Скачать

Контрольные вопросы

  1. Что такое массивы в С? Как происходит объявление многомерных массивов в программе?

  2. Как представляются двумерные массивы? Как рассчитывается количество байт памяти, занимаемое массивом?

  3. Что такое инициализация массивов?

  4. Какие существуют методы доступа к элементам массивов?

Лабораторная работа № 8. Написание программы на яп с с использованием указателей

Цель работы: формирование знаний и умений по работе с указателями, приобретение практических навыков применения указателей в программах на С.

Краткие теоретические сведения

Понимание и правильное использование указателей очень важно для создания хороших программ на языке С. Указатели необходимы для успешного использования функций и динамического распределения памяти.

Кроме того, многие конструкции языка Вorland С требуют применения указателей. Однако с указателями следует обращаться осторожно. Использование в программе неинициализированного указателя может привести к "зависанию" компьютера.

Объявление указателей

Указатель - это переменная, которая содержит адрес некоторого объекта. Здесь имеется в виду адрес в памяти компьютера. Вообще-то это просто целое число. Но нельзя трактовать указатель как переменную или константу целого типа. Если переменная будет указателем, то она должна быть соответствующим образом объявлена. Указатель объявляется следующим образом:

тип * <имя переменной>

В этом объявлении тип - некоторый тип языка С, определяющий тип объекта, на который указывает указатель (адрес которого содержит); * - означает, что следующая за ней переменная является указателем.

Пример:

char *ch; // указатель ch на символьный тип данных

int *temp,i,*j; // указатель temp и j на целый тип данных, i- переменная целого типа

float *pount,f; // указатель point на данные с плавающей запятой, переменная f типа float

В качестве модификаторов при объявлении указателя могут выступать ключевые слова const, near, far, huge. Ключевое слово const указывает, что указатель не может быть изменен в программе. Размер переменной объявленной как указатель, зависит от архитектуры компьютера и от используемой модели памяти, для которой будет компилироваться программа.

Пример:

const int * point;

Переменная point объявлена как указатель на константное выражение, т.е. значение указателя может изменяться в процессе выполнения программы, а величина, на которую он указывает, нет.

Указатель void

Нельзя создать переменную типа void, но можно создать указатель на тип void. Переменная, объявляемая как указатель на тип void, может быть использована для ссылки на объект любого типа.

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

Указателю на void можно присвоить указатель любого другого типа. Однако при обратном присваивании необходимо использовать явное преобразование указателя на void.

Пример:

void *p1; // объявление указателя на тип void

float f,*p2; // объявление указателя на float (p2) и переменной того же типа (f)

p2=&f; // указатель р2 теперь указывает на f (они имеют один и тот же адрес)

p1=p2; // присваивание указателю void указателя типа float.

p2=(float*)p1; //обратное присваивание: указателю р2 присваиваем указатель р1, предварительно преобразовав р1 к типу float

Операции над указателями

С указателями связаны две специальные операции: & и *.

Обе эти операции являются унарными, т. е. имеют один операнд, перед которыми они ставятся. Операция & соответствует операции "взять адрес". Операция * соответствует словам "значение, расположенное по указанному адресу".

Унарные операции & и * имеют наивысший приоритет наравне с унарным минусом.

В объявлении переменной, являющейся указателем, очень важен базовый тип. Компилятор знает, сколько байт памяти занимает переменная, на которую указывает данный указатель из базового типа указателя. Если указатель имеет базовый тип int, то переменная занимает 2 байта, сhаr - 1 байт и т. д.

Пример:

/* работа с указателями */

{

float x=10.1,y; //объявление с инициализацией переменной х и переменной у

float *point; //объявление указателя point на тип float

point=&x; //применение операции взятия адреса, теперь указатель point указывает на переменную х

y=*point; /*применение операции взятия значения, расположенного по адресу, на который указывает point, теперь у содержит значение х (т.к. point указывал на х) */

printf("x=%f y=%f",x, y); //вывод значений х и у (они одинаковы)

++*point; //увеличение на единицу значения, расположенного по адресу, на который указывает указатель point

printf("\n x=%f y=%f",x, y); // теперь значение х увеличено на 1, у – без изменения

y=1+*point*y; //сумма единицы и значения по указателю point, умноженного на значение переменной у

printf("\n x=%f y=%f",x, y); //вывод значений х и у

getch(); }

К указателям можно применить операцию присваивания. Указатели одного и того же типа могут использоваться в операции присваивания, как и любые другие переменные.

Пример:

{

int x=10;

int *p, *g; //объявление указателей p и g

p=&x; //установка указателя p на переменную х (адрес, на который указывает р будет совпадать с адресом переменной х)

g=p; // теперь указатель g тоже указывает на х

printf(''p=%p'',p); //вывод на экран содержимого указателя р (вывод адреса)

printf(''\n g=%p'', g); //вывод на экран содержимого указателя g(вывод адреса)

printf(''\n x=%d *g=%d'', x,*g); //вывод на экран величины переменной х и величины, находящейся по адресу указателя g

}

В языке С допустимо присвоить указателю любой адрес памяти. Од­нако, если объявлен указатель на целое:

int *p;

а по адресу, который присвоен данному указателю, находится переменная х типа float, то при компиляции программы будет выдано сообщение об ошибке в строке:

р=&х;

Эту ошибку можно исправить, преобразовав указатель на int к типу указателя на float явным преобразованием типа:

p=(int*)&x;

Но при этом теряется информация о том, на какой тип указывал ис­ходный указатель.

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

Как и над другими типами переменных, над указателями можно произ­водить арифметические операции: сложение и вычитание. (Операции ++ и -- являются частными случаями операций сложения и вычитания). Ариф­метические действия над указателями имеют свои особенности.

Пример:

{

int *p;

printf("p=%p", p);

printf("\n ++p=%p",++p); //вывод адреса, на который указывает р, а также адреса после операции ++ над указателем р

getch(); }

После выполнения данного фрагмента программы видно, что при операции ++р значение указателя р увеличилось не на 1, а на 2. И это правильно, так как новое значение указателя должно указывать не на следующий адрес па­мяти, а на адрес следующего целого. А целое, как мы помним, занимает 2 байта. Если бы базовый тип указателя был не int, а double, то были бы на­печатаны адреса, отличающиеся на 8, именно столько байт памяти зани­мает переменная типа double, т. е. при каждой операции ++р значение ука­зателя будет увеличиваться на количество байт, занимаемых переменной базового типа указателя.

Операции над указателями не ограничиваются только операциями ++ и --. К указателям можно прибавлять некоторое целое или вычитать целое. Пусть указатель р имеет значение 2000 и указывает на целое. Тогда в ре­зультате выполнения оператора:

p=p+3;

значение указателя р будет 2006. Если же указатель р1=2000 был бы указа­телем па float, то после применения оператора:

р1=р1+10;

значение р1 было бы 2040.

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

р=р+n;

будет иметь вид:

<р>=<р>+n*<количество байт памяти базового типа указателя >

Можно также вычитать один указатель из другого. Так, если р и р1 - ука­затели на элементы одного и того же массива, то операция р – р1 дает такой же результат, как и вычитание индексов соответствующих элементов массива.

Другие арифметические операции над указателями запрещены, напри­мер нельзя сложить два указателя, умножить указатель на число и т. д.

Указатели можно сравнивать. Применимы все 6 операций:

<,>,<=, >=, = ,= =, !=

Сравнение р<g означает, что адрес, находящийся в р, меньше адреса, находящегося в g. Если р и g указывают на элементы одного массива, то индекс элемента, на который указывает р, меньше индекса массива, на который указывает g.

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

int **point;

Переменная point имеет тип указатель на указатель на int. Чтобы получить целочисленное значение переменной, на которую указывает point следует использовать:

**point;

Пример:

{

int i=7,*p,**pp; //pp-указатель на указатель типа int

p=&i; //взятие адреса у переменной i, теперь р указывает на i

pp=&p; //взятие адреса у указателя р, теперь рр тоже указывает на i

printf("i=%d\n p=%p\n pp=%p", i, p, pp); //вывод значения переменной i, а также адресов р и рр

printf("\n*p=%d\n**pp=%d",*p,**pp);//вывод значений, расположенных по адресам указателей р и рр (значение переменной i)

++*p; //увеличение значения, расположенного по указателю р на единицу

printf("\n i=%d \n *p=%p\n **pp=%p", i, *p, **pp);

**pp=12; //значение i устанавливается равным 12 (т.к. указатель рр имеет адрес указателя р, который указывает на i)

printf("\n i=%d \n *p=%p\n **pp=%p", i, *p, **pp);

Инициализация указателей (указатель null)

После того как указатель был объявлен, но до того, как ему было при­своено какое-то значение, указатель содержит неизвестное значение. По­пытка использовать указатель до присвоения ему какого-то значения яв­ляется ошибкой, она может нарушить работу не только программы, но и операционной системы. Даже если этого не про­изошло, результат работы программы будет неправильным и найти эту ошибку будет достаточно сложно.

Принято считать, что указатель, который указывает в "никуда", должен иметь значение null, однако и это не делает его "безопасным". После того как он попадет в правую или левую часть оператора присваивания, он вновь может стать "опасным".

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

Если была попытка присвоить какое-либо значение тому, на что указывает указатель с нулевым значением, система выдает предупреждение, появ­ляющееся во время работы программы (или после окончания работы про­граммы) "Null pointer assignement". Появление этого сообщения указывает программисту осуществить поиск использования неинициализированного указателя в программе.

Связь массивов и указателей

В языке Си имя массива трактуется как указатель-константа на массив. Пусть в программе объявлен массив:

int X[10];

В таком случае Х является указателем на нулевой элемент массива в памяти компьютера. В связи с этим является истинным отношение

X==&X[0]

Следовательно, для доступа к элементам массива кроме индексированных имен можно использовать разадресованные указатели по принципу:

Имя[индекс] тождественно *(имя+индекс)

Например, для описанного выше массива Х взаимозаменяемы следующие обозначения элементов:

X[5], или *(Х+5), или *(5+Х).

В языке Си операция [ играет роль знака операции сложения адреса массива с индексом элемента.