一、C++构造与析构函数
1.1、构造函数和析构函数
在我们定义一个类的时候,总是会存在该类的属性需要在创建时候需要初始化怎么办?一般情况下我们我们可以提供一个init初始化的方法,但是这种方法也存在他自身的弊端,比如被别人篡改属性怎么办。
于是就提出了构造函数这个概念,构造函数在创建类的时候就会被运行。
构造函数定义方式:
class 类名
{
类名(参数列表)
{
}
};
最终在创建对象的时候,根据参数列表会进行不同的构造函数,如果没有创建显式构造函数,系统会默认给我们创建一个隐式构造函数。
如下面这段代码:
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
x = 0;
y = 0;
}
Test(int m_x)
{
cout << "Test(int m_x)" << endl;
x = m_x;
y = 0;
}
Test(int m_x,int m_y)
{
cout << "Test(int m_x,int m_y)" << endl;
x = m_x;
y = m_y;
}
void print(void)
{
cout << "x = " << x << " y = " << y << endl;
}
private:
int x;
int y;
};
int main(void)
{
Test t(10,23);
t.print();
cout << "-----------------------" << endl;
Test t2(10);
t2.print();
cout << "-----------------------" << endl;
Test t3;
t3.print();
return 0;
}
运行结果:
对于结果我们可以发现,创建对象的时候最终会根据形参类匹配对应的构造函数,特别对于第三种,和我们平时没有写构造函数一样也可以编译通过,就知道隐式构造函数如下:
Test()
{
}
里面没有任何东西。
讲完构造函数,接下来说一下析构函数,析构函数就是在删除对象之前需要运行的一个对象方法,最明显的特点就是比如我们在对象内有一个申请内存指向的指针,需要在类被删除时候将申请的内存释放掉,这样的话我们还专门去确定对象什么时候被删除,然后自己实现一个方法释放,这样反而麻烦,所以析构函数就出现了。
析构函数定义方式:
class 类名
{
~类名()
{
}
};
他是没有任何形参列表的,他也和构造函数一样,如果你写显示析构函数,那么他会运行你写的析构函数,否则他会运行隐式析构函数。
我们看一下下面这段代码:
#include <iostream>
using namespace std;
class Test
{
public:
Test(char *pname)
{
cout << "Test()" << endl;
x = 0;
y = 0;
int len = strlen(pname);
name = (char *)malloc(len + 1);
if (name == NULL){
cout << "init name err" << endl;
}
else {
strcpy(name, pname);
}
}
void print(void)
{
cout << " name = " << name << endl;
}
~Test()
{
cout << "~Test()" << endl;
if (name != NULL){
free(name);
name = NULL;
}
}
private:
int x;
int y;
char *name;
};
void test1(void)
{
Test t("gale");
t.print();
}
int main(void)
{
cout << "begin" << endl;
test1();
cout << "end" << endl;
return 0;
}
运行结果:
从结果我们看到,在该类被摧毁后,会调用析构函数,在里面我们 可以做一下需要清理内存的事情。
我们在来看一段代码:
#include <iostream>
using namespace std;
class Test
{
public:
Test(char *pname)
{
cout << "Test()" << endl;
x = 0;
y = 0;
int len = strlen(pname);
name = (char *)malloc(len + 1);
if (name == NULL){
cout << "init name err" << endl;
}
else {
strcpy(name, pname);
}
}
void print(void)
{
cout << " name = " << name << endl;
}
~Test()
{
cout << "~Test()" << endl;
if (name != NULL){
cout << "name:" << name << endl;
free(name);
name = NULL;
}
}
private:
int x;
int y;
char *name;
};
void test1(void)
{
Test t("gale");
t.print();
cout << "---------------------" << endl;
Test t2("feichang");
t2.print();
}
int main(void)
{
cout << "begin" << endl;
test1();
cout << "end" << endl;
return 0;
}
运行结果:
我们在函数里面增加多一个对象,然后在类里面的析构函数打印名字,主要是为了区分不同的对象,在结果我们看到,后定义的对象会先被析构函数进行,其实也可以理解,毕竟是谁先压栈,肯定是后面出栈。
1.2、拷贝构造函数
我们来看一段代码:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int m_x,int m_y)
{
cout << "Test()" << endl;
x = m_x;
y = m_y;
}
void print(void)
{
cout << "x = " << x << " y = " << y << endl;
}
private:
int x;
int y;
};
int main(void)
{
Test t(10,20);
cout << "t2----------" << endl;
Test t2(t);
t2.print();
return 0;
}
运行结果:
从中发现,使用下面这个语句:
Test t2(t);
是可以将t的属性初始值付给t2,但是我们并没有写对应的构造函数,他是如何进行元素赋值的呢。
其实这个是在类里面和构造函数一样,系统也给类创建了一个隐式拷贝构造函数,如果我们没有定义拷贝构造函数,那么系统将会调用系统的拷贝构造函数。
我们也可以写一个显式的拷贝构造函数:
在类里面增加这个函数:
Test(class Test &another)
{
cout << "Test(class Test &another)" << endl;
x = another.x;
y = another.y;
}
然后运行结果如下:
你就会发现,系统在运行拷贝构造函数时候,就已经调用我们写的显式拷贝函数了。
其中:
Test t2(t);
//等价于
Test t2 = t;
但是下面这种是不相等的:
Test t2(t);
//不等于
Test t2;
t2 = t
很明显,构造函数是在对象初始化的时候才会调用,而下面的 t2 = t 这个明显不是在创建对象时候运行的,这种是等号运算,我们可以看下面这个例子:
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
cout << "Test()" << endl;
x = 0;
y = 0;
}
Test(int m_x,int m_y)
{
cout << "Test(int m_x,int m_y)" << endl;
x = m_x;
y = m_y;
}
Test(class Test &another)
{
cout << "Test(class Test &another)" << endl;
x = another.x;
y = another.y;
}
void print(void)
{
cout << "x = " << x << " y = " << y << endl;
}
void operator=(class Test &another)
{
cout << "void operator=(class Test &another)" << endl;
x = another.x;
y = another.y;
}
private:
int x;
int y;
};
int main(void)
{
Test t(10,20);
cout << "t2----------" << endl;
Test t2(t);
t2.print();
cout << "t3----------" << endl;
Test t3;
t3 = t2;
t3.print();
return 0;
}
运行结果:
我们发现t3运行的是等号符号函数,所以在初始化对象时候需要注意这种情况。
1.3、构造函数的两个注意点
第一点:
当提供一个有参、无参、的构造函数或者拷贝构造函数,默认无参构造函数就会失效。
第二点:
定义一个函数:
void fanc(Test t)
将一个类做形参的时候,比如你想传递进去的是对象t1,那么最终传递过程等于:
Test t = t1;
所以最终参数t会调用构造函数进行处理。
1.4、拷贝函数的浅拷贝和深拷贝
我们先看一段浅拷贝的代码:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int m_id, char *pname)
{
id = m_id;
int len = strlen(pname);
name = (char *)malloc(len + 1);
if (name == NULL){
cout << "name init err" << endl;
}
else{
strcpy(name, pname);
}
}
void print()
{
cout << "id = " << id << " name = " << name << endl;
}
~Test()
{
if (name != NULL){
cout << "name: " << name << endl;
free(name);
name = NULL;
}
}
private:
int id;
char *name;
};
void func(void)
{
Test t1(1,"gale");
Test t2(t1);
}
int main(void)
{
func();
return 0;
}
运行结果:
其实只有在析构函数里面才有name的输出,可是他只输出gale后,便报错了,我们来分析一下这种情况。
如下面这个图:
因为我们运行的是系统的隐式拷贝构造函数,他只是单纯的赋值相等,所以是浅拷贝函数,所以两个对象所指向的名字都是同一个地址,但是当t2运行析构函数的时候,他将张三这个地址申请的内存释放掉,所以t2运行没有问题。
出现问题的是在t1也打算调用析构函数去释放掉申请的内存时,他就出现问题了,因为在之前该块地址早就被t2释放掉了,所以就出现问题了。
所以要切记:
如果属性中有指针的,一定要切记使用显示拷贝构造函数,在里面进行深拷贝,下面这个函数增加到类里面,他是显式的拷贝构造函数:
Test(class Test &another)
{
int len = strlen(another.name);
name = (char *)malloc(len +1);
if (name == NULL){
cout << "name init err" << endl;
}
else{
strcpy(name,another.name);
}
}
单独给对象中的名字申请一片独立的内存,这样就可以避免出现刚才那种错误,也就是得使用显式深拷贝构造函数,在里面单独申请一片内存给对应的对象。
运行结果:
这样就可以避免出错了。
1.5、构造函数列表
由于存在构造函数,那么就哟一个疑惑,就是我先定义一个类,然后在另外一个类中申明两个之前定义的类的对象,那么我该如何进行触发之前的对象的构造函数来初始化呢?
#include <iostream>
using namespace std;
class Test1
{
public:
Test1(int m_a, int m_b)
{
cout << "Test1(int m_a, int m_b)" << endl;
a = m_a;
b = m_b;
}
void printAB(void)
{
cout << "a = " << a << ",b = " << b << endl;
}
private:
int a;
int b;
};
class Test2
{
public:
Test2(Test1 &a, Test1 &b, int m)
{
a1 = a;
b1 = b;
c = m;
}
private:
Test1 a1;
Test1 b1;
int c;
};
int main(void)
{
Test1 a(10,20);
Test1 b(30,40);
Test2 a1(a,b,50);
return 0;
}
如果根据我们之前学到知识,一般来讲都是这样写,可是这样写,在
Test2(Test1 &a, Test1 &b, int m)
{
a1 = a;
b1 = b;
c = m;
}
其中 a1 = a; 和 b1 = b; 这两个是赋值语句,不是触发构造函数,所以这样根本没法给第二个类中的两个对象初始化,但是如果单独写一个方法又破坏类的统一,于是构造函数列表出现了。
其实也很简单,只需要在第二个类中的构造函数改变一下,如下:
Test2(Test1 &a, Test1 &b, int m) :a1(a), b1(b)
{
c = m;
}
这样就可以达到触发拷贝构造函数来初始化了。
最终代码如下:
#include <iostream>
using namespace std;
class Test1
{
public:
Test1(int m_a, int m_b)
{
cout << "Test1(int m_a, int m_b)" << endl;
a = m_a;
b = m_b;
}
void printAB(void)
{
cout << "a = " << a << ",b = " << b << endl;
}
private:
int a;
int b;
};
class Test2
{
public:
Test2(Test1 &a, Test1 &b, int m) :a1(a), b1(b)
{
c = m;
}
void print(void)
{
cout << "a1:" << endl;
a1.printAB();
cout << "b1:" << endl;
b1.printAB();
}
private:
Test1 a1;
Test1 b1;
int c;
};
int main(void)
{
Test1 a(10,20);
Test1 b(30,40);
Test2 a1(a,b,50);
a1.print();
return 0;
}
运行结果如下:
这样就可以完成之前想要在一个类中初始化类中的对象。