const的巧用
一、const 修饰一级指针
1、int a=10;
const int *p=&a;
int *const p=&a;
3、const int a=10;
int *p=&a; //不行,因为可以通过*p改变a
在c中:int *p=(int*)&a //可以将a取地址强转为int *,c++也可以,不过c++封装四种方式。
二、const修饰二级指针
1、int a=10;
int *p=&a;
int **q=&p
int *p=&a;
const int **q=&p //错,可以通过*p修改a
解答:因为我们这个const修饰**q限制的是说不能通过间接访问方式修改a,但是对于a来说,a是直接访问,如果要限制a,应该是用const直接修饰a,即const int a=10;表示a不能被修改。
3、权限缩小:int *->const int *
权限增大:const int * ->int *
4、int a=10;
int b=20;
int *p=&a;
int *const *q=&p; //const修饰的是*q,则,即p,表示我们不能通过*q修改p的值,但是我们可以通过p修改自身的值
p=&b //正确
*q=&b //错误;
这个其实和1是一样的道理。但是这个时候如果再有一个三级指针,那就有危险。
int ***pl=&q;
那这个当然也是不行的,因为可以通过解两次引用**pl=*q=p从而修改了p的指向
5、int a=10;
const int *p=&a;
int **q=&p; //会报错,因为可以通过**q修改a的值
三、const与引用
1、int a=10;
const int &b=a; //正确,这一句等同于 const int *b1=&a; 因为引用的底层实现就是指针,但是用的时候会给它自动解引用。
2、int &b=10; //error 因为这个等同于int *b=&10,对立即数取地址肯定是错的。
3、const int &b2=10; //ok
这一句相当于编译器做了两件事:
1)int temp=10 //创建了一个临时变量
2) const int *b2=&temp; //将b2指向了临时变量的地址
切记:注意这个是可以的,但是int *b=&10或者int& b=1=都是不可以的。
4、int a=(int&)b; // 看汇编代码发现这一句其实就是 让a=b,忽视了&
cout<<typeid(b3).name()<<endl; //输出的是int
5、int a=10;
const int &b3=a;
b3=10 //错误,因为上一句const 修饰的是b,也就是不能通过b修改a的值
int *p=&b3 //error还可以通过*p修改b3,可以用改成const int *p=&b3;
6、int a=10;
int &const b=a ; //这一句中const没起作用只有对&a整体使用才能发挥const的作用。
b=12; //ok
int *p=&a //ok
所以原来的 int &const b=a 相当于 int &b=a
7、int *p=&a;
思考怎样写出一级指针p的引用?
解答:可以先用指针转化,因为引用底层就是指针!
int **q=&p;
-》int *&q=p;
大家可以记住一个转化的小技巧:int &a=b;《 ——》int *a=&b;
四、const、引用、指针与形参变量结合(重载)
1、void func(int a=10){}
void func(int a){}
这样不行,重定义了,不叫重载,c++生成符号的时候,并没有把默认值部分加入到函数区别中,所以它们两个生成的函数符号一样。我们可以回顾一下,区别两个函数的因素有哪些:
1)函数名称
2)形参列表
3)形参个数
4)形参顺序
对实参的值是否有影响也是构成重载的一个条件,const /volatile修饰的值可以构成重载。
2、void func(const int p);
void func(int p);
不可以,因为传值不可以改变实参,这只是值传递,值传递只是在调用函数的栈帧压入了实参,都是在func中改变副本,退出func时都会被销毁,所以对实参没有影响,编译器觉得区别它们没意义,所以也不区别它们。
void func(int *p);
4、void func(int *cosnt p);
void func(int *p);
//不可以,这个只是对指针指向的修饰,表示p的指向不能改,可以这样的话,它们对实参的权限是一样的,都可以通过*p操作a,编译器无法区别它们对实参的操作上有什么不同,所以这两个函数也不能重载,也就是说我们c++在给每个符号做符号修饰的时候,并不区别int *const p和int *p。
注意:如果形参个数一样,我们区别形参类型的时候,注意我们看的是它们对实参是否有影响,如果两个都没有影响,比如形参是int a或者const int a;或者说它们对实参的影响是一样的权限,比如:int *p或者 int *const p,它们都可以修改*p 编译器在函数名和参数个数一样的时候,就看形参对实参的影响能否区别开来。如果是 int *
和 const int *p 那就可以,因为它们对形参一个是只读,一个是可读可修改,所以可以区别开来。
五、const、引用、指针与函数返回值的结合
1、int sum()
{
int tmp=10;
return tmp;
}
int main()
{
1) int& a=sum // 错 它的底层实现其实可以等同于int *a=&sum(); sum的值放在了寄存器中,对
// sum的取址就是对寄存器的取址,寄存器是不能取地址的,它没有地址。对寄存器
// 取地址就是错的。
2)const int &b=sum(); // 正确,他就等同于const int &b=10; ——》 int temp=10; cosnt int *b=&temp;
3)cosnt int *p=&sum();//错,对sum取地址,就是对寄存器取地址,对寄存器取地址,就是错的。
强调:cosnt int *p=&10// 不可以,不能对立即数取地址 const int a=10; //可以,相当于指向了临时变量
4) const int &p=&sum(); // 不可以,&sum()=&eax 只要是对寄存器取地址就不行。
}
2、int * sum()
{
static int tmp=10;
return& tmp;
}
int main()
{
int *&p=sum(); // 这是错误的,因为我们可以把它看成指针的形式,也就是int **p=∑sum不可取地址所以不行,是错的。
问:如果左边是不可修改的左值,右边不赋值左值,赋值一个立即数可以吗?
答:那就要看情况了,如果是这样const int& p=10,当然可以了,这个我们分析过了,这个实现其实分为了两步。如果是 const int *& p=10,这个当然是不行的&不参与类型比较,但是它和变量连接着,表示整体,那&p表示左边修饰的是cosnt int * 右边只是const int ,明显类型不匹配呀。 那如果是
int a=10;
int *q=&a;
const int *&p=q//错误 因为根据转换,const int **p=&q, 那我们还是可以通过*q 改变啊。
修改:只要在将int *q 改为 const int *q =&a; 就可以了。
int *cosnt &p=sum(); // 可以。
原因分析:因为虽然我们sum的返回值是寄存器带回来的,但是我么回想一下int cosnt &a=10,是不是可以的,因为,它其实分成了两步,一个是int temp=10; int const *a =&temp;
那我么这个题呢,int *const &p=sum()是不是也可以想成是
int *const temp=sum();
int *const *p=&temp;
那为什么我么temp是int*const 类型呢?
因为我们如果是int *temp类型,我么不用const修饰,就直接可以用了,我们通过测试发现只有这样编译器才能通过,我们可以尝试站在设计者的角度考虑一下这个问题,temp是底层封装的一个过程,我们用户根本就看不到,所以也不需要修改这个temp的指向,我们根本不知道它在哪,它是谁,既然都用不到,那就设置为const更安全,那为什么我们下面的const int *&p=sum();不行呢?
?? const int *&p=sum(); //不可以
原因:我们可以按照上一题的思路站在设计者的角度考虑一下,如果我们也为它创建一个临时变量,那这个临时变量激素int *temp=sum(); 然后const int **q=&temp可以吗,当然是错的,因为我们现在const 修饰的是**q,但是*temp却有修改的风险,虽然我们触碰不到,但是作为严谨的设计者,就要把一切可能的危险扼杀在摇篮里,那我们用const int *temp=sum()可以吗?当然也不合理,我本来在函数中返回的是可以修改的变量,用户也是想达到这样的效果,但是经过你这的时候,你不管用户想法,直接把它所指向的值设置为只读,权限变小了,所以也不能写成const
int *temp=sum(); 那就没办法了。
那const int *const &p=sum();
这当然也可以,我们分析过了,那个temp指针是int *const 类型,只要你接收的它的权限一样,或者比它还小,都没有问题,它只是不能修改指针指向,这个现在是既不能修改p指针指向,也不能修改**p的值了(p自带了一次解引用);
int &a =*sum; //可以,因为指针解引用就是tmp变量 ,转化就是int& a=tmp;
}
3、int &sum(){}
int main()
{
int &p=sum();//可以 因为返回&,表示有sum()作为右值的地方底层实现自动解引用一次,就相当于int& p=*sum()=tmp;
}