个人技术分享

目录

前言

一、再谈构造函数

二、static成员

三、友元

四、内部类

五、匿名对象

总结:


 

前言

终于来到了我们类和对象的最后一部分,类与对象下。在这部分中,我们主要讲解类与对象的“插件” ,也就类似于拓展部分,比如友元函数,初始化列表等,让大家对C++类与对象有一个更深层次的理解。

一、再谈构造函数

在创建对象时,编译器通过调用构造函数,给对象中的每个成员一个合适的初始值。虽然调用了构造函数之后对象中已经有了一个初始值,但这并不能叫做对对象中的成员变量初始化,构造函数函数体内的部分只能称作赋初值。因为构造函数体内可以多次赋值,但初始化只能初始化一次。

在类中负责初始化工作的是初始化列表,是一个:以一个冒号开始,以逗号分隔的成员数据列表,每个成员变量后面跟着一个放在括号中的初始值或者表达式。

//以日期类为例
class Date
{
public:
	Date(int year, int month, int day)//以一个冒号开始,以逗号分隔的成员数据列表,每个成员变//量后面跟着一个放在括号中的初始值或者表达式。
		:_year(year),
		_month(month),
		_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

每一个成员变量在初始化列表中只会出现一次(因为初始化只会进行一次),初始化列表就是成员变量初始化的地方。

注意,如果类中的成员变量包含如下类型,就必须把此变量放在初始化列表中进行初始化:

1、引用成员变量

2、const 成员变量

3、自定义类型成员(尤其是此类型没有默认构造函数时)

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

class B
{
public:
	B(int a, int ref)
		:_obj(a)
		,_ref(ref)
		, _n(10)
	{}
private:
	A _obj;// 没有默认构造函数
	int& _ref; // 引用
	const int _n; // const 
};

 虽然没有强制要求其他类型也要写进初始化列表初始化,但在尽量用初始化列表,因为对于自定义类型来说,它一定会先使用初始化列表初始化。

另外,初始化列表中的初始化顺序与它在初始化列表中的先后顺序无关,而与它在类中声明的先后相关,越先声明,越先在初始化列表中初始化。

class A
{
public:
	A(int a1)
		:_a1(a1)
		, _a2(_a1)
	{}
	void Print()
	{
		cout << _a1 << ' ' << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};

int main()
{
	A a(1);
	a.Print();
	return 0;
}

从这段代码中可以看出,我们在类中先声明了_a2,随后声明了_a1,在初始化列表中,我们先初始化_a1,随后用_a1给_a2初始化。倘若初始化顺序是按照在初始化列表中的先后顺序,这段代码的结果就会是两个1,但最后却是:

这就可以说明,初始化列表中的初始化顺序与它在初始化列表中的先后顺序无关!

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,其余均有默认值的构造函数来说,还会具有隐式类型转换的作用。

class Date
{
public:
	Date(int year, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{
	}
	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date a(1900);
	Date b = 2023;//1
	b.Print();
	a.Print();
	a = 2024;//2
	a.Print();
	return 0;
}

这里的1与2过程其实都是发生了隐式类型转换。实际上编译器背后会用2023,2024构造一个无名的对象,最后用无名对象给a与b赋值。

但倘若我们给构造函数前面加一个关键字explicit,就会编译报错,因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用。

概括的说, 用 explicit 修饰构造函数,将会禁止构造函数的隐式转换 。

二、static成员

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

用static修饰计算有多少个类:
class A
{
public:
    A() 
	{ 
		++_scount; 
	}

	A(const A & t) 
	{ 
		++_scount; 
	}

	~A() 
	{
		--_scount; 
	}

	static int GetACount() 
	{ 
		return _scount; 
	}
private:
    static int _scount;
};


int A::_scount = 0;//在类外初始化


void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	cout << A::GetACount() << endl;

	A a3(a1);
	cout << A::GetACount() << endl;
}

int main()
{
	TestA();
	cout << A::GetACount() << endl;

	return 0;
}

static修饰的类的静态成员有以下特性:

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

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

3、类静态成员可以通过类名::静态成员或者对象.静态成员来访问。

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

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

静态成员函数可以调用非静态成员函数,但是需要通过对象来调用非静态成员函数,因为非静态成员函数属于对象而不属于类。而非静态成员函数可以调用类的静态成员函数,因为静态成员函数属于类而不属于对象。

三、友元

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

友元分为友元函数与友元类。

1、友元函数

当我们发现对象与隐含的this指针在抢占第一个参数的位置时,但实际我们需要的第一个参数是形参参数,封装成全局函数又不好解决问题的时候,就可以通过友元函数来解决了。
友元函数可以访问该类的私有成员,他是一个定义在类外部的普通函数,不属于任何类,但需要在所使用的类内部声明,声明时加上关键字friend表示此函数为该类的友元函数。(译为你是我的朋友)

注意:

友元函数可以访问类的私有与保护成员,但不属于类的函数。

友元函数不能被const修饰(因为const不属于类成员函数,不受const限制)

友元函数可以在类定义的任何地方声明,不受访问限制符的限制(因为友元函数不属于类的成员函数)

一个函数可以是多个类的友元函数(我可以是很多人的朋友)

2、友元类

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

注意:

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

比如我们定义了一个A类与一个B类,在A类中我们声明B类为A类的友元类,那么我们就可以在B类中直接访问A类的非公有成员,但是A类不可以访问B类的非公有成员(我认为你是我的朋友,但别人不认为你是他的朋友)

2、友元关系不能传递:

倘若C是B的友元类,B是A的友元类,那么不能说明C是A的友元类.

3、友元关系不能继承


class Time
{
	friend class Date;//声明Date类是Time类的友元类,则可以直接在Date类里访问Time类的非公有成员
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)
	{
		//Date类的成员函数,却可以直接访问Time类里的私有变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;

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

四、内部类

概念倘若一个A类定义在一个B类的内部,那我们就称A为B的内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。因为外部类对内部类没有任何优越的访问权限。恰恰相反,内部类天生是外部类的友元类,内部类可以访问外部类的非公有成员。

特性:
1、内部类可以定义在外部类的任意位置,不受public,private,protected的限制。
2、内部类可以直接访问外部类的成员(包括static,且使用静态变量时不需要外部类的对象/类名).
3、用sizeof(外部类)的时候不会受到内部类的影响,计算的还是外部类的大小。
class Time
{
	class Date//天生是Time的友元类
	{
	public:
		Date(int year = 1900, int month = 1, int day = 1)
			:_year(year)
			, _month(month)
			, _day(day)
		{

		}
		void Couttime(const Time&a)
		{
			//Date类的成员函数,却可以直接访问Time类里的私有变量
			cout << a._hour << ' ' << a._minute << ' ' << a._second;
			cout << k;

		}
	private:
		int _year;
		int _month;
		int _day;
	};
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		:_hour(hour)
		, _minute(minute)
		, _second(second)
	{

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

五、匿名对象

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

int main()
{
	A();
	cout << 111 << endl;
	return 0;
}

 运行代码,我们可以看见:

通过A();我们生成了一个匿名的对象,这个匿名对象的生命周期只有这一行,过后就会自动调用析构函数。

总结:

通过这三节的讲解,大家要知道, 类就是对某一类实体 ( 对象 ) 来进行描述的,描述该对象具有那
些属性,那些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化
具体的对象 。

通过理解类与对象的概念,我们可以更好地应用面向对象的设计思想来解决实际问题,提高代码的质量和效率。同时,也能够更好地理解和应用面向对象编程语言中的各种特性和技术,如继承、多态、封装等。

在学习过程中,我们可能会遇到各种挑战和困难,但只要保持学习的态度,不断探索和实践,就能够不断提升自己的编程水平。希望本文能够为您对类与对象的理解提供一些帮助,并在您的编程之旅中起到一定的指导作用。