一、对变量的修饰
在c++中,如果我们希望定义一个值不会被改变的变量,那么可以用关键字const对它进行修饰,被修饰后的变量其作用相当于一个常量
1 //这两种方式等价 2 语法1:const 类型名 变量名 3 语法2:类型名 const 变量名
特别注意:
1.const对象一旦创建,其值就不能再被改变。因此const对象必须初始化
1 const int i=get_size();//正确:运行时初始化 2 const int j=42; //正确:编译时初始化 3 const int k; //错误:k是一个未经初始化的常量
2.在const对象上只能执行不改变其内容的操作
3.默认状态下,const对象被设定为仅在文件内有效。也就是说当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量(如:file1中的const变量name和file2中的const变量name是两个不同的变量)
[注]:如果希望在多个文件之间共享const对象,则对于const变量不管是声明还是定义都必须添加extern关键字
1 //文件file1中 2 extern const int bufSize=50;//定义并初始化一个常量,该常量能被其他文件访问 3 //文件file2中 4 extern const int bufSize; //与file1中定义的bufSize是同一个常量,这里extern的作用是指明bufSize并非本文件所独有,它的定义将在别处出现
二、对指针的修饰
A、底层const——指向常量的指针(常量指针)
指向常量的指针,顾名思义即指针指向的对象是一个常量,不能通过该指针对其所指向的对象进行修改
语法1:const 类型名 *变量名(推荐使用)
语法2:类型名 const *变量名
特别注意:
(1) 要想存放常量的地址,只能使用指向常量的指针
1 const double pi=3.14;//pi是一个常量,它的值不能被改变 2 double *pr=π//错误:ptr是一个普通的指针,不能用它指向一个常量 3 const double *cpr=π//正确:cpr是一个指向常量的指针 4 *cpr=42; //错误:不能通过指向常量的指针对其所指的对象进行修改
(2)允许指向常量的指针指向一个非常量对象,但仍旧不允许其对所指向的对象进行修改
1 double value=12.5;//value是一个非常量对象 2 const double *ptr=&value;//允许指向常量的指针指向一个非常量对象 3 *ptr=13.5; //错误:不允许指向常量的指针对其所指向的对象进行修改,哪怕所指向的对象不是一个常量而是一个变量
[理解技巧]:所谓指向常量的指针不过是指针的“自己为是”,它以为自己指向的是常量,所以自觉的不改变所指对象的值(参考《C++ Primer》)
B、顶层const——指针常量
指针常量,即指针本身就是一个常量。
语法:类型名 *const 变量名
特变注意:
(1) 常量指针必须初始化,而且一旦初始化,则它的值(也就是存放在指针中的那个地址)就不能在改变。也就是说常量指针的指向不能够改变,只能一直指向其初始化的对象。
1 int value=12; 2 int *const ptr=&value;//指针ptr是一个常量,它将一直指向变量value
(2) 指针本身是一个常量不代表不能通过该指针对其所指向的对象进行修改
1 int a=10; 2 int *const ptr=&a; 3 *ptr=11;//正确:运行通过常量指针对其所指向的对象进行就该
[理解技巧]:“从右向左”理解复杂const声明(参考《C++ Primer》)
1 const double *const ptr=π
•离ptr最近的符号是const,说明ptr本身是一个常量对象
•声明的下一个符号是*,说明ptr是一个常量指针
•之后的double说明指针ptr指向的对象是一个double类型的对象
•最后const说明指针ptr指向的对象还是一个常量对象
[注意]:更一般化来说,顶层const可以表示任意是常量的对象(适用于任何类型的数据),而底层const则与指针和引用等复合类型等基本类型部分有关
三、对引用的修饰
const引用,又称对常量的引用,顾名思义即为对const对象的应用
语法1:const 类型 &变量名(推荐)
语法2:类型 const &变量名
特别注意:
(1) 不能利用对常量的引用来修改其所绑定的对象
1 const int value=2017; //value是一个常量 2 const int &other_value=value;//正确:引用及其对应的对象都是常量 3 other_value=2018; //错误:oter_value是对常量的引用,不能通过其对所绑定的对象进行修改 4 int &value2=value; //错误:不允许让一个对非常量的引用来绑定一个常量,因为会有通过引用修改常量的风险
(2) 引用不是对象,而是别名。因此不存在引用是常量的情况(即不存在引用只能绑定在一个对象上恒定不变而无法改变绑定对象的情况)
(3) 初始化对常量的引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用了的类型即可
1 const int r=10; 2 const int &value=r*2+3;//正确
(4) 允许一个对常量的引用绑定一个非常量的对象、字面值、甚至是一个一般表达式
1 int re=2; 2 const int &value=re;//正确:允许一个对常量的引用绑定一个非常量对象 3 const int &value2=42;//正确:允许一个对常量的引用绑定一个字面值 4 const int &value3=re*2+4;//正确:允许一个对常量的引用绑定一个任意表达式 5 int &value4=re; //错误:不允许一个对非常量的引用绑定一个常量对象
[理解]:将一个对常量的引用绑定在一个非常量对象的过程
编译器将语句:
1 double value=3.14; 2 const double &r=value;
转化为:
1 double value=3.14; 2 const double temp=value;//由双精度浮点数生成一个临时的double常量temp 3 const double &r=temp;//让r绑定这个临时的double常量temp
[理解技巧]:所谓指向常量的引用不过是引用的“自己为是”,它以为自己绑定的是常量,所以自觉的不改变所绑定对象的值(参考《C++ Primer》)
四、对函数参数的修饰
用const修饰的函数参数,表示其在函数内是不允许被改变的
1 void function(const int a){ 2 a=10;//错误:a是常量,在函数内部不允许对其进行修改 3 }
特别注意:
(1) 顶层const作用于对象本身,当用实参初始化函数的形参时会忽略掉顶层const,换句话说也就是当形参由顶层const时,传给它常量对象还是非常量对象都是可以的。
1 void fcn(const int i){/*fcn能够读取变量i,但函数不允许修改它*/} 2 void fcn(int i){} //错误:由于顶层const被忽略了,因此不算对函数fcn进行了重载
(2) 尽量使用对常量的引用
•把函数不允许改变的形参定义成普通的引用会给函数的调用者一种“函数允许修改它的实参值”的错觉
•使用普通引用会限制函数所能接受的实参类型。比如const对象、字面值等无法传递给普通的形参变量;而对常量的引用不仅可以接受常量对象,还可以接受非常量对象
1 void func(const int &value){}//尽量使用对常量的引用
五、对函数返回值的修饰
用const修饰函数返回值的含义和用const修饰普通变量以及指针的含义基本相同,表示函数的返回值是一个常量。
1 const int function1()//没有太大的意义 2 const int *function2() //调用时:const int*ptr=function2(); 3 //可以把function2()看成一个变量,即返回一个指向常量的指针 4 int* const function3() //调用时:int *const ptr=function3(); 5 //可以吧function3()看成一个变量,即返回一个是常量的指针
六、对类成员变量的修饰
const修饰类的成员变量,表示其为成员常量,不能够被修改。
特别注意:
使用const修饰的成员变量,只能在类的构造函数的初始化列表中赋值,不能在类的构造函数的函数体内赋值
class Student{ private: const string m_Name;//成员常量,不能够被修改 ....... public: Student(string name):m_Name(name){ //正确,只能在构造函数的初始化列表中赋值 m_Name=name; //错误,不允许在构造函数的函数体内对成员常量进行赋值 ...... } }
七、对类成员函数的修饰
const修饰类的成员函数,表示在该成员函数体内不允许改变该类对象的任何成员变量,同时也不允许在该成员函数体内调用任何非const成员函数。通常定义时将关键字const写在函数参数列表之后。
1 class Student{ 2 private: 3 const long long id;//成员常量 4 string name; 5 int age; 6 public: 7 void function1(); 8 void function2() const;//声明一个常成员函数 9 void functon3() const{ //定义一个常成员函数 10 function1(); //错误:常成员函数中不允许调用非const成员函数 11 function2();//正确:常成员函数中允许调用const成员函数 12 age=23; //错误:常成员函数中不允许修改该类对象的任何成员变量或成员常量 13 return name;//正确:在不改变成员变量的前提下,运行引用成员变量或成员常量 14 } 15 }
特别注意:
常成员函数中的const关键字,其主要作用是用来修改隐式this指针的类型(this指针详见http://www.cnblogs.com/duwenxing/p/7410687.html)。默认情况下,this的类型是指向类类型非常量版本的常量指针(即指针本身是常量,当其指向的对象不是常量)。如上例中Student类中的普通成员函数的this指针的类型是 Student *cosnt;而常成员函数的this指针由于在其参数列表后添加了关键字const,故其的this指针的类型是const Student *const,也就是说此时this指针指向的是一个常量,因此常成员函数中不能修改类中的任何成员变量(在常成员函数中修改成员变量相当于先将成员变量赋值给一个常量,然后再对常量进行修改,因此是不允许的),也不能调用任何非const成员数(有修改类中成员变量的企图)。
[注意]:类中可以利用const对类中的成员函数进行重载
1 class Student{ 2 public: 3 void function(){} 4 void function() const {}//对成员函数进行重载 5 }
八、对类对象/对象指针/对象引用的修饰
•const修饰的类对象表示该对象为常量对象,也就是说该对象中的任何成员都不能被修改。(对象指针/对象引用类似)
•const修饰的对象不能调用该对象的任何非const成员函数,因为任何非cosnt成员函数都会有修改该对象成员变量的企图
class Student{ public: void functionA(){} void function() const {} } int main(){ const Student stu1;//stu1是一个常量对象 stu1.functionA(); //错误:常量对象不允许调用它的非const成员方法 stu1.functionB();//正确:常量对象只允许调用它的const成员方法 const Student * stu2=new Student(); stu2->functionA();//错误 stu2->fucntionB();//正确 }
九、const_cast
const_cast能够改变表达式的常量属性,但它只能改变运算对象的底层const,即:
•能够将常量指针转化为非常量指针,并且仍然直线原来的对象
•能够将对常量的引用转化为对非常量的引用,并且仍然绑定原来的对象
•能够将常量对象转化为非常量对象
语法:const_cast<type_id>(expression)
(具体见 http://www.cnblogs.com/duwenxing/p/7406043.html)
十、const与宏定义
• const常量有数据类型,而宏定义没有数据类型
• 编译器对const常量会进行类型安全检查;而对宏定义则只是进行字符替换,没有类型安全检查,甚至宏定义在字符替换时可能会产生意料不到的错误
• 从汇编角度来说,const常量给出的是对应的内存地址,而宏定义给出的则是立即数。因此const定义的常量在程序运行过程中只有一份拷贝,而宏定义的常量在内存中有若干拷贝
特别注意:
编译器通常不为const常量分配存储空间,而是将它们保存在符号表中,这使得其成为一个编译期间的常量,由于没有了存储于读内存的操作,故使得其的效率很高
1 #define VALUE 2017 //宏定义 2 const int VALUE=2017;//const常量