C++ 是在 C 语言基础上发展而来的,增加了面向对象编程、模板、异常处理等特性,同时拥有非常强大的标准库(STL)。因为执行效率高、标准库完善,C++ 是算法竞赛和系统开发中最受欢迎的语言之一。
本站所有的算法代码都配有 C++ 版本,你学完基础之后可以在刷题过程中熟悉和应用这门语言。下面我们就开始学习 C++ 的基础语法。
Hello World 与程序结构
先看一个最简单的 C++ 程序:
拆解一下这个结构:
#include <iostream> // 引入输入输出库
using namespace std; // 使用标准命名空间
int main() { // 程序入口函数
// 你的代码写在这里
return 0; // 返回 0 表示程序正常结束
}#include <iostream> 是预处理指令,告诉编译器引入输入输出库(input/output stream),这样你才能用 cout、cin 这些功能。
using namespace std; 是告诉编译器你要使用标准命名空间。不写这行的话,每次使用标准库的东西都要加 std:: 前缀,比如 std::cout、std::endl。加了这行就可以省略前缀,写起来方便很多。
int main() 是程序的入口函数,C++ 程序运行时会自动找到并执行这个函数。int 表示这个函数返回一个整数,return 0 表示程序正常结束。
每条语句末尾要加分号 ;,这是 C++ 的规矩。漏了分号编译器会报错,这是新手最常犯的错误之一。
标准输出 cout
cout 配合 << 运算符(插入运算符)来输出内容,endl 表示换行:
cout 比 C 语言的 printf 方便的地方在于不用记格式化占位符(%d、%f 之类的),直接用 << 把变量丢进去就行,编译器会自动根据类型来输出。
endl 和 "\n" 都能换行,区别是 endl 会多一步刷新缓冲区的操作。在算法题中大量输出时,用 "\n" 会比 endl 快一些,不过一般不用太在意。
如果想控制浮点数的小数位数,需要引入 <iomanip> 头文件:
fixed 表示用定点小数格式(而不是科学计数法),setprecision(n) 指定保留 n 位小数。设置之后对后续所有输出都生效,不需要每次都写。
读取输入 cin
cin 配合 >> 运算符(提取运算符)来读取输入:
cin >> 默认以空格和换行作为分隔符,所以输入 Alice 25 时,name 读到 Alice,age 读到 25。
上面的示例中,左下角的 stdin 区域就是模拟的用户输入,你可以修改输入内容再运行试试。
如果需要读取一整行(包括空格),可以用 getline:
注意一个坑:如果在 cin >> 之后紧接着用 getline,需要先调用 cin.ignore() 清掉缓冲区里残留的换行符,否则 getline 会读到一个空行。
循环读取到输入结束
有些题目的输入数据量不确定,需要一直读取到输入结束(EOF)。把 cin >> 放到 while 的条件中就行:
当输入结束时,cin >> 会返回一个"假"值,while 循环自动退出。
变量与数据类型
C++ 最常用的数据类型:
| 类型 | 说明 | 示例 |
|---|---|---|
int | 整数(32位) | int a = 10; |
long long | 长整数(64位) | long long b = 9999999999LL; |
double | 浮点数(小数) | double c = 3.14; |
char | 单个字符 | char d = 'A'; |
bool | 布尔值 | bool e = true; |
string | 字符串 | string s = "hello"; |
几个注意点:
long long 类型的字面量要加 LL 后缀(如 9999999999LL),否则编译器可能把它当成 int,超出范围就会出错。
char 用单引号 'A',string 用双引号 "Hello",这两个不能搞混。
bool 在 C++ 中是内置类型,不需要额外引入头文件。true 对应 1,false 对应 0。
string 是 C++ 标准库提供的字符串类型,比 C 语言的字符数组好用太多了——可以直接用 + 拼接、用 == 比较、用 .size() 获取长度,不用操心内存管理。
变量声明
C++ 是静态类型语言,变量必须先声明类型才能使用:
// 先声明,后赋值
int x;
x = 10;
// 声明的同时赋值
int y = 20;
// 一行声明多个同类型变量
int a = 1, b = 2, c = 3;常量
用 const 声明常量,声明后就不能修改了:
const int MAX_SIZE = 100;
const double PI = 3.14159;auto 类型推断
C++11 引入了 auto 关键字,让编译器自动推断变量的类型:
auto 本质上就是个语法糖,编译器在编译时就确定了类型,运行时和你手写类型没有任何区别。目前用处不大,但后面学到 STL 容器的迭代器时,类型名会特别长,用 auto 就省事多了。
数组基础
数组用来存储一组相同类型的数据,通过下标(索引)访问,下标从 0 开始:
C 风格数组的大小在创建后就固定了,不能动态增删元素。后面学到 STL 的 vector 之后,你就有动态数组可以用了,比这个灵活得多。
类型转换
小类型可以自动转换为大类型(不丢精度),反过来可能丢失数据:
特别注意整数除法:1 / 2 的结果是 0 而不是 0.5,因为两个 int 相除结果还是 int,小数部分直接丢掉。这在算法题中是个常见的坑。
解决办法:让其中一个操作数变成 double,比如写成 1.0 / 2,或者用强制转换 (double)a / b。
运算符
算术运算符
取余运算符 % 在算法题中非常常用,比如判断奇偶(n % 2 == 0)、循环数组索引(i % n)等。
比较与逻辑运算符
比较运算符返回 true 或 false:==(等于)、!=(不等于)、<、>、<=、>=。
逻辑运算符用于组合条件:&&(与)、||(或)、!(非)。
三元运算符
条件 ? 值1 : 值2 是 if-else 的简写形式,条件为真返回值1,否则返回值2:
int a = 10, b = 20;
int maxVal = (a > b) ? a : b; // maxVal = 20位运算
位运算在处理二进制相关问题时非常高效,在算法题中可能会遇到。详细的位运算原理可以参考 常用的位操作,这里了解一下基本用法就行:
控制流
条件判断 if / else
for 循环
for 循环是最常用的循环,由三部分组成:for (初始化; 条件; 更新)。
while 循环
while 循环适合循环次数不确定的场景:
do-while 和 while 的区别是:do-while 先执行一次再判断条件,所以循环体至少会执行一次。实际中 do-while 用得很少,了解一下就行。
range-based for(for-each)
C++11 引入了基于范围的 for 循环,可以更简洁地遍历数组和容器:
for (int x : nums) 的意思是"对于 nums 中的每个元素,依次赋值给 x"。如果想在循环中修改原数组的元素,需要用引用 int& x,否则 x 只是元素的一个副本,修改它不会影响原数组。
这里也可以用 auto 来简化类型声明:for (auto x : nums) 或 for (auto& x : nums)。
break 和 continue
break 跳出整个循环,continue 跳过当前这轮、进入下一轮:
switch 语句
switch 适合对一个变量做多个值的判断,比一长串 if-else 更清晰:
注意每个 case 后面要加 break,否则会"穿透"到下一个 case 继续执行。你可以试试把 break 去掉,看看会发生什么。
异常处理
C++ 用 try-catch 捕获运行时错误,用 throw 主动抛出异常。
throw 后面可以跟任何类型,但推荐抛 std::exception 的子类,能通过 what() 拿到错误信息。
基类 std::exception 定义在 <exception> 头文件里,常用业务异常子类(runtime_error、logic_error、out_of_range、invalid_argument 等)在 <stdexcept> 里,引入后者会间接带上前者。
多个 catch 按书写顺序匹配,所以父类 catch 要放在子类后面,否则子类永远匹配不到。catch (...) 是兜底写法,不知道抛什么类型时可以用,但代价是拿不到异常对象。
还有一个容易踩的坑:vector 的 at() 会做越界检查并抛 out_of_range,而 operator[](也就是 v[i])不会,越界是未定义行为。刷题时一般用 [] 追求性能,涉及外部输入时 at() 更安全。
算法题里基本用不到异常,但读标准库源码和业务代码时会经常遇到。
小结
这篇介绍了 C++ 的基础语法:程序结构、输入输出、数据类型、运算符和控制流。这些是所有 C++ 代码的基石。
下一篇我们来详细讲讲 C++ 的 头文件与标准库,看看 C++ 的头文件和 C 有什么不同,using namespace std 是怎么回事,以及刷题中超好用的万能头文件。