当前位置:网站首页>>电脑技术>>C、C++语言>>我的著作 双击自动滚屏
中西文全屏幕编辑程序的设计和编制(六)

发表日期:2006年1月1日      已经有5379位读者读过此文

                       第六章  字块操作和外部文件的插入

    在上一章中,已经谈到字块用四个整形变量来标识。字块仅仅是编辑过程中辅助用的,在大部分编辑软件中,文本文件存盘时,并不保存字块标志。建立字块操作功能的好处在于能大大地提高编辑工作的效率,因此对于一个好的文字处理软件来说,字块操作功能是不可缺少的。下面介绍一些常用的字块操作功能的实现方法。

                           6.1 字块的定义和取消

6.1.1 字块的定义
    定义字块实质上是设定字块的块首行、列号和块尾行、列号,也就是通过屏幕操作确定ksx、ksy、kwx、kwy 的值。本文实例程序中,用功能键 F7 定义块首,用 F8 定义块尾。这里使用了一个变量 blck 作为块定义的标志,blck=0 为未定义字块,blck=1 表示只定义了块首,blck=2 表示块首、块尾均已定义。各类字块操作时,将首先根据 blck 的值判断是否可以进行这类字块操作。在程序中,还将未定义字块时 ksx、ksy、kwx、kwy 的初值置为 -1,以区别于已定义块后的情况。
    如果要定义一个字块的块首,可先移动光标到拟定义字块的首字节处,按 F7键,调用函数 F7():

int blck=0;                    /* 是否已定义块的标志 */
long ksx=-1;                   /* 块首行号,未定义块时置-1 */
int ksy=-1;                    /* 块首列号,未定义块时置-1 */
long kwx=-1;                   /* 块尾行号,未定义块时置-1 */
int kwy=-1;                    /* 块尾列号,未定义块时置-1 */
........
F7()                           /* 定义块首行、列号 */
{
   ksx=xx;                     /* 块首标志行号等于当前光标行号 */
   ksy=yy;                     /* 块首标志列号等于当前光标列号 */
   if(blck==0) {               /* 如尚未定义块 */
      blck=1;                  /* 块标志置 1 */
      kwx=ksx;                 /* 块尾标志同块首重合 */
      kwy=ksy;
      disp(ss_x,x);            /* 重显当前行 */
      return;                  /* 返回 */
   }
   if(kwx<ksx||(kwx==ksx && kwy<=ksy)) { /* 如当前光标在原块尾后 */
      kwx=ksx;                 /* 块尾行号移至块首行 */
      kwy=ksy;                 /* 块尾列号移至块首列 */
      blck=1;                  /* 块标志置 1 */
   }
   else blck=2;                /* 否则块标志置 2 */
   disp_t();                   /* 重显一屏 */
}
 
    F7() 运行时,当前光标处的文本行、列号值就分别赋给了全局变量 ksx 和 ksy。F7()考虑了在几种不同情况下按 F7 键的处理。当在未定义块时按 F7 键,置 blck 为 1,并把块尾标志和块首重合,即令块尾的行、列号等于块首的行、列号。此时只要重新显示当前行,将块首后的一个半角或全角字符改为用字块色显示就可以了。第二种情况是已定义了块,当前光标位置在原定义的块尾之后,此时要将块首、块尾都定到当前光标处,blck 置为 1。在其他情况下按 F7,只需将当前光标行、列号赋给 ksx、ksy,并重显当前屏幕,反映重定义块首后的屏幕变化。
    块尾的定义按 F8 键,调用子函数 F8() 完成,其实现过程为:如原未定义块,按 F8 则视同于定义块首,调用 F7() 将当前光标位置定义为块首;如已定义过块,将当前光标处行、列号赋给 kwx、kwy;如当前光标位置在原块首前,将块首也定到当前光标处,blck 置为 1;如当前光标在原块首后,置 blck=2。

F8()                                 /* 定义块尾 */
{
  kwx=xx;
  kwy=yy;                            /* 定块尾坐标 */
  if(blck>=1)  {                     /* 块标志为 1 或 2 */
    if(kwx<ksx||(kwx==ksx && kwy<=ksy)){    /* 如在块首前 */
      ksx=kwx;                       /* 块首坐标同块尾 */
      ksy=kwy;                       /* 块首坐标同块尾 */
      blck=1;                        /* 块标志置 1 */
    }
    else   blck=2;                   /* 块标志置 2 */
    disp_t();                        /* 重显一屏 */
  }
  else  F7();                        /* 如未定义块,同设块首 */
}

6.1.2 取消字块的定义
    如果需要取消字块的定义,按 Shift+F7 或 Shift+F8 键。函数 Shift_F7() 将 ksx、ksy、kwx、kwy 重置为 -1,把 blck 置 0,并重显屏幕,屏幕上字块色的显示就全部被消除了。

Shift_F7()                           /* 清除块定义 */
{
  blck=0;                            /* 块标志置 0 */
  ksx=kwx=-1;                        /* 块标行号置初值 */
  ksy=kwy=-1;                        /* 块标列号置初值 */
  comp_disp();                       /* 重显一屏 */
}

                          6.2 当前光标位置移至块首

    这也是一个快速移动光标的操作,因为涉及到块,所以放在这一章中讲。其操作功能键是 Ctrl+F,调用子函数 Ctrl_F() 来完成。Ctrl_F() 先根据 blck 的值判断是否已定义了块首,如已定义,再判断块首行在当前行之前还是之后,分别调用子函数 upto() 或 dnto()移至块首行,再令 yy=ksy 将光标位置移至块首列。如果根据 blck 的值判断尚未定义块首,则用子函数 write_prompt(1) 在屏幕提示行显示字符串数组 *prom[ ] 中下标为 1 的一句提示:“请先定义块!”。

Ctrl_F(int a)                      /* 光标移至块首 */
{
  if(blck) {                       /* 如块标志为真,移至块首 */
    if(ksx<=xx)  upto(ksx);        /* 如目标行在当前行前,上移至目标行 */
    else dnto(ksx);                /* 如目标行在当前行后,下移至目标行 */
    ser-=yy-ksy;                   /* 计算字序数 */
    yy=ksy;                        /* 至块首列 */
    if(a)   comp_disp();           /* 如参数为 1,计算显示参数,重显当前屏幕 */
  }
  else  write_prompt(1);           /* 提示:请先定义块 */
}

                               6.3 字块的删除

    块的删除和块定义的取消是两个不同的概念,块定义的取消只是把字块的标志去掉,原块的内容并没有去除,而块删除则不同,块删除操作时,字块后的文本和字块前的文本相接,字块内容被实质性地消除了。
    我们用按 Ctrl+D 键,调用函数 Ctrl_D() 来完成删除字块的操作。Ctrl_D() 先判断是否已定义了字块,如果 blck=2,则字块已完整地定义了,就调用删块函数 del_block()进行删除块的操作,否则提示:“请先定义块!”。

Ctrl_D()                        /* 删除块 */
{
  if(blck==2)  {                /* 如已建立块 */
    chg=1;                      /* 文件已修改标志置为真 */
    txx=xx;                     /* 保存行号 */
    tyy=yy;                     /* 保存列号 */
    del_block();                /* 删块操作 */
    Shift_F7();                 /* 清除块定义,重显当前屏幕 */
  }
  else  write_prompt(1);        /* 提示:请先定义块 */
}

del_block()                      /* 删除块 */
{
  ........
  Ctrl_F(0);                     /* 移到块首 */
  if(ksx==kwx)   strcpy(ss[ss_x]+yy,ss[ss_x]+kwy);
                         /* 如块首块尾在同一行,将块尾后的字符串拷至块首起 */
  else  {                        /* 如块首块尾不在同一行 */
    Ctrl_T(0);                   /* 块首行删至行末,参数为 0 时不显示 */
    ........
    xx++;    ss_x++;             /* 至下行 */
    y=0;    yy=0;                /* 至行首 */
    while(xx<kwx)  Ctrl_Y(0);    /* 循环删除块中各行 */
    yy=kwy;                      /* 移光标至块尾列 */
    Ctrl_E(0);                   /* 删至行首 */
    delc();                      /* 本行剩余部分接至块前行末 */
  }
  ........
  mvto(txx);                     /* 重返原坐标 */
  ........
}

    字块的删除可逐行进行,del_block() 先调用 Ctrl_F() 把当前操作位置移至块首,然后分两种情况进行处理: 当块首和块尾在同一行时,用 strcpy() 函数将本行块尾后的字符串拷贝到块首列起,把字块删除掉。当块首和块尾不在同一行时,要分四步进行:
    (1) 先调用 Ctrl_T() 从当前光标处起删至行末,当前行后移一行。
    (2) 循环反复调用 Ctrl_Y() 删除块尾行前的所有字块行。
    (3) 移至块尾处,调用子函数 Ctrl_E() 把块尾行中块尾前的部分删除。
    (4) 此时当前操作位置在行首,前一行是删除了块首后部分的原块首行,调用 delc()将当前行接至上行末,字块即被全部删除了。
    字块删除后还要通过调用 mvto() 将光标移回光标的原始位置,在此前必须计算字块删除后光标原始位置的变化,这是因为,当光标原始位置在字块后或字块中时,随着字块的删除,原始位置的行号、列号都会发生变化。

                             6.4 字块的拷贝

    字块的拷贝也称为字块的复制,是把已经定义的字块复制插入到当前光标处的一种操作。字块的拷贝通过按 Ctrl_K 键调用子函数 Ctrl_K() 完成。在介绍 Ctrl_K() 前,先介绍两个函数 read_block() 和 write_block()。这两个函数和上一节讲到的 del_block() 是为字块操作编写的三个十分重要的子函数,有的函数在外部文件读入、字符串替代等功能子函数编制时也要用到。

6.4.1 把字块读入一个缓冲区
    read_block() 首先申请一块由字符型指针构成的暂存缓冲区 ddd,把已经定义的字块读入这个缓冲区中,ddd 占用内存的字节数的设定,可根据编辑软件的使用环境和使用要求确定。当块首、块尾在同一行时,为 ddd 分配的内存取一行最大长度的字节数,当字块占用不止一行时应考虑以下因素,如果汉字操作系统留给用户的空间较小,为 ddd 分配的内存空间可小一些,反之可设得大一些。本文实例中,为 ddd 设置的缓冲区为 32000 字节。

#define KK 32000                   /* 字块缓冲区 ddd 允许的最大字节数 */
........
int read_block()                   /* 字块读入缓冲区 ddd */
{
   int i,j;
   write_prompt(0);                /* 提示“请稍候...” */
   txx=xx;   tyy=yy;               /* 保存原行列号 */
   Ctrl_F(0);                      /* 移到块首,参数为 0 不显示 */
   if(ksx==kwx) {                  /* 如块首块尾在同一行 */
      j=kwy-ksy;                   /* 计算块串长 */
      ddd=malloc(HC);              /* 给 ddd 分配内存空间 */
      dd=ddd;                      /* 保存 ddd 指针首址 */
      for(i=0;i<j;i++)  *ddd++=ss[ss_x][ksy+i];    /* 字块读入 ddd */
      *ddd++=0;                    /* ddd 以 ''''\0'''' 结尾 */
      vv=i;                        /* 块长字节数 */
      yy+=i;                       /* 移至块尾 */
   }
   else  {                         /* 如块首块尾不在同一行 */
      ddd=malloc(KK);              /* 给 ddd 分配内存空间 */
      dd=ddd;                      /* 保存 ddd 首指针 */
      first=strlen(ss[ss_x])-ksy;  /* 计算块首行中块串长 */
      for(i=0;i<first;i++)
         *ddd++=ss[ss_x][ksy+i];   /* 首行中块串读入 ddd */
      vv=first;                    /* 读入 ddd 的字节数 */
      xx++;     ss_x++;            /* 至下行 */
      yy=0;                        /* 至行首 */
      for(;;)  {
         tj();                     /* 如当前行在缓冲区后半部,写 Q3 行到 fp1,
                                  数组行数不足数组一半,从 fp1 或 fp2 补充之 */
         if(kwx-xx<=ss_max-ss_x) { /* 如块尾在当前编辑数组中 */
            while(xx<kwx)          /* 为逐行读入建的循环 */
               if(hb())  return 1; /* 计算一行字节数,读入 ddd,如块太大返回 1 */
            break;                 /* 退出 for 循环 */
         }
         else                      /* 如块尾不在编辑数组中 */
            while(ss_x<ss_max)     /* 为数组中的字块部分读入 ddd 建的循环 */
               if(hb())  return 1; /* 计算本行字节数,读入 ddd,如块太大返回1 */
      }
      for(i=0;i<kwy;i++)  *ddd++=ss[ss_x][i];  /* 读入块末行的块字符串 */
      vv+=i;                       /* 读入字块字节数计数 */
      yy=kwy;                      /* 到块尾 */
      *ddd=0;                      /* ddd 以 ''''\0'''' 结尾 */
   }
   ser+=vv;                        /* 计算块尾处字序数 */
   ddd=dd;                         /* 返回指针头 */
   clear_prompt();                 /* 清提示区 */
   return 0;                       /* 正常读完,返回 0 */
}

    read_block() 先调用 Ctrl_F() 把当前操作位置移至块首,然后根据字块在一行内和不止一行两种情况进行处理。当字块在一行内时,一次读字块到 ddd 中,当字块不止一行时,先读入首行中的字块部分,再把字块中的整行逐行读入 ddd,最后把块尾行中的字块部分读入 ddd。在读入的过程中,还要计算读入 ddd 的字块总字节数,并将首行中字块部分的长度记入全局变量 first,first 用于在 ddd 中的字块拷入某文本行时计算是否会引起行超长溢出的错误。由于在将字块读入 ddd 时,一直用单目运算符移动 ddd 的指针,因此字块读入前必须保存 ddd 的首指针于 dd,字块读入 ddd 后再将指针返回,字块中整行读入 ddd,由函数 hb() 完成。

int hb()                            /* 计算字块中一行的字节数,读入 ddd 中 */
{
    int i=0;
    vv+=strlen(ss[ss_x]);           /* 读入数增加本行字节数 */
    if(vv>=KK-255)   {              /* 如字节数超出 ddd 最大空间 */
       mvto(txx);                   /* 重返原坐标 */
       comp_disp();                 /* 重显原屏幕 */
       write_prompt(2);             /* 提示“块太大!” */
       clear_ques();                /* 清提示区 */
       ddd=dd;                      /* 返回指针头 */
       free(ddd);                   /* 释放 ddd 占用内存空间 */
       return 1;                    /* 返回 1 */
    }
    while(ss[ss_x][i])  *ddd++=ss[ss_x][i++];  /* 读一行至 ddd */
    ss_x++;   xx++;                 /* 至下一行 */
    return 0;                       /* 正常读入,返回 0 */
}

    函数 hb() 除了把一行字块读入 ddd,并累加读入字块字节数外,还设置了一个发现字块过大,将使 ddd 溢出时,停止读块,返回读块前初始状态,并在屏幕提示区提示“块太大!”的出错处理功能,从而及时防止因定义的字块太大,而发生程序运行错乱甚至死机。使操作者能采取适当的补救措施,使编辑工作得以继续进行下去。
    一个好的编辑软件必须对可能产生的错误进行预防,对超出软件允许的极限条件的误操作提供化解的余地。后面我们还要讲到字块拷入后出现行超长的处理,也是属于这类化解错误的处理。

6.4.2 把缓冲区中的块插入当前光标处
    write_block() 函数的功能是把 ddd 中的内容插入当前光标位置:

write_block(long v)                  /* 将 ddd 中内容拷至当前行指定位置 */
{                                    /* 参数 v 为拷入后块尾行号 */
   int i,g;
   char *b;
   b=malloc(HC);                     /* 为 b 分配内存空间 */
   strcpy(b,ss[ss_x]+yy);            /* 将当前行光标后字符串暂放入 b 中 */
   if(ss_max-ss_x>Q3+H3) wfp2();     /* 如编辑数组当前行后较多,存一些到 fp2 */
   for(;;)  {                        /* 建一个无限循环 */
      write_prompt(0);               /* 提示“请稍候...” */
      if((g=v-xx)<QB-1-ss_max) {     /* 如块移入后编辑数组不会溢出 */
         mov(ss_x+1,g);              /* 将目标行后的编辑数组后移 */
         ss_max+=g;                  /* 缓冲区数组已用最大行号增加 */
         fp_rd+=g;                   /* fp 已读出最大行号增加 */
         ttl_x+=g;                   /* 文末行行号增加 */
         while(*ddd)  {              /* *ddd 为真则循环 */
            ss[ss_x][yy]=*ddd++;     /* ddd 中字符读入当前行 yy 处起 */
            ser++;                   /* 字序数加 1 */
            if(ss[ss_x][yy++]==0x0A) {  /* 如读到一换行符后 */
               ss[ss_x][yy]=0;       /* 串尾加 ''''\0'''' 定界 */
               ss_x++;   xx++;       /* 下移一行 */
               yy=0;                 /* 至行首列 */
            }
         }
         strcpy(ss[ss_x]+yy,b);      /* b 中字串接到行尾 */
         break;                      /* 跳出 for 循环 */
      }
      else  {                        /* 如缓冲区数组装不下 */
         if(ss_x>Q1)  wfp1();        /* 如当前行在后半数组,写一部分到 fp1 */
         else {                      /* 如当前行在前半数组 */
            mov(ss_x+1,(g=QB-1-ss_max));    /* 将当前行后的编辑数组后移 */
            ss_max=QB-1;             /* 计算数组实用最大行行号 */
            for(i=0;i<g;i++) {       /* ddd 中字块读入编辑数组共 g 行 */
               while(*ddd)  {        /* *ddd 为真则循环 */
                  ss[ss_x][yy]=*ddd++;    /* ddd中内容读入数组行当前列 */
                  ser++;             /* 字序号加 1 */
                  if(ss[ss_x][yy++]==0x0A)  {
                     ss[ss_x][yy]=0; /* 以 ''''\0'''' 结束本行 */
                     ss_x++;  xx++;  /* 行号增 1 */
                     yy=0;           /* 至行首列 */
                     fp_rd++;        /* fp 已读出最大行行号加 1 */
                     ttl_x++;        /* 文末行行号加 1 */
                     break;          /* 填完一行,跳出 while 循环 */
                  }
               }
            }
         }
      }
   }
   free(b);                          /* 释放内存 */
   clear_prompt();                   /* 清提示区 */
}

    形参 v 是拷入字块后块尾行的文本行号,由于要把字块插入当前行,就必须将当前行从当前光标处一分为二,write_block() 先把该行后半截放入一个字符型指针 b 中暂时保存起来。考虑到字块插入时编辑数组尽可能有较多的未用空间来容纳它,程序判断如果如内存数组当前行后已占用行较多时,就存一些到 fp2。接着建一个无限循环 for(;;),当判断字块插入后编辑数组不会溢出时,把编辑数组当前行后的各行后移相当于字块的行数,在当前行后让出填入字块所需要的空间,然后在 while(*ddd) 循环中,从当前行的当前列起把字块中的字符依次填入,每遇到一个换行符后,重起一行,直至 *ddd 为假,即字块中的字符全部移入编辑数组,最后把指针 b 中保存的字符串接到行末,字块的插入便告完成,用一个 break 语句跳出 for(;;) 循环。
    如果已定义的字块较大,编辑数组中一次不能全部装下时,如当前行在编辑数组后半部,存一些到临时文件 fp1 中,使当前行在编辑数组中的位置前移,如果当前行已在内存数组前半部,则将编辑数组中当前行后的行尽可能地后移,让出一定的空间,将 ddd 中的字符填入,用这样的方法把字块的一部分插入编辑数组。
    通过 for(;;) 循环,程序继续进行判断和向文本中插入剩余的字块内容,直到把字块全部插入完毕,将 b 中字符串接在其后,跳出循环。

6.4.3 字块拷贝的实现过程
    下面讲解字块拷贝函数 Ctrl_K() 的实现过程,Ctrl_K() 先判断是否已定义了字块,如果已经定义了字块,则调用子函数 read_block() 将字块读入一个新开辟的缓冲区 ddd 中,然后回到原光标所在位置,调用子函数 write_block() 将 ddd 中的内容插入。取消原块的定义, 把新插入的部分定义为字块。在 Ctrl_K() 中要计算块首、块尾行、列号 ksx、ksy、kwx、kwy 的变化。

Ctrl_K()                           /* 拷贝块 */
{
  int j,g;
  long i;
  if(blck==2)  {                   /* 如已定义块 */
    i=ser;                         /* 保存原字序号 */
    if(read_block() || over(string_lingth())) {
                                   /* 读块到 ddd,并检查行是否会超长,溢出退回 */
      ser=i;                       /* 恢复原字序号 */
      return;                      /* 返回 */
    }
    i=kwx-ksx;                     /* 保存原块标记有用参数 */
    j=kwy;
    g=kwy-ksy;
    ser+=tyy-yy;                   /* 计算字序数 */
    mvto(txx);                     /* 重返原坐标,即块拷贝目的位置 */
    kwy=(i==0)?yy+g:j;             /* 计算块坐标变化 */
    ksx=xx;
    ksy=yy;
    kwx=ksx+i;
    write_block(kwx);              /* 将 ddd 中的内容拷到目的位置 */
    chg=1;                         /* 文件已修改标志置为真 */
    comp_disp();                   /* 计算有关参数,显示当前屏幕 */
    ddd=dd;                        /* 指针返回缓冲区头 */
    free(ddd);                     /* 释放 ddd 占用的内存空间 */
  }
  else  write_prompt(1);           /* 提示:请先定义块 */
}

6.4.4 字块拷贝时文本行越界的处理
    由于定义编辑数组时限死了编辑数组每行的最多字符数,因此 Ctrl_K() 中还进行了字块拷入后数组行是否会超长的判断和处理,这是通过函数 over() 进行的。字块插入只有三种情况下可能发生行越界:
    (1) 块首、块尾在同一行,块字符串长和原当前行长相加时,可能超长。
    (2) 块首、块尾不在同一行,原当前行光标前长度加上字块首行长度,可能超长。
    (3) 块首、块尾不在同一行,原当前行光标后长度加上字块末行长度,可能超长。
   因此 over() 仅须对上述三种情况进行判断,当判定会超长时,over() 使当前光标返回原起始位置,释放字块暂存缓冲区占用的内存空间,并在屏幕提示行发出“行超长!”的警告。在 Ctrl_K() 等调用程序中,以当前行的长度作为实参,传递至函数 over(),对应于 over() 中的形参 g。在 over() 中,vv 为字块的长度,全局变量 first 为字块首行长,已在 read_block() 中确定,tyy 为原光标列号,也就是光标前的字符数。txx、tyy 为原光标的行、列号,均已在 read_block() 中被赋值。

int over(int g)                  /* 行超长处理 */
{
  if((kwx==ksx && vv+g>HC-4)||(kwx!=ksx &&(tyy+first>HC-4 || g-tyy+kwy>HC-4))){
    mvto(txx);                   /* 重返原位置 */
    comp_disp();                 /* 重显原屏幕 */
    ddd=dd;                      /* ddd 返回指针头 */
    free(ddd);                   /* 释放内存空间 */
    write_prompt(3);             /* 提示超长 */
    return 1;                    /* 发生超长返回 1 */
  }
  return 0;                      /* 未发生行超长返回 0 */
}

                               6.5 字块的移动

    字块移动和字块拷贝的区别在于,字块拷贝不删除原字块,只是在一个新位置将字块复制一遍,而字块的移动则在把字块复制到新位置的同时,删除了原字块,因此我们感觉上是把字块搬了一个家。了解了字块拷贝功能的实现原理,也就很容易掌握字块移动的实现方法    字块移动通过按组合键 Ctrl_V 调用函数 Ctrl_V() 实现。Ctrl_V() 和 Ctrl_K() 的区别仅在于 Ctrl_V() 在调用 read_block() 把字块读入 ddd ,并判断不会因字块插入而引起行超长后,又调用 del_block() 删除了原定义的字块,然后再将 ddd 中的内容插入光标位置。

Ctrl_V()                        /* 移动块 */
{
  int k,g;
  long j;
  if(blck==2)  {                /* 如已定义块 */
    j=ser;                      /* 保存原字序号 */
    if(read_block() || over(string_lingth())) {
                                /* 读块到 ddd,并检查行是否会超长,溢出退回 */
      ser=j;                    /* 恢复原字序号 */
      return;                   /* 返回 */
    }
    j=kwx-ksx;                  /* 保存原块标记有用参数 */
    k=kwy;
    g=kwy-ksy;
    del_block();                /* 删除原块 */
    kwy=(j==0)?yy+g:k;          /* 计算新位置块标记 */
    ksx=xx;
    ksy=yy;
    kwx=ksx+j;
    write_block(kwx);           /* ddd 中块拷至当前光标处 */
    comp_disp();                /* 计算 m,y,显示本屏 */
    ........
  }
  else  write_prompt(1);        /* 否则,提示“未定义块!” */
}

                               6.6 字块的存盘

    当需要把文件中一部分文本抽出来,构成一个单独的文件时,可以把这部分文本设置成字块,然后按 Ctrl+W 将其存盘。

Ctrl_W()                        /* 字块存盘 */
{
  int k;
  long g;
  if(blck==2)  {                /* 如已定义块 */
    g=ser;                      /* 保存原字序号 */
    write_ques(1);              /* 提问存盘文件名 */
    if(key_string(HH,25,hsz,PROM_COLOR)<=0) {  /* 如为空串或 ESC 键 */
      clear_ques();             /* 清提问区 */
      return;                   /* 退出本功能 */
    }
    if(findfirst(hsz,pt,0)==0){ /* 如当前目录中已有此文件 */
      write_ques(2);            /* 提问是否复盖 */
      if(key_yn(30)<1)  {       /* 输入 Y 或 N,如按 ESC 键或输入 N */
        clear_ques();           /* 清提问区 */
        return;                 /* 退出本功能 */
      }
    }
    fp3=fopen(hsz,"wb+");       /* 以写方式打开字块写盘文件,指针赋给 fp3 */
    if(read_block()) {          /* 读块到 ddd,如失败,跳出开关语句 */
      ser=g;                    /* 恢复原字序号 */
      return;                   /* 退出本功能 */
    }
    write_prompt(0);            /* 提示“请稍候...” */
    fwrite(ddd,sizeof(char),vv,fp3);       /* ddd 中内容写入文件 fp3 */
    fputc(0x1A,fp3);            /* 加文件结束符 */
    fclose(fp3);                /* 关闭文件 fp3 */
    free(ddd);                  /* 释放 ddd 占用的内存 */
    mvto(txx);                  /* 重返原坐标 */
    ........
  }
  else write_prompt(1);         /* 提示:请先定义块 */
}

    Ctrl_W() 函数先给字块存盘的文件输入一个文件名,用 write_ques() 在屏幕提问区提问字块存盘的文件名,用在第 4.1 节讲过的 key_string() 函数输入文件名,然后用 findfiest() 函数在当前目录中查找是否有同名文件。如果 findfirst() 的返回值为真,则表示当前目录中没有同名文件,如果返回 0,表示有同名文件。这时再提问是否覆盖该文件,调用 key_yn() 回答提问,如 key_yn()返回 0,则用 return 语句退出 Ctrl_W(),返回主程序,如返回 1,则继续执行下一步。
    接着,用库函数 fopen() 以写方式打开该文件,如果该文件不存在,则 fopen() 将建立这个文件,若文件存在,则该文件原先的内容将被抹去。fopen() 返回的文件指针放在程序前部用大写的 FILE 定义过的 fp3 中,文件打开后就能进行写盘操作了。
    这里我们又用到了 6.4 节中介绍过的函数 read_block(),用它把字块读入 ddd,同时计算出总共读入的字节数 vv,用 Turbo_C 的库函数 fwrite() 将 ddd 中的 vv 个字节一次写入打开的文件 fp3 中,作为一个完整的文本文件,有必要在文件末尾添加一个文件结束符 0x1A,这可用库函数 fputc() 完成。fwrite() 完成写操作后,文件的读写指针也随之到了文末,接下去就可执行 fputc(),而无需再去人为地移动文件的读写指针。存盘结束后,应该用库函数 fclose() 关闭文件,最后调用函数 mvto() 返回光标的原始位置。
    函数 Ctrl_W() 中用函数 key_yn() 回答是否要覆盖同名文件,这个函数接收的仅为一个字符,而且函数编写时限定除回车、Esc 、Ctrl_C 键外只能输入大小或小写的“Y” 和“N”。函数判断输入的是“Y”,则返回 1,如果输入的是“N”,返回 0。输入时光标通过函数 goto_xy() 定位在输入位置,但在后面第 7.2 节讲述的字符串替代函数中也要用到 key_yn(),那时为了使操作者看到搜索到的字符串位置,光标必须定位在该字符串尾,所以在函数 key_yn() 中根据输入位置 e 进行判断,区别情况进行处理。

int key_yn(int e)                          /* 在 HH 行输入 Y 或 N,e 为列号 */
{
  char sf=''''N'''';                             /* sf 置初值为 N */
  while(1)  {
    write_char(HH,e,sf,PROM_COLOR);        /* 在提问行显示字符 */
    if(e==59) goto_xy(x,y);                /* 替换时,光标定在找到的字符串尾 */
    else goto_xy(HH,e);                    /* 其余情况下,光标定在提问行 */
    cc.ii=bioskey(0);                      /* 读一键值 */
    clear_prompt();                        /* 清提示区 */
    if(cc.ch[0])                           /* 如低位字节不为 0 */
      switch(cc.ch[0])  {                  /* 判断低位字节 */
        case 27:  return -1;               /* 如为 ESC 键,返回 -1 */
        case 3 :  bk();                    /* 如为 Ctrl+C 键,返回 DOS 下 */
        case 13:  if(sf==''''Y'''') return 1;    /* 如为 回车键,输入 Y 返回 1 */
                  else return 0;           /* 否则返回 0 */
        default:
          if(cc.ch[0]==''''y''''||cc.ch[0]==''''n''''||cc.ch[0]==''''Y''''||cc.ch[0]==''''N'''') {
                                           /* 如为 y 或 n */
            sf=_toupper(cc.ch[0]);         /* 变为大写放入 sf */
            write_char(HH,e,sf,PROM_COLOR);/* 显示字符 */
          }
          else write_prompt(5);            /* 否则提示:必须输入 Y 或 N */
      }
  }
}

    库函数 _toupper() 的作用是把小写的英文字符变为大写形式,这个函数的原型在头文件 ctype.h 中。

                              6.7 外部文件的插入

    在文字编辑过程中,有时候需要把一个存在磁盘上的文本文件插入正在编辑的文件中,怎样设计这个功能模块呢 方法很简单,在分析字块操作时,已经分析了字块写入当前光标处的函数 write_block(),如果设想把外部文件读入内存,并将它当作一个字块,就能够调用 write_block() 完成将其插入编辑文本的工作。在 BJ 编辑软件中,外部文件的插入是通过按组合键 Ctrl+R,调用函数 Ctrl_R() 进行的。

Ctrl_R()                                    /* 外部文件读入光标处 */
{
  int i,j,g,v;
  write_ques(3);                            /* 提问外部文件名 */
  if(key_string(HH,23,hsz,PROM_COLOR)<=0) { /* 输入字符串,如为空串或按ESC */
    ........
    return;                                 /* 退出本功能 */
  }
  if((fp3=fopen(hsz,"rb"))==NULL) {         /* 如文件不存在 */
    ........
    write_prompt(6);                        /* 提示文件未找到 */
    return;                                 /* 退出本功能 */
  }
  ........
  g=0;                                      /* 累计读入行数置初值 0 */
  for(;;)  {                                /* 分批读入的循环 */
    ddd=malloc(KK);                         /* 给 ddd 分配内存 */
    dd=ddd;                                 /* 保存 ddd 首指针 */
    j=fread(ddd,sizeof(char),KK,fp3);       /* 读文件 KK 字节至 ddd */
    v=0;                                    /* 本次读入行数置初值 0 */
    while(*ddd) {                           /* 为逐字节判断设的循环 */       
      if(*ddd==0x1A)  {                     /* 以 ''''\0'''' 代替文件结束符 */
        *ddd=0;
        break;                              /* 退出循环 */
      }
      if(*ddd++==0x0A) {                    /* 计算读入行数 */
        ++v;                                /* 本次循环行数加 1 */
        ++g;                                /* 累计读入行数加 1 */
      }
    }
    ddd=dd;                                 /* 恢复指针到 ddd 文件头 */
    v+=xx;                                  /* 计算插入文本后光标所在行号 */
    write_block(v);                         /* ddd 中内容拷入当前位置 */
    ddd=dd;                                 /* 恢复指针到 ddd 文件头 */
    free(ddd);                              /* 释放 ddd 占用的内存 */
    if(j<KK)  break;                        /* 读入字节数小于KK时,文件已读完 */
  }
  ........                                  /* (计算块标位置变化,显示屏幕) */
  fclose(fp3);                              /* 关闭外部文件 */
}

    Ctrl_R() 先通过 write_ques() 提问要读入的外部文件名,用 key_string() 输入文件名,再用库函数 fopen() 用只读方式打开该文件。如果返回值等于 NULL 则在屏幕提示区显示提示“文件未找到!”,并退出本功能,返回主函数。如打开成功,则将文件指针赋于 fp3。
    为了把文本文件读入内存,要建立一个分配为 KK 字节的字符型指针变量 ddd。考虑到外部文件可能很大,不止 KK 字节,因此采取设置一个循环 for(;;),把外部文件分批读入ddd,并插入当前编辑文本的办法来解决。Turbo C 的库函数 fread() 可以一次从文本文件读入较多字节,这里我们指定 fread() 每次从 fp3 读入 KK 字节,返回的实际读入字节数存放在变量 j 中。如 j=KK,表示文件尚未读完,如 j<KK,则文件已读完。因为 fread() 读出后自动将在外部文件中的读指针移至下一次读的起始位置, 所以在编程时,再次调用 fread() 前无须考虑人为地移动读指针。为了准确地计算读入 ddd 的行数,可以通过统计 ddd 中换行符的数目来确定。当文件已读完时,还应将读入 ddd 的文件结束符去掉,以免插入编辑文本后出现错误。
    读入 ddd 的外部文件内容,视同一个字块,借用字块写入函数 write_block() 插入编辑文本的当前光标处。当 j<KK 时,通过 break 语句跳出 for(;;) 循环。
    必须提醒一下的是,由于在读入文件和将 ddd 中的内容写入编辑文本时都使用单目运算符“++”移动 ddd 的指针,因此,在进行下一步操作前必须把 ddd 的指针还原到首址。在用库函数 free() 释放指针变量占用的内存前也必须这样,否则内存空间就可能没有被完全释放,这是十分有害的。如本节中读入很长的外部文件,或在字块操作时多次进行块拷贝或块移动,程序运行时反复给某个指针变量分配内存空间,就会导至可用内存越来越少,最终因内存溢出而造成死机。要使指针复原,可在用 malloc() 给指针变量分配内存后就用另一个指针变量把指针地址保留起来,当需要复原时再把指针指向这个指针变量的地址。


相关专题: 我的著作
专题信息:
  C语言速成(第三章 程序控制语句)(2006-1-1 13:51:28)[9515]
  C语言速成(第七章 联合、枚举和自定义数据类型)(2006-1-1 13:51:46)[10217]
  C语言速成(第六章 结构)(2006-1-1 13:52:05)[8907]
  C语言速成(第五章 指针)(2006-1-1 17:28:54)[4666]
  C语言速成(第四章 数组)(2006-1-1 13:52:42)[4811]

相关信息:
 没有相关信息
  打印本页
设为首页 | 加入收藏 | 联系我们 | 管理入口
皖ICP备05018956号
Copyright © 2003 J.W.SHEN All Rights Reserved
后台管理系统 V1.0 制作
皖公网安备 34018102340322号