Clang-Format:代码格式化神器(文后有升华)

· · 科技·工程

总述

本文将从 Rust 格式化工具 rustfmtcargo-fmt 引入,探索 C++ 中最常用的代码格式化工具:clang-format。随后对比一些常见的预定义格式,尝试改进预定义格式。最后再尝试参照 Rust Style Guide 来完全手动设置一个格式。

:::info[提示]{open} 本文中出现的所有源代码都将注明来源,且都来源于公开代码(使用开源许可证或放弃版权的代码)。

后文中自设置的 .clang-format 源代码使用 MIT 许可证。 :::

起源:Rust 的灵感

一个月前,我决定重新阅读 Rust 的官方教程 The Rust Programming Language,并在其中的 "Appendix D: Useful Development Tools" 中发现了 Rust 自动格式化的命令行工具:rustfmtcargo-fmt

rustfmt 可以单独格式化一个 Rust 源代码文件。其 CLI 命令为:

rustfmt main.rs  # 格式化 main.rs

而在一个 Cargo 项目中,则可以使用 cargo-fmt 对整个 Cargo 项目的源代码进行格式化。其 CLI 命令为:

cargo fmt  # 格式化 Cargo 项目

这两个工具都是内置于 Rust 工具链中的,效果也非常好。我们来做一个对比:

:::success[格式化前的代码] 代码来源:P2894 提交记录 R277942507

提交记录中的源代码是格式化后代码,这里对代码进行了还原。

use std::io;

struct Context {
    input: io::Stdin,
    len: usize,
    ope_cnt: usize,
}

#[derive(Clone, Copy, Default)]
enum LazyTag {
    #[default]
    None,
    Occupy,
    Clear,
}

#[derive(Clone, Default)]
struct TreeNode {
    left_max: usize,
    right_max: usize,
    max_len: usize,
    len: usize,
    lazy_tag: LazyTag,
}

struct SegmentTree {
    tree: Vec<TreeNode>,
}

fn main() {
    let mut ctx = Context::new(io::stdin());

    read_data(&mut ctx);

    let mut seg_tree = SegmentTree::new(ctx.len);
    seg_tree.build(1, 1, ctx.len);

    for _ in 0..ctx.ope_cnt {
        let mut line = String::new();
        ctx.input.read_line(&mut line).unwrap();
        let mut iter = line.split_whitespace();

        let ope: i32 = iter.next().unwrap().parse().unwrap();
        let x: usize = iter.next().unwrap().parse().unwrap();

        if ope == 1 {
            if seg_tree.tree[1].max_len < x {
                println!("0");
            } else {
                let pos = seg_tree.query(1, 1, ctx.len, x);
                println!("{pos}");
                seg_tree.update(1, 1, ctx.len, pos, pos + x - 1, LazyTag::Occupy);
            }
        } else if ope == 2 {
            let y: usize = iter.next().unwrap().parse().unwrap();
            seg_tree.update(1, 1, ctx.len, x, x + y - 1, LazyTag::Clear);
        }
    }
}

fn read_data(ctx: &mut Context) {
    let mut line = String::new();
    ctx.input.read_line(&mut line).unwrap();
    let mut iter = line.split_whitespace();

    ctx.len = iter.next().unwrap().parse().unwrap();
    ctx.ope_cnt = iter.next().unwrap().parse().unwrap();
}

impl Context {
    fn new(input: io::Stdin) -> Self {
        Self {
            input,
            len: Default::default(),
            ope_cnt: Default::default(),
        }
    }
}

impl SegmentTree {
    fn new(len: usize) -> Self {
        Self {
            tree: vec![Default::default(); len * 4 + 1],
        }
    }

    #[inline]
    fn left_child(node: usize) -> usize {
        node * 2
    }

    #[inline]
    fn right_child(node: usize) -> usize {
        node * 2 + 1
    }

    #[inline]
    fn midpoint(left: usize, right: usize) -> usize {
        (left + right) / 2
    }

    fn push_up(&mut self, node: usize) {
        let lc = Self::left_child(node);
        let rc = Self::right_child(node);

        self.tree[node].left_max = if self.tree[lc].left_max == self.tree[lc].len {
            self.tree[lc].len + self.tree[rc].left_max
        } else {
            self.tree[lc].left_max
        };
        self.tree[node].right_max = if self.tree[rc].right_max == self.tree[rc].len {
            self.tree[rc].len + self.tree[lc].right_max
        } else {
            self.tree[rc].right_max
        };
        self.tree[node].max_len = self.tree[lc].max_len.max(self.tree[rc].max_len).max(self.tree[lc].right_max + self.tree[rc].left_max);
    }

    fn push_down(&mut self, node: usize) {
        let lc = Self::left_child(node);
        let rc = Self::right_child(node);

        match self.tree[node].lazy_tag {
            LazyTag::None => (),
            LazyTag::Occupy => {
                self.tree[lc].left_max = 0;
                self.tree[lc].right_max = 0;
                self.tree[lc].max_len = 0;
                self.tree[rc].left_max = 0;
                self.tree[rc].right_max = 0;
                self.tree[rc].max_len = 0;
                self.tree[lc].lazy_tag = self.tree[node].lazy_tag;
                self.tree[rc].lazy_tag = self.tree[node].lazy_tag;
            }
            LazyTag::Clear => {
                self.tree[lc].left_max = self.tree[lc].len;
                self.tree[lc].right_max = self.tree[lc].len;
                self.tree[lc].max_len = self.tree[lc].len;
                self.tree[rc].left_max = self.tree[rc].len;
                self.tree[rc].right_max = self.tree[rc].len;
                self.tree[rc].max_len = self.tree[rc].len;
                self.tree[lc].lazy_tag = self.tree[node].lazy_tag;
                self.tree[rc].lazy_tag = self.tree[node].lazy_tag;
            }
        }

        self.tree[node].lazy_tag = LazyTag::None;
    }

    fn build(&mut self, node: usize, left: usize, right: usize) {
        self.tree[node].len = right - left + 1;

        if left == right {
            self.tree[node].max_len = 1;
            self.tree[node].left_max = 1;
            self.tree[node].right_max = 1;
            return;
        }

        let mid = Self::midpoint(left, right);

        self.build(Self::left_child(node), left, mid);
        self.build(Self::right_child(node), mid + 1, right);

        self.push_up(node);
    }

    fn query(&mut self, node: usize, left: usize, right: usize, target: usize) -> usize {
        if left == right {
            return left;
        }

        let mid = Self::midpoint(left, right);
        self.push_down(node);

        if self.tree[Self::left_child(node)].max_len >= target {
            self.query(Self::left_child(node), left, mid, target)
        } else if self.tree[Self::left_child(node)].right_max + self.tree[Self::right_child(node)].left_max >= target {
            mid - self.tree[Self::left_child(node)].right_max + 1
        } else {
            self.query(Self::right_child(node), mid + 1, right, target)
        }
    }

    fn update(&mut self, node: usize, left: usize, right: usize, query_left: usize, query_right: usize, tag: LazyTag) {
        if query_left <= left && right <= query_right {
            match tag {
                LazyTag::None => unreachable!(),
                LazyTag::Clear => {
                    self.tree[node].left_max = self.tree[node].len;
                    self.tree[node].right_max = self.tree[node].len;
                    self.tree[node].max_len = self.tree[node].len;
                }
                LazyTag::Occupy => {
                    self.tree[node].left_max = 0;
                    self.tree[node].right_max = 0;
                    self.tree[node].max_len = 0;
                }
            }
            self.tree[node].lazy_tag = tag;
            return;
        }

        let mid = Self::midpoint(left, right);
        self.push_down(node);

        if query_left <= mid {
            self.update(Self::left_child(node), left, mid, query_left, query_right, tag);
        }
        if query_right > mid {
            self.update(Self::right_child(node), mid + 1, right, query_left, query_right, tag);
        }

        self.push_up(node);
    }
}

:::

:::success[格式化后的代码]

use std::io;

struct Context {
    input: io::Stdin,
    len: usize,
    ope_cnt: usize,
}

#[derive(Clone, Copy, Default)]
enum LazyTag {
    #[default]
    None,
    Occupy,
    Clear,
}

#[derive(Clone, Default)]
struct TreeNode {
    left_max: usize,
    right_max: usize,
    max_len: usize,
    len: usize,
    lazy_tag: LazyTag,
}

struct SegmentTree {
    tree: Vec<TreeNode>,
}

fn main() {
    let mut ctx = Context::new(io::stdin());

    read_data(&mut ctx);

    let mut seg_tree = SegmentTree::new(ctx.len);
    seg_tree.build(1, 1, ctx.len);

    for _ in 0..ctx.ope_cnt {
        let mut line = String::new();
        ctx.input.read_line(&mut line).unwrap();
        let mut iter = line.split_whitespace();

        let ope: i32 = iter.next().unwrap().parse().unwrap();
        let x: usize = iter.next().unwrap().parse().unwrap();

        if ope == 1 {
            if seg_tree.tree[1].max_len < x {
                println!("0");
            } else {
                let pos = seg_tree.query(1, 1, ctx.len, x);
                println!("{pos}");
                seg_tree.update(1, 1, ctx.len, pos, pos + x - 1, LazyTag::Occupy);
            }
        } else if ope == 2 {
            let y: usize = iter.next().unwrap().parse().unwrap();
            seg_tree.update(1, 1, ctx.len, x, x + y - 1, LazyTag::Clear);
        }
    }
}

fn read_data(ctx: &mut Context) {
    let mut line = String::new();
    ctx.input.read_line(&mut line).unwrap();
    let mut iter = line.split_whitespace();

    ctx.len = iter.next().unwrap().parse().unwrap();
    ctx.ope_cnt = iter.next().unwrap().parse().unwrap();
}

impl Context {
    fn new(input: io::Stdin) -> Self {
        Self {
            input,
            len: Default::default(),
            ope_cnt: Default::default(),
        }
    }
}

impl SegmentTree {
    fn new(len: usize) -> Self {
        Self {
            tree: vec![Default::default(); len * 4 + 1],
        }
    }

    #[inline]
    fn left_child(node: usize) -> usize {
        node * 2
    }

    #[inline]
    fn right_child(node: usize) -> usize {
        node * 2 + 1
    }

    #[inline]
    fn midpoint(left: usize, right: usize) -> usize {
        (left + right) / 2
    }

    fn push_up(&mut self, node: usize) {
        let lc = Self::left_child(node);
        let rc = Self::right_child(node);

        self.tree[node].left_max = if self.tree[lc].left_max == self.tree[lc].len {
            self.tree[lc].len + self.tree[rc].left_max
        } else {
            self.tree[lc].left_max
        };
        self.tree[node].right_max = if self.tree[rc].right_max == self.tree[rc].len {
            self.tree[rc].len + self.tree[lc].right_max
        } else {
            self.tree[rc].right_max
        };
        self.tree[node].max_len = self.tree[lc]
            .max_len
            .max(self.tree[rc].max_len)
            .max(self.tree[lc].right_max + self.tree[rc].left_max);
    }

    fn push_down(&mut self, node: usize) {
        let lc = Self::left_child(node);
        let rc = Self::right_child(node);

        match self.tree[node].lazy_tag {
            LazyTag::None => (),
            LazyTag::Occupy => {
                self.tree[lc].left_max = 0;
                self.tree[lc].right_max = 0;
                self.tree[lc].max_len = 0;
                self.tree[rc].left_max = 0;
                self.tree[rc].right_max = 0;
                self.tree[rc].max_len = 0;
                self.tree[lc].lazy_tag = self.tree[node].lazy_tag;
                self.tree[rc].lazy_tag = self.tree[node].lazy_tag;
            }
            LazyTag::Clear => {
                self.tree[lc].left_max = self.tree[lc].len;
                self.tree[lc].right_max = self.tree[lc].len;
                self.tree[lc].max_len = self.tree[lc].len;
                self.tree[rc].left_max = self.tree[rc].len;
                self.tree[rc].right_max = self.tree[rc].len;
                self.tree[rc].max_len = self.tree[rc].len;
                self.tree[lc].lazy_tag = self.tree[node].lazy_tag;
                self.tree[rc].lazy_tag = self.tree[node].lazy_tag;
            }
        }

        self.tree[node].lazy_tag = LazyTag::None;
    }

    fn build(&mut self, node: usize, left: usize, right: usize) {
        self.tree[node].len = right - left + 1;

        if left == right {
            self.tree[node].max_len = 1;
            self.tree[node].left_max = 1;
            self.tree[node].right_max = 1;
            return;
        }

        let mid = Self::midpoint(left, right);

        self.build(Self::left_child(node), left, mid);
        self.build(Self::right_child(node), mid + 1, right);

        self.push_up(node);
    }

    fn query(&mut self, node: usize, left: usize, right: usize, target: usize) -> usize {
        if left == right {
            return left;
        }

        let mid = Self::midpoint(left, right);
        self.push_down(node);

        if self.tree[Self::left_child(node)].max_len >= target {
            self.query(Self::left_child(node), left, mid, target)
        } else if self.tree[Self::left_child(node)].right_max
            + self.tree[Self::right_child(node)].left_max
            >= target
        {
            mid - self.tree[Self::left_child(node)].right_max + 1
        } else {
            self.query(Self::right_child(node), mid + 1, right, target)
        }
    }

    fn update(
        &mut self,
        node: usize,
        left: usize,
        right: usize,
        query_left: usize,
        query_right: usize,
        tag: LazyTag,
    ) {
        if query_left <= left && right <= query_right {
            match tag {
                LazyTag::None => unreachable!(),
                LazyTag::Clear => {
                    self.tree[node].left_max = self.tree[node].len;
                    self.tree[node].right_max = self.tree[node].len;
                    self.tree[node].max_len = self.tree[node].len;
                }
                LazyTag::Occupy => {
                    self.tree[node].left_max = 0;
                    self.tree[node].right_max = 0;
                    self.tree[node].max_len = 0;
                }
            }
            self.tree[node].lazy_tag = tag;
            return;
        }

        let mid = Self::midpoint(left, right);
        self.push_down(node);

        if query_left <= mid {
            self.update(
                Self::left_child(node),
                left,
                mid,
                query_left,
                query_right,
                tag,
            );
        }
        if query_right > mid {
            self.update(
                Self::right_child(node),
                mid + 1,
                right,
                query_left,
                query_right,
                tag,
            );
        }

        self.push_up(node);
    }
}

:::

由于我本身就习惯于遵照各个编程语言的 Style Guide 进行编码,所以二者差别不是很大。但是你可以看到一些过长行被按照某种方式折行,因为我没有手动折行的习惯。后面演示 C++ 代码格式化时效果可能显著一点。

寻觅与探索

既然 Rust 工具链中有这样的自动格式化工具,那么 C++ 作为一门尚具有生机的现代编程语言,也应该有相应的格式化工具吧?

于是,我尝试从我最喜欢使用的 Clang LLVM 工具链中寻找。最终,我找到了 clang-format

安装

在 Windows 系统中,你可以到 Clang LLVM 官网找到 clang-format 的安装包,直接安装即可。当然,如果你之前安装过 Clang 工具链,可以检查一下是不是已经配套安装好了。

在 Linux 系统中,你可以通过软件包管理器来安装 clang-format。以基于 Debian 的 Ubuntu 为例:

sudo apt update; sudo apt upgrade; sudo apt install clang-format

在 macOS 系统中,Clang 是默认的 C++ 工具链。安装 LLVM 工具链时会附带安装 clang-format 工具。可以使用 Homebrew 安装 LLVM 工具链,前提是你启用了 Homebrew:

brew install llvm

通过下面的命令来检查是否安装成功:

clang-format --version

如果看到正确的版本号,则安装成功;否则,应该检查安装过程中是否出现问题。

基本使用

通过下面的命令可以让 clang-format 格式化一个源代码,并将格式化后的代码输出到标准输出:

clang-format main.cpp

通过重定向标准输出,可以将格式化后的代码写入到另一个文件或者覆盖原文件:

clang-format main.cpp > main-format.cpp
clang-format main.cpp > main.cpp

当然,如果是想直接修改源文件,可以直接使用 -i 选项:

clang-format -i main.cpp

尝试格式化

我们来找一份代码来格式化一下。

:::success[格式化前的代码] 代码来源:P2894 提交记录 R277935428,用户 sunjx123456 的代码。

#include<bits/stdc++.h>
using namespace std;
#define int long long 
const int N = 100002, M = 1010;
const int inf = 1e18;
#define ec " "
int n, m;
int s, t;
const int mod = 1e9 + 7;
#define ls (u<<1)
#define rs (ls+1)
int a[N];
#define mid (l+r>>1)
struct node
{
    int w, wl, wr;
};
int lzy[N << 2];
node tree[N << 2];
void pushup(int u,int l,int r)
{
    tree[u].w = max({ tree[ls].w,tree[rs].w,tree[ls].wr + tree[rs].wl });
    tree[u].wl = tree[ls].wl;
    tree[u].wr = tree[rs].wr;
    if (tree[u].wl == mid-l+1)tree[u].wl += tree[rs].wl;
    if (tree[u].wr == r - mid)tree[u].wr += tree[ls].wr;
}
void build(int u, int l, int r)
{
    lzy[u] = -1;
    if (l == r)
    {
        tree[u] = { 1,1,1}; return;
    }
    build(ls, l, mid); build(rs, mid + 1, r);
    pushup(u, l, r);
}
void maketag(int u, int len, int op)
{
    tree[u].w = tree[u].wl = tree[u].wr = op * len;
    lzy[u] = op;
}
void pushdown(int u, int l, int r)
{
    if (lzy[u] != -1)
    {
        maketag(ls, mid - l + 1, lzy[u]);
        maketag(rs, r - mid, lzy[u]);
        lzy[u] = -1;
    }
}
int query(int u, int l, int r,int x)
{
    if (l == r)return l;
    pushdown(u, l, r);
    if (tree[ls].w >= x)return query(ls, l, mid, x);
    if (tree[ls].wr + tree[rs].wl >= x)return mid - tree[ls].wr + 1;
    return query(rs, mid + 1, r, x);
}
void update(int u, int l, int r, int ql, int qr, int op)
{
    if (ql <= l && qr >= r)
    {
        maketag(u, r - l + 1, op); return;
    }
    pushdown(u, l, r);
    if (ql <= mid)update(ls, l, mid, ql, qr, op);
    if (qr >= mid + 1)update(rs, mid + 1, r, ql, qr, op);
    pushup(u, l, r);

}
signed main()
{
    ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
    cin >> n;
    build(1, 1, n);
    cin >> m;
    while (m--)
    {
        int op; cin >> op; int x, y;
        if (op == 1)
        {
             cin >> x;
             if (tree[1].w >= x)
             {
                 int ans = query(1, 1, n, x);
                 cout << ans << endl;
                 update(1, 1, n, ans, ans + x - 1, 0);
             }
             else cout << 0 << endl;
        }
        else
        {
            cin >> x >> y;
            update(1, 1, n, x, y+x-1, 1);
        }
    }
    return 0;
}

:::

:::success[格式化后的代码] 格式化命令:

clang-format -i main.cpp

格式化后的代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100002, M = 1010;
const int inf = 1e18;
#define ec " "
int n, m;
int s, t;
const int mod = 1e9 + 7;
#define ls (u << 1)
#define rs (ls + 1)
int a[N];
#define mid (l + r >> 1)
struct node {
  int w, wl, wr;
};
int lzy[N << 2];
node tree[N << 2];
void pushup(int u, int l, int r) {
  tree[u].w = max({tree[ls].w, tree[rs].w, tree[ls].wr + tree[rs].wl});
  tree[u].wl = tree[ls].wl;
  tree[u].wr = tree[rs].wr;
  if (tree[u].wl == mid - l + 1)
    tree[u].wl += tree[rs].wl;
  if (tree[u].wr == r - mid)
    tree[u].wr += tree[ls].wr;
}
void build(int u, int l, int r) {
  lzy[u] = -1;
  if (l == r) {
    tree[u] = {1, 1, 1};
    return;
  }
  build(ls, l, mid);
  build(rs, mid + 1, r);
  pushup(u, l, r);
}
void maketag(int u, int len, int op) {
  tree[u].w = tree[u].wl = tree[u].wr = op * len;
  lzy[u] = op;
}
void pushdown(int u, int l, int r) {
  if (lzy[u] != -1) {
    maketag(ls, mid - l + 1, lzy[u]);
    maketag(rs, r - mid, lzy[u]);
    lzy[u] = -1;
  }
}
int query(int u, int l, int r, int x) {
  if (l == r)
    return l;
  pushdown(u, l, r);
  if (tree[ls].w >= x)
    return query(ls, l, mid, x);
  if (tree[ls].wr + tree[rs].wl >= x)
    return mid - tree[ls].wr + 1;
  return query(rs, mid + 1, r, x);
}
void update(int u, int l, int r, int ql, int qr, int op) {
  if (ql <= l && qr >= r) {
    maketag(u, r - l + 1, op);
    return;
  }
  pushdown(u, l, r);
  if (ql <= mid)
    update(ls, l, mid, ql, qr, op);
  if (qr >= mid + 1)
    update(rs, mid + 1, r, ql, qr, op);
  pushup(u, l, r);
}
signed main() {
  ios::sync_with_stdio(0);
  cin.tie(0), cout.tie(0);
  cin >> n;
  build(1, 1, n);
  cin >> m;
  while (m--) {
    int op;
    cin >> op;
    int x, y;
    if (op == 1) {
      cin >> x;
      if (tree[1].w >= x) {
        int ans = query(1, 1, n, x);
        cout << ans << endl;
        update(1, 1, n, ans, ans + x - 1, 0);
      } else
        cout << 0 << endl;
    } else {
      cin >> x >> y;
      update(1, 1, n, x, y + x - 1, 1);
    }
  }
  return 0;
}

:::

自定义格式探索

基于预定义格式进行修改

如果你仔细看了一下上面格式化之后的代码,你会觉得,这也不怎么好看嘛。是的,clang-format 默认使用 LLVM Style 进行格式化,而其风格就是这样的:尤其是 2 空格缩进。

如何对我们不满意的地方进行修改呢?比如修改一下缩进宽度?

当然可以!不过在这之前,我们要先来看一看 clang-format 自带的预定义格式。

预定义格式

一下是 clang-format 内置的常见预定义格式:

风格 对应规范 典型特征 推荐场景 备注
LLVM LLVM Coding Standards(LLVM 编码标准) 缩进通常是 2 空格、命名空间不缩进、访问修饰符(publicprivate 等)不缩进 参与 LLVM 项目开发,或偏好其紧凑、节省横向空间风格的团队。 clang-format 的默认风格。
Google Google C++ Style Guide(Google C++ 风格指南) 缩进为 2 空格、指针和引用的 */& 紧贴类型(如 int* ptr)、访问修饰符不缩进 大多数通用 C++ 项目,是除 LLVM 外最广泛使用的风格。 非常适合绝大部分项目,本身也是许多其他风格的基础。
Chromium Chromium C++ Style Guide(Chromium C++ 风格指南) 缩进为 2 空格、与 Google 风格极其相似,但针对 Chromium 的特定宏和命名习惯有优化 开发 Chromium 浏览器本身或其相关拓展、插件。 与 Google 风格同源,但为了 Chromium 项目做了微调。
Mozilla Mozilla Coding Style(Mozilla 编码风格) 访问修饰符缩进一级、指针和引用的 */& 紧贴变量名(如 int *ptr 参与 Firefox 浏览器开发。 注意:Mozilla 风格已被官方标记为已废弃(deprecated),仅供遗留项目支持。
WebKit WebKit Code Style(WebKit 编码风格) 缩进为 4 空格、大括号使用 K&R 风格 参与 WebKit 浏览器引擎或相关项目开发。 一个经典的风格,对可读性有独到的见解。
Microsoft Microsoft C++ Core Guidelines(Microsoft C++ 核心指南) 遵循 Visual Studio 的默认格式化设置,与许多 Windows 和游戏开发团队的风格一致。 开发 Windows 平台软件、使用 Microsoft 工具链的团队。 clang-format 9 开始引入。
GNU GNU Coding Standards(GNU 编码标准) 缩进为 2 空格、大括号使用 Allman 风格、函数定义和调用风格独特 维护或参与 GNU 项目(如 GCC、GDB)。 风格较为古典,与项目历史紧密相关。从 clang-format 11 开始引入。

表格由 DeepSeek 整理,人工审核并修改。

:::info[猜猜为什么 Mozilla 的 C++ 风格被弃用了] 我的直观想法是:Firefox 的浏览器内核不是开始用 Rust 重写了吗?Rust 有官方指定的全领域通用的 Rust Style Guide 呢。 :::

如果仔细阅读这些风格的文档,或者自己尝试格式化一下,就会发现一件事情:这些代码风格都注重实用而非美观。所以很多代码风格我们自己看起来也并不觉得特别好看。所以,我们就会想要在借用别人已经有的风格的基础上,调整一些内容(如缩进宽度),来符合我们自己的审美。那么,应该怎么做呢?

.clang-format 配置文件

通过查找相关资料,我们可以发现,clang-format 工具会使用一个叫做 .clang-format(注意这里有一个点)的 YAML 格式的配置文件来进行格式化。因此,我们也可以通过这个来设置我们的格式。

我们以最常用的 Google 风格为基础,调整缩进宽度为 4,并设置列宽限制为 100。.clang-format 的内容应该如下:

# .clang-format
BasedOnStyle: Google  # 基于 Google 风格
IndentWidth: 4  # 缩进宽度为 4
ColumnLimit: 100  # 列宽限制为 100

将这份代码保存在你要运行 clang-format 的工作目录或者工作目录的若干层父级目录中(一般我们放在项目根目录)就可以使用。

用这份配置再来格式化一下上面的代码试试:

:::success[新的格式化后的代码] 格式化命令:

clang-format -i main.cpp

格式化后的代码:

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N = 100002, M = 1010;
const int inf = 1e18;
#define ec " "
int n, m;
int s, t;
const int mod = 1e9 + 7;
#define ls (u << 1)
#define rs (ls + 1)
int a[N];
#define mid (l + r >> 1)
struct node {
    int w, wl, wr;
};
int lzy[N << 2];
node tree[N << 2];
void pushup(int u, int l, int r) {
    tree[u].w = max({tree[ls].w, tree[rs].w, tree[ls].wr + tree[rs].wl});
    tree[u].wl = tree[ls].wl;
    tree[u].wr = tree[rs].wr;
    if (tree[u].wl == mid - l + 1) tree[u].wl += tree[rs].wl;
    if (tree[u].wr == r - mid) tree[u].wr += tree[ls].wr;
}
void build(int u, int l, int r) {
    lzy[u] = -1;
    if (l == r) {
        tree[u] = {1, 1, 1};
        return;
    }
    build(ls, l, mid);
    build(rs, mid + 1, r);
    pushup(u, l, r);
}
void maketag(int u, int len, int op) {
    tree[u].w = tree[u].wl = tree[u].wr = op * len;
    lzy[u] = op;
}
void pushdown(int u, int l, int r) {
    if (lzy[u] != -1) {
        maketag(ls, mid - l + 1, lzy[u]);
        maketag(rs, r - mid, lzy[u]);
        lzy[u] = -1;
    }
}
int query(int u, int l, int r, int x) {
    if (l == r) return l;
    pushdown(u, l, r);
    if (tree[ls].w >= x) return query(ls, l, mid, x);
    if (tree[ls].wr + tree[rs].wl >= x) return mid - tree[ls].wr + 1;
    return query(rs, mid + 1, r, x);
}
void update(int u, int l, int r, int ql, int qr, int op) {
    if (ql <= l && qr >= r) {
        maketag(u, r - l + 1, op);
        return;
    }
    pushdown(u, l, r);
    if (ql <= mid) update(ls, l, mid, ql, qr, op);
    if (qr >= mid + 1) update(rs, mid + 1, r, ql, qr, op);
    pushup(u, l, r);
}
signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> n;
    build(1, 1, n);
    cin >> m;
    while (m--) {
        int op;
        cin >> op;
        int x, y;
        if (op == 1) {
            cin >> x;
            if (tree[1].w >= x) {
                int ans = query(1, 1, n, x);
                cout << ans << endl;
                update(1, 1, n, ans, ans + x - 1, 0);
            } else
                cout << 0 << endl;
        } else {
            cin >> x >> y;
            update(1, 1, n, x, y + x - 1, 1);
        }
    }
    return 0;
}

:::

是不是美观多了?

但是,还不够。

完全自设格式

看看这段格式化后的代码:

:::success[代码] 代码来源:P2894 提交记录 R277955198

#include <algorithm>
#include <cstddef>
#include <iostream>
#include <vector>

using size_t = std::size_t;

enum class LazyTag {
    None,
    Occupy,
    Clear,
};

struct TreeNode {
    size_t left_max = 0;
    size_t right_max = 0;
    size_t max_len = 0;
    size_t len = 0;
    LazyTag lazy_tag = LazyTag::None;

    TreeNode() = default;
};

class SegmentTree {
   public:
    std::vector<TreeNode> tree;

    SegmentTree(size_t len);
    inline static size_t left_child(size_t node);
    inline static size_t right_child(size_t node);
    inline static size_t midpoint(size_t left, size_t right);
    void push_up(size_t node);
    void push_down(size_t node);
    void build(size_t node, size_t left, size_t right);
    size_t query(size_t node, size_t left, size_t right, size_t target);
    void update(size_t node, size_t left, size_t right, size_t query_left, size_t query_right,
                LazyTag tag);
};

void read_data();

size_t len;
size_t ope_cnt;

int main() {
    read_data();

    SegmentTree seg_tree(len);
    seg_tree.build(1, 1, len);

    for (size_t i = 0; i < ope_cnt; i++) {
        int ope;
        size_t x;
        std::cin >> ope >> x;

        if (ope == 1) {
            if (seg_tree.tree[1].max_len < x) {
                std::cout << "0" << std::endl;
            } else {
                size_t pos = seg_tree.query(1, 1, len, x);
                std::cout << pos << std::endl;
                seg_tree.update(1, 1, len, pos, pos + x - 1, LazyTag::Occupy);
            }
        } else if (ope == 2) {
            size_t y;
            std::cin >> y;
            seg_tree.update(1, 1, len, x, x + y - 1, LazyTag::Clear);
        }
    }
}

void read_data() { std::cin >> len >> ope_cnt; }

SegmentTree::SegmentTree(size_t len) : tree(len * 4 + 1) {}

inline size_t SegmentTree::left_child(size_t node) { return node * 2; }

inline size_t SegmentTree::right_child(size_t node) { return node * 2 + 1; }

inline size_t SegmentTree::midpoint(size_t left, size_t right) { return (left + right) / 2; }

void SegmentTree::push_up(size_t node) {
    size_t lc = left_child(node);
    size_t rc = right_child(node);

    tree[node].left_max = (tree[lc].left_max == tree[lc].len) ? (tree[lc].len + tree[rc].left_max)
                                                              : tree[lc].left_max;
    tree[node].right_max = (tree[rc].right_max == tree[rc].len)
                               ? (tree[rc].len + tree[lc].right_max)
                               : tree[rc].right_max;
    tree[node].max_len =
        std::max({tree[lc].max_len, tree[rc].max_len, tree[lc].right_max + tree[rc].left_max});
}

void SegmentTree::push_down(size_t node) {
    size_t lc = left_child(node);
    size_t rc = right_child(node);

    switch (tree[node].lazy_tag) {
        case LazyTag::None:
            break;
        case LazyTag::Occupy:
            tree[lc].left_max = tree[lc].right_max = tree[lc].max_len = 0;
            tree[rc].left_max = tree[rc].right_max = tree[rc].max_len = 0;
            tree[lc].lazy_tag = tree[rc].lazy_tag = tree[node].lazy_tag;
            break;
        case LazyTag::Clear:
            tree[lc].left_max = tree[lc].right_max = tree[lc].max_len = tree[lc].len;
            tree[rc].left_max = tree[rc].right_max = tree[rc].max_len = tree[rc].len;
            tree[lc].lazy_tag = tree[rc].lazy_tag = tree[node].lazy_tag;
            break;
    }

    tree[node].lazy_tag = LazyTag::None;
}

void SegmentTree::build(size_t node, size_t left, size_t right) {
    tree[node].len = right - left + 1;

    if (left == right) {
        tree[node].left_max = tree[node].right_max = tree[node].max_len = 1;
        return;
    }

    size_t mid = midpoint(left, right);

    build(left_child(node), left, mid);
    build(right_child(node), mid + 1, right);

    push_up(node);
}

size_t SegmentTree::query(size_t node, size_t left, size_t right, size_t target) {
    if (left == right) {
        return left;
    }

    size_t mid = midpoint(left, right);
    push_down(node);

    if (tree[left_child(node)].max_len >= target) {
        return query(left_child(node), left, mid, target);
    } else if (tree[left_child(node)].right_max + tree[right_child(node)].left_max >= target) {
        return mid - tree[left_child(node)].right_max + 1;
    } else {
        return query(right_child(node), mid + 1, right, target);
    }
}

void SegmentTree::update(size_t node, size_t left, size_t right, size_t query_left,
                         size_t query_right, LazyTag tag) {
    if (query_left <= left && right <= query_right) {
        switch (tag) {
            case LazyTag::None:
                break;
            case LazyTag::Clear:
                tree[node].max_len = tree[node].left_max = tree[node].right_max = tree[node].len;
                break;
            case LazyTag::Occupy:
                tree[node].max_len = tree[node].left_max = tree[node].right_max = 0;
                break;
        }
        tree[node].lazy_tag = tag;
        return;
    }

    size_t mid = midpoint(left, right);
    push_down(node);

    if (query_left <= mid) {
        update(left_child(node), left, mid, query_left, query_right, tag);
    }
    if (query_right > mid) {
        update(right_child(node), mid + 1, right, query_left, query_right, tag);
    }

    push_up(node);
}

:::

你会发现依照 Google Style 进行格式化后,总是有“莫名其妙”的对齐和折行——为了效率,但不为了美观。再仔细阅读一下 Rust Style Guide,你会发现 Rust Style Guide 中提到的很多优点(如减小 git 更改、避免过度对齐造成维护负担等等)反过来就是这些风格的缺点。而且这些风格本身就不是为了美观而发明的。

那么,如果自己弄一套类似 Rust Style Guide 的风格出来呢?

于是我根据官方的 Clang-Format Style Options 文档,一个一个选项地设置,最终有了这份我自己手写的 .clang-format

.clang-format 使用 clang-format 21 版本,因此更高版本(22、23)的选项没有出现在其中。我还过滤掉了已经弃用的选项和有风险警告的选项。最终成果如下:

:::info[最终的 .clang-format 配置]

# Author: Glacyend
# License: MIT

DisableFormat: false

AccessModifierOffset: -4
AlignAfterOpenBracket: true
AlignArrayOfStructures: Left
AlignConsecutiveAssignments: None
AlignConsecutiveBitFields: None
AlignConsecutiveDeclarations: None
AlignConsecutiveMacros: None
AlignConsecutiveShortCaseStatements:
  Enabled: false
AlignConsecutiveTableGenBreakingDAGArgColons: None
AlignConsecutiveTableGenCondOperatorColons: None
AlignConsecutiveTableGenDefinitionColons: None
AlignEscapedNewlines: LeftWithLastLine
AlignOperands: DontAlign
AlignTrailingComments: Never
AllowAllArgumentsOnNextLine: false
AllowAllParametersOfDeclarationOnNextLine: false
AllowBreakBeforeNoexceptSpecifier: OnlyWithParen
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseExpressionOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortCompoundRequirementOnASingleLine: true
AllowShortEnumsOnASingleLine: false
AllowShortFunctionsOnASingleLine: Empty
AllowShortIfStatementsOnASingleLine: Never
AllowShortLambdasOnASingleLine: All
AllowShortLoopsOnASingleLine: false
AllowShortNamespacesOnASingleLine: false
AlwaysBreakBeforeMultilineStrings: true
BinPackArguments: false
BinPackLongBracedList: false
BinPackParameters: OnePerLine
BitFieldColonSpacing: Both
BraceWrapping:
  AfterCaseLabel: false
  AfterClass: false
  AfterControlStatement: MultiLine
  AfterEnum: false
  AfterFunction: false
  AfterNamespace: false
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  AfterExternBlock: false
  BeforeCatch: false
  BeforeElse: false
  BeforeLambdaBody: false
  BeforeWhile: false
  IndentBraces: false
  SplitEmptyFunction: false
  SplitEmptyRecord: false
  SplitEmptyNamespace: false
BracedInitializerIndentWidth: 4
BreakAdjacentStringLiterals: false
BreakAfterAttributes: Never
BreakAfterJavaFieldAnnotations: false
BreakAfterReturnType: Automatic
BreakArrays: false
BreakBeforeBinaryOperators: NonAssignment
BreakBeforeBraces: Custom
BreakBeforeConceptDeclarations: Always
BreakBeforeInlineASMColon: OnlyMultiline
BreakBeforeTemplateCloser: false
BreakBeforeTernaryOperators: true
BreakBinaryOperations: OnePerLine
BreakConstructorInitializers: AfterColon
BreakFunctionDefinitionParameters: false
BreakInheritanceList: AfterColon
BreakStringLiterals: false
BreakTemplateDeclarations: Yes
ColumnLimit: 100
CompactNamespaces: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
DerivePointerAlignment: false
EmptyLineAfterAccessModifier: Never
EmptyLineBeforeAccessModifier: LogicalBlock
EnumTrailingComma: Leave
FixNamespaceComments: false
IncludeBlocks: Regroup
IndentAccessModifiers: false
IndentCaseBlocks: false
IndentCaseLabels: true
IndentExportBlock: true
IndentExternBlock: Indent
IndentGotoLabels: true
IndentPPDirectives: BeforeHash
IndentRequiresClause: false
IndentWidth: 4
IndentWrappedFunctionNames: true
InsertBraces: false
InsertNewlineAtEOF: true
InsertTrailingCommas: Wrapped
JavaScriptQuotes: Double
JavaScriptWrapImports: true
KeepEmptyLines:
  AtEndOfFile: true
  AtStartOfBlock: false
  AtStartOfFile: false
LambdaBodyIndentation: Signature
LineEnding: LF
MainIncludeChar: Any
MaxEmptyLinesToKeep: 1
NamespaceIndentation: All
ObjCBinPackProtocolList: Never
ObjCBlockIndentWidth: 4
ObjCBreakBeforeNestedBlockParam: true
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: false
PPIndentWidth: 4
PackConstructorInitializers: Never
ReferenceAlignment: Left
ReflowComments: Always
RemoveEmptyLinesInUnwrappedLines: true
RequiresClausePosition: OwnLine
RequiresExpressionIndentation: OuterScope
SeparateDefinitionBlocks: Always
ShortNamespaceLines: 0
SortIncludes:
  Enabled: true
SortJavaStaticImport: Before
SortUsingDeclarations: Lexicographic
SpaceAfterCStyleCast: false
SpaceAfterLogicalNot: false
SpaceAfterOperatorKeyword: false
SpaceAfterTemplateKeyword: true
SpaceAroundPointerQualifiers: Default
SpaceBeforeAssignmentOperators: true
SpaceBeforeCaseColon: false
SpaceBeforeCpp11BracedList: true
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeJsonColon: false
SpaceBeforeParens: Custom
SpaceBeforeParensOptions:
  AfterControlStatements: true
  AfterForeachMacros: true
  AfterFunctionDeclarationName: false
  AfterFunctionDefinitionName: false
  AfterIfMacros: true
  AfterNot: true
  AfterOverloadedOperator: false
  AfterPlacementOperator: false
  AfterRequiresInClause: true
  AfterRequiresInExpression: true
  BeforeNonEmptyParentheses: false
SpaceBeforeRangeBasedForLoopColon: true
SpaceBeforeSquareBrackets: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: Never
SpacesInContainerLiterals: false
SpacesInLineCommentPrefix:
  Minimum: 1
  Maximum: 1
SpacesInParens: Never
SpacesInSquareBrackets: false
Standard: Auto
TabWidth: 4
UseTab: Never
VerilogBreakBetweenInstancePorts: true
WrapNamespaceBodyWithEmptyLines: Never

:::

最终的格式化

我们再使用上面我仿照 Rust Style Guide 设置的配置来格式化一些代码吧!

P1001 题解:虚拟机 + 字节码的“核弹轰蚊子”

题解原文:题解:P1001 A+B Problem

:::success[原始代码]

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned int
#define N 65536
int memory[N];
ull code[N] = {

};
/*
0 Input
1 Output
2 Write
3 Copy
4 Calculate (& | ~ ^ << >>)
5 Calculate (+ - * / %)
6 Goto
7 If-goto
8 x++
9 x--
f Exit
*/
signed main()
{
    for(int i=0;;i=(i+1)%N)
    {
        int op=code[i]>>60;
        if(op==0)       //Input
        {
            int p=code[i]&65535;
            cin >> memory[ (code[i]>>56)&1 ? memory[p] : p ];
        }
        else if(op==1)  //Output
        {
            int p=code[i]&65535, val = memory[ (code[i]>>56)&1 ? memory[p] : p ];
            if((code[i]>>57)&1)
            {
                cout<<char(val&127);
            }
            else
            {
                cout<<val;
            }
        }
        else if(op==2)  //Write
        {
            int p = (code[i]>>32)&65535, val = code[i]&((1ll<<32)-1);
            memory[ (code[i]>>56)&1 ? memory[p] : p ] = val;
        }
        else if(op==3)  //Copy
        {
            int pf = (code[i]>>16)&65535, pt = code[i]&65535;
            int val = memory[ (code[i]>>55)&1 ? memory[pf] : pf ];
            memory[ (code[i]>>56)&1 ? memory[pt] : pt ] = val;
        }
        else if(op==4)  //Calculate (& | ~ ^ << >>)
        {
            int p1 = (code[i]>>32)&65535, p2 = (code[i]>>16)&65535, p3 = code[i]&65535;
            int val1 = memory[ (code[i]>>58)&1 ? memory[p1] : p1 ],
            val2 = memory[ (code[i]>>57)&1 ? memory[p2] : p2 ], val3;
            int op2 = (code[i]>>52)&15;
            if(op2==0)
            {
                val3=val1&val2;
            }
            else if(op2==1)
            {
                val3=val1|val2;
            }
            else if(op2==2)
            {
                val3=~val2;
            }
            else if(op2==3)
            {
                val3=val1^val2;
            }
            else if(op2==4)
            {
                val3=((val2>>6)?0:val1<<val2);
            }
            else
            {
                val3=((val2>>6)?0:val1>>val2);
            }
            memory[ (code[i]>>56)&1 ? memory[p3] : p3 ] = val3;
        }
        else if(op==5)  //Calculate (+ - * / %)
        {
            //想要乘方的建议自己写一个快速幂, 应该是能写的 
            int p1 = (code[i]>>32)&65535, p2 = (code[i]>>16)&65535, p3 = code[i]&65535;
            int val1 = memory[ (code[i]>>58)&1 ? memory[p1] : p1 ],
            val2 = memory[ (code[i]>>57)&1 ? memory[p2] : p2 ], val3;
            int op2 = (code[i]>>52)&15;
            if(op2==0)
            {
                val3=val1+val2;
            }
            else if(op2==1)
            {
                val3=val1-val2;
            }
            else if(op2==2)
            {
                val3=val1*val2;
            }
            else if(op2==3)
            {
                val3=(val2?val1/val2:0);
            }
            else
            {
                val3=(val2?val1%val2:0);
            }
            memory[ (code[i]>>56)&1 ? memory[p3] : p3 ] = val3;
        }
        else if(op==6)  //Goto
        {
            int p=code[i]&65535;
            i = (code[i]>>56)&1 ? memory[p] : p;
            i = (i+N-1)%N;
        }
        else if(op==7)  //If-goto (> < == >= <= !=)
        {
            int p1 = (code[i]>>32)&65535, p2 = (code[i]>>16)&65535, p3 = code[i]&65535;
            int val1 = memory[ (code[i]>>58)&1 ? memory[p1] : p1 ],
            val2 = memory[ (code[i]>>57)&1 ? memory[p2] : p2 ];
            int op2 = (code[i]>>52)&15;
            bool flag;
            if(op2==0)
            {
                flag=(val1>val2);
            }
            else if(op2==1)
            {
                flag=(val1<val2);
            }
            else if(op2==2)
            {
                flag=(val1==val2);
            }
            else if(op2==3)
            {
                flag=(val1>=val2);
            }
            else if(op2==4)
            {
                flag=(val1<=val2);
            }
            else
            {
                flag=(val1!=val2);
            }
            if(flag)
            {
                i = (code[i]>>56)&1 ? memory[p3] : p3;
                i = (i+N-1)%N;
            }
        }
        else if(op==8)  //x++
        {
            int p=code[i]&65535;
            memory[ (code[i]>>56)&1 ? memory[p] : p ]++;
        }
        else if(op==9)  //x--
        {
            int p=code[i]&65535;
            memory[ (code[i]>>56)&1 ? memory[p] : p ]--;
        }
        else if(op==15) //Exit
        {
            break;
        }
        else
        {
            cout<<"\n\nError: Invalid code\n\n";
        }
    }
    return 0;
}

:::

:::success[格式化后代码]

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ull unsigned int
#define N 65536
int memory[N];
ull code[N] = {};

/*
0 Input
1 Output
2 Write
3 Copy
4 Calculate (& | ~ ^ << >>)
5 Calculate (+ - * / %)
6 Goto
7 If-goto
8 x++
9 x--
f Exit
*/
signed main() {
    for (int i = 0;; i = (i + 1) % N) {
        int op = code[i] >> 60;
        if (op == 0)  // Input
        {
            int p = code[i] & 65535;
            cin >> memory[(code[i] >> 56) & 1 ? memory[p] : p];
        } else if (op == 1)  // Output
        {
            int p = code[i] & 65535, val = memory[(code[i] >> 56) & 1 ? memory[p] : p];
            if ((code[i] >> 57) & 1) {
                cout << char(val & 127);
            } else {
                cout << val;
            }
        } else if (op == 2)  // Write
        {
            int p = (code[i] >> 32) & 65535, val = code[i] & ((1ll << 32) - 1);
            memory[(code[i] >> 56) & 1 ? memory[p] : p] = val;
        } else if (op == 3)  // Copy
        {
            int pf = (code[i] >> 16) & 65535, pt = code[i] & 65535;
            int val = memory[(code[i] >> 55) & 1 ? memory[pf] : pf];
            memory[(code[i] >> 56) & 1 ? memory[pt] : pt] = val;
        } else if (op == 4)  // Calculate (& | ~ ^ << >>)
        {
            int p1 = (code[i] >> 32) & 65535, p2 = (code[i] >> 16) & 65535, p3 = code[i] & 65535;
            int val1 = memory[(code[i] >> 58) & 1 ? memory[p1] : p1],
                val2 = memory[(code[i] >> 57) & 1 ? memory[p2] : p2], val3;
            int op2 = (code[i] >> 52) & 15;
            if (op2 == 0) {
                val3 = val1 & val2;
            } else if (op2 == 1) {
                val3 = val1 | val2;
            } else if (op2 == 2) {
                val3 = ~val2;
            } else if (op2 == 3) {
                val3 = val1 ^ val2;
            } else if (op2 == 4) {
                val3 = ((val2 >> 6) ? 0 : val1 << val2);
            } else {
                val3 = ((val2 >> 6) ? 0 : val1 >> val2);
            }
            memory[(code[i] >> 56) & 1 ? memory[p3] : p3] = val3;
        } else if (op == 5)  // Calculate (+ - * / %)
        {
            // 想要乘方的建议自己写一个快速幂, 应该是能写的
            int p1 = (code[i] >> 32) & 65535, p2 = (code[i] >> 16) & 65535, p3 = code[i] & 65535;
            int val1 = memory[(code[i] >> 58) & 1 ? memory[p1] : p1],
                val2 = memory[(code[i] >> 57) & 1 ? memory[p2] : p2], val3;
            int op2 = (code[i] >> 52) & 15;
            if (op2 == 0) {
                val3 = val1 + val2;
            } else if (op2 == 1) {
                val3 = val1 - val2;
            } else if (op2 == 2) {
                val3 = val1 * val2;
            } else if (op2 == 3) {
                val3 = (val2 ? val1 / val2 : 0);
            } else {
                val3 = (val2 ? val1 % val2 : 0);
            }
            memory[(code[i] >> 56) & 1 ? memory[p3] : p3] = val3;
        } else if (op == 6)  // Goto
        {
            int p = code[i] & 65535;
            i = (code[i] >> 56) & 1 ? memory[p] : p;
            i = (i + N - 1) % N;
        } else if (op == 7)  // If-goto (> < == >= <= !=)
        {
            int p1 = (code[i] >> 32) & 65535, p2 = (code[i] >> 16) & 65535, p3 = code[i] & 65535;
            int val1 = memory[(code[i] >> 58) & 1 ? memory[p1] : p1],
                val2 = memory[(code[i] >> 57) & 1 ? memory[p2] : p2];
            int op2 = (code[i] >> 52) & 15;
            bool flag;
            if (op2 == 0) {
                flag = (val1 > val2);
            } else if (op2 == 1) {
                flag = (val1 < val2);
            } else if (op2 == 2) {
                flag = (val1 == val2);
            } else if (op2 == 3) {
                flag = (val1 >= val2);
            } else if (op2 == 4) {
                flag = (val1 <= val2);
            } else {
                flag = (val1 != val2);
            }
            if (flag) {
                i = (code[i] >> 56) & 1 ? memory[p3] : p3;
                i = (i + N - 1) % N;
            }
        } else if (op == 8)  // x++
        {
            int p = code[i] & 65535;
            memory[(code[i] >> 56) & 1 ? memory[p] : p]++;
        } else if (op == 9)  // x--
        {
            int p = code[i] & 65535;
            memory[(code[i] >> 56) & 1 ? memory[p] : p]--;
        } else if (op == 15)  // Exit
        {
            break;
        } else {
            cout << "\n\nError: Invalid code\n\n";
        }
    }
    return 0;
}

:::

20 行平衡树的“极简压行代码”

同学分享给我的代码,来源未知。若有原作者或知晓来源者,可以提醒我一下。

:::success[原始代码]

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e4 + 1, mod = 1e9 + 7;
int n, m, op, l, r, v;
int idx, rt, ls[N], rs[N], rd[N], sz[N], mx[N], add[N], rev[N], val[N];
void pushup(int u){mx[u] = max({mx[ls[u]], mx[rs[u]], val[u]}); sz[u] = sz[ls[u]] + sz[rs[u]] + 1;}
void pushdown(int u){if(rev[u]){swap(ls[u], rs[u]); if(ls[u]) rev[ls[u]] ^= 1; if(rs[u]) rev[rs[u]] ^= 1; rev[u] = 0;} if(add[u]){if(ls[u]){add[ls[u]] += add[u], mx[ls[u]] += add[u], val[ls[u]] += add[u];} if(rs[u]){add[rs[u]] += add[u], mx[rs[u]] += add[u], val[rs[u]] += add[u];} add[u] = 0;}}
void split(int u, int k, int &a, int &b){ if(!u) return void(a = b = 0); pushdown(u); if(k > sz[ls[u]]) a = u, split(rs[u], k - sz[ls[u]] - 1, rs[u], b); else b = u, split(ls[u], k, a, ls[u]); pushup(u);}
int merge(int a, int b){if(!a || !b) return a ^ b; pushdown(a), pushdown(b); if(rd[a] <= rd[b]){rs[a] = merge(rs[a], b); pushup(a); return a;} ls[b] = merge(a, ls[b]); pushup(b); return b;}
void Add(int l, int r, int k){int a, b, c, d; split(rt, r, a, b); split(a, l - 1, c, d); add[d] += k, mx[d] += k, val[d] += k; rt = merge(merge(c, d), b);}
void Rev(int l, int r){int a, b, c, d; split(rt, r, a, b); split(a, l - 1, c, d); rev[d] ^= 1; rt = merge(merge(c, d), b);}
int que(int l, int r){int a, b, c, d; split(rt, r, a, b); split(a, l - 1, c, d); int ans = mx[d]; rt = merge(merge(c, d), b); return ans;}
signed main(){
  ios::sync_with_stdio(0); cin.tie(0); srand(time(0)); cin >> n >> m; val[0] = mx[0] = -1e18; for(int i = 1; i <= n; rt = merge(rt, i), i++) rd[i] = rand() * rand() % mod, sz[i] = 1;
  while(m--){cin >> op >> l >> r; if(op == 1) cin >> v, Add(l, r, v); else if(op == 2) Rev(l, r); else cout << que(l, r) << '\n';}
  return 0;
}

:::

:::success[格式化后代码]

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 5e4 + 1, mod = 1e9 + 7;
int n, m, op, l, r, v;
int idx, rt, ls[N], rs[N], rd[N], sz[N], mx[N], add[N], rev[N], val[N];

void pushup(int u) {
    mx[u] = max({mx[ls[u]], mx[rs[u]], val[u]});
    sz[u] = sz[ls[u]] + sz[rs[u]] + 1;
}

void pushdown(int u) {
    if (rev[u]) {
        swap(ls[u], rs[u]);
        if (ls[u])
            rev[ls[u]] ^= 1;
        if (rs[u])
            rev[rs[u]] ^= 1;
        rev[u] = 0;
    }
    if (add[u]) {
        if (ls[u]) {
            add[ls[u]] += add[u], mx[ls[u]] += add[u], val[ls[u]] += add[u];
        }
        if (rs[u]) {
            add[rs[u]] += add[u], mx[rs[u]] += add[u], val[rs[u]] += add[u];
        }
        add[u] = 0;
    }
}

void split(int u, int k, int& a, int& b) {
    if (!u)
        return void(a = b = 0);
    pushdown(u);
    if (k > sz[ls[u]])
        a = u, split(rs[u], k - sz[ls[u]] - 1, rs[u], b);
    else
        b = u, split(ls[u], k, a, ls[u]);
    pushup(u);
}

int merge(int a, int b) {
    if (!a || !b)
        return a ^ b;
    pushdown(a), pushdown(b);
    if (rd[a] <= rd[b]) {
        rs[a] = merge(rs[a], b);
        pushup(a);
        return a;
    }
    ls[b] = merge(a, ls[b]);
    pushup(b);
    return b;
}

void Add(int l, int r, int k) {
    int a, b, c, d;
    split(rt, r, a, b);
    split(a, l - 1, c, d);
    add[d] += k, mx[d] += k, val[d] += k;
    rt = merge(merge(c, d), b);
}

void Rev(int l, int r) {
    int a, b, c, d;
    split(rt, r, a, b);
    split(a, l - 1, c, d);
    rev[d] ^= 1;
    rt = merge(merge(c, d), b);
}

int que(int l, int r) {
    int a, b, c, d;
    split(rt, r, a, b);
    split(a, l - 1, c, d);
    int ans = mx[d];
    rt = merge(merge(c, d), b);
    return ans;
}

signed main() {
    ios::sync_with_stdio(0);
    cin.tie(0);
    srand(time(0));
    cin >> n >> m;
    val[0] = mx[0] = -1e18;
    for (int i = 1; i <= n; rt = merge(rt, i), i++)
        rd[i] = rand() * rand() % mod, sz[i] = 1;
    while (m--) {
        cin >> op >> l >> r;
        if (op == 1)
            cin >> v, Add(l, r, v);
        else if (op == 2)
            Rev(l, r);
        else
            cout << que(l, r) << '\n';
    }
    return 0;
}

:::

这个格式化完之后是真的清爽了!

P4779 题解:手写堆的 Dijkstra 单源最短路

题解原文:题解:P4779 【模板】单源最短路径(标准版)

:::success[原始代码]

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define un unsigned
struct Node{
    int id,dis;
    Node(){}
    Node(const int &id,const int &dis){
        this->id=id;this->dis=dis;
    }
};
struct edge{
    int to,cost;
    edge(){}
    edge(const int &to,const int &cost){
        this->to=to;this->cost=cost;
    }
};
auto cmp=[](const Node&a,const Node&b)->bool{
    return a.dis>b.dis;
};
typedef pair<int,int> PII;
constexpr int maxn=1e5+3;
namespace COB{
    template<typename _Tp,typename _Cmp=std::less<_Tp>>
    class priority_queue{
        private:
            struct Node{
                Node *ch[2];
                _Tp val;
                int dist;
                Node():ch{nullptr,nullptr},val(_Tp()),dist(0){}
                Node(_Tp v):ch{nullptr,nullptr},val(v),dist(0){}
                ~Node(){ch[0]=ch[1]=nullptr;}
            };
            Node *root;
            _Cmp cmp;
            std::size_t sz;
            int dist(Node* p){
                return p==nullptr? -1:p->dist;
            }
            Node* merge(Node* x,Node* y){
                if(x==nullptr||y==nullptr) return (x==nullptr? y:x);
                if(cmp(x->val,y->val)) std::swap(x,y);
                x->ch[1]=merge(x->ch[1],y);
                if(dist(x->ch[0])<dist(x->ch[1])) std::swap(x->ch[0],x->ch[1]);
                x->dist=dist(x->ch[1])+1;
                return x;
            }
            void build(const Node* p){
                if(!p) return;
                build(p->ch[0]);
                build(p->ch[1]);
                root=merge(root,new Node(p->val));
                return;
            }
            void clear(Node*& p){
                if(!p) return;
                clear(p->ch[0]);
                clear(p->ch[1]);
                delete p;p=nullptr;
                return;
            }
        public:
            priority_queue(_Cmp comp=_Cmp()):root(nullptr),sz(0),cmp(comp){}
            priority_queue(const priority_queue& oth,_Cmp comp=_Cmp()){
                root=nullptr,sz=oth.sz,cmp=comp;
                build(oth.root);
            }
            priority_queue(const std::initializer_list<_Tp>& init,_Cmp comp=_Cmp()){
                root=nullptr,sz=init.size(),cmp=comp;
                for(const auto& i:init) root=merge(root,new Node(i));
            }
            priority_queue(std::priority_queue<_Tp> q){
                root=nullptr,sz=q.size();
                while(!q.empty()) root=merge(root,new Node(q.top())),q.pop();
            }
            ~priority_queue(){
                clear(root);
            }
            priority_queue& operator=(const priority_queue& q){
                clear(root);
                build(q.root);
                sz=q.size();
                return *this;
            }
            const _Tp& top()const{
                return root->val;
            }
            void push(const _Tp& x){
                ++sz;
                root=merge(root,new Node(x));
                return;
            }
            void pop(){
                --sz;
                Node *lc=root->ch[0],*rc=root->ch[1];
                delete root;
                root=merge(lc,rc);
                return;
            }
            void join(priority_queue& q){
                root=merge(root,q.root);
                q.root=nullptr;
                sz+=q.sz;
                q.sz=0;
                return;
            }
            void swap(priority_queue& q){
                std::swap(q.root,root);
                std::swap(q.sz,sz);
                return;
            }
            std::size_t size()const{return sz;}
            bool empty()const{return sz==0;}
            void clear(){
                sz=0;
                clear(root);
                return;
            }
    };
}
COB::priority_queue<Node,decltype(cmp)> q(cmp);
int n,m,s,dis[maxn];
bool vis[maxn];
vector<edge> e[maxn];
void dijkstra(){
    memset(dis,0x3f,sizeof dis);
    memset(vis,false,sizeof vis);
    dis[s]=0;
    q.push(Node(s,0));
    while(!q.empty()){
        int u=q.top().id;q.pop();
        if(vis[u]) continue;
        vis[u]=true;
        for(auto v:e[u]){
            if(vis[v.to]) continue;
            if(dis[v.to]>dis[u]+v.cost){
                dis[v.to]=dis[u]+v.cost;
                q.push(Node(v.to,dis[v.to]));
            }
        }
    }
    return;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cerr.tie(nullptr);
    cin>>n>>m>>s;
    for(int i=1;i<=m;i++){
        int u,v,w;cin>>u>>v>>w;
        e[u].push_back(edge(v,w));
    }
    dijkstra();
    for(int i=1;i<=n;i++) cout<<dis[i]<<' '; 
    return 0;
}

:::

:::success[格式化后的代码]

#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define un unsigned

struct Node {
    int id, dis;

    Node() {}

    Node(const int& id, const int& dis) {
        this->id = id;
        this->dis = dis;
    }
};

struct edge {
    int to, cost;

    edge() {}

    edge(const int& to, const int& cost) {
        this->to = to;
        this->cost = cost;
    }
};

auto cmp = [](const Node& a, const Node& b) -> bool { return a.dis > b.dis; };
typedef pair<int, int> PII;
constexpr int maxn = 1e5 + 3;

namespace COB {
    template <typename _Tp, typename _Cmp = std::less<_Tp>>
    class priority_queue {
    private:
        struct Node {
            Node *ch[2];
            _Tp val;
            int dist;

            Node():
                ch {nullptr, nullptr},
                val(_Tp()),
                dist(0) {}

            Node(_Tp v):
                ch {nullptr, nullptr},
                val(v),
                dist(0) {}

            ~Node() {
                ch[0] = ch[1] = nullptr;
            }
        };

        Node *root;
        _Cmp cmp;
        std::size_t sz;

        int dist(Node *p) {
            return p == nullptr ? -1 : p->dist;
        }

        Node *merge(Node *x, Node *y) {
            if (x == nullptr || y == nullptr)
                return (x == nullptr ? y : x);
            if (cmp(x->val, y->val))
                std::swap(x, y);
            x->ch[1] = merge(x->ch[1], y);
            if (dist(x->ch[0]) < dist(x->ch[1]))
                std::swap(x->ch[0], x->ch[1]);
            x->dist = dist(x->ch[1]) + 1;
            return x;
        }

        void build(const Node *p) {
            if (!p)
                return;
            build(p->ch[0]);
            build(p->ch[1]);
            root = merge(root, new Node(p->val));
            return;
        }

        void clear(Node *& p) {
            if (!p)
                return;
            clear(p->ch[0]);
            clear(p->ch[1]);
            delete p;
            p = nullptr;
            return;
        }

    public:
        priority_queue(_Cmp comp = _Cmp()):
            root(nullptr),
            sz(0),
            cmp(comp) {}

        priority_queue(const priority_queue& oth, _Cmp comp = _Cmp()) {
            root = nullptr, sz = oth.sz, cmp = comp;
            build(oth.root);
        }

        priority_queue(const std::initializer_list<_Tp>& init, _Cmp comp = _Cmp()) {
            root = nullptr, sz = init.size(), cmp = comp;
            for (const auto& i : init)
                root = merge(root, new Node(i));
        }

        priority_queue(std::priority_queue<_Tp> q) {
            root = nullptr, sz = q.size();
            while (!q.empty())
                root = merge(root, new Node(q.top())), q.pop();
        }

        ~priority_queue() {
            clear(root);
        }

        priority_queue& operator=(const priority_queue& q) {
            clear(root);
            build(q.root);
            sz = q.size();
            return *this;
        }

        const _Tp& top() const {
            return root->val;
        }

        void push(const _Tp& x) {
            ++sz;
            root = merge(root, new Node(x));
            return;
        }

        void pop() {
            --sz;
            Node *lc = root->ch[0], *rc = root->ch[1];
            delete root;
            root = merge(lc, rc);
            return;
        }

        void join(priority_queue& q) {
            root = merge(root, q.root);
            q.root = nullptr;
            sz += q.sz;
            q.sz = 0;
            return;
        }

        void swap(priority_queue& q) {
            std::swap(q.root, root);
            std::swap(q.sz, sz);
            return;
        }

        std::size_t size() const {
            return sz;
        }

        bool empty() const {
            return sz == 0;
        }

        void clear() {
            sz = 0;
            clear(root);
            return;
        }
    };
}

COB::priority_queue<Node, decltype(cmp)> q(cmp);
int n, m, s, dis[maxn];
bool vis[maxn];
vector<edge> e[maxn];

void dijkstra() {
    memset(dis, 0x3f, sizeof dis);
    memset(vis, false, sizeof vis);
    dis[s] = 0;
    q.push(Node(s, 0));
    while (!q.empty()) {
        int u = q.top().id;
        q.pop();
        if (vis[u])
            continue;
        vis[u] = true;
        for (auto v : e[u]) {
            if (vis[v.to])
                continue;
            if (dis[v.to] > dis[u] + v.cost) {
                dis[v.to] = dis[u] + v.cost;
                q.push(Node(v.to, dis[v.to]));
            }
        }
    }
    return;
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    cerr.tie(nullptr);
    cin >> n >> m >> s;
    for (int i = 1; i <= m; i++) {
        int u, v, w;
        cin >> u >> v >> w;
        e[u].push_back(edge(v, w));
    }
    dijkstra();
    for (int i = 1; i <= n; i++)
        cout << dis[i] << ' ';
    return 0;
}

:::

代码逻辑与结构瞬间清楚。

P5490 题解:扫描线模板

题解原文:ls学习笔记note37:扫描线(Line Sweep Algorithm)

:::success[原始代码]

#include<cstdio>
#include<algorithm>
#define ri register int
using namespace std;
typedef long long ll;
ll ans;
ll n;
ll x1,x2,y1,y2;
ll y[210000];
struct smx
{
    ll y1,y2,x,mark;
}line[210000];
bool cmp(smx a,smx b){return a.x<b.x;}//排序 
struct node
{
    ll c,tag;
    //tag用来判断扫描线扫到下一条线段时还包不包括这个矩形 
}tr[810000];
void pushup(ll num,ll l,ll r) 
{
    if(!tr[num].tag)
    {
        if(l==r)tr[num].c=0; 
        else tr[num].c=tr[num<<1].c+tr[num<<1|1].c;
    }
}
void change(ll num,ll l,ll r,ll y1,ll y2,ll k)//这里的l,r是num的管理范围,y1、y2是上一个代码的l,r 
{
    if(y1<=l&&r<=y2)
    {
        tr[num].tag+=k;
        if(tr[num].tag)tr[num].c=y[r+1]-y[l];
        else tr[num].c=0;
        pushup(num,l,r);
        return ;
    }
    ll mid=(l+r)>>1;
    if(y1<=mid)change(num<<1,l,mid,y1,y2,k);
    if(mid+1<=y2)change(num<<1|1,mid+1,r,y1,y2,k);
    pushup(num,l,r);
}
int main()
{
    scanf("%lld",&n);
    for(ri i=1;i<=n;i++)
    {
        scanf("%lld%lld%lld%lld",&x1,&y1,&x2,&y2);
        y[(i<<1)-1]=y1;y[i<<1]=y2;
        line[(i<<1)-1]=(smx){y1,y2,x1,3};line[i<<1]=(smx){y1,y2,x2,-3};
    }   
    n<<=1; 
    sort(y+1,y+n+1);
    sort(line+1,line+n+1,cmp);
    int len=unique(y+1,y+n+1)-(&y[1])-1;
    for(ri i=1;i<n;i++)
    {
        y1=lower_bound(y+1,y+len+2,line[i].y1)-y;
        y2=lower_bound(y+1,y+len+2,line[i].y2)-y-1;
        change(1,1,len,y1,y2,line[i].mark);
        ans+=tr[1].c*(line[i+1].x-line[i].x);
    }
    printf("%lld\n",ans); 
    return 0;
}

:::

:::success[格式化后的代码]

#include <algorithm>
#include <cstdio>
#define ri register int
using namespace std;
typedef long long ll;
ll ans;
ll n;
ll x1, x2, y1, y2;
ll y[210000];

struct smx {
    ll y1, y2, x, mark;
} line[210000];

bool cmp(smx a, smx b) {
    return a.x < b.x;
}  // 排序

struct node {
    ll c, tag;
    // tag用来判断扫描线扫到下一条线段时还包不包括这个矩形
} tr[810000];

void pushup(ll num, ll l, ll r) {
    if (!tr[num].tag) {
        if (l == r)
            tr[num].c = 0;
        else
            tr[num].c = tr[num << 1].c + tr[num << 1 | 1].c;
    }
}

void change(ll num,
            ll l,
            ll r,
            ll y1,
            ll y2,
            ll k)  // 这里的l,r是num的管理范围,y1、y2是上一个代码的l,r
{
    if (y1 <= l && r <= y2) {
        tr[num].tag += k;
        if (tr[num].tag)
            tr[num].c = y[r + 1] - y[l];
        else
            tr[num].c = 0;
        pushup(num, l, r);
        return;
    }
    ll mid = (l + r) >> 1;
    if (y1 <= mid)
        change(num << 1, l, mid, y1, y2, k);
    if (mid + 1 <= y2)
        change(num << 1 | 1, mid + 1, r, y1, y2, k);
    pushup(num, l, r);
}

int main() {
    scanf("%lld", &n);
    for (ri i = 1; i <= n; i++) {
        scanf("%lld%lld%lld%lld", &x1, &y1, &x2, &y2);
        y[(i << 1) - 1] = y1;
        y[i << 1] = y2;
        line[(i << 1) - 1] = (smx) {y1, y2, x1, 3};
        line[i << 1] = (smx) {y1, y2, x2, -3};
    }
    n <<= 1;
    sort(y + 1, y + n + 1);
    sort(line + 1, line + n + 1, cmp);
    int len = unique(y + 1, y + n + 1) - (&y[1]) - 1;
    for (ri i = 1; i < n; i++) {
        y1 = lower_bound(y + 1, y + len + 2, line[i].y1) - y;
        y2 = lower_bound(y + 1, y + len + 2, line[i].y2) - y - 1;
        change(1, 1, len, y1, y2, line[i].mark);
        ans += tr[1].c * (line[i + 1].x - line[i].x);
    }
    printf("%lld\n", ans);
    return 0;
}

:::

手写神经网络矩阵向量库

原文:从零实现的神经网络

原文代码无代码仓库、无版权声明、无许可证使用,将默认其与文章内容同样使用知识共享协议发布。

:::success[原始代码]

#include "conf.h"

template<typename _Tp>
class matrix {

public:
    size_t n, m;
    size_t size;
    _Tp *data;

    inline _Tp *operator[](size_t index) { return data + (index * m); }
    inline const _Tp *operator[](size_t index) const { return data + (index * m); }
    inline matrix() : n(), m(), size(), data() {}
    inline matrix(size_t _n, size_t _m) : n(_n), m(_m), size(_n * _m) { data = new _Tp[size](); }
    inline matrix(matrix<_Tp> &&val) : n(val.n), m(val.m), size(val.size) { data = val.data; val.data = NULL; }
    inline matrix(const matrix<_Tp> &val) : n(val.n), m(val.m), size(val.size) { data = new _Tp[size](); for(size_t i = 0; i < size; i++) data[i] = val.data[i]; }
    inline ~matrix() { if(data != NULL) delete[] data; }

    inline void resize(size_t _n, size_t _m) { if(data != NULL) delete[] data; n = _n, m = _m, size = n * m; data = new _Tp[size](); }
    inline void clear() { if(data == NULL) data = new _Tp[size](); else for(size_t i = 0; i < size; i++) data[i] = _Tp(); }
    inline matrix<_Tp> &operator=(matrix<_Tp> &&val) { assert(n == val.n && m == val.m); if(data != NULL) delete[] data; data = val.data; val.data = NULL; return *this; }
    inline matrix<_Tp> &operator=(const matrix<_Tp> &val) { assert(n == val.n && m == val.m); if(data == NULL) data = new _Tp[size](); for(size_t i = 0; i < size; i++) data[i] = val.data[i]; return *this; }

};

template<typename _Tp>
class vec {

public:
    size_t n;
    _Tp *data;

    inline _Tp &operator[](size_t index) { return data[index]; }
    inline const _Tp &operator[](size_t index) const { return data[index]; }
    inline vec() : n(), data() {}
    inline vec(size_t _n) : n(_n) { data = new _Tp[n](); }
    inline vec(vec<_Tp> &&val) : n(val.n) { data = val.data; val.data = NULL; }
    inline vec(const vec<_Tp> &val) : n(val.n) { data = new _Tp[n](); for(size_t i = 0; i < n; i++) data[i] = val.data[i]; }
    inline ~vec() { if(data != NULL) delete[] data; }

    inline void resize(size_t _n) { if(data != NULL) delete[] data; n = _n; data = new _Tp[n](); }
    inline void clear() { if(data == NULL) data = new _Tp[n](); else for(size_t i = 0; i < n; i++) data[i] = _Tp(); }
    inline vec<_Tp> &operator=(vec<_Tp> &&val) { assert(n == val.n); if(data != NULL) delete[] data; data = val.data; val.data = NULL; return *this; }
    inline vec<_Tp> &operator=(const vec<_Tp> &val) { assert(n == val.n); if(data == NULL) data = new _Tp[n](); for(size_t i = 0; i < n; i++) data[i] = val.data[i]; return *this; }

};

:::

:::success[格式化后的代码]

#include "conf.h"

template <typename _Tp>
class matrix {
public:
    size_t n, m;
    size_t size;
    _Tp *data;

    inline _Tp *operator[](size_t index) {
        return data + (index * m);
    }

    inline const _Tp *operator[](size_t index) const {
        return data + (index * m);
    }

    inline matrix():
        n(),
        m(),
        size(),
        data() {}

    inline matrix(size_t _n, size_t _m):
        n(_n),
        m(_m),
        size(_n * _m) {
        data = new _Tp[size]();
    }

    inline matrix(matrix<_Tp>&& val):
        n(val.n),
        m(val.m),
        size(val.size) {
        data = val.data;
        val.data = NULL;
    }

    inline matrix(const matrix<_Tp>& val):
        n(val.n),
        m(val.m),
        size(val.size) {
        data = new _Tp[size]();
        for (size_t i = 0; i < size; i++)
            data[i] = val.data[i];
    }

    inline ~matrix() {
        if (data != NULL)
            delete[] data;
    }

    inline void resize(size_t _n, size_t _m) {
        if (data != NULL)
            delete[] data;
        n = _n, m = _m, size = n * m;
        data = new _Tp[size]();
    }

    inline void clear() {
        if (data == NULL)
            data = new _Tp[size]();
        else
            for (size_t i = 0; i < size; i++)
                data[i] = _Tp();
    }

    inline matrix<_Tp>& operator=(matrix<_Tp>&& val) {
        assert(n == val.n && m == val.m);
        if (data != NULL)
            delete[] data;
        data = val.data;
        val.data = NULL;
        return *this;
    }

    inline matrix<_Tp>& operator=(const matrix<_Tp>& val) {
        assert(n == val.n && m == val.m);
        if (data == NULL)
            data = new _Tp[size]();
        for (size_t i = 0; i < size; i++)
            data[i] = val.data[i];
        return *this;
    }
};

template <typename _Tp>
class vec {
public:
    size_t n;
    _Tp *data;

    inline _Tp& operator[](size_t index) {
        return data[index];
    }

    inline const _Tp& operator[](size_t index) const {
        return data[index];
    }

    inline vec():
        n(),
        data() {}

    inline vec(size_t _n):
        n(_n) {
        data = new _Tp[n]();
    }

    inline vec(vec<_Tp>&& val):
        n(val.n) {
        data = val.data;
        val.data = NULL;
    }

    inline vec(const vec<_Tp>& val):
        n(val.n) {
        data = new _Tp[n]();
        for (size_t i = 0; i < n; i++)
            data[i] = val.data[i];
    }

    inline ~vec() {
        if (data != NULL)
            delete[] data;
    }

    inline void resize(size_t _n) {
        if (data != NULL)
            delete[] data;
        n = _n;
        data = new _Tp[n]();
    }

    inline void clear() {
        if (data == NULL)
            data = new _Tp[n]();
        else
            for (size_t i = 0; i < n; i++)
                data[i] = _Tp();
    }

    inline vec<_Tp>& operator=(vec<_Tp>&& val) {
        assert(n == val.n);
        if (data != NULL)
            delete[] data;
        data = val.data;
        val.data = NULL;
        return *this;
    }

    inline vec<_Tp>& operator=(const vec<_Tp>& val) {
        assert(n == val.n);
        if (data == NULL)
            data = new _Tp[n]();
        for (size_t i = 0; i < n; i++)
            data[i] = val.data[i];
        return *this;
    }
};

:::

格式化的另一种用法

你可以根据我提供的 .clang-format 进行细节上的修改,变成你自己的格式化配置。如果你参与团队开发工作,那么必然要遵守团队的代码风格规范。然而,这个规范你并不一定喜欢,甚至有妨碍你编码。怎么办呢?

你完全可以在 pull 之后将要修改的代码先按照你的配置格式化为你舒服的形态,然后修改完后再格式化回团队的风格。

毕竟自动格式化的初衷,就是提供统一的代码风格,并且保障个体的审美习惯,同时纠正开发者的不良编码习惯。

缺点

当然,说完了 clang-format 的优点,自然要开说说它的缺点了。

仔细阅读 Clang-Format Style Options 文档,会发现其中有一些这样的警告。这里以 InsertBraces 选项的警告为例:

:::warning[Warning]{open} Setting this option to true could lead to incorrect code formatting due to clang-format's lack of complete semantic information. As such, extra care should be taken to review code changes made by this option. :::

让我们翻译成人话中文:

:::warning[警告]{open} 将此选项设置为 true 可能会导致错误的代码格式,因为 clang-format 缺乏完整的语义信息。因此,应格外注意查看此选项所做的代码更改。 :::

我在上面给出的仿 Rust Style Guide 的手写 .clang-format 配置刻意避开了这些带警告的选项。

那么这些警告到底是什么意思呢?

clang-format 工具仅有词法分析和语法分析的功能,根据 AST(Abstract Syntax Tree,抽象语法树)和 .clang-format 配置的指导进行格式化。它不具备语义分析的功能,因此无法分析代码的具体意图。

InsertBraces 做的又是下面这件事情:

// Before
if (cond)
    any_statement;

// After
if (cond) {
    any_statement;
}

也就是给省略花括号的子句补上花括号。

由于 clang-format 缺乏语义分析,所以如果启用这个选项,很可能会因为 clang-format 缺失语义信息而导致代码逻辑的更改。所以,尽管始终添加花括号是几乎所有风格的共识,几乎所有的 clang-format 的预定义风格中都不设置这条选项。哪怕是开发了 Go 这个不写花括号就不给编译的语言的 Google 公司,也没有在 clang-format 的 Google 风格中添加这条选项。

升华

说回 Rust。Rust 提供的 rustfmt 就不会出现语义更改的问题。Rust 拥有核心主导型的生态,有官方提供的完整而强大的工具链,有官方指定的教程、手册和指南文档,有全领域通用的官方规范。rustfmt 深度集成于 Rust 工具链,与其编译器共享前端,自然可以做到获取足够的语义信息。

反观 C/C++,由于出现时间较早,官方核心约束力不够。各个厂商开始自己写编译器、自己制定功能标准。要不是 ANSI 和 ISO 及时出手,C/C++ 早就分裂成各种不同的“方言”了。现在 C/C++ 的处境就是,除了 ISO 掌握标准制定之外,无官方教程、无官方工具链、无官方规范、无官方文档。工具链的分裂注定 clang-format 哪怕属于 Clang 工具链也无法深度集成——CMake + GCC + clang-format 在 Linux 开发的出现频率很高,此时的 clang-format 没有任何工具链可以给它提供语义信息。官方规范的缺失,也导致各个开发者都自己指定相应的风格指南,风格撕裂。

编程语言发展到今天,我们可以很容易地发现这条规律:FORTRAN、BASIC、Pascal、LISP、C/C++ 等出现时间较早的语言,除 C/C++ 之外,后来都基本上走向了分裂为各个“方言”的结局。C/C++ 也没有好到哪里去,只不过应该庆幸有 ISO 还在为它指定标准、有社区的人在努力进行开发体验的统一(如 CMake 等)。而比较年轻的现代化编程语言:Python、Java、Rust、Kotlin、Swift 等,都有以官方为核心的开发者社区,生态以官方为准。Python 有 PEP 8、Rust 有 Rust Style Guide;而 Python、Rust、Kotlin、Swift 等语言几乎都有官方编写的教程和社区中人们自己编写的翻译或特化版。而且,这些语言都有官方持续维护和更新的统一工具链,但同时又开源出去使得特定领域特化的衍生编译器得以发展。

以我为本、兼容并蓄,核心统一、外围开放,正是这些新兴编程语言能够快速发展而不走或少走歧路的根本原因。

统一,是一切的根本。