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

news2024/11/18 23:37:43

文章目录

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

在这里插入图片描述

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

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

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

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

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

相关文章

【二叉树】Leetcode 226. 翻转二叉树【简单】

翻转二叉树 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&#xff1a;[4,7,2,9,6,3,1] 解题思路 二叉树翻转操作是指将二叉树中每个节点的左右子树进行交换。具体…

[Android]模拟器登录Google Play失败

问题&#xff1a; 模拟器登录Google Play失败&#xff0c;提示couldnt sign in there was a problem communicating with google servers. try again later. 原因&#xff1a; 原因是模拟器没有连接到互联网&#xff0c;打开模拟器中Google浏览器进行搜索一样不行。 解决&am…

虚拟机如何在原有磁盘上扩容

虚拟机未开启状态–菜单栏–虚拟机–快照–拍摄快照–拍摄快照– 菜单栏–虚拟机–快照–快照管理器–点击刚刚的快照1–删除–是– 文件–新建或者打开–硬盘&#xff08;以本人Win 10.64.3GL为例&#xff09;–虚拟机设置–硬件– 硬盘&#xff08;SATA&#xff09;–磁盘实…

赛氪网亮相中国人工智能产业发展联盟会议,共筑赛事生态新篇章

2024年3月14日至15日&#xff0c;备受瞩目的中国人工智能产业发展联盟&#xff08;AIIA&#xff09;第十一次全体会议在海南海口盛大召开。作为人工智能领域的重要交流与合作平台&#xff0c;此次会议吸引了300余位联盟成员单位代表齐聚一堂&#xff0c;共襄盛举。在这场人工智…

梵宁教育:助力大学生掌握设计新技能

在当今数字化、信息化的社会里&#xff0c;设计技能的重要性日益凸显。设计不仅仅局限于美术或艺术领域&#xff0c;它已经渗透到各行各业&#xff0c;从产品外观到用户界面&#xff0c;从品牌形象到营销策划&#xff0c;设计无处不在。对于大学生而言&#xff0c;掌握设计新技…

ts enum elementUI 表格列表中如何方便的标识不同类型的内容,颜色区分 enum ts + vue3

ts enum elementUI 表格列表中如何方便的标识不同类型的内容&#xff0c;颜色区分 enum ts vue3 本文内容为 TypeScript 一、基础知识 在展示列表的时候&#xff0c;列表中的某个数据可能是一个类别&#xff0c;比如&#xff1a; enum EnumOrderStatus{"未受理" …

python如何获取word文档的总页数

最近在搞AI. 遇到了一个问题&#xff0c;就是要进行doc文档的解析。并且需要展示每个文档的总页数。 利用AI. 分别尝试了chatGPT, 文心一言&#xff0c; github copilot&#xff0c;Kimi 等工具&#xff0c;给出来的答案都不尽如人意。 给的最多的查询方式就是下面这种。 这个…

时序预测 | Matlab实现GWO-BP灰狼算法优化BP神经网络时间序列预测

时序预测 | Matlab实现GWO-BP灰狼算法优化BP神经网络时间序列预测 目录 时序预测 | Matlab实现GWO-BP灰狼算法优化BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现GWO-BP灰狼算法优化BP神经网络时间序列预测&#xff08;完整源码和数据…

kali系统下载,并在VMware免安装式打开kali虚拟机

Kali Linux | Penetration Testing and Ethical Hacking Linux Distribution 上面连接为官网连接 此安装过程也适合其他虚拟机的安装&#xff0c;也就是你曾经安装过的虚拟机&#xff0c;会保存一推文件&#xff0c;而这推文件&#xff0c;还可以用VMware再次打开。

Unity中如何实现草的LOD

1&#xff09;Unity中如何实现草的LOD 2&#xff09;用Compute Shader处理图像数据后在安卓机上不能正常显示渲染纹理 3&#xff09;关于进游戏程序集加载的问题 4&#xff09;预制件编辑模式一直在触发自动保存 这是第379篇UWA技术知识分享的推送&#xff0c;精选了UWA社区的热…

【Linux】线程的概念{虚拟地址堆区细分/缺页中断/页/初识线程/创建线程/优缺点}

文章目录 1.前导知识1.1 虚拟地址空间的堆区1.2 缺页中断1.3ELF文件格式1.4页/页框/页帧/页表/MMU1.5虚拟地址到物理地址 2.初识Linux线程2.1之前所学的进程2.2线程的引入2.3如何理解线程2.4如何理解轻量级进程 3.创建线程3.1pthread_create()函数3.2程序测试3.3Makefile怎么写…

力扣--并查集1631.最小体力消耗路径

这题将图论和并查集联系起来。把数组每个位置看成图中的一个节点。 这段代码的主要思路是&#xff1a; 遍历地图中的每个节点&#xff0c;将每个节点与其相邻的下方节点和右方节点之间的边加入到边集合中&#xff08;因为从上到下和从下到上他们高度绝对值一样的&#xff0c;…

文件IO的方式读取jpeg图片的分辨率

1、读取jpeg图片分辨率的两种方式 1.1 使用libjpeg库 可以使用libjpeg库读取JPEG图像文件&#xff0c;并获取图像的分辨率&#xff08;宽度和高度&#xff09;&#xff0c;简单demo示例如下&#xff1a; #include <stdio.h> #include <jpeglib.h>int main() {st…

6、ChatGLM3-6B 部署实践

一、ChatGLM3-6B介绍与快速入门 ChatGLM3 是智谱AI和清华大学 KEG 实验室在2023年10月27日联合发布的新一代对话预训练模型。ChatGLM3-6B 是 ChatGLM3 系列中的开源模型&#xff0c;免费下载&#xff0c;免费的商业化使用。 该模型在保留了前两代模型对话流畅、部署门槛低等众多…

GPT2从放弃到入门(四)

引言 体验地址&#xff1a;https://huggingface.co/spaces/greyfoss/gpt2-chatbot 上篇文章我们通过Gradio作为前端轻松地连接到训练好的Chatbot&#xff0c;本文介绍如何分享你创建好的模型给你的朋友。 当你训练好的模型推送到Huggingface Hub上后&#xff0c;其实还可以进一…

How to convert .py to .ipynb in Ubuntu 22.04

How to convert .py to .ipynb in Ubuntu 22.04 jupyter nbconvertp2j 最近看到大家在用jupyter notebook&#xff0c;我也试了一下&#xff0c;感觉还不错&#xff0c;不过&#xff0c;也遇到了一些问题&#xff0c;比方说&#xff0c;我有堆的.py文件&#xff0c;如果要一个一…

YOLOv8融入低照度图像增强算法---传统算法篇

YOLOv8n原图检测YOLOv8n增强后检测召回率和置信度都有提升 前言 这篇博客讲讲低照度,大家都催我出一些内容,没想到这么多同学搞这个,恰好我也做过这方面的一些工作,那今天就来讲解一些方法,低照度的图像增强大体分“传统算法”和“深度学习算法”; 目前低照度的图像增…

sqlite3嵌入式开发板命令行方式使用

如何在编译嵌入式版本的sqlite3&#xff0c;请看我上一篇文章 sqlite3 交叉编译-CSDN博客 一、sqlite3命令行方式使用 假如我将编译好的嵌入式的sqlite3放置在如下路径&#xff1a; 进入bin目录进行操作 1.运行sqlite3 运行sqlite3有两种方式 1&#xff09;直接在内存里面…

Wireshark使用相关

1.wireshark如何查看RST包 tcp.flags.reset1 RST表示复位&#xff0c;用来异常的关闭连接&#xff0c;在TCP的设计中它是不可或缺的。发送RST包关闭连接时&#xff0c;不必等缓冲区的包都发出去&#xff08;不像上面的FIN包&#xff09;&#xff0c;直接就丢弃缓存区的包发送R…

Fastgpt 无法启动或启动后无法正常使用的讨论(启动失败、用户未注册等问题这里)

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; FastGPT是非常实用并且相当厉害的个人知识库AI项目&#xff0c;项目是非常…