C++:类和对象(中)

这篇具有很好参考价值的文章主要介绍了C++:类和对象(中)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

1.类的6个默认成员函数:

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。


默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数
class Date {};

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

2.构造函数:

class Date
{
public:
    void Init(int year, int month, int day)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print()
    {
    cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    Date d1;
    d1.Init(2022, 7, 5);
    d1.Print();

    Date d2;
    d2.Init(2022, 7, 6);
    d2.Print();

    return 0;
}

对于Date类,可以通过 Init 公有方法给对象设置日期,但如果每次创建对象时都调用该方法设置
信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?

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

特性:

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

其特征如下:
        1. 函数名与类名相同。
        2. 无返回值。
        3. 对象实例化时编译器自动调用对应的构造函数。
        4. 构造函数可以重载。

注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

class Date
{
public:
    //Date()
    //{

    //}
    //Date(int year, int month, int day)
    //{
    //    _year = year;
    //    _month = month;
    //    _day = day;
    //}

    Date(int year = 10, int month = 10, int day = 10)
    {
        _year = year;
        _month = month;
        _day = day;
    }

    void Print()
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

int main()
{
    //Date d1();注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
    Date d1;
    d1.Print();

    Date d2(2023,10,31);
    d2.Print();

    return 0;
}

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

        5. 如果在类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

注意:

        如果用户显式定义了构造函数,编译器将不再生成;(即自己写了一个构造函数)

        将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数;

        将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用(因为这里放开后需要的是一个无参的构造函数,取消注释后,不在构造一个默认的无参构造函数,就导致没法与构造函数匹配)

class Date
{
public:
	/*
	// 如果用户显式定义了构造函数,编译器将不再生成
	Date(int year, int month, int day)
	{
	    _year = year;
	    _month = month;
	    _day = day;
	}
	*/
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数
    // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生成
	// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
	Date d1;
	return 0;
}

        6. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
        解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char...,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。内置类型不做处理,自定义类型编译器会去调用默认构造函数。

内置类型:int、char、double、……以及指针

自定义类型:如上面的Date、Time就是我们自己定义的类型。

注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在
类中声明时可以给默认值。

即可以:

private:

        int _year = 10;

        int _month = 10;

        int _day = 10;

class Time
{
public:
    Time()
    {
        cout << "Time()" << endl;
        _hour = 0;
        _minute = 0;
        _second = 0;
    }
private:
    int _hour;
    int _minute;
    int _second;
};

class Date
{
private:
    // 基本类型(内置类型)
    int _year;
    int _month;
    int _day;
    // 自定义类型
    Time _t;
};

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

        7. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:1.无参构造函数、2.全缺省构造函数、3.我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。可以不传参数就调用构造,都可以叫默认构造,这三个函数不能同时存在,只能存在一个。

总结: 

1.一般情况下,我们都要自己写构造函数;

2.成员都是自定义类型,或者声明时给了缺省值,可以考虑让编译器自己生成构造函数。

3.析构函数:

概念:

        析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

特性:

析构函数是特殊的成员函数,其特征如下:
        1. 析构函数名是在类名前加上字符 ~
        2. 无参数无返回值类型
        3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

注意:析构函数不能重载
        4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 其他方法...
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};
void TestStack()
{
	Stack s;
	s.Push(1);
	s.Push(2);
}//结束自动调用析构函数

int main()
{
    TestStack();
    return 0;
}

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

        5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

class Time
{
public:
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

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

程序运行结束后输出:~Time(),
在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
        因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

        6. 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

4.拷贝构造函数:

概念:

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

特征:

拷贝构造函数也是特殊的成员函数,其特征如下:
        1. 拷贝构造函数是构造函数的一个重载形式。

        2. 拷贝构造函数的参数只有一个且必须是类的类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

        3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定
义类型是调用其拷贝构造函数完成拷贝的。

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

        4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?
当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);

	Stack s2(s1);
	return 0;
}

在这个代码中我们为什么会崩溃呢?

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

此时,s1,s2都指向了同一块malloc开辟的空间,程序运行结束时,调用析构函数,先将s2释放了,但是s1也需要释放,所以就造成同一块空间释放了两次,造成程序崩溃!!!

注意:类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请
时,则拷贝构造函数是一定要写的,否则就是浅拷贝/值拷贝。

 上面代码正确的书写是:

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	Stack(const Stack& stt)
	{
		_array = (DataType*)malloc(sizeof(DataType) * stt._capacity);
		if (_array == nullptr)
		{
			perror("malloc fail");
			exit(-1);
		}
		memcpy(_array, stt._array, sizeof(DataType) * stt._size);
		_capacity = stt._capacity;
		_size = stt._size;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);

	Stack s2(s1);
	return 0;
}

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

5. 拷贝构造函数典型调用场景:
        1.使用已存在对象创建新对象
        2.函数参数类型为类类型对象
        3.函数返回值类型为类类型对象

class Date
{
public:
	//构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int,int,int):" << this << endl;
	}
	//拷贝构造函数
	Date(const Date& d)
	{
		cout << "Date(const Date& d):" << this << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date():" << this << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2022, 1, 13);
	Test(d1);
	return 0;
}

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

        为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

总结:

        1.浅拷贝/值拷贝:内置类型成员完成的拷贝;

        2.深拷贝:本质是熬吧指向的资源,让新对象拥有跟拷贝对象一样大的空间和一样的值;自定义类型成员调用这个成员的拷贝构造;

        比如:Stack就需要自己写拷贝构造完成深拷贝,顺序表、链表、二叉树等等的类都需要深拷贝(需要开辟资源!!!)。

5.赋值运算符重载:

运算符重载:

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型  operator操作符(参数列表)
注意:
        1.不能通过连接其他符号来创建新的操作符:比如operator[ 运算符 ] 
        2.重载操作符必须有一个类类型参数,比如:不能int operator+(int x,int y)这样就不会调用内置的+,导致改变了内置的运算规则
        3.用于内置类型的运算符,其含义不能改变,例如:内置的整型"+",不能改变其含义
        4.作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
        5.[ .* ] [ :: ] [ sizeof ] [ ?: ](三目运算符) [ . ] 注意以上5个运算符不能重载。这个经常在笔试选择题中出
现。

// 全局的operator==
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
    //这个是类外定义!!!
	return d1._year == d2._year
		&& d1._month == d2._month
		&& d1._day == d2._day;
}

void Test()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	cout << (d1 == d2) << endl;
}

int main()
{
	Test();
	return 0;
}

这里会发现运算符重载成全局需要成员变量是公有的,那么问题来了,封装性(自己的不让被别人使用)如何保证?

赋值运算符重载:

1. 赋值运算符重载格式
        1.参数类型:const T(类名)&,传递引用可以提高传参效率
        2.返回值类型:T(类名)&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
        3.检测是否自己给自己赋值(operator>(&d1, d2))
        4.返回*this :要符合连续赋值的含义

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

bool operator>(const Date& y)//(const Date* this,const Date& y)
	{
		if (_year > y._year)
		{
			return true;
		}
		else if (_year == y._year && _month > y._month)
		{
			return true;
		}
		else if (_year == y._year && _month == y._month && _day > y._day)
		{
			return true;
		}

		return false;
	}

bool operator==(const Date& y)//(const Date* this,const Date& y)
	{
		return _year == y._year
			&& _month == y._month
			&& _day == y._day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
    Date d1(2023,11,2);
    Dare d2(2000,1,1);
	bool ret1 = d1 > d2;  // operator>(d1, d2)-> d1.operator>(&d1, d2)
	bool ret2 = d1 == d2; // operator==(d1, d2)-> d1.operator==(&d1, d2)

}

 C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

2. 赋值运算符只能重载成类的成员函数不能重载成全局函数

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{
	if (&left != &right)
	{
		left._year = right._year;
		left._month = right._month;
		left._day = right._day;
	}
	return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

int main()
{
	Date d1(2000, 1, 1);
	Date d2(2000, 1, 2);

	(d1 = d2);

	return 0;
}

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

运算符重载和函数重载 他们之间没有关联!

        运算符重载:自定义类型可以直接使用运算符

        函数重载:可以允许参数不同的同名函数,

 operator= 我们不写,编译器会生成默认的operator =
 跟拷贝构造的行为类似,内置类型值拷贝,自定义类型调用他的赋值
 比如:Date MyQueue (用两个栈实现队列)也可以不写,默认生成operator =就可以用
 Stack必须自己实现operator=,实现深拷贝

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

3. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。(浅拷贝/值拷贝)

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

class Time
{
public:
	Time()
	{
		_hour = 1;
		_minute = 1;
		_second = 1;
	}
	Time& operator=(const Time& t)
	{
		if (this != &t)
		{
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};
int main()
{
	Date d1;
	Date d2;
	d1 = d2;
	return 0;
}

 C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?

// 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 10)
	{
		_array = (DataType*)malloc(capacity * sizeof(DataType));
		if (nullptr == _array)
		{
			perror("malloc申请空间失败");
			return;
		}
		_size = 0;
		_capacity = capacity;
	}
	void Push(const DataType& data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	size_t _size;
	size_t _capacity;
};
int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2;
	s2 = s1;
	return 0;
}

 C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现。

我们知道cout<<、cin>>是打印(流插入)和输入(流提取),如果我们想对“ << ”、“ >> ”重载呢! 

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

此时我们看到" cout << d16 '无法通过,需要d16在前才能通过,但是这样看着不顺,所以我们需要写成全局函数,但是又无法保证成员变量的封装性,此时引入友元函数,就能保证封装性了。 

 C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

//cout <<
ostream& operator<<(ostream& out,Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;

	return out;
}

//cout >>
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;

	return in;
}

总结:

        其他运算度一般是实现成成员函数,“<<” ">>" 流运算符必须实现到全局,这样才能让流对象在第一个参数位置,才符合可读性。

分析:

        流本质是为了解决自定义类型的输入输出问题,printf 、scanf 无法解决自定义类型的输入问题,所以用:面向对象 + 运算符重载解决。

还需注意:

        有的人会所这样不会与左移>>,右移<<冲突吗?这里这个符号是双重定义的(两个意思),但是注意这里是运算符重载条件是自定义类型,而左移,右移是内置类型的!!!

前置++和后置++重载:

#include <iostream>
using namespace std;

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 前置++:返回+1之后的结果
	// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
	Date& operator++()
	{
		_day += 1;
		return *this;
	}
	// 后置++:
	// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
	// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
	//自动传递
	// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
	//一份,然后给this + 1
	// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
	Date operator++(int)
	{
		Date temp(*this);
		_day += 1;
		return temp;
	}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d;
	Date d1(2022, 1, 13);
	d = d1++; // d: 2022,1,13 d1:2022,1,14
	d = ++d1; // d: 2022,1,15 d1:2022,1,15
	return 0;
}

前置++:返回+1之后的结果
注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
后置++:

前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载,C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
一份,然后给this+1而temp是临时对象,因此只能以值的方式返回,不能返回引用。

6.日期类的实现:

// 获取某年某月的天数
int Date::GetMonthDay(int year, int month)
{
	assert(_year >= 1 && _month >= 1);
	int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };

	if (_month == 2 && ((year % 4 == 0 && year != 100) || (year % 400 == 0)))
	{
		return 29;
	}
	return arr[month];
}

//打印
void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

// 全缺省的构造函数
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;

	if (_year < 1 ||
		_month < 1 || _month > 12 ||
		_day < 1 || _day > GetMonthDay(_year, _month))
	{
		//assert(false);
		Print();
		cout << "日期非法" << endl;
	}
}

// 拷贝构造函数
// d2(d1)
Date::Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;

	if (_year < 1 || _month < 1 || _month > 12 || _day < 1 || _day > GetMonthDay(_year, _month))
	{
		方法一
		//exit(-1);
		方法二
		//Print();
		//cout << "输入格式错误" << endl;
		//方法三
		assert(false);
	}
}

// 赋值运算符重载
// d2 = d3 -> d2.operator=(&d2, d3)
Date& Date::operator=(const Date& d)
{
	if (this != &d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	return *this;
}

// 析构函数
Date::~Date()
{
	_year = 0;
	_month = 0;
	_day = 0;
}

// 日期+=天数
Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		return *this -= (-day);
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

// 日期+天数
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp += day;

	return tmp;
}

// 日期-天数
Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;

	return tmp;
}

// 日期-=天数
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}

	_day -= day;
	while (_day <= 0)
	{
		_month--;
		if (_month == 0)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// 前置++
Date& Date::operator++()
{
	*this += 1;

	return *this;
}

// 后置++
Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;

	return tmp;
}

// 后置--
Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;

	return tmp;
}

// 前置--
Date& Date::operator--()
{
	*this -= 1;

	return *this;
}

// >运算符重载
bool Date::operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day > d._day)
	{
		return true;
	}

	return false;
}

// ==运算符重载
bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// >=运算符重载
bool Date::operator>= (const Date& d)
{
	if (*this > d || *this == d)
	{
		return true;
	}

	return false;

	return *this > d || *this == d;
}

// <运算符重载
bool Date::operator<(const Date& d)
{
	//if (_year < d._year)
	//{
	//	return true;
	//}
	//else if (_year == d._year && _month < d._month)
	//{
	//	return true;
	//}
	//else if (_year == d._year && _month == d._month && _day < d._day)
	//{
	//	return true;
	//}

	//return false;
    
    //可以复用尽量复用
	return !(*this >= d);
}

// <=运算符重载
bool Date::operator<= (const Date& d)
{
	//if (*this < d || *this == d)
	//{
	//	return true;
	//}

	//return false;

	return !(*this > d);
}

// !=运算符重载
bool Date::operator!= (const Date& d)
{
	//if (*this == d)
	//{
	//	return false;
	//}

	//return true;

	return !(*this == d);
}

// 日期-日期 返回天数
int Date::operator-(const Date& d)
{
	Date max = d;
	Date min = *this;
	int flag = 1;

	if (*this > d)
	{
		max = *this;
		min = d;
		flag = -1;
	}
	int n = 0;
	while (min != max)
	{
		++min;
		++n;
	}

	return n * flag;
}

这里需要了解我们程序员在工作上能写效率更高的代码,就写效率更高的,如下: 

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

7.const成员:

        将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	//const Date* this
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();//&d1:const Datr* d1
	const Date d2(2022, 1, 13);
	d2.Print();//&d2:const Datr* d2
}

int main()
{
	Test();

	return 0;
}

C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记

在成员函数后面加上‘ const ’ 即在传过来的参数前都加上const一般用于不改变传递过来的参数的情况:+ 、-、判断……

成员函数定义原则:

1。能定义成const的成员函数都应该定义成const,这样const对象和非对象都可以调用(即:权限平移或权限缩小)

2.要修改成员变量的成员函数,不可以定义成const,

8.取地址及const取地址操作符重载:

下面这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
注意:只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

	Date* operator&()
	{
		return this;
	}

	const Date* operator&() const
	{
		return this;
	}

特殊情况:

我们不想让别人知道这个地址时才会自己创建,此时:

        1.返回一个假地址;

        2.返回空指针

	Date* operator&()
	{
		//return this;

		//给一个假地址
		return (Date*)0x1111111;
	}

	const Date* operator&() const
	{
		//return this;

		//不给看地址
		return nullptr;
	}

 以上就是个人学习见解和学习的解析,欢迎各位大佬在评论区探讨!

感谢大佬们的一键三连! 感谢大佬们的一键三连! 感谢大佬们的一键三连!

                                              C++:类和对象(中),C++,c++,算法,开发语言,深度学习,学习方法,笔记文章来源地址https://www.toymoban.com/news/detail-773381.html

到了这里,关于C++:类和对象(中)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 深度理解实分析:超越公式与算法的学习方法

    在数学的学习旅程中,微积分和线性代数为许多学生提供了直观且具体的入门体验。它们通常依赖于明确的公式、算法以及解题步骤,而这些元素往往可以通过记忆和机械练习来掌握。然而,当我们迈入实分析的领域时,我们面临着一种全新的挑战。实分析不仅难度更大,而

    2024年02月20日
    浏览(38)
  • 03目标检测-传统方法与深度学习算法对比

    目录 一、目标学习的检测方法变迁及对比     二、深度学习目标检测算法基本流程 三、传统目标检测算法VS深度学习目标检测算法         “目标检测“是当前计算机视觉和机器学习领域的研究热点。从Viola-Jones Detector、DPM等冷兵器时代的智慧到当今RCNN、YOLO等深度学习土

    2024年02月07日
    浏览(47)
  • 基于深度学习方法的点云算法1——PointNetLK(点云配准)

    请点点赞,会持续更新!!! 基于深度学习方法的点云算法2——PointNet(点云分类分割) 基于深度学习方法的点云算法3——PointNet++(点云分类分割) 基于深度学习方法的点云算法4——PCT: Point Cloud Transformer(点云分类分割) 作者将PointNet看成一个可学习的成像函数(learn

    2024年02月10日
    浏览(44)
  • 基于深度学习方法的点云算法5——PointConv(点云分类分割)

    请点点赞,会持续更新!!! 基于深度学习方法的点云算法1——PointNetLK(点云配准) 基于深度学习方法的点云算法2——PointNet(点云分类分割) 基于深度学习方法的点云算法3——PointNet++(点云分类分割) 基于深度学习方法的点云算法4——PCT: Point Cloud Transformer(点云分类

    2024年02月06日
    浏览(45)
  • 点云算法好书推荐(3D Point Cloud Analysis 传统、深度学习和可解释的机器学习方法)附下载链接

    书籍介绍: 本书介绍了点云;它在工业中的应用,以及最常用的数据集。它主要关注三个计算机视觉任务——点云分类、分割和配准——这是任何基于点云的系统的基础。对传统点云处理方法的概述有助于读者快速建立背景知识,而对点云方法的深度学习包括对过去几年的突

    2024年02月12日
    浏览(46)
  • OpenCV、Dlib 和深度学习中的各种人脸检测方法与性能比较--包含C++ 和 Python 代码实现

    文末附基于Python和C++两种方式实现的测试代码下载链接 在本教程中,我们将讨论 OpenCV、Dlib 和深度学习中的各种人脸检测方法,并对这些方法进行定量比较。我们将为以下面部检测器共享 C++ 和 Python 代码: OpenCV 中的 Haar 级联人脸检测器 OpenCV 中基于深度学习的人脸检测器。

    2024年02月10日
    浏览(48)
  • 【C++学习】类和对象 | 拷贝构造 | 探索拷贝构造函数为什么需要引用传参 | 深拷贝 | 初识运算符重载

    上一篇文章我们开始学习类内的默认成员函数, 这里是传送门,有兴趣可以去看看:http://t.csdn.cn/iXdpH 这篇文章我们继续来学习类和对象的知识。 目录 写在前面: 1. 拷贝构造 2. 拷贝构造函数为什么需要引用传参? 3. 深拷贝 4. 初识运算符重载 写在最后: 我们在创建一个对

    2024年02月11日
    浏览(53)
  • C#学习系列之抽象类和抽象方法

    学习基础 abstract public void Func(); 在基类中如果使用abstract,不能有函数体,那么必须在派生类要用override进行重写。用虚方法可不用重写。因为没有函数体,必须重写。 抽象成员:方法、属性、事件、索引。 必须用abstract修饰符标记 不能有实现代码块 抽象类的存在只有一

    2024年02月06日
    浏览(42)
  • 深度剖析java类和对象

    个人主页:兜里有颗棉花糖 欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 兜里有颗棉花糖 原创 收录于专栏【JavaSE_primary】 在Java中,一切皆对象。 在Java中,我们通过类来描述对象。 类的定义:Java类是面向对象编程中的基本构建模块, 用于描述对象的属性(成员变量)和行

    2024年02月10日
    浏览(43)
  • <C++> 类和对象-面向对象

    C语言是 面向过程 的,关注的是过程,分析出求解问题的步骤, 通过函数调用逐步解决问题。 C++是基于 面向对象 的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成。 C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函

    2024年02月14日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包