UTF-8编码数据在命令行终端编织成表格并输出

来源:互联网 发布:淘宝导购怎么做 编辑:程序博客网 时间:2024/06/04 01:31
UTF-8编码数据在命令行终端编织成表格并输出


1.简介       
         我最近在Fadore23操作系统上学习C语言,发现在学习数据结构与算法这方面知识的时候,数据域中常常都只有一个简单类型数据或者是一个字符串,这样纯学算法而不去考虑数据结构,学到最后觉得很空,因为算法的本质是去操作数据。后来突发奇想,想去实现一个类似于mysql一样的数据库,当然实现这样一个数据库对现阶段的我肯定是个遥远梦想,但是为了梦想总是得迈出步伐,而我的第一步就是对单表进行增、删、改、查。
           众所周知,database都是有命令行终端进行操作的(sqlserver除外),那么数据以表格的形式在终端打印就成了我的第一个问题,为了后面的学习,我必须解决这个问题,通过了两天的努力,就有了今天的这篇博文,或许下面的代码效率低,也或许我的设计结构就不对,但是这是我的第一版,实现了自己的需求,后面我有了新的想法,我会不断的完善,或者重写。若您对我的代码有什么好的建议或者意见,请Email联系我。( Email: andensemail@163.com ) 对于您的关注我深表感激。

先上两张图看看^_^~







2.最需要解决的问题

   Linux系统常用的是UTF-8编码,一个UTF-8的汉字在内存占用三个字节,而在终端打印时,它只占用二个字符的位置,那么表格的格式(各字段对齐)成了主要问题。


3.数据结构介绍
               typedef      struct         table {  
                           char *line;//字符串指针
                           struct table *pNext;//后续节点指针
                }      TABLE,    *PTABLE;

          将需要写在表格一行中的数据有规则的组合成一个字符串,放在动态内存中。然后创建一个TABLE节点Tnode,让Tnode.line指向字符串的首地址,而一张表格是由多行字符串组成的,那就用一个以TABLE 为节点的链表表示。 Tnode.pNext指向下一个行字符串。当需要将表格打印在终端时,只要遍历打印这个链表就行。



4.接口函数介绍(   我就写了三个  )
  
     a. 打开一张表格
             PTABLE OpenTable(  char   symble1,   char  symble2, char  *tableName, char   *tableTail, char   **data    );

                           char symble1: 这个符号用于画表格的外边框
                           char symble2: 这个符号用于画表格内行的分格,而列我用的是空白分格
                           char *tableName: 表格的名字
                           char *tableTail: 表格的尾行,用于写一些统计数据,本程序只实现了统计数据的个数,后面有需求时我再完善,统计数据用一个 ‘$’号,写在文本中。
                           chr  **data: 这个就是要插入表格的数据,是一个字符串数组,长度任意,但最后一个字符指针指向  NULL
                            return :          PTABLE数据类型的,是一个TABLE的指针。
                                
             使用:   PTABLE     pHead    =      NULL;//定义一个表的头指针                                           
                            *data[10] = {
                                          "学号 姓名 性别 年龄 年级 班级 语文成绩 数学成绩 英语成绩 总成绩",
                                          "2008 王小晨 男 10 4 丙 95.5 100 98.5 294",
                                          "2009 张胜 男 10 4 丙 94.5 99 97.5 291",
                                          "2010 刘倩 女 10 4 丙 90.5 100 95.5 286",
                                          "2011 Mary 女 10 4 甲 99.5 100 95.5 295",
                                           NULL
                             };// 每个字段间可用 空格,逗号,问号,感叹号格开。
                            pHead    =      OpenTable(      '*'   ,    '-'     ,      "成绩表"    ,     "共计 $ 人"     ,      data       );//本函数的调用


    b.在命令行终端打印表格
            void     PrintTable(     PTABLE pHead     );

                           PTABLE    pHead:     是表指针,表格链表中每个节点都必须是  TABLE    类型的。
                           return          void          此函数返回值为空,我们用的主要是这个函数的副作用遍历调用C语言的   I  / O   库函数,在命令行终端打印。
           使用:PrintTable(   pHead    );

    c. 关闭一张表格
          void      CloseTable( PTABLE pHead );

                            PTABLE    pHead: 是表指针,表格链表中每个节点都必须是  TABLE    类型的。
                            return          void     此函数返回值为空,我们用的主要是这个函数的副作用,依次释放每个节点内动态分配的字符串空间与该节点的动态空间。

           使用:CloseTable(   pHead    );

5.初始化配置文件

          # define MALLOC( type, len ) (type*)malloc( sizeof(type) * len )             /*malloc预定义函数*/
          # define TRUE                  1                                                                                 /* BOOL    真*/
          # define FLASE                0                                                                                 /* BOOL    假*/
          # define PRE                    15                                                                               /* 表格的前置空白数 */
          # define COL                    102                                                                             /* 表格的横向长度 */
          # define DATACOL          11                                                                              /* 表格中最最大字段数 */
 
         
a.   后三个是表格的配置,可以根据自己的需求改变其值,也可以改变OpenTable函数,使这三个配置数据做为参数传入。
          b.   表格没有列数限制,所有数据全部打印,数据量大的时候,需配置好终端。我后面会做一个有列数限制且带上下翻页的,我想这个主要问题在于缓存的设计。



6.代码

a.测试主函数
/****************************************************************************************** *                                                                                        * *                                    测试主函数                                            * *                                                                                        * ******************************************************************************************/# include <stdio.h># include <stdlib.h># include <string.h># include "DrawTable.c"intmain( void ){PTABLE pHead1 = NULL,pHead2 = NULL, pHead3 = NULL;        char *data1[10] = {"学号 姓名 性别 年龄 年级 班级 语文成绩 数学成绩 英语成绩 总成绩","2008 王小晨 男 10 4 丙 95.5 100 98.5 294","2009 张胜 男 10 4 丙 94.5 99 97.5 291","2010 刘倩 女 10 4 丙 90.5 100 95.5 286","2011 Mary 女 10 4 甲 99.5 100 95.5 295",NULL};        char *data2[10] = {"编号 名称 数量 单价 产地","1001011 流量计 1000 4584.3 中国广州","1001012 液位计 1500 3363.6 中国湖南 aabbcc","1001013 压力变送器 1200 758.9 中国上海","1001014 温度变送器 2300 543.2 中国山东",NULL};pHead1 = OpenTable('*','-',"成绩表","共计 $ 人" ,data1 );PrintTable( pHead1 );CloseTable( pHead1 );pHead1 = NULL;pHead2 = OpenTable('*','-',"这是一张产品清单","共计 $ 种" ,data2 );PrintTable( pHead2 );CloseTable( pHead2 );pHead2 = NULL;pHead3 = OpenTable('*','-',"公共信息","共计 $ 种" ,NULL );PrintTable( pHead3 );CloseTable( pHead3 );pHead3 = NULL;return EXIT_SUCCESS;}



b.制表函数库
/************************************************************************************************ *            * *命令行终端上画表格函数库* *            * ************************************************************************************************//* 预处理器可以移到自己的头文件或配置中  * */# define MALLOC( type, len ) (type*)malloc( sizeof(type) * len )# define TRUE 1# define FLASE 0# define PRE 15  /* 表格的前置空白数 */# define COL 102 /* 表格的横向长度 */# define DATACOL 11 /* 表格中最最大字段数 *//****************************************声明*************************************************//*  数据结构     */typedef struct table {char *line;//字符串指针struct table *pNext;//后续节点指针} TABLE,*PTABLE;    /*  接口函数    */PTABLE OpenTable(char symble1, char symble2, char *tableName, char *tableTail, char **data );//打开一张表格void PrintTable( PTABLE pHead );//在stdout上打印一张表格void CloseTable( PTABLE pHead );//关闭一张表格/*  内部画图函数  */static PTABLE CreateTable();//创建一个表格static int DrawNull( PTABLE pHead, char symble ); //以symble字符画一条空心直线static int DrawLine( PTABLE pHead, char symble ); //以symble字符画一条实心直线static int DrawTitle( PTABLE pHead, char const *name, char symble );//画标题栏static int DrawData( PTABLE pHead, char **data, char symble );//填充并统计数据,第一行为导航static int DrawTail( PTABLE pHead, char *tableTail,int totleData, char symble);//画结尾统计行/*  内部功能函数  */static void AppendTable( PTABLE pHead, PTABLE new );//追加一个节点static void CopyData( char *line, char *data, int *dataPos );// 字符串格式化copystatic voidGetDataPos(char *buffer,int *dataPos , int offset );//在数据行中定位每个数据字段static void GetUtf8Length( char const *str, int *cnt );//utf-8编码的字符串统计/***********************************END**********************************************//*********************************接口函数*******************************************//* * Function:  打开一张表格 * symble1:   符号1,用于画表格的边框 * symble2:   符号2,用于画表格中的分割横线 * tableName: 表格名称,用于写入名称栏 * tableTail: 表格统计字符串的格式 * data:      表格中的数据,是一个字符串数组 * return:    返回一个表指针PTABLE,如果打开失败返回NULL * */PTABLEOpenTable( char symble1, char symble2, char *tableName, char *tableTail,  char **data ){PTABLE pHead = NULL;pHead = CreateTable();//初始化一个头节点if( pHead != NULL ){int totalData = 0; //数据行统计器DrawLine( pHead, symble1);                           //画横线DrawTitle( pHead, tableName, symble2);               //写表格名DrawLine( pHead, symble2);                           //画横线totalData = DrawData( pHead, data, symble1 );        //写数据DrawLine( pHead, symble2);                           //画横线DrawTail( pHead, tableTail,totalData, symble2);      //画结尾统计DrawLine( pHead, symble1);                           //画横线}return pHead;}/* * Function: 在标准输出打印表格  * pHead:    表格指针 * return:   void * */voidPrintTable( PTABLE pHead ){//链表拥有头节点,头节点中无数据,故跳过while( pHead = pHead->pNext, pHead != NULL )printf( "%s\n", pHead->line );}/* * Function: 关闭一个表格( 释放所有动态分配的指针 ) * pHead:    表格指针 * return:   void * */voidCloseTable( PTABLE pHead ){PTABLE p;//临时指针指向当前节点,当前节点指针前移,释放临时指针所指节点中所有的动态内存while( p = pHead, p != NULL){pHead = pHead->pNext;free( p->line);p->line = NULL;//拴野指针free( p );p = NULL;//拴野指针}}/***********************************END**********************************************//*********************************内部画图函数*******************************************//*  * Function:创建一个表格  (分配一个头节点)  * return:PTABLE  失败则返回NULL * * */static PTABLECreateTable(){PTABLE pHead;//动态分配一个节点pHead = MALLOC( TABLE , 1 );if( pHead != NULL){//该节点不包涵任何数据pHead->line = NULL;pHead->pNext = NULL;}return pHead;}/* function: 画一条空心直线     if( symbel == '*' )    line = "    *                          *\0"  * pHead:    表格指针. * symble:   制线符号,用symble这个字符为表格画两端. * return:    TRUE 画线成功, FLASE 画线失败,且释放了相应内存. * */static intDrawNull( PTABLE pHead , char symble ){//定义新节点空间并为其分配内存PTABLE new; new = MALLOC( TABLE, 1 );if( new != NULL ){//为新节点分配字符串内存new->line = MALLOC( char, PRE+COL);// PRE 表格前的空格数, COL表格行的长度if( new->line != NULL ){//画空心直线 ( 两头有字符,中间是空格,结尾加上NUL )memset( new->line, 32, PRE+COL );// PRE+COL个字符全部画成空格memset( new->line+PRE, (int)symble, 1 );//第一个位置画上符号memset( new->line+PRE+COL-2, (int)symble, 1 );//倒数第二个位置画上符号memset( new->line+PRE+COL-1, 0 , 1 );//倒数第一个位置写入NUL,标致字符串结束    /*当style == '*'时  行的表达:|           *                               *\0|  *///追加new->pNext = NULL;//这步也可以放到追加函数中去AppendTable( pHead, new );return TRUE;//这是函数唯一正确的出口}//字符串内存分配失败时,释放节点的内存-----------拒绝内存泄漏,拴住野指针free( new );new = NULL; //拴住野指针return FLASE;}return FLASE;}/* * Function: 画一条实心直线  if(symble == '*')    line = "         *********************************\0" * pHead:    表格指针. * symble:   制表符号,用symble这个字符为表格画直线. * return    TRUE 画线成功, FLASE 画线失败,且释放了相应内存. */static intDrawLine( PTABLE pHead, char symble ){//定义新节点并分配内存空间PTABLE new;new = MALLOC( TABLE, 1 );if( new != NULL ){//分配字符指针内存空间new->line = MALLOC( char, PRE+COL );if( new->line != NULL ){//画出字符串memset( new->line, 32, PRE+COL ); //字符串清空memset( new->line+PRE, (int)symble, COL-1 ); //画线memset( new->line+PRE+COL-1, 0, 1 );//加NUL字符//追加节点new->pNext = NULL;AppendTable( pHead, new );return TRUE;}//拒绝内存泄漏,拴住野指针free( new );new=NULL;return FLASE;}return FLASE;/*若有不清楚之处,参考DrawNull() 函数讲解*/}/* * Function: 画标题栏     if( symble == '-') line = "        -            TABLENAME            -\0" * pHead:    表格指针 * name:     标题内容指针 * symble:   表格符号 * return    TRUE 画线成功, FLASE 画线失败,且释放了相应内存. * */static int DrawTitle( PTABLE pHead, char const *name, char symble ){//定义新节点指针并分配动态内存PTABLE new;new = MALLOC( TABLE, 1 );if( new != NULL ){//统计utf8汉字的长度int cnt[5];//定义字符串统计数组memset( cnt, 0, sizeof(int) * 5 );//清空字符串统计数组GetUtf8Length( name, cnt );//统计字符串中utf-8编码字符的个数。/* 一个汉字以utf-8编码占3个字节。 * 一个汉字在终端显示,占2个字符的大小。 * 也就是说,一个汉字在内存中占了3个字符的位置,在终端打印时却只占用2个字符的大小。 * 问题:英文字符串与中文字符,让其文字个数一样,但是在终端显示长度却不一样,汉字短,刚好短汉字的个数个字符。 * PRE+COL+cnt[3], 故分配内存时就要加上被汉字多占的字符数,也就是加上汉字的个数。 * */new->line = MALLOC( char, PRE+COL+cnt[3] );if( new->line != NULL ){//居中定位int len,pos;len = strlen( name );pos = ( COL - len + cnt[3] - 1 ) / 2;//画线memset( new->line, 32, PRE+COL+cnt[3] );memset( new->line+PRE, (int)symble, 1 );memcpy( new->line+PRE+pos, name, len );//把标题字符串复制到,新字符串的居中位置//这里还可以用 strncpy 函数, 有长度了就不要用 strcpy 函数,strcpy函数会复制 NUL字符memset( new->line+PRE+COL+cnt[3] - 2, (int)symble, 1 );memset( new->line+PRE+COL+cnt[3] - 1, 0, 1 );//追加new->pNext = NULL;AppendTable( pHead, new );return TRUE;}//拒绝内存泄漏,拴住野指针free( new );new = NULL;return FLASE;}return FLASE;}/* * Function:添加并统计数据 * if( symble = '*' )   "       PTABLE:   * xxx  xxx  xxxxx  xx  xxx  xxxx *\0" *  * xx   xx   xxx    xxx xx   xx   *\0" *                                        * xxxx x    xxxx   xx  x    xxxx *\0" *. *    . *.  * pHead:   表格指针 * data:    数据,以字符串数组存储的数据。 * symble:  制表符号 * return:  统计的数据数,以行为单位. * */static int DrawData( PTABLE pHead, char **data, char symble ){int totalData = 0; // 数据计数器//用导航计算每个字段的定位点,整张表每条数据的每个字段的定位点都一样int dataPos[DATACOL];//定位点数组, DATACOL为最大字段数char buffer[COL];// strtok会破坏原字符串,所以要用一个缓存来解决这个问题if( data != NULL && *data != NULL){//数据不为空strcpy( buffer, *data );//数据写入缓存GetDataPos( buffer, dataPos, 3 );//获取字段定位点数据dataPos[1] += 1;//第一个字段定位点要存放制表符号,所以定位点得右移,让出这个位置}else{DrawNull( pHead,symble );DrawNull( pHead,symble );DrawTitle( pHead,"您还没有添加数据呢^_^",symble );DrawNull( pHead,symble );DrawNull( pHead,symble );return  totalData;}//循环追加数据行while( data != NULL && *data != NULL ){//定义新节点指针并分配动态内存PTABLE new = NULL;new = MALLOC( TABLE, 1 );if( new != NULL ){//统计汉字数量int cnt[5];memset( cnt, 0, sizeof(int) * 5 );GetUtf8Length( *data, cnt );//为字符串分配动态内存new->line = MALLOC( char, PRE + COL + cnt[3]);//DrawTitle函数中的有详解if( new->line != NULL ){//画数据行memset( new->line, 32, PRE+COL+cnt[3] );memset( new->line+PRE, (int)symble, 1 );strcpy( buffer, *data);// strtok会破坏原字符串,所以要用一个缓存来解决这个问题// 在每个字段定位点上,写入每个字段CopyData( new->line, buffer, dataPos );memset( new->line+PRE+COL+cnt[3]-2, (int)symble, 1 );memset( new->line+PRE+COL+cnt[3]-1, 0, 1 );//追加new->pNext = NULL;AppendTable( pHead, new );data++;//指向下一个字符串指针。totalData++;//统计加1continue;}free( new );new = NULL;continue;}continue;}return --totalData;//导航栏纳入统计}/* Function:  画结尾统计行  if( strcmp( tableTail, "共计 $ 人"))  *                                line = "  *          共计 totalData 人   *\0"; * pHead:     表格指针 * tableTail: 统计风格 * totalData: 数据统计个数 * symble:    制表符号 * return    TRUE 画线成功, FLASE 画线失败,且释放了相应内存. * */static int DrawTail( PTABLE pHead, char *tableTail, int totalData, char symble){//将整数保存为字符串并计算长度char str[5];int numLen;sprintf( str, "%d", totalData );numLen = strlen( str );//以$分解tableTail为2个字符串并计算各长度char pre[100], tail[100];int lenP, lenT;sscanf( tableTail, "%s $ %s", pre, tail );lenP = strlen( pre );lenT = strlen( tail );//tableTail的汉字统计int cnt[5];memset( cnt, 0, sizeof(int) * 5 );GetUtf8Length( tableTail, cnt);//定义新节点指针并分配动态内存PTABLE new;new = MALLOC( TABLE, 1 );if( new != NULL ){//为字符串分配动态内存new->line = MALLOC( char, (PRE+COL+cnt[3]) );if( new->line != NULL){//画统计行memset( new->line, 32, (PRE+COL+cnt[3]) );memset( new->line+PRE,(int)symble, 1 );memcpy( new->line+PRE+COL-18,pre, lenP );memcpy( new->line+PRE+COL-18+lenP+2,str, numLen );memcpy( new->line+PRE+COL-18+lenP+numLen+4,tail, lenT );memset( new->line+PRE+COL+cnt[3]-2,(int)symble, 1 );memset( new->line+PRE+COL+cnt[3]-1,0, 1 );//追加数据new->pNext = NULL;AppendTable( pHead, new );return TRUE;}//拒绝内存泄漏,拴住野指针free( new );return FLASE;}return FLASE;}/**************************************内部功能函数 *****************************************//* Function:追加一个节点  * phead:   表格指针 * new:     新的节点指针 * return   void * */static voidAppendTable( PTABLE pHead , PTABLE new ){//寻找表格的最后一个节点while( pHead->pNext != NULL )pHead = pHead->pNext;pHead->pNext = new;//注: new->pNext = NULL 这向工作在创建new节点时完成。return;}/* * Function: 分配插入位置 * buffer:   导航字符串指针  * dataPos:  统计位置数组指针 * offset:   数据写入时的向右偏移量  如*data 偏移0个字节,* data 偏移1个字节,*  data偏移2个字节....  * return    void * */static voidGetDataPos(char *buffer,int *dataPos, int offset ){//以 token 里的字符分解字符串 buffer, 统计字段数量int i;char *tmp;char token[] = "/ ,!?|-";for( i=0, tmp = strtok( buffer, token ); tmp !=NULL && i < DATACOL-1; tmp = strtok( NULL, token) )i++; dataPos[0] = i;//dataPos[1] 存放字段数量, dataPos[1]存放第一个字段的定位,dataPos[2].....//将ave平分i份,得到每份的长度int ave;ave = COL / i; //数据行的长度平分给i个字段,由于不一点完全整除,可能最后一个会长一点。//所有字段定位写入for( i = 1; i <= dataPos[0]; i++ )dataPos[i] = PRE + ave * (i-1) + offset;//  该字段位置 = 前置空白 + 平均值 * 第几个字段 + 偏移量  return;}/* * Function: 将数据写入表格已分配了好的空间 * *line:    已分配好的字符串空间 * *data:    被写入的数据 * *dataPos: 每行所有字段的定位 * return:   void * */voidCopyData( char *line, char *data, int *dataPos ){//分解数据字符串,将其写入subData字符指针数组char *subData[DATACOL];//字段(字符)数组char token[] = " ,/!?";int i;//字段计数器for( i=0, subData[i] = strtok( data, token); i < dataPos[0] && subData[i] != NULL; subData[i] = strtok(NULL,token) )i++;//subData数组必须以NULL结尾if( subData[i] != NULL )subData[i] = NULL;//字符串指针数组以null结尾//统计本条数据中,每个字段中文的个数,存放在pcnt数组中int pcnt[DATACOL];int cnt[5];memset( pcnt, 0, sizeof(int) * DATACOL );//置0for( i = 1; i <= dataPos[0]; i++ ){// pcnt 与 dataPos 一致,数据从 1 开始写cnt[3] = 0;//汉字计数器清零GetUtf8Length( subData[i-1], cnt );pcnt[i] = cnt[3];}pcnt[0] = 0;//由于pcnt用于加法运算,写上这一行更安全。//数据写入int j, pos, len;for( i = 1; i <= dataPos[0]; i++ ){//数据行中,有了汉字后,计算其实际定位for( j = i - 1, pos = 0; j > 0; j-- )pos +=pcnt[j];//当前字段前面所有字段中汉字的总和    len = strlen( subData[i-1] );strncpy( line+dataPos[i]+pos, subData[i-1], len );//复制数据}return;}/* * Function: utf-8字符串统计函数 * str:      字符串指针  * cnt:      统计数组指针 * return    void * */static void GetUtf8Length( char const *str, int *cnt ){//memset( cnt, 0 ,sizeof(int) * 5 );//我的其它博文中有讲解while( *str != '\0' ){if( *str & 1<<7 ){if( *str & 1<<6 ){if( *str & 1<<5 ){if( *str & 1<<4){cnt[4]++, cnt[0]++, str += 4;/* 11110xxx*/continue;}cnt[3]++, cnt[0]++, str += 3;/* 1110xxxx*/continue;}cnt[2]++, cnt[0]++, str += 2;/* 110xxxxx*/continue;}}cnt[1]++, cnt[0]++, str += 1;/*0xxxxxxx*/continue;}}/********************************************END*****************************************//***********************************解除预定义, 让项目更安全*********************************/# undef MALLOC # undef TURE # undef FLASE # undef PRE # undef COL # undef DATACOL  /**************************************END OF FILE***************************************/



  Writer:  Anden       Email:  andensemail@163.com      Time:  2016.04.04

1 0
原创粉丝点击