首页 > 编程语言 >【C++ STL】深入理解string类的底层实现

【C++ STL】深入理解string类的底层实现

时间:2024-09-30 18:54:01浏览次数:12  
标签:const string STL pos C++ str 拷贝 size

string类的模拟实现

一.string的构造与析构函数

1.普通构造函数与析构函数

我们首先实现一下string类的基本框架,包括成员变量,构造以及析构函数

string底层其实是一个字符数组,与普通意义上的数组不同的是,string支持自动扩容,并且可以通过断言(assert)的方式来更加严格的检查越界问题,使用起来要更加的安全有效

class string
{
public:
	//默认构造函数
	string(const char* str="")
		:_size(strlen(str))
	{
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
	}
	//析构函数
	~string()
	{
		delete[] _str;
		_str = nullptr;
		_size = _capacity = 0;
	}
private:
	char* _str;
	size_t _size;
	size_t capacity;
};

分析代码:

  1. 构造函数:申请空间,并初始化string
  2. 析构函数:释放空间,避免出现内存泄露

2.拷贝构造的浅拷贝所带来的问题

我们接下来再考虑拷贝构造如何实现?
首先,通过类和对象部分的学习.我们知道:我们不实现,编译器为我们实现的默认的拷贝构造函数完成的是值拷贝也就是浅拷贝,我们的string类如果浅拷贝的话,程序就会直接崩溃.

浅拷贝的危害:
1. 对同一块空间会析构两次,第二次1析构时程序会崩溃,因为此时的空间已经被释放,是未被申请利用的.
2.如果我们对其中一个string进行修改,那么另一个string对象也会受到影响.

3.如何实现深拷贝

经过上述分析,为了避免浅拷贝带来的问题,我们需要在拷贝构造函数中实现深拷贝。深拷贝确保每个对象都有自己独立的内存空间,不会与其他对象共享内存。

代码示例:

string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

上述是传统写法(我们自己手动释放旧空间,申请新空间,并完成内容拷贝),下面我们介绍一下现代写法:

	void string::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);
	}

现代写法就是借助构造函数来完成空间的申请和内容的拷贝,再利用swap函数来交换,这样当函数调用完成即函数栈帧销毁时,由于tmp是临时对象,会调用析构函数完成资源的清理.

二.运算符重载

1.赋值运算符重载

在C++中,当我们将一个对象赋值给另一个对象时,同拷贝构造,默认情况下,编译器会为我们生成一个浅拷贝的赋值运算符。这意味着赋值后的对象和原对象会共享同一个内存空间,这会导致和浅拷贝相同的潜在问题,特别是在一个对象被销毁时,另一个对象继续使用该内存区域会引发错误。

因此这里依然会出现浅拷贝的问题,我们都思路和拷贝构造一样,完成深拷贝.

代码示例:

string& operator=(const string& s)
{
	char* tmp = new char[s._capacity + 1];
	strcpy(tmp, s._str);

	delete _str;
	_str = tmp;
	_size = s._size;
	_capacity = s._capacity;

	return *this;
}

整体思路和拷贝构造相似,都是先创建新空间,拷贝数据,释放旧空间.
下面我们看看令人惊叹的现代写法,只需要一行代码即可搞定:

string& operator=(string s)
{
	swap(s);
	return *this;
}

代码分析:由于此时参数不再是引用,所以s就是一个临时对象,直接交换,出了作用域s销毁自动调用析构函数.

2.大小比较相关的运算符重载

string的大小比较是按照ASCII码来进行,依次取两个string的第一个字符,第二个字符以此类推,直至出现大小不同.
例如:
aaaabc > aaaaaaaa

*为了方便,我们主要实现<和==,其他的比较都可以复用这两个函数
代码示例:

	bool operator<(const string& s) const
	{
		return strcmp(_str, s._str) < 0;
	}

	bool operator>(const string& s) const
	{
		return !(*this <= s);
	}

	bool operator<=(const string& s) const
	{
		return *this < s || *this == s;
	}

	bool operator>=(const string& s) const
	{
		return !(*this < s);
	}

	bool operator==(const string& s) const
	{
		return strcmp(_str, s._str) == 0;
	}

	bool operator!=(const string& s) const
	{
		return !(*this == s);
	}

三.迭代器的实现

迭代器是我们访问容器的一种通用方式,它可以让我们在不了解具体某个数据结构的底层的情况下,依旧完成对容器的遍历,通常情况下,我们可以将迭代器理解为像"指针"一样的东西.

这里由于string的底层是连续的物理空间,我们直接使用char* 作为string的迭代器即可.当然,在Linux和VS环境下官方库中的string的迭代器并不是简答的指针,而是将指针封装之后的一个类

这里我们可以打印迭代器的类型看一下:

int main()
{
	cout << typeid(string::iterator).name() << endl;
	return 0;
}

在VS下,这段程序的运行结果是:

class std::_String_iterator<class std::_String_val<struct std::_Simple_types<char>> >

可以看出,是经过复杂封装之后的模板类.

我们自己实现的迭代器代码示例:

class string
{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
	    iterator string::begin()
		{
			return _str;
		}
		
		iterator string::end()
		{
			return _str + _size;
		}
		
		const_iterator string::begin() const
		{
			return _str;
		}
		
		const_iterator string::end() const
		{
			return _str + _size;
		}
};

在定义了begin和end函数之后,我们自己的string也就支持了C++11中的范围for遍历.

四.string常用操作的实现

1.静态const成员npos的定义

通过查看文档,我们会发现在插入,删除,查找等函数中,都用了一个缺省值npos,它的类型是const static size_t,值为-1,也就是说它表示无符号整数的最大值,大约是42亿多.

对于静态成员变量的声明与定义,之前的类和对象篇有过讲解,如有需要可以前往:类和对象(下).

代码示例:

class string {
public:
    static const size_t npos = -1;  // 可以在类内初始化
};

2.插入操作

	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);

			delete[] _str;
			_str = tmp;

			_capacity = n;
		}
	}
void insert(size_t pos, char ch)
{
	assert(pos < _size);

	if (_size == _capacity)
	{
		size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newcapacity);
	}

	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		end--;
	}

	_str[pos] = ch;
	_size++;
}

这里注意一个点即可: size_t(无符号整数) 与 0 相比较作为循环条件时,容易出现死循环
例如:

size_t end = _size ;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		end--;
	}
//当pos等于0时,会出现死循环!!!

3.查找操作

代码示例:

	//查找一个子串
	size_t find(const char* str, size_t pos) const
	{
		char* sub = strstr(_str + pos, str);
		return sub - _str;
	}
	//查找一个字符
	size_t find(char ch, size_t pos) const
	{
		for (int i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

4.删除操作

代码示例:

	//从pos位置开始,删除len个字符
	void erase(size_t pos, size_t len)
	{
		assert(pos < _size);

		if (len > _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
	}

希望能让你对string的理解更加透彻,感谢观看!!!

标签:const,string,STL,pos,C++,str,拷贝,size
From: https://blog.csdn.net/2301_80176226/article/details/142660402

相关文章

  • 用C/C++构建自己的Redis——第五章、Redis中的AVL树实现
    用C/C++构建自己的Redis——第五章、Redis中的AVL树实现文章目录用C/C++构建自己的Redis——第五章、Redis中的AVL树实现前言一、键值对集查询概念1.1键值对集合查询1.2数据结构排序的复习排序数组(SortedArrays)树形数据结构(TreeDataStructures)通过随机性平衡(Balan......
  • 在Ubuntu下,为Vim配置C/C++代码补全
    1.安装vim-plugvim-plug是vim的一个插件管理器。(1)vim-plug的下载网址(2)新建目录~/.vim/autoload/(3)将 plug.vim文件放入该目录(4)添加vim-plug的代码到~/.vimrc文件,如下所示callplug#begin()"ListyourpluginsherePlug'tpope/vim-sensible'callplug#end......
  • WPF Progrss bar stringformat {} {0}% IsDetermined
    //xaml<Windowx:Class="WpfApp425.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mi......
  • vscode 配置 c/c++
    vscode配置c/c++[!CAUTION]使用本文的配置需要预装cmake,msvc,拥有cmake、CMakeTools插件工程目录D:.│CMakeLists.txt│├─.vscode│CMakePresets.json│c_cpp_properties.json│└─code│main.cpp│└─head......
  • 分享C++程序员面试八股文(十五)
    以下是C++常见八股文(十五):一、C++中的高级文件操作(AdvancedFileOperations)解释文件随机访问的方法及应用场景方法:在C++中,可以使用文件流对象(如std::ifstream、std::ofstream、std::fstream)的seekg(设置输入位置)和seekp(设置输出位置)成员函数来实现文件的随机访问。这......
  • Qt/C++ 音视频开发 - FFmpeg 安卓版
    Qt/C++音视频开发-FFmpeg安卓版介绍FFmpeg是一个开源的多媒体框架,它可以用来录制、转换和流式传输音视频。在Qt/C++开发中,FFmpeg可以用于处理各种音视频任务,例如转码、推流等。将FFmpeg集成到安卓平台上,可以实现强大的移动端音视频处理功能。应用使用场景视频......
  • 南沙C++信奥赛陈老师解一本通题 2005:【20CSPJ普及组】直播获奖
    ​ 【题目描述】NOI2130即将举行。为了增加观赏性,CCF决定逐一评出每个选手的成绩,并直播即时的获奖分数线。本次竞赛的获奖率为 w%w%,即当前排名前 w%w% 的选手的最低成绩就是即时的分数线。更具体地,若当前已评出了 pp 个选手的成绩,则当前计划获奖人数为 max(1,⌊p∗w%......
  • 南沙C++信奥赛陈老师解一本通题1965:【14NOIP普及组】珠心算测验
    ​ 【题目描述】珠心算是一种通过在脑中模拟算盘变化来完成快速运算的一种计算技术。珠心算训练,既能够开发智力,又能够为日常生活带来很多便利,因而在很多学校得到普及。某学校的珠心算老师采用一种快速考察珠心算加法能力的测验方法。他随机生成一个正整数集合,集合中的数各不......
  • C++入门
    第1节:开发环境的搭建与配置1.1目标在本节课中,学生将学习如何在Windows上搭建一个现代化的C++开发环境,并使用VSCode和CMake工具进行C++程序的开发与调试。学生将掌握以下内容:安装VSCode及C++插件安装MinGW或其他C++编译器安装并配置CMake创建并编译第一个C++项目使用VSCod......
  • 南沙C++信奥赛陈老师解一本通题:1945:【09NOIP普及组】多项式输出
    ​ 【题目描述】一元 nn 次多项式可用如下的表达式表示: f(x)=anxn+an−1xn−1+...+a1x+a0,an≠0f(x)=anxn+an−1xn−1+...+a1x+a0,an≠0 其中,aixii 称为i次项,ai称为ii次项的系数。给出一个一元多项式各项的次数和系数,请按照如下规定的格式要求输出该多项式:1.多项式中......