C++ Primer也看了一两遍了,但有些东西总是容易忘记或者弄错。最近准备再过一遍,边看边把一些奇奇怪怪的点记录下来。

  • 1.编译器能够查出来的错误有:语法错误、类型错误、声明错误。
  • 2.键盘键入文件结束符,Windows上用Ctrl+z,Unix上用control+d。
  • 3.标准库头文件用<>,自定义非标准头文件用””。
  • 4.char 占8位,int占16位(8位为一个字节,4字节为一个字)。
  • 5.变量初始化有两种方式:复制初始化(用=操作符),直接初始化(用())。
  • 6.初始化指的是创建变量并赋给它初值;赋值则是擦出原来的值并且使用新值。
  • 7.只有当extern声明于函数外部时,才可以含有初始化式。
  • 8.const引用(例如const int &ref)是指向const对象的引用,而且const对象不能用非const引用。
  • 9.非const引用只能绑定到该引用相同类型的对象,而const可以绑定到不同但是相关类型的对象或者绑定右值。
  • 10.非const变量以及要到运行阶段才知道其值的const变量都不能用来定义数组的维数。
  • 11.C++允许计算数组或者对象的index超出末端,但不允许对此地址进行解引用操作。
  • 12.const指针和指向const的指针

    1
    2
    const int* ptr; //指向const int的指针
    int* const ptr; //const指针
  • 13.如果派生类定义了自己的复制构造函数,该复制构造函数应该显式地使用基类的复制构造函数来初始化基类部分。

  • 14.派生类析构函数不负责撤销基类对象的成员,因为编译器总是显式地调用派生类对象的基类部分的析构函数,每个析构函数只负责清除自己的成员。
  • 15.虚析构函数存在的意义:父类指针指向子类对象,运行时才可以判断对象的真正类型。所以这种指针在析构时,编译器并不知道其实际指向的类型,只会调用父类的析构函数,并没有动态绑定查找对象的实际析构函数,所以析构行为异常。当父类将析构函数设为虚函数后,即可动态查找到实际对象的析构函数,即子类的析构函数,执行正常的析构动作。
  • 16.在析构函数和构造函数中调用虚函数。在继承关系中,如果存在析构函数或者构造函数调用虚函数,请小心。因为在构造派生类和撤销派生类的时候,对象都是不完整的,所以编译器将对象类型视为在构造函数和析构函数期间发生了变化,所以其中调用的虚函数已经不是他们绑定的类实现的版本了。如果在构造函数或者析构函数中调用虚函数,则运行的是构造函数或者析构函数自身类型的版本。

Base类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#ifndef cpptest_Base_h
#define cpptest_Base_h
#include <iostream>
using namespace std;
class Base{
public:
Base(){
cout<<"call base constructor."<<endl;
newfunc();
}
virtual void newfunc(){
cout<<"call base new function."<<endl;
}
virtual void deletefunc(){
cout<<"call base del function."<<endl;
}
~Base(){
cout<<"call base destructor."<<endl;
deletefunc();
}
};
#endif

Derived类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifndef cpptest_Base_h
#define cpptest_Base_h
#include "Base.h"
using namespace std;
class Derived{
public:
void newfunc(){
cout<<"call Derived new function."<<endl;
}
void deletefunc(){
cout<<"call Derived del function."<<endl;
}
};
#endif

main函数:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include "Base.h"
#include "Derived.h"
using namespace std;
int main(int argc, const char * argv[])
{
Derived *d = new Derived();
delete d;
getchar();
return 0;
}

输出:

1
2
3
4
call base constructor.
call base new function.
call base destructor.
call base del function.

  • 17.派生类想通过自身类型使用所有的函数重载版本,就得在派生类中重定义所有版本,要么一个也不定义。因为编译器调用函数是按名查找,在子类中查到了该函数名就不会再往上继续查找继承链。
  • 18.函数调用的编译器按名查找:
    1. 确定进行函数调用的对象、引用、指针的“静态类型”
    2. 在该类中查找函数名,查不到就沿着继承链向父类依次查找,若无法找到,则调用失败
    3. 找到该名字之后,就进行常规类型检查,查看函数调用是否合法
    4. 如果不合法,则调用失败;如果合法,则编译器生成代码,并考虑是否是虚函数,是否需要加入动态绑定
  • 18.非类型模板形参,在调用函数时将会用值代替,值的类型在模板形参表指定。
  • 19.模板的实参类型推断不支持类型转换。
  • 20.不仅可以特化类模板,还可以特化类成员而不特化类
  • 21.函数重载中,涉及模板函数时,若进行调用时普通函数和模板函数同时匹配的一样好,则普通函数优先。
  • 22.虚继承
  • 23.嵌套类是独立的类,基本上与它们的外围类不相关,因此,外围类和嵌套类的对象是相互独立的,嵌套类的对象不具有外围类所定义的成员,同样,外围的类也不具备嵌套类所定义的成员。