第10期 《C语言基础知识2》04校2.docx
- 文档编号:5221835
- 上传时间:2022-12-14
- 格式:DOCX
- 页数:15
- 大小:818.78KB
第10期 《C语言基础知识2》04校2.docx
《第10期 《C语言基础知识2》04校2.docx》由会员分享,可在线阅读,更多相关《第10期 《C语言基础知识2》04校2.docx(15页珍藏版)》请在冰豆网上搜索。
第10期《C语言基础知识2》04校2
STM32入门100步- 第10期
C语言基础知识2
杜洋洋桃电子
数据
接下来我将介绍C语言当中的数据,数据在编程当中非常常用,单片机是微型计算机系统,计算机中所谓的计算就是在不断的计算数据,把几个数据从存储器中读出来,或是运算或是判断,再把处理的结果存回到存储器。
我们编程就是告诉单片机三件事:
需要从存储器里读出哪些数据;如何计算,是相加还是相乘;计算结果放到什么地方。
在单片机内部只有存储器(RAM)和运算器(加法器)两个硬件电路。
计算的过程就是从存储器中读出数据,放到运算器中计算,把结果放回存储器。
如此反复无数次,在硬件上就会呈现出我们想要的程序效果。
计算的过程看似简单,却需要很多不同属性的数据参与,有固定数值的常量,也有可变化数值的变量,更有不是数值仅告诉你数值存放在哪里的指针。
在数据值的表达形式上,有方便观看的十进制,有方便计算的十六进制。
而这里所讲的并不是数据的全部,仅算是抛砖引玉吧。
1,常量
常量就是固定的数据值,比如5就一个常量,5不能变成4,也不能代替3,5就是5。
常量是参与计算最多的数据,我们预先知道的、准备用于计算的数据都是常量。
但常量有很多种表达方式,比如12这个数值用十进制表示就是12,用二进制表达是1100,用十六进制表达是0x0C。
数值本身没有变,只是书写的方式变了。
就像苹果也叫Apple一样,我们需要在不同的环境下选择不同的表达方式。
下面就介绍一下常量的不同表示方法。
十进制数
十进制数是我们小学时数学课学过的数值表达方式。
由0到9组成,逢10进位。
本书的页码就是十进制数。
在C语言中没有任何前缀和后缀的数字,都被认为是十进制数表达方式。
如2、10、250等。
二进制数
二进制数虽然在单片机编程中并不常用,但它绝对是所有数值表达的基础。
因为单片机(或者说计算机系统)都是运算和处理二进制数的。
其他表达方式最终都会变成二进制数输入给单片机,单片机的任何输出也同样是二进制数。
二进制数只有0和1这两个数字来表达,它的原则是逢2进位。
为方便理解,我们用二进制数和日常使用的十进制数做一个对比。
在KEIL软件下STM32的c语言编程中不支持二进制书写,需要我们开发者先把二进制转化成十进制或十六进制,再用C语言表示。
如【表1】所示,二进制数中随着数值的变大,位数也会随之增多。
我们常说的8位单片机指的就是单片机一次可以处理的二进制数是8位,即一个字节(一个字节有8个位)。
而我们所学的STM32是32位的,可一次处理32个位,即4个字节的数据。
位数越高,处理速度就较快。
【表1】进制参照
二进制数
十进制数
十六进制数
0
0
0
1
1
1
10
2
2
11
3
3
100
4
4
101
5
5
110
6
6
111
7
7
1000
8
8
1001
9
9
1010
10
A
1011
11
B
1100
12
C
1101
13
D
1110
14
E
1111
15
F
10000
16
10
...
...
...
11111111
255
FF
十六进制数
十六进制数是单片机编程过程中最常用也是最重要的数值表达方式,它用0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F这16个数字和字母组成。
9加1后为A,再加1为B,依此类推,到F后开始进位。
虽然用英文字母表示数值会有一些不习惯,但十六进制数在单片机编程中很常用,必须熟练使用。
在C语言中用前缀“0x”的数字,都被认为是十六进制数的表达方式。
如0x00、0x10、0x1f、0xff等,字母不区分大小写。
你的进制数
了解了上面几种数值表达方式之后,你是否总结了一个规律呢。
二进制数逢2进位,十进制数逢10进位,十六进制位逢16进位,他们都是由0到9十个数字表达,当数字不足就用英文字母补充。
按照这个规律,你就很容易理解8进制数,也可以设计出自己个性的表达方式,如五进制数、二十七进制数等等,进制数只是为了表达或计算方便而公认出的一种表达方式而已。
数值转换
现在我们了解了十进制、二进制、十六进制的表达方式,那么他们之间如何转换呢?
最简单的方法是借助windows10系统中的计算器软件。
在windows系统中点击“开始菜单--附件--计算器”,打开计算器软件界面。
在计算器的菜单栏点击“程序员”或“科学型”,如【图1】所示。
这时界面中就会出现各种进制数的显示区,如【图2】所示。
先选择一个需要转换的进制类型,输入数值后就能在其他显示区看到对应的其他进制数值。
计算器中还有很多辅助功能,可以方便我们完成数据转换和计算。
【图1】点击菜单中的“程序员”
【图2】4种不同进制的显示区
另一种常用的数值转换心算法就是BCD码(也叫8421码),标准意义上的BCD码是用4位二进制数表示一位十六进制数。
后来发现BCD码的换算方法还可以在单片机编程上得到很多应用。
我们以4位二进制数与十六进制数的转换为例,这是BCD码的标准应用。
我们先想象出4位二进制数从左到右依次标上“8、4、2、1”,让4位二进制数与它们相乘,然后把结果相加。
即(0×8)+(0×4)+(0×2)+(0×1),所得的结果就是十六进制数的0到F。
当我们用8位二进制数时,就需要把其中的前4位和后4位分开计算。
例如十六进制数的0x95,转换为BCD码是10010101。
如【表2】所示是各进制与BCD码的对照。
【表2】BCD码参照
十进制
十六进制
BCD码
8
4
2
1
0
0
0
0
0
0
1
1
0
0
0
1
2
2
0
0
1
0
3
3
0
0
1
1
4
4
0
1
0
0
5
5
0
1
0
1
6
6
0
1
1
1
7
7
0
1
1
1
8
8
1
0
0
0
9
9
1
0
0
1
10
A
1
0
1
0
11
B
1
0
1
1
12
C
1
1
0
0
13
D
1
1
0
1
14
E
1
1
1
0
15
F
1
1
1
1
浮点数
以上介绍的常量都是整数,只是用不同的表现方法而已。
在C语言中除了整数之外,还可有小数的表达。
简单的理解,可以把带有小数点的数值称为浮点数,浮点数一般是用十进制来表示。
比如在程序中可以出现“3.4+89.23”这样的浮点数计算,但是浮点数的使用并不是KEIL中编译器原生支持的,一般你都需要使用浮点数库文件才能使用浮点数。
另外,浮点数在计算时与其他常量、变量之间的关系需要很有经验的编程者才能处理好,所以在初学期间我不建议大家学习使用浮点数,这里就会展开讲解了。
有精力的朋友可以找一下相关的教程,但在其实的项目开发中应用并不多。
2,变量
什么是变量?
它是相对于常量而言的,常量是不能赋值、也不能修改的数值,比如250、0x32、1、0,直接用十进制数或十六进制数表达。
而变量就是在程序运行的过程中,数值是不断变化的。
这一刻还是25,下一刻就变成31了。
变量不是一个具体的数值,而是一个空盒子,可以装进各种不同的数据。
这个空盒子有多大?
它可以装入多少数值?
每个数值的范围是多少?
这种对变量的设定叫数据类型。
C语言允许我们用英文字母、数字和“_”(下划线)给变量取一个名字,比如a、x、ABC、abc、a1、DY_a1等。
变量的定义字母是区分大小写的,也就是说ABC和abc是两个不同的变量,从业内人士的习惯来看,大家通常是用小写字母定义变量。
正如我们在网上注册电子信箱一样,变量的开头不能是数字。
已经被使用的函数名和语句不可作为变量名,比如if、for等。
建议大家先用简单的从a到z来定义变量,等熟练编程之后,自然可以游刃有余。
只要给出数据类型和名字就能定义一个变量。
格式是“数据类型 变量名;”。
变量名是字母和数字的组合。
数据类型则要参考【表3】,通过表中的“定义语句”来确定数据类型。
例如我们定义一个无符号整型变量,名字为a,占用了2个字节的空间,数值范围是在0到65535。
那么在程序中的定义语句是“unsignedinta;”。
如果想定义同一个数据类型的多个变量,也可以在一条定义中写入多个变量名,每个变量名用“,”(逗号)隔开。
比如“unsignedinta,b,c;”,就是同时定义了3个无符号整型变量。
另外,变量定义中还能设定初始值,比如“unsignedinta=1,b=0xff,c;”,其中变量a等于1,变量b等于0xff(十六进制数),变量c没有初始值,则默认初始值为0。
在STM32的开发中,数据类型的定义可以简写,例如“unsignedchar”可以简写成“u8”,那么定义变量就简化为“u8 a;”,具体可以参考【表3】。
还有一个“数值溢出”问题需要特别注意,如果定义一个变量“unsignedchari=600”,所定义的数值超出了数据类型的边界,有时初始值定义没有问题,可是在使用变量时也会出现数值溢出。
在编译时,数值溢出一般不会提示出错,但在程序运行时会出现不可预知的问题。
变量在程序结构上又分为函数内部的变量和可以跨函数使用的全局变量,这个部分在后面应用举例的时候再做介绍吧。
【表3】变量的数据类型
数据类型
定义语句
简写
占用空间
数值范围
使用频次
32位无符号变量
unsignedlong
u32
4个字节
0~4294967295
多
16位无符号变量
unsigned short
u16
2个字节
0~65535
极多
8位无符号变量
unsignedchar
u8
1个字节
0~255
极多
易变的32位无符号变量
volatileunsignedlong
vu32
4个字节
0~4294967295
少
易变的16位无符号变量
volatileunsigned short
vu16
2个字节
0~65535
少
易变的8位无符号变量
volatileunsignedchar
vu8
1个字节
0~255
多
只读的32位无符号变量
unsignedlong const
uc32
4个字节
0~4294967295
少
只读的16位无符号变量
unsigned short const
uc16
2个字节
0~65535
少
只读的8位无符号变量
unsignedchar const
uc8
1个字节
0~255
多
32位有符号变量
signedlong
s32
4个字节
-2147483648~2147483647
极少
16位有符号变量
signed short
s16
2个字节
-32768~32767
极少
8位有符号变量
signedchar
s8
1个字节
-128~127
极少
易变的32位有符号变量
volatilesignedlong
vs32
4个字节
-2147483648~2147483647
极少
易变的16位有符号变量
volatilesigned short
vs16
2个字节
-32768~32767
极少
易变的8位有符号变量
volatilesignedchar
vs8
1个字节
-128~127
极少
只读的32位有符号变量
signedlong const
sc32
4个字节
-2147483648~2147483647
极少
只读的16位有符号变量
signed short const
sc16
2个字节
-32768~32767
极少
只读的8位有符号变量
signedchar const
sc8
1个字节
-128~127
极少
位型
bit
1位
0,1
不能使用
浮点型
float
4个字节
±1.176E-38~±3.40E+38(6位)
极少
双精度浮点型
double
8个字节
±1.176E-38~±3.40E+38(10位)
极少
注意事项:
1.变量的定义应该在函数的最前面,定义变量的语句上方不能有其他语句。
2.在单片机开发中通常只会用到无符号数据类型,除非涉及到复杂的浮点计算。
3.在使用for循环等语句不断累加一个变量值时,需要考虑到数据类型范围的溢出问题。
4.在STM32的开发中是没有bit位型定义的。
3,数组
在单片机编程中有时会用到大量数据,假如我们需要30个甚至更多的同一类型的数据,该怎么办呢?
当然你可以单独定义30个变量,但最常用的方法是定义数组。
就好像书店里的图书,只有几本的时候可随便摆放(定义单独的变量),可成百上千本就该放到书架上,把图书按规律放上去,书架就是数组。
数组就是一组数据的集合,数组分为一维数组、二维数组、三维数组和多维数组。
各种数组的基本原理相同,这里仅介绍常用的一维数组和二维数组。
只有一横排而没有上下层,所有的图书都按序号排列在一横行,这种书架就是一维数组。
书架有多层,每一层又都是一维数组,这便是二维数组了。
一维数组定义形式:
“数据类型数组名[数量]={数值1,数值2};”,二维数组定义形式:
”数据类型数组名[列数量][行数量]={数值1,数值2};“。
其中“[]”(中括号)里所填写的是数组中数值的数量,如果空着的话编译器会计算”{}“(大括号)里的实际数值数量。
如果”[]“里写了10,可”{}“中的数值只有6个,编译器会准备10个数值,多出的4个用0补上。
如果”[]“里写了10个,可”{}“中的数值有12个,编译器会忽略最后2个数值。
以上面的规则来看,初学者朋友最好不要在[]里写数量,直接把数值添加到”{}“里就行了。
数组的定义方法和定义变量是一样的,只是数组所要定义的数据量更多。
比如我们要定义一个带有8个变量空间的数组,只要给出数据类型(u8)、数组名(b)、数量(8)就可以了,如【图3】第一行的定义语句所示。
如此定义的数组相当于8个变量,你可以在程序中向数组的8个位置读写数据。
还有一种是定义固定数据的数组,定义后的数组数据是不可改变的,在程序运行时只能读出这些数据而不能修改。
【图3】中第二行就是定义了数据类型为8位无符号(u8)、数组名是t、且是固定数据(const)。
其中只要有关键字“const”就表示为固定内容的数组,这时必须在等号(=)后面给出所有数据的值。
图中的4个数据分别是1、2、3、4。
【图3】中的第三行是定义固定数据的二维数组,方法和第二行的定义基本一致,只是大括号中又嵌套了一组大括号。
数组在程序中的调用也很简单,如【图4】所示是三种调用的举例。
其中第一行是把数据t中第0位的数据写入到变量a中,变量a的类型必须要与数组t一致。
这里需要注意,数据位置的计算是从0开始的,也就是说第0位才是数据的第1个值。
如【图3】所示我们知道数组t的值是1、2、3、4,如想读出数值“1”,那就需要用“a=t[0];”而不是“a=t[1];”。
如【图4】中的第二行是读出二维数组y中第0组第2位置的值,根据【图3】中的定义,我们读到的值是多少呢?
第0组一共有3个值为1、2、3,第2位置的值是3。
因为第0位置的值是1,第1位置的值是2,第2位置的值就是3。
在数组调用时,除了给出明确的位置外,还可以用变量读出数值。
【图4】中第三行的示例就是用变量i来作为读出数值的选择,改变i的值就可以改变读出数组数据的值,这一方法在程序开发当中非常常用。
【图3】定义数组的方法
【图4】数组的调用方法
4,枚举
枚举是一种数据类型,它只包含自定义的特定数据,它是一组有共同特性的数据的集合。
和数组很像,但也不太一样。
常见的枚举有四季(春、夏、秋、冬),星期,颜色,音阶等。
这么说很难理解,让我们举一个例子吧。
我们每个人都有手机,手机中的通讯录就是一个枚举。
手机通讯录中都会保存着亲朋好友的电话号码,这些号码本身只是一组13位数据。
13位的数字组合可以有万亿个,组合成可以拨通的电话号码也有几亿个。
但我们只需要通讯录中的几十、几百个,这就是在广泛的数据范围之中提取需要的一小部分,我们的选择永远不会超出这个通讯录的范围,在需要限制数据范围的应用中非常好用,这是枚举最大的作用。
如【图5】所示是一个枚举的例子,通讯录中存放着我们需要的电话号码,每个号码都有一个名称,当我们打电话给“小张”时就是使用名称关联的电话号码,最终拨出的是电话号码,数据本身。
这就是枚举的基本概念。
在C语言编程中,枚举并不常用,大部分开发者更喜欢定义范围更广泛的变量来代替枚举。
定义枚举的方法是使用关键字enum,格式是“enum 枚举名{ 标识符=整型常数,标识符=整型常数} 枚举变量;",如【图6】所示。
其中标识符是枚举中的一个成员,枚举的大括号里允许多个标识符。
在枚举中你必须给每一个标识符一个数据值。
如果没给标识符赋值,它的值就等于上一个标识符的值加1。
如果第一个标识符没有赋值,则系统默认它的值为0。
给枚举中增加标识符相当于给手机通讯录添加联系人,是确定枚举数据内容的关键一步。
每个枚举的大括号后面都要跟一个“枚举变量”,它是用于调出枚举数据的,类似于通讯录下方的“拨出号码”按钮。
当我们在通讯录中点击小张的名字,小张的电话号码被拨出,这时拨出号码就等于小张的号码。
同样,当某一个标识符赋值给枚举变量时,枚举变量的值就等于这个标识符所关联的数值。
其它程序就可以调用枚举变量,只有改变标识符对枚举变量的赋值才能改变枚举变量的值,且枚举变量的值只能是现有标识符所关联的值。
在数组中最核心的是数据的位置,因为在调用数组中的数据时,数组名右边中括号内的参数决定了取那一个数值。
枚举中最核心的是标识符,不同标识符向枚举变量赋值,得到不同的数据值。
枚举的使用在STM32固件库文件中很常见,例如【图7】中在stm32f10x_gpio.h文件中的枚举定义,这里定义了GPIO接口的多种工作模式,而真正对工作模式起作用的是标识符后面的数值。
使用标识符主要是为了方便我们开发者的理解和使用。
对于枚举不需要会用,只要能看懂就行。
【图5】枚举的例子
【图6】定义枚举的示意
【图7】在stm32f10x_gpio.h文件中的枚举定义
5,结构体
想了解结构体,只要知道结构体与枚举的区别就可以了。
枚举是同一类型数据的集合(一种数据类型),那么结构体就是不同类型数据的集合。
如【图8】所示,枚举是在一个数据类型中只选择一部分需要的数据。
比如定义一个8位无符号变量,它的值可以从0到255,但定义一个枚举可能只用到0到4,所以说枚举是取整个数据类型中的一部分。
而结构体是把多个不同类型的数据集合在一个类型之中。
比如一个结构体中可以包含一个8位无符号变量的全部值(0~255),同时还包含一个16位无符号变量的全部值(0~65535),两种类型合在一起构成了一个结构体。
再来看结构体与数组的区别,如【图9】所示。
在C语言中数组和结构体都是一种数据的集合方式,但数组也是同一种数据类型的集合,在定义数组的时候,数组名左边给出的数据类型限制了数组中全部数据的类型。
而结构体是不同类型的数据集合,结构体中可以包含各种类型的变量,还能包含数组、枚举、指针。
定义结构体需要使用关键字“struct”,格式是“struct 结构体名{ 结构体成员;结构体成员;} 结构体变量;”,此格式与枚举类似。
其中的结构体成员是用户需要添加的各种数据类型。
结构体变量决定了使用结构体中的哪组数据。
如【图11】所示,是一个普通结构体的定义、写入数据、调用数据的方法。
其中定义了3种不同类型的变量a、b、c,结构体变量为x,向结构体成员写入数据的方法是“结构体变量.成员名”。
即“x.a”表示结构体中的成员a,可以直接对其赋值。
调用时也是直接使用“x.a”,只把它视为一个普通变量来操作。
需要注意在写入和调用时,不同结构体成员有不同的数据类型,一定要了解每个成员的类型再操作。
除了普通的结构体定义,还有一种带有typedef前缀的结构体也很常用。
如【图12】所示,带有typedef前缀的结构体被看成一个数据类型定义前缀。
之前我们介绍的u8、u16就是数据类型定义的关键字,在使用typedef struct定义的结构体,结构体变量就变成数据类型关键字。
在【图12】中,结构体变量x不能被当变量看待,而要当成和u8、u16一样的关键字。
“xy;”的意思是定义一个变量y,它的数据类型是x。
然后变量y就可以代表结构体中的数据来操作了。
写入、调用的方法依然相同,只是从x改成了y。
关于结构体的知识细讲起来还有很多,但初学者只要能看懂别人程序中的结构体,不需要自己会运用,这对于我们后面学习编程开发没有什么影响,有兴趣的朋友可自学。
【图8】枚举和结构体的区别
【图9】结构体与数组的区别
【图10】结构体的组成
【图11】普通结构体的定义、写入、调用
【图12】带有typedef前缀的结构体的定义、写入、调用
6,指针
指针也是一种数据类型,它是通过指向数据位置的方式表达数据的。
为了更好的理解指针的概念,我们举一个例子来说明。
如【图13】所示,假如我们有10个盒子,每个盒子里存放着一个数据。
盒子的顺序是固定的,数据是由用户自己存放的。
这个结构有点像数组,如果是数组的话,我们想读出哪个盒子里的数据,只要给出盒子编号就行了。
例如读7号盒子,得到的数据就是40,输入的值是“7”,输出的值是“40”。
那么如果我们用指针方式的话,需要先定义一个“指针变量”,名字是“*P”,这时就会出现一个指针指向最开始的一个盒子,即盒子1。
如果这时我们读出这个指针变量,它的值就是“88”(盒子1中的数据)。
如果我想读盒子7中的值怎么办?
方法是让指针变量向后移动6格,即“*(P+6)”,输出的值为40。
从中我们可以看出指针的本质就是地址。
盒子1到盒子10是按顺序排列的一串地址,指针所保存的就是数据所在的地址。
要知道单片机程序之所以能够运行,也是依靠一个叫PC指针的东西。
我们把程序下载到FLASH空间中,程序代码是按顺序存入在FLASH中的寄存器。
这就形成了FLASH的地址。
单片机运行程序时,就是用PC指针指向程序代码的开始处,读出数据(程序内容)后让PC指针加1,读出下一个地址的数据。
就这样PC指针的值不断加1,程序代码的内容就源源不断的从FLASH读到ARM内核中执行。
而PC指针本身也是一个寄存器,只是这个寄存器比较特殊,寄存器里面的值不是有用的数据,而是一个地址值。
这个地址指向FLASH中的某个寄存器的位置,真正有用的数据存放在那个寄存器里面。
所以当我们定义一个指针变量时,实际是定义了一个寄存器,未来我们将用这个指针寄存器存放某个其他寄存器的地址。
我们可以在指定指针变量时设定这个变量的类型,可以是u8型、u16型、u32型。
【图13】指针的举例
定义指针只比定义变量多了一个“*”(星号),格式为“u8*a;”,其中“u8”是指针的数据类型,这个数据类型不是指针所指向数据的类型,而是指针本身的地址长度。
“*a”代表指针变量,指针变量上存放的不是具体的数据,而是数据所在的地址。
向指针写入数据的方法是“*a=0x30”,这是向指针当前指向的地址中写入数据0x30。
读出数据是“b=*a”,把指针当前指向的空间中读出数据赋值到b。
如果想移动指针指向其他空间,方法是“a=a+6”,意思是让指针地址加6。
注意移动指针的本质是改变指针的地址,所以不加星号。
如果加上星号“*a=*a+6”,那么就不是移动指针位置(改变地址),而
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- C语言基础知识2 第10期 C语言基础知识204校2 10 语言 基础知识 04