Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#14| Flamelurker
Demon's Souls |#13| Storm King
Demon's Souls |#12| Old Monk & Old Hero
Demon's Souls |#11| Мaneater part 2

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 1009.15 Kb

Хрестоматия по программированию на Си в Unix

Предыдущая страница Следующая страница
1 ... 17 18 19 20 21 22 23  24 25 26 27 28 29 30 ... 87
            }
            return 0;
    }

Эта программа неожиданно печатает

    % a.out
    в - печатный символ
    з - печатный символ

И все.  В чем дело???
Рассмотрим к примеру символ 'г'. Его код '\307'.  В операторе

    c = *string;

Символ c получает значение -57 (десятичное), которое ОТРИЦАТЕЛЬНО.  В системном файле
/usr/include/ctype.h макрос isprint определен так:

    #define isprint(c)      ((_ctype + 1)[c] & (_P|_U|_L|_N|_B))

И значение c используется в нашем случае как  отрицательный  индекс  в  массиве,  ибо
индекс  приводится  к  типу int (signed). Откуда теперь извлекается значение флагов -
нам неизвестно; можно только с уверенностью сказать, что НЕ из массива _ctype.

А. Богатырев, 1992-95                  - 129 -                              Си в UNIX

Проблему решает либо использование

    isprint(c & 0xFF)

либо

    isprint((unsigned char) c)

либо объявление в нашем примере

    unsigned char c;

В первом случае мы явно приводим signed к unsigned битовой операцией, обнуляя  лишние
биты.   Во втором и третьем - unsigned char расширяется в unsigned int, который оста-
нется положительным. Вероятно, второй путь предпочтительнее.

3.12.  Итак, снова напомним, что русские буквы char, а не unsigned char дают  отрица-
тельные индексы в массиве.

    char c = 'г';
    int x[256];

            ...x[c]...            /* индекс < 0 */
            ...x['г']...

Поэтому байтовые индексы должны быть либо unsigned char, либо & 0xFF.  Как в  следую-
щем примере:

    /* Программа преобразования символов в файле: транслитерация
                      tr abcd prst  заменяет строки
                      xxxxdbcaxxxx -> xxxxtrspxxxx
       По мотивам книги М.Дансмура и Г.Дейвиса.
    */
    #include 

    #define ASCII 256 /* число букв в алфавите ASCII */
    /* BUFSIZ определено в stdio.h */
    char mt[ ASCII ];       /* таблица перекодировки */

    /* начальная разметка таблицы */
    void mtinit(){
            register int i;
            for( i=0; i < ASCII; i++ )
                    mt[i] = (char) i;
    }

А. Богатырев, 1992-95                  - 130 -                              Си в UNIX

    int main(int argc, char *argv[])
    {
            register char *tin, *tout; /* unsigned char */
            char buffer[ BUFSIZ ];

            if( argc != 3 ){
                    fprintf( stderr, "Вызов: %s что наЧто\n", argv[0] );
                    return(1);
            }
            tin  = argv[1]; tout = argv[2];

            if( strlen(tin) != strlen(tout)){
                    fprintf( stderr, "строки разной длины\n" );
                    return(2);
            }

            mtinit();
            do{
                    mt[ (*tin++) & 0xFF ]  = *tout++;
                    /*   *tin - имеет тип char.
                     *   & 0xFF подавляет расширение знака
                     */
            } while( *tin );

            tout = mt;
            while( fgets( buffer, BUFSIZ, stdin ) != NULL ){
                    for( tin = buffer; *tin; tin++ )
                            *tin = tout[ *tin & 0xFF ];
                    fputs( buffer, stdout );
            }
            return(0);
    }

3.13.

    int main(int ac, char *av[]){
            char c = 'г';
            if('a' <= c && c < 256)
                    printf("Это одна буква.\n");
            return 0;
    }

Увы, эта программа не печатает НИЧЕГО. Просто потому, что signed char в сравнении  (в
операторе if) приводится к типу int.  А как целое число - русская буква отрицательна.
Снова  решением  является  либо  использование  везде  (c & 0xFF),  либо   объявление
unsigned char c.   В частности, этот пример показывает, что НЕЛЬЗЯ просто так сравни-
вать две переменные типа char. Нужно принимать предохранительные меры  по  подавлению
расширения знака:

    if((ch1 & 0xFF) < (ch2 & 0xFF))...;

Для unsigned char такой проблемы не будет.

3.14.  Почему неверно:

А. Богатырев, 1992-95                  - 131 -                              Си в UNIX

    #include 
    main(){
            char c;

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

Потому что c описано как char, в то время как EOF - значение типа int равное (-1).
     Русская буква "Большой твердый знак" в кодировке КОИ-8 имеет код '\377'  (0xFF).
Если  мы подадим на вход этой программе эту букву, то в сравнении signed char со зна-
чением знакового целого EOF, c будет приведено тоже к знаковому целому -  расширением
знака.   0xFF  превратится  в (-1), что означает, что поступил символ EOF. Сюрприз!!!
Посему данная программа будет делать вид, что в любом файле с большим русским твердым
знаком после этого знака (и включая его) дальше ничего нет. Что есть досадное заблуж-
дение.
     Решением служит ПРАВИЛЬНОЕ объявление int c.

3.15.  Изучите поведение программы

    #define TYPE char

    void f(TYPE c){
            if(c == 'й') printf("Это буква й\n");
            printf("c=%c c=\\%03o c=%03d c=0x%0X\n", c, c, c, c);
    }

    int main(){
            f('г'); f('й');
            f('z'); f('Z');
            return 0;
    }

когда TYPE определено как char, unsigned char, int.  Объясните  поведение.  Выдачи  в
этих трех случаях таковы (int == 32 бита):

    c=г c=\37777777707 c=-57 c=0xFFFFFFC7
    Это буква й
    c=й c=\37777777712 c=-54 c=0xFFFFFFCA
    c=z c=\172 c=122 c=0x7A
    c=Z c=\132 c=090 c=0x5A

    c=г c=\307 c=199 c=0xC7
    c=й c=\312 c=202 c=0xCA
    c=z c=\172 c=122 c=0x7A
    c=Z c=\132 c=090 c=0x5A

    и снова как 1 случай.

Рассмотрите альтернативу

            if(c == (unsigned char) 'й') printf("Это буква й\n");

где предполагается, что знак у русских букв и у c НЕ расширяется.   В  данном  случае
фраза  'Это буква й' не печатается ни с типом char, ни с типом int, поскольку в срав-
нении c приводится к типу signed int расширением знакового бита  (который  равен  1).
Слева получается отрицательное число!
     В таких случаях вновь следует писать

            if((unsigned char)c == (unsigned char)'й') printf("Это буква й\n");

А. Богатырев, 1992-95                  - 132 -                              Си в UNIX

3.16.  Обычно возникают проблемы при написании функций с переменным  числом  аргумен-
тов.   В языке Си эта проблема решается использованием макросов va_args, не зависящих
от соглашений о вызовах функций на данной машине, и использующих эти  макросы  специ-
альных   функций.   Есть  два  стиля  оформления  таких  программ:  с  использованием
 и .  Первый был продемонстрирован в  первой  главе  на  примере
функции poly().  Для иллюстрации второго приведем пример функции трассировки, записы-
вающей собщение в файл:

    #include 
    #include 
    void trace(char *fmt, ...) {
        va_list args;
        static FILE *fp = NULL;

        if(fp == NULL){
           if((fp = fopen("TRACE", "w")) == NULL) return;
        }
        va_start(args, fmt);
        /* второй аргумент: арг-т после которого
         * в заголовке функции идет ... */
        vfprintf(fp, fmt, args); /* библиотечная ф-ция */
        fflush(fp);     /* вытолкнуть сообщение в файл */
        va_end(args);
    }

    main(){ trace( "%s\n", "Go home.");
            trace( "%d %d\n", 12, 34);
    }

Символ `...' (троеточие) в заголовке функции обозначает переменный (возможно  пустой)
список  аргументов.  Он  должен  быть  самым последним, следуя за всеми обязательными
аргументами функции.
     Макрос va_arg(args,type), извлекающий из  переменного  списка  аргументов  `...'
очередное  значение типа type, одинаков в обоех моделях.  Функция vfprintf может быть
написана через функцию vsprintf (в действительности обе функции - стандартные):

    int vfprintf(FILE *fp, const char *fmt, va_list args){
        /*static*/ char buffer[1024]; int res;
        res = vsprintf(buffer, fmt, args);
        fputs(buffer, fp); return res;
    }

Функция vsprintf(str,fmt,args); аналогична функции sprintf(str,fmt,...) -  записывает
преобразованную по формату строку в байтовый массив str, но используется в контексте,
подобном приведенному.  В конец сформированной строки sprintf записывает '\0'.

3.17.  Напишите функцию printf, понимающую форматы %c (буква), %d (целое), %o  (вось-
меричное),  %x  (шестнадцатеричное),  %b  (двоичное),  %r (римское), %s (строка), %ld
(длинное целое).  Ответ смотри в приложении.

3.18.  Для того, чтобы один и тот же исходный текст программы транслировался на  раз-
ных  машинах  (в разных системах), приходится выделять в программе системно-зависимые
части.  Такие части должны по-разному выглядеть на разных машинах, поэтому их  оформ-
ляют в виде так называемых "условно компилируемых" частей:

    #ifdef XX
            ... вариант1
    #else
            ... вариант2
    #endif

А. Богатырев, 1992-95                  - 133 -                              Си в UNIX

Эта директива препроцессора ведет себя следующим образом: если макрос с именем XX был
определен
    #define XX
то в программу подставляется вариант1, если же нет - вариант2. Оператор #else не обя-
зателен  - при его отсутствии вариант2 пуст. Существует также оператор #ifndef, кото-
рый подставляет вариант1 если макрос XX не определен.  Есть еще и  оператор  #elif  -
else if:

    #ifdef макро1
      ...
    #elif  макро2
      ...
    #else
      ...
    #endif

Определить макрос можно не только при помощи #define, но и при помощи ключа  компиля-
тора, так

    cc -DXX file.c ...

соответствует включению в начало файла file.c директивы

    #define XX

А для программы

    main(){
    #ifdef XX
            printf( "XX = %d\n", XX);
    #else
            printf( "XX undefined\n");
    #endif
    }

ключ

    cc -D"XX=2" file.c ...

эквивалентен заданию директивы

    #define XX 2

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

    cc -Dvoid=int ...
    cc -Dstrchr=index ...

В некоторых системах компилятор автоматически  определяет  специальные  макросы:  так
компиляторы в UNIX неявно подставляют один из ключей (или несколько сразу):

            -DM_UNIX
            -DM_XENIX
            -Dunix
            -DM_SYSV
            -D__SVR4
            -DUSG
            ... бывают и другие

А. Богатырев, 1992-95                  - 134 -                              Си в UNIX

Это позволяет программе "узнать", что ее компилируют для системы  UNIX.   Более  под-
робно про это написано в документации по команде cc.

3.19.  Оператор #ifdef применяется в include-файлах, чтобы исключить повторное  вклю-
чение одного и того же файла.  Пусть файлы aa.h и bb.h содержат

           aa.h                        bb.h
    #include "cc.h"                 #include "cc.h"
    typedef unsigned long ulong;    typedef int cnt_t;

А файлы cc.h и 00.c содержат

           cc.h                        00.c
           ...                      #include "aa.h"
    struct II { int x, y; };        #include "bb.h"
           ...                      main(){ ... }

В этом случае текст файла cc.h будет вставлен в 00.c дважды: из aa.h и из  bb.h.  При
компиляции  00.c  компилятор  сообщит "Переопределение структуры II".  Чтобы include-
файл не подставлялся еще раз, если он уже однажды  был  включен,  придуман  следующий
прием - следует оформлять файлы включений так:

    /* файл   cc.h */
    #ifndef  _CC_H
    # define _CC_H  /* определяется при первом включении */
            ...
            struct II { int x, y; };
            ...
    #endif /* _CC_H */

Второе и последующие включения такого файла будут подставлять  пустое  место,  что  и
Предыдущая страница Следующая страница
1 ... 17 18 19 20 21 22 23  24 25 26 27 28 29 30 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама