第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++程序如下:
int main() {
return 0;
}这个程序什么都不做,直接返回0给操作系统。返回值0表示程序正常退出,非零值通常表示发生了某种错误。在C++中,main函数的返回类型必须是int,这是国际标准规定的。
一个更完整的程序通常包含以下几个部分:
#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 是标准输出流对象,通常连接到终端屏幕。使用插入运算符 << 将数据发送到输出流:
#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 是标准输入流对象,通常连接到键盘。使用提取运算符 >> 从输入流中读取数据:
#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:
#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:标准错误输出,经过缓冲区
#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++支持两种注释方式:
- 单行注释(
//):从//开始到行末的所有内容都是注释 - 多行注释(
/* */):从/*开始到*/结束的所有内容都是注释
// 这是单行注释
/* 这是
多行注释 */
int main() {
int x = 42; // 行尾注释
return 0;
}多行注释不能嵌套。如果你尝试在 /* */ 注释内部再使用 /* */,编译器会在遇到第一个 */ 时就结束注释,导致意外的编译错误。因此,在需要嵌套注释的场景下,推荐使用单行注释。
2.4 编译和链接过程
当你编写完C++源代码后,需要经过一系列步骤才能生成可执行程序。这个过程通常由编译器驱动程序(如 g++ 或 clang++)自动完成,但实际上包含四个阶段:
2.4.1 预处理阶段
预处理器处理所有以 # 开头的指令。#include <iostream> 会被替换为 iostream 头文件的实际内容。预处理还包括宏展开、条件编译等操作。
g++ -E main.cpp -o main.i预处理后的文件 main.i 通常非常大,因为它包含了所有被包含的头文件的内容。
2.4.2 编译阶段
编译器将预处理后的C++代码翻译成汇编语言。这个阶段涉及词法分析、语法分析、语义分析、优化和代码生成。
g++ -S main.i -o main.s2.4.3 汇编阶段
汇编器将汇编语言翻译成机器指令,生成目标文件(.o 文件)。
g++ -c main.s -o main.o2.4.4 链接阶段
链接器将多个目标文件和库文件合并,解析符号引用,生成最终的可执行文件。
g++ main.o -o main在实际开发中,我们通常只需要一条命令:
g++ main.cpp -o main编译器驱动程序会自动完成上述所有步骤。
2.5 命名空间
命名空间(namespace)是C++解决名称冲突的重要机制。标准库中的所有名称都定义在 std 命名空间中。使用命名空间可以避免不同库之间的名称冲突。
#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 简单的计算器程序
#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 多文件程序
在实际项目中,代码通常分布在多个文件中。以下是一个简单的多文件程序示例:
// 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;
}编译多文件程序时,需要将所有源文件一起传递给编译器:
g++ main.cpp add.cpp -o calculator或者分别编译再链接:
g++ -c main.cpp -o main.o
g++ -c add.cpp -o add.o
g++ main.o add.o -o calculator3.3 读取文件内容
#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++的编译过程中,哪个阶段处理模板实例化?
如果一个程序包含多个源文件,链接器是如何解析跨文件的符号引用的?
编程练习
编写一个程序,读取用户输入的名和姓,然后输出格式化后的完整姓名。
编写一个程序,读取一个整数文件,计算并输出所有整数的和、平均值、最大值和最小值。
编写一个多文件程序,实现一个简单的字符串工具库(包含反转、转大写、统计元音等功能),并在主程序中使用。