C语言学习笔记——数组
创始人
2024-06-02 03:08:42
0

前言

     数组是C语言中的一种自定义数据类型,它的使用非常广泛。但是很多新手在使用数组时,经常在一些细节上出问题,导致程序崩溃或者无法编译。今天,我就来详细聊聊数组的使用和我注意到的一些细节。


一、常见的数组类型与数组的创建

1. 常见数组类型 

       与常见指针类型类似,常见的数组类型也是与常见的数据类型一一对应的,这里简单列举几个最常用的。

int arr1[10]; //整型数组
char arr2[20];  //字符数组
double arr3[5];  //双精度浮点型数组
struct stru arr[6];  //结构体数组

2. 数组的创建 

int arr1[10];  //创建数组
int arr2[5] = {0,0,0,0,0};  //创建数组并完全初始化
int arr3[10] = {0};    //创建数组并不完全初始化
int arr4[] = {1,2,3,4,5};   //创建未给定大小的数组并初始化

       我们以整型数组为例,上方为整形数组的四种创建与初始化方式。

       首先来介绍第一行,也是最基础的数组创建方式,“数据类型 + 数组名 + [数组大小]”。其中,数组名和[ ]之间不能出现空格,数组大小只能为常量(可以为标识符常量)且只能为正整数。那么我们来解读第一行,就是一个名为“arr1”,可以存放10个整型数据的数组(数组大小为10个整型,即40个字节),该数组的数据类型为"int[10]"。

       我们可以用“sizeof()”来计算数组在内存中占用的空间大小,如下图。

       接下来看第二行,数组arr2为一个大小为5的整型数组,它的五个数据均被初始化为0。其中花括号中的第一个0即对应数组的第一个数据,称为数组的第一个元素,以此类推。

       同理,第三行的arr3数组为一个大小为10的整型数组,它的第一个元素被初始化为0,剩余的9个元素未初始化。

       第四行的arr4数组在创建时未给定大小,那么它的大小就由初始化的元素个数决定。由此得出arr4的大小为5。  

补充:变长数组 (VS编译器不支持)

       变长数组与常规数组的区别在于,常规数组在定义时,其数组大小只能为常量,而变长数组的大小可以为变量。因此,变长数组可以精准地开辟内存空间,从而避免空间浪费。

3. 字符数组的初始化

char ch1[] = {'a','b','c'};
char ch2[] = "abc";

       字符数组可以按照常规数组初始化方式进行初始化,也可以按照上图第二行的方式,直接用字符串进行初始化。那么上图中的ch1,ch2的大小分别是多少呢?显然,ch1的大小为3,那么ch2的大小也为3吗?我们来看下图。

       我们发现,数组ch2的大小为4,这又是为什么呢?我们不妨直接将这两个数组以字符串形式打印出来看看。

       可以看见,第一行出现了许多乱码,而第二行则是正常的"abc"。这是为什么呢?关于这个问题,等到介绍数组在内存中的存储方式时,我再来详细解释。

二、数组元素的访问 

       当我们创建了一个数组后,我们应该如何调用数组中的元素呢?换句话说,如何访问数组的元素呢?

1. 下标引用操作符 

//  [] -  下标引用操作符 int arr[10];
arr[0] = 1;

       数组的每个元素都有自己对应的下标,通过其下标即可访问该元素。我们可以通过下标引用操作符“[ ]”。如图,arr为一个大小为10的整型数组,当我们创建了数组之后,我们通过“数组名 + [元素下标]”,访问了arr数组中下标为0的元素,并将其赋值为1。

       注意,上方代码中创建数组和访问数组元素均使用了“[ ]”,但这两个“[ ]”的含义不同,第一行的“[ ]”代表arr为一个"int[10]"类型的数组,而第二行的“[ ]”则是下标引用操作符,用来访问数组元素。

       数组中第n个元素的下标为n-1,例如,第一个元素的下标为0。

2. 指针访问

int arr[10];
*(arr+1) = 2;  //等价于: arr[1] = 2;

       本质上,数组名就是指向数组起始地址的指针。由于数组arr为整型数组,故访问数组元素时,其数组名本质上为" int* "类型的指针,可通过指针的解引用操作符访问其指向的元素。图中“arr+1”即指针向后偏移一位,指向了数组中的第二个元素,因此等价于访问数组第二个元素。

int arr[10];
int* p = arr;
*(p+1) = 2;

       同样地,我们也可以用其它指针储存arr所指向的地址,并通过该指针访问数组元素,如图中的指针p。 

三、数组在内存中的存储 

       相信通过上面的访问方式,聪明的你一定已经猜到数组在内存中的存储形式了。数组在内存上占用一块连续的空间。我们通过VS的调试器来观察数组在内存中的存储,如下图。每个整型数据占用四个字节的空间,故地址每隔四存放一个数据。

        而数组名arr即是首元素的地址,我们可以通过打印地址来查看。这也就解释了为什么数组元素可以通过指针的方式来访问。

 

        接下来我们来解决之前字符数组留下来的问题。

       通过监视窗口,我们可以看到,ch1中只有三个元素,相比之下,ch2多出了第四个元素'\0'。'\0'是一个字符,用来终止字符串。

       由于ch2中存在'\0',打印ch2时才不会出现后面的随机值。而ch1中没有'\0',因此当ch1的元素全部打印完后,编译器会继续打印数组外的内容,由于未被赋值,这些内容通常为随机值,因此一直打印到遇到随机的'\0',打印才结束。

       使用双引号" "引用的字符串的末尾都会自带一个'\0',这就是为什么用"abc"初始化ch2后会多出一个'\0'。 

四、二维数组

1. 二维数组的创建

int arr1[3][3] = {0};
int arr2[][3] = {0};

       二维数组有以上两种创建方式,其中前后两个“[ ]”中的数字我们分别称为行数和列数。二维数组的大小等于行数乘以列数。

       在第一行中,我们给定了数组arr1的行数和列数,均为3,因此arr1的大小为9,即可容纳九个元素。同样,如果行数和列数都给定了,就可以选择不初始化。

       在第二行中,我们给定了数组arr2的列数,但没有给定行数,这样一来数组的大小就根据初始化的值来确定了,如图中将第一个元素初始化为0,那么为了存储元素0,数组必须开辟一行的空间,由于一行有三列,因此数组arr2的大小为3。从中我们可以看出,为给定行数的二维数组的大小是由列数和初始化元素个数决定的。其大小必须为列数的整数倍,并且必须大于元素个数。

2. 二维数组元素的访问 

int arr[3][3];
arr[0][0]=1;

       二维数组的元素也是通过下标来访问的,只不过由于二维数组有行和列,因此下标也分为行标和列标。同样的,行标和列标也都是从0开始,如图中arr[0][0]即是访问第一行第一列的元素。

       关于二维数组的指针访问,本文就不过多解释了,因为相对来说比较复杂,涉及到二维数组的原理。有兴趣的小伙伴可以自行推导。(提示:要用到二级指针)

3. 二维数组在内存中的存储 

       在我们理解二维数组时,我们把二维数组分成行和列,如上图。我们知道,一维数组在内存中是占用一块连续的空间。那么二维数组在内存中又是如何存储的呢?我们再次通过VS的内存窗口观察二维数组arr在内存中的存储情况。我们发现,二维数组在内存中也是连续存储的,并不是像我们理解的那样分为行和列来存储。

五、数组问题中一些常见的错误和技巧

1. 越界访问

int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int a = arr[10];

       我们来看上面这段代码。首先创建一个大小为10的整型数组并完全初始化,然后创建一个整型变量a,并初始化为arr数组中下标为10的元素。但是数组arr的大小为10,那么它最大的下标应该为9,也就是说不存在下标为10的元素。这就是所谓的越界访问。

       由于数组创建时只开辟了10个整型大小的合法空间,而上方代码访问了第11个整型的空间,因此形成非法访问。

2. 初始化与修改数组元素

//错误示范
int arr[] = {1,2,3,4,5};
arr = {1,2,3,0,0};
char ch[] = "hello";
ch = "world";//正确做法
int arr[] = {1,2,3,4,5};
arr[3] = 0;
arr[4] = 0; 
char ch[] = "hello";
ch = strcpy(ch,"world");

       在讲数组的初始化时,我提到可以使用花括号和字符串进行初始化。但是要注意,当数组创建完毕并初始化后就不能再使用花括号或字符串来修改数组了。如果数组创建完成后需要修改数组元素,则需通过下标访问需要修改的元素进行修改,若需要整体修改则可借助循环遍历数组。

3. 数组传参 

void test(int* arr)
{arr[0] = arr[1];
}int main()
{int arr[3] = {0,1,2};test(arr);return 0;
}

       之前介绍访问数组的方法时就提到,数组名其实就是指向数组首地址的指针,也就是说,数组传参其实也就等价于指针传参。在声明函数的参数时,若需接受数组,只需要声明一个同类型的指针变量即可。


结束语 

       以上就是我对数组的一些基础知识的总结啦。关于数组,我个人还是更喜欢将其理解为指针的一种变体,因为C语言中,指针可以说与内存息息相关,而数组则是通过一个指针(数组名)来管理该数组空间内的所有数据。不知道大家都是如何理解的呢?欢迎在评论区留言。

相关内容

热门资讯

安卓系统优酷代理使用,畅享高清... 你有没有发现,现在用安卓手机看视频,有时候会遇到播放卡顿、广告满天飞的情况?别急,今天就来给你揭秘如...
微信运动适合安卓系统,解锁健康... 你知道吗?现在手机上有个超好玩的运动应用,叫微信运动,它不仅能记录你的步数,还能和朋友们比一比谁更活...
安卓系统kodi怎么设置中文,... 你有没有发现,安卓系统上的Kodi播放器简直就是家庭影院的神器啊!不过,用起来是不是觉得有点小麻烦?...
安卓系统显示信号强度 你有没有发现,每次拿出手机,安卓系统的信号强度显示总是让人摸不着头脑?有时候信号满格,却感觉网速慢得...
安卓系统不能玩nba吗 你有没有想过,为什么你的安卓手机上不能玩NBA游戏呢?是不是觉得这事儿有点奇怪,毕竟安卓系统那么强大...
安卓pc操作系统安装,轻松实现... 你有没有想过,把安卓系统装在你的电脑上,是不是就像给电脑穿上了时尚的潮流外套呢?想象那些你手机上爱不...
ireader是安卓系统吗,i... 你有没有想过,你的ireader阅读器是不是安卓系统呢?这可是个让人好奇的问题,毕竟现在市面上各种电...
安卓系统没有开关机,智能体验无... 你有没有发现,用安卓手机的时候,有时候会突然觉得有点儿奇怪呢?比如说,你按了电源键,手机屏幕却没有任...
美团系统怎么改安卓系统,深度解... 你有没有想过,为什么你的手机上安装的美团APP总是那么流畅,而其他APP却时不时卡顿呢?这背后,其实...
手机安卓系统怎样改定位,安卓系... 你有没有发现,手机定位有时候会不准确,让人有点头疼呢?别急,今天就来教你怎么改手机安卓系统的定位,让...
安卓导航怎么重做系统,系统重做... 你的安卓导航是不是突然卡壳了,或者你只是想给它来个焕然一新的大变身?别急,今天就来手把手教你如何给安...
安卓手写平板系统重装,轻松恢复... 你那安卓手写平板系统是不是突然间就闹起了别扭,各种卡顿、崩溃,让你头疼不已?别急,今天就来给你详细说...
小米刷安卓13系统教程,小米手... 亲爱的米粉们,你是否已经迫不及待想要升级你的小米手机到最新的安卓13系统呢?别急,今天我就要手把手教...
语音包在哪里安卓系统,语音包生... 你有没有想过,有时候一句话就能让气氛瞬间活跃起来?没错,就是那些有趣的语音包!它们就像魔法一样,能让...
htc g10安卓系统,性能与... 你知道吗?最近我在手机圈里发现了一个小秘密,那就是HTC G10这款手机。这款手机搭载的安卓系统,简...
那几款手机是安卓系统 说到手机,安卓系统可是占据了半壁江山呢!市面上那么多手机,哪几款才是安卓系统中的佼佼者呢?今天,就让...
三星安卓系统评测,体验升级 你有没有发现,手机市场里总是有那么几个品牌,它们就像明星一样,总是能吸引我们的目光?今天,咱们就来聊...
老电脑装安卓6.0系统,轻松安... 你那台老电脑是不是已经服役多年,性能越来越不给力了?别急,今天就来给你支个招——给老电脑装上安卓6....
流畅度最高的安卓系统,揭秘安卓... 你有没有想过,为什么你的手机用起来那么顺滑,而别人的手机却总是卡得要命?这背后,其实隐藏着一个秘密—...
安卓手机系统服务在哪有,安卓手... 你有没有遇到过这种情况:手机里装了各种各样的应用,但是有时候想找某个服务却怎么也找不到?别急,今天就...