Главная страница

Лабораторная работа 5 Работа с указателями. Обработка числовых данных. Программирование пользовательских функций


Скачать 103 Kb.
НазваниеЛабораторная работа 5 Работа с указателями. Обработка числовых данных. Программирование пользовательских функций
АнкорLaboratornaya_rabota_5.doc
Дата19.03.2019
Размер103 Kb.
Формат файлаdoc
Имя файлаLaboratornaya_rabota_5.doc
ТипЛабораторная работа
#26076

Лабораторная работа №5


Работа с указателями. Обработка числовых данных.

Программирование пользовательских функций.

Цель работы:

  1. Изучить методы работы с указателями.

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

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


Теоретические сведения.

Указатели.

Работая с памятью, программист оперирует понятиями имя и значение переменной – это работа на высоком уровне абстракции. На машинном (низком уровне абстракции), компьютер оперирует понятиями «адрес участка памяти», выделенный под переменную и «содержимое участка памяти».

Определяя переменную, например: int var =1; программа заставляет компилятор выделять память, при этом имя переменной var адресует участок памяти, а константа 1 определяет значение, которое запишется по этому адресу. Имея в своем распоряжении адрес переменной, программисту необходимо с ним работать: сохранять, передавать и преобразовывать, для этой цели и служат указатели.

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

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

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

Пример:

int x, *p_i,; // объявить переменную x и указатель p_i

double f, *p_d; // объявить переменную f и указатель p_d

p_i = &x ; // установить указатели

p_d = &f ;

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

Операция &x означает: "получить адрес переменной x".

Оператор p_d = &f заносит значение в указатель, он означает следующее:

адрес переменной f занести в указатель p_d (указатель установлен на переменную f).

Установка указателя на объект – это обязательный этап работы с любым указателем. Будьте внимательны, неустановленный указатель главный источник неприятностей.

После того, как указатель установлен, можно обращаться к объекту, на который он указывает, для этой цели служит специальная операция –разыменование «*».

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

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

Пример:

int x, *ptr;

ptr = &x; // ptr ссылается на x

*ptr= *ptr +1; // аналог: x = x+1

*ptr = *ptr*5; // x=x*5

Арифметика указателей.

Работая с указателем программист имеет возможность совершать действия двух различных типов :

  • обращаться к объекту (к данным), на который ссылается указатель, используя операцию * (обращение по адресу указателя);

  • изменять адрес, записанный в указателе, таким образом, меняя объект на который направлен указатель.

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

Присваивание.

Операцию присваивания можно применять :

  • при занесении адреса в указатель;

  • при занесении нулевого значения в указатель;

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

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

Пример:

int *p,*k,*z,date=2007;

p=&date; // установка указателя на переменную date

k=p; // значение указателя p заносится в указатель k

z=NULL; // нулевое значение указателя

Инкремент и декремент.

Операции ++ и – применённые к указателю перемещают ссылку на следующий (или предыдущий) объект, позволяя последовательно перебирать элементы массива в прямом или обратном направлении.

Пример:

float M[10], *k;

char Q[60],*s;

s=&Q[0]; // установить указатель на Q[0]

k=&M[10]; // установить указатель на M[10]

s++; // переместить указатель на Q[1]

k--; // переместить указатель на M[9]

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

Сложение.

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

Если помнить, что указатели содержат адреса, то подобные ограничения вполне объяснимы, действительно, складывать между собой два разных адреса не имеет смысла, а вот прибавить к адресу некоторое число, означает переместиться вперед, «перешагнув» через несколько объектов.

Пример: Вывести каждый четвертый элемент массива на печать.



for (k=&dig[0] ; k<&M[N-1]; k=k+3)

printf("\t%f",*k); // печать элемента массива
Вычитание.

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

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

Пример:

Вывести каждый четвертый элемент массива на печать в обратном порядке.



for (k=&dig[N-1] ; k>&M[0]; k=k-3)

printf("\t%f",*k); // печать элемента массива

Пример: Вычитая адрес первого элемента из адреса последнего, получаем расстояние между этими объектами, то есть длину массива.



int x[5],*i , *r, j ;

i = &x[0] ;

k = &x[4] ;

j = k-i ; // j=5

Указатели и массивы.

В языке С между указателями и массивами существует тесная связь.

Рассмотрим что происходит, когда объявляется массив, например

int array[25]:

  • выделяется память для двадцати пяти элементов массива типа int

  • создается указатель c именем array на начало выделенной памяти.

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

Пример: Имя массива можно приравнять к указателю, так как оно также является указателем.



int arrey[25], *ptr;

ptr = array; // установка указателя ptr на начало массива



Оператор ptr=array можно заменить на эквивалентный ему: ptr=&array[0].

К массиву можно обратиться двумя способами:

  • с помощью индекса

  • с помощью указателя.


Пример: Сравнение двух способов доступа к элементам массива.

// определение массива mas и указателя q

char mas[100] , *q;

// установка указателя на начало массива

q = mas;

// Доступ к элементам массива

// (следующие записи эквивалентны):

mas[0]; // *q

mas[10]; // *(q+10)

mas[n-1]; // *(q+n-1)

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

Указатели в параметрах функции имеют три важных применения:

  • передачу массива в функцию

  • доступ к «внешней» памяти из тела функции (или блока);

  • возврат из функции более одного значения.

Если функция должна изменять внешнюю память, то параметры следует передавать через указатели.

Пример: Функция изменяет переменную в вызывающей программе.

#include

// ввести число с клавиатуры и записать во внешнюю переменную, адресуемую через указатель p

void f (int *p)

{ printf ("\nx=");

scanf("%d",p);

}

void main()

{ int x=0,*q;

f(&x); // вызов функции f()

printf("x=%d",x);
}

Функция f() имеет один параметр – указатель на тип int, который устанавливается фактическим параметром при вызове функции, то есть при вызове f(&x) происходит установка указателя p= &x, таким образом, функция f() получает доступ к внешней переменной x через указатель p (функция заносит в переменную значение, введенное с клавиатуры).

Подобным образом могут изменяться и внешние массивы. В функцию передаётся указатель на массив, через который и ведётся работа с его элементами.
Примеры программирования.
Подробнее теорию с примерами по использованию указателей смотрите в файле: «Лекция (указатели,ссылки_применение)»

Пример: Создать массив с помощью датчика случайных чисел, распечатать на экране в виде матрицы и найти его сумму.
#include

#include

void main()

{//____________________________________________________

// Область инициализации, здесь объявляются все объекты,

// размещенные в памяти компьютера

const int N=10; // размер массива

const int col=5; // число колонок при выводе массива на экран

// выделить память для различных объектов

double *k; // указатель на тип double

double dig[N], s;// массив dig и переменные s и i

int i ;

//___________________ конец области инициализации__

// Заполнить массив случайными числами

for (k=dig; k
*k=rand()/3.;

// печать исходного массива

for (k=dig,i=0; k
{ printf("%8.2f",*k);

if ((i+1)%col) printf("\t");

else printf("\n");

}

// суммирование массива

for (s=0,k=dig; k
s=s+ *k;

// печать результата

printf ("\n===============\ns=%f\n",s);

}
Рассмотрим цикл, заполняющий массив for (k=dig; k<dig+N; k++) :

  • перед началом цикла указатель устанавливается на начало массива, то есть k=dig,

  • цикл продолжается до тех пор, пока значение указателя меньше адреса последнего элемента массива, то есть k<dig+N,

  • на каждом проходе цикла дается приращение к адресу указателя : k++ и таким образом, указатель перемещается на следующий элемент

В цикле происходит перебор всех элементов массива от первого до последнего, тело цикла состоит их одного оператора *k=rand()/3.

Указатель k ссылается на очередной элемент, то есть содержит адрес этого элемента, а операция *k – обращение к этому элементу. Функция rand() возвращает случайное число целого типа, путем несложного вычисления преобразуем его в вещественное число. Поэтому оператор *k=rand()/3. не что иное, как запись в массив случайного вещественного числа.

В цикле печати, кроме указателя k, адресующего элементы массива, используется счётчик выведенных чисел i, который используется для форматирования. Если i кратно col, то необходимо закончить строку (вывести на печать символ «перевод строки»).

Суммирование массива проводится в отдельном цикле.
Пример: Решим ту же задачу с применением функций. Потребуются три функции: для заполнения массива, для вывода массива на экран и для суммирования массива.
#include

#include

//____________________ область определения функций________

// функция заполнения массива

void initmas (double *p, int n)

{ double *tp; // рабочий указатель(локальная переменная)

for (tp=p; tp < (p+n); tp++)

*tp=rand()/3.;

}

// функция печати массива

void printmas (double *p, int n, int k)

{ int i;

for (i=0; i
{ printf("%8.2f",*p);

if ((i+1)%k) printf("\t");

else printf("\n");

}

}

// функция вычисления суммы

double summas (double *p, int n)

{ double fs; //сумма (локальная переменная)

double *tp; // рабочий указатель(локальная переменная)

for (fs=0, tp=p; tp < (p+n); tp++)

fs=fs+ *tp; // суммирование массива

return fs; // возврат суммы в вызывающую программу

}

//_________ конец области определения функций_______________

void main()

{// область определения объектов( переменных и массива)_______

// Константы :

const int N=10; // размер массива

const int col=5; // число колонок массива на экранt

// Массив dig и переменная s

double dig[N], s;

//_________________________________________________________

// вызовы функций :

initmas(dig,N); // заполнение массива dig

printmas(dig,N,col); // печать в 5 колонок

s = summas(dig,N); // вычисление суммы

printf ("s=%f\n",s);

}

Все необходимые объекты (dig[] и s) определены в вызывающей программе, поэтому память, в которой располагаются эти объекты является внешней, по отношению к трём пользовательским функциям : initmas(), printmas() и summas(). Разберем подробно работу с внешним массивом на примере функции initmas().

Чтобы функция имела доступ к массиву dig(), необходимо передать указатель на массив (адрес начала) в качестве параметра функции (первый параметр) и длину массива (второй параметр). При вызове функции (смотрите оператор initmas(dig, N) в функции main()) происходит инициализация формальных параметров (p и n) функции initmas(), при входе в функцию параметры получают следующие значения : p = dig , n = N, то есть указатель p направлен на начало массива, а n содержит длину массива.

Для текущей работы потребуется ещё один указатель (tp), текущая работа заключается в перемещении указателя на очередной элемент массива.

Рассмотрим заголовок цикла for (tp=p; tp < (p+n); tp++)

  • перед началом цикла текущий указатель устанавливается на начало массива, то есть tp=p

  • цикл выполняется до тех пор, пока текущий указатель меньше адреса последнего элемента массива (выражение (p+n) дает адрес последнего элемента)

  • на каждом проходе цикла текущий указатель перемещается на следующий элемент, то есть tp++

Теперь понятно, почему потребовался дополнительный указатель tp, в самом деле, если на каждом проходе цикла необходимо проверять условие выхода из цикла, то следует где-то хранить адрес начала массива для того, чтобы вычислить адреса конца массива (p+n), а значит перемещать указатель p нельзя. Следует отметить, что когда мы работаем с массивами через указатель, всегда требуется как минимум два указателя – один для хранения «начала отсчета», а другой для текущей работы - это типовой стандартный подход к решению задачи.

Две другие функции printmas() и summas() используют те же принципы работы с указателями.

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

  1. Дайте понятие указателя. Для каких целей он служит?

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

  3. Какие действия выполняют операции * и & ?

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

  5. Что такое адресная арифметика?

  6. Какие ограничения действуют в адресной арифметике на операцию «присваивания»?

  7. Какие ограничения действуют в адресной арифметике на операции «инкремента» и «декремента»?

  8. Какие ограничения действуют в адресной арифметике на «сложение» и «вычитание»?

  9. Что представляет собой массив данных с точки зрения указателей?

  10. Перечислите три направления применения указателей в параметрах функций.

  11. Как функция может обратиться к памяти, находящейся за её пределами? Приведите пример.


Варианты заданий.

Общие требования.

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

  2. Все исходные массивы следует выводить на экран в форматированном виде (в виде «матрицы»)

  3. Обязательно использовать пользовательские функции (функции, написанные вами). Для всех вариантов создать две функции:

  • заполнение исходных массивов случайными числами

  • вывод исходных массивов на экран в форматированном виде

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

  2. На экран выводить поясняющие тексты.



Номер варианта задания

Задание



1,10,19

Даны два массива M и I, найти вхождение массива I в массив M (начало массива I). Массив M задать с помощью датчика случайных чисел, массив I ввести с клавиатуры. Размерности массивов задать в программе с помощью именованных констант.


2,11,20

Дан массив M, размерность задать в программе с помощью именованной константы. Найти положение максимума и минимума (вернуть 2 значения).


3,12,21

Дан массив M, размерность задать в программе с помощью именованной константы. Указать элемент-границу (ввести номер с клавиатуры). Инвертировать массив справа от границы, слева найти положение минимума и сам минимум.


4,13,22

Дан массив M, размерность задать в программе с помощью именованной константы. Найти n–ый максимальный по величине элемент, n ввести с клавиатуры. Если такого элемента нет – выдать сообщение.


5,14,23

Дан массив M, размерность задать в программе с помощью именованной константы. Указать элемент-границу (ввести номер с клавиатуры). Переставить местами правую и левую часть массива. Найти положение максимума и сам максимум.





6,15,24

Решить задачу линейной фильтрации сигнала, заданного массивом X, с «окном» шириной k.

Дан массив X, размерность задать в программе с помощью именованной константы. Сформировать массив Y, где

k ввести с клавиатуры.



7,16,25

Решить задачу медианной фильтрации сигнала, заданного массивом X.

Дан массив X, размерность задать в программе с помощью именованной константы. Сформировать массив Y, где



midl – это значение, которое находится между max и min


8,17,26

Дан массив M, размерность задать в программе с помощью именованной константы. Указать элемент-границу (ввести номер с клавиатуры), слева найти положение максимума и сам максимум, а справа положение минимума и сам минимум.


9,18,27

Дан массив M, размерность задать в программе с помощью именованной константы. Указать элемент-границу (ввести номер с клавиатуры). Инвертировать массив слева от границы, справа найти положение максимума и сам максимум.


написать администратору сайта