C语言详解(三) - 数组


1. 一维数组

1.1 一维数组的创建(定义)

格式: 数据类型 数组名[元素个数];
元素个数一般表示一个常量表达式不能是任何变量及表达式
但c99标准支持了变长数组创建,数组创建中元素个数可以是变量,但变长数组一旦创建数组长度(元素个数)便是确定的。变长数组不能在创建时初始化。
例如:

//含有10个字符元素的数组
char arr1[10];
//含有10个整型元素的数组
int arr2[10];
//含有20个浮点型元素的数组
double arr3[20];

1.2 一维数组的初始化

与创建变量时相同,不对数组中的元素进行初始化,数组中元素均是随机值(垃圾值),所以要为数组元素赋上一些合理的初始值。

  • 在创建时初始化。
  • 先创建再初始化。

在创建时初始化。

完全初始化:数组中的元素均被初始化。

int arr1[5] = {1,2,3,4,5};

初始化时可以不指定数组的元素个数,那么数组的元素个数是初始化时元素的个数。

int arr2[] = {1,2,3};

sizeof计算数组的长度,字节为单位。
image.png


不完全初始化:只对数组中部分元素进行初始化,其余未初始化的元素自动被初始化为0。

int arr3[5] = {1,0,3};
int arr4[5] = {0};

实际运行:
image.png


  • 先创建再初始化。
//先创建再初始化。
int arr[5];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
arr[3] = 4;
arr[4] = 5;

有些特殊的字符数组和字符串:

  • 字符数组:不以\0结尾。
  • 字符串:用双引号括起来的所有字符,以\0结尾。
char arr1[] = {'a', 'b', 'c', 'd'};
char arr2[4] = {'a', 'b', 'c', 'd'};
char arr3[] = "abcd";
char arr4[5] = {'a', 'b', 'c''d', '\0'};

printf("arr1[ ]	%d\n", sizeof(arr1));
printf("arr2[4] %d\n", sizeof(arr2));
printf("arr3[ ] %d\n", sizeof(arr3));
printf("arr4[5] %d\n", sizeof(arr4));

表面上看由相同的字符组成的字符数组和字符串,实际上有着不同:

  • 字符数组由于不以\0结尾,所以字符数组的元素个数比字符串少1,故字符数组的长度比字符串少1个字节。
  • 可能带来的影响是,使用%s格式打印时,字符串能正确打印,在'\0'处停止打印。但字符数组则不一定能正确打印,原因在于%s打印需要在'\0'处停止,但字符数组不含'\0',所以字符数组自身的内容打印完之后会继续打印字符数组之后的内容,直到遇到'\0'时才停止打印。

例如一个运行实例:
image.png

1.3 一维数组的使用

数组通过下标引用操作符[]实现对数组元素的单独使用。操作符[]内是数组的下标。
n个数组元素,第一个元素的下标是0,最后一个元素的下标是n-1。

int main() {
    //数组初始化
    int arr[10] = { 0 };
    int i = 0;
    //循环输出每个元素的值
    for (i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    //循环输入每个整型数组元素的值
    for (i = 0; i < 10; i++) {
        scanf("%d", &arr[i]);
    }
    //循环输出每个元素的值
    for (i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    return 0;
}

运行结果:
image.png


计算一个一维数组的大小:

#include <stdio.h>

int main() {
    int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
    int a = sizeof(arr);
    int b = sizeof(arr[0]);
    int c = sizeof(arr) / sizeof(arr[0]);
    printf("sizeof(arr) = %d\n", a);
    printf("sizeof(arr[0]) = %d\n", b);
    printf("sizeof(arr) / sizeof(arr[0]) = %d\n", c);

    return 0;
}

运行结果:
image.png


1.4 一维数组在内存中的储存

一维数组中的元素在内存中是连续存放的,具体表现为相邻元素的地址均相差一个类型所占的字节

int arr[10] = {1,2,3,4,5,6,7,8,9,0};
一维数组在内存中的存储.png

#include <stdio.h>

int main(){
    int arr[10] = {0};
    int i = 0;
    for(i = 0; i < 10; i++){
        //%p表示将打印的是变量的地址
        printf("&arr[%d] = %p\n", i, &arr[i]);
    }
    
    return 0;
}

运行结果:不看每个元素具体的地址,因为每次运行时同一元素的地址也可能不相同,只看相邻元素地址的差值。
image.png


2. 二维数组

二维数组:数组元素是一维数组的一维数组。

2.1 二维数组的创建(定义)

格式:数据类型 数组名[row][column];

row是二维数组的行数,column是二维数组的列数。
先定义二维数组中元素的数据类型。数组名与变量名的定义相同。

//创建3行4列的字符数组,含有12个字符
char arr1[3][4];
//创建3行4列的整型数组,含有12个整数
int arr2[3][4];
//创建5行5列的浮点型数组,含有25个浮点数
double arr3[5][5]

三维数组创建:

int a[3][4][5];

2.2 二维数组的初始化

  • 先定义在初始化
  • 在定义时初始化

先定义再初始化

int arr[3][4];
int i = 0;
for (i = 0; i < 3; i++) {
        int j = 0;
        for (j = 0; j < 4; j++) {
            scanf("%d", &arr[i][j]);
        } 
    }
for (i = 0; i < 3; i++) {
    int j = 0;
    for (j = 0; j < 4; j++) {
        printf("%d ", arr[i][j]);
    }
    printf("\n");
}

image.png


定义时初始化

int arr1[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
int arr2[3][4] = {{1,2,3},{5,6,7,8},{9,10}};
int arr3[3][4] = {1,2,3,4};

二维数组初始化时可以省略行,但不能省略列,省略的行可以通过计算得到。

int arr4[][4] = {1,2,3,45,6,7,8,9,10,11,12};

image.png


2.3 二维数组的使用

二维数组也通过下标[]使用二维数组的元素。
分为行下标和列下标。
行下标范围为0~row-1,列下标范围为0~column-1

#include <stdio.h>

int main(){
    //二维数组元素初始化为0
    int arr[3][4] = {0};
    int i = 0;
    int j = 0;
    //双重循环输入数组的各个元素
    for(i = 0; i < 3; i++){
        for(j = 0; j < 4; j++){
            scanf("%d", &arr[i][j]);
        }
    }
    //双重循环输出数组的各个元素
    for(i = 0; i < 3; i++){
        for(j = 0; j < 4; j++){
            printf("%d ", arr[i][j]);
        }
    }
    return 0;
}

运行结果:
image.png


2.4 二维数组在内存中的储存

二维数组理解上是多行多列的,但实际在内存中储存时是以一维数组的形式依次存放的。
也就是说,二维数组是元素为一维数组的一维数组。
一个简图:

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

二维数组在内存中的存储.png

#include <stdio.h>

int main() {
    int arr[3][4] = { 0 };
    int i = 0;
    int j = 0;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 4; j++) {
            printf("&a[%d][%d] = %p\n", i, j a[i][j]);
        }
    }
    return 0;
}

image.png


3. 数组越界

对数组的粗心使用可能会导致数组越界的情况。

  • 数组创建之后便有了确定的元素个数,使用下标对数组元素进行使用时需要知道数组使用时下标的有效范围
  • 例如,对于具有十个元素的一维数组int arr[10];下标的有效范围为09。对于具有n个元素的一维数组,下标的有效范围是0n-1。
  • C语言本身不对数组越界进行检查,数组越界时编译器也不一定会报错,所以一旦越界可能会导致严重的错误。所以需要我们在写代码时自己有意识的去检查
  • 二维数组的行和列用下标使用时也有有效范围,例如二维数组int arr[3][4];行下标有效范围是02,列下标有效范围是03。
  • 三维数组的下标也有着相似的下标有效范围。

4. 数组作为函数参数

数组时常作为函数的参数。

4.1 数组名是什么

int arr[10] = { 0 };
例如数组名arr
一般情况下数组名是数组首元素的地址。
特殊情况:

  1. 数组名单独作为sizeof的操作数,数组名代表的是整个数组,sizeof求的是所占内存的大小,所以sizeof(数组名)的结果是整个数组的大小,而不是数组首个元素(arr[0])的大小。
#include <stdio.h>

int main(){
    int arr[10] = { 0 };
    printf("sizeof(arr) = %d\n", sizeof(arr));
    printf("sizeof(arr[0])%d\n", sizeof(arr[0]));
    
    return 0;
}

image.png


  1. 取地址操作符&的操作数为数组名,即&数组名时,数组名代表的是整个数组,所以取出的也是整个数组的地址,而不是数组首元素的地址。也就是说整个数组的地址与数组首元素的地址只在数值上相同,但二者有着本质上的不同。

数组名的特例之&.png

#include <stdio.h>

int main(){
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    
    printf("&arr = %p", &arr);
    printf("&arr+1 = %p", &arr + 1);
    printf("&arr[0] = %p", &arr[0]);
    printf("&arr[0]+1 = %p", &arr[0] + 1);
    
    return 0;
}

运行结果:
image.png


可以看到整个数组的地址&arr与数组首元素的地址&arr[0]数值上相同,但整个数组的地址+1&arr+1跳过了整个数组的大小数组首元素+1&arr[0]+1跳过了一个数组元素的大小

4.2 数组名作为函数参数

一维数组作为函数参数

  • 一维数组传递给函数的是数组名,也就是数组首元素的地址,对应函数参数为一维数组,本质为一级指针,故一维数组作为函数参数时[]内写不写数组的元素个数效果都一样,一般都写上,有助于理解。
  • 所以在函数内sizeof(数组名)不能求整个数组的长度,求的是一个指针的大小,也就是一个地址的大小。
  • 地址的大小在32(位)bit操作系统下是4字节,在64(位)bit操作系统下是8字节。
#include <stdio.h>

int Add(int arr[], int sz);

int main() {
    int arr[5] = { 1,2,3,4,5 };
    printf("main sizeof(arr) = %d\n", sizeof(arr));
    int sz = sizeof(arr) / sizeof(arr[0]);
    int ret = 0;
    ret = Add(arr, sz);

    printf("ret = %d", ret);
    return 0;
}
//求一维数组中所有元素之和
//具有相同效果的不同写法
//int Add(int *arr, int sz)
//int Add(int arr[5], int sz)
int Add(int arr[], int sz) {
    printf("Add sizeof(arr) = %d\n", sizeof(arr));
    int i = 0;
    int sum = 0;
    for (i = 0; i < sz; i++) {
        sum += arr[i];
    }
    return sum;
}

image.png


#include <stdio.h>
//对有sz个元素的一维数组降序排列
void cmp_sort(int arr[], int sz);

int main() {
    //一维数组初始化
    int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
    //求一维数组元素的个数
    int sz = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    //输出一维数组的所有元素
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
    //对有sz个元素的一维数组降序排列
    cmp_sort(arr, sz);
    for (i = 0; i < sz; i++) {
        printf("%d ", arr[i]);
    }
    return 0;
}
//对有sz个元素的一维数组降序排列
void cmp_sort(int arr[], int sz) {
    int i = 0;
    for (i = 0; i < sz; i++) {
        int j = 0;
        for (j = i + 1; j < sz; j++) {
            if (arr[i] < arr[j]) {
                int tmp = arr[i];
                arr[i] = arr[j];
                arr[j] = tmp;
            }
        }
    }
}

运行结果:
image.png


二维数组作为函数的参数

  • 二维数组传递给函数的是二维数组的数组名,而二维数组的数组名是二维数组首元素的地址,二维数组的首元素实际上是第一个一维数组,故二维数组的数组名是一维数组的地址(指针)对应函数参数是二维数组,本质为指向一维数组的指针(数组指针),如指向含有4个整型元素的数组的指针int (*p)[4]

对指针数组 int (*p)[4]的解释:首先是一个指针,指向一个数组,数组中有4个元素,每个元素都是int。

#include <stdio.h>
//计算二维数组所有元素之和
int Add(int arr[3][4], int row, int column);

int main() {
    int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
    int ret = Add(arr, 3, 4);

    printf("ret = %d", ret);
    return 0;
}
//计算二维数组所有元素之和
//具有相同效果的二维数组作为参数的定义方式
//int Add(int arr[][4], int row, int column)
//int Add(int (*p)[4], int row, int column)
int Add(int arr[3][4], int row, int column) {

    int sum = 0;
    int i = 0;
    for (i = 0; i < row; i++) {
        int j = 0;
        for (j = 0; j < column; j++) {
            sum += arr[i][j];
        }
    }
    return sum;

}

二维数组作为函数参数时行数可以省略,但列数不能省略,因为本质为数组指针

运行结果:
image.png


对二维数组作函数参数的进一步理解:

#include <stdio.h>

int Add(int arr[3][4], int row, int column);

int main() {
    int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    printf("main sizeof(arr) = %d\n", sizeof(arr));

    printf("main sizeof(int(*)[4]) = %d\n", sizeof(int(*)[4]));

    printf("main sizeof(arr[0]) = %d\n", sizeof(arr[0]));
    printf("main sizeof(*(arr+0)) = %d\n", sizeof(*(arr + 0)));

    printf("main sizeof(arr[0][0]) = %d\n", sizeof(arr[0][0]));
    printf("main sizeof(*(*(arr+0)+0)) = %d\n", sizeof(sizeof(*(*(arr + 0) + 0))));
    int ret = Add(arr, 3, 4);
    printf("ret = %d", ret);

    return 0;
}
//具有相同效果的二维数组作为参数的定义方式
//int Add(int arr[][4], int row, int column)
//int Add(int (*p)[4], int row, int column)
int Add(int arr[3][4], int row, int column) {
    //这里的arr实际上是数组指针,sizeof算出的是地址的大小
    printf("Add sizeof(arr) = %d\n", sizeof(arr));
    //这里算的是数组指针的大小,是一个指针,sizeof算出的是地址的大小
    printf("Add sizeof(int(*)[4]) = %d\n", sizeof(int(*)[4]));
    //这里的arr[0]是二维数组的首元素,是一个一维数组名。而数组名单独作为
    // sizeof的操作数时数组名代表整个一维数组,求出的是整个一维数组的大小。
    //函数参数二维数组定义为3行4列,即每一个一维数组是四个整型元素
    printf("Add sizeof(arr[0]) = %d\n", sizeof(arr[0]));
    //arr本质是指针数组(int(*)[4]),指向一个一维数组。
    //(arr+0)解引用则是通过指针数组访问了其指向的一维数组,
    //故*(arr+0)相当于*arr相当于arr[0],是一维数组名。
    printf("Add sizeof(*(arr+0)) = %d\n", sizeof(*(arr + 0)));
    
    //arr[0][0]是二维数组中第一个一维数组中的的一个整型元素的值
    printf("Add sizeof(arr[0][0]) = %d\n", sizeof(arr[0][0]));
    //*(arr+0)是一维数组的数组名,*(arr+0)+0解引用则是通过一维数组名
    //访问一维数组的第一个元素,*(*(arr+0)+0)相当于*(arr[0]+0)相当于arr[0][0]
    printf("Add sizeof(*(*(arr+0)+0)) = %d\n", sizeof(sizeof( *(*(arr+0)+0) )));

    int sum = 0;
    int i = 0;
    for (i = 0; i < row; i++) {
        int j = 0;
        for (j = 0; j < column; j++) {
            sum += arr[i][j];
        }
    }
    return sum;

}

运行结果:
运行结果:
观察到main函数Add函数中计算sizeof(arr)的本质不同。
二维数组作函数参数程序运行截图.png


5. 回顾:

本文主要写了有关C语言数组的内容。首先先了解了一维数组的创建初始化使用在内存中的储存;然后就是二维数组的创建初始化使用在内存中的储存。在对数组的使用中要预防数组的越界,知道数组越界可能会造成严重的影响。数组名也经常用作为函数传参,因此需要熟悉一维数组传参和二维数组传参的本质是什么。

END

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

re怠惰的未禾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值