数组是一组相同类型元素的集合。
数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
int a1[5];
char a2[6];
float a3[7];
double a4[4 + 4]; //也可以是一个表达式
int n = 0;
scanf("%d", &n);
int arr[n];
为什么不可以呢?
变长数组
的概念讲完了数组该如何去创建,接下去我们来谈谈数组该如何初始化
首先要来辨析一下初始化和赋值的区别。万不可以混淆
int n = 0; //初始化
int m;
m = 0; //赋值
接下去就来看看数组的初始化
//1.不完全初始化,数组个数10个。第一个元素为1,其余9个位0
int arr1[10] = { 1 };
//2.完全初始化,数组个数10个
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
//3.若没有指定数组的个数,则初始化了几个这个数组的大小就为几
int arr3[] = { 1,2,3,4,5 };
可以通过DeBug调试来观察一下
//1.数组大小为4,初始化四位,abc + '\0'
char ch1[] = "abc";
//2.数组大小为3,初始化前三位,abc
char ch2[] = { 'a', 'b', 'c' };
\0
;若是以单个字符的形式初始化,则数组大小即为初始化的字符个数
拓展:数组作为局部变量
不初始化内容默认为【随机值】;数组作为全局变量
不初始化内容默认为【0】
初始化好了,那这个数组就可以使用了,我们来用用看
对于数组的使用我们之前介绍了一个操作符: []
,下标引用操作符。它其实就数组访问的操作符
int main(void)
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
sizeof()
去首先计算出数组的大小int main(void)
{
int arr[20] = { 1,2,3,4,5,6,7,8,9,10,11,12};
int sz = sizeof(arr) / sizeof(int);
int i = 0;
for (int i = 0; i < sz; ++i)
{
printf("%d ", arr[i]);
//arr[i]表示在访问数组中的一个元素,因此可以使用变量【C99】
}
return 0;
}
那有些同学可能会问这个arr[]
括号里面不是不可以写变量吗,上面还说到了VS不支持C99?
arr[i]
是在访问数组中的元素,上面说到不可以使用这个【变长数组】是在我们定义数组的期间,不可以去使用,这里已经在访问数组元素了是不受影响的小结:
想知道这个一维数组在内存中是如何存储的嘛,那就看看这一小节吧
int main(void)
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(int);
for (int i = 0; i < sz; ++i)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
低地址
,下面是高地址
,因为它们都是局部变量,都是在main函数的栈帧中开辟的,所以它们都是存放在栈中的小结:
讲完一维数组,接下去我们来讲讲二维数组,它能以一个矩阵的形式来存储数据
int main(void)
{
int arr1[3][4]; //整型二维数组
double arr2[3][5]; //字符型二维数组
float arr3[4][5]; //浮点型二维数组
return 0;
}
接下去讲重点了,注意看❗❗❗
对于二维数组在初始化的时候可以省略行,但是不可以省略列
我们可以到VS中来观察一下答:列决定了一行有几个元素,几行是由行数来决定的。行数可以不知道,但是列数必须知道,要告诉编译器你这一行有多少元素。这其实和数据库中的字段值挺像的。因为字段值是可以用来确定这个表的结构
初始化好了,我们来用用看,将一个二维数组打印在屏幕上
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
printf("%d ", arr1[i][j]);
}
printf("\n");
}
for (int j = 0; j < 4; ++j)
{
for (int i = 0; i < 3; ++i)
{
printf("%d ", arr1[i][j]);
}
printf("\n");
}
然后来说说对于二维数组在内存中是如何存储的
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 4; ++j)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr1[i][j]);
}
printf("\n");
}
1行12列的一维数组
有一点正好在这里说明,因为我们下面要讲到首元素地址
arr[1]
是第一行的首元素地址arr[2]
是第二行的首元素地址arr[3]
是第三行的首元素地址接下去我要讲得是非常重要的内容,无论是大神还是小白,都可能会犯这样错误。但是程序出现问题
int main(void)
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
for (int i = 0; i < 3; ++i)
{
for (int j = 0; j < 5; ++j)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
0~4
会依次访问五个元素,但是每一行只有四个元素;我刚才说过对于二维数组来说其实就相当于是一个一维数组char arr[] = "";
scanf("%s", arr);
printf("%s\n", arr);
arr[]
并没有指定数组的大小,因此数组大小由初始化的字符个数决定。但是可以看到这里只初始化了一个空字符,也就相当于只有一个\0
,那么这个数组的大小即为1。所以当我scanf
输入一个长度大于1的字符串时,其实就会造成数组越界的问题【arr数组周围的堆栈被破坏即为数组越界】int arr1[] = { 0 };
for (int i = 0; i < 10; ++i)
{
arr1[i] = i;
}
0~10
这个范围内的数据都放入arr数组中,仅放入一个是没问题的,但若是再放的话就不行了,便会导致数组越界的问题那有同学就就问为什么对于数组越界编译器察觉不了呢,因为编译器并不是探测仪,并不是所有的BGU它都可以抓得到,就像警察一样并不是每个小偷它都可以抓得到,这样应该是很形象了
对于冒泡排序来说,如果有不懂的可以看看我的这篇文章 ——> 十大排序超硬核八万字详解
void PrintArray(int* a, int n)
{
for (int i = 0; i < n; ++i)
{
printf("%d ", a[i]);
}
printf("\n");
}
void BubbleSort(int a[10])
{
int n = sizeof(a) / sizeof(a[0]);
for (int i = 0; i < n - 1; ++i)
{
for (int j = 0; j < n - 1 - i; ++j)
{
if (a[j] > a[j + 1])
{
int t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
}
int main(void)
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
PrintArray(arr, sz);
BubbleSort(arr);
PrintArray(arr, sz);
return 0;
}
首元素地址
,而并不是把整个数组作为参数传递过去,这一点对于编译器来说是做不到的既然是这样的话,就可以说得通了,为什么这个n算出来为1。
sizeof(a)
计算的便是首元素的字节大小,而sizeof(a[0])
计算的也是数组中第一个元素的大小。这两个值计算出来都是4B,那么相除便得到了1既然讲到了数组名这个东西,我就讲一下有关数组名相关的知识点。好做一个区分
sizeof(数组名)求解的是整个数组的字节大小
sizeof(arr)
最后打印出来的结果是多少呢❓int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr));
sizeof(a)
得出的结果为4B,但是这个为什么是40B呢sizeof(数组名)
计算的就是整个数组的大小,因为arr数组中有十个元素,一个整型元素占4个字节,所以整个数组的大小即为40B&数组名为整个数组的地址
printf("%p\n", &arr[0]);
printf("%p\n", arr);
printf("%p\n", &arr);
arr[0]
指的是首元素①,&arr[0]
指的便是首元素的地址;对于arr
来说也是一样为首元素地址来总结一下上面所说的三种情况
&数组名:数组名表示整个数组。取出的是整个数组的地址
sizeof(数组名):数组名表示整个数组。求解的是整个数组的大小,单位是字节
除此之外见到数组名全部都为该数组的首元素地址
了解了单单出现数组名为首元素地址,我们便可以对上面所写的冒泡排序做一个改进了
void BubbleSort(int a[10], int n)
BubbleSort(arr, sz);
arr[0]
来访问太累了,不妨我们将数组的首元素地址给到一个指针变量,让它保存下这个地址,然后让它逐步地向后移动。如果对指针还不是很了解的看看这篇文章——> 底层之美,莫过于C【1024,从0开始】先去了解一下什么是指针int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
p
指针第一个元素所在的地址,那么p + 1
便是指向2所在元素的地址,那要访问到这个地址上所在的内容,那就要使用到*
这个符号,对这块地址进行解引用*(p + 1)
,此时就可以访问到2这个元素了。那找3,找4也是一样的,只需要让这个指针向后偏移即可,所以我们可以通过循环去找,访问第i个元素便是*(p + i)
for (int i = 0; i < 10; ++i)
{
printf("%p == %p\n", p + i, &arr[i]);
}
printf("\n");
p + i
还是&arr[i]
,它们每次所访问的地址都是一样的,这其实也就意味着指针变量p在偏移的过程中相当于在代替数组首元素地址向后偏移有了这些知识作为铺垫,我们就可以去尝试访问数组中的所有内容了
因为一维数组是一块连续的存储空间,所以我们只要得到这个数组的首元素地址。就可以通过p + i这样的方式找到它之后所有元素的地址,并且把他们地址进行解引用便能访问到数组中的所有元素
int main(void)
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = &arr[0];
for (int i = 0; i < 10; ++i)
{
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
p + i
,而当我们要去访问这个地址的内容时,直接对其进行解引用即可*(p + i)
,然后便可以看到数组中的十个元素都被打印出来了int* p = &arr[0]
便可以写成int* p = arr
,Ctrl + F5让代码走起来可以看到结果也是一样的arr
和p
也就是一回事,那也可以说【arr <==> p】,所以我们在使用到arr的地方可以换成p,使用到p的地方可以换成arrarr[i]
,那此时是不是可以将arr[i]
和*(arr + i)
做一个联系呢?当然是可以的[]
是一个数组访问的操作符,那既然是操作符的话就会有操作数,操作数是谁呢?就是【arr】和【i】,那此时当我将arr[i]转换成*(arr + i)的时候,()
里面的也就是这两个操作数,根据加法的交换律就可以将【arr】和【i】进行一个交换,那也就变成了*(i + arr)
。*(arr +i)
可以写成arr[i]
<—— ⭐*(i + arr)
是否可以写成i[arr]
呢 <——⭐此时我们通过代码来尝试一下,将推测转化为实际
arr
和p
其实是一回事,那可以写【arr[i]】,是不是也可以写成【p[i]】呢?答案是:当然可以!看完上面的这些,相信你已经晕了(((φ(◎ロ◎;)φ))),不过没有关系,将知识点做个总结就可以很清晰了
arr[i] == *(arr + i) == *(p + i) == p[i]
int main(void)
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr[i]);
}
printf("\n\n\n");
for (int i = 0; i < 10; ++i)
{
printf("%d ", *(arr + i));
}
printf("\n\n\n");
for (int i = 0; i < 10; ++i)
{
printf("%d ", *(p + i));
}
printf("\n\n\n");
for (int i = 0; i < 10; ++i)
{
printf("%d ", p[i]);
}
printf("\n\n\n");
return 0;
}
来总结一下本文所讲述的内容
边界值的问题
,一类则是数组容量不够导致
,它们都会导致数组在使用的过程中出现错误arr[i] == *(arr + i) == *(p + i) == p[i]
,是否对数组和指针之间的关系有了进一步的了解呢