zoukankan      html  css  js  c++  java
  • const关键字到底该怎么用

    原文地址:https://www.yanbinghu.com/2019/01/28/7442.html

    前言

    我们都知道使用const关键字限定一个变量为只读,但它是真正意义上的只读吗?实际中又该如何使用const关键字?在解答这些问题之前,我们需要先理解const关键字的基本使用。本文说明C中的const关键字,不包括C++。

    基本介绍

    const是constant的简写,是不变的意思。但并不是说它修饰常量,而是说它限定一个变量为只读。

    修饰普通变量

    例如:

    const int NUM = 10; //与int const NUM等价
    NUM = 9;  //编译错误,不可再次修改

    由于使用了const修饰NUM,使得NUM为只读,因此尝试对NUM再次赋值的操作是非法的,编译器将会报错。正因如此,如果需要使用const修饰一个变量,那么它只能在开始声明时就赋值,否则后面就没有机会了(后面会讲到一个特殊情况)。

    修饰数组

    例如使用const关键字修饰数组,使其元素不允许被改变:

    const int arr[] = {0,0,2,3,4}; //与int const arr[]等价
    arr[2] = 1; //编译错误

    试图修改arr的内容的操作是非法的,编译器将会报错:

    error: assignment of read-only location ‘arr[2]’

    修饰指针

    修饰指针的情况比较多,主要有以下几种情况:
    1.const 修饰 *p,指向的对象只读,指针的指向可变:

    int a = 9;
    int b = 10;
    const int *p = &a;//p是一个指向int类型的const值,与int const *p等价
    *p = 11;    //编译错误,指向的对象是只读的,不可通过p进行改变
    p = &b;     //合法,改变了p的指向

    这里为了便于理解,可认为const修饰的是*p,通常使用*对指针进行解引用来访问对象,因而,该对象是只读的。

    2.const修饰p,指向的对象可变,指针的指向不可变:

    int a = 9;
    int b = 10;
    int * const p = &a;//p是一个const指针
    *p = 11;    //合法,
    p = &b;     //编译错误,p是一个const指针,只读,不可变

    3.指针不可改变指向,指向的内容也不可变

    int a = 9;
    int b = 10;
    const int * const p = &a;//p既是一个const指针,同时也指向了int类型的const值
    *p = 11;    //编译错误,指向的对象是只读的,不可通过p进行改变
    p = &b;     //编译错误,p是一个const指针,只读,不可变

    看完上面几种情况之后是否会觉得混乱,并且难以记忆呢?我们使用一句话总结:
    const放在*的左侧任意位置,限定了该指针指向的对象是只读的;const放在*的右侧,限定了指针本身是只读的,即不可变的。

    如果还不是很好理解,我们可以这样来看,去掉类型说明符,查看const修饰的内容,上面三种情况去掉类型说明符int之后,如下:

    const *p; //修饰*p,指针指向的对象不可变
    const p; //修饰p,指针不可变
    const * const p; //第一个修饰了*p,第二个修饰了p,两者都不可变

    const右边修饰谁,就说明谁是不可变的。上面的说法仅仅是帮助理解和记忆。借助上面这种理解,就会发现以下几种等价情况:

    const int NUM = 10; //与int const NUM等价
    int a = 9;
    const int *p  = &a;//与int const *p等价
    const int arr[] = {0,0,2,3,4}; //与int const arr[]等价

    const关键字到底有什么用

    前面介绍了这么多内容,是不是都常用呢?const关键字到底有什么用?

    修饰函数形参

    实际上,为我们可以经常发现const关键字的身影,例如很多库函数的声明:

    char *strncpy(char *dest,const char *src,size_t n);//字符串拷贝函数
    int  *strncmp(const char *s1,const char *s2,size_t n);//字符串比较函数

    通过看strncpy函数的原型可以知道,源字符串src是只读的,不可变的,而dest并没有该限制。我们通过一个小例子继续观察:

    //test.c
    #include<stdio.h>
    void myPrint(const char *str);
    void myPrint(const char *str)
    {
        str[0] = 'H';
        printf("my print:%s ",str);
    }
    int main(void)
    {
        char str[] = "hello world";
        myPrint(str);
        return 0;
    }

    在这个例子中,我们不希望myPrint函数修改传入的字符串内容,因此入参使用了const限定符,表明传入的字符串是只读的,因此,如果myPrint函数内部如果尝试对str进行修改,将会报错:

    $ gcc -o test test.c
    test.c:6:12: error: assignment of read-only location ‘*str’
         str[0] = 'H';

    因此,我们自己在编码过程中,如果确定传入的指针参数仅用于访问数据,那么应该将其声明为一个指向const限定类型的指针,避免函数内部对数据进行意外地修改

    修饰全局变量

    我们知道,使用全局变量是一种不安全的做法,因为程序的任何部分都能够对全局数据进行修改。而如果对全局变量增加const限定符(假设该全局数据不希望被修改),就可以避免被程序其他部分修改。这里有两种使用方式。
    第一种,在a文件中定义,其他文件中使用外部声明,例如:
    a.h

    //a.h
    const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定义int数组

    b.c

    //b.c
    extern const int ARR[];   //注意,这里不能再对ARR进行赋值
    //后面可以使用ARR

    第二种,在a文件中定义,并使用static修饰,b文件包含a文件,例如:
    a.h

    //a.h
    static const int ARR[] = {0,1,2,3,4,5,6,7,8,9};  //定义int数组

    b.c

    //b.c
    #include<a.h>
    //后面可以使用ARR

    注意,这里必须使用static修饰,否则多个文件包含导致编译会出现重复定义的错误。有兴趣的可以尝试一下。

    const修饰的变量是真正的只读吗?

    使用const修饰之后的变量真的是完全的只读吗?看下面这个例子:

    #include <stdio.h>
    int main(void)
    {
        const int a = 2018;
        int *p = &a;
        *p = 2019;
        printf("%d ",a);
        return 0;
    }

    运行结果:

    2019

    可以看到,我们通过另外定义一个指针变量,将被const修饰的a的值改变了。那么我们不禁要问,const到底做了什么呢?它修饰的变量是真正意义上的只读吗?为什么它修饰的变量的值仍然可以改变?

    #include<stdio.h>
    int main(void)
    {
        int a = 2019;
        //const int a = 2019;
        printf("%d ",a);
        return 0;
    }

    我们分别获取有const修饰和无const修饰的汇编代码。
    无const修饰,汇编代码:

    .LC0:
            .string "%d "
    main:
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16
            mov     DWORD PTR [rbp-4], 2019
            mov     eax, DWORD PTR [rbp-4]
            mov     esi, eax
            mov     edi, OFFSET FLAT:.LC0
            mov     eax, 0
            call    printf
            mov     eax, 0
            leave
            ret

    有const修饰,汇编代码:

    .LC0:
            .string "%dn"
    main:
            push    rbp
            mov     rbp, rsp
            sub     rsp, 16
            mov     DWORD PTR [rbp-4], 2019
            mov     eax, DWORD PTR [rbp-4]
            mov     esi, eax
            mov     edi, OFFSET FLAT:.LC0
            mov     eax, 0
            call    printf
            mov     eax, 0
            leave
            ret

    我们发现,并没有任何差异!当然这一个例子并不能说明所有的问题。但是我们要知道的是,const关键字告诉了编译器,它修饰的变量不能被改变,如果代码中发现有类似改变该变量的操作,那么编译器就会捕捉这个错误。

    那么它在实际中的意义之一是什么呢?帮助程序员提前发现问题,避免不该修改的值被意外地修改,但是无法完全保证不被修改!例如我们可以通过对指针进行强转:

    #include<stdio.h>
    void myPrint(const char *str);
    void myPrint(const char *str)
    {
        char *b = (char *)str;
        b[0] = 'H';
        printf("my print:%s ",b);

    }
    int main(void)
    {
        char str[] = "hello world";
        myPrint(str);
        return 0;
    }

    运行结果:

    my print:Hello world

    也就是说,const关键字是给编译器用的,帮助程序员提早发现可能存在的问题。
    但是实际中永远不要写这样的代码!

    总结

    介绍了这么多,关键点如下:

    • const关键字让编译器帮助我们发现变量不该被修改却被意外修改的错误。
    • const关键字修饰的变量并非真正意义完完全全的只读。
    • 对于不该被修改的入参,应该用const修饰,这是const使用的常见姿势。
    • const修饰的变量只能正常赋值一次。
    • 不要试图将const数据的地址赋给普通指针。
    • 不要忽略编译器的警告,除非你很清楚在做什么。
    • 虽然可以通过某种不正规途径修改const修饰的变量,但是永远不要这么做

    思考

    • 与#define相比,const有什么优点?
    • const关键字到底该什么时候用?

    微信公众号【编程珠玑】:专注但不限于分享计算机编程基础,Linux,C语言,C++,算法,数据库等编程相关[原创]技术文章,号内包含大量经典电子书和视频学习资源。欢迎一起交流学习,一起修炼计算机“内功”,知其然,更知其所以然。

  • 相关阅读:
    Windows7与Window2008 64位IIS7上面DCOM配置Excel、Word
    C#连接SQLite的...方法
    VS2010版快捷键
    ajax 安装包下载
    Type InvokeMember()用法简介
    Lambda表达式
    多源最短路径算法
    单源点有权图的最短路径算法
    单源无权图的最短路径算法
    Tree Traversals Again
  • 原文地址:https://www.cnblogs.com/bianchengzhuji/p/10335837.html
Copyright © 2011-2022 走看看