The-CPP-Programming-Language-NOTE

Table of Contents

持续更新中…

书中生词/字记录

  • 擘划 bo(4) hua(4)
  • 遴选 lin(2) xuan(3)
  • 皮洛士式的胜利 指付出极大代价才获得的胜利

值得记录的句子

  • 4th前言-第4段

    C++是一种通用程序设计语言,它强调富类型,轻量级抽象的设计和使用。

  • 4h前言-第2页-第4段-最后一句话

    计算机是一种通用机器,而C++在其中起着重要作用。特别是,C++的设计目标就是足够灵活和通用,以便处理那些连它的设计者都未曾想象过的未来难题。

  • p6-第7段-第3-4句话

    书中的主要程序片段都已在多个C++实现上进行了实验,那些使用了新特性的代码在某些编译器上会编译失败。但我认为指出某某编译器不能编译某某例子没有什么意义,这些信息很快就会过时,因为编译器设计者都在努力工作以确保他们的编译器能正确支持所有C++特性。

  • p6-第8段-第1句话

    当我发现在某个地方C++11特性最适合时,我就会使用C++11特性。例如,我倾向于使用{}风格的初始化方式以及使用using定义类型别名。有时,这些用法可能会让“老程序员”惊讶。但是,惊讶通常是促使你学习新知识的很好的诱因。

    我就是看了FinalCut的示例代码,代码中用到了using定义类型别名和使用{}风格的初始化方式而看不懂才来看这本书的,果然被作者说中了!

  • p7-第1段-最后一句话

    C++的设计理念是同时提供

    1. 将内置操作和内置类型直接映射到硬件,从而提供高效的内存利用和高校的底层操作。
    2. 灵活且低开销的抽象机制,使得用户自定义类型无论是符号表达,使用范围还是性能都与内置类型相当。

    这应该就是为什么C++十分强大,但执行速度又非常高的原因了吧。

  • p11-第2段-第1-2句话

    我们用来思考/编程的语言与我们能够想象的问题/解决方案的联系是非常紧密的。为此,以消除程序员的错误为目的的限制语言特性是无意义的,最好情况也只是一种危险的理念。

    这句话我觉得作者是在抨击一些编程语言,例如很多现代编程语言都提供了动态类型,不再需要指明变量的类型,这算不算是“以消除程序员的错误为目的的限制语言特性”呢?有待思考。

  • p13-第1段-第2句话

    一个精心设计的用户自定义类型与内置类型的区别仅仅在于定义的方式,而使用方式则是完全一样的。

  • p14-第4段-第2-3句话

    语言特性的存在是为了支持各种程序设计风格和技术。因此,语言的学习应该更关注掌握其固有、内在的风格。

  • p59-第1段-最后一句话

    如果类包含虚函数,则该类的每个对象需要一个额外的指针;另外每个这样的需要一个vtbl。

  • p70-第2段-第一句话

    用于指明通用算法关键操作含义的函数对象被称为策略对象(policy object)。

  • p81-第四段-最后一句话

    为给定的任务提供合适的容器以及之上有用的基本操作,是构建任何程序的重要步骤。

  • p126-第四段

    所谓单精度,双精度和扩展精度的确切含义是依赖于具体实现的。程序员只有对浮点运算有非常深刻的理解才能在解决实际问题时做出最好的选择。如果你做不到这一点,最好向有经验的程序员寻求建议或自学。实在不行就优先选择double类型,这是一种折中的选择,比较稳妥。

  • p194-开头的话

    程序员就是一台能把咖啡因变成代码的机器。

学到的新东西

遍历数组

在以前我写C++时遍历数组我通常是这样做的:

void iter_arr1() {
  const int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  for (int i = 0; i < 10; ++i)
    cout << arr[i] << endl;
}

这种方式叫做计数遍历吧,通过将i递增,依次访问数组arr的值。

现在我学会了通过范围For来进行遍历数组:

void iter_arr2() {
  const int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  for (auto x : arr)
    cout << x << endl;
}

这种方式就简单多了。

不过上面这种方式会将arr的值拷贝给x,随后再将x输出,中间是会占用一些内存的,我们可以通过下面这种引用的方式来访问,就省去了拷贝的操作。

void iter_arr3() {
  int arr[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
  for (auto &x : arr)
    cout << ++x << endl;
}

需要注意的是由于其访问是通过对其地址递增,所以它迭代的数组也必须是可迭代对象,不能是const。

有关范围for的内容还未结束,后面还会记录更多!

真正的空指针

以前写CPP时一直是用NULL和0当空指针的,现在C++11添加了个 nullprt 用于单独表示空指针。

虚函数表

当一个抽象类的纯虚函数被多个类实现了,且有函数形参是抽象类类型,但实参分别是两个实现类时,编译器是如何找到正确的实现函数的呢?

#include <iostream>
#include <string>

using namespace std;

class Car {
public:
  virtual string getCar() = 0;
};

class Ford : public Car {
public:
  string getCar() override { return "福特"; }
};

class Nissan : public Car {
public:
  string getCar() { return "尼桑"; }
};
void printCar(Car &);

int main(int argc, char *argv[]) {
  Ford f;
  Nissan n;
  printCar(f);
  printCar(n);
  return 0;
}
void printCar(Car &c) { cout << c.getCar() << endl; }

代码的执行结果是:

g++ abstract-demo.cpp -o abstract-demo -std=c++11 && ./abstract-demo
福特
尼桑

其实是通过 虚函数表 来寻找对应实现类的函数进行执行。 有关 虚函数表 会在后面的小节提到。

删除拷贝、移动构造函数

书中说:

对于深层次的类来说,使用默认的拷贝或移动操作常常意味着风险。

所以可以将类默认隐式生成的拷贝、移动构造函数删除掉。例如:

class Car {
public:
  // 删除拷贝构造函数
  Car(const Car &) = delete;
  // 删除拷贝赋值运算符
  Car &operator=(const Car &) = delete;

  // 删除移动构造函数
  Car(const Car &&) = delete;
  // 删除移动赋值运算符
  Car &operator=(const Car &&) = delete;
};

不过如果你显式定义了 析构函数 的话,是可以不用删除的。

map容器搜索注意项

map容器是提供了根据下标来查找元素的,但是通过下标的方式查找在未找到时是相当于插入了一个新的数据(值为类型的默认值,例如整型的默认值0)。 如果不想要上面所说的副作用,那么可以使用 find()insert()

窄化转换出错

窄化转换是C++11引进的新特性,我在初始化变量时遇到过。 C++11可以通过初始化列表的方式对变量进行初始化,这样的好处就在于如果当给定初始化值与需初始化变量类型不匹配,就会引发窄化转换出错。 而曾经的那种初始化方式就不会出错,因为发生了隐式转换。

using namespace std;
int main() {
  bool a = 10;
  cout << a << endl;            // 1
  bool b{10};                   // 出错
  return 0;
}

如果想要在初始化列表不要发生窄化转换出错可以利用一些CPP的特性进行转换,例如:

#include <iostream>
using namespace std;

void f(int i) {
  bool b = i != 0;
  cout << b << endl;            // 1
}
int main() {
  f(10);
  return 0;
}

这是因为在算术逻辑表达式中,bool会被自动转换为1,具有相同特性的还有位逻辑表达式。

void*

void*最常用在当不知对象确切类型的情况下,而又需要在内存中的地址进行存储或传递对象的情景。 其含义是 指向未知类型对象的指针

void*使用注重点

  • 由于编译器不知道void*类型的指针到底是什么类型(void类型就是代表什么类型都不是),所以任何类型的指针都可以转换为void类型,不过转换成void类型后,原本类型可用操作会无法使用。

    #include <iostream>
    using namespace std;
    
    int main(int argv, char *argc[]) {
      int a = 10;
      int *p = &a;
      cout << ++*p; // 正确,a=11
      void *vp = p; // 发生了隐式转换,从int变为void
      // cout << ++*vp;
      // 错误,void类型没有++操作
    
      return 0;
    }
    
  • 函数指针和指向类成员的指针无法指向void* 类型。

nullptr

nullptr即空指针,用于替代旧版本的以0作为空指针的写法。

nullptr和NULL(0)的区别

在C++11之前,大家都用0表示空指针,这样有个缺点就是当一个函数允许传递的参数既有指针又有整型,就容易引起歧义,因为可以用整型0代表空指针。

nullptr只能被赋予指针类型,而不能被赋予内置类型。

Date: 2020-12-04 Fri 00:00

Author: Evan Meek

Created: 2021-04-08 Thu 00:22

Validate