目录
什么是C语言
第一个C语言程序:HelloWorld
初识数据类型
初识常量、变量
初始字符串、转义字符、注释
初识选择语句、循环语句
初识函数、数组
初识关键字
定义常量和宏
格式化输入输出
printf()
scanf()
gets、getchar、缓冲区讲解
缓冲区详解
C语言是一门通用计算机编程语言,广泛应用于底层开发。
C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。
尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的C语言程序可以在各种平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。
C语言历史
C语言是从B语言发展而来,B语言是从BCPL发展而来,BCPL是从FORTRAN发展而来的。
BCPL和B都支持指针间接方式,所以C语言也支持指针间接。指针是C语言的灵魂。
C语言还受到了PL/I的影响。还和PDP-II的机器语言有很大的关系。
70年代早期,PL-I 是一种非常重要的底层编程语言,或者叫做系统编程语言。它与汇编语言及机器语言非常相近,所以可以用来做操作系统这样的一些基础的程序。C语言受到PL-I很大的影响,想要去充分表达计算机所使用的那种机器指令,所以在高级语言中,C语言显得是一种比较底层的语言。
1973年3月,第三版的Unix上第一次出现了C语言的编译器。这个编译器可以编译C语言程序
1973年11月,第四版的Unix发布了,这个版本的Unix是完全用C语言重新编写的。
C语言的发展与版本(C语言标准)
C语言主要用于编写什么?
C语言是一种工业语言,用于编写一些基础功能。所以对于C语言来说,我们更注重的是它的开发效率,而不是C语言的学习是不是容易、是不是快乐。所以日常应用很少使用C语言来编写,并且学习的过程中更多也是练习代码,而不是写真实软件。
C语言是一门面向过程的计算机语言。
C语言需要被编译才能运行,编译器主要有:Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C 等等
我们一般使用IDE(继承开发环境进行开发),其中就包含了C语言的编译器。
推荐使用:VS2019/Clion
创建test.c源文件
#include
int main()
{
printf("Hello World!\n");
return 0;
}
详解以上程序
/* C语言程序如何执行?
* C语言是从主函数的第一行开始执行的,所以每个C语言编写的源文件中都需要有mian函数
* main函数是C语言程序的入口,主函数的标准写法中,使用int作为函数的返回类型。
* 一个工程中,可以有多个.c文件,但是多个.c文件中,有且只能有一个main函数。
*
* 主函数的标准写法:
int main()
{
return 0;
}
*/
//printf函数的使用,需要引用头文件studio.h。 #include表示包含,文件名放在<>中
#include
//函数的返回类型 函数名() //int是整数型
int main()
//{}中的是函数体
{
//printf:库函数。用于打印信息。\n表示换行
printf("Hello World!\n");
//return 返回值;
return 0;
}
#include
int main()
{
char c = 'a';
// %d表明后面有一个整数要输出在这个位置上。
//printf("%d\n",100); //打印整数:%d //表示打印整数100
printf("%d\n",c);
//sizeof 是一个关键字,也是一个操作符。用于计算类型或者变量所占空间的大小。
//sizeof的单位是:字节(Byte)。计算机内部存储数据的最小单位:比特位(bit),一个比特位也就是一位二进制
// 1B=8b。
//我们可以看到整形int与长整型int占用的空间大小相同,为什么会这样?
// C语言标准中规定:sizeof(long)>=sizeof(int),并不一定非要sizeof(long)>sizeof(int)
//为什么整型就分了四种? 根据数据所占空间大小,选择类型,可以更好的利用存储空间。
printf("%d\n",sizeof(char)); // 1
printf("%d\n",sizeof(short)); // 2
printf("%d\n",sizeof(int )); // 4
printf("%d\n",sizeof(long)); // 4
printf("%d\n",sizeof(long long)); // 8
printf("%d\n",sizeof(float)); // 4
printf("%d\n",sizeof(double)); // 8
}
变量的定义
//生活中一些值是不变的,一些值是可变的。在C语言中:
//常量:不能改变的量。 变量:可以改变的量
//变量名是一种“标识符”,用于标识该变量,区别与其他变量。
//标识符的规则:标识符只能由字母、数字、下划线组成;并且不能以数字开头。同时,C语言的关键字(或叫保留字)也不能作为标识符。
//在ANSI C标准中,所有的变量定义都需要在代码开头就定义好。
//在C99标准中,变量可以定义在任何位置,需要用到的时候定义就可以。
#include
int main()
{
//所有的变量在使用之前必须定义或声明,所有的变量必须具有确定的数据类型。数据类型表明在变量中存放的是什么样的数据,变量中只能存放指定类型的数据,程序运行过程中也不能改变变量的类型。
//如果定义的变量未初始化,就进行使用,则可能出现不可预料的结果。
//定义变量:
//数据类型 变量名 = 初始值;
//这里的=,是赋值运算符,将右边的值赋给左边。这里为变量赋值,也被叫做变量的初始化。
int age = 20;
double weight = 70.5;
//改变变量的值
age = age + 1;
weight = weight -10 ;
// %d:打印整数型 %f:打印float型 %lf:打印double型
printf("%d\n",age); //21
printf("%lf\n",weight); //60.500000
//同时定义多个变量
int a,b,c;
//同时定义多个变量并且赋值
int d=10,e=230;
}
变量的分类
//变量分为:局部变量、全局变量
#include
//全局变量:{}外部定义的,也叫函数体外定义的。
//全局变量如果没有初始化,则在编译时被初始化为0。如果是一个全局指针变量没有初始化,则被初始化为NULL
int b;
int* pb;
int a = 100;
int main()
{
printf("%d\n",a); //100
//局部变量:{}内部定义的,或被称为是函数内部定义的。
int a = 10;
//当局部变量与全局变量的变量名相同时,局部引用时,优先使用局部变量。
//但是不建议局部变量与全局变量名称相同。
printf("%d\n",a);
printf("%d\n",b);//0
//指针也被初始化为NULL,以%d打印就是0
printf("%d\n",pb);//0
}
变量的使用:求两个整数的和。使用输入函数
//输入两个整数,求和
//printf是输出函数,scanf是输入函数。两个函数中最后面的f是format,意思为格式化的输入输出。
//int age = 18; int num = 0;
//printf("%d",age) 输出整型数据age。 scanf("%d",&num)
//两个函数中,第一个参数都是一个字符串,输出函数中表示:将第二个参数读取到%d的位置进行输出;输入函数中,按照格式,将输入的值读取到变量中。
#include
int main()
{
//int a = 0;
//int b = 0;
//也可以两个变量一起定义。
int a,b;
//也可以输入一个数,回车,输入另一个数;回车运行。
//这里我们在两个%d之间加入了一个空格,我们在控制台按照格式输入,输入一个数,空格,再输入另一个数,回车运行。实际上就是将我们输入的两个数据按照格式读取到我们的两个变量中
scanf("%d %d",&a,&b);
int sum = a + b;
printf("sum = %d",sum);
}
变量的作用域和生命周期
/*
* 作用域:变量的作用域,其实描述的就是变量的有效范围。在什么范围之内是可以被访问的,只要出了这个范围该变量就无法访问了。
* 变量的作用域只要记住一句话:出了大括号就不认识了。在同一个“作用域”中,一个变量只能声明一次(变量名不能重名)。
* 1、局部变量作用域:局部变量所在的范围中,出了其所在范围,该变量就不能使用了。
* 2、全局变量作用域:整个工程中都可以使用。在定义全局变量的文件中可以直接使用;如果是要在该文件外使用其全局变量,则需要声明一下才可以使用。声明语法:extren 全局变量类型 全局变量名
*
* 生命周期:变量的生命周期指的是变量从创建到销毁的指一个时间段。
* 1、局部变量的生命周期:程序执行到该局部变量所在的作用域时,局部变量的生命周期开始;执行完其所在作用域,局部变量生命周期结束。
* 2、全局变量的生命周期:因为全局变量在整个工程中有效,所以其生命周期也就相当于main()函数的生命周期,main函数开始执行(整个程序开始运行),全局变量生命周期开始;main函数执行完(整个程序运行完),全局变量生命周期结束。
*/
#include
int b = 100; //全局变量b
//如果需要访问另一个文件中定义的全局变量,声明一下就可以使用。
extern int c;
int main()
{
printf("%d\n",b);//100
//在这里定义一个变量d。
int d =300;
printf("%d\n",d);//300
{
int a = 10; //局部变量a
printf("%d\n",a); //10
printf("%d\n",b);//100
printf("%d\n",c);//1069 ,另一个文件中的全局变量也可以使用了。
//如果{}内的变量与{}外的变量的名称相同,则在{}n内部使用该变量时,使用的是{}内的这个变量
//但是同一个{}内,不能定义同名的变量。
int d = 250;
printf("%d\n",d);//250
}
//printf("%d",a); //这里不能访问变量a,因为出了其所在的{}。
printf("%d\n",b);//100
return 0;
}
常量
/*
* C语言中,常量分为以下几种:
* 1. 字面常量
* 2. const修饰的常变量
* 3. #define定义的标识符常量
* 4. 枚举常量
*/
#include
//固定不变的,就是常量。如果一个变量被定义为常量,则其变量名需要全部大写。
//常量的定义,语法:#define 常量名;
#define NUM 1000;
//创建性别枚举。
enum Sex
{//其中的三个值,都是枚举常量。
MALE, //男 //这三个值默认是0,1,2。如果是MALE=3, 则三个值是3,4,5。 被叫做赋初值
FEMALE,//女
SECRET //保密
};
int main()
{
//1、字面常量。(没什么意义,因为不能使用,但是存在)
//3.14; //浮点型常量
//10; //整型常量
//'a'; //char类型常量
//"abcd"; //字符串型常量
//2、const修饰的常变量
//常变量定义语法:const 数据类型 变量名 = 初始值;
//const是一个修饰符,加在数据类型的前面,用来给这个变量加上一个const(不变的)属性。const属性表示这个变量的值一旦定义,就不可以再次改变。其本质上依然是一个变量,只是被const修饰了,不能被修改,所以被叫做常变量。
//虽然const修饰的变量,其变量值不可变了。但是在需要常量的地方,仍然不可以使用该常变量。
const int n = 10; //n就是常变量,具有常属性(不能被改变的属性),但其本质仍然是个变量
//n =100; //不能修改,编译报错。
//3、 #define定义的标识符常量。常量也可以在这里定义
#define MAX 999;
//NUM = 10; //编译报错;不能修改。
int a = NUM;
int b = MAX;
printf("%d %d\n",a,b);
//4、枚举常量
enum Sex s = MALE;
printf("%d\n",s); //0
printf("%d\n",FEMALE); // 1
printf("%d\n",SECRET); //2
return 0;
}
字符串
#include
#include
// ”hello world“ ,这种由双引号引起来的一串字符称为字符串字面值,或者简称字符串。
//字符串的结束标志是一个\0的转义字符。在计算字符串长度的时候\0是结束标志,不算做字符串内容
//在Clion中,我们发现在字符串的最后是\000,这是八进制转义字符,可以简写为\0
int main()
{
//字符数组
char arr1[] = "abc";//在内存中实际存储的是abc\0
char arr2[] ={'a','b','c'};//在内存中实际存储的是abc
//打印
printf("%s\n",arr1);//输出abc,因为arr1中存储的字符串,后面有\0。所以打印完停止
printf("%s\n",arr2);//输出abcabc, 因为arr2中存储的字符后面不知道有什么,直到碰见\0才停止打印。
//当我们在其中存储一个\0时,打印就会停止了
char arr3[] ={'a','b','c','\0'};
printf("%s\n",arr3); //输出abc
//求一下字符串的长度,使用函数strlen。不计算结束标志\0,只计算字符串长度。
int len = strlen("abc"); //string length
printf("%d\n",len); //3
printf("%d\n",strlen(arr1)); //3
printf("%d\n",strlen(arr2)); //6 这个6是一个随机值,因为我们并不知道arr2后面存储的是什么
//所以如果计算没有存储\0的字符数组的长度,那么这个长度就是一个随机值。
return 0;
}
转义字符
//反斜杠(\)在C语言中具有转义功能。转义字符出现在特殊字符之前,会将特殊字符转换成普通字符。
//如果我们需要在屏幕上打印一个目录c:\tode\test.c
#include
#include
int main()
{
printf("c:\tode\test.c\n"); // 实际输出c: ode est.c
printf("c:\\tode\\test.c\n"); //使用转义字符将\变为普通字符,成功输出c:\tode\test.c
//%c表示打印字符
printf("%c\n",'\130'); //打印出X。130是一个八进制,转换成十进制是88,88作为ASCCI码,对应的是X
//像键盘上的ABC等符号我们都可以直接按出来,但是计算机只认识二进制,所以需要为他们编号排序。就是我们所说的ASCII表
//ASCII表中的每个字符都对应一个ASCII值,如A对应65;a对应97
printf("%c\n",'\0x30'); //打印出字符0
printf("%c\n",'\72'); //打印出字符:
//笔试题
// \t、\62、\t作为转义字符联合起来,每一个转义字符都算作一个字符。因为是八进制,所以\后面的8一定不能与转义字符一起算。
printf("%d\n",strlen("c:\test\628\test.c"));//14
return 0;
}
\n表示换行,\r表示将当前位置移动到文本开头。
实际上,我们键盘上的回车,就是相当于移动到文本开头,然后换行。
但是我们在程序中使用的一般都只是一个\n来实现换行,然后直接就是下一行的开头了。这是因为在执行程序时,程序与控制台(shell)进行交互,我们从控制台输入输入的数据,控制台翻译后与运行的程序进行交互。等程序执行完之后,将结果显示到显示器上。
为什么我们只用一个\n就实现了回车换行的功能?是因为shell帮我们将这个\n解释为回车换行了,所以我们一般只使用\n,基本上没有使用过\r。
转义字符 | 释义 |
---|---|
? | 在书写连续多个问号时使用,防止被解析为三字母词 |
\’ | 字符单引号 |
\" | 双引号 |
\\ | 反斜杠 |
\a | 响铃,也叫蜂鸣 |
\b | 退格,将当前位置移到前一列 |
\f | 换页,将当前位置移动到下页开头 |
\n | 换行,将当前位置移动到下一行开头 |
\r | 回车,将当前位置移动到本行开头 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ddd | ddd表示1-3个八进制数字。如\130就是字符X。如果是\072可以表示为\72,是: \0与\000意思相同 |
\xdd | dd表示2位16进制数字,如\x30 ,就是字符0 |
注释
//C++风格的注释
/*
* C语言风格的注释
* 缺陷:不支持嵌套,如果其中再次出现/**/,则后面的*/就会失效
*/
选择语句
#include
int main()
{
int num = 0;//输入的值
printf("下雨请按1,反之按0:\n");
scanf("%d",&num);
if(num ==1)
{
printf("请带伞\n");
}
else
{
printf("出行愉快\n");
}
//当只有一行c语句时,可以省略大括号
// if(num ==1)
// printf("请带伞\n");
// else
// printf("出行愉快\n");
return 0;
}
循环语句
#include
int main()
{
printf("饭熟了\n");
int eat = 0 ;
while(eat<10)
{
eat++;
printf("吃了一碗饭\n");
}
if(eat==10){
printf("胖了10斤!\n");
}
}
函数
int Add(int x ,int y)
{
int z = x + y;
return z;
}
int main()
{
int num1,num2;
scanf("%d %d",&num1,&num2);
//int sum = num1 + num2;
//调用函数解决相加
int sum = Add(num1,num2);
printf("%d\n",sum);
}
数组
//数组的定义:数据类型 数组名[数组中元素个数]={}
//数组如果初始化,可以不指定数组大小。并且数组大小不能传入一个变量。
//C语言规定:数组的每个元素都有一个下标,数组下标从0开始。数组可以通过下标来访问:数组名[下标]
//数组名本身就是一个地址,所以如果是在调用scanf函数时,数组名前就不用加&符号了。
int main()
{
int arrr[10] = {0} //初始化数组,其中的每个元素值都为0。
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//遍历数组
int i = 0;
while(i < 10)
{
printf("%d ",arr[i]);
i++;
}//1 2 3 4 5 6 7 8 9 10
printf("\n");
int arr2[5] = {1,2,3}; //不完全初始化,剩余的默认为0
int j = 0;
while(j < 5)
{
printf("%d ",arr2[j]);
j++;
}//1 2 3 0 0
return 0;
}
常见关键字
//这些关键字是C语言规定的。并且关键字不能作为变量名。
auto break case char const continue default do double else
enum extern float for goto if int long register return
short signed sizeof static struct switch typedef union unsigned
void volatile while
//extern 用来声明外部符号,如生命其他文件内定义的全局变量
//signed 有符号的 、unsigned 无符号的
//static 静态的
//union 联合体(共用体)
//void 无/空
//像我们已经使用过的include、define,他们并不是关键字,而是预处理指令。
extern
/*
* C程序通常由许多文件组成,为了让多个文件访问相同的变量,C++区分了声明和定义。
* - 变量定义:用于为变量分配存储空间,还可以为变量指定初始值。相同类型相同变量名的变量只能定义一次。
* - 变量的声明:用于向程序表明变量的类型和名字。定义也是声明:当定义变量的时候我们声明了它的类型和名字。
* - 可以通过使用extern声明变量名而不定义它。声明变量的变量名、变量类型。
* extern声明不分配存储空间,它只是说明该变量的定义在程序的其他地方。
* - 程序中变量可以声明多次,但在同一个作用域中,相同类型相同变量名的变量只能定义一次。
* - 只有当声明也是定义时,声明才可以有初始化式,因为只有定义才分配存储空间。初始化式必须要有存储空间来进行初始化。
* 如果声明有初始化式,那么它可被当作是定义,即使带有声明标记为extern。(但是不推荐这样定义,即使可以使用,但是现在这个定义被gcc编译器认为是错误的。)
* - 任何在多文件中使用的变量都需要有与定义分离的声明。
* 如:在一个文件中定义了某个变量,其他文件需要使用该变量,则需要在其他文件中声明该变量。
*
* 注意:
* 1、不要把变量的定义放入.h头文件中,防止头文件重复引用时,变量的重复定义。
* 2、不要在.h文件中定义变量。定义会产生内存分配的操作,是汇编阶段的概念。
* 而声明则只是告诉程序,本模块中使用了其他模块中的函数和变量。
* 3、使用static关键字可以把变量限制于该源文件作用域,除非变量被设计成全局的。
* 4、可以在头文件中声明一个变量,使用的时候包含这个头文件就声明了这个变量。
*
* 什么是模块化?
* 1、模块即是一个.c文件和一个.h文件的结合,头文件(.h)中是对于该模块接口的声明。
* 2、某模块提供给其它模块调用的外部函数及数据,需在.h中文件中冠以extern关键字声明。
* 3、模块内的函数和全局变量需在.c文件开头冠以static关键字声明;
* 4、一般情况下头文件中只放变量的声明,因为头文件要被其他文件包含(即#include)
* 如果把定义放到头文件的话,就无法避免变量的多次定义,
* C不允许变量的重复,一个程序中对指定变量的定义只有一次,声明可以无数次。
*
* 可以声明:函数、变量、结构、宏、枚举、类型、inline()函数。
*/
#include
//注意:除非有extern关键字,否则就都是定义。
extern int a; //声明
int b; //定义(未初始化)
//如果声明有初始化式,就被当作定义,即使前面加了extern。
//但是注意:只有extern声明位于函数外部时,才可以被初始化。
//虽然可以定义,但现在这个定义被gcc编译器认为是错误的,所以不推荐使用。
extern int c = 100;
//函数的声明和定义
extern int SUM(int x,int y);
//定义一个外部声明的int型变量d
//只是个声明。如果要对c进行读写操作,而我们并没有对c进行定义,因此虽然语法检查没有问题,但是在链接时,连接器会找不到c的地址。
extern int d;
int main()
{
//声明一个外部定义的变量:int类型型变量e
//注意:声明外部变量时可以把变量类型去掉如:extern e;
extern int e;
printf("%d\n",c);
return 0;
}
atuo:自动的。每个局部变量都是auto修饰
int main()
{
{
//auto修饰所有局部变量。表示其是自动变量。自动创建,自动销毁
auto int a = 10;
//auto一般都省略掉。auto在新的标准中也有其他用法,暂时不考虑
int b = 1;
}
return 0;
}
register关键字
//register —— 寄存器关键字
/*
* 在计算机中数据可以存储到哪里呢?
* 网盘——如百度网盘,送很大的存储空间,但是因为需要网络传输,所以速度很慢。
* 硬盘——电脑内置,一般500GB,需要花钱购买。速度相对较快,几十到几百MB
* 内存——8G-16G,也是需要花钱购买。读写数据,速度比硬盘快。
* 高速缓存——几十MB。但是读写很快,比内存还快。
* 寄存器——空间更小,但是读写非常快,比高速缓存更快。
* 从上往下,存储空间越来越小,并且造价越来越高,但是速度越来越快。
* 那我们的CPU在处理数据时,因为现在的CPU普遍性能很高,而内存跟不上用。那么一些常用的/需要反复使用的数据就会放在高速缓存以及寄存器中
* 这样CPU在调用数据时,直接从寄存器获取,速度就会很快。如果寄存器没有,则需要在高速缓存中读取再使用,以此类推。
*/
int main()
{
//大量/频繁使用的数据,想粗放在寄存器中,提升效率,就可以使用register关键字修饰
//现在编译器已经很聪明了,他觉得用的很多的数据,就会将其存放再寄存器中。
//这里我们仅仅只是建议,如果你建议存放的数据,编译器不认为应该存放在寄存器中,则数据就不会存放在寄存器中,还是编译器说了算
register int num = 100; //建议num的值存放在寄存器中
return 0;
}
typedef关键字
//typedef ——类型定义,因该理解为类型重命名
//重命名unsigned int类型为u_int
typedef unsigned int u_int;
int main()
{
//因为unsigned int写起来太繁琐了,我们将其重新定义
unsigned int num1 = 0;
//定义后使用u_int定义的就是unsigned int类型了
u_int num2 = 0;
return 0;
}
static关键字
/*
* 在C语言中:static是用来修饰变量和函数的
* 1. static修饰的局部变量,称为:静态局部变量。静态局部变量,实际上是特殊的全局变量。
* 作为局部变量,具有局部作用域,出了其大括号不能访问。但是又使用static修饰,说明具有全局生命周期。
* 2. static修饰的全局变量,称为:静态全局变量
* 在我们没有使用static修饰全局变量时,该全局变量可以在外部文件中通过extern关键字声明使用。
* 但是当我们使用static修饰全局变量后,这个全局变量只能在自己所在的源文件中使用,其他源文件中不能使用
* 原因:
* 当我们定义了全局变量之后,因为其具有外部链接属性,所以可以被其他外部文件链接使用。
* 但是被static修饰之后,全局变量的外部链接属性变为内部链接属性。所以其他源文件不能再连接到这个已经变为静态的全局变量了。
* 3. static修饰的函数,称为:静态函数
* 如果我们需要使用另一个文件中的一个函数:
* int Add(int x , int y)
* {
* return x+y;
* }
* 则需要使用extern声明(类似于声明全局变量),语法:extern 函数返回类型 函数名(函数参数)。例如:
* extern int Add(int,int) 或使用 extern int Add(int x , int y)都可以
* 如果该函数使用static修饰之后:
* static int Add(int x , int y)
* {
* return x+y;
* }
* 因为static修饰了该函数,使得函数只能在自己所在的源文件内部使用,不能在其他文件中使用了。
* 本质上:static将函数的外部链接属性变成了内部连接属性。(与static修饰全局变量一样)
*/
//修饰局部变量
#include
void test()
{
//int i = 0;//所有局部变量都由auto修饰。自动创建、自动销毁。
static int i = 0;
i++;
printf("%d ",i);
}
int main()
{
int a = 0;
for(a=0;a<10;a++)
{
test();
/*
* 如果是auto修饰的局部变量,则会输出10个1。因为每次调用test()方法,开始调用时创建i变量,结束调用后a被销毁,所以每次都是1。
* 在使用static修饰之后,会输出1~10。第一次调用test()方法,i变量被创建,因为是static修饰,所以不会自动销毁。
* C语言中,栈区——存储局部变量、函数参数 、堆区——动态内存分配 、 静态区——存储全局变量、static修饰的静态变量
* 所以static修饰的局部变量,实际上是改变了变量的存储位置。
* 结论:static修饰局部变量,改变了其生命周期。让局部变量在出了其作用域之后依然生效,直到程序结束,生命周期才结束。
*/
}
return 0;
}
//宏的定义:宏是一种抽象,根据一系列预定义的的规则替换一定的文本模式。解释器或编译器在遇到宏时会自动进行这一模式替换。宏就是替换。
//define定义常量
#include
#define MAX 1000;
//define定义宏
//这里不能这样写,因为宏使用的时候,是使用x+y替换ADD(x,y),而如果传入的x和y是两个表达式,如x=1+2,那么在计算顺序上可能就会出现问题。
#define ADD(x,y) x+y;
//所以我们需要为x和y都加上(),并且为他们整体再加上()
#define RIDE(x,y) ((x)*(y));
int main()
{
int i = MAX;
printf("%d\n",i); //1000
//使用定义的ADD(x,y)宏
int add1 = ADD(1,3);
printf("%d\n",add1);//4
int add2 = 5*ADD(1,3);
//这里我们认为5*(1+3),肯定是输出20,但结果却是8?
//是因为宏在使用的时候,是用后面的替换前面的。如5*ADD(1,3)就被替换为5*1+3=8。
//所以我们定义的时候要为x以及y加上(),并且整体再加一个(),因为x和y都可能是一个式子,如x=1+4,y=2+4
//如果不加(),则计算顺序就会再次混乱,加上()就会优先计算()里的,就可以得到我们想要的结果了。
printf("%d\n",add2);//8
//使用定义的RIDE(x,y)宏
//我们想要2*(2*4)=16
int ride1 = 2*RIDE(2,4);
printf("%d\n",ride1);//16
//我们想要45/(3*5)=3
int ride2 = 45/RIDE(2+1,10/2);
printf("%d\n",ride2);//3
//加上()之后顺序就再也不会出错了
return 0;
}
/*
* 函数原型:int printf(const char * format,...)
* 作用:将格式化数据打印到标准输出。
* - 将format指向的字符串写入标准输出(stdout)。
* - 如果format包含格式说明符(以%开头的子序列),则format后面的附加参数将被格式化并插入到结果字符串中,替换他们各自的说明符。
*
* 参数:要写入stdout的字符串。
* - 它可以选择包含嵌入的格式说明符,这些说明符被后续附加参数中指定的值替换,并根据请求进行格式化。
* - 格式说明符遵循此原型。%[Flags][width][.prec][length]specifier。
* 其中最后的说明字符是最重要的组成部分,因为它定义了类及其相应参数的解释。
*
* Flag(标志)
* @ - —— 在给定的字段宽度内左对齐。默认右对齐
* @ + —— 强制使用加号或减号(+或-)处理结果。
* 默认情况下,如果什么都不加,正数前面不显示减号。如果这时在%后跟一个+号,那么要打印的数如果是正数,则输出时会强制加上加号。
* 如果要打印的数是负数,则默认会加上。如果要打印一个负数,并且%后面跟了+,也是打印负数。
* @ 空格 —— 如果没有写入任何符号,则在该值前面插入一个空格。(正数留空)
* @ 0 —— 用0填充
* @ # —— 如果是%#o、%#x、%#X,则打印后的值会以0、0x、0X开头。
*
* width
* @ 数字 —— 要打印的最大字符数。如果要打印的字符数少于指定的数字,则用空格填充。如果输出的字符数大于指定的数字,则什么都不做。
* @ * —— 宽度在format字符串中未指定。而是作为""(格式化字符串)之后的附加整数值参数指定。
*
* .prec
* .数字 ——
* - 对于整数说明符(d、i、o、u、x、X):prec指定了要输入的数字的最小位数。如果写入的值少于该数字,则用0填充。
* 如果输入的值多于数字,则什么都不做。精度为0,表示不为值0写入任何字符。
* - 对于a、A、e、E、f、F说明符,是小数点后要打印的位数。(默认打印6位)
* - 对于g、G说明符,是要打印的最大有效位数
* - 对于s,是要打印的最大字符数。默认情况下,是打印所有字符,知道遇到结尾的空字符。
* - 如果指定值不明确,则假定为0
* .* —— 精度不在格式字符串中指定。而是作为""(格式化字符串)之后的附加整数值参数指定。
*
* length 长度说明符——修改数据类型的长度。
* - hh 和整型转换说明一起使用,表示signed char或unsigned char 类型的值
* - h 和整型转换说明一起使用,表示short int或 unsigned short int类型的值
* - j 和整型转换说明一起使用,表示intmax_t或uintmax_t类型的值。
* - l 和整型转换说明一起使用,表示long int或unsigned long int类型的值
* - ll 和整型转换说明一起使用,表示longlong int或unsigned long long int类型的值
* - L 和浮点转换说明一起使用,表示long double类型的值
* - t 和整型转换说明一起使用,表示ptrdiff_t(两个指针差值)类型的值
* - z 和整型转换说明一起使用,表示size_t类型的值
*
* specifier
* - i或d 打印有符号十进制整数
* - c 打印字符
* - s 字符串
* - u 无符号十进制整数
* - f float浮点型
* - lf double浮点型
* - x 无符号十六进制整数1122aabb(小写)
* - X 无符号十六进制整数1122AABB(大写)
* - p 指针地址
* - ld 用于long long
* - o 无符号八进制
* - e 科学计数法输出(小写),一般用于浮点数的打印
* - E 科学计数法输出(大写)
* - g 根据数值不同自动选择%f或%e
* - G 根据数值不同自动选择%F或%E
* - a 十六进制浮点数(小写)
* - A 十六进制浮点数(大写)
* - % %%,用于打印一个百分号。
* - n 什么都不打印,而且其对应参数是int*指针,用来接收盗墓标签位置写入的字符数,存入传进的指针。(不会用)
*
* 附加参数:根据跟是字符串,该函数可能需要一系列的附加参数,每个参数都包含一个值,用于替换格式字符串中的格式说明符。
* - 这些参数的数量至少应该和格式说明符中指定的值的数量一样多。多余的参数会被忽略。
*
* 返回值:成功时,返回写入的字符总数。
* - 如果发生写入错误,则设置错误指示符( ferror ) 并返回负数。
* 如果在写入宽字符时发生多字节字符编码错误,则将 errno设置为EILSEQ并返回一个负数。
*/
int main()
{
int i = 123;
//-
//右对齐
printf("%9d\n",i);// 123
//左对齐
printf("%-9d\n",i);//123
//+
printf("%d\n",-i); //-123 默认情况下,如果是个负数,打印的时候会加负号
printf("%d\n",i); //123 默认情况下,如果是正数,默认不打印正号
printf("%+d\n",i); //+123 如果是正数,%+,则会打印正数的正号。
printf("%+d\n",-i); //-123 如果是负数,%+,也是打印负数。
//空格
printf("% d\n",i); // 123
printf("% d\n",-i); //-123
//指定填充时,用0,而不是空格填充。
printf("%5d\n",i); // 123。默认用空格填充
printf("%05d\n",i); //00123。指定后,用0填充
printf("%-05d\n",i); //123。如果是左对齐,则指定之后也不会进行填充。
//#
printf("%o\n",i); //173。%o以八进制打印
printf("%#o\n",i); //0173。打印八进制时,带上开头的0
printf("%x\n",i); //7b。%x以十六进制打印(小写)
printf("%#x\n",i); //0x7b。打印十六进制时,带上小写的0x
printf("%X\n",i); //7B。%x以十六进制打印(大写)
printf("%#X\n",i); //0X7B。打印十六进制时,带上小写的0X
float a = 123.0;
//width 与.prec
//如果要打印的字符数少于指定的数字,则用空格填充。宽度为9,123.00共占6个字符,所以前面补3个空格。
//.2是要保留两位小数。所以123.0变成123.00
printf("%9.2f\n",a); // 123.00
//如果是* ,则宽度,作为格式字符串之后的第一个附加整型参数来传递
printf("%*d\n",6,i); // 123
//如果是.* 。 则小数点后的位数,作为格式字符串之后的第一个附加整型参数来传递
printf("%.*f\n",3,a); //123.000
//如果是*.* 。则格式字符串之后的第一个附加整型参数作为宽度排列,第二个附加整型参数作为精度传递。
printf("%*.*f\n",9,3,a); //123.000
return 0;
}
/*
* 函数原型:int scanf(const char* format,...)
* 作用:从标准输入(stdin)读取格式化数据
* - 从标准输入 读取数据并根据参数格式将它们存储到附加参数指向的位置。
* 附加参数应指向已分配的对象,其类型由格式字符串中的相应格式说明符指定。
*
* 参数:
* format:格式字符串。这些字符控制如何处理从stdin流中提取的字符。
* - 空白字符:该函数将读取并忽略在下一个非空白字符之前遇到的任何空白字符(空白字符包括空格、换行符和制表符)。
* 格式字符串中的单个空格验证从流中提取的任意数量的空格字符(包括无)。
* - 非空白字符,格式说明符(%)除外:任何不是空白字符(空格、换行符或制表符)或格式说明符的一部分(以%字符开头)的字符都会导致函数读取下一个字符从流中,将其与此非空白字符进行比较。
* 如果匹配,则将其丢弃,函数继续使用format的下一个字符。如果字符不匹配,则函数失败,返回流的后续字符并将其保留为未读。
* - 格式说明符:由初始百分号(%)形成的序列表示格式说明符,用于指定要从流中检索并存储到附加参数指向的位置的数据的类型和格式。
* - scanf 的格式说明符遵循此原型:%[*][width][length]specifier
* 其中最后的说明符字符是最重要的组成部分,因为它定义了需要提取哪些字符、及其相应参数的解释。
*
* 子说明符:
* - * , 表示数据将从流中读取,但是可以被忽略,即它不存储在参数指向的位置。
* - width , 指定当前读取操作中要读取的最大字符数
* - length ,改变相应参数所指向数据的存储预期类型。
* hh:表示signed char或unsigned char 类型的值
* h:短整型(针对d、i、n),无符号短整型(针对o、u、x)。
* l:长整型(针对d、i、n)或无符号整型(针对o、u、x),或双精度型(针对e、f、g)
* ll:和整型转换说明一起使用,表示longlong int或unsigned long long int类型的值
* L:长双精度型(针对e、f、g)
* j 和整型转换说明一起使用,表示intmax_t或uintmax_t类型的值。
* t 和整型转换说明一起使用,表示ptrdiff_t(两个指针差值)类型的值
* z 和整型转换说明一起使用,表示size_t类型的值
*
* specifier
* - c 读入单个字符。如果制定了一个不为1的width,则函数会读取width个字符,并通过参数传递,把它们存储在数组中的连续位置。在末尾不会追加空字符。
* - d 读入有符号十进制整数。数字前面的+或-是可选的。
* - i 任意数量的数字。可选地以符号 ( +或- ) 开头。默认情况下采用十进制数字,以0为前缀是八进制数字、0x开头十六进制数字
* - o 读入八进制整数
* - x 读入十六进制整数。可选择以0x或0X开头,并且全部可以选择以符号+或-开头。
* - f、F、e、E、g、G 浮点数。可选择包含一个小数点,可选择以符号+或-开头。可选地后跟e或E字符和一个十进制整数。例如:-732.103和7.12e4
* - s 字符串,连续读取任意数量的非空白字符,直到遇到空白字符。终止空字符会自动添加到其结尾。
* - p 读入一个指针
* - [characters] 扫描[]中任意数量的字符。
* -[^characters] 否定了扫描集中任意数量的字符,其中没有一个指定为括号之间的字符。任意数量的字符都没有指定为括号之间的字符。
* - a、A 读入一个浮点值。(仅C99有效)
* - % %%读入一个%。
* - n 不消耗任何输入。到目前为止从stdin读取的字符数,存储在指定位置。
*
* 附加参数:
* - 根据格式字符串,该函数可能需要一系列附加参数,每个参数都包含一个指向已分配存储空间的指针,其中提取的字符的解释以适当的类型存储。
* - 这些参数的数量至少应与格式说明符存储的值的数量一样多。该函数会忽略多余的其他参数。
* 这些参数应该是指针:要将scanf操作的结果存储在常规变量上,它的名称应该以引用运算符( & ) 开头。
*
* 返回值:
* - 成功时,该函数返回参数列表中成功填充的项目数。由于匹配失败、读取错误或到达文件末尾,此计数可以与预期的项目数匹配或更少(甚至为零) 。
* - 如果发生读取错误或读取时到达文件结尾,则设置正确的指示符(feof或ferror)。
* 并且,如果在任何数据可以成功读取之前发生任何一种情况,则返回EOF 。
* - 如果在解释宽字符时发生编码错误,该函数将errno 设置为EILSEQ。
*/
int main()
{
int num,num2,num3;
//比如输入了123 456,那么会跳过123,将456存储到num中。
// scanf("%*d %d",&num);
// printf("%d\n",num);
//%d与%d之间用逗号(,)隔开,意思是读取输入的时候,以逗号(,)为分隔符读取数据。默认是以空格隔开。
//如输入:12,32 。则num=12,num2=32。
//如果此时输入时以空格隔开:12 32。则num=12,num2=0。此时的32会返回stdin中,等待下一次scanf读取。
//如果我们后面还有一个scanf读取数据,则我们不需要再输入数据,32会被这个scanf读取,保存到num3中,则num3=32。
// scanf("%d,%d",&num,&num2);
// printf("%d %d\n",num,num2);
// scanf("%d",&num3);
// printf("%d\n",num3);
//定义一个scanf函数的返回值
//当所定义的数据类型与用户输入的数据类型不相符时,就会出现问题,输出结果就不是预期结果,这时可以定一个变量来表示成功读取数据的个数。
//如果输入:12 23 34 ,则ret=3,表示正常读取
//如果输入:12 s 23,则只有a是12,遇到s后,会直接退出,剩下的都没有读取成功。所以ret=1
// int a,b,c,ret;
// ret = scanf("%d %d %d",&a,&b,&c);
// printf("a=%d,b=%d,c=%d\n",a,b,c);
// printf("ret=%d\n",ret);
//如果输入:you are very good,则str中只存储you,因为遇到了空格。
//这里要注意的是"are very good"还在键盘缓冲区
//但是,其实这时缓冲区字符串首尾指针已经相等了,也就是说缓冲区清空了。scanf()函数应该只是扫描stdin流,这个残存信息是在stdin中)
char str[80];
scanf("%s",str);
printf("%s",str);
return 0;
}
/*
* scanf读取流程:
* - 从键盘输入的字符,首先会缓存到键盘缓冲区中,当用户输入回车,会清空键盘缓冲区,将键盘缓冲区数据(包括回车)送入stdin中。这时scanf()开始从stdin中读取数据。
*
* - scanf()在读取每个字段时,都会忽略空白符(%c比较特殊)。
* 以%d为例,scanf()会先忽略stdin中的空白符,直到遇到第一个0-9开始读取,如果后面的字符依然是0-9就继续读取,
* 直到遇到空白符或者非0-9的字符,scanf认为%d的读取完毕,将读取的字符序列转换成十进制整型保存到变量中。
* 最后遇到的空白符或非法字符将返回到stdin中去。
* - 如果忽略掉前面的空白符后第一个遇到的是非法的字符(非0-9)。
* 比如a,这时a会被返回到stdin中,程序也将会跳出scanf()函数(不管%d后面是否还存在带输入项,如另一个%d,都会跳出整个scanf()函数)。
*
* 注意:
* - 因为要将用户输入的数据存储到变量中,所以scanf()函数的参数,是要传变量的地址进来,也就是:&变量名。
* - %f、%lf、%d以“空白字符”为间割,空白字符有空格、制表符(\t)、回车符,遇到空白字符停止读取
* %c没有间隔,逐字符读取。%c不会把空格跳过去,也就是说如果输入的空格,也会被%c读取到。
* - 应当注意scanf中的两个%d之间最好不要加任何符号,否则输入时要以中间的那个符号作为间隔。如果输入时没有该符号,就会出现问题
* 也就是说:scanf()的格式化字符串中,两个%(如:%d和%d)之间可以使用其它非空白字符,但在输入时必须输入这些字符。
* - 输入double类型的值必须用%lf,不能用%f,否则输入失败。输出double类型变量的值可以用%lf,也可以用%f,没有区别
* - scanf()函数接收输入数据时,遇以下情况结束一个参数数据的输入:(不是结束函数,而是scanf函数仅在每一个数据域均有数据,并按回车后结束)。
* 遇到空格、“回车”、”跳格“。 遇见宽度结束、
*/
如何处理scanf()函数误输入造成程序死锁或出错?
/*
* ffluse()函数
* 函数原型:int fflush(FILE* stream);
* 作用:刷新流
* - 如果给定的流是为写入而打开的,并且这个更新流执行的操作不是输入,则其输出缓冲区中的任何未写入的数据都会写入文件。
* fflush函数将把任何未被写入的数据写入stream指向的文件(如标准输出:stdout)。
* - 如果流是空指针,则清空所有输出流和更新流
* - 在其他情况下,函数行为取决于特定库实现。在某些编译器上,刷新打开以进行读取的流会导致输入缓冲区被清空(但不是可移植的预期行为)。
* 由此可知,如果 stream 指向输入流(如 stdin),那么 fflush 函数的行为是不确定的。故而使用fflush(stdin) 是不正确的
* - 在此调用后,流将保持打开状态。
* - 当一个流被关闭时,无论是因为调用fclose()还是因为程序终止,与之相关的流都会被自动刷新。
*
* 参数: stream,指向指定缓冲流的FILE对象指针。
* 返回值:如果成功,函数返回零值。发生错误,则返回EOF,并设置错误指示符(ferror)
*/
/*
* 如下程序,如果正确输入a,b的值,那么没什么问题,但是,你不能保证使用者每一次都能正确输入。一旦输入了错误的类型,程序除了死锁,就是得到一个错误的结果
* 解决方法:
* - scanf()函数执行成功时的返回值是成功读取的变量数。
* 也就是说,你这个scanf()函数有几个变量,如果scanf()函数全部正常读取,它就返回几。
* - 但这里还要注意另一个问题,如果输入了非法数据,键盘缓冲区就可能还个有残余信息问题。
*/
#include
void flush()
{
char c;
//清空缓冲区,后续讲到
while ((c=getchar()) != '\n'&&c!=EOF);
}
int main()
{
int a,b,c; /*计算a+b*/
//当scanf函数没有成功读取时,清空数据,重新读取。直到scanf成功读取两个数据。
int ret = 1;
while(ret !=2)
{
printf("ret=%d,请输入两个数字:",ret);
ret = scanf("%d %d",&a,&b);
//fflush(stdin);//清空stdin中残留的数据 。可移植性差。
flush(stdin);//自己写的,用于清空缓冲区的方法。
}
c=a+b;
printf("%d+%d=%d",a,b,c);
return 0;
}
gets()函数
/*
* char * gets(char * str);
* 功能:从标准输入读取字符并将它们作为C字符串存储到str中,直到达到换行符或文件结尾。
* 如果读取到换行符,则不会将其复制到str中,而是在复制到str的字符之后自动附加一个字符串结尾符。
* 参数:str —— 这是指向一个字符数组的指针,该数组中存储了C字符串。
* 返回值:
* - 成功,函数返回str;
* - 如果到达文件结尾还未读取到任何字符,则返回NULL,并且str的内容保持不变
* - 如果发生读取错误,则设置错误提示符(ferror),并返回空指针,但str指向的内容可能已经更改。
*/
getchar()函数——只适用于标准输入流
/*
* int getchar(void)函数 —— 作用:输入一个字符。通常用于接收空格/换行符
* 返回值:该函数以无符号char强制类型转换为int的形式返回读取的字符,如果达到文件末尾或发生错误,则会返回(-1)EOF
* 返回字符对应的ASCII码
*
* int putchar(int char) —— 作用:输出一个字符。char是要输出的参数。
* 返回值:该函数以无符号char强制转换为int的形式返回写入的字符,如果发生错误则会返回(-1)EOF
* 参照ASCII表,将char对应的字符返回。
*
*注意:
* - 当程序调用getchar()时,等待用户按键,用户输入的字符被存放在键盘缓冲器中,直到用户按回车为止(这个回车字符也存放在缓冲区中)。
* 用户键入回车之后,getchar才开始从stdin流中每次读入一个字符。
* - getchar()函数不仅可以从输入设备获取一个可显示的字符,而且可以获得屏幕上无法显示的字符,如:回车换行/空格等
* - getchar()函数的返回值是:用户输入的字符的ASCII码,若文件结尾则返回-1(EOF),且将用户输入的字符回显到屏幕
* - 如果用户在按回车之前输入了不止一个字符,其他字符也会保留在键盘缓冲区中,等待后续getchar()调用读取
* 也就是说:后续的getchar调用不会等待用户按键,而是直接读取缓冲区中的字符,直到缓冲区中的字符读取完之后,才等待用户按键
*
*主要用法:
* - 清空回车符,这种情况一般发生在寻魂中涉及到输入的情况。
* - 某些编译平台(IDE)在运行程序时,并没有在程序程序运行后给别人看结果的时间。这时在程序最后加上getchar(),就可以造成程序的暂停。
* - 使用getchar();时,输入完字符,要按回车才能读取进去
* 使用getch();时,在键盘上按一个字符马上就被读取进去,不用按回车,因此可以作为“按任意键继续”的执行语句。
*/
/*
* - getch()函数
* getch与getchar基本功能相同,差别是:getch直接从键盘获取键值,不等待用户按回车,只要用户按一个键,getch就立刻返回
* getch返回值是用户输入的字符的ASCII码,出错返回-1。
* - 输入的字符不会会现在屏幕上。getch函数常用于程序调试中。在调试时,在关键位置显示有关的结果以待查看。
* 然后用getch函数暂停函数运行,当按任意键后程序继续运行。
* - getch()是非缓冲输入函数,就是不能用getch()来接收缓冲区已存在的字符。
*
* - getche()函数
* 这个函数与前两上类似,功能也相近,都是输入一个字符,返回值同样是输入字符的ASCII码。
* 但不同的是,此函数在输入后立即从控制台取字符,不以回车为结束(带回显)。
*
*/
#include
int main()
{
int ch = 0;
/*
* - 虽然getchar()是用于输入一个字符。
* 但如果用户在按回车之前输入了不止一个字符,其他字符也会保留在键盘缓冲区中,等待后续getchar()调用读取。
* 也就是说:后续的getchar调用不会等待用户按键,而是直接读取缓冲区中的字符,直到缓冲区中的字符读取完之后,才等待用户按键
*
* 程序运行后,等待键盘输入。输入一串字符后,循环读取这串字符,按顺序输出。
* 如输入abc并回车,实际输入:abc\n
* 则会输出 97——a---- 98——b---- 99——c---- 10——
----
* 这里10——后面换行才输出了----,就说明了:\n也被存储到了缓冲区中,其对应的ASCII码是:10。
*
*/
while ((ch=getchar()) != EOF)
{
//getchar()函数的返回值是其字符对应的ASCII码
printf("%d——",ch);
//putchar()函数的返回值是其传入变量对应的字符。
putchar(ch);
printf("----\t");
}
}
/*
* 键入缓冲区——所有从键盘输入的数据,不管是字符还是数字,都是先存储在内存的缓存区,叫做"键盘输入缓冲区",简称"输入缓冲区"或"输入流"
* 当我们调用的函数需要我们用键盘输入时,我们输入的数据都会被依次存入缓冲区。
* - 但是只有当按下回车之后,scanf函数才会进入缓冲区取数据,所取数据的个数取决于scanf的"输入参数"的个数。
* 所以不在于怎么输入。可以存一个取一个,也可以一次性全部存进去,然后一个一个取。
*
* 使用%d 和 %c 读取缓存的差别:
* - 对于%d,在缓冲区中,空格、回车、tab键都只是分隔符,不会被sacnf当成数据使用。%d碰见他们就跳过,取下一个数据。
* 遇到字符,会直接退出,不再取其中的数据,就算还有变量也不会再取,而是直接为没有取到数据的变量赋值为0.
* - 对于%c,空格、回车、tab键都会被当成数据输出给scanf取用。
* - %s,也是只会取出数据。不会取空格、回车、tab键
*/
/*
* 运行程序运行,我们输入:123456回车,却直接弹出了请确认密码(Y/N):确认失败。
* 原因:我们输入的数据存储到缓冲区中是:123456\n (\n是我们的回车,也会存储到缓存区中)
* - scnaf函数执行,用%s读取缓存区,只会取数据123456,把\n剩在了缓存区中。
* - 接下来调用getchar()获取我们键盘输入时,因为缓存区中还有数据,会先将其中的数据读取完之后才会等待用户继续输入。
* 这里的\n被getchar()获取走了,赋给了ch变量,而'\n'!='Y',所以没有等待我们输入,直接输出了“确认失败”
*/
//#include
//int main()
//{
// //定义字符数组,长度为20 初始化其中所有元素为0
// char password[20] = {0};
// printf("请输入密码:");
// //%s表示输入一个字符串
// scanf("%s",password);
// printf("请确认密码(Y/N):");
// int ch = getchar();
// if(ch == 'Y')
// {
// printf("确认成功\n");
// }
// else
// {
// printf("确认失败\n");
// }
// return 0;
//}
/*
* - 解决方法:
* 在int ch = getchar();语句前加上一个getchar();,用来取走缓冲区中的'\n'
* 然后等到执行int ch = getchar();时,就是等待我们输入Y/N了。
* - 注意:
* 我们使用getchar()取走了缓存区中的‘\n’。但是int ch = getchar();语句执行,也需要回车才会进去取这个字符
* 这时需要注意的是:与scanf函数取数据一样,getchar()函数取数据之后,也会把其产生的\n遗留在缓冲区中
*/
//#include
//int main()
//{
// char password[20] = {0};
// printf("请输入密码:");
// scanf("%s",password);
// printf("请确认密码(Y/N):");
// //在%c取字符之前,使用getchar()吸收遗留的\n
// getchar();
// int ch = getchar();
// if(ch == 'Y')
// {
// printf("确认成功\n");
// }
// else
// {
// printf("确认失败\n");
// }
// return 0;
//}
/*
* !思考:如果有多个scanf给int型变量赋值,那么每个scanf都会遗留一个回车,那么这时候是不是有几个scanf就需要几个getchar()呢?
* - 不需要,仍然只需要一个getchar()就可以。
* 当scanf用%d或%s取缓冲区数据的时候,如果遇到空格、回车、tab键,会跳过去。这里的跳过是指:释放掉了。
* 也就是说,scanf用%d或%s取缓冲区数据,碰到空格、回车、tab键,就会释放掉,不会再存在于缓存区中。
* - 所以加入有三个scanf给int型变量赋值,那么第一个把\n留在了缓冲区,第二个scanf取值时会释放掉第一个scanf遗留的回车。
* 第三个scanf取值时,会释放掉第二个scanf遗留的回车。而第三个遗留的回车,我们用一个getchar()就可以释放掉。
* 也就是说:混充去中永远不可能遗留多个回车。
*
* 结论:
* 当我们输入一串数据:有字符、数字、空格
* - scanf用%d获取其中的数据,只会取其中的数字。
* 如果遇到其中的空格、回车、tab键,则会将其当成分隔符。
* 如果遇到其中的字符,则会直接退出,不再取数据。
* - scanf用%c取数据
* 任何数据都被当成一个字符。
* - 所以如果要从输入流中取一个字符,但是在之前我们使用过scanf,那么此时就必须先使用getchar()吸收回车。
* 否则取到的就不是我们需要的字符了,而是scanf遗留在输入流中的回车。
* - 如果你要从输入流中取的不是字符,就不需要getchar()吸收回车了。
* - 但是在实际编程中,程序往往很长。我们很难预测到下一次到缓存区中取数据是%d、%c、gets()或是fgets()
* 所以为了避免忘记吸收回车,习惯上scanf后面都加上getchar()。
*/
/* - 运行程序,输入数据:123a456
* scanf以%d取数据,取到a发现如果输入的是字符,则会直接退出,不取数据。
* a被赋值123,而b、c没有取下值,则会赋值为0
* - 剩下的一串数据(a456以及scanf遗留的'\n')仍然会存储在缓存区中。
* 这时剩下的数据以5个getchar()进行回收
*/
//#include
//int main()
//{
// int a = 0;
// int b = 0;
// int c = 0;
// scanf("%d %d %d",&a,&b,&c);
// printf("%d %d %d\n",a,b,c);
// int ch = 0;
// while ((ch=getchar()) != EOF)
// {
// printf("%d——",ch);
// putchar(ch);
// printf("----\t");
// }
// return 0;
//}
/*
* fflush(stdin)方法
* - 前面介绍了使用 getchar() 吸收回车的方法,而fflush()方法,作用就是直接将输入缓冲区全部清空。
* - 清空缓冲区只需加一句 fflush(stdin) 即可。fflush 是包含在文件 stdio.h 中的函数。stdin 是“标准输入”的意思。
* std 即 standard(标准),in 即 input(输入),合起来就是标准输入。fflush(stdin) 的功能是:清空输入缓冲区。
*
* fflush 一般用于清除用户前面遗留的垃圾数据,提高代码的健壮性。因为如果是自己编程的话,一般都会按要求输入。
* 但对于用户而言,难免会有一些误操作,多输入了一些其他没有用的字符,如果程序中不对此进行处理的话可能会导致程序瘫痪。
* 所以编程时一定要考虑到各种情况,提高代码的健壮性和容错性。使用 fflush() 就可以将用户输入的垃圾数据全部清除。
*/
//#include
//int main()
//{
// int a = 0;
// printf("请输入一串字符和数字(请以数字开头):");
// scanf("%d",&a);
// printf("您输入的字符中,已存储的有效数字为:%d\n",a);
// fflush(stdin);
// printf("已经调用ffluh方法清空缓存区,请输入一个字符:");
// int ch = getchar();
// printf("%c",ch);
//
// return 0;
//}
/*
* - getchar()的高级用法 ———— while (getchar() != '\n');
* 它可以完全代替 fflush(stdion) 来清空缓冲区。不管用户输入多少个没用的字符,他最后都得按回车,而且只能按一次。
* 只要他按了回车那么回车之前的字符就都会被 getchar() 取出来。只要 getchar() 取出来的不是回车('\n') 那么就会一直取,直到将用户输入的垃圾字符全部取完为止。
*
* - while (getchar() != '\n');语句执行
* 循环执行getchar语句,会依次读取缓冲区的字符,这样所有缓冲区的字符都读入程序并依次被getchar()给吸收了,条件成立,执行空语句; 然后一直循环。
* 直到读取了缓冲区中的'\n'后,条件不成立,循环终止。虽然条件不成立,但是getchar()函数已经调用了,已经取走了其中'\n',这样就达到了清空缓冲区的效果。
*
*/
//#include
//int main()
//{
// int a = 0;
// printf("请输入一串字符和数字(请以数字开头):");
// scanf("%d",&a);
// printf("您输入的字符中,已存储的有效数字为:%d\n",a);
// while (getchar() != '\n');
// printf("高级运用getchar() 清空缓存区,请输入一个字符:");
// int ch = getchar();
// printf("%c",ch);
// return 0;
//}