1、extern关键字


extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。记住它是一个声明不是定义

C++中const修饰的全局常量据有跟static相同的特性,即它们只能作用于本编译模块中,但是const可以与extern连用来声明该常量可以作用于其他编译模块中, 如:

extern const char g_str[]

2、export关键字


为了访问其他编译单元(如另一代码文件)中的变量或对象,对普通类型(包括基本数据类、结构和类),可以利用关键字extern,来使用这些变量或对象时;但是对模板类型,则必须在定义这些模板类对象和模板函数时,使用标准C++新增加的关键字export(导出/出口/输出)。例如:

extern int n;
extern struct Point p;
extern class A a;
export template<class T> class stack<int> s;
export template<class T> void test (T& t) {...}

函数模版的编译模式分两种:完全包含编译模式和局部编译模式(需要用export关键字)


举例说明完全包含编译模式:

  • (1)test.h中在声明的后面include “test.cpp”,这样做的目的是把sum的声明和定义放在两个文件中

  • (2)first.cpp和second.cpp中都用到了sum函数模版,所以都要include “test.h”,所以在两个cpp文件中都有一份sum的定义,所以在实例化以后,存在两个相同的函数定义:int sum(int a,int b){return a+b;}

  • (3)对于这种重复定义的问题,完全包含编译模式下,编译器自己会去除冗余的函数定义,而只保留一个int sum(int a,int b)函数的定义

  • (4)所以这种编译模式下,编译效率会降低(因为如果100的cpp文件中都调用了sum(2,3),那么会存在100个int sum(int a,int b)函数的定义,去除其余99个冗余定义的操作会占用大量的编译时间)

test.h头文件的内容: 
template<typename Type> 
Type sum(Type a,Type b);//函数模版的声明 
 
#include "test.cpp" 
 
test.cpp文件的内容: 
template<typename Type> 
Type sum(Type a,Type b) 
{ 
    return a+b; 
}//函数模版的定义 
 
first.cpp文件的内容: 
#include "test.h" 
int main() 
{ 
    sum(2,3); 
    return 0; 
} 
 
second.cpp文件的内容: 
#include "test.h" 
int second() 
{ 
    sum(2,3); 
    return 0; 
}

举例说明局部编译模式

  • (1)test.h头文件中不需要再include “test.cpp”

  • (2)需要使用sum的cpp文件中只需要include “test.h”即可

  • (3)在实例化sum函数时,编译器会自动跟踪到sum函数模版的定义(通过export关键字)

  • (4)这样就提高了编译速度

test.h头文件的内容: 
template<typename Type> 
Type sum(Type a,Type b);          //函数模版的声明 
 
//#include "test.cpp"             //不需要包含sum函数的定义 
 
test.cpp文件的内容: 
export template<typename Type>    //前面加上export关键字 
Type sum(Type a,Type b) 
{ 
    return a+b; 
}                                 //函数模版的定义 
 
first.cpp文件的内容: 
#include "test.h" 
int main() 
{ 
    sum(2,3); 
    return 0; 
} 
 
second.cpp文件的内容: 
#include "test.h" 
int second() 
{ 
    sum(2,3); 
    return 0; 
}


3、register关键字

register修饰符暗示编译程序相应的变量将被频繁地使用,如果可能的话,应将其保存在CPU的寄存器中,以加快其存储速度。

但是使用register修饰符有几点限制 :


  • ①register变量必须是能被CPU所接受的类型。

    这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。

  • ②因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。

  • ③只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
    在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。

  • ④局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;

  • ⑤由于寄存器的数量有限(不同的cpu寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。


4、volatile关键字

volatile告诉编译器不要擅自做出有关数据的任何假定,在用到这个变量的时候必须每次都小心的重新去内存读取这个变量的值,而不是使用保存在寄存器里的备份。

注:一个参数可以使既是const又是volatile,一个指针也可以是volatile。


5、static关键字

①static修饰局部变量:该变量将成为局部静态变量,它的作用范围为仍然为函数体内,不同于普通变量,该变量的内存只被分配一次,因此其值在下次调用时仍能维持上次的值。
②static修饰全局变量:限定该全局变量只能在本文件内使用。
③static修饰普通函数:限定该函数只能在本文件内使用。
④static修饰类中成员变量:这个变量属于整个类所拥有,对类的所有对象只有一份拷贝。

⑤static修饰类中成员函数:这个函数属于整个类所拥有,且不接受this指针,只能访问类的static成员变量。

6、四种强制类型转换

  • static_cast:内建类型之间的强制类型转换及继承关系类的指针及引用之间的转换,不做类型安全检查。

  • dynamic_cast:继承关系类的指针及引用之间的转换,做类型安全检查即只能子类去强制转换成基类。

  • const_cast:间接去除或增加任意类型的变量(包含指针、引用)的const属性。

  • reinterpret_cast:强制类型转换、任意无关的指针类型包括函数指针都可以进行转换。

7、mutable

在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,即使在一个const函数中。


8、explicit:

首先, C++中的explicit关键字只能修饰只有一个参数的构造函数,或者有多个参数,但是除第一个参数外其他的参数都有默认值, 它的作用是表明该构造函数是显示的, 而非隐式的,禁止该类通过等号向单参数进行隐式转换,例如:

CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换


9、override

描述:override保留字表示当前函数重写了基类的虚函

目的:1.提示读者某个函数是重写了基类的虚函数;2.强制编译器检查某个函数是否重写基类虚函数,如果没有则报错。

用法:在类的成员函数参数列表后面添加该关键字既可。

10、dectype

decltype是C++11新增的一个关键字,它的主要用途是那些返回值依赖于形参型别的函数模板。一般来说decltype告诉你的结果与你预测的几乎相同,所以这里就不再累述,只需要记住以下几点即可:
decltype(expression) var; 具体判断逻辑如下:
    (1)如果expression没有使用括号括起来,则类型与expression完全相同,例如 const int i =0; decltype(i) var;则var为const int.
    (2)如果expression是一个函数调用,则var与函数返回值相同。注意,此时并不会调用函数,编译器通过函数原型,获取返回类型
    (3)如果expression是一个左值且使用括号括起来,则var为其类型的引用。例如int i =0; decltype((i)) var;则var为int&
    (4)以上情况都不满足,则var与expression完全相同.例如 long i =0;decltype(i+6)var;此时var为long类型。


11、noexcpt

只能修饰函数,表示该函数不会抛出异常,这样编译器将对其尽最大可能的优化,与C98的throw()一个意思

默认的,所有内存释放函数(delete函数)或析构函数,无论是用户自定义还是编译器生成,都隐式的声明了noexcept。

                                     

12、delete

如果不想使用某个函数,可以使用在函数声明后加上=delete,表示该函数未定义。例如

class Test {
public:
    void Fix(Test& t) {
        Test t1;
        t1 = t;  //编译报错,使用delete函数
    }
 
    Test& operator=(const Test& t) = delete;//只声明,不定义
};
 
Test t1,t2;
t1 = t2;   //编译报错,函数已经被删除

13、using

using别名声明和typedef声明都可以完成一个类型的声明,但使用using可以很简单且直接的完成这个操作,下面将通过代码分别声明相同的类型,就可以看出using的好处了。

   //声明指针函数
    typedef void(*FP)(int ,double);   //typedef晦涩不直白
    using FP = void(*)(int ,double); //using简单直白
     
     
    //typedef声明一个模板,表示一个链表
    template<class T>
    struct MyAllocList{
      typedef std::list<T,MyAlloc<T>> type;//typedef晦涩不直白
    };
     
    MyAllocList::type myList;  //类型使用晦涩不直白
     
     
    //using 声明一个模板,表示一个链表
    template<class T>
    using MyAllocList std::list<T,MyAlloc<T>>;//using简单直白
    MyAllocList myList;//类型使用简单直白

14、constexpr

constexpr变量必须使用一个具有const属性,且在编译阶段就已知的值对其初始化。例如:

    int x = 0;
    const int y = x;   //可以使用x对cosnt变量初始化
    const int z = 0;   
     
    constexpr int arraySize = x;  //错误,x为运行期间才确定值的变量
    constexpr int arraySize = y;  //错误,y为运行期间才确定值的变量
     
    constexpr int arraySize = z;  //正确,z为const变量,且编译期间就已知
    constexpr int arraySize = 11;  //正确,11为const变量,且编译期间就已知


对于constexpr函数,在调用时,将根据传入参数的型别,有不同的结果:

  •     如果传入的参数都是constexpr变量,则函数返回值也是constexpr变量

  •     如果传入的参数有一个或多个不是constexpr变量,则其和普通函数无差别


15、default

如果类中只定义了一个有参数的构造函数,默认构造函数编译器就不再生成了。那么在外部创建类时,如果创建无参数的类就会出错:因为没有一个无参构造函数。
可以通过default关键字让构造函数恢复classA() = default;
该函数比用户自己定义的默认构造函数获得更高的代码效率

16、final

修饰虚函数,表示该虚函数不能再被继承