class A;//虚拟基类最好不包含任何数据 class B1 : public virtual A; class B2 : public virtual A; class C : public B1, public B2;
关键字 | 说明 |
const |
用于表示变量不可改变,或者成员函数不能修改对象的状态 |
explicit |
用于禁止构造函数参数的隐式类型转换,只能用于单参的构造函数 |
extern |
声明函数或全局变量为一个外部链接(即在别处定义),且要在本模块引用,可以跨文件作用域的共享函数、全局变量 //这是一个全局变量声明 extern int i; extern char a[ ]; //这是一个全局变量定义 int i; char a[6]; //对于需要使用非常量表达式初始化的常量,不能在头文件中定义 //必须在头文件中使用extern声明,而且在某个文件中进行定义 /*header.h*/ extern const int NUM; /*source1.h*/ #include "header.h" const int num = random(); /*source2.h*/ #include "header.h" int main(){ count << num << endl; return 0; } |
extern "C" | 用于实现C/C++混合编程,具有双重含义:
//在C++中引用C语言中的函数和变量,在包含C语言头文件时,应当: extern "C" { #include "cheader.h" } //如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应当 extern "C" void m(); //在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C" //但是在C语言中不能直接引用声明了extern "C"的头文件, //应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型 /*C++头文件*/ extern "C" int add( int x, int y ); /*C++实现*/ #include "header.h" int add( int x, int y ) { return x + y; } /*C文件,不能引用C++头文件*/ extern int add( int x, int y ); int main( int argc, char* argv[] ) { add( 1, 2 ); return 0; } |
mutable |
用于类的非静态和非常量数据成员, 如果一个类的成员函数声明为const类型,则表示其不会改变对象的状态,但是标注为mutable的字段除外(也就是允许被const方法修改) |
static |
静态全局变量作用范围在一个文件内,程序开始时分配空间,结束时释放空间,默认初始化为0 静态局部变量,在线程第一次访问时创建,析构时依创建的相反顺序进行 静态成员和静态成员函数,类的所有实例共用一个副本 |
volatile |
类型修饰符,用它声明的类型变量随时可能被某些编译器所不知晓的因素更改,比如:操作系统、硬件或者其它线程 遇到这个关键字声明的变量,编译器对访问该变量的代码不进行优化。对此类变量的读操作总是从它的内存地址进行 |
using |
可以导入名字空间到当前名字空间中: using namespace std; // 然后,在当前名字空间中,你可以直接引用std中的成员 // 以::开头,表示命名空间的“绝对路径” ::cout << "Hello, World!" << ::endl; // 或者 cout << "Hello, World!" << endl; 可以导入名字空间中的某个成员到当前名字空间中: using std::cout; // 然后,你可以 ::cout << "Hello, World!" << std::endl; // 或者 cout << "Hello, World!" << std::endl; namespace NA { typedef int ma; } namespace NB { // 从其它名字空间导入的成员,就像自己的成员一样 using NA::ma; } int main() { // 可以被第三个名字空间导入 using NB::ma; ma i; } C++11中,using可以定义别名,类似于typedef: typedef void (*PFD)(double); // C风格 using PFD r = void (*)(double); // 等价的C++ 11风格 |
virtual |
用于定义虚函数成员,实现多态。注意:
|
throw | 用于抛出异常:
try { std::string("abc").substr(10); // 抛出 std::length_error } catch(const std::exception& e) { std::cout << e.what() << '\n'; throw e; // 复制初始化一个exception 类型的新异常对象 throw; // 重新抛出std::length_error类型的异常对象 } 也用于声明函数可能抛出的异常(C++ 11开始deprecated): void f() throw(int); // 函数 void (*pf)() throw (int); // 函数指针 void g(void pfa() throw(int)); // 作为参数的函数指针 typedef int (*pf)() throw(int); // 不允许 |
thread_local | 从C++ 11开始可用,用于声明线程本地存储。示例:
static thread_local ThreadLocalData thread_local_data_; |
包括:整数、浮点数、单个字符和布尔值的算术类型、一个void类型。
内置类型的机器表示:通常使用块chunk来使存储具有结构,块的bit数是2的幂,通常8位的块为字节;32位或4字节的为字(word)。
存储器中的每一个字节和一个称为地址的数关联起来。
关于有符号数(signed)和无符号数(unsigned):无符号数的所有位都表示数值,有符号数的第一位是符号位(0表示正数),8位无符号整数的范围为0 - 255;有符号整数的范围为 -127-127(某些实现允许-128 - 127)
整型 int、short 和 long 都默认为带符号型。要获得无符号型则必须指定该类型为 unsigned。unsigned int 类型可以简写为unsigned。
算术类型的存储空间(bit数)依机器而定,语言规定了最小存储空间:
类型 | 含义 | 最小存储空间 |
bool | boolean(可以将任何算数类型赋予bool,0假其余真) | NA |
char | character | 8bits |
wchar_t | wide character(用于扩展字符集,例如汉字) | 16bits |
short | short integer(一般为半个机器字长) | 16 bits |
int | integer (一般为1个机器字长) | 16 bits |
long |
long integer (一般为1-2个机器字长,在 32 位机器中 int 类型和 long 类型通常字长是相同)
|
32 bits |
float | single-precision floating-point (一般1个字) | 6 significant digits |
double | double-precision floating-point (一般2个字) | 10 significant digits |
long double | extended-precision floating-point (扩展精度,一般3-4个字) | 10 significant digits |
数据类型 | 举例 |
整数 |
L表示Long,U表示无符号;0开头表示8进制,0x开头表示16进制 20、20L、20UL、024、0x14
|
浮点数 |
3.14159F、 .001f、 12.345L、 0.、3.14159E0f、 1E-3F 、1.2345E1L 、0e0
|
布尔型 |
true false
|
字符型 |
L表示宽字符
'a' L'a'
|
字符串 |
为兼容C,C++的字符串字面值,由编译器在尾部添加\0
"string "、L"宽字符"
结尾反斜杠,可以把多行作为一个字符串处理:
"str\
ing" |
数据类型 | 说明 |
引用 |
不能定义引用类型的引用,声明时就必须初始化,一旦初始化就不能更改目标对象: int val = 1; int &refVal = val; //常引用 const int ival = 1024; const int &refVal = ival; //可以指定字面值初始化 const int &refVal = 1024; //如果一个函数返回的是一个引用类型,那么该函数的调用可以作为左值使用: int& func( int& i ){ return i; //注意,切勿返回局部变量的引用,因为函数调用结束了内存被回收 } int i = 0; func(i) = 2;//i的值为2 |
指针 |
指针可以为0,即不指向任何对象。指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,在32位的机器中,指针的数据长度为32bit。指针语法: int i = 2; //指向int类型的指针 int *pi = &i; //指向int*类型的指针 int **ppi = π int a[3] = { 1, 2, 3 }; //指向int[3]类型的指针,注意用(* )的形式强调变量的本质是指针,而不是数组 int (*pa)[3] = &a; ( *pa )[1] = 0; cout << a[1] << endl; // 没有用(*),这是int*的数组 int* poa[3] = { NULL, NULL, NULL }; int* (*ppoa)[3] = &poa; //这是一个指针,它指向int* [3]类型 int* (**pppoa)[3] = &ppoa; //数组可以作为指针看待,此时指针指向数组的第一个元素 //长度为10的数组,每个元素为int[15] int ad[10][15]; int (*pa)[15] = ad; //常量指针:指向常量的指针 const int i = 0; const int *pi = &i; int const *pi2 = i; //常指针:指针的指向不得改变 int j = 0; int * const pj = &j; //指向常量的常指针 const int * const cpi = &i; 指针相关的两个运算符:取地址&、解引用* |
函数指针 |
指向函数的指针,可以直接作为函数调用: //下面的不是函数指针,而是一个函数,其返回值是指向指针的指针 const vector* *seq_ptr( int ); //这是一个函数指针,指向的函数具有一个int参数,const vector*类型的返回值 const vector* ( *seq_ptr )( int ); seq_ptr = func;//可以把函数直接赋值给函数指针 const vector* ( Class*seq_ptr )( int );//指向成员函数的指针 const vector* ( *seq_ptr[] )( int ) = {...};//函数指针的数组 typedef void ( *newFuncTypeName )(int); //函数指针类型别名 |
typedef |
可以定义类型的同义词; 可以作为类型修饰符 typedef double wages; typedef unsigned int UINT; //简化复杂的数据类型 struct {int x; int y;} point_a, point_b; typedef struct {int x; int y;} Point; Point pa; |
枚举 |
默认枚举值从0开始,后续的值递增,可以指定某个元素的值(值可重复),但是递增的规律不变。枚举值必须是常量: enum open_modes { input, output = 3, append }; //枚举定义了新的类型,下面的方式声明枚举变量 open_modes om = input; typedef enum open_modes OM; OM om1 = input; //不能直接把整数赋值给枚举变量,必须进行类型转换,也不能使用枚举变量进行相互赋值 om = (open_modes)3; |
结构体 |
可以认为是仅仅有公共成员变量的类类型 struct Person { int age; string name; }; int main() { //使用值列表初始化 Person p = { 25, string( "Alex" ) }; } |
联合体 |
主要达到共享内存的效果,sizeof与其sizeof最大的成员一致: using namespace std; union Mixed { struct { int x; int y; } point; int a; }; int main() { Mixed m; m.point.x = 1; m.a = 2; cout << m.point.x << endl;//值变为2 } |
类 |
类定义一般会放入头文件。类定义示例: #include <iostream> using namespace std; //可以只声明,而不定义类。这里是前向声明(forward declaraton),可用于相互依赖的类的定义 //在定义之前,类是一个“不完全类型”,只能以有限方式使用。不能定义该类型的对象。 //不完全类型只能用于定义指向该类型的指针及引用 //或者用于声明(而不是定义)使用该类型作为形参类型或返回类型的函数 class Person; //在创建类的对象之前,必须完整地定义该类。在使用引用或指针访问类的成员之前,也必须已经定义类 //可以使用struct来定义类,区别:class定义第一个访问标号前是private,struct则是public class Person { //访问标号 public: //可以包含方法的定义 //常函数:该函数不会修改对象的变量,除了mutable变量 const string& getName() const { return name; } //静态函数不得是虚函数 static Person* newInstance( string n, int a ) { instanceCount++; return new Person( n, a ); } void setName( const string& name ); //运算符重载的声明:成员函数方式,对于二元操作符,this为左操作数 boolean operator==(const Person&) const(); //运算符重载:友元函数方式 friend boolean operation>(const Person& p1, const Person& p2); //可以包含类型别名定义 typedef int PersonType; //可以包含内部类,注意C++中没有内部类、静态内部类这样的区分 //只要定义了内部类就可以用Outer::Inner方式进行使用 class Address { public: string street; string no; }; //类型定义、内部类都可以使用名字空间操作符::访问,就像任何静态成员一样 private: //自我依赖,只要类名出现,就认为是已经声明,但目前还是不完全类型,必须使用引用或指针 Person& father; //只能声明变量的名字,不能初始化,初始化在构造函数中进行 string name; mutable int m;//易变的:即使发生改变,不会改变当前对象的常数性 int age; PersonType type; static int instanceCount; static const string ENTITY_NAME; //把运算符重载为私有函数,可以达到禁用的效果: Person& operator=(const Person&) const();//重载=,应该返回*this的引用 protected: //受保护成员,允许子类访问 virtual void eat() = 0; public: //构造函数,一个特殊的,与类同名的函数,可用于给每个数据成员设置适当的初始值 //如果类不声明任何构造函数,编译器会为之声明一个默认构造函数 //所谓默认构造函数,是指不包含形参,或者所有形参都提供默认值的函数 Person( string n = "", int a = 0 ) : //初始化列表,用于初始化成员变量,通常构造函数只包括初始化列表,不包括函数体 // 初始化列表的执行顺序,取决于成员变量的声明顺序,先声明的变量其初始化式先执行 name( n ), age( a * 1 ), //初始化式:可以是任意复杂度的表达式 father("",0), // 初始化式,用成员变量名来调用成员变量类的构造函数 { //不得在构造函数中调用任何虚函数,因为多态在这里不生效 //在这(父类构造方法)里,子类的“真实”类型是父类,可以通过dynamic_cast判断 } //析构函数,应当是公共的。不得抛出任何异常 //作为动态用途的父类,应当声明析构函数为虚函数 //virtual用于启用动态绑定,只能出现在类的方法声明,不得出现在类外的方法定义上 virtual ~Person() { //不得在析构函数中调用任何虚函数,因为多态在这里不生效 //在这里,子类的“真实”类型是父类 } //单参构造函数,可以用于自动的类型转换,explicit表示禁止隐式的类型转换 explicit Person( const User& u){}; //复制构造函数:具有单个const类型引用参数,它决定了对象pass-by-value的具体行为 //如果没有指定该函数,默认的行为是逐字段复制 Person( const Person& u){}; //纯虚函数,定义导致当前类变为抽象类 virtual getDob() = 0; //友元的声明 friend class FC; //FC类可以访问本类的私有成员 friend void fm(); //fm函数可以访问本类的私有成员 };//一旦遇到右花括号,类的定义就结束了,分号不可少,这是因为,可以立即定义类实例: ..} p1,p2; //在一个源文件中,一个类只能被定义一次。如在多个文件中定义一个类,每个文件中的定义必须相同 //将类定义在头文件中,可以保证在每个使用类的文件中以同样的方式定义类 //静态变量的定义须放在类定义的外部 const string Person::ENTITY_NAME = string( "Person" ); //方法定义可以在类定义的外部 void Person::setName( const string& name ) { this->name = name; } //派生类一般会重定义所继承的虚函数。派生类没有重定义某个虚函数,则使用基类中定义的版本 //派生类型必须对想要重定义的每个继承成员进行声明 //派生类中虚函数的声明必须与基类中的定义方式完全匹配,除了一个例外: //返回对基类型的引用(指针)的虚函数。派生类的虚函数可返回基类函数所返回类型的派生类的引用(指针) class User: public Person{ //一旦函数在基类中声明为虚函数,它就一直为虚函数,派生类无法改变该函数为虚函数这一事实 //派生类重定义虚函数时,可以使用 virtual 保留字,但不是必须这样做 protected: void eat(){}; public: User(): Person(){} //可以在初始化列表中调用父类的构造函数 } int main() { Person* p = Person::newInstance(); } |
函数对象 |
函数对象是类的一种,它是重载了()运算符的类 class FuncObj { public: //重载()运算符,注意,它可以接受任意个数的参数 inline void operator()( int a, int b, int c ) { cout << a + b + c << endl; } }; int main() { FuncObj fo; fo( 1, 2, 3 ); }
|
优先级 | 操作符 | 描述 | 示例 | 结合性 |
1 | () [] -> . :: ++ -- |
分组操作符 数组访问符 指针成员访问符 对象成员访问符 作用域操作符 后递增 后递减 |
(a + b) / 4; array[4] = 2; ptr->age = 34; obj.age = 34; Class::age = 2; for( i = 0; i < 10; i++ ) ... for( i = 10; i > 0; i-- ) ... |
左 |
2 | ! ~ ++ -- - + * & (type) sizeof |
逻辑非 按位否 前递增 前递减 一元负号 一元正号 解引用 取地址 强制类型转换 按字节返回占用内存长度 |
if( !done ) ... flags = ~flags; for( i = 0; i < 10; ++i ) ... for( i = 10; i > 0; --i ) ... int i = -1; int i = +1; data = *ptr; address = &obj; int i = (int) floatNum; int size = sizeof(floatNum); |
右 |
3 | ->* .* |
成员指针选择器 | ptr->*var = 24; obj.*var = 24; |
左 |
4 | * / % |
乘 除 模 |
int i = 2 * 4; float f = 10 / 3; int rem = 4 % 3; |
左 |
5 | + - |
加 减 |
int i = 2 + 3; int i = 5 - 1; |
左 |
6 | << >> |
按位左移 按位右移 |
int flags = 33 << 1; int flags = 33 >> 1; |
左 |
7 | < <= > >= |
小于 小于等于 大于 大于等于 |
if( i < 42 ) ... if( i <= 42 ) ... if( i > 42 ) ... if( i >= 42 ) ... |
左 |
8 | == != |
等于 不等于 |
if( i == 42 ) ... if( i != 42 ) ... |
左 |
9 | & | 按位与 | flags = flags & 42; | 左 |
10 | ^ | 按位异或 | flags = flags ^ 42; | 左 |
11 | | | Bi按位或 | flags = flags | 42; | 左 |
12 | && | 逻辑与 | if( conditionA && conditionB ) ... | 左 |
13 | || | 逻辑或 | if( conditionA || conditionB ) ... | 左 |
14 | ? : | 三元条件符 | int i = (a > b) ? a : b; | 右 |
15 | = += -= *= /= %= &= ^= |= <<= >>= |
赋值操作符 | int a = b; a += 3; b -= 4; a *= 5; a /= 2; a %= 3; flags &= new_flags; flags ^= new_flags; flags |= new_flags; flags <<= 2; flags >>= 2; |
右 |
16 | , | 序列估算操作符 | for( i = 0, j = 0; i < 10; i++, j++ ) ... | 左 |
为了让多个文件访问同一个的变量,C++对声明和定义进行了区分:
声明 | 定义 |
向程序表明变量的类型和名称 同一变量的声明可以发生多次 可以使用extern来多次声明,而不定义 |
向程序表明变量的类型和名称,并进行初始化 定义也同时是声明。只要提供初始化式,就认为是定义,即使标注extern关键字 |
初始化不是赋值:初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。未初始化的变量,用于任何其他用途其行为都是未定义的
int ival(1024); // 直接初始化,效率更高 int ival = 1024; // 赋值初始化 //对于类类型的对象,直接初始化是唯一的方式,必须通过调用构造函数完成 std::string titleA = "C++ Primer, 4th Ed.";//先调用默认工作函数,再赋值 std::string titleB("C++ Primer, 4th Ed.");//调用构造函数
内置类型 | 类类型 | |
文件作用域 |
自动初始化为0 |
如果提供了默认构造函数,自动调用完成初始化 |
局部作用域 |
必须手工初始化,否则是未定义的 |
如果提供了默认构造函数,自动调用完成初始化 |
堆作用域 |
必须显示调用 |
必须显式调用构造函数创建 |
销毁方式 | |
文件作用域 |
包括:全局对象、定义在namespace内的对象,class内、函数内、文件作用域内声明为static的对象 与程序生命周期一致,程序退出时析构。注意:全局对象的析构顺序和它们的构造顺序相反,但是全局对象的构造顺序不可预知,只能确保同一个文件中先定义的全局变量先构造 特别注意一下,函数的局部static变量:C++保证其在函数第一次调用时被初始化,可以用于解决跨编译单元的共享变量初始化不能保证完成的问题 |
局部作用域 |
局部作用域(包括块作用域)退出后,自动析构 |
堆作用域 |
必须手工删除 int* arr = new int[5]; //删除数组 delete[] arr;//delete不需要空指针校验 int* i = new int(1); //删除变量 delete i; |
使用限定符:const修饰,定义后就不能被修改,定义时必须初始化。默认为文件的局部变量:除非指定extern,否则不能被其他文件访问。允许在多个文件中进行const extern VAR的定义。
转型语法 | 说明 |
传统语法 |
C风格的转型(T)exp 函数风格的转型T(exp)
|
dynamic_cast(expr)
|
主要进行安全的向下转型,用于对象的指针和引用
对于指针:如果失败返回NULL;对于引用:如果失败抛出bad_cast异常
|
static_cast(expr)
|
进行任意的隐式转换(向上转换)和向下转型
|
const_cast(expr)
|
进行常量性移除或者设置 |
reinterpret_cast(expr)
|
重解释转型,可以转换一个指针为其它类型的指针,或者将指针与整数进行转换,不做任何相容性检查。示例:
tracing = *reinterpret_cast<const ::envoy::config::trace::v2::Tracing*>( &::envoy::config::trace::v2::_Tracing_default_instance_) |
所谓泛型编程,即以独立于任何特定类型的方式编写代码,这些类型(或者值)由模板的客户端在使用模板时提供。C++标准库中有大量模板的例子,每种容器,例如Vector,具有单一的定义,但是其使用者可以定义多种不同的Vector,其区别仅仅是容器包含的元素的类型不同。
/* 函数模板(function template) */ // 模板定义以关键字template开始,后接模板形参表,使用尖括号包围 // 其中包含一个或者多个模板形参的列表,模板形参表不能为空 // 模板形参表类似于函数形参表,定义却不初始化,在函数运行时(模板实例化时)才初始化 // typename 与 class 可以替换使用,没有区别 template<typename T> int compare( const T &v1, const T &v2 ) { //编译器接口,要求实际类型T支持小于操作符 if ( v1 < v2 ) return -1; if ( v2 < v1 ) return 1; return 0; } // 模板定义支持内联(也就是在调用模板函数的地方展开函数体),注意inline的位置 template<typename T> inline T min( const T&, const T& ); // 函数模板的返回值被指定为特殊的形参,这样是没法自动推导的,必须每次调用时显式指定 template<class T1, class T2, class T3> T1 sum( T2, T3 ); sum<int,int,int>(1, 2); // OK sum<int>(1, 2); // OK sum(1, 2); // ERROR typedef double Type; // 该类型定义在下面的模板内部不生效,被模板形参Type覆盖 /* 类模板(class template) */ template<class Type> class Queue { public: Queue(); Type &front(); // 模板作为成员函数返回值 const Type &front() const; void push( const Type & ); // 模板作为成员函数参数 void pop(); bool empty() const; //类模板中亦可声明成员函数模板 template<class V> V returnV( V &v ); }; /*函数模板的使用*/ // 使用函数模板时,编译器会推断哪个模板实参绑定到模板形参,一旦推定完毕,就称“实例化”了函数模板的实例 // 推导出实际模板实参后,编译器使用实参代替相应的模板形参产生编译该版本的函数 int main( int argc, char **argv ) { // 下面创建了函数模板的两个实例 compare( 1L, 2L ); // T推断为long类型 compare( "1", "2" ); // T推断为string类型 //可以将函数模板赋值给函数指针,编译器将自动实例化模板为函数指针需要的类型 int (*pf1)( const int&, const int& ) = compare; //函数模板的显式实参 sum<int>( 1, 2 ); // 模板类在实例化时,必须为模板形参指定实参 Queue<int> qi; } // 模板还可以这样写 template<int i> void hello() { std::cout << i << endl; } int main() { hello<1>(); }
参考下面的例子:
class Widget { public: Widget(); virtual ~ Widget(); virtual size_t size() const; }; //面向对象:显式接口与运行时多态 void doProcessing ( Widget& w ) { w.size(); } //面向模板:隐式接口与编译期多态 template <typename T> //声明一个模板类型参数 void doProcessing ( T& t ) { //模板参数需要支持的接口,由在参数上执行的操作决定: //模板内依赖于模板参数的名称,称为从属名称(dependent names) //例如下行的size就是一个从属名称: t.size(); //注意,默认情况下,从属名称被认为是一个数据成员的名字,如果期望将其看做类型 //应当显式的添加typename前缀。下面的例子防止const_iterator被解释为T的静态字段 typename T::const_iterator iter ( t .begin () ); //可以为作为类型的从属名称定义别名 typedef typename T ::const_iterator iter ( t .begin () ) ITER; }
所谓模板特化(template specialization),是指定制模板的具体实例(即所有形参被确定为具体的值、类型)的定义。
模板特化分为函数模板特化、类模板特化两种,对于后者,特化的类模板可以修改基模板中的函数定义、添加新的函数,或者删除基模板中的函数。尽管特化的类模板可以修改基模板的接口规格,但是应当尽量保持一致,避免违反模板使用者的直觉。
#include <string.h> /* 函数模板的特化 */ //template<>表示完全的模板特化 template<> //函数名后面附加尖括号,其中声明特化定义的模板形参 int compare<const char*>( const char* const &v1, const char* const &v2 ) { return strcmp( v1, v2 ); } //如果特化模板形参可以自动从函数形参推导,则不声明特化模板形参亦可 template<> int compare( const char* const &v1, const char* const &v2 ) { return strcmp( v1, v2 ); } //注意与下面的声明的区别:下面是声明一个普通的函数 int compare( const char* const &, const char* const & ); /* 类模板的特化 */ template<> class Queue<const char*> { public: void push( const char* ); void pop(); bool empty() const; // 特化可以定义与模板本身完全不同的成员,下面函数的定义与基模板不同 std::string front(); const std::string &front() const; }; //在外部定义特化类模板的成员时,前面不能添加template<>前缀 void Queue<const char*>::push( const char* val ) { } //只特化某个成员,而不特化整个类模板也是可以的 template<> void Queue<const char*>::push( const char * const &val ) { }
偏特化,即部分特化,则是指多个形参的类模板,针对其中一部分特化。类的偏特化模板本身仍然是模板。
template<class T1, class T2> class some_template { }; //固化T2参数为int类型,允许T1参数变化 template<class T1> class some_template<T1, int> { }; some_template<int, string> foo; // 使用基模板版本 some_template<string, int> bar; // 使用偏特化模板版本
注意:只能偏特化整个类,而不能仅仅偏特化某个成员函数。
Traits是一种小对象,其用途是,携带其它对象或算法所需要的信息,这些信息决定了“策略”或实现细节。
在C++中Traint通常都实现为结构,例如:
// Traint类型 template <Address::SocketType T> struct NetworkSocketTrait {}; template <> struct NetworkSocketTrait<Address::SocketType::Stream> { static constexpr Address::SocketType type = Address::SocketType::Stream; }; template <> struct NetworkSocketTrait<Address::SocketType::Datagram> { static constexpr Address::SocketType type = Address::SocketType::Datagram; }; // 其它对象 template <typename T> class NetworkListenSocket : public ListenSocketImpl { public: NetworkListenSocket(const Address::InstanceConstSharedPtr& address) // Traint携带的type被使用,决定创建UDP还是TCP套接字 : ListenSocketImpl(address->socket(T::type), address) {} // 使用Traint using TcpListenSocket = NetworkListenSocket<NetworkSocketTrait<Address::SocketType::Stream>>; socket_ = std::make_unique<Network::TcpListenSocket>(address);
Trait作为模板,它:
考虑一个容器模板类,当元素的move行为取决于元素类型时,可以将这个行为提取到Trait中实现:
struct Dog { void move() { } }; template<typename M> struct MoveableTrait { void move(M m) {} }; template<> struct MoveableTrait<Dog> { void move(Dog d) { d.move(); } }; template<typename T> struct Container { // 此行为取决于元素类型,委托给元素类型T的trait处理 void move(T *p) { MoveableTrait<T>().move(p); }; };
运算符重载是具有特殊函数名的函数:
// 形式一:重载普通运算符 operator op // 形式二:自定义类型转换函数,转换为type类型 operator type // 示例:转换为bool operator bool() const throw(){ return (0 < pn.use_count()); } // 形式三:分配函数 operator new operator new [] // 形式四:删除函数 operator delete operator delete [] // 形式五:自定义字面值,C++ 11 operator ""
下表中的@ 是表示所有匹配运算符的占位符:
表达式 | 重载为 | 示例 | |
成员函数 | 非成员函数 | ||
@a | (a).operator@ ( ) | operator@ (a) |
T& operator*() const { return *px; } // *object即*px |
a@b | (a).operator@ (b) | operator@ (a, b) | |
a=b | (a).operator= (b) | 不得作为非成员函数 | |
a(b...) | (a).operator()(b...) | ||
a[b] | (a).operator[](b) | ||
a-> | (a).operator-> ( ) |
T* operator->() const { return px; } // object->method()即px->method() |
|
a@ | (a).operator@ (0) | operator@ (a, 0) |
可以将运算符重载作为函数,显式的调用:
std::string str = "Hello, "; str.operator+=("world"); // 等价于 str += "world"; operator<<(operator<<(std::cout, str) , '\n'); // 等价于std::cout << str << '\n';
即raw字符串,支持多行、不需要转义:
const std::string BPF_PROGRAM = R"( int on_sys_clone(void *ctx) { bpf_trace_printk("Hello, World! Here I did a sys_clone call!\n"); return 0; } )";
你可以声明任意表达式甚至函数为编译期常量:
constexpr int N = 5; constexpr int five() { return 5; } int a[N];
此关键字在C++11中用于自动类型推导,推导在编译阶段完成。自动类型推导避免了复杂的类型声明,在模板编程中特别有用,因为在编写模板库时你不知道模板实参的类型。
用于从表达式中推导出类型:
int x = 1; decltype( x ) y = x; // 等价于 int y = x;
decltype不会对表达式进行求值,仅仅会推导其结果的类型。推导规则如下:
final用于禁止类被继承,或者禁止虚函数被子类覆盖:
class User final {}; class Admin:User{}; // error: cannot derive from ‘final’ base ‘User’ in derived type ‘Admin’ class User { protected: virtual void say(string what) final{} }; class Admin : User { protected: virtual void say(string what){} // error: overriding final function ‘virtual void User::say(std::string)’ };
override则用于显式的说明当前函数是覆盖了基类的虚函数:
class Admin : User { protected: virtual void say(string what) override {} };
用于显式的指定、禁止编译器自动生成的成员(例如构造器、析构器):
class User { protected: // 启用默认构造函数 User() = default; // 禁止默认的new运算符重载 void *operator new(size_t) = delete; };
标准化的,至少64位的整数。
静态断言,在编译期完成的断言,示例:
template< class T > struct Check { static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ; } ;
相对的:
为了解决NULL的二义性问题引入:
void f( int i ) { cout << "int" << endl; } void f( User *user ) { cout << "User" << endl; } int main() { f( NULL ); // error: call of overloaded ‘f(NULL)’ is ambiguous f( nullptr ); }
类似于Java的迭代器遍历语法:
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}}; for (auto p : m){ cout<<p.first<<" : "<<p.second<<endl; }
适用范围:数组、容器、string、任何迭代器(即具有begin/end函数的对象)。
现在你可以在构造函数的初始化列表中调用其它构造函数了:
class User { private : User( string name, int age, bool gender ) {} public: User(string name):User(name,0,0){} };
这种语法原先仅仅用于数组的初始化:
int arr[3]{1, 2, 3};
现在你可以:
// 初始化集合 vector<int> iv{1, 2, 3}; map<int, string>{{1, "a"}, {2, "b"}}; // 初始化字符串 string str{"Hello World"}; // 初始化自定义对象 struct User { User( string name, int age ){}; }; User alex{ "Alex", 30 }; // 任何时候你都可以适用 = 变体 User alex = { "Alex", 30 };
[函数对象参数](操作符重载函数参数)[mutable|exception声明] -> 返回值类型 { 函数体 } //举例: [ &a, =b, this ]( int c, &int d ) mutable -> void { }( 1, 2 );
标识一个Lambda的开始,这部分为必须
函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量 (包括Lambda所在类的this)。
函数对象参数有以下形式:
举例:
声明重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:a,b)和按引用(如:&a,&b)两种方式进行传递
按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)
声明函数返回值的类型,当返回值为void,或若函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略
标识函数的实现,这部分不能省略,但函数体可以为空
右值引用实现了移动语义 (Move Sementics) 和精确转发(完美转发,Perfect Forwarding)。其意义在于:
C/C++中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象,左值可以出现在 = 操作符的左边,右值则不可以:
// i是左值,0是右值(临时对象) // i可以被引用,0则不可以 int i = 0; // 左侧的整体是左值,但是 0 不能独立出现在左侧 ((i>0) ? i : j) = 1;
在C++ 11 之前右值仅仅能够被常量引用:
const int &a = 1;
在函数声明中,使用 &&表示引用右值:
void process( int &i ) {} // 重载版本,如果传入右值 void process( int &&i ) {} int main() { int i = 0; process( i ); process( 1 ); }
当传入临时对象1时,自动调用重载版本,临时对象不会被拷贝,而是被”移动“,减少了内存拷贝的开销。
此函数支持将左值引用转变为右值引用(赋予其临时对象语义):
#include <algorithm> int i = 0; process( std::move(i) );
使用此函数可以避免不必要的内存拷贝:
template <class T> swap(T& a, T& b){ T tmp(a); // copy a to tmp a = b; // copy b to a b = tmp; // copy tmp to b } // 下面的实现避免了三次内存拷贝 template <class T> swap(T& a, T& b){ T tmp(std::move(a)); // move a to tmp a = std::move(b); // move b to a b = std::move(tmp); // move tmp to b }
std::move的关键之处在于,它定义了移动语义 —— 声明一个对象放弃其持有的资源(内存),无条件的转换为右值引用。上面基本类型的移动语义不明显,可以考虑下面的例子:
class User { public: string name; User( string name ) : name( name ) {} }; int main() { User u1 = User( "Alex" ); cout << u1.name << endl; // Alex User u2 = std::move( u1 ); cout << u1.name << endl; // 空 }
在把右值赋值给变量u2时,调用了User的隐含移动构造器,导致u1持有的内存资源(字段name)转移。
注意:在使用了转移语义之后,原对象不需要析构,因为它变成了一个空壳。
所谓移动构造器,就是指类型T的这样的构造器:
以下情况下移动构造器会被调用:
T b; // 1 T a = std::move(b); // 2 T a(std::move(b)); // 3 void f(T t); f(std::move(b)); // 4 T f(){ // 返回语句调用移动构造器 return T(); }
如果用户没有为class/struct/union定义移动构造器,并且:
则编译器会自动声明一个隐含的移动构造器,其签名为T::T(T&&)。
即使不符合上述条件,你也可以强制要求编译器生成某个移动构造器的默认实现(其逻辑和隐含移动构造器相同),使用语法:
T ( T && ) = default;
如果类型T符合下面任何一种情况:
则T的默认/隐含移动构造器实现是删除的(deleted):
T ( T && ) = delete;
在某些场景下,我们需要将一组参数从一个函数原封不动的传递给另一个函数。所谓原封不动,不仅仅是指其值不变,还要求参数的左/右值、常量/非常量属性不变。
考虑下面的函数模板:
template <typename T> void forward_value(const T& val) { process_value(val); } template <typename T> void forward_value(T& val) { process_value(val); }
为了能够转发常量/非常量版本的参数,在以前我们不得不定义重载版本,这种工作很无聊。现在,我们只需要使用右值引用:
template <typename T> void forward_value(T&& val) { process_value(val); }
就可以了。不管实参是左值、右值,常量、非常量,都可以精确的转发给process_value。
std::forward可以用来将,模板函数参数转换为调用者指定的值类别(左/右):
// std::make_unique<Envoy::MainCommon>(argc, argv) // 模板变量声明 template<typename _Tp, typename... _Args> // 内联 // 返回值,用typename提示__single_object是结构模板_MakeUniq<_Tp>中定义的从属名称,而非静态成员 inline typename _MakeUniq<_Tp>::__single_object make_unique(_Args&&... __args) // 精确转发,上面的argc argv是什么类别,就转换为什么类别 ———— 左值作为左值传递,右值作为右值传递,给_Tp() { return unique_ptr<_Tp>(new _Tp(std::forward<_Args>(__args)...)); }
C++ 11之前,模板参数的数量是固定的,现在可以使用新语法来定义可变参数长度的模板:
// 可变参数模板 template<typename... Values> class tuple; // 上述类型的模板参数个数是任意的,0个也可以: tuple<int, std::vector<int>, std::map<<std::string>, std::vector<int>>> some_instance_name; tuple<> some_instance_name; // 要求至少有一个模板参数的可变参数模板: template<typename First, typename... Rest> class tuple;
此标记有两个用途:
示例:
#include <algorithm> #include <iostream> using namespace std; template<typename... Args> void print( Args... args ) { auto argList = { args... }; for ( auto i = argList.begin(); i != argList.end(); ++i ) { cout << *i << endl; } } int main() { print( 1, 2, 3 ); }
C++ 11引入的函数对象标准库,包含了各种内建函数对象和相关的操作函数。
std::function、std::bind、std::result_of、std::thread::thread、std::call_once、std::async、std::packaged_task、std::reference_wrapper等都可以向函数那样被调用,即支持()操作符。
这是一个通用的函数包装器,可以容纳任何可调用类型 —— 例如函数、函数指针、Lambda、bind表达式、成员函数、其它函数对象。该模板类的声明如下:
template< class R, class... Args > class function<R(Args...)>
用法示例:
void plus(int a, int b) { std::cout << a + b << std::endl; } int main() { // 函数 std::function<void(int, int)> plus_func = plus; plus_func(1, 2); // 3 //Lambda std::function<int(int, int)> plus_lmbda = [](int a, int b) -> int { return a + b; }; // 绑定,std::placeholders用于占位绑定后的函数的参数 // plus5()的第1参数对应plus的第2参数 auto plus5 = std::bind(&plus, 5, std::placeholders::_1); plus5(1); // 6 }
为函数绑定一部分参数,产生一个新的偏函数(Partial Function)。 新函数的参数使用std::placeholders中的成员来占位:
void abcde(char a, char b, char c, char d, char e) { std::cout << a << b << c << d << e << std::endl; } int main() { auto bd = std::bind(abcde, 'a', std::placeholders::_2, 'c', std::placeholders::_1, 'e'); bd('b', 'd'); // adcbe }
注意,std::placeholders::_1为偏函数的第一个参数占位,最多可以到std::placeholders::_29
C++11废弃了C++98中的auto_ptr,同时把shared_ptr、uniq_ptr引入到std名字空间。C++ 11包含的智能指针有:
指针 | 说明 |
unique_ptr | 确保资源仅仅能够被一个unique_ptr管理,如果unique_ptr生命周期结束,则资源自动销毁。可以利用右值引用来在不同指针之间转移对象:
unique_ptr<int> a( new int(0) ); unique_ptr<int> b = move( a ); |
shared_ptr | 多个shared_ptr可以共享一个资源,使用引用计数管理资源,当所有使用资源的shared_ptr生命周期结束,自动销毁资源 |
weak_ptr | 配合shared_ptr使用,weak_ptr不会影响引用计数 |
注意,传递智能指针时,总是传值。
相比其原来就有的可以定义二元组的std::pair,std::tuple可以定义任意元组:
std::tuple<int, double, string> t{ 1, 1.0, string( "" ) };
原先STL中的C++的map, multimap, set, multiset均基于红黑树实现,插入/查询的复杂度是O(lgn)。C++11提供了基于哈希的实现版本:std::unordered_set、std::unordered_multiset、std::unordered_map、std::unordered_multimap,它们的插入/查询的复杂度是O(1)
private: static bool & get_lock(){ //返回引用类型 static bool lock = false; return lock; //对于静态变量,可以安全返回,不会引用函数的退出而销毁 } public: static void lock(){ get_lock() = true; //返回引用类型,可以直接作为左值使用 } static void unlock(){ get_lock() = false; } static bool is_locked() { return get_lock(); } };
//list、vector的erase方法,返回下一个迭代器,故可以直接使用 using namespace boost::assign; vector v; v += 1, 2, 3, 4, 5; for ( vector< int>::iterator it = v .begin(); it != v.end(); ) { if ( *it > 2 && *it < 5 ) { it = v.erase( it++ ); } else { ++ it; } } //map、set的erase函数返回的 void,而在进行 erase 之后,当前迭代器会失效,无法再用于获取下一个迭代器: set s; s += 1, 2, 3, 4, 5; for ( set::iterator it = s.begin(); it != s.end(); ) { if ( * it > 2 && * it < 5 ) { s.erase( it++ ); } else { ++ it; } } //从反向迭代器获取正向迭代器,需要调用iter.base()方法。 using namespace boost::assign; vector v; v += 1, 2, 3, 4, 5; for ( vector::reverse_iterator it = v.rbegin(); it != v.rend(); ) { if ( * it > 2 && * it < 5 ) { it = vector< int>::reverse_iterator( v .erase( ( ++it ).base() ) ); } else { ++ it; } }
// 文件输入流 std::ifstream pipe("/sys/kernel/debug/tracing/trace_pipe"); // 读取到字符串 std::string line; while (true) { if (std::getline(pipe, line)) { // 最后一行 std::cout << line << std::endl; break; } else { // 还可以读取 } }
可能是由于使用Unicode引起的,把字符串直接量使用_T()包含即可。
可能原因:
普通函数指针、类成员函数指针不是一类东西,不能相互转换:
#include <iostream> class Duck { public: // 类成员函数指针 typedef void (Duck::*VoidClassFuncPtr)(); void quack() { } }; // 自由函数指针 typedef void (*VoidFuncPtr)(); void callFp( VoidFuncPtr fp ) { } int main() { Duck duck; // OK Duck::VoidClassFuncPtr cfp = &Duck::quack; // 调用成员函数指针的语法,注意括号不能少 ( duck.*cfp )(); // 报错:类型不兼容 VoidFuncPtr fp = &Duck::quack; return 0; }
类似的,带有捕获(Capture)的Lambda表达式也不是普通函数(自由函数),不能赋值给自由函数指针。
普通函数指针只能指向一个全局的、单独的函数,其关键是目标函数不会有关联的状态信息。没有关联状态的类静态函数,则可以安全的赋值给自由函数指针:
class Duck { public: static void quack() {} }; typedef void (*VoidFuncPtr)(); int main() { Duck duck; // OK VoidFuncPtr fp = &Duck::quack; return 0; }
所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间。段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。 指针没有初始化可能导致该错误。
报错信息 | 原因分析 |
include/boost/mpl/eval_if.hpp:60:31: error: no type named 'type' in 'struct boost::mpl::eval_if_c <true, boost::range_const_iterator, boost::range_mutable_iterator >::f_' |
join函数的分隔符,使用char类型导致 |
include/boost/algorithm/string/detail/finder.hpp:578:29: error: '((const boost::algorithm::detail::token_finderF*)this)->boost::algorithm::detail::token_finderF::m_Pred' cannot be used as a function | 用法错误,注意:split( fileNameSeg, fileName, is_any_of("\\/" ) ); 其中任意一个字符用于分隔 |
Leave a Reply to Alex Cancel reply