c++基础

0. 命名空间

  • c标准库可以认为是在匿名空间中
  • 匿名的命名空间中的实体不能跨模块使用
  • 匿名命名空间中的实体和全局变量同名时,会出现两种情况:
  • 使用 ::entryName, 只能访问到全局变量。
  • 使用 entryName, 产生二义性。
  • 有名命名空间可以嵌套使用、可以进行追加、可以跨模块使用

1. const限定符

1.1 宏定义和const的区别

宏定义:

  • 发生在 预处理阶段(发生时机)
  • 宏定义常量是 简单的文本替换存储在代码段

const

  • 编译阶段 由编译器 进行类型检查
  • const常量需要进行内存分配

1.2 常量指针 v.s. 指针常量

  • 常量指针 即 指向常量的指针;(不能通过该指针间接修改其所指向对象的值)
  • 指针常量 是指 指针变量本身是一个常量。(指针的指向是固定的)

1.3 数组指针 v.s. 指针数组

  • 数组指针 即 指向数组的指针;(该指针的偏移是以该数组类型的长度大小为单位的)
  • 指针数组 即 元素类型为指针的数组;

1.4 函数指针 v.s. 指针函数

  • 函数指针 即 指向函数的指针; (函数指针可以看作是一个函数对象)

    普通函数的函数名funcName 即 函数的入口地址,通过 funcName&funcName 都可以进行调用;

    静态成员函数可以通过 MyClass::funcName&MyClass::funcName 进行调用;

    成员函数的函数类型为 ReturnType MyClass::funcName (...);, 定义对应的成员函数指针类型为 ReturnType (MyClass::*FuncPtr) (...);

  • 指针函数 即 返回值类型是指针的函数;(返回值不能是一个局部变量的地址)


2. new & delete

2.1 形态

new… delete…

  • int *p = new int;
    • 没有进行初始化
  • int *p = new int();
    • 默认初始化
    • 此处默认初始化为0

new…[] delete[]…

2.2 new & malloc区别

  • new 是c++的表达式或操作符;malloc是c的库函数(stdlib.h)
  • new 返回是一个对应申请类型的指针;malloc返回的是void*指针
  • new 在申请时只需指定要申请的类型或者申请元素的个数;malloc需要指定分配内存空间大小的字节数
  • new 在申请空间时可以进行初始化;malloc不能在申请时进行初始化

2.3 new的底层实现

  • 通过 operator new 函数申请对应大小的未初始化的内存空间
  • 调用对应的构造函数进行初始化(如果有的话)
  • 返回一个对应申请类型的指针

2.4 delete的底层实现

  • 调用对应类型的析构函数(如果有的话)
  • 调用 operator delete 函数释放所申请的对应大小的内存空间

2.5 operator new 的底层实现

  • malloc
  • malloc失败则抛出崩溃异常

2.6 operator delete 的底层实现

  • 检查机制
  • free

3. 引用

  • 引用底层实现还是一个指针常量,只不过编译器限制获取其地址。因此,引用占据的空间大小仍然是一个指针大小。

3.1 用途

  1. 引用作为函数参数

    可以实现传入传出

  2. 引用作为函数的返回值

    • 不能返回局部变量的引用
    • 不能轻易返回一个堆空间变量的引用,除非有了相应的内存回收机制

3.2 引用 v.s. 指针

    • 引用和指针占据内存空间都是4或8字节
    • 指针常量和引用都必须初始化
    • 指针允许寻址,引用不允许寻址
    • 指针常量和引用都必须初始化,但指针常量可以为空,引用不可以
    • 数组元素的类型可以是指针,但不能是引用
    • sizeof运算符 对于引用是获得其所绑定对象的大小

4. 类型转换

4.1 static_cast

一般用于void*和其他类型指针之间的转换


4.2 dynamic_cast

运行时类型转换,通常用于基类和派生类之间的 向下转型

  • 基类指针指向派生类对象,将该基类指针强制转换为派生类指针的操作是安全的

  • 基类指针指向基类对象,那么将该基类指针转换为派生类指针是不安全的

  • 编译器默认将基类指针转换为派生类指针的操作是不允许的,那么要实现上面安全的操作,就需要用到 dynamic_cast,并且基类必须要有虚函数

    • 若是基类指针指向派生类对象,则 dynamic_cast 就会使得基类指针转换为派生类指针
    • 若是基类指针指向基类对象,则 dynamic_cast 就会返回一个nullptr

4.3 const_cast

去除(底层)const属性,原来的const常量不会发生改变

  • 实现底层修改的接口(在非const接口中调用const版本是安全的)
class Test {
public:
	Test() {
        for(int i = 0; i< 5; ++i) {
            _arr[i] = i;
        }
    }
    
    const int * test() const {
        return _arr;
    }
    
    int * test() {
        return const_cast<int*>(const_cast<const Test *>(this)->test());
    }

private:
    int _arr[5];
};
  • 可以使用 模板、自动推导 替代
class Test {
private:
    template<class T>
    static auto fun_impl(T * t) {
        return t->_arr;
    }
public:
    Test() {
        for(int i = 0; i< 5; ++i) {
            _arr[i] = i;
        }
    }
    
    int * test() {
        return fun_impl(this);
    }

    const int * test() const {
        return fun_impl(this);
    }

private:
    int _arr[5];
};

4.4 reintepret_cast

对于二进制类型重新解释


5. 函数重载

  • c不支持函数重载,c++支持,利用的原理是 名字改编 name mangling
  • 函数重载是指 当函数名相同时,根据 函数参数类型参数个数参数顺序进行改编的;并且函数返回值类型不作为函数重载标志。
  • c和c++函数调用方式不同,要是在c++中使用c的函数调用方式,则要使用 extern "C" {...}

6. 默认实参

  • 声明时指定,从左到右
  • 实现时不能重复出现
  • 默认实参的确定是在编译时,但是默认实参的值的确定分情况而定,若默认实参是字面值,则在编译时就以确定其值;若默认实参是函数调用或全局变量,则其值是在运行时确定

    因此对于虚函数中默认参数而言,其默认实参类型的确定是在编译时,因此是静态联编


7. inline函数

  • 是在编译时进行替换,并且会进行参数类型检查,没有函数调用的开销

  • 宏函数只是简单的文本替换,发生时机是在预处理

  • 不适于使用Inline函数场景

    • 当函数体很长时
    • 当函数体中含有循环时
  • 由于inline函数的发生时机是在编译时,因此不能将其的声明和实现分开放置于不同文件中;通常inline函数位于头文件中


8. bool类型

  • c++中的bool类型只占1个字节
  • c99中的_Bool类型就是int类型,占4个字节

9. try…catch…

  • google_cpp编程规范不推荐使用,破坏了代码结构

10. 类 (base)

10.1 类类型大小

  • 类类型的大小只与非静态数据成员有关,与静态数据成员、成员函数无关
  • 空类大小为1个字节

10.2 数据成员初始化顺序

  • 数据成员的初始化顺序只与其在类中声明顺序有关,与其在初始化列表中的顺序无关

10.3 构造函数

  • 默认构造函数

    • 若定义了有参默认构造函数,则缺省构造函数不会自动合成
  • 拷贝构造函数

    ClassName(const ClassName &);

    • “&” 不能去掉, 否则在进行调用拷贝构造函数时,会递归调用,从而造成栈溢出
    • “const” 不能去掉, 否则对于参数是右值的类对象时,非const引用不能进行绑定
    • 调用时机:
      • 当用一个已存在的类对象初始化一个新构造的类对象时。(值初始化)
      • 当函数的参数类型是一个类类型时,将实参传递给形参时。
      • 当函数的返回值是一个类类型时,执行return语句时。(若返回的是一个临时类对象若该类有移动构造函数的话,则会调用移动构造函数
  • 移动构造函数

    ClassName(ClassName &&);

10.4 析构函数

  • 每个类只能有一个析构函数
  • 调用时机: 当类对象被销毁时,会自动调用析构函数
    • 栈对象被销毁时(作用域结束时)
    • 堆对象被delete时
    • 全局类对象销毁时(程序退出时)
    • 静态类对象销毁时(程序退出时)
  • 析构函数也可以显式调用,但不会使得所对应类对象销毁。即析构函数的调用是类对象销毁的必要条件
  • 由于每个类都有唯一的析构函数,因此编译器在编译时会将其名字改变为Destructor。因此对于虚析构函数而言,只有基类将析构函数声明为虚函数,则其派生类的析构函数会自动成为虚函数

10.5 this指针

  • 成员函数中的第一个参数是隐含的this指针
  • 类的this指针类似于一个指针常量,绑定一个类对象

10.6 深拷贝 、 浅拷贝

  • 是针对于 堆空间变量的复制而言的

10.7 特殊的数据成员

  • const数据成员

    在初始化列表中进行初始化

  • 引用数据成员

    在初始化列表中进行初始化

  • 类数据成员
    • 需要在列表初始化中显示初始化,否则默认调用缺省默认构造函数
  • 静态数据成员
    • 需要在类外进行初始化
    • 存储于全局静态区,初始化位于data段,未初始化位于bss段,不占据类对象的存储空间,被所有类对象共享

10.8 特殊的成员函数

  • 常成员函数

    this指针进行const修饰

  • 静态成员函数

    没有this指针,因此可以直接访问静态数据成员

10.9 栈、堆 类对象

  • 栈类对象

    要创建一个栈类对象,则类的构造函数和析构函数必须具有public访问权限

  • 堆类对象

    new 、 delete

  • 只能创建栈对象,不能创建堆对象

    operator newoperator delete 函数 私有化 或者 删除

  • 只能创建堆对象,不能创建栈对象

    将类的 析构函数 私有化
    并且定义一个具有public权限的函数去调用delete来销毁堆对象

10.10 友元

  • 从类的外部访问类的私有成员
  • (友元函数)普通函数、成员函数、(友元类)类
  • 不能被继承不具有传递性具有单向性

10.11 运算符的重载

c++将 运算符 进行 泛化成函数

  • 不可重载的运算符

    . .* :: ?:: sizeof

  • 必须以成员函数形式重载的运算符

    = [] () -> ->*

  • 规定

    • 不能对标准类型进行运算符重载,c++规定重载运算符的操作数必须要有一个自定义类型或枚举类型
    • 重载运算符之后,运算符的结合性和优先级不发生改变
    • 重载运算符之后,运算符的操作数的个数和顺序不发生改变
    • 重载运算符函数不能具有默认实参
    • 重载运算符 && || 之后,不再具有 短路求值的特性
    • 不能臆造运算符
  • 赋值运算符

    自复制回收左操作数空间深拷贝return *this

  • 自增(减)运算符

    为了区别前置自增和后置自增运算符,c++规定 在后置自增运算符重载函数的形参中增加一个int类型

  • 函数调用运算符

    重载了函数调用运算符的类对象成为 函数对象

    • 函数对象 v.s. 普通函数

      相对于普通函数,函数对象携带了状态不同函数对象具有不同状态

      普通函数也可以携带状态,如static局部变量,只不过普通函数携带的状态是全局的

    • 函数对象

      普通函数、函数指针、成员函数、成员函数指针都可以看成是 广义上的函数对象

      • std::function

        • 用来存储函数对象的容器
        • 函数对象的调用中的参数的个数是由 std::function 的类模板参数决定的
      • std::bind

        • 改变函数调用的形态
        • std::bind 的函数参数是进行的值传递,为了进行引用传递,需要使用std::ref 引用包装器
        • std::bind 的函数参数个数是由被绑定的函数的参数个数相关的
        • std::bind 的参数中的占位符代表的是实参的位置
  • 下标运算符

    key --> value

  • 解引用、箭头运算符

    智能指针的雏形

    class Data {
    public:
        int getData() {
            return 10;
        }
    };
    class SecondLayer {
    public:
        SecondLayer(Data * pData) 
        : _pData(pData)
        {
            cout << "SecondLayer::SecondLayer()" << endl;
        }
    
        ~SecondLayer() {
            cout << "SecondLayer::~SecondLayer()" << endl;
            if(_pData) {
                delete _pData;
                _pData = nullptr;
            }
        }
        
        Data * operator->() {
            return _pData;
        }
    
        Data & operator*() {
            return *_pData;
        }
    
    private:
        Data *_pData;
    };
    
    class ThirdLayer {
    public:
        ThirdLayer (SecondLayer *pSecLayer) 
        : _pSecLayer(pSecLayer)
        {
        }
    
        ~ThirdLayer() {
            if(_pSecLayer) {
                delete _pSecLayer;
                _pSecLayer = nullptr;
            }
        }
        
        SecondLayer & operator->() {
            return *_pSecLayer;
        }
    
    private:
        SecondLayer * _pSecLayer;
    };
    
    
    • 编译器将 secLayer->->getData() 简化为 secLayer->getData()
    • 由于编译器的简化操作,SecondLayer 类似智能指针,因此在ThirdLayer中重载->运算符时返回的是SecondLayer引用
  • 类类型转换

    • 其他类型 => 类类型

      通过有参构造函数

      • TestClass t1 = 1;
        先调用有参构造函数创建一个临时对象
        调用拷贝构造函数进行复制
        若存在移动构造函数,则优先调用移动构造函数
      • TestClass t2(1);
        直接调用有参构造函数
      • t3 = 1;
        先调用有参构造函数创建一个临时对象
        调用赋值运算符函数进行复制
    • 类类型 => 其他类型

      类型转换成员函数

      • 没有返回值、参数
      • 函数名为 operator OtherType
      • 函数体中return 一个OtherType类型

10.12 类域

类的作用域 和能否从类外部访问无关

  • 全局作用域

  • 类作用域

    嵌套类

    • 类A中定义了一个类B
    • 若B的访问权限是private,则只能在A类的内部进行创建类B对象
    • 要想在嵌套类中使用外部类的非静态数据成员/成员函数,则需要传递外部类对象的地址给嵌套类
  • 块作用域

  • 类名作用域

    名字(数据成员、成员函数)的隐藏

  • 对象作用域


11. 继承

11.1 构造函数

在创建派生类对象时,只会调用派生类的构造函数

  1. 完成派生类对象所占用空间的开辟
  2. 对基类部分进行初始化

    显式初始化时使用 BaseName(...)

  3. 对初始化列表中的其他部分进行初始化
  4. 执行派生类构造函数的函数体

由于需要初始化派生类的基类部分,因此才需要调用基类的构造函数

11.2 析构函数

在调用派生类析构函数时,会自动调用基类的析构函数

  1. 调用派生类析构函数
  2. 调用派生类中成员对象的析构函数
  3. 调用基类的析构函数

11.3 派生类适应于基类

  • 向上转换

    • 派生类指针 => 基类指针
    • 基类引用 绑定派生类对象
    • 用派生类对象初始化基类对象
  • 向下转换

    • 基类指针 => 派生类指针

      编译器禁止

      • 基类指针指向基类对象(不安全
      • 基类指针指向派生类对象(安全

11.4 多重继承

  • 基类的初始化顺序和继承时声明的顺序一致
  • 默认继承的权限是private
  • 会造成二义性
    • 函数成员同名的二义性
    • 菱形继承中存储二义性

      解决方法:虚拟继承


11.5 虚拟继承

  • 虚基指针 vbptr

  • 虚基表 vbtable

    • 第一个表项

      虚基指针与其所对应子对象的首地址偏移

    • 第二个表项

      虚基指针与其所对应的虚基类对象的首地址偏移

  • 在虚拟继承的存储布局中,虚基类位于最末尾

  • 特征:

    • 存在

      虚基指针、虚基表确实存在

    • 间接

      通过虚基指针访问虚基表,从而访问其对应的虚基类

    • 共享

      对于菱形继承而言,只存在一份虚基类的存储空间

  • 虚拟继承中的构造函数和析构函数

    • 若在继承链上存在虚基类,则其初始化是由最底层的子类显示指定完成的;若不显示指定,则编译器会自动调用虚基类的缺省构造函数,若不存在则报错
    • 并且不管初始化顺序如何,虚基类的初始化优先于其他普通基类,并且其虚基类的构造函数只会调用一次

11.6 复制控制(基类、派生类间的复制)

拷贝构造函数、赋值运算符函数

  • 若派生类中没有复制控制函数,则执行派生类对象的复制时会自动调用基类的复制控制函数
  • 若派生类中有复制控制函数,则执行派生类对象的复制时不会自动调用基类的复制控制函数

12. 多态

12.1 静态多态v.s. 动态多态

静态联编

  • 发生时机:编译时
  • 函数重载模板运算符重载

动态联编

  • 发生时机:运行时
  • 基类指针(基类引用) + 虚函数

12.2 动态多态被激活的条件

  • 基类定义一个虚函数
  • 派生类中覆盖该虚函数
  • 创建一个派生类对象
  • 用一个基类指针指向该派生类对象 或 用一个基类引用绑定该派生类对象
  • 通过该基类指针/引用 访问 虚函数

12.3 虚函数

  • 虚函数的访问权限是由其静态类型决定的

    因为访问权限只是用于编译器进行检查,发生在编译时期

  • 虚函数可以有默认实参,但若某次函数调用了默认实参,则该默认实参的值是由本次调用的静态类型决定的

    因此,若虚函数有默认实参,则基类、派生类中的默认实参最好一致

  • 实现原理:

    • 虚函数指针 vfptr

    • 虚函数表(虚表)vftable

      • 存放虚函数的入口地址以及一些跳转指令
      • 存放于只读数据段.rodata
        不能修改
    • 当一个类中含有虚函数,则创建该类的对象时,其对象的存储空间布局的起始位置会多出一个只有一个)虚函数指针vfptr,指向其所对应的虚表

    • 回避虚函数机制

      使用 作用域限定符
      通常,只在成员函数 或 友元中使用

    • 在构造函数中使用虚函数,采用的是静态联编

    • 虚析构函数

      由于编译器对析构函数的底层函数名进行统一化Destructor,因此将基类的析构函数虚化后,派生类的析构函数自动成为虚函数

    • 不能设置为虚函数的函数

      • 构造函数

        还没有创建完一个对象

      • 普通函数
      • 静态成员函数
      • inline函数

        inlinevirtual 关键字同时出现时,inline失效

      • 友元函数
    • 特征

      • 存在

        虚函数指针、虚表是存在的

      • 间接

        对虚函数的访问是需要通过动态机制激活的

      • 共享

        基类共享派生类重定义后的虚函数

12.4 函数重载 v.s. 函数隐藏 v.s. 函数覆盖

  • overload

    同一类中,函数名相同,不同参数类型、参数个数、参数顺序

  • overseek

    基类和派生类中,相同函数名

  • override

    基类和派生类中,虚函数,相同函数原型、相同函数名

    • 发生时机:编译时

12.5 纯虚函数

  • virtual ReturnType FuncName (...) = 0;
  • 定义了一个与派生类中一致的接口

12.6 抽象类

不能创建对象

  • 含有纯虚函数的类

    只有当派生类实现所有的纯虚函数时,该派生类才不是一个抽象类

  • 构造函数的访问权限是protected的类


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值