C++的基础知识
Easul Lv6

由于需要紧急学习一下C++的一些知识,目前就根据以前其他语言开发的一些思想和流程来简单记录一下C++的处理方式了。

初始开发

开发环境目前我使用的是 Deepin23.1,CPU架构为 amd64,安装了一些相关开发依赖包就可以跑 Hello C++ 了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 安装g++,然后跑一下Hello C++
sudo apt update
sudo apt install -y g++
# 查看g++版本
g++ --version
# 编写一个简单的C++程序
cd ~/Desktop
tee hello.cpp <<EOF
#include <iostream>
using namespace std;

int main() {
cout << "Hello, C++!" << endl;
return 0;
}
EOF
# 编译
# -std=c++17 表示使用 C++17 标准
# -o 表示编译后的二进制文件名
g++ -std=c++17 hello.cpp -o hello
# 运行后会输出
# Hello, C++!
./hello

进行交叉编译

目前是需要让程序在 armv7 的物联网盒子上进行运行的,所以这里需要基于已有代码进行交叉编译。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 1. 安装基本的交叉编译工具(可选)
# 这一步只在对比 glibc 版本编译效果时使用
# 如果只打算用 musl 工具链,这一步可以省略
# 使用 musl 工具链可以不依赖系统的 glibc ,二进制文件更通用
sudo apt install g++-arm-linux-gnueabihf -y
# 2. 安装通用的 musl 工具(可选)
# 这主要是提供 musl-gcc 等命令,但不包含 arm 版本交叉链
sudo apt install musl-tools -y
# 3. 下载并解压 arm musl 交叉编译器
mkdir -p ~/software/musl
cd ~/software/musl
wget https://musl.cc/arm-linux-musleabihf-cross.tgz
tar -zxvf arm-linux-musleabihf-cross.tgz
rm -f arm-linux-musleabihf-cross.tgz
# 4. 验证交叉编译器是否可用
~/software/musl/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-g++ --version
# 5. 编译 armv7 可执行文件(静态链接)
# musl 版天然静态编译,运行时不依赖外部 so 库。
# 比 glibc 静态编译的体积略大,但更通用。
# 而且使用 musl 版可以有效避免DNS的解析问题
# 编译后的文件可以运行在 armv7(物联网盒子) 和 amd64(手机的termux) 上
cd -
~/software/musl/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-g++ -static hello.cpp -o hello_arm

常用代码操作

这里包括了

  • 简单IO操作
  • 函数定义与实现
  • 类的操作
  • 列表,集合,map等容器的增删改查与循环的操作
  • 多线程与异常相关的处理
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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// include 引入的头文件,<> 是系统头文件,"" 是自定义头文件
// 使用该指令后,引入的文件代码会在预处理阶段直接复制粘贴过来
// 而不是运行时动态加载并执行内存某个地址的函数
#include <iostream>
#include <vector>
#include <map>
#include <set>
#include <stdexcept>
#include <mutex>
#include <thread>
// 这里使用命名空间引入了std,这样的话以后使用std命名空间下的 cout endl 时,可以省略 std::
// 实际开发时不建议在头文件中写这个,容易产生命名冲突。
// 不过由于在很多地方都要大量使用标准库下的内容,所以还是导入该命名空间更方便
// using namespace std;

// 1. 函数的操作
// 进行 basicIoDemo函数 的声明,需要在调用前声明,否则会编译报错
int basicIoDemo();
// 对 basicIoDemo函数 的逻辑实现
// 函数常见使用可点击 [该链接](https://chatgpt.com/s/t_69003811ccf081918d5978a4fabb6dcd) 来参考
int basicIoDemo() {
std::string name;
int age;
std::cout << "Please input your name and age spilt by a blank." << std::endl;
// std::cin 表示标准输入流。输入的时候使用空格,tab,回车,可以将输入内容赋值给多个变量
// 变量类型这里可以自行匹配,如果有误 cin.fail() 会为 true
std::cin >> name >> age;
if (std::cin.fail()) {
std::cout << "Please correct your input!" << std::endl;
return -1;
}
// std::cout 表示标准输出流,类型是 std::ostream
// std::endl 用于换行+刷新缓冲区。如果只需要换行,在输出流中加上 \n 即可
// << 用于将内容插入到输出流,可以链式使用并连续输出多个值
// 作为 运算符重载 的时候,可以将自定义类型导向输出流
std::cout << "Dear " << name << ", you just tell me you are " << age << "." << std::endl;
return 0;
}

// 2. 类的操作
class Person {
// 私有属性或方法放到这里
private:
std::string name;
int age;
std::string printCurrentTimestamp() {
time_t now = time(nullptr);
char* dt = ctime(&now);
std::string timeStr(dt);
if (!timeStr.empty()) {
timeStr.erase(timeStr.size()-1);
}
return "[" + timeStr + "]";
}
// 公有属性或方法放到这里
public:
// 构造函数
Person(std::string n, int a) {
name = n;
age = a;
}
// 析构函数,对象销毁时自动执行。销毁时间为对象离开作用域后。
~Person() {
std::cout << "I will destroy the resources after the object is destroyed" << std::endl;
}
// 公有方法,这个属于对象方法
void sayHello() {
std::cout << printCurrentTimestamp() << " Hello, I'm " << name << ", " << age << " years old." << std::endl;
}
// 加了static的方法相当于是类方法,可以直接用 Person::printInfo(); 来调用
static void printInfo() {
std::cout << "Person is a word in English." << std::endl;
}
// Getter
std::string getName() {
return name;
}
// Setter
void setAge(int a) {
age = a;
}
}; // 类要有分号
void personDemo() {
// 这里对象创建是一种简单的写法,赋值的同时也进行了创建。
Person p("Easul", 12);
p.sayHello();
p.setAge(20);
p.sayHello();
}

// 3. 循环和容器
void containerDemo() {
// vector相当于是动态数组(数据连续存储)。如果需要用到链表,可以使用std::list
std::vector<int> vec = {2, 2, 3, 4, 5};
// 向后添加元素
vec.push_back(12);
// 传统for循环
for (int i = 0; i < vec.size(); i++) {
std::cout << vec[i] <<std::endl;
}
std::cout << "==================" <<std::endl;
// 从最后删除元素
vec.pop_back();
// 范围for循环
for (int n: vec) {
std::cout << n <<std::endl;
}

// map的处理
std::map<std::string, int> ages;
ages["Easul"] = 21;
ages["Alice"] = 18;
ages["John"] = 16;
// auto 可以根据变量的初始表达式自动推导变量的类型
// & 可以获取map中单个元素的地址,从而支持直接读取和修改源map中的内容
// 不加 & 则在每次获取元素的时候都在内存重新拷贝一遍(值拷贝),会消耗更多的资源
for (auto &pair : ages) {
std::cout << pair.first << "=>" << pair.second << std::endl;
}
// 查找元素
std::string name = "Stevie";
// 能找到则返回迭代器,找不到则返回end(),即哨兵。
if (ages.find(name) == ages.end()) {
std::cout << "未找到" << name << std::endl;
}
// 删除元素
ages.erase("Alice");

// set集合
std::set<std::string> fruits = {"apple", "grapes", "banana"};
// 重复插入不会生效
fruits.insert("banana");
for (auto &fruit : fruits) {
std::cout << fruit << std::endl;
}
fruits.erase("apple");
for (auto &fruit : fruits) {
std::cout << fruit << std::endl;
}
// 查找
if (fruits.count("banana") > 0) {
std::cout << "You can get banana here." << std::endl;
}
}

// 4. 异常与并发。在使用多线程时,编译需要加上 -pthread 参数
// 全局互斥锁,用于共享变量的处理
std::mutex countMutex;
void task(int id, int limit) {
try {
for (int i = 0; i < limit; i++) {
// 手动设置在某个情况下抛出异常
if (i == 3 && id == 2) {
throw std::runtime_error("Task 2 encountered an error when id equals 2");
}
{
// 使用共享变量的时候,需要加锁,锁会在作用域结束后自动释放
std::lock_guard<std::mutex> lock(countMutex);
std::cout << "Task " << id << ": i = " << i << std::endl;
}
// 执行完任务后,休眠一段时间
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
// 当某个地址的值只需要被读取,不需要修改,那么就可以定义为静态地址变量
} catch (const std::exception &e) {
// 当需要使用共享变量的时候,加一个锁,锁会在作用域结束后自动释放
std::lock_guard<std::mutex> lock(countMutex);
// 标准错误输出使用 str::cerr
std::cerr << "Exception in task " << id << ": " << e.what() << std::endl;
}
}
void runTaskDemo() {
// 创建一个线程数组
std::vector<std::thread> threads;
for (int i = 0; i < 3; i++) {
// 直接在容器尾部构造一个新的线程对象,不需要临时变量。创建后立即执行。
// 执行后这行代码结束,然后继续执行下一行代码,所以基本上相当于所有线程在并发运行
// 作用等同于 threads.push_back(std::thread(task, i, 10));
threads.emplace_back(task, i, 10);
}
for (auto &t : threads) {
// 在主线程等待所有线程执行完毕(会阻塞主线程不向下走)。
// 如果其他线程还没有执行完毕,主线程就退出,那么主线程就相当于是异常退出了
// 而如果希望其他线程在后台运行,不影响主线程运行,那么可以使用 t.detach()。
// 如果线程3先执行完毕,但这里是从线程1开始等待,那么也还是得等线程1执行完毕,再继续for循环
t.join();
}
}

// main函数返回0,是因为linux中都是用0,1,2等返回值来判断程序是否运行异常的,返回0表示运行正常。
int main() {
// 1. 函数调用
// basicIoDemo();
// 2. 对象使用
// personDemo();
// 3. 循环和容器
// containerDemo();
// 4. 异常与线程
// runTaskDemo();
return 0;
}
 评论