简单来讲:const关键字修饰的变量具备常属性。 即它所修饰的变量不能被修改。html
1 const int a = 10; 2 int const b = 20;
这两种写法是等价的,都是表示变量的值不能被改变,须要注意的是,用const修饰变量时,必定要给变量初始化,不然以后就不能再进行赋值了,并且编译器也不容许不赋初值的写法:安全
在C++中不赋初值的表达一写出来,编译器即报错,且编译不经过。函数
在C中不赋初值的表达写出来时不报错,编译时只有警告,编译能够经过。而当你真正给它赋值时才会报错,那么没有初值也不能被赋值这样的变量有什么用哪?post
1 const chsr* p = "qwerty"; //const用于修饰常量静态字符串
若是没有const的修饰,咱们可能会在后面有意无心的写p[4]=’x’这样的语句,这样会致使对只读内存区域的赋值,而后程序会马上异常终止。有了const,这个错误就能在程序被编译的时候就当即检查出来,这就是const的好处。让逻辑错误在编译期被发现。(这个特性在C/C++中相同)spa
常量指针是指针所指向的内容是常量,不可被修改。debug
1 const int * n = &a; 2 int const * n = &a;
上面两种写法也是等价的,性质以下:3d
1)常量指针经过不能这个指针改变变量的值,可是能够经过其余的引用来改变变量的值的。指针
1 const int *n = &a; 2 *n = b;
上面的写法报错调试
1 int c = 3; 2 const int *n = &a; 3 a = 10; 4 a = c;
这样赋值是能够的。code
2)常量指针指向的值不能改变,可是指针自己能够改变,即常量指针能够指向其余的地址。
1 int a = 1; 2 int b = 2; 3 const int *n = &a; 4 n = &b;
指针常量是指指针自己是个常量,不能在指向其余的地址,写法以下:
1 int a = 1; 2 int b = 2; 3 int * const n = &a; 4 *n = b;
5 b = a;
而这么写是错误的
1 int a = 1; 2 int b = 2; 3 int c = 3; 4 int * const n = &a; 5 n = &b;
它们的区别在于const的位置,能够这样记忆:const在“*”前面时它修饰(*n),而*n是n所指向的变量,因此是常量指针,const在“*”后面时它修饰(n),使指针变为常量,因此是指针常量。
指向常量的常指针
1 const int * const p= &a; 2 int const * const p= &a;
指针指向的位置不能改变而且也不能经过这个指针改变变量的值,可是依然能够经过变量赋值,或其余的普通指针改变变量的值。
(这种用法在C和C++中是相同的。)
1 int a = 1; 2 int const &a = b; 3 const int &a = b;
两种定义形式在本质上是同样的
根据const修饰指针的特性,const修饰函数的参数也是分为三种状况
1 void StrCopy(char *strdes, const char *strsrc);//防止修改指针指向的内容
其中 strsrc是输入参数,strdes是输出参数。给 strsrc 加上 const 修饰后,若是函数体内的语句试图改动 sresrc 的内容,编译器将指出错误。
1 void swap ( int * const p1 , int * const p2 ) //防止修改指针指向的地址
指针p1和指针p2指向的地址都不能修改。
1 void test ( const int * const p1 , const int * const p2 ) //以上两种的结合
另外当参数为引用时
1 void function(const Class& Var); //引用参数在函数内不能够改变 2 void function(const TYPE& Var); //引用参数在函数内为常量不可变
(这样的一个const引用传递和最普通的函数按值传递的效果是如出一辙的,他禁止对引用的对象的一切修改,惟一不一样的是按值传递会先创建一个类对象的副本, 而后传递过去,而它直接传递地址,因此这种传递比按值传递更有效.另外只有引用的const传递能够传递一个临时对象,由于临时对象都是const属性, 且是不可见的,他短期存在一个局部域中,因此不能使用指针,只有引用的const传递可以捕捉到这个家伙。)
若是给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
1 const int * fun2() //调用时 const int *pValue = fun2(); 2 //咱们能够把fun2()看做成一个变量,即指针内容不可变。 3 c.int* const fun3() //调用时 int * const pValue = fun2(); 4 //咱们能够把fun2()看做成一个变量,即指针自己不可变。
const int fun1() //这个其实无心义,由于参数返回自己就是赋值。
(1)用const修饰的类成员变量,只能在类的构造函数初始化列表中赋值,不能在类构造函数体内赋值。
1 class A 2 { 3 public: 4 A(int x) : a(x) // 正确 5 { 6 //a = x; // 错误 7 } 8 private: 9 const int a; 10 };
用const修饰的类成员函数,在该函数体内不能改变该类对象的任何成员变量, 也不能调用类中任何非const成员函数。通常写在函数的最后来修饰。
1 class A 2 { 3 public: 4 int& getValue() const 5 { 6 // a = 10; // 错误 7 return a; 8 } 9 private: 10 int a; // 非const成员变量 11 };
用const修饰的类对象表示该对象为常量对象,该对象内的任何成员变量都不能被修改。对于对象指针和对象引用也是同样。
所以不能调用该对象的任何非const成员函数,由于对非const成员函数的调用会有修改为员变量的企图。
1 class A 2 { 3 public: 4 void funcA() {} 5 void funcB() const {} 6 }; 7 int main 8 { 9 const A a; 10 a.funcB(); // 正确 11 a.funcA(); // X 12 13 const A* b = new A(); 14 b->funcB(); // 正确 15 b->funcA(); // X 16 }
(4)在类内重载成员函数
1 class A 2 { 3 public: 4 void func() {} 5 void func() const {} // 重载 6 };
另外,const数据成员只在某个对象生存期内是常量,而对整个类而言是可变的,由于类能够建立多个对象,不一样对象的const数据成员值能够不一样。
class A { public: A(int size) : _size(size) // 正确 {} private: const int _size; }; A a(10); //对象a的_size值为10 A b(20); //对象b的_size值为20
那么,怎样才能创建在整个类中都恒定的常量呢?用枚举常量。
class A { public: enum{SIZE1 = 10, SIZE2 = 20};//枚举常量 private: int arr1[SIZE1]; int arr2[SIZE2]; };
枚举常量不会占用对象的存储空间,它们在编译时被所有求值。但缺点是隐含数据类型是只能整数,最大值有限,且不能表示浮点数。
全局变量的做用域是整个文件,咱们应该尽可能避免使用全局变量,觉得一旦有一个函数改变了全局变量的值,它也会影响到其余引用这个变量的函数,致使除了bug后很难发现,若是必定要用全局变量,咱们应该尽可能的使用const修饰符进行修饰,这样方式没必要要的觉得修改,使用的方法与局部变量是相同的。
const常量有数据类型,而宏常量没有数据类型。编译器能够对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,而且在字符替换时可能会产生意料不到的错误(边际效应)。
1 //例子: 2 void f(const int i) { .........} //对传入的参数进行类型检查,不匹配进行提示
const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define同样给出的是当即数,因此,const定义的常量在程序运行过程当中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。
1 #define PI 3.14159 //常量宏 2 const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 3 ...... 4 double i=Pi; //此时为Pi分配内存,之后再也不分配! 5 double I=PI; //编译期间进行宏替换,分配内存 6 double j=Pi; //没有内存分配 7 double J=PI; //再进行宏替换,又一次分配内存!
宏定义是一个“编译时”概念,在预处理阶段展开,不能对宏定义进行调试,生命周期结束于编译时期。const常量是一个“运行时”概念,在程序运行时使用,相似于一个只读数据。
编译器一般不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操做,使得它的效率也很高
防止意外的修改,加强程序的健壮性。
1 void f(const int i) { i=10;//error! } //若是在函数体内修改了i,编译器就会报错 2
1 class A 2 { 3 ...... 4 void f(int i) {......} //一个函数 5 void f(int i) const {......} //上一个函数的重载 6 ...... 7 };
1 void f1 () 2 { 3 #define N 12 4 const int n 12; 5 } 6 void f2 () 7 { 8 cout<<N <<endl; //正确,N已经定义过,不受定义域限制 9 cout<<n <<endl; //错误,n定义域只在f1函数中。若想在f2中使用需定义为全局的 10 }
宏定义不能做为参数传递给函数;const常量能够在函数的参数列表中出现。
const_cast运算符用来修改类型的const或volatile属性。
(1)常量指针被转化成很是量的指针,而且仍然指向原来的对象;
(2)常量引用被转换成很是量的引用,而且仍然指向原来的对象。
1 void func() 2 { 3 const int a = 10; 4 int* p = const_cast<int*> (&a); 5 *p = 20; 6 std::cout<<*p; // 20 7 std::cout<<a; // 10 8 }
注:C++中使用const 常量而不使用宏常量,即const 常量彻底取代宏常量。
ps:高质量C/C++第5章、第11章。