0.目錄
1.要求
2.開啟、關閉回顯和緩沖
3.顯示大小寫字母和數字
4.實現退格鍵
5.實現光標左右移動
6.實現Del鍵刪除整行
7.總代碼
1.要求
設計完成一個行編輯器:能夠接受用戶輸入,能倒退刪除,插入,移動光標等。
2.開啟、關閉回顯和緩沖
想要實現行編輯器的功能,就得解決兩個問題:
一是在Linux命令行的默認模式下,輸入一個字符就會回顯在屏幕上,但是行編輯器不能讓每個字符都輸出在屏幕上,有些鍵是要作為功能鍵來使用的,所以必須關閉回顯設置,讓我們自己來設計有選擇的輸出字符。
二是在Linux命令行的默認模式下,輸入字符后必須按回車才能把緩沖區字符送到程序那里去執行,所以必須開啟立即響應模式,只要用戶按下了鍵就立刻響應一個功能。
以下是具體代碼:
我把開啟、關閉回顯和緩沖封裝成為了一個函數void fun_set(struct termios *info, char set);
set設置為0就是關閉,設置為1就是打開。
在執行循環代碼之前打開,循環代碼之后關閉,這樣就不會影響terminal的正常使用了。
/* 設計完成一個行編輯器
* 開啟、關閉回顯和緩沖
*/
#include <stdio.h>
#include <termios.h>
#define oops(s, x) { perror(s); exit(x); }
void fun_set(struct termios *info, char set);//設置回顯位,設置緩沖
int main()
{
int c;
struct termios info;
fun_set( &info, 0 );//關掉回顯位,關掉緩沖
while( ( c=getchar() ) != EOF )
{
break;
}
fun_set( &info, 1 );//打開回顯位,打開緩沖
}
void fun_set(struct termios *info, char set)
{
if ( tcgetattr(0, info) == -1 ) /* get attribs */
oops("tcgettattr", 1);
/*set為1,打開回顯位,打開緩沖;set為0,關掉回顯位,關掉緩沖*/
if( set )
{
(*info).c_lflag |= ECHO; /* turn on bit */
(*info).c_lflag &= ICANON; /* turn on bit */
}
else
{
(*info).c_lflag &= ~ECHO; /* turn off bit */
(*info).c_lflag &= ~ICANON; /* turn off bit */
}
if ( tcsetattr(0, TCSANOW, info) == -1 ) /* set attribs */
oops("tcsetattr",2);
}
3.顯示大小寫字母和數字
想要顯示大小寫字母和數字,只需要在while函數中判斷一下是不是大小寫字母和數字,是的話就輸出在屏幕上,不是的話就跳過。
while( ( c=getchar() ) != EOF )
{
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
putchar(c);
}
}
isalnum()函數判斷是不是一個數字或字母,然后通過putchar打印出字符即可。
在這里可以通過一個數組來保存這些輸出的字符,這樣當你在之后想保存到文件中去的時候也會更方便。
char str[30];//保存輸出的字符
int p = 0;//當前位置
while( ( c=getchar() ) != EOF )
{
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
putchar(c);
str[p++] = c;
}
}
4.實現退格鍵
退格鍵的實現其實是非常簡單的。
當你按下退格鍵的時候,程序實際上執行了三條指令:
1.putchar('\b');//將光標左移
2.putchar(' ');//輸出一個空字符
3.putchar('\b');//將光標左移
什么意思呢?其實就是先將光標移動到左邊,然后輸出一個空字符將原來的字符覆蓋掉,這樣就相當於把左邊的字符刪掉了,但是此時光標還停留在空字符的右邊。
比如原來是輸入了HelloWorld:
然后執行putchar('\b');
和putchar(' ');
就變成了:
這時候再執行一個putchar('\b');
:
將退格鍵封裝成一個函數void fun_backspace():
void fun_backspace()
{
//實現退格功能
putchar('\b');
putchar(' ');
putchar('\b');
}
因為退格鍵的ASCII碼為十六進制的0x7f,所以while循環里代碼為:
while( ( c=getchar() ) != EOF )
{
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
putchar(c);
str[p++] = c;
}
else if( c == 0x7f )
{
//退格鍵(user input a backspace)
fun_backspace();
str[p--] = '\0';
}
}
這樣就實現了退格鍵的功能,但是程序還有一個小bug:就是當所有字符都刪完了之后,你還是可以按退格鍵。雖然這時候退格鍵已經沒有刪任何字符了,但是在之后還是會帶來一些小的隱患。
所以為了解決這個問題,我們引入字符數組的長度len,讓len記錄字符數組的長度,當len大於0的時候,可以執行退格鍵。
char str[30];//保存輸出的字符
int p = 0;//當前位置
int len = 0;//總長度
while( ( c=getchar() ) != EOF )
{
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
putchar(c);
str[p++] = c;
len++;
}
else if( c == 0x7f )
{
//退格鍵(user input a backspace)
if( len > 0 )
{
fun_backspace();
str[p--] = ' ';
len--;
}
}
}
可以簡化為:
else if( c == 0x7f && len )
{
//退格鍵(user input a backspace)
fun_backspace();
str[p--] = ' ';
len--;
}
5.實現光標左右移動
因為方向鍵上下左右在實際測試中發現,其實是由三個字符組成的,實現起來需要一些特殊的方法。所以在這里先用'[{'鍵來實現'←'(也就是左鍵),用'→'鍵來實現'->'(也就是右鍵)。
之前實現退格鍵的時候,發現可以使用putchar('\b');
來實現光標左移,這樣也就直接實現了左鍵的功能。
那么如何實現光標右移呢?我想到的辦法是將當前所在位置的值重新輸出一遍。比如說:Hello,此時光標在最左邊的H上,那么我要右移,那就在這個位置上將H再輸出一遍,這樣也就間接實現了光標的右移。
上代碼:
else if ( c == '[' && p )
{
//使用'['進行左移光標
putchar('\b');
p--;
}
else if ( c == ']' && p < len )
{
//使用']'進行右移光標
//將當前位置的值再輸出一遍
putchar(str[p++]);
}
這里用p來代表當前光標所在的位置。
同樣,當光標已經移到最左邊的時候,就不執行左移鍵了;當光標已經移到最右邊的時候,也不執行右移鍵了。
到這里,也就實現了左移右移光標的功能了。
但是!如果就到這里結束了的話,那么程序會有很大的bug!比如說當你移動光標到HelloWorld的W上的時候:
再執行退格鍵,那么就會發現是這樣的:
你會發現刪除了一個字符后整個顯示的字符數組都會亂掉。
如果在這個時候還輸入一些大小寫字母或者數字,你就會發現情況會變得更亂。
那么怎么解決這個問題呢?
那就是分別在顯示字符的地方和實現退格的地方加入一些代碼來實現這些功能。
首先在while循環外面定義int i, j;
來輔助我們工作。然后是處理輸出大小寫字母和數字的函數:
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
//1.將當前位置之后的值依次后移
j = ++len;
while( j-- > p )
str[j] = str[j-1];
str[p] = c;
j = len - p - 1;//光標要移動的距離
//2.從當前位置開始重新輸出數組
while( p < len )
putchar(str[p++]);
//3.將光標移動到之前的位置
while( j-- > 0 && p-- )
putchar('\b');
}
然后是解決中間退格的問題:
else if( c == 0x7f && p )
{
//退格鍵(user input a backspace)
j = len - p;//光標要移動的距離
//1.將當前位置之后的值依次前移
putchar('\b');
while( p < len )
{
str[p-1] = str[p];
putchar(str[p]);
p++;
}
//2.將最后一個元素刪除
putchar(' ');
putchar('\b');
len--;
p--;
//3.將光標移動到之前的位置
while( j-- > 0 && p-- )
putchar('\b');
}
6.實現Del鍵刪除整行
實現Del鍵刪除整行的思路其實很簡單:
首先將光標移動到尾部,然后只要len大於0,就循環執行退格鍵。這樣就能夠將整行刪除了。
PS:Del鍵的ASCII碼為十六進制的0x7e,所以while循環里代碼為:
else if ( c == 0x7e && len )
{
//刪除鍵(Del):刪除整行(user input delete)
//1.從光標處移動到結尾
while( ++p <= len )
putchar(' ');
//2.從結尾往前依次退格
while( --p )
fun_backspace();
//3.len置0
len = 0;
}
7.總代碼
/* 設計完成一個行編輯器
* 能夠接受用戶輸入,能倒退刪除,插入,移動光標等
*/
#include <stdio.h>
#include <termios.h>
#define oops(s, x) { perror(s); exit(x); }
void fun_set(struct termios *info, char set);//設置回顯位,設置緩沖
void fun_backspace();//實現退格功能
int main()
{
int c;
int i, j;
struct termios info;
fun_set( &info, 0 );//關掉回顯位,關掉緩沖
char str[30];//保存輸出的字符
int p = 0;//當前位置
int len = 0;//總長度
while( ( c=getchar() ) != EOF )
{
if( isalnum(c) )
{
//isalnum()函數:如果c是一個數字或字母返回非0值,否則為0
//user input a letter or a number
//1.將當前位置之后的值依次后移
j = ++len;
while( j-- > p )
str[j] = str[j-1];
str[p] = c;
j = len - p - 1;//光標要移動的距離
//2.從當前位置開始重新輸出數組
while( p < len )
putchar(str[p++]);
//3.將光標移動到之前的位置
while( j-- > 0 && p-- )
putchar('\b');
}
else if( c == 0x7f && p )
{
//退格鍵(user input a backspace)
j = len - p;//光標要移動的距離
//1.將當前位置之后的值依次前移
putchar('\b');
while( p < len )
{
str[p-1] = str[p];
putchar(str[p]);
p++;
}
//2.將最后一個元素刪除
putchar(' ');
putchar('\b');
len--;
p--;
//3.將光標移動到之前的位置
while( j-- > 0 && p-- )
putchar('\b');
}
else if ( c == '[' && p )
{
//使用'['進行左移光標
putchar('\b');
p--;
}
else if ( c == ']' && p < len )
{
//使用']'進行右移光標
//將當前位置的值再輸出一遍
putchar(str[p++]);
}
else if ( c == 0x7e && len )
{
//刪除鍵(Del):刪除整行(user input delete)
//1.從光標處移動到結尾
while( ++p <= len )
putchar(' ');
//2.從結尾往前依次退格
while( --p )
fun_backspace();
//3.len置0
len = 0;
}
}
fun_set( &info, 1 );//打開回顯位,打開緩沖
}
void fun_set(struct termios *info, char set)
{
if ( tcgetattr(0, info) == -1 ) /* get attribs */
oops("tcgettattr", 1);
/*set為1,打開回顯位,打開緩沖;set為0,關掉回顯位,關掉緩沖*/
if( set )
{
(*info).c_lflag |= ECHO; /* turn on bit */
(*info).c_lflag &= ICANON; /* turn on bit */
}
else
{
(*info).c_lflag &= ~ECHO; /* turn off bit */
(*info).c_lflag &= ~ICANON; /* turn off bit */
}
if ( tcsetattr(0, TCSANOW, info) == -1 ) /* set attribs */
oops("tcsetattr",2);
}
void fun_backspace()
{
putchar('\b');
putchar(' ');
putchar('\b');
}