C++ 基础语法

C++ 是在 C 语言基础上发展而来的,增加了面向对象编程、模板、异常处理等特性,同时拥有非常强大的标准库(STL)。因为执行效率高、标准库完善,C++ 是算法竞赛和系统开发中最受欢迎的语言之一。

本站所有的算法代码都配有 C++ 版本,你学完基础之后可以在刷题过程中熟悉和应用这门语言。下面我们就开始学习 C++ 的基础语法。

Hello World 与程序结构

先看一个最简单的 C++ 程序:

拆解一下这个结构:

#include <iostream>      // 引入输入输出库
using namespace std;     // 使用标准命名空间

int main() {             // 程序入口函数
    // 你的代码写在这里
    return 0;            // 返回 0 表示程序正常结束
}

#include <iostream> 是预处理指令,告诉编译器引入输入输出库(input/output stream),这样你才能用 coutcin 这些功能。

using namespace std; 是告诉编译器你要使用标准命名空间。不写这行的话,每次使用标准库的东西都要加 std:: 前缀,比如 std::coutstd::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 读到 Aliceage 读到 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 对应 1false 对应 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)等。

比较与逻辑运算符

比较运算符返回 truefalse==(等于)、!=(不等于)、<><=>=

逻辑运算符用于组合条件:&&(与)、||(或)、!(非)。

三元运算符

条件 ? 值1 : 值2if-else 的简写形式,条件为真返回值1,否则返回值2:

int a = 10, b = 20;
int maxVal = (a > b) ? a : b;  // maxVal = 20

位运算

位运算在处理二进制相关问题时非常高效,在算法题中可能会遇到。详细的位运算原理可以参考 常用的位操作,这里了解一下基本用法就行:

控制流

条件判断 if / else

for 循环

for 循环是最常用的循环,由三部分组成:for (初始化; 条件; 更新)

while 循环

while 循环适合循环次数不确定的场景:

do-whilewhile 的区别是: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_errorlogic_errorout_of_rangeinvalid_argument 等)在 <stdexcept> 里,引入后者会间接带上前者。

多个 catch 按书写顺序匹配,所以父类 catch 要放在子类后面,否则子类永远匹配不到。catch (...) 是兜底写法,不知道抛什么类型时可以用,但代价是拿不到异常对象。

还有一个容易踩的坑:vectorat() 会做越界检查并抛 out_of_range,而 operator[](也就是 v[i])不会,越界是未定义行为。刷题时一般用 [] 追求性能,涉及外部输入时 at() 更安全。

算法题里基本用不到异常,但读标准库源码和业务代码时会经常遇到。

小结

这篇介绍了 C++ 的基础语法:程序结构、输入输出、数据类型、运算符和控制流。这些是所有 C++ 代码的基石。

下一篇我们来详细讲讲 C++ 的 头文件与标准库,看看 C++ 的头文件和 C 有什么不同,using namespace std 是怎么回事,以及刷题中超好用的万能头文件。