前言
本文介绍了C++面向对象的细节原理,基于施磊老师的C++基础课程
一、掌握对象的深拷贝和浅拷贝
对象默认的拷贝构造是做内存的数据拷贝,即浅拷贝。关键是对象如果占用外部资源,那么浅拷贝就会出现问题。如:
#include <iostream>
using namespace std;
class SeqStack {
public:
SeqStack(int size = 10) {
cout << this << " SeqStack() " << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
~SeqStack() {
cout << this << " ~SeqStack() " << endl;
delete[] _pstack;
_pstack = nullptr;
}
private:
int *_pstack;
int _top;
int _size;
};
int main()
{
/*
下面的代码运行后会爆core dump,因为在调用析构函数时,首先
调用s2的析构函数,释放了_pstack指向的资源,之后调用s1的析构
函数,由于二者指向的是同一块内存区域,因此重复释放导致错误
*/
SeqStack s;
SeqStack s1(10);
SeqStack s2 = s1;
return 0;
}
1、拷贝构造函数和赋值运算符重载
为了解决这个问题,我们需要自行实现拷贝构造函数,即深拷贝逻辑:
#include <iostream>
using namespace std;
class SeqStack {
public:
SeqStack(int size = 10) {
cout << this << " SeqStack() " << endl;
_pstack = new int[size];
_top = -1;
_size = size;
}
SeqStack(const SeqStack& src) {
/*
为当前对象开辟内存空间,并将原对象中的内容拷贝到当前对象中
*/
_pstack = new int[src._size];
for (int i = 0; i <= src._top; i++) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
~SeqStack() {
cout << this << " ~SeqStack() " << endl;
delete[] _pstack;
_pstack = nullptr;
}
private:
int *_pstack;
int _top;
int _size;
};
int main()
{
SeqStack s;
SeqStack s1(10);
SeqStack s2 = s1;
return 0;
}
上述代码解决了浅拷贝造成的问题,但是,对于赋值操作,依然存在问题,如:
int main()
{
SeqStack s;
SeqStack s1(10);
SeqStack s2 = s1;
/*
此处默认会做浅拷贝,造成s2的指针指向s1的内存空间,那么此时s2原来
的内存空间便没有指针管理,造成问题
*/
s2 = s1;
return 0;
}
于是我们还需实现其赋值运算符重载:
void operator= (const SeqStack& src) {
/*
自己对自己赋值则直接返回
*/
if (this == src) {
return;
}
delete[] _pstack;
_pstack = new int[src._size];
for (int i = 0; i <= src._top; i++) {
_pstack[i] = src._pstack[i];
}
_top = src._top;
_size = src._size;
}
2、剑指offer中的实现
待补充
二、掌握构造函数的初始化列表
用于在构造函数的形参列表之后进行类对象的参数初始化,主要有两点要注意:
1、当一个类如B中还有一个成员变量是类A,这时如果A没有提供默认构造函数,就需要初始化列表进行直接构造。如下面的代码会报错,因为A没有默认构造函数:
#include <iostream>
using namespace std;
class A {
public:
A(int x1, int y1) {
x = x1;
y = y1;
}
private:
int x, y;
};
class B {
public:
B(int x1, int y1, int z1) {
/*
A没有默认构造函数,不能这么赋值,B对象在构造A对象时会报错
*/
a = A(x1, y1);
z = z1;
}
private:
A a;
int z;
};
int main()
{
return 0;
}
使用初始化列表即可:
class B {
public:
B(int z1, int x1, int y1) : a(x1, y1), z(z1) {}
private:
A a;
int z;
};
2、成员变量的初始化和他们的定义顺序有关,和其在构造函数初始化列表中出现的顺序无关!
三、掌握类的各种成员方法以及区别
1、普通成员方法(常对象无法调用)
编译器会添加一个this形参变量
a. 属于类的作用域
b. 调用该方法时,需要依赖一个对象
c. 可以任意访问对象的私有成员
2、static静态成员方法
不会生成this形参
a. 属于类的作用域
b. 用类名作用域来调用方法
c. 只可以访问static静态成员变量
3、const常成员方法
1、属于类的作用域
2、调用依赖一个对象,普通对象和常对象都可以
3、只能读成员变量,不能写