WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!


Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 13 |

printf(" %p %p %p\n", pa, pc, pb);

printf(" %d %f\n", *pa, *pc); // может возникнуть ошибка Результат (в Ubuntu):

0x400410 0x7fff5b729580 (nil) -1991643855 0.Использование неопределенных указателей в программе при вычислениях чревато возникновением серьезных ошибок. Чтобы избежать этого, указателю можно присвоить значение, говорящее, что указатель никуда не ссылается (NULL). Использовать такой указатель в выражениях не получится, пока ему не будет присвоено конкретное значение:

int a = 5, b = 7;

float c = 6.98;

int *pa, *pb;

float *pc;

pa = pb = NULL;

pc = NULL;

printf(" %15p %15p %15p\n", pa, pc, pb);

// printf(" %15d %15f %15d\n", *pa, *pc, *pb); // Error pa = &a;

pb = &b;

pc = &c;

printf(" %15p %15p %15p\n", pa, pc, pb);

printf(" %15d %15f %15d\n", *pa, *pc, *pb);

Результат (в Ubuntu):

(nil) (nil) (nil) 0x7fffeabf4e44 0x7fffeabf4e4c 0x7fffeabf4e 5 6.980000 В данном случае, если попытаться извлечь значение из памяти с помощью указателя, который никуда не ссылается, то возникает "ошибка сегментирования".

На этом уроке вы должны понять, что такое адрес переменной и как его получить (&var), что такое переменная-указатель (type *p_var; p_var = &var) и как получить значение, хранимое в памяти, зная адрес ячейки (*p_var). Однако у вас может остаться неприятный осадок из-за непонимания, зачем все это надо Это нормально. Понимание практической значимости указателей придет позже по мере знакомства с новым материалом.

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

Урок 8. Функции. Передача аргументов по значению и по ссылке Общее представление Язык C как и большинство других языков программирования позволяет создавать программы, состоящие из множества функций, а также из одного или нескольких файлов исходного кода. До сих пор мы видели только функцию main(), которая является главной в программе на C, поскольку выполнение кода всегда начинается с нее. Однако ничего не мешает создавать другие функции, которые могут быть вызваны из main() или любой другой функции. На этом уроке мы рассмотрим создание только однофайловых программ, содержащих более чем одну функцию.

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

Локальные переменные в языке программирования C называют автоматическими. Область действия автоматических переменных распространяется только на ту функцию, в которой они были объявлены. Параметры функции также являются локальными переменными.

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

#include float median (int a, int b); // объявление функции main () { int num1 = 18, num2 = 35;

float result;

printf("%10.1f\n", median(num1, num2));

result = median(121, 346);

printf("%10.1f\n", result);

printf("%10.1f\n", median(1032, 1896));

} float median (int n1, int n2) { // определение функции float m;

m = (float) (n1 + n2) / 2;

return m;

} В данном случае в начале программы объявляется функция median(). Объявляются тип возвращаемого ею значения (float), количество и типы параметров (int a, int b).

Обратите внимание, когда объявляются переменные, то их можно группировать: int a, b;.

Однако с параметрами функций так делать нельзя, для каждого параметра тип указывается отдельно: (int a, int b).

Далее идет функция main(), а после нее — определение median(). Имена переменныхпараметров в объявлении функции никакой роли не играют (их вообще можно опустить, например, float median (int, int);). Поэтому когда функция определяется, то имена параметров могут быть другими, однако тип и количество должны строго совпадать с объявлением.

Функция median() возвращает число типа float. Оператор return возвращает результат выполнения переданного ему выражения; после return функция завершает свое выполнение, даже если далее тело функции имеет продолжение. Функция median() вычисляет среднее значение от двух целых чисел. В выражении (float) (n1 + n2) / сначала вычисляется сумма двух целых чисел, результат преобразуется в вещественное число и только после этого делится на 2. Иначе мы бы делили целое на целое и получили целое (в таком случае дробная часть просто усекается).

В теле main() функция median() вызывается три раза. Результат выполнения функции не обязательно должен быть присвоен переменной.

Вышеописанную программу можно было бы записать так:

#include float median (int n1, int n2) { float m;

m = (float) (n1 + n2) / 2;

return m;

} main () { int num1 = 18, num2 = 35;

float result;

printf("%10.1f\n", median(num1, num2));

result = median(121, 346);

printf("%10.1f\n", result);

printf("%10.1f\n", median(1032, 1896));



} Хотя такой способ и экономит одну строчку кода, однако главная функция, в которой отражена основная логика программы, опускается вниз, что может быть неудобно. Поэтому первый вариант предпочтительней.

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

Статические переменные В языке программирования C существуют так называемые статические переменные. Они могут быть как глобальными, так и локальными. Перед именем статической переменной пишется ключевое слово static.

Внешние статические переменные, в отличие от обычных глобальных переменных, нельзя использовать из других файлов в случае программы, состоящей не из одного файла. Они глобальны только для функций того файла, в котором объявлены. Это своего рода сокрытие данных, по принципу "не выставлять наружу ничего лишнего, чтобы 'что-нибудь' нечаянно не могло 'испортить' данные".

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

#include int hello();

main() { printf(" - %d-й вызов\n", hello());

printf(" - %d-й вызов\n", hello());

printf(" - %d-й вызов\n", hello());

} int hello () { static count = 1;

printf("Hello world!");

return count++;

} Результат:

Hello world! - 1-й вызов Hello world! - 2-й вызов Hello world! - 3-й вызов В этом примере в функции hello() производится подсчет ее вызовов.

Задание Придумайте свой пример с использованием статической переменной.

Передача аргументов по ссылке В первом примере этого урока мы передавали в функцию аргументы по значению. Это значит, что когда функция вызывается, ей передаются в качестве фактических параметров (аргументов) не указанные переменные, а копии значений этих переменных. Сами переменные к этим копиям уже никакого отношения не имеют. В вызываемой функции эти значения присваиваются переменным-параметрам, которые, как известно, локальны. Отсюда следует, что изменение переданных значений никакого влияния на переменные, переданные в функцию при вызове, не оказывают. В примере выше даже если бы в функции median() менялись значения переменных n1 и n2, то никакого влияния сей факт на переменные num1 и num2 не оказал.

Однако можно организовать изменение локальной переменной одной функции, с помощью другой функции. Сделать это можно, передав в функцию адрес переменной или указатель на нее. На самом деле в этом случае также передается копия значения. Но какого значения! Это адрес на область памяти. На один и тот же участок памяти может существовать множество ссылок, и с помощью каждой из них можно поменять находящееся там значение.

Рассмотрим пример:

#include void multi (int *px, int y);

main () { int x = 34, y = 6;

multi(&x, 367);

multi(&y, 91);

printf("%d %d\n", x, y);

} void multi (int *base, int pow) { while (pow >= 10) { *base = *base * 10;

pow = pow / 10;

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

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

Когда multi() вызывается в main(), то в качестве первого параметра мы должны передать адрес, а не значение. Поэтому, например, вызов multi(x, 786) привел бы к ошибке, а вызов multi(&x, 786) — правильный, т.к. мы берем адрес переменной x и передаем его в функцию. При этом ничего не мешает объявить в main() указатель и передавать именно его (в данном случае сама переменная p содержит адрес):

int x = 34, y = 6;

int *p;

p = &x;

multi(p, 367);

p = &y;

multi(p, 367);

printf("%d %d\n", x, y);

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

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

Решение задач Задания 1. Перепишите код первого примера этого урока так, чтобы в нем использовался указатель; а код примера с функцией multi(), наоборот, избавьте от указателей.

2. Напишите программу, в которой помимо функции main() были бы еще две функции: в одной вычислялся факториал переданного числа, в другой — находился n-ый элемент ряда Фибоначчи (n — параметр функции). Вызовите эти функции с разными аргументами.

3. Придумайте и напишите программу, в которой из функции main() вызывается другая функция, а из последней вызывается еще одна функция.

Урок 9. Форматированный ввод данных В то время как функция printf() осуществляет форматированный вывод данных, функция scanf() осуществляет их форматированный ввод. Это значит, что поступающие на ввод данные преобразуются соответственно указанному формату(ам) и записываются по адресу(ам) указанной(ых) переменной(ых):

scanf(строка_формата, адреса_переменных);

Причина, по которой в scanf() передаются адреса, а не значения переменных, очевидна.

Функция scanf() должна изменять значения переменных тех функций, из которых вызывается. Единственный способ — это получить адреса областей памяти.





Спецификации формата данных, допустимые в строке формата, для scanf() почти идентичны тем, что были описаны для функции printf(). На этом уроке мы подробно не рассмотрим все возможности форматированного ввода с помощьюscanf(), зато разберем ряд конкретных примеров.

Ввод чисел, символов и строк Пример ввода-вывода целого и вещественного чисел, символа и строки:

int a;

float b;

char ch, str[30];

scanf("%d%f%c%s", &a, &b, &ch, str);

printf("%d %.3f %c %s\n", a, b, ch, str);

Результат:

45 34.3456y hello 45 34.346 y hello Здесь при выполнении программы все данные были введены в одну строку. Разделителем между числами и строками является пробел, а также любой другой символ пустого пространства (например, '\n'). Однако при считывании символа, пробел учитывается как символ; чтобы этого не произошло, в примере букву записали сразу после числа. Данные можно было бы ввести, разделяя их переходом на новую строку (опять же при этом надо иметь ввиду, как считывается символ).

В строке формата функции scanf() между спецификациями вполне допустимо поставить пробелы: %d %f %c %s. Они никакой роли не сыграют. Понятно, что данные можно было получить и так:

scanf("%d", &a);

scanf("%f", &b);

scanf("%c", &ch);

scanf("%s", str);

Обратите внимание, перед переменной str отсутствует знак амперсанда. Это не опечатка. В последующих уроках вы узнаете, что имя массива уже само по себе является ссылкой на массив (другими словами, str содержит адрес начала массива).

В функции scanf() в спецификации формата вещественных чисел не указывается точность представления числа. Запись типа %.3f или %.10lf приведет к невозможности получить вещественное число. Чтобы получить число типа double используют формат %lf, для long double - %Lf.

Для целых чисел: длинное целое - %ld, короткое целое - %hd. Кроме того, существуют спецификации для ввода восьмеричных и шестнадцатеричных чисел.

Функция scanf() возвращает количество удачно считанных данных; т.е. значение, возвращаемое функцией, можно проанализировать и таким образом узнать, корректно ли были введены данные. Например:

int a;

double b;

char ch, str[30];

ch = scanf("%d %lf %s", &a, &b, str);

if (ch == 3) printf("%d %.3lf %s\n", a, b, str);

else printf("Error input\n");

Использование обычных символов В строке формата scanf() допустимо использование обычных символов. В этом случае при вводе данных также должны вводится и эти символы:

int a, b, c;

scanf("%d + %d = %d", &a, &b, &c);

printf("Your answer is %d\nThe correct answer is %d\n", c, a+b);

В данном случае, когда программа выполняется, ввод должен выглядеть примерно так:

342+1024 = 1366. Знаки "+" и "=" обязательно должны присутствовать между числами, наличие пробелов или их отсутствие абсолютно никакой роли не играет:

45 + 839=Your answer is The correct answer is Запрет присваивания Если какие-либо данные, вводимые пользователем, следует проигнорировать, то используют запрет присваивания, ставя после знака %, но перед буквой формата звездочку *. В таком случае данные считываются, но никакой переменной не присваиваются. Это можно использовать, например, когда нет определенной уверенности в том, что поступит на ввод, с одной стороны, и нужды сохранять эти данные, с другой:

float arr[3];

int i;

for(i = 0; i < 3; i++) scanf("%*s %f", &arr[i]);

printf("Sum: %.2f\n", arr[0]+arr[1]+arr[2]);

Здесь предполагается, что перед каждым числом будет вводится строка, которую следует проигнорировать, например:

First: 23.Second: 17.Third: 32.Sum: 73.Использование "шаблонов" Для функции scanf() есть пара спецификаций формата, отдаленно напоминающих шаблоны командной оболочки и др. Формат […] позволяет получить строку, содержащую любые символы, указанные в квадратных скобках. Как только на ввод поступает символ, не входящий в указанный набор, считывание данных прекращается. Формат [^…], наоборот, помещает в строку символы, не входящие в указанный набор, до тех пор пока не встретит любой из указанных.

В примере ниже как только поступает не цифра, считывание ввода завершается. При этом если первый символ — не цифра, то в str вообще ничего не записывается:

char str[30]="";

scanf("%[0-9]", str);

printf("%s\n", str);

А в этом случае строке будет присвоена последовательность символов до любого из указанных знаков препинания:

scanf("%[^;:,!]", str);

printf("%s\n", str);

Результат:

Hello, World! Hello Некоторые особенности и ограничения функции scanf() Как только поступают некорректные данные, функция scanf() завершает свою работу. В примере:

scanf("%d%f", &a, &b);

если переменной a попытаться присвоить символ или строку, что невозможно, то переменная b уже обрабатываться не будет. Можно предположить, что так будет надежнее:

scanf("%d", &a);

scanf("%f", &b);

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

Pages:     | 1 |   ...   | 3 | 4 || 6 | 7 |   ...   | 13 |










© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.