[收集]关于C/C++中的const

作者在 2007-12-21 20:43:02 发布以下内容

要用好C++,就必须用好const

                                                             -----我说的

============================================================================

c/c++中const用法总结

const类型定义:指明变量或对象的值是不能被更新,引入目的是为了取代预编译指令

**************常量必须被初始化*************************

const的作用
   (1)可以定义const常量         例如:
             const int Max=100;
             int Array[Max];       
   (2)便于进行类型检查            例如:
             void f(const int i) { .........}
        编译器就会知道i是一个常量,不允许修改;
   (3)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
        还是上面的例子,如果在函数体内修改了i,编译器就会报错;
        例如:
             void f(const int i) { i=10;//error! }
    (5) 为函数重载提供了一个参考。
         class A
         {
           ......
           void f(int i)       {......} file://一个函数
           void f(int i) const {......} file://上一个函数的重载
            ......
          };
     (6) 可以节省空间,避免不必要的内存分配。
         例如:
              #define PI 3.14159         file://常量宏
              const doulbe  Pi=3.14159;  file://此时并未将Pi放入ROM中
              ......
              double i=Pi;               file://此时为Pi分配内存,以后不再分配!
              double I=PI;               file://编译期间进行宏替换,分配内存
              double j=Pi;               file://没有内存分配
              double J=PI;               file://再进行宏替换,又一次分配内存!
         const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
     (7) 提高了效率。
           编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

使用const
   (1)修饰一般常量,常数组,常对象
   修饰符const可以用在类型说明符前,也可以用在类型说明符后。      例如:  
           int const x=2;  或  const int x=2;

       int const a[5]={1, 2, 3, 4, 5};    或  const int a[5]={1, 2, 3, 4, 5};

           class A;      const A a;  或     A const a;
     
   (2)修饰指针
        const int *A;   或  int const *A; //const修饰指向的对象,A可变,A指向的对象不可变
        int *const A;              //const修饰指针A,     A不可变,A指向的对象可变
        const int *const A;      //指针A和A指向的对象都不可变
   (3)修饰引用
        const double & v;      该引用所引用的对象不能被更新
 (4)修饰函数的返回值:
        const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:
            const int Fun1();
            const MyClass Fun2();
   (5)修饰类的成员函数:
        const修饰符也可以修饰类的成员函数,格式如下:
            class ClassName
     {
             public:
                  int Fun() const;
                    .....
             };
        这样,在调用函数Fun时就不能修改类里面的数据
    (6)在另一连接文件中引用const常量
         extern const int i;     //正确的引用
         extern const int j=10;  //错误!常量不可以被再次赋值
   


*******************放在类内部的常量有什么限制?
  
        class A
        {
         private:
           const int c3 = 7;               // err
           static int c4 = 7;               // err
           static const float c5 = 7;  // err
          ......
  };
 初始化类内部的常量
        1 初始化列表:
         class A
         {
          public:
                A(int i=0):test(i) {}
          private:
                const int i;
          };
         2 外部初始化,例如:
         class A
         {
          public:
                A() {}
          private:
                static const int i;  
          };
          const int A::i=3;

 

======================================================================

1.1. 简述
  const关键字的最初动机是取代预处理器 #define 进行值替代。用C语言进行程序设计时,预处理器可以不受限制地建立宏并用它来替代值。因为预处理器只做文本替代,它既没有类型检查思想,也没有类型检查工具,所以预处理器的值替代会产生一些微小而且往往是很难察觉而让程序员郁闷很久的问题,这些问题在C + +中可通过使用c o n s t而避免。
之后const又被用于指针、函数变量、返回类型、类对象及其成员函数。所有这些用法都稍有区别,但它们在概念上是一致的,都是表示"不变".
1.2. 使用在对象名称上
作用:
 1. 把一个对象转换成一个常量,防止错误的修改此对象。
2.代替魔数(magic number),增强可读性和是可维护性。
3.代替define宏,增强类型安全性
因为常量对象在定义后就不能被修改所以它必须被初始化未初始化的常量定义将导致编译错误。
1.3. 使用在函数参数上
目的: 利用编译器来保证不正确的修改参数对象

注意,如果const用于以值传递的对象时,没有意义。
所以,只对按引用传递的参数和参数为指针时候有意义。表示,不能在函数体内改变参数的值。如果参数的类型是用户定义的类型,则表示不允许改变类中的任何成员的值。
示例:
class CA{
public:
  CA( int i ) : mi( i ) {};
  int mi;
};

void fun( const CA& oa){
  oa.mi = 20;
}

函数fun的参数oa被声明为const,而在函数体内又要改变oa的mi,所以,编译器将提示错误。

1.4. 使用在返回值
1.4.1. 返回内部数据类型的值
  对于内部数据类型来说,返回值是否是常量并没有关系,所以返回一个内部数据类型的值时,应该去掉c o n s t从而使用户程序员不混淆。
1.4.2. 返回用户定义类型的对象的值
  如果以常量返回用户定义类型的一个对象的值,这意味着返回值不能被修改。注意,只是表示返回值本身不会被修改。通常意味着该函数不能作为左值

class CA{
public:
  int mi;
};

CA  fun(){
    return CA();
}

const CA  fun2(){
    return CA();
}

int main(){
  CA oa;

  fun() = oa ;
  fun2() = oa  ;
}

编译器将提示fun2() = oa  这句错误,因为它企图改变fun2()返回的值。

前面特别提示说只是表示返回值本身不会被修改,怎么理解这句话呢?看下面的例子(基于上面的例子):
在main函数中增加下面语句:
CA oa3;
oa3 = fun2();
oa3.mi = 10;
这2句并不会导致编译错误。这是因为oa3并不是代表fun2()的返回值,而只是把fun2()的返回值赋值给oa3。他们代表的是2个对象。

1.4.3. 返回一个地址
  如果以常量返回地址,c o n s t将保证该地址内容不会被改变。这也是const修饰返回值中最常用的。
看看c++标准库中的string类的成员函数c_str()的定义。
const char * c_str() const;
返回值被定义为const char *。
这样,只能通过const char* 变量来接收c_str(),从而保证不会改变string的值。

1.5. 使用在函数声明最后
编译器用来区分不安全与安全的成员函数,即区分试图修改类对象与不试图修改类对象的函数).
产生的原因:
前面说过,用const修饰的函数参数,在函数体内是不允许改变这个参数的,如果这个函数需要调用这参数对象的成员函数,编译器如何知道这个成员函数会不会改变这个对象呢?把const放在成员函数声明的最后就可以做到这点。
例如:
class CA{
public:
  CA( int i ) : mi( i ) {};
  void mfun(void) const {};
  int mi;
};

void fun( const CA& oa){
  oa.mfun();
}
在函数fun中调用了参数对象oa的成员函数mfun,因为mfun被声明为const的,所以编译器知道mfun不会改变oa对象。
如果去掉mfun声明的const关键字,编译器将提示错误。
注意:
关键字const必须用同样的方式出现在函数声明和函数定义里
const 函数不能调用类中的非const 函数
1.6. 类常量和类对象常量
1.6.1. 类常量
典型的例子是在一个类里建立一个数组,并用c o n s t代替# d e f i n e建立数组大小以及用于有关数组的计算。并把数组大小一直隐藏在类里,这样,如果用s i z e表示数组大小,就可以把s i z e这个名字用在另一个类里而不发生冲突。
但这样做行吗?
class CA{
public:
  int mi;
  const int size = 100 ;
  char sz[ size ];
};
 
不行.
把一个c o n s t放在类里, c o n s t恢复它在C中的一部分意思。它在每个类对象里分配存储并代表一个值,这个值一旦被初始化以后就不能改变。在一个类里使用c o n s t的意思是"在这个对象寿命期内,这是一个常量"。然而,对这个常量来讲,每个不同的对象可以含一个不同的值。这样,在一个类里建立一个c o n s t时,不能给它初值。这个初始化工作必须发生在构造函数里,并且,要在构造函数的某个特别的地方。
 
解决方法1:使用enum
enum是不会占用对象中的存储空间的,枚举常量在编译时被全部求值。
class CA{
public:
  int mi;
  enum{ size = 100 } ;
  char sz[ size ];
};
 

解决方法2:使用static const
  enum只能用来定义int型常量,static const可以定义所有内置类型常量。
class CA{
public:
  int mi;
  static const int size;
  char sz[ size ];
};

const int CA::size = 100 ;
注意: 有些编译器可能还不支持这一技巧(vc不支持,g++支持)

1.6.2. 类对象常量
 类对象常量属于具体的对象,每个对象的常量值可以不一样。
  类对象常量只能在initiallist中初始化

class CA{
public:
  CA( int sz ) : size( sz ) {};
  const int size;
};

1.7. 注意修饰指针时
  这是一个比较容易混淆的地方。在C++ Primer,think in c++, effective c++中都做了特别的说明。不过,还是effective c++说的好,用一句话进行了概括,很爽。
"Basically, you mentally draw a vertical line through the asterisk of a pointer declaration, and if the word const appears to the left of the line, what's pointed to is constant; if the word const appears to the right of the line, the pointer itself is constant; if const appears on both sides of the line, both are constant."

考虑下面的例子:
int i = 10 ;

const int *p1 = &i ;
int const *p2 = &i;
int *const p3 = &i ;
const int *const p4 = &i ;

p1,p2,p3,p4的声明和定义都是正确的。但是他们各代表什么意思呢?
用上面那句话就很容易得到答案。p1 和p2是一样的意思,think in c++建议采用p1的声明方式。

1.8. 注意默认的const
  有时候,你并不知道是在对常量进行操作,一个常见错误是:
 char* pstr = "fox";
 
 *pstr = 'a';
编译时候并不会出错,但运行的时候报内存错误。
原因是:pstr被初始化为一个常量指针,
而在第二行却改变了它的内容,所以运行到第二行的时候报错。
要改正这个错误,可以吧pstr申明为char数组:  char pstr[ 100 ] = "fox";

1.9. 个人对const的感受
  在对const已经理解之后,就可以在程序中使用const。
  使用Const的目的是加强程序的健壮性和合理性,同样的程序使用const和不使用const在功能上是没有什么区别的。
  仅仅要在某段程序中使用Const是很简单的。然而在整个应用程序中使用好Const,却是对设计的考验。
我曾经多次因为在程序设计时候未考虑周到而在后期写代码的时候不得不修改原来的程序的设计。举个简单得例子:
  有2个类CA和CB。

class CA
{
public:
 void funA(){};
};

class CB
{
public:
 void funB( const CA&  oa ) { oa. funA(); }
};

int main(int argc, char** argv)
{

    CA oa;
    CB ob;
   
    ob.funB( oa );
    system("PAUSE"); 
    return 0;
}

上面得代码编译通不过,因为funB得参数类型是 const CA,而在funB内部又调用了funA()。编译器不能断定funA()不会改变CA,所以提示错误。
如果把funA 申明未void funA() const {}; 就可以了。

对于2个类,2个函数可能好考虑一些,但是对于一个应用系统中得几十几百个类上千个函数,要考虑周全确实是对设计人员的一次考验。
  看看C++ STL,在const方面确实设计的很完美。

  Const使用最多的地方
1: 函数参数为类对象(而不是内置数据类型)的引用或者指针,且在函数中不允许或者不会改变此参数

 2:类的属性获取函数(一般以get开头)。
returntype getXXX(void) const;

在函数的返回值为引用类型或者指针类型时候,此类函数很多时候会使用2个const: const returntype getXXX(void) const; 这样可以保证不会修改对象。

C/C++ | 阅读 1667 次
文章评论,共0条
游客请输入验证码