类与对象;

代码:
class date
{        
public:
	void init(int year, int month, int day)
	{                                      //this指针不可以在形参和实参显式的写,但是可以在类里面显示的用
		_year = year;//this->_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
	void printpos()
	{
		cout << this << endl;
	}
private://类只存变量;
	int _year;//加_是为了好区分;
	int _month;
	int _day;
};
int main()
{
	date d1;
	d1.init(1, 2, 3);//d1.init(&d1,1, 2, 3);
	cout << &d1 << endl;
	d1.printpos();
	return 0;
}
运行结果:

结果分析:

init函数实际上是void init(date* this,int year, int month, int day)编译器加上this指针数;this指针就是定义的类的地址;this指针不可以在实参和形参显式的写,但是可以显式调用;

代码:
class a
{
public:
	void print()
	{
		cout << "print()" << endl;
	}
private:
	int _a;
};
int main()
{
	/*a* p = nullptr;
	p->print();*///p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;


	a* p = nullptr;
	(*p).print();//print依旧不在对象里,所以依旧不会对p解引用;  //只有光写p->a不会崩;p->a=1会崩;
	return 0;
}
运行结果:

代码分析:

p对象成员函数的地址不存在对象里,而是存在代码区,所以直接那函数名字地址去代码区找,所以没有对p解引用;是否在p解引用看访问的成员是否在p里面;

二、类的默认成员函数和成员;

1、构造函数;

定义:

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

特点:

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

1. 函数名与类名相同。
2. 无返回值。
3. 对象实例化时编译器 自动调用 对应的构造函数。
4. 构造函数可以重载。
	date()
	{
		cout << "date()" << endl;
		_year = 1;
		_month = 1;
		_day = 1;
	}
	//带参初始化;
	date(int year, int month, int day)
	{
		cout << "date(int year, int month, int day)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	上面两个构造合并;
	date(int year=1, int month=1, int day=1)//
	{
		cout << "date(int year=1, int month=1, int day=1)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
注意:如果通过无参构造函数创建对象时,对象后面不用跟括号(date d1),否则就成了函数声明(date d1());第一个和第三个语法上可以同时存在 ,但是使用的时候不可以;
如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦
用户显式定义编译器将不再生成。
默认构造的特点:
1、我们写它生成,我们写了它不生成;
2、内置类型:语言原生的类型(指针都是内置类型);自定义:类、结构;C++默认构造对内置类型不处理、处理自定义类型(C++支持声明给缺省值),会去调用这个成员的默认构造函数;
什么是默认构造:
无参的构造函数、全缺省的构造函数、我们不写编译器自动生成的构造函数都被称为默认构造函数(不传参就会调用),他们只能有一个;

2、析构函数;

概念:

通过前面构造函数的学习,我们知道一个对象是怎么来的,那一个对象又是怎么没呢的?

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

特性:

析构函数 是特殊的成员函数,其 特征 如下:
1. 析构函数名是在类名前加上字符 ~
2. 无参数无返回值类型。
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载。
4. 对象生命周期结束时, C++ 编译系统系统自动调用析构函数。(例如date类的析构函数是~date())。
5、如果系统没有显示定义,会生成默认析构,对于内置类型不处理,自定义类型调用它的析构函数处理。
代码:
class x
{
public:
	x(int a=1)
	{
		cout << "x()" << endl;
		_a = a;
	}
	~x()
	{
		cout << "~x()" << endl;
	}
private:
	int _a;
};
int main()
{
	x x1;

	return 0;
}

运行结果:

结果分析:

构造和析构的最大特性是自动调用。

3、拷贝构造函数;

概念:

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

特征:

拷贝构造函数也是特殊的成员函数,其 特征 如下:
1. 拷贝构造函数 是构造函数的一个重载形式
2. 拷贝构造函数的 参数只有一个 必须是类类型对象的引用 ,使用 传值方式编译器直接报错 , 因为会引发无穷递归调用。(如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要  d3(d2),无穷调用)。
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按
字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会造成资源二次释放的问题。
代码:
class stack//还可以定义成员函数;    //类里面不受访问限定符的限制,访问限定符限制的是外面的人使用;
{
public://类一开始默认为私有的;struct一开始默认为公有的;
	stack(int n = 4)
	{
		cout << "stack()" << endl;
		cout << "stack(int n = 4)" << endl;
		if (n == 0)
		{
			a = (int*)malloc(sizeof(int) * 4);
			if (a == nullptr)
			{
				perror("malloc fail");
			}
			size = 0;
			capa = 4;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("malloc fail");
			}
			size = 0;
			capa = n;
		}
	}
	void push(int x)
	{
		if (size == capa)
		{

			size_t newcapa = capa == 0 ? 4 : 2 * capa;
			auto x1 = a;
			a = (int*)realloc(a, newcapa * (sizeof(int)));
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			capa = newcapa;
		}
		a[size++] = x;
	}
	void pop()
	{
		assert(size > 0);
		size--;
	}
	int top()//栈顶元素;
	{
		return a[size - 1];
	}
	void print()
	{
		cout << size << endl << capa << endl;
	}
	stack(const stack& s)//解决了两次析构问题;(深拷贝);
	{      //s不能改变;防止错误代码;
		cout << "stack(stack& s)" << endl;
		a= (int*)malloc(s.capa * (sizeof(int)));
		if (a == nullptr)
		{
			perror("realloc fail");
			exit(-1);
		}
		memcpy(a, s.a,sizeof(int)*s.size);
		size = s.size;
		capa = s.capa;
	}
	~stack()
	{
		cout << "~stack()" << endl;
		free(a);
        a=nullptr;//这行和下一行不写不算错,算是好习惯;
		size = capa = 0;
	}
	bool isempty()
	{
		//assert(size>0);//
		return size == 0;
	}
private:
	int* a;
	int size ;
	int capa ;
};
class date
{
public:
	date(int year=2024, int month=11, int day=11)
	{
		year_ = year;
		month_ = month;
		day_ = day;
	}
	void print()
	{
		cout << year_<<"/" << month_ << "/" << day_ << endl;
	}
	//	date d2(d1);  d是d1的别名,this是d2的指针;
	date(const date& d)//如果是date(date d),那么d1(d2)会先d(d2),形成了新的拷贝构造,导致又要  d3(d2),无穷调用;
	{    //防止错误的改变,防止误伤;
		cout << "date(date& d)" << endl;
		year_ = d.year_;
		month_ = d.month_;
		day_ = d.day_;
	}
	~date()
	{
		cout << "~date()" << endl;
	}
private:
	int year_;
	int month_;
	int day_;
};
void func1(date d)
{
	d.print();
}
void func2(stack& s)//s的作用域是main函数栈帧,但是问题是s变化s1也变化;
{
	s.push(1);
	s.push(2);
}
int main()
{
	date d1;
	func1(d1);
	stack s1;
	func2(s1);//会出现浅拷贝问题
	stack s2(s1);
	
	
	
	//以下两种写法一样;
	date d2(d1);
	date d3 = d1;


	
	return 0;
}
运行结果:

4、赋值运算符重载;

概念:

C++为了增强代码的可读性引入了运算符重载运算符重载是具有特殊函数名的函数,也具有其

返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字 operator 后面接需要重载的运算符符号
函数原型: 返回值类型  operator 操作符 ( 参数列表 )
注意:
不能通过连接其他符号来创建新的操作符:比如 operator@
重载操作符必须有一个类类型参数
用于内置类型的运算符,其含义不能改变,例如:内置的整型 + ,不 能改变其含义
作为类成员函数重载时,其形参看起来比操作数数目少 1 ,因为成员函数的第一个参数为隐
藏的 this
.* :: sizeof ?: . 注意以上 5 个运算符不能重载。这个经常在笔试选择题中出现。

代码(以date类举例):

class date
{
public:
	date(int year=2024, int month=11, int day=11)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year<<"/" << _month << "/" << _day << endl;
	}
	date(const date& d);
	{   
		cout << "date(date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator<( const date& x2)//运算符重载可以写为全局函数,也可以写为类里面的成员函数;
	{
		if (_year < x2._year)
		{
			return true;
		}
		else if (_year == x2._year && _month < x2._month)
		{
			return true;
		}
		else if (_year == x2._year && _month == x2._month && _day < x2._day)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	bool operator==(const date& x2)
	{
		return _year == x2._year && _month == x2._month && _day == x2._day;
	}
	bool operator<=(const date& x2)
	{
		return ((*this)<x2)|| ((*this) == x2);
	}
	bool operator>(const date& x2)
	{
		return !((*this) <= x2);
	}
	bool operator!=(const date& x2)
	{
		return !((*this) == x2);
	}
	~date()
	{
		cout << "~date()" << endl;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	date d1(2023, 11, 11);
	date d2(2024, 12, 12);
	//为了对自定义类型进行比较,发明了运算符重载;
	int i1 = 10;
	int j1 = 100;
	i1 < j1;//内置类型转为运算符指令;
	cout << (d1< d2) << endl;//自定义类型转为运算符重载;
	//cout << (operator<(d2, d1) )<< endl;//可以显示调用;

	cout << (d1 < d2) << endl;//上面的自动转为下列公式;
	cout << d1.operator<(d2) << endl;
	cout << (d2 < d1) << endl;
	cout << d2.operator<(d1) << endl;

	cout << (d1 == d2)<<endl;
	cout << (d1 != d2) << endl;
	cout << (d1 <= d2) << endl;
	cout << (d1 > d2) << endl;
	return 0;
}
运行结果:

结果分析:

自定义类型原来不支持比较,写了运算符重载才支持;运算符重载不能改变操作符的操作数个数;写完运算符重载后可以显式调用也可以隐式调用。注意比较的时候操作数的左右顺序要和重载函数的顺序一致。

+=运算符和+运算符;

+=原来的数改变;+原来的数不改变;
代码:
	int getmonthday(int year,int month)//得到每年每月的天数。
	{
		int monthday[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2&&((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{//先判断是不是2月;
			monthday[2]+=1;
		}
		return monthday[month];
	}	
date& operator+=(int day)
	{
		_day += day;
		while (_day > getmonthday(_year, _month))//多次循环,直到天数小于该月的天数;
		{
			_day -= getmonthday(_year, _month);
			_month++;
			if (_month > 12)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	//+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;
	date operator+(int day)
	{
		date d2(*this);//不写这个是+=

		//d2._day += day;
		//while (d2._day > getmonthday(d2._year, d2._month))//多次循环,直到天数小于该月的天数;
		//{
		//	d2._day -= getmonthday(d2._year,d2._month);
		//	d2._month++;
		//	if (d2._month > 12)
		//	{
		//		d2._year++;
		//		d2._month = 1;
		//	}
		//}
		//return d2;
		d2 += day;
		return d2;
	}
代码分析:

+可以通过拷贝构造,然后复用+=;+=的时候返回的是*this,出了作用域还在,可以用引用返回;+返回的是d2,出了作用域就不在了,使用传值返回;

+=负数;

代码:
date& date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);
	}
	_day -= day;
	while (_day < 1)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		_day += getmonthday(_year, _month); 
	}
	return *this;
}
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 > 12)
		{
			_year++;
			_month = 1; 
		}
	}  

	return *this;
}
代码分析:

+=负值等于-=正值;

>>和<<运算符;

代码:
ostream& operator<<(ostream& out, 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; 

	return in;
}
代码分析:

由于cout<<d;打印的顺序d在后,所以不能将<<重载函数写在类里面;只能写在类外;由于cout<<d1<<d2,打印完d1之后还要打印d2,所以返回值是out。

前置++和后置++运算符;

代码:
date& date::operator++()
{
	*this += 1;
	return *this;
}
date date::date::operator++(int)//int 只是为了匹配;
{
	date d(*this);
	*this += 1;
	return d;
}
代码分析:

后置++和前置++最后都是加1,但是返回值不同;

赋值运算符重载

赋值运算符重载格式
参数类型 const T& ,传递引用可以提高传参效率
返回值类型 T& ,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
检测是否自己给自己赋值
返回 *this :要复合连续赋值的含义
赋值重载如果没有显示定义;默认赋值重载函数:内置类型值拷贝,自定义类型调用赋值重载函数;
代码:
Date& operator=(const Date& d)
 {
 if(this != &d)
       {
            _year = d._year;
            _month = d._month;
            _day = d._day;
       }
        
        return *this;
 }

初始化列表

上一篇:37.超级简易的计算器 C语言


下一篇:电动机三角型与星型的区别和接线方法图解