ヾ(????)?" 人總要為過去的懶惰而付出代價ヾ(????)?"
一、深淺拷貝
淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來。如果對象中管理資源,最后就會導致多個對象共享同一份資源,當一個對象銷毀時就會將該資源釋放掉,而此時另一些對象不知道該資源已經被釋放,以為還有效,所以當繼續(xù)對資源進項操作時,就會發(fā)生發(fā)生了訪問違規(guī)。
淺拷貝:(1)析構兩次,造成程序崩潰(2)一個對象修改影響另外一個
如果一個類中涉及到資源的管理,其拷貝構造函數(shù)、賦值運算符重載以及析構函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。
編譯器默認生成的拷貝構造,是淺拷貝,會是兩個對象指向同一塊空間,當程序結束的時候,那么兩個對象都會進行銷毀,那么一塊空間就會進行多次釋放,從而引起崩潰。
深拷貝:給每一個對象分配資源,保證多個對象之間不會因為共享資源而導致多次釋放造成程序崩潰。
二、傳統(tǒng)版寫法的string類(簡單)
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace yyqx//為了與庫里面的string進行區(qū)分
{
//僅僅實現(xiàn)一個簡單的string,僅僅考慮資源管理深淺拷貝問題
class string
{
public:
//構造函數(shù)
string(const char* str)
:_str(new char[strlen(str) + 1])//這里的+1,是為了'\0'開辟空間
{
strcpy(_str, str);//拷貝的時候'\0'也拷貝了
}
//拷貝構造(深拷貝)
//s2(s1)
string(const string& s)
:_str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
//賦值,也會有深淺拷貝的問題
string& operator=(const string& s)
{
if (this != &s)//避免自己給自己賦值,會導致值被釋放,就會變成隨機值
{
//delete[] _str;//首先進行釋放
//_str = new char[strlen(s._str) + 1];//C++的new是不需要檢查是否開辟空間
會拋異常
//strcpy(_str, s._str);
//為了避免開辟空間失敗,而本來的空間也被我們釋放,可以先開啟空間,
//進行拷貝,然后再釋放
char* tmp = new char[strlen(s._str) + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
}
return *this;
}
//析構函數(shù)
~string()
{
if (_str)
{
delete[] _str;
}
}
//目的為了輸出字符串
const char* c_str() const
{
return _str;
}//返回c格式的字符串
//重載[]
char& operator[](size_t pos)
{
assert(pos < strlen(_str));//注意這里的范圍
return _str[pos];
}
size_t size()
{
return strlen(_str);
}
private:
char* _str;
};
}
賦值運算符重載也會有深淺拷貝的問題。賦值,對象本身是有值的【拷貝的時候,如果空間小,就會不夠,空間大,就會造成資源浪費】
三、string類的模擬實現(xiàn)
string的增刪查改以及使用string【傳統(tǒng)】
基本框架:
#pragma once
#include <iostream>
using namespace std;
#include <assert.h>
namespace yyqx//為了與庫里面的string進行區(qū)分
{
class string
{
public:
構造函數(shù)+析構函數(shù):
寫法1:
//構造函數(shù)
string(const char* str)
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[strlen(str) + 1];//這里的+1,是為了'\0'開辟空間
strcpy(_str, str);//拷貝的時候'\0'也拷貝了
}
string()//注意,這里不是給的空,而是給了一個空的字符串//標準庫里的就是給了一個""
:_size(0)
,_capacity(0)
{
_str = new char[1];
_str[0] = '\0';
}
- 構造函數(shù):初始化列表,初始化的順序并不是初始化列表的順序,而是成員變量在類中的聲明次序。
- 構造函數(shù):注意默認的構造函數(shù)【編譯器自動生成、缺省、函數(shù)重載】,默認的構造函數(shù)這里選擇寫一個同名函數(shù),注意這里并不是給一個空指針,而是給了一個空字符串。
寫法2:(最優(yōu)寫法)
string(const char* str = "")//這里默認值不能給nullptr,strlen以及拷貝strcpy會崩潰
:_size(strlen(str))
,_capacity(_size)
{
_str = new char[strlen(str) + 1];//這里的+1,是為了'\0'開辟空間
strcpy(_str, str);//拷貝的時候'\0'也拷貝了
}
//析構函數(shù)
~string()
{
if (_str)
{
delete[] _str;
_str = nullptr;//好習慣
_size = 0;
_capacity = 0;
}
}
- 缺省值這不能給nullptr,strlen以及拷貝strcpy時程序會崩潰
- 注意初始化列表
- strcpy注意,拷貝的時候’\0’也拷貝了
- new開空間的時候,一定要多開一個給’\0’
拷貝構造+賦值重載函數(shù)+其他:
//拷貝構造(深拷貝)
//s2(s1)
string(const string& s)
:_size(strlen(s._str))
,_capacity(_size)
{
_str = new char[_capacity + 1];
strcpy(_str, s._str);
}
//賦值,也會有深淺拷貝的問題
string& operator=(const string& s)
{
if (this != &s)//避免自己給自己賦值,會導致值被釋放,就會變成隨機值
{
char* tmp = new char[s._capacity + 1];
strcpy(tmp, s._str);
delete[] _str;
_str = tmp;
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
//目的為了輸出字符串
const char* c_str() const
{
return _str;
}//返回c格式的字符串
char& operator[](size_t pos)//這里僅僅可以傳入對象,不能傳入const對象,如果是const對象,就會報錯
{
assert(pos < _size);//注意這里的范圍
return _str[pos];
}
const char& operator[](size_t pos) const//這里就可以傳入const對象
{
assert(pos < _size);
return _str[pos];
}
//這里的const修飾的是this指針指向的對象const string s;
size_t size() const//寫const,普通對象以及const對象都可以調用,如果不加const對象就不可以調用
{
return _size;
}
size_t capacity() const//寫const,普通對象以及const對象都可以調用
{
return _capacity;
}
添加:
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
void reverse(size_t n)//一個擴容的作用
{
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;//注意這里的釋放不是free
_str = tmp;
_capacity = n;
}
}
void resize(size_t n, char ch = '\0')
{
if (n < _size)
{
_size = n;
_str[_size] = '\0';
}
else
{
if (n > _capacity)
{
reverse(n);
}
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
void push_back(char ch)
{
if (_size == _capacity)
{
reverse(_capacity == 0 ? 4 : _capacity * 2);//如果是一個空字符串,就會導致并沒有擴容,
//擴容要注意剛開始沒有容量的情況下
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';//注意\0,容易遺漏
}
//append插入的字符個數(shù)是未知的,擴容二倍也不一定足夠
void append(const char* str)
{
size_t len = _size + strlen(str);
if (len > _capacity)
{
reverse(len);
}
strcpy(_str + _size, str);
_size = len;
}//但是我們一般用+=
- 判斷容量是否滿,如果 _size= _ capacity,容量擴2倍,new一個新容量的空間,釋放舊空間,最后指針指向新的空間。
- append (append插入的字符個數(shù)是未知的,擴容二倍也不一定足夠:解決辦法:reverse預留空間【一個擴容的作用】)
- reverse 為string預留空間,避免多次擴容(提高效率)
- resize用處:擴空間+初始化;刪除數(shù)據(jù)保留前n個
插入:
string& insert(size_t pos, char ch)
{
assert(pos <= _size);//這里的=_size相當于尾插
//注意,這里容易忘記,size_t就已經大于等于0了,所以在這里我們主要保證pos是小于_size即可
if (_size == _capacity)
{
reverse(_capacity == 0 ? 4 : 2 * _capacity);
}
//不可以用strcpy,這里不可以是同一塊地址,對導致內容不是我們想要的
//最后一個未知的字符移到_size然后就是倒數(shù)第二位移動,從后向前移動
size_t end = _size + 1;
//注意這里如果end=_size,當頭插的時候,進入循環(huán)end會變成-1,因為是size_t所以又會進入循環(huán),導致錯誤
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
return *this;
}
//插入\0,用c_str(遇到\0停止打印)打印顯示在屏幕的字符串長度會減小或者不變,但是_size會變大
//用范圍for或者迭代器可以打印出來
string& insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reverse(_size + len);
}
size_t end = _size + len;
while (end > pos + len - 1)//這里注意
{
_str[end] = _str[end - len];
--end;
}
strncpy(_str + pos, str, len);//防止為了遇見\0就不拷貝了(strcpy遇見\0就不拷貝了)
_size += len;
return *this;
}
插入字符:
- 不可以用strcpy,在字符進行向后移的時候,不可以是同一塊地址,對導致內容不是我們想要的,最后一個未知的字符移到_size然后就是倒數(shù)第二位移動,從后向前移動
- end=_size,當頭插的時候,進入循環(huán)end會變成-1,因為是size_t,又是大于0所以又會進入循環(huán),導致代碼錯誤
插入字符串:
- 防止為了遇見\0就不拷貝了,所以用的是strncpy(strcpy遇見\0就不拷貝了)
刪除:
//刪除
string& erase(size_t pos, size_t len = npos)
{
assert(pos < _size);
//刪除的數(shù)據(jù)大于等于_size
if (len == npos || pos + len >= npos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
size_t begin = pos + len;
while (begin <= _size)
{
_str[begin - len] = _str[begin];
++begin;
}
_size -= len;
}
return *this;
}
注意:npos類中靜態(tài)成員的初始化,必須在類外,類和對象(下)本篇文章中有詳細說明?!綾onst在定義的時候必須初始化,但是靜態(tài)成員的變量初始化又在外面】
查找:
size_t find(char ch, size_t pos = 0)
{
for (; pos < _size; ++pos)
{
if (_str[pos] == ch)
{
return pos;
}
}
return npos;
}
size_t find(const char* str, size_t pos = 0)
{
const char* p = strstr(_str + pos, str);
if (p == nullptr)
{
return npos;
}
else
{
return p - _str;
}
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
private:
char* _str;
size_t _size;//有效字符的個數(shù)
size_t _capacity;//存儲有效字符的空間大小
const static size_t npos;//正確的寫法是在類外進行初始化
//const static size_t npos = -1;//這種寫法也可以,但是違背了正確的寫法,要注意
};
const size_t string::npos = -1;
- strstr返回的是指針,沒有找到返回空指針。
流插入和流提取:
/流插入和流提取
//在類外
//不可以用c_str(),因為遇見\0會停止
//'\0'是不可以見字符,不會顯示
//流插入
ostream& operator<<(ostream& out, const string& s)
{
for (auto ch : s)
{
out << ch;
}
return out;
}
//流提取,字符從面板提取到s
istream& operator>>(istream& in, string& s)
{
s.clear();
//要把對象里面的字符清理掉,否則當對象不是空的時候,會導致字符直接加到已有對象的后面。
//但是我們想要的是,對象是我們輸入的字符串
//第一種思路(缺點:頻繁的+=,字符串過大,會導致頻發(fā)的擴容,影響效率)
/*char ch;
ch = in.get();
if (ch != ' ' && ch != '\n')
{
s += ch;
ch = in.get();
}
return in*/
//第二種思路(這種思路比較優(yōu),無論大小都可以避免頻繁擴容)
char ch;
ch = in.get();
char buff[128] = { '\0' };
size_t i = 0;
if (ch != ' ' && ch != '\n')
{
buff[i++] = ch;
if (i == 127)
{
s += buff;
memset(buff, '\0', 128);
i = 0;
}
ch = in.get();
}
s += buff;
return in;
}
- '\0’是不可以見字符,不會顯示
- clear()要把對象里面的字符清理掉,否則當對象不是空的時候,會導致字符直接加到已有對象的后面。但是我們想要的是,對象是我們輸入的字符串
運算符重載:
//運算符重載
//比較大小
//全局函數(shù).可以類比日期類
bool operator<(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) < 0;
}
bool operator==(const string& s1, const string& s2)
{
return strcmp(s1.c_str(), s2.c_str()) == 0;
}
bool operator<=(const string& s1, const string& s2)
{
return s1 < s2 || s1 == s2;
}
bool operator>(const string& s1, const string& s2)
{
return !(s1 <= s2);
}
bool operator>=(const string& s1, const string& s2)
{
return s1 > s2 || s1 == s2;
}
bool operator!=(const string& s1, const string& s2)
{
return !(s1 == s2);
}
}//這個是yyqx的大括號
這里是在全局變量,沒有在類里面,是在類外
迭代器:
string類private里面:
public:
//迭代器
typedef char* iterator;
typedef const char* const_iterator;
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
四、現(xiàn)代版寫法的string類
拷貝構造和賦值的現(xiàn)代寫法
//拷貝構造(深拷貝)
//s2(s1)//現(xiàn)代寫法,剝削行為,要完成深拷貝,
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
:_str(nullptr)
,_size(0)
,_capacity(0)//這里要進行初始化,否則交換后,局部變量的銷毀(隨機值銷毀,不可以)
{
//構造一個對象tmp,tmp里所有的東西this想要。this和tmp
string tmp(s._str);//局部變量,出了作用域會銷毀
swap(tmp);
//tmp出了作用域會銷毀
}
//賦值,也會有深淺拷貝的問題
//現(xiàn)代寫法
//第一種
//string& operator=(const string& s)
//{
// if (this != &s)//避免自己給自己賦值,會導致值被釋放,就會變成隨機值
// {
// string tmp(s._str);
// swap(tmp);//把tmp給this,出了作用域把this給tmp的值進行銷毀
// }
// return *this;
//}
//第二種
string& operator=(string s)//傳值傳參,拷貝構造,拷貝的值給this,并不會導致s的實參發(fā)生變化
{
swap(s);
return *this;
}
//掌握現(xiàn)代寫法
補充知識點
遍歷方式中有一個是范圍for(范圍for的底層實現(xiàn)是迭代器,如果沒有迭代器的程序,代碼會進行報錯)
代碼展示:
yyqx::string s("hello 12345");
for (auto ch : s)
{
cout << ch << " ";
}
cout << endl;
在c語言中,我們用atoi。
string中的兩個常用函數(shù)文章來源:http://www.zghlxwxcb.cn/news/detail-712951.html
五、總結
以上就是今天要講的內容,本文詳細的介紹了淺拷貝、淺拷貝和string的模擬實現(xiàn)。本文以及一文帶你走進string詳細的介紹了string的相關知識,希望給友友們帶來幫助!文章來源地址http://www.zghlxwxcb.cn/news/detail-712951.html
到了這里,關于【C++ ? STL】探究string的源碼的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!