C++软件开发工程师概念手册 干货

1. 面向对象的三个特性

  • 封装性封装是面向对象编程的核心思想,将对象的属性和行为封装起来,而将对象的属性和行为封装起来的载体是类,类通常对用户隐藏其实现的细节,这就是封装的思想。采用封装的思想保证了类内部数据结构的完整性,应用该类的用户不能轻易直接操纵该数据结构,而只能执行该类允许公开的数据。这样可以避免外部对内部数据的影响,提高程序的可维护性。
  • 多态性多态性指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。a.编译时多态性:通过重载函数实现b 运行时多态性:通过虚函数实现。
  • c++的多态(重载、覆盖、隐藏)

  • 继承性
    继承就是新类从已有类那里得到已有的特性。 类的派生指的是从已有类产生新类的过程。原有的类成为基类或父类,产生的新类称为派生类或子类,子类继承基类后,可以创建子类对象来调用基类函数,变量等。
  • 2. 面向对象的SOLID原则

    S.O.L.I.D是面向对象设计和编程(OOD&OOP)中几个重要编码原则(Programming Priciple)的首字母缩写。

  • 单一责任原则(SRP)当需要修改某个类的时候原因有且只有一个(THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE)。换句话说就是让一个类只做一种类型责任,当这个类需要承当其他类型的责任的时候,就需要分解这个类。
  • 开放封闭原则(OCP)软件实体应该是可扩展,而不可修改的。也就是说,对扩展是开放的,而对修改是封闭的。这个原则是诸多面向对象编程原则中最抽象、最难理解的一个。
  • 里氏替换原则(LSP)当一个子类的实例应该能够替换任何其超类的实例时,它们之间才具有is-A关系
  • 依赖倒置原则(DIP)
    1. 高层模块不应该依赖于低层模块,二者都应该依赖于抽象
    2. 抽象不应该依赖于细节,细节应该依赖于抽象
  • 接口分离原则(ISP)
    不能强迫用户去依赖那些他们不使用的接口。换句话说,使用多个专门的接口比使用单一的总接口总要好。
  • 3. 构造函数与析构函数

    子类构造、析构时调用父类的构造、析构函数顺序析构函数调用的次序是先派生类的析构后基类的析构,也就是说在基类的的析构调用的时候,派生类的信息已经全部销毁了。

    而定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数;析构的时候恰好相反:先调用派生类的析构函数、然后调用基类的析构函数。

    原因:

    派生类构造函数中的某些初始化可能是基于基类的,所以规定构造在类层次的最根处开始,而在每一层,首先调用基类构造函数,然后调用成员(此处的成员只指各种类对象如QString a,不含基本类型变量如int n、指针变量如QString *a)对象构造函数(因为C++的成员变量是不会自动初始化的,只能使用初始化列表初始化(调用成员的构造函数,如果不在初始化列表中显式调用的话,则会隐式调用成员变量的默认构造函数,通过汇编可以发现)或在本层构造函数内初始化) 参考:
    http://www.cnblogs.com/lidabo/p/3790606.html)。

    如果没有显式调用基类的构造函数,会自动调用基类的无参构造函数。而如果基类只有带参数的构造函数,则会 错。不一定要显式的无参构造函数,可以显式调用基类带参数的构造函数。

    4. 一个例子彻底搞懂C++的虚函数和纯虚函数

    1. 在类成员方法的声明(不是定义)语句前面加个单词:virtual,她就会摇身一变成为虚函数
    2. 在虚函数的声明语句末尾中加个 =0 ,她就会摇身一变成为纯虚函数
    3. 子类可以重新定义基类的虚函数,我们把这个行为称之为复写(override)
    4. 不管是虚函数还是纯虚函数,基类都可以为他们提供实现(implementation),如果有的话子类可以调用基类的这些实现;
    5. 子类可自主选择是否要提供一份属于自己的个性化虚函数实现;
    6. 子类必须提供一份属于自己的个性化纯虚函数实现。
    7. 虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。

    1. 虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。
    2. 虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。
    3. 虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。
    4. 虚函数的定义形式:virtual {method body}
      纯虚函数的定义形式:virtual { } = 0;
      在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。
    5. 虚函数必须实现,如果不实现,编译器将 错,错误提示为:error LNK****: unresolved external symbol “public: virtual void __thiscallClassName::virtualFunctionName(void)”
    6. 对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
    7. 实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
    8. 虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数
    9. 如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。

    5. 野指针 指针指向了一块随机的空间,不受程序控制

    1) 野指针产生的原因

  • 指针定义时未被初始化:指针在被定义的时候,如果程序不对其进行初始化的话,它会随机指向一个区域,因为任意指针变量(出了static修饰的指针)它的默认值都是随机的
  • 指针被释放时没有置空:我们在用malloc()开辟空间的时候,要检查返回值是否为空,如果为空,则开辟失败;如果不为空,则指针指向的是开辟的内存空间的首地址。指针指向的内存空间在用free()和delete释放后,如果程序员没有对其进行置空或者其他赋值操作的话,就会成为一个野指针
  • 指针操作超越变量作用域:不要返回指向栈内存的指针或者引用,因为栈内存在函数结束的时候会被释放。
  • 2) 野指针与空指针的区别

  • 野指针:访问一个已销毁或者访问受限的内存区域的指针,野指针不能判断是否为NULL来避免
  • 垂悬指针:指针正常初始化,曾指向一个对象,该对象被销毁了,但是指针未制空,那么就成了悬空指针。
  • 3) 野指针的危害问题:指针指向的内容已经无效了,而指针没有被置空,解引用一个非空的无效指针是一个未被定义的行为,也就是说不一定导致错误,野指针被定位到是哪里出现问题,在哪里指针就失效了,不好查找错误的原因。

    4)规避野指针的方法(1)在定义一个指针时同时初始化为NULL;例:int *p=NULL;

    (2)释放指针指向的内存空间时,将指针重置为NULL。(最好在编写代码时将free()函数封装一下,在调用free()后就将指针置为NULL。)例:

    
    

    5. 引用详解:关于C语言中的引用的使用方法 (墙裂推荐)

    扩展:指针和引用的定义和性质区别:(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:

    
    

    上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
    而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。

    (2)可以有const指针,但是没有const引用;

    (3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

    (4)指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;

    (5)指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了。

    (6)”sizeof引用”得到的是所指向的变量(对象)的大小,而”sizeof指针”得到的是指针本身的大小;

    (7)指针和引用的自增(++)运算意义不一样;

    6. C++覆盖方法和重载方法

    方法的覆盖和重载具有以下相同点

  • 都要求方法同名
  • 都可以用于抽象方法和非抽象方法之间
  • 方法的覆盖和重载具有以下不同点

  • 方法覆盖要求参数列表(参数签名)必须一致,而方法重载要求参数列表必须不一致。
  • 方法覆盖要求返回类型必须一致,方法重载对此没有要求。
  • 方法覆盖只能用于子类覆盖父类的方法,方法重载用于同一个类中的所有方法(包括从父类中继承而来的方法)
  • 方法覆盖对方法的访问权限和抛出的异常有特殊的要求,而方法重载在这方面没有任何限制。
  • 父类的一个方法只能被子类覆盖一次,而一个方法可以在所有的类中可以被重载多次。
  • 7. 关于静态链接库与动态链接库的区别和实例

    所谓静态链接库(Static Link Library),是在编译的链接阶段将库函数嵌入到应用程序的内部。但是如果多次调用,则该库函数会被调用多次,会极大的造成空间浪费以及链接器的负担(缺点)。它的优势在于,应用程序可以独立运行,因为在静态链接的时候已经将所需的组件都已经加载到了该应用程序中,不需要对应的DLL,但是应用程序比较大。

    所谓动态链接库(Dynamic link library),与静态链接方式不同的是,它会将公用的库函数以及相关组件信息存放在一个地方,只是将地址信息告诉了链接器,只有在应用程序调用了该动态库之后才会加载到内存。其缺点是应用程序不能独立运行,需要在操作系统中安装对应的DLL以及运行环境。优点是生成的可执行文件很小。

    动态链接库文件名的扩展名一般是dll,也有可能是drv,sys和fon,它和可执行文件(exe)非常类似,区别在于动态链接库中虽然包含了可执行代码却不能单独执行,而应由应用程序直接或间接调用。

    8. malloc 和 new

    1 属性new和delete是C++关键字,需要编译器支持;malloc和free是库函数,需要头文件支持。

    2 参数使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸

    3 返回类型new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

    4 自定义类型new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。

    malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。

    new可以调用malloc(),但malloc不能调用new。

    5 重载C++允许重载new/delete操作符,malloc不允许重载。

    6 内存区域new做两件事:分配内存和调用类的构造函数,delete是:调用类的析构函数和释放内存。而malloc和free只是分配和释放内存。

    new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。

    7 分配失败new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。

    8 内存泄漏内存泄漏对于new和malloc都能检测出来,而new可以指明是哪个文件的哪一行,malloc确不可以。

    9. typdef和define区别

    1. typedef的用法typedef常用来定义一个标识符及关键字的别名,typedef可以增强程序的可读性,以及标识符的灵活性,但它也有“非直观性”等缺点。
    2. #define宏作用在预处理阶段,因此只是简单的文本替换,宏没有分配内存空间,宏没有类型检查,不能调试。
    3. const宏发生在预处理阶段,属于直接替换,没有分配内存空间,const分配空间;宏没有类型检查,而且不能调试,所以c++中常用const替换。

    总结:从以上的概念便也能基本清楚,typedef只是为了增加可读性而为标识符另起的新名称(仅仅只是个别名),而#define原本在C中是为了定义常量,到了C++,const、enum、inline的出现使它也渐渐成为了起别名的工具。

    10. 内存分配——静态存储区 栈 堆 与static变量

    栈, 就是那些由编译器在需要的时候分配,在不需要的时候自动清除的变量的存储区。里面的变量通常是局部变量、函数参数等。在一个进程中,位于用户虚拟地址空间顶部的是用户栈,编译器用它来实现函数的调用。和堆一样,用户栈在程序执行期间可以动态地扩展和收缩。

    堆, 就是那些由 new 分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个 new 就要对应一个 delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。堆可以动态地扩展和收缩。

    自由存储区, 就是那些由 malloc 等分配的内存块,他和堆是十分相似的,不过它是用 free 来结束自己的生命的。

    全局/静态存储区, 全局变量和静态变量被分配到同一块内存中,在以前的 C 语言中,全局变量又分为初始化的和未初始化的(初始化的全局变量和静态变量在一块区域,未初始化的全局变量与静态变量在相邻的另一块区域,同时未被初始化的对象存储区可以通过 void* 来访问和操纵,程序结束后由系统自行释放),在 C++ 里面没有这个区分了,他们共同占用同一块内存区。

    常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)

    推荐阅读:

    [1] 数据结构与算法 | 你知道快速排序,那你知道它的衍生应用吗?Partition函数

    [2] 数据结构与算法 | 数据结构中到底有多少种“树”?一文告诉你

    [3] 数据结构与算法 | 二分查找:剑指offer53 在排序数组中查找数字

    [4] 2020字节跳动秋招笔试题解析与代码分享(持续更新中)

    知识星球: 群旨在分享AI算法岗的秋招/春招准备攻略(含刷题)、面经和内推机会、学习路线、知识题库等。

    △扫码加入「迈微电子研发 」学习辅导群

    声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!

    上一篇 2020年4月20日
    下一篇 2020年4月20日

    相关推荐