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

Давыдов В.Г. - Программирование и основы алгоритмизации - 2003

.pdf
Скачиваний:
839
Добавлен:
13.08.2013
Размер:
9.55 Mб
Скачать

//

указателем

pt,

т.е.

адрес

'\0'

ps в

"хвост"

//

Посимвольно

копируем

строку с

указателем

//

строки с

указателем

pt, пока

не будет

скопирован

//нуль -символ

while ( (

±nt ) ( *pt = *ps ) )

{

ps++;

pt++/

}

 

return;

 

/^

Файл

P24.CPP

 

функцию

strcat^

Главная

функция, использующая

*/

 

 

 

 

^Include

<stdio.h>

// Для

функций

ввода-вывода

//Прототип

void strcat

( char *, char

* ) ;

 

±Tib main (

void. )

// Возвращает 0 при

успехе

{

//Строка-приемник

char

 

t[ 24 ] = "Персональная

";

strcat

( t,

"ЭВМ IBM PC" ) ;

 

printf(

"\n

Конкатенация

строк:

%s", t ) ;

return

0;

 

 

 

}

В связи с рассмотренным примером напомним, что имя масси­ ва эквивалентно указателю на первый байт этого массива, а значе­ нием строки является указатель на ее первый символ. Эти указатели, как исходные данные, передаются функции strcat по значению, т.е. функции передаются их копии, и поэтому изменение копий указате­ лей в strcat не повлияет на значения указателей в главной функции.

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

130

Рассмотрим еще несколько примеров, целью которых является показать необходимость использования в списке параметров функ­ ций языка Си адресов объектов для получения из функций результа­ тов (сказанное относится только к языку Си).

/*

 

Файл

Р25.СРР

 

 

 

 

 

проект

 

с

двумя

 

функциями.

Пример

 

Однофайловый

программный

 

 

иллюстрирует

возможность

 

появления

 

ошибки

из-за отсутствия в

Си передачи

в

функцию

параметра

по

адресу

 

(ссылке)

 

 

V

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

^include

<stdio.h>

 

 

 

 

//

Для

функций

ввода-вывода

 

void

swap

(

int,

int

)

;

 

//

Прототип

 

 

 

 

 

int

main

(

void

 

)

 

 

 

 

//

Возвраш,ает

0 при

успехе

 

{

int

 

 

 

к

=

10,

у

 

=

20;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

swap(

 

X,

у

) ;

=

%d,

у

= %d",

Xr

у

) ;

 

 

 

 

 

printf(

 

"\п

X

 

 

 

 

 

// Будет

напечатано

 

х

=

10,

у

=

20

 

 

 

 

}

геЬлт

 

О;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Перестановка

значений

 

не

будет

выполнена,

 

так

как

 

//

параметры

функции

передаются

 

по

значению

 

 

void

swap (

 

 

X,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

int

 

 

 

)

 

 

 

//

X <-->

 

у

 

 

 

 

 

 

int

 

 

 

у

 

 

 

 

 

 

 

 

 

{

int

 

 

 

temp

= x;

 

x

= y;

у

=

temp/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

jcetvLm;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Обращаем

внимание, что будут напечатаны значения л: = 10 и

у = 20. Почему? Потому, что в функцию swap

"х" и ' У

передаются

по значению, т.е. копии "х" и " у . Следовательно, изменятся только

копии, а после выхода из swap

они будут потеряны.

 

 

Чтобы избежать этой ошибки в языке Си (это не относится к

языку C++), нужно в функцию swap

передавать адреса "х" и "jv" или,

что то же самое, указатели на эти объекты:

 

 

 

Файл

Р26,СРР

 

проект с двумя функциями.

Пример

Однофайловый

программный

иллюстрирует

использование

в

функции

Си

параметра-адреса

объекта

для получения

из нее

измененного

значения

оаъекта

131

^include

<stdio.h>

// Для функций

ввода-вывода

//Прототип

void

swap

( int

*, int

* )

;

 

 

 

 

 

 

 

 

int

main

(

void.

)

 

 

//

Возвращает

0

при

успехе

 

{

int

 

 

 

X = 10, у

=

20;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

swap

(

&x,

&y ) ;

 

//

В функцию

передаются

указатели

 

print

 

f ( "\n X =

%d, у = %d"r

^r У

)f

 

 

 

 

 

//

Теперь

будет

напечатано

x

= 20, у

= 10

 

 

 

}

re

turn

Of

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

//

Перестановка

значений

будет

выполнена,

так как

в

качестве

//

параметров

в

функцию

передаются

адреса

объектов

 

void

swap (

*рх,

 

 

 

 

 

 

 

 

 

 

 

 

int

 

 

 

)

//

Указатели

 

 

 

 

 

 

int

 

 

 

*ру

 

 

 

 

 

{

int temp = *рх; *рх = *ру; *ру = temp;

return;

}

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

Файл

Р2 7.СРР

программный

проект с

двумя

функциями.

Пример

Однофайловый

иллюстрирует

использование

в

функции

языка

C++

параметров,

передаваемых

по

ссылке, для

получения

из нее

измененных

зна­

чений

объектов

 

 

 

 

 

 

 

_V

 

 

 

 

 

 

 

 

 

^include

<stdio.h>

//

Для

функций

ввода-вывода

 

 

//Прототип

void

swap

( int

&, int

&

) ;

 

int

main

(

void

)

 

//

Возвращает 0 при

успехе

{

int

 

 

X = 10,

у

=

20;

 

 

 

 

 

 

swap

(

X, у

) ;

 

 

 

 

132

printf(

"\n X = %d, у

=

%d"r K,

у ) ;

 

//

Будет

напечатано

x

= 20,

у

= 10

 

retujcn

0;

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

// Перестановка

значений

 

будет

выполнена,

так как параметры в

//

функцию

передаются

по

ссылке

 

 

 

void

swap (

 

&х,

 

 

 

 

 

 

 

±nt

 

 

 

 

 

 

 

 

±nt

 

&у )

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

int

 

temp = x;

 

X = у;

у

=

temp;

 

return;

6.6. Указатель как значение, возвращаемое функцией

Иногда удобно получать от функции в качестве возвращаемого значения указатель, в частности при операциях над строками. В ка­ честве примера рассмотрим функцию копирования, возвращающую указатель на конец строки-приемника. Последовательный вызов этой функции обеспечит конкатенацию (сцепление) строк:

Файл

Р28.СРР

программный

проект с двумя

функциями.

Пример

Одно файловый

иллюстрирует сцепление

нескольких

строк с

помощью функции ко­

пирования

строки,

использующей

указатели

 

 

V

 

 

 

 

 

 

 

^include

<stdio.h>

 

//

Для

функций

ввода-вывода

 

//Прототип

char

* strcpy(

char

*,

char

* ) ;

 

 

 

±nt

main ( void.

)

 

//

Возвращает

0

при

успехе

{

char

t[ 24

],

//

Строка-приемник

 

 

строки-

 

 

*pt;

 

//

Указатель

на

конец

//приемника

pt

= strcpy

( t,

"Персональная"

) ;

//pt

 

= t

+ 16

//

pt = t

+ 12

 

" ЭВМ" ) ;

 

pt

= strcpy

( pt,

 

//

pt

= t

+ 23

" IBM PC" ) ;

 

pt

= strcpy

( pt,

%s \n", t ) ;

print

f ( "\n Конкатенация

строк:

133

*pt^ *ps )
// Указатель на // Указатель на
строку-приемник строку-источник

return

О;

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

/ • "

 

строку-источник

с

начальным

адресом ps в

строку-

Копирует

приемник

с

начальным адресом

pt

и возвращает

указатель

на по­

следний

символ

строки-приемника

 

 

 

*/

St г еру

(

 

 

 

 

 

char *

 

 

 

 

 

char char

{

while ( ( ±nt ) ( *pt = *ps ) )

(

pt++/ ps++;

}

return pt;

6.7. Массивы указателей

Одним из распространенных производных типов данных в Си является массив указателей. Приведем несколько иллюстрирующих примеров.

/ /

Пример

1: пять указателей

на

целые

значения

-

массив

//

 

указателей

*piarray[

5 ] ;

 

 

 

 

 

 

 

 

 

Int

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Данное определение трактуется следующим образом:

[]

-

наивысший

приоритет

 

 

 

 

 

-

массив;

 

 

*

-

приоритет

ниже

[]

 

 

 

 

 

-

указателей;

 

int - связывается

в последнюю

очередь

 

 

- на

целые

 

значения

//

Пример

2:

указатель

на

массив

из

пяти

целых

значений

int

 

 

(

*p±array

) [ 5

];

 

 

 

 

 

 

 

 

 

 

 

Данное определение трактуется следующим образом:

О

-

наивысший

приоритет,

как

у

[],

 

но

выполняется

слева

[]

 

направо

 

 

 

 

 

 

 

 

-

указателъ;

-

наивысший

приоритет

как

у

() ,

но выполняется

 

слева

int

 

направо

 

 

 

 

 

 

 

 

-

на

массив;

-

связывается

с идентификатором

 

в

последнюю

очередь

 

 

 

 

 

 

 

 

 

 

 

 

 

 

- целых

значений

//

Пример

3:

инициализация

массмва

указателей

на

строки

//символов

char

*stud_info[

] =

{

 

 

134

"ФТК" , "Иванов И. И. "1081/3", "01-09-97"

}.

В памяти эти данные будут располагаться так, как это указано на рис. 42 (значения адресов байтов - условные). Нетрудно заметить, что при таком определении каждая строка будет занимать мини­ мально необходимую память.

char *studJnfo[ ]

 

Сегмент данных

 

 

Адрес

 

Адрес

Содержимое

 

studJnfo[ 0 ]

15000 1-

15000

'Ф'

 

studJnfo[ 1 ]

 

V

15001

•т

 

15004 L

'К'

 

studJnfo[ 2 ]

15016

 

15002

 

studJnfo[ 3 ]

 

 

15003

ЛО'

 

 

 

 

15004

'И'

1

 

 

 

15005

'в'

 

 

 

 

15006

'а'

 

 

 

 

15007

'н'

 

 

 

 

15008

'о'

 

 

 

 

15009

'в'

 

 

 

 

15010

'И'

 

 

 

 

15011

 

 

 

 

15012

'И'

 

 

 

 

15013

 

 

 

 

15014

'\0

 

 

 

 

15015

 

1—•

1

Рис. 42. Размещение в памяти

Как организовать доступ к массиву строк?

/ /

Печать

сформированного

в

памяти

массива

строк

^include

"

<stdio,h>

%s

\п",

stud__lnfo[

О ]

) ;

printf(

Факультет:

printf(

"

ФИО: %s \п",

stud_lnfo[

1

]

) ;

 

//

Второй

символ

фамилии

заменим

на

 

'р'

 

*(

stud_info[l

] + 1

) =

 

'р';

 

 

 

 

Аргументы командной строки. По-видимому, наиболее рас­ пространенным приложением массива указателей является передача данных программе через командную строку операционной системы.

135

предположим, что имеется программа prog.exe, которая должна чи­

тать исходные данные из файла prog.dat

и писать результаты своей

работы в файл prog.res.

 

 

Тогда программу можно запустить из среды MS DOS с помо­

щью следующей командной строки:

 

prog.ехе

prog,dat

prog.res

[Enter]

Компилятор языка разбивает командную строку на слова, разделенные пробелами (в данном примере prog.exe, prog.dat и prog.res). Затем передает функции main в качестве аргументов число слов в командной строке (в примере 3) и массив указателей на каж­ дое слово (в первом элементе массива указателей находится адрес

"prog.exe'\ во втором — ''prog.daf\ в третьем - "prog.res'\ а значение четвертого - NULL).

Файл

Р29.СРР

программный

 

проект

с

одной

 

 

функцией.

 

Пример

Однофайловый

 

 

 

 

иллюстрирует

передачу

в

программу

аргументов

 

командной

стро­

ки.

Программа печатает

количество

 

слов

в

командной

строке и

сами

слова

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

*/

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

#include

<stdio.h>

 

 

 

//

Для функций

ввода-вывода

 

 

Izit

main

(

 

 

 

 

//

Возвращает

О при

 

успехе

 

 

 

±nt

 

argCr

 

//

ARGument

Counter:

 

число

слов

в

 

сЬа.г

 

 

 

 

 

//

 

командной

строке

 

 

 

 

*argv[

]

) /

/ ARGument

Value:

 

массив

указателей

 

 

 

 

 

 

 

//

 

на

аргументы

командной

строки

{

printf

(

"\п

Число

слов

в

командной

строке:

%d \п",

 

 

 

 

print

f (

а где

) ;

 

 

 

 

аргументы:

\п"

 

)

;

 

 

 

"\п

Передаваемые

 

 

 

 

 

unsigned

int

=

0;

 

 

 

 

 

 

 

 

 

 

 

 

 

while(

 

i

)

 

 

 

 

 

 

 

 

 

 

 

 

 

argv[

i

]

 

 

 

 

 

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

printf(

"%s

\n",

argvf

i + +

] )

;

 

 

 

 

 

}

//Другой вариант

print f

( "\n

Переда в a емые аргументы:

\n" ) ;

while(

*argv

)

 

{

 

 

 

printf(

"%s \n", *argv++ ) ;

 

}

 

 

 

jretixm

0;

 

 

136

6.8. Замена типов указателей

Основное применение замены типов указателей связано с устранением предупреждений в выражениях присваивания. Рассмотрим следующий иллюстрирующий пример.

 

Файл

РЗО.СРР

программный

проект

с

одной функцией.

Пример

 

Однофайловый

иллюстрирует

замену

типов

указателей

и

производит

побайтовое

копирование

одной

структуры

в другую

 

 

 

V

 

 

 

 

 

 

 

 

 

 

struct

STUDENT_INFO

 

//

Сведения

о

студенте

 

{

Факультет

 

 

 

 

 

 

 

 

//

fak_name[

30 ];

 

 

 

 

char

 

 

 

 

 

char

 

fio[

20

];//

ФИО

 

 

 

//Группа

char group__name[ 7 ];

char

 

 

date[

9

];//

 

Дата

поступления

в

университет

float

 

 

stip/

 

//

 

Размер

 

стипендии

 

источник

} s2

=

 

 

 

 

 

/ /

Определение

 

объекта:

 

{

 

"ФТК:",

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

И, И,

 

 

 

 

 

 

 

 

 

 

 

 

 

"Иванов

 

 

 

 

 

 

 

 

 

 

 

 

 

"1081/4",

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

"01-09-98",

 

 

 

 

 

 

 

 

 

 

 

 

 

 

100000.Of

 

 

 

 

 

 

 

 

 

 

 

 

} .

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

int main

( void

 

)

 

 

//

Возвращает

О при

успехе

 

{

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

STUDENT_INFO

 

 

//

 

Приемник

 

 

 

 

 

//

Адреса

 

si;

 

 

и

 

 

 

 

 

структур

si

s2

)&sl,

 

 

 

 

 

char

 

 

*psl

= (

char

'*

 

 

 

 

 

 

 

 

 

*ps2=

(char

*) &s2;

 

 

 

 

 

 

//

Побайтовое

копирование

 

структуры

s2 в

si

 

 

fori

unsigned

1

= 0; 1

<

slzeof

(

STUDENT_INFO

) ; i + + )

{

 

*psl

=

*ps2/

psl++;

 

ps2++;

 

 

 

 

 

 

 

 

 

 

 

 

 

}

Вообще-то

 

следует

иметь

в

виду,

что возможно

выражение

//

 

//

 

присваивания

над

структурами:

 

si = s2;

 

 

return

0;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

137

 

Операция

замены

типа

{char

*)

указывает

компилятору,

что

перед применением надо интерпретировать адрес структуры &s\

как

указатель на символ.

 

 

 

 

 

 

 

 

 

 

 

 

Рассмотрим еще один пример, демонстрирующий мощь

и

изя­

щество указателей.

 

 

 

 

 

 

 

 

 

 

 

/*

Р31.СРР

 

 

 

 

 

 

 

 

 

 

 

 

Файл

 

 

проект

с

одной

функцией.

Пример

 

Однофаиловыи

программный

иллюстрирует

"хитросплетение

 

ссылок".

Что

напечатает

данная

программа?

 

 

 

 

 

 

 

 

 

 

 

 

 

^include

<std±o.h>

 

//

Для

функций

 

ввода-вывода

 

 

chai:

 

*с[ ]

=

//

Массив

указателей

на

строки

 

{

"ENTER",

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

"МЕР",

 

 

 

 

 

 

 

 

 

 

 

 

 

"POINT",

 

 

 

 

 

 

 

 

 

 

 

 

 

"FIRST"

 

 

 

 

 

 

 

 

 

 

 

 

}

;

указателей

на

элементы

массива

указателей

на

строки

//

Массив

char

 

**ср[

] = {

с+3,

с+2, с+1,

с

} ;

 

 

 

char

 

***срр

=

ср;

 

 

 

 

 

 

 

 

 

//См. рис. 43 а

±nt main (

void

)

//

Возвращает

 

О при

успехе

{

 

"\n%s"r

**++срр

) ;

 

 

 

 

 

printf(

 

 

рис.

43

б

 

printf

(

"%s

",

//

См.

 

*--*-i- + cpp+3

) ;

43

в

 

printf

(

"%s",

//

См.

рис.

 

*срр[-2]+3

)

;

рис.

43

г

 

printf

(

"%s\n",

//

См.

 

срр[-1]

[-!]+!

) ;

43

д

 

 

 

 

 

//

См.

рис.

 

return

 

О;

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

 

 

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

"[ ]" - выполняются слева направо; "++", "—", "*" - выполняются справа налево; "+" - выполняются слева-направо.

138

а) б;

У:

срр [ / )

срр I

Г Р |

ПГ"

i

4 и

 

ГТ"

О

 

[т1FR"

 

 

"Т1

Гз"

\0 N

 

ГТ]

 

 

 

\0

\0

\0

*( *( ++СРР ) )

1

2

( *( - ( *( ++СРР ) ) ) ) + 3

1

2

 

 

 

Операции одинакового

 

 

 

 

 

приоритера, выполняются

 

4

 

 

 

справа налево.

 

 

 

 

 

 

 

 

 

 

 

5-

 

 

 

 

Будет напечатано:

 

 

 

 

 

 

 

 

 

Операции одинакового

 

 

POINT

 

 

 

 

 

 

 

 

 

 

приоритета, выпол­

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

няются справа налево.

 

 

 

 

 

 

 

 

 

 

Будет напечатано:

 

 

 

 

 

 

 

 

 

POINTER

 

 

 

Рис. 43. "Хитросплетение ссылок"

 

Рассмотрим еще один пример.

 

 

 

 

 

 

Файл

Р32.СРР

 

 

проект

с

одной

функцией.

Пример

Однофайловый

программный

иллюстрирует работу

с

массивом

с использованием

указателей.

Что напечатает

данная

 

программа?

 

 

 

 

 

V

 

 

 

 

 

 

 

 

 

 

1

^include

<stdlo.h>

 

//

Для

функций

 

ввода-вывода

 

int

 

а[ 3

] [

3 ]

== {

{ 1,

2,

3

} ,

 

 

 

 

 

 

 

(

4,

5,

6

Ь

 

 

 

 

 

 

 

{

7 ,

8 ,

9 }

}

,

 

139