zoukankan      html  css  js  c++  java
  • C++基础知识复习

    第一部分:基础知识

    一、const

    1. 作用

    • 修饰变量,表示不可能更改

    • 修饰指针

      • const int *ptr——pointer to const
      • int const *ptr—— const pointer
      • 原则:被const修饰的后面的值是不可改变的
    • 修饰引用

      常用于形参。即避免了copy,又避免了对其值的修改

    • 修饰成员函数

      表示该成员函数不能修改成员变量

    二、static

    1. 作用

    • 修饰普通变量

      修改变量的存储区域,使其存储在静态区。

      在main函数执行前就分配了空间;

      如果没有被显式初始化,则用默认值初始化

    • 修饰普通函数

      表明函数的作用范围。

      仅在定义该函数的文件内才能使用(多人项目中,为了防止与其他namespace重名,可以将函数定义为static)

    • 修饰成员变量

      使其变为类变量,即所有instance公用一个变量

    • 修饰成员函数

      使得不需要构造对象就可以访问此函数

      static函数内不能访问非静态成员(毕竟人家不要构造实例)

    三、this指针

    1. this指针是一个隐含于每一个非静态成员函数中的特殊指针,指向调用该成员函数的那个对象。(类似ptyhon类中的self
    2. 当对一个对象调用成员函数时,程序先讲对象的地址赋给this指针,然后调用成员函数;每次成员函数存取数据变量时,都隐式地使用this指针
    3. this指针被隐式地声明为ClassName *const this——意味着不能给this指针赋值;在ClassName类的const成员函数中,this指针的类型为:const ClassName* const,这说明不能修改this指针指向的数据成员。
    4. this并不是一个常规变量,而是一个右值,所以不能取得this的地址(能不能&this
    5. 以下场景中,经常显式的引用this指针:
      • 为实现对象的链式引用
      • 为避免对同一对象进行赋值操作
      • 为实现一些数据结构时,如list

    四、inline内连函数

    1. 特点

    • 编译时,将内联函数内容在调用处展开
    • 省略了进入函数的步骤,直接执行函数体
    • 类似于,但多了类型检查,具有函数特性
    • 编译器一般不inline包含循环、递归、switch等复杂操作的函数
    • 类中定义的函数,除了虚函数之外,其他函数都会自动隐式地的当成内联函数。

    2. 编译器对inline函数的处理步骤

    • inline函数体复制到inline函数调用处
    • 为所有inline函数中的局部变量分配内存空间
    • inline函数的输入参数和返回值映射到调用方法的局部变量中
    • 如果inline函数包括多个返回点,将其转变为inline函数代码块末尾的分支(使用GOTO)

    3. 优点

    • 省去了参数压栈、栈帧开辟与回收,结果返回的开销
    • 相比与来讲,在代码展开时,会做安全检查或者自动类型转换
    • 在类中声明同时定义的成员函数,会自动inline,因此内联函数可以访问类的成员变量,但不可以
    • 内联函数在运行时可调试,宏定义不可以

    4. 缺点

    • 代码膨胀。若执行函数体的时间比函数调用大很多,则inline收益很小。
    • inline无法随着函数库升级而升级。inline函数的改变需要重新编译,而non-inline可以直接连接
    • 是否内联,程序员不可控。inline函数只是对编译器的建议,是否内联,由编译器决定。

    5. 虚函数可以是inline

    • 可以。但当虚函数表现多态时的时候,不能内联
    • 内联发生在编译时,而虚函数的多态性是在运行期,编译器无法知道运行时调用哪个函数。因此虚函数表现为多态时(运行期)不能内联
    • inline virtual唯一可以内联:编译器知道要调用的对象是哪个类(如Base::who()),这只发生在编译器具有实际对象,而非对象的指针或引用。

    例子:

    #include <iostream>
    using namespace std;
    class Base {
      public:
        inline virtual void who(){cout << "Base::who"<<endl;}
        virtual ~Base()}{}
    }
    
    class Derived : public Base {
      public:
        void who(){cout << "Derived::who"<< endl;}
    }
    
    int main(){
      Base b;
      b.who(); // 此处编译其就知道要调用哪个函数
      
      Base *ptr = new Derived();
      ptr->who();// 多态
      
      delete ptr;
      ptr = nullptr;
      return 0;
    }
    

    五、sizeof

    • 对数组,返回整个数组占用的空间大小
    • 对指针,返回指针本身占用的空间大小

    六、extern "C"

    • extern的函数或变量是extern类型,跨文件访问
    • 被修饰的变量或函数是按照C语言方式编译和链接的
    #ifdef __cplusplus
    extern "C"{
    #endif
      void *memset(void *, int, size_t);
    #ifdef __cplusplus
    }
    #endif
    

    七、union联合

    1. 概念

    可以有多个数据成员,但在任意时刻只有一个数据成员可以有值。当某个成员被赋值后,其他成员变成未定义状态。

    2. 特点

    • 默认为public
    • 可以有构造函数、析构函数
    • 不能含有引用类型成员
    • 不能继承自其他类,不能作为基类
    • 不能包含虚函数
    • 匿名union在定义所在的作用域可以直接访问union成员
    • 匿名union不能包含protectedprivated成员
    • 全局匿名union必须是静态的
    #include <iostream>
    using namespace std;
    
    union UnionTest {
      UnionTest():i(10){}; // 构造函数
      int i;
      double d;
    };
    
    static union{ // 全局静态匿名union
      int i;
      double d;
    };
    
    int main(){
      UnionTest u;
      
      cout << u.i << u.d << endl;
      
      Union {   // 局部匿名union
        int i;
        double d;
      };
      ::i = 20; // 全局静态union.i值
      
      i = 30;// 局部i
        
      return 0;
    }
    

    八、C实现C++类

    • 封装

      使用函数指针把属性和方法封装在结构体

    • 继承

      结构体嵌套

    • 多态

      父类与子类方法的函数指针不同

    九、explicit

    • 修饰构造函数是,防止隐式转换和复制初始化
    • 修饰转换函数时,防止隐式转换,但按语境转换除外

    十、friend友元类和友元函数

    • 能访问私有成员
    • 破坏了封装性
    • 友元函数不可传递
    • 友元函数的单向性
    • 友元声明的形式和数量不受限制

    十一、using

    1. 引入命名空间的一个成员

    using namespace_name::name;
    

    2. C++11中派生类重用基类的构造函数

    class Derived : public Base{
      public:
      	using Base::Base; 
    };
    

    对于基类的每个构造函数,编译器都会生成一个与之对应(形参类列表完全相同)的派生类构造函数。

    3. using指示

    using namespace std;// 无需为std里的所有名字添加std前缀了
    

    注:

    1. 应尽量少用using 指示,会污染命名空间
    2. 如果只引入一个成员,且与局部名称冲突了,编译器会发出指示
    3. 但如果全导入了,且覆盖了局部名称,编译器不会提示,排查问题较难

    十二、::范围解析符

    1. 类别

    • ::name全局作用符。

      用于类型名称(如类、类成员、成员函数、变量)前。

    • class::name类作用域符。

      用于指定类型的作用域范围是具体某个类的。

    • namespace::name命名空间作用域符

      用于指定类型的作用域范围是某个命名空间的。

    int count = 1; // ::count
    
    class A{
      public:
      	static int count = 2; // A::count
    };
    void foo(){
      int count = 3;
    }
    

    十三、引用

    1. 左值引用

    常规引用,表示对象的身份。

    2. 右值引用

    右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。

    右值引用可以实现转移语义(move sementics)和精确传递(perfect forwarding),主要目的有两个:

    • 消除两个对象交互时不必要的对象拷贝,节省存储资源,提升效率
    • 能够更简洁明确地定义泛型函数

    3. 引用折叠

    • X& &X& &&X&& &可以折叠为X&
    • X&& &&可以折叠为X&&

    十四、成员初始化列表

    1. 优点

    • 更高效

      少了一次调用默认构造函数的过程

    • 有些场合必须要初始化列表

      • 常量成员

        因为常量只能初始化,不能赋值,所以必须放在初始化列表中

      • 引用类型

        引用必须在定义时初始化,并不能重新赋值

      • 没有默认构造函数的类类型

        因为使用初始化列表可以不必调用默认构造函数,就可初始化

    2. initializer_list

    用花括号初始化器初始化一个列表,其中构造函数接受一个std::initializer_list参数

    #include <iostream>
    #include <vector>
    #include <intializer_list>
    
    template<typename T>
    struct S{
      std::vector<T> v;
      S(std::initializer_list<T> l): v(l){
        std::cout << "init"<< endl;
      }
      
      void append(std::initializer_list<T> l){
        v.insert(v.end(), l.begin(), l.end());
      }
      
      std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};
      }
    };
    

    第二部分:面向对象

    一、多态

    1. 概念

    • 多态,可以理解为消息以多种形式显示的能力

    • 多态是以封装和继承为基础的

    • C++多态分类和实现

      • 重载多态(Ad-hoc,编译期)

        函数重载,运算符重载

      • 子类型多态(subtype,运行期)

        虚函数

      • 参数多态性(parametric,编译期)

        类模板、函数模板

      • 强制多态(coercion,编译器/运行期)

        基本类型转换、自定义类型转换

    2. 静态多态

    编译期,早绑定

    函数重载

    class A{
      void foo();
      void foo(int a);
    };
    

    3. 动态多态

    运行期/晚绑定

    虚函数:virtual修饰

    • 普通函数(非类成员函数)不能是虚函数

    • 静态函数(static)不能是虚函数

    • 构造函数不能是虚函数

      因为在调用构造函数时,虚表指针并没有在对象的内存空间中,必须要构造函数调用完成时,才会形成虚表指针(重要)

    • 内联函数不能是表现多态时的虚函数

    4. 虚析构函数

    是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

    Class Base{
      public:
      	Base();
      	virtual ~Base();
    };
    
    class Derived : public Base {
      public:
      	...
    };
    
    int main(){
      Base *ptr = new Derived();
      delete ptr;
      ptr = nullptr;
      return 0;
    }
    

    5. 纯虚函数

    一种特殊的虚函数

    在基类中不能对虚函数给出有意义的实现,而是声明为纯虚函数,它的实现留给派生类去做。

    Class AbstraceBase{
      virtual void foo(int) = 0;
    };
    

    虚函数 vs 纯虚函数

    • 类如果声明虚函数,且实现了,哪怕是空实现,则作用就是为了能让这个函数在派生类里被override。这样,编译器就可以使用后期绑定来达到多态了;纯虚函数只是一个接口,是个函数声明而已,要留到子类实现
    • 虚函数在派生类可以不重写;纯虚函数必须在子类实现才可以实例化
    • 虚函数的类用于实作继承,继承接口的同时也继承了父类的实现。纯虚函数关注的是接口的统一性,实现由子类去完成
    • 带纯虚函数的类称为抽象类,不能被实例化。只有继承并实现了纯虚函数,才能使用。派生类继承后,可以继续是抽象类,也可以是普通类。
    • 虚基类是虚继承中的基类。

    6. 虚函数指针、虚函数表

    • 虚函数指针

      在含有虚函数类的对象中,指向虚函数表,在运行时确定

    • 虚函数表

      在程序只读数据段.rodata section,存放虚函数指针。如果派生类实现了基类的某个虚函数,则在虚表中覆盖原本基类的那个虚函数指针,在编译时根据类的声明创建。

    7. 虚继承

    用于解决多继承条件下的菱形继承问题

    浪费存储空间、存在二义性

    底层实现原理与编译器相关,一般通过虚基类指针虚基类表实现。每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用空间)(但虚基类依旧会在子类里面存在拷贝,只是仅仅最多存在一份而已,并不是不在子类里面了);当虚继承的子类被当做父类继承时,虚基类指针也会被继承。

    实际上,vbptr指的是虚基类表指针,指向一个虚基类表——记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员,而虚继承也不用像普通多继承那样维持公有基类(虚基类)的两份同样拷贝,从而节省了存储空间。

    虚继承 vs 虚函数

    • 相同之处

      都利用了虚指针(均占用类的存储空间)和虚表(均不占用类的存储空间)

    • 不同之处

      • 虚继承
        • 虚基类依旧存在继承类中,之占用存储空间
        • 虚基类表存储的是虚基类的相对直接继承类的偏移
      • 虚函数
        • 虚函数不占用存储空间
        • 虚函数表存储的是虚函数地址

    模板类、成员模板、虚函数

    • 模板类可以使用虚函数
    • 一个类的成员模板(本身是模板的成员函数)不能是虚函数

    8. 抽象类、接口类、聚合类

    • 抽象类:含纯虚函数
    • 接口类:仅含纯虚函数的抽象类
    • 聚合类:可以直接访问其成员,具有特殊的初始化语法
      • 所有成员都是public
      • 没有定义任何构造函数
      • 没有类内初始化
      • 没有基类、没有virtual函数

    第三部分

    一、内存分配和管理

    1. malloc、calloc、realloc、alloca

    • malloc

      申请指定字节数的内存。申请到的内存中的初始值不确定

    • calloc

      指定长度的对象,分配能容纳指定个数的内存。申请的内存的每一位(bit)都初始化为0

    • realloc

      更改以前分配的内存长度(增加或减少)

      当增加长度是,可能需要将以前分配区的内容移到另一个足够大的区域。而新增的区域内的初始值不确定

    • alloca

      栈上申请内存。

      程序在出栈时,会自动释放内存。但需注意,alloca不具有可移植性,而且在没有传统堆栈的机器上很难实现

      alloca不宜使用在必须广泛移植的程序中。C99中支持变长数组(VLA),可以用来代替alloca。

    2. malloc和free

    分别用于内存的分配和释放

    // 内存申请
    char *str = (char*)malloc(100);
    assert(str != nullptr);
    
    // 内存释放
    free(str);
    str = nullptr;
    

    3. new和delete

    • new/new[]
      • 先底层调用malloc分配内存
      • 再调用构造函数(创建对象)
    • delete/delete[]
      • 先调用析构函数(清理资源)
      • 再底层调用free释放空间
    • new申请内存时会自动计算所需要的字节数;malloc则需要我们自己输入申请空间的字节数
    int main(){
      T* t = new T(); // 先分配内存,再构造
      delete t; // 先析构,再free
      return 0;
    }
    

    4. 定位new

    placement new允许我们向new传递额外的地址参数,从而再预先指定的内存区域创建对象。

    // place_addr是一个指针
    // initializers提供一个以逗号分割的初始值列表
    new(place_addr) type;
    new(place_addr) type (initializers);
    new(place_addr) type [size];
    new(place_addr) type [size] {braced intializer list};
    

    5. delete this 合法吗?

    合法,但是:

    • 必须保证this对象是通过new(不是new[]、不是placement new、不是栈上、不是全局、不是其他对象成员)分配的
    • 必须保证调用delete this的成员函数是最后一个调用this的成员函数
    • 必须保证成员函数的delete this后面不再调用this
    • 必须保证delete this后没有人再使用了

    总之,delete this对调用的成员函数有很严格的要求。

    class A{
      public:
      	A(){};
        void destory(){ delete this;} // 必须显式的调用,进行内存空间释放
      private:
       	~A(){}  // 私有函数,只能通过new去动态构造
    };
    

    6. 栈上或堆上生成对象

    C++中,类对象的建立有两种:

    • 静态建立

      • A a;

      • 由编译器为对象在栈空间分配内存,是通过移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数,形成一个栈对象。

      • 为什么不把构造函数设置为private?

        构造函数设置为私有,会影响new动态建立对象,因为其第二阶段也会调用构造函数。

    • 只能在

      • 方法:将析构函数设置为私有

        C++是静态绑定语言,编译器管理栈上对象的声明周期。

        编译器为类的对象分配栈空间时。会先检查类的析构函数的访问性;

        若析构函数不可访问,则不能在栈上创建对象。

      • 缺点:无法解决继承问题

        若其作为基类,则析构函数一般要设置为virtual,然后在子类重写,以实现多态,所以析构函数不能设置为private。可以将析构函数设置为protected

    class A{
      protected:  // protected,解决继承问题
      	A(){}
      	~A(){}
       public:
      	static A* create(){return new A();} // 静态方法,负责创建
      	void destroy() {delete this;}	// 负责释放内存空间,必须最后调用
    };
    
    • 只能在

      • 方法:将newdelete重载为private

        在堆上生成对象,使用new关键字操作,过程包括两个阶段:

        1. 使用new在堆上需找可用内存,分配给对象
        2. 调用构造函数生成对象

        new操作设置为private,那么第一阶段的操作就无法完成,就不能在堆上生成对象

    class A{
      private:
      	void *operator new(size_t){}; // 函数第一个参数和返回值都是固定的
      	void operator delete(void* ptr){};// 重载new就需要重载delete
      public:
      	A(){}
      	~A(){}
    };
    

    有个问题:

    如果既把析构函数设置为private,也将newdelete重载为了private,那么对象会创建在哪里?创建失败?

    二、智能指针

    1. 标准库

    #include <memory>
    std::auto_ptr<std::string> ps (new std::string(str));// C++98
    // C++ 11中 auto_ptr被弃用
    

    2. shared_ptr

    多个智能指针可以共享同一个对象,对象的最末一个拥有者有责任销毁对象,并清理与该对象相关的所有资源

    优点:

    • 支持定制型删除器(custom deleter

    • 可防范Cross-DLL问题

      对象在动态链接库DLL中new创建,却在另一个DLL内被delete销毁

    • 自动解除互斥锁

    3. Weak_ptr

    允许共享但不拥有某个对象,一旦最末一个拥有该对象的智能指针失去了所有权,任何weak_ptr都会自动成空。

    因此,在defaultcopy构造函数之外,weak_ptr只提供接受一个shared_ptr的构造函数

    优点:

    • 可打破环状引用

      两个其实已经没有被使用的对象,彼此互指,使之看似还在『被使用』的状态的问题

    4. unique_ptr

    C++ 11提供的类型,在异常时可以帮助避免资源泄露的智能指针,用于取代auto_ptr

    独占式拥有,即一个对象和相应的资源同一时间只被一个pointer拥有。

    一旦拥有者被销毁或设置为empty,或开始拥有另一个对象,先前拥有的那个对象就会被销毁,响应资源会被释放。

    uique_ptr v.s auto_ptr

    • auto_ptr可以赋值拷贝,复制拷贝后所有权转移;unique_ptr无赋值拷贝语义,但实现了move语义
    • auto_ptr对象不能管理数组(析构调用delete);unique_ptr可以管理数组(析构调用delete[]

    5. 强制类型转换

    待补充

    附录:

  • 相关阅读:
    CSS3点赞动画特效源码下载
    jQuery仿阿里云购买选择购买时间长度
    Ubuntu系统操作快捷键
    DIV+CSS颜色边框背景等样式
    HTML5翻页电子书
    淡蓝风格的手机登录HTML模板
    HTML常用符号
    SQL SERVER实例解析
    div+css页面右侧底部悬浮层
    C#引用C++代码
  • 原文地址:https://www.cnblogs.com/CocoML/p/14643401.html
Copyright © 2011-2022 走看看