前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >专栏 >类和对象(万字总结!深度总结了类的相关知识)(中)

类和对象(万字总结!深度总结了类的相关知识)(中)

作者头像
suye
发布2024-10-16 09:48:19
发布2024-10-16 09:48:19
10100
代码可运行
举报
文章被收录于专栏:17的博客分享17的博客分享
运行总次数:0
代码可运行

1. 类的6个默认构造函数

C++ 编译器会为每个类自动生成以下6个默认成员函数(除非显式定义)。如果用户没有定义这些函数,编译器会生成默认实现:

2. 构造函数
2.1 构造函数的特点:
  • 定义:构造函数是在对象创建时自动调用的特殊成员函数,用于初始化对象的成员变量和分配资源。构造函数的名称与类名相同,且没有返回值。
  • 自动调用:每当对象被创建时,构造函数就会被调用(无论是栈上还是堆上分配的对象)。例如:
代码语言:javascript
代码运行次数:0
运行
复制
A obj; // 自动调用构造函数
A* ptr = new A(); // 自动调用构造函数
  1. 作用:用于初始化对象成员变量,分配资源。
  2. 命名:构造函数的名字与类名相同(在C++中),没有返回类型(即使是void也没有)。
  3. 重载:构造函数可以被重载,即可以定义多个构造函数,参数列表不同以实现不同的初始化方法。
  4. 默认构造函数:如果不显式定义构造函数,编译器会提供一个默认的无参构造函数。
  5. 初始化列表:在C++中,构造函数可以通过初始化列表直接初始化成员,效率更高。
  6. 隐式调用:当对象以临时变量或局部变量形式被创建时,构造函数通常会隐式调用。
  • 注意:无参数和全缺省构造函数都称为默认构造函数,并且默认构造函数只能有一个。因为两者重载,无参调用时会存在歧义。
2.2 构造函数的类型:
  1. 默认构造函数
  • 无参数构造函数,用于对象的默认初始化。
  • 如果类没有用户定义的构造函数,编译器会生成一个默认构造函数。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
   A() {
       // 默认构造函数
       std::cout << "Default constructor called" << std::endl;
   }
};
  1. 带参数的构造函数
  • 允许用户传递参数来初始化对象的成员变量。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
   int x, y;
   A(int a, int b) : x(a), y(b) {
       std::cout << "Parameterized constructor called" << std::endl;
   }
};
  1. 拷贝构造函数
  • 允许通过现有对象来创建新对象。它的原型是A(const A& other),用于拷贝已有对象。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
   A(const A& other) {
       std::cout << "Copy constructor called" << std::endl;
   }
};
  1. 移动构造函数(C++11 引入)
  • 用于移动对象而非拷贝对象,特别是涉及资源管理时(如动态内存、文件句柄等),移动构造可以显著提高性能。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
   A(A&& other) noexcept {
       std::cout << "Move constructor called" << std::endl;
   }
};
2.3 构造函数初始化列表:

构造函数的初始化列表可以用于高效地初始化成员变量,特别是当成员是类类型或常量时。它比在构造函数体内赋值更优,因为成员变量会在进入构造函数体之前被初始化。

代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
	int x;
	const int y;
	A(int a, int b) : x(a), y(b) {  // 初始化列表
  		std::cout << "Constructor with initializer list called" << std::endl;
	}
};

优势

  • 避免成员变量被默认构造再赋值,减少性能开销。
  • 必须用于const成员或引用类型成员的初始化,因为它们只能被初始化一次。
2.4 内置类型和自定义类型:
  • 内置类型没有构造函数,编译器自动管理其生命周期。
  • 自定义类型通过构造函数初始化对象,可以提供无参、带参构造函数、重载构造函数、以及拷贝构造函数。
3. 析构函数
3.1 析构函数的特点:
  • 定义:析构函数用于对象销毁时的清理工作,释放资源(如内存、文件句柄等)。析构函数的名称与类名相同,前面带有 ~ 符号,且不接受参数也没有返回值。
  • 自动调用:对象生命周期结束时,析构函数会自动调用。当对象超出作用域或者被显式删除(通过delete),析构函数会被触发。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
   ~A() {
       std::cout << "Destructor called" << std::endl;
   }
};

int main() {
   A obj; // 栈上对象,超出作用域时析构
   A* ptr = new A(); // 堆上对象
   delete ptr; // 显式调用析构函数
}
  1. 作用:用于释放资源(如内存、文件句柄等),执行清理工作。
  2. 命名:析构函数的名字与类名相同,但前面加一个波浪号(~),没有返回类型,也不接受参数。
  3. 不能重载:析构函数不能被重载,类中只能有一个析构函数。
  4. 默认析构函数:如果不显式定义析构函数,编译器会提供一个默认析构函数,通常用于简单对象的清理。
  5. 执行顺序:在类的继承关系中,析构函数按照派生类到基类的顺序逆向调用,即先调用派生类的析构函数,然后调用基类的析构函数。
  6. 异常处理:析构函数不应抛出异常,如果析构函数抛出异常,可能导致程序崩溃或不确定行为。
3.2 析构函数的用途:
  • 释放动态分配的内存:如果类在构造函数中动态分配了内存(如使用new),析构函数中需要释放这些资源,避免内存泄漏。
代码语言:javascript
代码运行次数:0
运行
复制
class A {
private:
   int* data;
public:
   A() {
       data = new int[10]; // 动态分配资源
   }
   ~A() {
       delete[] data; // 释放资源
       std::cout << "Destructor called" << std::endl;
   }
};
  • 关闭文件或释放系统资源:析构函数可以用于关闭文件、释放锁、断开数据库连接等操作。
3.3 析构函数的调用顺序:
  • 对象销毁时,析构函数调用的顺序是与构造函数相反的顺序。即:最先构造的成员变量会最后被析构,最先调用的基类构造函数也会最后调用其析构函数。

总结:

  1. 一般情况下,有动态申请资源,就需要写析构函数释放资源。
  2. 没有动态申请的资源,不需要写析构。
  3. 需要释放资源的成员都是自定义类型,不需要写析构。
4.拷贝构造函数
4.1 拷贝构造函数的定义与特点

拷贝构造函数是用来通过已有对象初始化新对象的特殊构造函数。它的主要作用是复制一个对象的所有成员,从而创建一个新的与原对象相同的对象。

4.2 拷贝构造函数的定义

在C++中,拷贝构造函数的定义通常如下:

代码语言:javascript
代码运行次数:0
运行
复制
class A {
public:
	A(const A& other);  // 拷贝构造函数
};
  • 参数:它接受一个自身类型对象的引用作为参数,通常是const引用,避免在拷贝时对原对象的修改。
  • 默认实现:如果程序员没有显式定义拷贝构造函数,编译器会提供一个默认的拷贝构造函数,该函数会对对象的每个成员变量进行浅拷贝(逐成员复制)。
4.3 拷贝构造函数的特点
  1. 使用场景

当通过另一个对象来初始化新对象时,拷贝构造函数会被调用。例如:

代码语言:javascript
代码运行次数:0
运行
复制
A obj1;  // 默认构造函数
A obj2 = obj1;  // 调用拷贝构造函数

当对象被按值传递给函数时,也会调用拷贝构造函数。

当函数按值返回对象时,也会调用拷贝构造函数。

  1. 浅拷贝 vs 深拷贝
  • 浅拷贝:默认拷贝构造函数执行浅拷贝,即简单地复制对象的成员变量。如果对象包含指针成员,浅拷贝只会复制指针的地址,而不会复制指针指向的内容。
  • 深拷贝:对于包含指针等动态分配内存的类,需要显式定义拷贝构造函数来执行深拷贝,即创建新对象时分配新的内存并复制原指针指向的内容。
4.4 深拷贝构造函数的示例

以下是一个简单的类示例,演示了拷贝构造函数的工作方式:

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

class A {
public:
	int* data;

	// 构造函数
	A(int val) {
		data = new int(val);
		cout << "Constructor called, value: " << *data << endl;
	}

	// 拷贝构造函数(深拷贝)
	A(const A& other) {
		data = new int(*(other.data));  // 分配新内存,并复制数据
		cout << "Copy Constructor called, value: " << *data << endl;
	}

	// 析构函数
	~A() {
		delete data;
		cout << "Destructor called, value deleted" << endl;
	}
};

int main() {
	A obj1(10);        // 调用普通构造函数
	A obj2 = obj1;     // 调用拷贝构造函数(深拷贝)
	return 0;
}

输出结果:

代码语言:javascript
代码运行次数:0
运行
复制
Constructor called, value: 10
Copy Constructor called, value: 10
Destructor called, value deleted
Destructor called, value deleted

说明:

  • obj1 使用普通构造函数创建,分配了动态内存并存储值10。
  • obj2 是通过拷贝构造函数创建的,它分配了新的内存,并将obj1的数据复制给它(深拷贝)。
  • 程序结束时,两个对象都会调用各自的析构函数来释放内存。
  • 构造函数参数是 int,是因为你希望通过传递一个整数值来初始化对象。
  • 成员变量是 int* 指针,是为了在对象内部动态管理内存,提供深拷贝和析构的能力。这样每个对象都有自己的独立数据,避免了指针共享带来的潜在问题。
4.5 浅拷贝和深拷贝的区别:
  • 如果使用浅拷贝,多个对象可能会指向同一块内存区域,导致析构时重复释放同一内存,产生双重释放问题(内存管理错误),一个修改会影响另一个。
  • 深拷贝避免了这个问题,因为每个对象都有自己独立的内存空间。
5.赋值运算符重载

运算符重载(Operator Overloading)是C++的一种特性,它允许你为类定义或重载运算符,以便让这些运算符能够处理自定义数据类型。运算符重载使得自定义类可以使用与内置类型相同的运算符进行操作,提高了代码的可读性和一致性。

在C++中,赋值运算符的重载允许你定义如何将一个对象的值赋给另一个对象。其标准格式是:

代码语言:javascript
代码运行次数:0
运行
复制
class ClassName {
public:
// 赋值运算符重载
	ClassName& operator=(const ClassName& other);
};

详细解释:

  1. 返回类型
  • 赋值运算符通常返回对象本身的引用(ClassName&),以支持链式赋值(例如 a = b = c;)。
  1. 参数
  • 赋值运算符接受一个const引用作为参数,这个引用指向要赋值的对象(const ClassName& other)。使用const是为了保证赋值操作不会修改源对象。
5.1 赋值运算符重载的步骤
  1. 自我赋值检查:为了防止对象自我赋值(即a = a),需要先检查赋值的对象与当前对象是否相同。
  2. 释放已有资源:如果当前对象已经持有动态资源(如指针指向的内存),在分配新资源之前,需要释放它们,以避免内存泄漏。
  3. 深拷贝资源:如果赋值的对象有动态资源,则需要为当前对象分配新的资源,并将数据复制过来。
  4. 返回当前对象:返回当前对象的引用(*this),使得赋值运算符可以支持连续赋值(如 a = b = c)。
5.2 赋值运算符重载的实现示例

以下是一个类的赋值运算符重载的示例,包含指针的深拷贝操作:

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

class A {
public:
	int* data;

	// 构造函数
	A(int val) {
		data = new int(val);	// 动态分配内存,并用 val 初始化
		cout << "Constructor called, value: " << *data << endl;
	}

	// 拷贝构造函数(深拷贝)
	A(const A& other) {
		data = new int(*(other.data));
		cout << "Copy Constructor called, value: " << *data << endl;
	}

	// 赋值运算符重载(深拷贝)
	A& operator=(const A& other) {
		if (this == &other) {  // 自我赋值检查
			return *this;  // 如果是自我赋值,直接返回当前对象
		}

		delete data;  // 释放当前对象的已有资源
		data = new int(*(other.data));  // 分配新内存并复制数据
		cout << "Assignment Operator called, value: " << *data << endl;

		return *this;  // 返回当前对象的引用
	}

	// 析构函数
	~A() {
		delete data;	// 释放动态分配的内存
		cout << "Destructor called, value deleted" << endl;
	}
};

int main() {
	A obj1(10);        // 调用构造函数
	A obj2(20);        // 调用构造函数

	obj2 = obj1;       // 调用赋值运算符重载
	A obj3 = obj1;     // 调用拷贝构造函数

	return 0;
}

输出结果:

代码语言:javascript
代码运行次数:0
运行
复制
Constructor called, value: 10
Constructor called, value: 20
Destructor called, value deleted
Assignment Operator called, value: 10
Copy Constructor called, value: 10
Destructor called, value deleted
Destructor called, value deleted
Destructor called, value delete

详细解释:

  1. 构造函数A obj1(10)A obj2(20) 分别为两个对象分配动态内存,并初始化它们的值为 1020
  2. 赋值运算符重载obj2 = obj1 会调用赋值运算符重载。首先进行自我赋值检查,接着释放 obj2 原有的动态内存(值为 20),然后为 obj2 分配新内存并将 obj1 的值 10 复制过来。
  3. 拷贝构造函数A obj3 = obj1 会调用拷贝构造函数,通过深拷贝为 obj3 分配独立的内存并复制 obj1 的值。
  4. 析构函数:在 main 函数结束时,所有对象(obj1obj2obj3)都会调用析构函数,释放各自的动态内存。

自我赋值的处理:

在赋值运算符重载中,检查自我赋值是非常重要的。假设不进行自我赋值检查,可能会出现如下情况:

代码语言:javascript
代码运行次数:0
运行
复制
obj1 = obj1;  // 自我赋值
  • 如果不检查 this == &otherdelete data 会释放 obj1 的动态内存,而接下来的 data = new int(*(other.data)) 会访问已被释放的内存,导致未定义行为。

为什么返回 *this?

返回 *this(即当前对象的引用)是为了支持连续赋值操作。例如:

代码语言:javascript
代码运行次数:0
运行
复制
A obj1(10), obj2(20), obj3(30);
obj3 = obj2 = obj1;  // 支持链式赋值

通过返回当前对象的引用,赋值运算符可以被连续调用,从而支持链式操作。

总结:

  • 赋值运算符重载允许我们为类对象赋值时,控制动态资源的分配与管理,避免浅拷贝带来的问题(如内存泄漏或多次释放同一块内存)。
  • 在重载赋值运算符时,注意自我赋值检查、资源管理(如内存释放)和深拷贝操作。
5.3 日期类中的运算符重载
1. += 运算符重载

+= 运算符用于将指定的值加到当前对象上。它通常会修改当前对象的状态,并返回对象本身的引用。

代码语言:javascript
代码运行次数:0
运行
复制
Date& operator+=(int days) {
	// 增加指定的天数
	for (int i = 0; i < days; ++i) {
		incrementDay();  // 增加一天
	}
	return *this;  // 返回当前对象的引用,以支持链式操作
}
2. + 运算符重载

+ 运算符用于生成一个新的对象,该对象的值是当前对象加上指定的天数。它通常不会修改当前对象,而是返回一个新的对象。

代码语言:javascript
代码运行次数:0
运行
复制
Date operator+(int days) const {
	Date temp = *this;  // 创建当前对象的副本
	temp += days;       // 使用 `+=` 运算符增加天数
	return temp;        // 返回新的日期对象
}
3. 前置++运算符重载
代码语言:javascript
代码运行次数:0
运行
复制
Date& operator++() {
incrementDay();	 // 增加一天
return *this;	// 返回当前对象的引用
}
4. 后置++运算符重载
代码语言:javascript
代码运行次数:0
运行
复制
Date operator++(int) {
Date temp = *this; // 保存当前日期
incrementDay();
return temp; // 返回原日期
}

前置++和后置++的重载区别:

  • 前置++:先++后使用 ===>返回值是++后的值

this指向的对象函数结束后不会销毁,因此可以使用引用返回

  • 后置++:先使用后++ ===>返回值是++前的值

前置++和后置++都是一元运算符,为了使前置++和后置++能正确重载,C++规定:后置++运算符重载时多增加一个int类型的参数,但调用函数时该参数不用传递,由编译器自动传递。后置++要返回旧值,因此需要创建临时变量存储++前的值,最终返回的也是旧值,因为旧值存放在临时变量中,因此只能传值返回,不能引用返回。

  • 注意:对于后置++来说,它比前置++多了两次拷贝,一次调用拷贝构造,一次传值返回;因此对于自定义类型的变量,尽量使用前置++
5. << 运算符重载

在C++中,通常我们会为类重载输入/输出流运算符(<<)以实现自定义的输入和输出操作。对于日期类来说,你可能希望通过重载流插入运算符(<<)来格式化日期的输出。然而,这个重载函数通常不能作为类的成员函数,以下是一些原因:

  • 运算符的左操作数必须是非类类型

流插入运算符<<的左操作数通常是标准输出流对象(如std::ostream)。如果你将<<作为类的成员函数来重载,那么左操作数将隐式绑定为类的实例,即第一个操作数必须是类的对象。但在我们通常的用法中,左操作数是std::ostream,而不是类的实例。

例如,假设你有一个Date类:

代码语言:javascript
代码运行次数:0
运行
复制
class Date {
  int year, month, day;
  // 以成员函数形式的流插入运算符重载
  std::ostream& operator<<(std::ostream& out) {
      out << year << "年" << month << "月" << day << "日";
      return out;
  }
};

上面的代码会有问题,因为<<的左操作数必须是std::ostream,而类成员函数的隐含调用this指针的方式会将Date对象作为左操作数,这与标准使用方式相冲突。以下是示例代码:

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

class Date {
  int year, month, day;
  // 以成员函数形式的流插入运算符重载

public:
  ostream& operator<<(std::ostream& out) {
      out << year << "年" << month << "月" << day << "日";
      return out;
  }
  Date(int year, int month, int day) : year(year), month(month), day(day) {}
};

int main()
{
  Date d(2023, 9, 9);
  // cout << d; // 这里左操作数是std::cout

  d << cout;  // 正确操作,但是与标准操作不符
  return 0;
}

解决方案:我们通常将<<运算符重载为非成员函数,并且通常定义为友元函数以便它能访问类的私有成员。

代码语言:javascript
代码运行次数:0
运行
复制
class Date {
private:
	int year;
	int month;
	int day;

	friend ostream& operator<<(ostream& out, const Date& d);
};
  • 返回类型: ostream& 函数返回一个对输出流 (ostream) 的引用。这样做的目的是为了支持连续的流操作符链式调用。
  • 参数:
  • ostream& out: 这是输出流的引用。通常是std::cout或其他ostream类型的流,如文件流ofstream
  • const Date& d: 这是一个常量引用类型的Date对象。使用const确保函数在输出时不会修改传入的Date对象,使用引用避免传递对象时的拷贝,提高效率。
6. >> 运算符重载

<<一样需要定义友元函数以便它能访问类的私有成员,其他注意问题与<<同理解决,建议模仿上述代码自行理解。

返回类型: istream& 函数返回一个对输入流 (istream) 的引用。这使得可以支持链式输入操作。例如,允许这样的代码:

代码语言:javascript
代码运行次数:0
运行
复制
std::cin >> d1 >> d2;

返回流的引用使得多个对象可以通过单个流操作符连续读取。

参数:

  • istream& in: 输入流的引用,通常是 std::cin 或其他 istream 类型的输入流(如文件流 std::ifstream)。
  • Date& d: 一个传递进来的 Date 对象的引用。因为这是一个非 const 引用,意味着可以在函数内对 Date 对象进行修改(即从输入流中读取到的数据将赋值给这个 Date 对象)。
5.4 日期类的全部代码实现
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>
#include <stdexcept>
using namespace std;

class Date {
private:
	int year;
	int month;
	int day;

	// 声明友元函数,方便外部重载的流插入与流提取函数访问私有成员变量 
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

	// 检查是否是闰年
	bool isLeapYear(int year) const {
		return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
	}

// 检查日期是否合法
bool isValidDate(int year, int month, int day) const {
  if (month < 1 || month > 12) return false;
  if (day < 1 || day > daysInMonth(month, year)) return false;
  return true;
}

	// 获取指定月份的天数
	int daysInMonth(int month, int year) const {
		// 每个月的天数,2月在闰年时会有29天
		static const int daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		if (month == 2 && isLeapYear(year)) {
			return 29; // 闰年2月有29天
		}
		return daysInMonth[month - 1]; // 返回非闰年的月份天数
	}

	// 将日期递增一天
	void incrementDay() {
		++day; // 天数加1
		if (day > daysInMonth(month, year)) { // 如果天数超出该月的天数
			day = 1; // 设为1号
			++month; // 月份加1
			if (month > 12) { // 如果月份超出12
				month = 1; // 设为1月
				++year; // 年份加1
			}
		}
	}

public:
	// 构造函数,初始化日期
	    Date(int year, int month, int day) : year(year), month(month), day(day) {
       if (!isValidDate(year, month, day)) 
      throw invalid_argument("Invalid date");
  }

	// 前置 ++ 运算符重载
	Date& operator++() {
		incrementDay(); // 递增一天
		return *this; // 返回当前对象的引用,以支持链式操作
	}

	// 后置 ++ 运算符重载
	Date operator++(int) {
		Date temp = *this; // 创建当前对象的副本
		incrementDay(); // 递增一天
		return temp; // 返回副本,表示递增前的日期
	}

	// += 运算符重载
	Date& operator+=(int days) {
		for (int i = 0; i < days; ++i) {
			incrementDay(); // 增加指定的天数
		}
		return *this; // 返回当前对象的引用,以支持链式操作
	}

	// + 运算符重载
	Date operator+(int days) const {
		Date temp = *this; // 创建当前对象的副本
		temp += days; // 使用 += 运算符增加天数
		return temp; // 返回新的日期对象
	}  
};    

// << 运算符重载    
ostream& operator<<(ostream& out, const Date& d)
{
	out << d.year << "年" << d.month << "月" << d.day << "日" << endl;
	return out;
}

// >> 运算符重载    
istream& operator>>(istream& in, Date& d)
{
	in >> d.year >> d.month >> d.day;
if (!d.isValidDate(d.year, d.month, d.day)) {
  in.setstate(ios::failbit); // 输入非法时设置错误标志
  throw invalid_argument("Invalid date input");
}
	return in;
}

// 测试
int main() {
	Date d(2024, 2, 28); // 创建一个日期对象
	cout << d; // 打印: 2024年2月28日

	++d; // 前置 ++ 操作
	cout << d;; // 打印: 2024年2月29日 (2024年是闰年)

	d++; // 后置 ++ 操作
	cout << d; // 打印: 2024年3月1日

	d += 30; // 增加30天
	cout << d; // 打印: 2024年3月31日

	Date d2 = d + 1; // 使用 + 运算符
	cout << d2; // 打印: 2024年4月1日

	cin >> d;	// 输入2005 3 20
	cout << d;	// 打印:2005年3月20日

	return 0;
}
5.5 不能重载的操作符(笔试常考)
  1. . (成员访问符)
  2. .* (成员指针访问符)
  3. :: (作用域解析符)
  4. ?: (条件运算符)
  5. sizeof(字符长度运算符)
6.const成员函数
6.1 const关键字

我们知道,在C++中,若一个变量声明为const类型,则试图修改该变量的值的操作都被视编译错误。例如,

代码语言:javascript
代码运行次数:0
运行
复制
const int day = 5;
day = 15;	// 错误,day是常量,不能被修改

// a拷贝给b,b的改变不影响a,不涉及权限放大问题
const int a = 10;
int b = a;

// b是a的别名,b的改变会影响a,设计权限放大
const int a = 10;
int& b = a;

// 跟引用类似,涉及权限放大
const int a = 10;
int* pb = &a;
6.2 const的语法示例

在C++中,const成员函数是指那些不修改对象状态(即不修改对象成员变量)的成员函数。这类函数的声明在函数名后面加上const关键字,用来保证这个函数不会修改任何非const的成员变量。

特点:

  1. 不能修改成员变量:const成员函数中,不能修改任何非const成员变量。
  2. 只能调用其他const成员函数:const成员函数中,您只能调用其他const成员函数,不能调用非const成员函数,因为非const函数可能修改对象的状态。
  3. 用于const对象: 如果对象是const类型,则只能调用该类中的const成员函数,否则编译器会报错。

语法:

代码语言:javascript
代码运行次数:0
运行
复制
class MyClass {
public:
  int getValue() const {  // 这是一个 const 成员函数
      return value;
  }

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

private:
  int value;
};

说明:

  • 在上面的例子中,getValue函数被声明为const,这意味着它不能修改MyClass对象的任何成员变量。
  • setValue不是const,因此它可以修改成员变量value

const成员函数的作用:

  1. 提高代码安全性,确保某些操作不会意外修改对象的状态。
  2. 使得const对象可以调用这些函数,保持接口的一致性。

示例:

代码语言:javascript
代码运行次数:0
运行
复制
int main() {
  const MyClass obj;  // 定义一个 const 对象
  obj.getValue();     // 只允许调用 const 成员函数
  // obj.setValue(5);  // 错误:不能调用非 const 成员函数
}

总结:

  1. const对象不可以调用非·成员函数(权限放大不允许)
  2. const对象可以调用const成员函数(权限缩小允许)
  3. const成员函数内不可以调用其他的非const成员函数(权限放大不允许)
  4. 非·成员函数内可以调用其他的const成员函数(权限缩小允许)
  5. 凡是内部不改变成员变量,也就是*this对象数据的,这些成员函数都应该加const修饰
7.取地址及const取地址操作符重载

这两个运算符一般不需要重载,使用编译器默认生成取地址的重载即可。

代码语言:javascript
代码运行次数:0
运行
复制
//日期类
class Date
{
public:
	//取地址重载
	Date* operator&()
	{
		return this;
	}
	//const修饰的取地址操作符重载,给const对象调用
	const Date* operator&() const
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-09-24,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2. 构造函数
    • 2.1 构造函数的特点:
    • 2.2 构造函数的类型:
    • 2.3 构造函数初始化列表:
    • 2.4 内置类型和自定义类型:
  • 3. 析构函数
    • 3.1 析构函数的特点:
    • 3.2 析构函数的用途:
    • 3.3 析构函数的调用顺序:
  • 4.拷贝构造函数
    • 4.1 拷贝构造函数的定义与特点
    • 4.2 拷贝构造函数的定义
    • 4.3 拷贝构造函数的特点
    • 4.4 深拷贝构造函数的示例
    • 4.5 浅拷贝和深拷贝的区别:
  • 5.赋值运算符重载
    • 5.1 赋值运算符重载的步骤
    • 5.2 赋值运算符重载的实现示例
    • 5.3 日期类中的运算符重载
    • 1. += 运算符重载
    • 2. + 运算符重载
    • 3. 前置++运算符重载
    • 4. 后置++运算符重载
    • 5. << 运算符重载
    • 6. >> 运算符重载
    • 5.4 日期类的全部代码实现
    • 5.5 不能重载的操作符(笔试常考)
  • 6.const成员函数
    • 6.1 const关键字
    • 6.2 const的语法示例
  • 7.取地址及const取地址操作符重载
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档