侯捷老师c++笔记:string类的实现过程

string类:内部含有指针

如何去写一个类?

  1. 首先考虑类的内部的成员,主要是看类的数据成员是否包含pointer
  2. 其次考虑如何构造函数的参数和返回值(参数是否要添加const)
  3. 如果含有pointer的话,拷贝构造函数,拷贝赋值函数,以及析构函数都需要用户自己去定义,不能使用编译器提供的默认函数。
    • 一般来说,构造函数,拷贝构造函数,拷贝赋值函数是不能被设置为const方法的,因为类的数据成员是要发生改变的。
  4. 之后思考每一个函数内部如何实现。
  5. 添加公共接口,用来和外界进行交互。
  6. 一般来说,都需要重写输入输出运算符,因为标准的输入输出运算符只能够输出内置类型,而用户自定义的类类型,需要用户自身去定义。

下面就来实操一下吧:
首先考虑string类的作用,可以构造字符串。它的private成员:最好被设置为指针,因为如果直接使用字符数组的话,并不知道输入的字符串的大小是多少,而采用指针的方法:可以进行动态管理的内存。所以这里将私有成员设为指针变量。

class my_string
{
public:
	my_string(const char* cstr = 0)
	{
		if (cstr)
		{
			m_data = new char[strlen(cstr) + 1];
			//将str中的元素拷贝到m_data中。
			strcpy(m_data, cstr);
		}
		else
		{
			// 开辟内存空间
			m_data = new char[1];
			// 初始化
			*m_data = '\0';
		}
	}
	//拷贝构造函数,传入的参数是类的引用
	my_string(const my_string& str);
	//拷贝赋值函数
	my_string& operator=(const my_string& str);
	char* get_c_str() const { return m_data; }
	~my_string();
private:
	char* m_data;
};
inline my_string::~my_string()
{
	delete[] m_data;
}
inline my_string::my_string(const my_string& str)
{
	//开辟空间
	m_data = new char[strlen(str.m_data) + 1];
	//把元素放进去
	strcpy(m_data, str.m_data);
}
my_string& my_string:: operator=(const my_string& str)
{
	if (this == &str)
		return *this;
	//1.释放原本的空间
	delete[] m_data;
	// m_data开辟str.data大小的空间
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
	return *this;
}
ostream& operator<<(ostream& out, const my_string& str)
{
	out << str.get_c_str();
	return out;
}

测试代码:

int main()
{
    my_string a1("hello");
    my_string a2(a1);
    my_string a3("i am a student");
    /*a3 = a1;*/
    a3.operator=(a1);
    //等价于a3=a1;
    cout << a1 << ":" << a2 << ":" << a3 << endl;
}

几个重要的地方:
1.visual_2019规定必须在类内初始化成员变量:因此构造函数的话需要写在class内,而不能放在类外。
2.字符串进行拷贝之前,必须给字符串开辟空间,而不能直接strcpy。
3.自己写代码的时候:需要注意变量和返回值的类型,没有养成这个习惯。如果给定的参数不发生改变,最好加上const,防止在内部修改。
4.operator=:其实将可以将它看成函数名。this指针指向的是被调用对象的地址。
5.赋值运算符中,要预防自我赋值的原因不仅仅是为了提升效率,如果不预防自我赋值的话,程序就会出现错误,理由如下:
s1和s2指向同一块内存空间,在delete之后,m_data所指向的空间已经被销毁,因此计算strlen((s2.m_data))已经没有意义啦!
在这里插入图片描述

堆和栈的内存空间

1.栈:是存放在某作用域内的一块内存空间。
例如,当我们调用函数的时候,函数本身会形成一个stack来放置它接收的参数,以及它的返回地址。在函数体内声明的任何变量,在脱离了作用域之后会,会被系统自动回收。
2.堆:又称为system heap,它是由操作系统提供的一块全局内存空间,一般通过new或者malloc开辟的空间是堆中的内存空间,它需要用户自己手动的开辟和释放空间,否则将会造成内存泄漏。

class complex{.....};
.......
{
	complex* p= new complex;
}

如果不进行delete操作的话,指针p是存放在栈区的,因此当作用域结束之后,p所指向的heap-object 仍然存在,然而p指针的生命会结束,相当于我们找不到存储堆对象的那块空间了,故而会出现内存泄漏。

3.stack object:这里的c1就是stack object,也称为auto object,因为它是在栈上开辟的一块空间,因此其生命在作用域(scope)结束之后而结束。栈内的数据会被系统自动清理掉。

{
	complex c1(1,2);
}

4.static local object :c2即是静态对象,它的生命在scope结束之后仍然存在,直到整个程序结束。

{
	static complex c2(1,2);
}

new和malloc内部的机制

new的机制

在这里插入图片描述
new的基本原理是:先分配内存,再调用类的构造函数。
内部的函数如上图所示:
首先调用operator new函数分配一块内存空间-operator new的内部会调用malloc函数来开辟内存空间。
其次通过static_cast,即将void类型的指针强转为类类型的指针
最后通过指针访问该对象,调用其构造函数。

delete的机制

当对象的生命周期结束之后,系统会自动的调用析构函数。析构函数主要完成对类的清理善后工作,在最开始的例子中,string对象中的成员变量m_data是一个指针,既然是指针,那么其所指向的内存空间需要在析构函数中被释放,因为该内存空间也是new出来的。
在这里插入图片描述

为什么array new一定要搭配array delete?

在这里插入图片描述
如果申请了3个string类的对象,释放的时候一定要加[],如果直接delete p,由p申请的这三个heap object是可以被释放掉的,因为这里的21h:2*16+1=32字节,加1是因为:标记该内存块被使用,申请的总的内存大小是32个字节。由于已经记录了堆空间所开辟的内存的大小。
真正的原因在于:delete p的时候,只会调用一次析构函数,这样导致的结果就是后面两个对象没有调用析构函数,因此m_data所指向的内存空间无法被释放,如果数据成员中不含有指针的话,delete p和delete[] p是没有区别的,但是由于含有指针,因此它开辟的堆空间也需要被释放掉。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值