对象中的析构和构造概述:
我们在购买一台电脑或者手机,或者其他的产品,这些产品都有一个初始设 置,也就是这些产品对被创建的时候会有一个基础属性值。那么随着我们使用手机 和电脑的时间越来越久,那么电脑和手机会慢慢被我们手动创建很多文件数据,某 一天我们不用手机或电脑了,那么我们应该将电脑或手机中我们增加的数据删除 掉,保护自己的信息数据。
具体来说,当我们创建对象的时候,这个对象应该有一个初始状态,当 对象销毁之前应该销毁自己创建的一些数据。 对象的初始化和清理也是两个非常 重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知,同样的使 用完一个变量,没有及时清理,也会造成一定的安全问题。
c++为了给我们提供这 种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完 成对象初始化和对象清理工作。
对象的构造函数和析构函数
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动 调用,无须手动调用。 析构函数主要用于对象销毁前系统自动调用,执行一些清 理工作。
一,构造函数和析构函数语法
构造函数语法: 构造函数函数名和类名相同,没有返回值,不能有 void,但可以有参数。 ClassName(){}
析构函数语法: 析构函数函数名是在类名前面加”~”组成,没有返回值,不能有 void,不能有参数,不能重载。 ~ClassName(){}
class test {
public:
int age;
string name;
test()
{
name = "test";
age = 100;
cout << "构造函数调用" << endl;
}
~test()
{
cout << "析构函数调用" << endl;
}
};
void test01()
{
test t1;
cout << t1.age << " " << t1.name << endl;
//t1.~test();/*析构函数可以直接手动调用(但是系统会再多调用一次),但是构造函数不能手动调用*/
}
二,构造函数的分类和调用
1,按照参数来分:
有参构造和无参构造(默认构造)
,2,按照类型来分:
普通构造函数好和拷贝构造函数(复制构造函数)
class Person {
public:
int age;
/*无参构造的定义*/
Person()
{
cout << "Person:无参构造调用" << endl;
}
/*有参构造的定义*/
Person(int a)
{
age = a;
cout << "Person:有参数构造调用" << endl;
}
/*析构函数的定义*/
~Person()
{
cout << "Person:析构函数调用" << endl;
}
//拷贝构造
Person(const Person &p)//使用引用的方式传参,并且是不能改变p所引用的变量的值
{//传入对象的所有属性拷贝到另外一个对象上
cout << "拷贝构造函数调用" << endl;
age = p.age;
}
};
3,构造函数的调用方法:
括号法,显示法,隐示转换法
具体实现如下:
void test01()
{
//1,括号法
/*注意:
调用默认构造函数是不用加小括号(编译器认为是函数声明)不好人为是创建对象
*/
//Person p1;
// //()
//Person p2(10);
//Person p3(p2);
//cout << "p2:age = " << p2.age << endl;
//cout << "p3:age = " << p3.age << endl;
//2,显示法不能初始化匿名对象编译器认为没有小括号会被认为对象声明
//Person p1;
//Person p2 = Person(10);//匿名对象当前行结束后,系统会立即回收掉匿名对象
//Person p3 = Person(p2);
//3,隐式转换法
/*Person p4 = 10;
Person p5 = p4;*/
}
注:
匿名对象:b 为 A 的实例化对象,A a = A(b) 和 A(b)的区别? 当 A(b) 有变量来接的时候,那么 编译器认为他是一个匿名对象,当没有变量来接的时候,编译器认为你 A(b) 等价 于 A b。A是类名,不能去调用拷贝构造去初始化匿名变量,会报错,因为匿名变量在执行完对应的那行之后,系统会自动收回匿名对象。
三,拷贝构造函数调用详解:
class Peroson{
public:
//拷贝
Person(const Person &p)
{//传入对象的所有属性拷贝到另外一个对象上
cout << "拷贝构造函数调用" << endl;
age = p.age;
}
int age;
}
void test01()
{
Person p1;
p1.age = 10;
Person p2(p1);
}
void doBussiness(Person p)
{
}
void test02()
{
Person p(10);
doBussiness(p);
}
3,构造函数调用规则
默认情况下,c++编译器至少为我们写的类增加 3 个函数
1.默认构造函数(无参, 函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数
对类中 非静态成员属性简单值拷贝 如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数 如果用户定义了普通构造(非拷贝),c++不在提供默认无参构造,但是会 提供默认拷贝构造
拷贝构造中的深拷贝和浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然 是独立的两个对象,这种情况被称为浅拷贝.
深拷贝:当类中有指针,并且此指针有动态分配空间,析构函数做了释放处 理,往往需要自定义拷贝构造函数,自行给指针动态分配空间
class Person()
{
public:
Person()
{
}
Person(const Person& p)
{
name = (char *)malloc(strlen(p.name)+1)
strcpy(name, p.name);
age = p.age;
}
~Person()
{
free(name);/*对对象申请的内存空间进行释放*/
}
public:
int age;
char* name;
}
四,构造函数初始化列表的使用:
class test {
public:
test(int pa,int pb,int pc):a(pa),b(pb),c(pc)
{}
/*相当于
test(int pa,int pb,int pc)
{
a = pa;
b = pb;
c = pc;
}
*/
int a;
int b;
int c;
/*注意:顺序会影响初始化列表的值*/
};
五,一个类作为另外一个类的成员使用:
在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对象, 叫做对象成员。 C++中对对象的初始化是非常重要的操作,当创建一个对象的时候,c++编译器必须确保调用了所有子对象的构造函数。如果所有的子对象有默认 构造函数,编译器可以自动调用他们。对于子类调用构造函数,c++为此提供了专门的 语法,即构造函数初始化列表。 当调用构造函数时,首先按各对象成员在类定义 中的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始 化,最后再调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。 析构函数和构造函数调用顺序相反,先构造,后析构。
class A {
public:
int a;
A(int a)
{
this->a = a;
}
};
class B {
public:
int b;
B(int b)
{
this->b = b;
}
};
class C
{
public:
int c;
C(int c)
{
this->c = c;
}
};
class test {
public:
test(int a,int b ,int c):p1(a),p2(b),p3(c)
{
cout << "构造函数调用" << endl;
}
A p1;
B p2;
C p3;
};
void test01()
{
test t1(1, 2, 3);
cout << t1.p1.a << t1.p2.b << t1.p3.c << endl;
}
六,explicit关键字
c++提供了关键字 explicit,禁止通过构造函数进行的隐式转换。声明为 explicit 的 构造函数不能在隐式转换中使用。 [explicit 注意] explicit 用于修饰构造函数,防止隐式转化。 是针对单参数的构造函 数(或者除了第一个参数外其余参数都有默认值的多参构造)而言。
class MyString{
public:
explicit MyString(int n)
{
cout << "MyString(int n)!" << endl;
}
MyString(const char* str)
{
cout << "MyString(const char* str)" << endl;
}
};
void test()
{
Mystring str1 = 1;/*不会调用第一个构造函数,因为它不能隐示转换*/
}