Операторы отношения и логические операторы

      Комментарии к записи Операторы отношения и логические операторы отключены

В предыдущем примере использовался интуитивно понятный, но не рассмотренный ранее оператор «». Это один из операторов отношения, которые позволяют создавать различные выражения для конструкции «if-else».

Операторы отношения позволяют сравнить два числа, и в результате сравнения выдать конструкции ifзначение истинна или ложь.

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

a%2.

Существуют следующие операторы отношения:

«» – больше (левый операнд больше правого);

«=» – больше или равно;

«

«

«==» – равно;

«!=» – не равно;

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

0 = = a%2.

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

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

Если нужно выполнить определенные действия, если истинные несколько условий одновременно, то можно использовать оператора «» (оператор «и»). Например, нужно проверить, входит ли число x в определенный диапазон от a до b. Условное выражение можно записать следующим образом: (xax

Если нужно выполнить определенные действия если истинным является хотя бы одно из нескольких условий, то можно использовать оператор «||» (оператор «или»). Например, нужно проверить, нужно проверить, что число x не входит в диапазон от a до b. Условное выражение можно записать следующим образом: (x=b).

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

Остальные операторы

Остальные операторы языка C не обязательны для написания учебных программ, однако их использование позволяет сократить объем кода и повысить читаемость программ.

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

— побитовое И;

| — побитовое ИЛИ;

^ — побитовое исключающее ИЛИ;

— сдвиг вправо;

~ — побитовое отрицание (унарный).

Операторы присваивания, кроме собственно оператора присваивания («=»), предназначены для сокращения записи часто встречаемых выражений.

Часто в программе требуется прибавить к переменной какое-либо значение, и результат сохранить в эту же переменную. Это можно записать так: i=i+1;. Оператор присваивания «+=» позволяет сократить эту запись следующим образом: i+=1;.

Имеются аналогичные операторы присваивания, позволяющие выполнить любую арифметическую или побитовую операцию и одновременно сохранить результат: «+=», «–=», «*=», «/=», «%=», «=», «

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

Существуют два оператора инкремента: «a++», «++a». Постфиксный оператор «++» (т.е. тот, который записан после операнда) увеличивает операнд на 1, но возвращает старое значение. Префиксный оператор «++» (т.е. тот, который записан перед операндом) увеличивает операнд на 1, и возвращает новое, увеличенное значение. Например, если значение переменной a было равно 1, то после выполнения выражения b=a++; значение a будет равно 2, а значение b будет равно 1 (старому значению a). После выполнения выражения b=++a; значение a будет равно 2, а значение b будет равно 2 (новому значению a).

Постфиксный («a–?–») и префиксный («–?–a») варианты оператора декремента ведут себя аналогичным образом.

Условный оператор «?:» предназначен для короткой записи управляющей конструкции «if-else». Он имеет три операнда и в общем виде записывается следующим образом:

выр1 ? выр2 : выр3;

Первым вычисляется выражение выр1. Если его значение не нуль (истина), то вычисляется выражение выр2, и значение этого выражения становится значением всего условного выражения. В противном случае вычисляется выражение выр3 и его значение становится значением условного выражения.

Например, нужно в переменную z записать наибольшее число из переменных a и b. С помощью условного оператора это можно коротко записать следующим образом:

z = (ab) ? a : b;

Задание к работе

1) Написать программу, которая по номеру года определяется, является ли он високосным. Високосным считается год, номер которого делится на 4, при этом год не високосный, если он делится на 100, но не делится на 400.

2) Написать программу, в которой пользователь вводит целое положительное число, а компьютер сообщает, сколько у этого числа делителей меньше 10.

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

1. Числа четные

2. Числа нечетные

3. Одно число делится на другое

4. Одно число является квадратом другого

5. Одно число больше другого на 5

6. Среди введенных чисел есть число, делящееся на 5

7. Оба числа больше 10

8. Среди введенных чисел есть число больше 10

9. Одно число больше другого в 2 раза

10. Одно число четное, другое нечетное

4) Взять вариант на 1 больше и решить задачу из 3 задания используя условный оператор.

Содержание отчета

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

Контрольные вопросы

1) Как в общем виде записывается управляющая конструкция выбора?

2) Что такое составной оператор?

3) Какие в языке С есть операторы отношения?

4) Какие в языке С есть логические операторы?

5) Какие в языке С есть операторы присваивания?

6) Как в общем виде записывается условный оператор?

7) В каком порядке расположены приоритеты операторов отношения, логических и арифметических?

8) Чем отличаются постфиксная и префиксная формы операторов инкремента и декремента?

Лабораторная работа 4
Конструкции циклов

Цель работы: изучение основных конструкций циклов.

Теоретические сведения

Цикл while

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

Для решения этой задачи нам нужно выполнить действие несколько раз. Мы не можем записать этой действие в программе несколько раз, поскольку количество раз во время написания программы нам не известно.

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

В языке С есть несколько управляющих конструкций с помощью которых можно выполнить цикл. Основной из них является конструкция «while». В общем виде она записывается следующим образом:

while (выражение)

инструкция

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

a=1;

while(a*a

{

printf(“%i в квадрате = %i”, a, a*a);

a++;

}

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

Цикл for

В программах многие циклы строятся по одинаковой схеме. Сначала, до цикла, нужно проинициализировать переменные, используемые в цикле (например, в примере выше переменной a присваивается значение 1). При каждом выполнении цикла значение некоторой переменной нужно изменить (в примере – увеличение переменной a на 1), сделать шаг цикла.

Для более компактной и наглядной записи таких типичных циклов в языке С имеется управляющая конструкция «for». В общем виде она записывается следующим образом:

for (инициализация; условие; шаг)

инструкция

Эта конструкция эквивалента следующей:

инициализация;

while (условие)

{

инструкция

шаг;

}

Рассмотренный выше пример с помощью конструкции «for» можно записать так:

for (a=1; a*a

printf(“%i в квадрате = %i”, a, a*a);

Для создания циклов в программах на языке С обычно используются «while» и «for». Другие управляющие конструкции языка позволяют записать некоторые типы циклов более компактно, но используются значительно реже, поэтому на данном этапе изучение их не целесообразно.

Конструкция в конструкции

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

Рассмотрим пример программы, которая печатает таблицу умножения. Для нее понадобиться вложенные циклы. Внешний цикл будет печатать по одной все строки. Внутренний цикл будет печатать по одному все столбцы, т.е. печатать одну строку. Код программы приведен на рис. 8.

Рис. 8 Пример программы

В строках с 4 по 8 печатается шапка нашей таблицы. В 6 строке после символа процента указано число 2 и лишь после этого числа указан тип переменной. Таким способом можно установить, сколько цифр будет печататься на экране. Не зависимо от того, сколько реально разрядов в числе, на экране оно займет 2 символа. Это позволит получить в таблице ровные колонки.

В 9 строке записана условная конструкция для внешнего цикла. Что и почему записано в полях инициализации, условия и шага цикла можно разобраться самостоятельно. Обратите внимание, как удобно использовать конструкцию «for» если известно число повторений на момент начала цикла. Мы по порядку указываем, с чего нужно начать, чем закончить и с каким шагом двигаться в цикле. Поэтому иногда такую конструкцию называют циклом с известным числом повторений.

В 11 строке печатается заголовок строки, а в 12 начинается внутренний цикл. Мы опять используем цикл «for», чтобы «пройтись» по 9 столбцам.

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

В 14 строке печатается перевод на новую строку. Эта инструкция относится к внешнему циклу.

Для того, чтобы легче было понять, к какой конструкции относится та или иная инструкция, принято выравнивать инструкции. Выравнивание кода принято делать по следующему правилу: инструкции, находящиеся внутри конструкции, сдвигают относительно конструкции на одну позицию вправо. Сдвиг на одну позицию может пониматься по-разному, это либо сдвиг размер табуляции, либо сдвиг на 3-5 символов.

В примере, в 6 строке записана внутренняя инструкция цикла из 5 строки, и она сдвинута относительно его на 1 позицию. Строки с 11 по 14 являются внутренними инструкциями цикла из 9 строке, и также сдвинуты.

В 13 строке находится инструкция внутреннего цикла. Она сдвинута на 1 позицию относительно внутреннего цикла, и на 2 позиции относительно внешнего цикла. Благодаря этому мы легко можем определить, что инструкция из 13 строки находится во внутреннем цикле, а в 14 строке – во внешнем.

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

Задание к работе

Все задания этой лабораторной работы нужно проделать в 2 вариантах – с использованием конструкций «while» и «for». При необходимости можно использовать пустую инструкцию – если в некотором поле конструкции не нужно выполнять никаких действий, в него можно записать «;».

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

2) Найти наибольше и наименьшие значения функции для целых аргументов от 1 до 50. Для этого использовать конструкцию выбора внутри цикла (номер задания равен остатку от деления номера варианта на 10):

0. sin(2*3.14*i/15)/i

1. cos(2*3.14*i/15)/i

2. sin(2*3.14*i/50)/cos(2*3.14*i/50)

3. cos(2*3.14*i/50+3.14/2)/sin(2*3.14*i/50+3.14/2)

4. i*i-50*i

5. sqrt(50*i)-i

6. sin(2*3.14*i/25)

7. cos(2*3.14*i/25)

8. tan(2*3.14*i/50)

9. pow(i,2)-50*i

3) Нарисовать график функции из 2 задания. График представляет собой изображение из 50 символов по горизонтали и 50 символов по вертикали. Для отображения графика нужно организовать вложенные циклы. Внешний цикл выполняется 50 раз и рисует 50 строк. Внутренний цикл повторяется 50 раз и рисует 50 столбцов. Во внутреннем цикле проверяется, какой символ нужно нарисовать.

Для определения отображаемого символа используется следующее условное выражение. Если ceil(((func)-min)*50/(max-min))==50-j, то печатается символ *, иначе печатается символ (пробел). В условном выражении max и min это максимум и минимум функции, найденные в предыдущем задании. Func – это отображаемая функция. Функция сeil находит наименьшее целое, не большее аргумента. Все выражение масштабирует диапазон значений функций (от min до max) на диапазон от 0 до 50 и определяет, в какой строке функция пересекает соответствующий столбец.

Содержание отчета

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

Контрольные вопросы

1) Как в общем виде записывается управляющая конструкция «while»?

2) Как в общем виде записывается управляющая конструкция «for»?

3) Для чего используется выравнивание инструкций?

4) Какие конструкции можно использовать внутри конструкции цикла?

5) Какие поля есть в конструкции «while»?

6) Какие поля есть в конструкции «for»?

Лабораторная работа 5
Массивы и структуры

Цель работы: изучение массивов и структур.

Теоретические сведения

Пример массива

Рассмотрим задачу: даны 100 целых чисел, нужно найти их среднее значение.

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

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

Решение задачи приведено на рис. 9.

Рис. 9 Пример программы

Инструкция в 4 строке предназначена для инициализации генератора случайных чисел. Она использует функции из библиотеки time. Мы не будем рассматривать, как работает генератор случайных чисел, а просто используем его для первоначального заполнения массива.

В 6 строке написано объявление массива. Идентификатор int указывает на то, что элементы массива будут целого типа, «a» это имя массива, а в квадратных скобках указывается количество элементов. После этого объявления в памяти будут выделено место под 100 переменных целого типа, обратиться к которым можно, указав имя массива и его индекс.

В 7 и 8 строках записан цикл, заполняющий объявленный массив случайным числам. Он повторяется 100 раз, при этом переменная i принимает значения от 0 до 19. В 8 строке, слева от оператора присваивания, происходит обращение к i?ому элементу массива «a». Для того чтобы обратится к некоторому элементу массива, нужно указать имя массива и, в квадратных скобках, его индекс.

В языке С элементы массива нумеруются начиная с 0. В примере на первом шаге цикла присваивается значение элементу a[0], на втором – а[1], а на последнем –a[100].

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

В 9 и 10 строках записан цикл, который подсчитывает сумму элементов массива. В 11 строке печатается среднее значение.

Свойства массивов

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

Массивы могут содержать переменные любых типов. Так, например, можно объявить массив из 10 вещественных чисел:

float a[10];

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

float a[] = {1, 1.5, 2, 2.5, 3};

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

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

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

Можно сказать, что оператор индексирования a[i] фактически означает сумму адреса первого элемента и числа i умноженного на размер одного элемента.

Важно отметить два важных последствия такого свойства оператора индексирования.

Поскольку первый элемент массива расположен в самом его начале, то он должен иметь индекс 0. Поэтому массивы в С и нумеруются с 0.

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

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

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

Строки

Массив из символов (элементов типа char) часто называется строкой. Строки позволяют хранить фразы на естественном языке, например, на русском.

При выводе на экран и других операциях со строками нужно знать, сколько символов хранит строка. Например, если у нас есть массив для хранения имени пользователя, состоящий из 25 символов, то для пользователя «Вася» нужно при печати выводить только первые 4 символа.

Наиболее удобным оказалось хранить информацию о реальной длине строки прямо в массиве символов. Для этого используется специальный символ обозначаемый как «\0». Функция, работающая со строкой, например, печатающая ее, будет обрабатывать символы по порядку, пока не встретит символ «\0».

Для удобства записи массива символов можно использовать строковые константы. Строковые константы представляют собой набор символов, заключенных в двойные кавычки. Например, следующее объявление создаст массив из 7 символов:

char a[] = “Привет”;

Символ a[0] будет равен ‘П’, символ a[5] будет равен ‘т’, а символ a[6] будет равен ‘\0’. Обратите внимание, что когда мы записываем символы в двойных кавычках, то это будет строка, заканчивающаяся ‘\0’, а если в одинарных – то это один символ. Так, запись “П” будет состоять из 2 символов, а запись ‘Пр’ вызовет ошибку.

Для удобства работы со строками функции форматированного ввода-вывода (printf и scanf) содержат специальный спецификатор строки – «%s». Например, напечатать объявленную выше строку можно следующим образом:

printf(“%s”, a);

Для работы со строками в языке С есть целая стандартная библиотека – string.h. Например, следующая функция копирует (переписывает) строку scr в строку dest:

strcpy(dest, scr);

С полным списком функций этой библиотеки можно ознакомиться в [1].

Структуры

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

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

struct tovar

{

char name[20];

int price;

};

Это объявление сообщит компилятору, что структура tovar содержит поля name и price. Компилятор вычислит необходимый для структуры объем памяти и смещение полей относительно начала структуры.

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

tovar a,b;

После таких объявлений компилятор отведет под переменные a и b необходимое место в памяти.

При объявлении структуру можно сразу инициализировать. Инициализация структуры аналогична инициализации массива:

tovar a={”мыло”, 100};

Поскольку поля в структуре занимают различный объем, для обращения к ним нельзя использовать операцию индексирования. Для обращения к полю используют операцию обращения к полю, которая обозначается точкой. Например:

a.price = 10;

Для этого примера компилятор создаст код, который прибавит к адресу структуры a смещение поля price, вычисленному при объявлении структуры. По полученному адресу и будет записано число 10 – цена товара. Аналогичным образом происходит обращение к полю и в случае, если нужно получить его значение.

Задание к работе

1) Создать массив из 50 чисел, значения которых равны значению функции от индекса элемента. Функции взять из второго задания предыдущей лабораторной работы. Распечатать значения этого массива.

2) Пользователь вводит с клавиатуры слово (не больше 19 букв). Его нужно распечатать вертикально, выводя по одной букве в строке. Ввод пользователем слова в массив символов a можно реализовать следующей командой: scanf(%s, a); (для объяснения того, как она работает, потребовался бы еще не пройденный материал, поэтому объяснения пока пропустим). При выводе строки на экран нужно иметь в виду, что количество символов в строке неизвестно, но известно, что последний символ – ‘\0’. При написании программы подумайте, как можно сократить количество операций в ней.

3) Объявить структуру, содержащую строки, соответствующие имени и фамилии человека. Создать массив из 10 таких структур, инициализировать их именами и фамилиями любых известных людей. Напечатать список людей, чье имя совпадает с именем, которое ввел пользователь.

Содержание отчета

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

Контрольные вопросы

1) Что такое массив?

2) Как объявить массив?

3) Сколько элементов и с какими индексами содержит массив?

4) Что означает ошибка выхода за границы массива?

5) Что такое операции индексирования?

6) Что такое строка, как она создается и используется?

7) Что такое структура, как она создается и используется?

Лабораторная работа 6
Указатели

Цель работы: Изучение указателей, операций над ними, операций с указателями на массивы и структуры.

Теоретические сведения

Указатели

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

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

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

В языке С тип данных, который указывает адрес в памяти переменной, массива или структуры называется указатель. Указатель занимает объем памяти, необходимый для того, чтобы сохранить адрес любой ячейки памяти в компьютер. В 32-битной операционной системе указатель занимает 4 байта.

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

Указатель объявляется с помощью символа «*», который записывается после типа переменной, на которую он указывает. Например, так можно объявить указатель на переменную целого типа:

int* a;

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

struct employee

{

char name[20];

char fam[20];

employee* chief;

};

Третье поле в это структуре указывает на другую структуру того же типа.

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

Операции с указателями

Основные операции над указателями это присваивание и разыменование.

Операция присваивания записывает в указатель адрес какого-то объекта в памяти. Для того, чтобы узнать адрес какого-либо объекта, используется оператор взятия адреса – «». Например, чтобы присвоить указателю p адрес переменной a нужно записать:

p = a.

Обратная операция называется операцией разыменования. Результатом этой операции является переменная, которая хранилась по адресу, на который ссылался указатель. Эта операция указывается с помощью оператора «*». Например, следующий код запишет в переменную a число 1:

p = a;

*p = 1;

А следующее выражение увеличит значение переменной a на 1:

(*p)++;

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

Над указателями можно выполнять и арифметические действия сложения и вычитания (а также инкремента и декремента). Рассмотрим следующий фрагмент кода:

int a[]={0,1,2,3,4};

int b;

int* p = a[2];

p++;

b=*p;

Чему будет равно значение b после его выполнения?

В третьей строке в указатель p был записан адрес элемента a[2].

В четвертой строке этот адрес должен быть увеличен на 1. В С при прибавлении к указателю 1 он увеличивается таким образом, чтобы указывать на следующий в памяти элемент соответствующего типа. Например, если переменная целого типа занимает 4 байта, то адрес, хранящийся в указателе, увеличивается на 4. В пример, после увеличения на 1 указатель будет указывать на следующий элемент массива, т.е. на a[3].

В результате операции в последней строке в b будет записано число 3.

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

Аналогичным образом определена операция вычитания из указателя, только указатель уменьшается, или сдвигается в сторону начала массива.

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

Указатели и массивы

Операция индексирования, по сути, также является операцией адресной арифметики. Можно записать следующее равенство: a[i]=a+i. Фактически, имя массива a является указателем на начало массива. Однако, в отличие от обычных указателей, адрес массива определяется во время компиляции и не изменяется во время выполнения программы.

Сравним два объявления:

int a[10];

int *p;

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

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

За исключением сказанного, указатель и массив схожи. Можно в указатель переписать указатель на массив: p=a. Обратите внимание, операция взятия адреса тут не требуется. Над массивом можно выполнять адресные операции (которые не изменяют указатель на массив), а над указателем можно выполнять операцию индексирования.

Указатели и структуры

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

tovar a;

tovar *p=a;

После этого можно обратиться к полю структуры, на которую указывает указатель:

(*p).price = 10;

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

p-price = 10;

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

Задание к работе

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

2) Для следующего фрагмента программы записать в виде таблицы значение переменных после каждого шага программы (столбцы соответствуют переменным, строки – строкам программы). Тип int и указатели занимают по 4 байта. Компилятор расположил переменные по следующим адресам: a – 100, p1 – 120, p2 – 124, s.x – 128, s.y – 132. Число N – номер варианта.

struct str {int x; int y;};

int a[5] = {1, 2, 3, 4, 5};

int *p1, *p2;

str s;

p1 = a[N%5+1];

p2 = a+N/5+1;

s.x = *p1;

s.y = *p2++;

*(p1-2)=s.y;

p2=s;

p2-x=p1[1];

p2–y=s.y;

a[5]=s.x;

Содержание отчета

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

Контрольные вопросы

1) Что такое указатель?

2) Как объявить указатель?

3) Для чего предназначена операция взятия адреса?

4) Для чего предназначена операция разыменования?

5) Можно ли с указателями производить арифметические операции?

6) Что общего и чем отличаются указатель и массив?

7) Какие операции можно проводить с указателем на структуру?

Лабораторная работа 7
Функции

Цель работы: Ознакомиться с синтаксисом функция, научиться создавать собственные функции, понять способ передачи аргументов и возврата результата.

Теоретические сведения

Функции

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

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

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

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

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

тип_результата имя функции(аргументы)

{

тело_функции

returnрезультат;

}

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

Для примера определим функцию, которая вычисляет сумму квадратов двух целых чисел:

int square_sum (int x, int y)

{

int s = x*x+y*y;

return s;

}

Имя функции square_sum. Она имеет два аргумента целого типа x и y и возвращает результат целого типа.

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

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

Например, где-то в нашей программе встречается вызов этой функции: a=square_sum(2,3);. В первой строке тела функции в s будет записано значение 13, во второй строке это значение будет переписано в переменную a.

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

Статьи к прочтению:

Язык программирования Си. Урок 4. Операторы отношения, логические операторы


Похожие статьи: