C++模擬實現(xiàn)string的詳細過程
一、前言
在 C++ 編程中,字符串的處理是一項常見且重要的任務。標準庫中的 string 類為我們提供了便捷、高效的字符串操作方法。然而,深入理解其內(nèi)部實現(xiàn)機制對于提高編程技能和優(yōu)化代碼性能具有重要意義。
模擬實現(xiàn) string 類 的背景源于對 C++ 底層原理的探索欲望。通過親手模擬實現(xiàn),我們能夠更清晰地理解字符串的存儲、管理和操作方式。
其重要性體現(xiàn)在多個方面。首先,有助于掌握內(nèi)存管理的細節(jié),如動態(tài)分配、擴容和釋放,避免內(nèi)存泄漏和提高資源利用效率。其次,能夠深入理解字符串的各種操作,如拷貝構造、賦值、插入、刪除等,從而在實際編程中更加準確和高效地運用這些操作。此外,模擬實現(xiàn)過程培養(yǎng)了我們解決復雜問題的能力,提升了對 C++ 語言特性的掌握程度。
總之,模擬實現(xiàn) string 類 不僅是對知識的深入挖掘,更是為了在實際編程中能夠更加得心應手地處理字符串相關的任務,編寫更加健壯和高效的代碼。
二、準備工作
2.1 涵蓋必要的頭文件與命名空間
在對 string 類進行模擬實現(xiàn)時,我們必須包含一些不可或缺的頭文件,例如 <iostream> 用于執(zhí)行輸入輸出操作,<cstring> 用于和字符串相關的函數(shù),諸如 strlen 、 strcpy 等。與此同時,或許還需運用命名空間,例如 using namespace std; ,從而更便捷地使用標準庫中的函數(shù)與對象。
2.2 簡述模擬實現(xiàn) string 所需的基礎概念和知識
模擬實現(xiàn) string 類需要熟稔一些關鍵的概念與知識。
首先是動態(tài)內(nèi)存的管理,鑒于 string 的長度具備可變性,需要在運行期間動態(tài)地進行內(nèi)存的分配與釋放,以存儲字符串的內(nèi)容。這牽涉到 new 和 delete 操作符的使用。
其次,要領會指針的操作,憑借指針來管理動態(tài)分配的內(nèi)存。
還需熟知字符串的基本操作,像是字符串的拷貝、拼接、查找、插入、刪除等等。對于字符編碼和字符處理方面的知識也應當有所知曉,以保障字符串的處理準確無誤。
此外,掌控構造函數(shù)、析構函數(shù)、拷貝構造函數(shù)以及賦值運算符的重載也是至關重要的,以此確保對象能夠正確地創(chuàng)建、銷毀和復制。同時,對于異常處理也應當具備一定的認知,用以處理可能出現(xiàn)的諸如內(nèi)存分配失敗等異常狀況。
三、成員變量
char _str*
char* _str是一個指針,用于動態(tài)地存儲字符串的數(shù)據(jù)。在模擬實現(xiàn) string 類的過程中,它承擔著核心的角色。通過動態(tài)分配內(nèi)存,_str 能夠根據(jù)字符串內(nèi)容的長度靈活地調(diào)整存儲空間,從而有效地管理字符串數(shù)據(jù)。在進行字符串的操作,如拷貝、修改、插入或刪除時,都是基于 _str 所指向的內(nèi)存區(qū)域進行的。size_t _capacity
size_t _capacity用于記錄字符串的容量,即當前為存儲字符串所分配的內(nèi)存空間大小。它在字符串的擴容操作中起著關鍵作用。當字符串的長度即將超過當前分配的容量時,需要根據(jù)一定的策略(如倍增或按照特定比例增加)來重新分配更大的內(nèi)存空間,并更新 _capacity 的值,以確保有足夠的空間容納不斷增長的字符串。size_t _size
size_t _size表示字符串的有效長度,即實際存儲的字符數(shù)量。它與 _capacity 不同,_capacity 關注的是內(nèi)存空間,而 _size 關注的是字符串中實際包含的有效字符數(shù)。在進行字符串的操作,如插入、刪除字符時,需要根據(jù) _size 來確定操作的位置和范圍,以保證字符串的完整性和正確性。
四、默認成員函數(shù)
4.1 構造函數(shù)
// 短小頻繁調(diào)用的函數(shù),可以直接定義到類里面,默認是inline
string(const char* str = "")
{
_size = strlen(str);
// _capacity不包含\0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
上述構造函數(shù)實現(xiàn)了帶缺省參數(shù)的功能。若傳入的字符串為空指針或空字符串,會進行相應的處理,為字符串分配適當?shù)膬?nèi)存空間,并初始化_size和_capacity。
4.2 析構函數(shù)
析構函數(shù)用于釋放構造函數(shù)中動態(tài)分配的內(nèi)存,并將相關的成員變量重置為初始狀態(tài),以避免內(nèi)存泄漏和資源浪費。
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
4.3 拷貝構造函數(shù)
在傳統(tǒng)的拷貝構造函數(shù)中,首先計算源字符串的長度。然后,為新字符串分配足夠的內(nèi)存空間,并通過strcpy函數(shù)將源字符串的內(nèi)容復制到新分配的空間中。同時,正確設置_size和_capacity的值,以反映新字符串的長度和容量。
// 深拷貝問題
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
4.4 拷貝構造函數(shù)(臨時變量)
在現(xiàn)代的實現(xiàn)方式中,首先將_str初始化為nullptr。然后創(chuàng)建一個臨時對象tmp,其構造過程會完成對源字符串的復制。接著,通過交換函數(shù)swap將臨時對象tmp的_str與當前對象的_str進行交換,從而實現(xiàn)深拷貝。同時,也要確保_size和_capacity的值得到正確更新。
void swap(string& s)
{
std::swap(_str, s._str);
std::swap(_size, s._size);
std::swap(_capacity, s._capacity);
}
string(const string& s)
{
string tmp(s._str);
swap(tmp);
}
這種現(xiàn)代方式避免了直接的內(nèi)存分配和復制操作,通過巧妙地利用臨時對象和交換,提高了代碼的簡潔性和效率。
五、賦值運算符重載
5.1 傳統(tǒng)方式處理空間分配和字符串復制
在傳統(tǒng)的賦值運算符重載中,首先檢查當前對象是否與傳入的對象不同。若不同,則先釋放當前對象所占用的內(nèi)存。然后計算傳入對象字符串的長度,為新字符串分配足夠的內(nèi)存空間,并通過strcpy函數(shù)復制字符串內(nèi)容。同時,正確設置_size和_capacity的值。
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
5.2 現(xiàn)代方式通過交換實現(xiàn)賦值
string& operator=(string tmp)
{
swap(tmp);
return *this;
}
在現(xiàn)代的賦值實現(xiàn)方式中,首先創(chuàng)建一個傳入對象的副本other。然后通過交換函數(shù)swap交換當前對象和副本對象的_str指針。接著,直接將副本對象的_size和_capacity值賦給當前對象。這種方式避免了直接的內(nèi)存分配和復制操作,提高了賦值的效率。
六、容量相關操作接口
6.1 size()
size() 函數(shù)用于返回字符串的有效長度,即實際存儲的字符數(shù)量。它不包括字符串末尾的空字符 '\0' 。在使用時,直接調(diào)用 string 對象.size() 即可獲取字符串的有效長度。
例如:
size_t size() const{ return _size; }
在 size_t size() const { return _size; } 這段代碼中,const 關鍵字具有以下重要作用:
- 保證成員函數(shù)不修改類的成員變量:
這意味著在這個size()函數(shù)內(nèi)部,不能對類中的任何成員變量進行修改操作。
例如,如果類中有其他成員變量,如int count; ,在這個 size() 函數(shù)中就不能執(zhí)行類似于count++; 這樣的修改操作。 - 使函數(shù)可以用于常量對象:
當有一個常量對象時,只能調(diào)用其 const 成員函數(shù)。
例如,如果有 const MyClass obj; ,那么只能通過 obj.size(); 來獲取大小,而不能調(diào)用可能修改對象狀態(tài)的非 const 成員函數(shù)。 - 增強程序的可讀性和可維護性:
當看到 const 修飾的成員函數(shù),開發(fā)者能立即明白該函數(shù)不會修改對象的狀態(tài)。
6.2 capacity()
capacity() 函數(shù)返回字符串當前分配的內(nèi)存空間大小,即容量。它反映了為存儲字符串所預留的內(nèi)存空間。
例如:
size_t capacity() const{ return _capacity; }
6.3 reserve()
reserve() 函數(shù)用于調(diào)整字符串的容量。當我們預計字符串可能會增長到一定規(guī)模時,可以提前使用reserve()函數(shù)為其預留足夠的內(nèi)存空間,以減少后續(xù)頻繁的內(nèi)存重新分配操作,提高性能。
void string::reserve(size_t n)
{
// 不能用realloc,必須匹配使用
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
delete[] _str;
_str = tmp;
_capacity = n;
}
}
七、修改相關操作接口
7.1 push_back()在字符串末尾追加字符。
push_back() 函數(shù)用于在字符串的末尾追加一個字符。它能夠方便地擴展字符串的內(nèi)容。
void string::push_back(char ch)
{
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);
_str[_size] = ch;
++_size;
_str[_size] = '\0';
}
例如:
string str = "Hello";
str.push_back('!');
cout << str << endl; // 輸出:Hello!
在這個例子中,通過 push_back('!')操作,將感嘆號字符添加到了字符串 str 的末尾。
7.2 append()在字符串末尾追加字符串。
append() 函數(shù)可以在字符串的末尾追加一個字符串。其使用方式多樣,能滿足不同的需求。
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 大于2倍,要多少給多少,小于2倍按2倍擴
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
例如:
string s1 = "Hello"; string s2 = " World"; s1.append(s2); cout << s1 << endl; // 輸出:Hello World
7.3 insert()在指定位置插入字符或字符串。
insert() 函數(shù)能夠在指定的位置插入字符或字符串。比如:
void string::insert(size_t pos, const char* str)
{
assert(pos <= _size);
size_t len = strlen(str);
if (_size + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end - len + 1 > pos)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
還可以插入單個字符:
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);
+
// 挪動數(shù)據(jù)
size_t end = _size + 1;
while (end > pos)
{
_str[end] = _str[end - 1];
--end;
}
_str[pos] = ch;
_size++;
}
八、非成員函數(shù)
8.1 流插入運算符重載 (<<)
ostream& operator<<(ostream& out, const string& s) {
for (auto ch : s)
out << ch;
return out;
}
上述重載函數(shù)使得可以使用 << 運算符將自定義的 String 對象輸出到標準輸出流中。它直接將 String 對象內(nèi)部存儲的字符串數(shù)據(jù)輸出。
8.2 流提取運算符重載 (>>)
stream& operator>>(istream& in, string& s) {
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();// 獲取下一個字符
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
此重載函數(shù)實現(xiàn)了從輸入流中讀取數(shù)據(jù)到 String 對象的功能。首先,使用一個臨時緩沖區(qū)讀取輸入數(shù)據(jù),然后將其轉換為 String 對象并賦值給傳入的 String 引用。
整體代碼
string.h
#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<iostream>
#include<string>
#include<assert.h>
using namespace std;
namespace bit
{
class string
{
public:
typedef char* iterator;
typedef const char* const_iterator;
iterator begin()
{
return _str;
}
iterator end()
{
return _str + _size;
}
const_iterator begin() const
{
return _str;
}
const_iterator end() const
{
return _str + _size;
}
/*string()
:_str(new char[1]{'\0'})
,_size(0)
,_capacity(0)
{}*/
// 短小頻繁調(diào)用的函數(shù),可以直接定義到類里面,默認是inline
string(const char* str = "")
{
_size = strlen(str);
// _capacity不包含\0
_capacity = _size;
_str = new char[_capacity + 1];
strcpy(_str, str);
}
// 深拷貝問題
//
// s2(s1)
string(const string& s)
{
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
// s2 = s1
// s1 = s1
string& operator=(const string& s)
{
if (this != &s)
{
delete[] _str;
_str = new char[s._capacity + 1];
strcpy(_str, s._str);
_size = s._size;
_capacity = s._capacity;
}
return *this;
}
~string()
{
delete[] _str;
_str = nullptr;
_size = _capacity = 0;
}
const char* c_str() const
{
return _str;
}
void clear()
{
_str[0] = '\0';
_size = 0;
}
size_t size() const
{
return _size;
}
size_t capacity() const
{
return _capacity;
}
char& operator[](size_t pos)
{
assert(pos < _size);
return _str[pos];
}
const char& operator[](size_t pos) const
{
assert(pos < _size);
return _str[pos];
}
void reserve(size_t n);
void push_back(char ch);
void append(const char* str);
string& operator+=(char ch);
string& operator+=(const char* str);
void insert(size_t pos, char ch);
void insert(size_t pos, const char* str);
void erase(size_t pos, 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);
private:
//char _buff[16];
char* _str;
size_t _size;
size_t _capacity;
//static const size_t npos = -1;
static const size_t npos;
/*static const int N = 10;
int buff[N];*/
};
bool operator<(const string& s1, const string& s2);
bool operator<=(const string& s1, const string& s2);
bool operator>(const string& s1, const string& s2);
bool operator>=(const string& s1, const string& s2);
bool operator==(const string& s1, const string& s2);
bool operator!=(const string& s1, const string& s2);
ostream& operator<<(ostream& out, const string& s);
istream& operator>>(istream& in, string& s);
}
string.cpp
#include"string.h"
namespace bit
{
const size_t string::npos = -1;
// 保留 + 擴容
void string::reserve(size_t n)
{
// 不能用realloc,必須匹配使用
if (n > _capacity)
{
char* tmp = new char[n + 1];
strcpy(tmp, _str);
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';
}
//不能按2倍擴容
void string::append(const char* str)
{
size_t len = strlen(str);
if (_size + len > _capacity)
{
// 大于2倍,要多少給多少,小于2倍按2倍擴
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
strcpy(_str + _size, str);
_size += len;
}
string& string::operator+=(char ch)
{
push_back(ch);
return *this;
}
string&string:: operator+=(const char* str)
{
append(str);
return *this;
}
// 插入單個字符
void string::insert(size_t pos, char ch)
{
assert(pos <= _size);
if (_size == _capacity)
reserve(_capacity == 0 ? 4 : _capacity * 2);
// 挪動數(shù)據(jù)
//int end = _size;
//while (end >= (int)pos)// 不強轉會整形提升,導致pos為0時出錯
//{
// if (end == 0)
// {
// int i = 0;
// }
// _str[end + 1] = _str[end];
// --end;
//}
// 挪動數(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 + len > _capacity)
{
reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
}
size_t end = _size + len;
while (end - len + 1 > pos)
{
_str[end] = _str[end - len];
--end;
}
for (size_t i = 0; i < len; i++)
{
_str[pos + i] = str[i];
}
_size += len;
}
void string::erase(size_t pos, size_t len)
{
assert(pos < _size);
if (len > _size - pos)
{
_str[pos] = '\0';
_size = pos;
}
else
{
for (size_t i = 0; i <= _size - pos - len; i++)
{
_str[pos + i] = _str[pos + i + len];
}
_size -= len;
}
}
size_t string::find(char ch, size_t pos)
{
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)
{
assert(pos < _size);
const char* ptr = strstr(_str + pos, str);
if (ptr != nullptr)
{
return ptr - _str;
}
else
return npos;
}
// substr 函數(shù)用于從一個字符串中提取子字符串
string string::substr(size_t pos, size_t len)
{
assert(pos < _size);
// len大于剩余字符長度,更新一下len
if (len > _size - pos)
{
len = _size - pos;
}
string sub;
sub.reserve(len);
for (size_t i = 0; i < len; i++)
{
sub += _str[pos + i];
}
return sub;
}
// 字符串都是按ASCLL碼比較的
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 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 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);
}
// 必須為全局,但不一定要為友元
// 輸出流
ostream& operator<<(ostream& out, const string& s) {
for (auto ch : s)
out << ch;
return out;
}
// 輸入流
istream& operator>>(istream& in, string& s) {
s.clear();
const int N = 256;
char buff[N];
int i = 0;
char ch;
ch = in.get();
while (ch != ' ' && ch != '\n') {
buff[i++] = ch;
if (i == N - 1)
{
buff[i] = '\0';
s += buff;
i = 0;
}
ch = in.get();// 獲取下一個字符
}
if (i > 0)
{
buff[i] = '\0';
s += buff;
}
return in;
}
void test_string1()
{
string s1;
string s2("hello world");
cout << s1.c_str() << endl;
cout << s2.c_str() << endl;
for (size_t i = 0; i < s2.size(); i++)
{
s2[i] += 2;
}
cout << s2.c_str() << endl;
// 范圍for的本質(zhì)就是迭代器
for (auto e : s2)
{
cout << e << " ";
}
cout << endl;
string::iterator it = s2.begin();
while (it != s2.end())
{
cout << *it << " ";
++it;
}
}
void test_string2()
{
string s1("hello world");
s1 += " xxx";
s1.append(" %%%");
cout << s1.c_str() << endl;
s1.insert(5, '^');
cout << s1.c_str() << endl;
s1.insert(5, " &&& ");
cout << s1.c_str() << endl;
s1.erase(1);
cout << s1.c_str() << endl;
}
void test_string3()
{
string s1("hello world");
string s2 = s1.substr(0, 3);
cout << s2.c_str() << endl;
string copy(s1);
cout << copy.c_str() << endl;
string s3 = s1;
cout << s3.c_str() << endl;
char str[5];
cin >> s1;
cout << s1 << endl;
cin >> s1 >> s2 >> str;
cout << s1 << s2 <<str;
}
void test_string4()
{
std::string s1("11");
std::string s2(s1);// 深拷貝
printf("%p\n", s1.c_str());
printf("%p\n", s2.c_str());
cout << sizeof(s1) << endl;
cout << sizeof(s2) << endl;
}
}
總結
在模擬實現(xiàn) string 類的過程中,我們涉及了眾多的要點和需要特別注意的事項。
首先,對于成員變量的管理至關重要。char* _str 用于動態(tài)存儲字符串數(shù)據(jù),其內(nèi)存的分配和釋放要精確控制,以避免內(nèi)存泄漏和錯誤訪問。size_t _capacity 和 size_t _size 分別用于記錄容量和有效長度,它們的正確更新和使用影響著字符串的存儲和操作效率。
在函數(shù)實現(xiàn)方面,構造函數(shù)要處理好空字符串和正常字符串的初始化。析構函數(shù)務必釋放動態(tài)分配的內(nèi)存。拷貝構造函數(shù)和賦值運算符重載需要確保深拷貝,避免淺拷貝導致的問題。
容量相關操作接口,如 reserve() 和 resize() ,要理解其對內(nèi)存和字符串長度的影響,以及不同實現(xiàn)方式的性能差異。
修改相關操作接口,如 push_back()、append()、insert() 和 erase() ,需要注意操作的邊界情況和對字符串狀態(tài)的更新。
遍歷訪問相關接口,迭代器的實現(xiàn)要保證操作符重載的正確性和高效性,operator[] 要進行有效的索引檢查。
非成員函數(shù)中的流插入和流提取運算符重載,要保證數(shù)據(jù)的正確傳輸和對象的正確賦值。
總之,模擬實現(xiàn) string 類需要對 C++ 的內(nèi)存管理、指針操作、函數(shù)重載等知識有深入的理解和熟練的運用,同時要注重代碼的效率、安全性和可維護性。只有這樣,才能實現(xiàn)一個功能完善、性能優(yōu)良的 string 類模擬。
以上就是C++模擬實現(xiàn)string的詳細過程的詳細內(nèi)容,更多關于C++模擬實現(xiàn)string的資料請關注腳本之家其它相關文章!
相關文章
C++的靜態(tài)聯(lián)編和動態(tài)聯(lián)編詳解
這篇文章主要介紹了C++的靜態(tài)聯(lián)編和動態(tài)聯(lián)編詳解,對于深入理解C++編譯運行原理有很大幫助,需要的朋友可以參考下2014-07-07
Visual C++ 6.0實現(xiàn)域名解析為IP的示例代碼
本文主要介紹了在Windows環(huán)境下,使用Visual C++ 6.0(VC6)編譯器,通過Winsock庫調(diào)用DNS服務完成域名到IP地址的轉換,具有一定的參考價值,感興趣的可以了解一下2025-03-03
C++:函數(shù)對象,STL提供的函數(shù)對象,函數(shù)適配器詳解
這篇文章主要介紹了C++:函數(shù)對象,STL提供的函數(shù)對象,函數(shù)適配器的使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-08-08
C++實現(xiàn)LeetCode(59.螺旋矩陣之二)
這篇文章主要介紹了C++實現(xiàn)LeetCode(59.螺旋矩陣之二),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07

