初级C语言之【数组】

举报
春人. 发表于 2023/11/27 00:29:43 2023/11/27
【摘要】 一:一维数组的创建和初始化1.1:数组的定义数组是一组相同类型元素的集合1.2:数组的创建type_t arr_name [const_n];//type_t 是指数组的元素类型//arr_name是数组名//const_n 是一个常量表达式,用来指定数组的大小注意:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小...

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

1.1:数组的定义

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

1.2:数组的创建

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

注意:数组创建,在C99标准之前, [ ] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。

1.2.1:数组创建举例:

int arr1[10];
double arr2[5+5];

//支持c99的编译环境
int n=10;
char arr3[n];//变长数组,数组的大小是由变量n来指定的

1.3:数组的初始化

在数组创建的同时给数组一些值,就叫初始化

1.3.1:完全初始化

1.3.1.1:整型数组的完全初始化

int a[10] = {1,2,3,4,5,6,7,8,9,10};//这叫完全初始化,将数组里的全部10个元素都进行了初始化
int arr4[] = { 1,2,3,4,5,6,7,8,9,10 };//这里没有指定数组元素个数,编译会根据初始化的内容来确定数组的元素个数,这里的arr4数组就有10个元素

下面两个数组是有差异的

int arr1[]={1,2,3};//只有三个元素
int arr2[10]={1,2,3};//有十个元素

1.3.1.2:字符数组的完全初始化

char arr3[3] = {'a','b','c'};
char arr4[ ] = {'a','b','c'};
//这两种初始化效果一样

用字符串进行初始化

注意:字符串的后面默认有’\0’,也要算在字符个数里面

char arr5[4] = "abc";//arr5数组里面一共有4个元素分别是'a','b','c','\0'
char arr6[ ] = "abc";//arr6数组里面一共有4个元素分别是'a','b','c','\0'
//在这两种初始化的结果相同
char arr7[] = "abc";//arr7数组有4个元素分别是'a','b','c','\0'
char arr8[] = { 'a','b','c' };//arr8数组有3个元素分别是'a','b','c'
//这两种数组的初始化结果不同

arr7数组和arr8数组打印出来的结果有所不同,arr7打印出来的是abc,而arr8数组打印出来的是abc烫烫烫烫蘟bc,这时因为字符串在打印的时候只有遇到’\0’才会停下来,arr7数组是用字符串进行的初始化,数组最后一个元素默认是’\0’,因此arr7数组会打印出abc,而arr8数组是用单个字符进行初始化的,数字最后一个元素是’c’,此时打印arr8数组,会从字符’a’开始往后打印直到遇到了’\0’才会停下来。

1.3.2:不完全初始化

1.3.2.1:整型数组不完全初始化

int arr2[10] = { 1,2,3 };//不完全初始化,只对数组的前三个元素进行了初始化,后七个元素默认是0

1.3.2.2:字符型数组不完全初始化

char arr3[10] = {'a','b','c'};//只把前三个元素分别初始化成'a','b','c',后七个元素默认初始化成'\0'

1.4:一维数组的使用

数组是有下标的,下标是从0开始的。[ ] ,下标引用操作符。它其实就数组访问的操作符。

//访问数组中的元素,将其打印出来
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr)/sizeof(arr[0]);//sz的值代表了数组元素个数
	int i = 0;
	//顺序打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	//逆序打印
	for (i = sz - 1; i >= 0; i--)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

1.5:一维数组在内存中的存储

//将数组中每一个元素的地址打印出来
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int i = 0;
	//顺序打印
	for (i = 0; i < sz; i++)
	{
		printf("&arr[%d]=%p\n",i, &arr[i]);
	}
	printf("\n");
	return 0;
}

//打印结果:
&arr[0]=007CF730
&arr[1]=007CF734
&arr[2]=007CF738
&arr[3]=007CF73C
&arr[4]=007CF740
&arr[5]=007CF744
&arr[6]=007CF748
&arr[7]=007CF74C
&arr[8]=007CF750
&arr[9]=007CF754

可见相邻两个数组元素地址之间相差4个字节,也就是一个整型,这说明数组里面元素的地址是连续的,随着数组下标的增长,地址是由低到高变化的

在这里插入图片描述

//打印数组元素的另一种方式
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr)/sizeof(arr[0]);
	int* p = &arr[0];//把首元素的地址给p
	int i = 0;
	//顺序打印
	for (i = 0; i < sz; i++)
	{
		printf("%d ", *(p+i));//p+i就是第i个元素的地址
	}
	printf("\n");
	return 0;
}

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

2.1:二维数组的创建

int arr1[3][4];//一个三行四列的整型数组,共12个元素
char arr2[3][5];//一个三行五列的字符型数组,共15个元素
double arr3[2][4];//一个两行四列的双精度浮点型数组,共8个元素

2.2:二维数组的初始化

int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//完全初始化
int arr2[3][4] = { 1,2,3,4,5 };//不完全初始化
int arr3[3][4] = { {1,2},{3,4},{5,6} };//按行进行初始化,第一行的四个元素被初始化为1,2,0,0,第二行的四个元素被初始化成3,4,0,0,第三行的四个元素被初始化成5,6,0,0

注意:二维数组的行可以省略,但是列不能省略。列决定了一行可以放几个,所以只要列不省略,编译器就会自动计算出行数。

int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int arr1[ ][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
//这两个数组初始化的效果相同

2.3:二维数组的使用

二维数组中的每一个元素都有其对应的行号和列号,行号和列号都是从0开始的,故可以通过下标来使用二维数组

//通过下标访问数组的每一个元素并将其打印出来
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

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

//打印二维数组中每一个元素的地址
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	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;
}
//打印结果如下:
&arr[0][0]=00AFF860
&arr[0][1]=00AFF864
&arr[0][2]=00AFF868
&arr[0][3]=00AFF86C
&arr[1][0]=00AFF870
&arr[1][1]=00AFF874
&arr[1][2]=00AFF878
&arr[1][3]=00AFF87C
&arr[2][0]=00AFF880
&arr[2][1]=00AFF884
&arr[2][2]=00AFF888
&arr[2][3]=00AFF88C

可见二维数组中每相邻两个元素的地址是连续的

在这里插入图片描述

//因为二维数组中元素的存储是连续的,因此我们也可通过下面这种方式来打印数组中的每一位元素
int main()
{
	int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int* p = &arr[0][0];
	int i = 0;
	for (i = 0; i < 12; i++)
	{
		printf("%d ", *(p + i));
	}
	printf("\n");
	return 0;
}

如果把二维数组的每一行都看成是一个以为数组,那么:
arr[0]可以看作是第一行元素的数组名
arr[1]可以看作是第二行元素的数组名
arr[2]可以看作是第三行元素的数组名

2.4.1:二维数组行数和列数的计算

//计算二维数组由多少行
sizeof(arr)/sizeof(arr[0])//二维数组总的字节数除以每一行的字节数就是行数
//计算二维数组的列数
sizeof(arr[0])/sizeof(arr[0][0])//二维数组一行的字节数除以每一个元素的字节数就是列数

三:数组越界

int arr[10];//下标的取值范围是0~9,超出这个范围就是越界

数组的下标是有范围限制的。
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。

int main()
{
	int arr[10] = { 0 };
	//下标的取值范围是0~9,超出这个范围就是越界
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		printf("%d ", arr[i]);
	}
}
//结果:
0 0 0 0 0 0 0 0 0 0 -858993460
//通过最后一个打印结果(随机值)可以看出,发生了数组越界
int main()
{
	int arr[10] = { 0 };
	//下标的取值范围是0~9,超出这个范围就是越界
	arr[10] = 11;//数组已越界
}

执行上面这段代码,会报出如下的数组越界错误
/在这里插入图片描述

四:数组作为函数的参数

4.1:冒泡排序函数

冒泡排序的思想是:从左到右,相邻的两个元素进行比较,并且进行适当的交换。每次比较一轮,就会找到序列中最大的或者最小的数,这个数就会来到序列的最右边。
以从小到大排序为例:从左到右,让相邻的两个元素进行比较,如果左边的元素大于右边的元素就将这两个元素进行交换,从左到右,两两相邻的元素比较结束,最大的那个数就会来到序列的最右边,第一轮比较就结束了。接着进行第二轮,从左边第一个数一直比较到倒数第二个数(最后一个数就是序列里面最大的,无需再进行比较),如下图的序列:9 8 7 6 5 4 3 2 1.第一轮冒泡共进行了9次两两相互比较,第一轮冒泡排序结束后序列里面最大的数字9的位置就确定了下来,即在序列的最后面(因为是从小到大排序)。接着进行第二轮冒泡排序,找出9前面的序列:8 7 6 5 4 3 2 1 0里面的最大值让他来到9的前面,第二轮冒泡排序共进行了8次两两比较,最终序列:8 7 6 5 4 3 2 1 0中的最大值8来到了他应该出现的位置,即在9的前面。第三轮冒泡会让7去到他应该去到的位置,第四轮冒泡会让6去到他应该去到的位置。大家猜猜这十个数字要进行几轮冒泡呢?既然一轮搞定一个数字,那十个数字不就需要十轮嘛?答案是需要九轮,因为十个数经过九轮冒泡,那一定有九个数都去到了他们应该去的位置,既然九个数的位置都确定下来了,那最后剩的那个数肯定就在他应该出现的位置上,就不用再进行第十轮冒泡了。因此,如果有n个数就要进行n-1轮冒泡才能得到一个有顺序的序列。

在这里插入图片描述

void Sort(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//冒泡轮数
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)//第一轮要进行九次两两比较,第二轮要进行八次两两比较,所以这里的判断条件是sz-1-i
		{
			//每一轮冒泡要进行多少次两两比较
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}
int main()
{
	int arr[] = { 3,1,5,4,7,6,9,8,0,2 };//一共十个数
	//写一个函数对数组排序
	int sz = sizeof(arr) / sizeof(arr[0]);//计算数组中元素的个数,数组总的字节数除以每一个元素的字节数就是数组中元素的个数
	Sort(arr, sz);
	int i = 0;
	//将排完序的数组打印出来
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

注意:计算数组元素个数的这段代码“int sz = sizeof(arr) / sizeof(arr[0]);”一定得放在主函数里面,将计算出的个数sz传到Sort函数里面,切不可在Sort函数里面进行计算。为什么只能这样呢?这就是我们接下来要讨论的问题-数组名是什么了?

4.2:数组名是什么?

数组名是首元素的地址有两个例外
例外1:sizeof(数组名),这里的数组名是表示整个数组,计算的是整个数组的大小,单位是字节
例外2:&数组名,这里的数组名也表示整个数组,&数组名取出的是数组的地址

//通过这段代码可证明:数组名就是首元素的地址
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	printf("%p\n", arr);//打印数组名
	printf("%p\n", &arr[0]);//打印首元素地址
	return 0;
}
//执行结果:
004FFEAC
004FFEAC
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	printf("%p\n", arr);//打印数组名
	printf("%p\n", &arr[0]);//打印首元素地址
	printf("%p\n", &arr);//这里&arr取的是数组的地址
	return 0;
}
//执行结果:
00EFFBF8
00EFFBF8
00EFFBF8

虽然上面的这段代码打印出来的结果相同,但是意义却不相同,前两个仅仅代表首元素的地址,而第三个代表的是数组的地址
通过下面的代码便可得以验证

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

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+1);

	return 0;
}
//执行结果:
006FFB5C
006FFB60
006FFB5C
006FFB60
006FFB5C
006FFB80

可见arr+1和&arr[0]+1的结果一样,都是跳过了四个字节,而&arr+1则是跳过了四十个字节

在这里插入图片描述

这下我们就知道了:数组作为函数的参数,传递的是首元素的地址,既然是地址那形参就应该用一个指针来接收。此时我们再看这段代码“int sz = sizeof(arr) / sizeof(arr[0]);”如果放在函数内部,arr此时仅仅是一个整型指针,而一个指针的字节数在×86的环境下就是四个字节,所以sizeof(arr)的结果就是4而不是整个数组所占的字节数,而sizeof(arr[0])也是4,最终相除的结果就是1了,并不是我们想得到的数组元素个数,因此求数组的元素个数应该在主函数中求

//数组作为函数的参数
void Sort(int* arr)//形参是对应的指针类型
{

}

int main()
{
	int arr[] = { 1,2,3 };
	Sort(arr);//传的是首元素的地址
	return 0;
}

形参得到了数组的首元素地址,接着顺藤摸瓜就能找到数组中的其他元素

到这里,数组的有关分享就结束啦,喜欢的话可以点赞、评论和收藏哟!

在这里插入图片描述

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。