C++程序设计兼对象模型II(2)

70 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

class template类模板

function template函数模板

这里说class可以换成typename,之后看下

这里函数模板不需要提前指明类型,有实参推导。模板块的编译,编译后也只是半成品,不能保证后头一定成功,后面使用的时候还会再编译一次。模板会写出.cpp或者.h,本身可以编译通过,但是真正使用时和应用程序本身还会再编译一次,那时候过不过就不确定了。

member template成员模板

比如在标准库,这种成员模板往往出现在模板的构造函数上

可以,反之不可以。

为了智能指针可以实现up-cast,所以构造函数要是member template

specialization,模板特化

特化就是:作为设计者,你可能某些独特的类型要做特殊设计

例如,如果设计了算法可以求两点之间连线的每个点的坐标,而有人告诉你如果数都是整数,可以有个非常快的算法。

partial specialization模板偏特化

个数的偏

参数不能跳着,例如对1,3,5绑定,这样不行

范围的偏

例如原本范围是所有类型,现在是指针类型,而指针的指向可以是任意类型,注意上下两个T不是一个,可以用不同符号

template template parameter模板模板参数

不太明白这个模板模板参数怎么写的,这里Container前的class不能换为typename,为什么

下面之所以错,是因为容器其实有第二模板参数,有的还有三,平常不写是有默认值,但是这里语法过不了,要通过需要加上中间那两句话,它是2.0新加的语法,由于不能短时间说完,这里不讲。

这里list已经指定了,list不再是模板了,所以不是模板模板参数

variadic template(since C++11)

这个新语法允许写任意个数的模板参数

auto(since C++11)

要会写原本ite的类型,auto只是为了方便,不过有一种情况很难写他的类型,也就是2.0里的lambda

ranged-base for(since C++11)

这里{}也是一个容器,2.0新特性

以前遍历容器用迭代器或者for each

这里第一个循环是传值,所以不能修改容器里的元素,而想要修改就要用第二种,传引用

reference

reference常见用途

类函数是否加const构成重载

Composition(复合)关系下的构造与析构

Inheritance(继承)关系下的构造与析构

Inheritance+Composition关系下的构造和析构

对象模型(Object Model):

关于vptr和vtbl

有虚函数的对象会多一个指针(虚指针),不论是几个虚函数都是一个指针,所以对象的大小多4个字节,那么这时之前说过的子类对象有父类的成分这句话同样没错,因为子类也有虚函数,他一定有虚函数,他继承了父类函数的调用权。

在C里面,如果调用函数,会使用call+地址的方式,解析后跳过去,执行完再回来,这叫做静态绑定。

如果调用虚函数,编译器知道不能做静态绑定,这是面向对象整个设计的关键点,做动态绑定。通过指针p找到vptr,找到vtbl(虚表),再从里面看看到底指向哪个函数。n是虚函数在虚表的位置,是编译时编译器看你放在第几个就是几

  1. 子类和父类返回值参数相同,函数名相同,有virtual关键字,则由对象的类型决定调用哪个函数。
  2. 子类和父类只要函数名相同,没有virtual关键字,则子类的对象没有办法调用到父类的同名函数,父类的同名函数被隐藏了,也可以强制调用父类的同名函数class::funtion_name。
  3. 子类和父类参数不同,函数名相同,有virtual关键字,则不存在多态性,子类的对象没有办法调用到父类的同名函数,父类的同名函数被隐藏了,也可以强制调用父类的同名函数class::funtion_name。
  4. 子类和父类返回值不同,参数相同,函数名相同,有virtual关键字,则编译出错error C2555编译器不允许函数名参数相同返回值不同的函数重载。
#include<iostream>
#include<list>

using namespace std;
class A
{
public:
	void fun(){
		std :: cout << "A"<<std :: endl;
	}

	A(){};
	int c;
};
class B:public A
{
public:
	void fun(){
		std :: cout << "B"<<std :: endl;
	}

	B(){};
	int c;
};
int main()
{
	std::list<A*> l;
	A a;
	a.c = 4;
	B b;
	b.c = 6;
	l.push_back(&a);
	l.push_back(&b);
	std::list<A*>::iterator i;
	for( i = l.begin(); i != l.end();i ++)
	{
		(*i)->fun();//A   A,这里up cast
		// cout << (*i)->c <<endl;//4  4253600  ??
	}
	
	return 0;
}

只有虚函数才能实现多态,即如果是A调用A的fun,B调用B的fun(圆形调用圆形的draw,正方形调用他的draw)。上面虚函数调用的图就是虚机制,也就是动态绑定的形式。函数的这种用法就是多态。

关于this

关于Dynamic Binding

要满足动态绑定一定有三个条件:

  1. 指针
  2. 虚函数
  3. 向上转型

谈谈const

这里const只能放在成员函数后,全局函数后是不行的

我们使用的字符串是使用reference counting技巧做出来的,也就是说可以被共享,由于需要共享,所以如果改数据的话就需要COW。

在这里不能判断const是否是被const变量调用的,不知道是否该实现COW,所以又有了一个规则,就是上方灰色的。

关于new,delete

使用者直接用的new,delete都是表达式,表达式的行为是不能变的,不能重载的,也就是new和delete分解为三个和两个动作这个是不能变的,但是分解后的那个函数可以重载。

之前没说过class可以重载这些函数,它们用于内存管理,内存池的设计,这不是这里该讲的,这里讲的是重载的形式。

重载::operator new, ::operator delete,::operator new[],::operator delete[]

::表示全局的

new一定要有个参数大小,这些函数不是我们调用,我们调用的是表达式,表达式分成三个步,第一步就是这个,有重载就运行这个,没有就运行全局默认的,大小是编译器传进来的。delete要传指针。

重载member operator new/delete

接管后我们通常做内存池

重载member operator new[]/delete[]

示例,接口

示例

从这里可以看到如果new一个,他的size大小是12,但是new一个数组的大小不是元素个数*元素大小,还多了4,字节,他记录了有几个元素,这样才能知道调用几次构造和析构函数。

两个箭头表示的是构造和析构时this指针的移动方向

重载new(),delete()

有的人认为只有()里有指针的才叫placement new,因为指针定位在那里,所以名词才有意思

示例

basic_string使用new(extra)扩充申请量