- •Тема 7. Строки, потоки, файлы.
- •1. Строки.
- •1.1. Создание строк
- •1.2. Присваивание строк
- •1.3. Сравнение строк
- •1.4. Длина строки
- •1.5. Преобразования строк
- •1.6. Объединение строк
- •1.7. Другие функции для работы со строками
- •2. Файлы и потоки.
- •2.1. Понятие файла и потока.
- •2.2. Указатель на файл, открытие файла, обработка ошибок, закрытие файла, изменение режима открытия.
- •2.3. Чтение из файла и запись в файл
- •2.4. Позиционирование в потоке.
- •2.5. Контроль исполнения потоковых операций.
Тема 7. Строки, потоки, файлы.
1. Строки.
В С и С++ отсутствуют встроенные строковые типы в том смысле, в котором они есть в языках типа Basic и Pascal. И присущая этим языкам легкость оперирования строковыми переменными (присвоение, сравнение) в С недоступна. Что же такое строка в С?
Для начала разберемся, что такое строка в жизни. Очевидно, что строка – это последовательность символов. В С – как в жизни. С-строка — это последовательность символов. Как известно, последовательности в С представляются массивами или указателями.
Как известно:
- массив можно привести к указателю на его первый элемент, что неявно происходит при передаче массивов в функции, ожидающие указатели;
- информация о размере массива переданного таким образом в функцию теряется;
- в С не существует способа передать массив по значению с сохранением его размера;
- указателя на первый элемент массива достаточно для работы со всем массивом, при условии, что нам известна его длина.
То есть в С строка – это массив элементов типа char, ограниченный символом с кодом 0, называемом нуль-терминатором. Напомню, что c массивом элементов типа char связан указатель на char, поэтому С-строка– это еще и указатель типа char* на область памяти, заканчивающуюся символом с кодом 0.
1.1. Создание строк
char str1[10]; |
Строка - массив из 10 символов. Начальное значение символов не определено. |
char str2[10]="Hello"; |
Используется инициализация. В первые 5 символов записывается “Hello”, в 6 – нуль-терминатор, значение трех последних не определено. |
char str3[10]={'H', 'e', 'l', 'l', 'o', '\0'}; |
Эквивалентно предыдущему. |
char str4[10]="Very long line"; |
Ошибка. Массив из 10 элементов нельзя инициировать более длинной последовательностью. |
char str5[]="Very long line"; |
Компилятор автоматически определяет длину массива (в нашем случае 15) и инициализирует его последовательностью символов |
char* str6; |
Строка - указатель на символ. В большинстве случаев для ее использования потребуется выделить память. |
1.2. Присваивание строк
Примеры неправильного присваивания строк:
char str1[10], str2[10];
str1="Hello";
str2=str1;
Одна и та же ошибка в обоих операторах “=”, имя массива нельзя использовать в левой части оператора присваивания.
Эта ошибка относительно безопасна, так как приводит к сбою на этапе компиляции. Есть и гораздо более опасная ошибка.
char str1[10]= "Hello";
char* str2;
str2=str1;
str2[1]='u';
Этот код откомпилируется, но, возможно, содержит ошибку. Неправильно полагать, что в str2 теперь содержится копия str1. На самом деле этот указатель указывает не на копию, а на ту же самую строку. При любом изменении содержимого str2 изменяется str1. Однако если именно это и требуется, то все в порядке.
Еще один рискованный вариант присваивания указателей – присваивание их строковым литералам. Тип строкового литерала – const char*. Стандарт С++ разрешает присваивание без спецификации const. Что может иметь неприятные последствия:
char* str;
str="Hello";
str[3]=’p’;
str[4]=’!’;
Результат работы такой программы непредсказуем. Компилятор может разместить константы в памяти только для чтения, и попытка их изменить приведет к сбою. Поэтому всегда объявляйте указатели, в которые вы собираетесь записывать адреса строковых литералов как const char*.
Вопросы неправильного и рискованного присваивания строк мы рассмотрели. Теперь о правильном присваивании или копировании строк.
Первый и самый очевидный способ присваивания строк – присваивание отдельных символов. Например,
str1[0]=’H’;
str1[1]=’e’;
str1[2]=’l’;
str1[3]=’l’;
str1[4]=’o’;
str1[5]=’\0’;
Однако, это совершенно неудобно.
Для копирования строк существуют несколько библиотечных функций, наиболее общеупотребительной из которых является функция strcpy (библиотека string.h).
char* strcpy(char* dest, char* src)
Функция посимвольно копирует содержимое строки, на которую указывает src в строку, на которую указывает dest и возвращает dest. Так как массив может быть преобразован в указатель, такой вызов функции абсолютно легален:
char str1[10], str2[10];
strcpy(str1, "Hello");
strcpy(str2, str1);
При использовании этой функции следует соблюдать осторожность. Опасность заключается в том, что даже если исходная строка окажется больше, чем память, выделенная для второй строки (программистом через malloc или компилятором при использовании массивов), функция strcpy никак про это узнать не сможет и продолжит копирование в невыделенную память. Разумеется, последствия будут катастрофическими.
Снизить риск такого развития событий способна функция
char* strncpy(char* dest, char* src, int count)
Последний параметр – максимальное количество копируемых символов. Таким образом, передавая туда размер приемника, вы гарантируете, что функция никогда не выйдет за пределы выделенной памяти. Однако, если исходная строка будет скопирована не полностью, нуль-терминатор не появится в результирующей строке. Его придется записать самостоятельно.
