Главная · Поиск книг · Поступления книг · 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 ... 9 10 11 12 13 14 15  16 17 18 19 20 21 22 ... 87
массива array.  Тогда после

    TYPE *ptr2 = array + L;  /* L - целое */
    TYPE *ptr1 = ptr   + N;  /* N - целое */
          ptr += M;          /* M - целое */

указатели указывают на

    ptr1 == &array[x+N]   и   ptr  == &array[x+M]
    ptr2 == &array[L]

Если мы теперь рассмотрим цепочку равенств

    *ptr2 = *(array + L) = *(&array[L]) =
              array[L]

то получим
ОСНОВНОЕ ПРАВИЛО: пусть ptr - указатель или имя массива. Тогда  операции  индексации,
взятия  значения  по  адресу,  взятия адреса и прибавления целого к указателю связаны
соотношениями:

     ptr[x]  тождественно *(ptr+x)
    &ptr[x]  тождественно   ptr+x

(тождества верны в обе стороны), в том числе при x==0 и x < 0. Так что, например,

    ptr[-1] означает  *(ptr-1)
    ptr[0]  означает  *ptr

Указатели можно индексировать подобно массивам.  Рассмотрим пример:

            /* индекс:     0    1    2    3    4   */
    double  numbers[5] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
    double *dptr   = &numbers[2];
    double  number =  dptr[2];  /* равно 4.0 */

    numbers: [0]   [1]   [2]   [3]   [4]
                          |
            [-2]  [-1]   [0]   [1]   [2]
                         dptr

поскольку

    если dptr    = &numbers[x] = numbers + x
    то   dptr[i] = *(dptr + i) =
                 = *(numbers + x + i) = numbers[x + i]

     Указатель на один тип можно преобразовать в указатель на другой тип: такое  пре-
образование  не вызывает генерации каких-либо машинных команд, но заставляет компиля-
тор изменить параметры адресной арифметики, а также операции выборки данного по  ука-
зателю (собственно, разница в указателях на данные разных типов состоит только в раз-
мерах указуемых типов; а также в генерации команд `->>' для  выборки  полей  структур,
если указатель - на структурный тип).
     Целые (int или long) числа иногда можно преобразовывать в указатели.  Этим поль-
зуются  при написании драйверов устройств для доступа к регистрам по физическим адре-
сам, например:

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

    unsigned short *KISA5 = (unsigned short *) 0172352;

Здесь возникают два тонких момента:
1.   Как уже было сказано, адреса данных часто выравниваются  на  границу  некоторого
     типа.   Мы  же  можем  задать  невыровненное  целое значение.  Такой адрес будет
     некорректен.
2.   Структура адреса, поддерживаемая процессором, может не  соответствовать  формату
     целых (или длинных целых) чисел. Так обстоит дело с IBM PC 8086/80286, где адрес
     состоит из пары short int чисел, хранящихся в памяти подряд.  Однако весь  адрес
     (если  рассматривать  эти  два числа как одно длинное целое) не является обычным
     long-числом, а вычисляется более сложным способом: адресная пара  SEGMENT:OFFSET
     преобразуется так

    unsigned short SEGMENT, OFFSET; /*16 бит: [0..65535]*/
    unsigned long  ADDRESS = (SEGMENT << 4) + OFFSET;
      получается 20-и битный физический адрес ADDRESS

     Более того, на машинах с диспетчером памяти, адрес, хранимый в указателе,  явля-
     ется "виртуальным" (т.е. воображаемым, ненастоящим) и может не совпадать с физи-
     ческим адресом, по которому данные хранятся в памяти компьютера.  В памяти может
     одновременно  находиться  несколько программ, в каждой из них будет своя система
     адресации ("адресное пространство"), отсчитывающая виртуальные адреса с нуля  от
     начала  области  памяти, выделенной данной программе. Преобразование виртуальных
     адресов в физические выполняется аппаратно.
В Си принято соглашение, что указатель (TYPE *)0 означает "указатель ни на  что".  Он
является  просто  признаком,  используемым для обозначения несуществующего адреса или
конца цепочки указателей, и имеет специальное обозначение NULL.   Обращение  (выборка
или  запись данных) по этому указателю считается некорректным (кроме случая, когда вы
пишете машинно-зависимую программу и работаете с физическими адресами).
     Отметим, что указатель можно направить в неправильное место - на участок памяти,
содержащий  данные  не  того  типа,  который  задан в описании указателя; либо вообще
содержащий неизвестно что:

    int i = 2, *iptr = &i;
    double x = 12.76;
      iptr += 7;  /* куда же он указал ?! */
      iptr = (int *) &x;  i = *iptr;

Само присваивание указателю некорректного значения еще не  является  ошибкой.  Ошибка
возникнет  лишь  при  обращении  к  данным  по этому указателю (такие ошибки довольно
тяжело искать!).

     При передаче имени массива в качестве параметра функции, как аргумент передается
не  копия  САМОГО  МАССИВА  (это заняло бы слишком много места), а копия АДРЕСА 0-ого
элемента этого массива (т.е. указатель на начало массива).

    f(int x   ){ x++;     }
    g(int xa[]){ xa[0]++; }
    int a[2] = { 1, 1 }; /* объявление с инициализацией */
    main(){
     f(a[0]); printf("%d\n",a[0]); /* a[0] осталось равно 1*/
     g(a   ); printf("%d\n",a[0]); /* a[0] стало равно 2   */
    }

В f() в качестве аргумента передается копия элемента a[0] (и изменение этой копии  не
приводит  к  изменению  самого  массива  - аргумент x является локальной переменной в
f()), а в g() таким локалом является АДРЕС массива a -  но  не  сам  массив,  поэтому
xa[0]++  изменяет  сам  массив  a  (зато,  например, xa++ внутри g() изменило бы лишь
локальную указательную переменную xa, но не адрес массива a).
     Заметьте, что поскольку массив передается как указатель на его начало, то размер
массива  в  объявлении  аргумента  можно  не указывать.  Это позволяет одной функцией

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

обрабатывать массивы разной длины:

    вместо    Fun(int xa[5]) { ... }
    можно     Fun(int xa[] ) { ... }
    или даже  Fun(int *xa  ) { ... }

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

     int sum( int a[], int len ){
       int s=0, i;
       for(i=0; i < len; i++) s += a[i];
       return( s );
     }
     ... int arr[10] = { ... };
     ... int sum10 = sum(arr, 10); ...

Количество элементов в массиве TYPE arr[N]; можно вычислить специальным образом, как

    #define LENGTH (sizeof(arr) / sizeof(arr[0]))
            или
    #define LENGTH (sizeof(arr) / sizeof(TYPE))

Оба способа выдадут число, равное N.  Эти конструкции обычно употребляются для вычис-
ления длины массивов, задаваемых в виде

    TYPE arr[] = { ....... };

без явного указания размера.  sizeof(arr)  выдает  размер  всего  массива  в  байтах.
sizeof(arr[0])  выдает размер одного элемента.  И все это не зависит от типа элемента
(просто потому, что все элементы массивов имеют одинаковый размер).

     Строка в Си - это последовательность байт (букв,  символов,  литер,  character),
завершающаяся  в  конце специальным признаком - байтом '\0'. Этот признак добавляется
компилятором автоматически, когда мы задаем строку в  виде  "строка".   Длина  строки
(т.е.  число литер, предшествующих '\0') нигде явно не хранится. Длина строки ограни-
чена лишь размером массива, в котором сохранена строка, и может изменяться в процессе
работы  программы в пределах от 0 до длины массива-1.  При передаче строки в качестве
аргумента в функцию, функции не требуется знать длину строки, т.к. передается  указа-
тель на начало массива, а наличие ограничителя '\0' позволяет обнаружить конец строки
при ее просмотре.
     С массивами байт можно  использовать  следующую  конструкцию,  задающую  массивы
(строки) одинакового размера:

    char stringA [ITSSIZE];
    char stringB [sizeof stringA];

В данном разделе мы в основном будем рассматривать строки и указатели на символы.

2.1.  Операции взятия адреса объекта и разыменования указателя - взаимно обратны.

    TYPE  objx;
    TYPE *ptrx = &objx;  /* инициализируем адресом objx */

    *(&objx) = objx;
    &(*ptrx) = ptrx;

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

    if(c) a = 1;
    else  b = 1;

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

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

    #include 
    int main(int ac, char *av[]){
            int a, b, c;

            a = b = c = 0;
            if(av[1])  c = atoi(av[1]);

            *(c ? &a : &b) = 1;     /* !!! */

            printf("cond=%d a=%d b=%d\n", c, a, b);
            return 0;
    }

2.2.  Каким образом инициализируются по умолчанию внешние и статические массивы? Ини-
циализируются  ли  по умолчанию автоматические массивы?  Каким образом можно присваи-
вать значения элементам массива, относящегося к любому классу памяти?

2.3.  Пусть задан массив int arr[10]; что тогда означают выражения:

      arr[0]        *arr            *arr + 2
      arr[2]        *(arr + 2)       arr
     &arr[2]         arr+2

2.4.  Правильно ли написано увеличение величины, на которую указывает указатель a, на
единицу?

    *a++;

Ответ: нет, надо:

    (*a)++;   или    *a += 1;

2.5.  Дан фрагмент текста:

    char a[] = "xyz";
    char *b  = a + 1;

Чему равны

    b[-1]       b[2]      "abcd"[3]

(Ответ: 'x', '\0', 'd' )
     Можно ли написать a++ ? То же про b++ ?  Можно ли написать b=a ?  a=b  ?   (нет,
да, да, нет)

2.6.  Ниже приведена программа, вычисляющая среднее значение элементов массива

      int arr [] = {1, 7, 4, 45, 31, 20, 57, 11};
      main () {
         int i; long sum;

         for ( i = 0, sum = 0L;
               i < (sizeof(arr)/sizeof(int)); i++ )
                    sum += arr[i];
         printf ("Среднее значение = %ld\n", sum/8)

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

      }

Перепишите указанную программу с применением указателей.

2.7.  Что напечатается в результате работы программы?

         char arr[] = {'С', 'Л', 'А', 'В', 'А'};
         main () {
            char *pt; int i;

            pt = arr + sizeof(arr) - 1;
            for( i = 0; i < 5; i++, pt--  )
                 printf("%c %c\n", arr[i], *pt);
         }

Почему массив arr[] описан вне функции main()?  Как внести его  в  функцию  main()  ?
Ответ: написать внутри main
  static char arr[]=...

2.8.  Можно ли писать на Си так:

            f( n, m ){
                    int x[n]; int y[n*2];
                    int z[n * m];
                    ...
            }

Ответ: к сожалению нельзя (Си - это не Algol).  При отведении памяти  для  массива  в
качестве  размера должна быть указана константа или выражение, которое может быть еще
во время компиляции вычислено до целочисленной константы, т.е. массивы имеют фиксиро-
ванную длину.

2.9.  Предположим, что у нас есть описание массива

            static int mas[30][100];

a)   выразите адрес mas[22][56] иначе
b)   выразите адрес mas[22][0] двумя способами
c)   выразите адрес mas[0][0] тремя способами

2.10.  Составьте программу инициализации двумерного массива a[10][10],  выборки  эле-
ментов  с a[5][5] до a[9][9] и их распечатки.  Используйте доступ к элементам по ука-
зателю.

2.11.  Составьте функцию вычисления скалярного  произведения  двух  векторов.   Длина
векторов задается в качестве одного из аргументов.

2.12.  Составьте функцию умножения двумерных матриц a[][] * b[][].

2.13.  Составьте функцию умножения трехмерных матриц a[][][] * b[][][].

2.14.  Для тех, кто программировал на языке Pascal: какая допущена ошибка?

            char a[10][20];
            char c;
            int x,y;
              ...
            c = a[x,y];

Ответ: многомерные массивы в Си надо индексировать так:

А. Богатырев, 1992-95                  - 88 -                               Си в UNIX
Предыдущая страница Следующая страница
1 ... 9 10 11 12 13 14 15  16 17 18 19 20 21 22 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама