Объявление и инициализация массивов

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

Массивом называется набор данных одного и того же типа, собранных под одним именем. Каждый элемент массива определяется именем массива и порядковым номером элемента, который называется индексом. Индекс всегда является целым числом.

Основная форма объявления массива в программе:

Тип < имя массива > [размер 1] [размер 2]….[размер n];

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

Тип <имя массива> [размер];

Тип – базовый тип элементов (int, float, char).

Размер – количество элементов одномерного массива.

В языке Си индекс всегда начинается с 0. Первый элемент - массива всегда имеет индекс 0. Например, если мы объявили массив int a [100], это значит массив содержит 100 элементов – от a [0] до a [99]. Для одномерного массива легко подсчитать, сколько байт в памяти будет занимать этот массив.

N Килобайт = < размер базового типа > * < количество элементов >.

Язык Си допускает двумерные массивы. Их можно назвать как: массив одномерных массивов. Двумерный массив int a[3][4] можно представить в виде таблички:

  Hoмер столбца – второй индекс (j)
Номер строки – первый индекс (i) 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]

В памяти ЭВМ массив располагается непрерывно по строкам, т. е.

a [0] [0], a [0] [1], ……. a [0] [3], a [1] [0], a [1] [1], …..a [2] [3].

Следует помнить, что память для всех массивов, которые определены как глобальные, отводится во время компиляции, и сохраняется всё время, пока работает программа. Часто двумерные массивы используются для работы с таблицами, содержащими текст.

T U R B O   B A S I C \ 0          
T U R B O   C + + \ 0              

Инициализацию массивов можно производить разными способами.

Первый способ.

float arr [6] = {1.1, 2.2, 3.3, 4.0, 5.0, 6}; // одномерный массив

int a [3] [5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

Второй способ.

a[0] [0] = 1; a [0] [1] = 2; a [0] [2] = 3; a [0] [3] = 4

a [0] [4] = 5; a [0] [5] = 6; a [0] [6] = 7; a [0] [7] = 8 и т. д.

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

Инициализации:

int a [3] [5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; и

int a [3] [5] = {{1, 2, 3, 4, 5}, {6, 7, 8, 9, 10}, {11, 12, 13, 14, 15}};

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

В тоже время инициализации:

int a [3] [5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 20, 11}; и

int a [3] [5] = {{1, 2, 3}, {4, 5, 6, 7, 8}, {9, 10, 11}};

различны.

Соответствующие массивы будут заполнены следующим образом:

В первом случае:

       

Во втором случае:

   
   

В пустых клетках значения не определяются.

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

char str [15] = {‘B’, ‘o’, ‘r’, ‘l’, ‘a’, ‘n’, ‘d’, ‘ ‘, ‘C’, ‘+’, ‘+’};

а могут как строка:

сhаr str [15] = ‘Borland C + +’;

Можно указывать массив без указания размера:

int mas [ ] = {1, 2, 3, 4, 5, 1, 2};

char str [ ] = ”Это объявление и инициализация символов” ;

компилятор сам определит количество элементов массива. Символьные массивы в языке Си занимают особое место. Во многих языках есть специальный тип – строка символов. В языке Си этого нет, и работа со строками реализована как работа с одномерным массивом.

Строка – это одномерный массив типа char, заканчивающийся нулевым байтом. Нулевой байт – это байт, каждый бит которого равен нулю.

Например: char str [11];

Предполагается, что строка содержит 10 символов, а последний байт зарезервирован под нулевой байт.

Есть два способа ввести строку с клавиатуры:

1) С помощью функции scanf() со спецификатором %s. Она вводит символы до первого пробельного символа.

2) С помощью библиотечной функции gefs(), которая вводит строки с

пробелами.

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

int arr[][3] = {1, 2, 3,

5, 6, 7,

8, 9, 0};

Пример 4.1.: Все числа из каждой строки массива a умножить на числа соответствующего столбца массива b и результат сложения записать в массив.

/ * Перемножение матриц */

# include <stdio.h>

# include <conio.h>

void main (void)

{

/* Инициализация двумерных массивов */

int a [3] [4] = {

{1, 2, 3, 4},

{5, 6, 7, 8},

{9, 10, 11, 12}

};

int b [3] [2] = {

{1, 2},

{3, 4},

{5, 6},

{7, 8}

};

int c [3][2], i, j, k; // массив результатов и параметры цикла

clrscr();

for ( i = 0; i = 3; i + +) // проход по вертикали (строкам)

{

for ( j = 0; j < 3; j + +) // проход по горизонтали (рядам)

{

с [i] [j] = 0; // обнуление

for (k = 0; k<4; k + +)

c [i] [j] + = a [i] [k] * b [k] [j];

}

}

for (j = 0; j < 3; j + +)

{

for (j = 0; j < 2; j + +)

printf (“%6d”, printf (“\ n”);

}

}

// Результат прогона: Ручное вычисление

// 50 60 c [0, 0] = 1*1 + 2*3 + 3*5 + 4*7 = 50

// 114 140 c [0, 1] = 1*2 + 2*4 + 3*6 + 4*8 = 60

// 178 220 c [1, 0] = 5*1 + 6*3 + 7*5 + 8*7 =114

c [1, 1] = 5*2 + 6*4 + 7*6 + 8*8 =140

c [2, 0] = 9*1 + 10*3 + 11*2 + 12*7 = 178

c [2, 1] = 9*2 + 10*4 + 11*6 + 12*8 = 220

Указатели

Понимание и правильное использование указателей очень важно для создания очень хороших программ на языке Си. Указатели необходимы для успешного использования функций и распределения памяти. Кроме того, многие конструкции языка Borland C + + требуют применения указателей. Однако с указателями следует обращаться очень осторожно. При использовании неинициализированного указателя может быть зависание компьютера и ошибки.

Объявление указателей. Указатель – это переменная, которая содержит адрес объекта, т.е. адрес ячейки памяти. Вообще то это просто целое число. Но нельзя трактовать указатель как переменную или константу целого типа. Если переменная будет указателем, то она должна быть соответствующим образом объявлена.

Указатель объявляется следующим образом:

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

Например:

char *ch ;

int * temp, i, *j ;

float * pf, f ;

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

Операции над указателями. С указателями связаны две специальные операции & и *. Обе эти операции являются унарными, т. е. они имеют один операнд, перед которыми они ставятся.

Операция & соответствует операции «взять адрес».

Операция * соответствует словам «значение, расположенное по указанному адресу».

Особенность языка Си состоит в том, что знак * соответствует двум операциям, не имеющим друг к другу никакого отношения: арифметическое умножение и операции «взять значение». В тоже время спутать их в контексте нельзя, т.к. одна операция унарная содержит один операнд, а вторая – умножение, бинарная (содержит два операнда). Унарные операции & и * имеют наивысший приоритет, наравне с унарным минусом.

В указателе очень важным является базовый тип, т.к. он определяет, сколько байтов занимает переменная указатель. Если int – 2 байта, char – один байт и т.д. Простейшие действия с указателями продемонстрируем на следующей программе.

Пример 4.2.

# include <stdio.h>

main ()

{

float x = 10.1, y ;

float * pf ;

pf = &x ;

y = *pf ;

printf(“x = %f y= %f”, x, y); // Результат: x=10.1; y=10.1; pf=FFF6

*pf ++;

printf (“x = %f y = %f”, x, y); // Результат: x=10.1; y=10.1 ; pf=FFF2

y =1+ *pf * y;

printf (“x = %f y = %f”, x, y); // Результат: x=10.1; y=1 ; pf=FFF6

return 0;

}

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

Пример 4.3.

# include <stdio.h>

main ()

{

int x = 0 ;

int *p, *g ;

p = &x ;

g = p ;

printf(“%p”, p); /* печать содержимого p */

printf(“%p”, g ); /* печать содержимого g */

printf(“%d %d”, x., *g);/*печать величины х и величины по адресу g */

}

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

В языке Си указателю допустимо присвоить любой адрес памяти. Однако, если объявлен указатель на целое int *pi; а по адресу, который присвоен данному указателю, находится переменная типа float, то при компиляции будет выдано сообщение об ошибке в строке

p = &x;

Эту ошибку можно исправить, преобразовав указатель на int к типу указателя на float явным преобразованием типа:

p = (int*)&x;

но при этом теряется информация о том, на какой тип указывал исходный указатель.

Пример неправильной программы:

main ()

{

float x =10.1, y;

int *p;

p = &x /* потом заменим на p=(int*)&x */

y = *p;

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

}

В результате работы этой программы не будет получен тот ответ, который ожидался. Переменной y не будет присвоено значение переменной x, т.к. будут обрабатываться не 4 байта, как положено для переменной типа float, а только 2 байта, т.к. базовый тип указателя - int.

Как и над другими типами переменных, над указателями можно производить арифметические операции сложения и вычитания, а также операции (++) и (--). Указатели можно сравнивать. Применимы шесть операций:

<, >, < =, > =, =, = =, ! =

Пример 4.4

main()

{

int *p;

int x;

p = &x;

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

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

}

После выполнения этой программы, мы увидим, что при операции ++p значение указателя изменится не на 1, а на 2. И это правильно, т.к. значение

указателя должно указывать не на следующий адрес памяти, а на адрес следующего целого. А целое, как мы помним, занимает 2 байта. Если бы базовой тип указателя был не int, а double, то были бы напечатаны адреса, отличающиеся на 8. Именно столько байт занимает переменная типа double.

К указателям можно прибавлять некоторое целое или вычитать. Пусть указатель p имеет значение 2000 и указывает на целое, тогда в результате выполнения оператора p=p+3 его значение будет 2006. Если указатель p1=2000 был бы указателем на float, то после применения оператора p1= p1+10 значение p1 было бы 2040.

Общая формула для вычисления указателя по формуле p= p+ n будет иметь вид:

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

Сравнение p < g указывает, что адрес, находящийся в p, меньше адреса, находящегося в g.

Наши рекомендации