- 论坛徽章:
- 0
|
用C语言的curses例函数实现屏幕录入显示的cobol风格
---by melove 2008/4/26
尽管C语言是一门非常强大的编程语言,但在编写应用程序时如何实现
对数据和字符串在录入和显示时的有效控制,一直是一个令人头痛的问题
(别告诉我你用printf,scanf编写实用程序!)。 而略懂COBOL的人都
知道这方面恰恰是它的强项!!
本文利用C语言强大的屏幕管理例函数---curses.h,来实现数据,
字符串录入和显示的COBOL风格做一个初步的探索。废话少说, 先看源
程序吧。
/* term.c */
/* 屏幕初始化
该函数使得屏幕完全处在curses的控制之下 */
void initial()
{
initscr();
cbreak();
nonl();
noecho();
intrflush(stdscr,FALSE);
keypad(stdscr,TRUE);
refresh();
}
/* 将一个指定小数点位数的双精度数, 格式化成带千分号的字符串 */
/* dec为小数点位数 */
char *mdou(double value,int dec)
{
int i,sign;
int len=24; /* 限定字符串的最大长度 */
char *s;
s=(char *)calloc(len+1,sizeof(char));
/* 先置空格串 */
/* 然后,从低位到高位将双精度数格式化成字符串 */
for(i=0;i<len;i++) *(s+i)=' ';
s+=len;
*s='\0';
/* 置符号位 */
if(value<0){sign=1;value*=-1;} else sign=0;
/* 误差纠正 */
value+=0.1e-7;
/* 先格式化小数部分 */
for(i=0;i<dec;i++) value*=10;
for(i=0;i<dec;i++){*--s=(int)fmod(value,10)+'0';value/=10;}
if(dec>0) *--s='.'; /* 加入小数点 */
/* 再格式化整数部分 */
for(i=1;value>=10;value/=10,i++){
*--s=(int)fmod(value,10)+'0';
if(i%3==0) *--s=','; /* 加入千分符 */
}
if((int)value>0) *--s=(int)value+'0'; /* 加入最高位 */
if(sign) *--s='-'; /* 加入符号位 */
return(s);
}
/* 输入一个限制长度的字符串 */
/* min,max分别为最小和最大长度 */
int astr(char *s,int min,int max)
{
int i,k,c;
int lin,col; /* 光标行,列 */
getyx(stdscr,lin,col); /* 获得光标的当前位置 */
while(TRUE){
k=strlen(s);
/* 先显示已有的内容
不足的部分用空格清除 */
mvaddnstr(lin,col,s,max);
for(i=0;i<max-k;i++) addch(' ');
if(k<max) move(lin,col+k);else move(lin,col+max-1);
refresh();
switch((c=getch())){ /* 接受一个字符并判断 */
case '\r':
case '\n':
case KEY_DOWN:
if(k>=min) return(2); else beep();
break;
case KEY_UP:
if(k>=min) return(1); else beep();
break;
case KEY_LEFT:
if(k>=min) return(3); else beep();
break;
case KEY_RIGHT:
if(k>=min) return(4); else beep();
break;
case KEY_BACKSPACE: /* 删除字符,但该键读取可能会有问题 */
case 8: /* 所以直接用ascii码判断 */
case 127:
if(k>0) *(s+k-1)='\0';else beep();
break;
default:
if(k<max){*(s+k)=c;*(s+k+1)='\0';}
else if(k==max){*(s+k-1)=c;beep();}
else beep();
}
}
}
/* 输入一个指定长度和小数点位数的双精度数 */
/* min,max,dec分别为最小,最大位数和小点数位数 */
int adou(double *value,int min,int max,int dec)
{
int i,k,c,len;
int lin,col; /* 光标行,列 */
int Ex=0; /* 结束标志 */
int sign=0; /* 符号标志 */
char *s,*ss;
double p;
/* 置符号位,前把初值转换成只含整数的绝对值 */
if(*value>=0) p=*value;
else {p=*value*-1;sign=1;}
/* 误差纠正 */
p+=0.1e-7;
for(i=0;i<dec;i++) p*=10;
s=(char *)calloc(2*max+1,sizeof(char));
for(i=0;i<max;i++) *(s+i)=' ';
*(s+max)='\0';
ss=s+max;
/* 把初值转换成字符串 */
while(p>=10){*--ss=(int)fmod(p,10)+'0';p/=10;}
if((int)p>0) *--ss=(int)p+'0';
while(*s==' ') s++; /* 舍去前置多余的空格 */
/* 获得光标的当前位置 */
getyx(stdscr,lin,col);
/* 现在可以开始接受输入了 */
while(TRUE){
k=strlen(s);
move(lin,col);
for(i=0;i<max-k-1;i++) addch('_');
/* 打印符号位 */
if(sign==1) addch('-');
else if(k<max) addch('_');
addstr(s);
move(lin,col+max-1); /* 把输入光标定位在最后一个位置 */
refresh();
switch(c=getch()){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if(k<max-sign){*(s+k)=c;*(s+k+1)='\0';}
else if(k=max-sign) *(s+k-1)=c;
else beep();
break;
case '\r':
case '\n':
case KEY_DOWN:
if(k>=min) Ex=2;else beep();
break;
case KEY_UP:
if(k>=min) Ex=1;else beep();
break;
case KEY_LEFT:
if(k>=min) Ex=3;else beep();
break;
case KEY_RIGHT:
if(k>=min) Ex=4;else beep();
break;
case KEY_BACKSPACE: /* 删除字符,但该键读取可能会有问题 */
case 8: /* 所以直接用ascii码判断 */
case 127:
if(k>=1) *(s+k-1)='\0';else beep();
break;
case '-':
if(sign==1) sign=0;
else if(k<max) sign=1; /* 如果位置已满就不接受负号键 */
else beep();
break;
case '+':
sign=0;
break;
/* default:
beep(); */
}
/* 结束前处理 */
if(Ex!=0){
p=atof(s); /* 把字符串转换成双精度数 */
for(i=0;i<dec;i++) p/=10; /* 重新给出小数部分 */
if(sign==1) *value=p*-1;else *value=p; /* 给出符号位 */
return(Ex);
}
}
}
看完以上源程序,想必你已明白了它的目的。现在就让我们利用它来编写一个简单的测
试程序来看看它的效果如何,以及是否准确吧。
/* test.c */
#include "stdio.h"
#include "math.h"
#include "curses.h"
#include "term.c" /* 自定义函数 */
main(){
int i;
double value=0;
char *s;
/* 空间申请 */
s=(char *)calloc(50,sizeof(char));
initial(); /* 显幕初始化 */
while(value!=9){
/* 输入一个12位长,其中3位是小数的双精度数 */
move(24,10);i=adou(&value,0,12,3);
/* 打印输入的数值和函数返回值 */
mvprintw(22,10,"%lf %d",value,i);
/* 删除光标后的本行字符 */
clrtoeol();
/* 然后把它转换成带千分号的字符串,并存到s中 */
strcpy(s,mdou(value,3));
attron(A_REVERSE); /* 反白 */
/* 以s为初值,输入一个最小长度为2,最大长度为40的串 */
move(20,10);astr(s,2,40);
attroff(A_REVERSE);
}
endwin();
}
本文的测试环境为SCO OpenServer 5.0.5的自带开发系统,编译命令:
cc test.c -lc -lm -lcurses -o test
现在你该可以运行test了吧。
别急,你还得注意以下几个问题:
1、double类型的数据,有效位最多为16位,超过这个长度是不可靠的!
2、程序中之所以要进行误差纠正,是因为内存中的实际数值总比你要的小那么一丁点,
这不是程序或算法的问题,这是double本身的问题。
3、结束录入不仅可以是回车,还可以用上下左右方向键,并把它做为函数的返回值,
当然你也可以不这样做。
4、删除一个字符时,curses keypad定义的退格或删除键(KEY_BACKSPACE)在某些键盘
的某些环境下会读不到,所以直接用ascii码进行判断,这当然不是好办法,却也是无奈之举。
5、经过长时间测试,录入数值时,小数点位数最好不要超过4位!但我想也够用了。
6、调用initial()后,由于屏幕完全处在curses的控制之下,任何意图跟屏幕打交道的操作都
得使用curses的库函数,如printf,scanf等将不能再被使用!
本文只是初探,如果你觉得有用,则完全可以在这基础上,编写你自己的个性化函数,以供实
际应用!水平有限,如有更好的方法,或有什么错误请及时告知,不胜感激!! |
|