重生之C++糕手(类与对象下)

类与对象下

  • 前言
  • 一.初始化列表
    • 1.1初始化列表的定义
    • 1.2初始化列表的用处
    • 1.3explicit关键字
  • 二.友元
    • 2.1友元的定义
    • 3.2友元函数
    • 3.3友元类
  • 三.static成员
    • 3.1static成员的定义
  • 四.内部类
  • 五.匿名对象
  • 总结

前言

在学习了六大默认函数后,我们已经将类与对象的知识了解了七七八八可以拿捏百分之七八十的情况了但是仍有一些问题需要我们解决,而这些问题我们将会在这篇中掌握完全。

一.初始化列表

在上篇的构造函数中我们说了类的成员在声明时可以给默认值,而这个默认值的设置就牵扯到了我们今天所学的初始化列表

1.1初始化列表的定义

在学习了构造函数之后我们都知道要写一个类就要写他的构造函数,但是在我们使用当中发现了一些问题:
如果成员变量是要在定义的同时初始化例如:const和int&这类型的,还有成员变量是类的并且这个类还没有默认的成员函数。


```cpp
class Time
{
public:
	Time(int hour, int minute, int second)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class A
{
public:
	A(int year, int month, int day,int& p)
	{
		_year = year;
		_month = month;
		_day = day;
		_x = 1;
		_p = p;
		_t;
	}
private:
	int _year;
	int _month;
	int _day;
	//定义的同时需要初始化
	const int _x;
	int& _p;
	//没有默认构造函数
	Time _t;
};
int main()
{
	A a(2023, 8, 3);
	return 0;
}

这样的代码就会出现问题,而初始化列表的出现就会解决这个问题

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

class Time
{
public:
	Time(int hour, int minute, int second)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class A
{
public:
	A(int year, int month, int day, int& p)
		:_x(1)//初始化列表
		,_p(p)
		,_t(22,47,0)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << ' ' << _x << ' ' << _p << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	//定义的同时需要初始化
	const int _x;
	int& _p;
	Time _t;
};
int main()
{
	int p = 2;
	A a(2023, 8, 3,p);
	a.Print();
	return 0;
}

注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    引用成员变量
    const成员变量
    自定义类型成员(且该类没有默认构造函数时)

1.2初始化列表的用处

在构造函数中我们得知构造函数是将成员变量初始化的地方,但是这个概念在我们学习了初始化列表之后要稍加具体,构造函数的初始化的功能是在初始化列表中进行,而函数体内只是进行了具体的赋值。

那么为什么在之前使用构造函数时我们没有对初始化列表进行显式实现但没有出现错误呢?
因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。而内置类型则会给予一个随机值

重生之C++糕手(类与对象下)_第1张图片

这里我们可以看见内置类型的三个成员变量即使没有显式实现在初始化列表中但是它们已经定义而后在函数体内进行赋值而已。

class Time
{
public:
	Time(int hour = 1, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class A
{
public:
	A(int year, int month, int day, int& p)
		:_x(1)
		,_p(p)
		//,_t(22,47,0)//即使没有写在初始化列表中,它也会使用初始化列表进行初始化
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << ' ' << _month << ' ' << _day << ' ' << _x << ' ' << _p << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	//定义的同时需要初始化
	const int _x;
	int& _p;
	Time _t;
};
int main()
{
	int p = 2;
	A a(2023, 8, 3,p);
	a.Print();
	return 0;
}

注意:
成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

1.3explicit关键字

在C语言中我们就曾学习过隐式类型转换

int main()
{
	int i = 1;
	double b = 1.1;
	i = b;//隐式类型转换
	//即创建了一个临时变量被赋值为b,然后这个再将这个临时变量赋值给i。
	int& j = b;//报错,因为临时变量具有常性,这就造成了权限的放大。
	return 0;
}

那么在C++中也具有隐式类型转换但是条件变得更加苛刻了。

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

当explicit修饰构造函数,禁止了单参构造函数类型转换的作用

class A
{
public:
	//单个参数的隐式类型转换
	//explicit A(int a)
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	const void Print()const
	{
		cout << _a << endl;
	}
private:
	int _a;
};
class Date
{
public:
	//多参数的隐式类型转换
	//注意:除了第一个参数外其他参数均有默认值
	//explicit Date(int year, int month = 8, int day = 4)
	Date(int year, int month = 8, int day = 4)
		:_year(year)
		,_month(month)
		,_day(day)
	{
		cout << "	Date(int year, int month = 8, int day = 4)" << endl;
	}
	const void Print()const//const对象无法调用非const成员函数
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	A a(1);//正常使用构造函数
	A b = 2;//隐式类型转换
	//编译器用类型为int的2构造出了一个A类型的临时对象
	//再用拷贝构造生成了b
	//并且在编译器的优化下,构造加拷贝构造被优化成了一个构造
	// 
	// 
	//A& c = 2;//编译出错,因为临时对象具有常性,涉及权限的放大了
	const A& c = 3;//加了const即权限的平移

	Date d = 4;
	const Date& d1 = 5;
	//C++ 11
	Date d2 = { 2023,8,4 };//多参数的类,这样也构成隐式类型转换
	a.Print();
	b.Print();
	c.Print();
	d.Print();
	d1.Print();
	d2.Print();

	return 0;
}

重生之C++糕手(类与对象下)_第2张图片

二.友元

在中篇时我们在运算符重载中提及到,在类外定义的函数想要访问类的私有成员时要么重载成成员函数要么使用友元,那么友元到底是什么呢?

2.1友元的定义

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

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

3.2友元函数

在类的使用中我们时常需要打印出类的成员变量,但是一直需要调用这个成员函数,那么我们无论是为了图方便还是增加代码的可读性,我们可以利用运算符重载的知识将流提取<<进行重载。

class A
{
public:
	A(int year, int month, int day, int& p)
		:_year(year)
		,_month(month)
		,_day(day)
	{
	}
	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}
	//定义在类内的话,因为this指针抢占了第一个参数的位置
	ostream& operator<< (ostream& out)
	{
		out << _year << '/' << _month << '/' << _day << endl;
		return out;
	}
private:
	int _year;
	int _month;
	int _day;
	
};
//ostream& operator<< (ostream& out, A& a)
//{
//	out << a._year << '/' << a._month << '/' << a._day << endl;
//	return out;
//}
//在类外的我们无法访问类的私有成员
int main()
{
	int p = 2;
	A a(2023, 8, 3,p);
	a.Print();
	a << cout;
	//无论是从代码的价值还是我们使用的习惯来说都是很差劲的
	return 0;
}

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

class A
{
	friend ostream& operator<< (ostream& out, A& a);//友元声明
public:
	A(int year, int month, int day, int& p)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << '/' <<  endl;
	}
private:
	int _year;
	int _month;
	int _day;
	
};
ostream& operator<< (ostream& out, A& a)//A类的友元函数
{
	out << a._year << '/' << a._month << '/' << a._day << endl;
	return out;
}
int main()
{
	int p = 2;
	A a(2023, 8, 3,p);
	//a << cout;
	cout << a;
	return 0;
}

注意:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数。
  5. 友元函数的调用与普通函数的调用原理相同。

3.3友元类

不仅可以将一个函数声明成友元关系还可以将一整个类声明成友元关系

class Time
{
	friend class Date;//声明Date类是Time的友元,Date类可以访问Time的私有成员
		             //而Time类无法访问Date类的私有成员
public:
	Time(int hour = 1, int minute = 1, int second = 1)
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,_t(23,39,0)
	{}
	void Print()
	{
		cout << _t._hour << ':' << _t._minute << ':' << _t._second << endl;
		cout << _year << '/' << _month << '/' << _day  <<  endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d(2023, 8, 3);
	d.Print();
	return 0;
}

注意:

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
  2. 友元关系是单向的,不具有交换性。
  3. 友元关系不能传递,如果C是B的友元,B是A的友元,则不能说明C时A的友元。
  4. 友元关系不能继承,在继承位置再给大家详细介绍。

三.static成员

类的学习中,我们定义的成员变量和函数在每个对象中的值都是不同的,它们都存在各自对象的栈帧中。但是如果我们需要一个属于所有类的成员变量或者函数而不是某个类的成员变量或函数呢?这就需要我们定义静态成员变量或函数

3.1static成员的定义

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

当我们需要实现一个类,计算程序中创建出了多少个类对象和正在使用多少个类对象时

//1.正常使用
//创建了多少类
int n = 0;
//正在使用多少类
int m = 0;
class A
{
public:
	A()
	{
		n++;
		m++;
	}
	A(A& a)
	{
		n++;
		m++;
	}
	~A()
	{
		m--;
	}
};
int main()
{
	A a;
	A a1;
	cout << n << ' ' << m << endl;
	A();//匿名对象
	cout << n << ' ' << m << endl;
	return 0;
}
//弊端明显,C++讲究封装但是n和m的值可以随意修改
//但是如果定义为成员变量的话,在类外又无法进行访问而且每个对象的n和m值都不同


//2.静态成员方法
class A
{
public:
	A()
	{
		n++;
		m++;
	}
	A(A& a)
	{
		n++;
		m++;
	}
	~A()
	{
		m--;
	}
	//静态成员函数
	static void Print()
	{
		cout << n << ' ' << m << endl;
	}
private:
	//静态成员声明
	static int n;
	static int m;
};

//静态成员定义初始化
//必须在类外
int A::n = 0;
int A::m = 0;

int main()
{
	A a;
	A a1;
	A* n = nullptr;
	A();

	//静态成员变量为私有,无法在类外进行访问
	/*cout << n << ' ' << m << endl;*/

	//若静态成员变量为公有,下列四种方式皆可突破类域进行访问
	/*a.n;
	a1.n;
	n->n;
	A().n;*/

	//若定义为成员变量则需要每个对象进行调用,使代码价值和可读性下降
	/*a.Print();
	a1.Print();
	n->Print();
	A().Print();*/
	
	A::Print();
	return 0;
}

注意:

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明。
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问 。
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

四.内部类

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

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

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
	void Print()
	{
		cout << _year << '/' << _month << '/' << _day << endl;
	}
	//Date类天生就是Time类的友元
	class Time
	{
	public:
		Time(int hour = 1, int minute = 1, int second = 1)
			:_hour(hour)
			, _minute(minute)
			, _second(second)
		{}
		void Print(const Date& d)
		{
			cout << _hour << ':' << _minute << ':' << _second << endl;
			cout << d._year << '/' << d._month << '/' << d._day << endl;
		}
	private:
		int _hour;
		int _minute;
		int _second;
	};
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d(2023, 8, 3);
	Date::Time t(23, 49, 1);
	t.Print(d);
	return 0;
}

特性:

  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。

五.匿名对象

在我们使用类的时候会发现有时我们并不需要专门定义一个类,因为我们只需要类中的某个函数。这时候就到了我们匿名对象的上场时机。

class A
{
public:
	A(int a = 1)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
	int Add(int b ,int c)
	{
		return b + c;
	}
private:
	int _a;
};
int main()
{
	//有名对象
	A a1;
	//匿名对象
	A();
	//我们正常使用Add函数
	A a;
	cout << a.Add(2, 3) << endl;
	//使用匿名对象使用Add函数
	cout << A().Add(2, 3) << endl;
	return 0;
}

特性:

  1. 匿名对象不用取名
  2. 匿名对象的生命周期只有一行,下一行就会调用析构函数

总结

类与对象的学习就在这结束了,类与对象涉及甚广在上篇中我们了解了类的定义,访问限定符等,在中篇我们学习了六大默认函数的使用,而在下篇里我们学习了初始化列表,友元,静态成员等。这些知识繁琐且复杂需要我们二刷甚至三刷,并且大家光看无用更需要上手来使用才能了解更深。再见不是离别,下一个文章我会向大家介绍C++中有关内存和模板的知识,尽情期待!

你可能感兴趣的:(C++,c++)