# C++ 从 入门 到数据结构 *C++ 技术栏* 本文将详细演示如何使用 C++ 让您能够完美入门! ## 目录 [TOC]  # 控制台打印 ## cout 操作 cout 是用于将各种数据类型输出到控制台的一种手段,下面是个示例 ### 基本使用 ```c++ #include <iostream> class Test { public: int age; int id; }; int main() { Test test = Test(); test.age = 1024; test.id = 10; std::cout << "Hello, World!" << std::endl; std::cout << 1024 << std::endl; std::cout << 1024.1 << std::endl; std::cout << test.id << " " << test.age << std::endl; return 0; } ``` ### 对齐使用 #### 设置字段宽度 - setw 我们需要引入 `#include <iomanip>` 文件! ```C++ #include <iostream> #include <iomanip> class Test { public: int age; int id; }; int main() { Test test = Test(); test.age = 1024; test.id = 10; std::cout << std::setw(10) << "Hello, World!" << std::endl; std::cout << std::setw(10) << 1024 << std::endl; std::cout << std::setw(10) << 1024.1 << std::endl; std::cout << std::setw(10) << test.id << " " << test.age << std::endl; return 0; } ``` #### 设置字段对其方式 left or right ```C++ #include <iostream> #include <iomanip> class Test { public: int age; int id; }; int main() { Test test = Test(); test.age = 1024; test.id = 10; std::cout << std::left << std::setw(10) << "Hello, World!" << std::endl; std::cout << std::left << std::setw(10) << 1024 << std::endl; std::cout << std::left << std::setw(10) << 1024.1 << std::endl; std::cout << std::left << std::setw(10) << test.id << " " << test.age << std::endl; return 0; } ``` 下面是运行结果 ``` Hello, World! 1024 1024.1 10 1024 ``` ## cin操作 cout操作用于程序将数据输出到控制台,进而被我们看到,cin是用于让我们将数据进入到程序,是相反的操作!下面是示例 ```C++ #include <iostream> #include "string" int main() { system("chcp 65001"); std::string text; std::cout << "请输入内容:"; std::cin >> text; std::cout << "您输入的是:" << text << std::endl; return 0; } ``` ## printf 在 C++ 中虽然我们推荐使用 `std::cout` 进行输出,但如果你希望使用更传统的 **格式化输出方式**,C++ 也完全支持使用 C 风格的 `printf` 函数,它来自 `<cstdio>`(对应 C 的 `<stdio.h>`)头文件。 `printf` 的格式化语法非常强大,可以用来控制输出的格式、精度、宽度、对齐方式等。下面是一个 **完整、详细、适合教学和开发使用的 C++ 中 `printf` 的格式化语法指南**。 --- ### 🧾 一、基本语法 ```cpp #include <cstdio> // C++ 中使用 C 的 stdio.h int main() { printf("格式字符串", 参数1, 参数2, ...); } ``` 格式字符串中可以包含: - 普通字符(原样输出) - 转义字符(如 `\n`, `\t`) - 格式说明符(以 `%` 开头,用于替换变量) --- ### 🧩 二、常用格式说明符(Format Specifiers) | 格式符 | 类型 | 示例 | |--------|------|------| | `%d` 或 `%i` | int | `printf("%d", 123);` → `123` | | `%u` | unsigned int | `printf("%u", 456);` | | `%ld` | long | `printf("%ld", 123456789L);` | | `%lu` | unsigned long | `printf("%lu", 123456789UL);` | | `%lld` | long long | `printf("%lld", 1234567890123LL);` | | `%llu` | unsigned long long | `printf("%llu", 1234567890123ULL);` | | `%f` | double/float | `printf("%f", 3.1415);` → `3.141500` | | `%lf` | double(在 scanf 中使用) | 同 `%f` | | `%e` 或 `%E` | 科学计数法(小写e/大写E) | `printf("%e", 123.45);` → `1.234500e+02` | | `%g` 或 `%G` | 自动选择 `%f` 或 `%e` / `%E` | 更紧凑 | | `%c` | char | `printf("%c", 'A');` | | `%s` | 字符串 (char*) | `printf("%s", "Hello");` | | `%p` | 指针地址 | `int x; printf("%p", &x);` | | `%x` 或 `%X` | 十六进制(小写/大写) | `printf("%x", 255);` → `ff` | | `%o` | 八进制 | `printf("%o", 10);` → `12` | | `%%` | 输出一个 `%` 符号 | `printf("百分比:50%%");` | --- ### 🎛 三、格式化控制参数(修饰符) 你可以在 `%` 和格式符之间添加 **修饰符** 来控制输出格式: #### 1. 最小字段宽度 ```cpp printf("%10d", 123); // 输出: 123(右对齐,占10个字符) ``` #### 2. 精度控制 ```cpp printf("%.2f", 3.14159); // 输出:3.14 printf("%.5s", "HelloWorld"); // 输出:Hello ``` #### 3. 对齐方式 默认是右对齐,添加 `-` 表示左对齐: ```cpp printf("%-10s", "Left"); // 输出:Left (左对齐) ``` #### 4. 填充字符 默认填充是空格,可以用 `0` 填充数字: ```cpp printf("%05d", 12); // 输出:00012 ``` #### 5. 正负号显示 ```cpp printf("%+d", 123); // 输出:+123 printf("% d", 123); // 输出: 123(正数加空格,负数加-) ``` #### 6. 进制前缀(#) ```cpp printf("%#x", 255); // 输出:0xff printf("%#o", 10); // 输出:012 ``` --- ### 🧮 四、修饰符类型(长度修饰符) | 修饰符 | 适用类型 | 举例 | |--------|----------|------| | `h` | short | `%hd` | | `hh` | signed char | `%hhd` | | `l` | long | `%ld` | | `ll` | long long | `%lld` | | `L` | long double | `%Lf` | --- ### 📌 五、示例汇总 ```cpp #include <cstdio> int main() { int i = 123; double d = 3.1415926535; char str[] = "Hello"; char ch = 'A'; printf("整数:%d\n", i); printf("浮点数:%.2f\n", d); // 保留2位小数 printf("字符串:%s\n", str); printf("字符:%c\n", ch); printf("十六进制:%x\n", i); // 输出:7b printf("左对齐:%10s -> 右对齐\n", "left"); printf("右对齐:%-10s <- 左对齐\n", "right"); printf("带符号:%+d\n", i); printf("科学计数法:%e\n", d); printf("自动格式:%g\n", d); printf("地址:%p\n", &i); printf("填充零:%05d\n", i); printf("进制前缀:%#x\n", i); printf("百分比:50%%\n"); return 0; } ``` --- ### 📚 六、与std::cout 的对比(教学建议) | 特性 | `printf` | `std::cout` | |------|----------|-------------| | 学习曲线 | 中等(格式符复杂) | 简单 | | 类型安全 | ❌ 不安全(类型不匹配会出错) | ✅ 安全 | | 可扩展性 | ❌ 不能自定义类型输出 | ✅ 可重载 `<<` | | 多语言支持 | ✅ 更好(格式化独立) | 依赖 `std::locale` | | 性能 | ✅ 更快(适用于日志系统) | 略慢 | | 可读性 | ⚠️ 格式符复杂 | ✅ 更清晰 | --- ### ✅ 七、总结:什么时候用 printf? | 场景 | 推荐使用 `printf` | |------|-------------------| | 日志输出 | ✅ 快速、格式统一 | | 竞赛编程 | ✅ 快速调试 | | 嵌入式开发 | ✅ 资源有限 | | 多语言支持 | ✅ 格式字符串可外部化 | --- ### 📚 附录:完整格式字符串模板 ```cpp %[flags][width][.precision][length]type ``` - `flags`: `-`(左对齐)、`+`(显示符号)、空格、`0`(填充0)、`#`(进制前缀) - `width`: 最小字段宽度 - `.precision`: 浮点数精度 / 字符串最大长度 - `length`: `h`, `l`, `ll`, `L` 等 - `type`: 格式符(如 `d`, `f`, `s` 等) # 变量与数据类型与指针 ## 基本数据类型 C++的基本数据类型包括以下几种: **整型** - char: 通常用于表示字符,但实际上是整数类型,大小至少为8位。 - short 或 short int: 短整型,大小至少为16位。 - int: 整型,大小至少为16位,但在现代系统上通常是32位。 - long 或 long int: 长整型,大小至少为32位。 - long long 或 long long int: 更长的整型,大小至少为64位(C99标准引入)。 **浮点型** - float: 单精度浮点型,通常占用32位。 - double: 双精度浮点型,通常占用64位。 - long double: 扩展精度浮点型,大小不小于double,具体取决于实现。 **布尔型** > 值得一提的是 在C++ 中任何数值都可以隐式的转换为 布尔 - 0 转换为 false:如果一个数值表达式的值为0(无论是整数、浮点数还是指针),它在布尔上下文中被视为false。 - 非0 转换为 true:如果一个数值表达式的值不是0,则在布尔上下文中被视为true - bool: 表示逻辑值,可以是true或false。 **空类型** - void: 表示没有类型,通常用于函数返回类型,表示该函数不返回任何值。 ## 变量 当了解了基本类型之后,我们将可以创建一些变量来使用他们! ``` #include "iostream" using namespace std; int main() { // 这是一个整形 int n1 = 1024; // 这是一个长整形 long n2 = 1024; // 这是一个浮点 double n3 = 1024.1024; // 这是一个布尔 bool n4 = false; // 可以隐式转换布尔类型 不会报错 bool n5 = 1; // 打印一下数据 cout << n1 << endl; cout << n2 << endl; cout << n3 << endl; cout << n4 << endl; cout << n5 << endl; // 隐式转换的数据打印完还是 1 即使它可以给 if使用 return 0; } ``` ## 指针 🏙 C++ 中的数据是存储在内存中的,内存就像一个很大的城市,有很多住宅,每个住宅都有一个唯一的地址,用于存放不同的居民(数据)。 🧱 1. 内存就像一座城市 你可以把整个内存想象成一座巨大的城市,里面有成千上万的“房子”——这些“房子”就是内存中的 字节(Byte)。 每个“房子”都有一个唯一的地址,这个地址是一个数字,称为 内存地址(Memory Address)。 数据就住在这些“房子”里,比如一个 int 类型的数据可能占用 4 个连续的“房子”(字节)。 程序运行时,所有的变量、对象、函数等都会被分配到这些“房子”中。 🏠 2. 变量是带标签的房子 在 C++ 中,变量就是我们给这些“房子”贴上的标签,方便我们记住它存放的是什么数据 ```C++ int age = 25; ``` 这行代码的意思是:我们在内存中找一个能装下 int 的空间(通常是 4 个字节),把 25 存进去,并给这个空间起个名字叫 age。 ### 数值到指针的转换 age 就是一个“标签”,它指向内存中的一个位置。 你可以通过 &age 获取这个变量指针对象,也可以认为其是内存地址: ``` #include "iostream" using namespace std; int main() { int age = 25; // 获取到 age 的指针 int* agePoint = &age; // 打印内存地址 cout << agePoint << endl; return 0; } ``` ### 指针到数值的转换 在之前的代码中,agePoint 是一个指针变量,它指向一个 int 类型的变量。 它的值是 age 的地址。 你可以使用如下的方法来通过指针访问变量的值: ``` #include "iostream" using namespace std; int main() { int age = 25; // 获取到 age 的指针 int* agePoint = &age; // 打印内存地址 cout << agePoint << endl; // 反向获取到地址中存储的值 int ageNumber = *agePoint; // 打印 cout << ageNumber << endl; return 0; } ``` 下面是运行结果 ``` 0x5ffe80 25 ``` ## 数组数据类型 数组是一个很多数字的集合,每个数字是连续的住宅,我们先来看看如何使用数组 然后演示如何利用指针玩数组 ### 定义一个数组 并访问元素 ``` #include "iostream" using namespace std; int main() { int numbers[5] = {10, 22, 37, 49, 57}; // 打印其中的数值 cout << numbers[0] << endl; cout << numbers[1] << endl; cout << numbers[2] << endl; cout << numbers[3] << endl; cout << numbers[4] << endl; return 0; } ``` ### 修改其中的数据 ``` #include "iostream" using namespace std; int main() { int numbers[5] = {10, 22, 37, 49, 57}; // 打印其中的数值 cout << numbers[0] << endl; cout << numbers[1] << endl; cout << numbers[2] << endl; cout << numbers[3] << endl; cout << numbers[4] << endl; // 修改其中的 numbers[2] numbers[2] = 1024; cout << numbers[2] << endl; return 0; } ``` ### 关于堆栈内存 C++ 中的内存分为两个主要区域: 区域 特点 示例 栈(Stack) 自动分配,自动释放,速度快 局部变量、函数调用 堆(Heap) 手动分配(new),手动释放(delete),灵活但容易出错 动态数组、对象 你可以把栈想象成“短租房”,你进去住了一段时间,离开后自动退房; 堆就像是“长租房”,你住进去后要自己记得退房。 #### 创建一个数组在堆中 > 这个刚刚已经使用过啦 就是直接用大括号! #### 创建一个数组在栈中(需要释放) ``` #include "iostream" using namespace std; int main() { int *numbers = new int[]{10, 22, 37, 49, 57}; // 打印其中的数值 cout << numbers[0] << endl; cout << numbers[1] << endl; cout << numbers[2] << endl; cout << numbers[3] << endl; cout << numbers[4] << endl; // 修改其中的 numbers[2] numbers[2] = 1024; cout << numbers[2] << endl; // 注意在这里释放 delete[] numbers; return 0; } ``` #### 数组的指针 ``` #include "iostream" using namespace std; int main() { // 这里是一个数组的指针,这个指针指向的是数组中第一个元素的内存地址 int *numbers = new int[]{10, 22, 37, 49, 57}; // 获取到数组的指针对象 // (注意 使用 new 创建的数组,上面变量类型是一个内存地址,这里再一次提取了指针,因此这个地方相当于是指针的指针 所以是 int **) int **numbersPoint = &numbers; cout << numbersPoint << endl; // 通过指针对象获取索引为 2 的元素 // 首先需要获取到索引为 2 的元素对应的指针 // numbers指针 + 2 会直接跳转到这个内存地址的下两个 int *numberPoint2 = numbers+2; // 直接打印 cout << *numberPoint2 << endl; // 注意在这里释放 delete[] numbers; return 0; } ``` ### 关于野指针 如果你使用了一个已经释放的指针,就会导致“野指针(Dangling Pointer)”问题: ``` int* ptr = new int(100); delete ptr; std::cout << *ptr; // ❌ 野指针!访问了已经被释放的内存 ``` 这就像你拿着一个已经退房的钥匙去开门,结果可能不可预测。 为了避免这个问题,释放内存后最好把指针设为 nullptr: ``` delete ptr; ptr = nullptr; ``` ## 运算符 ### 基本运算符 > + - * / % 略过 ### 逻辑运算符 > && || ! 略过 ### 指针的基本运算 指针不是数字,因此它的计算方法是不同的,下面是具体的总结 | 运算 | 说明 | 示例 | |------|------|------| | `p++` | 指针向后移动一个元素 | `p` 指向下一个 `int` | | `p--` | 向前移动一个元素 | | | `p + n` | 向后移动 n 个元素 | `p + 2` | | `p - n` | 向前移动 n 个元素 | | | `p1 - p2` | 两个指针之间的元素个数 | 仅限同一数组内 | | `p1 < p2` | 比较地址大小 | 可用于判断位置 | > ✅ 注意:指针的“+1”不是地址加1,而是加 **一个元素的大小**。 > 例如:`int* p`,`p + 1` 实际上是 `p + sizeof(int)` 字节。 ### ✅ 示例:遍历数组 ```cpp int arr[] = {10, 20, 30, 40, 50}; int *p = arr; // 指向第一个元素 for (int i = 0; i < 5; ++i) { cout << *p << " "; // 输出值 p++; // 指针后移 } // 输出:10 20 30 40 50 ``` --- ## 🧩 逻辑运算符 + 指针 的经典结合用法 ### ✅ 1. 安全解引用指针(防止空指针崩溃) ```cpp int *ptr = nullptr; // 安全写法:先判断是否为空,再访问 if (ptr != nullptr && *ptr > 10) { cout << "值大于10"; } ``` > 🔥 关键:利用 **短路求值** > 如果 `ptr == nullptr`,`&&` 后面的 `*ptr > 10` 不会执行,避免崩溃! ❌ 错误写法: ```cpp if (*ptr > 10 && ptr != nullptr) { // 先解引用!崩溃! // ... } ``` --- ### ✅ 2. 多重指针检查 ```cpp int **pp = nullptr; if (pp != nullptr && *pp != nullptr && **pp > 0) { cout << "二级指针安全访问:" << **pp; } ``` 层层检查,避免空指针访问。 --- ### ✅ 3. 遍历数组并判断边界 ```cpp int arr[5] = {1, 2, 3, 4, 5}; int *p = arr; while (p < arr + 5) { // 指针比较:p 是否还在数组范围内 if (*p % 2 == 0) { cout << *p << " 是偶数" << endl; } p++; } ``` - `arr + 5` 是数组末尾的“哨兵地址”; - `p < arr + 5` 是典型的指针比较。 --- ## 📌 常见错误与陷阱 | 错误 | 说明 | |------|------| | `if (ptr && *ptr > 0)` | ✅ 正确:利用短路避免空指针 | | `if (*ptr > 0 && ptr)` | ❌ 危险:先解引用可能崩溃 | | `p + 100` 越界 | ❌ 访问非法内存 | | `delete p; p = nullptr;` | ✅ 好习惯:防止野指针 | | `delete[]` vs `delete` | 数组用 `delete[]`,否则未定义行为 | --- ## 🧰 现代 C++ 推荐替代方案 虽然指针很强大,但现代 C++ 更推荐使用: | 替代方案 | 优点 | |----------|------| | `std::vector<int>` | 自动管理内存,支持 `.data()` 获取指针 | | `std::unique_ptr<int[]>` | 智能指针,自动释放 | | `std::shared_ptr<int[]>` | 共享所有权 | | `std::optional<int*>` | 明确表示可能为空 | ### 示例:用智能指针替代裸指针 ```cpp #include <memory> auto arr = std::make_unique<int[]>(5); arr[0] = 10; // 无需手动 delete,自动释放 ``` ## 位运算符 C++ 的位运算符 有 `<<` `>>` `^` `&` `|` `~` 下面是一个使用示例 ``` int main() { // 准备数值 int a1 = 8; // 二进制为 1000 int a2 = 10; // 二进制为 1010 int a3 = 0; // 进行位运算 // 对两个操作数的每一位执行与操作。只有当两个相应的位都为 1 时,结果位才为 1 cout << (a1 & a2) << endl; cout << (a1 & a3) << endl; // 或运算 对两个操作数的每一位执行或操作。只要其中一个相应的位为 1,结果位就为 1 cout << (a1 | a2) << endl; cout << (a1 | a3) << endl; // 异或运算 对两个操作数的每一位执行异或操作。当两个相应的位不同时,结果位为 1 cout << (a1 ^ a2) << endl; // 取反运算 对操作数的每一位执行取反操作。将每个 1 改为 0,每个 0 改为 1。 cout << (~a1) << endl; } ``` # 流程控制 略 # 函数操作 ## 关于参数声明 ### 值传递 值传递操作,在函数内部不需要对参数解包,可以直接使用,修改操作只在函数内部生效,因为值传递本质上是复制了一个数值到新的内存地址,任何对参数的修改根本不会影响函数外的参数! ``` void fun(int a) { a += 1; cout << "函数内部 a = " << a << endl; } int main() { system("chcp 65001"); // 准备数值 int a1 = 8; // 二进制为 1000 // 修改参数(值传递会修改失败) fun(a1); cout << "函数外部 a = " << a1 << endl; } ``` ### 值的引用传递 - & C++ 中的函数形参 使用 & 修饰,这代表传递进函数的值是和外面的值属于一个内存地址哦!函数内部也是不需要解包的!可以直接使用 ``` void fun(int &a) { a += 1; cout << "函数内部 a = " << a << endl; } int main() { system("chcp 65001"); // 准备数值 int a1 = 8; // 二进制为 1000 // 修改参数(值传递会修改失败) fun(a1); cout << "函数外部 a = " << a1 << endl; } ``` ### 值的指针传递 指针传递 是最底层的一种传递方法,其和值的引用传递很像,但是更灵活,需要在函数内解包,提取数值! ``` #include "iostream" using namespace std; void fun(int *a) { // 解包一下然后修改 *a += 1; cout << "函数内部 a = " << *a << endl; } int main() { system("chcp 65001"); // 准备数值 int a1 = 8; // 二进制为 1000 // 修改参数(值传递会修改失败) fun(&a1); cout << "函数外部 a = " << a1 << endl; } ``` ### 左值和右值,右值引用 && 左值其实就是在带有 `=` 的表达式中左边的,一般就是变量之类的东西,也是上面我们使用的。 左值可以提取内存地址,生命周期较长 右值就是表达式右边的,一般是临时的常量,比如“zhao” 下面我们使用了一个例子演示传递一个右值 ``` #include "iostream" using namespace std; void fun(int &a) { // 解包一下然后修改 a += 1; cout << "函数内部 a = " << a << endl; } void fun2(int &&a) { // 解包一下然后修改 a += 1; cout << "函数内部 a = " << a << endl; } int main() { system("chcp 65001"); // 准备数值 int a1 = 8; // 二进制为 1000 // 左值引用传递 fun(a1); // 右值引用传递 fun2(1024); } ``` # 类与对象 ## 语义转移 - std::move 当传递的是一个外部的变量的时候,使用std::move 来实现将这个左值(如外部的变量)转换为一个右值(如常量),让这个值属于自己这个类! ### const 下不需要使用 因为 const 情况的变量本身也是不可以被修改的,所以也没必要传递所有权的概念,属于任何人可读不可写,仅仅是读,没必要担心。 ### 非 const 下的左值需要使用 我们在外面的变量,传递给一个类,在这里是 `name` 如果这个类被构造了,我在外面修改 name,类也会变化,这引起不必要的麻烦,因此 move一下 直接让这种情况不存在风险,值得一提的是,这样之后变量似乎也用不了了,因为 std::move 本身是为了不拷贝,减少开销,因此直接把这个内存地址给类了,谁都用不了。 ``` #include <utility> #include "iostream" using namespace std; class User { public: string name; int age; User(string &name, int age) { // 移动所有权 确保这个 name 完全属于这个类 this->name = std::move(name); this->age = age; } }; ostream& operator <<(ostream &os, User &u) { os << R"({"name"=")" << u.name << R"(", "age"=")" << u.age << "\"}"; return os; } int main() { system("chcp 65001"); // 准备数值 string name = "zhao"; // 构建用户类 User user = User(name, 1024); cout << "被传递给函数之前:" << user << endl; } ``` ## 结构体 ### 定义与使用 ``` #include "iostream" using namespace std; struct User { public: string name; int age; }; int main() { User user = User{"zhao", 22}; // 直接打印 cout << user.name << endl; cout << user.age << endl; // 修改 user.name = "zhang"; cout << user.name << endl; cout << user.age << endl; } ``` ## 联合体 ### 定义与使用 ``` #include "iostream" using namespace std; union User { long id; int age; }; int main() { union User user{}; user.id = 10240000; // 查看 name 和 age 的内存地址 cout << &user.id << endl; cout << &user.age << endl; // 查看值 cout << user.id << endl; cout << user.age << endl; // 总结 联合体的每个字段都是同一个内存地址 用于节约内存空间 // 也正因如此 使用联合体的时候,我们应该值调用其中的一个字段! } ``` ------ ***操作记录*** 作者:[zhao](https://www.lingyuzhao.top//index.html?search=4 "zhao") 操作时间:2025-08-01 23:12:58 星期五 【时区:UTC 8】 事件描述备注:保存/发布 中国 天津市 天津 [](如果不需要此记录可以手动删除,每次保存都会自动的追加记录)