C++ STL String底层实现分析

        STL String是最重要,最常用的STL之一,能够大大降低我们处理字符串的工作量。今天我们来自己写一个MyString,在编码中理解它的工作原理。

目录

基本结构

构造方法

重载运算符

方法实现

迭代器

其它 

整体代码


基本结构

        从一个容器的角度讲,string的结构并不复杂,本质上还是一个顺序表。如图可以生动展示一个string所占用的空间。其中_size表示当前已经使用的空间,_capacity表示最大容量。特别需要注意的是,string的字符串结尾会固定存放一个'/0'来表示字符串结尾。在实践中我发现在不主动调用reserve()方法与resize()方法的情况下,string.capacity()的值往往与string().size()相同。

         由此可以得到string的私有部分定义:

private:
	char* _ptr;						//实际存储字符串内容的空间 
	size_t _size;					//存储字符串当前的容量 
	size_t _capacity;				//存储字符串最大容量 

构造方法

        下面给出string的构造方法:

public:
	//构造函数 
	MyString():_ptr(new char[1]),_size(0),_capacity(){
		*_ptr='\0';
	}//没有参数的构造函数
	MyString(const char* ptr):_ptr(new char[strlen(ptr)+1]),_size(0),_capacity(0){//带参数的构造函数 
		strcpy(_ptr,ptr);			//将字符串赋值给string 
		_size=strlen(_ptr);			//将字符串的大小和容量更新为当前值 
		_capacity=strlen(_ptr);
	}
	MyString(const MyString& str):_ptr(nullptr),_size(0),_capacity(0){		//拷贝构造 
		strcpy(_ptr,str._ptr);
		_size=strlen(_ptr);
		_capacity=strlen(_ptr);
	}
	~MyString(){					//析构函数 
		if(_ptr){
			delete[] _ptr;
			_ptr=nullptr;
			_size=0;
			_capacity=0;
		}
	}

        上述代码基本没有新难点。 

重载运算符

        下面代码对一些运算符进行了重载

	MyString& operator=(MyString str){			//对=符号进行重载 
		strcpy(_ptr,str._ptr);
		_size=strlen(_ptr);
		_capacity=strlen(_ptr);
		return *this;
	}
	const char& operator[](size_t n) const{		//对[]符号进行重载,用以实现字符串随机访问 
		assert(n<_size);
		return _ptr[n];
	}
	
	MyString& operator+=(const char& c){		//对+=重载,来实现+=字符的操作 
		this->push_back(c);
		return *this; 
	}
	MyString& operator+=(const char* str){
		this->append(str);
		return *this;
	}
	MyString& operator+=(const MyString& str){
		this->append(str._ptr);
		return *this;
	}

        string的方法为了能够满足地址,指针和string三种情况的调用,往往要多次重写方法。建议先实现一些常用的方法再来完成运算符重载,这样可以直接调用已经写好的方法,避免出错,简化代码。例如上述代码中的push_back()append()方法可以提前写好调试完成。

        assert()方法在<assert.h>头文件中定义,用来对内部条件进行判定。一旦内部表达式为false,就向stderr打印一条错误信息,并终止程序。

方法实现

        下面代码实现了一些常用的方法。

size_t strlen(const char *str){			//strlen方法从当前指针位置开始向后累加,计算到\0为止的字符串长度 
    	size_t length=0;
    	if(str==nullptr) return 0;
    	while(*str++!='\0') length++;
    	return length;
	}
	
	char* strcpy(char *strDest, const char *strSrc){		//strcpy将strSrc的内容复制到strDest中,并且返回strDest的头指针 
     	assert((strDest!=NULL) && (strSrc !=NULL));			//如果两个字符串都是空的就直接中断 
     	char *address = strDest;							//用address指向strDest开始地址
     	while( (*strDest++ = * strSrc++) != '\0' );
     	return address ;									//返回strDest开始地址                       
	}
	
	void reserve(size_t newCapacity){			//reserve用来修改的是capacity 
		if (newCapacity>_capacity)
		{
			char* str = new char[newCapacity+1];//开新的空间
			for (size_t i=0;i<_size;i++){
				str[i] = _ptr[i];
			}
			delete[] _ptr;
			_ptr = str;
			_capacity = newCapacity;
		}
	}
	void resize(size_t newSize){							//resize用来修改的是size 
		if(newSize>_capacity){								//如果string容量不足,要先开辟容量 
			reserve(newSize);
		}
		if(newSize>_size){
			memset(_ptr+_size,'/0', newSize-_size);	//memset用来对一块空间进行初始化,这里将没有赋值但在string容器内的内存赋值为/0
		}
		_size=newSize;
		_ptr[_size]='\0'; 
	}
	
	void push_back(const char& c){				//在string后面插入新的字符 
		int newCapacity;
		if(_size==_capacity){					//string已满,需要扩容 
			if(_capacity==0){					//string的容量为0,此时扩容为 32
				newCapacity=32; 			
			}else{
				newCapacity=_capacity*2;
			}
			reserve(newCapacity);				//执行实际的扩容操作 
		}
		_ptr[_size]=c;
		_size++;
		_ptr[_size]='\0';
	} 
	
	size_t size(){
		return _size;
	}
	
	size_t capacity(){
		return _capacity;
	}
	
	void append(const char* str){
		int newLength=strlen(str);
		if(newLength+_size>_capacity){//容量不足,需要扩容 
			reserve(newLength+_size);
		}
		_ptr[newLength+_size]='\0';
		for(int i=_size;i<=_size+newLength;i++){//将str内容复制过来 
			_ptr[i]=str[i-_size];
		}
		_size+=newLength;
	}
	
	void insert(size_t position,const char& c){//插入一个字符 
		assert(position<=_size);
		if (_size == _capacity){
			size_t newCapacity;
			if(_capacity==0){
				newCapacity=32;
			}else{
				newCapacity=_capacity*2;
			}
			reserve(newCapacity);
		}
		size_t endPosition=_size;
		while (endPosition-position)
		{
			_ptr[endPosition] =_ptr[endPosition-1];		//将插入点后面的元素右移一位 
			--endPosition;
		}
		_ptr[position]=c; 					//插入新字符 
		_ptr[++_size] = '\0';
	}
	void insert(size_t position, const char* str){
		if(_size+strlen(str)>_capacity){//如果容量不足,就进行扩容 
			reserve(_size+strlen(str));
		} 
		for(int i=0;i<strlen(str);i++){
			insert(position++,str[i]);	//偷个懒,直接调用上面一个insert 
		}
		_ptr[_size]='\0';
	}
	
	void erase(size_t position,size_t length){	//用来删除指定位置的指定长度字符串 
		assert(position<_size&&position>=0);
		if (position+length>=_size){			//删除点后面没有需要保留的字符。直接将后续全部删除 
			_ptr[position]='\0';
			_size=position;
			return;
		}
		size_t start =position+length;			//start为后半部分保留的字符指针,将后半部分逐个前移到删除点 
		while(start<_size){
			_ptr[position++] =_ptr[start++];
		}
		_size-=length;
		_ptr[_size]='\0';						//'/0'不能忘 
	}
	
	size_t find(const char& c, size_t position=0){//查找position位置后面的第一个c的位置 
		assert(position<_size);
		while(position<_size){
			if (_ptr[position]==c){
				return position;
			}
			position++;
		}
		return -1;								//查不到就返回-1 
	}
	
	char* strstr(char *src,char *substr){ 		//用于查询src中是否存在substr 
  		assert(src != NULL && substr != NULL); 
  		unsigned int size = strlen(src); 
  		for(int i = 0; i < size; ++i,++src){ 
    		char *p = src; 
    		for(char *q = substr;;p++,q++){ 
      		if(*q == '\0'){//在src中找到连续的substr子串停止并返回 
        		return src; 
      			} 
      		if(*q != *p){ 
        		break; 
      			}
    		}
  		}
  	}
	size_t find(char* str, size_t position=0){
		assert(position< _size);
		char* s=strstr(_ptr+position, str);
		if (s){
			return s - _ptr;
		}
		return -1;
	}

        这部分代码量最然比较大,但大部分内容还是常规的顺序表操作。

        值得一提的是,其中reserve()方法用来修改的是_capacity的值,也就是最大容量。而resize()方法用来修改的是_size,也就是已占用的空间。这让我对_size和_capacity的意义出现了混淆。所以我使用原版的string进行了验证,令我意外的是原版的string中_size与_capacity似乎是始终相等的。于是我通过下面的代码进行测试:

#include<iostream>
#include<cstring>
using namespace std;
int main(){
	string s="abcabcabcabcabc";                        //一个随意的字符串
	cout<<s<<endl;
	cout<<"s.size() is "<<s.size()<<endl;              //输出当前的字符串长度和容量
	cout<<"s.capacity() is "<<s.capacity()<<endl;
	
	s.reserve(600);                                    //将字符串扩容
	s.resize(500);                                     //修改字符串长度
	cout<<s<<endl;
	cout<<"s.size() is "<<s.size()<<endl;              //再次输出字符串长度和容量
	cout<<"s.capacity() is "<<s.capacity()<<endl;
	cout<<"|"<<(int)s[490]<<"|"<<endl;                 //尝试获取新增加的有效空间的字符的ASCII码
} 

        得到了以下结果:

         当我使用reserve()方法与resize()方法修改string的长度时,情况果然发生了改变。为了让结果更加明显,设置了500和600两个比较夸张的数字,得到的字符串后面出现了大量的空白。猜测是resize()导致的原字符串后面的填充,于是让程序输出后面随意一个空格的ASCII码,果然其值为0.也就是说,在string中,如果用户强行将其_size提高,那么新增的有效空间会用ASCII码为0的空字符填充

        由此可见,假如用户实际使用的空间<_size<_capacity,那么此时string中的存储情况是这样的:

        另外,起初我使用的resize()的值大于reserve()的值,导致结果中size和capacity仍然是同样大的。这种现象是因为,当resize()的参数值大于reserve()时,会自动调用reserve()将string进行扩容,并扩容到resize()的大小。这一点在上面的resize()方法中已经体现。

迭代器

        下面代码实现了string的迭代器。由于是顺序存储,所以迭代器并不复杂。

	typedef char* iterator;
	typedef const char* const_iterator;
	iterator begin(){//首元素位置 
		return _ptr;
	}
	iterator end(){//末元素位置 
		return _ptr+_size;
	}
	const_iterator begin()const{
		return _ptr;
	}
	const_iterator end()const{
		return _ptr + _size;
	}

其它 

        为了让string能够符合用户习惯地输入和输出,需要对输入和输出进行一些处理。本质上其实属于'<<'和'>>'运算符的重载。

        在MyString对象类中,加入以下代码:

	friend ostream& operator<<(ostream& out, const MyString& str);
	friend istream& operator>>(istream& in, MyString& str);

        其中friend是声明友元函数。友元函数可以调用本类中的私有部分。

        在MyString对象之外加入以下代码:

ostream& operator<<(ostream& out, MyString& str){//重载输出运算符,注意添加头文件iostream 
		for (size_t i=0;i<str.size();i++){				
			out<<str[i];
		}
		cout<<endl;
		return out;
}
istream& operator>>(istream& in, MyString& str){//重载输入运算符 
		char ch;
		str.resize(0);
		str._size = str._capacity = 0;
		while ((ch=getchar())!=EOF){
			if (ch=='\n'){							//输入以回车作为结束 
				return in;
			}
			str+=ch;
	}
	return in;
}

        在对‘<<’的重载中,主要实现的是用户可以通过cout<<StringName;的方式直接打印整个字符串。在对'>>'的重载中,主要实现的是以用户输入的回车键作为输入的结尾。当然,如果你喜欢也可以做一些个性化的输入输出设置。

整体代码

        整体的代码如下:

#ifndef MYSTRING_H
#define MYSTRING_H
#include<assert.h>	//size_t类型在assert.h头文件中 
#include<memory.h>	
#include<iostream>				
using namespace std;
class MyString{
private:
	char* _ptr;						//实际存储字符串内容的空间 
	size_t _size;					//存储字符串当前的容量 
	size_t _capacity;				//存储字符串最大容量 			
public:
	//构造函数 
	MyString():_ptr(new char[1]),_size(0),_capacity(){
		*_ptr='\0';
	}//没有参数的构造函数
	MyString(const char* ptr):_ptr(new char[strlen(ptr)+1]),_size(0),_capacity(0){//带参数的构造函数 
		strcpy(_ptr,ptr);			//将字符串赋值给string 
		_size=strlen(_ptr);			//将字符串的大小和容量更新为当前值 
		_capacity=strlen(_ptr);
	}
	MyString(const MyString& str):_ptr(nullptr),_size(0),_capacity(0){		//拷贝构造 
		strcpy(_ptr,str._ptr);
		_size=strlen(_ptr);
		_capacity=strlen(_ptr);
	}
	~MyString(){					//析构函数 
		if(_ptr){
			delete[] _ptr;
			_ptr=nullptr;
			_size=0;
			_capacity=0;
		}
	}
	
	//运算符重载 
	MyString& operator=(MyString str){			//对=符号进行重载 
		strcpy(_ptr,str._ptr);
		_size=strlen(_ptr);
		_capacity=strlen(_ptr);
		return *this;
	}
	const char& operator[](size_t n) const{		//对[]符号进行重载,用以实现字符串随机访问 
		assert(n<_size);
		return _ptr[n];
	}
	
	MyString& operator+=(const char& c){		//对+=重载,来实现+=字符的操作 
		this->push_back(c);
		return *this; 
	}
	MyString& operator+=(const char* str){
		this->append(str);
		return *this;
	}
	MyString& operator+=(const MyString& str){
		this->append(str._ptr);
		return *this;
	}
	
	//方法实现 
	size_t strlen(const char *str){			//strlen方法从当前指针位置开始向后累加,计算到\0为止的字符串长度 
    	size_t length=0;
    	if(str==nullptr) return 0;
    	while(*str++!='\0') length++;
    	return length;
	}
	
	char* strcpy(char *strDest, const char *strSrc){		//strcpy将strSrc的内容复制到strDest中,并且返回strDest的头指针 
     	assert((strDest!=NULL) && (strSrc !=NULL));			//如果两个字符串都是空的就直接中断 
     	char *address = strDest;							//用address指向strDest开始地址
     	while( (*strDest++ = * strSrc++) != '\0' );
     	return address ;									//返回strDest开始地址                       
	}
	
	void reserve(size_t newCapacity){			//reserve用来修改的是capacity 
		if (newCapacity>_capacity)
		{
			char* str = new char[newCapacity+1];//开新的空间
			for (size_t i=0;i<_size;i++){
				str[i] = _ptr[i];
			}
			delete[] _ptr;
			_ptr = str;
			_capacity = newCapacity;
		}
	}
	void resize(size_t newSize){							//resize用来修改的是size 
		if(newSize>_capacity){								//如果string容量不足,要先开辟容量 
			reserve(newSize);
		}
		if(newSize>_size){
			memset(_ptr+_size,'/0', newSize-_size);	//memset用来对一块空间进行初始化,这里将没有赋值但在string容器内的内存赋值为/0
		}
		_size=newSize;
		_ptr[_size]='\0'; 
	}
	
	void push_back(const char& c){				//在string后面插入新的字符 
		int newCapacity;
		if(_size==_capacity){					//string已满,需要扩容 
			if(_capacity==0){					//string的容量为0,此时扩容为 32
				newCapacity=32; 			
			}else{
				newCapacity=_capacity*2;
			}
			reserve(newCapacity);				//执行实际的扩容操作 
		}
		_ptr[_size]=c;
		_size++;
		_ptr[_size]='\0';
	} 
	
	size_t size(){
		return _size;
	}
	
	size_t capacity(){
		return _capacity;
	}
	
	void append(const char* str){
		int newLength=strlen(str);
		if(newLength+_size>_capacity){//容量不足,需要扩容 
			reserve(newLength+_size);
		}
		_ptr[newLength+_size]='\0';
		for(int i=_size;i<=_size+newLength;i++){//将str内容复制过来 
			_ptr[i]=str[i-_size];
		}
		_size+=newLength;
	}
	
	void insert(size_t position,const char& c){//插入一个字符 
		assert(position<=_size);
		if (_size == _capacity){
			size_t newCapacity;
			if(_capacity==0){
				newCapacity=32;
			}else{
				newCapacity=_capacity*2;
			}
			reserve(newCapacity);
		}
		size_t endPosition=_size;
		while (endPosition-position)
		{
			_ptr[endPosition] =_ptr[endPosition-1];		//将插入点后面的元素右移一位 
			--endPosition;
		}
		_ptr[position]=c; 					//插入新字符 
		_ptr[++_size] = '\0';
	}
	void insert(size_t position, const char* str){
		if(_size+strlen(str)>_capacity){//如果容量不足,就进行扩容 
			reserve(_size+strlen(str));
		} 
		for(int i=0;i<strlen(str);i++){
			insert(position++,str[i]);	//偷个懒,直接调用上面一个insert 
		}
		_ptr[_size]='\0';
	}
	
	void erase(size_t position,size_t length){	//用来删除指定位置的指定长度字符串 
		assert(position<_size&&position>=0);
		if (position+length>=_size){			//删除点后面没有需要保留的字符。直接将后续全部删除 
			_ptr[position]='\0';
			_size=position;
			return;
		}
		size_t start =position+length;			//start为后半部分保留的字符指针,将后半部分逐个前移到删除点 
		while(start<_size){
			_ptr[position++] =_ptr[start++];
		}
		_size-=length;
		_ptr[_size]='\0';						//'/0'不能忘 
	}
	
	size_t find(const char& c, size_t position=0){//查找position位置后面的第一个c的位置 
		assert(position<_size);
		while(position<_size){
			if (_ptr[position]==c){
				return position;
			}
			position++;
		}
		return -1;								//查不到就返回-1 
	}
	
	char* strstr(char *src,char *substr){ 		//用于查询src中是否存在substr 
  		assert(src != NULL && substr != NULL); 
  		unsigned int size = strlen(src); 
  		for(int i = 0; i < size; ++i,++src){ 
    		char *p = src; 
    		for(char *q = substr;;p++,q++){ 
      		if(*q == '\0'){//在src中找到连续的substr子串停止并返回 
        		return src; 
      			} 
      		if(*q != *p){ 
        		break; 
      			}
    		}
  		}
  	}
	size_t find(char* str, size_t position=0){
		assert(position< _size);
		char* s=strstr(_ptr+position, str);
		if (s){
			return s - _ptr;
		}
		return -1;
	}
	//迭代器 
	typedef char* iterator;
	typedef const char* const_iterator;
	iterator begin(){//首元素位置 
		return _ptr;
	}
	iterator end(){//末元素位置 
		return _ptr+_size;
	}
	const_iterator begin()const{
		return _ptr;
	}
	const_iterator end()const{
		return _ptr + _size;
	}
	
	friend ostream& operator<<(ostream& out, const MyString& str);
	friend istream& operator>>(istream& in, MyString& str);
}; 

ostream& operator<<(ostream& out, MyString& str){//重载输出运算符,注意添加头文件iostream 
		for (size_t i=0;i<str.size();i++){				
			out<<str[i];
		}
		cout<<endl;
		return out;
}
istream& operator>>(istream& in, MyString& str){//重载输入运算符 
		char ch;
		str.resize(0);
		str._size = str._capacity = 0;
		while ((ch=getchar())!=EOF){
			if (ch=='\n'){							//输入以回车作为结束 
				return in;
			}
			str+=ch;
	}
	return in;
}
#endif

  • 7
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: 在网络编程中,C++STL(Standard Template Library)是一个非常有用的工具。STL提供了许多数据结构和算法,可以帮助我们更轻松地处理网络编程中的各种任务。 首先,STL容器类是网络编程中经常用到的数据结构。例如,我们可以使用STL的vector来存储动态大小的数组,非常适用于存储和处理网络数据。此外,STL的map和unordered_map类可以用于构建键值对结构,方便快速地查找和操作网络数据。 STL还提供了一系列的算法,可以用于处理网络编程中的常见任务。例如,我们可以使用STL的sort算法对网络数据进行排序,使用STL的find算法查找某个特定的数据,或者使用STL的accumulate算法计算网络数据的总和。这些算法能够在网络编程中提高代码的效率和可读性。 此外,STL还提供了一些其他功能,如字符串处理和输入输出操作等,这些功能在网络编程中也非常有用。 总之,C++STL在网络编程中是一个强大且方便的工具。它提供了多种数据结构和算法,可以帮助我们更轻松地处理网络数据,并提高代码的效率和可读性。无论是处理数据、查找特定的值还是进行计算,STL都能够提供相应的功能,使网络编程变得更加简单和高效。 ### 回答2: C++ STL(标准模板库)是C++语言的一个重要部分,它提供了一系列的模板类和函数,用于实现常用的数据结构和算法。网络编程是指利用计算机网络进行数据传输和通信的一种编程方式。 在C++中使用STL进行网络编程时,可以使用STL提供的一些容器类和算法来简化网络编程的过程。例如,使用vector可以方便地管理接收或发送的数据;使用algorithm库中的函数可以快速处理数据;使用string类可以方便地操作字符串等。 对于网络编程,C++ STL没有直接提供相应的网络编程接口,而是需要借助于操作系统提供的网络编程库(如Socket套接字编程)来实现网络通信。通过将底层的网络库和STL进行结合,可以更加方便和高效地编写网络应用程序。 在网络编程中,可以使用STL中的容器类来存储和管理从网络中接收到的数据,例如使用vector来存储接收到的数据包,或者使用list来保存已连接的客户端。同时,可以使用STL中的算法来处理这些数据,例如使用find函数查找数据包中的特定元素。 除此之外,STL还提供了一些有用的函数和类,例如thread类可以用于多线程编程,用于处理并发的网络请求;atomic类可以保证对共享数据的原子性操作,用于实现线程安全的网络通信。 总而言之,C++ STLC++编程中的重要工具,它提供了一系列的模板类和函数,可以方便地实现网络编程中的数据管理和算法处理,而网络编程则是利用计算机网络进行数据传输和通信的一种编程方式。 ### 回答3: C++ STL(Standard Template Library)是一组标准库,提供了许多常用的数据结构和算法。它包含的容器类(如vector、list、map等)可以方便地存储和操作数据,而提供的算法则可以实现一些常用的数据处理操作(如排序、查找等)。 在网络编程方面,C++ STL可以使用socket库(socket.h)来实现网络通信。通过socket库,可以创建网络套接字(socket),并使用套接字进行数据的收发。 C++ STL中的vector可以被用来存储接收和发送的数据,可以使用其成员函数push_back()向vector中添加数据,使用operator[]访问vector中的数据。使用vector的好处是可以动态调整容器大小,使得数据存储更加方便。 通过网络套接字的recv()函数可以接收网络中传来的数据,而send()函数可以将数据发送到网络中。需要注意的是,在使用recv()函数接收数据时,应该使用一个循环来确保接收到完整的数据。可以使用std::string和字符数组来存储接收和发送的数据。 除了基本的网络通信,C++ STL还提供了一些辅助函数,用于解析和构造网络地址。例如,使用inet_ntoa()函数可以将IP地址从网络字节顺序转换为字符串形式,而inet_aton()函数则可以将IP地址从字符串形式转换为网络字节顺序。 总而言之,C++ STL可以在网络编程中提供丰富的数据结构和算法,使开发者可以更加轻松地实现各种网络应用。使用网络套接字和STL提供的功能,可以实现数据的发送和接收,并对数据进行各种处理和操作。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值