最常见的问题:
c++多态?如何实现?虚函数表的内容
指针与引用
关键字:static const define extern
c++内存,分别存储什么类型,特点是什么
进程与线程 多线程同步
将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。
C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数。
在c++的内存中,主要有:栈、堆、数据区、代码区
在程序运行过程中,主要发生问题的在堆和栈上,在程序运行过程中,栈内存由编译器自动管理,当分配一个新变量,或进入一个函数时,栈指针向下移,相当于分配一块内存,当变量声明周期结束后,编译器回收内存。由于栈上的内存的分配和回收都是由编译器控制的,所以在栈上是不会发生内存泄露的,只会发生栈溢出(Stack Overflow),也就是分配的空间超过了规定的栈大小。
而堆内存是由程序直接控制的,程序可以通过 malloc/free 或 new/delete 来分配和回收内存,如果程序中通过 malloc/new 分配了一块内存,但忘记使用 free/delete 来回收内存,就发生了内存泄露。
如何查找?
在项目中,内存泄漏的发生很明显,每次切换视图是内存增加1.5M,比较有规律。所以根据泄露发生时的调用堆栈分析,内存泄漏应该发生在组件的新建过程中,界面模块涉及道很多组件的new,全局遍历new到组件的地方,并追踪该组件在试用结束后是否delete。最后定位到时qt的折线图,每次切换视图就会新建折线图,而之前的折线图并没有回收。所以解决办法就是回收结束后的组件,解决内存泄漏。
如何避免内存泄漏?
extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。也就是说,在一个文件中定义了变量和函数, 在其他文件中要使用它们, 可以有两种方式:
https://www.cnblogs.com/yhlboke-1992/p/9315263.html
同步方式:事件、信号量、互斥量、临界区
原因:系统资源不足;进程运行推进顺序不合适;资源分配不当
条件:互斥;不剥夺;循环等待;请求与保持
对应死锁产生的四个条件
① 互斥条件:一个资源一次只能被一个线程(进程)所使用
② 请求与保持条件:一个线程(进程)已占有一个资源,又请求别的资源,但请求的资源已被其他线程(进程)占用,此时请求被阻塞时,对已占有的资源保持不放
③ 不剥夺条件:线程(进程)已获得的资源,在未使用完之前不能被强行剥夺
④ 循环等待条件:若干线程(进程)形成一条首尾相连的循环等待资源关系。
预防:破坏任意一个条件
避免:银行家算法
检测:资源分配图简化法
抽象类和普通类主要有三点比较大的区别:
1.首先应该知道,抽象类是不能被实例化的,就是不能用new调出构造方法创建对象,而普通类则可以!
2.抽象类的访问权限限于Public和Protected,因为抽象类的方法是需要继承之后让子类去实现的,如果为Private,则无法被子类继承,子类也无法实现该方法
3.如果一个类继承于抽象类,则该子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract类。
抽象类与接口:
1.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
2.抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
3.抽象类主要用来抽象类别,接口主要用来抽象功能.
先举个例子,方便理解,然后从例子中抽象概括出结理论。
比如,一家生产门的公司,需要先定义好门的模板,以便能快速生产出各种规格的门。
这里的模板通常会有两类模板:抽象类模板和接口模板。
抽象类模板:这个模板里面应该包含所有门都应该具有的共同属性(如,门的形状和颜色等)和共同行为(如,开门和关门)。
接口模板:有些门可能需要具有报警和指纹识别等功能,但这些功能又不是所有门必须具有的,所以像这样的行为应该放在单独的接口中。
有了上面的两类模板,以后生产门就很方便了:利用抽象类模板和包含了报警功能的接口模板就能生产具有报警功能的门了。同理,利用抽象类模板和包含了指纹识别功能的接口模板就能生产具有指纹识别功能的门了。
总之:抽象类用来抽象自然界一些具有相似性质和行为的对象。而接口用来抽象行为的标准和规范,用来告诉接口的实现者必要按照某种规范去完成某个功能。
i++ :先引用后增加,先在i所在的表达式中使用i的当前值,后让i加1
++i :先增加后引用,让i先加1,然后在i所在的表达式中使用i的新值
重载 Overload
表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。
重写 Override
表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。
隐藏:
是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
New:
内存分配错误时,抛出bad_alloc异常,可以定义set_new_handler函数来在产生异常时进行处理;本身是一个运算符;分配内存的地方为自由存储区【为一个抽象概念】;对于对象而言,会先申请内存空间然后调用构造函数;无需指定大小
Malloc:
内存分配错误时,返回NULL;本身是一个库函数;分配内存的地方为堆;只申请内存空间;需要指定申请多大的内存;
Delete:
本身是一个运算符
Free:
本身是一个库函数
inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
内联函数和宏定义的区别
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关联上内联扩展,将与一般函数一样进行调用,比较方便。
另外,宏定义在使用时只是简单的文本替换,并没有做严格的参数检查,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。
C++的inline的提出就是为了完全取代宏定义,因为inline函数取消了宏定义的缺点,又很好地继承了宏定义的优点,《Effective C++》中就提到了尽量使用Inline替代宏定义的条款,足以说明inline的作用之大。