CB1-2-基础启航
C/C++道经第1卷 - 第2阶 - 基础启航
文章目录
- S01. 环境搭建
- S02. 基础知识
- E01. 预处理机制
- 1. include 指令
- 2. define 指令
- 3. undef 指令
- 4. 预定义宏
- 5. 条件编译
- E03. 程序运行原理
- 1. GCC
- 2. Make
- 3. CMake
- E04. 代码注释
- E05. 输出语句
- 1. printf
- 2. puts
- 3. cout
- 4. 特殊字符
- E06. 输入语句
- 1. scanf
- 2. gets
- 3. cin
- S03. 常量变量
- E01. 定义常量
- E02. 定义变量
- E01. 数据类型
- 1. 数字类型
- 2. 字符类型
- 3. 布尔类型
- 4. 字符串类型
- 5. 类型转换
- 6. 类型计算
- 7. 结果溢出
- S04. 运算符号
- E01. 基本运算符
- 1. 数学运算符
- 2. 赋值运算符
- 3. 自运算符
- 4. 关系运算符
- 5. 逻辑运算符
- 6. 位运算符
- E02. 其他运算符
- 1. 三目运算符
- 2. 数学工具类
- S05. 流程控制
- E01. 选择结构
- 1. IfElse结构
- 2. Switch结构
- E02. 循环结构
- 1. While循环
- 2. DoWhile循环
- 3. For循环
- E03. 流控关键字
- 1. continue
- 2. break
- S06. 数组类型
- E01. 一维数组
- 1. 一维数组声明
- 2. 一维数组遍历
- E02. 多维数组
- 1. 二维数组声明
- 2. 二维数组遍历
- S07. 高级知识
- E01. 函数
- 1. 函数创建
- 2. 函数传参
- 3. 函数递归
- 5. Lambda函数
- E02. 指针
- 1. 指针创建
- 2. 指针传递
- 3. 常量指针
- 4. 指针数组
- 5. 指针函数
- E03. 结构体
- 1. 结构体
- 2. 枚举类
- E04. 类型别名
- 1. typedef
- 2. using
- E05. 内存操作
- 1. malloc
- 2. calloc
- 3. realloc
- 4. new
- E06. 文件操作
- 1. fopen
- 2. fstream
心法:本章使用 C++ 项目进行练习
练习项目结构如下:
|_ v1-2-basic-start
S01. 环境搭建
心法:参考 CB1-1-新手村(一)S01 笔记。
S02. 基础知识
C++ 是在 C 语言的基础上发展而来的,它保留了 C 语言的大部分特性,同时进行了许多扩展和改进。
C 主要是面向过程的编程语言,它强调将程序分解为一系列的函数,通过函数的调用和数据的传递来完成特定的任务。程序的执行流程是按照函数的调用顺序依次进行的。
C++ 支持多种编程范式,包括面向过程、面向对象、泛型编程等。面向对象编程允许将数据和操作数据的函数封装在一起,形成类和对象,通过继承、多态等机制提高代码的可复用性和可维护性。
E01. 预处理机制
1. include 指令
心法:预处理器遇到 include 指令时,会将指定文件内容插入当前文件,替换该指令。
C++ 有自己独立的标准库头文件,例如 <iostream>
、<string>
等,这些是 C 语言所没有的。
同时,C++ 为了兼容 C 标准库,也提供了对应的头文件,命名规则是在 C 标准库头文件名前加上 c 并去掉 .h 后缀,如 C 语言中的 <stdio.h>
在 C++ 中可以使用 <cstdio>
进行引入。
其余参考 C 语言笔记: CB1-1-新手村(一)S02E01.1 笔记。
武技:测试 include 指令
// init/Include.h
// Created by 周航宇 on 2025/2/26.
//
#include <cstdio>
#ifndef V1_1_BASIC_ROOKIE_INCLUDE_H
#define V1_1_BASIC_ROOKIE_INCLUDE_Hnamespace Include {void test();
}#endif //V1_1_BASIC_ROOKIE_INCLUDE_H
// init/Include.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Include.h"void Include::test() {printf("使用 C 语言的 Printf\n");
}
2. define 指令
心法:预处理器遇到 define 指令时,会将参数批量替换到文本中,该操作通常被称为 宏 或 定义宏 ,该替换操作发生在程序运行之前,属于纯文本替换(字符串中的文本不会被替换)。
C++ 提供了更好的替代方案来定义常量和函数。
对于常量,C++ 推荐使用 const 关键字,它们具有类型检查,比宏常量更安全。
对于函数,C++ 可以使用 inline 内联函数,内联函数是真正的函数,有类型检查和作用域规则,避免了宏函数可能带来的副作用。
其余参考 C 语言笔记: CB1-1-新手村(一)S02E01.2 笔记。
武技:测试 define 指令
// init/Define.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_DEFINE_H
#define V1_1_BASIC_ROOKIE_DEFINE_H
#include <iostream>namespace Define {// 使用 const 定义常量const double pi = 3.14;// 使用内联函数替代宏函数inline int square(int x) {return x * x;}void test();
}#endif //V1_1_BASIC_ROOKIE_DEFINE_H
// init/Define.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Define.h"void Define::test() {std::cout << "PI:" << pi << std::endl;std::cout << "5的平方:" << square(5) << std::endl;
}
3. undef 指令
心法:undef 指令用于取消之前使用 define 定义的宏,当编译器遇到 undef 指令后,后续代码将不再识别该宏。
C:在 C 语言中,宏是定义常量和简单函数的常用手段,因此 undef 指令的使用相对较为常见。例如,在一些大型项目中,可能会根据不同的编译选项定义不同的宏,当某个宏的作用范围结束后,就可以使用 undef 指令取消其定义,避免对后续代码产生影响。
C++:C++ 提供了更安全和更具类型检查的替代方案,如使用 const 关键字定义常量,使用内联函数替代宏函数。因此,在 C++ 中宏的使用相对较少, undef 指令的使用频率也会相应降低。
其余参考 C 语言笔记: CB1-1-新手村(一)S02E01.3 笔记。
4. 预定义宏
心法:C 语言预定义宏由编译器预设,提供编译环境、代码位置和日期等信息。
C++ 中使用 __cplusplus
获取编译器支持的 C++ 标准版本。例如,201103L 表示支持 C++11 标准,201703L 表示支持 C++17 标准等。
其余参考 C 语言笔记: CB1-1-新手村(一)S02E01.4 笔记。
武技:测试预定义宏
// init/SysDefine.h
// Created by 周航宇 on 2025/2/26.
//
#include <iostream>#ifndef V1_1_BASIC_ROOKIE_SYS_DEFINE_H
#define V1_1_BASIC_ROOKIE_SYS_DEFINE_Hnamespace SysDefine {void test();
}#endif //V1_1_BASIC_ROOKIE_SYS_DEFINE_H
// init/SysDefine.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "SysDefine.h"void SysDefine::test() {// 202002 表示支持 C++20 标准std::cout << "当前C++版本" << __cplusplus << std::endl;
}
5. 条件编译
心法:条件编译包含 ifdef, else, endif 等编译指令,且支持嵌套。
参考 C 语言笔记: CB1-1-新手村(一)S02E01.5 笔记。
E03. 程序运行原理
1. GCC
心法:C++ 中使用 G++ 编译器代替 GCC 编译器,所以命令中的 gcc 命令将全部被替换为 g++ 命令。
参考 C 语言笔记: CB1-1-新手村(一)S02E02.1 笔记。
2. Make
心法:C++ 中使用 G++ 编译器代替 GCC 编译器,所以命令中的 gcc 命令将全部被替换为 g++ 命令。
参考 C 语言笔记: CB1-1-新手村(一)S02E02.2 笔记。
3. CMake
参考 C 语言笔记: CB1-1-新手村(一)S02E02.3 笔记。
E04. 代码注释
参考 C 语言笔记: CB1-1-新手村(一)S02E03 笔记。
E05. 输出语句
1. printf
参考 C 语言笔记: CB1-1-新手村(一)S02E04.1 笔记。
2. puts
参考 C 语言笔记: CB1-1-新手村(一)S02E04.2 笔记。
3. cout
心法:std::cout 是 C++ 标准库中 iostream 头文件里定义的一个输出流对象,用于将数据输出到标准输出设备,通常是控制台。
缓冲机制:
std::cout 是普通的输出语句,为了提高输出效率,减少对屏幕的频繁访问,该类型的输出语句会使用缓冲机制,这意味着输出的数据不会立即显示在屏幕上,而是先存储在缓冲区中,当缓冲区满或者遇到 std::flush 或 std::endl 时,缓冲区中的数据才会被输出到屏幕上。
std::cerr 用于提示一些异常的,错误的信息,为了确保错误信息能及时反馈出来,该类型的输出不使用缓冲机制,会立刻进行输出显示。
命名空间:
std 是一个命名空间,而 cout 是在 std 命名空间中定义的对象,配合 << 插入运算符来输出各种类型的数据:
- 支持在文件首部使用 using namespace std 添加 std 命名空间,此时可以省略文件中的全部 std:: 前缀,但要注意可能会引起命名冲突。
- 支持在文件首部使用 using std::out,此时 std::out 的前缀可以省略,其余同理。
- 无论使用哪种 using 方式,都推荐在源文件中定义,而非在头文件中定义,以免暴露实现细节。
输出语句:
C++ 中的输出语句代码 | 是否自动换行 | 是否刷新缓冲区 | 显示效果 |
---|---|---|---|
std::cout << "内容" | 不换行 | 不刷新 | 延迟显示 |
std::cout << "内容\n" | 换行 | 不刷新 | 延迟显示 |
std::cout << "内容" << std::flush | 不换行 | 刷新 | 立刻显示 |
std::cout << "内容" << std::endl | 换行 | 刷新 | 立刻显示 |
std::cerr << "内容" | 不换行 | 压根不使用缓冲区 | 立刻显示 |
std::cerr << "内容" << std::endl | 换行 | 压根不使用缓冲区 | 立刻显示 |
武技:测试 cout 函数
// basic/COut.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_C_OUT_H
#define V1_1_BASIC_ROOKIE_C_OUT_H
#include <iostream>namespace COut {void testCOut();void testCOutWithBuffer();void testCOutWithNoBuffer();void test();
}#endif // V1_1_BASIC_ROOKIE_C_OUT_H
// basic/COut.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "COut.h"
#include <unistd.h>using std::cout, std::cerr, std::flush, std::endl;void COut::testCOut() {// 打印空白行std::cout << std::endl;// 打印空白行:省略 std 前缀(由于文件头部添加了 using namespace std 配置)cout << endl;// 普通输出cout << "换行(普通信息)\n";cout << "换行(普通信息)" << endl;cout << "不";cout << "换";cout << "行";cout << "\n============" << endl;// 错误输出cerr << "换行(错误信息)\n";cerr << "换行(错误信息)" << endl;cerr << "不";cerr << "换";cerr << "行";
}// 测试不刷新缓冲区的输出语句
void COut::testCOutWithBuffer() {cout << "换行,不刷管\n";cout << "不换行,不刷管";sleep(3);
}// 测试刷新缓冲区的输出语句
void COut::testCOutWithNoBuffer() {cout << "换行,刷管" << endl;cout << "不换行,刷管" << flush;cerr << "不换行,刷管" << flush;sleep(3);
}void COut::test() {testCOut();testCOutWithBuffer();testCOutWithNoBuffer();
}
4. 特殊字符
参考 C 语言笔记: CB1-1-新手村(一)S02E04.3 笔记。
E06. 输入语句
1. scanf
参考 C 语言笔记: CB1-1-新手村(一)S02E05.1 笔记。
2. gets
参考 C 语言笔记: CB1-1-新手村(一)S02E05.2 笔记。
3. cin
心法:std::cin 是 C++ 标准库中 iostream 头文件里定义的一个输入流对象,用于从标准输入设备(通常是键盘)读取数据。
std 是一个命名空间,而 cin 是在 std 命名空间中定义的对象,配合 >>
提取运算符来读取各种类型的数据。
武技:测试 cin 函数
// basic/CIn.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_C_IN_H
#define V1_1_BASIC_ROOKIE_C_IN_H
#include <iostream>namespace CIn {void test();
}#endif //V1_1_BASIC_ROOKIE_C_IN_H
// basic/CIn.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "CIn.h"using std::cout, std::cin, std::string, std::endl;void CIn::test() {// 接收整形数据(浮点数和字符同理)int a;cout << "输入一个整数:" << endl;cin >> a;cout << a << endl;// 接收字符数据string str;cout << "输入一个字符串:" << endl;cin >> str;cout << str << endl;// 接收布尔数据:非 1 的值都视为 false(0)bool flag;cout << "输入一个布尔值(非1即负):" << endl;cin >> flag;cout << flag << endl;
}
S03. 常量变量
心法:常量就是在整个运行过程中都不可以发生变化的量,分为字面量和后天形成的常量两种。
E01. 定义常量
参考 C 语言笔记: CB1-1-新手村(二)S03E01 笔记。
E02. 定义变量
心法:在 C++ 中,auto 关键字允许编译器根据初始化表达式自动推断变量的类型,从而简化代码编写并提高代码的可读性和可维护性。
必须初始化:auto 变量必须在声明时进行初始化,因为编译器需要根据初始化表达式来推断变量的类型。
其余参考 C 语言笔记:CB1-1-新手村(二)S03E02 笔记。
武技:测试 auto 关键字
// basic/AutoType.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_AUTO_TYPE_H
#define V1_1_BASIC_ROOKIE_AUTO_TYPE_H
#include "iostream"namespace AutoType {void test();
};#endif //V1_1_BASIC_ROOKIE_AUTO_TYPE_H
// basic/AutoType.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "AutoType.h"using std::cout, std::endl;void AutoType::test() {// 编译器根据 10 推断 x 的类型为 intauto x = 10;// 编译器根据 3.14 推断 y 的类型为 doubleauto y = 3.14;// 编译器根据 'A' 推断 z 的类型为 charauto z = 'A';cout << "x: " << x << ", y: " << y << ", z: " << z << endl;
}
E01. 数据类型
1. 数字类型
参考 C 语言笔记:CB1-1-新手村(二)S03E03.1 笔记。
2. 字符类型
参考 C 语言笔记:CB1-1-新手村(二)S03E03.2 笔记。
3. 布尔类型
心法:C++ 额外支持直接定义布尔类型变量。
C++ 中使用 bool 作为布尔类型关键字,占 1 个字节,1 表示 true,0 表示 false。
武技:测试 C++ 布尔类型
// basic/Bool.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_BOOL_H
#define V1_1_BASIC_ROOKIE_BOOL_H#include <iostream>namespace Bool {void test();
};#endif //V1_1_BASIC_ROOKIE_BOOL_H
// basic/Bool.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Bool.h"using std::cout, std::endl;void Bool::test() {bool flag = true;cout << "bool 类型的大小:" << sizeof flag << endl;cout << "bool 类型的大小:" << sizeof(bool) << endl;
}
4. 字符串类型
心法:std::string 是 C++ 标准库中 string 头文件里定义的,它封装了字符串的操作并自动管理内存,使用起来更加方便和安全,减少了出错的可能性。
字符串风格转换:
- C 字符串转 C++:
const *char c = "hi"; string cpp = string(c);
- C++ 字符串转 C:
string cpp = "hi"; const *char c = cpp.c_str();
头文件:
- std::string 来自 string.h 头文件。
- std::move() 来自 utility.h 头文件。
字符串拷贝赋值:
- 示例代码:
string a = "hi"; string b = a;
- 内存流程:调用字符串的拷贝构造器,首先为 b 分配一块新的空间,然后将 a 中的内容逐字节复制到这块新的空间中,此时 a 和 b 是两个独立无关的对象。
- 适用场景:适用于需要保留原对象内容的情况,但会有一定的性能开销,因为需要进行内存复制。
字符串转移赋值:
- 示例代码:
string a = "hi"; string b = std::move(a);
- 内存流程:调用字符串的移动构造器,直接让 b 接管 a 指向的数据,此时 a 将指向一个空字符串,后续将不应再使用它(除非重新赋值),此时 a 和 b 是两个独立无关的对象。
- 适用场景:适用于原对象不再需要使用的情况。
常用 API 方法:
常用 API 方法 | 描述 |
---|---|
str.length() | 返回字符串长度,可读性更高 |
str.size() | 返回字符串长度,通用性更高,推荐使用 |
str.at(2) | 返回字符串的 2 号元素 |
str.front() | 返回字符串起始字符 |
str.back() | 返回字符串结束字符 |
str.find(str2) | 返回 str 中第一个 str2 的位置 |
str.rfind(str2) | 返回 str 中最后一个 str2 的位置 |
str.append(str2) | 追加 str2 到 str 末尾,也可以使用 += 符号进行追加 |
str.insert(n, str2) | 在 str 的 n 号位置插入 str2,作用于原字符串 |
str.erase(n, m) | 在 str 的 n 号位置删除 n 个元素,作用于原字符串 |
str.replace(n, m, str2) | 在 str 的 n 号位置删除 m 个元素,然后插入 str2,作用于原字符串 |
str.substr(n, m) | 在 str 的 n 号位置截取 m 个元素,返回新字符串,不作用于原字符串 |
武技:测试 C++ 字符串类型
// basic/String.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_STRING_H
#define V1_1_BASIC_ROOKIE_STRING_H
#include <iostream>
#include <string>namespace String {void testString();void testAssign();void test();
};#endif //V1_1_BASIC_ROOKIE_STRING_H
// basic/String.cpp
// Created by 周航宇 on 2025/2/26.
//
#include <iostream>
#include "String.h"using std::cout, std::endl, std::string;void String::testString() {string str = "hellohello";cout << str << endl;cout << "返回当前字符串的长度:\t" << str.length() << endl;cout << "返回当前字符串的长度:\t" << str.size() << endl;cout << "返回字符串2号元素:\t" << str.at(2) << endl;cout << "返回字符串起始字符:\t" << str.front() << endl;cout << "返回字符串结束字符:\t" << str.back() << endl;cout << "返回第一个llo位置:\t" << str.find("llo") << endl;cout << "返回最后一个llo位置:\t" << str.rfind("llo") << endl;cout << "末尾追加World字符串:\t" << str.append("World") << endl;cout << "在5号位置插入逗号:\t" << str.insert(5, ",") << endl;cout << "删除第2个hello子串:\t" << str.erase(6, 5) << endl;cout << "替换字符串首个h为H:\t" << str.replace(0, 1, "H") << endl;cout << "截取Hello子字符串:\t" << str.substr(0, 5) << endl;cout << str << endl;
}void String::testAssign() {// 拷贝赋值string str1 = "hello";string str2 = str1;cout << "str1: " << str1 << endl;cout << "str2: " << str2 << endl;str2 = "xxxx"; // 重新赋值cout << "str1: " << str1 << endl; // 无影响cout << "str2: " << str2 << endl; // 被影响// 转移赋值string str3 = "hello";string str4 = std::move(str3);cout << "str3: " << str3 << endl; // 输出空字符串cout << "str4: " << str4 << endl;str3 = "xxxx"; // 重新赋值cout << "str3: " << str3 << endl; // 被影响cout << "str4: " << str4 << endl; // 无影响
}void String::test() {testString();testAssign();
}
5. 类型转换
心法:C++ 中推荐使用
static_cast<目标类型>(表达式)
方式进行基本数据类型之间的静态类型转换,对比 C 中的 (目标类型)表达式 方式更加安全,可读性更高。
其余参考 C 语言笔记:CB1-1-新手村(二)S03E03.3 笔记。
武技:测试 C++ 基本数据类型转换
// basic/StaticCast.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_STATIC_CAST_H
#define V1_1_BASIC_ROOKIE_STATIC_CAST_H
#include <iostream>namespace StaticCast {void test();
}#endif //V1_1_BASIC_ROOKIE_STATIC_CAST_H
// basic/StaticCast.c
// Created by 周航宇 on 2025/2/26.
//
#include "StaticCast.h"using std::cout, std::endl;void StaticCast::test() {int a = 10;// auto 用来自动推导变量类型,避免重复编写 double 关键字auto b = static_cast<double>(a);cout << b << endl;
}
6. 类型计算
参考 C 语言笔记:CB1-1-新手村(二)S03E03.4 笔记。
7. 结果溢出
参考 C 语言笔记:CB1-1-新手村(二)S03E03.5 笔记。
S04. 运算符号
E01. 基本运算符
1. 数学运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.1 笔记。
2. 赋值运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.2 笔记。
3. 自运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.3 笔记。
4. 关系运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.4 笔记。
5. 逻辑运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.5 笔记。
6. 位运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E01.6 笔记。
E02. 其他运算符
1. 三目运算符
参考 C 语言笔记:CB1-1-新手村(二)S04E02.1 笔记。
2. 数学工具类
参考 C 语言笔记:CB1-1-新手村(二)S04E02.2 笔记。
S05. 流程控制
无论是选择结构还是循环结构,当 {} 中仅一行代码时,可以省略 {},但是并不建议这么写。
E01. 选择结构
心法:选择结构也叫分支结构,表示多选一。
1. IfElse结构
心法:C++ 允许在 IF 语句的小括号中进行变量声明,声明的变量作用域仅限于所在的 IF 或 ELSE 语句块内。
其他参考 C 语言笔记:CB1-1-新手村(二)S05E01.1 笔记。
武技:测试 IfElse 结构
// structure/Choose.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_CHOOSE_H
#define V1_1_BASIC_ROOKIE_CHOOSE_H
#include <iostream>namespace Choose {void test();
}#endif //V1_1_BASIC_ROOKIE_CHOOSE_H
// structure/Choose.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Choose.h"using std::cout, std::cin, std::endl;void Choose::test() {// 输入一个整数int score;cout << "输入一个分数:";cin >> score;if (auto avgScore = 68.9; score > avgScore) {cout << "超过平均分 68.9" << endl;} else {cout << "低于平均分 68.9" << endl;}// 这里无法访问 num 变量// cout << num << endl;
}
2. Switch结构
参考 C 语言笔记:CB1-1-新手村(二)S04E01.2 笔记。
E02. 循环结构
心法:循环就是重复性做事,但必须是有逻辑有规律的事情才可使用循环。
无论那种循环,都要满足初判变法则:
- 初: 初始化变量(只执行一遍)。
- 判: 循环判断条件(只有满足条件的时候才执行循环)。
- 变: 每次循环体结束之后将执行变量的变化。
1. While循环
参考 C 语言笔记:CB1-1-新手村(二)S04E02.1 笔记。
2. DoWhile循环
参考 C 语言笔记:CB1-1-新手村(二)S04E02.2 笔记。
3. For循环
参考 C 语言笔记:CB1-1-新手村(二)S04E02.3 笔记。
E03. 流控关键字
1. continue
参考 C 语言笔记:CB1-1-新手村(二)S04E03.1 笔记。
2. break
参考 C 语言笔记:CB1-1-新手村(二)S04E03.2 笔记。
S06. 数组类型
心法:数组是 heap 内存中一段内存连续的线性数据结构,属于引用数据类型。
数组结构特点 | 描述 |
---|---|
拥有下标 | 数组的下标从0开始,支持使用 数组[索引] 的格式来操作数组元素 |
拥有默认值 | 数组中的每个最底层元素都具有默认值,如 0, 0.0, false, \u0000, null 等 |
增删慢 | 数组长度不可改变,导致增删操作必须使用创新数组,效率低 |
查询快 | 数组中每个元素的内存地址连续可计算,所以在明确位置的情况下,查询效率更高 |
E01. 一维数组
心法:一维数组中的每个元素都是一个单一数据类型,且全部元素的数据类型必须一致
1. 一维数组声明
参考 C 语言笔记:CB1-1-新手村(三)S06E01.1 笔记。
2. 一维数组遍历
心法:相比 C 语言,C++ 支持使用范围 for 循环(Range-based for loop),也称增强 for 循环来遍历 std::vector,std::array,std::list 以及普通的 C 风格数组。
范围 for 循环会自动调用容器的 begin() 函数获取指向第一个元素的迭代器,然后依次访问容器中的每个元素,直到到达 end() 迭代器所指向的位置(即容器的末尾),在每次迭代中,将当前元素的值赋给循环变量,然后执行循环体中的代码。
其余参考 C 语言笔记:CB1-1-新手村(三)S06E01.2 笔记。
武技:测试范围 For 循环
// array/OneArray.h
// Created by 周航宇 on 2025/2/26.
//
#include <iostream>
#ifndef V1_1_BASIC_ROOKIE_ONE_ARRAY_H
#define V1_1_BASIC_ROOKIE_ONE_ARRAY_Hnamespace OneArray {void testRangeFor();void test();
};#endif //V1_1_BASIC_ROOKIE_ONE_ARRAY_H
// array/OneArray.cpp
// Created by 周航宇 on 2025/2/26.
//
#include <array>
#include "OneArray.h"using std::cout, std::endl, std::array;void OneArray::testRangeFor() {// 定义一个数组int arr[] = {1, 2, 3, 4, 5};// 使用范围 for 循环遍历数组for (auto num: arr) {cout << num << endl;}
}void OneArray::test() {testRangeFor();
}
E02. 多维数组
心法:二维数组的本质也是一维数组,只不过其中的每个元素都是一个一维数组类型,每个一维数组的类型必须一致,但元素的长度可以不同。
1. 二维数组声明
参考 C 语言笔记:CB1-1-新手村(三)S06E02.1 笔记。
2. 二维数组遍历
参考 C 语言笔记:CB1-1-新手村(三)S06E02.1 笔记。
S07. 高级知识
E01. 函数
心法:函数是完成特定任务的独立代码单元,主要的意义是用于提高代码重用性。
1. 函数创建
心法:不同于 C 语言,C++ 支持函数重载,即创建同名不同参的函数。
其余参考 C 语言笔记:CB1-1-新手村(三)S07E01.1 笔记。
武技:测试函数重载
// senior/Overload.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_OVERLOAD_H
#define V1_1_BASIC_ROOKIE_OVERLOAD_H
#include <iostream>namespace Overload {int add(int a, int b);double add(double a, double b);void test();
};#endif //V1_1_BASIC_ROOKIE_OVERLOAD_H
// senior/Overload.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Overload.h"using std::cout, std::endl;int Overload::add(int a, int b) {return a + b;
}double Overload::add(double a, double b) {return a + b;
}void Overload::test() {int a = 1, b = 2;double c = 1.1, d = 2.2;cout << add(a, b) << endl;cout << add(c, d) << endl;
}
2. 函数传参
心法:不同于 C 语言,C++ 函数支持对参数设置默认值。
其余参考 C 语言笔记:CB1-1-新手村(三)S07E01.2 笔记。
武技:测试函数参数默认值
// senior/DefaultParam.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_DEFAULT_PARAM_H
#define V1_1_BASIC_ROOKIE_DEFAULT_PARAM_H
#include <iostream>namespace DefaultParam {int sum(int a, int b);void test();
};#endif //V1_1_BASIC_ROOKIE_DEFAULT_PARAM_H
// senior/DefaultParam.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "DefaultParam.h"using std::cout, std::endl;// 函数定义,为参数 b 指定默认值
int DefaultParam::sum(int a, int b = 0) {return a + b;
}void DefaultParam::test() {int a = 1, b = 2;cout << sum(a) << endl;cout << sum(a, b) << endl;
}
3. 函数递归
参考 C 语言笔记:CB1-1-新手村(三)S07E01.3 笔记。
5. Lambda函数
心法:C++ 中的 Lambda 表达式是 C++11 引入的对象,会被编译器转换为一个匿名的函数闭包对象,常用于在函数内部定义临时函数。
Lambda 表达式可以定义在全局作用域,但此时将不能捕获局部变量,所以一般情况下,Lambda 都推荐定义在函数内部。
Lambda 结构:auto Lambda对象 = [捕获列表](形参列表) -> 返回值类型 {函数体}
Lambda 对象:Lambda 表达式的返回值是一个 Lambda 对象,本质是 functional 头文件中提供的 function 类型,具体结构为 std::function<返回类型(参数类型列表)>
,但由于编写复杂且具有额外的性能开销(涉及堆内存分配和虚函数调用等),一般使用 auto 自动推导即可。
Lambda 捕获列表:用于捕获外部作用域中的局部变量(全局变量和静态变量不需要捕获,直接在函数体内使用即可)供函数体内部使用,为空时中括号不能省略,捕获方式有如下几种:
捕获方式 | 示例 | 描述 |
---|---|---|
值捕获 | [a, b] | 捕获指定外部变量的值并传递给函数体 函数体内使用的是变量的副本 |
引用捕获 | [&a, &b] | 捕获指定外部变量的引用并传递给函数体 函数体内使用的是变量的引用,可修改其值 |
全部值捕获 | [=] | 按值捕获的方式捕获全部外部变量的值并传递给函数体 |
全部引用捕获 | [&] | 按引用捕获的方式捕获全部外部变量的引用并传递给函数体 |
Lambda 形参列表:当每次调用 Lambda 表达式时需要传入不同的数据进行处理时,推荐使用参数列表,为空时小括号不能省略。
Lambda 返回值类型:若省略返回类型,编译器会根据函数体中的返回语句自动推导返回类型,且返回值类型前,用于表示传递的箭头也必须要一起省略。
武技:测试 Lambda 表达式
// senior/Lambda.h
// Created by 周航宇 on 2025/3/1.
//
#ifndef V1_1_BASIC_ROOKIE_LAMBDA_H
#define V1_1_BASIC_ROOKIE_LAMBDA_H
#include <iostream>
#include <functional>namespace Lambda {void test();
}#endif //V1_1_BASIC_ROOKIE_LAMBDA_H
// senior/Lambda.cpp
// Created by 周航宇 on 2025/3/1.
//
#include "Lambda.h"using std::cout, std::endl, std::flush, std::string;void Lambda::test() {// 最简版 Lambdaauto lambda$01 = []() { cout << "r01" << endl; };// 通过 lambda 对象调用该函数lambda$01();// 自调用:通过末尾的小括号实现自调用[]() { cout << "r02" << endl; }();// 获取返回值:调用 Lambda 之后可以获取其返回值double r03 = []() { return 3.14; }();cout << "r03: " << r03 << endl;// 显示指定返回值string r04 = []() -> string { return "hi"; }();cout << "r04: " << r04 << endl;// 值捕获:内部只能访问,不能修改// 引用捕获:内部可以修改int a = 10;int b = 10;[a, &b]() {b = 20;cout << "r05: a=" << a << flush;}();cout << ",b=" << b << endl;// 形参列表[](int m, int n) {cout << "r06: " << (m + n) << endl;}(3, 4);
}
E02. 指针
心法:变量在内存中创建,每个变量都拥有唯一内存地址,而指针正是用于保存这个内存地址的。
仅声明未赋值的指针变量会存储一个不确定的随机值,可能是上次使用这片内存空间存储的数据遗留下来的值,也可能是编译器初始化的一个随机值,这是一种很危险的操作,所以即使不知道具体的值,也强烈推荐赋值为 空指针,即暂不指向任何内容。
空指针:
- 在 C 中,通常使用 NULL 来表示空指针,它是一个宏,通常被定义为 (void *)0。
- 在 C++ 中,除了 NULL 外,还引入了 nullptr 关键字来专门表示空指针,类型安全,推荐使用。
无类型指针:void 指针是无类型指针,它可以指向任何类型的数据。
![[image/draw/C语言-指针示意图|100%]]
1. 指针创建
参考 C 语言笔记:CB1-1-新手村(三)S07E02.1 笔记。
2. 指针传递
心法:不同于 C 语言,C++ 引入了引用类型(引用是变量的别名),作为函数参数传递时更加简洁易读。
指针参数:如 swap(int *a, int *b){}
:
- C 语言没有引用类型,只能通过指针来实现类似的效果。
- 指针存储的是变量地址,在形参列表和方法体中都需要使用取地址 & 和解地址 * 符号,较麻烦。
- 指针可以不初始化或初始化为 NULL,操作空指针不会编译报错,所以容易产生系统异常。
- 指针可以在其生命周期内指向不同的对象,通过重新赋值指针变量来改变其指向。
引用参数:如 swap(int &a, int &b){}
:
- C++ 引入了引用类型,函数参数可以使用引用传递,这使得代码更加简洁易读。
- 对引用的操作实际上就是对所引用对象的操作,不需要额外的取地址或解地址操作,更简单。
- 引用必须在声明时初始化且不能为 NULL,操作空引用会编译报错,从而避免系统异常。
- 引用一旦绑定到一个对象,就不能再绑定到其他对象,即引用的目标对象是固定的。
指针和引用均可以作为函数的返回值,但切记不要返回方法体内局部变量的指针或引用,因为局部变量在函数结束后会被销毁,返回的引用或指针将成为悬空引用或悬空指针。
武技:测试指针参数和引用参数
// senior/RefParam.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_REF_PARAM_H
#define V1_1_BASIC_ROOKIE_REF_PARAM_H
#include <iostream>namespace RefParam {/** C 风格的交换,需要传递两个变量的取地址,然后在参数中解地址 */void cSwap(int *a, int *b);/** C++ 风格的交换,不需要传递地址,直接传递两个变量,在参数中使用引用 */void cppSwap(int &a, int &b);void test();
}#endif //V1_1_BASIC_ROOKIE_REF_PARAM_H
// senior/RefParam.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "RefParam.h"using std::cout, std::endl;void RefParam::cSwap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;
}void RefParam::cppSwap(int &a, int &b) {int temp = a;a = b;b = temp;
}void RefParam::test() {int a = 1, b = 2;cSwap(&a, &b);cout << "a = " << a << ", b = " << b << endl;int c = 3, d = 4;cppSwap(c, d);cout << "c = " << c << ", d = " << d << endl;
}
3. 常量指针
参考 C 语言笔记:CB1-1-新手村(三)S07E02.3 笔记。
4. 指针数组
参考 C 语言笔记:CB1-1-新手村(三)S07E02.4 笔记。
5. 指针函数
参考 C 语言笔记:CB1-1-新手村(三)S07E02.5 笔记。
E03. 结构体
1. 结构体
C 的结构体:
- 结构体只能包含数据成员,不能包含成员函数。
- 结构体成员默认都是公共的,没有访问控制的概念,所有代码都可以直接访问结构体的成员。
- 使用结构体时通常需要加上 struct 关键字,除非使用 typedef 对结构体起了别名。
C++ 的结构体:
- 结构体可以包含成员函数:
- 函数体可以被 const 修饰,此时函数体中若尝试修改调用者某个非静态成员,编译器会发出警告。
- 返回值可以被 [[nodiscard]] 修饰,此时若返回值被忽略,编译器会发出警告。
- 结构体可以用 public,private 和 protected 控制成员的访问权限,默认 public 修饰:
- 一旦结构体的成员被非 public 修饰,则无法使用列表的方式进行初始化。
- 使用结构体类型时可以省略 struct 关键字,直接使用结构体名来定义变量。
其余参考 C 语言笔记:CB1-1-新手村(三)S07E03.1 笔记。
武技:测试 C++ 结构体
// senior/Structure.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_STRUCTURE_H
#define V1_1_BASIC_ROOKIE_STRUCTURE_H
#include <iostream>
#include <string>namespace Structure {struct User {private:// 主键long long id;public:// 年龄int age;// 体重double weight;// 姓名(字符串)std::string name;// 上级领导(指针)User *manager;void setId(long long newId);// [[nodiscard]] 表示该函数的返回值若被忽略,编译器会发出警告[[nodiscard]] long long getId() const;};void test();
}#endif //V1_1_BASIC_ROOKIE_STRUCTURE_H
// senior/Structure.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Structure.h"using std::cout, std::endl;void Structure::User::setId(long long int newId) {this->id = newId;
}long long Structure::User::getId() const {return this->id;
}void Structure::test() {// 创建 User 结构体User lucky;lucky.setId(1L);lucky.name = "lucky";lucky.age = 18;lucky.weight = 50.5;lucky.manager = nullptr;User joeZhou;joeZhou.setId(2L);joeZhou.name = "JoeZhou";joeZhou.age = 25;joeZhou.weight = 70.5;joeZhou.manager = &lucky;// 输出 User 结构体信息cout << "Lucky: " << endl;cout << "\tid: " << lucky.getId() << endl;cout << "\tname: " << lucky.name << endl;cout << "\tage: " << lucky.age << endl;cout << "\tweight: " << lucky.weight << endl;cout << "\tmanager: " << (lucky.manager ? lucky.manager->name : "无") << endl;cout << "JoeZhou: " << endl;cout << "\tid: " << joeZhou.getId() << endl;cout << "\tname: " << joeZhou.name << endl;cout << "\tage: " << joeZhou.age << endl;cout << "\tweight: " << joeZhou.weight << endl;cout << "\tmanager: " << (joeZhou.manager ? joeZhou.manager->name : "无") << endl;
}
2. 枚举类
心法:在 C 语言中没有枚举类(enum class)的概念,只有普通枚举(enum),而 C++ 不仅保留了普通枚举,还引入了枚举类(C++11 及以后)。
普通枚举:
- 枚举常量作用域为全局,同一作用域内不能有同名枚举常量,即使来自不同枚举类型。
- 需要使用
enum Color c = RED;
方式获取枚举常量。
枚举类:
- 枚举常量仅在所在类内有效,不同枚举类可有相同名称的常量,不会冲突。
- 需要使用
Color c = Color::RED;
方式获取枚举常量。
其余参考 C 语言笔记:CB1-1-新手村(三)S07E03.2 笔记。
武技:测试 C++ 枚举类
// senior/EnumClass.c
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_ENUM_CLASS_H
#define V1_1_BASIC_ROOKIE_ENUM_CLASS_H
#include <iostream>namespace EnumClass {enum class Color {RED = 1,GREEN = 2,BLUE = 3};void test();
}#endif //V1_1_BASIC_ROOKIE_ENUM_CLASS_H
// senior/EnumClass.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "EnumClass.h"using std::cout, std::endl, std::cin;void EnumClass::test() {// 接收一个 int 类型的变量cout << "请输入一个颜色代码 (1:RED, 2:BLUE, 3:GREEN):" << endl;int colorInt;cin >> colorInt;// 将 int 类型的变量转换为 Color 类型的变量auto color = static_cast<Color>(colorInt);switch (color) {case Color::RED:cout << "Color is RED" << endl;break;case Color::BLUE:cout << "Color is BLUE" << endl;break;case Color::GREEN:cout << "Color is GREEN" << endl;break;default:cout << "Color is UNKNOWN" << endl;}
}
E04. 类型别名
1. typedef
参考 C 语言笔记:CB1-1-新手村(三)S07E04.1 笔记。
2. using
心法:在定义普通类型别名时,C++11 引入的 using 别名声明可以替代 typedef,更加直观和灵活。
typedef int Integer:不能定义模板别名。
using Integer = int:可以定义模板别名,但注意 using namespace std 的写法并不是起别名,而是一种引入命名空间中所有名称到当前作用域的方式。
其余参考 C 语言笔记:CB1-1-新手村(三)S07E04 笔记。
武技:测试 using 别名
// senior/Using.h
// Created by 周航宇 on 2025/2/26.
//
#ifndef V1_1_BASIC_ROOKIE_USING_H
#define V1_1_BASIC_ROOKIE_USING_H
#include <iostream>
#include <string>namespace Using {// 定义一个模板结构体template<typename T>struct Point {T x;T y;};// 使用 using 为模板结构体 Point 起别名template<typename T>using P = Point<T>;void test();
}#endif //V1_1_BASIC_ROOKIE_USING_H
// senior/Using.cpp
// Created by 周航宇 on 2025/2/26.
//
#include "Using.h"using std::cout, std::endl;void Using::test() {P<int> p;p.x = 10;p.y = 20;cout << "x = " << p.x << ", y = " << p.y << endl;
}
E05. 内存操作
1. malloc
参考 C 语言笔记:CB1-1-新手村(三)S07E05.1 笔记。
2. calloc
参考 C 语言笔记:CB1-1-新手村(三)S07E05.2 笔记。
3. realloc
参考 C 语言笔记:CB1-1-新手村(三)S07E05.3 笔记。
4. new
心法:C++ 除了可以使用 C 语言的内存分配函数(malloc、calloc、realloc 和 free 等)外,还引入了 new 和 delete 运算符,用于对象的动态内存分配和释放。
C 语言内存分配(malloc / calloc / realloc):
- 分配内存时,不会自动调用对象的构造函数,释放内存时,也不会调用对象的析构函数。
- 分配内存后,返回的是 void* 类型的指针,需要手动进行类型转换,这可能会导致类型不匹配的问题。
- 内存分配失败时会返回 NULL,需要手动检查返回值来处理内存分配失败的情况。
C++ 语言内存分配(new):
- 分配内存后,会自动调用对象的构造函数进行初始化,释放内存前,会自动调用对象的析构函数进行资源清理。
- 分配内存后,自动返回正确类型的指针,不需要手动进行类型转换,提高了类型安全性。
- 内存分配失败时会抛出 std::bad_alloc 异常,可用 try-catch 捕获并处理异常,可用
e.what()
获取异常信息。
相关 API 方法 | 描述 |
---|---|
new | 动态分配内存并创建对象,返回一个指向该对象的指针 |
delete | 释放 new 分配的内存 |
武技:测试 malloc 申请内存以及 free 释放内存
// senior/Memory.h
// Created by 周航宇 on 2025/2/27.
//
#ifndef V1_1_BASIC_ROOKIE_MEMORY_H
#define V1_1_BASIC_ROOKIE_MEMORY_H
#include <iostream>namespace Memory {void test();
}#endif // V1_1_BASIC_ROOKIE_MEMORY_H
// senior/Memory.cpp
// Created by 周航宇 on 2025/2/27.
//
#include "Memory.h"void Memory::test() {try {// 使用 new 分配内存int *arr = new int[5];// 初始化数组元素for (int i = 0; i < 5; i++) {arr[i] = i;}// 打印数组元素for (int i = 0; i < 5; i++) {std::cout << arr[i] << " ";}std::cout << std::endl;// 释放内存delete[] arr;} catch (const std::bad_alloc &e) {std::cout << "内存分配失败" << e.what() << std::endl;}
}
E06. 文件操作
1. fopen
参考 C 语言笔记:CB1-1-新手村(三)S07E06.1 笔记。
2. fstream
心法:C++ 引入了流的概念,使用标准库中的输入输出流类 iostream 及其派生类来进行文件读写操作,比如ifstream 输入文件流,ofstream 输出文件流和 fstream 读写文件流。
文件操作:
相关 API 方法(路径支持相对和绝对两种) | 描述 |
---|---|
ifstream file("文件路径", "读写模式") | 创建文件输入流,默认 ios::in 模式,可明确 |
ofstream file("文件路径", "读写模式") | 创建文件输出流,默认 ios::out 模式,可明确 |
fstream file("文件路径", "读写模式") | 创建文件读写流,默认 ios::in | ios::out 模式,更灵活 |
file.is_open() | 返回文件流对象是否成功打开了指定的文件 |
file.get(char c) | 读取 1 个字符存入 char,可包括空格,制表符和换行符等 |
getline(file, string str) | 读取 1 行字符存入 string,直到遇到换行符或指定的行分隔符 |
file.read(char[] arr, int n) | 读取 n 个字符存入 char 数组,通常用于处理二进制文件 |
file.eof() | 返回输入流是否已经到达文件末尾 |
file.put(char c) | 写入 1 个字符到输出流 |
file.write(char[] arr, int n) | 写入 n 个字符到输出流,通常用于处理二进制文件 |
file.flush() | 在不关闭文件流的情况下,刷新缓冲区,确保数据立即写入文件 |
file.close() | 刷新缓冲区,释放文件资源,解除文件关联并重置文件流状态 |
读写模式:参数可以是以下几种模式的组合(使用按位或 | 运算符):
读写模式 | 描述 | 若文件不存在 | 若文件已存在 |
---|---|---|---|
ios::in | 以 输入(读) 模式打开文件 | 返回 NULL | 正常读 |
ios::out | 以 输出(写) 模式打开文件 | 自动创建文件 | 先清空文件,然后再写入 |
ios::app | 以 追加(写) 模式打开文件 | 自动创建文件 | 从文件末尾开始追加写入 |
ios::binary | 以 二进制 模式打开文件 |
武技:测试 fstream 函数
// senior/File.h
// Created by 周航宇 on 2025/2/27.
//
#ifndef V1_1_BASIC_ROOKIE_FILE_H
#define V1_1_BASIC_ROOKIE_FILE_H
#include <iostream>
#include <fstream>namespace File {void testWriteFile();void testReadFile();void test();
}#endif //V1_1_BASIC_ROOKIE_FILE_H
// senior/File.cpp
// Created by 周航宇 on 2025/2/27.
//
#include "File.h"using std::cout, std::endl, std::cerr, std::string;
using std::ios, std::ifstream, std::ofstream;void File::testWriteFile() {// 打开文件用于写入ofstream file("../04_senior/File.txt", ios::app);if (!file.is_open()) {cerr << "打开文件失败" << endl;return;}// 写入文件内容file << "Hello, World!" << endl;file << "Hello, World!" << endl;// 关闭文件file.close();
}void File::testReadFile() {// 打开文件用于读取ifstream file("../04_senior/File.txt");if (!file.is_open()) {cerr << "打开文件失败" << endl;return;}// 读取文件内容string line;while (getline(file, line)) {cout << line << endl;}if (file.eof()) {cout << "文件读取完毕" << endl;}// 关闭文件file.close();
}void File::test() {testWriteFile();testReadFile();
}
C/C++道经第1卷 - 第2阶 - 基础启航