C基础系列教程10——结构体

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

 

先思考一个问题:
(亲爱的小明出场) 假如一个小组有10个同学,需要记录在程序中,其中几个同学分别叫小明、小红、小绿、小然、小琪、小玮、小霄·········(这些只是随便起的名字,臻的是随便起的233333)
现在需要分别记录TA们的编号、身高、体重什么的,为了方便写代码所以只出现数字,都是int,应该不会有越界情况发生(逃)。请思考如何声明变量。
先想想,再往下看
————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
你可能写出这样的代码(这是个错误的示范)

每个人的每个数据(以后可以称之为属性)都声明一个变量。这样当然是可以的(如果你不嫌累)。
顺便一说,变量的命名要简洁明了,不要这么啰嗦=-=并且不要用拼音。

不多嘛,30行就写完了。然后呢,等你写完之后突然有人又告诉你(客户改需求了),需要每个人再增加一个年龄。。你内心一定是这样的
————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
现在遇到的问题就是这样,今天介绍一个新的东西,struct。叫做结构体,一个结构体可以包括多个变量, 定义方式如下。
你需要新建一个文件,文件名叫做person.h,这种后缀的文件我们称作头文件,内容是这样的

 

这样就定义了1个结构体类型叫做person,注意不要丢了右大括号后面的那个分号。在主程序中我们需要在#include<stdio.h>的后面加一行 #include “person.h” ,这种自己写的头文件我们用双引号引起来以便于和C自带的头文件混淆。

接下来就可以定义变量了,代码是这样的。

 


第一行声明了一个叫做xiaoming的变量,这是个特殊的变量,因为它是个结构体,里面包含了刚刚定义的三个属性,id、身高、体重。我们使用.(这是一个点儿)来访问结构体变量中的属性给它赋值(当然也可以读取,比如最后一行)。

这样的话定义起来就会方便很多,增加属性的话直接去更改头文件的定义就好了。

————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
接下来会出现一个问题,你每次定义的时候都要敲一个struct person,这令人十分讨厌(主要还是懒)。所以呢,我们在把头文件改成这样

我把原来的struct person改成了struct _person,加了一个下划线,是为了下面这样做,typedef的意思是定义一个新的类型,把struct _person 叫做person,也就是说你待会输入person就相当于输入了struct _person,这样就少写了一个单词 (我臻是太懒了=-=)
那么这样做之后你会发现原来的程序报错了,因为我们加了个下划线,所以源程序有两种改法
第一种,跟着加下划线,这种方法显然更麻烦
第二种,直接删掉struct,这也是我们这样做的目的(其实还是为了偷懒)。

更改后代码如下,小红就删了啊=-=


————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
今天有时间,就接着往下写吧=-=一次看不完可以下次接着看

在数据结构中,不光要储存数据,还要对应数据的一些操作,这些操作的集合我们叫做操作集,也就是一堆函数。
所以,在数据结构中很少通过定义一个本地的变量来操作,都是通过函数来进行的,那么这里就有一个问题,在函数中对本地变量操作无法直接传参数,需要传地址,也就是指针。

所以需要更改声明为

 

第一行是原来的声明,需要改写成第二行的样子,之所以注释掉是因为太长(虽然更好理解,也就是小明所在内存中的地址),所以依然叫做xiaoming(还是太懒) ,现在你要注意,接下来的 “xiaoming” 的意思是小明所在的地址,不再是之前的xiaoming了。还有一点要注意,指针的声明要初始化为NULL,否则叫做野指针,不知道指向哪里。

我们来写一个函数来进行和之前同样的操作。主程序是这样的,注意多加了一个stdlib.h

其中需要解释的是malloc这一行,意思是分配一块大小刚好能放得下person结构体的内存,这时候电脑并不知道这块内存要放什么,你需要强制类型转换成person指针类型来告诉电脑这里要放的是一个person。其中sizeof求出的是一个person占多大的内存,malloc负责向系统申请一块这么大的内存,然后返回一个内存的地址,但是是不知道类型的地址(也就是void *),最后强制转换成person*
这样的做法就相当于之前的person xiaoming;然后把小明的地址记下来。最后一行把小明的地址传给fun函数处理。

接下来写fun函数,我们接收到的是person的指针,叫做sb(somebody-_-||不要多想),这个sb就是一个指向了person的指针。

我们的做法和之前的整数交换差不多,*sb代表的是间接取出sb指向的内容,也就是小明,之后在用.(这是一个点儿)来访问小明的id、身高、体重。之所以加括号是因为那个“点”的运算优先级要高于间接取值的星号。
但是人总是懒的=-=又要打括号星号还有点太麻烦了→_→所以呢就有这这种写法,和上面完全等价

是的你没看错就是一个箭头,一个减号和一个大于号。
注意这俩符号中间不能加空格
注意这俩符号中间不能加空格
注意这俩符号中间不能加空格
重要的事情说三遍。

————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
补充说明:.(这是一个点)运算用在访问一个结构体变量的属性时,那个箭头用于指针指向的结构体属性时候,一个是直接访问一个是间接访问,后续还可能会出现结构体的属性指向结构体,就可能出现先箭头再一个点来访问,比如链表。。。这是小然提出来的补充哦(逃
————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
至此,数据结构基础告一段落,如果还有补充的我会继续发=-=
下面是我的github,这里有一些数据结构的源码。。。还在学习ing=-=(请勿抄袭!请勿抄袭!请勿抄袭!)

https://github.com/956237586/DataStructure-C/tree/master/DataStructure-C
————————— 我 是 萌 萌 哒 分 割 线 ●▽● —————————
题外话,关于箭头还有个黑魔法,依然转自知乎 http://www.zhihu.com/question/27417946

C基础系列教程9——指针3

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

当你在C中需要储存大量的数据的时候,最常见的一种做法就是数组,今天将介绍数组和指针之间的关系,展示数组真实的模样。

本次主要为一维数组,多维数组就是一维数组的推广而已,可以仿照本文方法分析研究。

首先编写测试代码如下

在这段程序中定义了一个整数数组,一个整数指针变量
断点调试观察内存的状态是这样的

这是整个数组,图中每个红框就是一个int,是数组中的一个单元,从下往上看,可以发现分别就是arr[4]一直到a[0]
————————我 是 萌 萌 哒 分 割 线 ●▽●——————


这是指针pa
————————我 是 萌 萌 哒 分 割 线 ●▽●——————
在接下来为了节省地方,我会把四个字节的内容放到一行,省略一些地址的显示,如下图

————————我 是 萌 萌 哒 分 割 线 ●▽●——————

这两张和之前的两张意思是一样的,只是省略了部分地址的显示,便于观察,看的时候从下向上,从右至左
————————我 是 萌 萌 哒 分 割 线 ●▽●——————

观察上图不难发现,取数组的地址的结果就是数组最开始的地址,指针也指向这个位置。继续增加代码如下

运行结果如图

让我们来一行一行的分析
第1行、第2行、第4行,如果我们尝试直接打印数组名字,会发现打印出来的就会和数组第一个元素的结果是一样的,和指针的内容也是一样的,这说明,数组的名字就代表了数组的首元素地址。指针指向数组的时候也会指向这个数组的首元素地址。

当然,打印出来的arr[0]也肯定会和*pa一样,因为pa的值就是第一个元素的地址。这没什么好解释的,自习对照上面的内存截图很容易就能看出来。

接下来我拿第3个元素作为例子,也就是arr[2],剩下的几行你会发现,pa + 2 我们称作指针的偏移,偏移2个单位,由于一个整数占4个字节也就是四个格子,所以也就偏移到了数组中的第3个数字,你可以把2改成其他值观察结果是怎样的,最后的结果你会发现实际上arr[2]的含义可以理解为从数组首地址也就是arr先偏移2再取值,也就是最后一行写的那样。

(题外话,这里还有个黑魔法,转自知乎)


如果你明白了刚才说的数组,那么这个黑魔法的原理你也一定能很快的想明白,这个就留给大家思考吧=-=
温馨提示,你可以编写一些代码验证这个黑魔法,断点调试观察内存,尝试更换等价表达式来验证你的猜想是否正确

下次介绍struct的基本使用

C基础系列教程8——指针2

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

上次我们说到,指针的一个用途,可以当作参数传递给函数进行使用,废话不说,直接上代码

 

一定要记的上次说的,星号的两种使用,一种是在声明的时候,代表变量类型为指针,第二种是使用的时候根据指针所指的地址间接取值。同理,在参数的声明时候也一样。在上面的代码中,声明了两个参数,类型都是整数指针,也就是将要储存整数变量所在地址的变量(有点绕,配合上次的内存截图理解),接下来的三行是整数交换的操作。注意,t是一个局部的整数变量,pa是个整数指针,只有间接取值的结果才是一个整数。同理,*pb代表的是pb指向位置的内容,这是第三个星号的使用:间接的存储。这三行看似简单,但是如果不能理解指针就会非常容易出错,究竟是不是应该加星号是最容易纠结的地方。接下来的几张图将表示函数执行的过程(图中的地址只是为了便于理解,并不符合真实内存的分配规律,上次的截图才是真实的内存状态)。

假设两个数 a = 1, b = 2,调用语句是这样的swap2(&a, &b); 这样就传入了两个变量的地址。

传入的地址的值会被原封不动复制到pa和pb两个新的指针变量的内容中,如上图

第1行,分配一块内存当作t,并且存入pa所指向的内容,如上图

第2行,改写pa指向地址的值,如上图

第3行,改写pb指向地址的值,如上图

最后, 函数结束,回收pa,pb,t三个变量占用的内存,如上图

这就是通过传入地址来间接交换两个变量,你可以用下面的代码观察结果

为了更好的理解值传递,下面的几张图将表示上一次的交换函数执行过程,也就是上面代码中的swap1,一样假设两个数a = 1, b = 2(图中的地址只是为了便于理解,并不符合真实内存的分配规律,上次的截图才是真实的内存状态)。

—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————

参数传入后会像上次说的那样原封不动的复制一份到新的参数中,虽然同名(也可以不同名),但是本质上已经不是同一个变量了, 如上图所示
—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————

—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————

—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————

—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————


最后, 函数结束,回收a,b,t三个变量占用的内存,如上图
—————————我 是 萌 萌 哒 分 割 线 ●▽● ———————————
下次介绍指针与数组的关系

C基础系列教程7——指针1

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

接下来主要是指针和结构体的使用,以及堆内存的分配。这些内容非常非常非常重要,将会在数据结构中大量使用,如果这些不懂,写数据结构的代码会比较困难。

首先是指针的使用,请思考,如何写一个函数,实现两个参数内容的交换?
比如我传入a = 1, b = 2,要求用函数交换这两个内容,你可能会写出这样的代码

最初的想法一定是这样的,你肯定会想,直接把两个变量交换一下不就结了- –
你可以使用这段代码来测试这个函数

        你会发现运行结果是这样的
当swap1之中结束之前,两个变量的值还是被交换的,但是当回到主函数中的时候,两个变量的值又回到了交换前的状态。
原因是这样的,函数的参数在主程序时候传入的那个a,和函数中的a,在这个例子中名字都是a,但实际上确实两个不同的变量,当传入一个参数给函数时候,电脑会把传入的参数变量原封不动的复制一遍,当函数执行完毕后,复制出来的那份变量会被电脑销毁。所以当你在swap1中交换两个变量的时候,实际操作的是复制出来的那份a和b,原来的那份a和b根本就没被交换,所以才会出现函数执行之后回到交换之前的状态。

那么怎么解决这个问题呢?
变量实际上是存在内存中的,你可以想象成一个巨大的表格,每个单元格有自己的地址,如图所示,左侧的0xXXXXXXXX是个16进制的数字,就是这个单元格的地址,可以理解为这个格子的编号。执行完int a = 1;之后,电脑就会分配4个单元格(4个字节)当作变量a,并且把内容改为00000001(从下往上看,栈内存分配是从高到低分配,不懂也没关系,不要在意这些细节)
想一下,函数参数的传递既然都是值传递(也就是刚才说的原封不动的复制一份以前的内容放到一个新的地方),那么可不可以把单元格的地址传给函数,在函数内部更改指定地址的内容呢?答案是可以的,这样就引出了指针,我们把储存内存地址的变量叫做指针,如下图,储存了上图中变量a的地址。接下来介绍指针的基本语法
在C语言中, 我们使用*来声明一个指针变量(也可以称作地址变量),使用&符号取一个变量的地址(这下知道为什么scanf的时候不能忘记&符号了吧),使用*来取出指针所指向地址中的内容(也就是取值),示例代码如下

需要注意的是,要注意区分两次星号的意义,第一个星号是声明一个整数类型的指针(四个字节,储存的是地址),第二个星号的意思是,先取出pa中的地址,再取出这个地址中的内容,是个间接的访问。(补充一点,在printf中%p用来显示内存地址,%d就不用说了吧)
你可以多次运行这段代码观察结果的规律,内存状态如下图所示。需要注意的是,你的运行结果和我图中结果一样的几率很小,但是规律肯定是一样的,因为每次运行电脑分配的内存都不一样。后两张是内存的状态,注意观察规律。

下一次介绍指针的应用之一,当作参数传递给函数,这样就可以解决开始提出的问题。。。
转载请注明出处,谢谢!

C基础系列教程6——函数

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

C基础系列教程5——多重循环

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看

C基础系列教程4——简单循环

该系列文章内容可能来源我本人或者zhrmoe(他的主页:https://zhr.moe)的编写。文章如果有错误欢迎批评指正,谢谢!转载请注明来自本站,另外,本系列教程中的代码建议初学者自己手打一遍,不要直接复制(由于某些奇怪的原因可能会导致你复制的代码出现错误!相信自己的双手吧=-=
如果你觉得进度太快,可以等你熟悉了前面的内容后再继续往下看