C++?string類擴(kuò)展知識(shí)全解析
1.string類的模擬實(shí)現(xiàn)

說明:上述String類沒有顯式定義其拷貝構(gòu)造函數(shù)與賦值運(yùn)算符重載,此時(shí)編譯器會(huì)合成默認(rèn)的,當(dāng)用s1構(gòu)造s2時(shí),編譯器會(huì)調(diào)用默認(rèn)的拷貝構(gòu)造。最終導(dǎo)致的問題是,s1、s2共用同一塊內(nèi)存空間,在釋放時(shí)同一塊空間被釋放多次而引起程序崩潰,這種拷貝方式,稱為淺拷貝。
1.1 淺拷貝
淺拷貝:也稱位拷貝,編譯器只是將對(duì)象中的值拷貝過來。如果對(duì)象中管理資源,最后就會(huì)導(dǎo)致多個(gè)對(duì)象共享同一份資源,當(dāng)一個(gè)對(duì)象銷毀時(shí)就會(huì)將該資源釋放掉,而此時(shí)另一些對(duì)象不知道該資源已經(jīng)被釋放,以為還有效,所以當(dāng)繼續(xù)對(duì)資源進(jìn)項(xiàng)操作時(shí),就會(huì)發(fā)生發(fā)生了訪問違規(guī)。
就像一個(gè)家庭中有兩個(gè)孩子,但父母只買了一份玩具,兩個(gè)孩子愿意一塊玩,則萬事大吉,萬一不想分享就你爭我奪,玩具損壞。
可以采用深拷貝解決淺拷貝問題,即:每個(gè)對(duì)象都有一份獨(dú)立的資源,不和其他對(duì)象共享。父母給每個(gè)孩子都買一份玩具,各自玩各自的就不會(huì)有問題了。
1.2 深拷貝
如果一個(gè)類中涉及到資源的管理,其拷貝構(gòu)造函數(shù)、賦值運(yùn)算符重載以及析構(gòu)函數(shù)必須要顯式給出。一般情況都是按照深拷貝方式提供。

1.3 傳統(tǒng)版寫法的String類
//傳統(tǒng)寫法
string::string(const string& s)
{
_str = new char[s._capacity + 1];
//strcpy不能拷貝中間有'\0'的字符串
//strcpy(_str, s._str);
memcpy(_str, s._str, s._size + 1);
_size = s._size;
_capacity = s._capacity;
}
//傳統(tǒng)寫法
//s1=s3
string& string::operator=(const string& s)
{
//&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;
}1.4 現(xiàn)代版寫法的String類
//現(xiàn)代寫法:本質(zhì)效率沒有提升,只是寫法不同
string::string(const string& s)
{
//this指針沒初始化,可能有風(fēng)險(xiǎn),所以可以在聲明變量時(shí)可以設(shè)置默認(rèn)參數(shù)
//private:
// char* _str=nullptr;
// size_t _size=0;
// size_t _capacity=0;
string tmp(s._str);
swap(tmp);
}
//現(xiàn)代寫法
string& string::operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}
//現(xiàn)代寫法更簡潔版
string& string::operator=(string tmp)
{
swap(tmp);
return *this;
}1.4 寫時(shí)拷貝(了解)
寫時(shí)拷貝就是一種拖延癥,是在淺拷貝的基礎(chǔ)之上增加了引用計(jì)數(shù)的方式來實(shí)現(xiàn)的。
引用計(jì)數(shù):用來記錄資源使用者的個(gè)數(shù)。在構(gòu)造時(shí),將資源的計(jì)數(shù)給成1,每增加一個(gè)對(duì)象使用該資源,就給計(jì)數(shù)增加1,當(dāng)某個(gè)對(duì)象被銷毀時(shí),先給該計(jì)數(shù)減1,然后再檢查是否需要釋放資源,如果計(jì)數(shù)為1,說明該對(duì)象時(shí)資源的最后一個(gè)使用者,將該資源釋放;否則就不能釋放,因?yàn)檫€有其他對(duì)象在使用該資源
1.5 string類的模擬實(shí)現(xiàn)
構(gòu)造函數(shù)和析構(gòu)函數(shù)不能有返回值
string模擬實(shí)現(xiàn)參考
//string.h
#pragma once
#include<iostream>
//strlen需要包含的
#include<string.h>
#include<assert.h>
#include<algorithm>
using namespace std;
//string與庫里有命名沖突
namespace bit
{
class string
{
public:
//1)[]下標(biāo)訪問
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
//2)范圍for底層是迭代器
typedef char* iterator;
typedef const char* const_iterator;//內(nèi)容不能修改
//開始位置的迭代器
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
size_t size() const
{
return _size;
}
const char* c_str() const
{
return _str;
}
//常量字符串默認(rèn)結(jié)尾有'\0'
string(const char* str = "");
~string();
string(const string& s);
string& operator=(const string& s);
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
//清除數(shù)據(jù)
void clear()
{
_str[0] = '\0';
_size = 0;
}
string& operator+=(const char* str)
{
append(str);
return *this;
}
string& operator+=(char ch)
{
push_back(ch);
return *this;
}
void resize(size_t n, char ch = '\0');
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos=0, size_t len=npos);
size_t find(char ch, size_t pos = 0);
size_t find(const char* str, size_t pos = 0);
string substr(size_t pos = 0, size_t len = npos);
void swap(string& s);
bool operator<(const string& s) const;
bool operator<= (const string & s) const;
bool operator>(const string& s) const;
bool operator>=(const string& s) const;
bool operator==(const string& s) const;
bool operator!=(const string& s) const;
private:
char* _str;
size_t _size;
size_t _capacity;
public:
//在.cpp中定義,但是const static整型可以這么用,特殊處理
const static size_t npos=-1;
//double不支持
//const static double npos = 1.0;
};
std::ostream& operator<<(std::ostream& out, const string& s);
std::istream& operator>>(std::istream& in, string& s);
std::istream& getline(std::istream& in, string& str, char delim = '\n');
}//string.cpp
#include"string.h"
namespace bit
{
//_str要預(yù)留一個(gè)字節(jié)存'\0'
//string()
// :_str(new char[1] {'\0'} )
// , _size(0)
// , _capacity(0)
//{}
string::string(const char* str)
:_size(strlen(str))
{
//不能用sizeof,只能計(jì)算數(shù)組大小
_str = new char[_size + 1];
_capacity = _size;
strcpy(_str, str);
}
//析構(gòu)未完成
string::~string()
{
delete[] _str;
_str = nullptr;
_size = 0;
_capacity = 0;
}
void string::reserve(size_t n)
{
//永遠(yuǎn)多開一個(gè)空間
//capacity和size是有效數(shù)字個(gè)數(shù)
if (n > _capacity)
{
//擴(kuò)容
char* tmp = new char[n + 1];
//strcpy(tmp, _str);
memcpy(tmp, _str, _size + 1);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
void string::push_back(char ch)
{
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
_str[_size] = ch;
_size++;
_str[_size] = '\0';
}
//添加字符
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(std::max(_size + len, _capacity * 2));
}
//strcpy(_str + _size, str);
memcpy(_str + _size, str, len + 1);
_size += len;
}
void string::insert(size_t pos, char ch)
{
//可以在'\0'(下標(biāo)為_size)位置直接插
assert(pos <= _size);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動(dòng)數(shù)據(jù)
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
++_size;
}
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size == _capacity)
{
reserve(_capacity == 0 ? 4 : _capacity * 2);
}
//挪動(dòng)數(shù)據(jù),第一種方法
//(int)pos 將 pos 強(qiáng)制轉(zhuǎn)換為 int
//是為了避免 end(有符號(hào) int)與無符號(hào) pos(通常 size_t)比較時(shí)可能出現(xiàn)的無符號(hào)整數(shù)回繞問題
//例如當(dāng) pos=0 時(shí),如果 end 是無符號(hào)且遞減到 0,再減 1 會(huì)變成極大值,導(dǎo)致無限循環(huán)
//這里 end 是 int,pos 轉(zhuǎn)成 int,比較在 int 域中進(jìn)行,安全。
/*int end = _size;
while (end > (int)pos)
{
_str[end+len] = _str[end];
--end;
}
strncpy(_str + pos, str, len);
_size += len;*/
size_t end = _size + len;
while (end > pos + len - 1)
{
_str[end] = _str[end - len];
--end;
}
//strncpy(_str + pos, str, len);為了統(tǒng)一寫成下面這種
memcpy(_str + pos, str, len);
_size += len;
}
void string::erase(size_t pos, size_t len)
{
//刪除有效字符個(gè)數(shù),不是assert(pos <= _size);
assert(pos < _size);
//說明刪除后面所有
//npos是整型的最大值,len==npos就是刪完所有的意思
if (len == npos || len >= _size - pos)
{
//直接覆蓋
_size = pos;
_str[_size] = '\0';
}
else
{
//刪除部分
//把后面往前覆蓋
//strcpy(_str + pos , _str + pos + len);
//需要移動(dòng)的長度[str+pos+len,_size],左閉右閉,左減右+1
memcpy(_str + pos, _str + pos + len, _size - (pos + len) + 1);
_size -= len;
}
}
//傳統(tǒng)寫法
//string::string(const string& s)
//{
// _str = new char[s._capacity + 1];
// //strcpy不能拷貝中間有'\0'的字符串
// //strcpy(_str, s._str);
// memcpy(_str, s._str, s._size + 1);
// _size = s._size;
// _capacity = s._capacity;
//}
//現(xiàn)代寫法:本質(zhì)效率沒有提升,只是寫法不同
string::string(const string& s)
{
//this指針沒初始化,可能有風(fēng)險(xiǎn),所以可以在聲明變量時(shí)可以設(shè)置默認(rèn)參數(shù)
//private:
// char* _str=nullptr;
// size_t _size=0;
// size_t _capacity=0;
string tmp(s._str);
swap(tmp);
}
//傳統(tǒng)寫法
//s1=s3
//string& string::operator=(const string& s)
//{
// //&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;
//}
//現(xiàn)代寫法
string& string::operator=(const string& s)
{
if (this != &s)
{
string tmp(s._str);
swap(tmp);
}
return *this;
}
//現(xiàn)代寫法更簡潔版
//string& string::operator=(string tmp)
//{
// swap(tmp);
// return *this;
//}
std::ostream& operator<<(std::ostream& out, const string& s)
{
for (auto ch : s)
{
cout << ch;
}
return out;
}
//改變?nèi)萜鞯拇笮?,多余空間賦值ch
void string::resize(size_t n, char ch)
{
if (n <= _size)
{
//刪除,保留前n個(gè)
_size = n;
_str[_size] = '\0';
}
else
{
reserve(n);
for (size_t i = _size; i < n; i++)
{
_str[i] = ch;
}
_size = n;
_str[_size] = '\0';
}
}
size_t string::find(char ch, size_t pos)
{
assert(pos < _size);
for (size_t i = pos; i < _size; i++)\
{
if (_str[i] == ch)
return i;
}
return npos;
}
size_t string::find(const char* str, size_t pos)
{
//找到對(duì)應(yīng)指針,否則返回空
const char* ptr = strstr(_str + pos, str);
//如果找到讓它返回下標(biāo)
if (ptr)
return ptr - str;
else
return npos;
}
//從字符串pos位置提取n個(gè)字符拷貝
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
if (len == npos || len >= _size - pos)
{
len = _size - pos;
}
string sub;
reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
std::istream& operator>>(std::istream& in, string& s)
{
s.clear();
char buff[256];
int i = 0;
char ch;
//這樣寫讀取不到' '
//in >> ch;
ch = in.get();
while (ch != '\n' && ch != ' ')
{
buff[i++] = ch;
//長的字符串
if (i == 255)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
//短的字符串
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
bool string::operator<(const string& s) const
{
return strcmp(_str, s._str) < 0;
}
bool string::operator<= (const string & s) const
{
return *this < s || *this == s;
}
bool string::operator>(const string& s) const
{
return !(*this <= s);
}
bool string::operator>=(const string& s) const
{
return!(*this < s);
}
bool string::operator==(const string& s) const
{
return strcmp(_str, s._str) == 0;
}
bool string::operator!=(const string& s) const
{
return !(*this == s);
}
std::istream& getline(std::istream& in, string& s, char delim)
{
s.clear();
char buff[256];
int i = 0;
char ch;
//這樣寫讀取不到' '
//in >> ch;
ch = in.get();
while (ch != delim )
{
buff[i++] = ch;
//長的字符串
if (i == 255)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();
}
//短的字符串
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
//庫里的swap函數(shù),會(huì)拷貝三次,代價(jià)太大,下面的更高效
void string::swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
}//test.cpp
#include"string.h"
namespace bit
{
void test_string1()
{
string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2[0] = 'x';
cout << s2.c_str() << endl;
for (size_t i = 0; i < s2.size(); i++)
{
s2[i]++;
}
cout << s2.c_str() << endl;
const string s3 = "hello world";//隱式類型轉(zhuǎn)換,構(gòu)造+拷貝構(gòu)造->優(yōu)化為構(gòu)造
for (size_t i = 0; i < s3.size(); i++)
{
cout << s3[i] << '-';
}
cout << endl;
for (auto ch : s3)
{
cout << ch << " ";
}
cout << endl;
string s4 = "hello world";
string::iterator it4 = s4.begin();
while (it4 != s4.end())
{
*it4 += 1;
//類似指針
cout << *it4 << " ";
++it4;
}
string::const_iterator it3 = s3.begin();
while (it3 != s3.end())
{
//類似指針
cout << *it3 << " ";
++it3;
}
}
void test_string2()
{
bit::string s1;
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2.push_back('x');
s2.push_back('y');
s2.push_back('z');
cout << s2.c_str() << endl;
string s3("hello");
s3.append("xxxxxxxxxxxxxxxxxx");
cout << s3.c_str() << endl;
string s4("world");
s4.append("xx");
cout << s4.c_str() << endl;
s4+= '*';
s4 += "hello bit";
cout << s4.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.insert(5, 'x');
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2.insert(5, "yyy");
cout << s2.c_str() << endl;
}
void test_string4()
{
string s1("hello world");
cout << s1.c_str() << endl;
s1.erase(5, 2);
cout << s1.c_str() << endl;
string s2("hello world");
cout << s2.c_str() << endl;
s2.erase(5);
cout << s2.c_str() << endl;
string s3("hello world");
cout << s3.c_str() << endl;
s3.erase(5,100);
cout << s3.c_str() << endl;
}
void test_string5()
{
//默認(rèn)生成的構(gòu)造是淺拷貝,會(huì)導(dǎo)致s1和s2指向同一空間,析構(gòu)兩次
string s1("hello world");
string s2(s1);
s1[0] = 'x';
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
//賦值重載
string s3("hello worldxxxxxx");
s1 = s3;
cout << s1.c_str() << endl;
cout << s3.c_str() << endl;
}
void test_string6()
{
//字符串中間可以有'\0'這個(gè)有效字符
string s1("hello world");
s1 += 'x';
s1 += '\0';
s1 += "yyy";
cout << s1 << endl;
cout << s1.c_str() << endl;
//對(duì)中間有'\0'的字符拷貝
string s2(s1);
cout << s1 << endl;
cout << s2 << endl;
}
void test_string7()
{
string s1;
s1.resize(100, '*');
cout << s1 << endl;
s1.resize(10);
cout << s1 << endl;
s1.resize(20, '#');
cout << s1 << endl;
}
void test_string8()
{
string ur1 = "https://cplusplus.com/reference/string/string/rfind/";
//分離網(wǎng)絡(luò)協(xié)議
size_t i1 = ur1.find(':');
if (i1 != string::npos)
{
//protocol協(xié)議
string protocol = ur1.substr(0, i1);
cout << protocol << endl;
//分離域名
size_t i2 = ur1.find('/', i1 + 3);
if (i2 != string::npos)
{
string domain = ur1.substr(i1 + 3, i2 - (i1 + 3));
cout << domain << endl;
//分離最后
string uri = ur1.substr(i2 + 1);
cout << uri << endl;
}
}
}
void test_string9()
{
string s1,s2("xxxxx");
cin >> s1>>s2;
cout << s1 << endl;
cout << s2 << endl;
getline(cin,s1);
cout << s1 << endl;
}
void test_string10()
{
string s1("hello world"), s2("xxxxx");
s1.swap(s2);
cout << s1 << endl;
cout << s2 << endl;
}
}
int main()
{
//bit::test_string1();
//bit::test_string2();
//bit::test_string3();
//bit::test_string4();
//bit::test_string5();
//bit::test_string6();
//bit::test_string7();
//bit::test_string8();
//bit::test_string9();
bit::test_string10();
return 0;
}1.6 vs和g++下string結(jié)構(gòu)的說明
注意:下述結(jié)構(gòu)是在32位平臺(tái)下進(jìn)行驗(yàn)證,32位平臺(tái)下指針占4個(gè)字節(jié)。
- vs下string的結(jié)構(gòu)
string總共占28個(gè)字節(jié),內(nèi)部結(jié)構(gòu)稍微復(fù)雜一點(diǎn),先是有一個(gè)聯(lián)合體,聯(lián)合體用來定義string中字符串的存儲(chǔ)空間:
- 當(dāng)字符串長度小于16時(shí),使用內(nèi)部固定的字符數(shù)組來存放
- 當(dāng)字符串長度大于等于16時(shí),從堆上開辟空間
union _Bxty
{
// storage for small buffer or pointer to larger one
value_type _Buf[_BUF_SIZE];
pointer _Ptr;
char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;這種設(shè)計(jì)也是有一定道理的,大多數(shù)情況下字符串的長度都小于16,那string對(duì)象創(chuàng)建好之后,內(nèi)部已經(jīng)有了16個(gè)字符數(shù)組的固定空間,不需要通過堆創(chuàng)建,效率高。
其次:還有一個(gè)size_t字段保存字符串長度,一個(gè)size_t字段保存從堆上開辟空間總的容量
最后:還有一個(gè)指針做一些其他事情。
故總共占16+4+4+4=28個(gè)字節(jié)。

- g++下string的結(jié)構(gòu)
G++下,string是通過寫時(shí)拷貝實(shí)現(xiàn)的,string對(duì)象總共占4個(gè)字節(jié),內(nèi)部只包含了一個(gè)指針,該指針將來指向一塊堆空間,內(nèi)部包含了如下字段:
- 空間總大小
- 字符串有效長度
- 引用計(jì)數(shù)
struct _Rep_base
{
size_type _M_length;
size_type _M_capacity;
_Atomic_word _M_refcount;
};
- 指向堆空間的指針,用來存儲(chǔ)字符串。
1.7 補(bǔ)充的接口

2.擴(kuò)展閱讀
面試中string的一種正確寫法
STL中的string類怎么了?
到此這篇關(guān)于C++ string類擴(kuò)展知識(shí)全解析的文章就介紹到這了,更多相關(guān)C++ string類內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- C++ string類詳解(STL簡介, string類,訪問修改字符)
- C++標(biāo)準(zhǔn)庫介紹及使用string類的詳細(xì)過程
- c++中string類型和int類型相互轉(zhuǎn)換的幾種常用方法
- C++深淺拷貝及簡易string類實(shí)現(xiàn)方式
- C# 如何調(diào)用C++ dll string類型返回
- C++實(shí)現(xiàn)String類的方法詳解
- 詳解如何利用C++實(shí)現(xiàn)Mystring類
- C++中String類型的逆序方式
- C++中string類的常用方法實(shí)例總結(jié)
- C++字符數(shù)組、字符數(shù)組指針和string類
相關(guān)文章
Qt中多線程QThread、QThreadPool、QConCurrent三種方式對(duì)比分析
QThread是Qt多線程的基石,本質(zhì)上是對(duì)操作系統(tǒng)原生線程的面向?qū)ο蠓庋b,本文介紹Qt中多線程QThread、QThreadPool、QConCurrent三種方式對(duì)比分析,感興趣的朋友一起看看吧2026-05-05
C語言時(shí)間函數(shù)之mktime和difftime詳解
這篇文章主要為大家詳細(xì)介紹了C語言時(shí)間函數(shù)之mktime和difftime,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一,希望能夠給你帶來幫助2022-02-02
C語言數(shù)組越界引發(fā)的死循環(huán)問題解決
本文主要介紹了C語言數(shù)組越界引發(fā)的死循環(huán)問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
win10系統(tǒng)VS2019配置點(diǎn)云庫PCL1.12.1的詳細(xì)流程
這篇文章主要介紹了win10系統(tǒng)VS2019配置點(diǎn)云庫PCL1.12.1的教程與經(jīng)驗(yàn)總結(jié),本文記錄小白在配置過程中踩過的一些小坑,需要的朋友可以參考下2022-07-07
C++ 實(shí)戰(zhàn)開發(fā)一個(gè)猜單詞的小游戲
眾所周知紙上得來終覺淺,我們要在實(shí)戰(zhàn)中才能真正的掌握技術(shù),小編為大家?guī)硪环萦肅++編寫的猜單詞小游戲,給大家練練手,快來看看吧2021-11-11
Qt顯示QImage圖像在label上,并保持自適應(yīng)大小問題
這篇文章主要介紹了Qt顯示QImage圖像在label上,并保持自適應(yīng)大小問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-11-11

