今年的要学习一门语言,加上目前从事的工作需要c++,恰好有实践的机会,才开始了c++
之旅
函数匹配
先来看一段代码。1
2
3
4
5
6
7
8
9template<class T>
T add(T a, T b){
return a+b;
}
double add(double a, double b){
return a+b;
}
由于函数重载,及函数模版的存在,c++
需要一个良好的策略来决定调用哪一个函数,这个过程被称为重载解析</code,具体过程如下:
- 创建候选函数列表,其中包含与被调用函数的名称相同的函数和模版函数。
- 使用候选函数列表创建可行函数列表,其中参数数目都是正确的,这其中会存在一个隐士的类型转换。
- 确定最佳可执行函数。
判断最佳可执行函数的优先级如下:
- 完全匹配,但常规函数优先于模板函数
- 提升转换:
char
short
转换为int
,float
转换为double
。 - 标准转换:
long
转换为double
。 - 用户定义的转换,如类中定义的转换。
左右值,与引用。
1 | class TestA { |
c++的构造函数除默认的构造函数之外,还有复制构造函数
移动构造函数
。
而赋值行为同样也有两类:复制赋值函数
移动复制函数
。
而这些函数都是从默认的构造函数重载得来,也就是说,这些函数的调用是遵从重载解析
的匹配规则的,需要主要的一点就是声明的右值引用类型
也是左值,因此下面代码是调用复制赋值函数
1
2TestA &&rd3 = std::move(t1);//定义引用类型,不需要构造
rd2 = rd3;//复制赋值
rd3
虽然是右值引用变量,但也是左值。
虚函数表
c++
中的函数地址绑定分为两种方式,一种为静态绑定
static binding:在编译器直接确定函数的实现地址。
另一种为动态绑定
dynamic binding:运行时确定函数的实现地址。
其中动态绑定
是由虚函数表来实现。使用关键字virtual
声明为虚函数,主要来实现多态。
在不声明为test
虚函数时,编译器将根据引用的类型
或
指针的类型
来确定应该执行的方法。1
2
3
4
5
6
7
8
9
10
11TestB b = TestB();
b.test(); //TestB test
TestA &a = b;
a.test(); //TestA test
TestA *c = &b
c->test(); //TestA test
TestA d = TestB()
d.test(); //???
如果test
声明为虚函数,函数的执行将根据指针或引用的对象的类型来决定。1
2
3
4
5
6
7
8
9
10
11TestB b = TestB();
b.test(); //TestB test
TestA &a = b;
a.test(); //TestB test
TestA *c = &b
c->test(); //TestB test
TestA d = TestB()
d.test(); //???
这里需要可以思考下这两个例子中,最后一段代码的调用结果,结果上面说的到构造函数讲解。1
2TestA d = TestB()
d.test(); //???
下面来分解下:
TestB()
将调用TestB
的构造函数,进而调用父类TestA
的构造函数。并返回TestB
的实例。- 此时 “=”左边的类型为 TestA,右边为
TestB
的实例,因此将发生一次“移动构造函数”。TestB()
为右值。 - 移动构造函数结束,
d
为TestA()
的实例,因此将调用TestA()
的test()
的方法。
虚函数表是指在每个包含虚函数的类中都存在着一个函数地址的数组。当我们用父类的指针来操作一个子类的时候,这张虚函数表指明了实际所应该调用的函数。
智能指针
unique_ptr
遵循着独占语义。在任何时间点,资源只能唯一地被一个unique_ptr
占有。同时不提供复制语义(复制赋值和复制构造都不可以),只支持移动语义。1
2
3
4
5
6
7
8
9void exampleMethod3(){
std::unique_ptr<TestA> uniqueA(new TestA());
{
std::unique_ptr<TestA> uniqueB = uniqueA;//复制构造 compile error :Call to implicitly-deleted copy constructor of 'std::unique_ptr<TestA>'
std::unique_ptr<TestA> uniqueC = std::move(uniqueA);//移动构造
}
//出了{} TestA 被释放
std::cout << "exampleMethod3 end!\n";
}
上面的实例首先定义了一个 unique_ptr uniqueA
,后通过移动语言将控制权转交给unique_ptr uniqueC
,由于uniqueC
的作用域为里面的“{}”因此会在出了作用域时释放。
shared_ptr
shared_ptr
有一个叫做共享所有权(sharedownership)的概念。其目标非常简单:多个指针可以同时指向一个对象,当最后一个shared_ptr离开作用域时,内存才会自动释放。也就是引用计数
1 | //std::shared_ptr<TestA> shareB; |
上面的实例同时有两个对TestA
的引用,当“{}”的作用域结束,会进行引用计数-1,等待shareB
的作用域结束进行释放。
如果把shareB
的作用域提高到全局,则TestA
的内存则会跟随程序的生命周期。