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

Лекция 07

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

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

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

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

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

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

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

СОДЕРЖАНИЕ

7.6.Массивы и указатели

7.7.Использование указателей для связи функций

7.8.Массив указателей

7.9.Указатель на указатель или адрес адреса

7.10.Динамическое выделение памяти под многомерные массивы

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

7.6. Массивы и указатели

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

Рассмотрим пример.

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

void print_string (char *); /* прототип функции print_string */ void main()

{

char *string = "HELLO,WORLD";

print_string (string);

}

/*эта функция печатает строку

# include <stdio.>

void print_string (char *array_ch)

{

printf ("s%",array_ch)

}

В этой программе объявлен указатель на тип char. Укaзатель инициализирован адресом строки "HELLO,WORLD", которая представляет собой массив символов, располагающийся в памяти байт за батом. string, следовательно, указывает, на символ 'H'.string+1 - на символ 'E'.string+2 - на символ 'L' и т. д. Далее в программе следует вызов функции print_string, которая печатает строку. Функция имеет один формальный параметр - укaзатель на тип char. При вызове функции ей передается адрес строки "HELLO,WORLD". Адрес строки поименован идентификатором string. Таким образом, имя (идентификатор) массива является адресом его первого элемента.

Вызов функции print_string может выглядеть и так:

print_string (&string[0]);

В данном случае используется операция получения адреса первого элемента массива, поименованного идентификатором string. При вызове функции

print_string (&string[6]);

на экран будет выдана стрoка WORLD, так как счет элементов массива начинается с нуля (символ 'W' является седьмым элементом). Рассмотрим другую программу.

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

void array_1(int ,int *); void main()

{

int array[20];

array_1 (20,array);

}

/*эта функция заполняет массив единицами */ void array_1(int n,int *array)

{

int i;

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

*(array + i) = 1; /// что эквивалентно array[i] = 1;

}

В этой программе объявлен целочисленный массив из 20 элементов. При вызове функции array_1 массив заполняется единицами. Формальными параметрами являются число элементов массива и адрес первого элемента, который поименован в данной программе идентификатором array. В теле функции в операторе *(array + i) = 1; в скобках вычисляется адрес элемента массива, а затем используется операция '*' - запись по адресу. Эквивалентный оператор: array[i] = 1; Отсюда ясно, почему индексация элементов массива в Си начинается с нуля: используется базовый адрес массива + смещение. Если смещение равно нулю (i =0), то мы адресуемся к первому элементу массива.

7.7.Использование указателей для связи функций

Вбольшинстве языков программирования параметры подпрограммам (функциям) передаются либо по ссылке (by reference), либо по значению (by value). В первом случае подпрограмма (функция) работает с адресом переменной, переданным ей в качестве фактического параметра. Во втором случае ей доступна не сама переменная, а только ее значение (копия числа). Различие здесь такое: переменную, переданную по ссылке, функция может модифицировать в вызвавшей ее функции, а переданную по значению - нет.

Вязыке Си параметры передаются только по значению. Общепринятый

способ обеспечить функции непосредственный доступ к

какой-либо

переменной из

вызывающей подпрограммы состоит в том,

чтобы вместо

самой переменной в качестве параметра передавать ее адрес.

 

Рассмотрим

программу, в которой осуществляется обмен значений

переменных с помощью функции swap ().

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

void swap(int *,int *); void main()

{

int a = 10,b = 20;

swap (&a,&b);

}

/*эта функция выполняет обмен значений переменных */ void swap(int *x,int *y)

{

int c; /* временная переменная */

c = *x; /* запоминаем значение переменной, на которую ссылается указатель х */

/* с = х; это неверно так как переменной с будет присвоен адрес ,а не само значение переменной, на которую ссылается указатель х */

*x =*y; /* переменной,на которую ссылается указатель х, присваиваем значение переменной, на которую ссылается указатель y */

*y = c; /* переменной,на которую ссылается указатель y, присваиваем значение из буфера */

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

Часто в программах требуется передавать функциям объекты классов или структурные переменные. Если передавать объект по значению, то будут иметь место большие накладные расходы на копирование памяти. Проще передавать указатель на объект – всего 4 байта памяти, содержащие адрес объекта. Доступ к члену класса в этом случае внутри функции должен осуществляться оператором ‘->’.

#include <vcl.h> #pragma hdrstop #pragma argsused

void calcul ( class A * ptr);

class A

{

public:

int a;

};

int main(int argc, char* argv[])

{

A a;

calcul (&a); return 0;

}

void calcul ( class A * ptr)

{

ptr-> a = 100; // доступ к члену а класса A

}

7.8. Массив указателей

Часто в программах требуется объявить массив строк. Это можно сделать, используя массив указателей, как показано ниже.

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

# define DIM(x) (sizeof(x) / sizeof( x[0] )) void main()

{

/* char *col[3] - массив указателей (адресов) из трех элементов*/ char *color[] = {

/* это инициализация массива адресами строк */

"RED", "BLUE","GREEN " };

short i;

/* выводим названия цветов на экран */ for ( i = 0; i < DIM (color); I ++)

printf("%s\n", color[i]);

}

В этой программе первому элементу массива color присваивается адрес строки "RED" (индекс массива i = 0), второму элементу массива color[1] -

адрес строки "BLUE" и т.д. Затем значения массива выводятся на экран с помощью функции printf( ).

7.9. Указатель на указатель или адрес адреса

Объявление в программе: int **point -означает, что переменная point используется для хранения адреса адреса. Указателю point при распределении памяти будет выделено также 4 байта. Проиллюстрируем возможность использования таких указателей следующей программой.

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

void main()

{

int a,*point_1,**point_2,***point_3;

a = 5;

point_1 = &a;/* инициализируем указатель адресом переменной а */ point_2 = &point_1; /* это адрес адреса переменной а */

point_3 = &point_2; /* это адрес адреса адреса переменной а */

***point_3 = 10; /* присваиваем переменной а значение 10 */

}

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

7.10. Динамическое выделение памяти под многомерные массивы

Часто двойная косвенная адресация используется для выделения памяти под двумерные массивы, тройная - под трехмерные и т. д. Программа # 1.19 иллюстрирует выделение памяти под двумерный массив типа float, возврат память OS и печать матрицы. Основная идея программы - представление двумерного массива как совокупности некоторого числа одномерных массивовстрок матрицы. Подробности содержатся в комментариях программы.

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

#include <vcl.h>

#include <conio.h> #include <stdio.h> #pragma hdrstop #pragma argsused

// Протипы функций, используемых в программе float ** Get_mem ( int n, int m );

void Del_mem ( float ** A, int n );

void print_matrix (float **A, int n, int m, char *format);

int main(int argc, char* argv[])

{

int n,m;

n =10; m=10;

float ** Matrix = Get_mem ( n, m); // Выделяем память под двумерный массив

//10*10

for ( int i =0; i < n ; i ++) // строим единичную матрицуна главной диагонали, которой - единицы

for ( int j =0; j < m ; j ++)

if ( i == j ) Matrix [i][j]= 1.0;

print_matrix (Matrix, n, m, "%3.1f ");

Del_mem (Matrix,n); // Возвращаем память ОС getch ();

return 0;

}

// Выделяем память для матрицы в n строк и m – столбцов float ** Get_mem ( int n, int m )

{

float ** buffer;

buffer = new float *[n]; // выделяем память под массив из n элементов. Этот //массив будет хранить адреса строк (массивов) матрицы. Поэтому в операторе //new тип данных float *(адреса данных типа float). Адрес данного

массива записываем в переменную buffer, смысл которой - адрес адресов (массив указателей).

for ( int i =0; i < n ; i ++) // выделяем память n –раз под массивы из m //элементов - строки матрицы

buffer[i] = new float [m];

// Обнуляем матрицу for ( int i =0; i < n ; i ++)

for ( int j =0; j < m ; j ++) buffer[i][j] = 0.0;

return buffer; // возвращаем адрес массива указателей

}

// возвращаем память OS

void Del_mem ( float ** A, int n )

{

for ( int i =0; i < n ; i ++) // удаляем память – строки матрицы

delete[] A[i]; // Так правильно удаляется память, выделенная под массив

// delete[]

delete[] A; // удаляем сам массив указателей

return;

}

// Вывод матрицы на экран

void print_matrix (float **A, int n, int m, char *format)

{

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

{

for ( int j =0; j < m ; j ++) printf (format, A[i][j]);

printf ("\n");

}

return;

}

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

Большинство ошибок в программах на языке С/ С++ является следствием неправильного использования указателей. Во-первых, применение неинициализированного указателя недопустимо. Во-вторых, если память выделяется динамически с помощью оператора new, то нельзя забывать об операторе delete. В противном случае будет иметь место утечка памяти. В- третьих, к указателям не установленным в ноль оператор delete нельзя применять дважды.

delete [] massiv;

//выполняется код программы

……………………………

……………………………

……………………………

//и повторно

delete [] massiv; // так нельзя

Однако, если указатель установить в ноль, то повторное использование delete уже безопасно.

delete [] massiv; massiv = NULL;

delete [] massiv; // так безопасно

Поэтому используйте следующий код перед тем, как применять оператор delete:

if (massiv)

{

delete[] massiv; massiv = NULL;

}

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

ЗАКЛЮЧЕНИЕ

Необходимость применения указателей в программах возникает в том случае, когда требуется работать с адресами памяти непосредственно,

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