C++类(5)

1.<<和>>操作符重载

我们该如何重载操作符<<和>>呢?

如果在类里面,

void operator<<(ostream& out)
{
	out << _year << "年" << _month << "月" << _day << "日" << endl;
}

好像这样就可以了,(>>用istream就可以了)

但是当你输入

cout << d1;//编译器报错

编译器就会报错

因为Date对象默认占用第一个参数(this指针),就是做了左操作数

正确做法是

	d1 << cout;
//本质是d1.operator<<(cout)

你也许觉得这样写会很反人人类,那有没有更好的方法呢?

答案当然有,那就是写成全局函数,

2.const成员

我们来思考一个问题

我们在写函数的时候如果不想this指针指向的内容被修改怎么办?

可能我们会说加一个const不久行了!

但是this指针是不能显示写的,C++给我们提供了一种方式

​
void Date::print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

 Date d1;
const Date d2;
d1.print();
d2.print();//不加print函数上的const编译会报错,因为这样d2的权限被放大了
           //但是权限只能缩小或者平移,所以编译器会报错

​

我们再来看看下面这个代码

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


Date d1;
const Date d2;
d1<d2//不会出错 原因是d1<d2本质上是d1.operator<(d2),d1传给*this,权限平移,
     //而d2传给const Date& x权限的平移
d2>d1//编译器会出出错  原因是d2<d1本质上是d2.operator<(d1),d1传给const Date& x权限缩小
      //而d2传给了*this,权限缩小

所以当成员函数后面加上const之后,普通变量和cosnt变量都可以调用

那么能不能所有成员函数都加这个const?

答案肯定不是,有些函数是要改变this指针指向的值,加了cosnt我怎么改值

同时,只要成员函数内部不修改成员变量,都应该加const,这样const对象和普通对象就都可以调用了!

当然这个权限对于指针同样使用

const int a=3;
int *b=a;  //权限放大编译无法通过

 3.取地址重载函数

我们讲了四个默认成员函数,还剩两个默认成员函数

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

	}
public:
	int _year;
	int _month;
	int _day;
};

这两个成员函数无非是对普通对象和const对象的取地址符进行重载,一般不需要我们自己去写,编译器自动生成就可以了,只有特殊情况才需要重载,比如想让别人获得指定内容!

class Date
{
public:
	Date* operator&()
	{
		return nullptr;
		}
	const Date* operator&()const
	{

	}
public:
	int _year;
	int _month;
	int _day;
};

比如说,像下面这样写,你就无法获得普通变量地址,但是可以获得const对象的地址!

4.再谈构造函数

我们如果想给对象的成员一个初始值,有两种方法,第一种就是我们前面学的构造函数体赋值,很熟悉

class Date
{
public:
Date (int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

今天我们学另外一种,就是初始化列表

1.初始化列表

class Date
{
public:
	
		Date(int year, int month, int day)
	
		:_year(year)
		,_month(month)
		,_day(day)
		{}

public:
	int _year;
	int _month;
	int _day;
};

2.cosnt成员和引用成员

 首先我们来看引用成员和const成员,这两种都必须在定义的时候初始化

但是类里面的成员函数都是声明

我们在调用类的构造函数体赋值,它的作用是对定义的成员初始化

所以,初始化要在构造函数前,也就是构造函数的{}前(即前面的初始化列表)

初始化列表相当于成员定义的地方

当然对于内置的const类型也可以用缺省参数+构造函数体赋值

3.自定义类型(没有默认构造函数)

自定义成员为啥在该类没有默认构造函数的时候只能用初始化列表?

首先,我们要明确,自定义初始化要调用构造函数,这个时候该类没有默认构造函数,怎么调用?

这个地方的默认构造函数包括,没有显示写编译器自动生成的,无参数的构造函数,全缺省参数的构造函数

什么时候我们没有默认构造函数,我们自己写一个带参不全确省的构造函数的时候

向上面这个代码类a不存在默认构造函数,编译器就不知道怎么处理了

所以这个地方我们只能在初始化列表可以

像这样

4.总结

当然我们不写初始化列表,每个成员也会走初始化列表

只不过里面没内容罢了

​
class Date
{
public:
	
		Date(int year, int month, int day)
	
		:_month(month)
		, _day(4)//这个地方显示写了,就不会去调用初始值了!
		,_year(year)
		{}

public:
	int _year=3;//这个地方的缺省值是给初始化列表的
	int _month=1;//这个地方的缺省值是给初始化列表的
	int _day=3;//这个地方的缺省值是给初始化列表的
};

​

我们更推荐写初始化列表,但是初始化列表也无法解决全部的问题!

比如我malloc开辟一块空间,列表初始化可以帮我们解决

但是它无法帮我们判断malloc是否成功,是否成功的判定只能写在构造函数体内

此外,初始化列表执行的顺序和语句顺序无关,和成员顺序有关

class Date
{
public:
	
		Date(int year, int month, int day)
	
		:_month(month)//(1)
		, _day(day)   //(2)
		,_year(year)  //(3)
 		{}

public:
	int _year;
	int _month;
	int _day;
};

这个地方列表初始化并不是(1)->(2)->(3)

而是(3)->(1)->(2)

因为这个地方成员声明的顺序是

_year ->_month->_day

6.explicit关键字

​
class aa
{
public:
	aa(int s)
		:a(s)
	{}
private:
	int a;
};

int main(void) {
	aa A(1);
	aa B = 2;//整形转换成自定义类型
	return 0;
}

​

我们看这串代码,编译器没报错,那么这个aa B=2;是啥意思?

答案是隐患性类型转换

比如我们前面提过,

int a = 3;
double b = a;//这个地方是隐式类型转换
//会创建一个临时变量 临时变量有const属性
aa B=2;
//这个地方会用2去调用构造函数,生成临时变量是aa类型
//这个临时变量再会拷贝构造给B
//但是许多新的编译器会优化,会用2直接构造

像vs2022这种比较新的编译器就会用2直接构造

当然,如果你不想这个转换发生也很简单

就直接在构造函数前面加一个explicit就可以了 

explicit 关键字的主要作用如下:
防止隐式类型转换:当构造函数被声明为 explicit 时,编译器将不会自动执行隐式类型转换。这意味着,只有在显式地指定类型转换时,才能使用该构造函数进行对象的创建。

class qaz
{
public:
	explicit qaz(int a)
	{
		qwe = a;
	}
private:
	int qwe;
};

int main(void) {
	int ww = 4;
	qaz q1(4);//正确
	q1 = ww;//错误
	q1 = (qaz)ww;//正确
	return 0;
}

explici只能作用于只有一个参数的构造函数,因为我把普通变量转换成类对象,普通变量只有一个数啊 !

但是返回值拷贝和隐式类型转换的常性和const有本质的区别

int swap( const int a)
{
	return a;
}

int main(void) {
	int ww = 4;
	int b = swap(ww);//返回值有常性 但是类型是int
	const int a = 3;
	int c ;
	a = c;//无法编译通过 a有常性但是类型是const int
	return 0;
}

但是对于函数参数的const int类型 既可以接受没有常性的int类型 也可以接收上面两种类型

只是无法改变其值大小而已 

5.static

 1.初始化列表只能初始化对象自己的成员,不能初始化全局的,比如static修饰的,

且static修饰变量只能初始化一次!

2.static修饰的变量也不能写在构造函数体内

3.static修饰的值,只能在类外面定义(只能定义在全局,也就是静态区),定义的时候不用加static,类里面的只是声明,所有该类的对象都可以共享

4.static修饰的变量不可以有缺省值,因为缺省值要走初始化列表!但是static修饰的变量不走初始化列表

5.全局变量的劣势,所有地方都可以修改 

6.static修饰的函数(静态成员函数)不可以访问普通的成员,因为没有this指针,你怎么知道是这个类哪个对象成员的成员呢?

7.static修饰的函数(静态成员函数), 没有this指针,访问需要指定类域且受访问限定符(public,private,protect)影响!

8.static修饰的函数(静态成员函数)可以通过具体的类的对象去调用,或者也可以直接用类域去访问

9.public,private,protetc访问限定符对static修饰的变量和函数同样有用

class Date
{
public:

	Date(int year, int month, int day)
		:_month(month)
		, _day(day)
		, _year(year)
		
	{
	}
	public:
	static void cha()

	{
		cout << "cha" << endl;
	}
public:
	static int a;
	int _year = 3;
	int _month = 1;
	int _day = 3;

};
int Date::a = 10;//对应第3点

int main(void) {
	
	class Date b(2005,4,14);
	cout << Date::a << endl;
	b.cha();//对应第8点
	Date::cha();//对应第8点
	return 0;
}

但是嘛

其实对于整型常量,加一个const就可以了,这样就可以直接初始化了

 但是反人类的是像double这样的非整型就不可以了

6.友元函数

前面讲过了,我们在类里面写一个operator<<很反人类,那有没有好的解决办法呢?

答案肯定是有的,答案就是友元函数了;

1.友元函数可以直接访问类的私有成员,它是定义在类外面的普通函数,不属于任何类,

但需要在类的内部声明,声明时需要加上friend关键字。(sizeof不算友缘函数大小,同时类的对象也不能调用该函数)

2.友元函数可以访问类的私有和保护成员,但不是类的成员函数

3.友元函数不能用const修饰,不是成员函数,哪来的this指针?

4.友元函数可以在类里面的任意一个地方定义声明,不受类访问符的限制

5.一个函数可以是多个类的友元函数

6.友元函数的调用和普通函数调用原理相同

其实在C++里面友元函数用的不多,因为友元函数会破会封装性

​
class Date
{
public:
 Date(int year, int month, int day)
		:_month(month)
		, _day(day)
		, _year(year)
		
	{}
	
private:
	friend void print(Date& x);//不受访问限定符影响
	int _year = 3;
	int _month = 1;
	int _day = 3;

};
 void print(Date &x)
//友元函数可以访问类里面的私有成员,由于不是类里面的成员,因此不用Date::了
{
	cout <<x._day<<x. _month<<x._year<<endl;
}
int main(void) {
	
	Date d1(2005,4,14);
	print(d1);
	return 0;
}

​

7.友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员

1.友元是关系是单向的,不具有交换性

2.友元关系不能传递,

3.友元关系不能继承

class Time
{
	friend class Date;
public:
	Time(int hour, int minute, int second)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	friend class Time;
 Date(int year, int month, int day)
		:_month(month)
		, _day(day)
		, _year(year)
		
	{}
public:
 void print(Time& x)
 {
	 cout <<x._hour<<"-" << x._minute << "-" << x._second << endl;
	}
private:
	int _year = 3;
	int _month = 1;
	int _day = 3;
};
int main(void) {
	
	Date d1(2005,4,14);
	Time t1(11, 34, 56);
	d1.print(t1);
	return 0;
}

 8.内部类

概念:如果一个类定义在另一个类的内部,,这个类叫做内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限

注意:内部类就是外部类的友元类,但是外部类不是内部类的友元

特性:1.内部类定义在外部类的protect,private,public都是可以的(且会受限定符限制,因为是定义不是声明)

           2.内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名

            3.sizeof(外部类)=外部类,和内部类没有任何关系

           4.类里面定义出来的东西才会被访问限定符限定,但是声明不会

             5.内部类不能直接定义,要A::B b(B是A的内部域)(前提是public的)

class Date
{
public:
	class Time
	{
	public:
		Time(int hour, int minute, int second)
		{
			_hour = hour;
			_minute = minute;
			_second = second;
		}
	private:
		int _hour;
		int _minute;
		int _second;
	};
public:
	friend class Time;
 Date(int year, int month, int day)
		:_month(month)
		, _day(day)
		, _year(year)
		
	{}
private:
	int _year = 3;
	int _month = 1;
	int _day = 3;
};
int main(void) {
	
	Date d1(2005,4,14);
	Date::Time t1(11, 34, 56);
	return 0;
}

8.匿名对象

class Date
{

public:
	friend class Time;
 Date(int year=2000, int month=3, int day=4)
		:_month(month)
		, _day(day)
		, _year(year)
		
	{
	 cout << "hi" << endl;
 }
public:
	void print()
	{
		cout << "hello" << endl;
	}
private:
	int _year = 3;
	int _month = 1;
	int _day = 3;
};
int main(void) {
	Date d1;//不能加括号的原因是和函数的声明无法分清
	Date(2003, 3, 2);//匿名对象 会调用构造函数
	Date().print();//匿名对象函数调用 会调用构造函数 加括号的原因是类型不能调用函数
	//匿名对象即用即销毁,语句执行完销毁
	//匿名对象和普通对象一样传参,只是没有名字
	Date& ra = Date(2004, 1, 09);//匿名对象具有常性
	const	Date&ra=Date(2004,2,3);//const引用延长了生命周期,生命周期在当前函数局部域
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值