Опубликован: 23.10.2009 | Доступ: свободный | Студентов: 2795 / 93 | Оценка: 4.28 / 4.22 | Длительность: 17:27:00
Специальности: Программист
Лекция 7:

Другие типы данных

< Лекция 6 || Лекция 7: 1234 || Лекция 8 >

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

В разделах 6.2 и 7.1 уже использовалось понятие массива, правда, применительно только к примитивным символьным данным. Но массив, в принципе, может состоять из любых данных, в том числе и числовых, и типов-классов и структур, а также из других массивов. Массивы представляют собой смежные ячейки памяти, начиная с начального адреса массива и до последней ячейки памяти массива. Доступ к каждой ячейке памяти осуществляется по его индексу. С помощью индекса вычисляется адрес нужной ячейки по формуле:

<адрес элемента> = <начальный адрес массива>+<индекс>*<длина типа данных>
Листинг 7.2.
Примечание: формула 7.2 верна для тех языков, нумерация массивов для которых начинается с нулевого элемента. В языках Бейсик, ФОРТРАН и др. нумерация массива начинается с первого элемента, и первый множитель заменяется на (<индекс>-1).

Индекс массива может меняться от 0 до N-1 или от 1 до N, где N - максимальный размер массива. Синтаксис объявления массива следующий:

<тип> <переменная>[<N>]
Листинг 7.3.

для языков Си, С++, Java, где N - максимальное число ячеек в массиве;

DIM <переменная с типом>(<N>)
Листинг 7.4.

для языка Бейсик

[Пример 09]

REM - определяет массив строк A$ из 10 переменных и матрицу вещественных чисел D размером 3х3;

DIM A$(10), D(3,3)

[Пример 10]

/* - определяет массив строк A из 10 переменных и матрицу вещественных чисел d размером 3х3,
а также массив примитивных символов array из десяти символов и указатель на символьный массив, которому можно назначить адрес имеющегося массива. */

string A[10];
double d[3][3];
char *c, array[10];

Индекс массива

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

[Пример 11]

/* Задание типов элементам */

char b, c, str[5];
int i;

...

/* Присвоение элементов: */

c = str[5]; b = str[i];

В приведенном примере символьным переменным b и c присваивается соответственно значение i -ой и третьей ячейки массива символьных данных str.

Замечание. При определении класса операцию индексирования можно переопределять (иначе говоря, перегрузить). О перегрузке операций сказано в книге [51].

Максимальный индекс массива определяется следующими способами:

  1. Явное указание максимальных индексов целыми константами:

    [Пример 12]

    const short MAXNUMBER = 8;
    
    char str[7];
    short nIndexes[MAXNUMBER];
  2. Определение элементов массива по количеству заранее предопределенных для него значений:

    [Пример 13]

    char str[] = "Привет!";
    short nIndexes[] = {5, 4, 6, 8, 7, 3, 1, 2};

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

Указатели

Кроме массивов, для обращения к переменной по ее адресу можно пользоваться указателем на переменную указанного типа. Указатели есть в языках Си, C++ и Java, но отсутствует во многих "старых" языках, таких как ФОРТРАН и Quick Basic.

Синтаксис определения указателя следующий:

<тип> *<имя переменной>
Листинг 7.5.

Замечание. Как уже отмечалось в примере 10, массивы можно определить как указатели. Однако это нужно делать в следующих случаях:

  1. При передаче массива как параметра функции;
  2. Для создания динамических массивов, то есть массивов, размещенных в памяти ЭВМ с помощью функций new, calloc, malloc и других функций выделения памяти. После окончания использования массива память следует освободить функциями delete, free и др.

После определения указателя, например, после объявления:

char *c, a, d='a';
Листинг 7.6.

в переменной c будет храниться адрес ячейки содержащей символ. Но для этого необходимо сначала создать эту ячейку, например, с помощью функции malloc:

c = (char *) malloc( sizeof( char ) );
Листинг 7.7.

или присвоить ей адрес существующей ячейки памяти:

c = &d;
Листинг 7.8.

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

a = *c;
Листинг 7.9.

В этом случае переменной a будет присвоено значение 'a', а после присвоения:

*c = 'b';
Листинг 7.10.

переменной d будет присвоено значение 'b'

Замечание. Не следует присваивать указателю - адресной переменной значение какой-либо другой переменной, отличной от ее адреса. В этом случае генерируется ошибка компилятора:
c = a; (компилятор выдаст ошибку!)
Листинг 7.11.
Замечание. При присвоении указателя необходимо обязательно указывать его тип. В этом (и только в этом) случае компилятор корректно обрабатывает код программы, вычисляет размер ячейки памяти и т.п. Если по логике программы необходимо изменить тип указателя, делайте эту операцию явно, например:

[Пример 14]

/* Описание переменных */

char *c, d='a';
unsigned char *u;

/* Присвоение указателей */

c = &d;
u = (unsigned char *)c;

В результате в переменной u хранится содержимое ячейки d "как есть". Переменная c будет по-прежнему иметь тип char * (указатель на тип char).

В некоторых функциях используется указатель на "пустой" тип: void *. Перед использованием этого указателя необходимо присвоить (переопределить) его на указатель на необходимый тип данных.

[Пример 15]

/* Определение переменных */

char *c;

/* Присвоение указателей: */

c = (char *) malloc( sizeof( char ) );

Чтобы присвоить переменной-указателю адрес выделенной ячейки памяти, необходимо явно изменить тип возвращаемого значения функции malloc (по-умолчанию функция malloc возвращает тип: "void *").

Ссылки

Ссылка - это "разыменованный" указатель. Она обозначается как <тип> &<переменная>. В основном она применяется в параметрах функции. Пусть, например, объявлена функция:

void func( int a, int *b, int &c);
Листинг 7.12.

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

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

Наконец, третий параметр функции сочетает в себе достоинства обоих способов передачи параметров. Так, ссылку, как и переменную, передаваемую "по значению", не надо разыменовывать. Однако, после выхода из функции, новое значение предаваемой "по ссылке" переменной сохраняется. Поэтому вызов функции func, определенное в [7.12], будет следующим:

func( i, &i, i );
Листинг 7.13.

где i - целая переменная - "фактический параметр".

Замечание. Передача данных "по ссылке" является потенциально опасной с точки зрения "безопасности" хранения и изменения данных. Поэтому не злоупотребляйте ее использованием!
< Лекция 6 || Лекция 7: 1234 || Лекция 8 >