Приветствую вас коллеги – программисты. В данной статье я хочу познакомить Вас с шаблонным классом 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, обладает намного большими возможностями, чем было рассмотрено в данной статье. По большому счёту, я не упоминал о многих (возможностях), таки из-за того, что эта статья рассчитанная прежде всего для новичков. Удачи в ваших начинаниях!