C 数组

由于数组有存储多个相同类型值的能力,我们能够做更多有趣的事情。首先我们要了解数组,然后再聊一聊数组可以做什么。

数组的声明和初始化

数组类型形式如右侧:数据类型 数组名[数组大小]={值1,值2,值3,...};
比如下面代码,声明了一些数组变量:

#include <stdio.h>

int main(void) {
    //声明了一个长度为3的整型数组
    int arr[3];
    //声明了一个长度为3的整型数组,并使用3、4、5给数组进行了初始化
    int arr3[3] = {3, 4, 5};
    //声明了一个长度为3的整型数组 并且使用了1、2对数组进行了不完全初始化
    int arr2[3] = {1, 2};
    //声明了一个长度为6的字符数组 并且使用a、b、c三个字符对数组进行了不完全初始化
    char str[6] = {'a', 'b', 'c'};
    //不指定数组长度,由系统初始化列表自动推断长度
    char str2[] = {'a', 'b', 'c'};
    char str3[];
    str2={'c','b','d'};
    return 0;
}

对于数组的声明和初始化有以下几点总结:

  • 函数内只声明了数组arr,未赋初值;这种数组不会自动初始化,所以,数组中的元素的值是随机的,不确定的;
  • 对于arr3,这是一种在声明时就将数组初始化的形式;右侧花括号中的参数列表,有三个逗号分隔开的值,这是一种完全初始化;
  • 对于arr2,这是一种不完全初始化形式;剩余的最后一个元素会被默认初始化为0;
  • 对于str,这是一个字符数组,它也没有被完全初始化;
  • 对于str2,这是一种不指定数组大小的形式,系统会根据初始化列表推断出数组的大小;
  • 对于str3,这种形式不被允许,编译器会报错--如果在声明时不指定数组大小,那么一定要在数组声明时就初始化;
  • str2={1,2,3},这种编译器会报错,因为数组名就像是一栋楼,比如6号楼,它永远就是6号楼,不允许再改变--即:不允许再对数组变量再进行赋值操作;
  • 其实数组名是一个地址类型常量--指针常量。不允许再次对数组地址进行修改。

如何通过声明认识数组

一个小技巧,拿int arr[10];来讲:先看数组名arr,然后往右和arr紧贴着的一对方括号,得知这是一个含有10个元素的数组、接下来将arr[10]忽略掉,再看剩下的部分就是数组元素的类型。好了,有了这个小技巧之后,我们来认识一下这个数组?

int *arr[6];
int arr2[6][2];

先来看arr

  • 第一步:arr右边紧挨着的方括号,得知arr是一个含有6个元素的数组;
  • 第二步:去掉arr和紧挨着的方括号,剩下 int * --数组元素为:整型指针;
  • 最后一步:arr是一个由6个整型指针组成的数组

再来看arr2:

  • 第一步:arr2右边紧挨着的方括号,得知arr2是一个含有6个元素的数组;
  • 第二步:去掉arr2和紧挨着的方括号,剩下 int [2] -- 数组元素为:数组类型;
  • 最后一步:arr2是一个由6个数组组成的数组。

数组名意味着什么

数组名在表达式中退化为指针

数组名存储了整个数组在内存中的首地址;由于数组元素在内存中是连续存储的,所以数组首元素的地址和数组的首地址是相同的;
由于使用数组名在表达式中,数组名会“退化”成指针
比如:可以使用数组名给指针变量赋值int arr[10]; int * p_arr=arr;
比如:可以使用数组名做指针运算int arr_item=*(arr+1);
但是你不要认为数组名就是指针类型!!!因为数组名有自己的类型,即数组类型!!!

#include <stdio.h>

int main(void) {
    int arr3[3] = {3, 4, 5};
    printf("arr3的地址是:%p\n", &arr3);
    printf("arr3的地址是:%p\n", arr3);
    printf("arr3的第一个元素的地址是:%p\n", &arr3[0]);
    return 0;
}

输出

arr3的地址是:0x16b2b7528
arr3的地址是:0x16b2b7528
arr3的第一个元素的地址是:0x16b2b7528

使用下标访问数组元素

声明并初始化一个数组后,如何访问或修改数组某个位置的元素呢?答案是使用数组名+元素下标位置;
当我们声明一个整型数组后,它的各个元素所处的下标如下图:

#include <stdio.h>
#include <string.h>

#define NUMBER 3

int main(void) {
    int arr3[NUMBER] = {3, 4, 5};

    for (int i = 0; i < NUMBER; i++) {
        printf("第%d个元素的值:%d\n", i + 1, arr3[i]);
    }
    return 0;
}

image
输出:

第1个元素的值:3
第2个元素的值:4
第3个元素的值:5

使用下标还可以修改此位置处的元素的值,这里不再演示。

使用指针对数组进行遍历

#include <stdio.h>

#define NUMBER 3

int main(void) {
    int arr3[NUMBER] = {3, 4, 5};

    int *arr3_ptr = arr3;
    for (int i = 0; i < NUMBER; i++) {
        printf("第%d个元素的值:%d\n", i + 1, *(arr3_ptr + i));
    }
    return 0;
}

由于知道数组名存储的值与数组首元素的地址相同,所以int *arr3_ptr = arr3;用数组名赋值给int类型指针逻辑上是合理的。
在for循环中,arr3_ptr+1操作,就是数组下一个元素所在的地址。而后通过解引用的操作,便可获取到下一个元素的值。

在sizeof和&取地址符号中的表现

  • 在sizeof面前,数组名又充当起了代表整个数组的角色,所以sizeof(数组名)得到的大小是整个数组的大小;而非其它普通指针,得到的是一个固定大小;
#include <stdio.h>

#define NUMBER 3

int main(void) {
    int arr3[NUMBER] = {3, 4, 5};
    printf("arr3大小是:%lu\n", sizeof(arr3));
    int *arr3_ptr = arr3;
    printf("arr3_ptr大小是:%lu\n", sizeof(arr3_ptr));
    return 0;
}

输出:

arr3大小是:12
arr3_ptr大小是:8
  • 在取地址&符号前,数组名也充当起了数组的角色,所在当&数组名+1后得到的地址并不是简单的往后移动一个数组元素的空间大小,而是数组长度*单个数组元素字节 大小;
    这两个点比较特殊,初学者需要特别关注一下这两个点。

数组元素在内存中的分布

从编辑器的内存视图来看,&数组名,得到的数组内存分布:
image
由上图可以看到数组元素在内存中是连续存储的;每个数组元素占用4字节。
&数组名+1
image

做一些有趣的事

上面说了这么多干的,也得来点儿湿的好消化,那么知道了数组的这些基础知识后,我们已经可以做一些有趣的东西了.
在开始前,先要讲一个写代码最重要的前提:一定要想好了,再去干!在动手完成功能前,先在脑子里和纸上写写画画,把程序的功能点、思路理好理完整;千万、千万、千万不要听到需求没想好就开干!!!
后面的小练习,都是秉持着先有思路,再动手的原则进行的。

找最大值

思路:认为第一个元素就是最大值;搞一个临时变量存储第一个变量的值;对数组开始遍历,在当前迭代中若发现比这个临时变量还要大的元素,则拿此元素的值更新这个临时变量;下一次迭代仍然是这个逻辑;直至数组遍历结束;那么临时变量存储的就已经是数组的最大值了。

#include <stdio.h>

#define NUMBER 13

int main(void) {
    int arr3[NUMBER] = {3, 4, 5, 1, 2, 19, 2, 5, 7, 8, 10, 9, 12};
    int max = arr3[0];
    for (int i = 0; i < NUMBER; i++) {
        if (max < arr3[i]) {
            max = arr3[i];
        }
    }
    printf("数组最大值是:%d\n", max);
    return 0;
}

输出

数组最大值是:19

数组逆置

思路:数组的大小能先确定、然后定义两个临时变量x、y分别存储数组的最小下标和最大下标;对数组开始遍历,在当前迭代中交换两个下标处元素的数据,然后让x值加1、让y值减1;下一次迭代仍然是这个逻辑;但是需要注意的是:x和y不能“碰面”--即x一定要小与y。

#include <stdio.h>

#define NUMBER 13

int main(void) {
    int arr3[NUMBER] = {3, 4, 5, 1, 2, 19, 2, 5, 7, 8, 10, 9, 12};
    for (int i = 0; i < NUMBER; i++) {
        printf("%d ", arr3[i]);
    }
    printf("\n");
    int x = 0;
    int y = NUMBER - 1;
    for (int i = 0; i < NUMBER; i++) {
        if (x < y) {
            int tmp = arr3[x];
            arr3[x] = arr3[y];
            arr3[y] = tmp;
            x++;
            y--;
        }
    }
    for (int i = 0; i < NUMBER; i++) {
        printf("%d ", arr3[i]);
    }
    return 0;
}

输出

3 4 5 1 2 19 2 5 7 8 10 9 12 
12 9 10 8 7 5 2 19 2 1 5 4 3

特殊的数组之二维数组

上面我们学习认识数组声明小技巧时,提到过二维数组int arr[6][2];它是有数组组成的数组--集:数组的数组,称为二维数组。
二维数组的初始化形式有:

#include <stdio.h>
#include <string.h>

int main(void) {
    int arr[6][2] = {
        {4, 5},
        {3, 4},
        {2, 3},
        {1, 2}
    };
    return 0;
}

把二维数组看成由6个一维数组组成的数组的角度来声明的;花括号中的参数列表中,每一个元素都是一个一维数组。

#include <stdio.h>
#include <string.h>

int main(void) {
    int arr[6][2] = {4, 5, 3, 4, 2, 3, 1, 2};
    return 0;
}

由于数组在内存中是连续存储的,所以二维数组的初始化可以使用,一组整型数做参数列表进行初始化。

二维数组在内存中的分布

通过编辑器的内存视图,来看二维数组的内存分布情况:
image

二维数组总结

  • 从数组的数组的角度来看二维数组,那么arr[m][n],我们通常看作:m行、n列;
  • 由此而为数组的访问便是,先访问第几行再访问第几列,如arr[1][2]则访问到了第2行第三列的元素;
  • 若是要遍历二维数组,则需要两层for循环,第一层负责访问行、第二层负责访问列。
#include <stdio.h>
#include <string.h>

int main(void) {
    int arr[4][2] = {
        {4, 5},
        {3, 4},
        {2, 3},
        {1, 2}
    };
    //遍历二维数组
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 2; j++) {
            printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);
        }
    }
    return 0;
}

输出

arr[0][0] = 4
arr[0][1] = 5
arr[1][0] = 3
arr[1][1] = 4
arr[2][0] = 2
arr[2][1] = 3
arr[3][0] = 1
arr[3][1] = 2

特殊的数组之字符数组

这里先给结论,

  • c程序中没有字符串类型;所以使用char 类型数组存储字符串;
  • 字符串字面量如"i love you !",是包含一个看不到的\0字符的;
  • 字符串字面量,可以直接给字符数组初始化的;
    image
  • 字符串一定是字符数组,但字符数组不一定是字符串--如果字符数组中没有\0字符的话,字符数组就不是字符串
    这一点,可以使用printf("%s\n",数组名)来看出区别:
#include <stdio.h>

int main(void) {
    char arr[] = {'a', 'b', 'c'};
    int int_arr[] = {1, 2, 3, 4};
    char str[] = "abc";
    printf("%s\n", arr);
    printf("%s\n", str);
    return 0;
}

image

可以看出,字符数组arr打印出来的带有特殊符号;str打印出来和预期一致;为什么arr打印出来有特殊符号?因为arr存储的就不是字符串,printf函数得到的指令确实打印字符串,所以它会从arr首元素开始找字符串结束符号\0,这个结束符号究竟在什么位置,是位置的;这也是arr打印时会出现特殊符号的由来。

  • 现在有一个存储了字符串的字符数组,使用字符串长度计算函数strlen得到的值和使用sizeof得到的值是不一样的
#include <stdio.h>
#include <string.h>

int main(void) {
    char arr[] = {'a', 'b', 'c'};
    int int_arr[] = {1, 2, 3, 4};
    char str[] = "abc";
    printf("str的内存占用为:%lu\n", sizeof(str));
    printf("str的长度为:%lu\n", strlen(str));
    return 0;
}

输出

str的内存占用为:4
str的长度为:3

提一下在网上看到的一位网友讲的:strlen 就像老师让我们读作文时不会把标点符号读出声; sizeof就像老师统计我们作文字数时,倒是会把标点符号统计在内。

posted @ 2025-11-10 08:29  BigBosscyb  阅读(8)  评论(0)    收藏  举报