• c/c++ 函数、常量、指针和数组的关系梳理


    压力才有动力,15年中旬就要准备实习,学习复习学习复习学习复习学习复习……无限循环中,好记性不如烂笔头……从数组开始,为主干。

    c 的array由一系列的类型相同的元素构成,数组声明包括数组元素个数和类型,c 中的数组参数是引用形式传参(传址调用),而常量标量是按值传递。

    //[]方括号表示声明的是数组,里面的数字表明了数组包含的元素数目
        int states[50];//声明50个整数的数组
        double code[365];//声明365个浮点数的数组
        char chr[20];//声明20个字符的数组

    数组下标,不同语言不一定一样,c 是0开始。

    ANSI C 才支持的 数组的标准初始化

    int pow[8] = {1, 2, 3, 4, 5, 6, 7, 8};//只有标准c(ANSI C)支持此初始化语法

    注意:数组声明的时候最好是初始化,虽然不会报错,但是和普通变量一样,使用没有初始化的数组,里面元素的值不定,出现垃圾值。

    数组初始化列表和大小不一致的情况

    初始化列表里的元素个数和数组大小不一致,当初始化元素数目少于数组大小,多余的元素自动初始化为0

    注意区分:如果没有初始化数组,那么和普通变量一样,存取的内存原来就有的垃圾值。初始化了,只不过部分初始化,那么编译器会自动初始化余下的元素为0。初始化了,但是初始化列表元素大于数组大小,那么编译报错,内存溢出。

    //初始化没有完全,编译器自动后面给初始化为0
        double d[4] = {1};//不过vs2010里编译运行,全都是0
        //error C2078: 初始值设定项太多
        double d[4] = {1, 2, 3, 4, 5, 6}; 

    数组大小可以用整型常量(unsigned)或者字符常量来指定大小,C99之前就是这两种方法。

        const int const_m = 10;//在c语言(不同于c++)里,const值不被看作是常量
        int n = 100;//定义了变量n
    
        double d1[10];// ok
        double d2[5 * 2 + 1];//ok
        double d3[];//error,没有初始化,也没有大小指定
        double d4[sizeof(int)];//ok,sizeof表达式在c里被认为返回一个整数常量
        double d5[-10];//error C2118: 负下标
        double d6[0];//error C2466: 不能分配常量大小为 0 的数组
        double d7[3.14];// error C2058: 常量表达式不是整型
        double d8[(int)3.14];//ok
    
        double d9[const_m];// error C2057: 应输入常量表达式,“d9”未知的大小,不能分配常量大小为 0 的数组
        double d10[n];//error C2057: 应输入常量表达式,“d9”未知的大小,不能分配常量大小为 0 的数组

    c99之后,后两种方式可以了,并且C99有了一种新的数组的名词,叫VLA(variable length array)变长数组,VLA。目的是为了让c更适合做数值计算。

    数组初始化小技巧

    省略填写数组大小,让编译器自动的去判断数组的实际大小和初始化列表里的项目

    //空的方括号告诉编译器去初始化列表里判断数组实际大小
        char chr[] = {'a', 'b', ' ', '4'};
        int i;
        //这里需要一个技巧,人工判断数组大小容易出错,使用sizeof运算符计算
        for (i = 0; i < sizeof(chr) / sizeof(chr[0]); i++)
        {
            printf("
    %d=%c", i + 1, chr[i]);
        }
    #define NUM 4//采用标识符常量代表数组大小,推荐的技巧
        int days[NUM] = {1, 2, 3, 4};//良好的编程风格,如果以后想修改数组大小,只需要修改开头的常量即可

    知道C99的新特性:对数组指定元素初始化

    可以对数组指定的元素直接初始化,如果对数组最后一个元素初始化,那么传统语法必须顺次初始化到最后一个元素前,才能对最后一个元素初始化。

    //对最后一个元素初始化为1
        float f[3] = {0, 0, 1};

    而c99规定可以直接去初始化

    //使用方括号【】,直接对数组某个元素初始化
        int i[3] = {i[2] = 100};//ok!需要加  数组名【】

    对于一般的数组初始化,部分初始化之后,余下的自动初始化为0(这里vs2010编译器不是这样的,全部都是0 了),如果在指定的初始化元素后还有别的初始化值,那么这些数值自动对数组后续元素进行初始化,并且指定初始化元素的数值在第几个元素处,那么也会同样初始化这个元素。

    int in[7] = {1, 2, in[2] = 10, 3, 4, in[0] = 11};   //首元素=11

    只读数组

    如果只需要对数组读取,而不进行修改,那么推荐关键字const,

    const int days[NUM] = {1, 2, 3, 4};//数组中每个元素都当作常量处理,和普通变量一样,const数组也需要声明的时候初始化

    数组的赋值

    使用数组的下标(索引)为数组元素赋值,c不支持整体赋值也不支持用花括号括起来的列表进行赋值(初始化除外)这是个注意点

        double d[SIZE] = {1, 2, 3};//ok这是可以的,列表进行初始化
        int i[SIZE], count;
        i = d;//error,c不支持数组整体赋值
    d[SIZE] = {11, 22, 33};//不起作用,这种形式只有初始化可以使用,赋值必须使用索引

    数组的边界问题

    数组索引不能越界,因为编译器不会检测这种错误。如果出现非法的索引,那么结果未知,有时候会中断,但是也有时候可以执行,但是结果很奇怪(编译器不同而不同)

    编译器不检测数组边界,是出于信任程序员,可以节省时间和效率。

    二维数组(注意:其实c 语言中只有一维数组,英文里没有多维数组的说法,仅仅是数组的数组)

    数组的数组,因为数组的内容在内存是连续存储的。

    double d[5][12];//定义一个数组的数组(二维数组),5个由12个浮点数组成的数组的数组

    d[5] 是一个包含5个元素的一维数组,而其中5个元素的每一个元素又是一个包含12个浮点数的数组,也就是说,数组d的每个元素类型都是double[12],这里要注意区分。首元素就是d[0][0],是double类型的,二维数组有5行,每行包含12列。改变第二个下标是沿着行移动,改变第一个下标是沿着列垂直移动。且数组是顺序存储的。

    二维数组的初始化

    int i[3][3] = {
            {1, 2, 3},
            {4, 5, 6},
            {7, 8, 9}
        };

    数组的数组进行初始化,外层的每一维数组初始化列表都用花括号括起来,而且中间也是用逗号隔开.对于一维数组的初始化问题,同样适用于二维数组,比如第一行列表里元素少了,那么剩余的元素自动初始化为0,如果多了,同样报错,而且一行和一行独立的,不影响以后的赋值.且 c 的数组是行优先存储。

    也可以省略内部的括号,只要最外面那层的括号,但是要保证正确.

    int i[3][3] = {
            1, 2, 3,
            4, 5, 6,
            7, 8, 9
        };//不建议,因为多了容易出错

    注意:多维数组初始化的两种方式效果不同

    //第一种就是内部初始化列表也带括号的
        int i[2][3] = {
        
            {4, 5},
            {7, 8}
        }; //很明显,初始化列表不够数组大小

    还有一种内部不带花括号

        int i[2][3] = {
        
            4, 5,
            7, 8
        }; //很明显,初始化列表不够数组大小

    多维(二维以上)数组

        double d[2][3][4];//三维数组的声明形式,更多维也一样

    通常处理一维数组用一层循环,二维数组用两层循环,多维用多层循环,以此类推,他们的初始化是一样的,大同小异。不过多维的用的不是太多。

    数组和指针的联系

    数组名可以当指针用,此时数组名的值是一个指针常量,是数组的第一个元素的内存地址。针对这几个常见的名词:常量指针,指针常量,常指针,指向常量的指针,指向常量的常量指针,和指向普通变量的常量指针,说明:经个人的学习和理解,查阅资料,我还是认同《c++ primer》、<pointers on c>、<c primer>中各位作者的叫法。并不赞同,一些博客或者网友的说法,貌似以前的老师也没这么教过。

    『指针常量』在 c 和 c++中饼没有专用的写法,很少直接用到。

    肯定的是:数组名当指针用的时候,这个指针的内容是一个地址,也就是指针类型的常量,而不是其他的整型或者浮点等基本数据类型的常量,不要和数学概念混。

    比如,有一个int 类型的变量x,假设存储在内存的0x11ff位置:

        //打算把100这个常量赋值给变量 x,报错
        *0x11ff = 100;

    提示间接的需要指针操作符。这是因为间接访问操作符*,必须跟指针类型的表达式结合,而0x11ff是字面值常量,是整型。那么修改为指针类型:

        //使用c 的强制类型转换,把0x11ff 从整型转换为了指向整型的指针类型,它唯一的用处就是当程序需要访问内存里一些特定的位置时,比如直接访问硬件的一些特定接口(地址固定)。那么可以使用,其他不行,因为我们无法预测编译器会把变量放在何处。
        *(int *)0x11ff = 3.14;

    数组名是这个数组首元素的地址,是一个指向某类型的常指针(或者说成指针常量),但是数组和指针是不同的!

    数组是包含多个元素的一种存储结构,通过数组名来标记。指针仅仅是一个标量而已。只有当数组在表达式(确切的说是数组在函数定义头,函数声明(当做参数传递),或者类似*(array + size等)使用,编译器才会为数组生成一个指针常量,用数组名标记这个常量。故不要把数组名和指针经常性的混为一谈。且静态存储的数据结构,程序运行期间,内存地址固定不变,比如c 和 c++的数组。

    数租在表达式里使用的例外情况

    使用 sizeof 操作符,数组名不是指针常量,求的是数组占据的内存字节数

        //长度为3的整型数组 array
        int array[3];
        printf("%d 
    ", array);//array 是指针,打印数组的手地址
        printf("%d 
    ", sizeof(array[0]));//打印的是数组中的单个元素的大小,int 类型 4个字节  32位
        printf("%d 
    ", sizeof(array));//打印数组的大小(不是元素个数),占据的内存字节数,12

    1606416396 (如果是%p解析是 :0x7fff5fbff80c )

    12 

    使用&操作符,数组名不是指针常量

        int array[10] = {0};
        int *p = &array[0];
        printf("%p 
    ", p);//打印的是数组手地址
        
        int *pp = array;
        printf("%p 
    ", pp);//打印的是数组手地址
        
        //对数组明取地址操作,如果数组名是解释为了指针常量,那么q 就是指向指针常量的指针
        int *q = &array;//那么*q就是指针常量了!?
        printf("%p 
    ", *q);//打印的0,不对

    此时的数组名,是一个指向数组的指针!指向的是一个地址数组。

        //对数组明取地址操作
        int (*q)[10] = &array;

    指向的是一个1行10列的二维地址数组。这还关系到了多维的数组和指针的关系。

    先看一维数组和指针的关系

        //SIZE值是3
        short dates[SIZE];
        short *pi;
        short index;
        double bills[SIZE];
        double *pf;
        //pi = dates;//把数组地址(首元素的地址)赋值给指针变量
        pi = &dates[0];
        pf = bills;//有两种方式

    下面的值是等价的

        dates == &dates[0];
        dates + 2 == &dates[2];
        *(dates + 2) ==dates[2];

    这些都说明c标准在描述数组的时候,借助了指针,他们有密切联系

        int i[SIZE];
        *(i + SIZE);//意思是寻址到内存中的i(数组首元素地址),然后继续找SIZE个单位,找到该地址再取出里面存放的数值

    注意:间接运算符*的优先级高于+

    *i + 10;//意思是整型数组i的首元素的值(一个整数)和整数10相加的和
    *(i + 10);//意思是i数组的第11个元素的值

    c 的数组不能用=进行复制:只能是循环遍历每个元素进行赋值,如果直接赋值数组名,仅仅是把指针常量的一个拷贝赋值了。

    数组的下标范围不被编译器检测(不仅仅是 c 和 c++,很多其他语言也是)

    数组的下标一般都认为是不能为负数的,要小于0,也不能大于数组的维数(长度-1),但是有一个情况是特殊的

        int array[5] = {0, 1, 2, 3, 4};
        int *p = array + 1;//p 指针指向了 array 的第2个元素,1
        //p[]也是数组
        p[0] = p[3];
        printf("%d 
    ", p[0]);//打印4
        //向前偏移了1,那么就是0元素
        printf("%d 
    ", p[-1]);//打印0,因为 c 的数组的下标引用和间接访问方式*是等价的
        p[-10] = 10;//编译器没报错,但是这样确实是非法操作,编译器并不检测数组的下标范围
        p[100] = 2;//非法

    c 的数组下标不被编译器检测,如果越界,我们无法得知编译器把它指向了哪里,可能造成不可估量的错误。因为检测的开销太大了。

    另一种数组下标引用的写法

       int a[5] = {0};
        2[a] = 1;

    完全正确的,说明编译器内部实现下标的方法逻辑里,是包含这样的特例的。但是不能那么写。不规范。等价于 a[2],*(a + 2)

    函数里的数组和指针

    int sum(int *i, int n);

    或者这样声明

    int sum(int i[], int n);

    main 函数代码段

    int in[5] = {1, 2, 3, 4, 5};
    printf("%d", sum(in, 5));

    第一个参数把数组首地址和数组的类型传递给了函数sum,第二个参数把数组大小传递给函数

    当且仅当在给函数原型声明或者函数定义的时候可以用int *i代替 int i[],任何情况下,int *i;都表示i是指向int类型的指针,而int i[];可以表示i是指向int的指针(只在函数原型声明和函数定义的时候可用),还有可以表示i是数组i的首地址。

    必须掌握如下四个等价方式函数原型声明可以省略参数名字),注意只有在函数原型声明或者函数定义头的时候int *i和int i[]是等价的。

    int sum(int i[], int n);
    int sum(int [], int);//因为可以省略参数名,故数组名可以省去
    int sum(int *i, int n);
    int sum(int *, int);//指针名可以省去

    而函数定义的时候不能省略参数名字,必须掌握这两个形式

    int sum(int *ii, int num){}
    int sum(int ii[], int num){}

    需要明白的是:指针不论在什么情况下,对于采用四字节地址的计算机系统,大小都是4个字节,和其他无关,只和计算机系统有关。

    sum(in, in + SIZE);

    语句里in是数组首元素地址,而数组索引从0开始,那么加size之后实际指向的是end元素的下一位。如果指向最后一位,应为:

    sum(in, in + SIZE - 1);

    一个问题:判断优先级

    total += *start ++; //一元运算符*和++具有相同的优先级,结合从右向坐。注意到这里是后缀自增,那么应该是先把指针指向的数据加到total,然后指针自增,等价于循环体里的两行代码效果

    如果是前缀自增,那么就是

    total += *++start ;

    指针先自增,然后再用自增之后指向的值累加到total。如果是

    total += (*start )++;

    那就是赋值完成之后,指针指向的内容自增,而不是指针变量自增,效果是指针指向的地址不变,但里面的元素变了。

    C语言中,i[SIZE]和*(i + SIZE)是等价的

    不论i是数组名还是i是指针变量。不过注意:只有当i是一个指针变量的时候,才可以使用诸如:i++的运算

    对数组内容的保护 

    当函数传入的参数需要改变,那么就必须传递指针(c++引用),处理数组的时候,原则上是必须用指针的,虽然也可以用值,那么函数必须先为原数组的拷贝分配一个足够的内存空间,再把原数组的数据写入到这个空间的新数组里,效率很低。故用指针进行传递,然函数去直接操作原数组,方便快捷。

    对形式参数使用const修饰

    如果设计操作数组的函数的初衷是不修改原数据,那么可以在函数原型声明和定义头里使用const关键字

    int sum(const int arr[], int n);

    告诉编译器:arr(其实arr是数组首元素地址)指向的数组是包含的常量元素,不能修改,如果修改就报错。注意:这样定义的话,只是在函数里对原数组不改变,在外面就不是常量对待了。c 里,const关键字可以定义符号常量(c99新规定,以前就是#define),还可以创建数组常量,指针常量,和指向常量的指针

    const int array[2] = {1, 2};//数组常量
    array[0] = 2;//error C2166: 左值指定 const 对象

    指向常量的指针

    double arrayD[5] = {1, 2, 3, 4, 5};
    const double *pd = arrayD;//指向常量的指针,指针pd指向了数组的开头,pd可变,*pd是常量,不可变。

    通常把指向常量的指针作为函数参数,说明函数不会用这个指针修改数据。

    注意区别:普通指针和指向常量的指针

    把常量或者非常量的数据赋值给指向常量的指针都是可以的(因为本身指针指向的是常量),但是对于普通指针,只能给它赋值非常量地址

    指针和二维数组的关系

         int arrayInt[4][2];//整型数组的数组
        //数组名arrayInt同时也是数组首元素地址,而本数组的首元素又是包含两个int元素的数组
        //故数组名arrayInt也是包含两个int类型元素的数组的地址
    
        arrayInt == &arrayInt[0];//ok的
    
        //而arrayInt[0]又是一个包含两个int元素的数组
        arrayInt[0] == &arrayInt[0][0];//ok

    arrayInt【0】是第一行里,第一个整数元素的地址,而arrayInt是两个整数元素的地址(第一行的数组的首地址,其实也就是第一行里第一个元素的地址),也就是说,二维数组的首地址,和里面第一行数组的首地址相等。

        arrayInt == arrayInt[0];//数组首地址==第一行数组的首地址
        arrayInt == &arrayInt[0][0];
        &arrayInt[0] == arrayInt[0];
        &arrayInt[0] == &arrayInt[0][0];//他们都是等价的四个语句

    注意:指针加减之后的变化

    指针加一,那么会对原来的数值加一个和指针指向内容的类型大小相对应的一个数值(不是单纯的加一)。故二维数组里,arrayInt+1和arrayInt[0] + 1不一样。

    ArrayInt指向的内容大小为两个int大小,而arrayInt[0]指向的内容大小为一个int。

    关键点:指针取值的变化

    对一个指针(地址)使用间接运算符取值,可以得到该指针指向的内容的数值。

    arrayInt[0]是二维数组第一行数组的首元素地址(&arrayInt[0][0])。*arrayInt[0]取出的是arrayInt[0][0],一个int元素。*arrayInt取出的是这个二维数组首元素的值,即arrayInt[0],而arrayInt[0]本身也是一个地址=&arrayInt[0][0],故*arrayInt其实还是一个地址*arrayInt[0][0],故要取出真正的数值,还需要再*一次,**arrayInt才是一个int数arrayInt[0][0]。即,二维数组名是一个地址的地址。这是难点。

    int i[2][2] = {
            {1,2},
            {3,4}
        };
        printf("%p
    ", i);//0030F924,二维数组首元素地址
        printf("%p
    ", &i[0]);//0030F924
        printf("%p
    ", i[0]);//0030F924,二维数组第一行数组首元素地址(其实就是二维数组首元素地址)
        printf("%p
    ", &i[0][0]);//0030F924
    
        printf("%p
    ", i + 1);//0030F92C,i代表两个int类型元素首地址,0030F924 + 8,
        printf("%p
    ", i[0] + 1);//0030F928,i[0]代表二维数组里第一行数组首元素地址,是一个int类型对象,故加4
    
        printf("%p
    ",*i);//取出的还是地址    0030F924,是二维数组首地址(第一行数组首地址)
        printf("%d
    ", **i);//1,第一行数组首地址就是1的地址
        printf("%d
    ", *i[0]);//1
        printf("%d
    ", i[0][0]);//1
    
        printf("%d
    ", i[1][1]);//4
        printf("%d
    ", *(*(i + 1) + 1));//4,寻址过程:先找到第2行数组i+1,然后取值*(i+1)得到第二行数组首地址,再加1,找到最后一个元素地址,再取值就是实际存储的内容。
    
        printf("%p
    ", (i[1] + 1));//    0030F930
        printf("%p
    ", *(i + 1) + 1);//    0030F930,最后一个元素地址

    如果有一个二维数组,那么如何声明一个指向它的指针?

    二维数组首地址是包含两个类型大小的,声明的时候需要注意具体是指向几个,再说创建[x]

    int (*ptr)[2];//创建一个指针变量ptr,指向了一个包含两个int类型值的数组
        //说明创建了一个指向包含两个int类型值的数组的指针

    表达式中[]的优先级高于*

    int *p[2];//意思是:[]先和p结合,则p[2]是包含两个int元素的数组,再和*结合,则说明p是两个指向int类型的指针构成的数组。
        //这样创建得到两个指针,都指向int类型,和前面的表达式不一样。
        int array[2][2] = {{1, 2}, {3, 4}};
        int *p[2] = array;// error C2075: “p”: 数组初始化需要大括号
        int (*p)[2] = array;//ok,括号先运算,就是一个指针,然后再是p[2]数组
        p = array;//ok

    指针的赋值类型的兼容性

    指针的赋值原则比数值类型严格的多,数值类型赋值有类型转换,但是指针没有。指针本身也是一种类型!

        int n = 5;
        double d = 1.1;
        int *pi = &n;
        double *pd = &d;
        n = d;//隐式的类型转换,ok
        pi = pd;//vs2010没有报错,很诡异
        pd = pi;

    比较复杂的:

        int *pi1;//创建一个指针pi1,指向一个int类型元素
        int (*pa)[3];//创建一个指针pa,pa指向包含三个int类型元素的数组
        int array1[2][3];
        int array2[3][2];
        int **pi2;//指向指针类型的指针
    
        pi1 = &array1[0][0];//array1[0][0]是一个int类型的元素
        pi1 = array1[0];//和上句等价
    
        pi1 = array1;//非法的操作,array1是包含三个元素的数组的地址
        pa = array1;
    
        pa = array2;//非法,一个指向的是3个int,一个是2个
    
        pi2 = &pi1;//pi2是一个指针,但是指向一个指向int类型的指针类型(存储的是指向int类型的指针类型本身的地址)
        //pi1是指针,指向了int类型元素,&pi1得到了指向int类型的指针类型本身的地址

    对于 array1【2】【3】,array1是二维数组的手地址,指向如图,这个地址也是指针,是指向了一个新的数组,即二维数组里的数组,而这个数组包含了三个 int类型的 元素。pil 指针,指向了一个 int 类型元素,而 int (*pa)[3];中的 pa,是一个指针,指向了一个包含了三个 int 类型元素的数组。

    多重间接运算里的const赋值

        int *p1;
        const int *p2;//创建一个指向常量的指针p2,也就是指向常量的指针
        const int **pp2;//创建一个指向常量的二级指针pp2

    const在谁前面谁就不允许改变。

    常量指针p, int * const p;也说作常指针,是说指针 p 本身是常量,不能修改,但是*p,也就是 p 指向的内容可以变,它不同于指向常量的指针。指向常量的指针 p 本身不是常量,p 可以 修改,但是 p 指向的内容不能被修改,不一定必须指向常量,也可以指向变量,仅仅是不能被赋值。pp2是指向常量的指针,也是一个二级指针,指向指针的指针,也就是一个指向常量的指针的指针。

        p1 = p2;//不能把指向常量的指针,赋值给非指向常量的指针
        p2 = p1;//反过来可以
        pp2 = &p1;//虽然也是把非指向常量的指针赋值给了指向常量的指针,但是在多层的间接运算里非法,很有可能出错!

    普通指针p1指向了 p2,p2指向的就可以被赋值修改(违法),故不能用普通指针指向。把非指向常量的指针赋值给指向常量的指针的前提是必须是一层间接运算。

    多层不合法,下面解释不合法的原因:

        int *p1;
        const int n = 10;
        const int *p2;//创建一个指向常量的指针p2
        const int **pp2;//创建一个指向常量的指针的指针pp2
        pp2 = &p1;//假设可以这样写,ok!
        *pp2 = &n;//本局,&n 是常量,*pp2是指向常量的指针,ok,但是呢,*pp2指向p1也成立了,那么此时 *p1不能被修改。而下句的*p1被赋值修改。
        *p1 = 1;//发生了错误

     注意:指向常量的指针是说指针 p 本身可以变,但是指向的内容不能被赋值,也可以是指向变量,仅仅是他们不能被赋值。而常量指针,是常指针,指针 p 本身不可变,但是 p 指向的内容能被赋值。

    函数和多维数组的关系

    通常可以使用数组符号代替指针参量.可以避免使用指针参量,看一个例子,求二维数组的和的程序:

      1 #define ROWS 3
      2 
      3 #define COLS 4
      4 
      5 //把二维数组名(指向首个子数组的指针)和行数作为形参
      6 
      7 void sum_rows(int ar[][COLS], int rows);//ar是指向包含4个int值的数组的指针
      8 
      9 void sum_cols(int [][COLS], int);//可以省略名称
     10 
     11 //ar是一个指向多维数组的指针
     12 
     13 int sum2d(int (*ar)[COLS], int rows);//当且仅当,ar是函数的形式参数,int (*ar)[COLS]和int ar[][COLS]等价
     14 
     15 int main()
     16 
     17 {
     18 
     19     int array[ROWS][COLS] = {
     20 
     21        {1, 2, 3, 4},
     22 
     23        {5, 6, 7, 8},
     24 
     25        {9, 10, 11, 12}
     26 
     27     };
     28 
     29     sum_rows(array, ROWS);//按照行方式求和
     30 
     31     sum_cols(array, ROWS);//按照列方式求和
     32 
     33     printf("%d
    ", sum2d(array, ROWS));
     34 
     35    
     36 
     37     system("pause");
     38 
     39     return 0;
     40 
     41 }
     42 
     43 void sum_cols(int ar[][COLS], int rows)
     44 
     45 {
     46 
     47     int r;
     48 
     49     int c;
     50 
     51     int tot;
     52 
     53     for (c = 0; c < COLS; c ++)
     54 
     55     {
     56 
     57        tot = 0;
     58 
     59        for (r = 0; r < rows; r++)
     60 
     61        {
     62 
     63            tot += ar[r][c];//按照列的方式进行二维数组的求和
     64 
     65        }
     66 
     67        printf("列%d的和=%d
    ", c, tot);
     68 
     69     }
     70 
     71 }
     72 
     73 void sum_rows(int ar[][COLS], int rows)//创建N维数组的指针,除了第一个空可以空,其余空的都要填写大小,因为第一个空表面这是一个指针。
     74 
     75 {//当然,都填上也可以,编译器编译会自动忽略
     76 
     77     int r;
     78 
     79     int c;
     80 
     81     int tot;
     82 
     83     for (r = 0; r < rows; r ++)
     84 
     85     {
     86 
     87        tot = 0;
     88 
     89        for (c = 0; c < COLS; c++)
     90 
     91        {
     92 
     93            tot += ar[r][c];//按照行的方式进行二维数组的求和
     94 
     95        }
     96 
     97        printf("行%d的和=%d
    ", r, tot);
     98 
     99     }
    100 
    101 }
    102 
    103 int sum2d(int (*ar)[COLS], int rows)
    104 
    105 {
    106 
    107     int r;
    108 
    109     int c;
    110 
    111     int tot = 0;
    112 
    113     for (r = 0; r < rows; r ++)
    114 
    115     {
    116 
    117        for (c = 0; c < COLS; c++)
    118 
    119        {
    120 
    121            tot += ar[r][c];
    122 
    123        }
    124 
    125     }
    126 
    127     return tot;
    128 
    129 }

     

    c99新内容:可以变化长度的数组VLA

    一般c数组的长度事先定义好,且必须是常量,不能是变量.这样的话,每次更改数组的长度,都要重新定义新的函数,麻烦.故C99引入了VLA(变长数组),VLA许可动态分配存储单元,可以在运行的时候指定数组的大小,常规的c数组是静态存储分配的,数组大小编译时已经确定,因为维数(长度)是常量。VLA(variable length array)可使用变量作为数组的长度,这里的变长,不是说数组的大小以后可以随时变,而是说数组的长,可以用变量来指定,而数组的大小创建之后不会改变.

    注意:VLA必须是自动存储类的,必须放在函数内部或者作为函数参量出现,否则报错.VLA声明的时候不能初始化.

    void sum(int rows, int cols, int arr[rows][cols]);//必须是c99及以后标准的编译器才支持.否则一样报错.并且参量的顺序不要错,先定义维数,才有后面.
    //c99规定,可以省略函数原型声明的参数名,作为数组的维数需要用*代替
    void sum(int , int , int arr[*][*]);

    数组的大小和长度的关系

    变长数组是指用整型变量或表达式声明或定义的数组,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的。因为变长数组一旦被声明,其大小就会保持不变直到生命期结束。其实就是可以让普通的 c 数组,动态的去创建内存,分配未知的存储单元!但是一旦运行期间分配完毕,这个数组的大小,照样以后不会变!直到内存释放。

    c语言把数组归结为派生类型,,比如声明一个int类型数组,一个char类型数组等

      

    欢迎关注

    dashuai的博客是终身学习践行者,大厂程序员,且专注于工作经验、学习笔记的分享和日常吐槽,包括但不限于互联网行业,附带分享一些PDF电子书,资料,帮忙内推,欢迎拍砖!

  • 相关阅读:
    [TimLinux] Python 函数(2)
    [TimLinux] Python nonlocal和global的作用
    [TimLinux] Python 装饰器
    fragment+viepager 的简单暴力的切换方式
    EditText键盘弹出时,会将布局底部的导航条顶上去(解决方法之一)
    EditText取消自动调用键盘事件(方法之一)
    Fragment滑动切换简单案例
    ListAdapter列表适配器
    ListView列表的简单案例
    ViewPager图片切换的简单案例
  • 原文地址:https://www.cnblogs.com/kubixuesheng/p/4328308.html
Copyright © 2020-2023  润新知