C++ 官方 FAQ 阅读笔记
潘忠显 / 2021-11-03
C++是什么
C++ is a practical but not perfect language.
C++ = 更好的C + 数据抽象 + 面向对象 + 泛型编程 + 函数式编程
上学的时候上的C++ Primer只有提到前四项,最后一项是随着 Modern C++ 的发展而加上的。
优秀的程序员应该能针对不同的情况选择最合适的方法或者方法组合,而不是使用单一的方法去解决所有的问题,这些方法就是指上边提到的:更好的C / 面向过程,数据抽象 / plain class,面向对象(OOP),泛型编程,函数式编程。
zero-overhead:C++ 的新特性不应该使得任何现有代码变多或变慢,也不应该比程序员不使用该特性写的代码更差。
https://www.stroustrup.com/abstraction-and-machine.pdf
类的基本作用:
- 能够让数据、函数相互之间的关系更容易理解
- 隐藏使用者不需要知道的细节
- 利用辅助函数进行资源管理
C++ 基本上 向后兼容 ANSI/ISO C。常见的不兼容的有几点: C++的函数符号跟C的不一样,因为C中没有重载,直接使用函数名作为符号;C++中可以直接使用结构体名定义变量,而C中必须以 struct Fred fred
的方式进行定义,所以为了兼容尝尝会看到 typedef struct Fred Fred
的定义,这样就可以在C找那个直接使用 Fred
来定义变量了。
1979年开始开发,1983年第一个版本,1985年第一个商业版本。1998年C++的标准化版本C++98,对C++进行标准化的组织叫做 WG21,是ISO的标准化工作组。
C++的官方标准需要购买,但是标准草稿是在Github上维护的。换言之,可以在Github上免费下载到C++标准。
C++ 中有很多 “undefined”, “unspecified”, “implementation defined”, and “well-formed” 不同程度的行为。
关于多态
多态(polymorphism)分为运行时多态和编译时多态,能够有效的复用代码。使用类的面向对象技术是运行时多态的实现手段,被描述为特设多态(ad hoc polymorphism),泛型编程是编译时多态的实现手段,也被描述为参数多态(parametric polymorphism)。具体的:
-
OOP 的优势在于使用继承来表示很多问题,缺点是会被用于表示很多不合适的问题。不使用虚函数的 C++ 不算 OOP。
-
泛型重要方法是使用模板(template),基础是类型参数化,能够在不牺牲性能的情况下最大化复用代码,适用于算法、数据结构的通用化。
-
相比于OOP,泛型不依赖于继承,更注重结构化,更灵活
多范式编程(multiparadigm programming):根据需要组合使用不同的功能编程,每种功能都发挥其最佳效果。
别去比较语言的优劣
This question generates much more heat than light.
Meaning: If a discussion generates more heat than light, it doesn’t provide answers, but does make people angry.
不要讨论语言的优劣。选择不同的语言,大部分情况下由业务考虑决定的,而不是技术考虑。
选择语言考虑的方面有很多:availability of a programming environment for the development machine, availability of runtime environment(s) for the deployment machine(s), licensing/legal issues of the runtime and/or development environments, availability of trained developers, availability of consulting services, and corporate culture/politics. These business considerations generally play a much greater role than compile time performance, runtime performance, static vs. dynamic typing, static vs. dynamic binding, etc.
虽然不要去讨论不同语言的优劣,但是要知道不去讨论的原因:没什么意义,也难做到公平。
如何学C++
C++应用非常广泛,大量应用使用C++,大量公司和产品将C++作为主要语言。
对新手而言,很难做到也很少有人做到知道 “all of C++”,对于C、Java这些语言也是这种情况。学习 C++ 方法:选择一个子集,写代码,追歼的熟悉语言、库、工具。初学者使用C++,参考 Programming: Principles and Practice using C++ (PPP)
花费不同时长学 C++ 的能达到的不同程度:
一天:C 程序员编程更高效的 C-风格C++编程
一学期:PPP
一到两年:完全熟悉C++ 主要的语言结构
三年:可以指导别人的专家
Some people never make it. You don’t have a chance unless you are teachable and have personal drive. As a bare minimum on “teachability,” you have to be able to admit when you’ve been wrong. As a bare minimum on “drive,” you must be willing to put in some extra hours. Remember: it’s a lot easier to learn some new facts than it is to change your paradigm, i.e., to change the way you think; to change your notion of goodness; to change your mental models.
想做到专家,还有两个必须的条件:be teachable and have personal drive,前者要求能承认自己的错误,后者要求愿意花费额外的精力。不得不承认,其实最终能做到专家的还是少数,大部分人都在因为这两个原因而停滞于半路。
通过学习的来改变自己范式,包括:思考的方式、对好的认识、思考方式。
不应该通过学习 C 或者 C# 来作为学习OO/C++的中间途径,应该做的两件事:
- 分清必须做,什么应该做
- 找个能指导自己的人
需要认清,优秀的语言有明显的优势,但也有自己的局限性,不是适用于任何场景。
不遵循静态(编译时)类型安全的原因:直接访问硬件(主存储器)、运行时性能优化、兼容 C
Software Development Is Decision Making
Business issues dominate technical issues, and any definition of “good” that fails to acknowledge that fact is bad.
C++ 的特点
静态类型检查
函数重载是泛型编程的重要基础
保证高性能的几个常见技术
机器内存与程序内存的直接映射
初始化与赋值不同。
初始化的任务是将内存填上内容,生成一个有效的对象。
constexpr在编译时计算
不自动初始化
注重编程技术,而非语言特性
A tour to C++
string literal, “Hello, World!\n”
bitwise 按位的,比如 bitwise or
truncate 截断
narrowing conversions
声明是引入一个实体到C++,实体包括:类型,对象,值,变量
变量是有命名的对象
对象是保存类型值的一块内存
类型是值和操作集合的定义,比如: bool,int
值是能映射成特定类型的位集合
int i2 {7.8}; // error: floating-point to integer conversion
使用auto的场景是小范围内+不影响可读性+默认类型能够满足(比如默认int double)
几种作用域:本地、类、命名空间、全局
const 的值可以在运行时计算,constexpr的需要在编译阶段计算出来
constexpr double max2 = 1.4∗square(var); // error: var is not a constant expression
const double max3 = 1.4∗square(var); // OK, may be evaluated at run time
This for-statement can be read as ‘‘set i to zero; while i is not 10, copy the ith element and increment i.’’
range-for-statement,
SQL 评审
在于表的设计,而不应该寄希望于优化SQL
4.4 编译器实现虚函数技术:将虚函数名转换成 虚函数表/virtual function table/vtbl 的索引。
每个有虚函数的类,都有自己的 vtbl 来区分、识别其虚函数。
4.5 Concrete class 和 有继承关系的类相比,前者更像是内建的类型,直接使用变量名进行使用;后者常使用 new 来创建并使用指针,或使用引用来调用有继承关系的类的函数。
构造函数是从 base 到 derived,析构函数从 derived 到 base。
使用智能指针避免上边直接new可能带来的内存泄漏
一个指针如何知道自己指向对象究竟是哪个对象?编译器如何实现的?
unique_ptr<Shape>{new Circle{p,r}};
copy initialization
copy assignment
I have another question about above output: why the last line seems executed twice but the
为什么不能用
有些时候,默认copy 函数是不符合预期的。比如,如果使用指针保存向量实际存储位置的容器,在默认copy的时候,只会copy一个指针,而不是真的拷贝内容:
![image-20211220105657135](/Users/panzhongxian/Library/Application Support/typora-user-images/image-20211220105657135.png)
预期的应该是:
![image-20211220110155325](/Users/panzhongxian/Library/Application Support/typora-user-images/image-20211220110155325.png)
对象的copy包括 拷贝构造和拷贝赋值
Vector(const Vector& a); // copy constructor
Vector& operator=(const Vector& a); // copy assignment
Vector& Vector::operator=(const Vector& a) { // copy assignment
double∗ p = new double[a.sz];
for (int i = 0; i != a.sz; ++i) p[i] = a.elem[i];
delete[] elem; // delete old elements
elem = p;
sz = a.sz;
return ∗this;
}
为什么需要move语义
考虑
r = x + y;
x + y
会得到一个新的向量,在调用 r =
则会创建一个新的向量,然后复制 x + y
的结果。中间的这个变量的创建和销毁是多余的,尤其在向量很长的情况下,会对性能带来很大的影响。
再考虑:
r = x + y + z;
这个过程会有更大的损耗,x + y
的到中间结果 r1,然后 r1 + z 的到中间结果 r2,最后将 r2 赋值给 r。中间创建和销毁 r1 和 r2。
Vector(Vector&& a); // move constructor
Vector& operator=(Vector&& a); // move assignment
移动构造/赋值与复制构造/赋值的区别还有一个:前者因为要移除参数里边的内容,因此不能被 const 修饰。
The && means ‘‘rvalue reference’’ and is a reference to which we can bind an rvalue.
为什么 move 语义能够提升性能
什么是左值,右值。
能在赋值左侧出现的叫左值。
右值:不能给他分配,比如一个函数返回的整数值
什么情况下知道调用的是移动构造/赋值,而不是复制构造/赋值?
https://zh.cppreference.com/w/cpp/language/value_category
但是没有解释为什么 r = x + y + z
变得高效了。
std::move
is used to indicate that an object t
may be “moved from”, i.e. allowing the efficient transfer of resources from t
to another object.
In particular, std::move
produces an xvalue expression that identifies its argument t
. It is exactly equivalent to a static_cast
to an rvalue reference type.
搞懂左右值、移动构造/赋值
strong resource safety 强资源安全~消除资源泄漏
vector thread fstream 都是出于这种目的。
C++ 可以使用 GC 的插件? gperftools 算是这种插件吗?
non-memory resource
自己定义hash<>来使用 unordered_map
比如对 pair<int, int>
就需要自己定义
模板是编译时的机制,不会产生运行时耗时
模板的实例化/特化,会产生代码并做类型检查,来保证如手写代码一样的类型安全。
模板参数的简化:
pair<int,double> p = {1,5.2};
-> auto p = make_pair(1,5.2);
-> pair p = {1,5.2};
因为模板的实例化是编译时的动作,所以这些推导
Vector vs {"Hello", "World"};
会推导成 vector<const char*>
function object 也被称为 functor: 定义对象可以像函数一样调用。function objects are widely used as arguments to algorithms.
STL
count
lambda function也是产生一个 functor
泛型 lambda
使用模板需要用到一些基础的语言特性:
basic_string
和 string
的关系
using string = basic_string
basic_string 是个类模板,std::string 是将类型设定为char的特化之后的类。
已经特化的还有 u32string u16string wstring 等
wchar_t 是宽字符表示。32bit能够支持 Unicode。
wchar_t - type for wide character representation (see wide strings). Required to be large enough to represent any supported character code point (32 bits on systems that support Unicode. A notable exception is Windows, where wchar_t is 16 bits and holds UTF-16 code units) It has the same size, signedness, and alignment as one of the integer types, but is a distinct type.
这些命名空间是干嘛的:
namespace pmr
namespace gsl
string_view
https://stackoverflow.com/questions/20803826/what-is-string-view
raw string literal