zoukankan      html  css  js  c++  java
  • 从Qt谈到C++(一):关键字explicit与隐式类型转换

    转载:果冻虾仁

    提出疑问

    当我们新建了一个Qt的widgets应用工程时。会自动生成一个框架,包含了几个文件。

    其中有个mainwindow.h的头文件。就是你要操纵的UI主界面了。我们看看其中的一段代码:

     1 class MainWindow : public QMainWindow  
     2 {  
     3     Q_OBJECT//一个宏,暂不考虑  
     4   
     5 public:  
     6     explicit MainWindow(QWidget *parent = 0);  
     7     ~MainWindow();  
     8   
     9 private:  
    10     Ui::MainWindow *ui;  
    11 };  

    这段代码定义了一个新的类MainWindow,继承自QMainWindow。我们可以看到在它的构造函数里,前面有一个关键字 explicit 。

    相信大家都对没有这个关键字的构造函数不陌生。那么这个 explicit 是起到什么作用的呢?

    explicit研究

     
    explicit是C++中的关键字,不是C语言中的。英文直译是“明确的”、“显式的”意思。
    出现这个关键字的原因,是在C++中有这样规定的基础上:当定义了只有一个参数的构造函数时,同时也定义了一种隐式的类型转换。先看类型转换。
     
    类型转换
     
    C/C++中,有很多类型转换。比如:
    1 double a = 12.34;  
    2 int b = (int)a;  

    我们都知道这时b的值是12. 在变量前面加括号包裹的类型,就能实现显式的类型转换。这种叫做强制类型转换。

    顺便值得一提的是,C++中还支持这种强制类型转换的例子:

    1 double a = 12.34;  
    2 int b = int(a);  

    除此之外,还有一种转换叫做 隐式类型转换。

    1 double a = 12.34;  
    2 int b = a;  

    同样的,b的值也是12.虽然没有显式的转换类型,但是编译器会帮你自动转换。同样的,不仅是基本数据类型,自己定义的类和对象之间也存在这种转换关系。

     隐式转换的场景

    等于号与构造函数

    比如你有一个类的对象A:
     1 class A  
     2 {  
     3 public:  
     4     A(int i)  
     5     {  
     6         a = i;  
     7     }  
     8     int getValue()  
     9     {  
    10         return a;  
    11     };  
    12 private:  
    13     int a;  
    14 };  

    你会发现,你在main函数中,使用下面的语句时是合法的:

    1 A a;  
    2 a  = 10;  
    之所以类A的对象可以直接使用整型通过等于号来初始化,是因为这一语句调用了默认的单参数构造函数,这种构造函数又称 类型转换构造函数
    其效果等价于A temp(10); a(temp);
    首先编译器执行A temp(10);在栈中创建了一个临时对象(假设叫做temp)。然后再调用对象a的拷贝初始化构造函数 a(temp) 给a初始化。然后临时对象temp销毁。这就是编译器做的隐式转换工作。你可以想到这样的隐式操作的结果和直接显示调用A a(10);的结果是一样的,但是隐式转换因为使用了拷贝构造函数所以在开销上会更高一些。当然这基本数据类型,或许不明显。如果一个复杂的对象,比如Qt的窗口。那么开销可想而知。
    注意当你使用A a = 10;时并不会产生中间的临时对象。而是直接把10作为参数传递给类型转换构造函数。
    又如当你使用如下语句会不通过:
    1 A a = "123";  

    因为没有参数为字符串的单参数构造函数。知道了这个,你修改一下就能通过了。

     1 class A  
     2 {  
     3 public:  
     4     A(int i)  
     5     {  
     6         a = i;  
     7     }  
     8     A(char * c)  
     9     {  
    10         a=c[0];  
    11     }  
    12     int getValue()  
    13     {  
    14         return a;  
    15     };  
    16 private:  
    17     int a;  
    18 };  

     函数调用

    我们再定义一个函数print 用来打印A对象的值。
    1 void print(A a)  
    2 {  
    3     cout<<a.getValue();  
    4 }; 

    在main函数中:

    1 void main()  
    2 {  
    3     print(10);  
    4 }  

    这样是可以编译运行的。虽然我们并没有创建一个类A的对象来传给print 函数。但是编译器默认会调用类A的单参数构造函数,创建出一个类A的对象出来。

     加上explicit

    上面可以看出编译器会为你做一些,隐式的类型转换工作。这或许会让你感到方便,但是有时候却会带来麻烦。我们来做一个假设:
    上面这个类A,只接受整型和字符串型。所以你想传递一个字符串 “2” 作为参数来构造一个新的对象。比如 A b = “2”; 然而,你却写错了,双引号写成了单引号变成了 A b = ‘2’; 然而这样并不会报错。编译器 会把 字符 ‘2’ 转型成 整型 也就是它的ascll码—— 50。为了避免这样我们不希望的隐式转换,我们可以加上explicit 关键字。
    1 public:  
    2     explicit A(int i)  
    3     {  
    4         a=i;  
    5     }  
    这样就能避免隐式的类型转换了,当你误写成单引号的时候,就会报错。这样就只允许显示的调用单参数构造函数了。如 A a(10); A b("123"); 
    不仅如此,在加上explicit之后,print函数也会报错了。因为编译器不会主动调用explicit标识的构造器。这就需要你自己显示的来调用了:
    1 print(A(10));  
    当然了,是否应该禁止隐式转换是没有定论的,没有一种放之四海皆准的标准,具体看你的情景需要了。

    一般而言,显示调用构造器,能避免一些麻烦,让程序员手动来管理。很多人说C++难,因为很多东西对于程序员来说不是透明的,比如内存释放什么的,这个显式调用也是需要程序员自己动手的。然而我感觉这正是C++的魅力所在,C++给了程序员几乎等同于上帝的权力,所有一切都能自己掌控,还比如运算符重载的权力,甚至像Qt这样可以自定义slot和signal关键字(其实是宏),这在其他高级语言里是不可想象的。当然了,语言这东西,是仁者见仁智者见智的。没必要争论优劣。我一直认为的是:没有最优秀的语言,只有最合适的语言。编程语言本身没有优劣之分,但是不同程序员对于不同语言确有好恶之别。

    顺便一提

    explicit关键字只用在类内部的声明中。在外部的实现部分不需要使用。

     1 #include<iostream>  
     2 using namespace std;  
     3 class A  
     4 {  
     5 public:  
     6     explicit A(int i);  
     7     A(char * c)  
     8     {  
     9         a=c[0];  
    10     }  
    11     int getValue()  
    12     {  
    13         return a;  
    14     };  
    15 private:  
    16     int a;  
    17 };  
    18 A::A(int i)//无需再指明explicit  
    19 {  
    20     a=i;  
    21 }  
    22 void print(A a)  
    23 {  
    24     cout<<a.getValue();  
    25 };  
    26 void main()  
    27 {  
    28     print(A(10));  
    29 }  
  • 相关阅读:
    libevent(十)bufferevent 2
    libevent(九)bufferevent
    maven本地库更新失败
    IDEA常用快捷键
    ELASTIC SEARCH 安装
    Hbase建模选择
    ElasticSearch关键概念
    Nginx+tomcat 负载均衡
    MapReduce (MRV1)设计理念与基本架构
    Kafka安装验证及其注意
  • 原文地址:https://www.cnblogs.com/fuqia/p/8877130.html
Copyright © 2011-2022 走看看