c和c++的区别(二)const和引用、一级指针、二级指针的结合

2022-02-24 15:56:54 浏览数 (1)

一、const和一级指针的结合

一级指针的模型

一级指针有两种表达方式,p*p。所以const与一级指针有两种结合方式。

代码语言:javascript复制
//在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 中,定义常量必须进行初始化。那么上边四个哪些是常量?

代码语言:javascript复制
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修饰,故是常量。
代码语言:javascript复制
int main(){
	int a=10;
	const int b=20;
	a=b;//正确,将常量值赋值给变量
	b=a;//错误,常量不能作左值
}

在C 中,当const关键字修饰常量时,const所在的位置,会不会出现问题。主要是担心代码会修改被const修饰的常量值,如果有这样的风险,编译器不会通过代码的编译的。

修改的方式有两种: 1.直接修改 直接修改比较容易判断,看常量是否作左值。 2.间接修改 会不会将常量的引用或地址泄漏出去,通过使用引用(使用引用会自动解引用)或指针间接修改常量。

代码语言:javascript复制
一级指针与const结合总结:
const int*  ->  int*     //错误
int*        ->  const int*    //正确

测试一:test.cpp

代码语言:javascript复制
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

代码语言:javascript复制
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

代码语言:javascript复制
test2.cpp
int main(){
	const int* p=NULL;
	int* q=p;
}

测试四:test3.cpp

代码语言:javascript复制
//输出类型
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 变量名这种形式。

代码语言:javascript复制
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&引用常量(包括可寻址的常量和不可寻址的常量)

代码语言:javascript复制
int main(){
	int &a=10;//错误,不能用立即数进行初始化
	const int &b=10;//正确的,为什么呢?
	return 0;
}

从汇编的角度看看常引用为什么是可行的,往往越底层的东西越能带来透彻的理解。

代码语言:javascript复制
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]中

通过上边汇编代码的分析,所谓常引用,实际上是在内存中寻取了一块空间,作为临时量,存放立即数。而引用则是对这块内存空间即临时量的引用。

代码语言:javascript复制
const int &a=10;//可以看作是下边两行代码
const int temp=10;
const int &a=temp;

指针变量与常引用结合

代码语言:javascript复制
如现在要向地址为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;	
}

引用不参与类型,不能说是引用类型

代码语言:javascript复制
#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和二级指针最基本的结合方式有三种。

代码语言:javascript复制
int const **q;//修饰的是**q,没有修饰*q和q
int* const *q;//修饰的是*q,没有修饰**q和q
int** const q;//修饰的是q,没有修饰**q和*q

二级指针和const结合的典型问题 1.

代码语言:javascript复制
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.

代码语言:javascript复制
int main(){
	int a=10;
	int *p=&a;
	const int *&q=p;//   q和p等价,错误同上
	const int **q=&p;
}

3.

代码语言:javascript复制
int main(){
	int a=10;
	int *p=&a;
	int* const *q=&p;//正确
	
	int a=10;
	int *p=&a;
	int** const q=&p;//正确
}

4.

代码语言:javascript复制
int main(){
	int a=10;
	const int* p=&a;//const修饰的是*p即内存a被封锁
	int** q=&p;//错误,通过**q可以将常量内存块修改
	改正为:int const **q=&p;
}

5.

代码语言:javascript复制
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*退化为一级指针考虑。`

0 人点赞