Скачиваний:
2
Добавлен:
03.01.2024
Размер:
567.33 Кб
Скачать
Брайэн Керниган

СПбГУТ им. проф. М.А. Бонч–Бруевича Кафедра программной инженерии и вычислительной техники (ПИ и ВТ)

ПРОГРАММИРОВАНИЕ

Единственный способ изучать новый язык программирования – писать на нем программы.

Лекция 9: Одномерные массивы

1.Понятие массива. Одномерные массивы

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

3.Строки как одномерные массивы данных типа char

4.Примеры обработки массивов

Санкт–Петербург, 2021г.

Данные − это формы представления информации, с которыми имеют дело информационные системы и их пользователи

Данные − это поддающееся многократной интерпретации представление информации в формализованном виде, пригодном для передачи, связи или обработки

Данные − это необработанные факты и цифры

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

Данные не являются конкретными, информация должна быть достаточно определённой для того, чтобы иметь смысл

Информация − это обработанные данные, которые приобрели смысл

Информация − это сведения независимо от формы их представления

Введение. Классификация данных по структуре

ДАННЫЕ

КОНСТАНТЫ ПЕРЕМЕННЫЕ

(защита от записи)

ДАННЫЕ

ПРОСТЫЕ

СЛОЖНЫЕ

 

 

 

 

 

 

 

 

 

1 ячейка

 

 

 

 

 

КЛАСС ...

МАССИВ

СТРУКТУРА

несколько (множество) ячеек

2

1. Понятие массива. Одномерные массивы

Введение индексированных переменных в языках

Одномерные массивы

 

 

 

 

программирования позволяет значительно облегчить

 

 

 

 

 

 

 

 

 

тип ID_массива [размер] = {список начальных значений};

реализацию многих сложных алгоритмов, связанных с

 

обработкой массивов однотипных данных.

 

 

 

 

 

 

 

 

 

Тип – базовый тип элементов массива (целый, вещественный,

Например, использование массивов данных позволяет

компактно записывать множество операций с помощью

символьный).

 

 

 

 

циклов.

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

 

 

 

Размер массива вместе с типом его элементов

В языке Си для этой цели используется сложный тип данных

 

массив, представляющий собой упорядоченную конечную

 

 

определяет объем памяти, необходимый для

совокупность элементов одного типа.

 

 

размещения массива, которое выполняется на этапе

 

 

 

компиляции, поэтому размер массива задается только

Число элементов массива называют его размером.

 

Каждый элемент массива определяется идентификатором

 

константой или константным выражением.

массива и своим порядковым номером индексом.

!!! Нельзя задавать массив переменного размера, для

Индекс целое число, по которому производится доступ к

 

этого существует отдельный механизм – динамическое

элементу массива.

 

выделение памяти.

 

 

 

 

Индексов может быть несколько. В этом случае массив

Индексы массивов в языке Си начинаются с 0, т.е. в

называют многомерным, а количество индексов одного

 

массиве а первый элемент: а[0], второй – а[1], … пятый –

 

а[4].

 

 

 

 

элемента массива является его размерностью.

 

 

 

 

 

Описание массива в программе отличается от описания

Список начальных значений используется при

простой переменной наличием после имени квадратных

необходимости инициализировать данные при объявлении,

он может отсутствовать.

 

 

 

 

скобок, в которых задается количество элементов массива.

 

 

 

 

Обращение к элементу массива в программе на языке Си

Например, double a [10]; описание массива из 10

вещественных чисел.

осуществляется в традиционном для многих других языков

стиле – записи операции обращения по индексу [ ]

 

 

 

 

(квадратные скобки)

 

 

 

 

 

 

Пример:

 

 

 

 

 

 

 

a[0]=1;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

a[i]++;

 

 

 

 

 

 

 

 

a[3]=a[i]+a[i+1];

 

 

 

 

 

 

 

 

 

 

 

 

3

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Массивы.

 

 

 

 

 

 

Пример:

 

 

 

 

 

 

 

 

 

 

 

Например, если мы опишем указатель: int *p;

 

 

 

 

 

 

 

 

 

 

 

 

 

 

присваивание p = m; будет не только возможно, но и не потребует

 

Объявление массива целого типа с инициализацией

 

 

 

 

 

 

 

от компилятора никакого преобразования типа: ведь здесь и

 

 

начальных значений:

 

 

 

слева, и справа от присваивания мы видим один и тот же тип int*.

 

 

 

int a[5]={2, 4, 6, 8, 10};

 

 

Больше того, после такого присваивания можно будет обращаться

 

Если в группе {…} список значений короче, то

 

 

 

к элементам массива m через переменную p с помощью всё той

 

 

оставшимся элементам присваивается 0.

 

 

 

же операции индексирования: p[0], p[1], ..., p[19] обозначают

 

 

 

 

 

 

 

 

 

теперь ровно то же самое, что и m[0], m[1], ..., m[19].

 

!!! В языке Си с целью повышения быстродействия

 

 

С другой стороны, поскольку m — это адрес, к нему можно

 

 

 

программы отсутствует механизм контроля выхода за

 

применить операцию разыменования, то есть обратиться к

 

 

 

границы индексов массивов.

 

первому элементу массива m можно не только через

 

 

При необходимости такой механизм должен быть

 

индексирование (m[0]), но и иначе: *m — это абсолютно то же

 

 

 

запрограммирован явно.

 

самое.

 

 

 

Мы видим, что индексирование в Си это операция не над

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

массивами (над массивами вообще нет никаких операций), а над

 

 

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

 

 

 

 

адресами.

 

«в языке Си массивов нет»:

Чтобы понять, что на самом деле представляет собой операция

 

Сразу после такого утверждения мы приведём пример описания

 

индексирования, нужно пояснить ещё один важный момент:

 

 

массива: int m[20];

 

 

в языке Си к типизированному адресному выражению (то

 

Здесь описан массив из 20 элементов типа int, причём эти

 

 

есть к адресному выражению любого типа, кроме void*)

 

 

 

элементы доступны с помощью операции индексирования под

 

 

можно прибавить целое число, при этом адрес изменяется

 

 

номерами (индексами) от 0 до 19.

 

 

на размер элемента того типа, на который указывает

 

Предвидя недоумение студента (ведь только что мы заявили, что

 

 

исходный адрес; например, адрес типа char* при

 

 

 

массивов нет), поясним: элементы массива доступны каждый

 

 

добавлении к нему единицы увеличится на единицу, а адрес

 

 

в отдельности, тогда как к самому массиву как единому

 

 

типа int* в таком же случае — на четыре (ведь int занимает

 

 

 

целому обратиться невозможно.

 

 

4 байта).

 

Практически во всех ситуациях введённое в описании имя m

Таким образом, если в памяти размещён массив элементов

 

 

 

обозначает не сам массив как таковой, а адрес его первого

 

одного типа, добавление единицы к адресу одного из этих

 

 

 

элемента.

 

элементов даст адрес следующего элемента массива.

 

Поскольку элементы массива, в том числе первый из них, в

Скажем больше: выражение a[b], каковы бы ни были эти a и b,

 

 

 

данном случае имеют тип int, имя m представляет собой

 

означает то же самое, что и *(a+b). Осознать всю глубину экзотики

 

 

константу типа int*.

 

позволяет замечание, что от перемены мест слагаемых сумма не

 

 

 

 

 

 

 

 

 

меняется, так что вместо m[17] можно написать 17[m], и

4

 

 

 

 

 

 

 

 

компилятор это примет. Не надо так делать! Иллюстрация!!!

Массивы. Представление в памяти

Массив – упорядоченная последовательность переменных одного типа, имеющая общее имя.

индексы

0

1

 

N-1

 

 

 

 

 

 

 

 

 

 

...

 

 

...

 

...

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

тут лежат элементы массива

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

<имя_типа> <имя_массива>[<количество_элементов>];

<Количество_элементов> - всегда ЦЕЛОЕ и задается константным_выражением.

Пример:

const int N = 10; /* N – именованная константа целого типа int со значением 10 */

int mas[10]; float A[N];

N обязательно определено ранее как константа!

N не может быть переменной! Константное_выражение может быть:

константой, например 10

именованной константой N (ранее была объявлена как const int N = <целое_значение>)

выражением, содержащим константы и именованные константы:

20 * 45

2 * N

Еще один способ объявить именованную константу:

#define <имя_константы> <значение> Пример:

#define N 50

int A [N]; int B [2 * N];

Если написать #define N =50

то ниже в тексте программы везде N будет замещено на =50

Объявление одномерного массива с инициализацией:

Способ 1:

<имя_типа> <имя_массива>[<константное_выражение>] = {значения элементов массива, разделенные ‘,’};

Способ 2:

<имя_типа> <имя_массива>[] = {значения элементов массива, разделенные ‘,’};

Пример:

myMas1[4] = {0, 12, 10, 4};

int

char

myMas2[ ] = {‘c’, ‘h’, ‘e’, ‘c’, ‘k’};

double myMas3[4] = {1, 2}; double myMas4[4] = {0};

double myMas4[ ]; /* Нельзя!!! Необходимо либо указать количество элементов, либо присвоить значения */

5

Массивы. Представление в памяти. Обращение к элементам

Пример:

int A[4] = {0, 12, 10, 4};

 

 

A[1]

 

A[2]

 

A[0]

 

 

 

 

 

 

 

 

 

A[3]

0

1

 

2

3

 

 

 

 

 

 

 

 

 

 

...

0

12

 

10

 

4

...

 

 

 

 

 

 

 

 

 

 

Элементы массива A

Обращение к произвольному элементу массива:

<имя_массива>[<индекс_элемента>]

Замечание:

<индекс_элемента> должен быть только целым числом и должен быть >= 0 и <= <размер_массива>

Представление в памяти:

int A[N] ;

Элементы массива A

0 1 ... N-1

...

A[0]

A[1]

...

A[N-1]

...

 

 

 

 

 

 

 

адрес адрес

 

адрес

 

A[0] A[1]

 

A[N-1]

Каждая ячейка имеет размер, соответствующий типу элементов массива. В данном примере - int

Имя массива – это адрес начала массива, а так же адрес элемента с индексом 0.

Т.е. обращение A и &A[0] – обращение к адресу, по которому начинается массив.

Одновременно работать со всем массивом нельзя, т.е. нельзя сложить два массива A и B вот так: A + B, необходимо все операции с массивами выполнять поэлементно.

6

Одномерные массивы. Промежуточные выводы

Следствия недоступности массива как единого объекта:

Одним из самых заметных (и тяжёлых) следствий является

невозможность присваивания массивов, которой часто не хватает (как в Pascal).

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

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

Ещё один интересный момент: в языке Си определена операция вычитания типизированных адресов, результатом которой является целое число.

Эта операция имеет смысл только при применении к адресам элементов одного массива;

её результат — расстояние между двумя элементами, то есть разность их индексов.

Например, если мы работаем всё с тем же массивом m и присвоили указателю p адрес элемента m[13] (то есть выполнили p = &m[13] или просто p = m + 13), то значением выражения p - m будет число 13.

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

Математическим понятием, которое привело к

появлению в языках программирования понятия «массив», являются матрица и ее частные случаи:

вектор-столбец;

вектор-строка.

Элементы матриц в математике принято обозначать с использованием индексов.

Существенно, что все элементы матриц либо вещественные, либо целые и т. п.

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

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

Например: int a[10];

Определяет: массив из 10 элементов a[0], a[1], ..., a[9]

Всоответствии с синтаксисом Си в языке существуют только одномерные массивы, однако элементами одномерного массива, в свою очередь, могут быть массивы.

Поэтому двумерный массив определяется как массив массивов: float z[13][6];

Таким образом, выше определен массив z из 13 элементов- массивов, каждый из которых, в свою очередь, состоит из 6 элементов типа float.

Массив z определяет двумерный массив, первый индекс которого принимает 13 значений от 0 до 12, второй индекс принимает 6 значений от 0 до 5.

7

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

Указатель — переменная, содержащая адрес объекта.

Указатель не несет информации о содержимом объекта, а содержит сведения о том, где размещен объект.

Каждая переменная в памяти имеет свой адрес - номер первой ячейки, где она расположена, а также свое значение.

Указатель — это тоже переменная, которая размещается в памяти. Она тоже имеет адрес, а ее значение является адресом некоторой другой переменной.

Переменная, объявленная как указатель, занимает 4 байта в

оперативной памяти (в случае 32-битной версии компилятора).

int a, *b; a = 134; b = &a;

Отношение указателей и массивов int A[5] = {1, 2, 3, 4, 5};

Массив хранит адрес, откуда начинаются его элементы A[3] эквивалентно *(A + 3)

Массив указателей их использование и инициализация

Можно создавать массивы указателей.

Для объявления массива целочисленных указателей из десяти элементов следует написать:

int *х[10];

Для присвоения адреса целочисленной переменной var третьему элементу массива следует написать:

х[2] = &var;

Для получения значения var следует написать:

*х[2]

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

Идентификатор одномерного массива – это адрес памяти, начиная с которого он расположен, т.е. адрес его первого элемента. Таким образом, работа с массивами тесно взаимосвязана с применением указателей.

Пусть объявлены одномерный целочисленный массив a из 5

элементов и указатель p на целочисленные переменные:

int a[5]={1, 2, 3, 4, 5}, *p;

ID массива a является константным указателем на его

начало, т.е. а = &a[0] – адрес начала массива. Расположение массива а в оперативной памяти, выделенной компилятором, может выглядеть следующим образом

a[0]

a[1]

a[2]

a[3]

a[4]

– элементы массива;

 

1

2

3

4

5

– значения элементов массива;

 

4000

4002

4004

4006

4008

– символические адреса.

8

 

 

 

 

 

 

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

Указатели и доступ к элементам массивов.

По определению, указатель – это либо объект со

значением «адрес объекта» или «адрес функции», либо

выражение, позволяющее получить адрес объекта или

функции.

 

Рассмотрим фрагмент:

int x,y;

Здесь p - указатель-объект, a &x, &y - указатели-

int *p =&x;

выражения, то есть адреса-константы.

p=&y;

Мы уже знаем, что p - переменная того же типа,

что и значения &x, &y.

Различие между адресом (то есть указателем-выражением) и указателем-объектом заключается в возможности изменять значения указателей-объектов.

Именно поэтому указатели-выражения называют указателями-константами или адресами, а для указателя- объекта используют название указатель-переменная или просто указатель.

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

Это нужно учитывать и помнить при работе с массивами и указателями.

a[0]

a[1]

a[2]

a[3]

a[4]

– элементы массива

1

2

3

4

5

– значения элементов

4000

4002

4004

4006

4008

массива

– символические адреса

Указатель а содержит адрес начала массива.

В нашем примере а равен 4000 (а = 4000).

Если установить указатель р на объект а, т.е. присвоить переменной-указателю адрес первого элемента массива:

р = а;

что эквивалентно выражению: р = &a[0]; то получим, что и р = 4000

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

а[i] *(а+i) *(р+i) р[i], приводящими к одинаковому результату.

Идентификаторы а и р – указатели, очевидно, что выражения а[i] и *(а+i) эквивалентны.

Отсюда следует, что операция обращения к элементу массива по индексу применима и при его именовании переменной- указателем.

Таким образом, для любых указателей можно использовать две эквивалентные формы выражений для доступа к элементам массива: р[i] и *(р+i).

Первая форма удобнее для читаемости текста, вторая – эффективнее по быстродействию программы.

9

3. Строки как одномерные массивы данных типа char

В языке Си отдельного типа данных «строка символов» нет.

Работа со строками реализована путем использования одномерных массивов типа char, т.е.

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

Нулевой байт – это байт, каждый бит которого равен нулю, при этом для нулевого байта определена символьная константа ´\0´ (признак окончания строки, или «нуль- символ»).

Поэтому если строка должна содержать k символов, то в описании массива размер должен быть k+1.

По положению нуль-символа определяется фактическая длина строки. Пример

char s[7]; – означает, что строка может содержать не более шести символов, а последний байт отводится под нуль- символ.

Строку можно инициализировать строковой константой

(строковым литералом), которая представляет собой набор символов, заключенных в двойные кавычки.

Например:

char S[ ] = “Работа со строками”;

Для данной строки выделено и заполнено 19 байт – 18 на символы и 19-й на нуль-символ.

Отсутствие нуль-символа и выход указателя при просмотре строки за ее пределы – распространенная ошибка при работе со строками.

В конце строковой константы явно указывать символ ´\0´ не нужно. Компилятор добавит его автоматически.

Символ ´\0´ нужно использовать явно тогда, когда символьный массив при декларации инициализируется списком начальных значений, например, следующим образом:

char str[10] ={‘V’ , ‘a’, ‘s’, ‘j’ , ‘а’, ‘\0’};

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

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

Процесс копирования строки s1 в строку s2 имеет вид:

char s1[25], s2[25];

for (int i = 0; i <= strlen(s1); i++) s2[i] = s1[i];

Длина строки определяется с помощью стандартной функции strlen, которая вычисляет длину, выполняя поиск нуль- символа.

Большинство действий со строковыми объектами в Си

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

strcpy(s1, ”Earth”); /* Правильно */

char s1[51];

 

/* Не верно! Константный указатель и не может

 

использоваться в левой части операции присваивания: */

 

s1 = ”Earth”;

10

Соседние файлы в папке Лекции