4.2.5 深拷贝与浅拷贝
浅拷贝:编译器提供的简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
示例:
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height); //利用关键字new把身高放在堆区,返回的是一个地址
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
//自己写一个拷贝构造函数,解决浅拷贝带来的问题:在堆区再开辟一段空间
m_age = p.m_age;
//m_height = p.m_height (编译器自动提供时的拷贝构造函数写法)
m_height = new int(*p.m_height); //深拷贝重新开辟一块内存
//通过传入的地址进行解引用之后,再在堆区申请一块内存存入
}
//析构函数
~Person() {
//析构代码,将堆区开辟的数据做释放操作(堆区的数据需要程序员手动开辟,也需要程序员手动释放)
//在对象销毁前对堆区的数据释放掉(test01执行完了之后),所以在析构函数时把数据释放干净
cout << "析构函数!" << endl;
if (m_height != NULL) //如果该指针不为空,就将其用delete删除
{
delete m_height;
m_height = NULL; //防止野指针出现,将其置空
}
}
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();
system("pause");
return 0;
}
具体差别如下图所示:
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
4.2.6 初始化列表
作用:
C++提供了初始化列表语法,为类中的属性进行初始化(类似于构造函数的初始化)
语法:构造函数():属性1(值1),属性2(值2)... {}
示例:
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) {}
// 写法2:Person() :m_A(1), m_B(2), m_C(3) {} 但是值被固定,不够灵活
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);
//对应类中的写法2:Person p;
p.PrintPerson();
system("pause");
return 0;
}
4.2.7 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
构造函数:先构造类中的成员(调用类中成员的构造),再构造本类
析构函数:与构造顺序相反
例如:
class A {}
class B
{
A a;
}
示例:
class Phone
{
public:
Phone(string name)
{
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone()
{
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
class Person
{
public:
//初始化列表可以告诉编译器调用哪一个构造函数
//相当于 Phone m_Phone = pName 隐式转换法用pName创建对象
Person(string name, string pName) :m_Name(name), m_Phone(pName)
{
cout << "Person构造" << endl;
}
~Person()
{
cout << "Person析构" << endl;
}
string m_Name;
Phone m_Phone;
};
void test01()
{
//当类中成员是其他类对象时,我们称该成员为 对象成员
//构造的顺序是 :先调用对象成员的构造,再调用本类构造
//析构顺序与构造相反
Person p("张三" , "华为mate60 Pro");
cout << p.m_Name << " 使用" << p.m_Phone.m_PhoneName << " 手机! " << endl;
}
int main() {
test01();
system("pause");
return 0;
}
4.2.8 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员;包括静态成员变量和静态成员函数。
静态成员变量 | 静态成员函数 |
特点 | |
(在内存中只有一个值,其他函数修改之后再调用也会变成修改后的值)
|
|
调用方式(静态成员函数的调用要增加作用域) | |
|
示例1 :静态成员变量
class Person
{
public:
static int m_A; //静态成员变量(类内声明)
private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10; //类外初始化(为了说明是Person下的要写作用域Person::)
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; //用p2去修改m_A的值
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();
system("pause");
return 0;
}
示例2:静态成员函数
class Person
{
public:
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();
system("pause");
return 0;
}