嘉明的C学习之Day8--数组

知识点回顾
整形 int 大小:4个字节
浮点型 float 大小:4个字节
字符型 char 大小:1个字节

数组

数组是什么?
举个例子:
小明是一个球鞋爱好者且是一名编程爱好者。为了收藏鞋子,他买了很多个独立的小鞋箱子放置他心爱的球鞋。
他把鞋箱放成一排(一维数组),其中一排有十个鞋箱(数组表示为a[10])。
有一天小明叫妈妈帮他拿鞋子,妈妈问:“你那么多鞋,我怎么知道拿那一对?”
小明说:“从左到右的第三个箱子的鞋子。”(a[2])
于是妈妈便很快找到了小明的鞋子。

随着时间的流逝,小明的鞋子越来越多鞋箱也越来越多。因为宽度不够,于是小明选择向上叠加叠到了三行,且每一列的排数箱子数量都是10个(二维数组)(a[3][10])。
又有一天小明叫妈妈帮他拿鞋子,妈妈问:“你的鞋子堆的到处都是,我怎么知道拿那一对?”
小明说:“拿a[1][4](第二行第五列)那对吧。”
妈妈顿了下问:“啥?”于是拿了第一行第四列的鞋子给小明
小明说:“不是这对啊,是a[1][4]啊,第二行第五列啊。”
妈妈说:“那不是a[2][5]吗,什么a[1][4]?”
小明说:“可是编程里面是从0开始的呀。”
妈妈看了看满屋的鞋子,愤怒的举起拿起扫把:“啊?什么编程?老娘的规矩就是规矩!买那么多鞋子浪费多少💴,打扫卫生时天天堵在那里看到都烦!”
小明妈妈拿起了扫把展示失传的少林棍法。小明就这样度过了快乐的一天

尬!

看了上面的例子是否感到尴尬的同时,也理解了数组是什么呢?
数组就是为了用一个符号来访问多个元素

一维数组

一维数组就是刚刚例子中的一排鞋盒
在这里插入图片描述
例如
int a[5],表示定义了有5个元素的一维数组
如下:

#include<stdio.h>
int main() {
	int a[5]={ 1,2,3,4,8 };
}

在这里插入图片描述
可以看到我们查看监视的时候只有a[0]到a[4]。这就是为什么上面的故事要这样编(哈哈哈哈就是皮)

内存问题
在这里插入图片描述
监视里还有个数组a的sizeof(字节大小)是20因为一个int为4 有5个.所以4*5=20。
其实在数组定义下来之后,大小就已经确认了
如果定义了数组可以存储5个,只赋值两个会怎么样,如下图一样
在这里插入图片描述
其他没赋值的都是0,而字节大小还是20。

常见的错误:
在这里插入图片描述

数组访问越界错误

在这里插入图片描述

比如我们定义了一个一维数组a[5],如果我们为a[5]或者a[6]赋值是会提示如上错误的。因为我们已经越界了

如下程序:

#include<stdio.h>
int main() {
	int i = 5;
	int j = 10;
	int a[5]={ 1,2 ,3,4,5};
	a[5] = 10;
}

在这里插入图片描述
前提摘要:
我们微软在编译时不同的变量是有8个字节的保护间隔的,注意只是在微软系统上。

在这里插入图片描述
在这里插入图片描述
例如上面这两张图,我一开始定义 i 和 j 就是为了说明这一点。可以看到他们地址之间间隔了8。

再来回到正题
我们调试的时候看到了内存里突然多出一块红色的列。这个值正好就是10。这个就是越界的值,但是因为保护,它还a[4]地址的4个字节之后显示。在这里插入图片描述
那么访问越界会导致什么问题呢?
在我们正常使用中,访问越界有些是不会提示的。如果它正常执行就会导致破坏其他变量的数据。

#include<stdio.h>
int main() {
	int i = 5;
	int j = 10;
	int a[5]={ 1,2 ,3,4,5};
	a[5] = 10;
	a[7] = 22;
	printf("%d", j);
}

我们现在加一个 a[7] =20,很明显这访问越界了。但是我们继续执行它
在这之前我们先记住j的值是20

输出结果
在这里插入图片描述
但是我们最后所得到的值是22?这是为什么呢?
我们回看一下内存,如下图
在这里插入图片描述
可以看到j的值已经变成22了。
这是因为j本来就在数组a的8个字节之后(因为定义的是5个元素,根据微软的编译规则有8个字节的保护区间,所以就是在a[4]地址后的8个字节)而a[7]就刚好在a[4]的后8个字节与变量j的地址重合,进而就会取代j的值。这就是越界的危害
其实在定义a[5]的时候已经访问越界了,访问量不属于自己的空间,但是在保护区间内。一旦超过了保护区间就会对数据造成破坏。

题外话:
在语言层面,例如java、python这种语言一般是不会出现越界的情况的,因为没有指针。而C难就难在指针,而且因为C中的内存是由我们自己来控制,所以使用C十分的高效和精确。但是问题就是对新手十分不友好,容易出BUG。但是对于老手来说C和C++无疑是最高效的。语言没有好与坏,只有符不符合自己,我也用过java做过一些轻量级开的项目发觉得很方便、也学过一点python觉得语法很简单库的功能很强大,但是C是最接近底层的这就是为什么考研大部分都C来考这样学习下来可以让我们明白很多原理的东西,例如计组、操作系统等等。总之各有各的好处,学语言一定要符合自己才是王道!

数组传递问题

举个例子,如下程序

#include<stdio.h>
void print(int a[]) {
	for (int i = 0; sizeof(a) / sizeof(a) > i; i++) {
		printf("a[%d]=%d\n", i, a[i]);
	}
}
int main() {
	int a[5] = { 1,2 ,3,4,5 };
	print(a);
}

我们定义一个打印的方法,其目的就是打印数组a所有的元素,比如运行之后输出a[0] =1 a[1]=2这样的结果。

那么有的同学就会想这个简单不直接用sizeof(a)算出数组的总字节数除以sizeof(int)int类型的字节大小就可以算出总数组的长度了吗?而且还灵活多变

那么我们运行一下看看结果是不是这样的。
在这里插入图片描述
结果好像有点跟我们想的不一样,没有输出5各元素的值只输出了第一个a[0]的值。
那为什么会这样呢?这也是我们接下来要讲的重点。
老规矩,在C中遇到与我们预期不符的事情,第一件事情就是debug看监视和内存
在这里插入图片描述
可以看到在print方法中的sizeof(a)是4。并不是原来的20,这是为什么呢?
所以下面我们要记住这一句话!
数组在传递的时候,传递的只是起始地址,元素个数是传递不过去的
根据这句话我们可以分析出,这里print方法里的数组变量a其实是由主方法main中数组a传过来的初始地址值,后续在方法中访问也是根据地址访问数组中的元素的。元素的个数并未传过去,因此它的sizeof只是一个整形的大小(4个字节)。
就好比小明给小红很多💴,但只小明没有直接给小红💴而是给了小红一张支票,因此小红手里也只有一张支票,需要去银行取钱。

那要怎么才能让print方法中实现我们预期的功能呢?
其实很简单,我们只需要在print方法中再添加一个参数表示数组从长度(元素总数)就可以了。

#include<stdio.h>
void print(int a[],int len) {
	for (int i = 0; len > i; i++) {
		printf("a[%d]=%d\n", i, a[i]);
	}
}
int main() {
	int a[5] = { 1,2 ,3,4,5 };
	print(a,5);
}

在这里插入图片描述
那么我们引申一下问题,在print方法中是否可以改变主方法main中数组a的某个元素的值呢?

#include<stdio.h>
void print(int b[],int len) {
	for (int i = 0; len > i; i++) {
		printf("a[%d]=%d\n", i, b[i]);
	}
	b[4] = 20;
}
int main() {
	int a[5] = { 1,2 ,3,4,5 };
	print(a,5);
	printf("\na[4]=%d", a[4]);
}

注意:方法中的参数名称给主方法main中变量名不需要一致,因为只是传递参数只要类型一样即可
比如上述代码中print方法中的b[4]=20可以改变a[4]的值吗?
在这里插入图片描述
从输出结果看来,是可以的。
因为主方法main中的a传过去的是初始地址,print中根据地址肯定可以找出a[4]因此也可以改变它的值。
还是刚刚举的例子,小红拿到支票后去银行取钱,她看到了有一张钱不太顺眼,当然可以把它换了。

字符数组

字符数组其实跟一维数组类似,只不过元素是字符。
但是也有很多需要注意的地方
例如其赋值可以

char c[6] = { 'h','e','l','l','o'};

也可以

char d[5] = "Hi";

其输出格式都是用%s输出的

接下来我们看下面一段程序
在讲之前先来复习一下printf中的%s
1.%s是输出字符串的,接收的参数需要时字符数组变量名或者“xx”双引号包括在字符串
2.字符串中例如hello,它所占的字节大小不是5个而是6个实际上是 h e l l o \0,%s只有读取到\0才会判断为字符读取完毕。

#include<stdio.h>
int main() {
	char c[5] = { 'h','e','l','l','o'};
	char d[5] = "Hi";
	printf("c=%s----------d=%s\n", c, d);
}

这个程序中的c和d都可以正常输出吗?
在这里插入图片描述
可以看到字符数组c输出是输出了hello但是后面很混乱而d就完好输出,这是为什么呢?

遇到bug先看内存
在这里插入图片描述
在这里插入图片描述
因为我们上面说到%s遇到\0才会停止对字符的读取
冷知识:两队c等于一个烫例如,cc cc =烫
因此这里有3队cc个烫,其他的直到00停止读取。
在这里插入图片描述
接下来我们把字符数组c变成c[6]看看会怎么样

#include<stdio.h>
int main() {
	char c[6] = { 'h','e','l','l','o'};
	char d[5] = "Hi";
	printf("c=%s----------d=%s\n", c, d);
}

在这里插入图片描述
结果是对了,我们再回看内存
在这里插入图片描述
可以看到总共有一个字节,最后一个是00,这就证明系统回自动加一个\0再最后面,这个也占一个字节。
总结:在我们定义字符数组时要比定义元素总是要比字符数多1

字符数组输入

输入也是%s

#include<stdio.h>
int main() {
	char c[20];
	char d[20];
	scanf("%s%s", &c, &d);
	printf("c=%s,d=%s", c, d);
}

在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值