1.定位new
我们使用类来实例化对象,开辟空间的时候会自动去调用它的构造函数。但在那篇博客我就特意强调过,使用a.A()的方式是错误的,A()根本不会被识别为一个构造函数,而会被识别为A类型。因此我们要注意最好在实例化对象,开辟空间时就对它进行初始化。
当我们向内存池申请空间,使用malloc时无法直接调用构造函数,我们需要主动去调用构造函数,那应该怎么办呢?这就需要使用定位new了。
注意格式中new后面的括号内的是指针,之后跟初始化信息,单参数用小括号,多参数要用花括号。这种初始化也遵循隐式类型转换规则,和前面相似。
定位new的使用场景很少,如果我们想将一个已使用对象初始化,我们就可以使用这种办法,同时,它在内存池(池化技术)中使用比较多,池化技术即一次向堆申请一大块空间(减少操作系统负担),每次需要开辟空间时就会去找自己的内存池,用完将整块空间还回操作系统。
2.函数模板
C语言中,当我们需要使用实现方式相同,但数据类型不同的函数时,我们只有多依靠复制粘贴实现多个极为相似的函数,这样会使代码变得冗长,而且使得效率低下,因此C++中引入了模板的概念,具体又分为函数模板和类模板。
(1)函数前声明template<typename T1, typename T2...>(也可写作template<class T1, class T2...>),标志着这个函数针对广泛类型编程,也叫泛型编程
注意:template<typename T1, typename T2...>是函数模板的一部分,我们在使用的时候必须加上它且不能和函数主体分离
(2)函数模板看起来像是一个函数,但其实当我们用多种类型去调用这个函数模板时,底层编译器将这个模板实例化成了多个函数,分别去匹配。
虽然调试的时候看起来是一份函数,但在反汇编看得出来其实是多个函数构成函数重载
因此我们可以总结出模板的原理:编译器根据传参的类型实例化模板生成函数或类,本质上是多个函数或类,在C语言中我们需要自己写,现在是编译器帮我们实现了(半自动化),可以提高代码效率
(3)typename与class等价,两者一般可以互换
(4)template推演问题
当函数参数类型不一致时,函数模板推演就会产生歧义,这个时候我们需要显式实例化(可能会出现隐式类型转化),或者使用多个typename来处理歧义。对于返回类型,如果会产生多种返回情况,可通过auto类型来规避。
(4)函数匹配原则
普通函数、函数模板如果都能匹配我们的调用时,应该怎样规定优先级呢?
a.如果参数能直接匹配普通函数(有现成的函数),那就直接调用该函数而不会去调用函数模板。
如果匹配不上,会去匹配函数模板
b.要注意匹配普通函数时是看参数合不合适,编译器不会去看返回值合不合适。
c.要注意匹配普通函数是指完全匹配,并不会去考虑隐式类型转换,如果需要通过隐式类型转换才能匹配普通函数的话,那就不符合规则(无const -> 有const也算作一种隐式类型转换),不会优先去匹配普通函数。
d.如果普通函数的参数不能匹配,就会去匹配更符合的函数模板,首先会去看参数部分,参数部分匹配会在完美匹配的基础上尽可能简洁,会用尽可能少的typename匹配
通过这里也可以看出,只要参数匹配更简洁,是不会去管返回值类型的。
关于完美匹配,一定要注意const,包括普通函数,无const -> 有const也算作一种隐式类型转换,这都不算作完美匹配
在这里我们就可以看出const T不能完美匹配无const类型,所以这个时候会去找有const修饰的typename,就算两个有const修饰的变量类型一致
总结:先匹配普通函数,如果不能完美匹配,找函数模板,函数模板中尽可能完美匹配且简洁,实在不行就使用能隐式类型转换的。
3.类模板
(1)类模板和函数模板相似,但唯一需要注意的是类模板只能显式实例化类型
在这里,A a(2)的2是不能推演出类模板的类型的。类模板只能显式实例化类型,这是规定。
(2)类中的函数声明定义分离的时候,要注意类域的名称为:类的名称<实例化typename>,在写函数时也要在最前面生命template
不要声明定义分离到两个文件,会出现链接错误,在同一个文件可以分离
类模板的其他特性和函数模板一致,这里就不再赘述了。