前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >剖析【C++】——类与对象(中)——小白篇—超详解

剖析【C++】——类与对象(中)——小白篇—超详解

作者头像
小李很执着
发布2024-06-15 10:07:33
发布2024-06-15 10:07:33
12600
代码可运行
举报
文章被收录于专栏:学习笔记学习笔记
运行总次数:0
代码可运行

1.类的6个默认成员函数:

在C++中,即使一个类没有定义任何成员或成员函数,编译器仍会为其生成以下6个默认成员函数。下面是对这些默认成员函数的简易分析和代码示例。

1. 默认构造函数(Default Constructor)

默认构造函数在创建对象时被调用。如果类中没有定义任何构造函数,编译器会自动生成一个默认的无参构造函数。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
    // 编译器会生成一个默认构造函数
};

MyClass obj; // 调用默认构造函数
2. 析构函数(Destructor)

析构函数在对象被销毁时调用。编译器会生成一个默认的析构函数来清理资源。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
    // 编译器会生成一个默认析构函数
};

{
    MyClass obj; // 析构函数在作用域结束时被调用
}
3. 拷贝构造函数(Copy Constructor)

拷贝构造函数用于创建一个新的对象作为现有对象的副本。如果没有定义拷贝构造函数,编译器会生成一个默认的。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
    // 编译器会生成一个默认的拷贝构造函数
};

MyClass obj1;
MyClass obj2 = obj1; // 调用默认拷贝构造函数
4. 拷贝赋值运算符(Copy Assignment Operator)

拷贝赋值运算符用于将一个对象的值赋给另一个对象。如果没有定义拷贝赋值运算符,编译器会生成一个默认的。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
    // 编译器会生成一个默认的拷贝赋值运算符
};

MyClass obj1;
MyClass obj2;
obj2 = obj1; // 调用默认拷贝赋值运算符
5. 移动构造函数(Move Constructor)

移动构造函数在C++11中引入,用于从一个临时对象中“偷取”资源。如果没有定义移动构造函数,编译器会生成一个默认的。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
    // 编译器会生成一个默认的移动构造函数
};

MyClass obj1;
MyClass obj2 = std::move(obj1); // 调用默认移动构造函数
2.构造函数详解

构造函数是C++中的一个重要概念,它使对象在创建时自动初始化。以下是对构造函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。

2.1 构造函数的概念

构造函数是一个特殊的成员函数,名字与类名相同。当创建类类型对象时,编译器会自动调用构造函数,以保证每个数据成员都有一个合适的初始值,并且在对象的整个生命周期内只调用一次。

示例类:Date

假设我们有一个 Date 类,需要在创建对象时设置日期信息。

代码语言:javascript
代码运行次数:0
运行
复制
class Date {
public:
    Date(int year, int month, int day) { // 构造函数
        _year = year;
        _month = month;
        _day = day;
    }
    
    void display() {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

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

通过这个构造函数,创建对象时可以直接设置日期信息:

代码语言:javascript
代码运行次数:0
运行
复制
Date today(2024, 5, 28); // 调用构造函数
today.display(); // 输出: 2024-5-28
2.2 构造函数的特性

构造函数具有以下特性:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载
示例代码:构造函数重载
代码语言:javascript
代码运行次数:0
运行
复制
class Date {
public:
    // 默认构造函数
    Date() : _year(0), _month(0), _day(0) {}
    
    // 带参数的构造函数
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}

    void display() {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

private:
    int _year;
    int _month;
    int _day;
};
使用构造函数
代码语言:javascript
代码运行次数:0
运行
复制
Date defaultDate; // 调用默认构造函数
Date specificDate(2024, 5, 28); // 调用带参数的构造函数

defaultDate.display(); // 输出: 0-0-0
specificDate.display(); // 输出: 2024-5-28
编译器生成的默认构造函数

如果没有显式定义构造函数,编译器会自动生成一个无参的默认构造函数。这个默认构造函数对内置类型成员变量不进行初始化,而对自定义类型成员变量会调用它们的默认构造函数。

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
public:
    int a; // 内置类型,不会初始化
    std::string b; // 自定义类型,会调用其默认构造函数
};

int main() {
    MyClass obj;
    std::cout << "a: " << obj.a << ", b: " << obj.b << std::endl; // a: 随机值, b: 空字符串
    return 0;
}
C++11中的改进

C++11允许在类定义时为内置类型成员变量提供默认值:

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
public:
    int a = 0; // 内置类型,提供默认值
    std::string b; // 自定义类型
};

int main() {
    MyClass obj;
    std::cout << "a: " << obj.a << ", b: " << obj.b << std::endl; // a: 0, b: 空字符串
    return 0;
}
总结

构造函数是用于初始化对象的特殊成员函数,其名称与类名相同且无返回值。构造函数可以重载,使得对象在不同的情况下被初始化。如果没有定义构造函数,编译器会生成一个默认的构造函数,但它对内置类型成员变量不进行初始化。C++11引入了在类定义时为内置类型成员变量提供默认值的功能,从而增强了默认构造函数的实用性。

3.析构函数详解

析构函数是C++中的一个重要概念,它使对象在销毁时能自动清理资源。以下是对析构函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。

3.1 析构函数的概念

析构函数与构造函数功能相反,不是完成对对象本身的销毁,而是用于清理对象中的资源。当对象的生命周期结束时,C++编译器会自动调用析构函数。

示例类:Date

假设我们有一个 Date 类,不需要特别的资源管理,因此可以使用编译器生成的默认析构函数。

代码语言:javascript
代码运行次数:0
运行
复制
class Date {
public:
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}

    void display() {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

    ~Date() {
        // 编译器会自动调用这个析构函数
        std::cout << "Date对象被销毁: " << _year << "-" << _month << "-" << _day << std::endl;
    }

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

通过这个析构函数,可以在对象销毁时自动打印一条消息:

代码语言:javascript
代码运行次数:0
运行
复制
int main() {
    Date today(2024, 5, 28);
    today.display();
    return 0;
}
// 输出:
// 2024-5-28
// Date对象被销毁: 2024-5-28
3.2 析构函数的特性

析构函数具有以下特性:

  1. 析构函数名是在类名前加上字符 ~
  2. 无参数,无返回值类型
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。析构函数不能重载。
  4. 对象生命周期结束时,C++编译系统自动调用析构函数
示例代码:编译器自动生成的析构函数
代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
public:
    MyClass() {
        std::cout << "MyClass对象创建" << std::endl;
    }

    ~MyClass() {
        std::cout << "MyClass对象销毁" << std::endl;
    }
};

int main() {
    MyClass obj;
    return 0;
}
// 输出:
// MyClass对象创建
// MyClass对象销毁

在上述代码中,当对象 obj 的生命周期结束时,编译器会自动调用析构函数。

资源管理示例:Stack类

当类中有资源需要管理时,例如动态内存,必须显式定义析构函数以防止资源泄漏。

代码语言:javascript
代码运行次数:0
运行
复制
class Stack {
public:
    Stack(int size) {
        _size = size;
        _data = new int[size]; // 动态分配内存
        std::cout << "Stack对象创建,分配内存" << std::endl;
    }

    ~Stack() {
        delete[] _data; // 释放内存
        std::cout << "Stack对象销毁,释放内存" << std::endl;
    }

private:
    int _size;
    int* _data;
};

int main() {
    Stack stack(10);
    return 0;
}
// 输出:
// Stack对象创建,分配内存
// Stack对象销毁,释放内存
总结

析构函数是用于清理对象资源的特殊成员函数,其名称是在类名前加上字符 ~,且无参数和返回值。一个类只能有一个析构函数,不能重载。当对象的生命周期结束时,C++编译器会自动调用析构函数。对于没有资源需要管理的类,可以使用编译器生成的默认析构函数;对于需要管理资源的类,必须显式定义析构函数以防止资源泄漏。

4.拷贝构造函数详解

拷贝构造函数允许创建一个与已存在对象完全相同的新对象。以下是对拷贝构造函数的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。

4.1 拷贝构造函数的概念

在C++中,拷贝构造函数是一个特殊的构造函数,用于创建一个与已有对象相同的新对象。它的参数是对本类类型对象的引用,通常用 const 修饰。

示例类:Date

假设我们有一个 Date 类,通过拷贝构造函数可以创建一个与已存在对象相同的新对象。

代码语言:javascript
代码运行次数:0
运行
复制
class Date {
public:
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}

    // 拷贝构造函数
    Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
        std::cout << "调用拷贝构造函数" << std::endl;
    }

    void display() const {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

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

int main() {
    Date date1(2024, 5, 28);
    Date date2 = date1; // 调用拷贝构造函数
    date2.display();
    return 0;
}
// 输出:
// 调用拷贝构造函数
// 2024-5-28
4.2 拷贝构造函数的特征

拷贝构造函数具有以下特征:

  1. 拷贝构造函数是构造函数的一个重载形式
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用。使用传值方式编译器会报错,因为会引发无穷递归调用。
  3. 若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数按内存存储字节序进行拷贝,这种拷贝叫做浅拷贝或值拷贝。内置类型按字节方式直接拷贝,自定义类型调用其拷贝构造函数完成拷贝。
浅拷贝与深拷贝
  • 浅拷贝:仅拷贝对象中的值,不考虑资源的深层次复制。
  • 深拷贝:不仅拷贝对象中的值,还对对象中涉及的资源进行深层次复制。
示例代码:浅拷贝与深拷贝
代码语言:javascript
代码运行次数:0
运行
复制
class Stack {
public:
    Stack(int size) : _size(size), _data(new int[size]) {
        std::cout << "Stack对象创建,分配内存" << std::endl;
    }

    // 拷贝构造函数实现深拷贝
    Stack(const Stack& other) : _size(other._size), _data(new int[other._size]) {
        std::copy(other._data, other._data + other._size, _data);
        std::cout << "调用拷贝构造函数,进行深拷贝" << std::endl;
    }

    ~Stack() {
        delete[] _data;
        std::cout << "Stack对象销毁,释放内存" << std::endl;
    }

private:
    int _size;
    int* _data;
};

int main() {
    Stack stack1(10);
    Stack stack2 = stack1; // 调用拷贝构造函数
    return 0;
}
// 输出:
// Stack对象创建,分配内存
// 调用拷贝构造函数,进行深拷贝
// Stack对象销毁,释放内存
// Stack对象销毁,释放内存
拷贝构造函数的典型调用场景
  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象

为了提高程序效率,一般对象传参时尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

总结

拷贝构造函数是用于创建一个与已有对象相同的新对象的特殊构造函数。它的参数是对本类类型对象的引用,且无返回值。若未显式定义,编译器会生成默认的拷贝构造函数,对内置类型进行浅拷贝,对自定义类型调用其拷贝构造函数完成拷贝。对于涉及资源管理的类,显式定义拷贝构造函数以实现深拷贝是必要的,以防止资源泄漏。

5.赋值运算符重载详解

赋值运算符重载是C++中运算符重载的一种形式,它允许我们自定义类对象之间的赋值行为。以下是对赋值运算符重载的详细解释和代码示例,帮助初学者深入理解其原理和使用方法。

5.1 运算符重载概述

运算符重载是C++引入的一种机制,用于增强代码的可读性。运算符重载的函数具有特殊的名字,并且具有返回值类型、函数名字以及参数列表,其返回值类型和参数列表与普通的函数类似。函数名字为关键字 operator 后面接需要重载的运算符符号。

示例:运算符重载函数原型
5.2 赋值运算符重载

赋值运算符重载是一种常见的运算符重载形式,用于定义类对象之间的赋值操作。

赋值运算符重载格式
  • 参数类型const T&,传递引用可以提高传参效率。
  • 返回值类型T&,返回引用可以提高返回效率,并支持连续赋值。
  • 检测是否自己给自己赋值
  • 返回*this:符合连续赋值的含义。
示例类:Stack

假设我们有一个 Stack 类,通过赋值运算符重载可以定义对象之间的赋值操作。

代码语言:javascript
代码运行次数:0
运行
复制
class Stack {
public:
    Stack(int size) : _size(size), _data(new int[size]) {
        std::cout << "Stack对象创建,分配内存" << std::endl;
    }

    // 拷贝构造函数实现深拷贝
    Stack(const Stack& other) : _size(other._size), _data(new int[other._size]) {
        std::copy(other._data, other._data + other._size, _data);
        std::cout << "调用拷贝构造函数,进行深拷贝" << std::endl;
    }

    // 赋值运算符重载
    Stack& operator=(const Stack& other) {
        if (this == &other) {
            return *this; // 检测自我赋值
        }

        delete[] _data; // 释放旧内存

        _size = other._size;
        _data = new int[_size];
        std::copy(other._data, other._data + _size, _data);

        std::cout << "调用赋值运算符重载,进行深拷贝" << std::endl;

        return *this; // 支持连续赋值
    }

    ~Stack() {
        delete[] _data;
        std::cout << "Stack对象销毁,释放内存" << std::endl;
    }

private:
    int _size;
    int* _data;
};

int main() {
    Stack stack1(10);
    Stack stack2(5);
    stack2 = stack1; // 调用赋值运算符重载
    return 0;
}
// 输出:
// Stack对象创建,分配内存
// Stack对象创建,分配内存
// Stack对象销毁,释放内存
// 调用赋值运算符重载,进行深拷贝
// Stack对象销毁,释放内存
赋值运算符重载的注意事项
  1. 赋值运算符只能重载成类的成员函数,不能重载成全局函数。原因是赋值运算符如果不显式实现,编译器会生成一个默认的。如果在类外实现一个全局的赋值运算符重载,会与编译器生成的默认赋值运算符重载冲突。
  2. 用户没有显式实现时,编译器会生成默认的赋值运算符重载,以值的方式逐字节拷贝。对于内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
  3. 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理,则必须实现
前置++和后置++重载

前置和后置自增运算符也可以重载。它们分别表示在变量本身修改之前和之后返回值。

示例代码:前置和后置自增运算符重载
代码语言:javascript
代码运行次数:0
运行
复制
class Counter {
public:
    Counter(int value = 0) : _value(value) {}

    // 前置++
    Counter& operator++() {
        ++_value;
        return *this;
    }

    // 后置++
    Counter operator++(int) {
        Counter temp = *this;
        ++_value;
        return temp;
    }

    void display() const {
        std::cout << "Counter: " << _value << std::endl;
    }

private:
    int _value;
};

int main() {
    Counter c(5);
    ++c; // 前置++
    c.display(); // Counter: 6
    c++; // 后置++
    c.display(); // Counter: 7
    return 0;
}
总结

赋值运算符重载允许自定义类对象之间的赋值行为。它的参数类型通常是 const T&,返回值类型是 T&,并且需要检测自我赋值和返回 *this 以支持连续赋值。赋值运算符只能重载成类的成员函数,并且如果类涉及资源管理,则必须显式实现赋值运算符重载。前置和后置自增运算符也可以重载,以实现不同的自增行为。

6.日期类的实现

构造函数、拷贝构造函数、赋值运算符重载、析构函数以及基本的成员函数,用于表示和操作日期。

1. 定义Date类

首先,我们定义类的成员变量和基本的构造函数。

代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

class Date {
public:
    // 带参数的构造函数
    Date(int year, int month, int day) : _year(year), _month(month), _day(day) {
        std::cout << "调用带参数的构造函数" << std::endl;
    }

    // 默认构造函数
    Date() : _year(0), _month(0), _day(0) {
        std::cout << "调用默认构造函数" << std::endl;
    }

    // 拷贝构造函数
    Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
        std::cout << "调用拷贝构造函数" << std::endl;
    }

    // 赋值运算符重载
    Date& operator=(const Date& other) {
        if (this != &other) { // 检测自我赋值
            _year = other._year;
            _month = other._month;
            _day = other._day;
            std::cout << "调用赋值运算符重载" << std::endl;
        }
        return *this; // 支持连续赋值
    }

    // 析构函数
    ~Date() {
        std::cout << "调用析构函数" << std::endl;
    }

    // 显示日期
    void display() const {
        std::cout << _year << "-" << _month << "-" << _day << std::endl;
    }

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

int main() {
    // 创建日期对象并显示
    Date date1(2024, 5, 28);
    date1.display();

    // 使用拷贝构造函数创建新对象并显示
    Date date2 = date1;
    date2.display();

    // 使用赋值运算符重载并显示
    Date date3;
    date3 = date1;
    date3.display();

    return 0;
}
2. 详细解析每个部分
2.1 构造函数

构造函数用于初始化对象的成员变量。带参数的构造函数可以接受初始化参数,而默认构造函数则不接受参数。

代码语言:javascript
代码运行次数:0
运行
复制
// 带参数的构造函数
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {
    std::cout << "调用带参数的构造函数" << std::endl;
}

// 默认构造函数
Date() : _year(0), _month(0), _day(0) {
    std::cout << "调用默认构造函数" << std::endl;
}

当我们创建对象时,构造函数会被调用。例如:

代码语言:javascript
代码运行次数:0
运行
复制
Date date1(2024, 5, 28); // 调用带参数的构造函数
Date date3; // 调用默认构造函数
2.2 拷贝构造函数

拷贝构造函数用于创建一个新的对象作为已有对象的副本。它接受一个常量引用作为参数。

代码语言:javascript
代码运行次数:0
运行
复制
// 拷贝构造函数
Date(const Date& other) : _year(other._year), _month(other._month), _day(other._day) {
    std::cout << "调用拷贝构造函数" << std::endl;
}

当我们用一个已有对象初始化新对象时,拷贝构造函数会被调用。例如:

代码语言:javascript
代码运行次数:0
运行
复制
Date date2 = date1; // 调用拷贝构造函数
2.3 赋值运算符重载

赋值运算符重载用于定义对象之间的赋值操作。它返回一个对当前对象的引用,以支持连续赋值。

代码语言:javascript
代码运行次数:0
运行
复制
// 赋值运算符重载
Date& operator=(const Date& other) {
    if (this != &other) { // 检测自我赋值
        _year = other._year;
        _month = other._month;
        _day = other._day;
        std::cout << "调用赋值运算符重载" << std::endl;
    }
    return *this; // 支持连续赋值
}

当我们将一个对象赋值给另一个对象时,赋值运算符重载会被调用。例如:

代码语言:javascript
代码运行次数:0
运行
复制
date3 = date1; // 调用赋值运算符重载
2.4 析构函数

析构函数用于在对象生命周期结束时清理资源。

代码语言:javascript
代码运行次数:0
运行
复制
// 析构函数
~Date() {
    std::cout << "调用析构函数" << std::endl;
}

当对象超出其作用域时,析构函数会被调用。例如:

代码语言:javascript
代码运行次数:0
运行
复制
{
    Date tempDate; // 创建临时对象
} // tempDate 超出作用域,调用析构函数
2.5 显示日期

display函数用于输出日期。

代码语言:javascript
代码运行次数:0
运行
复制
// 显示日期
void display() const {
    std::cout << _year << "-" << _month << "-" << _day << std::endl;
}

运行上面的代码,将会输出以下内容:

代码语言:javascript
代码运行次数:0
运行
复制
调用带参数的构造函数
2024-5-28
调用拷贝构造函数
2024-5-28
调用默认构造函数
调用赋值运算符重载
2024-5-28
调用析构函数
调用析构函数
调用析构函数
7.深度剖析C++中的const成员函数

const成员函数是指被const修饰的成员函数,这种修饰实际作用于该成员函数的隐含this指针,表明在该成员函数中不能对类的任何成员进行修改。通过以下问题的解答,我们可以深入理解const成员函数的行为。

问题1: const对象可以调用非const成员函数吗?

不可以。因为非const成员函数可能会修改对象的状态,而const对象保证其状态不会被改变。

问题2: 非const对象可以调用const成员函数吗?

可以。const成员函数不会修改对象的状态,因此非const对象可以调用它。

问题3: const成员函数内可以调用其它的非const成员函数吗?

不可以。因为非const成员函数可能会修改对象的状态,而在const成员函数内不能修改对象的状态。

问题4: 非const成员函数内可以调用其它的const成员函数吗?

可以。非const成员函数可以调用const成员函数,因为const成员函数不会修改对象的状态。

示例代码

下面我们通过一个示例来说明这些概念。

代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

class MyClass {
public:
    // 构造函数
    MyClass(int value) : _value(value) {}

    // 非const成员函数
    void setValue(int value) {
        _value = value;
    }

    // const成员函数
    int getValue() const {
        return _value;
    }

    // 非const成员函数调用const成员函数
    void printValue() {
        std::cout << "Value: " << getValue() << std::endl; // 调用const成员函数
    }

    // const成员函数尝试调用非const成员函数
    void trySetValue(int value) const {
        // setValue(value); // 错误:const成员函数不能调用非const成员函数
    }

private:
    int _value;
};

int main() {
    MyClass obj(42);

    // 非const对象可以调用非const成员函数
    obj.setValue(100);

    // 非const对象可以调用const成员函数
    std::cout << "Value: " << obj.getValue() << std::endl;

    // const对象不能调用非const成员函数
    const MyClass constObj(42);
    // constObj.setValue(100); // 错误:const对象不能调用非const成员函数

    // const对象可以调用const成员函数
    std::cout << "Value: " << constObj.getValue() << std::endl;

    return 0;
}
解释
构造函数和成员变量
代码语言:javascript
代码运行次数:0
运行
复制
MyClass(int value) : _value(value) {}

构造函数初始化成员变量_value

const成员函数
代码语言:javascript
代码运行次数:0
运行
复制
void setValue(int value) {
    _value = value;
}

const成员函数可以修改成员变量。

const成员函数
代码语言:javascript
代码运行次数:0
运行
复制
int getValue() const {
    return _value;
}

const成员函数不能修改成员变量。

const成员函数调用const成员函数
代码语言:javascript
代码运行次数:0
运行
复制
void printValue() {
    std::cout << "Value: " << getValue() << std::endl;
}

const成员函数可以调用const成员函数。

const成员函数尝试调用非const成员函数
代码语言:javascript
代码运行次数:0
运行
复制
void trySetValue(int value) const {
    // setValue(value); // 错误:const成员函数不能调用非const成员函数
}

const成员函数不能调用非const成员函数。

main函数示例
代码语言:javascript
代码运行次数:0
运行
复制
int main() {
    MyClass obj(42);

    // 非const对象可以调用非const成员函数
    obj.setValue(100);

    // 非const对象可以调用const成员函数
    std::cout << "Value: " << obj.getValue() << std::endl;

    // const对象不能调用非const成员函数
    const MyClass constObj(42);
    // constObj.setValue(100); // 错误:const对象不能调用非const成员函数

    // const对象可以调用const成员函数
    std::cout << "Value: " << constObj.getValue() << std::endl;

    return 0;
}
8.取地址及const取地址操作符重载

在C++中,取地址运算符(&)和const取地址运算符是两个默认成员函数,编译器会自动生成这些函数。通常情况下,我们不需要重新定义它们。但在某些特殊情况下,例如我们希望控制取地址运算符的行为,让它返回特定的内容时,才需要重载它们。下面我们将详细解释这些概念,并通过代码示例帮助理解。

1. 取地址运算符(&

取地址运算符用于获取对象的内存地址。在大多数情况下,编译器会生成默认的取地址运算符。但有时候我们希望取地址运算符返回特定的内容,这时就需要重载它。

2. const取地址运算符

const取地址运算符类似于取地址运算符,但它只能在const对象上调用。编译器也会生成默认的const取地址运算符,我们可以根据需要重载它。

示例代码

为了帮助理解,我们将实现一个示例类 MyClass,并重载其取地址运算符和const取地址运算符。

代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

class MyClass {
public:
    MyClass(int value) : _value(value) {}

    // 重载取地址运算符
    int* operator&() {
        std::cout << "调用重载的取地址运算符" << std::endl;
        return &_value;
    }

    // 重载const取地址运算符
    const int* operator&() const {
        std::cout << "调用重载的const取地址运算符" << std::endl;
        return &_value;
    }

    void display() const {
        std::cout << "Value: " << _value << std::endl;
    }

private:
    int _value;
};

int main() {
    MyClass obj(42);
    const MyClass constObj(100);

    obj.display();
    constObj.display();

    // 调用重载的取地址运算符
    int* addr = &obj;
    std::cout << "非const对象地址指向的值: " << *addr << std::endl;

    // 调用重载的const取地址运算符
    const int* constAddr = &constObj;
    std::cout << "const对象地址指向的值: " << *constAddr << std::endl;

    return 0;
}
代码解释

构造函数:用于初始化对象的成员变量 _value

重载取地址运算符:返回对象的 _value 的地址,并打印一条信息。

代码语言:javascript
代码运行次数:0
运行
复制
int* operator&() {
    std::cout << "调用重载的取地址运算符" << std::endl;
    return &_value;
}

重载const取地址运算符:返回const对象的 _value 的地址,并打印一条信息。

代码语言:javascript
代码运行次数:0
运行
复制
const int* operator&() const {
    std::cout << "调用重载的const取地址运算符" << std::endl;
    return &_value;
}

显示成员函数:显示对象的 _value

代码语言:javascript
代码运行次数:0
运行
复制
void display() const {
    std::cout << "Value: " << _value << std::endl;
}

main函数:创建非const对象和const对象,调用重载的取地址运算符和const取地址运算符。

代码语言:javascript
代码运行次数:0
运行
复制
int main() {
    MyClass obj(42);
    const MyClass constObj(100);

    obj.display();
    constObj.display();

    // 调用重载的取地址运算符
    int* addr = &obj;
    std::cout << "非const对象地址指向的值: " << *addr << std::endl;

    // 调用重载的const取地址运算符
    const int* constAddr = &constObj;
    std::cout << "const对象地址指向的值: " << *constAddr << std::endl;

    return 0;
}
运行示例

运行上面的代码,将会输出以下内容:

代码语言:javascript
代码运行次数:0
运行
复制
Value: 42
Value: 100
调用重载的取地址运算符
非const对象地址指向的值: 42
调用重载的const取地址运算符
const对象地址指向的值: 100
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-06-14,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

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

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 默认构造函数(Default Constructor)
  • 2. 析构函数(Destructor)
  • 3. 拷贝构造函数(Copy Constructor)
  • 4. 拷贝赋值运算符(Copy Assignment Operator)
  • 5. 移动构造函数(Move Constructor)
  • 2.构造函数详解
    • 2.1 构造函数的概念
    • 示例类:Date
    • 2.2 构造函数的特性
    • 示例代码:构造函数重载
    • 使用构造函数
    • 编译器生成的默认构造函数
    • C++11中的改进
    • 总结
  • 3.析构函数详解
    • 3.1 析构函数的概念
    • 示例类:Date
    • 3.2 析构函数的特性
    • 示例代码:编译器自动生成的析构函数
    • 资源管理示例:Stack类
    • 总结
  • 4.拷贝构造函数详解
    • 4.1 拷贝构造函数的概念
    • 示例类:Date
    • 4.2 拷贝构造函数的特征
    • 浅拷贝与深拷贝
    • 示例代码:浅拷贝与深拷贝
    • 拷贝构造函数的典型调用场景
    • 总结
  • 5.赋值运算符重载详解
    • 5.1 运算符重载概述
      • 示例:运算符重载函数原型
    • 5.2 赋值运算符重载
      • 赋值运算符重载格式
      • 示例类:Stack
    • 赋值运算符重载的注意事项
    • 前置++和后置++重载
      • 示例代码:前置和后置自增运算符重载
    • 总结
  • 6.日期类的实现
    • 1. 定义Date类
  • 2. 详细解析每个部分
    • 2.1 构造函数
    • 2.2 拷贝构造函数
    • 2.3 赋值运算符重载
    • 2.4 析构函数
    • 2.5 显示日期
  • 7.深度剖析C++中的const成员函数
    • 问题1: const对象可以调用非const成员函数吗?
    • 问题2: 非const对象可以调用const成员函数吗?
    • 问题3: const成员函数内可以调用其它的非const成员函数吗?
    • 问题4: 非const成员函数内可以调用其它的const成员函数吗?
  • 示例代码
  • 解释
    • 构造函数和成员变量
    • 非const成员函数
    • const成员函数
    • 非const成员函数调用const成员函数
    • const成员函数尝试调用非const成员函数
    • main函数示例
  • 8.取地址及const取地址操作符重载
    • 1. 取地址运算符(&)
    • 2. const取地址运算符
  • 示例代码
  • 代码解释
    • 运行示例
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档