大家好,很高兴又和大家见面啦!前面咱们已经把函数的相关知识点学习完了,今天咱们将开始进入数组内容的学习。
一维数组的创建和初始化
1.数组的创建
(1)数组定义
定义:数组是一组相同类型元素的集合。
(2)数组的创建方式
//数组的创建
type_t arr_name[const_n];
//type_t——数组的元素类型;
//arr_name——数组的名字;
//const_n——是一个常量表达式,用来指定数组的大小;
在初识C语言13中我们有对数组做过简单的介绍,感兴趣的朋友可以点击链接去回顾一下。下面我还是举几个例子吧:
//字符数组
char ch[10];
//整型数组
short / int / long / long long arr[20];
//浮点型数组
float / double arr[30];
有一个点是需要注意的,我们在创建数组的时候,中括号里的内容是一个常量表达式,也就是里面只能存放常量,如果我们存放的是变量,那是不能创建数组的:
这里我们可以看到,程序报错,原因是因为表达式必须含有常量值,但此时的a为局部变量;
我们通过#define定义的常量标识符a此时就是常量,它的大小是5,从结果我们也可以看到:
数组所占空间的大小就是元素个数与元素类型的乘积。
2.数组的初识化
(1)数组初始化定义
定义:在创建数组的同时给数组的内容一些合理初始值。
在介绍数组初始化前,我们先介绍一下一个比较熟悉的知识点:strlen和sizeof。
strlen与sizeof的区别
- 性质不同:strlen是一个库函数,在使用strlen时需要引用头文件<string.h>,而sizeof是一个操作符,详细介绍见初识C语言14有兴趣的朋友可以回顾一下;
- 用法不同:strlen是用来计算字符串的长度,sizeof是用来计算数据类型/变量/数组所占空间大小。
strlen与sizeof介绍完了,下面我举例子并借助strlen与sizeof来帮助大家对数组初始化进行理解:
//数组的初始化
int main()
{
char a[5] = "abcd";//完全初始化
char b[5] = { 'a','b','c','d','e' };//完全初始化
char c[5] = "abc";//不完全初始化
char d[5] = { 'a','b','\0' };//不完全初始化
char e[] = "abcd";//变长数组
char f[] = { 'a','b','c','d' };//变长数组
//计算数组所占空间大小
printf("%d\n", sizeof(a));
printf("%d\n", sizeof(b));
printf("%d\n", sizeof(c));
printf("%d\n", sizeof(d));
printf("%d\n", sizeof(e));
printf("%d\n", sizeof(f));
//计算数组字符串长度
printf("\n%d\n", strlen(a));
printf("%d\n", strlen(b));
printf("%d\n", strlen(c));
printf("%d\n", strlen(d));
printf("%d\n", strlen(e));
printf("%d\n", strlen(f));
return 0;
}
在这个代码中我们可以看到数组初始化的意思就是在创建数组的同时给数组一定的元素。
如果元素个数等于数组大小那就是完全初始化;
如果元素个数小于数组大小,那就是不完全初始化;
但是如果我们给了数组元素个数,但是没有给数组大小,那这个数组就是一个变长数组,它会因为数组内元素个数的改变,而使数组大小发生改变。
注:数组创建,在C99标准之前,[]中要给一个常量才可以,不能使用变量。在C99标准支持了变长数组的概念。
下面有一个问题,对于这些数组的字符长度以及元素个数,分别是多少呢?大家可以先在草稿纸上将自己的答案记录下来,然后再对照运行结果来判断自己的答案是否正确:
这个结果跟各位的答案是否一致呢?下面我们来探讨一下数组内的元素
(2)数组的元素
在前面的学习中,我们知道了字符串时由双引号引起的单个或多个字符组成,字符串会自带一个\0字符,所以当我们将字符串赋值给数组时,实质上是将看得到的字符加上\0一并赋值给了数组,也就是说数组abcdef的元素分别是:
//数组的元素
a[5] = { 'a','b','c','d','\0' };
b[5] = { 'a','b','c','d','e' };
c[5] = { 'a','b','c','\0' , 0, 0 };
d[5] = { 'a','b','\0',0,0 };
e[] = { 'a','b','c','d','\0' };
f[] = { 'a','b','c','d' };
这里可能会有朋友好奇了,为什么数组c、d中还有两个0?这是因为当我们在给数组进行不完全初始化时,除了被赋值的元素外,未被赋值的元素会默认赋值0。
我们在用strlen函数计算字符串长度时,会计算\0之前的所有字符个数。我们现在已经知道了数组中的元素分别是什么了,下面我们开始数各数组的字符个数,这里很容易可以得到:
//数组元素个数
a[5] = 5;
b[5] = 5;
c[5] = 5;
d[5] = 5;
e[] = 5;
f[] = 4;
从这里我们可以得到结论:
数组的大小=数组元素个数
接下来我们将这个值与元素的数据类型所占空间大小相乘就能得到数组所占空间的大小:
//数组所占空间大小
a[5] = 5;
b[5] = 5;
c[5] = 5;
d[5] = 5;
e[] = 5;
f[] = 4;
咱们将这个值与sizeof算出来的值进行对比,会发现一模一样,这个也即是sizeof计算数组大小时的工作原理:
数组所占空间大小=元素个数*元素类型所占空间大小;
接下来我们继续来数字符的个数:
//字符个数
a[5] = 5;
b[5] = 5;
c[5] = 5;
d[5] = 5;
e[] = 5;
f[] = 4;
我们可以看到,这个字符个数和数组的元素个数是一致的,但是为什么结果不一致呢?下面我们再来数一下\0之前的字符个数:
//\0之前的字符个数
a[5] = 4;
b[5] = 5;
c[5] = 3;
d[5] = 2;
e[] = 4;
f[] = 4;
接下来我们再来跟strlen所计算的答案进行对比,我们会发现数组b与数组f的字符个数与strlen所计算的字符长度不相同,为什么会这样呢?
会出现这个结果是因为strlen在计算字符长度时,只有遇到\0才会停止计算,但是在数组b和数组f中并未看到\0,所以strlen在计算其长度时会出现将已有的字符计算完后,会继续往后计算,直到遇到\0,才会停止。那问题来了,它怎么知道\0会在数组b的第21个和数组f的第17个呢?其实关于这个\0在数组的哪个位置,strlen同样也不知道,所以它在遇到\0之前,只能一直往后计算,这个\0的位置是随机出现的,所以不管是数组b计算的20也好还是数组f计算的16也好,它们都只是一个随机值。通过这个例子,我们可以得到结论:
strlen在计算字符串长度时,是计算字符\0之前的字符个数,当一串字符中没有\0时,strlen会计算出一个随机值。
接下来我们就来看看一维数组是如何使用的。
3.一维数组的使用
对于数组的使用,我们之前介绍了一个操作符:[]——下标引用操作符。它其实就是数组访问的操作符。通过这个操作符,我们对数组就有两种使用方式:通过数组下标访问数组元素、通过数组下标计算数组大小。
(1)通过数组下标访问数组元素
通过前面的学习,我们知道了数组元素的下标是从0开始进行编号,下面我们通过代码来介绍这个使用方式:
//一维数组的使用
//通过数组下标访问数组元素
int main()
{
int a[] = { 1,2,3,4,5,6,7,8,9,10 };
for (int i = 0; i < 6; i++)
{
printf("%d ", a[i]);
}
return 0;
}
在这个代码中我们通过下标i将数组下标在6以下的全部元素给打印出来,打印结果如下:
这就是通过下标来访问数组元素,下面我们来介绍一下第二种使用方式:
(2)通过数组下标计算数组大小
在前面的介绍中我们知道了数组所占空间的大小=数组大小*数组元素类型所占空间大小由此我们可以得到:
数组大小=数组所占空间的大小/数组元素类型所占空间大小
根据这个结论我们可以编写代码:
//通过数组下标计算数组大小
int sz = sizeof(a) / sizeof(a[0]);
//sizeof(a)——数组所占空间大小
//sizeof(a[0])——数组第一个元素所占空间大小
printf("\n%d", sz);
下面我们来看一下结果:
以上就是一维数组的使用,下面我们来介绍一下一维数组在内存中的存储:
4.一维数组在内存中的存储
1.内存
在初识C语言18中我们有简单介绍过内存,有兴趣的朋友可以点击链接回顾一下相关内容。这里我简单概括一下:
内存是一个可以存放和读取数据的空间;
它里面被分成了一个个小的内存单元,每个内存单元的大小是1个字节;
每一个内容单元都有它相应的编号,这个编号我们称为内存单元的地址。
2.地址的表现形式
既然在内存中,每一个内存单元都有自己的地址,那地址的表现形式是什么呢?如图所示:
我们可以看到,这个地址打印出来,又是数字又是字母的,这是什么东西呢?其实这种表现形式是通过16进制来表现的。那什么是十六进制呢?
3.十六进制
十六进制是在十进制的基础上加上了字母A~F,这些字母分别代表11~15,十进制是逢十进一,而十六进制则是逢十六进一。十六进制的数按从小到大排列分别是:
// 十六进制
十六进制数:0 1 2 3 4 5 6 7 8 9 A B C D E F
十进制数:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
计算机在存储数据的时候,肯定不是按十六进制来存储的,计算机存储数据只能通过二进制来存储,但是在向我们展示的时候则是通过十六进制来进行展示,那这个十六进制转化为二进制又是多少呢?
我们借助程序员计算器可以看到,这个十六进制的数转化为二进制时是:1110 1000 1111 1000 0111 0100,这时候有朋友就会问了,地址在32位系统中不应该是32个比特位吗?为什么这里只有24位,还有八位呢?这个问题问的非常好,剩下的八位去哪了呢?我们不妨再仔细看一下打印出来的地址前面两位是什么?它完整的地址应该是00e8f874,这里每个数值对应4个比特位,前面我们才提到十六进制的每一个数对应的十进制的数分别是什么,下面我们就来将其转换成二进制来看看:
十六进制 十进制 二进制
0 0 0000
0 0 0000
E 14 1110
8 8 1000
F 15 1111
8 8 1000
7 7 0111
4 4 0100
现在我们再跟刚刚转换的值对比一下,有没有发现是不是漏掉了两个0所对应的二进制数值呀,所以这里完整的二进制位应该是:
0000 0000 1110 1000 1111 1000 0111 0100
二进制位转换成十进制为这里我拿1110来举例:
1110转换为十进制=1*2^3+1*2^2+1*2^1+0*2^0=8+4+2+0=14,14转换为十六进制:14->E。
现在各位应该理解十六进制,以及十六进制、二进制转换为10进制了吧。
4.数组及数组元素的地址
在回顾完这些知识点后,我们通过下面的代码来看一下一维数组是如何在内存中存储的:
//一维数组在内存中的存储
int main()
{
char a[] = "abc";
int sz1 = sizeof(a) / sizeof(a[0]);//数组a的大小
printf("&a=%p\n", &a);//数组a的地址
for (int x = 0; x < sz1; x++)
{
printf("&a[%d}=%p\n", x, &a[x]);//打印数组a各元素的地址
}
short b[] = { 1,2,3,4 };
int sz2 = sizeof(b) / sizeof(b[0]);
printf("\n&b=%p\n", &b);
for (int y = 0; y < sz2; y++)
{
printf("&b[%d}=%p\n", y, &b[y]);
}
int c[] = { 1,2,3,4 };
int sz3 = sizeof(c) / sizeof(c[0]);
printf("\n&c=%p\n", &c);
for (int z = 0; z < sz3; z++)
{
printf("&c[%d}=%p\n", z, &c[z]);
}
return 0;
}
这里我分别定义了字符数组a、短整型数组b、和整型数组c,我们既然要了解数组在内存中的存储,那我们就需要知道它们在内存中的地址,我们通过数组的地址与数组元素的地址来说明它们在内存中是如何存储的:
从这个打印结果我们可以看到数组的地址与数组第一个元素的地址相同,在char类型的数组中,元素的地址相差1,在short类型的数组中,元素的地址相差2,在int类型的地址中,元素的地址相差4。char类型在内存中所占空间大小刚好是1个字节,short是2字节,int是4字节,这样一对比有没有发现什么呀?不管是char类型还是int类型是不是元素的地址都是紧挨着的呀,现在我们就可以得出以下结论:
(1)数组的地址与数组中第一个元素的地址相同;
(2)数组在内存中是由低地址到高地址连续存放的;
(3)每个元素地址相差的字节大小与元素的类型所占空间大小一致;
结语
到这里咱们本章的内容就全部结束了,希望这些内容能够帮助大家更好的理解一维数组的相关知识。接下来随着学习的深入,我会继续给大家分享我在学习过程中的感受。如果各位喜欢博主的内容,还请给博主的文章点个赞支持一下,有需要的朋友也可以收藏起来反复观看哦!感谢各位的翻阅,咱们下一篇见。