Bootstrap

【C++】类和对象(下)

一、再探构造函数

<1>
之前我们在实现构造函数时,是在函数体内完成的,初始化还有另外一种办法,初始化列表,初始化列表的表达方式是以一个冒号开始的:接着是以逗号,分割的数据成列表,每个成员后跟一个括号,括号中是初始值或者表达式
<2>
每个成员只能在初始化列表上只能出现一次,从语法上理解,可以理解为初始化列表是成员变量定义初始化的地方
<3>
引用成员变量,const成员变量,自定义类类型没有构造的成员变量,必须通过初始化列表进行初始化,否则程序会报错。

#include<iostream>
using namespace std;

class T
{
public:
	T(int a)
	{
		cout << "Func()" << endl;
	}
};

class A
{
public:
	//构造函数
	A(int& x ,int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,_cst(5)
		,_ra(_year)
		,_t(1)
	{
		//不需要开辟空间就不用
	}

private:
	int _year;
	int _month;
	int _day;

	// const 类型
	const int _cst;
	//引用类型
	int& _ra;
	//自定义类型
	T _t;
	
};

int main()
{
	int x = 5;
	A aa(x, 2024, 11, 26);

	return 0;
}

所以根据上面的代码,我们发现类类型变量是起一个声明作用,在初始化列表会将他们定义
<4>
C++11 支持在声明成员变量的位置给缺省值,这个缺省值主要是给在初始化列表里没有显示出来的成员变量用的

#include<iostream>
using namespace std;

class A
{
public:
	A(int year = 1, int month = 1, int day = 1)
		:_year(year)
		,_month(month)
	{

	}

	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

private:
	//C++11 新加入的
	//并不是赋值,而是在初始化列表没有初始化时,就会默认用这个缺省值
	int _year = 2;
	int _month = 2;
	int _day = 2;
};

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

<5>
尽量使用初始化列表进行初始化,因为你不在初始化列表初始化的成员也会走向初始化列表,从下面的动图就可以看到,当到 _day 这一成员变量时,也同样会走到上面的初始化列表,如果这个成员在声明时有缺省值就会使用缺省值进行初始化。如果没有给缺省值,对于没有显示类型在初始化列表初始化的成员变量怎么处理取决于编译器,C++没有规定。对于自定义类型若没有显示类型在初始化列表初始化会调用这个自定义变量的构造函数,若没有就会报错
在这里插入图片描述

<6>
初始化列表中初始化顺序按照类中声明的顺序,跟成员变量在初始化列表中的顺序无关,建议声明顺序与初始化顺序一样

#include<iostream>
using namespace std;

class A
{
public:
	A(int a = 1, int b = 1)
		:_a1(a)
		,_a2(b)
	{

	}
private:
	int _a2;
	int _a1;
};

int main()
{
	A aa;
	return 0;
}

在这里插入图片描述
从这里可以看出,会先初始化 _a2

初始化列表总结:

无论是否写初始化列表,每个构造函数都会走初始化构造
无论是否在初始化列表初始化,类的成员都会走一般初始化列表
在这里插入图片描述

二、类型转换

<1>
C++ 支持内置类型隐式类型转换为类类型对象,需要有关内置类型为参数的构造函数
<2>
构造函数前加explicit就不再支持隐式类型转换
<3>
类类型对象之间也可以隐式类型转换,但需要对应的构造函数

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1)
		:_a1(a1)
	{

	}
	A(int a1, int a2)
		:_a1(a1)
		,_a2(a2)
	{

	}

	int Add() const
	{
		return _a1 + _a2;
	}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	B(A& aa)
		:_b(aa.Add())
	{

	}
	void Print()
	{
		cout << _b << endl;
	}
private:
	int _b = 0;
};

int main()
{
	//将 1 隐式类型转换为A类型,并重载成第一个构造函数
	A aa1 = 1;
	aa1.Print();
	//将两个数隐式类型转换为A类型,并重载成第二个构造函数
	A aa2 = { 4,5 };
	aa2.Print();
	//将aa2重载成B类型
	B bb1 = aa2;
	bb1.Print();

	return 0;
}

三、static 成员

<1>
用static修饰的成员变量,称为静态成员变量,静态成员变量一定要在类外面进行初始化
<2>
静态成员变量为所以类对象共享,不属于某个对象,不存在对象中,存在静态区
<3>
用static修饰的成员函数,称为静态成员函数,静态成员函数没有this指针
<4>
静态成员函数可以访问静态成员变量,不能访问类成员变量,因为没有this指针
<5>
非静态成员函数可以任意访问静态成员变量

#include<iostream>
using namespace std;

class A
{
public:
	A(int a1 = 1, int a2 = 1)
		:_a1(a1)
		,_a2(a2)
	{

	}
	
	void Print()
	{
		//非静态成员函数可以任意访问静态成员变量
		cout << _a << " " << _a1 << " " << _a2 << endl;
	}

	static void GetStatic()
	{
		cout << _a << endl;
		//cout << _a1 << " " << _a2 << endl;
		//无法访问因为没有this指针
	}
private:
	//类内声明,在创建对象时,不会为静态成员变量开辟空间
	static int _a;
	int _a1;
	int _a2;//开8个字节的空间
};
//类外定义
int A::_a = 12;

int main()
{
	A aa;
	//对象大小
	cout << sizeof(aa) << endl;
	//输出为 8

	//调用
	aa.Print();
	aa.GetStatic();
	return 0;
}

<6>
突破类域就可以访问静态成员变量,通过 " : : " " . "甚至类指针
<7>
静态成员也是类成员,受 public, private, protected访问限定符的限制
<8>
静态成员变量不能在声明位置给缺省值,因为缺省值是给初始化列表用的,而静态成员变量不走初始化列表,在类外面进行初始化,所以不能给缺省值

#include<iostream>
using namespace std;

class A
{
public:
	A(int a2 = 1, int a3 = 1)
		:_a2(a2)
		,_a3(a3)
	{

	}
	//在公共区
	static int _a_pub;
private:
	//在私有区
	static int _a1;
	int _a2;
	int _a3;
};
int A::_a_pub = 12;

int main()
{
	A aa;

	//用 ::
	cout << A::_a_pub << endl;

	//用 .
	cout << aa._a_pub << endl;

	//用指针
	A* pub = nullptr;
	cout << pub->_a_pub << endl;

	//无法用以上方法访问 _a1 因为是私有

	return 0;
}

四、友元

<1>
友元提供了一种可以突破类访问限定符的方式,友元分为:友元函数,友元类,在函数声明或者类声明前加一个friend,并将友元声明放入一个类里面
<2>
外部友元函数可以访问类的成员变量,友元函数只是一种声明,不是类的成员函数
<3>
友元函数声明可以定义在类的任何位置,不受访问限定符的限定,但是一般建议放在最前面
<4>
一个函数可以是多个类的友元函数

#include<iostream>
using namespace std;

//声明一下B让A知道有这个类型
class B;

class A
{
	//友元声明可以在多个类
	friend void func(const A& a, const B& b);
public:
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
private:
	//友元声明可以加在任何位置
	friend void func(const A& a, const B& b);
	int _b1 = 3;
	int _b2 = 4;
};

void func(const A& a, const B& b)
{
	//访问A
	cout << a._a1 << " " << a._a2 << endl;

	//访问B
	cout << b._b1 << " " << b._b2 << endl;
}

int main()
{
	A aa;
	B bb;
	func(aa, bb);
	return 0;
}

<5>
友元类中的成员函数都可以是另一个类的友元函数,都可以访问类的私有和保护变量

#include<iostream>
using namespace std;

class A
{
	friend class B;
public:
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	void Print(const A& a)
	{
		cout << a._a1 << " " << a._a2 << endl;
		cout << _b1 << " " << _b2 << endl;
	}
private:
	int _b1 = 3;
	int _b2 = 4;
};

int main()
{
	A aa;
	B bb;
	bb.Print(aa);
	return 0;
}

<6>
友元类的关系是单向的,比如A是B的友元,但是B不是A的友元
<7>
友元类不具有传递性,比如A是B的友元,B是C的友元,但不能说A是C的友元
<8>
友元提供了便利,但是破坏了耦合性,不利于封装,不宜多用

五、内部类

<1>
如果一个类定义在另一个类的内部,那么这个类就叫做内部类。内部类是一个独立的类,他只受到外部类和访问限定符的限制,所以外部类定义的对象中不包含内部类
<2>
内部类默认是外部类的友元类
<3>
内部类本质就是一种封装,当A跟B紧密相连,A设计出来就是给B用时,外面可以将A直接定义在B的private和protected中,专供B使用

class A
{
public:
	class B
	{
	public:
		void Print(const A& a)
		{
			cout << a._a1 << " " << a._a2 << endl;
			cout << _b1 << " " << _b2 << endl;
		}
	private:
		int _b1 = 3;
		int _b2 = 4;
	};

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}

private:
	int _a1 = 1;
	int _a2 = 2;
};

int main()
{
	A aa;
	aa.Print();
	cout << endl;

	A::B bb;
	bb.Print(aa);
	return 0;
}

六、匿名对象

<1>
用 类型(实参)
定义出来的对象就叫匿名对象
类型 对象名(实参)
定义出来的叫有名对象
<2>
匿名对象的声明周期只有一行,所以在临时需要定义一个类的对象就用一下,就可以用匿名对象
<3>
下面外面来应用一下,对比上面的一些代码的一些地方,匿名对象会更方便

//有名对象
A* pub = nullptr;
cout << pub->_a_pub << endl;
//匿名对象
cout << A()._a_pub << endl;

包括我们如果只是想调用某一类的函数时,我们不需要将它实例化,我们可以直接用匿名对象

A(1, 2).Print;
;