1. 数组的创建
数组是一组相同元素类型的集合。
数组的创建方式:
type_t arr_name [const_n]; //type_t 是指数组的元素类型 //const_n 是一个常量表达式,用来指定数组的大小。
实例一:
int arr[10];这种创建方式是正确的
int count = 10; int arr[count] = {0};//这种创建方式是错误的
注意:数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。在C99标准支持了变长数 组的概念,数组的大小可以使用变量指定,但是数组不能初始化。但是visual studio编译器不支持变长数组。例如gcc编译器就支持c99中的变长数组。
2. 数组的初始化
数组的初始化是指,在创建数组时,给数组的内容一些合理的初始值。
实例二:
int arr1[10] = {1,2,3}; int arr2[] = {1,2,3,4}; int arr3[5] = {1,2,3,4,5}; char arr4[3] = {'a',98, 'c'}; char arr5[] = {'a','b','c'}; char arr6[] = "abcdef";
注意:数组在创建的时候,如果不想指定数组的大小那么就必须要初始化。数组的元素个数根据初始化的个数内容来确定。但是要特别注意字符数组的情况。
实例三:
- char arr1[] = {'a','b','c'};arr1数组中存放了三个单独的字符,arr1数组中就有三个元素。
- char arr2[] = {"abc"};arr2数组中存放了一个字符串,由于字符串是以 '\0'结尾的,所以在字符串的末尾还包含了一个 ‘\0'字符,所以arr2数组中含有四个元素。
3. 一维数组的使用
对于一维数组的元素的访问,要用到 [](下标引用)操作符。
实例四:
#include <stdio.h> int main() { int arr[10] = { 0 };//数组的不完全初始化 //计算数组的元素个数 int sz = sizeof(arr) / sizeof(arr[0]); //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以: int i = 0;//做下标 for (i = 0; i < 10; i++)//这里写10,好不好? { arr[i] = i; } //输出数组的内容 for (i = 0; i < 10; ++i) { printf("%d ", arr[i]); } return 0; }
实例四中,成功的对数组元素进行了赋值和打印。在赋值时,i的值最好设置成数组的大小,也就是 变量sz,尽量不要就写个常量10,这样在数组的元素个数变化时,sz也能跟着动态变化,比较方便。
总结:
- 数组的元素是通过下标进行访问的,下标是从0开始的。
- 数组的大小可以通过计算得到。
即:
int arr[10]; int sz = sizeof(arr)/sizeof(arr[0]);
4. 一维数组在内存中的存储
实例五:创建数组,并打印数组每个元素的地址。
#include <stdio.h> int main() { int arr[10] = {0}; int i = 0; int sz = sizeof(arr)/sizeof(arr[0]); for(i=0; i<sz; ++i) { printf("&arr[%d] = %p\n", i, &arr[i]); } return 0; }
运行结果:
通过对地址的观察,随着数组下标的增长,相应的元素的地址大小也呈上升趋势,并且其差值刚好就是数组中存放的元素类型的字节大小。(例如这里就是int类型的数组,那么每个元素的地址值的差值就是4)
由此得出结论:数组在内存中是连续存储的。并且随着下标的增长,数组的地址值越来越大,也就是,数组元素是由低地址到高地址增长的。
5. 二维数组的创建和初始化
二维数组的创建:
int arr[3][4]; char arr[3][5]; double arr[2][4];
注意:二维数组如果有初始化,行不能省略,但是列不能省略。
6. 二维数组的使用
二维数组的使用也是通过下标访问符([])进行访问的。
实例六:
int main() { int arr[3][4] = { 0 }; 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]); } } for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } } return 0; }
运行结果:
通过分析,我们也能发现:其实二维数组在内存中也是连续存储的,所有的元素之间都是连续的。
7. 数组越界
- 数组的下标是有范围限制的。
- 所以数组的下标规定是从0开始的,最后一个元素的下标是数组的长度-1.(n-1)
- 所以如果数组的下标小于0或者超过了n-1,就是数组越界访问了,超出了数组的合法空间的访问。
- C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的。
所以,程序员在写代码时,最好自己做越界检查。
#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("%d\n", arr[i]);//当i等于10的时候,越界访问了 } return 0; }
当然,二维数组的行和列也可能存在越界。
8. 数组作为函数的参数
当我们将数组作为参数传递给函数时。比如:我要实现一个冒泡排序。
实例七:
#include <stdio.h> void bubble_sort(int arr[]) { int sz = sizeof(arr)/sizeof(arr[0]);//这样对吗? int i = 0; for(i=0; i<sz-1; i++) { int j = 0; for(j=0; j<sz-i-1; j++) { 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,7,5,8,9,0,2,4,6}; bubble_sort(arr);//是否可以正常排序? int i = 0; for(i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
注意:这段代码是有问题的,调试进入 bubble_sort 函数内部 sz,是1。所以在函数体内部是不能计算数组的大小的。
9. 数组名的含义
实例八:
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5}; printf("%p\n", arr); printf("%p\n", &arr[0]); printf("%d\n", *arr); //输出结果 return 0; }
运行结果:
结论:
数组名是数组首元素的地址。(有两个例子除外)。
那么,既然数组名是数组首元素的地址,那么:
int arr[10] = {0}; printf("%d\n", sizeof(arr));
为什么输出的结果是40呢?
- sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。
- &数组名,取出的是数组的地址。&数组名,数组名表示整个数组。
除了上诉两种情况之外,所有的数组名都是表示数组的首地址。
10. 冒泡排序的正确实现
当数组传参的时候,实际上只是把数组的首元素的地址传递过去了。即使将冒泡函数的参数部分写成 int arr[]但其本质上仍然是 int * arr的类型。所以,函数内部的 sizeof(arr) 结果是4。
那么正确的形式该怎么写呢?我们可以在函数外部将数组的元素个数算出来再将其传给函数。
//方法2 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-i-1; j++) { 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,7,5,8,9,0,2,4,6}; int sz = sizeof(arr)/sizeof(arr[0]); bubble_sort(arr, sz);//是否可以正常排序? for(i=0; i<sz; i++) { printf("%d ", arr[i]); } return 0; }
这样就可以避免数组元素个数的计算问题。
11. 完结
本章的内容就到这里啦,若有不足,欢迎评论区指正,最后,希望大佬们多多三连吧,下期见!