说到再整理,很惭愧,以前学习数据结构与算法,没有系统性地学习过,本次整理,是结合近几年的工作经验整理常用的数据结构和算法。
先列一个list,后续文章详细介绍每个知识点, 并附上C++版实现代码。
数据结构部分
array
vector
list
slist
dequeue
queue
stack
heap
priority_queue
hashtable
binary search tree
balanced binary search tree
AVL tree
B tree
B+ tree
RB tree
trie tree
set
map
multiset
multimap
unordered_set (hash_set)
unordered_map (hash_map)
hash_multiset
hash_multimap
算法
binary search
bubble sort
insert sort
section sort
quick sort
shell sort
merge sort
heap sort
bucket sort
radix sort
external sort
LRU
Bloom filter
breadth first search
depth first search
书中有一句名言:要嗅到坏代码的味道
概括关系主要指继承体系中的关系
QVariant 是 Qt 中的万用类型,可表示 Qt 中大多数类型,但无法表示自定义的 enum 类型,这就需要借助 Qt 的元对象系统了。
enum Type
{
A,
B
};
添加自定义类型到元系统
Q_DECLARE_METATYPE(Type)
这个宏使系统(QMetaType)知道该自定义类型,包括 QVariant
enum Type
{
A,
B
};
Q_DECLARE_METATYPE(Type)
这样,enum 就可以被用于 QVariant 了,可以这么用:
QVariant type = QVariant::fromValue(Type::A); // 设置值
Type t = type.value<Type>(); // 取值
在 Python 中,我们可能会经常使用 try except 来捕获异常,比如:
try:
value = info_dict["key"]
except KeyError:
print("Error key")
这段代码捕获的是 dict 的 KeyError 异常,当发生此异常时,会打印一句话。
有时候,为了图省事方便,你可能会这么写:
try:
# some operation
except:
pass # pass 意味着什么都不做
当心!这么写是一个很糟糕的习惯。
我们来看看糟糕在哪里:
在 except 分支中只有 pass 语句,即发生异常时,什么也不做,程序继续执行,此时的状态很可能是错误的,正确的做法是在异常分支中打印适当的信息,以告诉用户发生了什么。
没有指定异常类型,程序便会捕获所有异常,包括Python中的各种异常,也包括内存满了,CPU 爆了,程序退出异常等系统性异常,正确的做法是指定对应的异常:
try:
# some operation
except Exception as e:
print("Exception occured:", e)
指定 Exception 会捕获所有 Python 的异常,并打印出异常信息。
先看一个例子:
class A
{
vector<int> _v;
public:
void setter(const vector<int> &v)
{
cout << "lvalue setter" << endl;
_v = v;
}
void setter(vector<int> &&v)
{
cout << "rvalue setter" << endl;
_v = move(v);
}
const vector<int>& getter() const
{
cout << "getter" << endl;
return _v;
}
};
A AFactory()
{
A a1;
a1.setter(vector<int>{1});
return a1;
}
A a2;
a2.setter(AFactory().getter());
输出:
rvalue setter
getter
lvalue setter
注意第三个输出,表示 a2 对象调用了左值引用版本。但实际上呢?
AFactory().getter()
的返回值是一个左值引用,a1 对象是临时对象,在语句执行完后就被销毁了,因此,a1.getter() 返回值也会被销毁,既然都会被销毁,那么 a2.setter() 期望调用的是右值重载版本,当然,这需要能够识别出一个对象是否是临时对象,c++11 提供了引用限定的成员函数(Ref-qualifiers for member function),能够根据对象来调用不同的成员函数。书写方式是在函数名括号后面加&或&&。
上述代码可修改为:
// 非临时对象调用版本
const vector<int>& getter() const &
{
cout << "lvalue getter" << endl;
return _v;
}
// 临时对象调用版本
vector<int>&& getter() &&
{
cout << "rvalue getter" << endl;
return move(_v);
}
输出:
rvalue setter
rvalue getter
lvalue setter
文章内容节选自 https://github.com/anjuke/zguide-cn。文章翻译自ZMQ官网指南。
ZMQ(ØMQ、ZeroMQ, 0MQ)看起来像是一套嵌入式的网络链接库,但工作起来更像是一个并发式的框架。它提供的套接字可以在多种协议中传输消息,如线程间、进程间、TCP、广播等。你可以使用套接字构建多对多的连接模式,如扇出、发布-订阅、任务分发、请求-应答等。ZMQ的快速足以胜任集群应用产品。它的异步I/O机制让你能够构建多核应用程序,完成异步消息处理任务。
目前的应用程序很多都会包含跨网络的组件,无论是局域网还是因特网。这些程序的开发者都会用到某种消息通信机制。有些人会使用某种消息队列产品,而大多数人则会自己手工来做这些事,使用TCP或UDP协议。这些协议使用起来并不困难,但是,简单地将消息从A发给B,和在任何情况下都能进行可靠的消息传输,这两种情况显然是不同的。
让我们看看在使用纯TCP协议进行消息传输时会遇到的一些典型问题。任何可复用的消息传输层肯定或多或少地会要解决以下问题:
我们可以找一个开源软件来做例子,如Hadoop Zookeeper,看一下它的C语言API源码,src/c/src/zookeeper.c。这段代码大约有3200行,没有注释,实现了一个C/S网络通信协议。它工作起来很高效,因为使用了poll()来代替select()。但是,Zookeeper应该被抽象出来,作为一种通用的消息通信层,并加以详细的注释。像这样的模块应该得到最大程度上的复用,而不是重复地制造轮子。
但是,如何编写这样一个可复用的消息层呢?为什么长久以来人们宁愿在自己的代码中重复书写控制原始TCP套接字的代码,而不愿编写这样一个公共库呢?
其实,要编写一个通用的消息层是件非常困难的事,这也是为什么FOSS项目不断在尝试,一些商业化的消息产品如此之复杂、昂贵、僵硬、脆弱。2006年,iMatix设计了AMQP协议,为FOSS项目的开发者提供了可能是当时第一个可复用的消息系统。AMQP比其他同类产品要来得好,但仍然是复杂、昂贵和脆弱的。它需要花费几周的时间去学习,花费数月的时间去创建一个真正能用的架构,到那时可能为时已晚了。
大多数消息系统项目,如AMQP,为了解决上面提到的种种问题,发明了一些新的概念,如“代理”的概念,将寻址、路由、队列等功能都包含了进来。结果就是在一个没有任何注释的协议之上,又构建了一个C/S协议和相应的API,让应用程序和代理相互通信。代理的确是一个不错的解决方案,帮助降低大型网络结构的复杂度。但是,在Zookeeper这样的项目中应用代理机制的消息系统,可能是件更加糟糕的事,因为这意味了需要添加一台新的计算机,并构成一个新的单点故障。代理会逐渐成为新的瓶颈,管理起来更具风险。如果软件支持,我们可以添加第二个、第三个、第四个代理,构成某种冗余容错的模式。有人就是这么做的,这让系统架构变得更为复杂,增加了隐患。
在这种以代理为中心的架构下,需要一支专门的运维团队。你需要昼夜不停地观察代理的状态,不时地用棍棒调教他们。你需要添加计算机,以及更多的备份机,你需要有专人管理这些机器。这样做只对那些大型的网络应用程序才有意义,因为他们有更多可移动的模块,有多个团队进行开发和维护,而且已经经过了多年的建设。
这样一来,中小应用程序的开发者们就无计可施了。他们只能设法避免编写网络应用程序,转而编写那些不需要扩展的程序;或者可以使用原始的方式进行网络编程,但编写的软件会非常脆弱和复杂,难以维护;亦或者他们选择一种消息通信产品,虽然能够开发出扩展性强的应用程序,但需要支付高昂的代价。似乎没有一种选择是合理的,这也是为什么在上个世纪消息系统会成为一个广泛的问题。
我们真正需要的是这样一种消息软件,它能够做大型消息软件所能做的一切,但使用起来又非常简单,成本很低,可以用到所有的应用程序中,没有任何依赖条件。因为没有了额外的模块,就降低了出错的概率。这种软件需要能够在所有的操作系统上运行,并能支持所有的编程语言。
ZMQ就是这样一种软件:它高效,提供了嵌入式的类库,使应用程序能够很好地在网络中扩展,成本低廉。
ZMQ的主要特点有:
其实ZMQ可以做的还不止这些,它会颠覆人们编写网络应用程序的模式。虽然从表面上看,它不过是提供了一套处理套接字的API,能够用zmq_recv()和zmq_send()进行消息的收发,但是,消息处理将成为应用程序的核心部分,很快你的程序就会变成一个个消息处理模块,这既美观又自然。它的扩展性还很强,每项任务由一个节点(节点是一个线程)、同一台机器上的两个节点(节点是一个进程)、同一网络上的两台机器(节点是一台机器)来处理,而不需要改动应用程序。