zoukankan      html  css  js  c++  java
  • C-C++到底支不支持VLA以及两种语言中const的区别

    C-C++到底支不支持VLA以及两种语言中const的区别

    到底支不支持VLA

    VLA就是variable-length array,也就是变长数组。

    最近写程序的时候无意间发现,gcc中竟然支持下面这种写法:

    int n = 10;

    int a[n];

    注意上面的语句是在函数内部写的,也就是n和a都是自动变量。

    当时十分疑惑,C语言中数组的长度不应该是常量或常量表达式吗?为什么变量也可以。我将代码在VC中跑了一下,发现编译出错,提示数组的大小未知,说明VC中是不支持VLA的。

    那既然有的编译器支持VLA,又有的编译器不支持VLA,那么C标准到底是怎样规定的呢?然后我看是看书、在网上查资料。

    C Primer Plus一书中是这样描述的:

    C90标准中并不支持VLA,C99开始支持VLA,很大的一个原因:FORTRAN中支持这种写法。C99中对对VLA有一些限制,比如变长数组必须是自动存储类型,也就是说,如果我上面两句放在函数外面就就不能通过编译了,这是因为在函数外面定义的是全局变量,此外,使用VLA不能对数组进行初始化,因为它的长度在运行时才能确定。

    此外VLA并不是真正的变长,它实际上只是将数组的长度推迟到运行时确定而已,也就是说C90标准中,数组的长度必须在编译时期就知道,但C99支持VLA后,数组的长度可以推迟到运行时知道,但是,一旦长度确定,数组的长度就不能变了。

    此外,网上的大神说,C++的标准中无论是C++90还是C++99还是C++11都不支持VLA的这种写法

    鉴于以上原因,在C语言中,如果想用变长的数组,还是老老实实用malloc分配吧,在C++中当然有更好的选择,就是vector,当然C++11中又推出了一个array,而且这两种都是真正的变长,也就是数组的长度随时都可以改变。

    下面我还想说一下C和C++中const关键字的区别。

    const关键字最早是C++中的产物,后来才引入到C语言中。const在C语言中和在C++中是相当不一样的。

    在C语言中const修饰的,被认为是一个只读的、或者叫不可改变的变量,它实际上只是一个变量,只不过对这个变量做了一些限制。而C++中const修饰的才是真正的常量。当然这其中还有很多细节的地方,有些地方我也还有些模糊,下面,我就通过例子,把自己已经理解的东西写出来。

    先来一个例子:

    const int a = 10;
    
    int array[a];
    
    int main()
    
    {
    
             return 0;
    
    }

    用gcc和g++编译的结果分别如下:

     

    可以看到gcc下不能通过编译,但是g++下可以通过,说明C语言中有错,在C++中没错。

    原因解释:

    首先说C语言中:

    首先说明,即使在支持VLA的编译器下,(我的gcc是支持的),前面提到了VLA数组是有限制的,VLA必须是自动存储类型,而上面的代码中数组是全局变量,所以并不存在是否支持VLA的问题。上面提到,在C语言中const被认为是一个受到一定限制的变量,是变量就要被分配数据区(或者运行时的栈区等)内存空间,由于a是全局变量,全局变量位于数据区,空间在编译时期就分配了。而,需要注意,编译时期,编译器是不能读取数据区的内存的(它可以分配数据区的内存,并初始化内存,但是不能从数据区的牛叉女内存中读取数据)。所以在编译时期,编译器其实并不知道a的值是什么,因为它不能读数据区的内存而a的值是在内存中的。但是,对于数组array编译器是一定要知道数组的长度才行的,也就是必须要知道a的值,这样就矛盾了,所以编译器就报错了!

    那在C++中有为什么能够通过呢?

    原因就是C++真的把const当成常量看待。

    详细解释一下:

    const int a = 10;这条语句中10是我们所说的字面量,无论是在C中还是在C++中字面量都是保存在代码段中,编译初期会将其保存在符号表中。C++尽量不对const分配数据区(或者运行时的栈区)的内存空间,只在必须分配内存时才分配(这个后面再说)。下面一条语句int array[a],编译器一定要知道a的值的,C语言要想知道a的值,必须读内存,但是C++却不需要,直接读取代码段中的的符号表即可,编译时期访问符号表是没有任何问题的,但是访问数据区的内存是做不到的。所以上面的语句在C++中是没有问题的。

    再来说说,什么叫尽可能不为const分配内存。

    如果代码是这样

    const int a = 10;
    
    const int *p = &a;
    
    int array[a];
    
     
    
    int main()
    
    {
    
             return 0;
    
    }

    注意 const int *p = &a;这句,对a取地址操作,我们知道位于代码段的数据是不取地址的,所以这个时候,只能给a在数据区分配空间了。

    于是就出现了新的问题,既然给a分配了数据区的空间,那是不是编译时期就不知道a的值了,因为毕竟编译时期是不能读取数据区的内存的,那么后面数组的定义也就不行了吧?但是答案却相反,依然可以,这是因为当编译器读a的值的时候,不是从数据区的内存中,而是程序段的符号表中读取的那个字面常量10。所以在编译实际依然能够确定数组的长度。

    下面的例子应该更能说明这个问题:

    #include <stdio.h>
    
    int main()
    
    {
    
             const int a = 10;
    
             int *pa = (int *)&a;
    
             printf("pa指向的地址为:%p  a的地址为:%p
    ",pa,&a);
    
             (*pa)++;
    
             printf("a = %d,*pa = %d
    ",a,*pa);
    
     
    
             return 0;
    
    }

    我们分别用gcc和g++编译他,然后分别看结果,如下:

     

    惊奇地发现,虽然都能顺利通过编译,但是C的执行和C++的执行竟然不一样!

    好吧,下面解释原因。

    还是要声明一下,C语言中const就是一个值不能改变的变量,就是个受限制的变量,但是,我们虽然我们不能通过a修改那块内存的值,但是我们可以通过指针间接去修改。这里要注意那个强制类型转换,如果不写强制类型转换,编译器就会报错,是允许将const int *赋值给int*的。在C++中,这一点和C是一样的,就是虽然我们不能通过a本身修改那块内存的值,但是我们可以通过指针间接去修改。但是为什么C和C++中的输出不一样呢?原因就是C++在读a的时候,其实是去代码段中读字面常量10去了,而C是读a所标识的那块栈区的内存。其实a所标识的内存的内容都已经变成11了,无论是C还是C++都是一样,区别就在于C读const数据和读普通变量一样,都是从数据段(如果是局部变量就是从栈区)读取数组,而C++却是读取代码段的字面常量!(间接修改const的时候,当然都是修改的数据区或栈区的内存,而不是代码段,因为代码段是只读的)

    所以C++中const修饰的可以认为就是常量!但是C语言中却不能这么认为。

    最后要小心C++中的const蜕变成C语言中的const。

    其实通过上面的分析,我们应该可以得出一个结论:C++中的const之所以和C语言中的const不一样,C++中的const之所以能够看成常量,就是因为C++在读取const的时候,实际上读取的是代码段的字面常量,而不是数据区(对于全局变量来说是静态区,对于局部变量来说是栈区)的内存中的数值。

    那么问题来了:如果const保存的不是一个字面常量呢?

    看下面代码:

    #include <stdio.h>
    
    int main()
    
    {
    
             int i = 10;
    
             const int a = i;
    
             int *pa = (int *)&a;
    
             printf("pa指向的地址为:%p  a的地址为:%p
    ",pa,&a);
    
             (*pa)++;
    
             printf("a = %d,*pa = %d
    ",a,*pa);
    
     
    
             return 0;
    
    }

    几乎还是同样的代码,只是先把字面常量10赋值给了变量i,然后用i初始化const int a,但是我们发现,在C++中,执行结果却变了。

     

    为什么呢?前面强调了,C++读取const的值,实际上是读取代码段的字面常量,那么,如果我们初始化const的时候,给它的不是字面量(或者是常量表达式),那么他就没有字面量可以读啦!这时候就只能退而求其次,去读数据区内存中的值啦!这个时候,C++中的const和C语言中的const就一样了。

    需要注意的是 sizeof是C和C++中的运算符,而且他的值通常都是在编译时确定的,可以认为是一个字面常量。

    比如:

    #include <stdio.h>
    
    int main()
    
    {
    
             const int a = sizeof(int);
    
             int *pa = (int *)&a;
    
             printf("pa指向的地址为:%p  a的地址为:%p
    ",pa,&a);
    
             (*pa)++;
    
             printf("a = %d,*pa = %d
    ",a,*pa);
    
     
    
             return 0;
    
    }

    此外在类中使用const修饰类成员变量的时候也要小心,因为也会退化成C语言中的const

    比如:

    #include <stdio.h>
    
    class A{
    
             public:
    
                      const int a;
    
                      int array[a];
    
                     
    
                      A(int i):a(i)
    
                      {
    
     
    
                      }
    
    };
    
    int main()
    
    {
    
             return 0;
    
    }

    编译器会报错:

     

    首先,类只是定义类型的地方,不能再类中初始化成员变量,所以在类定义中写const int a = 10是不对的,const成员的初始化要放在初始化列表中。我们知道,在对象创建之前才会调用构造函数进行对象的初始化,所以在编译时期我们根本就不知道a的值。所以把a当做数组的长度是有问题的。

    我们可以这样:

    #include <stdio.h>
    
    class A{
    
             public:
    
                      static const int a = 10;
    
                      int array[a];
    
                     
    
                      A()
    
                      {
    
     
    
                      }
    
    };
    
    int main()
    
    {
    
             return 0;
    
    }

    这样定义的a就可以当成一个常量来使用了。注意的是,这时候,a的语句不是声明了,而是定义+初始化,而且C++只允许这种情况可以在类内初始化,就是当变量被 static 和const修饰,且为int类型时。(当然这种情况依然可以在类外定义和初始化,只不过后面就不能用a定义数组的长度了,也会提示数组的长度不确定)

    简单总结一下就是:

    C语言中const就是一个受限制的变量,读取const时是从数据区的内存读取的(全局从静态去,局部从栈区),可以用指针间接修改其标识的数据区的内存区域,在读取const的值时,也是读取它标识的数据区的内存中的值。

    在C++中,const大多数情况下可以当成常量来使用,这是因为,虽然C++也会为const在数据区开辟内存(C++尽量不这样左),我们也可以通过指针(或者非常量的引用)来简介修改其标识的数据区的内存区域的值,但是,在读取const时,不是读取数据区的内存区域中的值(也很有可能根本就没有分配内存),而是读取代码段的字面常量。所以可以达到常量的效果。

    最后要警惕C++中const退化成C语言中的const,有两种情况,一种是初始化的const的时候是用变量初始化的,而不是字面常量(或常量表达式)。第二种情况就是const修饰类中成员变量的时候。

    给一个建议:如果我们想用const定义常量,那么我们就要用字面常量或常量表达式初始化const,而且要将const用static修饰(注意在C++中如果定义的是全局的const,默认为static修饰,但是在类中并不是这样的,这也是为什么我们在类中定义常量要用static修饰)。在类中定义常量的时候要同时用cosnt和static修饰,而且尽量在类的内部进行初始化。

    如果你觉得对你有用,请赞一个吧

  • 相关阅读:
    超详细:idea中同一项目同时使用Git与SVN,切换版本控制
    MailKit在.NetCore5下发送邮件 An error occurred while attempting to establish an SSL or TLS connection
    ClickHouse学习系列之七【系统命令介绍】
    ClickHouse学习系列之六【访问权限和账户管理】
    ClickHouse学习系列之五【系统库system说明】
    ClickHouse学习系列之四【副本&分片部署说明】
    MongoDB4.2 分片扫盲说明
    MongoDB4.2 副本集扫盲说明
    【工程应用一】 多目标多角度的快速模板匹配算法(基于NCC,效果无限接近Halcon中........)
    【工程应用四】 基于形状的多目标多角度的高速模板匹配算法进一步研究。
  • 原文地址:https://www.cnblogs.com/qingergege/p/7520565.html
Copyright © 2011-2022 走看看