пятница, 28 февраля 2014 г.

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

Операторами отношения являются
> >= < <=
Все они имеют одинаковый приоритет. Сразу за ними идет приоритет операторов сравнения на равенство:
== !=
Операторы отношения имеют более низкий приоритет, чем арифметические, поэтому выражение вроде i < lim - 1 будет выполняться так же, как
i < (lim - 1).

Более интересны логические операторы && и ||. Выражения, между которыми стоят операторы && или ||, вычисляются слева направо. Вычисление прекращается, как только становится известна истинность или ложность результата.

for (i = 0; i < lim - 1 && (с = getchar()) != EOF && с != '\n'; ++i)
  s[i] = c;

Прежде чем читать очередной символ, нужно проверить, есть ли для него место в массиве s, иначе говоря, сначала необходимо проверить соблюдение условия i < lim - 1. Если это условие не выполняется, мы не должны продолжать вычисление, в частности читать следующий символ. Так же было бы неправильным сравнивать с и EOF до обращения к getchar; следовательно, и вызов getchar, и присваивание должны выполняться перед указанной проверкой.

Приоритет оператора && выше, чем таковой оператора ||, однако их приоритеты ниже, чем приоритет операторов отношения и равенства. Из сказанного следует, что выражение вида
i < lim - 1 && (с = getchar()) != '\n' && с != EOF
не нуждается в дополнительных скобках. Но, так как приоритет != выше, чем приоритет присваивания, в (с = getchar()) ! = '\n' скобки необходимы, чтобы сначала выполнить присваивание, а затем сравнение с '\n' .

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

Унарный оператор ! преобразует ненулевой операнд в 0, а нуль в 1. Обычно оператор ! используют в конструкциях вида
if (!valid)
что эквивалентно
if (valid == 0)

четверг, 27 февраля 2014 г.

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

Бинарными (т. е. с двумя операндами) арифметическими операторами являются +, -, *, /, а также оператор деления по модулю %.

Деление целых сопровождается отбрасыванием дробной части.

#include <stdio.h>
int main(void)
{
    int a = 17;
    int b = 3;
    float c = 0;
    c = a / b;
    printf("%f\n", c);

    return 0;
}
$ ./a.out
5.000000

Тогда как 17 / 3 = 5,6666666666666666666666666666667

Картина меняется, если одну из переменных (либо float a = 17, либо float b = 3) опредлеить как float. Тогда
$ ./a.out
5.666667

Выражение x % y дает остаток от деления x на y и 0 (нуль), если x делится на y нацело.

Например, год является високосным, если он делится на 4, но не делится на 100. Кроме того, год является високосным, если он делится на 400.

#include <stdio.h>
int main(void)
{
    int year;
    for (year = 2004; year <= 2014; ++year)
    {
        if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
            printf("%d високосный год\n", year);
        else
            printf("%d невисокосный год\n", year);
    }
    return 0;

}

Оператор % к операндам типов float и double не применяется.

1. #include <stdio.h>
2. int main(void)
3. {
4.     printf("17.0 % 5.0 = %d\n", 17.0 % 5.0);
5.     return 0;
6. }

test.c: In function 'main':

test.c:4:38: error: invalid operands to binary % (have 'double' and 'double')

В какую сторону (в сторону увеличения или уменьшения числа) будет усечена дробная часть при выполнении / зависит от машины.
В моем случае, в сторону уменьшения.

Каким будет знак результата операции % с отрицательными операндами, так же, зависит от машины. В моем случае знак "-".

#include <stdio.h>
int main(void)
{
    signed int x = -8;
    signed int y = -5;
    printf("-8 %% -5 = %d\n", x % y);

    return 0;
}
$ ./a.out
-8 % -5 = -3

Бинарные операторы + и - имеют одинаковый приоритет, который ниже приоритета операторов *, / и %, который в свою очередь ниже приоритета унарных операторов + и -. Арифметические операции одного приоритетного уровня выполняются слева направо.

Объявления

Все переменные должны быть объявлены раньше, чем будут использоваться. Объявление специфицирует тип и содержит список из одной или нескольких переменных этого типа.
int lower, upper, step; 
char с, line [1000];
int i;
В своем объявлении переменная может быть инициализирована.
int i = 0; 
int limit = MAXLINE + 1;
Инициализация неавтоматической переменной осуществляется только один раз — перед тем, как программа начнет выполняться, при этом начальное значение должно быть константным выражением. Явно инициализируемая автоматическая переменная получает начальное значение каждый раз при входе в функцию или блок, ее начальным значением может быть любое выражение. Внешние и статические переменные по умолчанию получают нулевые значения. Автоматические переменные, явным образом не инициализированные, содержат неопределенные значения ("мусор").

К любой переменной в объявлении может быть применен квалификатор const для указания того, что ее значение далее не будет изменяться.
const double e = 2.71828182845905; 
const char msg[] = "предупреждение: ";
Применительно к массиву квалификатор const указывает на то, что ни один из его элементов не будет меняться. Указание const можно также применять к аргументу-массиву, чтобы сообщить, что функция не изменяет этот массив:
int strlen(const char[]);
Реакция на попытку изменить переменную, помеченную квалификатором const, зависит от реализации компилятора.

1. #include <stdio.h>
2. int main(void)
3. {
4.     const double e = 2.71828182845905;
5.     printf("Число Эйлера: %lf\n", e);
6.
7.     e = 2.718282; /* Попытаемся изменить переменную */
8.
9.     return 0;
10. }

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

test.c: In function 'main':
test.c:7: error: assignment of read-only variable 'e'

вторник, 18 февраля 2014 г.

Константы

При рассмотрении данной темы я буду использовать оператор sizeof, который возвращает размер в байтах объекта или типа данных.
$ vim test.c
#include <stdio.h>

#define INTEGER 1       /* целая константа, нет окончания */
#define LONG 1L         /* константа типа long */
#define UNLONG 1UL      /* константа типа unsigned long */
#define LONGLONG 1LL    /* константа типа long long */
#define FLOAT 1.0F      /* константа типа float */
#define DOUBLE 1.0      /* константа с плавающей точкой, без окончания, тип double */
#define LONGDBL 1.0L    /* константа типа long double */

int main()
{
    printf("Размер типа \"INTEGER\", байт - %d\n", sizeof(INTEGER));
    printf("Размер типа \"LONG\", байт - %d\n", sizeof(LONG));
    printf("Размер типа \"UNSIGNED LONG\", байт - %d\n", sizeof(UNLONG));
    printf("Размер типа \"LONG LONG\", байт - %d\n", sizeof(LONGLONG));
    printf("Размер типа \"FLOAT\", байт - %d\n", sizeof(FLOAT));
    printf("Размер типа \"DOUBLE\", байт - %d\n", sizeof(DOUBLE));
    printf("Размер типа \"LONG DOUBLE\", байт - %d\n", sizeof(LONGDBL));

    return 0;
}
$ cc -g -O0 -Wall -o a.out test.c
$ ./a.out
Размер типа "INTEGER", байт - 4
Размер типа "LONG", байт - 4
Размер типа "UNSIGNED LONG", байт - 4
Размер типа "LONG LONG", байт - 8
Размер типа "FLOAT", байт - 4
Размер типа "DOUBLE", байт - 8
Размер типа "LONG DOUBLE", байт - 12

Целое значение помимо десятичного может иметь восьмеричное или шестнадцатеричное представление.
$ vim test_2.c
#include <stdio.h>

#define OCTAL 037
#define HEX 0x1F

int main(void)
{
    printf("Восьмиричная константа %d\n", OCTAL);
    printf("Шестнадцатеричная константа %d\n", HEX);

    return 0;

}
$ cc -g -O0 -Wall -o a.out test_2.c
$ ./a.out
Восьмиричная константа 31
Шестнадцатеричная константа 31

Символьная константа есть целое, записанное в виде символа, обрамленного одиночными кавычками. Значением символьной константы является числовой код символа из набора символов на данной машине. Например, символьная константа '0' в кодировке ASCII имеет значение 48, которое никакого отношения к числовому значению 0 не имеет.
$ vim test_3.c
#include <stdio.h>

#define SYMB '0'

int main(void)
{
    printf("Символ: %c\n", SYMB);
    printf("Код ASCII: %d\n", SYMB);

    return 0;
}
$ ./a.out
$ cc -g -O0 -Wall -o a.out test_2.c 
Символ: 0
Код ASCII: 48

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

Некоторые символы в символьных и строковых константах записываются с помощью эскейп-последовательностей, например \n (символ новой строки); такие последовательности изображаются двумя символами, но обозначают один. Кроме того, произвольный восьмеричный код можно задать в виде
'\ooo'
где ooo — одна, две или три восьмеричные цифры (0…7) или
'\xhh'
где hh — одна, две или более шестнадцатеричные цифры (0…9, а…f, А…F).
$ vim test_4.c
#include <stdio.h>
/* Восьмеричный код */
#define VTAB '\013'     /* вертикальная табуляция в ASCII */
#define BELL '\007'     /* звонок в ASCII */
int main(void)
{
    printf("%c", VTAB);
    printf("%c\n", BELL);
    return 0;
}
$ vim test_5.c
#include <stdio.h>
/* Шестнадцатеричный код */
#define VTAB '\xb'     /* вертикальная табуляция в ASCII */
#define BELL '\x7'     /* звонок в ASCII */
int main(void)
{
    printf("%c", VTAB);
    printf("%c\n", BELL);
    return 0;
}

Набор эскейп-последовательностей:
сигнал-звонок
\b возврат-на-шаг (забой)
\f перевод-страницы
\n новая-строка
\r возврат-каретки
\t горизонтальная-табуляция
\v вертикальная-табуляция
\\ обратная наклонная черта
\? знак вопроса
\’ одиночная кавычка
\” двойная кавычка
\ooo восьмеричный код
\xhh шестнадцатеричный код

Символьная константа '\0' — это символ с нулевым значением, так называемый символ NULL. Вместо просто 0 часто используют запись '\0', чтобы подчеркнуть символьную природу выражения, хотя и в том и другом случае запись обозначает нуль.
$ vim test_6.c
#include <stdio.h>
#define NULL_1 '\0'
#define NULL_2 NULL
int main(void)
{
    if (NULL_1 == NULL_2){
        printf("\'\\0\' == null\n");
        printf("%d\n", NULL_1);
        printf("%d\n", NULL_2);
    }
    return 0;

}
$ cc -g -O0 -Wall -o a.out test_6.c
test_6.c: In function 'main':
test_6.c:9:2: warning: format '%d' expects type 'int', but argument 2 has type 'void *'
$ ./a.out
'\0' == null
0
0

Константные выражения — это выражения, оперирующие только с константами. Такие выражения вычисляются во время компиляции, а не во время выполнения.
$ vim test_7.c
#include <stdio.h>
#define OFFSET 1
int main(void)
{
    int massiv [5 + OFFSET]; /* константное выражение */
    int i;
    massiv[0] = 0;
    massiv[1] = 1;
    massiv[2] = 2;
    massiv[3] = 3;
    massiv[4] = 4;
    massiv[5] = 5;
    printf("За пределами массива %d\n", massiv[6]);
    for (i = 0; i < 6; ++i)
            printf("%d ", massiv[i]);
    printf("\n");
    return 0;

}
$ cc -g -O0 -Wall -o a.out test_7.c

$ ./a.out
За пределами массива -1215582220
0 1 2 3 4 5

Строковая константа, или строковый литерал, — это нуль или более символов, заключенных в двойные кавычки, как, например,
"Я строковая константа"
или
"" /* пустая строка */
Кавычки не входят в строку, а служат только ее ограничителями. Так же, как и в символьные константы, в строки можно включать эскейп-последовательности. Строковые константы можно конкатенировать ("склеивать") во время компиляции.

Фактически строковая константа — это массив символов. Во внутреннем представлении строки в конце обязательно присутствует нулевой символ '\0', поэтому памяти для строки требуется на один байт больше, чем число символов, расположенных между двойными кавычками. Это означает, что на длину задаваемой строки нет ограничения, но чтобы определить ее длину, требуется просмотреть всю строку.

В Си имеется еще один вид константы — константа перечисления. Перечисление — это список целых констант.
$ vim test_8.c
#include <stdio.h>
int main(void)
{
    /* значения констант неспецифицированы,
     * значения констант генерируются автоматически начиная с 0 */
    enum boolean {NO, YES};
    printf("NO - %d\n", NO);
    printf("YES - %d\n", YES);

    /* все значения констант специфицированы */
    enum escapes {BELL = '\a', BACKSPACE = '\b', TAB = '\t',
    NEWLINE = '\n', VTAB = '\v', RETURN = '\r'};

    /* Прогрессия начинается от последнего специфицированного значения */
    enum months {JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC};
    printf("Значение FEB %d\n", FEB);

    return 0;
}
$ cc -g -O0 -Wall -o a.out test_8.c
$ ./a.out
NO - 0
YES - 1
Значение FEB 2

Имена в различных перечислениях должны отличаться друг от друга. Значения внутри одного перечисления могут совпадать.

среда, 12 февраля 2014 г.

Упражнение 2.1.

Напишите программу, которая будет выдавать диапазоны значений типов char, shortint и long, описанных как signed и как unsigned, с помощью печати соответствующих значений из стандартных заголовочных файлов и путем прямого вычисления. Определите диапазоны чисел с плавающей точкой различных типов. Вычислить эти диапазоны сложнее.

Write a program to determine the ranges of char, short, int, and long variables, both signed and unsigned, by printing appropriate values from standard headers and by direct computation. Harder if you compute them: determine the ranges of the various floating-point types.

вторник, 11 февраля 2014 г.

Типы и размеры данных

Базовые типы языка программирования Си:

char - единичный байт, который может содержать один символ;
int - целое, обычно отображающее естественное представление целых на машине;
float - число с плавающей точкой одинарной точности;
double - число с плавающей точкой двойной точности.

Имеется также несколько квалификаторов, которые можно использовать вместе с указанными базовыми типами. Например, квалификаторы short и long применяются к целым:
short int sh;
long int counter;
В таких объявлениях слово int можно опускать, что обычно и делается.

Размеры объектов типов short int, int и long int зависят от реализации компилятора, но должны быть соблюдены следующие правила:

  • значения типов short и int представляются по крайней мере 16 битами;
  • типа long — по крайней мере 32 битами;
  • размер short не больше размера int, который в свою очередь не больше размера long.

Квалификаторы signed (со знаком) или unsigned (без знака) можно применять к типу char и любому целочисленному типу. Значения unsigned всегда положительны или равны нулю и подчиняются законам арифметики по модулю
,
где n — количество битов в представлении типа. Так, если значению char отводится 8 битов, то unsigned char имеет значения в диапазоне от 0 до 255, a signed charот -128 до 127. Являются ли значения типа просто char знаковыми или беззнаковыми, зависит от реализации, но в любом случае коды печатаемых символов положительны.

Тип long double предназначен для арифметики с плавающей точкой повышенной точности. Как и в случае целых, размеры объектов с плавающей точкой зависят от реализации; float, double и long double могут представляться одним размером, а могут двумя или тремя разными размерами.

Именованные константы для всех размеров вместе с другими характеристиками машины и компилятора содержатся в стандартных заголовочных файлах <limits.h> и <float.h>.

Имена переменных

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

Большие (прописные) и малые (строчные) буквы различаются, так что х и X — это два разных имени.

Минимальные требования стандарта ANSI C на длину имен:
  • для внутренних имен - 31 символ;
  • для внешних имен - 6 символов.
Компиляторы могут поддерживать и имена большей длины.

Ключевые слова if, else, int, float и т. д. зарезервированы, и их нельзя использовать в качестве имен переменных. Все они набираются на нижнем регистре.

Типы, операторы и выражения

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

пятница, 7 февраля 2014 г.

Упражнение 1.24.

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

Write a program to check a C program for rudimentary syntax errors like unmatched parentheses, brackets and braces. Don't forget about quotes, both single and double, escape sequences, and comments. (This program is hard if you do it in full generality.)

$ vim ex_1_24.c
#include <stdio.h>

#define MAXLINE 1000

int getstr(char s[], int maxline);

int main(void)
{
    char line[MAXLINE];
    int len;    /* длина строки */
    int i;      /* индекс элемента строки */
    int parentheses;    /* круглые скобки */
    int braces;         /* фигурные скобки */
    int brackets;       /* квадратные скобки */
    int quotes;         /* кавычки */
    int single_quotes;  /* одинарные кавычки */
    int comment;        /* комментарий */
    int escape;         /* эскейп-последовательность */

    len = 0;
    parentheses = 0;
    braces = 0;
    brackets = 0;
    quotes = 0;
    single_quotes = 0;
    comment = 0;
    escape = 0;

    while ((len = getstr(line, MAXLINE)) > 0)
    {
        i = 0;
        while (i < len)
        {
            if (line[i] == '(')
                ++parentheses;
            if (line[i] == ')')
                --parentheses;

            if (line[i] == '{')
                ++braces;
            if (line[i] == '}')
                --braces;

            if (line[i] == '[')
                ++brackets;
            if (line[i] == ']')
                --brackets;

            if (line[i] == '"')
                ++quotes;

            if (line[i] == '\'')
                ++single_quotes;

            if (line[i] == '/' && line[i + 1] == '*')
                ++comment;

            if (line[i] == '*' && line[i + 1] == '/')
                --comment;

            /* Если за символом '\\' следует
             * либо 'n', либо 't', либо 'b', либо '0', либо '\'', либо '\"',
             * тоесть образуется эскейп-последовательность,
             * сигнализируем об отсутствии ошибок */
            if (line[i] == '\\')
            {
                if (line[i + 1] == 'n' ||
                        line[i + 1] == 't' ||
                        line[i + 1] == '\'' ||
                        line[i + 1] == '\"' ||
                        line[i + 1] == 'b' ||
                        line[i + 1] == '0')
                escape = 0;

                /* Если за символом '\\' следует символ '\\',
                 * что тоже образует эскейп-последовательность,
                 * мы сигнализируем об отсутствии ошибок,
                 * но увеличиваем индекс массива дабы повторно
                 * не обрабатывать второй символ '\\' */
                else if (line [i + 1] == '\\')
                {
                        escape = 0;
                        ++i;
                }

                else
                        ++escape;
            }
            ++i;
        }
    }

    /* Подводим итоги.
     * Поскольку скобки открываются и закрываются, то
     * если parentheses != 0, braces != 0, brackets != 0
     * значит существует соответствующая несбалансированность*/
    if (parentheses != 0)
        printf("Несбалансированность круглых скобок.\n");
    if (braces != 0)
        printf("Несбалансированность фигурных скобок.\n");
    if (brackets != 0)
        printf("Несбалансированность квадратных скобок.\n");

    /* Число кавычек должно быть парным */
    if ((quotes % 2) != 0)
        printf("Несбалансированность кавычек.\n");
    if ((single_quotes % 2) != 0)
        printf("Несбалансированность одинарных кавычек.\n");
    if (comment != 0)
        printf("Несбалансированность комментариев.\n");
    if (escape > 0)
        printf("%d синтаксических ошибок в эскейп-последовательностях\n", escape);
    if (parentheses == 0 &&
            braces == 0 &&
            brackets == 0 &&
            (quotes % 2) == 0 &&
            (single_quotes % 2) == 0 &&
            comment == 0 &&
            escape == 0)
        printf("Элементарные синтаксические ошибки отсутствуют.\n");

    return 0;
}

int getstr(char s[], int lim)
{
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
    {
        s[i] = c;
    }
    if (c == '\n')
    {
        s[i] = c;
        i++;
    }
    s[i] = '\0';
    return i;
}
$ ./a.out
int main(void)
{
    printf("Значение EOF: %d\n", EOF);
    return 0;
}
Элементарные синтаксические ошибки отсутствуют.
$ ./a.out
int main(void)
{
    printf("Значение EOF: %d\n", EOF));
    return 0;
}
Несбалансированность круглых скобок.

четверг, 6 февраля 2014 г.

Упражнение 1.23.

Напишите программу, убирающую все комментарии из любой Си-программы. Не забудьте должным образом обработать строки символов и строковые константы. Комментарии в Си не могут быть вложены друг в друга.

Write a program to remove all comments from a C program. Don't forget to handle quoted strings and character constants properly. C comments don't nest.

$ vim ex_1_23.c
#include <stdio.h>

#define MAXLINE 1000

int getstr(char s[], int maxline);

int main(void)
{
    char line[MAXLINE];
    int len;    /* длина строки */
    int i;      /* индекс элемента строки */
    int in_comment;     /* 1 - находимся в комментарии; 0 - вне комментария */
    int in_quote;       /* 1 - находимся в кавычках; 0 - вне кавычек */

    len = 0;
    in_comment = 0;
    in_quote = 0;

    while ((len = getstr(line, MAXLINE)) > 0)
    {
        i = 0;
        while (i < len)
        {
            if (line[i] == '"')
                in_quote = 1;

            if (in_quote != 1)  /* мы вне строковых констант */
            {
                if (line[i] == '/' && line[i + 1] == '*')
                {
                    in_comment = 1;
                    i = i + 2;
                }

                if (line[i] == '*' && line[i + 1] == '/')
                {
                    in_comment = 0;
                    i = i + 2;
                }

                if (in_comment == 1)    /* мы внутри комментария */
                    ++i;

                else
                {
                    printf("%c", line[i]);
                    ++i;
                }
            }

            else
            {
                printf("%c", line[i]);
                ++i;
            }
        }
    }

    return 0;
}

int getstr(char s[], int lim)
{
    int c, i;

    for (i = 0; i < lim - 1 && (c = getchar()) != EOF && c != '\n'; ++i)
    {
        s[i] = c;
    }
    if (c == '\n')
    {
        s[i] = c;
        i++;
    }
    s[i] = '\0';
    return i;

}
$ cc -g -O0 -Wall -o a.out ex_1_23.c
$ ./a.out
int in_quote;       /* 1 - находимся в кавычках; 0 - вне кавычек */
int in_quote;
return /* комментарий внутри инструкции */ 0;
return  0;
printf("/*тест*/");
printf("/*тест*/");

Примечание!
Данная программа не удаляет многострочные комментарии.