cpp面向对象(类的成员、构造函数、析构函数)

发布时间 2023-12-30 22:12:00作者: DGJG

class/struct

在cpp面向对象编程中,一般使用class来作为OOP的载体,而将struct仅作为类型的一个集合。虽然这两者在功能上基本没有差异,除了class的默认访问控制是private,而struct的是public的。

类的成员

类作用域

类本身是一个作用域,我们可以在类内声明一个函数/变量,并且用类名::函数名的方式在类外定义它。

特别的,对于类的静态变量而言,我们必须这么做。

静态成员

对于类来说,静态成员并不依赖于类的实例而存在,也就是说,它的静态成员会存在于整个程序的执行过程中,直到程序结束。因此,类的所有实例可以通过类名::成员名的方式来共享类的所有静态成员,且静态函数只能调用静态成员。

const成员函数

如果一个类指针是const的,那么它不能修改这个类中的任何成员变量。

如果成员函数在参数列表之后紧跟着一个const,表示这是一个常量成员函数,默认情况下,this是一个指向非常量类型的常量指针(可以看作是const在后的指针),这里const的作用是修改隐式this指针的类型,使其变成一个指向常量的指针。

构造函数

如果我们使用new运算符新建一个对象,那么它首先会调用malloc分配内存,然后调用构造函数创建这个对象。

如果类有继承层次,那么首先执行基类的构造函数,再执行派生类的。cpp没有super关键字,也就是说,我们无需在派生类的构造函数中显式的调用基类构造函数,这个调用关系由编译器来管理。

构造函数不能是const:显然类内成员需要初始化

构造函数不能是virtual:

  1. 对象创建时,虚表还没有构造完成
  2. 构造函数有其调用次序,virtual显然会破坏这种原则

每个类都有一个默认的构造函数,自定义构造函数会覆盖默认的构造函数,如果我们需要保留默认构造函数,则可以将其声明为default:A() = default

构造函数必须是public的,但不能通过类指针进行调用。

初始值列表构造函数:很方便的一种赋初值形式,甚至可以在赋完初值后再执行一些额外的操作

class A {
  int x;
  double y;
  char z;
public:
  A(int a, double b, char c) : x(a), y(b), z(c) {}
};

委托构造

允许一个构造函数调用另一个构造函数,例如:

class A {
public:
  A() : A(0) {}
  A(int x) { std::cout << x << std::endl; }
}

这样,第一个构造函数就调用了第二个构造函数。

拷贝构造

拷贝构造函数默认只一一复制非静态成员。

原型:A(const A& other),有以下四种场景会触发:

  1. 用一个对象初始化另一个对象时,如A x; A y = x
  2. 通过值进行传参
  3. 通过值返回对象
  4. 用花括号列表初始化类成员

拷贝赋值运算符

实际上是重载赋值号,和拷贝构造的区别是,被赋值的对象是已经创建好的。原型:A& operator=(const A& other)

移动构造

原型:A(const A&& other)

移动赋值运算符

原型:A& operator=(const A&& other)

析构函数

与构造函数正好相反,先派生类后基类。

基类的析构函数需要声明为虚函数,考虑以下场景:

Base* ptr = new Derived();
delete ptr;

执行delete ptr;时,如果基类的析构函数非虚,采用静态绑定方式,会直接调用Base类的析构函数。

如果基类的析构函数为虚,那么编译器看到虚函数会采用动态绑定方式,发现指针指向的类型实际上是Derived,由于Derived类的析构函数重写了Base类的析构函数(为什么不同名能重写,问就是特性),而且Derived的析构函数调用完成后会默认调用Base的析构函数,这样就实现了正常的资源释放。

析构函数一般不显式调用,一种特例是使用placement new(一种可以在自己申请的内存上创建对象的方法)创建对象时,由于对象不一定在堆空间上,此时不能调用delete释放资源,而需要手动调用析构函数。