Cpp 奇异递归模板模式 CRTP
CRTP
是一个非常经典的C++静态多态实现方案,在很多库中都有应用,这里整理一下。
概述
奇异递归模板模式(Curiously Recurring Template Pattern, CRTP)是 C++
编程中一种常用的静态多态方案,它通过模板继承和静态绑定来实现类型多态。
相比于C++直接提供的基于虚函数的动态多态方案,CRTP既可以避免虚函数的额外性能开销,还可以在编译时获得更好的类型检查和优化。
CRTP的核心思想如下:
模板基类是一个模板类,模板参数是派生类自身。(这种递归的模板参数是这个模式名字的来源)
派生类:派生类继承自通过自身类型实例化的基类。
通过基类调用方法时,首先会将this指针进行类型转换为派生类的类型(因为派生类类型是模板参数,基类可以获取到派生类类型),然后就可以调用派生类的方法。
这种处理导致我们无法添加基类自身的方法实现并使用,因此基类只能作为一个抽象基类使用。
这一切都发生在模板实例化的过程中,在编译期中即可完全确定。
CRTP具有如下的优势:
静态多态:CRTP的是静态多态,通过基类调用派生类的方法是静态绑定的,完全在编译期通过模板实例化确 ...
Cpp optinal+variant+any+expected 学习笔记
现代C++提供了几个非常实用的类型工具,分别为
std::optional<T>
可能含有T类型的值或者无值;(C++17引入)
std::variant<T1,T2,...>
(类型安全的union)可能含有类型列表中某个类型的值;(正常情况下不会无值)(C++17引入)
std::any
可能含有任意类型的值,也可能无值,使用者必须提供自行指定合理的类型;(如果内部的值转换失败会抛异常)(C++17引入)
std::expected<T,E>
可能含有正常的T类型的结果,也可能含有异常的E类型的值;(C++23引入)
它们的定位和用法比较类似,因此一起整理一下,因为这几个工具的语法仍然在迅速发展中,本文只考虑C++20和C++23已经支持的语法。
需要注意的是,这些类型工具都只能接收简单的类型,不允许使用数组类型和引用类型,并且最好不要含有cv修饰符。
std::optinal
std::optional<T>对象的值可能是T对象,也可能是std::nullopt(代表没有值),注意这里的T不允许是数组类型和引用类型。
构造和赋值 ...
Cpp static+extern+inline 学习笔记
整理一下关于C++中的几个关键词(static,extern和inline)的笔记。
static
static这个关键词的语义非常复杂,在C语言中本来就有多重语义,C++又添加了额外的语义。
静态函数
默认情况下,函数可以被其它编译单元使用,具有外部链接性,可以使用static将其改为具有内部链接性的静态函数,只有在当前文件中才可以使用此函数。
例如 123static void func();void func(){}
静态全局变量
与静态函数类似,全局变量在默认情况下可以被其它编译单元所使用,具有外部链接性,
可以使用static将其改为静态全局变量,只有在当前文件中才可以使用。
例如 1static int s = 100;
静态局部变量
在函数体内部使用static修饰的局部变量会变成静态局部变量,此时静态的语义不再是对外部不可见,而是延长生命周期,此时变量的存储位置从栈区转移到了数据区,不会因为函数调用的结束而销毁,下次进入函数体时会自动忽略定义和初始化语句,例如
12345int func(){ static int s = 0; ...
Cpp 访问权限
在 C++
的面向对象编程中,有三个访问权限限定词:public、private
和
protected,用于定义对类成员(成员包括变量和函数)的可访问性,
也包括在继承后的访问权限变化。
我们主要对class进行讨论,最后会讨论struct和class的区别。
访问权限
C++
通过访问权限限定词来提供数据封装和信息隐藏机制,这有助于提高代码的可读性和可维护性,三种访问权限限定词的基本语义如下:
public成员:公开的类成员,可以通过类的成员函数和类的对象访问。
protected成员:受保护的类成员,只能通过类的成员函数访问,无法通过类的对象访问。
private成员:私有的类成员,只能通过类的成员函数访问,无法通过类的对象访问。
注意这里访问的含义:对数据成员的访问是读写,对成员函数的访问则是函数调用。
对于数据的访问权限并没有被进一步拆分为只读和可读写,只读的访问效果需要基于const实现,实现只写的访问效果则需要考虑左值和右值特性,不在本文的讨论范围。
通过类的对象访问是指a.x和a.show()这类语法。
通过类的成员函数访问是指在成员函数体内部对类的其它成员访问,因 ...
C/Cpp 面向对象基础
面向对象编程(OOP)并不是一种特定的语言或者工具,它只是一种设计方法、设计思想。
OOP表现出来的三个最基本的特性就是封装、继承与多态。
在本文中我们将讨论C语言如何简单地实现OOP的基础功能,并且关注C++是如何实现OOP的,对于C++的讨论不涉及过多的语法细节,不涉及访问权限的讨论(全部使用public)。
封装
封装就是在形式上将数据和操作数据的方法打包在一起,然后提供部分接口给外部访问,隐藏内部的实现细节。
但是C语言没有直接提供将内部信息隐藏的机制,我们只能使用其它的间接方案:
君子协定,约定只通过固定的接口进行访问;
利用动态库的符号部分导出的特点将动态库的部分信息隐藏;
利用static变量和函数对源文件外部不可见的性质,将部分信息隐藏
下面我们只关注数据和操作方法的打包,因为C语言的结构体只含有数据成员,我们需要通过函数指针在结构体中加上操作数据的函数,例如
1234567891011121314151617181920212223242526272829303132333435363738#include <stdio.h>typedef struc ...
Cpp 虚函数与动态多态
C++使用虚函数机制来实现运行期的多态(动态多态),与之相对的是编译期的多态(静态多态),
对于C++来说,静态多态可以通过函数重载和泛型编程实现,而动态多态则通过虚函数实现。
这里简单整理一下虚函数和动态多态的内容。
这部分是C++非常核心的语法内容,不同平台和不同编译器得到的结果应该都是一致的。
在本文讨论的范围内,基类或派生类指针/引用的使用方法通常是完全等效的,示例主要以指针的使用为主。
为了简化讨论,我们不关注public,private,protected修饰的区别,无论是对类的方法还是继承关系的修饰,统一使用public。
我们不关注多继承、菱形继承以及虚继承等复杂情景,只考虑简单的单继承情景。
简单示例
我们直接从一个涉及简单继承关系的例子开始 12345678910111213141516171819202122232425262728293031323334353637383940414243#include <iostream>struct Base { virtual ~Base() = default; void hello( ...
C/Cpp 类型转换和类型别名
关于C/C++的类型转换和类型别名的整理笔记。
C语言类型转换
从C语言的类型转换开始,包括最基本的显式类型转换(强制类型转换)和隐式类型转换(自动类型转换)。
数值类型转换语义
整数和浮点数之间的类型转换满足舍入原则:
整数转换到浮点数的结果是距离最近的浮点数
浮点数转换到整数的结果是向0舍入的整数(如果得到的整数值不在目标类型范围内,行为未定义)
两个方向的转换均可能损失信息。
整数类型到整数类型的转换满足如下规则:
如果原始整数值可以在目标整数类型下精确表达,那么转换是无损的;
在其它情形下则是有损的转换:
如果目标是无符号整数类型,具体实现是取模意义下的。
如果目标是有符号整数类型,具体实现是未知的。
指针类型转换语义
指向无限定类型对象的指针可以隐式转换成指向该类型有限定版本的指针,也就是说,
我们可以添上const、
volatile等限定符,原指针与结果比较相等,例如
12int n;const int* p = &n; // &n 拥有类型 int*
任何指针类型都可以与void *类型进行双向的隐式转换,例如
123int n = 10;v ...
Cpp 函数参数默认值
C语言不支持函数重载,也不支持函数参数的默认值,这既可以说体现了C语言的简陋,也可以说是避免了很多麻烦。
C++的函数参数支持默认值的机制就比较烦人,因此需要整理一下。
为了提高代码的可读性,C++尽量也不要使用函数默认值,在讨论的最后提供了几种简单的方式可以替代。
基础
C++支持给函数参数提供默认值,例如
12345678910void func(int a, int b=1){ ...}int main(){ func(1,2); func(3); // == func(3,1), a=3,b=1 return 0;}
C++要求参数的默认值必须从右向左连续地提供,保证无默认值的参数不能出现在有默认值的参数的右侧,否则编译器无法判断参数缺省时的对应关系,这是语法错误,例如
1234567void func(int a, int b=1, int c){ // compile error ...}void func(int a, int b=1, int c=2){ // ok ...
Cloudreve个人云盘搭建
记录一下Cloudreve云盘的搭建。
概述
Cloudreve云盘的优点:
颜值可以;
部署很简单,连docker都不需要(还没鼓捣清楚docker),数据库之类的不用管,傻瓜式操作即可。
缺点:
功能比较简单,没什么插件扩展(其实也不需要);
部分开源,有一个付费的版本包含更多功能,社区环境不太友好;
只支持网页端和ios的app,不支持Windows和安卓(也不需要,因为有的开源网盘就算提供了app,UI也很简陋)
本地部署
Linux下直接下载压缩包到合适位置,解压执行即可完成本地部署,目前放置在~/cloudreve/目录下。
12345678#解压获取到的主程序tar -zxvf cloudreve_VERSION_OS_ARCH.tar.gz# 赋予执行权限chmod +x ./cloudreve# 启动 Cloudreve./cloudreve
启动之后,Cloudreve
默认会监听5212端口。(建议使用root账户启动,因为会生成相应的数据库文件等)
注意:
Cloudreve在首次启动时,会创建初始管理员账号,注意保管管理员密码,此密码只会在首次启动时 ...
C语言 数组和指针笔记
虽然在C++中完全不需要处理C语言中原始数组和原始指针等,C++提供了很多更好的替代实现,但是还是顺手整理一下吧。
数组基础
一维数组
定义数组 1int a[5];
在定义时可以赋初值,此时可以省略数组长度,例如 1int a[] = {1,2,3,4,5};
可以用如下方式获取数组长度 1int len = sizeof(a)/sizeof(a[0]);
如果我们不赋初值,则数组的初值是随机的,这可能导致程序BUG。
需要通过索引读写数组的元素,数组的索引从0开始,例如 123int a[5];a[0] = 100;print("%d",a[1]);
C语言不会检查索引的合法性,对越界的索引进行的读写操作是未定义的危险行为,需要结合下面的指针理解。
二维数组
定义二维数组 1int a[2][3];
二维数组赋初值 1int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
二维数组通过行索引和列索引读写元素 123int a[2][3];a[0][1] = 1 ...