C++ 描述字之const (1)

        const可能是C++最受欢迎的描述字了。他提供了更好的安全语义。很多企业在面试的时候经常抛出诸如“请谈谈const的用法”之类的题目。

const描述字允许我们提供一个语义约束:声称其被描述的对象具有“不该被改动”的性质,令人感到兴奋的是,编译器会强制实施这项约束。同时,他也允许告诉编译器和其他程序员,某些值应该保持不变。const多才多艺,在许多语境下可以用到他:变量、指针、对象、参数、返回值甚至函数本身。现在把constC/C++中的用法作一个总结。

1C中的const

       C中的const主要用于描述变量、指针以及函数声明,我们一步步来。

1.1 变量

以下是const修饰变量时的常用用法:

cpp 代码
 
  1. const int SIZE = 1024;   
  2. const double PI = 3.1415926;  

<o:p></o:p>

       他为程序引入一个常量,以防止出现太多的“Magic Number”,同时也是为了能够方便的维护。在C中,我们也可以用#define来达到同样的目的:

cpp 代码
 
  1. #define SIZE 1024   
  2. #define PI 3.1415926  

不过其中存在一些微妙的区别,以至于我们强烈推荐使用const而不是#define来引入一个常量。原因如下:

       #define语句是一个预处理命令,他在编译器对源代码进行编译之前,就把所有的“名称”用“值”所替换了。所以,我们的程序中,其实并没有包括SIZEPI这样的字,而是10243.1415926,因此,也更加不会有SIZEPI的类型信息。所以,概括来说,用#define来引入常量有两点缺陷:

1.  我们所使用的名称没有被引入符号表(symbol table);

2.  他抛弃了名称的类型信息;

第一点会在调试、维护阶段添不少麻烦。可以说,用#define仅仅屏蔽了“开发期”的Magic Number。而第二点,部分的放弃了C++蕴含的严格的类型检查机制所带来的种种益处。综合考虑,我们强烈推荐在C/C++中用const替代#define

此外,还有一个传说中的“the enum hack”,用一个无名的enum来引入常量。其用法是:

<o:p> </o:p>
cpp 代码
 
  1. enum { SIZE = 1024, MAX = 4096 };  

    从某方面来讲,enum#define有些相似,有时候这是必须的。取enum或者#define常量的地址是不合法行为,但取const常量的地址就是合法的。如果你不想别人取得你常量的指针或者引用,同时你也不希望与#define带来的缺点进行某种“妥协”,那么enum会是一个很好的选择。我们应该认识enum hack的动力还来自于,很多现存的C++代码都大量的使用了这个技巧,我们应该认识他们。

1.2 指针

    指针从某方面来说也是一种变量,所以上一小节所述同样也适用。不过还是有两点要注意:

    首先指针蕴含两方面内容:指针本身以及所指对象,可以针对这两部分分别使用const描述字:

cpp 代码
 
  1. char greeting[] = “hello”;   
  2. char * p = greeting;              //non-const ptr, non-const data;   
  3. const char * p = greeting;        //non-const ptr, const data;   
  4. char * const p = greeting;        //const ptr, non-const data;   
  5. const char * const p = greeting;  //const ptr, const data;  

    同时,还应该注意到,const char *char const *并没有什么不同,个人习惯罢了。虽然变化多端,却并不高深莫测:星号右边的[1]const描述指针本身;星号左边的const描述所指对象。

    第二点,由于常量指针经常用于引入常量字符串,所以我们在头文件中经常看见上述用法。不过,这并不是一个好想法。如果我们的想法确实是引入字符串常量,请尝试使用:

<o:p> </o:p>
cpp 代码

 
  1. const std::string GREETING(“hello”);  
1.3 函数声明

    const最具威力的用法就是在函数声明时的应用。

    关于const参数,没有什么特别新颖的观念。唯一需要注意的就是,尽量在不会被修改的参数前面加上const。只不过多打6个字母,却可以在以后很长时间内,省下那些恼人的错误。这也是C++中为数不多的,很“便宜”的好处,尽量的使用吧!如果还想深入一点,就需要注意以下问题:由于种种原因[2],利用const来区分“需要改动”和“不需要改动”的参数,并不是一种“万能公式”。幸好,经过我们精心修改以后的“公式”,在大多数场合都是适用的:

1.  如果参数是内置类型,请使用Type *Type来加以区分;

2.  如果参数是用户自定类型,并且具有较复杂的构造-析构函数,请使用Type &  const Type &来加以区分;

关于返回值,也差不多这样:返回一个内置类型的时候,按值返回就好了;当返回一个用户自定义类型时,最好采用const Type。原因是:良好的自定义类型应该避免无端的与内置类型不兼容。而改动内置类型返回值是不合法的,所以,我们也最好为我们自己的类型提供这个“安全锁”。这样,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。

2.面向对象中的const

       这一章我们从前面一章的内容过渡到成员函数上,然后最后讨论以下有关bit constlogical const的相关概念。

2.1 过渡

       第一章的内容大部分都可以适用于这一章。但有两点需要注意:

       首先,关于常量初始化的问题。因为const变量一旦定义后即不可更改,所以在global域或者namespace[3]中使用const常量,通常(也必须)把声明和定义放在一起。在面向对象部分则不能这么干。因为类定义不可以对数据成员进行初始化(虽然有些编译器允许),所以就要另外找个地方对const数据成员进行初始化。构造函数?不。按照构造函数的语义,他是在已经“创造”了一个对象后,才进行的一些初始化操作。而按照用户自定义类中的常量语义,他应该在此之前就存在。所以应该(事实上也必须)在成员初始化列表中对数据常量进行定义。

cpp 代码
 
  1. class A {   
  2.   const int MAX_SIZE;   
  3.   int* arr;   
  4. public:   
  5.   A(void)    
  6.   : MAX_SIZE(1024),    
  7.     arr(new int [MAX_SIZE] ) {   
  8.     for(int i=0; i
  9.       arr[i] = i;   
  10.   }   
  11.   ~A() {}   
  12.   void print(void) {   
  13.   for(int i=0; i
  14.     std::cout << arr[i] << std::endl;   
  15.   }   
  16. };  

     这样做有着清晰却复杂的语法。所以另外一种妥协方式就是“the enum hack”:

 cpp 代码

 

 

 
  1. class A {   
  2.   enum { MAX_SIZE = 1024 };   
  3.   int* arr;   
  4. public:   
  5.   A(void)    
  6.   : arr(new int [MAX_SIZE] ) {   
  7.     for(int i=0; i
  8.       arr[i] = i;   
  9.   }
  10. …   
  11. };   

    简洁明了,这也是为什么现有代码大量使用这种技术的原因。不过他也有个显见的缺点:不能引入除整型以外其他类型的常量。

2.2 const成员函数

     const还可以修饰类的成员函数。

像函数参数一样,类的成员函数也可以划分两类:修改数据成员和不修改数据成员。当我们声明一个成员函数的时候,编译器会默认其为修改数据成员的函数,我们称这种为non-const成员函数(non-const member function),而与之对应的称为const成员函数(const member function)。同时,C++规定,对于用const声明的类对象如:

<o:p> </o:p>
cpp 代码
 
  1. const A test_A;  

    不能调用其non-const成员函数,而只能调用const成员函数。也就是说如果我们不采取一些措施来通知编译器,那么按默认规则,test_A将不能调用任何成员函数(构造和析构函数除外)。

cpp 代码
 
  1. const A test_A;   
  2. //Error!   
  3. //const object can not call non-const member function;   
  4. test_A.print();   

     改善C++程序效率的一个根本办法是以const Type &来传递对象,而这技术可行的前提是可以调用const对象的const成员函数。所以在设计类的成员函数的时候,我们就一定要明确区分那些是const成员函数,哪些是non-const成员函数,并且在const成员函数的参数列表和分号之间,使用const描述字,例如class A中:

 cpp 代码

 
  1. class A {   
  2.   …   
  3.   void print(voidconst {   
  4.     for(int i=0; i
  5.       std::cout << arr[i] << std::endl;   
  6.   }   
  7.   
  8. };   
  9.   
  10. int main(void) {   
  11.   const A test_A;   
  12.   test_A.print();  //OK!   
  13.   return 0;   
  14. }  

    同时注意,如果成员函数的常量性不同可以被重载。例如:

 cpp 代码

 
  1. class A {   
  2.   …   
  3.   void print(void){   
  4.   for(int i=0; i
  5.     std::cout << arr[i] << std::endl;   
  6.   }   
  7.   void print(voidconst {   
  8.   for(int i=0; i
  9.     std::cout << arr[i] << std::endl;   
  10.   }   
  11. };   
  12.   
  13. int main(void) {   
  14.   const A const_A;   
  15.   A non_const_A;   
  16.   const_A.print();      //const version print();   
  17.   non_const_A.print();  //non-const version print();   
  18.     
  19.   return 0;   
  20. }  



 

[1] 距离指针变量最近的,按照英语定语修饰习惯,应该是描述最直接的、关键的性质;较远的描述间接的、次要的性质。

[2] 为了不离题太远,这里就不具体说明了。

[3] global域或者namespace域:函数块、类块以外域。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值