Тема 3
Разработка сложных программных продуктов
Лекция 3.4.
Указатели .
Литература
Основная:
Подбельский В.В., Фомин С.С. Программирование на языке Си: Учеб. пособие. – М.: Финансы и статистика, 2004.
Дополнительная:
С/ C ++ Структурное программирование. Практикум. Программирование на языке высокого уровня. Питер, 2004.
Юркин А. Задачник по программированию. Гриф МО. Учебное пособие, Питер, 2002.
Шмидт Г. Самоучитель Си++. СПб.: БХВ – Петербург, 2004.
Вопросы:
1. Адреса и указатели.
2. Операции над указателями.
3. Применение указателей.
1. Адреса и указатели
Указатель – это переменная, значением которой является адрес объекта конкретного типа.
Если в некоторый момент времени адрес объекта неизвестен или переменная – указатель еще не получила значения, то такой переменной придается значение специальной константы NULL , не равное никакому адресу («нулевой» адрес).
Указатели, как и любые переменные программы, должны быть объявлены. В объявление указателя входит указание типа объекта, на который ссылается указатель, признак (символ * ) того, что переменная является указателем, имя указателя.
Примеры объявления указателей:
int * f ; /*указатель на объект целого типа*/
float * a ,* b ; /*указатели на объекты вещественного типа*/
1.Обычный указатель (кроме указателя типа void ) может ссылаться на объекты только того типа, который указан при объявлении.
2. После объявления значение указателя не определено (соответствует NULL ) .
Получение адресной информации осуществляется с помощью унарной операции & . Например, адрес переменной " А " формируется на основе выражения
&А
Это выражение в ходе выполнения программы позволяет определить адрес участка памяти, выделенного переменной А .
2. Операции над указателями
Применительно к указателям в языке Си допустимы операции:
1. Присваивание;
2. Разыменование;
3. Унарные операции сложения и вычитания;
4. Аддитивные операции;
5. Операции сравнения;
6. Получение адреса самого указателя.
Операция присваивания
Предполагает, что справа от знака присваивания стоит:
- Другой указатель, который имеет конкретное значение;
- Константа NULL ;
- Адрес объекта того же типа, что и указатель слева.
Примеры записи операции присваивания:
с= d ;
Предполагается, что переменные с и d являются указателями одного типа. После этой операции оба указателя обеспечивают доступ к одному участку памяти.
c = NULL ;
c=&a; // Указателю присваивается значение адреса
Если указателю одного типа требуется присвоить значение указателя другого типа, то применяют приведение типов:
int * k ;
float * z ;
k =( int ) z ; // преобразование перед присваиванием
Операция разыменования (обращения по адресу, раскрытия ссылки)
- Позволяет обратиться к соответствующему объекту (переменной). Эта операция является унарной.
- Операция разыменования обозначается символом звездочки . Выражение * f соответствует объекту, на который указывает указатель " f ".
- Выражение * f имеет все "права" переменной, например оператор * f =0 означает занесение нуля в тот участок памяти, адрес которого хранится в переменной – указателе " f ".
- Разыменовывание указателя, соответствующего NULL , недопустимо.
Две эквивалентные группы операторов
int b ;
float f , a ;
f = a + b ;
и
int b;
float f, a, *c;
с=& f ; // указателю с присваивается адрес переменной f
* c = a + b ; // значение суммы заносится в участок
// памяти по адресу, равному значению указателя с
Унарные операции сложения ++ и вычитания – –
Позволяют увеличить или уменьшить значение указателя на величину, равную длине участка памяти, занимаемого соответствующим типом данных. Тем самым обеспечивается переход к началу участка памяти для соседней переменной.
В частности, переменные типа char занимают в памяти участок длиной в 1 байт, переменные типа int – два байта, переменные типа long int и float – 4 байта, переменные double – 8 байт, long double – 10 байт.
Аддитивные операции
В полном объеме эти операции не разрешены. Переменные типа указатель нельзя суммировать , но к переменной типа указатель можно добавлять целую величину k . При этом указатель увеличивается на величину k * r , где r – длина участка в байтах, занимаемого соответствующей переменной. Так при добавлении к указателю переменной типа float величины, равной трем, указатель увеличится на значение 3*4 = 12 байт.
Аналогично изменяется значение указателя при вычитании из его значения целой величины .
Операция вычитания применима к указателям одного типа. Из значения одного указателя можно вычитать значение другого указателя. Результатом операции является целая величина со знаком. Значение результата формируется в "масштабе" соответствующего типа данных. Например, при вычитании указателей двух соседних величин типа int результат будет равнее единице, а не двум, хотя переменные этого типа занимают два байта.
Операции сравнения (, =, ==, != )
Применимы только при сопоставлении указателей одного типа или при сравнении указателя с константой NULL .
Операция получения адреса
Позволяет получить адрес указателя. Указатель является переменной, поэтому и к нему применима операция получения адреса. Например, следующий фрагмент программы позволит отобразить значение адреса указателя
float * a ;
printf ("\ n адрес указателя % p ", & a );
Отображение указателей и адресов производится с использование спецификации р – представление шестнадцатеричных чисел.
Отображение результатов вычитания указателей осуществляется с использованием спецификации d для целых чисел.
3. Применение указателей
Применение указателей при работе с массивами
В языке Си при традиционном объявлении массива его имя становится указателем на начало области памяти, выделенной под массив, т.е. имя массива без индексов является указателем на первый элемент, т.е. элемент с нулевым индексом этого массива.
В частности:
float r[10], *pn1;
pn1=r;
pn 1=& r [0];
В этом фрагменте программы два последних оператора эквиваленты.
Элементы массива в памяти располагаются последовательно друг за другом, поэтому, изменяя указатель на величину, равную длине элемента массива, можно переходить к соседним элементам.
Имя массива интерпретируется как указатель – константа, следовательно, к нему не применимы операции изменения значений, оно не может стоять слева от знака присваивания.
Фрагмент программы для подсчета среднего значения элементов массива с применением указателей
float r[10],s, *pn1;
for (pn1=&r[0], s=0; pn1
s /=10;
В приведенном примере тело цикла отсутствует (за оператором for стоит символ ";"), все действия по подсчету суммы записаны в заголовке цикла.
Выражения:
pn 1= &r[0]
обеспечивает установку указателя на первый (т.е. нулевой) элемент массива;
s +=* pn 1
суммирование элементов массива;
pn 1++
изменение указателя для перехода к следующему элементу массива (фактическое приращение указателя происходит не на единицу, а на 4, так как каждый элемент в памяти занимает 4 байта).
Операция индексирования r[ i ] эквивалентна *( r + i ) , где r – имя массива, i – выражение целого типа. Для многомерных массивов правило остается такое же. Например, z 22[ n ] [ m ] [ k ] эквивалентно *( z 22[ n ] [ m ] + k ) и далее *(*(*( z 22+ n )+ m )+ k ) .
Применение указателей в параметрах функции
Схема вызова функции с помощью передачи параметров по значению не позволяет непосредственно возвращать из функции несколько результатов.
Существует косвенная возможность изменения значений переменных в вызывающей программе на основе применения указателей:
1. В качестве фактических параметров при обращении к функции задаются адреса объектов вызывающей программы;
2. В функции с помощью разыменования указателей обеспечивается доступ к соответствующему объекту вызывающей программы, в том числе, это позволяет изменять значение объекта программы.
Рассмотрим программу с функцией, возвращающей:
среднее значение
moj =( x 1 + x 2 + …+ x n )/ n ;
оценку дисперсии:
disp =( x 1 2 + x 2 2 + … + x n 2 )/ n – moj 2 ;
стандартизованное значение элементов массива
x i =( x i – moj )/ disp 0,5 .
#include
#include
#include
void mdis(double *a, int n, double *m, double *b)
{
int j;
double *aa, sig1;
for (*m=0, *b=0,j=0; j
*m=*m/n;
*b=*b/n;
*b=*b-pow(*m,2.0);
sig1=sqrt(*b);
for (aa=a; aa
*aa=(*aa-*m)/sig1, aa++);
}
void main()
{
int i, n; double b[10], moj, disp,r;
n=10;
for (i=0; i
{
r=rand()/(pow(2.0,15.0)-1);
b[i]=-log(r);
printf("%7.3lf", b[i]);
}
mdis(&b[0], n, &moj, &disp);
printf("\n");
for (i=0; i
printf("\nmo= %10.5lf sko= %10.5lf\n", moj, disp);
}
Для возведения величины x в степень y используется стандартная функция pow ( x , y ) , здесь хотя бы одно число следует записать как вещественное.
Для вычисления корня квадратного – функция sqrt ( x ) .
Применение математических функций требует подключения библиотеки с заголовочным файлом math . h .
Для получения случайного числа - функция rand() , которая требует подключения заголовочного файла stdlib.h .
Расчет среднего значения, оценки дисперсии и стандартизованных значений элементов массива оформлен в виде пользовательской функции mdis . Эта функция описана до начала основной программы, поэтому нет необходимости объявлять ее прототип в основной программе.
Пользовательская функция в качестве формальных параметров содержит:
- Указатель на массив величин типа double ;
- Размер массива int n ;
- Указатели на переменные double *m, double *b .
Разыменование указателей внутри функции обеспечивает занесение результатов в соответствующие области памяти, объявленные в основной программе для фактических параметров moj и disp .
Для пересчета значений элементов массива в функции осуществляется установка указателя на первый элемент массива, переданного в качестве формального параметра. Перебор элементов массива производится с применением указателя, что обеспечивает изменение значений элементов массива, объявленного в основной программе.
Функция возвращает "пустое" значение – тип функции void , поэтому обращение к функции записано в виде выражения – оператора. Оператор return не указан, компилятор добавит его самостоятельно.
Фактические параметры при обращении к функции:
- адрес первого элемента массива;
- количество элементов массива;
- адреса переменных для среднего значения &moj и оценки среднеквадратического отклонения &disp .
Применение указателей на функции
Необходимость применения указателей на функции связана с передачей параметра – имени функции в другую функцию или с возвратом значения – имени функции.
Подобно имени массива наиболее часто в качестве указателя на функцию применяется идентификатор функции. Этот идентификатор задается при определении или описании прототипа функции, т.е. имя и указатель на функцию являются синонимами, но в обычном контексте термин указатель не применяется, принято говорить об имени функции. В таком виде указатель на функцию является константой, поэтому изменять его нельзя.
Для применения указателя на функцию вводят специальную переменную. Объявление переменной – указателя на функцию производится отдельно от определения и от прототипа функции с помощью конструкции вида:
тип (* имя указателя )( спецификация параметров );
где тип – определяет тип возвращаемого функцией значения;
имя указателя – выбранный программистом идентификатор;
спецификация параметров – определяет состав и типы параметров функции.
Пример объявления переменной – указателя на функцию
float (* uk 1)( float , int );
Здесь указатель имеет имя uk 1 (обязательно записывается в круглых скобках), функция имеет два параметра (типа float и int ), возвращаемое функцией значение имеет тип float .
В отличие от имени функции, указатель на функцию является переменной, следовательно, этой переменной можно присваивать значения адресов других функций (значения других указателей). Но при этом тип присваиваемого указателя обязательно должен совпадать с типом указателя, стоящего в левой части оператора присваивания.
Варианты обращения к функции:
Обычным образом:
имя_функции ( список параметров )
Обращением по указателю на функцию
указатель_на_функцию ( список параметров )
Обращением по указателю с его разыменованием, при этом указатель обязательно записывается в круглых скобках
(* указатель )( список параметров )
Иллюстрация способов обращения к функции
…
void f1(float x)
{
printf("\nf1=%6.2f",10*sin(x));
}
void f2(float x)
{
printf ("\nf2=7.4",2*cos(x));
}
void main()
{
void (*u1)(float);/ / *u1 указатель на функцию
…
f 1( y ); // обычный вызов функции
u 1= f 1; // настройка указателя на функцию f 1
u 1( y ); // вызов функции f1 по указателю
//вызов функции f1 с разыменованием указателя
(* u 1)( y );
u 1= f 2; // настройка указателя на функцию f 2
u 1( y ); // вызов функции f2 по указателю
//вызов функции f2 с разыменованием указателя (* u 1)( y );
}
Задание к практическому занятию.
Составить программу, состоящую из основной функции и функции пользователя. Основная функция обеспечивает:
- формирование двумерного массива чисел из n -х строк и m столбцов, значения n m х ij = -(1/3) ln ( r ). Числа r в диапазоне от 0 до 1 формируются с помощью датчика случайных чисел, реализуемого функцией int rand (void) из библиотеки stdlib.h , которая формирует случайные числа в диапазоне от 0 до 2 15 – 1;
- вывод результатов формирования массива;
- обращение к функции пользователя ;
- отображение преобразованного массива и значения минимального элемента в массиве.
Функция пользователя обеспечивает:
- поиск минимального значения в массиве и передачу его значения в основную функцию;
- преобразование элементов массива, путем вычитания из значений элементов минимальной величины.

Основы алгоритмизации и программирования. Указатели (292 KB)

