【C++入门】类和对象Ⅲ:Static成员、匿名对象、友元、内部类、拷贝对象时的一些编译器优化、再次理解封装(含 oj 题)


🔗接上篇【Ⅱ】默认成员函数篇:构造+析构、拷贝构造+赋值重载、const成员函数、取地址重载、初始化列表

👉【C++入门】类和对象Ⅱ:默认成员函数、构造函数、初始化列表、析构函数、拷贝构造函数、赋值运算符重载函数、const成员函数、取地址及const取地址操作符重载(含 时间计算器 部分实现)


四、补充知识点

类和对象的学习基本收尾,最后补充一些零散的知识点~


4.1 static 静态成员

类中的 static 静态成员,不属于某个对象,而是属于所有对象,属于整个类
用static修饰的成员变量,称之为 静态成员变量;用 static 修饰的成员函数,称之为 静态成员函数

注意事项:

  1. 类静态成员变量一定要在类外进行初始化,类中的只是声明。
  2. 类静态成员函数 没有 this 指针,不能直接访问 非静态成员,因为非静态成员都是属于对象,用 this 调用的
  3. 类静态成员的访问可通过 类名::静态成员 或者 对象.静态成员

一道面试题:实现一个类,计算程序中创建出了多少个类对象。

class A
{
public:
	A(int a = 0)
	{
		++count;
		cout << "构造" << endl;
	}
	A(const A& aa)
	{
		++count;
		cout << "拷贝" << endl;
	}

	// 静态成员函数 -- 没有 this 指针
	static int getCount()
	{
		// _a++;	//err.. 
		return count;
	}
	// 静态成员变量
	static int count;	// 声明
};
int A::count = 0;	// 静态成员变量 定义初始化
void func(A aa)
{}

int main()
{
	A a1;			//----> 构造
	A a2(1);		//----> 构造
	A a3 = 1;		//----> 构造(编译优化结果,详见上篇 explicit 关键字)
	A a4(a1);		//----> 拷贝
	func(a1);		//----> 拷贝

	A a5[10];		//----> 构造X10

	cout << A::count << endl;			//----> 15
	cout << a1.count << endl;			//----> 15

	A* ptr = nullptr;
	cout << ptr->count << endl;			//----> 15  能运行哦~~虽然是空指针,但是没有解引用
	
	//cout << a1.getCount() << endl;	// 对也不对,如果对象创建在函数里就很挫了~
	cout << A::getCount() << endl;		//----> 15

	return 0;
}

oj 题练习:🔗JZ64 求1 + 2 + 3 + … + n

// 描述:求1 + 2 + 3 + ... + n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A ? B : C)。
class Sum {
public:
	Sum()
	{
		_sum += _i;
		_i++;
	}

	static int getSum()
	{
		return _sum;
	}

	static int _i;
	static int _sum;
};

int Sum::_i = 1;
int Sum::_sum = 0;

class Solution {
public:
	int Sum_Solution(int n) {
		//Sum a[n];	// C99 才支持变长数组
		Sum* ptr = new Sum[n];
		return Sum::getSum();
	}
};

/*int main()
{
	Solution s;
	cout << s.Sum_Solution(10) << endl;

	return 0;
}*/

在该题中,为了调用函数专门创建了一个对象 s,这种多此一举的举动,可以使用下面这个知识点进行优化~😊


匿名对象

匿名对象,定义时不需要对象名,生命周期只在该行(一次性对象~~)
只需要调用函数,不需创建对象时,我们可以尝试使用它

上面 oj 题中,创建对象,用对象调用函数的写法,可以转换成如下:

cout << Solution().Sum_Solution(10) << endl;

使用案例:

A func(int n)
{
	int ret = Solution().Sum_Solution(n);

	//A retA(ret);
	//return retA;
	return A(ret); 
}

4.2 友元

友元作为一种突破封装的手段,虽然提供了便利,但是使用它会增加耦合度,破坏封装。不宜多用 ~

友元分为 友元函数友元类


4.2.1 友元函数

当类将一些权限设置为 私有 成员,我们可以通过增设 友元函数,便于对其进行 直接访问
特征:

  • 定义在类外普通函数
  • 不属于任何类,但需要在 类的内部声明,并加 关键字 friend
// 实现:重载<<、>>  (通过 Date 类,要求 cin<<d cout<<d 能够直接输入和输出日期)
class Date
{
	// 友元函数声明
	friend ostream& operator<<(ostream& _cont, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);

public:
	Date(int year = 1900, int month = 12, int day = 31)
		:_year(year)
		,_month(month)
		,_day(day)
	{}

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


ostream& operator<<(ostream& _cout, const Date& d)
{
	cout << d._year << "-" << d._month << "-" << d._day;
	return _cout;
}

istream& operator>>(istream& _cin, Date& d)	// 注意:没有 const,不然改不了~表现为栈溢出
{
	cin >> d._year;
	cin >> d._month;
	cin >> d._day;
	return _cin;
}

int main()
{
	Date d;
	cin >> d;
	cout << d << endl;
	return 0;
}

4.2.2 友元类

助记:我当你是朋友,所以把我的所有都跟你共享。你把我当不当做朋友都无所谓。

特征:

  1. 友元关系是 单向
  2. 友元关系 不具有传递性 (like:a 是 b 的友元,b 是 c 的友元,不能说 a 是 c 的友元)
  3. 友元关系 不能继承,继承篇会进行详细介绍

例子: 声明 日期类为时间类的友元类,则在日期类中,可以直接访问 Time 类中的私有成员变量

class Time
{
	// 友元类声明
	friend class Date;

public:
	Time(int hour = 11, int minute = 59, int second = 59)
		:_hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};

4.3 内部类

类的里面再定义一个类。内部类是一个独立的类,不属于外部类的成员,只是受了外部类域的限制,不能通过外部类访问的对象访问内部类的成员。内部类就是外部类的友元类,参见友元的定义。

特性:

  1. 内部类定义在外部类的 public、protected、private 都是可以的。
  2. 内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
  3. 内部类是独立的类,sizeof(外部类)=外部类,和内部类没有任何关系。
class Out
{
public:
	// 【内部类】,In 天生就是 Out 的友元
	class In 
	{
	public:
		void func(const Out& o)
		{
			cout << sta << endl;
			cout << o.sta << endl;
		}
	private:
		int b;
	};

private:
	int a;
	static int sta;
};

int Out::sta = 1;
int main()
{
	cout << sizeof(Out) << endl;	// 外部类的大小跟内部类无关
	
	Out::In ii;
	ii.func(Out());

	return 0;
}
------
输出结果:
4
1
1

重写 oj 题练习:🔗JZ64 求1 + 2 + 3 + … + n(只是为了展示语法)

// 描述:求1 + 2 + 3 + ... + n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A ? B : C)。
// 要求:使用内部类增加保密性
class Solution {
private:
	class Sum {
	public:
		Sum()
		{
			_sum += _i;
			_i++;
		}
	};

	static int _i;
	static int _sum;

public:
	int Sum_Solution(int n) {
		//Sum a[n];	// C99 才支持变长数组
		Sum* ptr = new Sum[n];
		return _sum;
	}
};

int Solution::_i = 1;
int Solution::_sum = 0;

4.4 拷贝对象时的一些 编译器优化

编译器优化原则:一句话里面的多个步骤,才敢考虑优化

我们在此讨论两种优化情况:

  1. 构造+拷贝构造 —>(优化为)—> 直接构造
  2. 连续拷贝 —>(优化为)—> 一次拷贝

这里的实验有一本参考书目叫做《深度探索C++对象模型》



void func1(A aa)
{}

void func2(const A& aa)	// 临时变量和匿名都是常。加 const 后可调用
{}

A func3()
{
	A aa;			// 构造(无优化)
	return aa;		// 拷贝(无优化)
}

A func4()
{
	return A();		// 构造+拷贝 --> 直接构造
}
int main()
{
	A aa1 = 1;		// 构造+拷贝 --> 直接构造

	func1(aa1);		// 拷贝(无优化)
	func1(2);		// 构造+拷贝 --> 直接构造
	func1(A(2));	// 构造+拷贝 --> 直接构造

	cout << "---------------func2-----------------" << endl;

	func2(aa1);		// 无优化
	func2(2);		// 构造(无优化)
	func2(A(2));	// 构造(无优化)

	cout << "---------------func3-----------------" << endl;

	func3();	// 构造 拷贝 (均无优化)
	A aa2 = func3();	// 构造 拷贝+拷贝 --> 构造 优化为一个拷贝

	// 干扰了,不能优化
	A aa2_2;
	aa2_2 = func3();

	cout << "---------------func4-----------------" << endl;
	
	func4();	// 构造+拷贝 --> 直接构造
	A aa3 = func4();	// 构造+拷贝+拷贝 --> 直接构造

	return 0;
}
------
输出函数调用结果:
A(int a)
A(const A& aa)
~A()
A(int a)
~A()
A(int a)
~A()
---------------func2-----------------
A(int a)
~A()
A(int a)
~A()
---------------func3-----------------
A(int a)A(const A& aa)			|
~A()					|
~A()A(int a)		————————————↰
A(const A& aa)				|	
~A()A(int a)		————————————↰
A(int a)					|	
A(const A& aa)				|
~A()						|
A& operator=(const A& a)	|
~A()---------------func4-----------------
A(int a)
~A()
A(int a)
~A()
~A()
~A()
~A()

对象返回 总结:

  • 接收 返回值对象,尽量以 拷贝构造 的方式接收,不要赋值接收(根据 func3 )
  • 函数中返回对象时,尽量返回 匿名对象(根据 func4 和 func3 的对比)

函数传参 总结:

  • 尽量使用 const & 传参,这样便不需要依赖优化(根据 func2)

五、再次了解 类和对象🙂

类和对象是现实世界的一个镜像,即抽象类别和实体,而我们学习 C++ 这门语言,需要更多关注的是类和类之间的关系。


六、📕一些 oj 题

牛客网🔗HJ73 计算日期到天数转换

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值