《A Tour of C++ Third Edition》1. The Basics
1.1 Introduction
在本章中讨论的主要内容是C语言的一些重要特性。这些特性能归类为过程式编程的特点,并包括以下几个方面:标识符用于变量命名、存储模型来管理数据空间以及运算机制来执行计算操作。此外,在编写程序时采用适当的方法和策略有助于提高效率和可维护性。
1.2 Programs

C++代码到程序的编译过程
同一个可执行程序在同一操作系统的环境下才能正常运行。由于软件编译环境的不同,在不同的操作系统中会遇到各种各样的问题导致相同的源代码可能生成不同的二进制程序。当我们在探讨源代码兼容性时,则关注的是源代码的表现而不是具体的二进制形式。
C++标准定义了语言中两种实体:
- 基本语言特点涵盖内建数据类型与循环结构或其他相关属性
- 标准库部分包括容器类组件和IO操作相关的功能
C++是一种基于静态类型的语言,在编译阶段,所有实体必须明确其静态类型。由于类型的特性决定了操作行为及内存占用情况。
1.2.1 Hello, World!
int main() { } // the minimal C++ program
这是最短的一段C++代码
C++代码以main函数作为程序执行的起点(虽然除了main函数之外还有其他额外代码),其返回值会被操作系统的相关机制所处理(针对Linux系统)。当main函数返回非零值时,则表示程序运行过程中出现了错误或异常情况。
import std;
int main() {
std::cout << "Hello, World!\n";
}
<<将右边参数写入左边参数
import std; // import the declarations for the standard library
using namespace std; // make names from std visible without std:: (§3.3)
double square(double x) // square a double-precision floating-point number
{
return x * x;
}
void print_square(double x) {
cout << "the square of " << x << " is " << square(x) << "\n";
}
int main() {
print_square(1.234); // print: the square of 1.234 is 1.52276
}
面向过程的编程范式主要通过将具体的功能逻辑实现为独立的函数,并通过main函数直接或间接地发起调用请求。
1.3 Functions
讲了函数的组成部分和重载,后面还会详细介绍
void print(int,double);
void print(double,int);
void user2()
{
print(0,0); // error: ambiguous
}
1.4 Types, Variables, and Arithmetic
涉及类型系统的主题方面,在编程语言领域已有许多广泛探讨的内容。重点在于介绍C++中的常见难点以及其近年来的发展与改进方向。
1.4.2 Initialization
double d1 = 2.3; // initialize d1 to 2.3
double d2 {2.3}; // initialize d2 to 2.3
double d3 = {2.3}; // initialize d3 to 2.3 (the = is optional with { ... })
complex<double> z = 1; // a complex number with double-precision floating-point scalars
complex<double> z2 {d1,d2};
complex<double> z3 = {d1,d2}; // the = is optional with { ... }
vector<int> v {1, 2, 3, 4, 5, 6}; // a vector of ints
花括号作为编程语言中更为简洁的初始化标记符,在处理数据转换时具有明显优势。具体而言,在遇到精度损失的情况下执行收窄转换操作(narrowing conversions)会导致编译器在运行时抛出报错信息,并指出这种异常情况发生的原因是在数据处理过程中出现了精度丢失的问题。
auto d = 1.2; // a double
auto z = sqrt(y); // z has the type of whatever sqrt(y) returns
auto bb {true}; // bb is a bool
auto 一般用于复杂类型的对象声明
1.5 Scope and Lifetime
这也是很重要的概念:作用域与生存期
先讲作用域:
- 局部作用域:函数体内部以及其嵌套的lambda表达式体内涉及的对象
- 类作用域:类及其相关成员
- 命名空间作用域:命名空间内定义的函数、lambda、类及相关的枚举类型以外的对象
这里强调全局对象的作用域是全局作用域global namespace
vector<int> vec; // vec is global (a global vector of integers)
void fct(int arg) // fct is global (names a global function)
// arg is local (names an integer argument)
{
string motto {"Who dares wins"}; // motto is local
auto p = new Record{"Hume"}; // p points to an unnamed Record (created by new)
// ...
}
struct Record {
string name; // name is a member of Record (a string member)
// ...
};
再看生存期:
- 局部对象:局部变量是在函数调用开始前生成后,在函数返回前生命周期末尾处会被摧毁(全局变量则会在函数退出后才会进行摧毁操作)。
- 类成员:类中的成员变量会在类实例析构的时候一并清除。
- 动态(匿名)对象:通过new指令创建的对象在delete指令用于删除之前会一直保持有效状态
1.6 Constants
不可变性主要有两种含义:
- const:‘‘I promise not to change this value.’’
该关键字通常用于修饰接口,在这种情况下通过引用或指针传递的参数不会发生更改;因此该对象的赋值可能仅在运行时进行
- constexpr:‘‘to be evaluated at compile time.’’
该关键字主要用于修饰常量,并使得数据能够存储在ROM中;特别适用于追求性能优化的情况;其值必须能够在编译阶段预先确定
constexpr int dmv = 17; // dmv is a named constant
int var = 17; // var is not a constant
const double sqv = sqrt(var); // sqv is a named constant, possibly computed at run time
double sum(const vector<double>&); // sum will not modify its argument (§1.7)
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: sum(v) is evaluated at run time
constexpr double s2 = sum(v); // error: sum(v) is not a constant expression
函数想要用在常量表达式中的话,必须声明为constexpr或consteval
constexpr double square(double x) { return x*x; }
constexpr double max1 = 1.4*square(17); // OK: 1.4*square(17) is a constant expression
constexpr double max2 = 1.4*square(var); // error: var is not a constant, so square(var) is not a constant
const double max3 = 1.4*square(var); // OK: may be evaluated at run time
constexpr函数可以传入非常量参数,但此时结果将不再是constexpr
当我们要在编译阶段进行评估函数的值而非强制其为consteval表达式时,则应使用consteval关键字。
consteval double square2(double x) { return x*x; }
constexpr double max1 = 1.4*square2(17); // OK: 1.4*square(17) is a constant expression
const double max3 = 1.4*square2(var); // error: var is not a constant
在书中将这两类函数定义为pure functions, 其特点是有无副作用, 并且不允许引用来自外部的非局部变量
这两类函数的用处主要在:
- 常量表达式:在编程语言中遵循特定语法规则的索引(如数组下标)、用于区分不同情况的case标签、用于传递数据的模板参数以及用于变量声明的常量声明。
- 在编译阶段进行求值能够有助于提升程序性能。
1.7 Pointers, Arrays, and References
T a[n] // T[n]: a is an array of n Ts
T* p // T*: p is a pointer to T
T& r // T&: r is a reference to T
T f(A) // T(A): f is a function taking an argument of type A returning a result of type T
1.7.1 The Null Pointer
int count_x(const char* p, char x)
// count the number of occurrences of x in p[]
// p is assumed to point to a zero-terminated array of char (or to nothing)
{
if (p==nullptr)
return 0;
int count = 0;
for (; *p!=0; ++p)
if (*p==x)
++count;
return count;
}
该部分阐述了指针放置于条件判断语句位置时的应用场景。具体而言,在所讨论的情境中指出:当所指针不为null时,则条件表达式if(p)的结果为真
1.8 Tests
void do_something(vector<int>& v)
{
if (auto n = v.size(); n!=0) {
// ... we get here if n!=0 ...
}
// ...
}
if语句中也可以定义局部变量
这里n!=0其实都可以省略
1.9 Mapping to Hardware
1.9.2 Initialization
Initialization与赋值不同。通常情况下,在赋值操作中,目标对象必须已初始化。另一方面,在初始化过程中,默认的内存区域会被转换为有效的对象。对于绝大多数类型来说,在读取或写入未初始化的变量时的行为是未定义的。
这里明确指出初始化与赋值并非同一概念。具体而言,在编程语言中,默认情况下变量可能尚未被分配内存空间;因此,在程序运行之前必须执行初始化操作以确保变量能够获得内存资源并被赋予初始数值;相比之下,则是指对已有的数据或变量进行更新或重置的操作
