分类: C/C++
2009-09-14 19:51:49
1、什么是const? 常类型是指使用类型修饰符const说明的类型,常类型的变量或对 2、为什么引入const? const 推出的初始目的,正是为了取代预编译指令,消除它的缺点 3、cons有什么主要的作用? (1)可以定义const常量,具有不可变性。 例如: const int Max=100; int Array[Max]; (2)便于进行类型检查,使编译器对处理内容有更多了解 例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改; (3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调 同宏定义一样,可以做到不变则已,一变都变!如(1)中 (4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错; 例如: void f(const int i) { i=10;//error! } (5) 为函数重载提供了一个参考。 class A { ...... void f(int i) {......} void f(int i) const {......} ...... }; (6) 可以节省空间,避免不必要的内存分配。 例如: #define PI 3.14159 const doulbe Pi=3.14159; ...... double i=Pi; double I=PI; double j=Pi; double J=PI; const定义常量从汇编的角度来看,只是给出了对应的内存地址 (7) 提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在 3、如何使用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; 定义常对象时,同样要进行初始化,并且该对象不能再被更新 (4)修饰常指针 const int *A; 修饰指向的对象,A可变,A指向的对象不可变 int const *A; 修饰指向的对象,A可变,A指向的对象不可变 int *const A; 修饰指针A, A不可变,A指向的对象可变 const int *const A; (5)修饰常引用 使用const修饰符也可以说明引用,被说明的引用为常引用 const double & v; (6)修饰函数的常参数 const修饰符也可以修饰函数的传递参数,格式如下: void Fun(const int Var); 告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无 (7)修饰函数的返回值: const修饰符也可以修饰函数的返回值,是返回值不可被改变 const int Fun1(); const MyClass Fun2(); (8)修饰类的成员函数: const修饰符也可以修饰类的成员函数,格式如下: class ClassName { public: int Fun() const; ..... }; 这样,在调用函数Fun时就不能修改类里面的数据 (9)在另一连接文件中引用const常量 extern const int i; extern const int j=10; 另外,还要注意,常量必须初始化! 例如: const int i=5; 4、几点值得讨论的地方: (1)const究竟意味着什么? 说了这么多,你认为const意味着什么?一种修饰符?接口抽象 也许都是,在Stroustup最初引入这个关键字时 (2)位元const V.S. 抽象const? 对于关键字const的解释有好几种方式,最常见的就是位元con class A { public: ...... A f(const A& a); ...... }; 如果采用抽象const进行解释,那就是f函数不会去改变所引用对 我们可以看到位元解释正是c++对const问题的定义 为什么这样呢?因为使用位元const有2个好处: 最大的好处是可以很容易地检测到违反位元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句对吗?呵呵,都不对!使用这种类内部的初始化语法 那么,我们的标准委员会为什么做这样的规定呢?一般来说 (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可以用于集合,但编译器不能把一个集合存放在它的符号表 你再看看下面的例子: class A { public: A(int i=0):test[2]({1,2}) {} private: const int test[2]; }; vc6下编译通不过,为什么呢? 关于这个问题,前些时间,njboy问我是怎么回事?我反问他: 呵呵,看了这一段冠冕堂皇的话,真让我笑死了!njboy别怪我揭 这里我们看到,常量与数组的组合没有什么特殊!一切都是数组惹的祸 (6)this指针是不是const类型的? this指针是一个很重要的概念,那该如何理解她呢 (7)const到底是不是一个重载的参考对象? 先看一下下面的例子: class A { ...... void f(int i) {......} void f(int i) const {......} ...... }; 上面是重载是没有问题的了,那么下面的呢? class A { ...... void f(int i) {......} void f(const int i) {......} file://?????/ ...... }; 这个是错误的,编译通不过。那么是不是说明内部参数的const不 class A { ...... void f(int& ) {......} void f(const int& ) {......} file://?????/ ...... }; 这个程序是正确的,看来上面的结论是错误的。为什么会这样呢 (8)什么情况下为const分配内存? 以下是我想到的可能情况,当然,有的编译器进行了优化 A、作为非静态的类成员时; B、用于集合时; C、被取地址时; D、在main函数体内部通过函数来获得值时; E、const的 class或struct有用户定义的构造函数、析构函数或基类时 F、当const的长度比计算机字长还长时; G、参数中的const; H、使用了extern时。 不知道还有没有其他情况,欢迎高手指点:) (9)临时变量到底是不是常量? 很多情况下,编译器必须建立临时对象。像其他任何对象一样 (10)与static搭配会不会有问题? 假设有一个类: class A { public: ...... static void f() const { ......} ...... }; 我们发现编译器会报错,因为在这种情况下static不能够与co 为什么呢?因为static没有this指针,但是const修饰 (11)如何修改常量? 有时候我们却不得不对类内的数据进行修改,但是我们的接口却被声明 1)标准用法:mutable class A { public: A(int i=0):test(i) { } void Setvalue(int i)const { test=i; } private: mutable int test; }; 2)强制转换:const_cast class A { public: A(int i=0):test(i) { } void Setvalue(int i)const { const_cast private: int test; }; 3)灵活的指针:int* class A { public: A(int i=0):test(i) { } void Setvalue(int i)const { *test=i; } private: int* test; }; 4)未定义的处理 class A { public: A(int i=0):test(i) { } void Setvalue(int i)const { int *p=(int*)&test; *p=i; }//这里处理! private: int test; }; 注意,这里虽然说可以这样修改,但结果是未定义的,避免使用! 5)内部处理:this指针 class A { public: A(int i=0):test(i) { } void Setvalue(int i)const { ((A*)this)->test=i; }//这里处理! private: int test; }; 6)最另类的处理:空间布局 class A { public: A(int i=0):test(i),c('a') { } private: char c; const int test; }; int main() { A a(3); A* pa=&a; char* p=(char*)pa; int* pi=(int*)(p+4);//利用边缘调整 *pi=5; return 0; } 虽然我给出了6中方法,但是我只是想说明如何更改 (12)最后我们来讨论一下常量对象的动态创建。 既然编译器可以动态初始化常量,就自然可以动态创建,例如: const int* pi=new const int(10); 这里要注意2点: 1)const对象必须被初始化!所以(10)是不能够少的。 2)new返回的指针必须是const类型的。 那么我们可不可以动态创建一个数组呢? 答案是否定的,因为new内置类型的数组,不能被初始化。 |