☰
【C++】STL源码仿写(一):string
2026/10/3 19:57:20 网站建设 项目流程

一、介绍

STL源码很难懂,但是我们可以从中学到许多有趣的知识。有没有可能既规避源码复杂度,又可以一窥STL的各种实现呢?——本系列是一个仿写STL的系列,我在这个系列中力求简化的源码复杂度,带你手撕一遍核心的实现。

当然了,无论如何简化,读者必须拥有一定的C++基础,如果你有其他程序的经验(go、java等)但是对C++不够了解,可以先看我的五分钟带你了解C++系列,这个C++介绍系列同样力求删繁就简,让你可以快速了解必要的C++特性,上手编码。

二、string 概览

string是一种C++ STL当中常见的容器对象,用于操作字符串:

支持字符串类型:C风格与二进制安全字符串
string包括了C风格string和二进制安全string两种,我们在实现的过程中要注意这一点:C风格的string包含了‘\0’,并且以此为分隔符,而二进制安全的string则是基于长度的。

private 字段:数据结构
我们在实现的中,string实际上是char*字符数组data_,内存中为数组所分配的内存长度是capacity_,字符数组的实际长度是size_。要注意,我们capacity和size字段都不应该将'\0'记录在内。

rule of five:RAII
为了实现RAII资源管理,我们手撕string必须遵守Rule of five——拷贝构造函数、析构函数、拷贝赋值运算符重载、移动构造函数、移动赋值运算符重载。

public 方法:提供对外操作
还有就是仿造STL string实现string的一些公有方法,比如append()、empty()、c_str()、data()、size()这类。

还有一些辅助函数和辅助字段,这些到我们后面实现的时候遇到了再说。

三、string 实现

3.1. 构造函数以及string包含的字段

上面我们分析过private字段需要包含的内容:

private:char*data_;//实际的字符size_t size_;//大小,即实际的字符长度size_t capacity_;//容量,即内存已分配的空间

STL源码当中针对不同大小的字符串有堆栈内存优化,默认以15字节为阈值,小于15字节的存在栈上,大于则存在堆上,为了简化设计,我们的实现中不做堆栈优化,但是需要保留这个15字节的阈值。

classstring{//...//static修饰的作用:全体string对象共享staticconstsize_t s_min_cap_;//...};constsize_t s_min_cap_=15;//外部初始化

接下来我们先实现构造函数,值得注意的是:构造函数需要提供C风格和二进制安全两种字符串,还有无参数默认为空的构造。

我们的实现中,因为需要兼容C风格的字符串,所以内部的data_需要在末尾添加'\0',但capacity_和size_均不算入这个特殊的字符。我们用strlen取长度一样是没有包括这个特殊字符的。

//无参数默认string():size_(0),capacity_(s_min_cap_){data_=newchar[capacity_+1];data[size_]='\0';}//C风格构造string(constchar*str){if(!str)throwstd::invalid_argument("null pointer");size_=std::strlen(str);capacity_=std::max(s_min_cap_,size_);data_=newchar[capacity_+1];std::memcpy(data_,str,size_);data_[size_]='/0';}//二进制安全字符串string(constchar*str,size_t len){if(!str)throwstd::invalid_argument("null pointer");size_=len;capacity_=std::max(s_min_cap_,size_);data_=newchar[capacity_+1];std::memcpy(data_,str,size_);data_[size_]='/0';}

3.2. Rule of Five 的实现

rule of five的析构函数,非常简单

~string(){delete[]data_;}

rule of five当中除了析构函数,还有拷贝构造函数和移动构造函数,这两种构造函数都是从另一个string获取资源,但是拷贝构造是复制了一份另一个string,移动构造是将另一个string转移到了自己这里(偷取)。这里要注意的细节是拷贝构造的参数需要用const修饰,这样才可以对其他全部的string对象进行拷贝(比如同样用const修饰的string、无默认构造函数的string以及右值对象);而移动构造则需要用noexcept修饰,这样才能确保移动语义不退化为拷贝。

//拷贝构造string(conststring&other):size_(other.size_),capacity_(other.capacity_){data_=newchar[capacity_+1];std::memcpy(data_,other.data_,size_);data_[size_]='\0';}//移动构造string(string&&other)noexcept:size_(other.size_),capacity_(other.capacity_),data_(other.data_){//偷取之后置空other.size_=0;other.capacity_=0;other.data_=nullptr;}

我们已经实现了rule of five的析构、拷贝构造和移动构造,还剩下拷贝复制运算符和移动复制运算符的重载,运算符的重载的语义是这样的:this所指向的对象本身已经有一块装着data的地址了,但是赋值后这块地址装的东西需要变更为其他的内容,原先的data需要丢弃。

//拷贝赋值运算符重载string&operator=(conststring&other){if(this!=&other){char*new_data=newchar[other.capacity_+1];delete[]data_;//丢弃std::memcpy(new_data,other.data_,other.size_);data_=new_data;//现在data指向newdata了data_[other.size_]='\0';size_=other.size_;capacity_=other.capacity_;}return*this;}//移动赋值运算符重载string&operator=(string&&other)noexcept{if(this!=&other){delete[]data_;//丢弃size_=other.size_;capacity_=other.capacity_;data_=other.data_;other.size_=0;other.capacity_=0;other.data_=nullptr;}return*this;}

至此,rule of five所要求的已经全部实现。

3.3. append() 以及其他公有方法的实现

append()的实现需要考虑内存管理,即扩容的问题——要求我们判断append之后,新string的长度是否超出当前的容量,一般我们采取的扩容策略都是直接翻倍扩容。需要实现一个reserve()方法来预先分配内存,再填入数据。

voidreserve(size_t new_cap){realloc_memory(new_cap);}string&append(constchar*str){returnappend(str,std::strlen(str));}string&append(constchar*str,size_t len){if(!str)throwstd::invalid_argument("null pointer");if(size_+len>capacity_){// 扩容(改capacity)reserve((size_+len)*2);}std::memcpy(data_+size_,str,len);//将str的内容追加到data后面size_+=len;data_[size_]='\0';return*this;}

我们需要设计一个私有的辅助函数 realloc_memory() 用于修改capacity,并且分配新的内存、将老的data拷贝过去,并且释放老data

voidrealloc_memory(size_t new_cap){capacity_=std::max(s_min_cap_,new_cap);//最小是15字节char*new_data=newchar[capacity_+1];if(size_>0)std::memcpy(new_data,data_,size_);//把老数据拷贝过来delete[]data_;//释放老数据data_=new_data;data_[size_]='\0';capacity_=new_cap;//不要忘记修改capacity}

还有一些公有接口,实现很简单,值得一提的第一个const修饰是为了避免外部通过this指针修改内部数据,,第二个const修饰是为了防止this指向被修改。

constchar*c_str()constnoexcept{returndata_;}constchar*data()constnoexcept{returndata_;}size_tsize()constnoexcept{returnsize_;}size_tcapacity()constnoexcept{returncapacity_;}boolempty()constnoexcept{returnsize_==0;}

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询