备战蓝桥杯,第六章:C++语言的输入输出(下)

一.OJ题目输入情况汇总

在竞赛中的OJ题目中,一般关于输入场景总结为以下四类:

1.单组测试用例

(1)练习一:计算(a+b)/c的值

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
 int a, b, c;
 cin >> a >> b >> c;
 cout << (a + b) / c << endl;
 return 0;
}

本题目是单组测试用例的题目,代码首先创建了三个整型变量,分别作为计算式中的三个操作数。最后打印出计算结果即可。

(2)练习二:与7无关的数

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
 int n = 0;
 cin >> n;
 int i = 1;
 int sum = 0;
 while (i <= n)
 {
 if (i % 7 != 0 && i / 10 != 7 && i % 10 != 7)
 sum += (i * i);
 i++;
 }
 cout << sum << endl;
 return 0;
}

上述代码需要打印出一定范围的与7无关的数,本题目也是一个单组测试用例。首先需要我们遍历小于n的所有数,接下来我们要判断每次循环的数是否与7无关,如果无关则累加在创建的变量上即可。

2.多组测试用例

(1)测试用例组数已知

练习一:多组输入a+b

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
 int n = 0;
 int a = 0, b = 0;
 cin >> n;
 while (n--)
 {
 cin >> a >> b;
 cout << a + b << endl;
 }
 return 0;
}

本题目是多组输入的测试用例,需要输入n次。所以利用while循环,将输入的a+b打印在屏幕上。最终只要输入n次,就会打印出n个结果。

练习二:斐波那契数列

cpp 复制代码
#include <iostream>
using namespace std;
#include <iostream>
using namespace std;
int main()
{
 int n = 0;
 int a = 0;
 int i = 0;
 //计算好前30个斐波那契数,存储到ret数组中 
 //需要第⼏个就从下标⼏的位置去取 
 int ret[40] = {0, 1, 1};
 for(i = 3; i < 30; i++)
 {
 ret[i] = ret[i-1] + ret[i-2]; 
 }
 cin >> n;
 int z = 0;
 while (n--)
 {
 cin >> a;
 cout << ret[a] << endl;
 }
 return 0;
}

本题目有多组测试用例,需要输入n次数据。首先斐波那契数列每位上的数都是前面两个数相加,所以需要先列出分段函数的表达式,通过递归的方式得到第n位上的数值。因为本题有多组测试用例,所以需要用到while循环,每次输入一个数,都对应一个斐波那契数被打印出来。

练习三:制糊串

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
 string s, t;
 cin >> s >> t;
 int q;
 cin >> q;
 int l1, r1, l2, r2;
 while (q--) // 这种写法是常⻅的处理 q 次询问的⽅式 
 {
   cin >> l1 >> r1 >> l2 >> r2;
   string s1 = s.substr(l1 - 1, r1 - l1 + 1); // 注意这道题的字符串是从 1 开始计数的 
   string t1 = t.substr(l2 - 1, r2 - l2 + 1);
   if (s1 < t1)
   {
     cout << "yifusuyi" << endl;
   }
   else if (s1 > t1)
   {
     cout << "erfusuer" << endl;
   }
   else
   {
      cout << "ovo" << endl;
   }
 }
 return 0;
}

本题目有多组测试用例。这里给出编程遇到多组测试用例的小技巧:如果题目说有q次询问,意思就是程序要处理q组测试数据,也就是对应使用q次循环。我们要针对每次询问,给出一个结果。其实就是单组测试变成了q次循环的单组测试而已。

(2)测试用例未知

练习一:多组输入a+b

cpp 复制代码
#include <iostream>
using namespace std;
int a, b;
int main()
{
 while (cin >> a >> b)
 {
 cout << a + b << endl;
 }
 return 0;
}

上述代码遇到的是多组测试用例且测试用例数未知的情况,上述代码将cin>>a作为while循环判断的条件原因是:cin函数会返回一个流对象的引用,即cin函数本身。在C++语言中,流对象cin可以作为布尔值来检查流的状态。如果流的状态良好(没有发生错误),流的布尔值就为true。否则布尔值为false。在while循环中,循环的条件部分检查cin流的状态。如果成功读取到值就会返回true循环继续。如果读取失败(遇到输入结束符或无法读取目标次数),cin将会返回false,循环终止。

练习二:数字三角形的打印

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
 int n = 0;
 while(cin >> n)
 {
   for(int i = 1; i <= n; i++)
   {
     for(int j = 1; j <= i; j++)
     cout << "* ";
     cout << endl; 
   }
 }
 return 0;
}

上述代码针对输入变量n次输入,也是一个测试用例未知的多组测试用例的题目。所以此题需要循环n次,当打印三角形时,三角形的行数为输入的n,所以外层循环次数为n。三角形每行的列数逐渐增加,满足的规律是等于行数,所以内层循环的次数为该行所处的行数。

练习三:定位查找

cpp 复制代码
#include <iostream>
using namespace std;
const int N = 25;
int arr[N];
int main()
{
 int n = 0;
 int m = 0;
 while (cin >> n)
 {
   for (int i = 0; i < n; i++)
   {
     cin >> arr[i];
   }
   cin >> m;
   int i = 0;
   for (i = 0; i < n; i++)
   {
     if (m == arr[i])
   {
   cout << i << endl;
   break;
  }
 }
  if (i == n)
  cout << "No" << endl;
 }
 return 0;
}

本题目需要首先输入一个整型值n,作为需要判断数的组数。每组数需要查找目标数。一共需要查找n组数据。所以n次通过循环,每次判断一组数字内是否存在目标数。如果存在,则打印1;否则打印0。

(3)特殊值结束测试数据

练习一:字符统计

cpp 复制代码
//代码1 
#include <iostream>
using namespace std;
int main()
{
 int ch = 0;
 int Letters = 0;
 int Digits = 0;
 int Others = 0;
 while ((ch = getchar()) != '?')
 {
 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
 Letters++;
 else if (ch >= '0' && ch <= '9')
 Digits++;
 else
 Others++;
 }
 cout << "Letters=" << Letters << endl;
 cout << "Digits=" << Digits << endl;
 cout << "Others=" << Others << endl;
 return 0;
}

本题目具有多组测试数据,当输入?符号时,停止测试数据。所以需要用到循环,当输入函数读取到字符?时,循环终止。最终返回每种字符的数量。下面代码是另一种处理形式:

cpp 复制代码
//代码2 
#include <iostream>
using namespace std;
int main()
{
 string s;
 int Letters = 0;
 int Digits = 0;
 int Others = 0;
 getline(cin, s);
 s.pop_back(); //去掉? 
 for (auto ch : s)
 {
 if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'))
 Letters++;
 else if (ch >= '0' && ch <= '9')
 Digits++;
 else
 Others++;
 }
 cout << "Letters=" << Letters << endl;
 cout << "Digits=" << Digits << endl;
 cout << "Others=" << Others << endl;
 return 0;
}

本题目也可以用上一章节学到的函数getline,将结束标志设置成问号即可。或者像问题这样尾删问号,随后读取一行的所有数据也可以达到此题的多组测试用例的目的。

练习二:多组数据a+b

cpp 复制代码
#include <iostream>
using namespace std;
int main()
{
 int a = 0, b = 0;
 while (cin >> a >> b, a && b)
 {
 cout << a + b << endl;
 }
 return 0;
}

本题目规定的测试用例结束的标志是两数字同时为0,所以在while循环中带上这个条件即可应对多组测试用例。

二.输入时的特殊技巧

1.技巧一:含空格字符串的处理

编程小技巧:根据我们现在掌握的知识,含有空格的字符串,如果读取就有fgets、scanf、getchar、getline函数来应对。但是有时候根据题目的情况:不一定非要完整的带空格的字符串,而是想要处理用空格隔开的字符。为了更加方便,也避免处理空格字符的问题,我们就需要类似于下方代码的解法:

练习:统计数字字符的个数

解法一:读取整个带空格的字符串

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
 string s;
 getline(cin, s); // 读⼊⼀⾏字符串 
 int ret = 0;
 for (auto ch : s) // 遍历每⼀个字符 
 {
   if (ch >= '0' && ch <= '9')
   {
     ret++;
   }
 }
 cout << ret << endl;
 return 0;
}

上述代码通过我们学习的getline函数读取一行输入的数据,随后遍历字符串进行下一步的判断。因为这个函数并没有忽略空白字符,所以并没有达到简便的目的,所以更改为下方代码:

解法二:按照多个单词分析

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
 string s;
 int cnt = 0;
 while (cin >> s)
 {
   for (auto c : s)
   {
     if (c >= '0' && c <= '9')
     cnt++;
   }
 }
 cout << cnt << endl;
 return 0;
}

利用cin函数的返回值,当读取失败时循环终止。每次循环统计字符是否为数字,是的话将计数器加一,否则进入下次循环。

2.技巧二:数字的特殊处理

当程序运行起来的时候,在控制台输入的123,这里的123是字符,并非一百二十三。123是一个字符的序列。随后程序会根据代码的数据类型,将123解析成整型的123。在打印时,需要将整型的数字解析成字符串打印在屏幕上。所以在遇到题目的数字,我们不仅可以将其看成整型数字,也可以看成字符串序列。从而使数字处理更加方便。

cpp 复制代码
int num = 0;
cin >> num;//输⼊123, 就被解析成整数 
string s;
cin >> s; //输⼊123, 就被解析成字符串 

这里的解析方式,主要依赖编译器对变量数据类型的识别,根据数据类型再对字符串数据转换为对应的数据类型。

练习:小乐乐改数字

解法一:当作整型值解析

cpp 复制代码
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
 int n;
 cin >> n;
 int ret = 0;
 int i = 0; // 标记⼀下此时处理到多少位 
 while (n)
 {
 if (n % 10 % 2 == 1) // 如果这⼀位是奇数 
 {
 ret += pow(10, i); // pow(a, b) 求 a的b次⽅  
 }
 n /= 10; // 把最后⼀位⼲掉 
 i++; // 去判断下⼀位 
 }
 cout << ret << endl;
 return 0;
}

当我们将123当作整数来解析时,需要我们利用循环得到每一位的数字,随后将其改变后乘对应的权。最后将所有的权进行相加。得到的就是题目要求的结果。

解法二:当作字符串解析

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;
int main()
{
 string s;
 cin >> s;
 for (int i = 0; i < s.size(); i++) // 数字字符与对应的数的奇偶⼀致 
 {
 if (s[i] % 2)
 {
 s[i] = '1';
 }
 else
 {
 s[i] = '0';
 }
 }
 cout << stoi(s) << endl; // 转换成数字输出 
 return 0;
}

但是当我们将123当作字符串来接受,我们只需要根据下标进行更改每一位上的值,随后打印的时候将字符串类型强制类型转换位整型即可。

三.scanf/printf和cin/cout函数的对比

scanf/printf函数是C语言的标准输入输出函数,而cin/cout函数是C++语言的标准输入输出流对象。它们各自有优缺点,整体来说cin和cout会更加方便,但有些时候会不得不使用scanf和printf函数。

1.格式控制的差异

  • C语言的输入输出函数不能自动识别输入数据的类型,需要手动指定格式字符串,容易出现格式错误。开发者需要确保格式字符串与变量类型匹配,否则会导致未定义行为。
  • cin和cout会根据变量类型自动处理输入输出的数据,避免格式化的错误,相较于C语言的输入输出函数更加方便。
  • C语言的输入输出函数格式化输出更加精确直观,适合复杂格式的输入输出。
cpp 复制代码
#include <cstdio>
#include <iostream>
using namespace std;
int main()
{
 float a = 3.50;
 double d = 16.50;
 
 cout << "cout: " <<a << " "<< d <<endl;
 printf("printf: %f %lf\n", a, d);
 return 0;
}

根据上面的代码可以看出:

  1. cout默认不会输出六位小数,自动忽略小数后多余的0;printf函数打印浮点数时,会默认打印小数点后6位。
  2. cout函数在输出的时候不需要指定格式,printf函数则需要指定具体的格式

2.性能差异

(1)案例演示

结论:C语言的输入输出函数比较于C++的输入输出函数更快,效率更高。

原因:C++的函数需要考虑兼容C语言的输入输出,封装实现更加复杂,通常比C语言的输入输出函数稍慢,但这种差异在大多数场景下可以忽略不记。但是在竞赛中,输入输出量较大时,使用C++函数会存在超时的问题,而C语言的函数不会有类似的问题。下面是两个案例:

案例一:数字游戏

使用C++输入输出函数的代码演示:

cpp 复制代码
#include <iostream>
using namespace std;
int t, x;
int main()
{
  cin >> t;
  while (t--)
  {
     cin >> x;
     int ret = 0;
     while (x)
     {
         int count = 0, high = 0;
         int tmp = x;
         while (tmp)
         {
             //计算最右边的1代表的值 
             int low = tmp & -tmp;
             //如果low中剩余的1就是最后⼀个1 
             //就是最左边的1 
             if (tmp == low)
             {
                 high = low;
             }
             //去掉最右边的1 
             tmp -= low;
             count++;
         }
         if (count % 2 == 0)
         {
             x -= high;
         }
         else
         {
             x ^= 1;
         }
         ret++;
    }
 cout << ret << endl;
 }
 return 0;
}

使用C语言的输入输出函数的代码演示:

cpp 复制代码
#include <iostream>
using namespace std;
int t, x;
int main()
{
  scanf("%d", &t);
  while (t--)
  {
     scanf("%d", &x);
     int ret = 0;
     while (x)
     {
         int count = 0, high = 0;
         int tmp = x;
         while (tmp)
         {
             //计算最右边的1代表的值 
             int low = tmp & -tmp;
             //如果low中剩余的1就是最后⼀个1 
             //就是最左边的1 
             if (tmp == low)
             {
                 high = low;
             }
             //去掉最右边的1 
             tmp -= low;
             count++;
         }
         if (count % 2 == 0)
         {
             x -= high;
         }
         else
         {
             x ^= 1;
         }
         ret++;
    }
 printf("%d\n", ret);
 }
 return 0;
}

案例二:求第k小的数

使用C++语言的输入输出函数

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main()
{
 int n, k;
 cin >> n >> k;
 for (int i = 0; i < n; i++)
 {
 cin >> arr[i];
 }
 sort(arr, arr + n);
 cout << arr[k] << endl;
 return 0;
}

使用C语言的输入输出函数

cpp 复制代码
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N = 5000010;
int arr[N];
int main()
{
 int n, k;
 cin >> n >> k;
 for (int i = 0; i < n; i++)
 {
 scanf("%d", &arr[i]);
 }
 sort(arr, arr + n);
 cout << arr[k] << endl;
 return 0;
}

我们可以看出这两个案例中,输入的数据量都比较大,在输入数据的时候使用cin函数,都会出现超时的问题。但是利用scanf函数就能正确的通过。这是因为两函数的性能差异导致的。接下来我们讲解下C++语言的输入输出函数的性能优化:

(2)性能优化

<1>背景

在C++语言中,标准输入输出流是由C++标准库提供的;而在C语言中,标准输入函数是由C标准库提供的,由于C++是C语言发展来的语言,C++标准库的输入输出系统需要与C标准库的流对象同步在一起。这种操作意味着每次使用C++语言的输入输出函数时,都会自动刷新C标准的缓冲区,以确保C++和C的输入输出是一致的。

在默认情况下,cin和cout函数之间存在绑定的关系。这种绑定意味着每当cin函数读取数据时,任何之前通过cout函数输出的内容都会被强制的刷新到屏幕上,这种机制保证了输出内容能够立即显示给用户,这对于交互程序非常有用。但是这种绑定可能导致性能问题,特别是在需要频繁读取大量数据的情况下。这是因为每次从cin函数读取数据都会触发一次刷新缓冲区的操作,即使实际上没有进行输出操作,也会浪费时间,影响性能。

<2>ios::sync_with_stdio(false)

优化做法

调用ios::sync_with_stdio(false)可以关闭C++标准库与C语言标准库之间的输入输出同步。这就会导致:

  • C++标准库的cin和cout函数将不会与C语言的stdin和stdout同步。
  • cin和cout的输入输出操作将不会自动刷新C语言的标准库缓冲区。

原理

默认情况下,调用该内容会使cin和cout等流对象与stdin和stdout等流对象同步。这种同步操作会在每次输入输出操作时确保两数据之间的数据一致性,但会增加性能的开销。当调用该内容时,C++标准库会解除这种同步,从而允许cin和cout函数的输入输出操作以更高效率独立进行。

注意事项

混用C和C++的输入输出函数:如果混用两种语言的输入输出函数,则不建议调用ios::sync_with_stdio(false),因为这样会导致不可预期的后果。例如:输出顺序的错乱。

线程安全性:解除同步后,输入输出操作可能不再是线程安全的,特别是在多线程环境中谨慎使用。

<3>cin.tie(0)

作用

取消cin函数和cout函数之间的绑定,这样一来,当cin读取数据时,cout函数的缓冲区就不会被刷新。这样可以提高输入操作的速度,尤其需要处理大量数据的时候效果显著。

使用场景

高效能输入输出:在算法竞赛中或者需要告诉输入输出的程序中,解除cin和cout函数的绑定可以显著提高代码运行的效率。

非交互式程序:如果代码不是交互的,或者不需要实时显示给用户,那么解除绑定可以避免不必要的缓冲区刷新。

并行处理:当程序需要同时处理多个输入输出流时,解除绑定会减少同步带来的延迟。

注意事项

虽然cin.tie(0)可以提高程序的性能,但是在某些依赖于默认绑定行为的程序中,取消绑定可能会导致程序的逻辑错误。

<4>代码演示
cpp 复制代码
#include <iostream>​
using namespace std;​
int main() ​
{​
    ios::sync_with_stdio(false);       // 取消C风格I/O的同步​
    cin.tie(0);                        // 解除cin与cout的绑定​
    int number;​
    cout << "请输入一个数字: ";​
    cin >> number;​
    cout << "您输入的数字是: " << number << endl;​
​
    return 0;​
}

3.优化方案和演示

通过下面案例,我们看看scanf和cin函数的差异,然后再想想如何优化?

C语言输入输出函数的案例:

cpp 复制代码
#include<iostream>
#include<ctime>
#include<cstdio>
using namespace std;
const int num = 10000000;
int main()
{
 int i, x;
 //freopen是将stdin重定向到⽂件 
 //意思是scanf可以⽂件中读取数据 
 freopen("data.txt", "r", stdin);
 clock_t t1, t2;
 t1 = clock();
 for (i = 0; i < num; i++)
 {
 scanf("%d", &x);
 }
 t2 = clock();
 cout << "Runtime of scanf: " << t2 - t1 << " ms" << endl;
 return 0;
}

C++语言输入输出函数的案例:

cpp 复制代码
#include<iostream>
#include<ctime>
#include<cstdio>
using namespace std;
const int num = 10000000;
int main()
{
 //freopen是将stdin重定向到⽂件 
 //意思是cin可以⽂件中读取数据 
 freopen("data.txt", "r", stdin);
 int i, x;
 clock_t t1, t2;
 t1 = clock();
 for (i = 0; i < num; i++)
 {
 cin >> x;
 }
 t2 = clock();
 cout << "Runtime of cin: " << t2 - t1 << " ms" << endl;
 return 0;
}

通过上面的学习,我们知道了如何优化,接下来我们优化下看看成果:

cpp 复制代码
#include<iostream>
#include<ctime>
#include<cstdio>
using namespace std;
const int num = 10000000;
int main()
{
 ios::sync_with_stdio(false); //取消给C语⾔输⼊输出缓冲区的同步 
 cin.tie(0); //取消了cin和cout的绑定 
 freopen("data.txt", "r", stdin);
 int i, x;
 clock_t t1, t2;
 t1 = clock();
 for (i = 0; i < num; i++)
 {
 cin >> x;
 }
 t2 = clock();
 cout << "Runtime of cin: " << t2 - t1 << " ms" << endl;
 return 0;
}

根据上面的优化可以看出,优化后的C++语言的输入输出函数竟然比C语言的输入输出函数效率还高。

所以在今后在使用C还是C++输入输出函数抉择时,如果需要追求性能就用C语言的输入输出函数,或者优化版本的C++语言的输入输出函数;如果不追求性能,直接使用C++语言的输入输出函数即可。

小提示:

如果输入的数据量比较小(以内),用C语言和C++语言的输入输出函数都可以。

如果输入的数据量比较大(左右),就推荐使用C语言的输入输出函数,避免因为输入输出的开销导致代码的超时。

在大多情况下,C语言或C++语言的使用根据个人的习惯进行选择就行,其实有些时候,输入输出规模非常大的时候,C语言的输入输出函数也不能满足的时候,就会用到快速读写的方式。这个在后面的算法章节会讲到。

相关推荐
赤水无泪2 小时前
03 C++语言---预处理器
开发语言·c++
李余博睿(新疆)2 小时前
c++三级
c++
2401_832131952 小时前
模板编译期机器学习
开发语言·c++·算法
2401_838472512 小时前
单元测试在C++项目中的实践
开发语言·c++·算法
naruto_lnq2 小时前
移动语义与完美转发详解
开发语言·c++·算法
赤水无泪3 小时前
04 C++语言---运算符和符号
开发语言·c++
100分简历3 小时前
无图标简洁大方的简历模板下载
人工智能·面试·职场和发展·pdf·编辑器
v_for_van3 小时前
力扣刷题记录1(无算法背景,纯C语言)
算法·leetcode·职场和发展
从此不归路3 小时前
Qt5 进阶【10】应用架构与插件化设计实战:从「单体窗口」走向「可扩展框架」
开发语言·c++·qt·架构