C++面试用题(一)

堆和栈的区别:

(注意不是数据结构里的堆和栈)
栈(stack)由编译器自动分配释放,存放函数的参数值,局部变量的值。
堆(heap)由程序员分配释放,如果不释放,最后程序结束时会被操作系统回收。
全局区(静态区static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在同一区域,未初始化的全局变量和未初始化的静态变量在相邻的另一区域。

int a;//系统在栈中为b开辟空间

int* a=new int;//程序员在堆中申请空间存放a
a=(int*)malloc(sizeof(int));//

new和malloc的区别:

new和malloc都是程序员在堆中分配空间的手段
malloc:单纯的开辟出一块空间,然后返回一个指向分配内存空间的指针(如果指针类型不强转的话会返回一个void类型的指针),开辟空间的大小就是size参数指定的字节数。
free:跟malloc对应,释放申请的内存。
new:开辟一块内存,并调用该类的构造函数,申请空间的大小会自动计算,最后返回指定类型的指针。
delete:与new相对应,调用析构函数并回收空间。

多态

转自:https://www.cnblogs.com/alinh/p/9636352.html
在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时会根据对象的实际类型来调用相应的函数——即如果对象是基类,就调用基类里的函数,如果是派生类,就调用派生类的函数。

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
using namespace std;
 
class Father
{
public:
    void Face()
    {
        cout << "Father's face" << endl;
    }
 
    void Say()
    {
        cout << "Father say hello" << endl;
    }
};
 
 
class Son:public Father
{
public:    
    void Say()
    {
        cout << "Son say hello" << endl;
    }
};
 
void main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();
}

最后输出是:I am father

从编译的角度来看,C++编译器在编译的时候,要确定每个对象调用的函数的地址,这是早期绑定,当我们将Son类的对象son的地址赋给pfather时,C++编译器进行了类型转换,此时C++编译器认为变量pfather保存的就是father对象的地址。
在这里插入图片描述

Son类的对象内存模型如上图
在构造Son类对象的时候,首先要调用Father的类去构造Father类的对象,然后才调用Son类的构造函数生成Son类对象。当我们将Son类对象转换为Father类型的时候,该对象就被认为是原对象对整个内存模型的上半部分。当我们利用类型转换后的指针去调用它的方法时,就会调用它所在内存中的方法,所以会输出Father类中的方法。

如果希望输出的结果是son类的say方法,就要用到虚函数了。之前输出father类的对象,是因为在编译器编译的时候,就已经确定了对象调用的函数的地址,要解决这个问题,就要使用晚期绑定——即在运行时再确定对象的类型以及正确的调用函数。
而要让编译器采用晚绑定的方法,就要在基类中声明函数的时候加上关键字virtual。

在刚刚的代码中略作改动

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
using namespace std;
 
class Father
{
public:
    void Face()
    {
        cout << "Father's face" << endl;
    }
 
    virtual void Say()
    {
        cout << "Father say hello" << endl;
    }
};
 
 
class Son:public Father
{
public:    
    void Say()
    {
        cout << "Son say hello" << endl;
    }
};
 
void main()
{
    Son son;
    Father *pFather=&son; // 隐式类型转换
    pFather->Say();
}

最后就会输出:i am son

看看将say()声明为virtual的时候,背后发生了什么。
在编译的时候,发现Father类有虚函数,编译器会为每个包含虚函数的类创建一个虚表,该表示一个一位数组,在这个数组中存放了每一个虚函数的地址。

在这里插入图片描述
那么如何定位虚表呢,编译器另外给每个对象提供了一个虚表指针,程序运行的时候,根据对象的方法去初始化虚表指针,从而让虚表指针正确的指向了所属类的虚表,从而在调用虚函数的时候,能够找到正确的函数。对于第二段代码,由于pfather实际指向的对象类型是Son,因此虚指针指向Son类的虚表,当调用pFather->Say()时,根据虚表中的函数地址,找到的就是Son类的Say()函数。
  正是由于每个对象调用的虚函数都是通过虚表指针来索引的,也就决定了虚表指针的正确初始化是非常重要的,换句话说,在虚表指针没有正确初始化之前,我们不能够去调用虚函数,那么虚表指针是在什么时候,或者什么地方初始化呢?
  答案是在构造函数中进行虚表的创建和虚表指针的初始化,在构造子类对象时,要先调用父类的构造函数,此时编译器只“看到了”父类,并不知道后面是否还有继承者,它初始化父类对象的虚表指针,该虚表指针指向父类的虚表,当执行子类的构造函数时,子类对象的虚表指针被初始化,指向自身的虚表。
  **因此,构造函数可以是虚函数嘛?**不可以,因为虚表是在构造函数里面创建的。虚表指针在没有正确的初始化前,是不能调用虚函数的。所以想要用构造函数,就得先建立虚表,想先建立虚表,就得先调用构造函数…

虚表补充

加入定义了这样一个类

class Base1
{
public:
    int base1_1;
    int base1_2;
};

对象大小以及偏移是:
sizeof(Base1) 8
offsetof(Base1, base1_1) 0
offsetof(Base1, base1_2) 4
可以看到, 成员变量是按照定义的顺序来保存的, 最先声明的在最上边, 然后依次保存!
类对象的大小就是所有成员变量大小之和.


class Base1
{
public:
    int base1_1;
    int base1_2;
 
    void foo(){}
}

如果一个函数不是虚函数,那么他就不可能会发生动态绑定,也就不会对对象的布局造成任何影响.
当调用一个非虚函数时, 那么调用的一定就是当前指针类型拥有的那个成员函数. 这种调用机制在编译时期就确定下来了.

class Base1
{
public:
    int base1_1;
    int base1_2;
 
    virtual void base1_fun1() {}
};

sizeof(Base1) 12
offsetof(Base1, base1_1) 4
offsetof(Base1, base1_2) 8
在这里插入图片描述
会发现类的大小多了4个字节,并且变量往后偏移了4个字节。
如果多加一个虚函数,会发现,类对象大小仍然是12字节。
在这里插入图片描述
通过上面两张图表, 我们可以得到如下结论:

1,更加肯定前面我们所描述的: __vfptr只是一个指针, 她指向一个函数指针数组(即: 虚函数表)
2,增加一个虚函数, 只是简单地向该类对应的虚函数表中增加一项而已, 并不会影响到类对象的大小以及布局情况

前面已经提到过: __vfptr只是一个指针, 她指向一个数组, 并且: 这个数组没有包含到类定义内部, 那么她们之间是怎样一个关系呢?
不妨, 我们再定义一个类的变量b2, 现在再来看看__vfptr的指向:
在这里插入图片描述
发现:b1和b2这两个类变量的虚函数指针,指向同一个地址。

由此我们可以总结出:

同一个类的不同实例共用同一份虚函数表, 她们都通过一个所谓的虚函数表指针__vfptr(定义为void**类型)指向该虚函数表.

是时候该展示一下类对象的内存布局情况了:
在这里插入图片描述
她是编译器在编译时期为我们创建好的, 只存在一份
定义类对象时, 编译器自动将类对象的__vfptr指向这个虚函数表

后续可以看
https://blog.csdn.net/li1914309758/article/details/79916414

纯虚函数

虚函数是在声明父类对象等于子类对象后,想要正确的调用子类方法时采取的手段。如果在子类中有对应的虚函数,那么在虚表中,同名的子类虚函数就会覆盖父类函数的位置,其他虚表位置不变。

当子类没有同名函数时,指针会调用父类的函数。但是当父类中,将该函数声明为纯虚函数时,如果在调用时,子类内部没有对应的函数,那么将无法编译通过。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值