Day 10 C++面向对象编程 之 对象的初始化和清理
Day 10 C++面向对象编程 之 对象的初始化和清理
生活中我们买的电子产品都基本会有出厂设置,在某一天我们不用的时候也会删除一些自己的信息数据保证安全。C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置。
一、构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题:
- 一个对象或者变量没有初始状态,对其使用的后果是位置的;
- 使用完一个对象或变量,若没有及时清理,也会造成一定的安全问题
C++利用构造函数和析构函数解决上述两个问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作:
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,进行初始化操作,构造函数都有编译器自动调用,无需手动调用。
- 析构函数:主要作用在于对象销毁前系统自动调用,进行清理操作,执行一些清理工作。
1. 构造函数语法:类名(){}
- 构造函数没有返回值也不写void;
- 函数名称与类(class 类名)名称相同;
- 构造函数可以有参数,因此可以发生重载;
- 程序在调用对象时候会自动调用构造函数,无需手动调用,且只会调用一次。
2. 析构函数语法:~类名(){}
- 析构函数没有返回值也不写void;
- 函数名称与类名形同,在名称前+符号~;
- 析构函数不可以有参数,因此不可能发生重载;
- 程序在对象销毁前会自动调用析构,无需手动调用而且只会调用一次。
#include<iostream>
#include<stdio.h>
using namespace std;
class Person
{
public:
//1.构造函数,
//没有返回值,没有void,函数名与类名相同,可以有参数与发生重载,创建对象时会被调用,且只被调用一次
Person()
{
cout << "Person 构造函数的调用" << endl;//若系统自动写的时候,{}里面是没有任何东西的
}
//2.析构函数
//没有返回值,没有void,函数名与类名相同,在名称前面+~,销毁对象时会被调用,不可以有参数和发生重载,只会被调用一次
~Person()
{
cout << "Person的析构函数的调用" << endl;
}
};
void test01()
{
Person p;//在栈上的数据,执行完之后,会释放这个对象p
};
int main()
{
test01();//一次构造一次析构
//Person p;//只够构造没有析构,但是在main函数执行完之后也会出现析构
system("pause");
return 0;
}
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供这二者,编译器提供的构造函数和析构函数的空实现。
二、构造函数的分类及调用
1. 两种分类方式:
- 按参数分:有参构造和无参构造
- 按类型分:普通构造和拷贝构造
class Person
{
public:
//构造函数
//普通构造函数:有参构造+无参构造
Person()//无参构造,也叫默认构造函数,因为我们不写,编译器就会自己提供,提供的就是无参构造
{
cout << "构造函数出现" << endl;
}
Person(int a)//有参构造
{
age = a;
cout << "构造函数出现" << endl;
}
//拷贝构造函数
Person(const Person &p)
{
//把参数的对象的所有属性都赋值一份给另一个新的一个对象,const防止参数对象被修改
age = p.age;
}
//析构函数
~Person()
{
cout << "析构函数出现" << endl;
}
int age;
};
2. 三种调用方式:
括号法、显示法、隐式转换法
void test02()
{
//1.括号法
Person p;//调用默认构造函数,调用默认构造函数的时候不要加括号
Person p2(7);//调用有参构造函数
Person p3(p2);//调用拷贝构造函数
cout << "p2的年龄:" << p2.age << endl;//7
cout << "p3的年龄:" << p3.age << endl;//7
//2.显示法
Person p1;
Person p2 = Person(7);//调用有参构造,右边是一个匿名对象,然后给这个匿名对象取名为p2
Person p3 = Person(p2);//调用拷贝构造
Person(7);// 为匿名对象,当前行执行结束后,系统会立即回收掉匿名的对象
//3.隐式转换法
Person p4 = 7;//相当于写了Person p4=Person(10),有参构造
Person p5 = p4;//拷贝构造
}
在一个函数体内部可以写另一个函数的声明,默认构造函数调用时,不要加括号,若加了括号 Person p(),编译器会认为是一个函数的声明,不会认为在创建对象。匿名对象,匿名对象当前行执行结束后,系统会立即回收掉匿名的对象。不要用拷贝构造函数初始化匿名对象,比如:Person(p3),编译器会认为 Person(p3) ==Person p3;,对象的声明,重定义了。
三种构造更推荐第一种括号法,直观明了。
三、拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化另一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
//拷贝构造函数调用时机
void test03()
{
Person p1(7);
//1.使用一个已经创建完毕的对象来初始化另一个新对象
Person p2(p1);//调用拷贝构造函数
cout << "p2的年龄:" <<p2.age<< endl;
cout << "p1的年龄:" <<p1.age<< endl;
}
void doWork(Person p)//值传递,创建副本
{
//2.值传递的方式给函数参数传值
}
void test04()
{
Person p;//调用默认构造函数,test04函数结束就析构
doWork(p);//调用拷贝构造函数,test04函数结束就析构
}
//3.以值方式返回局部对象
Person doWork02()
{
Person p1;//调用默认构造函数
return p1;//返回,但是使用值的时候要调用拷贝构造函数
}
void test05()
{
Person p = doWork02();//返回值时,调用拷贝构造函数,拷贝一个新的p1给这个p
}
四、构造函数调用规则
1. 默认情况下,c++编译器至少自动给一个类添加3个函数:
- 默认构造函数(无参,函数体为空);
- 默认析构函数(无参,函数体为空);
- 默认拷贝构造函数,对属性进行值拷贝,当使用一个已经创建完毕的对象来初始化另一个新对象:Person p2(p1);时,系统会调用拷贝构造函数,如果用户没有写拷贝构造函数,编译器会提供默认的拷贝构造函数,但这个构造函数只是简单的对值进行拷贝,没有其他作用。
void test03()
{
Person p1(7);
//使用一个已经创建完毕的对象来初始化另一个新对象
Person p2(p1);//调用拷贝构造函数
cout << "p2的年龄:" <<p2.age<< endl;//7
cout << "p1的年龄:" <<p1.age<< endl;//7
}
用户写了构造函数

用户没写构造函数

2. 构造函数调用规则如下:
- 如果用户有自己定义的有参构造函数,c++不再提供默认无参构造函数,这个时候再调用无参构造函数的话:Person p,会出错;但是会提供默认拷贝构造;
- 如果用户有自己定义的拷贝构造函数,c++不会再提供其他构造函数。
五、深拷贝与浅拷贝
- 深拷贝:在堆区重新申请空间,进行拷贝操作;
- 浅拷贝:简单的赋值拷贝操作。
class Person
{
...
~Person()
{
//使用new创建的对象一定要释放,要在析构之前被释放,所以,写再析构函数中
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int age;
int *m_Height;//指针,指向的是一个身高,把身高的数据存放到堆区
}
void test04()
{
Person p1(18,165);
cout << "P1的年龄为:" << p1.age <<"身高为:"<<*p1.m_Height<< endl;
Person p2(p1);//自己没有写拷贝构造函数时,编译器会自己提供拷贝构造函数,是浅拷贝
cout << "P2的年龄为:" << p2.age << "身高为:" << *p2.m_Height<< endl;
}
//会报错
1. 浅拷贝操作:
自己没有写拷贝构造函数时,编译器会自己提供拷贝构造函数,调用拷贝构造函数是是浅拷贝操作。使用new创建的指针存放在堆区,原有空间存放的是堆区地方的地址,而这个地址在堆区中对应的地方存放的是指针所指向的值。使用new创建的对象一定要释放,要在析构之前被释放,所以,写在析构函数中。这时,因为是浅拷贝,所以p2只是简单的把p1中的值赋值过去,这样就造成了p2和p1身高这一个指针指向的是堆区的同一片空间,在函数调用结束之后,会调用析构函数,此时,在解决p1时,就调用了一次析构函数,在析构函数中有我们所写的释放对象的代码,这里,堆区所存放的对象被释放,在析构p2时,又被调用析构函数,又要释放对象,但是指针指向的堆区的对象已经被我们释放了,这里释放就会产生错误(非法访问)。为解决这个问题,就利用深拷贝操作解决。
2. 深拷贝操作:
就在堆区再申请一个空间存放和p1一样的值,复制过去,复制的是指向的值,而不是浅拷贝的“值”,然后p2申请的指针所指的空间就是堆区重新申请的空间,p1所指的空间就是堆区原来的空间
class Person
{...
Person(const Person &p)
{
cout << "Person拷贝构造函数的调用" << endl;
age = p.age;
//m_Height = p.m_Height;编译器会默认执行这种指针拷贝操作,是浅拷贝
m_Height = new int(*p.m_Height);//重新申请空间并把“值(指针指向的值)传递”
}
~Person()
{
//使用new创建的对象一定要释放,要在析构之前被释放,所以,写再析构函数中
if (m_Height != NULL)
{
delete m_Height;
m_Height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int age;
int *m_Height;//指针,指向的是一个身高,把身高的数据存放到堆区
};
如果属性有在堆区开辟的,那么一定要自己提供拷贝构造函数,申请新的空间赋值一样的值,要让p1和p2指向不一样的堆区空间,才不会发生二次释放造成非法访问。
六、初始化列表
作用: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() :m_a(7), m_b(8), m_c(9) {}//固定了,无法灵活修改,写成下面
Person(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
{
//更灵活,不是固定的了
}
int m_a;
int m_b;
int m_c;
};
void test07()
{
//传统初始化
//Person p(7, 8, 9);
//初始化列表初始化属性
Person p1(4,5,6);
cout << "m_a的值:" << p1.m_a << endl;
cout << "m_b的值:" << p1.m_b << endl;
cout << "m_c的值:" << p1.m_c << endl;
}
七、类对象作为类成员
c++类中的成员可以是另一个类的对象,我们称为该成员为对象成员。
class A{}
class B
{
int b;
A a;//A称为对象成员
}
B类中有对象A作为成员,A为对象成员,创建B类的对象时,会先构造A的对象,会先调用A是构造函数
class Phone
{
public:
Phone(string pName)//给手机名字赋值,这是方法1,另一种方法就是之前写过的get,set函数
{
m_PName = pName;
cout << "手机的构造" << endl;
}
string m_PName;
};
class Person
{
public:
//m_Phone(pName)相当于Phone m_Phone=pName;隐式转换法
Person(string name, string pName):m_Name(name),m_Phone(pName)
{
//初始化列表进行赋初值的操作
cout << "人的构造" << endl;
}
string m_Name;
Phone m_Phone;
};
void test08()
{
Person p("张三", "HONOR");
cout << "名字:" << p.m_Name << "手机:" << p.m_Phone.m_PName << endl;
}
析构的时候,先析构B的,再析构A的,与构造的顺序相反。
八、静态成员
静态成员就是再成员变量和成员函数前加上关键字static,称为静态成员。静态成员分为:
1. 静态成员变量
- 所有对象共享同一份数据,成员数据只有一份,别人改了,你用的时候就是改之后的数据;
- 在编译阶段分配内存,编译阶段(程序还没运行之前就被分配了),在全局区;
- 类内声明,类外初始化,必须要有初始值,否则不能使用
class Person
{
public:
//本句为类内声明
static int m_a;//变成静态成员变量,1.所有对象都共享同一份数据,2.编译阶段就分配内存,3.类内声明,类外初始化操作
private:
static int m_b;
};
int Person::m_a = 100;//本句为类外初始化,去掉static,并加上Person::来表示这个是Person里面的成员
int Person::m_b = 7;//不可被访问
void test09()
{
Person p;
cout << p.m_a << endl;//100
Person p2;
p2.m_a = 200;//虽然的不同的对象,但是m_a是静态成员变量,所有对象都共享,所以这里100被改成200
cout << p.m_a << endl;//200
}
void test10()
{
//静态成员变量,不属于某个对象,所有对象都共享同意扽数据
//因此静态成员变量有两种访问方式
//1.通过对象进行访问
Person p;
cout << p.m_a << endl;//100
//2.通过类名进行访问
cout << Person::m_a << endl;//100
}
静态成员变量也是有访问权限的。public作用域下的可以访问,私有左右用类外不可访问。
2. 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
class Person
{
public:
static void func()//静态成员函数
{
cout << "静态成员函数的调用" << endl;
//m_a = 7;//错误,静态成员函数不可以访问非静态成员变量,无法区分到底是哪个对象的m_a
m_b = 7;//正确,静态成员函数可以访问静态成员变量
}
int m_a;
static int m_b;
};
void test11()
{
//1.通过对象进行访问
Person p;
p.func();
//2.通过类名进行调用
Person::func();
}
静态成员函数也是有访问权限的,私有作用域下的静态成员函数类外访问不到,不可以访问。
更多推荐
所有评论(0)