C语言的输入输出
printf()
printf() 的基本用法
- printf() 是最灵活、最复杂、最常用的输出函数,完全可以替代 puts() 和 putchar()
- 首先汇总一下前面学到的格式控制符:
printf()的高级用法
- printf() 格式控制符
%[flag][width][.precision]type
- type 表示输出类型,比如 %d、%f、%c、%lf,type 就分别对应 d、f、c、lf;再如,%-9d中 type 对应 dtype. 这一项必须有,这意味着输出时必须要知道是什么类型。
- width 表示最小输出宽度,也就是至少占用几个字符的位置;例如,%-9d中 width 对应 9,表示输出结果最少占用 9 个字符的宽度。
- 当输出结果的宽度不足 width 时,以空格补齐(如果没有指定对齐方式,默认会在左边补齐空格)
- 当输出结果的宽度超过 width 时,width 不再起作用,按照数据本身的宽度来输出。
请看下面的例子:
#include <stdio.h>
int main(){
int m = 192, n = -943;
float f = 84.342;
printf("m=%10d, m=%-10d\n", m, m); //演示 - 的用法
printf("m=%+d, n=%+d\n", m, n); //演示 + 的用法
printf("m=% d, n=% d\n", m, n); //演示空格的用法
printf("f=%.0f, f=%#.0f\n", f, f); //演示#的用法
return 0;
}
运行结果:
m= 192, m=192
m=+192, n=-943
m= 192, n=-943
f=84, f=84.
C语言再屏幕的任意位置输出字符
开发小游戏第一步
#include<stdio.h>
#include<Windows.h>
void setColor(int color);
void setCursorPosition(int x, int y);
int main()
{
setColor(3);
setCursorPosition(3, 3);
puts("★");
setColor(0XC);
setCursorPosition(1, 1);
puts("◆");
setColor(6);
setCursorPosition(6, 6);
puts("■");
return 0;
}
//自定义光标定位函数
void setCursorPosition(int x, int y)
{
//定义光标位置
COORD coord;
coord.X = x;
coord.Y = y;
//获取控制台缓冲区句柄
HANDLE ConsoleHandle = GetStdHandle(STD_OUTPUT_HANDLE);
//设置光标位置
SetConsoleCursorPosition(ConsoleHandle, coord);
}
//自定义文字颜色函数
void setColor(int color)
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color);
}
-
SetConsoleCursorPosition
函数,在window.h头文件中,实现光标定位功能 -
hConsoleOutput
表示控制台缓冲句柄,可通过GetStdHandle(STD_OUTPUT_HANDLE)
来获得 -
dwCursorPosition
是光标位置,也是第几行第几列,它是COORD类型的结构体 - 从代码输出可以发现,先输出的字符位置不一定靠前,后输出的字符位置不一定靠后,他们都可以在任意位置
键盘输入字符和字符串
输入单个字符
- getchar()
- 最容易理解的字符输入函数是 getchar(),它就是scanf("%c", c)的替代品,除了更加简洁,没有其它优势了;或者说,getchar() 就是 scanf() 的一个简化版本。
- getche()
- getche() 就比较有意思了,它没有缓冲区,输入一个字符后会立即读取,不用等待用户按下回车键,这是它和 scanf()、getchar() 的最大区别
- getche() 位于 conio.h 头文件中,而这个头文件是 Windows 特有的
- getch()
- getch() 也没有缓冲区,输入一个字符后会立即读取,不用按下回车键,这一点和 getche() 相同。getch() 的特别之处是它没有回显,看不到输入的字符。所谓回显,就是在控制台上显示出用户输入的字符;没有回显,就不会显示用户输入的字符,就好像根本没有输入一样。
- getche() 一样,getch() 也位于 conio.h 头文件中
输入字符串
- gets()
- gets() 是有缓冲区的,每次按下回车键,就代表当前输入结束了,gets() 开始从缓冲区中读取内容,这一点和 scanf() 是一样的
- gets() 能读取含有空格的字符串,而 scanf() 不能。
总结
C语言中常用的从控制台读取数据的函数有五个,它们分别是 scanf()、getchar()、getche()、getch() 和 gets()。其中 scanf()、getchar()、gets() 是标准函数,适用于所有平台;getche() 和 getch() 不是标准函数,只能用于 Windows。
scanf() 是通用的输入函数,它可以读取多种类型的数据。
getchar()、getche() 和 getch() 是专用的字符输入函数,它们在缓冲区和回显方面与 scanf() 有着不同的特性,是 scanf() 不能替代的。
gets() 是专用的字符串输入函数,与 scanf() 相比,gets() 的主要优势是可以读取含有空格的字符串。
scanf() 可以一次性读取多份类型相同或者不同的数据,getchar()、getche()、getch() 和 gets() 每次只能读取一份特定类型的数据,不能一次性读取多份数据。
缓冲区的世界
缓冲区的定义
缓冲区是内存的一部分,也就是说,计算机在内存中预留了一定的存储空间,用来暂时保存输入和输出的数据,这部分预留空间叫做缓冲区
为什么要引入缓冲区
缓冲区是为了让低速的输入输出设备和高速的用户程序能够协调工作,并降低输入输出设备的读写次数
用户程序的执行速度可以看做CPU的运行速度,如果没有各种硬件的阻碍,理论上他们是同步的
缓冲区类型
- 全缓冲
在这种情况下,当缓冲区被填满以后才进行真正的输入输出操作.缓冲区都是有大小限制的,数据量达到最大值时就清空缓冲区
全缓冲的典型代表是对硬盘文件的读写
- 行缓冲
在这种情况下,当在输入输出过程中遇到换行符时,才执行真正的输入输出操作
行缓冲的典型代表就是标准输入设备(键盘)和标准输出设备(显示器)
- 不带缓冲
不带缓冲区,数据就没有地方缓存,必须立即进行输入输出
getche(), getch()就不带缓冲区,输入一个字符后立即就执行了,根本不用按下回车键
缓冲区的刷新
- 不管是行缓冲还是全缓冲,缓冲区满时会自动刷新
- 行缓冲遇到
\n
时会刷新 - 关闭文件时会刷新缓冲区
- 程序关闭时一般也会刷新缓冲区,这个是有标准库来保障的
- 使用特定的函数也可以说的刷新缓冲区
总结
缓冲区位于用户程序和硬件设备之间,用来缓存数据,目的是让快速的CPU不必等待慢速的输入输出设备,同时减少操作硬件的次数.对于IO密集型的网络应用程序,比如网站,数据库,DNS,CDN等,缓冲区的设计至关重要,它能十倍甚至一百倍得提高程序性能.
C语言清空(刷新)缓冲区
清空输出缓冲区:
- windows平台下的printf(),puts(),putchar()等输出函数都是不带缓冲区的,所以不用清空
- fflush(stdout);linux和Mac os平台下清空缓冲区
清空输入缓冲区(两种方法):
- 使用getchar()清空缓冲区
getchar()是带有缓冲区的,每次从缓冲区读取一个字符,包括空格,制表符,换行符等空白符,只要我们让getchar()不停地获取缓冲区字符,直到读完缓冲区所有字符,就能达到清空缓冲区的效果了
#include<stdio.h>
int main()
{
int a=1;b=2;
char c;
scanf("a=%d",&a);
while((c=getchar()) !='\n' && c != EOF);
scanf("a=%d, b=%d",a,b);
return 0;
}
//输入示例
a=100
b=300
a=100,b=300
//
a=100b=200
b=300;
a=100,b=300
- 使用scanf()清空缓冲区
scanf()还有一个高级的用法。就是使用类似于正则表达式的通配符,这样他就可以读取所有的字符了,包括空格。换行符。制表符等空白符,不会再忽略他们。并且,scanf()还允许把读到的数据直接丢掉,不会赋值给变量
scanf(“%*[^\n]”);scanf("%*c");
效果:
#include<stdio.h>
int main()
{
int a = 1; int b = 2;
scanf_s("a=%d", &a);
scanf_s("%*[^\n]"); scanf_s("%*c"); //在下次读取前清空缓冲区
scanf_s("b=%d", &b);
printf("a=%d, b = %d\n",a,b);
return 0;
}
//输入示例
//同getchar()代码中的输入示例
相比使用getchar(),这种方案不用额外定义一个变量,看起来更简洁
C语言scanf()的高级用法
三个方面:
1. 指定读取长度
- 与printf()最小输出宽度类似,scanf()也有类似的用法,也可以在格式控制符的中间加一个数字,用来表示读取数据的最大长度。
例如:
- %2d 表示最多读取两位整数
- %10s 表示读取的字符串的长度最大为十,或者说,最多读取十个字符。
#include<stdio.h>
int main()
{ int n;
float f;
char str[23];
scanf("%2d,&n");
scanf("%*[^\n]"); scanf("%*c");
scanf("%5f",&f);
scanf("%*[^\n]"); scanf("%*c");
scanf("%22s",str);
printf("n=%d,f=%g,str=%s\n",n,f,str);
return 0;
}
//输入示例
8920
10.2579
http://data.biancheng.net
n=89,f=10.25,str=http://data.biancheng.
2. 匹配特定字符
- %s控制符匹配除空白符以外的所有字符,有两个缺点:
%s不能读取特定字符,比如朱读取小写字母,或十进制数等,%s就无能为力了
%s读取到的字符串不能包含空白符,例如,无法将多个单词存放到一个字符串中,因为点此之间有空格,%s遇到空格就读取结束了
想要解决以上问题,可以使用scanf()的另外一种字符匹配方式,就是%[xxx], [ ]保卫起来的是需要读取的字符集合,例如,%[abcd]表示只读取字符abcd,遇到其他字符就读取结束
#include<stdio.h>
int main()
{
char str[30];
scanf("%[acbd]", str);
printf("%s\n",str);
return 0;
//输入示例
abcd//此行为输入
abcd//此行为输出
absd//此行为输入
ab//此行为输出
}
使用连接符
为了简化输入字符集合的写法,scanf()支持使用连字符-来表示一个范围内的字符,例如 %[a-z]、%[0-9]等不匹配某些字符
假如现在有一种需求,就是读取换行符以外的字符,或者读取0-9以外的字符解决办法:
%[^\n]表示匹配换行符以外所有的字符,遇到换行符就停止读取
%[^0-9]表示匹配除十进制数字以外的所有字符,遇到十进制就停止读取
3.丢弃读取到的字符
scanf()允许把读取到的数据直接丢掉,不往变量中存放 ,具体方法就是在%后面加一个 *
例如:
- %*d表示读取一个整数并丢弃;
- %*[a-z]表示读取小写字母并丢弃;
- %*[^\n]表示将换行符以外的字符全部丢掉
总结
scanf()的控制字符串的完整写法为:
%{*} {width} type
- type表示读取什么类型的数据,例如%d,%s,%[a-z],%[^\n]等;type必须有
- width表示最大读取宽度,可有可无
-
*
表示丢弃读取到的数据,可有可无
循环结构和选择结构
查漏补缺:
- 关系运算符都是双目运算符,其结合性均为左结合
- 关系运算符的优先级低于算术运算符,高于赋值运算符
- 在六个关系运算符中,<、<=、>、>=的优先级相同,高于==和!=,==和!=的优先级相同。
- && 和 || 低于关系运算符,! 高于算术运算符。
if语句的判断条件:
if 语句的判断条件中不是必须要包含关系运算符,它可以是赋值表达式,甚至也可以是一个变量,例如:
//情况①
if(b){
//TODO:
}
//情况②
if(b=5){ //情况①
//TODO:
}
都是允许的。只要整个表达式的值为非0,条件就成立。
上面两种情况都是根据变量 b 的最终值来判断的,如果 b 的值为非0,那么条件成立,否则不成立。
switch语句
switch(){
case 1: .....;break;
case 2 .....;break;
case 3: .....;break;
case 4: .....;break;
......
default: ....;
}
注: case后面必须是一个整数,或者是结果为整数的表达式,但不能包含任何变量。
条件运算符:
- 条件运算符的优先级低于关系运算符和算术运算符,但高于赋值符
//所以:
max=(a>b) ? a : b;
//可以去掉括号而写为
max=a>b ? a : b;
- 条件运算符的结合方向是自右至左。例如:
a>b ? a : c>d ? c : d;
//应理解为:
a>b ? a : ( c>d ? c : d );
C语言for循环中的三个表达式
- for 循环中的“表达式1(初始化条件)”、“表达式2(循环条件)”和“表达式3(自增或自减)”都是可选项,都可以省略(但分号;必须保留)。