C++
2. 面向对象
2.1 构造与析构
2.1.1 构造函数
含义:使用一个类的对象时,为其分配完空间后,一般需要对创建的对象的属性进行初始化的操作,这个操作就可以在构造函数中完成。是对象生命周期的起点
函数定义:无返回值,无返回类型,与类名同名,可以有不同的重载。
// 定义
class Person{
public:
Person(){
cout << "无参构造" << endl;
}
Person(int age){
cout << "有1参构造" << endl;
}
Person(int age, int score){
cout << "有2参构造" << endl;
}
};
// 调用
int main(){
// 创建对象的时候调用构造函数
// 1.显式调用
Person xiaoming1 = Person(); // 无参构造
Person xiaoming2 = Person(18, 100); // 有2参构造
Person xiaoming3; //Person xiaoming3()不能加括号,否则会默认在定义函数
Person xiaoming4(18, 100);
// 2.隐式调用
Person xiaoming5 = {}; // 无参构造
Person xiaoming6 = 18; // 有1参构造,如果大括号内只有一个值{18},可以省略大括号
Person xiaoming7 = {18, 100}; // 有2参构造
// 使用new在堆上创建对象,如果调用无参构造函数,小括号可以省略不写
// 使用new的方式,在堆上创建对象,这个过程叫实例化
Person* xiaogming8 = new Person;
return 0;
}
explicit 关键字:修饰构造函数,写在构造函数之前,表示不能通过隐式调用构造函数。
explict Person(int age){
cout << "有1参构造" << endl;
}
注意:
- 如果在一个类中没有写任何的构造函数,系统会为其添加一个public权限的无参构造函数,可以让我们创建对象。
- 如果我们给一个类写了构造函数,系统不会再为我们提供任何默认的构造函数。
如果我们为一个类写了有参的构造函数,一般会同时写无参的构造函数。
// 构造使用
class Person{
public:
string name;
int age;
string gender;
int score;
// Person(){
// name = "";
// age = 0;
// dender = "";
// score = 0;
// }
Person(): name(""), age(0), gender(""), score(0){ } //初始化列表赋值
// Person(string n, int a, string g, int s){ n和name不能相同,a和age不能相同……,同名无法区分
// name = n;
// age = a;
// gender = g;
// score = s;
// }
person(string n = "", int a = 0, string g = "", int s = 0): name(n), age(a), gender(g), score(s){ }
person(string name, int age, string gender): name(name), age(age), gender(gender){ } //初始化列表赋值则可以同名,会自动区分
};
拷贝构造:根据一个对象,拷贝出另外一个对象,即开辟一块新内存给新对象,将一个对象的属性值拷贝到新对象中。
系统自动提供拷贝函数。
新对象与旧对象的内存地址不同,但是属性值相同。
class Person{
public:
string name;
int age;
Person(){}
Person(string name, int age) : name(name), age(age) { }
//自己定义拷贝函数,const常量,Person&p表示引用,引用传递,p是引用,p指向xiaoming,p和xiaoming指向同一个对象
Person(const Person& p){
cout << "调用了拷贝函数" << endl;
name = p.name;
age = p.age+1;
}
};
int main(){
Person xiaoming = Person("xiaoming", 18);
Person xiaohong = xiaoming;
cout << "小明的年龄" << xiaoming.age << endl;
cout << "小红的年龄" << xiaohong.age << endl;
return 0;
};
2.1.1 析构函数
- 析构函数是对象生命周期的终点,在对象空间被销毁之前调用。
- 在析构函数中,一般进行资源的释放,堆内存的销毁。
- 不能重载,一个类只有一个析构函数。
- 使用~开头,不能有参数。
class Person{
int a;
public:
Person() : a(0) {} // 初始化成员变量a为0
~Person(){
cout << "析构函数调用" << endl;
}
};
int main(){
Person p;
return 0;
}
2.2 浅拷贝与深拷贝
- 浅拷贝:在拷贝构造函数中,直接完成属性的值拷贝。即拷贝对象和被拷贝对象共享同一个内存空间,修改其中一个对象,另一个对象也会改变。
- 深拷贝:在拷贝构造函数中,创建出来新的空间,属性中的指针指向的是新的空间。
class Cat{};
class Person{
public:
int age;
Cat* pet;
Person() {
age = 0;
pet = new Cat; //其实存放的是堆内存中Cat的地址
}
//拷贝构造函数
Person(const Person& p){
//这里默认是浅拷贝
age = p.age;
pet = p.pet; //其实传递的是堆内存中Cat的地址,所以拷贝后pet指向的是同一个地址
}
~Person(){
if (pet != nullptr){
delete pet;
pet = nullptr;
}
}
};
int main(){
Person xiaoming;
Person xiaohong = xiaoming;
//栈空间里销毁内存是先进后出,所有是先释放xiaohong,再释放xiaoming
//先释放xiaohong的时候,会调用Cat的析构函数,pet被销毁(即存放Cat的堆内存被销毁)
//当释放xiaoming的时候,pet已经被销毁(即存放Cat的堆内存已经被销毁),程序就会出现异常
return 0;
}
//拷贝构造函数
Person(const Person& p){
age = p.age;
//深拷贝
pet = new Cat; //重新在堆内存中为申请一块空间Cat,将地址赋值给pet
pet->age = p.pet->age;
pet->name = p.pet->name;
}
2.3 this指针
- this指针是一个隐含的指针,它指向当前对象的地址。
- 当前对象:谁调用这个函数,this就指向谁。
class Person{
public:
int age;
int getAge(){
return this->age;
//绝大多数情况下this->age和age是一样的,且this->可以省略
//但是如果有局部变量与成员变量同名,为避免混淆,this->不可以省略,否则访问的是局部变量
}
};
int main(){
Person xiaoming{10};
Pe