段错误的概念
计算机为了防止某一些程序针对地址的操作,可能会引发系统的崩溃而做出的保护性指令,从而在有问题的代码处发出段错误型号,程序接收到段错误信号后,就会直接结束并显示段错误,核心已转储。
引发段错误的原因:
① 访问了空指针上的值
② 修改了野指针地址上的值
③ 通过指针修改一个常量的值
#include<stdio.h>
//编译通过但是产生段错误的原因
/*
常量指针:指向的地址上的值不一定是常量,所以他可以通过其他普通指针去修改地址上的值。
而指向常量的指针,地址上的值肯定是个常量,没有任何指针可以去改变他,如果改变则段错误
常量指针无量指向哪里,编译器会对该指针指向的数据做上强制保护措施,一旦试图修改,则编译错误
而指向常量的指针,编译器不会得知该指针的指向,所以即使修改了,编译阶段不会发现,但是运行阶段发现错误后,直接段错误
*/
int main(){
int a = 5;
int* pa = NULL;
*pa = a;//① 访问了空指针上的值,段错误
int* pb;
*pb = 15;//② 修改了野指针地址上的值,段错误
char* str = "hello";//指向常量的指针
*str = 'H';//③ 指针修改了一个常量的值,段错误
return 0;
}
常量指针与指针常量
常量指针:
常量指针:他是一个指针,什么样的指针?指向常量地址的指针。这个指针不是常量,所以指针可以随意更改指向,但是指针指向的内容无法改变
注意:常量指针指向的数据不一定是一个常量,只不过不能通过该常量指针修改地址上的值
指针常量:
指针常量:指针本身是一个常量。(指针的全称:指针变量)。指针指向的内容可以改变,但是指针的指向无法改变
常量的表示方式:使用const修饰的数据就会变成常量
常量指针p:const char* p
指针常量p:char* const p
#include<stdio.h>
int main(){
int a = 5;
int b = 10;
const int* pa = &a;//常量指针,指针指向的数据不能改变
//(简单理解就是这个常量指针只有读的功能)
int* const pb = &b;//指针常量,指针的指向不能改变
//(简单理解就是这个指针不能移动,只能固定地指向一块内存)
int* ppa = &a;//一般的指针变量
*ppa = 10;//ppa为一般指针变量,可通过*ppa修改所指向的地址里的数据
//常量指针可以移动,就是数据不能改
pa = &b;
//*pa = 20;//常量指针pa无法通过*pa来修改数据
//指针常量可以改数据,就是不能移动指向
*pb = a;
//pb = &a;//不能移动pb的指向
return 0;
}
输入的合法性
现在给到的所有标准输入函数,都是能够输入任意数据。
很多时候,在拥有选项的情况下,选项外的输入其实是不合法的,这种情况通常使用default来处理。
但是,我们有更好的合法输入方式:
就是说,程序中设定好允许输入的数据的范围,当我输入的数据在这个范围之内,则输出到屏幕上
分析:输出到屏幕上有2个过程
①单纯的输入到屏幕上
②将输出的数据保存到字符数组中
为什么是这两个过程:
因为要做到选择性的数据输出,只能将标准输入流的显示关闭,然后当键盘输入的内容符合设定数据范围,那么通过printf向终端输出刚才输入的内容。为了达到输入一个数据就判断一次是否输入,还需要将标准输入流的缓存关闭。
缓存开启的时候:键盘输入数据,所有输入的数据都在缓存内,只有当输入回车后,缓存内的所有数据才会进入程序进行处理
缓存关闭的时候;键盘输入的任何数据,一旦输入,就会直接进入程序进行处理
既然如此,输入的数据就会变成若干个单一的字符,为了让这些输入的字符形成一个字符串,则需要每一次输入后,将输入的数据存储到字符数组中
以上那么多要求具体形容就是:请输入密码,要求密码只能是英文字母或者数字构成。如果输入?则不显示和不存入数组
如果显示了‘?’却不存入数组,则和用户的直观体验相违背
如果不显示‘?’却存入了数组,则保存的密码是错误
密码隐藏
/*
编写一个输入密码的函数,要求密码只能由英文字母和数组构成,并且在书函数中输出刚才输入的密码
要求输入的密码用*隐藏,然后要求输入密码错误的情况下,可以按backspace回删密码
*/
#include<stdio.h>
#include<unistd.h>
#include<termios.h>
#include<assert.h>
#include<string.h>
//函数功能:从终端获取一个字符,并且返回这个字符
char getch(){
char c=0;
struct termios org_opts;//形容终端的结构体,这个结构体中能够完善的描述终端所有属性
struct termios new_opts;
int res=0;
res=tcgetattr(STDIN_FILENO, &org_opts);
//获取终端所有数据信息后,存入org_opts中
assert(res==0);
memcpy(&new_opts, &org_opts, sizeof(org_opts));
//将org_opts中所有内容拷贝给new_opts
new_opts.c_lflag &=~(ICANON | ECHO | ECHOE | ECHOK | ECHONL | ECHOPRT | ECHOKE | ICRNL);
//将new_opts中关于终端的缓存以及显示全都关闭
tcsetattr(STDIN_FILENO, TCSANOW, &new_opts);
//使用更改后的new_opts去设置标注输入流
c=getchar();
res=tcsetattr(STDIN_FILENO, TCSANOW, &org_opts);
//使用原来的终端信息恢复当前终端
assert(res==0);
return c;
}
void getpswd(char* pswd){
char ch = 0;
int i = 0;
while(1){
ch = getch();
if(ch=='\n'){
pswd[i] = 0;//不接收回车
printf("\n");
break;
}else if((ch>='a'&&ch<='z')||(ch>='A'&&ch<='Z')||(ch>='0'&&ch<='9')){
printf("*");
fflush(stdout);//清空输出缓冲区,并把缓冲区内容输出。
pswd[i] = ch;
i++;
}else if(ch == 127 && i>0){
printf("\b \b");
i--;
}
}
}
int main(){
char pswd[50] = {0};
getpswd(pswd);
printf("%s\n",pswd);
return 0;
}