Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

книги / Язык Си

..pdf
Скачиваний:
6
Добавлен:
20.11.2023
Размер:
7.64 Mб
Скачать

ДОМАШНЕЕ ЗАДАНИЕ

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. Указатель на функцию

Объявление указателя на функцию имеет вид

тип (*имя указателя) (список параметров);