[C++ 面向对象高级编程]string类的实现过程
文章概述
该文章为 侯捷教授的 C++ 面向对象高级编程 课程的个人笔记,记录了string类相关的知识点
作者信息
NEFU 2020级 zsl
ID:fishingrod/鱼竿钓鱼干
Email:851892190@qq.com
欢迎各位引用此博客,引用时在显眼位置放置原文链接和作者基本信息
参考资料
感谢前辈们留下的优秀资料,从中学到很多,冒昧引用,如有冒犯可以私信或者在评论区下方指出
正文部分
相比complex类,string是带指针的类,在编写的时候需要考虑不同的地方
class String
{
public:
String(const char* cstr=0); //构造函数
String(const String& str); //拷贝构造
String& operator=(const String& str); //拷贝赋值
~String(); //析构函数
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
拷贝构造,拷贝赋值,析构函数
String s3(s2);//拷贝构造
s3 = s1;//拷贝赋值
在编写含有指针的类首先需要考虑编写拷贝构造,拷贝赋值。因为编译器默认实现的是很原始的每位copy,显然这对带指针的类是不合适的。
如果是使用默认的拷贝构造和拷贝赋值,我们进行的是浅拷贝,只拷贝了一个指针,这会造成多个指针指向同一块内存(一个修改了会影响其他的),或者内存泄漏(一块内存区域仍然存在但没有指针指向它)
因此我们需要手写深拷贝的,拷贝构造函数和拷贝赋值函数
拷贝赋值
拷贝赋值:检测自我赋值,如果不是就先清空原来的,再复制过去
inline
String& String::operator=(const String& str)//拷贝赋值函数
{
if (this == &str)//检测自我赋值
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
检测自我赋值:如果不写检测自我赋值,那么有可能会直接把自己杀掉了,后面也就没法获取原来的信息了。
拷贝构造
拷贝构造:申请一个新空间,把内容复制过去
inline
String::String(const String& str)//拷贝构造函数
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
析构函数
析构函数是拿来清理善后的,当对象结束其生命周期,如对象所在的函数已调用完毕时,系统自动执行析构函数。至于为何要编写析构函数,会在下面内存分配标题下做解释
inline
String::~String()
{
delete[] m_data;
}
output函数
还是重载<<来处理输出
对此做简单补充说明:首先cout是可以将字符串指针输出为字符串的,所以我们可以把指针传过去
另外为了符合使用习惯,所以我们使用非成员函数来实现<<的重载,否则用的时候写的顺序就要反过来很难受
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
堆栈与内存管理
在下面的代码中我们看到了创建和删除对象的新方法new
和delete
inline
String::String(const char* cstr)//构造函数
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()//析构函数
{
delete[] m_data;
}
堆栈
栈
:存在于某个作用域(scope)内的一块内存空间
堆
:由操作系统指定的一块global空间,可动态分配
Complex c3(1,2); //global object
{
Complex c1(1,2);//stack object
static Complex c2(1,2); //static object
Complex* p = new Complex; //heap object
delete p;
}
stack object
:生命随着作用域结束而结束,会被自动清理,析构函数会自动的调用起来
static object
:生命在作用域结束之后仍然存在直到程序结束析构函数要一直到整个程序结束
global object
:生命在程序结束之后结束,作用域为整个程序,可以看作是一种static object
heap object
:生命在它被delete
时结束,如果不delete
那么作用域结束时,p所指的heap object
仍然存在,但是p的生命结束了,这样会造成内存泄漏
我们先前写的complex
类当中并没有编写特定的析构函数,因为我们没有去new之类的操作,所以没有必要。编译器会自动添加一个析构函数的声明,只不过什么都不做罢了。反正作用域结束他就消失了。
new与delete
new:先分配memory,再调用构造函数
Complex * pc = new Complex(1,2);
//编译器视角
void* mem = operator new(sizeof(Complex));//分配内存,内部调用malloc
pc = static_cast<Complex*>(mem);//转型
pc->Complex::Complex(1,2);//构造函数
delete:先调用析构函数,在释放memory
String* ps = new String("Hello");
delete ps;
//编译器delete视角
String::~String(ps);//析构函数
operator delete(ps);//释放内存,内部为free
array new 搭配 array delete
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()
{
delete[] m_data;
}
如果不是用
array delete
编译器不知道要删除的是一个数组,这样的话虽然new出来的string数组对象整体会被删除(存指针的部分),但是程序不知道数组里其他的指针还指向了其他的一些内容。这样会造成Memory leak
即内存泄漏(指针被删除了,但是后面内容没有消除)。
因此对于有指针的类,务必要记住array new
对应array delete
。
同理,我们之前写的Complex类
因为没有指针也没有new动态分配内存,所以可以不用array delete
,但是何必呢
要点总结
- 带指针类实现需要注意以下几点:拷贝构造,拷贝赋值,析构函数
- 构造函数记得传初值
- 字符串new申请空间长度+1,存结束符
- array new搭配array delete
- 拷贝赋值注意连续赋值需要返回值,以及自我赋值检测
代码
string.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0); //构造函数
String(const String& str); //拷贝构造
String& operator=(const String& str); //拷贝赋值
~String(); //析构函数
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = '\0';
}
}
inline
String::~String()//析构函数
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)//自我赋值检测
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this; //注意返回值连续赋值情况需要返回值
}
inline
String::String(const String& str)//拷贝赋值函数
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
test_string.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}