欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++初识:类和对象(2)

程序员文章站 2022-06-01 11:37:48
...

在之前的文章里,我们出初步了解了,什么是类,类如何定义,类的大小怎么计算等

https://blog.csdn.net/LSFAN0213/article/details/81698194

一个空类里面什么也没有,但是它并非是什么也没有,只要是类,它就有6个默认的成员函数:

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值操作符重载

5.取地址操作符重载

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

类的构造函数:

class Date{
public:
    void Display()
    {
        cout<<_year<<"_"<<_month<<"_"<<_day<<endl;
    }
    void SetDate(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year; //年
    int _month; //月
    int _day;  //日
};

int main()
{
    Date firstDate,secondDate;
    firstDate.SetDate(2018,8,15);
    secondDate.SetDate(2018,8,14);
    firstDate.Display();
    secondDate.Display();
    return 0;
}

日期的成员的变量是私有的,我们是怎么初始化这些变量的?

其实这就是构造函数的功劳,它是一种随着对象创建而被自动调用的公有成员函数,有且仅在定义对象时自动执行一次,它的主要用途是用来作对象的初始化

构造函数是特殊的成员函数,特征有:

1.函数名与类名相同

2.无返回值

3.对象构造时系统自动调用对应的构造函数

4.构造函数可以重载

5.构造函数可以在类中定义,也可以在类外定义

6.如果类定义中没有给出构造函数,则C++编译器会自动产生一个默认的构造函数,但只要我们定义了一个构造函数,系统就不会自动生成默认的构造函数。

7.无参的构造函数和全默认值得构造函数都认为是默认构造函数,并且默认的构造函数只能有一个

无参构造函数

class Date
{
public:
    //1.无参构造函数
    Date()
    {}
    //2.带参构造函数
    Date(int year,int month,int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
private:
    int _year;
    int _month;
    int _day;
};

void TestDate()
{
    Date date1; //调用无参构造函数
    Date date2(2018,8,15); //调用带参的构造函数
    Date date3(); //注意这里没有调用date3的构造函数定义出 date3
}

带默认参的构造函数

class Date
{
public:
    //3.默认参数的构造函数
    Date(int year=2000,int month=1.int day=1)
    {
        _year = year;    
        _minth = month;
        _day = day;
    }
    //4.半默认参数的构造函数(不推荐)
    Date(int year,int month=1)
    {
        _year = year;
        _month = month;
        _day = 1;
    }
private:
    int _year;
    int _month;
    int _day;
};

void Test()
{
    Date date1; //调用默认构造函数
    Date date2(2015,1,2); //调用默认构造函数
}

析构函数

当一个对象的生命周期结束的时候,C++编译系统会自动调用一个成员函数,这个成员函数即析构函数

特征:

1.析构函数名是在类名前加上字符 ~;

2.无参数无返回值;

3.一个类有且只有一个析构函数(说明析构函数不能重载);(若未显示定义,系统会自动生成默认的析构函数)

4.对象生命周期结束时,C++编译系统自动调用析构函数;

class Date
{
public:
    //析构函数
    ~Date()
private:
    int _year;
    int _month;
    int _day;
};

析构函数内部不是删除对象,而是做一些删除对象前的清理工作

class MyVector
{
public:
    MyVector(int size)
    {
        _ptr = (int *)malloc(size*sizeof(int));
    }
    //这里析构函数需要完成清(shi)理(fang)工(kong)作(jian).
    ~MyVector()
    {
        if(_ptr)
        {
            free(_ptr); //释放堆上的空间
            _ptr = 0;   //将指针置空
        }
    }
private:
    int *_ptr;
};

类的拷贝构造函数

创建对象时使用同类对象来进行初始化,这时所使用的构造函数称为拷贝构造函数

特征:

1.拷贝构造函数是构造函数的一个重载

2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值会引发无穷递归

3.若未显示定义,系统会默认生成默认的拷贝构造函数。默认的拷贝构造函数会按照成员的声明顺序依次拷贝类成员进行初始化

class Date
{
public:
    Date()
    {}
    //拷贝构造函数
    Date(const Date &d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

void TestDate1()
{
    Date date1;
    //下面两种用法都是调用拷贝构造函数,是等价的
    Date date2(date1);  //调用拷贝构造函数
    Date date3 = date1;  //调用拷贝构造函数
}

问题来了,为什么拷贝构造函数的参数使用传值会引发无穷递归?

C++初识:类和对象(2)

问题是这样,我们传值进去的候,会先拷贝出一个临时变量,来进行初始化,而这个过程就是拷贝构造函数的过程,所以会一直在调用拷贝构造函数,引发无穷递归

运算符重载

为了增强程序的可读性,C++支持运算符重载

特征:

1.operator+合成的运算符 构成函数名(operator<)

2.重载运算符以后,不能改变预算符的优先级/结合性/操作数个数

class Date
{
public:
    //构造函数
    Date()
    {}
    //拷贝构造函数
    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    // == 操作符的重载
    bool operator == (const Date& d)
    {
        return _year == d._year;
            && _month = d._month;
            && _day = d._day;
    }
private:
    int _year;
    int _month;
    int _day;
};

void Test()
{
    Date date1;
    Date date2 = date1;  //调用拷贝构造函数
    date2 == date1;      // 调用 == 运算符重载
}

这个地方我们要注意,在写操作符重载函数的时候,参数中我们可以传值,但为什么要传引用,因为传值进去的话,在函数体内会先拷贝一份临时变量,但是传引用就不需要,省去了这个步骤,因为传引用基本上就和传地址是类似的,但是,我们得保证传进去的形参不改变实参的具体内容,所以得加上 const 修饰

另外,我们看看编译器对 == 操作符的重载处理过程:

C++初识:类和对象(2)

我们在使用 ==操作符重载时,编译器实际上帮我们加了一个参数,就是this指针,我们前面说过,this指针是隐式指针,我们看不见,但是我们要清楚,它一直指着对象。==操作符重载,我们需要两个操作数,但是实际上我们只需要写出一个,因为编译器会帮我们把this指针加上。

另外,C++中有5个不能重载的操作符:

C++初识:类和对象(2)

类的赋值操作符重载

1.赋值运算符的重载是对一个已存在的对象进行拷贝赋值

2.当程序没有显示地提供一个以本类或本类的引用为参数的赋值运算符重载函数时,编译器会自动生成这样一个赋值运算符重载函数

class Date
{
public:
    Date()
    {}
    //拷贝构造函数
    Date (const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    //赋值操作符重载
    Date& operator = (const Date& d)
    {
        if(this != &d)
        {
            this->_year = d._year;
            this->_month = d._month;
            this->_day = d._day;
        }
           return this;
    }
private:
    int _year;
    int _month;
    int _day;
};

void Test()
{
    Date date1;
    Date date2 = date1;  //调用拷贝构造函数
    Date date3;
    date3 = date1;  //调用赋值运算符重载
}

赋值操作符重载为什么要返回一个引用呢?

为了进行连续赋值,即 x = y = z

1、赋值返回引用

x = y = z  先执行y = z,返回y的引用,执行x = y

2、赋值不返回引用

x = y = z  先执行y = z,返回用y初始化的临时对象(注意临时对象都是常对象),再执行x = y的临时对象,返回用x初始化的临时对象。

还有一点,if判断不可少,这里的判断就是为了防止,将值赋给自己

类的const成员函数

const修饰普通变量

在C++中,const修饰的变量已经为一个常量,具有宏的属性,即在编译的时候,编译器会将const所修饰的常量进行替换

const修饰类成员

1.const修饰类成员变量时,该成员变量必须在构造函数的初始化列表中初始化、

2.const修饰类成员函数时,实际修饰该成员隐含的this指针,该成员函数不能对类的任何成员进行修改

void TestFunc()
{
    const int a = 10;
    int* pa = (int *)&a;

    *pa = 100;
    
    cout<<a<<endl;
    cout<<*pa<<endl;
}

在这段代码中,打印出a的值是 10 ,*pa 是 100

因为,a是一个常量10,在被const修饰的时候就具有了宏的属性,编译的时候,我们就可以认为它是这样:cout<<10<<endl;

下来我们看看编译器对const修饰的成员函数的处理

C++初识:类和对象(2)

有些时候,在const修饰的成员函数可能需要对类的某个成员变量进行修改,该成员变量只需被mutable关键字修饰即可

几个问题:

const对象可以调用非const成员函数和const成员函数吗?

非const对象可以调用非const成员函数和const成员函数吗?

const成员函数内可以调用其他的const成员函数和非const成员函数吗?

非const成员函数内可以调用其他的const成员函数和非const成员函数吗?

我们只需要记住一点,const修饰的 我们可以理解为 小作用域 而非const修饰的 我们可以理解为大作用域

大作用域可以调用小作用域,而小作用域不能调用大作用域

类的取地址操作符重载 和 const修饰的取地址操作符重载

这两个默认的成员函数一般不用重新定义,编译器会默认生成

class Date
{
public:
    Date* operator &()
    {
        return this;
    }
    const Date* operator() const
    {
        return this;
    }
private:
    int _year;
    int _month;
    int _day;
};

只有在一种情况下,才需要你自己重载这两个操作符,那就是你只想让别人获取到你指定的内容。