初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;

int x; // 在任何函数之外定义的基本类型变量,默认值是 0

int main() {
int u; // 内容是 undefined
int a = {1};
int b{2}; // 等号可以省略
int c(3); // 等价于 int c = 3;
double i = {3.14};
// int j = {3.14}; // 错误,不允许对初始化列表中的值进行截断,或者说隐式转换
int k = 3.14; // 可以,k = 3
int m(3.14); // 可以,k = 3
cout << x << ' ' << a << ' ' << b << ' ' << c << ' ' << i << ' ' << k << ' '
<< m << endl;
cout << '-' << endl;
cout << u << endl; // u 的内容完全不确定
return 0;
}

作用域

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int p = 3;

int main() {
int p = 2;
cout << p << ' ' << ::p << endl; // 访问全局变量 p
return 0;
}

复合类型

引用

1
int a, b, c, &ref;

其中 int 是 BaseType,a, b, c, &ref 是 declarators

Declaration 就是一个 Base Type 后接上一些 declarators

1
2
3
4
5
6
7
8
9
10
#include <iostream>
using namespace std;

int main() {
int i, &ri = i;
i = 5;
ri = 10;
std::cout << i << " " << ri << std::endl;
return 0;
}

声明引用时必须初始化,后续无法改变引用指向的对象

引用就是一个对象的别名,对引用的操作,就相当于操作引用关联的对象

指针

Unlike a reference, a pointer is an object in its own right.

A pointer holds the address of another object.

1
2
int ival = 42;
int *p = &ival; // p holds the address of ival; p is a pointer to ival

& is the address-of operator.

Pointer values

The value stored in a pointer can be in one of four states:

  1. It can point to an object
  2. It can point to the location just immediately past the end of an object
  3. It can be a null pointer, indicating that it is not bound to any object
  4. It can be invalid; values other than the preceding three are invalid

Null Pointers

A null pointer doesn’t point to any object.

1
2
3
4
int *p1 = nullptr;
int *p2 = 0;
// must #include cstdlib
int *p3 = NULL;

一些指针操作:

1
2
3
4
5
6
int i = 42;
int *pi = 0; // pi is initialized but addresses no object
int *pi2 = &i; // pi2 initialized to hold the address of i
int *pi3; // if pi3 is defined inside a block, pi3 is uninitialized
pi3 = pi2; // pi3 and pi2 address the same object, e.g., i
pi2 = 0; // pi2 now addresses no object
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int *pi = 0;       // pi is a valid, null pointer
int *pi2 = &ival; // pi2 is a valid pointer that holds the address of ival
if (pi) // pi has value 0, so condition evaluates as false
// . . .
if (pi2) // pi2 points to ival, so it is not 0; the condition evaluates as true
// . . .
int i = 42;
int *p = i; // 错误,不能用另一个变量的值初始化一个指针
int *p2 = 2; // 错误

int a = 3;
int *p = nullptr;
int *&r = p; // r 是 p 的引用
return 0;

放在条件判断中的指针必须是合法的,因为条件判断表达式要用到指针的值

const 关键字

The compiler will usually replace uses of the variable with its corresponding value during compilation.

对于使用的常量,编译器在编译阶段将那些常量替换成实际内容。

To substitute the value for the variable, the compiler has to see the variable’s initializer. When we split a program into multiple files, every file that uses the const must have access to its initializer. In order to see the initializer, the variable must be defined in every file that wants to use the variable’s value (§ 2.2.2, p. 45).

为了能替换这些常量为实际内容,编译器必须能访问常量的 initializer。当程序分成多个文件时,每个文件中使用常量,必须能访问到这个常量的 initializer,所以这个常量需要在每个文件中都定义一遍。

To support this usage, yet avoid multiple definitions of the same variable, const variables are defined as local to the file. When we define a const with the same name in multiple files, it is as if we had written definitions for separate variables in each file.

默认情况下,常量是文件局部作用域。即使在每个文件中定义相同名字的常量,就好像我们分别写了许多不同变量的定义。

实际测试来看,如果在一个文件定义一个常量 a,在另一个文件引入前一个文件,再定义一个常量 a,编译时会报错。

To define a single instance of a const variable, we use the keyword extern on both its definition and declaration(s):

1
2
3
4
// file1.cpp
extern const a = 3;
// file1.h
extern const a;

Reference to const

We can bind a reference to an object of a const type.

A reference to const cannot be used to change the object to which the reference is bound:

1
2
3
4
const int a = 1;
const int &r = a;
r = 3; // error
int &r2 = a; // error nonconst reference to a const object

Initialization and References to const

We can initialize a reference to const from any expression that can be converted to the type of the reference. In particular, we can bind a reference to const to a nonconst object.

1
2
3
4
5
int i = 42;
const int &r1 = i;
const int &r2 = 42; // r2 绑定的其实是一个临时对象,由编译器生成的
const int &r3 = r1 * 2; // r3 绑定的其实是一个临时对象,由编译器生成的
int &r4 = r1 * 2; // 错误,r4 绑定的是一个临时对象,这个引用不是一个 reference to const,是可以修噶的,而修改这个临时对象没意义,所以这样写是非法的

Pointers and const

1
2
const double pi = 3.14;
const double *p = &pi;

References and pointers to const,它们认为自己引用或指向的对象是常量的。

const Pointers

1
2
3
4
const double pi = 3.14;
const double *const p = &pi; // 第二个 const 表示,指针自己不能变
*p = 3; // 错误,指针指向的内容不能变
p = 0; // 错误,指针存储的地址不能变

Dealing with Types

Type Aliases

1
2
3
typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

其中 const pstring cstr = 0; 等价于 char *const cstr = 0;

一种错误的解读方式是直接将 typedef 定义的类型直接带入,得到 const char *cstr = 0;

An alias declaration starts with the keyword using followed by the alias name and an =.

1
using SI = Sales_item; // SI is a synonym for Sales_item

The auto Type Specifier

we can let the compiler figure out the type for us by using the auto type specifier.

auto 推断出来的类型,会丢弃 top-level const,使用引用对象原本的类型

1
2
3
4
5
const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int* (& of an int object is int*)
auto e = &ci; // e is const int* (& of a const object is low-level const)

如果需要 top-level const,需要显示指定 const auto

不能用一个 auto 初始化多个不同类型的变量

Because a declaration can involve only a single base type, the initializers for all the variables in the declaration must have types that are consistent with each other:

1
auto a = 1, b = 3.14;

我们也可以要求返回一个引用:

1
2
3
auto &g = ci; // g is a const int& that is bound to ci
auto &h = 42; // error: we can’t bind a plain reference to a literal
const auto &j = 42; // ok: we can bind a const reference to a literal

The decltype Type Specifier

有时候希望用一个表达式返回值的类型来定义一个变量,但又不想计算那个表达式,就可以用 decltype

1
decltype(f()) sum = x;

When the expression to which we apply decltype is a variable, decltype returns the type of that variable, including top-level const and references:

1
2
3
4
const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized

Defining Our Own Data Structures

定义属于自己的结构。

定义一个结构体,注意末尾的分号:

1
2
3
4
5
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};

定义一个结构体,并立刻创建定义对象:

1
2
3
4
struct Sales_data { /* ... */ } accum, trans, *salesptr;
// equivalent, but better way to define these objects
struct Sales_data { /* ... */ };
Sales_data accum, trans, *salesptr;

编写头文件

当预处理器看到 #include 时,会将 #include 替换成相应文件的实际内容。

header guards 用于防止一个头文件被多次引用,导致问题。

1
2
3
4
5
6
7
8
9
#ifndef SALES_DATA_H // 如果没有定义 SALES_DATA_H 预处理器变量才执行这句话到 #endif 之间的语句
#define SALES_DATA_H // 定义预处理器变量
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif

预处理器变量必须整个程序内唯一。