隐式this指针及C++类中六个默认成员函数

本文详细解析了C++中隐式指针的概念及其应用场景,特别是通过拷贝构造函数的实例深入浅出地介绍了this指针的作用。此外,还全面介绍了C++默认提供的六个成员函数,并重点探讨了运算符重载的方法和技术细节。

隐式指针->this指针

什么叫隐式指针?
它其实就是在对象实例化以后,在传参的时候默认传过去的一个对象的地址
怎么理解这句话呢?
比如:我们的拷贝构造函数(不知到拷贝构造函数是干什么的可以往下看)

class Date
{
public:
    // 构造函数
    Date(int year = 1970, int month = 1, int day = 1) 
        :_year(year), _month(month), _day(day){}
    // 这里就不做日期判断

    // 拷贝构造函数
    // 这里有隐含this指针
    // 这里用const修饰是为了不希望在拷贝构造的时候修改我原有的值
    Date(const Date& d)     
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    // 析构函数
    ~Date();

private:
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date d;
    Date d1(d);
    return 0;
}

上面的拷贝构造函数,当d1对象拷贝d对象时候,本应该传入两个参数,但只有一个,这里就有一个隐式的this指针 ,所以在拷贝函数里面也有两个形参

Date(const Date& d) -》  Date(Date *this, const Date& d)
Date d1(d); -》  Date d1(&d1, d);

理解这个再理解运算符重载就很简单了。

C++中默认六个成员函数

1. 构造函数

2. 拷贝构造函数

3. 析构函数

4. 赋值操作符重载

5. 取地址操作符重载

6. const修饰的取地址操作符重载

构造函数
构造函数分为有参和无参,是用来初始化类中的成员变量,所以在实例化一个对象时,就自动调用构造函数进行初始化。
构造函数的特征:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象在实例化过程中自动调用
  4. 构造函数可以重载
  5. 如果没有构造函数,系统会自动添加一个无参数的构造函数
  6. 无参构造函数和全缺省的构造函数否认为是缺省构造函数,并且缺省的构造函数只能有一个。

拷贝构造函数
这是用来对需要创建同类对象时进行初始化用的。比如有个对象d1,我们需要有一个和d1一样参数的对象,这时我们就需要用拷贝构造函数。
特征:

  1. 拷贝构造可以说是构造函数的一个重载
  2. 拷贝构造时必须要用引用传参,如果用传值方式会发生无穷递归调用
  3. 如果为定义,系统会自动生成一个缺省的拷贝构造。

析构函数
析构函数是在对象生命周期结束时,系统会调用它来对对象进行一些清理工作
析构函数不能重载
析构函数无返回值
如果未定义,系统会自动生成缺省的析构函数

*运算符重载
运算符重载的特征:
poerator+合法运算符 构成运算符重载
注意:重载后不能改变运算符的优先级,结合性,操作数个数。

有五个运算符不能重载
.* / :: / sizeof / ?: / .

赋值运算符重载
这里要把赋值运算符重载和拷贝构造比较记忆
拷贝构造是创建对象时候,使用一个已有对象来为这个初始化
赋值运算的重载是对一个已经存在的对象进行拷贝赋值

到这里以日期类举例:

#include<iostream>
#include<windows.h>
#include<assert.h>
using namespace std;

class Date
{
public:
    // 默认六大基本函数

    // 构造函数>>你可以重载
    // Date(); // 不能和全缺省的构造函数共存
    Date(int, int, int);
    bool IsLeapYear(int); // 判断是否是闰年
    int GetMonthDay(int, int); // 得到该月的天数

    // 拷贝构造函数
    // 这里有隐含this指针
    Date(const Date& d) // 这里用const修饰是为了不希望在拷贝构造的时候修改我原有的值
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    // 赋值操作符重载
    // d1 = d2;函数栈帧结束,变量未释放&
    // 不加Date&中的&,会多调用一次拷贝构造
    Date& operator=(const Date& d) // 这个就相当于 Date& operator=(Date *this, const Date&d)
    {
    // 这里是如过要拷贝的对象是自己本身那么就不用拷贝直接返回
        if (this == &d) 
        {
            return *this;
        }
        // 这里有隐含的this指针
        _year = d._year;
        _month = d._month;
        _day = d._day;
        return *this;
    }


    ////////////////////////////
    // 运算符重载
    // 判断是否相等。
    bool operator==(const Date& d)
    {
        if (_year == d._year && _month == d._month && _day == d._day)
        {
            return true;
        }
        return false;
    }

    bool operator!=(const Date& d)
    {
        if (!(*this == d))
        {
            return true;
        }
        return false;
    }

    bool operator>(const Date& d)
    {
        if (_year > d._year)
        {
            return true;
        }
        else if (_month > d._month)
        {
            return true;
        }
        else if (_day >d._day)
        {
            return true;
        }
        return false;
    }

    bool operator>=(const Date& d)
    {
        if (*this == d || *this > d)
        {
            return true;
        }
        return false;
    }

    bool operator<(const Date& d)
    {
        if (!(*this >= d))
        {
            return true;
        }
        return false;
    }

    bool operator<=(const Date& d)
    {
        if (*this < d || *this == d)
        {
            return true;
        }
        return false;
    }

    // 日期加上一个天数,d1 + 10;
    // 不改变原来的d1,所以不能用引用
    Date operator+(int day)
    {
        Date ret(*this);
        if (day < 0)
        {
            ret -=(-day);
            return ret;
        }
        ret._day += day;
        while (GetMonthDay(ret._year, ret._month) < ret._day)
        {
            ret._day -= GetMonthDay(ret._year, ret._month);

            if (ret._month == 12)
            {
                ret._month = 1;
                ++ret._year;
            }
            else
            {
                ++ret._month;
            }
        }
        return ret;
    }

    // 加等d1 = d1 + 10;
    // 可以用引用,出了作用于变量还在
    Date& operator+=(int day)
    {
        *this = *this + day;
        return *this;
    }

    // 前置++和后置++
    // 前置++
    Date& operator++()
    {
        *this = *this + 1;
        return *this;
    }
    // 后置++
    Date operator++(int)// 用这样的方式来区分
    {
        Date ret(*this);
        *this = *this + 1;
        return ret;
    }

    // 日期减上天数
    // 不能用引用
    Date operator-(int day)
    {
        Date ret(*this);
        if (day < 0)
        {
            ret += (-day);
            return ret;
        }
        ret._day -= day;
        while (ret._day <= 0)
        {
            if (ret._month == 1)
            {
                --ret._year;
                ret._month = 12;
            }
            else
            {
                --ret._month;
            }
            // 因为前面已经相减为负数,所以要相加
            ret._day += GetMonthDay(ret._year, ret._month);
        }
        return ret;
    }

    // 前置--和后置--
    // 前置--
    Date& operator--()
    {
        *this = *this - 1;
        return *this;
    }

    // 后置--
    Date operator--(int)
    {
        Date ret(*this);
        *this = *this - 1;
        return ret;
    }

    Date& operator-=(int day)
    {
        *this = *this - day;
        return *this;
    }

    // 日期减去日期,求中间所差几天
    int operator-(const Date& d)
    {
        int count = 0;

        if (*this < d)
        {
            Date tmp(*this);
            while (tmp != d)
            {
                ++count;
                ++tmp;
            }
        }
        else
        {
            Date tmp(d);
            while (tmp != *this)
            {
                ++count;
                ++tmp;
            }
        }
        return count;
    }

    // 以上就是运算符重载
    /////////////////////////////
    void Print();

    // 析构函数
    ~Date();

private:
    int _year;
    int _month;
    int _day;
};

// 无参数构造函数 >> 并且你不能和全缺省构造参数一块出现
// Date::Date(){}

// 全缺省构造函数
Date::Date(int year = 1970, int month = 1, int day = 1) :_year(year), _month(month), _day(day)
{
    if (!(year > 0 && month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year, month)))
    {
        assert(false);
    }
    /*_year = year;
    _month = month;
    _day = day;*/
}

bool Date::IsLeapYear(int year)
{
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
    {
        return true;
    }
    return false;
}

int Date::GetMonthDay(int year, int month)
{
    int days[13] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    int day = days[month];
    if (day == 2 && IsLeapYear(year))
    {
        day = 29;
    }
    return day;
}

void Date::Print()
{
    cout << _year << "-" << _month << "-" << _day << endl;
}

// 你不能重载
Date::~Date(){}


int main()
{
    Date d;
    d.Print();
    Date d1(2017, 2, 20);
    d1.Print();
    Date d2(2017, 2, 29);
    d2.Print();
    cout << d1.operator-(d2) << endl;
    Date d3;
    d3 = d2;
    d3.operator+= (-100);
    d3.Print();
    system("pause");
    return 0;
}

取地址操作符重载
这个重载和const修饰的取地址操作符重载相比前面四种默认函数,用的频率不是很高,一般系统生成的默认函数就够用了,但是这还是得了解。
就用日期类来说:Date *operator&(){} 这就是&重载。
const修饰的取地址操作符重载
先举例子
const Date *operator() const
如果了解const,就知道怎么用了,如果不了解,继续往下看。

最后两个默认成员函数后续继续更新

<think> 我们面临三个主要问题: 1. Q_OBJECT失效(导致信号槽无法使用) 2. 非静态成员无法声明为inline(编译器报错) 3. QRunnable默认构造函数不能为constexpr(编译器报错) 解决方案: 一、解决Q_OBJECT失效问题 原因:通常是由于没有正确继承QObject或宏位置不正确。 观察代码:MainWindow已经继承了QObject(通过QMainWindow间接继承),并且Q_OBJECT宏在中声明开头。 但注意:MainWindow定义中,Q_OBJECT后面有一个分号,这是错误的,应该去掉分号。 修改前: Q_OBJECT; 修改后: Q_OBJECT 二、非静态成员无法声明为inline 问题:在定义中,成员函数的定义如果写在体内,则默认为内联函数(inline)。但是,如果该成员函数使用了Q_OBJECT宏(即信号、槽等),则不能同时为内联函数。 观察代码:我们发现很多成员函数的声明在体内,但定义并没有在体内(在体外定义),所以不存在这个问题。 但是,我们注意到有一个成员函数`setCheckboxState`的定义在体内(在头文件中定义),而且它没有使用信号槽,所以可以保留。但是,如果其他函数在体内定义且使用了信号槽,则应该移到体外。 然而,我们注意到代码中有大量的静态成员变量和静态成员函数。对于静态成员变量,它们在中声明,但必须在体外定义(分配存储空间)。例如: static HANDLE hProcess; 需要在外定义:HANDLE MainWindow::hProcess = nullptr; 三、QRunnable默认构造函数不能为constexpr 问题:我们自定义的DoubleClick、SearchTask、HeQiuTask都继承了QRunnable,并且没有显定义构造函数。编译器会生成一个默认构造函数,但这个默认构造函数不是constexpr的,而QRunnable要求其子必须有可调用的构造函数。 但是,实际上QRunnable并没有要求构造函数为constexpr。这个错误可能是由于其他原因引起的。 然而,我们注意到这些自定义任务同时继承了QMainWindow和MainWindow,这样的多重继承可能是不合适的。因为: - QMainWindow是窗口,而QRunnable是用于线程池的任务,不应该有窗口特性。 - 同时继承MainWindow(我们的主窗口)会导致任务包含了整个主窗口的实例,这可能是非常大的,而且也不合理。 因此,我们需要重新设计任务: 任务应该只继承QRunnable,并且通过构造函数传入主窗口的指针(如果需要访问主窗口的数据)。 修改示例(以DoubleClick为例): class DoubleClick : public QRunnable { public: DoubleClick(MainWindow* mainWindow) : mainWindow(mainWindow) { } void run() override; private: MainWindow* mainWindow; // 指向主窗口的指针,用于访问数据 }; 同理,修改SearchTask和HeQiuTask。 四、其他问题修正 1. 静态成员变量初始化 在声明中,静态成员变量只是声明,需要在外定义(在cpp文件中): HANDLE MainWindow::hProcess = nullptr; // 其他静态成员变量同理 2. 避免在头文件中定义成员函数(除非是内联需要) 将体内实现的成员函数(除了简单的get/set)移到cpp文件中定义。 3. 头文件保护 使用`#pragma once`是可行的,但确保所有头文件都有保护。 4. 的前向声明 在头文件中,如果只需要指针或引用,可以使用前向声明,减少头文件依赖。 5. 在MainWindow中,我们注意到有很多静态成员变量和静态成员函数。如果这些静态成员不需要被外的函数访问,应该改为私有。 五、具体修改步骤 步骤1:修改MainWindow,去掉Q_OBJECT后面的分号。 步骤2:将DoubleClick、SearchTask、HeQiuTask改为只继承QRunnable,并持有MainWindow指针。 步骤3:在cpp文件中定义MainWindow的静态成员变量。 步骤4:将MainWindow体内定义的成员函数`setCheckboxState`移到cpp文件中(如果合适,也可以保留为内联,但考虑到代码清晰,建议移到cpp文件)。 注意:由于静态成员变量在多个地方使用,我们需要在cpp文件中初始化它们。 例如,在MainWindow.cpp中: HANDLE MainWindow::hProcess = nullptr; std::string MainWindow::mnq = ""; unsigned long long MainWindow::nianheAdress = 0; // ... 其他静态成员变量 步骤5:修改任务的使用方。 在创建任务时,传递this指针(MainWindow实例)给任务对象。 示例: DoubleClick *doubleClickTask = new DoubleClick(this); threadPool->start(doubleClickTask); 步骤6:解决循环依赖问题。 由于任务中包含了MainWindow指针,而MainWindow头文件又包含了任务的声明,可能会引起循环依赖。因此,我们将任务的声明移到MainWindow的前面(在MainWindow.h中),或者将任务的声明和定义分开。 考虑到任务比较简单,我们可以在MainWindow.h中,在MainWindow之前声明任务,然后定义MainWindow,再定义任务。 调整顺序: // 前向声明MainWindow class MainWindow; // 声明任务 class DoubleClick : public QRunnable { // ... 使用MainWindow* }; // 然后定义MainWindow 但是,这样会导致任务中无法访问MainWindow的私有成员。如果需要访问私有成员,可以将任务声明为MainWindow的友元,或者通过公有接口访问。 考虑到任务需要访问MainWindow的私有数据,我们可以将任务作为MainWindow的内部。这样,任务就可以访问MainWindow的私有成员。 另一种做法:将任务定义在MainWindow内部。 示例: class MainWindow : public QMainWindow { Q_OBJECT public: // ... 其他 class DoubleClick : public QRunnable { public: DoubleClick(MainWindow* mainWindow) : mainWindow(mainWindow) {} void run() override; private: MainWindow* mainWindow; }; }; 这样,DoubleClick就是MainWindow的内部,可以访问MainWindow的私有成员。 根据需求选择。 六、代码结构调整示例 由于修改量较大,这里给出部分调整后的代码: MainWindow.h: ```cpp #pragma once #include <QObject> #include <QMainWindow> #include "ui_MainWindow.h" #include <QByteArray> #include <windows.h> #include <tlhelp32.h> #include <vector> #include <QThread> #include <QRunnable> #include <QThreadPool> #include <QMutex> #include "AndroidTool.h" using namespace std; // 前向声明 class MainWindow; // 任务基?或者单独声明每个任务 // 由于任务需要访问MainWindow的私有成员,我们改为内部,所以先不在这里声明 class MainWindow : public QMainWindow { Q_OBJECT // 去掉分号 public: MainWindow(QWidget* parent = nullptr); ~MainWindow(); // ... 其他成员函数声明 // 内部任务 class DoubleClick : public QRunnable { public: DoubleClick(MainWindow* mainWindow) : mainWindow(mainWindow) {} void run() override; private: MainWindow* mainWindow; }; class SearchTask : public QRunnable { public: SearchTask(MainWindow* mainWindow) : mainWindow(mainWindow) {} void run() override; signals: void searchFinished(); // 注意:QRunnable不能直接使用信号,需要继承QObject才行,这里需要调整 private: MainWindow* mainWindow; }; // 解决SearchTask的信号问题:由于QRunnable不是QObject,不能有信号,因此我们可能需要另一种方通知完成。 // 可以将SearchTask改为继承QObject和QRunnable(多重继承),并添加Q_OBJECT宏。但是,这样就需要在堆上创建,并且注意线程亲和性。 // 或者,我们可以使用QMetaObject::invokeMethod或QTimer::singleShot在主线程执行回调。 // 鉴于时间,我们暂时不实现SearchTask的信号,而是通过其他方(例如设置标志位)来通知。 class HeQiuTask : public QRunnable { public: HeQiuTask(MainWindow* mainWindow) : mainWindow(mainWindow) {} void run() override; private: MainWindow* mainWindow; }; public slots: void treewidget_itemClicked(); //点击不同的items,索引stackedwidget // ... 其他槽函数 public: // ... 其他成员 private: Ui::MainWindow ui; QThreadPool* threadPool; AndroidTool tool; // Android工具实例 static HHOOK WheelHookID; //滚轮hook句柄 static MainWindow* instance; // 静态实例指针 // 不再需要静态的searchTask,因为每次搜索可以创建一个新的SearchTask }; ``` 注意:SearchTask中的信号需要调整,因为QRunnable本身不是QObject。我们可以让SearchTask同时继承QObject和QRunnable,并添加Q_OBJECT宏。但是,这样就需要使用Qt的信号槽机制,并且注意对象线程亲和性。 修改后的SearchTask: ```cpp class SearchTask : public QObject, public QRunnable { Q_OBJECT public: SearchTask(MainWindow* mainWindow) : mainWindow(mainWindow) {} void run() override; signals: void searchFinished(); private: MainWindow* mainWindow; }; ``` 然后在创建SearchTask对象时,注意要指定父对象或连接信号槽,并确保在主线程中连接信号(因为跨线程需要队列连接)。 但是,由于QRunnable是在线程池中运行,而QObject需要在同一个线程中创建和销毁,所以要小心管理生命周期。我们可以不设置父对象,并在任务完成后删除该对象(通过设置自动删除)。 在MainWindow中连接信号: SearchTask *task = new SearchTask(this); connect(task, &SearchTask::searchFinished, this, &MainWindow::handleSearchFinished); task->setAutoDelete(true); // 运行结束后自动删除 threadPool->start(task); 但是,注意:由于SearchTask对象是在主线程创建的,所以信号槽连接是自动的队列连接,因此信号会在主线程触发。 七、总结 修改点: 1. 去掉Q_OBJECT后面的分号。 2. 将任务改为MainWindow的内部,并只继承QRunnable(或者同时继承QObject,如果需要信号槽)。 3. 对于需要信号槽的任务(如SearchTask),则多重继承QObject和QRunnable,并添加Q_OBJECT宏。 4. 在cpp文件中初始化MainWindow的静态成员变量。 5. 将体内定义的成员函数(如setCheckboxState)移到cpp文件中(可选,如果函数体较长建议移动)。 由于代码量较大,请按照上述步骤逐步修改。 如果遇到其他问题,请提供具体的错误信息。 §§相关问题§§ 1. 如何在Qt中正确使用信号槽机制? 2. 如何在QRunnable任务中安全地更新UI? 3. 静态成员变量在中如何初始化? </think> ### 解决代码中三大问题:Q_OBJECT失效、非静态成员声明、QRunnable构造函数 #### 一、Q_OBJECT宏失效问题 **根本原因**:Q_OBJECT必须是中首个声明元素,且基顺序会影响元对象系统初始化[^1]。 **解决方案**: ```cpp class MainWindow : public QMainWindow // 确保QMainWindow为首个基 { Q_OBJECT // 必须是public后首个声明(移除分号) public: MainWindow(QWidget* parent = nullptr); ~MainWindow(); // 其他成员声明... private: Ui::MainWindow ui; // 私有成员在后 }; ``` #### 二、非静态成员无法inline声明 **问题分析**:Q_OBJECT会展开为虚函数声明,与非静态成员冲突。 **修复方案**: ```cpp public: // 移除内函数实现,改为在cpp文件定义 void setCheckboxState(QTreeWidget* treeWidget, const QString& parentText, const QString& itemText, bool checked); // 在MainWindow.cpp中实现 void MainWindow::setCheckboxState(QTreeWidget* treeWidget, ...) { // 具体实现 } ``` #### 三、QRunnable构造函数问题 **核心问题**:QRunnable任务错误地继承了QMainWindow。 **重构任务**: ```cpp // 独立任务(不继承任何窗口) class DoubleClick : public QRunnable { public: explicit DoubleClick(MainWindow* context) : m_context(context) { setAutoDelete(true); // 自动内存管理 } void run() override { // 通过m_context访问主窗口数据 } private: MainWindow* m_context; // 上下文指针 }; // 在主窗口中使用 void MainWindow::someFunction() { QThreadPool::globalInstance()->start(new DoubleClick(this)); } ``` #### 四、静态成员初始化 **必须**在cpp文件中初始化静态成员: ```cpp // MainWindow.cpp HANDLE MainWindow::hProcess = nullptr; std::string MainWindow::mnq = ""; unsigned long long MainWindow::nianheAdress = 0; // ...其他静态成员初始化 ``` #### 五、信号槽连接验证 在构造函数中添加连接检查: ```cpp MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) { ui.setupUi(this); // 验证信号槽连接 Q_ASSERT(connect(ui.treeWidget, &QTreeWidget::itemClicked, this, &MainWindow::treewidget_itemClicked)); } ``` ### 关键修改说明 1. **基顺序调整** QMainWindow必须作为首个基,确保Qt元对象系统正确初始化[^1]。 2. **任务重构** QRunnable任务只需持有主窗口指针,避免多重继承带来的复杂性: $$ \text{Task} = \text{QRunnable} + \text{Context Pointer} $$ 3. **静态成员分离** C++要求静态成员在外定义存储空间: ```cpp // 头文件声明 static HANDLE hProcess; // cpp文件定义 HANDLE MainWindow::hProcess = nullptr; ``` 4. **构建系统清理** 修改后必须: - 删除所有`moc_*.cpp`文件 - 执行qmake重新生成Makefile - 完全重新编译项目 > 通过上述修改可同时解决Q_OBJECT失效、非静态成员冲突和QRunnable构造问题[^1]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值