[C++基础](6)类和对象(下) :友元,内部类,初始化列表,静态成员

本文详细探讨了C++中的友元函数和友元类,包括如何使用友元实现流插入和提取运算符重载。此外,讲解了构造函数的体内赋值与初始化列表的区别,并强调了explicit关键字在防止隐式类型转换中的作用。最后,阐述了静态成员变量和静态成员函数的概念,以及它们在内存管理和类行为中的角色。
摘要由CSDN通过智能技术生成


紧接上一篇👉: 【C++】(5)类和对象练习,日期类的实现,运算符重载

友元

友元函数

要想实现流插入<<运算符的重载,首先简单看一下下图。

img

原来cout其实是一个对象,它的类型为ostream。一个<<有两个操作数,那么我们也可以把它作为运算符重载的一个参数。

如果重载<<写在类里面:

void operator<<(std::ostream& out)
{
    out << _year << "-" << _month << "-" << _day << endl;
}

调用:

因为this指针默认是第一个参数,所以d要写在cout前面。

void test4()
{
	Date d(1919, 8, 10);
	d << cout;
}
//结果:1919-8-10

👆:程序跑起来是没什么问题,但是这个d << cout;的调用方式也太奇怪了吧,我们平时都是cout写在前面的啊。


看来只能写成全局的:

注意

  • 全局函数不能写在.h文件里,因为在Date.cpp和test.cpp同时展开,会造成重定义。
  • 为了支持连续的流插入,需要有返回值。
std::ostream& operator<<(std::ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}

但是全局的函数在类外,访问不了类里面的私有成员。

我们可以把它设置为类的==友元函数==:

友元函数特性

  • 友元函数需要在类内声明,声明时要加friend关键字
  • 友元函数可以在类的任意地方声明,不受访问限定符的限制
  • 友元函数可以直接访问类内的私有和保护成员,它是定义在类外的普通函数,不属于任何类
class Date
{
	friend std::ostream& operator<<(std::ostream& out, const Date& d); //友元函数声明
public:
    //略...
private:
	int _year;
	int _month;
	int _day;
};

这样我们的调用就可以写成cout << d;了。


流提取>>运算符的重载与上述同理,cin也是对象,类型是istream

注意检查输入的日期是否合法。

class Date
{
	friend std::ostream& operator<<(std::ostream& out, const Date& d); //友元函数声明
	friend std::istream& operator>>(std::istream& in, Date& d);
public:
    //略...
private:
	int _year;
	int _month;
	int _day;
};

std::istream& operator>>(std::istream& in, Date& d)
{
	int year, month, day;
	in >> year >> month >> day;
	if (year >= 1
		&& month <= 12 && month >= 1
		&& day >= 1 && day <= d.GetMonthDay(year, month))
	{
		d._year = year;
		d._month = month;
		d._day = day;
	}
	else
		cout << "日期非法" << endl;
	return in;
}

友元类

一个类可以是另一个类的友元,友元类可以访问另一个类的私有成员。

  • 友元关系不具有交换性,比如下述Date类可以访问Time类的私有成员,Time不能访问Date类的私有成员。
  • 友元关系不具有传递性,A类是B类的友元,B类是C类的友元,A类不一定是C类的友元。
class Date; // 前置声明
class Time
{
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问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 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;
};

内部类

如果一个类定义在另一个类的内部,那么这个类就叫做内部类

  • 内部类天生就是外部类的友元。
  • 内部类受外部的访问限定符的限制。
class A
{
public:
	class B //B是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;
			cout << a.h << endl; //B可以访问A的私有成员
		}
	private:
		int _b;
	};
private:
	static int k;
	int h;
};

sizeof(A)是多少?

答:A类型的对象里面没有B类型的对象,static修饰的k不算大小,所以只有一个int h占大小,答案为4

再谈构造函数

构造函数体内赋值

我们之前写的构造函数,成员变量在函数体内初始化:

//函数体内初始化
Date(int year = 1, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

严格来说,函数体内初始化并不能叫做初始化,应该叫赋初值,因为初始化只有一次,而函数体内可以多次赋值。对于必须在定义时就初始化的类型,比如引用,函数体内就写不了了。

初始化列表

使用方法

初始化列表:以一个冒号:开始,接着是由逗号,分隔的数据成员列表,每个成员变量后跟一个括号,括号内为初始值或表达式。

//初始化列表初始化
Date(int year = 1, int month = 1, int day = 1)
	:_year(year)
	, _month(month)
	, _day(day)
{}

初始化列表才是对象里的成员定义的地方,编译器自己生成的默认构造函数也有初始化列表,用来定义成员变量,这也是为什么它对内置类型不处理(随机值),而自定义类型会调用它的默认构造函数。

C++11支持在成员变量声明处给缺省值,给的缺省值其实就是给初始化列表用的。


注意

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能有一次)
  2. 以下三类必须在初始化列表初始化:引用成员变量const成员变量自定义类型成员(其类里面没有默认构造函数)

三种类型成员变量在初始化列表初始化:

class Date
{
public:
	Date(int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};

class A
{
public:
	A(int a, int ci, const Date& d)
		:_a(a)
		, _ci(ci)
		, _d(d)
	{}
private:
	int& _a; //引用
	const int _ci; //const修饰
	Date _d; //无默认构造的自定义类型
};

👆:A类中有Date类型的成员变量,而Date类没有提供默认构造函数,所以Date类在定义的时候就必须手动传参初始化。

建议:成员变量尽量都在初始化列表初始化。


成员变量在初始化列表中的初始化顺序是它在类中的声明次序,与它在初始化列表中的顺序无关

如下代码的结果是多少?

class A
{
public:
	A(int n)
		:_a(n)
		, _b(_a)
	{}
	void Print()
	{
		cout << _a << " " << _b << endl;
	}
private:
	int _b;
	int _a;
};

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

答案:1 随机值

解析:_b先声明,先初始化_b,因为_b(_a)中的_a还未初始化,所以_b最后被初始化为随机值。接着_a(n)n为1,最后_a被初始化为1,所以最终结果为:1 随机值

explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用

class Date
{
public:
	Date(int year)
		:_year(year)
	{}
	void Print()
	{
		cout << _year << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022); //构造
	Date d2 = 2023; //隐式类型转换
	d2.Print(); //结果:2023
	return 0;
}

对于Date d2 = 2023; ,一般编译器会使用2023构造一个临时对象,然后用它拷贝构造d2,不过编译器通常会对这种过程进行优化,直接使用2023去构造d2。

另外因为临时对象具有常性,它的引用必须加const,比如const Date& d3 = 2022;


explicit关键字可以用来修饰构造函数,被修饰的构造函数会被禁止隐式类型转换:

class Date
{
public:
	explicit Date(int year) //被explicit修饰的构造函数
		:_year(year)
	{}
	void Print()
	{
		cout << _year << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022); //构造
	Date d2 = 2023; //此处报错:不存在从 "int" 转换到 "Date" 的适当构造函数
	d2.Print();
	return 0;
}

static成员

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

静态成员变量

静态成员变量存储在静态区(不占用对象的空间),其属于整个类,也属于该类的所有对象

面试题:实现一个类,计算程序中创建出多少个该类的对象。

class A
{
public:
	A()
	{
		++_count;
	}

	A(const A& aa)
	{
		++_count;
	}
	static int _count; //静态成员变量
};

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

A func(A a)
{
	A copy(a);
	return copy;
}

int main()
{
	A a1;
	A a2 = func(a1);
	cout << A::_count << endl;//也可以a1._count访问
	return 0;
}
//结果:4

静态成员函数

static也可以修饰成员函数

注意static成员函数没有this指针,正因如此,它也无法直接访问其他非静态成员函数

class A
{
public:
	A()
	{
		++_count;
	}

	A(const A& aa)
	{
		++_count;
	}

	static int GetCount() //静态成员函数
	{
		return _count;
	}
private:
	static int _count; //设为私有,不能在类外直接访问的情况
};

int A::_count = 0; //在类外定义

A func(A a)
{
	A copy(a);
	return copy;
}

int main()
{
	A a1;
	A a2 = func(a1);
	cout << A::GetCount() << endl; //调用GetCount函数//也可以a1.GetCount()调用
	return 0;
}

👆:因为没有this指针,所以这里调用静态成员函数不需要指定对象,只要指定类域即可。


下面这道题,可以用静态成员变量解决:

原题链接:求1+2+3+…+n__牛客网 (nowcoder.com)

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

数据范围: 0 < n ≤ 200 0<n≤200 0<n200

进阶: 空间复杂度 O ( 1 ) O(1) O(1),时间复杂度 O ( n ) O(n) O(n)

示例1

输入 5

输出 15

示例2

输入 1

输出 1

参考代码:

class Sum
{
public:
    Sum()
    {
        _ret += _i;
        ++_i;
    }
    static int GetRet()
    {
        return _ret;
    }
private:
    static int _i;
    static int _ret;
};

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return Sum::GetRet();
    }
};
  • 10
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论
评论 9
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世真

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值