系列文章目录
速通C语言系列
速通C语言第一站 一篇博客带你初识C语言 http://t.csdn.cn/K43IN
速通C语言第二站 一篇博客带你搞定分支循环 http://t.csdn.cn/O9ISr
速通C语言第三站 一篇博客带你搞定函数 http://t.csdn.cn/vkcsU
你是故意点开的还是不小心点开的?
无论是故意的还是不小心的,感谢佬们阅读支持!
文章目录
前言
上篇博客我们细致的学习了函数,在这篇博客中,我们将学习为后面的通讯录、顺序表、栈奠定基础的数组。下面,我们就开始吧!
一、一维数组
1 数组的定义
在初识C语言中,我们知道了数组的定义是一组相同元素的集合。
2 数组的结构
初识C语言中,我们只是简单初始化了了一个int类型的数组,现在我来具体介绍下数组的结构。
例如:
//整形数组,8个元素
int arr[8];
//字符数组,5个元素
char ch[5];
另外,下标引用操作符中必须是常量表达式,不然会报错。
int n = 8;
int arr[n];
虽然上述操作报错了,啊但是!
注:在C99语法中,有变长数组的概念,即数组的下标引用操作符中可以是变量。
3 数组的初始化
在初始C语言中,我们只是简单初始化了一个数组,其实数组的初始化有三种方式。
下面我将为大家一一介绍。
三种方式
1 完全初始化
完全初始化 即把数组中的所有元素都初始化。
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
2
我们在初始化时可以不写元素个数,编译器会为我们自动统计。
int arr[] = { 0,1,2,3,4,5,6,7,8,9 };
3 不完全初始化
在初始化时,如果未将所有元素都初始化,其余元素将自动初始化为0。
int arr[10] = { 1,2,3,4,5 };
我们开启调试观察一下。
确实如此,arr数组的后5个元素都自动初始化成了0.
类似的,字符数组也有类似的操作,我们直接上例子
char ch1[5] = { 'a','b','c' };
char ch2[] = "bit";
等我们了解了数组与指针间的关系,我们还将解锁第四种初始化方式哦
4 数组元素个数的计算
数组的元素是可以通过计算得到的,即数组总大小除以第一个元素的大小。
int sz = sizeof(arr) / sizeof(arr[0]);
注:这步操作非常重要,我们后期会反复用到。
5 研究数组中的存储
#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;
}
由运行结果可知,地址每次都递增4,而int整形正好是4个字节,所以我们能推出数组在内存中是连续存储的,且随着下标的增加,地址也是由低到高增加的。
6 数组名和地址的关系
我们先来记一句很重要的话。
数组名是数组首元素的地址。
我们可以简单的证明一下
#include<stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}
由于数组是连续存储的,所以我们只要拿到了首元素的地址,再对指针进行加加,就可以打印整个数组的元素。
二、二维数组
1.二维数组的定义
关于二维数组的定义,我更愿意将其理解为矩阵。矩阵是一种按行和列排放数的数表。
例如,我们给到一个四行三列的矩阵。
下面,我们将用二维数组来表示它。
2.二维数组的结构和创建
相比于一维数组,二维数组用了两个下标引用操作符,第一个表示行,第二个表示列。
A是个三行四列的矩阵,所以用二维数组表示为
int arr[4][3];
3 二维数组的初始化
创建好了二维数组,我们还要对其进行初始化。
一:我们可以对其直接初始化。
1 将给到矩阵中的所有值都赋值给这个二维数组,这波是二维数组中的完全初始化。
int arr[4][3] = { 9,13,5,1,11,7,3,9,2,6,0,7 };
2 同理,我们也可以进行不完全初始化。
int arr[4][3] = { 9,13,5,1,11,7 };
其余的元素会自动初始化为0.
二:我们可以对其看作几个(几行->几个)一维数组进行初始化。
比如:我们给到了一个四行三列的二维数组,所以将其看作4个一维数组,每个一维数组分别有三个元素。
int arr[4][3] = { { 9,13,5 },{1,11,7},{3,9,2},{6,0,7} };
另外:与一维数组类似,二维数组的行数会通过初始化的情况自动计数,而列不行。
换言之,我们初始化时,可以省略行标,但必须写列标。
int arr[][3] = { { 9,13,5 },{1,11,7},{3,9,2},{6,0,7} };
4 二维数组的使用
与一维数组相同,二维数组同样靠下标来访问和使用。
arr[ 1 ] [ 2 ] 即为第一行第二列,即为13.
我们可以通过访问下标,将这个二维数组打印出来。
#include<stdio.h>
int main()
{
int arr[][3] = { { 9,13,5 },{1,11,7},{3,9,2},{6,0,7} };
int i = 0;
int j = 0;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d ", arr[i][j]);
}
//打印完一行,就换个行
printf("\n");
}
return 0;
}
与一维数组类似,行标、列标均从0开始。所以用for循环时,我们要注意区间的控制。
(成功打印)
5 二维数组的存储
为了研究二维数组的存储,我们将上个例子打印的东西换成地址。
即
printf("%p\n", &arr[i][j]);
由结果我们可以看到,无论是一行中的每个地址,还是上一行的最后一个地址和下一行第一个地址,都相差4,因为一个int是4,所以二维数组中的所有元素,均是连续存储的。
既然是连续存储的,我们只要拿到了第一个元素arr[ 0 ] [ 0 ]的地址,便可访问整个数组。
int main()
{
int arr[][3] = { { 9,13,5 },{1,11,7},{3,9,2},{6,0,7} };
//用*p取出arr数组首元素的地址
int* p = &arr[0][0];
int i = 0;
for (i = 0; i < 12; i++)
{
printf("%d ",*p);
//p++指针向后移动一个整型
p++;
}
return 0;
}
成功打印
对二维数组的补充理解
我们来补充一个对二维数组的高端理解。
在前面讲初始化时,有一种方式为可以讲二维数组分开为几个一维数组进行初始化。
其实,我们可以直接将二维数组看作几个一维数组理解。
将红框内的arr[0]看作数组名,后面的 [0] [1] [2]当成下标,这个二维数组的第一行,就成了一个一维数组。
由此,我们的那个二维数组就变成了四个一维数组。
这个理解在我们学习指针中将会用到哦。
6 一道面试题
近来还在某个模拟面试中听到这样一道题:
一个二维数组,以行遍历快还是以列遍历快?
显然是以行遍历快。
前面我们知道一维数组是连续存储的,而二维数组由很多一维数组构成
遍历连续的内存显然是快的
而按列遍历就会涉及到跳转,所以会相对较慢
三 数组作为函数参数
1 两种方式
第一种为形参数组式:如:void test(int arr [10])
第二种为指针的形式:如:void test(int *arr)
2 示例 冒泡排序
排序是后期数据结构中很重要的章节,这里我们先介绍其中很简单的一种,冒泡排序。
冒泡排序是比较排序的一种,即通过比较两个数来排序。
下面我将通过画图来为大家具体介绍它的具体逻辑(以升序为例)
我们将其实现为代码。
先在主函数给一个数组。再用我们上面学过的的操作求一下数组的元素个数。
#include<stdio.h>
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
}
写出函数,并传参
//数组传参,传的是数组首元素地址,再将元素个数传过去
bubble_sort(arr, sz);
开写冒泡排序函数
我们再对排好的数组进行打印
void bubble_sort(int *arr,int sz)
{
//通过元素个数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;
}
}
}
}
最后将排好的数组打印
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
非常奈斯
完整代码
(带注释)
#include<stdio.h>
void bubble_sort(int *arr,int sz)
{
//通过元素个数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[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//数组传参,传的是数组首元素地址,再将元素个数传过去
bubble_sort(&arr, sz);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
(不带注释)
#include<stdio.h>
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[10] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(&arr, sz);
for (int i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
}
四 关于数组和地址间关系的补充
上文我们提到了,数组名是数组首元素的地址,但是有两种特殊情况
1 sizeof(数组名) 数组名表示整个数组,所以算出的是整个数组的大小。
2 &数组名 数组名表示整个数组,故取出的是整个数组的地址。
啊但是,在第二种情况中,当我们分别打印&arr arr &arr[0] 的地址时
int main()
{
int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
printf("%p\n", &arr);
printf("%p\n", arr);
printf("%p\n", &arr[0]);
}
数组名等于数组首元素地址,所以2、3相等是合理的,为什么&arr也和它们相等呢?
因为整个数组的地址也是从数组的第一个元素开始的,所以相等。
纵使相等,但是意义是不同的。
我们可以进行一波验证
printf("%p\n", &arr);
printf("%p\n", &arr+1);
printf("%p\n", arr);
printf("%p\n", arr+1);
第一个由于指针+1跳过了整个数组,所以地址变化了40字节(需将16进制->10进制),也就是4*10字节,刚好为一个数组的大小。
而第二个仅仅变化了4,即一个整形。
另外,指针跳过几个字节,与指针类型有关。
总结
做总结,今天这篇博客带大家简单的剖析了数组的相关要点,后期的通讯录,顺序表,栈都会用到数组,所以我们必须要拿下数组哦。为了加深对数组的理解并提前体验写大文件的经验,下两篇主线博客将分别为大家带来三子棋和扫雷的实现。水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。