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

news2024/10/6 14:17:03

 

目录

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
    

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Mybatis】映射文件中#{}里的其他属性

#{}里除了可以写接收参数的名称外&#xff0c;还可以设置javaType&#xff0c;jdbcType&#xff0c;mode&#xff0c;numericScale,resultMap,typeHandler,jdbcTypeName这些属性的。 其他的属性除mode&#xff08;存储过程相关讲到存储过程的时候在讲&#xff09;外使用方式都…

单链表的相关题目

1.删除链表中给定值val的所有结点 public void removeall(int key) {//由于是删除链表中所有和key值相同的结点,所以可以设置两个ListNode类型的数据,一个在前面,一个在后面.//直到前面的走到链表的最后,这样完成了遍历.//先判断一下这个链表是否为空if(headnull){System.out.…

Vim安装与配置教程(解决软件包Vim没有安装可候选)

一、Vim检测是否安装 1-输入vi查看是否安装&#xff1b; 2-按Tab键&#xff0c;显示以下字符为未安装&#xff1b; 3-显示以下字符为已安装&#xff08;可以看到有Vim&#xff09; 二、Vim安装过程 1. 打开终端&#xff0c;输入 sudo apt install vim; 2. 输入Y/y&#xff…

STM32Cube系列教程11:使用STM32 RNG硬件随机数模块生成彩票号码

文章目录 配置RNG模块编写代码获取生成的随机数运行测试 今天写段代码测试一下STM32U083RC的(RNG)硬件随机数模块 顺便写个小demo生成7位真随机数的彩票号码&#xff0c;帮助那些买彩票还有选择困难症的人群 (doge)(手动狗头)。 全部代码以上传到github&#xff1a;https://gi…

C++ (week5):Linux系统编程3:线程

文章目录 三、线程1.线程的基本概念①线程相关概念②我的理解 2.线程的基本操作 (API)(1)获取线程的标识&#xff1a;pthread_self(2)创建线程&#xff1a;pthread_create()(3)终止线程①pthread_exit()&#xff1a;当前线程终止&#xff0c;子线程主动退出②pthread_cancel()&…

C语言 | Leetcode C语言题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; int** generate(int numRows, int* returnSize, int** returnColumnSizes) {int** ret malloc(sizeof(int*) * numRows);*returnSize numRows;*returnColumnSizes malloc(sizeof(int) * numRows);for (int i 0; i < numRows; i) {re…

【RocketMQ】安装RocketMQ5.2.0(单机版)

下载 官网下载地址&#xff1a;下载 | RocketMQ github地址&#xff1a;Tags apache/rocketmq GitHub 选择对应的版本下载。https://dist.apache.org/repos/dist/release/rocketmq/5.2.0/rocketmq-all-5.2.0-bin-release.zip 5.2.0的二进制包&#xff1a;下载地址 5.2.0的…

C语言 | Leetcode C语言题解之第117题填充每个节点的下一个右侧节点指针II

题目&#xff1a; 题解&#xff1a; void handle(struct Node **last, struct Node **p, struct Node **nextStart) {if (*last) {(*last)->next *p;}if (!(*nextStart)) {*nextStart *p;}*last *p; }struct Node *connect(struct Node *root) {if (!root) {return NULL…

随机森林算法实现分类

随机森林算法实现对编码后二进制数据的识别 1.直接先上代码&#xff01; import numpy as np import pandas as pd from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import …

数据结构之堆(优先级队列)

前言 在上一章我们讲了二叉树&#xff0c;这一节我们来讲堆&#xff08;优先级队列&#xff09;&#xff0c;所以想知道堆创建&#xff0c;可以看一下二叉树的一些简单概念。http://t.csdnimg.cn/4jUR6http://t.csdnimg.cn/4jUR6 目录 前言 堆 1.概念 2.优先级队列的模拟实…

Day06-Mybatis

1. Mybatis介绍 2. Mybatis连接数据库并返回数据事例 连接oracle数据的设置方式 spring.application.namespringboot-mybatis spring.datasource.driver-class-nameoracle.jdbc.OracleDriver spring.datasource.urljdbc:oracle:thin:192.168.100.66:1521:orcl spring.datasour…

每日一题《leetcode--59.螺旋矩阵 》

https://leetcode.cn/problems/spiral-matrix-ii/ 这道题跟我昨天发布的那道题一模一样&#xff0c;只需要注意这个矩阵是n*n。 文章代码如下&#xff1a; int** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {int** array (int**)malloc(sizeof(int*) *…

Python | Leetcode Python题解之第118题杨辉三角

题目&#xff1a; 题解&#xff1a; class Solution:def generate(self, numRows: int) -> List[List[int]]:ret list()for i in range(numRows):row list()for j in range(0, i 1):if j 0 or j i:row.append(1)else:row.append(ret[i - 1][j] ret[i - 1][j - 1])ret…

HCIP-Datacom-ARST自选题库__BGP多选【22道题】

1.BGP认证可以防止非法路由器与BGP路由器建立邻居&#xff0c;BGP认证可以分为MD5认证和Keychain认证&#xff0c;请问以下哪些BGP报文会携带BCGP Keychain认证信息?(报头携带) open Update Notication Keepalive 2.传统的BGP-4只能管理IPv4单播路由信息&#xff0c;MP-B…

总线带宽(总线系统的数据传送速率)

定义 总线上每秒钟传输的最大字节数或比特数 表示方法 通常使用“比特率”来表示&#xff0c;单位为比特每秒&#xff08;bps&#xff0c;b/s&#xff09;。 计算公式 总线带宽总线宽度/传输周期 其中&#xff0c;总线宽度是指数据总线的位数&#xff08;单位&#xff1a…

GBB和Prob IoU[旋转目标检测理论篇]

在开始介绍YOLOv8_obb网络之前,需要先介绍一下arxiv.org/pdf/2106.06072 这篇文章的工作,因为v8_obb就是基于这篇论文提出的GBB和prob IoU来实现旋转目标检测的。 1.高斯分布 一维高斯分布的规律是中间高两边低,且当x为均值的时候取到最大值,表达式如下,标准正态分布图如…

数据库(10)——图形化界面工具DataGrip

以后关于数据库的图片演示就使用DataGrip了 : ) 创建数据库和表 在连接上数据库之后&#xff0c;可以选择Schema创建一个新的数据库。 点击OK后&#xff0c;就已经创建了一个空的表。 要在数据库中建立一张新的表&#xff0c;右键数据库&#xff0c;点击new table 要给新表添…

基于开源项目HAL STM32F4 +DSP库跑SVPWM开环速度测试

HAL STM32F4 ARM DSP库跑SVPWM开环速度测试 ✨本篇硬件电路和代码来源于此开源项目&#xff1a;https://github.com/MengYang-x/STM3F401-FOC/tree/main&#x1f4cd;硬件电路和项目介绍&#xff0c;立创开源广场&#xff1a;https://oshwhub.com/shadow27/tai-yang-neng-wu-re…

STL库--string

目录 string的定义 string中内存的访问 string常用函数实例解析 string的定义 定义string的方式跟基本类型相同&#xff0c;只需要在string后跟上变量名即可&#xff1a; string str; 如果要初始化&#xff0c;可以直接给string类型的变量进行赋值&#xff1a; string s…

Visual Studio 的使用

目录 1. 引言 2. 安装和配置 2.1 系统要求 2.2 安装步骤 2.3 初次配置 3. 界面介绍 3.1 菜单栏和工具栏 3.2 解决方案资源管理器 3.3 编辑器窗口 3.4 输出窗口 3.5 错误列表 3.6 属性窗口 4. 项目管理 4.1 创建新项目 4.2 导入现有项目 4.3 项目属性配置 5. 代…