C++ STL string 详解

Posted DR5200

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ STL string 详解相关的知识,希望对你有一定的参考价值。

文章目录

一.string类的常用接口声明

(1).string类对象的常见构造

(1). string();
功能 : 构造空的string类对象,即空字符串
(2).string (const string& str);
功能 : 拷贝构造函数
(3).string (const string& str, size_t pos, size_t len = npos);
功能 : 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾.
(4).string (const char* s);
功能 : 拷贝s所指的以空结尾的字符序列(C字符串).
(5).string (const char* s, size_t n);
功能 : 拷贝s所指的字符数组中前n个字符.
(6).string (size_t n, char c);
功能 : 用n个连续的字符c填充字符串.

// 1. 空字符串
string s1;
// 2. 拷贝s所指的以空结尾的字符序列
string s2("hello world");
string s3 = "hello world";
// 3. 拷贝构造函数
string s4(s3);
// 4. 拷贝str中从字符位置pos开始len个字符的部分(如果str太短或len是string::npos),则拷贝到str的末尾
string s5(s4,0,3);
string s6(s4,0,string::npos);
// 5. 拷贝s所指的字符数组中前n个字符
string s7("123456",3);
// 6. 用n个连续的字符c填充字符串
string s8(10,'a');

(2).string类对象的访问和遍历方式

构造出string类对象以后,我们想要遍历字符串要怎么做呢?
(1). 下标访问

(2). 迭代器
iterator begin();
const_iterator begin()const;
功能 : 返回指向字符串第一个字符的迭代器.如果string对象是const限定的,则函数返回一个const迭代器

iterator end();
const_iterator end()const;
功能 : 返回指向字符串最后一个字符的下一个位置的迭代器.如果string对象是const限定的,则函数返回一个const迭代器

(3).范围for

int main()
{
	string s1("hello world");
	// 1. 下标访问
	for(size_t i = 0;i < s1.size();i++)
	{
		cout<<s1[i]<<" ";
		// cout<<s1.at(i)<<endl;
	}
	cout<<endl;
	// 2. 迭代器
	// begin()返回的是第一个位置,end()返回的是最后一个位置的下一个位置
	string::iterator it = s1.begin();
	while(it != s1.end())
	{
		cout<<*it<<" ";
		it++;
	}
	cout<<endl;
	// 反向迭代器,逆序遍历
	string::reverse_iterator rit = s1.rbegin();
	while(rit != s1.rend())
	{
		cout<<*rit<<" ";
		rit++;
	}
	cout<<endl;
	// 3.范围for
	for(auto& e : s1)
	{
		cout<<e<<" ";
	}
	cout<<endl;
}

(4).string类对象的修改操作

(1).void push_back(char c)
功能 : 将字符c追加到字符串的末尾,使其长度增加1.

(2).string& append (const char* s);
string& append (const string& str);
更多形式可查阅 append

int main()
{
	string s1;
	s1.push_back('h');
	s1.push_back('e');
	s1.push_back('l');
	s1.push_back('l');
	s1.push_back('o');

	s1.append("world");

	string s2("!!");
	s1.append(s2);
}

(3).string& operator+= (const string& str);
string& operator+= (const char* s);
string& operator+= (char c);

int main()
{
	string s1;
	s1 += ' ';
	s1 += "hello ";
	string s2("world");
	s1 += s2;
}

(4). string& insert (size_t pos, const string& str);
string& insert (size_t pos, const char* s);
功能 : 在pos位置插入字符串

int main()
{
	string s1("hello world");
	s1.insert(0,"xx");
	string s2("!!!!");
	s1.insert(2,s2);
}

(5). string& erase (size_t pos = 0, size_t len = npos);
iterator erase (iterator p);
iterator erase (iterator first, iterator last);

(1). 删除字符串值中从字符位置pos开始并跨越len个字符的部分(如果内容太短或len是basic_string::npos,则删除到字符串末尾)。
请注意,默认参数会删除字符串中的所有字符(如成员函数clear).

(2). 删除p所指的字符.

(3). 删除[first,last]范围内的字符序列。

功能 : 删除基本字符串的一部分,减少其长度.

int main()
{
	string s1("hello world");
	s1.erase(0,3);
}

(6). const char* c_str() const;
功能 : 获取C字符串
返回一个指向数组的指针,该数组包含一个以null结尾的字符序列(即C字符串),表示string对象的当前值。此数组包含组成string对象值的相同字符序列,以及结尾处的附加终止空字符(’\\0’)。

	string s1("hello world");
	cout << s1 << endl; // 调用 operator<<(cout,s1)
	cout << s1.c_str() << endl; // 调用 operator<<(cout,const char*)

	s1.resize(20);
	s1 += "!!!";
	cout << s1 << endl;
	cout << s1.c_str() << endl;


(7). size_t find (const string& str, size_t pos = 0) const;
size_t find (const char* s, size_t pos = 0) const;
size_t find (const char* s, size_t pos, size_t n) const;
size_t find (char c, size_t pos = 0) const;

功能 : 在字符串中搜索由其参数指定的序列的第一个匹配项 .

(8).string substr (size_t pos = 0, size_t len = npos) const;

功能 : 返回一个新构造的字符串对象,子字符串是对象的一部分,从字符位置pos开始,跨越len个字符(或直到字符串的结尾,以先到者为准).

// 获取域名
string GetDomain(const string& url)
{
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		size_t start = pos + 3; 
		size_t end = url.find('/', start);
		if (end != string::npos)
		{
			return url.substr(start,end - start);
		}
		else
		{
			return string();
		}
	}
	else
	{
		return string();
	}

}
// 获取协议名
string GetProtocol(const string& url)
{
	size_t pos = url.find("://");
	if (pos != string::npos)
	{
		return url.substr(0,pos);
	}
	else
	{
		return string();
	}
}
string url = "http://www.cplusplus.com/reference/string/string/find/";
cout << GetDomain(url) << endl;
cout<<GetProtocol(url)<<endl;

(3).string类对象的容量操作

(1).size_t size() const;
功能 : 返回字符串的长度(以字节为单位).

(2).size_t capacity() const;
功能 : 返回当前为字符串分配的存储空间的大小(以字节为单位).

(3).bool empty() const;
功能 : 返回字符串是否为空(即其长度是否为0).

(4).void clear();
功能 : 删除字符串的内容,该字符串将变为空字符串(长度为0).

(5).void resize (size_t n);
void resize (size_t n, char c);
功能 : 将字符串大小调整为n个字符的长度。
如果n小于当前字符串长度,则当前值将缩短为其第一个n个字符,从而删除超过n个的字符。
如果n大于当前字符串长度,则通过在末尾插入尽可能多的字符以达到n的大小,来扩展当前内容。如果指定了c,则新元素将初始化为c,否则,为空字符.

(6).void reserve (size_type n = 0);
功能 : 请求对字符串容量进行调整,以适应计划的大小更改,长度最多为n个字符.
如果n大于当前字符串容量,则函数会使容器将其容量增加到n个字符(或更大)。
在所有其他情况下,它被视为收缩字符串容量的非绑定请求:容器实现可以自由地进行其他优化,并将基本字符串保留为大于n的容量。

	string s1;
	cout << "s1 size :"<<s1.size() << endl;
	cout << "s1 capacity : "<<s1.capacity() << endl;
	cout << s1 << endl;

	s1.resize(20, 'x');
	cout << "s1 size :" << s1.size() << endl;
	cout << "s1 capacity : " << s1.capacity() << endl;
	cout << s1 << endl;

	string s2("hello world");
	s2.resize(5);
	cout << s2 << endl;

	string s3;
	cout << "s3 size :" << s3.size() << endl;
	cout << "s3 capacity : " << s3.capacity() << endl;

	s3.reserve(40);
	cout << "s3 size :" << s3.size() << endl;
	cout << "s3 capacity : " << s3.capacity() << endl;

二.string类的模拟实现

string类接口

namespace lyp
{
	class string
	{
	public:
		// 迭代器
		typedef char* iterator;
		// const迭代器
		typedef const char* const_iterator;

		// 默认成员函数
		string(const char* str = "");
		string(const string& str);
		string& operator=(const string& str);
		~string();

		// 修改操作函数
		void push_back(char c);
		void append(const char* str);
		string& operator+=(char c);
		string& operator+=(const char* str);
		string& insert(size_t pos,char c);
		string& insert(size_t pos, const char* str);
		string& erase(size_t pos, size_t len = npos);
		void swap(string& str);

		// 迭代器相关函数
		iterator begin();
		const_iterator begin()const;
		iterator end();
		const_iterator end()const;

		// 容量相关函数
		size_t size()const;
		size_t capacity()const;
		void resize(size_t n,char c = '\\0');
		void reserve(size_t n);
		void clear();
		bool empty()const;

		// 元素遍历函数
		char& operator[](size_t index);
		const char& operator[](size_t index)const;
		
		// String operations
		const char* c_str()const;
		size_t find(char c,size_t pos = 0)const;
		size_t find(const char* str,size_t pos = 0)const;
		size_t rfind(char c, size_t pos = npos)const;
		size_t rfind(const char* str, size_t pos = npos)const;

	private:
		char* _str;
		size_t _size; // 有效字符的个数
		size_t _capacity; // 能存储有效字符的最大个数
		static const size_t npos; // 静态无符号成员变量
	};
	const size_t string::npos = -1;
	// 非成员函数

	// relational operators
	bool operator<(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	// << >> 运算符重载
	istream& operator>>(istream& in,string& str);
	ostream& operator<<(ostream& out, const string& str);

	// getline
	istream& getline(istream& in, string& str);
}

默认成员函数

(1).构造函数

string(const char* str = "");

函数的默认参数为空字符串,当然我们也可以传递自己给定的字符串,创建的对象存储字符串的空间比参数字符串的长度多 1 ,目的是为了存储空字符,并将参数字符串的内容拷贝到对象字符串空间中_size 和 _capcity 都初始化为参数字符串的长度

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

(2).拷贝构造函数

在实现我们的拷贝构造函数之前,我们要先了解一下深拷贝和浅拷贝

浅拷贝又称值拷贝,只是单纯的拷贝值而已,浅拷贝在某些情况下是不存在问题的,但是对于string类的构造函数如果使用浅拷贝,就会出现问题,因为浅拷贝之后两个对象的字符串指针指向同一块内存空间,在对象销毁时,这块内存空间就被析构了两次

而编译器默认生成的拷贝构造函数完成的是浅拷贝,所以我们自己需要去实现深拷贝,即拷贝构造出来的对象和源对象拥有不同的字符串空间


深拷贝的实现一般有两种方式,一种是传统方式,一种是现代方式

传统方式的步骤如下

(1).新开一块和源对象字符串空间一样大的空间
(2).将源对象字符串的内容拷贝到新空间

string(const string& str)
			:_str(new char[strlen(str._str) + 1])
			,_size(str._size)
			,_capacity(str._capacity)
{
			strcpy(_str, str._str);
}

现代方式的步骤如下

(1).利用我们上面写好的构造函数,用 str 构造出一个临时对象 tmp
(2).分别交换 创建的对象 和 tmp 对象的成员变量即可

注意 : 创建的对象_str 要初始化为空指针,否则创建的对象_str 为随机值,和 tmp 对象进行交换以后,tmp对象的_str为随机值,在tmp对象销毁时,调用析构函数,释放tmp对象的_str所指向的空间会出现问题

string(const string& str)
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
{
			string tmp(str._str); // 构造临时对象
			std::swap(_str,tmp._str); // 交换
			std::swap(_size,tmp._size); // 交换
			std::swap(_capacity,tmp._capacity); // 交换
			
			// 后面自己实现的swap函数
			//swap(tmp);
}

(3).赋值运算符重载

编译器默认生成的赋值运算符重载完成的也是浅拷贝,因此我们仍需要自己去实现深拷贝,赋值运算符重载同样也有两种写法

传统写法步骤如下

(1).释放掉原空间,新开一块和 str 字符串空间一样大的空间
(2).将 str 的内容拷贝到新空间中

string& operator=(const string& str)
{
		// 防止自己给自己赋值 
		if (this != &str)
		{
			delete[]_str; // 释放空间
			_str = new char[strlen(str._str) + 1]; // 新开一块空间
			strcpy(_str, str._str); // 将 str 的内容拷贝到新空间中
			_size = str._size;
			_capacity = str._capacity;
		}
		return *this;
}

现代写法的步骤如下 :

注意 : 现代写法的参数并没有传引用,而是传值

(1). 因为是传值,所以会调用拷贝构造函数构造出 str
(2). 分别交换 创建的对象 和 str 对象的成员变量即可

string& operator=(string str)
{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		
		// 后面自己实现的swap函数
		//swap(str);
		return *this;
}

但此方法有一个缺陷,就是当自己给自己赋值的时候,自己存储字符串的地址发生了改变,我们可以采取以下写法来避免该情况发生

string& operator=(const string& str)
{
		// 防止自己给自己赋值
		if (this != &str)
		{
			string tmp(str._str);
			std::swap(_str, tmp._str)C++ STL详解

STL之string使用详解

C++ STL之tuple详解

C++ STL之tuple详解

C++ STL 双端队列deque详解

C++ STL之queue详解