类和对象(下)

类和对象(下),第1张

目录

1.const成员

1.1const修饰的成员函数

2.初始化列表

2.1初始化列表的使用

2.1.1注意

2.1.2总结

2.2对于自定义类型

 2.2.1总结

2.3 explicit关键字

3. static成员

 4.友元类

5.内部类


1.const成员 1.1const修饰的成员函数

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


如果说我们

class Date
{
public:
	void Display()
	{
		cout << "Display ()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}

	/*void Display() const
	{
		cout << "Display () const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}*/
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};

int main()
{
	/*Date d1;
	d1.Display();*/

	const Date d2;
	d2.Display();
}

 可以看到的的是这里的打印函数写了两个,一个是没有const修饰的一个是有的,

这里先定义的是d1,那么它调用那个打印函数都是可以的,就算是调用第二个那也是权限缩小

d2可以看到是定义的是const类型的,那么他就不能调用第一个打印函数,因为这是权限放大,这是违法的。


2.初始化列表 2.1初始化列表的使用
class Date
{
public:
	/*Date(int year = 1,int month=1,int day=1)
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	Date(int year=0,int month=0,int day=0)
		:_year(year)
		, _month(month)
		, _day(day)
	{

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

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

}

很多人可能会问这和构造函数有什么区别吗?区别是肯定有的,就是我们如果在类里面声明一个const类型的成员变量,我们要怎么定义它呢?

class Date
{
public:
	// 初始化列表 - 成员变量定义的地方
	Date(int year, int month, int day)
	{
		//  error C2166: 左值指定 const 对象
		// 这里报错说明到构造函数体内时,成员变量已经定义出来了
		_N = 10;

		_year = year;
		_month = month;
		_day = day;
	}


private:
	int _year;  // 声明
	int _month;
	int _day;

	const int _N; 
};

int main()
{
	Date d1(2022, 1, 19); // 对象定义/对象实例化


	return 0;
}

这个时候的会出现编译错误是(error C2166: 左值指定 const 对象)这里报错说明到构造函数体内时,成员变量已经定义出来了,我们总不可能在函数声明的时候就定义吧。


所以在这里初始化列表就起到作用了。


class Date
{
public:
	// 初始化列表 - 成员变量定义的地方
	Date(int year, int month, int day)
		:_N(10)
	{
		//  error C2166: 左值指定 const 对象
		// 这里报错说明到构造函数体内时,成员变量已经定义出来了
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;  // 声明
	int _month;
	int _day;

	const int _N; 
};

int main()
{
	Date d1(2022, 1, 19); // 对象定义/对象实例化


	return 0;
}

这里看出只需把_N改成初始化列表的方式就行了。


2.1.1注意

1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

2. 类中包含以下成员,必须放在初始化列表位置进行初始化:

引用成员变量

const成员变量

自定义类型成员(该类没有默认构造函数)

class A
{
public:
	A(int a)
	{
		_a = a;
	}
private:
	int _a;
};

class Date
{
public:
	// 初始化列表 - 成员变量定义的地方
	Date(int year, int month, int day, int i)
		:_N(10)
		, _ref(i)
		, _aa(-1)
	{
		//  error C2166: 左值指定 const 对象
		// 这里报错说明到构造函数体内时,成员变量已经定义出来了
		//_N = 10;

		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;  // 声明
	int _month;
	int _day;

	const int _N;  // const
	int& _ref;     // 引用
	A _aa;         // 没有默认构造函数的自定义类型成员变量
};

int main()
{
	int i = 0;
	Date d1(2022, 1, 19, i); // 对象定义/对象实例化
	// int i;
	// 常量必须在定义的时候初始化
	// const int j = 0;

	return 0;
}
2.1.2总结

1、初始化列表 - 成员变量定义的地方
2、const、引用、没有默认构造函数的自定义类型成员变量必须在初始化列表初始化,因为他们都必须在定义的时候初始化
3、对于像其他类型成员变量,如int year、int _month,在哪初始化都可以

2.2对于自定义类型

对于自定义类型来说,如果他有默认的构造函数,那么为什么选择初始化列表呢?为什么就不能选择构造函数呢?

class A
{
public:
	A(int a = 0)
	{
		cout << "A(int a = 0)" << endl;
		_a = a;
	}

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
		_a = aa._a;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		_a = aa._a;

		return *this;
	}
private:
	int _a;
};
class Date
{
public:
		// 不使用初始化列表
		//Date(int year, int month, int day, const A& aa)
		//{
		//	_aa = aa;
		//	_year = year;
		//	_month = month;
		//	_day = day;
		//}
	
		// 使用初始化列表
      Date(int year, int month, int day, const A& aa)
			:_aa(aa)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;  // 声明
		int _month;
		int _day;
	
		A _aa;
};
	
int main()
{
		Date d1(2022, 1, 19, A(10)); // 对象定义/对象实例化
	
		return 0;
}

我们可以看到的是如果调用的是不使用初始化列表的,是调用了两次构造函数,一次赋值运算符重载

 这是使用初始化列表

 2.2.1总结

可以看到对于自定义类型来说,效率是有差异的。


内置类型:在函数体和初始化列表定义都是可以的。


自定义类型:建议在初始化列表里初始化,这样更高效,参考上面的例子。


2.3 explicit关键字

要学习这个内容需要知道一个东西,就是C语言的内容就是隐式类型转换和强制类型转换

//隐式类型转换 - 相近类型 -- 表示意义相似的类型
	double d = 1.1;
	int i = d;
	const int& i = d;

// 强制类型转换 - 无关类型
	int* p = &i;
	int j = (int)p;

那么在C++里面有这种东西

/ class Date
{
public:
	/*explicit Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}*/
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl;
	}

	Date(const Date& dd)
	{
		cout << "Date(const Date& dd)" << endl;
	}

private:
	int _year;
};

int main()
{
	 虽然他们两都是直接构造,但是过程是不一样的
	Date d1(2022);
	Date d2 = 2022; // 隐式类型转换
}
	

就是为什么int的2022可以转化为Date d2的变量Date d2 = 2022, 本来用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2但是C++编译器在连续的一个过程中,多个构造会被优化,合二为一,所以这里被优化为直接就是一个构造。


如果你不想发生这种事情就在你写的初始化列表加explicit。


3. static成员

在成员变量前加static关键字静态成员变量

特性:

1. 静态成员为所有类对象所共用

2. 静态成员变量必须在类外定义,定义时不添加static关键字

3. 可以直接通过访问限定符访问,但是只有公有的今天函数才能在类外直接使用

4. 静态成员函数没有隐藏的this指针,其他的成员变量

5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	static int GetACount() { return _scount; }
private:
	static int _scount;
	
};
int A::_scount = 0;
void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}
int main()
{
	TestA();
}

 4.友元类
class Date
{
public:
 Date(int year=1991, int month=1, int day=1)
 {
 _year = year;
 _month = month;
 _day = day;
 }

 void Display()
 {
 cout <<_year<< "-" <<_month << "-"<< _day <

我们想要打印出这个日期,只能调用我们的函数DisPlay来实现

但却不能用cout来实现

cout<

我们如果要实现cout输出自定义类型该怎么办呢?我们可以考虑运算符重载呀

我们在类里面重载一个<<符号,我们可以查阅cplus参考来看看cout的原型

cout可以当做一个outstream类型的形参,所以我们的函数可以这样设计

void operator<<(ostream&out)//注意:这个函数是在类里面定义的
{
	cout<<_year<<"-"<<_month<<"-"<<_day<

之后,我们的类就是这个样子

但是,我们这个函数定义完了后,却还是不能使用

 

为什么呢?

还记得我们之前的this指针吗?

我们之前说过,只要在类里面定义的函数,函数的形参都会自动的带一个指向对象本身的this指针

但是,这个指针,是会默认占据形参的第一个位置的

我们验证了 *** 作数顺序的问题,所以,我们应该这么使用这个重载符号

	Date d1(2022, 1, 20);
	d1 << cout;


 

 

但是,这么使用函数不符合我们的使用习惯,所以我们怎么才能让这个函数不带this呢?

当然,我们自然能想到,把它定义在类外就行了嘛!这样我们的函数就不会带this了,这种想法没错,但是定义在类外我们就不能访问我们封装好的元素了,所以我们就要想的办法怎么能让类外的某些函数访问某个类的私有元素

友元就能有效解决这个问题

友元的定义及使用
友元分为两种:友元函数和友元类

我们上面问题的引入其实就涉及了友元函数的知识

友元提供了一种突破封装的方式,有时提供了便利。


但是友元会增加耦合度,破坏了封装,所以友元不宜多用。


友元虽然能访问类的私有成员,但是仍然属于全局函数,不属于成员函数

定义方式:我们可以在类域内的任意位置声明友元函数,需要在函数最前面加上friend关键字

class Date
{
	friend void operator<<(outstream&out,const Date&d1);
	//...
}

然后在全局位置定义函数,友元不需要在函数名前加上::类域符

这样我们就能以正常习惯使用我们的cout函数了

而友元类与同理,友元类中的成员函数都可以访问另一个类的私有成员

class A
{
	friend classB;//B可以访问A的私有成员
	//..
}

注意友元是单向的,如果声明了B是A的友元,那么B可以访问A的私有成员,但A却不能访问B的私有成员

cout<<重载实现

我们上面的函数虽然是完成了任务,但是遇到这样的情况却还是不能解决

cout<

*** 作符是从左到右进行的,左边的 *** 作符返回了一个void类型,这样第二个<<就匹配不上类型了

所以我们还需要让函数返回cout才能实现任务

//最终的cout<<重载
outsream& operator<<(ostream&out,const Date& d)
{
	cout<
5.内部类

而内部类,顾名思义,就是定义在某一个类里面的类

class A
{
private:
 static int k;
 int h;
public:
 class B
 {
 public:
 void foo(const A& a)
 {
 cout << k << endl;//OK
 cout << a.h << endl;//OK
 }
 };
};

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。


注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。


外部类对内部类没有任何优越的访问权限。


注意:内部类就是外部类的友元类。


注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。


但是外部类不是内部类的友元。


内部类可以定义在外部类的public、protected、private都是可以的,private同样不能被外界访问到
 

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/571840.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-09
下一篇 2022-04-09

发表评论

登录后才能评论

评论列表(0条)