const、引用与指针
希望这是最后一次修顶这篇essay了,之前写的两次都有些小毛病。|||打脸,又来更新了。。
ps:右值指的是字面常量和表达式求值过程中创建的临时变量。
前提
我们忽略掉了相同类型是否可以赋值的情况(我到现在的学习里都还可以相互赋值),以及类型兼容的情况。只考虑const、&、*等修饰符带来的影响
类型兼容:
- 强制类型转换
- 基类与子类间的兼容
- 类的构造函数类型兼容(构造函数的参数类型可以转换为该类的类型,仅转换一次)
const
const:
- const初始化的变量要必须绑定一个值(变量还是常量都行)
- 这个绑定值可以隐式或者显示,大部分时候用显示更安全
- eg:string等容器,使用构造函数可以进行隐式默认初始化,const string str;就是合法的
底层const与顶层const
底层const与顶层const的概念是针对指针和const的搭配的
底层const:
底层const写在其他修饰符前面,也就是距离变量较远的地方
- 此类const修饰的变量一般指向的对象可以是变量也可以是常量
- 不可修改指向的对象
- 也不可以将该变量赋值给同类型的无const修饰的变量
顶层const:
- 用此类const修饰的变量,变量都会变为常量
- 常量是右值,不能被赋值操作
- 常量是可以进行初始化赋值的
const指针
指针:
- 会有自己的空间
- 指向目标对象
const type *ptr
此ptr 认为 自己指向的是一个常量,不能改变所指对象的值,但还是可以释放掉内存,该ptr也是一个变量(左值),可以进行赋值,但不能让它赋值给无const修饰的同类型的指针
type *const ptr
- 可以进行初始化赋值,但指向的必须是变量
- 如果可以指向常量,(*ptr)因为没有规定所以是可以改变的,指向的常量却不能改变,相矛盾
- 该ptr是常量(右值), 不接受赋值操作,ptr现在也变成了const常量
- 可以用它去赋值给无const修饰的同类型变量
顶层const问题:实际上是指针本身是不是常量和指针所指是不是一个常量的两个相互独立问题。恰好:底层const是指向看作"常量",本身看作"变量",只是不能赋值给其他变量;顶层const是本身是常量,指向是变量,可以复制给其他变量
const引用
(无const修饰)引用:
- 必须绑定一个左值
- 取的另一个别名
- 只能初始化进行绑定一次
- 本质上还是指针,只不过比较特殊
const type &temp = variate
variate可以是常量,也可以是变量,temp不能修改
(const type &temp 可以绑定右值,但是右值不一定都是const类型,这样绑定后可能会使得temp失去原来右值的部分功能)
- temp被称为常量引用
type & const temp = variate
规定不能这么写,可以进行。效果会类似于省略顶层const,既type & const temp == type & temp
int c = 10;
int & const b = c;
b = 5;
cout << c << endl; //结果是5
下面是引用和指针的区别
int a = 0, b = 5;
b = a;
a = 11;
上面这些操作都是直接作用于"内存变量"
int *d = &b; //d指针变量获得b的指针
int &c = a; //c其实也获得了a的指针,姑且称为c_ptr
引用和指针在此时的汇编操作是一样的,均是把右边变量的指针取出放入左边
c = 12; //此时相当于 *c_ptr = 12
*d = 13;
这张汇编图很清楚的展示了原理
那么还有最后一个问题,引用传参、指针传参和值传递的区别了?
我们既然知道了引用实际上也是指针操作,只是帮助我们隐去了部分指针操作而已。
void func(int &_a, int *_b, int _c){
...
}
func(a, &b, c);
- 值传递会拷贝内存的副本进入当作局部变量,此时_c的值和原来一样,但地址不同
- b把自己的地址传入,_b此时值为b地址的指针局部变量
- _a是引用传递,先把a的地址传入给 "_a_ptr",_a实际上也是a的局部地址变量。
- _a在被询问地址的时候,相当于访问_a_ptr
- _a在被使用的时候,相当于访问*_a_ptr
- ps:_a_ptr是为了方便理解,我自己设定的,实际上是直接用_a进行地址存储
多级指针和const
C++不允许const T*
转换为T*
类型,除非使用const_cast显示地转除const限定符
const int d = 0;
const int *c = &d;
int e = 1;
int *f = &e;
int **b = &f;
const int **a=b;//假设可以编译通过
*a = c;//*a是const int *,c也是const int *类型,所以是对的
b初始化了a,修改*a
也就是修改*b
,此时*a,*b,c
三者理论上指向同一个内存。再看c是const int*
,而*b
是 int*
型!间接导致了从const int*
型到int *
型的转换!是不是就破坏了上面加粗的规则!
所以int**
不能转换为const int**
!!!
如果把a定义为const int *const *a = b;
自然就不能改变*a
的值,这句*a=c
就错了,所以不会破环规则。
总结:int**
之流可能通过const int **
进行间接将const int *
转为int *
,所以是违法的