本来以为const很温顺,单单表示不能改变的状态就可以了,后来发现不是一般的麻烦。
const跟引用,指针,auto,decltype都有联系。
1、纯纯的const
#include <iostream>
using namespace std;
int main(){
int a=1;
const int b=2;
int const c=3;
const int d=a;
int e=b;
cout<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<e;
return 0;
}
int类型和const int 类型可以互相赋给对方,毫无压力/
2、与引用勾搭的const
int main(){
string FirstPassword;
cin>>FirstPassword;
const string &SecondPassword=FirstPassword;
cin>>FirstPassword;
cout<<SecondPassword;
return 0;
}
强调的只是绑定状态的不改变;上面的第一密码不仅不是常数甚至还可以一直输入一直改变值。
不同于纯纯的状态,此时int const与 int 的互动发生了变化
int main(){
int a;
int const c=10;
cin>>a;
int const &b=a;
int &d=c; //error
cout<<b<<" "<<d;
return 0;
}
不能把const int 类型的变量赋给 int 型的引用;而const int 型的引用则是通杀。
也就是const与引用的结合中不存在 常量被绑定在板凳上,而板凳可以替换它的常量 的状况。
然而很魔性的东西是
int main(){
const int &i=42;
return 0;
}
如果没有const就感觉是无理取闹,像是把字面值赋给地址;然而此时的意义就变成了给变量i绑定了
一个值为42的字面值,而且不可以改变。
更魔性的是
#include <string>
using std::string;
using std::cin;
using std::cout;
int main(){
string FirstPassword,a; //空字符串,而不同于NULL
cin>>FirstPassword; //输入的字符串不能用空格,输出的想要空格用字符串字面值
// cout<<FirstPassword+" a";
const string &SecondPassword=FirstPassword+" m";//去掉const报错了!
//cout<<SecondPassword; //此常量引用与之绑定的对象无法解绑
FirstPassword+" m"=FirstPassword+" n"+FirstPassword; //失效,非报错
cin>>FirstPassword; //不执行,被屏蔽,不报错
cout<<SecondPassword;//此时似乎既无法改变所指对象亦无法改变值,而去掉m后无此效果
return 0;
}
这是一时的一个脑洞,居然可以实现string类型变量和一个字符串相加然后赋给常的string类型变量。
(如果不是常的当然是会报错。)
然后此时居然就起到了双const的效果。
3、与指针勾搭的const
这个比引用多了一层板凳那个状态,不仅有常量指针还有指针常量。
#include <iostream>
using namespace std;
int main(){
const int a=1;
int b=2;
const int *p1=&b;
int *p2=&b; //a is error,p1 is error
int *const p3=&b; //a is error,p1 is error
const int *const p4=p2;
p1=p4;
p2=p3; //p4 is error
return 0;
}
const int *p 是针对*p是常量;int *const p 是针对p是常量。
*p是常量就是相当于值是常量的情况,不能被int型指针及int *const所指向。
也就是说不强调指向常量的指针不能指向 常量或 指向常量的指针。
可以看到不存在int 型的引用绑定了int const型对象;
不存在int 型指针(或常指针)指向了int const型对象(或指针)。 //int const==const int
内在逻辑是把赋值操作和类型判断区别开,只要能赋值,类型就变为赋值后的逻辑类型,而不受之前类型的影响;
而能否赋值由赋值对象和接收赋值一方的状态决定,原则是 int const型对象(指针)不流向int型指针(常指针)或引用。
(const int a=10; int b=a; 是完全正确的)
4、与auto
并不喜欢顶层const和底层const理论,用起来还要判断一番
一个常见的例子
int main(){
int a=1;
const int b=a,&c=a;
auto d=&a; //auto==int*
auto e=&b; //auto==const int*
return 0;
}
显然,只需要根据 简化原则即可。const int a=10; *** b=a中b是否const都是对的,则auto的判断优先不加。
同时参照 int const型对象(指针)不流向int型指针(常指针)或引用 , auto选择有时加const。
int main(){
const string str("world");
auto &c=str; //auto==const string
return 0;
}
其他同理。如引用,指针等。当然可以在auto前加const等。
5、decltype
decltype用的时候和auto不一样的在于,它前面有个括号
decltype (a) b=c; 通过a确定b的类型,而不是通过C。因此decltype就更有底气同时保留顶层const和底层const。逻辑上我们也更倾向于它如此。
关于其他方面 decltype对引用的偏执,比如
int a=1, &b=a;
decltype(b+0) c=2; //此时是int类型
int *p=&ci;
decltype(*p)c; //错误,c是int&类型而非int类型,必须初始化
/*如果表达式的内容是解引用类型,将得到引用类型*/
double k=3.14;
decltype(k=ci) e=k; //e是double&类型,k=ci产生引用类型
/*赋值会产生引用类型,引用的类型是左值表达式 */
int a=1;
decltype((a)) b=a; // 此时b的类型是int类型的引用, b和a绑定在了一起
以及decltype如何不需要计算结果,因此不需要函数调用,而auto需要,那就是他们之间的恩怨了。
附:常量引用绑定对象时的问题
int a=1;
const int &b=a;
实际执行的操作为:
int a=1;
const int temp=a; //temp临时变量
const int &b=temp;
常量引用只有以下情况会绑定到实际对象上:
const int a;
const int &b=a;
或
int a;
const int &b=a;
(是否赋值不受影响)