Первые два вида представления в C++, с которыми вам стоит познакомится это C-string и std::string.
Первый вариант пришёл к нам из языка C, но до сих пор используется и является неотъемлемой частью C++.
Второй вариант, забегая вперёд является классом и взаимодействие с ним происходит, в том числе, через методы класса. Проблема заключается в том что сейчас вы не знаете что такое класс и тем более о других элементах стандартной библиотеки, которым является std::string.
Так что разобрать си-строки вместе std::string и сделать это сейчас - это логичное, хотя и компромиссное решение.
C-string
Это самый стандартный тип строк, который использовался до C++ и используется сейчас в старых API и даже относительно современных библиотеках, таких как ImGui.
Основание для переноса концепции C-string обосновано необходимостью поддержки старых интерфейсов API, низкоуровневой работы и потому что литерал "Hello" в C++ по умолчанию имеет тип const char*, что важно понимать при инициализации std::string в дальнейшем.
void ImDrawList::AddText(
const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end
)
{
AddText(NULL, 0.0f, pos, col, text_begin, text_end);
}
Как мы видим в параметрах тут используется тип const char* - то есть указатель на массив типа char.
По сути C-string это обычный массив символов со всеми его свойствами.
Из этого следует, что это достаточно быстрый способ хранения текстовой информации с минимальными накладными расходами.
Но это так же означает наличие всех минусов ручной работы с памятью - опасность и неудобность.
Как вы помните длина массива в таких случаях должна хранится отдельно.
В этом случае тут используется другое решение - нулевой терминатор \0.
За счёт этого литерала обеспечивается реализация функций, где требуется длина строки.
Объявление, инициализация, ввод/вывод С-string
Мы можем объявить строку следующим образом:
char str1[] = "Hello"; // '\0' добавляется автоматически
char str2[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // то же самое явно
char str3[10] = "Hello"; // массив больше, чем нужно, остаток — '\0'
Вывод строки работает как у обычной переменной:
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
std::cout << str1 << std::endl;
Если мы забудем про \0 в конце строки то у нас может произойти такая ситуация:
char str1[5] = {'H', 'e', 'l', 'l', 'o'};
std::cout << str1 << std::endl;
Hello╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠╠↓F⌡═ ⌂
При выводе код пытается найти символ \0 завершающий строчку, но этого не происходит и вывод продолжается до первого случайно оказавшегося там нулевого байта.
Это поведение ничем не гарантированно и зависит от того где написан этот код, каким компилятором собран и ещё множества факторов.
char text[100];
std::cin >> text;
std::cout << text;
first second
first
Таким образом можно прочитать текст с клавиатуры.
Но тут у нас строка читается до первого пробела.
Если будет введено более 99 символов, то может произойти выход за пределы массивы - отсюда неопределённое поведение.
char text[100];
std::cin.width(100);
std::cin >> text;
std::cout << text;
В таких случаях стоит использовать std::cin.width.
char text[100];
std::cin.getline(text, 100);
std::cout << text;
Или же можно использовать std::cin.getline.
Если размер строки заранее неизвестен и требуется считать весь ввод, приходится реализовывать побуквенное чтение через while (std::cin.get(c) && c != '\n'), а при достижении текущего предела буфера вручную увеличивать выделенную память с помощью new, перераспределяя данные в новый массив большей длины.
Особенности работы с C-string
Нужно отметить, что строка такого типа неявно распадается в указатель на первый символ и тут имеет место быть арифметика указателей
char str1[] = "Hello";
std::cout << *str1 << '\n'; // 'H'
std::cout << *(str1 + 1) << '\n'; // 'e'
std::cout << str1[2] << '\n'; // 'l'
Из этого следует, что мы можем использовать механику обращения по индексу для доступа к элементам:
char str1[] = "Hello";
std::cout << str1[0] << '\n'; // 'H'
str1[0] = 'h';
std::cout << str1[0] << '\n'; // 'h'
Мы можем создавать строку через указатель
const char* str1 = "Hello";
str1[0] = 'h'; // expression must be a modifiable value
Но в этом случае мы не можем изменять данные, потому что память выделяется в read-only секции памяти.
char* str1 = "Hello";
// a value of type "const char *" cannot be used
// to initialize an entity of type "char *"
Убрать модификатор const мы не можем, потому что изначально литералы такого типа имеют тип const char*.
int main() {
const char* str1 = "Hello";
std::cout << sizeof(str1) << std::endl;
char str2[] = "Hello";
std::cout << sizeof(str2) << std::endl;
}
8
6
Тут мы получаем разные результаты выполнения оператора sizeof.
В первом случае переменная str1 имеет тип const char*, поскольку приложение скомпилировано в x64. Массив неявно преобразуется в указатель на первый элемент.
Во втором случае переменная str2 имеет тип char[6] и по этому мы получаем sizeof(char) * 6.
То есть только в ряде случаев мы можем получить длину строки таким образом.
Функции для работы со C-string
Для операций мы можем использовать функции strlen, strcpy, strcmp из заголовочного файла <cstring>.
#include <iostream>
int main() {
const char* str1 = "Hello";
printf("%zu", strlen(str1));
}
5
Тут код у нас компилируется без ошибки, по крайней мере у меня, при использования MSVC в режиме x64.
Причиной этому, по всей видимости, является то, что <cstring> используется внутри <iostream>.
Это не гарантируется стандартом, но конкретная реализация может так сделать.
#include <cstdio>
int main() {
const char* str1 = "Hello";
printf("%zu", strlen(str1)); // 'strlen': identifier not found
}
А вот так уже получаем ошибку без #include <cstring>.
Как вы уже поняли функция strlen используется для получения длинны строки.
#include <iostream>
int main() {
char source[] = "Hello";
char target[10];
strcpy_s(target, source);
std::cout << target;
}
Функция strcpy копирует строку. Тут мы используем strcpy_s вместо strcpy из-за особенностей компилятора по аналогии со scanf и scanf_s.
char a[] = "Hello";
char b[] = "Hello";
std::cout << (a == b) << std::endl;
0
Мы не можем "в лоб" сравнивать строки, потому что это фактически сравнение адресов указателей.
char a[] = "Hello";
char b[] = "Hello";
std::cout << strcmp(a, b) << std::endl;
0
Для сравнения мы используем функцию strcmp.
В этом случае мы так же получаем 0, потому что эта функция возвращает 0 в случае, если строки равны.
В случае, если строки различны, то применяется лексикографическое сравнение и результатом могут стать отличные от нуля числа.
char a[100] = "Hello ";
char b[] = "world";
std::strcat(a, b);
std::cout << a;
Hello world
Как вы догадываетесь чтобы выполнить конкатенацию строк нам будет необходимо вручную работать с каждым символом.
Тут может помочь функция strcat.
Одна из тривиальных задач связанная со строками - это преобразование в число и обратно.
Оформите подписку, чтобы иметь доступ ко всем публикациям без ограничений. Вы получите мгновенный доступ к полным версиям материалов, исходному коду и новым публикациям сразу после их выхода.
Я регулярно публикую материал и разборы ситуаций для различных игр, движков, а так же подходов разработки включая Internal, External и DMA.