欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-01-01 18:21:10
...

一、const和一级指针的结合

一级指针的模型
c和c++的区别(二)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*,不能作左值,编译错误
}

c和c++的区别(二)const和引用、一级指针、二级指针的结合
测试二: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;
}

c和c++的区别(二)const和引用、一级指针、二级指针的结合
测试三:test2.cpp

test2.cpp
int main(){
    const int* p=NULL;
    int* q=p;
}

c和c++的区别(二)const和引用、一级指针、二级指针的结合
测试四: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;
}

c和c++的区别(二)const和引用、一级指针、二级指针的结合
有上图结果可知,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;
}

从汇编的角度看看常引用为什么是可行的,往往越底层的东西越能带来透彻的理解。
c和c++的区别(二)const和引用、一级指针、二级指针的结合

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;
}

c和c++的区别(二)const和引用、一级指针、二级指针的结合
可见引用不参与类型,但是指针是参与类型的。

三、const和二级指针的结合
二级指针的模型
c和c++的区别(二)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*退化为一级指针考虑。

上一篇:

下一篇: