Строки в си. Введение
Работа со строками. Класс 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++), нужно подключить библиотеку
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 .
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 = 014. Сравнение частей строк. Функция 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 = 115. Получение строки с символом конца строки ‘\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
Хабра, привет!
Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики.
Разговор о программировании под 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
И, собственно, результат работы программы:
$ 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
Пропускаем через 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 проблемы:
- А если нам надо выделить память под формируемую с помощью, например, s(n)printf(..) строку? Аргументы мы не поддерживаем.
- Внешний вид. Строка с объявлением переменной выглядит просто ужасно. Некоторые ребята к malloc еще и (char *) умудряются прикручивать, будто под плюсами пишут. В программе где регулярно требуется обрабатывать строки есть смысл найти более изящное решение.
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
const char *s не подразумевает, что передаваемая в s строка может быть строкой формата с переменным количеством аргументов.
Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы:
#include
Запускаем программу в 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
Запускаем с 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
В качестве первого аргумента она принимает указатель на строку (**strp) и аллоцирует память по разыменованному указателю.
Наша программа, написанная с использованием asprintf() будет выглядеть так:
#include
И, собственно, в 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), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали.
Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил - никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код.
Я верю, что после прочтения этой статьи ваш код станет чуточку лучше:)
Удачи, Хабр!
В современном стандарте C++ определен класс с функциями и свойствами (переменными) для организации работы со строками (в классическом языке C строк как таковых нет, есть лишь массивы символов char):
#include
#include#include
Для работы со строками также нужно подключить стандартный namespace:
Using namespace std;
В противном случае придётся везде указывать описатель класса std::string вместо string .
Ниже приводится пример программы, работающей со string (в старых си-совместимых компиляторах не работает!):
#include
Основные возможности, которыми обладает класс string:
- инициализация массивом символов (строкой встроенного типа) или другим объектом типа string . Встроенный тип не обладает второй возможностью;
- копирование одной строки в другую. Для встроенного типа приходится использовать функцию strcpy() ;
- доступ к отдельным символам строки для чтения и записи. Во встроенном массиве для этого применяется операция взятия индекса или косвенная адресация с помощью указателя;
- сравнение двух строк на равенство. Для встроенного типа используются функции семейства strcmp() ;
- конкатенация (сцепление) двух строк, дающая результат либо как третью строку, либо вместо одной из исходных. Для встроенного типа применяется функция strcat() , однако чтобы получить результат в новой строке, необходимо последовательно задействовать функции strcpy() и strcat() , а также позаботиться о выделении памяти;
- встроенные средства определения длины строки (функции-члены класса size() и l ength()). Узнать длину строки встроенного типа можно только вычислением с помощью функции strlen() ;
- возможность узнать, пуста ли строка.
Рассмотрим эти базовые возможности более подробно.
Инициализация строк при описании и длина строки (не включая завершающий нуль-терминатор):
String st("Моя строка\n"); cout << "Длина " << st << ": " << st.size() << " символов, включая символ новой строки\n";
Строка может быть задана и пустой:
String st2;
Для проверки того, пуста ли строка , можно сравнить ее длину с 0:
If (! st.size()) // пустая
или применить метод empty() , возвращающий true для пустой строки и false для непустой:
If (st.empty()) // пустая
Третья форма создания строки инициализирует объект типа string другим объектом того же типа:
String st3(st);
Строка st3 инициализируется строкой st . Как мы можем убедиться, что эти строки совпадают ? Воспользуемся оператором сравнения (==):
If (st == st3) // инициализация сработала
Как скопировать одну строку в другую ? С помощью обычной операции присваивания:
St2 = st3; // копируем st3 в st2
Для сцепления строк используется операция сложения (+) или операция сложения с присваиванием (+=). Пусть даны две строки:
String s1("hello, "); string s2("world\n");
Мы можем получить третью строку, состоящую из конкатенации первых двух, таким образом:
String s3 = s1 + s2;
Если же мы хотим добавить s2 в конец s1 , мы должны написать:
S1 += s2;
Операция сложения может сцеплять объекты класса string не только между собой, но и со строками встроенного типа. Можно переписать пример, приведенный выше, так, чтобы специальные символы и знаки препинания представлялись встроенным типом char * , а значимые слова – объектами класса string:
Const char *pc = ", "; string s1("hello"); string s2("world"); string s3 = s1 + pc + s2 + "\n"; cout << endl << s3;
Подобные выражения работают потому, что компилятор "знает", как автоматически преобразовывать объекты встроенного типа в объекты класса string . Возможно и простое присваивание встроенной строки объекту string:
String s1; const char *pc = "a character array"; s1 = pc; // правильно
Обратное преобразование при этом не работает . Попытка выполнить следующую инициализацию строки встроенного типа вызовет ошибку компиляции:
Char *str = s1; // ошибка компиляции
Чтобы осуществить такое преобразование, необходимо явно вызвать функцию-член с названием c_str() ("строка Си"):
Const char *str = s1.c_str();
Функция c_str() возвращает указатель на символьный массив, содержащий строку объекта string в том виде, в каком она находилась бы во встроенном строковом типе. Ключевое слово const здесь предотвращает "опасную" в современных визуальных средах возможность непосредственной модификации содержимого объекта через указатель.
К отдельным символам объекта типа string , как и встроенного типа, можно обращаться с помощью операции взятия индекса. Вот, например, фрагмент кода, заменяющего все точки символами подчеркивания:
String str("www.disney.com"); int size = str.size(); for (int i = 0; i < size; i++) if (str[i] == ".") str[ i ] = "_"; cout << str;
Replace(str.begin(), str.end(), ".", "_");
Правда, здесь использован не метод replace класса string , а одноимённый алгоритм:
#include
Поскольку объект string ведет себя как контейнер, к нему могут применяться и другие алгоритмы. Это позволяет решать задачи, не решаемые напрямую функциями класса string .
Ниже приводится краткое описание основных операторов и функций класса string , ссылки в таблице ведут к русскоязычным описаниям в интернете. Более полный список возможностей класса string можно получить, например, в Википедии или на сайте cplusplus.com .