标题:关于set容器存自定义类型数据的疑问
只看楼主
lllyyy3
Rank: 2
等 级:论坛游民
威 望:1
帖 子:24
专家分:20
注 册:2022-10-15
结帖率:100%
已结贴  问题点数:20 回复次数:7 
关于set容器存自定义类型数据的疑问
下面是set容器里,存放自定义类型MyInt的代码,这个类里只有一个int类型的参数。
写这个代码的目的是为了试set容器调用insert()方法存自定义类型数据的过程。
程序代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <set>
using namespace std;

//这个是存入set的对象
class MyInt{
public:
    MyInt(int arg){
        this->arg = arg;
        cout << "构造" << endl;
    }
    MyInt(const MyInt& obj) {//必须加const
        this->arg = obj.arg;
        cout <<"拷贝构造" << endl;
    }
    ~MyInt(){
        cout << "析构" << endl;
    }
public:
    friend ostream& operator<<(ostream& out, MyInt s);
public:
    int    arg;
};
//重载<<运算符
ostream& operator<<(ostream& out, MyInt obj){
    out << obj.arg << endl;
    return out;
}
//
struct IntFunc {
    //重载()
    bool operator()(const MyInt& obj1, const MyInt& obj2) const{
        if (obj1.arg < obj2.arg){
            cout<< "重载()"<<endl; 
            return true;
        }
        else{
            return false;
        }
    }
};

void FuncTest(){
    set<MyInt, IntFunc> s;
    
    MyInt num1(5);
    MyInt num2(2);

    s.insert(num1);
    s.insert(num2); 
    set<MyInt, IntFunc>::iterator it = s.begin(); 
    for (; it != s.end(); it++){
        cout << *it;
    }
}
int main()
{
    FuncTest();
    return 0;
}


下面是上面代码的运行结果:
程序代码:
运行结果:
构造         //创建num1对象调用构造函数
构造         //创建num2对象调用构造函数
拷贝构造     //insert()是先创建临时对象,然后把临时对象拷贝到容器里,调用拷贝构造函数
重载()       //我都困惑是,(1)上一步不都已经调用拷贝构造把临时对象拷贝到容器里了吗?为什么又调用重载()了?这两个的调用顺序为什么反了?
重载()       //(2)为什么这里又是先调用重载(),然后调用拷贝构造把临时对象拷贝到容器里?
拷贝构造
拷贝构造     //(3)为什么用for输出set里对象还要调用MyInt的拷贝构造?感觉就是调用也应该调用set的迭代器的拷贝构造呀!
2
析构
拷贝构造    //(4)为什么用for输出的时候还调用了2次?直接迭代器++,然后迭代器访问值不就行了,为什么还要再次调用拷贝构造?
5
析构
析构
析构
析构
析构

上面4个问题一直想不明白,希望大佬能帮助我解答一下,看看我的问题在哪里,帮我指点迷津。谢谢了!
搜索更多相关主题的帖子: set arg 调用 拷贝 构造 
2022-10-25 15:01
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:0 
拷贝构造     //insert()是先创建临时对象,然后把临时对象拷贝到容器里,调用拷贝构造函数
重载()       //我都困惑是,(1)上一步不都已经调用拷贝构造把临时对象拷贝到容器里了吗?为什么又调用重载()了?这两个的调用顺序为什么反了?
重载()       //(2)为什么这里又是先调用重载(),然后调用拷贝构造把临时对象拷贝到容器里?
拷贝构造
两个“重载()”都是在执行 s.insert(num2) 时发生的,之所以需要调用多次(也可能一次)是为了保证“严格弱序”(毕竟用户提供的Compare可以在逻辑上瞎搞,既让a在b之前,又让b在a之前)

拷贝构造     //(3)为什么用for输出set里对象还要调用MyInt的拷贝构造?感觉就是调用也应该调用set的迭代器的拷贝构造呀!
2
析构
拷贝构造    //(4)为什么用for输出的时候还调用了2次?直接迭代器++,然后迭代器访问值不就行了,为什么还要再次调用拷贝构造?
5
析构

因为你写的是
friend ostream& operator<<(ostream& out, MyInt s);
ostream& operator<<(ostream& out, MyInt obj){
还是改为正常的 const MyInt& 吧
2022-10-25 15:30
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:20 
我有多个建议,懒得讲了,因此我将你的代码用C++重写一下,我想表达的内容都在代码中

程序代码:
#include <iostream>
#include <compare>

class MyInt
{
public:
    explicit MyInt( int arg ) noexcept : arg_(arg) {
        std::clog << "构造\n";
    }
    MyInt( const MyInt& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "拷贝构造\n";
    }
    MyInt( MyInt&& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "移动构造\n";
    }
    ~MyInt() noexcept {
        std::clog << "析构\n";
    }

    auto operator<=>( const MyInt& rhs ) const noexcept
    {
        std::clog << "比较\n";
        return arg_ <=> rhs.arg_;
    }

private:
    int arg_;

    friend std::ostream& operator<<( std::ostream& out, const MyInt& obj )
    {
        return out << obj.arg_;
    }
};

#include <set>
using namespace std;

int main( void )
{
    {
        set<MyInt> s;
        s.emplace( 5 );
        s.emplace( 2 );

        for( const auto& e : s )
            cout << e << endl;
    }
}


输出
构造
构造
比较
比较
比较
2
5
析构
析构
2022-10-25 15:55
lllyyy3
Rank: 2
等 级:论坛游民
威 望:1
帖 子:24
专家分:20
注 册:2022-10-15
得分:0 
回复 3楼 rjsp
哈哈哈哈哈 刚刚查了一下什么是严格弱序,大概懂了是什么意思,我还正在组织语言问新问题,没想到又有新回复了。
那我先看看新回复,看能不能理解你的意思,再看看是不是刚好是我要问的问题
2022-10-25 16:19
lllyyy3
Rank: 2
等 级:论坛游民
威 望:1
帖 子:24
专家分:20
注 册:2022-10-15
得分:0 
以下是引用rjsp在2022-10-25 15:55:20的发言:

我有多个建议,懒得讲了,因此我将你的代码用C++重写一下,我想表达的内容都在代码中

#include <iostream>
#include <compare>

class MyInt
{
public:
    explicit MyInt( int arg ) noexcept : arg_(arg) {
        std::clog << "构造\n";
    }
    MyInt( const MyInt& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "拷贝构造\n";
    }
    MyInt( MyInt&& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "移动构造\n";
    }
    ~MyInt() noexcept {
        std::clog << "析构\n";
    }

    auto operator<=>( const MyInt& rhs ) const noexcept
    {
        std::clog << "比较\n";
        return arg_ <=> rhs.arg_;
    }

private:
    int arg_;

    friend std::ostream& operator<<( std::ostream& out, const MyInt& obj )
    {
        return out << obj.arg_;
    }
};

#include <set>
using namespace std;

int main( void )
{
    {
        set<MyInt> s;
        s.emplace( 5 );
        s.emplace( 2 );

        for( const auto& e : s )
            cout << e << endl;
    }
}

输出

c++11都没搞定,看这个代码好累
代码想表达的建议是不是:
(1)、用operator<=>取代专门写一个仿函数当传入set
(2)、用emplace代替insert
(3)、用for的新写法代替写迭代器再访问输出

另外,用了这个operator<=>来写比较,为什么还是不能一次比较出来,还要执行3次比较?这个写法不是遵循了c++要求的严格弱序了吗?

我试着再加一个s.emplace( 1 );会多出现4次比较,
再加一个s.emplace( 0 );继续多出现4次比较(为什么不是5次),
再加一个s.emplace( -1 );继续多出现5次比较
再加一个s.emplace( 9 );又只多出现了4次比较,
想问一下r老师,这个比较出现的次数是怎么回事?我要学什么内容才能理解这个地方?是c++20的新特性吗?
2022-10-25 17:20
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:0 
(1)、用operator<=>取代专门写一个仿函数当传入set
如果是作为有序容器的比较器,那只要重载 “operator <” 就行了

用了这个operator<=>来写比较,为什么还是不能一次比较出来,还要执行3次比较?
operator<=> 只是将各种比较的实现代码放在一起而已,并不能保证程序员写的代码就一定符合认知逻辑。
“还要执行3次比较?”--- 这就要看你所用std::set的实现了,但一般而言,两个对象a和b相比较,至多只需要执行两次,确保 a<b 与 b<a 不同时成立

我试着再加一个s.emplace( 1 );会多出现4次比较,
再加一个s.emplace( 0 );继续多出现4次比较(为什么不是5次),
再加一个s.emplace( -1 );继续多出现5次比较
再加一个s.emplace( 9 );又只多出现了4次比较,
想问一下r老师,这个比较出现的次数是怎么回事?我要学什么内容才能理解这个地方?是c++20的新特性吗?
std::set使用的是红黑树,你去看红黑树的介绍就行了。与 C++20 甚至 C++ 无关。
2022-10-26 08:42
rjsp
Rank: 20Rank: 20Rank: 20Rank: 20Rank: 20
等 级:版主
威 望:507
帖 子:8890
专家分:53117
注 册:2011-1-18
得分:0 
我将输出信息改得更详细一些
程序代码:
#include <iostream>

class MyInt
{
public:
    explicit MyInt( int arg ) noexcept : arg_(arg) {
        std::clog << "构造 MyInt(" << arg_ << ")\n";
    }
    MyInt( const MyInt& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "拷贝构造 MyInt(" << arg_ << ")\n";
    }
    MyInt( MyInt&& obj ) noexcept : arg_(obj.arg_) {
        std::clog << "移动构造 MyInt(" << arg_ << ")\n";
    }
    ~MyInt() noexcept {
        std::clog << "析构 MyInt(" << arg_ << ")\n";
    }

    bool operator<( const MyInt& rhs ) const noexcept
    {
        std::clog << "MyInt(" << arg_ << ") < MyInt(" << rhs.arg_ << ") ? " << (arg_<rhs.arg_?"true":"false") << '\n';
        return arg_ < rhs.arg_;
    }

private:
    int arg_;

    friend std::ostream& operator<<( std::ostream& out, const MyInt& obj )
    {
        return out << obj.arg_;
    }
};

#include <set>
using namespace std;

int main( void )
{
    {
        set<MyInt> s;
        s.emplace( 5 );
        s.emplace( 2 );
        s.emplace( 1 );
        s.emplace( 0 );
        s.emplace( -1 );
        s.emplace( 9 );

        for( const auto& e : s )
            cout << e << endl;
    }
}


一种可能的(因为C++并不规定具体实现)输出是
构造 MyInt(5)
构造 MyInt(2)
MyInt(5) < MyInt(2) ? false
MyInt(2) < MyInt(5) ? true
构造 MyInt(1)
MyInt(5) < MyInt(1) ? false
MyInt(2) < MyInt(1) ? false
MyInt(1) < MyInt(2) ? true
构造 MyInt(0)
MyInt(2) < MyInt(0) ? false
MyInt(1) < MyInt(0) ? false
MyInt(0) < MyInt(1) ? true
构造 MyInt(-1)
MyInt(2) < MyInt(-1) ? false
MyInt(1) < MyInt(-1) ? false
MyInt(0) < MyInt(-1) ? false
MyInt(-1) < MyInt(0) ? true
构造 MyInt(9)
MyInt(2) < MyInt(9) ? true
MyInt(5) < MyInt(9) ? true
-1
0
1
2
5
9
析构 MyInt(9)
析构 MyInt(5)
析构 MyInt(2)
析构 MyInt(1)
析构 MyInt(0)
析构 MyInt(-1)
2022-10-26 09:07
lllyyy3
Rank: 2
等 级:论坛游民
威 望:1
帖 子:24
专家分:20
注 册:2022-10-15
得分:0 
以下是引用rjsp在2022-10-26 08:42:46的发言:

如果是作为有序容器的比较器,那只要重载 “operator <” 就行了

operator<=> 只是将各种比较的实现代码放在一起而已,并不能保证程序员写的代码就一定符合认知逻辑。
“还要执行3次比较?”--- 这就要看你所用std::set的实现了,但一般而言,两个对象a和b相比较,至多只需要执行两次,确保 a<b 与 b<a 不同时成立

std::set使用的是红黑树,你去看红黑树的介绍就行了。与 C++20 甚至 C++ 无关。

谢谢r老师,明白了。
2022-10-26 10:47



参与讨论请移步原网站贴子:https://bbs.bccn.net/thread-510435-1-1.html




关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.024478 second(s), 7 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved