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

       

Массивы, строки, указатели. Хрестоматия по программированию на Си в Unix


Массивы, строки, указатели.

Массив представляет собой агрегат из нескольких переменных одного и того же типа. Массив с именем a из LENGTH элементов типа TYPE объявляется так:

TYPE a[LENGTH];

Это соответствует тому, что объявляются переменные типа TYPE со специальными именами a[0], a[1], ..., a[LENGTH-1]. Каждый элемент массива имеет свой номер - индекс. Доступ к x-ому элементу массива осуществляется при помощи операции индексации:

int x = ... ; /* целочисленный индекс */ TYPE value = a[x]; /* чтение x-ого элемента */ a[x] = value; /* запись в x-тый элемент */

В качестве индекса может использоваться любое выражение, выдающее значение целого

типа: char, short, int, long. Индексы элементов массива в Си начинаются с 0 (а не с 1), и индекс последнего элемента массива из LENGTH элементов - это LENGTH-1 (а не LENGTH). Поэтому цикл по всем элементам массива - это

TYPE a[LENGTH]; int indx; for(indx=0; indx < LENGTH; indx++) ...a[indx]...;

indx < LENGTH равнозначно indx <= LENGTH-1. Выход за границы массива (попытка чтения/записи несуществующего элемента) может привести к непредсказуемым результатам и поведению программы. Отметим, что это одна из самых распространенных ошибок.

Статические массивы можно объявлять с инициализацией, перечисляя значения их элементов в {} через запятую. Если задано меньше элементов, чем длина массива остальные элементы считаются нулями:

int a10[10] = { 1, 2, 3, 4 }; /* и 6 нулей */

Если при описании массива с инициализацией не указать его размер, он будет подсчитан компилятором:

int a3[] = { 1, 2, 3 }; /* как бы a3[3] */

В большинстве современных компьютеров (с фон-Неймановской архитектурой) память представляет собой массив байт. Когда мы описываем некоторую переменную или массив, в памяти выделяется непрерывная область для хранения этой переменной. Все байты памяти компьютера пронумерованы. Номер байта, с которого начинается в памяти наша переменная, называется адресом этой переменной (адрес может иметь и более сложную структуру, чем просто целое число - например состоять из номера сегмента памяти и номера байта в этом сегменте). В Си адрес переменной можно получить с помощью операции взятия адреса &. Пусть у нас есть переменная var, тогда &var - ее адрес. Адрес нельзя присваивать целой переменной; для хранения адресов используются указатели (смотри ниже).

Данное может занимать несколько подряд идущих байт. Размер в байтах участка памяти, требуемого для хранения значения типа TYPE, можно узнать при помощи операции sizeof(TYPE), а размер переменной - при помощи sizeof(var). Всегда выполняется sizeof(char)==1. В некоторых машинах адреса переменных (а также агрегатов данных массивов и структур) кратны sizeof(int) или sizeof(double) - это так называемое "выравнивание (alignment) данных на границу типа int". Это позволяет делать доступ к данным более быстрым (аппаратура работает эффективнее).

Язык Си предоставляет нам средство для работы с адресами данных - указатели




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

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

/* При помощи индексации массива */ 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 указывает на более левый элемент массива.

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

Ответ:

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

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

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

#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)){ res += (*s-- - '0') * pow; pow *= 10; } if(*s == '-') res = -res; return res; }




Раз уж речь зашла о функции strdup (кстати, это стандартная функция), приведем еще одну функцию для сохранения строк.

char *savefromto(register char *from, char *upto) { char *ptr, *s; if((ptr = (char *) malloc(upto - from + 1)) == NULL) return NULL; for(s = ptr; from < upto; from++) *s++ = *from; *s = '\0'; return ptr; }

Сам символ (*upto) не сохраняется, а заменяется на '\0'.

Упрощенный аналог функции printf.

/* * Машинно - независимый printf() (упрощенный вариант).

* printf - Форматный Вывод. */ #include <stdio.h>

#include <ctype.h>

#include <varargs.h>

#include <errno.h>

#include <string.h>

extern int errno; /* код системной ошибки, формат %m */ /* чтение значения числа */ #define GETN(n,fmt) \ n = 0; \ while(isdigit(*fmt)){ \ n = n*10 + (*fmt - '0'); \ fmt++; \ } void myprintf(fmt, va_alist) register char *fmt; va_dcl { va_list ap; char c, *s; int i; int width, /* минимальная ширина поля */ prec, /* макс. длина данного */ sign, /* выравнивание: 1 - вправо, -1 - влево */ zero, /* ширина поля начинается с 0 */ glong; /* требуется длинное целое */ va_start(ap); for(;;){ while((c = *fmt++) != '%'){ if( c == '\0' ) goto out; putchar(c); } sign = 1; zero = 0; glong = 0; if(*fmt == '-'){ sign = (-1); fmt++; } if(*fmt == '0'){ zero = 1; fmt++; } if(*fmt == '*'){ width = va_arg(ap, int); if(width < 0){ width = -width; sign = -sign; } fmt++; }else{ GETN(width, fmt); } width *= sign; if(*fmt == '.'){ if(*++fmt == '*'){ prec = va_arg(ap, int); fmt++; }else{ GETN(prec, fmt); } }else prec = (-1); /* произвольно */ if( *fmt == 'l' ){ glong = 1; fmt++; } switch(c = *fmt++){ case 'c': putchar(va_arg(ap, int)); break; case 's': prStr(width, prec, va_arg(ap, char *)); break; case 'm': prStr(width, prec, strerror(errno)); break; /* strerror преобразует код ошибки в строку-расшифровку */ case 'u': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 10 /* base */, zero); break; case 'd': prInteger(width, glong ? va_arg(ap, long) : (long) va_arg(ap, int), 10 /* base */, zero); break; case 'o': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 8 /* base */, zero); break; case 'x': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 16 /* base */, zero); break; case 'X': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), -16 /* base */, zero); break; case 'b': prUnsigned(width, glong ? va_arg(ap, unsigned long) : (unsigned long) va_arg(ap, unsigned int), 2 /* base */, zero); break; case 'a': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), 16 /* base */, zero); break; case 'A': /* address */ prUnsigned(width, (long) (char *) va_arg(ap, char *), -16 /* base */, zero); break; case 'r': prRoman(width, prec, va_arg(ap, int)); break; case '%': putchar('%'); break; default: putchar(c); break; } } out: va_end(ap); } /* --------------------------------------------------------- */ int strnlen(s, maxlen) char *s; { register n; for( n=0; *s && n < maxlen; n++, s++ ); return n; } /* Печать строки */ static prStr(width, prec, s) char *s; { int ln; /* сколько символов выводить */ int toLeft = 0; /* к какому краю прижимать */ if(s == NULL){ pr( "(NULL)", 6); return; } /* Измерить длину и обрубить длинную строку. * Дело в том, что строка может не иметь \0 на конце, тогда * strlen(s) может привести к обращению в запрещенные адреса */ ln = (prec > 0 ? strnlen(s, prec) : strlen(s)); /* ширина поля */ if( ! width ) width = (prec > 0 ? prec : ln); if( width < 0){ width = -width; toLeft = 1; } if( width > ln){ /* дополнить поле пробелами */ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, ' '); pr(s, ln); } } else { pr(s, ln); } } /* Печать строки длиной l */ static pr(s, ln) register char *s; register ln; { for( ; ln > 0 ; ln-- ) putchar( *s++ ); } /* Печать n символов c */ static prSpace(n, c) register n; char c;{ for( ; n > 0 ; n-- ) putchar( c ); } /* --------------------------------------------------------- */ static char *ds; /* Римские цифры */ static prRoman(w,p,n){ char bd[60]; ds = bd; if( n < 0 ){ n = -n; *ds++ = '-'; } prRdig(n,6); *ds = '\0'; prStr(w, p, bd); } static prRdig(n, d){ if( !n ) return; if( d ) prRdig( n/10, d - 2); tack(n%10, d); } static tack(n, d){ static char im[] = " MDCLXVI"; /* ..1000 500 100 50 10 5 1 */ if( !n ) return; if( 1 <= n && n <= 3 ){ repeat(n, im[d+2]); return; } if( n == 4 ) *ds++ = im[d+2]; if( n == 4 n == 5 ){ *ds++ = im[d+1]; return; } if( 6 <= n && n <= 8 ){ *ds++ = im[d+1]; repeat(n - 5, im[d+2] ); return; } /* n == 9 */ *ds++ = im[d+2]; *ds++ = im[d]; } static repeat(n, c) char c; { while( n-- > 0 ) *ds++ = c; } /* --------------------------------------------------------- */ static char aChar = 'A'; static prInteger(w, n, base, zero) long n; { /* преобразуем число в строку */ char bd[128]; int neg = 0; /* < 0 */ if( n < 0 ){ neg = 1; n = -n; } if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Теперь печатаем строку */ prIntStr( bd, w, zero, neg ); } static prUnsigned(w, n, base, zero) unsigned long n; { char bd[128]; if( base < 0 ){ base = -base; aChar = 'A'; } else { aChar = 'a'; } ds = bd; prUDig( n, base ); *ds = '\0'; /* Теперь печатаем строку */ prIntStr( bd, w, zero, 0 ); } static prUDig( n, base ) unsigned long n; { unsigned long aSign; if((aSign = n/base ) > 0 ) prUDig( aSign, base ); aSign = n % base; *ds++ = (aSign < 10 ? '0' + aSign : aChar + (aSign - 10)); } static prIntStr( s, width, zero, neg ) char *s; { int ln; /* сколько символов выводить */ int toLeft = 0; /* к какому краю прижимать */ ln = strlen(s); /* длина строки s */ /* Ширина поля: вычислить, если не указано явно */ if( ! width ){ width = ln; /* ширина поля */ if( neg ) width++; /* 1 символ для минуса */ } if( width < 0 ){ width = -width; toLeft = 1; } if( ! neg ){ /* Положительное число */ if(width > ln){ if(toLeft){ pr(s, ln); prSpace(width - ln, ' '); } else { prSpace(width - ln, zero ? '0' : ' '); pr(s, ln); } } else { pr(s, ln); } }else{ /* Отрицательное число */ if(width > ln){ /* Надо заполнять оставшуюся часть поля */ width -- ; /* width содержит одну позицию для минуса */ if(toLeft){ putchar('-'); pr(s, ln); prSpace(width - ln, ' '); } else{ if( ! zero ){ prSpace(width - ln, ' '); putchar('-'); pr(s,ln); } else { putchar('-'); prSpace(width - ln, '0'); pr(s, ln); } } } else { putchar('-'); pr(s, ln); } } } /* --------------------------------------------------------- */ main(){ int i, n; static char s[] = "Hello, world!\n"; static char p[] = "Hello, world"; long t = 7654321L; myprintf( "%%abc%Y\n"); myprintf( "%s\n", "abs" ); myprintf( "%5s|\n", "abs" ); myprintf( "%-5s|\n", "abs" ); myprintf( "%5s|\n", "xyzXYZ" ); myprintf( "%-5s|\n", "xyzXYZ" ); myprintf( "%5.5s|\n", "xyzXYZ" ); myprintf( "%-5.5s|\n", "xyzXYZ" ); myprintf( "%r\n", 444 ); myprintf( "%r\n", 999 ); myprintf( "%r\n", 16 ); myprintf( "%r\n", 18 ); myprintf( "%r\n", 479 ); myprintf( "%d\n", 1234 ); myprintf( "%d\n", -1234 ); myprintf( "%ld\n", 97487483 ); myprintf( "%2d|%2d|\n", 1, -3 ); myprintf( "%-2d|%-2d|\n", 1, -3 ); myprintf( "%02d|%2d|\n", 1, -3 ); myprintf( "%-02d|%-2d|\n", 1, -3 ); myprintf( "%5d|\n", -12 ); myprintf( "%05d|\n", -12 ); myprintf( "%-5d|\n", -12 ); myprintf( "%-05d|\n", -12 ); for( i = -6; i < 6; i++ ) myprintf( "width=%2d|%0*d|%0*d|%*d|%*d|\n", i, i, 123, i, -123, i, 123, i, -123); myprintf( "%s at location %a\n", s, s ); myprintf( "%ld\n", t ); n = 1; t = 1L; for( i=0; i < 34; i++ ){ myprintf( "for %2d |%016b|%d|%u|\n\t |%032lb|%ld|%lu|\n", i, n, n, n, t, t, t ); n *= 2; t *= 2; } myprintf( "%8x %8X\n", 7777, 7777 ); myprintf( "|%s|\n", p ); myprintf( "|%10s|\n", p ); myprintf( "|%-10s|\n", p ); myprintf( "|%20s|\n", p ); myprintf( "|%-20s|\n", p ); myprintf( "|%20.10s|\n", p ); myprintf( "|%-20.10s|\n", p ); myprintf( "|%.10s|\n", p ); }




Следствием такой интерпретации имен массивов является то, что для того чтобы поставить указатель на начало массива, надо писать

ptr = array; или ptr = &array[0]; но не ptr = &array;

Операция & перед одиноким именем массива не нужна и недопустима!

Такое родство указателей и массивов позволяет нам применять операцию * к имени массива: value = *array; означает то же самое, что и value = array[0];

Указатели - не целые числа! Хотя физически это и номера байтов, адресная арифметика отличается от обычной. Так, если дан указатель TYPE *ptr; и номер байта (адрес), на который указывает ptr, равен byteaddr, то

ptr = ptr + n; /* n - целое, может быть и < 0 */

заставит ptr указывать не на байт номер byteaddr + n, а на байт номер

byteaddr + (n * sizeof(TYPE))

то есть прибавление единицы к указателю продвигает адрес не на 1 байт, а на размер указываемого указателем типа данных! Пусть указатель ptr указывает на x-ый элемент массива 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) числа иногда можно преобразовывать в указатели. Этим пользуются при написании драйверов устройств для доступа к регистрам по физическим адресам, например:

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).

Заметьте, что поскольку массив передается как указатель на его начало, то размер

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

вместо 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];

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



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

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

&(*ptrx) = ptrx;

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



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

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

#include <stdio.h>

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; }



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

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

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

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

*a++;

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

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

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

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

Чему равны

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

(Ответ: 'x', '\0', 'd' )

Можно ли написать a++ ? То же про b++ ? Можно ли написать b=a ? a=b ? (нет, да, да, нет)

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

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) }

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

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

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[]=...

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

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

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



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

static int mas[30][100];

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




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



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

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

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

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

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

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

    c = a[x][y];

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

    c = a[y];

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

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

    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); }

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

    #include <stdio.h>

    #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' },



    Массив 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++ = '*';

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

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


    Содержание раздела