1. 程式人生 > >C++string類常用介面說明,深淺拷貝

C++string類常用介面說明,深淺拷貝

標準庫中的string類

  1. string是表示字串的字串類
  2. 該類的介面與常規容器的介面基本相同,再添加了一些專門用來操作string的常規操作。
  3. string在底層實際是:basic_string模板類的別名,typedef basic_string<char,char_traits,allocator>string;
  4. 不能操作多位元組或者變長字元的序列

在使用string類時,必須包含標頭檔案以及using namespace std;

string類的常用介面說明:

1.string類物件的常見構造

  • string()    構造空的string類物件,既空字串
  • string(const char* s)    用C格式字串來構造string物件
  • string(size_t n,char c)    string類物件中包含n個字元c
  • string(const string&s)    拷貝建構函式
  • string(const string&s,size_t n)    用s中前5個字元構造新的string物件
void TestString1()
{
	string s1;//構造空的string類物件s1
	string s2("hello word");//用C格式字串構造string類物件s2
	string s3(10, 'a');//用10個字元'a'構造string類物件s3
	string s4(s2);//拷貝構造s4
	string s5(s3, 8);//用s3中下標為8的字元構造string物件s5
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;

}

 執行結果:

2.string類物件的容量操作

  • size_t size() const    返回字串有效字元長度
  • size_t length() const    返回字串有效字元長度
  • size_t capacity() const    返回空間總大小
  • bool empty() const    檢測字串釋放為空串,是返回true,否則返回false
  • void clear()    清空有效字元
  • void resize(size_t n,char c)    將有效字元的個數改成n個,多出的空間用字元c填充
  • void resize(size_t n)    將有效字元的個數改成n個,多出的空間用0填充
  • void reserve(size_t res_arg = 0)    為字串預留空間
void TestString1()
{
	//注意:string類物件支援直接用cin和cout進行輸入和輸出
	string s("hello,word!!!");
	cout << s.length() << endl;
	cout << s.size() << 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;
	cout << s << 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;
}

void TestString2()
{
	string s;
	//測試reserve是否會改變string中有效元素個數
	s.reserve(100);
	cout << s.size() << endl;
	cout << s.capacity() << endl;

	//測試reserve引數小於string的底層空間大小時,是否會將空間縮小
	s.reserve(50);
	cout << s.size() << endl;
	cout << s.capacity() << endl;
}

測試結果如下:

注意:

  1. size()與length()方法底層實現原理完全相同,引入size()的原因是為了與其他容器的介面保持一致,一般情況下用size().
  2. clear()只是將string中有效字元清空,不改變底層空間大小
  3. resize(size_t n)與resize(size_t n,char c)都是將字串中有效字元個數改變到n個不同的是當字元個數增多時:resize(n)用0來填充多出的元素空間,resize(size_t n,char c)用字元c來填充多出的元素空間。注意:resize在改變元素個數時,如果是將元素個數增多,可能會改變底層容量的大小,如果是將元素個數減少,底層空間總大小不變
  4. reserve(size_t res_arg = 0):為string預留空間,不改變有效元素個數,當reserve的引數小於string的底層空間總大小時,reserve不會改變容量大小。

3.string類物件的訪問操作

  • char& operator[](size_t pos)    返回pos位置的字元,const string類物件呼叫
  • const char& operator[](size_t pos)    返回pos位置的字元,非const string類物件呼叫
void TestString1()
{
	string s1("hello word");
	const string s2("Hello word");
	cout << s1 << " " << s2 << endl;
	cout << s1[0] << " " << s2[0] << endl;

	s1[0] = 'H';
	cout << s1 << endl;

	for (size_t i = 0; i < s1.size(); ++i)
	{
		cout << s1[i] << endl;
	}

	//s2[0] = 'h';  編譯失敗,因為const型別物件不能修改
}

執行結果如下:

4.string類物件的修改操作

  • void push_back(char c)    在字串後尾插字元c
  • string& append(const char* s)    在字串後追加一個字串
  • string& operator+=(const string& str)    在字串後追加字串str
  • string& operator+=(const char*s)    在字串後追加C格式字串
  • string& operator+=(char c)    在字串後追加字元c
  • const char* c_str() const    返回C格式字串
  • size_t find(char c,size_t pos=0) const    從字串pos位置開始往後找字元c,返回該字元在字串中的位置
  • size_t rfind(char c,size_t pos = npos)    從字串pos位置開始往前找字元c,返回該字元在字串中的位置
  • string substr(size_t pos=0,size_t n=npos) const    在str中從pos位置開始,擷取n個字元,然後將其返回
void TestString1()
{
	string str;
	str.push_back(' ');//在str後插入空格
	str.append("hello");//在str後追加一個字串
	str += 'w';//在str後追加一個字元
	str += "ord";//在str後面追加一個字串
	cout << str << endl;
	cout << str.c_str() << endl;//以C語言的格式列印字串

	//獲取檔案的字尾
	string file1("string.cpp");
	size_t pos = file1.rfind('.');
	string file2(file1.substr(pos, file1.size() - pos));
	cout << file2 << endl;

	//npos是string裡面的一個靜態成員變數
	//static const size_t npos = -1;
	//取出url的域名
	string url("https://blog.csdn.net/Damn_Yang");
	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;
}

//利用reserve提高插入資料的效率,避免增容帶來的開銷
void TestPushBack()
{
	string s;
	size_t sz = s.capacity();
	cout << "making s grow : \n";
	for (int i = 0; i < 100; ++i)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
}

void TestPushBack_P()
{
	string s;
	s.reserve(50);
	size_t sz = s.capacity();
	cout << "making s grow: \n";
	for (int i = 0; i < 100; i++)
	{
		s += 'c';
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
}

執行結果如下:

注意:

  1. 在string尾部追加字元時,s.push_back/s.append(1,c)/s += 'c'三種實現方式差不多,一般情況下string類的+=操作用的比較懂,+=操作不僅可以連結單個字元,還可以連線字串。
  2. 對string操作時,如果能夠大概預估到放多少字元,可以先通過reserve把空間預留好。

5.string類非成員函式

  • operator+    連線字串  儘量少用,因為效率低
  • operator>>    輸入運算子過載
  • operator<<    輸出運算子過載
  • getline            獲取一行字串
  • relational operators        大小比較

string的訪問方式:

1.迭代器--不常用,跟其他容器保持統一的訪問方式

void TestString2()
{
	string num("1234");
	//迭代器給出統一的方式訪問容器,遮蔽掉底層複雜的結構細節
	string::iterator it = num.begin();//指向第一個位置迭代器
	int value = 0;
	while (it != num.end())//指向最後一個數據的迭代器
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	
}

void TestString3()
{
	string num("1234");
	//反向迭代器
	string::reverse_iterator rit = num.rbegin();
	while (rit != num.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}

2.for+下標--更常用

void TestString4()
{
	string num("1234");
	for (size_t i = 0; i < num.size(); ++i)
	{
		cout << num[i] << " ";
	}
	cout << endl;
}

3.C++11 語法糖

void TestString5()
{
	string num("1234");
	for (auto& e : num)
	{
		cout << e << " ";
	}
	cout << endl;
}

淺拷貝

淺拷貝:也成為拷貝,編譯器只是將物件中的值拷貝過來。如果物件中管理資源,最後就會導致多個物件共享同一份資源,當一個物件銷燬時就會將該資源釋放掉,而此時另一些物件不知道該資源已經釋放,以為還有效,所以當繼續對資源進行操作時,就會發生訪問違規。

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

說明:上述String類沒有顯示定義其拷貝建構函式和運算子過載,此時編譯器會生成預設的,當用s1構造s2時,編譯器會呼叫預設的拷貝構造,最終導致的問題是:s1和s2共用同一塊記憶體空間,在釋放時同一塊空間被釋放多次而引起程式崩潰,這種拷貝方式,稱為淺拷貝

深拷貝

如果一個類中涉及到資源的管理,其拷貝建構函式、賦值運算子過載以及解構函式必須要顯式給出。一般情況都是按照深拷貝方式提供。

深拷貝:給每個物件獨立分配資源,保證多個物件之間不會因共享資源而造成多次釋放造成程式崩潰的問題
傳統寫法:

class String
{
	
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String&s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	
	String& operator=(const String&s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

現代寫法:

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			str = "";
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(String& s)
		: _str(nullptr)
	{
		String strTmp(s._str);
		swap(_str, strTmp._str);
	}

	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}
	/*String& operator=(const String&s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}*/
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

3.寫時拷貝

寫時拷貝就是一種拖延症,是在淺拷貝的基礎之上增加了引用技術的方式來實現的。

引用計數:用來記錄資源使用者的個數。在構造時,將資源的計數給成1,每增加一個物件使用該資源,就給計數增加1,當某個物件被銷燬時,先給該計數減1,然後再檢查是否需要釋放資源,如果計數為1,說明該物件時資源的最後一個使用者,將該資源釋放;否則就不能釋放,因為還有其他物件在使用該資源。