我是如何让我的 Zsh 像丝般顺滑的

TODO: 这篇文章已经过时。我后来又做了许多的改动,有兴趣的可以直接去看我的 dotfiles 。等我有空了会更新这篇文章。 引言 我用 Zsh 到现在大约三年了,从抛弃 Oh My Zsh 自行配置开始也有大约两年了,零零散散积攒了不少我觉得值得分享的东西,因此有了这篇 blog。另外,考虑到我的朋友大多对 Zsh 的使用比较轻度,写 Bash 居多,这篇 blog 也会顺便讲解一些零碎的 Zsh 的小知识。 我的配置文件全都放在 QuarticCat/dotfiles,有兴趣的可以去翻阅一下。值得一提的是,管理这个 repo 所用的 dotfile manager 也是我用 Zsh 写的。 尽管在 repo 里我把所有 Zsh 的配置放到了一个文件夹里,但它们在我系统中是分开的,结构大致上是这样: - ~ - .zshenv - $XDG_CONFIG_HOME/zsh - all-other-files 因为文件太多,全放在 home 目录会很乱,因此我遵循 XDG Base Directory 把大部分文件转移到了 $XDG_CONFIG_HOME,只有 .zshenv 必须得放在 home 目录。下面我就一个一个文件介绍一下我都配置了些什么。 .zshenv 关于 Zsh 几个配置文件的区别可以看这篇 blog 。在这里我主要放一些环境变量,这样它们对 DE 启动的 GUI 程序也生效(似乎是因为 SDDM 会自动 source 这个文件)。其中很多变量也可以用 ~/.pam_environment、~/.xsession 等配置文件管理,但它们都都没有 Zsh 写得舒服,而且合在一起修改也比较方便(当然它们在效果上有轻微的差异)。...

2022-05-30 · QuarticCat

RVV 也许没有想象的那么好

本文已过时,请移步英文版。

2022-02-27 · QuarticCat

C++:异质查找(heterogeneous lookup)

太久没更新博文了,水一篇凑数 从 String View 说起 C 风格的字符串常常需要自己记录长度、管理生命周期,涉及长度变化时更是比较麻烦。于是在 C++ 中我们有了 std::string,并且有了与之配套的一系列函数,比如 std::stoi,对应 C 里面的 atoi。这个函数的声明如下: int stoi(const std::string& str, std::size_t* pos = 0, int base = 10); 这个接口接受一个 const std::string&,乍看或许是理所应当的:我是 C++ 函数,我需要读取字符串,但是我不需要修改它。实际上,C++ 中有很多接受 const std::string& 的函数,然而很遗憾,这个设计是失败的。 考虑这样一种情况,我们需要读取一个 std::string 里的子串,但我们不需要修改它。比如对一个拥有很多数字的字符串进行连续 parse ,或者在某个大文本里找到某个模板再对匹配结果进行进一步筛选。这种情况下,这些接受 const std::string& 的函数就变得不好用了,因为子串不是一个 std::string 对象。我们往往不得不把子串复制到一个新的 std::string 对象里,造成了额外的开销。 类似的常见情况还有,我们接收到了一个 C 风格的字符串,以 char* str + size_t len 的形式,而我们希望能在这个字符串上使用各种 C++ 函数的功能。比如跟 C 接口交互的时候,或者用 buffer 从别的地方接收字符串数据的时候。 所以要怎么解决呢?我们可以采用 C 风格的接口,即 char* str + size_t len,或者采用迭代器风格的接口,即 char* begin + char* end。而将这两个参数合起来,我们就得到了 std::string_view ——只读字符串接口的正确答案。它不仅比 const std::string& 更泛用,而且在没有发生 SSO 的情况下,它还比 const std::string& 减少了一次指针跳转。...

2021-10-16 · QuarticCat

C++ 名称查找的又一个恶心设计

众所周知,C++ 的名称查找一直以来都很反直觉。比如臭名昭著的 ADL,这玩意经常在意想不到的地方恶心到你,还往往难以排查。具体表现为你在当前的命名空间里自己定义了一个函数,结果在调用它的时候编译器却找到了十万八千里外的另一个同名函数,即使你没有在当前命名空间引入该函数。下面是两个很常见的例子: namespace fuckadl { struct Fuck {}; void foo(Fuck) { puts("not mine"); } } // namespace fuckadl // case 1 void foo(fuckadl::Fuck) { puts("mine"); } // // case 2 // template<class T> // void foo(T) { puts("mine"); } int main() { foo(fuckadl::Fuck{}); } 对于 case 1 ,你会得到一个编译错误。编译器告诉你不知道该选择哪个 foo 函数。而对于 case 2,你甚至一个警告都不会得到,编译器自动就选择了 fuckadl::foo 。假如是不知道这个 feature 的 C++ 新人,怕是 debug 一天也找不到哪里出了问题。 这种设计直接破坏了命名空间的封装意义,要知道无数的 header only 库都在用命名空间来对外隐藏内部符号(谁让 C++ 的模块机制拖延了这么久呢),你都没法知道什么时候就和别人函数名字撞上了。 最近发现了 C++ 名称查找的又一个恶心设计。来请出我们的主角,C++ Standard Draft N3337 10....

2021-06-23 · QuarticCat

C++:把字符串编码进类型里

又到了我第 114514 喜欢的类型体操环节。 这玩意有什么用 非要举一个具有实用意义的场景的话,PEGTL 是我能想到的一个很好的例子。这是一个 parser combinator 库,它使用类型来组合 parser ,比如这样: struct separater: star<one<' ', '\t', '\r', '\n'>> {}; 那么要 parse 一段字符串的时候当然就要把字符串信息编码进类型里面了。 我在自己的玩具 parser combinator 库里也用了这种方法,只不过我写法上使用变量来组合。 除此之外,著名的 fmt 库也用到了这个东西来进行大量的编译期字符串操作。但它为了兼容性,实现方式都比较原始,而且重复实现了大量标准库中后来加入或者被标记为 constexpr 的东西,也许我有时间会用 C++20 实现一个简易版的 fmt 库。 不就是个 char... 吗 看了上面的例子,肯定有人会这么想。但其实再想想,我们可以有好几种方案在模板参数里接收一个字符串: template<const char* /* , size_t N */> struct Str1 {}; template<char...> struct Str2 {}; // Need C++20, will explain later template<SomeUserDefinedString> struct Str3 {}; 为什么这里不写数组类型呢,因为在模板参数里,数组类型会被自动替换成指针,和第一种方案实际上是一样的。总之就这么三个,我们一个一个来讲。 方案一 template<const char* /* , size_t N */> struct Str1 {}; 把它放在第一个讲是因为它是最废物的,只要一写就发现:...

2021-03-05 · QuarticCat