Last active
February 11, 2024 18:13
-
-
Save Lerbytech/bb81f76688d747f5374e759bee375e35 to your computer and use it in GitHub Desktop.
Пример введения массива строк и вывода на экран
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <ctype.h> | |
int main() | |
{ | |
// НУЖНЫЕ НАМ ПЕРЕМЕННЫЕ. | |
//Это универсальный набор, минимально необходимый для любой задачи с массивом строк | |
char *STR[10]; // наш массив строк | |
char **s; // указатель на массив. Обязательно двойной, не ошибайтесь | |
char buf[80]; // массив куда заносится введенная с клавиатуры консоль | |
int n = 0; // счетчик числа введённых строк | |
int max_number_of_strings = 10; // указываете сколько максимум строк хотите ввести(опционально) | |
// ПРОЦЕДУРА ВВОДА | |
// если хотите чтобы можно было вводить сколько угодно строк, пока не будет введена нулевая, поправьте условие цикла на: | |
// *gets(buf) != '\0' | |
for (s = STR; n < max_number_of_strings && *gets(buf) != '\0'; n++, s++) { //считываем данные в buf | |
*s = (char*)malloc(strlen(buf) + 1); //выделяем память в массива строк s | |
strcpy(*s, buf); //копируем данные из buf в свежевыделенную память | |
} | |
printf("---------------------\n"); // сделаем разбиение для красоты | |
// ВЫВОД НА ЭКРАН | |
// обратите внимание на условие цикла. Если будете менять кол-во строк в массиве, обязательно изменяйте значение переменной n | |
for (s = STR; s < STR + n; s++) | |
puts(*s); //выводим все подстроки | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Объяснение на пальцах того, как работать с массивом строк.
Логические блоки:
1. Числовые матрицы
Чтобы понять массивы строк вспомним второй блок задач задач про числовые матрицы. Обычно мы считали их квадратными, но я буду предполагать что они могут быть прямоугольными.
ТВ показывала различные варианты объявления матрицы. Рассмотрим их ещё раз.
Проще всего объявить через индексы:
0>
int Mat[10][10];
Такой вариант объявления матрицы даёт нам фиксированную матрицу размером 10 на 10. Изменить его в ходе работы нельзя. В памяти будет выделено
W*H*sizeof(int)
байт. Отметим что матрице блок памяти выделяется последовательно. Очевидно что для большой матрицы вы можете столкнуться с некоторыми проблемами - она может в целом влезать в память, но вот однородного свободного блока для неё не будет ...Зато обращение к элементу производится легко и просто (за что все индексы и любят):
1>
int N = Mat[0][1]; // 0 строка, 1 столбец
Другой вариант объявления матрицы который вы проходили заключался в объявлении массива указателей вот такого вида:
2>
int *Arr[10]; // массив из 10 указателей типа integer
Объявляется массив указателей из 10 элементов. Каждый элемент - указатель, который будет дальше указывать на какую-либо строку матрицы.
Сразу заметим, что у вас не может быть больше 10 строк, так как на 11 физически нет места в массиве в виде свободного указателя. С другой стороны, в памяти пока что занято место только под эти 10 указателей (уже меньше, чем в прошлом примере, верно?).
Как же выделить место под каждую строчку матрицы шириной в W столбцов? Да легко, каждому указателю из массива Arr можно выделить память через malloc.
3>
В чём преимущество данного подхода?
В первую очередь, это не фиксированный размер матрицы. Можно выделить сколько угодно места под каждую строчку, то есть матрицу можно легко сделать неквадратной и даже определить кол-во столбцов в ходе работы программы.
Во-вторых, вы можете даже не использовать все 10 указателей под каждую строчку. В общем, по памяти это оптимальнее.
С другой стороны, простота данного подхода является мнимой. Если вам понадобится больше 10 строк, то ваш массив Arr не подойдет по размеру. Можно выделить и больший размер, но где потолок? 20? 100? 1000? Всегда у вас либо не хватает, либо вы берёте с избытком.
Откровенно говоря, эта проблема немного надуманная. Когда вы реально с ней столкнетесь, то будете знать много больше чем сейчас и заботить вас будут другие вещи. С другой стороны, из спортивного интереса и чувства вселенской справедливости, было бы неплохо научиться делать всё в виде чистой динамики.
Так что нет места полумерам! Мы можем объявить полностью динамически определяемую матрицу! Для этого введем двойной указатель.
4>
Код выше - абсолютная динамика. Размеры матрицы задаются в любом месте программы. Да, двойной указатель может казаться страшным. Но он точно выглядит менее уродским, чем конструкции массивов указателей. С другой стороны, массивы указателей достаточно часто используются в программах Жуковой и, вероятно, она будет ожидать увидеть именно их.
Важно понимать, что и двойной указатель, и массив указателей взаимозаменяемы.
int **Mat;
int *Arr[4];
// пропустим код где внесли значения в Mar и Arr
Обратимся к элементу в второй строчке и втором столбце. Для этого через двойной указатель
*( (*Mat + 1) + 2)
, а через массив -*(*(Arr[1]) + 2).
Как видно, разницы при работе между ними почти нет.Пытливый читатель может задаться вопросом - как это связано с строками? Ответ: самым очевидным образом.
Строка представляется как массив символов. С ней удобно работать через указатели.
Пусть вы вводите массив строк. Заранее количество вводимых строк скорее всего вам неизвестно. Каждая строка имеет различное число элементов. Работать через двойной указатель в этих условиях проще всего. Но сегодня здесь будет описана только работа через массив строк.
Просмотрите ещё раз код программы на самом верху страницы прежде чем начать читать следующий раздел.
2. Массивы строк
Обычная строка в памяти представляется как массив символов. Все элементы лежат последовательно.

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

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

Обсудим эту картинку подробнее.
Задан массив указателей, указателям заданный строки. Хотим найти длину строки 2 ( one ) с помощью strlen
Обратимся я ячейке
Arr[1]
и произведем разыменовывание (*(Arr[1])
). Как компьютер поймет сколько в полученной строке символов?Массив указателей хранит адреса блоков памяти, а именно - адреса первых ячеек каждого блока. При разыменовании указателя компьютер берет адрес хранящийся в указателе(1), переходит по нему на нужный блок памяти (2) и считывает все ячейки по возрастанию пока не встретит '\0' - нуль-терминирующий символ(3) [подробнее об этом - http://qoo.by/321t]
То есть, заданный в программе массив указателей даёт нам кучку указателей, для каждого из которых можно выделить память, заполнить её значениями и работать с ними. Для простоты, можно считать, что в каждой ячейке массива хранится строка.
3. Ввод массива строк
Рассмотрим подробнее что происходит в нашей программе-примере.
Программа выполняет следующее: пользователь вводит строку, то есть последовательность символов и жмет enter. Эта последовательность записывается в память. Если пользователь ввёл пустую строку (просто нажал enter), то ввод считается законченным. После чего на экран выводятся записанные в память строки.
Теперь построчно:
char *STR[10];
- массив указателей. Каждому указателю будет выделена память, куда будет записана введенная строка.char **s;
- указатель на строку из массива. С его помощью в цикле будем обращаться к нужной строке. Обязательно двойной: первый уровень позволяет обратиться к элементу массива, а второй уровень должен совпадать с типом строки_char*_
char buf[80];
- массив фиксированной длины, в который заносится введенная с клавиатуры строка. Можете использовать и char*, но проще через массив ( по не до конца понятным мне причинам если использовать строку возникают непонятные глюки. С массивом такой байды нету + Жукова обычно пишет массивона что-то знает). Обратите внимание - в массиве 80 элементов, то есть строка большей длины записана не будет. Функция gets() передаст всю введенную строку в этот массив, но поместятся только первые 80.int n = 0;
- счетчик числа введенных строк.int max_number_of_strings = 10;
- указываете сколько максимум строк хотите ввести. Счетчик количества строк не превысит это значение.Для того, чтобы работать с строкой мы будем пользоваться переменной
**s
- она имеет такой же тип данных, как и наш массив указателей (для людей это разное, для компьютера одно и то же).Перед началом работы укажем:
s = STR
В дальнейшем после каждой итерации цикла ввода (то есть, после каждой успешно введенной строки) будем увеличивать эту переменную:
s++
Так как ввод строк осуществляется в цикле, то обсудим условия выхода из него. Их ровно два:
С первым условие проблем нет: у нас есть счётчик
n
и условиеn < max_number_of_strings
подойдет под наши задачи.С вторым условием немножко сложнее.
Строка считывается функцией
gets(char*)
. Аргумент функции - это переменная в которую мы хотим записать пользовательский ввод. Будем записывать в наш массивbuf
(то есть buffer // буфер):gets(buf)
Если пользователь ввёл пустую строку, то в строке будет записан только
'\0'
. В случае нашего буфера мы говорим о ячейкеbuf[0]
.Второе условие можно записать следующим образом:
buf[0] != '\0'
Теперь когда мы считали строку с клавиатуры и убедились, что её следует записать в память, запишем. Я объясню этот момент чуть позже, пока закончим с циклом.
Если руководствоваться нашими соображениями, то нам нужен вот такой цикл:
Язык Си очень хитёр. Формально, к каждой функции можно обратиться по указателю и тип
void
- не исключение. Есть хитрая возможностьповыё******сяоптимизировать работу циклаи сделать его ещё более непонятным.Мы можем обратиться к функции
gets(buf)
через разыменовывание и, тем самым, получим доступ к считанной ей строке. Этот доступ указывает сразу на начало строки, которое совпадает с первым элементом. Сравниваем его с символом конца строки и дело в шляпе. Наш счётчикn
тоже можно вынести в цикл.Из таких соображений мы получаем цикл:
for (s = STR, n = 0; n < max_number_of_strings && *gets(buf) != '\0'; n++, s++)
Теперь рассмотрим как записать в память нашу считанную строку. Сейчас (после выполнения
gets(buf)
) она хранится вbuf
Нам необходимо нужному(!) указателю из нашего массива указателей
STR
выделить память подходящего размера и в эту память записать значенияbuf
.Для выделения памяти воспользуемся
malloc
:*s= (char*)malloc( strlen(buf) * sizeof(char) + 1)
В первую очередь, обратите внимание что вы выделяем память не s, а хранимому в ней значению. То есть, мы берем наш указатель из массива
STR
на который сейчас указываетs
и ему(!) выделяем память. Соответственно в скобочках передmalloc
пишем одну звездочку, а не две (так как строка - этоchar*
, а неchar**
)Для выделения памяти нам нужно знать сколько её выделять, то есть - нужно найти длину строки buf воспользовавшись
strlen
. Эта функция вернем нам количество символов, но не учтёт нуль-символ. Его придется добавить вручную - просто увеличим+1
объем памяти. Обратите внимание на эту деталь, так убережёте себя от кучи убитых часов на отладку.Теперь остается скопировать нашу строчку из временного места хранения в постоянное. Вызываем
strcpy
:strcpy(STR[i], buf).
И на этом всё! В полном виде наш цикл устроен так:
Обсудим вывод строки. Он полностью аналогичен работе с массивами (ну или работе с строками, кому как угодно). Мы знаем сколько строк введено. Берем указатель
S
, связываем его с массивом и выводим на экран все первыеn
элементов.Один нюанс: наш
s
формально указывает на указатель в массиве, а не на строчку. Строка же - это то, что хранится в том блоке памяти, на который указывает указатель. Поэтому воспользуемся разыменованием чтобы получить нашу строчку.( Компилятор вам сразу выдаст подсказку: функция
puts
принимает на входchar*
, а типs
-char**
.