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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章