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

книги / Практикум по программированию на языке Си

..pdf
Скачиваний:
23
Добавлен:
12.11.2023
Размер:
3.53 Mб
Скачать

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

вименах символов, не входящих в алфавит языка Си (русские буквы), воспринимается как ошибка. В-четвертых, использование в именах символов как разделителей или знаков операций (%) приводит к ошибкам компиляции. В-пятых, неверное использование препроцессорных идентификаторов, введенных в заголовочном файле (например, NULL, INT_MAX), приводит к ошибкам только в том случае, если соответствующее препроцессорное определение идентификатора уже включено в текст программы (в файлах stdio.h, limits.h). В- шестых, применение названий библиотечных функций (puts, printf)

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

мере выдал сообщение "called object is not a function" – "вызываемый объект не является функцией".

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

ЗАДАЧА 04-03. Введите с помощью typedef свои названия tuli и real для базовых типов, соответственно unsigned long int, long double. Продемонстрируйте в программе, что tuli и real – это не переменные и не названия новых типов, а только синонимы обозначений уже существующих типов. Для вывода значений с обозначениями определите соответствующие препроцессорные макросы (см. в теме 3 определения макросов PRINTF(), PRINTD()…).

91

// 04_03.c - typedef - новые имена стандартных

типов

#include <stdio.h>

/*

02

*/

#define PRINTI(EXPRESSION) \

 

 

 

printf(#EXPRESSION"=%d\n",EXPRESSION)

 

 

#define PRINTLI(EXPRESSION) \

 

 

 

printf(#EXPRESSION"=%ld\n",EXPRESSION)

07

*/

int main()

/*

{

/*

08

*/

typedef unsigned long int tuli;

/*

09

*/

typedef long double real;

/*

10

*/

tuli a, unsigned_long_int_variable 12345L;/*

11

*/

real x;

/*

12

*/

long double d;

/*

13

*/

tuli=18L;

/*

14

*/

PRINTLI(unsigned_long_int_variable);

/*

15

*/

PRINTI(sizeof(tuli));

/*

16

*/

return 0;

/*

17

*/

}

/*

18

*/

Некорректная трансляция:

04_03.c: In function `main':

04_03.c:14: parse error before `='

Попытка в строке 14 использовать tuli в качестве имени переменной и присвоить ей значение 18L, естественно, оказалась неудачной. "Закомментировав" строку 14, получим синтаксически правильную программу, в которой для обозначения одного типа равноправно используются и введенный программистом идентификатор real, и стандартное обозначение long double.

Результаты выполнения исправленной программы (без строки

14):

unsigned_long_int_variable=12345 sizeof(tuli)=4

Стандарт языка требует, чтобы реализация допускала имена (идентификаторы) длиной не менее 31 символа.

92

ЗАДАНИЕ. Выполните эксперименты, позволяющие определить, сколько первых символов длинного идентификатора ваш компилятор считает значимыми.

Например, определите в программе две переменных, имена которых отличаются последней буквой. Постепенно увеличивая длину имен, установите, когда компилятор примет их за одно имя и выдаст сообщение о повторном определении ("redeclaration of"). Попытка автора проделать этот эксперимент с компилятором DJGPP не была завершена. При длине идентификаторов 288 символов, из которых совпадали 287 первых, компилятор распознал их как разные имена. Дальнейших попыток увеличить длину имен автор не делал.

4.2. Определение и инициализация переменных

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

ЗАДАЧА 04-04. Напишите программу, в которой глобальные и локализованные в теле функции переменные базовых типов инициализируются с помощью:

1) имен препроцессорных констант;

2)выражений, включающих константы;

3)выражений, в которые входят переменные.

//04_04.c - определение и инициализация переменных

#include <stdio.h>

#define PRINTI(EXPRESSION) \

printf(#EXPRESSION"=%d\n",EXPRESSION)

 

#define BASE 77

/* 05 */

int global=99;

/* 06 */

int error=global/3;

/* 07 */

int correct=BASE/3;

/* 08 */

int main ()

/* 09 */

{

/* 10 */

int result=BASE;

/* 11

*/

int sum=result+correct;

/* 12

*/

int count=BASE*3/2;

/* 13

*/

93

int internal=global-99;

/* 14 */

int flag = count - result;

/* 15 */

PRINTI(correct);

/* 16 */

PRINTI(result);

/* 17 */

PRINTI(sum);

/* 18 */

PRINTI(count);

/* 19 */

PRINTI(internal);

/* 20

*/

PRINTI(flag);

/* 21

*/

return 0;

/* 22

*/

}

/* 23

*/

Некорректная трансляция:

04_04.c:7: initializer element is not constant

В программе переменные global, error и correct определены вне тела каких-либо функций. Это так называемые внешние по отношению к функциям переменные. Ошибочно использование в строке 7 неконстантного выражения global/3 в качестве инициализатора для внешней переменной, а выражение BASE/3 вполне допустимо (BASE – препроцессорная константа).

Требования к инициализирующим выражениям переменных автоматической памяти существенно более слабые, что и демонстрируют определения переменных в теле функции main(). Убрав ("закомментировав" строку 7) определение внешней переменной error, получим после успешной трансляции исполнимую программу.

Результат выполнения исправленной программы:

correct=25

result=77

sum=102

count=115

internal=0

flag=38

В результатах обратите внимание на значение внешней переменой correct. За счет округления до целой величины значение выражения BASE/3 равно 25.

Программа 04_04.с демонстрирует доступность внешних переменных внутри тела функции. Для иллюстрации локализации внут-

94

ренних определений в блоке и "экранирования" внутренним определением внешнего имени рассмотрите следующую задачу.

ЗАДАЧА 04-05. В программе определите препроцессорную константу и инициализированную внешнюю (глобальную) переменную. Используйте препроцессорную константу и внешнюю переменную для инициализации переменных в теле функции и во вложенном в нее блоке. Продемонстрируйте "экранирование" внешних определений внутренними.

/* 04_05.c - локализация и "экранирование" имен */ #include <stdio.h>

#define PRINTI(EXPRESSION) \ printf(#EXPRESSION"=%d\n",EXPRESSION)

#define BASE 77 int global=99; int main ()

{

int result=BASE;

int sum=result+global; int value=66;

{/* Внутренний блок в теле функции main() */ int sum=value/3+33;

int value=22;

puts("At the block:"); PRINTI(result); PRINTI(sum);

PRINTI(value);

}

puts("Out of the block:"); PRINTI(result); PRINTI(sum); PRINTI(value);

return 0;

}

Результат выполнения программы:

At the block: result=77 sum=55

95

value=22

Out of the block: result=77

sum=176

value=66

4.3. Арифметические выражения

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

ЗАДАЧА 04-06. Пусть введена целочисленная переменная int number=4. Какое из выражений ошибочно и почему:

number+++24

24+++number

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

Следующая программа отвечает на вопросы задачи.

/* 04_06.c - операции +++ в выражениях */

/* 02 */

#include <stdio.h>

#define PRINT_EXPI(EXPRESSION)\

 

 

printf("The value of "#EXPRESSION" is %d\n",

 

EXPRESSION)

/* 06 */

int main ()

{

/* 07 */

int number=4;

/* 08 */

PRINT_EXPI(24+++number);

/* 09 */

PRINT_EXPI(number);

/* 10 */

PRINT_EXPI(number+++24);

/* 11 */

PRINT_EXPI(number);

/* 12

*/

return 0;

/* 13

*/

}

/* 14

*/

96

Некорректная трансляция:

04_06.c: In function `main':

04_06.c:9: invalid lvalue in increment

Компилятор сообщает об ошибке в строке 09. Ошибочность выражения 24+++value следует из ассоциативности и рангов операций ++ и +, в соответствии с которыми выражение трактуется как (24++)+value. Ну а попытка изменить значение константы 24, естественно, не верна.

Явно определим операции + и ++, добавив между ними пробел, т.е. так запишем оператор: PRINT_EXPI(24+ ++number). Получим следующий результат выполнения программы:

The value of 24+ ++number is 29

The value of number is 5

The value of number+++24 is 29

The value of number is 6

Как видите, в вычислении выражения number+++24 используется number со значением 5, а только затем оно увеличивается на 1.

Тот факт, что присваивание является операцией, применяемой к двум операндам и возвращающей значение, не всегда осознается.

ЗАДАЧА 04-07. Выведите значения следующих выражений j=14, j=-12, j-=12.

/* 04_07.c - операции =, -= и выражения с ними */ #include <stdio.h>

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

int j; PRINT_EXPI(j=14); PRINT_EXPI(j=-12); PRINT_EXPI(j-=12); PRINT_EXPI(j); return 0;

}

97

Результат выполнения программы:

The value of j=14 is 14

The value of j=-12 is -12

The value of j-=12 is -24

The value of j is -24

ЗАДАЧА 04-08. Чтобы обратить внимание на различие между постфиксными и префиксными операциями инкремента и декремента, получите значения выражений n+++m, n---m, --n, n-- и входящих в них переменных.

/* 04_08.c - операции ++, -- и выражения с ними */ #include <stdio.h>

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

int n=5,m=7; PRINT_EXPI(--n); PRINT_EXPI(n); PRINT_EXPI(n--); PRINT_EXPI(n); PRINT_EXPI(n+++m); PRINT_EXPI(n); PRINT_EXPI(m); PRINT_EXPI(n---m); PRINT_EXPI(n); PRINT_EXPI(m); return 0;

}

Результат выполнения программы:

The value of --n is 4

The value of n is 4

The value of n-- is 4

The value of n is 3

The value of n+++m is 10

98

The value of n is 4

The value of m is 7

The value of n---m is -3

The value of n is 3

The value of m is 7

После инициализации переменные n и m получили, соответственно, значения 5 и 7. Последовательность их изменений при вычислении выражений наглядно демонстрирует правила выполнения операций.

ЭКСПЕРИМЕНТ. Попытайтесь получить значения выраже-

ний n+++++m, --n---m, n++-++m, n--+--m.

/* 04_08_1.c - последовательности операций +++++,

--+--

*/

#include <stdio.h>

/* 03 */

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n",

EXPRESSION)

/* 07 */

int main ()

{

/* 08 */

int n=5,m=7;

/* 09 */

PRINT_EXPI(n+++++m);

/* 10 */

PRINT_EXPI(--n---m);

/* 11 */

PRINT_EXPI(n++-++m);

/* 12 */

PRINT_EXPI(n);

/* 13 */

PRINT_EXPI(m);

/* 14 */

PRINT_EXPI(n--+--m);

/* 15 */

PRINT_EXPI(n);

/* 16

*/

PRINT_EXPI(m);

/* 17

*/

return 0;

/* 18

*/

}

/* 19

*/

Некорректная трансляция:

04_08_1.c: In function `main':

04_08_1.c:10: invalid lvalue in increment

04_08_1.c:11: invalid lvalue in decrement

Проанализируем выражения n+++++m и --n---m из строк 10, 11. Операции инкремента и декремента имеют приоритет над адди-

99

тивными операциями. Следовательно, первое из них трактуется так: ((n++)++)+m. Значение выражения (n++) – целое число, а применение к числу операции ++ недопустимо, т.е. (n++)++ – недопустимое выражение. Аналогично ошибочно выражение (--n)--. Это и сообщает компилятор, указывая на ошибки.

В выражениях n++-++m и n--+--m результаты унарных операций ++ и -- используются в качестве операндов бинарных операций + и -, что полностью корректно. Удалив из программы

PRINT_EXPI(n++++m); PRINT_EXPI(--n---m); получим:

The value of n++-++m is -3

The value of n is 6

The value of m is 8

The value of n--+--m is 13

The value of n is 5

The value of m is 7

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

ЗАДАЧА 04-09. Предложите варианты выражений с целыми операндами, приводящие к ошибочным результатам за счет округления при делении и "переполнения" при умножении.

Вследующей программе приведено одно из решений:

//04_09.c - выражения с целочисленными операндами

#include <stdio.h>

#define PRINT_EXPI(EXPRESSION)\

printf("The value of "#EXPRESSION" is %d\n", EXPRESSION)

int main ()

{

PRINT_EXPI(100*(70/100));

100