Главная страница
Навигация по странице:

  • 2.5. Арифметические операторы

  • 62 Глава 2. Типы, операторы и выражения

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

  • Упражнение 2.2.

  • 64 Глава 2. Типы, и выражения

  • 2.7. Преобразования типов 67

  • 68 _ Глава 2. Типы, операторы и выражения

  • 2.8. Операторы инкремента и декремента

  • 2.8. Операторы инкремента и декремента _ _ 69

  • 70 _ Глава 2. Типы, операторы и выражения

  • Б. Керриган, Д. Ритчи Язык программирования C. Б. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003


    Скачать 31.48 Mb.
    НазваниеБ. Керниган, Д. зык программирования и . Издание 3е, исправленное Перевод с английского под редакцией Вс. С. Штаркмана СанктПетербург 2003
    АнкорБ. Керриган, Д. Ритчи Язык программирования C.pdf
    Дата06.04.2017
    Размер31.48 Mb.
    Формат файлаpdf
    Имя файлаБ. Керриган, Д. Ритчи Язык программирования C.pdf
    ТипКнига
    #4546
    страница6 из 28
    1   2   3   4   5   6   7   8   9   ...   28
    60 Глава 2. Типы, операторы и выражения
    Имена в различных перечислениях должны отличаться друг от друга.
    Значения внутри одного перечисления могут
    Средство обеспечивает удобный способ присвоить константам имена, причем в отличие от ine значения констант при этом способе могут генерироваться автоматически. Хотя разрешается объявлять пере- менные типа enum, однако компилятор не обязан контролировать, входят ли присваиваемые этим переменным значения в их тип. Но сама возмож- ность такой проверки часто делает enum лучше, чем ine. Кроме того,
    отладчик получает возможность печатать значения переменных типа в символьном виде.
    2.4. Объявления
    Все переменные должны быть объявлены раньше, чем будут использо- при этом некоторые объявления могут быть получены неявно - из контекста. Объявление специфицирует тип и содержит список из од- ной или нескольких переменных этого типа, как, например, в int lower, upper, step;
    char с, line
    Переменные можно распределять по объявлениям произвольным обра- зом,
    что указанные выше списки можно записать и в следующем виде:
    int lower;
    int upper;
    int step;
    char c;
    char
    Последняя форма записи занимает больше места, тем не менее она луч- ше, поскольку позволяет добавлять к каждому объявлению комментарий.
    Кроме того, она более удобна для последующих модификаций.
    В своем объявлении переменная может быть инициализирована, как,
    например:
    char esc =
    int i = 0;
    int limit =
    float eps =
    Инициализация неавтоматической переменной осуществляется толь- ко один раз - перед тем, как программа начнет выполняться, при этом начальное значение должно быть константным выражением. Явно ини-

    2.5. Арифметические операторы
    автоматическая переменная получает начальное значение каждый раз при входе в функцию или блок, ее начальным значением мо- жет быть любое выражение. Внешние и статические переменные по умол- чанию получают нулевые значения. Автоматические переменные, явным образом не инициализированные, содержат неопределенные значения
    ("мусор").
    К любой переменной в объявлении может быть применен тор const для указания того, что ее значение далее не будет изменяться.
    const double e =
    const char
    =
    Применительно к массиву const указывает на то, что ни один из элементов не меняться. Указание const можно также применять к аргументу-массиву, чтобы сообщить, что функция не изме- няет этот массив:
    int strlen(const char[] );
    Реакция на попытку изменить переменную, помеченную const, зависит от реализации компилятора.
    2.5. Арифметические операторы
    Бинарными (т. е. с двумя арифметическими оператора- ми являются +, -, *, /, а также оператор деления по модулю %. Деление целых сопровождается отбрасыванием дробной части, какой бы она ни была. Выражение дает остаток от деления х на у и, следовательно, нуль, если х делится на у нацело. Например, год является високосным, если он делится на 4, но не де- лится на
    Кроме год является високосным, если он делится на 400.
    Следовательно,
    if
    % 4 == 0
    year % 100 != О ! i year % 400 == 0)
    високосный year);
    else невисокосный year);
    Оператор % к операндам типов и double не применяется. В какую сторону (в сторону увеличения или уменьшения числа) будет усечена дробная часть при выполнении / и каким будет знак результата операции
    % с отрицательными операндами, зависит от машины.

    62 Глава 2. Типы, операторы и выражения
    Бинарные операторы + и - имеют одинаковый приоритет, который ниже приоритета операторов / и %, который в свою очередь ниже приоритета унарных операторов + и -. Арифметические операции одного приоритет- ного уровня выполняются слева направо.
    В конце этой главы (параграф 2.12) приводится таблица 2.1, в которой представлены приоритеты всех операторов и очередность их выполнения.
    2.6. Операторы отношения
    и логические операторы
    Операторами отношения являются
    Все они имеют одинаковый приоритет. Сразу за ними идет приоритет операторов сравнения на равенство:
    Операторы отношения имеют более низкий приоритет, чем арифмети- ческие, поэтому выражение вроде i <
    будет выполняться так же,
    как i <
    т. е. как мы и ожидаем.
    Более интересны логические операторы и
    Выражения, между ко- торыми стоят операторы && или ! вычисляются слева направо. Вы- числение прекращается, как только становится известна истинность или ложность результата. Многие Си-программы опираются на это свойство,
    как, например, цикл из функции getline, которую мы приводили в главе 1:
    for (i = 0; i < lim-1 && (с =
    != EOF && с !=
    ++i)
    s[i] c;
    Прежде чем читать очередной нужно проверить, есть ли для него место в s, иначе говоря, сначала необходимо проверить соблюде- ние
    < lim-1. Если это условие не выполняется, мы не должны продолжать вычисление, в частности читать следующий символ. Так же было бы неправильным сравнивать с и EOF до обращения к следо- вательно, и вызов getchar, и присваивание должны выполняться перед указанной проверкой.
    Приоритет оператора && выше, чем таковой оператора однако их при- оритеты ниже, чем приоритет операторов отношения и равенства. Из ска- занного следует, что выражение вида i < lim-1 && (с =
    && с
    EOF
    не нуждается в дополнительных скобках. Но, так как приоритет ! = выше,

    2.7.
    типов 63
    чем приоритет присваивания, в

    ! =
    скобки необходимы, чтобы сначала выполнить присваивание, а затем срав- нение с '
    По определению численным результатом вычисления выражения от- ношения или логического выражения является если оно истинно, и О,
    если оно ложно.
    Унарный оператор ! преобразует ненулевой операнд в 0, а нуль в 1.
    Обычно оператор ! используют в конструкциях вида if что эквивалентно if (valid == 0)
    Трудно сказать, какая из форм записи лучше. Конструкция вида ! valid хорошо читается ("если не но в более сложных выражениях может оказаться, что ее не так-то легко понять.
    Упражнение 2.2. Напишите эквивалентный приведенному выше f
    не пользуясь операторами && и
    2.7. Преобразования типов
    Если операнды оператора принадлежат к разным типам, то они приво- дятся к некоторому общему типу. Приведение выполняется в соответствии с небольшим числом правил. Обычно автоматически производятся лишь те преобразования, которые без какой-либо потери информации превра- щают операнды с меньшим диапазоном значений в операнды с большим диапазоном, как, например, преобразование целого в число с плавающей точкой в выражении вроде f + i. Выражения, не имеющие смысла, напри- мер число с плавающей точкой в роли индекса, не допускаются. Выраже- ния, в которых могла бы теряться информация (скажем, при присваива- нии длинных целых переменным более коротких типов или при присваи- вании значений с плавающей точкой целым переменным), могут повлечь за собой предупреждение, но они
    Значения типа
    - это просто малые целые, и их можно свободно использовать в арифметических выражениях, что значительно облегчает всевозможные манипуляции с символами. В качестве примера приведем простенькую реализацию функции преобразующей последователь- ность цифр в ее числовой эквивалент.

    64 Глава 2. Типы,
    и выражения
    /* atoi: преобразование s в целое */
    int atoi(char
    {
    int i, n;
    n = 0;
    for (i = 0; s[i] >=
    &&
    s[i] <=
    ++i)
    n = 10 * n + (s[i] - return n;
    Как мы уже говорили в главе выражение s[i]
    дает числовое значение символа,
    в так как значения '
    и пр. образуют непрерывную возрастающую последовательность.
    Другой пример приведения к int связан с функцией кото- рая одиночный символ из набора ASCII, если он является заглавной бук- вой, превращает в строчную. Если же символ не является заглавной бук- вой,
    его не изменяет.
    /* lower: преобразование с в строчную; только для ASCII */
    int lower(int с)
    {
    if (с >=
    &&
    с <=
    return с +
    - else return с;
    }
    В случае ASCII эта программа будет работать правильно, потому что меж- ду одноименными буквами верхнего и нижнего регистров - одинаковое расстояние (если их рассматривать как числовые значения). Кроме того,
    латинский алфавит — плотный, т. е. между буквами А и Z расположены только буквы. Для набора EBCDIC последнее условие не выполняется,
    и поэтому наша программа в этом случае будет преобразовывать не толь- ко буквы.
    Стандартный заголовочный файл описанный в приложе- нии В, определяет семейство функций, которые позволяют проверять и преобразовывать символы независимо от символьного набора. Напри- мер, функция возвращает букву с в коде нижнего регистра, если она была в коде верхнего регистра, поэтому tolower - универсальная за- мена функции рассмотренной выше. Аналогично проверку с
    >=
    && с <=

    2.7. Преобразования типов - 65
    можно заменить на
    Далее мы будем пользоваться функциями из h>.
    Существует одна тонкость, касающаяся преобразования символов в целые числа: язык не определяет, являются ли переменные типа char знаковыми или беззнаковыми. При преобразовании char в int может ли когда-нибудь получиться отрицательное целое? На машинах с разной ар- хитектурой ответы могут отличаться. На некоторых машинах значение типа char с единичным старшим битом будет превращено в отрицатель- ное целое (посредством "распространения знака"). На других - преобра- зование char в int осуществляется добавлением нулей слева, и, таким образом, получаемое значение всегда положительно.
    Гарантируется, что любой символ из стандартного набора печатаемых символов никогда не будет отрицательным числом, поэтому в выражени- ях такие символы всегда являются положительными операндами.
    извольный восьмибитовый код в переменной типа на одних маши- нах может быть отрицательным числом, а на других - положительным.
    Для совместимости переменные в которых хранятся несимволь- ные данные, следует специфицировать явно как signed или unsigned.
    Отношения вроде i > j и логические выражения, перемежаемые опера- торами && и ! определяют выражение-условие, которое имеет значение если оно истинно, и 0, если ложно. Так, присваивание d = с >=
    &&
    с <=
    установит d в значение если с есть цифра, и 0 в противном случае. Одна- ко функции, подобные isdigit, в качестве истины могут выдавать любое ненулевое значение. В местах проверок внутри while, for и пр. "исти- на" просто означает "не нуль".
    Неявные арифметические преобразования, как правило, осуществля- ются естественным образом. В общем случае, когда оператор вроде + или с двумя операндами (бинарный оператор) имеет разнотипные операн- ды, прежде чем операция начнет выполняться, "низший" тип повышается
    до "высшего". Результат будет иметь высший тип. В параграфе 6 приложе- ния А правила преобразования сформулированы точно. Если же в выра- жении нет беззнаковых операндов, можно удовлетвориться следующим набором неформальных правил:
    • Если какой-либо из операндов принадлежит типу long double, то и дру- гой приводится к long double.
    • В противном случае, если какой-либо из операндов принадлежит типу double, то и другой приводится к double.
    1116

    66 Глава 2. Типы, операторы и выражения
    • В противном если какой-либо из операндов принадлежит типу то и другой приводится к
    • В противном случае операнды типов char и s h o r t int.
    • И наконец, если один из операндов типа long, то и другой приводится к long.
    Заметим, что операнды типа не приводятся автоматически к типу double; в этом данная версия языка отличается от первоначальной. Вооб- ще говоря, математические функции, аналогичные собранным в теке h>, базируются на вычислениях с двойной точностью. В основ- ном используется для экономии памяти на больших массивах и не так часто - для ускорения счета на тех машинах, где арифметика с двой- ной точностью слишком дорога с точки зрения расхода времени и памяти.
    Правила преобразования усложняются с появлением операндов типа unsigned. Проблема в том, что сравнения знаковых и беззнаковых значе- ний зависят от размеров целочисленных типов, которые на разных маши- нах могут отличаться. Предположим, что значение типа int занимает
    16 битов, а значение типа long - 32 бита. Тогда
    <
    поскольку при- надлежит типу unsigned int и повышается до типа signed long. Но
    >
    повышается типа unsigned long и воспринимается как боль- шое положительное число.
    Преобразования имеют место и при присвоениях: значение правой ча- сти присвоения приводится к типу левой части, который и является ти- пом результата.
    Тип превращается путем распространения знака или другим описанным выше способом.
    Тип long i n t преобразуются в short int или в значения типа char пу- тем отбрасывания старших разрядов. Так, в int i;
    char с;
    i
    = с;
    с =
    i;
    значение с не изменится. Это справедливо независимо от того, распрост- раняется знак при переводе c h a r в int или нет. Однако, если изменить очередность присваиваний,
    потеря информации.
    Если х принадлежит типу a i - типу int, то и х = i, и i = x вызовут преобразования, причем перевод float в int сопровождается отбрасыва- нием дробной части. Если double переводится во то значение либо округляется, либо обрезается; это зависит от реализации.
    Так как аргумент в вызове функции есть выражение, при передаче его функции также возможно преобразование типа. При отсутствии прото-

    2.7. Преобразования типов 67
    типа функции аргументы типа c h a r и short переводятся в int, a
    - в double. Вот почему мы объявляли аргументы типа int или double даже тогда, когда в вызове функции использовали аргументы типа char или float.
    И наконец, для любого выражения можно явно ("насильно") указать преобразование его типа, используя унарный оператор, называемый при-
    ведением. Конструкция вида
    выражение
    приводит выражение к указанному в скобках типу по перечисленным выше правилам. Смысл операции приведения можно представить себе так:
    выражение как бы присваивается некоторой переменной указанного и эта переменная используется вместо всей конструкции. Например, би- блиотечная функция рассчитана на аргумент типа double и выдает чепуху, если ей подсунуть что-нибудь другое описана в
    Поэтому, если п имеет целочисленный тип, мы можем написать sqrt((double) n)
    и перед тем, как значение п будет передано функции, оно будет переведе- но в double. Заметим, что операция приведения всего лишь вырабатывает
    значение п указанного типа, но саму переменную п не затрагивает. При- оритет оператора приведения столь же высок, как и любого унарного опе- ратора, что зафиксировано в таблице, помещенной в конце этой главы.
    В том случае, когда аргументы описаны в прототипе функции,
    тому и следует быть, при вызове функции нужное преобразование выполняет- ся автоматически. Так, при наличии прототипа функции sq double sqrt(double);
    перед обращением к sq rt в присваивании root2 = sqrt(2);
    целое 2 будет переведено в значение автоматически без явного указания операции приведения.
    Операцию приведения проиллюстрируем на переносимой версии ге- нератора псевдослучайных чисел и функции, инициализирующей "семя".
    И генератор, и функция входят в стандартную библиотеку.
    unsigned long int next = 1;
    /* rand: возвращает псевдослучайное целое
    */
    int rand(void)
    {
    next = next * 1103515245 + 12345;

    68 _ Глава 2. Типы, операторы и выражения
    return (unsigned int)(next/65536) % 32768;
    /* srand: устанавливает "семя" для rand() */
    void srand(unsigned int seed)
    <
    next = seed;
    }
    Упражнение 2.З. Напишите функцию h t o l ( s ) , которая преобразует последовательность цифр, начинающуюся с Ох или
    ОХ, в соответствующее целое.
    цифрами являются символы f,
    F.
    2.8. Операторы инкремента и декремента
    В Си есть два необычных оператора, предназначенных для увеличения и уменьшения переменных. Оператор инкремента ++ добавляет 1 к своему операнду, а оператор декремента — вычитает 1. Мы уже неоднократно ис- пользовали ++ для наращивания значения переменных, как, например, в if (с ==
    Необычность операторов ++ и — в том, что их можно использовать и как префиксные (помещая перед переменной:
    и как постфиксные
    (помещая после переменной: п++)
    В обоих случаях значение п увеличивается на 1, но выражение ++п увеличивает п до как его зна- чение будет использовано, а п++ - после того. Предположим, что п со- держит 5, тогда
    х = п++;
    -
    установит х в значение 5, а х = ++п;
    установит х в значение 6. И в том и другом случае п станет равным 6. Опе- раторы инкремента и декремента можно применять только к переменным.
    Выражения вроде
    )++ недопустимы.
    Если требуется только увеличить или уменьшить значение перемен- ной (но не получить ее значение), как например
    (С ==

    2.8. Операторы инкремента и декремента _
    _ 69
    то безразлично, какой оператор выбрать - префиксный или постфикс- ный. Но существуют ситуации, когда требуется оператор вполне опреде- ленного типа. Например, рассмотрим функцию с), которая уда- ляет из строки s все символы, совпадающие с с:
    /* squeeze: удаляет все с из s */
    void int с)
    {
    int i, j;
    for (i
    = j = 0; s[i] !=
    if
    (s[i] != c)
    Каждый раз, когда встречается символ, отличный от с, он копируется в текущую j -ю позицию, и только после этого переменная j увеличива- ется на подготавливаясь таким образом к приему следующего символа.
    Это в точности совпадает со следующими действиями:
    if
    !=
    с)
    {
    Другой пример - функция getline, которая нам известна по главе 1.
    Приведенную там запись if (с ==
    {
    s[i]
    = с;
    можно переписать более компактно:
    if
    (с ==
    s[i++] = с;
    В качестве третьего примера рассмотрим стандартную функцию st rcat которая строку t помещает в конец строки s.
    что в s достаточно места, чтобы разместить там суммарную строку. Мы написали st так, что она не возвращает никакого результата. На самом деле би- блиотечная st возвращает указатель на результирующую строку.
    /* strcat: помещает t в конец s; s достаточно велика */
    strcat (char s[], char

    70 _ Глава 2. Типы, операторы и выражения
    i = j = 0;
    while (s[i] !=
    /* находим конец s */
    i++;
    while
    =
    !=
    /* копируем t */
    При копировании очередного символа из t в s постфиксный оператор ++
    применяется и к i, и к j, чтобы на каждом шаге цикла переменные i и j правильно отслеживали позиции перемещаемого символа.
    Упражнение 2.4. Напишите версию функции которая удаляет из s1 все символы, встречающиеся в строке s2.
    Упражнение 2.5. Напишите функцию которая возвращает либо ту позицию в где стоит первый символ, совпавший с любым из символов в s2, либо -1 (если ни один символ из s1 не совпадает с символами из s2). (Стандартная библиотечная функция делает то же самое,
    но выдает не номер позиции символа, а указатель на символ.)
    1   2   3   4   5   6   7   8   9   ...   28


    написать администратору сайта