Приветствую вас коллеги – программисты. В данной статье я хочу познакомить Вас с шаблонным классом string
. Который призван облегчить жизнь программистам. Кто не знает, string
– это STL’евский класс основанный на шаблонах, который входит в стандартную библиотеку C++
. Для использования данного класса в ваших приложениях, нужно подключить директиву <string>
.
Вначале посмотрим на простой пример применения, возможности и внутреннее представление класса, а в конце статьи реализуем собственный класс string
!
Если вы раньше уже имели дело со стандартным строковым типом, то могли заметить, что его применение опасно возникновением ошибок и не так удобно как хотелось бы из-за того, что реализован он на более низком уровне. Так вот, использование класса string, здорово облегчит вам процесс использования строковых типов в ваших приложениях.
Давайте рассмотрим простейший пример использования:
#include <iostream> #include <string> int main() { std::string str; std::cout << "Enter your name: "; std::getline(std::cin, str); std::cout << "Hello, " << str << "!!! \n"; return 0; }
Здесь создается экземпляр класса под именем str
, далее пользователя просят представиться, при помощи std::getline
данные записываются из потока cin
непосредственно в нашу переменную str
. Думаю далее всё очевидно.
Как видите использование класса string
, не сложнее, а поверьте даже намного легче чем обычного строкового типа char*
.
Итак не забывайте что, для того чтобы использовать объекты класса string
, необходимо включить соответствующий заголовочный файл:
#include <string>
Далее для демонстрации работы методов и вспомогательных функций будем использовать следующий объект:
std::string str;
Чтобы узнать длину строки, можно воспользоваться функцией-членом size()
, или length()
, которые, как и ожидается, возвращают саму длину (длина не включает завершающий нулевой символ).
str.size(); str.length();
А как же узнать, пуста ли строка? Вы скажете у нас же есть метод возвращающий длину, следовательно можно применить следующую конструкцию:
if (str.size() == 0) // Если условие истинно, то строка пуста.
Однако и для таких случаев имеется специальный метод empty()
, возвращающий true
для пустой строки и false
для непустой:
str.empty();
Как мы можем узнать, совпадают ли строки? Воспользуемся оператором сравнения ==
:
if (str == str2)
А как же можно скопировать одну строку в другую? Это можно реализовать с помощью обычной операции присваивания =
:
std::string str2; // Создание объекта строки str2 = str; // при помощи оператора присваивания, копируем str в str2
Возможно вы спросите почему я не сделал так?:
std::string str2 = str;
Эта запись эквивалентна предыдущей, за исключением того, что в предыдущем коде мы использовали оператор присваивания, а этом случае работает конструктор копирования.
Для конкатенации строк используется операция сложения +
или операция сложения с присваиванием +=
. Рассмотрим следующий код:
std::string str1 = "Hello"; std::string str2 = "World";
Мы можем получить строку "HelloWorld"
, состоящую из конкатенации str1
и str2
, таким образом:
std::string str3 = str1 + str2;
В результате в строке str3
будет хранится следующее: "HelloWorld"
.
Так же подобного результата, мы можем добиться и другим способом:
std::string str3 = str1; str3 += str2;
Здесь мы создаем строку str3
инициализируя её содержимым str1
, и далее при помощи оператора +=
в конец str3
добавляем str2
. Результат будет таким как и в предыдущем примере.
Операция сложения может конкатенировать (соединять, присоединять) экземпляры (объекты) класса std::string
не только между собой, но и со строками встроенного типа. Такой код будет вполне работоспособным:
char* str = "Hello"; std::string str2 = str; str2 += " World"; str2 += '!';
Надеюсь как вы уже догадались в результате в строке str2
, будет хранится "HelloWorld!"
Ещё одна весьма полезная функция это: c_str()
.
Функция c_str()
возвращает указатель на символьный массив, который содержит строку объекта стринг (string
) в том виде, в котором она размещалась бы, во встроенном строковом типе. Например:
std::string str; const char* str2 = str.c_str();
Чтобы обращаться к отдельным символам строки типа string
, можно воспользоваться операцией взятия индекса ([]
), или при помощи метода at().
Например:
std::string str = "Hello World"; std::cout << str[7] << str[0] << std::endl; // На консоли увидим: oH
Метод at()
предлагает похожую схему доступа, за исключением того, что индекс предоставляется как аргумент функции:
std::string str = "Hello World"; std::cout << str.at(7) << str.at(0) << std::endl; // На консоли увидим: oH
В отличии от оператора []
, метод at()
, обеспечивает проверку границ и генерирует исключение out_of_range
, если вы пытаетесь получить несуществующий элемент.
Так же в классе string
, имеется функция, которая возвращает строку, являющуюся подстрокой исходной строки, начиная с позиции pos
и включая n
символов, или до конца строки.
str.substr(pos, n);
На самом деле std::string
имеет намного больше методов и возможностей, чем было рассмотрено здесь, и если бы я решил описать хотя бы половину, то вся эта информация никак не поместилась бы в одну статью. Но основные моменты работы со стрингом мы рассмотрели и новичкам будет достаточно и этих знаний.
Полное представление класса string
(конструкторы, перегруженные операции, методы, дополнительные функции, и т.д), можно посмотреть здесь: http://www.cplusplus.com/reference/string/basic_string Стандартный класс string
основан на таком определении шаблона:
template<class charT, class traits = char_traits<charT>, class Allocator = allocator<charT> > class basic_string {...};
Здесь charT
представляет тип, который хранится в строке. Параметр traits
представляет класс, определяющий необходимые свойства, которыми должен обладать тип для представления строки.
Например метод length()
, который возвращает длину строки, представленную в виде массива типа charT
. Конец такого массива указан значением charT(0)
, которое является обобщенной формой нулевого символа. Параметр Allocator
представляет класс для управления распределением памяти под строку. Шаблон по умолчанию allocator<charT>
использует операции new
и delete
стандартными способами.
Как и любой более-менее сложный пользовательский тип (класс), класс string
определяет свои конструкторы. Да да не конструктор, а конструкторы. Их в нём 11.
Вот часть из них:
explicit basic_string(constAllocator& a = Allocator()); basic_string(const charT* s, constAllocator& a = Allocator()); basic_string(const charT* s, size_type n, const Allocator& a = Allocator()); basic_string(const basic_string& str); basic_string(const basic_string& str, const Allocator&);
Думаю пока нет смысла приводить здесь полный листинг реализации класса string
, так как данная статья рассчитана на начинающих программистов, и в ней я планирую скорее научить вас использовать string
, нежели объяснять его внутреннее представление. Я привёл здесь определение шаблона и описание некоторых конструкторов, только чтобы показать вам, что это действительно класс. Который можете реализовать и вы (возможно не такой эффективный и гибкий).
Ну что же, настало время реализовать свой простенький класс string
. Но перед этим, не забывайте, что класс string
обладает всеми возможностями стандартных типов. Если вы знакомы с классами, то наверное уже догадались, что для реализации таких возможностей, в классе должны быть перегружены многие операторы (в том числе ввода >>
и вывода <<
). Кроме того класс string
содержит ещё множество полезных методов.
Итак, код нашего класса String
выглядит так:
#ifndef STRING_H #define STRING_H #include <iostream> namespace STD { int StrLen(char*); void StrCpy(char*, char*); bool StrCmp(char*, char*); class String { public: String(char* _str = ""); String(const String&); ~String(); String& operator=(const String&); friend String operator+(const String&, const String&); String& operator+=(const String&); friend bool operator==(const String&, const String&); friend bool operator!=(const String&, const String&); friend bool operator>(const String&, const String&); friend bool operator>=(const String&, const String&); friend bool operator<(const String&, const String&); friend bool operator<=(const String&, const String&); const char& operator[](int) const; char& operator[](int); friend std::ostream& operator<<(std::ostream&, const String&); friend std::istream& operator>>(std::istream&, String&); private: char* str; }; String::String(char* _str) { str = new char[StrLen(_str)+1]; StrCpy(str, _str); } String::String(const String& rhs) { str = new char[StrLen(rhs.str)+1]; StrCpy(str, rhs.str); } String::~String() { delete str; } // --- String& String::operator=(const String& rhs) { if (this != &rhs) { delete[] this->str; this->str = new char[StrLen(rhs.str)+1]; StrCpy(this->str, rhs.str); } return *this; } String& String::operator+=(const String& rhs) { int sz = StrLen(this->str) + StrLen(rhs.str); char* ts = new char[sz+1]; for (int i = 0; i < StrLen(this->str); i++) ts[i] = this->str[i]; for (int ii = StrLen(this->str), j = 0; ii <= sz; ii++, j++) ts[ii] = rhs.str[j]; delete this->str; this->str = ts; return *this; } String operator+(const String& lhs, const String& rhs) { String ts = lhs; return ts += rhs; } // -- bool operator==(const String& lhs, const String& rhs) { return StrCmp(lhs.str, rhs.str); } bool operator!=(const String& lhs, const String& rhs) { return !(StrCmp(lhs.str, rhs.str)); } bool operator>(const String& lhs, const String& rhs) { return (StrLen(lhs.str) > StrLen(rhs.str)) ? true : false; } bool operator>=(const String& lhs, const String& rhs) { return (StrLen(lhs.str) >= StrLen(rhs.str)) ? true : false; } bool operator<(const String& lhs, const String& rhs) { return (StrLen(lhs.str) < StrLen(rhs.str)) ? true : false; } bool operator<=(const String& lhs, const String& rhs) { return (StrLen(lhs.str) <= StrLen(rhs.str)) ? true : false; } // --- const char& String::operator[](int i) const { //std::cerr << "Index out of range. \n"; return (i >= 0 && i < StrLen(this->str)) ? this->str[i] : 0; } char& String::operator[](int i) { static char DUMMY; DUMMY = ''; //std::cerr << "Index out of range. \n"; return (i >= 0 && i < StrLen(this->str)) ? this->str[i] : DUMMY; } // --- std::ostream& operator<<(std::ostream& os, const String& obj) { return os << obj.str; } std::istream& operator>>(std::istream& is, String& obj) { char BUFF[2048]; is.getline(BUFF, sizeof BUFF); obj = BUFF; return is; } // --- int StrLen(char* _str) { int size = 0; for (; _str[size] != 0; size++); return size; } void StrCpy(char* in_str, char* src_str) { for (int i = 0; i < StrLen(in_str); i++) in_str[i] = src_str[i]; } bool StrCmp(char* str_f, char* str_s) { int i = 0; for (; str_f[i] == str_s[i] && i < StrLen(str_f); i++); return (i == StrLen(str_f)) ? true : false; } } #endif
Безусловно этот класс, не может сравнится со стандартным, но всё же он вполне работоспособный и его также можно использовать в своих приложениях. Ну вот и всё, что я хотел рассказать вам в этой статье, помните как я уже говорил, std::string
, обладает намного большими возможностями, чем было рассмотрено в данной статье. По большому счёту, я не упоминал о многих (возможностях), таки из-за того, что эта статья рассчитанная прежде всего для новичков. Удачи в ваших начинаниях!