Раздел 2. Структура программы на языке C

Раздел 6. Основные синтаксические конструкции языка C

Заголовок функции и прототип функции

Любая программная единица на языках C, C++ оформляется как функция, причем в отличие от языка Pascal функции не могут быть вложены друг в друга. Поэтому функция представляется как некий кирпичик, который может быть размещен в любом месте программы. А вся программа состоит из последовательности таких кирпичиков, среди которых обязательно присутствует главный – функция с именем main.

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

trv namef(type1 par1,type2 par2,...)

Здесь trv – тип возвращаемого значения;

namef – имя функции;

par1 – имя первого аргумента функции, имеющего тип type1;

par2 – имя второго аргумента функции, имеющего тип type2;

………………………………………………………….

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

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

· по значению (в заголовке функции вслед за типом параметра располагается его имя);

· по указателю (в заголовке функции имени параметра предшествует символ *);

· по ссылке (в заголовке функции имени параметра предшествует символ &).

Пример 1. Функции передаются два значения, по которым она находит и возвращает среднее арифметическое.

double mid(doublex,doubley)

{ return (x+y)/2.0; }

Пример 2. Функции передаются два параметра по указателю. Функция меняет местами значения переданных ей переменных.

void swap(int*x,int*y)

{ int tmp;

tmp=*x; *x=*y; *y=tmp;

}

Пример 3. Функции передаются два параметра по ссылке. Функция меняет местами значения переданных ей переменных.

void swap(float&x,float&y)

{ float tmp;

tmp=x; x=y; y=tmp;

}

По сути дела, два последние способа передачи параметров одинаковы – и в том, и в другом случае функции сообщают адрес расположения параметра в оперативной памяти. По этому адресу функция может извлечь текущее значение параметра или записать на его место новое значение. Разница только в форме обращения по полученному адресу. Ссылки делают это обращение более простым, к имени параметра не надо ничего добавлять. При работе с указателями выборка и запись по указанному адресу сопровождается дополнительным символом *.

Возврат значения функции

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

8.11. "Левые" функции

В документации по системам программирования и в сообщениях об ошибках иногда можно встретить термины lvalue и rvalue. Они обозначают, соответственно, величины, которые могут находиться слева (lvalue = left value) от знака равенства в операторе присваивания или справа (rvalue = right value).

Как правило, функции, возвращающие значение, используются в правой части оператора присваивания. Однако функции в качестве своего значения могут возвращать указатели и ссылки. А по указателям и ссылкам возможна запись. Именно такие функции называют "левыми".

Приведем в качестве примера функцию, возвращающую ссылку на максимум из двух своих аргументов:

double& max(double &x, double &y)

{ return (x>y)? x : y; }

Ее обычное использование:

double r=max(a,b);

Использование с учетом "левизны":

double a=5,b=6;

max(a,b)=10; //эквивалентно b=10;

Аналогичный вариант, когда функция max возвращает указатель:

double* max(double *x, double *y)

{ return (*x>*y)?*x:*y; }

.........................

double a=5,b=6;

*max(&a,&b)=10; //эквивалентно b=10;

Левая функция, возвращающая ссылку на максимальный элемент массива:

int& Mmax(int a[],int n)

{ int im=0; //индекс максимального элемента

for(int j=1;j<n;j++) im=(a[im>a[j])? im : j;

return a[im];

}

Левая функция, возвращающая указатель на максимальный элемент массива:

int* Mmax(int a[],int n)

{ int im=0; //индекс максимального элемента

for(int j=1;j<n;j++) im=(a[im>a[j])? im : j;

return &a[im];

}

Для запрета левого использования функции, возвращающей указатель или ссылку, достаточно разместить в начале ее заголовка спецификатор const:

const double& max(double &x, double &y)

{ return (x>y)? x : y); }

Оформление и вызов программных единиц в системе Turbo C

Все программные единицы в Си носят название функций и разницу в оформлении настоящих подпрограмм и настоящих функций можно обнаружить по типу возвращаемого значения. Если в качестве указания типа использовано служебное слово void, то перед нами типичная подпрограмма (в терминах Си – функция, не возвращающая значение). Перед заголовком объявляемой функции всегда присутствует либо стандартный тип, либо описатель типа с последующей звездочкой. Последнее означает что функция возвращает указатель на данное соответствующего типа. В частности, допускается, что таковым указателем может быть "void *" В самом общем виде заголовок функции выглядит следующим образом:

void имя_функции([параметры])

или

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

или

тип * имя_функции([параметры])

Тело функции, расположенное после заголовка, заключается в фигурные скобки. Если функция возвращает значение, то в ее теле должен присутствовать хотя бы один оператор return c указанием возвращаемого значения. Например:

int sign(int x)

{

/* Определение знака целого числа */

if(x<0) return -1;

if(x>0) return 1;

return 0;

}

В отличие от Бейсика и Паскаля функции Си, не имеющие параметров, всегда сопровождаются пустыми круглыми скобками. Например – clrscr().

Головная программа на Си представлена функцией main, которая может иметь до трех параметров, связанных с извлечением аргументов из командной строки. Чаще всего эта функция не имеет параметров и не возвращает значение:

void main(void)

К функции-подпрограмме в Си обращаются, указав ее имя со списком фактических параметров:

имя_функции(фактические параметры);

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

int qq;

......

qq=getch(); /*ожидание ввода кода символа с клавиатуры*/

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

sin(0.5);

Системная функция в данном случае проработает, но эффекта от такого вызова никто не заметит. Разве что немного увеличится время работы программы. А в случае с функцией getch игнорирование результата позволяет зафиксировать момент, когда пользователь нажал какую-то клавишу.

Оформление и вызов программных единиц в системе Turbo Pascal

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

procedure имя_процедуры[(параметры)];

Тело процедуры на Паскале в точности повторяет структуру головной программы. В нем могут присутствовать разделы описания меток (label), констант (const), типов данных (type), переменных (var) и других процедур и функций, входящих в состав данной процедуры. Наличие вложенных процедур и функций отличает Паскаль и от Си, и от Бейсика. Собственно вычислительная часть тела процедуры начинается со служебного слова begin и заканчивается соответствующим end, после которого в отличие от головной программы следует не точка, а точка с запятой.

Фрагмент программы от заголовка процедуры до завершающей операторной скобки end принято называть блоком. Для Паскаля характерна возможность использования вложенных блоков и с каждым из них можно связать натуральное число, соответствующее уровню вложения. Так, например, в блок A могут быть вложены два последовательных блока первого уровня - блоки B и C. Если же блок C вложен в блок B, входящий, в свою очередь в блок A, то блок C уже имеет уровень два по отношению к блоку A. В теле любого блока могут содержаться обращения к вложенным блокам своего первого уровня или к последовательным блокам такого же уровня. Блок не имеет права напрямую обратиться к своим процедурам второго или более высокого уровня вложенности.

Если процедура A обращается к процедуре B, то описание процедуры B должно предшествовать описанию процедуры A. Однако, если программа включает рекурсивную цепочку из двух или более процедур, одна из них должна объявляться (но не описываться) первой с добавкой forward. Соответствующее объявление носит название опережающего (forward – впереди бегущий футболист, нападающий) :

procedure имя_процедуры[(параметры)]; forward;

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

Для обращения к процедуре достаточно задать ее имя с последующим набором фактических параметров.

Описание функции отличается только тем, что ее заголовок начинается со служебного слова function и заканчивается указанием типа функции.

function имя_функции[(параметры)]:тип;

Тело функции отличается от тела подпрограммы только наличием оператора присваивания:

имя_функции:=выражение;

Досрочный выход из тела функции или тела процедуры осуществляется по оператору exit.

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

program имя_программы;

Собственно тело головной программы начинается с операторной скобки begin, расположенной вслед за последним объявлением, и завершается закрывающей операторной скобкой end с последующей точкой.

Оформление модулей на Паскале

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

Unit имя_модуля;

Если в качестве имени паскалевской программы может выступать любой идентификатор, в том числе и достаточно длинный, то именем модуля может быть только имя файла, допустимое в операционной системе MS-DOS. Это связано с тем, что при некоторых обстоятельствах (например, если пользователь вместо режима Compile выбрал режим Make или Build) система программирования Turbo Pascal вынуждена заново оттранслировать пользовательские модули. И тогда она пытается открыть дисковые файлы, имена которых совпадают с именами обновляемых модулей.

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

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

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

Unit Calendar

interface

type

Days=(Mon,Tue,Wed,Thu,Fri,Sat,Sun);

WorkingDays=Mon..Fri;

Months=(Jan,Feb,Mar,Apr,May,June,July,Aug,Sept,Oct,Nov,Decem);

Summer=June..Aug;

Autumn=Sep..Nov;

Spring=Mar..May;

DayNo=1..31;

YearNo=1900..2119;

Date=record

Day:DayNo;

Month:Months;

Year:YearNo;

end;

implementation

end.

Модуль, содержащий только инициирующую часть, может произвести какие-то первоначальные действия – открыть временный файл, проверить принтер и т.п.

Билет 2

1. Данные целого типа. Операции над целочисленными операндами. Ввод и вывод целочисленных данных. В чем сходство и в чем различия между Си и Паскалем.

Форматный ввод

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

#include <stdio.h>

int main()

{ int i;

float f;

double d;

..........

scanf("%d %f %lf",&i,&f,&d);

Строка вводимых данных поступает со стандартного устройства ввода (stdin), которым по умолчанию считается клавиатура. Завершение набора строки ввода – нажатие клавиши Enter.

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

В приведенном примере переменной i (в списке ввода указан ее адрес – &i), объявленной с помощью спецификатора типа int, соответствует форматный указатель %d. Это означает, что первым числовым значением в строке ввода может быть только целое десятичное число со знаком (d – от decimal, десятичный). Вещественной переменной f типа float в форматной строке соответствует указатель %f. Это означает, что второе числовое значение в строке ввода должно принадлежать диапазону, предусмотренному для коротких вещественных данных. Для переменной d типа double использован форматный указатель %lf (l – от long).

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

scanf("%d %*l %lf",&i,&d);

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

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

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

%[*][ширина][{l|h|L}]{d|i|u|o|x|X|f|e|E|g|G}

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

Символ * после начального символа является указанием о пропуске соответствующего значения из строки ввода. Необязательное, и, как правило, не используемое при вводе, поле ширина задает количество символов во вводимом значении. Дополнительные признаки l, h и L уточняют длину машинного формата соответствующей переменной (l, L – long; h – short). Значение последнего обязательного символа форматного указателя расшифровано в табл. 4.3.

Таблица 4.3

Символ формата Допустимое значение в строке ввода
d целое десятичное число со знаком
i целое число
u целое число без знака
o целое восьмеричное число без знака
x,X целое шестнадцатеричное число без знака
f вещественное число
e,E вещественное число
g,G вещественное число

Форматный ввод также как и потоковый не позволяет вводить числовые значения в переменные типа char. Дело в том, что минимальная длина числового значения вводимого с помощью функции scanf – 2 байта. И значение введенного старшего байта затирает в памяти еще один байт вслед за переменной типа char. Заметить такую ошибку удается не каждому, но на работу программы такой ввод может повлиять довольно серьезно. Поэтому возьмите за правило – в однобайтовые переменные типа char числовую информацию вводить нельзя.

Вывод числовых результатов

Форматный вывод

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

#include <stdio.h>

int main()

{ int i;

float f;

double d;

..........

printf("%d %f %lf", i+1, f, f*d);

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

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

%[флажки][ширина][.точность][{l|h|L}]{d|i|u|o|x|X|f|e|E|g|G}

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

Поле флажков может содержать до четырех управляющих символов из набора [–,+,пробел,0,#]. Символ "минус" устанавливает прижим выводимого значения к левой границе выделенного поля (по умолчанию действует правый прижим). Символ "плюс" устанавливает обязательный режим вывода знака числа (даже если оно положительно). Символ "пробел" устанавливает такой режим, при котором вместо знака "+" выводится пробел. Символ "нуль" устанавливает режим вывода чисел, при котором старшие незначащие позиции поля вывода заполняются нулями.

Символ # влияет на формат вывода восьмеричных, шестнадцатеричных и вещественных чисел. При его использовании перед восьмеричными числами выводится лидирующий нуль, перед шестнадцатеричными числами – префикс 0x или 0X. Для форматов f, e, и E обязательно отображается десятичная точка после целой части (по умолчанию, числа, у которых дробная часть равна 0, выводятся без десятичной точки). Для форматов g и G не удаляются лидирующие нули и всегда выводится десятичная точка.

.

Необязательное поле точность задает для целочисленных значений обязательное количество цифр (если число меньше, то перед его старшим разрядом добавляется необходимое количество нулей). Например, вывод целочисленного значения –5 по формату %8.4d выделяет в строке вывода поле из восьми символов, на котором первые три позиции заполнены пробелами, а следующие пять символами –0005.

Для данных вещественного типа поле точность определяет количество цифр в дробной части числа. Не забывайте о количестве значащих цифр, хранение которых обеспечивает тот или иной тип данных. Например, если переменной типа float присвоить значение 3.14159265, то на выводе по формату %10.8f мы увидим результат 3.14159274. Двум последним цифрам результата доверять нельзя, т.к. формат float обеспечивает хранение 7-8 десятичных цифр.

Разница между форматами %0x и %0X заключается в том, что в первом случае шестнадцатеричная запись числа формируется из малых букв [a,b,c,d,e,f], а во втором случае – из больших букв [A,B,C,D,E,F].

Для вывода однобайтовых целочисленных данных со знаком (типа char) можно пользоваться одним из следующих форматов – %o, %0x, %0X, %i, %d, %ho, %hx, %hX, %hi, %hd. Для вывода однобайтовых целых без знака наряду с перечисленными указателями допустим формат %u. Однако следует иметь ввиду, что однобайтовые значения расширяются до двухбайтовых, сохраняя знак числа. Поэтому попытка вывести однобайтовое значение 127 по формату %u приведет к правильному результату. Но если мы по такому же формату выдадим число -5, то результатом будет число 65531 (дополнение до максимального двухбайтового числа).

Вывод числовых результатов вещественного типа предусматривает две формы отображения – с фиксированной запятой (форматные указатели %f и %lf) или с плавающей запятой (форматные указатели %e и %E). Форматные указатели %g и %G предлагают системе самой выбрать один из этих форматов, который окажется более компактным для выводимого значения. Большая или маленькая буква в форматных указателях с плавающей запятой приводит к тому, что порядку числа предшествует большая или маленькая буква "e".

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

x1=127; x2=-350;

printf("x1=%d x2=%d",x1,x2);

на экране дисплея появится строка:

x1=127 x2=-350

Если мы хотим расположить выводимую информацию на экране дисплея с начала следующей строки, то в начале форматных указателей обычно вставляют управляющий символ "\n":

printf("\nx1=%d x2=%d),x1,x2);

Такой же управляющий символ может быть включен в середину или в конец форматной строки. В языках C, C++ с помощью символа "\" в форматную строку могут быть включены и другие управляющие символы (так называемые Escape-последовательности), список которых приведен в табл. 4.6.

Потоковый вывод

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

#include <iostream.h>

int main()

{ int i;

float f;

double d;

..........

cout << i+1 << f << f*d;

Здесь на первых порах не надо заботиться о форматах выводимых результатов. Для каждого типа данных существуют соответствующие системные соглашения. Переход в начало следующей строки здесь осуществляется путем включения в список вывода либо упомянутого выше управляющего символа '\n', либо аналогичного ему признака конца строки endl:

cout << i+1 << f << f*d << endl;

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

cout << "x1="<< x1 << "x2=" << x2 << '\n';

В потоковом выводе тоже имеются средства управления форматом вывода числовых результатов. Однако научиться пользоваться ими почти так же сложно, как и овладеть нюансами работы с форматными указателями. Дело в том, что потоки вывода в языке C++ организованы двумя способами – старым и новым. Поэтому в управлении потоком вывода можно встретить различные синтаксические конструкции – традиционные функции (hex – установка режима вывода шестнадцатеричного числа, setw – установка ширины поля вывода, setprecision – установка количества отображаемых цифр и т.п.) и более современные обращения к методам класса (cout.fill(…), cout.width(...)). Некоторые из установленных режимов действуют только на вывод следующего значения, другие сохраняют свое действие до следующей установки. С некоторыми деталями управления форматами числовых данных в потоковом выводе можно познакомиться на примере 4 из следующего параграфа.

В паскале

Цикл for существует в двух формах:

for счетчик:=значение to конечное_значение do

тело_цикла;

for счетчик:=значение downto конечное_значение do

тело_цикла;

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

Количество итераций цикла for известно именно до его выполнения, но не до выполнения всей программы. Так в примере ниже, количество выполнений цикла определяется пользователем. Значение присваивается переменной, а затем используется в заголовке цикла. Но когда оно используется, циклу уже точно известно, сколько раз надо выполниться.

var

i, n: integer;

begin

write ('Количество знаков: ');

readln (n);

for i := 1 to n do

write ('(*) ');

readln

end.

Билет 3

Паскаль

while(a>=c) do

begin

...

end;

Билет 4

Форматный ввод

Список форматных указателей функции scanf предусматривает возможность ввода значений односимвольных (%c) и многосимвольных (%s) переменных:

#include <stdio.h>

void main()

{ char ch1,ch2;

char str1[10];

scanf("%c %c",&ch1,&ch2);

scanf("%s",str1);

....................

Обратите внимание на то, что для ввода данных в скалярные переменные ch1 и ch2 в списке ввода необходимо указывать их адреса (&ch1, &ch2), а при вводе в массив str1 – достаточно написать его имя. Дело в том, что имя массива одновременно выполняет роль адреса своего первого элемента. Поэтому str1 и &str1[0] указывают на один и тот же адрес.

Ввод значений символьных переменных ch1 и ch2 можно организовать одним из двух способов. Во-первых, в строке ввода можно набрать два требуемых символа либо слитно, либо разделяя их хотя бы одним пробелом и нажать клавишу Enter. Во-вторых, можно набрать первый символ, предназначенный для переменной ch1, и нажать клавишу Enter. Затем повторить аналогичным образом ввод следующего символа.

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

Довольно неожиданно, но с помощью задания ширины поля ввода и одного форматного указателя %с можно за один прием ввести несколько символов в элементы символьного массива:

char q[20];

............

scanf("%20c",q); //ввод 20 символов в массив q

В отличие от ввода по форматному указателю %s в массив q здесь не записывается признак конца строки – вводятся только запрашиваемые символы, которые набираются в одной строке с последующим нажатием клавиши Enter.

Функция scanf предусматривает еще два интересных форматных указателя, которые обеспечивают ввод либо тех символов, которые указаны в заданном множестве, либо всех символов, которые не принадлежат заданному множеству. Например, для ввода цифр из диапазона от 0 до 9 такой указатель имеет вид %[0-9], а для ввода символов, не являющихся цифрами – %[^0-9]. Рассмотрим следующий пример, который не только демонстрирует ввод по таким форматным указателям, но и содержит непредвиденный пассаж, связанный с использованием "грязного" буфера ввода.

#include <stdio.h>

#include <conio.h>

void main()

{ char str[10];

int j;

printf("Enter 123ABC\n");

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

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

for(j=0; j<10; j++)

printf("%3x",str[j]);

printf("\nEnter DEF123\n");

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

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

for(j=0; j<10; j++)

printf("%3x",str[j]);

getch();

}

//=== Результат работы ===

Enter 123ABC

str=123

31 32 33 0 0 1 0 0 1 0

Enter DEF123

str=ABC

DEF

41 42 43 a 44 45 46 0 1 0

После ввода первой строки программе были переданы символы '123', вслед за которыми в массив str был записан нулевой байт – признак конца строки. Однако в буфере ввода остались невостребованными символы 'ABC' и код клавиши Enter (код 0xa). После набора второй строки к содержимому буфера ввода добавились еще три символа 'DEF' и т.к. все семь первых символов не являются кодами цифр, то все они были переданы в массив str. При выводе первые три отобразились в одной строке, затем сработал управляющий код 0xa и три следующие символа были выведены в следующей строке. Для наглядности содержимое массива после каждого вывода по формату %s отображалось еще и в шестнадцатеричном формате. Значения байтов, начиная с str[4], объясняются тем, что массив str является локальным, и под него выделяется "грязная память". Если его описание вынести за пределы main, то он станет глобальным и будет расписан нулями.

Чтобы не пострадать от непредвиденного ввода символов, задержавшихся в буфере ввода, рекомендуется после обращения к функции scanf чистить буфер ввода с помощью функции fflush(stdin). Если бы мы включили в предыдущий пример обращение к функции fflush, то после ввода второй строки в массиве str оказались бы только символы 'DEF' .

Потоковый ввод

Потоковый ввод в символьные и "строковые" переменные организуется следующим образом:

#include <iostream.h>

void main()

{ char ch1,ch2;

char str1[10];

cin >> ch1 >> ch2;

cin >> str1;

....................

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

Вывод текстовых данных

При выводе текстовых данных особые проблемы возникают только в том случае, когда сообщения, содержащие русские буквы, готовятся в среде Windows (кодовая страница 1251), а выводятся консольным приложением в 866-й кодовой странице. В этом случае можно написать сравнительно несложную функцию конвертирования текстов из одной кодировки в другую. В кодовой странице 1251 буквы русского алфавита кодируются подряд, начиная с кода 192 (большая буква 'А') до кода 255 (малая буква 'я'). Буквы 'Ё' и 'ё' имеют коды 164 и 184 соответственно. Поэтому при перекодировке необходимо:

· коды букв, принадлежащие интервалу [192, 239] уменьшить на 64, чтобы вогнать их в интервал [128, 174];

· коды букв, принадлежащие интервалу [240, 255] уменьшить на 16, чтобы вогнать их в интервал [224, 239];

· коды букв Ё и ё заменить на 240 и 241 соответственно.

#include <stdio.h>

#include <iostream.h>

#include <conio.h>

#include <string.h>

char *to_866(unsigned char *s)

{ static unsigned char str[80];

int j=0;

while (s[j]!='\0')

{ str[j]=s[j];

if(s[j]>=192 && s[j]<=239) str[j]-=64;

if(s[j]>=240 && s[j]<=255) str[j]-=16;

if(s[j]==164) str[j]=240;

if(s[j]==184) str[j]=241;

j++;

}

str[j]='\0';

return str;

}

void main()

{ char s[]="Привет;

cout << s << endl;

cout <<to_866(s) << endl;

getch();

}

//=== Результат работы ===

Раздел 2. Структура программы на языке C - student2.ru

В первой строке вывод реализован без перекодировки, а во второй – с перекодировкой.

Форматный вывод

Для форматного вывода символьных значений в функции printf используется форматный указатель %c, а для вывода строк – форматный указатель %s. При создании консольных приложений Windows можно воспользоваться программой перекодировки, аналогичной функции to_866.

#include <stdio.h>

#include <conio.h>

void main()

{

char ch1='F';

unsigned char ch2='5';

char ch3[]="ABCD";

printf("%c %c %s",ch1,ch2,ch3);

getch();

}

//=== Результат работы ===

F 5 ABCD

Потоковый вывод

В потоковом выводе единственная проблема может возникнуть в связи с перекодировкой русских сообщений в консольном приложении Windows.

#include <iostream.h>

#include <conio.h>

void main()

{

char ch1='F';

unsigned char ch2='5';

char ch3[]="ABCD";

cout<<ch1<<' '<<ch2<<' '<<ch3;

getch();

}

//=== Результат работы ===

F 5 ABCD

Паскаль

repeat

...

ubtil(a<>b);

Билет 5

Объявление указателей

В языках C, C++ различают три категории указателей. Первая категория указателей предназначена для хранения адресов данных определенного типа (по терминологии языка Паскаль – типизированные указатели). При их объявлении указывается тип данных, на которые эти указатели могут "смотреть". Ко второй категории относятся указатели, которые могут "смотреть" на данные любого типа (по терминологии языка Паскаль – не типизированные указатели). При их объявлении используется служебное слово void. Наконец, третью группу составляют указатели, значениями которых могут быть только адреса точек входа в функции (по терминологии языка Паскаль – данные процедурного типа). Объявление и использование указателей разных категорий имеет свою специфику.

Для объявления одного указателя с именем p1 или нескольких указателей с именами p1, p2, …, которые должны будут "смотреть" на объекты типа type1, используется одна из следующих синтаксических конструкций:

type1 *p1;

type1 *p1,*p2,...;

Объявленные указатели еще никуда конкретно не смотрят – в выделенных им участках памяти находится "грязь". И одна из типичных ошибок начинающих программистов – попытка записать что-либо по указателям, которым еще не присвоены значения. Инициализацию указателя можно совместить с его объявлением:

int x=2,y;

int *p1=&x; //инициализация адресом переменной x

int *p2(&x); //инициализация адресом переменной x

int *p3=p1; //инициализация значением другого указателя

Указатель является переменной и его значение можно задать или изменить с помощью оператора присваивания:

p1=&y; //теперь значением p1 является адрес переменной y

Если целочисленному указателю p1 присваивается имя массива a или его адрес, то это эквивалентно засылке в p1 адреса первого элемента массива a[0]:

int a[10];

int *p1=a; //p1 смотрит на начало массива a

int *p2=&a[0]; //p2 тоже смотрит на начало массивa a

int *p3=(int *)&a; //p3 тоже смотрит на начало массивa a

Когда указатель p1 "смотрит" на переменную x, то по значению указателя можно извлечь значение переменной x или изменить его:

int x=5,y;

int *p1=&x; //значением p1 является адрес x

..........

y=*p1; //теперь значение переменной y равно 5

*p1=2; //теперь значение переменной x равно 2

Когда указатель p2 "смотрит" на начало массива q, то доступ к элементам этого массива можно организовать одним из следующих способов:

int q[20];

int *p2=q;

...........

y=*(p2+5); //теперь y=q[5]

x=p2[3]; //теперь x=q[3]

*(p2+1)=7; //теперь q[1]=7

В программах на языке C можно встретить нагромождение символов "*". Пугаться не надо – это просто многоступенчатая адресация:

int x,y;

int *p1=&y;

int **p2=&p1;

x=**p2; //то же, что x=*(*p2)=*(p1)=y

Объявление не типизированного указателя выглядит следующим образом:

void *pu;

<

Наши рекомендации