生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己信息数据保证安全
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置
对象的初始化和清理是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知的
使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决了上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:
1. 构造函数,没有返回值也不写void
2. 函数名称与类名相同
3. 构造函数可以有参数,因此可以发生重载
4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法:
1. 析构函数,没有返回值也不写void
2. 函数名称与类名相同,在名称前加上符号~
3. 析构函数不可以有参数,因此不可以发生重载
4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
#include
using namespace std;
class Person
{
public:
// 构造函数
Person()
{
cout << "构造函数已调用......" << endl;
}
// 析构函数
~Person()
{
cout << "析构函数已调用......" << endl;
}
};
void test()
{
Person p;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
括号法
显示法
隐式转换法
#include
using namespace std;
class Person
{
public:
// 无参构造函数(普通构造函数)
Person()
{
cout << "无参构造函数已调用......" << endl;
}
// 有参构造函数(普通构造函数)
Person(int p_age)
{
age = p_age;
cout << "有参构造函数已调用......" << endl;
cout << "年龄:" << age << endl;
}
// 拷贝构造函数
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数已调用......" << endl;
cout << "拷贝的年龄为:" << age << endl;
}
// 析构函数
~Person()
{
cout << "析构函数已调用......" << endl;
}
int age;
};
void test1()
{
// 1. 括号法
// 调用无参构造函数
Person p1;
// 调用有参构造函数
Person p2(23);
// 调用拷贝构造函数
Person p3(p2);
// 注意事项
// 调用默认构造函数的时候,不能加(),因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
// Person p1();
}
void test2()
{
// 2. 显示法
// 调用无参构造函数
Person p1;
// 调用有参构造函数
Person p2 = Person(23);
// 调用拷贝构造函数
Person p3 = Person(p2);
// Person(23); 匿名函数 特点:当前行执行结束后,系统会立即回收掉匿名函数
// 注意事项
// 不要利用拷贝构造函数初始化匿名对象,编译器会认为 Person(p6) ===== Person p6;对象声明
// Person(p6)
}
void test3()
{
// 3. 隐式转换法
// 调用无参构造函数
Person p1;
// 调用有参构造函数
Person p2 = 23;
// 调用拷贝构造函数
Person p3 = p1;
}
int main(int argc, char* argv[])
{
test1();
cout << "------------------------" << endl;
test2();
cout << "------------------------" << endl;
test3();
cout << "------------------------" << endl;
return 0;
}
C++中拷贝构造函数调用的时机通常有三种情况
1. 使用一个已经创建完毕的对象来初始化一个新对象
2. 值传递的方式给函数参数传值
3. 以值方式返回局部对象
#include
using namespace std;
class Person
{
public:
Person()
{
cout << "无参构造函数已调用......" << endl;
}
Person(int p_age)
{
age = p_age;
cout << "有参构造函数已调用......" << endl;
cout << "年龄:" << age << endl;
}
Person(const Person &p)
{
age = p.age;
cout << "拷贝构造函数已调用......" << endl;
cout << "拷贝的年龄为:" << age << endl;
}
~Person()
{
cout << "析构函数已调用......" << endl;
}
int age;
};
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
void test1()
{
Person p1(23);
Person p2(p1);
}
// 2. 值传递的方式给函数参数传值
void doWork(Person p)
{
}
void test2()
{
// 创建一个无参构造函数
Person p;
// 实参传递给形参的时候会创建一个拷贝构造函数
doWork(p);
}
// 3. 值方式返回局部对象
Person doWork2()
{
// 创建一个无参构造函数
Person p1;
// 局部对象以值的方式返回时,会创建一个新的拷贝构造函数
return p1;
}
void test3()
{
Person P = doWork2();
}
int main(int argc, char* argv[])
{
test1();
cout << "------------------------" << endl;
test2();
cout << "------------------------" << endl;
test3();
cout << "------------------------" << endl;
return 0;
}
默认情况下,C++编译器至少给一个类添加三个函数:
1. 默认构造函数(无参,函数体为空)
2. 默认析构函数(无参,函数体为空)
3. 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,C++不会再提供其他构造函数
深浅拷贝是面试经典问题,也是常见的一个坑
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新深浅空间,进行拷贝操作
#include
using namespace std;
class Person
{
public:
Person(int m_age)
{
age = m_age;
cout << "有参构造函数已调用......" << endl;
}
~Person()
{
cout << "析构函数已调用......" << endl;
}
int age;
};
void test()
{
Person p1(18);
cout << "p1的年龄为:" << p1.age << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
利用系统提供的默认拷贝构造函数进行普通的数据拷贝不会报错
但是当手动开辟堆区数据并在析构函数中进行释放时会报错
原因:利用编译器提供的默认拷贝构造函数会做浅拷贝操作,会把p1的成员属性逐字节的拷贝到p2中,p1和p2的指针指向了堆区同一地址的内容,堆栈的数据先进后出,所以p2先进行析构,把指向的堆区内存的内容释放干净,然后p1析构时仍然会对堆区的内容进行释放,暴露出来浅拷贝的问题:堆区的内容重复释放。浅拷贝的问题要利用深拷贝进行解决,再堆区重新申请一块内容用来存放相同的数据,p2指向此内存。
使用系统提供的默认拷贝构造函数(报错):
#include
using namespace std;
class Person
{
public:
Person(int m_age,int m_height)
{
age = m_age;
height = new int(m_height);
cout << "有参构造函数已调用......" << endl;
}
~Person()
{
// 由程序员手动开辟的堆区数据也需要程序员手动释放,在堆区数据销毁前释放
// 堆区开辟的数据在析构函数执行完后销毁,所以应该析构函数内进行释放
// 析构代码,将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数已调用......" << endl;
}
int age;
int* height;
};
void test()
{
Person p1(18,180);
cout << "p1的年龄为:" << p1.age << endl;
cout << "p1的身高为:" << *p1.height << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
cout << "p2的身高为:" << *p2.height << endl;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
使用自己实现的拷贝构造函数:
#include
using namespace std;
class Person
{
public:
Person(int m_age,int m_height)
{
age = m_age;
height = new int(m_height);
cout << "有参构造函数已调用......" << endl;
}
Person(const Person& p)
{
cout << "拷贝构造函数已调用......" << endl;
age = p.age;
// height = p.height; 编译器默认实现的就是这行代码
// 深拷贝操作
height = new int(*p.height);
}
~Person()
{
// 由程序员手动开辟的堆区数据也需要程序员手动释放,在堆区数据销毁前释放
// 堆区开辟的数据在析构函数执行完后销毁,所以应该析构函数内进行释放
// 析构代码,将堆区开辟的数据做释放操作
if (height != NULL)
{
delete height;
height = NULL;
}
cout << "析构函数已调用......" << endl;
}
int age;
int* height;
};
void test()
{
Person p1(18,180);
cout << "p1的年龄为:" << p1.age << endl;
cout << "p1的身高为:" << *p1.height << endl;
Person p2(p1);
cout << "p2的年龄为:" << p2.age << endl;
cout << "p2的身高为:" << *p2.height << endl;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
总结:1. 浅拷贝:编译器提供的等号赋值操作 深拷贝:从堆区重新创建一块内容存放数据
2. 析构代码的用处:将手动开辟的堆区数据进行释放操作
3. 拷贝构造函数进行深拷贝可以避免堆区数据重复释放的问题
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)......{}
#include
#include
using namespace std;
class Person
{
public:
Person(string m_name,int m_age,int m_height):name(m_name),age(m_age),height(m_height)
{
cout << "构造函数已调用......" << endl;
}
~Person()
{
cout << "析构函数已调用......" << endl;
}
void printInfo()
{
cout << "姓名:" << name << endl;
cout << "年龄:" << age << endl;
cout << "身高:" << height << endl;
}
private:
string name;
int age;
int height;
};
int main(int argc, char* argv[])
{
Person p("张三", 23, 180);
p.printInfo();
return 0;
}
C++类中的成员可以是另一个类的对象,我们称该成员为对象成员
构造的顺序是:先调用对象成员的构造,在调用本类的构造
析构的顺序与构造相反
#include
#include
using namespace std;
class Phone
{
public:
Phone(string name):phone_name(name)
{
cout << "Phone类的构造函数已调用......" << endl;
}
~Phone()
{
cout << "Phone类的析构函数已调用......" << endl;
}
string phone_name;
};
class Person
{
public:
// 隐式转换法:phone(phone_name) ======= Phone phone = phone_name
Person(string name,string phone_name):person_name(name),phone(phone_name)
{
cout << "Person类的构造函数已调用......" << endl;
}
~Person()
{
cout << "Person类的析构函数已调用......" << endl;
}
string person_name;
Phone phone;
};
void test()
{
Person p("张三", "Iphone 15 pro max远峰蓝");
}
int main(int argc, char* argv[])
{
test();
return 0;
}
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员分为:
静态成员变量:1. 所有对象共享同一份数据
2. 在编译阶段分配内存
3. 类内声明,类外初始化
静态成员函数:1. 所有对象共享同一个函数
2. 静态成员函数只能访问静态成员变量
静态成员变量:
#include
using namespace std;
class Person
{
public:
static int age; // 静态成员变量
private:
static int height; // 静态成员变量
};
// 类内声明,类外初始化
int Person::age = 23;
int Person::height = 180;
void test()
{
// 所有对象共享同一份数据
Person p1;
cout << p1.age << endl; // 23
Person p2;
p2.age = 10;
cout << p1.age << endl; // 10
// 静态成员变量两种访问方式
// 1. 通过对象
cout << p1.age << endl;
// 2. 通过类名
cout << Person::age << endl;
// 静态成员变量也有访问权限
// cout << p1.height << endl; p1.height不可访问
// cout << Person::height << endl; Person::height不可访问
}
int main(int argc, char* argv[])
{
test();
return 0;
}
静态成员函数:
#include
using namespace std;
class Person
{
public:
static void printInfo()
{
cout << "age:" << age << endl; // 静态成员函数可以访问静态成员变量
age = 100;
cout << "age:" << age << endl;
// cout << "height:" << height << endl; 报错,不可访问
cout << "printInfo静态成员函数已调用......" << endl;
}
static int age; // 静态成员变量
int height; // 非静态成员变量
private:
static void writeInfo()
{
cout << "writeInfo静态成员函数已调用......" << endl;
}
};
int Person::age = 23;
void test()
{
Person p1;
// 静态成员函数两种访问方式
// 1. 通过对象
p1.printInfo();
// 2. 通过类名
Person::printInfo();
// 静态成员函数也有访问权限
// p1.writeInfo(); 不可访问
// Person::writeInfo(); 不可访问
}
int main(int argc, char* argv[])
{
test();
return 0;
}