- •Министерство образования Российской Федерации
- •Контрольные вопросы
- •Задания для выполнения
- •Варианты задания
- •Классы памяти. Массивы, операция индексации Цель работы
- •Контрольные вопросы
- •Варианты заданий
- •Массивы. Связь массивов и указателей Цель работы
- •Методические указания
- •Контрольные вопросы
- •Варианты заданий
- •Символьные строки
- •Контрольные вопросы
- •Варианты заданий
- •Функции. Основные правила использования функций
- •Контрольные вопросы
- •Варианты заданий
- •Функции. Использование массивов и функций в качестве формальных параметров
- •Контрольные вопросы
- •Варианты заданий
- •Контрольные вопросы
- •Варианты заданий
- •Файлы Цель работы
- •Методические указания
- •Функция
- •Контрольные вопросы
- •Варианты заданий
Методические указания
Определение указателя
Значением переменной типа указатель служит адрес некоторой величины, т. е. указатель – это адрес памяти. Общая форма описания указателя имеет следующий вид: <тип> *<имя указателя>. Например;
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.Указатель определяет значения по адресу, которая имеет переменная указатель (операция * – косвенная ссылка).
Инициализация указателей
При работе с указателями в программе могут возникнуть серьезные ошибки, вызванные неинициализированным указателем. Подобная ошибка одна из наиболее частых при работе с указателями. Например, описание 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];*/
Пример выполнения задания
В качестве примера рассмотрим задачу пункта 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”);
}
