跳到主要内容位置

C++-类和对象-2

类的六个默认成员函数:

类的六个默认成员函数#

1.构造函数#

构造函数是一个特殊的成员函数,函数名与类名相同;

在创建对象的时候被调用,进行对象的初始化与资源申请;

对象的生命周期只调用一次,并且可以重载。

例如,上面的日期类,设置日期可以不使用自定义的成员函数SetDate,而是使用构造函数:

#include <iostream>
#include <ostream>
using namespace std;
class Date
{
public:
Date();
Date(int year, int month, int date);
void PrintDate();
private:
int _year;
int _month;
int _date;
};
Date::Date()
{
}
Date::Date(int year, int month, int date)
{
_year = year;
_month = month;
_date = date;
}
void Date::PrintDate()
{
cout<<_year<<"-"<<_month<<"-"<<_date<<endl;
}
int main(int argc, const char** argv) {
Date a;
Date b(0, 0, 1);
a.PrintDate();
b.PrintDate();
return 0;
}

输出结果:

book@100ask:~/Desktop$ g++ -o test test.cpp
book@100ask:~/Desktop$ ./test
1267428112-22041-1267427440
0-0-1

需注意构造函数以下特点

  • 函数名与类名相同
  • 无返回值
  • 对象实例化时编译器自动调用对应的构造函数
  • 构造函数可以重载
  • 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成
  • 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。

全缺省构造函数:

  • 定义了全缺省构造函数,就不能有无参构造函数
  • 只需要在声明的时候给出缺省参数即可,定义时不能重复给出。
#include <iostream>
#include <ostream>
using namespace std;
class Date
{
public:
// Date();
Date(int year=2024, int month=6, int date=29);
void PrintDate();
private:
int _year;
int _month;
int _date;
};
// Date::Date()
// {
// }
Date::Date(int year, int month, int date)
{
_year = year;
_month = month;
_date = date;
}
void Date::PrintDate()
{
cout<<_year<<"-"<<_month<<"-"<<_date<<endl;
}
int main(int argc, const char** argv) {
Date a;
Date b(0, 0, 1);
a.PrintDate();
b.PrintDate();
return 0;
}

输出结果:

book@100ask:~/Desktop$ g++ -o test test.cpp
book@100ask:~/Desktop$ ./test
2024-6-29
0-0-1

2.析构函数#

析构函数与构造函数相反,在对象销毁时会自动调用析构函数,完成类资源的清理。

析构函数的特点:

  • 在类名前面加上~符号的成员函数
  • 没有返回值没有参数
  • 一个类只有一个析构函数,会自动生成默认的析构函数
  • 对象生命周期结束时自动调用
class Date
{
public:
// Date();
Date(int year=2024, int month=6, int date=29);
~Date() // 析构函数
{}
void PrintDate();
private:
int _year;
int _month;
int _date;
};

3.拷贝构造函数#

通过拷贝构造函数可以创建一个与传入对象一样的新对象。

只有单个形参,该形参就是对本类类型的对象引用(一般都以const修饰)。

在用已存在对象构建新对象时自动调用。

class Date
{
public:
// Date();
Date(int year=2024, int month=6, int date=29);
Date(const Date& d);
void PrintDate();
private:
int _year;
int _month;
int _date;
};
...
Date::Date(const Date& d)
{
_year = d._year;
_month = d._month;
_date = d._date;
}
int main(int argc, const char** argv) {
Date a;
Date b(0, 0, 1);
Date c(a);
a.PrintDate();
b.PrintDate();
c.PrintDate();
return 0;
}

运行结果:

book@100ask:~/Desktop$ ./test
2024-6-29
0-0-1
2024-6-29

特点:

  • 拷贝构造函数是构造函数的一个重载形式

  • 拷贝构造函数的参数只有一个且必须传引用,因为传值调用会引起拷贝构造的无穷递归

  • 若未显示定义拷贝构造函数,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。 这里留了一个问题:

    // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
    class String
    {
    public:
    String(const char* str = "jack")
    {
    _str = (char*)malloc(strlen(str) + 1);
    strcpy(_str, str);
    }
    ~String()
    {
    cout << "~String()" << endl;
    free(_str);
    }
    private:
    char* _str;
    };
    int main()
    {
    String s1("hello");
    String s2(s1);
    }

    gpt解答:这个程序会崩溃的原因是因为浅拷贝的问题。默认情况下,编译器生成的拷贝构造函数会对指针成员进行浅拷贝,即只是拷贝指针的值而不是指针指向的内容。这会导致多个对象共享同一块内存。当这些对象被析构时,会出现多次释放同一块内存的情况,从而导致程序崩溃

4.赋值操作符重载函数#

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

因此,赋值操作符重载函数属于运算符重载

我们先介绍运算符重载,再说明默认成员函数的赋值操作符。

运算符重载:

  • 函数名为关键字operator后面加操作符符号

  • 函数原型是

    返回值类型 operator操作符(参数列表)
  • 操作符的参数中规定,第一个参数为左操作数,第二个参数为右操作数

  • 不能通过连接其他符号来创建新的操作符:比如operator@

  • 不能改变操作符的含义

  • .* //这里是点星运算符,不是星
    ::
    sizeof
    ?:
    .

    上面5个运算符不能重载

    tip:点星运算符

    #include <iostream>
    class MyClass {
    public:
    int data;
    void display() {
    std::cout << "Data: " << data << std::endl;
    }
    };
    int main() {
    MyClass obj;
    obj.data = 42;
    // 定义成员指针:指向的不是对象,而是直接指向对象内部成员的地址
    int MyClass::*dataPtr = &MyClass::data;
    void (MyClass::*funcPtr)() = &MyClass::display;
    // 使用 .* 运算符访问成员变量和成员函数
    std::cout << "Using .* operator to access data: " << obj.*dataPtr << std::endl;
    (obj.*funcPtr)(); // 调用成员函数
    return 0;
    }

这里我同样使用日期类实现示例:重载了大量的运算符,建议练习一下。

#include <iostream>
class Date {
public:
// 获取某年某月的天数
int GetMonthDay(int year, int month) const {
static int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int day = days[month];
if (month == 2 &&
((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) {
day += 1;
}
return day;
}
bool isLeapYear(int year) const {
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
// 全缺省的构造函数
Date(int year = 1900, int month = 1, int day = 1);
// 拷贝构造函数
// d2(d1)
Date(const Date &d);
// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date &operator=(const Date &d);
// 析构函数
~Date();
// 日期+=天数
Date &operator+=(int day);
// 日期-=天数
Date &operator-=(int day);
// 日期+天数
Date operator+(int day) const;
// 日期-天数
Date operator-(int day) const;
// 前置++
Date &operator++();
// 后置++
Date operator++(int);
// 后置--
Date operator--(int);
// 前置--
Date &operator--();
// >运算符重载
bool operator>(const Date &d) const;
// ==运算符重载
bool operator==(const Date &d) const;
// >=运算符重载
bool operator>=(const Date &d) const;
// <运算符重载
bool operator<(const Date &d) const;
// <=运算符重载
bool operator<=(const Date &d) const;
// !=运算符重载
bool operator!=(const Date &d) const;
int totalDays() const;
// 日期-日期 返回天数
int operator-(const Date &d) const;
// 输出日期
friend std::ostream &operator<<(std::ostream &out, const Date &date);
// 添加 getter 操作,以便能在 << 运算符中正常显示
int getYear() const { return _year; }
int getMonth() const { return _month; }
int getDay() const { return _day; }
private:
int _year;
int _month;
int _day;
};
#include "DateClass.h"
#include <iostream>
// 缺省参数只需要在声明中指出就可以了
Date::Date(int year, int month, int day) {
this->_year = year;
this->_month = month;
this->_day = day;
}
Date::Date(const Date &d) {
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
Date::~Date() {}
Date &Date::operator=(const Date &d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
Date &Date::operator+=(int day) {
_day += day;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12) {
_year++;
_month = 1;
}
}
return *this;
}
Date &Date::operator-=(int day) {
_day -= day;
while (_day <= 0) {
_month--;
if (_month <= 0) {
_year--;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date Date::operator+(int day) const {
Date tmp(*this);
tmp += day; // 使用了上面重载的+=操作符
return tmp;
}
Date Date::operator-(int day) const {
Date tmp(*this);
tmp -= day; // 使用了上面重载的-=操作符
return tmp;
}
// 前置++
Date &Date::operator++() {
*this += 1;
return *this;
}
// 后置++
Date Date::operator++(int) {
Date tmp(*this);
*this += 1;
return tmp;
}
// 前置--
Date &Date::operator--() {
*this -= 1;
return *this;
}
// 后置--
Date Date::operator--(int) {
Date tmp(*this);
*this -= 1;
return tmp;
}
// >运算符重载
bool Date::operator>(const Date &d) const {
return (_year > d._year) || (_year == d._year && _month > d._month) ||
(_year == d._year && _month == d._month && _day > d._day);
}
// ==运算符重载
bool Date::operator==(const Date &d) const {
return _year == d._year && _month == d._month && _day == d._day;
}
// >=运算符重载
bool Date::operator>=(const Date &d) const {
return (*this > d) || (*this == d);
}
// <运算符重载
bool Date::operator<(const Date &d) const {
return (_year < d._year) || (_year == d._year && _month < d._month) ||
(_year == d._year && _month == d._month && _day < d._day);
}
// <=运算符重载
bool Date::operator<=(const Date &d) const {
return (*this < d) || (*this == d);
}
// !=运算符重载
bool Date::operator!=(const Date &d) const { return !(*this == d); }
int Date::totalDays() const {
int total = 0;
// 添加每年的天数
for (int y = 1970; y < _year; ++y) {
total += (isLeapYear(y) ? 366 : 365);
}
// 添加当年每个月的天数
for (int m = 1; m < _month; ++m) {
total += GetMonthDay(_year, m);
}
// 添加当前月的天数
total += _day;
return total;
}
// 日期-日期 返回天数
int Date::operator-(const Date &d) const {
int days1 = this->totalDays();
int days2 = d.totalDays();
std::cout << days1 << "\n" << days2 << std::endl;
return days1 - days2;
}
// 重载 << 运算符以便输出日期
std::ostream &operator<<(std::ostream &out, const Date &d) {
out << d.getYear() << "-" << d.getMonth() << "-" << d.getDay();
return out;
}

赋值运算符重载

class Date
{
public :
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year ;
int _month ;
int _day ;
};

需要注意:

  1. 参数类型:const + 引用,和拷贝构造函数一样
  2. 返回值:引用,返回*this
  3. 检测是否自己给自己赋值
  4. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

5.取地址操作符重载#

这个默认成员函数一般不用重新定义 ,编译器默认会生成。只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

class Date
{
public :
Date* operator&()
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};

6.const取地址操作符重载#

这个默认成员函数一般不用重新定义 ,编译器默认会生成。只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

const成员:

const修饰成员函数,实际修饰的是成员函数的隐含参数this指针,表示不对成员进行修改

  1. const对象可以调用非const成员函数吗?不能(权限放大)
  2. 非const对象可以调用const成员函数吗?可以(权限缩小)
  3. const成员函数内可以调用其它的非const成员函数吗?不能(权限放大)
  4. 非const成员函数内可以调用其它的const成员函数吗?可以(权限缩小)

总结:调用成员函数过程中,(this指针)权限可以缩小,但不能放大。

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