Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
153
Добавлен:
11.05.2015
Размер:
328.83 Кб
Скачать

4.3. Двумерные массивы

Язык С допускает многомерные массивы, простейшими из которых являются двумерные массивы. Можно сказать, что двумерный массив – это массив одномерных массивов.

Двумерный массив int a[3][4] можно представить в виде табл. 4.1.

 

 

 

Таблица 4.1

 

Двумерный массив int a[3][4]

 

 

 

 

 

 

 

a[0][0]

a[0][1]

a[0][2]

a[0][3]

 

 

 

 

 

a[1][0]

a[1][1]

a[1][2]

a[1][3]

 

 

 

 

 

a[2][0]

a[2][1]

a[2][2]

a[2][3]

 

 

 

 

 

Здесь первый индекс – номер строки, второй номер столбца. Количество байт памяти, необходимое для хранения двумерного массива в памяти определяется как

<размер типа данных>*<число строк>*<число столбцов>.

В памяти компьютера двумерный массив располагается по строкам. Память для массивов, которые определены как глобальные, отводится в процессе компиляции и сохраняется на все время выполнения программы.

Часто двумерные массивы используются для работы с числовыми и

ссимвольными таблицами(массивы строк). Рассмотрим пример:

#include < stdio.h >

#include < string.h >

/* Пример 21 */ main()

{

char text[5][20]; strcpy(text[0],”Turbo Basic”); strcpy(text[1],”Turbo Pascal”); strcpy(text[2],”Borland C++”); strcpy(text[3],”Turbo Prolog”); strcpy(text[4],”Turbo Fortran”);

}

В данной прграмме заполняется массив text[][], причем, в функции strcpy при заполнении массива, используется только первый индекс. Заполнение text[][] иллюстрируется табл. 4.2.

41

Таблица 4.2

Расположение массива text[][] в памяти компьютера

T

u

r

b

o

 

B

a

s

i

c

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T

u

r

b

o

 

P

a

s

c

a

l

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

B

o

r

l

a

n

d

 

C

+

+

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T

u

r

b

o

 

P

r

o

l

o

g

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T

u

r

b

o

 

F

o

r

t

r

a

n

\0

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

4.4. Инициализация массивов

При программировании бывает важным уметь инициализировать массивы, т. е. присваивать элементам массива некоторые начальные значения. Самый простой способ инициализации – указать список инициализаторов в фигурных скобках при объявлении массива, например:

float ff[5]={1.4, 2.5, 3.6, 12.8, 0.9}; int z[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};

Многомерные массивы можно инициализировать, рассматривая их как массивы массивов:

char str z[3][4]={ {1,2,3,4},{5,6,7,8},{9,10,11,12}};

Количество инициализаторов может быть меньше, чем количество элементов массива. В этом случае оставшиеся элементы массива считаются неопределенными.

Символьные массивы могут инициализироваться как обычный массив: char str[12]={‘a’,’b’,’c’,’d’};

а могут – как строка символов:

char str[12]=“abcd”;

Отличие состоит в том, что во втором случае будет добавлен автоматически нулевой байт.

Допускается объявление и инициализация массива без явного указания его размера:

char str[]=”abcd”;

В этом случае компилятор сам определит необходимое количество элементов массива, включая нулевой байт. Таким образом можно объявлять массивы любого типа:

int a[]={10, 20, 30, 40, 50};

42

Но при объявлении многомерных массивов с неизвестным количеством элементов, можно не указывать размер только в самых левых квадратных скобках:

int a[][3]={1, 2, 3 5, 6, 7

8, 9, 10};

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

# include < stdio.h > /* Пример 22 */ main()

{

int arr[10]={9, 12, 43, 2, 4, 78, 15, 34, 11, 27}; int i,j,tmp;

printf(“ Неотсортированный массив: ”); for ( i=0; i<10; i++) printf(“%d “, arr[i]); printf(“\n”);

for ( i=0; i<8; i++) for ( j=i+1; j<9; j++) if( arr[i] < arr[j] )

{

tmp=arr[i];

arr[i]=arr[j];

arr[j]=tmp;

}

printf(“ Отсортированный массив: ”); for ( i=0; i<10; i++) printf(“%d “, arr[i]); printf(“\n”);

}

4.5. Указатели, объявление указателей, операции над указателями

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

Указатель – это переменная, которая предназначена для хранения и использования в программе адреса некоторого объекта. Здесь имеется в

43

виду адрес в памяти компьютера. Адрес представляет собой простое целое число, но его нельзя трактовать как переменную или константу целого типа. Если переменная по смыслу является указателем, то она должна быть соответствующим образом объявлена. Форма объявления указателя следующая:

Тип *<имя переменной>;

В этом объявлении тип – некоторый допустимый для языка С тип данных, на который указывает указатель. Знак * – означает, что следующая за ним переменная является указателем. Например:

char *ch; int *temp, i, *j;

float *pf, f;

Здесь объявлены указатели ch,temp,j,pf, переменная i типа int и переменная f типа float.

С указателями связаны две специальные операции & и *. Обе эти операции являются унарными, т. е. имеют один операнд, перед которым они ставятся. Операция & соответствует по смыслу операции взятия (определения) адреса. Операция * является по смыслу операцией взятия (определения) значения по указанному адресу. Данные операции нельзя спутать с соответсвующими им по написанию бинарными операциям – поразрядным AND и операцией умножения, так как они являются унарными, что всегда видно из контекста программы.

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

# include < stdio.h > /* Пример 23 */ main

{

float x=12.3, y; float *p; p=&x;

y=*p;

printf(“ x = %f y = %f”,x,y);

44

*p++; /* Увеличиваем на 1 значение, взятое по указателю p */ printf(“ x = %f y = %f”,x,y);

y=1+*p*y; /* Добавляем 1 к произведению значения взятого по указателю p на y */

printf(“ x = %f y = %f”,x,y);

}

К указателям можно применять операцию присваивания, если они являются указателями одного типа. Например:

# include < stdio.h > /* Пример 24 */ main

{

int x=12; int p, g; p=&x; g=p;

printf(“%p”,p);

printf(“%p”,g); printf(“%d %d”,x,*g);

}

В этом примере приведена спецификация формата %p функции printf(), которая используется для вывода адреса памяти в шестнадцатеричной форме.

Нельзя создать переменную типа void, но можно создать указатель на такой тип. Указателю на void можно присвоить указатель любого другого типа. При обратном присваивании необходимо использовать явное преобразование указателя на void. Например, рассмотрим следующий фрагмент:

void *pv; float f, *pf; pf=&f; pv=pf; pf=(float*)pv;

В языке С допустимо присвоить указателю любой адрес памяти. Однако если объявлен указатель на целое (int p), а по адресу, который присвоен данному указателю, находится переменная типа float (float x),

45

то при компиляции программы будет выдано сообщение об ошибке в строке p=&x . Эту ошибку можно исправить, преобразовав указатель на int к типу указателя на float явным преобразованием типа: p=(int*)&x; , но при этом теряется информация о том, на какой тип указывал исходный указатель.

Над указателями можно производить арифметические операции: сложение и вычитание. Арифметические действия над указателями имеют свои особенности. Рассмотрим программу:

# include < stdio.h > /* Пример 25 */ main

{

int *p; int x=12; p=&x;

printf(“%p %p”,p,++p);

}

При выполнении этой программы увидим, что при операции ++p, значение указателя p увеличиться на 2, а не на 1. Это правильно, так как следующее значение указателя указывает на адрес следующего целого, а не на следующий адрес (целое занимает 2 байта). Таким образом, при каждой операции ++p значение указателя будет увеличиваться на количество байт, занимаемой переменной базового типа указателя.

К указателям можно прибавлять или вычитать некоторое целое. Пусть указатель p имеет значение 4000 и указывает на целое. Тогда в результате выполнения оператора p=p+5; значение указателя станет равным 4010. Общая формула для вычисления значения указателя после выполнения операции p=p+m; будет иметь вид:

<p>=<p>+m*<количество байт базового типа указателя>

Аналогичны правила вычитания целых констант от значения указателя. Кроме того, можно вычитать один указатель из другого. Так, если p1 и p2 – указатели на элементы одного и того же массива, то операция p1-p2 дает такой же результат, как вычитание индексов соответствующих элементов массива.

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

46

Указатели можно сравнивать. Применимы все 6 операций сравнения. Сравнение p<g дает “истинну”, если адрес, находящийся в p, меньше адреса, находящегося в g. Если p и g указывают на элементы одного массива, то индекс элемента, на который указывает p, меньше индекса массива, на который указывает g.

4.6.Связь указателей и массивов

Вязыке С связь между массивами и указателями заключается в том, что в объявленном массиве его имя является указателем на массив, а точнее, на первый элемент объявленного массива. Таким образом, если был объявлен массив int plus[20]; , то plus является указателем на массив, а операторы pl=plus; и pl=&plus[0]; приведут к одному и тому же результату. Для того чтобы получить значение 8-го элемента массива plus можно написать plus[7] или *(pl+7), если ранее в программе выполнился оператор pl=plus;. Результат будет один и тот же. Преимущество второго варианта заключается в том, что арифметические операции над указателями выполняются быстрее, чем действия с элементами массива.

Многие программы связаны с обработкой текстов, которые можно представлять как массивы строк. Строка – это массив символов. Рассмотрим примеры двух программ, выполняющих схожие действия:

#include < stdio.h >

#include < ctype.h >

/* Пример 26 */ main()

{

char str[]=”String From Letters in Different Registers”; int i;

printf(“Строка Будет Напечатана Заглавными Буквами”); while ( str[i] )

printf(“%c”, toupper(str[i++]));

}

#include < stdio.h >

#include < ctype.h > main()

{

char str[]=”String From Letters in Different Registers”;

47

char *p; p=str;

printf(“Строка будет напечатана строчными буквами”); while ( str[i] )

printf(“%c”, tolower(*p++));

}

Если в этих примерах заменить строку на английском языке на строку, набранную русскими буквами, то никакого преобразования букв в строчные, или наоборот, в прописные не произойдет. Это связано с тем, что стандартные функции toupper() и tolower() анализируют значения аргумента и возвращают то же самое значение, если он не является соответственно строчной или прописной буквой латинского алфавита. Если же аргумент является строчной буквой латинского алфавита, то значением функции toupper() будет соответствующая прописная буква (точнее, код этой буквы). Функция tolower() изменяет код только прописных букв латинского алфавита. Прототипы этих функций находятся в файле ctype.h.

4.7. Массивы указателей

Указатели, как и переменные любого другого типа, могут объединяться в массивы. Объявление массива указателей на 12 чисел имеет вид int *x[11]; . Каждому из элементов массива можно присвоить адрес; например, пятому элементу этого массива присвоим адрес целой и ранее объявленной переменной y: x[4]=&y;. Если затем необходимо найти значение переменной y, то это можно сделать, выполнив операцию *x[4]. Рассмотрим пример использования массива указателей:

#include < stdio.h >

#include < string.h >

#include < stlib.h >

#include < conio.h > /* Пример 27 */ main()

{

char *ext[]={“exe”,”com”,”dat”,”c”,”pas”,”cpp”}; char ch, sl[80];

for( ; ; )

{do

48

{ printf(“ Файлы с расширением:\n”); printf(“1. exe\n”);

printf(“2. com\n”); printf(“3. dat\n”); printf(“4. c\n”); printf(“5. pas\n”); printf(“6. cpp\n”); printf(“7. Exit\n”); printf(“ Ваш выбор: \n”); ch=getche();

printf(“\n”);

}

while ((ch<’1’)||(ch>’7’)); if ( ch == 7 ) break; strcpy(sl,”dir *.”); strcat(sl,ext[ch-49]; system(sl);

}

}

Данная программа формирует командную строку с учетом пожеланий пользователя и затем выполняет ее, используя библиотечную функцию system(). Данная функция выполняет указанную в командной строке команду (dir – вывод справочника файлов). Расширение для имен выводимых файлов задается пользователем. Константа 49 условно состоит из двух слагаемых – 48 и 1. 48это символьный код нуля. Поэтому, если пользователь программы хочет вывести файлы с расширением pas , он указывает символьную пятерку, которой соответствует код – 53. Разность 53-49 определяет индекс пятого элемента в массиве ext (первый элемент массива в С имеет нулевой индекс).

Часто массив указателей используется, если надо иметь ссылки на стандартный набор строк. Например, для хранения сообщений о возможных ошибках удобно сделать так:

char *errors[]={“Cannot open file”, “Cannot close file”, “Allocation error”, “System error” };

49

При таком объявлении строчные константы будут занесены в раздел констант в памяти, массив указателей будет состоять из четырех элементов, под которые будет выделена память, и эти элементы будут инициированы адресами, указывающими на начало строчных констант.

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

char *p;

p=”Hello, World !”;

В языке С возможна ситуация, когда указатель указывает на указатель. В этом случае описание имеет следующий вид:

int **point;

Здесь point имеет тип указатель на указатель int. Соответственно, чтобы получить целочисленное значение переменной, на которую указывает point, надо в выражении использовать **point. Рассмотрим пример:

# include < stdio.h > /* Пример 28 */ main()

{

int i; int *pi;

int **ppi; i=12; pi=&i; ppi=π

printf(“i = %d pi = %p ppi = %p **ppi = %d\n”,i,pi,ppi,**ppi);

}

После того, как указатель был объявлен, но до того, как ему было присвоено значение, указатель содержит неопределенное значение. Попытка использовать такое значение может вызвать ошибку при выполнении программы, и даже, нарушить работу операционной системы. Принято считать, что указатель с неопределенным значением должен иметь значение null, но это не делает такой указатель “безопасным”. С другой стороны нулевой указатель иногда удобно использовать как признак окончания некоторого массива указателей или списка, поэтому он часто фигурирует в операциях сравнения.

50