【C++】从C到C++、从面向过程到面向对象(类与对象)

文章目录

  • C++入门知识
  • C与C++的关系
  • 1. 类的引入:从结构体到类
  • 2. 类的声明和定义
  • 3. 类的作用域
  • 4. 类的访问限定符
  • 5. 面向对象特性之一:封装
  • 6. 类的实例化:对象
  • 7. 计算类对象的内存大小
  • 8. 成员函数中暗藏的this指针
  • 9. 类的六个默认生成的成员函数
    • 9.1 构造函数
    • 在声明时给成员变量默认值
    • 9.2 析构函数
    • 9.3 拷贝构造函数
    • 运算符重载
    • 9.4 赋值重载
    • 区分拷贝构造和赋值重载
    • 9.5 取地址重载(了解)
    • 最后总结默认成员函数机制

C++入门知识

  1. C++内联函数与宏的对比
  2. C++引用与指针的对比
  3. C++nullptr与NULL的对比
  4. C++auto关键字
  5. C++函数缺省参数
  6. C++函数重载

C与C++的关系

C语言是面向过程(procedure-oriented)的语言,分析出求解问题的步骤,通过函数调用逐步解决问题。C++是面向对象(object-oriented)的语言,将一件事情拆分成不同的对象,靠对象之间的交互完成。

C与C++的关系是:C++即C Plus Plus,是C语言的扩展,文件后缀是.cpp。C程序可以在C++编译器下编译和运行,也就是说编写C++程序可以完全用C的语法去写。

1. 类的引入:从结构体到类

C语言结构体中只能定义变量,在C++中结构体内不仅可以定义变量,也可以定义函数,C++的结构体已经升级到了类(class)的概念。

struct Person {
	char name[20];
	int age;
	char gender;
	int height;
	int weight;

	void showInfo() {
		cout << name << " - " << age << " - " << gender << endl;
	}

	void sleep() {
		
	}

	void washCloth() {

	}

	void readBook() {

	}

	void work() {

	}

	void study() {

	}
};

C++中的struct可以这么做是因为需要兼容C,可以这么认为,类是结构体的升级,事实上C++更喜欢用class关键字表示一个类:

class Person
{
	// 类体由成员变量和成员函数组成
};

在C++中用struct表示类与用class表示类有访问权限的区别,在后面的“类的访问权限”部分中会讲到。

2. 类的声明和定义

类有两种定义方式:

  1. 成员声明和定义全部放在类体中,需要注意的是:成员函数放在类体中一起声明和定义,编译器会将这个函数当成内联函数处理。

  2. 声明和定义分开,仅成员的声明放在类体,且是写在头文件中;而成员函数的定义是写在另外一个.cpp文件中的,推荐使用这种方式。

3. 类的作用域

类定义了一个新的作用域,在类体外定义成员时,需要使用 ::,::是作用域操作符,指明成员属于哪个类域。

// Person.h
class Person {
	char name[20];
	int age;
	char gender;
	int height;
	int weight;

	void showInfo();
	void sleep();
	void washCloth();
	void readBook();
	void work();
	void study();
};
// Person.cpp
void Person::showInfo() {
	cout << name << " - " << age << " - " << gender << endl;
}

4. 类的访问限定符

访问限定符用于确定类成员的访问权限

  1. public:被public修饰的成员在类外可以被访问;
  2. protected:被protected修饰的成员在类外不能被访问,但可以在继承的子类中被访问;
  3. private:被private修饰的成员在类外不能被访问。

如果用struct声明和定义一个类,这个类中所有成员的默认访问权限为public,这是因为需要兼容C,C并没有访问限定符这个语法规则。使用class声明和定义一个类,成员的默认访问权限为private。

5. 面向对象特性之一:封装

面向对象的三大特性:封装、继承、多态。封装是通过private(私有的访问权限)来隐藏对象内部的属性和实现细节,控制哪些函数可以在类外部直接被使用,仅对外公开接口来和对象进行交互。

比如对于电脑这样一个复杂的设备,提供给用户的就只有开关机键、通过键盘输入,显示器,USB插孔等,让用户和计算机进行交互。对于计算机使用者而言,不用关心内部核心部件,主板上线路是如何布局的,CPU内部是如何设计的。

class Person {
private:
	char name[20];
	int age;
	char gender;
	int height;
	int weight;
public:
	void showInfo();
	void sleep();
	void washCloth();
	void readBook();
	void work();
	void study();
};

6. 类的实例化:对象

类是对一个事物进行描述,是一个模型,定义出一个类并没有并没有分配实际的内存空间存储它。使用一个类,需要用这个类创建对象,这个过程称为“类的实例化”。一个类可以实例化出多个对象,实例化出的对象占用实际的内存空间,存储类的成员变量。

比如上面的Person类例子,对这个Person类实例化:

Person zhangsan;
Person lisi;
Person wangwu;

7. 计算类对象的内存大小

类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算
一个类的大小?

其实计算对象的内存大小与计算结构体的大小方法一致,也就是有内存对齐的规则,可以看这篇文章计算结构体的大小了解。也就是说计算对象的内存大小,其实就是计算类的成员变量大小,不包含成员函数。成员函数不会包括在内,是因为成员函数是n个对象共用的,所以存放在公共代码区给这个类的多个对象共用。

在这里插入图片描述

空类比较特殊,它也有大小,占用1个字节空间。给1字节的的逻辑可能是,如果给0字节那么将毫无意义,不如给1个字节,那么空类的作用大概率只是一个占位,表示可能以后会对其进行完善。

8. 成员函数中暗藏的this指针

this指针本质上是“成员函数”的形参,当对象调用成员函数时,编译器将对象地址作为实参传递给this形参,因此this指针并不存放在对象中,而是在栈中(可能也在寄存器中,取决于编译器)。除了static成员函数(后面会提到),每个成员函数的参数中都隐藏了一个this指针,可以调试查看。
在这里插入图片描述

this指针的类型:类的类型* const,如Person* const,this只能在“成员函数”的内部使用。this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要程序员手动传递。

下面两段程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行

class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	 A* p = nullptr;
	 p->Print();
	 return 0;
}

C.正常运行,表面存在空指针问题,但成员函数中并没有使用其它成员,仅仅只是打印一个字符串,不会导致空指针访问。

class A
{ 
public:
    void PrintA() 
	{
	    cout<<_a<<endl;
	}
private:
	int _a;
};
int main()
{
    A* p = nullptr;
    p->PrintA();
    return 0;
}

B.运行崩溃,成员函数中使用了成员变量,实际上是this指针访问的成员,造成了空指针。

9. 类的六个默认生成的成员函数

如果一个类中什么成员都没有,简称为空类。但其实空类并不是什么都没有,编译器会自动生成6个默认成员函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

初始化和清理:

  1. 构造函数:初始化对象;
  2. 析构函数:清理对象中申请的动态内存;

拷贝和赋值:
3. 拷贝构造:使用同类对象创建另一个之前不存在的对象;
4. 赋值重载:把一个对象赋值给另一个已经存在的对象(就是相当于赋值最常规的用法,只不过这里的赋值是改变一个对象的值);

取地址重载:
5. 普通对象取地址重载;
6. const对象取地址重载。

常常会把构造函数、析构函数、拷贝构造函数和赋值重载函数重新去自定义以满足需求,而两个取地址重载基本不会去自定义重载,下面会顺便介绍原因。

9.1 构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建对象时由编译器自动调用,在对象整个生命周期内只调用一次,构造函数并不是开空间创建对象,而是完成对象的初始化工作。C语言中经常会写一个Init()函数用于初始化,构造函数就相当于这个。

class Date {
private:
	int year;
	int month;
	int day;
public:
	Date() { // 无参构造

	}
	Date(int y, int m, int d) { // 带参构造
		year = y;
		month = m;
		day = d;
	}
};

int main() {
	Date date1; // 通过无参构造函数初始化对象,不用跟括号。
	Date date2(2024, 3, 11);
	return 0;
}

构造函数特征如下:

  1. 函数名与类名相同。
  2. 无返回值(意思是不用写返回值,不是void的意思)。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。
  5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
  6. 无参的构造函数、全缺省的构造函数、编译器默认生成的构造函数都称为默认构造函数。这三个默认构造函数只能存在其中一个,因为这三个都可以不用传参,如果同时存在,调用时会产生歧义。
class Date {
private:
	int year;
	int month;
	int day;
public:
	Date() { // 与下面全缺省的构造函数存在冲突

	}
	Date(int y = 2024, int m = 2, int d = 22) {
		year = y;
		month = m;
		day = d;
	}
};

在这里插入图片描述
7.默认构造函数会对类中其它自定类型成员调用的它的默认构造函数,比如Date类中如果包含一个Time类成员:

class Time {
private:
	int hour;
	int minute;
	int second;
public:
	Time() {
		cout << "Time()" << endl;
	}
};

class Date {
private:
	int year;
	int month;
	int day;
	Time time;
public:
	Date(int y = 2024, int m = 2, int d = 22) {
		cout << "Date(int, int, int)" << endl;
		year = y;
		month = m;
		day = d;
	}
};

int main() {
	Date date;
	return 0;
}

在这里插入图片描述

在声明时给成员变量默认值

由于默认生成的构造函数不会进行有效的初始化,给的是随机值,所以C++11开始可以给内置类型(int/char/double等)成员在类中声明时给默认值,如果没有指定初始化则初始化成默认值。

class Date
{
private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	Time _time;
};

9.2 析构函数

与构造函数功能相反,注意不是销毁对象本身,而是对象在销毁时会自动调用析构函数,完成对象中资源的清理工作,就是清理那些申请了内存资源的成员,释放资源。

析构函数的特征如下:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数和无返回值类型。
  3. 一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数。
  4. 析构函数不能重载。
  5. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("Stack malloc failed.");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
  1. 和默认构造函数一样,默认析构函数对自定义类型的成员调用它的析构函数。
class Time
{
public:
	Time() {
		cout << "Time()" << endl;
	}
	 ~Time()
	 {
	 	cout << "~Time()" << endl;
	 }
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	Time _time;
};

int main()
{
	Date d;
	return 0;
}

在这里插入图片描述
如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数。有资源申请时(malloc、new,new后面会提到),一定要重新自定义释放资源(free、delete),否则会造成资源泄漏。

9.3 拷贝构造函数

拷贝构造函数创建一个与已存在对象一样值的新对象,参数只有单个形参,该形参是对本类类型对象的引用,且一般常用const修饰,在用已存在的类型对象创建新对象时由编译器自动调用。

拷贝构造函数特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
class Date
{
public:
	// 构造
	Date(int year = 1900, int month = 1, int day = 1)
	{
	 _year = year;
	 _month = month;
	 _day = day;
	}
	// 拷贝构造
	Date(const Date& d)  
	{
	 _year = d._year;
	 _month = d._month;
	 _day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
  1. 其它任何一个函数,只要形参不是传引用、返回值不是返回引用,调用该函数时编译器首先都会去调用拷贝构造。所以拷贝构造的形参必须是引用,如果不是引用,编译器器会报错,因为按传值的说法来看拷贝构造会引发无穷递归。
void Test1(const Date d) {
	// 首先会先调用拷贝构造
}
void Test2(const Date& d) {
	// 不会调用拷贝构造
}
Date Test3() {
	Date date(2024, 1, 1);
	return date; // 会先调用拷贝构造再返回
}
int main() {
	Date date1;
	Test1(date1); 
	Test2(date1); 
	Test3();
}

调用Test1(const Date d),参数不是引用,所以会先调用拷贝构造:
在这里插入图片描述

拷贝构造的参数不是引用,导致无穷递归:
在这里插入图片描述

  1. 若未显式定义拷贝构造,编译器默认生成的拷贝构造函数按字节序完成拷贝,这种拷贝叫做浅拷贝(值拷贝)。如果类中有需要申请内存资源的成员(有malloc、new的成员),默认的拷贝构造无法完成拷贝,需要自己显示定义完成深拷贝。比如:
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("Stack malloc failed.");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	Stack(const Stack& rStack) {
		_capacity = rStack._capacity;
		_size = rStack._size;
		_array = (DataType*)malloc(sizeof(DataType) * _capacity);
		memcpy(_array, rStack._array, _size);
		//for (int i = 0; i < _size; ++i) {
		//	_array[i] = rStack._array[i];
		//}
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

如果使用编译器默认生成的拷贝构造传入stack1初始化stack2,stack2的array仅仅只是把stack1的array地址值拷贝过来了,意味着共用同一块内存。
在这里插入图片描述
自定义完成深拷贝的拷贝构造,有内存资源申请的成员,地址不一样:
在这里插入图片描述
为了提高程序效率,一般对象传参时,尽量使用引用类型;函数返回值根据实际场景,能用引用尽量使用引用,因为不返回引用的函数,实际上返回前都会去调用拷贝构造,这样会耗费一些时间和临时占用一部分内存空间。

运算符重载

在学习赋值重载之前,还需要了解运算符重载,因为赋值本身也是一种运算符,篇幅较长,不过很好理解。

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,如operator+、operator-等。

  1. 不是所有运算符都能重载,常见能重载的运算符有:+、+=、-、-=、++和–(有规定如何区分前置和后置)、>、>=、<、<=、!=、=。
  2. 运算符重载函数必须有一个类的类型参数。
  3. 流插入 << 操作符、流提取 >> 操作符也可以重载。

下面以实现Date日期类操作理解运算符重载:

Date类声明:

#include <iostream>
#include <cassert>
using std::cout;
using std::endl;

int dayOfMonth[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

class Date {
private:
	int _year;
	int _month;
	int _day;
public:
	// 全缺省的构造函数
	Date(int y = 1970, int m = 1, int d = 1) {
		_year = y;
		_month = m;
		_day = d;
	}

	// 拷贝构造函数、析构函数、赋值重载用编译器默认生成的
	//Date(const Date& d) {}
	//~Date() {}
	//Date& operator=(const Date& d) {} 这个后面会自定义实现

	/* 运算符重载 */
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator!=(const Date& d);

	// 获取某年某月的天数
	int GetDayOfMonth(int year, int month) {
		assert(month >= 1 && month <= 12);
		// 2月并且是闰年
		return month == 2  
			&& (year % 400 == 0 
			|| (year % 100 != 0 && year % 4 == 0))
			? 29 : dayOfMonth[month];
	}

	// 日期+天数
	Date& operator+=(int day);
	// 日期+天数
	Date operator+(int day);
	// 日期-天数
	Date operator-(int day);
	// 日期-=天数
	Date& operator-=(int day);
	// 日期-日期 返回天数
	int operator-(const Date& d);
	
	
	Date& operator++(); // 前置++
	Date operator++(int); // 后置++
	Date& operator--();
	Date operator--(int);

	void Show() {
		cout << _year << "-" + _month << "-" << _day << endl;
	}
};

Date类的成员函数定义(实现这些运算符重载函数):

bool Date::operator>(const Date& d) {
	if (_year > d._year) {
		return true;
	}
	if (_year == d._year) {
		if (_month > d._month) {
			return true;
		}
		if (_month == d._month) {
			return _day > d._day;
		}
	}
	return false;
}

bool Date::operator>=(const Date& d) {
	//return *this > d || *this == d; 
	//		 this->operator>(d) || this->operator==(d);
	return !(*this < d);
}

bool Date::operator==(const Date& d) {
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

bool Date::operator<(const Date& d) {
	//return !(this->operator>=(d));
	return !(*this >= d);
}

bool Date::operator<=(const Date& d) {
	//return !(this->operator>(d));
	return !(*this > d);
}

bool Date::operator!=(const Date& d) {
	return _year != d._year
		|| _month != d._month
		|| _day != d._day;
	//return !(*this == d);
}


/* 先实现+=,再利用已经实现的+=实现+ */
Date& Date::operator+=(int day) {
	_day += day;
	int dayOfMonth = GetDayOfMonth(_year, _month);
	while (_day > dayOfMonth) {
		_day -= dayOfMonth;
		++_month;
		if (_month > 12) {
			_month = 1;
			++_year;
		}
		dayOfMonth = GetDayOfMonth(_year, _month);
	}
	return *this;
}

Date Date::operator+(int day) {
	// Date newdate = *this;
	// 不是调用了赋值重载,实际是调用拷贝构造,
	// 因为*this已经存在,而date开始不存在,详见区分拷贝构造和赋值重载部分。
	Date newdate(*this); // 或Date newdate = *this; 
	newdate += day; // newdate.operator+=(day);
	return newdate;
}

/* 先实现+,再利用已经实现的+实现+=(不推荐) */
//Date& Date::operator+=(int day) {
//	*this = *this + day; // (*this).operator+(day);
//	return *this;
//}
//
//Date Date::operator+(int day) {
//	Date newdate(*this);
//	newdate._day += day;
//	int dayOfMonth = GetDayOfMonth(newdate._year, newdate._month);
//	while (newdate._day > dayOfMonth) {
//		newdate._day -= dayOfMonth;
//		++newdate._month;
//		if (newdate._month > 12) {
//			newdate._month = 1;
//			++newdate._year;
//		}
//		dayOfMonth = GetDayOfMonth(newdate._year, newdate._month);
//	}
//	return newdate;
//}



Date Date::operator-(int day) {
	Date newdate(*this);
	newdate.operator-=(day); // newdate -= day;
	return newdate;
}

Date& Date::operator-=(int day) {
	_day -= day;
	while (_day < 1) {
		--_month;
		if (_month < 1) {
			_month = 12;
			--_year;
		}
		_day += GetDayOfMonth(_year, _month);
	}
	return *this;
}

int Date::operator-(const Date& d) {
	int amountDays1 = _day;
	int month = _month - 1;
	for (int y = _year; y > 0; --y) {
		for (int m = month; m > 0; --m) {
			amountDays1 += GetDayOfMonth(y, m);
		}
		month = 12;
	}

	int amountDays2 = d._day;
	month = d._month - 1;
	for (int y = d._year; y > 0; --y) {
		for (int m = month; m > 0; --m) {
			amountDays2 += GetDayOfMonth(y, m);
		}
		month = 12;
	}
	int gapDay = amountDays1 - amountDays2;
	return gapDay >= 0 ? gapDay : gapDay * -1;
}

// 前置++
Date& Date::operator++() {
	*this += 1; // (*this).operator+=(1);
	return *this;
}
// 后置++
Date Date::operator++(int) {
	Date newdate(*this);
	*this += 1; 
	return newdate;
}

Date& Date::operator--() {
	*this -= 1; 
	return *this;
}
Date Date::operator--(int) {
	Date newdate(*this);
	*this -= 1;
	return newdate;
}

重载前置++或前置–规定返回值必须是引用,其实就是提前–完返回自身。重载后置++或后置++规定返回值不能是引用(不能返回自身,因为得返回++前或–前的值),而且必须有一个int符在参数列表占位,这个参数没有实际作用,纯粹就是用来表明是重载后置++或后置–。

调用运算符重载函数可以正常像函数调用一样,也可以像正常的运算符一样使用:

Date d1(2024, 4, 12);
Date d2(2022, 4, 14);
cout << d1.operator==(d2) << endl;
cout << (d1 == d2) << endl;

cout << d1.operator>(d2) << endl;
cout << (d1 > d2) << endl;

cout << d1.operator>=(d2) << endl;
cout << (d1 >= d2) << endl;

cout << d1.operator<(d2) << endl;
cout << (d1 < d2) << endl;

cout << d1.operator<=(d2) << endl-
cout << (d1 <= d2) << endl;

cout << d1.operator!=(d2) << endl;
cout << (d1 != d2) << endl;

Date d3(d2);
Date& tempRefd3 = d3.operator+=(19);
// Date& tempRefd3 = d3 += 19;

Date d4 = d3.operator+(19);
// Date d4 = d3 + 19;

Date& tempRefd3 = d3.operator-=(9);
//Date& tempRefd3 = d3 -= 9;

Date& tempRefd3 = d3.operator-=(19);
//Date& tempRefd3 = d3 -= 19;

cout << "d1 - d2:" << d1.operator-(d2) << "天" << endl;
cout << "d1 - d2:" << d1 - d2 << "天" << endl;

d1.operator+=(100);
d2 += 2000;
Date date1 = d1.operator+(100);
Date date2 = d2 + 2000;

d1.operator-=(100);
d2 -= 563;
Date date1 = d1.operator-(100);
Date date2 = d2 - 563;

9.4 赋值重载

编译器会默认生成一个赋值重载函数,效果和下面这个自定义实现一样。

Date& operator=(const Date& d) {
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

重点是返回引用和*this的写法。

一般情况下不需要去自定义实现,需要自定义实现的场景可以看下面的注意事项。自定义实现需要注意的是:

  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数。因为如果不显示定义,编译器会生成一个默认的。此时用户再在类外自己实现一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值运算符重载只能是类的成员函数。

  2. 编译器生成的默认赋值运算符重载函数,以值的方式逐字节拷贝(也就是值拷贝,常说的浅拷贝)。内置类型成员变量是直接赋值的,而自定义类型成员变量会调用对应类的赋值运算符重载完成赋值,因为自定义类型成员中究其到底也是内置类型成员。

所以由于编译器默认生成的赋值重载是浅拷贝,如果类中有成员申请内存资源,那么使用默认的赋值重载函数是不合适的,这会导致两个对象共用一块空间,这与之前的拷贝构造函数一样,需要自定义实现完成深拷贝

区分拷贝构造和赋值重载

拷贝构造和赋值重载看起来作用是一样的,但它们有不同的作用和机制。

  1. 拷贝构造创建一个对象,这个对象之前是不存在的。
  2. 赋值重载是对一个对象赋值,这个被赋值的对象其实是已经存在的对象了,所以赋值重载的作用就是就是修改一下值而已,本身赋值的作用就是这样。

那上面实现的Date类举例,可以更好地理解他们的区别。现在还有很多人喜欢这么写,看起来是赋值重载,其实不是:

Date Date::operator+(int day) {
	Date newdate = *this;   
	// Date newdate = *this;不是调用了赋值重载,实际是调用拷贝构造,
	// 因为*this已经存在,而newdate最开始不存在。
	// 所以这样写和写成拷贝构造的方式区别不大:Date newdate(*this);  
	newdate += day; // newdate.operator+=(day);
	return newdate;
}

9.5 取地址重载(了解)

这两个取地址重载函数不用自己去定义,用编译器默认生成的就好了。非要自定义实现的话,也很简单:

class Date {
private:
	int _year;
	int _month;
	int _day;
public:
	Date* operator&() {
		return this;
	}
	// 最后的const表示const成员,和前面那个返回值的const修饰作用不一样,后面会提到。
	const Date* operator&() const {
		return this;
	}
}

如果连这个&引用重载都要自己去定义,那就太麻烦了,毕竟这是使用频率非常高的操作符。C++设计者做了这个语法,我估计完全就是为了完成操作符重载这一概念的逻辑闭环,确实得有这个东西,但一般很少去自己去重新定义。

除非有啥特殊的需求,就是要去重新定义,比如返回空指针、假地址、野指针之类的,但即使是这种需求感觉意义也不大,我看就是恶搞故意整一个bug。比如:

Date* operator&() {
	return nullptr;
}
const Date* operator&() const {
	return (const Date*)0x0012ff40;
	// int a = 10;
	// return (const Date*)&a;
}

在这里插入图片描述

最后总结默认成员函数机制

构造函数和析构函数:不处理内置类型成员,对于自定义类型成员会去调用它自己的构造函数和析构函数。

拷贝构造函数和赋值重载函数:处理内置类型成员,不过仅完成值拷贝(浅拷贝)。对于自定义类型成员会去调用它自己的拷贝构造函数和赋值重载函数。

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

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

相关文章

NVIDIA 发布 Project GR00T 人形机器人基础模型和 Isaac 机器人平台重大更新

系列文章目录 前言 Isaac 机器人平台现可为开发者提供全新的机器人训练仿真器、Jetson Thor 机器人计算机、生成式 AI 基础模型和由 CUDA 加速的感知和操作库。 Project GR00T 是一种多模态人形机器人通用基础模型&#xff0c;作为机器人的大脑&#xff0c;使它们能够学习技能…

【Redis安装】Ubuntu下Redis编译安装

Redis编译安装 文档说明 本文作者:SwBack 创作时间:2024/3/26 17:19:19 知乎:https://www.zhihu.com/people/back-88-87 CSDN:https://blog.csdn.net/qq_30817059 百度搜索: SwBack系统: Ubuntu redis: 2.8.17 下载redis源代码,并进行解压 wget http://download.redis.io/re…

百人一岗,Android开发者的困境。。。。。

前言 在当前的Android开发领域&#xff0c;竞争的激烈程度已经达到了前所未有的水平&#xff0c;几乎到了100个开发者竞争1个岗位的地步。 这种“内卷”现象的背后&#xff0c;是技术的快速发展和市场对Android开发者技能要求的不断提升。随着移动应用的普及和多样化&#xf…

SV-7041VP SIP塑料壳sip音箱支持POE供电(白色弧形)

SV-7041VP SIP塑料壳sip音箱支持POE供电 &#xff08;白色弧形&#xff09; 一、描述18123651365微信 SV-7041VP是深圳锐科达电子有限公司的一款壁挂式SIP网络有源音箱&#xff0c;具有10/100M以太网接口&#xff0c;可将网络音源通过自带的功放和喇叭输出播放&#xff0c;可…

day11-数据统计(图形报表)

1. Apache ECharts 1.1 介绍 Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表。 官网地址&#xff1a;https://echarts.apache.org/zh/index.html 常见效果展示&a…

如何本地部署开源AI知识库 FastGPT(新手教程)

环境: Win10 WSL2 ubuntu22.04 问题描述: 如何本地部署开源AI知识库 FastGPT 解决方案: 一、安装docker和docker-compose 安装docker 1.apt install docker.io2.apt install docker-compose3.docker --version4.docker compose version

基于nodejs+vue学生信息管理系统python-flask-django-php

本论文对学生信息管理系统的需求分析、功能设计、系统设计进行了详细的阐述&#xff0c;并对系统的总体设计进行了阐述&#xff0c;并对各功能的实现和主要功能进行了说明&#xff0c;并附上了相应的操作界面图。 前端技术&#xff1a;nodejsvueelementui, Express 框架于Node运…

2.9 Python缩进规则(包含快捷键)

Python缩进规则&#xff08;包含快捷键&#xff09; 和其它程序设计语言&#xff08;如 Java、C 语言&#xff09;采用大括号“{}”分隔代码块不同&#xff0c;Python采用代码缩进和冒号&#xff08; : &#xff09;来区分代码块之间的层次。 在 Python 中&#xff0c;对于类…

JVM垃圾收集——相关算法

文章目录 1、对象存活判断1.1、引用计数算法1.2、可达性分析算法 2、GC Roots集合2.1、GC Roots2.2、MAT追踪GC Roots的溯源2.3、JProfiler追踪GC Roots的溯源 3、对象的finalization机制4、清除垃圾对象4.1、标记–清除算法4.2、复制算法4.3、标记–压缩算法 5、垃圾收集算法的…

Altium Designer的差分对布线走线技巧及规则设置

AD的PCB页面是有差分对布线的工具的&#xff0c;这种工具的使用首先需要自己添加差分对&#xff0c;才能进行交互式差分对布线&#xff1a; 在原理图中放置差分对标识&#xff0c;其中差分对要以_P和_N结尾来命名&#xff1a; 在原理图中放置差分对&#xff1a; 差分对在PCB中的…

Java编程练习之类的多态

类的多态可以从两方面体现&#xff1a;一是方法的重载&#xff0c;二是类的上下转型。 1&#xff09;方法的重载 方法的重载就是在同一个类中允许同时存在多个同名方法&#xff0c;只要这些方法的参数个数或类型不同即可。 练习1&#xff1a; 使用方法的重载描述所有的超市…

最小割问题合集,最大权闭合图,最大密度子图,最小权点覆盖,最大权独立子图,OJ练习,代码详解

文章目录 零、回顾1、流网络的割2、最小割问题 一、最小割的应用1.1POJ1966 -- Cable TV Network1.1.1原题链接1.1.2思路分析1.1.3AC代码 1.2ZOJ 2676 Network Wars1.2.1原题链接1.2.2思路分析1.2.3AC代码 1.3OPTM - Optimal Marks1.3.1原题链接1.3.2思路分析1.3.3AC代码 二、最…

【网站项目】305银行账目账户管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

对称加密算法的原理和优缺点

对称加密算法是现代信息安全领域中的一项核心技术&#xff0c;它通过使用相同的密钥进行数据的加密和解密操作&#xff0c;确保了信息在传输过程中的安全性和保密性。在这篇文章中&#xff0c;我们将对对称加密算法的基本原理、常见类型、应用场景以及优缺点进行详细的探讨。 基…

[Linux]条件变量:实现线程同步(什么是条件变量、为什么需要条件变量,怎么使用条件变量(接口)、例子,代码演示(生产者消费者模型))

目录 一、条件变量 1.什么是条件变量 故事说明 2、为什么需要使用条件变量 竞态条件 3.什么是同步 饥饿问题 二、条件变量的接口 1.pthread_cond_t 2.初始化&#xff08;pthread_cond_init&#xff09; 3.销毁&#xff08;pthread_cond_destroy&#xff09; 4.等待…

【JSON2WEB】11 基于 Amis 角色功能权限设置页面

【JSON2WEB】01 WEB管理信息系统架构设计 【JSON2WEB】02 JSON2WEB初步UI设计 【JSON2WEB】03 go的模板包html/template的使用 【JSON2WEB】04 amis低代码前端框架介绍 【JSON2WEB】05 前端开发三件套 HTML CSS JavaScript 速成 【JSON2WEB】06 JSON2WEB前端框架搭建 【J…

网安小白入门课程/ Web渗透0基础就业班

想从事渗透相关工作&#xff0c;却不知从何学起&#xff1f;网上自学资料又旧又乱&#xff0c; 学起来时间周期长、效果差&#xff1f;到底学到什么程度才能找到工作&#xff1f; 知识又杂又乱&#xff0c;花了不少时间学习出来却很难成体系&#xff1f; 独自学习枯燥无味&a…

HomeLink项目部署和发布的完整流程

由于Java项目的配置较为繁琐&#xff0c;长时间不使用可能会忘记&#xff0c;因此我特意总结了一下配置流程。 1.软件环境: myeclipse-10.7.1-offline-installer-windows(直接安装) apache-tomcat-7.0.65-windows-x64(Tomcat下载安装以及配置-CSDN博客) JDK(Myeclipse自带…

openGauss学习笔记-252 openGauss性能调优-使用Plan Hint进行调优-Scan方式的Hint

文章目录 openGauss学习笔记-252 openGauss性能调优-使用Plan Hint进行调优-Scan方式的Hint252.1 功能描述252.2 语法格式252.3 参数说明252.4 示例 openGauss学习笔记-252 openGauss性能调优-使用Plan Hint进行调优-Scan方式的Hint 252.1 功能描述 指明scan使用的方法&#…

对 Transformer 中位置编码 Position Encoding 的理解

目录 什么是位置编码 Position Encoding 一、将绝对位置编码加在 Transformer 的输入端 (Sinusoidal 位置编码或可学习位置编码) 二、将绝对位置编码乘在 q k v (RoPE 位置编码) 三、将相对位置编码加在注意力权重 (ALiBi 位置编码) 什么是位置编码 Position Encoding Tr…