Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
16
Добавлен:
17.04.2015
Размер:
62.9 Кб
Скачать

Лекция № 19. Указатели.

Содержание лекции.

1. Роль указателей в языке С.

2. Назначение указателей.

3. Определение указателя.

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

    2. Операции получения адреса и значения.

    3. Правила использования основных операций.

    4. Указатель на переменную и константу

    5. Инициализация указателей.

    6. Работа с указателями

    7. Задание, инициализация, ввод и вывод переменных и их указателей.

    8. Ограничения на использование основных операций над указателями.

    9. Указатель константа.

    10. Константы и ссылки/указатели.

    11. Примеры с использованием указателей.

    12. Порядок выполнения операций над указателями.

    13. Многоуровенная адресация.

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

    15. Пустой указатель.

    16. Ввод адреса с клавиатуры

    17. Разность значений указателей.

    18. Указатели и управление памятью.

    19. Присваивание указателей различного типа.

    20. Роль операции sizeof в управлении типа памяти.

    21. Указатель на void.

    22. Работа с памятью переменного формата.

            1. Роль указателей в языке С.

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

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

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

· указатель – адресная информация о расположении информационного ресурса, через которую пользователь может обратиться к нему. При изменении содержимого объекта через указатель на него всегда возникает проблема синхронизации (разделения) ресурса между несколькими пользователями, имеющими адресную информацию о нем. Синонимом указателя в информационных технологиях является ссылка. Иногда она имеет все внешние признаки объекта, например, ярлык файла на рабочем столе, который внешне выглядит как файл, а на самом деле ссылается на файл-оригинал.

В языках программирования термины объект (значение), указатель и ссылка имеют примерно аналогичный смысл, но касаются способов доступа и передачи значений переменных.

· терминология ссылка, значение касается фундаментальных свойств переменных в языках программирования. Имя переменной в различных контекстах может восприниматься как ее значение (содержимое памяти), так и ссылка на нее (адрес памяти, указатель). Например, при присваивании левая часть рассматривается как ссылка, а правая – как значение ;

· при передаче формальных параметров при вызове процедур (функций) практически во всех языках программирования реализованы способы передачи по ссылке и по значению;

· в Паскале и Си определено понятие указатель как переменная особого вида, содержащая адрес размещения в памяти другой переменной. Использование указателей позволяется создавать динамические структуры данных, в которых элементы взаимно ссылаются друг на друга;

· и, наконец, в Си существует расширенная интерпретация указателя, именуемая адресной арифметикой, которая позволяет интерпретировать значение любого указателя как адрес не отдельной переменной, а памяти в целом, где она размещена.

Указатель как элемент архитектуры компьютера. Указатели занимают особое место среди типов данных, потому что они проецируют на язык программирования ряд важных принципов организации обработки данных в компьютере. Понятие указателя связано с такими понятиями компьютерной архитектуры как адрес, косвенная адресация, организация внутренней (оперативной) памяти. От них мы и будем отталкиваться. Внутренняя (оперативная) память компьютера представляет собой упорядоченную последовательность байтов или машинных слов (ячеек памяти), проще говоря - массив. Номер байта или слова памяти, через который оно доступно как из команд компьютера, так и во всех других случаях, называется адресом. Если в команде непосредственно содержится адрес памяти, то такой доступ этому слову памяти называется прямой адресацией.

Возможен также случай, когда машинное слово содержит адрес другого машинного слова. Тогда доступ к данным во втором машинном слове через первое называется косвенной адресацией. Команды косвенной адресации имеются в любом компьютере и являются основой любого регулярного процесса обработки данных. То же самое можно сказать о языке программирования. Даже если в нем отсутствуют указатели, как таковые, работа с массивами базируется на аналогичных способах адресации данных.

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

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

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

· указатель, который содержит адрес переменной, ссылается на эту переменную или назначен на нее;

· переменная, адрес которой содержится в указателе, называется указуемой переменной.

2. Назначение указателей.

Язык Си отличается от других структурированных немашинных языков широким использованием указателей. В определённой степени именно наличие в языке Си указателей сделало его очень удобным для системного программирования. Указатели обеспечивают доступ к адресам переменных в памяти. Они позволяют работать с символическими адресами (именами переменных, содер-жащих адреса) и выполнение над адресами ряда операций. С помощью указа-телей например можно:

  • обрабатывать многомерные и одномерные массивы, строки, символы, структуры и массивы структур.

  • динамически создавать новые переменные в прцессе выполнения программы.

  • обрабатывать связанные структуры: стеки, очереди, списки, деревья, сети.

  • передавать функциям адреса фактических параметров.

  • передавать функциям адреса функция в качестве параметров.

Применение указателей часто критикуется из-за того, что в силу их природы невозможно определить, на что (на какую переменную) указывает в данный момент указатель, если не возвращаться к тому месту, где указателю в последний раз было присвоено значение. Это усложняет программу и делает её правильность сомнительной.Программист, знающий Си, должен прежде всего знать, что такое указатели, и уметь их испльзовать.

          1. Определение указателя.

Указатель-это переменная или константа, которая содержит значение адреса другой переменной.

 

Рис. 1. Графическая интерпретация указателя.

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

 

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

Указатель-это переменная или константа стандартного типа данных для хранения адреса переменной определённого типа. Тип адресуемой переменной может быть стандартный, перечислимый, структурный, объединение или void. Указатель на тип void может быть адрес.

Форма объявления переменной типа указатель:

тип [модификатор] *

где :

тип-имя типа переменной, адрес которой будет содержать переменная- указатель.(например integer, char, long)

имя-указателя –идентификатор переменной типа указатель.(имя собственное)

*-определяет переменную типа указатель.

 Значение переменной-указателя-это адрес некоторой величины, целое без знака. При выводе значения указателя надо использовать формат %u. Указатель содержит адрес первого байта переменной определённого типа. Тип адресуемой переменной, на которую ссылается указатель, определяет объём оперативной памяти, выделяемой переменной, связанной с указателем. Для того, что бы машинной программой обработать (например прочитать или записать) значение переменной с помощью указателя, надо знать адрес её начального (нулевого) байта и количество байтов, которая занимает эта переменная. Ну и указатель естественно содержит эти данные.

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

Примеры объявлений даны на рис.2.

Рис. 2. Примеры объявлений указателей.

        1. Операции получения адреса и значения.

Язык Си даёт возможность использования адресов переменных программы с помощью основных операций: &и*

&-получение адреса переменной.

*-извлечение значения, расположенного по этому адресу.

С помощью основных операций можно получить значение адреса переменной и использовать косвенную адресацию-получение значения переменной по её адресу.

Операции * и& можно писать вплотную к имени операнда или через пробел.Например:&i, *ptri.

Назначение этих операций:

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

*-имя указателя-получение значения определённого типа по заданному адресу. Определяет содержимое, находящееся по адресу, который содержится в указателе-переменной или указателе-константе. Иначе: косвенная адресация. Косвенная адресация значения с помощью операции* осуществляет доступ к значению по указателю, то есть извлечение значения, расположенного по адресу-содержимому указателя. Операнд*(т.е имя после этого значка) должно быть типа указатель(где-то раньше объявлено).

Оператор присваивания значения адреса указателю(иначе инициализация указателя) имеет вид:

имя указателя_переменной=&имя_переменной

Например: int *ptri,i; //объявление указателя и переменной типа int

ptri=&i; //ptri получает значение адреса ‘i’

В общем виде оператор присваивания, использующий имя указателя и *операцию косвеной адресации, можно представить в виде:

Имя_переменной=*имя_указателя

Где имя-указателя –это переменная или константа, которая содержит адрес размещаемого значения, требуемого для переменной левой части оператора присваивания.

Например: i=*ptri; // ‘i’ получает значение, расположенное по адресу содержащемся в указателе‘ptri’

Как и любые переменные, переменная типа указатель ptri имеет адрес и значение.

Схематично взаимосвязь между указателям и адресуемым значением представлениа на рис.3

 

Рис. 3. Взаимосвязь указателя, адреса и значения переменной.

Указатели можно использовать:

*ptri-значение переменной, находящейся по адресу, содержащемуся в указателяе ptri

ptri-значение адреса переменной

&ptri-адрес местоположения самого указателя

  1. Правила использования основных операций.

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

int i=123, j, *ptri; //объявление переменных и указателя

ptri=&i; //инициализация указателя(присвоение адреса i)

i=*ptri+1; //переменной i(*ptri)присваивается значение

переменной i и к её содержимому прибавляется единичка.

Следует отметить, что операции & и* более приоритетны, чем арифметические операции.

Операции & и * взаимоибратны:

Таким образом, последовательность действий при работе с указателем включает 3 шага:

1. Определение указуемых переменных и переменной-указателя. Для переменной-указателя это делается особым образом.

int a,x; // Обычные целые переменнные

int *p; // Переменная - указатель на другую целую переменную

В определении указателя присутствует та же самая операция косвен-ного обращения по указателю. В соответствии с принципами контек-стного определения типа переменной эту фразу следует понимать так: переменная p при косвенном обращении к ней дает переменную типа int. То есть свойство ее – быть указателем, определяется в контексте возможного применения к ней операции *. Обратите внимание, что в определении присутствует указуемый тип данных. Это значит, что указатель может ссылаться не на любые переменные, а только на переменные заданного типа, то есть указатель в Си типизирован.

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

p = &a; // Указатель содержит адрес переменной a

Операция & понимается буквально как адрес переменной, стоящей справа от нее. В более широкой интерпретации она «превращает» объект в указатель на него (или производит переход от объекта к указателю на него) и является в этом смысле прямой противополож-ностью операции *, которая «превращает» указатель в указуемый объект. То же самое касается типов данных. Если переменная a имеет тип int, то выражение &a имеет тип – указатель на int или int*.

3. И наконец, в любом выражении косвенное обращение по указателю интерпретируется как переход от него к указуемой переменной с вы-полнением над ней всех далее перечисленных в выражении операций.

*p=100; // Эквивалентно a=100

x = x + *p; // Эквивалентно x=x+a

(*p)++; // Эквивалентно a++

Замечание: при обращении через указатель имя указуемой перемен-ной в выражении отсутствует. Поэтому можно считать, что обраще-ние через указатель производится к «безымянной» переменной, а опе-рацию «*» называются также операцией разыменования указателя.

Указатель дает «степень свободы» или универсальности любому алгоритму обработки данных. Действительно, если некоторый фраг-мент программы получает данные непосредственно в некоторой пере-менной, то он может обрабатывать ее и только ее. Если же данные он получает через указатель, то обработка данных (указуемых перемен-ных) может производиться в любой области памяти компьютера (или программы). При этом сам фрагмент может и «не знать», какие дан-ные он обрабатывает, если значение самого указателя передано прог-рамме извне.

7. Указатель на переменную и константу.

Указатель может быть константой или переменной, а также указывать на константу или переменную.

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

int i; // целая переменная

const int ci = 1; // целая константа

int * p i ; // указатель на целую переменную

const int * pci; // указатель на целую константу

int * const ср = &i; // указатель-константа на целую

переменную

const int * const срс = &ci; // указатель-константа на целую

константу

Как видно из примеров, модификатор const, находящийся между именем указателя и звездочкой, относится к самому указателю и запрещает его изменение, а const слева от звездочки задает посто-янствозначения, на которое он указывает.

Для инициализации указателей использована операция получения адреса &.

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

              1. Инициализация указателей.

При определении указателя надо стремиться выполнить его инициализацию, то есть присвоение начального значения.Непреднамеренноеиспользование неинициализированных указателей —распространенныйисточник ошибок в программах. Инициализатор записыва-ется после имени указателя либо в круг-лых скобках, либо после знака равенства.

Существуют следующие способы инициализации указателя:

1. Присваивание указателю адреса существующего объекта:

• с помощью операции получения адреса:

int а = 5; // целая переменная

int* р = &а; //в указатель записывается адрес а

int* р (&а): // то же самое другим способом

• с помощью значения другого инициализированного указателя:

int* r= р;

• с помощью имени массива или функции, которые трактуются как адрес:

int b[10]; // массив

i n t * t = b; // присваивание адреса начала массива

void f ( int а ){ /* ... * / } // определение функции

void ( * pf ) ( int ); // указатель на функцию

pf = f; // присваивание адреса функции

2. Присваивание указателю адреса области памяти в явном виде:

char* vp = (char *)0хВ8000000:

Здесь ОхВ8000000 — шестнадцатеричная константа, (char *) — операцияприведения типа: константа преобразуется к типу «указатель на char».

3. Присваивание пустого значения:

int* SUXX = NULL;

1nt* rulez = 0:

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

Никогда не следует использовать неинициализированные указатели, потому что это приводит к ошибкам, которые весьма трудно подда-ются отладке.Инициализация указателей более актуальна, чем пере-менных, так как поведение программы, имеющей неопределённые указатели, непредсказуемо и часто вызывает зависание системы.

            1. Работа с указателями.

Указатели можно:

Объявить:

char *pC;

int *pI;

float *pF;

Указатели объявляются в списке переменных,но перед их именем ста-витсязнак*. Указатель всегда указывает на переменную того типа, для которого он был объявлен. Например,pC может указывать налю-бойсимвол,pI – на любое целое число,аpF – на любое вещественное число.

Присвоить адрес:

int i, *pI;

...

pI = &i;

Такая запись означает: «записать в указатель pI адрес переменнойi». Запись&i обозначает адрес переменнойi.

Получить значение по этому адресу:

float f , *pF;

...

f = *pF;

Такая запись означает: «записать в переменную f то вещественное число, на которое указывает указательpF». Запись*pF обозначает содержимое ячейки, на которую указываетpF.

Сдвинуть:

int *pI;

...

pI ++;

pI += 4;

-- pI;

pI -= 4;

В результате этих операций значение указателя меняется особым способом:pI++ сдвигает указатель на РАЗМЕР ЦЕЛОГО ЧИСЛА, то есть на 4 байта, а не на 1 как можно подумать.А записьpI+=4 илиpI=pI+4 сдвигает указатель на 4 целых числа дальше, то есть на 16 байт.

Обнулить:

char *pC;

...

pC = NULL;

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

Вывести на экран:

int i, *pI;

...

pI = &i;

printf("Адр.i =%p", pI);

Для вывода указателей используется формат %p или %u.