在之前的学习中,我们初步知晓了如何去定义一个整数型的变量,但是,当我们需要的变量数目增加时,单个的定义就已经不能满足我们的开发了,这时,就需要使用数组来直接划分一块连续的内存区域来方便我们储存数据
数组可以存储一个固定大小的相同类型元素的顺序集合
你可以将我们C++中所有的数据类型比作房子的户型,而单个的变量就像独立小别墅,只能容纳一个户型。而数组就如同现代电梯公寓,在同一栋楼中,可以容纳多个相同的户型。而我们对数组中单个元素的访问,就相当于我们去访问公寓内某个特定的楼层的特定户型
从上面可以类比得知,我们在定义数组值,必须给数组起一个合理的名字,并且要指定数组的大小,就如同修房子,我们不仅要对楼栋编号,而且要计划好楼层的数目 代码如下
int a[100];在这一行代码中,我们定义了一个名字叫做a的整型 一维 数组,并且规定了其能容纳100个整型变量
在此之前,我们应该知道,C++各种数据类型占用的字节数与二进制位数,并且应当了解1Byte = 8b,1KB=1024B,1MB=1024KB,1GB=1024MB
然后,我们会有这么一张表
常见变量类型占用字节占用位bool18char18short216int2~416~32long432long long864float432double864在这里特别提醒,int并不是任何时候都是占4字节,在早期16位电脑或者如今一些MCU上,int是占用2字节
这里附上一张int类型数组的内存占用图片 可以看出,在int类型的数组中,我们的程序在内存中划分了一块连续的空间,并且以四个Byte为一组,分配给数组中的每一个元素 同样,我们也可以用代码访问内存地址来直观的看到
#include<cstdio> using namespace std; int arr[10]; int main() { for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++) printf("&arr[%d] = %p\n", i, &arr[i]); return 0; }运行结果(结果不一定相同):
&arr[0] = 000000000040c040 &arr[1] = 000000000040c044 &arr[2] = 000000000040c048 &arr[3] = 000000000040c04c &arr[4] = 000000000040c050 &arr[5] = 000000000040c054 &arr[6] = 000000000040c058 &arr[7] = 000000000040c05c &arr[8] = 000000000040c060 &arr[9] = 000000000040c064 -------------------------------- Process exited after 0.04451 seconds with return value 0 请按任意键继续. . .sizeof实际上是获取了数据在内存中所占用的存储空间,以字节为单位来计数
任何东西都是拿来用的,如果不能很好的使用这些工具,那么工具便失去了其存在的意义 但是,在使用数组之前,我们应该明白以下几个概念,再此,我们以定义数组**int a[10]**为例 1:int 声明了我们定义的数组的变量类型,在这里时整型 2:方括号前的字母a,表示了我们的数组名称叫做a,这里名称大家可以随意取名,但是要注意符合C++变量名的规范 3:后面的[10]表示我们定义的这个数组可以容纳10个int类型变量 4:当我们想要调用数组内的某一个元素时,我们应该在调用时加上数组的下标,例如,我们要调用a数组的第5个元素,那么,我们应该使用a[4] 5:在调用数组元素时,我们应该特别注意,我们定义的时候,方括号内表示的是元素个数,但是在调用时,方括号内是元素下标,而数组的下标是从0开始而非1,这也就是为什么在上面,我们调用第5个元素,数组下标却为4的原因,如果我们调用a[10],这是第11个元素,但我们的数组有且只有10个元素,这时,我们就访问到了不存在于数组中的一个内存地址,我们将其称为数组下标越界 6:为了防止我们的数组在实际使用中出现越界这种尴尬情况,我们一般会将数组的大小多开5~10,例如,我们只需要10000个元素,但我们的数组可以开10010个元素来防止越界
学习了理论,我们就可以进行实际的操作了 例如,我们需要统计某位同学的高考成绩,这里以全国卷为例,我们依次输入语文、数学外语、理综,要求输出总分和理综,并以空格分隔开,代码如下
#include<cstdio> using namespace std; int a[10]; int main() { for(int i=1;i<=4;i++) scanf("%d",&a[i]); int sum=0; sum=a[1]+a[2]+a[3]; printf("%d %d",sum+a[4],a[4]); return 0; }在这个代码中,我们几乎包含了数组的所有用法,大家可以仔细的品味
既然有一维数组,那么是否也有二维乃至更高维的数组呢? 答案是肯定的,就像同一层楼也可以容纳不同户型的原理,我们的数组也可进行拓展,代码如下
int b[10][10]; //二维数组 int c[10][10][10]; //三位数组 int d[5][5][5].....;//请充分发挥你的想象力在我们的代码中,我们时常要对一个数组赋予一定的初始值,这个过程,称为数组的初始化 实现初始化的方式多种多样 比较常用的有如下几种: 1:最简单,也是常用的方法就是将数组放在主函数外部进行定义,这时,我们的数组会被系统自动初始化为0 2:另外一种方法就是使用上一节讲到的循环,我们可以人为的写一个循环来一个一个的对数组的元素进行赋值,也就相当于对数组进行了初始化 3:此外,我们可以调用 < cstring > 中的 memset 函数,通过简单的设置参数,让系统自动完成赋值 4:数组也可以向普通变量一样,在定义阶段就给其赋予初始值,但是其与普通的单一变量赋值略有不同,样例代码如下
int a[5]={0,1,2,3,4}; int b[2][2]{{1,2},{3,4}}; int c[10]={1,2,3} //值得注意,在我们对数组c赋值的时候,我们并没有对其全部赋值 //这时后面我们没有赋值的区域会被系统自动赋值为0memset函数用法 结构
void * memset ( void * ptr, int value, size_t num );可以看出,memset是一个没有返回值的函数 其内部包含有三个参数 第一个是以指针形式传入的数组 第二个是需要初始化的值 第三个是需要初始化的数组的长度 这里附上cplusplus中的样例和运行结果 样例:
/* memset example */ #include <stdio.h> #include <string.h> int main () { char str[] = "almost every programmer should know memset!"; memset (str,'-',6); puts (str); return 0; }运行结果:
------ every programmer should know memset!在我们的现在程序中,如果输入输出都只有单纯的数字,在我们需要读取一个英文字母时,单纯的数字显然就不能满足我们的需求 于是,这时我们就需要定义、读取、处理、输出其他不同类型的变量
于是这里先引入字符变量char
在C++中,变量的类型十分丰富 比如整数类型:short、int、long、long long 浮点类型:float、double 字符类型:char 布尔型:bool
当我们需要同时存储多个字符时,我们也可以运用定义数组的方式来存储,这时,这个数组就被我们叫做字符串,如char str[100];
要知道,任何数据在计算机中,都是以二进制储存,非0即1,我们在输出或者输入的时候,就一定要告诉程序,我们现在要操作的这个是一个什么东西,系统才能对其进行对应的处理
所以在这里,我们对字符操作就需要用到%c或%s这个格式转换符
格式转换说明符大全 %a(%A) 浮点数、十六进制数字和p-(P-)记数法(C99) %c 字符 %d 有符号十进制整数 %f 浮点数(包括float和doulbe) %e(%E) 浮点数指数输出[e-(E-)记数法] %g(%G) 浮点数不显无意义的零"0" %i 有符号十进制整数(与%d相同) %u 无符号十进制整数 %o 八进制整数 e.g. 0123 %x(%X) 十六进制整数<?xml:namespace prefix = st1 />() e.g. 0x1234 %p 指针 %s 字符串 %% “%”
值得注意,对于一个特定的数据类型,我们同样也可以对其进行其他的转换,以一个int或者其它类型进行输入输出 例如下面代码:
#include<cstdio> using namespace std; int main() { int a=65; char b='A'; printf("%c %d",a,b); return 0; }运行结果:
A 65 -------------------------------- Process exited after 0.02979 seconds with return value 0 请按任意键继续. . .可以清楚的看到,我们以字符格式打印一个值为65的整型变量a却输出了A 我们以整数型来输出一个存了字母A的字符变量b却输出了65
那么我们可以看出来,我们的每一个符号,其实是以数字的形式被储存在变量中,于是我们有了这么一张图(也是大佬们发明的方法) 这张表被称为ASCLL表,里面对应的代码叫做ASCLL码(美国信息交换标准代码)
讲解完这么多的前导知识,我们终于要开始我们的正题,字符串输入输出了 与整型变量相似,我们的字符与字符串也可以采用相似的格式类进行输入输出操作 代码如下
char s[1000]; scanf("%c",&s[1]); printf("%c",s[1]); scanf("%s",s); printf("%s",s);在此可以看到,当我们使用%c对单个字符输入时,与整型一样,用到了&,也就是取地址符,但是当我们有多个字符以字符串输入时,却没有了取地址符,这时为什么呢? 回到我们对于数字储存方式的讨论,当我们在这里创建了一个s[100]的数组后,s实际就代表的时我们首位的内存地址,而[100]代表着我们以这种方式创建100个同样的内存序列,这便构成了一个数组
还是用下面这份代码来解释
#include<cstdio> using namespace std; int arr[10]; int main() { for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++) printf("&arr[%d] = %p\n", i, &arr[i]); return 0; }当我们把
&arr[i]改为
arr+i得到以下代码:
#include<cstdio> using namespace std; int arr[10]; int main() { for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++) printf("&arr[%d] = %p\n", i, arr+i); return 0; }同样可以得到一个类似的序列(不一定相同):
&arr[0] = 000000000040c040 &arr[1] = 000000000040c044 &arr[2] = 000000000040c048 &arr[3] = 000000000040c04c &arr[4] = 000000000040c050 &arr[5] = 000000000040c054 &arr[6] = 000000000040c058 &arr[7] = 000000000040c05c &arr[8] = 000000000040c060 &arr[9] = 000000000040c064也就时是说,我们传入的是一个内存地址,让我们的输入函数从这个位置开始,我们依次写入我们的字符,直到遇到空格或回车 而在输出的时候,我们也告诉输出函数,从这个内存地址开始,依次输出里面的字符
但是,新的问题又出现了:我们的函数怎么知道在什么时候结束输出 当然是你告诉它 在我们的字符串中,如果是定义的全局变量,里面的数值会被自动初始化为0,对应 ‘\0’,在C++中,这个特殊标记起到告诉输出函数什么时候停止的作用,当我们的输出函数一个一个的像后面输出字符,知道其读取到 ‘\n’ 的时候就停止
那么,如果我们对一个字符串进行了初始化,那么它是不是在读取之后就会把当前输入的与之前初始化的都输出呢? 答案是否定的,因为在每一次输入后,我们的程序会自动在后面添加一个‘\0’来标注,以免出现上述情况
数组是编程中相当基础的东西,我们不仅应当了解其使用方法,更应该去了解它背后的原理与结构,不仅数组,这个方法对于其他语法同样适用,这是我们深入了解代码的重要步骤
