文章目录

    • 一.C语言的输入输出
      • 1.printf
        • i. 输出整数
        • ii. 浮点数
        • iii.字符 & 字符串
      • 2.scanf
        • i.整数
        • ii.浮点数
        • iii. 字符 & 字符串
      • 3.特殊用法
        • i. * 的应用
        • ii. %n 的应用
        • iii. %[] 的应用
    • 二.C++中的输入输出
      • 1.cout
        • i. 缓冲区(buffer)
        • ii. cout之格式化输出
      • 2.cin
        • i. cin.get()
        • ii. cin.getline()
        • iii. 流状态
    • 三.取消同步流

一.C语言的输入输出

在讲 c++ 的输入输出之前,有必要先回顾一下最经典的 c 语言的输入输出 —— s c a n f——scanf——scanf p r i n t fprintfprintf,他们被声明在头文件 #include

scanf & printf 的优势:

  1. 格式化输入输出
  2. 效率高

1.printf

p r i n t fprintfprintf 函数为格式输出函数,其功能是按照用户指定的格式,将指定的数据输出到屏幕上:

printf("格式控制字符串", 输出表项);

格式控制字符串有两种:格式字符串、非格式字符串,非格式字符串在输出的时候原样打印;格式字符串是以 %\%% 开头的字符串,后面跟不同格式字符,用来说明输出数据的类型、形式、长度、小数位数等,就像一个模具,用来控制希望得到的物体的形态

类型格式字符串
int%d
float%f
double%lf
char%c
char[]%s
long long%lld

格式字符串的形式为:% [对齐方式][输出最小宽度] [.精度] 类型

  1. 对齐方式:- 表示左对齐,不填表示右对齐(没有% +的写法),默认为右对齐,如 % -d,表示左对齐
  2. 最小宽度NN N:当实际宽度小于NN N时,用指定的字符填充剩余部分(默认以空格填充),使其长度为NN N并输出;当实际宽度大于NN N时,按实际位数输出,如 % 010d,表示最小宽度为 10,不足部分用 0 填充
  3. 精度控制:这里精度表示保留小数点后几位,如 %.2f,表示保留小数点后两位

i. 输出整数

代码实现:

#includeint main(){int a=100;printf("%d\n", a);//100printf("%5d\n", a); //100printf("%05d\n", a);//00100printf("%.2d\n", a);//100.00return 0;}

ii. 浮点数

浮点数有两种类型: f l o a t 、 d o u b l efloat、doublefloatdouble

  • floatfloat float是单精度浮点数类型,通常在内存中占据44 4个字节,它可以表示大约6到7位的有效数字
  • doubledouble double是双精度浮点数类型,通常在内存中占据88 8个字节,它可以表示大约15到16位的有效数字

当使用 p r i n t fprintfprintf输出时,若不控制精度,均默认保留至小数点后 666

代码实现:

#includeint main(){float a=2.01;printf("%f\n", a); //2.010000printf("%.3f\n", a); //2.010printf("%10f\n", a); //2.010000printf("%10.3f\n", a); // 2.010double b=5.01;printf("%lf\n", b);//5.010000printf("%.3lf\n", b);//5.010return 0;}

iii.字符 & 字符串

const char*char[] 都可以用来表示字符串:

  • const char* 是一个指向常量字符的指针,即指针指向的字符内容是不可更改的
  • char[] 是一个字符数组,可以通过索引修改字符串

均可以通过 % s\%s%s 得到整个字符串

代码实现:

#includeint main(){char a = 'A';printf("%c\n", a);//Aconst char* str="Hello World!";char buf[]="Hello World!";printf("%s\n", str);//Hello World!printf("%s\n", buf);//Hello World!return 0;}

2.scanf

s c a n fscanfscanf 函数称为格式输入函数,即按照格式字符串的格式,从键盘上把数据输入到指定的变量之中,其调用的基本格式为:

scanf("格式控制字符串",输入项地址列表);

其中,格式控制字符串的作用与 p r i n t fprintfprintf函数相同,地址表项中的地址给出各变量的地址,地址是由地址运算符 & 后跟变量名组成的

为什么需要加上取址符 & ?

因为 s c a n fscanfscanf函数需要知道变量的内存地址才能将输入的值存储到正确的位置上,当我们使用 s c a n fscanfscanf函数时,我们需要将输入的值存储到一个变量中,而不是直接给出变量的名称,通过使用&操作符,我们可以获取该变量的内存地址,从而告诉 s c a n fscanfscanf函数将输入的值存储到这个地址所对应的内存位置上

特别地,对于数组而言,可以不用加 &,因为数组名本身就是指向数组首地址的指针

scanfscanf scanf 的返回值:

s c a n f ( )scanf()scanf() 函数返回成功读入的项目的个数,如果它没有读取任何项目(比如它期望接收一个数字而实际却输入的一个非数字字符时就会发生这种情况),scanf() 则会返回 000

当它检测到 文件末尾 (end of file) 时,它返回 E O FEOFEOF E O FEOFEOF 在是文件 stdio.h 中的定义好的一个特殊值,通常 #define 指令会将 E O FEOFEOF 的值定义为 − 1-11,则可以结合 w h i l ewhilewhile 循环实现多行输入,通常有两种 c 的写法:

  1. while(scanf("%d",&a) != EOF)
  2. while(~scanf("%d",&a))

代码实现:

#include int main() { int a,b; while(scanf("%d %d", &a, &b) != EOF) printf("%d\n", a+b); return 0;}

也就是说,若不按下 Ctrl+Z,循环就不会结束

WindowsWindows Windows允许通过键盘模拟文件尾:Ctrl+ZCtrl+Z Ctrl+Z

i.整数

代码实现:

#include int main(){int a, b;scanf("%d %d",&a, &b);//1 2printf("%d,%d\n", a, b);//1,2return 0;}

ii.浮点数

代码实现:

#include int main(){double c, d;scanf("%lf %lf", &c, &d); //1 2printf("%.2lf,%.3lf\n", c, d);//1.00,2.000return 0;}

iii. 字符 & 字符串
  1. 输入单个字符

代码实现:

#includeint main(){char e, f;scanf("%c %c", &e, &f); //a bprintf("%c,%c\n", e, f);//a,breturn 0;}

  1. 利用 %s 输入字符串

注意:%s 输入遇到空格或者回车就会停下(截断)

#includeint main(){char s[10];scanf("%s", s);printf("%s", s);return 0;}

输出结果:

  1. 利用正则表达式输入字符串

[] 是正则表达式,表示只要不是回车就读入字符串(空格不会截断)

#includeint main(){char s0[15];scanf("%[^\n]", s0);printf("%s", s0);return 0;}

输出结果:

3.特殊用法

i. * 的应用

′∗′ ‘*’ p r i n t fprintfprintf 中的应用

控制宽度和精度:假如在输出时,不想事先指定字段宽度 ///精度,而是希望由程序来制定该值,则可以在字段宽度 ///精度部分使用 * 代替数字来达到目的,同时需要在后面多增加一个参数来告诉函数宽度 ///精度的值是多少,具体的说,如果转换说明符为%*d,那么参数列表中应该包括一个*的值和一个d的值,来控制宽度 ///精度和变量的值

代码示例:

#includeint main(void){printf("%0*d\n", 5, 10); //00010printf("%.*f\n", 2, 10.12345); //10.12printf("%*.*f\n", 5, 1, 10.0); // 10.0return 0;}

′∗′ ‘*’ s c a n fscanfscanf 中的应用

忽略赋值* s c a n fscanfscanf 函数中通常用来指定忽略某个输入值的赋值,这在处理输入时非常有用,因为有时候我们可能只想跳过一些输入,而不需要将其赋值给特定的变量

例如:有以下的输入数据 “10 20”,但是我们只想读取第二个整数而不关心第一个整数,则可以使用 *

#include int main(){int num;scanf("%*d %d", &num);//10 20printf("%d", num);//20return 0;}

ii. %n 的应用

% n\%n%n p r i n t fprintfprintf 中的应用

统计字符数 p r i n t f ( )printf()printf() 中,%n 是一个特殊的格式说明符,它不打印某些内容,而是会将目前为止打印输出字符的数量存储到参数列表中指向的变量中

代码示例:

#includeint main(){int k=0;printf("hello %nworld", &k);//输出 hello worldprintf("%d",k); //6return 0;}

% n\%n%n s c a n fscanfscanf 中的应用

统计字符数 s c a n f ( )scanf()scanf() 中,%n 会将目前为止读入字符的数量存储到参数列表中指向的变量中

代码示例:

#include int main() {char str[100];int count;scanf("%s%n", str, &count); //abcdprintf("%d\n", count);//4return 0;}

iii. %[] 的应用

常见用法

  1. %[0-9]: 表示只读入’0’到’9’之间的字符
  2. %[a-zA-Z]: 表示只读入字母
  3. %[^\n]: 就表示读入除换行符之外的字符,^ 表示除…之外
  4. %k[^=]: 读入”=”号前的至多kk k个字符

当读取到范围之外的字符时,就会做截取,也就是之后的数据将不会放入字符串中

代码示例:

//1#include int main() {char number[10];scanf("%[0-9]", number);//123abcprintf("%s\n", number); //123return 0;}//2#include int main() {char letters[20];scanf("%[a-zA-Z]", letters);//abc121efgprintf("%s\n", letters);//abcreturn 0;}//3#includeint main(){char s0[15];scanf("%[^\n]", s0);//123abc abcprintf("%s", s0); //123abc abcreturn 0;}//4#include int main() {char str[20];scanf("%10[^=]", str);//123=abcprintf("%s\n", str);//123return 0;}

二.C++中的输入输出

现在来到 C++ 中,其输入输出通过头文件 #include 来完成,其命名空间为 s t dstdstd

流( S t r e a mStreamStream)是一个抽象概念,用于表示数据的序列,它可以是从一个地方到另一个地方的数据传输通道,流可以用于从文件、内存、网络或其他设备中读取数据,也可以用于向这些地方写入数据,而 C++ 的 I / OI/OI/O 就是发生在流:

  • 输入流(istreamistream istream):用于从数据源中读取数据,比如从键盘读取用户输入、从文件中读取数据、从网络连接中读取数据等
  • 输出流(ostreamostream ostream):用于向目标位置写入数据,比如将数据输出到屏幕、写入到文件、发送到网络等

1.cout

cout 是 C++ 标准库中的标准输出流对象

cout << "hello world" << endl;

<< 是输出运算符,左侧必须是 o s t r e a mostreamostream 对象,右侧是要打印的值:此运算符将给定的值写到给定的 o s t r e a mostreamostream 对象中,计算结果就是我们写入给定值的那个 o s t r e a mostreamostream 对象

cout 是一个输出对象,输出语句本质上就是不断创造 o s t r e a mostreamostream 对象的过程,第一个计算的结果是第二个的输入,从而形成链条,最终将所有信息输出到屏幕上,例如:

cout << "hello" << endl;等价于1.cout << "hello";2.cout << endl;

第一个 << 给用户打印一条消息,这个消息是一个字符串字面值常量,在双引号之间的文本会被打印到标准输出;第二个 << 打印 endl,这是一个被称为操作符的特殊值,写入 endl 的效果是结束当前行,并将与设备关联的缓冲区中的内容刷到设备中

i. 缓冲区(buffer)

当程序向输出设备(比如屏幕、打印机)输出数据时,数据通常不是立即传递到设备上,而是先存储到缓冲区中,这样做的好处是可以减少向设备频繁传输数据的开销,而是利用缓冲区将数据一次性传输,从而提高效率,类似地,当数据从输入设备(比如键盘、网络)输入进来时,也会先存储到输入缓冲区中,程序可以逐步读取这些数据。

在 C++ 中,对于标准输出流 c o u tcoutcout,也存在一个输出缓冲区,当程序使用 c o u tcoutcout 进行输出时,数据首先被存储在输出缓冲区中,直到缓冲区满了或者遇到显式的刷新操作时,数据才会被真正输出到屏幕上:

  • 使用 std::flush 操纵符:手动刷新输出缓冲区
  • 输出末尾添加 std::endl :自动刷新缓冲区并插入换行符

ii. cout之格式化输出

通过 #include 头文件来实现格式化的输出:

  1. std::setw(int n): 设置域宽为 nn n,默认为右对齐
  2. std::setprecision(int n): 设置浮点数的精度为 nn n 位小数
  3. std::setfill(char c): 设置填充字符为 cc c
  4. std::left: 设置输出在设定的宽度内左对齐
  5. std::right: 设置输出在设定的宽度内右对齐
  6. std::boolalpha: 将布尔类型的输出从 0/10/1 0/1 改为 true/falsetrue/false true/false
  7. std::hex: 以十六进制形式输出值
  8. std::oct: 以八进制形式输出值
  9. std::dec: 以十进制形式输出值

代码示例:

#include #include int main() {int n = 255;double pi = 3.1415926;std::cout << "Default: " << n << " " << pi << std::endl;std::cout << "setw: " << std::setw(10) << n << " " << std::setw(10) << pi << std::endl;std::cout << "setprecision: " << std::setprecision(4) << n << " " << std::setprecision(3) << pi << std::endl;std::cout << "setfill: " << std::setfill('*') << std::setw(10) << n << " " << std::setfill('#') << std::setw(10) << pi << std::endl;std::cout << "left/right: " << std::left << std::setw(10) << n << std::right << std::setw(10) << pi << std::endl;std::cout << "boolalpha: " << std::boolalpha << true << " " << false << std::endl;std::cout << "Hex/Oct/Dec: " << std::hex << n << " " << std::oct << n << " " << std::dec << n << std::endl;return 0;}

输出结果:

2.cin

cin 是 C++ 标准库中的标准输入流对象,在查看输入流的时候, c i ncincin 会自动跳过空白(空格,换行符,制表符)直到遇到非空白字符

int a, b;cin >> a >> b;

>> 是输入运算符,其左侧为一个 i s t r e a mistreamistream 运算对象,再接受一个变量作为右侧运算对象,可以连续读取多个数据,并将它们存储到不同的变量中

一些简单代码示例:

  1. 整数
#include using namespace std;int main(){int a, b;cin >> a >> b;cout << a << ' ' << b << endl;return 0;}

  1. 浮点数
#include using namespace std;int main(){double c, d;cin >> c >> d;cout << fixed << setprecision(3) << c << ' ' << d << endl;//保留三位有效数字return 0;}

  1. 单个字符
#include using namespace std;int main(){char ch;cin >> ch;cout << ch << endl;return 0;}

  1. 字符串
#include using namespace std;int main(){char str[10];cin >> str; //cin输入字符串也是遇到空格或回车就会结束cout << str << endl;return 0;}

注意 c i n > >cin>>cin>> 接受一个字符串时,遇到 ‘空格’、‘ t a btabtab’、’回车’就会结束,当需要输入包含空格的整行文本时,可以使用 std::getline 函数来读取输入流,而不是直接使用 >> 运算符,这样可以确保整行文本被完整地存储到字符串中

getline(cin, string)

  • 头文件:#include
  • 第一个参数是输入流(比如 std::cinstd::cin std::cin 或文件流),第二个参数是用来存储读取的文本的字符串

代码示例:

#include #include using namespace std;int main(){string s;getline(cin, s);//可以实现整行输入cout << s << endl;return 0;}

i. cin.get()

cin.get(字符变量名)

  • 每次接受单个字符,逐个字符读取
  • 当读取的输入流有空格或回车时,会读取空格或回车,并将其视为普通字符,而不会将其作为流结束的标志来处理:ASCIIASCII ASCII 中,空格−32-32 32,回车−10-10 10

代码示例:

#include using namespace std;int main (){char a, b;cin.get(a);cin.get(b);cout<<a<<" "<<b<<endl;cout<<(int)a<<" "<<(int)b<<endl;return 0;}

输出:

示例1:输入 a[回车]aa97 10结果得到的是字母 a 和 换行符(\n)示例2:输入 a[空格]b[回车]a ba97 32结果得到的是字母 a 和 空格符

cin.get(字符数组名,接收字符数目,结束符)

  • 接收一定长度的字符串
  • 接受字符数目:如 cin.get(s, 5) 会读取最多 55 5 个字符到数组 ss s 中,并且在必要时加上空字符'\0' 以表示字符串的结束,也就是说,实际读取的长度会减 11 1,因为结尾的 '\0' 占了一位
  • 结束符:默认为回车 '\n',也就是说,可以接受空格,不接受回车,但不读取不代表丢弃,回车仍然在输入缓冲区内

代码示例:

#include using namespace std;int main (){char ch1,ch2[10];cin.get(ch2,5); //在不遇到结束符的情况下,最多可接收5-1=4个字符到ch2中,注意结束符为默认Entercin.get(ch1); //读取单个字符cout<<"ch2="<<ch2<<endl;cout<<ch1<<"="<<(int)ch1<<endl;return 0;}

输出:

示例1:输入 a[回车]ach2=a=10由于第二个就读取到了换行符,因此直接结束,之后换行符被第二个 cin.get() 读取示例2:输入长度为 7 的字符串 abcdefgabcdefgch2=abcde=101

cin.get()

  • 无参数,可用于舍弃输入流中的不需要的字符,或者舍弃回车 '\n',弥补 cin.get(字符数组名,字符数目,结束符) 的不足

代码示例:

#include using namespace std;int main(){char ch1,ch2[10];cin.get(ch2,5);cin.get();//舍弃一个缓冲区中的字符cin.get(ch1);cout<<ch2<<endl;cout<<ch1<<"\n"<<(int)ch1<<endl;return 0;}

输出:

示例1:输入长度为 7 的字符串abcdefgch2=abcdf=102结果表明,第二个 cin.get() 跳过了字符 'e',使得第三个 cin.get() 读取到了 'f'示例2:输入 1a[回车]bcdef1abcdefch2=1ab=98同理,第二个 cin.get() 跳过了换行符
ii. cin.getline()

cin.getline(字符数组名,接收长度,结束符)cin.get(...)的用法极为类似,但存在几个注意点:

  • cin.get()cin.get() cin.get() 当输入的字符串超长时,不会引起 cincin cin 函数的错误,后面若有 cincin cin 操作,会继续执行,只是直接从缓冲区中取数据,但是 cin.getline()cin.getline() cin.getline() 当输入超长时,会引起 cincin cin 函数的错误,后面的 cincin cin 操作将不再执行

代码示例:

#include using namespace std;int main(){char ch1, ch2[10];cin.getline(ch2, 5);cin>>ch1;cout<<ch2<<endl;cout<<ch1<<"\n"<<(int)ch1<<endl;return 0;}

输出结果:

可以看到,此时出现输入超长, c h 2 = 1234ch2=1234ch2=1234,此时缓冲区剩余:5[回车],但是结果得到了 000(g++环境下),说明 c i ncincin 已失效!

  • cin.get()cin.get() cin.get() 每次读取一整行并把由 Enter键 生成的换行符 '\n' 留在输入队列中,然而 cin.getline()cin.getline() cin.getline() 每次读取一整行后会将换行符'\n'丢弃 !

代码示例:

#include using namespace std;int main() {cout << "Enter your name:";char name[15];cin.getline(name, 15);//输入xjc(enter)cout << "name:" << name << endl;char ch;cin.get(ch);// 输入123(enter) 注:因为cin.getline把最后一个换行符丢弃了,所以此处ch读取字符'1'cout << ch << "=" << (int)ch << endl;//输出49'1'的ASCII码值return 0;}

输出结果:

iii. 流状态

流状态是顾名思义就是输入或输出流的状态, c i ncincin c o u tcoutcout 对象都包含了一个描述流状态的变量( i o s t a t eiostateiostate 类型),它由三个元素组成: e o f b i t , f a i l b i t , b a d b i teofbit,failbit,badbiteofbitfailbitbadbit,其中每一个元素都由一位来表示( 111代表是, 000代表否)

成员描述
eofbit到达文件尾则此位设置为1
badbit如果流被破坏则此位设置为1
failbit如果输入未能读取预期的字符或输出操作没有写入预期字符,则此位设置为1
goodbit流状态正常,也代表所有位为 0 即表示 0
good()如果流正常则返回 true
eof()如果 eofbit 为 1 返回 true
bad()如果 badbit 为 1 返回 true
fail()如果 badbit 或 failbit 为 1 返回 true
rdstate()返回流状态
exceptions()返回一个位掩码,指出哪些标记将导致异常发生
exceptions(iostate ex)设置哪些状态将导致 clear() 引发异常
clear(iostate s)将流状态设置为 s,s 默认为 0 即goodbit
setstate(iostate s)设置与 s 中对应位的流状态,其他位保持不变,相当于用 s 与 当前流状态做 “或运算”

来看一个缓冲区未读取完,导致 f a i l b i t = 1failbit=1failbit=1 的例子:

#include using namespace std;int main(){char ch, str[20];cin.getline(str, 5);cout<<"flag:"<<cin.good()<<endl;// 查看goodbit状态,即是否有异常cin.clear();// 清除错误标志cout<<"flag:"<<cin.good()<<endl;// 清除标志后再查看异常状态cin>>ch;cout<<"str:"<<str<<endl;cout<<"ch :"<<ch<<endl;system("pause");return 0;}

输出结果:

可以看出,因输入缓冲区未读取完造成输入异常 cin.good()=false

通过cin.clear()可以清除输入流对象 c i ncincin的异常状态,不影响后面的 cin>>ch 从输入缓冲区读取数据,因为 cin.getline 读取之后,输入缓冲区中残留的字符串是:5[回车],所以 c i n > > c hcin>>chcin>>ch 555 读取并存入 c hchch,打印输入并输出 555

如果没有 cin.clear()cin>>ch 将读取失败, c hchch 为空

输出结果:

三.取消同步流

在算法题中,涉及到大量数据读入的时候,通常避免使用cin读入数据而改用scanf,原因是scanf相对速度更快

解决方法:

  1. c i ncincin 效率低的原因一是在于默认 C++ 中的 c i ncincin (输入流对象) 与 C语言中的 s t d i nstdinstdin (输入流指针) 总是保持同步, c i ncincin 会把要输出的东西先存入缓冲区,进而消耗时间,通过关闭同步,可以有效提高 c i ncincin 效率,可以用 sync_with_stdio(0) 实现异步

  2. 默认情况下 c i ncincin 绑定的是 c o u tcoutcout,每次执行 << 的时候都要调用 f l u s hflushflush,当 cout 的缓冲区刷新的时候,cin 的缓冲区由于绑定的存在也同时进行了刷新,进而增加 I / OI/OI/O 负担,因此可以通过 tie(0) 解绑

关闭同步后, c i ncincin 的速度将与 s c a n fscanfscanf 相差无几:

int main(){//取消同步流ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);//其余操作不变int x;cin >> x;cout << x;system("pause");return 0;}

注意

  • 关闭同步之后请不要同时使用C与C++的读写方式,避免不必要的麻烦,如 printf(),scanf(),gets(),pus(),getchar() 不要与 cin,cout 共用
  • coutcout cout 中不要使用 endlendl endl,每次使用 endlendl endl,都要 flushflush flush 缓冲区,造成大量时间耗费
    ch$ 为空