C++ 总结

来源:互联网 发布:官网淘宝店铺装修模板 编辑:程序博客网 时间:2024/05/07 22:16

整理自传智扫地僧教学视频


OOP: 封装 继承 多态


C++对C语言的增强

  • register关键字。请求将变量储存在寄存器中。C语言中register关键字修饰的变量不能取地址。C++中支持register关键字,当需要取register修饰的变量的地址时,register对变量的声明变得无效。另外不使用该关键字也可以对代码进行优化。
  • C语言中同名的全局变量最终会被链接到全局数据区的同一个地址。C++不允许重定义全局变量。
  • C语言中struct定义了一组变量的集合,编译器不认为是一种新的数据类型。C++中的struct是一种新类型的声明。与class关键字的区别
  • 三目运算符。 C语言中返回的是一个值,不能做左值;C++中返回的是变量自身,可以作为左值。C语言中可以返回地址,通过间接赋值来实现

C/C++ 中的 const

  • C中的const,只读变量,有自己的存储空间,但可通过指针间接修改!
  • C++中的const,不一定分配存储空间。当遇到const声明时,以键值对的形式将变量和值储存在符号表中。
    • 当对变量取地址时,会为变量开辟存储空间,与符号表中的变量无关。
    • 当const常量为全局,并且需要在其它文件中使用时,也会分配存储空间。
#include<iostream>using namespace std;int main(){    const int a = 10;    int *p = (int *)&a;    *p = 20;    cout << a << endl;    cout << *p << endl;    return 0;}
  • 与#define的区别
    • const:编译器处理。提供类型检查和作用域检查。
    • #define:预处理器处理,单纯的文本替换。(#undef 卸载宏定义)
  • const修饰成员函数
    • 修饰的是this指针所指的对象, this指针本身是const
int fun() const // int fun(const ClassName *const pThis, ...){    ...}

引用

  • 变量名的本质是一段连续存储空间的别名,引用即为一段连续的存储空间取其他别名。普通引用必须要初始化。
  • 引用的本质: 常量指针。 type &name <–> type *const name
  • 函数返回值是引用(static或全局变量,不要返回局部变量的引用)
    • 用变量接,相当于按值传递
    • 函数当左值
  • 指针的引用 type * & name
  • 常引用 让变量拥有只读属性

    • 用变量初始化
    int x = 1;const int &y = x;// y只读,不能通过y修改x
    • 用字面量初始化
    int &x = 1; // 错 字面量没有内存地址const int &x = 1; //可以(const和&)分配一个存储空间,存放字面量的一个副本,引用作为这段空间的别名。
  • 成员函数返回引用(返回对象自身) return *this;

C++对C的函数扩展

  • 内联函数 关键字:inline; 替代宏代码片段(#define…)
    • inline必须和函数实现写在一起
    • 内联函数没有普通函数调用时的额外开销,如:入栈、出栈、返回
    • 内联函数是对编译器的一种请求。内联函数由编译器处理,宏代码替换由预处理器处理。
  • 默认参数
    • 默认参数必须全在形参列表的右边
    • 仅出现在函数声明中
  • 占位参数
  • 函数重载
    • 函数的返回值不是判断函数重载的标准。
    • 函数重载遇到默认参数 二义性,编译不能通过

类的封装 & 访问控制

  • 类是一种数据类型(固定大小内存块的别名),定义类时不分配内存。通过类定义变量时才分配内存
  • 关键字
    • private 修饰的成员变量和函数,只能在该类的内部访问(是类的默认属性
    • protected 修饰的成员变量和函数,只能在该类及其子类的内部访问, 用在继承中,将访问的范围扩展至子类的内部
    • public 修饰的成员变量和函数,可以在类的内部和外部访问
  • 与struct的区别
    • struct 默认属性是public
  • C++中类的实现

    • 成员变量和成员函数分开存储
      • 非静态成员变量: 存储在对象中 同struct
      • 静态成员变量: 存储在全局区中
    • 成员函数
      • 存储在代码段中
    • 成员函数如何识别具体对象? this指针

      • 非静态成员函数 隐式包含一个指向当前对象的this指针

        eg: int fun(int val) –> int fun(ClassName *const pThis, int val)

      • 静态成员函数 不包含指向具体对象的this指针

        eg: static int fun() –> int fun()

  • 友元函数 & 友元类

    • 友元函数是全局函数,不是类的成员函数。可以修改类的私有成员。破坏了类的封装。
    • B是A的友元类,则B的所有成员函数都是A的友元函数,可以修改A类的私有成员。

静态成员 & 静态成员函数 static

  • 静态成员提供了一种同类对象的共享机制。静态成员局部于类,不是对象成员。对象所占的内存空间不包括静态成员。
  • 静态成员的初始化: 在外部进行初始化。静态成员不属于类的任何一个对象,不是在创建对象时被定义,因此不是由类的构造函数初始化的。(例外:const整数类型的初始值,可以在类内初始化。P270
  • 静态成员函数
    • 调用方法
      • ObjectName.FunctionName();
      • ClassName::FunctionName();
    • 静态成员函数中不能使用非静态成员,不能调用非静态成员函数。 非静态成员属于具体的一个对象。

构造函数 & 析构函数

  • 构造函数 (仅用于初始化对象,在’=’号右边出现的构造函数是初始化临时变量然后进行赋值。)
    • 语法相关
      • 函数名与类名相同
      • 可以含参数,无任何返回类型的声明
      • 自动调用或显示调用。
      • 构造函数私有,单例
    • 构造函数的分类
      • 无参构造函数 (只有当未定义任何一个构造函数时,编译器才提供默认的无参构造函数
        • 应用场景: ClassName ObjectName;
      • 有参构造函数
        • 普通构造函数
        • 拷贝构造函数 (若未定义,编译器提供默认的拷贝构造函数, 浅拷贝。
          • 第一个参数是自身类类型的引用(通常为const引用,临时对象不能赋给非const引用),且任何额外参数都有默认值。
          • 实参初始化形参(按值传递),会调用拷贝构造函数创建临时对象。
          • 函数返回对象时,也会调用拷贝构造函数创建临时对象,原返回对象被析构掉。
    • 临时对象的去留问题在构造函数中调用其它构造函数是个危险的行为
      • 临时对象不使用,自动被析构。
      • 用临时对象初始化对象时,临时对象直接转成要初始化的对象,不会被析构,在这个过程中不调用拷贝构造函数。(赋值不调用拷贝构造函数,只有初始化对象才调用构造函数,只有通过一个对象初始化另一个对象时才调用拷贝构造函数
      • 通过构造函数进行赋值而非初始化时,临时对象会被析构。
    • 构造函数的调用方法
      • 无参:
        • 隐式调用: ClassName ObjectName; 不能这样用: ClassName ObjectName();函数声明
        • 显式调用: ClassName ObjectName = ClassName();
      • 有参
        • 隐式调用: ClassName ObjectName(参数列表);
        • 显示调用: ClassName ObjectName = ClassName(参数列表)
        • 拷贝构造函数还可以: ClassName ObjectName = ObjectName2; ObjectName2为已初始化的对象。
    • 构造函数的初始化列表
      • 必须使用初始化列表的场景
        • 场景1: 类成员本身是一个类或结构,但它没有无参构造函数。
        • 场景2: 类中包含const成员,const成员必须通过初始化列表的方式进行初始化。
  • 浅拷贝

    • 编译器提供的默认拷贝构造函数
    • 编译器提供的默认’=’操作
    • 使用memcpy()函数,当拷贝的数据中包含指针时,隐含着浅拷贝!!!
      • 场景:定义一个容器,当其中存放自定义的类对象时,若对象中包含着指针,存在动态内存分配。此时在拷贝构造函数或重载’=’操作符函数中使用memcpy()函数,造成浅拷贝。最好通过for循环,显式的通过自定义类中重载的’=’操作符进行拷贝赋值。参见自定义的myVector类。
  • 析构函数 释放对象

    • 语法相关
      • ~ClassName();
      • 不含参数,也没有任何返回类型的声明
      • 对象销毁时自动被调用

内存的动态分配 & 释放

  • malloc()、free() 函数
  • new、delete 操作符 (C++建议)

    基础类型/类对象
    type *p = new type(表达式); 表达式结果为初始化值, (表达式)可缺省
    delete p;
    数组
    type *p = new type[表达式];
    delete [] p;

  • new/delete与malloc/free的区别: 分配类对象时,new可以自动调用构造函数,delete可以自动调用析构函数

    • 自定义类型的数据结构 (struct, class),在使用new时, new 后跟构造函数

运算符重载 operator

  • 实现方法: 写出函数实现,将函数名替换为 operator* (*代表要重载的运算符)
    • 友元函数重载
      • 二元操作符: 左操作数为第一个形参,右操作数为第二个形参。
      • 一元操作符: 操作数由形参提供。
    • 成员函数重载
      • 二元操作符: 左操作数是调用对象(通过this指针传递),右操作数由形参提供。
      • 一元操作符: 操作数由对象通过this指针传递。
  • 一元运算符区分前置和后置
    • 后置,形参列表中加一个int占位参数
  • 运算符重载的本质是函数调用。
  • 重载方法的选择
    • 优先选择通过成员函数重载。(不要滥用友元函数)
    • 友元函数重载通常用于左右操作数类型不一样的场景。
    • 不能通过友元函数重载的运算符: = () [] ->
  • 尽量不要重载 && 和 ||
    • &&、||内置短路规则, 而操作符重载是通过函数重载实现的, 不能实现短路规则
  • 不能重载的操作符: ‘.’, ‘::’, ‘.*’, ‘?:’, ‘sizeof’

继承

  • 类和类之间的关系
    • has-a: 包含关系
    • use-a: 一个类部分的使用另一个类
    • is-a: 继承
      • 基类: 父类; 派生类: 子类
      • 单继承、多继承
  • 派生类的定义

    class 派生类名: 基类名表
    {

    };
    基类名表的构成:
    访问控制 基类名1, 访问控制 基类名2, …

  • 继承的特性
    • 子类拥有父类的成员变量和成员函数(除了构造和析构),还拥有父类没有的方法和属性。
    • 子类是一种特殊的父类, 子类对象可以当父类对象使用。
  • 派生类的访问控制
    • 影响因素
      • 父类的访问控制
      • 子类的继承方式
      • 语句写在子类的内部还是外部
    • 在子类的内部能否访问成员取决于父类中成员的访问控制属性,与继承方式无关。
    • 在子类的外部(通过子类对象)能否访问成员取决于父类的访问控制和子类的继承方式
      • public继承, 不改变成员的访问控制属性。
      • projected继承,父类中的public和projected在子类中是projected.父类的private在子类中仍是private.
      • private继承, 子类中继承的成员全为private。
  • 类型兼容性原则
    • 对象
      • 子类对象可以当作父类对象使用
      • 子类对象可以直接赋值给父类对象。
      • 子类对象可以直接初始化父类对象。
    • 指针 & 引用
      • 父类指针可以指向子类对象。
      • 父类引用可以直接引用子类对象。
  • 继承中的对象模型
    • 构造
      • 创建子类对象时, 先调用父类构造函数对继承的成员进行初始化,再调用子类构造函数。
        • 当父类的构造函数有参数时,需要在子类的初始化列表中显式调用。
    • 析构
      • 析构子类对象时, 先调用子类析构函数,再调用父类析构函数对继承的成员进行清理。
  • 继承和组合混合 (子类中包含其他类对象)
    • 构造
      • 先构造父类,再构造成员变量(子类中的其他类对象),最后构造自己
    • 析构
      • 先析构自己, 再析构成员变量(子类中的其他类对象), 最后析构父类
  • 继承中同名变量、同名成员函数的处理方法
    • 基类成员的作用域延伸到所有派生类。
    • 派生类的重名成员屏蔽基类的同名成员。(若要使用基类同名成员,通过作用于解析操作符::处理。)
  • 派生类中的static关键字
    • 基类定义的静态成员,将被所有派生类共享。
    • 访问控制遵循一般规则。
  • 多继承 (摒弃)
    • 一个类继承自多个基类。
    • 虚继承 (关键字:virtual)
      • 场景: 一个类继承自多个基类,而多个基类继承自同一个类A。派生类在访问A中的成员时,存在二义性。

多态

  • 根据对象的类型来决定同名函数的调用
  • 父类中函数声明virtual,子类中同名函数无论写不写virtual,均默认为virtual。
    • virtual关键字的作用
      • 无virtual: 静态联编
      • 写virtual: 动态联编(迟绑定),在运行的时候,根据具体的对象类型,执行不同的函数。
  • 实现多态的三个条件
    • 继承
    • 虚函数重写
    • 父类指针或引用
  • 虚析构函数
    • 场景: 通过父类指针,调用子类对象的析构函数,释放子类资源。
  • 重载、 重写、重定义
    • 重载
      • 同一个类中的函数
      • 子类无法重载父类的函数,父类中的同名函数被覆盖
    • 重写
      • 发生在父类和子类之间
      • 加virtual关键字,多态。
      • 不加virtual,重定义
  • 多态原理 vptr指针、虚函数表
    • 含有virtual函数的类中自动生成和维护一张虚函数表,存储虚函数的入口地址。由该类创建的对象拥有一个vptr指针,在调用虚函数时,根据对象的vptr指针(编译器不需要区分是子类对象还是父类对象),在所指的虚函数表中查找函数并调用(查找和调用在运行时完成,动态联编。非虚函数,编译器直接确定被调用的函数,静态联编)。
    • vptr指针的分步初始化
      • 问题: 构造函数中调用虚函数,能发生多态吗?
      • 不能。创建子类对象时,调用父类构造函数,vptr指针指向父类的虚函数表,会调用父类的虚函数,当父类构造函数执行完毕后,vptr指针指向子类的虚函数表。
  • 可否将类中成员函数都声明为虚函数?
    • 构造函数不能被声明为虚函数。其它函数可以,但效率低。虚函数在运行时通过寻址确定要调用的函数。
  • 纯虚函数 & 抽象类
    • 形式 virtual 类型 函数名(参数列表) = 0;
    • 具有纯虚函数的基类称为抽象类。
      • 抽象类不能创建对象,但可以声明抽象类的指针。
      • 抽象类不能作为返回类型,不能作为参数类型,但可以声明抽象类的引用。
    • 面向抽象类编程: 面向一套预定义好的接口编程。
    • 抽象类中多继承的应用

面向接口编程和C多态

  • 函数指针基本语法
int add(int a, int b){    return a + b;}typedef int (Add) (int, int); //定义函数类型typedef int (*pAdd) (int, int); //定义函数指针类型int (*p) (int, int); //定义函数指针,是个变量
  • 函数指针做函数参数
    • 实现了任务的实现者和调用者的分离,解耦和。
    • 正向调用
    • 反向调用

避免头文件重复包含

  • 方法1
#ifndef _XXX_H_#define _XXX_H_...#endif
  • 方法2
    #pragma once