1.7 数组、字符串与指针

本书大量案例使用了数组定义,例如,下面的数组DSY_CODE定义了0~9的七段数码管段码表:

        uchar code DSY_CODE[] = {0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,0x80,0x90};

由于在程序运行过程中DSY_CODE的数据保持不变,因此,这里将存储类型设为code,如果将code改为data也不会影响程序的运行,只是在程序运行时数组会被分配到数据RAM,而不是仅占用程序ROM空间,在Small模式下,省略code相当于将程序存储类型设为默认的data类型。

读者在实际设计时,如果要将变化的数组定义在数据RAM中,由于data类型仅允许使用128B,如果编译时提示数据RAM空间不够,可尝试将data改为idata,例如:

        uchar idata Sort_Result[200];

另外,存储类型code、data和idata还可以放到数据类型前面。

字符串类型也在本书大量案例中使用,例如,下面的字节串定义:

        char s[20] = "Current Voltage:";
        char s[20] = {"Current Voltage:"};
        char s[20] = {'R','e','s','u','l','t',':'};

这3种定义是完全相同的,它们都占用20B空间,实际串长为16个字符,最后未明确赋值的4B全部为0x00(即'\0'),在液晶屏上显示这类字符串时,可用以下方法:

     (1)for(i = 0; i < 16; i++){//显示字符s[i]};
     (2)for(i = 0; i < strlen(s1); i++){//显示字符s[i]};
     (3)i = 0; while (s[i++]!= '\0') {//显示字符s[i]};

要注意的是,如果字符串长为16,而字符数据空间也只固定给出了16B,那么上述方法中的后两种就不可靠了,因为最后一个字符后面不一定是字符串结束标志'\0'。

字符串还可以这样定义:

        char s[] = "Current Voltage:";
        char *s = "Current Voltage:";

这两种定义也是相同的,其串长均为16 个字符,所占用的字节空间均为17B,因为字符串末尾被自动附加了结束标志字节0x00(即'\0')。

在已知串长时,上述三种字符串显示方法均可使用,在字符串长未知时可使用上述方法中的后两种,另外,上述显示方法还可以改写成:

     (1)for(i = 0; i < 16; i++){//显示字符*(s+i)};
     (2)for(i = 0; i < strlen(s1); i++){//显示字符*(s+i)};
     (3)i = 0; while (*(s+i)!= '\0') {//显示字符*(s+i);i++;};

在编写C语言程序时,除使用字符数组(字符串)外,还会使用字符串数组,例如:

        char s[][20] = {"Current Voltage:","Counter:     ","TH:    TL:    "};

如果要在液晶上显示“Counter:”这个字符串,可用以下语句实现:

        for(i = 0; i < strlen(s[1]);i++){//显示字符s[1][i]};

在英文字符液晶上显示数值时需要将待显示数据转换为字符串,这时可用此前提到的数据位分解方法,先分解出各位数字,然后加上0x30(即'0')得到对应数字的ASCII编码。另一种更为简单的方法是使用sprintf函数,示例代码如下:

        char Buf[10];
        float x = -123.45;
        sprintf(Buf , "%5.2f" , x);

上述语句运行后,Buf会被以下字节填充:

        0x2D,0x31,0x32,0x33,0x2E,0x34,0x35,0x00,0x00,0x00

这些字节代表字符串"-123.45",该字符串可直接送字符至液晶屏显示。

如果已经有语句:

        char Buf[25] = "Result:           ";
        语句sprintf(Buf + 7 , "%5.2f" , x)会使Buf中的字符串会变为:"Result: -123.45"

读者还可以使用下面的语句得到同样的结果:

        char Buf[25];
        sprintf(Buf , "Result:%5.2f" , x) ;

另外,C语言还提供了与字符串有关的数据转换函数atoi、atol、atof、strtod、strtol、strtoul,读者在程序设计中涉及数据输入/输出及运算与显示时可以恰当使用这些函数。

指针是C语言的重要特色之一,对于语句:

        uchar d[10] = {1,2,3,4,5,6,7,8,9,10};
        uchar *pd = d;

pd指向数组d中的第0个字节,显示数组内容可使用下面的代码:

        for(i = 0;i < 10; i++) {//输出d[i]、*(pd+i)、*pd++ 或 *(d+i)};

但是不能使用下面的代码:

        for(i = 0;i < 10; i++) {//输出*d++};

因为数组名d虽然也是第0个字节的地址,但它不能在运行过程中改变,也正是因为数组名同样也是第0个元素的指针,因此,某些函数定义中的形参为数组,调用函数时给出的实参常为指向同类型数据的指针,反之形参为指针,实参为数组名也很常见。

前面字符串示例中也出现了指针应用,读者同样要能熟练运用它们。

由于8051及其派生系列具有独特结构,Keil C51支持以下两种不同类型的指针。

1)通用指针

上述示例uchar *pd中的pd就是通用指针,指针声明与标准C语言完全一样,其特点是总是用3B来存储指针,第一字节表示存储器类型,第二、三字节分别是指针所指向数据地址的高字节和低字节,这种定义很方便但速度较慢,在所指向的目标空间不明确时普遍使用。

2)存储器指针

这种指针在定义时指明了存储器类型,并且指针总是指向特定的存储器空间(片内数据RAM、片外数据RAM或程序ROM),例如:

        char data *str;
        int xdata *pd;
        ulong code *pul;

由于定义中已经指明了存储器类型,因此,相对于通用指针而言,指针第一字节省略,对于data、bdata、idata存储器类型,指针仅需要1B,因为它们的寻址空间都在256B以内,而code和xdata存储类型则需要2B指针,因为它们的寻址空间最大为64KB。

使用存储器指针比使用通用指针效率高,指针所占空间小,速度更快,在存储器空间明确时,建议使用存储器指针,如果存储器空间不明确则使用通用指针。