0%

C++面试

9.13面试拾遗

9.13面试拾遗

  • Delete与Delete[]

delete 释放new分配的单个对象指针指向的内存;

delete[] 释放new分配的对象数组指针指向的内存。

 

如果是简单类型,其实都可以!

int *a = new int[10];
delete a;
delete[] a; //都不会报错

释放效果是相同的,分配简单类型时内存大小已经确定,系统已经记忆好了。

它直接通过指针可以获取实际分配的内存空间,信息存在结构体_CrtMemBlockHeader中

 

如果时复杂类型就会出现差异。

class A
{
    private:
    char *m_cBuffer;
    int m_nLen;
public:
    A(){ m_cBuffer = new char[m_nLen]; }
    ~A() { delete [] m_cBuffer; }
};
A *a = new A[10];
delete a; //仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放 从而造成内存泄漏
delete[] a; //调用使用类对象的析构函数释放用户自己分配内存空间并且 释放了a指针指向的全部内存空间

delete ptr代表用来释放ptr指向的内存。

delete[] rg用来释放rg指向的内存,还逐一调用数组中每个对象的destructor。

简单类型没有,析构函数,所以加不加中括号是等同的。

如果对象中含有系统资源,那么不调用析构函数容易造成文件线程端口等的死锁

 

  • 直接调用基类函数

继承关系中,如果只拥有子类的引用,要调用基类函数,可以在子类函数中写下:Base::func()

 

  • 数组初始化

无敌了真能被这个给问倒

int a[10]={0};
char str[10]="\0";    //等价于char str[10]={0};


int a[10]={0,1,2,3,4,5,6,7,8,9};
char str[10]="Hello";

int a[]={0,1,2,3,4,5,6,7,8,9};

int a[2][5]={{0,1,2,3,4},{5,6,7,8,9}};

int *pia = new int[10];

 

  • delete this

对象必须是new出来的才能这样自行删除。

这一句代码执行后该对象、该对象的任意成员变量都不能被访问,也就是说安全性不足。

保证delete this之后调用的函数不会调用到这个对象的虚函数以及成员变量。

因为detele之后内存不是立即返回给操作系统,而是会因为缓存保留一段时间,这个时候反而是可以读取到的,就有可能出现问题。

析构函数不能用delete this,delete this本身就是在调用析构函数,如此就堆栈溢出了。

强制需要this的情况:需要将对象作为整体引用时。

 

  • 虚基类

Class A;

Class B :public A;

Class C :public virtual A;

Class D :public B ,public C;

实现原理和虚函数一样,都是虚类表与虚类指针。

虚基类依旧存在继承类中,只占用存储空间;虚函数不占用存储空间。

虚基类表存储的是虚基类相对直接继承类的偏移;而虚函数表存储的是虚函数地址。

C++ 虚继承实现原理(虚基类表指针与虚基类表)-CSDN博客

 

  • 内存对齐
struct stu{
    char sex;			
    int length;			
    char name;		
};

我不知道为什么我老喜欢说这个sizeof后是9,纯天才

 

  • 拷贝构造函数
class A{
    A(const A &a){
        //临时对象具有常性,也就是穿过来的右值,所以用const
        //标准上说const可以接收const与非const的参数
        //如果不加&就会变成普通的复制构造函数
    }
}

 

  • Unity文件引用

由于我有写查找对象引用经历,被问到了,忘了

meta文件中含有guid,无论什么文件都有这个guid。

FileId指在文件内区分不同文件的标识符。

保存引用关系就是通过保存guid来实现的。

 

  • 链表节点删除

单给一个节点,如何单独删除它,在不知道前序节点的情况下。

获取后面节点的值,将其赋给当前节点,再直接删除后续节点。

 

  • 链表快排

和数组快排一样

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* solve(ListNode* p){
        if(!p||!p->next){
            return p;
        }

    ListNode* L = partition(p);
    ListNode* R = p->next;
    //快排的细节是操控节点的选取要默认往下一位。
    //要注意这里的p其实是已经经过partition后的,也就是右边节点的第一个。
    //不然会出现111这种无限排序的现象,在数组中因为我们习惯性默认往下排了一位,但链表需要特殊处理
    
    p->next = NULL;

    //返回出左边头节点和右边头节点,然后遍历左边链表到尾节点后链接到右边
    ListNode* resultL = solve(L);
    ListNode* resultR = solve(R);
    ListNode* P = resultL;
    while(P->next){
        P = P->next;
    }
    P->next = resultR;
    return resultL;
}

ListNode* partition(ListNode* h){
    //逆大天数据超时,这里可以优化成选择中间节点(快慢指针)
    ListNode* L = new ListNode(-1);
    ListNode* R = new ListNode(-1);
    ListNode* l = L;
    ListNode* r = R;
    ListNode* p = h;
    int val = h->val;
    while(h){
        p = h->next;
        h ->next = NULL;
        if(h->val<val){
            L->next = h;
            L = L->next;
        }
        //这里的h隐性放到了右边节点的第一位,间接影响了主函数的遍历
        else{
            R->next = h;
            R = R->next;
        }
        h = p;
    }

    L->next = r->next;
    R->next =NULL;
    return l->next;
}

ListNode* sortList(ListNode* head) {
    return solve(head);
}

};