• 【C++】类和对象 — 日期类的实现 运算符重载 初始化列表 友元(下篇)


    前言

    前情回顾:
    👉 类和对象(上篇)
    👉 类和对象(中篇)

    上两篇已经讲了类和对象的基本使用和最难理解的默认成员函数,接下来将继续深入学习类和对象剩余的内容。


    1. 运算符重载

    1.1 运算符重载的引入:

    • 内置类型,可以直接用各种运算符
    • 自定义类型,不能直接用各种运算符

    为了自定义类型可以自己使用各种运算符,边引入了运算符重载

    C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

    • 函数名字为:关键字operator后面接需要重载的运算符符号。
    • 函数原型:返回值类型 operator操作符(参数列表)

    1.2 运算符重载的使用:

    #include
    using namespace std;
    
    class Date
    {
    public:
    	Date(int year = 1, int month = 1, int day = 1)
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    
    	bool operator==(Date d2)
    	{
    		return _year == d2._year
    			&& _month == d2._month
    			&& _day == d2._day;
    	}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    int main()
    {
    	Date d1(2022, 5, 16);
    	Date d2(d1);
    
    	//可以这样写 - 这样写和使用函数一样
    	//if (d1.operator==(d2))
    	
    	//更多是下面这样用
    	if (d1 == d2)//编译器会处理成对应调用if (operator==(d1, d2))
    	{
    		cout << "==" << endl;
    	}
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 可以当做正常的函数使用,但是更多的时候是直接使用重载之后的运算符
    • if (d1 == d2)编译器会处理成对应调用if (operator==(d1, d2))

    注意:

    • 不能通过连接其他符号来创建新的操作符:比如operator@
    • 重载操作符必须有一个类类型参数
    • 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
    • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
    • .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

    2. 日期类的实现:

    2.1 头文件:(Date.h)

    #pragma once
    
    #include
    #include
    
    using std::cout;
    using std::cin;
    using std::endl;
    
    class Date
    {
    	//友元函数
    	friend std::ostream& operator<<(std::ostream& out, const Date& d);
    	friend std::istream& operator>>(std::istream& in, Date& d);
    public:
    	bool isLeapYear(int year)
    	{
    		return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
    	}
    
    	int GetMonthDay(int year, int month);
    
    	Date(int year = 1, int month = 1, int day = 1);
    
    	Date(const Date& d)
    	{
    		_year = d._year;
    		_month = d._month;
    		_day = d._day;
    	}
    
    	//传引用返回
    	Date& operator=(const Date& d)
    	{
    		//避免自己给自己赋值
    		if (this != &d)
    		{  
    			_year = d._year;
    			_month = d._month;
    			_day = d._day;
    		}
    
    		return *this;
    	}
    
    	bool operator==(const Date& d) const;
    	bool operator<(const Date& d) const;
    
    	Date operator+(int day) const;
    	Date& operator+=(int day);
    
    	Date operator-(int day) const;
    	Date& operator-=(int day);
    
    	//C++规定有参数的是后置的,无参数的是前置的
    	Date& operator++()   //前置
    	{
    		*this += 1;
    		return *this;
    	}
    
    	Date operator++(int i) //后置 - 不能加缺省值,因为不传参的时候分不清楚,或者不加参数只写类型
    	{
    		Date tmp(*this);
    		*this += 1;
    		
    		return tmp;
    	}
    
    	//d1 - d2
    	int operator-(const Date& d) const;
    
    	//inline不支持声明和定义分别放到.h 和 .cpp
    	//所以成员函数中要称为inline最好直接在类里面定义
    	//类里面定义默认是inline
    	bool operator>(const Date& d) const
    	{
    		//return d < *this;
    		return !(*this <= d);
    	}
    
    	bool operator>=(const Date& d) const
    	{
    		return !(*this < d);
    	}
    
    	bool operator!=(const Date& d) const
    	{
    		return !(d == *this);
    	}
    
    	//d1 <= d2
    	bool operator<=(const Date& d) const
    	{
    		return *this < d || *this == d;
    	}    
    
    	//建议成员函数中不修改成员变量的成员函数,都可以加上const
    	//普通对象和const对象都可以调用
    	void Print() const//等价于void Print(const Date* const this) - 保护指针指向的内容
    	{
    		cout << _year << "-" << _month << "-" << _day << endl;
    	}
    	
    	Date* operator&()
    	{
    		return this;
    	}
    
    	//const取地址重载,返回的是一个const Date*的指针
    	const Date* operator&() const 
    	{
    		return this;
    	}
    
    	
    	/*void operator<<(std::ostream& out)
    	{
    		out << _year << "-" << _month << "-" << _day << endl;
    	}*/
    
    	/*void operator>>(std::istream& in)
    	{
    
    	}*/	
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 全局函数不能在头文件定义,因为其他源文件包含时候会都展开
    • 相当于一个函数在多个源文件中定义,符号表中都有函数的定义,链接的时候就冲突了
    • 类里面定义的默认是内联函数是不会放到符号表中的

    2.2 函数具体实现:(Date.cpp)

    #include"Date.h"
    
    //成员函数在类里面声明在类外面定义了 - 也是类里面的函数
    //所以是可以访问私有的,因为这也算是类里面的成员函数
    
    //写一个小于等于或者是大于等于其他的都可以复用了
    bool Date::operator<(const Date& d) const
    {
    	if (_year < d._year || _year == d._year && _month < d._month
    		|| _year == d._year && _month == d._month && _day < d._day)
    	{
    		return true;
    	}
    	else
    	{
    		return false;
    	}
    }
    
    //d1 == d2
    bool Date::operator==(const Date& d) const
    {
    	return _year == d._year
    		&& _month == d._month
    		&& _day == d._day;
    }
    
    int Date::GetMonthDay(int year, int month)
    {
    	assert(year >= 0 && month > 0 && month < 13); 
    	//加上static的好是避免平凡开辟数组空间
    	static int monthDayArray[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
    	if (month == 2 && isLeapYear(year))
    	{
    		return 29;
    	}
    	else
    	{
    		return monthDayArray[month];
    	}
    }
    
    Date::Date(int year, int month, int day)
    {
    	if (year >= 1 &&
    		month <= 12 && month >= 1 &&
    		day >= 1 && day <= GetMonthDay(year, month))
    	{
    		_year = year;
    		_month = month;
    		_day = day;
    	}
    	else
    	{
    		cout << "日期非法" << endl;
    	}
    }
    
    d1 + 100 -->d1.operator(day);
    d1被改变了,d1是不应该被改变的
    //Date Date::operator+(int day)
    //{
    //	_day += day;
    //	while (_day > GetMonthDay(_year, _month))
    //	{
    //		_day -= GetMonthDay(_year, _month);
    //		_month++;
    //		if (_month == 13)
    //		{
    //			_year++;
    //			_month = 1;
    //		}
    //	}
    //
    //	return *this;
    //}
    
    //运用拷贝构造:
    Date Date::operator+(int day) const
    {
    	Date ret(*this);
    
    	/*ret._day += day;
    	while (ret._day > GetMonthDay(ret._year, ret._month))
    	{
    		ret._day -= GetMonthDay(ret._year, ret._month);
    		ret._month++;
    		if (ret._month == 13)
    		{
    			ret._year++;
    			ret._month = 1;
    		}
    	}*/
    
    	ret += day;
    
    	//出了作用域这个ret对象就不在了  
    	return ret;
    }
    
    Date& Date::operator+=(int day)
    {
    	if (day < 0)
    		return *this -= -day;
    	_day += day;
    	while (_day > GetMonthDay(_year, _month))
    	{
    		_day -= GetMonthDay(_year, _month);
    		_month++;
    		if (_month == 13)
    		{
    			_year++;
    			_month = 1;
    		}
    	}
    
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 运算符重载加的时候,一个日期加一个日期原来的值应该不被改变
    • 所以这里巧妙运用了一下拷贝构造
    • 并且加等和加是可以相互复用的
    Date Date::operator-(int day) const
    {
    	Date ret = *this;
    	ret -= day;
    	 
    	return ret;
    }
    
    //用传值返回一定是对的,但是不一定是高效的
    Date& Date::operator-=(int day)
    {
    	if (day < 0)
    		return *this += -day;
    
    	_day -= day;
    	while (_day <= 0)
    	{
    		_month--;
    		if (_month == 0)
    		{
    			_month = 12;
    			_year--;
    		}
    		_day += GetMonthDay(_year, _month);
    	}
    
    	return *this;
    }
    
    //d1 - d2
    int Date::operator-(const Date& d) const
    {
    	int flag = 1;
    	Date max = *this;
    	Date min = d;
    	if (*this < d)
    	{
    		min = *this;
    		max = d;
    		flag = -1;
    	}
    
    	int n = 0;
    	while (min != max)
    	{
    		n++;
    		min++;
    	}
    
    	return n * flag;
    }
    
    //流插入
    std::ostream& operator<<(std::ostream& out, const Date& d)
    {
    	out << d._year << "-" << d._month << "-" << d._day << endl;
    	return out;
    }
    
    //流提取
    std::istream& operator>>(std::istream& in, Date& d)
    {
    	in >> d._year >> d._month >> d._day;
    	return in;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    cout 和 cin其实是全局的对象,包含在iostream这个头文件中,库中实现的时候就将其重载了
    istream和ostrem的对象,还用到了运算符重载

    在这里插入图片描述
    在这里插入图片描述
    将cout符号重载写成全局的原因:

    • 因为成员函数第一个参数默认都是隐式的this指针
    • 正常的流插入cout是(假设有个对象d),cout << d1;
    • 而现在却是 d1 << cout,这与我们正常的使用习惯有所出入
    • 所以写成全局的,在类外面实现,传两个参数实现,这样就能实现正常的使用了

    能传引用的地方尽量传引用,能返回引用的地方也尽量返回引用,可以提高效率

    补充:

    int main()
    {
    	Date d1(2022, 5, 18);
    	//Date d2 = d1 + 15;
    	Date d3 = d1; //这里调用的是拷贝构造,并不是赋值重载Date d3(d1)
    	//拷贝构造是用一个对象去初始化同类型的对象
    
    	//d1 = d2;//两个已经存在的对象才是赋值
    	d1 += 15;
    	d1.Print();
    	//d2.Print();
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    Date d3 = d1; 这里并不是赋值运算符的重载,而是调用拷贝构造
    拷贝构造是用一个对象初始化同类型的对象
    **d1 = d2;**这才是调用负值重载,对于两个已经存在的对象才是调用赋值重载


    3. const对隐式指针this的修饰:

    3.1 const的普通用法:

    1. 误写
    • 不该修改的值如果被误写的话,加上const是不能被改的,会报错
    1. 具有const属性的变量有可能会权限被放大
      在这里插入图片描述

    正确的做法应该是将this的指针用const修饰,但是又因为this指针是隐式的所以C++新增了一个语法就是在成员函数后面直接加上const,表示给this指针的类型修改成 :const 类类型* const this

    在这里插入图片描述
    补充:
    如果声明和定义分离,那么声明和定义都要加上const。


    3.2 加const的应用场景:

    补充:

    int a = 10;
    int* const pa = &a;
    int* pb = pa;
    
    • 1
    • 2
    • 3

    上述写法是允许的,只是赋值。

    int a = 10;
    const int* pa = &a;
    int* pb = pa;
    
    • 1
    • 2
    • 3

    上述写法是不允许的,因为&a是int* 类型的指针,但是const int*类型的指针是不能通过指针修改指针指向的内容的,两者是有区别的。

    在这里插入图片描述
    在这里插入图片描述
    综上所述:

    如果传参的值是不希望被修改的就要考虑加上const加以保护,同时还要考虑连续运算符中返回值产生临时变量的问题,也是const该用到的地方。


    4. 初始化列表

    4.1 初始化列表的引入:

    1.每一个对象都有自己的空间,成员变量是属于某个对象的,对象是整体定义的,而里面的成员变量是什么时候定义的呢?

    • 成员变量是在初始化列表定义的

    2.再次理解构造函数:

    • 虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化。
    • 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

    3.必须在定义的时候初始化的值:

    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)

    注意:类中包含以上成员,必须放在初始化列表位置进行初始化

    初始化列表可以认为就是对象成员变量定义的地方,并且尽量要在初始化列表初始化。

    4.2 初始化列表的使用:

    • 初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
    int value = 1;
    class B
    {
    public:
    	B(int a = 0)
    	{
    		_a = a;
    	}
    private:
    	int _a;
    };
    
    class A
    {
    public:
    	A()
    		:_b(2)
    		,bb(1)
    		,_ref(value)
    
    	{
    		_a = 1;
    		
    	}
    private:
        //可以定义时初始化
        //也可以定义时不初始化,后面再赋值修改
    	int _a;
    
        //下面的只能在定义的时候初始化
    	int& _ref;
    	const int _b;
    	B bb;
    };
    
    int main()
    {
    	A aa;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    如下代码:

    • 如果MyQueue这个类构造函数不写,编译器会生成一个默认构造函数
    • 该默认构造对于内置类型不处理,对于自定义类型处理调用该类型的默认构造

    如果写了默认构造但是却不对自定义类型处理,但是调试的过程中会发现,编译器还是对内置类型进行了处理。
    为什么显式写的构造函数中没有写自定义类型的构造函数,但是还是对自定义类型进行处理了呢?

    • 这是因为所有的构造函数都走了一遍初始化列表
    • 初始化列表显式初始化了某个成员,它就会被初始化
    • 如果没有显式的写,也会被初始化
    • 只是对内置类型没有值去初始化,自定义类型会去调用它的构造函数
    class Stack
    {
    public:
    	Stack(int capacity = 0)
    	{
    		_a = (int*)malloc(sizeof(int) * capacity);
    		assert(_a);
    		_top = 0;
    		_capacity = capacity;
    	}
    private:
    	int* _a;
    	int _top;
    	int _capacity;
    };
    
    class MyQueue
    {
         MyQueue()
         {}
         
    private:
    	Stack _st1;
    	Stack _st2;
    	int _size = 0; //缺省值,给初始化列表用的
    };
    
    int main()
    {
    	MyQueue mq;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    5. static修饰成员变量和成员函数

    5.1 static修饰的特性:

    • 声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量
    • 用static饰的成员函数,称之为静态成员函数
    • 静态成员变量一定要在类外进行初始化
    • 静态成员变量属于这个类,可以用指定类域去访问,不需要对象,也可以是属于任何一个对象
    • 静态成员变量可以认为是类共享的

    5.2 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;
    
    int main()
    {
    	cout << A::GetACount() << endl;
    	A a1, a2;
    	A a3(a1);
    	cout << A::GetACount() << endl;
    
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    int A::_scount = 0:

    • 在main函数之前就创建好了,生命周期是全局的
    • 全局的静态,函数里面局部的静态,类里面的静态,生命周期都是全局的

    只是全局的静态他的作用域是全局的,在全局任何地方都能使用
    局部的静态只能在函数里面使用,类的静态,只能在类里面使用,或者说指定类域,去类域里面找可以访问。

    总结:

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

    6. 友元

    6.1 友元的引入:

    • 友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
    • 友元分为:友元函数和友元类
    • 友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

    6.2 友元的使用:

    #include
    using namespace std;
    
    class Date
    {
    	friend ostream& operator<<(ostream& _cout, const Date& d);
    	friend istream& operator>>(istream& _cin, Date& d);
    public:
    	Date(int year = 1900, int month = 1, int day = 1)
    		: _year(year)
    		, _month(month)
    		, _day(day)
    	{}
    private:
    	int _year;
    	int _month;
    	int _day;
    };
    ostream& operator<<(ostream& _cout, const Date& d) {
    	_cout << d._year << "-" << d._month << "-" << d._day;
    	return _cout;
    }
    istream& operator>>(istream& _cin, Date& d) {
    	_cin >> d._year;
    	_cin >> d._month;
    	_cin >> d._day;
    	return _cin;
    }
    int main()
    {
    	Date d;
    	cin >> d;
    	cout << d << endl;
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    注意:

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

    6.3 友元类:

    • 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
    • 友元关系是单向的,不具有交换性。

    友元关系不能传递:

    • 如果B是A的友元,C是B的友元,则不能说明C时A的友元。
    • 友元关系不能继承,在继承位置再给大家详细介绍
  • 相关阅读:
    CocoaPods podfile 文件配置
    CsPbI3钙钛矿量子点 CsPbI3 QDs发射波长670±10nm
    40-Java方法重载、return关键字的单独使用
    JavaGUI------------常用的组件(标签、按钮)
    JVM 内存结构
    vue之封装预约类组件
    Java项目:JSP的电影院售票系统(含论文、任务书、中期检查表)
    WPF Border控件 基本使用
    找不到VCRUNTIME140_1.dll怎么办,VCRUNTIME140_1.dll丢失的5个解决方法
    计算机竞赛 机器视觉 opencv 深度学习 驾驶人脸疲劳检测系统 -python
  • 原文地址:https://blog.csdn.net/m0_63059866/article/details/126083775