c++ const关键字
通常,不用const来区别数据的类型,在声明过程中,const关键字起到的是修饰限定的作用。
1 符号常量(通常,在赋初值后,其值不能改变;而常量在定义时必须赋初值)
int i; const int k = 3; //标识符k代表的内存单元中存放的int型的数据,关键字const限定,不能通过该标识符k来修改所代表的内存单元中的int值
i和k代表的内存单元存放的都是int型数据,只是const限定的k为常量;符号常量,在声明时必须赋初值,赋初值后其值不能改变
下面尝试通过声明一个指针变量t引用k所代表的内存单元,希望通过指针t来改变常量k的值,但结果很奇怪,t指向的内存单元就是常量k的内存单元,但值却是2个不同的值
int main() { // const限定声明的量k为常量,通过k不能改变其所表示的值 const int k = 3; int *t; t = (int *) &k; *t = 7; cout<<&k<<endl; //0018FF14 cout<<t<<endl; //0018FF14 cout<<k<<endl; //3 cout<<*t<<endl; //7 return 0; };
2. 数组可以被声明为常量(指的是数组中的每个元素都被当作都被当作常量对待)
const char cc[] = "hello"; //在声明时必须赋初值,初始化后,数组元素的值不能改变
3 常成员函数、常数据成员
使用const修饰的成员函数 -- 常成员函数
使用const限定的成员函数,为常成员函数,在常成员函数中,不能修改目的对象的数据成员值(通常说的,不能改变对象的状态,mutable关键字修饰的数据成员,不算对象的状态)
在常成员函数中,不能调用非const成员函数(普通成员函数),因为普通成员函数存在的目的就是修改对象的状态
使用const修饰的数据成员 -- 常数据成员(对象实例中的常量部分 -- 每个对象中都拥有各自的副本,与static限定的静态数据成员相区分,静态数据成员 -- 在整个类中只有一个数据副本,为该类的所有对象共同使用、维护)
在数据成员的类型前加const关键字来表征,const限制了对该数据成员的修改,其意思是:该数据成员在构造函数初始化列表中赋初值后,在任何地方都不能对其值进行修改,以后只需做读取该常数据成员的的值即可 --- 此处正契合常量在类中的使用习惯
即类的数据成员也可以定义为常量
#include "stdafx.h" #include <string> #include <iostream> using namespace std; class ConstTest { private: int a; const int b; //常数据成员 mutable int c; public: ConstTest(int a, int b, int c) : b(b) //常数据成员的初始化,只能在构造函数的初始化列表中进行 { this->a = a; //this->b = b; //错误,error C2166: l-value specifies const object, 等号左边的值是常对象,而常对象在赋初值后其值不能改变 this->c = c; } void incre() { a++; } //在常成员函数中,不能修改目的对象的数据成员 void fun() const { c++; cout<<"a="<<a<<", b="<<b<<", c="<<c<<endl; } }; int _tmain(int argc, _TCHAR* argv[]) { ConstTest c_test(3, 5, 9); c_test.fun(); //a=3, b=5, c=10 c_test.incre(); c_test.fun(); //a=4, b=5, c=11 return 0; }
4 常引用
在引用声明时,使用const修饰,则被声明的引用就是常引用;通过声明的常引用不能对所引用的对象进行修改。
int a = 3; // const限定声明的引用k为常引用,通过引用k不能改变其所引用的对象的值 const int &k = a; //k = 9; //error C2166: l-value specifies const object const int b = 5; const int &t = b; //t = 9; //error C2166: l-value specifies const object
常引用,可以绑定到普通对象,也可以绑定到常对象,但常引用把绑定到的对象都当作常对象来使用。这意味着,对于引用基本数据类型的常引用,不能对其赋值,也不能修改其值;对于引用类类型的常引用,不能修改所引用对象的数据成员的值(也包括不能调用它的普通成员函数)
即,通过常引用,只能调用被绑定对象的常成员函数,不能调用它的普通成员函数,当然更不能修改被绑定对象的数据成员 -- 一个目的就是,常引用不能改变其引用对象的状态,因为常引用已经把其所引用的对象当作常对象来看待了。
而常对象,是这样的对象:它的数据成员值在对象的整个生存期间内不能被改变,通过常对象不能调用普通成员函数,常对象对外可以的接口就是 --- 常成员函数。
普通引用,只能引用(绑定到)普通对象,要引用常对象需强制类型转换
如:
const Point p_const_obj1(7,7);
//Point &p_infer2 = p_const_obj1; //'initializing' : cannot convert from 'const class Point' to 'class Point &'
Point &p_infer2 = (Point &) p_const_obj1;
常对象,其数据成员的值在初始化后不能被改变(应该是通过声明时的常对象名无法改变其数据成员的值,因为const对该对象名对对象的访问操作做了限制(而这种限制只是间接性的限制,通过这个对象名是没法达到改变对象数据成员值这个目的),而不是直接限制该对象名所对应的那块内存单元中的内容不能改变)
#include <iostream> #include <cmath> #include <typeinfo> #include <string> using namespace std; template<typename T> string getTypeInfo(const T t) { const type_info &info1 = typeid(t); return info1.name(); } class Point { public: Point(int x=0, int y=0); //构造函数 Point(const Point &p); //复制构造函数 int getX() { return x;} int getY() { return y;} void move(); friend float dist(const Point &p1, const Point &p2); public: int x; int y; }; //构造函数实现 Point::Point(int x, int y) : x(x), y(y) { cout<<"构造函数Point::Point(int x, int y)被调用: "; cout<<"("<<x<<", "<<y<<")"<<endl; } //复制构造函数实现 Point::Point(const Point &p) { x = p.x; y = p.y; cout<<"复制构造函数Point::Point(const Point &p)被调用: "; cout<<"("<<x<<", "<<y<<")"<<endl; } //移动点,此处仅给横纵坐标加1 void Point::move() { cout<<"this pointer's typeinfo: "<<getTypeInfo(this)<<endl; //this指针的类型为: class Point * x++; y++; } //求2点之间距离函数实现 //dist函数是类Point的友元,在其函数体中,可以通过"对象名.属性"的方式访问类的私有和保护成员 float dist(const Point &p1, const Point &p2) { int xx = p1.x - p2.x; int yy = p1.y - p2.y; return static_cast<float> (sqrt(xx*xx + yy*yy)); } int main() { /* 调用复制构造函数的特定是,从一个Point对象拷贝构造出另一个Point对象(用源对象的数据成员初始化目的对象的数据成员),都是Point类型,不存在类型的转换 而,通常的调用函数是存在类型上的转换的,其目的是使用调用构造函数传入的参数为新构建的对象的数据成员初始化 */ Point p_obj1(1, 1), p_obj2(4, 5); //调用构造函数 Point pb1 = p_obj1; //调用复制构造函数 Point pb2(p_obj2); //调用复制构造函数 Point p_obj3 = 3; //调用构造函数 cout<<dist(p_obj1, p_obj2)<<endl; //常引用p_infer1绑定到普通对象p_obj1, 从常引用p_infer1的角度, 操作p_obj1要遵循常对象的限制规则 //其效果等价于,const Point p_const_obj = p_obj1, 只是此处要调用复制构造函数,构造一个新的常对象,在之后的使用中,都遵守常对象的限制规则 const Point &p_const_infer1 = p_obj1; /* * 在move()成员函数中,存在一个this指针,其类型为class Point &, 即this指针引用的类Point对象 * 此处调用该方法的目的对象的类型为const class Point **/ //p_const_infer1.move(); //cannot convert 'this' pointer from 'const class Point' to 'class Point &' /* pc1的类型为 class Point */ cout<<"p_const_infer1 typeinfo: "<<getTypeInfo(p_const_infer1)<<endl; //class Point p_obj1.move(); //初始化出错,普通引用不能绑定到常对象 //转换: class Point(普通对象) --> const Point &(常引用) 可以进行, 常引用可以绑定到普通对象 //转换: const class Point(常对象) --> Point & (普通引用) 不可以进行, 普通引用不能绑定到常对象 //Point &p_infer1 = p_const_infer1; //'initializing' : cannot convert from 'const class Point' to 'class Point &' Point &p_infer1 = (Point &) p_const_infer1; p_infer1.move(); //p_obj1对象的值被改变了, (3, 3) const Point &p_const_infer2 = p_const_infer1; //可以,这样p_const_infer2和p_const_infer1都引用p_obj1对象,并把其当作常对象使用 //测试常对象 const Point p_const_obj1(7,7); //Point &p_infer2 = p_const_obj1; //'initializing' : cannot convert from 'const class Point' to 'class Point &' Point &p_infer2 = (Point &) p_const_obj1; p_infer2.move(); //p_const_obj1对象的值被改变了, (8, 8) return 0; };