注解目录
第二章《c语言的一些“操作”及其深层理解》
一、字符串的实质就是指针
(如何将 35 转为对应的十六进制字符串”0X23”?)
二 、转义符\
(打入字符串内部的“奸细”。)
三、字符串常量的连接
(字符串常量是双面胶,你知道吗?)
四、长字符串的拆分技巧
(GPS 数据帧 NMEA、Shell 命令行和 AT 指令的解析,是长串拆分的典型应用。)
五、巧取数值的各位数码
(玩多位数码管的必有操作。)
六、printf 的实质与使用技巧
(自认为很了解 printf?那你试过向 3 个 UART 打印吗?或者打印到液晶屏上?)
七、关于浮点数的传输
(浮点只是一种假象,看清它的本质。)
八、关于数据的直接操作
(如何快速计算浮点的相反数,乘以-1.0?再想想。)
九、 浮点的四舍五入与比较
(老师说浮点不能直接判等,为什么?)
十、的 出神入化的 for 循环
(for 循环很熟悉了吧?OK,振南出了几道题,来试试。)
十一、 隐藏的死循环
(我们在明处,有时死循环在暗处。)
十二、 看似多余的空循环
(没用的东西?)
十三、 独立执行体
(这个概念 C 语言里没学过?那就对了,我经常用。)
十四、 多用() 无坏处
(万物皆可加括号。)
十五、== 的反向测试
(把==错写成=,能让你调程序调到吐血。)
十六、 赋值操作的实质
(让数学教授困惑半生的 C 语言赋值操作。)
十七、 关于补码
(摊牌了,CPU 其实不会作减法。)
十八、 关于-1
(-1 就是全 F,全 F 就是-1。)
二十、字节快速位逆序
(时间与空间的相互转化--计算机中的相对论)
二十一、关于 volatile
(有些东西不可优化。)
二十二、关于变量互换
(位操作的奇妙。)
二十三、关于 sizeof
(告诉你关于 sizeof 那些少人关注的问题。)
二十四、memcpy 的效率
(小小的函数也有大大的背景)
二十五、[] 的本质
(你以为[]只是数组下标?)
二十六、# 与##( 串化与连接)
(一个不曾出现在 C 语言教材中的知识点)
1
字符串的实质就是指针
长字符串的拆分技巧
很多时候我们需要进行长字符串的拆分。在振南的研发经历中,使用到这种操作的最典型的应用场合有三个。
1.NMEA 协议数据的解析
NMEA 可能很多人不太了解,但是说到GPS肯定大家都很熟悉。当我们从GPS 模块中读取定位信息的时候,数据就是遵循NMEA协议格式的。图 2.2 为一个标准的GPS数据帧。
图 2.2 一个符合 NMEA 协议标准的 GPS 数据帧
整个数据帧采用ASCII编码,它以 $GP作为开始,后面依次排列的是各项参数,参数之间使用,作为分隔。比如 $GPRMC为推荐定位信息,我当时就是使用这一条数据来获取经纬度信息的(当时是 Intel 杯嵌入式邀请赛需要作一个手持 GPS 跟踪器)。这条数据中N后面是纬度,E后面是经度。我们要作的就是将它们从整个数据帧(一个长字符串)中提取出来。所以,这就涉及到了所谓的“长串拆分”。
2.Shell 命令行的命令解析
在很多项目中,我都习惯于基于串口编写一个后台 Shell 系统,可以起到一个基本的调试作用。从而一定程度上减少修改代码和固件烧录的次数。比如,项目中如果涉及DAC电压经常的调整输出,我就会在后台中设计一个命令SetVn,以便随时灵活的操控DAC。随着项目功能的升级,后台命令也会变得开始复杂。比如 SetArg a b c d e f g h.... ,用于同时设置程序中多个关键参数的值;再比如 SetV channel n freq a,设置某通道第n个信号的输出幅值和频率。
这些命令通过PC上的串口助手或调试终端来发送,比如超级终端、 SecureCRT 或 XShell等。程序中从串口接收到命令之后,将其放入内存的缓冲区中,其形式就是一个字符串。命令字以及后面的若干参数之间使用空格来分隔。程序要匹配命令字,并提取参数,以便执行相应的操作。所以,这也涉及长串的拆分。
3.DTU 模块的 AT 指令解析
AT指令其实和NMEA 是一个道理,它们都是一种通信协议格式,只不过AT指令更多使用在网络通信模块中,比如SIM800 、 ESP8266 、 HC06 蓝牙串口等。举个例子,我们想知道网络信号强度,就可以向模块发送” AT+CSQ\r\n ”,模块会返回” +CSQ:29 , 0\r\n ”。CSQ:后面的 29 就是信号强度。它们都是ASCII编码的,也就是一个字符串。我们需要将 29 从其中提取出来。当然,AT 指令也有比较复杂的,字符串会比较长,包含的参数也会比较多。所以,要想使用这些网络模块实现网络通信,就必须实现对 AT 指令的解析。
说了这么多,都是在说长串拆分很重要。根本问题是如何实现它?很多人可能都会想到使用那个分隔字符,比如空格、逗号。然后去一个个数要提取的参数前面有几个分隔字符,然后后将相应位置上的字符组成一个新的短字符串,如图 2.3 所示
图 2.3 通过分隔字符定位要提取的部分
这种方法固然可行,但是略显笨拙。其实对于这种有明显分隔符的长字符串,我们可以采用“打散”或“爆炸”的思想,具体过程是这样的:将长字符串中的所有分隔符全部替换为’\0 ’,即字符串结束符。此时,长字符串就被分解成了在内存中顺序存放的若干个短字符串。如果要取出第 n 个短字符串,可以用这个函数:
char*substr ( char*str , n )
{
unsigned charlen=strlen ( str );
for (; len ﹥ 0 ; len-- ){if ( str [ len-1 ] ==ˈˈ )str [ len-1 ] =0 ;}
for (; n ﹥ 0 ; n-- )
{
str+= ( strlen ( str ) +1 );
}
returnstr ;
}
很多时候我们需要一次性访问长字符串中的多个短字符串,此时振南经常会这样来作:通过一个循环,将长字符串中的所有分隔符替换为’\0 ’,在此过程中将每一个短字符串首字符的位置记录到一个数组中,代码如下:
好,举个例子:我们要提取”abc 1000 50 off 2500 ”中的” abc ”、” 50 ”和” off ”,可以使用上面的函数来实现。