методичка. Языки C, C++ (1). Методическое пособие для учащихся Москва 2017 Оглавление 2 удк ббк хлебников А. А
Скачать 2.43 Mb.
|
2.5 Оператор switch Конечно, два варианта развития событий - это хорошо, но что делать, если требуется несколько больше реакций. Можно поступить следующим образом: Но это довольно неудобно, поэтому лучше использовать оператор switch: Этот код проверяет, что какое число ввел пользователь, и в случае если это число – 15, 10 или 20, он выводит на экран то, что ввел пользователь. Здесь есть несколько моментов, на которых нужно остановиться. Во- первых, оператор break, предназначенный для выхода из вложения, будь то цикл или оператор switch. Если его не ставить после каждого case, то на экран будет выведено не только, то что нужно, но и все написанное ниже. Во-вторых, default, который исполняется, если ни одно из условий не является верным. Оглавление 21 Практическая работа №1 1. Напишите программу, принимающую на вход число, и определяющую является ли это число простым. Для решения этой задачи используйте решето Эратосфена. 2. Выведите на экран размеры стандартных типов. Чтобы решить эту непростую задачу, используйте библиотечную функцию sizeof. Программа не принимает никаких значений с клавиатуры и выводит размеры стандартных типов. 3. С помощью символов “*” и “=” выведите на экран американский флаг размером 8 на 12. 4. Посчитайте объем усеченного конуса. Реализуйте эту программу так, чтобы пользователь НЕ мог ввести ничего, кроме числа. Если он вводит не число, то пусть он повторит ввод. Не забудьте про несуществующие значения (деление на 0 и прочее) Примечание: 5. Напишите программу, которая решает квадратные уравнения, коэффициенты вводятся с клавиатуры. Сделайте ее в цикле, чтобы она завершалась, когда пользователь отказывался больше вводить коэффициенты. 6. Билет называют «счастливым», если в его номере сумма первых трех цифр равна сумме последних трех. Подсчитать число тех «счастливых» билетов, у которых сумма трех цифр равна 13. Номер билета может быть от 000000 до 999999. 7. Напишите простой калькулятор. Его функционал должен включать в себя произведение, сложение, вычитание, деление. Оглавление 22 8. На ввод по очереди подаются координаты 5 точек. Определить, сколько из них попадает в круг радиусом R с центром в точке (a,b). 9. Радиус и координаты центра выберите сами. Посчитайте НОД и НОК, числа вводятся с клавиатуры. 10. Каждая бактерия делится на две в течение одной минуты. В начальный момент имеется одна бактерия. Составьте программу, которая рассчитывает количество бактерий на заданное вами целое значение момента времени (15 минут, 7 минут и т.п.). Оглавление 23 3. Функции 3.1 Объявление и определение функций Определяющей особенностью любого структурного языка являются функции. Так как обычная программа на любом языке состоит из множества строк кода, то для облегчения написания/чтения было решено объединять некоторые участки программы в отдельные блоки – функции. Сам по себе, язык C предоставляет некоторое количество библиотек со стандартными функциями. Однако, для понимания работы, лучше сначала научиться создавать свои. Итак, пример функции: Что же, это был самый простой пример функции - с типом возвращаемого значения void и без аргументов. Здесь мы можем увидеть 3 упоминания функции: объявление, вызов и определение. Объявление перед main() предупреждает компилятор о том, что где-то далее в программе есть тело функции с таким же полным именем. Полное имя включает в себя возвращаемое значение, типы параметров и, собственно, имя. Объявление же содержит тело функции. Для лучшего понимания рассмотрим еще небольшой код: Оглавление 24 Чтобы обобщить все сказанное выше построим примерный план написания любой функции: Примечание: Можно поставить определение на место объявления, а определения убрать, этот подход практикуется, если функция небольшая. Однако лучше им не пользоваться, т.к. когда функций много, вы можете запутаться. Оглавление 25 3.2 Передача аргументов по ссылке В предыдущей теме, были рассмотрены общие моменты создания функций. Разумеется, что существует некоторое число нюансов, которые будут описаны далее. Первым является передача аргументов по ссылке. Ранее в примере аргументы передавались в функцию по значению, это значило, что в функции мы работали с копиями этих объектов, а если размер объектов слишком большой или важна память, очевидно, что лишние расходы ее на копии ни к чему. Другим аспектом, заслуживающим внимания, является то, что иногда от функции требуется вернуть больше одного значения, но синтаксис C не позволяет этого сделать. И тут на помощь приходят ссылки. Ссылка представляет собой механизм, который не создает копии, а позволяет работать с самой переменной, указанной в качестве аргумента. Пример программы, возводящей число в степень: В листинге данного кода функция power ( ) принимает адрес, который присваивается локальной переменной-указателю (о которых будет рассказано чуть ниже). В теле функции происходит изменение значения по адресу, содержащемуся в указателе. Однако по факту это адрес переменной n, из функции main ( ), следовательно меняется и значение n. ВАЖНО: Передавать по адресу можно лишь переменные, если вы вместо переменной передадите какое-то значение, к примеру, в нашем случае вместо &n передать n, то вызов этой функции приведет к ошибке. Оглавление 26 3.3 Рекурсия Если просто, рекурсия - это вызов функцией самой себя. Это мощный инструмент в руках опытного программиста. Часто для примера берут программу, вычисляющую числа Фибоначчи, что же посмотрим: (Данный пример считает до 20 числа Фибоначчи, приведен чуть ниже). Оглавление 27 3.4. Встраиваемые функции Использование функций хоть и сокращает размер программы, однако увеличивает время выполнения программы. Для выполнения функции должны быть сгенерированы команды переходов, команды, сохраняющих значения регистров процессора, команды, помещающие в стек и извлекающие из стека аргументы функции (в том случае, если они есть), команды, восстанавливающие значения регистров после выполнения функции и команда перехода функции обратно в программу. Обычно в функцию выносится повторяющийся участок кода, однако если мы вынесем маленький повторяющийся участок кода, то мы не выиграем по времени работы программы, хоть и сэкономим память. Для работы с маленьким участком кода и были придуманы встраиваемые функции. Они так названы, т.к. при компиляции программы их исполняемый код «встраивается» в код программы, но при этом остается независимой частью программы. Рассмотрим пример: Функция example_of_funct – встроенная функция. Ключевое слово inline НЕ является указанием компилятору, а является лишь рекомендацией. Если функция слишком большая, то компилятор проигнорирует inline и скомпилирует эту функцию, как и обычную Оглавление 28 4. Структуры Структура – объединение простых переменных с некими стандартными типами: int, float, double и т.д. Создаются для объединения неких данных. Типы переменных, присутствующих в структуре, могут быть как различны, так и одинаковы. Рассмотрим пример: Мы создали структуру bookcase (книжный шкаф), в которой хранятся данные (поля). Это ее определение: В строке structBookcasex = { 5, "IT", 25.9, 12.0 } – структурная переменная инициализируется, а в struct Bookcase y–определение структурной переменной. Члены структуры заключены в фигурные скобки. Структура (в нашем случае) хранит данные о книжном шкафе: ширина и высота книжного шкафа, название книги и номер книжной полки (для упрощения структуры, предположим, что одна книжная полка = одной книге). Оглавление 29 Доступ к полям структуры возможен благодаря применению операции точки. Оглавление 30 4.1 Структуры в памяти Обратите внимание на следующие структуры, они являются анонимными, т.е. имена этих структур не смогут использоваться в дальнейшем. Иначе говоря, у нас будет существовать лишь 1 экземпляр данных структур: А теперь вопрос: как вы думаете, сколько выделяется памяти для переменных этих структур? 6 - для А, 8 - для B, 7 - для С? Нет. Все для всех этих структур будет выделено 8 байт. Правило состоит в том, что если структура занимает меньше 4 байт, то ее размер равен сумме размеров переменных, иначе же он должен быть кратен 4. Это делается для того чтобы, выровнять структуры в памяти, чтобы обеспечить удобную работу с ними. Однако кратность можно изменить с помощью директивы #pragma pack(push,n), где n-кратность памяти. (Что такое директивы будет рассказано чуть позже). Возможные параметры - степени двойки. Следует использовать эту директиву только тогда, когда памяти может не хватить, ведь при уменьшении расхода памяти увеличивается время работы со структурной переменной. Пример: Оглавление 31 Обязательно ставьте #pragma pack(pop), иначе память программы может потерять свою структурированность, и может случиться что угодно (и вряд ли хорошее). Оглавление 32 5. Перечисления Это также еще один способ создания пользовательского типа данных. В отличие от структур этот способ используется, когда переменные создаваемого типа принимают известное конечное множество значений. Пример: Перечисления перечисляют все возможные значения переменных создаваемого типа. Обычно перечисление начинается с 0, однако в данном случае мы определили Jan=1 и, соответственно, перечисление началось с 1. Переменным, типом которых является заданное перечисление, нужно присваивать только те значения, которые указаны в перечислении. Перечисляемые типы данных (как видно из примера выше) разрешают применение основных арифметических операций. Также перечисление удобно использовать как замена типу bool. Выглядит это так: enum click {No, Yes} Именно так удобно записывать, так как в таком случае No=0 и Yes=1, как и в bool. Оглавление 33 Практическая работа №2 1. Посчитайте факториал переменной, которая вводится с клавиатуры. Примечание:n! = 1*2*3*…*n 2. У вас есть комната, у нее есть параметры – длина и ширина. Посчитайте ее площадь и периметр. Используйте структуры и функции. 3. Номер телефона можно разделить на 3 части: код города, номер телефонной станции и номера абонента. Напишите программу, которая хранит эти части телефонного номера, ищет соответствие по какой-то из частей номера и выводит этот номер на экран. 4. У вас есть декартова система координат, в которой, как известно, существуют 2 координаты – x и y. Пользователь задает вектор его координатами (1 координата – начало вектора, 2 координата – его конец), ваша программа должна вывести его модуль. Указание: использовать структуры. 5. Мы высчитываем время в часах, минутах и секундах. Создать код, который бы высчитывал сумму и вычитание двух значений времени. Указание: делать с помощью структур и функций 6. Вычислить заданную функциюy = 45*e^(2x 2 ) в диапазоне [-5,5] с шагом ½. 7. Понять, когда данное выражение выполняться не будет |sin(𝑥) 𝑛 − sin(𝑥) 𝑛−1 | < 𝐴, где А – некая бесконечно малая окрестность (вводится пользователем). Вывести n, при котором это уже не выполняется. Указание: нужно знать, как раскладывается sinx, чтобы решить эту задачу. В точке x = 0, эта функция раскладывается так: Оглавление 34 sin(𝑥) = ∑(−1) 𝑛 ∗ 𝑥 2𝑛+1 (2𝑛 + 1)! 𝑖𝑛𝑓 0 Оглавление 35 6. Указатели Указатели являются характерной особенностью языка Си. Эта особенность является одновременно и преимуществом языка, и его проблемой. Здесь будут рассмотрены лишь необходимые основы, поскольку формат пособия не предполагает углубленного изучения. Насколько вы знаете, память компьютера представляет собой набор из битов, но это очень маленькая ячейка, и для повышения быстродействия и по ряду технических причин, наименьшей доступной ячейкой в памяти является байт. Все байты имеют адрес, по которому к ним происходит обращение. Программа, загружаясь в память, занимает некоторое количество этих адресов. Следовательно, каждая переменная или функция начинается с определенного адреса. Небольшой рисунок для понимания: Видно, что, переменная типа short занимает 2 байта, а типа char 1 байт. Объявление указателя происходит таким образом: где pointer – указатель, который хранит адрес ячейки памяти, в которой лежит целое число (int). Есть два способа использования указателя: - Использовать имя указателя без символа *, таким образом можно получить фактический адрес ячейки памяти, куда ссылается указатель. Оглавление 36 - Использовать имя с указателем с символом *, это позволит получить значение, хранящееся в памяти. В рамках указателей, у символа * есть техническое название – операция разыменования. По сути, мы принимаем ссылку на какой-то адрес памяти, чтобы получить фактическое значение. В строчке pointer = &cell мы получаем адрес ячейки, куда ссылается указатель. В 8 строчке *pointer– это процесс разыменования. 8 строчка выводит 126 на экран. Указатели, инициализированные значением 0, называются нулевыми и ни на что не указывают. Бывают случаи, когда нас интересует просто значение адреса, а не тип указываемого объекта. С этой целью в язык C++ введены указатели void *, способные хранить адрес любого объекта. В отличие от типизированных указателей, рассмотренных выше, указатели void* - нетипизированные. Если p является указателем на структуру s с полем a, то оператор -> в записи p->a используется для сокращения записи (*p).a. Например: Попытка разыменования нулевого указателя приводит к ошибке при выполнении программы, попытка же разыменования указателя void * вызовет ошибку на этапе компиляции. Оглавление 37 6.1 Выделение памяти Конечно, вышеперечисленная информация только знакомит с концепцией указателей. Однако сейчас разберем самое частое использование указателей (и основную идею, для которой они были придуманы) – работа с памятью. В памяти компьютера есть особая область, называемая кучей (англ. heap), которая позволяет выделять память динамически, т.е. во время работы программы (Строго говоря, есть еще 2 типа хранения памяти стек и непосредственно тело исполняемого модуля). Для этого существует специальная функция – malloc(). То, как она работает проще понять на примере программы, подсчитывающей среднее арифметическое у массива произвольной длины: Первую строчку main ( ) можно переписать так: Эта«строчка»main(), это пример того что для того чтобы объявить указатели друг за другом через запятую, нужно использовать * перед именем каждого указателя, иначе (как в данном случае) вы получите один указатель на целый тип и несколько переменных целого типа. Делать так не следует, так как человеку, читающему ваш код, будет труднее понять его листинг. Как видно для использования перед malloc нужно в скобках ставить тип указателя, для которого нужно выделить память, а аргументом нужно Оглавление 38 указывать количество необходимых ячеек. Честно говоря, функция free(), здесь не нужна, так как по завершению программы память освободится автоматически, но в больших проектах об этом не стоит забывать. Возможно, вы удивились, увидев здесь выражение (double)array[i], данная конструкция является явным преобразованием типов. О ней будет рассказано в следующем пункте. Примечание. При выделении память для двумерного массива используется следующая конструкция: А для освобождения обратный порядок: Возможно, вы удивились, увидев здесь операцию [], но как уже было сказано массив является указателем (константным, его адрес нельзя изменить), а конструкция a[i] это не что иное, как измененный вид конструкции *(a+i), по этой причине индексация массива начинается с нуля, ведь имя массива-указатель на его первый элемент. Следующая функция для выделения памяти calloc достаточно похожа на malloc, единственное различие, что при применении calloc ячейки памяти, которые она выделяет заполняются нулями, так же различается количеством аргументов, здесь добавляется вес переменной в байтах: В остальном же если подставить это в предыдущую программу, то ничего не изменится. Говоря о выделении памяти нельзя также не сказать о функции, real- loc(). Она предназначена для перераспределения памяти. Например, если у вас есть некая выделенная память под процесс, но может случится так, что Оглавление 39 этой памяти не хватит, и тогда будет достаточно просто с помощью realloc() выделить дополнительную память. Следующая программа дает возможность ввести строку неограниченной длины до символа ‘=’: Оглавление 40 6.2. Явное и неявное преобразование типов. В языках С и C++ преобразование одного типа в другой называется приведением типа. Оно бывает явным и неявным: - При неявном приведении преобразование происходит автоматически - При явном приведении перед выражение, необходимо использовать оператор приведения типа static_cast<>. Для языкаC++. В языке Cэтого оператора нет! В некоторых старых компиляторах оператор static_cast отсутствует, поэтому приходится использовать приведение типа в старом стиле Работает и в C, и в C++ В новой редакции C++ старый способ приведения заменен на четыре новых, различающихся по степени безопасности. О них будет рассказано в разделе C++. Оглавление 41 6.3 Указатели на функции Хотя функция - это не переменная, она по-прежнему имеет физическое положение в памяти, которое может быть присвоено указателю. Адрес, присвоенный указателю, является входной точкой в функцию. Далее этот указатель можно использоваться для вызова функции.Адрес функции получается при использовании имени функции без каких-либо скобок или аргументов. Как вы можете видеть для объявления указателя на функцию вам достаточно знать лишь прототип функции, т. е. список параметров и тип возвращаемого значения. Кроме того, при объявлении указателя на функцию имя указателя вместе с символом * заключаются в скобки. |