Неформатированные ввод из стандартного потока и вывод в стандартный поток

Функция printf() позволяет легко вывести на экран строку с пробелами:

printf("%s", "Hello world");

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

Для таких случаев полезной может оказаться функция getchar(), которая позволяет вводить данные посимвольно:

int i;char str[20]; 
for (i=0; (str[i] = getchar()) != '\n'; i++);
str[i] = '\0';
 
printf("\n%s\n", str);

В заголовке цикла getchar() возвращает символ, который записывается в текущую ячейку массива. Если символ равен '\n', цикл заканчивается. Затем он затирается символом '\0'. В примере специально не добавлен проверочное условие для предотвращения выхода за пределы массива.

На языке программирования C можно использовать более простые способы работы со строками. Функции стандартной библиотеки gets() и puts() применяются для ввода строк из стандартного потока и их вывода. Буква s в названиях функций указывает на слово string (строка).

Обе функции принимают указатель на массив символов (это может быть имя массива или указатель).

Функция gets() записывает введенные символы в массив, указанный в качестве аргумента. Символ перехода на новую строку игнорируется. В свою очередь, функция puts() выводит строку и добавляет символ новой строки самостоятельно. Самый простой пример их использования:

char str[20];
 
gets(str);
puts(str);

Таким образом, если вам достаточно работать со строками, а посимвольная обработка не требуется, функция puts() и gets() будут удобным выбором. (Однако, стоит отметить, что функцию gets() признали небезопасной, и она была удалена из стандарта C11.)

Массив символов и указатель на строку

Строка — это массив символов, последний элемент которого — это нулевой символ '\0' в кодировке ASCII. При работе со строками, как и с числовыми массивами, можно использовать указатели. Вы можете объявить массив символов, записать в него строку, а затем установить указатель на первый или любой другой элемент массива, работая с ним через указатель:

char name[30];
char *nP;
 
printf("Введите имя и фамилию: ");
gets(name);
 
printf("Имя: ");
for(nP = name; *nP != ' '; nP++)
    putchar(*nP);
 
printf("\nФамилия: ");
puts(nP+1);

Цикл начинается с присвоения указателю адреса первого элемента массива. Затем значение указателя увеличивается до тех пор, пока не будет достигнут пробел. Таким образом, с помощью указателя можно получить вторую часть строки.

Иногда в программах можно увидеть подобные объявления и определения для указателя-строки:
char \*strP = "Hello World!";

Строку, присвоенную указателю, можно также вывести на экран, обратившись по указателю:
puts(strP);

Но какова разница между строкой, присвоенной указателю, и строкой, присвоенной массиву?

Когда в программе объявляются переменные и определяется память для данных, изменять эти данные во время выполнения программы нельзя, если они не были присвоены переменным.

Что это значит в нашем примере? В программу вводится строка-константа (литерал), ссылка на первый элемент которой присваивается указателю. Вы можете менять значение указателя для перехода к любым элементам строкового массива или даже начать ссылаться на другую строку. Но изменять элементы самой строки нельзя. Это можно подтвердить таким примером кода:

char *strP;
	strP = "This is a literal";  // работает, но строку нельзя изменить
 
	puts(strP);
	printf("%c\n",strP[3]);
	strP[3] = 'z'; // ошибка

Попытка изменить строку-константу в последней строке кода приведет к ошибке.
В таком случае недопустимо делать следующее:

char *strP;
	scanf("%s",strP); // ошибка сегментации

В этой ситуации память под массив символов не выделена, есть только указатель. Поэтому записать строку некуда. Если же память выделена путем объявления массива и адрес массива присвоен указателю, ситуация меняется:

char str[12];
	char *strP;
	strP = str;
	gets(strP); // память выделена под массив заранее
	puts(strP);

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

Передача строки в функцию

Передача строки в функцию происходит аналогично передаче массивов чисел:

void change (char *s) {
	for (;*s != '\0'; s++)
		(*s)++;
}
Задание:

Объявите три массива символов в программе. Получите данные для двух из них с помощью функции gets(). Третий массив должен хранить результат конкатенации (объединения) двух введенных строк. Напишите функцию для выполнения конкатенации строк.

Массив строк и массив указателей

Рассмотрим более сложный пример. У нас есть набор строк. Нужно отсортировать их по длине, начиная с самых коротких:

Набор строк можно представить как двумерный массив, где каждый одномерный массив — это строка символов:
        char str[][10] = {"Hello", "World", "!!!", "&&&"};

Сортировка строк требует перемещения значений в ячейках памяти, что может быть затруднительно для компьютера при большом количестве строк. Однако, есть иной подход. Можно создать массив указателей, где каждый элемент указывает на строку в первом массиве. Далее сортируем указатели, что более эффективно. Хотя сам набор строк не отсортируется, с помощью указателей комплексный "срез" будет упорядочен:

Сортировка строк через массив указателей

#include <stdio.h>
#include <string.h>
 
#define N 6
 
void sortlen(char *s[]);
 
int main() {
char strings[N][30];
char *strP[N];
int i;
 
	for(i=0; i<N; i++) {
		gets(strings[i]);
		strP[i] = &strings[i][0];
	}
	printf("\n");
	sortlen(strP);
	for(i=0; i<N; i++) {
		printf("%s\n",strP[i]);
	}
 
}
 
void sortlen(char **s) { // **s == *s[] - псевдомассив указателей
	int i, j;
	char *str;
 
	for (i=0; i<N-1; i++)
		for (j=0; j < N-i-1; j++) {
			if (strlen(s[j]) > strlen(s[j+1])) {
				str = s[j];
				s[j] = s[j+1];
				s[j+1] = str;
			}
		}
}

Замечания:

  • Параметром функции sortlen() является указатель на указатель или, что проще, массив указателей на символы. Мы передаем в функцию указатель на первый элемент массива strP.
  • Сортировка выполняется методом пузырька: если длина строки, на которую указывает следующий указатель массива strP, меньше длины строки по текущему указателю, значения указателей меняются.
  • Выражение strP[i] = &strings[i][0] означает, что элементу массива указателей присваивается адрес первого символа строки.
Задание:

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

Вопросы для самопроверки:

  1. Каково предназначение `\0` в строке на языке программирования C?
  2. Почему недопустимо использовать scanf("%s", strP) без выделения памяти для строки?
  3. Как в программе реализовать конкатенацию (объединение) двух строк?
  4. Опишите, как можно отсортировать строки по длине с использованием массива указателей.
  5. Как бы вы передали строку в функцию для изменения её символов?

Программа курса:

  1. Описание курса
  2. Введение в язык программирования C
  3. Типы данных в C и форматированный вывод
  4. Символьные типы и управляющие символы в C
  5. Операторы ветвления и switch в C
  6. Циклы и операторы в языке C
  7. Битовые операции в языке C
  8. Посимвольный ввод и вывод в C - буферизация
  9. Переменные, адреса и указатели в C
  10. Передача аргументов по ссылке и значению в C
  11. Форматированный ввод данных с использованием scanf
  12. Генерация псевдослучайных чисел на C
  13. Адресная арифметика в массивах C
  14. Передача массивов в функции и указатели
  15. Строки в языке C - особенности и функции работы
  16. Функции работы со строками в C
  17. Работа со структурами в C - создание и применение
  18. Динамические структуры данных в C
  19. Ввод и вывод данных из файлов в языке C
  20. Передача аргументов в C и работа с файлами
  21. Препроцессор в языке C - директивы и макросы
  22. Создание и компиляция многофайловых программ в C
  23. Использование статических и динамических библиотек в C