首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >类和对象进阶

类和对象进阶

原创
作者头像
凤年徐
发布2025-09-24 09:07:30
发布2025-09-24 09:07:30
2680
举报
文章被收录于专栏:代码飞升代码飞升

1. 类的默认成员函数

默认成员函数是指用户没有显式实现时,编译器会自动生成的成员函数。在C++中,如果我们不主动编写,编译器会为类默认生成以下6个默认成员函数。需要注意的是,这6个函数中最重要的是前4个,最后两个取地址重载在实际开发中较少使用,了解即可。此外,C++11标准之后还增加了移动构造函数和移动赋值运算符,这些内容我们将在后续章节详细讲解。

默认成员函数的学习需要从两个方面入手:

  • 第一:当我们不编写这些函数时,编译器默认生成的行为是什么,是否满足我们的需求。
  • 第二:如果编译器默认生成的行为不符合我们的需求,我们需要自己实现,那么应该如何实现。

2. 构造函数

构造函数是一种特殊的成员函数。需要注意的是,虽然名称中包含"构造",但构造函数的主要任务并不是开辟内存空间创建对象(局部对象在栈帧创建时空间就已经分配好了),而是在对象实例化时对其进行初始化。构造函数的本质是替代我们之前在Stack和Date类中编写的**Init**函数的功能,其自动调用的特性完美地替代了**Init**函数的手动调用

构造函数的特点:

  1. 函数名与类名完全相同
  2. 无返回值(不需要写void或其他返回类型,这是C++的语法规定)
  3. 对象实例化时编译器会自动调用对应的构造函数
  4. 构造函数支持重载,可以根据不同的参数列表定义多个构造函数
  5. 如果类中没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数;一旦用户显式定义了任何构造函数,编译器将不再自动生成默认构造函数
  6. 无参构造函数、全缺省构造函数和编译器默认生成的构造函数都称为默认构造函数。这三种构造函数有且只能存在一个,不能同时存在。虽然无参构造函数和全缺省构造函数构成函数重载,但在调用时会产生歧义。关键点:不传递实参就能调用的构造函数就是默认构造函数
  7. 对于内置类型成员变量,编译器默认生成的构造函数不会进行初始化(值是不确定的,取决于编译器);对于自定义类型成员变量,会调用该成员的默认构造函数进行初始化。如果该成员没有默认构造函数,则会产生编译错误,此时需要使用初始化列表来解决(初始化列表将在后续章节详细讲解)

说明:C++将类型分为内置类型(基本类型)和自定义类型。内置类型是语言提供的基本数据类型,如int、char、double、指针等;自定义类型是使用class/struct等关键字自定义的类型。

构造函数示例代码:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    // 1. 无参构造函数
    Date()
    {
        _year = 1;
        _month = 1;
        _day = 1;
        cout << "无参构造函数被调用" << endl;
    }
    
    // 2. 带参构造函数
    Date(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
        cout << "带参构造函数被调用" << endl;
    }
    
    // 3. 全缺省构造函数(与无参构造函数冲突,需注释掉)
    /*
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
        cout << "全缺省构造函数被调用" << endl;
    }
    */
    
    void Print()
    {
        cout << _year << "/" << _month << "/" << _day << endl;
    }

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

int main()
{
    // 如果只保留带参构造函数,编译将报错:没有合适的默认构造函数可用
    Date d1;           // 调用默认构造函数
    Date d2(2025, 1, 1); // 调用带参构造函数
    
    // 注意:通过无参构造函数创建对象时,对象后面不要跟括号
    // 否则编译器无法区分这是函数声明还是对象实例化
    // Date d3();  // 这行代码会被解析为函数声明,而不是对象创建
    
    d1.Print();
    d2.Print();
    
    return 0;
}

栈类的构造函数实现:

代码语言:cpp
复制
#include<iostream>
using namespace std;

typedef int STDataType;

class Stack
{
public:
    // 构造函数:初始化栈的容量,默认为4
    Stack(int n = 4)
    {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
        cout << "Stack构造函数被调用,容量为:" << n << endl;
    }
    
    // 其他成员函数...
    
private:
    STDataType* _a;     // 指向动态数组的指针
    size_t _capacity;   // 栈的容量
    size_t _top;        // 栈顶指针
};

// 使用两个Stack实现队列
class MyQueue
{
public:
    // 编译器自动生成的MyQueue构造函数会调用Stack的构造函数
    // 完成两个Stack成员的初始化
private:
    Stack pushst;  // 用于入队的栈
    Stack popst;   // 用于出队的栈
};

int main()
{
    MyQueue mq;  // 自动调用MyQueue的默认构造函数
    return 0;
}

3. 析构函数

析构函数的功能与构造函数相反。需要注意的是,析构函数并不是完成对象本身的销毁(比如局部对象存储在栈帧中,函数结束时栈帧销毁,对象自然释放),而是在对象销毁时自动调用,完成对象中资源的清理工作。析构函数的功能类似于我们之前Stack类中实现的Destroy函数。

对于像Date这样没有动态资源需要管理的类,严格来说不需要析构函数;而对于像Stack这样有动态内存分配的类,必须实现析构函数来防止内存泄漏。

析构函数的特点:

  1. 函数名是在类名前加上**~**符号
  2. 无参数无返回值(与构造函数类似,不需要写void)
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
  4. 对象生命周期结束时,编译器会自动调用析构函数
  5. 对于内置类型成员,编译器自动生成的析构函数不做任何处理;对于自定义类型成员,会调用其析构函数
  6. 即使我们显式编写析构函数,对于自定义类型成员也会自动调用其析构函数
  7. 如果类中没有申请资源,析构函数可以不写,直接使用编译器生成的默认析构函数即可(如Date类);如果有资源申请,一定要自己编写析构函数,否则会造成资源泄漏(如Stack类)
  8. 在同一作用域内,后定义的对象先析构(栈的特性:后进先出)

析构函数示例代码:

代码语言:cpp
复制
#include<iostream>
using namespace std;

typedef int STDataType;

class Stack
{
public:
    Stack(int n = 4)
    {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
        cout << "Stack构造函数被调用" << endl;
    }
    
    ~Stack()
    {
        cout << "~Stack()析构函数被调用" << endl;
        free(_a);      // 释放动态分配的内存
        _a = nullptr;  // 防止悬空指针
        _top = _capacity = 0;
    }

private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

// 使用两个Stack实现队列
class MyQueue
{
public:
    // 编译器自动生成的MyQueue析构函数会调用Stack的析构函数
    // 释放Stack内部的资源
    
    // 即使显式编写析构函数,也会自动调用Stack的析构函数
    /*
    ~MyQueue()
    {
        // 空的析构函数,但仍然会调用成员变量的析构函数
    }
    */

private:
    Stack pushst;
    Stack popst;
};

int main()
{
    Stack st;     // 构造函数被调用
    MyQueue mq;   // 构造函数被调用
    
    // main函数结束时,对象按照创建的反序析构
    // 先析构mq,再析构st
    return 0;
}

C++与C实现栈的对比:

通过对比使用C++和C实现的栈来解决括号匹配问题,我们可以发现有了构造函数和析构函数后,代码更加简洁和安全,不再需要手动调用Init和Destroy函数。

代码语言:cpp
复制
#include<iostream>
using namespace std;

// 使用C++版本的Stack实现括号匹配
bool isValid(const char* s) {
    Stack st;  // 自动调用构造函数
    
    while (*s) {
        if (*s == '[' || *s == '(' || *s == '{') {
            st.Push(*s);
        } else {
            // 右括号比左括号多,数量不匹配
            if (st.Empty()) {
                return false;
            }
            
            // 从栈中取出左括号进行匹配
            char top = st.Top();
            st.Pop();
            
            // 括号类型不匹配
            if ((*s == ']' && top != '[') ||
                (*s == '}' && top != '{') ||
                (*s == ')' && top != '(')) {
                return false;
            }
        }
        ++s;
    }
    
    // 栈为空说明所有括号都匹配成功
    return st.Empty();
    // st对象会自动调用析构函数释放资源
}

// 使用C版本Stack实现的对比
bool isValid_C(const char* s) {
    ST st;
    STInit(&st);  // 需要手动初始化
    
    while (*s) {
        // 左括号入栈
        if (*s == '(' || *s == '[' || *s == '{') {
            STPush(&st, *s);
        } else { // 右括号取栈顶左括号尝试匹配
            if (STEmpty(&st)) {
                STDestroy(&st);  // 需要手动释放资源
                return false;
            }
            
            char top = STTop(&st);
            STPop(&st);
            
            // 不匹配
            if ((top == '(' && *s != ')') ||
                (top == '{' && *s != '}') ||
                (top == '[' && *s != ']')) {
                STDestroy(&st);  // 需要手动释放资源
                return false;
            }
        }
        ++s;
    }
    
    // 栈不为空,说明左括号比右括号多
    bool ret = STEmpty(&st);
    STDestroy(&st);  // 需要手动释放资源
    return ret;
}

int main()
{
    cout << isValid("[()][]") << endl;  // 输出1(true)
    cout << isValid("[(])[]") << endl;  // 输出0(false)
    return 0;
}

4. 拷贝构造函数

如果一个构造函数的第一个参数是自身类类型的引用,且任何额外的参数都有默认值,那么这个构造函数就是拷贝构造函数。也就是说,拷贝构造函数是一种特殊的构造函数

拷贝构造函数的特点:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的第一个参数必须是类类型对象的引用。如果使用传值方式,编译器会直接报错,因为这会引发无穷递归调用。拷贝构造函数可以有多个参数,但第一个参数必须是引用,后面的参数必须有缺省值
  3. C++规定自定义类型对象进行拷贝时必须调用拷贝构造函数。因此,自定义类型的传值传参和传值返回都会调用拷贝构造函数
  4. 如果未显式定义拷贝构造函数,编译器会自动生成一个。自动生成的拷贝构造函数对内置类型成员变量进行值拷贝/浅拷贝(逐字节拷贝),对自定义类型成员变量会调用其拷贝构造函数
  5. 对于像Date这样成员变量全是内置类型且没有指向资源的类,编译器自动生成的拷贝构造函数就足够了;对于像Stack这样有指向资源的指针的类,编译器自动生成的浅拷贝不符合需求,需要自己实现深拷贝(对指向的资源也进行拷贝);对于像MyQueue这样主要包含自定义类型成员的类,编译器自动生成的拷贝构造函数会调用成员的拷贝构造函数,通常也不需要显式实现
  6. 一个实用的技巧:如果一个类显式实现了析构函数来释放资源,那么它通常也需要显式实现拷贝构造函数,否则可能会出现双重释放等问题
  7. 传值返回会产生临时对象并调用拷贝构造函数,而传引用返回则不会产生拷贝。但如果返回的是局部对象,使用引用返回会产生悬空引用(类似于野指针),因此要确保返回的对象在函数结束后仍然存在才能使用引用返回

拷贝构造函数示例代码:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    // 错误的拷贝构造函数:参数不能是传值方式
    // Date(Date d)  // 编译报错:非法的复制构造函数
    
    // 正确的拷贝构造函数:参数为const引用
    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
        cout << "Date拷贝构造函数被调用" << endl;
    }
    
    // 这是一个普通构造函数,不是拷贝构造函数
    Date(Date* d)
    {
        _year = d->_year;
        _month = d->_month;
        _day = d->_day;
    }
    
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

void Func1(Date d)  // 传值传参,会调用拷贝构造函数
{
    cout << "Func1中d的地址:" << &d << endl;
    d.Print();
}

// 返回局部对象的引用是危险的
Date& Func2()
{
    Date tmp(2024, 7, 5);
    tmp.Print();
    return tmp;  // 返回局部对象的引用,危险!
}

int main()
{
    Date d1(2024, 7, 5);
    
    // 传值传参会调用拷贝构造函数
    Func1(d1);
    cout << "main中d1的地址:" << &d1 << endl;
    
    // 这是普通构造函数,不是拷贝构造
    Date d2(&d1);
    d1.Print();
    d2.Print();
    
    // 这才是拷贝构造函数
    Date d3(d1);     // 直接初始化
    Date d4 = d1;    // 拷贝初始化
    
    d3.Print();
    d4.Print();
    
    // 返回局部对象引用是危险的
    Date ret = Func2();  // 可能导致未定义行为
    ret.Print();
    
    return 0;
}

深拷贝示例:

代码语言:cpp
复制
#include<iostream>
using namespace std;

typedef int STDataType;

class Stack
{
public:
    Stack(int n = 4)
    {
        _a = (STDataType*)malloc(sizeof(STDataType) * n);
        if (nullptr == _a)
        {
            perror("malloc申请空间失败");
            return;
        }
        _capacity = n;
        _top = 0;
    }
    
    // 深拷贝构造函数
    Stack(const Stack& st)
    {
        // 需要为_a创建新的资源空间
        _a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);
        if (nullptr == _a)
        {
            perror("malloc申请空间失败!!!");
            return;
        }
        
        // 拷贝数据
        memcpy(_a, st._a, sizeof(STDataType) * st._top);
        _top = st._top;
        _capacity = st._capacity;
        
        cout << "Stack深拷贝构造函数被调用" << endl;
    }
    
    void Push(STDataType x)
    {
        // 压栈操作实现...
        if (_top == _capacity)
        {
            int newcapacity = _capacity * 2;
            STDataType* tmp = (STDataType*)realloc(_a, newcapacity * sizeof(STDataType));
            if (tmp == NULL)
            {
                perror("realloc fail");
                return;
            }
            _a = tmp;
            _capacity = newcapacity;
        }
        _a[_top++] = x;
    }
    
    ~Stack()
    {
        cout << "~Stack()析构函数被调用" << endl;
        free(_a);
        _a = nullptr;
        _top = _capacity = 0;
    }

private:
    STDataType* _a;
    size_t _capacity;
    size_t _top;
};

// 两个Stack实现队列
class MyQueue
{
public:
    // 使用编译器自动生成的拷贝构造函数即可
    // 它会调用Stack的拷贝构造函数完成深拷贝

private:
    Stack pushst;
    Stack popst;
};

int main()
{
    Stack st1;
    st1.Push(1);
    st1.Push(2);
    
    // 如果Stack不使用深拷贝,st1和st2的_a会指向同一块内存
    // 析构时会重复释放,导致程序崩溃
    Stack st2 = st1;  // 调用拷贝构造函数
    
    MyQueue mq1;
    MyQueue mq2 = mq1;  // 自动调用MyQueue的拷贝构造函数
    
    return 0;
}

5. 赋值运算符重载

5.1 运算符重载基础

运算符重载是C++的重要特性,它允许我们为自定义类型定义运算符的行为。当运算符用于类类型的对象时,C++会调用对应的运算符重载函数。

运算符重载的基本规则:

  • 运算符重载函数的名字由operator和要重载的运算符组成
  • 重载运算符函数的参数数量与该运算符作用的运算对象数量一致 txt - 一元运算符有一个参数 - 二元运算符有两个参数(左侧运算对象传给第一个参数,右侧传给第二个参数)
  • 如果运算符重载是成员函数,第一个运算对象会隐式传递给this指针,因此参数数量比运算对象少一个
  • 重载后的运算符优先级和结合性与对应的内置运算符保持一致
  • 不能重载C++语法中不存在的符号(如operator@
  • 以下5个运算符不能重载:.*::sizeof? :.(选择题常考)
  • 重载操作符至少有一个类类型参数,不能改变内置类型对象的运算符含义
  • 一个类需要重载哪些运算符,取决于哪些运算符对该类有意义
  • 前置++和后置++通过参数区分:后置++增加一个int形参
  • <<>>通常重载为全局函数,因为作为成员函数时不符合使用习惯

运算符重载示例:

代码语言:cpp
复制
#include<iostream>
using namespace std;

// 错误:运算符重载必须至少有一个类类型参数
// int operator+(int x, int y)
// {
//     return x - y;
// }

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

// 为了演示,暂时将成员设为public
public:
    int _year;
    int _month;
    int _day;
};

// 全局运算符重载函数
bool operator==(const Date& d1, const Date& d2)
{
    return d1._year == d2._year &&
           d1._month == d2._month &&
           d1._day == d2._day;
}

int main()
{
    Date d1(2024, 7, 5);
    Date d2(2024, 7, 6);
    
    // 运算符重载函数可以显式调用
    operator==(d1, d2);
    
    // 通常的使用方式:编译器会转换为operator==(d1, d2)
    bool result = (d1 == d2);
    cout << "d1 == d2: " << result << endl;
    
    return 0;
}

成员函数形式的运算符重载:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
    
    // 成员函数形式的运算符重载
    bool operator==(const Date& d)
    {
        return _year == d._year &&
               _month == d._month &&
               _day == d._day;
    }
    
    // 前置++
    Date& operator++()
    {
        cout << "前置++被调用" << endl;
        // 实现日期增加一天的逻辑...
        return *this;
    }
    
    // 后置++(通过int参数区分)
    Date operator++(int)
    {
        Date tmp(*this);  // 保存原值
        cout << "后置++被调用" << endl;
        // 实现日期增加一天的逻辑...
        return tmp;  // 返回原值
    }

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

int main()
{
    Date d1(2024, 7, 5);
    Date d2(2024, 7, 6);
    
    // 成员函数形式的运算符调用
    d1.operator==(d2);  // 显式调用
    d1 == d2;           // 隐式调用
    
    ++d1;  // 调用前置++
    d1++;  // 调用后置++
    
    return 0;
}

5.2 赋值运算符重载

赋值运算符重载是一个默认成员函数,用于完成两个已存在对象之间的拷贝赋值。需要注意的是,赋值运算符重载与拷贝构造函数的区别:拷贝构造函数用于一个对象拷贝初始化另一个要创建的对象,而赋值运算符重载用于两个已存在对象之间的赋值

赋值运算符重载的特点:

  1. 必须重载为成员函数
  2. 参数通常为const当前类类型引用,避免不必要的拷贝
  3. 有返回值,通常返回当前类类型引用,以支持连续赋值
  4. 需要检查自赋值情况,避免不必要的操作和潜在错误
  5. 如果未显式实现,编译器会自动生成一个默认的赋值运算符重载
  6. 默认赋值运算符重载的行为与默认拷贝构造函数类似:对内置类型进行值拷贝,对自定义类型调用其赋值运算符重载
  7. 与拷贝构造函数类似,需要根据类的特性决定是否需要深拷贝

赋值运算符重载示例:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    Date(const Date& d)
    {
        cout << "Date拷贝构造函数被调用" << endl;
        _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;
        }
        // 返回*this以支持连续赋值
        return *this;
    }
    
    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }

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

int main()
{
    Date d1(2024, 7, 5);
    Date d2(d1);        // 拷贝构造函数
    Date d3(2024, 7, 6);
    
    d1 = d3;            // 赋值运算符重载
    
    // 注意:这是拷贝构造,不是赋值重载
    Date d4 = d1;       // 拷贝构造函数
    
    return 0;
}

5.3 完整的日期类实现

下面是一个功能完整的日期类实现,包含了各种运算符重载:

代码语言:cpp
复制
#pragma once
#include<iostream>
using namespace std;
#include<assert.h>

class Date
{
    // 友元函数声明,允许访问私有成员
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);

public:
    Date(int year = 1900, int month = 1, int day = 1);
    void Print() const;
    
    // 获取某年某月的天数(内联函数)
    int GetMonthDay(int year, int month)
    {
        assert(month > 0 && month < 13);
        
        static int monthDayArray[13] = { -1, 31, 28, 31, 30, 31, 30, 
                                        31, 31, 30, 31, 30, 31 };
        
        // 闰年二月有29天
        if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
        {
            return 29;
        }
        else
        {
            return monthDayArray[month];
        }
    }
    
    bool CheckDate();  // 检查日期是否合法
    
    // 比较运算符重载
    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;
    
    // 日期加减运算
    Date& operator+=(int day);
    Date operator+(int day) const;
    Date& operator-=(int day);
    Date operator-(int day) const;
    int operator-(const Date& d) const;  // 两个日期相差的天数
    
    // 自增自减运算符
    Date& operator++();      // 前置++
    Date operator++(int);    // 后置++
    Date& operator--();      // 前置--
    Date operator--(int);    // 后置--

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

// 流操作符重载声明
ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

(完整的日期类实现代码较长,这里只展示头文件部分,具体实现可参考原文)

6. 取地址运算符重载

6.1 const成员函数

const成员函数是指在函数声明后加上const关键字,它实际修饰的是该成员函数隐含的this指针,表明在该成员函数中不能修改类的任何成员。

const成员函数的特点:

  • 将const修饰符放在成员函数参数列表的后面
  • const实际修饰隐含的this指针,使其从Date* const this变为const Date* const this
  • const对象只能调用const成员函数
  • 非const对象可以调用const成员函数(权限缩小)

const成员函数示例:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    
    // const成员函数:不能修改成员变量
    void Print() const
    {
        // _year = 2025;  // 错误:不能在const成员函数中修改成员
        cout << _year << "-" << _month << "-" << _day << endl;
    }
    
    // 非const成员函数:可以修改成员变量
    void SetYear(int year)
    {
        _year = year;
    }

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

int main()
{
    Date d1(2024, 7, 5);
    d1.Print();        // 非const对象可以调用const成员函数
    
    const Date d2(2024, 8, 5);
    d2.Print();        // const对象只能调用const成员函数
    // d2.SetYear(2025); // 错误:const对象不能调用非const成员函数
    
    return 0;
}

6.2 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载。一般情况下,编译器自动生成的取地址运算符就足够使用了,不需要显式实现。只有在特殊场景下(如不想让别人获取对象的真实地址),才需要自己实现。

取地址运算符重载示例:

代码语言:cpp
复制
#include<iostream>
using namespace std;

class Date
{
public:
    Date(int year = 1, int month = 1, int day = 1)
        : _year(year), _month(month), _day(day)
    {
    }
    
    // 普通取地址运算符重载
    Date* operator&()
    {
        return this;      // 返回真实地址
        // return nullptr; // 可以返回空指针,隐藏真实地址
    }
    
    // const取地址运算符重载
    const Date* operator&() const
    {
        return this;      // 返回真实地址
        // return nullptr; // 可以返回空指针,隐藏真实地址
    }

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

int main()
{
    Date d1(2024, 7, 5);
    Date* p1 = &d1;      // 调用普通取地址运算符重载
    
    const Date d2(2024, 8, 5);
    const Date* p2 = &d2; // 调用const取地址运算符重载
    
    return 0;
}

总结

本文详细介绍了C++中类的默认成员函数,特别是构造函数、析构函数、拷贝构造函数和赋值运算符重载这四个最重要的成员函数。通过丰富的代码示例,展示了这些函数的使用场景和注意事项。

关键要点总结:

  1. 构造函数负责对象初始化,有默认构造、带参构造、拷贝构造等多种形式
  2. 析构函数负责资源清理,有动态资源分配的类必须显式实现析构函数
  3. 拷贝构造函数用于对象拷贝初始化,需要根据类特性决定使用浅拷贝还是深拷贝
  4. 赋值运算符重载用于已存在对象间的赋值,需要注意自赋值检查
  5. 运算符重载让自定义类型支持天然的操作符语法,提高代码可读性
  6. const成员函数保证函数不会修改对象状态,提高代码的安全性

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 类的默认成员函数
  • 2. 构造函数
    • 构造函数的特点:
    • 构造函数示例代码:
    • 栈类的构造函数实现:
  • 3. 析构函数
    • 析构函数的特点:
    • 析构函数示例代码:
    • C++与C实现栈的对比:
  • 4. 拷贝构造函数
    • 拷贝构造函数的特点:
    • 拷贝构造函数示例代码:
    • 深拷贝示例:
  • 5. 赋值运算符重载
    • 5.1 运算符重载基础
    • 运算符重载示例:
    • 成员函数形式的运算符重载:
    • 5.2 赋值运算符重载
    • 赋值运算符重载示例:
    • 5.3 完整的日期类实现
  • 6. 取地址运算符重载
    • 6.1 const成员函数
    • const成员函数示例:
    • 6.2 取地址运算符重载
    • 取地址运算符重载示例:
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档