Символ конца строки c. Строки в си
В современном стандарте 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 .
Задание символов в строке |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
operator= |
присваивает значения строке |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
assign |
назначает символы строке |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Доступ к отдельным символам |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
at |
получение указанного символа с проверкой выхода индекса за границы |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
operator |
получение указанного символа |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
front |
получение первого символа |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
back |
получение последнего символа |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
data |
возвращает указатель на первый символ строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
c_str |
возвращает немодифицируемый массив символов С , содержащий символы строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Проверка на вместимость строки |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
empty |
проверяет, является ли строка пустой |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
size
|
возвращает количество символов в строке |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
max_size |
возвращает максимальное количество символов |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
reserve |
резервирует место под хранение |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Операции над строкой |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
clear |
очищает содержимое строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
insert |
вставка символов |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
erase |
удаление символов |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
push_back |
добавление символа в конец строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
pop_back |
удаляет последний символ |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
append |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
operator+= |
добавляет символы в конец строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
compare |
сравнивает две строки |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
replace |
заменяет каждое вхождение указанного символа |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
substr |
возвращает подстроку |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
copy |
копирует символы |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
resize |
изменяет количество хранимых символов |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
34
--- Руководство по C# --- Строки С точки зрения регулярного программирования строковый тип данных string относится к числу самых важных в C#. Этот тип определяет и поддерживает символьные строки. В целом ряде других языков программирования строка представляет собой массив символов. А в C# строки являются объектами. Следовательно, тип string относится к числу ссылочных. Построение строкСамый простой способ построить символьную строку - воспользоваться строковым литералом . Например, в следующей строке кода переменной ссылки на строку str присваивается ссылка на строковый литерал: String str = "Пример строки"; В данном случае переменная str инициализируется последовательностью символов "Пример строки". Объект типа string можно также создать из массива типа char. Например: Char chararray = {"e", "x", "a", "m", "p", "l", "e"}; string str = new string(chararray); Как только объект типа string будет создан, его можно использовать везде, где только требуется строка текста, заключенного в кавычки. Постоянство строкКак ни странно, содержимое объекта типа string не подлежит изменению. Это означает, что однажды созданную последовательность символов изменить нельзя. Но данное ограничение способствует более эффективной реализации символьных строк. Поэтому этот, на первый взгляд, очевидный недостаток на самом деле превращается в преимущество. Так, если требуется строка в качестве разновидности уже имеющейся строки, то для этой цели следует создать новую строку, содержащую все необходимые изменения. А поскольку неиспользуемые строковые объекты автоматически собираются в "мусор", то о дальнейшей судьбе ненужных строк можно даже не беспокоиться. Следует, однако, подчеркнуть, что переменные ссылки на строки (т.е. объекты типа string) подлежат изменению, а следовательно, они могут ссылаться на другой объект. Но содержимое самого объекта типа string не меняется после его создания. Рассмотрим пример: Static void addNewString() { string s = "This is my stroke"; s = "This is new stroke"; } Скомпилируем приложение и загрузим результирующую сборку в утилиту ildasm.exe . На рисунке показан CIL-код, который будет сгенерирован для метода void addNewString(): Обратите внимание на наличие многочисленных вызовов кода операции ldstr (загрузка строки). Этот код операции ldstr в CIL предусматривает выполнение загрузки нового объекта string в управляемую кучу. В результате предыдущий объект, в котором содержалось значение "This is my stroke", будет в конечном итоге удален сборщиком мусора. Работа со строкамиВ классе System.String предоставляется набор методов для определения длины символьных данных, поиска подстроки в текущей строке, преобразования символов из верхнего регистра в нижний и наоборот, и т.д. Далее мы рассмотрим этот класс более подробно. Поле, индексатор и свойство класса StringВ классе String определено единственное поле: Public static readonly string Empty; Поле Empty обозначает пустую строку, т.е. такую строку, которая не содержит символы. Этим оно отличается от пустой ссылки типа String, которая просто делается на несуществующий объект. Помимо этого, в классе String определен единственный индексатор, доступный только для чтения: Public char this { get; } Этот индексатор позволяет получить символ по указанному индексу. Индексация строк, как и массивов, начинается с нуля. Объекты типа String отличаются постоянством и не изменяются, поэтому вполне логично, что в классе String поддерживается индексатор, доступный только для чтения. И наконец, в классе String определено единственное свойство, доступное только для чтения: Public int Length { get; } Свойство Length возвращает количество символов в строке. В примере ниже показано использование индексатора и свойства Length: Using System; class Example { static void Main() { string str = "Простая строка"; // Получить длину строки и 6й символ в строке используя индексатор Console.WriteLine("Длина строки - {0}, 6й символ - "{1}"", str.Length, str); } } Операторы класса StringВ классе String перегружаются два следующих оператора: == и!=. Оператор == служит для проверки двух символьных строк на равенство. Когда оператор == применяется к ссылкам на объекты, он обычно проверяет, делаются ли обе ссылки на один и тот же объект. А когда оператор == применяется к ссылкам на объекты типа String, то на предмет равенства сравнивается содержимое самих строк. Это же относится и к оператору!=. Когда он применяется к ссылкам на объекты типа String, то на предмет неравенства сравнивается содержимое самих строк. В то же время другие операторы отношения, в том числе =, сравнивают ссылки на объекты типа String таким же образом, как и на объекты других типов. А для того чтобы проверить, является ли одна строка больше другой, следует вызвать метод Compare(), определенный в классе String. Как станет ясно дальше, во многих видах сравнения символьных строк используются сведения о культурной среде. Но это не относится к операторам == и!=. Ведь они просто сравнивают порядковые значения символов в строках. (Иными словами, они сравнивают двоичные значения символов, не видоизмененные нормами культурной среды, т.е. региональными стандартами.) Следовательно, эти операторы выполняют сравнение строк без учета регистра и настроек культурной среды. Методы класса StringВ следующей таблице перечислены некоторые наиболее интересные методы этого класса, сгруппированные по назначению:
Пример следующей программы использует несколько из вышеуказанных методов: Using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace ConsoleApplication1 { class Program { static void Main(string args) { // Сравним первые две строки string s1 = "это строка"; string s2 = "это текст, а это строка"; if (String.CompareOrdinal(s1, s2) != 0) Console.WriteLine("Строки s1 и s2 не равны"); if (String.Compare(s1, 0, s2, 13, 10, true) == 0) Console.WriteLine("При этом в них есть одинаковый текст"); // Конкатенация строк Console.WriteLine(String.Concat("\n" + "Один, два ","три, четыре")); // Поиск в строке // Первое вхождение подстроки if (s2.IndexOf("это") != -1) Console.WriteLine("Слово \"это\" найдено в строке, оно "+ "находится на: {0} позиции", s2.IndexOf("это")); // Последнее вхождение подстроки if (s2.LastIndexOf("это") != -1) Console.WriteLine("Последнее вхождение слова \"это\" находится " + "на {0} позиции", s2.LastIndexOf("это")); // Поиск из массива символов char myCh = {"Ы","х","т"}; if (s2.IndexOfAny(myCh) != -1) Console.WriteLine("Один из символов из массива ch "+ "найден в текущей строке на позиции {0}", s2.IndexOfAny(myCh)); // Определяем начинается ли строка с заданной подстроки if (s2.StartsWith("это текст") == true) Console.WriteLine("Подстрока найдена!"); // Определяем содержится ли в строке подстрока // на примере определения ОС пользователя string myOS = Environment.OSVersion.ToString(); if (myOS.Contains("NT 5.1")) Console.WriteLine("Ваша операционная система Windows XP"); else if (myOS.Contains("NT 6.1")) Console.WriteLine("Ваша операционная система Windows 7"); Console.ReadLine(); } } } Немного о сравнении строк в C#Вероятно, из всех операций обработки символьных строк чаще всего выполняется сравнение одной строки с другой. Прежде чем рассматривать какие-либо методы сравнения строк, следует подчеркнуть следующее: сравнение строк может быть выполнено в среде.NET Framework двумя основными способами: Во-первых, сравнение может отражать обычаи и нормы отдельной культурной среды, которые зачастую представляют собой настройки культурной среды, вступающие в силу при выполнении программы. Это стандартное поведение некоторых, хотя и не всех методов сравнения. И во-вторых, сравнение может быть выполнено независимо от настроек культурной среды только по порядковым значениям символов, составляющих строку. Вообще говоря, при сравнении строк без учета культурной среды используется лексикографический порядок (и лингвистические особенности), чтобы определить, является ли одна строка больше, меньше или равной другой строке. При порядковом сравнении строки просто упорядочиваются на основании невидоизмененного значения каждого символа. В силу отличий способов сравнения строк с учетом культурной среды и порядкового сравнения, а также последствий каждого такого сравнения настоятельно рекомендуется руководствоваться лучшими методиками, предлагаемыми в настоящее время корпорацией Microsoft. Ведь выбор неверного способа сравнения строк может привести к неправильной работе программы, когда она эксплуатируется в среде, отличающей от той, в которой она разработана. Выбор способа сравнения символьных строк представляет собой весьма ответственное решение. Как правило и без всяких исключений, следует выбирать сравнение строк с учетом культурной среды, если это делается для целей отображения результата пользователю (например, для вывода на экран ряда строк, отсортированных в лексикографическом порядке). Но если строки содержат фиксированную информацию, не предназначенную для видоизменения с учетом отличий в культурных средах, например, имя файла, ключевое слово, адрес веб-сайта или значение, связанное с обеспечением безопасности, то следует выбрать порядковое сравнение строк. Разумеется, особенности конкретного разрабатываемого приложения будут диктовать выбор подходящего способа сравнения символьных строк. В классе String предоставляются самые разные методы сравнения строк, которые перечислены в таблице выше. Наиболее универсальным среди них является метод Compare(). Он позволяет сравнивать две строки полностью или частично, с учетом или без учета регистра, способа сравнения, определяемого параметром типа StringComparison , а также сведений о культурной среде, предоставляемых с помощью параметра типа CultureInfo . Те перегружаемые варианты метода Compare(), которые не содержат параметр типа StringComparison, выполняют сравнение символьных строк с учетом регистра и культурной среды. А в тех перегружаемых его вариантах, которые не содержат параметр типа CultureInfo, сведения о культурной среде определяются текущей средой выполнения. Тип StringComparison представляет собой перечисление, в котором определяются значения, приведенные в таблице ниже. Используя эти значения, можно организовать сравнение строк, удовлетворяющее потребностям конкретного приложения. Следовательно, добавление параметра типа StringComparison расширяет возможности метода Compare() и других методов сравнения, например, Equals(). Это дает также возможность однозначно указывать способ предполагаемого сравнения строк. В силу имеющих отличий между сравнением строк с учетом культурной среды и порядковым сравнением очень важно быть предельно точным в этом отношении.
В любом случае метод Compare() возвращает отрицательное значение, если первая сравниваемая строка оказывается меньше второй; положительное значение, если первая сравниваемая строка больше второй; и наконец, нуль, если обе сравниваемые строки равны. Несмотря на то что метод Compare() возвращает нуль, если сравниваемые строки равны, для определения равенства символьных строк, как правило, лучше пользоваться методом Equals() или же оператором ==. Дело в том, что метод Compare() определяет равенство сравниваемых строк на основании порядка их сортировки. Так, если выполняется сравнение строк с учетом культурной среды, то обе строки могут оказаться одинаковыми по порядку их сортировки, но не равными по существу. По умолчанию равенство строк определяется в методе Equals(), исходя из порядковых значений символов и без учета культурной среды. Следовательно, по умолчанию обе строки сравниваются в этом методе на абсолютное, посимвольное равенство подобно тому, как это делается в операторе ==. Несмотря на большую универсальность метода Compare(), для простого порядкового сравнения символьных строк проще пользоваться методом CompareOrdinal(). И наконец, следует иметь в виду, что метод CompareTo() выполняет сравнение строк только с учетом культурной среды. В приведенной ниже программе демонстрируется применение методов Compare(), Equals(), CompareOrdinal(), а также операторов == и!= для сравнения символьных строк. Обратите внимание на то, что два первых примера сравнения наглядно демонстрируют отличия между сравнением строк с учетом культурной среды и порядковым сравнением в англоязычной среде: Using System; class Example { static void Main() { string str1 = "alpha"; string str2 = "Alpha"; string str3 = "Beta"; string str4 = "alpha"; string str5 = "alpha, beta"; int result; // Сначала продемонстрировать отличия между сравнением строк // с учетом культурной среды и порядковым сравнением result = String.Compare(str1, str2, StringComparison.CurrentCulture); Console.Write("Сравнение строк с учетом культурной среды: "); if (result 0) Console.WriteLine(str1 + " больше " + str2); else Console.WriteLine(str1 + " равно " + str2); result = String.Compare(str1, str2, StringComparison.Ordinal); Console.Write("Порядковое сравнение строк: "); if (result 0) Console.WriteLine(str1 + " больше " + str2); else Console.WriteLine(str1 + " равно " + str4); // Использовать метод CompareOrdinal() result = String.CompareOrdinal(str1, str2); Console.Write("Сравнение строк методом CompareOrdinal():\n"); if (result 0) Console.WriteLine(str1 + " больше " + str2); else Console.WriteLine(str1 + " равно " + str4); Console.WriteLine(); // Определить равенство строк с помощью оператора == // Это порядковое сравнение символьных строк if (str1 == str4) Console.WriteLine(str1 + " == " + str4); // Определить неравенство строк с помощью оператора!= if(str1 != str3) Console.WriteLine(str1 + " != " + str3); if(str1 != str2) Console.WriteLine(str1 + " != " + str2); Console.WriteLine(); // Выполнить порядковое сравнение строк без учета регистра, // используя метод Equals() if(String.Equals(str1, str2, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("Сравнение строк методом Equals() с " + "параметром OrdinalIgnoreCase:\n" + str1 + " равно " + str2); Console.WriteLine (); // Сравнить части строк if(String.Compare(str2, 0, str5, 0, 3, StringComparison.CurrentCulture) > 0) { Console.WriteLine("Сравнение строк с учетом текущей культурной среды:" + "\n3 первых символа строки " + str2 + " больше, чем 3 первых символа строки " + str5); } } } Выполнение этой программы приводит к следующему результату: Хабра, привет! Не так давно у со мной произошел довольно-таки интересный инцидент, в котором был замешан один из преподавателей одного колледжа информатики. Разговор о программировании под Linux медленно перешел к тому, что этот человек стал утверждать, что сложность системного программирования на самом деле сильно преувеличена. Что язык Си прост как спичка, собственно как и ядро Linux (с его слов). У меня был с собой ноутбук с Linux, на котором присутствовал джентльменский набор утилит для разработки на языке Си (gcc, vim, make, valgrind, gdb). Я уже не помню, какую цель мы тогда перед собой поставили, но через пару минут мой оппонент оказался за этим ноутбуком, полностью готовый решать задачу. И буквально на первых же строках он допустил серьезную ошибку при аллоцировании памяти под… строку. Char *str = (char *)malloc(sizeof(char) * strlen(buffer));
Я думаю, определенно найдутся люди, которые спросят: «Разве что-то тут может быть не так?». А что именно - читайте по катом. Немного теории - своеобразный ЛикБез.Если знаете - листайте до следующего хэдера.Строка в C - это массив символов, который по-хорошему всегда должен заканчиваться "\0" - символом конца строки. Строки на стеке (статичные) объявляются вот так: Char str[n] = { 0 };
Присваивание { 0 } - «зануление» строки (опционально, объявлять можно и без него). Результат такой же, как у выполнения функций memset(str, 0, sizeof(str)) и bzero(str, sizeof(str)). Используется, чтобы в неинициализированных переменных не валялся мусор. Так же на стеке можно сразу проинициализировать строку: Char buf = "default buffer text\n";
Char *str = malloc(size);
В случае со стековой переменной, для определения размера массива я использовал обозначение n, в случае с переменной на куче - я использовал обозначение size. И это прекрасно отражает истинную суть отличия объявления на стеке от объявление с аллоцированием памяти на куче, ведь n как правило используется тогда, когда говорят о количестве элементов. А size - это уже совсем другая история… Нам поможет valgrindВ своей предыдущей статье я также упоминал о нем. Valgrind ( , два - небольшой how-to) - очень полезная программа, которая помогает программисту отслеживать утечки памяти и ошибки контекста - как раз те вещи, которые чаще всего всплывают при работе со строками.Давайте рассмотрим небольшой листинг, в котором реализовано что-то похожее на упомянутую мной программу, и прогоним ее через valgrind: #include $ gcc main.c
$ ./a.out
-> Hello, Habr!
$ 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== ERROR SUMMARY: 3 errors from 2 contexts (suppressed: 0 from 0)
Да очень просто. Весь «прикол» в том, что функция strlen не учитывает символ конца строки - "\0". Даже если его явно указать во входящей строке (#define HELLO_STRING «Hello, Habr!\n\0»), он будет проигнорирован. Чуть выше результата исполнения программы, строки -> Hello, Habr! есть подробный отчет, что и где не понравилось нашему драгоценному valgrind. Предлагаю самостоятельно посмотреть эти строчки и сделать выводы. Собственно, правильная версия программы будет выглядеть так: #include $ 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)
Что интересно, в большинстве случаев и первая и вторая программа будут работать одинаково, но если память, выделенная под строку, в которую не влез символ окончания, не была занулена, то функция printf(), при выводе такой строки, выведет и весь мусор после этой строки - будет выведено все, пока на пути printf() не встанет символ окончания строки. Однако, знаете, (strlen(str) + 1) - такое себе решение. Перед нами встают 2 проблемы:
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!");
Char * str = malloc(sizeof(char) * (strlen(str, "Hello, %s\n", "Habr!") + 1)); - не прокатит. Прототип функции strlen() выглядит так: #include Тут нам поможет то полезное свойство функции snprintf(), о котором я говорил выше. Давайте посмотрим на код следующей программы: #include $ 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)
$
Но с другой стороны, нам пришлось завести дополнительную переменную, да и конструкция Size_t needed_mem = snprintf(NULL, 0, "Hello, %s!\n", "Habr") + sizeof("\0");
Вообще, + sizeof("\0") можно убрать, если в конце строки формата явно указать "\0" (size_t needed_mem = snprintf(NULL, 0, «Hello, %s!\n\0
», «Habr»);), но это возможно отнюдь не всегда (в зависимости от механизма обработки строк мы можем выделить лишний байт). #define strsize(args...) snprintf(NULL, 0, args) + sizeof("\0")
Проверим наше решение на практике: #include $ 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)
Но, напоследок, скажу еще кое-что. В случае, если нам надо выделить память под какую-либо строку (даже с аргументами) есть уже полностью рабочее готовое решение . Речь идет о функции asprintf: #define _GNU_SOURCE /* See feature_test_macros(7) */
#include Наша программа, написанная с использованием asprintf() будет выглядеть так: #include $ 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)
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), с кем не бывает). Собственно говоря, это и сподвигло меня на создание сего творения, которое вы только что прочитали. Я надеюсь, кому-то эта статья будет полезной. К чему я это все городил - никакой язык не бывает прост. Везде есть свои тонкости. И чем больше тонкостей языка вы знаете, тем лучше ваш код. Я верю, что после прочтения этой статьи ваш код станет чуточку лучше:) Строки. Ввод-вывод строк. Форматированный ввод-вывод. Обработка строк с использованием стандартных функций языка С. Работа с памятью. 1.1. Объявление и инициализация строк. Строкой называется массив символов, который заканчивается пустым символом ‘\0’. Строка объявляется как обычный символьный массив, например, char s1; // строка длиной в девять символов char *s2; // указатель на строку Различие между указателями s1 и s2 заключается в том, что указатель s1 является именованной константой, а указатель s2 – переменной. Строковые константы заключаются в двойные кавычки в отличие от символов, которые заключаются в одинарные кавычки. Например, “This is a string.” Длина строковой константы не может превышать 509 символов по стандарту. Однако, многие реализации допускают строки большей длины. При инициализации строк размерность массива лучше не указывать, это выполнит компилятор, подсчитав длину строки и добавив к ней единицу. Например, char s1 = “This is a string.”; В языке программирования С для работы со строками существует большое количество функций, прототипы которых описаны в заголовочных файлах stdlib.h и string.h. Работа с этими функциями будет рассмотрена в следующих параграфах. 1.2. Ввод-вывод строк. Для ввода строки с консоли служит функция char* gets (char *str); которая записывает строку по адресу str и возвращает адрес введенной строки. Функция прекращает ввод, если встретит символ ‘\n’ или EOF (конец файла). Символ перехода на новую строку не копируется. В конец прочитанной строки помещается нулевой байт. В случае успеха функция возвращает указатель на прочитанную строку, а в случае неудачи NULL. Для вывода строки на консоль служит стандартная функция int puts (const char *s); которая в случае удачи возвращает неотрицательное число, а в случае неудачи – EOF. Прототипы функций gets и puts описаны в заголовочном файле stdio.h. #include printf("Input String: "); 1.3. Форматированный ввод-вывод. Для форматированного ввода данных с консоли используется функция int scanf (const char *format, …); которая в случае успешного завершения возвращает количество единиц прочитанных данных, а в случае неудачи – EOF. Параметр format должен указывать на форматируемую строку, которая содержит спецификации форматов ввода. Количество и типы аргументов, которые следуют после строки форматирования, должны соответствовать количеству и типам форматов ввода, заданным в строке форматирования. Если это условие не выполняется, то результат работы функции непредсказуем. Пробел, символы "\t" или "\n" в форматной строке описывают один или более пустых символов во входном потоке, к которым относятся символы: пробел, ‘\t’, ‘\n’, ‘\v’, ‘\f’. Функция scanf пропускает пустые символы во входном потоке. Литеральные символы в форматной строке, за исключением символа %, требуют, чтобы во входном потоке появились точно такие же символы. Если такого символа нет, то функция scanf прекращает ввод. Функция scanf пропускает литеральные символы. В общем случае спецификация формата ввода имеет вид: %[*] [ширина] [модификаторы] тип Символ ‘*’ обозначает пропуск при вводе поля, определенного данной спецификацией; - ‘ширина’ определяет максимальное число символов, вводимых по данной спецификации; Тип может принимать следующие значения: c – символьный массив, s – строка символов, строки разделяются пустыми символами, d – целое число со знаком в 10 с/c, i – целое число со знаком, система счисления завит от двух первых цифр, u – целое число без знака в 10 с/с, o – целое число без знака в 8 с/c, х, Х – целое число без знака в 16 с/с, e, E, f, g, G – плавающее число, p – указатель на указатель, n – указатель на целое, […] – массив сканируемых символов, например, . В последнем случае из входного потока будут вводиться только символы, заключенные в квадратные скобки. Если первый символ внутри квадратных скобок равен ‘^’, то вводятся только те символы, которые не входят в массив. Диапазон символов в массиве задается через символ ‘-‘. При вводе символов ведущие пустые символы и завершающий нулевой байт строки также вводятся. Модификаторы могут принимать следующие значения: h – короткое целое, l, L – длинное целое или плавающее, и используются только для целых или плавающих чисел. В следующем примере показаны варианты использования функции scanf. Обратите внимание, что перед спецификатором формата, начиная с ввода плавающего числа, стоит символ пробел. #include printf("Input an integer: "); scanf("%d", &n); printf("Input a double: "); scanf(" %lf", &d); printf("Input a char: "); scanf(" %c", &c); printf("Input a string: "); scanf(" %s", &s); Обратите внимание, что в этой программе число с плавающей точкой проинициализировано. Это сделано для того, чтобы компилятор подключил библиотеку для поддержки работы с плавающими числами. Если этого не сделать, то на этапе выполнения при вводе плавающего числа произойдет ошибка. Для форматированного вывода данных на консоль используется функция int printf (const char *format, …); которая в случае успешного завершения возвращает количество единиц выведенных данных, а в случае неудачи – EOF. Параметр format представляет собой форматируемую строку, которая содержит спецификации форматов вывода. Количество и типы аргументов, которые следуют после строки форматирования, должны соответствовать количеству и типам спецификациям формата вывода, заданным в строке форматирования. В общем случае спецификация формата вывода имеет вид: %[флаги] [ширина] [.точность] [модификаторы] тип - ‘флаги’ – это различные символы, уточняющие формат вывода; - ‘ширина’ определяет минимальное количество символов, выводимых по данной спецификации; - ‘.точность’ определяет максимальное число выводимых символов; - ‘модификаторы’ уточняют тип аргументов; - ‘тип’ определяет тип аргумента. Для вывода целых чисел со знаком используется следующий формат вывода: %[-] [+ | пробел] [ширина] [l] d - – выравнивание влево, по умолчанию – вправо; + – выводится знак ‘+’, заметим, что для отрицательных чисел всегда выводится знак ‘-‘; ‘пробел’ – в позиции знака выводится пробел; d – тип данных int. Для вывода целых чисел без знака используется следующий формат вывода: %[-] [#] [ширина] [l] # – выводится начальный 0 для чисел в 8 c/c или начальные 0x или 0X для чисел в 16 c/c, l – модификатор типа данных long; u – целое число в 10c/c, o – целое число в 8 c/c, x, X – целое число в 16 c/c. Для вывода чисел с плавающей точкой используется следующий формат вывода: %[-] [+ | пробел] [ширина] [.точность] "точность" – обозначает число цифр после десятичной точки для форматов f, e и E или число значащих цифр для форматов g и G. Числа округляются отбрасыванием. По умолчанию принимается точность в шесть десятичных цифр; f – число с фиксированной точкой, e – число в экспоненциальной форме, экспонента обозначается буквой "e", E – число в экспоненциальной форме, экспонента обозначается буквой "E", g – наиболее короткий из форматов f или g, G – наиболее короткий из форматов f или G. printf ("n = %d\n f = %f\n e = %e\n E = %E\n f = %.2f", -123, 12.34, 12.34, 12.34, 12.34); // печатает: n = 123 f = 12.340000 e = 1.234000e+001 E = 1.234000E+001 f = 12.34 1.4. Форматирование строк. Существуют варианты функций scanf и printf, которые предназначены для форматирования строк и называются соответственно sscanf и sprintf. int sscanf (const char *str, const char *format, …); читает данные из строки, заданной параметром str, в соответствии с форматной строкой, заданной параметром format. В случае удачи возвращает количество прочитанных данных, а в случае неудачи – EOF. Например, #include char str = "a 10 1.2 String No input"; sscanf(str, "%c %d %lf %s", &c, &n, &d, s); printf("%c\n", c); // печатает: a printf("%d\n", n); // печатает: 10 printf("%f\n", d); // печатает: 1.200000 printf("%s\n", s); // печатает: String int sprintf (char *buffer, const char *format, …); форматирует строку в соответствии с форматом, который задан параметром format и записывает полученный результат в символьный массив buffer. Возвращает функция количество символов, записанных в символьный массив buffer, исключая завершающий нулевой байт. Например, #include char str = "c = %c, n = %d, d = %f, s = %s"; char s = "This is a string."; sprintf(buffer, str, c, n, d, s); printf("%s\n", buffer); // печатает: c = c, n = 10, d = 1.200000, s = This is a string 1.5. Преобразование строк в числовые данные. Прототипы функций преобразования строк в числовые данные приведены в заголовочном файле stdlib.h, который нужно включить в программу. Для преобразования строки в целое число используется функция int atoi (const char *str); char *str = “-123”; n = atoi (str); // n = -123 Для преобразования строки в длинное целое число используется функция long int atol (const char *str); которая в случае успешного завершения возвращает целое число, в которое преобразована строка str, а в случае – неудачи 0. Например, char *str = “-123”; n = atol (str); // n = -123 Для преобразования строки в число типа double используется функция double atof (const char *str); которая в случае успешного завершения возвращает плавающее число типа double, в которое преобразована строка str, а в случае – неудачи 0. Например, char *str = “-123.321”; n = atof (str); // n = -123.321 Следующие функции выполняют действия, аналогичные функциям atoi, atol, atof, но предоставляют более широкие возможности. long int strtol (const char *str, char **endptr, int base); преобразует строку str в число типа long int, которое и возвращает. Параметры этой функции имеют следующее назначение. Если аргумент base равен 0, то преобразование зависит от первых двух символов строки str: Если первый символ – цифра от 1 до 9, то предполагается, что число представлено в 10 c/c; Если первый символ – цифра 0, а второй – цифра от 1 до 7, то предполагается, что число представлено в 8 c/c; Если первый символ 0, а второй – ‘Х’ или ‘х’, то предполагается, что число представлено в 16 c/c. Если аргумент base равен числу от 2 до 36, то это значение принимается за основание системы счисления и любой символ, выходящий за рамки этой системы, прекращает преобразование. В системах счисления с основанием от 11 до 36 для обозначения цифр используются символы от ‘A’ до ‘Z’ или от ‘a’ до ‘z’. Значение аргумента endptr устанавливается функцией strtol. Это значение содержит указатель на символ, который остановил преобразование строки str. В случае успешного завершения функция strtol возвращает преобразованное число, а в случае неудачи – 0. Например, n = strtol (“12a”, &p, 0); printf (“ n = %ld, %stop = %c, n, *p); // n = 12, stop = a n = strtol (“012b”, &p, 0); printf (“ n = %ld, %stop = %c, n, *p); // n = 10, stop = b n = strtol (“0x12z”, &p, 0); printf (“ n = %ld, %stop = %c, n, *p); // n = 18, stop = z n = strtol (“01117”, &p, 0); printf (“ n = %ld, %stop = %c, n, *p); // n = 7, stop = 7 unsigned long int strtol (const char *str, char **endptr, int base); работает аналогично функции strtol, но преобразует символьное представление числа в число типа unsigned long int. double strtod (const char *str, char **endptr); преобразует символьное представление числа в число типа double. Все функции, перечисленные в этом параграфе, прекращают свою работу при встрече первого символа, который не подходит под формат рассматриваемого числа. Кроме того, в случае если символьное значение числа превосходит диапазон допустимых значений для соответствующего типа данных, то функции atof, strtol, strtoul, strtod устанавливают значение переменной errno в ERANGE. Переменная errno и константа ERANGE определены в заголовочном файле math.h. При этом функции atof и strtod возвращают значение HUGE_VAL, функция strtol возвращает значение LONG_MAX или LONG_MIN, а функция strtoul – значение ULONG_MAX. Для преобразования числовых данных в символьные строки могут использоваться нестандартные функции itoa, ltoa, utoa, ecvt, fcvt и gcvt. Но лучше для этих целей использовать стандартную функцию sprintf. 1.6. Стандартные функции для работы со строками. В этом параграфе рассмотрены функции для работы со строками, прототипы которых описаны в заголовочном файле string.h. 1. Сравнение строк. Для сравнения строк используются функции strcmp и strncmp. int strcmp (const char *str1, const char *str2); лексикографически сравнивает строки str1, str2 и возвращает –1, 0 или 1, если строка str1 соответственно меньше, равна или больше строки str2. int strncmp (const char *str1, const char *str2, size_t n); лексикографически сравнивает не более чем n первых символов из строк str1 и str2. Функция возвращает –1, 0 или 1, если первые n символов из строки str1 соответственно меньше, равны или больше первых n символов из строки str2. // пример сравнения строк #include #include char str1 = "aa bb"; char str2 = "aa aa"; char str3 = "aa bb cc"; printf("%d\n", strcmp(str1, str3)); // печатает: -1 printf("%d\n", strcmp(str1, str1)); // печатает: -0 printf("%d\n", strcmp(str1, str2)); // печатает: 1 printf("%d\n", strncmp(str1, str3, 5)); // печатает: 0 2. Копирование строк. Для копирования строк используются функции strcpy и strncpy. char *strcpy (char *str1, const char *str2); копирует строку str2 в строку str1. Строка str2 копируется полностью, включая завершающий нулевой байт. Функция возвращает указатель на str1. Если строки перекрываются, то результат непредсказуем. char *strncpy (char *str1, const char *str2, size_t n); копирует n символов из строки str2 в строку str1. Если строка str2 содержит меньше чем n символов, то последний нулевой байт копируется столько раз, сколько нужно для расширения строки str2 до n символов. Функция возвращает указатель на строку str1. char str2 = "Copy string."; strcpy (str1, str2); printf (str1); // печатает: Copy string. 4. Соединение строк. Для соединения строк в одну строку используются функции strcat и strncat. char* strcat (char *str1, const char *str2); присоединяет строку str2 к строке str1, причем завершающий нулевой байт строки str1 стирается. Функция возвращает указатель на строку str1. char* strncat (char *str1, const char *str2, size_t n); присоединяет n символов из строки str2 к строке str1, причем завершающий нулевой байт строки str1 стирается. Функция возвращает указатель на строку str1. если длина строки str2 меньше n, то присоединяются только символы, входящие в строку str2. После соединения строк к строке str1 всегда добавляется нулевой байт. Функция возвращает указатель на строку str1. #include #include char str1 = "String "; char str2 = "catenation "; char str3 = "Yes No"; strcat (str1, str2); printf ("%s\n", str1); // печатает: String catenation strncat (str1, str3, 3); printf ("%s\n", str1); // печатает: String catenation Yes 5. Поиск символа в строке. Для поиска символа в строке используются функции strchr, strrchr, strspn, strcspn и strpbrk. char* strchr (const char *str, int c); ищет первое вхождение символа, заданного параметром c, в строку str. В случае успеха функция возвращает указатель на первый найденный символ, а в случае неудачи – NULL. char* strrchr (const char *str, int c); ищет последнее вхождение символа, заданного параметром c, в строку str. В случае успеха функция возвращает указатель на последний найденный символ, а в случае неудачи – NULL. #include #include char str = "Char search"; printf ("%s\n", strchr (str, "r")); // печатает: r search printf ("%s\n", strrchr (str, "r")); // печатает: rch size_t strspn (const char *str1, const char *str2); возвращает индекс первого символа из строки str1, который не входит в строку str2. size_t strcspn (const char *str1, const char *str2); возвращает индекс первого символа из строки str1, который входит в строку str2. char str = "123 abc"; printf ("n = %d\n", strspn (str, "321"); // печатает: n = 3 printf ("n = %d\n", strcspn (str, "cba"); // печатает: n = 4 char* strpbrk (const char *str1, const char *str2); находит первый символ в строке str1, который равен одному из символов в строке str2. В случае успеха функция возвращает указатель на этот символ, а в случае неудачи – NULL. char str = "123 abc"; printf ("%s\n", strpbrk (str, "bca")); // печатает: abc 6. Сравнение строк. Для сравнения строк используются функция strstr. char* strstr (const char *str1, const char *str2); находит первое вхождение строки str2 (без конечного нулевого байта) в строку str1. В случае успеха функция возвращает указатель на найденную подстроку, а в случае неудачи – NULL. Если указатель str1 указывает на строку нулевой длины, то функция возвращает указатель str1. char str = "123 abc 456; printf ("%s\n", strstr (str, "abc"); // печать: abc 456 7. Разбор строки на лексемы. Для разбора строки на лексемы используется функция strtok. char* strtok (char *str1, const char *str2); возвращает указатель на следующую лексему (слово) в строке str1, в которой разделителями лексем являются символы из строки str2. В случае если лексемы закончились, то функция возвращает NULL. При первом вызове функции strtok параметр str1 должен указывать на строку, которая разбирается на лексемы, а при последующих вызовах этот параметр должен быть установлен в NULL. После нахождения лексемы функция strtok записывает после этой лексемы на место разделителя нулевой байт. #include #include char str = "12 34 ab cd"; p = strtok (str, " "); printf ("%s\n", p); // печатает в столбик значения: 12 34 ab cd p = strtok (NULL, " "); 8. Определение длины строки. Для определения длины строки используется функция strlen. size_t strlen (const char *str); возвращает длину строки, не учитывая последний нулевой байт. Например, char str = "123"; printf ("len = %d\n", strlen (str)); // печатает: len = 3 1.7. Функции для работы с памятью. В заголовочном файле string.h описаны также функции для работы с блоками памяти, которые аналогичны соответствующим функциям для работы со строками. void* memchr (const void *str, int c, size_t n); ищет первое вхождение символа, заданного параметром c, в n байтах строки str. int memcmp (const void *str1, const void *str2, size_t n); сравнивает первые n байт строк str1 и str2. void* memcpy (const void *str1, const void *str2, size_t n); копирует первые n байт из строки str1 в строку str2. void* memmove (const void *str1, const void *str2, size_t n); копирует первые n байт из строки str1 в строку str2, обеспечивая корректную обработку перекрывающихся строк. void* memset (const void *str, int c, size_t n); копирует символ, заданный параметром c, в первые n байтов строки str. Последнее обновление: 31.10.2015 КонкатенацияКонкатенация строк или объединение может производиться как с помощью операции + , так и с помощью метода Concat: String s1 = "hello"; string s2 = "world"; string s3 = s1 + " " + s2; // результат: строка "hello world" string s4 = String.Concat(s3, "!!!"); // результат: строка "hello world!!!" Console.WriteLine(s4); Метод Concat является статическим методом класса String, принимающим в качестве параметров две строки. Также имеются другие версии метода, принимающие другое количество параметров. Для объединения строк также может использоваться метод Join: String s5 = "apple"; string s6 = "a day"; string s7 = "keeps"; string s8 = "a doctor"; string s9 = "away"; string values = new string { s5, s6, s7, s8, s9 }; String s10 = String.Join(" ", values); // результат: строка "apple a day keeps a doctor away" Метод Join также является статическим. Использованная выше версия метода получает два параметра: строку-разделитель (в данном случае пробел) и массив строк, которые будут соединяться и разделяться разделителем. Сравнение строкДля сравнения строк применяется статический метод Compare: String s1 = "hello"; string s2 = "world"; int result = String.Compare(s1, s2); if (result<0) { Console.WriteLine("Строка s1 перед строкой s2"); } else if (result > 0) { Console.WriteLine("Строка s1 стоит после строки s2"); } else { Console.WriteLine("Строки s1 и s2 идентичны"); } // результатом будет "Строка s1 перед строкой s2" Данная версия метода Compare принимает две строки и возвращает число. Если первая строка по алфавиту стоит выше второй, то возвращается число меньше нуля. В противном случае возвращается число больше нуля. И третий случай - если строки равны, то возвращается число 0. В данном случае так как символ h по алфавиту стоит выше символа w, то и первая строка будет стоять выше. Поиск в строкеС помощью метода IndexOf мы можем определить индекс первого вхождения отдельного символа или подстроки в строке: String s1 = "hello world"; char ch = "o"; int indexOfChar = s1.IndexOf(ch); // равно 4 Console.WriteLine(indexOfChar); string subString = "wor"; int indexOfSubstring = s1.IndexOf(subString); // равно 6 Console.WriteLine(indexOfSubstring); Подобным образом действует метод LastIndexOf , только находит индекс последнего вхождения символа или подстроки в строку. Еще одна группа методов позволяет узнать начинается или заканчивается ли строка на определенную подстроку. Для этого предназначены методы StartsWith и EndsWith . Например, у нас есть задача удалить из папки все файлы с расширением exe: String path = @"C:\SomeDir"; string files = Directory.GetFiles(path); for (int i = 0; i < files.Length; i++) { if(files[i].EndsWith(".exe")) File.Delete(files[i]); } Разделение строкС помощью функции Split мы можем разделить строку на массив подстрок. В качестве параметра функция Split принимает массив символов или строк, которые и будут служить разделителями. Например, подсчитаем количество слов в сроке, разделив ее по пробельным символам: String text = "И поэтому все так произошло"; string words = text.Split(new char { " " }); foreach (string s in words) { Console.WriteLine(s); } Это не лучший способ разделения по пробелам, так как во входной строке у нас могло бы быть несколько подряд идущих пробелов и в итоговый массив также бы попадали пробелы, поэтому лучше использовать другую версию метода: String words = text.Split(new char { " " }, StringSplitOptions.RemoveEmptyEntries); Второй параметр StringSplitOptions.RemoveEmptyEntries говорит, что надо удалить все пустые подстроки. Обрезка строкиДля обрезки начальных или концевых символов используется функция Trim: String text = " hello world "; text = text.Trim(); // результат "hello world" text = text.Trim(new char { "d", "h" }); // результат "ello worl" Функция Trim без параметров обрезает начальные и конечные пробелы и возвращает обрезанную строку. Чтобы явным образом указать, какие начальные и конечные символы следует обрезать, мы можем передать в функцию массив этих символов. Эта функция имеет частичные аналоги: функция TrimStart обрезает начальные символы, а функция TrimEnd обрезает конечные символы. Обрезать определенную часть строки позволяет функция Substring : String text = "Хороший день"; // обрезаем начиная с третьего символа text = text.Substring(2); // результат "роший день" Console.WriteLine(text); // обрезаем сначала до последних двух символов text = text.Substring(0, text.Length - 2); // результат "роший де" Console.WriteLine(text); Функция Substring также возвращает обрезанную строку. В качестве параметра первая использованная версия применяет индекс, начиная с которого надо обрезать строку. Вторая версия применяет два параметра - индекс начала обрезки и длину вырезаемой части строки. ВставкаДля вставки одной строки в другую применяется функция Insert: String text = "Хороший день"; string subString = "замечательный "; text = text.Insert(8, subString); Console.WriteLine(text); Первым параметром в функции Insert является индекс, по которому надо вставлять подстроку, а второй параметр - собственно подстрока. Удаление строкУдалить часть строки помогает метод Remove: String text = "Хороший день"; // индекс последнего символа int ind = text.Length - 1; // вырезаем последний символ text = text.Remove(ind); Console.WriteLine(text); // вырезаем первые два символа text = text.Remove(0, 2); Первая версия метода Remove принимает индекс в строке, начиная с которого надо удалить все символы. Вторая версия принимает еще один параметр - сколько символов надо удалить. ЗаменаЧтобы заменить один символ или подстроку на другую, применяется метод Replace : String text = "хороший день"; text = text.Replace("хороший", "плохой"); Console.WriteLine(text); text = text.Replace("о", ""); Console.WriteLine(text); Во втором случае применения функции Replace строка из одного символа "о" заменяется на пустую строку, то есть фактически удаляется из текста. Подобным способом легко удалять какой-то определенный текст в строках. Смена регистраДля приведения строки к верхнему и нижнему регистру используются соответственно функции ToUpper() и ToLower() : String hello = "Hello world!"; Console.WriteLine(hello.ToLower()); // hello world! Console.WriteLine(hello.ToUpper()); // HELLO WORLD! Статьи по теме
|