TheBadZhang 发布的文章

《C++ Primer》5th 读书笔记

虽然说是这本书的笔记,但是还是会引入一些书外面的概念和新版标准的部分内容
对于不属于这本书的特殊的地方我会额外加以标注
[TOC]

第 1 章 开始

C++ 超级基础Base

可以看我之前写的关于c++的必知必会的文章

UB(Undefined-Behaviors) 未定义行为

char 是 signed char 还是 unsigned char 是未定义的
int 是大于等于 short 小于等于 long 的

流可以作为 condition,用于判断是否到了流末尾,当到达流末尾时为 false,所以我们经常可以看到如下的写法


int n;
while (std::cin >> n) {
    // do somthing...
}

此时调用了 std::cin 类的 >> 函数,往 n 中输入数据,返回的是 std::cin 类的 istream 对象,到达流末尾时(通常这个标记为 EOF(End-Of-File)),在windows上可通过 Ctrl-z 和 Ctrl-d 输入 EOF 符号,标志流结束。
>> 作为一个函数,同理

std::cout << "Hello World" << std::endl;

<< 也是一个函数,但是你会发现,其中存在多次调用,因为 std::cout<< 的返回值是一个 ostream 对象,即 std::cout 本身,所以实现了上面这样子的连续调用,但实际上,这个操作可以像下面这样分解,

std::cout << "Hello World";
std::cout << std::endl;

这两种写法是完全等价的,于是就引出了一个问题,线程安全,因为,这个操作可以被等价分成两份,所以它的操作是不原子的,就可能在别的线程中,被插入,造成输出顺序混乱,比如输出了

Hello World123123\n

但是你想要输出的是

Hello World\n123123

其中,上面的\n的是转义字符,让换行看起来更加明显。解决办法也是有的,使用原子锁,但是这个内容是在后面学习多线程时才会讲到的,所以这里不加以赘述。

字面量

字面量大致可以分为两种,一种是语言提供的字面量,一种是库提供的字面量
语言提供的字面量,例如(其实我也没有仔细研究过具体哪些属于哪些,反正把自己知道的都列出来了)

// 指针字面量
nullptr;
// 布尔字面量
true;false;
// 整数字面量
auto a = 1;        // int a
auto b = 0x1;      // int b
auto c = 01;       // int c
// 二进制字面量在 c++14 中引入
auto d = 0b1;      // int d
auto e = 1u;       // unsigned int e
auto f = 1ll;      // long long f
auto uf= 1ull;     // unsigned long long f
auto g = 1l;       // long g
// 其实这些后缀大小写都是可以的,为了方便书写,这里都写小写
// 浮点数字面量
auto h = 1.0l;     // long double h
auto i = 1e-1;     // double i
auto j = 1.0f;     // float j
auto k = 1e-1f;    // float k
// 字符(串)字面量
auto l = "Hello";  // const char *l
auto m = u"World"; // const char16_t *m
auto n = U"你好";  // const char32_t *n
auto o = u8"世界"; // const char8_t *o
auto p = L'!';    // wchar_t p
auto q = '!';      // char q

但实际上,部分字面量会根据自己的数据大小自动变更数据类型,如果数据超过了long所能承载的范围,就会自动变为 long long,类型都是所能承受的最小类型,当然,char和short不在此列;
用户自定义字面量
使用运算符重载的方式

auto operator"" end(something) {

}

就可以弄一个 somethingend 的一个字面量,something 作为参数Parameter传入 函数,处理后返回

第 I 部分 C++ 基础

第 2 章 变量和基本类型

初始化与赋值

其实这个是老生常谈的问题了,因为总是有非常多的事情,在这个上面纠结

int a = 10;  // mov     DWORD PTR [rbp-4], 10
int b;       // 
int d = a;   // mov     eax, DWORD PTR [rbp-4]
             // mov     DWORD PTR [rbp-8], eax
int c = b;   // mov     eax, DWORD PTR [rbp-12]
             // mov     DWORD PTR [rbp-16], eax
b = 10;      // mov     DWORD PTR [rbp-12], 10

左侧为 c++ 代码,右侧为生成的汇编。这就很神奇了,你发现第二行定义变量 b 的时候,没有生成任何代码。在你尝试书写

int a = 10; // mov     DWORD PTR [rbp-4], 10
int b;      // 
b = 10;     // mov     DWORD PTR [rbp-12], 10

如上的代码的时候,生成的汇编也只有两行,就算你开 -O0 也一样,因为这个定义的语句,的确,什么事情也没有干,只是告诉编译Compiler器,「这个位置我占掉了,虽然里面的东西我没有明确给它,但是我占了,你得让接下来的变量都往后挪挪,而且不能说我不在」的这种状态。

类型、限定符、修饰符

int, char, short, long, long long, float, double, long double 等被称为基础数据类型,一切的一切基于此而产生

分析一个变量的具体类型,应该从右王座看,看到 & 就是引用ref类型,看到 [] 就是数组类型,看到 *就是指针类型,然后看 顶层const还是底层const

人们喜欢讨论 * 这个字符用于限定变量的时候的位置,就会产生下面三种结果

int *p1;  // *p1 的类型为 int
int * p1; // 两边各退一步
int* p1;  // p1 的类型为 int*

我个人倾向于使用 int* 的方式,将其作为一整个类型,因为 c++ 的类型系统过于复杂,比如

int x = 20;
int* px = &x;
decltype(x) px2 = px;

如果说是 *p 的类型为 type 的话,应当如何解释 decltype(x) 所推断出来的类型?所以,我倾向于使用 type* nameauto px3 = px;也是如此,如果不是类型,何来「类型推断」?
同理,引用类型也是这样

// 指针
type* name1;
// 引用
(type*)& name2 = name1;
// 上面这个是对于指针类型的引用,引用必须要初始化

下面讲讲关于 const 和 constexpr

在此之前,真正的 constant 实际上是使用 #define 来定义的,但是 c++11 出现了 constexpr,终于可以用正经的方式定义一个真正的 constant 了。
constexpr 要求变量或者表达式的值能够在编译器得到计算,于是乎,用 constexpr 修饰的变量,是一个定值
const 所代表的是,不变量,与变量相对,只是在使用的过程中不会发生变化,但不代表它是一个固定的值

int a;
std::cin >> a;
const int b = a;

合法么,合法,但是 b 的值会随着我们的输入而发生变化,但是在接下来试图改变b的值,都会造成编译器错误。
但其实也不一定的,虽然说不能直接通过b修改b所对应的对象值,但是我们可以通过间接的方式,访问到b,并修改,而且不会引发编译器错误。
这个我们可以拿 nim 中类似的语句进行对比

const str = "Hello World"
# Mutable variables
var c: int
c = 20
# Immutable variables
let e = c

nim 中
const 是常量,var 是变量,let 是不可变量
c++ 中
constexpr 是常量,没有限定符的各种类型 是变量,const 是不可变量
这样一比其实也不难发现 c++ 在发展上的滞后性了

const int i = 20;
// 不能修改的 int 类型变量(不变量也是不会变的变量)
const int* const pi = &i;
// 首先,a 是 const,a 本身的值不能改动
// 其次,是 const int* 类型,代表是 cosnt int 类型的指针
// 说明 a 所指向的对象是 int 且不能修改
const int*& const rpi = pi;
// 一个 const int* 类型的引用,且本身也不能修改,(虽然说引用本来就不能改,不知道加上有没有

我们再加上一个数组类型,数组类型就非常有趣了,因为其中的矛盾点实在太多了,比如可以弱化成指针,这一点就很折磨人,所以和别人解释,但是这个内容再在下一章解释

自动类型推断

auto
decltype(statment)
decltype(auto)     //c++17 引进

自定义数据结构,使用 struct 将各种数据归为一类,
但是好像没有看到 union 这个数据结构在这本书中被介绍
虽然说用的少,但其实,还是很有用的,比如用作动态类型的数据结构

第 3 章 字符串、向量和数组

其实这一章前面部分没有什么特别重要的事情,只不过介绍了 std::string 和 std::vector 两个标准库「容器Container
我想这个内容可能看 C++ 标准库 可能更加适合一些
但有一点可以注意的就是

std::vector<std::string> a(10, "20");
std::vector<std::string> b{10, "20"};

的效果是一样的

迭代器

std::string::itrator;
std::vector<int>::itrator;

数组

int a[10];
using int10 = int[10];
int10 b;

第 4 章 表达式

提升,转换,重载,左右值

赋值,取址,解引用和下标……
都必须要使用左值,

decltype 对左值,取到的是一个引用,但对于右值,取到的是本来的类型
这个在之前的例子中有出现过

int i = 10;
int* pi = &i;
decltype (*pi) d = i;  // 实际上这是 int& 类型
decltype (pi) e = pi;  // 这个是 int* 类型
sizeof int
sizeof (1+1)
int a = f1() + f2();

像上面这两个函数返回值相加,但是你没有办法知道先执行的是哪一个函数,这个是不确定的,一定程度上也是线程不安全的

T operator+(const T &a, const T2 &b);

同理,因为加号会图上面这样子重载,对于某一个类型的重载,你也可以把int类型的+重载成*,这个以后再讲。f1和f2就成为了 operator+ 的两个参数,这样同样表明,函数作为函数参数时,其运行顺序也是不确定的

取余的运算比较复杂,日后重新罗列

第 5 章 语句

if

if-else

for

for

while

do-while

switch

break

continue

goto

throw

try

catch

第 6 章 函数

const 的故事到这里,才算真正地开始……

整个 c++11 就是一个类型斗争史,auto,decltype,template,using,透露出两个字,类型,类型,还是类型,函数重载、尾置返回、左值引用、右值引用也涉及到类型推断,
尾置返回类型

auto func (int i) -> int(*)[10];

函数参数中的顶层 const 会被忽略掉

void func (const int i);
void func (int i);

上面两个声明其实都是一个函数,会报错

void print (const int* i);
void print (const int i[]);
void print (const int i[10]);

上面三种声明,也是一样的,所以,在作为函数参数的时候,数组会弱化为指针

const int& i = 41;
void func (int& ar[]);   // 引用的数组
void func (int (&ar)[]); // 数组的引用

函数返回数组指针

int (*function(void))[10];  // 一个返回指向 int[10] 的指针的函数
// 使用尾置返回
auto function(void) -> int(*)[10];
using pi10 = int(*)[10];
pi10 function (void);

函数返回函数指针

int (*function (void)) (int*, int);
auto function(void) -> int(*)(int*,int);
using ifpii = int(*)(int*, int);
ifpii function (void);

关于如何向数组传入长度参数,其实还有别的小妙招

template <typename T>
void func (int& ar[T]) {

}
type (*function(parameters))[dimension] {

}

可以定义一个返回 数组的指针 的函数

折叠表达式(C++17 起)

template<typename... Args>
bool all(Args... args) { return (... && args); }
template<typename... Args>
bool any(Args... args) { return (... || args); }
template<typename... Args>
bool sum(Args... args) { return (... +  args); }

bool b = all(true, true, true, false);
 // 在 all() 中,一元左折叠展开成
 //  return ((true && true) && true) && false;
 // b 为 false
// 将一元折叠用于零长包展开时,仅允许下列运算符:

// 1) 逻辑与(&&)。空包的值为 true
// 2) 逻辑或(||)。空包的值为 false
// 3) 逗号运算符(,)。空包的值为 void()
// 注解
// 若用作 初值 或 形参包 的表达式在顶层具有优先级低于转型的运算符,则它可以加括号:

template<typename ...Args>
int sum(Args&&... args) {
//    return (args + ... + 1 * 2); // 错误:优先级低于转型的运算符
    return (args + ... + (1 * 2)); // OK
}

函数重载匹配其实也是一个很复杂的问题,
但是拒绝认识它也是可以的,就是不要写出具有歧义的重载函数

第 7 章 类

访问控制
public private protect
友元
friend
类成员
作用域和名字查找
构造函数初始化列表
委托构造函数
默认构造函数
=default =delete
隐式类类型转换
explicit

第 II 部分 C++ 标准库

第 8 章 IO 库

头文件 类型 描述
iostream istream, wistream 从流读入数据
ostream, wostream 向流写入数据
iostream, wiostream 读写流
fstream ifstream, wifstream 从文件流读入数据
ofstream, wofstream 向文件流写入数据
fstream, wfstream 文件读写流
sstream istringstream, wistringstream 从 string 读入数据
ostringstream, wostringstream 向 string 写入数据
stringstream, wstringstream 读写 string

c++ 定义了上面这些流操作的类型,提供了最基础的流抽象功能,其他功能也可以基于此进行更深的抽象

但是,C++ 的流可能是一个非常失败的设计,因为输入输出的符号大家都不是很喜欢,而且在前期缺少合适的文本格式化工具,直到 c++20 引入了 format 库,才有所改观,但实际上现在没有任何一家的编译器是支持 format 库的再者是自带的 STL 库都没有提供对于标准化输入输出流的支持,只能自己手动输入输出。

但是我们也可以通过这个设计实现一个统一的输入输出操作

可以通过若干种标志判断当前流的状态处于失败、结尾、正常或者是异常
也可以通过对应的函数设置当前的状态

unitbuf, nounitbuf, flush, endl
立即刷新,不立即刷新,强制刷新,强制刷新并换行

我们可以使用 fstream 完成对文件流的读写操作,其具体操作与输入输出流并没有特别多的区别,唯一的是需要指明文件的路径和打开方式

需要注意的是,文件的写操作默认是附带 std::ios::trunc 的,这个意味着打开一个文件的时候,如果原先存在文件,则会将原先的文件删除

使用 stringstream 则可以对流进行细分,在实际使用中出现频率还是很高的

第 9 章 顺序容器

顺序容器主要有 类型 介绍
vector 动态数组,即长度可变,支持快速随机Random访问,数据连续存储,所以插入数据可能很慢
deque 双向队列,长度可变,支持快速随机访问,头尾插入删除很快,数据连续存储的同时分块存储
list 双向链表,长度可变,随机访问并不快速,任意一个元素的插入删除都很快,就链表的存储方式
forward_list 单向链表,长度可变,随机访问也不快速,但是相比双向链表少了一个方向,所以在插入和删除时比自己手写的链表快不了多少
array 静态数组,长度不可变,可以看作是原生数组的高级版本
string 和vector类似,但是专门用于保存字符,同时提供大量字符串处理相关的函数
queue 单向队列,由双向队列继承而来
priority_queue
stack 栈,由双向队列继承而来

其实这里有一个很巧妙的点,为什么说是快速随机访问呢,确实,链表因为数据结构的问题,其实不支持随机访问,但是可以通过遍历的方式,实现一个非常慢速的随机访问,但也其实,可以通过一个vector存储链表的迭代器,再对vector随机访问,就可以实现对链表的随机访问了,这个适用于大规模的对于链表的随机访问

同时,这些容器库之所以要叫容器库,是因为它们提供了对任意类型的「容」,这个得益于 c++ 复杂的模板,在编译器对各种类型进行展开,同时,由于模板的存在,很多本来看上去很正常的名字,就变得极为不正常了,这也导致使用了模板的报错变得异常难读,学会从模板报错中找到正确的错误,也是一个非常重要的技巧

这些容器库,STL,都包含着若干统一的操作函数,但这里就不一一列举了,这不应该成为学习 c++ 的负担。诸如比较,构造,复制,交换,添加删除,以及各类迭代器(c++11 引入了一种新的反迭代器,还有各种容器的构造,赋值,交换,追加,插入,删除,移动,拷贝,这里也不加以细说。

往容器中添加元素又变得非常有复杂,但也没有那么复杂
push_back, push_front, emplace_back, emplace_front, insert,其中 emplace 系列函数于 c++11 引入,究其原因还是因为 c++11 带来的右值引用,push 系列函数在插入一个值的时候,会先对值进行拷贝,但是 emplcae 函数借用右值引用直接将值写入对应位置,减少了一次拷贝,一定程度上提升了性能(右值引用牛逼!)
https://zhuanlan.zhihu.com/p/213853588
pop_back, pop_front, erase 用于删除元素
各个迭代器的使用,forward_list独树一帜的特殊操作

对于容器的插入删除可能导致迭代器失效,因为移动了容器中实际内容的位置,vector在内容将要填满预先分配的空间时,会将当前空间扩大为两倍,使用capacity可以查看已分配的空间,size查看已使用的空间

对字符串的各种操作函数在这里也不加以赘述了,实际上字符串库应当搭配c++11引入的regex正则匹配库和c++20引入的format格式化库使用更加顺手,正则匹配是一个好东西,就是看起来效率非常差,但也没有那么差了

第 10 章 泛型算法

泛型,何为泛型,即通用的,对于任何类型都可以使用,类型无关
这些泛型函数主要通过迭代器和传入的函数进行使用
其中大多数函数定义在 algorithm 算法库中,c++11 提供了超过一百种的内置算法,为开发提供了非常有用的帮助,尤其是 sort 函数我使用的次数不可谓不多
find, find_if...
(我会在将来的某天详细地介标准库的内容,但不会是在这本书上
泛型算法主要分为只读算法,写算法和排序算法
查找算法,判断算法等算法为只读算法
写算法,插入算法(使用插入迭代器),拷贝算法(利用迭代器),
排序算法,sort不稳定排序,stable_sort稳定排序,性能上各有千秋

之后,这本书在这个地方介绍了一个非常重量的 c++11 更新,lambda 函数,同时,这也更一步的让c++拥有了函数式编程的风范,一个较为简单的方式声明并使用一个函数,其中的详细内容我会另开一个文章进行介绍,但书中没有在这里引入function函数用来存储lambda函数我感觉还是有些欠妥当,不过也介绍了bind函数关于绑定函数参数的内容,同时介绍了find_if 函数和for_each 函数。值捕获,引用捕获,隐式捕获,等等等等,设置返回值,自动推断返回值,这里甚至还产生了闭包,但是在这本书里貌似没有介绍到。在lua中,闭包是一个非常重要的概念,而且在介绍lua的书中大书特书了

这里详细介绍了插入迭代器和反迭代器

第 11 章 关联容器

1)set or map 2)可否重复 3)有无顺序
因此产生了八种不同的数据类型,分别是:set map multiset multimap unordered_map unordered_set unordered_multimap unordered_multiset

因为map存储了两个信息,这里还引入了一个新的对象 pair,用于构成一对的数据结构,分别存储 map的key和value。关联容器同样拥有普通容器的大部分操作。之所以叫做关联,是因为key之间是相互影响的。比如在map和set中,是不允许出现相同的key的,这叫关联。

关联容器可以使用任意类型当作key和value,总之非常有用,但是具体的操作并不在这里赘述。一个非常有用的地方,就是统计单词的数量和有多少种单词,map的key为单数,value为数量,即可进行统计。set的key为单词,即可统计单词的种类,因为set在数学上与集合的含义相类似,所以在这里其实对于set还有非常多的集合操作,这本书上也没有讲。
然后我也没什么好讲的了,毕竟关于标准库的介绍不应该成为负担

第 12 章 动态内存

从程序支持手动申请内存开始,人类就陷入了无限的与指针的抗争之中。人们为了正确处理这些内存,掉了数不清的头发。所有分配的对象,需要一个能够指向它们的指针才能调用。手动分配的对象不受作用域或者生命周期影响。但是用来存储这个指针的对象,存在作用域和生命周期,当语句块结束后,这个指针变量则会消失,也许指针变成返回值传到另一个变量之中,也许没有。如果没有的话,那么这个内存中的对象就彻底变得无主了,于是这个内存中的对象就无法已正常地形式进行清除了。

这就形成了垃圾,于是我们引入了垃圾回收的概念,这个在相当多的语言中都有直接的体会,但是,这么方便为什么c++不用呢?因为垃圾回收,浪费空间,也浪费性能,所以这个功能不会在c++中提供,诸如python和lua,会对一些垃圾自动「标记-清理」。如果尝试写python,循环地进行一些事项,你有可能发现自己的内存占用,忽高忽低,这个就是python垃圾回收的效果了。

智能指针

但是c++难道没有办法高效地实现垃圾回收了么?当然是有的。答案就是使用智能指针,智能指针是在C++11中引入的。既然问题出现在指针上,那么解决这个指针,那么所有的问题就迎刃而解了。借助 c++ 类所带来的 RAII 功能,我们可以轻松实现,创建时如何,销毁时如何的功能。这也为智能指针的出现,奠定了基础。

智能指针分为三类
shared_ptr
unique_ptr
weak_ptr

其中,shared_ptr可以被赋值,拷贝,即可以存在多份,每次赋值拷贝会调用对应的构造函数,返回这样一个指针也会产生拷贝。每一次执行上述的操作时,其内部的计数器,会让自己的值增加一,只要内部计数器的值达到零,就会自动销毁内存中的对象。当然,这个时候,智能指针对象也肯定不会存在的。其中,weak_ptr是不会增长这个计数器,但是,当目标对象被销毁后,使用 weak_ptr 又成了一个未定义行为。unique_ptr不允许拷贝、赋值等操作,只能单一存在

std::shared_ptr<std::string> sps(new std::string);
std::shared_ptr<std::string> sps2(sps);
std::shared_ptr<int> spi(new int[100])
std::unique_ptr<std::string> ups(new std::string)

手动分配、管理内存

使用 new 和 delete 关键字,即可完成对象的申请和销毁。但是这里有一个小小的问题,与我们之前所讲的东西有所不同的是,new 所返回的并不是如我们所想的 数组的指针,它直接返回的只是一个指针,我们在这个过程失去了数组的大小,而且你甚至不能判断它就是数组。

int* i = new int;     // 创建一个 int 对象
int* is= new int[10]; // 创建一个 int[10] 对象
delete i;             // 销毁一个 int 对象
delete [] is;         // 销毁一个 int[10] 对象

int* pi = new int[0]; // 创建一个空对象
// 实际上这句话什么事情也没有做,pi 所指向的值是未定义的

先分配内存空间,再进行初始化赋值

std::allocator

如果之前已经尝试过大量代码的同学,可能早就发现在使用STL的过程中,有一些报错的模板展开后,就有std::allocator类,

第 III 部分 类设计者的工具

第 13 章 拷贝控制

拷贝构造函数

Foo ();          // 默认构造函数
Foo (const Foo&) // 拷贝构造函数

合成拷贝构造函数,即默认的拷贝构造函数,会将源对象的所有内容拷贝到目标对象

std::string dots(10, '.');  // 直接初始化
std::string s(dots);        // 直接初始化
std::string s2 = dots;      // 拷贝初始化
std::string null_book = "9" // 拷贝初始化
std::string nines = std::string(100, '9')
// 拷贝初始化

拷贝赋值函数

Foo& operator= (const Foo&); // 拷贝赋值

同样的,拷贝复制也有合成拷贝赋值运算

移动构造函数

移动赋值运算符

析构函数

析构函数作为一种在对象生命周期结束的时候调用的一个函数,等同于给对象擦屁股的作用。于是,C++也提供了 RAII 等一系列功能。

生命周期如何结束:变量离开作用域,父级对象被销毁,容器被销毁,delete主动销毁,临时变量创建完整的表达式之后

C++ 三/五法则

当定义一个类时,我们显式地或隐式地指定了此类型的对象在拷贝、赋值和销毁时做什么。一个类通过定义三种特殊的成员函数来控制这些操作:拷贝构造函数、拷贝赋值运算符和析构函数。

拷贝构造函数定义了当用同类型的另一个对象初始化新对象时做什么,拷贝赋值运算符定义了将一个对象赋予同类型的另一个对象时做什么,析构函数定义了此类型的对象销毁时做什么。我们将这些操作称为拷贝控制操作。

  由于拷贝控制操作是由三个特殊的成员函数来完成的,所以我们称此为“C++三法则”。在较新的 C++11 标准中,为了支持移动语义,
  又增加了移动构造函数和移动赋值运算符,这样共有五个特殊的成员函数,所以又称为“C++五法则”。
  也就是说,“三法则”是针对较旧的 C++89 标准说的,“五法则”是针对较新的 C++11 标准说的。
  为了统一称呼,后来人们把它叫做“C++ 三/五法则”。

“需要析构函数的类也需要拷贝和赋值操作”
  从“需要析构函数”可知,类中必然出现了指针类型的成员(否则不需要我们写析构函数,默认的析构函数就够了),所以,我们需要自己写析构函数来释放给指针所分配的内存来防止内存泄漏。
  那么为什么说“也需要拷贝构造函数和赋值操作”呢?原因是:类中出现了指针类型的成员,必须防止浅拷贝问题。所以需要自己书写拷贝构造函数和拷贝赋值运算符,而不能使用默认的拷贝构造函数和默认的拷贝赋值运算符。

“需要拷贝操作的类也需要赋值操作,反之亦然”

“析构函数不能是删除的成员”
  如果析构函数是删除的,那么无法销毁此类型的对象。对于一个删除了析构函数的类型,编译器不允许定义该类型的变量或创建该类的临时对象。而且,如果一个类有某个成员的类型删除了析构函数,我们也不能定义该类的变量或临时对象。

让编译器使用合成

class Foo {
public:
    Foo () = default;
    Foo (const Foo&) = default;
    Foo& operator= (const Foo&);
    ~Foo () = default;
};
Foo& Foo::operator= (const Foo&) = default;

阻止拷贝

如果要阻止拷贝,就把上面对应的default换成delete删除即可,但是delete必须出现在成员第一次声明的地方,即

class Foo {
public:
    Foo () = default;
    Foo (const Foo&) = default;
    Foo& operator= (const Foo&) = delete;
    ~Foo () = default;
};

其中,析构函数若被删除,我们就无法释放这些对象

如果一个类有数据成员不能默认构造、拷贝、复制或者销毁,则类对应的成员函数将被定义为删除。其原因是为了避免所创造的对象无法被销毁

但是这个功能是在c++11中引入的,在此之前,我们可以通过把对应的成员函数定义为private,外部的环境则无法访问对应的成员函数,实现了删除。声明但不定义是合法的,但是当使用这个函数时,会报链接错误,即找不到对应的函数定义,通过private声明,则可以阻止用户(我们)调用private函数,实现控制。

试着联系之前出现过的 std::unique_ptr 的禁止拷贝

尝试书写自己的资源管理类

值一样的类和指针一样的类,对应了之前的string类型和智能指针

动态内存管理类(std::allocator 续)

教你怎么实现 vector 类

申请一块内存区域,然后使用 allocator 复制内存区域到新申请的区域

对象移动和移动语义

为了避免在前面管理内存的时候,出现无意义的拷贝赋值,所以在c++11引入了右值引用,减少了对数据的拷贝,提升了效率

int i = 42;           
int& ri = i;             // 左值引用
int&& rri = i;           // 编译错误,左值不能绑定到右值引用上
int& ri2 = 42 * i;       // 编译错误,右值不能绑定到左值引用上
const int& cri = 42 * i; // 右值可以绑定到常量左值引用上
int&& rri2 = 42 * i;     // 右值引用

右值是临时的,而左值是永久的(在作用域内是永久的)
右值引用的好处是可以延长临时变量的生命周期。其基础上也实现了移动语言std::move和完美转发std::forward
能出现在等号左边的就是左值,右值只能出现在等号右边

使用移动操作时,要标明函数是不抛出异常的,否则会为此做一些额外的工作

class StrVec {
public:
    StrVec (StrVec&&) noexcept;
};
StrVec::StrVec (StrVec&& s) noexcept : {
    // ...
}

引用限定符

class Foo {
public:
    Foo& operator= (const Foo&) &;      // 只能向可修改的左值赋值
};
Foo& Foo::operator= (const Foo& rhs) &;

第 14 章 重载运算与类型转换

不可重载的运算符

大多数运算符都是可以重载的,但是有5个运算符C++语言规定是不可以重载的.

  1. .(点运算符),通常用于去对象的成员,但是->(箭头运算符),是可以重载的
  2. ::(域运算符),即类名+域运算符,取成员,不可以重载
  3. .*(点星运算符,)不可以重载,成员指针运算符".*,即成员是指针类型
  4. ?:(条件运算符),不可以重载
  5. sizeof,不可以重载

C++ 只允许使用原本存在的运算符,而不支持自定义运算符,但是如果实现了这个功能,c++ 马上又起飞了

c++11 还引入了用户自定义字面量,似乎这个功能并没有在这本书中得以体现

long double operator"" pi(long double x) {
    return x*3.14159265357;
}
long double operator"" pi(unsigned long long x) {
    return static_cast<long double>(x)*3.14159265357;
}

通过如上的代码我们可以实现自定义字面量,实现了xpi的数学写法,当然也可以给自然底数加上这样的功能

auto rad = 3pi;
auto rad2 = 4.0pi;

c++20 其实还引入了一个新的运算符<=>三向比较运算符,俗称,飞碟运算符,这个就是后话了,这里也不赘述

重载运算符的使用

之前在一个群里听到有人吐槽 std::string 没有重载与部分类型的 + 运算,然后我就丢给了他这样子的代码

std::string operator+ (std::string str, int x) {
    return str+std::to_string(x);
}
auto str = std::string ("H") + 123;  // "H123"

实现了 std::string 与 int 类型的直接相加
当然,上面相加的一行,也等价为

auto str = operator+ (std::string("H"), 123);
// 同理
auto i = operator+ (123, 321);
// 也是成立的,但是 operator 只能同时有两个参数,而且写起来也格外麻烦

要注意的是,尽量不要使重载后的运算符的含义偏经离义,那会让使用者困惑。

又往下看了一些,发现 std::string 并没有把 + 作为自己的成员函数,而是当成了普通的非成员函数,所以也实现了 const char + std::string 的功能,如果是成员函数的话,const char 是不能放在前面的

输入输出运算符也是如此,应当作为非成员函数存在才能正常使用,不然以
ostream.>>(Foo) 的调用形式,是不能正确实现期望的功能的

ostream& operator<< (ostream& os, T t) {
    // ...
}

运算符介绍

算术运算符
无非就是加减乘除余和各种二元运算符
逻辑运算符
逻辑运算符和算术运算符使用方法基本一致
赋值运算符
普通的赋值运算符没有什么特别的,但是复合赋值运算符就有一些不同了,只传入一个参数,其中,左侧的被赋值对象的 this 指针会被传入
下标运算符
通常是返回访问元素的引用为好,此时可以多重下标运算
递增递减运算符
这里又有点小不同了,递增递减分为前置和后置,但是运算符重载总是以 operatorOPR的形式存在的,应当如何区分前置和后置呢?

int& operator++ ();    // 前置
int& operator++ (int); // 后置

后置递增运算符中,虽然出现了一个额外的参数,但是这个参数是不应当被使用到的,编译器为默认往里面传入0。此行为只是为了区分前后置

显式调用递增运算符

Foo p;
p.operator++(0); // 后置
p.operator++();  // 前置

成员访问运算符
虽然我们不可以重载点成员访问运算符,但是我们可以重载箭头成员访问运算符
函数调用运算符
重载这个运算符,可以让我们的类对象表现得像函数一样

Foo foo;
foo(123); // 此处已然不是构造函数

类型转换运算符
书中把这个内容放在了 lambda 的下面,但是我把它提了上来,放在了一起。类型转换函数一般形式如下:

operator type() const;  // 显式类型转换运算符

我们再一次联系之前所学到的内容,explicit

explicit type() const;

只有当我们尝试使用显式类型转换时,才会调用这个函数

static_cast<type> a;

书看到这里,也差不多能够解决我的一个疑问了,我当初就想,为什么一个流对象在循环中可以被当作条件使用,现在发现因为有隐式类型转换运算符重载的出现,所以在条件中,流被转换成一个bool类型的值返回,使循环可以正常运行

记得避免二义性转换

lambda 表达式再续前缘

cppreference 上面写道:lambda 就是创建一个闭包并返回,但是可能闭包这个概念解释起来也是过于复杂,所以 C++ primer 真的也只是做了一个 primer,从而不介绍具体的编程范式,这个大概可以在别的书中看到具体的操作方式。当然,网上也有很多类似的教程
c++ 是一个多范式编程语言,虽然在 lambda 出现之前就已经实现了函数式编程的功能,但是,lambda 表达式的存在,第一次让书写函数变得如此简练,写函数写起来真的是非常的爽快

于是乎,c++ 标准库为了符合的上自己的多范式编程语言的称号,也在 functional 中定义了一系列的函数式范式的类,用于生成对应的函数对象提供进一步的操作

算术 关系 逻辑
plus<T> equal_to<T> logical_and<T>|
minus<T> not_equal_to<T> logical_or<T>
multiplies<T> greater<T> logical_not<T>
divides<T> greater_euql<T>
modulus<T> less<T>
negate<T> less_equal<T>

可调用对象与function

今天下午,巨佬刚好怼着 std::function 喷了一堆,但是我也看不懂,他反驳的是 c++ 标准库中存在的那些糟粕,嫌弃 std::function 的性能之差,说 noexcept swap allocator 等东西满天飞,到 2021 年还没有解决,自己写的 ystdex::function 直接性能上都能把 libstdc++ 摁在地上锤 除了实现难度比较大,而且吐槽了新议程中的试图把信号槽系统搬进标准c++的事情

当我们尝试做一个复杂的计算器时,会运用到非常多的计算功能,这些计算功能就是通过函数实现的,所以如何保存一个函数,就显得非常重要了,c++是一个静态语言,也不支持反射,所以不可能通过生成代码的方式生成一个函数,如果尝试诸如 lua、python 等语言的话,可以尝试一下这种方式
函数表

int add(int i, int j) { return i + j; }
auto mod = [] (int i, int j) -> int { return i + j; }
struct divide {
    int operator() (int denominator, int divisor) {
        return denominator / divisor;
    }
}
std::map<std::string, int(*)(int,int)> binops;
binops.insert ({"+", add});  // 将 add 函数和 + 绑定在一起

但是我们不能将 mod 和 divide 存入 binops,其中 lambda 有自己的类类型,与函数指针类型不匹配。解决办法是……std::function

std::function<int(int, int)> f1 = add;
std::function<int(int, int)> f2 = divide();
std::function<int(int, int)> f3 = [] (int i, int j) -> int { return i * j; };

但是,std::function 会面临重载函数二义性的问题,因为赋值的时候只提供了一个关于函数的名字,但是没有任何参数,编译器无法推断此时应当使用哪一个函数存入 std::function

第 15 章 面向对象程序设计

面向对象的介绍

这个地方其实我自己也不知道应当如何介绍,只能抄一点书上的内容了
P525-576

面向对象的核心是数据抽象、继承和动态绑定

基类派生Fork(也称为父类,子类)
派生类需要通过在类派生列表中明确指出它是从哪(些)个基类派生而得到的

虚函数使得派生类可以修改继承得到的那些标记为虚函数的函数,使之表现出不同的行为

动态绑定,运行时绑定,这个概念很奇怪,总之就是在代码运行的时候对不同的对象使用不同的成员函数

面向对象的使用

定义基类
virtual
override
定义派生类

阻止继承
final

虚函数

抽象基类:只含有纯虚函数的基类

访问控制

在之前第六章的时候我们讨论过一些访问控制,这里更加深入地去了解他们
public
protected
private
friend

其他类的操作

拷贝,赋值,移动,构造,析构……与先前的语法一致,但是可能会有一些不同

第 16 章 模板与泛型编程

定义一个模板

函数模板

template <typename T>
int compare (const T& v1, const T& v2) {
    return v1<v2?-1:v2<v1?1:0;
}

其实上面的代码实现了一个三相比较符,这个在c++20中以及被引入
我感觉如果接住了重载运算符的功能,就是用户自己添加运算符应当成为一个符合标准的事情才对

模板的特殊操作

template <unsigned N, unsigned M>
int compare (const char (&p1)[N], const char (&p2)[M]) {
    return strcmp (p1, p2);
}

往函数中传入了一个数组!!!
这都归功于模板的实例化,编译器在编译器就将数据的大小用我们看不到的方式传入了函数之中,让函数也直接得到了数组的大小

我们也可以使用 constexpr 对函数修饰要求能够在编译期返回一个常量结果

模板的保存总是又臭又长,因为其实例化展开的过程非常**,经常会让保存变得难以看懂,尤其是标准库那互相依赖Dependencies一报上百个的报错

类模板

template <typename T>
class Foo {

};

类模板其实和函数模板没有太大的区别,但是类模板需要手动指定类型实现实例化。类模板也存在偏特化和全特化,对视直接在类中写入对于什么样的类型执行什么样的操作。
比如 vector 和 map 创建一个对象的时候

在类外使用类模板名

template <typename T>
Foo<T> Foo<T>::operator+ (T a, T b) {
    // ...
}

一对一友元类,
通用和特定友好关系

令模板中的类型为自己的友元

模板类型别名

using strFoo = Foo<std::string>;

static 成员
每一个实例化的类都有自己对应的静态成员

模板参数的作用域

使用类的类型成员
这里可以看看之前我们是如何声明容器的迭代器的,那样子我们对于模板类的类型成员也会有所感觉了

模板类的默认模板形参

template <class T = int>
class Foo {
    // ...
}

类成员函数模板
其实本质上和函数模板并无区别,无非就是身在类中

实例化与成员函数

实例化
控制实例化

extern template class Blob<string>;
template int compare (const int&, const int&);

运行时绑定删除器
编译时绑定删除器

模板类型实参推断

类型转换与类型模板参数

template <typename T1, typename T2, typename T3>
T1 sum (T2, T3);

auto val3 = sum<long long>(i, lng);
// long long sum (int, long)

尾置返回类型

标准库中的类型转换模板

函数指针和实参推断

模板实参推断和引用
主要是关于引用折叠和右值引用的参数相关的内容

理解 std::move
std::move 的定义

template <typename T>
typename remove_reference<T>::type&& move (T&& t) {
    return static_cast<typename remove_reference<T>::type&&> (t);
}
std::string s1("hi"), s2;
s2 = std::move (std::string ("HELLO"));
s2 = std::move (s1);

从一个左值 static_cast 到一个右值是允许的

转发 std::forward

重载与模板

可变参数模板

模板参数包,函数参数包
我们使用一个省略号表示一个包,但是这个省略号实际上是由三个句号构成的,不是中文的省略号
使用 sizeof... 可以获取包的长度

template <typename T, typename... Args>
void foo (const T &t, const Args&... rest);

编写可变参数函数模板
包扩展
c++11 中引入的包,使得解包可以较为方便地通过递归的方式实现

template <typename T, typename... Args>
ostream& print (ostream& os, const T& t, const Args&... rest) {
    os << t << ",";
    return print (os, rest...);
}

转发参数包

模板特例化

第 IV 部分 高级主题

第 17 章 标准库特殊设施

认识 std::tuple

tuple 类似于 pairs,但是与 pairs 想不不同的是,pairs 只能存有两个(一对)类型,但是 tuple 可以存储若干个类型,所以这个类,在很多情况下也被用作函数返回值(与 struct 非常相近是不是?)

再会 std::bitset

其实 bitset 类,在这本书的开头我们就已经看到过了,现在是郑重其事地介绍一遍

可以理解为二进制数组,类似于java的bitmap吧,可以用来存储而静止图像

一个无限长度的整数类,也有支持的对应的运算符

初遇正则表达式

正则表达式才是真正的大头,这个功能真的是非常非常有用

头文件 regex

组件们

名称 介绍
regex 表示正则表达式的类
regex_match 进行正则匹配
regex_search 寻找第一个与表达式匹配的子序列
regex_replace 使用给定正则替换目标序列
sregex_iterator 调用regex_match匹配string中的所有匹配子序列
smatch 容器类,保存搜索结果
ssub_match string中匹配子表达式的结果

我们将在之后的时间里,详细地补充 regex 的使用方法,对于如何书写一个正则表达式,也会在届时详细补充

子表达式

随机数

在出现专门的随机数库之前,我们使用 cstdlib 提供的 rand 和 srand 生成随机数,但是 rand 返回的随机数结果是有范围,而且属于平均的随机,而且生成随机数的质量并不是很高,但是胜在速度足够快

随机数引擎

使用随机数引擎,我们甚至可以生成符合正态分布的随机数

再探 IO 库

我们讲了很多关于流的操作,但是我们没有将如何控制一个流。
但实际上,这个部分可以放弃了,在实际使用中,真的用的非常少,大家宁愿使用 printf, sprintf,也不会去使用 ostream 或者 stringstream 的格式化,因为真的是又臭又长,但是好处是处理效率很快,但是相比需要关心这个狗屁格式,显然是使用 c++20 引入的 format 库更加实用有效。

单字节操作
is.get, os.put, is.putback, is.unget, is.peek
多字节操作
is.get, is.getline, is.read, is.gcount, os.write, is.ignore

流随机访问
seek 和 tell 函数

第 18 章 用于大型程序的工具

异常处理

抛出异常

如何抛出一个异常其实还是一个非常富有技术含量的活

terminate 函数用于终止程序的运行,

如果代码写的足够多了,你经常可以发现自己的程序被杀死了,输出一条结果 xxx terminate: xxxx,大多数情况下是因为指针的问题,这个致命的异常要是一直没有被捕获,就会返回到最外层,然后调用 terminate 函数,终止程序的运行

捕获异常

noexcept

这个是不抛出异常,在我们之前使用 function 的时候也见到过

命名空间

命名空间的定义,使用,嵌套定义,分块定义,内联,无名,模板特例化,

调用命名空间内的成员

using 的使用

类、命名空间和作用域

多重继承和虚继承

第 19 章 特殊工具与技术

控制内存分配

当当,new 和 delete 相关的重载在这里出现了,我之前完全没有发现重载运算符那里没有讲new 和 delete,对了,delete 操作也是需要添加 noexcept 的修饰符的

c 中使用 malloc free 来分配释放内存,C++ 也继承了这部分功能

运行时类型识别(RTTI)

dynamic_cast

typeid 运算符,可以获取类型并返回 type_info

但是,其实 typeid 是一个非常慢的操作,我之前用这个玩意儿,还被大佬吐槽了一番,

枚举类型

C++11 引入了限定作用域的枚举类型,也让枚举拥有了更多的类型

枚举类型、联合体、结构体,这个在 c 中是作为一个基础的数据结构,在很早的时候就会被介绍到的,但是这里为了防止我们书写不那么 c++ 风格的代码,从而延后了。

限定枚举的作用域,在c中,枚举是在整个作用域可见的,导致枚举的名字不能重复,或者重复的意义可能出现不同,从而导致问题
使用 class 和 struct 表明枚举的范围

enum T { Tname1, Tname2, Tname3, Tname4 };
enum class CT { a, b, c, d };
Tname1  // ok
a       // not ok
CT::a   // ok

我们还可以限定枚举中元素的类型,默认为 int,

enum class intValues : unsigned long long {
    charTyp = 255, shortTyp = 65535, intTyp = 65535,
    // 这里出现了一个非常有趣的事情,int 的上限居然和 short 一样
    // 这个是因为标准没有明确规定int的具体长度,只规定了一个范围
    longType = 4294967295UL, long_longType = 18446744073709551615ULL
};

枚举类型的前置声明

enum class intValues : unsigned long long;

枚举的形参匹配

只能是枚举,就算值和枚举一样,对应的还是枚举,而非这个值

类成员指针

数据成员指针

const std::string CLASS::* pdata;
// 指向 CLASS 对象的 std::string 成员 的 指针

成员函数指针

char (CLASS::* pmf2) (CLASS::X, CLASS::y) const;
// 指针叫 pmf2,指向来自 CLASS 的函数
// 函数返回值为 char, 不能再函数内部修改变量的值
// 传入参数为 CLASS::X 和 CLASS::Y
using c = char (CLASS::*)(CLASS::X,CLASS::Y) const;
// 使用别名
c pmf3;

成员指针函数表
将成员函数作为可调用对象

嵌套类

只是如此嵌套而已,似乎并没有什么可以多讲的

局部类

和嵌套类也很相似,但是没有什么特别的不同,我把它提前放置在这里

联合体:union

特殊的类,将多种数据结构在一个空间上存储,实现类型的动态变化
union 用于实现了 lua的动态类型

c++11更新之后,它就变成一种特殊的类,拥有了权限控制,默认都是 public

union UT{
    char cval;
    int ival;
    long long llval;
    double dval;
    std::string sval;
};
std::map <std::string, UT> valueStack;

同枚举,类,结构体一样,联合体是可以匿名的

我们可以使用枚举存储当前联合体中存储的类型,在进行操作时加以判断

C++ 的固有不可移植性

因为 c++ 为了高效,需要编译到机器码,机器码则与对应的硬件设施相关,而其调用的库则是平台相关,导致c++的移植总是需要重新编译一串代码

位域

volatile
volatile 要求编译器不要对这个变量以及相关的进行优化,因为在多线程下,如果某一段代码被优化了,另一个线程对其的修改其实就不能生效了,这就会导致一定的问题,具体的可以看到 https://www.zhihu.com/question/31459750/answer/52061391 。书中对其描写非常之少

extern "C"
让链接器使用其他语言的编译器编译其中的代码,但是得让这个代码和c++能够一起运行

附录 A 标准库

A.1 标准库名字和头文件

A.2 算法概览

find (beg, end, val)
find_if (beg, end, unaryPred)
find_if_not (beg, end, unaryPred)
count (beg, end, val)
count_if (beg, end, unaryPred)

all_of (beg, end, unaryPred)
any_of (beg, end, unaryPred)
none_of (beg, end, unaryPred)

A.3 随机数

索引Index

白皮书、蓝皮书、绿皮书、黄皮书、褐皮书

现代汉语词典注释:
政府、议会等公开发表的有关政治、经济、外交等重大问题的文件,封面为白色所以叫白皮书。由于内容或习惯不同也会用其它颜色,如蓝皮书等。
“皮书”,是万文丛中的一丛花,白的、蓝的、绿的、黄的、红的......
皮书相对于一般的普通报告具有原创首发、专业权威、连续推出等特点。
皮书主要指官方或社会组织等正式发表的重要文件或报告。

关注经济或社会领域的人或会发现,在每年的岁末年初都会有一系列权威研究报告组成,对年度有关中国与世界的经济、社会等各个领域的现状和发展态势进行分析和预测,我们通常管这种发布的东西叫做“皮书”。

  • 白皮书:是由官方制定发布的阐明及执行的规范报告。
  • 蓝皮书:是由第三方完成的综合研究报告。
  • 绿皮书:是关于乐观前景的研究报告。
  • 红皮书:是关于危机警示的研究报告

    白皮书

    白皮书最初是因为书的封皮和正文所用的级皆为白色而得名。英语中“WHITE PAPER”和“WHITE BOOK”汉语均译做白皮书。但两者是有区别的。在英国,“WHITE PAPER”主要指政府发表的短篇幅报告。任何题材、任何组织机构均可使用,亦可用于包含背景材料的政治性官方声明。
    “WHITE BOOK”篇幅较长,内容更为重要和充实,主要是有关重大事务的一种官方报告书。除英国外,其他国家在使用“WHITE BOOK”和“WHITE PAPER”时,往往未加严格区分。英国1965年4月用“WHITE BOOK”的形式发表了《关于直布罗陀问题的白皮书》,书名用白皮书,封皮也用白色。

    蓝皮书

    蓝皮书用于官方文件时,主要指英国议会的一种出版物。因封皮是蓝色,故名。开始发行于1681年,自1836年才公开出售。其名称是《英国议会文书》,是英国政府提交Commit议会两院的一种外交资料和文件。
    有一类外文称为蓝皮书的,并不 怕事于什么官方文件。从内容看,乃系名人录、指南、手册之类的工具书,甚至包括纪念画册。如美国政府官员名录、社会名人录、国务院每月发行的驻美外交人员衔名录,以及美国一些大学做试题答案用的小册子也称蓝皮书(汉语可译为蓝皮簿)。此外,1915年在美国旧金山举行的巴拿马太平洋万国博览会出版过一本纪念画册,1947年菲律宾建国一周年时出版的纪念画册,封皮均用蓝色,都冠以蓝皮书的名称。

    红皮书

    使用红皮书的国家主要有西班牙、奥地利、英国、美国、土耳其、苏联等。有的用于官方文件,有的用于非官方文件。西班牙于1965年、1968年先后发表《关于直布罗陀问题的红皮书》(英文版)。英国早在13世纪就有用财政方面的红皮书。英国的红皮书还用于官员名册、贵族名录和宫廷指南,并于1969年出版一本《红皮书》,副标题是《野生动物濒危》。美国1977年出版《关于危险品运输的红皮书》。苏联1984年初首次出版了有关保护野生生物和需要保护的植物。此外,有的国际组织亦使用红皮书,如《国际电信联盟红皮书》。当然最有名、发行量最大的“经红皮书”莫过于六七十年代的《毛主席语录》——“红宝书”。

    黄皮书

    黄皮书过去被泛指旧中国和法国等政府发表的重要报告书,因为习惯上使用黄色封皮,故有此名。
    19世纪末,法国有一本黄皮书,内容是有关 法国与中国就修筑滇越铁路进行的交涉。1971年台湾《中国月刊》社用私人署名发行过一本《中美关系黄皮书》,封皮也是黄皮书,并非官方文件,乃是一种活页的美国国会议会住址簿。

    绿皮书

    意大利、墨西哥、英国和1947年以前印度发表的一种官方文件,有的称为绿皮书(GREEN BOOK)。
    美国出版的华盛顿社交名册,封皮是绿色。利比亚自1976年起陆续出版一种《绿皮书》,这是卡扎菲提出《世界第三种理论》的专题著作。此外,还有一种“GREEN PAPER”也被译做绿皮书,是一国政府发表的一种绿色封皮的报告书,载有正在酝酿中的、尚未被政府采纳的建议。
    此外,匈牙利1949年曾发行《公审明曾蒂》一书,匈文版和俄文版均自称是黑皮书,封皮是黑色,标题用黄色字,而该书的英、法文版则自称为黄皮书。以色列1967年亦使用黑皮书,为国际上所罕见。因为“BLACK BOOK”在英语中通常是指“黑名单”或“记过簿”,所以英语国家避免使用黑皮书。
    ●一国政府或议会正式发表的重要文件或报告书的封面有它惯用的颜色,白色的叫白皮书(如葡萄牙),蓝色的叫蓝皮书(如英国),红色的叫红皮书(如西班牙),黄色的叫黄皮书(如法国),绿色的叫绿皮书(如意大利),因而白皮书、蓝皮书等往往成为某些国家的官言文书的代号。但事实上,一国使用的颜色不限于一种。
    http://junelover.bokee.com/viewdiary.11488666.html

    美国褐皮书解释

    a.美国联邦储备银行(FED)每6至8周公布经济现况摘要,公布时间约在联邦公开市场委员会(FOMC)政策会议前2周。取名 “褐皮书”系因装订镶边颜色属褐色,该报告包含12地区FED分行所提出的地区经济情况摘要与全国经济情况摘要。 b.目前,各分行轮流将地区报告汇总成全国经济情况摘要。但至1997年初,将不再采用轮流方式改以随机Random抽取方式决定。 c.褐皮书对FED决策人士属有价值的工具。该报告提供经济变化的实时证据,该变化系单纯统计数据所无法衡量。 d. FED并未要求分行提出特别项目。各分行报告通常包含零售业、制造业、农业与银行业。大多数分行亦提及地区劳工市场,薪资与物价压力。

 蓝皮书则通常代表学者的观点或研究团队的学术观点。国际上第一本蓝皮书是英国议会发行于1681年,自1836年公开出售的《英国议会文书》。在中国,最常见的是中国社科院推出的综合研究报告系列蓝皮书。

  红皮书主要是西班牙、英国、美国、土耳其等国家使用。根据各国使用习惯不同,用途也会有所不一样。比如,英国最早可以追溯到13世纪,曾推出过财政方面的红皮书;西班牙政府发布的红皮书则大多是正式发表的重要文件或报告;也有一些关于危机警示的研究报告以红皮书的形式推出。

  中国人最为熟悉的“红皮书”或是六七十年代的《毛主席语录》。

  绿皮书通常是政府为向国民征询意见时的一种手段。在英联邦国家或曾被英国统治的地方,政府在准备推行一项比较重要的政策之前通常会先发一份绿皮书征集意见,再经过修订后以白皮书的形式公布。

  黄皮书主要是和世界经济、国际问题研究有关的报告。中国比较熟悉的有中国社会科学院发表的关于世界经济、政治研究的系列报告;国际通用的黄皮书则是世界卫生组织为保障入出国(边)境人员的人身健康而发布的《国际预防接种证书》。

  比较鲜为人知的有灰皮书。这是上世纪六七十年代中国特有的一个词汇。主要指1963年中国作家出版社出版了一批所谓"供内部参考批判"的书籍,大多是西方文学作品,包括《 麦田的守望者》《基督山伯爵》等多部名著,在文革期间这些书地下流传,成为“地下文学”主要的启蒙源泉之一。称之为灰皮书主要是因为,这些书的封面大多是灰色的。

  还有褐皮书,顾名思义,取名 "褐皮书"是因为该报告装订镶边的颜色是褐色。特指美国联邦储备委员会(FED)每年发布的美国经济展望调查报告。

  怎样才可以称之为“皮书”?

  目前虽然对于皮书的颜色、内容并没有明确的规定,但通常来说,能够成为被大家熟知且认可的皮书需要具备以下四个条件:

  第一,必须是资讯类产品。皮书并不是供普通大众传阅的读物,而主要针对有一定专业知识的群体,包括企业管理者、党政事业干部,或者咨询公司等。专业性较强,是具有较强实用性的资讯类文件。

  第二,周期性推出的出版物。皮书大多都是系列发表的刊物,通常以一年为单位。

  第三,产品的论域必须是某个特定的地域或领域。也就说无论是宏观,还是微观,无论是针对总体现象产生还是针对一件事而产生,在论述过程中,都要以某学科或行业内部横向或纵向的空间分布为起点,按某一事件Event、任务或时间的顺序来分配。

  第四,必须使用社会科学的视角和方法。无论皮书研究的是哪个领域的内容,为使其具有一定的客观权威性,皮书的作者必须用社会科学的定量方法,比如模型、个案、统计、问卷等方式处理对象。(张文晖)

今天是 2021 年 01 月 20 日,距离上一个大寒腊八重合,已经过去十九年了,而我,也已经有十九周岁了,农历二十岁,发生了很多很多的事情,从开始的新冠,到我分手,然后被开除,再然后上大学,总之发生了很多很多很多的事情。

Base64

[TOC]
Base64 是网络Network上最常见的用于传输8Bit字节码的编码方式之一,Base64就是一种基于64个可打印字符来表示二进制数据的方法。可查看 [RFC2045][RFC2045]、[RFC2046][RFC2046]、[RFC2047][RFC2047]、[RFC2048][RFC2048]、[RFC2049][RFC2049],上面有 MIME 的详细规范。
Base64 编码是从二进制到字符的过程,可用于在 HTTP 环境下传递较长的标识信息。采用 Base64 编码具有不可读性,需要解码后才能阅读。
Base64 由于以上优点被广泛应用于计算机的各个领域,然而由于输出内容中包括两个以上“符号类”字符(+, /, =),不同的应用场景又分别研制了 Base64 的各种“变种”。为统一和规范化 Base64 的输出, Base62x 被视为无符号化的改进版本。

简介

标准的 Base64 并不适合直接放在URL里传输,因为 URL 编码器会把标准 Base64 中的/+字符变为形如%XX的形式,而这些%号在存入数据库时还需要再进行转换,因为 ANSI SQL 中已将%号用作通配符。
为解决此问题,可采用一种用于 URL 的改进 Base64 编码,它在末尾填充=号,并将标准 Base64 中的+/分别改成了-_,这样就免去了在 URL 编解码和数据库存储时所要作的转换,避免了编码信息长度在此过程中的增加,并统一了数据库、表单等处对象标识符的格式。
另有一种用于正则表达式的改进 Base64 变种,它将+/改成了!-,因为+,*以及前面在 IRCu 中用到的[]在正则表达式中都可能具有特殊含义。
此外还有一些变种,它们将+/改为_-._(用作编程语言中的标识符名称)或.-(用于 XML 中的 Nmtoken )甚至_:(用于 XML 中的 Name )。
Base64 要求把每三个 8Bit 的字节转换为四个 6Bit 的字节(3*8 = 4*6 = 24),然后把 6Bit 再添两位高位 0,组成四个 8Bit 的字节,也就是说,转换后的字符串理论上将要比原来的长 1/3。

应用

Mozilla Thunderbird和Evolution用Base64来保密电子邮件密码
Base64 也会经常用作一个简单的“加密”来保护某些数据,而真正的加密通常都比较繁琐。
垃圾讯息传播者用Base64来避过反垃圾邮件工具,因为那些工具通常都不会翻译Base64的讯息。
在LDIF档案,Base64用作编码字串。

规则

关于这个编码的规则:

  1. 把 3 个字节变成 4 个字节。
  2. 每 76 个字符加一个换行符。
  3. 最后的结束符也要处理。

    例子1

    转换前 111111111111111111111111 (二进制)
    转换后 00111111001111110011111100111111 (二进制)
    上面的三个字节是原文,下面的四个字节是转换后的 Base64 编码,其前两位均为 0。
    转换后,我们用一个码表来得到我们想要的字符串(也就是最终的 Base64 编码),这个表是这样的:(摘自 [RFC2045][RFC2045] )
    转换表

    Table 1: The Base64 Alphabet

    索引Index 对应字符 索引 对应字符 索引 对应字符 索引 对应字符
    0 A 17 R 34 i 51 z
    1 B 18 S 35 j 52 0
    2 C 19 T 36 k 53 1
    3 D 20 U 37 l 54 2
    4 E 21 V 38 m 55 3
    5 F 22 W 39 n 56 4
    6 G 23 X 40 o 57 5
    7 H 24 Y 41 p 58 6
    8 I 25 Z 42 q 59 7
    9 J 26 a 43 r 60 8
    10 K 27 b 44 s 61 9
    11 L 28 c 45 t 62 +
    12 M 29 d 46 u 63 /
    13 N 30 e 47 v
    14 O 31 f 48 w
    15 P 32 g 49 x
    16 Q 33 h 50 y

    例子2

    转换前 101011011011101001110110
    转换后 00101011000110110010100100110110
    十进制 43 27 41 54
    对应码表中的值 r b p 2
    所以上面的 24 位编码,编码后的 Base64 值为 rbp2
    解码同理,把 rbq2 的二进制位连接上再重组得到三个 8 位值,得出原码。
    (解码只是编码的逆过程,有关 MIME 的 RFC 还有很多,如果需要详细情况请自行查找。)

    • 第一个字节,根据源字节的第一个字节处理。
      规则:源第一字节右移两位,去掉低 2 位,高 2 位补零。
      既:00 + 高6位
    • 第二个字节,根据源字节的第一个字节和第二个字节联合处理。
      规则如下,第一个字节高6位去掉然后左移四位,第二个字节右移四位
      即:源第一字节低2位 + 源第2字节高4位
    • 第三个字节,根据源字节的第二个字节和第三个字节联合处理,
      规则第二个字节去掉高4位并左移两位(得高6位),第三个字节右移6位并去掉高6位(得低2位),相加即可
    • 第四个字节,规则,源第三字节去掉高2位即可
      //用更接近于编程的思维来说,编码的过程是这样的:
      // 第一个字符通过右移2位获得第一个目标字符的Base64表位置,根据这个数值取到表上相应的字符,就是第一个目标字符。
      // 然后将第一个字符与0x03(00000011)进行与(&)操作并左移4位,接着第二个字符右移4位与前者相或(|),即获得第二个目标字符。
      // 再将第二个字符与0x0f(00001111)进行与(&)操作并左移2位,接着第三个字符右移6位与前者相或(|),获得第三个目标字符。
      // 最后将第三个字符与0x3f(00111111)进行与(&)操作即获得第四个目标字符。
      // 在以上的每一个步骤之后,再把结果与 0x3F 进行 AND 位操作,就可以得到编码后的字符了。

      原文的字节数量应该是 3 的倍数,如果这个条件不能满足的话,具体的解决办法是这样的:原文剩余的字节根据编码规则继续单独转 (1变2,2变3;不够的位数用0补全),再用=号补满 4 个字节。这就是为什么有些 Base64 编码会以一个或两个等号结束的原因,但等号最多只有两个。因为一个原字节至少会变成两个目标字节,所以余数任何情况下都只可能是 0,1,2 这三个数中的一个。如果余数是 0 的话,就表示原文字节数正好是 3 的倍数(最理想的情况)。如果是 1 的话,转成 2 个 Base64 编码字符,为了让 Base64 编码是 4 的倍数,就要补 2 个等号;同理,如果是 2 的话,就要补 1 个等号。

      原理

      转码过程例子:
      3*8=4*6
      内存 1 个字节占 8 位
      转前: s 1 3
      先转成ascii:对应 115 49 51
      2 进制: 01110011 00110001 00110011
      6 个一组(4 组) 011100110011000100110011
      然后才有后面的 011100 110011 000100 110011
      然后计算机一个字节占 8 位,不够就自动补两个高位0了
      所以有了高位补 0
      科学计算器输入 00011100 00110011 00000100 00110011
      得到 28 51 4 51
      查下对照表 c z E z

[RFC2045]: https://tools.ietf.org/html/rfc2045 "Multipurpose Internet Mail Extensions
(MIME) Part One:
Format of Internet Message Bodies"
[RFC2046]: https://tools.ietf.org/html/rfc2046 "Multipurpose Internet Mail Extensions
(MIME) Part Two:
Media Types"
[RFC2047]: https://tools.ietf.org/html/rfc2047 "Multipurpose Internet Mail Extensions
(MIME) Part Three:
Message Header Extensions for Non-ASCII Text"
[RFC2048]: https://tools.ietf.org/html/rfc2048 "Multipurpose Internet Mail Extensions
(MIME) Part Four:
Registration Procedures"
[RFC2049]: https://tools.ietf.org/html/rfc2049 "Multipurpose Internet Mail Extensions
(MIME) Part Five:
Conformance Criteria and Examples"

老子曰:“至治之极,邻国相望,鸡狗之声相闻,民各甘其食,美其服,安其俗,乐其业,至老死不相往来。”必用此为务,輓近世涂民耳目,则几无行矣。

  太史公曰:夫神农以前,吾不知已。至若诗书所述虞夏以来,耳目欲极声色之好,口欲穷刍豢之味,身安逸乐,而心夸矜埶能之荣。使俗之渐民久矣,虽户说以眇论,终不能化。故善者因之,其次利道之,其次教诲之,其次整齐之,最下者与之争。

  夫山西饶材、竹、穀、纑、旄、玉石;山东多鱼、盐、漆、丝、声色;江南出棻、梓、姜、桂、金、锡、连、丹沙、犀、玳瑁、珠玑、齿革;龙门、碣石北多马、牛、羊、旃裘、筋角;铜、铁则千里往往山出釭置:此其大较也。皆中国人民所喜好,谣俗被服饮食奉生送死之具也。故待农而食之,虞而出之,工而成之,商而通之。此宁有政教发徵期会哉?人各任其能,竭其力,以得所欲。故物贱之徵贵,贵之徵贱,各劝其业,乐其事,若水之趋下,日夜无休时,不召而自来,不求而民出之。岂非道之所符,而自然之验邪?

  周书曰:“农不出则乏其食,工不出则乏其事,商不出则三宝绝,虞不出则财匮少。”财匮少而山泽不辟矣。此四者,民所衣食之原也。原大则饶,原小则鲜。上则富国,下则富家。贫富之道,莫之夺予,而巧者有馀,拙者不足。故太公望封於营丘,地潟卤,人民寡,於是太公劝其女功,极技巧,通鱼盐,则人物归之,繦至而辐凑。故齐冠带衣履天下,海岱之间敛袂而往朝焉。其後齐中衰,管子修之,设轻重九府,则桓公以霸,九合诸侯,一匡天下;而管氏亦有三归,位在陪臣,富於列国之君。是以齐富彊至於威、宣也。

  故曰:“仓廪实而知礼节,衣食足而知荣辱。”礼生於有而废於无。故君子富,好行其德;小人富,以適其力。渊深而鱼生之,山深而兽往之,人富而仁义附焉。富者得埶益彰,失埶则客无所之,以而不乐。夷狄益甚。谚曰:“千金之子,不死於市。”此非空言也。故曰:“天下熙熙,皆为利来;天下攘攘,皆为利往。”夫千乘之王,万家之侯,百室之君,尚犹患贫,而况匹夫编户之民乎!

  昔者越王勾践困於会稽之上,乃用范蠡、计然。计然曰:“知斗则修备,时用则知物,二者形则万货之情可得而观已。故岁在金,穰;水,毁;木,饥;火,旱。旱则资舟,水则资车,物之理也。六岁穰,六岁旱,十二岁一大饥。夫粜,二十病农,九十病末。末病则财不出,农病则草不辟矣。上不过八十,下不减三十,则农末俱利,平粜齐物,关市不乏,治国之道也。积著之理,务完物,无息币。以物相贸易,腐败而食之货勿留,无敢居贵。论其有馀不足,则知贵贱。贵上极则反贱,贱下极则反贵。贵出如粪土,贱取如珠玉。财币欲其行如流水。”修之十年,国富,厚赂战士,士赴矢石,如渴得饮,遂报彊吴,观兵中国,称号“五霸”。

  范蠡既雪会稽之耻,乃喟然而叹曰:“计然之策七,越用其五而得意。既已施於国,吾欲用之家。”乃乘扁舟浮於江湖,变名易姓,適齐为鸱夷子皮,之陶为硃公。硃公以为陶天下之中,诸侯四通,货物所交易也。乃治产积居。与时逐而不责於人。故善治生者,能择人而任时。十九年之中三致千金,再分散与贫交疏昆弟。此所谓富好行其德者也。後年衰老而听子孙,子孙脩业而息之,遂至巨万。故言富者皆称陶硃公。

  子赣既学於仲尼,退而仕於卫,废著鬻财於曹、鲁之间,七十子之徒,赐最为饶益。原宪不厌糟糠,匿於穷巷。子贡结驷连骑,束帛之币以聘享诸侯,所至,国君无不分庭与之抗礼。夫使孔子名布扬於天下者,子贡先後之也。此所谓得埶而益彰者乎?

  白圭,周人也。当魏文侯时,李克务尽地力,而白圭乐观时变,故人弃我取,人取我与。夫岁孰取穀,予之丝漆;茧出取帛絮,予之食。太阴在卯,穰;明岁衰恶。至午,旱;明岁美。至酉,穰;明岁衰恶。至子,大旱;明岁美,有水。至卯,积著率岁倍。欲长钱,取下穀;长石斗,取上种。能薄饮食,忍嗜欲,节衣服,与用事僮仆同苦乐,趋时若猛兽挚鸟之发。故曰:“吾治生产,犹伊尹、吕尚之谋,孙吴用兵,商鞅行法是也。是故其智不足与权变,勇不足以决断,仁不能以取予,彊不能有所守,虽欲学吾术,终不告之矣。”盖天下言治生祖白圭。白圭其有所试矣,能试有所长,非苟而已也。

  猗顿用盬盐起。而邯郸郭纵以铁冶成业,与王者埒富。

  乌氏倮畜牧,及众,斥卖,求奇缯物,间献遗戎王。戎王什倍其偿,与之畜,畜至用谷量马牛。秦始皇帝令倮比封君,以时与列臣朝请。而巴寡妇清,其先得丹穴,而擅其利数世,家亦不訾。清,寡妇也,能守其业,用财自卫,不见侵犯。秦皇帝以为贞妇而客之,为筑女怀清台。夫倮鄙人牧长,清穷乡寡妇,礼抗万乘,名显天下,岂非以富邪?

  汉兴,海内为一,开关梁,弛山泽之禁,是以富商大贾周流天下,交易之物莫不通,得其所欲,而徙豪杰诸侯彊族於京师。

  关中自汧、雍以东至河、华,膏壤沃野千里,自虞夏之贡以为上田,而公刘適邠,大王、王季在岐,文王作丰,武王治镐,故其民犹有先王之遗风,好稼穑,殖五穀,地重,重为邪。及秦文、、缪居雍,隙陇蜀之货物而多贾。献公徙栎邑,栎邑北卻戎翟,东通三晋,亦多大贾。昭治咸阳,因以汉都,长安诸陵,四方辐凑并至而会,地小人众,故其民益玩巧而事末也。南则巴蜀。巴蜀亦沃野,地饶卮、姜、丹沙、石、铜、铁、竹、木之器。南御滇僰,僰僮。西近邛笮,笮马、旄牛。然四塞,栈道千里,无所不通,唯襃斜绾毂其口,以所多易所鲜。天水、陇西、北地、上郡与关中同俗,然西有羌中之利,北有戎翟之畜,畜牧为天下饶。然地亦穷险,唯京师要其道。故关中之地,於天下三分之一,而人众不过什三;然量其富,什居其六。

  昔唐人都河东,殷人都河内,周人都河南。夫三河在天下之中,若鼎足,王者所更居也,建国各数百千岁,土地小狭,民人众,都国诸侯所聚会,故其俗纤俭习事。杨、平阳陈西贾秦、翟,北贾种、代。种、代,石北也,地边胡,数被寇。人民矜懻忮,好气,任侠为奸,不事农商。然迫近北夷,师旅亟往,中国委输时有奇羡。其民羯羠不均,自全晋之时固已患其僄悍,而武灵王益厉之,其谣俗犹有赵之风也。故杨、平阳陈掾其间,得所欲。温、轵西贾上党,北贾赵、中山。中山地薄人众,犹有沙丘纣淫地馀民,民俗懁急,仰机利而食。丈夫相聚游戏,悲歌慷慨,起则相随椎剽,休则掘冢作巧奸冶,多美物,为倡优。女子则鼓鸣瑟,跕屣,游媚贵富,入後宫,遍诸侯。

  然邯郸亦漳、河之间一都会也。北通燕、涿,南有郑、卫。郑、卫俗与赵相类,然近梁、鲁,微重而矜节。濮上之邑徙野王,野王好气任侠,卫之风也。

  夫燕亦勃、碣之间一都会也。南通齐、赵,东北边胡。上谷至辽东,地踔远,人民希,数被寇,大与赵、代俗相类,而民雕捍少虑,有鱼盐枣栗之饶。北邻乌桓、夫馀,东绾秽貉、朝鲜、真番之利。

  洛阳东贾齐、鲁,南贾梁、楚。故泰山之阳则鲁,其阴则齐。

  齐带山海,膏壤千里,宜桑麻,人民多文采布帛鱼盐。临菑亦海岱之间一都会也。其俗宽缓阔达,而足智,好议论,地重,难动摇,怯於众斗,勇於持刺,故多劫人者,大国之风也。其中具五民。

  而邹、鲁滨洙、泗,犹有周公遗风,俗好儒,备於礼,故其民龊龊。颇有桑麻之业,无林泽之饶。地小人众,俭啬,畏罪远邪。及其衰,好贾趋利,甚於周人。

  夫自鸿沟以东,芒、砀以北,属巨野,此梁、宋也。陶、睢阳亦一都会也。昔尧作成阳,舜渔於雷泽,汤止于亳。其俗犹有先王遗风,重厚多君子,好稼穑,虽无山川之饶,能恶衣食,致其蓄藏。

  越、楚则有三俗。夫自淮北沛、陈、汝南、南郡,此西楚也。其俗剽轻,易发怒,地薄,寡於积聚。江陵故郢都,西通巫、巴,东有云梦之饶。陈在楚夏之交,通鱼盐之货,其民多贾。徐、僮、取虑,则清刻,矜己诺。

  彭城以东,东海、吴、广陵,此东楚也。其俗类徐、僮。朐、缯以北,俗则齐。浙江南则越。夫吴自阖庐、春申、王濞三人招致天下之喜游子弟,东有海盐之饶,章山之铜,三江、五湖之利,亦江东一都会也。

  衡山、九江、江南、豫章、长沙,是南楚也,其俗大类西楚。郢之後徙寿春,亦一都会也。而合肥受南北潮,皮革、鲍、木输会也。与闽中、干越杂俗,故南楚好辞,巧说少信。江南卑湿,丈夫早夭。多竹木。豫章出黄金,长沙出连、锡,然堇堇物之所有,取之不足以更费。九疑、苍梧以南至儋耳者,与江南大同俗,而杨越多焉。番禺亦其一都会也,珠玑、犀、玳瑁、果、布之凑。

  颍川、南阳,夏人之居也。夏人政尚忠朴,犹有先王之遗风。颍川敦愿。秦末世,迁不轨之民於南阳。南阳西通武关、郧关,东南受汉、江、淮。宛亦一都会也。俗杂好事,业多贾。其任侠,交通颍川,故至今谓之“夏人”。

  夫天下物所鲜所多,人民谣俗,山东食海盐,山西食盐卤,领南、沙北固往往出盐,大体如此矣。

  总之,楚越之地,地广人希,饭稻羹鱼,或火耕而水耨,果隋蠃蛤,不待贾而足,地埶饶食,无饥馑之患,以故呰窳偷生,无积聚而多贫。是故江淮以南,无冻饿之人,亦无千金之家。沂、泗水以北,宜五穀桑麻六畜,地小人众,数被水旱之害,民好畜藏,故秦、夏、梁、鲁好农而重民。三河、宛、陈亦然,加以商贾。齐、赵设智巧,仰机利。燕、代田畜而事蚕。

  由此观之,贤人深谋於廊庙,论议朝廷,守信死节隐居岩穴之士设为名高者安归乎?归於富厚也。是以廉吏久,久更富,廉贾归富。富者,人之情性,所不学而俱欲者也。故壮士在军,攻城先登,陷阵卻敌,斩将搴旗,前蒙矢石,不避汤火之难者,为重赏使也。其在闾巷少年,攻剽椎埋,劫人作奸,掘冢铸币,任侠并兼,借交报仇,篡逐幽隐,不避法禁,走死地如骛者,其实皆为财用耳。今夫赵女郑姬,设形容,揳鸣琴,揄长袂,蹑利屣,目挑心招,出不远千里,不择老少者,奔富厚也。游闲公子,饰冠剑,连车骑,亦为富贵容也。弋射渔猎,犯晨夜,冒霜雪,驰阬谷,不避猛兽之害,为得味也。博戏驰逐,斗鸡走狗,作色相矜,必争胜者,重失负也。医方诸食技术之人,焦神极能,为重糈也。吏士舞文弄法,刻章伪书,不避刀锯之诛者,没於赂遗也。农工商贾畜长,固求富益货也。此有知尽能索耳,终不馀力而让财矣。

  谚曰:“百里不贩樵,千里不贩籴。”居之一岁,种之以穀;十岁,树之以木;百岁,来之以德。德者,人物之谓也。今有无秩禄之奉,爵邑之入,而乐与之比者。命曰“素封”。封者食租税,岁率户二百。千户之君则二十万,朝觐聘享出其中。庶民农工商贾,率亦岁万息二千,百万之家则二十万,而更徭租赋出其中。衣食之欲,恣所好美矣。故曰陆地牧马二百蹄,牛蹄角千,千足羊,泽中千足彘,水居千石鱼陂,山居千章之材。安邑千树枣;燕、秦千树栗;蜀、汉、江陵千树橘;淮北、常山已南,河济之间千树萩;陈、夏千亩漆;齐、鲁千亩桑麻;渭川千亩竹;及名国万家之城,带郭千亩亩锺之田,若千亩卮茜,千畦姜韭:此其人皆与千户侯等。然是富给之资也,不窥市井,不行异邑,坐而待收,身有处士之义而取给焉。若至家贫亲老,妻子软弱,岁时无以祭祀进醵,饮食被服不足以自通,如此不惭耻,则无所比矣。是以无财作力,少有斗智,既饶争时,此其大经也。今治生不待危身取给,则贤人勉焉。是故本富为上,末富次之,奸富最下。无岩处奇士之行,而长贫贱,好语仁义,亦足羞也。

  凡编户之民,富相什则卑下之,伯则畏惮之,千则役,万则仆,物之理也。夫用贫求富,农不如工,工不如商,刺绣文不如倚市门,此言末业,贫者之资也。通邑大都,酤一岁千酿,醯酱千瓨,浆千甔,屠牛羊彘千皮,贩穀粜千锺,薪千车,船长千丈,木千章,竹竿万个,其轺车百乘,牛车千两,木器魨者千枚,铜器千钧,素木铁器若卮茜千石,马蹄躈千,牛千足,羊彘千双,僮手指千,筋角丹沙千斤,其帛絮细布千钧,文采千匹,榻布皮革千石,漆千斗,糵麹盐豉千荅,鮐{此鱼}千斤,鲰千石,鲍千钧,枣栗千石者三之,狐龂裘千皮,羔羊裘千石,旃席千具,佗果菜千锺,子贷金钱千贯,节駔会,贪贾三之,廉贾五之,此亦比千乘之家,其大率也。佗杂业不中什二,则非吾财也。

  请略道当世千里之中,贤人所以富者,令後世得以观择焉。

  蜀卓氏之先,赵人也,用铁冶富。秦破赵,迁卓氏。卓氏见虏略,独夫妻推辇,行诣迁处。诸迁虏少有馀财,争与吏,求近处,处葭萌。唯卓氏曰:“此地狭薄。吾闻汶山之下,沃野,下有蹲鸱,至死不饥。民工於市,易贾。”乃求远迁。致之临邛,大喜,即铁山鼓铸,运筹策,倾滇蜀之民,富至僮千人。田池射猎之乐,拟於人君。

  程郑,山东迁虏也,亦冶铸,贾椎髻之民,富埒卓氏,俱居临邛。

  宛孔氏之先,梁人也,用铁冶为业。秦伐魏,迁孔氏南阳。大鼓铸,规陂池,连车骑,游诸侯,因通商贾之利,有游闲公子之赐与名。然其赢得过当,愈於纤啬,家致富数千金,故南阳行贾尽法孔氏之雍容。

  鲁人俗俭啬,而曹邴氏尤甚,以铁冶起,富至巨万。然家自父兄子孙约,俯有拾,仰有取,贳贷行贾遍郡国。邹、鲁以其故多去文学而趋利者,以曹邴氏也。

  齐俗贱奴虏,而刀间独爱贵之。桀黠奴,人之所患也,唯刀间收取,使之逐渔盐商贾之利,或连车骑,交守相,然愈益任之。终得其力,起富数千万。故曰“宁爵毋刀”,言其能使豪奴自饶而尽其力。

  周人既纤,而师史尤甚,转毂以百数,贾郡国,无所不至。洛阳街居在齐秦楚赵之中,贫人学事富家,相矜以久贾,数过邑不入门,设任此等,故师史能致七千万。

  宣曲任氏之先,为督道仓吏。秦之败也,豪杰皆争取金玉,而任氏独窖仓粟。楚汉相距荥阳也,民不得耕种,米石至万,而豪杰金玉尽归任氏,任氏以此起富。富人争奢侈,而任氏折节为俭,力田畜。田畜人争取贱贾,任氏独取贵善。富者数世。然任公家约,非田畜所出弗衣食,公事不毕则身不得饮酒食肉。以此为闾里率,故富而主上重之。

  塞之斥也,唯桥姚已致马千匹,牛倍之,羊万头,粟以万锺计。吴楚七国兵起时,长安中列侯封君行从军旅,赍贷子钱,子钱家以为侯邑国在关东,关东成败未决,莫肯与。唯无盐氏出捐千金贷,其息什之。三月,吴楚平,一岁之中,则无盐氏之息什倍,用此富埒关中。

  关中富商大贾,大抵尽诸田,田啬、田兰。韦家栗氏,安陵、杜杜氏,亦巨万。

  此其章章尤异者也。皆非有爵邑奉禄弄法犯奸而富,尽椎埋去就,与时俯仰,获其赢利,以末致财,用本守之,以武一切,用文持之,变化有概,故足术也。若至力农畜,工虞商贾,为权利以成富,大者倾郡,中者倾县,下者倾乡里者,不可胜数。

  夫纤啬筋力,治生之正道也,而富者必用奇胜。田农,掘业,而秦扬以盖一州。掘冢,奸事也,而田叔以起。博戏,恶业也,而桓发用富。行贾,丈夫贱行也,而雍乐成以饶。贩脂,辱处也,而雍伯千金。卖浆,小业也,而张氏千万。洒削,薄技也,而郅氏鼎食。胃脯,简微耳,浊氏连骑。马医,浅方,张里击锺。此皆诚壹之所致。

  由是观之,富无经业,则货无常主,能者辐凑,不肖者瓦解。千金之家比一都之君,巨万者乃与王者同乐。岂所谓“素封”者邪?非也?

  货殖之利,工商是营。废居善积,倚巿邪赢。白圭富国,计然强兵。倮参朝请,女筑怀清。素封千户,卓郑齐名。