STL容器_string类以及string类的模拟实现

前言

STL(Standard Template Library-标准模板库),是C++标准库的重要组成部分,STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。这篇博客就来带大家梳理一下STL中string类的相关知识。

C语言中管理字符串的缺陷:

  • C语言对于字符串的增删查改操作不能很好的支持
  • C标准库中提供的库函数与字符串是分离的,不符合OOP思想
  • 底层空间需要用户自己管理,可能会造成越界访问地风险

于是C++给出了string类封装了很多字符串常用接口,这个容器可以帮助我们更加方便地操作字符串

一:标准库中的string类

string是表示字符串的字符串类

string的底层:basic_string模板类的别名,typedef basic_string <char, char_traits, allocator>
string;

1.1 string类的常用接口

  1. string类对象的常见构造:
函数名称 功能说明
string () 构造空的string类对象,即空字符串
string (const char* s) 用C-string来构造string类对象
string (const string&s) 拷贝构造函数
string (size_t n, char c) string类对象中包含n个字符c

举个栗子:

void Teststring(){
	string s1; // 构造空的string类对象s1
	string s2("hello"); // 用C格式字符串构造string类对象s2
	string s3(s2); // 拷贝构造s3
	string s4(10,‘a’); // 10个字符a
}
  1. string类对象的容量操作:
函数名称 功能说明
size 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
clear 清空有效字符(注意:不释放空间)
empty 检测字符串是否为空串(空:返回true,非空:返回false)
reserve 为字符串预留空间
resize 将有效字符串的个数改为n,多出的空间用字符c填充

举个栗子:

void Teststring1(){
	// 注意:string类对象支持直接用cin和cout进行输入和输出
	string s("hello");
	cout << s.size() << endl;
	cout << s.length() << endl;
	cout << s.capacity() << endl;
	cout << s <<endl;
	// 将s中的字符串清空,注意清空时只是将size清0,不改变底层空间的大小
	s.clear();
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	// 将s中有效字符个数增加到10个,多出位置用'a'进行填充
	// “aaaaaaaaaa”
	s.resize(10, 'a');
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	// 将s中有效字符个数增加到15个,多出位置用缺省值'\0'进行填充
	// "aaaaaaaaaa\0\0\0\0\0"
	// 注意此时s中有效字符个数已经增加到15个
	s.resize(15);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
	// 将s中有效字符个数缩小到5个
	s.resize(5);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
	cout << s << endl;
}
  1. string类对象的访问遍历:
函数名称 功能说明
operator [pos] 返回pos位置的字符,const string类对象调用
begin + end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
范围for C++11支持更简洁的范围for遍历方式

举个栗子:

void Teststring(){
	string s("hello");
	// 3种遍历方式:
	// 以下三种方式除了遍历string对象,还可以遍历修改string中的字符,
	// 另外以下三种方式对于string而言,第一种使用最多

	// 1. for+operator[]
	for (size_t i = 0; i < s.size(); ++i)
		cout << s[i] << " ";
	cout << endl;

	// 2.迭代器
	//string::iterator it = s.begin();
	auto it = s.begin();
	while (it != s.end()){
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//string::reverse_iterator rit = s.rbegin();
	auto rit = s.rbegin();
	while (rit != s.rend()){
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
		
	// 3.C++11范围for(基于字符串范围自动遍历)
	for (auto ch : s){
		cout << ch << " ";
	}
	cout << endl;
}
  1. string类对象的修改操作:
函数名称 功能说明
push back 在字符串后尾插字符c
append 在字符串后追加一个字符串
operator+= 在字符串后追加字符串str
c str 返回C格式字符串
find + npos 从字符串pos位置开始往后找字符c,返回该字符在字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回

举个栗子:

void Teststring(){
	string str;
	str.push_back(' '); // 在str后插入空格
	str.append("hello"); // 在str后追加一个字符"hello"
	str += 'b'; // 在str后追加一个字符'b'
	str += "it"; // 在str后追加一个字符串"it"
	cout<<str<<endl;
	cout<<str.c_str()<<endl; // 以C语言的方式打印字符串
	
	// 获取file的后缀
	string file("string.cpp");
	size_t pos = file.rfind('.');
	string suffix(file.substr(pos, file.size()-pos));
	cout << suffix << endl;
	
	// npos是string里面的一个静态成员变量
	// static const size_t npos = -1;
	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos){
		cout << "invalid url" << endl;
		return;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;
	
	// 删除url的协议前缀
	pos = url.find("://");
	url.erase(0, pos+3);
	cout<<url<<endl;
}
  1. string类的非成员函数:
函数名称 功能说明
operator+ 尽量少用,因为传值返回,导致深拷贝效率低
operator>> 输入运算符重载
operator<< 输出运算符重载
getline 获取一行字符串(cin遇到空格和换行都结束,getline只有遇到换行才结束)
relational operators 大小比较

1.2 string类的模拟实现

  • 简单string类的模拟实现(构造、析构、拷贝构造、赋值)

浅拷贝: 让指针等于目标对象的指针,两指针指向同一空间(释放空间会出问题)
深拷贝: 开辟独立的内存空间并将目标对象中的值拷贝过来

// 避免命名冲突,自定义命名空间。
namespace WJL{
	class string{
	public:
		//// 1.构造函数无参
		//string()
		//	// 解决空指针问题
		//	:_str(new char[1])
		//{
		//	_str[0] = '\0';
		//}

		// 2.构造函数带参
		string(char* str = "")
			:_str(new char[strlen(str)+1])
		{
			// _str指向的空间在堆上(可进行灵活操作:修改、扩容)
			strcpy(_str, str);
		}

		// 3.析构函数
		~string(){
			delete[] _str;
			_str = nullptr;
		}

		// 4.拷贝构造(解决浅拷贝:同一空间释放两次) string s2(s1);
		string(const string& s)
			// 深拷贝:拷贝空间
			:_str(new char[strlen(s._str)+1])
		{
			strcpy(_str, s._str);
		}

		// 5.赋值(解决浅拷贝)s1 = s3;
		string& operator=(const string& s){
			// 防止自己给自己赋值
			if (this != &s){
				// 开辟和s3相同大小的临时空间
				char* temp = new char[strlen(s._str) + 1];
				strcpy(temp, s._str);
				// 释放s1的空间
				delete[] _str;
				// s1指向新空间
				_str = temp;
			}

			return *this;
		}
		// 6.size
        size_t size(){
			return strlen(_str);
		}
		
		// 7.operator[]
		char& operator[](size_t i){
			return _str[i];
		}

	private:
		// string是管理字符的数组,底层是一个指向数组的指针
		char* _str;
	};

	void test_string1(){
		// 构造函数和析构函数
		string s1;
		string s2("hello");
	}

	void test_string2(){
		// 拷贝构造
		string s1("hello");
		string s2(s1);

		// 赋值
		string s3("world");
		s1 = s3;
	}
}
  • 复杂string类的模拟实现(增、删、查、改)
#include<assert.h>
// 避免命名冲突,自定义命名空间。
namespace WJL{
	class string{
	public:
		// 迭代器
		typedef char* iterator;

		iterator begin(){
			return _str;
		}

		iterator end(){
			return _str + _size;
		}

		// 1.构造函数
		string(const char* str = ""){
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s){
			::swap(_str,s._str);
			::swap(_size,s._size);
			::swap(_capacity,s._capacity);
		}
	
		// 拷贝构造 现代写法
		string(const string& s){
			:_str(nullptr)
			,_size(0)
			,_capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}

		string& operator=(string s){
			swap(s);
			return *this;
		}

		 // 2.析构函数
		~string(){
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

		// 3.size
		size_t size() const{
			return _size;
		}

		// 4.capacity
		size_t capacity() const{
			return _capacity;
		}

		// 5.operator[]
		char& operator[](size_t i){
			assert(i < _size);
			return _str[i];
		}
		const char& operator[](size_t i) const{
			assert(i < _size);
			return _str[i];
		}

		// 6.c_str
		const char* c_str(){
			return _str;
		}

		// 扩容
		void reserve(size_t n){
			if (n > _capacity){
				char* newstr = new char[n + 1];
				strcpy(newstr, _str);
				delete[] _str;
				_str = newstr;
				_capacity = n;
			}
		}

		// 改变有效字符串个数
		void resize(size_t n, char ch = '\0'){
			if (n < _size){
				_str[n] = '\0';
				_size = n;
			}
			else{
				if (n>_capacity){
					reserve(n);
				}
				for (size_t i = _size; i < n; ++i){
					_str[i] = ch;
				}

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

		// 7.push back
		void push_back(char ch){
			//  检查容量
			if (_size == _capacity){
				size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
				/*char* newstr = new char[newcapacity + 1];
				strcpy(newstr, _str);
				delete[] _str;
				_str = newstr;
				_capacity = newcapacity;*/
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			// 插入字符会覆盖‘\0’
			_str[_size] = '\0';
		}

		// 8.append
		void append(const char* str){
			size_t len = strlen(str);
			if (_size + len > _capacity){
				size_t newcapacity = _size + len;
				/*char* newstr = new char[newcapacity + 1];
				strcpy(newstr, _str);
				delete[] _str;
				_str = newstr;
				_capacity = newcapacity;*/
				reserve(newcapacity);
			}

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

		// 9.operator+=
		string& operator+=(char ch){
			this->push_back(ch);
			return *this;
		}

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

		// 10.insert
		string& insert(int pos, char ch){
			assert(pos < _size);
			if (_size == _capacity){
				size_t newcapacity = _capacity == 0 ? 2 : _capacity * 2;
				reserve(newcapacity);
			}

			// 定义挪动的位置
			int end = _size;
			while (end >= pos){
				// 向后挪数据
				_str[end + 1] = _str[end];
				--end;
			}

			_str[pos] = ch;
			++_size;

			return *this;
		}

		string& insert(int pos, const char* str){
			assert(pos <= _size);

			// 空间不够先进行增容
			size_t len = strlen(str);
			if (_size + len > _capacity){
				reserve(_size + len);
			}

			// 挪动数据
			int end = _size;
			while (end >= pos){
				_str[end + len] = _str[end];
				--end;
			}

			/*for (size_t i = 0; i < len; ++i)
			{
			_str[pos++] = str[i++];
			}*/
			strncpy(_str + pos, str, len);

			_size += len;

			return *this;
		}

		// 11.erase
		string& erase(size_t pos, size_t len = npos){
			assert(pos < _size);
			// _size-pos:pos到结尾的字符个数
			// 全部删完
			if (len >= _size - pos){
				_str[pos] = '\0';
				_size = pos;
			}
			// 需要挪动数据覆盖被删除的字符
			else{
				// 从i开始向前挪动数据
				size_t i = pos + len;
				while (i <= _size){
					_str[i - len] = _str[i];
					++i;
				}

				_size -= len;
			}

			return *this;
		}

		// 12.find
		size_t find(char ch, size_t pos = 0){
			for (size_t i = pos; i < _size; ++i){
				if (_str[i] == ch){
					return i;
				}
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0){
			char* p = strstr(_str, str);
			if (p == nullptr){
				return npos;
			}
			else{
				return p - _str;
			}
		}

		// 13.比大小
		bool operator<(const string& s){
			int ret = strcmp(_str, s._str);
			return ret < 0;
		}

		bool operator==(const string& s){
			int ret = strcmp(_str, s._str);
			return ret == 0;
		}

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

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

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

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


	private:
		// string是管理字符的数组,底层是一个指向数组的指针
		char* _str;
		// 支持扩容
		size_t _size;
		size_t _capacity;

		static size_t npos;
	};

	size_t string::npos = -1;

	// getline就是没有“ ”
	istream& operator>>(istream& in, string& s){
		while (1){
			char ch;
			ch = in.get();
			if (ch == ' ' || ch == '\n'){
				break;
			}
			else{
				s += ch;
			}
		}

		return in;
	}

	ostream& operator<<(ostream& out, const string& s){
		for (size_t i = 0; i < s.size(); ++i){
			out << s[i];
		}
		return out;
	}

	void test_string1(){
		string s1;
		string s2("hello");

		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;

		// 三种遍历方式
		for (size_t i = 0; i < s2.size(); ++i){
			s2[i] += 1;
			cout << s2[i] << " ";
		}
		cout << endl;

		// 迭代器
		string::iterator it = s2.begin();
		while (it != s2.end()){
			*it -= 1;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		s2.push_back('w');
		s2.push_back('o');
		s2.push_back('r');
		s2.push_back('l');
		s2.push_back('d');

		s2.append("hello");

		// 范围for是由迭代器支持的
		for (auto e : s2){
			cout << e << " ";
		}
		cout << endl;


		s1 += 'w';
		s1 += "orld";
		s1.insert(0, 'x');
		s1.insert(0, "zzz");
		s1.erase(0, 1);

		for (auto e : s1){
			cout << e << " ";
		}
		cout << endl;

		cout << s1.find('z') << endl;
		cout << s1.find("world") << endl;

		string s3;
		cin >> s3;
		cout << s3;
	}
}

1.3 拷贝构造和赋值的现代写法

#include<iostream>
using namespace std;

namespace copymodern{
	class string{
	public:
		// 1.构造函数
		string(char* str = "")
			:_str(new char[strlen(str) + 1])
		{
			// _str指向的空间在堆上(可进行灵活操作:修改、扩容)
			strcpy(_str, str);
		}

		// 2.析构函数
		~string(){
			delete[] _str;
			_str = nullptr;
		}

		//// 3.拷贝构造传统写法(解决浅拷贝:同一空间释放两次) string s2(s1);
		//string(const string& s)
		//	// 深拷贝:拷贝空间
		//	:_str(new char[strlen(s._str) + 1])
		//{
		//	strcpy(_str, s._str);
		//}

		// 3.拷贝构造的现代写法
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);
			swap(_str, tmp._str);
		}

		//// 4.赋值传统写法(解决浅拷贝)s1 = s3;
		//string& operator=(const string& s){
		//	// 防止自己给自己赋值
		//	if (this != &s){
		//		// 开辟和s3相同大小的临时空间
		//		char* temp = new char[strlen(s._str) + 1];
		//		strcpy(temp, s._str);
		//		// 释放s1的空间
		//		delete[] _str;
		//		// s1指向新空间
		//		_str = temp;
		//	}
		//	return *this;
		//}

		//// 4.赋值现代写法
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		string tmp(s);
		//		swap(_str, tmp._str);
		//	}

		//	return *this;
		//}

		// 4.赋值现代写法
		string& operator=(string s)
		{
			swap(_str, s._str);
			return *this;
		}

		// 5.size
		size_t size()const{
			return strlen(_str);
		}

		// 6.operator[]
		char& operator[](size_t i){
			return _str[i];
		}

	private:
		// string是管理字符的数组,底层是一个指向数组的指针
		char* _str;
	};
}

ostream& operator<<(ostream& out, const string& s){
	for (size_t i = 0; i < s.size(); ++i){
		out << s[i];
	}
	return out;
}

int main(){
	string s1("hello");
	string s2(s1);
	cout << s2 << endl;

	string s3("world");
	s1 = s3;
	cout << s1 << endl;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章