# 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】
事件描述备注:保存/发布
中国 天津市 天津
[](如果不需要此记录可以手动删除,每次保存都会自动的追加记录)