返回首页 单片机教程(二)

12.5 ​C 语言字符数组和字符指针

常量和符号常量

在程序运行过程中,其值不能被改变的量称之为常量。常量分为不同的类型,有整型常量如1、2、3、100;浮点型常量3.14、0.56、-4.8;字符型常量„a‟、„b‟、„0‟;字符串常量“a”、“abc”、“1234”、“1234abcd”等。

细心的同学会发现,整型和浮点型常量我们直接写的数字,而字符型常量用单引号来表示一个字符,用双引号来表示一个字符串,尤其大家要注意„a‟和“a”是不一样的,这个等会我们要详细介绍。

常量一般有两种表现形式:

  • 直接常量:直接以值的形式表示的常量称之为直接常量。上述举例这些都是直接常量,直接写出来了。
  • 符号常量:用标识符命名的常量称之为符号常量,就是为上面的直接常量再取一个名字。使用符号常量一是方便理解,提高程序可读性,更重要的是方便程序的后续维护,习惯上符号常量我们都用大写字母和下划线来命名。

比如,我们可以把3.14取名为 PI(即π)。再比如,我们上节课的串口程序,我们用的波特率是9600,如果用符号常量来进行提前声明的话,那我们要修改成其它速率的话,就不用在程序中找9600修改了,直接修改声明处就可以了,两种方法举例说明。用 const 声明。比如我们在程序开始位置定义一个符号常量 BAUD。

定义形式是:

    const  类型  符号常量名字=常量值;

    const unsigned int BAUD = 9600;  /*注意结尾有个分号*/

我们就可以在程序中直接把9600改成 BAUD,这样我们如果要改波特率的话,直接在程序开头位置改一下这个值就可以了。用预处理命令 #define 来完成,预处理命令我们先来认识 #define。

定义形式是:

    #define  符号常量名  常量值

    #define  BAUD  9600  /*注意结尾没有分号*/

这样定义以后,只要在程序中出现 BAUD 的话,意思就是完全替代了后边的9600这个数字。

不知大家是否记得,我们之前定义数码管真值表的时候,用了一个 code 关键字。

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};

我们当时说加了 code 之后,这个真值表的数据只能被使用,不能被改变,如果我们直接写 LedChar[0] = 1;这样就错了。实际上 code 这个关键字是51单片机特有的,如果是其它类型的单片机我们只需要写成 const unsigned char LedChar[]={}就可以了,自动保存到 FLASH 里,而51单片机只用 const 而不加 code 的话,这个数组会保存到 RAM 中,而不会保存到 FLASH 中,鉴于此,在51这个体系下,const 反倒变得不那么重要了,它的作用被 code 取代了,这里大家知道这么回事即可。

我们来对各种类型的常量做进一步说明。

整型常量和浮点型常量就没多少可说的了,之前我们应用的都很熟练了,整型直接写数字就是十进制如128,前边 0x 开头的表示是十六进制 0x80,浮点型直接写带小数点的数据就可以了。

字符型常量是由一对单引号括起来的单个字符。它分为两种形式,一种是普通字符,一种是转义字符。

普通字符就是那些我们可以直接书写直接看到的有形的字符,比如阿拉伯数字0~9,英文字符 A~z,以及标点符号等。它们都是 ASCII 码表中的字符,而它们在单片机中都占用一个字节的空间,其值就是对应的 ASCII 码值。比如„a‟的值是97,„A‟的值是65,„0‟的值是48,如果定义一个变量 unsigned char a = „a‟,那么变量 a 的值就是97。

除了上述这些字符之外,还有一些特殊字符,它们一些是无形的,像回车符、换行符这些都是看不到的,还有一些像‟\”这类字符它们已经有特殊用途了,想象一下如果写 '''觉得编译器会怎么去解释呢。针对这些特殊符号,为了可以让它们正常进入到我们的程序代码中,C 语言就规定了转义字符,它是以反斜杠()开头的特定字符序列,让它们来表示这些特殊字符,比如我们用 \n 来代表换行。我们用一个简单表格来说明一下常用的转义字符的意思,如表12-2所示。

表 12-2 常用转义字符及含义

字符形式 含义
\n 换行
\t 横向跳格(相当于 Tab)
\v 竖向跳格
\b 退格
\r 光标移到行首
\|反斜杠字符„\‟
\‟ 单引号字符
\” 双引号字符
\f 走纸换页
\0 空值

表格不需要大家记住,用到了,过来查就可以了。

字符串常量是用双引号括起来的字符序列,一般我们都称之字符串。如“a”、“1234”、“welcome to www.kingst.org”等都是字符串常量。字符串常量在内存中按顺序逐个存储字符串中的字符的 ASCII 码值,并且特别注意,最后还有一个字符„\0‟,„\0‟字符的 ASCII 码值是0,它是字符串结束标志,在写字符串的时候,这个„\0‟是隐藏的,我们看不到,但是实际却是存在的。所以“a”就比„a‟多了一个 „\0‟,“a”的就占了2个字节,而 „a‟只占一个字节。

还有一个地方要注意, 就是字符串中的空格, 也是一个字符,比如 “welcome to www.kingst.org ”一共占了26个字节的空间。其中21个字母,2个„.‟,2个 „ ‟(空格字符)以及一个„\0‟。

字符和字符串数组实例

为了对比字符串、字符数组、常量数组的区别,我们写个了简单的演示程序,定义了4个数组分别是:

unsigned char array1[] = "1-Hello!\r\n";
unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};
unsigned char array3[] = {51, 45, 72, 101, 108, 108, 111, 33, 13, 10};
unsigned char array4[] = "4-Hello!\r\n";

在串口调试助手下,发送十六进制的1、2、3、4,使用字符形式显示的话,会分别往电脑上送这4个数组中对应的那个数组。我们只是在起始位置做了区分,其它均没有区别。大家可以比较一下效果。

此外还要说明一点,数组1和数组4,数组1我们是发完整的字符串,而数组4我们仅仅发送数组中的字符,没有发结束符号。串口调试助手用字符形式显示是没有区别的,但是大家如果改用十六进制显示,大家会发现数组1比数组4多了一个字节„ \0 ‟的 ASCII 值00。

#include <reg52.h>
bit cmdArrived = 0; //命令到达标志,即接收到上位机下发的命令
unsigned char cmdIndex = 0; //命令索引,即与上位机约定好的数组编号
unsigned char cntTxd = 0; //串口发送计数器
unsigned char *ptrTxd; //串口发送指针

unsigned char array1[] = "1-Hello!\r\n";
unsigned char array2[] = {'2', '-', 'H', 'e', 'l', 'l', 'o', '!', '\r', '\n'};
unsigned char array3[] = {51, 45, 72, 101, 108, 108, 111, 33, 13, 10};
unsigned char array4[] = "4-Hello!\r\n";

void ConfigUART(unsigned int baud);
void main(){
    EA = 1; //开总中断
    ConfigUART(9600); //配置波特率为 9600

    while (1){
        if (cmdArrived){
            cmdArrived = 0;
            switch (cmdIndex){
                case 1:
                    ptrTxd = array1; //数组1的首地址赋值给发送指针
                    cntTxd = sizeof(array1); //数组1的长度赋值给发送计数器
                    TI = 1; //手动方式启动发送中断,处理数据发送
                    break;
                case 2:
                    ptrTxd = array2;
                    cntTxd = sizeof(array2);
                    TI = 1;
                    break;
                case 3:
                    ptrTxd = array3;
                    cntTxd = sizeof(array3);
                    TI = 1;
                    break;
                case 4:
                    ptrTxd = array4;
                    cntTxd = sizeof(array4) - 1; //字符串实际长度为数组长度减1
                    TI = 1;
                    break;
                default:
                    break;
            }
        }
    }
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud){
    SCON = 0x50; //配置串口为模式1
    TMOD &= 0x0F; //清零 T1 的控制位
    TMOD |= 0x20; //配置 T1 为模式2
    TH1 = 256 - (11059200/12/32)/baud; //计算 T1 重载值
    TL1 = TH1; //初值等于重载值
    ET1 = 0; //禁止 T1 中断
    ES = 1; //使能串口中断
    TR1 = 1; //启动 T1
}
/* UART 中断服务函数 */
void InterruptUART() interrupt 4{
    if (RI){ //接收到字节
        RI = 0; //清零接收中断标志位
        cmdIndex = SBUF; //接收到的数据保存到命令索引中
        cmdArrived = 1; //设置命令到达标志
    }
    if (TI){ //字节发送完毕
        TI = 0; //清零发送中断标志位
        if (cntTxd > 0){ //有待发送数据时,继续发送后续字节
            SBUF = *ptrTxd; //发出指针指向的数据
            cntTxd--; //发送计数器递减
            ptrTxd++; //发送指针递增
        }
    }
}