• c++面试题汇总-58-118


    c++面试题汇总-58-118

    58. class和struct在使用大括号{ }上的区别

    关于使用大括号初始化
    1.class和struct如果定义了构造函数的话,都不能用大括号进行初始化
    2.如果没有定义构造函数,struct可以用大括号初始化。
    3.如果没有定义构造函数,且所有成员变量全是public的话,class可以用大括号初始化

    59. 说说static关键字的用法和特点

    引出原因:函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间,大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现?
    最容易想到的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅受此函数控制)。类的静态成员也是这个道理。
    解决方案:因此C++中引入了static,用它来修饰变量,它能够指示编译器将此变量在程序的静态存储区分配空间保存,这样即实现了目的,又使得此变量的存取范围不变static函数的作用
    1.static 全局变量:
    只能在当前文件使用的全局变量,不支持extern
    2.static 局部变量:
    在局部使用
    3.static 修饰成员属性
    存放在全局区,类共享的变量,不能被继承(共用),补依赖对象存在,要在类外初始化,不能在.h文件中初始化(会发生重定义),需要在.cpp文件中初始化 使用时 类名::
    4.static 修饰成员函数
    类共用的,不能被virtual修饰,不能直接调用非静态成员属性,想调用要依赖对象,只能调用静态的,【实质:static函数无this指针】
    5.static对象 与变量的理解一样
    【const和static不能修饰成员属性和成员函数,可以修饰变量】

    60. 迭代器失效的原因?(迭代序列改变前和改变后是否有变化)

    数组型数据结构:该数据结构的元素是分配在连续的内存中,insert和erase操作,都会使得删除点和插入点之后的元素挪位置,所以,插入点和删除点之后的迭代器全部失效,也就是说insert(*iter)(或erase(*iter)),然后在iter++,是没有意义的。解决方法:erase(*iter)的返回值是下一个有效迭代器的值。 iter =cont.erase(iter);
    链表型数据结构:对于list型的数据结构,使用了不连续分配的内存,删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器。解决办法两种,erase(*iter)会返回下一个有效迭代器的值,或者erase(iter++)。
    树形数据结构: 使用红黑树来存储数据,插入不会使得任何迭代器失效;删除运算使指向删除位置的迭代器失效,但是不会失效其他迭代器。erase迭代器只是被删元素的迭代器失效,但是返回值为void,所以要采用erase(iter++)的方式删除迭代器。
    注意:经过erase(iter)之后的迭代器完全失效,该迭代器iter不能参与任何运算,包括iter++,*ite
    https://blog.csdn.net/cmdxly/article/details/126587100?spm=1001.2014.3001.5501

    61. 使用过哪些容器?

    顺序性容器:vector,deque,list,forward_list(单向链表)
    关联性容器:map/unordered_map,set/unordered_sethash表,当元素总量超过buckets的数量后,进行rehashing,对散列表进行扩容,将元素进行重新散列(解决哈希冲突使用的是拉链法)
    容器配置器:stack,queue
    <1>vector容器
    vector容器是一个动态数组的结构,在内存中有一个指针指向一块连续的内存。类似数组结构一样。它的特点支持随机访问数据,因为其在内存中的单元是连续。如此之外, vector的大小是可以自动增长的。当向一个vector中继续存放数据的时候,如果当前的内存大小不够,内核会重新生成一个是原来1.5倍的大小的单元,之后将数据从原先的单元中拷贝至新创建的单元中,并将原来的单元释放掉。(但是所谓的释放单元,仅仅是将原来单元中的数据清空,相应的内存单元还是存在的)
    优点:支持随机访问,所以查询效率高。
    缺点:当向其分非尾插入元素时,因内存单元需要移动数据元素,所以插入的效率比较低。
    适用场景:适用于对象简单,变化较小,并且频繁随机访问的场景。
    2.deque容器
    deque和vector类似,支持快速随机访问。二者最大的区别在于,vector只能在末端插入数据,而deque支持双端插入数据。deque的内存空间分布是小片的连续,小片间用链表相连,实际上内部有一个map的指针。deque空间的重新分配要比vector快,重新分配空间后,原有的元素是不需要拷贝的。deque的连续是假象,实际为分段连续每次扩充一个buffer
    2.list容器
    list容器在内存中的结构是类似双向链表结构,每个元素的内存单元结构是不连续的,彼此之间通存储相关的地址进行关联。由于在内存中的单元不是连续的,所以其不支持随机访问,不具备[]操作运算。每一个节点都有三个域,前驱节点指针域,数据域,后驱节点指针域。
    优点:因为类似链表结构在内存中,所以任意位置删除节点和插入节点都是高效的。
    缺点:因为内存单元不连续,所以不支持随机访问操作。
    适用场景:对象变化大,并且对象数量变化频繁,删除和插入操作的情况。
    3.map容器
    map容器是一个关联式容器。在其内部元素的存储结构是通过key-value结构进行存储,并且其key是唯一存在的。支持那种一对一的数据处理过程。其底层是通过rbtree来实现的,利用红黑树的一种严格的平衡二叉树结构实现。在其实现过程,自动创建key-value的插入数据的过程。并且支持快速的查找,通过键值可进行快速的查找对应的数据元素。以及快速删除操作。
    4.set容器
    set也是一种关联性容器,它同map一样,底层使用红黑树实现,插入删除操作时仅仅移动指针即可,不涉及内存的移动和拷贝,所以效率比较高。set中的元素都是唯一的,而且默认情况下会对元素进行升序排列。所以在set中,不能直接改变元素值,因为那样会打乱原本正确的顺序,要改变元素值必须先删除旧元素,再插入新元素。不提供直接存取元素的任何操作函数,只能通过迭代器进行间接存取。

    62. STL中的sort使用什么排序?

    快排+插入排序+堆排序
    vector,deque适用sort算法
    STL的sort算法,数据量大时采用快速排序,分段归并排序。一旦分段后的数据量小于某个门槛(16),为避免快排的递归调用带来过大的额外负荷,就改用插入排序。如果递归层次过深,改用堆排序,因为堆排序是时间复杂度恒定为nlogn,又因为插入排序在面对“几近排序”的序列时,表现更好

    63. 静态全局变量和全局静态变量一样吗?若有不同,体现在哪?

    首先,并不是说全局变量在定义时加了static关键字才是静态存储,不加static就是动态存储,不是的。不管加不加static,全局变量都是存储在静态存储区的,都是在编译时分配存储空间的,两者只是作用域不同,全局变量默认具有外部链接性,作用域是整个工程,全局静态变量的作用域仅限本文件,不能在其他文件中引用。

    64. 左值,右值,右值引用

    左值:左值是指表达式结束后依然存在的持久化对象
    右值:表达式结束时就不再存在的临时对象
    区分:看能不能对表达式取地址,如果能,则为左值,否则为右值。

    65. 函数参数传递的三种方式

    函数的参数传递:当进行函数调用的时候,要填入与函数形式参数个数相同的实际参数,在程序运行的过程中,实参会将参数值传递给形参,这就是函数的参数传递。
    函数参数传递有以下三种:
    一. 值传递 void fun(int x)

    1. 用值传递方式,实际上是把实参的内容复制到形参中,实参和形参是存放在两个不同的内存空间中。在函数体内对形参的一切修改对实参都没有影响;
    2. 如果形参是类的对象,利用值传递的话每次都要调用类的构造函数构造对象,效率比较低。
      二. 指针传递(地址传递)void fun(int *x)
    3. 当进行指针传递的时候,形参是指针变量,实参是一个变量的地址或者是指针变量,调用函数的时候,形参指向实参的地址;
    4. 指针传递中,函数体内可以通过形参指针改变实参地址空间的内容。
      三. 传递引用 void fun(int &x)
      1.引用实际上是某一个变量的别名,和这个变量具有相同的内存空间;
    5. 实参把变量传递给形参引用,相当于形参是实参变量的别名,对形参的修改都是直接修改实参;
    6. 在类的成员函数中经常用到类的引用对象作为形参,大大的提高代码的效率。

    66. C++中父类指针指向子类对象是如何实现的?

    #include
    using namespace std;
    
    //基类
    class Base
    {
    public:
    	Base()
    	{
    		cout<<"===Base()==="<<endl;
    	}
    	//虚析构函数
    	virtual int fun() = 0;
    	~Base()
    	{
    		cout<<"===~Base()==="<<endl;
    	}
    };
    //子类
    class Son:public Base
    {
    public:
    	Son()
    	{
    		cout<<"===Son()==="<<endl;
    	}
    	int  fun()
    	{
    		cout<<"===fun()==="<<endl;
    		return 0;
    	}
    	~Son()
    	{
    		cout<<"===~Son()==="<<endl;
    	}
    };
     void test()
     {
    	Son s;
    	s.fun();
    	//父类指针指向子类对象
    	Base *b = &s;
    	b->fun();
     }
     int main()
     {
    	 test();
    	 return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    67. 如何不用第三个变量交换两个地址空间?

    A = A + B;
    B = A – B:
    A = A – B;
    或者
    A = A ^ B;
    B = A ^ B;
    A = A ^ B;

    68. 什么是深拷贝?什么是浅拷贝?(浅拷贝共用一个实体,深拷贝相互独立)

    深拷贝:源对象和拷贝对象相互独立,其中任何一个对象的改动都不会对另外一个对象造成影响
    浅拷贝:源对象与拷贝对象共用一份实体,仅仅是引用的变量不同,对其中任何一个改动,都会影响另一个对象
    浅拷贝的影响:

    1. 两个对象的指针成员可能指向相同的内存,相互影响。
    2. 对其中一个指针进行释放操作或销毁析构,将导致另一个指针成为 “悬空指针”

    69. 深拷贝和浅拷贝各出现什么时候?

    c++类的中有两个特殊的构造函数
    (1) 无参构造函数,(2)拷贝构造函数。
    它们的特殊之处在于:
    (1)当类中没有定义任何构造函数时,编译器会默认提供一个无参构造函数且其函数体为空;
    (2)当类中没有定义拷贝构造函数时,编译器会默认提供一个拷贝构造函数,进行成员变量之间的拷贝。(系统不知道申请多大空间,所以只能浅拷贝)
    1.70. 什么是拷贝构造?什么时间会用到拷贝构造?
    在什么情况下系统会调用拷贝构造函数:(三种情况)
    (1)用类的一个对象去初始化另一个对象时
    (2)当函数的形参是类的对象时(也就是值传递时),如果是引用传递则不会调用
    (3)当函数的返回值是类的对象或引用时

    拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它的一个参数必须是本类型的一个引用变量。

    71. 拷贝构造参数为什么是引用传递,改成值传递或者地址传递可以吗?

    首先值传递是不可以的,使用值传递,那么就需要给形参拷贝构造,形参还会再给形参拷贝构造,就会陷入死循环;如果使用地址传递,不符合使用习惯,因为拷贝构造,是使用对象来给另一个对象来初始化,这里不使用指针,所以用地址传递不符合习惯
    
    • 1

    72. 句柄和指针的区别?

    句柄实际上是一种指向某种资源的指针,但与指针又有所不同:指针对应着一个数据在内存中的地址,得到了指针就可以自由地修改该数据。Windows并不希望一般程序修改其内部数据结构,因为这样太不安全。所以Windows给每个使用GlobalAlloc等函数声明的内存区域指定一个句柄(本质上仍是一个指针,但不要直接操作它),平时你只是在调用API函数时利用这个句柄来说明要操作哪段内存。当你需要对某个内存进行直接操作时,可以使用GlobalLock锁住这段内存并获得指针来直接进行操作。
    https://blog.csdn.net/weixin_42845574/article/details/89110434

    73. 结构体和联合体的区别?

    结构体:是一种数据类型,把不同类型的数据组合成一个整体,其内存在对齐后为各个数据成员的和(引向字节对齐)
    联合体:使几个不同类型变量共占同一段空间(相互覆盖),其内存大小为联合体中成员内存最大的那个,存入新成员后,旧有成员失效

    74. 字节对齐的意义?

    首先,我们需要明确无论数据是否对齐,大多数计算机还是能够正确工作,但在有些处理器中,如果存在未对齐的数据,可能无法运行。
    我们假设计算机总是从内存中取8个字节,如果一个double数据的地址对齐成8的倍数,那么一次内存操作就可以读或者写。
    但是如果这个double数据的地址没有对齐,数据就可能被放在两个8字节块中,那么我们可能需要执行两次内存访问,才能读写完成。
    显然在这样的情况下,是低效的,所以需要字节对齐来提高内存读写性能。

    75. 静态链接库和动态链接库链接的过程,显式和隐式的区别,其中动态链接库的显式调用的几个函数?

    Windows
    静态库:函数和数据被编译进一个二进制文件(.lib)(一个或多个obj文件打包),在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.exe)。

    动态库:使用动态库时,往往提供两个文件:引入库(后缀也为.lib)、动态链接库(.DLL)。引入库包含被DLL导出的函数和变量的符号名,DLL包含实际的函数和数据。在编译链接可执行文件时,只需要链接引入库,DLL中的函数代码和数据并不可复制到可执行文件中,在运行时,再去加载DLL,访问DLL中导出的函数。

    隐式调用:在编译/链接阶段完成,由编译系统根据动态库的头文件和库文件进行编译和链接,即需要将引入库加入到应用程序的工程中,从而确定待调用的函数原型和地址。

    显示调用:利用API函数实现加载和卸载DLL,获取调用函数地址获取错误信息等功能。

    Linux
    静态库:与Windows下的静态库类似,后缀名为.a。

    动态库:与Windows下动态库类似,没有引入库,后缀名为.so。

    隐式调用:编译可执行程序时需要指定库文件的搜索路径。

    显式调用:显式调用编译可执行程序时不用加上动态库的搜索路径(因为已经在主函数中包含了库文件的路径),但是需要增加几个系统调用。

    76. delete和delete[]的用法,使用不当为什么会造成那些现象?原理是什么?

    https://blog.csdn.net/ARLoverKang/article/details/9122413

    77. main函数为什么返回0,返回0代表什么?成功返回后程序还会走其他函数吗?

    在这里插入图片描述

    78. 内存池的好处?

    为什么需要内存池:

    1. 因为在C/C++下的内存管理需要调用malloc或者是new,系统需要根据最先匹配和最优匹配或者其他算法在内存空闲块表中查找一块空闲内存,调用free/delete,系统可能需要合并空闲内存块,这些会产生额外的开销
    2. 频繁使用时会产生大量的内存碎片,从而降低程序效率
    3. 容易造成内存泄漏

    什么是内存池?
    代替直接调用malloc/free、new/delete进行内存管理的常用方法,当我们申请内存空间时,首先到我们的内存池中查找合适的内存块,而不是直接向操作系统申请,优势在于:

    1. 比malloc/free进行内存申请/释放的方式快
    2. 不会产生或很少产生堆碎片
    3. 可避免内存泄漏
      内存池的架构?

    79. extern,static的区别?static和auto变量的区别?

    https://www.jb51.net/article/234831.htm

    80. fopen和open的区别?

    https://blog.csdn.net/qq_26690505/article/details/120201708

    81. 析构函数为什么要声明成虚函数?

    我们知道在类的继承中,构造函数的执行顺序是先构造基类然后再构造派生类,析构函数则相反,是先析构派生类再析构基类。我们也知道声明父类的指针指向派生类,编译器会默认实施静态绑定,不能调用派生类重写的函数,所以才需要虚函数。虚函数是通过虚函数表实现,在运行时进行动态绑定,可以调用在派生类重写的函数。那么如果一个类存在派生,那么就应该将基类的析构函数声明为虚函数,否则,将实施静态绑定无法析构派生类对象。
    https://blog.csdn.net/Li_haiyu/article/details/88082071

    82. 位运算有哪些?怎么用?

    https://blog.csdn.net/qq_54180412/article/details/126160242

    83. 指针大小多大?是确定的吗?

    https://blog.csdn.net/puppet_master/article/details/49963727
    指针大小是由当前CPU运行模式的寻址位数决定!

    84. 内联和宏的应用场合?

    https://blog.csdn.net/Mr_H9527/article/details/100596854

    85. 局部变量可以和全局变量重名吗?

    能,局部会屏蔽全局。要用全局变量,需要使用 “::” ;
    局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。

    86. 左++和右++哪个效率高,原因?

    https://blog.csdn.net/weixin_42795141/article/details/89331430

    87. 用过GetLastError吗?怎么实现的?

    https://blog.csdn.net/liunan199481/article/details/94432950

    88. 普通函数和虚函数的区别?

    https://blog.csdn.net/weixin_50897975/article/details/123913888

    89. 左引用和右引用?

    https://www.cnblogs.com/kaleidopink/p/13720773.html

    90. 智能指针有哪些?

    https://blog.csdn.net/misterdo/article/details/108684059

    91. unique_ptr可以拷贝吗?

    不能
    https://blog.csdn.net/qq_34525916/article/details/121822361

    92. 容器是线程安全的吗 怎么保证线程安全?

    不是
    https://cloud.tencent.com/developer/article/1915007

    93. 如果底层是hash表怎么加锁?

    94. 如何理解互斥锁、条件变量、读写锁以及自旋锁?

    https://mp.weixin.qq.com/s?__biz=MjM5NDIyMjI3OQ==&mid=2649002539&idx=1&sn=b0cd8b1d1e471be565168b71458863e4&scene=21#wechat_redirect

    95. C++异常?

    https://blog.csdn.net/weixin_45966328/article/details/120474036

    96. shared_ptr和unique_ptr的区别

    https://blog.csdn.net/MissXy_/article/details/81029794

    97. int const * p和int * const p的区别

    https://blog.csdn.net/weixin_52848650/article/details/116658437

    98. map和unordered_map的底层实现和复杂度以及两者区别(红黑树和hash表)

    https://blog.csdn.net/sebeefe/article/details/124164460

    99. 手写智能指针shared_ptr

    #include 
    #include 
    using namespace std;
    template<typename T>
    class my_shared_ptr {
    private:
    	size_t* _useCnt;  
    	T* _ptr;
    
    public:
    	// 默认构造函数
    	my_shared_ptr() : _ptr(new T), _useCnt(new size_t(0)) {}
    
    	// 构造函数
    	//explicit显示转换声明,implicit隐式转换声明
    	explicit my_shared_ptr(T* p) : _ptr(p),  _useCnt(new size_t){
    		if (p) {
    			*_useCnt = 1;
    		} else {
    			*_useCnt = 0;
    		}
    	}
    
    	// 拷贝构造函数
    	my_shared_ptr(const my_shared_ptr& rhs) {
    		this->_ptr = rhs._ptr; // 指向同一块内存
    		this->_useCnt = rhs._useCnt;
    		(*this->_useCnt)++; // 引用计数增加	
    	}
    
    	// 拷贝赋值函数,注意检查自赋值
    	my_shared_ptr& operator= (const my_shared_ptr& rhs) {
    		if (this != &rhs) {			// 检查自赋值
    			if (_ptr) {				// 防止自身原来指向空指针,_useCnt>0说明确实是指向实体的
    				--(*_useCnt);		// 自己要被rhs覆盖了,引用计数减1
    				// 如果没有引用计数了,把自己清空,释放动态内存
    				if (*_useCnt == 0) {
    					delete _useCnt;
    					delete _ptr;
    				}
    				// 把自己变成rhs, 引用计数加一
    				_useCnt = rhs._useCnt;
    				_ptr = rhs._ptr;
    				++(*_useCnt);
    			}
    		}
    		return *this;
    	}
    
    	// 得到计数
    	int use_count() {
    		return *_useCnt;
    	}
    	~my_shared_ptr() {
    		if (!_ptr || (*_useCnt == 1)) { // 只有当前指针指向空或者只有一个指向对象时释放内存
    			delete _useCnt;
    			delete _ptr;
    		} else {  // 否则只减少引用计数
    			--(*_useCnt);
    		}
    	}
    };
    
    int main() {
    
    	// 检查构造函数是否正常工作
    	my_shared_ptr<int> p1(new int(1));
    	cout << "p1 cnt:" << p1.use_count() << endl;
    
    	// 检查拷贝构造是否正常工作
    	{
    		my_shared_ptr<int> p2(p1);
    		cout << "p2 cnt:" << p2.use_count() << endl;
    		cout << "p1 cnt:" << p1.use_count() << endl;
    	}
    
    	// 检查引用计数是否正常工作
    	cout << "p1 cnt:" << p1.use_count() << endl;
    
    	// 检查默认初始化时计数是否正常
    	my_shared_ptr<int> p4;
    	cout << "p4 cnt:" << p4.use_count() << endl;
    
    	// 检查拷贝赋值是否正常工作
    	p4 = p1;
    	cout << "p1 cnt:" << p1.use_count() << " p4 cnt:" << p4.use_count() << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89

    100. C++对象模型,虚继承

    https://blog.csdn.net/cmdxly/article/details/125488012

    101. 普通函数的实现底层

    https://blog.csdn.net/weixin_51431342/article/details/120358794

    102. 模板?

    https://blog.csdn.net/cmdxly/article/details/125794425?spm=1001.2014.3001.5501
    https://blog.csdn.net/cmdxly/article/details/125980866?spm=1001.2014.3001.5501
    https://blog.csdn.net/cmdxly/article/details/126295022?spm=1001.2014.3001.5501

    103. 面向对象特性?

    https://blog.csdn.net/weixin_44728238/article/details/116173026

    104. 讲一下hashMap底层实现和HashSet底层实现

    105. 泛型编程

    https://blog.csdn.net/liyazhen2011/article/details/82349890

    106. 实现一个不能实例对象的类

    #include
    using namespace std;
    
    //基类-因为纯虚函数,不能实例化对象
    class Base
    {
    public:
    	Base()
    	{
    		cout<<"===Base()==="<<endl;
    	}
    
    	//虚析构函数
    	virtual int fun() = 0;
    
    	~Base()
    	{
    		cout<<"===~Base()==="<<endl;
    	}
    };
    
    //子类
    class Son:public Base
    {
    public:
    	Son()
    	{
    		cout<<"===Son()==="<<endl;
    	}
    
    	int  fun()
    	{
    		cout<<"===fun()==="<<endl;
    		return 0;
    	}
    
    	~Son()
    	{
    		cout<<"===~Son()==="<<endl;
    	}
    };
    
     void test()
     {
    	Son s;
    	s.fun();
    	Base *b = &s;
    	b->fun();
     }
    
     int main()
     {
    	 test();
    	 return 0;
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    107. 智能指针(进阶)

    https://blog.csdn.net/qq_56673429/article/details/124837626

    108. 左值,右值引用和move语义(进阶)

    https://blog.csdn.net/fb_941219/article/details/118516925

    109. 类型转换方式(进阶)

    https://blog.csdn.net/GreedySnaker/article/details/112614456

    110. 线程安全的单例模式(进阶)

    https://blog.csdn.net/qq_34462436/article/details/100888303

    111. 内存溢出和内存泄漏(进阶)

    https://blog.csdn.net/weixin_52244492/article/details/124620561

    112. C++11新特性(进阶)

    113. 静态链接库和动态链接库(进阶)

    https://blog.csdn.net/oqqHuTu12345678/article/details/125083174

    114. C++传递参数的形式?

    C++中有三种方式来进行参数传递
    值传递,地址传递,引用传递
    值传递
    向函数传递参数的值,即把参数的值复制给函数的形式参数。
    这种情况下,修改函数内的形式参数,并不会影响到函数外的实际参数
    地址传递
    向函数传递参数的指针,即把参数的地址复制给形式参数。
    在函数内,该地址用于访问要用到的实际参数,这意味着修改形式参数会影响实际参数
    引用传递
    形参相当于是实参的“别名”,对形参的操作其实就是对实参的操作,在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

    115. C++异常底层的实现?

    https://blog.csdn.net/fcsfcsfcs/article/details/120106519

    116. const 常量修饰

    const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时
    继承它的优点。
    现在它的形式变成了:
    const DataType VariableName = VariableValue ;
    2) 具体作用
    1.const 用于指针的两种情况分析:
      int const *A;  //A可变,*A不可变
      int *const A;  //A不可变,*A可变
     分析:const 是一个左结合的类型修饰符,它与其左侧的类型修饰符和为一个
    类型修饰符,所以,int const 限定 *A,不限定A。int const 限定A,不限定A。
    2.const 限定函数的传递值参数:
      void Fun(const int Var);
      分析:上述写法限定参数在函数体中不可被改变。
    3.const 限定函数的值型返回值:
    const int Fun1();
    const MyClass Fun2();
      分析:上述写法限定函数的返回值不可被更新,当函数返回内部的类型时(如Fun1),已经是一个数值,当然不可被赋值更新,所以,此时const无意义,最好去掉,以免困惑。当函数返回自定义的类型时(如Fun2),这个类型仍然包含可以被赋值的变量成员,所以,此时有意义。
    4. 传递与返回地址: 此种情况最为常见,由地址变量的特点可知,适当使用const,意义昭然。
    5. const 限定类的成员函数:
    class ClassName {
     public:
      int Fun() const;
     …
    }
      注意:采用此种const 后置的形式是一种规定,亦为了不引起混淆。在此函数的声明中和定义中均要使用const,因为const已经成为类型信息的一部分。
    获得能力:可以操作常量对象。
    失去能力:不能修改类的数据成员,不能在函数中调用其他不是const的函数。

    117. 宏(#define)和内联函数(inline)的理解以及区别


    缺点:
    1.宏没有类型检测,不安全
    2.宏是在预处理时进行简单文本替换,并不是简单的参数传递(很难处理一些特定情况。例如:Add(z++))
    3.使代码变长
    4.宏不能进行调试
    5.当预处理搜索#define定义的符号时,字符串常量并不被搜索
    优点:
    1.加快了代码的运行效率
    2.让代码变得更加的通用
    内联函数
    类中的成员函数是默认的内联函数
    内联函数内不准许有循环语句和开关语句
    内联函数的定义必须出现在第一次调用内联函数之前

    缺点:代码变长,占用更多内存
    优点:1.有类型检测,更加的安全
    2.内联函数是在程序运行时展开,而且是进行的是参数传递
    3.编译器可以检测定义的内联函数是否满足要求,如果不满足就会当作普通函数调用(内联函数不能递归,内联函数不能太大)

    对比
    相同点:
    两者都是可以加快程序运行效率,使代码变得更加通用
    不同点:
    1.内联函数的调用是传参,宏定义只是简单的文本替换
    2.内联函数可以在程序运行时调用,宏定义是在程序编译进行
    3.内联函数有类型检测更加的安全,宏定义没有类型检测
    4.内联函数在运行时可调式,宏定义不可以
    5.内联函数可以访问类的成员变量,宏不可以
    6.类中的成员函数是默认的内联函数

  • 相关阅读:
    【附源码】计算机毕业设计SSM网上宠物店预约系统
    求二维子数组的和(剖析)
    Java开发2年,如今跳槽成功,入职阿里核心部门涨薪20k!
    【Verilog基础】一文搞懂线性反馈移位寄存器(LFSR)
    vscode代码快捷输入
    docker常见面试题
    一个非常简单的变分量子分类器 (VQC)
    【Linux operation 49】实现ubuntu-22.04.1允许root用户登录图形化桌面
    目标检测——食品饮料数据集
    java-php-python-ssm思政课程在线考试系统计算机毕业设计
  • 原文地址:https://blog.csdn.net/cmdxly/article/details/126764714