string: шаблонный строковый класс STL

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

Практика

К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!

Автор: Marienko L.
Дата: 02.04.2013
Поделиться:

Оставить комментарий

Вы должны войти, чтобы оставить комментарий.