1C++ Primer(第5版)

开始

C++程序的基本结构

程序结构编译链接iostream

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第1章:开始(Getting Started)

一、导读

1.1 本章学习目标

第1章是《C++ Primer》的开篇之作,它为读者提供了C++ programming 的第一个完整视角。不同于许多教材从语法细节入手,本章以一个完整的程序为线索,让读者在实践中感受C++的基本面貌。本章的核心学习目标包括:

  • 理解C++程序的基本结构:一个C++程序由哪些部分组成?main函数的特殊地位是什么?
  • 掌握iostream库的基本使用:如何使用cin和cout进行标准输入输出?
  • 了解编译和链接的过程:源文件如何变成可执行程序?预处理、编译、汇编、链接各做了什么?
  • 认识C++的多范式特性:C++同时支持过程式、面向对象和泛型编程,本章初步展示这些特性。
  • 理解命名空间的概念:为什么需要namespace?using声明的作用是什么?
  • 初步了解C++与C的关系和区别:C++在C的基础上增加了哪些特性?

1.2 为什么第一章如此重要

很多初学者会急于跳过第一章,直奔"有趣"的语法特性。这种做法是极其错误的。第一章的价值在于建立对C++的整体认知框架——你需要先知道C++程序长什么样、如何编译运行、基本的输入输出怎么做,才能在后续章节中理解那些抽象概念的实际用途。

C++是一门复杂的语言,它同时支持过程式编程、面向对象编程、泛型编程和函数式编程等多种范式。如果没有一个清晰的起点,很容易在语言的复杂性中迷失方向。本章通过最简单的示例,让你看到C++程序的基本轮廓,为后续深入学习打下坚实的基础。

1.3 核心问题引导

在阅读本章时,请带着以下问题思考:

#include 指令到底做了什么?为什么需要它?

std::cout << "Hello" 中的 << 运算符和算术中的位移运算符有什么关系?

return 0; 为什么是必须的?它向谁报告?

C++的编译过程和C语言有什么异同?

为什么C++引入了命名空间?不使用 using 声明会怎样?

C++的 .cpp 文件和 .h 文件应该如何组织?


二、核心概念详解

2.1 C++程序的基本结构

每一个C++程序都从一个main函数开始执行。main函数是程序的入口点,操作系统在运行程序时会调用这个函数。一个最简单的C++程序如下:

cpp
int main() {
    return 0;
}

这个程序什么都不做,直接返回0给操作系统。返回值0表示程序正常退出,非零值通常表示发生了某种错误。在C++中,main函数的返回类型必须是int,这是国际标准规定的。

一个更完整的程序通常包含以下几个部分:

cpp
#include <iostream>
#include <string>

int main() {
    std::string name;
    std::cout << "Enter your name: ";
    std::cin >> name;
    std::cout << "Hello, " << name << "!" << std::endl;
    return 0;
}

这个程序展示了C++程序的典型结构:

  • 预处理指令(#include):告诉预处理器在编译之前将指定头文件的内容包含进来
  • main函数:程序的入口点
  • 语句:每条语句以分号结尾
  • 返回语句:将控制权交还给操作系统

2.2 iostream库与标准输入输出

C++的输入输出通过iostream库实现,这是一个面向对象的IO系统。与C语言的 printf/scanf 不同,C++的IO系统基于流(stream)的概念,使用运算符 <<(输出)和 >>(输入)来操作数据。

2.2.1 标准输出 cout

std::cout 是标准输出流对象,通常连接到终端屏幕。使用插入运算符 << 将数据发送到输出流:

cpp
#include <iostream>

int main() {
    std::cout << "Hello, World!" << std::endl;
    std::cout << "The answer is " << 42 << std::endl;
    std::cout << "Pi is approximately " << 3.14159 << std::endl;
    
    // 链式输出
    std::cout << "Name: " << "Alice" << ", Age: " << 20 << std::endl;
    
    return 0;
}

std::endl 是一个特殊 manipulator,它的作用是输出一个换行符并刷新输出缓冲区。你也可以使用 \n 来换行,但 \n 不会刷新缓冲区。在大多数情况下,两者的区别不大,但在需要立即看到输出时(如调试),std::endl 更合适。

2.2.2 标准输入 cin

std::cin 是标准输入流对象,通常连接到键盘。使用提取运算符 >> 从输入流中读取数据:

cpp
#include <iostream>
#include <string>

int main() {
    int age;
    std::string name;
    
    std::cout << "Enter your name: ";
    std::cin >> name;
    
    std::cout << "Enter your age: ";
    std::cin >> age;
    
    std::cout << "Hello, " << name << "! You are " << age << " years old." << std::endl;
    
    return 0;
}

需要注意的是,>> 运算符在读取字符串时会以空白字符(空格、制表符、换行符)作为分隔符。如果需要读取包含空格的整行文本,应该使用 std::getline:

cpp
#include <iostream>
#include <string>

int main() {
    std::string line;
    std::cout << "Enter a sentence: ";
    std::getline(std::cin, line);
    std::cout << "You said: " << line << std::endl;
    return 0;
}

2.2.3 标准错误 cerr 和 clog

C++还提供了两个标准错误流:

  • std::cerr:标准错误输出,不经过缓冲区,直接输出到终端
  • std::clog:标准错误输出,经过缓冲区
cpp
#include <iostream>

int main() {
    int divisor = 0;
    if (divisor == 0) {
        std::cerr << "Error: Division by zero!" << std::endl;
        return 1;
    }
    std::cout << "Result: " << 10 / divisor << std::endl;
    return 0;
}

2.3 注释

C++支持两种注释方式:

  • 单行注释(//):从 // 开始到行末的所有内容都是注释
  • 多行注释(/* */):从 /* 开始到 */ 结束的所有内容都是注释
cpp
// 这是单行注释

/* 这是
   多行注释 */

int main() {
    int x = 42; // 行尾注释
    return 0;
}

多行注释不能嵌套。如果你尝试在 /* */ 注释内部再使用 /* */,编译器会在遇到第一个 */ 时就结束注释,导致意外的编译错误。因此,在需要嵌套注释的场景下,推荐使用单行注释。

2.4 编译和链接过程

当你编写完C++源代码后,需要经过一系列步骤才能生成可执行程序。这个过程通常由编译器驱动程序(如 g++ 或 clang++)自动完成,但实际上包含四个阶段:

2.4.1 预处理阶段

预处理器处理所有以 # 开头的指令。#include <iostream> 会被替换为 iostream 头文件的实际内容。预处理还包括宏展开、条件编译等操作。

bash
g++ -E main.cpp -o main.i

预处理后的文件 main.i 通常非常大,因为它包含了所有被包含的头文件的内容。

2.4.2 编译阶段

编译器将预处理后的C++代码翻译成汇编语言。这个阶段涉及词法分析、语法分析、语义分析、优化和代码生成。

bash
g++ -S main.i -o main.s

2.4.3 汇编阶段

汇编器将汇编语言翻译成机器指令,生成目标文件(.o 文件)。

bash
g++ -c main.s -o main.o

2.4.4 链接阶段

链接器将多个目标文件和库文件合并,解析符号引用,生成最终的可执行文件。

bash
g++ main.o -o main

在实际开发中,我们通常只需要一条命令:

bash
g++ main.cpp -o main

编译器驱动程序会自动完成上述所有步骤。

2.5 命名空间

命名空间(namespace)是C++解决名称冲突的重要机制。标准库中的所有名称都定义在 std 命名空间中。使用命名空间可以避免不同库之间的名称冲突。

cpp
#include <iostream>

// 方式1:使用完全限定名
int main() {
    std::cout << "Hello" << std::endl;
    return 0;
}

// 方式2:使用using声明
using std::cout;
using std::endl;

int main() {
    cout << "Hello" << endl;
    return 0;
}

// 方式3:使用using指令(不推荐在全局作用域使用)
using namespace std;

int main() {
    cout << "Hello" << endl;
    return 0;
}

在头文件中,应该避免使用 using 声明或 using 指令,因为这会影响所有包含该头文件的源文件,可能导致名称冲突。

2.6 C++与C的关系

C++最初被称为"C with Classes",它在C语言的基础上增加了面向对象编程、泛型编程等特性。C++兼容大部分C语法,但两者并不完全相同:

  • C++有更严格的类型检查
  • C++引入了引用、类、模板、异常处理等C没有的特性
  • C++的标准库(STL)比C标准库更加丰富
  • C++的IO系统(iostream)与C的IO系统(stdio)完全不同

在编写C++程序时,应该优先使用C++的特性,而不是简单地使用C的方式。例如,使用 iostream 而不是 stdio,使用 string 而不是字符数组,使用 vector 而不是原始数组。


三、实战案例

3.1 简单的计算器程序

cpp
#include <iostream>

int main() {
    double num1, num2;
    char op;
    
    std::cout << "Simple Calculator" << std::endl;
    std::cout << "Enter expression (e.g., 3 + 4): ";
    std::cin >> num1 >> op >> num2;
    
    double result;
    switch (op) {
        case '+':
            result = num1 + num2;
            break;
        case '-':
            result = num1 - num2;
            break;
        case '*':
            result = num1 * num2;
            break;
        case '/':
            if (num2 == 0) {
                std::cerr << "Error: Division by zero!" << std::endl;
                return 1;
            }
            result = num1 / num2;
            break;
        default:
            std::cerr << "Error: Unknown operator!" << std::endl;
            return 1;
    }
    
    std::cout << num1 << " " << op << " " << num2 << " = " << result << std::endl;
    
    return 0;
}

这个程序展示了基本的输入输出、switch语句和错误处理。程序首先读取用户输入的两个数字和运算符,然后根据运算符执行相应的计算,最后输出结果。

3.2 多文件程序

在实际项目中,代码通常分布在多个文件中。以下是一个简单的多文件程序示例:

cpp
// add.h - 头文件声明
#ifndef ADD_H
#define ADD_H

int add(int a, int b);

#endif

// add.cpp - 实现文件
#include "add.h"

int add(int a, int b) {
    return a + b;
}

// main.cpp - 主程序
#include <iostream>
#include "add.h"

int main() {
    int result = add(3, 5);
    std::cout << "3 + 5 = " << result << std::endl;
    return 0;
}

编译多文件程序时,需要将所有源文件一起传递给编译器:

bash
g++ main.cpp add.cpp -o calculator

或者分别编译再链接:

bash
g++ -c main.cpp -o main.o
g++ -c add.cpp -o add.o
g++ main.o add.o -o calculator

3.3 读取文件内容

cpp
#include <iostream>
#include <fstream>
#include <string>

int main() {
    std::ifstream file("example.txt");
    
    if (!file) {
        std::cerr << "Error: Cannot open file!" << std::endl;
        return 1;
    }
    
    std::string line;
    int line_number = 1;
    
    while (std::getline(file, line)) {
        std::cout << line_number << ": " << line << std::endl;
        line_number++;
    }
    
    file.close();
    return 0;
}

这个程序展示了文件IO的基本用法。std::ifstream 用于读取文件,std::getline 逐行读取内容。程序还展示了如何检查文件是否成功打开,以及如何处理文件结束的情况。


四、本章小结

第1章通过简单的示例,展示了C++程序的基本面貌:

程序结构:每个C++程序都有且仅有一个main函数作为入口点。程序由预处理指令、函数定义和语句组成。

输入输出:C++通过iostream库提供类型安全的IO操作。std::cout 用于输出,std::cin 用于输入,std::cerr 用于错误输出。

编译过程:C++程序经过预处理、编译、汇编、链接四个阶段生成可执行文件。理解这个过程有助于调试编译错误。

命名空间:命名空间解决了大型项目中的名称冲突问题。标准库的名称都在 std 命名空间中。

C++与C的关系:C++兼容大部分C语法,但提供了更丰富的抽象机制。在C++中应该优先使用C++的特性。

本章最重要的启示是:C++是一门多范式语言,它支持过程式、面向对象和泛型编程。在接下来的章节中,你将逐步了解这些范式的细节。不要急于求成,扎实地掌握每一个概念,才能在这门复杂的语言中游刃有余。


五、思考与练习

思考题

为什么C++的main函数返回类型必须是int?返回其他类型会怎样?

std::endl 和 \n 有什么区别?在什么场景下应该使用 std::endl?

为什么在头文件中不推荐使用 using namespace std;?

C++的编译过程中,哪个阶段处理模板实例化?

如果一个程序包含多个源文件,链接器是如何解析跨文件的符号引用的?

编程练习

编写一个程序,读取用户输入的名和姓,然后输出格式化后的完整姓名。

编写一个程序,读取一个整数文件,计算并输出所有整数的和、平均值、最大值和最小值。

编写一个多文件程序,实现一个简单的字符串工具库(包含反转、转大写、统计元音等功能),并在主程序中使用。


1/12开始
0%