一个安全的程序在用户输入密码时不应该显示密码本身,而应该回显星号或者点号,例如······或******,这在网页、PC软件、ATM机、POS机上经常看到。但是C语言没有提供类似的功能,控制台上只能原样显示用户输入的字符。
我们完全可以模拟密码输入的效果,请先看下面的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <stdio.h> #include <conio.h> #include <ctype.h> #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); 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; } |
运行结果:
Input password: ********* The password is: 123456789 |
代码中定义了一个函数 getpwd(),它有两个参数:pwd 为保存密码的内存的首地址,pwdlen 为密码的最大长度。
函数通过 while 循环来不断读取用户输入的字符,并逐一对它们进行处理:
- 如果用户按下回车键,表示输入结束了,getch() 将会读取到\r;
- 如果用户按下删除键,表示删除前面的字符,getch() 将会读取到\b;
- 如果用户输入可打印字符,那么就读取该字符并回显星号。
while 循环中之所以使用 getch() 来获取字符,是因为该函数既没有回显也没有缓存,可以立即读取到用户输入的字符,并且不会在屏幕上显示出来。
从循环条件 i<pwdlen 可以看出,当用户输入的密码超过最大长度时跳出循环,结束输入。
用户按下回车键时,getchar() 将读取到\n字符,而 getch() 将读取到\r字符。也就是说,对于不同的字符输入函数,回车键产生的字符不同,这个细节读者要引起注意。
删除密码的思路
密码保存在字符数组中,当用户按下删除键时,不仅要删除前面的星号,还应该删除字符数组中前面的元素。
需要注意的是,数组中的元素在内存中是连续分布的,无法直接删除。上面代码中,我们并没有对数组元素进行任何操作,而是将变量 i 减 1,跳过要删除的字符。这些本该被删除的字符依然留在数组中,它们会被后续输入的字符覆盖掉。
第29行代码中,我们通过printf("\b \b");来删除前面的星号。\b表示退格,也就是光标向后移动一个位置。退格,输出空格,再退格就能删除前面的星号。例如我们输入了密码 123,它在屏幕上显示为:
***_ |
输出一个退格,光标向前移动一个字符的位置,变为:
**<ins>*</ins> |
此时再输出一个空格,星号就会被覆盖掉,变为:
** _ |
虽然星号被空格替换掉了,但是光标也同时向后移动了一个位置,这样光标和星号之间就有了一个字符的间隔,所以还需要再输出一个退格,消除间隔。再输出一个退格后变为:
**_ |
函数体最后一行代码也至关重要,它向字符数组中添加了字符串结束符,决定者密码在何处结束。