【逐步剖C】-第三章-数组

一、一维数组

1. 一维数组的定义与使用

  • (1)数组的简单概念:一组具有相同类型的元素的集合

  • (2)数组的创建:
    格式:类型名+数组名+[数组大小]
    需要注意的是:对多数情况而言,在指定的数组大小时,需要用常量表达式。
    (PS:若编译器支持C99,则可使用变量来指定数组的大小(即变长数组);但变长数组有一些限制:变长数组必须是自动存储类的,意味着它们必须在函数内部或作为函数参数声明,而且声明时不可以进行初始化。)
    数组的大小可以省略,但若想省略则必须在创建时就对其进行初始化。

  • (3)数组的初始化:
    和变量的初始化类似,在创建它的时候赋予它一些合理的值
    一般的初始化形式:数组名[数组大小]={…}
    数组的下标是从0开始的,故需根据实际需求正确指定数组的大小
    若数组的大小省略,则数组会根据初始化的内容来确定数组的大小
    如:int arr[]={1,2,3};
    数组arr的大小就为3个整型变量的大小
    若数组的初始化部分即{ }中的元素达不到数组的大小时,相应没有指定初始化的地方自动初始化为零,称为数组的不完全初始化
    如:int arr[3] = {1};
    此时数组中对应的元素为 arr[0] = 1; arr[1] = 0; arr[2] = 0

故常通过如下面这段代码来实现对数组每个元素赋0的初始化:

int arr[10] = {0};

对于作为全局变量的数组 ,其元素默认初始化为0

  • (4)一维数组的使用:通过下标引用操作符[ ]来实现对每个元素的访问
arr[2] = 2;
//将数组的第三个元素赋值为2
  • (5) 可通过sizeof来计算数组的长度
int len = sizeof(arr)/sizeof(arr[0]);
  • (6)易踩坑点:用数组存储字符串的初始化

时刻要在脑海中的一个知识点字符串的最后一个字符是‘\0’,这也是函数对字符串进行操作的标志,如strlen strcmp等。故在用数组进行字符串的存储时,一定要考虑到 \0 而正确指定数组的大小

如下代码:

char arr[3] = { "abc"};

将其打印出来的结果是:
​​​​在这里插入图片描述
解释:此时数组的情况是arr[0] = ‘a’; arr[1] = ‘b’; arr[2] = ‘c’; 而‘\0’ 并没有被随着字符串存储到数组当中,故在对其进行打印操作时,找不到结束的标志, 在打印完abc后又打印了乱码

结论:对字符串来说,其长度就为组成它的字符个数,如上字符串“abc”的长度就是3,而对数组来说,还要另将字符串的结束标志 \0考虑进去。通过如下代码加深印象:

int main()
{
	char arr1[3] = { "abc"};
	char arr2[3] = { 'a','b','c' };
	char arr3[4] = { 'a','b','c','\0' };
	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);
	return 0;
}

打印结果:
在这里插入图片描述

2. 一维数组在内存中的存储

先说结论数组在内存中是连续存放的,且是从低地址到高地址,即随着下标的增长,其每个元素的地址也在有规律地递增。
如下代码展示:

#include<stdio.h>
int main()
{
	int arr[5] = { 0 };
	int len = sizeof(arr) / sizeof(arr[0]);
//上面说的计算数组长度的方法
	int i = 0;
	for (i = 0; i < len; i++)
		printf("%p\n", &arr[i]);
	return 0;
}

运行结果:
在这里插入图片描述

二、二维数组

1. 二维数组的创建和使用

  • (1)格式:
int arr [rows] [cols];

同一维数组, [ ] 中为常量表达式

  • (2)初始化:
int arr[2][2] = {1,2};
//数组的第一行的一二列的初始化
int arr[2][2] = {{1},{2}};
//数组的第一列的一二行的初始化
int arr[][2] = {1,2};
//数组的省略初始化,但要注意行可以省略,但列不能省略
  • (3)使用:同一维数组,使用 [ ] 操作符进行访问。

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

也和一维数组一样,按地址由高到低连续存放
(PS:其实所谓行和列只是我们的一种理解,其实二维数组的存放和一维数组一样是一整行的),如下图:

在这里插入图片描述
所以这里可以有一种理解方法:把对应行(如arr[0])看作是对应一维数组的数组名,而再通过操作符 [ ] 中的数字来访问这个一维数组的每一个元素。即把二维数组当一维数组来使用。如arr[0][1]就可以理解为访问的是一维数组arr[0]的第二个元素
所以整个二维数组就可以理解为是一个存放着一维数组的一维数组
(PS:此种理解方式还是很重要的,在理解数组指针时会很有帮助)
这里我们同样可以通过sizeof来对二维数组进行一些运算:

//计算二维数组的行数:
sizeof(arr) / sizeof(arr[0])

//计算二维数组每行元素个数:
sizeof(arr[0]) / sizeof(arr[0][0])

三、补充知识

1.数组越界问题

所谓越界就是指访问的下标超过了数组的长度。需要注意的是C语言本身不做数组下标的越界检查,编译器也不一定报错,编译器不报错并不代表程序就本身是正确的。所以我们在写代码的时候一定要细心,写完后最好做一遍越界的检查。

2.数组作为函数参数(数组名的意义)

先说知识点:数组传参,本质上传的是数组首元素的地址。这里就不得不谈到数组名的问题。
数组名其实就代表着该数组首元素的地址,如下代码:

int arr[5] = { 1,2,3 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d", *arr);

运行结果:
在这里插入图片描述
当然也有两个例外情况:

(1)用sizeof计算数组大小时,()中的数组名,代表的时整个数组。

(2)&数组名,其中的数组名也表示整个数组,这个操作指的是取出整个数组的地址(到指针进阶总结时会更详细说明)

3. 对数组类型的理解:

int num = 10;

对于变量num而言, int 就是它的类型(即去掉变量名)
可以得到 sizeof(int) == sizeof(num)
对于数组而言其实是同样的道理:

int arr[10];

对于上面这个数组arr而言呢,int[10] 就是它的类型(即去掉数组名)
同理也有:sizeof(int[10]) == sizeof(arr)
(PS:同样,掌握这种理解方式后有助于在后续的学习中理解数组指针。)

4. 数组作为函数参数的重要实例:冒泡排序

冒泡排序的主要思想是:相邻两数进行对比,若不符合想要实现的序列(升序或降序),则两数进行交换
先来个错误的函数例子,好涨记性:

void bubble_sort(int arr[])
{
 int len = sizeof(arr)/sizeof(arr[0]);
//这里想求数组长度,但调试发现结果不符合预期
    int i = 0;
 for(i=0; i<len-1; i++)
   {
        int j = 0;
        for(j=0; j<len-i-1; j++)
       {
            if(arr[j] > arr[j+1])
           {
                int tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
           }
       }
   }
}

结果为1的原因:上面说过,用sizeof计算数组长度时()中的数组名表示整个数组,或着说应当为整个数组的地址,而数组传参本质上传的是数组首元素的地址,而地址的大小,在32位机上位4个字节,在64位机上为8个字节,又因为该数组为整型数组,其元素的类型大小为4个字节,故最终的len的值只可能是2或1。

解决办法:数组长度在外面算好后,再传进去

改正并通过flag优化后的完成冒泡排序程序如下:

void bubble_sort(int arr[], int n)
{
	int i, j;
	int tmp = 0;
	for (i = 0; i < n - 1; i++)		//趟数,即有多少个数需要被处理
	{
		int flag = 1;		
		for (j = 0; j < n - 1 - i; j++)		//对换次数,即一趟需对换多少次
		{
			if (arr[j] > arr[j + 1])		//本质上是想让不满足既定有序条件的两个数对换
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;				//当i=1时,5和4就已发生对换,整个数组已经有序									
			}
		}
		if (flag == 1)
			break;
	}
}

对flag优化的解释:利用flag可以减少趟数,优化代码(用来解决可能提前排序完成的情况)(即已经有序了)。因为n-1趟的规律是在完全倒序情况下总结的,即数组为9,8,7…,但实际上数组可能只需一趟就完成了排序
如1,2,3,5,4。
如果没有flag的话,代码依然会按照n-1趟来判断,一些过程就重复了。
flag可以理解为是一个检测有序的标志
结合代码理解:如上例{1,2,3,5,4},第一趟4和5发生对换,对换后数组已经有序,此时进行下一趟排序时,内循环中的交换将一次都不进行,也就是flag的值不会被置为0,内循环结束时,flag的值仍为1,这就代表着数组已经有序,就可以跳出循环了。

以上就是我对数组这一部分知识的总结啦。

看完觉得有觉得帮助的话不妨点赞收藏鼓励一下,有疑问或有误地方的地方还恳请过路的朋友们留个评论,多多指点,谢谢朋友们🌹🌹🌹!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值