const 关键字详解


一、const关键字简介

const含义:只要一个变量前用const来修饰,就意味着该变量里的数据只能被访问,而不能被修改,也就是意味着“只读”(readonly)。

const规则: const在谁后面谁就不可以修改,const在最前面则将其后移一位;const修饰一个变量时,一定要给这个变量初始化,若不初始化,在后面也不能初始化。

const作用:
1:可以用来修饰变量,修饰函数参数,修饰函数返回值,且被const修饰的东西,都受到强制保护,可以预防其它代码无意识的进行修改,从而提高了程序的健壮性(是指系统对于规范要求以外的输入能够判断这个输入不符合规范要求,并能有合理的处理方式。ps:即所谓高手写的程序不容易死);
2:使编译器保护那些不希望被修改的参数,防止无意代码的修改,减少bug;
3:增强代码的可读性,给读代码的人传递有用的信息,声明一个参数,是为了告诉用户这个参数的应用目的。


二、const的用法

1.修饰局部变量、全局变量以及字符串常量

我们通过一段代码来解读(选自:const 修饰的全局变量、局部变量以及字符串常量
代码如下(示例):

#include<stdio.h>                                                                                                                                  
#include<stdlib.h>
#include<string.h>
 
const int a = 10;   //const修饰的全局变量放在常量区

//1.const修饰的全局变量,即使语法通过,但是运行的时候会受到常量区的保护,段错误,运行失败
void test01()
{
    //a = 100;   //直接修改语法不通过
    int *p = &a;
    *p = 100;    //间接修改语法通过,运行时产生段错误
    printf("a  %d\n",a);
}

//2.const修饰的局部变量
void test02()
{
    const int b = 10;   //分配到栈上
    //b = 100;  //直接修改语法不通过
    
    //c语言下称为伪常量
    int *p = &b;
    *p = 100;
    printf("b  %d\n",*p); //间接修改成功
 
    //int a[b];  伪常量是不可以初始化数组的
}
 //3.字符串常量
void test03()
{
    char *p1 = "hello world";
    char *p2 = "hello world";
    char *p3 = "hello world";
    
    printf("%s\n",p1);
    printf("%s\n", p2);
    printf("%s\n", p3);
    printf("%s\n",&"hello world");   //四个输出的结果一样
 
    //p1[0] = 'z'; //不允许修改字符串常量
    printf("p1[0]   %c\n",p1[0]);  //可以输出
}

int main()
{
//  test01();
//  test02();
    test03();
    
    return 0;
}  

const修饰的普通变量:定义的时候就要给它赋初值,之后哪怕是赋相同值都不行。const修饰的局部变量还是变量,直接修改编译器报错,可以间接修改,存放在栈区,代码块结束时释放

const修饰全局变量:直接修改编译器报错,间接修改编译器也许会通过,但运行时会报错(段错误)。const修饰的全局变量存放在全局(静态)存储区,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放

注意:全局变量的作用域是整个文件,我们应该尽量避免使用全局变量,因为一旦有一个函数改变了全局变量的值,它也会影响到其他引用这个变量的函数,导致除了bug后很难发现,如果一定要用全局变量,我们应该尽量的使用const修饰符进行修饰,这样防止不必要的人为修改,使用的方法与局部变量是相同的。

const修饰字符串常量:字符串常量位于文字常量区(也有文章归类于代码区),本身就不允许被修改,如果没有const的修饰,我们可能会在后面有意无意的修改字符串常量,这样会导致对只读内存区域的赋值,然后程序会立刻异常终止。有了const,这个错误就能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译期被发现。

注意:
const修饰的变量的存储位置在C语言和C++中是有区别,这个区别在第四节中介绍。

2.修饰指针

常量指针、指针常量、指向常量的常指针

    //第一种
    const int *p1;      //p本身不是const的,而p指向的变量是const
    //第二种
    int const *p2;      //p本身不是const的,而p指向的变量是const
    //第三种
    int* const p3;     //p本身是const的,而p指向的变量不是const
    //第四种
    const int* const p4;        //p本身是const的,而p指向的变量也是const

1.常量指针,就是上面代码中的第一和第二种,即指向常量的指针。

#include <stdio.h>

int main()
{
    int a = 5;
    int b = 20; 
    const int *p = &a; 
//  *p = 100;   //编译器报错
    p = &b;     //完全可以

    printf("%d\n",*p); //间接修改成功                                                                                                              
    return 0;
}

需要注意的是以下两点:
①常量指针说的是不能通过这个指针改变变量的值,但是还是可以通过其他的方式来改变变量的值的。
②常量指针指向的值不能改变,但是这并不是意味着指针本身不能改变,常量指针可以指向其他的地址。

2.指针常量,代码第三种。

#include <stdio.h>

int main()
{
    int a = 5;
    int b = 20; 
    int *p = &a; 
    int* const n = &a; 
//  n = &b;		//error: assignment of read-only variable ‘n’                                                                                                                                     
    *p = 8;

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

指针常量是指指针本身是个常量,不能在指向其他的地址,需要注意的是,指针常量指向的地址不能改变,但是地址中保存的数值是可以改变的,可以通过其他指向该地址的指针来修改。

3.指向常量的常指针,代码第四种。
是以上两种的结合,指针指向的位置不能改变并且也不能通过这个指针改变变量的值,但是依然可以通过其他的普通指针改变变量的值。

3.修饰函数的参数

const修饰参数是为了防止函数体内可能会修改参数原始对象。因此,有三种情况可讨论:

1.函数参数为值传递:值传递(pass-by-value)是传递一份参数的拷贝给函数,因此不论函数体代码如何运行,也只会修改拷贝而无法修改原始对象,这种情况不需要将参数声明为const。
例如:void func(int x)不用写成void func(const int x)

2.函数参数为指针:指针传递(pass-by-pointer)只会进行浅拷贝,拷贝一份指针给函数,而不会拷贝一份原始对象。根据上面对指针常量、常量指针等讨论,同样分为三种情况:
①防止修改指针指向的内容
典型C库函数:char *strcpy(char *dest, const char *src);

②防止修改指针指向的地址
void swap ( int * const p1 , int * const p2 );指针p1和指针p2指向的地址都不能修改。

③防止修改指针指向的内容和地址

3.函数参数为引用(C++中):引用传递(pass-by-reference)有一个很重要的作用,由于引用就是对象的一个别名,因此不需要拷贝对象,减小了开销。这同时也导致可以通过修改引用直接修改原始对象(毕竟引用和原始对象其实是同一个东西),因此,大多数时候,推荐函数参数设置为pass-by-reference-to-const。给引用加上const,既可以减小拷贝开销,又可以防止修改底层所引用的对象。

4.修饰函数的返回值

1.如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。例如把函数int GetInt(void) 写成const int GetInt(void)是没有意义的。

2.如果给以“指针传递”方式的函数返回值加 const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。

const char * GetString(void);
char *str = GetString();	//error: conflicting types for ‘str’
const char *str = GetString();		//这种用法才是正确的

3.但是返回值不是内部数据类型,例如:A get_string(void),这样会产生一个临时的对象用来存放返回的数据,会调用拷贝构造函数,这样效率会低,所以采用“引用传递”A &get_string(void),如果加上const那么返回值的内容就不会被改变const A &get_string(void)

这里对函数返回值使用 const 的目的在于限制不能将函数调用表达式作为左值使用。
例如有如下函数:int & min ( int &i, int &j);可以对函数调用进行赋值,因为它返回的是左值:min ( a , b )=4;但是,如果对函数的返回值限定为 const 的,即定义为:const int & min ( int & i, int &j );那么,就不能对 min ( a, b ) 调用进行赋值了。事实上,函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
例如:

class A
{
	A &amp; operate = (const A &amp; other); // 赋值函数
};
	A a, b, c; // a, b, c 为A 的对象
	a = b = c; // 正常的链式赋值
	(a = b) = c; // 不正常的链式赋值,但合法

如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

5.const成员函数(C++)

1.任何不会修改成员的函数都应该声明为const类型。
例如计数函数不会对成员进行修改所以int get_count(void)const;注意const成员函数的声明将const放到函数的尾部。
2.const成员函数不可以修改对象的数据。
const对象只能访问const成员函数,非const对象可以任意访问任意的成员函数。
3.

class A
{
	public:
	int get_count(int)const;
}

int get_count(int)const,准确的说这个const修饰的是this指向的对象,其实get_count(int)这个函数在调用方法时会被编译器改写成get_count(A *const this, int)const;为了不允许get_count()这个函数改变this指向的对象,则用const修饰这个函数,因此this指针的类型变为const A *const this。

参考文章:
C语言中const关键字的用法
C++ const关键字的总结
const修饰函数参数,返回值,成员函数


三、const与define区别

关键字const用来定义常量,如果一个变量被const修饰,那么它的值就不能再被改变,我想一定有人有这样的疑问,C语言中不是有#define吗,干嘛还要用const呢,我想事物的存在一定有它自己的道理,所以说const的存在一定有它的合理性,与预编译指令相比,const修饰符有以下的优点:

1、预编译指令只是对值进行简单的替换,不能进行类型检查

2、可以保护被修饰的东西,防止意外修改,增强程序的健壮性

3、编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高


四、const在C语言中和C++中的区别

区别一:
C语言中const是定义了一个变量,是一个伪常量,该变量只具备读的功能;
C++中const是定义了一个常量,具备写的功能。

const int a = 5;
int array[a]; //在C语言中编译错误,因为a是一个变量
int array[a];//在C++中正确,因为a是一个常量

我们再来看一段相同的代码在C和C++中运行有什么不同(gcc\g++):

#include <stdio.h>

int main()
{
    const int a = 10; 
    int *p = NULL;
    printf("%d\n", a); 
 
    p = (int *) &a;                                                                                                                                
    *p = 20; 
    printf("%d\n", a); 
}

C语言中编译运行结果:
10
20

C++中编译运行结果:
10
10

通过指针p间接修改a的值,如果在C语言编译器中,则打印20,常量a被修改,所以C语言中const是一个冒牌货;但是在c++中此处打印的结果依然是10,所以c++中的const才是真正意义上的常量。
在C语言中定义一个const变量a,它是一个伪常量,存放在栈区,可以通过指针间接修改;
在C++中定义一个const变量a,相当于定义了一个常量,这个变量将会被放到一个符号表中,没有给a分配空间,编译过程中若发现对const使用了extern或者&操作符,即当a在别的文件中被调用时或者被指针间接访问时才会分配空间,但是这个空间中的值和常量a是两回事,通过地址修改它不能真正的修改符号表中的常量a!

区别二:
const修饰的变量在C语言和C++中存储位置不同。

c语言中:
const全局变量存储在只读数据段,编译期最初将其保存在符号表中,第一次使用时为其分配内存,在程序结束时释放;
而const局部变量(局部变量就是在函数中定义的一个const变量,)存储在栈中,代码块结束时释放。
在c语言中可以通过指针对const局部变量进行修改,而不可以对const全局变量进行修改。因为const全局变量是存储在只读数据段。

c++中:
在c++中是否要为const全局变量分配内存空间,取决于这个const变量的用途,如果是充当着一个值替换(即就是将一个变量名替换为一个值),那么就不分配内存空间,不过当对这个const全局变量取地址或者使用extern时,会分配内存,存储在只读数据段,也是不能修改的。
c++中对于局部的const变量要区别对待:
对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存,如果用一个变量初始化const变量,如const int a = b,那么也是会给a分配内存。
对于自定数据类型,比如类对象,那么也会分配内存。

摘自文章:
const修饰的变量的存储位置

区别三:
C语言中不能定义const函数;
C++中可以定义const函数,C++中的const成员函数:不能修改类的成员变量的值。

#include <iostream>
using namespace std;
                                                                                                                                                   
class Date
{
public:
    Date(int year = 2018, int month = 6, int day = 23) 
        : _year(year)
        , _month(month)
        , _day(day)
    {}  
    
    const int& GetDay()const //被const修饰后,this指针类型从Date* const this-->const Date* const this
    {   
        return _day;
    }   
private:
    int _year;
    int _month;
    int _day;
};
 
int main()
{
    Date d1; 
    cout << d1.GetDay() << endl;
}

上面的Getday()方法中,如果确实想要修改某个成员变量的值,也不是不可以,可以在想要修改的成员变量前加mutable关键字。

(该段摘自:c中const和c++中const的区别


五、const与static修饰变量的区别

static修饰的变量称为静态变量,存储在全局(静态)区,生命周期从程序编译到运行结束。

const修饰的变量并不会存放在全局(静态)区,而是取决于它定义的地方,局部定义的就存在栈区,全局定义的就存放在静态区。

可能有的人会疑惑:const修饰的变量不是不能修改的么,是只读的,那应该存在只读数据区啊。事实上并不是这样,C语言中,使用const修饰了一个变量,该变量不能直接修改,但是我们可以通过拿到这个变量的地址,然后通过它的地址来修改它。
(当然了,如果const修饰的是文字常量区的常量,如字符串常量,那么是无论如何也修改不了的)

  • 39
    点赞
  • 307
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值