一、对象的赋值

一个对象被初始化之后再做赋值运算就是对象的赋值,如果对象在做初始化时,进行"="号运算实际上是调用拷贝构造。

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;
}
  1. 看到这里,读者可能会有一点疑惑,为什么要判断一下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。

  1. 其实,在赋值时,指针所带来的内存地址相同的问题不只这里。 当两个对象进行赋值时,类中有有指针,则在进行赋值时,两个不同的对象的指针所指向的内存地址是相同的,也就是说并不会为被赋值的对象(左值)的指针再分配一块内存。那么当两个对象被析构时,同一块内存会被析构两次,会报错。(自己定义了析构函数才会报错,采用默认的析构函数不会报错。)
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

二、对象隐式转换

  1. 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; 没有调用赋值函数。