再识C语言(四)

再识C语言(四),第1张

再识C语言(四) 目录

数组

一维数组

二维数组

数组越界

数组作为函数参数

 数组名是什么

总结


数组

前面简单的认识了一下数组,这次更加深入的去了解一下C语言中的数组。

C语言是支持多维数组的,但是经常使用的是一维数组和二维数组。

存放一个数据就要创建一个变量来接收这个数据,如果要存放一百个数据就要创建一百个变量来接收这个一百个数据,这样做会很麻烦。所以C语言中引入了数组,创建一个数组就可以开辟一片连续的空间来存放多个数据。

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

一维数组

1、数组的创建

数组的创建方式:    type_t   arr_name [const_n];

  • type_t    :是指数组的元素类型
  • arr_name:是数组名
  • const_n :是一个常量表达式,用来指定数组的大小
    //创建一个存放5个元素的数组,这个数组中每个元素都是整型
	//数组名:arr,数组类型:int,数组大小:5(可以存放5个元素)
	int arr[5];

	//创建一个存放10个元素的数组,这个数组中每个元素都是字符
	//数组名:ch,数组类型:char,数组大小:10(可以存放10个元素)
	char ch[10];

	//创建一个存放15个元素的数组,这个数组中每个元素都是浮点型
	//数组名:du,数组类型:double,数组大小:15(可以存放15个元素)
	double du[15];

补充:变长数组。

变长数组是指用整型变量或表达式声明或定义的数组大小,而不是说数组的长度会随时变化,变长数组在其生存期内的长度同样是固定的。

  • C99标准之前是不支持数组的大小使用变量,只能使用常量!
  • C99中增加了变长数组的概念,允许数组的大小是变量,而且要求编译器支持C99。
  • 变长数组创建的时候不可以初始化。
  • 我们常见的编译器对C99的支持都不够好,这里只是简单的了解一下。

 2、数组的初始化

创建的同时给一些初始值叫初始化。

1.整型数组初始化

(1)完全初始化:数组的大小是多少就初始化多少个元素

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

如果数组中存放的元素大于数组的大小,程序运行就会报错。

 (2)不完全初始化:只初始化一部分元素,剩下的元素默认初始化为0。

     int arr[10] = { 1,2,3 };

 (3)将数组全部初始化为0

 int arr[10] = { 0 };

这种写法是将数组中第一个元素初始化为0,剩下的元素默认初始化为0。

(4)对数组初始化不指定数组大小。

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

当对数组初始化的同时可以不指定数组的大小,数组的大小是根据初始化的内容来确定。

补充:如果对数组初始化成下面这种代码,再对该数组赋多个值编译器就会报错。

int arr[] = { 0 }; 

将数组中的元素初始化为0,这里数组的大小是1。如果输入多个值会造成数组越界。

数组创建的时候也可以不初始化 ,但不对数组初始化数组中存放的就是随机值。

这样在使用数组时对数组是不可以控的,所以建议在创建数组的同时对数组进行初始化(可以初始化为0)。

2.字符数组初始化

    char ch1[] = { 'a',98,'b' }; //这里的98是 b 的ASCII码值
	char ch2[] = { 'a','b','c' };
	char ch3[] = "abcde";

  • ch1 与 ch2有3个元素,数组的大小是3个字节
  • ch3 有4个元素,数组的大小是4个字节

补充:

(1)strlen是一个库函数,使用时要引入头文件

计算的是字符串的长度,并且只能针对字符串,关注的是字符串中是否有'',计算的是''之前的字符个数。

(2)sizeof是一个 *** 作符(运算符)

sizeof是用来计算所占内存空间大小的,任何数据类型都可以使用,只关注空间的大小,不在乎内存中是否存在''。

 计算一下数组的大小:

计算一下字符串长度

因为ch1和ch2中并没有存放字符 '',所以计算出的的是随机值,ch3中字符串末尾隐藏了字符'',strlen只计算 '' 之前的字符,所以计算出的结果是3。

字符数组初始化是也可以指定大小,但是要注意当使用字符数组存放字符串的时候,数组的大小要比字符串中的字符个数多1。

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

3、数组的使用

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

(1)使用数组的下标来访问数组的一个元素。

  *** 作符: [ ] ,下标引用 *** 作符。就是数组访问的 *** 作符。

既然是 *** 作符那肯定有 *** 作数(比如 a+b 中,a 和 b 就是 + 的 *** 作数)。arr 和 5 就是 [ ] 的 *** 作数(这个可以在写代码的时候慢慢去感受)。

(2)访问数组的所有元素(输出)

#include
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	//计算数组的元素个数
	//sizeof(arr)计算的是这个数组的大小
	//sizeof(arr[0])计算数组第一个元素的大小
	//因为这个一个整型数组,每个元素都是整型,所占内存的大小都是4
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("n");
	return 0;
}

运行结果

(3)数组赋值(输入)

#include
int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);
	}

	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("n");
	return 0;
}

运行结果

总结:

  1. 数组是使用下标来访问的,下标是从0开始。
  2. 数组的大小可以通过计算得到。
    int arr[10];
	int sz = sizeof(arr) / sizeof(arr[0]);

 4、数组在内存中的存储

#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("&arr[%d] = %pn", i, &arr[i]);
	}
	return 0;
}

运行结果

#include
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int* p = &arr[0];
	for (i = 0; i < 10; i++)
	{
		printf("%p -------%pn", p + i, &arr[i]);
	}
	return 0;
}

 

只要指针变量 p 指向数组第一个元素的地址,p + i 就是 arr[ i ] 的地址 。

可以使用指针 p + i 访问数组的元素。

随着数组下标的增长,元素的地址也在有规律的递增。

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

二维数组

1、数组的创建

	//创建一个3行4列的整型数组,数组中每个元素都是整型
	int arr1[3][4];

	//创建一个2行5列的整型数组,数组中每个元素都是浮点型
	double arr2[2][5];

	//创建一个3行5列的整型数组,数组中每个元素都是字符型
	char arr3[3][5];

  2、数组的初始化

(1)完全初始化

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

(2)不完全初始化

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

 在指定位置放指定元素

二维数组初始化的时候,在大括号里将元素放到几个大括号内就表示将二维数组设置为几行。

 第一行放1,2;第二行放3,4;第三行放5,6;

 (3)二维数组如果有初始化,行可以省略,列不能省略

    int arr1[][3] = { {3,4},{5,6} };
	int arr2[][3] = { 3,4,5,6 };

二维数组可以省略行下标不可以省略列下标。

  • 二维数组在内存中的存储是连续的,第二行必须放到第一行的后面。
  • 省略了列但知道行,不能确定下一行的起始位置。
  • 省略了行但知道列,可以确定每一行的起始位置。
  • 数组会根据初始化的内容将一行占满后,再将下一个元素放到下一行。

 3、数组的使用

(1)使用数组的下标来访问数组的一个元素。

(2)访问数组的所有元素(输出)

#include
int main()
{
	int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr1[i][j]);
		}
		printf("n");
	}
	return 0;
}

运行结果

 (3) 数组赋值(输入)

#include
int main()
{
	int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			scanf("%d", &arr1[i][j]);
		}
	}

	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("%d ", arr1[i][j]);
		}
		printf("n");
	}
	return 0;
}

运行结果

 二维数组可以通过行列下标访问数组,行列下标都是从0开始的。

 4、数组在内存中的存储

#include
int main()
{
	int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("&arr1[%d][%d] = %pn", i, j, &arr1[i][j]);
		}
		printf("n");
	}
	return 0;
}

运行结果

数组越界

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

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

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

正确的代码 :

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

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

数组作为函数参数

写一个冒泡排序来感受一下数组传参

冒泡排序:

相邻的两个元素进行比较,不满足条件(升序或降序)就进行交换。

一趟冒泡排序可以确定一个数的位置(让当前待排序的数组中一个元素来到最终应该出现的位置)。n个元素就进行n-1趟排序。

代码

#include
void bubble_sort(int arr[])
{
	int i = 0;
	int j = 0;
	//计算元素个数
	int sz = sizeof(arr) / sizeof(arr[0]);
	//确定趟数
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		for (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] = { 10,9,8,7,6,5,4,3,2,1 };
	int i = 0;
	//设计一个函数对arr数组进行排序 - 冒泡排序
	bubble_sort(arr);
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("n");
	return 0;
}

运行结果

程序执行后根据结果可以看出,此时写的冒泡排序并没有将数组中的元素按升序排列。

错误原因:

根据代码的调试结果可以得出:实际上数组传参,传递的不是整个数组,传过去的是数组首元素的地址。

数组传参传递数组首元素的地址的原因:

  • 因为数组是在内存中开辟一大块空间,如果将这块空间传递过去,那么函数在接收的时候也要开辟一样大小的空间来接收这个数组,这样做就造成了空间的浪费。
  • 数组在内存中开辟的空间都是连续的(地址是连续的),数组传参的时候只要将数组首元素的地址传递过去,就可以找到整个数组的元素。所以数组传参的时候只传递了首元素的地址,这样不会浪费空间,还可以找到数组中的所有元素。

因为数组传参传递的是首元素的地址,所以在接收地址的时候可以写出指针的形式。

  • 严格的讲,函数接收数组传递的地址参数应该用指针接收,形参可以写出数组的形式是因为,写出数组的形式可以帮助初学者更好的理解。
  • 因为数组传递的是首元素的地址,所以需要在main函数中先计算出元素的个数,再将元素个数传递给函数。

正确的代码

#include
//void bubble_sort(int* arr,int sz)
void bubble_sort(int arr[], int sz)
{
	int i = 0;
	int j = 0;
	//确定趟数
	for (i = 0; i < sz-1; i++)
	{
		//一趟冒泡排序的过程
		for (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]);
	int i = 0;
	//设计一个函数对arr数组进行排序 - 冒泡排序
    //实际上数组传参,传递的不是整个数组,传过去的是数组首元素的地址
	bubble_sort(arr, sz);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

运行结果

 数组名是什么
#include
int main()
{
	int arr[] = { 1,2,3,4 };
	printf("%dn", sizeof(arr));
	return 0;
}

运行结果

 打印地址,看一下数组名

#include
int main()
{
	int arr[] = { 1,2,3,4 };
	printf("%pn", arr);//arr是数组首元素的地址
	printf("%pn", &arr[0]);//数组首元素的地址
	printf("%pn", &arr);//数组的地址
	return 0;
}

运行结果

 从结果可以看出,数组首元素的地址与数组的地址是相同的,可是两个所代表的意义并不相同。

给每个地址加1,看一下结果

#include
int main()
{
	int arr[] = { 1,2,3,4 };
	printf("%pn", arr);//arr是数组首元素的地址
	printf("%pn", &arr[0]);//数组首元素的地址
	printf("%pn", &arr);//数组的地址
	printf("n");
	printf("%pn", arr+1);//arr是数组首元素的地址
	printf("%pn", &arr[0]+1);//数组首元素的地址
	printf("%pn", &arr+1);//数组的地址
	return 0;
}

运行结果

  1. sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数 组。
  2. &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
  3. 除了这两种情况,所有的数组名都表示数组首元素的地址。
总结

这篇文章详细的叙述了C语言中数组的知识点,可能有些地方说的不是很清楚,望谅解!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5432566.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-11
下一篇 2022-12-11

发表评论

登录后才能评论

评论列表(0条)

保存