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

osnovyiprogrammirovaniyanac

.pdf
Скачиваний:
24
Добавлен:
12.03.2015
Размер:
1.46 Mб
Скачать

Указатели

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

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

т ип * и м я ;

Здесь тип определяет тип объектов, адреса которых могут храниться в указателе, имя - имя указателя. Указателю может быть выбрано любое имя в соответствии с правилами создания идентификатора. Перед именем ставится звездочка, что и является отличительным признаком указателя: *

int *а, *Ь, *с; char *d;

Объявление char *d говорит о том, что значение, записанное по адресу d, имеет тип char. Чтобы записать адрес некоторой переменной в указатель (или, как говорят, заставить указатель «смотреть» на эту переменную), нужно перед именем переменной

поставить операцию & (взятие адреса), а полученный результат присвоить указателю:

int

х ;

int

*р;

Р =

&х;

Теперь указатель р будет хранить в себе адрес переменной х. Операцию & нельзя применять к константам и выражениям; конструкции вида &(х + 5) или &27 недопустимы.

Как, зная адрес, узнать, какое значение находится по этому адресу? Для этих целей определена операция *. Иногда ее называют проходом по адресу. Если перед указателем поставить звездочку, то возвращаемым значением будет число, хранящееся по адресу, записанному в указателе. Например, если р = &х; у = *р; то у = х.

Указатели могут встречаться и в выражениях. Если р - указатель на целое, т.е. имело место объявление int *р;, то *р может появиться там же, где и любая другая переменная, не являющаяся указателем. Таким образом, следующие выражения вполне допустимы:

*р = 7; *т *= 5;

( * д ) + + ;

Первое из них заносит число 7 в ячейку памяти по адресу р, второе увеличивает значение по адресу m в пять раз, третье добавляет единицу к содержимому ячейки с адресом g. В последнем случае круглые скобки необходимы, т.к. операции * и ++ с одинаковым приоритетом выполняются справа налево. В результате если, например, g = 5, то (*g)++ приведет к тому, что *g = 6, a *g++ всего лишь изменит сам адрес g (операция ++ выполняется над адресом g, а не над значением *g по этому адресу).

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

-указатель, то операция р++; увеличит его значение; теперь оно является адресом следующего элемента. Указатели и целые числа можно складывать. Конструкция р + п (р

-указатель, п - целое число) задает адрес n-го объекта, на который указывает р. Это

справедливо для любых объектов (int, char, float и т.п.).

*

Как связаны массивы и указатели?

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

int arr[4] = {1,2,3,4};

и указатель:

int

*р;

 

Тогда указателю можно присвоить адрес массива:

*

р

=

&агг[0];

 

или так:

 

 

 

р

=

агг;

 

После этого можно вывести на экран значение третьей ячейки массива, ее индекс равен 2: p r i n t f ( " % i " , a r r [ 2 ] ) ;

л и б о т а к :

printf("%i ",

* (р+2));

либо даже

т а к :

printf("%i

",

р[2]);

Пример 10.5. В ходе выполнения программы объявляется целочисленный массив агг [ 4 ] и указатель р, после чего указателю присваивается адрес начала массива, что позволяет далее обращаться к элементам массива агг, используя идентификатор указателя, так, как если бы был объявлен массив с именем р.

int агг[4] = {1,2,3,4}; int *р;

р=агг;

p r i n t f ( " % i % i % i \ n " , a r r [ 2 ] , * ( p + 2 ) , p [ 2 ] ) ;

В результате выполнения программы будет выведено три тройки.

Строки можно определять через указатели, только в этом случае необходимо сразу же присваивать им начальное значение, иначе программа может выдавать удивительные результаты, так как длина строки не будет изначально определена. Естественно, даже при инициализации не всегда можно поручиться, что строки большей длины, чем начальная, будут корректно обрабатываться. Тем не менее, такой способ тоже имеет право на существование.

Так строка:

char *s="hello";

полностью эквивалентна

char s[]="hello";

Как и в случае с обычными массивами, такой указатель можно изменять с использованием арифметики указателей. Тогда размер строки будет увеличиваться или уменьшаться, то есть либо в строку не будут входить первые символы, либо в качестве первых будут выдаваться символы, соответствующие данным, расположенным в памятки до строки, а только затем остальная строка. Важно понимать, что признаком конца строки является символ '\0', поэтому ее длина определяется компилятором автоматически, а не

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

Например:

char str[255];

printf("Введите строчку текста: " ) ; scanf("%s", &str);

printf("Строка равна = %s \n", str); printf("Третья буква = %c \n", str[4]);

printf("Сдвинем указатель вправо на один символ %s", str + 1 ) ;

На экране появится приглашение ввести строку. Пусть введено слово "Hello". Компьютер выведет на экран "Hello", затем пятую букву этой строки 'о'. Последний оператор вывода будет печатать строчку str, начиная со 2-го символа ('е') и до конца строки ('\0'). В нашем случае на экране появится: "ello". При вводе строки в функции scanf необходимо указывать адрес памяти, начиная с которого будет записываться вводимое значение. Но т.к. имя массива - str - является одновременно адресом начала строки, то вместо записи scanf("%s", &str[0]) можно написать проще: scanf("%s", &str). Обратите внимание на то, что спецификатор ввода/вывода для строки - %s, в то время как для символа %с.

Пример 10.6. Напишите программу, которая просит ввести пароль, содержащий любые символы, что резко уменьшит шансы подобрать этот пароль

const char password[] = "u8h5nkv888"; // строка-пароль char pass[255]; // вводимая строка do

{

printf("Введите пароль : " ) ;

scanf("Is",

&pass);

}

// пока pass не равна строке

while(strcmp(pass, password) != 0 ) ; // password, выполняем цикл printf("Добро пожаловать в систему ! \ п " ) ;

В программе использована новая функция strcmp(strl, str2), которая умеет сравнивать две строки посимвольно. Если строчки равны, то она возвращает ноль. Если бы мы написали просто while(pass != password), то тогда бы получился бесконечный цикл, т.к. в этом случае сравниваются указатели, т.е. адреса начала строк passs и password. А они, конечно же, имеют разные значения.

Функции для работы со строками определены в заголовочном файле string.h, который подключается в начале программы. Среди них стоит отметить:

1.strcpy(sl,s2) - копирует строку s2 в массив si. Возвращает строку si;

2.strucpy(sl,s2,n) - копирует не более п символов из s2 в si. Возвращает строку si;

3.strcat(sl,s2) - объединяет строку s2 и si. Возвращает строку si;

4.strcmp(sl,s2) - сравнивает две строки si и s2;

5.

strlen(sl) - возвращает длину строки si;

 

6.

s3 = strstr(sl,s2) - строка s3 содержит все символы строки si

начиная от той

 

позиции, где впервые встречается строка s2.

*

Пример 10.7. Напишите программу, которая объединяет слова «Российская» и«Федерация» в одно предложение. При этом важно, чтобы переменные, в которых хранятся слова, сохранили свои значения. *

char result[255];

char space = 1 \ sl[] = "Russian", s2 = "Federation"; strcpy(result,s1);

strcat(result, space); strcat(result, s 2 ) ; printf("%s\n", result);

73

Задачи к главе 10. Указатели и строки

Задача 10.1. Напишите программу, которая спрашивает имя пользователя, а потом выводит личное приветствие.

Рекомендуемый вид экрана:

Как Ваше имя? Вася

Здравствуйте, Вася!

Задача 10.2. Напишите программу, которая выводит на экран всю таблицу ASCII по 15 символов. Очередная группа символов появляется при нажатии пользователем любой клавиши.

Задача 10.3. Напишите программу, которая выводит код клавиши, нажатой пользователем. Программа выполняется до тех пор, пока не нажата клавиша <Esc>.

Задача 10.4. Напишите программу, которая переставляет символы введенного слова в

обратном порядке.

 

Рекомендуемый вид экрана:

,

Введите слово: поезд дзеоп

Задача 10.5. С клавиатуры вводится полный путь к файлу. Напишите программу, которая выделяет из этой строки имя файла Рекомендуемый вид экрана:

С:\windows\system32\activeds.dll activeds.dll

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

Рекомендуемый вид экрана:

Сидоров Петр Иванович Петр

Задача 10.7. Напишите программу, которая проверяет, является ли введенная с клавиатуры строка целым числом.

Рекомендуемый вид экрана:

2 7 , 5

Введенная строка не является целым числом

s

Задача 10.8. Напишите программу, которая определяет, есть ли в строке введенное пользователем слово.

Рекомендуемый вид экрана:

Введите строку: Организация Объединенных Наций Введите слово: наций Такое слово присутствует в строке Введите слово: нац Такого слова в строке нет

Задача 10.9. Напишите программу, которая удаляет из строки все пробелы.

Гпава 11

Функции

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

У функций есть очень много преимуществ: во-первых, сокращается код программы. Во-вторых, такая программа состоит из вызовов функций, по названию которых можно определить выполняемые ими действия. Такую программу легче читать. Отладив один раз функцию, вы можете ее использовать вновь и вновь, уменьшая этим риск возникновения ошибок и опечаток. В-третьих, функции можно собирать вместе для создания целых библиотек, которые мы подключаем в начале файла с помощью директивы #include.

При создании своих функций необходимо соблюдать следующие правила:

1.Функция не может быть описана внутри другой функции. В языке С++ вс,е функции глобальны.

2.Каждая функция имеет тип возвращаемого значения, имя, список входных параметров и тело функции.

3.Имя функции должно быть выбрано согласно правилам создания идентификаторов. Целесообразно отразить в имени функции те действия, которые она выполняет. Например, если вы написали функцию возведения в куб, то неразумно ее называть Vasja(), даже если автор ее Вася. Более подходящее для нее имя Cube().

4.Функция должна быть описана перед главной функцией main() или иметь прототип.

Описание функции в программе имеет вид:

/*

о п и с а н и е функции

*/

тип

имя__функции (параметры)

 

т е л о функции;

 

};

 

 

/*

о с н о в н а я функция

*/

m a i n ( )

 

{

 

 

 

т е л о функции

m a i n ;

}

 

 

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

Пример 11.1. Напишите программу, в которой описана функция, выводящая 10 звездочек.

#include

<stdio.h>

 

 

 

 

 

 

 

'

#include

<conio.h>

 

 

 

 

 

 

 

 

void

Stars()

 

 

//

описание

функции

Stars

 

{

 

 

 

 

 

 

 

 

 

 

 

int

i;

 

 

 

//

объявление

локальной

переменной

 

for

(i = 0; i <

10;

i++)

 

 

 

 

 

 

 

p u t c h a r ( ) ;

 

 

 

 

 

 

 

 

 

printf ("\n") ;

 

 

 

 

 

 

 

 

};

 

 

 

 

 

 

 

 

 

 

void

main()

//

главная функция

-

начало выполнение

программы

{

 

 

 

 

 

 

 

 

 

 

 

clrscr();

 

 

 

 

 

 

 

 

 

Stars ();

 

//

вызов

функции

Stars()

 

 

 

printf("Привет, Вася ! " ) ;

 

 

 

 

 

 

Stars ();

 

//

вызов

функции

Stars()

 

 

}

 

 

 

 

 

 

 

 

 

 

Здесь мы описываем функцию Stars(), которая печатает 10 звездочек и переходит на новую строку. Эта функция ничего не возвращает. Для таких случаев предусмотрен тип void - ничто. Далее в круглых скобках указываются входные параметры функции. Их тоже нет, но круглые скобки обязательны. Внутри функции могут быть определены свои внутренние или локальные переменные. Они доступны только внутри функции и никак не пересекаются с переменными в функции main(). Поэтому если в main() уже есть такая же переменная целого типа i, то компилятор поймет, что это разные переменные и не спутает их.

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

Итак:

int

п;

,

 

 

//

описание

глобальной переменной

 

{

,

 

 

 

 

 

 

 

 

void

 

Stars ()

 

 

//

описание

функции Stars()

 

int

i;

 

 

 

//

объявление

локальной переменной

15

for

(i = 0; i < n;

i++)

 

//

цикл: вывести n

звездочек

 

putchar(1 * ' ) ;

 

 

 

 

 

 

 

 

printf("\n");

 

 

 

 

 

 

 

 

};

 

 

 

 

 

 

 

 

 

 

 

void

main()

 

// главная функция - начало

выполнение программы

 

{

 

 

 

 

 

 

 

 

 

 

 

clrscr();

 

 

 

 

 

 

 

 

 

n =

5;

 

 

 

 

 

 

 

 

 

Stars();

 

//

вызов

функции

Stars()

 

 

 

'printf("Привет, Вася ! " ) ;

 

 

 

 

 

 

n =

15;

 

 

 

 

 

 

 

 

 

Stars();

 

//

вызов

функции

Stars()

 

 

 

}

 

 

 

 

 

 

 

 

 

 

 

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

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

переменную?

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

void

Stars(int

n)

 

 

//

о п и с а н и е

функции Stars()

с п а р а м е т р о м

{

 

 

 

 

 

 

 

 

 

 

 

 

int

i;

 

 

 

//

о б ъ я в л е н и е

л о к а л ь н о й п е р е м е н н о й

for

(i = 0; i

< n;

i++)

 

//

ц и к л :

в ы в е с т и

n з в е з д о ч е к

putchar(**');

 

 

 

 

 

 

 

 

 

 

printf("\n");

 

 

 

 

 

 

 

 

 

 

 

};

 

 

 

 

 

 

 

 

 

 

 

 

void

main()

 

//

г л а в н а я функция

-

н а ч а л о

выполнение программы

{

 

 

 

 

 

 

 

 

 

 

 

 

int

z = 7;

 

//

о п и с а н и е

л о к а л ь н о й

п е р е м е н н о й z

 

clrscr();

 

 

 

 

 

 

 

 

 

 

 

Stars (5);

 

//

вызов

функции

Stars()

с п а р а м е т р о м

5

printf ("Привет, Вася ! " ) ;

 

 

 

 

 

 

 

 

Stars(z + 3 ) ;

 

//

вызов

функции

Stars()

с п а р а м е т р о м

10

}

 

 

 

 

 

 

 

 

 

 

 

 

В заголовке функции Stars() написано, что функция при вызове будет требовать в качестве параметра целое число, которое запишется в переменную п. Такую переменную еще иногда называют формальным параметром. Цикл внутри функции выполнится п раз, после чего переменная п будет уничтожена. При новом обращении к функции Stars() указывается новое значение входного параметра.

Зачастую функция выполняет какие-нибудь расчеты, и результат этих вычислений нужно вернуть обратно в программу. Тип результата, вырабатываемый функцией, указывается при ее описании перед именем функции. После выполнения всех расчетов значение возвращается с помощью оператора return. в Пример 11.2. Напишите программу, в которой описана функция, возводящая в

квадрат число, введенное с клавиатуры.

float

Square(float х)

//

о п и с а н и е

функции

Square()

с п а р а м е т р о м

{

 

 

 

 

 

 

 

 

 

 

float

res;

 

 

 

 

 

 

 

res = х * х;

 

 

 

 

 

 

 

return res;

 

 

 

 

 

 

 

};

 

 

 

 

 

 

 

 

 

 

void

main()

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

 

 

float

a, b,

с;

//

о п и с а н и е л о к а л ь н о й

п е р е м е н н о й z

clrscr{);

 

 

 

 

 

 

 

a

=

5.5;

 

 

 

 

 

 

 

b

=

Square(a);

//

вычисляем

к в а д р а т

ч и с л а

a

 

с

=

Square(b

+ 3 ) ;

//

вычисляе м

к в а д р а т

ч и с л а

b

+ 3

p r i n t f ("Результа т вычислений = %.2f", с ) ;

}

Описанная функция Square() имеет один входной параметр типа float. Это значение записывается внутри функции в переменную х. Локальной переменной res присваивается квадрат значения х. С помощью оператора return это значение «подставляется» вместо функции Square().

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

Пример 11.3. Напишите программу, в которой описана функция, цель которой состоит в вычислении среднего арифметического двух чисел, введенных пользователем.

float Average(int

х, int у)

// Функция

вычисления

среднего двух целых

{

 

 

 

 

 

return (х + у) / 2 . 0 ;

 

 

 

};

 

 

 

 

 

void

main()

 

 

 

 

{

 

 

 

 

 

int

a, b;

 

 

 

 

printf("Введите два числа : " ) ;

 

 

 

scanf("%i %i", &a,

&b);

 

 

 

printf("Среднее арифметическое этих чисел =

%.lf \n",

Average(a, b ) ) ;

}

 

 

 

 

 

»

В функции описаны два входных параметра целого типа. Несмотря на то что тип у них одинаковый, мы не можем написать так: float Average(int х, у). Т.е. для каждой переменной необходимо указывать ее тип.

Внутри одной функции могут встречаться несколько операторов return. Например, функция для нахождения максимального числа из двух целых:

int max(int a, int b)

{

if (a > b) return a; else return b;

}

Если в программе определяется несколько функций, то иногда удобней определить их после функции main(), а до нее описать их прототип:

int max(int a, int b ) ;

// прототип функции max

float Square(float x ) ; // описание функции Square() с параметром

void

main()

// функция main()

{

 

 

 

float a, b,

c;

clrscr();

 

a

=

5.5;

 

b

=

Square(a)?

с = Square(b + 3 ) ;

printf("Результат вычислений = %.2f", с ) ; };

int max(int a, int b) // реализация функции max()

{

if (a > b) return a; else return b;

}

float

Square(float x)

// реализация функции Square() с параметром

{

 

 

float

res;

 

res = x * x;

 

return

res;

*

} ;

Рассмотрим еще один важный вопрос: как передавать массивы в качестве параметра функции? Следует передавать не сами значения массива, а указатель на начало массива, т.е. адрес в памяти.

Пример 11.4. Напишите программу, в которой описана функция, реализующая посимвольный вывод строки на экран.

/* Бегущая строка

*/

 

 

 

 

 

void

OutText(char

*str)

//

функция

требует

указатель на

тип char

{

 

 

 

 

 

 

 

 

int

i = 0;

 

 

 

 

 

 

 

while(str[i]

!= *\0')

//

цикл:

пока не

конец строки,

выполнять

{

 

 

 

 

 

 

 

 

printf ("%с",

str [i] ).;

 

 

 

 

 

sound(500);

 

 

 

 

 

,

delay(100);

 

 

 

 

 

 

 

nosound();

 

 

 

 

 

 

 

i++;

 

 

 

 

 

 

 

>

 

 

 

 

 

 

 

 

}

 

 

 

 

 

 

 

 

void

main()

 

 

 

 

 

 

 

{

 

 

 

 

 

 

 

 

char name[20];

 

 

 

 

 

 

textcolor(14);

 

 

 

 

 

 

textbackground(0);

 

 

 

 

 

clrscr() ;

 

 

 

 

 

 

 

OutText("Привет ! \ n " ) ;

 

 

 

 

 

OutText("Это

пример работы функции

OutText() \ h " ) ;

 

scanf("%s",

Sname);

 

 

 

 

 

OutText(name);

 

 

 

 

 

 

OutText("Функция

описана

корректно

\ n " ) ;

 

 

getch();

 

 

 

 

 

 

 

В программе реализована функция OutText(), входным параметром которой является указатель на массив типа char (т.е. строка). Эта строка выводится в цикле while() посимвольно, до тех пор, пока компьютер не встретит символ окончания строки '\0'. После вывода очередной буквы подается звуковой сигнал с помощью функции 8оип(1(частота), описанной в библиотеке dos.h, где частота - целое число, выраженное в герцах (например, для ноты "ля" это 440 Гц). Далее следует пауза выполнения программы длиной 0.5 секунды, во время которой звуковой сигнал не прекращается. После завершения паузы звук выключается функцией nosound(), и тело цикла выполняется заново.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]