提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
前言
接下来了解下类中的其他几类构造函数
回顾:
我们先回顾下,上篇文章讲的内容有些什么,在类中有六个默认构造函数,意思就是我们不写,编译器也会默认生成,当然这必须是我们不写,编译器才会生成,我们写了的话,编译器会直接调用我们所写的默认构造函数,关于是否自己写默认构造函数,看编译器所生成的默认构造函数是否符合我们的要求。
构造函数:无返回值,函数名与类名相同,对象实例化的时候自动调用。
默认构造函数:起初始化作用,在程序运行时自动调用。
析构函数:起销毁作用,在程序结束的时候自动调用。
this指针:在类,对象中的隐式参数,this指针并不存放在对象中,this指针是一个形参,在类中并不显示写。
类中的成员函数并不是存在类中的,也并不是存在对象中的。
正文:
一、拷贝构造函数
拷贝构造函数只有一个形参,这个形参是对本类型对象的引用(为右值,具有常性,一般使用const修饰),在使用已经存在的对象创建新的对象的时候由编译器自动调用。对于自定义类型,调用它的拷贝构造,对于内置类型,进行值拷贝。
拷贝构造函数的特征:
1、拷贝构造函数是构造函数的一个重载形式。
2、拷贝构造函数的参数只能有一个,且必须是类对象的引用,使用传值的方法会编译器会直接报错,因为会发生无穷递归调用。
拷贝构造的使用:
在我们写代码中拷贝构造是不可避免的,比如说我们在给函数传参的时候,这时候我们传的是对象本身吗?并不是,这里是调用了拷贝构造函数对s1进行了拷贝,再将拷贝的类交给Funk函数,所以说,拷贝构造是我们避不开的函数。
class A
{
static int a;
};
void Funk(A a){}
int main()
{
A s1;
Funk(s1);
}
当然拷贝构造函数还有一个坑,大家来看这段程序:
这里的问题就涉及到了析构函数,我们都知道析构函数会在程序结束的时候自动调用,但是这里我们所写的析构函数调用的时候会将b的空间给释放掉,但是我们这里使用了拷贝构造,将s1的b拷贝给了s2的b,这时两个对象的b都指向同一块空间,但是析构函数是每有一个对象就会调用一次,所以这里就出现了两次析构的问题。
class A
{
int a=1;
int* b = (int*)malloc(sizeof(int)*1);
public:
void Print()
{
cout << a << endl;
}
~A()
{
free(b);
cout << "~A" << endl;
}
};
void Funk(A a)
{
a.Print();
}
int main()
{
A s1;
A s2(s1);
return 0;
}
好了,我们来看看为什么说,写拷贝构造函数必须使用引用作为参数。
来看看:
class A
{
int a;
int* b;
public:
void Print()
{
cout << a << endl;
}
~A()
{
free(b);
cout << "~A" << endl;
}
A()
{
int a = 1;
int* b = (int*)malloc(sizeof(int) * 1);
}
A(A& s)
{
a = s.a;
b = s.b;
}
};
void Funk(A a)
{
a.Print();
}
int main()
{
A s1;
A s2(s1);
return 0;
}
在定义上,我们知道,当我们将一个已经存在的对象用于创建另一个对象时,就会自动调用拷贝构造,那么问题来了,拷贝构造的实现原理就是:将传入的参数用于创建一个新的拷贝,那么,拷贝构造的机制就是用一个已经存在的对象创建另一个对象,这就会使拷贝构造函数调用自己进行拷贝,如此往复,无穷无尽。
那么我们回到开始的问题,当我们使用拷贝构造的时候,我们如何解决类中的指针问题呢?
class A
{
int a;
int* b;
public:
void Print()
{
cout << a << endl;
}
~A()
{
free(b);
cout << "~A" << endl;
}
A()
{
int a = 1;
int* b = (int*)malloc(sizeof(int) * 1);
*b = 1;
}
A(A& s)
{
a = s.a;
b = (int*)malloc(sizeof(s.b));
cout << sizeof(b)<< endl;
}
};
void Funk(A a)
{
a.Print();
}
int main()
{
A s1;
A s2(s1);
return 0;
}
说到拷贝,我们不就是将一个对象进行复制,创造一个一模一样的对象嘛,那么这里我们只需要将s1中b指针的大小计算出,然后在新拷贝的对象中开辟一个大小一样的空间不就行了吗?
二、运算符重载
我们写代码的时候经常使用一些运算符来对数据进行处理,例如:
那么对于自定义类型的数据,这些运算符还能够正常使用吗?
int main()
{
A s1;
A s2;
int a = 1;
int b = 2;
a + b;
a - b;
s1 + s2;
s1 - s2;
return 0;
}
答案是:不能,因为编译器根本不知道你要对这些自定义类型进行什么操作,编译器只知道内置类型要怎么操作。
那么,我们要怎样才能正常操作呢?
这里就用到了我们的运算符重载。
那么什么是运算符重载呢?
1.什么是运算符重载
运算符重载的意思和函数重载的意思没有什么区别,都是赋予其更多的选择,增加其匹配的参数,使其功能更多,但是二者并没有什么联系。
2.运算符重载的使用
运算符重载要用到一个新的东西,operator,它的后面跟需要重载的运算符,
语法为: 返回值 operator 运算符(参数){ 函数体 }
class A
{
int a;
public:
int operator+(A b)
{
return (a + b.a);
}
};
运算符重载常用于自定义类型的运算,赋予运算符更多的匹配参数有助于我们更加灵活的操作数据,但是有些运算符并不能进行运算符重载,如.、.*、::、?:、sizeof等,这些并不能进行运算符重载,大家一定要注意!
总结
运算符重载能够让我们更加了函数重载的意义,并且能够让我们更加灵活的处理数据,但是有少部分运算符并不能进行重载,这里需要注意,
文章中如有什么错误,望指正。————谢谢!