您的位置:首页 > 编程语言 > C语言/C++

由C++复制控制想到

2011-05-18 15:31 190 查看
由C++复制控制想起 一、什么是初始化、赋值。它说初始化是创建变量并给变量赋初值,而赋值是擦除对象的当前值并用新值代替。 二、C++的初始化、赋值。在C++中进行初始化时必须使用初始化列表才叫初始化。而不是在构造函数中对其赋值。那么,初始化列表和直接赋值有什么本质区别?那下我们来看代码:
A::A(int const var1, string const& var2) : id(var1), name(var2) { }
这一次id和name就只分别调用了它们各自的复制构造函数一次,而免去了多余了那一次对operator = 的调用.对于非内置类型来说,这就提高了对象的初始化效率。虽然对于内置类型来说这没有什么大的不同,但为了保持一致性,通常也把它们放在初始化成员列表中进行初始化。 另外 = 操作符所表示的意思也不总是赋值,在对象定义声明时使用操作符 = 对象调用的就是其复制构造函数而不是operator =。
三、explicit为何物。      从《c++ primer》中的一句话说起:“某些版本将参数为一个string的构造函数定义为explicit。如果构造函数是显式的,则初始化失败;如果构造函数不是显式的,则初始化成功”。这里说的显式即是使用explicit来声明这个构造函数,而不是显式则不使用explicit。下面来看一点代码:显式使用explicit :class Sales_item{private:       string str;public:       explicit Sales_item(string str)       {              this->str = str;       }       void print()       {              cout<<str<<endl;       }};void main(){     Sales_item temp= "abc.dsljf";      //① error ,不能通过编译。     Sales_item item = string("abc.dsljf"); //② error ,由无名对象初始化,不能通过编译。        Sales_item item("abcsljd");    //③ OK,可以通过编译。       temp.print();}
在①、②中这时我们的右值是一个C-style的字符串,而左值是一个类。当不使用explicit 时自然是不会出现这样的情况的,可以顺利的通过编译并正常的执行。。C++作为一种强类型的语言,这里并没有显式的进行类型转换,那么一定是发生了隐式的类型转换。编译器知道传入的值是字符串,而需要的右值是Sales_item类型,但它同时也知道调用Sales_item的构造函数将字符串转换成一个合适的Sales_item。而加上explicit后,就会出现上面程序中所示的错误,那么现在为什么会是这样的情况呢?这个问题得从explicit说起。在C++中,explicit关键字用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。只要定义了任何一个需要参数的构造函数,编译器就不会为你生成默认构造函数。就这个问题而言,在这条语句中:Sales_item item = "abc.dsljf"; 若没有在构造函数中使用explicit这个关键字,那么这个隐式转换的过程如下:
我们并没有显式的指定一个复制构造函数,这里是通过默认的复制构造函数实现这个转换过程。而当我们使用了explicit后,编译器就不允许进行隐式转换。这个时候,上面的过程不能进行。会报出如下错误:

当我们使用③,即直接构造形式时不会存在上述过程,那么就可以顺利通过。我们使用explicit的目的就是强调类型的匹配!不能随意的进行隐式转换。explicit使用注意事项:explicit 关键字只能用于类内部的构造函数声明上,并且explicit 关键字作用于单个参数的构造函数,当然,多个参数时,除首个参数外的其它参数具有默认值除外。那么再看下面的例子(感谢网友提供):按照默认规定,只有一个参数的构造函数也定义了一个隐式转换,将该构造函数对应数据类型的数据转换为该类对象,如下面所示:class String {      String ( const char* p ); // 用C风格的字符串p作为初始化值     //…}String s1 = “hello”; //OK 隐式转换,等价于String s1 = String(“hello”);
但是有的时候可能会不需要这种隐式转换,如下:
[b]下面两种写法比较正常:String s2 ( 10 );   //OK 分配10个字节的空字符串String s3 = String ( 10 ); //OK 分配10个字节的空字符串
下面两种写法就比较疑惑了:
s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。为了避免这种错误的发生,我们可以声明显示的转换,使用explicit 关键字:class String {       explicit String ( int n ); //本意是预先分配n个字节给字符串       String ( const char* p ); // 用C风格的字符串p作为初始化值       //…}
加上explicit,就抑制了String ( int n )的隐式转换,下面两种写法仍然正确:
下面两种写法就不允许了:String s4 = 10; //编译不通过,不允许隐式的转换String s5 = ‘a’; //编译不通过,不允许隐式的转换
因此,某些时候,explicit 可以有效得防止构造函数的隐式转换带来的错误或者误解explicit只对构造函数起作用,用来抑制隐式转换。如:
当调用Function(2)的时候,2会隐式转换为A类型。这种情况常常不是程序员想要的结果,所以,要避免之,就可以这样写:   class   A{             explicit   A(int   a);     };   int   Function(A   a);
这样,当调用Function(2)的时候,编译器会给出错误信息(除非Function有个以int为参数的重载形式),这就避免了在程序员毫不知情的情况下出现错误。 四、现在来说复复制和赋值我们可以通过直接形式来初始化一个类:
当然也可以通过复制的方式进行:1)Sales_item temp2 = “I am a Chinese!”;//通过复制构造函数完成。生成一个临时对象,再复制到正在创建的对象。
或者:
2)Sales_item tmp(temp2);
总之我们需要明白,复制构造函数就是:
Sales_item(const Sales_itemp& it);
只要是实现了在初始化时,通过对象来赋值即是调用的复制构造函数。

而赋值则是:
Sales_item temp3; //调用相应的无参数的构造函数。temp3 = temp1; //调用operator完成赋值。 一般来说需要复制构造函数也需要operator=。应当将二者看为一个整体。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: