1. 复习内容
1.1 匿名命名空间
字面意思:声明命名空间时忽略名字
编译器内部会为这个命名空间生成一个唯一的名字和using指令
namespce {
char c;
int i;
double d;
}
所以上面的代码在编译器内部类似于:
namespace __UNIQUE_NAME_ {
char c;
int i;
double d;
}
using namespace __UNIQUE_NAME_;
它和静态变量的相似之处?
匿名命名空间也具有内连接属性,也就是说名称的作用域被限制在当前文件中,无法在其他文件使用extern来扩展作用域
它和静态变量相比更优在哪?
对于多个同一文件的标识符函数只需要用一个匿名空间来声明,不需要多次输入static
1.2 如何引用命名空间
// 方式一
ace::Mutex mutex;
// 方式二
using ace::Mutex;
// 方式三
using namespace ace;
-
方式一:
只是在必要的时候运用域运算符::来引用指定空间里的标识符。
适用于:当前编译单元内引用ace内的标识符不多且使用次数不多 -
方式二:
只引入ace::Mutex一个标识符
适用于:当前编译单元内ace::Mutex使用次数较多的情况 -
方式三:
把ace里的全部标识符都引入当前命名空间中,此后ace内所有的标识符对于当前空间都是可见的
适用于:当前编译单元内使用ace内的标识符较多,且不会出现标识符冲突的问题
对于上面三种方式的选择应由一到三,因为越往后产生命名冲突的可能越大
1.3 override与final
-
override:指定一个虚函数覆写另一个虚函数
在成员声明或定义中,override确保该函数为虚并且复写来自基类的虚函数,若不是就会出现编译错误 -
final:指定派生类不能覆写虚函数,或类不能被继承
在虚函数声明或定义中,final确保函数为虚且不可被派生类复写。
在类定义中,final指定此类不能被派生
1.4 访问者模式
-
定义:封装某些作用于某数据结构中各元素的操作,它可以在
不改变数据结构
的前提下定义作用于这些元素的新的操作 - 自己的理解:在衣服店定制了几套衣服,成衣后我又想对某件衣服进行改变,但是已经不能改变衣服了,所以收到衣服后,我只能针对我想改变的那件,新增加一些小搭配(丝巾等等)
-
含有角色:
抽象访问者 访问者 抽象元素类 元素类 结构对象
package yanbober.github.io;
import java.util.ArrayList;
import java.util.List;
//Vistor(抽象访问者)
interface Vistor {
void visit(ConcreteElementNodeA node);
void visit(ConcreteElementNodeB node);
}
//ConcreteVisitor(具体访问者)
class ConcreteVisitorA implements Vistor {
@Override
public void visit(ConcreteElementNodeA node) {
System.out.println(node.operationA());
}
@Override
public void visit(ConcreteElementNodeB node) {
System.out.println(node.operationB());
}
}
class ConcreteVisitorB implements Vistor {
@Override
public void visit(ConcreteElementNodeA node) {
System.out.println(node.operationA());
}
@Override
public void visit(ConcreteElementNodeB node) {
System.out.println(node.operationB());
}
}
//Element(抽象元素)
abstract class ElementNode {
public abstract void accept(Vistor vistor);
}
//ConcreteElement(具体元素)
class ConcreteElementNodeA extends ElementNode {
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
public String operationA() {
return "ConcreteElementNodeA";
}
}
class ConcreteElementNodeB extends ElementNode {
@Override
public void accept(Vistor vistor) {
vistor.visit(this);
}
public String operationB() {
return "ConcreteElementNodeB";
}
}
//ObjectStructure(对象结构)
class ObjectStructure {
private List<ElementNode> nodeList = new ArrayList<>();
public void action(Vistor vistor) {
for (ElementNode node : nodeList) {
node.accept(vistor);
}
}
public void add(ElementNode node) {
nodeList.add(node);
}
}
//客户端
public class Main {
public static void main(String[] args) {
ObjectStructure objectStructure = new ObjectStructure();
objectStructure.add(new ConcreteElementNodeA());
objectStructure.add(new ConcreteElementNodeB());
Vistor vistor = new ConcreteVisitorA();
objectStructure.action(vistor);
}
}
参考:
设计模式(行为型)之访问者模式(Visitor Pattern)
23种设计模式(9):访问者模式
1.5 何时捕获异常
我们只能捕获我们能够处理的异常,能够恢复的异常将它捕获后恢复,像不能恢复的如,越界等就不要捕获了。
对使用 C++ 异常处理应具有怎样的态度?
1.6 可重入函数
1.7 杂项
- explicit构造函数:指定构造函数或转换函数为显式,即它不能用于隐式转换和复制初始化
- 静态存储周期:static用于声明对象拥有静态存储期
- POD(Plain Old Data):该类型包括标量类型,c/c++的基本类型,用户自定义的类类型(这里的类必须是无析构函数和构造函数/拷贝构造函数的类,其实也就相当于struct)
- const_cast:用来移除变量的cv限定符
-
volatile:一般编译器在访问变量时,都会对变量进行优化。也就是不会每一次都去变量的内存中读数据,编译器会把变量值存在「寄存器」中,在下一次查询时直接到此取数据,这样访问速度就大大增加。
而volatile修饰的变量,编译器每一次查询都会去内存中读取数据。它常用于多线程编程,当一个线程改变变量的值的时候,「寄存器」的值可能还未更新,所以必须从内存中读数据读到的才是真实的数据。 -
mutable:常用于不影响类的外部可变状态的成员,该成员在const函数内依旧可被修改
下面是它在lambda表达式中的使用:
虽然看起来修改了x,其实只是修改了x的拷贝
int x{0};
auto f1 = [=]() mutable {x = 42;}; // okay, 创建了一个函数类型的实例
auto f2 = [=]() {x = 42;}; // error, 不允许修改按值捕获的外部变量的值
- std::function:通用多态的函数封装器,它的实例可以存储,复制及调用任何可调用目标
// 存储自由函数
std::function<void(int)> f_display = print_num;
f_display(-9);
// 存储 lambda
std::function<void()> f_display_42 = [](){ print_num(42); };
f_display_42();
2. 代码风格的思考(仅针对客户端)
2.1 在别人代码上进行迭代
- 注意不要破坏别人原有的风格,对函数命名/变量命名应与前人统一
- 修改之前应该仔细看看代码的结构,不要把本来耦合度很低的代码改成了耦合度极高的代码
2.2 自己写代码
- 在做csd时就思考好节点的构造
- 低耦合
- 代码简洁,清楚
- 命名易于理解,别人在看你代码时,可以通过命名直接明白函数需要做什么
- 考虑内存!考虑内存!需要做缓存的就做缓存
下面以这个页面来对代码做一个梳理:
首先有一个头部主要是放活动图片/标题等,中部是一个横向scroll,上面的btn可以刷新下面的纵向scroll里的节点信息
- 首先拿到页面所需要信息(initData)
- 根据信息从上往下在代码中对页面进行展示(initView)
- 思考scroll是否需要做缓存,在这里我只对纵向scroll做了缓存
- 初始化横向scroll,注册回调事件来刷新纵向scroll(initMiddleScroll,updateMiddleScroll)
- 定义两个table:tableItemsSel 和 tableItemsSpare(用于存储缓存数据)
- 封装纵向scroll的信息,使用一个table封装,这里的point用于等会插入缓存table(initScrollData)
local tableItemsSel = {
{ data = data,
point = nil,
pos = {x = 0,y = 0}
}
}
- 初始化纵向scroll,计算节点位置,更新tableItemsSel.pos(initScroll)
- 填充纵向scroll
设定一个值为预加载范围,如果在范围内就让节点可见,不在范围内就让节点不可见且插入缓存tableItemsSpare
local scrollH = scroll的高度
local tableShowH = {beginY = math.max(0,scroll的y轴偏移-0.5*scrollH),endY = scroll的y轴偏移+1.5*scrollH,}
白色区域为我们的初始可视区域,红色区域就是我们现在扩大了的可视区域,超过这个可视区域,就让节点不可见且插入缓存tableItemsSpare
if v.pos.y < tableShowH.beginY or v.pos.y > tableShowH.endY then
v.point:setVisible(false)
table.insert(self.tableItemsSpare,v.point)
v.point = nil
end
如果缓存数组里有节点就取出设为可见,再初始化它。否则新建节点
- 设置scroll回调事件去重新填充scroll