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

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


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

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


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

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

Предыдущая страница Следующая страница
1 ... 10 11 12 13 14 15 16  17 18 19 20 21 22 23 ... 87

            c = a[x][y];

В написанном же примере мы имеем в качестве индекса выражение  x,y  (оператор  "запя-
тая") со значением y, т.е.

            c = a[y];

Синтаксической ошибки нет, но смысл совершенно изменился!

2.15.  Двумерные массивы в памяти представляются как одномерные. Например, если

    int a[N][M];

то конструкция a[y][x] превращается при компиляции в одномерную конструкцию, подобную
такой:

    int a[N * M]; /* массив развернут построчно */
    #define a_yx(y, x)   a[(x) + (y) * M]

то есть

    a[y][x] есть *(&a[0][0] + y * M + x)

Следствием этого является то, что компилятор для генерации  индексации  двумерных  (и
более)  массовов  должен  знать M - размер массива по 2-ому измерению (а также 3-ему,
4-ому, и.т.д.).  В частности, при передаче многомерного массива в функцию

    f(arr) int arr[N][M]; { ... }   /* годится    */
    f(arr) int arr[] [M]; { ... }   /* годится    */
    f(arr) int arr[] [];  { ... }   /* не годится */

    f(arr) int (*arr)[M]; { ... }   /* годится    */
    f(arr) int  *arr [M]; { ... }   /* не годится:
                  это уже не двумерный массив,
                  а одномерный массив указателей  */

А также при описании внешних массивов:

    extern int a[N][M];     /* годится */
    extern int a[ ][M];     /* годится */
    extern int a[ ][ ];     /* не годится: компилятор
              не сможет сгенерить операцию индексации */

Вот как, к примеру, должна выглядеть работа  с  двумерным  массивом  arr[ROWS][COLS],
отведенным при помощи malloc();

    void f(int array[][COLS]){
            int x, y;
            for(y=0; y < ROWS; y++)
                for(x=0; x < COLS; x++)
                    array[y][x] = 1;
    }
    void main(){
            int *ptr = (int *) malloc(sizeof(int) * ROWS * COLS);
            f( (int (*) [COLS]) ptr);
    }

2.16.  Как описывать ссылки (указатели) на двумерные массивы?  Рассмотрим такую прог-
рамму:

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

    #include 
    #define First  3
    #define Second 5

    char arr[First][Second] = {
            "ABC.",
            { 'D', 'E', 'F', '?', '\0' },
            { 'G', 'H', 'Z', '!', '\0' }
    };

    char (*ptr)[Second];

    main(){
            int i;

            ptr = arr;      /* arr и ptr теперь взаимозаменимы */
            for(i=0; i < First; i++)
                    printf("%s\t%s\t%c\n", arr[i], ptr[i], ptr[i][2]);
    }

Указателем здесь является ptr. Отметим, что у  него  задана  размерность  по  второму
измерению:  Second, именно для того, чтобы компилятор мог правильно вычислить двумер-
ные индексы.
     Попробуйте сами объявить

    char (*ptr)[4];
    char (*ptr)[6];
    char **ptr;

и увидеть, к  каким  невеселым  эффектам  это  приведет  (компилятор,  кстати,  будет
ругаться;  но  есть вероятность, что он все же странслирует это для вас.  Но работать
оно будет плачевно).  Попробуйте также использовать ptr[x][y].
     Обратите также внимание на инициализацию строк в нашем примере.   Строка  "ABC."
равносильна объявлению

            { 'A', 'B', 'C', '.', '\0' },

2.17.  Массив s моделирует двумерный  массив  char  s[H][W];  Перепишите  пример  при
помощи     указателей,    избавьтесь    от    операции    умножения.    Прямоугольник
(x0,y0,width,height) лежит целиком внутри (0,0,W,H).

    char s[W*H]; int x,y; int x0,y0,width,height;
    for(x=0; x < W*H; x++) s[x] = '.';
         ...
    for(y=y0; y < y0+height; y++)
      for(x=x0; x < x0+width; x++)
          s[x + W*y] = '*';

Ответ:

    char s[W*H]; int i,j; int x0,y0,width,height;
    char *curs;
         ...
    for(curs = s + x0 + W*y0, i=0;
        i < height; i++, curs += W-width)
      for(j=0; j < width; j++)
            *curs++ = '*';

Такая оптимизация возможна в некоторых функциях из главы "Работа с видеопамятью".

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

2.18.  Что означают описания?

    int i;            // целое.
    int *pi;          // указатель на целое.
    int *api[3];      // массив из 3х ук-лей на целые.
    int (*pai)[3];    // указатель на массив из 3х целых.
                      // можно описать как    int **pai;
    int fi();         // функция, возвращающая целое.
    int *fpi();       // ф-ция, возвр. ук-ль на целое.
    int (*pfi)();     // ук-ль на ф-цию, возвращающую целое.
    int *(*pfpi)();   // ук-ль на ф-цию, возвр. ук-ль на int.
    int (*pfpfi())(); // ф-ция, возвращающая указатель на
                      // "функцию, возвращающую целое".
    int (*fai())[3];  // ф-ция, возвр. ук-ль на массив
                      // из 3х целых. иначе ее
                      // можно описать как    int **fai();
    int (*apfi[3])(); // массив из 3х ук-лей на функции,
                      // возвращающие целые.

Переменные в Си описываются в формате их использования.  Так описание

    int (*f)();

означает, что f можно использовать в виде

    int value;
    value = (*f)(1, 2, 3 /* список аргументов */);

Однако из такого способа описания  тип  самой  описываемой  переменной  и  его  смысл
довольно  неочевидны.  Приведем прием (позаимствованный из журнала "Communications of
the ACM"), позволяющий прояснить смысл описания.  Описание на Си переводится в описа-
ние в стиле языка Algol-68.  Далее

    ref      ТИП    означает  "указатель на ТИП"
    proc()   ТИП              "функция, возвращающая ТИП"
    array of ТИП              "массив из элементов ТИПа"
    x:       ТИП              "x имеет тип ТИП"

Приведем несколько примеров, из которых ясен и способ преобразования:

    int (*f())();     означает
            (*f())()  :                    int
             *f()     :             proc() int
              f()     :         ref proc() int
              f       :  proc() ref proc() int

то есть f - функция, возвращающая указатель на функцию, возвращающую целое.

    int (*f[3])();    означает
            (*f[])()  :                      int
             *f[]     :               proc() int
              f[]     :           ref proc() int
              f       :  array of ref proc() int

f - массив указателей на функции, возвращающие целые.  Обратно: опишем g  как  указа-
тель  на функцию, возвращающую указатель на массив из 5и указателей на функции, возв-
ращающие указатели на целые.

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

           g          : ref p() ref array of ref p() ref int
          *g          :     p() ref array of ref p() ref int
         (*g)()       :         ref array of ref p() ref int
        *(*g)()       :             array of ref p() ref int
       (*(*g)())[5]   :                      ref p() ref int
      *(*(*g)())[5]   :                          p() ref int
     (*(*(*g)())[5])():                              ref int
    *(*(*(*g)())[5])():                                  int
                         int *(*(*(*g)())[5])();

В Си невозможны функции, возвращающие массив:

    proc() array of ...
            а только
    proc() ref array of ...

Само название типа (например, для использования в операции приведения  типа)  получа-
ется вычеркиванием имени переменной (а также можно опустить размер массива):

            g = ( int *(*(*(*)())[])() ) 0;

2.19.  Напишите функцию strcat(d,s), приписывающую строку s к концу строки d.
Ответ:

     char *strcat(d,s) register char *d, *s;
     {  while( *d ) d++;      /* ищем конец строки d */
        while( *d++ = *s++ ); /* strcpy(d, s)        */
        return (d-1);         /* конец строки        */
     }

Цикл, помеченный "strcpy" - это наиболее краткая запись операторов

        do{ char c;
            c = (*d = *s); s++; d++;
        } while(c != '\0');

На самом деле strcat должен по стандарту возвращать свой первый аргумент, как и функ-
ция strcpy:

     char *strcat(d,s) register char *d, *s;
     {  char *p = d;
        while( *d ) d++;
        strcpy(d, s); return p;
     }

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

2.20.  Напишите программу, которая объединяет и распечатывает две строки, введенные с
терминала.  Для  ввода  строк  используйте  функцию  gets(),  а  для их объединения -
strcat(). В другом варианте используйте
  sprintf(result,"%s%s",s1,s2);

2.21.  Модифицируйте предыдущую программу таким образом,  чтобы  она  выдавала  длину
(число  символов)  объединенной строки.  Используйте функцию strlen().  Приведем нес-
колько версий реализации strlen:

    /* При помощи индексации массива */

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

    int strlen(s) char s[];
    {   int length = 0;
        for(; s[length] != '\0'; length++);
        return (length);
    }
    /* При помощи продвижения указателя */
    int strlen(s) char *s;
    {   int length;
        for(length=0; *s; length++, s++);
        return length;
    }
    /* При помощи разности указателей */
    int strlen(register char *s)
    {   register char *p = s;
        while(*p) p++;   /* ищет конец строки */
        return (p - s);
    }

Разность двух указателей на один и тот же тип - целое число:

    если TYPE *p1, *p2;
    то  p2 - p1 = целое число штук TYPE
                  лежащих между p2 и p1
    если p2 = p1 + n
    то   p2 - p1 = n

Эта разность может быть и отрицательной если p2 < p1, то есть p2 указывает  на  более
левый элемент массива.

2.22.  Напишите оператор Си, который обрубает строку s до длины n букв. Ответ:

    if( strlen(s) > n )
     s[n] = '\0';

Первое сравнение вообще говоря излишне.  Оно написано лишь на тот случай, если строка
s  короче,  чем n букв и хранится в массиве, который также короче n, т.е. не имеет n-
ого элемента (поэтому в него нельзя производить запись признака конца).

2.23.  Напишите функции преобразования строки, содержащей изображение целого числа, в
само  это  число.  В  двух разных вариантах аргумент-адрес должен указывать на первый
байт строки; на последний байт. Ответ:

    #define isdigit(c) ('0' <= (c) && (c) <= '9')

    int atoi(s) register char *s;
    {   register int res=0, neg=0;
        for(;;s++){
            switch(*s){
            case ' ': case '\t': continue;
            case '-':            neg++;
            case '+':            s++;
            } break;
        }
        while(isdigit(*s))
            res = res * 10  +  *s++ - '0';
        return( neg ? -res : res );
    }

    int backatoi(s) register char *s;
    {   int res=0, pow=1;
        while(isdigit(*s)){

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

            res += (*s-- - '0') * pow;
            pow *= 10;
        }
        if(*s == '-') res = -res;
        return res;
    }

2.24.  Можно ли для занесения в массив s строки "hello" написать

            char s[6]; s = "hello";
                 или
            char s[6], d[] = "hello"; s = d;

Ответ: нет.  Массивы в Си нельзя присваивать целиком. Для пересылки массива байт надо
использовать функцию strcpy(s,d).  Здесь же мы пытаемся изменить адрес s (имя массива
- это адрес начала памяти, выделенной для хранения массива), сделав его равным адресу
безымянной  строки  "hello"  (или  массива  d во втором случае).  Этот адрес является
константой и не может быть изменен!
     Заметим однако, что описание массива с инициализацией вполне допустимо:

            char s[6] = "hello";
                    или
            char s[6] = { 'h', 'e', 'l', 'l', 'o', '\0' };
Предыдущая страница Следующая страница
1 ... 10 11 12 13 14 15 16  17 18 19 20 21 22 23 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама