Главная · Поиск книг · Поступления книг · 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 2 3 4 5 6 7 8  9 10 11 12 13 14 15 ... 87
кого параметра.  Чтобы изменять фактический параметр, надо передавать его адрес!

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

1.101.  Поясним последнюю фразу. (Внимание! Возможно, что данный  пункт  вам  следует
читать  ПОСЛЕ  главы про указатели). Пусть мы хотим написать функцию, которая обмени-
вает свои аргументы x и y так, чтобы выполнялось x < y. В качестве  значения  функция
будет выдавать (x+y)/2. Если мы напишем так:

    int msort(x, y) int x, y;
    { int tmp;
      if(x > y){ tmp=x; x=y; y=tmp; }
      return (x+y)/2;
    }
    int x=20, y=8;
    main(){
      msort(x,y); printf("%d %d\n", x, y); /* 20 8 */
    }

то мы не достигнем желаемого эффекта. Здесь переставляются x и  y,  которые  являются
локальными переменными, т.е. копиями фактических параметров.  Поэтому вне функции эта
перестановка никак не проявляется!
     Чтобы мы могли изменить аргументы, копироваться в локальные переменные должны не
сами значения аргументов, а их адреса:

    int msort(xptr, yptr) int *xptr, *yptr;
    { int tmp;
      if(*xptr > *yptr){tmp= *xptr;*xptr= *yptr;*yptr=tmp;}
      return (*xptr + *yptr)/2;
    }
    int x=20, y=8, z;
    main(){
      z = msort(&x,&y);
      printf("%d %d %d\n", x, y, z); /* 8 20 14 */
    }

Обратите внимание, что теперь мы передаем в функцию не значения x и y, а их адреса &x
и &y.
     Именно поэтому (чтобы x смог измениться)  стандартная  функция  scanf()  требует
указания адресов:

    int x; scanf("%d", &x); /* но не scanf("%d", x); */

Заметим, что адрес от арифметического выражения или от константы (а не от переменной)
вычислить нельзя, поэтому законны:

    int xx=12, *xxptr = &xx, a[2] = { 13, 17 };
    int *fy(){ return &y; }
            msort(&x, &a[0]);       msort(a+1, xxptr);
            msort(fy(), xxptr);

но незаконны

            msort(&(x+1), &y);   и  msort(&x, &17);

Заметим еще, что при работе с адресами мы можем направить указатель в неверное  место
и получить непредсказуемые результаты:

            msort(&xx - 20, a+40);

(указатели указывают неизвестно на что).
     Резюме: если аргумент служит только для передачи значения В  функцию  -  его  не
надо  (хотя  и  можно) делать указателем на переменную, содержащую требуемое значение
(если только это уже не указатель).  Если же аргумент служит для передачи значения ИЗ
функции  -  он  должен  быть  указателем  на  переменную  возвращаемого  типа  (лучше

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

возвращать значение как значение функции - return-ом, но иногда надо возвращать  нес-
колько значений - и этого главного "окошка" не хватает).
     Контрольный вопрос: что печатает фрагмент?

    int a=2, b=13, c;
    int f(x, y, z) int x, *y, z;
    {
            *y += x; x *= *y; z--;
            return (x + z - a);
    }
    main(){ c=f(a, &b, a+4); printf("%d %d %d\n",a,b,c); }

(Ответ: 2 15 33)

1.102.  Формальные аргументы функции - это такие же локальные переменные.   Параметры
как бы описаны в самом внешнем блоке функции:

    char *func1(char *s){
            int s;        /* ошибка: повторное определение имени s */
            ...
    }
    int func2(int x, int y){
            int z;
            ...
    }
    соответствует
    int func2(){
            int x = безымянный_аргумент_1_со_стека;
            int y = безымянный_аргумент_2_со_стека;
            int z;
            ...
    }

Мораль такова: формальные аргументы можно смело изменять и использовать как локальные
переменные.

1.103.  Все параметры функции можно разбить на 3 класса:
-    in - входные;
-    out - выходные, служащие для возврата значения из функции;  либо  для  изменения
     данных, находящихся по этому адресу;
-    in/out - для передачи значения в функцию и из функции.

Два последних типа параметров должны быть указателями.  Иногда (особенно в прототипах
и в документации) бывает полезно указывать класс параметра в виде комментария:

    int f( /*IN*/    int  x,
           /*OUT*/   int *yp,
           /*INOUT*/ int *zp){
       *yp = ++x + ++(*zp);
       return (*zp *= x) - 1;
    }
    int x=2, y=3, z=4, res;
    main(){  res = f(x,  &y,  &z);
     printf("res=%d  x=%d y=%d z=%d\n",res,x,y,z);
             /*  14     2    8   15   */
    }

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

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

область памяти, в которой будет размещен результат.  Пример на эту тему есть в  главе
"Текстовая обработка" (функция bi_conv).

1.104.  Известен такой стиль оформления аргументов функции:

    void func(   int    arg1
             ,   char  *arg2      /* argument 2 */
             ,   char  *arg3[]
             ,   time_t time_stamp
             ){ ... }

Суть его в том, что запятые пишутся в столбик и в одну линию с (  и  )  скобками  для
аргументов.   При  таком  стиле легче добавлять и удалять аргументы, чем при версии с
запятой в конце.  Этот же стиль применим, например, к перечислимым типам:

    enum {  red
         ,  green
         ,  blue
         };

Напишите программу, форматирующую заголовки функций таким образом.

1.105.  В чем ошибка?

    char *val(int x){
         char str[20];
         sprintf(str, "%d", x);
         return str;
    }
    void main(){
         int x = 5; char *s = val(x);
         printf("The values:\n");
         printf("%d %s\n", x, s);
    }

Ответ: val возвращает указатель на автоматическую переменную. При выходе  из  функции
val()  ее локальные переменные (в частности str[]) в стеке уничтожаются - указатель s
теперь указывает на испорченные данные!  Возможным решением проблемы является превра-
щение str[] в статическую переменную (хранимую не в стеке):

    static char str[20];

Однако такой способ не позволит писать конструкции вида

    printf("%s %s\n", val(1), val(2));

так как под оба вызова val() используется один и тот же буфер  str[]  и  будет  печа-
таться  "1 1"  либо  "2 2",  но  не "1 2".  Более правильным будет задание буфера для
результата val() как аргумента:

    char *val(int x, char str[]){
          sprintf(str, "%d", x);
          return str;
    }
    void main(){
         int x=5, y=7;
         char s1[20], s2[20];
         printf("%s %s\n", val(x, s1), val(y, s2));
    }

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

1.106.  Каковы ошибки (не синтаксические) в программе[*]?

            main() {
               double y; int x = 12;
               y = sin (x);
               printf ("%s\n", y);
            }

Ответ:
-    стандартная библиотечная функция sin() возвращает значение типа  double,  но  мы
     нигде  не  информируем об этом компилятор.  Поэтому он считает по умолчанию, что
     эта функция возвращает значение типа int и делает в присваивании y=sin(x) приве-
     дение типа int к типу левого операнда, т.е. к double.  В результате возвращаемое
     значение (а оно на самом деле - double) интерпретируется неверно (как int), под-
     вергается  приведению  типа  (которое портит его), и результат получается совер-
     шенно не таким, как надо.  Подобная же ошибка возникает при использовании  функ-
     ций,  возвращающих указатель, например, функций malloc() и itoa().  Поэтому если
     мы пользуемся библиотечной функцией, возвращающей не int,  мы  должны  предвари-
     тельно (до первого использования) описать ее, например[**]:

                 extern double sin();
                 extern long   atol();
                 extern char  *malloc(), *itoa();

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

         /*extern*/ char *f();
                    main(){
                         char *s;
                         s = f(1); puts(s);
                    }
                    char *f(n){ return "knights" + n; }

     Функции, возвращающие целое, описывать не  требуется.   Описания  для  некоторых
     стандартных  функций  уже помещены в системные include-файлы. Например, описания
     для  математических  функций  (sin,  cos,  fabs,   ...)   содержатся   в   файле
     /usr/include/math.h. Поэтому мы могли бы написать перед main

                 #include 
         вместо
                 extern double sin(), cos(), fabs();

-    библиотечная функция sin() требует аргумента типа  double,  мы  же  передаем  ей
     аргумент  типа int (который короче типа double и имеет иное внутреннее представ-
     ление).  Он будет неправильно  проинтерпретирован  функцией,  т.е.  мы  вычислим
     синус отнюдь НЕ числа 12.  Следует писать:

                 y = sin( (double) x );
              и  sin(12.0); вместо sin(12);

____________________
   [*] Для трансляции программы, использующей стандартные математические  функции  sin,
cos, exp, log, sqrt, и.т.п. следует задавать ключ компилятора -lm
   cc file.c -o file -lm
   [**] Слово extern ("внешняя") не является обязательным, но является  признаком  хоро-
шего  тона  -  вы сообщаете программисту, читающему эту программу, что данная функция
реализована в другом файле, либо вообще является стандартной и берется из библиотеки.

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

-    в printf мы печатаем значение типа  double  по  неправильному  формату:  следует
     использовать  формат  %g  или  %f (а для ввода при помощи scanf() - %lf).  Очень
     частой ошибкой является печать значений типа long по формату %d вместо %ld .

Первых двух проблем в современном Си удается избежать  благодаря  заданию  прототипов
функций  (о них подробно рассказано ниже, в конце главы "Текстовая обработка").  Нап-
ример, sin имеет прототип

            double sin(double x);

Третяя проблема (ошибка в формате) не может быть локализована средствами Си  и  имеет
более-менее приемлемое решение лишь в языке C++ (streams).

1.107.  Найдите ошибку:

            int sum(x,y,z){  return(x+y+z); }
            main(){
                    int s = sum(12,15);
                    printf("%d\n", s);
            }

Заметим, что если бы для функции sum() был задан прототип, то  компилятор  поймал  бы
эту  нашу оплошность! Заметьте, что сейчас значение z в sum() непредсказуемо. Если бы
мы вызывали

            s = sum(12,15,17,24);

то лишние аргументы были бы просто проигнорированы (но и тут  может  быть  сюрприз  -
аргументы могли бы игнорироваться с ЛЕВОГО конца списка!).
     А вот пример опасной ошибки, которая не ловится даже прототипами:

            int x;   scanf("%d%d", &x );

Второе число по формату %d будет считано  неизвестно  по  какому  адресу  и  разрушит
память  программы.  Ни  один компилятор не проверяет соответствие числа %-ов в строке
формата числу аргументов scanf и printf.

1.108.  Что здесь означают внутренние (,,) в вызове функции f() ?

    f(x, y, z){
            printf("%d %d %d\n", x, y, z);
    }
    main(){ int t;
            f(1, (2, 3, 4), 5);
            f(1, (t=3,t+1), 5);
    }

Ответ: (2,3,4) - это оператор "запятая", выдающий значение  последнего  выражения  из
списка перечисленных через запятую выражений. Здесь будет напечатано 1 4 5. Кажущаяся
двойственность возникает из-за того, что аргументы функции тоже  перечисляются  через
Предыдущая страница Следующая страница
1 2 3 4 5 6 7 8  9 10 11 12 13 14 15 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама