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*退化为一级指针考虑。

猜你喜欢

转载自blog.csdn.net/ASJBFJSB/article/details/81393629