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

Общая форма оператора for следующая:

for (инициализация; условие; приращение) оператор;

Инициализация – это присваивание начального значения переменной, которая называется параметром цикла. Условие представляет собой условное выражение, определяющее, следует ли выполнять оператор цикла в очередной раз. Оператор приращение осуществляет изменение параметра цикла при каждой итерации. Эти три оператора (они называются также секциями оператора for) обязательно разделяются точкой с запятой. Цикл for выполняется, если выражение условие принимает значение истина. Если оно хотя бы один раз примет значение ложь, то программа выходит из цикла и выполняется оператор, следующий за телом цикла for.

В следующем примере в цикле for выводятся на экран числа от 1 до 100:

#include

int main(void)

{

int x;

for(x=1; x<=100; x++) printf(“%d”,x); return 0; } В этом примере параметр цикла x инициализирован числом 1, а затем при каждой итерации сравнивается с числом 100. Пока переменная х меньше 100, вызывается функция printf() и цикл повторяется. При этом х увеличивается на 1 и опять проверяется условие цикла x<=100. Процесс повторяется, пока переменная х не станет больше 100. После этого процесс выходит из цикла, а управление передается оператору, следующему за ним. В этом примере параметром цикла является переменная х, при каждой итерации она изменяется и проверяется в секции условия цикла. В операторе for условие цикла всегда проверяется перед началом итерации. Это значит, что операторы цикла могут не выполняться ни разу, если перед первой итерацией условие примет значение ложь. 35. Операторы цикла: цикл с предусловием. Общая форма цикла while имеет следующий вид: while (условие) оператор; Здесь оператор (тело цикла) может быть пустым оператором, единственным оператором или блоком. Условие (управляющее выражение) может быть любым допустимым в языке выражением. Условие считается истинным, если значение выражения не равно нулю, а оператор выполняется, если условие принимает значение истина. Если условие принимает значение ложь, программа выходит из цикла и выполняется следующий за циклом оператор. В следующем примере ввод с клавиатуры происходит до тех пор, пока пользователь не введет символ А: char wait_for_char(void) { char ch; ch=’\0’; while (ch!=’A’) ch=getchar(); return ch; } Переменная сh является локальной, ее значение при входе в функцию произвольно, поэтому сначала значение ch инициализируется нулем. Условие цикла while истинно, если ch не равно А. Поскольку ch инициализировано нулем, условие истинно и цикл начинает выполняться. Условие проверяется при каждом нажатии клавиши пользователем. При вводе символа А условие становится ложным и выполнение цикла прекращается. В цикле while условие проверяется перед началом итерации. Это значит, что если условие ложно, тело цикла не будет выполнено. 36. Операторы цикла. Цикл с постусловием. В отличие от циклов for и while, которые проверяют свое условие перед итерацией, do-while делает это после нее. Поэтому цикл do-while всегда выполняется как минимум один раз. Общая форма цикла do-while следующая: do { оператор; } while (условие); Если оператор не является блоком, фигурные скобки не обязательно, но их почти всегда ставят, чтобы оператор достаточно наглядно отделялся от условия. Итерации оператора do-while выполняются, пока условие не примет значение ложь. В примере в цикле do-while числа считываются с клавиатуры, пока не встретится число, меньшее или равное 100: do { scanf(“%d”, &num); }while(num>100);

37. Операторы прерывания и продолжения цикла. Вложенные циклы.
Оператор break приеняется в двух случаях. Во-первых, в операторе switch с его помощью прерывается выполнение последовательности case. В этом случае оператор break не передает управление за пределы блока. Во-вторых, оператор break используется для немедленного прекращения выполнения цикла без проверки его условия, в этом случае оператор break передает управление оператору, следующему после оператора цикла.

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

Оператор break часто используется в циклах, в которых некоторое событие должно вызвать немедленное прекращение выполнения цикла. Например, нажатие клавиши прекращает определенное событие.

Если оператор break присутствует внутри оператора switch, который вложен в какие-либо циклы, то break относится только к switch, выход из цикла не происходит.

Оператор продолжения цикла: continue.

Его действие заключается в прерывании выполнения тела цикла. После этого вызывается следующая итерация (шаг) этого цикла.

Пр: Фрагмент программы вывода четных чисел до 100.

for(i=0; i<100; i++) {if(i%2) continue; printf(“%d\n”,i); } Если значение i – четно, то остаток от деления на 2 будет равен нулю и, следовательно, результат проверки условия в операторе if – ложь, и будет выполнен оператор печати. В противном случае, результат проверки условия – истина, и, следовательно, будет выполнен оператор продолжения. В этом случае мы, минуя оператор печати, сразу переходим к следующей итерации (следующему значению i). В операторах цикла очень удобно использовать оператор разрыва для перехода к следующему оператору программы. Существует возможность организовать цикл внутри тела другого цикла. Такой цикл будет называться вложенным циклом. Вложенный цикл по отношению к циклу, в тело которого он вложен, будет именоваться внутренним циклом, и наоборот, цикл, в теле которого существует вложенный цикл, будет именоваться внешним по отношению к вложенному. Внутри вложенного цикла, в свою очередь, может быть вложен еще один цикл. Количество уровней вложенности, как правило, не ограничивается. Полное число исполнений тела внутреннего цикла не превышает произведения числа итераций внутреннего и всех внешних циклов. Например, взяв три вложенных друг в друга цикла, каждый по 10 итераций, получим 10 исполнений тела для внешнего цикла, 100 для цикла второго уровня и 1000 в самом внутреннем цикле. Одна из проблем, связанных с вложенными циклами – организация досрочного выхода из них. В Си есть оператор досрочного завершения цикла – break – но он, как правило, обеспечивает выход только из цикла того уровня, откуда вызван. Вызов его из вложенного цикла приведет к завершению только этого внутреннего цикла, объемлющий же цикл продолжит выполняться. Однако, эту проблему можно решить – использовать оператор безусловного перехода goto для выхода в точку программы, непосредственно следующую за циклом. 38. Одномерные и многомерные массивы, их инициализация. Массив – это набор переменных одного типа, имеющих одно и то же имя. Доступ к конкретному элементу массива осуществляется с помощью индекса. В языке С все массивы располагаются в отдельной непрерывной области памяти. Первый элемент массива располагается по самому меньшему адресу, а последний – по самому большому. Массивы могут быть одномерными и многомерными. Общая форма объявления одномерного массива имеет следующий вид: тип имя_переменной [размер]; Как и другие переменные, массив д.б. объявлен явно, чтобы компилятор выделил для него определенную область памяти. Здесь тип обозначает базовый тип массива, являющийся типом каждого элемента. Размер задает количество элементов массива. Например, следующий оператор объявляет массив из 100 элементов типа double под именем balance: double balance[100]; Доступ к элементу массива осуществляется с помощью имени массива и индекса. Индекс элемента массива помещается в квадратных скобках после имени. Например, оператор balance[3]=12.23; присваивает 3-му элементу массива balance значение 12.23. Индекс первого элемента любого массива в языке С равнее нулю. Поэтому оператор char p[10]; объявляет массив из 10 элементов – от p[0] до p[9]. Объем памяти, необходимый для хранения массива, непосредственно определяется его типом и размером. Во время выполнения программы на С не проверяется ни соблюдение границ массивов, ни их содержимое. В область памяти, занятую массивом, может быть записано что угодно. Программист должен сам, где это необходимо, ввести проверку границ индексов. Можно сказать, что одномерный массив – это список, хранящийся в непрерывной области памяти в порядке индексации. Пр. так хранится в памяти массив а, начинающийся по адресу 1000 и объявленный как char a[7]; Элемент a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] Адрес 1000 1001 1002 1003 1004 1005 1006 1007 Стандартом С определены многомерные массивы. Простейшая форма многомерного массива – двухмерный массив. Двухмерный массив – это массив одномерных массивов. Объявление двухмерного массива d с размерами 10 на 20 выглядит следующим образом: int d [10][20]; Аналогично обращению к элементу одномерного массива, обращение к элементу с индексами 1 и 2 двухмерного массива d выглядит так: d[1][2]. Двухмерные массивы размещаются в матрице, состоящей из строк и столбцов. Первый индекс указывает номер строки, а второй – номер столбца. В языке С можно использовать массивы, размерность которых больше двух. Общая форма объявления многомерного массива: тип имя_массива [Размер1][Размер2]…[РазмерN]; Массивы, у которых число измерений больше трех, используются довольно редко, потому что они занимают большой объем памяти. При обращении к многомерным массивам компьютер много времени затрачивает на вычисление адреса, так как при этом приходится учитывать значение каждого индекса. Поэтому доступ к элементам многомерного массива происходит значительно медленнее, чем к элементам одномерного. В языке С массивы при объявлении можно инициализировать. Общая форма инициализации массива аналогична инициализации переменной: тип имя_массива[размер1]…[размерN]={список_значений}; Список_значений представляет собой список констант, разделенных запятыми. Типы констант должны быть совместимы с типом массива. Первая константа присваивается первому элементу массива, вторая – второму и так далее. После закрывающейся фигурной скобки точка с запятой обязательна. int i[10]={1,2,3,4,5,6,7,8,9,10}; Здесь элементу i[0] присваивается 1, а i[9] – 10. Символьные массивы, содержащие строки, можно инициализировать строковыми константами: char имя_массива[размер]=”строка’; Массив str инициализируется фразой «Язык С»: char str[9]= «Язык С»; Это объявление можно записать так: char str[9]={‘Я’, ‘з’, ‘ы’, ‘к’, ‘ ‘, ‘C’,’\0’}; Строка кончается нулевым символом, поэтому при объявлении необходимо задавать размер массива, достаточный для того, чтобы этот символ поместился в нем. Многомерные массивы инициализируются так же, как и одномерные. Инициализация безразмерных массивов Предположим, что необходимо создать таблицу сообщений об ошибках, используя инициализацию массивов: char e1[12]=”Ошибка чтения\n”; char e2[13]=”Ошибка записи\n”; char e3[18]=”Нельзя открыть файл\n”; Для задания размера массива пришлось бы вручную подсчитывать количество символов в каждом сообщении. Однако в языке С есть конструкция, благодаря которой компилятор автоматически определят длину строки. Если в операторе инициализации массива не указан размер массива, компилятор создает массив такого размера, что в нем умещаются все инициализирующие элементы. Таким образом создается безразмерный массив. Используя этот метод, предыдущий пример можно записать так: char e1[]=”Ошибка чтения\n”; char e2[]=”Ошибка записи\n”; char e3[]=”Нельзя открыть файл\n”; Кроме уменьшения трудоемкости, инициализация безразмерных массивов полезна тем, что позволяет изменять длину любого сообщения, не заботясь о соблюдении границ массивов. 39. Указатели. Связь между указателями и массивами. Указатель – это переменная, значением которой является адрес некоторого объекта (обычно другой переменной) в памяти компьютера. Например, если одна переменная содержит адрес другой переменной, то говорят, что первая переменная указывает (ссылается) на вторую. Переменную, являющуюся указателем, нужно соответствующим образом объявить. Объявление указателя состоит из имени базового типа, символа * и имени переменной. Общая форма объявления указателя следующая: тип *имя; Базовый тип указателя определяет тип объекта, на который указатель будет ссылаться. После объявления нестатического локального указателя до первого присвоения он содержит неопределенное значение. Указатель, не ссылающийся в текущий момент времени должным образом на конкретный объект, должен содержать нулевое значение. Нуль используется, потому что С гарантирует отсутствие чего-либо по нулевому адресу. Следовательно, если указатель равен нулю, то это значит, во-первых, что он ни на что не ссылается, а во-вторых – что его сейчас нельзя использовать. Указателю можно задавать нулевое значение, присвоив ему 0. Например, следующий оператор инициализирует p нулем: char *p=0; Дополнительно к этому во многих заголовочных файлах языка С, например, в определен макрос NULL, являющийся нулевой указательной константой. Поэтому в программах на С часто можно увидеть следующее присваивание: p=NULL; Однако равенство указателя нулю не делает его абсолютно «безопасным». Использование нуля в качестве признака неподготовленности указателя – это только соглашение программистов, но не правило языка С. Указатель на 1-й элемент массива можно создать путем присваивания ему имени массива без индекса. Например, если есть объявление int sample[10]; то в качестве указателя на 1-й элемент массива можно использовать имя sample. В следующем фрагменте программы адрес 1-го элемента массива sample присваивается указателю p: int *p; int sample[10]; p=sample; В обеих переменных (p и sample) хранится адрес 1-го элемента, отличаются эти переменные только тем, что значение sample в программе изменить нельзя. Адрес первого элемента можно также получить, используя оператор получения адреса &. Например, выражение sample и &sample[0] имеют одно и то же значение. Указатели и массивы тесно связаны друг с другом. Имя массива без индекса – это указатель на первый элемент массива. Рассмотрим следующий массив: char p[10]; Следующие два выражения идентичны: P &p Выражение p==&p[0] принимает значение истина, потому что адрес 1-го элемента массива – это то же самое, что и адрес массива. Имя массива без индекса представляет собой указатель. И наоборот, указатель можно индексировать как массив. int *p, i[10]; p=i; p[5]=100; // в присвоении используется индекс *(p+5)=100; //в присвоении используется адресная арифметика. Оба оператора присваивания заносят число 100 в 6-ой элемент массива i. Первый из них индексирует указатель p, во втором применяются правила адресной арифметики. В обоих случаях получается один и тот же результат. Можно также индексировать указатели на многомерные массивы. Например, если а – это указатель на двухмерный массив целых размерностью 10х10, то следующие два выражения эквиваленты: A &a[0][0] 40. Операции над указателями. В языке С определены две операции для работы с указателями: * и &. Оператор & - это унарный оператор, возвращающий адрес своего операнда. Оператор m=&count; присваивает m адрес переменной count. Можно сказать, что адрес – это номер первого байта участка памяти, в котором хранится переменная. Адрес и значение переменной – это совершенно разные понятия. Предположим, переменная count хранится в ячейке памяти под номером 2000, а ее значение равно 100. Тогда переменной m будет присвоено значение 2000. Вторая операция для работы с указателями (ее знак, т.е. оператор, *) выполняет действие, обратное по отношению к &. Оператор * - это унарный оператор, возвращающий значение переменной, расположенной по указанному адресу. Например, если m содержит адрес переменной count, то оператор q=*m; присваивает переменной q значение переменной count. Таким образом, q получит значение 100, потому что по адресу 2000 расположена переменная count, которая имеет значение 100. Присваивание указателей Указатель можно использовать в правой части оператора присваивания для присваивания его значения другому указателю. Если оба указателя имеют один и тот же тип, то выполняется простое присваивание, без преобразования типа. Допускается присваивание указателя одного типа указателю другого типа. Однако для этого необходимо выполнять явное преобразование типа указателя. Преобразование типа указателя Указатель можно преобразовать к другому типу. Эти преобразования бывают двух видов: с использованием указателя типа void* и без его использования. В языке С допускается присваивание указателя типа void* указателю любого другого типа (и наоборот) без явного преобразования типа указателя. Тип указателя void* используется, если тип объекта неизвестен. В отличие от void*, преобразования всех остальных типов указателей должны быть всегда явными (т.е. должна быть указана операция приведения типов). Адресная арифметика В языке С допустимы только две арифметические операции над указателями: суммирование и вычитание. Предположим, текущее значение указателя p1 типа int * равно 2000. Предположим также, что переменная типа int занимает в памяти 2 байте. Тогда после операции увеличения p1++; указатель p1 принимает значение 2002, а не 2001. Это же справедливо и для операции уменьшения. Например, если p1 равно 2000, то после выполнения оператора p1-- ; значение p1 будет равно 1998. Операции адресной арифметики подчиняются следующим правилам. После выполнения операции увеличения над указателем, данный указатель будет ссылаться на следующий объект своего базового типа. После выполнения операции уменьшения – на предыдущий объект. Применительно к указателям на char, операции адресной арифметики выполняются как обычные арифметические операции, потому что длина объекта char всегда равна 1. Для всех указателей адрес увеличивается или уменьшается на величину, равную размеру объекта того типа, на который они указывают. Операции адресной арифметики не ограничены увеличением (инкрементом) и уменьшением (декрементом). Например, к указателям можно добавлять целые числа или вычитать из них целые числа. p1=p1+12; передвигает указатель p1 на 12 объектов в сторону увеличения адресов. Кроме суммирования и вычитания указателя и целого, разрешена еще только одна операция адресной арифметики: можно вычитать два указателя. Благодаря этому можно определить количество объектов, расположенных между адресами, на которые указывают данные два указателя; правда, при этом считается, что тип объектов совпадает с базовым типом указателей. Все остальные арифметические операции запрещены. А именно: нельзя делить и умножать указатели, суммировать два указателя, выполнять над указателями побитовые операции, суммировать указатель со значениями, имеющими тип float или double и т.д. Сравнение указателей Стандартном С допускается сравнение двух указателей. Например, если объявлены два указателя p и q, то следующий оператор является правильным: if(p < q) printf("p ссылается на меньший адрес, чем q\n"); Как правило, сравнение указателей может оказаться полезным, только тогда, когда два указателя ссылаются на общий объект, например, на массив. 41. Массивы переменных размеров. Функции использования динамической памяти. Динамический массив – это массив с переменным размером, т.е. количество элементов может изменяться во время выполнения программы. Динамические массивы дают возможность более гибкой работы с данными, так как позволяют не прогнозировать хранимые объемы данных, а регулировать размер массива в соответствии с реально необходимыми объемами. Пример: float *array1; array1=(float*)malloc(10*sizeof(float)) // выделение 10 блоков по sizeof(float) байт каждый. Динамическое распределение означает, что программа выделяет память для данных во время своего выполнения. Память для глобальных переменных выделяется во время компиляции, а для нестатических локальных переменных - в стеке. Во время выполнения программы ни глобальным, ни локальным переменным не может быть выделена дополнительная память. Память, выделяемая в С функциями динамического распределения данных, находится в т.н. динамически распределяемой области памяти. Динамически распределяемая область памяти – это свободная область памяти, не используемая программой, операционной системой или другими программами. Размер динамически распределяемой области памяти заранее неизвестен, но как правило в ней достаточно памяти для размещения данных программы. Большинство компиляторов поддерживают библиотечных функции, позволяющие получить текущий размер динамически распределяемой области памяти, однако эти функции не определены в Стандарте С. Хотя размер динамически распределяемой области памяти очень большой, все же она конечна и может быть исчерпана. Основу системы динамического распределения в С составляют функции malloc() и free(). Эти функции работают совместно. Функция malloc() выделяет память, а free() – освобождает ее. Это значит, что при каждом запросе функция malloc() выделяет требуемый участок свободной памяти, а free() освобождает его, то есть возвращает системе. В программу, использующую эти функции, должен быть включен заголовочный файл . Прототип функции malloc() следующий: void *malloc(size_t количество_байтов); Здесь количество байтов – размер памяти, необходимой для размещения данных. При успешном выполнении malloc() возвращает указатель на первый байт непрерывного участка памяти, выделенного в динамически распределяемой области памяти. Если в динамически распределяемой области памяти недостаточно свободной памяти для выполнения запроса, то память не выделяется и malloc() возвращает нуль. 42. Определение функций. Программирование с использованием функций. Возвращение значения: оператор return. Функция – это самостоятельная единица программы, созданная для решения конкретной задачи. Как и переменные функции надо объявлять. Функцию необходимо объявить до ее использования. Каждая функция языка С имеет имя и список аргументов (формальных параметров). Функции могут возвращать значение. Это значение может быть использовано далее в программе. Так как функция может вернуть какое-нибудь значение, то обязательно нужно указать тип данных возвращаемого значения. Если тип не указан, то по умолчанию предполагается, что функция возвращает целое значение (типа int). После имени функции принято ставить круглые скобки. В этих скобках перечисляются параметры функции, если они есть. Если у функции нет параметров, то при объявлении и при описании функции вместо <список параметров> надо поставить void – пусто.

Основная форма описания функции имеет вид:
тип <имя функции> (список параметров)

{

тело функции

}

Объявление (прототип) функции имеет вид:
тип <имя функции> (список параметров);

Вызов функции делается следующим образом:

<имя функции> (параметры);
или

<переменная>=<имя функции>(параметры);
Почему надо объявлять функцию до использования? Дело в том, что для правильной работы кода функции машине надо знать тип возвращаемого значения, количество и типы аргументов. При вызове какой-либо функции копии значений фактических параметров записываются в стек, в соответствии с типами указанными в ее прототипе. Затем происходит переход в вызываемую функцию.

Пример вызова функции, которая будет печатать строку «вызвали функцию» на экран.

#include

void main(void) //точка входа в программу

{

void function1(void) //объявление функции

function1(); //вызов функции
}

/*Описание функции*/

void function1(void) // заголовок функции
{ // Начало тела функции

printf(“Вызвали функцию\n”);

} //Конец тела функции

Результатом работы программы будет строка, напечатанная на экране.

В теле функции main() мы объявили функцию function1(), затем ее вызвали. В теле функции function1() мы вызываем функцию printf().

Тип возвращаемого значения у функции function1 void (пусто). Это значит, что функция не будет возвращать никакого значения.

Функции возвращающие значение.

Функции с параметрами.

Функции языка С могут иметь параметры. Эти параметры передаются в функцию и там обрабатываются. В списке параметров для каждого параметра должен быть указан тип.

Пример правильного списка параметров: function(int x, char a, float z). Пример неправильного списка параметров: function(int x, a, float z).

Формальные параметры – параметры, которые мы объявляем в заголовке функции при описании.

Фактические параметры – параметры, которые мы подставляем при вызове функции.

Оператор return.

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

Оператор имеет следующий формат:

return [(выражение)];

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

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

t=func(a,b)+sum(mas,k);

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

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


Автор: Жанара Кенжебекова

Жанара — исследовательница культуры и традиций. Ее статьи поднимают важные темы многообразия культур Казахстана.