目录
1. 一维数组的创建和初始化
1.1 数组的创建
1.2 数组的初始化
1.3 一维数组的使用
1.4 一维数组在内存中的存储
1.4.1 内存和地址
1.4.2一维数组在内存中的存储
2. 二维数组的创建和初始化
2.1 二维数组的创建
2.2 二维数组的初始化
2.3 二维数组的使用
2.3.1 打印二维数组中的某个元素:
2.3.2 打印二维数组中的所有元素:
2.3.3 给二维数组重新赋值后再全部打印出来:
2.3.4 可以把二维数组理解为:一维数组的数组
2.4 二维数组在内存中的存储
3. 数组越界
4. 数组作为函数参数
4.1 冒泡排序函数
5. 数组名是什么?
5.1 一维数组的数组名的理解
5.1.1 sizeof(数组名) :数组名表示整个数组
5.1.2 &数组名 :数组名表示整个数组
5.2 二维数组的数组名的理解
1. 一维数组的创建和初始化
1.1 数组的创建
数组是一组相同类型元素的集合。
数组是一组相同类型元素的集合。
数组的创建方式:
数组的元素类型 数组名[常量表达式,用来指定数组的大小(即数组的元素个数)];
例如:
int arr[10]; char ch[5]; double data1[20]; double data2[15 + 5];
那数组的大小可以是变量吗?
在C99标准之前,数组的大小必须是常量或常量表达式
在C99标准之后,数组的大小可以是变量,为了支持变长数组(变长数组:数组的大小是通过变量来指定的,可以通过输入来指定数组的大小)下面的代码只能在支持C99标准的编译器上编译:
int n = 10; scanf("%d",&n); //通过输入来指定数组的大小 int arr2[n];//这个就是变长数组,注意:变长数组是不能初始化的。注意:即使是在C99标准之后,变长数组也是不能初始化的。
1.2 数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
不完全初始化:剩余的元素默认初始化为0
(1)整型数组:
int arr[10] = { 1,2,3 };
//数组有很多元素,初始化时用大括号括起来。
(2)字符数组:(字符数组初始化有两种方式)
逐个初始化字符数组:
char ch1[10] = { 'a','b','c' };
//char类型数组中放了三个字符a b c,剩余初始化0
字符串初始化字符数组:
char ch2[10] = "abc";
//此字符数组中放了四个字符a b c ' ',剩余初始化0
// {“abc”} 和 “abc” 都可以
完全初始化:
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
下面也是完全初始化
int arr2[] = { 1,2,3 }; //数组元素个数为 3
char ch3[] = { 'a','b','c' }; // a b c 数组元素个数为 3
char ch4[] = "abc"; // a b c 数组元素个数为 4
1.3 一维数组的使用
数组在内存中是连续存放的。
[ ] - 下标引用操作符
数组是使用下标来访问的,下标是从0开始。通过下标可以找到对应的元素 (如:arr[4]是下标为4的元素)
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
不完全初始化:剩余的元素默认初始化为0
(1)整型数组:
int arr[10] = { 1,2,3 };
//数组有很多元素,初始化时用大括号括起来。
(2)字符数组:(字符数组初始化有两种方式)
逐个初始化字符数组:
char ch1[10] = { 'a','b','c' };
//char类型数组中放了三个字符a b c,剩余初始化0
字符串初始化字符数组:
char ch2[10] = "abc";
//此字符数组中放了四个字符a b c ' ',剩余初始化0
// {“abc”} 和 “abc” 都可以
完全初始化:
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
下面也是完全初始化
int arr2[] = { 1,2,3 }; //数组元素个数为 3
char ch3[] = { 'a','b','c' }; // a b c 数组元素个数为 3
char ch4[] = "abc"; // a b c 数组元素个数为 4
数组在内存中是连续存放的。
[ ] - 下标引用操作符
数组是使用下标来访问的,下标是从0开始。通过下标可以找到对应的元素 (如:arr[4]是下标为4的元素)
打印下标为4的数组元素:
#includeint main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; //在内存的栈区找了一块连续的空间存放了这10个数值,这块空间叫arr printf("%dn", arr[4]);//打印下标为4的数组元素 return 0; }
打印数组中的所有元素:
数组的元素个数 = 总数组的大小 / 第一个元素的大小
sz = sizeof(arr) / sizeof(arr[0])
sizeof(数组名) - 计算的是总数组的大小,单位是字节
#includeint main() { int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]);//sz - 数组元素个数 //打印数组所有元素,写个循环 for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } printf("n"); //倒着打印数组所有元素,写个循环 for (i = sz - 1; i >= 0; i--) { printf("%d ",arr[i]); } return 0; }
可能有的同学会有疑问,为什么在这里可以用 arr[ i ] ,怎么在这里数组的大小可以是变量了呢?
在1.1数组的创建中我们说过,创建数组时,在C99标准之后数组的大小([ ]中)才能是变量;在C99标准之前,数组的大小必须是常量或常量表达式。
但是在这里是访问数组元素,是通过数组下标访问数组元素,
访问数组元素不是创建数组,访问数组元素时数组下标([ ]中)可以是变量!!!
那如果数组是字符数组呢,如何打印数组中的所有元素?
1、逐个初始化字符数组时:char arr[ ] = { 'a','b','c','d','e','f' };
打印方法和打印整型数组中所有元素的方法一样。
#includeint main() { char arr[] = { 'a','b','c','d','e','f' }; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); for (i = 0; i < sz; i++) { printf("%c ", arr[i]); } return 0; }
2、字符串初始化字符数组时:char arr[ ] = "abcdef";
因为是字符串,除了上面那种打印方法以外,还有第二种打印方法:打印字符串
#includeint main() { char arr[] = "abcdef"; int i = 0; int sz = sizeof(arr) / sizeof(arr[0]); //第一种打印方法: for (i = 0; i < sz; i++) { printf("%c", arr[i]); } printf("n"); //第二种打印方法: printf("%sn", arr); return 0; }
总结:
1、数组在内存中是连续存放的。
2、数组是使用下标来访问的,下标是从0开始。
3、数组的大小可以通过计算得到。
4、访问数组元素时数组下标可以是变量
5、字符串初始化的字符数组 打印数组元素时可以直接用%s打印字符串。
1.4 一维数组在内存中的存储
1.4.1 内存和地址
内存是电脑上特别重要的存储器。为了有效的使用内存,内存被划分成一个个小的内存单元,每个内存单元的大小是1个字节。每个内存单元都有一个编号,编号就是地址。每个字节都有一个地址。相邻地址之间相差1个字节。
内存是电脑上特别重要的存储器。为了有效的使用内存,内存被划分成一个个小的内存单元,每个内存单元的大小是1个字节。每个内存单元都有一个编号,编号就是地址。每个字节都有一个地址。相邻地址之间相差1个字节。
1.4.2一维数组在内存中的存储
我们说数组在内存中是连续存放的,那这到底是是不是真的呢?
通过打印数组的每个元素的地址来观察:( %p:打印地址)
#includeint main() { int arr[] = { 1,2,3,4,5,6,7,8,9,0 }; int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz; i++) { printf("&arr[%d] = %pn", i, &arr[i]); } return 0; }
观察内存和运行结果可以看到,
数组中每一个整形元素都占4个字节,每个字节都有一个地址,但是打印时只会输出第一个元素的地址 (看运行结果)。数组相邻元素的地址之间(如 &arr[0]和&arr[1])刚好相差4个字节,所以一维数组在内存中是连续存放的。
随着数组下标的增长,元素的地址,也在有规律的递增,从低地址到高地址。
2. 二维数组的创建和初始化
2.1 二维数组的创建
数组的元素类型 数组名[行][列];
例如:
int arr[3][4];
char arr[3][5];
double arr[2][4];
2.2 二维数组的初始化
数组的元素类型 数组名[行][列];
例如:
int arr[3][4]; char arr[3][5]; double arr[2][4];
2.2 二维数组的初始化
二维数组初始化时,可以省略行,但必须有列。
完全初始化:
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 }; int arr[][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 }; int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} }; int arr[][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };不完全初始化:剩余的元素默认初始化为0
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4 }; int arr[][4] = { 1,2,3,4,2,3,4,5,3,4 };int arr[3][4] = { {1,2,3},{2,3,4},{3,4,5} }; int arr[][4] = { {1,2,3},{2,3,4},{3,4,5} };
2.3 二维数组的使用
二维数组的行和列的下标都是从0 开始的。通过下标可以找到对应的元素。
二维数组的行和列的下标都是从0 开始的。通过下标可以找到对应的元素。
2.3.1 打印二维数组中的某个元素:
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
printf("%dn",arr[0][1]);
printf("%dn",arr[2][0]);
return 0;
}
2.3.2 打印二维数组中的所有元素:
#include
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
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.3.3 给二维数组重新赋值后再全部打印出来:
#include
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
scanf("%d", &arr[i][j]);
}
}
printf("n");
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.3.4 可以把二维数组理解为:一维数组的数组
把二维数组的一行看成一个元素,那么二维数组就可以看成一维数组。比如下图这个二维数组就可以看成有3个元素的一维数组。
其中,每个元素又是一个一维数组,所以,可以将二维数组理解为:一维数组的数组。
我们通过 数组名[下标];的形式访问一维数组的某个元素,
访问二维数组第一行的某个元素时是 arr[ 0 ][ 下标 ] ,那么我们就可以将 arr[ 0 ] 看做是第一行这个一维数组的数组名,同样的,我们可以将 arr[ 1 ] 看做是第二行这个一维数组的数组名,arr[ 2 ] 看做是第三行这个一维数组的数组名。
把二维数组的一行看成一个元素,那么二维数组就可以看成一维数组。比如下图这个二维数组就可以看成有3个元素的一维数组。
其中,每个元素又是一个一维数组,所以,可以将二维数组理解为:一维数组的数组。
我们通过 数组名[下标];的形式访问一维数组的某个元素,
访问二维数组第一行的某个元素时是 arr[ 0 ][ 下标 ] ,那么我们就可以将 arr[ 0 ] 看做是第一行这个一维数组的数组名,同样的,我们可以将 arr[ 1 ] 看做是第二行这个一维数组的数组名,arr[ 2 ] 看做是第三行这个一维数组的数组名。
如下图:
2.4 二维数组在内存中的存储
#include
int main()
{
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("&arr[%d][%d] = %pn", i,j,&arr[i][j]);
}
}
return 0;
}
所以,二维数组在内存中也是连续存放的
因为二维数组在内存中是连续存放的,所以其实
int arr[3][4]; 与 int arr[12]; 这两个数组最终在内存中的体现是完全相同的,只是访问的形式有所不同而已。(一个按照二维数组的形式访问,一个按照一维数组的形式访问)
3. 数组越界
数组的下标是有范围限制的。
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的, 所以程序员写代码时,最好自己做越界的检查。
数组的下标是有范围限制的。
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
C语言本身不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的, 所以程序员写代码时,最好自己做越界的检查。
越界访问的例子:(只能访问到下标为5的元素,之后就是越界访问)
#includeint main() { int arr[] = { 1,2,3,4,5,6 }; int i = 0; for (i = 0; i < 10; i++) { printf("%d ",arr[i]); } return 0; }
二维数组的行和列也可能存在越界。有时候是在自己的范围内越界的。但最终还是会访问到非我们自己空间的数据。
二维数组的越界例子:
#includeint main() { int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 }; 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; }
4. 数组作为函数参数
往往我们在写代码的时候,会将数组作为参数传给函数。
数组传参传的是数组名(如 arr),传参时数组名本质上是数组首元素的地址,所以数组传参实际上传递的是数组首元素的地址,而不是整个数组。而且数组传参时,形参部分并不会创建一个数组,所有的访问数组都是在访问main()函数中的那个数组。
数组传参的时候,形参有2种写法:
1、数组 (非常直观,数组传参,数组接收) int arr[ ](因为传的是数组首元素的地址,地址应该用指针来接收,所以这里看似是数组,本质是指针变量)
2、指针 int* arr
4.1 冒泡排序函数
下面我们来实现一个冒泡排序函数(将数组作为参数传给函数):
冒泡排序的核心思想:
两个相邻的元素进行比较 ,如果比较出来的大小不满足我们需要的顺序,就进行交换,来调整顺序。
假设有一个数组int arr[ ] = { 9,8,7,6,5,4,3,2,1,0 };
用冒泡排序的算法将数组中的元素排成升序并打印出来:
解题思路:
一趟冒泡排序让一个数据来到它最终应该出现的位置上!
那么如果数组中有sz个元素,则需要sz-1趟冒泡排序
第1趟冒泡排序需要sz-1-0次两两相邻比较
第2趟冒泡排序需要sz-1-1次两两相邻比较第3趟冒泡排序需要sz-1-2次两两相邻比较
代码和运行结果:
#include//冒泡排序 void bubble_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++) { //两两相邻比较 if (arr[j] > arr[j + 1]) { //交换 int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[] = { 9,8,7,6,5,4,3,2,1,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz); int i = 0; for (i = 0; i < sz; i++) { printf("%d ",arr[i]); } return 0; }
5. 数组名是什么?
5.1 一维数组的数组名的理解
前面提过数组名是首元素的地址,其实是有一点不准确的。
数组名确实能表示首元素的地址,但是有两个例外:
1、sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
除此之外,数组名都是首元素的地址!!!
5.1.1 sizeof(数组名) :数组名表示整个数组
#include
int main()
{
int arr[10] = {0};
printf("%dn", sizeof(&arr[0]));
printf("%dn", sizeof(arr));
return 0;
}
5.1.2 &数组名 :数组名表示整个数组
#include
int main()
{
int arr[10] = {0};
printf("%pn", arr);//arr就是首元素的地址
printf("%pn", arr+1);
printf("-----------------------n");
printf("%pn", &arr[0]);//首元素的地址
printf("%pn", &arr[0]+1);
printf("-----------------------n");
printf("%pn", &arr);//整个数组的地址
printf("%pn", &arr+1);
return 0;
}
5.2 二维数组的数组名的理解
两个例外和一位数组一样:
1、sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
除去两个例外之外,二维数组的数组名表示的是数组首行的地址
其实,二维数组的数组名也表示数组首元素的地址,
但这个首元素的地址不是arr[0][0]的地址(&arr[0][0] ),
而是要把二维数组看成一维数组的数组,
数组首元素的地址其实是首行arr[0]的地址 ( &arr[0] ),
所以说,二维数组的数组名表示的是数组首行的地址。
两个例外和一位数组一样:
1、sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节
2、&数组名,这里的数组名表示整个数组,取出的是整个数组的地址
除去两个例外之外,二维数组的数组名表示的是数组首行的地址
其实,二维数组的数组名也表示数组首元素的地址,
但这个首元素的地址不是arr[0][0]的地址(&arr[0][0] ),
而是要把二维数组看成一维数组的数组,
数组首元素的地址其实是首行arr[0]的地址 ( &arr[0] ),
所以说,二维数组的数组名表示的是数组首行的地址。
可以通过以下代码来理解:
#includeint main() { int arr[3][4] = {0}; printf("%pn", &arr[0][0]);//首元素的地址 printf("%pn", &arr[0][0] + 1); printf("-----------------------n"); printf("%pn", &arr[0]);//首行的地址 printf("%pn", &arr[0]+1); printf("-----------------------n"); printf("%pn", arr);//arr是首行的地址 printf("%pn", arr + 1); return 0; }
理解这些之后,我们可以通过计算得到二维数组的行数和列数:
行:总数组的大小/一行的大小 sizeof(arr) / sizeof(arr[0]) arr[0]是第一行的数组名
列:一行的大小/一个元素的大小 sizeof(arr[0]) / sizeof(arr[0][0]) arr[0][0]是第一个元素
#includeint main() { int arr[3][4] = { 0 }; printf("%dn", sizeof(arr) / sizeof(arr[0])); printf("%dn", sizeof(arr[0]) / sizeof(arr[0][0])); return 0; }



