c++学习笔记(十、map、函数对象、函数对象适配器)

本来还以为这一篇就能搞定基础,结果想不到的是,最后几节的知识点这么多,比较还没接触过,所以还是分为两篇,这一篇只要说map容器和函数对象,特别是对象适配器讲的比较多,对象适配器是新知识点,所以讲的细了一点。

10.1 map/multimap

10.1.1 map/multimap介绍

map相对于set区别,map具有键值(key)和实值(value),所有元素根据键值自动排序,pair(对组)的第一个元素被称为key,第二个元素称为value。map也是以红黑树为底层实现的。

在这里插入图片描述
存储的数据如图所示,一个key和一个value。
map和multimap的区别还是map不能有key值相同,而multimap允许key值相同。
注意,不能直接修改 multimap 容器中的关键字。因为 multimap 中的元素是按照关键字排序的,当关键字被修改后,容器并不会自动重新调整顺序,于是容器的有序性就会被破坏,再在其上进行查找等操作就会得到错误的结果。

map模板类定义

// CLASS TEMPLATE map
template <class _Kty, class _Ty, class _Pr = less<_Kty>, class _Alloc = allocator<pair<const _Kty, _Ty>>>
class map : public _Tree<_Tmap_traits<_Kty, _Ty, _Pr, _Alloc, false>> {
	using value_type             = pair<const _Kty, _Ty>;
}

我们目前比较关系的是前面两个参数,class _Kty 就是key,class _Ty是value,第三个参数我在set容器中,讲过了,就是制定排序顺序的,它有一个默认参数,可以不填。

10.1.2 map/multimap构造函数

在这里插入图片描述
构造函数都很熟悉了吧,只不过map<T1, T2>,T1是key,T2是value。

10.1.3 map/multimap赋值操作

在这里插入图片描述
老生长谈了。

10.1.4 map/multimap大小操作

在这里插入图片描述

10.1.5 map/multimap插入数据元素操作

在这里插入图片描述
例子:

int main(int argc, char **argv)
{
    map<int, string> mymap;   //调用无参构造函数

    //第一种,申请pair匿名对象插入
    //pair<map<int, string>::iterator, bool> 
    auto ret = mymap.insert(pair<int ,string>(1, "李西"));
    //这个插入的返回值比较麻烦,上面屏蔽掉的,就是类型,c++ 11中可以yogaauto匹配类型
    if(ret.second) {
        cout << "插入成功" << endl;
    } else {
        cout << "插入失败" << endl;
    }

    ret = mymap.insert(pair<int ,string>(1, "李西"));
    if(ret.second) {
        cout << "插入成功" << endl;
    } else {
        cout << "插入失败" << endl;
    }

    //第二种,通过make_pair方法创建pair对象插入
    mymap.insert(make_pair(2, "赵五"));

    //第三种,通过value_type的方式插入
    mymap.insert(map<int, string>::value_type(3, "老王"));

    //第四种,通过数组方法插入
    mymap[1] = "小李";
    mymap[5] = "老李";
    //发现如果key不存在,创建pair插入到map容器中,
    //如果发现key存在,那么会修改key对应的value值

    //如果通过[]方式去访问map中一个不存在key,
    //那么map会将这个访问的key插入到map中,并且给value一个默认值
    cout << "mymap[7] = " << mymap[7] << endl;

    for(map<int, string>::iterator it = mymap.begin(); it != mymap.end(); it++) {
            cout << "第一个 " << it->first << "  第二个 " << it->second << endl;
    }
    
    return 0;
}

例子中,有插入的四种方法,和说明。

10.1.6 map/multimap删除操作

在这里插入图片描述
删除操作跟以前差不多。

10.1.7 map/multimap查找操作

在这里插入图片描述
查找操作比较复杂一点,我们练练手:
count需要注意,如果是map的话,返回的不是0就是1,如果是multimap的话,返回一个key的个数,需要遍历用的。

int len = mymap.count(1);   //count用int类型接
cout << "len = " << len << endl;

pair<map<int, string>::iterator, map<int, string>::iterator> ret1 = mymap.equal_range(2);
//这个返回值更长,先返回一个对组,然后对组中包含两个map的迭代器
cout << "第一个 " << ret1.first->first << "  第二个 " << ret1.first->second << endl;
cout << "第一个 " << ret1.second->first << "  第二个 " << ret1.second->second << endl;

结果:
在这里插入图片描述

10.2 容器元素的深浅拷贝问题

我们容器的元素是浅拷贝进去的,也就是如果遇到指针要申请内存的话,需要深拷贝,也就是要重载拷贝构造函数和等号操作符。

#include <map>
#include <string.h>
#include <set>
#include <vector>
class Person
{
 public:
     int age;
     int id;
     char *name;

     Person(char*name, int age, int id): age(age), id(id){
         this->name = new char[strlen(name) + 1];
         strcpy(this->name, name);
         cout << "构造函数" << endl;
     }
     
     bool operator()(Person& p){
         cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id << endl;
     }

     ~Person() {
         cout << "析构函数" << &(this->name) << endl;
         if(this->name != NULL){
             delete[] this->name;
             //this->name = NULL;
         }
     }

     //set容器排序,需要用到<号,也可以像上面那个用仿函数
     bool operator<(const Person& anthor) const {
         return this->age > anthor.age;
     }
 };

int main(int argc, char **argv)
{
     Person p1("lizi", 10, 1);
     Person p2("zhaoxi", 13, 2);
     Person p3("wangwu", 15, 3);
     Person p4("xixi", 16, 4);
     vector<Person> vp;
     vp.push_back(p1);
     vp.push_back(p2);
     vp.push_back(p3);
     vp.push_back(p4);

     //for_each(vp.begin(), vp.end(), Person("jj", 12, 9));

     return 0;
 }

不知道g++具体在内部实现了什么,在添加4个之后,就出现了释放有问题,这个是结果:
在这里插入图片描述
然后我们把拷贝构造函数修改成深拷贝,(=号操作符重载也改成深拷贝):

Person(const Person& p) {
    cout << "拷贝构造函数 " << endl;
    this->age = p.age;
    this->id = p.id;
    this->name = new char[strlen(p.name) + 1];
    strcpy(this->name, p.name);
}

Person& operator=(const Person& p){
    cout << "等号操作符 ===================" << endl;
    //1.防止自身赋值
    if (this == &p) {
        return *this;
    }

    //2.释放原来的内存
    if(this->name != NULL) {
        cout << "this->name有值" << endl;
        delete[] this->name;
        this->name = NULL;
        this->id = 0;
    }

    this->id = p.id;
    this->age = p.age;
    this->name = new char[strlen(p.name) + 1];
    strcpy(this->name, p.name);
    return *this;
}

执行的结果:
在这里插入图片描述
虽然我也不清楚为什么拷贝构造这么多次,析构这么多次,反正总的来说不崩溃了。有谁知道的,可以留言告诉我,谢谢。

10.3 容器共性和使用场景

共性:

  • 除了queue和stack之外,每个容器都提供迭代器,可以使用迭代器遍历数据。
  • 通常STL不会抛异常,需要我们用的时候小心
  • 每个容器都提供一个默认的构造函数和一个默认的拷贝构造函数
  • 大小相关的构造方法:1 size()返回容器中元素的个数,2empty()判断容器是否为空

使用时机:
在这里插入图片描述

  1. vector :比如软件历史操作记录的存储,我们经常要查记录,但是很少删除。
  2. deque:比如排队购票系统,支持头端快速移除,尾端快速添加。
  3. list : 支持频繁的不确定位置删除和插入
  4. set : 比如对手机游戏个人得分记录存储,存储要求从高到低顺序排列。
  5. map : 比如按ID号存储十万数据,想要通过ID快速查找到数据

vector和deque比较:
vector.at()比deque.at()效率高,vector.at(0)是固定,deque.at(0)是不确定的
如果需要大量释放的话,vector花的时间更少。
deque支持头部快速插入和删除。

10.4 函数对象

10.4.1 函数对象概念

重载“()”操作符,使得类对象可以像函数那样调用,所以称为函数对象或仿函数。
注意:

  1. 函数对象(仿函数)是一个类,不是一个函数
  2. 函数对象(仿函数)重载了“()”操作符使得它可以像函数一样调用

重载的Operator()要求传一个参数,我们就将这个类称为“一元仿函数”,如果需要两个参数就“二元仿函数”。函数对象也可以有参数和返回值。

10.4.2 谓词

谓词是指普通函数或**重载的operator()**返回值是bool类型的函数对象(仿函数)。如果operator()能接受一个函数,那就叫一元谓词,那么接受两个参数,就叫二元谓词。

10.4.3 内建函数对象

STL内建了一些函数对象。分为算法类函数对象,关系运算类函数对象,逻辑运算类仿函数。这些仿函数和函数对象,跟一般函数用法一样。使用内建函数对象时,需要引用头文件#include。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

plus<int> pl;   //这个是定义了一个对象,然后再调用仿函数
pl(10, 20);

int aa = plus<int>()(10, 20);   //申请一个匿名对象  然后调用函数对象
cout << "aa = " << aa << endl; 

10.5 函数对象适配器

函数对象适配器是完成一些配接工作,这些配接包括绑定(bind),否定(negate),以及对一般函数或成员函数的修饰,使其成为函数对象。

bind1st :将参数绑定为函数对象的第一个参数

bind2nd :将参数绑定为函数对象的第二个参数

not1 :对一元函数对象取反

not2 :对二元函数对象取反

ptr_fun :将普通函数修饰成函数对象

mem_fun :修饰成员函数

mem_fun_ref : 修饰成员函数

10.5.1 bind1st/bind2nd

我们先看原来for_each函数操作:

int main(int argc, char **argv)
{
    Person p1("lizi", 10, 1);
    Person p2("zhaoxi", 13, 2);
    Person p3("wangwu", 15, 3);
    Person p4("xixi", 16, 4);
    vector<Person> vp;
    vp.push_back(p1);
    vp.push_back(p2);
    vp.push_back(p3);
    vp.push_back(p4);


    for_each(vp.begin(), vp.end(), Person());

    return 0;
}

//Person仿函数
class Person
{
public:
   int age;
   int id;
   char *name;

   Person() {
       this->age = 0;
       this->id = 0;
       this->name = new char[1];
       strcpy(this->name, " ");
   }

   Person(char*name, int age, int id): age(age), id(id){
       this->name = new char[strlen(name) + 1];
       strcpy(this->name, name);
       //cout << "构造函数" << endl;
   }

   Person(const Person& p) {
       //cout << "拷贝构造函数 " << endl;
       this->age = p.age;
       this->id = p.id;
       this->name = new char[strlen(p.name) + 1];
       strcpy(this->name, p.name);
   }

   Person& operator=(const Person& p){
       //cout << "等号操作符 ===================" << endl;
       //1.防止自身赋值
       if (this == &p) {
           return *this;
       }

       //2.释放原来的内存
       if(this->name != NULL) {
           cout << "this->name有值" << endl;
           delete[] this->name;
           this->name = NULL;
           this->id = 0;
       }

       this->id = p.id;
       this->age = p.age;
       this->name = new char[strlen(p.name) + 1];
       strcpy(this->name, p.name);
       return *this;
   }

   ~Person() {
       //cout << "析构函数" << &(this->name) << endl;
       if(this->name != NULL){
           delete[] this->name;
           //this->name = NULL;
       }
   }

   bool operator()(Person& p){
       cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id << endl;
   }

   //set容器排序,需要用到<号,也可以像上面那个用仿函数
   bool operator<(const Person& anthor) const {
       return this->age > anthor.age;
   }
};

执行结果:
在这里插入图片描述
这样遍历就很正常了,但是如果我们需要让id添加任意数的时候,我们会修改()号操作符的函数:

bool operator()(Person& p, int id_base){
 cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
}

但是我们的for_each()函数

template <class _InIt, class _Fn>
_CONSTEXPR20 _Fn for_each(_InIt _First, _InIt _Last, _Fn _Func) { // perform function for each element [_First, _Last)
    _Adl_verify_range(_First, _Last);
    auto _UFirst      = _Get_unwrapped(_First);
    const auto _ULast = _Get_unwrapped(_Last);
    for (; _UFirst != _ULast; ++_UFirst) {
        _Func(*_UFirst);    //只能接受一个参数
    }

    return _Func;
}

通过看源码发现只能接受一个参数,这时候就要适配器了。

class Person : public binary_function<int, Person, bool>   //int int 是参数类型, bool是返回值
{
public:
    int age;
    int id;
    char *name;

    Person() {
        this->age = 0;
        this->id = 0;
        this->name = new char[1];
        strcpy(this->name, " ");
    }

    Person(char*name, int age, int id): age(age), id(id){
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
        //cout << "构造函数" << endl;
    }

    Person(const Person& p) {
        //cout << "拷贝构造函数 " << endl;
        this->age = p.age;
        this->id = p.id;
        this->name = new char[strlen(p.name) + 1];
        strcpy(this->name, p.name);
    }

    Person& operator=(const Person& p){
        //cout << "等号操作符 ===================" << endl;
        //1.防止自身赋值
        if (this == &p) {
            return *this;
        }

        //2.释放原来的内存
        if(this->name != NULL) {
            cout << "this->name有值" << endl;
            delete[] this->name;
            this->name = NULL;
            this->id = 0;
        }

        this->id = p.id;
        this->age = p.age;
        this->name = new char[strlen(p.name) + 1];
        strcpy(this->name, p.name);
        return *this;
    }

    ~Person() {
        //cout << "析构函数" << &(this->name) << endl;
        if(this->name != NULL){
            delete[] this->name;
            //this->name = NULL;
        }
    }

    bool operator()(Person& p, int id_base) const {
        cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
    }

    bool operator()(int id_base, Person& p) const {
        cout << "aa名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
    }

    // Person& operator+(int base) {
    //     //cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
    //     this->age += base;
    // }

    //set容器排序,需要用到<号,也可以像上面那个用仿函数
    bool operator<(const Person& anthor) const {
        return this->age > anthor.age;
    }
};


#include <functional>
#include <algorithm>
int main(int argc, char **argv)
{
    Person p1("lizi", 10, 1);
    Person p2("zhaoxi", 13, 2);
    Person p3("wangwu", 15, 3);
    Person p4("xixi", 16, 4);
    vector<Person> vp;
    vp.push_back(p1);
    vp.push_back(p2);
    vp.push_back(p3);
    vp.push_back(p4);


    for_each(vp.begin(), vp.end(), bind1st(Person(), 100));

    return 0;
}

感觉代码老是重复,下次不会了,下次值放差异部分,

//需要继承特定的类,才能调用适配器,后面的模板参数是,参数、参数、返回值
class Person : public binary_function<int, Person, bool> 
//这就是for_each使用了适配器的方法
//适配器就是把二元函数对象转变成一元函数对象。
for_each(vp.begin(), vp.end(), bind1st(Person(), 100));

bind1st bind2nd的区别就是:
bind1st 绑定为函数的第一个参数
bind2nd 绑定为函数的第二个参数

10.5.2 not1/not2

sort(vp.begin(), vp.end(), compare());       //这是原来的排序函数
sort(vp.begin(), vp.end(), not2(compare())); //这是在原理的排序函数基础上,反转排序

class compare : public binary_function<Person, Person, bool>{
public:
   bool operator()(const Person& p1, const Person& p2) const {   //记得要加const
       return p1.age > p2.age;
   }
};

两个函数执行结果:
在这里插入图片描述
在这里插入图片描述
是不是反转过来了,按年龄排序。

not1:对一元谓词取反
not2:对二元谓词取反

10.5.3 binary_function/unary_function

这两个不是仿函数,只是两个需要继承的类,看例子:

class compare2 : public unary_function<Person, bool>{   //函数参数为一个的时候
public:
    bool operator()(const Person& p1) const {   
        return p1.age > 5;
    }
};

vector<Person>::iterator it = find_if(vp.begin(), vp.end(), compare2());
if(it == vp.end()) {
      cout << "没有找到 " << endl;
  } else {
      cout << *it << endl;
  }

这个我们使用find_if算法,这个算法是查找第一个大于compare2里面的值,这个这个回调函数写死成一个参数,直接判断。
就是写成了一个参数之后,我们发现继承的类是unary_function,所以得出结论:
binary_function : 函数参数是两个的时候,继承这个类
unary_function : 函数参数是一个的时候,继承这个类

10.5.4 ptr_fun

ptr_fun : 把普通函数变成函数对象
例子:

bool show3(int id_base, int v) {
   cout << v << "  " << id_base << endl;
}

vector<int> ip;
ip.push_back(1);
ip.push_back(2);
ip.push_back(3);
ip.push_back(4);

for_each(ip.begin(), ip.end(), bind1st(ptr_fun(show3), 100));

for_each其实可以直接接收普通函数,但是当普通函数需要,添加参数的时候,bind1st是不能普通函数的,只能用函数对象,所以需要转换一下,有一个疑问,就是自定义类的时候,老是有问题,不知道什么原因。

10.5.5 mem_fun/mem_fun_ref

mem_fun/mem_fun_ref : 使用成员函数打印,例子:

class Person{
void show() {    //person添加新的函数
   cout << "show 名字 : " << this->name << " 年龄 : " << this->age << " id :" << this->id << endl;
}
}

vector<Person> vp;
vp.push_back(p1);
vp.push_back(p2);
vp.push_back(p3);
vp.push_back(p4);
for_each(vp.begin(), vp.end(), mem_fun_ref(&Person::show));

这个是调用成员方法,运行结果:
在这里插入图片描述
还有另一个:

vector<Person*> vpp;
vpp.push_back(&p1);
vpp.push_back(&p2);
vpp.push_back(&p3);
vpp.push_back(&p4);
cout << "======================" << endl;
for_each(vpp.begin(), vpp.end(), mem_fun(&Person::show));

这个是存放指针的,运行结果:
在这里插入图片描述
也是可以打印的,由上面的例子总结:
如果存放的是对象的指针,使用mem_fun
如果存放的是对象,使用mem_fun_ref

这一篇写的不好,有点乱,知识点比较多,并且零散,导致总结不到位,代码重复很多,如果都贴出来,逻辑比较清晰,但是占用的字数太多,所以就贴了一点差异的,整体代码现在贴在下面,看着整体的代码会比较清晰一点。

class Person : public binary_function<int, Person, bool>   //int int 是参数类型, bool是返回值
{
 public:
     int age;
     int id;
     char *name;

     Person() {
         this->age = 0;
         this->id = 0;
         this->name = new char[1];
         strcpy(this->name, " ");
     }

     Person(char*name, int age, int id): age(age), id(id){
         this->name = new char[strlen(name) + 1];
         strcpy(this->name, name);
         //cout << "构造函数" << endl;
     }

     Person(const Person& p) {
         //cout << "拷贝构造函数 " << endl;
         this->age = p.age;
         this->id = p.id;
         this->name = new char[strlen(p.name) + 1];
         strcpy(this->name, p.name);
     }

     Person& operator=(const Person& p) {     //这个const引发的血案
         //cout << "等号操作符 ===================" << endl;
         //1.防止自身赋值
         if (this == &p) {
             return *this;
         }

         //2.释放原来的内存
         if(this->name != NULL) {
             //cout << "this->name有值" << endl;
             delete[] this->name;
             this->name = NULL;
             this->id = 0;
         }

         this->id = p.id;
         this->age = p.age;
         this->name = new char[strlen(p.name) + 1];
         strcpy(this->name, p.name);
         return *this;
     }

     ~Person() {
         //cout << "析构函数" << &(this->name) << endl;
         if(this->name != NULL){
             delete[] this->name;
             //this->name = NULL;
         }
     }

     void show() {
         cout << "show 名字 : " << this->name << " 年龄 : " << this->age << " id :" << this->id << endl;
     }

     // bool operator()(Person& p, int id_base) const {
     //     cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
     // }

     bool operator()(int id_base, Person& p) const {
         cout << "aa名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
     }

     friend ostream& operator<<(ostream& os, const Person& p);

     // Person& operator+(int base) {
     //     //cout << "名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
     //     this->age += base;
     // }

     //set容器排序,需要用到<号,也可以像上面那个用仿函数
     bool operator<(const Person& anthor) const {   //sort排序  是使用这个判断的
         return this->age < anthor.age;
     }
 };

 //用这种也可以
 class compare : public binary_function<Person, Person, bool>{
 public:
     bool operator()(const Person& p1, const Person& p2) const {   //记得要加const
         return p1.age > p2.age;
     }
 };

 class compare2 : public unary_function<Person, bool>{   //函数参数为一个的时候
 public:
     bool operator()(const Person& p1) const {   
         return p1.age > 5;
     }
 };

 ostream& operator<<(ostream& os, const Person& p) {
     os << "aa名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id << endl;
     return os;
 }

 void show(const Person& p) {
     cout << "aa名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id  << endl;
 }

 bool show2(int id_base, Person& p) {
     cout << "ab名字 : " << p.name << " 年龄 : " << p.age << " id :" << p.id + id_base << endl;
 }

 bool show3(int id_base, int v) {
     cout << v << "  " << id_base << endl;
 }

 int main(int argc, char **argv)
 {
     Person p1("lizi", 66, 1);
     Person p2("zhaoxi", 13, 2);
     Person p3("wangwu", 17, 3);
     Person p4("xixi", 4, 4);
     vector<Person> vp;
     vp.push_back(p1);
     vp.push_back(p2);
     vp.push_back(p3);
     vp.push_back(p4);

     vector<int> ip;
     ip.push_back(1);
     ip.push_back(2);
     ip.push_back(3);
     ip.push_back(4);

     for_each(vp.begin(), vp.end(), bind1st(Person(), 100));
     //sort(vp.begin(), vp.end(), not2(compare()));         //内部执行了等号操作符的重载
     sort(vp.begin(), vp.end(), compare()); 
     cout << "======================" << endl;
     for_each(vp.begin(), vp.end(), bind1st(Person(), 100));

     cout << "======================" << endl;
     vector<Person>::iterator it = find_if(vp.begin(), vp.end(), compare2());
     if(it == vp.end()) {
         cout << "没有找到 " << endl;
     } else {
         cout << *it << endl;
     }

     for_each(vp.begin(), vp.end(), show);   //普通函数也可以作为回调函数
     cout << "======================" << endl;
     //for_each(vp.begin(), vp.end(), bind1st(ptr_fun(show2), 100));    //不知道类的时候,为什么有问题
     for_each(ip.begin(), ip.end(), bind1st(ptr_fun(show3), 100));

     cout << "======================" << endl;
     for_each(vp.begin(), vp.end(), mem_fun_ref(&Person::show));


     vector<Person*> vpp;
     vpp.push_back(&p1);
     vpp.push_back(&p2);
     vpp.push_back(&p3);
     vpp.push_back(&p4);
     cout << "======================" << endl;
     for_each(vpp.begin(), vpp.end(), mem_fun(&Person::show));

     return 0;
 }
发布了32 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/C1033177205/article/details/104139876