C++面向对象整理(3)之构造函数中的深拷贝与浅拷贝、this指针
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
C++中 构造函数中的深拷贝与浅拷贝、this指针
提示:本文为 C++ 中常用的 深浅拷贝 和 this指针 的用法和举例
一、this指针
1、this指针的含义
this 指针是指向调用成员函数的那个对象本身的一个隐藏在成员函数内部的指针。
this指针的隐式性质:this指针隐藏地加在每个成员函数中,当我们调用一个类的成员函数时,编译器会自动将对象的地址作为 this 指针传递给该函数。我们不需要显式地传递 this 指针给成员函数。实际上甚至也不能显式地传递 this 指针,编译器会为我们处理好它。
一句话,this 指针是隐式存在的,我们不需要(也不能)在函数调用时显式地传递它。比如有下面一个类:
class MyClass {
public:
int value;
void setValue(int v) {
value = v; // 在这里,value 默认指的是当前对象的成员变量
}
};
MyClass a;
a.setValue(100)
在上面的代码中,value = v;
会将传入的参数 v 赋值给当前对象的 value
成员,因为在这个上下文中没有其他的 value
可以引用。这里成员函数setValue
其实内部就暗含了一个this指针,value = v;
本质上等同于this->value = v;
只是隐藏省略了,这里a.setValue(100)
时,this指针就会指向对象a
,然后让对象a
的成员被赋值。在大多数情况下,this->value = v; 和 value = v; 在类的成员函数内部是可以互换使用的,并且它们有相同的效果。这是因为当在类的成员函数内部使用不带 this-> 前缀的变量名时,编译器会默认查找当前对象的成员变量。所以,如果没有局部变量、函数参数与成员变量重名,那么可以省略 this-> 前缀。
2、this指针的作用
如果成员函数的参数名或局部变量名与成员变量名相同,那么你就需要使用 this-> 前缀来明确指定你想要访问的是对象的成员变量,以此解决命名重复的冲突。例如:
class MyClass {
public:
int value;
void setValue(int value) {
this->value = value; // 使用 this-> 来区分成员变量 value 和函数参数 value
}
};
在这个例子中,this->value 指的是类的成员变量,而 value(没有 this-> 前缀)指的是函数参数。因此这时,使用 this-> 是必要的,以避免名称冲突。使用 this-> 可以使代码更加清晰直观。但工程上往往在命名成员变量的时候加上下划线,比如value_,然后函数参数用value。
3、利用*this 在成员函数中返回对象本身
因为 *this 实际上就是当前对象本身。当我们使用 *this 时,实际上是在引用调用成员函数的那个对象,这样就可以做链式调用。例如:
class MyClass {
public:
MyClass& incrementValue() {
this->value++; // 增加当前对象的 value 值
return *this; // 返回当前对象(即 *this)的引用
}
int value;
};
MyClass obj;
obj.incrementValue().incrementValue();//链式调用
在上面的代码中,incrementValue 函数返回 *this 的引用,之后便允许我们进行链式调用。
总的来说,this 指针在 C++ 中非常有用,它允许我们明确地引用当前对象,并解决可能的名称冲突问题和链式调用。
二、关于拷贝构造的浅拷贝
1、浅拷贝
当对象的某个成员变量是指向动态分配内存的指针时,(即new、malloc)。编译器默认提供的拷贝构造函数是浅拷贝构造函数,只是简单地复制指针的值,而不是复制指针所指向的实际内存内容。这意味着两个对象将共享相同的内存地址,相当于两个对象既地址相同也值相同。当这两个对象在生命周期结束时分别调用它们的析构函数时,每个析构函数都会尝试释放同一块内存,这会导致重复释放错误。 这种错误通常会导致运行时异常,因为操作系统或内存管理器不允许同一块内存被释放多次。
为了解决这个问题,通常需要为包含动态分配内存的类实现一个深拷贝构造函数和深拷贝赋值运算符。深拷贝会分配新的内存来存储数据,并将原始对象的数据复制到新分配的内存中。这样,每个对象都有自己的独立数据副本,并且在析构时只释放自己的内存。
下面是一个简单的例子来说明这个问题:
class ShallowCopyProblem {
public:
int* data;
ShallowCopyProblem(int value) {
data = new int(value); // 动态分配内存
}
// 编译器提供的默认拷贝构造函数是浅拷贝
ShallowCopyProblem(const ShallowCopyProblem& other) = default; //可以不要这句,这句意思是使用默认的自带拷贝构造
~ShallowCopyProblem() {
delete data; // 释放内存
}
};
int main() {
ShallowCopyProblem obj1(10); // 创建第一个对象,分配内存
ShallowCopyProblem obj2 = obj1; // 浅拷贝,obj1和obj2共享同一块内存
// 当obj1和obj2的生命周期结束时,它们的析构函数都会被调用
// 这将导致同一块内存被释放两次,引起问题
return 0;
}
在上面的代码中,obj1
和obj2
共享同一块通过new分配的内存。当main函数结束时,obj1和obj2
的析构函数都会被调用,每个析构函数都会尝试释放同一块内存,从而导致问题。
2、深拷贝函数的实现
为了避免这个问题,需要自定义深拷贝构造函数和赋值运算符:
class DeepCopySafe {
public:
int* data;
DeepCopySafe(int value) {
data = new int(value); // 动态分配内存
}
// 自定义深拷贝构造函数
DeepCopySafe(const DeepCopySafe& other) {
data = new int(*other.data); // 分配新内存并复制数据
}
// 自定义深拷贝赋值运算符
DeepCopySafe& operator=(const DeepCopySafe& other) {
if (this != &other) {
int* temp = new int(*other.data);
delete data; // 释放旧内存
data = temp; // 指向新内存
}
return *this;
}
~DeepCopySafe() {
delete data; // 释放内存
}
};
在上面的代码中,DeepCopySafe
类提供了深拷贝构造函数和深拷贝赋值运算符,确保了每个对象都有自己的独立数据副本,并且在析构时只释放自己的内存,从而避免了重复释放的问题。