4、如何使用const? (1)修饰一般常量 一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。 例如: int const x=2; 或 const int x=2;
(2)修饰常数组 定义或说明一个常数组可采用如下格式: int const a[5]={1, 2, 3, 4, 5}; const int a[5]={1, 2, 3, 4, 5};
(3)修饰常对象 常对象是指对象常量,定义格式如下: class A; const A a; A const a; 定义常对象时,同样要进行初始化,并且该对象不能再被更新,修饰符const可以放在类名后面,也可以放在类名前面。
(4)修饰常指针 const int *A; //const修饰指向的对象,A可变,A指向的对象不可变 int const *A; //const修饰指向的对象,A可变,A指向的对象不可变 int *const A; //const修饰指针A, A不可变,A指向的对象可变 const int *const A;//指针A和A指向的对象都不可变
(2)位元const V.S. 抽象const? 对于关键字const的解释有好几种方式,最常见的就是位元const 和 抽象const。下面我们看一个例子: class A {public: ...... A f(const A& a); ...... }; 如果采用抽象const进行解释,那就是f函数不会去改变所引用对象的抽象值,如果采用位元const进行解释,那就成了f函数不会去改变所引用对象的任何位元。 我们可以看到位元解释正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。为什么这样呢?因为使用位元const有2个好处:最大的好处是可以很容易地检测到违反位元const规定的事件:编译器只用去寻找有没有对数据成员的赋值就可以了。另外,如果我们采用了位元const,那么,对于一些比较简单的const对象,我们就可以把它安全的放入ROM中,对于一些程序而言,这无疑是一个很重要的优化方式。(关于优化处理,我们到时候专门进行讨论) 当然,位元const也有缺点,要不然,抽象const也就没有产生的必要了。首先,位元const的抽象性比抽象const的级别更低!实际上,大家都知道,一个库接口的抽象性级别越低,使用这个库就越困难。其次,使用位元const的库接口会暴露库的一些实现细节,而这往往会带来一些负面效应。所以,在库接口和程序实现细节上,我们都应该采用抽象const。有时,我们可能希望对const做出一些其它的解释,那么,就要注意了,目前,大多数对const的解释都是类型不安全的,这里我们就不举例子了,你可以自己考虑一下,总之,我们尽量避免对const的重新解释。
(3)放在类内部的常量有什么限制? 看看下面这个例子: class A { private: const int c3 = 7; // ??? static int c4 = 7; // ??? static const float c5 = 7; // ??? ...... }; 你认为上面的3句对吗?呵呵,都不对!使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。这显然是一个很严重的限制!那么,我们的标准委员会为什么做这样的规定呢?一般来说,类在一个头文件中被声明,而头文件被包含到许多互相调用的单元去。但是,为了避免复杂的编译器规则,C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话,这种规则就被破坏了。
(4)如何初始化类内部的常量? 一种方法就是static 和 const 并用,在内部初始化,如上面的例子; 另一个很常见的方法就是初始化列表: class A { public: A(int i=0):test(i) {} private: const int i; }; 还有一种方式就是在外部初始化,例如: class A { public: A() {} private: static const int i;//注意必须是静态的! }; const int A::i=3;
(5)常量与数组的组合有什么特殊吗? 我们给出下面的代码: const int size[3]={10,20,50}; int array[size[2]]; 有什么问题吗?对了,编译通不过!为什么呢? Const可以用于集合,但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const意味着“不能改变的一块存储”。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。自然,作为数组的大小就不行了:) 你再看看下面的例子: class A { public: A(int i=0):test[2]({1,2}) {}//你认为行吗? private: const int test[2]; }; vc6下编译通不过,为什么呢?关于这个问题,前些时间,njboy问我是怎么回事?我反问他:“你认为呢?”他想了想,给出了一下解释,大家可以看看:我们知道编译器堆初始化列表的操作是在构造函数之内,显式调用可用代码之前,初始化的次序依据数据声明的次序。初始化时机应该没有什么问题,那么就只有是编译器对数组做了什么手脚!其实做什么手脚,我也不知道,我只好对他进行猜测:编译器搜索到test发现是一个非静态的数组,于是,为他分配内存空间,这里需要注意了,它应该是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],这就导致数组的初始化实际上是赋值!然而,常量不允许赋值,所以无法通过。呵呵,看了这一段冠冕堂皇的话,真让我笑死了!njboy别怪我揭你短呀:)我对此的解释是这样的:C++标准有一个规定,不允许无序对象在类内部初始化,数组显然是一个无序的,所以这样的初始化是错误的!对于他,只能在类的外部进行初始化,如果想让它通过,只需要声明为静态的,然后初始化。这里我们看到,常量与数组的组合没有什么特殊!一切都是数组惹的祸!