Date Редакция Категория comp Теги C / K&R

Упражнение 7.1. Напишите программу, осуществляющую перевод ввода с верхнего регистра на нижний или с нижнего на верхний в зависимости от имени, по которому она вызывается и текст которого находится в arg[0].

Требование про текст в arg[0] я поначалу пропустил и написал обычную программу, в которой переключение между регистрами происходит с помощью arg[1]:

 #include <stdio.h>
 #include <ctype.h>
 #include <stdlib.h>

 /* uplow: переводит ввод в верхний или нижний регистр */
 int main(int argc, char *argv[])
 {
    int c, u;

    if (argc != 2) {
        printf("usage: uplow -{u|l}\n");
        exit(1);
    }

    while (--argc > 0 && (*++argv)[0] == '-')
        while ((c = *++argv[0]))
          switch (c) {
          case 'u':
            u = 1;
            break;
          case 'l':
            u = 0;
            break;
          default:
            u = 1;
            break;
          }
    if (u)
        while ((c = getchar()) != EOF)
            putchar(toupper(c));
    else
        while ((c = getchar()) != EOF)
            putchar(tolower(c));

    return 0;
 }

Перечитал задание и исправился. Теперь, если программа называется lower, она переводит все символы в нижний регистр, а если называется upper, то выполняет обратное преобразование. Хотя в оригинале сказано "upper case to lower or lower case to upper", мы не выполняем проверок isupper/islower, а просто преобразуем к одному регистру:

 #include <stdio.h>
 #include <ctype.h>
 #include <stdlib.h>
 #include <string.h>

 /* upper/lower: переводит ввод в верхний/нижний регистр */
 int main(int argc, char *argv[])
 {    
    int c;

    if (argc != 1) {
        printf("usage: /.{upper|lower}\n");
        exit(1);
    }

    printf("%s\n", argv[0]);

    if (strcmp(argv[0], "./upper") == 0)
        while ((c = getchar()) != EOF)
            putchar(toupper(c));
    else
        while ((c = getchar()) != EOF)
            putchar(tolower(c));

    return 0;
 }

В Ubuntu argv[0] возвращает полный путь к файлу, из которого необходимо выделить имя файла. Поскольку в задании этого не требовалось, я просто ограничил возможности запуска программы каталогом пользователя.

Упражнение 7.2. Напишите программу, которая будет печатать разумным способом любой ввод. Как минимум она должна уметь печатать неграфические символы в восьмеричном или шестнадцатеричном виде (в форме, принятой на вашей машине), обрывая длинные текстовые строки.

Важно определить, что это за неграфические символы (non-graphic characters). В ctype.h существует функция isgraph(c), которая возвращает истину, если c является печатаемым символом кроме пробела. Есть и более жесткий вариант -- isprint(c) (печатаемый символ, включая пробел), на нём и остановимся:

 #include <stdio.h>
 #include <ctype.h>


 #define MAXLEN 30

 /* печатает неграфические символы ввода как 16-ричные числа, 
  * разрывает длинные строки                                       */
 int main(void)
 {    
    int n = 0;
    char c;

    while ((c = getchar()) != EOF) {
        if (c == '\n') {
            n = 0;
            putchar(c);
        }
        else if (!isprint(c) && c != '\n') {
            if (n > MAXLEN) {
                putchar('\n');
                n = 0;
            }
            printf("%x", c);
            n++;
        }
        else {
            if (n > MAXLEN) {
                putchar('\n');
                n = 0;
            }
            putchar(c);
            n++;
        }
    }

    return 0;
 }

Протестируем программу на тексте с фейсбук-страницы Бритни Спирс:

Can't say I'm thrilled our song leaked but… I AM super excited that my fans seem to love it as much as will.i.am and I do. What do you guys think about Scream & Shout?

It hits radio tmw and iTunes later in the week. Gonna do some sick remixes too. Who would you like to remix Scream & Shout?

После обработки, получаем:

 Can't say I'm thrilled our song
  leaked but\ffffffe2\ffffff80\ffffffa6 I AM super excit
 ed that my fans seem to love it
  as much as will.i.am and I do.
  What do you guys think about S
 cream & Shout?
 It hits radio tmw and iTunes la
 ter in the week. Gonna do some 
 sick remixes too. Who would you
  like to remix Scream & Shout?

Многоточие преобразовалось в 16-ричные числа, а длинные строки разбиты по 30 символов.

Упражнение 7.3. Дополните minprintf другими возможностями printf.

Совершенствуем minprintf из п. 7.3, дополняя его возможностями обработки других символов формата.

 #include <stdio.h>
 #include <stdarg.h>


 void minprintf(char *, ...);

 int main(void)
 {    
    int i = 12;
    double f = 12.0;
    minprintf("%d, %o, %x, %f, %g, %e, %s, %c \n", 
                i, i, i, f, f, f, "string", 'c');
    return 0;
 }

 /* minprintf: минимальный printf с переменным числом аргументов */
 void minprintf(char *fmt, ...)
 {
    va_list ap;        /* указатель на очередной безымянный аргумент */ 
    char *p, *sval;
    int ival;
    double dval;
    unsigned int uval;

    va_start(ap, fmt); /* устанавливает ap на 1-й безымянный аргумент */ 
    for (p=fmt; *p; p++) {
        if (*p !='%') {
            putchar(*p);
            continue;
        }
        switch (*++p) {
        case 'd': case 'i':
            ival = va_arg(ap, int);
            printf ("%d", ival);
            break;
        case 'o':
            uval = va_arg(ap, unsigned int);
            printf ("%o", uval);
            break;            
        case 'x':
            uval = va_arg(ap, unsigned int);
            printf ("%x", uval);
            break;            
        case 'f':
            dval = va_arg(ap, double);
            printf("%f", dval);
            break;
        case 'g':
            dval = va_arg(ap, double);
            printf("%g", dval);
            break;
        case 'e':
            dval = va_arg(ap, double);
            printf("%e", dval);
            break;                        
        case 'c':
            ival = va_arg(ap, int);
            putchar(ival);
            break;
        case 's':
            for (sval = va_arg(ap, char *); *sval; sval++)
                putchar(*sval);
            break;
        default:
            putchar(*p);
            break;
        }
    }
    va_end(ap); /* очистка, когда все сделано */
 }

К сожалению, символы форматирования опять "съелись" шаблоном.

Результат:

 12, 14, c, 12.000000, 12, 1.200000e+01, string, c

Упражнение 7.4. Напишите свою версию scanf по аналогии с minprintf из предыдущего параграфа.

Наш minscanf() вводит только целые и вещественные числа, а также строки:

 #include <stdio.h>
 #include <stdarg.h>

 #define MAXLEN 10

 void minscanf(char *, ...);

 int main(void)
 {    
    int in;
    double db;
    char str[MAXLEN];

    printf("Input int double string: ");
    minscanf("%d %f %s", &in, &db, str);
    printf("Output: %d, %lf, %s", in, db, str);
    return 0;
 }

 /* minscanf: минимальный scanf с переменным числом аргументов */
 void minscanf(char *fmt, ...)
 {
    va_list ap;        /* указатель на очередной безымянный аргумент */ 
    char *p, *sval;
    int *ival;
    double *dval;

    va_start(ap, fmt); /* устанавливает ap на 1-й безымянный аргумент */ 
    for (p=fmt; *p; p++) {
        if (*p =='%') {
            switch (*++p) {
            case 'd':
                ival = va_arg(ap, int *);
                scanf ("%d", ival);
                break;
            case 'f':
                dval = va_arg(ap, double *);
                scanf("%lf", dval);
                break;
            case 's':
                sval = va_arg(ap, char *);
                scanf("%s", sval);
                break;
            default:
                putchar(*p);
                break;
            }
        }
    }
    va_end(ap); /* очистка, когда все сделано */
 }

Упражнение 7.5. Перепишите основанную на постфиксной записи программу калькулятора из главы 4 таким образом, чтобы для ввода и преобразования чисел она использовала scanf и/или sscanf.

Изменениям, вызванным появлением scanf(), подвергаются main() и getop(). push() и pop() остаются неизменными.

scanf() считывает операнды, а getop() проверяет из тип. Поскольку scanf() считывает целиком строку, а не отдельные символы, то многочисленные проверки из старого getop() теперь не нужны.

 #include <stdio.h>
 #include <stdlib.h>
 #include <ctype.h>

 #define MAXOP 100  /* макс. размер операнда или оператора */
 #define NUMBER '0' /* признак числа */

 int getop (char *);
 void push (double);
 double pop (void);

 /* калькулятор с обратной польской записью */
 int main()
 {
    int op;
    double op2;
    char s[MAXOP];

    while ((op = scanf("%s", s)) && op != EOF) {
        switch (getop(s)) {
        case NUMBER:
            push (atof(s));
            break;
        case ':
            push (pop() + pop());
            break;
        case '*':
            push (pop() * pop());
            break;
        case '-':
            op2 = pop();
            push (pop() - op2);
            break;
        case '/':
            op2 = pop();
            if (op2 != 0.0)
                push (pop() / op2);
            else
                printf("ошибка: деление на нуль\n");
            break;
        default:
            printf("ошибка: неизвестная операция %s\n", s);
            break;
        }
    }
    printf("\t%.8g\n", pop());

    return 0;
 }

 /* getop: получает следующий оператор или операнд */
 int getop(char *s)
 {
    if (!isdigit(s[0]) && s[0] != '.')
        return s[0];    /* не число */
    else
        return NUMBER;
 }

Тест:

calcs.png

В первой строке ввод, во второй -- результат.

Упражнение 7.6. Напишите программу, сравнивающую два файла и печатающую первую строку, в которой они различаются.+'

Особое внимание следует уделить проверке, не заканчивается ли один из файлов раньше другого.

 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>

 #define MAXLINE 100

 /* filecmp: сравнение содержимого двух файлов */
 int main(int argc, char *argv[])
 {
    int i = 0;
    FILE *fp1, *fp2;
    char *s1, *s2, line1[MAXLINE], line2[MAXLINE], *prog = argv[0]; /* имя программы */

    if (argc != 3) {
        fprintf(stderr, "usage: filecmp file1 file2\n");
        exit(1);
    }
    if ((fp1 = fopen(argv[1], "r")) == NULL) {
        fprintf(stderr, "%s: can't open file %s\n", prog, argv[1]);
        exit(1);
    }
    if ((fp2 = fopen(argv[2], "r")) == NULL) {
        fprintf(stderr, "%s: can't open file %s\n", prog, argv[2]);
        exit(1);
    }
    if (!strcmp(argv[1], argv[2])) { /* не одинаковы ли файлы? */
        printf("no differences\n");
        exit(0);
    }
    while ((s1 = fgets(line1, MAXLINE, fp1)) != NULL && (s2 = fgets(line2, MAXLINE, fp2)) != NULL) {
        i++;
        if (strcmp(line1, line2)) {
            printf("\ndifference:\n");
            printf("line %d in %s: %s", i, argv[1], s1);
            printf("line %d in %s: %s", i, argv[2], s2);
            exit(0);
        }
    }
    /* если один из файлов закончился раньше другого */
    if (s1 && !s2) {
        printf("\ndifference:\n");
        printf("line %d in %s: %s", i, argv[1], s1);
        printf("EOF in %s\n", argv[2]);
    }
    else if (!s1 && s2) {
        printf("\ndifference:\n");
        printf("EOF in %s\n", argv[1]);        
        printf("line %d in %s: %s", i, argv[2], s2);
    }
    else
        printf("no differences\n");
    exit(0);
 }

Упражнение 7.7. Модифицируйте программу поиска по образцу из главы 5 таким образом, чтобы она брала текст из множества именованных файлов, а если имен файлов в аргументах нет, то из стандартного ввода. Следует ли печатать имя файла, в котором найдена подходящая строка?

Вариант, где имя файла не печатается. Проверка строк входного файла на наличие образца вынесена в функцию finder().

 #include <stdio.h>
 #include <string.h>
 #include <stdlib.h>

 #define MAXLINE 1000

 int except = 0, number = 0, found = 0;

 void finder(char *, FILE *, int);

 /* find: поиск строк по образцу из командной строки */
 int main(int argc, char *argv[])
 {
    char *pattern, *prog = argv[0];
    FILE *inp;
    int c, t;

    while (--argc > 0 && (*++argv)[0] == '-')
        while ((c = *++argv[0]))
          switch (c) {
          case 'x':
            except = 1;
            break;
          case 'n':
            number = 1;
            break;
          default:
            fprintf(stderr, "find: неверный параметр %c\n", c);
            argc = 0;
            found = -1;
            break;
          }
    if (argc < 1) {
        fprintf(stderr, "usage: find -x -n pattern\n");
        exit(1);
    }
    pattern = *argv;
    if (!--argc) {
        finder(pattern, stdin, MAXLINE);
        exit(0);
    }
    while (argc-- && (inp = fopen(*++argv, "r")) != NULL) {
         finder(pattern, inp, MAXLINE);
    }
    if (!argc) {
        fprintf(stderr, "%s: can't open %s\n", prog, *argv);
        exit(1);
    }
    return found;
 }

 /* finder: поиск по образцу pattern в файле input */
 void finder(char *pattern, FILE *input, int maxline) {
    long lineno = 0;
    char line[MAXLINE], *p;

    while ((p = fgets(line, MAXLINE, input)) != NULL) {
        lineno++;
        if ((strstr(line, pattern) != NULL) != except) {
            if (number)
                printf("%ld:", lineno);
            printf("%s", line);
            found++;
        }
    }
 }

Если же указывать, в каком файле была найден образец, а это логично, если у файла есть имя, отличное от stdin, то цикл основной функции

 while (argc-- && (inp = fopen(*++argv, "r")) != NULL) {
     finder(pattern, inp, MAXLINE);
 }

следует переделать:

 int t;

 while (argc-- && (inp = fopen(*++argv, "r")) != NULL) {
     t = found;
     finder(pattern, inp, MAXLINE);
     if ((found - t) != 0)
         printf("------------------ found in file %s ------------------\n", *argv);
 }

Тестовые файлы здесь. Результаты проверки:

find.png

Упражнение 7.8. Напишите программу, печатающую несколько файлов. Каждый файл должен начинаться с новой страницы, предваряться заголовком и иметь свою нумерацию страниц.

Основой стала программа из предыдущего упражнения. Переход на новую страницу выполняется по счетчику pageno. Если последняя строка файла имеет номер не кратный lineno, выполняется прогон до конца страницы (PAGELEN).

 #include <stdio.h>
 #include <stdlib.h>

 #define MAXLINE 1000
 #define PAGELEN 15


 void fprinter(FILE *, int);

 /* fileprint: печать файлов, имена которых заданы в командной строке */
 int main(int argc, char *argv[])
 {
    char *prog = argv[0];
    FILE *inp;

    if (argc == 1) {
        fprintf(stderr, "usage: fileprint file1, file2,...\n");
        exit(1);
    }
    while (--argc) {
        if ((inp = fopen(*++argv, "r")) != NULL) {
            printf("-------- begin of file %s --------\n", *argv);
            fprinter(inp, MAXLINE);
            printf("--------- end of file %s ---------\n", *argv);
        }
        else {
            fprintf(stderr, "%s: can't open %s\n", prog, *argv);
            exit(1);
        }
    }
    return 0;
 }

 /* fprinter: печать файла input */
 void fprinter(FILE *input, int maxline) {
    long t, lineno = 0;
    int pageno = 1;
    char line[MAXLINE], *p;

    printf("-%d-\n", pageno);
    while ((p = fgets(line, MAXLINE, input)) != NULL) {
        if (lineno++ < PAGELEN)
            printf("%s", line);
        else {
            lineno = 0;
            printf("-%d-\n", ++pageno);
        }
    }
    if ((t = PAGELEN - lineno) > 0)
        while (--t)
            printf("\n");
 }

Упражнение 7.9. Реализуя функции вроде isupper, можно экономить либо память, либо время. Напишите оба варианта функции.

Вместе с тестовой программой:

 #include <stdio.h>
 #include <ctype.h>
 #include <string.h>
 #include <time.h>

 #define my_isupper3(c) (c >= 'A' && c <= 'Z')

 int my_isupper1(char );
 int my_isupper2(char );

 int main(void)
 {
    int i, count, num = 20000000;
    double t1, t2;
    char line[20] = "My sample String";

    t1 = clock();
    while (--num)
        for (i=0; line[i]; i++)
            if (my_isupper3(line[i]))
                count++;
    t2 = (clock() - t1)/CLOCKS_PER_SEC;
    printf("%lf\n", t2);

    return 0;
 }

 int my_isupper1(char c) {
    return (c >= 'A' && c <= 'Z');
 }

 int my_isupper2(char c) {
    return (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ",c) != NULL);
 }

my_isupper1 практически очевиден, my_isupper2 должен занимать больше памяти. Тест показал, что он к тому же занимает и большее время. Так что, как говорят футбольные комментаторы: "Удар был не сильный, но и неточный". Лучше всего по скорости показала себя макроверсия, хотя её использование, возможно, и не по правилам.



Комментарии

comments powered by Disqus