Реферат Курсовая Конспект
Структура программы на языке Си. Этапы выполнения программы - раздел Информатика, Структура Программы На Язык...
|
Структура программы на языке Си. Этапы выполнения программы
Лексемы
Из символов алфавита формируются лексемы языка – минимальные значимые единицы текста в программе:
- идентификаторы;
- ключевые (зарезервированные) слова;
- знаки операций;
- константы;
- разделители (скобки, точка, запятая, пробельные символы).
Границы лексем определяются другими лексемами, такими, как разделители или знаки операций, а также комментариями.
Знаки операций
Знак операции – это один или более символов, определяющих действие над операндами (например, ">=", означающий условие "больше или равно). Внутри знака операции пробелы не допускаются. Операции делятся на унарные, бинарные и тернарные операции, по количеству участвующих в них операндов (1, 2 или 3).
Литералы (константы)
Когда в программе встречается некоторое число, например 1, то это число называется литералом или литеральной константой. Константой, потому что мы не можем изменить его значение, и литералом, потому что буквально передает свое значение (от латинского literal – буквальный).
Литерал является неадресуемой величиной: хотя реально он, конечно, хранится в памяти машины, нет никакого способа узнать его адрес. Каждый литерал имеет определенный тип.
Комментарии
Еще один базовый элемент языка программирования – комментарий, – не является лексемой. Внутри комментария можно использовать любые допустимые на данном компьютере символы, а не только символы из алфавита языка программирования, поскольку компилятор комментарии игнорирует.
В Си комментарии ограничиваются парами символов /* и */, а в С++ был введен вариант комментария, который начинается символами // и заканчивается символом перехода на новую строку.
Ошибки
Ошибки, допускаемые при написании программ, разделяют на синтаксические и логические.
Синтаксические ошибки - нарушение формальных правил написания программы на конкретном языке, обнаруживаются на этапе трансляции и могут быть легко исправлены.
Программа, содержащая синтаксическую ошибку, не может быть запущена. При попытке ее компиляции выдается сообщение, обычно содержащее указание того места в тексте, "дочитав" до которого, компилятор заметил ошибку; сама ошибка может быть как в этом месте, так и выше него (часто - в предыдущей строке).
Логические ошибки делят на ошибки алгоритма и семантические ошибки - могут быть найдены и исправлены только разработчиком программы.
Причина ошибки алгоритма - несоответствие построенного алгоритма ходу получения конечного результата сформулированной задачи.
Причина семантической ошибки - неправильное понимание смысла (семантики) операторов языка.
Программа, содержащая логическую ошибку, может быть запущена. Однако она либо выдает неверный результат, либо даже завершается "аварийно" из-за попытки выполнить недопустимую операцию (например, деление на 0) - в таком случае выдается сообщение об ошибке времени выполнения. Поиск места в программе, содержащего логическую ошибку, является непростой задачей; он носит название отладки программы.
Переменные и константы. Типы данных
Для программиста на языке Си память компьютера представляется как набор ячеек, каждая из которых называется переменной, или константой, в зависимости от того, меняется ее значение в процессе работы или нет. Каждая переменная имеет имя (идентификатор, ID). Константа может иметь или не иметь имени.
Род информации, которую способна хранить ячейка, определяется ее типом.
Рассмотренные выше константы не имеют имен. Но иногда одну и ту же постоянную (т.е. не меняющуюся в ходе работы программы) величину приходится использовать в тексте программы многократно. Тогда может быть удобно назвать ее именем, чтобы в дальнейшем обращаться к ней по этому имени. Значение же ее будет указываться лишь в одном месте - при ее объявлении, и для его изменения необходимо лишь единственное исправление в тексте программы.
Объявление такой именованной константы пишется так же, как и объявление переменной, но перед ее типом указывается слово const :
const double pi=3.14159
Далее ее можно использовать в выражениях, указывая ее имя, например:
double x=2*pi;
Таким образом, именованная константа выглядит, как переменная, но ее значение нельзя менять в процессе работы программы. Зато ее можно использовать там, где разрешается использовать только константы. В языке С++ в сложных программах, разбитых на модули, употребление именованных констант часто считается предпочтительнее, чем директива #define.
Обзор операций
Операции, выражения
Операции языка Си предназначены для управления данными (более 40). Для их использования необходимо знать:
- синтаксис;
- приоритеты (15 уровней);
- порядок выполнения.
Выражения состоят из операндов, операций, скобок и используются для вычисления некоторого значения определенного типа. Каждый операнд может быть, в свою очередь, выражением или одним из его частных случаев.
Операции, применяемые к одному операнду, - унарные, к двум операндам – бинарные, есть операция с тремя операндами - тернарная. Операции выполняются в соответствии с приоритетами. Для изменения порядка выполнения операций используются круглые скобки.
Большинство операций выполняются слева направо, например, a+b+c ® (a+b)+c. Исключение: унарные операции, операции присваивания и условная операция (?:) - справа налево.
Полный список операций в соответствии с их приоритетом приводится ниже, в § 4.7.
Рассмотрим кратко основные операции языка Си.
Арифметические операции
Арифметические операции - бинарные. Перечень арифметических операций и их обозначений:
+ - сложение;
- - вычитание (либо унарная операция - изменение знака);
/ - деление (для int операндов - с отбрасыванием остатка);
* - умножение;
% - остаток от деления целочисленных операндов, со знаком первого операнда (деление по модулю).
Операндами традиционных арифметических операций (+ - * /) могут быть константы, переменные, элементы массивов, любые арифметические выражения.
Порядок выполнения операций:
- выражения в круглых скобках;
- функции (стандартные математические, функции пользователя);
- * / выполняются слева направо;
- + ─ слева направо.
Порядок выполнения операций можно определять круглыми скобками, тогда выражение в скобках выполняется в первую очередь (слева направо).
Унарные операции + и – обладают самым высоким приоритетом, определены только для целых и вещественных операндов, «+» носит только информационный характер, «–» меняет знак значения операнда на противоположный (не адресная операция).
Таким образом, так как операции *, /, % обладают высшим приоритетом над операциями +, -, при записи сложных выражений нужно использовать общепринятые математические правила:
x+y*z-a/b Û x+(y*z)-(a/b)
Операции сравнения
== - равно или эквивалентно;
!= - не равно;
< - меньше;
<= - меньше либо равно;
> - больше;
>= - больше либо равно.
Пары символов соответствующих операций разделять нельзя.
Общий вид операций отношений:
<выражение1> <знак_операции> <выражение2>
Общие правила:
- операндами могут быть любые базовые (скалярные) типы;
- значения операндов перед сравнением преобразуются к одному типу;
- результат операции отношения - целое значение 1, если отношение истинно, или 0 в противном случае. Следовательно, операция отношения может использоваться в любых арифметических выражениях.
Логические операции
Перечень логических операций в порядке убывания относительного приоритета и их обозначения:
! - отрицание (логическое НЕТ);
&& - коньюнкция (логическое И);
|| - дизьюнкция (логическое ИЛИ).
Общий вид операции отрицания:
!<выражение>
Общий вид операций коньюнкции и дизьюнкции
<выражение1> <знак_операции> <выражение2>
Например:
y>0 && x==7 ® истина, если 1-е и 2-е выражения истинны;
e>0 || x==7 ® истина, если хотя бы одно выражение истинно.
Ненулевое значение операнда трактуется как "истина", а нулевое - "ложь".
Например:
!0 ® 1
!5 ® 0
x=10; y=10;
!((x==y)>0) ® 0
Пример правильной записи двойного неравенства:
0<x<100 → (0<x)&&(x<100)
Особенность операций коньюнкции и дизьюнкции – экономное последовательное вычисление выражений-операндов:
<выражение1> <операция><выражение2>,
- если выражение1 операции коньюнкция ложно, то результат операции - ноль и выражение2 может не вычисляться;
- если выражение1 операции дизьюнкция истинно, то результат операции - единица и выражение2 может не вычисляться.
Таким образом, появляется возможность записью логического выражения задать условную последовательность вычисления выражений в направлении слева направо:
scanf("%d",&i) && test1(i) && test2(i) ® нулевой результат одной из функций может привести к игнорированию вызова остальных;
search1(x) || search2(x) || search3(x) ® только ненулевой результат одной из функций может привести к игнорированию вызова остальных.
Действительно ли "ненужные" функции не будут вызываться - зависит от настроек компилятора.
Операции (продолжение).
Сокращенная запись операции присваивания
В языке Си используются два вида сокращенной записи операции присваивания:
1) вместо записи:
v = v # e;
где # – любая арифметическая или битовая операция, рекомендуется использовать запись v #= e;
Например,
i = i + 2; « i += 2; (+= – без пробелов);
2) вместо записи:
x = x # 1;
где # означает + либо - , x – переменная одного из целочисленных типов (или переменная-указатель), рекомендуется использовать запись:
##x; – префиксную, или x##; – постфиксную.
Если эти операции используются в отдельном виде, то различий между постфиксной и префиксной формами нет. Если же они используются в выражении, то в префиксной форме (##x), сначала значение x изменится на 1, а затем будет использовано в выражении; в постфиксной форме (x##) – сначала значение используется в выражении, а затем изменяется на 1. (Операции над указателями будут рассмотрены позже.)
Рекомендации использования сокращений обоснованы возможностью оптимизации программы (ускорение в работе программы), т.к. схема выражения вида v #= e соответствует схеме выполнения многих машинных команд типа "регистр-память".
Преобразование типов операндов
Ввод-вывод в оконных приложениях.
Для ввода-вывода в оконных приложениях C++ Builder'a используются различные компоненты (Memo, Edit, Label и др.) Например, ввод-вывод в Edit осуществляется обычно через операцию присваивания:
Edit1->Text=IntToStr(k);
X:=StrToFloat(Edit2->Text);
а вывод в Memo - через метод Add() :
Memo1->Lines->Add("x="+FloatToStrF(x, ffFixed, 15, 7));
При этом обычно используются функции преобразования типов C++ Builder'a:
Функция | Направление преобразования | Пример |
StrToInt() | String à int | k=StrToInt (Edit1->Text); |
IntToStr() | int à String | Edit1->Text= IntToStr(k); |
StrToFloat() | String à double | x= StrToFloat (Edit1->Text); |
FloatToStr() | double à String | Edit1->Text= FloatToStr(x); |
FloatToStrF() | double à String | Edit1->Text=FloatToStrF(x, ffFixed, 15, 8); |
В функции FloatToStrF(), в отличие от функции FloatToStr(), задается максимальное общее количество цифр в числе (которое поэтому лучше задавать побольше, например, 15) и количество цифр после запятой. Второй параметр может принимать лишь одно из нескольких допустимых значений, имеющих смысл, подобный спецификаторам формата printf() (ffFixed - %f, ffExponent - %e, ffGeneral - %g и т.п.).
В русской версии Windows в функциях StrToFloat(), FloatToStr(), FloatToStrF() дробная часть числа отделяется от целой запятой; а в английской версии Windows - точкой.
Операторы языка С.
Основной частью любой программы являются операторы, то есть инструкции (действия), выполняемые машиной во время работы программы. В конце каждого оператора ставится точка с запятой.
Простые операторы:
- пустой оператор «;» ;
- оператор присваивания - выполнение операций присваивания;
- оператор вызова функции - выполнение операции вызова функции.
Примеры простых операторов:
;
b=3;
a=b+7;
printf("%lfn", a);
Если в программе не указано иное, то операторы выполняются по очереди, один за другим, сверху вниз по тексту программы. Но существуют операторы, меняющие последовательность выполнения других операторов. К ним относятся условные операторы, операторы цикла и перехода.
Составление разветвляющихся алгоритмов
Условные операторы
Условный оператор ifиспользуется для разветвления процесса выполнения кода программы на два направления.
В языке Си имеется две формы условного оператора: простая и полная. Синтаксис простой формы:
if (выражение) оператор;
выражение – логическое или арифметическое выражение, вычисляемое перед проверкой, и, если выражение истинно (не равно нулю), то выполняется оператор, иначе он игнорируется; оператор – любой оператор языка Си. Если в случае истинности выражения необходимо выполнить несколько операторов (более одного), их необходимо заключить в фигурные скобки. (Группа операторов, заключенная в фигурные скобки, называется составным оператором или блоком).
Примеры записи:
if (x > 0) x = 0;
if (i != 1) j++, s = 1; – используем операцию «запятая»;
if (i != 1)
{
j++; s = 1; – последовательность операций (блок);
}
if (getch() != 27) k = 0; – если нажата любая клавиша, кроме “Esc”.
if (!x) exit (1); или так : if (x == 0) exit(1);
if ( i>0 && i<n) k++; – если нужно проверить несколько условий, то их объединяют знаками логических операций.
Синтаксис полной формы условного оператора:
if (выражение) оператор 1 ;
else оператор 2 ;
Если выражение не равно нулю (истина), то выполняется оператор 1, иначе – оператор 2. Операторы 1 и 2 могут быть простыми или составными (блоками).
Наличие символа «;» перед словом else в языке Си обязательно.
Примеры записи:
if (x > 0) j = k+10;
else m = i+10;
if ( x > 0 && k !=0 )
{
j = x/k;
x += 10;
}
else m = k*i + 10;
Операторы 1 и 2 могут быть любыми операторами, в том числе и условными. Тогда, если есть вложенная последовательность операторов if – else, то слово else связывается с ближайшим к ней предыдущим if, не содержащим ветвь else. Например:
if (n > 0)
if(a > b) z = a;
else z = b;
Здесь ветвь else связана со вторым if (a > b). Если же необходимо связать слово else с внешним if, то используются операторные скобки:
if (n > 0) {
if (a > b) z = a;
}
else z = b;
В следующей цепочке операторов if – else – if выражения просматриваются последовательно:
if (выражение 1) оператор 1;
else
if (выражение 2) оператор 2;
else
if (выражение 3) оператор 3;
else оператор4 ;
Если какое-то выражение оказывается истинным, то выполняется относящийся к нему оператор и этим вся цепочка заканчивается. Каждый оператор может быть либо отдельным оператором, либо группой операторов в фигурных скобках. Оператор 4 будет выполняться только тогда, когда ни одно из проверяемых условий не выполняется. Иногда при этом не нужно предпринимать никаких явных действий, тогда последний else может быть опущен, или его можно использовать для контроля, чтобы зафиксировать "невозможное" условие (своеобразная экономия на проверке условий).
Пример:
if (x < 0) printf("n X отрицательное n");
else if(x==0) printf ("n X равно нулю n");
else prinf("n X положительное n");
Замечание. Наиболее распространенной ошибкой при создании условных операторов является использование в выражении операции присваивания «=» вместо операции сравнения на равенство операндов «==» (два знака равно). Например, в следующем операторе синтаксической ошибки нет:
if (x = 5) a++;
но значение а будет увеличено на единицу независимо от значения переменной х, т.к. результатом операции присваивания х = 5 в круглых скобках является значение 5¹0 – истина.
Оператор выбора альтернатив (переключатель)
Оператор switch (переключатель) предназначен для разветвления процесса вычислений на несколько направлений.
Общий вид оператора:
switch ( выражение )
{
caseконстанта1: список операторов 1
caseконстанта2: список операторов 2
...
caseконстантаN: список операторов N
default: список операторов N+1 – необязательная ветвь;
}
Выполнение оператора начинается с вычисления выражения, значение которого должно быть целого или символьного типов. Это значение сравнивается со значениями констант, и используется для выбора ветви, которую нужно выполнить.
В данной конструкции константы фактически выполняют роль меток. Если значение выражения совпало с одной из перечисленных констант, то управление передается в соответствующую ветвь.
Все константы должны иметь разные значения, но быть одного и того же типа. Несколько меток могут следовать подряд, и тогда переход в указанную ветвь будет происходить при совпадении хотя бы одной из них. Порядок следования ветвей не регламентируется.
В случае несовпадения значения выражения ни с одной из констант выбора происходит переход на метку default, либо, при ее отсутствии, к оператору, следующему за оператором switch.
Управляющий оператор break (разрыв) выполняет выход из оператора switch. Если в некоторой ветви выход из переключателя явно не указан, то после ее выполнения начнут последовательно выполняться последующие ветви. Поэтому, если по совпадению с каждой константой должна быть выполнена одна и только одна ветвь, схема оператора switch следующая:
switch (выражение)
{
case константа1: операторы 1; break;
case константа2: операторы 2; break;
...
case константаN: операторы N; break;
default: операторы (N+1);
}
Структурная схема рассмотренной конструкции (с использованием оператора break) приведена на рисунке.
Пример оператора switch с использованием оператора break:
void main(void)
{
int i = 2;
switch(i)
{
case 1: puts ( "Случай 1. "); break;
case 2: puts ( "Случай 2. "); break;
case 3: puts ( "Случай 3. "); break;
default: puts ( "Случай default. "); break;
}
}
Для того чтобы выйти из оператора switch в любом месте использовали оператор break, поэтому результатом данной программы будет:
Случай 2.
void main()
{
int i = 2;
switch(i)
{
case 1: puts ( "Случай 1. ");
case 2: puts ( "Случай 2. ");
case 3: puts ( "Случай 3. ");
default: puts ( "Случай default. ");
}
}
Так как оператор разрыва отсутствует, результат в данном случае:
Случай 2.
Случай 3.
Случай default.
Пример реализации простейшего калькулятора на четыре действия с контролем правильности ввода символа нужной операции. Ввод данных осуществляется следующим образом: операнд 1, символ нужной операции, операнд 2. Вывод – а, символ операции, b, = , вычисленное значение.
#include <stdio.h>
void main(void)
{
double a, b, c;
char s;
m1: fflush(stdin); // Очистка буфера ввода stdin
printf("n Введите операнд 1, символ операции, операнд 2:");
scanf("%lf%c%lf", &a, &s, &b);
switch(s) {
case '+': c = a+b; break;
case '–': c = a–b; break;
case '*': c = a*b; break;
case '/': c = a/b; break;
default: printf("n Ошибка, повторите ввод!”); goto m1;
}
printf("n a %c b = %lf", s, c);
printf("n Продолжим? (Y/y) ");
s = getch();
if ( (s=='Y') || (s=='y') ) goto m1;
printf("n Good bye! ");
}
После запуска программы на экран выводится подсказка, нужно набрать соответствующие значения без пробелов, например, как показано ниже, и нажать клавишу Enter:
Введите операнд 1, символ операции, операнд 2: 2.4+3.6
На экран будет выведен результат и дальнейший диалог:
a + b = 6.000000
Продолжим? (Y/y)
Введя символ y (Y), вернемся в начало функции и на экране вновь:
Введите операнд 1, символ операции, операнд 2:
Если ошибочно ввести – 2r3 ,появятся следующие сообщения:
Ошибка, повторите ввод!
Введите операнд 1, символ операции, операнд 2:
2 * 3
a*b = 6.000000
Continue? (Y/y)
Нажимаем любую клавишу, кроме y или Y, получим сообщение
Good bye!
Программа закончена.
7.3. Условная операция «? :»
Если одно и то же выражение (или переменная) вычисляется по-разному в зависимости от некоторого условия, вместо оператора if можно использовать более короткую запись - условную операцию. Эта операция – тернарная, т.е. в ней участвуют три операнда. Формат написания условной операции следующий:
Выражение1 ? выражение2: выражение3;
если выражение 1 (условие) отлично от нуля (истинно), то результатом операции является значение выражения 2, в противном случае – значение выражения 3. Каждый раз вычисляется только одно из выражений 2 или 3.
На рисунке приведена схема вычисления результата, которая аналогична схеме полного оператора if (см. рисунок):
Рассмотрим участок программы для нахождения максимального значения z из двух чисел a и b, используя оператор if и условную операцию.
1. Запишем оператор if :
if (a > b) z = a;
else z = b;
2. Используя условную операцию:
z = a > b ? a : b;
Условную операцию можно использовать так же, как и любое другое выражение. Если выражения 2 и 3 имеют разные типы, то тип результата определяется по правилам преобразования. Например, если f имеет тип double, а n – int, то результатом операции:
(n > 0) ? f : n;
по правилам преобразования типов будет double, независимо от того, положительно n или нет.
Оператор с предусловием while
Цикл с предусловием реализует структурную схему, приведенную на рис. 8.1а и имеет вид:
while (выражение)
код цикла;
Выражение определяет условие повторения кода цикла, представленного простым или составным оператором.
Если выражение в скобках – истина (не равно 0), то выполняется код цикла. Это повторяется до тех пор, пока выражение не примет значение 0 (ложь). В этом случае происходит выход из цикла и выполняется оператор, следующий за конструкцией while. Если выражение в скобках изначально ложно (т.е. равно 0), то цикл не выполнится ни разу.
Код цикла может включать любое количество операторов, связанных с конструкцией while, которые нужно заключить в фигурные скобки (организовать блок), если их более одного.
Переменные, изменяющиеся в коде цикла и используемые при проверке условия продолжения, называются параметрами цикла. Целочисленные параметры цикла, изменяющиеся с постоянным шагом на каждой итерации, называются счетчиками цикла.
Начальные установки могут явно не присутствовать в программе, их смысл состоит в том, чтобы до входа в цикл задать значения переменным, которые в этом цикле используются.
Рис. 8.1
Цикл завершается, если условие его продолжения не выполняется. Возможно принудительное завершение, как текущей итерации, так и цикла в целом.
Для этого используют оператор continue – переход к следующей итерации цикла и break – выход из цикла.
Передавать управление извне внутрь цикла не рекомендуется, так как получите непредсказуемый результат.
Например, необходимо сосчитать количество символов в строке. Предполагается, что входной поток настроен на начало строки. Тогда подсчет символов выполняется следующим образом:
int count = 0;
char ch;
scanf("%c", &ch);
while ( ch != n') {
count++;
scanf("%c", &ch);
}
8.2. Оператор цикла с постусловием do – while
Цикл с постусловием реализует структурную схему, приведенную на рис. 8.1 б.
Общий вид записи такой конструкции:
do
код цикла;
while (выражение);
Код цикла будет выполняться до тех пор, пока выражение истинно. Все, что говорилось выше справедливо и здесь, за исключением того, что данный цикл всегда выполняется хотя бы один раз, даже если изначально выражение ложно.
Здесь сначала выполняется код цикла, после чего проверяется, надо ли его выполнять еще раз.
Следующая программа будет вас приветствовать до тех пор, пока будем вводить символ Y или y (Yes). После введения любого другого символа цикл завершит свою работу.
#include <stdio.h>
void main(void)
{
char answer;
do {
puts(" Hello! => ");
scanf(“%c”, &answer);
fflush(stdin);
}
while ((answer=='y')||(answer=='Y'));
}
Результат выполнения программы:
Hello! => Y
Hello! => y
Hello! => d
Оператор цикла с предусловием и коррекцией for
Цикл реализует структурную схему, приведенную на рис. 8.2 (такой цикл еще называют циклом с параметром).
Рис. 8.2
Общий вид оператора:
for (выражение 1; выражение 2; выражение 3)
код цикла;
где выражение 1 – инициализация счетчика (параметр цикла);
выражение 2 – условие продолжения счета;
выражение 3 – коррекция счетчика.
Инициализация используется для присвоения счетчику (параметру цикла) начального значения.
Выражение 2 определяет условие выполнения цикла. Как и в предыдущих случаях, если его результат не нулевой («истина») то цикл выполняется.
Коррекция выполняется после каждой итерации цикла и служит для изменения параметра цикла.
Выражения 1,2 и 3 могут отсутствовать (пустые выражения), но символы «;» опускать нельзя.
Например, для суммирования первых N натуральных чисел можно записать такой код:
sum = 0;
for ( i = 1; i<=N; i++) sum+=i;
Заметим, что в выражении 1 переменную-счетчик можно декларировать:
for (int i = 1; i<=N; i++)
Областью действия такой переменной будет код цикла. Но в некоторых версиях компиляторов такие действия могут интерпретироваться иначе.
Цикл for эквивалентен последовательности инструкций:
выражение 1;
while (выражение 2) {
...
выражение 3;
}
а оператор
for (; выражение 2; )
код цикла;
эквивалентен оператору
while (выражение 2)
код цикла;
Если пропущено выражение 2, то цикл будет выполняться бесконечное количество раз, поскольку пустое условие всегда остается истинным. Бесконечный оператор:
for ( ; ; ) код цикла;
эквивалентен оператору
while (1) код цикла;
В циклической структуре for может использоваться операция «запятая». Она позволяет включать в его выражения несколько операторов. Тогда рассмотренный пример суммирования первых N натуральных чисел можно записать в следующем виде:
for ( sum = 0 , i = 1; i<=N; sum+= i , i++) ;
Оператор for имеет следующие возможности:
– можно вести подсчет с помощью символов, а не только чисел:
for (ch = 'a'; ch <= 'z'; ch++) ... ;
– можно проверить выполнение некоторого произвольного условия:
for (n = 0; s[i] >= '0' && s[i] < '9'; i++) ... ;
или:
for (n = 1; n*n*n <= 216; n++) ... ;
Первое выражение не обязательно должно инициализировать переменную. Необходимо только помнить, что первое выражение вычисляется только один раз перед тем, как остальные части начнут выполняться.
num=0;
for(printf(" вводить числа по порядку! n"); num!=6;)
scanf("%d", & num);
printf(" последнее число – это то, что нужно. n");
В этом фрагменте первое сообщение выводится на печать один раз, а затем осуществляется прием вводимых чисел, пока не поступит число 6.
Переменные, входящие в выражения 2 и 3 можно изменять при выполнении кода цикла, например, значения k и delta:
for (n = 1; n < 10*k; n += delta) k=delta--;
Использование условных выражений позволяет во многих случаях значительно упростить программу, например:
for (i = 0; i<n; i++)
printf("%6d%c", a[i], (i%10==0 || i==n–1) ? 'n' : ’ ‘);
В этом цикле печатаются n элементов массива а по 10 в строке, разделяя каждый столбец одним пробелом и заканчивая каждую строку (включая последнюю) одним символом перевода строки. Символ перевода строки записывается поле каждого десятого и n-го элементов. За всеми остальными – пробел.
Наиболее часто встречающиеся ошибки при создании циклов – это использование в коде цикла неинициализированных переменных и неверная запись условия выхода из цикла.
Чтобы избежать ошибок, нужно стараться:
– проверить, всем ли переменным, встречающимся в правой части операторов присваивания в коде цикла, присвоены до этого начальные значения (а также, возможно ли выполнение других операторов);
– проверить, изменяется ли в цикле хотя бы одна переменная, входящая в условие выхода из цикла;
– предусмотреть аварийный выход из цикла по достижению некоторого количества итераций;
– если в состав цикла входит не один, а несколько операторов, нужно заключать их в фигурные скобки.
Операторы передачи управления.
Формально к операторам передачи управления относятся:
– оператор безусловного перехода goto;
– оператор перехода к следующему шагу (итерации) цикла continue;
– выход из цикла, либо из оператора switch – break;
– оператор возврата из функции return.
Оператор безусловного перехода goto
В языке Си предусмотрен оператор goto, общий вид которого:
goto метка ;
Он предназначен для передачи управления оператору, помеченному указанной меткой. Метка представляет собой идентификатор с символом «двоеточие» после него, например, пустой помеченный меткой m1 оператор:
m1: ;
Циклы и переключатели можно вкладывать вдруг в друга, и наиболее характерный оправданный случай использования оператора goto – выполнение прерывания (организация выхода) из вложенных циклов. Например, при обнаружении грубых смысловых ошибок необходимо выйти из двух (или более) вложенных циклов (где нельзя использовать непосредственно оператор break, т.к. он прерывает только самый внутренний цикл):
for (...)
for (...) {
...
if (ошибка) goto error;
}
...
error: операторы для устранения ошибки;
Второй оправданный случай: организация переходов из нескольких мест программы в одно, например, когда перед завершением работы функции необходимо сделать одну и ту же операцию.
Не рекомендуется, однако, совершать с помощью оператора goto переходы внутрь цикла из-за его пределов:
goto HHH;
do{
i++;
HHH:
}while(i<5);
а также делать переходы, обходящие объявление переменных:
goto YYY;
int k;
YYY:
т.к. компилятор не всегда способен правильно "понять" такую программу, что может привести к ошибкам как во время компиляции, так и во время выполнения.
Оператор goto не способен осуществлять переход из одной функции в другую.
Заметим также, что в некоторых организациях существует отрицательное отношение к оператору goto вообще. Причины этого и других подобных ограничений будут рассмотрены в теме "Стиль программирования".
Операторы continue, break и return
В определенных случаях вместо оператора goto удобнее использовать операторы continue или break.
Оператор continue может использоваться во всех типах циклов (но не в операторе-переключателе switch). Наличие оператора continue вызывает пропуск "оставшейся" части итерации и переход к началу следующей, т.е. досрочное завершение текущего шага и переход к следующему шагу.
В циклах while и do-while это означает непосредственный переход к проверочной части. В цикле for управление передается на шаг коррекции, т.е. модификации выражения 3.
Оператор continue часто используется, когда последующая часть цикла оказывается слишком сложной, так что рассмотрение условия, обратного проверяемому, приводит к слишком высокому уровню вложенности программы.
Оператор break производит досрочный выход из цикла или оператора-переключателя switch, к которым он принадлежит, и передает управление первому оператору, следующему за текущим оператором. Т.е. break обеспечивает переход в точку кода программы, находящуюся за оператором, внутри которого он (break) находится.
Оператор return производит выход из текущей функции и будет рассмотрен позже.
Примеры алгоритмов, использующих одномерные массивы.
А) Простейший пример
Задача 1. Ввести с клавиатуры массив, а затем вывести только его положительные элементы:
#include<iostream.h>
#include<conio.h>
#include<stdio.h>
void main()
{
int a[100],n,i;
M: cout<<"Vvedite n<=100:";
cin>>n;
if(n>100) goto M;
cout<<"Vvedite massiv:n";
for (i=0; i<n; i++)
cin>>a[i];
cout<<"Polozhitelnye elementy: ";
for (i=0; i<n; i++)
if (a[i]>0)
cout<<a[i]<<" ";
cout<<endl;
getch();
}
Г) Проверка условия, относящегося к массиву.
Задача 11. Проверить, все ли элементы массива меньше 5.
Для этого достаточно подсчитать количество элементов,не меньших 5:
for (i=s=0; i<n; i++)
if (a[i]>=5)
s++;
if (s)
cout<<"Ne vse elementy men'she 5 !";
Заметим, что s++ можно заменить на s=1, т.к. конкретное количество элементов здесь несущественно.
Таким образом, для проверки условия, относящегося к массиву в целом, обычно нужна дополнительная переменная и цикл.
Примеры алгоритмов, использующих двумерные массивы.
А) Простейшие примеры
Задача 1. Ввести матрицу и увеличить все ее элементы на единицу.
#include<iostream.h>
#include<conio.h>
#include<stdio.h>
void main() {
int a[10][10],n,m,i,j;
// Ввод матрицы
cout<<"Vvedite n,m <=10:";
cin>>n>>m;
cout<<"Vvedite massiv:n";
for (i=0; i<n; i++)
for (j=0; j<m; j++)
cin>>a[i][j];
// Увеличение на 1
for (i=0; i<n; i++)
for (j=0; j<m; j++)
a[i][j]++;
// Вывод матрицы
puts("Result:");
for (i=0; i<n; i++)
for (j=0; j<m; j++)
printf("%3d%c", a[i][j], j==m-1? 'n' : ' ');
getch();
}
Задача 2. Найти в матрице наибольший элемент и его позицию.
max=a[0][0];
im=jm=0;
for (i=0; i<n; i++)
for (j=0; j<m; j++)
if (a[i][j]>max) {
max=a[i][j];
im=i;
jm=j;
}
printf("Max element a[%d][%d]=%dn", im, jm, max);
Задача 3. Переписать матрицу в одномерный массив.
int b[100];
for (i=k=0; i<n; i++)
for (j=0; j<m; j++)
b[k++]=a[i][j];
После выполнения этого участка программы k=n*m -количество элементов в полученном массиве.
Размещение данных и программ в памяти ПЭВМ
Стандартная часть таблицы символов (ASCII)
КС | С | КС | С | КС | С | КС | С | КС | С | КС | С | КС | С | КС | С |
► | @ | P | ` | p | |||||||||||
☺ | ◄ | ! | A | Q | a | q | |||||||||
☻ | ↕ | " | B | R | b | r | |||||||||
♥ | ‼ | # | C | S | c | s | |||||||||
♦ | ¶ | $ | D | T | d | t | |||||||||
♣ | § | % | E | U | e | u | |||||||||
♠ | ▬ | & | F | V | f | v | |||||||||
• | ↨ | ' | G | W | g | w | |||||||||
◘ | ↑ | ( | H | X | h | x | |||||||||
○ | ↓ | ) | I | Y | i | y | |||||||||
◙ | → | * | : | J | Z | j | z | ||||||||
♂ | ← | + | ; | K | [ | k | { | ||||||||
♀ | ∟ | , | < | L | l | | | |||||||||
♪ | ↔ | - | = | M | ] | m | } | ||||||||
♫ | ▲ | . | > | N | ^ | n | ~ | ||||||||
☼ | ▼ | / | ? | O | _ | o | ⌂ |
Некоторые из вышеперечисленных символов имеют особый смысл. Так, например, символ с кодом 9 обозначает символ горизонтальной табуляции, символ с кодом 10 – символ перевода строки, символ с кодом 13 – символ возврата каретки.
Дополнительная часть таблицы символов
КС | С | КС | С | КС | С | КС | С | КС | С | КС | С | КС | С | КС | С |
А | Р | а | ░ | └ | ╨ | р | Ё | ||||||||
Б | С | б | ▒ | ┴ | ╤ | с | ё | ||||||||
В | Т | в | ▓ | ┬ | ╥ | т | Є | ||||||||
Г | У | г | │ | ├ | ╙ | у | є | ||||||||
Д | Ф | д | ┤ | ─ | ╘ | ф | Ї | ||||||||
Е | Х | е | ╡ | ┼ | ╒ | х | ї | ||||||||
Ж | Ц | ж | ╢ | ╞ | ╓ | ц | Ў | ||||||||
З | Ч | з | ╖ | ╟ | ╫ | ч | ў | ||||||||
И | Ш | и | ╕ | ╚ | ╪ | ш | ° | ||||||||
Й | Щ | й | ╣ | ╔ | ┘ | щ | ∙ | ||||||||
К | Ъ | к | ║ | ╩ | ┌ | ъ | · | ||||||||
Л | Ы | л | ╗ | ╦ | █ | ы | √ | ||||||||
М | Ь | м | ╝ | ╠ | ▄ | ь | № | ||||||||
Н | Э | н | ╜ | ═ | ▌ | э | ¤ | ||||||||
О | Ю | о | ╛ | ╬ | ▐ | ю | ■ | ||||||||
П | Я | п | ┐ | ╧ | ▀ | я |
В таблицах обозначение КС означает "код символа", а С – "символ".
Тип char рассматривается компилятором как "целочисленный", поэтому возможно использование signed char (по умолчанию) - коды символов от -128 до +127 и unsigned char - коды символов от 0 до 255. При этом коды символов первой половины кодовой таблицы для signed char и unsigned char совпадают. Коды символов второй половины кодовой таблицы для signed char являются отрицательными, а для unsigned char - положительными, от 128 до 255 (см. выше кодировку целых отрицательных чисел).
Таким образом, одни и те же значения типа char могут рассматриваться и как символы, и как числа (знаковые либо беззнаковые); при этом конкретный смысл значения определяется операцией, которая над ним совершается.
Заметим, что коды однотипных символов (цифр; больших латинских букв; малых латинских букв) идут подряд, в порядке возрастания. Этим можно воспользоваться для упрощения многих действий.
Пример 1. Проверить, является ли символ цифрой. Если да, то присвоить переменной целого типа значение этой цифры.
char c; int i;
...
if (c>='0' && c<='9')
i=c-'0'; // Равносильно i=c-48;
Пример 2. Занести в переменную случайно выбранную большую букву латинского алфавита.
#include<stdlib.h>
...
char c;
randomize(); // Инициализация счетчика случайных чисел
...
c=random(26)+'A'; // 26 - число латинских букв в алфавите
Напомним, что символьная константа – это символ, заключенный в одинарные кавычки: 'A', 'х'.
Побитовые логические операции. Операции над битами
В СИ предусмотрен набор операций для работы с отдельными битами слов. Эти операции нельзя применять к переменным вещественного типа (float, double).
Перечень операций над битами и их обозначения:
~ - инвертирование (унарная операция);
& - побитовое И - конъюнкция;
| - побитовое включающее ИЛИ - дизъюнкция;
^ - побитовое исключающее ИЛИ - сложение по модулю 2;
>> - сдвиг вправо;
<< - сдвиг влево.
Пары символов (>>,<<) разделять нельзя.
Общий вид операции инвертирования:
~<выражение>
Остальные операции над битами имеют вид:
<выражение1> <знак_операции> <выражение2>
Операндами операций над битами могут быть только выражения, приводимые к целому типу. Операнды представляются в двоичной форме, и операции выполняются поразрядно, т.е. над каждым битом (для операций &, |, ^ - над каждой парой соответствующих бит, взятых от первого и второго операндов) (знаковый разряд особо не выделяется).
Операция ~ означает замену значения бита на противоположное (т.е 0 на 1 , а 1 на 0).
Операция & дает 1, если оба участвующих в ней бита равны 1; иначе дает 0.
Операция | дает 1, если хотя бы один из участвующих в ней битов равен 1; иначе дает 0.
Операция ^ дает 1, если один из участвующих в ней битов равен 1, а другой 0; иначе (т.е. если оба бита одинаковы) дает 0.
Примеры:
~0xF0 Û 0x0F
0xFF & 0x0F Û 0x0F
0xF0 | 0x11 Û 0xF1
0xF4 ^ 0xF5 Û 0x01
Операция & часто используется для маскирования (выделения) некоторого множества битов. Например, оператор w = n & 0177 передает в w семь младших битов n, полагая остальные равными нулю.
Необходимо отличать побитовые операции & и | от логических операций && и ||, которые подразумевают вычисление значения истинности. Если x=1, y=2, то x & y равно нулю, а x && y равно 1.
0x81<<1 Û 0x02
0x81>>1 Û 0x40
Унарная операция (~) дает дополнение к целому. Это означает, что каждый бит со значением 1 получает значение 0 и наоборот. Эта операция обычно оказывается полезной в выражениях типа:
X & (~)077,
где последние 6 битов X маскируются нулем. Это выражение не зависит от длины слова и поэтому предпочтительнее, чем, например:
X & 0177700,
где предполагается, что X занимает 16 битов, такая переносимая форма не требует никаких дополнительных затрат.
Операции сдвига << и >> осуществляют соответственно сдвиг вправо (влево) своего левого операнда, на число битовых позиций, задаваемых правым операндом. Таким образом, x<<2 сдвигает x влево на две позиции, заполняя освобождающиеся биты нулями, что эквивалентно умножению на 4.
Операции сдвига выполняются для всех разрядов с потерей выходящих за границы битов. Если выражение1 имеет тип unsigned, то при сдвиге вправо освобождающиеся разряды гарантированно заполняются нулями (логический сдвиг). Выражения типа signed могут, но не обязательно, сдвигаться вправо с копированием знакового разряда (арифметический сдвиг). При сдвиге влево освобождающиеся разряды всегда заполняются нулями. Выражение2 не должно быть отрицательно.
Операции сдвига вправо на k разрядов весьма эффективны для деления, а сдвиг влево - для умножения целых чисел на 2 в степени k:
x<<1 « x*2
x>>1 « x/2
x<<3 « x*8
Подобное применение операций сдвига безопасно для беззнаковых и положительных значений выражения1.
Двуместные операции над битами (&, |, ^, <<, >>) могут использоваться в сокращенных формах записи операции присваивания:
int i,j,k;
. . .
i |= j « i = i | j - включение в поле i единиц из поля j;
i &= 0xFF « i = i & 0xFF - выделение в поле i единиц по маске поля 0x00FF;
k ^= j - выделение в поле k отличающихся разрядов в полях k и j;
i ^= i - обнуление всех разрядов поля i .
Операции над битами реализуются, как правило, одной машинной командой и рекомендуются для использования во всех подходящих случаях.
В математическом смысле операнды логических операций над битами можно рассматривать как отображение некоторых множеств с размерностью не более разрядности операнда на значения {0,1}.
Пусть единица означает обладание элемента множества некоторым свойством, тогда очевидна теоретико-множественная интерпретация рассматриваемых операций:
~ - дополнение; | - объединение; & - пересечение.
Простейшее применение - проверка нечетности целого числа:
int i;
...
if (i &1) printf(" Значение i нечетно!");
Комбинирование операций над битами с арифметическими операциями часто позволяет упростить выражения. Например, получение размера области в блоках размером 16 байт для размещения объекта размером x байт:
(x + 15)>>4
Другие возможности оперирования над битами:
- использование структур с битовыми полями;
- доступ к битам как разрядам арифметических данных.
Строки как нуль-терминированные массивы char.
А) Основные понятия.
Один из способов организовать работу со строками - это использование одномерных массивов типа char. Тогда строка символов – это одномерный массив типа char, заканчивающийся нулевым байтом.
Нулевой байт – это байт, каждый бит которого равен нулю, при этом для нулевого байта определена символьная константа ´