目录
数组
一维数组
二维数组
数组越界
数组作为函数参数
?数组名是什么
总结
数组
前面简单的认识了一下数组,这次更加深入的去了解一下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是一个库函数,使用时要引入头文件<string.h>
计算的是字符串的长度,并且只能针对字符串,关注的是字符串中是否有'\0',计算的是'\0'之前的字符个数。
(2)sizeof是一个操作符(运算符)
sizeof是用来计算所占内存空间大小的,任何数据类型都可以使用,只关注空间的大小,不在乎内存中是否存在'\0'。
?计算一下数组的大小:
计算一下字符串长度
因为ch1和ch2中并没有存放字符 '\0',所以计算出的的是随机值,ch3中字符串末尾隐藏了字符'\0',strlen只计算 '\0' 之前的字符,所以计算出的结果是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<stdio.h>
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<stdio.h>
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;
}
运行结果
总结:
- 数组是使用下标来访问的,下标是从0开始。
- 数组的大小可以通过计算得到。
int arr[10];
int sz = sizeof(arr) / sizeof(arr[0]);
?4、数组在内存中的存储
#include<stdio.h>
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] = %p\n", i, &arr[i]);
}
return 0;
}
运行结果
#include<stdio.h>
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 -------%p\n", 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<stdio.h>
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<stdio.h>
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<stdio.h>
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] = %p\n", i, j, &arr1[i][j]);
}
printf("\n");
}
return 0;
}
运行结果
数组越界
数组的下标是有范围限制的。
数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
正确的代码 :
C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。
程序员写代码时,最好自己做越界的检查。
数组作为函数参数
写一个冒泡排序来感受一下数组传参
冒泡排序:
相邻的两个元素进行比较,不满足条件(升序或降序)就进行交换。
一趟冒泡排序可以确定一个数的位置(让当前待排序的数组中一个元素来到最终应该出现的位置)。n个元素就进行n-1趟排序。
代码
#include<stdio.h>
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<stdio.h>
//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<stdio.h>
int main()
{
int arr[] = { 1,2,3,4 };
printf("%d\n", sizeof(arr));
return 0;
}
运行结果
?打印地址,看一下数组名
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4 };
printf("%p\n", arr);//arr是数组首元素的地址
printf("%p\n", &arr[0]);//数组首元素的地址
printf("%p\n", &arr);//数组的地址
return 0;
}
运行结果
?从结果可以看出,数组首元素的地址与数组的地址是相同的,可是两个所代表的意义并不相同。
给每个地址加1,看一下结果
#include<stdio.h>
int main()
{
int arr[] = { 1,2,3,4 };
printf("%p\n", arr);//arr是数组首元素的地址
printf("%p\n", &arr[0]);//数组首元素的地址
printf("%p\n", &arr);//数组的地址
printf("\n");
printf("%p\n", arr+1);//arr是数组首元素的地址
printf("%p\n", &arr[0]+1);//数组首元素的地址
printf("%p\n", &arr+1);//数组的地址
return 0;
}
运行结果
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数 组。
- &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
- 除了这两种情况,所有的数组名都表示数组首元素的地址。
总结
这篇文章详细的叙述了C语言中数组的知识点,可能有些地方说的不是很清楚,望谅解!
|