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);
}
};