您的位置:首页 > 编程语言 > C语言/C++

面试1——C/C++相关知识点储备

2020-05-27 15:58 13 查看

1.c++虚函数原理

作用:C++中的虚函数的作用主要是实现了多态的机制。当基类中的成员函数定义了虚函数,其子类可以重新改写该函数。也即是允许派生类调用父类的同名函数而实现不同的功能,也叫动态联编。在主函数调用时,只需要定义一个基类指针就可以进行派生类的分别操作。
底层原理虚函数表+虚函数表指针。每一个类都会对应一个虚函数表,一个存放虚函数地址虚函数表,并创建虚函数指针(vptr)来指向表。

如果基类有3个虚函数,那么基类的虚表中就有三项(虚函数地址),同时虚表可以继承,派生类也会有虚表,至少有三项,如果重写了相应的虚函数,那么虚表中的地址就会改变,指向自身的虚函数实现,如果派生类有自己的虚函数,那么虚表中就会添加该项。

2.虚函数指针的初始化过程

虚函数表的创建和虚函数指针的初始化都是在构造函数中进行的。当编译器发现基类当中有虚函数存在时,就会为每个含有虚函数的类创建一个虚函数表(vtable),该表是一个一维数组,存放的是虚函数的地址,子类中如果没有虚函数也会从基类中继承虚函数表,虚表创建之后还会创建一个虚函数指针来指向虚表,即(vptr)。
构造函数调用顺序:基类构造函数、对象成员构造函数、派生类本身的构造函数 。
当构造子类对象是,需要先调用父类的构造函数,而此时编译器还不知道是否有继承者,那么会创建父类的虚函数表及虚函数指针,指针指向父类的虚函数,当执行到子类的构造函数是,就会初始化子类的虚函数表和虚函数指针。

3.析构函数可以是虚函数?为什么?

析构函数主要是为类对象释放资源而存在的,和构造函数成对。
当基类的析构函数定义为虚函数的时候,就算在基类中删除了基类指针, 子类可以利用自己的析构函数来调用基类的析构函数,从而达到整个类的完全析构。
相反,当基类指针操作派生类对象,基类析构函数不是虚函数时,派生类执行析构函数时只是释放掉了派生类的资源,而不会调用基类的析构函数,从而造成整个类没有完全析构。。
C++中析构函数为虚函数,简单示例

4.c++多态实现

以上两点说提到的虚函数就是C++多态性质的基本原理,多态究其根本就是是一个接口多种实现。
c++中的多态性,当c++编译器在编译的时候,发现基类的某个函数是虚函数,这个时候c++就会采用晚绑定技术,也就是编译时并不确定具体调用的函数,而是在运行时,依据对象的类型来确认调用的是哪一个函数,这种能力就叫做c++的多态性,我们没有在函数前加virtual关键字时,c++编译器就确定了哪个函数被调用,这叫做早期绑定

//多态的形成
基类
{
vitural 函数;
}
子类 :基类
{
函数//可重定义
}
main()
{

基类 *p = new 基类;
p -> 函数;
p = 派生类;//重定位基类指针
p -> 函数;
}

#include <iostream>
using namespace std;
//军队
class Troops{
public:
virtual void fight(){ cout<<"Strike back!"<<endl; }
};
//陆军
class Army: public Troops{
public:
void fight(){ cout<<"--Army is fighting!"<<endl; }
};
//99A主战坦克
class _99A: public Army{
public:
void fight(){ cout<<"----99A(Tank) is fighting!"<<endl; }
};
//武直10武装直升机
class WZ_10: public Army{
public:
void fight(){ cout<<"----WZ-10(Helicopter) is fighting!"<<endl; }
};
//长剑10巡航导弹
class CJ_10: public Army{
public:
void fight(){ cout<<"----CJ-10(Missile) is fighting!"<<endl; }
};
//空军
class AirForce: public Troops{
public:
void fight(){ cout<<"--AirForce is fighting!"<<endl; }
};
//J-20隐形歼击机
class J_20: public AirForce{
public:
void fight(){ cout<<"----J-20(Fighter Plane) is fighting!"<<endl; }
};
//CH5无人机
class CH_5: public AirForce{
public:
void fight(){ cout<<"----CH-5(UAV) is fighting!"<<endl; }
};
//轰6K轰炸机
class H_6K: public AirForce{
public:
void fight(){ cout<<"----H-6K(Bomber) is fighting!"<<endl; }
};
int main(){
Troops *p = new Troops;
p ->fight();
//陆军
p = new Army;
p ->fight();
p = new _99A;
p -> fight();
p = new WZ_10;
p -> fight();
p = new CJ_10;
p -> fight();
//空军
p = new AirForce;
p -> fight();
p = new J_20;
p -> fight();
p = new CH_5;
p -> fight();
p = new H_6K;
p -> fight();
return 0;
}

5.智能指针

C++程序设计中的内存管理通常会有程序员申请+及时释放,但是在实际情况中会有很多指针频繁的申请和释放,容易出现一些疏忽大意的问题(内存泄漏)。C++11中引入智能指针的概念,智能指针能够更好的管理内存。
4.1 智能指针:
1.实质:对普通指针的进一步封装,实质是一个类对象,行为表现和指针一样。
2.作用:防止程序员忘记调用delete释放内存,以及其他程序内存异常情况造成的程序崩溃。
3.功能: 当智能指针(类对象)被使用过之后,会被自动删除,指向的内存也会自动的释放。

4.2 四种智能指针分类:
智能指针在C++11版本之后提供,包含在头文件中,auto_ptr(C++11 已经废弃)、unique_ptr、shared_ptr、weak_ptr
1.auto_ptr
auto_ptr 由 C++98 引入,其功能和用法类似于 unique_ptr,由 new expression 获得对象,在 auto_ptr 对象销毁时,他所管理的对象也会自动被 delete 掉。C++11中用unique_ptr来替换auto_ptr,原因是前者的安全性更高。
详情移步四种智能指针详解
2.unique_ptr
unique_ptr (独一无二的ptr) 该指针不共享它所指向的对象,也即是它无法复制到另外一个unique_ptr指针,内有拷贝语义,只能是将其管理的内存资源权限进行转移

unique_ptr<string> u_ptr1 = std::move(u_ptr2);
,转移之后,原指针将不再拥有此资源。而auto_ptr有拷贝语义,没有转移语义,也就是会增加内存出现问题的概率。如下

unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//编译出错,已禁止拷贝
unique_ptr<string> upt1=upt;	//编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt);  //控制权限转移
auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//编译通过
auto_ptr<string> apt1=apt;	//编译通过

3.shared_ptr
shared_ptr 与 unique_ptr形成对比,shared_ptr是允许多个指针指向同一个对象,它是为了解决(auto_ptr)unique_ptr在对象所有权的局限性,后者在对象的内存管理权上是独占的。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
4.weak_ptr
weak_ptr 被设计为与shared_ptr共同工作,weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 operator-> ,因此取名为 weak,表明其是功能较弱的智能指针。它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。

weak_ptr<T> w;	 	//创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr<T> w(sp);	//与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p;				//p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset();			//将 w 置空
w.use_count();		//返回与 w 共享对象的 shared_ptr 的数量
w.expired();		//若 w.use_count() 为 0,返回 true,否则返回 false
w.lock();			//如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr

6.c语言如何实现c++对象以及私有成员.

C语言实现C++对象
1.C语言中没有类,但是可以用struct结构体来充当一个类
2.但是结构体中智能定义变量,不能定义函数,只能通过函数指针的方式来实现功能
3.与C++类对象的区别,结构体的构造函数只能在“类”(结构体)外部实现
C语言如何实现是私有化成员?
解决方案:将结构体的声明放在头文件(st.h)中,将其实现放在源文件(st.cpp)中。
然后在main.cpp中调用st.cpp中定义的相关函数接口就能访问结构体成员变量。(注:只能在st .cpp下的函数才能访问结构体,也即是成员变量私有化操作)
理解:当main.cpp编译时,编译器只包含了st.h头文件,但是st.h中只有结构体定义,却没有结构体的实现,所以在main.cpp下访问结构体的成员变为无效,只能通过st.cpp中的接口函数才能访问。C语言结构体实现成员私有化

#include<stdio.h>
typedef struct Person
{
char name;
int age;
void(*Function)(struct Person this);
}Person;
//定义函数功能
void Function(struct Person this)
{
printf("name is %c,age is %d" Person.name,Person.age);
}
//定义“类”的构造函数
//与面向对象不同,C语言的“类”的构造函数不能放在“类”中,只能放在“类”外
//构造函数主要完成变量的初始化,以及函数指针的赋值
Person *NewPerson(Person  *this)
{
this->name='G';
this->age=22;
this->Function = Function;
}
//主函数调用
int main()
{
Person person;
NewPerson(&person);
person.Function(person);
return 0;
}

7.new和malloc的区别以及底层实现原理

1.new 是C++中动态管理内存运算符,和delete搭配使用。malloc是c语言中动态申请内存的库函数,和free搭配使用。
2.new / delete 可以看作是带构造函数和析构函数的 malloc / free 。new 对象是带有初始化参数的,使用时无需指定内存块的大小,delete也是自动调用析构函数的。而malloc返回的是一个void * 空类型指针,需要指定内存块的大小,

void * malloc(size_t size);
而且内存没有经过初始化,所以在申请之后需要立马进行初始化。
**3.delete/free (指针):**此时并不是删除了指针,而是释放了该指针指向的内存资源,指针依然会存在。指针如果不管的话,它依然指向该块内存。所以为了保证野指针的问题,有必要将其置空。即是 p = null;操作。

8.STL中的vector怎么扩容

1.vector ,动态数组,array,静态数组,他们在性质上有些许相似。
2.array是静态空间,一旦配置了就不能改变;要换个大(或小)一点的房子,可以,一切琐细都得由客户端自己来:首先配置一块新空间,然后将元素从旧址一一搬往新址,再把原来的空间释还给系统。vector是动态空间,当检测到需要扩容的时候,会自动申请另外一块连续的空间,并不是直接在原空间地址连接另一块空间,并把源空间的数据迁移过来,将原空间释放掉。这一切的操作都是自动完成的。
3.在VS2017中,vector每次扩容都是为前一次的1.5倍,即每次增加前一次的一半。除此之外,还可以通过reserve()函数来自定义扩充容量。vector.reserve();

9.c++11 原子变量介绍

在多线程中需要在不同线程之间共享一些数据,在共享变量的操作中如果没有进行过加锁操作,就会产生著名的 i++问题 i++问题

i++;
操作在汇编层面实际分为三步,所以在这种多步走操作中,如果不加处理,当多个线程同时访问该数据的时候就会发生意想不到的错误。
解决方案
1.对共享变量进行加锁,保证每个时刻只会有一个线程在访问该数据,以此来保证数据不会发生错乱,以保证线程安全
2. C++11中提供了原子变量与原子操作来支持上面的操作,避免了我们自己去加锁的麻烦。
原子操作:就是将一系列操作能看成一个原子,不可分割,要么每个步骤都完成,要么都不做,同时能够按照顺序进行。从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。
C++11标准在标准库atomic头文件提供了模版
atomic<>
来定义原子量结构体:

<template class T>
struct atomic;

结构体中,主要定义了原子变量的基本构造函数,运算符重载函数,和一些基本操作。
除此之外还有其他的很多类型特例,在其中都有相应的构造函数和成员函数。

//一系列封装的原子操作
T exchange(T val, memory_order = memory_order_seq_cst) volatile;
T exchange(T val, memory_order = memory_order_seq_cst);
//交换操作
void store(T val, memory_order = memory_order_seq_cst) volatile;
void store(T val, memory_order = memory_order_seq_cst);
//store 写操作
T load(memory_order = memory_order_seq_cst) const volatile;
T load(memory_order = memory_order_seq_cst) const;
//load 读操作
bool compare_exchange_weak(T& expected, T val, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_weak(T &, T, memory_order = memory_order_seq_cst);
bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst) volatile;
bool compare_exchange_strong(T &, T, memory_order = memory_order_seq_cst);
//比较更改操作

原子变量操作实例

10.c++11特性有哪些,说用过的

1.auto关键字:编译器可以根据赋给的初始值来自动推导出类型,但是数组,函数形参不能使用,有一定的局限性。
2.nullptr关键字:传统NULL一般宏定义为0,在遇到重载时可能会出现问题,而nullptr是一种特殊类型的字面值,它可以被转换成任意其他的指针类型。
3.智能指针:C++11新增加了shared_ptr、weak_ptr等类型的智能指针,用于解决内存管理等问题。
4.新增atomic原子变量操作,使得在多线程中不用我们对数据的访问过程添加互斥资源锁,可以理解为原子变量内部已经加了锁。
5.STL新增array,forward_list、unordered_map、unordered_set等容器。array 和数组一样,forward_list 前向链表,单链表、unordered_map 哈希表实现的map、unordered_set 哈希表实现的 set集合。

11.怎么理解重载与重写

1.重载在一个类里面实现,重写在父子类中实现。
2.重载是在一个类里面,方法名字相同,而参数不同,返回类型可以相同也可以不同。
重写是子类对父类的方法名字进行核心重写,即是方法名字,返回值和形参都不能改变,重写的对象必须含有虚函数成员函数,重写即是子类对父类进行多态的一种实现。如果父类没有虚函数,那就称之为重定义,而不是重写。
3.每个重载的方法都必须有一个独一无二的参数类型列表
重写的好处在于子类可以根据自己的需要,定义特定的行为方法。

12.怎么理解c++中的 static、const、volatile 关键字

1.static 主要使用场景有:
修饰局部变量(函数体内),延长了局部变量的生命周期,static的局部变量不会随着函数的执行而被销毁,当函数被再次执行时,该静态变量会保持上一次函数结束时的值。
修饰全局变量(.cpp 源文件中),主要用作该静态变量作用域的限制,static修饰的函数或者全局变量,只能在该源文件中被引用,其他的文件不能访问。
在C++面向对象编程中,static可以用来修饰类成员函数和数据成员变量
对于类中被修饰的数据成员变量,相当于整个类阈中的全局变量,它可以被类的所有对象共享。**由于静态数据成员存储在全局数据区,因此,在定义时就要分配内存,这也就导致静态数据成员不能在类声明中定义。**因此static修饰的数据成员必须在类外进行初始化且只会初始化一次。

class Test
{
public:
Test() {};
~Test() {};
public:
static int num;     //类中声明
//static int num = 10; 不行
};
int Test::num = 10;    //类外定义

同样,类中static修饰的成员函数也是同静态数据成员一样,静态成员函数也是属于类,而不属于任何一个类的实体对象,因此,静态成员函数不含有this指针。同时,它也不能访问类中其它的非静态数据成员和函数。(非静态成员函数可以访问静态数据数据成员和静态成员函数)
2.const 防止数据被改写,提高安全性。一句话:Use const everywhere when you want.
3.vialoat : 防止被优化,即是一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去优化这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。

13.vector和list 的区别

1.内存上,vector是一段连续的内存空间,起始地址不变。list是双向链表,(fowerd_list是单向链表),它的内存是不连续的。
2.从数据的访问上,vector能进行搞笑的读取,下标查找,但是在删除和插入上需要内存块的整体迁移,时间复杂度为O(n)。list只能通过指针访问数据,查找效率很低,但是插入和删除很高效。

14.c++的内存分配

1.代码区:存放二进制代码,
2.全局区:存放全局变量、静态变量以及常量,程序结束的时候会自动释放
3.栈:存放函数参数,局部变量等,数组,由操作系统自动分配以及释放
4.堆:由程序员申请以及释放。

15.map与set的底层实现

1.存储方式:map、set都是STL中的关联容器,其中map是以键值对的方式存储,

pair(key,value);
其中key是唯一的,不能有重复,重复的话可用multimap。set中以value存储,value值不允许重复,自动排序,需要重复的可以用multiset。
2.两者的底层都是红黑树,红黑树的特性保证了它在插入和删除方面高效,节点式的操作方式,不纯在内存移动,也就不存在迭代器失效的情况,(vector扩容之后,整体地址改变,迭代器也相应的失效)。
3.红黑树,AVL树,平衡二叉树,B树对比详见下回。

15.1 unorder_map

红黑树容器 有序 O(LogN)

map //键值对,不能有重复 key 值, 红黑树
multimap // 同上,但是可以有重复 key 值,find.(key) 指向第一个元素,依次而已
set //集合元素,只有value 值,红黑树,不可重复,自动排序
multiset //同上,可重复。

哈希表容器 无序 O(1)

unordered_map // 哈希存储容器,查找性能O(1),键值对存储
unordered_multimap//可重复
unordered_set//Hashtable   不可重复
unordered_multiset// 可重复

16.类静态变量的初始化

C++类的静态变量成员是需要进行初始化的。静态变量在类中仅仅只是声明,没有定义,所以需要在类的外面为静态变量分配内存和定义。
1.在类中,仅仅是静态变量的声明,系统并没有为其分配内存空间,这点和结构体比较相似。
2.类外,全局区为静态变量进行定义,即赋值等操作,最后才能在main()等函数调用。

#include <stdio.h>
class A {
public:
static int a; //声明但未定义,还未分配内存
};
static int A::a = 3; //定义了静态成员变量,同时初始化。也可以写"int A:a;",即不给初值,同样可以通过编译
int main() {
printf("%d", A::a);
return 0;

17.深拷贝与浅拷贝

通俗的来讲:在有指针的情况下,浅拷贝只是复制了指针,没有开辟新的内存空间;深拷贝不仅复制了指针,还复制了对象,重新开辟了一块内存空间,是一个对象的完整的拷贝,需要自己动手来析构或者free/delete。重要的一点:深拷贝避免了浅拷贝中的对同一内存空间进行重复析构的常见错误。
插一句题外话:引用和指针的区别。本质上,引用是对象的别名,而指针是地址。
引用“&”,在创建的时候必须初始化,将其引用到一个有效的对象。其次,引用一旦初始化就不能被改变,即不能把其他对象再次赋给它,最后引用的创建和销毁不会调用类的构造和析构函数。
一般理解 引用是不占内存的。

作为变量、常量、空语句、都会在编译后占用空间,从这点上看,同一变量的多次使用也是要占用内存的。
但是引用在编译以后直接以被引用的对象进行操作,其所占空间就是原对象所占空间,并没有“额外”的添加空间来保存对象、因此可以说是不占内存空间。只能说引用占用内存只是暂时的。

指针“*”,初始化分配内存空间,可以被任意改变指向的内存空间,销毁可用析构或者free、delete,然后在将指针置空,注意预防野指针。

18.指针常量,常量指针的区别

1.指针常量(int * const p)
本质上是一个常量,指针用来说明指针类型,(char * const p;即是字符类的指针常量):p是常量(指针类型的,所以p是地址,该地址是不能够改变的,因为是常量),但是(*p)是一个指针变量,所以可以随意改变其指向的内容。
*定义的同时必须初始化,然后 p ,即地址不能改变,但是(p)指向的内容可以被改变。
*2.指针常量( const int p)
本质是一个指针,常量表示指针指向的内容,说明该指针指向一个常量。通常在常量指针中,(*p)指向的常量一经初始化就不能被改变。
*定义的同时必须初始化,然后(p)即指向的常量值不能被改变,但是p(地址)可以改变。
详解点击

int main() {
int m = 10;
const int n = 20; // 必须在定义的同时初始化

const int *ptr1 = &m; // 指针指向的内容不可改变
int * const ptr2 = &m; // 指针不可以指向其他的地方

ptr1 = &n; // 正确
ptr2 = &n; // 错误,ptr2不能指向其他地方

*ptr1 = 3; // 错误,ptr1不能改变指针内容
*ptr2 = 4; // 正确

int *ptr3 = &n; // 错误,常量地址不能初始化普通指针吗,常量地址只能赋值给常量指针
const int * ptr4 = &n; // 正确,常量地址初始化常量指针

int * const ptr5; // 错误,指针常量定义时必须初始化
ptr5 = &m; // 错误,指针常量不能在定义后赋值

const int * const ptr6 = &m; // 指向“常量”的指针常量,具有常量指针和指针常量的特点,指针内容不能改变,也不能指向其他地方,定义同时要进行初始化
*ptr6 = 5; // 错误,不能改变指针内容
ptr6 = &n; // 错误,不能指向其他地方

const int * ptr7; // 正确
ptr7 = &m; // 正确

int * const ptr8 = &n;
*ptr8 = 8;

return 0;
}

18.一个空的类占多少内存

#include<iostream>
using namespace std;
class a {};
class b{};
class e{
public:
void func1();
virtual void func2();
private:
static int n;
int m;
};
class c:public a{
virtual void fun()=0;
};
class d:public b,public c{};
int main(){
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
cout<<"sizeof(e)"<<sizeof(e)<<endl;
return 0;
}


类a,b明明是空类,它的大小应该为为0,为什么 编译器输出的结果为1呢?这就是我们刚才所说的实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以a,b的大小为1.
而类c是由类a派生而来,它里面有一个纯虚函数,由于有虚函数的原因,有一个指向虚函数的指针(vptr),在32位的系统分配给指针的大小为4个字节,所以最后得到c类的大小为4.
类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节.
当然在不同的编译器上得到的结果可能不同,但是这个实验告诉我们初学者,不管类是否为空类,均可被实例化(空类也可被实例化),每个被实例都有一个独一无二的地址.

19.vector底层实现,empty() 和size==0 有什么区别

最主要的还是通过三个指针 first / last / end

_Myfirst 和 _Mylast 可以用来表示 vector 容器中目前已被使用的内存空间;
_Mylast 和 _Myend 可以用来表示 vector 容器目前空闲的内存空间;
_Myfirst 和 _Myend 可以用表示 vector 容器的容量。

template <class _Ty, class _Alloc = allocator<_Ty>>
class vector
{
public:
iterator begin() {return _Myfirst;}
iterator end() {return _Mylast;}
size_type size() const {return size_type(end() - begin());}
size_type capacity() const {return size_type(_Myend - begin());}
bool empty() const {return begin() == end();}
reference operator[] (size_type n) {return *(begin() + n);}
reference front() { return *begin();}
reference back() {return *(end()-1);}
protected:
pointer _Myfirst;
pointer _Mylast;
pointer _Myend;
}

20.C++的垃圾回收机制

1.引用计数法:
为每个对象加一个计数器,计数器记录的是所有指向该对象的引用数量,每次有一个新的引用指向该对象时+1,反之 -1,最后为0时表名安全,即可回收
引用计数法会有循环引用的问题出现,所以并不是一种非常安全的算法。C/C++ 采用了智能指针的方式来保证内存安全。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: