tplink面试准备

1. c++基础部分

最常见的问题:

  1. c++多态?如何实现?虚函数表的内容

  2. 指针与引用

  3. 关键字:static const define extern

  4. c++内存,分别存储什么类型,特点是什么

  5. 进程与线程 多线程同步

1. 为什么析构函数必须是虚函数?为什么C++默认的析构函数不是虚函数

将可能会被继承的父类的析构函数设置为虚函数,可以保证当我们new一个子类,然后使用基类指针指向该子类对象,释放基类指针时可以释放掉子类的空间,防止内存泄漏。

C++默认的析构函数不是虚函数是因为虚函数需要额外的虚函数表和虚表指针,占用额外的内存。而对于不会被继承的类来说,其析构函数如果是虚函数,就会浪费内存。因此C++默认的析构函数不是虚函数,而是只有当需要当作父类时,设置为虚函数

2. 内存泄漏是如何发生的?怎么查找?如何避免?

在c++的内存中,主要有:栈、堆、数据区、代码区
在程序运行过程中,主要发生问题的在堆和栈上,在程序运行过程中,栈内存由编译器自动管理,当分配一个新变量,或进入一个函数时,栈指针向下移,相当于分配一块内存,当变量声明周期结束后,编译器回收内存。由于栈上的内存的分配和回收都是由编译器控制的,所以在栈上是不会发生内存泄露的,只会发生栈溢出(Stack Overflow),也就是分配的空间超过了规定的栈大小。
而堆内存是由程序直接控制的,程序可以通过 malloc/free 或 new/delete 来分配和回收内存,如果程序中通过 malloc/new 分配了一块内存,但忘记使用 free/delete 来回收内存,就发生了内存泄露。
如何查找?
在项目中,内存泄漏的发生很明显,每次切换视图是内存增加1.5M,比较有规律。所以根据泄露发生时的调用堆栈分析,内存泄漏应该发生在组件的新建过程中,界面模块涉及道很多组件的new,全局遍历new到组件的地方,并追踪该组件在试用结束后是否delete。最后定位到时qt的折线图,每次切换视图就会新建折线图,而之前的折线图并没有回收。所以解决办法就是回收结束后的组件,解决内存泄漏。
如何避免内存泄漏?

  • 尽量避免在堆上分配内存
  • 尽可能使用智能指针shared_ptr
    智能指针可以自动删除分配的内存。智能指针和普通指针类似,只是不需要手动释放指针,而是通过智能指针自己管理内存的释放,这样就不用担心内存泄漏的问题了。
    c++提供了auto_ptr、unique_ptr、shared_ptr和weak_ptr这几种智能指针(auto_ptr是C++98提供的解决方案,C+11已将将其摒弃,并提供了另外两种解决方案。)在此我们只介绍后三个智能指针:
    (1)shared_ptr 共享的智能指针:
    shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
    注意事项:
    1.不要用一个原始指针初始化多个shared_ptr。
    2.不要再函数实参中创建shared_ptr,在调用函数之前先定义以及初始化它。
    3.不要将this指针作为shared_ptr返回出来。
    4.要避免循环引用。
    2)unique_ptr独占的智能指针:
    <1>Unique_ptr是一个独占的智能指针,他不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另外一个 unique_ptr。
    <2>unique_ptr不允许复制,但可以通过函数返回给其他的unique_ptr,还可以通过std::move来转移到其他的unique_ptr,这样它本身就不再 拥有原来指针的所有权了。
    <3>如果希望只有一个智能指针管理资源或管理数组就用unique_ptr,如果希望多个智能指针管理同一个资源就用shared_ptr。
    (3)weak_ptr弱引用的智能指针:
    弱引用的智能指针weak_ptr是用来监视shared_ptr的,不会使引用计数加一,它不管理shared_ptr内部的指针,主要是为了监视shared_ptr的生命 周期,更像是shared_ptr的一个助手。 weak_ptr没有重载运算符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中关连的资源是否存在。 weak_ptr还可以用来返回this指针和解决循环引用的问题。

3. extern的作用

extern用在变量或者函数的声明前,用来说明“此变量/函数是在别处定义的,要在此处引用”。extern声明不是定义,即不分配存储空间。也就是说,在一个文件中定义了变量和函数, 在其他文件中要使用它们, 可以有两种方式:

  • 使用头文件,然后声明它们,然后其他文件去包含头文件;
  • 在其他文件中直接extern。
    在项目中,main函数获取到的终端号等信息,需要在界面文件中使用,在界面文件中加上extern,就可以使用;

4. 隐式类型转换与显示类型转换;(c++定义的4个类型转换操作)

5. 设计模式是否了解,如工厂模式、装配者、适配器

6. 线程同步有哪些方法?那比如使用互斥量,什么情况会导致死锁;什么是死锁,如何预防?

https://www.cnblogs.com/yhlboke-1992/p/9315263.html
同步方式:事件、信号量、互斥量、临界区

  • 原因:系统资源不足;进程运行推进顺序不合适;资源分配不当

  • 条件:互斥;不剥夺;循环等待;请求与保持
    对应死锁产生的四个条件
    ① 互斥条件:一个资源一次只能被一个线程(进程)所使用
    ② 请求与保持条件:一个线程(进程)已占有一个资源,又请求别的资源,但请求的资源已被其他线程(进程)占用,此时请求被阻塞时,对已占有的资源保持不放
    ③ 不剥夺条件:线程(进程)已获得的资源,在未使用完之前不能被强行剥夺
    ④ 循环等待条件:若干线程(进程)形成一条首尾相连的循环等待资源关系。

  • 预防:破坏任意一个条件

  • 避免:银行家算法

  • 检测:资源分配图简化法

7. 抽象类与普通类有什么区别?抽象类与接口有什么区别?

抽象类和普通类主要有三点比较大的区别:
1.首先应该知道,抽象类是不能被实例化的,就是不能用new调出构造方法创建对象,而普通类则可以!
2.抽象类的访问权限限于Public和Protected,因为抽象类的方法是需要继承之后让子类去实现的,如果为Private,则无法被子类继承,子类也无法实现该方法
3.如果一个类继承于抽象类,则该子类必须实现父类的抽象方法。如果子类没有实现父类的抽象方法,则必须将子类也定义为abstract类。
抽象类与接口:
1.抽象类 和 接口 都是用来抽象具体对象的. 但是接口的抽象级别最高
2.抽象类可以有具体的方法 和属性, 接口只能有抽象方法和不可变常量
3.抽象类主要用来抽象类别,接口主要用来抽象功能.
先举个例子,方便理解,然后从例子中抽象概括出结理论。
比如,一家生产门的公司,需要先定义好门的模板,以便能快速生产出各种规格的门。
这里的模板通常会有两类模板:抽象类模板和接口模板。
抽象类模板:这个模板里面应该包含所有门都应该具有的共同属性(如,门的形状和颜色等)和共同行为(如,开门和关门)。
接口模板:有些门可能需要具有报警和指纹识别等功能,但这些功能又不是所有门必须具有的,所以像这样的行为应该放在单独的接口中。

有了上面的两类模板,以后生产门就很方便了:利用抽象类模板和包含了报警功能的接口模板就能生产具有报警功能的门了。同理,利用抽象类模板和包含了指纹识别功能的接口模板就能生产具有指纹识别功能的门了。

总之:抽象类用来抽象自然界一些具有相似性质和行为的对象。而接口用来抽象行为的标准和规范,用来告诉接口的实现者必要按照某种规范去完成某个功能。

8. i++与++i的区别

i++ :先引用后增加,先在i所在的表达式中使用i的当前值,后让i加1

++i :先增加后引用,让i先加1,然后在i所在的表达式中使用i的新值

9. STL中常用容器的实现(底层原理,以及底层代码)

10. 红黑树的特征是什么?红黑树如何插入和删除的?

11.重载,重写和隐藏的区别?

重载 Overload
表示同一个类中可以有多个名称相同的方法,但这些方法的参数列表各不相同(即参数个数或类型不同)。

重写 Override
表示子类中的方法可以与父类中的某个方法的名称和参数完全相同,通过子类创建的实例对象调用这个方法时,将调用子类中的定义方法,这相当于把父类中定义的那个完全相同的方法给覆盖了,这也是面向对象编程的多态性的一种表现。
隐藏:
是指派生类的函数屏蔽了与其同名的基类函数,注意只要同名函数,不管参数列表是否相同,基类函数都会被隐藏。
tplink面试准备_第1张图片

12. Malloc和new的区别?free和delete的区别?

New:
内存分配错误时,抛出bad_alloc异常,可以定义set_new_handler函数来在产生异常时进行处理;本身是一个运算符;分配内存的地方为自由存储区【为一个抽象概念】;对于对象而言,会先申请内存空间然后调用构造函数;无需指定大小

Malloc:
内存分配错误时,返回NULL;本身是一个库函数;分配内存的地方为堆;只申请内存空间;需要指定申请多大的内存;

Delete:
本身是一个运算符
Free:
本身是一个库函数

13. 内联函数

inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
内联函数和宏定义的区别
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关联上内联扩展,将与一般函数一样进行调用,比较方便。

另外,宏定义在使用时只是简单的文本替换,并没有做严格的参数检查,也就不能享受C++编译器严格类型检查的好处,另外它的返回值也不能被强制转换为可转换的合适的类型,这样,它的使用就存在着一系列的隐患和局限性。

C++的inline的提出就是为了完全取代宏定义,因为inline函数取消了宏定义的缺点,又很好地继承了宏定义的优点,《Effective C++》中就提到了尽量使用Inline替代宏定义的条款,足以说明inline的作用之大。

你可能感兴趣的:(面试准备)