C++ 六个默认成员函数 + this指针

本文详细介绍了C++中的this指针,解释了其概念、作用、示例和注意事项。此外,文章还探讨了C++中的六个默认成员函数——构造函数、析构函数、拷贝构造函数、重载赋值运算符,以及它们的使用场景和编译器默认行为。通过对this指针和默认成员函数的理解,有助于深入掌握C++面向对象编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

this指针

引出:

概念:

示例:

注意点:

this指针存储在哪里?

一个经典题

六个默认成员函数👇

一、构造函数

简介与作用

基本语法与一些规则

默认构造函数

初始化列表

类成员变量声明处的默认值/缺省值

编译器自动生成的默认构造函数做了什么工作

二、析构函数

作用与简介:

基本语法:

默认析构函数

什么时候需要主动实现析构函数

析构函数调用顺序

三、拷贝构造函数

作用:

拷贝是什么操作?

语法与细节:

为什么参数必须是类对象的&,和const

默认拷贝构造函数的操作:

什么时候需要自己编写拷贝构造函数:

拷贝构造函数使用情景

四、重载拷贝赋值运算符函数

重载运算符函数

理解:

语法与规则:

重载拷贝赋值运算符函数

简介与功能

什么时候是拷贝构造,什么时候是赋值?

语法与细节:

编译器默认生成的重载赋值运算符函数

什么时候需要自己实现重载赋值运算符函数

重载<<   >>运算符

前言

重载<< >>函数必须定义为全局的,不能定义在类内。

注意点:

五,六 重载&运算符函数


this指针

this指针是C++类和对象中的一个关键,后面的运算符重载,以及很多知识都涉及到this指针。

引出:

bool Date::Change(int year, int month, int day) {
    if(CheckDate(year,month,day)) {
        _year = year;
        _month = month;
        _day = day;
        return true;
    }else{
        return false;
    }
}

如上是一个日期类的修改日期的函数,如果date1 和 date2分别调用此函数,那么这个函数是如何正确修改对应对象的数据成员_year _month _ day的呢? 就是因为this指针。

概念:

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

示例:

所以上述示例代码编译器会处理为:

bool Date::Change(Date* const this, int year, int month, int day) {
    if(this->CheckDate(year,month,day)) {
        this->_year = year;
        this->_month = month;
        this->_day = day;
        return true;
    }else{
        return false;
    }
}

而我们的调用语句,编译器会处理为

    Date d1(2022,8,10);
    Date d2(2021,3,1);
    d1.Print(&d1);
    d1.Change(&d1,2022,2,22);
    d2.Change(&d2,2021,1,1);

由上可知,任何一个非静态成员函数,调用时必须由某个类实例化对象调用,而调用后,就会隐式地在第一个参数位置传递这个对象的地址。而形参列表的第一个也是一个隐式的Date* const this。由此才能对某个对象的数据成员进行准确的读写操作!

注意点:

1. 实参和形参部分,不能显式地传递和接收this指针,这是编译器隐式进行的操作。
2. 在函数内可以显式地使用this指针,去调用其数据成员或者成员函数,如果不显式写,在每个数据成员和成员函数前,编译器会隐式处理为this-> 
3. this指针只能在成员函数内部使用
4. this指针默认为常量指针,也就是this的指向不可以改变。
5. const对象的this指针为const X* const this,代表此this指向的对象的数据不可以被改变,即指向常量对象的常量指针。 这就会引出const成员函数等一系列问题。后面再详细说
6. 如果成员函数被定义为const成员函数,则表示此函数不会改变this指向的对象的数据,则this指针变为const X* const this ,而对应第5点,可知,const对象只能调用const成员函数,因为指向常量的this指针如果传递给一个非const成员函数,就属于权限的方法,是非法的。(后面再详细说const成员函数,其实内容并不多)
7. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

this指针存储在哪里?

this指针并不存储在对象内部,因为this指针是作为参数传递的,所以在函数栈帧内,也就是栈区。但是这个也不绝对,因为this指针是一个使用频繁的参数,有些编译器会将使用频繁的变量进行优化,将其存储在寄存器中,不过这是属于编译器的优化行为。

一个经典题

class A
{
public:
    void Print()
    {
        printf("%p\n",this);
//        cout<<this<<endl;
        cout<<"Print()"<<endl;
    }
    void PrintA()
    {
        cout<<_a<<endl;
    }
private:
    int _a;
};

 int main()
{
    A* p = nullptr;
    p->Print();
    p->PrintA();
    return 0;
}

问,如果单独运行p->Print();  和 p->PrintA(); 分别是什么结果?

答: Print()函数会正常运行,因为这里的p虽然是空指针,但是我们调用Print函数时,并没有解引用它,因为函数代码根本不存在对象内,是存储在公共代码区里的,通过指针得知其指向A类型对象,然后到对应的区域调用Print()函数,打印出00000000 和 Print()   (这里也会把this指针作为第一个参数隐式传递过去)

        PrintA();函数会运行崩溃,因为执行函数内代码cout<<_a<<endl;时,_a为this->_a ,会解引用this指针,因为_a是存储在对象内的。后发觉这是一个空指针,就会崩溃。(这并不是编译时报错,而是运行时)        

六个默认成员函数👇

默认成员函数:用户没有显式实现时,编译器自动生成的成员函数称为默认成员函数。

下面介绍六个默认成员函数,默认成员函数即每个类默认都会有的成员函数,它们各自有不同的作用,如果你不显式实现,那么编译器会自动实现一个默认的。分别是:构造函数,析构函数,拷贝构造函数,拷贝赋值运算符重载函数,&运算符重载函数,const版&运算符重载函数。其中前四个为重点,最后两个了解即可。  

 学习这些函数时,需要针对几个点进行学习
这些函数各自的作用,什么时候调用,编译器默认生成的能够完成什么工作,什么时候需要我们自己实现。   暂时想不到其他的了,剩下的就是一些细节的点了

一、构造函数

简介与作用

构造函数的存在的意义是什么呢? 比如我们用C语言写一个Stack,那么这个Stack创建出变量/对象之后,要调用Init进行初始化。其实构造函数的作用就是,帮助我们进行更方便地初始化对象。因为每个类实例化对象时,都会由编译器自动调用配对的构造函数,(无论是编译器自己生成的还是我们写的,无论是有参还是无参)这样就保证了对象的初始化工作,并且更加的方便。(一个类如果没有构造函数,则无法创建对象)

所以说,构造函数并不是用来构造/创建对象的,而是初始化对象的!

基本语法与一些规则

1. 构造函数无返回值,函数名与类名相同(不想说了)
2. 构造函数在创建对象时由编译器自动调用,且必须调用一个,无论哪个版本
3. 构造函数可以重载,以适应不同的初始化情形。

默认构造函数

默认构造函数只能有一个,(默认构造函数是不传参时调用的那个构造函数),为了避免歧义,所以只能有一个。

默认构造函数包括:

a、我们不实现任意构造函数时,编译器自动创建的那个

b、我们写的无参构造函数 Date() {} 

c、我们实现的,所有参数都有默认值/缺省值的构造函数 Date(int year = 1, int month = 1, int day = 1) {}   

这三个,一个类中只能存在一个。 (一旦用户显式定义任何一个构造函数,编译器都不会再生成那个默认构造函数)建议每个类都实现一个默认构造函数,原因暂时略了。


构造函数还有几个需要注意的点,如初始化列表,成员变量声明处的默认值。编译器自动生成的默认构造函数完成了什么工作。


Date类的初步实现,主要关注其中的两个构造函数

class Date
{
public:
    // 我们主动实现的全缺省默认构造函数
    Date(int year = 1, int month = 1, int day =1)
        : _year(year),_month(month),_day(day){
        assert(CheckDate(_year,_month,_day)); // 断言此检查为真,如果为假则报错。
    }
    // 构造函数重载
    Date(int month,int day)
        :_month(month),_day(day) {
        _year = 2022;
    }
    ~Date() = default;
    Date(const Date& d) = default;
    Date& operator=(const Date& d) = default;

    void Print()const;
    static bool CheckDate(int year,int month,int day);
    bool Change(int year,int month, int day);
    static int GetDayOfMonth(int year,int month);  // 这里设置为静态的,主要是因为函数内部没有使用this
private:
    int _year = 1;
    int _month = 1;
    int _day = 1;
};

初始化列表

初始化列表对于理解构造函数非常重要。 理解初始化列表的作用,它和构造函数函数体的功能的区别!!

1. 每个构造函数都存在初始化列表,如果我们不显式写,就会有一个默认的。且每次执行构造函数都会执行初始化列表

2. 初始化列表的作用是初始化数据成员,构造函数函数体的作用是给数据成员赋值。他们是有本质区别的

3. 如果我们不写初始化列表,它会执行默认的。默认的对于基本类型会初始化为随机值,自定义类型会调用其默认构造函数进行初始化。 这里和编译器自动形成的默认构造函数的作用也有关系,因为那个构造函数的作用就是靠它的初始化列表实现的!!

4. 由上我们可以知道,能使用初始化列表就使用,除非一些特定的操作必须函数体内执行。

(有点烦)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值