вторник, 31 декабря 2013 г.

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

Напишите программу, копирующую вводимые символы в выходной поток с заменой
символа табуляции на \t, символа забоя на \b и каждой обратной наклонной
черты на \\. Это делает видимыми все символы табуляции и забоя.

$ vim replace.c

#include <stdio.h>

int main(void)
{
        int c;
        int n;  /* если ни один из нужных */

        while((c = getchar()) != EOF)
        {
                n = 0;

                if (c == '\t')
                {
                        printf("\\t");
                        n = 1;
                }

                if (c == '\b')
                {
                        printf("\\b");
                        n = 1;
                }

                if (c == '\\')
                {
                        printf("\\\\");
                        n = 1;
                }

                if (n != 1)
                        putchar(c);
        }

        return 0;
}

$ cc -g -O0 -Wall -o a.out replace.c
$ ./a.out

Test    Test
Test\tTest
Test\Test
Test\\Test
Test    \\      Test
Test\t\\\\\tTest

понедельник, 30 декабря 2013 г.

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

Напишите программу, копирующую символы ввода в выходной поток и заменяющую стоящие подряд пробелы на один пробел.

$ vim space.c

#include <stdio.h>

int main(void)
{
        int c;

        while((c = getchar()) != EOF)
        {
            if (c == ' ')
            {
                putchar(c);
                while((c = getchar()) == ' ')
                    putchar(0);
            }
            putchar(c);
        }
        return 0;
}


$ cc -g -O0 -Wall -o a.out space.c
$ ./a.out
Hello, my friend!
Hello, my friend!
Hello   hello       hello!
Hello hello hello!

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

Напишите программу для подсчета пробелов, табуляций и новых строк.

$ vim count.c
#include <stdio.h>
int main(void)
{
        int c, nl, ns, nt;

        /* nl - строка
         * ns - пробел
         * nt - табуляция */

        nl = ns = nt = 0;

        while ((c = getchar()) != EOF) {
                if (c == ' ')
                        ++ns;

                if (c == '\t')
                        ++nt;

                if (c == '\n')
                        ++nl;
        }

        printf("Было введено\n");
        printf("Строк: %d\nПробелов: %d\nТабуляций: %d\n", nl, ns, nt);

        return 0;
}

$ cc -g -O0 -Wall -o a.out count.c
$ ./a.out
C is a general-purpose programming language.
Было введено
Строк: 1
Пробелов: 5
Табуляций: 0


Подсчет строк

#include <stdio.h>

/* подсчет строк входного потока */
int main(void)
{
        int c, nl;

        nl = 0;
        while ((c = getchar()) != EOF)
                if (c == '\n')
                        ++nl;
        printf("%d\n", nl);

        return 0;
}

Подсчет символов

#include <stdio.h>

/* подсчет вводимых символов; 1-я версия */
int main(void)
{
        long nc;
        nc = 0;

        while(getchar() != EOF)
                ++nc;
        printf("%ld\n", nc);

        return 0;
}


Инструкция
++nc;
представляет новый оператор ++, который означает увеличить на единицу. Вместо этого можно было бы написать nc = nc + 1, но ++nc намного короче, а часто и эффективнее. Существует аналогичный оператор --, означающий уменьшить на единицу. Операторы ++ и -- могут быть как префиксными (++nc), так и постфиксными (nc++).

Спецификация %ld в printf указывает, что соответствующий аргумент имеет тип long.


#include <stdio.h>

/* подсчет вводимых символов; 2-я версия */
int main(void)
{
        double nc;

        for (nc = 0; getchar() != EOF; ++nc)
                ;
        printf("%.0f\n", nc);

        return 0;

}


В printf спецификатор %f применяется как для float, так и для double; спецификатор %.0f означает печать без десятичной точки и дробной части.

Грамматика Си требует, чтобы for-цикл имел тело. Для выполнения этого требования используется точка с запятой, называемая пустой инструкцией.

В циклах while и for условие проверяется до того, как выполняется тело цикла.

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

Напишите программу, печатающую значение EOF.

#include <stdio.h>

int main(void)
{
        printf("Значение EOF: %d\n", EOF);

        return 0;
}

./a.out
Значение EOF: -1

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

Убедитесь в том, что выражение getchar() != EOF получает значение 0 или 1 .

#include <stdio.h>

int main(void)
{
        printf("Для выхода из цикла нажмите <q> и <Enter>\n");
        while (getchar() != 'q')
                printf("Выражение \"getchar() != EOF\" \  получает следующее значение %d\n", getchar() != EOF);
        return 0;
}

$ ./a.out
Для выхода из цикла нажмите <q> и <Enter>
1
Выражение "getchar() != EOF" получает следующее значение 1
2
Выражение "getchar() != EOF" получает следующее значение 1
Нажимаем <Ctrl>-<D>
Выражение "getchar() != EOF" получает следующее значение 0
q

$

воскресенье, 29 декабря 2013 г.

Ввод-вывод символов

Копирование файла


Алгоритм:
чтение символа
while (символ не является признаком конца файла)
вывод только что прочитанного символа
чтение символа

#include <stdio.h>

/* копирование ввода на вывод; 1-я версия */
int main(void)
{
        int c;
        c = getchar();
        while(c != EOF)
        {
                putchar(c);
                c = getchar();
        }
        return 0;

}

$ ./a.out
1
1
2
2

"Конец файла" - комбинация клавиш <Ctrl> - <D>.

#include <stdio.h>

/* копирование ввода на вывод; 2-я версия */
int main(void)
{
        int c;

        while((c = getchar()) != EOF)
                putchar(c);
        return 0;
}

Именованные константы

#include <stdio.h>

#define LOWER 0         /* нижняя граница таблицы */
#define UPPER 300       /* верхняя граница */
#define STEP 20         /* размер шага */

/* Печать таблицы температур по Фаренгейту и Цельсию */

int main(void)
{
        int fahr;

        for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP)
        printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));

        return 0;
}


 0  -17.8
 20   -6.7
 40    4.4
 60   15.6
 80   26.7
100   37.8
120   48.9
140   60.0
160   71.1
180   82.2
200   93.3
220  104.4
240  115.6
260  126.7
280  137.8
300  148.9

Вывод:
Строка #define определяет символьное имя, или именованную константу, для заданной строки символов:
#define имя подставляемый-текст

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

Измените программу преобразования температур так, чтобы она печатала таблицу в обратном порядке, т. е. от 300 до 0.

#include <stdio.h>

/* Печать таблицы температур по Фаренгейту и Цельсию */

int main(void)
{
        int fahr;

        for (fahr = 300; fahr >= 0; fahr = fahr - 20)
        printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));

        return 0;
}

300  148.9
280  137.8
260  126.7
240  115.6
220  104.4
200   93.3
180   82.2
160   71.1
140   60.0
120   48.9
100   37.8
 80   26.7
 60   15.6
 40    4.4
 20   -6.7
  0  -17.8

Инструкция for

#include <stdio.h>

/* Печать таблицы температур по Фаренгейту и Цельсию */

int main(void)
{
        int fahr;

        for (fahr = 0; fahr <= 300; fahr = fahr + 20)
        printf("%3d %6.1f\n", fahr, (5.0 / 9.0) * (fahr - 32));

        return 0;
}

 0  -17.8
 20   -6.7
 40    4.4
 60   15.6
 80   26.7
100   37.8
120   48.9
140   60.0
160   71.1
180   82.2
200   93.3
220  104.4
240  115.6
260  126.7
280  137.8
300  148.9

Выводы:
  1. В любом контексте, где возможно использовать значение переменной какого-то типа, можно использовать более сложное выражение того же типа. Так, на месте третьего аргумента функции printf согласно спецификатору %6.1f должно быть значение с лавающей точкой, следовательно, здесь может быть любое выражение этого типа.
  2. Выбор между while и fог определяется соображениями ясности программы. Цикл for более удобен в тех случаях, когда инициализация и приращение шага логически связаны друг с другом общей переменной и выражаются единичными инструкциями, поскольку названный цикл компактнее цикла while, а его управляющие части сосредоточены в дном месте.

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

Напишите программу, которая будет печатать таблицу соответствия температур по Цельсию температурам по Фаренгейту.

#include <stdio.h>

int main(void)
{
        float celsius, fahr;
        int lower, upper, step;

        lower = -17;
        upper = 149;
        step = 11;

        printf("\n\tТАБЛИЦА ТЕМПЕРАТУР\n\n");
        printf("Градусы Цельсия\tГрадусы Фаренгейта\n\n");

        celsius = lower;

        while (celsius <= upper) {
                fahr =  9.0 / 5.0 * celsius + 32;
                printf("%8.0f\t%10.1f\n", celsius, fahr);
                celsius = celsius + step;
        }
        
        return 0;
}

        ТАБЛИЦА ТЕМПЕРАТУР

Градусы Цельсия Градусы Фаренгейта

     -17               1.4
      -6              21.2
       5              41.0
      16              60.8
      27              80.6
      38             100.4
      49             120.2
      60             140.0
      71             159.8
      82             179.6
      93             199.4
     104             219.2
     115             239.0
     126             258.8
     137             278.6
     148             298.4

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

Усовершенствуйте программу преобразования температур таким образом, чтобы над таблицей она напечатала заголовок.
#include <stdio.h>

/* печать таблицы температур по Фаренгейту
 * и Цельсию для fahr = 0, 20, ..., 300 */

int main(void)
{
        float fahr, celsius;
        int lower, upper, step;

        lower = 0;      /* нижняя граница таблицы температур */
        upper = 300;    /* верхняя граница */
        step = 20;      /* шаг */

        printf("\n\tТАБЛИЦА ТЕМПЕРАТУР\n\n");
printf("Градусы Фаренгейта\tГрадусы Цельсия\n\n");
        fahr = lower;
        while (fahr <= upper)
        {
                celsius = (5.0 / 9.0) * (fahr - 32.0);
                printf("%9.0f %17.1f\n", fahr, celsius);
                fahr = fahr + step;
        }

        return 0;
}


        ТАБЛИЦА ТЕМПЕРАТУР

Градусы Фаренгейта      Градусы Цельсия

        0                   -17.8
       20                    -6.7
       40                     4.4
       60                    15.6
       80                    26.7
      100                    37.8
      120                    48.9
      140                    60.0
      160                    71.1
      180                    82.2
      200                    93.3
      220                   104.4
      240                   115.6
      260                   126.7
      280                   137.8
      300                   148.9

пятница, 27 декабря 2013 г.

Переменные и арифметические выражения

fahr.c

#include <stdio.h>

/* print Fahrenheit-Celsius table
 * for fahr = 0, 20, ..., 300 */

int main(void)
{
        int fahr, celsius;
        int lower, upper, step;

        lower = 0;      /* lower limit of temperature scale */
        upper = 300;    /* upper limit */
        step = 20;      /* step size */

        fahr = lower;
        while (fahr <= upper) {
                celsius = 5 * (fahr - 32) / 9;
                printf("%d\t%d\n", fahr, celsius);
                fahr = fahr + step;
        }

        return 0;
}

$ cc -g -O0 -Wall -o a.out fahr.c
 $ ./a.out

0       -17
20      -6
40      4
60      15
80      26
100     37
120     48
140     60
160     71
180     82
200     93
220     104
240     115
260     126
280     137
300     148

Причина, по которой мы сначала умножаем на 5 и затем делим на 9, а не сразу умножаем на 5/9, связана с тем, что в Си деление целых сопровождается отбрасыванием, т. е. потерей дробной части. Так как 5 и 9 - целые, отбрасывание в 5/9 дало бы нуль, и на месте температур по Цельсию были бы напечатаны нули.

#include <stdio.h>

/* print Fahrenheit-Celsius table
 * for fahr = 0, 20, ..., 300 */

int main(void)
{
        int fahr, celsius;
        int lower, upper, step;

        lower = 0;      /* lower limit of temperature scale */
        upper = 300;    /* upper limit */
        step = 20;      /* step size */

        fahr = lower;
        while (fahr <= upper) {
                celsius = 5 / * (fahr - 32);
                printf("%d\t%d\n", fahr, celsius);
                fahr = fahr + step;
        }

        return 0;
}


$ cc -g -O0 -Wall -o a.out fahr.c
$ ./a.out
0       0
20      0
40      0
60      0
80      0
100     0
120     0
140     0
160     0
180     0
200     0
220     0
240     0
260     0
280     0
300     0



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

#include <stdio.h>

/* print Fahrenheit-Celsius table
 * for fahr = 0, 20, ..., 300 */

int main(void)
{
        int fahr, celsius;
        int lower, upper, step;

        lower = 0;      /* lower limit of temperature scale */
        upper = 300;    /* upper limit */
        step = 20;      /* step size */

        fahr = lower;
        while (fahr <= upper) {
                celsius = 5 * (fahr - 32) / 9;
                printf("%3d\t%6d\n", fahr, celsius);
                fahr = fahr + step;
        }

        return 0;
}


$ cc -g -O0 -Wall -o a.out fahr.c
$ ./a.out
  0        -17
 20         -6
 40          4
 60         15
 80         26
100         37
120         48
140         60
160         71
180         82
200         93
220        104
240        115
260        126
280        137
300        148

Вторая, более серьезная проблема связана с тем, что мы пользуемся целочисленной арифметикой и поэтому не совсем точно вычисляем температуры по шкале Цельсия. Например, 0°F на самом деле (с точностью до десятой) равно -17.8 °С, а не -17. Чтобы получить более точные значения температур, нам надо пользоваться не целочисленной арифметикой, а арифметикой с плавающей точкой. Это потребует некоторых изменений в программе.

fahr2.c

#include <stdio.h>

/* print Fahrenheit-Celsius table
 * for fahr = 0, 20, ..., 300; floating-point version */

int main(void)
{
        float fahr, celsius;
        int lower, upper, step;

        lower = 0;      /* lower limit of temperatuire scale */
        upper = 300;    /* upper limit */
        step = 20;      /* step size */

        fahr = lower;
        while (fahr <= upper)
        {
                celsius = (5.0 / 9.0) * (fahr - 32.0);
                printf("%3.0f %6.1f\n", fahr, celsius);
                fahr = fahr + step;
        }

        return 0;
}

$ cc -g -O0 -Wall -o a.out fahr2.c
$ ./a.out
  0  -17.8
 20   -6.7
 40    4.4
 60   15.6
 80   26.7
100   37.8
120   48.9
140   60.0
160   71.1
180   82.2
200   93.3
220  104.4
240  115.6
260  126.7
280  137.8
300  148.9

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

Выясните, что произойдет, если в строковую константу аргумента printf вставить \с,
где с - символ, не входящий в представленный выше список.

1. #include <stdio.h>
2. int main(void)
3. {
4.        printf("hello, world\c")
5.
6.        return 0;
7. }

$ cc -g -O0 -Wall -o a.out hello.c

hello.c:4:9: warning: unknown escape sequence '\c'

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

Выполните программу, печатающую "здравствуй, мир", в вашей системе. Поэкспериментируйте, удаляя некоторые части программы, и посмотрите, какие сообщения об ошибках вы получите.

1. #include <stdio.h>
2. int main(void)
3. {
4.        printf("hello, world\n")
5.
6.        return 0;
7. }

$ cc -g -O0 -Wall -o a.out hello.c
hello.c: In function 'main':
hello.c:6: error: expected ';' before 'return'


1. #include <stdio.h>
2. int main(void)
3. {
4.        printf("hello, world\n
5.                        ");
6.
7.        return 0;
8. }

$ cc -g -O0 -Wall -o a.out hello.c
hello.c:4:9: warning: missing terminating " character
hello.c: In function 'main':
hello.c:4: error: missing terminating " character
hello.c:5:4: warning: missing terminating " character
hello.c:5: error: missing terminating " character
hello.c:7: error: expected expression before 'return'
hello.c:8: error: expected ';' before '}' token

среда, 25 декабря 2013 г.

Начнем, пожалуй

1. Запускаем редактор vim для создания и редактирования файла hello.c
$ vim hello.c
2. Нажатием клавиши <i> переходим в режим --INSERT-- и набираем текст программы:

#include <stdio.h>

main()
{
        printf("hello, world\n");
}

Сохранение изменений и выход из редактора vim: клавиша <Esc> и ввод :wq

3. Компилируем файл командой:
$ cc -g -O0 -Wall -o a.out hello.c
-Wall - включение предупреждений компилятора;
-g - включение отладочной информации в исполняемый файл;
-O0 - отключение оптимизации;
-o - имя исполняемого файла после компиляции.

$ cc -g -O0 -Wall -o a.out hello.c
hello.c:3:1: warning: return type defaults to 'int'
hello.c: In function 'main':
hello.c:6:1: warning: control reaches end of non-void function
4. Запускаем файл на выполнение
 $ ./a.out
в итоге получаем hello, world

Выводы:

  1. Предупреждения при компиляции не мешают созданию исполняемого файла;
  2. Если не указан тип, возвращаемого функцией результата, то он автоматически становится 'int';
  3. Компилятор выдает предупреждение, когда функция должна вернуть значение, но не возвращает.
Чтобы программа компилировалась без предупреждений, изменим ее:

#include <stdio.h>

int main()
{
        printf("hello, world\n");

        return 0;
}

Символ новой строки никогда не вставляется автоматически, так что одну строку можно напечатать по шагам с помощью нескольких обращений к printf. Нашу первую программу можно написать и так:

#include <stdio.h>
int main(void)
{
        printf("hello, ");
        printf("world");
        printf("\n");

        return 0;

}

$ cc -g -O0 -Wall -o a.out hello.c
$ ./a.out
hello, world


Инструменты

Для изучения языка я буду использовать дистрибутив Linux Slackware 13.47 в состав которого входят компилятор GCC версии 4.5.2 и редактор VIM версии 7.3.154.