c和c++的区别(二)const和引用、一级指针、二级指针的结合
一、const和一级指针的结合
一级指针的模型
一级指针有两种表达方式,p
和*p
。所以const
与一级指针有两种结合方式。
//在c++语法规则中,const修饰距离它最近的类型。
int a=10;
int *p=&a;
int const *p;
//距离const最近的类型是int,而不是int*,因为int已经是类型了
//const的是*p,p本身没有被const修饰
const int *p;
//距离const最近的是int,*不能构成类型。const修饰的是*p,p没有被修饰
int* const p;
//距离const最近的类型是int*,修饰的是一个指针变量p。但*p没有被修改
//存在内存泄漏
const int* const p;
//距离第一个const最近的类型是int,修饰的是*p。距离第二个const的类型
//是int*,所以修饰的p。
在C++中,定义常量必须进行初始化。那么上边四个哪些是常量?
int a=10;
int *p=&a;
int const *p;//const修饰*p,但是没有修饰p。p可更改,故不是常量。
const int *p;//const修饰*p,没有修饰p。p可更改,故不是常量。
int* const p;//const修饰的是p,是常量。
const int const* p;//变量名p本身被const修饰,故是常量。
int main(){
int a=10;
const int b=20;
a=b;//正确,将常量值赋值给变量
b=a;//错误,常量不能作左值
}
在C++中,当const关键字修饰常量时,const所在的位置,会不会出现问题。主要是担心代码会修改被const修饰的常量值,如果有这样的风险,编译器不会通过代码的编译的。
修改的方式有两种:
1.直接修改 直接修改比较容易判断,看常量是否作左值。
2.间接修改 会不会将常量的引用或地址泄漏出去,通过使用引用(使用引用会自动解引用)或指针间接修改常量。
一级指针与const结合总结:
const int* -> int* //错误
int* -> const int* //正确
测试一:test.cpp
int main(){
/*
int a=10;
int *p=&a;//&a int* 正确的赋值
*/
const int b=10;
int *p = &b;
const int* q = &b;
//&b -> const int* ,将常量的地址泄漏出去了
//但是泄漏出去不一定是错误的,且看下边的例子
*p=20;//错误,可以通过*p修改b内存块的地址。const没有修饰*p
//存在间接修改常量内存块的风险,编译是不通过的
*q=20;//此时q为const int*,不能作左值,编译错误
}
测试二:test1.cpp
int main(){
//对于const int*,可以存储常量的地址,也可以存储变量的地址
int a=10;// const int a=10;
const int* p=&a;
int* q=p;//直观的感受q就是&a啊,a是变量,可以通过*q修改a
/*但是编译是错误的,为什么呢?
对于const int* p,其类型为const int*,不管存储的常量的地址
还是变量的地址,都按照其类型存储,即const int*,即使是存储
的是变量的地址也会提升为常量的地址。
int *q=p;*q并没被const的修饰,所以会出现编译错误
*/
可见,其实const int*里边存储与变量a无关
return 0;
}
测试三:test2.cpp
test2.cpp
int main(){
const int* p=NULL;
int* q=p;
}
测试四:test3.cpp
//输出类型
test3.cpp
#include<iostream>
#include<typeinfo>
using namespace std;
int main(){
int a=10;
int const *p=&a;
int *const q=&a;
cout<<typeid(p).name()<<endl;
cout<<typeid(q).name()<<endl;
return 0;
}
有上图结果可知,const没有修饰*(指针)/&(引用),不用考虑。
二、const和引用的结合
定义应用时,由于&
和变量名紧挨着。所以const和引用结合只有一种方式,即const int &变量名
或int const &变量名
,而不会出现int &const 变量名
这种形式。
int main(){
//int a=10;
//int &b=a;
//const int &c=a;
const int a=10;
int &b=a;//错误,将a的引用泄露出去,通过对b赋值可以修改常量
//对于常变量只能使用常引用
const int a=10;
const int& b=a;
return 0;
}
常引用
const&引用常量(包括可寻址的常量和不可寻址的常量)
int main(){
int &a=10;//错误,不能用立即数进行初始化
const int &b=10;//正确的,为什么呢?
return 0;
}
从汇编的角度看看常引用为什么是可行的,往往越底层的东西越能带来透彻的理解。
const int& a=10;
mov dword ptr[ebp-14h],OAh
//函数栈帧空间以栈底指针ebp的偏移量offset表示栈空间的地址
//将OAh(10)存到[ebp-14h]指向的四字节的内存空间中
mov eax,[ebp-14h]
//将地址[ebp-14h]存放到eax寄存器中
mov dword ptr[a],eax
//将eax寄存器中的内容即[ebp-14h]存放到地址为a四字节的空间[a]中
通过上边汇编代码的分析,所谓常引用,实际上是在内存中寻取了一块空间,作为临时量,存放立即数。而引用则是对这块内存空间即临时量的引用。
const int &a=10;//可以看作是下边两行代码
const int temp=10;
const int &a=temp;
指针变量与常引用结合
如现在要向地址为0x0011ff22内存块写入10,定义指针的引用变量
int main(){
int *&p = (int*)0x0011fff22;*p=10;
//显然这是错误,引用不能用立即数初始化
//结合上边的常引用
const int*&p还是int* const &p哪一个是正确的呢?
const int*&p其中,const修饰的是*p,并非引用,是错误的
int * const &p;是正确的
int* const &p=(int*)0x0011ff22;//可以看作是
int* const temp=(int*)0x0011ff22;//临时量存储立即数
int* const &p=temp;
}
引用不参与类型,不能说是引用类型
#include<iostream>
#include<typeinfo>
using namespace std;
int main(){
int a=10;
int &b=a;
cout<<typeid(b).name()<<endl;
return 0;
}
可见引用不参与类型,但是指针是参与类型的。
三、const和二级指针的结合
二级指针的模型
二级指针有三种表达方式,即q
、*q
和 **q
,所以const
和二级指针最基本的结合方式有三种。
int const **q;//修饰的是**q,没有修饰*q和q
int* const *q;//修饰的是*q,没有修饰**q和q
int** const q;//修饰的是q,没有修饰**q和*q
二级指针和const结合的典型问题
1.
int main(){
int a=10;
int* p=&a;
const int** q=&p;
//错误 **q和*p是等价的,*q和p是等价的
//由于const修饰了**q,所以不需考虑通过*p修改常量的值
//*q是const int*类型
//const int a=10; &a -> const int*
//*q=&a 由于*q和p等价 p=&a
//所以存在通过对*q解引用修改常量内存块的风险
//通过对p解引用修改常量内存块的风险
以下两种修改方式均是正确的
int a=10;
const int *p=&a;
const int **q=&p
或
int a=10;
int *p=&a;
const int* const *q=&p;
}
2.
int main(){
int a=10;
int *p=&a;
const int *&q=p;// q和p等价,错误同上
const int **q=&p;
}
3.
int main(){
int a=10;
int *p=&a;
int* const *q=&p;//正确
int a=10;
int *p=&a;
int** const q=&p;//正确
}
4.
int main(){
int a=10;
const int* p=&a;//const修饰的是*p即内存a被*
int** q=&p;//错误,通过**q可以将常量内存块修改
改正为:int const **q=&p;
}
5.
int main(){
int a=10;
int* const p=&a;//const修饰的是p
int** q=&p;//错误,通过*q可以修改常量内存块的值
改正为:int* const *q=&p;
}
综上:当一级指针、二级指针和const结合时。
1.const int*
转化为int*
错误
2.int*
转化为const int*
正确
3.const int**
转化为int**
错误
4.int**
转化为const int**
错误
5.当const为**
之间时,*const*退化为一级指针考虑。