1.1 Introduction
本章讲的大多是C中的语言特性,也就是过程式编程的特性,包括表记、内存模型、计算以及组织代码的方式
1.2 Programs
![](https://img-blog.csdnimg.cn/direct/0e943530c3a74698b36e418ccd7acbbf.png)
同一个可执行程序在不同操作系统上是不可以运行的,由于文件系统的原因,exe文件在Linux上无法运行等。当我们讨论可移植性时,我们讨论的是souce code
C++标准定义了语言中两种实体:
- 核心语言特性,包括char、int等内置类型和循环等特性
- 标准库组件,包括容器、IO操作等
C++是静态类型语言,编译期所有实体的静态类型都需要确定,因为类型决定了操作和内存占用
1.2.1 Hello, World!
int main() { } // the minimal C++ program
这是最短的一段C++代码
C++代码以main函数作为程序起点(当然除了main还有其他的额外部分),main函数的返回值会被操作系统利用(指Linux),main函数返回非0值时代表出错
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++里的坑以及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.’’
const关键字主要用于修饰接口,这样通过指针或引用传入的参数将不可被改变,const对象的求值可能在运行时
- constexpr:‘‘to be evaluated at compile time.’’
constexpr关键字主要修饰常量,允许数据存放在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 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;
}
这里提到指针放在条件判断处的应用,如果指针不是nullptr,则if(p)为true
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 differs from assignment. In general, for an assignment to work correctly, the
assigned-to object must have a value. On the other hand, the task of initialization is to make an
uninitialized piece of memory into a valid object. For almost all types, the effect of reading from or
writing to an uninitialized variable is undefined.
这里说初始化和赋值不是一件事,初始化是保证对象在使用前分配内存,通过给定初始值的方式使它成为内存中的对象;而赋值是对已有的值进行修改