const的语义为只读。修饰的值不能改变,必须在定义时就给它赋予初值。 本文将通过实际代码来理解这句话。
1.普通变量前加const
void testConstVar(){
int j = 20;
const int i = j; //const变量初始化时可以用非const变量赋值
j = i; //非const变量可以用const变量赋值
j = 10;
cout<<i<<','<<j<<endl; //20,10
const int x = 10; //const变量初始化时可以用常量赋值
int y;
x = y; //err!const变量初始化后不能被赋值,这里是用非const变量测试
const int z = 20;
z = x; //err!const变量初始化后不能被赋值,这里是用const变量测试
}
小结:对于加了const关键字的变量,其定义时必须初始化,无论是const还是非const变量都可以对其进行初始化;而对于初始化了的const变量,不能再被赋值。
2.与函数关联的const
2.1 在函数返回值前加const
考虑一个如下函数
const int getNum(){
int i;
return i;
}
对它进行如下调用
void callGetNum(){
std::cout<<typeid(getNum()).name()<<std::endl; //我是在win下用g++编译的,打印出来是i,表示的是int。如果是用visual studio集成开发环境的话可以用__typeof__(var)宏(其中var是变量)打印类型。
int ret = getNum();
cout<<ret<<endl; //0
const int cret = getNum();
cout<<cret<<endl; //0
const int& ref = getNum();
cout<<ref<<endl; //0
auto aret = getNum();
cout<<aret<<endl; //0
}
小结:可以看到,用int,const int,const int& ,auto类型的都能接收到,那在函数返回值前加const还有什么意义呢?在这里const是用于说明getNum返回的是一个const int,也就是函数的设计者希望不要改变函数的返回值,作为调用者,最好应该使用const int& 接收。看一下接收后的情况
void callGetNum(){
std::cout<<typeid(getNum()).name()<<std::endl;
int ret = getNum();
cout<<ret<<endl; //0
const int cret = getNum();
cout<<cret<<endl; //0
int& ref = getNum(); //err! 返回值是const int,引用类型不能是非const
const int& cref = getNum();
cout<<cref<<endl; //0
auto aret = getNum();
cout<<aret<<endl; //0
ret = 10;
cret = 10; //err! cret是const,初始化不能再被赋值
cref = 10; //err! 不仅仅因为ref是const而不能被赋值,还因为ref是引用,也就是右值
aret = 10;
}
从上可以看到用const int&接收更符合函数设计者的意图,虽然用cret接收也能实现函数的返回值不被改变,但cret是左值,开辟了新的内存,而cref是右值,是函数返回值的引用,与函数设计者要表达的意思是一致的。
2.2 在函数参数中加const
考虑如下函数
int getNum_1(const int i){
//int i;
//i = 10; //err! 传入的是const,不能改变
return i;
}
函数形参加const表示函数设计者不希望改变函数调用者传入的实参,考虑如下调用
void callGetNum_1(){
int a = 10;
const int b = 10;
cout<<getNum_1(a)<<endl; //10
cout<<getNum_1(b)<<endl; //10
}
可以看到,无论是用普通的变量还是const变量,都没错,这与const变量(形参i)初始化时可以用const或非const变量赋值一致,从这可能还看不出在形参前加const的用处,但当形参是指针或引用类型时,就很有必要了,在后面再做详细说明。
2.3 const在函数体后面
首先非成员函数上是不能这样使用的,必须在成员函数上才能使用,const在函数体后面,表示是类的常成员函数考虑如下类
class GetNum{
int i = 10;
public:
int getNum() const {
//i = 20; //err! 类的成员不能改变
return i;
}
};
做如下调用
void callGetNumClass(){
GetNum cn;
cout<<cn.getNum()<<endl; //10
}
在类中加入普通成员函数和常成员函数,新的类定义如下
class GetNum{
int i = 10;
int static j;
public:
static int getStaticNum(){
return j;
}
void changeNum(){
i = 20;
}
void printNum()const{
cout<<i<<endl;
}
int getNum() const {
//i = 20;
//changeNum(); //err! 常成员函数只能调用常成员函数
printNum();
cout<<"in getNum fun: "<<"call getStaticNum fun val is "<<getStaticNum()<<endl;
return i;
}
};
int GetNum::j = 1;
做如下调用
void callGetNumClass(){
GetNum cn;
cn.getNum();
cn.changeNum();
cn.getNum();
cn.getStaticNum();
}
结果如下
10
in getNum fun: call getStaticNum fun val is 1
20
in getNum fun: call getStaticNum fun val is 1
可以看到,常成员函数是可以调用常成员函数和静态成员函数的。这里的静态成员函数是个例外,也就是说常成员函数没有对静态成员变量进行限制。
2.4 在*左边或右边加const
2.4.1常量指针
常量的指针,是底层const,即指针指向的是一个只读的对象。const在*左边,靠近数据类型。
示例:
int a = 10;
int b = 20;
const int * pa = &a; //int const * pa = &b;
pa = &b; //pa的值可变
cout<<*pa<<endl; //20
2.4.2指针常量
指针类型的常量,是顶层const,指针常量只能在定义时初始化,之后不能改变。const在*右边,靠近变量名。
int a = 10;
int * const pa = &a;
*pa = 20; //pa指向的对象值可变
cout<<*pa<<endl; //20
2.4.3修饰引用
表示引用的值是常量,不能通过引用修改。
void testConstRef(){
int a = 10, b = 20, c = 30; //引用的对象必须初始化
const int& ref_a = a;
//ref_a = 15; //err!
cout<<ref_a<<endl;
int const& ref_b = b;
//ref_b = 25; //err!
cout<<ref_b<<endl;
/*int & const ref_c = c; //err! 引用本质上是一个常量指针,const加在这里是语义冲突的
ref_c = 35;
cout<<ref_c<<endl;*/
}
注:关于引用的一个注意点,引用是变量的别名,必须初始化,从一而终不可变,不存在指向空值的引用。
int a = 10, b = 20;
const int& cref_a = a;
int& ref_a = a;
cout<<ref_a<<endl; //10
cout<<cref_a<<endl; //10
//ref_a = 15;
ref_a = b; //注意!这里不是改引用绑定,ref_a绑定的还是a,只是它的值现在通过b赋予
cout<<ref_a<<endl; //20 //a的值现在是20
cout<<cref_a<<endl; //20 //a的值现在是20
关于const的位置大概就这么多,它们之间的组合就不再一一介绍了,如有错误或遗漏,欢迎交流补充,谢谢!