前言
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類的常用接口
- 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
}
- 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;
}
- 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;
}
- 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;
}
- 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;
}