主要讲了 c++ 中对象的一些特性:构造函数、析构函数、拷贝、初始化以及指针等
1. 对象的初始化和清理
对象的初始化和清理是两个非常重要的问题。
一个对象或者变量没有初始状态,则对其使用后果是未知;
同样使用完一个对象或者变量后,没有及时清理,也会造成一定的安全问题。
c++使用 构造函数 和 析构函数 来完成以上两个步骤,上面两个函数会被编译器自动调用,用来完成对象的初始化以及清理。这是编译器强制要求我们执行的步骤,如果开发人员不实现这两个函数的话,编译器会自动调用默认的空实现。
析构函数
主要作用于对象销毁之前,执行一些清理工作,系统自动调用
语法:~类名(){}
1.1 构造函数
主要作用于创建对象时为对象的成员变量赋初始值,构造函数由编译器自动调用,无需手动调用
语法:类名(){}
按参数分:有参构造和无参构造
按类型分:普通构造和拷贝构造
三种调用方式:括号法、显示法、隐式转换法
c++
#include <iostream>
using namespace std;
class Person {
int age;
string name;
string sex;
public:
// 有参构造
Person(int age, string name, string sex) {
this->age = age;
this->name = name;
this->sex = sex;
}
// 无参构造
Person() {
cout << "调用无参构造函数" << endl;
}
// 拷贝构造函数:按传进来的拷贝一份
// 1. const防止修改 2. 使用引用
Person(const Person &p) {
age = p.age;
name = p.name;
sex = p.sex;
}
};
int main() {
// 调用方式一:括号法
Person p1; // 调用无参构造,默认构造不用加括号(加了会被认为函数)
Person p2(18, "张三", "男");// 调用有参构造
Person p3(p2); // 拷贝构造
// 调用方式二:显示法
Person p4;
Person p5 = Person(18, "张三", "男");
Person p6 = Person(p5);
Person(18, "张三", "男"); // 匿名对象
// 调用方式三:隐式转换法
Person p7 = p6;
return 0;
}
1.2 拷贝构造调用时机
c++
#include <iostream>
using namespace std;
class Person {
int age;
string name;
string sex;
public:
// 有参构造
Person(int age, string name, string sex) {
this->age = age;
this->name = name;
this->sex = sex;
}
// 无参构造
Person() {
cout << "调用无参构造函数" << endl;
}
// 拷贝构造函数:按传进来的拷贝一份
// 1. const防止修改 2. 使用引用
Person(const Person &p) {
cout << "调用拷贝构造函数" << endl;
age = p.age;
name = p.name;
sex = p.sex;
}
};
// 拷贝构造场景一:使用一个对象初始化另一个对象
void test01() {
Person p1(18, "张三", "男");
Person p2(p1);
}
// 拷贝构造场景二:函数的参数是类对象
void test02(Person p) {
}
// 拷贝构造场景三:返回值是类对象
Person test03() {
Person p(18, "张三", "男");
cout << &p << endl;
return p;
}
int main() {
test01();
Person p1(18, "张三", "男");
test02(p1);
Person p2 = test03();
// test03里面的 p 地址和 p2地址不同,已经拷贝构造了
// 我擦但是是发现是相同的,查了下是现代 c++ 的优化,如果检测到了会返回 p
// 直接在 main 函数的栈里构造了 p,也就是 test03结束后 p 其实根本没有回收,继续在 main 函数中使用
cout << &p2 << endl;
return 0;
}
1.3 构造函数调用规则
默认情况下,c++编译器至少给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数的调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
1.4 深拷贝和浅拷贝
浅拷贝:简单的赋值拷贝操作
沈拷贝:在堆区重新申请空间,进行赋值操作
c++
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
int *m_height;
// 有参构造
Person(int age, int height) {
cout << "调用有参构造函数" << endl;
m_age = age;
m_height = new int(height);
}
// 无参构造
Person() {
cout << "调用无参构造函数" << endl;
}
// 拷贝构造
Person(const Person &p) {
cout << "调用拷贝构造函数" << endl;
m_age = p.m_age;
m_height = new int(*p.m_height);
}
// 析构函数
~Person() {
// 应该释放掉在堆区开辟的数据
if (m_height != nullptr) {
delete m_height;
m_height = nullptr;
}
cout << "调用析构函数" << endl;
}
};
int main() {
Person p1(10, 160);
cout << "p1的年龄为:" << p1.m_age << "身高为:" << *p1.m_height << endl;
// 报错:由于 p2由 p1拷贝构造,所以两个对象的 m_height 指针指向同一个地址,析构的时候会释放两次
Person p2(p1);
cout << "p2的年龄为:" << p2.m_age << "身高为:" << *p2.m_height << endl;
// 利用深拷贝来解决:重写拷贝构造
Person p3 = p1;
cout << "p3的年龄为:" << p3.m_age << "身高为:" << *p3.m_height << endl;
return 0;
}
初始化列表
c++提供了初始化列表语法,用来初始化属性
语法:
c
#include <iostream>
using namespace std;
class Person {
public:
int m_age;
int m_height;
string m_name;
// 正常有参构造
Person(int age, int height, string name) {
m_age = age;
m_height = height;
m_name = name;
}
// 初始化列表
Person():m_age(0), m_height(0), m_name("") {
}
// 优势在于初始化的直接赋值,不用再赋值
Person(int age, int height): m_age(age), m_height(height) {
m_age = age;
}
};
int main() {
Person p1(10, 160, "张三");
cout << "p1的年龄为:" << p1.m_age << ",身高为:" << p1.m_height << "姓名为:" << p1.m_name << endl;
Person p2;
cout << "p2的年龄为:" << p2.m_age << ",身高为:" << p2.m_height << "姓名为:" << p2.m_name << endl;
Person p3(10, 160);
cout << "p3的年龄为:" << p3.m_age << ",身高为:" << p3.m_height << endl;
return 0;
}