一、问题现象

黑苹果,安装好声卡后可以正常使用,但是睡眠后再次起来声卡就没有声音了。

问题原因

机器进入睡眠后,外部放大器CodecCommander处于关机状态。唤醒后,音频工作正常了,但是这个功放还没有启动,需要发送一个解码器命令才能启动。

二、解决方案

安装CodecCommander驱动,在网上找到适合自己声卡的kext驱动,放到EFI/kexts/Other目录下。

最新公用版本下载地址:OS-X-EAPD-CodecCommander,在页面找到最新版本安装就可以了。

如果装了驱动还不管用,就需要定制声卡驱动了,这个比较麻烦。

三、参考

使用AppleALC声卡仿冒驱动AppleHDA的正确姿势

AppleALC支持的Codecs列表及AppleALC的使用

ALC892的经验之谈,简化大神教程帮助喜欢动手仿冒的朋友

转载自阿里云开发者社区:Homebrew 镜像

一、简介

Homebrew 是一款自由及开放源代码的软件包管理系统,用以简化 macOS 系统上的软件安装过程。它拥有安装、卸载、更新、查看、搜索等很多实用的功能,通过简单的一条指令,就可以实现包管理,十分方便快捷。

二、配置方法

首先确保你已经安装好了 Homebrew 了, 如果没有, 请参考 OPSX 指引页的 Homebrew 文档;然后你只需要粘贴下述命令在对应终端运行。

2.1 Bash 终端配置

# 替换brew.git:
cd "$(brew --repo)"
git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git
# 替换homebrew-core.git:
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git
# 应用生效
brew update
# 替换homebrew-bottles:
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.bash_profile
source ~/.bash_profile

2.2 Zsh 终端配置

# 替换brew.git:
cd "$(brew --repo)"
git remote set-url origin https://mirrors.aliyun.com/homebrew/brew.git
# 替换homebrew-core.git:
cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
git remote set-url origin https://mirrors.aliyun.com/homebrew/homebrew-core.git
# 应用生效
brew update
# 替换homebrew-bottles:
echo 'export HOMEBREW_BOTTLE_DOMAIN=https://mirrors.aliyun.com/homebrew/homebrew-bottles' >> ~/.zshrc
source ~/.zshrc

2.3 恢复默认配置

出于某些场景, 可能需要回退到默认配置, 你可以通过下述方式回退到默认配置。

首先执行下述命令:

# 重置brew.git:
$ cd "$(brew --repo)"
$ git remote set-url origin https://github.com/Homebrew/brew.git
# 重置homebrew-core.git:
$ cd "$(brew --repo)/Library/Taps/homebrew/homebrew-core"
$ git remote set-url origin https://github.com/Homebrew/homebrew-core.git

然后删掉 HOMEBREW_BOTTLE_DOMAIN 环境变量,将你终端文件

 ~/.bash_profile

或者

 ~/.zshrc

HOMEBREW_BOTTLE_DOMAIN

行删掉, 并执行

 source ~/.bash_profile

或者

 source ~/.zshrc9

三、相关链接

一、使用说明

1.1 下载地址

所有的驱动都可以在官方驱动列表找到。

1.2 使用说明

打开关于本机,双击版本号(10.13.6),然后系统的详细版本信息就会显示出来了

进去到上面的驱动列表页面,里面是一些xml格式的数据,例如:

<dict>
    <key>downloadURL</key>
    <string>https://images.nvidia.com/mac/pkg/387/WebDriver-387.10.10.10.40.134.pkg</string>
    <key>size</key>
    <string>63950151</string>
    <key>OS</key>
    <string>17G11023</string>
    <key>version</key>
    <string>387.10.10.10.40.134</string>
</dict>

直接在页面里面搜索子版本号17G11023,找到对应的段,然后复制downloadURL中的值就是下载链接了:

https://images.nvidia.com/mac/pkg/387/WebDriver-387.10.10.10.40.134.pkg

最后在浏览器打开或者用shell下载安装就可以了。

二、各版本驱动整理

收集到目前(2020-02-05)最新的驱动列表:

版本系统下载
387.10.10.10.40.134macOS 10.13.6 High Sierra (17G11023)点我下载
387.10.10.10.40.133macOS 10.13.6 High Sierra (17G10021)点我下载
387.10.10.10.40.132macOS 10.13.6 High Sierra (17G9016)点我下载
387.10.10.10.40.131macOS 10.13.6 High Sierra (17G8037)点我下载
387.10.10.10.40.130macOS 10.13.6 High Sierra (17G8030)点我下载
387.10.10.10.40.128macOS 10.13.6 High Sierra (17G7024)点我下载
387.10.10.10.40.127macOS 10.13.6 High Sierra (17G6030)点我下载
387.10.10.10.40.123macOS 10.13.6 High Sierra (17G6029)点我下载
387.10.10.10.40.122macOS 10.13.6 High Sierra (17G5019)点我下载
387.10.10.10.40.113macOS 10.13.6 High Sierra (17G4015)点我下载
387.10.10.10.40.108macOS 10.13.6 High Sierra (17G3025)点我下载
387.10.10.10.40.105macOS 10.13.6 High Sierra (17G65)点我下载
387.10.10.10.35.106macOS 10.13.5 High Sierra (17F77)点我下载
387.10.10.10.30.107macOS 10.13.4 High Sierra (17E202)点我下载
387.10.10.10.30.106macOS 10.13.4 High Sierra (17E199)点我下载
387.10.10.10.25.160macOS 10.13.3 High Sierra (17D2102)点我下载
387.10.10.10.25.161macOS 10.13.3 High Sierra (17D102)点我下载
387.10.10.10.25.157macOS 10.13.3 High Sierra (17D2047)点我下载
387.10.10.10.25.156macOS 10.13.3 High Sierra (17D47)点我下载
378.10.10.10.25.106macOS 10.13.2 High Sierra (17C2205)点我下载
378.10.10.10.25.105macOS 10.13.2 High Sierra (17C2120)点我下载
378.10.10.10.25.104macOS 10.13.2 High Sierra (17C205)点我下载
378.10.10.10.25.103macOS 10.13.2 High Sierra (17C89)点我下载
378.10.10.10.25.102macOS 10.13.2 High Sierra (17C88)点我下载
378.10.10.10.20.109macOS 10.13.1 High Sierra (17B1003)点我下载
378.10.10.10.20.108macOS 10.13.1 High Sierra (17B1002)点我下载
378.10.10.10.20.107macOS 10.13.1 High Sierra (17B48)点我下载
378.10.10.10.15.121macOS 10.13.0 High Sierra (17A405)点我下载
378.10.10.10.15.114macOS 10.13.0 High Sierra (17A365)点我下载
378.05.05.25f19macOS 10.12.6 Sierra (16G2136)点我下载
378.05.05.25f18macOS 10.12.6 Sierra (16G2128)点我下载
378.05.05.25f16macOS 10.12.6 Sierra (16G2016)点我下载
378.05.05.25f15macOS 10.12.6 Sierra (16G1918)点我下载
378.05.05.25f14macOS 10.12.6 Sierra (16G1917)点我下载
378.05.05.25f13macOS 10.12.6 Sierra (16G1815)点我下载
378.05.05.25f12macOS 10.12.6 Sierra (16G1710)点我下载
378.05.05.25f11macOS 10.12.6 Sierra (16G1618)点我下载
378.05.05.25f10macOS 10.12.6 Sierra (16G1510)点我下载
378.05.05.25f09macOS 10.12.6 Sierra (16G1408)点我下载
378.05.05.25f08macOS 10.12.6 Sierra (16G1314)点我下载
378.05.05.25f06macOS 10.12.6 Sierra (16G1212)点我下载
378.05.05.25f04macOS 10.12.6 Sierra (16G1114)点我下载
378.05.05.25f03macOS 10.12.6 Sierra (16G1036)点我下载
378.05.05.25f01macOS 10.12.6 Sierra (16G29)点我下载
378.05.05.15f01macOS 10.12.5 Sierra (16F73)点我下载
378.05.05.05f02macOS 10.12.4 Sierra (16E195)点我下载
367.15.10.35f01macOS 10.12.3 Sierra (16D32)点我下载
367.15.10.25f02macOS 10.12.2 Sierra (16C68)点我下载
367.15.10.25f01macOS 10.12.2 Sierra (16C67)点我下载
367.15.10.15f03macOS 10.12.1 Sierra (16B2659)点我下载
367.15.10.15f03macOS 10.12.1 Sierra (16B2657)点我下载
367.15.10.15f01macOS 10.12.1 Sierra (16B2555)点我下载
367.15.10.05f01macOS 10.12.0 Sierra (16A323)点我下载
346.03.15f16OS X 10.11.6 El Capitan (15G22010)点我下载
346.03.15f15OS X 10.11.6 El Capitan (15G21013)点我下载
346.03.15f14OS X 10.11.6 El Capitan (15G20015)点我下载
346.03.15f12OS X 10.11.6 El Capitan (15G19009)点我下载
346.03.15f11OS X 10.11.6 El Capitan (15G18013)点我下载
346.03.15f10OS X 10.11.6 El Capitan (15G17023)点我下载
346.03.15f09OS X 10.11.6 El Capitan (15G1611)点我下载
346.03.15f08OS X 10.11.6 El Capitan (15G1510)点我下载
346.03.15f07OS X 10.11.6 El Capitan (15G1421)点我下载
346.03.15f06OS X 10.11.6 El Capitan (15G1217)点我下载
346.03.15f05OS X 10.11.6 El Capitan (15G1212)点我下载
346.03.15f04OS X 10.11.6 El Capitan (15G1108)点我下载
346.03.15f03OS X 10.11.6 El Capitan (15G1004)点我下载
346.03.15f02OS X 10.11.6 El Capitan (15G31)点我下载
346.03.10f02OS X 10.11.5 El Capitan (15F34)点我下载
346.03.06f01OS X 10.11.4 El Capitan (15E65)点我下载
346.03.05f02OS X 10.11.3 El Capitan (15D21)点我下载
346.03.04f02OS X 10.11.2 El Capitan (15C50)点我下载
346.03.03f02OS X 10.11.1 El Capitan (15B42)点我下载
346.03.02f02OS X 10.11.0 El Capitan (15A284)点我下载
346.02.03f14OS X 10.10.5 Yosemite (14F2511)点我下载
346.02.03f13OS X 10.10.5 Yosemite (14F2411)点我下载
346.02.03f12OS X 10.10.5 Yosemite (14F2315)点我下载
346.02.03f11OS X 10.10.5 Yosemite (14F2109)点我下载
346.02.03f10OS X 10.10.5 Yosemite (14F2009)点我下载
346.02.03f09OS X 10.10.5 Yosemite (14F1912)点我下载
346.02.03f08OS X 10.10.5 Yosemite (14F1909)点我下载
346.02.03f07OS X 10.10.5 Yosemite (14F1808)点我下载
346.02.03f06OS X 10.10.5 Yosemite (14F1713)点我下载
346.02.03f05OS X 10.10.5 Yosemite (14F1605)点我下载
346.02.03f04OS X 10.10.5 Yosemite (14F1509)点我下载
346.02.03f03OS X 10.10.5 Yosemite (14F1505)点我下载
346.02.03f02OS X 10.10.5 Yosemite (14F1021)点我下载
346.02.03f01OS X 10.10.5 Yosemite (14F27)点我下载
346.02.02f03OS X 10.10.4 Yosemite (14E46)点我下载
346.01.02f04OS X 10.10.3 Yosemite (14D136)点我下载
346.01.02f01OS X 10.10.3 Yosemite (14D131)点我下载
346.01.01f01OS X 10.10.2 Yosemite (14C1514)点我下载
343.02.02f03OS X 10.10.2 Yosemite (14C1510)点我下载
343.02.02f02OS X 10.10.2 Yosemite (14C109)点我下载
343.02.01f01OS X 10.10.1 Yosemite (14B25)点我下载
343.01.01f03OS X 10.10.0 Yosemite (14A389)点我下载
334.01.03f11OS X 10.9.5 Mavericks (13F1911)点我下载
334.01.03f10OS X 10.9.5 Mavericks (13F1808)点我下载
334.01.03f09OS X 10.9.5 Mavericks (13F1712)点我下载
334.01.03f08OS X 10.9.5 Mavericks (13F1603)点我下载
334.01.03f07OS X 10.9.5 Mavericks (13F1507)点我下载
334.01.03f06OS X 10.9.5 Mavericks (13F1134)点我下载
334.01.03f05OS X 10.9.5 Mavericks (13F1112)点我下载
334.01.03f04OS X 10.9.5 Mavericks (13F1096)点我下载
334.01.03f03OS X 10.9.5 Mavericks (13F1077)点我下载
334.01.03f02OS X 10.9.5 Mavericks (13F1066)点我下载
334.01.03f01OS X 10.9.5 Mavericks (13F34)点我下载
334.01.02f02OS X 10.9.4 Mavericks (13E28)点我下载
334.01.01f01OS X 10.9.3 Mavericks (13D65)点我下载
331.01.01f04OS X 10.9.2 Mavericks (13C1021)点我下载
331.01.01f02OS X 10.9.2 Mavericks (13C64)点我下载
313.01.04f06OS X 10.8.5 Mountain Lion (12F2560)点我下载
313.01.04f05OS X 10.8.5 Mountain Lion (12F2542)点我下载
313.01.04f04OS X 10.8.5 Mountain Lion (12F2518)点我下载
313.01.04f02OS X 10.8.5 Mountain Lion (12F2501)点我下载
313.01.04f01OS X 10.8.5 Mountain Lion (12F45)点我下载
313.01.03f01OS X 10.8.5 Mountain Lion (12F37)点我下载
313.01.02f01OS X 10.8.4 Mountain Lion (12E55)点我下载
313.01.01f03OS X 10.8.3 Mountain Lion (12D78)点我下载

点击Preferences-Settings,然后在User配置中添加段:

{
    "ignored_packages": []
}

默认的值是:

"ignored_packages": ["Vintage"]

去掉Vintage保存就可以使用vim了。

一、问题描述

在HTTPS连接被中间人代理后(一般出现在公共场所,例如公共WIFI或者需要ssl解密的场景),第一次访问网站会弹出HSTS错误:

HSTS是一个很简单的访问安全策略,通过在HTTP头部中增加字段告诉客户端HTTPS连接的相关信息,客户端(浏览器)通过这些信息校验连接是否可信。

因此,出现这个错误的原因是因为服务端的HTTPS证书被劫持了、而服务端又开启了HSTS策略导致的。完美的解决方案只有一种,就是避免网站被劫持。临时的解决方案可以通过修改浏览器配置来完成。

二、解决办法

在浏览器(chrome)输入:

chrome://net-internals/#hsts

拉到最下面,输入刚刚访问的网站域名,点击delete

一、HTTP1.0和HTTP1.1

HTTP1.0和1.1的主要区别为:

  1. 长连接:HTTP1.0默认是短连接,HTTP1.1默认使用长连接。
  2. 断点续传:HTTP1.1支持断点续传,可以通过 Range头部指定需的资源数据部分。
  3. 添加Host头部:HTTP1.1中为了解决虚拟主机的使用场景,通过Host字段来指定访问某个特定web服务。
  4. 状态码:HTTP1.1添加了更多的状态码,如100等。

二、HTTP1.1和HTTP2.0

HTTP2.0相对于1.1来说跨了一个大版本,相应的改动也是非常大的。它的主要目标是提高HTTP协议的传输效率,不过主要都是基于数据传输上的改动,HTTP协议本身并没有修改太多。关于HTTP2.0的相关信息可以参考HTTP/2简介

HTTP2.0和HTTP1.1的主要区别为:

  1. HTTP1.1通过ascii码传输数据,而HTTP2.0通过二进制帧来传输。
  2. HTTP2.0默认使用长连接,并且每个来源只有一个连接。
  3. HTTP2.0压缩了HTTP头部,优化头部传输机制,大幅减少http传输空间。
  4. HTTP2.0原生支持服务端推送。

来源:Latency Numbers Every Programmer Should Know

图片版:

文字版:

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns                      14x L1 cache
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns                      20x L2 cache, 200x L1 cache
Compress 1K bytes with Zippy             3,000   ns        3 us
Send 1K bytes over 1 Gbps network       10,000   ns       10 us
Read 4K randomly from SSD*             150,000   ns      150 us          ~1GB/sec SSD
Read 1 MB sequentially from memory     250,000   ns      250 us
Round trip within same datacenter      500,000   ns      500 us
Read 1 MB sequentially from SSD*     1,000,000   ns    1,000 us    1 ms  ~1GB/sec SSD, 4X memory
Disk seek                           10,000,000   ns   10,000 us   10 ms  20x datacenter roundtrip
Read 1 MB sequentially from disk    20,000,000   ns   20,000 us   20 ms  80x memory, 20X SSD
Send packet CA->Netherlands->CA    150,000,000   ns  150,000 us  150 ms

Notes
-----
1 ns = 10^-9 seconds
1 us = 10^-6 seconds = 1,000 ns
1 ms = 10^-3 seconds = 1,000 us = 1,000,000 ns

中文整理版:

操作耗时备注
CPU一级缓存寻址0.5纳秒
CPU二级缓存寻址7纳秒
互斥锁25纳秒
内存寻址100纳秒
使用zippy压缩1k文件3000纳秒(3微秒)
在1Gbps的网络中发送1k数据10000纳秒(10微秒)
从ssd中随机读取4KB数据150000纳秒(150微秒)SSD速率:1GB/s
从内存中顺序读取1MB250000纳秒(250微秒)
同一数据中心往返耗时500000纳秒(500微秒)
从ssd中随机读取1MB数据1000000纳秒(1000微秒,1毫秒)SSD速率:1GB/s
磁盘寻址10000000纳秒(10000微秒,10毫秒)约等于20次数据中心往返
从磁盘中顺序读取1MB数据20000000纳秒(20000微秒,20毫秒)

备注:1秒=$10^3$毫秒=$10^6$微秒=$10^9$纳秒。

一、单链表

链表是一种线性结构,通过前后节点指针连接起每个节点,从结构来看就像是用一个链把所有的节点都串起来了,因此被称为链表。

它和数组最大的不同就是不能随机访问,数组的内存空间是顺序的,通过计算位置偏差就能定位到元素。而链表的节点的地址是不连续的,它无法直接通过地址定位随机访问。但是数组在以下场景就没有链表方便了:

  1. 开辟新空间:数组元素到达上限后,如需添加新元素,需要重新开辟一块更大的空间,然后把当前数组的数据拷贝过去再继续添加元素。
  2. 删除元素:删除数组元素后,需要把被删除元素后面的元素都集体前移。
  3. 移动元素:移动元素和删除类似,移动节点会导致部分节点数据迁移。

但是对于链表而言,都不存在以上问题。它不管是新增、删除甚至移动节点,都能在O(1)的时间完成。因此,在某些情况下,它更适合数组来使用。

二、单链表的操作

2.1 链表结构

链表都是由一个个节点组成的,从面向对象的特性上来说,链表和节点是两个不同的东西。每个节点至少都包含了一个数据字段和一个next节点指针,next指针指向的是下一个节点地址,而链表只需要保存一个首节点指针就行了。通过链表的首节点指针和每个节点的next指针,就可以循环遍历到节点的每一个元素了。

2.2 添加节点操作

添加节点的操作可通过下图演示:

添加节点

可以分为两步来描述:

  1. 设置新节点(B节点)的next指针为下一个节点(C节点)地址。
  2. 把插入节点(A)节点的next指针指向新节点(B节点)。

这样就完成了一个节点的插入。

2.3 删除节点操作

删除节点的操作图示:

删除节点

操作描述:

  1. 找到删除节点的前置节点(A节点)。
  2. 设置前置节点的next指针为删除节点的next指针指向。
  3. 删除节点。

2.4 移动节点操作

移动节点的操作可以分解为删除和添加节点两步:

  1. 把移动节点从链表中删除。
  2. 把这个删除的节点重新插入到链表的指定位置。

三、代码描述(C++)

3.1 节点和链表定义

节点定义:

template<typename T>
class list_node {
public:
    friend class singly_linklist<T>;
    list_node() : next(nullptr) {}
    list_node(const T &data) : next(nullptr), data(data) {}
private:
    T data;
    list_node<T> *next;
};

节点的定义很简单,包含了一个T类型的数据域和next指针,其中比较特殊的是对singly_linklist类的友元定义,singly_linklist是后面要定义的链表类,通过设置友元可以方便链表类直接操作节点中的私有数据域,因为需要在链表中对节点进行新增、删除和移动等操作。

链表的定义如下:

template<typename T>
class singly_linklist {
public:
    singly_linklist() : len(0), head(nullptr), tail(nullptr) {}
private:
    size_t len;
    list_node<T> *head;
    list_node<T> *tail;
};

链表中包含了两个指针headtailhead指针是上面说的链表首元素的地址,而tail指针则是链表尾元素指针,tail指针在单链表中实际上没有很大的用途,但是在后续的双向链表和双向循环链表中都会用到,单链表中可以用作冗余字段使用。

除了首位指针以外,还有一个表示链表长度的字段,这个字段也是作为冗余使用,实际项目中如果需要获取链表长度就可以在0(1)时间返回,避免了便利链表。链表长度的设置方法也很简单:每添加一个节点,长度加一;每删除一个节点,长度减一。

3.2 添加节点

插入链表节点的逻辑很简单,代码在网上一搜也是一大把,但很少有准确并简单的实现方式。

本文中的链表(包括后续的双向链表和循环链表)都参考了linux内核中的实现方式,力求简单、高效并且准确。

链表节点的插入有头插法和尾插法,头插法的意思是把节点插到链表头部,而尾插法的意思是把节点插到链表尾部。两者逻辑不同,但是共同点就是都要插入节点到链表中,只是插入的位置不同。因此对添加节点而言,有必要把插入节点逻辑抽离出来作为私有函数供其他插入函数调用。

定义一个私有的添加节点函数(c语言中通过static关键字完成,c++通过private关键字完成),传入参数包含三个:

  • 待插入的节点new_node,因为是私有函数提供给类中的其他函数使用的,因此外部函数调用前要确保值不为空。
  • 待插入节点的前置节点prev,值可能为空(插入节点到链表首部的时候)。
  • 待插入节点的后置节点next,值可能为空(插入节点到链表尾部的时候)。

添加节点除了设置前后节点指针以外,还有一个难处理的地方就是首尾指针的赋值。

什么时机才应该重新赋值首尾指针???

更新首尾指针的地址的场景:

  1. 空链表插入新节点。
  2. 非空链表插入节点到链表首部或者尾部。
  3. 删除首、尾节点(删除的场景在下面讨论)。

可以整理出来的一个规律是:

  • 如果新插入节点的前置节点是空(此时插入节点到链表头),设置头指针为新插入的节点。
  • 如果新插入节点的后置节点为空(此时插入节点到链表尾),设置尾指针为新插入的节点。

代码如下:

/*
 * 添加节点操作,新添加的节点需确保不是空值
 * @new_node 新节点
 * @prev 前驱节点,节点可能为空(插入到首部的时候)
 * @next 后继节点,节点可能为空(插入到尾部的时候)
 */
template <typename T>
void singly_linklist::add_node(list_node<T> *new_node, list_node<T> *prev, list_node<T> *next) {
    len++;
    new_node->next = next;

    if (prev) { // 如果前置节点不为空,修改前置节点的next指针
        prev->next = new_node;
    } else { // 前置节点为空,说明放在了链表头部,设置头节点的指针
        head = new_node;
    }

    if (next == nullptr) { // 后置节点为空,说明放在链表的尾部,设置尾节点的指针
        tail = new_node;
    }
}

实现了add_node函数后,再实现头插和尾插函数就简单了:

/*
 * 插入元素到末尾
 * @data 待插入数据
 */
template <typename T>
const singly_linklist<T> &singly_linklist::push_back(list_node<T> *node) {
    if (node == nullptr)
        return *this;

    // 添加节点到末尾,前置节点是尾节点,后置节点是空
    add_node(node, tail, nullptr);

    return *this;
}

/*
 * 插入数据到开头
 * @node 待插入的数据节点
 */
template <typename T>
const singly_linklist &singly_linklist::push_front(list_node<T> *node) {
    if (node == nullptr)
        return *this;

    // 添加节点到开头,前置节点是空,后置节点是头节点
    add_node(node, nullptr, head);
  
    return *this;
}

一个要注意的地方是两个函数的返回值都是const singly_linklist &,在函数结束后都返回*this,这么做的目的就是为了支持链式表达式。

3.3 删除节点

删除节点和添加节点一样,难处理的地方就是首尾指针的赋值(也就是上面添加节点时说到的需要处理但是还没有处理的的第3点)。

删除节点时处理首尾指针的规律和插入节点一致:

  1. 如果删除节点的前置节点是空(删除的是头节点),设置头节点指针为删除节点的后置节点。
  2. 如果删除节点的后置节点是空(删除的是尾节点),设置尾节点指针为删除节点的前置节点。

代码如下:

/*
 * 删除节点,待删除的节点请确保不是空值
 * @del_node 待删除节点
 * @prev 前驱节点
 * @next 后继节点
 */
template <typename T>
void singly_linklist::remove_node(list_node<T> *del_node, list_node<T> *prev, list_node<T> *next) {
    len--;
    del_node->next = nullptr;

    if (prev) { // 如果存在前置节点,更新前置节点的next指针
        prev->next = next;
    } else { // 不存在前置节点,删除的是头节点,更新头节点的指针
        head = next;
    }

    if (next == nullptr) { // 不存在后置节点,删除的是尾节点,更新尾节点指针
        tail = prev;
    }
}

对比remove_node和add_node的函数实现可以看到,函数开头都没有对新增节点或删除节点做合法校验。

不校验的意义在于两个都是内部函数,提供给内部其他函数使用的,其他函数在外层调用的时候已经判断参数合法性了,函数内已经确保不会为空。

在实现remove_node之后,需要再实现的一个功能是找到前置节点,因为删除节点要先找到前置节点才能调用。查找前置节点函数find_prev的操作为:

/*
 * 查找某个节点的前一个节点
 * @node 当前节点
 * @return 如果存在上一个节点,返回上一个节点的地址,否则返回nullptr
 */
template <typename T>
list_node<T> *singly_linklist::find_prev(list_node<T> *node) {
    list_node<T> *p = head;
    if (node == nullptr)
        return nullptr;
  
    // 遍历链表查找前置节点
    while (p != nullptr) {
        if (p->next == node) {
            return p;
        }
        p = p->next;
    }
    return p;
}

有了find_prev之后,对外的remove函数就可以实现了:

/*
 * 删除节点
 * @node 待删除节点
 */
template <typename T>
const singly_linklist &singly_linklist::remove(list_node<T> *node) {
    list_node<T> *prev;
    if (node == nullptr)
        return;

    // 找到前置节点
    prev = find_prev(node);

    // 删除节点
    remove_node(node, prev, node->next);

    delete node;
}

基于remove函数实现pop_frontpop_bak

// 弹出首元素
template <typename T>
const singly_linklist &singly_linklist::pop_front() {
    remove(head);
}

// 弹出尾元素
template <typename T>
const singly_linklist &singly_linklist::pop_back() {
    remove(tail);
}

四、单元测试

测试头插法:

TEST(singly_linklist, push_front) {
    int i;
    singly_linklist<int> list;
    const list_node<int> *p;
    vector<int> v{1, 2, 3};

    list.push_front(3).push_front(2).push_front(1);
    EXPECT_EQ(3, list.get_len());

    p = list.get_head();
    for (i = 0; i < v.size(); i++) {
        EXPECT_EQ(p->get_data(), v[i]);
        p = p->get_next();
    }
    EXPECT_EQ(p, nullptr);
}

测试尾插法:

TEST(singly_linklist, push_back) {
    int i;
    singly_linklist<int> list;
    const list_node<int> *p;
    vector<int> v{1, 2, 3};

    list.push_back(1).push_back(2).push_back(3);
    EXPECT_EQ(3, list.get_len());

    p = list.get_head();
    for (i = 0; i < v.size(); i++) {
        EXPECT_EQ(p->get_data(), v[i]);
        p = p->get_next();
    }
    EXPECT_EQ(p, nullptr);
}

测试获取前置节点:

TEST(singly_linklist, get_prev) {
    int i;
    singly_linklist<int> list;
    const list_node<int> *p;
    list_node<int> *node1, *node2, *node3;

    // 插入3个节点
    node1 = new list_node<int>(1);
    node2 = new list_node<int>(2);
    node3 = new list_node<int>(3);
    list.push_back(node1).push_back(node2).push_back(node3);
    EXPECT_EQ(3, list.get_len());

    p = list.find_prev(node1);
    EXPECT_EQ(p, nullptr);
    p = list.find_prev(node2);
    EXPECT_EQ(p, node1);
    p = list.find_prev(node3);
    EXPECT_EQ(p, node2);

}

测试删除节点:

TEST(singly_linklist, remove) {
    int i;
    singly_linklist<int> list;
    const list_node<int> *p;
    list_node<int> *node1, *node2, *node3;

    node1 = new list_node<int>(1);
    node2 = new list_node<int>(2);
    node3 = new list_node<int>(3);

    list.push_front(node1);
    list.push_back(node2);
    list.push_back(node3);

    // 删除中间节点
    list.remove(node2);
    EXPECT_EQ(node1, list.get_head());
    EXPECT_EQ(node3, list.get_tail());
    EXPECT_EQ(node1->get_next(), node3);

    // 删除头节点
    list.remove(node1);
    EXPECT_EQ(node3, list.get_head());
    EXPECT_EQ(node3, list.get_tail());
    EXPECT_EQ(node3->get_next(), nullptr);

    // 删除尾节点
    list.remove(node3);
    EXPECT_EQ( list.get_head(), nullptr);
    EXPECT_EQ( list.get_tail(), nullptr);
}

测试结果:

一、基本用法

画一个简单的二叉树:

digraph bin_tree {
    1->2;
    1->3;
}

图形:

二、设置形状

上面的1/2/3都是一个node,通常被称为节点,默认情况下节点是圆形的。可以通过shape属性来设置节点形状。

设置形状为长方形:

digraph bin_tree {
    node [shape="rectangle"];
    1->2;
    1->3;
}

设置形状为三角形:

digraph bin_tree {
    node [shape="triangle"];
    1->2;
    1->3;
}

graphviz提供了很多形状可以选择,具体的类型和样式可在Node Shapes找到。

三、设置线条

3.1 设置虚线

设置线条的属性要修改edge属性,线条不只是箭头,每个node的边也被edge属性控制。

例如设置节点的线条为虚线:

digraph bin_tree {
    node [shape="rectangle" style="dashed"];
    1->2;
    1->3;
}

dash-node

设置箭头的线条为虚线:

digraph bin_tree {
    node [shape="rectangle" style="dashed"];
    edge [style="dashed"];
    1->2;
    1->3;
}

3.2 设置箭头形状

当然,箭头的形状也是可以设置的,例如设置成不要箭头:

digraph bin_tree {
    node [shape="circle"];
    edge [arrowhead="none"];
    1->2;
    1->3 ;
}

箭头的形状可以在Arrow Shapes找到,还有很多线条的形状可以设置。

四、设置颜色

设置线条颜色为红色,节点填充色为灰色:

digraph bin_tree {
    node [shape="rectangle" style="dashed,filled" color="gray"];
    edge [style="dashed" color="red"];
    1->2;
    1->3;
}

对node/edge的属性配置是全局生效的,局部生效的方法:

digraph bin_tree {
    node [shape="rectangle" style="dashed,filled" color="gray"];
    edge [style="dashed" color="red"];
    1->2;
    1->3 [color="blue"];
}

设置rgb颜色

edge [style="dashed" color="#ff00ff"];