首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》18.2 一维字符数组和指针

关灯直达底部

一维字符数组和数值数组有如下两个重要区别。

(1)字符数组需要一个结束符,所以定义长度为n的字符数组能存储的有效字符只有n-1个。

(2)字符数组能作为整体输出。

18.2.1 字符数组的偏移量

【例18.5】要求程序的输出结果如下:


a a b b c c d d e e f f g g h h i ic c d d e e f f g gf fghig ghiabcdefghi  

下面是有错误的程序,找出错误并改正之,使其满足上述输出。


#include <stdio.h>int main ( ){            char *p, a[10]=/"abcdefghi/";            int i;            p = a;            for (i=0; a[i]==/'0/'; i++)                  printf(/"%c %c /",a[i],*(a+i));            printf(/"n/");            p=&a[5];            for (i=-2; i<3; i++)                     printf(/"%c %c /",p[i],*(p+i));            printf(/"n/");            for (i=0; i<2; i++,p++)                     printf(/"%c %sn/",*p,p);            p=a;            printf(/"pn/");           return 0;}  

第1个for语句中有两个错误。字符串的结束符是/'/0/',不是/'0/'。判断要用“!=”,即


for (i=0;  a[i]!=/'/0/';  i++)  

也可以使用i判断,虽然“i<10”也能正确运行,但正确的形式是“i<9”,即


for (i=0;  i<9;  i++)  

从第2个循环语句的对称输出可知p[0]为字符e,应该是a[4]。即将“p=&a[5];”改为


p = &a[4];  

第3个循环语句是从f开始输出一个字符,然后输出从f开始的整体字符串,所以要调整指针的指向。可以增加一句


p = &a[5];  

也可以在for语句中置p的初始值,即修改for循环语句为


for (i=0, ++p;  i<2;  i++, p++)  

最后输出的是a的全部内容,而语句“printf(/"pn/");”是将p作为字符输出,即输出“p”。可以改为


printf( p ); printf( /"n/" );  

或者使用如下的等效语句。


printf(/"%sn/", p);//完整的参考程序#include <stdio.h>int main ( ){            char *p, a[10]=/"abcdefghi/";            int i;            p = a;            for (i=0; a[i]!=/'/0/'; i++)                    printf(/"%c %c /",a[i],*(a+i));            printf(/"n/");            p=&a[4];            for (i=-2; i<3; i++)                   printf(/"%c %c /",p[i],*(p+i));            printf(/"n/");            for (i=0,++p; i<2; i++,p++)                  printf(/"%c %sn/",*p,p);            p=a;            printf(p); printf(/"n/");            return 0;}  

由此可见,字符数组的特点与数值数组的一样,数组下标从0开始,而指针则可正可负。数值数组没有结束符,而字符数组有结束符。这就决定了数值数组不能作为整体输出,而字符数组不仅可以作为整体输出,而且结束符还可以作为编程的依据。

【例18.6】下面程序计算字符串的长度,程序对吗?


 #include <stdio.h> int main ( ) {       char *p, *s;       s = /"abcdefghijklmnopqrstuvwxyz/";      p=s;       while ( *p != /'/0/' )             p++;       printf ( /"%dn/", (p-s+1) );     return 0;}  

循环结束条件是到结束符为止,使用p-s+1的计算是不对的,因为字符的有效长度比数组的少1个。将其改为p-s即可。字符串长度为26个。这正是非对称边界的优点。

18.2.2 字符数组不对称编程综合实例

【例18.7】假设用数组buffer[N]模拟一个缓冲区,将另一个数组a的内容写入缓冲区。使用不对称方法编程,模拟演示使用缓冲区的两种主要情况:一种是分两次写入缓冲区,缓冲区尚没写满;另一种也是分两次写入缓冲区,第1次没写满,第2次的数据大于缓冲区剩余的空间。

【解答】为了便于演示,将缓冲区定义的小一点(N=10)。将接收数据的字符数组定义为a[16](大于缓冲区),以便方便演示。假设设计一个函数bufwrite,用以将长度不等的输入数据送到缓冲区buffer(看做能容纳N个字符的内存)中,当这块内存被“填满”时,就将缓冲区的内容输出。考虑使用如下方法声明缓冲区和定义指针变量。


#define N 10static char buffer[N];static char* bufptr ;  

可以让指针bufptr始终指向缓冲区中最后一个已占用的字符。不过,这里使用“不对称边界”编程,所以让它指向缓冲区中第1个未占用的字符。根据“不对称边界”的惯例,使用语句


*bufptr++ = c;

就把输入字符c放到缓冲区中,然后指针bufptr递增1,又指向缓冲区中第1个未占用的字符。因此,可以用语句


Bufptr = &buffer[0];  

声明缓冲区为空,或者直接写成:


Bufptr = buffer;  

甚至在声明时直接使用如下语句:


static char* bufptr = buffer;  

在任何时候,缓冲区中已存放的字符数都是bufptr-buffer,将这个表达式与N比较,就可以判断缓冲区是否已满。当缓冲区全部“填满”时,表达式bufptr-buffer就等于N,而缓冲区中未被占用的字符数为N-(bufptr-buffer)。假设函数bufwrite初步具有如下形式:


void bufwrite(char *p, int n){     while(-- n > = 0)      {            if(bufptr == &buffer[N])                      flushbuffer();     //输出缓冲区内容并将指针置缓冲区首地址            *bufptr ++ = *p++;          //向缓冲区写入}  

指针变量p指向要写入缓冲区的第1个字符,也就是数组a的首地址。n是一个整数,代表将要写入缓冲区的字符数,也就是数组a的字符数。重复执行表达式“--n>=0”,共循环n次,写入n个字符。

如果n>N,当写入N个字符时,缓冲区已满,调用flushbuffer函数,将缓冲区内容输出并执行bufptr=buffer,将指针指向缓冲区中第1个未占用的字符,以便继续将后续的字符写入缓冲区。比较语句


if(bufptr == &buffer[N])  

中引用了不存在的地址&buffer[N]。虽然缓冲区buffer没有buffer[N]这个元素,但是却可以引用这个元素的地址&buffer[N]。buffer中实际不存在的“溢界”元素的地址位于buffer所占内存之后,这个地址可以用来进行赋值和比较(引用该元素的值则是非法的)。

函数flushbuffer的定义如下所示,其实它的定义也很简单。


void flushbuffer(){    printf(/"%sn/", buffer);          //输出已满缓冲区内容    bufptr = buffer;               //缓冲区满将指针置缓冲区首地址}  

不过,一次移动一个字符太麻烦,可以有更好的办法。例如,如果n<N,可以将n个字符一次连续移入缓冲区。如果2×N>n>N,可以先移动N个字符,输出缓冲区内容后,再移动剩下的字符。其实,库函数memcpy能够一次移动k个字符,这里定义一个自己的函数,目的是在函数里面加入调试信息以方便观察运行过程。


void memcpy1(char *dest,const char *source,int k){       printf(/"source:k=%d,%sn/",k,source);     //调试语句       while(--k >= 0)           *dest++ = *source++;       printf(/"buffer:%sn/",buffer);          //调试语句}  

需要计算一次能移动的次数k。这要根据缓冲区还有多少空间rem来计算。


rem = N - (bufptr - buffer);          //求缓冲区尚有空间大小k = n > rem? rem: n;               //求一次移动的字符数  

一次移动的个数k由缓冲区空间rem和要移动的字符数n决定。如果n<rem,则缓冲区装得下n个字符,即k=n。如果n>rem,则只能移入rem个字符,即k=rem。

这需要重写bufwrite函数。


void bufwrite(char *p, int n){     while(n > 0)  {         int k,rem;         if(bufptr == &buffer[N])          //若缓冲区满,输出缓冲区内容                 flushbuffer();          //并将指针置缓冲区首地址         rem = N - (bufptr - buffer);     //求缓冲区尚有空间大小         k = n > rem? rem: n;          //求一次移动的字符数k         memcpy1(bufptr, p, k);          //一次移动k个字符         bufptr += k;               //将指向缓冲区的指针前移k个字符         n-=k;                    //缓冲区减少k个字符容量         p+=k;                    //将输入字符串的指针前移k个字符     }}  

这里的n就是要写入的字符串的个数,也就是字符串数组a中的字符数目,所以要先计算n值。为了演示连续写入,使用for循环语句即可。


for(i=0;i<2;i++){     scanf(/"%s/",a);     n=strlen(a);                    //字符数     bufwrite(a,n);               //将要写入缓冲区的数组a及字符个数n作为参数 }  

下面给出加入调试信息以便演示操作过程的完整程序。


//注意调试语句#include <stdio.h>#include <string.h>#define N 10static char buffer[N];static char* bufptr = buffer;void memcpy1(char *dest,const char *source,int k){       printf(/"source:%s,k=%dn/",source,k);     //调试语句       while(--k >= 0)           *dest++ = *source++;       printf(/"buffer:%sn/",buffer);          //调试语句}void flushbuffer(){    printf(/"已满:%sn/",buffer);               //输出已满缓冲区的内容    bufptr = buffer;                    //将指针置缓冲区首地址}void bufwrite(char *p,int n){     while(n > 0)     {         int k,rem;         if(bufptr == &buffer[N])               //若缓冲区满,输出缓冲区内容                 flushbuffer();               //并将指针置缓冲区首地址         rem = N - (bufptr - buffer);          //求缓冲区尚有空间大小         k = n > rem? rem: n;               //求一次移动的字符数k         memcpy1(bufptr, p, k);               //一次移动K个字符         bufptr += k;                    //将指向缓冲区的指针前移k个字符         n-=k;                         //减少缓冲区k个字符容量         p+=k;                         //将输入字符串的指针前移k个字符     }}int main(){      char a[16];      int n,i;      for(i=0;i<2;i++){            printf(/"输入字符串:/");            scanf(/"%s/",a);            n=strlen(a);                    //字符数            printf(/"字符数:%d 字符串:%sn/",n,a);     //调试信息            bufwrite(a,n);            printf(/"buffer:%sn/",buffer);          //调试信息      }      return 0;} 

设N=10,第1次输入“qazw”4个字符,第2次输入“erdfc”5个字符,两次共9个,缓冲区尚剩1个字符空间,其内容为两次输入的拼接“qazwerdfc”,运行示范如下。


输入字符串:qazw字符数:4 字符串:qazwsource:qazw,k=4buffer:qazwbuffer:qazw输入字符串:erdfc字符数:5 字符串:erdfcsource:erdfc,k=5buffer:qazwerdfcbuffer:qazwerdfc  

实验满的情况,第1次输入“12345678”8个字符,缓冲区还有2个字符空间。第2次输入“ABC”3个字符,所以只能写入2个。写满缓冲区,调flushbuffer函数输出缓冲区内容“12345678AB”并将指针置缓冲区首地址buffer,然后从头写入最后一个字符C。因为并没有清除内容,所以只是改写缓冲区第1个单元的内容,即将字符1改写为字符C,所以现在缓冲区的内容是“C2345678AB”,但缓冲区还有9个字符空间。可以增加for循环的次数验证这一点。下面是运行示范,注意有一次缓冲区已满信息。


输入字符串:12345678字符数:8 字符串:12345678source:12345678,k=8buffer:12345678buffer:12345678输入字符串:ABC字符数:3 字符串:ABCsource:ABC,k=2buffer:12345678AB已满:12345678ABsource:C,k=1buffer:C2345678ABbuffer:C2345678AB