本篇讲述以上两种默认函数
我们可以创建一个对象,那么能否再创建一个和这个对象一模一样的的新的对象呢?这就引入了拷贝构造函数
拷贝构造函数与之前讲的构造函数一样,函数名为类名,当用已存在的类类型的对象创建新的对象时,编译器会自动调用拷贝构造函数。
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
private:
int _a;
double _b;
};
int main()
{
A1 A(5,1.5)//已经存在的对象
A1 B(A);//通过A创建一个一模一样的的对象B
}
上面我们没有显式的定义出拷贝构造函数,若要显式定义,如下:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
A1(const A1& a)//拷贝构造函数,需要通过引用传值,否则会一直调用拷贝函数进入死递归
{
_a = a._a;
_b = a._b;
}
private:
int _a;
double _b;
};
int main()
{
A1 A(5,1.5)//已经存在的对象
A1 B(A);//通过A创建一个一模一样的的对象B
}
这里的的默认拷贝构造函数,是一种浅拷贝(以后会讲到),资源是拷贝不过去的。
总结:
1.拷贝构造函数是构造函数的一个重载形式。
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝称为浅拷贝,或者值拷贝。
4.如果涉及到资源的拷贝,便需要我们自己显式定义拷贝构造函数,完成资源的拷贝。
5.建议在引用前加上const保证可以使用临时对象进行拷贝
如果,我们要判断两个对象是否相等,可以如下:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
bool isequal(const A1& a)//判断是否相等
{
return _a == a._a&&_b == a._b;
}
private:
int _a;
double _b;
};
int main()
{
A1 A;
A1 B(A);
cout<<B.isequal(A)<<endl;
}
B.isequal(A)写的话,代码的可读性并不是很高,如果写成A==B的话,那么便一目了然的知道是判断是否相等。因此,这里引入运算符重载函数
运算符重载函数:
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
例如将上述比较对象是否相等的函数写为运算符重载函数如下:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
bool operator==(const A1& A)
{
return _a == A._a&&_b == A._b;
}
// bool isequal(const A1& a)
// {
// return _a == a._a&&_b == a._b;
// }
private:
int _a;
double _b;
};
int main()
{
A1 A;
A1 B(A);
cout<<(A==B)<<endl;
//cout << A.operator==(B) << endl;等价于上面
//cout<
}
这里的bool operator==(const A1& a)是有两个参数的,只不过第一个参数是调用者的this指针,即左操作数是this指向的调用函数的对象。
当然还有以下这种定义方式:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
bool operator==(const A1& A,const A1& B)
{
return B._a == A._a&&B._b == A._b;
}
// bool isequal(const A1& a)
// {
// return _a == a._a&&_b == a._b;
// }
//private:
int _a;
double _b;
};
int main()
{
A1 A;
A1 B(A);
cout<<(A==B)<<endl;
//cout << A.operator==(B) << endl;等价于上面
//cout<
}
这是定义的全局的operator== 因此,需要时成员变量是公有的,如果还想要保持封装性的话,就需要用到我们后面学习的友元解决,
再比如我们定义一个求两数的和:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
int operator+(const A1& a)//求两数和
{
return _a + a._a;
}
private:
int _a;
double _b;
};
int main()
{
A1 A;
A1 B(5,10.2);
cout<<(A+B)<<endl;
cout<<A.operator+(B)<<endl;
}
结果如图
总结:
1.不能通过连接其他符号来创建新的操作符:比如operator@
2.重载操作符必须有一个类类型或者枚举类型的操作数
3.用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4.作为类成员的重载函数时,其形参看起来比操作数数目少1,成员函数的操作符有一个默认的形参this,限定为第一个形参
5.’*’ 、’::’ 、‘sizeof’ 、’?:’ 、’.’ 注意以上5个运算符不能重载.
赋值运算符重载
之前我们用构造函数或拷贝函数给另对象赋值,这里我们可以通过运算符来赋值,比如:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
//运算符赋值重载函数
//默认返回的是*this
const A1& operator=(const A1 &A)
{
if(this!=&A)
{
_a = A._a;
_b = A._b;
}
return *this;
}
void display()
{
cout<<_a<<" "<<_b<<endl;
}
private:
int _a;
double _b;
};
int main()
{
A1 A;
A1 B(5,10.2);
B.display();
B = A;//把A赋给B
B.display();
}
打印结果如图:
注意:
1.=调用:如果对象都存在,调用赋值运算符重载函数, 如果左边对象不存在,调用拷贝构造
2.返回值为引用类型
3.默认返回*this
4.一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
比如:
class A1
{
public:
A1(int a = 1,double b = 0.1)
{
_a = a;
_b = b;
}
void display()
{
cout<<_a<<" "<<_b<<endl;
}
private:
int _a;
double _b;
};
int main()
{
A1 A(10,2.5);
A1 B = A;//编译器为我们默认生成一个赋值运算符重载函数
B.display();
}