Menu

  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay
  • Home
  • Work
    • Cloud
      • Virtualization
      • IaaS
      • PaaS
    • Java
    • Go
    • C
    • C++
    • JavaScript
    • PHP
    • Python
    • Architecture
    • Others
      • Assembly
      • Ruby
      • Perl
      • Lua
      • Rust
      • XML
      • Network
      • IoT
      • GIS
      • Algorithm
      • AI
      • Math
      • RE
      • Graphic
    • OS
      • Linux
      • Windows
      • Mac OS X
    • BigData
    • Database
      • MySQL
      • Oracle
    • Mobile
      • Android
      • IOS
    • Web
      • HTML
      • CSS
  • Life
    • Cooking
    • Travel
    • Gardening
  • Gallery
  • Video
  • Music
  • Essay

C++学习笔记

15
Apr
2008

C++学习笔记

By Alex
/ in C++
/ tags 学习笔记
0 Comments
C++基础
名词术语
  1. Stack unwinding:栈展开。此概念常常在讨论C++异常处理时提及。当异常被抛出时,将寻找最近的try-catch块。如果找不到处理该异常的块,则当前栈帧被弹出,并到Caller函数中寻找。此操作会递归的进行,直到找到异常处理代码——整个过程就是所谓Unwinding。Unwinding过程中会执行栈上对象的自动析构,这种自动析构能力正是概念RAII(Resource Acquisition Is Initialization)的基础,可以帮助自动化内存、数据库连接或者其它资源的管理
常见陷阱和技巧
  1. 在构造函数、析构函数体中,虚函数的多态性得不到体现,这点与Java不同
  2. 使用基类对象,而非基类的引用或指针时,虚函数的多态性得不到体现
  3. 函数调用时,实参的构造顺序是不被保证的,并不是像Java那样从第一个开始构造
  4. 内置类型、函数对象、STL迭代器往往适合pass-by-value
  5. 必须返回对象时,勿尝试返回其引用,例如:切勿尝试返回局部栈变量的引用
  6. 尽可能延迟变量定义式的出现时间,避免不必要的构造、析构开销
  7. 尽可能使用类的声明式代替定义式:例如:class Date; Date today(); void clear(Date);//后面两个表达式不需要知道定义式
  8. 如果使用&或者*可以完成任务,则不使用object本身
  9. 使用纯虚函数用来保证类必须被继承
  10. 不要定义继承来的非virtual函数
  11. 不要定义继承来的缺省参数值:虚函数是动态绑定,而其缺省参数却是静态绑定
  12. public继承:基类的public成员仍然为public,protected仍然为protected
    protected继承,基类的public、protected成员均为protected
    私有继承:基类的public、protected均为private
  13. 合理的使用私有继承:私有继承意味着仅仅继承实现,接口部分被略去,在软件设计层面没有意义。尽可能使用复合代替
  14. 钻石型继承层次问题(通过大于1个路径到达同一个基类):使用虚拟继承(使得派生类如果继承基类多次,但只有一份基类的拷贝在派生类对象中),防止重复的继承变量:
    C++
    1
    2
    3
    4
    class A;//虚拟基类最好不包含任何数据
    class B1 : public virtual A;
    class B2 : public virtual A;
    class C : public B1, public B2;
  15. 如果函数抛出异常:C++保证,所有函数中的局部变量的destructor都被调用
  16. 全局变量的初始化:保证在main函数执行前,完成全局变量的初始化
  17. 静态变量的初始化:C语言中的全局、静态变量的内存空间都是全局的,初始化发生在任何代码执行之前。而C++规定,函数内的局部静态变量只有在第一次使用时才初始化
  18. 在编写类的定义(和声明分离)时,不要给函数添加static、virtual等修饰符
关键字
 关键字  说明
const

用于表示变量不可改变,或者成员函数不能修改对象的状态

explicit

用于禁止构造函数参数的隐式类型转换,只能用于单参的构造函数

extern 

声明函数或全局变量为一个外部链接(即在别处定义),且要在本模块引用,可以跨文件作用域的共享函数、全局变量

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//这是一个全局变量声明
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++混合编程,具有双重含义:
  1. 被修饰的函数或变量是extern类型的
  2. 被修饰的变量和函数是按照C语言方式编译和链接的,不使用C++方式的名称改编

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//在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

可以导入名字空间到当前名字空间中:

C++
1
2
3
4
5
6
using namespace std;
// 然后,在当前名字空间中,你可以直接引用std中的成员
// 以::开头,表示命名空间的“绝对路径”
::cout << "Hello, World!" << ::endl;
// 或者
cout << "Hello, World!" << endl;

可以导入名字空间中的某个成员到当前名字空间中:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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:

C++
1
2
3
typedef void (*PFD)(double);    // C风格
using PFD
r = void (*)(double);    // 等价的C++ 11风格
virtual

用于定义虚函数成员,实现多态。注意:

  1. 在构造、析构函数中不要调用虚函数,因为没有多态效果
  2. 析构函数定义为虚拟的,则可以允许通过基类指针析构子类对象
  3. 不要在析构函数中调用父类的析构函数,析构函数总是自动递归调用,顶级类最后调用
throw 用于抛出异常:
C++
1
2
3
4
5
6
7
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):

C++
1
2
3
4
void f() throw(int);            // 函数
void (*pf)() throw (int);       // 函数指针
void g(void pfa() throw(int));  // 作为参数的函数指针
typedef int (*pf)() throw(int); // 不允许 
thread_local 从C++ 11开始可用,用于声明线程本地存储。示例:
C++
1
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"
其他数据类型
数据类型   说明
引用

不能定义引用类型的引用,声明时就必须初始化,一旦初始化就不能更改目标对象:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
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。指针语法:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int i = 2;
//指向int类型的指针
int *pi = &i;
//指向int*类型的指针
int **ppi = &pi;
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;

 指针相关的两个运算符:取地址&、解引用* 

函数指针

指向函数的指针,可以直接作为函数调用:

C++
1
2
3
4
5
6
7
8
9
//下面的不是函数指针,而是一个函数,其返回值是指向指针的指针
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

可以定义类型的同义词; 可以作为类型修饰符

C++
1
2
3
4
5
6
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开始,后续的值递增,可以指定某个元素的值(值可重复),但是递增的规律不变。枚举值必须是常量:

C++
1
2
3
4
5
6
7
8
9
enum open_modes {
    input, output = 3, append
};
//枚举定义了新的类型,下面的方式声明枚举变量
open_modes  om = input;
typedef enum open_modes OM;
OM om1 = input;
//不能直接把整数赋值给枚举变量,必须进行类型转换,也不能使用枚举变量进行相互赋值
om = (open_modes)3;
结构体

可以认为是仅仅有公共成员变量的类类型

C++
1
2
3
4
5
6
7
8
9
10
struct Person
{
        int age;
        string name;
};
int main()
{
    //使用值列表初始化
    Person p = { 25, string( "Alex" ) };
}
联合体

主要达到共享内存的效果,sizeof与其sizeof最大的成员一致:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
}
类

类定义一般会放入头文件。类定义示例:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#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();
}
函数对象

函数对象是类的一种,它是重载了()运算符的类

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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关键字

左值和右值
  1. 左值(ell-value):左值可以出现在赋值语句的左边或右边。左值对应了内存中具有明确存储地址的对象
  2. 右值( are-value):右值只能出现在赋值的右边,不能出现在赋值语句的左边。不是左值,也就是没有明确存储地址的对象,均为右值
变量的初始化、赋值与生命周期

初始化不是赋值:初始化指创建变量并给它赋初始值,而赋值则是擦除对象的当前值并用新值代替。未初始化的变量,用于任何其他用途其行为都是未定义的

初始化方式:
C++
1
2
3
4
5
int ival(1024);  // 直接初始化,效率更高
int ival = 1024; // 赋值初始化
//对于类类型的对象,直接初始化是唯一的方式,必须通过调用构造函数完成
std::string titleA = "C++ Primer, 4th Ed.";//先调用默认工作函数,再赋值
std::string titleB("C++ Primer, 4th Ed.");//调用构造函数
初始化规则:
   内置类型 类类型 

文件作用域

自动初始化为0

如果提供了默认构造函数,自动调用完成初始化

局部作用域

必须手工初始化,否则是未定义的

如果提供了默认构造函数,自动调用完成初始化

堆作用域

必须显示调用

必须显式调用构造函数创建
销毁规则:
   销毁方式

文件作用域

包括:全局对象、定义在namespace内的对象,class内、函数内、文件作用域内声明为static的对象

与程序生命周期一致,程序退出时析构。注意:全局对象的析构顺序和它们的构造顺序相反,但是全局对象的构造顺序不可预知,只能确保同一个文件中先定义的全局变量先构造
C++ 03标准规定,cout/cerr等标准I/O对象在最后析构,但是一些老旧的编译器没有遵循此标准

特别注意一下,函数的局部static变量:C++保证其在函数第一次调用时被初始化,可以用于解决跨编译单元的共享变量初始化不能保证完成的问题

局部作用域

局部作用域(包括块作用域)退出后,自动析构

堆作用域

必须手工删除

C++
1
2
3
4
5
6
int* arr = new int[5];
//删除数组
delete[] arr;//delete不需要空指针校验
int* i = new int(1);
//删除变量
delete i;
变量的作用域
  1. 支持块级作用域,块用{}限定
  2. 函数内定义的局部变量,只能在函数内访问
  3. 函数外定义的变量,具有全局作用域,可以被整个程序访问(其它文件访问需要extern声明此变量)
常量

使用限定符: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)
重解释转型,可以转换一个指针为其它类型的指针,或者将指针与整数进行转换,不做任何相容性检查。示例:
Go
1
2
tracing = *reinterpret_cast<const ::envoy::config::trace::v2::Tracing*>(
      &::envoy::config::trace::v2::_Tracing_default_instance_)
模板与泛型编程
模板编程基础

所谓泛型编程,即以独立于任何特定类型的方式编写代码,这些类型(或者值)由模板的客户端在使用模板时提供。C++标准库中有大量模板的例子,每种容器,例如Vector,具有单一的定义,但是其使用者可以定义多种不同的Vector,其区别仅仅是容器包含的元素的类型不同。

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
/* 函数模板(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>();
}
隐式接口与编译期多态

参考下面的例子:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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),是指定制模板的具体实例(即所有形参被确定为具体的值、类型)的定义。

模板特化分为函数模板特化、类模板特化两种,对于后者,特化的类模板可以修改基模板中的函数定义、添加新的函数,或者删除基模板中的函数。尽管特化的类模板可以修改基模板的接口规格,但是应当尽量保持一致,避免违反模板使用者的直觉。

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#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 )
{
 
}

偏特化,即部分特化,则是指多个形参的类模板,针对其中一部分特化。类的偏特化模板本身仍然是模板。

C++
1
2
3
4
5
6
7
8
9
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

Traits是一种小对象,其用途是,携带其它对象或算法所需要的信息,这些信息决定了“策略”或实现细节。

在C++中Traint通常都实现为结构,例如:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 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作为模板,它:

  1. 声明了统一的接口(包括类型、枚举、函数方法等、字段) 
  2. 通过模板特化,针对不同数据类型或其他模板参数,为类、函数或者通用算法在因为使用的数据类型不同而导致处理逻辑不同时,提供了区分不同类型的具体细节,从而把这部分用Traits实现的功能与其它共同的功能区分开来

考虑一个容器模板类,当元素的move行为取决于元素类型时,可以将这个行为提取到Trait中实现:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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); };
};
运算符重载

运算符重载是具有特殊函数名的函数:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 形式一:重载普通运算符
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 ""
签名规则

下表中的@ 是表示所有匹配运算符的占位符:

  1. @a 中为所有前缀运算符
  2. a@ 为除外 -> 的所有后缀运算符
  3. a@b为除外 = 的所有其他运算符
表达式 重载为 示例
成员函数 非成员函数
@a (a).operator@ ( ) operator@ (a)
C++
1
2
3
4
5
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-> ( )
C++
1
2
3
4
5
T* operator->() const
{
    return px;
}
// object->method()即px->method() 
a@ (a).operator@ (0) operator@ (a, 0)  
显式调用

可以将运算符重载作为函数,显式的调用:

C++
1
2
3
std::string str = "Hello, ";
str.operator+=("world");                       // 等价于 str += "world";
operator<<(operator<<(std::cout, str) , '\n'); // 等价于std::cout << str << '\n';
C++ 11
R"()"字符串

即raw字符串,支持多行、不需要转义:

C++
1
2
3
4
5
6
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关键字

你可以声明任意表达式甚至函数为编译期常量:

C++
1
2
3
constexpr int N = 5;
constexpr int five() { return 5; }
int a[N];
auto关键字

此关键字在C++11中用于自动类型推导,推导在编译阶段完成。自动类型推导避免了复杂的类型声明,在模板编程中特别有用,因为在编写模板库时你不知道模板实参的类型。

decltype关键字

用于从表达式中推导出类型:

C++
1
2
int x = 1;
decltype( x ) y = x;  // 等价于 int y = x;

decltype不会对表达式进行求值,仅仅会推导其结果的类型。推导规则如下:

  1. 如果表达式为变量、函数参数、类成员,则返回表达式的声明类型
  2. 否则,根据表达式的值类别决定推导结果,假设表达式的类型为T:
    1. 如果表达式为左值(lvalue,可寻址值),则推导为 T&
    2. 如果表达式为临终值(xvalue,对象生命周期已经结束,但是内存尚未回收),推导为 T&&
    3. 如果表达式为纯右值(pvalue)推导为 T
final/override关键字

final用于禁止类被继承,或者禁止虚函数被子类覆盖:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
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则用于显式的说明当前函数是覆盖了基类的虚函数:

C++
1
2
3
4
class Admin : User {
protected:
    virtual void say(string what) override {}
};
default/delete关键字

用于显式的指定、禁止编译器自动生成的成员(例如构造器、析构器):

C++
1
2
3
4
5
6
7
class User {
protected:
    // 启用默认构造函数
    User() = default;
    // 禁止默认的new运算符重载
    void *operator new(size_t) = delete;
};
long long int 

标准化的,至少64位的整数。

static_assert

静态断言,在编译期完成的断言,示例:

C++
1
2
3
4
template< class T >
struct Check {
  static_assert( sizeof(int) <= sizeof(T), "T is not big enough!" ) ;
} ;

相对的:

  1. assert宏在运行期起作用
  2. 预处理指令 #error在预处理期间生效
nullptr

为了解决NULL的二义性问题引入:

C++
1
2
3
4
5
6
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 );
}
简化的for循环

类似于Java的迭代器遍历语法:

C++
1
2
3
4
map<string, int> m{{"a", 1}, {"b", 2}, {"c", 3}};
for (auto p : m){
    cout<<p.first<<" : "<<p.second<<endl;
}

适用范围:数组、容器、string、任何迭代器(即具有begin/end函数的对象)。

构造函数委托

现在你可以在构造函数的初始化列表中调用其它构造函数了:

C++
1
2
3
4
5
6
class User {
private :
    User( string name, int age, bool gender ) {}
public:
    User(string name):User(name,0,0){}
};
{}初始化语法

这种语法原先仅仅用于数组的初始化:

C++
1
int arr[3]{1, 2, 3};

现在你可以:

C++
1
2
3
4
5
6
7
8
9
10
11
12
// 初始化集合
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 };
Lambda表达式
语法形式
C++
1
2
3
4
5
6
7
[函数对象参数](操作符重载函数参数)[mutable|exception声明] -> 返回值类型
{
    函数体
}
//举例:
[ &a, =b, this ]( int c, &int d ) mutable -> void {
}( 1, 2 );
[函数对象参数]

标识一个Lambda的开始,这部分为必须
函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义Lambda为止时Lambda所在作用范围内可见的局部变量 (包括Lambda所在类的this)。
函数对象参数有以下形式:

  1.  空 没有使用任何函数对象参数
  2. = 函数体内可以使用Lambda所在作用范围内所有可见的局部变量 (包括Lambda所在类的this),并且是值传递方式
  3. & 函数体内可以使用Lambda所在作用范围内所有可见的局部变量 (包括Lambda所在类的this),并且是引用传递方式
  4. this 函数体内可以使用Lambda所在类中的成员变量

举例:

  1. a  将a按值进行传递。按值进行传递时,函数体内不能修改传递进來的a的拷贝,除非添加mutable修饰符
  2. &a 将a按引用进行传递
  3. a, &b 将a按值进行传递,b按引用进行传递
  4. =, &a,&b 除a和b按引用进行传递外,其他参数都按值进行传递
  5. &, a, b 除a和b按值进行传递外,其他参数都按引用进行传递
(操作符重载函数参数)

声明重载的()操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如:a,b)和按引用(如:&a,&b)两种方式进行传递

mutable或exception声明

按值传递函数对象参数时,加上mutable修饰符后,可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
exception声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)

返回值类型

声明函数返回值的类型,当返回值为void,或若函数体中只有一处return的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略

{函数体}

标识函数的实现,这部分不能省略,但函数体可以为空

右值引用

右值引用实现了移动语义 (Move Sementics) 和精确转发(完美转发,Perfect Forwarding)。其意义在于:

  1. 消除两个对象交互时不必要的对象(特别是深度)拷贝,节省运算存储资源,提高效率
  2. 能够更简洁明确地定义泛型函数

C/C++中所有的表达式和变量要么是左值,要么是右值。通俗的左值的定义就是非临时对象,那些可以在多条语句中使用的对象,左值可以出现在 = 操作符的左边,右值则不可以:

C++
1
2
3
4
5
6
// i是左值,0是右值(临时对象)
// i可以被引用,0则不可以
int i = 0;
 
// 左侧的整体是左值,但是 0 不能独立出现在左侧
((i>0) ? i : j) = 1;

在C++ 11 之前右值仅仅能够被常量引用:

C++
1
const int &a = 1;
使用右值引用

在函数声明中,使用 &&表示引用右值:

C++
1
2
3
4
5
6
7
8
9
void process( int &i ) {}
// 重载版本,如果传入右值
void process( int &&i ) {}
 
int main() {
    int i = 0;
    process( i );
    process( 1 );
}

当传入临时对象1时,自动调用重载版本,临时对象不会被拷贝,而是被”移动“,减少了内存拷贝的开销。

std::move

此函数支持将左值引用转变为右值引用(赋予其临时对象语义):

C++
1
2
3
#include <algorithm>
int i = 0;
process( std::move(i) );

使用此函数可以避免不必要的内存拷贝:

C++
1
2
3
4
5
6
7
8
9
10
11
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的关键之处在于,它定义了移动语义 —— 声明一个对象放弃其持有的资源(内存),无条件的转换为右值引用。上面基本类型的移动语义不明显,可以考虑下面的例子:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
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的这样的构造器:

  1. 第一个参数类型为T&&, const T&&, volatile T&&, 或者 const volatile T&& 
  2. 没有后续参数,或者后续参数均具有默认值

以下情况下移动构造器会被调用:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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定义移动构造器,并且:

  1. 没有自定义拷贝构造器
  2. 没有重载拷贝赋值操作符
  3. 没有重载移动赋值操作符
  4. 没有自定义析构器

则编译器会自动声明一个隐含的移动构造器,其签名为 T::T(T&&)。

即使不符合上述条件,你也可以强制要求编译器生成某个移动构造器的默认实现(其逻辑和隐含移动构造器相同),使用语法:

C++
1
T ( T && ) = default;

如果类型T符合下面任何一种情况:

  1. T的某个非静态成员不能被移动 —— 例如定义了不可见、删除的(deleted)、二义性的移动构造器
  2. T的直接或者virtual基类不能被移动
  3. T的直接或者virtual基类具有删除的(deleted)、不可见的析构器

则T的默认/隐含移动构造器实现是删除的(deleted):

C++
1
T ( T && ) = delete;    
精确转发 

在某些场景下,我们需要将一组参数从一个函数原封不动的传递给另一个函数。所谓原封不动,不仅仅是指其值不变,还要求参数的左/右值、常量/非常量属性不变。

考虑下面的函数模板:

C++
1
2
3
4
5
6
template <typename T> void forward_value(const T& val) {
    process_value(val);
}
template <typename T> void forward_value(T& val) {
    process_value(val);
}

为了能够转发常量/非常量版本的参数,在以前我们不得不定义重载版本,这种工作很无聊。现在,我们只需要使用右值引用:

C++
1
2
3
template <typename T> void forward_value(T&& val) {
    process_value(val);
}

就可以了。不管实参是左值、右值,常量、非常量,都可以精确的转发给process_value。

std::forward可以用来将,模板函数参数转换为调用者指定的值类别(左/右):

C++
1
2
3
4
5
6
7
8
9
10
// 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之前,模板参数的数量是固定的,现在可以使用新语法来定义可变参数长度的模板:

C++
1
2
3
4
5
6
7
8
9
// 可变参数模板
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;
...标记

此标记有两个用途:

  1. 放在形参名字左侧,声明一个参数包(parameter pack)。使用这个参数包,可以绑定0个或多个模板实参给这个参数包
  2. 省略号出现在包含参数包的表达式的右侧,把此参数包解开为一组实参。省略号左侧的表达式整体使用解开后的每个实参分别求值,求值结果用逗号分隔

示例:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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 );
}
<functional>

C++ 11引入的函数对象标准库,包含了各种内建函数对象和相关的操作函数。

可调用类型

std::function、std::bind、std::result_of、std::thread::thread、std::call_once、std::async、std::packaged_task、std::reference_wrapper等都可以向函数那样被调用,即支持()操作符。

std::function

这是一个通用的函数包装器,可以容纳任何可调用类型 —— 例如函数、函数指针、Lambda、bind表达式、成员函数、其它函数对象。该模板类的声明如下:

C++
1
template< class R, class... Args > class function<R(Args...)>

用法示例:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}
std::bind

为函数绑定一部分参数,产生一个新的偏函数(Partial Function)。 新函数的参数使用std::placeholders中的成员来占位:

C++
1
2
3
4
5
6
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生命周期结束,则资源自动销毁。可以利用右值引用来在不同指针之间转移对象:
C++
1
2
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可以定义任意元组:

C++
1
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)

常用代码片段
语言基础
返回引用类型的技巧
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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();
    }
};
STL
迭代器erase函数用法
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
//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;
    }
}
输入输出流
Shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 文件输入流
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 {
    // 还可以读取
  }
}
常见问题
语言基础
cannot convert parameter * from 'const char [*]' to 'LPCWSTR'

可能是由于使用Unicode引起的,把字符串直接量使用_T()包含即可。

undefined reference to ...

可能原因:

  1. 声明但没有某个地方定义类的静态成员(static class member)
  2. 没有链接到适当的库
invalid conversion from ‘void (*)()’ to ‘VoidFuncPtr {aka void*}’

普通函数指针、类成员函数指针不是一类东西,不能相互转换:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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表达式也不是普通函数(自由函数),不能赋值给自由函数指针。

普通函数指针只能指向一个全局的、单独的函数,其关键是目标函数不会有关联的状态信息。没有关联状态的类静态函数,则可以安全的赋值给自由函数指针:

C++
1
2
3
4
5
6
7
8
9
10
11
class Duck {
public:
    static void quack() {}
};
typedef void (*VoidFuncPtr)();
int main() {
    Duck duck;
    // OK
    VoidFuncPtr fp = &Duck::quack;
    return 0;
}
Cygwin / MinGW
Cygwin程序报错:Exception: STATUS_ACCESS_VIOLATION at eip=004013A6 SIGSEGV:Segmentation fault

所谓的段错误 就是指访问的内存超出了系统所给这个程序的内存空间。段错误应该就是访问了不可访问的内存,这个内存区要么是不存在的,要么是受到系统保护的。 指针没有初始化可能导致该错误。

BOOST
报错信息 原因分析
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("\\/" ) ); 其中任意一个字符用于分隔
← Linux命令知识集锦
ExtJS知识集锦 →

Leave a Reply Cancel reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code class="" title="" data-url=""> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre class="" title="" data-url=""> <span class="" title="" data-url="">

Related Posts

  • Istio Mixer与Envoy的交互机制解读
  • 基于Broadway的HTML5视频监控
  • Eclipse 4.3.2开发环境搭建
  • HTML5视频监控技术预研
  • 基于MinGW的海康视频监控开发

Recent Posts

  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
  • A Comprehensive Study of Kotlin for Java Developers
  • 背诵营笔记
  • 利用LangChain和语言模型交互
  • 享学营笔记
ABOUT ME

汪震 | Alex Wong

江苏淮安人,现居北京。目前供职于腾讯云,专注容器方向。

GitHub:gmemcc

Git:git.gmem.cc

Email:gmemjunk@gmem.cc@me.com

ABOUT GMEM

绿色记忆是我的个人网站,域名gmem.cc中G是Green的简写,MEM是Memory的简写,CC则是我的小天使彩彩名字的简写。

我在这里记录自己的工作与生活,同时和大家分享一些编程方面的知识。

GMEM HISTORY
v2.00:微风
v1.03:单车旅行
v1.02:夏日版
v1.01:未完成
v0.10:彩虹天堂
v0.01:阳光海岸
MIRROR INFO
Meta
  • Log in
  • Entries RSS
  • Comments RSS
  • WordPress.org
Recent Posts
  • Investigating and Solving the Issue of Failed Certificate Request with ZeroSSL and Cert-Manager
    In this blog post, I will walk ...
  • A Comprehensive Study of Kotlin for Java Developers
    Introduction Purpose of the Study Understanding the Mo ...
  • 背诵营笔记
    Day 1 Find Your Greatness 原文 Greatness. It’s just ...
  • 利用LangChain和语言模型交互
    LangChain是什么 从名字上可以看出来,LangChain可以用来构建自然语言处理能力的链条。它是一个库 ...
  • 享学营笔记
    Unit 1 At home Lesson 1 In the ...
  • K8S集群跨云迁移
    要将K8S集群从一个云服务商迁移到另外一个,需要解决以下问题: 各种K8S资源的迁移 工作负载所挂载的数 ...
  • Terraform快速参考
    简介 Terraform用于实现基础设施即代码(infrastructure as code)—— 通过代码( ...
  • 草缸2021
    经过四个多月的努力,我的小小荷兰景到达极致了状态。

  • 编写Kubernetes风格的APIServer
    背景 前段时间接到一个需求做一个工具,工具将在K8S中运行。需求很适合用控制器模式实现,很自然的就基于kube ...
  • 记录一次KeyDB缓慢的定位过程
    环境说明 运行环境 这个问题出现在一套搭建在虚拟机上的Kubernetes 1.18集群上。集群有三个节点: ...
  • eBPF学习笔记
    简介 BPF,即Berkeley Packet Filter,是一个古老的网络封包过滤机制。它允许从用户空间注 ...
  • IPVS模式下ClusterIP泄露宿主机端口的问题
    问题 在一个启用了IPVS模式kube-proxy的K8S集群中,运行着一个Docker Registry服务 ...
  • 念爷爷
      今天是爷爷的头七,十二月七日、阴历十月廿三中午,老人家与世长辞。   九月初,回家看望刚动完手术的爸爸,发

  • 6 杨梅坑

  • liuhuashan
    深圳人才公园的网红景点 —— 流花山

  • 1 2020年10月拈花湾

  • 内核缺陷触发的NodePort服务63秒延迟问题
    现象 我们有一个新创建的TKE 1.3.0集群,使用基于Galaxy + Flannel(VXLAN模式)的容 ...
  • Galaxy学习笔记
    简介 Galaxy是TKEStack的一个网络组件,支持为TKE集群提供Overlay/Underlay容器网 ...
TOPLINKS
  • Zitahli's blue 91 people like this
  • 梦中的婚礼 64 people like this
  • 汪静好 61 people like this
  • 那年我一岁 36 people like this
  • 为了爱 28 people like this
  • 小绿彩 26 people like this
  • 彩虹姐姐的笑脸 24 people like this
  • 杨梅坑 6 people like this
  • 亚龙湾之旅 1 people like this
  • 汪昌博 people like this
  • 2013年11月香山 10 people like this
  • 2013年7月秦皇岛 6 people like this
  • 2013年6月蓟县盘山 5 people like this
  • 2013年2月梅花山 2 people like this
  • 2013年淮阴自贡迎春灯会 3 people like this
  • 2012年镇江金山游 1 people like this
  • 2012年徽杭古道 9 people like this
  • 2011年清明节后扬州行 1 people like this
  • 2008年十一云龙公园 5 people like this
  • 2008年之秋忆 7 people like this
  • 老照片 13 people like this
  • 火一样的六月 16 people like this
  • 发黄的相片 3 people like this
  • Cesium学习笔记 90 people like this
  • IntelliJ IDEA知识集锦 59 people like this
  • 基于Kurento搭建WebRTC服务器 38 people like this
  • Bazel学习笔记 37 people like this
  • PhoneGap学习笔记 32 people like this
  • NaCl学习笔记 32 people like this
  • 使用Oracle Java Mission Control监控JVM运行状态 29 people like this
  • Ceph学习笔记 27 people like this
  • 基于Calico的CNI 27 people like this
Tag Cloud
ActiveMQ AspectJ CDT Ceph Chrome CNI Command Cordova Coroutine CXF Cygwin DNS Docker eBPF Eclipse ExtJS F7 FAQ Groovy Hibernate HTTP IntelliJ IO编程 IPVS JacksonJSON JMS JSON JVM K8S kernel LB libvirt Linux知识 Linux编程 LOG Maven MinGW Mock Monitoring Multimedia MVC MySQL netfs Netty Nginx NIO Node.js NoSQL Oracle PDT PHP Redis RPC Scheduler ServiceMesh SNMP Spring SSL svn Tomcat TSDB Ubuntu WebGL WebRTC WebService WebSocket wxWidgets XDebug XML XPath XRM ZooKeeper 亚龙湾 单元测试 学习笔记 实时处理 并发编程 彩姐 性能剖析 性能调优 文本处理 新特性 架构模式 系统编程 网络编程 视频监控 设计模式 远程调试 配置文件 齐塔莉
Recent Comments
  • qg on Istio中的透明代理问题
  • heao on 基于本地gRPC的Go插件系统
  • 黄豆豆 on Ginkgo学习笔记
  • cloud on OpenStack学习笔记
  • 5dragoncon on Cilium学习笔记
  • Archeb on 重温iptables
  • C/C++编程:WebSocketpp(Linux + Clion + boostAsio) – 源码巴士 on 基于C/C++的WebSocket库
  • jerbin on eBPF学习笔记
  • point on Istio中的透明代理问题
  • G on Istio中的透明代理问题
  • 绿色记忆:Go语言单元测试和仿冒 on Ginkgo学习笔记
  • point on Istio中的透明代理问题
  • 【Maven】maven插件开发实战 – IT汇 on Maven插件开发
  • chenlx on eBPF学习笔记
  • Alex on eBPF学习笔记
  • CFC4N on eBPF学习笔记
  • 李运田 on 念爷爷
  • yongman on 记录一次KeyDB缓慢的定位过程
  • Alex on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • will on Istio中的透明代理问题
  • haolipeng on 基于本地gRPC的Go插件系统
  • 吴杰 on 基于C/C++的WebSocket库
©2005-2025 Gmem.cc | Powered by WordPress | 京ICP备18007345号-2