книги / Язык Си
..pdfДОМАШНЕЕ ЗАДАНИЕ
1. Написать функцию, которая находит самые удаленные друг от друга точки. Координаты п точек хранятся в одномерных мас сивах X и у.
2. Написать функцию, которая по заданному числу членов ря да и и действительному аргументу х вычисляет результат выра жения
3. Написать функцию приближенного вычисления площад фигуры, образованной кривой у = 0.3(лг—I)2 + 4, осью абсцисс и двумя прямыми х = 1 и х = 3.
4. Написать две функции. Первая вычисляет значение ряда
QX COSJC ( sin2x cos 2* ' sin3;c cos3* ]
--+ ----
вторая - формирует двумерный массив, первая строка которого - аргумент ряда Р, меняющийся на отрезке [2, 4] с шагом 0.5, вторая строка массива - значения ряда Р в этих точках.
5. Написать рекурсивную функцию, которая вычисляет про изведение двух целых чисел без использования операции умноже ния.
ЛЕКЦИЯ 10. УКАЗАТЕЛИ И ССЫЛКИ
Указатель - это переменная, значением которой является ад рес другой переменной.
Чаще всего указатели используются:
-для доступа к адресам памяти и манипуляций с ними;
-распределения динамической памяти при выполнении про граммы;
-передачи в функцию массивов и строковых переменных;
-передачи функций в качестве параметров других функций;
-создания динамических структур, таких как связанный спи сок, двоичное дерево и т.п.
10.1. Объявление и инициализация указателей
Указатели подобно любым другим переменным перед исполь зованием должны быть объявлены. Объявление указателя имеет вид
тип *имя указателя;
Тип - один из базовых типов языка Си (см. лекцию 1).
Имя указателя выбирается по тем же правилам, что и любая переменная (см. лекцию 1).
int *а, Ь, *с; |
//а, |
с |
- указатели |
float s,*s2; |
//s2 |
- |
указатель |
Звездочку * можно ставить как перед именем указателя, так и после имени типа. Объявления float * а; иfloat *а; компилятор вос примет одинаково.
Указатель при инициализации может получить следующие значения: 0, NULL или адрес. Указатель с начальным значением 0 или NULL ни на что не указывает. NULL - это символическая кон станта, определенная специально для цели показать, что данный указатель ни на что не указывает.
Пример. Инициализация указателя при объявлении (только в Си++).
int |
х; |
//инициализация нулем |
int |
*а(0); |
|
int |
*b(NULL);//инициализация константой NULL |
|
int |
*с(&х); |
//инициализация адресом переменной х |
printf("%р\п%р\п%р",а,Ь,с);
Результат на экране:
00000000
00000000
0012FF88 //адрес переменной х может быть другим
Обратим внимание, что такой вариант инициализации указа телей допустим только в Си++.
Пример. Инициализация указателя в Си.
int |
х; |
int |
*а=0; |
int |
*b=NULL; |
int |
*с=&х; |
ИЛИ |
х,*а,*b,*с; |
int |
|
а = |
0; |
b = NULL; |
|
с = |
&х; |
Также указатель можно инициализировать другим указателем:
float х;
float *а=&х;
float *b(a); //или float *b=a;
Если использовать неинициализированный указатель, то в нем будет храниться какой-то случайный адрес, по которому рас положен, например, код другой программы. Таким образом, рабо тая с неинициализированным указателем, можно случайно повре дить другую программу.
Неинициализированный указатель трудно обнаружить, по скольку компилятор не выдает предупреждения о подобных ошиб ках.
10.2.Операции с указателями
Суказателями связаны две специальные операции: амперсанд {&) и звездочка (*). Обе эти операции являются унарными, т.е. имеют один операнд, перед которым они ставятся. Операция & соответствует операции адресации и читается как «взять адрес». Операция * называется операцией косвенной адресации или операци ей разыменования указателя и соответствует словам «взять значение, расположенное по указанному адресу». Каждая переменная, объяв ляемая как указатель, должна иметь перед собой знак *.
Операция & применима только к объектам, имеющим имя и размещенным в памяти. Ее нельзя применять к выражениям, кон стантам, битовым полям структур, регистровым переменным.
int а=5; int *b,x;
printf("%p\n",b);//печатает 00000100,
|
//но |
если b=NULL, |
то 00000000 |
|
b=&a; |
//взять адрес |
переменной |
а |
|
printf("%р\п",Ь); |
//печать этого |
адреса |
||
|
|
//дает 0012FF88 |
|
|
х=*Ь; |
//переменной |
х присваивается |
значение |
|
|
//переменной а, на которую указывает Ь, |
|||
printf("%сГ ,х); |
//печатает 5 |
|
Операцию разыменования нельзя применять к указателю типа void, поскольку для него неизвестно, какой размер надо разымено вывать.
int |
*p; |
//так указателю можно присваивать адрес, |
|
p=10; |
|||
|
|
//а не значение |
|
printf("%d",*р); //теперь попытка обратиться |
|||
|
|
//к значению указателя *р |
|
int |
*р; |
//вызовет ошибку |
|
//правильно - обращаемся к значению |
|||
*р=10; |
|||
|
|
//указателя и заменяем его на 10 |
|
printf("%d",*р); //напечатает 10 |
|||
int |
а=10; |
||
int |
*р; |
||
р=&а; |
//правильно - переменная а уже размещена |
||
|
|
//в памяти и ее адрес известен |
printf("%d",*р); //напечатает 10
Как и над другими типами переменных, над указателями можно производить арифметические операции: сложение, вычита ние, инкремент -и- и декремент — .
Операция присваивания = предполагает, что слева от нее по мещено имя указателя, справа - указатель, уже имеющий значе ние, либо константа NULL, определяющая условное нулевое зна чение указателя, либо адрес любого объекта того же типа, что и указатель слева.
Арифметические действия над указателями имеют свои осо бенности:
double *р,х; р=&х;
printf("%p\n",p); //0012FF80
Р++;
printf("%ри,р) ; //0012FF88 getch();
return 0;
После выполнения операции р++ значение указателя р увели чилось не на 1, а на 8. Поскольку указатель содержит адрес пере
менной, а не значение, переменная типа double занимает в памяти 8 байтов, и адрес меняется с 0012FF80 на 0012FF88.
К указателям можно прибавлять и вычитать только целые числа:
double *р,х; |
|
|
|
|
|
|
р=&х; |
|
//0012FF80 |
|
|
|
|
printf ("%p\nff,р) ; |
|
|
|
|
||
р+=3; |
|
//0012FF98 |
3*8=24 |
байта |
|
|
printf ("%р", р) ; |
|
|||||
р+=3.0; |
|
//ОШИБКА |
|
|
|
|
Прибавив 3 к |
указателю (р+=3), мы |
сместили адрес |
на |
|||
24 байта, а 24 в десятичной системе счисления - |
это 18 в шестна |
|||||
дцатеричной. Таким |
образом, адрес изменился |
с |
0012FF80 |
на |
0012FF98.
Также нужно различать, где идет работа с адресом, а где со значением:
int |
х=10; |
//настраиваем указатель на |
адрес |
||
int |
*р=&х; |
||||
|
|
//переменной х |
этому |
адресу |
|
printf("%d\n",*р+3); //берем по |
|||||
|
|
//значение |
(это |
10) |
и |
|
|
//прибавляем к нему |
3 |
printf("%d",* (р+3)); //к адресу переменной х
//добавляем 3*4 = 12 байт и //берем значение по этому адресу
//(какое-то случайное число)
Указатели можно вычитать один из другого, float a,b,c;
float *d=&a,*е=&с;
printf("%р\п",d); //печатает адрес 0012FF88 printf("%р\п",е); //печатает адрес 0012FF80 printf("%р\п",d-e); //печатает 00000002
а=5 ;
с=8 ;
printf("%f",*d-*e);//печатает значение -3.000000
Обратим внимание, что при вычитании указателей d - e полу чили разность между ними в количестве переменных типа float, умещающихся в 8 байтах: 00000002. Так получилось потому, что при объявлении переменных между а и с еще была объявлена пе ременная 6. Поменяем порядок объявления переменных и в ре зультате получим
float a,c,b;
float *d=&a,*е=&с;
printf ("%p\n" ,d) ; //печатает адрес D012FF88 printf("%p\n",e); //печатает адрес 0012FF84 printf("%p\nM,d-e); //печатает 00000001
Другие арифметические операции над указателями запреще ны, например, нельзя складывать два указателя, умножать указа тель на число и т.д.
Указатели можно сравнивать. Применимы все шесть опера
ций:
< |
> |
<= |
>= |
== |
! = |
Сравнение а < Ъозначает, что адрес, находящийся в указателе а, меньше адреса, находящегося в указателе Ь.
Иногда требуется присвоить указателю одного типа значение указателя другого типа. В этом случае используется приведение типов:
char *z;
int *k;
z=(char *)k;
10.3. Связь указателей и массивов
Имя массива - это адрес памяти, начиная с которого распо ложен массив, т.е. адрес первого элемента массива. Таким обра зом, если объявлен массив int *[10], то *, по сути, является указа телем на массив, а точнее, на первый элемент массива.
int |
a [5]; |
|
int |
*p; |
|
p=a; |
//ставим указатель на начало массива |
|
printf("%р\п",р); //печатаем адрес - 0012FF60 |
||
р=&а[0]; //берем |
адрес нулевого элемента массива |
|
printf(M%p",р); |
//печатаем адрес - 0012FF60 |
Как видно, операторы р = а; и р = &а[0]; приводят к одному и тому же результату.
Пример. Получить значение четвертого элемента массива.
int а[5]={16,4,13,2,9};
int *р=а;
р+=3;
printf(n%dn,*р); //печатает 2
Пример. Получить адрес четвертого элемента массива.
int а[5]={16,4,13,2,9};
int *р; |
|
р=&а[3]; |
//печатает адрес 0012FF80 |
printf("%р",р); |
Пример. Удостовериться, |
что элементы массива располо |
|||
жены в памяти друг за другом. |
|
|
|
|
#include<conio.h> |
|
|
|
|
#include<stdio.h> |
|
|
|
|
main() |
|
|
|
|
{//задаем строку |
|
klmnopqr st uvwxyz"; |
||
char |
s [100] =,,abcd efghij |
|||
int |
i,n; |
|
|
|
n=strlen(s);//определяем длину строки |
||||
for(i=0;i<n;i++) |
|
//печатаем |
значение |
|
{printf ("%c: "^ ( s +i)); |
||||
printf(M%p\nM,s+i); |
|
//элемента |
массива |
|
//печатаем адрес элемента |
||||
|
|
//массива |
|
}
getch(); return 0;
}
В результате работы программы можно будет увидеть, что адреса элементов расположены по порядку друг за другом. Таким образом, элементы массива занимают в памяти непрерывную об ласть.
Указатели, как и переменные любого другого типа, могут объединяться в массивы. Объявление массива указателей на десять целых чисел имеет вид
int *х[10];
Каждому из элементов массива можно присвоить адрес, на пример, третьему элементу присвоим адрес целой переменной у:
int *х[10],у=5;
х [2]=&у;
Чтобы найти значение переменной у, можно написать *лг[2].
printf ("%dff,*х [2] );
Указатели удобно использовать при обработке строки:
char *ps="Hello !";
puts(ps); |
//печатает Hello |
и массива строк:
char *color[]={MRedM,"Yellow","Green"};
int i;
f o r (i= 0 ; i< 3 ; i+ +)
puts(color[i]); //печатает в столбик
//Red Yellow Green
Пример. Печать строкового массива через указатель.
#include<stdio.h>
#include<conio.h>
main()
{int i;
char color [3] [10] ={"Red","Yellow","Green"};
char *p;
p=&color[1][0]; |
//ставим указатель на Y |
for(i=0;i<6;i++) |
//печатает Yellow |
printf("%c" ,* (p+i) ); |
|
getch(); |
|
return 0; |
|
}
Также возможна ситуация, когда указатель указывает на ука затель:
int **р;
Теперь, чтобы получить значение переменной, на которую указывает /?, надо в выражении использовать **/?.
Пример. Печать строкового массива через двойной указа
тель.
#include<stdio.h>
#include<conio.h>
main() {int i;
char *color[]={"Red","Yellow","Green"}; char **p;
p=color+l; //устанавливаем указатель на Y
for(i=0;i<2;i++) printf("%c",**(p+i)); //печатает YG
printf("\n");
for(i=0;i<3;i++)
printf("%c",*(*p+i)); //печатает Yel
getch(); return 0;
10.4. Указатель на функцию
Объявление указателя на функцию имеет вид
тип (*имя указателя) (список параметров);