C++ 程序设计入门到实用:语法、函数、数组、算法、类与指针

这篇笔记根据 AMA2222 Principles of Programming 的课件整理,目标不是把 C++ 语法逐条背下来,而是建立一条能真正写程序的路线:先理解输入、输出和变量,再用条件与循环控制程序流程,然后用函数拆分问题,用数组和字符串保存数据,用搜索、排序、递归训练算法思维,最后进入类、继承、运算符重载、指针和动态内存。

如果你是从 Python 转到 C++,最重要的差异是:C++ 更强调类型、内存、编译和程序结构。Python 里很多事情解释器帮你处理了,C++ 会要求你明确地告诉电脑变量是什么类型、数组有多大、函数返回什么、对象如何初始化,以及什么时候申请或释放内存。

参考资料下载

原始课件和复习资料我也保留在博客里,方便对照阅读:

1. 一段 C++ 程序到底由什么组成

最小 C++ 程序通常长这样:

1
2
3
4
5
6
7
#include <iostream>
using namespace std;

int main() {
cout << "Hello, C++!" << endl;
return 0;
}

这里每一行都有意义:

  • #include <iostream>:引入输入输出库,否则不能使用 cincout
  • using namespace std;:让你可以直接写 cout,不用写 std::cout
  • int main():程序入口。运行程序时,电脑从 main 开始执行。
  • { ... }:代码块。C++ 用花括号表示一组语句的范围。
  • cout << ...:输出到屏幕。
  • return 0;:告诉系统程序正常结束。

C++ 的语句通常以分号结尾。初学时很多报错都来自少写分号、括号不配对、变量没有声明、类型不匹配。

2. 输入、输出、变量与类型

程序可以用 IPO 模型理解:

1
Input -> Process -> Output

例如输入两个整数,判断第一个是否能被第二个整除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <iostream>
using namespace std;

int main() {
int m, n;

cout << "Enter two integers: ";
cin >> m >> n;

if (n != 0 && m % n == 0) {
cout << m << " is divisible by " << n << endl;
} else {
cout << "Not divisible, or divisor is zero." << endl;
}

return 0;
}

这里有几个关键点:

  • int m, n; 是变量声明。C++ 需要先声明变量,再使用变量。
  • cin >> m >> n; 会从键盘读取两个值。
  • % 是取余数。m % n == 0 表示整除。
  • 判断除法前先检查 n != 0,否则可能出现除以 0 的错误。

常用基础类型:

类型 用途 示例
int 整数 int age = 20;
double 小数 double gpa = 3.7;
char 单个字符 char grade = 'A';
bool 真/假 bool passed = true;
string 字符串 string name = "Richard";

注意 charstring 不一样。'A' 是单个字符,用单引号;"A" 是字符串,用双引号。

3. 条件判断:让程序做选择

条件判断是程序从“顺序执行”走向“有逻辑”的第一步。

1
2
3
4
5
if (condition) {
// condition 为 true 时执行
} else {
// condition 为 false 时执行
}

一个 BMI 分类例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

int main() {
double weight, height;
cin >> weight >> height;

double bmi = weight / (height * height);

if (bmi < 18.5) {
cout << "underweight" << endl;
} else if (bmi < 25) {
cout << "normal" << endl;
} else if (bmi < 30) {
cout << "overweight" << endl;
} else {
cout << "obese" << endl;
}

return 0;
}

条件判断里最常用的运算符:

运算符 含义
== 等于
!= 不等于
<, <=, >, >= 大小比较
&& 并且
`
! 取反

初学最容易犯的错是把 == 写成 =

1
2
3
4
5
if (x = 5) {   // 错误倾向:这是赋值,不是判断相等
}

if (x == 5) { // 正确:判断 x 是否等于 5
}

另一个建议是:即使 if 后面只有一行,也尽量写花括号。这样以后加代码时更不容易出错。

4. 循环:重复执行一段逻辑

循环用于处理“重复做某件事”的场景,比如求和、遍历数组、生成序列、模拟实验。

for 循环

for 适合循环次数已知的场景:

1
2
3
4
5
6
7
int sum = 0;

for (int i = 1; i <= 100; i++) {
sum += i;
}

cout << sum << endl;

for 的结构是:

1
2
3
for (初始化; 循环条件; 每轮更新) {
循环体
}

while 循环

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

1
2
3
4
5
6
7
8
9
10
int n;
cin >> n;

int digits = 0;
while (n > 0) {
n /= 10;
digits++;
}

cout << digits << endl;

这个程序每次把数字去掉最后一位,直到数字变成 0。它本质上是在数整数有多少位。

循环常见坑

  • 忘记更新循环变量,导致无限循环。
  • 数组下标写到边界外,例如访问 a[n],但合法下标只有 0n - 1
  • for (int i = 0; i <= n; i++)for (int i = 0; i < n; i++) 差一个元素,要非常小心。

5. 函数:把问题拆成小块

函数让程序更清晰。你可以把一个大问题拆成多个小问题,每个函数负责一件事。

1
2
3
4
5
6
7
8
double getArea(double width, double height) {
return width * height;
}

int main() {
cout << getArea(3, 4) << endl;
return 0;
}

函数一般包含:

1
2
3
4
返回类型 函数名(参数列表) {
函数体
return 返回值;
}

例如:

1
2
3
bool isEven(int x) {
return x % 2 == 0;
}

这个函数表达力很强。调用处写:

1
2
3
if (isEven(n)) {
cout << "even" << endl;
}

比直接写 % 2 更容易读。

局部变量与全局变量

函数里面定义的变量叫局部变量,只在函数内部有效:

1
2
3
4
void printSquare(int x) {
int y = x * x;
cout << y << endl;
}

y 只能在 printSquare 内使用。初学阶段建议少用全局变量,因为全局变量会让程序状态变得难追踪。

值传递与引用传递

普通参数默认是值传递,函数拿到的是一份副本:

1
2
3
void addOne(int x) {
x++;
}

调用 addOne(a) 不会改变 a 本身。如果想改变原变量,可以用引用:

1
2
3
void addOne(int& x) {
x++;
}

int& x 表示 x 是外部变量的别名。

6. 字符与字符串:文本处理的基础

char 表示单个字符,背后有 ASCII 编码。比如:

1
2
3
4
5
6
char letter = 'A';

cout << letter << endl; // A
cout << (int)letter << endl; // 65
cout << letter + 1 << endl; // 66
cout << (char)(letter + 1); // B

这解释了为什么字符可以做加减法:C++ 会把字符转成对应的 ASCII 数字。

字符串用 string

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <string>
using namespace std;

int main() {
string name;
getline(cin, name);

cout << "Hello, " << name << endl;
cout << "Length: " << name.length() << endl;

return 0;
}

如果要读取一整行,使用 getline(cin, name)。如果用 cin >> name,遇到空格就会停止。

常见字符串操作:

1
2
3
4
5
string s = "programming";

cout << s[0] << endl; // p
cout << s.length() << endl; // 11
cout << s.substr(0, 7) << endl; // program

一个统计字符串中字母数量的例子:

1
2
3
4
5
6
7
8
9
int countLetterA(string s) {
int count = 0;
for (char ch : s) {
if (ch == 'A' || ch == 'a') {
count++;
}
}
return count;
}

这段代码用到了 range-based for。它的意思是:依次取出字符串里的每个字符。

7. 数组:保存一批同类型数据

数组用于保存多个同类型值:

1
int scores[5] = {80, 75, 90, 88, 92};

数组下标从 0 开始:

1
2
cout << scores[0] << endl; // 第一个元素
cout << scores[4] << endl; // 第五个元素

求数组平均值:

1
2
3
4
5
6
7
8
9
double average(int a[], int size) {
int sum = 0;

for (int i = 0; i < size; i++) {
sum += a[i];
}

return static_cast<double>(sum) / size;
}

注意函数参数里传数组时,通常也要传数组长度。因为函数内部不知道数组到底有多少个元素。

数组传入函数时会被修改

数组传给函数时,本质上传的是起始地址,所以函数里改数组,外面的数组也会变:

1
2
3
4
5
void doubleAll(int a[], int size) {
for (int i = 0; i < size; i++) {
a[i] *= 2;
}
}

这个和普通 int 的值传递不同,是 C++ 初学者非常容易混淆的地方。

反转数组

1
2
3
4
5
6
7
8
void reverseArray(int a[], int size) {
for (int i = 0; i < size / 2; i++) {
int j = size - 1 - i;
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
}

这个例子很重要,因为它训练了两个能力:

  • 用下标控制位置。
  • 用临时变量交换两个元素。

8. 二维数组:矩阵与表格数据

二维数组适合表示矩阵、棋盘、表格:

1
2
3
4
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};

遍历二维数组:

1
2
3
4
5
6
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
cout << matrix[i][j] << " ";
}
cout << endl;
}

如果把二维数组传入函数,列数通常要写清楚:

1
2
3
4
5
6
7
8
void printMatrix(int a[][3], int rows) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < 3; j++) {
cout << a[i][j] << " ";
}
cout << endl;
}
}

这是因为 C++ 需要知道每一行有多少列,才能正确计算 a[i][j] 的内存位置。

9. 搜索与排序:从语法进入算法

学 C++ 不能只学语法,还要开始训练算法思维。最基础的算法包括搜索和排序。

线性搜索

线性搜索就是从头到尾找:

1
2
3
4
5
6
7
8
int linearSearch(int a[], int size, int target) {
for (int i = 0; i < size; i++) {
if (a[i] == target) {
return i;
}
}
return -1;
}

如果找到,返回下标;如果找不到,返回 -1

二分搜索

二分搜索要求数组已经排好序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int binarySearch(int a[], int size, int target) {
int left = 0;
int right = size - 1;

while (left <= right) {
int mid = left + (right - left) / 2;

if (a[mid] == target) {
return mid;
} else if (a[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}

return -1;
}

二分搜索的核心思想是每次排除一半候选区域。它比线性搜索快很多,但前提是数据有序。

冒泡排序

冒泡排序不断比较相邻元素,把较大的元素往后推:

1
2
3
4
5
6
7
8
9
10
11
void bubbleSort(int a[], int size) {
for (int pass = 0; pass < size - 1; pass++) {
for (int i = 0; i < size - 1 - pass; i++) {
if (a[i] > a[i + 1]) {
int temp = a[i];
a[i] = a[i + 1];
a[i + 1] = temp;
}
}
}
}

初学时不要只背排序代码,要能说出每层循环在做什么:

  • 外层循环表示已经完成了多少轮。
  • 内层循环负责比较相邻元素。
  • 每一轮结束后,当前未排序部分最大的元素会到达正确位置。

10. 递归、分治与贪心

递归是函数调用自己。递归一定要有两个部分:

  • base case:最小问题,直接返回。
  • recursive case:把问题变小,然后调用自己。

例如计算数字位数:

1
2
3
4
5
6
int countDigits(int n) {
if (n < 10) {
return 1;
}
return 1 + countDigits(n / 10);
}

如果输入 345678

1
2
3
4
5
countDigits(345678)
= 1 + countDigits(34567)
= 1 + 1 + countDigits(3456)
...
= 6

再看一个递归数组求和:

1
2
3
4
5
6
int sumArray(int a[], int size) {
if (size == 1) {
return a[0];
}
return a[size - 1] + sumArray(a, size - 1);
}

分治思想是:

1
divide -> solve subproblems -> combine

很多经典算法都可以这样理解,比如归并排序、快速排序、二分搜索。

贪心算法则是每一步都做当前看来最好的选择。贪心不是所有问题都正确,但在零钱兑换、区间选择、最小生成树等问题里很重要。初学时要记住:贪心需要证明,不能只凭直觉。

11. 类与对象:C++ 的面向对象核心

类是对象的模板。对象有状态和行为:

  • 状态:数据字段,比如矩形的 widthheight
  • 行为:成员函数,比如计算面积。

一个矩形类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Rectangle {
private:
double width;
double height;

public:
Rectangle() {
width = 1;
height = 1;
}

Rectangle(double w, double h) {
width = w;
height = h;
}

double getArea() {
return width * height;
}

double getPerimeter() {
return 2 * (width + height);
}

bool isSquare() {
return width == height;
}
};

使用类:

1
2
3
4
5
6
7
8
9
int main() {
Rectangle r1;
Rectangle r2(3, 4);

cout << r1.getArea() << endl;
cout << r2.getPerimeter() << endl;

return 0;
}

这里的重点是封装。课件里一开始会把数据字段写在 public,方便理解;但真实项目中更推荐用 private 保护数据,再通过成员函数访问。

构造函数

构造函数用于初始化对象。它有三个特点:

  • 名字必须和类名一样。
  • 没有返回类型,连 void 都不写。
  • 创建对象时自动调用。
1
2
Rectangle r1;       // 调用无参数构造函数
Rectangle r2(3, 4); // 调用带参数构造函数

setter 与 getter

如果要控制数据合法性,可以写 setter:

1
2
3
4
5
6
7
8
9
void setWidth(double w) {
if (w > 0) {
width = w;
}
}

double getWidth() {
return width;
}

这比直接让外部修改 width 更安全。

12. 运算符重载:让对象像数字一样运算

假设我们定义一个分数类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Fraction {
private:
int num;
int den;

public:
Fraction(int p, int q) {
num = p;
den = q;
}

Fraction operator*(Fraction r) {
return Fraction(num * r.num, den * r.den);
}

void print() {
cout << num << "/" << den << endl;
}
};

现在可以这样写:

1
2
3
4
Fraction r1(3, 4);
Fraction r2(5, 6);
Fraction r3 = r1 * r2;
r3.print(); // 15/24

operator* 的意思是:当两个 Fraction 对象使用 * 时,执行我们自定义的乘法逻辑。

运算符重载不是为了炫技,而是为了让对象的使用方式符合直觉。比如分数加法、复数乘法、向量加法,都适合用运算符重载。

13. 继承:复用已有类的属性和行为

继承可以让一个类基于另一个类扩展:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
protected:
string name;

public:
Person(string n) {
name = n;
}

void printName() {
cout << name << endl;
}
};

class Student : public Person {
private:
string studentId;

public:
Student(string n, string id) : Person(n) {
studentId = id;
}

void printStudent() {
cout << name << " " << studentId << endl;
}
};

StudentPerson 的子类,它继承了 Person 的名字字段和函数,又新增了学生编号。

理解继承时要注意:

  • public 继承表示“is-a”关系:Student is a Person。
  • 子类可以复用父类代码。
  • 子类构造函数通常要调用父类构造函数。
  • 不要为了省几行代码乱用继承。真实设计里,组合有时比继承更合适。

14. 指针与动态内存:C++ 最需要耐心的部分

指针保存的是地址。

1
2
3
4
5
6
7
int x = 10;
int* p = &x;

cout << x << endl; // 10
cout << &x << endl; // x 的地址
cout << p << endl; // p 保存的地址
cout << *p << endl; // 通过地址取出值,得到 10

几个符号要分清:

符号 含义
&x 取变量 x 的地址
int* p 声明一个指向 int 的指针
*p 解引用,取出指针指向的值

指针和数组

数组名经常可以看成数组首元素地址:

1
2
3
4
5
6
int a[3] = {10, 20, 30};
int* p = a;

cout << *p << endl; // 10
cout << *(p + 1) << endl; // 20
cout << *(p + 2) << endl; // 30

这解释了为什么数组传入函数后会被修改:函数拿到的是数组起始地址。

动态内存

如果程序运行时才知道需要多少空间,可以动态申请:

1
2
3
4
5
6
7
8
9
10
int n;
cin >> n;

int* a = new int[n];

for (int i = 0; i < n; i++) {
a[i] = i * i;
}

delete[] a;

new 申请内存,delete[] 释放数组内存。如果申请了不释放,就可能造成内存泄漏。

现代 C++ 更推荐使用 vectorstring、智能指针等工具减少手动内存管理,但学习指针仍然很重要,因为它能帮助你理解数组、对象、引用和底层性能。

15. 文件读写:让程序处理真实数据

控制台输入适合小练习,文件输入适合真实数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <fstream>
using namespace std;

int main() {
ifstream fin("input.txt");
ofstream fout("output.txt");

int x, sum = 0;

while (fin >> x) {
sum += x;
}

fout << "sum = " << sum << endl;

fin.close();
fout.close();

return 0;
}

实际写文件程序时,要检查文件是否成功打开:

1
2
3
4
if (!fin) {
cout << "Cannot open input file." << endl;
return 1;
}

文件读写对数据分析和算法题都很有用。以后做项目时,输入可能来自 CSV、日志、文本语料或实验结果,文件处理能力会直接影响工程效率。

16. 随机模拟与数值方法

最后几章会进入模拟和应用。模拟的核心思想是:用程序重复实验,观察统计结果。

一个简单的随机投点估计圆周率思路:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
srand(time(0));

int n = 100000;
int inside = 0;

for (int i = 0; i < n; i++) {
double x = static_cast<double>(rand()) / RAND_MAX;
double y = static_cast<double>(rand()) / RAND_MAX;

if (x * x + y * y <= 1.0) {
inside++;
}
}

double pi = 4.0 * inside / n;
cout << pi << endl;

return 0;
}

这类方法叫 Monte Carlo simulation。它不一定给出精确答案,但样本足够多时可以得到不错的近似。机器学习、金融、优化和强化学习里都经常能看到类似思想。

17. 从这门课到算法实习的学习路线

如果目标是算法、NLP、搜索推荐或 AI infra,C++ 不只是考试语言,它还训练几种很硬的能力:

  • 精确理解类型和内存。
  • 用函数拆分复杂逻辑。
  • 能写数组、字符串、搜索、排序、递归。
  • 理解对象、封装、继承和接口。
  • 对性能有初步直觉。

建议按下面顺序复习:

  1. 先写熟 cincout、变量、ifforwhile
  2. 每天做 2 到 3 道数组或字符串题,重点练下标和边界。
  3. 把线性搜索、二分搜索、冒泡排序、选择排序、递归求和自己默写出来。
  4. 用类写 3 个小对象:RectangleFractionStudent
  5. 最后再学指针和动态内存,不要一开始就被它卡住。

18. 一套最小练习清单

下面这些题如果能独立写出来,说明 C++ 入门已经比较稳:

  • 输入两个整数,输出较大值。
  • 输入三个角度,判断是否能构成三角形,并判断锐角、直角、钝角。
  • 输入一个整数,统计它有多少位。
  • 输入一个字符串,统计其中大写字母、小写字母、数字的数量。
  • 写函数判断一个数是否为质数。
  • 用数组保存 10 个分数,输出最大值、最小值、平均值。
  • 写函数反转数组。
  • 写线性搜索和二分搜索。
  • 写冒泡排序或选择排序。
  • 用递归计算阶乘、数字位数、数组求和。
  • 定义 Rectangle 类,支持面积、周长、判断正方形。
  • 定义 Fraction 类,支持乘法和打印。
  • new 创建动态数组,读入若干数字并求平均,最后释放内存。
  • 从文件读取整数,输出总和到另一个文件。

19. 常见错误速查

编译错误

如果程序编译不过,优先检查:

  • 是否漏了分号。
  • 变量是否声明过。
  • 函数返回类型和 return 是否匹配。
  • 花括号是否配对。
  • 是否忘了 #include <string>#include <fstream> 等头文件。

运行错误

如果程序能编译但运行崩溃,优先检查:

  • 数组是否越界。
  • 是否除以 0。
  • 指针是否没有初始化。
  • 动态数组是否释放方式错误。

逻辑错误

如果程序结果不对,优先检查:

  • 循环边界是 < n 还是 <= n
  • if 条件是否写反。
  • 整数除法是否导致小数被截断。
  • 函数参数是值传递还是引用传递。
  • 排序或搜索前提是否满足,例如二分搜索要求数组有序。

20. 最后的理解方式

C++ 入门最好的学习方式不是一直看语法,而是反复写小程序。每个知识点都可以对应一个“可运行”的小练习:

  • 条件判断对应分类问题。
  • 循环对应累计、统计、模拟。
  • 函数对应拆解任务。
  • 数组对应批量数据。
  • 字符串对应文本处理。
  • 搜索排序对应算法基础。
  • 类对应现实对象建模。
  • 指针对应内存和底层机制。

把这些串起来,C++ 就不再是一堆孤立语法,而是一套控制计算机完成任务的工具。对后续学习数据结构、算法、系统、推理优化和高性能工程来说,这套基础非常值得认真打牢。

C++ 程序设计入门到实用:语法、函数、数组、算法、类与指针

https://richardf123.github.io/2026/06/24/ama2222-cpp-programming-guide/

作者

RichardF

发布于

2026-06-24

更新于

2026-06-24

许可协议