我与C++的爱恋:类和对象(四)


🔥个人主页guoguoqiang. 🔥专栏我与C++的爱恋

朋友们大家好!本篇是类和对象的最后一个部分。

一、static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

统计A类中创建了多少个对象

cpp 复制代码
class A
{
public:
	A()
	{}

	A(const A& a)
	{

	}
private:
};
A Func()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	A aa2;
	Func();
	return 0;
}

我们可以想象一下用了多少次构造函数就创建了多少对象,我们可以通过全局变量来计数

cpp 复制代码
int count=0;
class A
{
public:
	A()
	{
	++count;
	}

	A(const A& a)
	{
	++count
	}
private:
};
A Func()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;
	A aa2;
	Func();
	cout<<count<<endl'
	return 0;
}

但这个count是全局变量可能会被随意修改,能不能把他封装到类中呢?

cpp 复制代码
class A
{
public:
	A()
	{
	++count;
	}

	A(const A& a)
	{
	++count
	}
private:
int count=0;
};

但是这里会是临时拷贝可能不是同一个count;

这里需要设置为静态变量

private:

static int count=0;

静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。所以需要在类外定义

cpp 复制代码
class A
{
public:
	A()
	{
	++count;
	}

	A(const A& a)
	{
	++count
	}
private:
static int count=0;
};
int A::count=0;

这个count则受到类的限制,无法随意访问,如果想访问count,有两种办法:

方法一,将count改为公有,但是破坏了封装性,不建议

方法二,get函数

cpp 复制代码
class A
{
public:
A() { 
	++_scount;
}
A(const A & t) {
	++_scount; 
}
~A() {
	--_scount;
}
static int GetACount() { //get
	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;
}
cpp 复制代码
class A
{
public:
	A()
	{
	++count;
	}

	A(const A& a)
	{
	++count
	}
	static int Getcount(){
		return count;
	}
private:
static int count=0;
};

静态成员函数在类中有特殊的作用和行为.

静态成员函数不能调用非静态成员函数,

非静态成员函数可以调用静态成员函数。

静态成员函数通常用于提供一些与类的任何特定实例无关的功能,或者访问静态成员变量,而不依赖于类的对象。在设计类时,如果某个函数的行为不需要依赖于对象的状态,那么就应该将其声明为静态的

1特性

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

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

3.类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5.静态成员也是类的成员,受public、protected、private 访问限定符的限制

static成员 不能给缺省值,因为缺省值是给初始化列表,静态区不存在对象中不走初始化列表。

二、友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以

友元不宜多用。

友元分为:友元函数和友元类

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。

1.重载<<和>>

cpp 复制代码
class Date
{
public:
Date(int year, int month, int day)
     : _year(year)
     , _month(month)
     , _day(day)
 {}
private:
 int _year;
 int _month;
 int _day;
};
cpp 复制代码
class Date
{
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
// d1 << cout; -> d1.operator<<(&d1, cout); 不符合常规调用
// 因为成员函数第一个参数一定是隐藏的this,所以d1必须放在<<的左侧
ostream& operator<<(ostream& _cout)
{
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};

我们之前重载时会不符合常规调用。

作为成员函数重载,this指针占据了第一个参数,意味着Date必须是左操作数

所以这个函数只能写成全局函数,这里访问不了私有先置为公有

cpp 复制代码
class Date
{
public:
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	
//private:
	int _year;
	int _month;
	int _day;
};
void operator<<(ostream& out,const Date &d)
{
	out << d._year << "-" << d._month << "-" <<d._day;
}
int main()
{
	Date d1(2024, 4, 22);
	cout << d1;
	return 0;
}

这下可以访问了,但不能连续赋值。

cpp 复制代码
	Date d1(2024, 4, 22);
	Date d2(2024, 4, 20);
	cout << d1<<d2;

在这里我们可以理解为cout是从左往右进行的 cout<<d1 返回cout,返回后再继续流输出。

cpp 复制代码
ostream& operator<<(ostream& out,const Date &d)
{
	out << d._year << "-" << d._month << "-" <<d._day;
}

同理可以写出流提取:

cpp 复制代码
istream& operator>>(istream& _cin, Date& d)
{
 _cin >> d._year;
 _cin >> d._month;
 _cin >> d._day;
 return _cin;
}

2.友元函数

现在尝试去重载operator<<,然后发现没办法将operator<<重载成成员函数。因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以要将operator<<重载成全局函数。但又会导致类外没办法访问成员,此时就需要友元来解决。operator>>同理。

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在 类的内部声明,声明时需要加friend关键字。

cpp 复制代码
class Date
{
 friend ostream& operator<<(ostream& _cout, const Date& d);
 friend istream& operator>>(istream& _cin, Date& d);
public:
 Date(int year = 1900, int month = 1, int day = 1)
 : _year(year)
 , _month(month)
 , _day(day)
 {}
private:
 int _year;
 int _month;
 int _day;
};

友元函数的特点

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

友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

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

友元函数的调用与普通函数的调用原理相同

3.友元类

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

cpp 复制代码
class Time
{
	friend class Date;//日期类中可以直接访问Time中的私有成员变量,但是Time中不能访问Date的私有
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}

private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

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

需要注意:

1.友元关系是单向的,不具有交换性。比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接

访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

2.友元关系不能传递。如果C是B的友元, B是A的友元,则不能说明C时A的友元。

3.友元关系不能继承。

三、内部类(内部类是外部类的私有)

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类(仅仅受到类域限制),它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限

内部类就是外部类的友元类(内部类可以访问外部类),参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元

cpp 复制代码
class A
{
private:
	static int k;
	int h;
public:
	class B // B天生就是A的友元
	{
	public:
		void fun(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	};
};
int A::k = 1;
int main()
{
	A::B b;
	b.fun(A());

	return 0;
}

B可以访问A的所有成员

特性:

1.内部类可以定义在外部类的public、protected、private都是可以的。

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

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

cpp 复制代码
	A::B b;

B这个类受到A类的类域的限制

四、匿名对象

cpp 复制代码
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};

有名对象:

A aa1 A aa2(2)

匿名对象:

A() A(11)

cpp 复制代码
class A
{
public:
    A(int a = 0)
        :_a(a)
    {
        cout << "A(int a)" << endl;
    }
    ~A()
    {
        cout << "~A()" << endl;
    }
private:
    int _a;
};

int main()
{
	A aa1(1);
    A();
// 我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,下一行他就会自动调用析构函数
    return 0;
}

其中第二,三行是A()的构造函数和析构函数。

cpp 复制代码
class Solution {
public:
 int Sum_Solution(int n) {
 //...
 return n;
 }
};
int main()
{
	Solution().Sum_Solution(10);
	return 0;
}

匿名对象在这样场景下就很好用,当我需要一个临时对象去调用其成员函数,但又不想为这个临时使用的对象创建一个具体的变量名,这样使用就很方便。

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

隐式类型,连续构造+拷贝构造->优化为直接构造

一个表达式中,连续构造+拷贝构造->优化为一个构造

一个表达式中,连续拷贝构造+拷贝构造->优化为一个拷贝构造

本节内容到此结束!感谢大家观看!!

相关推荐
娅娅梨32 分钟前
C++ 错题本--not found for architecture x86_64 问题
开发语言·c++
兵哥工控37 分钟前
MFC工控项目实例二十九主对话框调用子对话框设定参数值
c++·mfc
汤米粥38 分钟前
小皮PHP连接数据库提示could not find driver
开发语言·php
冰淇淋烤布蕾41 分钟前
EasyExcel使用
java·开发语言·excel
我爱工作&工作love我44 分钟前
1435:【例题3】曲线 一本通 代替三分
c++·算法
拾荒的小海螺1 小时前
JAVA:探索 EasyExcel 的技术指南
java·开发语言
马剑威(威哥爱编程)1 小时前
哇喔!20种单例模式的实现与变异总结
java·开发语言·单例模式
娃娃丢没有坏心思1 小时前
C++20 概念与约束(2)—— 初识概念与约束
c语言·c++·现代c++
lexusv8ls600h1 小时前
探索 C++20:C++ 的新纪元
c++·c++20
lexusv8ls600h1 小时前
C++20 中最优雅的那个小特性 - Ranges
c++·c++20