Object initialization and cleanup
Object initialization and cleanup
- In our daily lives, most electronic products we buy come with factory settings, and at some point when we no longer use them, we'll wipe personal data to ensure security.
- Object-oriented programming in C++ originates from real life, and each object also has initial settings as well as cleanup settings before the object is destroyed.
Constructor and Destructor
The initialization and cleanup of objects are also two very important security concerns.
An object or variable has no initial state, and using it will have unknown consequences.
That's true - similarly, if an object or variable isn't cleaned up after use, it can also lead to certain security issues.
C++ utilizes constructors and destructors to solve the aforementioned problem. These two functions are automatically called by the compiler to handle object initialization and cleanup.
Object initialization and cleanup are things the compiler forces us to do, so if we don't provide constructors and destructors, the compiler will provide them.
The constructors and destructors provided by the compiler are empty implementations.
- Constructor: The main purpose is to assign values to an object's member properties when it is created. The constructor is automatically called by the compiler, so there is no need to call it manually.
- Destructor: Primarily responsible for automatically performing cleanup operations before an object is destroyed.
Constructor syntax: 类名(){}
- Constructors have no return type and don't write void.
- Function name is the same as the class name
- Constructors can have parameters, so overloading is possible.
- When a program calls an object, the constructor is automatically invoked without needing to be called manually, and it will only be called once.
Destructor syntax: ~类名(){}
- Destructor, has no return value and also doesn't use void.
- The function name is the same as the class name, with the symbol ~ added before the name.
- Destructors cannot have parameters, so they cannot be overloaded.
- The program will automatically call the destructor before the object is destroyed, without requiring manual invocation, and it will be called only once.
class Person
{
public:
//构造函数
Person()
{
cout << "Person的构造函数调用" << endl;
}
//析构函数
~Person()
{
cout << "Person的析构函数调用" << endl;
}
};
void test01()
{
Person p;
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Classification and Invocation of Constructors
Two classification methods:
Classified by parameters: parameterized constructors and non-parameterized constructors
Divided by type: regular construction and copy construction
Three calling methods:
Parentheses method
Display Method
Implicit conversion method
Example:
//1、构造函数分类
// 按照参数分类分为 有参和无参构造 无参又称为默认构造函数
// 按照类型分类分为 普通构造和拷贝构造
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
//2、构造函数的调用
//调用无参构造函数
void test01() {
Person p; //调用无参构造函数
}
//调用有参的构造函数
void test02() {
//2.1 括号法,常用
Person p1(10);
//注意1:调用无参构造函数不能加括号,如果加了编译器认为这是一个函数声明
//Person p2();
//2.2 显式法
Person p2 = Person(10);
Person p3 = Person(p2);
//Person(10)单独写就是匿名对象 当前行结束之后,马上析构
//2.3 隐式转换法
Person p4 = 10; // Person p4 = Person(10);
Person p5 = p4; // Person p5 = Person(p4);
//注意2:不能利用 拷贝构造函数 初始化匿名对象 编译器认为是对象声明
//Person p5(p4);
}
int main() {
test01();
//test02();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Copy constructor invocation timing
In C++, the copy constructor is typically called in three situations:
- When initializing a new object as a copy of an existing object of the same type.
- When an object is passed by value as a function argument.
- When an object is returned by value from a function.
- Initialize a new object using an already existing object.
- Pass values to function parameters by value.
- Returning a Local Object by Value
When a function returns a local object by value, it creates a copy of that object to pass to the caller. This ensures the object remains valid after the function's scope ends.
Key Points:
- Safety: The caller receives an independent copy, eliminating risks like dangling references or pointers to memory that is no longer valid.
- Performance: It involves a copy (or move) operation. Modern compilers often optimize this through techniques like Copy Elision (e.g., RVO - Return Value Optimization, NRVO - Named Return Value Optimization) or move semantics, potentially reducing the cost.
- Use Case: This is the default and recommended way to return objects when ownership should be transferred to the caller and the object is not expensive to copy.
Example:
class Person {
public:
Person() {
cout << "无参构造函数!" << endl;
mAge = 0;
}
Person(int age) {
cout << "有参构造函数!" << endl;
mAge = age;
}
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
mAge = p.mAge;
}
//析构函数在释放内存之前调用
~Person() {
cout << "析构函数!" << endl;
}
public:
int mAge;
};
//1. 使用一个已经创建完毕的对象来初始化一个新对象
void test01() {
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
//2. 值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
}
//3. 以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
int main() {
//test01();
//test02();
test03();
return 0;
}
Running/Observing the results: After running, the example will print variable values or addresses; address values depend on the runtime environment, so focus on observing the relative positioning and pointer changes of similar objects.
构造函数调用规则
在面向对象编程中,构造函数的调用遵循以下基本规则:
- 自动调用:构造函数在对象创建时由系统自动调用,无需显式执行。
- 默认构造函数:若未定义任何构造函数,编译器会提供无参的默认构造函数;若已定义其他构造函数,则默认构造函数不再自动提供。
- 显式调用:
- 可通过
new关键字或对象初始化语法触发。 - 在继承中,子类构造函数默认调用父类无参构造函数;若需调用父类带参构造函数,须使用
super(参数)显式指定。
- 可通过
- 重载与匹配:构造函数支持重载,调用时根据参数类型和数量匹配对应的构造函数。
- 初始化顺序:执行构造函数前,系统会先初始化成员变量(按声明顺序),并调用父类构造函数。
注:具体规则可能因编程语言(如 Java、C++)而略有差异,需结合语言特性使用。
By default, the C++ compiler adds at least three functions to a class.
- Default constructor (no parameters, empty function body)
- Default destructor (parameterless with an empty function body)
- The default copy constructor performs value copying of properties
Constructor call rules are as follows:
- If the user defines a parameterized constructor, C++ no longer provides a default no-argument constructor, but it will provide a default copy constructor.
- If a user defines a copy constructor, C++ will not provide any other constructors.
Example:
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int a) {
age = a;
cout << "有参构造函数!" << endl;
}
//拷贝构造函数
Person(const Person& p) {
age = p.age;
cout << "拷贝构造函数!" << endl;
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
}
public:
int age;
};
void test01()
{
Person p1(18);
//如果不写拷贝构造,编译器会自动添加拷贝构造,并且做浅拷贝操作
Person p2(p1);
cout << "p2的年龄为: " << p2.age << endl;
}
void test02()
{
//如果用户提供有参构造,编译器不会提供默认构造,会提供拷贝构造
Person p1; //此时如果用户自己没有提供默认构造,会出错
Person p2(10); //用户提供的有参
Person p3(p2); //此时如果用户没有提供拷贝构造,编译器会提供
//如果用户提供拷贝构造,编译器不会提供其他构造函数
Person p4; //此时如果用户自己没有提供默认构造,会出错
Person p5(10); //此时如果用户自己没有提供有参,会出错
Person p6(p5); //用户自己提供拷贝构造
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Deep copy and shallow copy
Deep and shallow copying is a classic interview question and a common pitfall.
Shallow copy: Simple assignment copy operation
Deep copy: Allocating new space on the heap and performing the copy
Example:
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Summary: If a property is allocated on the heap, you must provide your own copy constructor to prevent issues caused by shallow copying.
initialization list
Function:
C++ provides initializer list syntax to initialize attributes.
Syntax: 构造函数():属性1(值1),属性2(值2)... {}
Example:
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) {}
void PrintPerson() {
cout << "mA:" << m_A << endl;
cout << "mB:" << m_B << endl;
cout << "mC:" << m_C << endl;
}
private:
int m_A;
int m_B;
int m_C;
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Class objects as class members
A member of a C++ class can be an object of another class, and such a member is referred to as an object member.
For example:
class A {}
class B
{
A a;
}
Run/Observation Results: This section is syntax definition-focused, typically requiring compilation together with the calling code. Pay attention to the definition method and usage location.
Class B contains an object of type A as a member.
So when creating object B, what is the order of construction and destruction between A and B?
Example:
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化列表可以告诉编译器调用哪一个构造函数
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
void playGame()
{
cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 牌手机! " << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "苹果X");
p.playGame();
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
static member
Static members are variables and functions that have the keyword "static" added before them, which are referred to as static members.
Static members are classified into:
- static member variable
- All objects share the same data.
- Allocate memory at compile time.
- Declared in class, initialized outside
- static member function
- All objects share the same function.
- Static member functions can only access static member variables.
Example 1: Static Member Variable
class Person
{
public:
static int m_A; //静态成员变量
//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据
private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.
Example 2: static member function
class Person
{
public:
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}
static int m_A; //静态成员变量
int m_B; //
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.func();
//2、通过类名
Person::func();
//Person::func2(); //私有权限访问不到
}
int main() {
test01();
return 0;
}
Running/Observation Results: After running, the corresponding content will be printed according to the output statements. The variable values can be inferred based on the order of initialization, assignment, and function calls.