习惯写Leetcode而不习惯写工程的在校同学,很大概率在处理细节时会感到陌生
类定义如下:
class String
{
public:
String();//默认构造函数
String(const char* str);//构造函数
String(const String& other);//拷贝构造函数
String(String&& other);//移动构造函数
~String();//析构函数
String& operator= (const String& other);//拷贝赋值函数
String& operator= (String&& other);//移动赋值函数
private:
char* m_data;
int size;
};
主要是对c风格字符串的处理。
具体函数实现如下:
String::String() : m_data(new char[1]), size(0) {
m_data[0] = '\0';
}
String::String(const char* str)//传递const char*字符串
{
if (str == nullptr)
{
m_data = new char[1];
*m_data = '\0';
size = 0;
}
else
{
size = strlen(str);//strlen不包括标志符号
m_data = new char[size+1];
strcpy(m_data, str);
}
}
String::~String()
{
delete[] m_data;
}
String::String(const String& other)
{
size = other.size;
m_data = new char[size+1];
strcpy(m_data, other.m_data);
}
//移动构造
String::String( String&& other) noexcept{
//在移动构造和移动复制中明确告诉编译器,不会抛出异常
m_data = other.m_data;
size = other.size;
other.m_data = nullptr;
other.size = 0;
}
//拷贝赋值
String& String::operator=(const String& other)
{
if (this == &other)return *this;
delete[] m_data;
size = other.size;
m_data = new char[size + 1];
strcpy(m_data, other.m_data);
return *this;
}
String& String::operator=(String && other) noexcept
{
if (this != &other) {
delete[] m_data;
m_data = other.m_data;
size = other.size;
other.m_data = nullptr;
other.size = 0;
}
return *this;
}
需要注意的点
1.成员初始化列表:
String::String() : m_data(new char[1]), size(0) {
m_data[0] = '\0';
}
成员初始化列表的使用允许直接初始化类的成员变量。在默认构造函数中,m_data
和 size
在构造函数体执行之前已经被初始化。这种方式不仅简洁,还可以提高性能,避免不必要的赋值操作。
a.对于成员对象(类的成员变量是其他对象),在构造函数体中赋值会涉及:
- 先调用成员对象的默认构造函数
- 再在构造函数体中对其赋值,这样会引发不必要的对象构造与析构操作。
使用成员初始化列表可以直接构造成员对象,避免多余的对象操作,从而提高性能。
b.有些类型不能在构造函数体中使用赋值进行初始化,比如 const
成员变量和引用(&
)。它们必须通过初始化列表进行初始化,因为它们的值一旦初始化就不能再改变。
在本例中,m_data
是一个指针,而 new char[1]
会动态分配一块内存。这种初始化可以直接在初始化列表中完成,保证在构造函数体执行之前,指针已经指向一块有效的内存区域。
2.使用动态内存分配:
这里使用new分配内存,而且分配的是字符数组,所以要使用delete[];
3.深拷贝:
拷贝构造函数执行深拷贝,即为新对象分配自己的内存空间,而不是简单地复制指针。这避免了多个对象共享同一块内存的风险,保证了每个对象独立管理自己的内存。
4.移动构造函数:
- 右值引用:
String&& other
表示移动语义中接收的右值引用,可以直接“偷取”资源(指针和大小)而不是进行拷贝。 noexcept
关键字:使用noexcept
明确表示该函数不会抛出异常,这对性能优化有帮助(特别是在某些容器的优化操作中,如std::vector
会利用noexcept
移动构造函数提高效率)。- 资源转移:将
other.m_data
的指针转移到当前对象,并将other.m_data
置为nullptr
,确保其他对象不再拥有这块内存的控制权,避免重复释放。
5.拷贝赋值运算符:
- 自赋值检测:
if (this == &other)
检测自赋值的情况,避免自赋值导致内存出错。 - 删除旧数据:在进行新的拷贝之前,必须先释放当前对象所管理的内存,避免内存泄漏。
- 深拷贝:重新分配内存并拷贝数据,确保赋值后的对象有自己独立的内存。
6.*this:
this
是一个指向当前对象的指针,而 *this
表示解引用该指针,即得到当前对象本身的引用。返回 *this
是为了实现链式调用和返回当前对象本身。