Skip to content

    一.C++ 输入与输出基础

    在 C++ 中,输入与输出(Input/Output, 简称 I/O)是程序与外界(如用户、文件、网络)交换信息的核心机制。你可以把它想象成程序与你对话的"嘴巴"(输出)和"耳朵"(输入)。

    C++ 提供了两种主要的 I/O 方式:

    1. C++ 风格 I/O(<iostream>:这是 C++ 的标准方式,基于“流”(Stream)的概念,是面向对象的、类型安全的。
    2. C 风格 I/O(<cstdio>:继承自 C 语言,基于函数(如 printfscanf),在某些特定场合(如算法竞赛)因其格式化能力和潜在速度而被使用。

    我们将从 C++ 风格的 <iostream> 开始,然后介绍 C 风格的 <cstdio>

    1.C++ 标准 I/O(<iostream>

    要使用 C++ 风格的 I/O,你必须包含 <iostream> 头文件。它为你提供了两个核心对象:cin(标准输入流,通常是键盘)和 cout(标准输出流,通常是屏幕)。

    基本输出:cout

    cout 与“插入运算符”(<<)配合使用,将数据“插入”到输出流中。

    C++

    Plain Text
    #include <iostream> // 包含输入输出流头文件
    
    // 'std' 是一个命名空间,cout 和 endl 都在里面。
    // "using namespace std;" 可以让我们在下面直接写 cout,
    // 否则你需要写 std::cout 和 std::endl。
    // 在大型项目中不推荐全局使用,但在教学和简单程序中很常见。
    using namespace std; 
    
    int main() {
        // 声明并初始化一个变量
        int age = 20;
        
        // 1. 打印字符串字面量
        cout << "Hello, C++!";
        
        // 2. 打印换行符 '\n'
        // '\n' 是一个转义字符,代表换行
        cout << '\n'; 
        
        // 3. 打印变量
        cout << "Your age is: ";
        cout << age;
        cout << '\n';
        
        // 4. 链式调用:将多个输出连接在一起
        cout << "Next year, you will be: " << (age + 1) << '\n';
        
        // 5. 使用 endl (end line)
        // endl 的作用是:1. 换行 2. 刷新输出缓冲区
        // 刷新缓冲区能确保你立即看到输出,但频繁使用可能比 '\n' 慢
        cout << "Goodbye!" << endl;
        
        return 0;
    }

    运行结果示例:

    Plain Text
    Hello, C++!
    Your age is: 20
    Next year, you will be: 21
    Goodbye!

    基本输入:cin

    cin 与“提取运算符”(>>)配合使用,从输入流中“提取”数据并存入变量。

    C++

    Plain Text
    #include <iostream>
    using namespace std;
    
    int main() {
        // 声明变量,准备接收输入
        int age;
        double height;
        
        // 提示用户输入
        cout << "Please enter your age: ";
        
        // 1. 从键盘读取一个整数,存入 'age'
        // cin 会自动跳过开头的空白(空格、Tab、换行)
        // 然后读取数据,直到遇到下一个空白或无效字符
        cin >> age;
        
        cout << "Your age is: " << age << endl;
        
        // 2. 链式读取:一次读取多个值
        // cin 会按顺序等待输入
        cout << "Enter your age and height (separated by a space): ";
        cin >> age >> height;
        
        cout << "New age: " << age << ", Height: " << height << endl;
        
        return 0;
    }

    运行结果示例 (用户输入 25,然后输入 30 1.75):

    Plain Text
    Please enter your age: 25
    Your age is: 25
    Enter your age and height (separated by a space): 30 1.75
    New age: 30, Height: 1.75

    2.C 风格 I/O(<cstdio>

    要使用 C 风格的 I/O,你需要包含 <cstdio> 头文件(在 C 语言中是 <stdio.h>)。它提供了 printf(格式化输出)和 scanf(格式化输入)等函数。

    C 风格输出:printf

    printf(print formatted)函数允许你按照指定的“格式化字符串”来输出数据。

    • 它通过格式说明符(format specifier,以 % 开头)来指定后续变量应被如何格式化。
    说明符类型示例
    %dint整数
    %fdouble浮点数
    %lfdouble(在 printf%f%lf 相同)
    %cchar单个字符
    %schar*C 风格字符串

    C++

    Plain Text
    #include <cstdio> // 包含 C 风格 I/O 头文件
    
    int main() {
        int age = 20;
        double height = 1.78;
        char grade = 'A';
        char name[] = "Alice"; // C 风格字符串(字符数组)
        
        // 1. 打印普通文本,使用 \n 换行
        printf("Hello, C-style output!\n");
        
        // 2. 打印变量
        // %d 会被 age 的值替换
        printf("Age: %d\n", age); 
        
        // 3. 打印多个变量
        // 格式说明符必须与变量的类型和顺序一一对应
        printf("Name: %s, Grade: %c, Height: %f\n", name, grade, height);
        
        // 4. 格式化:控制浮点数精度
        // %.2f 表示保留两位小数
        printf("Height (2 decimal places): %.2f\n", height);
        
        return 0;
    }

    运行结果示例:

    Plain Text
    Hello, C-style output!
    Age: 20
    Name: Alice, Grade: A, Height: 1.780000
    Height (2 decimal places): 1.78

    C 风格输入:scanf

    scanf(scan formatted)函数按照格式化字符串从标准输入读取数据。

    极其重要: scanf 在读取数据到变量时,必须传递变量的内存地址。我们使用“取地址运算符”(&)来获取地址。

    C++

    Plain Text
    #include <cstdio>
    
    int main() {
        int age;
        double height;
        
        printf("Please enter your age: ");
        
        // 1. 读取一个整数
        // 注意:我们传递的是 &age (age 的地址),而不是 age
        // scanf 会将读取到的值放入该地址对应的内存中
        scanf("%d", &age);
        
        printf("Your age is: %d\n", age);
        
        // 2. 读取多个值
        // %lf 用于读取 double 类型
        printf("Enter your age and height (separated by a space): ");
        scanf("%d %lf", &age, &height);
        
        printf("New age: %d, Height: %.2lf\n", age, height);
        
        // 3. 读取 C 风格字符串(字符数组)
        // 特例:数组名本身就代表地址,所以不需要 &
        char name[50];
        printf("Enter your name: ");
        scanf("%s", name); // 注意:name 前没有 &
        
        printf("Hello, %s!\n", name);
        
        return 0;
    }

    运行结果示例 (用户输入 25,然后输入 30 1.75,然后输入 Bob):

    Plain Text
    Please enter your age: 25
    Your age is: 25
    Enter your age and height (separated by a space): 30 1.75
    New age: 30, Height: 1.75
    Enter your name: Bob
    Hello, Bob!

    3.性能问题

    性能提示: 在算法竞赛中,如果 I/O 量非常大,cin/cout 默认的同步可能导致超时。可以通过在 main 函数开头添加 std::ios::sync_with_stdio(false); std::cin.tie(NULL); 来关闭同步,使其速度与 scanf/printf 媲美。


    4.处理整行输入(带空格的字符串)

    cinscanf("%s", ...) 的一个共同问题是:它们遇到空格就会停止读取。

    getline

    要读取一整行(包括空格),我们使用 getline 函数。它需要 <string> 头文件。

    C++

    Plain Text
    #include <iostream>
    #include <string> // 必须包含 <string> 才能使用 std::string 和 getline
    using namespace std;
    
    int main() {
        string name;
        
        cout << "Please enter your full name: ";
        
        // getline(cin, variable)
        // 它会从 cin 读取一行,存入 name,直到遇到换行符
        getline(cin, name); 
        
        cout << "Hello, " << name << "!" << endl;
        
        return 0;
    }

    运行结果示例 (用户输入 "John Doe"):

    Plain Text
    Please enter your full name: John Doe
    Hello, John Doe!

    5.I/O 的常见陷阱和注意事项

    1. scanf 忘记 &

      这是 C 风格 I/O 中最致命的错误。

      C++

      Plain Text
      int age;
      // 错误!没有 &。程序会尝试写入一个无效的内存地址
      // 极有可能导致 "段错误" (Segmentation Fault) 或程序崩溃
      scanf("%d", age); 
      
      // 正确
      scanf("%d", &age);
    2. cin 和 getline 混用

      这是一个非常隐蔽的陷阱。

      C++

      Plain Text
      int id;
      string name;
      
      cout << "Enter ID: ";
      cin >> id; // 用户输入 101,然后按回车
      
      // 此时,'101' 被 cin 读取了,
      // 但是换行符 '\n' 仍然留在输入缓冲区中
      
      cout << "Enter name: ";
      // getline 立即读到了那个被留下的 '\n'
      // 它认为这一行已经结束了,所以 name 是空的
      getline(cin, name); 
      
      cout << "ID: " << id << ", Name: '" << name << "'" << endl;
      
      // 运行结果:
      // Enter ID: 101
      // Enter name: ID: 101, Name: ''

      解决方法:getline 之前,使用 cin.ignore() 清除缓冲区中多余的换行符。

      C++

      Plain Text
      // ...
      cin >> id; 
      cin.ignore(); // 忽略掉缓冲区里的一个字符(那个换行符)
      getline(cin, name); // 现在就可以正常工作了
      // ...
    3. printf / scanf 格式与类型不匹配

      编译器通常会警告,但不会阻止你。

      C++

      Plain Text
      // 错误:用 %d (整数) 打印 %f (浮点数)
      printf("%d", 3.14); // 输出垃圾值
      
      // 错误:用 %f (浮点数) 打印 %d (整数)
      printf("%f", 10); // 输出垃圾值
      
      int x;
      // 错误:用 %f 读取整数
      scanf("%f", &x); // 结果未定义
    4. scanf("%s", ...) 的缓冲区溢出

      scanf("%s", ...) 不知道你的字符数组有多大,它会一直读取直到遇到空白。

      C++

      Plain Text
      #include <cstdio>
      
      int main() {
          // 只能安全存储 9 个字符 + 1 个 '\0'
          char str[10]; 
      
          printf("Enter a long string: ");
      
          // 如果用户输入 "HelloWorldThisIsTooLong"
          // scanf 会写入超过 10 个字节,破坏程序内存
          // 这是一个严重的安全漏洞!
          scanf("%s", str); 
      
          return 0;
      }

      解决方法: 使用 C++ 的 std::stringcin,它们会自动管理内存,没有溢出风险。或者在 C 风格中指定宽度 scanf("%9s", str);