Отметим, что приведенные выше операторы присваивания становятся инструкциями присваивания, если в конце каждого из них поставить точку с запятой:
x = 4.5; y = x - 3;
Приведем примеры некорректных присваиваний. define MAX 100
const int n = 10; int main(void)
{
n = 1; |
/* Ошибка */ |
MAX = 200; |
/* Ошибка */ |
return 0; |
|
}
При компиляции этого программного кода в среде Builder v. 6 появились два сообщения об ошибках (error). Первое из них относилось к инструкции “n = 1;” и содержало следующее сообщение “Cannot modify const object” (Нельзя модифицировать константный объект). Второе сообщение было связано с инструкцией “MAX = 200;” и содержало сообщение “Lvalue required” (Lvalue требуется).
Язык Си позволяет в одном выражении присвоить значение нескольким переменным. Такое присваивание называется множественным. Пусть имеются три переменные k, n, и m, имеющие тип int. Тогда допустима следующая инструкция присваивания:
k = n = m = 4;
В результате вычисления приведенного выше выражения все три переменные получат значение, равное 4.
Составной оператор присваивания имеет следующий формат a @= b
Здесь @ – один из десяти операторов: +, -, *, /, %, <<, >>, &, |, ^. Программная конструкция
a @= b
полностью эквивалентна программной конструкции a = a @ b
Приведем ряд примеров. Пусть имеются следующие операторы присваивания, записанные в форме простого присваивания, для которых имеется возможность записать их в форме составного присваивания:
x = x + dx p = p * i
Эквивалентные этим простым присваиваниям составные присваивания будут иметь следующий вид:
x += dx p *= i
36
Если при выполнении оператора присваивания тип переменной, стоящей слева от оператора присваивания, не совпадает с типом значения выражения, находящегося справа, то выполняется преобразование типа в соответствии со следующим правилом: тип значения выражения приводится к типу переменной.
Следует учитывать, что при присваивании возможна потеря информации. Это может иметь место в том случае, когда диапазон значений типа значения выражения превосходит диапазон типа переменной. Это, например, возможно при выполнении следующего фрагмента программы.
int main(void)
{
short small; long big; float f; double d; /* … */ small = big; f = d;
big = d; /* … */
}
Все приведенные выше присваивания относятся к разряду потенциально опасных операций, при которых возможна потеря информации. Рассмотрим первое из этих присваиваний: small = big. В этом случае обе переменные имеют целочисленные значения. Однако диапазон значений типа переменной big превосходит диапазон значений переменной small. Действительно, переменная small имеет тип short, а переменная big – тип long. Диапазон значений типа long превосходит диапазон значений типа short. Такие присваивания в языке Си выполняются путем отбрасывания старших разрядов записываемого в переменную значения. Во втором присваивании: f = d переменной типа float присваивается значение типа double. При таком присваивании возможна потеря информации
Указатели чрезвычайно широко применяются при программировании на языке Си. Например, указатели используются в вызове функции scanf() (см. программный код, приведенный в пункте. 1.2.4). Понять работу этой функции, не располагая хотя бы самыми общими сведениями об указателях, весьма затруднительно. В связи с этим ниже приводятся начальные сведения об указателях. Представляется, что приводимые сведения позволят уяснить механизм возврата результатов вычислений, полученных в функции, через ее параметры.
37
Указатели предназначены для работы с адресами. Существуют две разновидности указателей. К первой из них относятся указатели– переменные. Такие указатели предназначены для хранения адресов. Ко второй разновидности относятся указатели–выражения, служащие для вычисления (получения) адресов.
Ограничимся рассмотрением одной категории указателей–выражений, которая используется для получения адреса объекта. Такой вид указателя встречается при работе с функцией scanf(). Пусть x – переменная одного из арифметических типов. Тогда применение к этой переменной унарного оператора взятия адреса & позволяет образовать указатель–выражение следующего вида &x.
Простейший формат объявления указателя–переменной имеет
следующий вид
тип_объекта *имя_переменной_указателя
Приведем пример объявления указателя, предназначенного для хранения адресов объектов типа double
double *px;
Указатель – переменную можно инициализировать во время объявления. Такая инициализация выполнена с помощью указателя–выражения в приводимом ниже коде.
double x = 10.5;
double *px = &x;
Здесь &x – указатель – выражение, которое позволяет получить адрес объекта x.
Основным оператором при работе с указателями является унарный оператор разыменования (*). С помощью операции разыменования можно обратиться к объекту, с которым работает указатель (на который установлен
указатель.) Приведем пример double x = 10.5; double *px = &x;
*px = 10; printf(“*px=%0.4g\n”, *px);
В этом примере выражение *px используется дважды. Один раз для записи в объект, на который установлен указатель, а второй раз – для чтения.
Полезной является точка зрения, в соответствии с которой указатель – это программный компонент, управляющий доступом к другому программному компоненту. В этом примере с помощью указателя px, получаем доступ к памяти, отведенной для переменной x.
Теперь представим, что указатель px и объект x, с которым указатель работает, принадлежат различным функциям. В этом случае одна функция получает доступ к памяти, выделенной в другой функции. Это дает возможность функции возвращать в точку вызова результаты, полученные в теле вызываемой функции, используя аппарат параметров. Для иллюстрации этой возможности рассмотрим библиотечную функцию modf(), которая используется для извлечения из действительного числа целой и дробной части.
38
Прототип рассматриваемой функции имеет следующий вид:
#include<math.h>
double modf(double value, double *iptr);
Функция modf() имеет два параметра. С помощью параметра value в функцию передается исходное действительное число. Функция извлекает из переданного числа дробную и целую части. Функция modf() возвращает дробную часть, а целая часть сохраняется в переменной, на которую будет установлен указатель iptr.
Ниже приводится программый код, иллюстрирующий работу рассматриваемой функции.
#include <stdio.h> #include<math.h> #include<conio.h> int main(void)
{
double n, x;
x = modf(10.2, &n);
printf("n= %0.4g x= %0.4g", n, x); getch();
return 0;
}
В приведенном программном коде, дробная часть исходного числа, которую вернула функция modf(), сохранена функцией main() в переменной x, а целая часть оказалась записанной функцией modf() в переменную n. Протокол работы с рассматриваемой программой, который приведен ниже, подтверждает правильность выполненного анализа:
n= 10 x= 0.2
При разработке программ часто возникает необходимость в принятии решений. В строго типизированных языках для этих целей используются логические (булевские) типы данных. В классическом языке Си логический тип отсутствовал. Для принятия решений использовались некоторые соглашения относительно замены логических значений числами. В связи с тем, что эти соглашения должны обеспечивать взаимные переходы между числами и логическими значениями необходимо учитывать следующее:
●Принцип интерпретации числовых значений в качестве логических значений
●Интерпретация логических значений в качестве чисел.
В языке Си эти соглашения выглядят следующим образом:
●Любое число (целое или вещественное), отличное от нуля, может интерпретироваться в соответствующем контексте как логическое значение true, а число, равное нулю – как логическое значение false.
●Логическое значение true в контексте, требующем числовой интерпретации, воспринимается как целое число равное 1, логическое значение false – воспринимается как целое число равное 0.
39
При этом оказывается, что в качестве “источников” логических значений можно использовать следующие программные конструкции:
●Обычные арифметические выражения (нулевое значение такого выражения соответствует логическому значению false, а любое не нулевое значение - true).
●Отношения и сравнения на равенство.
●Выражения, использующие логические операторы, в качестве операндов которых служат арифметические выражения, отношения и сравнения на равенство.
Ближайшей нашей задачей является рассмотрение отношений и сравнений на равенство.
Как уже отмечалось ранее, отношения и сравнения на равенство используются для получения логических значений. Это обеспечивается путем оценки истинности отношений и сравнений на равенство. Причем в том случае, когда истинность имеет место, указанное выражение возвращает значение 1, в противном случае возвращается значение 0.
В таблице приводятся названия рассматриваемых операций, математические знаки операций, операторы языка Си и примеры их
применения |
|
|
|
Название операции |
Математический знак |
Оператор Си |
Пример |
меньше |
< |
< |
a < b |
не меньше |
³ |
>= |
a >= b |
больше |
> |
> |
a > b |
не больше |
£ |
<= |
a <= b |
равно |
= |
== |
a == b |
не равно |
¹ |
!= |
a != b |
Первые четыре оператора, приведенные в таблице, имеют одинаковый приоритет, а операторы сравнения на равенство имеют приоритет на один уровень ниже. Рассматриваемые операторы имеют более низкий приоритет по сравнению с арифметическими операторами. Пусть имеется следующий
фрагмент программного кода:
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . */ int n = 3;
printf(“n > 2 = %d n < -1 = %d\n”, n > 2, n < -1);
/* . . . . . . . . . . . . . . . . . . . . . . . . . . . */
В этом фрагменте кода оцениваются значения отношений n > 2 и n < -1 для случая, когда значение переменной n равно 3. Нетрудно видеть, что первое отношение должно принимать значение true (истина), а второе отношение – значение false (ложь). Обратимся теперь к выводу, полученному
в результате выполнения данного фрагмента, который имеет следующий вид: n > 2 = 1 n < -1 = 0
С учетом принятых в языке Си относительно кодирования булевских значений соглашений в качестве значения отношения n > 2 выведено число 1, в качестве значения второго отношения – число 0.
40