C++ 进阶知识点详细教程 - 第1部分
1. do while 循环
1.1 基本语法
do {// 循环体
} while (条件);
关键特点:至少执行一次循环体,因为条件判断在循环体执行之后。
1.2 与while循环的区别
// while循环:先判断条件,再执行
int i = 10;
while (i < 5) {cout << i << endl; // 不会执行,因为10 < 5为false
}// do-while循环:先执行,再判断条件
int j = 10;
do {cout << j << endl; // 会执行一次,输出10
} while (j < 5); // 然后判断10 < 5为false,退出循环
1.3 实际应用场景
场景1:菜单程序
#include <iostream>
using namespace std;int main() {int choice;do {cout << "======= 主菜单 =======" << endl;cout << "1. 新建文件" << endl;cout << "2. 打开文件" << endl;cout << "3. 保存文件" << endl;cout << "0. 退出程序" << endl;cout << "请选择: ";cin >> choice;switch (choice) {case 1: cout << "新建文件成功!" << endl; break;case 2: cout << "打开文件成功!" << endl; break;case 3: cout << "保存文件成功!" << endl; break;case 0: cout << "感谢使用,再见!" << endl; break;default: cout << "无效选择,请重新输入!" << endl;}cout << endl;} while (choice != 0); // 只有选择0才退出return 0;
}
场景2:输入验证
#include <iostream>
using namespace std;int main() {int score;do {cout << "请输入成绩(0-100): ";cin >> score;if (score < 0 || score > 100) {cout << "输入无效!成绩必须在0-100之间。" << endl;}} while (score < 0 || score > 100);cout << "你输入的成绩是: " << score << endl;return 0;
}
场景3:游戏循环
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;int main() {srand(time(0));char playAgain;do {int secret = rand() % 100 + 1;int guess, attempts = 0;cout << "\n=== 猜数字游戏 ===" << endl;cout << "我想了一个1-100之间的数字,你来猜!" << endl;do {cout << "请输入你的猜测: ";cin >> guess;attempts++;if (guess < secret) {cout << "太小了!再试试。" << endl;} else if (guess > secret) {cout << "太大了!再试试。" << endl;} else {cout << "恭喜你!猜对了!" << endl;cout << "你总共猜了 " << attempts << " 次。" << endl;}} while (guess != secret);cout << "再玩一次吗?(y/n): ";cin >> playAgain;} while (playAgain == 'y' || playAgain == 'Y');cout << "游戏结束,谢谢游戏!" << endl;return 0;
}
2. switch 语句
2.1 基本语法和规则
switch (表达式) {case 常量1:// 语句块1break;case 常量2:// 语句块2break;case 常量3:// 语句块3break;default:// 默认语句块(可选)break;
}
重要规则:
- 表达式类型:必须是整型(int、char、short、long)或枚举类型
- case常量:必须是编译时常量,不能是变量
- case唯一性:每个case后的常量值必须唯一,不能重复
- break的作用:跳出switch语句,防止继续执行下一个case
- default位置:可以放在任何位置,通常放在最后
2.2 break的重要性
没有break的后果(case穿透):
#include <iostream>
using namespace std;int main() {int day = 2;cout << "没有break的情况:" << endl;switch (day) {case 1:cout << "星期一" << endl;case 2:cout << "星期二" << endl; // 会执行这个case 3:cout << "星期三" << endl; // 也会执行这个!case 4:cout << "星期四" << endl; // 还会执行这个!default:cout << "其他" << endl; // 甚至会执行这个!}cout << "\n有break的情况:" << endl;switch (day) {case 1:cout << "星期一" << endl;break;case 2:cout << "星期二" << endl; // 只执行这个break; // 然后跳出switchcase 3:cout << "星期三" << endl;break;case 4:cout << "星期四" << endl;break;default:cout << "其他" << endl;break;}return 0;
}
输出结果:
没有break的情况:
星期二
星期三
星期四
其他有break的情况:
星期二
2.3 case常量的限制
#include <iostream>
using namespace std;int main() {int x = 10;int choice = 1;switch (choice) {case 1: // 正确:字面常量cout << "选择1" << endl;break;case 2 + 3: // 正确:常量表达式cout << "选择5" << endl;break;// case x: // 错误!x是变量,不是常量// cout << "选择x" << endl;// break;// case 1: // 错误!重复的case值// cout << "重复" << endl;// break;default:cout << "其他选择" << endl;break;}return 0;
}
2.4 有意的case穿透(实用技巧)
#include <iostream>
using namespace std;int main() {int month;cout << "请输入月份(1-12): ";cin >> month;switch (month) {case 12: // 12月case 1: // 1月 case 2: // 2月 - 这三个case都会执行到下面的语句cout << "冬季 ❄️" << endl;cout << "天气寒冷,注意保暖" << endl;break;case 3:case 4:case 5:cout << "春季 🌸" << endl;cout << "万物复苏,春暖花开" << endl;break;case 6:case 7:case 8:cout << "夏季 ☀️" << endl;cout << "天气炎热,注意防暑" << endl;break;case 9:case 10:case 11:cout << "秋季 🍂" << endl;cout << "秋高气爽,收获季节" << endl;break;default:cout << "无效的月份!请输入1-12之间的数字。" << endl;break;}return 0;
}
2.5 没有匹配case的情况
#include <iostream>
using namespace std;int main() {int score = 75;char grade = score / 10; // 7cout << "情况1:有default" << endl;switch (grade) {case 10:case 9:cout << "A等级" << endl;break;case 8:cout << "B等级" << endl;break;case 7:cout << "C等级" << endl; // 会执行这个break;default:cout << "需要努力" << endl;break;}grade = 5; // 改变值cout << "\n情况2:没有default,且没有匹配的case" << endl;switch (grade) {case 10:case 9:cout << "A等级" << endl;break;case 8:cout << "B等级" << endl;break;case 7:cout << "C等级" << endl;break;// 没有default,且grade=5没有对应的case// 结果:什么都不执行,直接跳过整个switch}cout << "switch执行完毕" << endl;return 0;
}
2.6 switch vs if-else 对比
| 特性 | switch | if-else |
|---|---|---|
| 适用条件 | 等值比较 | 任意条件 |
| 性能 | 编译器可优化为跳转表 | 逐个判断 |
| 可读性 | 多分支时更清晰 | 复杂条件更灵活 |
| 支持类型 | 整型、字符型、枚举 | 任意布尔表达式 |
| 范围判断 | 不支持 | 支持 |
switch适用场景:
// 菜单选择、状态机、字符分类等
switch (menuChoice) {case 1: createFile(); break;case 2: openFile(); break;case 3: saveFile(); break;
}
if-else适用场景:
// 范围判断、复杂条件
if (score >= 90) {cout << "优秀";
} else if (score >= 80) {cout << "良好";
} else if (score >= 60) {cout << "及格";
} else {cout << "不及格";
}
2.7 实战示例:计算器
#include <iostream>
using namespace std;int main() {double num1, num2, result;char op;cout << "简单计算器" << endl;cout << "请输入表达式 (如: 5 + 3): ";cin >> num1 >> op >> num2;switch (op) {case '+':result = num1 + num2;cout << num1 << " + " << num2 << " = " << result << endl;break;case '-':result = num1 - num2;cout << num1 << " - " << num2 << " = " << result << endl;break;case '*':result = num1 * num2;cout << num1 << " * " << num2 << " = " << result << endl;break;case '/':if (num2 != 0) {result = num1 / num2;cout << num1 << " / " << num2 << " = " << result << endl;} else {cout << "错误:除数不能为0!" << endl;}break;case '%':if (num2 != 0) {// 注意:%运算符只能用于整数cout << (int)num1 << " % " << (int)num2 << " = " << (int)num1 % (int)num2 << endl;} else {cout << "错误:除数不能为0!" << endl;}break;default:cout << "错误:不支持的运算符 '" << op << "'" << endl;cout << "支持的运算符:+ - * / %" << endl;break;}return 0;
}
3. 流程图
3.1 基本符号
- 椭圆:开始/结束
- 矩形:处理
- 菱形:判断
- 平行四边形:输入/输出
3.2 示例
开始 → 输入n → 判断n>0? → 是:输出正数 / 否:输出负数 → 结束
4. string 字符串
4.1 基本操作
#include <string>
string s = "Hello";
s.length() // 长度
s.empty() // 判空
s += " World" // 连接
s[0] // 访问
4.2 常用函数
s.substr(0, 5) // 子串
s.find("World") // 查找
s.insert(5, ",") // 插入
s.erase(5, 6) // 删除
s.replace(6,5,"C++")// 替换
4.3 转换
to_string(123) // 数字→字符串
stoi("456") // 字符串→int
stod("3.14") // 字符串→double
5. 引用
5.1 什么是引用
引用是给已存在的变量起一个别名,引用和原变量共享同一块内存空间。
#include <iostream>
using namespace std;int main() {int a = 10;int& ref = a; // ref是a的引用(别名)cout << "a = " << a << endl; // 10cout << "ref = " << ref << endl; // 10cout << "&a = " << &a << endl; // 地址相同cout << "&ref = " << &ref << endl; // 地址相同ref = 20; // 修改ref就是修改acout << "修改ref后,a = " << a << endl; // 20return 0;
}
5.2 引用的特点
- 必须初始化:声明引用时必须立即初始化
- 不能重新绑定:引用一旦绑定到某个变量就不能改变
- 没有独立内存:引用不占用额外的内存空间
- 不能为NULL:引用必须指向一个有效的对象
int a = 10, b = 20;// 正确的引用声明
int& ref1 = a; // 必须初始化// 错误的引用声明
// int& ref2; // 错误:引用必须初始化
// int& ref3 = NULL; // 错误:引用不能为NULL// 引用不能重新绑定
ref1 = b; // 这不是让ref1指向b,而是把b的值赋给a
cout << a << endl; // 输出20,a的值被改变了
5.3 引用 vs 指针
| 特性 | 引用 | 指针 |
|---|---|---|
| 初始化 | 必须初始化 | 可以不初始化 |
| 重新指向 | 不能改变指向 | 可以改变指向 |
| 内存占用 | 不占用额外空间 | 占用指针大小的空间 |
| NULL值 | 不能为NULL | 可以为NULL |
| 运算 | 不能进行指针运算 | 可以进行指针运算 |
| 使用方式 | 直接使用 | 需要解引用(*) |
#include <iostream>
using namespace std;int main() {int a = 10, b = 20;// 指针的使用int* ptr = &a; // 指针指向acout << *ptr << endl; // 通过指针访问a的值:10ptr = &b; // 指针可以改变指向cout << *ptr << endl; // 现在指向b:20// 引用的使用int& ref = a; // 引用绑定到acout << ref << endl; // 直接访问:10ref = b; // 这是赋值,不是改变引用指向cout << a << endl; // a变成了20cout << ref << endl; // ref仍然是a的别名:20return 0;
}
5.4 函数参数:形参与实参
5.4.1 传值调用(值传递)
#include <iostream>
using namespace std;// 形参:函数定义中的参数
void func1(int x) { // x是形参x = 100; // 只修改形参的副本cout << "函数内x = " << x << endl;
}int main() {int a = 10; // a是实参cout << "调用前a = " << a << endl; // 10func1(a); // 传递a的值给形参xcout << "调用后a = " << a << endl; // 10(没有改变)return 0;
}
输出结果:
调用前a = 10
函数内x = 100
调用后a = 10
5.4.2 引用传递
#include <iostream>
using namespace std;// 形参是引用类型
void func2(int& x) { // x是实参的引用x = 100; // 直接修改实参cout << "函数内x = " << x << endl;
}int main() {int a = 10;cout << "调用前a = " << a << endl; // 10func2(a); // 传递a的引用给形参xcout << "调用后a = " << a << endl; // 100(被修改了)return 0;
}
输出结果:
调用前a = 10
函数内x = 100
调用后a = 100
5.4.3 指针传递
#include <iostream>
using namespace std;// 形参是指针类型
void func3(int* x) { // x是指向int的指针*x = 100; // 通过指针修改实参cout << "函数内*x = " << *x << endl;
}int main() {int a = 10;cout << "调用前a = " << a << endl; // 10func3(&a); // 传递a的地址给形参xcout << "调用后a = " << a << endl; // 100(被修改了)return 0;
}
5.5 实用的引用应用
5.5.1 交换函数
#include <iostream>
using namespace std;// 错误的交换函数(值传递)
void wrongSwap(int a, int b) {int temp = a;a = b;b = temp;// 只交换了形参的副本,实参不变
}// 正确的交换函数(引用传递)
void correctSwap(int& a, int& b) {int temp = a;a = b;b = temp;// 直接交换实参
}int main() {int x = 10, y = 20;cout << "交换前: x=" << x << ", y=" << y << endl;wrongSwap(x, y);cout << "错误交换后: x=" << x << ", y=" << y << endl;correctSwap(x, y);cout << "正确交换后: x=" << x << ", y=" << y << endl;return 0;
}
5.5.2 避免大对象的复制
#include <iostream>
#include <vector>
#include <string>
using namespace std;// 低效:传值会复制整个vector
void printVector1(vector<int> v) { // 复制开销大cout << "Vector size: " << v.size() << endl;for (int x : v) cout << x << " ";cout << endl;
}// 高效:传引用不复制
void printVector2(const vector<int>& v) { // const防止修改cout << "Vector size: " << v.size() << endl;for (int x : v) cout << x << " ";cout << endl;
}// 可以修改的引用传递
void addOne(vector<int>& v) {for (int& x : v) { // 注意这里也用引用x++;}
}int main() {vector<int> nums = {1, 2, 3, 4, 5};printVector2(nums); // 高效的只读访问addOne(nums); // 修改vectorprintVector2(nums); // 再次查看return 0;
}
5.5.3 返回多个值
#include <iostream>
using namespace std;// 通过引用参数返回多个值
void divideWithRemainder(int dividend, int divisor, int& quotient, int& remainder) {quotient = dividend / divisor;remainder = dividend % divisor;
}// 解二次方程 ax² + bx + c = 0
bool solveQuadratic(double a, double b, double c,double& x1, double& x2) {double discriminant = b * b - 4 * a * c;if (discriminant < 0) {return false; // 无实数解}x1 = (-b + sqrt(discriminant)) / (2 * a);x2 = (-b - sqrt(discriminant)) / (2 * a);return true;
}int main() {// 除法示例int q, r;divideWithRemainder(17, 5, q, r);cout << "17 ÷ 5 = " << q << " 余 " << r << endl;// 二次方程示例double root1, root2;if (solveQuadratic(1, -5, 6, root1, root2)) {cout << "方程 x² - 5x + 6 = 0 的解:" << endl;cout << "x1 = " << root1 << ", x2 = " << root2 << endl;}return 0;
}
5.6 常引用(const引用)
#include <iostream>
#include <string>
using namespace std;int main() {int a = 10;const int& ref = a; // 常引用,不能通过ref修改acout << ref << endl; // 可以读取// ref = 20; // 错误:不能修改a = 20; // 可以直接修改acout << ref << endl; // ref会反映a的变化:20// 常引用可以绑定到字面量const int& ref2 = 100; // 正确// int& ref3 = 100; // 错误:非常引用不能绑定字面量return 0;
}
常引用的优势:
- 可以接受字面量和临时对象
- 防止意外修改
- 提高代码安全性
// 函数参数使用常引用
void printString(const string& s) { // 高效且安全cout << s << endl;// s += "!"; // 错误:不能修改
}int main() {string name = "Alice";printString(name); // 传递变量printString("Bob"); // 传递字面量printString(name + "!"); // 传递临时对象return 0;
}
6. 联合体(Union)
6.1 什么是联合体
联合体是一种特殊的数据类型,所有成员共享同一块内存空间。同一时刻只能存储其中一个成员的值。
#include <iostream>
using namespace std;union Data {int i; // 4字节float f; // 4字节char c; // 1字节
}; // 联合体大小 = 最大成员的大小 = 4字节int main() {Data d;cout << "联合体大小: " << sizeof(d) << " 字节" << endl; // 4cout << "int大小: " << sizeof(d.i) << " 字节" << endl; // 4cout << "float大小: " << sizeof(d.f) << " 字节" << endl; // 4cout << "char大小: " << sizeof(d.c) << " 字节" << endl; // 1return 0;
}
6.2 联合体的内存共享
#include <iostream>
using namespace std;union Data {int i;float f;char c;
};int main() {Data d;// 存储整数d.i = 1000;cout << "存储整数后:" << endl;cout << "d.i = " << d.i << endl; // 1000cout << "d.f = " << d.f << endl; // 垃圾值cout << "d.c = " << (int)d.c << endl; // 垃圾值// 存储浮点数(覆盖之前的整数)d.f = 3.14f;cout << "\n存储浮点数后:" << endl;cout << "d.i = " << d.i << endl; // 垃圾值cout << "d.f = " << d.f << endl; // 3.14cout << "d.c = " << (int)d.c << endl; // 垃圾值// 存储字符(覆盖之前的浮点数)d.c = 'A';cout << "\n存储字符后:" << endl;cout << "d.i = " << d.i << endl; // 垃圾值cout << "d.f = " << d.f << endl; // 垃圾值cout << "d.c = " << d.c << endl; // Areturn 0;
}
6.3 联合体 vs 结构体
#include <iostream>
using namespace std;// 结构体:每个成员有独立的内存
struct StructData {int i; // 偏移0,4字节float f; // 偏移4,4字节char c; // 偏移8,1字节// 总大小:12字节(考虑内存对齐)
};// 联合体:所有成员共享内存
union UnionData {int i; // 偏移0,4字节float f; // 偏移0,4字节char c; // 偏移0,1字节// 总大小:4字节(最大成员)
};int main() {StructData s;UnionData u;cout << "结构体大小: " << sizeof(s) << " 字节" << endl; // 12cout << "联合体大小: " << sizeof(u) << " 字节" << endl; // 4// 结构体:每个成员独立s.i = 100;s.f = 3.14f;s.c = 'X';cout << "\n结构体存储后:" << endl;cout << "s.i = " << s.i << endl; // 100cout << "s.f = " << s.f << endl; // 3.14cout << "s.c = " << s.c << endl; // X// 联合体:只能存储一个值u.i = 100;cout << "\n联合体存储整数后:" << endl;cout << "u.i = " << u.i << endl; // 100u.f = 3.14f; // 覆盖之前的整数cout << "\n联合体存储浮点数后:" << endl;cout << "u.f = " << u.f << endl; // 3.14cout << "u.i = " << u.i << endl; // 垃圾值return 0;
}
6.4 联合体的实际应用
6.4.1 节省内存空间
#include <iostream>
using namespace std;// 一个变量在不同时刻需要不同类型的值
enum DataType { INT_TYPE, FLOAT_TYPE, CHAR_TYPE };struct Variable {DataType type; // 记录当前存储的类型union {int intVal;float floatVal;char charVal;} value;
};void printVariable(const Variable& var) {switch (var.type) {case INT_TYPE:cout << "整数: " << var.value.intVal << endl;break;case FLOAT_TYPE:cout << "浮点数: " << var.value.floatVal << endl;break;case CHAR_TYPE:cout << "字符: " << var.value.charVal << endl;break;}
}int main() {Variable var;// 存储整数var.type = INT_TYPE;var.value.intVal = 42;printVariable(var);// 存储浮点数var.type = FLOAT_TYPE;var.value.floatVal = 3.14f;printVariable(var);// 存储字符var.type = CHAR_TYPE;var.value.charVal = 'A';printVariable(var);return 0;
}
6.4.2 类型转换和位操作
#include <iostream>
#include <iomanip>
using namespace std;// 查看浮点数的二进制表示
union FloatBits {float f;unsigned int bits;
};// IP地址的不同表示
union IPAddress {unsigned int addr; // 32位整数表示unsigned char bytes[4]; // 4个字节表示
};int main() {// 浮点数位表示FloatBits fb;fb.f = 3.14f;cout << "浮点数 " << fb.f << " 的二进制表示: " << hex << fb.bits << dec << endl;// IP地址转换IPAddress ip;ip.bytes[0] = 192;ip.bytes[1] = 168;ip.bytes[2] = 1;ip.bytes[3] = 100;cout << "IP地址: " << (int)ip.bytes[0] << "."<< (int)ip.bytes[1] << "."<< (int)ip.bytes[2] << "."<< (int)ip.bytes[3] << endl;cout << "32位表示: " << ip.addr << endl;return 0;
}
6.4.3 网络数据包解析
#include <iostream>
using namespace std;// 网络数据包头部
union PacketHeader {struct {unsigned char version : 4; // 版本号(4位)unsigned char headerLen : 4; // 头部长度(4位)unsigned char typeOfService; // 服务类型(8位)unsigned short totalLength; // 总长度(16位)} fields;unsigned int raw; // 原始32位数据
};// 颜色的不同表示
union Color {struct {unsigned char r, g, b, a; // RGBA分量} rgba;unsigned int value; // 32位颜色值
};int main() {// 颜色示例Color color;color.rgba.r = 255; // 红色分量color.rgba.g = 128; // 绿色分量color.rgba.b = 0; // 蓝色分量color.rgba.a = 255; // 透明度cout << "颜色 RGB(" << (int)color.rgba.r << ", " << (int)color.rgba.g << ", " << (int)color.rgba.b << ")" << endl;cout << "32位值: " << hex << color.value << dec << endl;return 0;
}
6.5 匿名联合体
#include <iostream>
using namespace std;struct Point {int type; // 点的类型union { // 匿名联合体struct { int x, y; } point2D; // 2D坐标struct { int x, y, z; } point3D; // 3D坐标int coordinates[3]; // 数组形式}; // 注意分号
};int main() {Point p;p.type = 2; // 2D点// 直接访问联合体成员,不需要联合体名称p.point2D.x = 10;p.point2D.y = 20;cout << "2D点: (" << p.point2D.x << ", " << p.point2D.y << ")" << endl;// 也可以通过数组访问cout << "通过数组: (" << p.coordinates[0] << ", " << p.coordinates[1] << ")" << endl;return 0;
}
6.6 联合体的注意事项
- 类型安全:程序员需要记住当前存储的是哪种类型
- 初始化:只能初始化第一个成员
- 构造函数:C++11后联合体可以有构造函数和析构函数
- 内存对齐:联合体的大小会考虑最严格的对齐要求
#include <iostream>
using namespace std;union Data {int i;double d; // 8字节,需要8字节对齐char c;
};int main() {Data d1 = {100}; // 只能初始化第一个成员cout << "d1.i = " << d1.i << endl;// Data d2 = {3.14}; // 错误:不能初始化第二个成员cout << "联合体大小: " << sizeof(Data) << " 字节" << endl; // 8字节return 0;
}
6.7 现代C++中的联合体
C++17引入了std::variant作为类型安全的联合体替代:
#include <iostream>
#include <variant>
#include <string>
using namespace std;int main() {// 类型安全的联合体variant<int, float, string> v;v = 42;cout << "整数: " << get<int>(v) << endl;v = 3.14f;cout << "浮点数: " << get<float>(v) << endl;v = string("Hello");cout << "字符串: " << get<string>(v) << endl;return 0;
}
联合体总结:
- 优点:节省内存,适合嵌入式系统
- 缺点:类型不安全,需要程序员管理
- 现代替代:
std::variant提供类型安全的选择