构造函数的调用想法记录

构造函数分为:有参构造函数,无参构造函数
有参构造函数分为:拷贝构造函数,一般构造函数

我理解的是:类中的构造函数有点像是函数的重载,函数的参数类型不同,可以执行不同的行为。当参数没有时,执行默认构造函数,当有参数时,执行的有参构造函数,特殊的,当参数为类时,执行拷贝构造函数。但是使用有一定限制。
见调用原则。

拷贝构造函数的作用就是给新的类,一个初始化的操作,同一个类,不同的初始化,这就像是手机一样,我设置的不一样,开机显示的也不一样,但是类都是一样的。

拷贝构造函数的调用时机:
1.用对象的参数初始化另一对象
2.函数的参数为创建的类的实例化对象的时候
因为参数是实参的时候,函数调用实质是拷贝了参数的值, 故函数的形参也会拷贝原来的类,使其调用拷贝函数
3.函数的返回值类型为对象的时候
因为返回值被接收的时候,返回值不是原值返回,而是拷贝了一个新的值返回

构造函数的调用规则
1.创建一个类,c++默认了三个函数:构造函数,析构函数,和拷贝构造函数
2.若自己写了有参构造函数,编译器就不再提供默认构造,但是依然提供拷贝构造和析构函数
3.写了拷贝构造函数,编译器就不再提供普通构造函数函数,包括无参构造函数和有参构造函数。

注:写了有参构造函数,就不能调用无参构造函数

析构函数可以释放new出来的空间
默认构造:m_age=p.m_age
自己构造:m_age=new int(*p.m_age)

初始化列表也能赋初始值,在类里面赋初值,类似于构造函数,只是不用在构造函数内部赋值,采用了初始化列表的方式。

person(int a,int b,int c):m_A(a),m_B(b),m_c(c)
{
}

构造函数有参数,构造类对象的时候也要带参数。
静态成员变量赋值的时候,通过类进行直接访问:

int person::m_age=10

静态成员函数不让访问非静态成员变量
静态成员函数

类可以设定自己的好基友,友元函数,访问自己的私有函数。设置的时候只需要在类里面声明一下即可,在函数前面加上friend。

函数的返回值也可以用引用返回。表示函数的返回值,嗯,不能在额外的开辟空间复制进行返回,而是应该直接引用原来的变量进行返回。用引用其实就是规定下这个变量的值到底是采用原来存储空间内的变量,还是新开辟一片空间复制之后进行应用的情况。

继承方式
继承方式

对象模型:通过开发人员命令提示符查看
cl /d1 reportSingleClassLayout+类名
添加同名属性作用域
s.Base::m_A
添加同名函数作用域
s.Base::func()
如果子类中出现和父类同名的成员函数(或者静态成员函数),子类的同名成员会隐藏掉父类中所有同名成员函数(或静态成员函数)。
想要访问父类中被隐藏同名函数,所以需要加上作用域。

静态成员变量的特点:所有对象共享同一份数据,编译阶段分配内存,类内声明类外初始化。
子类可以有父类的静态成员变量和静态成员函数。他们的静态成员变量和静态成员函数的名称可以一样。可以供其他对象进行访问。

静态成员属性可以通过对象进行访问,也可以通过类名进行访问。
在这里插入图片描述
注意代码内两个冒号的意义。
通过类名方式进行访问父类作用域下的属性/成员。

多继承类:允许儿子认干爹
class 子类:继承方式 父类1,继承方式 父类2,继承方式 父类3
加作用域就代表着你必须要知道这个作用于是谁,在哪个作用域下,如果知道在哪个作用域下就代表着你对这些类的关系特别熟悉。

菱形继承。
虚继承:构建了指针,指向了父类数据,不额外开辟内存复制,其余的继承是需要额外开辟内存复制的。

int *a = new int[5];
class A {...}   //声明一个类 A
A *obj = new A();  //使用 new 创建对象
delete []a;
delete obj;

注意new是怎么创造对象的

虚函数的应用举例:

class A
{
public:
    virtual void foo()
    {
        cout<<"A::foo() is called"<<endl;
    }
};
class B:public A
{
public:
    void foo()
    {
        cout<<"B::foo() is called"<<endl;
    }
};
int main(void)
{
    A *a = new B();
    a->foo();   // 在这里,a虽然是指向A的指针,但是被调用的函数(foo)却是B的!
    return 0;
}

。。。。。。。。。。。。。。。。。。。。。。。。。
详细解释了虚函数的底层原理
虚函数的实现要求对象携带额外的信息,这些信息用于在运行时确定该对象应该调用哪一个虚函数。典型情况下,这一信息具有一种被称为 vptr(virtual table pointer,虚函数表指针)的指针的形式。vptr 指向一个被称为 vtbl(virtual table,虚函数表)的函数指针数组,每一个包含虚函数的类都关联到 vtbl。当一个对象调用了虚函数,实际的被调用函数通过下面的步骤确定:找到对象的 vptr 指向的 vtbl,然后在 vtbl 中寻找合适的函数指针。
虚拟函数的地址翻译取决于对象的内存地址,而不取决于数据类型(编译器对函数调用的合法性检查取决于数据类型)。如果类定义了虚函数,该类及其派生类就要生成一张虚拟函数表,即vtable。而在类的对象地址空间中存储一个该虚表的入口,占4个字节,这个入口地址是在构造对象时由编译器写入的。所以,由于对象的内存空间包含了虚表入口,编译器能够由这个入口找到恰当的虚函数,这个函数的地址不再由数据类型决定了。故对于一个父类的对象指针,调用虚拟函数,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数(取决于对象的内存地址)。
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就会为这个类创建一个虚函数表(VTABLE)保存该类所有虚函数的地址,其实这个VTABLE的作用就是保存自己类中所有虚函数的地址,可以把VTABLE形象地看成一个函数指针数组,这个数组的每个元素存放的就是虚函数的地址。在每个带有虚函数的类 中,编译器秘密地置入一指针,称为v p o i n t e r(缩写为V P T R),指向这个对象的V TA B L E。 当构造该派生类对象时,其成员VPTR被初始化指向该派生类的VTABLE。所以可以认为VTABLE是该类的所有对象共有的,在定义该类时被初始化;而VPTR则是每个类对象都有独立一份的,且在该类对象被构造时被初始化。
通过基类指针做虚函数调 用时(也就是做多态调用时),编译器静态地插入取得这个V P T R,并在V TA B L E表中查找函数地址的代码,这样就能调用正确的函数使晚捆绑发生。为每个类设置V TA B L E、初始化V P T R、为虚函数调用插入代码,所有这些都是自动发生的,所以我们不必担心这些。

class base
{
private:
	int a;
public:
	void bfun()
	{
	}
	virtual void vfun1()
	{
	}
	virtual void vfun2()
	{
	}
};
 
class derived : public base
{
private:
	int b;
public:
	void dfun()
	{
	}
	virtual void vfun1()
	{
	}
	virtual void vfun3()
	{
	}
};

两个类的VPTR指向的虚函数表(VTABLE)分别如下:
base类
——————
VPTR——> |&base::vfun1 |
——————
|&base::vfun2 |
——————

derived类
———————
VPTR——> |&derived::vfun1 |
———————
|&base::vfun2 |
———————
|&derived::vfun3 |
———————
每当创建一个包含有虚函数的类或从包含有虚函数的类派生一个类时,编译器就为这个类创建一个VTABLE,如上图所示。在这个表中,编译器放置了在这个类中或在它的基类中所有已声明为virtual的函数的地址。如果在这个派生类中没有对在基类中声明为virtual的函数进行重新定义,编译器就使用基类 的这个虚函数地址。(在derived的VTABLE中,vfun2的入口就是这种情况。)然后编译器在这个类中放置VPTR。当使用简单继承时,对于每个对象只有一个VPTR。VPTR必须被初始化为指向相应的VTABLE,这在构造函数中发生
一旦VPTR被初始化为指向相应的VTABLE,对象就"知道"它自己是什么类型。但只有当虚函数被调用时这种自我认知才有用。
没有虚函数类对象的大小正好是数据成员的大小,包含有一个或者多个虚函数的类对象编译器向里面插入了一个VPTR指针(void *),指向一个存放函数地址的表就是我们上面说的VTABLE,这些都是编译器为我们做的我们完全可以不关心这些。所以有虚函数的类对象的大小是数据成员的大小加上一个VPTR指针(void *)的大小。

总结一下VPTR 和 VTABLE 和类对象的关系:
每一个具有虚函数的类都有一个虚函数表VTABLE,里面按在类中声明的虚函数的顺序存放着虚函数的地址,这个虚函数表VTABLE是这个类的所有对象所共有的,也就是说无论用户声明了多少个类对象,但是这个VTABLE虚函数表只有一个。
在每个具有虚函数的类的对象里面都有一个VPTR虚函数指针,这个指针指向VTABLE的首地址,每个类的对象都有这么一种指针。

父类指针或引用指向子类对象,在编译器编译的时候,子类对象实质上是先指向的父类的指针,在程序运行的时候,腹内厄的指针指向子类对象,会将子类对象指向父类虚函数的指针替换掉。就实现了动态的链接。
静态多态的时候,同一函数(包括名字一样返回值一样以及参数一样的函数),会直接在编译的时候给予确定的地址。,当在主函数中调用的时候,会直接调用这个地址。
动态多态的函数,同一函数(包括名字一样返回值一样以及参数一样的函数),在直接编译的时候,不会在调用函数的地方。给予确定的地址了,但是他会为同一函数,分配两个内存空间,具体在调用的时候,指向哪一块内存中的函数,是由调用的时候所指向的子类对象的地址决定的。

函数名其实就是函数的地址在编译的时候,这个函数名称并不代表某个具体的地址,它是不一定的。他具体代表哪个地址是根据父类指针所指向的子类对象的地址决定的。

感觉多态可以实现功能的解偶合。
在这里插入图片描述

2、虚继承
这个是比较不好理解的,对于虚继承,若派生类有自己的虚函数,则它本身需要有一个虚指针,指向自己的虚表。另外,派生类虚继承父类时,首先要通过加入一个虚指针来指向父类,因此有可能会有两个虚指针。

。。。。。。。。。。。。。。。。。。。。。。。。。

虚函数与不用虚函数运行结果
虚函数底层实现机制

虚继承:

函数的重载与虚函数区别:虚函数:函数的返回值类型。函数名和参数列表,完全一致。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值