Datastructure-String

73 阅读13分钟

String


概述

string 是对字符串进行一系列操作的结构,由于 string 类中有各种编码类格式,例如:u16string,u32string,wstring 等,本篇将不会一一阐述,以 basic_string 为主进行模拟实现。由于 string 类也有部分功能存在一定程度的冗余,且些许功能使用次数极少,故仅针对频繁且有实际意义的成员函数提供实现。由于此部分由 C++ 进行实现,故在其中将进行一些格式变更。


类的实现

成员变量

管理字符串在底层依然是依靠 char* 指针,且辅佐以大小和容量,方便管理扩容等操作。故成员变量一共有以下三种:

namespace Thepale
{
    class string
    {
    private:
        char* _str;
        size_t _size;
        size_t _capacity;
    };
}

为了和 std 中的 string 类区分,故加上命名空间隔离。


成员函数

构造函数

一般而言,string 类多以无参初始化或字符串初始化,故此处仅考虑这两种情况。当 string 以无参构造时,并非将指针置为 nullptr,这会给后续管理带来很大麻烦,例如插入数据需要额外考虑 nullptr 的情况,拷贝数据也需要考虑 nullptr 的情况等等,后续自然会见证。故就算是无参初始化,也默认附带一个 '\0',这样很大程度上能进行统一管理。

string(const char* str = "")
    :_str(nullptr)
    , _size(strlen(str))
    , _capacity(_size + 1)
{
    _str = new char[_capacity];
    strcpy(_str, str);
}

在本篇实现的代码中,_capacity 表示的是实际开辟的容量,_size表示的是字符串的实际大小(不包括结尾的 '\0',这里仅指结尾,若是中间的 '\0' 是会被计算的)。在针对空间的开辟中,使用 strlen 和 strcpy,显然如果所传递的字符串在中间有 '\0' 出现,它将会被解析为字符串的结束,这里不做特殊处理,因为是直接的字符串传递,若遇 '\0' 则释义就是结束。若对此点有疑惑,请见:[^拷贝构造函数]。

这里恰好可以同时解决两种情况,若无参构造则传递 '\0',在初始化列表中也可以直接 new 空间,但个人更习惯先给 nullptr,依据个人喜好即可。而这里需要注意 _capacity 是依据 _size 初始化的,这就表明在声明成员变量时 _capacity 必须在 _size 后被声明,必须注意这一点。

拷贝构造函数

string(const string& str)
{
    _size = str._size;
    _capacity = str._capacity;

    _str = new char[_capacity];
    memcpy(_str, str._str, str._capacity);
}

拷贝构造需要进行的是深拷贝,不能让两个指针管理同一块空间,故这里必须额外开辟空间。这里必须使用 memcpy,若在字符串 _size 为 3,字符串为:A \0 B \0,则中间的 '\0' 必须拷贝,若使用 strcpy 则会在遇到 '\0' 时停止,因为无法避免用户插入 '\0' 的情况。

额外手动开辟空间是可行的,但也可以复用构造函数:

void swap(string& str)
{
    std::swap(_str, str._str);
    std::swap(_size, str._size);
    std::swap(_capacity, str._capacity);
}

string(const string& str)
    :_str(nullptr)
    ,_size(0)
    ,_capacity(0)
{

    string tmp(str._str);
	swap(tmp);
}

可以直接利用构造函数构造和原 string 字符串数据一样但空间不同的对象,(实质上是构造函数帮忙完成了手动开辟空间),然后交换它们的内容,将 tmp 的内容交给当前对象管理,而 tmp 在拷贝构造结束时又会进行销毁,且析构 nullptr 是无危害的,便借助构造函数完成了拷贝构造函数的功能。

但这种写法有两点需要注意:其中一点是,在进行拷贝构造时,当前对象的空间未被初始化,很有可能出现随机值,当交换后,随机空间被释放则会报错(在赋值运算符重载中会再次说明);还有一点是若字符串中在中间出现 '\0',使用这种写法在借助构造函数时传递的是字符串,故会截断字符串,导致拷贝不完全,出于严谨应使用第一种写法。

析构函数

析构字符串即析构动态开辟的空间是十分简单的,不过多赘述:

~string()
{
    delete[] _str;
    _str = nullptr;

    _size = 0;
    _capacity = 0;
}

赋值运算符重载

赋值一定是针对两个已存在的对象的,若仔细划分,则会存在三种情况:赋值过来的空间比原空间小,赋值过来的空间和原空间相等,赋值过来的空间比原空间大。若比原空间小,一般不会执行缩容操作,故必然有一部分空间会被浪费;若和原空间相等,则需要替换其内容;若比原来的空间大,则需要执行扩容操作后再替换其内容。上述操作实则可以直接简化为释放原空间后再次申请空间完成赋值,则省略了冗余的判断和空间浪费的问题:

 string& operator=(const string& str)
{
    if (this != &str) //防止自身拷贝的情况导致不必要的消耗
    {
        char* tmp = new char[str._capacity];
        memcpy(tmp, str._str, str._capacity);

        delete[] _str;
        _str = tmp;

        _size = str._size;
        _capacity = str._capacity;
    }

    return *this;
}

只需要开辟一个新的 tmp 来存放 str 中的内容,再将原空间释放,重新管理 tmp 的空间即可。当然,这一过程不仅仅可以复用构造函数,也可以复用拷贝构造,一般而言复用拷贝构造,可直接在传参部分完成拷贝:

string& operator=(string str) //在传参时不使用引用,直接完成拷贝构造创建新对象
{
    if (this != &str)
    {
		swap(str);
    }

    return *this;
}

在传参时完成拷贝新对象,直接将拷贝的对象资源交给当前对象管理,将需要释放的资源交换给了 str,在函数结束时 str 又会自动销毁,则顺带销毁了原本需要释放的当前对象的空间,一举两得。

一般而言,部分编译器(以 VS 为例)会解决拷贝构造函数中未初始化当前对象的问题,就算不在拷贝构造函数中写明初始化列表,成员变量依然被初始化,重要的是指针被初始化为 nullptr,这将不会引起释放时的问题。但如果出现以下情况:

两个对象执行赋值操作,在 e2 拷贝构造 str 时,str(红框)会出现随机值,此时将 str 和构造函数创建的 tmp 对象进行空间交换,拷贝构造函数执行完成后会释放 tmp 空间(即 str 未初始化的随机空间),会产生报错。故在复用构造函数实现拷贝构造函数时,初始化列表是必须的。


Iterators

迭代器只实现普通和 const 版本,一般而言反向迭代器和 const 反向迭代器的应用场景比较有限,且易被替代,实现的意义不大。由于在 string 类中,迭代器实则就是对指针的直接封装,所以实现也非常简单,故做了合并处理。需要注意迭代器区间是左闭右开。

typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
    return _str;
}

iterator end()
{
    return _str + _size; 
}

const_iterator cbegin() const
{
    return _str;
}

const_iterator cend() const
{
    return _str + _size;
}

Capacity

在这里不会实现 max_size,在各个编译器中它的值都是不确定的,这取决于编译器行为。同时也不会实现 length,它和 size 的含义实际是一样的,由于 string 早于 STL 问世,所以才会有 length 的出现,size 做到了和 STL 标准一致。shrink_to_fit 由于不考虑缩容操作,它不仅会产生性能开销且存在释放的不确定性(缩容是一个请求而非强制要求)。

reserve

reserve 一般主要负责空间的开辟,后续有多个函数可以对 reserve 进行复用完成扩容操作。

void reserve(size_t new_capacity)
{
    if (new_capacity > _capacity) //检查新容量是否大于当前容量,一般不进行缩容操作
    {
        char* tmp = new char[new_capacity];

        memcpy(tmp, _str, _size + 1);
        delete[] _str;

        _str = tmp;

        _capacity = new_capacity;
    }
}

这里先开辟了空间,转移数据后就释放了原空间,因为 C++ 并没有类似 C 语言中 realloc 的操作,只能释放后重新开辟。且这里只能使用 memcpy,若使用 strcpy 在字符串中间出现 '\0' 时则会导致数据拷贝时丢失。这里仅需拷贝 _size + 1 的空间,而非大小为 _capacity 的所有空间。

resize

resize 在完成空间开辟的同时也会实现赋值操作,一般为 n 个 char,若不指明 char 则初始化为 '\0' :

void resize(size_t n, char c = '\0')
{
    if (n < _size)
    {
        _size = n;
        _str[_size] = '\0';
    }
    else
    {
        reserve(n + 1); //两种情况一起处理

        for (size_t i = _size; i < n; ++i)
        {
            _str[i] = c;
        }

        _size = n;
        _str[_size] = '\0';
    }
}

对于 n < _size 的情况,一般认为是删除数据,仅需添加 '\0' 表示字符串结束即可。(这里并不会导致进行拷贝时拷贝 '\0' 后的多余数据,因为 _size 也改变了)等于和大于的情况可以一起处理,等于情况没有新开的空间,所以无需处理,大于的情况将新开的空间赋值为对应 char 即可。

size

需要返回 size 和 capacity 的情况都十分简单,不过多赘述,不过别忘了 const 对象:

size_t size() const
{
    return _size;
}
capacity
size_t capacity() const
{
    return _capacity;
}
empty

判空操作是必要的,它在很多地方都能派上用场,而 string 的判空也是极为简单:

bool empty() const
{
    return _size == 0;
}
clear

清除操作的目的在于清空所有元素,当然它也不会涉及缩容:

void clear()
{
    _str[0] = '\0';
    _size = 0;
}

始终注意字符串的实际大小是由 _size 控制的,所以即使不清除此字符串 '\0' 后的内容,在计算机看来它依然是结尾的 '\0' 而非中间的。


Element Access

对于元素的访问仅实现 operator[],它和 at 的差别在于 at 是抛异常而 operator[] 是断言检查。front 和 back 的实现意义真的微乎其微,有这个功夫为何不直接使用 str[0] 和 str[str.size() - 1] 呢?

operator[]

这样访问到的元素是可以进行修改的(如果不是 const 对象的话),故需要返回引用:

char& operator[](size_t pos)
{
    assert(pos < _size);

    return _str[pos];
}

const char& operator[](size_t pos) const 
{
    assert(pos < _size);

    return _str[pos];
}

Modifiers

assign 用于对字符串进行删除重写,即全部替换;replace 实现对字符串的部分替换。它们的工作更像是对 insert、clear、erase 的进一步封装,这里不予实现。

push_back

尾插负责完成单个字符的插入工作:

void push_back(char c)
{
    if (_size + 1 >= _capacity)
    {
        reserve(_capacity * 2);
    }

    _str[_size++] = c;
    _str[_size] = '\0';
}
append

append 负责完成字符串的拼接:

void append(const char* str)
{
    size_t len = strlen(str);

    if (_size + len >= _capacity)
    {
        reserve(len + _size + 1);
    }

    strcpy(_str + _size, str);
    _size = _size + len;
}

这里容量不够的处理操作是将容量扩大到刚好能装下,当然不同的编译器会有不同的处理方式,也可能会扩的比当前空间大(例如 VS)。strcat 作为 C 语言的字符串操作函数在此处复用让我觉得有些不合适,它强调的是字符串拼接,但如果出现字符串中有 '\0' 的情况则会导致后续内容被拼接的字符串覆盖,但若加到 _size 后的地址空间又失去了拼接的意义,故采用 strcpy 将需要拼接的内容拷贝到后面即可(由于 append 接收到的是原生字符串,故可以不采用 memcpy,将 '\0' 解释为字符串结束即可)。

insert

插入操作的重载也很多,这里主要实现两种以完成大部分功能(插入 n 个字符或字符串):

void insert(size_t pos, size_t n, char c)
{
    assert(pos <= _size);

    if (_size + n >= _capacity)
    {
        reserve(_size + n + 1);
    }

    for (size_t i = pos; i < _size + 1 - pos; ++i) //挪动数据
    {
        _str[_size + n - i] = _str[_size - i];
    }

    for (size_t i = 0; i < n; ++i) //完成插入
    {
        _str[pos + i] = c;
    }

    _size += n;
}
void insert(size_t pos, const char* str)
{
    assert(pos <= _size);

    size_t len = strlen(str);
    if (_size + len >= _capacity)
    {
        reserve(_size + len + 1);
    }

    for (size_t i = 0; i < _size + 1 - pos; ++i) //挪动数据
    {
        _str[_size + len - i] = _str[_size - i];
    }

    strncpy(_str + pos, str, len); //完成插入
    _size += len;
}

因为 str 是原生字符串,故可以不使用 memcpy。

operator+=

这是 string 类中及其方便的一个运算符重载,它可以直接实现对字符串的尾插(尾插字符或字符串),事先实现好的 push_back 和 append 也将在此时被复用:

string& operator+=(char c)
{
    push_back(c);

    return *this;
}
string& operator+=(const char* str)
{
    append(str);

    return *this;
}

考虑连续加等的情况,请使用引用返回。

string& operator+=(const string& str)
{
    append(str._str);

    return *this;
}

当然,加一个对象也是允许的。

pop_back

尾删负责完成单个字符的删除工作:

void pop_back()
{
    assert(_size > 0);

    --_size;
}
erase

删除的重载也比较多,删除对应区间是比较常用且覆盖较广的一个,仅实现这一个:

这里涉及到了 npos,它是无符号形式的 -1(在 x86 环境下,它是 4294967295,整型的最大值,一般用于表示字符串的结尾)

size_t string::npos = -1; //这里是定义部分,请自行于类中声明
void erase(size_t pos, size_t len = npos)
{
    assert(pos <= _size);

    if (len == npos || len > _size - pos) //pos 后全部删除的情况
    {
        _str[pos] = 0;
        _size = pos;
    }
    else
    {
        for (size_t i = 0; i <= _size - pos - len; ++i) //删除中间的情况
        {
            _str[pos + i] = _str[pos + len + i];
        }

        _size -= len;
    }
}

String Operations

在这里仍然会挑选使用频率高、覆盖范围大的函数进行实现。例如我认为 find_first_of、find_last_of 等函数大可用 find 代替等。

c_str

它用于直接取出对象所管理的指针,由名称可知这个接口更多程度是为了契合 C 语言,在 vector 中提供了 data() 函数来完成这一点,尽管 string 中也提供了 data(),但 c_str 或许仍然是更为公认的表示方法。

char* c_str() const
{
    return _str;
}
find

find 分为查找字符和查找字符串两类:

size_t find(char c, size_t pos = 0) const
{
    assert(pos < _size);

    for (size_t i = pos; i < _size; ++i)
    {
        if (_str[i] == c)
        {
            return i;
        }
    }

    return npos; //表示未查找到字符串
}
size_t find(const char* str, size_t pos = 0) const
{
    assert(pos < _size);

    const char* p = strstr(_str + pos, str); //字符串的匹配正好使用 strstr
    if (p != nullptr)
    {
        return p - _str;
    }

    return npos;
}

可见在官方所提供的标准中,find 返回的仍然是下标位置,而非迭代器,迭代器在 string 中不太常用,所以在之前的构造函数中,我思索一番依然没有写出迭代器构造的版本。对于大多数程序员而言,直接通过指针和下标索引等操作字符串是更方便舒适的。

substr

substr 的功能是取出子串:

string substr(size_t pos = 0, size_t len = npos)
{
    assert(pos < _size);

    string ret;
    if (len > _size - pos) 
    {
        len = _size - pos;
    }

    ret.reserve(len + 1);
    ret._size = len;

    memcpy(ret._str, _str + pos, len); //不能使用 strcpy,原因已说明

    (ret._str)[ret._size] = '\0'; //这一步要注意(单纯因为我不小心写错了调试了很久)

    return ret;
}

Non-Member Function Overloads

Relational Operators

字符串比较时,使用 C 语言的 strcmp 无法应对字符串中间出现 '\0' 的情况,这里务必自己实现。

bool operator<(const string& str) const
{
    int ret = memcmp(_str, str._str, _size < str._size ? _size : str._size);

    return ret == 0 ? _size < str._size : ret < 0;
}

bool operator==(const string& str) const
{
    return _size == str._size && memcmp(_str, str._str, _size) == 0;
}

bool operator<=(const string& str) const
{
    return *this < str || *this == str;
}

bool operator>(const string& str) const
{
    return !(*this <= str);
}

bool operator>=(const string& str) const
{
    return !(*this < str);
}

bool operator!=(const string& str) const
{
    return !(*this == str);
}
operator<<

由于可以通过对象直接取到 size 和 capacity,故不必为友元函数。

std::ostream& operator<<(std::ostream& out, const string& str)
{
    for (size_t i = 0; i < str.size(); ++i)
    {
        std::cout << str[i];
    }

    return out;
}

这里并没有通过 c_str 直接输出,原因也是为了防止中间出现 '\0' 的情况,会导致字符串提前截至,应严格按照 _size 的大小输出。

operator>>

字符串的流插入重载并非如想象中简单,因为要输入的字符串长度是未知的,无法通过提前开空间储存字符串,所以一般采用逐个字符读取后尾插的方式完成:

std::istream& operator>>(std::istream& in, string& str)
{
    str.clear(); //插入前会清空字符串的内容

    char c;
    c = in.get();

    //清理字符串前的空格和换行
    while (c == ' ' || c == '\n')
    {
        c = in.get(); 
    }

    size_t i = 0;
    while (c != ' ' && c != '\n')
    {
        str += c;
        c = in.get();   
    }

    return in;
}

清理字符串前的空格和换行时,不会导致进入 while 时 c 为空格或换行,因为 c 接收到的永远是清除时 while 判断的后一个字符。

但进行逐个字符插入时,str 将进行多次扩容操作带来额外的消耗,故可以添加一个固定的 buffer 数组来事先储存输入的字符串,然后一次推入 buffer 数组大小的数据量,以减少扩容次数:

std::istream& operator>>(std::istream& in, string& str)
{
    str.clear();

    char c;
    c = in.get();

    while (c == ' ' || c == '\n')
    {
        c = in.get();
    }

    char buffer[128] = { 0 };

    size_t i = 0;
    while (c != ' ' && c != '\n')
    {
        buffer[i++] = c;
        c = in.get();

        if (i == 127)
        {
            buffer[i] = '\0';
            str += buffer;
            i = 0;
        }
    }

    if (i != 0)
    {
        buffer[i] = '\0';
        str += buffer;
    }

    return in;
}

整体实现

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once

#include <iostream>
#include <assert.h>

namespace Thepale
{
	class string
	{
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		static const size_t npos;
        
        //typedef
        typedef char* iterator;
        typedef const char* const_iterator;


	public:
		void swap(string& str) //这并非 swap 的正确位置,但为了便于下文理解,故将 swap 单独提前
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}
        
		string(const char* str = "")
			:_str(nullptr)
			, _size(strlen(str))
			, _capacity(_size + 1)
		{
			_str = new char[_capacity];
			strcpy(_str, str);
		}

		string(const string& str)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{

			string tmp(str._str);
			swap(tmp);
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;

			_size = 0;
			_capacity = 0;
		}

		string& operator=(string str)
		{
			if (this != &str)
			{
				swap(str);
			}

			return *this;
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator cbegin() const
		{
			return _str;
		}

		const_iterator cend() const
		{
			return _str + _size;
		}

		void reserve(size_t new_capacity)
		{
			if (new_capacity > _capacity)
			{
				char* tmp = new char[new_capacity];

				memcpy(tmp, _str, _size + 1);
				delete[] _str;

				_str = tmp;

				_capacity = new_capacity;
			}
		}

		void resize(size_t n, char c = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n + 1);

				for (size_t i = _size; i < n; ++i)
				{
					_str[i] = c;
				}

				_size = n;
				_str[_size] = '\0';
			}
		}

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		bool empty() const
		{
			return _size == 0;
		}

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);

			return _str[pos];
		}

		const char& operator[](size_t pos) const
		{
			assert(pos < _size);

			return _str[pos];
		}

		void push_back(char c)
		{
			if (_size + 1 >= _capacity)
			{
				reserve(_capacity * 2);
			}

			_str[_size++] = c;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			size_t len = strlen(str);

			if (len + _size >= _capacity)
			{
				reserve(len + _size + 1);
			}

			strcpy(_str + _size, str);
			_size = _size + len;
		}

		void insert(size_t pos, size_t n, char c)
		{
			assert(pos <= _size);

			if (_size + n >= _capacity)
			{
				reserve(_size + n + 1);
			}

			for (size_t i = pos; i < _size + 1 - pos; ++i)
			{
				_str[_size + n - i] = _str[_size - i];
			}

			for (size_t i = 0; i < n; ++i)
			{
				_str[pos + i] = c;
			}

			_size += n;
		}

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			if (_size + len >= _capacity)
			{
				reserve(_size + len + 1);
			}

			for (size_t i = 0; i < _size + 1 - pos; ++i)
			{
				_str[_size + len - i] = _str[_size - i];
			}

			strncpy(_str + pos, str, len);
			_size += len;
		}

		string& operator+=(char c)
		{
			push_back(c);

			return *this;
		}

		string& operator+=(const char* str)
		{
			append(str);

			return *this;
		}

		string& operator+=(const string& str)
		{
			append(str._str);

			return *this;
		}

		void pop_back()
		{
			assert(_size > 0);

			--_size;
		}


		void erase(size_t pos, size_t len = npos)
		{
			assert(pos <= _size);

			if (len == npos || len > _size - pos)
			{
				_str[pos] = 0;
				_size = pos;
			}
			else
			{
				for (size_t i = 0; i <= _size - pos - len; ++i)
				{
					_str[pos + i] = _str[pos + len + i];
				}

				_size -= len;
			}
		}

		char* c_str() const
		{
			return _str;
		}

		size_t find(char c, size_t pos = 0) const
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == c)
				{
					return i;
				}
			}

			return npos;
		}

		size_t find(const char* str, size_t pos = 0) const
		{
			assert(pos < _size);

			const char* p = strstr(_str + pos, str);
			if (p != nullptr)
			{
				return p - _str;
			}

			return npos;
		}

		string substr(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);

			string ret;
			if (len > _size - pos)
			{
				len = _size - pos;
			}

			ret.reserve(len + 1);
			ret._size = len;

			memcpy(ret._str, _str + pos, len);

			(ret._str)[ret._size] = '\0';

			return ret;
		}

		bool operator<(const string& str) const
		{
			int ret = memcmp(_str, str._str, _size < str._size ? _size : str._size);

			return ret == 0 ? _size < str._size : ret < 0;
		}

		bool operator==(const string& str) const
		{
			return _size == str._size && memcmp(_str, str._str, _size) == 0;
		}

		bool operator<=(const string& str) const
		{
			return *this < str || *this == str;
		}

		bool operator>(const string& str) const
		{
			return !(*this <= str);
		}

		bool operator>=(const string& str) const
		{
			return !(*this < str);
		}

		bool operator!=(const string& str) const
		{
			return !(*this == str);
		}
	};

	std::ostream& operator<<(std::ostream& out, const string& str)
	{
		for (size_t i = 0; i < str.size(); ++i)
		{
			std::cout << str[i];
		}

		return out;
	}

	std::istream& operator>>(std::istream& in, string& str)
	{
		str.clear();

		char c;
		c = in.get();

		while (c == ' ' || c == '\n')
		{
			c = in.get();
		}

		char buffer[128] = { 0 };

		size_t i = 0;
		while (c != ' ' && c != '\n')
		{
			buffer[i++] = c;
			c = in.get();

			if (i == 127)
			{
				buffer[i] = '\0';
				str += buffer;
				i = 0;
			}
		}

		if (i != 0)
		{
			buffer[i] = '\0';
			str += buffer;
		}

		return in;
	}

	const size_t string::npos = -1;

}

以下为测试代码(非严谨测试):

#include "string.h"

int main()
{
	//构造函数测试
	cout << "构造函数测试" << endl;
	Thepale::string e1; cout << e1 << endl;
	Thepale::string e2("Thepale 5201314"); cout << e2 << endl;

	//拷贝构造函数测试
	cout << "拷贝构造函数测试" << endl;
	Thepale::string e3(e2); cout << e3 << endl;

	//赋值运算符重载测试
	cout << "赋值运算符重载测试" << endl;
	Thepale::string e4("hello");
	e4 = e2; cout << e4 << endl;

	//迭代器测试
	cout << "迭代器测试" << endl;
	Thepale::string e5("I am a iterator tester");
	Thepale::string::iterator it = e5.begin();
	while (it != e5.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	const Thepale::string e6("I am a const_iterator tester");
	Thepale::string::const_iterator cit = e6.cbegin();
	while (cit != e6.cend())
	{
		cout << *cit << " ";
		++cit;
	}
	cout << endl;


	//reserve 测试
	cout << "reserve测试" << endl;
	Thepale::string e7("1"); cout << e7.size() << " " << e7.capacity() << endl;
	e7.reserve(100); cout << e7.size() << " " << e7.capacity() << endl;

	//resize 测试
	cout << "resize测试" << endl;
	Thepale::string e8("hello"); cout << e8 << " " <<  e7.size() << " " << e7.capacity() << endl;
	e8.resize(10, 'x'); cout << e8 << " " << e8.size() << " " << e8.capacity() << endl;

	//size capacity 测试略
	
	//empty 测试
	cout << "empty 测试" << endl;
	Thepale::string e9; cout << e9.empty() << endl;
	Thepale::string e10("1"); cout << e10.empty() << endl;

	//clear 测试
	cout << "clear 测试" << endl;
	Thepale::string e11("5201314"); cout << e11 << endl;
	e11.clear(); cout << e11 << endl;

	//operator[] 测试
	cout << "operator[] 测试" << endl;
	Thepale::string e12("Love is Gone");
	for (size_t i = 0; i < e12.size(); ++i)
	{
		cout << e12[i] << " ";
	}
	cout << endl;
	const Thepale::string e13("Love is Gone(const)");
	for (size_t i = 0; i < e13.size(); ++i)
	{
		cout << e13[i] << " ";
	}
	cout << endl;

	//push_back 测试
	cout << "push_back 测试" << endl;
	Thepale::string e14("hello");
	e14.push_back('6');
	e14.push_back('6');
	e14.push_back('6');
	e14.push_back('6');
	e14.push_back('6');
	e14.push_back('6');
	cout << e14 << endl;

	//append 测试
	cout << "append 测试" << endl;
	Thepale::string e15("hello");
	e15.append("world"); cout << e15 << endl;

	//insert 测试
	cout << "insert 测试" << endl;
	Thepale::string e16("whatever");
	//e16.insert(3, "MMMM");
	e16.insert(0, 10, 'O');
	e16.insert(e16.size(), "END");
	e16.insert(e16.size(), 20, 'T');
	cout << e16 << endl;

	//operator+= 测试略

	//pop_back 测试
	cout << "pop_back 测试" << endl;
	Thepale::string e17("AA"); cout << e17 << endl;
	e17.pop_back(); cout << e17 << endl;
	e17.pop_back(); cout << e17 << endl;

	//erase 测试
	cout << "erase 测试" << endl;
	Thepale::string e18("ILoveYouLikeTheJoker");
	e18.erase(2, 7); cout << e18 << endl;
	e18.erase(0, 10000); cout << e18 << endl;

	//c_str 测试略

	//find 测试
	cout << "find 测试" << endl;
	Thepale::string e19("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
	size_t pos1 = e19.find("ABC"); cout << pos1 << endl;
	size_t pos2 = e19.find("JKM"); cout << pos2 << endl;
	size_t pos3 = e19.find('X'); cout << pos3 << endl;
	size_t pos4 = e19.find('E', 10); cout << pos4 << endl;

	//subser 测试
	cout << "subser 测试" << endl;
	Thepale::string e20("I am a substr tester");
	cout << e20.substr(0, 4) << endl;
	cout << e20.substr(7, 6) << endl;
	cout << e20.substr(0) << endl;
	cout << e20.substr(14) << endl;

	return 0;
}

补充说明

  • string 类不属于类模板,它就是 basic_string,严格来说它不属于 STL,所以在容器中甚至都没有它的身影。在 STL 中类将会以类模板的形式展现,以兼容多种不同的数据类型。