1. 说⼀下你理解的 C++ 中的四种智能指针
智能指针的作用是管理指针,可以避免内存泄漏的发生。
智能指针就是一个类,当超出了类的作用域时,就会调用析构函数,这时就会自动释放资源。
所以智能指针作用的原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
- auto_ptr,采用所有权方式,当指针拷贝的时候,被拷贝的指针会丧失所有权,会报错产生内存崩溃的问题。
- unique_ptr,独占式拥有概念,保证同一时间只有一个智能指针指向该对象,所以在拷贝的时候就会报错。
- shared_ptr,共享式拥有概念,是强引用,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后⼀个引⽤被销毁”时候释放。
- weak_ptr,弱引用,不控制对象⽣命周期的智能指针,它指向⼀个 shared_ptr 管理的对象。进⾏该对象的内存管理的是那个强引⽤的 shared_ptr。
⾯试官你好,⾸先,说⼀下为什么要使⽤智能指针:智能指针其作⽤是管理⼀个指针,避免咱们程序员申请的空间在函数结束时忘记释放,造成内存泄漏这种情况的发⽣。
然后使⽤智能指针可以很⼤程度上的避免这个问题,因为智能指针就是⼀个类,当超出了类的作⽤域时,类会⾃动调⽤析构函数,析构函数会⾃动释放资源。所以智能指针的作⽤原理就是在函数结束时⾃动释放内存空间,不需要⼿动释放内存空间。
T* get();
T& operator*();
T* operator->();
T& operator=(const T& val);
T* release();
void reset (T* ptr = nullptr);
- T 是模板参数, 也就是传⼊的类型;
- get() ⽤来获取 auto_ptr 封装在内部的指针, 也就是获取原⽣指针;
- operator() 重载 , operator->() 重载了->, operator=()重载了=;
- release() 将 auto_ptr 封装在内部的指针置为 nullptr, 但并不会破坏指针所指向的内容, 函数返回的是内部指针置空之前的值;
- 直接释放封装的内部指针所指向的内存, 如果指定了 ptr 的值, 则将内部指针初始化为该值(否则将其设置为nullptr;
1. auto_ptr
(C++98 的⽅案, C11 已抛弃)采⽤所有权模式
auto_ptr<std::string> p1 (new string ("hello"));
auto_ptr<std::string> p2;
p2 = p1; //auto_ptr 不会报错.
此时不会报错, p2 剥夺了 p1 的所有权,但是当程序运⾏时访问 p1 将会报错。所以 auto_ptr的缺点是:存在潜在的内存崩溃问题!
2. unique_ptr
(替换 auto_ptr )
unique_ptr 实现独占式拥有或严格拥有概念,保证同⼀时间内只有⼀个智能指针可以指向该对象。它对于避免资源泄露特别有⽤。
采⽤所有权模式,还是上⾯那个例⼦
unique_ptr<string> p3 (new string (auto));//#4
unique_ptr<string> p4; //#5
p4 = p3;//此时会报错
编译器认为 p4=p3 ⾮法,避免了 p3 不再指向有效数据的问题。
因此, unique_ptr ⽐ auto_ptr 更安全。
3. shared_ptr
(共享型,强引⽤)
shared_ptr 实现共享式拥有概念,多个智能指针可以指向相同对象,该对象和其相关资源会在“最后⼀个引⽤被销毁”时候释放。从名字 share 就可以看出了资源可以被多个指针共享,它使⽤计数机制来表明资源被⼏个指针共享。
可以通过成员函数 use_count() 来查看资源的所有者个数,除了可以通过 new 来构造,还可以通过传⼊auto_ptr, unique_ptr,weak_ptr 来构造。当我们调⽤ release() 时,当前指针会释放资源所有权,计数减⼀。当计数等于 0 时,资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性 (auto_ptr 是独占的),在使⽤引⽤计数的机制上提供了可以共享所有权的智能指针
4. weak_ptr
(弱引⽤)
weak_ptr 是⼀种不控制对象⽣命周期的智能指针,它指向⼀个 shared_ptr 管理的对象。进⾏该对象的内存管理的是那个强引⽤的 shared_ptr。
weak_ptr 只是提供了对管理对象的⼀个访问⼿段。 weak_ptr 设计的⽬的是为配合shared_ptr ⽽引⼊的⼀种智能指针来协助 shared_ptr ⼯作,它只可以从⼀个 shared_ptr 或另⼀个 weak_ptr 对象构造,,它的构造和析构不会引起引⽤记数的增加或减少。
weak_ptr 是⽤来解决 shared_ptr 相互引⽤时的死锁问题,如果说两个 shared_ptr 相互引⽤,那么这两个指针的引⽤计数永远不可能下降为0,也就是资源永远不会释放。它是对对象的⼀种弱引⽤,不会增加对象的引⽤计数,和 shared_ptr 之间可以相互转化, shared_ptr 可以直接赋值给它,它可以通过调⽤ lock 函数来获得shared_ptr。
当两个智能指针都是 shared_ptr 类型的时候,析构时两个资源引⽤计数会减⼀,但是两者引⽤计数还是为 1,导致跳出函数时资源没有被释放(的析构函数没有被调⽤),解决办法:把其中⼀个改为weak_ptr就可以。
2. C++ 中内存分配情况
- 栈:由编译器管理分配和回收,存放局部变量和函数参数。
- 堆:由程序员管理,需要⼿动 new malloc delete free 进⾏分配和回收,空间较⼤,但可能会出现内存泄漏和空闲碎⽚的情况。
- 全局/静态存储区:分为初始化和未初始化两个相邻区域,存储初始化和未初始化的全局变量 和静态变量。
- 常量存储区:存储常量,⼀般不允许修改。
- 代码区:存放程序的⼆进制代码。
3. C++ 中的指针参数传递和引⽤参数传递
指针参数传递本质是值传递,传递的是一个地址,开辟栈帧空间存放主调函数传递的实参作为局部变量处理,形成一个拷贝。被调函数对形参的操作不会影响主调函数的实参。
引用参数传递,在栈中存放的形参也作为局部变量,但是存放的是主调函数实参的地址。被调函数对形参的任何操作都会被处理成间接寻址,访问主调函数中的实参。因此可以影响。
编译的角度,符号表中指针变量对应的地址值是指针变量的地址值,而引用对应的地址值是引用对象的地址值。符号表生成之后不会再改,所以指针可以改变指向的对象,引用对象不可以改。
指针参数传递本质上是值传递,它所传递的是⼀个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从⽽形成了实参的⼀个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进⾏的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。
引⽤参数传递过程中,被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。
引⽤传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的⼀个局部变量,但是任何对于引⽤参数的处理都会通过⼀个间接寻址的⽅式操作到主调函数中的相关变量。⽽对于指针传递的参数,如果改变被调函数中的指针地址,它将应⽤不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就得使⽤指向指针的指针或者指针引⽤。
从编译的⻆度来讲,程序在编译时分别将指针和引⽤添加到符号表上,符号表中记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,⽽引⽤在符号表上对应的地址值为引⽤对象的地址值(与实参名字不同,地址相同)。符号表⽣成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),⽽引⽤对象则不能修改。
4. C++ 中 const 和 static 关键字(定义,⽤途)
static作用:控制变量的存储方式和可见性
- 修饰局部变量
一般局部变量存放在栈区,并且语句块执行结束时生命周期也结束;修饰之后会存放在静态数据区,生命周期会一直持续到程序结束。其中局部变量的作用域没有改变 - 修饰全局变量
全局变量可以在本文件和同一工程中的其他源文件访问,修饰后改变了作用域范围,从整个工程可见变为本文件可见 - 修饰函数
和修饰全局变量类似,也是改变了函数的作⽤域 - 修饰类
修饰类中的函数,表示该函数属于⼀个类⽽不是属于此类的任何特定对象
对类中的某个变量进⾏ static 修饰,则表示该变量以及所有的对象所有,存储空间中只存在⼀个副本,可以通过类和对象去调⽤ - 类成员/函数声明static
const
- 修饰基本数据类型,在前在后都一样,只要不改变常量的值即可
- 修饰指针和引用变量,在
*
左侧表示指针的指向为常量,在右侧表示指针为常量 - 修饰函数参数,对原对象进行保护
- 修饰类
static 作⽤:控制变量的存储⽅式和可⻅性。
作⽤⼀:修饰局部变量
⼀般情况下,对于局部变量在程序中是存放在栈区的,并且局部的⽣命周期在包含语句块执⾏结束时便结束了。但是如果⽤ static 关键字修饰的话,该变量便会存放在静态数据区,其⽣命周期会⼀直延续到整个程序执⾏结束。但是要注意的是,虽然⽤static 对局部变量进⾏修饰之后,其⽣命周期以及存储空间发⽣了变化,但其作⽤域并没有改变,作⽤域还是限制在其语句块。
作⽤⼆:修饰全局变量
对于⼀个全局变量,它既可以在本⽂件中被访问到,也可以在同⼀个⼯程中其它源⽂件被访问(添加 extern进⾏声明即可)。⽤ static 对全局变量进⾏修饰改变了其作⽤域范围,由原来的整个⼯程可⻅变成了本⽂件可⻅。
作⽤三:修饰函数
⽤ static 修饰函数,情况和修饰全局变量类似,也是改变了函数的作⽤域。
作⽤四:修饰类
如果 C++ 中对类中的某个函数⽤ static 修饰,则表示该函数属于⼀个类⽽不是属于此类的任何特定对象;如果对类中的某个变量进⾏ static 修饰,则表示该变量以及所有的对象所有,存储空间中只存在⼀个副本,可以通过类和对象去调⽤。
(补充:静态⾮常量数据成员,其只能在类外定义和初始化,在类内仅是声明⽽已。)
作⽤五:类成员/类函数声明 static
- 函数体内 static 变量的作⽤范围为该函数体,不同于 auto 变量,该变量的内存只被分配⼀次,因此其值在下次调⽤时仍维持上次的值;
- 在模块内的 static 全局变量可以被模块内所⽤函数访问,但不能被模块外其它函数访问;
- 在模块内的 static 函数只可被这⼀模块内的其它函数调⽤,这个函数的使⽤范围被限制在声明它的模块内;
- 在类中的 static 成员变量属于整个类所拥有,对类的所有对象只有⼀份拷⻉;
- 在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因⽽只能访问类的 static 成员变量。
- static 类对象必须要在类外进⾏初始化, static 修饰的变量先于对象存在,所以 static 修饰的变量要在类外初始化;
- 由于 static 修饰的类成员属于类,不属于对象,因此 static 类成员函数是没有 this 指针,this 指针是指向本对象的指针,正因为没有 this 指针,所以 static 类成员函数不能访问⾮static 的类成员,只能访问 static修饰的类成员;
- static 成员函数不能被 virtual 修饰, static 成员不属于任何对象或实例,所以加上 virtual没有任何实际意义;静态成员函数没有 this 指针,虚函数的实现是为每⼀个对象分配⼀个vptr 指针,⽽ vptr 是通过 this 指针调⽤的,所以不能为 virtual;虚函数的调⽤关系,this->vptr->ctable->virtual function
const 关键字:含义及实现机制
const 修饰基本类型数据类型
基本数据类型,修饰符 const 可以⽤在类型说明符前,也可以⽤在类型说明符后,其结果是⼀样的。在使⽤这些常量的时候,只要不改变这些常量的值即可。
const 修饰指针变量和引⽤变量
如果 const 位于⼩星星的左侧,则 const 就是⽤来修饰指针所指向的变量,即指针指向为常量;如果 const 位于⼩星星的右侧,则 const 就是修饰指针本身,即指针本身是常量。
const 应⽤到函数中
作为参数的 const 修饰符:调⽤函数的时候,⽤相应的变量初始化const 常量,则在函数体中,按照 const 所修饰的部分进⾏常量化,保护了原对象的属性。
参数 const 通常⽤于参数为指针或引⽤的情况; 作为函数返回值的 const 修饰符:声明了返回值后, const 按照"修饰原则"进⾏修饰,起到相应的保护作⽤。
const 在类中的⽤法
const 成员变量
只在某个对象⽣命周期内是常量,⽽对于整个类⽽⾔是可以改变的。因为类可以创建多个对象,不同的对象其 const 数据成员值可以不同。所以不能在类的声明中初始化 const 数据成员,因为类的对象在没有创建时候,编译器不知道 const数据成员的值是什么。 const 数据成员的初始化只能在类的构造函数的初始化列表中进⾏。
const 成员函数
const 成员函数的主要⽬的是防⽌成员函数修改对象的内容。要注意, const关键字和 static 关键字对于成员函数来说是不能同时使⽤的,因为 static 关键字修饰静态成员函数不含有 this 指针,即不能实例化, const 成员函数⼜必须具体到某⼀个函数。
const 修饰类对象,定义常量对象
常量对象只能调⽤常量函数,别的成员函数都不能调⽤。
补充: const 成员函数中如果实在想修改某个变量,可以使⽤ mutable 进⾏修饰。成员变量中如果想建⽴在整个类中都恒定的常量,应该⽤类中的枚举常量来实现或者 static const。
C ++ 中的 const类成员函数(⽤法和意义)
常量对象可以调⽤类中的 const 成员函数,但不能调⽤⾮ const 成员函数;
(原因:对象调⽤成员函数时,在形参列表的最前⾯加⼀个形参 this,但这是隐式的。 this 指针是默认指向调⽤函数的当前对象的,所以,很⾃然, this 是⼀个常量指针 test * const,因为不可以修改this 指针代表的地址。但当成员函数的参数列表(即⼩括号)后加了 const 关键字(void print() const;)
此成员函数为常量成员函数,此时它的隐式this形参为 const test * const,即不可以通过 this 指针来改变指向对象的值。
⾮常量对象可以调⽤类中的 const 成员函数,也可以调⽤⾮ const 成员函数。
C 和 C++ 区别 (函数/类/struct/class)
- c++有新增的语法和关键字,头文件和命名空间,动态内存管理,new和delete,引用,auto和explicit,dynamic_cast
- c++有函数重载和虚函数的概念,用以实现多态
- c++的类和c的struct不同,类可以有成员函数,增加了权限访问概念
- c++中增加了模板还重用代码,有stl标准库
⾸先, C 和 C++ 在基本语句上没有过⼤的区别。
C++ 有新增的语法和关键字
语法的区别有头⽂件的不同和命名空间的不同, C++ 允许我们⾃⼰定义⾃⼰的空间, C 中不可以。关键字⽅⾯⽐如 C++ 与 C 动态管理内存的⽅式不同,C++ 中在 malloc 和 free 的基础上增加了 new 和 delete,⽽且 C++ 中在指针的基础上增加了引⽤的概念,关键字例如 C++中还增加了 auto, explicit 体现显示和隐式转换上的概念要求,还有 dynamic_cast 增加类型安全⽅⾯的内容。
函数⽅⾯ C++ 中有重载和虚函数的概念:
C++ ⽀持函数重载⽽ C 不⽀持,是因为 C++ 函数的名字修饰与 C 不同, C++ 函数名字的修饰会将参数加在后⾯,例如, int func(int,double)经过名字修饰之后会变成_func_int_double,⽽ C 中则会变成 _func
,所以 C++ 中会⽀持不同参数调⽤不同函数
C++ 还有虚函数概念,⽤以实现多态。
类⽅⾯, C 的 struct 和 C++ 的类也有很⼤不同
C++ 中的 struct 不仅可以有成员变量还可以有成员函数,⽽且对于 struct 增加了权限访问的概念, struct 的默认成员访问权限和默认继承权限都是 public, C++ 中除了 struct 还有 class 表示类, struct 和 class 还有⼀点不同在于 class 的默认成员访问权限和默认继承权限都是 private。
C++ 中增加了模板还重⽤代码,提供了更加强⼤的 STL 标准库。
最后补充⼀点就是 C 是⼀种结构化的语⾔,重点在于算法和数据结构。 C 程序的设计⾸先考虑的是如何通过⼀个代码,⼀个过程对输⼊进⾏运算处理输出。⽽ C++ ⾸先考虑的是如何构造⼀个对象模型,让这个模型能够契合与之对应的问题领域,这样就能通过获取对象的状态信息得到输出。
C 的 struct 更适合看成是⼀个数据结构的实现体,⽽ C++ 的 class 更适合看成是⼀个对象的实现体。