C++相关问题总结

构成动态多态的条件:

多态存在的三个条件:
1.必须存在继承关系;
2.继承关系中必须有同名的虚函数,并且它们是覆盖关系(重载不行)。
3.存在基类的指针,通过该指针调用虚函数。
注意:派生类中的虚函数必须覆盖(不是重载)基类中的虚函数,才能通过基类指针访问。
隐藏、覆盖:
覆盖指的是子类覆盖父类函数(被覆盖),特征是:
1.分别位于子类和父类中
2.函数名字与参数都相同
3.父类的函数是虚函数(virtual)
隐藏指的是子类隐藏了父类的函数(还存在),具有以下特征:
子类的函数与父类的名称相同,但是参数不同,父类函数被隐藏
子类函数与父类函数的名称相同,参数也相同,但是父类函数没有virtual,父类函数被隐藏

关键字

const:

1.可以定义 const常量,将const常量放入符号表中
2.类型检查:const常量是有数据类型的,而#define没有,前者在编译时期可以进行类型检查,所以const更安全
3.可以保护被修饰的东西不被修改
4.可以作为函数重载的一个条件
5.可以节省空间,避免不必要的浪费:.const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝
6.提高效率:编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高    //注意::如果在编译器发现extern或者&,则会为该常量分配空间,but不会使用该内存内的值,只是为了兼容C语言
7.可调试性:#define不可调试,const可以调试

将Const类型转化为非Const类型的方法:
采用const_cast 进行转换。  
用法:const_cast <type_id>  (expression) 
该运算符用来修改类型的constvolatile属性,除了constvolatile修饰之外, type_id和expression的类型是一样的。

/*要大胆的使用const,这将给你带来无尽的益处,但前提是必须搞清楚原委
要避免最一般的赋值操作错误,如将const变量赋值
在参数中使用const应该使用引用或指针,而不是一般的对象实例
const在成员函数中的三种用法(参数、返回值、函数)要很好的使用
不要轻易的将函数的返回值类型定为const
除了重载操作符外一般不要将返回值类型定为对某个对象的const引用
任何不会修改数据成员的函数都应该声明为const 类型*/

static

1.隐藏性:所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问,加上static后对其他源文件隐藏,会减少命名冲突等问题
2.保持变量内容持久性:存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只不过和全局变量比起来,static可以控制变量的可见范围
//PS:如果作为static局部变量在函数内定义,它的生存期为整个源程序,但是其作用域仍与自动变量相同,只能在定义该变量的函数内使用该变量。退出该函数后, 尽管该变量还继续存在,但不能使用它
3.类中的static1>类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致 了它仅能访问类的静态数据和静态成员函数
2>不能将静态成员函数定义为虚函数:整个类共享而不是对象独享
3>静态数据成员是静态存储的,所以必须对它进行类外初始化(程序员手动初始化,否则编译时一般不会报错,但是在Link时会报错误)
4>为了防止父类的影响,可以在子类定义一个与父类相同的静态变量,以屏蔽父类的影响。这里有一点需要注意:我们说静态成员为父类和子类共享,但我们有重复定义了静态成员,但是并不会引起错误,我们的编译器采用了一种绝妙的手法:name-mangling 用以生成唯一的标志

sizeof

1.sizeof是计算栈上分配内存的大小
2.sizeof不计算static所占用内存大小
3.sizeof后如果是类型名则必须加括号,如果是变量名可以不加括号,这是因为sizeof是运算符
4.当使用结构类型或者变量时,sizeof返回实际的大小。当使用静态数组时返回数组的全部大小,sizeof不能返回动态数组或者外部数组的尺寸
5.strlen的区别:/*
1.sizoef是关键字,而strlen是函数
2.数组作sizeof的参数时不会退化为指针,而传递给strlen是就退化为指针
3.sizeof是编译时的常量,而strlen要到运行时才会计算出来,且是字符串中字符的个数而不是内存大小*/

易混淆

指针/数组/引用

1.数组要么在全局数据区被创建,要么在栈上被创建;而指针可以随时指向任意类型的内存块
2.指针可以指向堆上的内存,如果只是char *p = "ABCDEF";则是在静态区;不可更改;
3.int a[] = "ABCDEF"; &a是数组的首地址(代编整个数组) 数组名a作右值时,代表数组首元素的首地址,数组名不能作左值:不能整体访问数组;
&a + 1 = &a + sizeof(a)     ==>下一个数组的首地址
a  + 1 = &a + sizeof(a[0])  ==>下一个元素的首地址
/*
4.引用:C++中扩展的 const int &a = 1 ==>不合法,普通引用初始化时必须用同类型的变量,但是作参数时不用初始化(因为调用时用外部变量已经初始化了),但是:const int &a = 1 合法,因为会产生一个临时量;
PS:引用的本质是一个指针常量 int &a ==  int * const a;
引用也占内存空间  使用时会自动解引用 但是不可以多级引用
*/

内存补齐问题

合理的内存对齐能提高效率
X86上默认4字节对齐
对齐规则:
1.数据类型自身对齐:char 1 int 4
2.结构体对齐:成员中自身对齐值最大的那个
3.指定对齐值:#pramga pack(value)
4.有效对齐值:指定对齐值和自身对齐值中小的那个
5.补齐字节数

STL相关:

1>当vector的内存用完了,它是如何动态扩展内存的?它是怎么释放内存的?用clear可以释放掉内存吗?是不是线程安全的?
:vector内存用完了,会以当前size大小重新申请2*size的内存,然后把原来的元素复制过去,把新元素插上,然后释放原来的内存。一般我们释放vector里的元素使用clear,其实它不能释放内存,要想释放内存要使用swap
《effective stl》的第十二条:当涉及 STL容器和线程安全性时,你可以指望一个 STL库允许多个线程同时读一个容器,以及多个线程对不同的容器做写入操作。你不能指望 STL库会把你从手工同步控制中解脱出来,而且你不能依赖于任何线程支持。必须自己去写多线程安全措施

2>map能不能边遍历边删除?
不能,因为MAP不会earse后不会返回一个迭代器

3>vector每次insert或erase之后,以前保存的iterator会不会失效?
理论上会失效,理论上每次insert或者erase之后,所有的迭代器就重新计算的,所以都可以看作会失效,原则上是不能使用过期的内存
看情况:插入后会不会重新分配  删除后的迭代器位置

4>hash_map和map的区别在哪里?
hash_map底层是散列的所以理论上操作的平均复杂度是常数时间,map底层是红黑树,理论上平均复杂度是O(logn)
这里总结一下,选用map还是hash_map,如果是要很多次操作,要求其整体效率,那么使用hash_map,平均处理时间短。如果是少数次的操作,使用 hash_map可能造成不确定的O(N),那么使用平均处理时间相对较慢、单次处理时间恒定的map

5>vector :
resize()函数和容器的size息息相关。调用resize(n)后,容器的size即为n。
至于是否影响capacity,取决于调整后的容器的size是否大于capacity。
reserve()函数和容器的capacity息息相关。调用reserve(n)后,若容器的capacity<n,则重新分配内存空间,从而使得capacity等于n。
如果capacity>=n呢?capacity无变化

6>什么情况下用vector,什么情况下用list
vector可以随机存储元素(即可以通过公式直接计算出元素地址,而不需要挨个查找)但在非尾部插入删除数据时,效率很低,适合对象简单,对象数量变化不大,随机访问频繁
list不支持随机存储,适用于对象大,对象数量变化频繁,插入和删除频繁

7>dequevector的区别。
1vector是单向开口的连续线性空间,deque是双向开口的连续线性空间。(双向开口是指可以在头尾两端分别做元素的插入和删除操作)
2deque没有提供空间保留功能,而vector则要提供空间保留功能
3deque也提供随机访问迭代器,但是其迭代器比vector迭代器复杂很多

8>STL的底层数据结构实现
1vector:底层数据结构为数组,支持快速随机访问
2list:底层数据结构为双向链表,支持快速增删
3deque:底层数据结构为一个中央控制器和多个缓冲区,支持首尾(中间不能)快速增删,支持随机访问
4stack:底层用deque或者list实现,不用vector的原因是扩容耗时
5queue:底层用deque或者list实现,不用vector的原因是扩容耗时
6)priority_queue:底层数据结构一般以vector为底层容器,heap为处理规则来管理底层容器实现
7set:底层数据结构为红黑树,有序,不重复
8multiset:底层数据结构为红黑树,有序,可重复
9map:底层数据结构为红黑树,有序,不重复
10multimap:底层数据结构为红黑树,有序,可重复
11)hash_set:底层数据结构为hash表,无序,不重复
12)hash_map:底层数据结构为hash表,无序,不重复
13)hashtable:底层数据结构是vector

面试题:

1>STL中sort用的什么算法:毫无疑问是用到了快速排序,但不仅仅只用了快速排序,还结合了插入排序和堆排序::
/*
数据量大时采用QuickSort快排算法,分段归并排序。一旦分段后的数据量小于某个门槛(16),为避免QuickSort快排的递归调用带来过大的额外负荷,就改用Insertion Sort插入排序。如果递归层次过深,还会改用HeapSort堆排序。
*/

2>局部变量能否和全局变量重名?
可以,但是局部会屏蔽全局。要用全局变量,需要使用域作用符“::”

3>类的成员函数重载、覆盖和隐藏的概念和区别?概念:重载是指再同一个作用域内,有几个同名的函数,但是参数列表的个数和类型不同。 函数覆盖是指派生类函数覆盖基类函数,函数名、参数类型、返回值类型一模一样。派生类的对象会调用子类中的覆盖版本,覆盖父类中的函数版本。 “隐藏”是指派生类的函数屏蔽了与其同名的基类函数。覆盖和重载的区别:函数是否处在不同的作用域,参数列表是否一样;基类函数是否有virtual关键字

4>析构函数能抛出异常吗
肯定是不能。 C++标准指明析构函数不能、也不应该抛出异常。C++异常处理模型最大的特点和优势就是对C++中的面向对象提供了最强大的无缝支持。那么如果对象在运行期间出现了异常,C++异常处理模型有责任清除那些由于出现异常所导致的已经失效了的对象(也即对象超出了它原来的作用域),并释放对象原来所分配的资源, 这就是调用这些对象的析构函数来完成释放资源的任务,所以从这个意义上说,析构函数已经变成了异常处理的一部分

5>永远不要在构造函数或析构函数中调用虚函数?
在子类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的,因为还没有vfptr;一旦一个派生类的析构器运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理

智能指针

1.为什么引入智能指针?
A:智能指针就是一个在栈上的特有对象管理堆上的对应内存,当栈上的对象析构时,会释放掉堆上的内存,防止内存泄漏!==>并非仅因为程序员的疏忽而忘记释放内存,也有可能是其他原因:比如说析构函数前就发生了异常,那么这个析构函数就没有完成导致内存泄漏

2.类别:
scoped_ptr包装了new在堆上的动态对象,能保证对象能在任何时候都被正确的删除。scoped_ptr的所有权更严格,不能转让
unique_ptr不仅可以代理new创建的单个对象,也可以代理new[]创建的数组对象,就是说它结合了scoped_ptr和scoped_array两者的能力,unique_ptr的基本能力跟scoped_ptr一样,同样可以在作用域内管理指针,也不允许拷贝和赋值
shared_ptr是最像指针的智能指针。shared_ptr和scoped_ptr一样包装了new在堆上的动态对象,也不可以管理new[]产生的数组指针,也没有指针算术操作。但是shared_ptr实现了引用计数,可以自由拷贝赋值,在任意地方共享它,当没有代码使用它(引用计数为0)时,它才删除被包装的动态对象。shared_ptr可以被放在标准容器中,STL容器存储指针的标准做法
weak_ptr是为了配合shared_ptr而引入的,它没有普通指针的行为,没重载*和->。它最大的作用是协助shared_ptr工作,像旁观者一样观测资源的使用情况。weak_ptrk可以从一个shared_ptr或者一个weak_ptr对象构造,获得对象资源观测权。但是weak_ptr并没有共享资源,它的构造不会引起引用计数的增加,也不会让引用计数减少,它只是静静的观察者

3.shared_prt的线程安全:
借 shared_ptr 来实现线程安全的对象释放,但是 shared_ptr 本身不是 100% 线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化

//shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值