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

 

目录

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

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取地址运算符

示例代码

代码解释

运行示例


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

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

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

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

class MyClass {
    // 编译器会生成一个默认构造函数
};

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

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

class MyClass {
    // 编译器会生成一个默认析构函数
};

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

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

class MyClass {
    // 编译器会生成一个默认的拷贝构造函数
};

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

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

class MyClass {
    // 编译器会生成一个默认的拷贝赋值运算符
};

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

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

class MyClass {
    // 编译器会生成一个默认的移动构造函数
};

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

2.构造函数详解

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

2.1 构造函数的概念

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

示例类:Date

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

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;
};

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

Date today(2024, 5, 28); // 调用构造函数
today.display(); // 输出: 2024-5-28
2.2 构造函数的特性

构造函数具有以下特性:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载
示例代码:构造函数重载
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;
};
使用构造函数
Date defaultDate; // 调用默认构造函数
Date specificDate(2024, 5, 28); // 调用带参数的构造函数

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

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

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允许在类定义时为内置类型成员变量提供默认值:

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 类,不需要特别的资源管理,因此可以使用编译器生成的默认析构函数。

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;
};

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

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++编译系统自动调用析构函数
示例代码:编译器自动生成的析构函数
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类

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

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 类,通过拷贝构造函数可以创建一个与已存在对象相同的新对象。

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. 若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数按内存存储字节序进行拷贝,这种拷贝叫做浅拷贝或值拷贝。内置类型按字节方式直接拷贝,自定义类型调用其拷贝构造函数完成拷贝。
浅拷贝与深拷贝
  • 浅拷贝:仅拷贝对象中的值,不考虑资源的深层次复制。
  • 深拷贝:不仅拷贝对象中的值,还对对象中涉及的资源进行深层次复制。
示例代码:浅拷贝与深拷贝
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 类,通过赋值运算符重载可以定义对象之间的赋值操作。

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. 如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理,则必须实现
前置++和后置++重载

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

示例代码:前置和后置自增运算符重载
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类

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

#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 构造函数

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

// 带参数的构造函数
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 date1(2024, 5, 28); // 调用带参数的构造函数
Date date3; // 调用默认构造函数
2.2 拷贝构造函数

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

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

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

Date date2 = date1; // 调用拷贝构造函数
2.3 赋值运算符重载

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

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

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

date3 = date1; // 调用赋值运算符重载
2.4 析构函数

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

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

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

{
    Date tempDate; // 创建临时对象
} // tempDate 超出作用域,调用析构函数
2.5 显示日期

display函数用于输出日期。

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

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

调用带参数的构造函数
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成员函数不会修改对象的状态。

示例代码

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

#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;
}

解释

构造函数和成员变量
MyClass(int value) : _value(value) {}

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

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

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

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

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

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

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

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

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

main函数示例
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取地址运算符。

#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;
}

代码解释

  1. 构造函数:用于初始化对象的成员变量 _value
  2. 重载取地址运算符:返回对象的 _value 的地址,并打印一条信息。
    int* operator&() {
        std::cout << "调用重载的取地址运算符" << std::endl;
        return &_value;
    }
    
  3. 重载const取地址运算符:返回const对象的 _value 的地址,并打印一条信息。
    const int* operator&() const {
        std::cout << "调用重载的const取地址运算符" << std::endl;
        return &_value;
    }
    
  4. 显示成员函数:显示对象的 _value
    void display() const {
        std::cout << "Value: " << _value << std::endl;
    }
    
  5. main函数:创建非const对象和const对象,调用重载的取地址运算符和const取地址运算符。
    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: 42
    Value: 100
    调用重载的取地址运算符
    非const对象地址指向的值: 42
    调用重载的const取地址运算符
    const对象地址指向的值: 100
    

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/656151.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【RK3288 Android10 T8pro usb hid-multitouch idc配置】

【RK3288 Android10 T8pro usb hid-multitouch idc配置】 文章目录 【RK3288 Android10 T8pro usb hid-multitouch idc配置】背景代码分析1. 读取配置文件2. 标志内外置屏幕3. 设置输入设备4. findviewport()5. 根据对应的viewport来计算相应的mapping的参数 结论 背景 T8pro …

C++网络编程——socket

在服务器中&#xff0c;需要建立一个socket套接字才能对外提供一个网络通信接口&#xff0c;在Linux系统中套接字仅是一个文件描述符&#xff0c;也就是一个int类型的值 socket概念 socket 的原意是“插座”&#xff0c;在计算机通信领域&#xff0c;socket 被翻译为“套接字…

骆驼大赛

目录 一&#xff0c;主版图 二&#xff0c;骰子 三&#xff0c;初始设置 四&#xff0c;核心规则 五&#xff0c;结算 这是适合5-8人玩的一个概率推理类的回合制桌游。 一&#xff0c;主版图 赛道由16个格子组成&#xff0c;编号为1-16。 一共7个骆驼&#xff0c;其中正…

python如何巧妙地利用内置函数与列表切片组织舞会派对

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言 二、问题分析 三、解决方案 1. 利用内置函数创建参会人员名单 2. 利用列表切片…

【教学类-58-06】黑白三角拼图06(1页3张彩色黑点卡片,一种宫格36张,适合一个班级一次操作)

作品展示 背景需求 【教学类-58-05】黑白三角拼图05&#xff08;2-10宫格&#xff0c;每个宫格随机1张-6张&#xff0c;带空格纸&#xff0c;1页3张黑白3张白卡&#xff09;-CSDN博客文章浏览阅读343次&#xff0c;点赞10次&#xff0c;收藏6次。【教学类-58-05】黑白三角拼图…

基于深度强化学习的无人车自适应速度规划

论文&#xff1a;Adaptive speed planning for Unmanned Vehicle Based on Deep Reinforcement Learning 编辑&#xff1a;东岸因为一点人工一点智能 基于深度强化学习的无人车自适应速度规划本文对无人车辆的速度规划部分进行了一些改进。首先&#xff0c;将车辆速度与车辆与…

Excel中怎样将第一行建立好的规则套用到每一行?

考虑使用条件格式来完成&#xff0c;有两种方式可以尝试&#xff1a; 一、一次性创建条件格式 1.选中需要设置条件格式的区域&#xff0c;如果是不连续的区域&#xff0c;可以按住Ctrl键&#xff0c;然后用鼠标依次选中需要的数据区域 2.点击 开始选项卡&#xff0c;条件格式…

探索python循环逻辑的魅力:从无限到有限

新书上架~&#x1f447;全国包邮奥~ python实用小工具开发教程http://pythontoolsteach.com/3 欢迎关注我&#x1f446;&#xff0c;收藏下次不迷路┗|&#xff40;O′|┛ 嗷~~ 目录 一、引言&#xff1a;循环逻辑的初步认识 二、无限循环&#xff1a;持续运转的引擎 三、有…

OpenHarmony Camera源码分析

一、简介 当前&#xff0c;开源在科技进步和产业发展中发挥着越来越重要的作用&#xff0c;OpenAtom OpenHarmony&#xff08;简称“OpenHarmony”&#xff09;赋予了开发者孕育创新的种子&#xff0c;也为数字化产业发展开辟了一片土壤。深开鸿是开源的坚定践行者&#xff0c…

云服务器平台AutoDL--基本介绍与使用感受

因为课程作业需要复现DreamBooth&#xff0c;找了几个教程之后&#xff0c;发现了AutoDL这个好东西&#xff0c;芜湖~ 相关概念 以下回答来自于ChatGPT。 云计算平台&#xff1a;云服务器平台是提供按需计算资源和服务的在线平台&#xff0c;通常包括存储、处理能力、数据库、…

所以研究生有不变胖的吗?

天天吃 记得和骏骏一样减肥 分享昨天无人机拍的照片

新零售收银解决方案:传统门店超市的数字化-亿发

在数字化浪潮的推动下&#xff0c;零售行业正经历着前所未有的变革。阿里巴巴提出的“新零售”概念&#xff0c;不仅仅是一个商业口号&#xff0c;它代表了一种全新的商业模式和运营理念。随着时代的进步和消费需求的不断升级&#xff0c;新零售的兴起已成为行业发展的必然趋势…

关于我转生从零开始学C++这件事:升级Lv.25

❀❀❀ 文章由不准备秃的大伟原创 ❀❀❀ ♪♪♪ 若有转载&#xff0c;请联系博主哦~ ♪♪♪ ❤❤❤ 致力学好编程的宝藏博主&#xff0c;代码兴国&#xff01;❤❤❤ OK了老铁们&#xff0c;又是一个周末&#xff0c;大伟又来继续给大家更新我们的C的内容了。那么根据上一篇博…

Python读取Excel表格文件并绘制多列数据的曲线图

本文介绍基于Python语言&#xff0c;读取Excel表格数据&#xff0c;并基于给定的行数范围内的指定列数据&#xff0c;绘制多条曲线图&#xff0c;并动态调整图片长度的方法。 首先&#xff0c;我们来明确一下本文的需求。现有一个.csv格式的Excel表格文件&#xff0c;其第一列为…

鸿蒙OS开发:【一次开发,多端部署】(音乐专辑主页)

一多音乐专辑主页 介绍 本示例展示了音乐专辑主页。 头部返回栏: 因元素单一、位置固定在顶部&#xff0c;因此适合采用自适应拉伸&#xff0c;充分利用顶部区域。专辑封面: 使用栅格组件控制占比&#xff0c;在小尺寸屏幕下封面图与歌单描述在同一行。歌曲列表: 使用栅格组…

【详细介绍WebKit的结构】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

2.搜索游戏中不可见的数据坐标

内容参考于&#xff1a; 易道云信息技术研究院VIP课 首先一般游戏在设计时会把人物相关的属性放到一起&#xff0c;在 1.搜索游戏中的数据与环境搭建 里也能证实&#xff0c;角色的等级、攻击力、生命值、经验等它们的内存地址都是相差4字节也就是用int类型存储的&#xff0c;…

QT学习(20):QStyle类

Qt包含一组QStyle子类&#xff0c;这些子类&#xff08;QWindowsStyle&#xff0c;QMacStyle等&#xff09;模拟Qt支持的不同平台的样式&#xff0c;默认情况下&#xff0c;这些样式内置在Qt GUI模块中&#xff0c;样式也可以作为插件提供。 Qt的内置widgets使用QStyle来执行几…

PostgreSQL入门简介

PostgreSQL 是一个功能强大、开源的对象关系型数据库管理系统&#xff0c;以其稳定性、可靠性和丰富的功能集著称。以下是对 PostgreSQL 的入门简介&#xff1a; 1. 什么是 PostgreSQL&#xff1f; PostgreSQL 是一个开源的关系数据库管理系统&#xff0c;支持 SQL&#xff08;…

SpringBoot整合SpringSecurit,实现ajax的登录、退出、权限校验

1、本文章中SpringBoot整合SpringSecurity&#xff0c;只是基于session方式&#xff0c;并且没有使用到redis。 2、登录、登出都是通过ajax的方式进行。 项目目录&#xff1a; 1、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xm…