栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

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

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

文章目录
  • 友元
    • 友元函数
    • 友元类
  • 内部类
  • 再谈构造函数
    • 构造函数体内赋值
    • 初始化列表
    • explicit关键字
  • static成员
    • 静态成员变量
    • 静态成员函数

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

友元 友元函数

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

原来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

进阶: 空间复杂度 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();
    }
};
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/994735.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号