一、对象的赋值
一个对象被初始化之后再做赋值运算就是对象的赋值,如果对象在做初始化时,进行"="号运算实际上是调用拷贝构造。
A a2;
A a = a2; //拷贝构造
A a3; //a3已经被初始化,
a3 = a2; //对象的赋值
- 两个对象之间的赋值,只会让这两个对象数据成员相同,而两个对象仍然是相互独立的。
- 对象赋值是通过赋值运算符实现的。如果程序员没有重载“=”运算符,那么编译器会创建一个默认的赋值运算符。默认运算符函数的结构如下:
A& A::operator=(const T& rhs){
//check for self assignment
if(this != &rhs){
//perform assignment
}
return *this;
}
- 看到这里,读者可能会有一点疑惑,为什么要判断一下this != &rhs 呢?也就是说这里要判断一下是不是在做 ”自己=自己” 的运算。我们知道,对象的赋值实际上就是在做对象数据成员的赋值,那么就算 “自己=自己” 不加if判断,直接自己与自己赋值一下又有什么不可以呢?下面来看一段代码:
class A{
char *p;
A& operator=(const A& that){
delete p;
p = new char[strlen(that.p)+1]
strcpy(p,that.p);
return *this;
}
}
可以看到,指针P指向的内存被释放了,假如 this == &that 那么释放this->p指向的内存就顺便也把 that.p的内存释放掉了。因此默认赋值都会加一个if 判断是不是在做自我赋值,如果是,则直接跳过赋值,直接return *this。
- 其实,在赋值时,指针所带来的内存地址相同的问题不只这里。 当两个对象进行赋值时,类中有有指针,则在进行赋值时,两个不同的对象的指针所指向的内存地址是相同的,也就是说并不会为被赋值的对象(左值)的指针再分配一块内存。那么当两个对象被析构时,同一块内存会被析构两次,会报错。(自己定义了析构函数才会报错,采用默认的析构函数不会报错。)
class A{
private:
char *p;
public:
A(){};
A(char *s){ p=new char[strlen(s)+1]; strcpy(p,s); };
void fun(){ cout<<"p :"<<p<<endl; };
~A(){ delete []p; };
A& operator=(A& obj){ //这里必须得重载,否则两个对象的p指向同一块地址
delete []p;
p = new char[ strlen(obj.p) + 1];
strcpy(p,obj.p);
return *this;
}
};
int main(){
A a("das");
A a2("");
a2 = a; //调用默认赋值运算
a2.fun();
return 0;
}
此时就需要自己定义运算符重载,先Delete p
二、对象隐式转换
- A c = 2; 将int类型的数据隐式转化成A类,程序会去A类中寻找相应的构造函数: A(int n){ }; 如果找不到的话,就会转换失败(当然,A(double n){} 如果有也是可以的 )。
下面来看一段代码示例:
class A{
public:
A(){}
};
class B{
public:
B(int ){}
B(const A&){} //以A为参数的构造函数
};
int main(){
B b = 1; // B(int)
A a,a2;
B b2 = a; // 隐式的去B类中寻找B(const A& )构造函数
b2 = a2; //先去B中找以A为参数的构造函数,再调用默认赋值函数 ?????
}
解释:
- 当执行b2 = a2;这句代码时,编译器会自动对a2(A类的实例化)进行隐式转换,将a2转换成B类。转不转换的成功取决于B类中有没有对应的构造函数。
- 转换成功后b2 = (A)a2; 这里编译器又会自动调用默认赋值函数。
- 那么如何阻止这种隐式转换呢?也就是说程序员不希望编译器进行这种隐式转换。这时候需要用到
explicit
关键字。
三、explicit关键字 明确的
explicit关键字只能用于修饰只有一个参数
的类构造函数
(多个参数、除了第一个外,其它均缺省值的构造函数也可以), 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式).
- 告诉编译器,这个函数不能用来隐式转换
class A{
public:
A(){}
};
class B{
public:
B(int ){}
explicit B(const A&){} //以A为参数的构造函数
};
int main(){
B b = 1; // B(int)
A a,a2;
B b2 = a; // ERROR !!! explict取消了隐式转换
b2 = a2; // ERROR !!! A类 无法被隐式转换
b = B(a2); // OK 显示转换
}
解释:
- 使用了explicit关键字后,就不能调用构造函数来强制转换了。也就是说构造函数只能用来构造而不能用来强转。
- B b = a; 与 B b(a); 是不同的,在explicit B(const A&){}约束下,B b = a;是无法完成的,而B b(a)是可以的。顺便补充一下,B b =a; 没有调用赋值函数。