C语言初阶——5数组(保姆级胎教)


前言

作者写本篇文章旨在复习自己所学知识,并把我在这个过程中遇到的困难的将解决方法和心得分享给大家。由于作者本人还是一个刚入门的菜鸟,不可避免的会出现一些错误和观点片面的地方,非常感谢读者指正!希望大家能一同进步,成为大牛,拿到好offer。
本系列(初识C语言)只是对C语言的基础知识点过一遍,是为了能让读者和自己对C有一个初步了解。


日志

1.一维数组的创建和初始化

1.1数组的创建

  1. 数组是一组相同类型元素的集合
    当前要储存一个数,比如说1的时候,可以给上一个int a = 1;。再比如说2的时候,int b = 2;。要存上一个3.0的时候,float c = 3.0f;l。
    这个时候你会发现,如果只有一个数字要存储的时候,创建一个变量就可以了。但如果要存一组数据的时候呢?
    比如说,现在要存的是1,2,3,4,5,6,7,8,9,10十个数字。如果要把这10个数字存起来,10个整型,要连续创建10个整型变量,确实可以。但会显得很啰嗦。
    这10个数字是类型相同的一组数字,都是int类型。那能不能把它们放到一整块空间里去呢?
    创建一个比变量a是创建一块空间,里面存放1。
    那是不是说存这个10个数字,就开辟能存放10个数字的一整块空间,把这10个数字放进去就可以了?
    这个时候C语言就有了一个概念——数组
    数组-可以存放一组数
    C语言如果想把1~10这样的一组数,一次性把它们存起来,这个时候就该给一个数组。此前写的数组都是一维数组。
  2. 数组的创建方式
    在这里插入图片描述

比如说,现在要把1~10这组数存起来,该如何创建?
要放1~10,每个元素都是int类型的,所以数组的类型应该是int。
int
名字就给个arr吧(array数组的缩写),名字只要不和关键字相同,随便起(往后这种要起名的,基本上都是和变量命名规则一样)。
int arr
arr后面放一个方块,方块里面还得有一个常量表达式,用来指定数组的大小。其实里面放的是数组元素的个数,但放了元素个数也就相当于指定了元素的大小
这个数组要放10个值,也就是10个元素,所以方块里面放个10就可以了
int arr[10];
在这里插入图片描述

创建一个整型数组,存放10个元素就是这样子。那创建一个数组,存放字符呢?
存放字符,那么类型就是char。这里名字我取ch,你可以随便取。想放5个元素,方块里面给上5
在这里插入图片描述

  1. 数组创建的实例:
//代码1
int arr1[10];
//创建一个名为arr1的数组,能存放10个元素,元素的类型是int

//代码2
int count = 10;
int arr2[count];//数组可以正常创建吗?
//前面讲这个地方要放const_n,是一个常量表达式
//变量count行不行?测试一下

在这里插入图片描述
这种方式完全可以,没有报错。因为10就是一个常量
在这里插入图片描述
报错。提示应输入常量表达式,count是变量。说明创建数组的时候,方块内应输入常量表达式才可以。
当然在C99之后引入了变长数组的概念,数组的大小可以是变量,但这种数组不能初始化。
在这里插入图片描述

只要编译器支持变长数组,就可以使用变量来指定大小。这VS code配置的gcc编译器就支持变长数组的,但我没有配置,不便演示。

//假设现在是在gcc下
#include <stdio.h>
int main()
{
	int n = 10;
	int arr[n];//此时是可以的
	return 0;
}

此时如果把它的值打印出来看看,会发现是随机值

//gcc下
#include <stdio.h>
int main()
{
	int n = 10;
	int arr[n];
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

还是没有贴图,但值都肯定是随机的,知道就行。这是因为这些数组是局部的变量,局部变量不初始化,默认为为函数开辟栈帧空间时候设置的0xCCCCCCCC。

//gcc下
#include <stdio.h>
int main()
{
	int n = 10;
	int arr[n];//局部的变量,这些局部变量或数组是存放在栈区的
	//存放在栈区的数组,如果不初始化,默认是随机的值ccccccccc
	//cccccccc的意思在我的函数栈帧的创建和销毁那篇博客有讲
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d\n", arr[i]);//这里是数组的使用,并不是创建,所以能用变量
	}
	return 0;
}

注意:数组在创建的时候,[]中要给一个常量才可以,不能使用变量。但是数组在使用的时候,是可以使用变量的。

1.2数组的初始化

  1. 完全初始化
    数组的初始化是指,在创建数组的同时给数组的内容一些合理的初始值(初始化)。
    int arr[10];数组这样去写,里面是随机值。我们可以设置它的初始值
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    这就叫做数组的初始化(并且是完全初始化,把所有的值都初始化了)
    在这里插入图片描述
    打开监视看到,值都放进去了。

  2. 不完全初始化
    上面的数组是把它全部的值初始化好了,那我们只初始化一部分看看。
    在这里插入图片描述
    arr2这里我只初始化了3个值,并没有完全把所有的元素初始化,叫做不完成初始化。不完全初始化的情况下,没有被初始化的剩余元素默认都是0。
    因此当你想给一个数组全为0的时候,就可以这么写
    在这里插入图片描述

  3. 省略数组的大小
    在这里插入图片描述
    arr3和arr4看着很像,但arr3里有10个元素,arr4呢?从监视里我们可以看到,当数组不指定大小的时候,会根据初始化内容(即初始化的个数)自动计算数组的大小。arr4里只放了一个0,所以数组的大小其实是1。

  4. 省略数组大小,数组必须初始化
    在这里插入图片描述

  5. 数组省略初始化要根据实际情况来看,否则可能出错。
    比如arr只有3个元素,如果非要去访问第4个元素,就会发生数组越界(就在后面)。
    在这里插入图片描述

  6. char类型数组的初始化
    刚刚举的例子都是int类型,而char类型有一点特殊,其他类型基本一样。
    在这里插入图片描述

这里还有小小的变化。字符b的ASCII码值是98,拿放98是不是也是一样的?

在这里插入图片描述

这个时候发现和放’b’是一样的。所以虽然放的是98,但相当于放的是b。

1.3一维数组的使用

对于数组的使用我们之前就介绍了一个操作符:[],下标引用操作符,它其实就是数组访问的操作符。
数组是有默认的下标的,从0开始,以1向后递增。所以可以通过下标引用操作符,去访问对应的元素
在这里插入图片描述
[]打印1~10。那访问的范围是多少呢?
在这里插入图片描述
从0开始访问,访问到下标是元素个数sz-1。arr里有多少个元素个数?可以通过sizeof计算
在这里插入图片描述
所以在这里,我们就要从下标0开始访问,访问到下标9

#include <stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//10个元素,下标0-9
	for (i = 0; i < sz; i++)//访问到9,小于10即可
	{
		printf("%d\n", arr[i]);
	}
	return 0;
}

在这里插入图片描述

这个里就是刚刚说的数组的使用(访问)是可以使用变量的
在这里插入图片描述
当然你可以按照自己的想法改变它的遍历。比如跳着遍历,调整部分i+=2就可以了。
在这里插入图片描述

还可以倒着打印
在这里插入图片描述

这就是一维数组的使用。可以访问它里面的值,也可以给数组输入一些值。
在这里插入图片描述
总结

  1. 数组的下标是从0开始的
    这是C语言语法规定死的
  2. 数组的大小可以通过计算得到
    在这里插入图片描述
    40是因为,每个元素都是int类型,占4个字节。而数组总共有10个元素,因此是40。也可以打印单个元素大小看一下
    在这里插入图片描述
    所以个数sz就等于总大小除以一个元素大小
    在这里插入图片描述

1.4一维数组在内存中的存储

接下来探讨数组在内存中的存储
要看数组在内存中的存储,那就看它内存中的布局。而只要把每个元素的地址打印出来,就能探究清楚它的布局。

#include <stdio.h>
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]);
	}
	//%p是以16进制的形式打印地址
	return 0;
}

运行之后,下标是连着走的。每两个地址相差4
在这里插入图片描述

为什么相差4?是因为每一个元素占4个字节。
之前在指针那里讲过,一个内存单元是1个字节。
在这里插入图片描述
每个内存单元都有一个地址,即每个字节都给一个地址,假设这个字节的地址就是0093FD74
在这里插入图片描述
因为每个内存单元都有一个地址,下一个地址就应该加1,所以下个字节的地址就是
在这里插入图片描述
因此往后的地址就是这样
在这里插入图片描述

而一个int类型占4个字节,而arr[0]占四个字节,并且从0093FD74开始,也就是这块大空间。
在这里插入图片描述

0x0093FD74就是这个四个字节的起始地址
在这里插入图片描述

下来就是arr[1]的所占空间和起始地址
在这里插入图片描述

arr[1]的起始地址是0x0093FD77,占四个字节,那么arr[2]的起始地址是不是就是0x0093FD7C?
在这里插入图片描述
一看果真如此。一个整型就占4个字节,下一个整型刚好就是加了4个字节。说明它们在内存中时连续存放的。
从结果来看,一维数组在内存中是连续存放的。正因为是连续存放,知道起始位置之后,顺藤摸瓜就能找到后面所有元素。
由此可以得出结论:数组在内存中是连续存放的,同时随着下标的增长地址是由低到高变化的
小的地址叫低地址,大的地址叫高地址
在这里插入图片描述
这里可能和前面的栈区开辟空间是从高地址到低地址的,被搞混。其实它们的规则并不冲突。
这是栈区开辟空间的方式。相当于一个水桶里,想要加add多的水,只能从桶口加。这是一整个栈区,每压栈进去一个空间的规律。
在这里插入图片描述
而数组下标增长的低地址到高地址,是数组这个在main函数里的一个对象内部的规律。
在这里插入图片描述

2.二维数组的创建和使用

二维数组和一维数组非常相似。一维数组可以放一组数,比如说把1~10放到一块空间里,这种其实就是一维数组。
二维数组可以存多组数据。比如说要存这几组数
在这里插入图片描述
原来说的是每行给一个一维数组
在这里插入图片描述
所以就干脆把一行一行再组合起来,就形成了二维数组。
在这里插入图片描述

2.1二维数组的创建

//数组创建
int arr1[3][4];
char arr2[3][5];
double arr3[2][4];

在这里插入图片描述
像刚刚那组数就是4行5列
在这里插入图片描述
接下来创建一个二维数组存放这几组数,数组名随便起

int main()
{
	int arr[4][5];//当然此时没有初始化,里面放的是随机值
	return 0;
}

也可以创建一个字符数组

int main()
{
	int arr[4][5];//当然此时没有初始化,里面放的是随机值
	char ch[3][8];//创建3行8列的空间,用来存放字符
	return 0;
}

其他类型也是如此

2.2二维数组的初始化

  1. 第一种方式
//数组初始化
int arr1[3][4] = { 1,2,3 4 };
int arr2[3][4] = {{1,2},{4,5}};
int arr3[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不可以省略

在创建的时候给值。而二维数组也是数组,可以存多个元素,所以也给大括号
在这里插入图片描述
我们来放这个二维数组的第一个元素。一看发现它的第一个元素是一维数组啊,一维数组又要给一个大括号。
在这里插入图片描述

一维数组初始化也是给大括号,所以你在初始化第一行的时候,也可以给大括号。
在这里插入图片描述
这样的话就搞定了第一行。然后逗号,再给大括号就是第二行
在这里插入图片描述
后面第3和第4行都是如此
在这里插入图片描述
一维数组存放多个变量需要大括号。而一维数组作为二维数组的元素,有多个一维数组,所有外面还需要一个大括号,一维数组们整体括起来。这就是二维数组的初始化,调试看看。
在这里插入图片描述
实际上还有其他初始化的方式。

  1. 第二种方式
    给第一行放12345直接放
    在这里插入图片描述
    第二行放23456,也是逗号直接放
    在这里插入图片描述
    第三行,第四行,第五行都是直接放。
    在这里插入图片描述
    这种形式也可以的
    在这里插入图片描述
    arr2和arr完全一样。而且并没有像arr那样把每行的值括起来。这种初始化的方式是一行放满之后,再放下一行。反正都是5个元素,放满5个就下一行。也可以删掉几个看看
    在这里插入图片描述
    这叫不完全初始化。当然如果你只想让第一行放123,可以把第一行的内容括起来
    在这里插入图片描述

括上一部分的时候,如果不够自动补0

在这里插入图片描述
这就是第二种初始化的方式。不过建议还是把各自的行括起来。避免出错的时候,还能看清楚些。

  1. 二维数组初始化后可以省略行,但不能省略列
    有人会想,初始化了之后,能不能把行和列省略掉?就像一维数组,初始化后数组大小可以省略掉。
    在这里插入图片描述
    当这样操作的时候,直接报错了
    在这里插入图片描述
    行可以省略是因为编译器可以根据大括号对数知道了行数。但列不能省略。下面一点再介绍不能省略行的原因。

2.3二维数组的使用

二维数组的使用也是通过下标的方式。
一维数组通过下标就能定位到元素
在这里插入图片描述
二维数组也是如此。只不过它是通过行下标和列下标来锁定元素。
在这里插入图片描述
所以只要交代了第几行第几列,就能锁定到该元素。

#include <stdio.h>
int main()
{
	int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };
	printf("%d\n", arr[2][3]);
	return 0;
}

在这里插入图片描述
这个地方只是定位一个元素。把所有元素打印出来也可以,但这个时候就需要遍历了。当找到第1行的时候,遍历它的列数。当找到第2行的时候,遍历它的列数。第3行,第4行也一样。
所以这需要两层for循坏。先用i遍历行,再用j来遍历列。

#include <stdio.h>
int main()
{
	int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };
	//printf("%d\n", arr[2][3]);
	int i = 0;
	//行数
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		//列数
		for (j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");//打印完一行后换行
	}
	return 0;
}

在这里插入图片描述

2.4二维数组在内存中的存储

  1. 像一维数组一样,打印它的地址来查看内存的分布。
#include <stdio.h>
int main()
{
	int arr[4][5] = { 0 };//这里为了简单一点改成0
	//printf("%d\n", arr[2][3]);
	int i = 0;
	//行数
	for (i = 0; i < 4; i++)
	{
		int j = 0;
		//列数
		for (j = 0; j < 5; j++)
		{
			printf("&arr[%d][%d]=%p\n", i, j, &arr[i][j]);
		}
	}
	return 0;
}

在这里插入图片描述
再来看看一行内部的地址。
在这里插入图片描述
一行内部,它们是整型,而两个元素之间又相差4个字节。说明一行内部之间每个元素之间是连续的。再看看跨行的
在这里插入图片描述
跨行的时候相差的也是4。也就是说每相邻的两个元素之间相差4,跨行也是差4。即每相邻的两个元素之间都差4。说明二维数组在内存中也是连续存放的。
假想中,二维数组是这种多行多列的场景
在这里插入图片描述
这样很好理解。事实上二维数组不是这样放的。
在这里插入图片描述
因为连续存放,元素类型是int。一个元素占4个字节,所以两个相邻元素之间地址相差4个字节。
那只要知道第0行、第0列的元素,顺藤摸瓜就能找到后面所有地址。因为它们在内存中是连续存放的。
说到这里,就可以讲明白之前的那个问题。二维数组为什么可以省略行,不能省略列。
就因为二维数组在内存中是连续存放的,只有知道一行有几个元素的时候,下一行的位置才能确定。如果列都省略了,即使有大括号表示行数,但是一个大括号里放的元素够不够列数?编译器也不知道的啊,也就不知道要不要补齐了。就这样找不到下一行的位置,或者不知道要补多少个元素。
在这里插入图片描述
所以行可以省略,但是列不能。

  1. 接下来可以这么理解。把二维数组的第1,2,3,4行想象成一维数组
    在这里插入图片描述
    这个时候,可以把这个一维数组想象成一个数组元素
    在这里插入图片描述
    所以二维数组可以理解为一维数组的数组。即放了多个一维数组的数组。
    在这里插入图片描述
    当理解这点的时候。再理解第2个点。
    整个二维数组的名字叫arr。访问第0行所有元素的时候
    在这里插入图片描述
    访问1,2,3行的时候是这样的
    在这里插入图片描述
    你会发现,访问第0行的时候前面都是arr[0]。访问第1行的时候都是arr[1],第2行arr[2],第3行arr[3]。
    在这里插入图片描述

有了这个前提之后,你在访问二维数组的每一行的时候可以这么理解
在这里插入图片描述
而在访问一维数组的时候,数组名加上下标j即可以访问到下标为j的元素
在这里插入图片描述
那把第0行想象成一维数组,再访问这个数组的时候
在这里插入图片描述
所以第0行是个一维数组,那arr[0]就是它的数组名。后面的1,2,3行也一样。
在这里插入图片描述
这是一些二维数组的理解

3.数组越界

数组的下标是有范围限制的,比如说10个元素
在这里插入图片描述
数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
所以数组的下标小于0,或者大于n-1,就造成了数组越界访问,超出了数组合法空间的访问。
以下是一个正常的打印通过数组来打印1-10的代码

#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 ", arr[i]);
	}
	return 0;
}

在这里插入图片描述

但如果有人在判断部分不小心写了一个i <= 10,访问的下标范围就从0-9,变成0-10。索引"10"超出了"0"至"9"的范围。
在这里插入图片描述

运行之后,结果也能出来,没有报错
在这里插入图片描述
程序没有报错,只是结果不是我们想象中的。打印了1~10之后,还打印了一个随机值。这个时候就形成了越界访问。
但为什么编译器没有报错?
C语言本身是不做数组下标的越界检查的,编译器也不一定报错,但是编译器不报错,并不一定意味着程序是正确的。
所以程序员写代码时,最好自己做越界的检查。
当然二维数组的行和列也可能存在越界。因为行和列也有自己的范围。

4.数组作为函数参数

往往我们在创建数组的时候,不仅仅是像刚刚那样自己直接就用了。更多的是传递给别人让别人去用。

4.1冒泡排序函数的错误设计

比如:实现一个冒泡排序函数,将一个整型数组排序
要求是给出一组数,对这组数进行排序。
这里不管其他,先给上一个数组。不知道存什么,就放0。在用循环的方式输入就好了

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序
	return 0;
}

输入之后就要进行排序。而这里排的是arr,排其他的呢?如果想把这个排序的动作复用,这个时候需要实现排序的算法,写一个排序的函数。函数名就叫bubble_sort()。让这个函数来完成数组arr中数据的排序,并且排成升序。

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr);
	return 0;
}

这个冒泡排序,排的是数组arr,就把arr数组传过去。这个地方就涉及了数组传参。数组为参数,传递给函数。而数组传参的时候,数组名就可以了。不需要其他。写arr[10]的话,是把下标为10的元素传过去。
arr传给bubble_sort()之后,要进行打印。

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

因为只要排序就行了,不需要返回,所以void。传数组,函数就要用数组来接收

#include <stdio.h>

void bubble_sort(int arr[10])
{
	
}
int main()
{
	int arr[10] = { 0 };
	//输入
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort();
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这个地方,我们要实现冒泡排序的函数,在没有讲过冒泡排序之前,有点不方便。先不写函数了,就排arr就行。先把冒泡排序写出来,在封装成函数,就很容易写了

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

先讲一些冒泡排序的排序方式。它的核心思想是两两相邻的元素进行比较
假设排这组数
在这里插入图片描述
让它相邻的两个元素比较。
9和8比较,9比8大,不满足升序,交换它们的位置
在这里插入图片描述
之后
在这里插入图片描述
就这样相邻两个元素持续比下去,不满足交换。最后所有相邻两个元素处理完之后,变成8 7 6 5 4 3 2 1 0 9。最终会让9来到应该出现的地方
在这里插入图片描述
而刚刚这一堆的操作叫做一趟冒泡排序。一趟冒泡排序让一个值来到最终一个出现的位置上。

在这里插入图片描述
当进行第一趟冒泡排序的时候,9再也不要动了,它已经来到了最终应该出现的位置。
在这里插入图片描述
所以第二趟冒泡排序的内容就是8 7 6 5 4 3 2 1 0
在这里插入图片描述
排完之后就是这样子
在这里插入图片描述
对前面8 7 6 5 4 3 2 1 0排序,又叫一趟冒泡排序。这次又解决了一个问题8。依次类推下去,一趟搞定一个,一组10个数,排成升序只需要9趟冒泡排序。第9趟的时候,每个元素都在最终出现的位置。n个元素进行n-1趟。而一趟内部,两两相邻比较,如果不满足升序,就交换。
这就是冒泡排序。有两个重要的事情
在这里插入图片描述
回到前面,有sz个元素,也就有sz-1趟。这就确定了趟数

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	for (i = 0; i < sz - 1; i++)//趟数
	{
		
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

而一趟内部是两个相邻元素进行比较。
假设j从0开始,此时arr[j]为9,arr[j+1]为8。
在这里插入图片描述
发现9>8,所以交换。再让j++。
此时arr[j]为9,arr[j+1]为7
在这里插入图片描述
发现9>7,所以交换,再让j++。因此这是一个循环

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	for (i = 0; i < sz - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j; j++)//判断部分先不管
		{

		}
	}
	for (i = 0; i < sz; i++)//此时判断还不知道
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

当i=0的时候,进行第一趟冒泡排序。

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	for (i = 0; i < sz - 1; i++)//趟数
	{
		int j = 0;
		for (j = 0; j; j++)
		{
			if (arr[j] > arr[j + 1])//不满足顺序,交换
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

那进行多少对比较呢?
在这里插入图片描述
如果判断部分写成j < sz -1;就固定是9次比较了,那就写死了。而第一趟是9,第二趟是8,第三趟是7了。即i为0的时候,9对比较。i为1的时候8对比较,i为2的时候7对比较……也就是说趟数i加1,比较对数j减1
那判断部分写成j < sz - 1 - i;就可以了。随着趟数的增加,比较的对数减少。

#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++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	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 (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这样冒泡排序就成功了
在这里插入图片描述
当然这仅仅是一个独立的代码,没有进行封装。在排其他数的时候,有要拷贝一份,不方便。
所以要把它封装成一个函数。回到之前的代码

#include <stdio.h>

void bubble_sort(int arr[10])
{
	
}
int main()
{
	int arr[10] = { 0 };
	//输入
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

而冒泡排序的实现就可以拿进来了

#include <stdio.h>

void bubble_sort(int arr[10])
{
	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] = { 0 };
	//输入
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

这个时候sz和i在bubble_sort这个函数里相当于没有定义了,要重新算。

#include <stdio.h>

void bubble_sort(int arr[10])
{
	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 -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] = { 0 };
	//输入
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

在这里插入图片描述
并没有排序。说明哪里出问题了。调试去找问题。
在这里插入图片描述
这里输入9~0。数据确实输入进去了,也就是说没排,说明是函数出了问题。f11进入函数内部。
在这里插入图片描述

在数组传参之后,光给个数组个数组名是观察不了全部元素的。
接下来看一下sz的值
在这里插入图片描述

sz是1,不应该是10吗?怎么会是1?
在这里插入图片描述

循环没进去,压根没排序。这就是为什么打印出来的结果没有排序。
那么问题来了,为什么sz算的是1?
数组在传参的时候,按照刚刚那样写,肯定是有bug的。
arr作为数组进行了传参,数组名传参传递的是地址,那么形参本质就是指针。指针变量是用来接收地址的嘛。所以形参实际上就是一个指针变量。
在这里插入图片描述

在这里插入图片描述

所以sz是1。所以数组传参的时候,虽然形参这部分放的是数组名,但数组传参本质上传递的是地址。而且是首元素的地址。
而这个时候肯定有人会想,你这是不是有矛盾
在这里插入图片描述
又是首元素地址,又是整个数组。到底是哪个?
这个时候就要讲一些关于数组名的理解

4.2数组名是什么?

先给大家一个结论
数组名通常情况下就是数组首元素的地址。可以通过验证一下
在这里插入图片描述
所以说数组名就是首元素地址。但为什么arr的大小又是整个数组?
在这里插入图片描述
数组名如果都是首元素地址的话,那它就是个指针。指针大小要么4要么8,为什么是40?这里就解释不清楚了。因为这个地方不是首元素地址。
所以说数组名通常就是数组首元素地址,但有两个例外。
1.sizeof(数组名) 数组名单独放在sizeof内部,这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名也表示整个数组,这里取出的是整个数组的地址。
除此之外,所有的属组名都表示首元素地址。
所以40是一种特殊情况。第2个例外可以这么理解。&数组名是取出整个数组的地址,那打印出来看看
在这里插入图片描述
为什么三个相同?因为数组的地址也是从起始地址开始的,因为它们都从首元素开始,所以值上是一样的
在这里插入图片描述
所以三个打印结果相同。但它们还是有区别的。给它们各自加1
在这里插入图片描述
对于arr和&arr[0]来说,它们都是首元素地址,这两个是完全一致的东西。加1是指加了1个整型,跳过了4个字节
在这里插入图片描述
但对于整个数组的地址&arr可不一样。加之前都是B4,加1之后就不一样了,变成了DC。可以算一下加了几
在这里插入图片描述
所以除了这两种情况,在其他地方遇到的任何数组名都是首元素地址。
了解了这点上面的错误就很好理解了
在这里插入图片描述
所以传过去的是首元素地址,传过去之后就应该是指针,int arr[10]本质上就是指针。既然是指针,那大小就应该是4。
所以你不可能在函数里求到数组个数。因此以后设计函数时,要是函数内部要使用到个数,都需要在外面就算好sz,再把sz传进去
在这里插入图片描述
元素个数在外面求好了,求完传进去就可以了。
在这里插入图片描述
这个时候一运行
在这里插入图片描述
代码就完成了。这才是数组传参的正确方式。
一般来说如果函数内部需要用到元素个数,一定要把元素个数传进去。
这个时候把形参改为指针,也能运行
在这里插入图片描述
只把形参改成了指针,没有对其他做任何修改,也能运行。说明数组传递的就是首元素的地址,也就是指针。而改成指针,没有任何毛病。

4.3冒泡排序函数的正确设计

通过上述,冒泡排序函就真的设计了出了

#include <stdio.h>

void bubble_sort(int* arr, int sz)//本质上是个指针变量
{
	//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 - 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] = { 0 };
	//输入
	int i = 0;
	//arr单独放在sizeof里,是整个数组 没问题
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);//arr[i]是一个变量,是一个元素  需要&
	}
	//排序-升序
	bubble_sort(arr, sz);//这不是那两种特殊情况 第1没有把数组名放到sizeof内部,第2没有&
	//所以这个arr是数组首元素的地址
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

但这个程序它还是很繁琐的
在这里插入图片描述
所以我们会发现,如果数组已经有序了,那就不要再进行冒泡排序了。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
而就像这样的场景,即使有序了,程序还在工作。这是没有必要的。可以这样优化
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这个时候加上一个if语句
在这里插入图片描述
在这里插入图片描述
这样的话,接近有序的数组就得到了优化

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值