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

Массивы в c (часть 1)

Массивы — очень полезные сущности, особенно в тех случаях, когда необходимо под одним именем объединить группы переменных одного и того же типа, обращаться к таким переменным можно через целочисленный индекс. Например, всем известная игра крестики нолики может быть представлена в виде массива. Размер поля — 3*3 клетки (левая верхняя ячейка может быть нулевым элементом, а правая нижняя клетка 8-м элементом массива). На самом деле, массивы — это самый эффективный способ для хранения большого количества однотипной информации под одним и тем же названием. Массивы могут иметь абсолютно любой тип данных, включая структуры и классы.

Массив можно представить как набор ячеек:

1

[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ]

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

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

1

int instanceArray[10]; // мы объявили массив

Мы только что объявили целочисленный массив размером 10 ячеек (мест, в которых значения массива хранятся). Чтобы обратиться к конкретному элементу массива, нужно просто указать имя массива и в квадратных скобках — порядковый номер. Единственно, вам следует знать, что на самом деле первый элемент массива имеет индекс — 0, тогда индекс последнего элемента равен размеру массива минус один. Например, индексы для массива из 10 элементов будут соответствовать этому диапазону: от 0 до 9. Будьте осторожны, никогда не выходите за пределы массива, это может привести к непредсказуемым ошибкам.

Но как вы можете применить только что полученные знания? Допустим, вам нужно сохранить строку, но язык Cи не имеет встроенный типа данных для строк, в таком случае мы можем воспользоваться массивом. Например:

1

char string[10];

Этот код позволит нам объявить массив символов из 10 элементов. И мы сможем сохранить в этот массив данные, которые введет пользователь. Первый входной символ сохраниться в нулевую ячейку, второй символ — в 1-ю ячейку и т. д. Таким образом работать со строками — сравнительно легко, потому что это позволяет работать со строками любой длинны и вся строка хранится в одной переменной. А теперь представьте ситуацию, когда пришлось бы хранить строку не в массиве, а в десятках переменных — это не очень удобно. Кроме того, с массивами очень легко работать в цикле, поочереди перебирая каждый элемент массива:

1

2

3

4

5

6

7

8

9

10

11

char string[10]; // символьный массив на 10 элементов

 

scanf( "%s", string ); // использование scanf для ввода строки - это очень хороший способ

int ct = 0;

for ( ct = 0; ct < 10; ++ct )

{

    if ( string[ct] == 'a' )

    {

        printf( "Вы ввели символ a!\n" );

    }

}

Давайте посмотрим, что в этой программе происходит. Функция scanf() считывает вводимую пользователем строку. Первый её параметр — %s говорит о том, что необходимо считать строку. Во втором параметре мы не используем амперсанд, так как второй параметр это указатель на первый элемент массива string.  То есть имя массива — это указатель на первый элемент массива, запомните это. Так что мы просто передаем массив непосредственно в scanf() без использования амперсанда и это работает прекрасно.

Кроме того, обратите внимание, что для доступа к элементу массива, мы просто используем квадратные скобки, внутри которых ставим значение, которое нас интересует. В данном случае, мы перебираем элементы от 0 до 9 и проверяем каждый элемент на равенство символу — a. Обратите внимание, что в данном примере есть один не маловажный момент, если пользователь введет строку размером — более 10 символов, то вся строка не поместится в массив и часть строки будет отброшена. Но вы пока на это не обращайте внимание, более подробно работу строк мы рассмотрим в следующем уроке.

Нарушение границ массива

1

2

3

4

int array[10];

//код...

for(int ix = 1; ix <= 10; ix++)

  cout << array[ix];

«Почему вывод программы содержит неправильные значения?»

Элементы массива индексируются, начиная с 0, а значит, общая длинна на 1 меньше. Например, если у вас есть десять элементов массива, первый элемент находится в положении нуля, а последний элемент в позиции 9.

1

2

3

4

int array[10];

//код...

for(int ix = 0; ix < 10; ix++)

  cout << array[ix];

В этом коде границы массива уже не нарушаются.

#

Массивы в C (часть 2): многомерные массивы

До этого момента, мы рассматривали только одномерные массивы, то есть, к элементу массива мы обращались через один индекс. Однако, массивы могут быть и двумерными и трехмерными и, даже, n-мерными. Многомерные массивы — это массивы, у которых есть более одного индекса. Вместо одной строки элементов, многомерные массивы можно рассматривать как совокупность элементов, которые распределены по двум или более измерениям. Вот так, например, можно визуализировать двумерный массив:

1

2

3

[][][][][]

[][][][][]

[][][][][]

В этом примере изображен двумерный массив размером 3*5, 3 — строки и 5 столбцов. Объявление двумерного массива почти ничем не отличается от объявления одномерного, за исключением того, что при объявлении двумерного массива, нужно указывать размер каждого измерения в квадратных скобочках. Например, давайте объявим двумерный массив размером 8*8, это размер поля для стандартных шашек — 8 строк и 8 столбцов:

1

int checkers[8][8]; // двумерный массив

То есть, двумерный массив хорошо подходит для хранения информации на шашечном поле. Также двумерный массив можно легко использовать для хранения информации о любой другой игре — шахматы, крестики нолики, сапер и т. д. Чтобы получить доступ к любому элементу такого массива, нужно воспользоваться двумя значениями — индексами, первый индекс — это номер строки, а второй — номер столбца. Все выше сказанное относится и к n-мерным массивам. Хотя, уже 4-х мерные массивы сложновато визуализировать. Присваивать значения элементам массива очень просто, вот пример:

1

2

// присваиваем первому элементу массива значение - 5

myArray[0] = 5;

Пример присваивания значения элемента двумерного массива:

1

2

// присваиваем первому элементу массива значение - 5

myArray[1][3] = 10;

В этом примере мы присвоили значение 10 элементу двумерного массива myArray, который находится во второй строке и в 4-м столбце. Визуально это выглядит так:

1

2

3

[__][__][__][__][__]

[__][__][__][10][__]

[__][__][__][__][__]

Как видите, все просто, главное помните, что нумерация строк и столбцов всегда начинается с 0. То есть, я еще раз хочу вам напомнить, что вы никогда не должны пытаться записать данные после последнего элемента массива, например, когда у вас есть массив размером — 10 элементов и вы пытаетесь присвоить значение элементу с индексом [10]. Память для массива была выделена только для десяти элементов, (индексы от 0 до 9), поэтому элемента с индексом 10 просто не существует. В таком случае, запись в оперативной памяти может привести к непредсказуемым последствиям — например, вы можете в конечном итоге испортить работу параллельно запущенной программы. Однако, как правило, операционная система не позволит такого рода безрассудное поведение и приведет к краху программы, если та попытается получить доступ к нераспределенной памяти.

Давайте рассмотрим практический пример использования массивов в программах:

#include <stdio.h>

 

int main()

{

  int i, j;

  int myArray[8][8]; // объявляем массив размером 8*8 элементов

 

  for ( i = 0; i < 8; i++ ) {

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

      myArray[i][j] = i * j; // каждому элементу присваиваем значение произведения текущих индексов элемента массива

  }

 

  printf( "Вот такой массив у нас получился:\n" );

  for ( i = 0; i < 8; i++ ) {

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

    {

        printf( "[%d][%d]=%d ", i, j, myArray[i][j] );

    }

    printf( "\n" );

  }

  getchar();

}

Сразу смотрим результат работы программы:

CppStudio.com

Вот такой массив у нас получился:

[0][0]=0 [0][1]=0 [0][2]=0 [0][3]=0 [0][4]=0 [0][5]=0 [0][6]=0 [0][7]=0

[1][0]=0 [1][1]=1 [1][2]=2 [1][3]=3 [1][4]=4 [1][5]=5 [1][6]=6 [1][7]=7

[2][0]=0 [2][1]=2 [2][2]=4 [2][3]=6 [2][4]=8 [2][5]=10 [2][6]=12 [2][7]=14

[3][0]=0 [3][1]=3 [3][2]=6 [3][3]=9 [3][4]=12 [3][5]=15 [3][6]=18 [3][7]=21

[4][0]=0 [4][1]=4 [4][2]=8 [4][3]=12 [4][4]=16 [4][5]=20 [4][6]=24 [4][7]=28

[5][0]=0 [5][1]=5 [5][2]=10 [5][3]=15 [5][4]=20 [5][5]=25 [5][6]=30 [5][7]=35

[6][0]=0 [6][1]=6 [6][2]=12 [6][3]=18 [6][4]=24 [6][5]=30 [6][6]=36 [6][7]=42

[7][0]=0 [7][1]=7 [7][2]=14 [7][3]=21 [7][4]=28 [7][5]=35 [7][6]=42 [7][7]=49

В этом примере, мы сначала заполняем двумерный массив произведением его индексов, строки 8 — 11. А потом выводим на экран его содержимое, строки 13 — 20.

Если вы хотите объявить указатель на массив, то вы не должны использовать операцию взятия адреса — &, вот пример:

1 char *ptrArray;

2 char myString[10];

3 ptrArray = myString;  // указателю присваиваем адрес первого элемента массива myString без использования &

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

1

2

3

int *ptrNumber;

int number;

ptrNumber = &number; // обязательно используем оператор - &

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

#

Си-строки

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

Что такое строки?

Отметим, что наряду со строками в стиле С, которые, по сути, являются простыми массивами, есть также строковые литералы, такие как этот "literal". В действительности, что строки, что литералы — это просто наборы символов, расположенных рядом в памяти компьютера. Но между массивами и литералами все таки есть разница, литералы нельзя изменять и строки — можно.

Любая функция, которая принимает строку в стиле С, также может принимать в качестве параметра — литерал. В си также есть некоторые сущности, которые могут выглядеть как строки, хотя, на самом деле, они таковыми не являются. Я сейчас говорю о символах, они заключены в одинарные кавычки, вот пример — 'а', как видите, это не строка. Символ можно, в определенном месте, присвоить строке, но символы не могут быть обработаны в виде строки. Если вы помните, массивы работают как указатели, поэтому, если вы передаете один символ в строку, это будет считаться ошибкой.

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

1

"Это статическая строка"

Вы еще не забыли про специфику строк, которая упоминалась немного выше? Так вот, Си-строки всегда должны завершаться нулевым символом, буквально — '\0'. Поэтому, чтобы объявить строку, состоящую из 49 букв, необходимо зарезервировать дополнительную ячейку под нулевой символ:

1

char myString[50];

ак видно из примера, длинна массива — 50 символов, 49 из которых займет строка и один, последний займет нулевой символ. Важно помнить, что в конце си-строк всегда должен быть нуль-символ, точно так же как и в конце каждого предложения есть точка. Хотя нуль символ не отображается при выводе строки, он все-равно занимает место в памяти. Поэтому, технически, в массиве из пятидесяти элементов вы смогли бы сохранить только 49 букв, потому что, последний символ нужен для завершения строки. Кроме того, указатели также могут быть использованы в качестве строки. Если вы читали статью про выделение памяти в Си, вы можете сделать нечто подобное:

1

2

char *myString; // указатель типа char

myString = malloc( sizeof(*myString) * 64 ); // выделение памяти

В этом примере мы выделили 64 ячейки в памяти для массива myString. Для высвобождения памяти воспользуйтесь функцией free().

1

free( myString );

Использование строк

Строки полезно использовать тогда, когда вам необходимо выполнять различные операции с текстовой информацией. Например, если вы хотите, чтобы пользователь вводил имя в программу, вы должны использовать строку. Использование функции scanf() для ввода строки — работает, но это может привести к переполнению буфера. Ведь входная строка может оказаться больше, чем размер строки-буфера. Есть несколько способов для решения этой проблемы, но самый простой способ — это использовать fgets() функцию, которая объявлена в заголовочном файле <stdio.h>.

Когда fgets() считывает входные данные от пользователя, она будет читать все символы, кроме последнего. После этого в конец считанной строки, fgets() поместит нулевой терминатор. Функция fgets() будет cчитывать символы до тех пор, пока пользователь не нажмет Enter. Давайте посмотрим пример использования fgets():

1

2

3

4

5

6

7

8

9

10

11

12

13

14

#include <stdio.h>

 

int main()

{

    char myString[100]; // длинная строка

 

    printf( "Введите длинную строку: " );

 

    fgets( myString, 100, stdin ); // считываем из потока ввода строку

 

    printf( "Вы ввели следующую строку: %s", myString );

 

    getchar();

}

Первым параметром для fgets() является строка, второй параметр — размер строки и третий параметр — это указатель на входной поток данных.

Результат работы программы:

CppStudio.com

Введите длинную строку: Судьба оставляет свой отпечаток

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

Для закрытия данного окна нажмите <ВВОД>...

Как видите, из вывода программы, во входную строку попал символ новой строки — '\n'. Так случилось из-за того, чтоfgets() считала в строку myString нажатие кнопки Enter и завершила работу. Это означает, что вам может понадобиться вручную удалить символ новой строки. Один из способов сделать это, посимвольный перебор. Давайте доработаем программу и удалим символ новой строки:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

#include <stdio.h>

 

int main()

{

    char myString[100]; // длинная строка

 

    printf( "Введите длинную строку: " );

 

    fgets( myString, 100, stdin ); // читываем из потока ввода строку

 

    int i;

    for ( i = 0; i < 100; i++ )

    {

        if ( myString[i] == '\n' )

        {

            myString[i] = '\0';

            break;

        }

    }

 

    printf( "Вы ввели следующую строку: %s", myString );

 

    getchar();

}

Обратите внимание, что если входная строка содержит меньше 100 символов, то в строку попадет и символ новой строки. Поэтому мы можем удалить этот символ, используя простой перебор. В программу мы добавили цикл, в котором перебираем символы  строки, строки 12-19. И когда нам встречается символ новой строки, мы его заменяем нулевым символом, строка 16. Результат работы программы:

CppStudio.com

Введите длинную строку: Судьба оставляет свой отпечаток

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

Для закрытия данного окна нажмите <ВВОД>...

На этом пока все. В следующей статье я расскажу вам о специальных функциях для работы со строками.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]