C++ 类和对象(二)

 

目录

1.前言

2.类的六个默认成员函数

3.构造函数

3.1概念

3.2特性

3.2.1 函数名与类名相同

3.2.2 无返回值

 3.2.3对象实例化时自动调用

 3.2.4 构造函数可以重载

3.2.5 默认构造函数的自动生成

3.2.6 默认构造函数对内置类型成员的初始化

3.2.7 默认构造函数的定义

4.析构函数

4.1概念

4.2特性

4.2.1析构函数名是在类名前加上字符 ~

4.2.2 无参数无返回值类型

4.3.3 一个类只能有一个析构函数

4.3.4 对象生命周期结束时,析构函数自动调用

 4.3.5 编译器自动生成的析构函数

4.3.6 资源管理和析构函数

5.拷贝构造函数

5.1概念

5.2特性

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

5.2.2 参数必须是类类型对象的引用

5.2.3 编译器生成的默认拷贝构造函数

5.2.4 需要显式实现拷贝构造函数的情况

5.2.5 拷贝构造函数典型调用场景

6.赋值运算符重载

6.1运算符重载

6.2赋值运算符重载

6.2.1 赋值运算符重载格式

6.2.2 赋值运算符只能重载为成员函数

6.2.3 编译器生成的默认赋值运算符

6.3前置++与后置++重载

6.3.1前置++重载

6.3.2后置++重载

7.日期类的实现

8.类的const成员

9.取地址操作符重载

10.小结


(图像由AI生成) 

1.前言

在C++ 类和对象(一)中,我们引入了类的定义,并介绍了类的一些相关知识,如类的作用域、实例化和储存等。在本篇博客中,我们将深入探讨C++中类的默认成员函数、构造函数、析构函数、拷贝构造函数、赋值运算符重载以及其他高级特性,进一步揭开C++中“类与对象”的面纱。

2.类的六个默认成员函数

在C++中,即使一个类看起来是空的(即没有声明任何成员变量或成员函数),编译器仍会自动为其生成一些默认成员函数,这使得该类能够进行基本的操作。这些成员函数是:

  1. 默认构造函数:如果没有其他构造函数被声明,编译器将生成一个无参的默认构造函数,用于创建类的对象。

  2. 析构函数:用于在对象生命周期结束时进行清理工作。如果用户没有显式定义析构函数,编译器会提供一个默认的析构函数。

  3. 拷贝构造函数:当新对象需要通过现有对象进行初始化时,拷贝构造函数被调用。如果用户没有定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数,进行成员到成员的拷贝。

  4. 拷贝赋值运算符(=):当一个对象需要通过已存在的同类型的另一个对象进行赋值时,这个运算符被调用。编译器会提供一个默认的拷贝赋值运算符,它执行成员到成员的赋值。

  5. 取地址运算符重载(&):允许对象的地址被取出。编译器自动生成的取地址运算符返回对象的实际内存地址。

  6. const对象取地址运算符重载(const &):这是取地址运算符的一个特殊形式,用于在对象为const时获取其地址。编译器生成的此函数保证即使是const对象,也能安全地获取其地址。

这些自动生成的函数确保了即使是最简单的类也能在C++环境中正确地存储、复制和访问。这种设计反映了C++语言的一项基本原则——提供安全且易于使用的默认行为,同时允许程序员通过显式定义来覆盖这些行为,以满足特定的程序需求。

3.构造函数

3.1概念

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

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

以下面代码为例:

class Box {
public:// Default constructorBox() {}// Initialize a Box with equal dimensions (i.e. a cube)explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list{}// Initialize a Box with custom dimensionsBox(int width, int length, int height): m_width(width), m_length(length), m_height(height){}int Volume() { return m_width * m_length * m_height; }private:// Will have value of 0 when default constructor is called.// If we didn't zero-init here, default constructor would// leave them uninitialized with garbage values.int m_width{ 0 };int m_length{ 0 };int m_height{ 0 };
};

其中:Box(){}即为Box类的一个默认构造函数。至于什么是默认构造函数?Box类中其他几个函数是什么构造函数?且听我细细道来。 

3.2特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任 务并不是开空间创建对象,而是初始化对象。 其特征如下:

3.2.1 函数名与类名相同

构造函数必须与其所在类的名称相同。这是C++用于区分构造函数与其他成员函数的一种约定。

class Widget {
public:int size;// 构造函数名称与类名相同Widget() {size = 10;}
};int main() {Widget w;std::cout << "Widget size: " << w.size << std::endl;  // 输出:Widget size: 10return 0;
}

3.2.2 无返回值

构造函数不能有返回值,甚至不能写 void。它的目的是初始化对象,而不是提供返回值。

class Widget {
public:int size;// 构造函数无返回值Widget() {size = 10;}
};

 3.2.3对象实例化时自动调用

构造函数是在创建类的对象时自动调用的。这确保了对象一被创建就处于一个有效的状态。

class Widget {
public:int size;Widget() {size = 10;std::cout << "Constructor called automatically on object creation." << std::endl;}
};int main() {Widget w;  // 自动调用构造函数return 0;
}

 3.2.4 构造函数可以重载

类可以有多个构造函数,只要它们的参数数量或类型不同即可。

class Widget {
public:int size;std::string color;// 无参数构造函数Widget() : size(10), color("black") {}// 重载构造函数,有参数Widget(int s, std::string c) : size(s), color(c) {}};int main() {Widget w1;  // 调用无参数构造函数Widget w2(15, "red");  // 调用有参数的构造函数std::cout << "w1: size=" << w1.size << ", color=" << w1.color << std::endl;std::cout << "w2: size=" << w2.size << ", color=" << w2.color << std::endl;return 0;
}

3.2.5 默认构造函数的自动生成

如果类中未显式定义任何构造函数,C++编译器会自动生成一个无参的默认构造函数。

class Widget {
public:int size;
};int main() {Widget w;std::cout << "Default constructed Widget size: " << w.size << std::endl;  // 未初始化的size值return 0;
}

3.2.6 默认构造函数对内置类型成员的初始化

在C++11及更高版本中,可以在类定义中为内置类型成员指定默认值。

class Widget {
public:int size = 10;  // 内置类型的成员初始化
};int main() {Widget w;std::cout << "Initialized Widget size: " << w.size << std::endl;  // 输出:Initialized Widget size: 10return 0;
}

3.2.7 默认构造函数的定义

无参数的构造函数和全缺省参数的构造函数都视为默认构造函数。每个类只能有一个默认构造函数。

class Widget {
public:int size;// 默认构造函数Widget(int s = 10) : size(s) {}};int main() {Widget w;  // 使用默认构造函数std::cout << "Widget size: " << w.size << std::endl;  // 输出:Widget size: 10return 0;
}

4.析构函数

析构函数在C++中是类的一个特殊成员函数,它与构造函数的作用相反。构造函数用于对象的创建和初始化,而析构函数则用于对象的清理和释放资源。析构函数确保在对象生命周期结束时,所有由该对象持有的资源都能被妥善处理。

4.1概念

析构函数是一个在对象即将被销毁时自动调用的特殊成员函数,其主要任务是执行清理操作,以防资源泄露(如内存泄漏、文件未关闭等)。析构函数的命名规则非常简单:它的名称是类名前加一个波浪符(~),比如 ~ClassName()

4.2特性

析构函数在C++中是用来处理对象销毁前的清理工作的特殊成员函数。下面详细介绍析构函数的几个关键特性,并通过具体的代码示例加以说明。

4.2.1析构函数名是在类名前加上字符 ~

析构函数的名称由类名前加波浪号(~)组成,这标志着它的特殊作用:清理对象。

class Sample {
public:// 析构函数~Sample() {std::cout << "Destructor called." << std::endl;}
};

4.2.2 无参数无返回值类型

析构函数不能接受参数,也不返回任何值,这保证了析构函数的调用是简单且自动的。

class Sample {
public:~Sample() {// 无参数,无返回值}
};

4.3.3 一个类只能有一个析构函数

一个类中只能定义一个析构函数,它不能被重载,这保证了析构行为的唯一性和简单性。

class Sample {
public:// 唯一的析构函数~Sample() {std::cout << "Destructor called." << std::endl;}
};

4.3.4 对象生命周期结束时,析构函数自动调用

当对象的生命周期结束(如局部对象的作用域结束,或者通过delete删除动态对象)时,C++编译系统自动调用析构函数。

class Sample {
public:~Sample() {std::cout << "Destructor called." << std::endl;}
};int main() {Sample obj; // 当obj的生命周期结束时,析构函数被调用
}

 4.3.5 编译器自动生成的析构函数

如果未显式定义析构函数,编译器会自动生成一个默认的析构函数。这个默认析构函数会调用其成员变量(如果它们是对象)的析构函数。

class Component {
public:~Component() {std::cout << "Component destructor called." << std::endl;}
};class Composite {
public:Component c;// 默认析构函数自动调用成员c的析构函数
};int main() {Composite obj; // 析构时会先调用c的析构函数,然后是obj的
}

4.3.6 资源管理和析构函数

如果类中包含资源申请(如动态内存、文件句柄等),需要显式定义析构函数来释放这些资源。否则,可以依赖编译器生成的默认析构函数。

class Date {// 使用默认析构函数,因为没有特殊资源需要释放
};class Stack {int* data;
public:Stack() {data = new int[10]; // 申请资源}~Stack() {delete[] data; // 必须显式释放资源}
};int main() {Date d; // 使用默认析构函数Stack s; // 使用自定义析构函数释放动态分配的内存
}

5.拷贝构造函数

拷贝构造函数在C++中扮演着重要的角色,它定义了一个对象如何通过使用另一个已存在的同类型对象进行初始化。在深入研究C++的对象复制机制时,理解拷贝构造函数的工作原理至关重要。

5.1概念

拷贝构造函数是一个特殊的构造函数,它在创建一个新对象时,使用同类型的另一个已存在对象作为其初始化源。拷贝构造函数通常具有以下特点:

  • 单一形参:拷贝构造函数通常有一个形参,该形参是对本类类型对象的引用。一般来说,这个参数会使用const修饰符来防止被修改,这是因为在拷贝一个对象时,通常不希望改变源对象。
  • 自动调用:在特定情况下,拷贝构造函数会被编译器自动调用。这包括使用一个对象来初始化另一个对象、将对象作为参数传递给函数(按值传递),或从函数返回一个对象时(虽然现代编译器优化了这一过程,通过返回值优化(RVO)避免不必要的拷贝)。

5.2特性

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

拷贝构造函数是一种特殊的构造函数,用于从同类的另一个对象初始化新对象。作为构造函数的一种,它遵循构造函数的基本原则,但专门用于处理对象的复制。

5.2.2 参数必须是类类型对象的引用

拷贝构造函数的参数是一个对同类对象的引用。如果尝试使用传值方式定义参数,会导致编译错误,因为这样会触发无限递归的拷贝构造调用。

class Widget {
public:int data;// 错误示例:尝试以传值方式接收参数// Widget(Widget w) { }  // 会导致编译错误// 正确的拷贝构造函数Widget(const Widget& w) : data(w.data) {std::cout << "Copy constructor called." << std::endl;}
};

5.2.3 编译器生成的默认拷贝构造函数

如果未显式定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。这个默认的拷贝构造函数执行所谓的"浅拷贝",即直接按字节复制数据。对于内置数据类型,这意味着值会被直接复制;对于包含自定义类型的对象,其相应的拷贝构造函数会被调用。

class Simple {
public:int *ptr;Simple(int val) {ptr = new int(val);}// 默认拷贝构造函数会导致ptr指向相同的内存地址,即浅拷贝
};int main() {Simple obj1(42);Simple obj2 = obj1; // 使用默认拷贝构造函数std::cout << "Original: " << *obj1.ptr << " Copy: " << *obj2.ptr << std::endl;// 改变obj1将影响obj2*obj1.ptr = 21;std::cout << "Original: " << *obj1.ptr << " Copy: " << *obj2.ptr << std::endl;
}

 

5.2.4 需要显式实现拷贝构造函数的情况

当类包含资源如动态内存时,应显式定义拷贝构造函数以实现深拷贝,避免资源泄漏或多重释放。

class Stack {int* data;int size;
public:Stack(int sz) : size(sz), data(new int[sz]) { }  // 分配资源// 实现深拷贝Stack(const Stack& s) : size(s.size), data(new int[s.size]) {std::copy(s.data, s.data + s.size, data);}~Stack() {delete[] data;}
};int main() {Stack s1(10);Stack s2 = s1; // 使用自定义的深拷贝拷贝构造函数
}

5.2.5 拷贝构造函数典型调用场景

拷贝构造函数在多种场景下被调用:

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象(按值传递)
  • 函数返回值类型为类类型对象(虽然通常优化掉)
void display(Widget w) {  // 按值传递会调用拷贝构造函数std::cout << "Displaying data: " << w.data << std::endl;
}Widget createWidget() {Widget w(10);return w;  // 返回对象可能调用拷贝构造函数(通常优化为移动构造)
}int main() {Widget a(20);Widget b = a;  // 直接使用拷贝构造函数display(a);  // 调用时使用拷贝构造函数Widget c = createWidget();  // 可能使用拷贝构造函数
}

6.赋值运算符重载

在C++中,运算符重载允许程序员为用户定义的类型指定操作符的行为。这样可以使得用户定义的类型在表达式中使用起来更自然,提高代码的直观性和可读性。赋值运算符重载是这其中一个非常常用的例子。

6.1运算符重载

运算符重载基本上是一种特殊的函数,它使用特定的语法格式定义。其目的是让用户定义的类型能够使用标准C++运算符进行操作。运算符重载可以让对象之间的操作更加符合直觉,例如,通过重载+运算符使得两个复数对象可以直接相加。

基本语法:

ReturnType operatorOp(ArgType arg);

这里ReturnType是运算符重载函数的返回类型,operatorOp是关键字operator后跟需要重载的运算符,而ArgType是参数类型。

注意点:

  • 不能创建新的操作符:C++不允许创造新的运算符。例如,operator@是不合法的。
  • 至少一个操作数是用户定义的类型:这确保了重载运算符是针对用户定义的类型,而不是内置类型。
  • 不能改变内置类型运算符的行为:例如,不能为内置的int类型重载+运算符来改变其原本的加法行为。
  • 某些运算符不能重载:包括.(点), .*, ::(作用域限定符), sizeof ?:(三目运算符)

6.2赋值运算符重载

6.2.1 赋值运算符重载格式

赋值运算符通常被重载为一个成员函数,以控制对象如何处理赋值操作。这个函数通常遵循以下的格式:

  • 参数类型const T&,使用常量引用作为参数类型,这样可以提高参数传递的效率,并避免不必要的对象复制。
  • 返回值类型T&,返回对象自身的引用。这样做可以提高效率,并且支持连续赋值,例如a = b = c;
  • 自赋值检查:检查赋值是否是对象自身赋值给自己,如果是,则直接返回,避免不必要的操作和潜在错误。
  • 返回*this:函数通过返回当前对象的引用(*this)来支持连续赋值。

示例:日期类的赋值运算符重载

class Date {
public:int day, month, year;Date(int d = 1, int m = 1, int y = 1970): day(d), month(m), year(y) {}// 赋值运算符重载Date& operator=(const Date& other) {if (this != &other) {  // 避免自赋值day = other.day;month = other.month;year = other.year;}return *this;  // 返回当前对象的引用}
};int main() {Date date1(10, 10, 2010);Date date2;date2 = date1;  // 调用赋值运算符Date date3;date3 = date2 = date1;  // 连续赋值return 0;
}

6.2.2 赋值运算符只能重载为成员函数

赋值运算符必须作为类的成员函数来重载。它不能被定义为全局函数因为赋值运算符需要访问对象的内部数据

6.2.3 编译器生成的默认赋值运算符

如果开发者没有显式实现赋值运算符,编译器将自动生成一个默认的赋值运算符。这个默认赋值运算符会执行浅拷贝,逐字节地复制对象的内容。对于类的成员变量:

  • 内置类型的成员变量会直接被赋值。
  • 自定义类型的成员变量将调用其各自的赋值运算符来完成赋值。

这种默认行为通常足够处理简单的类,但对于涉及动态内存分配或其他需要显式管理的资源的类,通常需要自定义赋值运算符以实现深拷贝,防止资源泄露或重复释放问题。

6.3前置++与后置++重载

在C++中,递增运算符++可以以两种形式存在:前置++和后置++。这两者在功能上相似,但在表达式中的行为和返回值有所不同。通过对这两种形式进行重载,可以让用户定义的类型(如日期类)使用这些运算符以符合直觉的方式递增其值。

6.3.1前置++重载

前置++重载通常返回对象的引用,并且递增对象的值之前就完成了递增操作。这意味着它返回的是递增后的对象。

语法:

T& operator++();

6.3.2后置++重载

后置++重载在表达式中稍微复杂一些,因为它需要返回递增前的对象的一个副本。这需要在递增操作执行后进行。为了区分前置和后置,后置++接受一个额外的int参数,但这个参数在实际中并不提供(只是用来区分前置和后置)。

语法:

T operator++(int);

让我们以一个简单的日期类为例,展示如何重载这两个运算符。

#include <iostream>class Date {
public:int day, month, year;Date(int d = 1, int m = 1, int y = 1970): day(d), month(m), year(y) {}// 前置++重载Date& operator++() {++day; // 增加天数normalize(); // 调整日期return *this;}// 后置++重载Date operator++(int) {Date temp = *this; // 保存当前状态++day; // 增加天数normalize(); // 调整日期return temp; // 返回未修改前的日期}void normalize() {// 假设每月30天简化逻辑if (day > 30) {day = 1;++month;if (month > 12) {month = 1;++year;}}}void print() {std::cout << day << "/" << month << "/" << year << std::endl;}
};int main() {Date date(30, 12, 1999);date.print();  // 输出:30/12/1999++date;        // 使用前置++date.print();  // 输出:1/1/2000date++;        // 使用后置++date.print();  // 输出:2/1/2000return 0;
}

在这个示例中:

  • 前置++:先递增day,然后通过调用normalize()方法确保日期保持有效,最后返回递增后的对象。
  • 后置++:先保存当前对象状态,再递增day,通过normalize()调整日期,最后返回原始日期状态的副本。

7.日期类的实现

下面我们给出一种日期类的完整代码实现:

date.h

#pragma once
#include<iostream>
using namespace std;class Date
{
private:int _day;int _month;int _year;
public:Date(int year = 2000, int month = 1, int day = 1);//构造函数Date(const Date& d);//拷贝构造函数~Date();//析构函数Date& operator=(const Date& d);//赋值运算符重载void Print()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;//大于等于运算符重载bool operator!=(const Date&d)const;//不等于运算符重载int getDayOfMonth(int year, int month)const;//获取某年某月的天数Date& operator+=(int day);//+=运算符重载Date operator+(int day)const;//+运算符重载Date& operator++();//前置++运算符重载Date operator++(int);//后置++运算符重载Date& operator-=(int day);//-=运算符重载Date operator-(int day)const;//-运算符重载Date& operator--();//前置--运算符重载Date operator--(int);//后置--运算符重载int operator-(const Date&d)const;//-运算符重载friend ostream& operator<<(ostream& _cout, const Date& d);//<<运算符重载friend istream& operator>>(istream& _cin, Date& d);//>>运算符重载
};

date.cpp

#include"date.h"//构造函数
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}
//拷贝构造函数
Date::Date(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
//析构函数(在日期类中没啥用)
Date::~Date()
{_year = 2000;_month = 1;_day = 1;
}
//赋值运算符重载
Date& Date::operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
//打印日期
void Date::Print()const
{cout << _year << "-" << _month << "-" << _day << endl;
}//等于运算符重载
bool Date::operator==(const Date& d)const
{return _year == d._year && _month == d._month && _day == d._day;
}
//小于运算符重载
bool Date::operator<(const Date& d)const
{if (_year < d._year)return true;else if (_year == d._year){if (_month < d._month)return true;else if (_month == d._month){if (_day < d._day)return true;}}return false;
}
//小于等于运算符重载
bool Date::operator<=(const Date& d)const
{return *this < d || *this == d;
}
//大于运算符重载
bool Date::operator>(const Date& d)const
{return !(*this <= d);
}
//大于等于运算符重载
bool Date::operator>=(const Date& d)const
{return !(*this < d);
}
//不等于运算符重载
bool Date::operator!=(const Date& d)const
{return !(*this == d);
}
//获取某年某月的天数
int Date::getDayOfMonth(int year, int month)const
{int days[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0){days[2] = 29;}return days[month];
}
//+=运算符重载
Date& Date::operator+=(int day)
{if (day < 0){return *this -= -day;}_day += day;while (_day > getDayOfMonth(_year, _month)){_day -= getDayOfMonth(_year, _month);_month++;if (_month > 12){_month = 1;_year++;}}return *this;
}
//+运算符重载
Date Date::operator+(int day)const
{Date ret(*this);ret += day;return ret;
}
//前置++运算符重载
Date& Date::operator++()
{*this += 1;return *this;
}
//后置++运算符重载
Date Date::operator++(int)
{Date ret(*this);*this += 1;return ret;
}
//-=运算符重载
Date& Date::operator-=(int day)
{if (day < 0){return *this += -day;}_day -= day;while (_day < 1){_month--;if (_month < 1){_month = 12;_year--;}_day += getDayOfMonth(_year, _month);}return *this;
}
//-运算符重载
Date Date::operator-(int day)const
{Date ret(*this);ret -= day;return ret;
}
//前置--运算符重载
Date& Date::operator--()
{*this -= 1;return *this;
}
//后置--运算符重载
Date Date::operator--(int)
{Date ret(*this);*this -= 1;return ret;
}int Date::operator-(const Date& d)const
{Date max = *this;Date min = d;int flag = 1;if (max < min){max = d;min = *this;flag = -1;}int count = 0;while (min < max){min++;count++;}return count * flag;
}//<<运算符重载
ostream& operator<<(ostream& _cout, const Date& d)
{_cout << d._year << "-" << d._month << "-" << d._day;return _cout;
}//>>运算符重载
istream& operator>>(istream& _cin, Date& d)
{_cin >> d._year >> d._month >> d._day;return _cin;
}

8.类的const成员

在C++中,const关键字用于声明不可修改的变量。当const修饰一个类的成员函数时,它影响的是这个函数对类成员的操作权限,即确保成员函数不会修改对象的任何成员变量。这种函数被称为const成员函数。

const成员函数的特性

语法:

ReturnType FunctionName(Parameters) const;
  • const位于成员函数的参数列表之后,函数体之前,表示这个函数不会改变任何类的成员变量的状态。
  • 实际上,const修饰的是成员函数隐含的this指针。this指针是一个指向当前对象的指针,用于访问类的成员。在const成员函数中,this指针变为指向const对象的指针,这意味着你不能通过这个this指针修改任何成员变量。

为什么使用const成员函数

  1. 安全保证:const成员函数提供了一个明确的语义保证,即不会修改对象。这是在编程中进行接口设计时非常有用的,特别是在多线程环境下,保证数据的读取不会引起数据状态的改变。

  2. 使用const对象:只有const成员函数才能被const对象调用。如果你有一个const的类对象,你只能调用它的const成员函数。

  3. 逻辑上的正确性:如果一个函数不应改变对象状态,将其声明为const成员函数有助于防止编程错误,这样编译器就可以帮助确保函数的行为符合预期。

示例:使用const成员函数的日期类(详见 7.日期类的实现)

9.取地址操作符重载

在C++中,取地址操作符&通常用来获取变量或对象的内存地址。然而,在特定的情况下,你可能需要对类对象的取地址操作进行特殊处理,比如管理对象在内存中的布局或记录访问情况。为此,可以重载取地址操作符。此外,你还可以重载对const对象的取地址操作符,以适应对const对象的取地址请求。

取地址操作符&的重载

取地址操作符可以被重载为成员函数,来自定义对象地址的获取方式。

语法:

T* operator&(); 
const T* operator&() const;

第一个版本用于非const对象,第二个版本用于const对象。这两个版本确保即使在const环境下,也能正确返回对象的地址。

示例代码

下面是一个简单的示例,展示如何重载取地址操作符,以及如何为const和非const对象分别处理:

#include <iostream>class Widget {
private:int data;public:Widget(int d = 0) : data(d) {}// 重载非const对象的取地址操作符Widget* operator&() {std::cout << "Non-const & operator called." << std::endl;return this;}// 重载const对象的取地址操作符const Widget* operator&() const {std::cout << "Const & operator called." << std::endl;return this;}
};int main() {Widget w(10);const Widget cw(20);// 获取非const对象的地址Widget* pw = &w;// 获取const对象的地址const Widget* pcw = &cw;return 0;
}

但是,这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容。

10.小结

在本博客中,我们深入探讨了C++中类的多个关键概念和特性,包括类的六个默认成员函数、构造函数和其特性、析构函数、拷贝构造函数、赋值运算符重载,以及前置和后置递增运算符的重载。我们还涉及了如何使用const成员函数以增强方法的安全性和重载取地址操作符以适应特定需求。通过这些知识,可以更好地理解和利用C++提供的面向对象能力,编写更加健壮和高效的代码。C++类与对象(三),我们再会!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Docker安装(一)

一、安装Docker 服务器系统&#xff1a;centos 7 1.本地有docker的首先卸载本机docker yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-selinux \docker-engine-selinux \dock…

3_4.mysql数据库的基本管理

### 一.数据库的介绍 ### 1.什么是数据库 数据库就是个高级的表格软件 2.常见数据库 Mysql Oracle mongodb db2 sqlite sqlserver ....... 3.Mysql (SUN -----> Oracle) 4.mariadb ##数据库中的常用名词## ①字段 &#xff1a;表格中的表头 ②表 &#xff1a;表格 ③库 &…

lua基本语法

Lua语法入门 初识lua vi hello.lua print("hello,lua") lua hello.lua 变量和循环 变量 循环 条件控制、函数 条件控制

视觉SLAM学习打卡【11】-尾述

到目前为止&#xff0c;视觉SLAM14讲已经到了终章&#xff0c;历时一个半月&#xff0c;时间有限&#xff0c;有些地方挖掘的不够深入&#xff0c;只能在后续的学习中更进一步。接下来&#xff0c;会着手ORB-SLAM2的开源框架&#xff0c;同步学习C。 视觉SLAM学习打卡【11】-尾…

专业清洁工匠服务网站模板 html网站

目录 一.前言 二.页面展示 三.下载链接 一.前言 该HTML代码生成了一个网页&#xff0c;包括以下内容&#xff1a; 头部信息&#xff1a;指定了网页的基本设置和元数据&#xff0c;例如字符编码、视口大小等。CSS文件&#xff1a;引入了多个CSS文件&#xff0c;用于设置网页…

前端开发攻略---实现与ChatGPT同款光标闪烁打字效果。

1、演示 2、实现代码 <!DOCTYPE html> <html lang"ch-ZN"><head><meta charset"UTF-8" /><meta http-equiv"X-UA-Compatible" content"IEedge" /><meta name"viewport" content"widt…

代码随想录算法训练营第三十七天| LeetCode 738.单调递增的数字、总结

一、LeetCode 738.单调递增的数字 题目链接/文章讲解/视频讲解&#xff1a;https://programmercarl.com/0738.%E5%8D%95%E8%B0%83%E9%80%92%E5%A2%9E%E7%9A%84%E6%95%B0%E5%AD%97.html 状态&#xff1a;已解决 1.思路 如何求得小于等于N的最大单调递增的整数&#xff1f;98&am…

秋叶Stable diffusion的创世工具安装-带安装包链接

来自B站up秋葉aaaki&#xff0c;近期发布了Stable Diffusion整合包v4.7版本&#xff0c;一键在本地部署Stable Diffusion&#xff01;&#xff01; 适用于零基础想要使用AI绘画的小伙伴~本整合包支持SDXL&#xff0c;预装多种必须模型。无需安装git、python、cuda等任何内容&am…

Python文件操作大全

1 文件操作 1.1 文件打开与关闭 1.1.1 打开文件 在Python中&#xff0c;你可以使用 open() 函数来打开文件。以下是一个简单的例子&#xff1a; # 打开文件&#xff08;默认为只读模式&#xff09; file_path example.txt with open(file_path, r) as file:# 执行文件操作…

数组索引哈希表(Array Indexed Hash Table)

数组索引哈希表&#xff08;Array Indexed Hash Table&#xff09; 哈希表&#xff1a;散列表&#xff0c;通过建立key与value之间的映射关系&#xff0c;实现高效的元素查询。 哈希表,数组&#xff0c;链表的查询效率 数组&#xff1a; 查询效率&#xff1a;对于已知索引的位…

HackMyVM-suidy

目录 信息收集 arp nmap WEB web信息收集 gobuster 目录批量查看 hydra ssh连接 提权 系统信息收集 提权 信息收集 arp ┌─[rootparrot]─[~/HackMyVM] └──╼ #arp-scan -l Interface: enp0s3, type: EN10MB, MAC: 08:00:27:16:3d:f8, IPv4: 192.168.9.115 St…

Spring Boot统一功能处理(一)

本篇主要介绍Spring Boot的统一功能处理中的拦截器。 目录 一、拦截器的基本使用 二、拦截器实操 三、浅尝源码 初始化DispatcherServerlet 处理请求&#xff08;doDispatch) 四、适配器模式 一、拦截器的基本使用 在一般的学校或者社区门口&#xff0c;通常会安排几个…

一夜爆红的4款国产软件,却一度被大众误以为是外国人开发

在现今高度信息化的时代&#xff0c;计算机已经深深地渗透到了我们生活的每一个角落。 从日常的办公学习到娱乐休闲&#xff0c;几乎都离不开计算机技术的支持。而在这背后&#xff0c;软件作为计算机的灵魂&#xff0c;其发展历史可谓波澜壮阔。 中国软件产业经过多年的积累和…

Linux 安装KVM虚拟机

什么是KVM虚拟机&#xff1f; KVM 是 Kernel-based Virtual Machine 的缩写&#xff0c;是一种用于虚拟化的开源硬件虚拟化技术。它使用 Linux 内核的虚拟化模块&#xff0c;将物理服务器划分为多个虚拟机。KVM 允许虚拟机直接访问物理硬件资源,从而提供出色的性能和稳定性,同…

基于二级片内硬件堆栈的后向CFI 验证方法研究,第5章 测试及实验结果

5.1 SoC系统功能仿真 按照上一章的设计方案&#xff0c;采用verilog HDL语言&#xff0c;在玄铁E906处理器平台中分别采用延迟验证和批处理验证方法完成了二级硬件堆栈的RTL级硬件描述。为了验证二级硬件堆栈的正确性&#xff0c;采用斐波那契数列进行验证&#xff0c;设置RAB…

【R语言】组合图:散点图+箱线图+平滑曲线图+柱状图

用算数运算符轻松组合不同的ggplot图&#xff0c;如图&#xff1a; 具体代码如下&#xff1a; install.packages("devtools")#安装devtools包 devtools::install_github("thomasp85/patchwork")#安装patchwork包 library(ggplot2) library(patchwork) #p1是…

AMPLE: 基于图简化和增强图表征学习的漏洞检测

GNN已被证实能有效学习源代码的图表示&#xff0c;然而GNN难以处理代码结构图中长距离节点之间连接的局限性&#xff0c;导致无法捕获代码图的全局信息&#xff08;即远距离节点间的依赖关系&#xff09;。针对上述问题&#xff0c;提出漏洞检测框架AMPLE&#xff0c;它包括图简…

机器学习引领金融革命:重塑金融服务领域新格局,开启智能化新篇章

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

我们一起看看《看漫画学C++》中如何介绍的字符串的用法

C中的字符串使用的是 std::string 类型&#xff0c;它是C标准库中提供的字符串类&#xff0c;提供了丰富的字符串操作方法。下面是关于C字符串的一些常用用法&#xff1a; 字符串拼接 字符串查找 字符串追加 购书地址&#xff1a;https://item.jd.com/14418856.html

【教程】一个比较良心的C++代码混淆器

这是一个比较良心的C代码混淆器&#xff0c;用于信息竞赛训练和保护代码免受抄袭。本文将介绍这个混淆器的使用方法、混淆效果和已知的一些bug。同时&#xff0c;我们也会给出一些示例来演示混淆器的具体操作。 引言 在信息竞赛训练和实际开发中&#xff0c;保护代码的安全性和…