C++面向对象

一 内存四区

  • C++在执行.exe程序的时候,会对内存进行区域划分,主要目的是方便更加高效以及灵活的编程。内存的区域主要被划分为四个部分:代码区,全局区,栈区,堆区。

  • 代码区主要存放的是二进制的代码,有操作系统进行管理

  • 全局区存放全局变量,静态变量,常量(包括字面量和const修饰的全局变量)

  • 栈区:存放所有的非动态开辟的(new或者malloc)的局部变量,其空间的分配和释放有编译器管理,当函数结束时,局部变量占用的空间自动被释放。匿名对象应该㛑存放在这个区域。

  • 堆区:存放所有动态开辟的变量,其空间分配由程序员管理

一些小问题

  • 形如Object obj;这样的obj对象,是存放在栈区?其实不然,obj具有自动存储的性质,意思就是这个对象存储的位置取决于其所声明所在的上下文。

    如果这个语句出现在函数内部,那么毫无疑问,obj将创建在栈上。

    如果这个语句是一个类的成员变量,则取决于这个类的对象是存储在哪里的。参考如下代码:

    class Class{
        Object obj;
    }
    int main(){
        Class* pClass = new Class;
    }
    

    因为指针pClass所指向的对象是在堆上分配空间的,因此pClass->obj也是创建在堆上的。

  • 对象析构时,成员变量会怎么样?

    • 在一个析构函数中,首先执行函数体,然后销毁成员属性。成员属性按照初始化的逆序进行销毁。
    • 成员变量在销毁时依赖于其类型。销毁类类型的成员时,会执行其析构函数。内置类型则没有析构函数,因此销毁内置类型成员什么也不需要做。
  • 何时会调用析构函数

    • 变量在离开其作用域时被销毁
    • 当一个对象被销毁时,其成员被销毁
    • 容器被销毁时,其元素被销毁
    • 对于临时对象,当创建它的完整表达式结束时被销毁
    • 对于动态分配的对象,当指向它的指针执行delete运算符时被销毁

二 再谈引用

  1. 函数的调用可以作为左值:如果函数返回的类型是引用,且返回的引用的对象的生命周期是长久存在的,此时函数调用可以作为左值,对函数调用的更改将会作用域这个长久存在的对象。

    int& retRef(){
        static int a = 10;
        return a;
    }
    
    int main(){
        cout << retRef() << endl;	// 输出10
        retRef() = 100;
        cout << retRef() << endl;	// 输出100
    }
    
  2. 引用的本质:指针常量。C++编译器会在编译的时候为我们做一些额外的工作,在编译的时候自动将引用替换为指针常量解引用。

三 再谈函数

  1. 默认参数:在函数声明和函数定义中,每个参数只能有一次机会定义参数默认值,否则报冲定义默认参数错误。

    void fun(int a, int b = 10);
    
    void fun(int a = 20, int b) {
        cout << a + b << endl;
    }
    
    int main() {
    
        fun(30, 30);
        
        return 0;
    }
    
  2. 占位参数:在定义函数的时候,参数名也可以省略。被省略参数名的参数被称为占位参数。暂时还不知道到有什么用。

  3. 函数重载的条件:

    • 同一作用域

    • 函数名相同

    • 函数参数类型不同个数不同或者顺序不同

    • 函数的返回值不可以作为函数重载的条件,因为在调用的时候可以不接收返回值,仅仅调用函数。

    • 函数重载有默认参数时,可能会出现二义性。在函数重载时,尽量少使用默认参数。

四 面向对象:封装

  1. 三种访问权限

    • public: 类内可以访问,类外可以访问
    • protected: 类内可以访问,类外不可以访问,子类可以访问父类protected内容
    • private: 类内可以访问,类外不可以访问,子类不可以访问父类private内容
  2. struct和class的区别:没什么区别,唯一的区别就在于默认访问权限不同

    • struct默认访问权限为public
    • class默认访问权限为private
  3. C++中,类中的成员变量的隐式初始化机制如下:

    int *ptr;  // 包含任意野值
    int number; // 包含任意野值
    string name; // 空string
    string *name; // 包含任意野值
    string &refname; //编译错误
    

    而Java中会给类的成员变量进行0值初始化

  4. 构造函数

    • 如果遇到类没有适当的复制构造函数的错误时,是因为复制构造函数的函数声明是Object(const Object& o),缺少了const就会报这样的错误。

    • 不要利用拷贝构造函数来创建一个匿名对象。编译器会去调括号,认为在声明一个同名对象。

    • 构造函数隐式转换:如果构造函数只有一个参数,且参数是基本数据类型,可以直接去掉括号;或者是复制构造函数也可以这么做。

      Student::Student(int age) {
      	this->age = age;
      }
      Student::Student(const Student& s) {
      	*this = s;
      }
      int main(){
          Student s4 = 22;
          Student s5 = s4;
      }
      
      
    • 如果没有拷贝构造函数,是无法使用Student s5 = s4;这样的初始化语句的。

    • 拷贝构造函数调用时机:

      • 用一个已经存在的对象来初始化一个新对象
      • 值传递方式给函数参数传值
      • 在不同编译器中,以值方式返回局部对象有不同的情况。vs2022中,返回局部对象本身;而老版本的vs中可能返回的是局部对象的复制对象,这时候会调用拷贝构造函数。
    • C++中

      • 如果不写构造函数,编译器会默认提供无参构造函数、拷贝构造函数和析构函数。
      • 如果写了有参构造函数,编译器就不会提供无参构造函数,但会提供拷贝构造函数
      • 如果写了拷贝构造函数,编译器就不会提供无参够赞函数
    • 浅拷贝、深拷贝

      • 浅拷贝:完完全全的值的复制。如果对象中有指针,那么经过浅拷贝之后两个对象中的指针将指向同一个地址。会带来的问题就是堆内存重复释放。
      • 深拷贝:对于指针类型,重新开辟一块内存进行存储指针指向的对象。两个指针指向的地址不一样,但内容是一样的。如果对象中有在堆区开辟空间,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。
    • 初始化列表:在构造函数后面申明初始化列表,可以对成员属性进行初始化。是一种语法糖。

      class Student{
          string name;
          int age;
          int height;
          
      public:
          Student(string name, int age, int height): name(name), age(age), height(height) {
              
          }
          
      }
      
  5. 成员对象和对象本身的创建顺序:先调用成员对象的构造函数,再调用本类的构造函数;析构函数调用顺序相反,先调用本类析构函数,再调用成员对象的构造函数。

  6. 静态成员变量:需要在类内声明,类外初始化,不可以直接初始化(有点变态)。

  7. C++对象模型

    • C++编译器会给每个空对象分配一个字节的空间,是为了标记空对象在内存中的位置。(Java中空对象可不止这么小,对象头保存了很对信息,比如GC分代年龄,哈希码,锁状态标识,线程持有的锁等等,另外还有类型指针、实例数据等空间)
    • 成员变量和成员函数分开存储,只有非静态成员变量会存储到对象空间上。静态成员变量和静态成员方法属于类的信息,不会存储在对象上。非静态成员方法也是单独存储一份,会通过this指针来确定调用者。
  8. 常函数:在函数参数声明之后加上const关键字,表示在此函数范围内,this指针指向的对象是const类型,不可修改。因此常函数内不可修改类的成员对象。除非是加了mutable关键字的成员对象。mutable:让const类型的对象或变量可以被修改。

    // 定义常函数
    int getName() const{
        
    }
    
  9. 常对象:常对象的内容不可更改,也不可以调用非const函数。因为普通函数是可以修改成员变量的值的,这就变相的更改了常对象的值。因此无法通过常对象进行调用。

  10. 友元:在程序中,有些私有属性也想让类外的一些函数或者类进行访问,就需要用到友元技术。

    • 全局函数做友元:只需要在类内部用friend关键字声明该函数是友元即可。函数名不可省略,因为函数名是唯一标识该函数的方式。
    • 在类中申明友元类,该友元类必须在本类之前定义。
  11. 重载运算符:使用类似于operator+()、operator<<() 这样的函数来重载运算符。一般来说可以使用成员函数来重载运算符,也可以使用全局函数来重载运算符。

    // 成员函数重载运算符
    class Person{
    public:
        int age;
        int money;
        Person operator+(Person &person){
            Person temp;
            temp.age = person.age + this->age;
            temp.money = person.money + this->money;
            return temp;
        }
    }
    
    // 使用全局函数重载运算符
    Person operator+(Person& p1, Person& p2){
        Person temp;
        temp.age = p1.age + p2.age;
        temp.money = p1.money + p2.money;
        return temp;
    }
    
  12. 重载左移运算符<<:左移运算符无法使用成员函数的方式进行重载,必须使用全局函数的方式进行重载,并且对象cout要放在第一个参数的位置。

    ostream operator<<(ostream& cout, Person& p){
        cout << "age" << p.age << ", money" << p.money;
        return cout;
    }
    

    左移运算符配合友元技术可以输出自定义格式的数据类型。

  13. 重载递增运算符:分为前置重载和后置重载

    // 重载前置递增运算符
    // 返回引用
    Person& operator++(){
        this.age += 1;
        this.money +=1;
        return *this;
    }
    // 后置递增运算符重载
    // 使用占位参数,告诉编译器这是重载的后置运算符
    // 返回值
    Person operator++(int){
        Person temp = *this;
        this.age += 1;
        this.money +=1;
        return temp;
    }
    
  14. 重载()运算符:也叫做仿函数,因为()运算符使用起来非常像函数调用。比较灵活,根据需求的不同可以有多重实现方式。

五 面向对象:继承

  1. 基本语法:class 子类 : 继承方式 父类

  2. 三种继承方式:

    • 公共继承
    • 保护继承
    • 私有继承
  3. 继承方式会限制子类成员的访问权限,会根据父类中成员修饰符和继承方式来共同决定子类中对应成员的访问权限。具体情况如下:
    在这里插入图片描述

  4. 子类可以直接访问父类中不同名的成员。如果子类中出现了和父类同名的成员函数,子类的同名成员函数会隐藏掉父类中所有同名的重载的成员函数。不能够再直接访问,必须加上作用域才行。

  5. C++允许多继承。但可能会出现不同父类的成员的同名冲突。因此需要加上作用域进行区分。另外,由于多继承可能会出现这种问题,因此在实际开发中不建议使用多继承。

  6. 菱形继承:使用虚继承来解决数据重复问题。

  7. 虚继承:在菱形继承的中间层中加入virtual关键字来声明是虚继承。虚继承,被多继承的父类的同名成员都会是一个指针,指向子类的成员变量。因此子类中的成员将只有一份。

    class Animal{
    public:
        int age;
    };
    class Sheep : virtual public Animal{};
    class Tuo : virtual public Animal{};
    class SheepTuo : public Sheep, public Tuo{}
    
    int main(){
        SheepTuo st;
        st.Sheep::age = 100;
        st.Tuo::age = 200;
        
        // 全部输出200
        cout << "st.Sheep::age = " << st.Sheep::age << endl;
        cout << "st.Tuo::age = " << st.Tuo::age << endl;
        cout << "st.age = " << st.age << endl;
    }
    

六 面向对象:多态

  1. 静态多态:通过函数重载和运算符重载实现功能复用

  2. 动态多态:通过派生类虚函数来实现运行时多态。

  3. 多态的本质:父类的引用或者指针指向子类对象。但C++中多态与Java是不一致的。之前说过,在C++类中,成员方法是全局只有一份的。不同对象调用的时候会通过this来确定调用的对象到底是谁。因此在编译的时候,类的非虚方法的地址就确定下来了,因此非虚函数的地址是早绑定的。通过virtual关键字声明虚方法,可以让地址在运行阶段绑定,即地址晚绑定。

    class Animal{
    public:
        int age;
        void speak(){
            cout << "动物在说话" << endl;
        }
    };
    
    class Sheep : public Animal{
        void speak(){
            cout << "羊在说话" << endl;
        }
    };
    
    void speak(Animal& animal){
        animal.speak();
    }
    
    int main(){
        Sheep sheep;
        speak(sheep);
    }
    

    在这里插入图片描述

    而在Java中,地址总是晚绑定的。

    public class Main {
        static int i = 0;
        public static void main(String[] args) {
            Speaker speaker = new Speaker();
            Man man = new Man();
            speaker.createSpeaker(man);
        }
    }
    
    class Person{
        public void speak(){
            System.out.println("人在说话");
        }
    }
    
    class Man extends Person{
        public void speak(){
            System.out.println("男人在说话");
        }
    }
    
    class Speaker{
        public void createSpeaker(Person person){
            person.speak();
        }
    }
    

    在这里插入图片描述

  4. 一句话总结:Java中所有的方法都是虚函数,Java中的抽象方法就是纯虚函数。

  5. 析构函数与虚析构函数:如果使用了父类的指针指向子类对象的方式创建对象,那么在调用析构函数的时候,只会调用父类的析构函数,而不会调用子类的析构函数。解决的办法就是将父类的析构函数用virtual关键字声明为虚析构函数,这样在调用析构函数的时候就会先调用子类的析构函数,再调用父类的析构函数。可以解决子类释放内存不干净的问题。

    那么,既然虚析构函数具有这样的有点,是不是所有的析构函数都应该声明为虚析构函数呢?其实不然,如果子类中没有额外额内存空间需要释放,则不需要虚析构函数。虚函数会带来一定的开销,会多查虚函数表的过程,另外内存中也要维持虚函数表和虚函数指针。因此声明成虚函数会增大时间和空间的消耗。

  6. 纯虚析构函数必须在类外进行实现。因为父类也必定需要进行析构,如果不实现析构函数,会报错。有纯虚析构的类也是抽象类,无法实例化对象。

七 文件输入输出

  1. 默认路径 visual studio 和 CLion是不同的

    • vs:与cpp文件同级目录下
    • clion:cpp同级目录/cmake-build-debug
  2. 关于读写模式:在ios_base类中定义了一些static枚举。basic_ios类继承了ios_base。在iosfwd中typedef了一些对象,其中就包含ios。使用ios::out、ios_base:out、basic_ioc<char>::out本质上是一样的

  3. 读文件三种方式

    • ifstream >> char[]
    • char[] + ifstream .getline(buf, size)
    • string + 全局函数getline(ifstream , str)
  4. 在写数据的时候,建议使用char[]而非string

  5. 在包装输入输出流时,Java是使用了装饰器模式,层层套娃。而C++使用了|操作符,直接声明读写模式即可。

    ofstream ofs("person.txt", ios::out | ios::binary);
    Person p("张三", 18);
    // 将地址转换为const char*
    ofs.write((const char*)&p, sizeof p);
    
    Person p2;
    ifstream ifs("person.txt", ios::in | ios::binary);
    ifs.read((char*)&p2, sizeof p2);
    cout << p2.name << " " << p2.age << endl;
    

八 泛型与模板

C++中的泛型是通过模板来实现的。

在C++中使用模板,需要先进行声明。

  1. 函数模板

    template<typename T>
    void swap(T& a, T& b){
        T temp = a;
        a = b;
        b = temp;
    }
    
    int main(){
        int a = 10;
        int b = 20;
        // 可能会报错,call to 'swap' is ambiguous
        // 是因为swap和类库中函数名冲突
        // 改成::swap,强制使用当前命名空间的函数
        // 自动推导
        ::swap(a, b);
        // 显示声明
        ::swap<int>(a, b);
        cout << "a = " << a << endl;
        cout << "b = " << b << endl;
    }
    
  2. 如果自定义函数名和类库中函数名冲突,可以使用::函数名来强制使用当前定义的函数。

  3. 普通函数和函数模板的区别

    • 普通函数在调用时,参数可以发生隐式类型转换
    • 函数模板在使用自动类型推导的时候,不可以发生隐式类型转换
    • 函数模板在使用显示类型指定的时候,可以发生隐式类型转换
  4. 普通函数和函数模板的重载调用规则

    • 如果普通函数和函数模板都可以调用,则优先调用普通函数
    • 可以通过<>来强制调用函数模板
    • 函数模板也可以发生重载
    • 如果函数模板可以有更好的参数匹配,则优先调用函数模板
  5. 建议:提供了函数模板,就不要提供普通函数,不然容易发生二义性。

  6. 类模板和函数模板的区别

    // 定义模板类
    ...
    
    • 类模板没有自动类型推导的使用方式
    • 类模板可以给模板参数增加默认类型
  7. 类模板中的成员函数在使用的时候才会创建,即在定义模板的类型的时候才会创建。

  8. 类模板中的成员函数在类外实现时,需要加上模板参数列表。

  9. 在类模板分文件编写的时候,会遇到包含.h文件在编译的时候无法链接的情况。可以有两种解决方式

    • 直接包含.cpp文件

    • 在使用类模板的时候,可以将类中方法的声明和实现放在一个文件中,然后将这个文件的后缀改为.hpp,默认.hpp文件都是一个类模板。

  10. 友元函数如果在类内定义,则不需要在类外声明。此时这个友元函数可以作为一个全局函数。而且类模板的友元函数建议在类内实现,因为类外实现非常复杂。

九 STL容器

string

  • find():从左往右查

  • rfind:从右往左查

  • replace(index, len, newstr):从index开始,len长度,替换为newstr

  • 字符串比较

    = 返回 0

    > 返回 1

    < 返回 -1

  • 字符串比较一般用于比较是否相等,比较谁打谁小意义并不是很大。

  • insert(position, str):从position位置插入str

  • erase(position, len):从position位置删除len长度

  • substr(position, len):从position位置起截取len长度的子串

vector

  • 本质上是一个模板类,底层用数组存储数据。(类似于Java中的ArrayList)

在这里插入图片描述

  • 构造函数
    • vector<T>():默认构造
    • vector<T>(v1.begin(), v1.end()):传入两个迭代器,之间的数据会进行初始化
    • vector<T>(n, ele):内容初始化为n长度个ele
    • vector<T>(const vector &v1):用v1进行初始化
  • 赋值
    • 等号赋值
    • assign(begin, end)
    • assign(n, ele)
  • 常用方法
    • empty:判断是否为空
    • size:返回元素个数
    • capacity:返回数组容量
    • resize:重新指定vector的size
    • swap(vector):互换容器内容。可以用于vector内存空间收缩。
  • 插入和删除
    • push_back(ele):尾部插入元素ele,返回值是void
    • pop_back:尾部删除一个元素,弹出元素,但返回值是void
    • insert(const_iterator pos, ele):在迭代器pos位置插入一个元素ele
    • insert(const_iterator pos, int count, ele):在迭代器pos位置插入count个元素ele
    • erase(const_iterator pos):删除迭代器pos指向的元素
    • erase(const_iterator start, const_iterator end):删除迭代器start到end之间的元素
    • clear:删除vector所有元素
  • 数据存储
    • 访问方式:通过[]下标或者at(下标)可以进行随机访问
    • front():获取第一个元素
    • back():获取最后一个元素

deque

  • 双端队列,内存模型如下:

在这里插入图片描述

  • 构造函数

    • deque<T>():默认构造
    • deque<T>(d1.begin(), d1.end()):传入两个迭代器,之间的数据会进行初始化
    • deque<T>(n, ele):内容初始化为n长度个ele
    • deque<T>(const deque &d1):用v1进行初始化
  • 赋值操作

    • 等号赋值

    • assign(begin, end)

    • assign(n, ele)

  • 常用方法–deque没有容量,可以无限扩展

    • empty:判断是否为空
    • size:返回元素个数
    • resize:重新指定deque的size
  • 插入和删除:pos,begin,end均指的是迭代器

    • push_back(ele):尾部插入元素ele,返回值是void
    • push_front(ele):头部插入元素ele,返回值是void
    • pop_back:尾部删除一个元素,弹出元素,但返回值是void
    • pop_front:头部删除一个元素,弹出元素,但返回值是void
    • insert(pos, ele):在迭代器pos位置插入一个元素ele
    • insert(pos, n, ele):在迭代器pos位置插入n个元素ele
    • insert(pos, begin, end):在迭代器pos位置插入其他deque中begin到end的数据
    • erase(pos):删除迭代器pos指向的元素
    • erase(begin, end):删除迭代器start到end之间的元素
    • clear:删除vector所有元素
  • 数据存取

    • 访问方式:通过[]下标或者at(下标)可以进行随机访问
    • front():获取第一个元素
    • back():获取最后一个元素

stack

  • 先进后出,无法遍历,只有栈顶元素可以被访问到
  • 构造函数
    • stack<T>:默认构造函数
    • stack(const stack &s):拷贝构造函数
  • 赋值操作
    • 等号赋值
  • 数据存取
    • push(ele):压栈,返回值为void
    • pop():弹栈,返回值为void
    • top():返回栈顶元素
  • 大小操作
    • empty():判断是否为空
    • size():返回栈的大小

queue

  • 先进先出,无法遍历,只有队头和队尾的元素可以被访问到
  • 构造函数
    • queue<T>:默认构造函数
    • queue(const queue &q):拷贝构造函数
  • 赋值操作:
    • 等号赋值
  • 数据存取
    • push(ele):对头添加元素
    • pop:队尾移除元素
    • back:返回最后一个元素
    • front:返回第一个元素

list

  • 链表,地址非连续,类似于Java中的LinkedList

  • 构造函数

    • list<T>():默认构造
    • list<T>(begin, end):传入两个迭代器,之间的数据会进行初始化
    • list<T>(n, ele):内容初始化为n长度个ele
    • list<T>(const list& l1):用l1进行初始化
  • 赋值和交换

    • 等号赋值
    • assign(begin, end)
    • assign(n, ele)
    • swap(list)
  • 大小操作

    • size
    • empty
    • resize
  • 插入和删除

    • push_back
    • pop_back
    • push_front
    • pop_front
    • insert(pos, ele)
    • insert(pos, n, ele)
    • insert(pos, begin, end)
    • clear
    • erase(begin, end)
    • erase(pos)
    • remove(ele):删除list中左右与ele值匹配的元素
  • 数据存取

    • 不支持随机访问

    • front():返回第一个元素

    • back():返回最后一个元素

  • 反转和排序:对于非连续存储的数据结构,无法使用全局的sort()函数。但其内部会提供相应的sort()函数。

    • list.reverse()
    • list.sort()
    • list.sort(bool compare())

set/multiset

  • 集合,不允许重复,不按照插入顺序排序,而按照数值大小进行排序,和Java中的TreeSet类似

  • 构造函数

    • set
    • set<const set& s>
  • 赋值

    • 等号赋值
  • 大小和交换:无法resize,size随着插入的数据是固定的

    • size
    • empty
    • swap
  • 插入和删除

    • insert(ele):返回值是pair<set<T>::iterator, bool>,第一个是迭代器,第二个是是否已存在数据
    • clear()
    • erase(pos)
    • erase(begin, end)
    • erase(elem)
  • 查找和统计

    • find(key):查找key元素是否存在,若存在返回该元素的迭代器;若不存在,返回set.end()
    • count(key):返回key的个数
  • 排序

    • 利用仿函数设置默认排序规则

      class MyCompare{
          operate()(int v1, int v2){
              return v1 > v2;
          }
      }
      int main(){
          set<int> s;
          s.insert(50);
          s.insert(20);
          s.insert(40);
          s.insert(30);
      }
      
    • 对于自定义类型,必须定义排序规则才能插入set,要定义一个仿函数传入构造函数才行。

      • 仿函数:本质上是一个类,重载了()运算符,用起来像函数
      set<Person, compare> s1;
      class compare{
      public:
          bool operator()(const Person& p1, const Person& p2){
              if(p1.age != p2.age){
                  return p1.age < p2.age;
              } else {
                  return p1.height < p2.height;
              }
          }
      };
      
      • 如果报错no type named 'const_iterator' in 'std::_Rb_tree<XXX, XXX, std::_Identity<Person>, compare, std::allocator<XXX>>',是因为仿函数没加public
  • multiset:集合,允许重复,不按照插入顺序排序,而按照数值大小进行排序

    • insert(ele):返回值是set<T>::iterator迭代器

map/multimap

  • map中所有元素都是pair,pair相当于Java中的Map.Entry,不过pair可以单独定义对象,而Map.Entry只能通过遍历Map.EntrySet获取。
  • pair第一个元素为key,第二个元素为value
  • map中所有的pair都会根据key的值进行自动排序
  • map/multimap的底层实现是红黑树;而Java中的HashMap的实现是基于基于hash算法的数组+链表+红黑树
  • 构造和赋值
    • map<T1, T2> m:默认构造
    • map(const map& m):拷贝构造
    • 等号赋值
  • 大小和交换
    • size
    • empty
    • swap
  • 插入和删除
    • insert(pair<T1, T2>):插入必须插入一个pair
    • insert(make_pair(2, 20)):使用make_pair生成一个pair
    • insert(map<T1, T2>::value_type(2, 20)):不建议使用
    • m[2] = 20:直接使用下标进行赋值
    • erase(begin):删除迭代器删除
    • erase(begin, end):区间删除
    • erase(key):删除指定key对应的pair
    • clear():清空
  • 查找和统计:
    • find(key)
    • count()
  • 排序:默认根据key从小到大排序
    • 可以通过仿函数来指定排序规则

十 函数对象-仿函数

概念

  • 函数对象本质上是一个类,重载了()操作符,可以像函数一样进行调用,并不是一个函数。
  • 函数对象在使用时,可以像普通函数那样调用,可以有参数,也可以有返回值
  • 函数对象超出了普通函数的概念,函数对象可以有自己的状态,因为函数对象是类,有成员属性可以记录状态。
  • 函数对象可以作为参数传递,可以实现代理模式。

谓词

  • 返回bool类型的仿函数称为谓词
  • 一元谓词和二元仿函数:重载()的方法有一个参数或两个参数
  • 内建函数对象:在使用时需要引入头文件#include <functional>
    • 算数仿函数
      • template<class T> plus<T>
      • template<class T> minus<T>
      • template<class T> multiplies<T>
      • template<class T> divides<T>
      • template<class T> modulus<T>
      • template<class T> negate<T>
    • 关系仿函数
      • template<class T> bool equal_to<T>
      • template<class T> bool not_equal_to<T>
      • template<class T> bool greater<T>
      • template<class T> bool greater_equal<T>
      • template<class T> bool less<T>
      • template<class T> bool less_equal<T>
    • 逻辑仿函数:使用场景极少
      • template<class T> bool logical_and<T>
      • template<class T> bool logical_or<T>
      • template<class T> bool logical_not<T>

十一 STL常用算法

常用遍历算法

  • for_each(begin, end, function)
    • function:可以使函数,也可以是仿函数。如果是函数则提供函数名,如果是仿函数则提供函数对象。作用是在遍历过程中进行何种操作。
  • transform(sBegin, send, oBegin, _func):复制搬运
    • _func:仿函数,在搬运过程中进行何种操作。
    • 在搬运过程中,目标容器必须提前开辟空间,否则会无法搬运成功

常用查找算法

  • find(begin, end, ele):查找容器中与ele相同的元素:如果是查找自定义类型,则自定义类型必须重载==运算符
  • find_if(begin, end, _func):按条件查找元素
  • adjacent_find(begin, end):查找相邻重复元素,如果找到则返回第一个元素的迭代器
  • binary_search(begin, end, ele):二分查找,查找的容器必须是有序的
  • count(begin, end, ele):统计容器中和ele相同的元素个数:统计自定义类型,也需要重载==运算符
  • count_if(begin, end, _func):按条件统计元素个数

常用的排序算法

  • sort(begin, end, _func):_func:仿函数,表示排序规则,也可以使用内建函数对象
  • random_shuffle(begin, end):将begin到end区间中的元素堆积打乱,需要加随机数种子。如果不加则每次打乱的结果都相同。
  • merge(v1.begin(), v1.end, v2.begin(), v2.end(), v3.begin()):合并v1和v2两个容器的元素到v3。两个容器约定而非必须是有序的,并且合并后的结果也是有序的。类似于归并排序
  • reverse(begin, end):反转容器元素

常用的拷贝和替换算法

  • copy(sBegin, sEnd, oBegin):
  • replace(begin, end, oldVal, newVal):从begin到end,替换oldVal为newVal
  • replace_if(begin, end, _func, newVal):从begin到end筛选满足条件的元素替换为newVal
  • swap(T1, T2):替换两个元素中的内容

算数生成算法

  • 使用时要包含#include <numeric>
  • accmulate(begin, end, offset):从begin累加到end,最后加上偏置值offset
  • fill(begin, end, value):从begin到end都填充为value

集合算法

  • 使用要包含#include <algorithm>
  • set_intersection(v1.begin, v1.end, v2.begin, v2.end, v3.begin):将v1和v2的交集放到v3。v3的大小要初始化为v1和v2中较小的的值。返回值为最后一个交集元素的迭代器,而不是v3.end。
  • set_union(v1.begin, v1.end, v2.begin, v2.end, v3.begin):将v1和v2的并集放到v3。v3的大小要初始化为v1和v2大小之和。返回值为最后一个并集元素迭代器。
  • set_difference(v1.begin, v1.end, v2.begin, v2.end, v3.begin):将v1和v2的差集放到v3。v3的大小要初始化为v1的大小。返回值为最后一个并集元素迭代器。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值