博客主页:江池俊的博客

收录专栏:C语言——探索高效编程的基石

其他专栏:数据结构探索

代码仓库:江池俊的代码仓库

如果觉得博主的文章还不错的话,请点赞收藏 三连支持一下博主

目录

一、一维数组的创建和初始化

1.1 数组的创建

1.2 数组的初始化

1.3 一维数组的使用

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

二、二维数组的创建和初始化

2.1 二维数组的创建

2.2 二维数组的初始化

2.3 二维数组的使用

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

三、数组越界

四、数组作为函数参数

4.1 冒泡排序函数的错误设计

4.2 数组名是什么?

4.3 冒泡排序函数的正确设计

思路:


一、一维数组的创建和初始化

1.1 数组的创建

数组是一组相同类型元素的集合。

数组的创建方式:

type_t arr_name [const_n];//type_t 是指数组的元素类型//const_n 是一个常量表达式,用来指定数组的大小

数组创建的实例:

//代码1int arr1[10];//代码2int count = 10;int arr2[count];//数组时候可以正常创建?//代码3char arr3[10];float arr4[1];double arr5[20];

注:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。

1.2 数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

看代码:

int arr1[10] = {1,2,3};int arr2[] = {1,2,3,4};int arr3[5] = {1,2,3,4,5};char arr4[3] = {'a',98, 'c'};char arr5[] = {'a','b','c'};char arr6[] = "abcdef";

数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。

但是对于下面的代码要区分,内存中如何分配。

char arr1[] = "abc";char arr2[3] = {'a','b','c'};

对于 arr1 数组,使用双引号括起来的字符串常量 “abc” 会自动在末尾添加一个空字符 ‘\0’,因此 arr1 数组的大小为 4(包括字符 ‘a’, ‘b’, ‘c’ 和空字符 ‘\0’)。

Memory layout for arr1: | 'a' | 'b' | 'c' | '\0' |

对于 arr2 数组,使用花括号括起来的字符列表是没有自动添加空字符 ‘\0’ 的,因此 arr2 数组的大小是 3(只包括字符 ‘a’, ‘b’, ‘c’)。

Memory layout for arr2: | 'a' | 'b' | 'c' |

需要注意的是arr1arr2 在初始化方式上有区别:

▪️arr1 是用字符串常量进行初始化,编译器会自动根据字符串常量的长度为其分配足够的空间,并 在末尾添加一个空字符 ‘\0’,确保它是以 null-terminated 形式存储的。

▪️arr2 是用字符列表进行初始化,只会把指定的字符存储在数组中,不会自动添加空字符 ‘\0’。

因此,在使用这两个数组时需要注意它们的大小和内存布局。对于字符串处理,通常建议使用 arr1 这样以 null-terminated 形式存储的字符数组,这样可以方便使用标准字符串函数。而 arr2 则更适用于需要明确指定字符内容的情况,但需要特别注意不要访问超过数组大小的索引,否则可能导致未定义行为。

1.3 一维数组的使用

对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。

我们来看代码:

#include int main(){ int arr[10] = {0};//数组的不完全初始化//计算数组的元素个数int sz = sizeof(arr)/sizeof(arr[0]); //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以: int i = 0;//做下标 for(i=0; i<10; i++)//这里写10表示数组元素个数 { arr[i] = i; }//输出数组的内容 for(i=0; i<10; ++i) { printf("%d ", arr[i]); } return 0;}

总结:

1. 数组是使用下标来访问的,下标是从0开始。

2. 数组的大小可以通过计算得到。

int arr[10];int sz = sizeof(arr)/sizeof(arr[0]);//sizeof计算的是字节数

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

看代码:

#include int main(){ int arr[10] = {0}; int i = 0;int sz = sizeof(arr)/sizeof(arr[0]); for(i=0; i<sz; ++i) { printf("&arr[%d] = %p\n", i, &arr[i]); } return 0;}

运行结果:

仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。

由此可以得出结论:数组在内存中是连续存放的。

二、二维数组的创建和初始化

2.1 二维数组的创建

//数组创建int arr[3][4];char arr[3][5];double arr[2][4];

2.2 二维数组的初始化

//数组初始化int arr[3][4] = {1,2,3,4};//数组元素:1234 0000 0000int arr[3][4] = {{1,2},{4,5}};//数组元素:1200 4500 0000int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略//数组元素:2300 4500

2.3 二维数组的使用

二维数组的使用也是通过下标的方式。

看代码:

#include int main(){int arr[3][4] = { 0 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){arr[i][j] = i * 4 + j;}}for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 4; j++){printf("%-2d ", arr[i][j]);}printf("\n");}return 0;}

运行结果:

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

像一维数组一样,这里我们尝试打印二维数组的每个元素地址。

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

运行结果:

通过结果我们可以分析到,其实二维数组在内存中也是连续存储的。

三、数组越界

数组的下标是有范围限制的。

数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。

所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的,

所以程序员写代码时,最好自己做越界的检查。

以下代码是一维数组的越界:

#include int main(){ int arr[10] = {1,2,3,4,5,6,7,8,9,10};int i = 0;for(i=0; i<=10; i++) {printf("%d\n", arr[i]);//当i等于10的时候,越界访问了 } return 0;}

同理,二维数组的行和列也可能存在越界。

四、数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传个函数,比如:我要实现一个冒泡排序(这里要讲算法 思想)函数将一个整形数组排序

4.1 冒泡排序函数的错误设计

冒泡排序(Bubble Sort)是一种简单的排序算法,其基本思想是重复地遍历要排序的数组,每次比较相邻两个元素,如果它们的顺序错误,则交换它们,直到整个数组排序完成为止。排序过程中较大的元素会像气泡一样逐步上浮到正确的位置,因此得名”冒泡排序”。

//方法1:#include void bubble_sort(int arr[]){ int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗?int i = 0; for(i=0; i<sz-1; i++) {int j = 0;for(j=0; j arr[j+1]) {int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp; } } }}int main(){int arr[] = {3,1,7,5,8,9,0,2,4,6};bubble_sort(arr);//是否可以正常排序?for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++) {printf("%d ", arr[i]); }return 0;}

方法1,出现问题,那我们找一下问题,调试之后可以看到 bubble_sort 函数内部的 sz ,是1。 难道数组作为函数参数的时候,不是把整个数组的传递过去?

4.2 数组名是什么?

下面带你玩转数组名:

#include int main(){int arr[10] = {1,2,3,4,5};printf("%p\n", arr);printf("%p\n", &arr[0]);printf("%d\n", *arr);//输出结果return 0;}

运行结果:

结论:数组名是数组首元素的地址。(有两个例外)

但是,如果数组名是首元素地址,那么:

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

为什么输出的结果是:40?

补充:

sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数 组。

&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。

总结:除补充的两种情况之外,所有的数组名表示数组首元素的地址。

4.3 冒泡排序函数的正确设计

思路:

1.从数组的第一个元素开始,比较相邻的两个元素。如果第一个元素比第二个元素大(升序排序),则交换这两个元素的位置,使得较大的元素“冒泡”到右侧。

2.继续比较后续的相邻元素,重复上述操作,直到最后一个元素。

3.一轮遍历完成后,最大的元素将会被“冒泡”到数组的最后一个位置。

4.然后从数组的第一个元素开始,进行下一轮遍历,但这次不需要考虑已经排序好的最后一个元素。

5.重复上述过程,直到所有元素都排序完成。

冒泡排序是一个简单但效率较低的排序算法,其时间复杂度为O(n^2),其中n是数组的大 小。由于其效率较低,通常不推荐在大规模数据上使用,但对于小规模的数据或是教学示例,冒泡排序仍然是一种简单易懂的排序算法。

当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。 所以即使在函数参数部分写成数组的形式: int arr[] 表示的依然是一个指针: int *arr 。 那么,函数内部的 sizeof(arr) 结果是4。 所以在传参时我们需要多传递一个变量(它表示的是数组的大小)。

//方法2#includevoid bubble_sort(int arr[], int sz)//参数接收数组元素个数{int i = 0;for (i = 0; i < sz - 1; i++){int j = 0;for (j = 0; j  arr[j + 1]){int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}}int main(){int arr[] = { 3,1,7,5,8,9,0,2,4,6 };int sz = sizeof(arr) / sizeof(arr[0]);printf("冒泡排序前:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");bubble_sort(arr, sz);printf("冒泡排序后:\n");for (int i = 0; i < sz; i++){printf("%d ", arr[i]);}printf("\n");return 0;}

运行结果:

以上就是数组的全部内容,学完数组后,大家可以尝试去写三子棋和扫雷这两个有意思的小游戏,相信大家能够拿下这两个小游戏,大家也可以参考我写的三子棋和扫雷⏩

三子棋 【C语言】三子棋—-详解扫雷【C语言】扫雷—-详解(扩展版)

今天的分享就到这里,如果觉得博主的文章还不错的话,请三连支持一下博主哦