# 模拟密码输入 一个安全的程序在用户输入密码时不应该显示密码本身,而应该回显星号或者点号,例如`······`或`******`,这在网页、PC软件、ATM机、POS机上经常看到。但是C语言没有提供类似的功能,控制台上只能原样显示用户输入的字符。 ```c #include #include #include #include #define PWDLEN 20 void getpwd(char *pwd, int pwdlen); int main() { char pwd[PWDLEN + 1]; printf("Input password: "); getpwd(pwd, PWDLEN); printf("The password is: %s\n", pwd); system("pause"); return 0; } /** * 获取用户输入的密码 * @param pwd char* 保存密码的内存的首地址 * @param pwdlen int 密码的最大长度 **/ void getpwd(char *pwd, int pwdlen) { char ch = 0; int i = 0; while (i < pwdlen) { ch = _getch(); if (ch == '\r') { //回车结束输入 printf("\n"); break; } if (ch == '\b' && i > 0) { //按下删除键 i--; printf("\b \b"); } else if (isprint(ch)) { //输入可打印字符 pwd[i] = ch; printf("*"); i++; } } pwd[i] = 0; } ``` 代码中定义了一个函数 getpwd(),它有两个参数:pwd 为保存密码的内存的首地址,pwdlen 为密码的最大长度。 函数通过 while 循环来不断读取用户输入的字符,并逐一对它们进行处理: - 如果用户按下回车键,表示输入结束了,getch() 将会读取到`\r`; - 如果用户按下删除键,表示删除前面的字符,getch() 将会读取到`\b`; - 如果用户输入可打印字符,那么就读取该字符并回显星号。 while 循环中之所以使用 getch() 来获取字符,是因为该函数既没有回显也没有缓存,可以立即读取到用户输入的字符,并且不会在屏幕上显示出来。 从循环条件 i 用户按下回车键时,getchar() 将读取到`\n`字符,而 getch() 将读取到`\r`字符。也就是说,对于不同的字符输入函数,回车键产生的字符不同,这个细节读者要引起注意。 #### 删除密码的思路 密码保存在字符数组中,当用户按下删除键时,不仅要删除前面的星号,还应该删除字符数组中前面的元素。 需要注意的是,数组中的元素在内存中是连续分布的,无法直接删除。上面代码中,我们并没有对数组元素进行任何操作,而是将变量 i 减 1,跳过要删除的字符。这些本该被删除的字符依然留在数组中,它们会被后续输入的字符覆盖掉。 第29行代码中,我们通过`printf("\b \b");`来删除前面的星号。`\b`表示退格,也就是光标向后移动一个位置。退格,输出空格,再退格就能删除前面的星号。例如我们输入了密码 123,它在屏幕上显示为: ``` ***_ ``` 输出一个退格,光标向前移动一个字符的位置,变为: ``` *** ``` 此时再输出一个空格,星号就会被覆盖掉,变为: ``` ** _ ``` 虽然星号被空格替换掉了,但是光标也同时向后移动了一个位置,这样光标和星号之间就有了一个字符的间隔,所以还需要再输出一个退格,消除间隔。再输出一个退格后变为: ``` **_ ``` 函数体最后一行代码也至关重要,它向字符数组中添加了字符串结束符,决定者密码在何处结束。