C++合集

C++程序设计

  • 从C过渡到C++
  • C++面向对象 对象和类
  • 封装 继承 多态
  • 运算符重载
  • 异常和IO
  • 模板的语法
  • C++11语法特性
  • STL 模板库
    • STL里面有容器 数据结构 都有封装

C++基本介绍

  • ++ C语言运算符 自增 C++是在C语言的基础之上进行自增(扩展) 基本上兼容C语言
  • 1979年本贾尼-斯特劳斯特卢(劳)普(C++之父) 在贝尔实验室从事C语言改良工作 C with Classes 带类的C语言 只是C语言的一部分
  • 1983年正式命名为C++
  • 1989年制定C++标准,直到1998年才投入使用 C++98标准
  • 2003年有修复bug, 03标准
  • 2011年 C++11标准 颠覆性地(增加了很多语法知识) 每三年固定发布新标准
  • 2014年 C++14
  • 2017年 C++17
  • 2020年 C++20
C++的特点
  • 支持面向对象,(C语言面向过程)C++宏观上支持面向对象,微观支持面向过程
  • 支持运算符重载 让自定义类型(struct/class)的数据也支持±*/><==操作
  • 支持异常机制 错误处理 #include <errno.h>
  • 支持泛型编程 对类型抽象化 list 双向链表 存储任意类型的数据
C++的应用
  • 执行效率与开发效率兼顾
    • C语言具有其它高级编程语言无法比拟的执行效率
    • C++优化编译器,在某些场合能使得C++具有C语言的执行效率
    • C++面向对象编程,有丰富的扩展库,用C++进行开发,直接可以使用库
  • 科学计算库 基本上都是C++实现
  • 计算机网络 ACE库
  • 网络游戏 大型的网络游戏 基本上都是用 C++实现的
  • 强建模能力 数学建模 3D
  • 桌面应用程序 office办公软件 windows桌面
  • 驱动程序 C++优化的编译器使得C++执行效率提高
C和C++的区别?
  • C++基本上完全兼容C语言 C语言是C++的一个子集 C++是C语言的一个超集
  • C语言面向过程,C++面向对象的 C++宏观面向对象,微观面向过程的
  • C++支持运算符重载
  • C++支持异常机制
  • C++支持泛型编程
第一个C++程序
//01hello.cpp

#include <iostream>
using namespace std;

int main(int argc,char *argv[]){
	cout << "Hello wrold!" << endl;
	return 0;
}
  • g++ 01hello.cpp 默认生成a.out可执行程序

  • C++源代码的后缀一般都是

    cpp      c++       cc       C
    
  • C++代码的编译器是g++

    • 但是某些情况下,也可以使用gcc编译器,但需要链接 -lstdc++ 库文件

      gcc 01hello.cpp -lstdc++         //-lstdc++ 链接C++的库
      
  • 解释C++第一个程序的语法知识

    #include <iostream>  //C++标准IO的头文件   iostream               C++标准库没有.h
    //在C++中可以直接使用C语言的头文件         但是如果使用C语言标准的头文件    stdio.h一般来说C++有自己提供的             #include <cstdio>     #include <cstdlib>
    
    using namespace std;            //C++把标准库中所有对象和函数都放在了std名字空间里面
    //名字空间是对代码的逻辑划分
    //声明std名字空间中的内容对当前作用域可见
    
    //主函数 main   和C语言中的一模一样
    int main(int argc,char *argv[]){
        cout << "Hello world" << endl;
        return 0;
    }
    
    << 输出运算符
    >> 输入运算符
    
  • cout 标准输出对象

  • cin 标准输入对象

  • cerr 标准错误输出对象

  • endl 换行 \n 对象

C++的基本数据类型
  • C++中有bool布尔类型
    • bool类型的取值为 true 和 false 表示 真和假 底层用 1和0存储
    • 在输出时,true – 1 false—0 可以使用boolalpha 输出 true 和 false
C++中的struct
  • C++中的struct与C语言中struct的区别?

    • C++用struct定义的结构体类型之后,定义变量时可以不用struct关键字

    • sizeof(空结构体)在C语言中为0 C++中结果为1 主要是为了标识内存

    • C++ struct中可以定义函数 C语言中不可以 在自定义类型中定义函数,即面向对象的思想

      • 在struct中定义的函数,可以直接访问结构体中定义的变量
      • 在函数中直接访问的是调用该函数时结构体变量的属性
    • C++struct中的变量可以用static修饰

      • 需要在struct外面进行定义
      • 在求sizeof时,并不会包含static成员的大小
      • static属性属于类型,而不是属于某个结构体变量 在内存中只有一份,该类型的变量共享
      • 可以直接通过 类名::静态属性 直接访问静态属性
      //结构体中的静态属性  需要在类外进行定义
      类型 结构体名::成员名 = init_val;
      
    • C++struct中可以用访问控制属性(public(公开的,默认),protected,private)加以访问限制

    • C++中struct允许继承

C++中的枚举
  • C++中的枚举是一种独立的数据类型,不能直接用整数进行赋值

    enum Direction{UP,DOWN,LEFT,RIGHT};
    
    Direction d = UP;//cout << d << endl; 依然是整数 
    d = DOWN;
    d = LEFT;
    d = RIGHT;
    
    d = 0;   d = 1;   d = 1024;          //错误的  但是C语言中允许 
    int num = d;
    num = LEFT;  //可以的
    
C++中的联合
  • C++中支持匿名联合(既没有类型,也没有定义联合变量)

  • C++中的匿名联合表示的是变量在内存中的布局方式

  • 可以直接访问匿名联合中的属性

    //以编译时,以联合的形式编译布局a,b,c,即让a,b,c共用一块内存
    union{
        int a;
        int b;
        char c;
    };
    
    a = 1024;
    b = 9527;
    c = 'A';
    
    
C++中的字符串
  • C++保留了C风格的字符串,同时引入了string类型的字符串

  • string的初始化

    string s1;  //空字符串
    string s2 = "Hello world"; //C指针的初始化方式
    string s3("Hello world");
    string s4 = {"HEllo world"};
    string s5 = s4;
    string s6(10,'X'); //"XXXXXXXXXX"
    //指针是一种天然的迭代器
    string s7(iterator begin,iterator end);  //[begin,end)  用这个区间中的字符构造字符串
    
  • string变量的赋值

    string s = "Hello world";
    s = "中国速度";  //对string变量进行赋值
    
    string s1("hello");
    s1 = s;        //字符串之间进行赋值
    
    
  • 字符串的拼接 +

  • 字符串中字符的访问与修改 []

  • 字符串长度

    size_t length();                s.length()
    
  • 返回C风格的字符串

    char *c_str();
    
  • 字符串判断相等 ==

    string a("Hello"),b("Hello");
    if(a == b){//字符串a和字符串b相等
        
    }
    
  • 元素访问

    string s("Hello world");
    //1.下标  []
    cout << s[0] << endl;
    s[1] = 'E';
    //2.at  对比[]  进行越界检查    如果越界会抛出异常   一旦越界,会抛出 std::out_of_range 异常
    char& at(size_t index);
    cout << s.at(0) << endl;   //s[0]
    //3.front  C++11  g++ -std=c++0x
    char& front()   获取首字符  返回其引用
    //4.back   
    char& back()    获取末尾字符          不是\0
    //5.data()
    const char* data();
    //6.c_str()
    const char *c_str();
    
  • 迭代器(指针)

    正向迭代器    string::iterator it
    iterator begin();            //正向迭代器   字符串首字符的地址
    iterator end();              //正向迭代器   字符串最后一个字符的下一个地址
    
    逆向迭代器
    reverse_iterator rbegin()    //逆向迭代器   字符串最后一个字符的地址
    reverse_iterator rend()      //逆向迭代器   字符串第一个字符的前一个地址
        
    //不能通过迭代器修改字符串中的内容
    常正向迭代器   const_iterator  
        cbegin()    cend()
    常逆向迭代器   const_reverse_iterator 
        crbegin()   crend()
        
        
    正向迭代器   ++    往后偏移    --  往前偏移
    逆向迭代器   ++    往前偏移    --  往后偏移
        
    *迭代器             就相当于指针解引用
    
  • 字符数量和容量

    bool empty()              //检查字符串是否为空
    size_t size();            //字符数量
    size_t lenght();          //字符数量
    void reserve(size_t s);           //预分配内存   如果s小于目前的容量则不会干任何事件  只扩不缩
    size_t capacity()         //容量    自动扩容 
    
  • 操作

    void clear();
    //指定位置
    basic_string& insert( size_type index, size_type count, CharT ch );//在index位置插入count个ch字符
    basic_string& insert( size_type index, const CharT* s ); //在index位置插入一个C风格的字符串
    basic_string& insert( size_type index, const basic_string& str, size_type index_str, size_type count );//在index位置插入 str字符串 index_str开始往后count个字符
    
    //指定迭代器
    iterator insert( iterator pos, CharT ch );
    void insert( iterator pos, size_type count, CharT ch );
    void insert( iterator pos, InputIt first, InputIt last );
    
    
    basic_string& erase( size_type index = 0, size_type count = npos );//从index处开始移除,移除count个字符,如果没有传递count,则移除至末尾
    
    iterator erase( iterator position ); //移除指定位置(迭代器)的元素
    iterator erase( iterator first, iterator last );//移除[first,last)区间的元素
    
    void push_back( CharT ch );  //自动扩容 
    void pop_back();
    
    //追加若干个字符
    basic_string& append( size_type count, CharT ch );
    basic_string& append( const basic_string& str );//追加一个字符串
    basic_string& append( const basic_string& str,size_type pos, size_type count = npos );
    
    
    int compare( const basic_string& str ) const;
    支持==
        
    basic_string& replace( size_type pos, size_type count,const basic_string& str );
    
    basic_string substr( size_type pos = 0, size_type count = npos ) const;
    constexpr basic_string substr( size_type pos = 0, size_type count = npos ) const;
    
    //如果没有找到str  则返回 npos     从pos位置开始查找
    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
名字空间
  • 名字空间其实是对代码的逻辑划分,避免名字冲突

  • 名字空间的定义

    namespace 名字空间名{
        定义全局变量;
        定义函数;
        定义类型;
    }
    
  • 使用名字空间中的内容 作用域限定符 ::

    名字空间::标识符                     ---指定使用哪个名字空间中的内容
    
  • 声明名字空间对当前作用域可见

    using namespace 名字空间名;
    
    直接使用标识符           不需要  名字空间::标识符
    
    • 如果使用using namespace声明多个名字空间,如果使用同名的标识符,会引发冲突,引发冲突之后,必须使用 名字空间::标识符
  • 把名字空间中的标识符导入到当前作用域

    using namespace::标识符               优先级会比using namespace 名字空间   优先级高  局部优先原则
    
  • 名字空间名相同的名字空间 自动合并

    • 名字空间名不会有重定义的问题
    namespace N1{
        int a;
    }
    namespace N1{
        int b;
    }
    
  • 名字空间嵌套

  • 匿名名字空间

    • C++会把声明定义在全局区的标识符通通归纳到一个统一的名字空间中,称为匿名名字空间

    • 直接使用 ::标识符 指定访问全局域中的标识符 (匿名名字空间中的标识符)

      int g = 1024;
      int main(){
          int g = 9527;
          cout << g << endl;
          {
              extern int g;
              cout << g << endl;
          }
          cout << ::g << endl;
          return 0;
      }
      
    • 多文件编程中

      • 声明

        namespace N{
            extern int a;
            void func();
            struct Stu{
                int no;
                string name;
            };
        }
        
      • 定义实现

        int N::a;
        void N::func(){
            
        }
        

C++中的函数

  • g++编译器不会再为函数提供隐式函数声明
    • C++调用函数之前,必先声明或定义
  • C语言函数的隐式声明:
    • gcc编译器在编译代码时,如果遇到一个调用的函数在此之前没有定义和声明,则为其隐式声明一个函数,且为其声明函数的返回值类型为int
  • C++函数的形参列表为空 就相当于 C语言中 形参列表为void
C++的函数支持重载
  • 函数重载: 即在同一个作用域下,可以定义同名的函数,但函数的参数列表不同相当

    • 同一个作用域下,函数名相同,参数列表不同即构成重载,在编译调用重载的函数时,会根据实参的个数和类型来绑定调用的函数,静态重载
    • 前提条件:同一个作用域下 如果在不同的作用域,不能构成重载
    • 必要条件:函数名相同,函数列表不同
      • 函数列表不同
        • 参数个数不同
        • 同位置的类型不同
        • 对于指针和引用的类型,常属性不同也能构成重载
          • 如果是非指针和引用,常版本和非常版本会重定义
      • 重载与形参名和返回值类型无关
  • 为什么C++支持重载,而C语言不支持重载

    • g++编译在编译函数时,会对函数进行改名操作 _Zn函数名形参类型简写 _Z3maxii _Z3maxdd _Z3maxiii

    • gcc编译器不会对函数名进行更名操作

    • 可以用 extern “C” 来指定让g++使用C风格的方式来编译函数

      extern "C"   //如果需要在C语言中调用C++的函数,那就必须使用extern "C"来编译C++的代码
      void func(){}
      
      
      extern "C"{
          void f1(){};
          void f2(){}
      }
      
  • 注意调用重载时产生的二义性 (歧义)

    void func(char a,char b){
        
    }
    void func(int a,int b){
        
    }
    
    int main(){
        func('a','b');//有明确类型   没有任何歧义 
        func(1,2);    //没有歧义 
        //func(3.14,5.16);  //歧义   double->char   double->int
        //func('a',5);      //歧义    char,char     int,int
        func('a',3.14);     //没有歧义    char,char  只需要有一个类型隐式转换     
        func(3.14,5);       //int,int 
        return 0;
    }
    
C++的函数支持默认值(缺省值)
  • 在定义函数时,形参可以声明默认值

    • 好像定义变量直接初始化一样
  • 如果一个函数有默认值,则在调用该函数时,可以为该形参传递实参,也可以选择不传,不传则采用默认值

  • 靠右原则

    • 如果一个形参有默认值,则其后面(右边)所有的形参都必要要有默认值
  • 有缺省值注意不要和重载产生歧义(二义性)

    //_Z3barv
    void bar(){
        
    }
    //_Z3barii
    void bar(int a=1,int b=2){
    
    }
    
    //bar();//一调用则产生歧义
    bar(1024);
    bar(9527);
    
  • 如果在函数声明和定义分离,则缺省值只能出现在函数声明中

    void func(int a=1024);
    
    void func(int a){
        
    }
    
C++的函数支持哑元
  • 只有类型,没有形参名谓之哑元

    void func(int){
        
    }
    
    • 在调用哑元函数时,一定要传递一个该类型的实参 只关心类型,不关心值的大小
    • 需要注意和重载的重定义
C++的函数支持内联
  • inline
  • 优化方式 提高代码的运行效率
    • 直接用函数的二进制指定替换函数的调用指令 加快程序执行速度 使可执行程序变大
    • 只是一种申请和建议 取决于编译器
    • 简单且重复调用的函数可以声明为内联函数

C++的动态内存

  • 动态内存:堆内存,需要程序员自己手动申请和手动释放
  • C语言中有一套函数来操作动态内存 malloc/calloc/realloc/free
  • C++申请动态内存 new /new [] 释放动态内存 delete/delete []
申请动态内存
申请单个数据的动态内存
数据类型*  p = new 数据类型(初始值);

int *p1 = new int;  //new一个int类型的内存空间  初始化为"零"
int *p2 = new int(1024);   //new一个int类型的内存空间   并且初始化为1024
int *p3 = new int{9999};   //C++中{}

//释放动态内存
delete p1;
delete p2;
delete p3;

//变量的初始化   大概有三种
int a = 1024;       
struct Stu s = {110,"jack"};
string s("hello");

//C++11进行了统一      不管什么数据,都可以用 {}这样形式进行初始化    {}初始化列表
申请数组的动态内存
数据类型* p = new 数据类型[个数];

int *p1 = new int[10];
int *p2 = new int[5]{1024,9527,1111,2222,618};


//在释放时必须使用 delete[]
delete[] p1;
delete[] p2;
new/delete和malloc/free的区别?
    1. new/delete是C++中申请动态内存的关键字(不是函数),malloc/free是C语言标准库中用于申请动态内存的函数
    1. new和new[]会调用类型的构造函数,但malloc/calloc不会调用类的构造函数

      delete和delete[]会调用类的析构函数,但free不会调用类的析构函数

    1. new和new[]返回的是特定类型的指针,但是malloc/calloc返回的是void *,在使用时,C++需要转换成特定类型的指针
    1. new和new[]申请动态内存失败时,抛出异常bad_alloc,malloc/calloc申请动态内存失败时返回NULL
    1. new申请动态内存时,需要给定特定的类型,不需要自己求类型的字节宽度,但是malloc需要给定申请动态内存的字节大小 new只关心类型 malloc只关心大小
    1. 申请多个内存空间时, new用的是 new[], 但是malloc只需要传递内存大小即可(自己计算)
    1. new申请内存用delete释放,new[]申请的数组内存用delete[]释放,但是malloc/calloc申请的内存一律用free

引用

  • C++想用引用来取代指针,C++依然存在着大量的指针
  • C++引用的底层实现是指针 包装
  • 引用即别名
    • 引用变量和所引用的对象是同一个变量
    • 引用变量的内存地址 和 目标对象的内存地址是 一样的
void swap(int& a,int &b){
    int t = a;
    a = b;
    b = t;
}

int main(){
    int a = 1024,b = 9527;
    cout << "a=" << a << ",b=" << b << endl;
    swap(a,b);
    cout << "a=" << a << ",b=" << b << endl;
    return 0;
}
引用的定义
数据类型& ref = 引用的目标;
  • 引用必须初始化 让引用有确定的引用目标

    int &ra;   //编译错误
    
  • 引用一旦有引用的目标,不能再引用其它的目标

    int n = 1024;
    int & ra = n;  //让ra引用引用n这个变量   ra和n至死方休
    int m = 9527;
    ra = m;        //对引用变量的ra进行赋值   不是让ra引用m变量   ra还是引用n   其实相当于n = m;
    
  • 引用不能为空 指针NULL 不存在空引用

常引用 引用的目标是常量
  • 引用的是常量/变量,不能通过引用修改目标的值
const 数据类型& ref = 引用的目标;

常引用  可以引用常量,也可以引用变量
    
常量如果需要定义引用来引用,那么这个引用必须是常引用

const int& ra = 10;
int a = 1024;
const int& rb = a;

const int& const ref = a;  //错误的
//因为引用本身就不能更改引用目标   对引用的操作实际上都是对引用目标的操作

//没有引用常量这一说
C++中的const
  • C++中定义的const变量是真正的常量,在代码中使用const常量的地方,在编译时就用const常量的值替换(相当于C语言中的宏一样),C++中会为常量分配内存

  • C++中用const定义的常量必须初始化

    const int num;       //编译错误
    
  • C++中变量和对象的const一旦缺失则会报错

C++11中右值引用
  • 右值引用只能引用右值,不能引用左值
int&& ra = 10;
ra = 1024;
const int&& rb = 10;
  • move(左值) 获得其 右值
  • 字面值是右值 const修饰的变量称为常量,但依然是左值
引用普通变量(左值)const变量(只读的左值)右值const属性右值
int&OKNONONO
const int &OKOKOKOK
int&&NONOOKNO
const int&&NONOOKOK
指针与引用的区别?
    1. 指针是实体变量,但是引用不是实体变量

      sizeof(指针) == 4/8 sizeof(引用变量) 不是固定的

    1. 指针可以不初始化 ,但引用必须初始化

      int *p;  //可以的
      int &ra; //错误的
      
    1. 指针可以初始化为空NULL,但引用不能为空
    1. 指针可以改变指向,但引用不能更改引用目标

      int n=1024,m=9527;
      int *p = &n;
      p = &m;   //指针改变指向
      
      int &rn = n;  //引用n     
      //&rn = m;      //rn不能再引用m 
      rn = m;      //对rn进行赋值 
      
    1. 可以定义指针的引用,不能定义引用的指针

      int n = 1024;
      int *pn = &n;
      int *& rpn = pn;  //指针的引用
      
      int& rn = n;
      //int& *pr = &rn;  //无法定义int&(引用)的指针
      
    1. 可以定义指针的指针,不能定义引用的引用

      int n = 1024;
      int *pn = &n;
      int **ppn = &pn;   //指针的指针   二级指针
      
      int& rn = n;
      //int&& rrn = rn;    //错误的   C++11 && 右值引用       没有二级引用
      
    1. 可以定义数组的引用,但不定义引用的数组

      int arr[5] = {1,2,3,4,5};
      int (&ra)[5] = arr;  //ra引用了arr数组  ra就相当于数组名arr
      
      //int& brr[5];        //引用的数组    错误的      不能声明引用的数组
      
  • 引用的本质还是指针,底层用指针实现的引用

显示类型转换

  • 隐式类型转换
    • 数据之间可以自动进行转换 基础数据类型之间都可以进行隐式类型转换
  • 显示类型转换
    • 强制类型转换
      • (目标类型)源对象; 强制类型转换的结果可能是不正确的
    • 静态类型转换
      • static_cast<目标类型>(源对象)
      • 适用场景: 源对象类型和目标类型双方在一个方向上可以进行隐式类型转换,则两个方向上都可以进行静态类型转换
      • TYPE * —> void * 可以进行隐式类型转换
      • SON * —> FATHER * 可以进行隐式类型转换
    • 去常属性类型转换
      • const_cast<目标类型>(源对象)
      • 适用场景:只适用于去除常指针或者常引用的常属性
    • 重解释类型转换
      • reinterpret_cast<目标类型>(源对象)
      • 适用场景:不同类型的指针之间,或者整数与指针之间的类型转换
        • char * int *
        • int void *
        • void * int
    • 动态类型转换
      • dynamic_cast<目标类型>(源对象)
      • 适合场景:只适用于有多态关系的父子指针或者引用类型之间的转换

运算符别名

  • && and
  • || or
  • ! not
  • ^ xor
  • & bitand
  • | bitor
  • {} <% %>
  • [] <: :>

vector 向量(顺序表)

  • 构造 初始化方式

    //1.无参构造
    vector();
    vector<int> v0;   //容量和元素个数都为0
    
    //2.指定初始元素个数
    vector( size_type count, const T& value = T(),const Allocator& alloc = Allocator());
    vector<int> v1(10);    //容量和元素个数都为10   元素全部初始化为 "零"
    vector<int> v2(10,1024);   //容量和元素个数都为10   元素全部初始化为1024
    //Allocator 分配器
    
    //3.用区间元素构造  
    template< class InputIt >
    vector( InputIt first, InputIt last,const Allocator& alloc = Allocator() );
    int arr[5] = {1,2,3,4,5};
    vector<int> v3(&arr[0],&arr[5]); //[first,last)
    vector<int> v4(v3.begin(),v3.end());
    
    //4.拷贝构造
    vector( const vector& other );
    vector<int> v5(v4);
    vector<int> v6 = v4;
    
  • 向量之间可以相互赋值

    vector<int> v1(10,1024);
    vector<int> v2;
    v2 = v1;
    
  • 元素访问

    //1.下标访问
    reference operator[]( size_type pos );
    vector<int> v(10,1024);
    v[0] = 1111;
    v[1] = 1212;
    cout << v[0] << endl;
    
    //2.at    本质和[]是一样的  会进行越界检查,一旦越界  抛出 std::out_of_range异常
    reference at( size_type pos );
    
    
    //3.首元素引用    容器为空时行为未定义
    reference front();
    
    //4.最后一个元素的引用    容器为空时行为未定义
    reference back();
    
    //5.返回首元素地址
    T* data() noexcept;
    
  • 迭代器 向量的迭代器是随机迭代器 支持 ± n

    //正向迭代器   ++ 从前往后    -- 从后往前
    vector<int>::iterator
    begin()    end()
        
    //常正向迭代器
    vector<int>::const_iterator
    cbegin()   cend()
        
    //逆向迭代器    ++  从后往前    --从前往后
    vector<int>::reverse_iterator
    rbegin()   rend()
        
    //常逆向迭代器
    vector<int>::const_reverse_iterator 
    crbegin()   crend()
       
    
  • 容量和元素个数

    bool empty() const;
    size_type size() const;
    
    void reserve( size_type new_cap );   //预分配内存   提高插入的效率   只扩不缩
    size_type capacity() const;
    
  • 修改器 操作容量中的元素

    void clear();   //清空元素  不影响容量
    
    //插入      在指定迭代器位置pos插入元素value
    iterator insert( iterator pos, const T& value );
    //在指定迭代器位置pos插入count个value
    void insert( iterator pos, size_type count, const T& value );
    //在指定迭代器位置pos插入[first,last)迭代器区间的元素
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    
    //原位构造
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    //末尾插入元素value
    void push_back( const T& value );
    
    template< class... Args >
    void emplace_back( Args&&... args );
    
    //删除末尾元素  如果为空则行为未定义
    void pop_back();
    
    //改变容器中元素的个数
    void resize( size_type count );
    	//1.count如果小于容器目前的size          删除末尾的元素,保留前count个元素    容量不会缩小
        //2.count如果大于容器目前的size     则在末尾用"零"(无参构造)填充  可能引发扩容 
    
    void swap( vector& other );
    

新式for循环 C++11

//容器一定得提供迭代器
for(循环变量& n:容器){
    
}
  • 用vector实现学生成绩管理系统

list 双向链表

C++之父的建议

  • 树立面向对象的编程思想
  • 少用宏,多用枚举和内联
  • 少用C风格的字符串,多用string
  • 学会C++的异常处理机制
  • 少用普通数组,多用vector,list

面向对象

  • 面向过程: 把问题分解成若干步骤,把每个步骤又封装成函数,然后按照解决问题的顺序调用各个函数

  • 如何把一头大象装进冰箱?

    • 第一步:打开冰箱门 open(bx)
    • 第二步:把大象装进去 push(bx,dx)
    • 第三步:关上冰箱门 close(bx)
  • 面向对象:把问题看作是对象之间相互作用

    • 面向对象编程中,一切都是类和对象
  • 如何把一头大象装进冰箱?

    • 大象 冰箱
    • 冰箱自动打开 bx.open();
    • 大象能够进到冰箱 bx.push(dx)
    • 冰箱自动关门 bx.close()

类和对象

  • 类 --> 类型 一类事物的统称,泛化的抽象的概念

  • 对象 --> 变量 类实例化的结果,特指一个具体存在的事物

  • 用类定义变量的过程,就叫做实例化对象

  • C++中定义类,可以使用struct,也可以使用class

    class 类名{
    访问控制属性:
        //属性
        //方法
    };
    
    

面向对象的三大特征

  • 封装
    • 把一类事物具有的特征抽象为属性,把一类事物都具有的行为抽象为方法,并加以访问控制属性的限制,称为封装
  • 继承
  • 多态
//封装一个类
class 类名{
    //属性    struct 定义变量
    
    //方法    函数    过程            可以直接访问成员属性
};
实例化对象
  • 用类型定义变量
  • 类名 对象名; 实例化出来的对象,对象的属性是垃圾值,需要额外的赋值语句对其进行赋值
  • 如果需要在构造对象时,给对象进行初始化,需要在定义类时,添加构造函数/方法
  • 实例化对象一定会调用构造函数
//无参构造实例化对象     在栈内存实例化对象
ObjType  obj;   //调用无参构造实例化对象
ObjType  obj(); //函数声明    声明一个函数名为obj   返回值类型为ObjType,且没有任何参数  ***!!!

ObjType  obj(arg,..);   //调用有参构造函数

//C++11
ObjType  obj{};     //调用无参构造
ObjType  Obj{arg,...};  //调用有参构造


//在堆内存中实例化对象
ObjType *po = new ObjType;    //调用无参构造
ObjType *po = new ObjType();  //调用无参构造
ObjType *po = new ObjType{};  //调用无参构造
ObjType *po = new ObjType(arg,...);
ObjType *po = new ObjType{arg,...};


//匿名对象         没有变量(对象)名的对象  
ObjType();              //调用无参构造函数实例化一个匿名对象        
ObjType(arg,...);

构造函数
  • 在实例化对象时调用的函数,称为构造函数

    class 类名{
    public:
        //构造函数
        类名(形参列表){
            
        }
        
    };
    
      1. 构造函数没有返回值类型,也不能声明为void
      1. 构造函数的函数名和类型是一模一样的
      1. 如果一个类没有实现构造函数,则编译器会自动生成一个无参的构造函数

        class A{
        public:
            A(void){//无参构造函数
                
            }
        };
        
      1. 如果一个类中有提供构造函数,则编译器将不再为该类提供默认的无参构造函数

      2. 构造函数允许重载

        ​ 重载构造函数,即可以按照不同的需求来实例化对象

      1. 在构造函数中基本上都是对属性进行初始化
  • 通过类实例化对象,一定需要要匹配的构造函数,否则就会编译报错

练习:

写个长方形类,提供必要的构造函数

为学生类提供构造函数

访问控制属性 — 封装的本质
  • public 公开的

  • protected 受保护的

  • private 私有的

  • 一般来讲,属性会设置为private or protected ,方法会设置为public

  • 一般来讲,为会private的属性提供set和get方法

访问控制属性类中类外子类
publicOKOKOK
protectedOKNOOK
privateOKNONO
struct和class在C++中的区别?
  • class访问控制属性是private,而struct的访问控制属性是public
友元 friend
  • 可以声明全局函数有友元函数,那么在友元函数中可以访问该类的私有/保护成员

    class 类名{
        
    	friend 友元函数返回值类型  函数名(形参列表);
    };
    
  • 可以声明类为友元类,那么在友元类中可以访问该类的私有/保护成员

    • 友元类的声明是单面的
  • 友元其实是打破了封装的效果,但某些情况不得已而为之

this
  • 当成员属性名和成员函数中的局部变量同名时,遵循的是局部优先原则,直接访问,访问的是局部变量

  • this->属性 这样能访问到成员属性

  • this是一个指针 指针常量 Type * const this; 指针是一个常量

  • 在所有的成员函数中,都隐含着一个this指针

    • 构造函数中的this指针指向正在构造的对象
    • 成员函数中的this指针指向正在调用该函数的对象
  • obj.func(); 底层实现会转换为 func(&obj); func(Type *const this);

构造函数初始化列列表
  • 只有构造函数才能有初始化列表
class 类名{
    类名(形参列表):初始化列表{
        
    }
    //初始化列表   :成员属性名(初始化值)            初始化值可以是局部变量,也可以成员变量
};
  • 只有在初始化列表中,才叫初始化

  • 在构造函数体中,都叫赋值

  • 如果一个类中的常属性的成员或者有引用类型的成员,都必须在初始化列表中进行初始化

    class A{
    private:
        const int n;  //常属性的成员
        int& r;       //引用型成员
    public:
        A(int n,int& r):n(n),r(r){
            
        }
        
    };
    
  • 初始化列表的执行顺序与初始化列表中语句顺序无关,只与属性的定义的先后顺序有关

    class B{
    private:
        int n;
        int m;
    public:
        B(int a=1024):m(a),n(m){
            
        }
        int getN(){
            return n;
        }
        int getM(){
            return m;
        }
    };
    
    B b;
    cout << b.getN() << ":" << b.getM() << endl;
    
    
组合 在初始化列表中调用类类型成员的构造函数
  • 关于成员有类类型成员时,在构造函数的初始化列表中,默认调用类类型成员的无参构造函数进行初始化

    class Point{
        double x;
        double y;
    public:
        Point(double x,double y):x(x),y(y){
            
        }
    };
    
    class Circle{
        Point center;
        double r;
    public:
        //center(0,0)即调用 Point(double x,double y)有参构造
        Circle():center(0,0){
            
        }
    };
    
    int main(){
        Circle c;  //编译报错          解决方案有2:   1.让Point提供无参构造   2.在Circle构造函数的初始化列表中显示调用Point的有参构造
        return 0;
    }
    
  • 构造函数的初始化列表中,如果有必要调用有参构造函数进行构造成员,需要显示调用

    :类类型成员(实参,…)

  • 构造函数的执行顺序

    • 先按照类类型成员的定义顺序,依次调用类类型成员的构造函数(默认是无参构造),然后再执行构造函数体
  • 可以利用构造函数在main函数之前和之后输出内容

    class A{
    public:
        A(){
            cout << "A()" << endl;
        }
        ~A(){
            cout << "~A()" << endl;
        }
    };
    A obj;  //在main begin之前输出 A()   在main end之后输出 ~A()
    int main(){
        cout << "main begin" << endl;
        cout << "main end" << endl;
        return 0;
    }
    
单参构造函数和explicit
  • 如果一个类A提供了一个T类型的单参构造函数,那么T类型的数据可以隐式调用该单参构造函数转换为A类型的对象
  • 如果需要禁止T类型数据隐式调用单参构造函数转换为A类型的对象,可以在A类型的单参构造函数前加上explicit关键字
  • 加上explicit之后就不能隐式进行,只能显示调用构造函数进行转换
静态属性和静态方法
静态属性
  • 属性用static修饰,称为静态属性,类属性

  • 静态属性需要在类外进行定义

    class 类名{
    public:
        static int s;//声明      没有分配内存空间
    };
    
    int 类名::s = 1024;   //在类外定义和初始化
    
  • 静态属性属于类,而不属于某个对象

    • 这个类所有的对象都共享 每个对象都能访问,访问到的都是同一个
    • 静态属性存储在全局数据区
    • 成员属性一般存在在栈区或者堆区
    • 静态属性和成员属性分开存储 sizeof(类) 不包含静态属性
  • 静态属性就相当于加了 类名:: 作用域限定的全局变量

    • 静态属性可以直接用 类名::静态属性 的方式直接访问
    • 也可以使用 对象.静态属性 的方式进行访问
静态方法
  • 方法用static关键字修饰,称为静态方法,类方法

  • 静态方法属于类,而不是属于某个对象

  • 相当于加了 类名:: 作用域限定符的 全局函数

  • 可以直接使用 类名::静态方法名(参数列表); 方式直接调用静态方法

  • 也可以使用 对象.静态方法名(参数列表); 方式进行调用

  • 静态方法中没有隐含的this指针 !!!!

    • 静态方法中不能直接访问成员变量
    • 静态方法中不能直接调用成员方法
  • 静态方法中可以直接访问静态属性 和 调用其它静态方法

  • 单例模式

    • 一个类只能实例化一个对象
      • 私有化构造函数
      • 静态方法获得唯一的实例
常对象和常函数
  • C++中const和C语言中的const是不一样的

    • C语言中的const仅仅是表示只读
    • C++const表示却是常量 g++编译器会对const进行替换处理,在编译时对使用const定义变量进行直接替换,好比C语言中的宏定义 C++中const变量必须初始化
  • 常对象,const修饰的对象 常对象的属性是只读(read only)

  • mutable修饰属性,表示常对象也可以修改该属性的值

  • 常方法/常函数(一定得是成员函数才行)

    class A{
        
        //const 常方法、常函数   const修饰的是   *this
        返回值类型  成员方法名(形参列表)const{
            
        }
    };
    
  • 常对象,只能调用常方法 不能调用非常函数

  • 普通对象能够调用常方法 如果没有非常方法的情况下

  • 常函数和非常函数构成重载

    • 常对象只能调用常方法
    • 普通对象在有非常方法时,选择调用非常方法,如果没有非常方法,则调用常方法
  • 在实践过程中,如果一个方法的常方法和非常方法的实现是一样,且不会修改成员时,都会把方法定义成常方法

析构函数
  • 在实例化对象时 创建了一个对象 对象存在生命周期,到对象要消亡时,会自动调用析构函数

    class 类名{
    public:
        //构造函数
        类名(){}
        //析构函数
        ~类名(){}
    };
    
  • 析构函数没有返回值 也不能是void

  • 析构函数的名字 ~类名

  • 析构函数没有参数, 所以析构函数不能重载

  • 如果一个类没有实现析构函数,则编译器会自动生成一个析构函数,该析构函数为一个空析构

  • 析构函数不需要手动调用

    • 对象消亡时自动调用
      • 栈内存中的对象 栈内存回收时
      • 堆内存中的对象 delete
      • 全局数据区的对象 程序结束
  • 什么时候需要自己实现析构函数?

    • 对象有申请动态内存或者打开文件等等操作时 有必要自己实现析构函数
    • 在析构函数中释放动态内存 或者 关闭文件等操作
    • 析构函数避免内存泄露
  • 析构函数执行顺序

    • 正好和构造的顺序相反

实现一个栈 Stack 构造 empty push pop top size()

成员 vector v;

成员属性指针 和 成员方法指针
成员属性指针
成员类型  类名::*指针名;

成员类型  类名::*指针名 = &类名::属性名;  

指针名 = &类名::属性名;

成员属性指针保存的并不是真实的内存地址  (1)

--直接成员指针解引用    对象
    对象.*成员属性指针    
--间接成员指针解引用    对象的指针
    对象指针->*成员属性指针


成员方法指针
//成员方法指针定义
成员函数返回值类型   (类名::*pf)(形参列表);

成员函数返回值类型   (类名::*pf)(形参列表) = &类名::方法名;

pf = &类名::方法名;

(对象.*pf)(实参列表);

(对象指针->*pf)(实参列表);

作业:

1.定义一个平面点类,提供构造函数,提供成员方法计算点到原点的距离

​ 提供成员方法 计算一个点到另外一个点的距离

​ 提供静态方法 计算两个点之间的距离

2.定义一个日期类,成员有年,月,日,是否有效的标识,提供构造函数

​ 提供成员方法计算下一个天

​ 提供成员方法获取 年 月 日分量

​ 提供成员方法计算这是当年的第几天

​ 提供静态函数,计算某一年是否是闰年

3.定义一个复数类

​ 提供构造函数

​ 提供成员方法 计算 复数的 加法 减法 乘法 除法

拷贝构造
  • 由已经存在的同类型的对象构造一个新的副本对象 调用的是 拷贝构造函数

  • 拷贝构造函数的形式是固定不变的

    class 类名{
    	//拷贝构造函数
        类名(const 类名& other){
            
        }
        
    };
    
  • 拷贝构造函数也是构造函数,是构造函数里比较特殊的一个

  • 特殊在 参数 接收一个同类型的对象的常引用

  • 如果在实现一个类时,没有为这个类添加拷贝构造函数,则编译器会自动为该类提供一个默认的拷贝构造函数

  • 默认的拷贝构造函数的实现是 成员复制 (按字节拷贝) 副本和原对象一模一样

  • 如果有自己实现拷贝构造函数,则编译器将不再提供默认的

  • 什么情况下调用拷贝构造函数?

    • 用存在的对象构造新的同类型的对象
    • 在调函数非引用传递对象时调用拷贝构造函数
    • 把对象存储到容器中一律都是调用拷贝构造函数 容器存储的对象是插入对象副本
    • 类名 对象名(旧对象);
    • 类名 对象名 = 旧对象;
  • 什么情况下需要自己实现拷贝构造函数?

    • 如果需要实现深拷贝时则需要自己实现拷贝构造函数 默认的拷贝构造函数是浅拷贝
    • 有指针指向动态内存时一般需要实现拷贝构造函数
  • 浅拷贝 和 深拷贝

    • 浅拷贝 ------------- 按字节拷贝 按字节复制 拷贝的副本和原对象一模一样
      • 对于指针,只拷贝内存地址 拷贝之后,两个指针指向(存储)同一块内存 指针的值是一样
    • 深拷贝 ------------- 相对指针而言(普通数据没有深拷贝这一说) 拷贝指针所指向的内容
      • 对于指针,重新申请内存,把原来指针所指向的内存空间中的数据拷贝过来 新旧指针指向不同的内存空间 但是它们所指向的内存空间中间值是一样的
拷贝赋值
  • 当两个已经存在的同类型对象之间的相互赋值 调用的是拷贝赋值函数

  • 拷贝赋值函数形如

    class 类名{
    //拷贝赋值函数
        类名& operator=(const 类名& other){
            
        }
    };
    
  • 本质是重载=运算符

  • 如果一个类没有实现拷贝赋值函数,则编译器会自动提供拷贝赋值函数,默认的拷贝赋值函数是浅拷贝

  • 如果需要实现深拷贝,则需要自己实现拷贝赋值函数

  • 一般来说,遵循三/五原则

    • C++11之后三原则:析构 拷贝构造 拷贝赋值 要么是全部自己实现,要么是全部默认的
    • C++11之后五原则:析构 拷贝构造 拷贝赋值 移动构造 移动赋值 要么全实现,要么全默认
string
  • 自己实现简单的string类

    class String{
    public:
        //用C风格的字符串构造 String对象
        String(const char *s=NULL):str(strcpy(new char[s?strlen(s)+1:1],s?s:"")){
            /*
            if(s==NULL){
                str = new char[1];
                strcpy(str,""); \\'\0'  str[0] = '\0';
            }else{
                str = new char[strlen(s)+1];
                strcpy(str,s);
            }
            */
        }
        ~String(void){
            if(str != nullptr){
                delete [] str;
                str = nullptr;
            }
        }
        //拷贝构造函数   用同类型的对象拷贝构造一个同类型的副本对象  
        String(const String& s):str(strcpy(new char[strlen(s.str)+1],s.str)){
            //str = new char[strlen(s.str)+1];
            //strcpy(str,s.str);
        }
        // a = b;    a已经存在了    a.operator=(b)     (b = c) = a;
        // a = a;
        String& operator(const String& s){
            if(this != &s){//避免自己给自己赋值时  直接 delete
                String _tmp(s); //拷贝构造一个s的副本
                swap(str,_tmp.str);  //看不到释放内存  但它有释放  delete [] _tmp.str
                //_tmp对象生命周期在语句块结束之后 到期  自动调用析构   
                /*
                char *ps = new char[strlen(s.str)+1];
                strcpy(ps,s.str);
                delete [] str;
                str = ps;
                */
                /*
                delete [] str;
                str = new char[strlen(s.str)+1];
                strcpy(str,s.str);
                */
            }
            return *this;
        }
        size_t length(void)const{
            return strlen(str);
        }
        const char *c_str(void)const{
            return str;
        }
    private:
        char *str;
    };
    
    
    

封装一个栈

​ 不能用vector实现

​ 只允许 int *

​ 构造函数 指定栈的容量 full empty push pop top size capacity

​ 拷贝构造

​ 拷贝赋值

​ 析构

总结默认的无参构造,拷贝构造,拷贝赋值,析构
  • 默认的无参构造(一个类没有实现构造函数时编译器自动提供的)在初始化列表中会调用类类型成员的无参构造函数

    • 如果需要显示调用类类型的有参构造函数,需要在初始化列表中 类类型成员名(实参)
  • 默认的拷贝构造函数会在初始化列表中调用类类型成员的拷贝构造函数

    • 如果实现拷贝构造函数,需要实现对成员的拷贝
  • 默认的拷贝赋值函数会在函数体中调用类类型成员的拷贝赋值函数

    • 如果实现拷贝赋值函数,需要实现对成员的拷贝
  • 默认的析构函数(和空实现析构函数),都会自动调用类类型成员的析构函数

  • 如果一个空类,至少有几个成员函数?

    • 无参构造
    • 拷贝构造
    • 拷贝赋值
    • 析构函数
    • & 取址函数 T* operator&(void)
    • & 常取址函数 const T* operator&(void)const
  • 如果在一个类中,只提供拷贝构造函数,则这个类只有一个构造函数

移动构造 C++11
  • 用一个存在的对象来构造一个同类型新的对象,但是旧的对象是马上就会消亡的,为了效率着想,就没有必要调用拷贝构造(造成资源浪费,复制一份,析构一份),这个时候就可以选择移动构造,把旧对象的资源移动(赠予)新的对象,需要保证旧的对象能够正常析构

  • 旧对象不能再使用了

    class 类名{
    public:
        类名(类名&& obj){
            
        }
    };
    
移动赋值 C++11
  • 用一个存在的对象给另外一个存在的对象的进行赋值,正常情况下,被赋值的资源应该析构,然后把另外一个对象的资源拷贝一份给被赋值的对象,但是如果=右边的对象马上就会消亡时,为了效率着想,就没有必要拷贝一份给被赋值的对象,直接把资源交给(转移给)被赋值的对象

  • =右边的对象不能再使用了

    class 类名{
    public:
        类名& operator=(类名&& obj){
            
        }
    };
    
单例模式
  • 模式 总结出来的开发套路 减少代码重复,提高代码可靠性、效率 且实现特定的功能

  • 23设计模式

  • 单例模式 ---- 一个类只能创建一个对象

    • 懒汉
      • 顾名思义:很懒,不到万不得已,绝对不会动,只有饿了才会去找吃的
      • 单例模式的对象只有有需求时才会创建
      • 优缺点
        • 只有有需求时才会创建,节省内存空间,用完了可以及时释放
        • 线程不安全 两个线程同时去创建可能会创建多份实例,所以需要考虑线程同步,线程同步效率变低
    • 饿汉
      • 顾名思义:很饿,需要时刻备吃的
      • 单例模式的对象一直存在,不管是否有需求,它都一直存在,在程序运行起来,形成内存映像时就会实例化对象
      • 优缺点
        • 不管是否有需求,一直占用内存,浪费内存空间
        • 饿汉模式是在程序加载阶段就去实例,能够保证只有一个线程,线程安全的
  • STL中所有容器都支持深拷贝的

1.实现一个队列类,提供必要的接口,构造、拷贝构造、拷贝赋值、移动构造、移动赋值、析构

2.实现一个简易的vector类,实现构造、拷贝构造、拷贝赋值、移动构造、移动赋值、析构

​ 提供push_back,pop_back等非迭代器的接口函数

运算符/操作符重载
  • = 赋值运算符 拷贝函数 operator=() 编译器自动提供

    重载运算符O

    返回值类型 operator O(形参列表);

  • 运算符的分类

    • 单目运算符 只有一个操作对象 +正号 -负 &取址 *取值 ~按位取反 !取非 ++ – sizeof typeid new delete
    • 双目运算符 操作对象有两个 + - * / % [] << >> > < <= == != = +=
    • 三目运算符 ?: 不能重载
运算符重载的方式
  • 成员的方法
    • 第一个操作数一定是当前类的对象
    • 第一个操作不作为参数的
    • #D D.operator#(void) 单目运算符以成员方式重载
    • N#M N.operator#(M) 双目运算符以成员方式重载
  • 全局函数的方式重载
    • #D operator#(D) 单目运算符以全局(友元方式)函数方式重载
    • N#M operator#(N,M) 双目运算符以全局(友元方式)函数方式重载
  • 如果一个运算符即可以使用成员方式重载,也可以使用全局函数方式重载,只需要选择其中一个方式重载即可,两都都重载会有歧义
运算符的返回值
  • 是否需要引用
  • C返回指针 不能返回普通局部变量的地址 函数调用之后普通局部变量的地址消失
  • C++返回引用 不能返回普通局部对象的引用 函数调用之后普通对象已经消亡
只能以成员方式重载的运算符
=
()    函数运算符
[]    下标运算符
->    
只能全局函数方式重载的运算符
<<   
>>
++/–
//++ --

class X{
    X& operator++(void){
        //
        return *this;
    }
    X operator++(int){
        X tmp(*this);
        //
        return tmp;
    }
    friend X& operator--(X& x);
    friend X operator--(X& x,int);
};

X& operator--(X& x){
    //--
    return x;
}
X operator--(X& x,int){
    X tmp(x);
    //--
    return tmp;
}
<< >>
  • 输入输出运算符只能以全局函数的方式进行重载
  • cout 是 ostream类型的对象
  • cin 是 istream 类型的对象
  • cout 和 cin 不支持拷贝构造 只能传递引用
class X{
    
	friend ostream& operator<<(osteram& os,const X& x);
    friend istream& operator>>(istream& is,X& x);
};

//cout << a << b << c << d << e << endl;  //   operator<<(operator<<(cout,a),b)    
ostream& operator<<(osteram& os,const X& x){
    //os << x.-;
    return os;
}

istream& operator>>(istream& is,X& x){
    //is >> x.-;
    return is;
}
[]下标运算符
  • 只能以成员方式重载

  • 一般都会实现两个

    class X{
      	T& operator[](size_t index){
            
        }  
        const T& operator[](size_t index)const{
            
        }
    };
    
&取址运算符
  • 一般都不会实现 每个对象,不管是否是常对象,都可以进行&运算
class X{
    
	X* operator&(void){
        return this;
    }
    const X* operator&(void)const{
        return this;
    }
};
*解引用
  • 返回对象的引用 右值
  • 迭代器重载*
-> 间接成员解引用
  • 返回对象的指针
  • 迭代器重载->
关系运算符重载
  • 返回值类型都是bool

    class X{
         bool operator<(const X& x)const{
             
         }  
    };
    
  • == != 一般成对出现

  • < <= > >=

逻辑运算符
  • 一般返回值类型都是bool
  • && || !
位运算符
  • & | ~ ^ >> <<
  • 一般不重载
new/delete
  • 以静态函数或者全局函数的方式重载
void *operator new(size_t size){
    void *ptr = malloc(size);
    return ptr;
}

void operator delete(void *ptr){
    free(ptr);
}

void *operator new[](size_t size){
    void *ptr = malloc(size);
    return ptr;
}

void operator delete[](void *ptr){
    free(ptr);
}
() 函数运算符 重载()的类的对象 称为函数对象
  • 函数对象 对象所对应的类型重载了()运算符
  • 仿函数 函数对象可以像函数一样调用函数
  • 在STL模板库中会大量使用仿函数
类型运算符
class X{
    //在X类中重载TYPE类型运算符
    operator TYPE(void){
        return TYPE类型对象
    }
};
  • 在X类中重载TYPE类型运算符,则X类型的对象都可以隐式调用重载的类型函数转换为TYPE类型的对象

  • explicit关键字可以防止X类型对象隐式调用operator TYPE函数,如果需要进行转换,则需要显示调用该函数

    TYPE t = TYPE(x);  //显示调用   operator TYPE 函数
    
类型转换
  • 有A类型对象a和B类型对象b 让a对象转换为b对象的方式有哪些?

    • 如果A和B都是基础数据类型,则a自动转换为b

    • 如果A是基础数据类型,B是类类型对象 在B类型中提供A类型的单参构造函数

      class B{
      public:
          B(const A& a){//A--->B
              
          }
      };
      
    • 如果A是类类型,B是基础数据类型 在A类型中重载B类型运算符

      class A{
      public:
          operator B(void){  //A--->B
              
          }
      };
      
    • 如果A和B都是类类型 则可以在A类中重载B类型运算符 或者 在B类型中提供A类型的构造函数

其它
  • ->*

运算符重载的限制
  • 至少有一个操作数对象为类类型对象

  • 不是所有运算符都能够重载

    ::            作用域限定符
    .             成员访问运算符
    .*            成员指针解引用运算符
    ?:
    sizeof
    typeid
    
  • 不是所有运算符都能够以全局函数的方式重载

    //只能以成员方式重载
    []
    ()
    ->
    =
    类型运算符
    
  • 不能发明新的运算符

面向对象-继承
  • 在定义一个类时,可以继承另外的类,所谓继承,事实上是指继承父类的属性和方法

  • 被继承的类,称为父类(基类) 继承自父类的类称为子类(派生类)

  • 继承之后,子类拥有父类的属性和方法 这就是继承的意义

  • C++允许多继承,一个类继承自多个父类,每个父类的继承方式可以不一样

    class Son:继承方式1 基类1,继承方式2 基类2,继承方式3 基类3,...{
        
        
    };
    
  • 继承方式

    • public 公开继承
    • protected 保护继承
    • private 私有继承
      • class缺省默认的继承方式private 缺省的访问控制属性 private
      • struct缺省默认继承方式是public 缺省的访问控制属性 public
    • 继承方式对于继承的影响
      • public公开继承
        • 把父类中public的属性继承到子类中依然在子类中属于public
        • 把父类中protected的属性继承到子类中依然是protected的属性
        • 把父类中private的属性继承到子类中依然是private的属性(子类中不可以访问)
      • protected保护继承
        • 把父类中public的属性继承到子类中为protected的属性
        • 把父类中protected的属性继承到子类中保留protected的属性
        • 把父类中private的属性继承到子类中依然是private的属性(子类中不可以访问)
      • private 私有继承
        • 把父类中public的属性继承到子类中为子类private属性
        • 把父类中protected的属性继承到子类中为private属性
        • 把父类中private的属性继承到子类中为private属性(子类不可以访问)
在子类中的访问属性父类中public父类中protected父类中private
子类中不能访问
public公开继承publicprotectedprivate
protected保护继承protectedprotectedprivate
private私有继承privateprivateprivate
public公开继承父类本类(继承父类)子类(继承本类)类外
父类中的public属性OKOKOKOK
父类中的protected属性OKOKOKNO
父类中的private属性OKNONONO

protected保护继承父类本类子类类外(通过本类的对象)
父类中public属性OKOKOKNO
父类中protected属性OKOKOKNO
父类中private属性OKNONONO
private私有继承父类本类(全部变成了private)子类类外(通过本类的对象)
父类中public属性OKOKNONO
父类中protected属性OKOKNONO
父类中private属性OKNONONO
  • 继承的意义

    • 复用父类的代码
    • 可以扩展父类的功能
    • 为多态提供基础
  • 继承关系本质上是 Is A 的关系

    • 子类拥有父类全部的属性和方法

      • 一个对象拥有一个类的全部的属性和方法 可以 认为该对象就是这个类实例的对象
      • 人类 class Human; 男人类 继承 人类 女人类 继承 人类
        • 男人 男人类的对象 人类的对象
    • 子类对象 Is A 父类类型对象

    • 子类就是父类集合中的一个子集

      class:继承列表{
          
          
      };
      
子类对象
  • 子类对象拥有父类全部的属性,但是如果父类中的属性为private的,子类虽然拥有,但是无法访问

  • 子类对象在存储时,依然遵循对齐补齐

  • 子类对象中存储着基类对象,称为基类子对象

  • 如果只有一个基类子对象时,那么所有的成员都是一起对齐补齐的

  • 如果一个子类对象中,拥有多个基类子对象,那么每个基类子对象都会独立进行对齐对齐

  • 在多继承中,子类对象中会按照继承顺序在子类对象中储备多个基类子对象

  • 子类对象 is A 父类类型对象

    • 子类类型的指针都可以隐式转换为父类类型的指针 可以用父类类型的指向指向子类对象 ****

    • 子类类型的引用都可以隐式转换为父类类型的引用 可以用父类类型的引用引用子类对象 ****

    • 父类 *p = &子类对象; 事实上p指向的是父类类型的基类子对象 可能和&子类对象的内存地址不一样,可能存储偏移 特别是多个基类子对象

    • 父类 &r = 子类对象; 事实上r引用的是父类在子类对象中的基类子对象

    • 父类 obj = 子类对象; obj实际上是通过基类子对象拷贝构造的 obj是新构造的对象

    • obj = 子类对象 用基类子对象给父类类型对象赋值

class A{
	char a;   
};

class B{
    int b;
};

class C{
  	char c;  
};

class D:public A,public B,public C{
    
};

D d;
A *pa = &d;
B *pb = &d;
C *pc = &d;

A& ra = d;
B& rb = d;
C& rc = d;
子类/派生类的构造函数、拷贝构造、拷贝赋值、析构函数
子类构造函数
  • 子类的构造函数(不管是自己写的还是编译器提供的),在初初始化列表中按照继承顺序,依次默认调用父类无参的构造函数用于构造基类子对象
  • 如果父类中没有无参构造函数,或者需要显示调用父类中的有参构造函数,则需要在子类构造函数的初始化列表中指明调用父类有参构造 :父类名(实参列表)
  • 构造函数的执行顺序(与初始化列表顺序无关):
    • (1)按照继承顺序,依次调用父类的构造函数构造基类子对象
    • (2)按照类类型成员的定义顺序,依次调用类类型成员的构造函数构造成员对象
    • (3)执行构造函数体
子类的析构函数
  • 子类的析构函数会默认调用父类的析构函数用于析构基类子对象
  • 子类的析构函数会默认调用类类型成员的析构函数用于析构成员对象
  • 析构函数的执行顺序 与 构造函数的执行顺序正好严格相反
子类的拷贝构造函数
  • 子类默认的拷贝构造函数,会按照继承顺序依次调用父类的拷贝构造函数拷贝基类子对象,然后按照成员的定义顺序依次调用类类型成员的拷贝构造函数拷贝成员

  • 如果自己实现子类的拷贝构造函数,需要注意拷贝基类子对象和成员对象

    • 需要在拷贝构造函数的初始化列表中显示调用父类的拷贝构造和成员的拷贝构造函数

      class S:public F1,public F2{
      public:
          S(){}
          S(const S& s):F1(s),F2(s),a(s.a),b(s.b),c.(s.c){
              
          }
      private:
          A a;
          B b;
          C c;
      };
      
子类的拷贝赋值函数
  • 子类默认的拷贝赋值函数,按照继承顺序依次调用父类的拷贝赋值函数,然后按照成员的定义顺序依次调用类类型成员的拷贝赋值函数

  • 如果自己实现子类的拷贝赋值函数,则需要手动调用父类的拷贝赋值函数和类类型成员的拷贝赋值函数,因为是手动实现的,所以先后顺序就不会有默认的一样了

    class S:public F1,public F2{
    public:
        S(){}
        S(const S& s):F1(s),F2(s),a(s.a),b(s.b),c.(s.c){
            
        }
        S& operator=(const S& s){
            if(this != &s){
                F1::operator=(s);
                F2::operator=(s);
                a = s.a;  //a.operator=(s.a)
                b = s.b;  //b.operator=(s.b)
                c = s.c;  //c.operator=(s.c)
            }
            return *this;
        }
    private:
        A a;
        B b;
        C c;
    };
    
子类的移动构造和移动赋值
  • 和拷贝构造和移动赋值处理方式一样
名字冲突 — 隐藏
  • 子类中同名的属性和函数会隐藏父类中同名的属性和函数(还有虚函数可以会覆盖,这种情况目前不考虑)
  • 子类中的函数和从父类中继承下来的函数不会构造重载,即使函数名相同,参数列表不同也不会构造重载
  • 隐藏 — 子类中隐藏父为中同名的属性和方法
  • 解隐藏
    • 子类对象.父类名::属性 子类对象.父类名::方法名
    • this->父类名::属性 this->父类名::方法名
用父类指针指向子类对象
  • 事实上指向的是子类对象中的基类子对象 和 真正的子类对象的地址会存在一定的偏移

  • 用父类指针指向子类对象时,只能访问父类的成员和调用父类的方法

  • 父类类型指针指向子类对象时,不能直接隐式转换为子类类型的指针

    • 静态类型转换 static_cast<> 能够计算出子类对象的起始地址
    • 强制类型转换 (SON*) 能够计算出子类对象的起始地址
    • 重解释类型转换 不能得到正确的子类对象的内存地址 reinterpret_cast<> 地址不会偏移
  • 父类指针 *p = new 子类对象();

    • delete p;
      • 父类指针是继承中的第一个父类,只会调用父类的析构函数,造成内存泄露
      • 父类指针是不是继承中的第一个父类,则会引发段错误
    • 在释放时,需要通过类型转换为子类类型
      • delete (子类*)p;
      • delete static_cast<子类*>§;
用父类类型的引用引用子类对象
  • 事实上所引用的是子类对象中的基类子对象
  • 用父类类型的引用引用子类对象时,只能访问父类类型中的属性和方法
  • 子类类型的引用可以隐式转换为父类类型的引用
  • 父类类型的引用引用子类对象时,可以通过static_cast转换为子类类型的引用 能够正确计算子类对象的位置
多态
虚函数 virtual
  • 一个类的成员函数可以声明为虚函数,即在该函数之前加上 virtual关键字
    • 全局函数、构造函数、静态函数、内联函数 不能声明为virtual
    • 析构函数可以声明为virtual,而且在多态中,往往会把析构函数声明为virtual函数
  • 一个函数如果声明为虚函数,则子类在继承这个类时,可以为该虚函数提供覆盖的版本
    • 重写虚函数
覆盖 重写
  • 子类重写/覆盖父类中同类型的虚函数
    • 同类型的虚函数
      • 父类中的函数一定为虚函数 不管子类中是否有virtual关键字,子类重写/覆盖的版本也为虚函数
      • 函数名一定相同
      • 参数列表相同
        • 常规类型 是否有const都能够重写覆盖
        • 对于引用和指针类型 常属性不同不能构成重写覆盖
      • 返回值类型
        • 基础数据类型 或者 类类型 必须相同 常属性必必须相同
        • 如果是类类型的指针或者引用 子类重写覆盖版本的返回值类型可以是父类返回值类型的子类类型的引用和指针
      • 函数的常属性必须一致
      • 子类重写覆盖版本不能声明更多异常说明
重载 隐藏 覆盖的区别
  • 重载: 要求在同一个作用域下,函数名相同,参数列表不同即构成重载

    • 需要在同一个类里面定义的成员函数 函数名相同 与virtual无关 与返回值类型无关 参数列表不同
  • 覆盖: 子类重写覆盖父类中同名的虚函数

    • 一定是子类覆盖父类的虚函数 如果父类中的函数不是虚函数,不能构成覆盖
    • 覆盖版本的函数函数名相同,参数列表相同,函数常属性必须相同,返回值类型相同(如果是类类型的指针和引用,子类覆盖版本返回值类型可以是父类虚函数返回值类型的子类类型的指针或者引用)
      • 如果覆盖版本,函数名相同,参数列表也相同,对返回值类型有要求,如果不满足要求,编译报错
  • 隐藏

    • 如果父类中同名的函数不是虚函数,这个时候,无论参数列表返回值类型是怎么,都构成隐藏
    • 如果父类中的函数是虚函数,同名函数除去覆盖的情况,都构成隐藏
多态的概念
  • 用父类类型的指针指向子类对象 或者 用父类类型的引用引用子类对象,调用是父类中的虚函数,实际调用到的是子类重写版本的虚函数,这种现象称为多态

  • 多态 = 虚函数 + 指针/引用

  • 多态 包含继承的思想

    • 子类对象 is a 父类类型对象
    • 父类中定义统一的接口 (虚函数) 子类提供覆盖的版本
  • 定义立体图形类

    • 提供虚函数 体积 和 表面积
    • 定义球体 圆柱 长方体 类
    • 随机生成10个立体图形 属性值也随机生成 求体积最大 表面积最大的图形

重载 overload

重写 override

虚析构
  • 析构函数可以定义为虚函数,在有多态关系中,往往会把析构函数定义为virtual
  • F *p = new S();
    • delete p;
      • 如果F中的析构函数不是虚函数,则delete只会调用父类的析构函数
      • 如果F中的析构函数是virtual,则delete时调用到是子类的析构函数,执行完子类析构函数体后自动调用父类的析构函数
  • 避免了内存泄露
  • 即使父类析构函数中为空,也经常把析构函数实现,并且声明为virtual
多继承
  • 一个类可以继承多个类,一个类可以有多个父类,一个类也允许有多个子类
  • 子类拥有所有父类的属性和方法
  • 子类对象可以用任意父类类型的指针和引用 指向或者引用
  • java只支持单继承
钻石继承 与 虚继承
  • 钻石继承,一个类继承多个父类,而父类中又有相同的祖先(直接或者间接继承同一个类)
  • 在钻石继承中,父类公共的祖先在子类对象中会存在两份属性(沿着不同的继承路径,在子类对象中生成基类子对象),调用不同父类中的方法,事实上操作的是不同的属性
虚继承
  • 在钻石继承中,可以指针父类的继承方式为virtual继承
  • 如果使用虚函数,则在最终的子类中只会保留一份公共基类的属性,通过不路路径继承下来的方法都是操作同一份属性
纯虚函数 抽象类 纯抽象类
  • 纯虚函数

    • 虚函数没有函数体,直接 = 0;
    • 成员函数 virtual虚函数 没有函数体 virtual double area() =0;
  • 抽象类

    • 拥有纯虚函数的类称为抽象类
    • 抽象类不能实例化对象 所谓抽象,就是不能具体,具体就是指实例化具体的对象
      • 抽象类允许有构造函数,但不允许实例化对象
      • 构造函数的意义在于构造基类子对象 初始化抽象类中的属性
    • 抽象类可以定义指针和引用
      • 抽象类的指针都是指向子类对象
      • 抽象类的引用都是引用子类对象
    • 如果一个类继承了抽象类,那么这个类应该重写覆盖抽象类中所有的纯虚函数,这样子这个子类才不是抽象类(具体类) 只要有一个纯虚函数没有被重写,那么该类就是抽象类
  • 纯抽象类

    • 如果一个类只有构造函数和析构函数不是纯虚函数,(除去构造函数和析构函数,其它所有函数都是纯虚函数),这样的类就称为抽象类
    • 构造函数不能声明为virtual函数
    • 析构函数可以声明为virtual,但不能声明为纯虚函数
  • 抽象类和纯抽象类的意义

    • 封装子类公共的特征和行为,提供一个公共的类型
    • 为子类提供统一的接口(纯虚函数)
    • 虽然不能实例化对象,但可以用抽象类的指针指向子类对象和用抽象类的引用引用子类对象,调用纯虚函数,实际上调用到提子类重写覆盖的版本,这就是多态
C++ 11 =default =delete
=default
  • 显示说明让编译器提供默认的函数
=delete
  • 删除某些函数 禁止使用
  • 比如某些类禁止拷贝构造、拷贝复制 C++11之前如果需要禁止使用拷贝构造和拷贝复制需要私有化
  • 析构函数不能=delete 不能删除析构函数
虚函数
  • 如果一个类中有虚函数,这个类则会在原有属性的基础之上增加一个指针属性(32bit 4 amd64 8)(一般在对象开始位置 存储指针之后存储对象的属性)
  • 如果一个类有虚函数,则该类的每一个对象都会有一个额外的指针,这个指针的值是一样的
  • 每个对象中的这个指针,称为虚函数指针,即指向这个类的虚函数表
虚函数表 和 虚表指针
虚函数表
  • 如果一个类中有虚函数,那么在编译时会为该类生成一个虚函数表,虚函数表即 虚函数指针数组 : 即数组中每一个元素都是虚函数的内存地址
  • 单继承中:如果一个类继承了另外一个类,如果父类中有虚函数表,那么子类将会继承(复制)父类的虚函数表,如果子类中重写父类中的虚函数,那么子类将会用子类重写虚函数的地址替换虚函数表中对应的值,如果子类不重写,则保留父类虚函数的地址,子类如果有新的虚函数会增加
  • 多继承中,如果两个父类中都有虚函数表,则会继承两个父类的虚函数表,子类自己的虚函数增加到第一个虚函数表中,子类重写的虚函数,会替换对应虚函数表中的值
虚表指针
  • 一个拥有虚函数类的对象,都拥有一个指针,这个指针都指向该类的虚函数表的位置,这个指针称为虚表指针
  • 一个拥有多个虚函数表的对象,会拥有多个指针,每一个指针都是一个虚表指针,指向对应虚函数表
动态绑定 动态运行时
  • 静态绑定 重载 静态多态

    • 编译阶段,对调用的函数进行绑定,根据参数的个数和类型来绑定调用重载的函数
  • 动态绑定 多态 动态多态

    • 用基类的指针指向子类对象或者用基类的引用引用子类对象,调用基类中的虚函数时,编译器不会直接生成调用指令,而是用一段特殊的指令来替换,这段特殊的指令完成的工作是:
      • 通过指针或者引用 找到目标对象的虚表指针
      • 通过虚表指针找到虚函数表 (找到目标对象真实类型的虚函数表)
      • 通过虚函数表,找到对应的虚函数的地址,调用该虚函数
    • 所谓动态绑定,就是在编译阶段无法确定调用哪个函数,只有当运行阶段,才确定调用的函数
  • 过多的动态绑定(多态)会使得程序的运行效率变低

动态运行时信息 (RTTI Run-Time Type Identification)
  • Object Oriented Programming OOP

  • #include

    • typeid 操作符,获取一个变量的运行时信息

    • 如果没有多态的情况下,typeid获得的就是变量本身的类型

      //没有多态的情况下
      A *p = xx;
      typeid(*p)    ==   typeid(A)
          
      A& ra = xx;
      typeid(ra)    ==   typeid(A)
      
    • 如果存在多态,则typeid会追踪指针所指向的目标 和 追加引用所引用的目标 的真实类型

      F *p = new S();
      typeid(*p)  ==   typeid(S)
      S s;
      F& rf = s;
      typeid(rf)  ==   typeid(S)
      
  • typeid返回动态运行时信息对象

    • 支持 name() 获取类型信息
    • 支持 == 和 != 比较
动态类型转换
  • dynamic_cast<目标类型>(源对象)

  • 只能适用于有多态关系的父子类型之间的指针和引用转换

  • 动态类型转换会校源对象和目标类型是否一致,只有类型一致才能转换成功,否则转换失败,返回NULL

  • 而static_cast 和 reinterpret_cast 和 强制类型转换都不会检验源对象是否能够转换成目标对象,在程序中要避免这类转换

  • 项目另外一部分完成 登录校验

  • 部门管理 员工管理

  • 模型

  • 分层

    • 水平 用户界面 逻辑实现 数据访问
    • 垂直 接口层(纯抽象类 接口) 实现层 对象层
UML

UML (Unified Modeling Language)为面向对象软件设计提供统一的、标准的、可视化的建模语言

流程图

C++类图

异常机制
C语言的错误处理
  • 通过返回值来表示 需要返回特殊的值

    • fopen malloc 返回NULL表示出错 非NULL就是正常的值

    • fread fwrite 返回0表示出错 非0表示正确读写次数

    • 缺陷

      • 层层判断函数返回结果,且正常代码逻辑和错误处理逻辑代码混在一起的,如果有多种错误,还需要为每一个错误设定一个特殊返回值
      • 有些函数正常的返回值就可以取到任意结果,这种情况就无法用特殊值来表示错误
    • 当调用C语言标准库中的函数或者系统调用失败之后,设置全局的errno,获取错误信息的方式有

      #include <errno.h>
      extern int errno;     //全局的errno
      
      #include <string.h>
      //通过errno(错误码)获取对应的错误信息
      char *strerror(int errno);
      
      const char *perror(const char *str);
      
      //printf   %m    格式占位符   自动用strerror(errno)来获取对应的错误信息
      
      • 只有当调用失败时才会设置errno,如果调用成功,不会把errno的值设置为0
      • errno的作用,只能通过判断函数的返回值确定函数调用失败之后,用errno来获取错误信息,不能直接通过errno!=0来表示函数调用成功
  • setjmp/longjmp

    #include <setjmp.h>
    
    int setjmp(jmp_buf env);
    void longjmp(jmp_buf env,int val);
    
    jmp_buf是一个类型,每使用一次,需要定义一个jmp_buf类型的全局变量
    setjmp第一次调用都是返回0
        当调用longjmp函数时,代码会直接跳转到setjmp函数处,并使用setjmp函数返回val(要非0)值
    
    //信号集
    int sigsetjmp(jmp_buf env,int savesigs);
    void siglongjmp(jmp_buf env,int val);
    
    • 缺陷
      • 在C++中,局部对象无法得到析构(在调用longjmp函数之前的局部对象) 内存泄露
C++的错误处理方式
  • 可以使用C语言的错误处理方式,但都会有缺陷

  • C++建议使用异常机制

    • C++在throw抛出异常之前,会对局部对象进行析构,保证内存资源不会泄露
  • 异常机制

  • 当程序出错之后,可以throw抛出异常,抛出异常之前,会对所有的局部对象进行析构,同时从抛出异常的代码中直接往外跳出(抛出异常之后不会继续往下执行)去匹配最近层次的try-catch语句,根据异常的类型来匹配catch分配中的异常,如果异常类型的对象 Is a catch中的异常类型的对象,则进入到catch分支中进行异常处理,异常处理完成之后,继续从最后一个catch分支往下正常执行,如果catch了异常,但无法处理,则可以继续往外抛出,一个异常要么被捕获,要么不一直往外抛出,直到系统捕获,执行默认处理(显示异常信息,终止程序执行)

  • 语法

    try{
        //可能发生异常的语句
        
    }catch(异常类型& e1){//如果发生异常之后,首先匹配e1,如果不满足则匹配e2...
        
    }catch(异常类型& e2){
        
    }catch(异常类型& e3){
        
    }catch(...){   //如果上面没有匹配的异常类型,它一定会捕获异常,进入该分支     相当于else分支
        
    }
    
    
    在一个完整的try catch分支中,如果出现异常则会从上至下对catch进行匹配,如果有匹配的,则进入该分支,后面的则不会再匹配,所以如果异常类型有继承的话,子类异常应该在父类异常catch之前进行catch;可以在最后加上catch(...)确保所有异常都能被捕获
    
标准异常
  • 所有的标准异常都直接或者间接继承至 std::exception 类
  • logic_error
    • out_of_range
    • invalid_argument
  • runtime_error
    • range_error
    • overflow_error
    • underflow_error
  • bad_typeid
  • bad_cast
  • bad_alloc
C++中一般用异常类型来表示不同的错误
  • C++中也允许用一种类型不同的值来表示不同的错误
  • C++中也允许使用任意类型来表示异常
C++中可以自己抛出异常
throw  异常类型(实参列表);
--throw是一个关键字,可以抛出异常
C++异常处理流程
  • 正常逻辑代码和异常处理分离的
  • 局部对象能够正确析构
  • 每一层只捕获自己能够处理的异常,不能处理的异常让其继续往外抛出
  • 如果要确保捕获所有异常,可以用catch(…)
  • 如果有异常没有被捕获,会把异常交给操作系统作默认处理(显示异常信息,终止程序执行)
  • 程序员可以自己定义异常类型,一般都会遵循直接或者间接继承exception类的原则,并且重写what()函数
  • 程序员可以使用throw关键字抛出异常,某些特殊情况下,一个异常如果被catch了,但又无法处理,则可以继续用throw抛出
函数的异常说明
函数返回值类型 函数名(形参列表)throw (异常类型,...) {  //如果是成员函数,还可以有const
    
}
  • 函数的异常说明表示的是该函数中抛出的异常可以被捕获的异常有哪些类型,除了异常说明以外所有抛出的异常类型都无法被catch
  • 往往用于说明一个函数可能抛出的异常种类,在try-catch时,只需要catch异常说明中的异常类型即可
  • 一个函数如果没有异常说明,则表示该函数可以抛出任意的异常,且所有的异常都可以被捕获
  • 一个函数的异常说明如果为 throw() 则表示该函数抛出的所有异常都无法被catch
  • C++11增加了 noexcept 说明符,表示该函数不抛出任意异常,如果一旦抛出异常,则该异常无法被catch
  • 在重写覆盖中,子为重写版本函数的异常说明,不能比父类版本有"更多"的异常说明
    • 子类重写版本不能放松对于throw的限定
关于构造函数和析构函数中的异常
  • 如果一个函数在构造过程中产生了异常,则该对象称为构造不完全对象/不完整对象,不完全对象是无法进行析构的(不会调用析构函数),所以,如果在构造函数中产生了异常,需要在构造函数中自行捕获处理(如果在异常产生之前,构造函数中有new动态内存,则需要手动释放),并且抛出异常
  • 永远不要在析构函数中抛出异常
C++IO库
  • #include

  • C++程序不直接处理输入输出,提供了标准库

    • istream 输入流类型,提供输入操作

    • ostream 输出流类型,提供输出操作

    • cin 是一个istream对象,从标准输入读取数据 scanf sscanf fscanf

    • cout 是一个ostream对象,向标准输出写入数据 printf sprintf fprintf

    • cerr 是一个ostream对象,通常用于输出程序的错误信息,写入标准错误

      >>运算符,用来从一个istream对象读取输入数据
      <<运算符,用来从一个ostream对象写入输出数据
      
  • iostream 定义了用于读写民的基本类型 scanf/printf

    • istream wistream
    • ostream wostream
    • iostream wiostream
  • fstream 定义了读写文件的类型 fscanf/fprintf

    • ifstream wifstream
    • ofstream wofstream
    • fstream wfstream
  • sstream 定义了读写内存string对象的类型 sscanf/sprintf

    • istringstream wistringstream
    • ostringstream wostringstream
    • stringstream wstringstream
  • w标准库定义一组类型和对象来操作wchar_t类型的数据,宽字符版本类型和函数的名字以一个w开始

                                ios_base
                                  |
                                 ios
                           /              \
                      istream                ostream
                  /       |       \      /          |       \
           ifstream istringstream  iostream  ostrimgstream  ofstream
                                   fstream
                                   stringstream
               
io对象无拷贝和无赋值
  • 所有的IO对象,都不可以进行拷贝构造和拷贝赋值

  • 进行IO损人和函数通常以引用方式传递和返回流

  • 读写一个IO对象会改变其状态,因此IO对象流也不能是const

    ostream& operator<<(ostream& os,const Type& obj);
    istream& operator>>(istream& is,Type& obj);
    
条件状态
标识 函数条件状态
iostateiostate是一种机器相关的类型,提供了表达条件状态的完整功能 int
badbit用来指出流已崩溃 某一个二进制为1
failbit用来指出一个IO操作失败了 某一个二进制为1
eofbit用来指出流到达了文件结束 某一个二进制为1
goodbit用来指出流未处于错误状态,此值保证为0
ios.eof()若ios的eofbit置位,则返回true
ios.fail()若ios的failbit或badbit置位,则返回true
ios.bad()若ios的badbit置位,则返回true
ios.good()若ios处于有效状态,则返回true
ios.clear()将流ios中所有条件状态复位,将流的状态设置为有效,返回void
ios.clear(flags)清除,flag就是iostate类型的变量
ios.setstate(flags)设置
ios.rdstate()获得流ios的当前条件状态
管理输出缓冲
1.输出\n
2.缓冲区满了
3.强制刷新缓冲区   flush(stdout)
4.printf-->scanf
5.程序正常结束
1.输出endl(换行)ends(空白字符,书上说会刷新缓冲区)
    	endl 换行   向输出缓冲区输出一个换行,然后刷新缓冲区
        ends 空字符  
2.缓冲区满了
3.flush                cout << "hello" << flush
4.程序正常结束   
5.unitbuf操作作符
    	如果想每次输出之后都刷新缓冲区,可以使用  cout >> unitbuf;
		可以使用  cout >> nounitbuf;  恢复正常情况
6.关联输入和输出流   cin.tie(&cout)         cout--->cin
    交互式系统通常应该关联输入流和输出流,意味着所有的输出,包括用户提示信息,都会在读操作之前被打印出来。
    每个流同时最多关联到一个流,但多个流可以同时送给到同一个ostream   

IO操纵符
定义于头文件 <ios>
boolalpha noboolalpha在布尔值的文本和数值表示间切换 (函数)
showbase noshowbase控制是否使用前缀指示数值基数 (函数)
showpoint noshowpoint控制浮点表示是否始终包含小数点 (函数)
showpos noshowpos控制是否将 + 号与非负数一同使用 (函数)
skipws noskipws控制是否跳过输入上的前导空白符 (函数)
uppercase nouppercase控制一些输出操作是否使用大写字母 (函数)
unitbuf nounitbuf控制是否每次操作后冲洗输出 (函数)
internal left right设置填充字符的布置 (函数)
dec hex oct更改用于整数 I/O 的基数 (函数)
fixed scientific hexfloat defaultfloat (C++11)(C++11)更改用于浮点 I/O 的格式化 (函数)
定义于头文件 <istream>
ws消耗空白符 (函数模板)
定义于头文件 <ostream>
ends输出 ‘\0’ (函数模板)
flush冲洗输出流 (函数模板)
endl输出 ‘\n’ 并冲洗输出流 (函数模板)
定义于头文件 <iomanip>
resetiosflags清除指定的 ios_base 标志 (函数)
setiosflags设置指定的 ios_base 标志 (函数)
setbase更改用于整数 I/O 的基数 (函数)
setfill更改填充字符 (函数模板)
setprecision更改浮点精度 (函数)
setw更改下个输入/输出域的宽度 (函数)
格式化IO
<<    >>      格式化的IO
    读取时默认到空白字符结束   会自动跳过空白字符 ws
字符IO
get()
put()
    
peek()   查看 不读走
unget()
putback()    
字符串IO
getline()
basic_istream& getline( char_type* s, std::streamsize count, char_type delim );
二进制IO
read()
write()
随机IO
tellg()
seekg()
    
tellp()
seekp()
    
 ios::beg
 ios::cur
 ios::end
文件流对象
ifstream in(file);      //构造一个ifstream对象并打开给定的文件   
ofstream out;           //输出文件流对象并未关联到任何文件
         out.open(file)
fstream  ios(file,mode)
      mode
      in      以读方式打开                     ios::in
      out     以写方式打开
      app     每次写操作前均定位到文件末尾
      ate     打开文件后立即定位到文件末尾
      trunc   截断文件
      binary  以二进制方式进行IO
  • 只可以对ofstream和fstream对象设定out模式
  • 只可以对ifstream和fstream对象设定in模式
  • 只有当out也被设定时才可以设trunc模式
  • 只要trunc没有被设定,就可以设定app模式,在app模式下,即使没有显式指定out模式,文件总是以输出方式被打开
  • 即使没有指定trunc模式,以out模式打开的文件也会被截断
  • 如果需要保留文件中的内容进行写,必须使用out和app模式
sstream
  • istringstream

    string str("1024 952.7 hello");
    istringstream is(str);
    int n;double d;string s;
    is >> n >> d >> s;
    
  • ostringstream

    ostringstream os;
    os << n << " " << d << " " << s;
    cout << os.str() << endl;
    

模板与泛型编程

  • 所谓模板就是泛型编程,为了编写更加通用的程序代码(代码的复用),把类型抽象化,实现算法与类型分离,编写更加抽象的类和函数
函数模板
template<typename T,...>
返回值类型  函数模板名(形参列表){
    
}
//template模板关键字<模板类型参数>    
//typename关键字  可以用   class  替换
//模板类型参数可以有很多,它不是具体的类型,每一个模板参数都需要用逗号隔开
函数模板实例化—>模板函数
  • 在使用函数模板时,需要用具体的类型进行实例化

  • 函数模板名<类型参数列表>(实参列表);

  • 自动类型推导

    • 某些函数(模板类型用于函数模板的参数列表)模板支持自动类型推导
    • 根据使用时传递的实参,根据实参的类型进行推导,有些情况下,根据类型进行推导可能产生二义性,这个时候就需要提供类型参数
函数模板特化
  • 针对某些特殊的类型,通过的函数模板可能不适用,就需要针对这种特殊的类型写一个特化的函数

    template<>
    返回值类型 函数模板名(实参列表){
        
    }
    
函数模板支持非类型参数
template<unsigned N,unsigned M>
int compare(const char (&p1)[N],const char (&p2)[M]){
    return strcmp(p1,p2);
}

int ret = compare("Hello","hi");
  • 注意,在对非类型模板参数实例化,只能用常量或者常量表达式
模板编译
  • 当编译器遇到一个模板定义时,它并不生成代码,只有当实例化出模板的一个特定版本时,编译器才会生成代码。所以模板会进行二次编译,第一次编译是检查模板本身的语法,第二次编译是模板实例化之后,生成特定类型版本的函数和类进行编译。

  • 多文件编程时,函数和类的声明放在头文件中.h,函数和类的定义放在实现文件中.cpp

  • 模板可以把模板的声明和定义放在不同的文件中吗? 显然是不可以的

    • 如果只包含头文件(模板的声明),在实例化时将无法得到模板的定义,所以模板编程中,会把模板的声明和定义直接放在头文件中
编译错误报错
  • 第一阶段,编译模板代码,针对模板本身进行语法检查,需要模板代码满足模板语法规则
  • 第二阶段,在使用模板实例时,检查实例化时模板参数的个数是否相等,检查参数类型是否匹配
  • 第三阶段,模板实例化,根据模板用具体类型实例化,生成具体的模板函数或者模板类,再次编译模板函数和模板类,进行语法检查,只有在这个阶段,才能发现类型相关的问题(不支持的运算符,没有链接到函数)
普通函数和模板函数一样时
  • 如果普通函数和模板函数同名且参数列表一致时,在调用函数时,肯定会选择匹配度更高的版本,会优先选择普通函数调用,如果普通函数和模板函数都一样,优先选择普通函数,如果想调用函数模板,则需要显示类型实例化;函数模板中如果针对类型有特化的版本,则调用特化的版本

    //通用的函数模板
    template<typename T>
    void func(T a){
        
    }
    //特化的函数模板
    template<>
    void func(int a){
        
    }
    //普通函数
    void func(int a){
        
    }
    
    func(10);//普通函数
    func<int>(10); //特化版本
    func('a');//通用的函数模板
    
尾置返回值类型
tempate<typename IT>
auto maxarr(IT beg,IT end)->decltype(*beg){
    
}
类模板
  • 通用的类模板
template<typename T,...>
class 类模板名{
    
    
};
  • 类模板在实例化时无法进行自动类型推导
针对特殊的类型进行全员特化
  • 类所有的属性和方法都需要重新写过

    template<>
    class 类模板名<特化的类型>{
        //实现类
        
    };
    
针对特殊的类型进行成员特殊
  • 只针对特殊的几个函数进行特化

    template<> 返回值类型 类模板名<特化的类型>::成员方法名(形参列表){
        
    }
    
针对特殊的类型进行偏特化(局部特化)
  • 针对指针或者数组类型的偏特化 或者 针对类型本身特殊情况的特化

    template<typename T>
    class A<T[]>{//针对数组仿特化
        
    };
    
    template<typename T>
    class A<T*>{//针对指针的偏特化
        
    };
    
    template<typename T1,typename T2>
    class B{
        
    };
    
    template<typename T1>
    class B<T1,T1>{//针对两个类型相同情况下的偏特化
        
    };
    
    • 在实例化,会选择特化程序更高的匹配版本进行实例化
类模板允许有非类型参数
  • 非类型参数在实例化时必须是常量或者常量表达式
类模板允许有模板缺省值
  • 缺省模板参数需要满足靠右原则
  • 函数模板在C++11以后也允许有模板缺省参数
普通类中含有模板成员方法
可变长类型模板参数
void func(){
    
}

template<typename t,typename ...Args>
void func(const T& t,const Args& ...args){
    //cout << t << " " << endl;
    func(args...);//func()
}

template<typename T>
class X{
    template<typename ... Args>
    void emplace(iterator pos,Args&& ...args){
        
    }
};

STL

  • Standard template library C++标准模板库
六大组件
  • 容器(containers)

    • 容器就是用来存储数据对象,数据结构
    • 线性容器 顺序容器
      • array 静态数组 C++11
      • forward_list 单向链表 C++
      • vector 向量 动态顺序表 支持扩容 支持 []
      • list 双向链表
      • deque 双端队列 可以在两端进行快速插入和删除的顺序表 支持扩容 支持[]
    • 容器适配器(底层是线性容器,只保留某些特殊的接口)
      • stack 栈 FILO
      • queue 队列 FIFO
      • priority_queue 优先队列 "优"者先出
    • 关联容器
      • 有序关联容器(红黑树,多重:意味着红黑树中允许有重复的元素)
        • set 集合
        • multiset 多重集合
        • map 映射
        • multimap 多重映射
      • 无序关联容器(哈希表)
        • unordered_set 集合
        • unordered_mulitset
        • unordered_map
        • unordered_multimap
      • 民间大神实现的无序关联容器(不是STL)
        • hashmap/hashset/hashmultimap/hashmultiset
  • 迭代器(iterators)

    • 容器和算法之间的粘合剂,用来连接容器和算法
    • STL容器(除了容器适配器)都有自己专属的迭代器
    • 原生的指针也是一种迭代器
    • 输入迭代器 支持++ == != *
    • 输出迭代器 支持++ == != *
    • 正向迭代器 支持++ == != * ->
    • 逆向迭代器
    • 双向迭代器 支持++ – == != * ->
    • 随机迭代器 支持 +n -n ++ –
    • 常迭代器
  • 算法(algorithms)

    • sort
    • search
    • copy
    • find
  • 仿函数(functors)

    • 仿函数其实就一个类看起来像函数,其实就是重载了operator()运算符
    • 函数对象
  • 配接器(adapters)

    • 修饰容器,仿函数或者迭代器接口的东西
    • 配接器修改类的接口,使原来不相互匹配的两个类可以相互匹配,进行合作
    • bind
  • 配置器(allocators)

    • 配置器主要负责空间的配置和管理
六大组件之间的关系
  • 容器通过配置器获取数据存储空间
  • 算法通过迭代器获取容器的内容
  • 仿函数协助算法完成策略变化
  • 配接器修饰或者套接仿函数(bind)
智能指针
auto_ptr (c++11之前 C++17删除)
unique_ptr
shared_ptr
weak_ptr
容器的共性
  • 1.容器存储的数据都是副本(拷贝构造一份)存储到容器的数据需要支持完整语义的上拷贝
  • 2.容器本身都支持拷贝构造和赋值 (都是深拷贝)
  • 3.容器都支持swap
array 静态的连续数组
  • 封装的固定大小的数组容器

  • 固定大小数组 不能进行增加元素,也不能删除元素

    #include <array>
    template< class T,std::size_t N> 
    struct array;
    
    //T 即数组中存储的数据类型      N即数组长度
    
  • 构造

    array<int,10> arr;
    array<int,5> brr = {1,2,3,4,5};
    array<int,5> crr{1,2,3,4,5};
    array<int,5> drr;
    drr.fill(1024);
    array<int,5> err(drr);
    arr = brr;
    
  • 元素访问

    //获取指定下标位置的元素   返回的元素的引用   如果越界会产生异常
    reference at(size_type pos);          T& at(size_t pos);
    const_reference at( size_type pos ) const;
    
    //越界访问是未定义
    reference operator[]( size_type pos );
    const_reference operator[]( size_type pos ) const;
    
    //所有容器支持at和[]都是一样的    array  vector和deque     时间复杂度O(1)
    //关联容器都支持[]         O(logn)      O(1)--O(n)
    
    //首元素
    reference front();
    const_reference front() const;
    
    //最后一个元素
    reference back();
    const_reference back() const;
    
    T* data() noexcept;
    const T* data() const noexcept;
    
    
  • 迭代器

    //迭代器支持随机迭代和双向迭代  +n   -n
    begin cbegin  返回指向起始的迭代器
    end cend      返回指向末尾的迭代器
    rbegin crbegin  返回指向起始的逆向迭代器
    rend crend      返回指向末尾的逆向迭代器
    
    
  • 容量

    constexpr bool empty() const noexcept;
    constexpr size_type size() const noexcept;
    constexpr size_type max_size() const noexcept;
    
  • 操作

    void fill( const T& value );
    void swap( array& other ) noexcept;
    
vector 动态连续数组
  • 向量

  • 可扩容的顺序表

  • 内存连续

  • 在末尾插入和删除的效率O(1) 如果在末尾插入引发扩容,时间最复杂度O(n)

    • 提供了push_back/pop_back
  • erase/insert平均时间复杂度O(n) 越靠近front效率越低

  • 尽量避免频繁地进行扩容(分配新的内存,然后把原内存中的数据拷贝过来(拷贝构造)),频繁扩容造成效率低下

  • 通过push_back和insert插入到vector中对象,都是经过拷贝构造的对象

  • 通过emplace(涉及到移动元素)和emplace_back(涉及到扩容 ) 原位构造,只会调用构造函数,效率会比push_back和insert要好

template< class T,class Allocator = std::allocator<T> > 
class vector;
  • 元素访问

    at
    operator[]
    front()
    back()
    data()
    
  • 迭代器

    //迭代器支持随机迭代和双向迭代  +n   -n
    begin cbegin  返回指向起始的迭代器
    end cend      返回指向末尾的迭代器
    rbegin crbegin  返回指向起始的逆向迭代器
    rend crend      返回指向末尾的逆向迭代器
    
  • 容量

    empty()
    size()
    max_size()
        
        
    reserve    预留存储空间    只扩不缩    提高vector插入效率的有效手段  减少自动扩容
    capacity   返回当前存储空间能够容纳的元素数     
    
    
    shrink_to_fit   通过释放未使用的内存减少内存的使用    让容量等于元素的个数
    
    
    
  • 修改

    clear()    调用类的析构析构vector中所有对象    元素为0   容量不变
    insert()   在指定的迭代器前插入 副本   拷贝构造    移动元素
    emplace()  在指定的迭代器前插入 原位构造   移动元素
    erase()    删除指定迭代器  迭代器范围
    emplace_back()
    push_back()
    pop_back()    
    resize()   改变元素个数  可能引发扩容,调用拷贝构造和构造函数    可能使元素减少(容量不变),调用析构
    swap()
    
forward_list
  • 单向链表
#include <forward_list>
template<class T,class Allocator = std::allocator<T>> class forward_list;
  • 在任何位置插入和删除的时间复杂度都为O(1)

  • forward_list的插入和删除都是在指定迭代器后进行的

  • 元素访问 不支持随机访问

    reference front();
    const_reference front() const;
    
  • 迭代器 只支持正向迭代 只能++ 不能–

    //第一个元素之前的迭代器     保证能在第一个位置进行插入和删除
    iterator before_begin() noexcept;
    const_iterator before_begin() const noexcept;
    const_iterator cbefore_begin() const noexcept;
    
    //首元素
    iterator begin() noexcept;
    const_iterator begin() const noexcept; 
    const_iterator cbegin() const noexcept; 
    
    //最后一个结点的next
    iterator end() noexcept;
    const_iterator end() const noexcept;
    const_iterator cend() const noexcept;
    
    
  • 容量

    bool empty() const noexcept;
    size_type max_size() const noexcept;
    
  • 修改器

    void clear() noexcept;
    
    //在指定迭代器pos后面插入   
    iterator insert_after( const_iterator pos, const T& value );//
    iterator insert_after( const_iterator pos, T&& value );
    iterator insert_after( const_iterator pos, size_type count, const T& value );
    template< class InputIt >
    iterator insert_after( const_iterator pos, InputIt first, InputIt last );
    iterator insert_after( const_iterator pos, std::initializer_list<T> ilist ); 
    
    
    initializer_list   初始化列表   {v1,v2,...}
    
    template< class... Args >
    iterator emplace_after( const_iterator pos, Args&&... args );
    
    
    //删除之后 返回下一个元素的迭代器
    iterator erase_after( const_iterator pos );
    iterator erase_after( const_iterator first, const_iterator last ); 
    
    //因为在单向链表头插入和删除的时间复杂度为O(1)     在末尾插入和删除的效率为O(n)
    void push_front( const T& value );
    void push_front( T&& value );
    
    template< class... Args >
    void emplace_front( Args&&... args );
    template< class... Args >
    reference emplace_front( Args&&... args );
    void pop_front(); 
    
    void resize( size_type count );
    void resize( size_type count, const value_type& value ); 
    void swap( forward_list& other );
    
  • 操作

    //如果存储是类类型成员  调用的是  operator<
    void merge( forward_list& other );
    void merge( forward_list&& other );
    
    //Compare比较器
    template <class Compare>
    void merge( forward_list& other, Compare comp );
    template <class Compare>
    void merge( forward_list&& other, Compare comp ); 
    
    
    //把另外一个forward_list中的结点剪切到当前单向链表的Pos后面
    void splice_after( const_iterator pos, forward_list& other );
    void splice_after( const_iterator pos, forward_list&& other );
    void splice_after( const_iterator pos, forward_list& other, const_iterator it );
    void splice_after( const_iterator pos, forward_list&& other, const_iterator it );
    void splice_after( const_iterator pos, forward_list& other, const_iterator first, const_iterator last );
    void splice_after( const_iterator pos, forward_list&& other, const_iterator first, const_iterator last ); 
    
    
    
    //移除指定的元素value
    void remove( const T& value );
    size_type remove( const T& value );
    
    //根据条件删除
    template< class UnaryPredicate >
    void remove_if( UnaryPredicate p );
    template< class UnaryPredicate >
    size_type remove_if( UnaryPredicate p );
    
    
    UnaryPredicate  一元谓词    函数对象     类中重载operator()(const T& a)
        二元谓词   类中重载operator()(const T& a,const T& b)
        
    //单链表逆序
    void reverse() noexcept;
    
    
    //删除连续的"重复"元素
    void unique();
    size_type unique();
    template< class BinaryPredicate >
    void unique( BinaryPredicate p );
    template< class BinaryPredicate >
    size_type unique( BinaryPredicate p );
    	BinaryPredicate  二元谓词    相邻两个元素满足条件进就删除后一个元素
            
    
    //排序
    void sort();     默认是用<比较
    template< class Compare >
    void sort( Compare comp );   //比较两个元素时用 comp
    
    
list
  • 双向链表

    #include <list>
    template<class T,class Allocator = std::allocator<T>> class list;
    
  • list 是支持常数时间从容器任何位置插入和移除元素的容器。不支持快速随机访问。它通常实现为双向链表。与 forward_list相比,此容器提供双向迭代但在空间上效率稍低。

  • 在 list 内或在数个 list 间添加、移除和移动元素不会非法化迭代器或引用。迭代器仅在对应元素被删除时非法化。

  • 元素访问

    reference front();
    const_reference front() const; 
    
    reference back();
    const_reference back() const; 
    
  • 迭代器 双向

    begin cbegin
    end cend
    rbegin crbegin
    rend crend
    
  • 容量

    bool empty() const;
    bool empty() const noexcept; 
    size_type size() const;
    size_type size() const noexcept;
    
    
  • 修改器

    void clear();
    void clear() noexcept;
    
    //在指定位置pos前插入 ...
    iterator insert( iterator pos, const T& value );
    iterator insert( const_iterator pos, const T& value );
    iterator insert( const_iterator pos, T&& value );
    void insert( iterator pos, size_type count, const T& value );
    iterator insert( const_iterator pos, size_type count, const T& value );=
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    template< class InputIt >
    iterator insert( const_iterator pos, InputIt first, InputIt last );
    iterator insert( const_iterator pos, std::initializer_list<T> ilist ); 
    
    //在指定位置前原位构造对象插入
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    //根据迭代器删除
    iterator erase( iterator pos );
    iterator erase( const_iterator pos );  
    iterator erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last ); 
    
    
    void pop_back();
    void push_back( const T& value );
    void push_back( T&& value ); 
    template< class... Args >
    void emplace_back( Args&&... args );
    template< class... Args >
    reference emplace_back( Args&&... args );
    
    void pop_front();
    void push_front( const T& value );
    void push_front( T&& value ); 
    template< class... Args >
    template< class... Args >
    reference emplace_front( Args&&... args ); 
    
    void resize( size_type count ); 
    void resize( size_type count, T value = T() );
    void resize( size_type count, const value_type& value ); 
    
  • 操作

    void merge( list& other );
    void merge( list&& other );
    template <class Compare>
    void merge( list& other, Compare comp );
    template <class Compare>
    void merge( list&& other, Compare comp ); 
    
    
    
    void splice( const_iterator pos, list& other );
    void splice( const_iterator pos, list&& other );
    void splice( const_iterator pos, list& other, const_iterator it );
    void splice( const_iterator pos, list&& other, const_iterator it );
    void splice( const_iterator pos, list& other,
                  const_iterator first, const_iterator last);
    void splice( const_iterator pos, list&& other,
                  const_iterator first, const_iterator last ); 
    
    
    void remove( const T& value );
    size_type remove( const T& value );
    template< class UnaryPredicate >
    void remove_if( UnaryPredicate p );
    template< class UnaryPredicate >
    size_type remove_if( UnaryPredicate p );
    
    void reverse();
    void reverse() noexcept;
    
    void unique();
    size_type unique();
    template< class BinaryPredicate >
    void unique( BinaryPredicate p );
    template< class BinaryPredicate >
    size_type unique( BinaryPredicate p );
    
    void sort();
    template< class Compare >
    void sort( Compare comp );
    
    
deque
  • 双端队列
  • 顺序表
  • 允许在双端进行快速插入和删除,同时支持随机访问
#include <deque>
template<class T,class Allocator = std::allocator<T>> class deque;
  • deque ( double-ended queue ,双端队列)是有下标顺序容器,它允许在其首尾两段快速插入及删除。

  • 另外,在 deque 任一端插入或删除不会非法化指向其余元素的指针或引用。

  • 与 vector相反, deque 的元素不是相接存储的:典型实现用单独分配的固定大小数组的序列,外加额外的登记,这表示下标访问必须进行二次指针解引用,与之相比 vector 的下标访问只进行一次。

  • deque 的存储按需自动扩展及收缩。扩张 deque 比扩张 vector 更优,因为它不涉及到复制既存元素到新内存位置。另一方面, deque 典型地拥有较大的最小内存开销;只保有一个元素的 deque 必须分配其整个内部数组(例如 64 位 libstdc++ 上为对象大小 8 倍; 64 位 libc++ 上为对象大小 16 倍或 4096 字节的较大者)。

  • deque 上常见操作的复杂度(效率)如下:

    • 随机访问——常数 O(1)
    • 在结尾或起始插入或移除元素——常数 O(1)
    • 插入或移除元素——线性 O(n)
  • 元素访问

    reference       at( size_type pos );
    const_reference at( size_type pos ) const; 
    
    reference       operator[]( size_type pos );
    const_reference operator[]( size_type pos ) const; 
    
    reference front();
    const_reference front() const; 
    
    reference back();
    const_reference back() const; 
    
    
  • 迭代器 随机 双向

    begin cbegin
    end cend
    rbegin crbegin
    rend crend
    
  • 容量

    bool empty() const;
    bool empty() const noexcept; 
    size_type size() const;
    size_type size() const noexcept;
    void shrink_to_fit();
    
  • 修改器

    void clear();
    void clear() noexcept; 
    
    iterator insert( iterator pos, const T& value );
    iterator insert( const_iterator pos, const T& value );
    iterator insert( const_iterator pos, T&& value );
    void insert( iterator pos, size_type count, const T& value );
    iterator insert( const_iterator pos, size_type count, const T& value );
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    template< class InputIt >
    iterator insert( const_iterator pos, InputIt first, InputIt last );
    iterator insert( const_iterator pos, std::initializer_list<T> ilist ); 
    
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    
    iterator erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last ); 
    
    void push_back( const T& value );
    void push_back( T&& value ); 
    
    
    template< class... Args >
    void emplace_back( Args&&... args );
    template< class... Args >
    reference emplace_back( Args&&... args ); 
    void pop_back();
    
    void push_front( const T& value );
    void push_front( T&& value ); 
    
    
    template< class... Args >
    void emplace_front( Args&&... args );
    template< class... Args >
    reference emplace_front( Args&&... args ); 
    void pop_front();
    
    void resize( size_type count );
    void resize( size_type count, T value = T() );
    void resize( size_type count, const value_type& value ); 
    
    //move
    //forward
    //ref
    
    
线性容器总结
  • vector没有push_front和pop_front,方法
    • 拥有push_back/pop_back/ front/back
  • list和deque都拥有push_front/push_back/pop_front/pop_back/ front/back
容器适配器
  • 容器适配器提供顺序容器的不同接口
stack
  • 适配一个容器以提供栈(LIFO 数据结构)

  • 先进后出 只在一端进行插入和删除的操作 push_back/pop_back/back

    #include <stack>
    template<class T,class Container = std::deque<T>> class stack;
    
  • 底层默认实现为deque

  • 只要容器能够提供: back()``push_back()``pop_back() 就可以作为stack底层容器

bool empty();
size_t size();
void push();
void emplace();
T& top();
void pop();
queue
  • 适配一个容器以提供队列(FIFO 数据结构)
  • queue 在底层容器尾端推入元素,从首端弹出元素。 底层容器需要支持 push_back() / pop_front()
  • vector没有pop_front,所以vector不能作为queue底层容器
T& front();
T& back();
bool empty();
size_t size();
void push();    //push_back
void emplace(); //emplace_back
void pop();     //pop_front
priority_queue
  • 适配一个容器以提供优先级队列

    #include <queue>
    template<class T,class Container = std::vector<T>,class Compare = std::less<typename Container::value_type> > class priority_queue;
    
  • 底层默认容器用的vector list不能作为priority_queue的底层容器,因为迭代器不支持随机迭代(±n)

T& top();
bool empty();
size_t size();
void push();        //直接插入O(n)  在堆中插入  O(logn)   push_heap
void emplace();    
void pop();         //O(1)         删除堆顶元素  O(logn) pop_heap

template<typename IT,typename T>
IT bfind(IT beg,IT end,const T& value){
    IT mid = beg + (end-beg)/2;  //beg和end需要支持随机迭代才可能 
    //mid = (beg+end)/2;//错误的
}
堆操作
#include <algorithm>

//检测是否是"大"堆
template< class RandomIt >
bool is_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr bool is_heap( RandomIt first, RandomIt last );

template< class ExecutionPolicy, class RandomIt >
bool is_heap( ExecutionPolicy&& policy, RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
bool is_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr bool is_heap( RandomIt first, RandomIt last, Compare comp );

template< class ExecutionPolicy, class RandomIt, class Compare >
bool is_heap( ExecutionPolicy&& policy, RandomIt first, RandomIt last, Compare comp ); 





template< class RandomIt >
RandomIt is_heap_until( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr RandomIt is_heap_until( RandomIt first, RandomIt last );

template< class ExecutionPolicy, class RandomIt >
RandomIt is_heap_until( ExecutionPolicy&& policy, RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );

template< class ExecutionPolicy, class RandomIt, class Compare >
 RandomIt is_heap_until( ExecutionPolicy&& policy, RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void make_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void make_heap( RandomIt first, RandomIt last ); 

template< class RandomIt, class Compare >
void make_heap( RandomIt first, RandomIt last,Compare comp ); 

template< class RandomIt, class Compare >
constexpr void make_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void push_heap( RandomIt first, RandomIt last );
template< class RandomIt >
constexpr void push_heap( RandomIt first, RandomIt last );
template< class RandomIt, class Compare >
void push_heap( RandomIt first, RandomIt last, Compare comp );  
template< class RandomIt, class Compare >
constexpr void push_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void pop_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void pop_heap( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void pop_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr void pop_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void sort_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void sort_heap( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void sort_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr void sort_heap( RandomIt first, RandomIt last, Compare comp ); 

关联容器
有序关联容器 红黑树
  • 常规操作时间复杂度 O(logn)
set 集合
  • 存储key元素 key唯一,不能重复

    template<class Key,class Compare = std::less<Key>,class Allocator =std::allocator<Key>> class set;
    
  • std::set 是关联容器,含有 Key 类型对象的已排序集。用比较函数 比较(compare),进行排序。搜索、移除和插入拥有对数复杂度。 set 通常以红黑树实现。

  • !comp(a, b) && !comp(b, a) 为true,则认为它们相等

  • 修改器

    void clear();
    void clear() noexcept; 
    
    //返回的是一对值(pair)  pair中第一个值是插入元素的迭代器,第二是bool,表示插入是否成功
    std::pair<iterator,bool> insert( const value_type& value );
    std::pair<iterator,bool> insert( value_type&& value );
    
    iterator insert( iterator hint, const value_type& value );
    iterator insert( const_iterator hint, const value_type& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    
    //根据迭代器删除
    void erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    void erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last );
    
    //根据key的值来删除  删除等于key的元素  比较也是用<
    size_type erase( const key_type& key ); 
    
    
    
    void swap( set& other );
    void swap( set& other ) noexcept(); 
    
    
    node_type extract( const_iterator position );
    
    template<class C2>
    void merge( std::set<Key, C2, Allocator>& source );
    template<class C2>
    void merge( std::set<Key, C2, Allocator>&& source );
    template<class C2>
    void merge( std::multiset<Key, C2, Allocator>& source );
    template<class C2>
    void merge( std::multiset<Key, C2, Allocator>&& source ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
    size_type count( const K& x ) const; 
    
    
    iterator find( const Key& key );
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    //返回不"小"于key的第一个元素的迭代器
    iterator lower_bound( const Key& key );
    const_iterator lower_bound( const Key& key ) const;
    template< class K >
    iterator lower_bound(const K& x);
    template< class K >
    const_iterator lower_bound(const K& x) const; 
    
    
    //返回首个"大于"key的首元素的迭代器
    iterator upper_bound( const Key& key );
    const_iterator upper_bound( const Key& key ) const;
    template< class K >
    iterator upper_bound( const K& x );
    template< class K >
    const_iterator upper_bound( const K& x ) const; 
    
    
  • 比较器

    key_compare key_comp() const;
    std::set::value_compare value_comp() const;
    

基于set存储的学生管理系统

增加

按学号删除

列出

查找

pair
#include <utility>
template<class T1,class T2> 
struct pair{
  	T1 first;
    T2 second;
};

//调用构造函数构建
pair<string,int> p(string("hello"),1);


template< class T1, class T2 >
std::pair<T1,T2> make_pair( T1 t, T2 u );
template< class T1, class T2 >
std::pair<V1,V2> make_pair( T1&& t, T2&& u );
template< class T1, class T2 >
constexpr std::pair<V1,V2> make_pair( T1&& t, T2&& u ); 

//模板函数构建对象
make_pair(string("hello"),1);

multiset 多重集合
  • 存储key元素 key不唯一 允许重复

  • 方法和set一模一样 头文件都是#include

map 映射
  • 存储key-value(键-值)对 以key构建管理红黑树 key唯一,不能重复

    #include <map>
    template< class Key,class T,class Compare = std::less<Key>,
         class Allocator = std::allocator<std::pair<const Key, T> >
    > class map;
    
  • 元素访问

    //获取key所对应的value  如果Key不存在,则抛出out_of_range异常     左值    m.at(key) = value
    T& at( const Key& key );
    const T& at( const Key& key ) const;
    
    //获取key所对应的value   如果key不存在,则插入key,value就无参构造(基础数据类型为0)     如果key存在则获取   可以作为左值  m[key]=value   修改key所对应的value
    T& operator[]( const Key& key );
    T& operator[]( Key&& key ); 
    
    
  • 迭代器

    • 映射的迭代器 解引用 取到的是一个pair对象 包括key和value
    begin cbegin
    end cend
    rbegin crbegin
    rend crend
        
       it->first    key       it->value    value
       (*it).first            (*it).second
    
  • 容量

    bool empty()const;
    size_t size()const;
    
  • 修改器

    void clear();
    void clear() noexcept;
    
    
    std::pair<iterator,bool> insert( const value_type& value );
    template< class P >
    std::pair<iterator,bool> insert( P&& value );
    std::pair<iterator,bool> insert( value_type&& value );
    iterator insert( iterator hint, const value_type& value );
    iterator insert( const_iterator hint, const value_type& value );
    template< class P >
    iterator insert( const_iterator hint, P&& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    
    template <class M>
    std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj);
    template <class M>
    std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj);
    template <class M>
    iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj);
    template <class M>
    iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    
    
    template <class... Args>
     pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);
    template <class... Args>
     pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
    template <class... Args>
     iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args);
    template <class... Args>
     iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); 
    
    
    
    void erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    void erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last );
    size_type erase( const key_type& key ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
     size_type count( const K& x ) const; 
    
    iterator find( const Key& key );
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    
    iterator lower_bound( const Key& key );
    const_iterator lower_bound( const Key& key ) const;
    template< class K >
     iterator lower_bound(const K& x);
    template< class K >
     const_iterator lower_bound(const K& x) const; 
    
    
    iterator upper_bound( const Key& key );
    const_iterator upper_bound( const Key& key ) const;
    template< class K >
     iterator upper_bound( const K& x );
    template< class K >
     const_iterator upper_bound( const K& x ) const; 
    
    
multimap 多重映射
  • 存储key-value(键-值)对 以key构建管理红黑树 key不唯一,允许重复

  • 成员方法和map是一样的,头文件也一样

  • 唯一缺少at和operator[] !!!!!

  • key如果是基础数据类型,除非特别指定Compare,否则不需要提供什么

  • key如果是自定义类型,则需要自定义类型能够进行<比较

    • 重载operator<

    • 提供额外Compare

      class X{
      public:
          bool operator()(const 自定义类型& x1,const 自定义类型& x2)const{
              
          }
      };
      
  • value如果是自定义类型,使用[]时必须要支持无参构造

  • 关联容器都不能通过迭代器修改key的值

无序关联容器 哈希表
  • 常规操作时间复杂度 O(1) — O(n)
unordered_set
  • 迭代器 iterator / local_iterator 都是常迭代器 不能通过迭代器修改容器中的元素

    //iterator  能够遍历所有元素
    iterator begin() noexcept;
    const_iterator begin() const noexcept;
    const_iterator cbegin() const noexcept; 
    
    iterator end() noexcept;
    const_iterator end() const noexcept;
    const_iterator cend() const noexcept; 
    
    
  • 容量

    bool empty() const noexcept;
    size_type size() const noexcept;    //常量
    size_type max_size() const noexcept;
    
  • 修改器

    void clear() noexcept;
    
    std::pair<iterator,bool> insert( const value_type& value );
    std::pair<iterator,bool> insert( value_type&& value );
    iterator insert( const_iterator hint, const value_type& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    iterator erase( const_iterator first, const_iterator last );
    size_type erase( const key_type& key ); 
    
    void swap( unordered_set& other );
    
    node_type extract( const_iterator position );
    node_type extract( const key_type& x ); 
    
    
    
    template<class H2, class P2>
    void merge( std::unordered_set<Key, H2, P2, Allocator>& source );
    template<class H2, class P2>
    void merge( std::unordered_set<Key, H2, P2, Allocator>&& source );
    template<class H2, class P2>
    void merge( std::unordered_multiset<Key, H2, P2, Allocator>& source );
    template<class H2, class P2>
    void merge( std::unordered_multiset<Key, H2, P2, Allocator>&& source ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
    size_type count( const K& x ) const; 
    
    
    iterator find( const Key& key );   //需要先对key进行   hash_func(Hash()(key)) 散列  映射到桶,然后再和桶中的元素进行  equal_to/operator== 比较  
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    
  • 桶接口

    //桶的迭代器  n为桶的下标
    local_iterator begin( size_type n );
    const_local_iterator begin( size_type n ) const;
    const_local_iterator cbegin( size_type n ) const; 
    
    local_iterator end( size_type n );
    const_local_iterator end( size_type n ) const;
    const_local_iterator cend( size_type n ) const; 
    
    //返回桶的数量
    size_type bucket_count() const;
    size_type max_bucket_count() const;//桶最大的数量  取决于系统和库设置
    //返回指定桶n中元素的个数
    size_type bucket_size( size_type n ) const;  
    //返回key所在桶的对应的下标
    size_type bucket( const Key& key ) const;
    
    
    
  • 哈希策略

    float load_factor() const; //加载因子  = 元素数量/桶数量
    
    float max_load_factor() const; //返回哈希表设定的加载因子
    void max_load_factor( float ml ); //设置哈希表的最大加载因子
    
    //在添加元素时,如果加载因子 > 设定最大加载因子    需要重新散列   需要重新分配内存  然后拷贝现有对象
    
    void rehash( size_type count );    //设置桶数为 count 并重哈希容器,即考虑桶总数已改变,再把元素放到适当的桶中。若新的桶数使加载因子大于最大加载因子( count < size() / max_load_factor() ),则新桶数至少为 size() / max_load_factor() 。
    
    void reserve( size_type count );    //设置桶数为适应至少 count 个元素,而不超出最大加载因子所需的数,并重哈希容器,即考虑桶数已更改后将元素放进适合的桶。等效地调用 rehash(std::ceil(count / max_load_factor()))
    
    //加载因子过大,意味着冲突加剧,加载因子过小,意味着内存浪费
    
    
    
  • 自定义类型存储到无序关联容器(作为Key)

    • 提供Hash类型,重载operator() 有一个自定义类型参数 ,返回一个特定的int类型

      class Hash{
      public:
          int operator()(const 自定义类型& obj)const{
              return xx;
          }
      };
      
    • 涉及到比较,需要重载 == 运算符, 或者 提供 比较的类

      class 自定义类型{
      public:
          bool operator==(const 自定义类型& obj)const{
              
          }
      };
      
      class Equal{
      public:
          bool operator()(const 自定义类型& o1,const 自定义类型& o2)const{
              
          }
      };
      

    通讯录管理系统 用unordered_set存储

  • 在提供自自定类型的hash和equal时

    • find,需要经过hash映射,然后在和桶中的元素进行equal比较
    • hash字段,也需要在equal比较中相等
  • hash

    #include <functional>
       
    template< class Key >
    struct hash; 
    
    template<> struct hash<bool>;
    template<> struct hash<char>;
    template<> struct hash<signed char>;
    template<> struct hash<unsigned char>;
    template<> struct hash<char8_t>;        // C++20
    template<> struct hash<char16_t>;
    template<> struct hash<char32_t>;
    template<> struct hash<wchar_t>;
    template<> struct hash<short>;
    template<> struct hash<unsigned short>;
    template<> struct hash<int>;
    template<> struct hash<unsigned int>;
    template<> struct hash<long>;
    template<> struct hash<long long>;
    template<> struct hash<unsigned long>;
    template<> struct hash<unsigned long long>;
    template<> struct hash<float>;
    template<> struct hash<double>;
    template<> struct hash<long double>;
    template<> struct hash<std::nullptr_t>;
    template< class T > struct hash<T*>;
    
    class User{
    public:
        User(string name="",string tel=""):name(name),tel(tel){
            
        }
        bool operator==(const User& user)const{//用于hash字段一定要相等
            return name == tel.name;
        }
        string getName()const{
            return user;
        }
    private:
        string name;
        string tel;
    };
    
    class HashUser{
    public:
        size_t operator()(const User& user)const{
            return hash<string>()(user.getName());
        }
    };
    
    
unordered_mulitset
  • unordered_multiset 是关联容器,含有可能非唯一 Key 类型对象的集合。搜索、插入和移除拥有平均常数时间复杂度。

  • 元素在内部并不以任何顺序排序,只是被组织到桶中。元素被放入哪个桶完全依赖其值的哈希。这允许快速访问单独的元素,因为一旦计算哈希,它就指代放置该元素的准确的桶。

  • 方法和unordered_set是一样的

unordered_map
template<
    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;
unordered_multimap
string
  • 可以把string看作是存储字符类型数据的顺序容器
initializer_list 初始化列表
size_type size() const noexcept;

const T* begin() const noexcept;
constexpr const T* begin() const noexcept; 

const T* end() const noexcept;
constexpr const T* end() const noexcept; 


auto l = {1,2,3,4,4,5}

tuple 一组值 pair扩展
tuple( const Types&... args );
template< class... UTypes >
tuple( UTypes&&... args ); 



template< class... Types >
tuple<VTypes...> make_tuple( Types&&... args );
template< class... Types >
constexpr tuple<VTypes...> make_tuple( Types&&... args ); 



template< class... Types >
 tuple<Types&...> tie( Types&... args ) noexcept;
template< class... Types >
constexpr tuple<Types&...> tie( Types&... args ) noexcept; 

//创建一个tuple变量,变量中的成员都是  引用
int a,b,c;
tie(a,b,c)
    
    

template< class... Tuples >
std::tuple<CTypes...> tuple_cat(Tuples&&... args);
template< class... Tuples >
constexpr std::tuple<CTypes...> tuple_cat(Tuples&&... args); 


std::get(std::tuple)  //提取tuple中的对象     
get<0>(t)       根据位置
get<1>(t)
    
get<int>(t)     根据类型
    

tuple_size          在编译时获得 tuple 的大小 
tuple_element       获得指定元素的类型 
ignore              用 tie 解包 tuple 时用来跳过元素的占位符 
any C++17

弱类型编程

any a = 1;

a = 3.14;

a = true;

a = “hello”;

a = Stu(110,“jack”,100);

a = Book(100011,“三国演义”,“罗贯中”);

算法
#include <algorithm>
  • 不修改序列容器

    bool all_of( InputIt first, InputIt last, UnaryPredicate p );
    
    bool any_of( InputIt first, InputIt last, UnaryPredicate p );
    
    bool none_of( InputIt first, InputIt last, UnaryPredicate p );
    
    UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
    
    InputIt for_each_n( InputIt first, Size n, UnaryFunction f );
    
    
    typename iterator_traits<InputIt>::difference_type count( InputIt first, InputIt last, const T &value );
    
    typename iterator_traits<InputIt>::difference_type count_if( InputIt first, InputIt last, UnaryPredicate p );
    
    std::pair<InputIt1,InputIt2>mismatch( InputIt1 first1, InputIt1 last1,InputIt2 first2 );
    constexpr std::pair<InputIt1,InputIt2> mismatch( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2 );
    
    
    InputIt find( InputIt first, InputIt last, const T& value );
    InputIt find_if( InputIt first, InputIt last, UnaryPredicate p );
    InputIt find_if_not( InputIt first, InputIt last,UnaryPredicate q );
    
    ForwardIt1 find_end( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last );  //在范围 [first, last) 中搜索序列 [s_first, s_last) 的最后一次出现。
    
    ForwardIt1 find_first_of( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last );//在范围 [first, last) 中搜索范围 [s_first, s_last) 中的任何元素。
    
    ForwardIt adjacent_find( ForwardIt first, ForwardIt last );
    ForwardIt adjacent_find( ForwardIt first, ForwardIt last, BinaryPredicate p );
    
    
     
    ForwardIt1 search( ForwardIt1 first, ForwardIt1 last,ForwardIt2 s_first, ForwardIt2 s_last );  //搜索范围 [first, last - (s_last - s_first)) 中元素子序列 [s_first, s_last) 的首次出现。
    ForwardIt1 search( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last, BinaryPredicate p );
    
    ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T& value );
    ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T& value, BinaryPredicate p );
    在范围 [first, last) 中搜索 count 个等同元素的序列,每个都等于给定的值 value 
         
    
  • 修改序列的操作

    OutputIt copy( InputIt first, InputIt last, OutputIt d_first );
    OutputIt copy_if( InputIt first, InputIt last,OutputIt d_first, UnaryPredicate pred );
    
    OutputIt copy_n( InputIt first, Size count, OutputIt result );
    BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
    
    OutputIt move( InputIt first, InputIt last, OutputIt d_first );
    
    template<class InputIt, class OutputIt>
    OutputIt move(InputIt first, InputIt last, OutputIt d_first)
    {
        while (first != last) {
            *d_first++ = std::move(*first++);//不是复制数据  而是移动赋值
        }
        return d_first;
    }
    
    BidirIt2 move_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
    
    void fill( ForwardIt first, ForwardIt last, const T& value );
    void fill_n( OutputIt first, Size count, const T& value );
    
    OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first,UnaryOperation unary_op );
    
    
    void generate( ForwardIt first, ForwardIt last, Generator g );
    void generate_n( OutputIt first, Size count, Generator g );
    
    template<class ForwardIt, class Generator>
    void generate(ForwardIt first, ForwardIt last, Generator g)
    {
        while (first != last) {
            *first++ = g();
        }
    }
    
    //并返回范围新结尾的尾后迭代器
    ForwardIt remove( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPredicate p );
    1) 移除所有等于 value 的元素,用 operator== 比较它们。
    2) 移除所有 p 对于它返回 true 的元素。
        
    //拷贝除了value以外的[first,last)区间所有元素
    OutputIt remove_copy( InputIt first, InputIt last, OutputIt d_first,const T& value );
    //拷贝不满足p条件以外的[first,last)区间所有元素
    OutputIt remove_copy_if( InputIt first, InputIt last, OutputIt d_first,UnaryPredicate p );
    
    
    void replace( ForwardIt first, ForwardIt last,const T& old_value, const T& new_value );
    void replace_if( ForwardIt first, ForwardIt last,UnaryPredicate p, const T& new_value );
    
    OutputIt replace_copy( InputIt first, InputIt last, OutputIt d_first,const T& old_value, const T& new_value );
    OutputIt replace_copy_if( InputIt first, InputIt last, OutputIt d_first, UnaryPredicate p, const T& new_value );
    
    
    template< class T >
    void swap( T& a, T& b );
    
    ForwardIt2 swap_ranges( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2 );
    
    template< class ForwardIt1, class ForwardIt2 >
    void iter_swap( ForwardIt1 a, ForwardIt2 b );  //交换两个迭代器所指向的元素
    
    //逆序
    void reverse( BidirIt first, BidirIt last );
    
    OutputIt reverse_copy( BidirIt first, BidirIt last, OutputIt d_first );
    
    void rotate( ForwardIt first, ForwardIt n_first, ForwardIt last );
    OutputIt rotate_copy( ForwardIt first, ForwardIt n_first,ForwardIt last, OutputIt d_first );
    
    void random_shuffle( RandomIt first, RandomIt last );
    void random_shuffle( RandomIt first, RandomIt last, RandomFunc& r );
    
    //移除范围内的连续重复元素   list不适用
    ForwardIt unique( ForwardIt first, ForwardIt last );
    ForwardIt unique( ForwardIt first, ForwardIt last, BinaryPredicate p );
    
    OutputIt unique_copy( InputIt first, InputIt last,OutputIt d_first );
    OutputIt unique_copy( InputIt first, InputIt last,OutputIt d_first, BinaryPredicate p );
    
  • 划分操作

    //范围 [first, last) 中的所有满足 p 的元素都出现在所有不满足的元素前则返回 true 。若 [first, last) 为空亦返回 true
    bool is_partitioned( InputIt first, InputIt last, UnaryPredicate p );
    
    //重排序范围 [first, last) 中的元素,使得谓词 p 对其返回 true 的元素前于谓词 p 对其返回 false 的元素。不保持相对顺序    指向第二组元素首元素的迭代器(第一个不满足p条件的元素的迭代器)
    BidirIt partition( BidirIt first, BidirIt last, UnaryPredicate p );
    std::pair<OutputIt1, OutputIt2>
          partition_copy( InputIt first, InputIt last,OutputIt1 d_first_true, OutputIt2 d_first_false,UnaryPredicate p );
    
    //划分  保证相同元素的相对位置不变
    BidirIt stable_partition( BidirIt first, BidirIt last, UnaryPredicate p );
    
    ForwardIt partition_point( ForwardIt first, ForwardIt last, UnaryPredicate p );
    
  • 排序操作

    bool is_sorted( ForwardIt first, ForwardIt last );
    bool is_sorted( ForwardIt first, ForwardIt last, Compare comp );
    
    
    ForwardIt is_sorted_until( ForwardIt first, ForwardIt last );
    ForwardIt is_sorted_until( ForwardIt first, ForwardIt last,Compare comp );
    void sort( RandomIt first, RandomIt last );
    void sort( RandomIt first, RandomIt last, Compare comp );
    void partial_sort( RandomIt first, RandomIt middle, RandomIt last );
    void partial_sort( RandomIt first, RandomIt middle, RandomIt last, Compare comp );
    RandomIt partial_sort_copy( InputIt first, InputIt last,RandomIt d_first, RandomIt d_last );
    RandomIt partial_sort_copy( InputIt first, InputIt last,RandomIt d_first, RandomIt d_last, Compare comp );
    
    //稳定排序
    void stable_sort( RandomIt first, RandomIt last );
    void stable_sort( RandomIt first, RandomIt last, Compare comp );
    
    //以*nth元素为分割  
    void nth_element( RandomIt first, RandomIt nth, RandomIt last );
    void nth_element( RandomIt first, RandomIt nth, RandomIt last, Compare comp );
    
  • 二分搜索(有序)

    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    bool binary_search( ForwardIt first, ForwardIt last, const T& value );
    bool binary_search( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    ForwardIt mid = next(first,distance(first,last)/2);
    
    
    std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last,const T& value );
    std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last,const T& value, Compare comp );
    
  • 有序操作

    OutputIt merge( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    
    OutputIt merge( InputIt1 first1, InputIt1 last1,  InputIt2 first2, InputIt2 last2,OutputIt d_first, Compare comp );
    
    
    void inplace_merge( BidirIt first, BidirIt middle, BidirIt last );
    void inplace_merge( BidirIt first, BidirIt middle, BidirIt last, Compare comp );
    
  • 集合操作(在已排序范围上)

    bool includes( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );
    bool includes( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, Compare comp );
    
    OutputIt set_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2,OutputIt d_first, Compare comp );
    
    OutputIt set_intersection( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_intersection( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first,Compare comp );
    
    OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first,Compare comp );
    
    OutputIt set_union( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, OutputIt d_first );
    OutputIt set_union( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );
    
  • 排列操作

    //相等元素交换位置也是同一种排列
    bool next_permutation( BidirIt first, BidirIt last );
    bool next_permutation( BidirIt first, BidirIt last, Compare comp);
    bool prev_permutation( BidirIt first, BidirIt last);
    bool prev_permutation( BidirIt first, BidirIt last, Compare comp);
    
    bool is_permutation( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2 );
    bool is_permutation( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2, BinaryPredicate p );
    
  • 数值运算

    //以始于 value 并重复地求值 ++value 的顺序递增值填充范围 [first, last) 。
    void iota( ForwardIt first, ForwardIt last, T value );
    
    T accumulate( InputIt first, InputIt last, T init );
    T accumulate( InputIt first, InputIt last, T init,BinaryOperation op );
    //op 等价  Ret fun(const Type1 &a, const Type2 &b);   a当前累积   b当前项
    
    T inner_product( InputIt1 first1, InputIt1 last1,InputIt2 first2, T init );
    OutputIt adjacent_difference( InputIt first, InputIt last,OutputIt d_first );
    
    OutputIt partial_sum( InputIt first, InputIt last, OutputIt d_first );
    
  • 内存操作

    #include <memory>
    //复制来自范围 [first, last) 的元素到始于 d_first 的未初始化内存
    ForwardIt uninitialized_copy( InputIt first, InputIt last, ForwardIt d_first );
    ForwardIt uninitialized_copy_n( InputIt first, Size count, ForwardIt d_first);
    void uninitialized_fill( ForwardIt first, ForwardIt last, const T& value );
    void uninitialized_fill_n( ForwardIt first, Size count, const T& value );
    
迭代器
    InputIt                OutputIt
           \              /
              forwardIt
                 |
              bidirectionIt
                 |
              RandomAccessIt
              
输入迭代器   支持: ++  ==  !=  ->  *  
输出迭代器   支持: ++  ==  !=  ->  *
前向迭代器   支持: ++  ==  !=  ->  *
双向迭代器   支持: ++  ==  !=  ->  *   --
随机迭代器   支持: ++  ==  !=  ->  *   --    +n  -n    -=n   +=n
  • 迭代器适配器

    • reverse_iterator
  • 操作

    #include <iterator>
    
    void advance( InputIt& it, Distance n );
    std::iterator_traits<InputIt>::difference_type distance( InputIt first, InputIt last );
    InputIt next(InputIt it,typename std::iterator_traits<InputIt>::difference_type n = 1 );
    BidirIt prev(BidirIt it,typename std::iterator_traits<BidirIt>::difference_type n = 1 );
    
    
配置器
  • 分配器(Allocator) 管理内存的申请和释放 内存池

  • std::allocator 类模板是所有标准库容器所用的默认allocator ,若不提供用户指定的分配器。默认分配器无状态,即任何给定的 allocator 实例可交换、比较相等,且能解分配同一 allocator 类型的任何其他实例所分配的内存。

    pointer allocate( size_type n, const void * hint = 0 );
    T* allocate( std::size_t n, const void * hint);
    T* allocate( std::size_t n );
    
    void deallocate( T* p, std::size_t n );
    
    void construct( pointer p, const_reference val );
    template< class U, class... Args >
    void construct( U* p, Args&&... args );
    void destroy( pointer p );
    template< class U >
    void destroy( U* p );
    
配接器(适配器)
  • 容器适配器

  • 迭代器适配器

  • 仿函数适配器

仿函数
  • function 包装具有指定函数调用签名的任意类型的可调用对象
  • bind

1.读取票房信息,从高到低输出

2.读取一个文件(只有英文单词,而且单词之间用空格隔开) 统计每个单词出现的次数(找出出现次数最多的单词)

3.投票 五名候选人[张三,李四,王五,赵六,钱七] 现在有20个投票人进行投票,输出每名候选人的得票情况,并输出获胜者

4.业绩 四名员工分别有四个季度的业绩,需要统计每个员工的总的业绩

张三 一季度 业绩8000

李四 一季度 业绩8800

王五 一季度 业绩9000

赵六 一季度 业绩8000

张三 二季度 业绩8000

李四 二季度 业绩8800

王五 二季度 业绩9000

赵六 二季度 业绩8000

张三 三季度 业绩8000

李四 三季度 业绩8800

王五 三季度 业绩9000

赵六 三季度 业绩8000

张三 四季度 业绩8000

李四 四季度 业绩8800

王五 四季度 业绩9000

赵六 四季度 业绩8000

智能指针
  • 内存泄露

    • 申请的动态内存忘记释放

    • 申请的动态内存无法执行delete/free

      class A{
      public:
          A(int n=0){
              p = new int(n);
              cout << "A构造" << endl;
          }
          ~A(){
              cout << "A析构" << endl;
              delete p;
          }
      private:
         	int *p;
      };
      
      void func(){
          A *pa = new A(1024);//构造    
          
          //如果在delete之前发生异常
          new int[0xFFFFFFFF];// throw string("产生异常");  -->直接跳出  try -- catch
          //下面的语句可能无法被执行  造成了delete pa没有执行,申请的动态内存没有释放  内存泄露
          
          delete pa;  //析构
      }
      
      
  • 智能指针的意义

    • 管理动态内存 ,避免内存泄露
    • 管理动态内存,不需要手动delete
  • 原理

    • 在异常机制里,局部对象能够正常析构(自动调用其析构函数),在智能指针的析构函数中,delete释放申请的动态内存
  • #include

  • 智能指针只能管理new出来的动态内存,不能用非new动态内存来构造智能指针对象,因为智能指针的析构函数中,对内存进行了delete操作

  • 如果用智能指针来管理new出来的动态内存,不能自己再去delete

  • 用处

    • 它可用于为动态分配的对象提供异常安全(产生异常时,动态内存能够得到析构)、传递动态分配对象的所有权给函数和从函数返回动态分配的对象。
auto_ptr C++17被删除
  • C++11之前只有auto_ptr,C++11添加了unique_ptr,shared_ptr,weak_ptr来取代auto_ptr,C++17中,auot_ptr就被删除了

  • 拥有严格对象所有权语义的智能指针

  • 复制 auto_ptr ,会复制指针并转移所有权给目标: auto_ptr 的复制构造和复制赋值都会修改其右侧参数,而且“副本”不等于原值。因为这些不常见的复制语义,不可将 auto_ptr 置于标准容器中。此用途及其他使用更适合用 unique

  • auto_ptr拷贝构造和拷贝赋值函数 实现是 按照 移动构造 和移动赋值函数的语义实现的

  • C++11之前没有移动构造和移动赋值,所以C++11之前 auto_ptr 没有任何问题

  • 但是C++11中添加了移动构造和移动赋值,和移动构造和移动赋值区别开来了,就不能再用拷贝构造去实现移动构造的语义了

  • 拷贝构造之后,原对象把对象的控制权交给新构造的对象,原智能指针就不无法再使用了

    auto_ptr<int> p(new int(1024));
    auto_ptr<int> p1(p);        //p就失效了 把对内存对象的控制权转移给了p1
    
  • 拷贝赋值,也意味着对象的控制权转移和释放

    auto_ptr<int> p(new int(1024));
    auto_ptr<int> p1(new int(9527));
    p1 = p;  //p就失效了   p1原来的控制的动态内存会delete,转而控制原来p控制的动态内存
    
  • 构造函数

    explicit auto_ptr( X* p = 0 ) throw();  //参数必须是new出来的动态内存
    auto_ptr( auto_ptr& r ) throw();        //拷贝构造实现的却是移动构造
    auto_ptr& operator=(auto_ptr& r);       //拷贝赋值实现的却是移动构造
    
  • 观察器

    T* get() const throw();
    
    //智能指针在使用时,可以像指针一样使用     解引用    ->
    T& operator*() const throw();
    T* operator->() const throw(); 
    
    
  • 修改器

    //释放被管理对象的所有权
    T* release() throw();//返回保有的指针。调用后 *this 保有空指针。
    
    void reset( T* p = 0 ) throw();
    
  • 同一块动态内存不能构造出来多个智能对象管理

    int *p = new int(1024);
    //不管什么智能指针  都不能这样干!!!!!
    auto_ptr<int> app1(p);  //app1消亡时 delete p;
    auto_ptr<int> app2(p);  //app2消亡时 delete p;
    //double free    
    
  • 因为auto_ptr不支持完整语义上拷贝,所以不能放到容器中

  • auto_ptr任意时刻只有一个智能指针拥有对象的控制权 唯一

  • C++11之后,就用unique来取代auto_ptr

unique_ptr
template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

//针对数组类型进行了偏特化(局部特化)
template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;
  • std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针

  • 在下列两者之一发生时用关联的删除器释放对象:

    • 销毁了管理的 unique_ptr 对象
    • 通过 operator=或 reset 赋值另一指针给管理的 unique_ptr 对象。
  • unique_ptr删除了拷贝构造和拷贝赋值函数

  • unique_ptr实现了移动构造和移动赋值函数

  • 不支持拷贝构造和拷贝赋值,只支持移动,所以就确保了 拥有独有对象所有权语义的智能指针

    explicit operator bool() const noexcept;
    //检查 *this 是否占有对象,即是否有 get() != nullptr 
    
    typename std::add_lvalue_reference<T>::type operator*() const;
    pointer operator->() const noexcept; 
    
    //针对数组特化版本
    T& operator[]( std::size_t i ) const;
    
shared_ptr
template< class T > class shared_ptr;
  • std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。

  • 下列情况之一出现时销毁对象并释放内存(引用计数为0):

    • 最后剩下的占有对象的 shared_ptr 被销毁;
    • 最后剩下的占有对象的 shared_ptr 被通过operator=或 reset() 赋值为另一指针。
  • 引用计数

    • 调用构造函数创建的shared_ptr,引用计数为1
    • 通过拷贝构造函数创建的shared_ptr,引用计数+1
    • shared_ptr消亡时,引用计数-1 或者 shared_ptr被赋值或者reset 引用计数-1
  • 构造函数

    constexpr shared_ptr() noexcept;
    constexpr shared_ptr( std::nullptr_t ) noexcept; 
    
    explicit shared_ptr( Y* ptr );
    shared_ptr( Y* ptr, Deleter d );
    
    //支持拷贝构造  和  移动 构造
    shared_ptr( const shared_ptr<Y>& r ) ;
    shared_ptr( shared_ptr<Y>&& r ) noexcept;
    
    
  • 赋值

    shared_ptr& shared_ptr( const shared_ptr<Y>& r ) ;
    shared_ptr& shared_ptr(shared_ptr<Y>&& r ) ;
    
  • 修改器

    void reset() noexcept;
    void reset( Y* ptr );
    void reset( Y* ptr, Deleter d );
    
    void swap( shared_ptr& r ) noexcept;
    
  • 观察器

    T* get() const noexcept;
    element_type* get() const noexcept; 
    
    T& operator*() const noexcept;
    T* operator->() const noexcept; 
    
    element_type& operator[]( std::ptrdiff_t idx ) const;
    //引用计数
    long use_count() const noexcept;
    
    bool unique() const noexcept;//use_count() == 1
    
    explicit operator bool() const noexcept;
    
    bool owner_before( const shared_ptr<Y>& other) const noexcept;
    bool owner_before( const std::weak_ptr<Y>& other) const noexcept;
    
  • 非成员方法

    shared_ptr<T> make_shared( Args&&... args );
    
  • 对于shared_ptr管理数组的动态内存,需要在构造时指定deleter

    class Del{
    public:
        void operator()(const A* ptr)const{
            delete [] ptr;
        }
    };
    
    shared_ptr<A> sp(new A[4],Del());
    shared_ptr<A> sp1(new A[4],default_delete<A[]>());
    
    
weak_ptr
  • 解决了shared_ptr循环引用所导致的内存泄露问题

    struct B;
    struct A{
        A(){cout << "A()" << endl;}
    	~A(){cout << "~A()" << endl;}
        shared_ptr<B> sp;
        //weak_ptr<B> sp;
    };
    struct B{
      	B(){cout << "B()" << endl;}
        ~B(){cout << "~B()" << endl;}
        shared_ptr<A> sp;
        //weak_ptr<A> sp;
    };
    
    //循环引用
    shared_ptr<A> pa(new A());
    shared_ptr<B> pb(new B());
    pa.sp = pb;  //weak_ptr 用 shared_ptr进行构造和赋值
    pb.sp = pa;
    
    
  • 通过shared_ptr构造weak_ptr不会引发引用计数+1

  • std::weak_ptr 是一种智能指针,它对被 shared_ptr 管理的对象存在非拥有性(「弱」)引用。在访问所引用的对象前必须先转换为 shared_ptr。

constexpr weak_ptr() noexcept;

weak_ptr( const weak_ptr& r ) noexcept;

template< class Y >
weak_ptr( const weak_ptr<Y>& r ) noexcept;

template< class Y >
weak_ptr( const std::shared_ptr<Y>& r ) noexcept;  //不会使得引用计数+1

weak_ptr( weak_ptr&& r ) noexcept;

template< class Y >
weak_ptr( weak_ptr<Y>&& r ) noexcept 

    
void reset() noexcept; 
void swap( weak_ptr& r ) noexcept;

long use_count() const noexcept;//返回共享被管理对象所有权的 shared_ptr 实例数量,或0,若被管理对象已被删除,即 *this 为空。

bool expired() const noexcept;//等价于 use_count() == 0 。可能仍未对被管理对象调用析构函数,但此对象的析构已经临近(或可能已发生)。

std::shared_ptr<T> lock() const noexcept;
//创建新的 std::shared_ptr 对象,它共享被管理对象的所有权。若无被管理对象,即 *this 为空,则返回亦为空的 shared_ptr 。等效地返回 expired() ? shared_ptr<T>() : shared_ptr<T>(*this) ,原子地执行。

C++11新特性

智能指针 unique_ptr shared_ptr weak_ptr
容器 array forward_list unordered_set unordered_map unordered_multiset unordered_multimap
移动构造 和 移动赋值
for范围循环
nullptr
  • nullptr是用来取代NULL

  • NULL本质上是宏常量 直接替换 本质就是0 类型就会变成int

  • nullptr是nullptr_t类型的常量 nullptr_t 指针类型

  • nullptr是一个字面值,可以被转换为任意其它的指针类型

    void *p = nullptr;
    int *p1 = nullptr;
    double *p2 = nullptr;
    
  • ptrdiff_t 用来记录两个指针之间的差值

constexpr
  • 常量表达式(const expression)是指值不会改变且在编译过程中就能得到计算结果的表达式。

    //1.字面值    0    1   'a'   'x'    '0'   'A'   333
    //2.只有字面值的表达式   0+1+2*3-1
    //3.用常量表达式初始化的const对象
    const int a = 10;
    const int b = 10*10+2;
    //4.sizeof  
    //5.宏常量
    //6.枚举值
    
  • 一个对象(或者表达式)是不是常量表达式由它的数据类型和初始值共同决定

    const int max_files = 20;  //max_files 是
    const int limit = max_files + 10;   //limit 是
    int size = 20;            //不是
    int getsize(){
        return 20;
    }
    const int sz = getsize();   //sz不是
    int a = 10,b = 20;
    const int c = a+b;         // c不是
    
    只有对象是const且初始值是常量表达式,则对象本身才是常量表达式
    
  • 在一个复杂的系统中,其实很难分辨一个初始值到底是不是常量表达式。

  • 定义一个const变量并把它初始值设置为某个值,但实际情况下,可能是常量也可能不是常量

  • C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量值是否是一个常量表达式。

  • 声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

    • 所以如果一个变量声明为constexpr,但初始值不是常量表达式,则编译报错
  • constexpr函数(constexpr function)是指能用于常量表达式的函数

    • 定义constexpr函数的方法与其它函数类似,不过需要遵循几项约定:

      • 函数的返回值类型及所有形参(主要是考察传递的实参)的类型都得是字面值类型 (常量表达式)
      • 函数体中必须有且只有一条return语句 return语句可以是表达式
      constexpr int g = 10;
      constexpr int getsize(){
          return 10+20;  //return g;
      }
      
      constexpr int getsize(int a){
          return a + g;
      }
      
      constexpr int r = getsize(10); // 正确
      int b = 10;
      constexpr int ret = getsize(b);  //错误
      
    • constexpr函数和内联函数一样,可以在程序中多次定义,所以一般,可以直接把cosntexpr和内联函数直接放在.h中定义

    • constexpr构造函数

      • constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造或者是一条常量表达式
      • 有constexpr构造函数,就可以构造constexpr对象
  • constexpr与指针

    • const与指针

      const char *p1;
      char const *p2;
      char * const p3;
      const char * const p4;
      
    • constexpr与指针 只能修改指针本身

      • 限定符constexpr仅对指针有效,与指针所指的对象无关

        const int *p = nullptr;    //p是一个指向整型常量的指针
        constexpr int *q = nullptr;//q是一个指向整数的常量指针    int * const q = nullptr;  
        
    • constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

      int g = 10;
      static int s = 10;
      constexpr int cs = 10;
      void func(){
          constexpr int a = 10;
          //constexpr int *p1 = &a;  错误的  a存储在栈内存中,内存地址不固定
          constexpr int *p2 = &g; 
          constexpr int *p3 = &s;
          static int vs = 10;
          constexpr int *p4 = &vs;
          constexpr int *p5 = nullptr;
          constexpr int *p6 = 0;
          //constexpr int *p7 = &cs; //错误的   &cs const int *   p7  int *
          constexpr const int *p8 = &cs;  //const修饰*p8   constexpr修饰p8
          const int * const p9 = &cs;
          
      }
      
      
  • 记住C++中对比C语言中的const不一样的

    • C++中的const定义的变量如果是常量表达式,则编译器会自动优化,在编译过程中会对const定义的变量进行直接替换;如果const变量的结果不是常量达式,则不会替换
using
  • using使用名字空间

    using nemaspace std;  //声明名字空间中的内容对象当前作用域可见
    using std::cout;      //把名字空间中的标识符导入到当前作用域
    
  • C++11中using取类型别名

    typedef int *  IPTR;
    IPTR a,b;   //int *a,*b;
    
    typedef int (*handler_t)(int,void *);  //函数指针类型
    
    typedef struct A{
        int a;
        int b;
    }A;
    
    typedef int ARR[3];
    
    //给类型取别名
    using  INTPTR = int *;
    using  handler_t = int (*)(int,void *);
    using  A = struct A;
    using  ARR = int [3];
    using  intvec = vector<int>;
    using  MIT = map<int,string>::iterator;
    
    
    typedef char * mystr;
    char s[10] = "hello";
    const mystr a = s;   //char * const a;    const修饰的是a本身
    const char *b = s;   //const char * b;    const修饰的是*b
    
  • C++11中增加了继承构造函数,在其中使用using

  • C++11成员函数隐藏场景中使用

    如果派生类的函数与基类的函数同名,但是参数不同,此时,无论基类中的函数是否有virtual关键字,基类的函数将被隐藏。
    如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字,此时,基类的函数将被隐藏。
    
    可以在子类中使用 
    using 基类::函数名;      对父类中的函数进行解隐藏
    
  • C++中的别名模板

    template <class T>
    using map_s = unordered_map<string,T>;
    
    map_s<int> m;  //unordered_map<string,int> m;
    m["abc"] = 123;
    
    
auto类型说明符
  • auto定义变量,让编译器通过变量的初始值来推算变量的类型

  • auto定义的变量,必须有初始值

    auto a;   //错误的
    
  • auto一条语句定义多个变量时,需要确保推导出来的类型一样,如果推导出来的类型不一致则会报错

    auto j = 0, q = &j;   //错误的
    auto i = 0, k = 3.14;  //错误的
    
  • 编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其符合初始化规则 。

    • 如果初始值是一个引用类型变量,无法推导出来引用 所以如果要定义引用类型变量,需要在auto后面手动加&

      auto int i = 0,&r = i;
      auto a = i;     //a  int
      auto &b = i;    //b  int&
      
    • auto一般会忽略顶层const,同时底层const则会保留下来

      const int a = 10,b = 20; //const修饰a本身  顶层的const
      auto c = a;//c   auto --> int
      int * const p1 = &c;  //const修饰p1本身  顶层的const
      auto p2 = p1;      //p2   -->  int *  
      
      const char *ps1 = "Hello";  //const修饰的是   *ps1   底层的const
      auot ps2 = ps1;     //  -->   const char *
      
      
      
    • 如果希望推断出来的auto类型是一个顶层的const,需要明确指定

      const auto f = 3.14;
      
      auto& r1 = 42;//错误的
      const auto& r2 = 42; //正确的
      
    • 注意

      • 不能在函数的形参列表中使用auto

      • 不能用auto来定义数组

      • 函数返回值类型尾置

        auto func(T a,K b)->decltpye(a+b){
            
        }
        
decltype类型指示符
  • 希望从表达式推断出要定义变量的类型,但是不想用该表达式的值初始化变量

  • 编译器分析表达式并得到它的类型,和sizeof一样,并不会计算表达式的值

    decltype(expr) var;
    
    int n = 0;
    decltype(++n) v1;  
    
    decltype(func()) v2;
    
  • decltype处理顶层的const和auto是不一样的

    • decltype会保留顶层的const属性
  • decltype处理引用和auto是不一样的

    • decltype会保留引用的特性
  • decltype 和 引用

    • 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。

    • decltype(r0+r1) 一个具体的值,而非引用类型

    • 如果表达式的内容是解引用操作, *p ,则decltype将得到引用类型。

    • 只要对变量加上()那么,decltype得到将就引用 decltype((var)) 引用

      • decltype(var) 只有当var是引用时,得到的才是引用类型

      • decltype((var)) 不管var是否是引用,得到的一定是引用类型

auto和decltype的区别
  • 第一,auto类型说明符用编译器计算变量的初始值来推断其类型,而delctype虽然也让编译器分析表达式并得到它的类型,但是不会计算表达式的值
  • 第二,编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果的类型使其更符合初始化规则 。auto一般会忽略顶层的const,而把底层const保留下来,auto也会忽略引用,与之相反,decltype会保留顶层底层的const及引用
  • 第三,与auto不同,decltype的结果类型与表达式形式密切相关,如果变量名加上(),则一定得到的是变量类型的引用类型。
  • 第四,auto必须初始值,decltype可以不初始化
C++11修复 > >bug
  • vector<vector> v; //c++98 >> 中间必须有一个空格
  • C++98在嵌套模板实参有中要使用 “> >” 而非 “>>”
for范围循环
  • 只要支持迭代器,则可以使用for范围循环 begin end
  • 数组支持范围循环
初始化列表 和 initializer_list类
  • C++11中支持统一的初始化方式 {}
  • {1,2,3,4,5} 初始化列表
=default =delete
  • =default使用编译器默认的构造、拷贝构造函数
  • =delete删除某些编译器自动生成的函数 析构函数不能被=delete
C++支持委托构造 和 类内部成员直接初始化
  • 委托构造: 在初始化列表中调用自己的其它构造函数
  • 类内部成员直接初始化,对定义的属性直接赋值
override指示符
  • 在重写中,重写基类的虚函数时,特征一点也不明显

  • 用override来表示该函数为重写基类中的虚函数

    class A{
    public:
        virtual void func(){
            
        }
        void bar(){
            
        }
    };
    
    class B:public A{
    public:
        void func()override{
            
        }
        
        void bar()override{//不是重写  编译报错
            
        }
    };
    
final
  • class 类名 final{}; 阻止类被继承
  • 阻止重写的虚函数被子类再次重写
lambda 表达式
  • 在调用算法时,需要给算法传递一个可调用的对象(callable object)

    • 函数/函数对象/lambda
  • lambda表达式

    [capture list](parameter list)->return type{function body}
    * captrue list 捕获列表 局部变量的列表,在lambda表达式依然可以访问的外部变量列表
    * parameter list 参数列表  调用该函数时需要传递的参数  和普通函数参数列表一样
    * ->return type  返回值类型尾置
        
     有时候可以忽略参数列表和返回值类型,但是必须包含捕获列表和函数体
     
    auto f = []{return 0;};
    
    
  • lambda表达式表示一个可调用的代码单元,可以理解为未命名的内联函数

  • lambda关于变量的捕获方式

情况说明
[]空捕获列表,lambda不能使用所在函数中的变量
[&]隐式捕获列表,采用引用捕获方式。lambda函数体中能够使用来自函数中的所有变量,且都是使用引用方式捕获,能够在lambda中修改局部变量的值并影响其结果
[=]隐式捕获列表,采用值捕获方式。lambda函数体中能够访问来自函数中的所有变量,但不能修改。
[names]names是一个以逗号隔开的名字列表,lambda函数体中能访问这些变量
[&, names]除了names列表中的变量全部以引用方式捕获 names一般来说都是a,b,c
[=,names]除了names列表中的变量全部以值方式捕获 names一般来说都是&a,&b,&c
  • lambda参数

    • lambda不能有默认参数
    • 和正常函数的形参列表是一样的
  • lambda返回值类型

    • 如果想指定lambda返回值类型,必须使用返回值类型尾置的方式
    • lambda表达式一般都能推断出来返回值类型
  • 使用STL算法时,经常使用lambda表达式

bind 参数绑定
  • bind1st

  • bind2nd

  • C++11 bind #include

  • 给函数绑定实参,产生一个新的可调用对象

  • 可以调用参数的顺序,产生一个新的可调用对象

    _1,_2  参数占位符     新的可调用对象需要传递的参数
    using namespace std::placeholders;
    
    void func(int a,int b,string c,char d,double e);
    auto f = bind(func,_1,_2,"hello",'A',3.14);
    f(a,b);
    
    auto f1 = bind(func,_2,1024,"Helo",_1,3.333);
    f1(d,a);
    
    
正则表达式
  • 处理文本字符串

  • 用特殊的字符规则来匹配字符串

  • 标准的正则表达式

    符号意义
    “abc”“abc”
    ^匹配行的开头
    $匹配行的结尾
    .匹配任意单个字符
    [abc]匹配a或者b或者c
    […]匹配[]中任意一个字符
    [a-z]匹配任意任意一个小写字母
    [a-zA-Z0-9]匹配任意一个字母和数字字符
    [^abc]匹配除了a,b,c以外任意一个字符
    [^0-9]匹配除了0-9以外的任意一个字符
    (…)设置分组
    \转义字符
    \\匹配一个\
    \d匹配数字字符 [0-9]
    \D匹配非数字字符 [^0-9]
    \w匹配[a-zA-Z0-9_] 字母,数字,下划线
    \W匹配[^a-zA-Z0-9_]
    \s匹配空格
    \S匹配非空格字符
    |逻辑或
  • 匹配次数

    符号说明
    +前面的元素重复1次或者多次 \d+
    *前面的元素重复0次或者多次
    前面的元素重复0次或者1次
    {n}前面元素重复n次
    {n,}前面元素至少重复n次
    {n,m}前面元素重复至少n次,最多m次
C++形式字符类
字符类名说明
[:alnum:]字母和数字 [a-zA-Z0-9]
[:alpha:]字母
[:black:]空格或制表符
[:cntrl:]文件格式转义字符
[:digit:]数字
[:graph:]字母,数字,英文标点
[:lower:]小写字母
[:upper:]大写字母
[:print:]字母,数字,英文标点和空格
[:punct:]英文标点
[:space:]空格
[:xdigit:]十六进制字符 0-9abcdefABCDEF
C++11提供了正则表达式库 regex
#include <regex>

regex     正则表达式的类模板
regex r("[6-12]@163.com");

regex_match(str,reg)  //尝试匹配一个正则表达式到整个字符序列   str整体满足reg才匹配成功
regex_search(str,reg) //尝试匹配一个正则表达式到字符序列的任何部分
regex_replace(str,reg,s) //把str中匹配reg都替换为s

邮箱:

​ 输入一个字符串 能不能判断它是一个有效的163邮箱

​ [6-12]@163.com

​ “\w{6,12}@163\.com”

​ “0571-\d{8}”

​ “\d{17}[0-9X]”

​ “(abc)?xyz”

​ 要么以abc开头 要么以 xyz开头

        ~~~

1[by][cz]”

“^(abc)|(xyz)”
~~~

C++程序设计

  • 从C过渡到C++
  • C++面向对象 对象和类
  • 封装 继承 多态
  • 运算符重载
  • 异常和IO
  • 模板的语法
  • C++11语法特性
  • STL 模板库
    • STL里面有容器 数据结构 都有封装

C++基本介绍

  • ++ C语言运算符 自增 C++是在C语言的基础之上进行自增(扩展) 基本上兼容C语言
  • 1979年本贾尼-斯特劳斯特卢(劳)普(C++之父) 在贝尔实验室从事C语言改良工作 C with Classes 带类的C语言 只是C语言的一部分
  • 1983年正式命名为C++
  • 1989年制定C++标准,直到1998年才投入使用 C++98标准
  • 2003年有修复bug, 03标准
  • 2011年 C++11标准 颠覆性地(增加了很多语法知识) 每三年固定发布新标准
  • 2014年 C++14
  • 2017年 C++17
  • 2020年 C++20
C++的特点
  • 支持面向对象,(C语言面向过程)C++宏观上支持面向对象,微观支持面向过程
  • 支持运算符重载 让自定义类型(struct/class)的数据也支持±*/><==操作
  • 支持异常机制 错误处理 #include <errno.h>
  • 支持泛型编程 对类型抽象化 list 双向链表 存储任意类型的数据
C++的应用
  • 执行效率与开发效率兼顾
    • C语言具有其它高级编程语言无法比拟的执行效率
    • C++优化编译器,在某些场合能使得C++具有C语言的执行效率
    • C++面向对象编程,有丰富的扩展库,用C++进行开发,直接可以使用库
  • 科学计算库 基本上都是C++实现
  • 计算机网络 ACE库
  • 网络游戏 大型的网络游戏 基本上都是用 C++实现的
  • 强建模能力 数学建模 3D
  • 桌面应用程序 office办公软件 windows桌面
  • 驱动程序 C++优化的编译器使得C++执行效率提高
C和C++的区别?
  • C++基本上完全兼容C语言 C语言是C++的一个子集 C++是C语言的一个超集
  • C语言面向过程,C++面向对象的 C++宏观面向对象,微观面向过程的
  • C++支持运算符重载
  • C++支持异常机制
  • C++支持泛型编程
第一个C++程序
//01hello.cpp

#include <iostream>
using namespace std;

int main(int argc,char *argv[]){
	cout << "Hello wrold!" << endl;
	return 0;
}
  • g++ 01hello.cpp 默认生成a.out可执行程序

  • C++源代码的后缀一般都是

    cpp      c++       cc       C
    
  • C++代码的编译器是g++

    • 但是某些情况下,也可以使用gcc编译器,但需要链接 -lstdc++ 库文件

      gcc 01hello.cpp -lstdc++         //-lstdc++ 链接C++的库
      
  • 解释C++第一个程序的语法知识

    #include <iostream>  //C++标准IO的头文件   iostream               C++标准库没有.h
    //在C++中可以直接使用C语言的头文件         但是如果使用C语言标准的头文件    stdio.h一般来说C++有自己提供的             #include <cstdio>     #include <cstdlib>
    
    using namespace std;            //C++把标准库中所有对象和函数都放在了std名字空间里面
    //名字空间是对代码的逻辑划分
    //声明std名字空间中的内容对当前作用域可见
    
    //主函数 main   和C语言中的一模一样
    int main(int argc,char *argv[]){
        cout << "Hello world" << endl;
        return 0;
    }
    
    << 输出运算符
    >> 输入运算符
    
  • cout 标准输出对象

  • cin 标准输入对象

  • cerr 标准错误输出对象

  • endl 换行 \n 对象

C++的基本数据类型
  • C++中有bool布尔类型
    • bool类型的取值为 true 和 false 表示 真和假 底层用 1和0存储
    • 在输出时,true – 1 false—0 可以使用boolalpha 输出 true 和 false
C++中的struct
  • C++中的struct与C语言中struct的区别?

    • C++用struct定义的结构体类型之后,定义变量时可以不用struct关键字

    • sizeof(空结构体)在C语言中为0 C++中结果为1 主要是为了标识内存

    • C++ struct中可以定义函数 C语言中不可以 在自定义类型中定义函数,即面向对象的思想

      • 在struct中定义的函数,可以直接访问结构体中定义的变量
      • 在函数中直接访问的是调用该函数时结构体变量的属性
    • C++struct中的变量可以用static修饰

      • 需要在struct外面进行定义
      • 在求sizeof时,并不会包含static成员的大小
      • static属性属于类型,而不是属于某个结构体变量 在内存中只有一份,该类型的变量共享
      • 可以直接通过 类名::静态属性 直接访问静态属性
      //结构体中的静态属性  需要在类外进行定义
      类型 结构体名::成员名 = init_val;
      
    • C++struct中可以用访问控制属性(public(公开的,默认),protected,private)加以访问限制

    • C++中struct允许继承

C++中的枚举
  • C++中的枚举是一种独立的数据类型,不能直接用整数进行赋值

    enum Direction{UP,DOWN,LEFT,RIGHT};
    
    Direction d = UP;//cout << d << endl; 依然是整数 
    d = DOWN;
    d = LEFT;
    d = RIGHT;
    
    d = 0;   d = 1;   d = 1024;          //错误的  但是C语言中允许 
    int num = d;
    num = LEFT;  //可以的
    
C++中的联合
  • C++中支持匿名联合(既没有类型,也没有定义联合变量)

  • C++中的匿名联合表示的是变量在内存中的布局方式

  • 可以直接访问匿名联合中的属性

    //以编译时,以联合的形式编译布局a,b,c,即让a,b,c共用一块内存
    union{
        int a;
        int b;
        char c;
    };
    
    a = 1024;
    b = 9527;
    c = 'A';
    
    
C++中的字符串
  • C++保留了C风格的字符串,同时引入了string类型的字符串

  • string的初始化

    string s1;  //空字符串
    string s2 = "Hello world"; //C指针的初始化方式
    string s3("Hello world");
    string s4 = {"HEllo world"};
    string s5 = s4;
    string s6(10,'X'); //"XXXXXXXXXX"
    //指针是一种天然的迭代器
    string s7(iterator begin,iterator end);  //[begin,end)  用这个区间中的字符构造字符串
    
  • string变量的赋值

    string s = "Hello world";
    s = "中国速度";  //对string变量进行赋值
    
    string s1("hello");
    s1 = s;        //字符串之间进行赋值
    
    
  • 字符串的拼接 +

  • 字符串中字符的访问与修改 []

  • 字符串长度

    size_t length();                s.length()
    
  • 返回C风格的字符串

    char *c_str();
    
  • 字符串判断相等 ==

    string a("Hello"),b("Hello");
    if(a == b){//字符串a和字符串b相等
        
    }
    
  • 元素访问

    string s("Hello world");
    //1.下标  []
    cout << s[0] << endl;
    s[1] = 'E';
    //2.at  对比[]  进行越界检查    如果越界会抛出异常   一旦越界,会抛出 std::out_of_range 异常
    char& at(size_t index);
    cout << s.at(0) << endl;   //s[0]
    //3.front  C++11  g++ -std=c++0x
    char& front()   获取首字符  返回其引用
    //4.back   
    char& back()    获取末尾字符          不是\0
    //5.data()
    const char* data();
    //6.c_str()
    const char *c_str();
    
  • 迭代器(指针)

    正向迭代器    string::iterator it
    iterator begin();            //正向迭代器   字符串首字符的地址
    iterator end();              //正向迭代器   字符串最后一个字符的下一个地址
    
    逆向迭代器
    reverse_iterator rbegin()    //逆向迭代器   字符串最后一个字符的地址
    reverse_iterator rend()      //逆向迭代器   字符串第一个字符的前一个地址
        
    //不能通过迭代器修改字符串中的内容
    常正向迭代器   const_iterator  
        cbegin()    cend()
    常逆向迭代器   const_reverse_iterator 
        crbegin()   crend()
        
        
    正向迭代器   ++    往后偏移    --  往前偏移
    逆向迭代器   ++    往前偏移    --  往后偏移
        
    *迭代器             就相当于指针解引用
    
  • 字符数量和容量

    bool empty()              //检查字符串是否为空
    size_t size();            //字符数量
    size_t lenght();          //字符数量
    void reserve(size_t s);           //预分配内存   如果s小于目前的容量则不会干任何事件  只扩不缩
    size_t capacity()         //容量    自动扩容 
    
  • 操作

    void clear();
    //指定位置
    basic_string& insert( size_type index, size_type count, CharT ch );//在index位置插入count个ch字符
    basic_string& insert( size_type index, const CharT* s ); //在index位置插入一个C风格的字符串
    basic_string& insert( size_type index, const basic_string& str, size_type index_str, size_type count );//在index位置插入 str字符串 index_str开始往后count个字符
    
    //指定迭代器
    iterator insert( iterator pos, CharT ch );
    void insert( iterator pos, size_type count, CharT ch );
    void insert( iterator pos, InputIt first, InputIt last );
    
    
    basic_string& erase( size_type index = 0, size_type count = npos );//从index处开始移除,移除count个字符,如果没有传递count,则移除至末尾
    
    iterator erase( iterator position ); //移除指定位置(迭代器)的元素
    iterator erase( iterator first, iterator last );//移除[first,last)区间的元素
    
    void push_back( CharT ch );  //自动扩容 
    void pop_back();
    
    //追加若干个字符
    basic_string& append( size_type count, CharT ch );
    basic_string& append( const basic_string& str );//追加一个字符串
    basic_string& append( const basic_string& str,size_type pos, size_type count = npos );
    
    
    int compare( const basic_string& str ) const;
    支持==
        
    basic_string& replace( size_type pos, size_type count,const basic_string& str );
    
    basic_string substr( size_type pos = 0, size_type count = npos ) const;
    constexpr basic_string substr( size_type pos = 0, size_type count = npos ) const;
    
    //如果没有找到str  则返回 npos     从pos位置开始查找
    size_type find( const basic_string& str, size_type pos = 0 ) const;
    
名字空间
  • 名字空间其实是对代码的逻辑划分,避免名字冲突

  • 名字空间的定义

    namespace 名字空间名{
        定义全局变量;
        定义函数;
        定义类型;
    }
    
  • 使用名字空间中的内容 作用域限定符 ::

    名字空间::标识符                     ---指定使用哪个名字空间中的内容
    
  • 声明名字空间对当前作用域可见

    using namespace 名字空间名;
    
    直接使用标识符           不需要  名字空间::标识符
    
    • 如果使用using namespace声明多个名字空间,如果使用同名的标识符,会引发冲突,引发冲突之后,必须使用 名字空间::标识符
  • 把名字空间中的标识符导入到当前作用域

    using namespace::标识符               优先级会比using namespace 名字空间   优先级高  局部优先原则
    
  • 名字空间名相同的名字空间 自动合并

    • 名字空间名不会有重定义的问题
    namespace N1{
        int a;
    }
    namespace N1{
        int b;
    }
    
  • 名字空间嵌套

  • 匿名名字空间

    • C++会把声明定义在全局区的标识符通通归纳到一个统一的名字空间中,称为匿名名字空间

    • 直接使用 ::标识符 指定访问全局域中的标识符 (匿名名字空间中的标识符)

      int g = 1024;
      int main(){
          int g = 9527;
          cout << g << endl;
          {
              extern int g;
              cout << g << endl;
          }
          cout << ::g << endl;
          return 0;
      }
      
    • 多文件编程中

      • 声明

        namespace N{
            extern int a;
            void func();
            struct Stu{
                int no;
                string name;
            };
        }
        
      • 定义实现

        int N::a;
        void N::func(){
            
        }
        

C++中的函数

  • g++编译器不会再为函数提供隐式函数声明
    • C++调用函数之前,必先声明或定义
  • C语言函数的隐式声明:
    • gcc编译器在编译代码时,如果遇到一个调用的函数在此之前没有定义和声明,则为其隐式声明一个函数,且为其声明函数的返回值类型为int
  • C++函数的形参列表为空 就相当于 C语言中 形参列表为void
C++的函数支持重载
  • 函数重载: 即在同一个作用域下,可以定义同名的函数,但函数的参数列表不同相当

    • 同一个作用域下,函数名相同,参数列表不同即构成重载,在编译调用重载的函数时,会根据实参的个数和类型来绑定调用的函数,静态重载
    • 前提条件:同一个作用域下 如果在不同的作用域,不能构成重载
    • 必要条件:函数名相同,函数列表不同
      • 函数列表不同
        • 参数个数不同
        • 同位置的类型不同
        • 对于指针和引用的类型,常属性不同也能构成重载
          • 如果是非指针和引用,常版本和非常版本会重定义
      • 重载与形参名和返回值类型无关
  • 为什么C++支持重载,而C语言不支持重载

    • g++编译在编译函数时,会对函数进行改名操作 _Zn函数名形参类型简写 _Z3maxii _Z3maxdd _Z3maxiii

    • gcc编译器不会对函数名进行更名操作

    • 可以用 extern “C” 来指定让g++使用C风格的方式来编译函数

      extern "C"   //如果需要在C语言中调用C++的函数,那就必须使用extern "C"来编译C++的代码
      void func(){}
      
      
      extern "C"{
          void f1(){};
          void f2(){}
      }
      
  • 注意调用重载时产生的二义性 (歧义)

    void func(char a,char b){
        
    }
    void func(int a,int b){
        
    }
    
    int main(){
        func('a','b');//有明确类型   没有任何歧义 
        func(1,2);    //没有歧义 
        //func(3.14,5.16);  //歧义   double->char   double->int
        //func('a',5);      //歧义    char,char     int,int
        func('a',3.14);     //没有歧义    char,char  只需要有一个类型隐式转换     
        func(3.14,5);       //int,int 
        return 0;
    }
    
C++的函数支持默认值(缺省值)
  • 在定义函数时,形参可以声明默认值

    • 好像定义变量直接初始化一样
  • 如果一个函数有默认值,则在调用该函数时,可以为该形参传递实参,也可以选择不传,不传则采用默认值

  • 靠右原则

    • 如果一个形参有默认值,则其后面(右边)所有的形参都必要要有默认值
  • 有缺省值注意不要和重载产生歧义(二义性)

    //_Z3barv
    void bar(){
        
    }
    //_Z3barii
    void bar(int a=1,int b=2){
    
    }
    
    //bar();//一调用则产生歧义
    bar(1024);
    bar(9527);
    
  • 如果在函数声明和定义分离,则缺省值只能出现在函数声明中

    void func(int a=1024);
    
    void func(int a){
        
    }
    
C++的函数支持哑元
  • 只有类型,没有形参名谓之哑元

    void func(int){
        
    }
    
    • 在调用哑元函数时,一定要传递一个该类型的实参 只关心类型,不关心值的大小
    • 需要注意和重载的重定义
C++的函数支持内联
  • inline
  • 优化方式 提高代码的运行效率
    • 直接用函数的二进制指定替换函数的调用指令 加快程序执行速度 使可执行程序变大
    • 只是一种申请和建议 取决于编译器
    • 简单且重复调用的函数可以声明为内联函数

C++的动态内存

  • 动态内存:堆内存,需要程序员自己手动申请和手动释放
  • C语言中有一套函数来操作动态内存 malloc/calloc/realloc/free
  • C++申请动态内存 new /new [] 释放动态内存 delete/delete []
申请动态内存
申请单个数据的动态内存
数据类型*  p = new 数据类型(初始值);

int *p1 = new int;  //new一个int类型的内存空间  初始化为"零"
int *p2 = new int(1024);   //new一个int类型的内存空间   并且初始化为1024
int *p3 = new int{9999};   //C++中{}

//释放动态内存
delete p1;
delete p2;
delete p3;

//变量的初始化   大概有三种
int a = 1024;       
struct Stu s = {110,"jack"};
string s("hello");

//C++11进行了统一      不管什么数据,都可以用 {}这样形式进行初始化    {}初始化列表
申请数组的动态内存
数据类型* p = new 数据类型[个数];

int *p1 = new int[10];
int *p2 = new int[5]{1024,9527,1111,2222,618};


//在释放时必须使用 delete[]
delete[] p1;
delete[] p2;
new/delete和malloc/free的区别?
    1. new/delete是C++中申请动态内存的关键字(不是函数),malloc/free是C语言标准库中用于申请动态内存的函数
    1. new和new[]会调用类型的构造函数,但malloc/calloc不会调用类的构造函数

      delete和delete[]会调用类的析构函数,但free不会调用类的析构函数

    1. new和new[]返回的是特定类型的指针,但是malloc/calloc返回的是void *,在使用时,C++需要转换成特定类型的指针
    1. new和new[]申请动态内存失败时,抛出异常bad_alloc,malloc/calloc申请动态内存失败时返回NULL
    1. new申请动态内存时,需要给定特定的类型,不需要自己求类型的字节宽度,但是malloc需要给定申请动态内存的字节大小 new只关心类型 malloc只关心大小
    1. 申请多个内存空间时, new用的是 new[], 但是malloc只需要传递内存大小即可(自己计算)
    1. new申请内存用delete释放,new[]申请的数组内存用delete[]释放,但是malloc/calloc申请的内存一律用free

引用

  • C++想用引用来取代指针,C++依然存在着大量的指针
  • C++引用的底层实现是指针 包装
  • 引用即别名
    • 引用变量和所引用的对象是同一个变量
    • 引用变量的内存地址 和 目标对象的内存地址是 一样的
void swap(int& a,int &b){
    int t = a;
    a = b;
    b = t;
}

int main(){
    int a = 1024,b = 9527;
    cout << "a=" << a << ",b=" << b << endl;
    swap(a,b);
    cout << "a=" << a << ",b=" << b << endl;
    return 0;
}
引用的定义
数据类型& ref = 引用的目标;
  • 引用必须初始化 让引用有确定的引用目标

    int &ra;   //编译错误
    
  • 引用一旦有引用的目标,不能再引用其它的目标

    int n = 1024;
    int & ra = n;  //让ra引用引用n这个变量   ra和n至死方休
    int m = 9527;
    ra = m;        //对引用变量的ra进行赋值   不是让ra引用m变量   ra还是引用n   其实相当于n = m;
    
  • 引用不能为空 指针NULL 不存在空引用

常引用 引用的目标是常量
  • 引用的是常量/变量,不能通过引用修改目标的值
const 数据类型& ref = 引用的目标;

常引用  可以引用常量,也可以引用变量
    
常量如果需要定义引用来引用,那么这个引用必须是常引用

const int& ra = 10;
int a = 1024;
const int& rb = a;

const int& const ref = a;  //错误的
//因为引用本身就不能更改引用目标   对引用的操作实际上都是对引用目标的操作

//没有引用常量这一说
C++中的const
  • C++中定义的const变量是真正的常量,在代码中使用const常量的地方,在编译时就用const常量的值替换(相当于C语言中的宏一样),C++中会为常量分配内存

  • C++中用const定义的常量必须初始化

    const int num;       //编译错误
    
  • C++中变量和对象的const一旦缺失则会报错

C++11中右值引用
  • 右值引用只能引用右值,不能引用左值
int&& ra = 10;
ra = 1024;
const int&& rb = 10;
  • move(左值) 获得其 右值
  • 字面值是右值 const修饰的变量称为常量,但依然是左值
引用普通变量(左值)const变量(只读的左值)右值const属性右值
int&OKNONONO
const int &OKOKOKOK
int&&NONOOKNO
const int&&NONOOKOK
指针与引用的区别?
    1. 指针是实体变量,但是引用不是实体变量

      sizeof(指针) == 4/8 sizeof(引用变量) 不是固定的

    1. 指针可以不初始化 ,但引用必须初始化

      int *p;  //可以的
      int &ra; //错误的
      
    1. 指针可以初始化为空NULL,但引用不能为空
    1. 指针可以改变指向,但引用不能更改引用目标

      int n=1024,m=9527;
      int *p = &n;
      p = &m;   //指针改变指向
      
      int &rn = n;  //引用n     
      //&rn = m;      //rn不能再引用m 
      rn = m;      //对rn进行赋值 
      
    1. 可以定义指针的引用,不能定义引用的指针

      int n = 1024;
      int *pn = &n;
      int *& rpn = pn;  //指针的引用
      
      int& rn = n;
      //int& *pr = &rn;  //无法定义int&(引用)的指针
      
    1. 可以定义指针的指针,不能定义引用的引用

      int n = 1024;
      int *pn = &n;
      int **ppn = &pn;   //指针的指针   二级指针
      
      int& rn = n;
      //int&& rrn = rn;    //错误的   C++11 && 右值引用       没有二级引用
      
    1. 可以定义数组的引用,但不定义引用的数组

      int arr[5] = {1,2,3,4,5};
      int (&ra)[5] = arr;  //ra引用了arr数组  ra就相当于数组名arr
      
      //int& brr[5];        //引用的数组    错误的      不能声明引用的数组
      
  • 引用的本质还是指针,底层用指针实现的引用

显示类型转换

  • 隐式类型转换
    • 数据之间可以自动进行转换 基础数据类型之间都可以进行隐式类型转换
  • 显示类型转换
    • 强制类型转换
      • (目标类型)源对象; 强制类型转换的结果可能是不正确的
    • 静态类型转换
      • static_cast<目标类型>(源对象)
      • 适用场景: 源对象类型和目标类型双方在一个方向上可以进行隐式类型转换,则两个方向上都可以进行静态类型转换
      • TYPE * —> void * 可以进行隐式类型转换
      • SON * —> FATHER * 可以进行隐式类型转换
    • 去常属性类型转换
      • const_cast<目标类型>(源对象)
      • 适用场景:只适用于去除常指针或者常引用的常属性
    • 重解释类型转换
      • reinterpret_cast<目标类型>(源对象)
      • 适用场景:不同类型的指针之间,或者整数与指针之间的类型转换
        • char * int *
        • int void *
        • void * int
    • 动态类型转换
      • dynamic_cast<目标类型>(源对象)
      • 适合场景:只适用于有多态关系的父子指针或者引用类型之间的转换

运算符别名

  • && and
  • || or
  • ! not
  • ^ xor
  • & bitand
  • | bitor
  • {} <% %>
  • [] <: :>

vector 向量(顺序表)

  • 构造 初始化方式

    //1.无参构造
    vector();
    vector<int> v0;   //容量和元素个数都为0
    
    //2.指定初始元素个数
    vector( size_type count, const T& value = T(),const Allocator& alloc = Allocator());
    vector<int> v1(10);    //容量和元素个数都为10   元素全部初始化为 "零"
    vector<int> v2(10,1024);   //容量和元素个数都为10   元素全部初始化为1024
    //Allocator 分配器
    
    //3.用区间元素构造  
    template< class InputIt >
    vector( InputIt first, InputIt last,const Allocator& alloc = Allocator() );
    int arr[5] = {1,2,3,4,5};
    vector<int> v3(&arr[0],&arr[5]); //[first,last)
    vector<int> v4(v3.begin(),v3.end());
    
    //4.拷贝构造
    vector( const vector& other );
    vector<int> v5(v4);
    vector<int> v6 = v4;
    
  • 向量之间可以相互赋值

    vector<int> v1(10,1024);
    vector<int> v2;
    v2 = v1;
    
  • 元素访问

    //1.下标访问
    reference operator[]( size_type pos );
    vector<int> v(10,1024);
    v[0] = 1111;
    v[1] = 1212;
    cout << v[0] << endl;
    
    //2.at    本质和[]是一样的  会进行越界检查,一旦越界  抛出 std::out_of_range异常
    reference at( size_type pos );
    
    
    //3.首元素引用    容器为空时行为未定义
    reference front();
    
    //4.最后一个元素的引用    容器为空时行为未定义
    reference back();
    
    //5.返回首元素地址
    T* data() noexcept;
    
  • 迭代器 向量的迭代器是随机迭代器 支持 ± n

    //正向迭代器   ++ 从前往后    -- 从后往前
    vector<int>::iterator
    begin()    end()
        
    //常正向迭代器
    vector<int>::const_iterator
    cbegin()   cend()
        
    //逆向迭代器    ++  从后往前    --从前往后
    vector<int>::reverse_iterator
    rbegin()   rend()
        
    //常逆向迭代器
    vector<int>::const_reverse_iterator 
    crbegin()   crend()
       
    
  • 容量和元素个数

    bool empty() const;
    size_type size() const;
    
    void reserve( size_type new_cap );   //预分配内存   提高插入的效率   只扩不缩
    size_type capacity() const;
    
  • 修改器 操作容量中的元素

    void clear();   //清空元素  不影响容量
    
    //插入      在指定迭代器位置pos插入元素value
    iterator insert( iterator pos, const T& value );
    //在指定迭代器位置pos插入count个value
    void insert( iterator pos, size_type count, const T& value );
    //在指定迭代器位置pos插入[first,last)迭代器区间的元素
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    
    //原位构造
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    //末尾插入元素value
    void push_back( const T& value );
    
    template< class... Args >
    void emplace_back( Args&&... args );
    
    //删除末尾元素  如果为空则行为未定义
    void pop_back();
    
    //改变容器中元素的个数
    void resize( size_type count );
    	//1.count如果小于容器目前的size          删除末尾的元素,保留前count个元素    容量不会缩小
        //2.count如果大于容器目前的size     则在末尾用"零"(无参构造)填充  可能引发扩容 
    
    void swap( vector& other );
    

新式for循环 C++11

//容器一定得提供迭代器
for(循环变量& n:容器){
    
}
  • 用vector实现学生成绩管理系统

list 双向链表

C++之父的建议

  • 树立面向对象的编程思想
  • 少用宏,多用枚举和内联
  • 少用C风格的字符串,多用string
  • 学会C++的异常处理机制
  • 少用普通数组,多用vector,list

面向对象

  • 面向过程: 把问题分解成若干步骤,把每个步骤又封装成函数,然后按照解决问题的顺序调用各个函数

  • 如何把一头大象装进冰箱?

    • 第一步:打开冰箱门 open(bx)
    • 第二步:把大象装进去 push(bx,dx)
    • 第三步:关上冰箱门 close(bx)
  • 面向对象:把问题看作是对象之间相互作用

    • 面向对象编程中,一切都是类和对象
  • 如何把一头大象装进冰箱?

    • 大象 冰箱
    • 冰箱自动打开 bx.open();
    • 大象能够进到冰箱 bx.push(dx)
    • 冰箱自动关门 bx.close()

类和对象

  • 类 --> 类型 一类事物的统称,泛化的抽象的概念

  • 对象 --> 变量 类实例化的结果,特指一个具体存在的事物

  • 用类定义变量的过程,就叫做实例化对象

  • C++中定义类,可以使用struct,也可以使用class

    class 类名{
    访问控制属性:
        //属性
        //方法
    };
    
    

面向对象的三大特征

  • 封装
    • 把一类事物具有的特征抽象为属性,把一类事物都具有的行为抽象为方法,并加以访问控制属性的限制,称为封装
  • 继承
  • 多态
//封装一个类
class 类名{
    //属性    struct 定义变量
    
    //方法    函数    过程            可以直接访问成员属性
};
实例化对象
  • 用类型定义变量
  • 类名 对象名; 实例化出来的对象,对象的属性是垃圾值,需要额外的赋值语句对其进行赋值
  • 如果需要在构造对象时,给对象进行初始化,需要在定义类时,添加构造函数/方法
  • 实例化对象一定会调用构造函数
//无参构造实例化对象     在栈内存实例化对象
ObjType  obj;   //调用无参构造实例化对象
ObjType  obj(); //函数声明    声明一个函数名为obj   返回值类型为ObjType,且没有任何参数  ***!!!

ObjType  obj(arg,..);   //调用有参构造函数

//C++11
ObjType  obj{};     //调用无参构造
ObjType  Obj{arg,...};  //调用有参构造


//在堆内存中实例化对象
ObjType *po = new ObjType;    //调用无参构造
ObjType *po = new ObjType();  //调用无参构造
ObjType *po = new ObjType{};  //调用无参构造
ObjType *po = new ObjType(arg,...);
ObjType *po = new ObjType{arg,...};


//匿名对象         没有变量(对象)名的对象  
ObjType();              //调用无参构造函数实例化一个匿名对象        
ObjType(arg,...);

构造函数
  • 在实例化对象时调用的函数,称为构造函数

    class 类名{
    public:
        //构造函数
        类名(形参列表){
            
        }
        
    };
    
      1. 构造函数没有返回值类型,也不能声明为void
      1. 构造函数的函数名和类型是一模一样的
      1. 如果一个类没有实现构造函数,则编译器会自动生成一个无参的构造函数

        class A{
        public:
            A(void){//无参构造函数
                
            }
        };
        
      1. 如果一个类中有提供构造函数,则编译器将不再为该类提供默认的无参构造函数

      2. 构造函数允许重载

        ​ 重载构造函数,即可以按照不同的需求来实例化对象

      1. 在构造函数中基本上都是对属性进行初始化
  • 通过类实例化对象,一定需要要匹配的构造函数,否则就会编译报错

练习:

写个长方形类,提供必要的构造函数

为学生类提供构造函数

访问控制属性 — 封装的本质
  • public 公开的

  • protected 受保护的

  • private 私有的

  • 一般来讲,属性会设置为private or protected ,方法会设置为public

  • 一般来讲,为会private的属性提供set和get方法

访问控制属性类中类外子类
publicOKOKOK
protectedOKNOOK
privateOKNONO
struct和class在C++中的区别?
  • class访问控制属性是private,而struct的访问控制属性是public
友元 friend
  • 可以声明全局函数有友元函数,那么在友元函数中可以访问该类的私有/保护成员

    class 类名{
        
    	friend 友元函数返回值类型  函数名(形参列表);
    };
    
  • 可以声明类为友元类,那么在友元类中可以访问该类的私有/保护成员

    • 友元类的声明是单面的
  • 友元其实是打破了封装的效果,但某些情况不得已而为之

this
  • 当成员属性名和成员函数中的局部变量同名时,遵循的是局部优先原则,直接访问,访问的是局部变量

  • this->属性 这样能访问到成员属性

  • this是一个指针 指针常量 Type * const this; 指针是一个常量

  • 在所有的成员函数中,都隐含着一个this指针

    • 构造函数中的this指针指向正在构造的对象
    • 成员函数中的this指针指向正在调用该函数的对象
  • obj.func(); 底层实现会转换为 func(&obj); func(Type *const this);

构造函数初始化列列表
  • 只有构造函数才能有初始化列表
class 类名{
    类名(形参列表):初始化列表{
        
    }
    //初始化列表   :成员属性名(初始化值)            初始化值可以是局部变量,也可以成员变量
};
  • 只有在初始化列表中,才叫初始化

  • 在构造函数体中,都叫赋值

  • 如果一个类中的常属性的成员或者有引用类型的成员,都必须在初始化列表中进行初始化

    class A{
    private:
        const int n;  //常属性的成员
        int& r;       //引用型成员
    public:
        A(int n,int& r):n(n),r(r){
            
        }
        
    };
    
  • 初始化列表的执行顺序与初始化列表中语句顺序无关,只与属性的定义的先后顺序有关

    class B{
    private:
        int n;
        int m;
    public:
        B(int a=1024):m(a),n(m){
            
        }
        int getN(){
            return n;
        }
        int getM(){
            return m;
        }
    };
    
    B b;
    cout << b.getN() << ":" << b.getM() << endl;
    
    
组合 在初始化列表中调用类类型成员的构造函数
  • 关于成员有类类型成员时,在构造函数的初始化列表中,默认调用类类型成员的无参构造函数进行初始化

    class Point{
        double x;
        double y;
    public:
        Point(double x,double y):x(x),y(y){
            
        }
    };
    
    class Circle{
        Point center;
        double r;
    public:
        //center(0,0)即调用 Point(double x,double y)有参构造
        Circle():center(0,0){
            
        }
    };
    
    int main(){
        Circle c;  //编译报错          解决方案有2:   1.让Point提供无参构造   2.在Circle构造函数的初始化列表中显示调用Point的有参构造
        return 0;
    }
    
  • 构造函数的初始化列表中,如果有必要调用有参构造函数进行构造成员,需要显示调用

    :类类型成员(实参,…)

  • 构造函数的执行顺序

    • 先按照类类型成员的定义顺序,依次调用类类型成员的构造函数(默认是无参构造),然后再执行构造函数体
  • 可以利用构造函数在main函数之前和之后输出内容

    class A{
    public:
        A(){
            cout << "A()" << endl;
        }
        ~A(){
            cout << "~A()" << endl;
        }
    };
    A obj;  //在main begin之前输出 A()   在main end之后输出 ~A()
    int main(){
        cout << "main begin" << endl;
        cout << "main end" << endl;
        return 0;
    }
    
单参构造函数和explicit
  • 如果一个类A提供了一个T类型的单参构造函数,那么T类型的数据可以隐式调用该单参构造函数转换为A类型的对象
  • 如果需要禁止T类型数据隐式调用单参构造函数转换为A类型的对象,可以在A类型的单参构造函数前加上explicit关键字
  • 加上explicit之后就不能隐式进行,只能显示调用构造函数进行转换
静态属性和静态方法
静态属性
  • 属性用static修饰,称为静态属性,类属性

  • 静态属性需要在类外进行定义

    class 类名{
    public:
        static int s;//声明      没有分配内存空间
    };
    
    int 类名::s = 1024;   //在类外定义和初始化
    
  • 静态属性属于类,而不属于某个对象

    • 这个类所有的对象都共享 每个对象都能访问,访问到的都是同一个
    • 静态属性存储在全局数据区
    • 成员属性一般存在在栈区或者堆区
    • 静态属性和成员属性分开存储 sizeof(类) 不包含静态属性
  • 静态属性就相当于加了 类名:: 作用域限定的全局变量

    • 静态属性可以直接用 类名::静态属性 的方式直接访问
    • 也可以使用 对象.静态属性 的方式进行访问
静态方法
  • 方法用static关键字修饰,称为静态方法,类方法

  • 静态方法属于类,而不是属于某个对象

  • 相当于加了 类名:: 作用域限定符的 全局函数

  • 可以直接使用 类名::静态方法名(参数列表); 方式直接调用静态方法

  • 也可以使用 对象.静态方法名(参数列表); 方式进行调用

  • 静态方法中没有隐含的this指针 !!!!

    • 静态方法中不能直接访问成员变量
    • 静态方法中不能直接调用成员方法
  • 静态方法中可以直接访问静态属性 和 调用其它静态方法

  • 单例模式

    • 一个类只能实例化一个对象
      • 私有化构造函数
      • 静态方法获得唯一的实例
常对象和常函数
  • C++中const和C语言中的const是不一样的

    • C语言中的const仅仅是表示只读
    • C++const表示却是常量 g++编译器会对const进行替换处理,在编译时对使用const定义变量进行直接替换,好比C语言中的宏定义 C++中const变量必须初始化
  • 常对象,const修饰的对象 常对象的属性是只读(read only)

  • mutable修饰属性,表示常对象也可以修改该属性的值

  • 常方法/常函数(一定得是成员函数才行)

    class A{
        
        //const 常方法、常函数   const修饰的是   *this
        返回值类型  成员方法名(形参列表)const{
            
        }
    };
    
  • 常对象,只能调用常方法 不能调用非常函数

  • 普通对象能够调用常方法 如果没有非常方法的情况下

  • 常函数和非常函数构成重载

    • 常对象只能调用常方法
    • 普通对象在有非常方法时,选择调用非常方法,如果没有非常方法,则调用常方法
  • 在实践过程中,如果一个方法的常方法和非常方法的实现是一样,且不会修改成员时,都会把方法定义成常方法

析构函数
  • 在实例化对象时 创建了一个对象 对象存在生命周期,到对象要消亡时,会自动调用析构函数

    class 类名{
    public:
        //构造函数
        类名(){}
        //析构函数
        ~类名(){}
    };
    
  • 析构函数没有返回值 也不能是void

  • 析构函数的名字 ~类名

  • 析构函数没有参数, 所以析构函数不能重载

  • 如果一个类没有实现析构函数,则编译器会自动生成一个析构函数,该析构函数为一个空析构

  • 析构函数不需要手动调用

    • 对象消亡时自动调用
      • 栈内存中的对象 栈内存回收时
      • 堆内存中的对象 delete
      • 全局数据区的对象 程序结束
  • 什么时候需要自己实现析构函数?

    • 对象有申请动态内存或者打开文件等等操作时 有必要自己实现析构函数
    • 在析构函数中释放动态内存 或者 关闭文件等操作
    • 析构函数避免内存泄露
  • 析构函数执行顺序

    • 正好和构造的顺序相反

实现一个栈 Stack 构造 empty push pop top size()

成员 vector v;

成员属性指针 和 成员方法指针
成员属性指针
成员类型  类名::*指针名;

成员类型  类名::*指针名 = &类名::属性名;  

指针名 = &类名::属性名;

成员属性指针保存的并不是真实的内存地址  (1)

--直接成员指针解引用    对象
    对象.*成员属性指针    
--间接成员指针解引用    对象的指针
    对象指针->*成员属性指针


成员方法指针
//成员方法指针定义
成员函数返回值类型   (类名::*pf)(形参列表);

成员函数返回值类型   (类名::*pf)(形参列表) = &类名::方法名;

pf = &类名::方法名;

(对象.*pf)(实参列表);

(对象指针->*pf)(实参列表);

作业:

1.定义一个平面点类,提供构造函数,提供成员方法计算点到原点的距离

​ 提供成员方法 计算一个点到另外一个点的距离

​ 提供静态方法 计算两个点之间的距离

2.定义一个日期类,成员有年,月,日,是否有效的标识,提供构造函数

​ 提供成员方法计算下一个天

​ 提供成员方法获取 年 月 日分量

​ 提供成员方法计算这是当年的第几天

​ 提供静态函数,计算某一年是否是闰年

3.定义一个复数类

​ 提供构造函数

​ 提供成员方法 计算 复数的 加法 减法 乘法 除法

拷贝构造
  • 由已经存在的同类型的对象构造一个新的副本对象 调用的是 拷贝构造函数

  • 拷贝构造函数的形式是固定不变的

    class 类名{
    	//拷贝构造函数
        类名(const 类名& other){
            
        }
        
    };
    
  • 拷贝构造函数也是构造函数,是构造函数里比较特殊的一个

  • 特殊在 参数 接收一个同类型的对象的常引用

  • 如果在实现一个类时,没有为这个类添加拷贝构造函数,则编译器会自动为该类提供一个默认的拷贝构造函数

  • 默认的拷贝构造函数的实现是 成员复制 (按字节拷贝) 副本和原对象一模一样

  • 如果有自己实现拷贝构造函数,则编译器将不再提供默认的

  • 什么情况下调用拷贝构造函数?

    • 用存在的对象构造新的同类型的对象
    • 在调函数非引用传递对象时调用拷贝构造函数
    • 把对象存储到容器中一律都是调用拷贝构造函数 容器存储的对象是插入对象副本
    • 类名 对象名(旧对象);
    • 类名 对象名 = 旧对象;
  • 什么情况下需要自己实现拷贝构造函数?

    • 如果需要实现深拷贝时则需要自己实现拷贝构造函数 默认的拷贝构造函数是浅拷贝
    • 有指针指向动态内存时一般需要实现拷贝构造函数
  • 浅拷贝 和 深拷贝

    • 浅拷贝 ------------- 按字节拷贝 按字节复制 拷贝的副本和原对象一模一样
      • 对于指针,只拷贝内存地址 拷贝之后,两个指针指向(存储)同一块内存 指针的值是一样
    • 深拷贝 ------------- 相对指针而言(普通数据没有深拷贝这一说) 拷贝指针所指向的内容
      • 对于指针,重新申请内存,把原来指针所指向的内存空间中的数据拷贝过来 新旧指针指向不同的内存空间 但是它们所指向的内存空间中间值是一样的
拷贝赋值
  • 当两个已经存在的同类型对象之间的相互赋值 调用的是拷贝赋值函数

  • 拷贝赋值函数形如

    class 类名{
    //拷贝赋值函数
        类名& operator=(const 类名& other){
            
        }
    };
    
  • 本质是重载=运算符

  • 如果一个类没有实现拷贝赋值函数,则编译器会自动提供拷贝赋值函数,默认的拷贝赋值函数是浅拷贝

  • 如果需要实现深拷贝,则需要自己实现拷贝赋值函数

  • 一般来说,遵循三/五原则

    • C++11之后三原则:析构 拷贝构造 拷贝赋值 要么是全部自己实现,要么是全部默认的
    • C++11之后五原则:析构 拷贝构造 拷贝赋值 移动构造 移动赋值 要么全实现,要么全默认
string
  • 自己实现简单的string类

    class String{
    public:
        //用C风格的字符串构造 String对象
        String(const char *s=NULL):str(strcpy(new char[s?strlen(s)+1:1],s?s:"")){
            /*
            if(s==NULL){
                str = new char[1];
                strcpy(str,""); \\'\0'  str[0] = '\0';
            }else{
                str = new char[strlen(s)+1];
                strcpy(str,s);
            }
            */
        }
        ~String(void){
            if(str != nullptr){
                delete [] str;
                str = nullptr;
            }
        }
        //拷贝构造函数   用同类型的对象拷贝构造一个同类型的副本对象  
        String(const String& s):str(strcpy(new char[strlen(s.str)+1],s.str)){
            //str = new char[strlen(s.str)+1];
            //strcpy(str,s.str);
        }
        // a = b;    a已经存在了    a.operator=(b)     (b = c) = a;
        // a = a;
        String& operator(const String& s){
            if(this != &s){//避免自己给自己赋值时  直接 delete
                String _tmp(s); //拷贝构造一个s的副本
                swap(str,_tmp.str);  //看不到释放内存  但它有释放  delete [] _tmp.str
                //_tmp对象生命周期在语句块结束之后 到期  自动调用析构   
                /*
                char *ps = new char[strlen(s.str)+1];
                strcpy(ps,s.str);
                delete [] str;
                str = ps;
                */
                /*
                delete [] str;
                str = new char[strlen(s.str)+1];
                strcpy(str,s.str);
                */
            }
            return *this;
        }
        size_t length(void)const{
            return strlen(str);
        }
        const char *c_str(void)const{
            return str;
        }
    private:
        char *str;
    };
    
    
    

封装一个栈

​ 不能用vector实现

​ 只允许 int *

​ 构造函数 指定栈的容量 full empty push pop top size capacity

​ 拷贝构造

​ 拷贝赋值

​ 析构

总结默认的无参构造,拷贝构造,拷贝赋值,析构
  • 默认的无参构造(一个类没有实现构造函数时编译器自动提供的)在初始化列表中会调用类类型成员的无参构造函数

    • 如果需要显示调用类类型的有参构造函数,需要在初始化列表中 类类型成员名(实参)
  • 默认的拷贝构造函数会在初始化列表中调用类类型成员的拷贝构造函数

    • 如果实现拷贝构造函数,需要实现对成员的拷贝
  • 默认的拷贝赋值函数会在函数体中调用类类型成员的拷贝赋值函数

    • 如果实现拷贝赋值函数,需要实现对成员的拷贝
  • 默认的析构函数(和空实现析构函数),都会自动调用类类型成员的析构函数

  • 如果一个空类,至少有几个成员函数?

    • 无参构造
    • 拷贝构造
    • 拷贝赋值
    • 析构函数
    • & 取址函数 T* operator&(void)
    • & 常取址函数 const T* operator&(void)const
  • 如果在一个类中,只提供拷贝构造函数,则这个类只有一个构造函数

移动构造 C++11
  • 用一个存在的对象来构造一个同类型新的对象,但是旧的对象是马上就会消亡的,为了效率着想,就没有必要调用拷贝构造(造成资源浪费,复制一份,析构一份),这个时候就可以选择移动构造,把旧对象的资源移动(赠予)新的对象,需要保证旧的对象能够正常析构

  • 旧对象不能再使用了

    class 类名{
    public:
        类名(类名&& obj){
            
        }
    };
    
移动赋值 C++11
  • 用一个存在的对象给另外一个存在的对象的进行赋值,正常情况下,被赋值的资源应该析构,然后把另外一个对象的资源拷贝一份给被赋值的对象,但是如果=右边的对象马上就会消亡时,为了效率着想,就没有必要拷贝一份给被赋值的对象,直接把资源交给(转移给)被赋值的对象

  • =右边的对象不能再使用了

    class 类名{
    public:
        类名& operator=(类名&& obj){
            
        }
    };
    
单例模式
  • 模式 总结出来的开发套路 减少代码重复,提高代码可靠性、效率 且实现特定的功能

  • 23设计模式

  • 单例模式 ---- 一个类只能创建一个对象

    • 懒汉
      • 顾名思义:很懒,不到万不得已,绝对不会动,只有饿了才会去找吃的
      • 单例模式的对象只有有需求时才会创建
      • 优缺点
        • 只有有需求时才会创建,节省内存空间,用完了可以及时释放
        • 线程不安全 两个线程同时去创建可能会创建多份实例,所以需要考虑线程同步,线程同步效率变低
    • 饿汉
      • 顾名思义:很饿,需要时刻备吃的
      • 单例模式的对象一直存在,不管是否有需求,它都一直存在,在程序运行起来,形成内存映像时就会实例化对象
      • 优缺点
        • 不管是否有需求,一直占用内存,浪费内存空间
        • 饿汉模式是在程序加载阶段就去实例,能够保证只有一个线程,线程安全的
  • STL中所有容器都支持深拷贝的

1.实现一个队列类,提供必要的接口,构造、拷贝构造、拷贝赋值、移动构造、移动赋值、析构

2.实现一个简易的vector类,实现构造、拷贝构造、拷贝赋值、移动构造、移动赋值、析构

​ 提供push_back,pop_back等非迭代器的接口函数

运算符/操作符重载
  • = 赋值运算符 拷贝函数 operator=() 编译器自动提供

    重载运算符O

    返回值类型 operator O(形参列表);

  • 运算符的分类

    • 单目运算符 只有一个操作对象 +正号 -负 &取址 *取值 ~按位取反 !取非 ++ – sizeof typeid new delete
    • 双目运算符 操作对象有两个 + - * / % [] << >> > < <= == != = +=
    • 三目运算符 ?: 不能重载
运算符重载的方式
  • 成员的方法
    • 第一个操作数一定是当前类的对象
    • 第一个操作不作为参数的
    • #D D.operator#(void) 单目运算符以成员方式重载
    • N#M N.operator#(M) 双目运算符以成员方式重载
  • 全局函数的方式重载
    • #D operator#(D) 单目运算符以全局(友元方式)函数方式重载
    • N#M operator#(N,M) 双目运算符以全局(友元方式)函数方式重载
  • 如果一个运算符即可以使用成员方式重载,也可以使用全局函数方式重载,只需要选择其中一个方式重载即可,两都都重载会有歧义
运算符的返回值
  • 是否需要引用
  • C返回指针 不能返回普通局部变量的地址 函数调用之后普通局部变量的地址消失
  • C++返回引用 不能返回普通局部对象的引用 函数调用之后普通对象已经消亡
只能以成员方式重载的运算符
=
()    函数运算符
[]    下标运算符
->    
只能全局函数方式重载的运算符
<<   
>>
++/–
//++ --

class X{
    X& operator++(void){
        //
        return *this;
    }
    X operator++(int){
        X tmp(*this);
        //
        return tmp;
    }
    friend X& operator--(X& x);
    friend X operator--(X& x,int);
};

X& operator--(X& x){
    //--
    return x;
}
X operator--(X& x,int){
    X tmp(x);
    //--
    return tmp;
}
<< >>
  • 输入输出运算符只能以全局函数的方式进行重载
  • cout 是 ostream类型的对象
  • cin 是 istream 类型的对象
  • cout 和 cin 不支持拷贝构造 只能传递引用
class X{
    
	friend ostream& operator<<(osteram& os,const X& x);
    friend istream& operator>>(istream& is,X& x);
};

//cout << a << b << c << d << e << endl;  //   operator<<(operator<<(cout,a),b)    
ostream& operator<<(osteram& os,const X& x){
    //os << x.-;
    return os;
}

istream& operator>>(istream& is,X& x){
    //is >> x.-;
    return is;
}
[]下标运算符
  • 只能以成员方式重载

  • 一般都会实现两个

    class X{
      	T& operator[](size_t index){
            
        }  
        const T& operator[](size_t index)const{
            
        }
    };
    
&取址运算符
  • 一般都不会实现 每个对象,不管是否是常对象,都可以进行&运算
class X{
    
	X* operator&(void){
        return this;
    }
    const X* operator&(void)const{
        return this;
    }
};
*解引用
  • 返回对象的引用 右值
  • 迭代器重载*
-> 间接成员解引用
  • 返回对象的指针
  • 迭代器重载->
关系运算符重载
  • 返回值类型都是bool

    class X{
         bool operator<(const X& x)const{
             
         }  
    };
    
  • == != 一般成对出现

  • < <= > >=

逻辑运算符
  • 一般返回值类型都是bool
  • && || !
位运算符
  • & | ~ ^ >> <<
  • 一般不重载
new/delete
  • 以静态函数或者全局函数的方式重载
void *operator new(size_t size){
    void *ptr = malloc(size);
    return ptr;
}

void operator delete(void *ptr){
    free(ptr);
}

void *operator new[](size_t size){
    void *ptr = malloc(size);
    return ptr;
}

void operator delete[](void *ptr){
    free(ptr);
}
() 函数运算符 重载()的类的对象 称为函数对象
  • 函数对象 对象所对应的类型重载了()运算符
  • 仿函数 函数对象可以像函数一样调用函数
  • 在STL模板库中会大量使用仿函数
类型运算符
class X{
    //在X类中重载TYPE类型运算符
    operator TYPE(void){
        return TYPE类型对象
    }
};
  • 在X类中重载TYPE类型运算符,则X类型的对象都可以隐式调用重载的类型函数转换为TYPE类型的对象

  • explicit关键字可以防止X类型对象隐式调用operator TYPE函数,如果需要进行转换,则需要显示调用该函数

    TYPE t = TYPE(x);  //显示调用   operator TYPE 函数
    
类型转换
  • 有A类型对象a和B类型对象b 让a对象转换为b对象的方式有哪些?

    • 如果A和B都是基础数据类型,则a自动转换为b

    • 如果A是基础数据类型,B是类类型对象 在B类型中提供A类型的单参构造函数

      class B{
      public:
          B(const A& a){//A--->B
              
          }
      };
      
    • 如果A是类类型,B是基础数据类型 在A类型中重载B类型运算符

      class A{
      public:
          operator B(void){  //A--->B
              
          }
      };
      
    • 如果A和B都是类类型 则可以在A类中重载B类型运算符 或者 在B类型中提供A类型的构造函数

其它
  • ->*

运算符重载的限制
  • 至少有一个操作数对象为类类型对象

  • 不是所有运算符都能够重载

    ::            作用域限定符
    .             成员访问运算符
    .*            成员指针解引用运算符
    ?:
    sizeof
    typeid
    
  • 不是所有运算符都能够以全局函数的方式重载

    //只能以成员方式重载
    []
    ()
    ->
    =
    类型运算符
    
  • 不能发明新的运算符

面向对象-继承
  • 在定义一个类时,可以继承另外的类,所谓继承,事实上是指继承父类的属性和方法

  • 被继承的类,称为父类(基类) 继承自父类的类称为子类(派生类)

  • 继承之后,子类拥有父类的属性和方法 这就是继承的意义

  • C++允许多继承,一个类继承自多个父类,每个父类的继承方式可以不一样

    class Son:继承方式1 基类1,继承方式2 基类2,继承方式3 基类3,...{
        
        
    };
    
  • 继承方式

    • public 公开继承
    • protected 保护继承
    • private 私有继承
      • class缺省默认的继承方式private 缺省的访问控制属性 private
      • struct缺省默认继承方式是public 缺省的访问控制属性 public
    • 继承方式对于继承的影响
      • public公开继承
        • 把父类中public的属性继承到子类中依然在子类中属于public
        • 把父类中protected的属性继承到子类中依然是protected的属性
        • 把父类中private的属性继承到子类中依然是private的属性(子类中不可以访问)
      • protected保护继承
        • 把父类中public的属性继承到子类中为protected的属性
        • 把父类中protected的属性继承到子类中保留protected的属性
        • 把父类中private的属性继承到子类中依然是private的属性(子类中不可以访问)
      • private 私有继承
        • 把父类中public的属性继承到子类中为子类private属性
        • 把父类中protected的属性继承到子类中为private属性
        • 把父类中private的属性继承到子类中为private属性(子类不可以访问)
在子类中的访问属性父类中public父类中protected父类中private
子类中不能访问
public公开继承publicprotectedprivate
protected保护继承protectedprotectedprivate
private私有继承privateprivateprivate
public公开继承父类本类(继承父类)子类(继承本类)类外
父类中的public属性OKOKOKOK
父类中的protected属性OKOKOKNO
父类中的private属性OKNONONO

protected保护继承父类本类子类类外(通过本类的对象)
父类中public属性OKOKOKNO
父类中protected属性OKOKOKNO
父类中private属性OKNONONO
private私有继承父类本类(全部变成了private)子类类外(通过本类的对象)
父类中public属性OKOKNONO
父类中protected属性OKOKNONO
父类中private属性OKNONONO
  • 继承的意义

    • 复用父类的代码
    • 可以扩展父类的功能
    • 为多态提供基础
  • 继承关系本质上是 Is A 的关系

    • 子类拥有父类全部的属性和方法

      • 一个对象拥有一个类的全部的属性和方法 可以 认为该对象就是这个类实例的对象
      • 人类 class Human; 男人类 继承 人类 女人类 继承 人类
        • 男人 男人类的对象 人类的对象
    • 子类对象 Is A 父类类型对象

    • 子类就是父类集合中的一个子集

      class:继承列表{
          
          
      };
      
子类对象
  • 子类对象拥有父类全部的属性,但是如果父类中的属性为private的,子类虽然拥有,但是无法访问

  • 子类对象在存储时,依然遵循对齐补齐

  • 子类对象中存储着基类对象,称为基类子对象

  • 如果只有一个基类子对象时,那么所有的成员都是一起对齐补齐的

  • 如果一个子类对象中,拥有多个基类子对象,那么每个基类子对象都会独立进行对齐对齐

  • 在多继承中,子类对象中会按照继承顺序在子类对象中储备多个基类子对象

  • 子类对象 is A 父类类型对象

    • 子类类型的指针都可以隐式转换为父类类型的指针 可以用父类类型的指向指向子类对象 ****

    • 子类类型的引用都可以隐式转换为父类类型的引用 可以用父类类型的引用引用子类对象 ****

    • 父类 *p = &子类对象; 事实上p指向的是父类类型的基类子对象 可能和&子类对象的内存地址不一样,可能存储偏移 特别是多个基类子对象

    • 父类 &r = 子类对象; 事实上r引用的是父类在子类对象中的基类子对象

    • 父类 obj = 子类对象; obj实际上是通过基类子对象拷贝构造的 obj是新构造的对象

    • obj = 子类对象 用基类子对象给父类类型对象赋值

class A{
	char a;   
};

class B{
    int b;
};

class C{
  	char c;  
};

class D:public A,public B,public C{
    
};

D d;
A *pa = &d;
B *pb = &d;
C *pc = &d;

A& ra = d;
B& rb = d;
C& rc = d;
子类/派生类的构造函数、拷贝构造、拷贝赋值、析构函数
子类构造函数
  • 子类的构造函数(不管是自己写的还是编译器提供的),在初初始化列表中按照继承顺序,依次默认调用父类无参的构造函数用于构造基类子对象
  • 如果父类中没有无参构造函数,或者需要显示调用父类中的有参构造函数,则需要在子类构造函数的初始化列表中指明调用父类有参构造 :父类名(实参列表)
  • 构造函数的执行顺序(与初始化列表顺序无关):
    • (1)按照继承顺序,依次调用父类的构造函数构造基类子对象
    • (2)按照类类型成员的定义顺序,依次调用类类型成员的构造函数构造成员对象
    • (3)执行构造函数体
子类的析构函数
  • 子类的析构函数会默认调用父类的析构函数用于析构基类子对象
  • 子类的析构函数会默认调用类类型成员的析构函数用于析构成员对象
  • 析构函数的执行顺序 与 构造函数的执行顺序正好严格相反
子类的拷贝构造函数
  • 子类默认的拷贝构造函数,会按照继承顺序依次调用父类的拷贝构造函数拷贝基类子对象,然后按照成员的定义顺序依次调用类类型成员的拷贝构造函数拷贝成员

  • 如果自己实现子类的拷贝构造函数,需要注意拷贝基类子对象和成员对象

    • 需要在拷贝构造函数的初始化列表中显示调用父类的拷贝构造和成员的拷贝构造函数

      class S:public F1,public F2{
      public:
          S(){}
          S(const S& s):F1(s),F2(s),a(s.a),b(s.b),c.(s.c){
              
          }
      private:
          A a;
          B b;
          C c;
      };
      
子类的拷贝赋值函数
  • 子类默认的拷贝赋值函数,按照继承顺序依次调用父类的拷贝赋值函数,然后按照成员的定义顺序依次调用类类型成员的拷贝赋值函数

  • 如果自己实现子类的拷贝赋值函数,则需要手动调用父类的拷贝赋值函数和类类型成员的拷贝赋值函数,因为是手动实现的,所以先后顺序就不会有默认的一样了

    class S:public F1,public F2{
    public:
        S(){}
        S(const S& s):F1(s),F2(s),a(s.a),b(s.b),c.(s.c){
            
        }
        S& operator=(const S& s){
            if(this != &s){
                F1::operator=(s);
                F2::operator=(s);
                a = s.a;  //a.operator=(s.a)
                b = s.b;  //b.operator=(s.b)
                c = s.c;  //c.operator=(s.c)
            }
            return *this;
        }
    private:
        A a;
        B b;
        C c;
    };
    
子类的移动构造和移动赋值
  • 和拷贝构造和移动赋值处理方式一样
名字冲突 — 隐藏
  • 子类中同名的属性和函数会隐藏父类中同名的属性和函数(还有虚函数可以会覆盖,这种情况目前不考虑)
  • 子类中的函数和从父类中继承下来的函数不会构造重载,即使函数名相同,参数列表不同也不会构造重载
  • 隐藏 — 子类中隐藏父为中同名的属性和方法
  • 解隐藏
    • 子类对象.父类名::属性 子类对象.父类名::方法名
    • this->父类名::属性 this->父类名::方法名
用父类指针指向子类对象
  • 事实上指向的是子类对象中的基类子对象 和 真正的子类对象的地址会存在一定的偏移

  • 用父类指针指向子类对象时,只能访问父类的成员和调用父类的方法

  • 父类类型指针指向子类对象时,不能直接隐式转换为子类类型的指针

    • 静态类型转换 static_cast<> 能够计算出子类对象的起始地址
    • 强制类型转换 (SON*) 能够计算出子类对象的起始地址
    • 重解释类型转换 不能得到正确的子类对象的内存地址 reinterpret_cast<> 地址不会偏移
  • 父类指针 *p = new 子类对象();

    • delete p;
      • 父类指针是继承中的第一个父类,只会调用父类的析构函数,造成内存泄露
      • 父类指针是不是继承中的第一个父类,则会引发段错误
    • 在释放时,需要通过类型转换为子类类型
      • delete (子类*)p;
      • delete static_cast<子类*>§;
用父类类型的引用引用子类对象
  • 事实上所引用的是子类对象中的基类子对象
  • 用父类类型的引用引用子类对象时,只能访问父类类型中的属性和方法
  • 子类类型的引用可以隐式转换为父类类型的引用
  • 父类类型的引用引用子类对象时,可以通过static_cast转换为子类类型的引用 能够正确计算子类对象的位置
多态
虚函数 virtual
  • 一个类的成员函数可以声明为虚函数,即在该函数之前加上 virtual关键字
    • 全局函数、构造函数、静态函数、内联函数 不能声明为virtual
    • 析构函数可以声明为virtual,而且在多态中,往往会把析构函数声明为virtual函数
  • 一个函数如果声明为虚函数,则子类在继承这个类时,可以为该虚函数提供覆盖的版本
    • 重写虚函数
覆盖 重写
  • 子类重写/覆盖父类中同类型的虚函数
    • 同类型的虚函数
      • 父类中的函数一定为虚函数 不管子类中是否有virtual关键字,子类重写/覆盖的版本也为虚函数
      • 函数名一定相同
      • 参数列表相同
        • 常规类型 是否有const都能够重写覆盖
        • 对于引用和指针类型 常属性不同不能构成重写覆盖
      • 返回值类型
        • 基础数据类型 或者 类类型 必须相同 常属性必必须相同
        • 如果是类类型的指针或者引用 子类重写覆盖版本的返回值类型可以是父类返回值类型的子类类型的引用和指针
      • 函数的常属性必须一致
      • 子类重写覆盖版本不能声明更多异常说明
重载 隐藏 覆盖的区别
  • 重载: 要求在同一个作用域下,函数名相同,参数列表不同即构成重载

    • 需要在同一个类里面定义的成员函数 函数名相同 与virtual无关 与返回值类型无关 参数列表不同
  • 覆盖: 子类重写覆盖父类中同名的虚函数

    • 一定是子类覆盖父类的虚函数 如果父类中的函数不是虚函数,不能构成覆盖
    • 覆盖版本的函数函数名相同,参数列表相同,函数常属性必须相同,返回值类型相同(如果是类类型的指针和引用,子类覆盖版本返回值类型可以是父类虚函数返回值类型的子类类型的指针或者引用)
      • 如果覆盖版本,函数名相同,参数列表也相同,对返回值类型有要求,如果不满足要求,编译报错
  • 隐藏

    • 如果父类中同名的函数不是虚函数,这个时候,无论参数列表返回值类型是怎么,都构成隐藏
    • 如果父类中的函数是虚函数,同名函数除去覆盖的情况,都构成隐藏
多态的概念
  • 用父类类型的指针指向子类对象 或者 用父类类型的引用引用子类对象,调用是父类中的虚函数,实际调用到的是子类重写版本的虚函数,这种现象称为多态

  • 多态 = 虚函数 + 指针/引用

  • 多态 包含继承的思想

    • 子类对象 is a 父类类型对象
    • 父类中定义统一的接口 (虚函数) 子类提供覆盖的版本
  • 定义立体图形类

    • 提供虚函数 体积 和 表面积
    • 定义球体 圆柱 长方体 类
    • 随机生成10个立体图形 属性值也随机生成 求体积最大 表面积最大的图形

重载 overload

重写 override

虚析构
  • 析构函数可以定义为虚函数,在有多态关系中,往往会把析构函数定义为virtual
  • F *p = new S();
    • delete p;
      • 如果F中的析构函数不是虚函数,则delete只会调用父类的析构函数
      • 如果F中的析构函数是virtual,则delete时调用到是子类的析构函数,执行完子类析构函数体后自动调用父类的析构函数
  • 避免了内存泄露
  • 即使父类析构函数中为空,也经常把析构函数实现,并且声明为virtual
多继承
  • 一个类可以继承多个类,一个类可以有多个父类,一个类也允许有多个子类
  • 子类拥有所有父类的属性和方法
  • 子类对象可以用任意父类类型的指针和引用 指向或者引用
  • java只支持单继承
钻石继承 与 虚继承
  • 钻石继承,一个类继承多个父类,而父类中又有相同的祖先(直接或者间接继承同一个类)
  • 在钻石继承中,父类公共的祖先在子类对象中会存在两份属性(沿着不同的继承路径,在子类对象中生成基类子对象),调用不同父类中的方法,事实上操作的是不同的属性
虚继承
  • 在钻石继承中,可以指针父类的继承方式为virtual继承
  • 如果使用虚函数,则在最终的子类中只会保留一份公共基类的属性,通过不路路径继承下来的方法都是操作同一份属性
纯虚函数 抽象类 纯抽象类
  • 纯虚函数

    • 虚函数没有函数体,直接 = 0;
    • 成员函数 virtual虚函数 没有函数体 virtual double area() =0;
  • 抽象类

    • 拥有纯虚函数的类称为抽象类
    • 抽象类不能实例化对象 所谓抽象,就是不能具体,具体就是指实例化具体的对象
      • 抽象类允许有构造函数,但不允许实例化对象
      • 构造函数的意义在于构造基类子对象 初始化抽象类中的属性
    • 抽象类可以定义指针和引用
      • 抽象类的指针都是指向子类对象
      • 抽象类的引用都是引用子类对象
    • 如果一个类继承了抽象类,那么这个类应该重写覆盖抽象类中所有的纯虚函数,这样子这个子类才不是抽象类(具体类) 只要有一个纯虚函数没有被重写,那么该类就是抽象类
  • 纯抽象类

    • 如果一个类只有构造函数和析构函数不是纯虚函数,(除去构造函数和析构函数,其它所有函数都是纯虚函数),这样的类就称为抽象类
    • 构造函数不能声明为virtual函数
    • 析构函数可以声明为virtual,但不能声明为纯虚函数
  • 抽象类和纯抽象类的意义

    • 封装子类公共的特征和行为,提供一个公共的类型
    • 为子类提供统一的接口(纯虚函数)
    • 虽然不能实例化对象,但可以用抽象类的指针指向子类对象和用抽象类的引用引用子类对象,调用纯虚函数,实际上调用到提子类重写覆盖的版本,这就是多态
C++ 11 =default =delete
=default
  • 显示说明让编译器提供默认的函数
=delete
  • 删除某些函数 禁止使用
  • 比如某些类禁止拷贝构造、拷贝复制 C++11之前如果需要禁止使用拷贝构造和拷贝复制需要私有化
  • 析构函数不能=delete 不能删除析构函数
虚函数
  • 如果一个类中有虚函数,这个类则会在原有属性的基础之上增加一个指针属性(32bit 4 amd64 8)(一般在对象开始位置 存储指针之后存储对象的属性)
  • 如果一个类有虚函数,则该类的每一个对象都会有一个额外的指针,这个指针的值是一样的
  • 每个对象中的这个指针,称为虚函数指针,即指向这个类的虚函数表
虚函数表 和 虚表指针
虚函数表
  • 如果一个类中有虚函数,那么在编译时会为该类生成一个虚函数表,虚函数表即 虚函数指针数组 : 即数组中每一个元素都是虚函数的内存地址
  • 单继承中:如果一个类继承了另外一个类,如果父类中有虚函数表,那么子类将会继承(复制)父类的虚函数表,如果子类中重写父类中的虚函数,那么子类将会用子类重写虚函数的地址替换虚函数表中对应的值,如果子类不重写,则保留父类虚函数的地址,子类如果有新的虚函数会增加
  • 多继承中,如果两个父类中都有虚函数表,则会继承两个父类的虚函数表,子类自己的虚函数增加到第一个虚函数表中,子类重写的虚函数,会替换对应虚函数表中的值
虚表指针
  • 一个拥有虚函数类的对象,都拥有一个指针,这个指针都指向该类的虚函数表的位置,这个指针称为虚表指针
  • 一个拥有多个虚函数表的对象,会拥有多个指针,每一个指针都是一个虚表指针,指向对应虚函数表
动态绑定 动态运行时
  • 静态绑定 重载 静态多态

    • 编译阶段,对调用的函数进行绑定,根据参数的个数和类型来绑定调用重载的函数
  • 动态绑定 多态 动态多态

    • 用基类的指针指向子类对象或者用基类的引用引用子类对象,调用基类中的虚函数时,编译器不会直接生成调用指令,而是用一段特殊的指令来替换,这段特殊的指令完成的工作是:
      • 通过指针或者引用 找到目标对象的虚表指针
      • 通过虚表指针找到虚函数表 (找到目标对象真实类型的虚函数表)
      • 通过虚函数表,找到对应的虚函数的地址,调用该虚函数
    • 所谓动态绑定,就是在编译阶段无法确定调用哪个函数,只有当运行阶段,才确定调用的函数
  • 过多的动态绑定(多态)会使得程序的运行效率变低

动态运行时信息 (RTTI Run-Time Type Identification)
  • Object Oriented Programming OOP

  • #include

    • typeid 操作符,获取一个变量的运行时信息

    • 如果没有多态的情况下,typeid获得的就是变量本身的类型

      //没有多态的情况下
      A *p = xx;
      typeid(*p)    ==   typeid(A)
          
      A& ra = xx;
      typeid(ra)    ==   typeid(A)
      
    • 如果存在多态,则typeid会追踪指针所指向的目标 和 追加引用所引用的目标 的真实类型

      F *p = new S();
      typeid(*p)  ==   typeid(S)
      S s;
      F& rf = s;
      typeid(rf)  ==   typeid(S)
      
  • typeid返回动态运行时信息对象

    • 支持 name() 获取类型信息
    • 支持 == 和 != 比较
动态类型转换
  • dynamic_cast<目标类型>(源对象)

  • 只能适用于有多态关系的父子类型之间的指针和引用转换

  • 动态类型转换会校源对象和目标类型是否一致,只有类型一致才能转换成功,否则转换失败,返回NULL

  • 而static_cast 和 reinterpret_cast 和 强制类型转换都不会检验源对象是否能够转换成目标对象,在程序中要避免这类转换

  • 项目另外一部分完成 登录校验

  • 部门管理 员工管理

  • 模型

  • 分层

    • 水平 用户界面 逻辑实现 数据访问
    • 垂直 接口层(纯抽象类 接口) 实现层 对象层
UML

UML (Unified Modeling Language)为面向对象软件设计提供统一的、标准的、可视化的建模语言

流程图

C++类图

异常机制
C语言的错误处理
  • 通过返回值来表示 需要返回特殊的值

    • fopen malloc 返回NULL表示出错 非NULL就是正常的值

    • fread fwrite 返回0表示出错 非0表示正确读写次数

    • 缺陷

      • 层层判断函数返回结果,且正常代码逻辑和错误处理逻辑代码混在一起的,如果有多种错误,还需要为每一个错误设定一个特殊返回值
      • 有些函数正常的返回值就可以取到任意结果,这种情况就无法用特殊值来表示错误
    • 当调用C语言标准库中的函数或者系统调用失败之后,设置全局的errno,获取错误信息的方式有

      #include <errno.h>
      extern int errno;     //全局的errno
      
      #include <string.h>
      //通过errno(错误码)获取对应的错误信息
      char *strerror(int errno);
      
      const char *perror(const char *str);
      
      //printf   %m    格式占位符   自动用strerror(errno)来获取对应的错误信息
      
      • 只有当调用失败时才会设置errno,如果调用成功,不会把errno的值设置为0
      • errno的作用,只能通过判断函数的返回值确定函数调用失败之后,用errno来获取错误信息,不能直接通过errno!=0来表示函数调用成功
  • setjmp/longjmp

    #include <setjmp.h>
    
    int setjmp(jmp_buf env);
    void longjmp(jmp_buf env,int val);
    
    jmp_buf是一个类型,每使用一次,需要定义一个jmp_buf类型的全局变量
    setjmp第一次调用都是返回0
        当调用longjmp函数时,代码会直接跳转到setjmp函数处,并使用setjmp函数返回val(要非0)值
    
    //信号集
    int sigsetjmp(jmp_buf env,int savesigs);
    void siglongjmp(jmp_buf env,int val);
    
    • 缺陷
      • 在C++中,局部对象无法得到析构(在调用longjmp函数之前的局部对象) 内存泄露
C++的错误处理方式
  • 可以使用C语言的错误处理方式,但都会有缺陷

  • C++建议使用异常机制

    • C++在throw抛出异常之前,会对局部对象进行析构,保证内存资源不会泄露
  • 异常机制

  • 当程序出错之后,可以throw抛出异常,抛出异常之前,会对所有的局部对象进行析构,同时从抛出异常的代码中直接往外跳出(抛出异常之后不会继续往下执行)去匹配最近层次的try-catch语句,根据异常的类型来匹配catch分配中的异常,如果异常类型的对象 Is a catch中的异常类型的对象,则进入到catch分支中进行异常处理,异常处理完成之后,继续从最后一个catch分支往下正常执行,如果catch了异常,但无法处理,则可以继续往外抛出,一个异常要么被捕获,要么不一直往外抛出,直到系统捕获,执行默认处理(显示异常信息,终止程序执行)

  • 语法

    try{
        //可能发生异常的语句
        
    }catch(异常类型& e1){//如果发生异常之后,首先匹配e1,如果不满足则匹配e2...
        
    }catch(异常类型& e2){
        
    }catch(异常类型& e3){
        
    }catch(...){   //如果上面没有匹配的异常类型,它一定会捕获异常,进入该分支     相当于else分支
        
    }
    
    
    在一个完整的try catch分支中,如果出现异常则会从上至下对catch进行匹配,如果有匹配的,则进入该分支,后面的则不会再匹配,所以如果异常类型有继承的话,子类异常应该在父类异常catch之前进行catch;可以在最后加上catch(...)确保所有异常都能被捕获
    
标准异常
  • 所有的标准异常都直接或者间接继承至 std::exception 类
  • logic_error
    • out_of_range
    • invalid_argument
  • runtime_error
    • range_error
    • overflow_error
    • underflow_error
  • bad_typeid
  • bad_cast
  • bad_alloc
C++中一般用异常类型来表示不同的错误
  • C++中也允许用一种类型不同的值来表示不同的错误
  • C++中也允许使用任意类型来表示异常
C++中可以自己抛出异常
throw  异常类型(实参列表);
--throw是一个关键字,可以抛出异常
C++异常处理流程
  • 正常逻辑代码和异常处理分离的
  • 局部对象能够正确析构
  • 每一层只捕获自己能够处理的异常,不能处理的异常让其继续往外抛出
  • 如果要确保捕获所有异常,可以用catch(…)
  • 如果有异常没有被捕获,会把异常交给操作系统作默认处理(显示异常信息,终止程序执行)
  • 程序员可以自己定义异常类型,一般都会遵循直接或者间接继承exception类的原则,并且重写what()函数
  • 程序员可以使用throw关键字抛出异常,某些特殊情况下,一个异常如果被catch了,但又无法处理,则可以继续用throw抛出
函数的异常说明
函数返回值类型 函数名(形参列表)throw (异常类型,...) {  //如果是成员函数,还可以有const
    
}
  • 函数的异常说明表示的是该函数中抛出的异常可以被捕获的异常有哪些类型,除了异常说明以外所有抛出的异常类型都无法被catch
  • 往往用于说明一个函数可能抛出的异常种类,在try-catch时,只需要catch异常说明中的异常类型即可
  • 一个函数如果没有异常说明,则表示该函数可以抛出任意的异常,且所有的异常都可以被捕获
  • 一个函数的异常说明如果为 throw() 则表示该函数抛出的所有异常都无法被catch
  • C++11增加了 noexcept 说明符,表示该函数不抛出任意异常,如果一旦抛出异常,则该异常无法被catch
  • 在重写覆盖中,子为重写版本函数的异常说明,不能比父类版本有"更多"的异常说明
    • 子类重写版本不能放松对于throw的限定
关于构造函数和析构函数中的异常
  • 如果一个函数在构造过程中产生了异常,则该对象称为构造不完全对象/不完整对象,不完全对象是无法进行析构的(不会调用析构函数),所以,如果在构造函数中产生了异常,需要在构造函数中自行捕获处理(如果在异常产生之前,构造函数中有new动态内存,则需要手动释放),并且抛出异常
  • 永远不要在析构函数中抛出异常
C++IO库
  • #include

  • C++程序不直接处理输入输出,提供了标准库

    • istream 输入流类型,提供输入操作

    • ostream 输出流类型,提供输出操作

    • cin 是一个istream对象,从标准输入读取数据 scanf sscanf fscanf

    • cout 是一个ostream对象,向标准输出写入数据 printf sprintf fprintf

    • cerr 是一个ostream对象,通常用于输出程序的错误信息,写入标准错误

      >>运算符,用来从一个istream对象读取输入数据
      <<运算符,用来从一个ostream对象写入输出数据
      
  • iostream 定义了用于读写民的基本类型 scanf/printf

    • istream wistream
    • ostream wostream
    • iostream wiostream
  • fstream 定义了读写文件的类型 fscanf/fprintf

    • ifstream wifstream
    • ofstream wofstream
    • fstream wfstream
  • sstream 定义了读写内存string对象的类型 sscanf/sprintf

    • istringstream wistringstream
    • ostringstream wostringstream
    • stringstream wstringstream
  • w标准库定义一组类型和对象来操作wchar_t类型的数据,宽字符版本类型和函数的名字以一个w开始

                                ios_base
                                  |
                                 ios
                           /              \
                      istream                ostream
                  /       |       \      /          |       \
           ifstream istringstream  iostream  ostrimgstream  ofstream
                                   fstream
                                   stringstream
               
io对象无拷贝和无赋值
  • 所有的IO对象,都不可以进行拷贝构造和拷贝赋值

  • 进行IO损人和函数通常以引用方式传递和返回流

  • 读写一个IO对象会改变其状态,因此IO对象流也不能是const

    ostream& operator<<(ostream& os,const Type& obj);
    istream& operator>>(istream& is,Type& obj);
    
条件状态
标识 函数条件状态
iostateiostate是一种机器相关的类型,提供了表达条件状态的完整功能 int
badbit用来指出流已崩溃 某一个二进制为1
failbit用来指出一个IO操作失败了 某一个二进制为1
eofbit用来指出流到达了文件结束 某一个二进制为1
goodbit用来指出流未处于错误状态,此值保证为0
ios.eof()若ios的eofbit置位,则返回true
ios.fail()若ios的failbit或badbit置位,则返回true
ios.bad()若ios的badbit置位,则返回true
ios.good()若ios处于有效状态,则返回true
ios.clear()将流ios中所有条件状态复位,将流的状态设置为有效,返回void
ios.clear(flags)清除,flag就是iostate类型的变量
ios.setstate(flags)设置
ios.rdstate()获得流ios的当前条件状态
管理输出缓冲
1.输出\n
2.缓冲区满了
3.强制刷新缓冲区   flush(stdout)
4.printf-->scanf
5.程序正常结束
1.输出endl(换行)ends(空白字符,书上说会刷新缓冲区)
    	endl 换行   向输出缓冲区输出一个换行,然后刷新缓冲区
        ends 空字符  
2.缓冲区满了
3.flush                cout << "hello" << flush
4.程序正常结束   
5.unitbuf操作作符
    	如果想每次输出之后都刷新缓冲区,可以使用  cout >> unitbuf;
		可以使用  cout >> nounitbuf;  恢复正常情况
6.关联输入和输出流   cin.tie(&cout)         cout--->cin
    交互式系统通常应该关联输入流和输出流,意味着所有的输出,包括用户提示信息,都会在读操作之前被打印出来。
    每个流同时最多关联到一个流,但多个流可以同时送给到同一个ostream   

IO操纵符
定义于头文件 <ios>
boolalpha noboolalpha在布尔值的文本和数值表示间切换 (函数)
showbase noshowbase控制是否使用前缀指示数值基数 (函数)
showpoint noshowpoint控制浮点表示是否始终包含小数点 (函数)
showpos noshowpos控制是否将 + 号与非负数一同使用 (函数)
skipws noskipws控制是否跳过输入上的前导空白符 (函数)
uppercase nouppercase控制一些输出操作是否使用大写字母 (函数)
unitbuf nounitbuf控制是否每次操作后冲洗输出 (函数)
internal left right设置填充字符的布置 (函数)
dec hex oct更改用于整数 I/O 的基数 (函数)
fixed scientific hexfloat defaultfloat (C++11)(C++11)更改用于浮点 I/O 的格式化 (函数)
定义于头文件 <istream>
ws消耗空白符 (函数模板)
定义于头文件 <ostream>
ends输出 ‘\0’ (函数模板)
flush冲洗输出流 (函数模板)
endl输出 ‘\n’ 并冲洗输出流 (函数模板)
定义于头文件 <iomanip>
resetiosflags清除指定的 ios_base 标志 (函数)
setiosflags设置指定的 ios_base 标志 (函数)
setbase更改用于整数 I/O 的基数 (函数)
setfill更改填充字符 (函数模板)
setprecision更改浮点精度 (函数)
setw更改下个输入/输出域的宽度 (函数)
格式化IO
<<    >>      格式化的IO
    读取时默认到空白字符结束   会自动跳过空白字符 ws
字符IO
get()
put()
    
peek()   查看 不读走
unget()
putback()    
字符串IO
getline()
basic_istream& getline( char_type* s, std::streamsize count, char_type delim );
二进制IO
read()
write()
随机IO
tellg()
seekg()
    
tellp()
seekp()
    
 ios::beg
 ios::cur
 ios::end
文件流对象
ifstream in(file);      //构造一个ifstream对象并打开给定的文件   
ofstream out;           //输出文件流对象并未关联到任何文件
         out.open(file)
fstream  ios(file,mode)
      mode
      in      以读方式打开                     ios::in
      out     以写方式打开
      app     每次写操作前均定位到文件末尾
      ate     打开文件后立即定位到文件末尾
      trunc   截断文件
      binary  以二进制方式进行IO
  • 只可以对ofstream和fstream对象设定out模式
  • 只可以对ifstream和fstream对象设定in模式
  • 只有当out也被设定时才可以设trunc模式
  • 只要trunc没有被设定,就可以设定app模式,在app模式下,即使没有显式指定out模式,文件总是以输出方式被打开
  • 即使没有指定trunc模式,以out模式打开的文件也会被截断
  • 如果需要保留文件中的内容进行写,必须使用out和app模式
sstream
  • istringstream

    string str("1024 952.7 hello");
    istringstream is(str);
    int n;double d;string s;
    is >> n >> d >> s;
    
  • ostringstream

    ostringstream os;
    os << n << " " << d << " " << s;
    cout << os.str() << endl;
    

模板与泛型编程

  • 所谓模板就是泛型编程,为了编写更加通用的程序代码(代码的复用),把类型抽象化,实现算法与类型分离,编写更加抽象的类和函数
函数模板
template<typename T,...>
返回值类型  函数模板名(形参列表){
    
}
//template模板关键字<模板类型参数>    
//typename关键字  可以用   class  替换
//模板类型参数可以有很多,它不是具体的类型,每一个模板参数都需要用逗号隔开
函数模板实例化—>模板函数
  • 在使用函数模板时,需要用具体的类型进行实例化

  • 函数模板名<类型参数列表>(实参列表);

  • 自动类型推导

    • 某些函数(模板类型用于函数模板的参数列表)模板支持自动类型推导
    • 根据使用时传递的实参,根据实参的类型进行推导,有些情况下,根据类型进行推导可能产生二义性,这个时候就需要提供类型参数
函数模板特化
  • 针对某些特殊的类型,通过的函数模板可能不适用,就需要针对这种特殊的类型写一个特化的函数

    template<>
    返回值类型 函数模板名(实参列表){
        
    }
    
函数模板支持非类型参数
template<unsigned N,unsigned M>
int compare(const char (&p1)[N],const char (&p2)[M]){
    return strcmp(p1,p2);
}

int ret = compare("Hello","hi");
  • 注意,在对非类型模板参数实例化,只能用常量或者常量表达式
模板编译
  • 当编译器遇到一个模板定义时,它并不生成代码,只有当实例化出模板的一个特定版本时,编译器才会生成代码。所以模板会进行二次编译,第一次编译是检查模板本身的语法,第二次编译是模板实例化之后,生成特定类型版本的函数和类进行编译。

  • 多文件编程时,函数和类的声明放在头文件中.h,函数和类的定义放在实现文件中.cpp

  • 模板可以把模板的声明和定义放在不同的文件中吗? 显然是不可以的

    • 如果只包含头文件(模板的声明),在实例化时将无法得到模板的定义,所以模板编程中,会把模板的声明和定义直接放在头文件中
编译错误报错
  • 第一阶段,编译模板代码,针对模板本身进行语法检查,需要模板代码满足模板语法规则
  • 第二阶段,在使用模板实例时,检查实例化时模板参数的个数是否相等,检查参数类型是否匹配
  • 第三阶段,模板实例化,根据模板用具体类型实例化,生成具体的模板函数或者模板类,再次编译模板函数和模板类,进行语法检查,只有在这个阶段,才能发现类型相关的问题(不支持的运算符,没有链接到函数)
普通函数和模板函数一样时
  • 如果普通函数和模板函数同名且参数列表一致时,在调用函数时,肯定会选择匹配度更高的版本,会优先选择普通函数调用,如果普通函数和模板函数都一样,优先选择普通函数,如果想调用函数模板,则需要显示类型实例化;函数模板中如果针对类型有特化的版本,则调用特化的版本

    //通用的函数模板
    template<typename T>
    void func(T a){
        
    }
    //特化的函数模板
    template<>
    void func(int a){
        
    }
    //普通函数
    void func(int a){
        
    }
    
    func(10);//普通函数
    func<int>(10); //特化版本
    func('a');//通用的函数模板
    
尾置返回值类型
tempate<typename IT>
auto maxarr(IT beg,IT end)->decltype(*beg){
    
}
类模板
  • 通用的类模板
template<typename T,...>
class 类模板名{
    
    
};
  • 类模板在实例化时无法进行自动类型推导
针对特殊的类型进行全员特化
  • 类所有的属性和方法都需要重新写过

    template<>
    class 类模板名<特化的类型>{
        //实现类
        
    };
    
针对特殊的类型进行成员特殊
  • 只针对特殊的几个函数进行特化

    template<> 返回值类型 类模板名<特化的类型>::成员方法名(形参列表){
        
    }
    
针对特殊的类型进行偏特化(局部特化)
  • 针对指针或者数组类型的偏特化 或者 针对类型本身特殊情况的特化

    template<typename T>
    class A<T[]>{//针对数组仿特化
        
    };
    
    template<typename T>
    class A<T*>{//针对指针的偏特化
        
    };
    
    template<typename T1,typename T2>
    class B{
        
    };
    
    template<typename T1>
    class B<T1,T1>{//针对两个类型相同情况下的偏特化
        
    };
    
    • 在实例化,会选择特化程序更高的匹配版本进行实例化
类模板允许有非类型参数
  • 非类型参数在实例化时必须是常量或者常量表达式
类模板允许有模板缺省值
  • 缺省模板参数需要满足靠右原则
  • 函数模板在C++11以后也允许有模板缺省参数
普通类中含有模板成员方法
可变长类型模板参数
void func(){
    
}

template<typename t,typename ...Args>
void func(const T& t,const Args& ...args){
    //cout << t << " " << endl;
    func(args...);//func()
}

template<typename T>
class X{
    template<typename ... Args>
    void emplace(iterator pos,Args&& ...args){
        
    }
};

STL

  • Standard template library C++标准模板库
六大组件
  • 容器(containers)

    • 容器就是用来存储数据对象,数据结构
    • 线性容器 顺序容器
      • array 静态数组 C++11
      • forward_list 单向链表 C++
      • vector 向量 动态顺序表 支持扩容 支持 []
      • list 双向链表
      • deque 双端队列 可以在两端进行快速插入和删除的顺序表 支持扩容 支持[]
    • 容器适配器(底层是线性容器,只保留某些特殊的接口)
      • stack 栈 FILO
      • queue 队列 FIFO
      • priority_queue 优先队列 "优"者先出
    • 关联容器
      • 有序关联容器(红黑树,多重:意味着红黑树中允许有重复的元素)
        • set 集合
        • multiset 多重集合
        • map 映射
        • multimap 多重映射
      • 无序关联容器(哈希表)
        • unordered_set 集合
        • unordered_mulitset
        • unordered_map
        • unordered_multimap
      • 民间大神实现的无序关联容器(不是STL)
        • hashmap/hashset/hashmultimap/hashmultiset
  • 迭代器(iterators)

    • 容器和算法之间的粘合剂,用来连接容器和算法
    • STL容器(除了容器适配器)都有自己专属的迭代器
    • 原生的指针也是一种迭代器
    • 输入迭代器 支持++ == != *
    • 输出迭代器 支持++ == != *
    • 正向迭代器 支持++ == != * ->
    • 逆向迭代器
    • 双向迭代器 支持++ – == != * ->
    • 随机迭代器 支持 +n -n ++ –
    • 常迭代器
  • 算法(algorithms)

    • sort
    • search
    • copy
    • find
  • 仿函数(functors)

    • 仿函数其实就一个类看起来像函数,其实就是重载了operator()运算符
    • 函数对象
  • 配接器(adapters)

    • 修饰容器,仿函数或者迭代器接口的东西
    • 配接器修改类的接口,使原来不相互匹配的两个类可以相互匹配,进行合作
    • bind
  • 配置器(allocators)

    • 配置器主要负责空间的配置和管理
六大组件之间的关系
  • 容器通过配置器获取数据存储空间
  • 算法通过迭代器获取容器的内容
  • 仿函数协助算法完成策略变化
  • 配接器修饰或者套接仿函数(bind)
智能指针
auto_ptr (c++11之前 C++17删除)
unique_ptr
shared_ptr
weak_ptr
容器的共性
  • 1.容器存储的数据都是副本(拷贝构造一份)存储到容器的数据需要支持完整语义的上拷贝
  • 2.容器本身都支持拷贝构造和赋值 (都是深拷贝)
  • 3.容器都支持swap
array 静态的连续数组
  • 封装的固定大小的数组容器

  • 固定大小数组 不能进行增加元素,也不能删除元素

    #include <array>
    template< class T,std::size_t N> 
    struct array;
    
    //T 即数组中存储的数据类型      N即数组长度
    
  • 构造

    array<int,10> arr;
    array<int,5> brr = {1,2,3,4,5};
    array<int,5> crr{1,2,3,4,5};
    array<int,5> drr;
    drr.fill(1024);
    array<int,5> err(drr);
    arr = brr;
    
  • 元素访问

    //获取指定下标位置的元素   返回的元素的引用   如果越界会产生异常
    reference at(size_type pos);          T& at(size_t pos);
    const_reference at( size_type pos ) const;
    
    //越界访问是未定义
    reference operator[]( size_type pos );
    const_reference operator[]( size_type pos ) const;
    
    //所有容器支持at和[]都是一样的    array  vector和deque     时间复杂度O(1)
    //关联容器都支持[]         O(logn)      O(1)--O(n)
    
    //首元素
    reference front();
    const_reference front() const;
    
    //最后一个元素
    reference back();
    const_reference back() const;
    
    T* data() noexcept;
    const T* data() const noexcept;
    
    
  • 迭代器

    //迭代器支持随机迭代和双向迭代  +n   -n
    begin cbegin  返回指向起始的迭代器
    end cend      返回指向末尾的迭代器
    rbegin crbegin  返回指向起始的逆向迭代器
    rend crend      返回指向末尾的逆向迭代器
    
    
  • 容量

    constexpr bool empty() const noexcept;
    constexpr size_type size() const noexcept;
    constexpr size_type max_size() const noexcept;
    
  • 操作

    void fill( const T& value );
    void swap( array& other ) noexcept;
    
vector 动态连续数组
  • 向量

  • 可扩容的顺序表

  • 内存连续

  • 在末尾插入和删除的效率O(1) 如果在末尾插入引发扩容,时间最复杂度O(n)

    • 提供了push_back/pop_back
  • erase/insert平均时间复杂度O(n) 越靠近front效率越低

  • 尽量避免频繁地进行扩容(分配新的内存,然后把原内存中的数据拷贝过来(拷贝构造)),频繁扩容造成效率低下

  • 通过push_back和insert插入到vector中对象,都是经过拷贝构造的对象

  • 通过emplace(涉及到移动元素)和emplace_back(涉及到扩容 ) 原位构造,只会调用构造函数,效率会比push_back和insert要好

template< class T,class Allocator = std::allocator<T> > 
class vector;
  • 元素访问

    at
    operator[]
    front()
    back()
    data()
    
  • 迭代器

    //迭代器支持随机迭代和双向迭代  +n   -n
    begin cbegin  返回指向起始的迭代器
    end cend      返回指向末尾的迭代器
    rbegin crbegin  返回指向起始的逆向迭代器
    rend crend      返回指向末尾的逆向迭代器
    
  • 容量

    empty()
    size()
    max_size()
        
        
    reserve    预留存储空间    只扩不缩    提高vector插入效率的有效手段  减少自动扩容
    capacity   返回当前存储空间能够容纳的元素数     
    
    
    shrink_to_fit   通过释放未使用的内存减少内存的使用    让容量等于元素的个数
    
    
    
  • 修改

    clear()    调用类的析构析构vector中所有对象    元素为0   容量不变
    insert()   在指定的迭代器前插入 副本   拷贝构造    移动元素
    emplace()  在指定的迭代器前插入 原位构造   移动元素
    erase()    删除指定迭代器  迭代器范围
    emplace_back()
    push_back()
    pop_back()    
    resize()   改变元素个数  可能引发扩容,调用拷贝构造和构造函数    可能使元素减少(容量不变),调用析构
    swap()
    
forward_list
  • 单向链表
#include <forward_list>
template<class T,class Allocator = std::allocator<T>> class forward_list;
  • 在任何位置插入和删除的时间复杂度都为O(1)

  • forward_list的插入和删除都是在指定迭代器后进行的

  • 元素访问 不支持随机访问

    reference front();
    const_reference front() const;
    
  • 迭代器 只支持正向迭代 只能++ 不能–

    //第一个元素之前的迭代器     保证能在第一个位置进行插入和删除
    iterator before_begin() noexcept;
    const_iterator before_begin() const noexcept;
    const_iterator cbefore_begin() const noexcept;
    
    //首元素
    iterator begin() noexcept;
    const_iterator begin() const noexcept; 
    const_iterator cbegin() const noexcept; 
    
    //最后一个结点的next
    iterator end() noexcept;
    const_iterator end() const noexcept;
    const_iterator cend() const noexcept;
    
    
  • 容量

    bool empty() const noexcept;
    size_type max_size() const noexcept;
    
  • 修改器

    void clear() noexcept;
    
    //在指定迭代器pos后面插入   
    iterator insert_after( const_iterator pos, const T& value );//
    iterator insert_after( const_iterator pos, T&& value );
    iterator insert_after( const_iterator pos, size_type count, const T& value );
    template< class InputIt >
    iterator insert_after( const_iterator pos, InputIt first, InputIt last );
    iterator insert_after( const_iterator pos, std::initializer_list<T> ilist ); 
    
    
    initializer_list   初始化列表   {v1,v2,...}
    
    template< class... Args >
    iterator emplace_after( const_iterator pos, Args&&... args );
    
    
    //删除之后 返回下一个元素的迭代器
    iterator erase_after( const_iterator pos );
    iterator erase_after( const_iterator first, const_iterator last ); 
    
    //因为在单向链表头插入和删除的时间复杂度为O(1)     在末尾插入和删除的效率为O(n)
    void push_front( const T& value );
    void push_front( T&& value );
    
    template< class... Args >
    void emplace_front( Args&&... args );
    template< class... Args >
    reference emplace_front( Args&&... args );
    void pop_front(); 
    
    void resize( size_type count );
    void resize( size_type count, const value_type& value ); 
    void swap( forward_list& other );
    
  • 操作

    //如果存储是类类型成员  调用的是  operator<
    void merge( forward_list& other );
    void merge( forward_list&& other );
    
    //Compare比较器
    template <class Compare>
    void merge( forward_list& other, Compare comp );
    template <class Compare>
    void merge( forward_list&& other, Compare comp ); 
    
    
    //把另外一个forward_list中的结点剪切到当前单向链表的Pos后面
    void splice_after( const_iterator pos, forward_list& other );
    void splice_after( const_iterator pos, forward_list&& other );
    void splice_after( const_iterator pos, forward_list& other, const_iterator it );
    void splice_after( const_iterator pos, forward_list&& other, const_iterator it );
    void splice_after( const_iterator pos, forward_list& other, const_iterator first, const_iterator last );
    void splice_after( const_iterator pos, forward_list&& other, const_iterator first, const_iterator last ); 
    
    
    
    //移除指定的元素value
    void remove( const T& value );
    size_type remove( const T& value );
    
    //根据条件删除
    template< class UnaryPredicate >
    void remove_if( UnaryPredicate p );
    template< class UnaryPredicate >
    size_type remove_if( UnaryPredicate p );
    
    
    UnaryPredicate  一元谓词    函数对象     类中重载operator()(const T& a)
        二元谓词   类中重载operator()(const T& a,const T& b)
        
    //单链表逆序
    void reverse() noexcept;
    
    
    //删除连续的"重复"元素
    void unique();
    size_type unique();
    template< class BinaryPredicate >
    void unique( BinaryPredicate p );
    template< class BinaryPredicate >
    size_type unique( BinaryPredicate p );
    	BinaryPredicate  二元谓词    相邻两个元素满足条件进就删除后一个元素
            
    
    //排序
    void sort();     默认是用<比较
    template< class Compare >
    void sort( Compare comp );   //比较两个元素时用 comp
    
    
list
  • 双向链表

    #include <list>
    template<class T,class Allocator = std::allocator<T>> class list;
    
  • list 是支持常数时间从容器任何位置插入和移除元素的容器。不支持快速随机访问。它通常实现为双向链表。与 forward_list相比,此容器提供双向迭代但在空间上效率稍低。

  • 在 list 内或在数个 list 间添加、移除和移动元素不会非法化迭代器或引用。迭代器仅在对应元素被删除时非法化。

  • 元素访问

    reference front();
    const_reference front() const; 
    
    reference back();
    const_reference back() const; 
    
  • 迭代器 双向

    begin cbegin
    end cend
    rbegin crbegin
    rend crend
    
  • 容量

    bool empty() const;
    bool empty() const noexcept; 
    size_type size() const;
    size_type size() const noexcept;
    
    
  • 修改器

    void clear();
    void clear() noexcept;
    
    //在指定位置pos前插入 ...
    iterator insert( iterator pos, const T& value );
    iterator insert( const_iterator pos, const T& value );
    iterator insert( const_iterator pos, T&& value );
    void insert( iterator pos, size_type count, const T& value );
    iterator insert( const_iterator pos, size_type count, const T& value );=
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    template< class InputIt >
    iterator insert( const_iterator pos, InputIt first, InputIt last );
    iterator insert( const_iterator pos, std::initializer_list<T> ilist ); 
    
    //在指定位置前原位构造对象插入
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    //根据迭代器删除
    iterator erase( iterator pos );
    iterator erase( const_iterator pos );  
    iterator erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last ); 
    
    
    void pop_back();
    void push_back( const T& value );
    void push_back( T&& value ); 
    template< class... Args >
    void emplace_back( Args&&... args );
    template< class... Args >
    reference emplace_back( Args&&... args );
    
    void pop_front();
    void push_front( const T& value );
    void push_front( T&& value ); 
    template< class... Args >
    template< class... Args >
    reference emplace_front( Args&&... args ); 
    
    void resize( size_type count ); 
    void resize( size_type count, T value = T() );
    void resize( size_type count, const value_type& value ); 
    
  • 操作

    void merge( list& other );
    void merge( list&& other );
    template <class Compare>
    void merge( list& other, Compare comp );
    template <class Compare>
    void merge( list&& other, Compare comp ); 
    
    
    
    void splice( const_iterator pos, list& other );
    void splice( const_iterator pos, list&& other );
    void splice( const_iterator pos, list& other, const_iterator it );
    void splice( const_iterator pos, list&& other, const_iterator it );
    void splice( const_iterator pos, list& other,
                  const_iterator first, const_iterator last);
    void splice( const_iterator pos, list&& other,
                  const_iterator first, const_iterator last ); 
    
    
    void remove( const T& value );
    size_type remove( const T& value );
    template< class UnaryPredicate >
    void remove_if( UnaryPredicate p );
    template< class UnaryPredicate >
    size_type remove_if( UnaryPredicate p );
    
    void reverse();
    void reverse() noexcept;
    
    void unique();
    size_type unique();
    template< class BinaryPredicate >
    void unique( BinaryPredicate p );
    template< class BinaryPredicate >
    size_type unique( BinaryPredicate p );
    
    void sort();
    template< class Compare >
    void sort( Compare comp );
    
    
deque
  • 双端队列
  • 顺序表
  • 允许在双端进行快速插入和删除,同时支持随机访问
#include <deque>
template<class T,class Allocator = std::allocator<T>> class deque;
  • deque ( double-ended queue ,双端队列)是有下标顺序容器,它允许在其首尾两段快速插入及删除。

  • 另外,在 deque 任一端插入或删除不会非法化指向其余元素的指针或引用。

  • 与 vector相反, deque 的元素不是相接存储的:典型实现用单独分配的固定大小数组的序列,外加额外的登记,这表示下标访问必须进行二次指针解引用,与之相比 vector 的下标访问只进行一次。

  • deque 的存储按需自动扩展及收缩。扩张 deque 比扩张 vector 更优,因为它不涉及到复制既存元素到新内存位置。另一方面, deque 典型地拥有较大的最小内存开销;只保有一个元素的 deque 必须分配其整个内部数组(例如 64 位 libstdc++ 上为对象大小 8 倍; 64 位 libc++ 上为对象大小 16 倍或 4096 字节的较大者)。

  • deque 上常见操作的复杂度(效率)如下:

    • 随机访问——常数 O(1)
    • 在结尾或起始插入或移除元素——常数 O(1)
    • 插入或移除元素——线性 O(n)
  • 元素访问

    reference       at( size_type pos );
    const_reference at( size_type pos ) const; 
    
    reference       operator[]( size_type pos );
    const_reference operator[]( size_type pos ) const; 
    
    reference front();
    const_reference front() const; 
    
    reference back();
    const_reference back() const; 
    
    
  • 迭代器 随机 双向

    begin cbegin
    end cend
    rbegin crbegin
    rend crend
    
  • 容量

    bool empty() const;
    bool empty() const noexcept; 
    size_type size() const;
    size_type size() const noexcept;
    void shrink_to_fit();
    
  • 修改器

    void clear();
    void clear() noexcept; 
    
    iterator insert( iterator pos, const T& value );
    iterator insert( const_iterator pos, const T& value );
    iterator insert( const_iterator pos, T&& value );
    void insert( iterator pos, size_type count, const T& value );
    iterator insert( const_iterator pos, size_type count, const T& value );
    template< class InputIt >
    void insert( iterator pos, InputIt first, InputIt last);
    template< class InputIt >
    iterator insert( const_iterator pos, InputIt first, InputIt last );
    iterator insert( const_iterator pos, std::initializer_list<T> ilist ); 
    
    template< class... Args >
    iterator emplace( const_iterator pos, Args&&... args );
    
    
    iterator erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last ); 
    
    void push_back( const T& value );
    void push_back( T&& value ); 
    
    
    template< class... Args >
    void emplace_back( Args&&... args );
    template< class... Args >
    reference emplace_back( Args&&... args ); 
    void pop_back();
    
    void push_front( const T& value );
    void push_front( T&& value ); 
    
    
    template< class... Args >
    void emplace_front( Args&&... args );
    template< class... Args >
    reference emplace_front( Args&&... args ); 
    void pop_front();
    
    void resize( size_type count );
    void resize( size_type count, T value = T() );
    void resize( size_type count, const value_type& value ); 
    
    //move
    //forward
    //ref
    
    
线性容器总结
  • vector没有push_front和pop_front,方法
    • 拥有push_back/pop_back/ front/back
  • list和deque都拥有push_front/push_back/pop_front/pop_back/ front/back
容器适配器
  • 容器适配器提供顺序容器的不同接口
stack
  • 适配一个容器以提供栈(LIFO 数据结构)

  • 先进后出 只在一端进行插入和删除的操作 push_back/pop_back/back

    #include <stack>
    template<class T,class Container = std::deque<T>> class stack;
    
  • 底层默认实现为deque

  • 只要容器能够提供: back()``push_back()``pop_back() 就可以作为stack底层容器

bool empty();
size_t size();
void push();
void emplace();
T& top();
void pop();
queue
  • 适配一个容器以提供队列(FIFO 数据结构)
  • queue 在底层容器尾端推入元素,从首端弹出元素。 底层容器需要支持 push_back() / pop_front()
  • vector没有pop_front,所以vector不能作为queue底层容器
T& front();
T& back();
bool empty();
size_t size();
void push();    //push_back
void emplace(); //emplace_back
void pop();     //pop_front
priority_queue
  • 适配一个容器以提供优先级队列

    #include <queue>
    template<class T,class Container = std::vector<T>,class Compare = std::less<typename Container::value_type> > class priority_queue;
    
  • 底层默认容器用的vector list不能作为priority_queue的底层容器,因为迭代器不支持随机迭代(±n)

T& top();
bool empty();
size_t size();
void push();        //直接插入O(n)  在堆中插入  O(logn)   push_heap
void emplace();    
void pop();         //O(1)         删除堆顶元素  O(logn) pop_heap

template<typename IT,typename T>
IT bfind(IT beg,IT end,const T& value){
    IT mid = beg + (end-beg)/2;  //beg和end需要支持随机迭代才可能 
    //mid = (beg+end)/2;//错误的
}
堆操作
#include <algorithm>

//检测是否是"大"堆
template< class RandomIt >
bool is_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr bool is_heap( RandomIt first, RandomIt last );

template< class ExecutionPolicy, class RandomIt >
bool is_heap( ExecutionPolicy&& policy, RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
bool is_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr bool is_heap( RandomIt first, RandomIt last, Compare comp );

template< class ExecutionPolicy, class RandomIt, class Compare >
bool is_heap( ExecutionPolicy&& policy, RandomIt first, RandomIt last, Compare comp ); 





template< class RandomIt >
RandomIt is_heap_until( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr RandomIt is_heap_until( RandomIt first, RandomIt last );

template< class ExecutionPolicy, class RandomIt >
RandomIt is_heap_until( ExecutionPolicy&& policy, RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr RandomIt is_heap_until( RandomIt first, RandomIt last, Compare comp );

template< class ExecutionPolicy, class RandomIt, class Compare >
 RandomIt is_heap_until( ExecutionPolicy&& policy, RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void make_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void make_heap( RandomIt first, RandomIt last ); 

template< class RandomIt, class Compare >
void make_heap( RandomIt first, RandomIt last,Compare comp ); 

template< class RandomIt, class Compare >
constexpr void make_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void push_heap( RandomIt first, RandomIt last );
template< class RandomIt >
constexpr void push_heap( RandomIt first, RandomIt last );
template< class RandomIt, class Compare >
void push_heap( RandomIt first, RandomIt last, Compare comp );  
template< class RandomIt, class Compare >
constexpr void push_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void pop_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void pop_heap( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void pop_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr void pop_heap( RandomIt first, RandomIt last, Compare comp ); 



template< class RandomIt >
void sort_heap( RandomIt first, RandomIt last );

template< class RandomIt >
constexpr void sort_heap( RandomIt first, RandomIt last );

template< class RandomIt, class Compare >
void sort_heap( RandomIt first, RandomIt last, Compare comp );

template< class RandomIt, class Compare >
constexpr void sort_heap( RandomIt first, RandomIt last, Compare comp ); 

关联容器
有序关联容器 红黑树
  • 常规操作时间复杂度 O(logn)
set 集合
  • 存储key元素 key唯一,不能重复

    template<class Key,class Compare = std::less<Key>,class Allocator =std::allocator<Key>> class set;
    
  • std::set 是关联容器,含有 Key 类型对象的已排序集。用比较函数 比较(compare),进行排序。搜索、移除和插入拥有对数复杂度。 set 通常以红黑树实现。

  • !comp(a, b) && !comp(b, a) 为true,则认为它们相等

  • 修改器

    void clear();
    void clear() noexcept; 
    
    //返回的是一对值(pair)  pair中第一个值是插入元素的迭代器,第二是bool,表示插入是否成功
    std::pair<iterator,bool> insert( const value_type& value );
    std::pair<iterator,bool> insert( value_type&& value );
    
    iterator insert( iterator hint, const value_type& value );
    iterator insert( const_iterator hint, const value_type& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    
    //根据迭代器删除
    void erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    void erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last );
    
    //根据key的值来删除  删除等于key的元素  比较也是用<
    size_type erase( const key_type& key ); 
    
    
    
    void swap( set& other );
    void swap( set& other ) noexcept(); 
    
    
    node_type extract( const_iterator position );
    
    template<class C2>
    void merge( std::set<Key, C2, Allocator>& source );
    template<class C2>
    void merge( std::set<Key, C2, Allocator>&& source );
    template<class C2>
    void merge( std::multiset<Key, C2, Allocator>& source );
    template<class C2>
    void merge( std::multiset<Key, C2, Allocator>&& source ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
    size_type count( const K& x ) const; 
    
    
    iterator find( const Key& key );
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    //返回不"小"于key的第一个元素的迭代器
    iterator lower_bound( const Key& key );
    const_iterator lower_bound( const Key& key ) const;
    template< class K >
    iterator lower_bound(const K& x);
    template< class K >
    const_iterator lower_bound(const K& x) const; 
    
    
    //返回首个"大于"key的首元素的迭代器
    iterator upper_bound( const Key& key );
    const_iterator upper_bound( const Key& key ) const;
    template< class K >
    iterator upper_bound( const K& x );
    template< class K >
    const_iterator upper_bound( const K& x ) const; 
    
    
  • 比较器

    key_compare key_comp() const;
    std::set::value_compare value_comp() const;
    

基于set存储的学生管理系统

增加

按学号删除

列出

查找

pair
#include <utility>
template<class T1,class T2> 
struct pair{
  	T1 first;
    T2 second;
};

//调用构造函数构建
pair<string,int> p(string("hello"),1);


template< class T1, class T2 >
std::pair<T1,T2> make_pair( T1 t, T2 u );
template< class T1, class T2 >
std::pair<V1,V2> make_pair( T1&& t, T2&& u );
template< class T1, class T2 >
constexpr std::pair<V1,V2> make_pair( T1&& t, T2&& u ); 

//模板函数构建对象
make_pair(string("hello"),1);

multiset 多重集合
  • 存储key元素 key不唯一 允许重复

  • 方法和set一模一样 头文件都是#include

map 映射
  • 存储key-value(键-值)对 以key构建管理红黑树 key唯一,不能重复

    #include <map>
    template< class Key,class T,class Compare = std::less<Key>,
         class Allocator = std::allocator<std::pair<const Key, T> >
    > class map;
    
  • 元素访问

    //获取key所对应的value  如果Key不存在,则抛出out_of_range异常     左值    m.at(key) = value
    T& at( const Key& key );
    const T& at( const Key& key ) const;
    
    //获取key所对应的value   如果key不存在,则插入key,value就无参构造(基础数据类型为0)     如果key存在则获取   可以作为左值  m[key]=value   修改key所对应的value
    T& operator[]( const Key& key );
    T& operator[]( Key&& key ); 
    
    
  • 迭代器

    • 映射的迭代器 解引用 取到的是一个pair对象 包括key和value
    begin cbegin
    end cend
    rbegin crbegin
    rend crend
        
       it->first    key       it->value    value
       (*it).first            (*it).second
    
  • 容量

    bool empty()const;
    size_t size()const;
    
  • 修改器

    void clear();
    void clear() noexcept;
    
    
    std::pair<iterator,bool> insert( const value_type& value );
    template< class P >
    std::pair<iterator,bool> insert( P&& value );
    std::pair<iterator,bool> insert( value_type&& value );
    iterator insert( iterator hint, const value_type& value );
    iterator insert( const_iterator hint, const value_type& value );
    template< class P >
    iterator insert( const_iterator hint, P&& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    
    template <class M>
    std::pair<iterator, bool> insert_or_assign(const key_type& k, M&& obj);
    template <class M>
    std::pair<iterator, bool> insert_or_assign(key_type&& k, M&& obj);
    template <class M>
    iterator insert_or_assign(const_iterator hint, const key_type& k, M&& obj);
    template <class M>
    iterator insert_or_assign(const_iterator hint, key_type&& k, M&& obj); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    
    
    template <class... Args>
     pair<iterator, bool> try_emplace(const key_type& k, Args&&... args);
    template <class... Args>
     pair<iterator, bool> try_emplace(key_type&& k, Args&&... args);
    template <class... Args>
     iterator try_emplace(const_iterator hint, const key_type& k, Args&&... args);
    template <class... Args>
     iterator try_emplace(const_iterator hint, key_type&& k, Args&&... args); 
    
    
    
    void erase( iterator pos );
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    void erase( iterator first, iterator last );
    iterator erase( const_iterator first, const_iterator last );
    size_type erase( const key_type& key ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
     size_type count( const K& x ) const; 
    
    iterator find( const Key& key );
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    
    iterator lower_bound( const Key& key );
    const_iterator lower_bound( const Key& key ) const;
    template< class K >
     iterator lower_bound(const K& x);
    template< class K >
     const_iterator lower_bound(const K& x) const; 
    
    
    iterator upper_bound( const Key& key );
    const_iterator upper_bound( const Key& key ) const;
    template< class K >
     iterator upper_bound( const K& x );
    template< class K >
     const_iterator upper_bound( const K& x ) const; 
    
    
multimap 多重映射
  • 存储key-value(键-值)对 以key构建管理红黑树 key不唯一,允许重复

  • 成员方法和map是一样的,头文件也一样

  • 唯一缺少at和operator[] !!!!!

  • key如果是基础数据类型,除非特别指定Compare,否则不需要提供什么

  • key如果是自定义类型,则需要自定义类型能够进行<比较

    • 重载operator<

    • 提供额外Compare

      class X{
      public:
          bool operator()(const 自定义类型& x1,const 自定义类型& x2)const{
              
          }
      };
      
  • value如果是自定义类型,使用[]时必须要支持无参构造

  • 关联容器都不能通过迭代器修改key的值

无序关联容器 哈希表
  • 常规操作时间复杂度 O(1) — O(n)
unordered_set
  • 迭代器 iterator / local_iterator 都是常迭代器 不能通过迭代器修改容器中的元素

    //iterator  能够遍历所有元素
    iterator begin() noexcept;
    const_iterator begin() const noexcept;
    const_iterator cbegin() const noexcept; 
    
    iterator end() noexcept;
    const_iterator end() const noexcept;
    const_iterator cend() const noexcept; 
    
    
  • 容量

    bool empty() const noexcept;
    size_type size() const noexcept;    //常量
    size_type max_size() const noexcept;
    
  • 修改器

    void clear() noexcept;
    
    std::pair<iterator,bool> insert( const value_type& value );
    std::pair<iterator,bool> insert( value_type&& value );
    iterator insert( const_iterator hint, const value_type& value );
    iterator insert( const_iterator hint, value_type&& value );
    template< class InputIt >
    void insert( InputIt first, InputIt last );
    void insert( std::initializer_list<value_type> ilist );
    insert_return_type insert(node_type&& nh);
    iterator insert(const_iterator hint, node_type&& nh); 
    
    template< class... Args >
    std::pair<iterator,bool> emplace( Args&&... args );
    template <class... Args>
    iterator emplace_hint( const_iterator hint, Args&&... args );
    
    iterator erase( const_iterator pos );
    iterator erase( iterator pos );
    iterator erase( const_iterator first, const_iterator last );
    size_type erase( const key_type& key ); 
    
    void swap( unordered_set& other );
    
    node_type extract( const_iterator position );
    node_type extract( const key_type& x ); 
    
    
    
    template<class H2, class P2>
    void merge( std::unordered_set<Key, H2, P2, Allocator>& source );
    template<class H2, class P2>
    void merge( std::unordered_set<Key, H2, P2, Allocator>&& source );
    template<class H2, class P2>
    void merge( std::unordered_multiset<Key, H2, P2, Allocator>& source );
    template<class H2, class P2>
    void merge( std::unordered_multiset<Key, H2, P2, Allocator>&& source ); 
    
    
  • 查找

    size_type count( const Key& key ) const;
    template< class K >
    size_type count( const K& x ) const; 
    
    
    iterator find( const Key& key );   //需要先对key进行   hash_func(Hash()(key)) 散列  映射到桶,然后再和桶中的元素进行  equal_to/operator== 比较  
    const_iterator find( const Key& key ) const;
    template< class K > iterator find( const K& x );
    template< class K > const_iterator find( const K& x ) const; 
    
    bool contains( const Key& key ) const;
    template< class K > bool contains( const K& x ) const; 
    
    
    std::pair<iterator,iterator> equal_range( const Key& key );
    std::pair<const_iterator,const_iterator> equal_range( const Key& key ) const;
    template< class K >
    std::pair<iterator,iterator> equal_range( const K& x );
    template< class K >
    std::pair<const_iterator,const_iterator> equal_range( const K& x ) const; 
    
    
  • 桶接口

    //桶的迭代器  n为桶的下标
    local_iterator begin( size_type n );
    const_local_iterator begin( size_type n ) const;
    const_local_iterator cbegin( size_type n ) const; 
    
    local_iterator end( size_type n );
    const_local_iterator end( size_type n ) const;
    const_local_iterator cend( size_type n ) const; 
    
    //返回桶的数量
    size_type bucket_count() const;
    size_type max_bucket_count() const;//桶最大的数量  取决于系统和库设置
    //返回指定桶n中元素的个数
    size_type bucket_size( size_type n ) const;  
    //返回key所在桶的对应的下标
    size_type bucket( const Key& key ) const;
    
    
    
  • 哈希策略

    float load_factor() const; //加载因子  = 元素数量/桶数量
    
    float max_load_factor() const; //返回哈希表设定的加载因子
    void max_load_factor( float ml ); //设置哈希表的最大加载因子
    
    //在添加元素时,如果加载因子 > 设定最大加载因子    需要重新散列   需要重新分配内存  然后拷贝现有对象
    
    void rehash( size_type count );    //设置桶数为 count 并重哈希容器,即考虑桶总数已改变,再把元素放到适当的桶中。若新的桶数使加载因子大于最大加载因子( count < size() / max_load_factor() ),则新桶数至少为 size() / max_load_factor() 。
    
    void reserve( size_type count );    //设置桶数为适应至少 count 个元素,而不超出最大加载因子所需的数,并重哈希容器,即考虑桶数已更改后将元素放进适合的桶。等效地调用 rehash(std::ceil(count / max_load_factor()))
    
    //加载因子过大,意味着冲突加剧,加载因子过小,意味着内存浪费
    
    
    
  • 自定义类型存储到无序关联容器(作为Key)

    • 提供Hash类型,重载operator() 有一个自定义类型参数 ,返回一个特定的int类型

      class Hash{
      public:
          int operator()(const 自定义类型& obj)const{
              return xx;
          }
      };
      
    • 涉及到比较,需要重载 == 运算符, 或者 提供 比较的类

      class 自定义类型{
      public:
          bool operator==(const 自定义类型& obj)const{
              
          }
      };
      
      class Equal{
      public:
          bool operator()(const 自定义类型& o1,const 自定义类型& o2)const{
              
          }
      };
      

    通讯录管理系统 用unordered_set存储

  • 在提供自自定类型的hash和equal时

    • find,需要经过hash映射,然后在和桶中的元素进行equal比较
    • hash字段,也需要在equal比较中相等
  • hash

    #include <functional>
       
    template< class Key >
    struct hash; 
    
    template<> struct hash<bool>;
    template<> struct hash<char>;
    template<> struct hash<signed char>;
    template<> struct hash<unsigned char>;
    template<> struct hash<char8_t>;        // C++20
    template<> struct hash<char16_t>;
    template<> struct hash<char32_t>;
    template<> struct hash<wchar_t>;
    template<> struct hash<short>;
    template<> struct hash<unsigned short>;
    template<> struct hash<int>;
    template<> struct hash<unsigned int>;
    template<> struct hash<long>;
    template<> struct hash<long long>;
    template<> struct hash<unsigned long>;
    template<> struct hash<unsigned long long>;
    template<> struct hash<float>;
    template<> struct hash<double>;
    template<> struct hash<long double>;
    template<> struct hash<std::nullptr_t>;
    template< class T > struct hash<T*>;
    
    class User{
    public:
        User(string name="",string tel=""):name(name),tel(tel){
            
        }
        bool operator==(const User& user)const{//用于hash字段一定要相等
            return name == tel.name;
        }
        string getName()const{
            return user;
        }
    private:
        string name;
        string tel;
    };
    
    class HashUser{
    public:
        size_t operator()(const User& user)const{
            return hash<string>()(user.getName());
        }
    };
    
    
unordered_mulitset
  • unordered_multiset 是关联容器,含有可能非唯一 Key 类型对象的集合。搜索、插入和移除拥有平均常数时间复杂度。

  • 元素在内部并不以任何顺序排序,只是被组织到桶中。元素被放入哪个桶完全依赖其值的哈希。这允许快速访问单独的元素,因为一旦计算哈希,它就指代放置该元素的准确的桶。

  • 方法和unordered_set是一样的

unordered_map
template<
    class Key,
    class T,
    class Hash = std::hash<Key>,
    class KeyEqual = std::equal_to<Key>,
    class Allocator = std::allocator< std::pair<const Key, T> >
> class unordered_map;
unordered_multimap
string
  • 可以把string看作是存储字符类型数据的顺序容器
initializer_list 初始化列表
size_type size() const noexcept;

const T* begin() const noexcept;
constexpr const T* begin() const noexcept; 

const T* end() const noexcept;
constexpr const T* end() const noexcept; 


auto l = {1,2,3,4,4,5}

tuple 一组值 pair扩展
tuple( const Types&... args );
template< class... UTypes >
tuple( UTypes&&... args ); 



template< class... Types >
tuple<VTypes...> make_tuple( Types&&... args );
template< class... Types >
constexpr tuple<VTypes...> make_tuple( Types&&... args ); 



template< class... Types >
 tuple<Types&...> tie( Types&... args ) noexcept;
template< class... Types >
constexpr tuple<Types&...> tie( Types&... args ) noexcept; 

//创建一个tuple变量,变量中的成员都是  引用
int a,b,c;
tie(a,b,c)
    
    

template< class... Tuples >
std::tuple<CTypes...> tuple_cat(Tuples&&... args);
template< class... Tuples >
constexpr std::tuple<CTypes...> tuple_cat(Tuples&&... args); 


std::get(std::tuple)  //提取tuple中的对象     
get<0>(t)       根据位置
get<1>(t)
    
get<int>(t)     根据类型
    

tuple_size          在编译时获得 tuple 的大小 
tuple_element       获得指定元素的类型 
ignore              用 tie 解包 tuple 时用来跳过元素的占位符 
any C++17

弱类型编程

any a = 1;

a = 3.14;

a = true;

a = “hello”;

a = Stu(110,“jack”,100);

a = Book(100011,“三国演义”,“罗贯中”);

算法
#include <algorithm>
  • 不修改序列容器

    bool all_of( InputIt first, InputIt last, UnaryPredicate p );
    
    bool any_of( InputIt first, InputIt last, UnaryPredicate p );
    
    bool none_of( InputIt first, InputIt last, UnaryPredicate p );
    
    UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
    
    InputIt for_each_n( InputIt first, Size n, UnaryFunction f );
    
    
    typename iterator_traits<InputIt>::difference_type count( InputIt first, InputIt last, const T &value );
    
    typename iterator_traits<InputIt>::difference_type count_if( InputIt first, InputIt last, UnaryPredicate p );
    
    std::pair<InputIt1,InputIt2>mismatch( InputIt1 first1, InputIt1 last1,InputIt2 first2 );
    constexpr std::pair<InputIt1,InputIt2> mismatch( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2 );
    
    
    InputIt find( InputIt first, InputIt last, const T& value );
    InputIt find_if( InputIt first, InputIt last, UnaryPredicate p );
    InputIt find_if_not( InputIt first, InputIt last,UnaryPredicate q );
    
    ForwardIt1 find_end( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last );  //在范围 [first, last) 中搜索序列 [s_first, s_last) 的最后一次出现。
    
    ForwardIt1 find_first_of( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last );//在范围 [first, last) 中搜索范围 [s_first, s_last) 中的任何元素。
    
    ForwardIt adjacent_find( ForwardIt first, ForwardIt last );
    ForwardIt adjacent_find( ForwardIt first, ForwardIt last, BinaryPredicate p );
    
    
     
    ForwardIt1 search( ForwardIt1 first, ForwardIt1 last,ForwardIt2 s_first, ForwardIt2 s_last );  //搜索范围 [first, last - (s_last - s_first)) 中元素子序列 [s_first, s_last) 的首次出现。
    ForwardIt1 search( ForwardIt1 first, ForwardIt1 last, ForwardIt2 s_first, ForwardIt2 s_last, BinaryPredicate p );
    
    ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T& value );
    ForwardIt search_n( ForwardIt first, ForwardIt last, Size count, const T& value, BinaryPredicate p );
    在范围 [first, last) 中搜索 count 个等同元素的序列,每个都等于给定的值 value 
         
    
  • 修改序列的操作

    OutputIt copy( InputIt first, InputIt last, OutputIt d_first );
    OutputIt copy_if( InputIt first, InputIt last,OutputIt d_first, UnaryPredicate pred );
    
    OutputIt copy_n( InputIt first, Size count, OutputIt result );
    BidirIt2 copy_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
    
    OutputIt move( InputIt first, InputIt last, OutputIt d_first );
    
    template<class InputIt, class OutputIt>
    OutputIt move(InputIt first, InputIt last, OutputIt d_first)
    {
        while (first != last) {
            *d_first++ = std::move(*first++);//不是复制数据  而是移动赋值
        }
        return d_first;
    }
    
    BidirIt2 move_backward( BidirIt1 first, BidirIt1 last, BidirIt2 d_last );
    
    void fill( ForwardIt first, ForwardIt last, const T& value );
    void fill_n( OutputIt first, Size count, const T& value );
    
    OutputIt transform( InputIt first1, InputIt last1, OutputIt d_first,UnaryOperation unary_op );
    
    
    void generate( ForwardIt first, ForwardIt last, Generator g );
    void generate_n( OutputIt first, Size count, Generator g );
    
    template<class ForwardIt, class Generator>
    void generate(ForwardIt first, ForwardIt last, Generator g)
    {
        while (first != last) {
            *first++ = g();
        }
    }
    
    //并返回范围新结尾的尾后迭代器
    ForwardIt remove( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt remove_if( ForwardIt first, ForwardIt last, UnaryPredicate p );
    1) 移除所有等于 value 的元素,用 operator== 比较它们。
    2) 移除所有 p 对于它返回 true 的元素。
        
    //拷贝除了value以外的[first,last)区间所有元素
    OutputIt remove_copy( InputIt first, InputIt last, OutputIt d_first,const T& value );
    //拷贝不满足p条件以外的[first,last)区间所有元素
    OutputIt remove_copy_if( InputIt first, InputIt last, OutputIt d_first,UnaryPredicate p );
    
    
    void replace( ForwardIt first, ForwardIt last,const T& old_value, const T& new_value );
    void replace_if( ForwardIt first, ForwardIt last,UnaryPredicate p, const T& new_value );
    
    OutputIt replace_copy( InputIt first, InputIt last, OutputIt d_first,const T& old_value, const T& new_value );
    OutputIt replace_copy_if( InputIt first, InputIt last, OutputIt d_first, UnaryPredicate p, const T& new_value );
    
    
    template< class T >
    void swap( T& a, T& b );
    
    ForwardIt2 swap_ranges( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2 );
    
    template< class ForwardIt1, class ForwardIt2 >
    void iter_swap( ForwardIt1 a, ForwardIt2 b );  //交换两个迭代器所指向的元素
    
    //逆序
    void reverse( BidirIt first, BidirIt last );
    
    OutputIt reverse_copy( BidirIt first, BidirIt last, OutputIt d_first );
    
    void rotate( ForwardIt first, ForwardIt n_first, ForwardIt last );
    OutputIt rotate_copy( ForwardIt first, ForwardIt n_first,ForwardIt last, OutputIt d_first );
    
    void random_shuffle( RandomIt first, RandomIt last );
    void random_shuffle( RandomIt first, RandomIt last, RandomFunc& r );
    
    //移除范围内的连续重复元素   list不适用
    ForwardIt unique( ForwardIt first, ForwardIt last );
    ForwardIt unique( ForwardIt first, ForwardIt last, BinaryPredicate p );
    
    OutputIt unique_copy( InputIt first, InputIt last,OutputIt d_first );
    OutputIt unique_copy( InputIt first, InputIt last,OutputIt d_first, BinaryPredicate p );
    
  • 划分操作

    //范围 [first, last) 中的所有满足 p 的元素都出现在所有不满足的元素前则返回 true 。若 [first, last) 为空亦返回 true
    bool is_partitioned( InputIt first, InputIt last, UnaryPredicate p );
    
    //重排序范围 [first, last) 中的元素,使得谓词 p 对其返回 true 的元素前于谓词 p 对其返回 false 的元素。不保持相对顺序    指向第二组元素首元素的迭代器(第一个不满足p条件的元素的迭代器)
    BidirIt partition( BidirIt first, BidirIt last, UnaryPredicate p );
    std::pair<OutputIt1, OutputIt2>
          partition_copy( InputIt first, InputIt last,OutputIt1 d_first_true, OutputIt2 d_first_false,UnaryPredicate p );
    
    //划分  保证相同元素的相对位置不变
    BidirIt stable_partition( BidirIt first, BidirIt last, UnaryPredicate p );
    
    ForwardIt partition_point( ForwardIt first, ForwardIt last, UnaryPredicate p );
    
  • 排序操作

    bool is_sorted( ForwardIt first, ForwardIt last );
    bool is_sorted( ForwardIt first, ForwardIt last, Compare comp );
    
    
    ForwardIt is_sorted_until( ForwardIt first, ForwardIt last );
    ForwardIt is_sorted_until( ForwardIt first, ForwardIt last,Compare comp );
    void sort( RandomIt first, RandomIt last );
    void sort( RandomIt first, RandomIt last, Compare comp );
    void partial_sort( RandomIt first, RandomIt middle, RandomIt last );
    void partial_sort( RandomIt first, RandomIt middle, RandomIt last, Compare comp );
    RandomIt partial_sort_copy( InputIt first, InputIt last,RandomIt d_first, RandomIt d_last );
    RandomIt partial_sort_copy( InputIt first, InputIt last,RandomIt d_first, RandomIt d_last, Compare comp );
    
    //稳定排序
    void stable_sort( RandomIt first, RandomIt last );
    void stable_sort( RandomIt first, RandomIt last, Compare comp );
    
    //以*nth元素为分割  
    void nth_element( RandomIt first, RandomIt nth, RandomIt last );
    void nth_element( RandomIt first, RandomIt nth, RandomIt last, Compare comp );
    
  • 二分搜索(有序)

    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt lower_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value );
    ForwardIt upper_bound( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    bool binary_search( ForwardIt first, ForwardIt last, const T& value );
    bool binary_search( ForwardIt first, ForwardIt last, const T& value, Compare comp );
    
    ForwardIt mid = next(first,distance(first,last)/2);
    
    
    std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last,const T& value );
    std::pair<ForwardIt,ForwardIt> equal_range( ForwardIt first, ForwardIt last,const T& value, Compare comp );
    
  • 有序操作

    OutputIt merge( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    
    OutputIt merge( InputIt1 first1, InputIt1 last1,  InputIt2 first2, InputIt2 last2,OutputIt d_first, Compare comp );
    
    
    void inplace_merge( BidirIt first, BidirIt middle, BidirIt last );
    void inplace_merge( BidirIt first, BidirIt middle, BidirIt last, Compare comp );
    
  • 集合操作(在已排序范围上)

    bool includes( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2 );
    bool includes( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, Compare comp );
    
    OutputIt set_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_difference( InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2,OutputIt d_first, Compare comp );
    
    OutputIt set_intersection( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_intersection( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first,Compare comp );
    
    OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first );
    OutputIt set_symmetric_difference( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2,OutputIt d_first,Compare comp );
    
    OutputIt set_union( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, OutputIt d_first );
    OutputIt set_union( InputIt1 first1, InputIt1 last1,InputIt2 first2, InputIt2 last2, OutputIt d_first, Compare comp );
    
  • 排列操作

    //相等元素交换位置也是同一种排列
    bool next_permutation( BidirIt first, BidirIt last );
    bool next_permutation( BidirIt first, BidirIt last, Compare comp);
    bool prev_permutation( BidirIt first, BidirIt last);
    bool prev_permutation( BidirIt first, BidirIt last, Compare comp);
    
    bool is_permutation( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2 );
    bool is_permutation( ForwardIt1 first1, ForwardIt1 last1,ForwardIt2 first2, BinaryPredicate p );
    
  • 数值运算

    //以始于 value 并重复地求值 ++value 的顺序递增值填充范围 [first, last) 。
    void iota( ForwardIt first, ForwardIt last, T value );
    
    T accumulate( InputIt first, InputIt last, T init );
    T accumulate( InputIt first, InputIt last, T init,BinaryOperation op );
    //op 等价  Ret fun(const Type1 &a, const Type2 &b);   a当前累积   b当前项
    
    T inner_product( InputIt1 first1, InputIt1 last1,InputIt2 first2, T init );
    OutputIt adjacent_difference( InputIt first, InputIt last,OutputIt d_first );
    
    OutputIt partial_sum( InputIt first, InputIt last, OutputIt d_first );
    
  • 内存操作

    #include <memory>
    //复制来自范围 [first, last) 的元素到始于 d_first 的未初始化内存
    ForwardIt uninitialized_copy( InputIt first, InputIt last, ForwardIt d_first );
    ForwardIt uninitialized_copy_n( InputIt first, Size count, ForwardIt d_first);
    void uninitialized_fill( ForwardIt first, ForwardIt last, const T& value );
    void uninitialized_fill_n( ForwardIt first, Size count, const T& value );
    
迭代器
    InputIt                OutputIt
           \              /
              forwardIt
                 |
              bidirectionIt
                 |
              RandomAccessIt
              
输入迭代器   支持: ++  ==  !=  ->  *  
输出迭代器   支持: ++  ==  !=  ->  *
前向迭代器   支持: ++  ==  !=  ->  *
双向迭代器   支持: ++  ==  !=  ->  *   --
随机迭代器   支持: ++  ==  !=  ->  *   --    +n  -n    -=n   +=n
  • 迭代器适配器

    • reverse_iterator
  • 操作

    #include <iterator>
    
    void advance( InputIt& it, Distance n );
    std::iterator_traits<InputIt>::difference_type distance( InputIt first, InputIt last );
    InputIt next(InputIt it,typename std::iterator_traits<InputIt>::difference_type n = 1 );
    BidirIt prev(BidirIt it,typename std::iterator_traits<BidirIt>::difference_type n = 1 );
    
    
配置器
  • 分配器(Allocator) 管理内存的申请和释放 内存池

  • std::allocator 类模板是所有标准库容器所用的默认allocator ,若不提供用户指定的分配器。默认分配器无状态,即任何给定的 allocator 实例可交换、比较相等,且能解分配同一 allocator 类型的任何其他实例所分配的内存。

    pointer allocate( size_type n, const void * hint = 0 );
    T* allocate( std::size_t n, const void * hint);
    T* allocate( std::size_t n );
    
    void deallocate( T* p, std::size_t n );
    
    void construct( pointer p, const_reference val );
    template< class U, class... Args >
    void construct( U* p, Args&&... args );
    void destroy( pointer p );
    template< class U >
    void destroy( U* p );
    
配接器(适配器)
  • 容器适配器

  • 迭代器适配器

  • 仿函数适配器

仿函数
  • function 包装具有指定函数调用签名的任意类型的可调用对象
  • bind

1.读取票房信息,从高到低输出

2.读取一个文件(只有英文单词,而且单词之间用空格隔开) 统计每个单词出现的次数(找出出现次数最多的单词)

3.投票 五名候选人[张三,李四,王五,赵六,钱七] 现在有20个投票人进行投票,输出每名候选人的得票情况,并输出获胜者

4.业绩 四名员工分别有四个季度的业绩,需要统计每个员工的总的业绩

张三 一季度 业绩8000

李四 一季度 业绩8800

王五 一季度 业绩9000

赵六 一季度 业绩8000

张三 二季度 业绩8000

李四 二季度 业绩8800

王五 二季度 业绩9000

赵六 二季度 业绩8000

张三 三季度 业绩8000

李四 三季度 业绩8800

王五 三季度 业绩9000

赵六 三季度 业绩8000

张三 四季度 业绩8000

李四 四季度 业绩8800

王五 四季度 业绩9000

赵六 四季度 业绩8000

智能指针
  • 内存泄露

    • 申请的动态内存忘记释放

    • 申请的动态内存无法执行delete/free

      class A{
      public:
          A(int n=0){
              p = new int(n);
              cout << "A构造" << endl;
          }
          ~A(){
              cout << "A析构" << endl;
              delete p;
          }
      private:
         	int *p;
      };
      
      void func(){
          A *pa = new A(1024);//构造    
          
          //如果在delete之前发生异常
          new int[0xFFFFFFFF];// throw string("产生异常");  -->直接跳出  try -- catch
          //下面的语句可能无法被执行  造成了delete pa没有执行,申请的动态内存没有释放  内存泄露
          
          delete pa;  //析构
      }
      
      
  • 智能指针的意义

    • 管理动态内存 ,避免内存泄露
    • 管理动态内存,不需要手动delete
  • 原理

    • 在异常机制里,局部对象能够正常析构(自动调用其析构函数),在智能指针的析构函数中,delete释放申请的动态内存
  • #include

  • 智能指针只能管理new出来的动态内存,不能用非new动态内存来构造智能指针对象,因为智能指针的析构函数中,对内存进行了delete操作

  • 如果用智能指针来管理new出来的动态内存,不能自己再去delete

  • 用处

    • 它可用于为动态分配的对象提供异常安全(产生异常时,动态内存能够得到析构)、传递动态分配对象的所有权给函数和从函数返回动态分配的对象。
auto_ptr C++17被删除
  • C++11之前只有auto_ptr,C++11添加了unique_ptr,shared_ptr,weak_ptr来取代auto_ptr,C++17中,auot_ptr就被删除了

  • 拥有严格对象所有权语义的智能指针

  • 复制 auto_ptr ,会复制指针并转移所有权给目标: auto_ptr 的复制构造和复制赋值都会修改其右侧参数,而且“副本”不等于原值。因为这些不常见的复制语义,不可将 auto_ptr 置于标准容器中。此用途及其他使用更适合用 unique

  • auto_ptr拷贝构造和拷贝赋值函数 实现是 按照 移动构造 和移动赋值函数的语义实现的

  • C++11之前没有移动构造和移动赋值,所以C++11之前 auto_ptr 没有任何问题

  • 但是C++11中添加了移动构造和移动赋值,和移动构造和移动赋值区别开来了,就不能再用拷贝构造去实现移动构造的语义了

  • 拷贝构造之后,原对象把对象的控制权交给新构造的对象,原智能指针就不无法再使用了

    auto_ptr<int> p(new int(1024));
    auto_ptr<int> p1(p);        //p就失效了 把对内存对象的控制权转移给了p1
    
  • 拷贝赋值,也意味着对象的控制权转移和释放

    auto_ptr<int> p(new int(1024));
    auto_ptr<int> p1(new int(9527));
    p1 = p;  //p就失效了   p1原来的控制的动态内存会delete,转而控制原来p控制的动态内存
    
  • 构造函数

    explicit auto_ptr( X* p = 0 ) throw();  //参数必须是new出来的动态内存
    auto_ptr( auto_ptr& r ) throw();        //拷贝构造实现的却是移动构造
    auto_ptr& operator=(auto_ptr& r);       //拷贝赋值实现的却是移动构造
    
  • 观察器

    T* get() const throw();
    
    //智能指针在使用时,可以像指针一样使用     解引用    ->
    T& operator*() const throw();
    T* operator->() const throw(); 
    
    
  • 修改器

    //释放被管理对象的所有权
    T* release() throw();//返回保有的指针。调用后 *this 保有空指针。
    
    void reset( T* p = 0 ) throw();
    
  • 同一块动态内存不能构造出来多个智能对象管理

    int *p = new int(1024);
    //不管什么智能指针  都不能这样干!!!!!
    auto_ptr<int> app1(p);  //app1消亡时 delete p;
    auto_ptr<int> app2(p);  //app2消亡时 delete p;
    //double free    
    
  • 因为auto_ptr不支持完整语义上拷贝,所以不能放到容器中

  • auto_ptr任意时刻只有一个智能指针拥有对象的控制权 唯一

  • C++11之后,就用unique来取代auto_ptr

unique_ptr
template<
    class T,
    class Deleter = std::default_delete<T>
> class unique_ptr;

//针对数组类型进行了偏特化(局部特化)
template <
    class T,
    class Deleter
> class unique_ptr<T[], Deleter>;
  • std::unique_ptr 是通过指针占有并管理另一对象,并在 unique_ptr 离开作用域时释放该对象的智能指针

  • 在下列两者之一发生时用关联的删除器释放对象:

    • 销毁了管理的 unique_ptr 对象
    • 通过 operator=或 reset 赋值另一指针给管理的 unique_ptr 对象。
  • unique_ptr删除了拷贝构造和拷贝赋值函数

  • unique_ptr实现了移动构造和移动赋值函数

  • 不支持拷贝构造和拷贝赋值,只支持移动,所以就确保了 拥有独有对象所有权语义的智能指针

    explicit operator bool() const noexcept;
    //检查 *this 是否占有对象,即是否有 get() != nullptr 
    
    typename std::add_lvalue_reference<T>::type operator*() const;
    pointer operator->() const noexcept; 
    
    //针对数组特化版本
    T& operator[]( std::size_t i ) const;
    
shared_ptr
template< class T > class shared_ptr;
  • std::shared_ptr 是通过指针保持对象共享所有权的智能指针。多个 shared_ptr 对象可占有同一对象。

  • 下列情况之一出现时销毁对象并释放内存(引用计数为0):

    • 最后剩下的占有对象的 shared_ptr 被销毁;
    • 最后剩下的占有对象的 shared_ptr 被通过operator=或 reset() 赋值为另一指针。
  • 引用计数

    • 调用构造函数创建的shared_ptr,引用计数为1
    • 通过拷贝构造函数创建的shared_ptr,引用计数+1
    • shared_ptr消亡时,引用计数-1 或者 shared_ptr被赋值或者reset 引用计数-1
  • 构造函数

    constexpr shared_ptr() noexcept;
    constexpr shared_ptr( std::nullptr_t ) noexcept; 
    
    explicit shared_ptr( Y* ptr );
    shared_ptr( Y* ptr, Deleter d );
    
    //支持拷贝构造  和  移动 构造
    shared_ptr( const shared_ptr<Y>& r ) ;
    shared_ptr( shared_ptr<Y>&& r ) noexcept;
    
    
  • 赋值

    shared_ptr& shared_ptr( const shared_ptr<Y>& r ) ;
    shared_ptr& shared_ptr(shared_ptr<Y>&& r ) ;
    
  • 修改器

    void reset() noexcept;
    void reset( Y* ptr );
    void reset( Y* ptr, Deleter d );
    
    void swap( shared_ptr& r ) noexcept;
    
  • 观察器

    T* get() const noexcept;
    eleme
    

  1. ax ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Visual C++ 运行库合集(Visual C++ Redistributable)是由微软公司开发的一系列动态链接库(DLL)的集合,它们为在 Windows 操作系统上运行使用 Visual C++ 编写的软件提供所需的运行时支持。 Visual C++ 运行库合集包含了多个版本的运行库,每个版本都与 Visual C++ 编译器的不同版本相对应。根据编程时所选用的版本,需要安装相应版本的运行库才能保证软件在用户的计算机上正确运行。这意味着如果一款软件使用 Visual C++ 2010 编译,用户需要安装 Visual C++ 2010 运行库才能运行该软件。 Visual C++ 运行库合集的安装通常由软件开发人员在软件安装过程中进行自动安装,也可以手动下载和安装。Windows 操作系统中的“程序和功能”部分列出了已安装的 Visual C++ 运行库合集,同时也提供了卸载选项。 Visual C++ 运行库合集的使用是为了提高软件的兼容性和稳定性。它们提供了许多常见的运行时功能,如内存管理、异常处理、多线程支持等。通过使用统一的运行库合集开发人员可以减少对不同的操作系统和屏蔽硬件差异的关注,从而更专注于软件逻辑和用户体验的开发。 总之,Visual C++ 运行库合集是提供在 Windows 操作系统上运行使用 Visual C++ 编写的软件所需的运行时支持的集合。它们通过提供常见的运行时功能来增加软件的兼容性和稳定性,方便开发人员和用户同时使用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值