Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C_Lect4.doc
Скачиваний:
5
Добавлен:
08.09.2019
Размер:
107.52 Кб
Скачать

2.2. Элементарные математические функции

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

#include <math.h>

Стандартная функция C

Математическое обозначение

Примечания

fabs(x)

аргумент и функция имеют тип double.

abs(x)

аргумент и функция имеют тип int.

sqrt(x)

exp(x)

log(x)

log10(x)

pow(x,y)

оба аргумента и функция имеют тип double. Если y содержит ненулевую дробную часть, то функция вычисляется как exp(y*log(x)). В этом случае x не может быть нулем или отрицательным числом.

ceil(x)

Наименьшее целочисленное значение, которое больше или равно x. Аргумент и функция имеют тип double.

floor(x)

Наибольшее целочисленное значение, которое меньше или равно x. Аргумент и функция имеют тип double.

sin(x)

аргумент в радианах

cos(x)

аргумент в радианах

tan(x)

аргумент в радианах

asin(x)

значение функции в радианах

acos(x)

значение функции в радианах

atan(x)

значение функции в радианах

atan2(y,x)

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

sinh(x)

гиперболический синус

cosh(x)

гиперболический косинус

tanh(x)

гиперболический тангенс

2.3. Преобразование типов при вычислениях

а) Неявные преобразования в арифметических операциях

В языке C разрешается использовать смешанные выражения, т.е. применять арифметические операции к операндам разных типов (например, умножить число с плавающей точкой на целое типа int). В этих случаях перед выполнением операции оба операнда автоматически преобразуются к единому типу, и такой же тип будет иметь результат операции. Точнее говоря, один из операндов, имеющий «низший» тип, преобразуется к типу второго («высшего») операнда. Слова «низший» и «высший» мы употребили условно, за неимением более четких терминов. Можно считать, что все целые типы (включая тип char) являются «низшими» по сравнению с типами с плавающей точкой, а внутри этих двух классов «низшими» являются более короткие значения. Иными словами, самый «низкий» тип — char, затем идут short int, int и long int, а за ними — float, double и long double. Ситуация несколько усложняется, если один из операндов имеет беззнаковый вариант целого типа (unsigned). В этом случае более короткий тип приводится к более длинному, а при одинаковой длине знаковый тип превращается в беззнаковый. Например, перед сложением unsigned int и [signed] long int беззнаковое целое превратится в длинное целое со знаком и результат будет иметь этот же тип. Однако при сложении unsigned long и signed long второе число будет преобразовано в unsigned long, и таким же получится тип результата.

б) Неявные преобразования при присваивании

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

int k, m;

.........

m = 2.5*k;

выражение справа от знака присваивания имеет тип double (поскольку такой тип имеет первый сомножитель — константа 2.5), причем для четных значений целой переменной k результат умножения получится целочисленным (число типа double с нулевой дробной частью), а для нечетных k результат будет полуцелым. Перед присваиванием вычисленное значение выражения преобразуется к типу переменной m (т.е. int), при этом дробная часть числа будет отброшена (без округления). Если произведение было целочисленным, такое преобразование не приведет к потере информации (т.е. фактическое значение результата сохранится). Если же произведение было полуцелым, часть информации (а именно, дробная часть результата) потеряется.

Похожий эффект случается, когда длинное значение целого типа присваивается переменной, имеющей более короткий тип (например, значение int присваивается переменной типа short или даже char). В этом случае слишком длинное значение укорачивают, отрезая «лишние» старшие разряды (неважно, были они нулевыми или нет). Здесь также возможна потеря информации. Рассмотрим такой пример:

char c;

.........

c = 1027;

Выражение (в данном случае это просто константа) имеет тип int, а переменная c — более короткий тип char, размер которого всегда равен 1 байту. В итоге c получит значение 3. Чтобы понять, почему это так, нужно знать, как выглядит машинное двоичное представление целых чисел. Число 1027 в двоичной системе имеет вид 10000000011. В переменную c запишутся только младшие 8 разрядов. Три старших разряда (те, которые подчеркнуты) в результате преобразования будут отрезаны. Двоичное число 00000011 — это десятичное число 3.

Разумеется, могут быть преобразования и в обратную сторону — например, целое число присваивается переменной типа double, либо значение типа char присваивается переменной типа int. Такие преобразования безопасны с точки зрения потери информации.

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

Следует иметь в виду, что изложенные выше правила действуют также при передаче аргументов в функцию. Мы уже упоминали, что в языке C аргументы функций передаются «по значению». Если, например, определена некая функция

double fun(int n, double x)

{

... здесь что-то вычисляется ...

}

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

z = fun(5, 2);

то в момент вызова будут созданы временные переменные n и x типов int и double, причем n будет присвоено значение 5, а x — значение 2. Во втором случае будет выполнено неявное преобразование значения int к типу double, поскольку, согласно приведенному описанию, таков тип переменной x. Временные переменные n и x являются внутренними (локальными) для функции fun, причем они будут уничтожены, как только функция закончит работу и передаст вычисленное значение в вызвавшую программу. (Уничтожение временных переменных означает, что выделенный для них участок памяти будет считаться свободным и может быть использован для каких-либо иных целей.)

в) Явное преобразование (приведение) типа

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

int k, m;

double q;

.........

k = 2;

m = 3;

q = k/m;

В правой части последнего оператора присваивания вычисляется частное от деления двух величин типа int. Соответственно, операция выполняется по правилам целочисленного деления, и частное тоже будет иметь тип int. Таким образом, значение выражения получится равным нулю. После этого нуль типа int преобразуется в нуль типа double и результат будет записан в переменную q. Предположим, что по смыслу программы требовалось иное — k и m должны быть целыми, но в q нужно получить их частное с правильной дробной частью. Для этого достаточно, чтобы один из операндов при делении был числом с плавающей точкой (типа double) — тогда будут применены правила неявного преобразования, рассмотренные выше в параграфе (а).

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

int k, m;

double q;

.........

k = 2;

m = 3;

q = k;

q = q/m;

(последний оператор можно также записать как q /= m; с использованием совмещенной операции деления и присваивания). Здесь в предпоследнем присваивании значение k копируется в q, причем происходит неявное преобразование к типу double, и эта преобразованная копия участвует далее в операции деления. Получается частное типа double, которое и становится окончательным значением переменной q. У этого решения только один недостаток: пришлось написать лишний оператор (q = k;), чтобы вынудить компилятор преобразовать тип значения k. Сохранять его в виде промежуточного значения переменной q, в общем-то, не требовалось, но иначе воспользоваться результатом неявного преобразования не удается. Текст программы стал менее ясным (с первого взгляда можно и не сообразить, зачем переменной q присваивается значение k, если это значение тут же заменяется на другое).

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

k = 2;

m = 3;

q = (double)k/m;

Название типа в круглых скобках — это и есть операция приведения к указанному типу. В данном случае значение переменной k приводится к типу double, после чего выполняется его деление на величину типа int (по правилам смешанной арифметики).

Приведение типа — унарная операция, ее приоритет выше, чем у деления или умножения. Написанное выше выражение подразумевает, что тип значения k будет приведен к double раньше, чем начнется выполнение деления. Заметим, что операция приведения не влияет на тип самой переменной k и на значение, которое продолжает там храниться. Речь идет лишь о том, что из памяти, отведенной под переменную k, читается содержимое; затем прочитанное значение превращается в число с плавающей точкой и используется в операции деления.

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

int k;

char c;

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

k += 32;

c = (char)k;

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

последнее присваивание является потенциально опасным (возможна потеря или искажение информации вследствие усечения значения int до размера char). Обратите внимание, что указанное преобразование было бы выполнено без всякого приведения типа, автоматически, согласно правилам неявных преобразований при операциях присваивания. Наличие приведения типа лишь указывает, что это не случайная ошибка программиста, а намеренное действие. Возможно, суть алгоритма такова, что значение k+32 ни при каких условиях не выходит за рамки диапазона, допустимого для char. Быть может, наоборот, смысл данного фрагмента как раз и состоит в выделении младшего байта и отбрасывании старших разрядов значения. Так или иначе, написав приведение типа, программист взял всю ответственность за последствия на себя, и компилятор воспримет этот текст без возражений.

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

2 В английской терминологии эта операция называется type cast.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]