UML类图速成
类的表示 一个类在类图中用矩形框表示,矩形框分为三层: 类名 类的成员变量:名称和类型 类的方法:名称,参数类型和名称,返回值类型(也就是完整的函数签名) 对于成员变量和方法,使用不同的前缀代表访问修饰符: 缺省代表default +代表public -代表private #代表protected 对于一个抽象类或接口,它的类名以及抽象方法(留给子类实现的方法)名称使用斜体表示,以示区别。 类的关系 下面的几种关系讨论的都是两个类之间的关系,也可以理解为两个类的实例之间的关系。 对于满足后四种关系的两个类,如果屏蔽了其中一个类的定义,另一个类是无法通过编译的。 有的教程将后四种关系统称为关联关系,将聚合/组合/依赖关系视作特殊的关联关系。 继承与实现 继承和实现是类之间非常强的关系,在代码中的特征非常明显: 继承关系(泛化关系) 子类和父类之间的继承关系。 在记号上使用带空心三角箭头的实线相连,空心三角箭头指向父类。 实现关系 类和接口之间的实现关系。 在记号上使用带空心三角箭头的虚线相连,空心三角箭头指向接口。 这里的接口主要是针对Java的,Java...
Cpp Pimpl 模式
Pimpl(Pointer to Implementation)是一种常用的C++设计模式,用于隐藏类的实现细节、减少编译依赖性和提高封装性。 概述 Pimpl模式主要的思路就是:在实现类的外层套上一层简单的接口类,接口类只含有一个指向实现类的指针,并暴露必要的接口。 注意接口类包含的不是实现类对象,而是实现类指针,否则修改实现类无法做到二进制的稳定性。 Pimpl模式可以达到如下的效果: 隐藏实现细节:实现细节被封装在实现类中,提供给用户的接口类只暴露必要的接口,提高代码的封装性 维护接口稳定性:我们只需要维护暴露在接口类中的接口稳定性即可,实现类的内部可以自由地进行更改 减少编译依赖性:由于接口类的实现细节被隐藏,接口类只含有实现类的指针,对实现类进行的细节改变不会导致依赖于接口类的文件重新编译,从而减少编译时间。 Pimpl模式和继承以及虚函数的作用是不重合的,并不存在相互取代的关系。 和虚函数类似,Pimpl增加了间接的指针调用,这必然会影响到程序的运行效率。 示例 包括三个文件: Demo.h:接口类的声明 Demo.cpp:实现类的完整实现,以及接口类的实现 m...
Linux ssh 笔记
详细记录一下关于ssh的内容,这部分内容比较繁杂。 基本使用(1) ssh 最基本的用途就是登录远程服务器 1ssh user@hostname 其中: user是登录用户名 hostname是主机名,它可以是域名,也可以是某个具体的 IP 地址或局域网内部的主机名。 可以缺省用户名,此时将使用本地用户名作为远程服务器的登录用户名。 1ssh hostname 用户名也可以通过-l参数指定,这样用户名和主机名就不用写在一起了,在脚本中可能更方便 1ssh -l username hostname ssh 会默认连接远程服务器的22端口,使用-p参数也可以指定其他端口(同时也要修改远程服务器的监听端口) 1ssh -p 8821 foo.com ssh 在连接到远程服务器后会进行验证:如果第一次通过ssh连接某一台服务器, 命令行会显示一段文字,表示不认识这台机器,提醒用户确认是否需要连接 123The authenticity of host 'foo.com (192.168.121.111)' can't be established.ECD...
C/Cpp 枚举类与强枚举类
整理一下关于C/C++中枚举类的用法。 概述 C和C++中提供了枚举类型,用于定义一组相关的命名离散常量, 通常直接使用非负整数实现,并且枚举可以很方便地和非负整数进行相互转换。 但是这不符合C++类型系统的设计要求,C++将之前的枚举称为弱枚举类型,并且提供了更加安全的强枚举类型, 强枚举相比弱枚举有如下优点: 作用域限制:枚举成员不会污染所在作用域的命名空间。 类型安全:强类型枚举不允许隐式转换为整数,必须显式进行转换。 明确的基础类型:可以指定枚举成员的基础类型,默认是int。 弱枚举类型 使用enum定义枚举类型和枚举变量,例如 12345enum DAY{ MON, TUE, WED, THU, FRI, SAT, SUN};enum DAY day; 也可以将它们合在一起简写 1234enum DAY{ MON, TUE, WED, THU, FRI, SAT, SUN} day1, day2; 使用枚举类例如 12345678910111213#include <stdio.h>enum DAY &...
Cpp 进阶笔记——3.引用折叠、万能引用和完美转发
引用折叠规则 引入了右值引用后,我们必须要处理右值引用所带来的一系列类型推导问题,因为C++不允许“引用的引用”这种类型存在, 对于涉及两个连续出现的引用修饰词的类型推导时,定义了如下的引用折叠规则: 1234& + & -> && + && -> &&& + & -> &&& + && -> && 简而言之,就是左值引用短路右值引用:只有连续两个右值引用遇到一起,才会推导出右值引用,只要出现左值引用,就会推导出左值引用。这套规则主要在模板类型匹配和auto中使用。 C++希望坚持“引用就是别名”的原则,并且不允许直接定义引用的引用(还是可以间接实现的),这与指针的指针可以任意级嵌套是不同的。虽然左值引用主要就是靠指针实现,但那其实只是编译器选择的一种实现方案,并不是语法直接规定的。 模板函数实验 我们考虑如下五类的模板函数进行实验,对它们的类型推导可谓是各不相同 1234567891011121314template...
Cpp 进阶笔记——2.移动语义
手动创建将亡值 我们继续前面的函数传参的例子,但是我们做一些修改:调用方提供的不再是一个临时对象,而是一个普通的局部对象 12345678910111213void ProcessBuf(Buffer buf) { for (int i = 0; i < buf.size(); i++) { buf.at(i) = 2 * i; } for (int i = 0; i < buf.size(); i++) { std::cout << buf.at(i) << " "; } std::cout << "\n";}void test2() { Buffer a{5}; a.at(0) = 100; ProcessBuf(a);} 程序运行结果如下(这里无所谓是否关闭优化,因为编译器并不敢进行优化) 12345call constructor // ...
Cpp 进阶笔记——1.右值引用
我们关注现代C++中比较难以理解的概念:右值引用,移动语义,完美转发等, 这些概念都是C++11之后才提出的,目的是进一步压榨程序的运行期效率,避免某些非必要的临时变量的拷贝构造和析构过程。这些语法是完全针对底层实现的,并不是针对于上层的语义优化,不是为了让程序变得更易读的语法糖。 左值和右值 (1) 在C++中,表达式由一个或多个运算对象通过运算符组成,对表达式求值得到一个结果。 字面量和变量是最简单的表达式,它们的结果就是字面量和变量的值。 一个表达式至少具有如下两个属性: 类型:描述计算产生的值的静态类型 值类别:描述值是如何产生的,以及表达式的行为如何被影响 从语法上检查一个表达式能否给另一个表达式赋值,既需要判断类型之间能否进行转换,还需要判断值类别是否满足要求。在本文中我们不讨论类型问题,重点关注表达式的值类别。 在C语言和C++的早期语法中,值类别被简单分为左值和右值。 简单地说,在一个合法的赋值语句中,等号左边的就是左值表达式,等号右边的就是右值表达式。 在赋值过程中,右值表达式不会被改变,而左值表达式会因为赋值而改变。 1lvalue = rvalue; 左...
Cpp 函数 API 设计
整理一下关于现代化的C++函数API设计的笔记, 我们关注如何设计可读性好,使用安全的函数接口:设计函数接口的名称、参数类型、返回值等。 部分参考现代化的 API 设计指南 基本原则 函数API的设计要满足如下的原则: 向用户传达清晰的语义,保证接口的可读性,避免因为语义不清晰导致用户的错误调用; 向编译器提供更多的信息,尽可能让错误的使用在编译时就被发现,由编译器发出警告或直接导致编译失败。 如果需要考虑: 跨编译器使用:混用不同编译器编译得到的库 跨语言使用:混用C语言和C++,或者将当前的C++库提供给其它语言调用(例如Python) 那么必须保证使用extern "C"提供C语言形式的接口。 函数和参数名称 保持统一的函数名称风格:大驼峰或使用下划线,例如 123void get_date();void SetDate(); 函数名称通常使用动词加名称的形式,避免提供名称相似的函数接口,尤其是不要出现仅有大小写不同的函数接口。 对于参数的命名,遵循统一的规则即可,但是最好保留具体的语义,例如 1void SetDate(int year, int month,...
Cpp 命名空间
C++提供命名空间用于组织代码并避免命名冲突,这在大型项目和库开发中尤为重要。 命名空间机制只会影响编译期间的各种符号可见性和链接顺序,并不会产生任何的运行时影响。 基本使用 定义一个命名空间的示例如下,可以在命名空间中加入类/函数/变量等,在命名空间之外必须加上命名空间的前缀才能访问。 123456789101112131415161718192021222324252627#include <iostream>// 声明需要包裹在命名空间中namespace MyNamespace {extern int s;int func();}// 定义也需要包裹在命名空间中namespace MyNamespace {int s = 20;int func() { return 40; }}int func() { return 30; }int main() { int s = 10; std::cout << "s = " <&l...
Cpp const* 关键词
C++关于常量提供了很多形如const*的关键词,在语义上涉及到运行期的常量约束和编译期的初始化这两个性质,这几个关键词的含义相当类似,有必要整理一下。 概述 C++陆续引入了下面几个关键词: 第一代:const,从C语言中继承,在C++中一直都存在 第二代:constexpr,C++11引入 第三代:consteval和constinit,C++20引入 其中const继承自C语言的,其它几个则是C++独有的关键词,关于它们的用法仍然在迅速发展中,因为C++很想发掘出编译期计算的巨大潜力,但是历史包袱导致它一直干不好。 我们重点关注的是编译期计算,可以使用如下的两种方式来检查: 第一种方式利用在数组的定义,数组长度需要提供一个编译期的值,因为普通变量的初始化始终发生在运行期,直接使用一个变量作为数组长度是无法通过编译的。(有时确实可以通过编译,但那属于编译器的扩展功能,不是语法标准规定的用法) 第二种方式是使用C++专门提供的static_assert关键字来进行静态断言(将其称为编译期断言更合适,与C语言中assert所代表的运行期断言相对应),它会在编译期判定一个编译...