sonyps4.ru

C функции для работы со строками. Работа со строками

Объявление строк

Строка в языке Си представляет собой одномерный массив символов, последним элементом которой является символ конца строки – нуль (строка, завершающаяся нулем, то есть NULL terminated string).

Объявление переменной типа строка в языке Си возможно тремя способами, два из которых инициализируют строку во время объявления.

Первый способ:

Объявления массива символов (не забудьте добавить место для завершающего нуля):

Char s;

Второй способ:

Присвоить строковой переменной начальное значение (при этом длину строки компилятор может вычислить сам):

Char s = "Пример инициализации строки";

Справа от знака присваивания записана строковая константа. В конце строки автоматически добавляется ноль (‘\0’). Константы символьных строк помещаются в класс статической памяти.

Третий способ:

Неявное указание, что используется массив. В левой части от знака присваивания указывается указатель на символ:

Char *s="Второй вариант инициализации";

Переменная s будет указателем на то место в оперативной памяти, где располагается строковая константа. В такой форме записи кроется потенциальная ошибка, заключающаяся в том, что указатель на символ часто называют строкой. Представленная ниже запись – это только указатель на символ, так как для размещения строки место не предусмотрено:

Char *s;

Ввод строки со стандартного устройства ввода (клавиатуры)

Для работы со строками есть набор функций. Для ввода со стандартного устройства ввода (клавиатуры) чаще всего используются библиотечные функциями из модуля стандартного ввода-вывода: scanf и gets .

Для ввода строки с помощью функции scanf , использует формат «%s » , причем обратите внимание на то, что перед идентификатором строки не используется знак адреса «& » , так как одномерный массив уже представлен указателем на его начало:

Scanf("%s", s);

Функция gets() считывает символы до тех пор, пока не достигнет символа перехода на новую строку. Функция принимает все символы вплоть до символа перевода строки, но не включает его. К концу строки добавляется завершающий ноль (‘\0’). Функция gets() помещает считанную с клавиатуры последовательность символов в параметр типа строка и возвращает указатель на эту строку (если операция завершилась успешно), или NULL (в случае ошибки). В приведенном ниже примере при успешном завершении операции, на экран будет выведено две одинаковые строки:

#include int main() { char s; char *p; p=gets(s); printf(" \n Введена строка %s. ",s); if (p) printf(" \n Введена строка %s. ",p); return 0; }

Попутно заметим, что функция gets часто используется для ввода лю-бых данных с клавиатуры в виде строки с целью дальнейшего преобразования функцией sscanf к нужному формату или для предварительного анализа вводимых данных, например:

#include #include #include int main() { char s; int x, err; do { printf(" \n Введите целое число -> "); gets(s); err=sscanf(s, "%d",&x); if (err!=1) printf(" \n Ошибка ввода. "); } while (err!=1); printf("\n Введено целое число -> %d", x); return 0; }

Вывод строк на стандартное устройство вывода (экран монитора)

Для вывода строк на стандартное устройство вывода (экран монитора) можно использовать две функции printf и puts . В функции printf в качестве формата передается «%s». Удобство использования этой функции заключается в том, что помимо строки можно сразу выводит данные других типов. Особенность функции puts заключается в том, что после вывода строки автоматически происходит переход на следующую строку.

Функции для работы со строками

Для преобразования строк в языке Си предусмотрена библиотека string. Каждая из функций имеет свой формат записи (прототип).

Наиболее используемые функции рассмотрены в этой статье. — читать

Пример программ(листинг) работающей со строками

Работа со строками. Класс string . Конструкторы класса. Функции assign() , append() , insert() , replace() , erase() , find() , rfind() , compare() , c_str() . Примеры

1. Какое назначение класса string в программах на C++?

Класс string предназначен для работы со строками типа char* , которые представляют собой строку с завершающим нулем. Класс string был введенн как альтернативный вариант для работы со строками типа char* . Строки, которые завершаются символом ‘\0’ еще называются C-строками. Поскольку, string есть классом, то можно объявлять объекты этого класса.

2. Какие модули (библиотеки) нужно подключить, чтобы использовать возможности класса string в MS Visual Studio C++?

Чтобы использовать возможности класса string в MS Visual Studio (C++), нужно подключить библиотеку и пространство имен std .

#include using namespace std;
3. Каким образом осуществляется объявление переменной типа string ? Примеры

Объявление переменной типа string осуществляется точно так же как и обычной переменной. Возможный вариант объявления с одновременной инициализацией.

// тип string string s1; // переменная с именем s1 типа string string s2 = "This is a string variable" ; // объявление с инициализацией // использование переменной типа string с оператором присваивания s1 = s2; // s1 = "This is a string variable" s2 = "New text" ;
4. Какие преимущества и недостатки дает использование класса string в сравнении с типом char* ?

Создание нового типа string было обусловлено недостатками работы с строками символов, который демонстрировал тип char* . В сравнении с типом char* тип string имеет следующие основные преимущества:

  • возможность обработки строк стандартными операторами C++ (= , + , = = , <> и т.п.). Как известно, при использовании типа char* даже наиболее простые операции со строками выглядели сложно и требовали написания чрезмерного программного кода;
  • обеспечение лучшей надежности (безопасности) программного кода. Например, при копировании строк, тип string обеспечивает соответствующие действия, которые могут возникнуть в случае, если строка-источник имеет больший размер чем строка-приемник;
  • обеспечение строки, как самостоятельного типа данных. Объявление типа string как строки есть единым для всех переменных в программе, которая обеспечивает непротиворечивость данных.

Основным недостатком типа string в сравнении с типом char* , есть замедленная скорость обработки данных. Это связано с тем, что тип string – это, фактически, контейнерный класс. А работа с классом требует дополнительной реализации программного кода, который, в свою очередь занимает лишнее время.

5. Какие операторы можно использовать с объектами класса string ?

Класс string есть удобен тем, что позволяет удобно манипулировать строками, используя стандартные (перегруженные) операторы.

С объектами класса string можно использовать нижеследующие операторы

  • = – присваивание
  • + – конкатенация (объединение строк)
  • += – присваивание с конкатенацией
  • == – равенство
  • != – неравенство
  • < – меньше
  • <= – меньше или равно
  • > – больше
  • >= – больше или равно
  • – индексация

Пример, который демонстрирует использование вышеприведенных операторов

// тип string, операции над строками string s1 = "s-1" ; string s2 = "s-2" ; string s3; bool b; // операция "=" (присваивание строк) s3 = s1; // s3 = "s-1" // операция "+" - конкатенация строк s3 = s3 + s2; // s3 = "s-1s-2" // операция "+=" - присваивание с конкатенацией s3 = "s-3" ; s3 += "abc" ; // s3 = "s-3abc" // операция "==" - сравнение строк b = s2==s1; // b = false b = s2=="s-2" ; // b = true // операция "!=" - сравнение строк (не равно) s1 = "s1" ; s2 = "s2" ; b = s1 != s2; // b = true // операции "<" и ">" - сравнение строк s1 = "abcd" ; s2 = "de "; b = s1 > s2; // b = false b = s1 < s2; // b = true // операции "<=" и ">=" - сравнение строк (меньше или равно, больше или равно) s1 = "abcd" ; s2 = "ab" ; b = s1 >= s2; // b = true b = s1 <= s2; // b = false b = s2 >= "ab" ; // b = true // операция - индексация char c; s1 = "abcd" ; c = s1; // c = "c" c = s1; // c = "a"
6. Содержит ли класс string конструкторы?

Как и любой класс, класс string имеет ряд конструкторов. Основные из них следующие:

String(); string(const char * str); string(const string & str);

7. Примеры инициализации с помощью конструкторов

Ниже приведены примеры инициализации переменных типа string

String s1("Hello!" ); string s2 = "Hello!" ; // инициализация - конструктор string(const char * str) char * ps = "Hello" ; string s3(ps); // инициализация string s4(s3); // инициализация - конструктор string(const string & str) string s5; // инициализация - конструктор string()

8. Присваивание строк. Функция assign() . Примеры

Чтобы присвоить одну строку другой, можно применить один из двух методов:

  • использовать оператор присваивания ‘=’ ;
  • использовать функцию assign() из класса string .

Функция assign() имеет несколько перегруженных реализаций.

Первый вариант – это вызов функции без параметров

String &assign(void );

В этом случае происходит простое присваивание одной строки другой.

Второй вариант позволяет копировать заданное количество символов из строки:

String &assign(const string & s, size_type st, size_type num);

  • s – объект, из которого берется исходная строка;
  • st – индекс (позиция) в строке, из которой начинается копирование num символов;
  • num – количество символов, которые нужно скопировать из позиции st ;
  • size_type – порядковый тип данных.

Третий вариант функции assign() копирует в вызывающий объект первые num символов строки s :

String & assign(const char * s, size_type num);

  • s – строка, которая завершается символом ‘\0’ ;
  • num – количество символов, которые копируются в вызывающий объект. Копируются первые num символов из строки s .

Ниже приведен пример с разными реализациями функции assign() .

Пример.

// присваивание строк, функция assign() string s1 = "сайт" ; string s2; string s3; char * ps = "сайт" ; s3 = s1; // s3 = "сайт" s2.assign(s1); // s2 = "сайт" s2.assign(s1, 0, 4); // s2 = "best" s2.assign(ps, 8); // s2 = "bestprog"
9. Объединение строк. Функция append() . Пример

Для объединения строк используется функция append() . Для добавления строк также можно использовать операцию ‘+’ , например:

String s1; string s2; s1 = "abc" ; s2 = "def" ; s1 = s1 + s2; // s1 = "abcdef"

Однако, функция append() хорошо подходит, если нужно добавлять часть строки.

Функция имеет следующие варианты реализации:

String &append(const string & s, size_type start); string &append(const char * s, size_type num);

В первом варианте реализации функция получает ссылку на строчный объект s , который добавляется к вызывающему объекту. Во втором варианте реализации функция получает указатель на строку типа const char * , которая завершается символом ‘\0’ .

Пример. Демонстрация работы функции append() .

String s1 = "abcdef" ; s2 = "1234567890" ; append(s2, 3, 4); // s1 = "abcdef4567" char * ps = "1234567890" ; s1 = "abcdef" ; s1.append(ps, 3); // s1 = "abcdef123"

10. Вставка символов в строке. Функция insert() . Пример

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

Первый вариант функции позволяет вставить полностью всю строку s в заданную позицию start вызывающей строки (вызывающего объекта):

String & insert(size_type start, const string &s);

Второй вариант функции позволяет вставить часть (параметры insStart , num ) строки s в заданную позицию start вызывающей строки:

String & insert(size_type start, const string &s, size_type insStart, size_type num);

В вышеприведенных функциях:

  • s – строка, которая вставляется в вызывающую строку;
  • start – позиция в вызывающей строке, из которой осуществляется вставка строки s ;
  • insStart – позиция в строке s , из которой происходит вставка;
  • num – количество символов в строке s , которые вставляются с позиции insStart .
string s1 = "abcdef" ; string s2 = "1234567890" ; s1.insert(3, s2); // s1 = "abc"+"1234567890"+"def"="abc1234567890def" s2.insert(2, s1, 1, 3); // s2 = "12bcd34567890"
11. Замена символов в строке. Функция replace() . Пример

Функция replace() выполняет замену символов в вызывающей строке. Функция имеет следующие варианты реализации:

String &replace(size_type start, size_type num, const string &s); string &replace(size_type start, size_type num, const string &s, size_type replStart, size_type replNum);

В первом варианте реализации вызывающая строка заменяется строкой s . Есть возможность задать позицию (start ) и количество символов (num ) в вызывающей строке, которые нужно заменить строкой s .

Второй вариант функции replace() отличается от первого тем, что позволяет заменять вызывающую строку только частью строки s . В этом случае задаются два дополнительных параметра: позиция replStart и количество символов в строке s , которые образуют подстроку, которая заменяет вызывающую строку.

Пример. Демонстрация работы функции replace() .

String s1 = "abcdef" ; string s2 = "1234567890" ; s2.replace(2, 4, s1); // s2 = "12abcdef7890" s2 = "1234567890" ; s2.replace(3, 2, s1); // s2 = "123abcdef67890" s2 = "1234567890" ; s2.replace(5, 1, s1); // s2 = "12345abcdef7890" // замена символов, функция replace() string s1 = "abcdef" ; string s2 = "1234567890" ; s2.replace(2, 4, s1); // s2 = "12abcdef7890" s2 = "1234567890" ; s2.replace(3, 2, s1); // s2 = "123abcdef67890" s2 = "1234567890" ; s2.replace(5, 1, s1); // s2 = "12345abcdef7890" s2 = "1234567890" ; s2.replace(5, 1, s1, 2, 3); // s2 = "12345cde7890" s2 = "1234567890" ; s2.replace(4, 2, s1, 0, 4); // s2 = "1234abcd7890"

12. Удаление заданного количества символов из строки. Функция erase() . Пример

Для удаления символов из вызывающей строки используется функция erase() :

String & erase(size_type index=0, size_type num = npos);

  • index – индекс (позиция), начиная из которой нужно удалить символы в вызывающей строке;
  • num – количество символов, которые удаляются.

Пример.

String s = "01234567890" ; s.erase(3, 5); // s = "012890" s = "01234567890" ; s.erase(); // s = ""

13. Поиск символа в строке. Функции find() и rfind() . Примеры

В классе string поиск строки в подстроке можно делать двумя способами, которые отличаются направлением поиска:

  • путем просмотра строки от начала до конца с помощью функции find() ;
  • путем просмотра строки от конца к началу функцией rfind() .

Прототип функции find() имеет вид:

Size_type find(const string &s, size_type start = 0) const ;

  • s – подстрока, которая ищется в строке, что вызывает данную функцию. Функция осуществляет поиск первого вхождения строки s . Если подстрока s найдена в строке, что вызвала данную функцию, тогда возвращается позиция первого вхождения. В противном случае возвращается -1;

Прототип функции rfind() имеет вид:

Size_type rfind(const string &s, size_type start = npos) const ;

  • s – подстрока, которая ищется в вызывающей строке. Поиск подстроки в строке осуществляется от конца к началу. Если подстрока s найдена в вызывающей строке, то функция возвращает позицию первого вхождения. В противном случае функция возвращает -1;
  • npos – позиция последнего символа вызывающей строки;
  • start – позиция, из которой осуществляется поиск.

Пример 1. Фрагмент кода, который демонстрирует результат работы функции find()

// тип string, функция find() string s1 = "01234567890" ; string s2 = "345" ; string s3 = "abcd" ; int pos; pos = s1.find(s2); // pos = 3 pos = s1.find(s2, 1); // pos = 3 pos = s1.find("jklmn" , 0); // pos = -1 pos = s1.find(s3); // pos = -1 pos = s2.find(s1); // pos = -1

Пример 2. Демонстрация работы функции rfind() .

// тип string, функции find() и rfind() string s1 = "01234567890" ; string s2 = "345" ; string s3 = "abcd" ; string s4 = "abcd---abcd" ; int pos; pos = s1.rfind(s2); // pos = 3 pos = s1.rfind(s2, 12); // pos = 3 pos = s1.rfind(s2, 3); // pos = 3 pos = s1.rfind(s2, 2); // pos = -1 pos = s2.rfind(s1); // pos = -1 pos = s1.rfind(s3, 0); // pos = -1 // разница между функциями find() и rfind() pos = s4.rfind(s3); // pos = 7 pos = s4.find(s3); // pos = 0
14. Сравнение частей строк. Функция compare() . Пример

Поскольку тип string есть классом, то, чтобы сравнить две строки между собой можно использовать операцию ‘= =’ . Если две строки одинаковы, то результат сравнения будет true . В противном случае, результат сравнения будет false .

Но если нужно сравнить часть одной строки с другой, то для этого предусмотрена функция compare() .

Прототип функции compare() :

int compare(size_type start, size_type num, const string &s) const ;
  • s – строка, которая сравнивается с вызывающей строкой;
  • start – позиция (индекс) в строке s , из которой начинается просмотр символов строки для сравнения;
  • num – количество символов в строке s , которые сравниваются с вызывающей строкой.

Функция работает следующим образом. Если вызывающая строка меньше строки s , то функция возвращает -1 (отрицательное значение). Если вызывающая строка больше строки s , функция возвращает 1 (положительное значение). Если две строки равны, функция возвращает 0.

Пример . Демонстрация работы функции compare() :

// тип string, функция compare() string s1 = "012345" ; string s2 = "0123456789" ; int res; res = s1.compare(s2); // res = -1 res = s1.compare("33333" ); // res = -1 res = s1.compare("012345" ); // res = 0 res = s1.compare("345" ); // res = -1 res = s1.compare(0, 5, s2); // res = -1 res = s2.compare(0, 5, s1); // res = -1 res = s1.compare(0, 5, "012345" ); // res = -1 res = s2.compare(s1); // res = 1 res = s2.compare("456" ); // res = -1 res = s2.compare("000000" ); // res = 1
15. Получение строки с символом конца строки ‘\0’ (char * ). Функция c_str() . Пример

Чтобы получить строку, которая заканчивается символом ‘\0’ используется функция c_str() .

Прототип функции:

const char * c_str() const ;

Функция объявлена с модификатором const . Это означает, что функция не может изменять вызывающий объект (строку).

Пример 1 . Преобразование типа string в const char * .

// тип string, функция c_str() string s = "abcdef" ; const char * ps; ps = s.c_str(); // ps = "abcdef"

Пример 2.

Ниже продемонстрирован перевод строки из string в тип System::String для отображения его в элементе управления типа Label

Теги: Си строки. Char array.

Строки в си. Введение.

Э то вводная статья по строкам в си. Более подробное описание и примеры будут, когда мы научимся работать с памятью и указателями. В компьютере все значения хранятся в виде чисел. И строки тоже, там нет никаких символов и букв. Срока представляет собой массив чисел. Каждое число соответствует определённому символу, который берётся из таблицы кодировки. При выводе на экран символ отображается определённым образом.
Для хранения строк используются массивы типа char. Ещё раз повторюсь – тип char – числовой, он хранит один байт данных. Но в соответствии с таблицей кодировки каждое из этих чисел связано с символом. И в обратную сторону – каждый символ определяется своим порядковым номером в таблице кодировки.
Например

#include #include void main() { char c = "A"; int i = 65; printf("display as char %c\n", c); printf("display as int %d\n", c); printf("display as char %c\n", i); printf("display as char %d\n", i); getch(); }

Мы создали две переменные, одна типа char , другая int . Литера "A" имеет числовое значение 65. Это именно литера, а не строка, поэтому окружена одинарными кавычками. Мы можем вывести её на печать как букву

Printf("display as char %c\n", c);

Тогда будет выведено
A
Если вывести её как число, то будет
65
Точно также можно поступить и с числом 65, которое хранится в переменной типа int .
Спецсимволы также имеют свой номер

#include #include void main() { printf("%c", "\a"); printf("%d", "\a"); printf("%c", 7); getch(); }

Здесь будет сначала "выведен" звуковой сигнал, затем его числовое значение, затем опять звуковой сигнал.
Строка в си – это массив типа char , последний элемент которого хранит терминальный символ "\0". Числовое значение этого символа 0, поэтому можно говорить, что массив оканчивается нулём.
Например

#include #include void main() { char word; word = "A"; word = "B"; word = "C"; word = "\0"; //word = 0; эквивалентно printf("%s", word); getch(); }

Для вывода использовался ключ %s. При этом строка выводится до первого терминального символа, потому что функция printf не знает размер массива word.
Если в этом примере не поставить

Word = "\0";

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

#include #include void main() { char word = "ABC"; char text = {"H", "E", "L", "L", "O"}; printf("%s\n", word); printf("%s", text); getch(); }

В данном случае всё корректно. Строка "ABC" заканчивается нулём, и ею мы инициализируем массив word. Строка text инициализируется побуквенно, все оставшиеся символы, как следует из главы про массивы, заполняются нулями.

Чтение строк

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

#include #include void main() { char buffer; scanf("%19s", buffer); printf("%s", buffer); getch(); }

В данном случае количество введённых символов ограничено 19, а размер буфера на 1 больше, так как необходимо хранить терминальный символ. Напишем простую программу, которая запрашивает у пользователя строку и возвращает её длину.

#include #include void main() { char buffer; unsigned len = 0; scanf("%127s", buffer); while (buffer != "\0") { len++; } printf("length(%s) == %d", buffer, len); getch(); }

Так как числовое значение символа "\0" равно нулю, то можно записать

While (buffer != 0) { len++; }

Или, ещё короче

While (buffer) { len++; }

Теперь напишем программу, которая запрашивает у пользователя два слова и сравнивает их

#include #include /* Результатом сравнения будет число 0 если слова равны 1 если первое слово больше второго в лексикографическом порядке -1 если второе слово больше */ void main() { char firstWord; //Первое слово char secondWord; //Второе слово unsigned i; //Счётчик int cmpResult = 0; //Результат сравнения scanf("%127s", firstWord); scanf("%127s", secondWord); for (i = 0; i < 128; i++) { if (firstWord[i] > secondWord[i]) { //Больше даже если второе слово уже закончилось, потому что //тогда оно заканчивается нулём cmpResult = 1; break; } else if (firstWord[i] < secondWord[i]) { cmpResult = -1; break; } } printf("%d", cmpResult); getch(); }

Так как каждая буква имеет числовое значение, то их можно сравнивать между собой как числа. Кроме того, обычно (но не всегда!) буквы в таблицах кодировок расположены по алфавиту. Поэтому сортировка по числовому значению также будет и сортировкой по алфавиту.

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

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

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

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

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

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

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

Char myString;

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

Char *myString; // указатель типа char myString = malloc(sizeof(*myString) * 64); // выделение памяти

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

Free(myString);

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

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

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

#include int main() { char myString; // длинная строка printf("Введите длинную строку: "); fgets(myString, 100, stdin); // считываем из потока ввода строку printf("Вы ввели следующую строку: %s", myString); getchar(); }

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

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

<ВВОД>...

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

#include int main() { char myString; // длинная строка 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 . Результат работы программы:

Введите длинную строку: Судьба оставляет свой отпечаток Вы ввели следующую строку: Судьба оставляет свой отпечаток Для закрытия данного окна нажмите <ВВОД>...

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

P.S.: Все мы любим смотреть разные видео-записи, но иногда бывает так, что не всегда получается воспроизвести некоторые форматы видео-файлов. Так вот, решить эту проблему можно с помощью программы — xilisoft converter ultimate . Вы без труда сможете быстро переконвертировать видео из одного формата в другой. Кроме того, эта программа умеет конвертировать еще и аудио-файлы, и анимированные изображения.

Хабра, привет!

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

Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов).

У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу.

И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку.

Char *str = (char *)malloc(sizeof(char) * strlen(buffer));
buffer - стековая переменная, в которую заносились данные с клавиатуры.

Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?».
Поверьте, может.

А что именно - читайте по катом.

Немного теории - своеобразный ЛикБез.

Если знаете - листайте до следующего хэдера.

Строка в C - это массив символов, который по-хорошему всегда должен заканчиваться "\0" - символом конца строки. Строки на стеке (статичные) объявляются вот так:

Char str[n] = { 0 };
n - размер массива символов, то же, что и длина строки.

Присваивание { 0 } - «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор.

Так же на стеке можно сразу проинициализировать строку:

Char buf = "default buffer text\n";
Помимо этого строку можно объявить указателем и выделить под нее память на куче (heap):

Char *str = malloc(size);
size - количество байт, которые мы выделяем под строку. Такие строки называются динамическими (вследствие того, что нужный размер вычисляется динамически + выделенный размер памяти можно в любой момент увеличить с помощью функции realloc()).

В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче - я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size - это уже совсем другая история…

Нам поможет valgrind

В своей предыдущей статье я также упоминал о нем. Valgrind ( , два - небольшой how-to) - очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста - как раз те вещи, которые чаще всего всплывают при работе со строками.

Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind:

#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * strlen(HELLO_STRING)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
И, собственно, результат работы программы:

$ gcc main.c $ ./a.out -> Hello, Habr!
Пока ничего необычного. А теперь давайте запустим эту программу с valgrind!

$ valgrind --tool=memcheck ./a.out ==3892== Memcheck, a memory error detector ==3892== Copyright (C) 2002-2015, and GNU GPL"d, by Julian Seward et al. ==3892== Using Valgrind-3.12.0 and LibVEX; rerun with -h for copyright info ==3892== Command: ./a.out ==3892== ==3892== Invalid write of size 2 ==3892== at 0x4005B4: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004c is 12 bytes inside a block of size 13 alloc"d ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== ==3892== Invalid read of size 1 ==3892== at 0x4C30BC4: strlen (vg_replace_strmem.c:454) ==3892== by 0x4E89AD0: vfprintf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4E90718: printf (in /usr/lib64/libc-2.24.so) ==3892== by 0x4005CF: main (in /home/indever/prg/C/public/a.out) ==3892== Address 0x520004d is 0 bytes after a block of size 13 alloc"d ==3892== at 0x4C2DB9D: malloc (vg_replace_malloc.c:299) ==3892== by 0x400597: main (in /home/indever/prg/C/public/a.out) ==3892== -> Hello, Habr! ==3892== ==3892== HEAP SUMMARY: ==3892== in use at exit: 0 bytes in 0 blocks ==3892== total heap usage: 2 allocs, 2 frees, 1,037 bytes allocated ==3892== ==3892== All heap blocks were freed -- no leaks are possible ==3892== ==3892== For counts of detected and suppressed errors, rerun with: -v ==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
==3892== All heap blocks were freed - no leaks are possible - утечек нет, и это радует. Но стоит опустить глаза чуть пониже (хотя, хочу заметить, это лишь итог, основная информация немного в другом месте):

==3892== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
3 ошибки. В 2х контекстах. В такой простой программе. Как!?

Да очень просто. Весь «прикол» в том, что функция strlen не учитывает символ конца строки - "\0". Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!\n\0»), он будет проигнорирован.

Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы.

Собственно, правильная версия программы будет выглядеть так:

#include #include #include #define HELLO_STRING "Hello, Habr!\n" void main() { char *str = malloc(sizeof(char) * (strlen(HELLO_STRING) + 1)); strcpy(str, HELLO_STRING); printf("->\t%s", str); free(str); }
Пропускаем через valgrind:

$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==3435== ==3435== HEAP SUMMARY: ==3435== in use at exit: 0 bytes in 0 blocks ==3435== total heap usage: 2 allocs, 2 frees, 1,038 bytes allocated ==3435== ==3435== All heap blocks were freed -- no leaks are possible ==3435== ==3435== For counts of detected and suppressed errors, rerun with: -v ==3435== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Отлично. Ошибок нет, +1 байт выделяемой памяти помог решить проблему.

Что интересно, в большинстве случаев и первая и вторая программа будут работать одинаково, но если память, выделенная под строку, в которую не влез символ окончания, не была занулена, то функция printf(), при выводе такой строки, выведет и весь мусор после этой строки - будет выведено все, пока на пути printf() не встанет символ окончания строки.

Однако, знаете, (strlen(str) + 1) - такое себе решение. Перед нами встают 2 проблемы:

  1. А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
  2. Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.
Давайте придумаем такое решение, которое удовлетворит и нас, и valgrind.

snprintf()

int snprintf(char *str, size_t size, const char *format, ...); - функция - расширение sprintf, которая форматирует строку и записывает ее по указателю, переданному в качестве первого аргумента. От sprintf() она отличается тем, что в str не будет записано байт больше, чем указано в size.

Функция имеет одну интересную особенность - она в любом случае возвращает размер формируемой строки (без учета символа конца строки). Если строка пустая, то возвращается 0.

Одна из описанных мною проблем использования strlen связана с функциями sprintf() и snprintf(). Предположим, что нам надо что-то записать в строку str. Конечная строка содержит значения других переменных. Наша запись должна быть примерно такой:

Char * str = /* тут аллоцируем память */; sprintf(str, "Hello, %s\n", "Habr!");
Встает вопрос: как определить, сколько памяти надо выделить под строку str?

Char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1)); - не прокатит. Прототип функции strlen() выглядит так:

#include size_t strlen(const char *s);
const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.

Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:

#include #include #include void main() { /* Т.к. snprintf() не учитывает символ конца строки, прибавляем его размер к результату */ size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0"); char *str = malloc(needed_mem); snprintf(str, needed_mem, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
Запускаем программу в valgrind:

$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==4132== ==4132== HEAP SUMMARY: ==4132== in use at exit: 0 bytes in 0 blocks ==4132== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==4132== ==4132== All heap blocks were freed -- no leaks are possible ==4132== ==4132== For counts of detected and suppressed errors, rerun with: -v ==4132== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0) $
Отлично. Поддержка аргументов у нас есть. Благодаря тому, что мы в качестве второго аргумента в функцию snprintf() передаем ноль, запись по нулевому указателю никогда не приведет к Seagfault. Однако, несмотря на это функция все равно вернет необходимый под строку размер.

Но с другой стороны, нам пришлось завести дополнительную переменную, да и конструкция

Size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0");
выглядит еще хуже, чем в случае с strlen().

Вообще, + sizeof("\0") можно убрать, если в конце строки формата явно указать "\0" (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!\n\0 », «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт).

Надо что-то сделать. Я немного подумал и решил, что сейчас настал час воззвать к мудрости древних. Опишем макрофункцию, которая будет вызывать snprintf() с нулевым указателем в качестве первого аргумента, и нулем, в качестве второго. Да и про конец строки не забудем!

#define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0")
Да, возможно, для кого-то будет новостью, но макросы в си поддерживают переменное количество аргументов, и троеточие говорит препроцессору о том, что указанному аргументу макрофункции (в нашем случае это args) соответствует несколько реальных аргументов.

Проверим наше решение на практике:

#include #include #include #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0") void main() { char *str = malloc(strsize("Hello, %s\n", "Habr!")); sprintf(str, "Hello, %s\n", "Habr!"); printf("->\t%s", str); free(str); }
Запускаем с valgrund:

$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6432== ==6432== HEAP SUMMARY: ==6432== in use at exit: 0 bytes in 0 blocks ==6432== total heap usage: 2 allocs, 2 frees, 1,041 bytes allocated ==6432== ==6432== All heap blocks were freed -- no leaks are possible ==6432== ==6432== For counts of detected and suppressed errors, rerun with: -v ==6432== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Да, ошибок нет. Все корректно. И valgrind доволен, и программист наконец может пойти поспать.

Но, напоследок, скажу еще кое-что. В случае, если нам надо выделить память под какую-либо строку (даже с аргументами) есть уже полностью рабочее готовое решение .

Речь идет о функции asprintf:

#define _GNU_SOURCE /* See feature_test_macros(7) */ #include int asprintf(char **strp, const char *fmt, ...);
В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.

Наша программа, написанная с использованием asprintf() будет выглядеть так:

#include #include #include void main() { char *str; asprintf(&str, "Hello, %s!\n", "Habr"); printf("->\t%s", str); free(str); }
И, собственно, в valgrind:

$ valgrind --tool=memcheck ./a.out -> Hello, Habr! ==6674== ==6674== HEAP SUMMARY: ==6674== in use at exit: 0 bytes in 0 blocks ==6674== total heap usage: 3 allocs, 3 frees, 1,138 bytes allocated ==6674== ==6674== All heap blocks were freed -- no leaks are possible ==6674== ==6674== For counts of detected and suppressed errors, rerun with: -v ==6674== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Все отлично, но, как видите, памяти всего было выделено больше, да и alloc"ов теперь три, а не два. На слабых встраиваемых системах использование это функции нежелательно.
К тому же, если мы напишем в консоли man asprintf, то увидим:

CONFORMING TO These functions are GNU extensions, not in C or POSIX. They are also available under *BSD. The FreeBSD implementation sets strp to NULL on error.

Отсюда ясно, что данная функция доступна только в исходниках GNU.

Заключение

В заключение я хочу сказать, что работа со строками в C - это очень сложная тема, которая имеет ряд нюансов. Например, для написания «безопасного» кода при динамическом выделении памяти рекомендуется все же использовать функцию calloc() вместо malloc() - calloc забивает выделяемую память нулями. Ну или после выделения памяти использовать функцию memset(). Иначе мусор, который изначально лежал на выделяемом участке памяти, может вызвать вопросы при дебаге, а иногда и при работе со строкой.

Больше половины моих знакомых си-программистов (большинство из них - начинающие), решивших по моей просьбе задачу с выделением памяти под строки, сделали это так, что в конечном итоге это привело к ошибкам контекста. В одном случае - даже к утечке памяти (ну, забыл человек сделать free(str), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.

Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил - никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.

Я верю, что после прочтения этой статьи ваш код станет чуточку лучше:)
Удачи, Хабр!



Загрузка...