♻️五、C++中对C的const关键字增强

date: 2020-05-31
C++对C的const关键字增强的底层机制

(1)问题导入

背景:
我们总说C语言中const修饰的变量看上去似乎是常量,其实是个“冒牌货”,应该叫”常变量”,比如用指针间接赋值,就能改变了。

代码测试:
将指针间接修改变量值的代码放到C语言编译器和C++编译器去测试

DevC++的C语言编译器

//demo.c 
#include<stdio.h>
int main()
{
    const int a=98;

    int *p=NULL;
    p=(int *)&a;
    *p=100;

    printf("%d",a);
    return 0;
}

结果:

DevC++的C++语言编译器

//test.cpp 
#include<cstdio>
int main()
{
    const int a=98;

    int *p=NULL;
    p=(int *)&a;
    *p=100;

    printf("%d",a);
    return 0;
}

结果:

奇特的结果:
我们要是写了这样的函数在银行程序中,要是用不同编译器,那么对账就对不上了。
总结:
C中的const是个常变量,变量的值能够被间接修改。
C++中的const是一个真正的常量!

Tips:

以上两次测试,都没有显示warning和error

那么,我们或许会疑惑:
Q:我们说C++中的是一个真正的常量,那为什么,没有C++编译器对我们”用指针间接修改”的行为,没有报warning或者error呢?
A:C++要兼容C,所以,它认为这个语法是可以的
Q:那么问题又来了,那他既然兼容,那么为什么最后却没改变那个变量的值呢?
A:因为C++只是兼容那种语法写法,但是底层的实现却对const关键字进行了加强。

(2)底层原理分析

1、C++编译器对const做了一些加强,做了一些特殊的处理

当C++编译器,扫描到常量声明时,它不再像C语言那样,把这个const给它单独分配内存。

在我们先前的//test.cpp中。
C++进行了如下操作:

  • 1)扫描到这一行,const int a=98;
    C++编译器会把这个变量a放在一个符号表(键值-值对)里面
    此时,并没有分配内存!!!
    注意:这样的话,key和value是定了,不能修改的了。
    符号表具体的实现和我们的内存中的,栈,堆不是同一套实现机制。
    有很多常量就都放在这个里面了。
    Tips:
    当你去使用这个a的时候,它就给你从符号表里面给你把这个98给拿出来,供你使用
注意"使用"一词
  • 2)遇到类似这样的情况,此时才给a变量另外分配一个内存。
    扫描到这一句p=(int )&a;
    当你对这个a变量取地址的时候,C++编译器,会为这个a再单独的开辟一块内存空间,然后你把这个内存空间,赋给了p,相当于一个指针P指向了这里。然后你通过
    p去间接的修改的地址,不再是原来的值(value),而是我们新开辟的空间的值(注意理解)
    所以,当你再使用a的时候,你打印的还是98(符号表中的a)

2、证明

我们现在来证明这个开辟的内存空间的存在

//solution.cpp 
#include<cstdio>
int main()
{
    const int a=98;

    int *p=NULL;
    p=(int *)&a;
    *p=100; 

    printf("a=%d\n",a);
    printf("*p=%d",*p);
    return 0;
}

打印的是:

注意:

C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值,除非你用指针操作。

(3)结论和补充

1)C语言中的const变量
C语言中const变量是只读变量,有自己的存储空间
2)C++中的const常量

注意:可能分配存储空间,也可能不分配存储空间!

编译过程中若发现使用常量则直接以符号表中的值替换

Tips:
只有下面两种的时候,它才会分配空间

  • 当const常量为全局,并且需要在其它文件中使用,即使用了extern操作符
  • 当使用&操作符取const常量的地址,编译过程中若发现对const使用了&操作符,则给对应的常量分配存储空间(兼容C)

(4)补充疑问

Q:那么要是分配内存,C++中那个const的分配内存是在什么时候分配的呢?是在编译器编译阶段,还是在执行阶段分配?

A:C++中const分配内存的时机,是在编译期间!(记住!)

证明的代码:

//test.cpp 
#include<cstdio>
int main()
{
    int a;
    const int b=98;
    int c;
    printf("&a=%d\n",&a);
    printf("&b=%d\n",&b);//用了取地址 
    printf("&c=%d\n",&c);

    return 0;
}

结果表明:
const int b的地址在a和c之间,符合我们局部变量申请内存的压栈的顺序它并没有因为,&b这句话写到int c后面,就先分配a,c最后才b,而是,它扫描完之后,看到这里有&b了,就分配地址了。

(5)详解C++中const常见用途

C++对C语言的const关键字进行了增强!

特点:
C++中的const修饰的变量,变成了真正的常量,所以叫const常量

  • 区别:
  • 1)C语言中的const变量
    C语言中const变量是只读变量,有自己的存储空间
  • 2)C++中的const常量
    注意:可能分配存储空间,也可能不分配存储空间!

Tips:要想了解C++中const实现机制,请移步《C++中对C的const关键字增强》一文

1、const成员函数

int GetLength() const {return itslength;}  //成员函数声明为常量

当成员函数被声明为const时,如果这个const成员函数试图修改这个对象的数据,编译器将视为错误。
因为:你都和我约定了不能修改,那就得履行。

2、指向const对象的指针

如果声明了一个指向const对象的指针,则通过该指针只能调用const方法(成员函数)。

-因为:const就是用来规定,属于这个对象的是不能有改变的

  • const成员函数(常量成员函数),不会去修改这个对象任何非静态成员变量的值。
  • static成员函数(静态成员函数),也不会去修改这个对象任何非静态成员变量(毕竟,静态成员函数和静态成员变量其实本质是全局变量嘛,不需要作用在具体的对象上,我们写在类里面,只是为了彰显和这个类有紧密的关系)
  • 因此,我们指向const对象的指针,能够调用的是const成员函数和static成员函数
  • 普通成员函数,内部可以实现修改非静态成员变量的语句,所以可能会破坏const对象规定的,不能改变我的内部数据的原则
Rectangle* pRect = new Rectangle;
const Rectangle * pConstRect = new Rectangle;     //指向const对象
Rectangle* const pConstPtr = new Rectangle;
// pConstRect是指向const对象的指针,它只能使用声明为const的成员函数,如GetLength()。

3、const 修饰函数的返回值

函数返回值采用“引用传递”的场合,出现在类的赋值函数中,目的是为了实现链式表达。
例如:

class A
{
A & operate = (const A &other); // 赋值函数
};
A a, b, c; // a, b, c 为A 的对象
a = b = c; // 正常的链式赋值
(a = b) = c; // 不正常的链式赋值,但合法
//如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

4、补充

  • 非const对象可以访问任意的成员函数,自然包括const成员函数,静态成员函数
  • const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
  • const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查。
  • 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的

补充杂项:

  • const 修饰引用(&)修饰输入参数的用法:
  • 1-对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。当然,虽然这样的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
  • 2-对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。(如何理解???)
  • const 修饰星号(*)用法:
  • 如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例如函数
const char * GetString(void);
如下语句将出现编译错误:
char *str = GetString();
正确的用法是
const char *str = GetString();

(6)const导致的函数重载

1、C++不允许仅根据函数的返回类型重载函数名称;
2、可以编写两个名称相同,参数也相同的函数,其中一个是const,另一个不是。

  • 没用参数的两个函数是不能重载的.说法是不正确的,因为还有const