C语言数组超详细解析

Posted Clover三叶

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言数组超详细解析相关的知识,希望对你有一定的参考价值。

C语言数组

【一、数组的创建和初始化】

1.数组的创建

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

         创建方式:

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

例子:

int arr[10];
char arr2[5];
float arr3[1];
double arr4[20]; 

注意:括号内是一个常量表达式

int n = 5;
char ch[n];
//这是错误的,n 是一个变量不是常量

 

2.数组的初始化

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

例子:

int arr[10] = {1,2,3};
char arr2[4] = {\'a\',\'b\'};
char arr3[5] = "ab";
char arr4[8] = "abcdef";

提示:

  • char arr4[8] = "abcdef"; 的元素是 7 个,包括了 ‘\\0’
char arr4[] = "abcdef";
printf("%d\\n",sizeof(arr4));
printf("%d\\n",strlen(arr4));  //库函数,头文件——<string.h>

运行结果:
7
6

分析:arr4 为 char 类型,所以每个元素占一个字节,有 7 个元素,1 * 7 = 7,使用 strlen 计算字符串长度时,‘\\0’ 不计入内容,所以共 6 个元素,1 * 6 = 6

【拓展】:

int arr4[] = {1,2,3};
printf("%d\\n",sizeof(arr4));

运行结果:12

分析:arr4 为 int 类型,所以每个元素大小为 4 个字节,共三个元素,3 * 4 = 12

 

  • char arr2[4] = {\'a\',\'b\'}; 可以替换成 char arr2[4] = {\'a\',98};  (效果是相同的)
  • int arr[10] = {1,2,3}; arr 给了 10 个元素,但是只初始化了 3 个,那剩下的默认初始化为 0 ,其他的也是,没有初始化的都默认为 0 

 

char arr4[] = "abcdef";

这种写法是允许的,编译器会根据初始化的内容来给定数组长度

 

前面说过在创建数组的时候如果想不指定数组的确定的大小就要初始化。数组的元素个数根据初始化的内容来确定。但是对于下面的代码要区分,内存中是如何分配的:

char arr[] = "abc";
char arr1[3] = {\'a\',\'b\',\'c\'};
printf("%d\\n",sizeof(arr));
printf("%d\\n",sizeof(arr1));
printf("%d\\n",strlen(arr));
printf("%d\\n",strlen(arr1));

为什么 strlen(arr1)输出的结果是 6 ,之前的博文有讲过,当然 6 不是定值

 

【二、一维数组的使用】

1.一维数组的使用

  • 对于数组的使用我们之前介绍了一个操作符:[ ],这个是下标引用操作符,它其实就是数组访问的操作符。
char arr[] = "abcdef";
printf("%c\\n",arr[3]);

运行结果:d

 

打印数组中的所有元素:

char arr[] = "abcdef";
for(int i = 0;i < 6;i ++)
{
	printf("%c\\n",arr[i]);
}

其他的写法:

char arr[] = "abcdef";
for(int i = 0;i < (int)strlen(arr);i ++)
{
	printf("%c\\n",arr[i]);
}

strlen 默认是无符号整型,使用 int 强转成有符号整型

char arr[] = "abcdef";
int len = strlen(arr);
for(int i = 0;i < len;i ++)
{
	printf("%c\\n",arr[i]);
}

 

2.使用sizeof求元素个数

int arr[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i < len;i ++)
{
	printf("%d ",arr[i]);
}

分析:sizeof(arr) 表示整个数组的大小,一共 10 个元素(这里没有‘\\0’了,不高搞混,这不是字符串),故大小为 4 * 10 = 40 个字节,sizeof(arr[0]) 表示,第一个元素的大小,结果为 4;len = 40 / 4 = 10 ;i < 10

 

 

【三、一维数组在内存中的存储】

int arr[] = {1,2,3,4,5,6,7,8,9,10};
int len = sizeof(arr)/sizeof(arr[0]);
for(int i = 0;i < len;i ++)
{
	printf("&arr = [%d] = %p\\n",i,&arr[i]);
}

分析:每个元素的距离都是相同的,他们都是整型,每个地址都差 4 ,由此可得出结论,数组在内存中是连续存放的,下标在增长的同时,地址也在增长。

 

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

1.二维数组的创建

int arr[3][4];           //三行四列 
char ch[5][6];         //五行六列 
double arr1[2][4];  //二行四列 

 

2.二维数组的初始化

int arr[3][4] = {1,2,3,4,5};  //不完全初始化 

int arr[3][4] = {{1,2,3},{4,5}};   //不完全初始化

注意:两种效果是不同的,注意看

 

一维数组:

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

二维数组:

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

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

二维数组正确写法

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

 

【五、二维数组的使用】

int arr1[][4] = {{1,2,3},{4,5,6},{7,8,9}};
for(int i = 0;i < 3;i ++)
{
	for(int j = 0;j < 4;j ++)
	{
		printf("%d ",arr1[i][j]);
	}
	printf("\\n");
}

 

【六、二维数组在内存中的存储】

1.代码

int arr1[][4] = {{1,2,3},{4,5,6},{7,8,9}};
for(int i = 0;i < 3;i ++)
{
	for(int j = 0;j < 4;j ++)
	{
		printf("&arr[%d][%d] = %p   ",i,j,&arr1[i][j]);
	}
	printf("\\n");
}

 

2.在内存中的存储方式

 

【7、数组作为函数参数】

1.冒泡排序法思想

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

冒泡排序:

【降序变成升序】

原来的数组排序:10  9  8  7  6  5  4  3  2  1(降序)

要求的数组顺序:1  2  3  4  5  6  7  8  9  10(升序)

思路:要想变成升序首先要找到最大的数,并且让他放到最后一位,要找到这个最大的数就要进行比较,两两之间进行比较,大的数往后放,小的数往前放,一直到满足升序为止

具体操作:

第一趟就找到了最大的数,接下来就找第二大的数,也是同样的方法,两两进行比较,换位,直到大于其他数,小于10

一直以此类推下去,这里就不多画图了,而且有图不难发现,每进行一趟就少一行,少比较一次,第一趟的时候原本有10个元素,两两之间比较了9次,如下图

所以一共进行 9 趟循环,每趟循环都少比较 1 次,例如,第一趟比较 9 次,第二趟比较 8 次,以此类推第九趟比较 1 次,所以这里要使用嵌套循环,一层表示趟数,一层表示每趟之中元素两两的比较数

for(int i = 0;i < sz - 1;i ++)
{
	for(int j = 0;j < sz - 1 - i;j ++)
	{
			
	}
}

sz 指的是元素的个数,下面会讲到:(这里有一个地方容易出错)

我们现在说的是调用函数来实现排序的方法

他们会把数组当做参数传给函数,然后在函数里面进行求函数个数(sz)

错误代码

void bubble(int arr[])
{
	int sz = sizeof(arr)/sizeof(arr[0]);
	
	for(int i = 0;i < sz - 1;i ++)
	{
		for(int j = 0;j < sz - 1 - i;j ++)
		{
			if(arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
			
		}
	}
}

这样子做编译器当然是不会报错的,因为在语法上他并无问题,但是我们知道,一个数组当做参数传给函数时,它传的是数组首元素的地址,那么 sizeof(arr) 的值就是 4 ,sizeof(arr[0] 的值也是 4 ,4 / 4 = 1,sz = 1,for 循环中,第一层循环条件为 0 ,假,所以循环没有机会执行,最终也没有进行排序,打印出来的结果没变

正确的写法应该是,sz 的大小在主函数中计算完成后再传给函数使用:

完整正确的代码(调用函数写的)

#include <stdio.h>
void bubble(int arr[],int sz)
{
	for(int i = 0;i < sz - 1;i ++)
	{
		for(int j = 0;j < sz - 1 - i;j ++)
		{
			if(arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
			
		}
	}
}
int main()
{
	int arr[] = {10,9,8,7,6,5,4,3,2,1};
	int sz = sizeof(arr)/sizeof(arr[0]);  //10
	bubble(arr,sz);
	
	for(int i = 0;i < sz;i ++)
	{
		printf("%d ",arr[i]);
	}
	
	return 0;
}

完整正确的代码(在主函数直接写的)

#include <stdio.h>
int main()
{
	int arr[] = {10,9,8,7,6,5,4,3,2,1};
	int sz = sizeof(arr)/sizeof(arr[0]);  //10
	
	for(int i = 0;i < sz - 1;i ++)
	{
		for(int j = 0;j < sz - 1 - i;j ++)
		{
			if(arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
			
		}
	}
	
	for(int i = 0;i < sz;i ++)
	{
		printf("%d ",arr[i]);
	}
	
	return 0;
}


2.上面写法的局限性

当要排序的数组已经有序了,例如要实现升序排序,但是需要排序的数组已经是升序的情况,程序还是老老实实两两之间进行比较,这样效率就低了,我们可以优化代码,来解决这种情况。

思路:当对数组进行第一趟排序的时候,内部一对元素都没有交换,说明已经是有序的,就不需要进行后续的冒泡排序了,这样就达到了优化的效果

代码:

void bubble(int arr[])
{
	int sz = sizeof(arr)/sizeof(arr[0]);
	
	for(int i = 0;i < sz - 1;i ++)
	{
		int flag = 1;
		for(int j = 0;j < sz - 1 - i;j ++)
		{
			if(arr[j] > arr[j + 1])
			{
				int temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
				int flag = 0;
			}
			
		}
		if(1 == flag)
		{
			break;
		}
	}
}

flag  = 1 表示数组已经有序,进行第二层循环,两两之间进行比较,当有元素是无序的,那必然会进行交换,并且执行 flag = 0,这就说明了本趟循环不完全有序,进行第二趟循环,再使 flag = 1,表示数组已经有序,直到程序不执行交换,flag仍然是 1 的时候,跳出循环,表示数组已经有序了

 

这里有些朋友会疑惑,break 不是只能用在循环中吗,那怎么用在 if 语句中了

仔细看看,代码中的 if 语句是嵌套在 for 循环中的,并不是单独的使用,在那个代码中执行 break 后就跳出了循环;当然如果没有循环直接让 break 和 if 语句使用那是不行的,语法错误

#include <stdio.h>
int main()
{
	if(1)
		break;
	
	return 0;
}

 

3.数组名的使用

  • 数组名是首元素的地址(有两个例外)
int arr[] = {1,2,3,4,5,6,7};
printf("%p\\n",arr);
printf("%p\\n",&arr[0]);
printf("%d\\n",*arr);

对首元素地址解引用得到值,这样就进一步证明数组名就是首元素地址



例外1.

int sizeof(arr)/sizeof(arr[0]);

sizeof 内部单独放一个数组名,此时的数组名表示整个数组的大小,单位:字节

 

例外2.

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

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

呀!这不是一样的吗?虽然三个结果相同,但是所代表的意义不同,&arr取出的不是首元素的地址,那首元素的地址为什么会和整个元素的地址相同,他们之间有什么联系吗?

当然有了,接下来我们就来剖析

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

这样是不是就一幕了然了,前面的 arr + 1 和 &arr[0] + 1 后地址地址和上一个地址都相差 4 ,而 &arr 的地址与 &arr 的地址相差了 1C,也就是 28,数组中 一共 7 个元素,每个元素 4 个字节,整个元素大小为 28 个字节,所以 &arr + 1 就跳过了整个数组,这就证明了,&arr 取出来的地址代表整个数组的地址

 

哈哈,这章就到这里,有错误或者不懂的地方可以评论区留言,会及时纠正错误和解答疑惑

以上是关于C语言数组超详细解析的主要内容,如果未能解决你的问题,请参考以下文章

C语言知识点总结笔记吃透getchar()函数,超详细解析!!!

C语言解题篇一看就会用,超详细解析递归函数!!!

手把手带你搞定C语言实现三子棋游戏,让你的代码有趣起来(超详细教程,从思路到代码,快码起来!)

指针的这些知识你知道吗?C语言超硬核指针进阶版3w+字详解+指针笔试题画图+文字详细讲解

C语言指针(指针数组数组指针函数指针传参回调函数等)超详细

搞定C语言指针,指针超详细讲解,及指针面试题