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

Лекция 06

.pdf
Скачиваний:
10
Добавлен:
19.05.2015
Размер:
262.62 Кб
Скачать

Кафедра автоматизации технологических процессов Тверского государственного технического университета

Разработчик: доцент В. Г. Васильев

ЛЕКЦИЯ № 6 «Использование указателей в программах

на языках С / С++ (часть 1)

по курсу «Структуры и алгоритмы обработки данных»

(для специальности «Управление в технических системах»)

СОДЕРЖАНИЕ

6.1.Указатели на базовые типы данных

6.2.Основные операции, связанные с применением указателей

6.3.Указатель на неопределенный тип данных

6.4.Указатели на структурные переменные и объекты классов

6.5.Указатели на функции

ВВЕДЕНИЕ

Применение языков программирования С/C++ наиболее эффективно для решения задач системного программирования и для разработки различных инструментальных средств. Данные языки позволяют осуществлять работу с адресами памяти компьютеров через специальные переменные, называемые указателями. Поэтому свободное владение механизмом применения указателей является одним из ключевых моментов разработки сложных и эффективных программ.

Данная работа состоит из трех разделов. Первый раздел посвящен применению указателей при разработке программ для OS Windows. При разработке таких программ в подавляющем числе приложений используется уже не язык С, а его наследник С++. Однако механизм применения указателей в программах одинаков в обоих языках.

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

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

Третий раздел посвящен применению указателей при разработке программ на языке C# для платформы .NET. Здесь применение указателей весьма специфично.

6.1 Указатели на базовые типы данных

Указатель - это поименованный адрес памяти компьютера. Это означает, что в Си – программах адресам переменных могут быть даны имена, что позволяет обращаться к переменным, используя их символическое обозначение. К примеру, адрес здания по проспекту Ленина, д. 25 имеет имя: корпус «ХТ ТГТУ». Использование имени часто оказывается более удобным, чем использование адреса.

Для примера рассмотрим следующую Си - программу.

/* программа # 1.1 */

void main( void )

{

char Ch; int count;

}

В программе объявлено две переменных: первая символьного типа, а вторая целого. Для первой переменной при компиляции программы будет выделен один байт, а для второй - четыре байта памяти (в 32–х разрядной машине). Для того чтобы узнать адрес байта переменной Ch и адрес первого байта переменной count, необходимо объявить два указателя и присвоить им адреса соответствующих переменных, как показано в программе #1.2:

/* программа # 1.2 */

void main ( void )

{

char Ch, *point_Ch; int count,*point_count;

Ch = 'T'; count = 0;

point_Ch = &Ch; // операция получения адреса и инициализация указателя point_count =&count; // операция получения адреса и инициализация

указателя

return;

}

Приведенные объявления в программе читаются следующим образом: point_Ch есть указатель на тип сhar. Переменная point_count есть указатель на тип int. Указатель может быть объявлен на любой тип данных без исключения. Это может быть структура, класс, функция, объединение и пр.

Выбор имен (идентификаторов) указателей дело программиста. Обязательным является символ '*' перед именем идентификатора. Именно этот символ характеризует данную переменную как указатель. При компиляции программы будет выделена память для хранения двух адресов. В 32 – разрядной машине это - 4 байта независимо от того, с каким типом данных будет работать указатель.

Символ '&', стоящий перед именем переменной, означает операцию получения адреса этой переменной. В двух последних операторах программы переменной point_Ch присваивается адрес переменной Ch, а переменной point_count - адрес переменной count. Другими словами, в двух последних операторах выполняется инициализация указателей. Теперь адрес переменной Ch имеет имя point_Ch, а переменной count - point_count.

Для того чтобы узнать адреса переменных Ch и count, усложним программу.

/* программа # 1.3 */

# include <stdio.h> void main ( void )

{

char Ch, *point_Ch; int count,*point_count;

Ch = 'T'; count = 0;

point_Ch = &Ch; point_count =&count;

printf("Адрес переменной Ch = %08X \n",point_Ch); printf("Адрес переменной count = %p \n",point_count);

}

В начале поясним, что означает спецификация формата 0Х8 в первом операторе printf. 0 - напечатать ведущие (незначащие) нули в адресе. 8Х-

отвести 8 позиции для выдачи на экран значения адреса, которое должно быть представлено шестнадцатеричным числом. Х прописное означает, что если в шестнадцатеричном числе имеются символы, то они должны быть напечатаны прописными буквами. Во втором операторе printf используется спецификация формата p, которая специально предназначена для печати адресов. Адрес самого указателя также может быть получен через операцию &. Например, с помощью оператора программы

printf("Адрес переменной point_Ch = %08X\n",&point_Ch);

На экран будет выведен адрес самого указателя point Ch (точнее адрес первого байта, поскольку самая переменная 4-x байтная).

6.2 Основные операции, связанные с применением указателей

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

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

point_Ch = &Ch; // операция получения адреса и инициализация указателя.

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

int *point_count = NULL;

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

Пример:

char *point_Ch;

printf("Адрес переменной point_Ch = %08X\n",&point_Ch);

3.Операция чтения содержимого памяти ПК по адресу, на который ссылается указатель.

/* программа # 1.4 */

# include <stdio.h> void main ( void )

{

int count, count_1, *point_count;

count = 5; point_count =&count;

count_1 = *point_count;

printf("Значение count_1 = %d\n",count_1); // или сразу так

printf("Значение count_1 = %d\n",*point_count);

}

В операторе count_1 = *point_count; перед переменной point_count стоит оператор ‘*’ и сам оператор стоит в правой части оператора присваивания. Это означает операцию чтения (получения) числа по адресу. В итоге переменной count_1 будет присвоено число 5. Обратите внимание, что перед тем, как выполнить операцию чтения памяти по адресу, выполнена инициализация указателя (указателю присвоен адрес переменной count_1).

В программе 1.5

/* программа # 1.5 */

# include <stdio.h> void main ( void )

{

int count,count_1, *point_count;

count = 5;

count_1 = *point_count;

printf("Значение count_1 = %d\n",count_1);

}

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

4.Операция записи чисел по адресу, на который ссылается указатель.

В программе 1.6

/* программа # 1.6*/

# include <stdio.h> void main ( void )

{

int count, *point_count;

count = 5; point_count = &count; *point_count = 10;

printf("Значение count = %d\n",count);

}

указатель point_count стоит в левой части оператора присваивания. По адресу, на который ссылается указатель, будет записано число 10. Эквивалентный оператор программы count = 10.Это нетрудно показать следующим образом.

Поскольку point_count = &count, то оператор *point_count = 10 может быть записан так:

*&count = 10;

Здесь в начале будет получен адрес переменной count( операция *), а затем по этому адресу записано число 10, что равносильно оператору count = 10.Таким образом, операция * уничтожает операцию &.

Таким образом, если операция ‘*’ (ее еще называют операцией операцией снятия ссылки ) находится в левой части операции присваивания, то выполняется операция присваивания (записи по данному адресу). И наоборот, если операция ‘*’ находится в правой части операции присваивания, то выполняется операция чтения по адресу. Если в первом случае данные могут быть испорчены (опасная операция) , то во втором - нет.

Объясним теперь, почему при объявлении указателей необходимо указывать тип данных, на который ссылается указатель. Причина в том, что в операциях чтения и записи по адресу операции выполняются с тем числом байт памяти, которые в данной системе программирования отводятся для хранения данных соответствующего типа. К примеру, если указатель объявлен на тип данных double (8 байт), то в операциях чтения и записи работа будет осуществляться с 8 байтами. Если указатель объявлен на тип int, то c 4- байтами пр.

Приведем пример программы #1.7, поясняющей возможность побайтного чтения памяти.

/* программа # 1.7 */

# include < stdio.h> void main()

{

char *point_Ch; unsigned long l; int i;

l = 0x89ABCDEF; point_Ch = (char *)&l;

for ( i = 0; i < sizeof ( long) ; i++)

{

printf("%x ", point_Ch,*point_Ch & 0x00ff); point_Ch ++;

}

}

Здесь указателю point_Ch присвоен адрес длиной целой l = 0x89ABCDEF. В памяти эта переменная занимает 4 байта. В цикле на каждой итерации значение адреса увеличивается на единицу. В операторе point_Ch ++ осуществляется переход к следующему байту памяти. При этом каждый раз читается только один байт, так как указатель point_Ch ссылается на тип char. В операторе printf(...) выводится значение по адресу.

При выполнении этой программы был получен такой результат: efcdab89

Исходное число выведено на экран в обратном виде, так как младшие разряды числа хранятся по младшим адреса памяти, а старшие – по старшим.

ЗАМЕЧАНИЕ. Подумайте, как сделать программу, чтобы число на экране выглядело правильно.

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

Программа # 1.7 демонстрирует еще одну операцию с указателями. Указатель - это число, под которым подразумевается адрес памяти. Сложение или

вычитание влечет за собой изменение адреса. В приведенной программе это реализовано оператором point_Ch ++; где ++ - операция инкремента на единицу. Фактически адрес увеличивается не на единицу, а на то количество байт памяти, с каким типом данных работает указатель.

6.И, наконец, последняя операция - присваивание одному указателю значения другого, что и показано в программе #1.8:

/* программа # 1.8 */

# include < stdio.h> void main()

{

char *point_Ch; unsigned long l,*point_l;

point_l = &l;

point_Ch = (char *)point_l;

}

В последнем операторе выполнено приведение типов данных. Если этого не сделать, то компилятор выдаст ошибку при компиляции программы.

6.3. Указатель на неопределенный тип данных

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

void * point;

Ключевое слово void в объявлении указателя позволяет отсрочить определение типа, на который ссылается указатель. Часто это дает возможность сделать программы более эффективными.

Рассмотрим пример. void main ()

{

char a; int *point;

point = &a;

}

В этой программе указателю на тип int присваивается адрес переменной символьного типа. При этом компилятор выдаcт ошибку. Однако, ошибки не будет если поступить так, как это сделано в программе # 1.9

/* программа #1. 9 */

void main ()

{

char a; int b; float c; void *point;

point = &a; point = &b; point = &c;

}

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

/* программа # 1.10 */

void main ()

{

double c;void *point;

point = &c;

*((double *)point) = 3.1415926; /* приводим указатель

к типу double и выполняем операцию записи по адресу */ printf("Значение числа PI = %lf\n",c);

}

Фактически оператор программы:

*((double *)point) = 3.1415926;

означает, что в данный момент мы работаем с данными типа double. Часто применение указателей на тип void бывает полезным.

6.4.Указатели на структурные переменные и объекты классов

Вприведенных примерах были использованы указатели на базовые типы данных языка Си. Однако использование указателей гораздо шире.

Пусть в программе объявлена структура, имя которой _BOOK_. struct _BOOK_

{

char autor[20]; int cost;

} book, *point_book;

Оператором программы

point_book = & book;

будет инициализирован указатель адресом переменной book. Для доступа к полям структуры через указатель используется операция "стрелка", которая в Си программах выглядит так '->'.К примеру, поместим в поле структуры autor фамилию автора книги:

strcpy( point_book -> autor,"ИВАНОВ И. И.");

Функция strcpy () предназначена для копирования строк. Эквивалентная, но более длинная и потому редко используемая запись, выглядит таким образом:

strcpy( (*point_book ). autor,"ИВАНОВ И. И.");

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

Вследующем примере показана возможность чтения поля структуры и присваивания значения cost поля переменной cost_book:

int cost_book;

cost_book = point_book -> cost ;

//cost_book = (*point_book). cost ; можно и так, но это длинно.

Аналогичным образом осуществляется доступ к членам классов. /* программа # 1.11 */

class A

{