Родитель
							
								
									fcf8a9ab72
								
							
						
					
					
						Сommit
						cdda268119
					
				@ -0,0 +1,111 @@
 | 
				
			|||||||
 | 
					4. Работа со строками C
 | 
				
			||||||
 | 
					Данная часть лабораторной работы выполняется отдельно от предыдущей части, в новом проекте.
 | 
				
			||||||
 | 
					Вместо пошагового выполнения ЛР рассмотрим решение двух типовых задач: ввода и обработки строки C функциями стандартной библиотеки и загрузки текста из файла в строку C. Задание на ЛР представляет собой их комбинацию.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					4.1. Ввод строки C и её обработка функциями стандартной библиотеки
 | 
				
			||||||
 | 
					Решим задачу: считать строку C и напечатать по отдельности слов в ней (слова разделены пробелами и знаками препинания).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Ввод строки C
 | 
				
			||||||
 | 
					Для определенности предположим, что длина строки не превышает некоторой заранее заданной, например, 255 символов. С учетом завершающего '\0' под строку нужно 256 символов:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const size_t MAX_SIZE = 256;
 | 
				
			||||||
 | 
					char text[MAX_SIZE];
 | 
				
			||||||
 | 
					Ввести с строку C можно функцией fgets(). Ознакомимся с документацией по ссылке. В документации обычно есть и примеры использования описываемых функций.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Прототип функции:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					char* fgets(char* str, int count, std::FILE* stream);
 | 
				
			||||||
 | 
					Над прототипом написано: Defined in header <cstdio> — это значит, что для использования функции нужно включить заголовочный файл <cstdio>.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Под прототипом написано, что делает данная функция: считывает не более count - 1 символов и записывает их в массив, на который указывает str; чтение ведется из файлового потока stream.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Нам необходимо считывать строку со стандартного ввода, где взять файловый поток для него? В справочнике std::FILE является ссылкой на статью «C-style file input/output» («Файловый ввод-вывод средствами C»). В конце её в разделе Macros можно найти запись:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					stdin       expression of type FILE* associated with the input stream
 | 
				
			||||||
 | 
					То есть глобальная переменная stdin из <cstdio> и есть нужный поток.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Итак, вызов для чтения строки:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fgets(text, MAX_SIZE, stdin);
 | 
				
			||||||
 | 
					Заметим, что на практике, а не в учебных целях, удобнее считывать строки C++:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					string text;
 | 
				
			||||||
 | 
					getline(cin, text);
 | 
				
			||||||
 | 
					Если затем нужен указатель на массив считанных символов, его можно получить как text.c_str() (менять символы с этом массиве нельзя; при необходимости есть метод text.data()).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Разбиение строки на слова
 | 
				
			||||||
 | 
					Чтобы напечатать слова строки по отдельности, нужно искать границы слов и печатать часть строки от начала до конца слова. Чтобы найти конец слова, нужно найти первый (от любой позиции внутри слова, в том числе от его начала) символ-разделитель. Разделители могут идти подряд. Вот пример текста:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					News,from beyond the Narrow Sea.  Haven't you heard?!
 | 
				
			||||||
 | 
					    ↑                           ↑↑
 | 
				
			||||||
 | 
					нет пробела                 два пробела
 | 
				
			||||||
 | 
					Кроме знаков препинания, разделители включают также пробел и символы перевода строк:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char* separators = " \r\n,.!?:;()-";
 | 
				
			||||||
 | 
					Функции стандартной библиотеки для работы со строками C — в заголовочном файле <cstring>. Из обширного списка наиболее подходящими для задачи выглядят описания:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					strspn() — определяет, сколько первых символов строки подряд относятся к множеству, заданному другой строкой;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					strсspn() — определяет, сколько первых символов строки подряд не относятся к множеству, заданному другой строкой (например, сколько символов с начала строки — не разделители слов);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Алгоритм решения:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Определить, сколько разделителей находятся в начале строки — strspn().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Пропустить их (сместить указатель на начало строки).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Если достигнут конец строки (начальный символ — '\0'), закончить работу.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Найти первый разделитель от нового начала строки (или слова), то есть длину слова — strcspn().
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Напечатать часть строки от начала слова до разделителя (это можно сделать методом cout.write() или функцией fwrite()). Также напечатать символ перевода строки.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Сдвинуть начало строки вперед на длину слова.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Перейти к пункту 1.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Почти каждый шаг алгоритма — всего одна строка или конструкция. Начало строки (то есть еще не разобранной части) будем хранить в переменной:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const char* start = text;
 | 
				
			||||||
 | 
					Алгоритм представляет собой цикл:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					while (true) {
 | 
				
			||||||
 | 
					Определить, сколько разделителей находятся в начале строки:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const size_t separator_count = strspn(start, separators);
 | 
				
			||||||
 | 
					Пропустить их:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					start += separator_count;
 | 
				
			||||||
 | 
					Если достигнут конец строки, закончить работу.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (start[0] == '\0') {
 | 
				
			||||||
 | 
					    break;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					Найти первый разделитель от нового начала строки:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const size_t word_length = strcspn(start, separators);
 | 
				
			||||||
 | 
					Напечатать часть строки от начала слова до разделителя:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cout.write(start, word_length);
 | 
				
			||||||
 | 
					Также напечатать символ перевода строки:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      cout << '\n';
 | 
				
			||||||
 | 
					Сдвинуть начало строки вперед на длину слова:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					start += word_length;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Соединив участки кода в полноценную программу, можно убедиться, что она работает правильно:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					echo "News,from beyond the Narrow Sea.  Haven't you heard?" | lab04.exe
 | 
				
			||||||
 | 
					Вывод:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					News
 | 
				
			||||||
 | 
					from
 | 
				
			||||||
 | 
					beyond
 | 
				
			||||||
 | 
					the
 | 
				
			||||||
 | 
					Narrow
 | 
				
			||||||
 | 
					Sea
 | 
				
			||||||
 | 
					Haven't
 | 
				
			||||||
 | 
					you
 | 
				
			||||||
 | 
					heard
 | 
				
			||||||
					Загрузка…
					
					
				
		Ссылка в новой задаче