Contents
  1. I. Constructors and Destructors
  2. II. Constructor Categories and Invocation
  3. III. When the Copy Constructor Is Called
  4. IV. Constructor Invocation Rules
  5. V. Deep Copy and Shallow Copy
  6. VI. Initializer Lists
  7. VII. Class Objects as Class Members
  8. VIII. Static Members

Every object needs initial setup and cleanup before it is destroyed.

I. Constructors and Destructors

Initialization and cleanup of objects are two very important safety concerns.

  • Using an object or variable without an initial state has unknown consequences
  • Failing to clean up an object or variable in time after use can also cause safety issues

C++ uses constructors and destructors to address these two issues. The compiler automatically calls these two functions to perform object initialization and cleanup.

The compiler requires us to initialize and clean up objects, so if we do not provide constructors and destructors, the compiler will provide them—and the compiler-provided constructor and destructor are empty implementations.

  • Constructor: assigns values to an object’s member attributes when the object is created
  • Destructor: performs cleanup before the object is destroyed

Constructor syntax: 类名(){}

  1. A constructor has no return value and does not use void
  2. The function name is the same as the class name
  3. A constructor may take parameters, so overloading is possible
  4. The program automatically calls the constructor when an object is created; you do not call it manually, and it is called only once.

Destructor syntax: ~类名(){}

  1. A destructor has no return value and does not use void
  2. The function name is the same as the class name, with ~ prepended
  3. A destructor cannot take parameters, so overloading is not possible
  4. The program automatically calls the destructor before an object is destroyed; you do not call it manually, and it is called only once
#include <iostream>
using namespace std;

class Person{
public:
	//构造函数,进行初始化操作
	//构造函数没有返回值 不用写void
	//函数名与类名相同
	//构造函数可以有参数,可以发生重载
	//创建对象的时候,构造函数会自动调用,而且只调用一次
	Person(){
		cout << "Person的构造函数" << endl;
	}

	//析构函数,执行清理操作
	//没有返回值,不写void
	//函数名和类名相同,前面加~
	//不可以有参数,不可以发生重载
	//创建对象的时候,构造函数会自动调用,而且只调用一次
	~Person(){
		cout << "Person的析构函数" << endl;
	}

};

void test(){
	Person p;
}

int main(){
	
	test();
	Person p;

	system("Pause");
	return 0;
}

II. Constructor Categories and Invocation

Classification:

  • By parameters: parameterized constructors and default constructors (no-parameter constructors)
  • By type: ordinary constructors and copy constructors
//普通构造、无参构造(默认构造)
Person(){
	cout << "无参构造函数的调用" << endl;
}
//普通构造、有参构造
Person(int a){
	age = a;
	cout << "有参构造函数的调用" << endl;
}
//拷贝构造
Person(const Person &p){
	//将传入的类中所有的属性传到此对象上
	age = p.age;
	cout << "拷贝构造函数的调用" << endl;
}

Invocation methods:

  • Parenthesis form
Person p1;		//调用无参构造函数
Person p2(10);	//调用有参构造函数
Person p3(p2);	//调用拷贝构造函数

Note: When calling the default constructor, do not add ()

Because Person p1(); , the compiler will treat it as a function declaration

  • Explicit form
Person p1;				//调用无参构造函数
Person p2 = Person(10);	//调用有参构造函数
Person p3 = Person(p2);	//调用拷贝构造函数

Person(10) is an anonymous object.

Characteristic: after the current line finishes executing, the system immediately reclaims the anonymous object.

Do not use a copy constructor to initialize an anonymous object

Person(p3); //报错重定义

The compiler treats Person (p3) as equivalent to Person p3;

  • Implicit conversion form
Person p1;		//调用无参构造函数
Person p2 = 10;	//调用有参构造函数
Person p3 = p2;	//调用拷贝构造函数

Person p2 = 10; is equivalent to Person p2 = Person(10);

III. When the Copy Constructor Is Called

In C++, the copy constructor is typically called in the following situations:

  • Using an already-created object to initialize a new object
  • Passing a function argument by value
例:
#include<iostream>
using namespace std;

class Person{
public:
	Person(){
		cout << "默认构造函数调用" << endl;
	}

	Person(int age){
		m_Age = age;
		cout << "有参构造函数调用" << endl;
	}
	
	Person(const Person &p){
		m_Age = p.m_Age;
		cout << "拷贝构造函数调用" << endl;
	}

	~Person(){
		cout << "析构函数调用" << endl;
	}

	int m_Age;

};

//用已经创建完毕的对象初始化一个新对象
void test01(){
	Person p1(20);
	Person p2(p1);	//调用拷贝构造函数

	cout << "p2的年龄为:" << p2.m_Age << endl;
}

//值传递的方式给函数参数传值
void doWork(Person p){}//值传递相当于 Person p = p 的隐式写法
void test02(){
	Person p;
	doWork(p);	//调用拷贝构造函数
}

int main(){

	//test01();
	//test02();
	test03;
	system("pause");
	return 0;
}

IV. Constructor Invocation Rules

By default, a C++ class has at least three functions:

  • Default constructor (no parameters, empty function body)
  • Default destructor (no parameters, empty function body)
  • Default copy constructor, which copies attribute values

Constructor invocation rules:

  • If the user defines a parameterized constructor, C++ no longer provides a default no-parameter constructor, but it still provides a default copy constructor
  • If the user defines a copy constructor, C++ no longer provides other constructors

V. Deep Copy and Shallow Copy

Shallow copy: a simple assignment copy operation

Deep copy: allocate new space on the heap and perform the copy operation

例1:浅拷贝的问题,此程序的问题是,m_Height指向的区域,经过两次析构函数的调用,被重复释放了。
#include<iostream>
using namespace std;

class Person{
public:
	Person(){
		cout << "默认构造函数调用" << endl;
	}

	Person(int age, int height){
		m_Age = age;
		m_Height = new int(height);
		cout << "有参构造函数调用" << endl;
	}

	~Person(){
		//析构函数,将堆区开辟的数据做释放操作
		if (m_Height != NULL){
			delete m_Height;
			m_Height = NULL;
		}
		cout << "析构函数调用" << endl;
	}

	int m_Age;
	int *m_Height;	//身高数据开辟到堆区

};


void test01(){
	Person p1(21, 160);

	cout << "P1的年龄为:" << p1.m_Age << "\t身高为:" << *p1.m_Height << endl;

	Person p2(p1);

	cout << "P1的年龄为:" << p1.m_Age << "\t身高为:" << *p2.m_Height << endl;
}

int main(){

	test01();

	system("pause");
	return 0;
}
解决方法:自己实现拷贝构造函数,解决浅拷贝带来的问题
	Person(const Person &p){
		m_Age = p.m_Age;
		//m_Height = p.m_Height;//编译器默认实现的是这行代码
		m_Height = new int(*p.m_Height);
		cout << "拷贝构造函数调用" << endl;
	}

VI. Initializer Lists

Purpose: initialize class attributes

Syntax: 构造函数(): 属性1(值1),属性2(值2)...{}

Advantages: when a class member is a constant, it can only be initialized, not assigned; when a class member is a reference, it can only be initialized, not assigned; improves efficiency.

#include <iostream>
using namespace std;

class Person{
public:
	//传统的初始化
	//Person(int a, int b, int c){
	//	m_A = a;
	//	m_B = b;
	//	m_C = c;
	//}

	//初始化列表进行初始化
	Person(int a, int b, int c):m_A(a),m_B(b),m_C(c){}
	int m_A;
	int m_B;
	int m_C;
};

void test01(){
	Person p(10,20,30);
	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_B = " << p.m_B << endl;
}

int main(){

	test01();

	system("pause");
	return 0;
}

VII. Class Objects as Class Members

In C++, a class member can be an object of another class. Such a member is generally called an object member.

class A{};
class B{
    A a;
};

When another class’s object serves as a member of this class:

When constructing: first construct the class object, then construct itself.

When destructing: the order is the reverse of construction.

#include <iostream>
#include <string>
using namespace std;

//手机类
class Phone{
public:
	Phone(string brand){
		m_Brand = brand;
		cout << "Phone的构造函数调用" << endl;
	}

	~Phone(){
		cout << "Phone的析构函数调用" << endl;
	}

	string m_Brand;//品牌
};

//人类
class Person{
public:
	//这里的m_Phone(brand)相当于使用括号法Phone m_Phone(brand)创建对象
	Person(string name, string brand):m_Name(name),m_Phone(brand){
		cout << "Person的构造函数调用" << endl;
	}

	~Person(){
		cout << "Person的析构函数调用" << endl;
	}

	string m_Name;
	Phone m_Phone;
};

void test01(){
	Person p("Huffie","Huawei");

	cout << p.m_Name << " with " << p.m_Phone.m_Brand << endl;
}

int main(){

	test01();

	system("pause");
	return 0;
}

VIII. Static Members

Static members are created by placing static before member variables and member functions

  • Static member variables:
    • All objects share the same data
    • Memory is allocated during compilation (in the global area)
    • Declared inside the class, initialized outside the class

Static member variables can be accessed in two ways (if they are private, they cannot be accessed outside the class):

  1. Access through an object

    Person p;
    cout << p.m_A << endl;
  2. Access through the class name

    cout << Person::m_A << endl;
#include<iostream>
using namespace std;

class Person{
public:
	//类内声明
	static int m_A;
private:
	static int m_B;
};
//类外初始化
int Person::m_A = 100;
int Person::m_B = 200;

void test01(){
	Person p1;
	cout << p1.m_A << endl;	//输出100
	
	Person p2;
	p2.m_A = 200;
	cout << p1.m_A << endl;	//输出200,说明数据共享
}

void test02(){
	//静态成员变量不属于某个对象,所有对象都共享同一份数据,因此静态成员变量有两种访问方式
	//通过对象访问
	Person p;
	cout << p.m_A << endl;
	//通过类名访问
	cout << Person::m_A << endl;
	//cout << Person::m_B << endl;错误,私有权限类外访问不到
}

int main(){

	test01();
	test02();

	system("pause");
	return 0;
}
  • Static member functions
    • All objects share the same function
    • Static member functions can access only static member variables

Static member functions can be accessed in two ways (if they are private, they likewise cannot be accessed outside the class):

  1. Access through an object

    Person p;
    p.func();
  2. Access through the class name

    Person::func();
#include<iostream>
using namespace std;

class Person{
public:
	//静态成员函数
	static void func(){
		m_A = 100;
		//m_B = 200;静态成员函数不可以访问非静态成员变量
		cout << "static void func的调用" << endl;
	}

	//静态成员变量
	static int m_A;
	//非静态成员变量
	int m_B;

private:
	//静态成员函数也是有访问权限的
	static void func2(){
		cout << "static void func2的调用" << endl;
	}

};

int Person::m_A = 0;

void test01(){
	//通过对象访问
	Person p;
	p.func();
	//通过类名访问
	Person::func();

	//Person::func2();类外无法访问私有的静态成员函数
}

int main(){

	test01();

	system("pause");
	return 0;
}

Reference: Heima Programmer’s Craftsmanship | C++ Tutorial, Starting at 0 and Reaching 1: Getting Started with Programming, Learning Programming Is No Longer Hard Link: https://www.bilibili.com/video/BV1et411b73Z