Scroll progress animations in CSS

  • Scroll progress animations in CSS

    • 把 css animation 绑定到 scroll progress timeline
    1
    2
    3
    .progress {
    animation-timeline: scroll();
    }
    • scroll() 函数指定滚动容器和轴。默认值为 scroll(nearest block) ,也可以绑定到根容器 scroll(root block)
    1
    2
    3
    4
    5
    6
    7
    8
    .progress {
    animation-timeline: scroll(root);
    // 结合关键帧动画
    // 可简写为:animation: scaleProgress auto linear;
    animation-name: scaleProgress;
    animation-duration: auto;
    animation-timing-function: linear;
    }
  • Using HTML landmark roles to improve accessibility

    • aria 规定了 8 个标志角色

    • banner

    • navigation

    • search

    • main

    • region

    • complementary

    • form

    • contentinfo

    • 使用 role 属性标记

      • <div class="banner" role="banner"></div>
        
    • 部分 html 元素自带角色标记

Termux - 使用旧手机搭建服务

Termux - 使用旧手机搭建服务

Termux是一款 Android 终端模拟器和 Linux 环境应用程序,无需 root 或设置即可直接运行。自动安装最小的基本系统 - 使用 APT 包管理器可以使用其他包

安装 termux

f-droid 下载 termux apk 文件安装

环境准备

  • pkg up 更新本地包

  • pkg 是 termux 对 apt 封装

  • termux-setup-storage 设置手机存储权限

配置 ssh

  • pkg install openssh
  • passwd 配置密码
  • ifconfig 查看本地 ip
  • termux 默认使用 8022 端口访问 ssh
  • sshd 开启服务,pkill port 关闭服务,nmap localhost -p port 查看服务状态
  • 外部访问 ssh -p 8022 server_ip, 默认使用当前用户登录
  • 保持手机息屏 ssh 不断开,在手机下拉框里点击 ACQUIRE WAKELOCK,看到wake lock held即可

安装 node.js

pkg install nodejs

装好 node 后就可以开始愉快的进行服务部署了(#^.^#)

C++ 线程

线程

基本用法

构造线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <thread>  //线程类型在此头文件中定义
int main() {
//定义线程函数,此函数接收两个参数
auto t_fun(int t_id, int num) {
for (size_t i = 0; i < num; i++) {
cout<< to_string(t_id) + ":" + to_string(i) << endl;
}
}
//开一个线程执行线程函数,给线程函数传入两个参数:0,6
thread t(func, 0, 6);
//开另一个线程执行线程函数,参数为1,6
thread t2(func, 1, 6);
//join方法可以等待线程结束,
//如果线程不结束就开启另一个线程,那么两个线程是并行执行的
//此处两个线程就是并行执行的
t.join();
t2.join();
auto c = getchar();
}

C++ 容器

容器

顺序容器

  • std::vector (可变数组)

  • 声明 vector<int> myVector {1,2,3};

  • 访问元素 myVector[2], 不检查是否越界

  • 修改指定位置 myVector[2] = 33;

  • 如果把一个对象存入容器中,那么容器将存储该对象的副本

  • vector 的析构函数执行时,会调用容器中每个对象的析构函数

  • push_back:

    1
    2
    3
    std::vector<int> numbers;
    numbers.push_back(1);
    numbers.push_back(2);
  • pop_back:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    numbers.pop_back();
  • size:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    std::cout << "Size: " << numbers.size() << std::endl;
  • at:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    int value = numbers.at(1); // 获取索引为1的元素值
  • front:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    int firstValue = numbers.front(); // 获取第一个元素值
  • back:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    int lastValue = numbers.back(); // 获取最后一个元素值
  • clear:

    1
    2
    std::vector<int> numbers = {1, 2, 3};
    numbers.clear(); // 清空所有元素
  • erase:

    1
    2
    std::vector<int> numbers = {1, 2, 3, 4, 5};
    numbers.erase(numbers.begin() + 2); // 删除索引为2的元素
  • insert: insert 插入比如使用 myVector.begin() 返回 iterator 明确插入元素位置,不能直接使用数字

    1
    2
    3

    std::vector<int> numbers = {1, 2, 3, 4, 5};
    numbers.insert(numbers.begin() + 2, 99); // 在索引为2的位置插入值为99的元素
  • empty:

1
2
3
4
5

std::vector<int> numbers;
if (numbers.empty()) {
std::cout << "Vector is empty." << std::endl;
}
  • reserve:
1
2
std::vector<int> numbers;
numbers.reserve(10); // 预留空间,但不改变 size
  • resize:
1
2
std::vector<int> numbers = {1, 2, 3};
numbers.resize(5); // 将容器的大小调整为5,多余的元素填充默认值
  • 遍历容器
1
2
3
for (auto& item:myVector) {
cout << item << endl;
}

其他顺序容器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <list>
#include <vector>
#include <deque>

// forward_list 单向链表
list<int> myList; // 双向链表容器
myList.push_back(1);
myList.push_back(2);
myList.push_back(3);
//没办法执行myList.begin()+2这样的操作
myList.insert(++myList.begin(), 7);

deque<int> myDeque; // 双端队列
myDeque.push_back(1);
myDeque.push_front(1);
myDeque.push_back(2);
myDeque.push_back(3);
myDeque.insert(myDeque.begin()+2, 2);

容器适配器

队列和栈(对 deque 的包装)

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
#include <iostream>
#include <queue>
#include <stack>
using namespace std;

int main() {
//队列容器(先进先出)
queue<int> myQueue;
myQueue.push(1);
myQueue.push(2);
myQueue.push(3);
do {
cout << myQueue.front() << endl; //输出 1 2 3
myQueue.pop();
} while (myQueue.size() > 0);

//堆栈容器(后进先出)
stack<int> myStack;
myStack.push(1);
myStack.push(2);
myStack.push(3);
do {
cout << myStack.top() << endl; //输出 3 2 1
myStack.pop();
} while (myStack.size() > 0);

auto c = getchar();
}

关联容器

  • map 容器用于保存一系列的键值对数据,与 JavaScript 中的 Map 功用相似

  • 包含头文件:

    1
    #include <map>
  • 定义 std::map 对象:

    1
    std::map<KeyType, ValueType> myMap;

    其中,KeyType 是键的数据类型,ValueType 是值的数据类型。

  • 插入键值对:

    1
    myMap[key] = value;  // 或者 myMap.insert(std::make_pair(key, value));
  • 访问元素:

    1
    ValueType value = myMap[key];
  • 遍历 std::map

    1
    2
    3
    4
    5
    for (const auto& pair : myMap) {
    KeyType key = pair.first;
    ValueType value = pair.second;
    // 处理键值对
    }
  • 查找元素:

    1
    2
    3
    4
    5
    6
    7
    auto it = myMap.find(key);
    if (it != myMap.end()) {
    // 找到了
    ValueType value = it->second;
    } else {
    // 未找到
    }
  • 删除元素:

    1
    myMap.erase(key);
  • 判断是否为空:

    1
    2
    3
    if (myMap.empty()) {
    // map 为空
    }
  • 合并 map

    1
    2
    3
    map<string, string> myMap1{ {"key1","val1"},{"key2","val2"} };
    map<string, string> myMap2{ {"key3","val3"},{"key4","val4"} };
    myMap1.merge(myMap2);
  • 获取元素个数:

    1
    size_t size = myMap.size();
  • 清空 std::map

    1
    myMap.clear();
  • 自定义比较函数: 如果键类型不支持比较操作,或者需要自定义排序方式,可以提供一个比较函数或者使用函数对象。

    1
    std::map<KeyType, ValueType, CompareFunction> myMap;

其中,CompareFunction 是用于比较键的函数或者函数对象。

  • 遍历 map

    1
    2
    3
    4
    5
    6
    //遍历map容器,
    //keyval是pair<string,string>类型的对象,
    //pair模板可以将两个不同类型的值组合起来
    for (const auto& keyval: myMap) {
    cout << keyval.first << ":" << keyval.second << endl;
    }
  • sset 容器用于存储一系列不重复的元素(不是键值对),与 JavaScript 中的 Set 功用相似

  • set 接口几乎与 map 相同,主要区别是 set 没有下标操作符(operator[])、insert_or_assign 和 try_emplace 等接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    int main() {
    set<int> s {1,2,3};
    //不会被插入,也不会报错,因为set容器中已经包含了元素3
    s.insert(3);
    cout << s.size() << endl; //输出3
    // 遍历
    for (auto& item:s) {
    cout << item << endl; //输出 1,2,3
    }
    //移除元素3,不是移除第三个位置的元素
    s.erase(3);
    //获取最后一个元素
    cout << *(--s.end()) << endl; //输出2
    s.clear();
    return 0;
    }
  • multiset 和 multimap 允许存储多个相同的元素

无序关联容器

  • unordered_mapunordered_multimapunordered_setunordered_multiset
  • ,无序关联容器内存储的元素是无序的,元素在容器中的位置由键的哈希值确定
  • 无序关联容器获取指定的元素会比普通关联容器要快,但要遍历迭代整个无序关联容器效率则没有普通关联容器好。

元组

用于存储多个元素,每个元素的类型可以不同。元组提供了一个有序的集合,可以方便地将多个值组合在一起。

以下是一些关于元组的基本特点和用法:

  • 包含头文件:

    1
    #include <tuple>
  • 定义 std::tuple 对象:

    1
    std::tuple<T1, T2, ..., Tn> myTuple;  // 其中 T1, T2, ..., Tn 是元素的类型
  • 初始化元组:

    1
    auto myTuple = std::make_tuple(value1, value2, ..., valueN);
  • 访问元组元素:

    1
    std::get<Index>(myTuple);  // Index 是元素的索引,从 0 开始

    或者使用结构化绑定(C++17 及以上):

    1
    auto [var1, var2, ..., varN] = myTuple;
  • 修改元组元素:

    1
    std::get<Index>(myTuple) = newValue;
  • 获取元组大小:

    1
    constexpr std::size_t tupleSize = std::tuple_size<decltype(myTuple)>::value;
  • 比较元组:

    1
    bool isEqual = (tuple1 == tuple2);
  • 遍历元组:

    1
    std::apply([](auto&&... args) { /* 处理元组中的元素 */ }, myTuple);

元组在需要将多个相关的值一起传递或组织时非常有用,尤其是当值的类型不同或者数量不确定时。元组提供了一种更灵活的数据结构,可以在编译时或运行时动态地组织和访问元素。

C++ 标准库

标准库

字符串

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
#include <iostream>
#include <string>
#include <format>
using namespace std;

//移除字符串左侧空白符
static inline void ltrim(std::string& s) {
//从字符串左侧开始查找,找到第一个不是空白符的字符的位置
auto endPosition = std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
});
s.erase(s.begin(), endPosition);//移除字符串中指定位置区间的字符
}

//移除字符串右侧空白符
static inline void rtrim(std::string& s) {
//从字符串右侧开始查找,找到第一个不是空白符的字符的位置
//由于入参有两个reverse_iterator,所以最后要调用base
auto startPosition = std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base();
s.erase(startPosition, s.end());//移除字符串中指定位置区间的字符
}

//移除字符串左侧和右侧的空白符
static inline void trim(std::string& s) {
rtrim(s);
ltrim(s);
}

int main() {
auto str1{ " liulun "s }; //加了s尾缀,str1就是std:string类型,不然就是const char*
trim(str1); //c++标准库中没有trim,使用的是自己实现的。
cout << str1 << endl; //输出:liulun

int num1 = stoi("1237"s); //字符串转整型
double num2 = stod("123.45"s); //字符串转浮点型
cout << num1 << " " << num2 << endl; //输出:1237 123.45

auto str2 = to_string(num1); //数字转字符串
auto str3 = to_string(num2); //数字转字符串
cout << str2 << " " << str3 << endl; //输出:1237 123.450000

auto str4{ "他1926年8月17日出生,2022年11月30日逝世,是江苏省扬州市人。"s };
//获取子字符串,第一个参数是位置,第二个参数是数量,
cout << str4.substr(21, 19) << endl; //输出:2022年11月30日逝世
//查找子字符串的位置
cout << str4.find("2022") << endl; //输出:21
//格式化字符串
cout << format("他{0}年8月17日出生,{1}年11月30日逝世,他{0}年8月17日出生,{1}年11月30日逝世。", "1926", "2022");
//输出:他1926年8月17日出生,2022年11月30日逝世,他1926年8月17日出生,2022年11月30日逝世。
auto c = getchar();
}

正则表达式

正则入门

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
#include <iostream>
#include <regex>
using namespace std;

//匹配手机号
void isPhoneNumber() {
//定义正则表达式,regex_constants::icase不区分大小写
regex reg{ R"(^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$)" ,regex_constants::icase };
//match是全文匹配,要求整个字符串都符合规则
bool flag = regex_match("18158135758", reg);
//输出:Is phone number:true
cout << "Is phone number:" << (flag?"true":"false") << endl;
}
//使用正则表达式替换命中的匹配项
void replaceByRegex() {
string str = "等忙完这一阵子,就可以接着忙下一阵子了。";
//一个中文字符相当于两个英文字符
regex reg{ R"(一.{2}子)" };
//输出:等忙完这一会儿,就可以接着忙下一会儿了。
cout << regex_replace(str, reg, "一会儿") << endl;
}
//使用正则表达式搜索字符串
void regexSearch() {
string str = "他1926年8月17日出生,2022年11月30日逝世,是江苏省扬州市人。";
smatch result;
regex reg{ R"(\d{4})" };
string::const_iterator iterStart = str.begin(); //这里不能用auto
string::const_iterator iterEnd = str.end(); //这里不能用auto
string temp;
//输出:1926 2022
while (regex_search(iterStart, iterEnd, result, reg))
{
cout << result[0] << " ";
//更新搜索起始位置,搜索剩下的字符
iterStart = result[0].second;
}
}

int main() {
isPhoneNumber();
replaceByRegex();
regexSearch();
auto c = getchar();
}

随机数

  • 一种是使用 srand 和 rand 方法,它的取值范围为 0 到 32767

  • 另一种是使用 random_device 类,取值范围为 0 到 4294967295

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
#include <iostream>
#include <random>
using namespace std;

//获取一定范围内的随机数
void getRangeRnd(int min, int max) {
random_device dev; //创建random_device对象
int result = (dev() % (max + 1 - min)) + min; //可以是最小值min,也可以是最大值max
cout << result << endl;
}
//使用rand方法连续获取5个随机数
void get5RandNumByRand() {
srand(time(nullptr)); //使用当前时间设置rand的随机数种子
cout << rand() << " "
<< rand() << " "
<< rand() << " "
<< rand() << " "
<< rand() << endl;
}
//使用random_device对象连续获取5个随机数
void get5RandNumByRandomDevice() {
random_device dev; //创建random_device对象
cout << dev() << " "
<< dev() << " "
<< dev() << " "
<< dev() << " "
<< dev() << " " << endl;
}
int main() {
get5RandNumByRand();
get5RandNumByRandomDevice();
getRangeRnd(100, 200);
auto c = getchar();
}

日期时间

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
#include <iostream>
#include <chrono>
using namespace std;
using namespace chrono;
int main() {
//获取当前时间 time_t为int64的值
std::time_t time = std::time(nullptr);
tm localTime;
localtime_s(&localTime, &time); //转型为本地时间
//获取当前时间,类型为system_clock:time_point
system_clock::time_point now = system_clock::now();
seconds::rep milliseconds = duration_cast<chrono::milliseconds>(now.time_since_epoch()).count() % 1000; //获取毫秒数
//打印年份,当前年份距离1900年的差值
cout << localTime.tm_year + 1900 << "-"
<< localTime.tm_mon + 1 << "-" //打印月份:tm_mon取值范围0到11
<< localTime.tm_mday << " " //打印日期:取值范围1到31
<< localTime.tm_hour << ":" //打印小时:取值范围0到23
<< localTime.tm_min << ":" //打印分钟:取值范围0到59
<< localTime.tm_sec << "." //打印秒:取值范围0到60
<< milliseconds << endl //打印毫秒:取值范围0到1000
//打印星期:取值范围0-6,星期日为0
<< "星期:" << (localTime.tm_wday == 0 ? 7 : localTime.tm_wday) << endl
//打印当前是一年中的第几天,取值范围:0到365
<< "今年的第" << localTime.tm_yday + 1 << "天" << endl;
auto a = getchar();
duration<double> span = system_clock::now() - now; //时间计算
cout << span.count() << endl; //打印时间差值:输出:4.26886
duration<double> span2 = hours(3); //表示3小时时间段
cout << span2.count() << endl; //单位秒, 输出10800
auto c = getchar();
c = getchar();
}

变体 variant

  • C++ 标准库中的 variant 可用于保存给定类型集合的一个值
  • variant 变量只能存储值、指针或 reference_wrapper 的实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <variant>
using namespace std;

int main() {
//变体定义时必须指定它可能包含的类型
variant<int, double, string> myVariant;
//可以把不同类型的值赋给变体变量
myVariant = 12;
myVariant = 3.1415926;
myVariant = "hello liulun";
//holds_alternative方法可用于验证变体中是否存储着指定类型的值
cout << holds_alternative<string>(myVariant) << endl; //输出 1
//index方法用于查询存储在变体中值的类型的索引,string的索引为2
cout << myVariant.index() << endl; //输出2
//根据index获取myVariant内的值,如果没有值则抛出异常
cout << get<2>(myVariant) << endl; //输出:hello liulun
//尝试根据类型获取myVariant内的值,如果没有值也不会抛出异常,
//get_if方法的输入参数是指针,返回值也是指针
string* val = get_if<string>(&myVariant);
cout << (val ? (*val) : "null") << endl; //输出:hello liulun
auto c = getchar();
}

可选变量

  • optional 变量用于保存特定类型的值或什么都不保存
  • 只能存储值、指针或 reference_wrapper 的实例
  • 可选变量与变体常用于方法的返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <optional>
using namespace std;

int main() {
//默认myOptional变量内不存在值
optional<int> myOptional;
myOptional = 123;
//把myOptional设置为空,除了使用nullopt外,还可以使用myOptional = {};
myOptional = nullopt;
//判断myOptional中是否有值
cout << myOptional.has_value() << endl;//输出0
//使用value_or获取myOptional中的值,如果值不存在,则使用789
cout << myOptional.value_or(789) << endl;//输出789
myOptional = 456;
//使用value获取myOptional中的值,如果值不存在,则抛出错误
cout << myOptional.value() << endl; //输出456
//值存在,输出变量内的值,值不存在输出789
cout << myOptional.value_or(789) << endl;//输出456

auto c = getchar();
}

任意类型 any

  • C++ 标准库还提供了 any 类型用于存储任意类型的值
  • 不能在 any 变量中存储引用,只能存储值、指针或 reference_wrapper 的实例。
  • any 变量常用于函数参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>
#include <any>
using namespace std;

int main() {
any myAny1;
cout << myAny1.has_value() << endl; //输出0
any myAny2{ 3 };
//any变量一旦构建就已经明确了类型,不能在用其他类型为其赋值
//myAny2 = "allen"; error
//可以使用当前类型为其更改值
myAny2 = 4;
//使用any_cast获取myAny2的值
cout << myAny2.has_value() << " " //输出1
<< any_cast<int>(myAny2) << endl; //输出 4

auto c = getchar();
}

C++ 内联方法、异常处理

内联方法

  • 使用 inline 关键字定义修饰方法

  • 编译器在编译代码时,会把这类方法体放置到调用者的方法内,以提高性能

  • ```c++
    inline void print(string& str) {

    //
    

    }

    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

    ### 异常处理

    [异常类型查询](https://en.cppreference.com/w/cpp/error/exception)

    - 抛异常和 JS 类似,`throw xx_error("..")`

    - 处理异常使用 `try {} catch (const std::exception& ex) {}`

    - 一个 try 代码块可以对应多个 catch 代码块,用于捕获多种不同类型的异常

    - ```c++
    try
    {
    phoneNum = getPhoneNum();
    }
    catch (const std::exception& ex) //接收exception类型的异常
    {
    cout << ex.what() << endl; //打印异常信息信息
    return 0;
    }
    catch (const string& ex) //接收字符串类型的异常
    {
    cout << ex << endl; //打印异常信息信息
    return 0;
    }
    catch (...) { //所有其他类型的异常
    cout << "undefinde Error" << endl; //打印异常信息信息
    }
  • 可以抛出自定义错误类型,但需要对应类型的 catch 捕获

  • 如果被标记了 noexcept 的方法抛出了异常,那么 C++ 将调用 terminate() 方法终止应用程序

    • string getPhoneNum() noexcept{
          //...
      }
      
  • try catch 可以捕获深度嵌套的异常(多少层都可以)

C++ 左值与右值

左值与右值

简单定义

  • 左值(lvalue, locator value)表示了一个占据内存中某个可识别的位置(也就是一个地址)的对象
  • 右值(rvalue)是一个不表示内存中某个可识别位置的对象的表达式(一个表达式不是左值就是右值)
1
2
3
int var = 4l; // 正确
4 = var; // 错误
(var + 1) = 4; // 错误

常量 4 和表达式 var + 1 都不是左值(也就是说,它们是右值),因为它们都是表达式的临时结果,而没有可识别的内存位置(也就是说,只存在于计算过程中的每个临时寄存器中)。因此,赋值给它们是没有任何语义上的意义的——我们赋值到了一个不存在的位置。

右值引用

  • 定义: 右值引用是一种新的引用类型,通过 && 表示。它允许我们绑定到右值(临时对象、将要销毁的对象)。
  • 作用: 主要用于实现移动语义和完美转发
1
int&& rvalue = 42;  // rvalue 是右值引用

移动语义

  • 定义: 移动语义是一种允许将资源(如内存)从一个对象“移动”到另一个对象的语义。它通过右值引用来实现,避免了不必要的拷贝操作。
  • 优势: 提高了性能,特别是在处理临时对象、容器元素的插入、返回值优化等场景。

万能引用

1
2
3
4
5
template<typename T>
void print(T&& str) {
str = "hello liulun";
std::cout << str << std::endl;
}

这里的 print 可以接受左值引用,也能接受右值引用

C++ 函数

函数

函数指针

  • 使用函数指针把函数当做参数传递

  • ```c++
    //定义一个函数
    void print(string& content) {

    cout << content << endl;
    

    }

    //函数的第一个参数就是函数指针
    void callPrint(void (*f) (string&), string& param) {

    f(param); //调用函数指针指向的函数
    

    }

    int main() {

    string content{ "allen" };
    //使用函数指针,把print方法当做参数直接传递给callPrint方法
    callPrint(print, content); //输出allen
    return 0;
    

    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    - 代码 `void (*f) (string&)` 定义了一个函数指针

    - f 是函数指针变量名

    - 这个指针指向一个接收 string 引用且无返回值的方法

    - 可以使用 `auto f` 替代上面那一坨

    - 可以使用 `using` 定义别名

    - ```c++
    using printType = void (*) (string&);
    void callPrint(printType f, string& param) {
    f(param);
    }

函数对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//函数对象类型
class MyFunction {
public:
//重载函数调用运算符
int operator()(int x, int y) {
return x + y;
}
//函数调用运算符的重载版本
double operator()(double x, double y) {
return x * y;
}
};

int main() {
MyFunction func; //函数对象
cout << func(12, 13) << " " << func(3.2,19.8) << endl; /才使用函数对象
//执行函数对象上的方法,输出:25 63.36
return 0;
}
  • 函数对象就是一个重载了函数调用运算符 operator()() 的普通的对象

std::function 模板

1
2
3
4
5
6
#include <functional> //std::function定义在这个头文件中

//f是一个function类型的对象。
void callFunction(function<int(int, int)> f,int param1,int param2) {
cout << f(param1,param2) << endl;
}
  • 使用 std::function 定义的参数可以同时接受函数指针函数对象

匿名函数(lambda 表达式)

  • 定义形式[capture](parameters) -> returnType { body }
  • [capture]用于让匿名函数的内部逻辑具备访问匿名函数外部变量的能力
  • (parameters) 用于声明匿名函数的参数
  • -> returnType 是匿名函数返回值类型(返回类型后置,一般可以忽略)
  • { body }是匿名函数的函数体

lambda 捕获外部变量

  • [],不捕获任何外部变量,此时在匿名函数体内使用外部变量都会报错。
  • [=]:按值捕获所有外部变量。
  • [&]:按引用捕获所有外部变量。
  • [x, &y]:x 按值捕获,y 按引用捕获。
  • [&, x]:x 按值捕获,其他变量按引用捕获。
  • [=, &x]:x 按引用捕获,其他变量按值捕获。
  • [this]:捕获匿名函数所在作用域的对象,this 是当前对象的指针,此时不会复制整个对象。
  • [*this]:捕获匿名函数所在作用域的对象的副本,此时会复制整个对象。

使用 [=][&] 捕捉外部变量时,只有在匿名函数体内真正使用到的变量才会被复制或引用

1
2
3
4
5
6
7
8
9
10
11
12
int main() {
string str { "等忙完这一阵子," };
string str2 {"就可以接着忙下一阵子了。"};
// 按引用捕获变量 str
auto print = [&str](string& param) {
string str2 { "马克思说:" };
// 输出: 马克思说:等忙完这一阵子,就可以接着忙下一阵子了。
cout << str2 << str << param << std::endl;
};
print(str2);
return 0;
}

泛型匿名函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 注意此方法的返回值是auto类型的,实际上返回的是一个方法指针
auto getFunction() {
// 注意:这里匿名函数的参数类型被定义为auto
// 其实是一个模板函数,允许开发者向它传递不同类型的参数
auto print = [](auto param) {
std::cout << param << std::endl;
};
return print;
}

int main() {
// 得到匿名函数的方法指针
auto print = getFunction();
print(123); //输出:123
print("liulun"); //输出:liulun
return 0;
}

this 指针与链式调用

**在执行一个对象的成员函数时,编译器会为该成员函数自动生成一个指针:this**,这个指针就是调用该成员函数的对象指针。

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
class ORM {
public:
ORM* Select(const char* param) {
cout << param << endl;
return this; //返回当前对象指针
}
ORM* From(const char* param) {
cout << param << endl;
return this;
}
ORM* Where(const char* param) {
cout << param << endl;
return this;
}
ORM* OrderBy(const char* param) {
cout << param << endl;
return this;
}
};

int main() {
ORM* orm {new ORM()};
orm->Select("userName")->From("user")->Where("id=123")->OrderBy("createTime");
return 0;
}
  • 执行对象的成员函数时,我们可以在函数体内直接访问对象的成员变量或方法

    • 编译器把这样的代码:callYourClassMethod();,翻译成这样的代码:this->callYourClassMethod();

C++ 模板

模板

函数重载

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
//函数重载:整型版本
int getLargestNumber(const vector<int>& vect) {
int largestNumber{ vect.at(0) };
for (auto& val : vect)
{
if (val > largestNumber) largestNumber = val;
}
return largestNumber;
}

//函数重载:浮点数版本
double getLargestNumber(const vector<double>& vect) {
double largestNumber{ vect.at(0) };
for (auto& val : vect)
{
if (val > largestNumber) largestNumber = val;
}
return largestNumber;
}

int main() {
vector<int> vect1{ 1,2,3,4,5,9,8,7,6,0 };
vector<double> vect2{ 1.1,2.2,3.3,4.4,9.9,8.8,7.7,6.6 };
auto result1 = getLargestNumber(vect1);
auto result2 = getLargestNumber(vect2);
cout << result1 << endl; //输出:9
cout << result2 << endl; //输出:9.9
auto c = getchar();
}
  • 同名函数但函数参数不同或参数类型不同
  • C++ 会根据传入的参数调用对应的函数

函数模板

使用模板来编写处理多种数据类型的通用函数,而不是写多个函数重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 函数模板
template <typename T>
T add(T a, T b) {
return a + b;
}

int main() {
int result1 = add(5, 3); // 实例化为 int 类型
double result2 = add(2.5, 3.5); // 实例化为 double 类型

std::cout << "Result 1: " << result1 << std::endl;
std::cout << "Result 2: " << result2 << std::endl;

return 0;
}
  • 编译器在处理 add 调用时,会根据参数类型生成 add 函数的重载实现

  • 只在必要时生成

  • 可以使用 auto 关键字自动推断返回类型

    • ```c++
      template <typename T1, typename T2>
      auto getBiggerNum(T1 param1,T2 param2) {

      // auto 关键字自动推断返回类型
      return param1 > param2 ? param1 : param2;
      

      }

      int main() {

      int a{ 123 };
      double b{ 987.23 };
      auto result = getBiggerNum(a, b);
      cout << result << endl;  //输出:987.23
      auto c = getchar();
      

      }

      1
      2
      3

      ### 类模板

      #include
      #include
      using namespace std;

//模板类:键值对
template
class KeyValPair
{
public:
KeyValPair(string k, T v) :key{ k }, value{ v } { };
string key;//键
T value; //值
};

//模板类:键值字典
template
class Dic
{
public:
Dic()=default;
void PushBack(KeyValPair val) {
vect.push_back(val);
}
auto operator[](string key) {
for (KeyValPair& pair:vect)
{
if (pair.key == key) {
return pair.value;
}
}
}
vector<KeyValPair> vect;
};

//使用模板类
int main() {
Dic dic;
// { “test1” , 123 } 直接用于创建KeyValPair对象
dic.PushBack({ “test1” , 123 });
dic.PushBack({ “test2” , 456 });
dic.PushBack({ “test3” , 789 });
cout << dic[“test2”] << endl; //输出456
auto c = getchar();
}

1
2
3
4
5
6
7
8
9

### 模板别名

```c++
//模板别名
using myType = MyTemplateType<int,string,double,char>;

myType obj;
//...

模板参数

非类型模板参数不代表一个具体的数据类型,而是一个常量表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
//模板类:非类型模板参数:WIDTH,以及模板参数的默认值
template <typename T = int,T WIDTH = 123>
class MyType
{
public:
MyType() = default;
T getWidth() {
return WIDTH; //使用模板参数
}
};

int main() {
MyType obj; //使用了模板参数的默认值
cout << obj.getWidth() << endl; //输出123
MyType<double,456.1> obj2; //没有使用模板参数的默认值
cout << obj2.getWidth() << endl; //输出456.1
auto c = getchar();
}

总结

  • 以模板为基础的泛型也是一种多态的表现形式,但面向对象的多态是运行时多态,而泛型多态是编译期多态
  • 对于模板类,编译器应该要能同时访问到模板的定义和实现,把模板的定义和实现全部放在头文件中

C++ 清理内存

资源获得即初始化惯用法 RAII

RAII (Resource Acquisition Is Initialization) 用于一个实例离开作用域时自动释放已获取资源

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
#include <iostream>
#include <string>
using namespace std;

//假设这是一个会占用较大堆空间的类型
class Message
{
public:
Message() : val{"这是一个非常长的字符串"} {
}
~Message() { }
void SendMessage() {
cout << val << endl;
}
string val;
};

//这个类型负责管理Message对象的释放工作
class MessageRAII {
public:
MessageRAII() : msgObj{ new Message() } { };
Message* get() {
return msgObj;
}
~MessageRAII() {
delete msgObj;
}
Message* msgObj;
};

//入口函数
int main() {
{
// 在栈上声明一个代理类型,负责管理目标对象
// 代理类型自身会在离开作用域时销毁
MessageRAII msgRAIIobj;
msgRAIIobj.get()->SendMessage();
}
auto c = getchar();
}

智能指针

  • 代理对象,智能管理内存
  • 通过 ->* 解引用访问代理对象
  • 通过 . 访问自身的管理方法

共享指针 shared_ptr

more info

  • 自动释放关联的堆空间

  • 同一对象可被多个共享指针持有

    • 内部记录该类型的引用个数
    • 引用记数为 0时,释放内存
  • 定义方法

    • ```c++
      shared_ptr myClass0 = make_shared();

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20

      - `shared_ptr` 定义指针

      - `make_shared` 初始化,如果 MyClass 构造函数有参数,通过 `make_shared` 传递

      - 在 `memory` 头文件定义

      - 尖括号中放置共享指针的类型

      - 通过共享指针的 `use_count` 方法检查堆空间一共存在多少个共享指针引用

      - myClass0.use_count();

      - **如果一个共享指针被重新赋值(让共享指针指向一个全新的对象),那么该共享指针被重新赋值前关联的堆空间的引用计数会减一**

      - ```c++
      {
      auto myClass1 = make_shared<MyClass>();
      myClass1 = make_shared<MyClass>();
      }
      • 上面的代码, MyClass 的析构函数会执行两次,第一次是智能指针 myClass1 被重新赋值时,第二次是智能指针 myClass1 超出作用域之后
  • 成员函数

    • swap
    • reset
    • get
    • use_count
    • unique

独占指针 unique_ptr

more info

  • 自动释放关联的堆空间

  • 同一对象只能被一个独占指针持有

  • 定义方法

    • ```c++
      unique_ptr myClass = make_unique();
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10

      - **如果一个独占指针被重新赋值,那么该独占指针被重新赋值前,其关联的堆空间会被立即释放**,使用 reset 重新绑定

      - 独占指针的 `reset` 方法立即释放堆内存

      - ```c++
      auto myClass = make_unique<MyClass>();
      myClass.reset();
      // 或者像 reset 传入新的类型指针
      // myClass.reset(new Cls());
  • 成员函数

    • get 获取代理对象指针
    • get_deleter
    • release 释放对象,返回原始对指针
    • reset
    • swap

弱指针 weak_ptr

more info

用来解决共享指针的循环引用问题