🦖作者:学写代码的恐龙
🦖博客主页:学写代码的恐龙博客主页
🦖专栏:【初级c语言】
🦖语录:❀未来的你,一定会感谢现在努力奋斗的自己❀
初级C语言之【数组】
- 一:一维数组的创建和初始化
- 1.1:数组的定义
- 1.2:数组的创建
- 1.2.1:数组创建举例:
- 1.3:数组的初始化
- 1.3.1:完全初始化
- 1.3.1.1:整型数组的完全初始化
- 1.3.1.2:字符数组的完全初始化
- 1.3.2:不完全初始化
- 1.3.2.1:整型数组不完全初始化
- 1.3.2.2:字符型数组不完全初始化
- 1.4:一维数组的使用
- 1.5:一维数组在内存中的存储
- 二:二维数组的创建和初始化
- 2.1:二维数组的创建
- 2.2:二维数组的初始化
- 2.3:二维数组的使用
- 2.4:二维数组在内存中的存储
- 2.4.1:二维数组行数和列数的计算
- 三:数组越界
- 四:数组作为函数的参数
- 4.1:冒泡排序函数
- 4.2:数组名是什么?
一:一维数组的创建和初始化
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;
}
形参得到了数组的首元素地址,接着顺藤摸瓜就能找到数组中的其他元素
到这里,函数栈帧的有关分享就结束啦,喜欢的话可以点赞、评论和收藏哟!