C++ String部分成员模拟实现流程详解

目录
  • string类的成员设计
  • 普通构造函数的模拟
  • 拷贝构造函数的模拟
  • 赋值重载函数的模拟
  • String的析构函数模拟
  • 补全上述的成员函数
  • 迭代器的简单模拟
  • 其他成员函数的模拟

string类的成员设计

	class string
	{
	private:
		char* _str;
		int _size;
		int _capacity;
	};

说明:以下的五个成员函数的模拟实现,均去除了_size_capacity成员变量,目的是为了更方便解释重点。在五个成员函数模拟后,会对string类的设计进行补全。

普通构造函数的模拟

我们是否可以使用默认构造函数来初始化对象?在这种情况下是万万不能的!要记住默认的构造函数对自定义类型会去调用它自己的构造函数进行初始化,而对于内置类型是不做处理的,此时我们的成员变量_str的类型是内置类型,不会被初始化,所以一定要自己写构造函数。

//这种构造函数是否可行?
string(const char* str)
{
	_str = str;
}

这种写法做不到用字符串构造一个对象。

原因:这样会使得str_str指向的都是同一块空间。str会影响到_str.

所以正确的做法是,给_str分配一块属于自己的空间,再把str的值拷贝给_str.

string(const char* str)
{
	_str = new char[strlen(str) + 1]; //要多给一个'\0'的空间
	strcpy(_str, str);
}

修一下小细节:

1.实例化对象的时候是支持无参构造的,所以可以给参数一个缺省值"",里面自己隐藏的有一个\0.如果没有传参数,则使用缺省值。

string(const char* str = "")
{
	_str = new char[strlen(str) + 1]; //要多给一个'\0'的空间
	strcpy(_str, str);
}

拷贝构造函数的模拟

看了普通构造函数的模拟实现以后,最不应该犯的错就是把一个string对象的数据直接给了另一个string对象

所以直接甩代码

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

当然,如果有前面所写普通构造函数,还可以利用普通构造函数来拷贝构造一个对象。

//还可以借助普通构造函数
string(const string& s)
	:_str(nullptr)
{
	string tmp(s._str);
	swap(_str, tmp._str);
}

赋值重载函数的模拟

这里重载赋值,是为了把一个已经存在的string对象的数据给另一个已经存在的string对象。

也就意味着,两个对象均有自己的空间。不要把string对象的_str直接赋值,否则析构的时候会析构两次,并且这两个string对象由于_str使用的是同一块空间,会相互之间影响。

string& operator=(const string& s)
{
	delete[] _str; //把原来的空间释放掉
	_str = new char[strlen(s._str) + 1]; //给一块新的空间
	strcpy(_str, s._str);;
}

上面这种方法是不行的。

1.不排除自己给自己赋值的情况,自己都给释放了,拿什么来赋值?

2.使用delete先释放,只要地址正确无论如何都会释放成功,但是new一块空间不一定会成功,如果一开始就给释放了,而我去申请空间却申请不到,那就是不仅没有赋值成功,还把我自己原本有的给丢了。

//正确的写法
string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[strlen(s._str) + 1];
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
	}
	return *this; //如果自己给自己赋值,那就返回自己
}

还可以使用传值的方法

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

String的析构函数模拟

~string()
{
	if (_str)
	{
		delete[] _str;
		_str = nullptr;
	}
}

补全上述的成员函数

//因为std库里原本有一个string,所以这里加上一个命名空间,防止命名污染
namespace YDY
{
	class string
	{
	public:
		string(const char* str = "")
			:_size(strlen(str))
			,_capacity(_size)
		{
			_str = new char[_capacity + 1]; //要多给一个'\0'的空间
			strcpy(_str, str);
		}
		string(const string& s)
			:_str(nullptr)
			,_size(s._size)
			,_capacity(s._capacity)
		{
			string tmp(s._str);
			swap(_str, tmp._str);
		}
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				char* tmp = new char[strlen(s._str) + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}
			return *this;
		}
		~string()
		{
			if (_str)
			{
				delete[] _str;
				_str = nullptr;
			}
			_size = _capacity = 0;
		}
	private:
		char* _str;
		int _size;
		int _capacity;
	};
	void test()
	{
		string s1;
		string s2(s1);
		string s3 = s1;
	}
}

迭代器的简单模拟

		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;
		}

其他成员函数的模拟

		const char* c_str()
		{
			return _str;
		}
		size_t size()
		{
			return _size;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		//reserve
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//扩容到n+1
				//tmp是内置类型,
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}
		//
		void push_back(char c)
		{
			//空间不够,扩容
			if (_size == _capacity)
			{
				//扩容
				reserve(_size + 1);
			}
			_str[_size] = c;
			_size++;
			_str[_size] = '\0';
		}
		void append(const char* str)
		{
			int len = strlen(str);
			if (_size + len > _capacity)
			{
				//增容
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* str)
		{
			//复用追加函数append()
			append(str);
			return *this;
		}
		//任意位置的插入
		string& insert(size_t pos, char ch)
		{
			if (_size == _capacity)
			{
				reserve(_size + 1);
			}
			//开始插入
			int end = _size + 1;
			//找到pos的位置,并留出pos的位置以便插入
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
			return *this;
		}
		string& insert(size_t pos, const char* str)
		{
			assert(pos < _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				//增容
				reserve(_size + len);
			}
			//找到pos的位置,并且留出要插入的位置
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				end--;
			}
			//开始插入
			strncpy(_str + pos, str, len);
			return *this;
		}
		//从pos的位置开始删除len的长度
		string& erase(size_t pos = 0, size_t len = std::string::npos)
		{
			assert(pos < _size);
			if (len == std::string::npos || pos + len > _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

到此这篇关于C++ String部分成员模拟实现流程详解的文章就介绍到这了,更多相关C++ String成员模拟实现内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 详解C++中String类模拟实现以及深拷贝浅拷贝

    详解C++中String类模拟实现以及深拷贝浅拷贝 在C语言中/C++中,字符串是一个应用很广泛的类型,也是很基础的类型,C语言并没有直接处理字符串的操作而是采用字符指针和字符串数组进行操作,而在C++中标准库为我们封装了一个字符串的类供我们使用,使用需要#inlcude <string>头文件.我们也可以自己模拟实现一个简单的String类. 在模拟实现String类的过程中,不可避免的会遇到深拷贝浅拷贝的问题,下面就深拷贝浅拷贝做一个简介.所谓深拷贝浅拷贝,简单来说就是浅拷贝只是简单的将值

  • C++中string的模拟实现

    c++中的string类可以实现字符串对象的一系列操作,如下图就是从cplusplus上截取的string的一部分功能: 接下来我就简单模拟几个函数实现 首先,我们要给出完整的string类,包括构造函数,析构函数,私有成员char* str 并且在类内声明要实现的函数(本文我只实现了operator=,operator[ ],pushback(),以及三个operator+=,五个insert等) #include<iostream> #include<cstring> usin

  • C++string底层框架模拟实现代码

    目录 一. 前言 二. 浅拷贝与深拷贝优缺点 1. 浅拷贝 2. 深拷贝 3. 深拷贝现代版 4. 写时拷贝 三. string框架搭建 1. 框架定义 2. 构造函数 3. 析构函数 4. 赋值重载 5. 实现扩容 6. 增添数据 7. 删除数据 8. 数据查找 9. iterator迭代器 10. 插入/提取流与getline函数 四. 完整代码 一. 前言 本节文章主要说明浅拷贝和深拷贝的优缺点,以及仿写string类的逻辑并分析实现过程 如果人对一个事物感兴趣,总是那么好奇,就会刨根问底

  • 关于C++STL string类的介绍及模拟实现

    目录 一.标准库中的string类 1.string类 2.string类中的常用接口说明+模拟实现 2.1 string类对象的常见构造+模拟实现 2.2 string类对象的容量操作+模拟实现 2.3 string类对象的访问及遍历操作+模拟实现 2.4 string类对象的修改操作+模拟实现 2.5 string类非成员函数+模拟实现 一.标准库中的string类 1.string类 字符串的表示字符序列的类 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专

  • c++模拟实现string类详情

    目录 一.string类简介 二.模拟实现 成员变量 成员函数 迭代器 重载运算符[ ] 三.几种常见函数 reserve() resize() push_back() append() 重载+= insert() erase() find() 四.操作符重载 流插入<< 流提取>> 一.string类简介 标准库类型string表示可变长的字符序列,使用string类型必须首先包含string头文件.作为标准库的一部分,string定义在命名空间std中. 二.模拟实现 成员变量

  • C++ String部分成员模拟实现流程详解

    目录 string类的成员设计 普通构造函数的模拟 拷贝构造函数的模拟 赋值重载函数的模拟 String的析构函数模拟 补全上述的成员函数 迭代器的简单模拟 其他成员函数的模拟 string类的成员设计 class string { private: char* _str; int _size; int _capacity; }; 说明:以下的五个成员函数的模拟实现,均去除了_size 和_capacity成员变量,目的是为了更方便解释重点.在五个成员函数模拟后,会对string类的设计进行补全

  • C语言 数据结构之数组模拟实现顺序表流程详解

    目录 线性表和顺序表 线性表 顺序表 静态顺序表 动态顺序表 代码已经放在Gitee上,需要可以小伙伴可以去看看 用C语言数组模拟实现顺序表 Gitee 线性表和顺序表 线性表 线性表(linear list)是n个具有相同特性的数据元素的有限序列,这是我们广泛使用的数据结构. 线性表在逻辑上是线性结构,也就说是连续的一条直线.但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储. 常见的线性表:顺序表.链表.栈.队列.字符串- 顺序表 顺序表是用一段物理地址连

  • C++模拟实现vector流程详解

    目录 模拟vector 总结 模拟vector 我们可以通过模板实现类似vector的类.我们实现一个StrVecTemp类,其内部通过allocator开辟空间,存储的类型用T来表示,T是模板类型. template <typename T> class StrVecTemp { public: StrVecTemp() : elements(nullptr), first_free(nullptr), cap(nullptr) {} //拷贝构造函数 StrVecTemp(const St

  • Vue echarts模拟后端数据流程详解

    目录 KOA2的使用 安装 Koa app.js入口文件 KOA2的使用 KOA2是由Express 原班人马打造. 环境依赖 Node v7.6.0 及以上. 支持 async 和 await 洋葱模型的中间件 写响应函数(中间件) 响应函数是通过use的方式才能产生效果, 这个函数有两个参数, ctx :上下文, 指的是请求所处于的Web容器,我们可以通过 ctx.request 拿到请求对象, 也可 以通过 ctx.response 拿到响应对象 next :内层中间件执行的入口 模拟服务

  • Java代码生成器的制作流程详解

    1. 前言 前几天写了篇关于Mybatis Plus代码生成器的文章,不少同学私下问我这个代码生成器是如何运作的,为什么要用到一些模板引擎,所以今天来说明下代码生成器的流程. 2. 代码生成器的使用场景 我们在编码中存在很多样板代码,格式较为固定,结构随着项目的迭代也比较稳定,而且数量巨大,这种代码写多了也没有什么技术含量,在这种情况下代码生成器可以有效提高我们的效率,其它情况并不适于使用代码生成器. 3. 代码生成器的制作流程 首先我们要制作模板,把样板代码的固定格式抽出来.然后把动态属性绑定

  • C语言静态与动态通讯录的实现流程详解

    目录 静态通讯录 contact.h contact.c test.c 动态通讯录 contact.h contact.c qsort.c test.c 本次通讯录的代码已经放到我的Gitee仓库中,感兴趣的小伙伴可以去看看 Gitee 静态通讯录 在我们学习完C语言的结构体.指针以及动态内存管理之后,我们就可以实现一些有意思的小项目了,通过这些小项目可以加深我们对于相关知识的理解. 静态通讯录主要要求有 静态大小,可以记录10个人的信息(大小自己定) 记录的信息如下:名字.性别.年龄.电话.住

  • Java GUI图形界面开发实现小型计算器流程详解

    目录 一.设计目标 二.界面设计 三.功能实现 四.全部代码 五.功能测试 六.小结 一.设计目标 (1)主要功能:实现简单的加.减.乘.除等双目运算,和开平方.百分数等单目运算 (2)辅助功能:按钮“C”实现清空文本框:按钮“←”实现退格,删除文本框中最右边的一个字符 二.界面设计 创建“面板对象”,并设置其布局管理方式为5行4列的GridLayout布局方式,以用于容纳20个按钮.文本框和容纳20个按钮组件的面板则使用边界布局方式分别将其布局到窗体BorderLayout.NORTH和中央位

  • Vue登录功能的实现流程详解

    目录 Vue项目中实现登录大致思路 安装插件 创建store 封装axios qs vue 插件 api.js的作用 路由拦截 登录页面实际使用 Vue项目中实现登录大致思路 1.第一次登录的时候,前端调后端的登陆接口,发送用户名和密码 2.后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token 3.前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面 4.前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登

  • Java中缀表达式转后缀表达式流程详解

    目录 一.栈 1.栈的基本介绍 2.栈的底层实现 二.中缀表达式转后缀表达式 1.拆解中缀表达式 2.中缀转后缀的算法 3.中缀转后缀代码解析 4.对后缀表达式进行计算 一.栈 1.栈的基本介绍 栈是⼀个先⼊后出的有序列表.栈(stack)是限制线性表中元素的插⼊和删除只能在线性表的同⼀端进⾏的⼀种特殊线性表.允许插⼊和删除的⼀端,为变化的⼀端,称为栈顶(Top),另⼀端为固定的⼀端,称为栈底(Bottom). 根据栈的定义可知,最先放⼊栈中元素在栈底,最后放⼊的元素在栈顶,⽽删除元素刚好相反,

  • Springboot实现动态定时任务流程详解

    目录 一.静态 二.动态 1.基本代码 2.方案详解 2.1 初始化 2.2 单次执行 2.3 停止任务 2.4 启用任务 三.小结 一.静态 静态的定时任务可以直接使用注解@Scheduled,并在启动类上配置@EnableScheduling即可 @PostMapping("/list/test1") @Async @Scheduled(cron = "0 * * * * ?") public void test1() throws Exception { Ob

随机推荐