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

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

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

2.1. Знакомство с функцией форматного вывода

Различают собственно константы и именованные константы. Как для всякого объекта программы, для представления константы в памяти ЭВМ выделяется некоторый участок памяти. Размеры этого участка и способ размещения в нем кода значения константы зависят от ее типа. Стандарты языка определили следующие четыре типа

констант:

(floating constants);

!

вещественные

!

целые

(integer constants);

!перечислимые (enumeration constants);

! символьные

(character constants).

Кроме того,

константами (но не базовыми) считают строковые

литералы (string literal) – последовательности символов, заключенных в кавычки (двойные, не в апострофы).

Для экспериментального изучения свойств констант и некоторых особенностей их представления в памяти ЭВМ используем функцию printf() и операцию sizeof, позволяющую определять размер памяти (в байтах), выделяемой для представления ее операнда. Функция printf() позволяет выводить на экран дисплея (в стандартный выходной поток stdout) символьные представления (“изображения”) значений своих аргументов (фактических параметров). Количество аргументов у функции printf() может быть различным. Но первым аргументом функции printf() всегда служит форматная строка, определяющая формы внешних представлений значений всех последующих аргументов. Для задания указанных форм программист использует в форматной строке спецификации преобразования данных. Вот основные из них:

!%d – для изображения десятичного целого со знаком;

!%c – для изображения отдельного символа;

!%f – для изображения вещественного числа с фиксированной точкой;

!%e – для изображения вещественного числа с мантиссой и порядком;

!%s – для изображения символьной строки.

Кроме спецификаций преобразования данных в форматную стро-

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

31

не дисплея). Среди этих символов могут быть управляющие, например, '\t'(табуляция), '\n'(начало новой строки).

ЗАДАЧА 02-01. Выведите с помощью функции printf() значение вещественной константы 3.14159, используя спецификации преобразования для вещественных значений. Вывод снабдите поясняющим текстом.

/* 02_01.c - первая программа с printf() */ #include <stdio.h>

int main ()

{

printf("The value of PI=%f\n",3.14159); printf("The value of PI=%e\n",3.14159); return 0;

}

Программа корректно транслируется и правильно исполняется. В случае корректной трансляции (без сообщений об ошибках и

предупреждений о возможных нарушениях синтаксиса) мы в дальнейшем будем опускать эту фразу.

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

The value of PI=3.141590

The value of PI=3.141590e+00

В отличие от рассмотренной ранее функции puts(), функция printf() не обеспечивает после вывода переход на новую строку. Поэтому в конец каждой форматной строки добавлен управляющий символ '\n'.

ЭКСПЕРИМЕНТ. Удалите ‘\n’ из форматных строк, оттранслируйте и выполните ту же программу 02_01.с.

В результате получите:

The value of PI=3.141590The value of PI=3.141590e+00

32

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

ЭКСПЕРИМЕНТ. Заменим в 02_01.с спецификации %f и %e на %d (предназначена для целых) и %c (используется для отдельных символов).

/* 02_01_1.c - эксперименты с printf() */ #include <stdio.h>

int main ()

{

printf("The value of PI=%d\n",3.14159); printf("The value of PI=%c\n",3.14159); return 0;

}

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

The value of PI=-266631570

The value of PI=n

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

ЭКСПЕРИМЕНТ. Попытаемся вывести то же самое значение вещественной константы 3.14159 с помощью спецификации преобразования %s, т.е. как строковое значение.

/* 02_01_2.c - ошибочное применение строковой спецификации */

#include <stdio.h> int main ()

{

printf("The value of PI=%s\n",3.14159); return 0;

}

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

Exiting due to signal SIGSEGV

General Protection Fault at eip=00004efe

33

eax=00000000 ebx=0004d100 ecx=ffffffff edx=00000073 esi=00001560 edi=f01b866e

ebp=0004c46c esp=0004b09c program=D:\2000_C\PRACTI~1\PROGRAMS\TEST.EXE cs: sel=00a7 base=82e86000 limit=0005ffff ds: sel=00af base=82e86000 limit=0005ffff es: sel=00af base=82e86000 limit=0005ffff fs: sel=0087 base=0000e270 limit=0000ffff gs: sel=00bf base=00000000 limit=ffffffff ss: sel=00af base=82e86000 limit=0005ffff

Call frame traceback EIPs: 0x00004efe

0x00001e7c

0x0000157b

0x00001ada

The value of PI=

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

В целом результаты двух последних экспериментов со спецификациями преобразования данных демонстрируют "чувствительность" функции printf() к ошибкам в форматной строке (к неверному выбору спецификаций преобразования).

ЗАДАЧА 02-02. Продемонстрировать использование функции printf() вместо функции puts().

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

/* 02_02.c - printf() заменяет puts()*/ #include <stdio.h>

int main ()

{

printf

("From formatted string: Hello, World!\n"); printf

("%s\n","From argument of printf(): Hello, World!"); return 0;

}

34

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

From formatted string: Hello, World! From argument of printf(): Hello, World!

В первом обращении к функции printf() всего один аргумент – форматная строка. В ней нет спецификаций преобразования, и ее содержимое непосредственно пересылается на экран дисплея (в стандартный выходной поток stdout). Единственное отличие от функции puts() – использование управляющего символа '\n' в конце выводимого текста для организации перехода на новую строку.

Во втором вызове функции printf() форматная строка включает только спецификацию преобразования '%s' и управляющий символ '\n'. Выводимый текст (строка) использован в качестве второго аргумента.

ЗАДАЧА 02-03. Вывести, используя спецификации %c и %d, значения символьных констант.

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

/* 02_03.c - разные спецификации для символа */ #include <stdio.h>

int main ()

{

printf("The integer value of %c equals %d\n", 'A','A');

printf("The integer value of %c equals %d\n", 'z','z');

return 0;

}

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

The integer value of A equals 65

The integer value of z equals 122

В программе 02_03.с в каждом обращении к функции printf() использованы: форматная строка и два одинаковых аргумента, зна-

35

чения которых выводятся с использованием двух различных спецификаций преобразования %c и %d. Спецификация %c предусматривает вывод "изображения" ("образа") символа, а спецификация %d предполагает вывод целого значения кода символа. Обратите внимание на эту "двойственность" символьных данных. Возможность обращения к целочисленным кодам символов часто используется в программах на Си. Чтобы убедиться в правильности полученных в программе 02_03.с результатов, посмотрите таблицу ASCII-кодов символов (Приложение 1 из [3]).

ЗАДАНИЕ. Замените в предыдущей программе выводимые символы 'A' (латинское) и 'z' русскими символами, например, 'Б'

и 'я'.

Полученные при выполнении программы результаты будут зависеть от того, в какой кодировке представлены в тексте программы русские буквы: в кодах MS-DOS (кодовая таблица 866) или в кодах MS Windows (кодовая таблица 1251). При использовании текстовых редакторов MS Windows программа и результаты будут такими:

/* 02_03_1.c - вывод русских символов */ #include <stdio.h>

int main ()

{

printf("The integer value of %c equals %d\n", 'Б','Б');

printf("The integer value of %c equals %d\n", 'я','я');

return 0;

}

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

The integer value of - equals -63

The integer value of _ equals -1

Изображения символов не верны, так как выходной поток направляется в окно MS-DOS-приложения, где коды русских букв должны иметь другие значения. Числовые значения кодов также не верны, так как коды от 128 до 255 имеют в начале единичный бит, который трактуется как признак отрицательного числа.

36

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

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

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

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

На третий вопрос можно ответить на основе анализа выполнения программ 02_01_1.с (вещественный аргумент, спецификации %d и %c) и 02_01_2.с (вещественный аргумент – строковая спецификация %s). В обоих случаях компиляция проходит успешно, но результаты исполнения не верны.

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

2.2. Вещественные константы

Напомним, что вещественные константы имеют: 1) конечную точность представления в ЭВМ;

2)тип (явно заданный либо выбранный по умолчанию);

3)предельные значения, точно определяемые реализацией компилятора для каждого типа.

Если вещественная константа записана без суффикса, то ей по умолчанию приписывается тип double. Суффикс f или F делает типом константы float, а суффикс L или l соответствует типу long double.

ЗАДАЧА 02-04. Определить размеры памяти, выделяемой для вещественных констант разных типов.

37

/* 02_04.c - размеры вещественных констант */ #include <stdio.h>

int main ()

{

printf("sizeof(2.71828) = %d\n", sizeof(2.71828));

printf("sizeof(2.71828F) = %d\n", sizeof(2.71828F));

printf("sizeof(2.71828L) = %d\n", sizeof(2.71828L));

return 0;

}

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

sizeof(2.71828) = 8 sizeof(2.71828F) = 4 sizeof(2.71828L) = 12

Напомним, что приводятся результаты, получаемые при использовании свободно распространяемого 32-разрядного компилятора

DJGPP.

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

/* 02_04_1.c - размеры вещественных констант */ #include <stdio.h>

int main ()

{

printf("sizeof(0.) = %d\n", sizeof(0.)); printf("sizeof(123456789.) = %d\n",

sizeof(123456789.)); printf("sizeof(123456789.987654321) = %d\n",

sizeof(123456789.987654321)); printf("sizeof(0.987654321) = %d\n",

sizeof(0.987654321)); return 0;

}

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

38

sizeof(0.) = 8 sizeof(123456789.) = 8 sizeof(123456789.987654321) = 8 sizeof(0.987654321) = 8

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

ЗАДАНИЕ. Решите подобную задачу с константами типов float и long double.

Вещественные числа представляются в ЭВМ с конечной точностью. Наиболее ярко это проявляется при использовании чисел типа float (самых "коротких"). Но и числа double не всегда будут сохранены именно в том виде, как их изображение записано в тексте программы.

ЗАДАЧА 02-05. Продемонстрируйте независимость числа сохраняемых цифр во внутреннем представлении вещественной константы от количества цифр в ее записи.

/* 02_05.c - конечная точность констант double */ #include <stdio.h>

int main ()

{

printf

("The value of 123456789.987654321 equals %f\n", 123456789.987654321);

printf

("The value of 123456789.987654321 equals %e\n", 123456789.987654321);

return 0;

}

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

The value of 123456789.987654321 equals 123456789.987654

The value of 123456789.987654321 equals 1.234568e+08

39

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

ЗАДАНИЕ. Измените тип констант в предыдущей программе, добавив к ним суффикс f.

/* 02_05_1.c – конечная точность констант float */ #include <stdio.h>

int main ()

{

printf

("The value of 123456789.987654321f equals %f\n", 123456789.987654321f);

printf

("The value of 123456789.987654321f equals %e\n", 123456789.987654321f);

return 0;

}

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

The value of 123456789.987654321f equals 123456792.000000

The value of 123456789.987654321f equals 1.234568e+08

Как и следовало ожидать, результаты в первой строке стали менее точными. Значение, выведенное по спецификации %e, не изменилось, тут проявилось не влияние конечной точности внутреннего представления чисел, а особенность спецификации %e, когда в ее записи опущены сведения о ширине поля и точности представления выводимого числа (см. гл. 7 в [3]).

ЗАДАНИЕ. Модифицируйте предыдущую программу, введя ширину поля и точность в спецификациях %f и %e.

/* 02_05_2.c - точность констант типа float */ #include <stdio.h>

int main ()

{

40