速通C语言第四站 一篇博客带你学会数组

系列文章目录

速通C语言系列

 速通C语言第一站 一篇博客带你初识C语言        http://t.csdn.cn/K43IN

 速通C语言第二站 一篇博客带你搞定分支循环   http://t.csdn.cn/O9ISr

 速通C语言第三站  一篇博客带你搞定函数         http://t.csdn.cn/vkcsU


 你是故意点开的还是不小心点开的?

无论是故意的还是不小心的,感谢佬们阅读支持!

文章目录

  • 系列文章目录
  • 前言
  • 一、一维数组
  •        1 数组的定义
  •        2 数组的结构
  •        3 数组的初始化
  •               三种方式
  •        4 数组元素个数的计算
  •        5 研究数组中的存储
  •        6 数组名和地址的关系
  • 二、二维数组
    • 1.二维数组的定义
    • 2.二维数组的结构和创建
    • 3 二维数组的初始化
    • 4 二维数组的使用
    • 5 二维数组的存储
    •       对二维数组的补充理解
    • 6 一道面试题
  • 三 数组作为函数参数
  •         1 两种方式
  •         2 示例  冒泡排序
  •                完整代码
  • 四  关于数组和地址间关系的补充
  • 总结


前言

 上篇博客我们细致的学习了函数,在这篇博客中,我们将学习为后面的通讯录、顺序表、栈奠定基础的数组。下面,我们就开始吧!


一、一维数组

  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 };

我们在初始化时可以不写元素个数,编译器会为我们自动统计。

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哦。

 每日gitee侠:今天你交gitee了嘛

  • 10
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值