понедельник, 20 января 2014 г.

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

Перевод: Напишите программу detab, заменяющую символы табуляции во вводимом тексте нужным числом пробелов (до следующего "стопа" табуляции). Предполагается, что "стопы" табуляции расставлены на фиксированном расстоянии друг от друга, скажем, через n позиций. Как лучше задавать n - в виде значения переменной или в виде именованной константы?

Оригинал: Write a program detab that replaces tabs in the input with the proper number of blanks to space to the next tab stop. Assume a fixed set of tab stops, say every n columns. Should n be a variable or a symbolic parameter?

    Количество пробелов до следующего "стопа" будем получать по алгоритму: от расстояния между стопами отнимаем результат остатка от деления позиции символа табуляции на расстояние между "стопами". Предположим, что "стопы" табуляции расположены на расстоянии пяти позиций друг от друга.
    Пусть 7-й символ будет символом табуляции. Арифметическая операция остаток от деления 7 % 5 = 2, а 5 - 2 = 3. Получаем, что до следующего "стопа" табуляции остается 3 позиции.


7 % 5 = 2
7 = 5 * 1 + 2

    Пусть 11-й символ будет символом табуляции. Арифметическая операция остаток от деления 11 % 5 = 1, а 5 -1 = 4. Получаем, что до следующего "стопа" табуляции остается 4 позиции.
11 % 5 = 1
11 = 5 * 2 + 1
 $ vim ex_1_20.c
#include <stdio.h>
#define MAXLINE 1000    /* максимальный размер вводимой строки */
#define STOP 5          /* расстояние "стопов" табуляции друг от друга */

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

int main(void)
{
    int len;
    char line[MAXLINE];
    int i, j;
    int symbols;        /* количество символов, выведенных на экран */
    int test = 0;       /* количество символов, выведенных на экран, до следующего "стопа" табуляции  */

    while((len = getstr(line, MAXLINE)) > 0)
    {
        for(i = 0, symbols = 0; line[i] != '\0'; ++i)
        {
            if(line[i] == '\t')
            {
                test = STOP - (symbols % STOP);

                for (j = 0; j < test; ++j)
                {
                    putchar('|');     /* для наглядности будем использовать символ '|' вместо пробела */
                    ++symbols;
                }
            }
            else
            {
                putchar(line[i]);
                ++symbols;
            }
        }
    }
    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 detab ex_1_20.c
$ ./detab
<Tab>
|||||
1<Tab>
1||||
11<Tab>
11|||
111<Tab>
111||
1111<Tab>
1111|
11111<Tab>
11111|||||

воскресенье, 19 января 2014 г.

Внешние переменные и область видимости

    Автоматические переменные образуются и исчезают одновременно с входом в функцию и выходом из нее, они не сохраняют своих значений от вызова к вызову и должны устанавливаться заново при каждом новом обращении к функции.
    В качестве альтернативы автоматическим переменным можно определить внешние переменные, к которым разрешается обращаться по их именам из любой функции. Так как внешние переменные доступны повсеместно, их можно использовать вместо аргументов для связи между функциями по данным. Кроме того, поскольку внешние переменные существуют постоянно, а не возникают и исчезают на период выполнения функции, свои значения они сохраняют и после возврата из функций, их установивших.
    Внешняя переменная должна быть определена, причем только один раз, вне текста любой функции; в этом случае ей будет выделена память. Она должна быть объявлена во всех функциях, которые хотят ею пользоваться. Объявление может быть явным, в виде инструкции extern, или неявным, когда нужная информация получается из контекста. Чтобы конкретизировать сказанное, авторы книги предлагают переписать программу печати самой длинной строки с использованием linelongest и max в качестве внешних переменных. Это потребует изменений в вызовах, объявлениях и телах всех трех функций.
$ vim longest.c
#include <stdio.h>

#define MAXLINE 1000    /* максимальный размер вводимой строки */

int max;        /* длина максимальной из просмотренных строк */
char line[MAXLINE];     /* текущая строка */
char longest[MAXLINE];  /* самая длинная строка */

int getstr(void);
void copy(void);

/* печать самой длинной строки; специализированная версия */
int main(void)
{
    int len;
    extern int max;
    extern char longest[];

    max = 0;
    while ((len = getstr()) > 0)
        if (len > max)
        {
            max = len;
            copy();
        }
    if (max > 0)        /* была ли хоть одна строка */
        printf("%s", longest);

    return 0;
}

/* getstr: специализированная версия */
int getstr(void)
{
    int c, i;
    extern char line[];

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

    return i;
}

/* copy: специализированная версия */
void copy(void)
{
    int i;
    extern char line[], longest[];

    i = 0;
    while ((longest[i] = line[i]) != '\0')
        ++i;
}
$ cc -g -O0 -Wall -o a.out longest.c
    Внешние переменные для main, getstr и copy определяются в начале примера, где им присваивается тип и выделяется память. Определения внешних переменных синтаксически ничем не отличаются от определения локальных переменных, но поскольку они расположены вне функций, эти переменные считаются внешними. Чтобы функция могла пользоваться внешней переменной, ей нужно прежде всего сообщить имя соответствующей переменной. Объявления внешней переменной начинается с ключевого слова extern.
    В некоторых случаях объявление extern можно опустить. Если определение внешней переменной в исходном файле расположено выше функции, где она используется, то в объявлении extern нет необходимости. Таким образом, в main, getstr и copy объявления extern избыточны. Обычно определения внешних переменных располагают в начале исходного файла, и все объявления extern для них опускают.
    Если же программа расположена в нескольких исходных файлах и внешняя переменная определена в файле1, а используется в файле2 и файлеЗ, то объявления extern в файле2 и файлеЗ обязательны, поскольку необходимо указать, что во всех трех файлах функции обращаются к одной и той же внешней переменной. На практике обычно удобно собрать все объявления внешних переменных и функций в отдельный файл, называемый заголовочным (header-файлом), и помещать его с помощью #include в начало каждого исходного файла.

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

Перевод: Напишите функцию reverse(s), размещающую символы в строке s в обратном порядке. Примените ее при написании программы, которая каждую вводимую строку располагает в обратном порядке.

Оригинал: Write a function reverse(s) that reverses the character string s. Use it to write a program that reverses its input a line at a time.
$ vim ex_1_19.c
#include <stdio.h>
#define MAXLINE 1000 /* максимальный размер вводимой строки */

int getstr(char line[], int maxline);
void reverse(char mass[]);

int main(void)
{
        int len;
        char line[MAXLINE];

        while((len = getstr(line, MAXLINE)) > 0)
        {
                reverse(line);
                printf("\b%s\n", line);
        }

        return 0;
}

/* getstr: читает строку в s, возвращает длину */
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;
}

/* reverse: располагает строку в обратном порядке */
void reverse(char mass[])
{
        int i, j;
        int temp;

        for (j = 0; mass[j] != '\0'; ++j) /* Получаем размер массива */
                ;

        for (i = 0; i < j; ++i)
        {
                temp = mass[i];
                mass[i] = mass[j - 1];
                mass[j - 1] = temp;
                --j;
        }

}
$ cc -g -O0 -Wall -o a.out ex_1_19.c
$ ./a.out
1
1
12
21
123
321
1234
4321
12345
54321
Hello, world!
!dlrow ,olleH
<Ctrl>+<D>

суббота, 18 января 2014 г.

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

Перевод: Напишите программу, которая будет в каждой вводимой строке заменять стоящие подряд символы пробелов и табуляций на один пробел и удалять пустые строки.

Оригинал: Write a program to remove trailing blanks and tabs from each line of input, and to delete entirely blank lines.

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

#define MAXLINE 1000

/* Программа удаляет все завершающие пробелы и табуляции */

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

int main(void)
{
    int len, i;
    char line[MAXLINE];

    while ((len = getstr(line, MAXLINE)) > 0)
    {
        /* для исключения символа '\0' */
        len = len - 1;

        printf("Длина строки %d символа(-ов)\n", len);
        for (i = len - 1; i >= 0; --i)
            if (line[i] == ' ' || line[i] == '\t')
                len = len - 1;
            else
                i = 0;

        for (i = 0; i < len; ++i)
            printf("%c", line[i]);
        printf("\n");

        printf("Длина строки без завершающих ее пробелов и табуляций %d\n", 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_18_5.c
$ ./a.out
Hello<Space><Tab><Space><Enter>
Длина строки 8 символа(-ов)
Hello
Длина строки без завершающих ее пробелов и табуляций 5

среда, 15 января 2014 г.

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

Напишите программу печати всех вводимых строк, содержащих более 80 символов.

#include <stdio.h>
#define MAXLINE 1000    /* максимальный размер вводимой строки */
#define MAXLENGTH 81    /* 80 символов + символ новой строки */

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

int main(void)
{
    int len;    /* длина текущей строки */
    char line[MAXLINE]; /* текущая строка */

    while ((len = getstr(line, MAXLINE)) > 0 )
        if (len > MAXLENGTH) /* данная строка длиннее 80 символов*/
            printf("%s", line);
    return 0;
}

/* getstr: читает строку в s, возвращает длину */
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';        /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

четверг, 9 января 2014 г.

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

    Я долго думал над этим упражнением, но к сожалению не смог его решить ((
Наученный опытом опечаток во время перевода, я глянул на текст оригинала.

Перевод:
Перепишите main предыдущей программы так, чтобы она могла печатать самую длинную строку без каких-либо ограничений на ее размер.

Оригинал:
Revise the main routine of the longest-line program so it will correctly print the length of arbitrary long input lines, and as much as possible of the text.

    Итак, задача упрощается, необходимо напечатать правильную длину строки и как можно больше содержимого этой строки при условии, что сама строка может быть длиннее максимального размера вводимой строки, определенного как MAXLINE. Только непонятно, что подразумевается под main routine, все-таки функция main или главная процедура (рутина) самой программы.

    Переписать функцию main оказалось не так просто. Поэтому, для начала перепишем функцию getstr, которая читает строку из текстового потока и будет возвращать ее правильную длину.

$ vim ex_1_16_4.c
#include <stdio.h>
#define MAXLINE 10 /* уменьшим до 10 размер строки */


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

void copy(char to[], char from[]);

int main(void)

{
    int len;    /* длина текущей строки */
    int max;    /* длина максимальной из просмотренных строк */
    char line[MAXLINE]; /* текущая строка */
    char longest[MAXLINE];      /* самая длинная строка */

    int i;
    for (i = 0; i < MAXLINE; ++i)
            line[i] = longest[i] = 0;

    max = 0;

    /* есть ли еще строка */
    while ((len = getstr(line, MAXLINE)) > 0 )
    {
        if (len > max)
        {
            max = len;
            copy(longest, line);
        }
    }
    if (max > 0)        /* была ли хоть одна строка? */
    {
        printf("Длина самой длинной строки равна %d символа(ов)\n", max);
        printf("%s\n", longest);
    }

    return 0;
}

/* getline: читает строку в s, возвращает длину */
int getstr(char s[], int lim)
{
    int c;
    int i; /* длина строки */
    int index; /* индекс массива s[] */


    /* Убираем ограничение на длину строки в for-цикле */
   for (i = 0, index = 0; (c = getchar()) != EOF && c != '\n'; ++i)
    {
        /* i продолжает расти при вводе символов
         * даже если массив s[] "почти" полон*/
        if (i < lim - 1)/* массив s[] не должен превышать MAXLINE */
        {
            s[index] = c; /* наполнение массива s[] символами */
            ++index;
        }
    }

    if (c == '\n')
    {
        s[index] = c;
        ++index;
        ++i;
    }
    s[index] = '\0';    /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

/* copy: копирует из 'from' в 'to'*/
void copy(char to[], char from[])
{
    int i;
    i = 0;
    while ((to[i] = from[i]) != '\0')
        ++i;
}
$ cc -g -O0 -Wall -o a.out ex_1_16_4.c
$ ./a.out
До сих пор мы пользовались готовыми функциями вроде main, getchar и putchar, теперь настала пора нам самим написать несколько функций.<Enter>
Механизм использования функции в Си удобен, легок и эффективен.<Enter>
Нередко вы будете встречать короткие функции, вызываемые лишь единожды; они оформлены в виде функции с одной-единственной целью — получить более ясную программу.<Enter>
<Ctrl>+<D>
Длина самой длинной строки равна 162 символа(ов)
Нередко в

    Для проверки точности полученного результата используем программу wc (print newline, word, and byte counts for each file), о которой упоминалось на 36 странице книги.
$ wc -m -
Нередко вы будете встречать короткие функции, вызываемые лишь единожды; они оформлены в виде функции с одной-единственной целью — получить более ясную программу.<Enter>
<Ctrl>+<D>
162 -

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


#include <stdio.h>
#define MAXLINE 10      /* максимальный размер вводимой строки */

int getstr(char line[], int maxline);
void copy(char to[], char from[]);

int main(void)
{
    int len;    /* длина текущей строки */
    int max;    /* длина максимальной из просмотренных строк */
    char line[MAXLINE]; /* текущая строка */
    char longest[MAXLINE];      /* самая длинная строка */
    char temp[MAXLINE]; /* для временного хранения фрагмента потока */

    /* Сигнализировать о том, что текстовый поток имеет длину
     * более, чем объявлено в MAXLINE
     * wait = 1 - предыдущий фрагмент потока больше MAXLINE
     * wait = 0 - предыдущий фрагмент потока меньше или равен MAXLINE*/
    int wait;
    wait = 0;

    int iNt; /* промежуточная длина текстового потока */
    iNt = 0;

    int i; /* для for-цикла */
    for (i = 0; i < MAXLINE; ++i)
        line[i] = longest[i] = temp[i] = 0;

    max = 0;
    /* есть ли еще строка */
    while ((len = getstr(line, MAXLINE)) > 0 )
    {
        if(line[len-1] != '\n')
        {
            if (wait == 0)
                copy(temp, line);
            iNt = iNt + len;
            if (max < iNt)
                max = iNt;
            wait = 1;
        }

        else
        {
            if (wait == 1)
            {
                if (max < (iNt + len))
                {
                    max = iNt + len;
                    copy(longest, temp);
                }
                wait = 0;
            }
            else if (len > max)
            {
                max = len;
                copy(longest, line);
            }
            iNt = 0;
        }
    }
    if (max > 0)        /* была ли хоть одна строка? */
    {
        printf("%s\n", longest);
        printf("Длина самой длинной строки %d символа(ов)\n", max);
    }

    return 0;
}

/* getstr: читает строку в s, возвращает длину */
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';        /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

/* copy: копирует из 'from' в 'to'; to достаточно большой */
void copy(char to[], char from[])
{
    int i;

    i = 0;
    while ((to[i] = from[i]) != '\0')
        ++i;
}

вторник, 7 января 2014 г.

Символьные массивы

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


    while (есть ли еще строка?)
    if (данная строка длиннее самой длинной из предыдущих)
    запомнить ее
    запомнить ее длину
    напечатать самую длинную строку


    Функция getline получает строку, выдает ее длину или нуль если строки нет. Функция copy копирует строку в "надежное" место.
$ vim char_array_1.c
#include <stdio.h>
#define MAXLINE 1000    /* максимальный размер вводимой строки */

int getline(char line[], int MAXLINE);
void copy(char to[], char from[]);

int main(void)
{
    int len;    /* длина текущей строки */
    int max;    /* длина максимальной из просмотренных строк */
    char line[MAXLINE]; /* текущая строка */
    char longest[MAXLINE];      /* самая длинная строка */

    max = 0;
    /* есть ли еще строка */
    while ((len = getline(line, MAXLINE)) > 0 )
        if (len > max) /* данная строка длиннее самой длинной из предыдущих*/
        {
            max = len;  /* запоминаем длину самой длинной строки */
            copy(longest, line);        /* запоминаем самую длинную строку */
        }
    if (max > 0)        /* была ли хоть одна строка? */
        printf("%s", longest);
    return 0;
}

/* getline: читает строку в s, возвращает длину */
int getline(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';        /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

/* copy: копирует из 'from' в 'to'; to достаточно большой */
void copy(char to[], char from[])
{
    int i;

    i = 0;
    while ((to[i] = from[i]) != '\0')
        ++i;
}
$ cc -g -O0 -Wall -o a.out char_array_1.c
char_array_1.c:4:30: error: expected ';', ',' or ')' before numeric constant
char_array_1.c: In function 'main':
char_array_1.c:16:5: warning: passing argument 1 of 'getline' from incompatible pointer type
/usr/include/stdio.h:671:20: note: expected 'char ** __restrict__' but argument is of type 'char *'
char_array_1.c:16:5: warning: passing argument 2 of 'getline' makes pointer from integer without a cast
/usr/include/stdio.h:671:20: note: expected 'size_t * __restrict__' but argument is of type 'int'
char_array_1.c:16:5: error: too few arguments to function 'getline'
/usr/include/stdio.h:671:20: note: declared here
char_array_1.c: At top level:
char_array_1.c:28:5: error: conflicting types for 'getline'
/usr/include/stdio.h:671:20: note: previous declaration of 'getline' was here


    Ошибка в 4 строке на 30 символе сообщает об ожидании компилятором символов ';'или ',', или ')' перед числовой константой. Так как ранее мы определили именованную константу MAXLINE. Использование константы в качестве аргумента в прототипе функции getline недопустимо и является опечаткой во время перевода книги на русский язык. В оригинале книги используется переменная целого типа maxline.
    
    Все последующие ошибки связаны с тем, что функция с именем getline уже объявлена в заголовочном файле /usr/include/stdio.h. Решением будет переименование функции в getstr.

   С учетом указанных изменений текст программы будет выглядеть так:
$ vim char_array_2.c
#include <stdio.h>
#define MAXLINE 1000    /* максимальный размер вводимой строки */

int getstr(char line[], int maxline);
void copy(char to[], char from[]);

int main(void)
{
    int len;    /* длина текущей строки */
    int max;    /* длина максимальной из просмотренных строк */
    char line[MAXLINE]; /* текущая строка */
    char longest[MAXLINE];      /* самая длинная строка */

    max = 0;
    /* есть ли еще строка */
    while ((len = getstr(line, MAXLINE)) > 0 )
        if (len > max) /* данная строка длиннее самой длинной из предыдущих*/
        {
            max = len;  /* запоминаем длину самой длинной строки */
            copy(longest, line);        /* запоминаем самую длинную строку */
        }
    if (max > 0)        /* была ли хоть одна строка? */
        printf("%s", longest);
    return 0;
}

/* getline: читает строку в s, возвращает длину */
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';        /* в конец строки дописывам "0" */
    return i;   /* функция возвращает длину строки */
}

/* copy: копирует из 'from' в 'to'; to достаточно большой */
void copy(char to[], char from[])
{
    int i;

    i = 0;
    while ((to[i] = from[i]) != '\0')
        ++i;
}
$ cc -g -O0 -Wall -o a.out char_array_2.c
    Компиляция прошла без ошибок и предупреждений.
$ ./a.out
До сих пор мы пользовались готовыми функциями вроде main, getchar и putchar, теперь настала пора нам самим написать несколько функций.
Механизм использования функции в Си удобен, легок и эффективен.
Нередко вы будете встречать короткие функции, вызываемые лишь единожды; они оформлены в виде функции с одной-единственной целью — получить более ясную программу.
<Ctrl>+<D>
Нередко вы будете встречать короткие функции, вызываемые лишь единожды; они оформлены в виде функции с одной-единственной целью — получить более ясную программу.

    Программа напечатала самую длиную из трёх, введенных мною, строк.