C++ 零散知识点

8/25/2019 面向对象

# 章节 1

# 赋值运算符

通常,C++ 中的初始化赋值,我们是使用赋值运算符 =。但 C++ 实际上还提供了一种更新的方式 {}

double d1 = 2.3;
double d2 {2.3};
double d3 = {2.3}; // = 符号可以有, 但没必要
1
2
3

两者之间的区别在于:

  • = 符号存在潜在的发生窄化类型转换(丢失数据)的风险
  • {} 赋值会在编译器检测数据类型,不会导致数据丢失
int x1 = 2.1;  // x1 = 2
int x2 {2.1};  // 错误,试图执行浮点数向整数的类型转换
int x3 = {2.1};// 错误,试图执行浮点数向整数的类型转换(=是多余的)
1
2
3

注意:{} 赋值只能在初始化的时候用。

# 常量

C++ 支持两种不变性概念

  • const “我承诺不改变这个值”
  • constexpr “在编译时求值”

const 主要用于说明接口,表示变量在传入函数时不会在函数内部被改变,编译器负责确认并执行 const 的承诺。而 constexpr 主要用于说明常量,作用是允许将数据置入只读内存中以及提升性能。

const 只是承诺这个值不会被程序员修改(readonly),而 constexpr 是真正的常量(在满足条件的情况下会在编译期就被计算出来),甚至可以用来代替宏替换。

int x = 5;              // 变量 x,运行时求值
const int y1 = x;       // 只读变量,x 并不是一个常量,所以 y1 也并不是一个常量
const int y2 = 5;       // 常量
constexpr int z1 = x;   // error
constexpr int z2 = y1;  // error
constexpr int z3 = y2;  // 常量

constexpr double s1 = 1.4 * square(x);   // error
constexpr double s2 = 1.4 * square(z3);  // accept
1
2
3
4
5
6
7
8
9

如果某个函数用在常量表达式中,则该函数必须定义成 constexpr。例如

constexpr int square(double x) {
    return x * x;
}
1
2
3

函数要想定义成 constexpr , 必须足够简单:函数中只能有一个用于计算某个值的 return 语句。实际上,constexpr 也可以接受非常量实参,但是,此时其结果将不再是一个常量表达式。

当程序上下文不需要常量表达式时,我们可以使用非常量表达式实参来调用 constexpr 函数。我们不必把同一个函数定义两次,一个用于常量表达式,一个用于变量。

有的场合,常量表达式是语言规则所必须的的。比如数组的界、case 标签、某些模板实参和使用 constexpr 申明的常量。

# 范围循环

C++ 针对数组与迭代器提供了范围循环。

for (<type> <variable-name> : <range_expression>) {
  // loop_statement
}  
1
2
3

可遍历的对象包括:

  • 数组(不包括指针数组)
  • STL 容器以及其他定义了返回该类迭代器的 begin()end() 方法的类对象。

也就是说在遍历对象的时候,范围循环其实等效于以下代码。

for (auto x = v.begin(); x != v.end(); ++x) {
  // code
}
1
2
3

通过上述示例不难看出:

  • 若要通过范围循环改变序列元素的值,必须将元素声明成引用。否则,此元素相当于一个指定序列元素的拷贝。
  • 不能通过范围循环增删序列元素。因为序列的迭代器已经被保存,增删会使迭代器失效。

下面验证在范围循环中改变元素的值必须声明成引用。

int data[]{1, 2, 3, 4, 5, 6};
for (auto &x : data) {
    std::cout << x;
    ++x;
}
std::cout << std::endl;
for (auto x : data) {
    std::cout << x;
    ++x;
}
std::cout << std::endl;
for (auto x : data) {
    std::cout << x;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

控制台输出结果

123456
234567
234567
1
2
3

# 用户自定义类型 - 枚举类

C++11 新增了了枚举类这一自定义类型的简单形式,使得我们可以枚举一系列值。枚举类型常用于描述规模较小的整数值集合。通过使用有指代意义的(且易于记忆的)枚举值名字可提高代码可读性,降低出错风险。

enum class TrafficLight {
    red,
    green,
    yellow,
};

enum class Color {
    red,
    blue,
    green
};
1
2
3
4
5
6
7
8
9
10
11

接下来可以这么定义枚举类:

Color col = Color::red;
TrafficLight light = TrafficLight::red;
1
2

其中枚举值(如 red)位于其 enum class 作用域之内。因此我们可以在不同的 enum class 中重复使用这些枚举值而不致引起混淆。

enum 后的 class 指明了两点:

  • 枚举是强类型的。
  • 它的枚举值位于指定的作用域中。

不同的 enum class 是不同的类型。不能够混用。在上面的例子中,我们不能混用 TrafficLightColor 的值。

Color fo = red;                 // 错误,哪个 red ?
Color foo = TrafficLight ::red; // 错误,这个 red 不是 Color 的对象
1
2

也不能隐式混用 Color 和整数值:

int i = Color::red;  // 错误,Color::red 不是一个 int
Color z = 2;         // 错误,2 不是一个 Color 对象
1
2

如果不希望显示地限定枚举值名字,并且希望枚举值可以是 int(无须显式转换),则应该去掉 enum class 中的 class 而得到一个普通的 enum

默认情况下,enum class 之定义了赋值,初始化和比较操作。然后,既然枚举类型是一种用户自定义类型,那么我们就可以为它定义别的运算符。比如为 TrafficLight 定义 ++ 运算符:

TrafficLight& operator++(TrafficLight& tl) {
    switch (tl) {
        case TrafficLight::green:
            return tl = TrafficLight::yellow;
        case TrafficLight::yellow:
            return tl = TrafficLight::red;
        case TrafficLight::red:
            return tl = TrafficLight::green;
        default:
            return tl = TrafficLight::red;
    }
}

TrafficLight light = TrafficLight::red; // light -> TrafficLight::red
++light;                                // light -> TrafficLight::green
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 名字空间

名字空间主要为了表达两个语义:

  • 某些声明是属于一个整体的
  • 他们的名字不会与其他名字空间中的名字冲突

namespace mine {
    class complex {};
    int main() {}
}

int mine::main() {

}

int main() {
  return mine::main();
}
1
2
3
4
5
6
7
8
9
10
11
12
13

上述代码我们将 complex 类与 main 放在了 mine 名字空间内,就可以确保我们的名字不会和名字空间 std 中的标准库中的名字冲突。

// 全局使用名字空间
using namespace std;

// 使用指定名字空间中的函数
mine::main();
1
2
3
4
5

# 抽象机制

# 构造函数与析构函数

RAII(资源获取即初始化)

# 初始化容器

容器的作用是保存元素,因此我们需要找到一种便利的方式将元素存入容器中。为了做到这一点,一种可能的方式是先用若干元素创建一个 Vector,然后再依次为这些元素进行赋值。显然这不是最好的方式,下面列举两种更简洁的途径。

  • 初始化器列表(initializer-list constructor):使用元素列表进行初始化
  • push_back(): 在序列末尾添加一个新元素

# 拷贝和移动容器

page 62 - 等学会了左值引用与右值引用再来

# 函数对象

实质是重载 () 运算符

Last Updated: 10/23/2021, 4:31:30 PM