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

Методические указания

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

Значением переменной типа указатель служит адрес некоторой величины, т. е. указатель – это адрес памяти. Общая форма описания указателя имеет следующий вид: <тип> *<имя указателя>. Например;

int* p; /* указатель на переменные типа int */

char* Z; /*указатель на символьную переменную*/

float* x; /* указатель на переменные с плавающей точкой */

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

Наиболее важные операции с указателями – это операция задания адреса указателю и операция обращения по адресу * (косвенная ссылка).

Например;

int x = 2, z = 10;

int *p;

p = &x; /*& x – это адрес размещения переменной x*/.

p; /* это величина, помещенная по адресу &x, т. е. *p = 2 */.

p = &Z; /* принимает значение адреса размещения Z*/.

* p /* величина, помещенная по адресу & Z, т. е. *p = 10 */.

Таким образом указатель характеризуется тремя свойствами:

1. Под указатель отводится память, размер которой можно определить с помощью функции sizeof <указатель>, адрес размещения указателя можно определить через операцию &.

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

3.Указатель определяет значения по адресу, которая имеет переменная указатель (операция * – косвенная ссылка).

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

При работе с указателями в программе могут возникнуть серьезные ошибки, вызванные неинициализированным указателем. Подобная ошибка одна из наиболее частых при работе с указателями. Например, описание int *x приводит к тому, что компилятор резервирует память, необходимую для размещения указателя. В этой ячейке памяти содержится некоторый адрес памяти. Этот адрес может совпадать с адресом операционной системы или транслятора. Если программа в этот момент вводит по этому адресу какое–то значение, например *x = 10, то это может привести к тому, что будет затерта часть операционной системы. К сожалению, компилятор языка Си не может обнаружить такую ошибку. Поэтому при использовании указателей в программе они обязательно должны быть инициализированы.

Для инициализации указателей существует несколько способов:

1. Присвоить указателю адрес переменной, используя операцию взятия адреса «&».

2. Присвоить указателю значение другого указателя, к этому моменту уже правильно инициализированного.

3. Использовать функции распределения памяти такие как malloc и calloc. Описание этих функций содержатся в файле <alloc.h>. Этот файл должен быть включен с помощью директивы #include.

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

Например, float * x = (float*) malloc (sizeof (float)); приводит к тому, что из свободной памяти выделяется 4 байта, преобразуется к типу float, и указатель x принимает адрес выделенной памяти. Если в памяти нет свободного места, то в качестве результата функция malloc возвращает адрес ноль. Поэтому вышеуказанный пример можно переписать таким образом

float *x;

if ((x = (float*) malloc (sizeof (float))! = 0)

{*/* фрагмент программы */}

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

Имя массива является константой–указателем, т. е. имя массива определяет адрес размещения первого элемента массива. Например объявление int x [5] приводит к тождеству x = = &x[0]. Оба обозначения являются константами, поскольку адреса размещения массива изменить нельзя. Однако, их можно присвоить переменной типа указатель и изменять значение переменной. Приведем текст программы с различными вариантами операций с указателями и массивами.

void main ( )

{

float *q; static float Z[2] = {4, 3.5}; int i;

q = Z; /* либо q = & Z[0] */

/* присваивание указателю адрес первого элемента массива Z*/

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

printf («указатель + % d: адрес = % u: значение: % f\n”, i, q, * q++);

/* аналогично ниже */

/* printf («указатель + % d: адрес = % u: значение: % g\n”,i, & Z [i]) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, q + i, q [i]) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, q + i, *(q+i) */

/* printf (“указатель + % d: адрес = % u: значение: % f \n”, i, Z+i, *(Z+i)*/

}

При выполнении этой программы с любым одним оператором вывода на экране дисплея получим:

указатель + 0: адрес 56026: значение 4

указатель + 1: адрес 56030: значение 3.5

Адрес 56026 – это начальный адрес массива Z.

Вторая строка–результат прибавления единицы к адресу. Прибавление единицы к указателю означает что, компилятор языка добавляет единицу памяти (для float – эта величина 4), а для массивов осуществляется переход к следующему элементу массива. Поэтому величина 56026 + 1 дает адрес равный 56030.

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

p = Z; Z[i] = = *(Z + i) = = p[i] = = * (p + i).

Таким образом, при работе с массивами допустимы два вида операций, а именно: операция индексации и операция типа указатель. Использование операции индексации делает программу более читаемой, а использование операции типа указатель делает ее более компактной и эффективной.

Ниже приведена программа где используются указатели.

# include <stclio.h>

# include <alloc.h>

# define RAS 50

{ main ( )

float max, * a = (float *) malloc (RAS * sizeof (float));

/* выделение памяти для массива a */

int i;

puts (“вводите элементы массива”);

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

for (i = 0; i < RAS; scanf (“% f”, a + i), i++);

/* цикл определения max */

for (i = 1, max = *a; i < RAS; ++a, i++)

if (*a > max) max = *a;

printf (“max = % 5.2 f \n”, max);

}

3. Указатели и многомерные массивы

Имя многомерного массива также как и одномерного, является константой указателем, поэтому и в этом случае указателю можно присвоить адрес размещения любого элемента массива. Рассмотрим такое описание: int Z [4][2], *P. Тогда присваивание p = Z, указывает на то, что указатель p получил адрес размещения первого элемента массива Z, т. е. справедливо тождество p = = Z = = & Z [0][0]. Так как элементы матрицы располагаются в памяти последовательно друг за другом по строкам, то увеличение p на единицу приводит к тому, что p будет указывать на следующий элемент массива. Это обстоятельство можно подтвердить таким тождеством p = 1 = = Z + 1 = = & Z [0][1] = = ++p, а любая компонента из нижеследующей записи *(p + 1) = = * (Z+ 1) = = Z [0][1] = = *(++p) определяет значения второго элемента первой строки.

Двухмерный массив – это массив массивов. Если Z – двухмерный массив, то можно определить имена четырех строк, каждая из которых является массивом из двух элементов. Имя первой строки Z[0], второй Z[1] …, четвертый Z[3]. Значит Z[0] = = &Z[0][0]; Z[1] = = &Z[1][0]; … Z[3] = = &Z[3][0]. Все выражения в Си, содержащие обращения к массивам переводятся в выражения с указателями. Выражение Z[3][1] эквивалентно выражению с указателем *(Z[3]+1) = = *(*(Z+3)+1).

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

void main ( )

{

static ffloat Z[5][4] = {{…},…P }};

float max, *p; int i, j;

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

{

max = * Z[i];

/*либо p = Z[i]; max = *p; */

for (j = 1; j < 4; j++)

if (*(Z[i]+j) > max) max = * (Z[i] + j);

/* или if (*(p+j) > max) max = (p+j)*/

/* или if (*(Z+4*i+j) > max) max = (Z+4*i+j)*/

/*Z+4*i*j – это адрес элемента &Z[i][j]*/

printf(“\\n для строки i = % d, max = % +”, i, max);

}

}

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

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

float Z[5][4]; int *m[5], i;

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

m[i] = Z[i];

/*m[i] = &Z[i][0];*/

  1. Пример выполнения задания

В качестве примера рассмотрим задачу пункта 3 из лабораторной работы № 9.

# include <stdio.h>

# include <alloc.h>

# define GOD 10

# define MES 12

main ( )

{

int i, j; float sommes, SOMGOD;

float * Z = (float*) malloc (GOD *MES* sizeof (float);

/* это почти то же, что и float Z[GOD][MES]*/

float p = Z; /* для запоминания начального адреса выделенной памяти */

for (j = 0; j < MES * GOD; j++)

{

printf (“вводите % d элемент матрицы Z \n”, j);

scanf (“% f”, Z++); /* либо scanf (“%f”, Z + J)*/

printf (“год количество осадков \n”);

for (i = 0; i < GOD: i++)

{

for (j = 0, SUMGOD = 0; j< MES; j++)

SUMGOD + = * (Z + i* MES + J);

/* если использовался scanf (“%f”, Z+j) */

/* или SUMGOD + = * (p + i * MES +j), если ввод scanf (“%f”, Z++)*/

printf (“%5d%10.1f\n”, 1990+i, SUMGOD);

}

printf (“янв. фев. мар. апр. май. июн. июл. авг”);

printf (“сен. окт. ноя. дек. \n”);

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

{for j = 0, SUMMES = 0; J < GOD; J++)

SUMMES + = * (Z + j * MES + i);

/* или SUMMES + = * (p + j * MES + i), если ввод scanf (“%f”, Z++)*/

printf (“%4.1f”, SUMMES/GOD);

}

printf (“\n”);

}

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]