类和对象(下)

一、static 成员

在类中用 static 修饰的成员称为静态成员变量和静态成员函数,静态成员变量存放在静态区

  • 静态成员被所有类对象共享,不属于某个具体的对象
    外部访问类的静态成员时,只需要说明静态成员所在的类域即可,因此可以用 类名::静态成员 或者 对象.静态成员 进行访问

  • 静态成员也是类的成员,因此非静态成员函数内既可以访问类的静态成员变量,也可以调用静态成员函数,外部访问时,受 public、protected、private 访问限定符的限制

  • 静态成员变量只能在全局初始化,类中只是声明
    静态成员变量并不属于某个对象,因此不能放到初始化列表中,也不能在声明时指定缺省值(C++11)

  • 静态成员函数没有隐藏的 this 指针
    因此静态成员函数内不能访问任何非静态成员变量,也不能调用非静态成员函数

#include <iostream>

using namespace std;

class Date
{
    
    
public:
	Date(int year = 1970, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    
		++_size;	//成员函数可以访问静态成员
	}

	//静态成员函数
	//通常静态成员变量都是私有的,因此外部不能通过类或对象直接访问静态成员
	//为了不用创建对象就可以获取静态成员变量的值
	//于是通常提供静态函数来获取静态成员变量,然后用类名来调用该静态成员函数
	static int GetDateSize()
	{
    
    
		return _size;	//静态成员函数内不可以访问任何非静态成员
	}

private:
	int _year;
	int _month;
	int _day;
	static int _size;	//静态成员变量
};

//静态成员变量必须在全局域中初始化
int Date::_size = 0;

int main()
{
    
    
	Date d1;
	Date d2;
	
	//可以通过 类名:: 或者 对象. 来调用静态成员函数
	cout << Date::GetDateSize() << endl;//输出2
	cout << d1.GetDateSize() << endl;	//输出 2

	return 0;
}

二、友元

友元提供了一种突破类的封装的方式,使得外部可以直接访问类的私有成员,因此友元会增加模块的关联程度,不宜多用,友元分为 友元函数和友元类

在类中使用 friend 关键字声明外部函数为类的友元函数,友元函数可以直接访问类中的私有成员

#include <iostream>

using namespace std;

class Date
{
    
    
	//友元函数声明
	friend void Print(Date& d);

public:
	//...

private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};

//Date 类的友元函数,虽然是类外部的函数,但是可以直接访问其私有成员
void Print(Date& d)
{
    
    
	cout << "我是 Date 类的好朋友" << endl;
	cout << d._year << "/" << d._month << "/" << d._day << endl;
}

int main()
{
    
    
	Date d;

	//我是 Date 类的好朋友
	//1970 / 1 / 1
	Print(d);

	return 0;
}
  • 友元函数是类外部的函数,不是类的成员函数,可以访问类的私有和保护成员
    因此友元函数中没有该类的 this 指针,不能用 const 修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

假设在 Date 类中,声明 Time 类为友元类,那么 Time 类的所有成员函数都是 Date 类的友元函数,都可以访问 Date 类中的私有成员

#include <iostream>

using namespace std;

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

public:
	//...

private:
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};

//Date 类的友元类
//Time 类中的所有成员函数都可以直接访问 Date 类的私有成员
class Time
{
    
    
public:
	void Print(Date& d)
	{
    
    
		cout << d._year << "/" << d._month << "/" << d._day << " ";
		cout << _hour << ":" << _minute << ":" << _second << endl;
	}

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

int main()
{
    
    
	Date d;
	Time t;
	t.Print(d); //输出 1970/1/1 0:0:0

	return 0;
}
  • 友元类的关系是单向的,不具有对称性
    在上述的 Date类和 Time 类中,在 Date类中声明了 Time类是 Date类的友元类,但 Date类并不是 Time类的友元类,Date 类的成员函数不能直接访问 Time类的私有成员
  • 友元类的关系不是传递性的
    如果 C 是 B 的友元类, B 是 A 的友元类,并不能说明 C 是 A 的友元类

三、内部类

在类 A 中定义类 B,类 B 就称为类 A 的内部类

内部类和外部类是相互独立的,只是在访问上受到约束

  • 内部类对外部类的访问:内部类天生就是外部类的友元类,内部类可以直接访问外部类的静态成员,不需要外部类的类名/对象,也可以通过外部类的对象参数来访问外部类中的所有成员

  • 外部类对内部类的访问: 受到内部类的访问限定符限制

  • 外部类的外部对内部类的访问: 受到外部类的访问限定符限制,内部类可以定义在外部类的 public、protected、private 下

#include <iostream>

using namespace std;

class A
{
    
    
public:
	class B
	{
    
    
	public:
		static void Print()
		{
    
    
			cout << "外部类的静态成员:" << _aaa << endl;
		}

	private:
		int _b;
	};

private:
	static int  _aaa;
};

int A::_aaa = 10;

int main()
{
    
    
	//类 A 和 类 B 空间是独立的
	cout << sizeof(A) << endl;	//输出 4
	cout << sizeof(A::B) << endl;	//输出 4
	
	A::B::Print(); //输出 外部类的静态成员:10

	return 0;
}

四、匿名对象

类可以实例化没有名字的对象,特点是:匿名对象的声明周期只有一条语句,匿名对象即用即销毁

#include <iostream>

using namespace std;

class Date
{
    
    
public:
	Date(int year = 1970, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
    
    
		cout << "Date()" << endl;
	}

	~Date()
	{
    
    
		cout << "~Date()" << endl;
	}

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

int main()
{
    
    
	//输出 Date()
	//	   ~Date()
	Date();	//匿名对象

	//输出 Date()
	//	   ~Date()
	Date(2023);	//匿名对象

	return 0;
}

五、explicit关键字

#include <iostream>

using namespace std;

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

	}

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

int main()
{
    
    
	//C++98 中调用单参数的构造函数时可以进行隐式类型转换,多参数不行
	//编译器会通过 1 构造一个 Date类型的临时对象然后拷贝给 d1 对象
	Date d1 = 1;	//<==> Date d1 = Date tmp(1);
	const Date& ref1 = 1;	//即可说明隐式类型转换存在临时对象

	//C++11 后,调用多参数的构造函数也可以进行隐式类型转换
	//编译器会通过 2023, 2, 11 构造一个 Date类型的临时对象然后拷贝给 d2 对象
	Date d2 = {
    
     2023, 2, 11 };	//<==> Date d2 = Date tmp(2023, 2, 11);	
	const Date& ref2 = {
    
     2023, 2, 11 };	//即可说明隐式类型转换存在临时对象

	return 0;
}

在构造函数前加上关键字 explicit,可以禁止实例化对象时的隐式类型转换

explicit Date(int year = 1970, int month = 1, int day = 1)

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

值传递参数函数值传递返回 的某些场景下,一般编译器会做一些优化,减少对象的拷贝,有时还是非常有用的,但是 在能传引用的时候尽量传引用

编译器优化一般只会优化一条语句中的过程

#include <iostream>

using namespace std;

class Demo
{
    
    
public:
	Demo(int d1 = 1, int d2 = 2)
		: _d1(d1)
		, _d2(d2)
	{
    
    
		cout << "Demo()" << endl;
	}

	Demo(const Demo& d)
		: _d1(d._d1)
		, _d2(d._d2)
	{
    
    
		cout << "Demo(const Demo& d)" << endl;
	}

	void operator=(const Demo& d)
	{
    
    
		_d1 = d._d1;
		_d2 = d._d2;

		cout << "operator=" << endl;
	}

private:
	int _d1;
	int _d2;
};

void Test1(Demo d)
{
    
    
	;
}

Demo Test2()
{
    
    
	Demo tmp;
	//...

	return tmp;
}

Demo Test4()
{
    
    
	return 1;
}

Demo Test5()
{
    
    
	return Demo(1, 2);
}

int main()
{
    
    
	//隐式类型转换,这里会创建临时变量
	//构造临时变量 + 拷贝构造 -> 编译器会优化为直接构造
	Demo d1 = 1;	//只输出一个 Demo
	Test1(1);	//只输出一个 Demo

	Test1(Demo(1, 2));	//构造匿名对象 + 拷贝构造 -> 编译器会优化为直接构造
	cout << "-------------" << endl;

	//函数传值返回对象时,会通过临时变量返回
	//拷贝构造临时变量 + 拷贝构造d2 -> 编译器优化为 直接拷贝构造d2
	Demo d2 = Test2();
	cout << "-------------" << endl;

	Demo d3;
	//拷贝构造临时变量 + 赋值重载d3 -> 编译器不优化
	d3 = Test2();
	cout << "-------------" << endl;

	//构造临时变量 + 拷贝构造d4 -> 编译器优化为 直接构造d4
	Demo d4 = Test4();
	cout << "-------------" << endl;

	//构造匿名对象 + 拷贝构造临时变量 + 拷贝构造d4 -> 编译器优化为 直接构造d4
	Demo d5 = Test5();

	return 0;
}

八、类和对象

计算机只认识二进制格式的数据,对于现实生活中的实体,计算机并不认识,因此用户需要通过某种面向对象的语言,对实体进行描述,计算机才可以认识该实体

当我们想让计算机认识洗衣机实体时,需要经过以下步骤:

  1. 用户先对现实中的洗衣机实体进行抽象,在人为思想层面对洗衣机进行认识,抽象出洗衣机的属性和功能,即对洗衣机进行抽象认知的一个过程

  2. 经过1之后,在人的头脑中已经对洗衣机有了一个清晰的认识,为了让计算机可以识别人想象中的洗衣机,便需要人通过某种面相对象的语言(比如:C++、Java、Python等)将人头脑中的洗衣机用类来进行描述

  3. 经过2之后,在计算机中拥有了一个洗衣机类,但是洗衣机类只是站在计算机的角度对洗衣机对象进行描述的,还需要通过洗衣机类实例化出具体的洗衣机对象,此时计算机也就认识了洗衣机实体

  4. 用户就可以借助计算机中洗衣机对象,来模拟现实中的洗衣机实体了
    在这里插入图片描述

类就是描述实体(对象)具有那些属性和方法,描述完成后就可以形成一种新的自定义类型,在用该自定义类型便可以实例化具体的对象

八、面向过程和面向对象

C语言是面向过程的语言,在使用 C语言解决一个问题时,通常都是分析出解决问题的步骤,然后根据步骤设计对应的函数,通过调用函数从而解决这个问题

C++ 是面向对象的语言,在使用 C++解决一个问题时,通常是将问题涉及到的事物分类,然后根据分类设计出每一个类,然后用类实例化创建对象,通过对象之间的交互解决问题

九、封装

封装是面向对象的三大特性之一

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互,封装可以使得用户不用知道内部的实现细节可以更方便用户使用

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来
隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用

猜你喜欢

转载自blog.csdn.net/qq_70793373/article/details/128969982