<类与对象>总结与扩展——《C++初阶》

 

目录

         <类和对象>往期博文链接:

1. 回顾构造函数

1.1 构造函数体赋值

1.2 初始化列表

1.3 explicit关键字

2. static成员

2.1 概念

2.2 特性

3.C++11 的成员初始化新玩法。

4. 友元

4.1 友元函数

 4.2 友元类

5.内部类

5.1概念及特性

5.2题目:计算日期到天数转换:

5.3 关于构造+拷贝构造—>构造的优化问题:

5.4类的匿名对象:

        5.4.1匿名对象使用场景:

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

6. 再次理解封装

7.再次理解面向对象

可以看出面向对象其实是在模拟抽象映射现实世界。​编辑

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!                                                                     ——By 作者:新晓·故知


<类和对象>往期博文链接:

<类和对象(上)>:<类与对象(上)>——《C++初阶》_新晓·故知的博客-CSDN博客

<类和对象(中)>:<类与对象(中)>——《C++初阶》_新晓·故知的博客-CSDN博客

<类和对象(下)>:<类与对象(下)>——《C++初阶》_新晓·故知的博客-CSDN博客

1. 回顾构造函数

1.1 构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。
class Date
{
public:
 Date(int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 
private:
 int _year;
 int _month;
 int _day;
};
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值

1.2 初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。 
class Date
{
public:
 Date(int year, int month, int day)
 : _year(year)
 , _month(month)
 , _day(day)
 {}
 
private:
 int _year;
 int _month;
 int _day;
};
【注意】
1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    ●引用成员变量
    ●const成员变量
    ● 自定义类型成员(该类是没有默认构造函数的自定义类型,若有,则不必在初始化列表显式初始化)
class A {
public:
 A(int a)
 :_a(a)
 {}
private:
 int _a;
};
class B {
public:
 B(int a, int ref)
 :_aobj(a)
 ,_ref(ref)
 ,_n(10)
 {}
private:
 A _aobj; // 没有默认构造函数
 int& _ref; // 引用
 const int _n; // const 
};

注:自定义类型成员(该类没有默认构造函数),是没有默认构造的,如果写了对应的构造函数则不必在初始化列表初始化

 

 

初始化列表是显式定义初始化的地方。

自定义类型class A 中的_a如果有默认构造函数,就不用在class Date中的初始化列表进行初始化,而是调用自己的默认构造函数进行初始化。

但是如果class A 中的对象没有默认构造函数,就需要在class Date 中的初始化列表进行初始化。

(如果class A 中的对象_a有默认构造函数,假若_a在调用自己的默认构造函数进行初始化后的值为10,但是又想给在class Date函数体中,给_a赋值为100,那可以这样做:使用class A 中同类型的对象(例如bb)给_a赋值,但这样做,相当于进行了两层机制(先使用了_a的默认构造函数初始化_a为10,又在class Date的函数体中使用class A 中同类型的对象(例如bb)给_a赋值为100),这么做绕过了在class Date 使用初始化列表进行初始化,费时费力,不常用)

 

因此:建议尽量使用初始化列表进行初始化。

因为普通成员变量即可以在函数体初始化,也能在初始化列表初始化,而那3种类型只能在初始化列表初始化,所以尽量一般都在初始化列表初始化。

 

 

 

 最终形式:

#include<iostream>
using namespace std;

//class Date
//{
//public:
//	//1.函数体内初始化
//	自己实现构造函数
//	//Date(int year=2020, int month=01, int day=01)
//	//{
//	//	_year = year;
//	//	_month = month;
//	//	_day = day;
//	//}
//	2.初始化列表
//	//Date(int year = 2020, int month = 01, int day = 01)
//	//	:_year(year)
//	//	, _month(month)
//	//	, _day(day)
//	//{}
//	//3.1+2混用初始化
//	Date(int year = 2020, int month = 01, int day = 01)
//		:_year(year)
//		, _month(month)
//		
//	{
//		_day=(day);
//	}
//private:
//	int _year;      
//	int _month;
//	int _day;
//};
//
//int main()
//{
//	Date d1(2022,07,26);
//	Date d2;
//	return 0;
//}

//class A
//{
//public:
//	//写了带参的构造函数
//	A(int a)
//		:_a(a)
//	{}
//	//默认构造函数
//	//1.全缺省构造函数
//	//A(int a=10)
//	//	:_a(a)
//	//{}
//	//2.无参构造函数
//	/*A()
//	{}*/
//	//3.不写
//private:
//	int _a;
//};
//为了区分Date类中的_a,将class A中的成员变量换成x
class A
{
public:
	//写了带参的构造函数
	A(int x)
		:_x(x)
	{}
	//默认构造函数
	//1.全缺省构造函数
	/*A(int x=10)
		:_x(x)
	{}*/
	//2.无参构造函数
	/*A()
	{}*/
	//3.不写
private:
	int _x;
};

class Date
{
public:
	//1.有些变量可以在函数体内初始化,也可以在初始化列表初始化
	//2.有些变量必须在定义时初始化(初始化列表初始化)
	//比如:const int _n;
	//int& _val;
	
	//初始化列表可以认为就是对象成员变量(年月日)定义的地方
	//Date(int year = 2020, int month = 01, int day = 01)
	//	:_year(year)
	//	, _month(month)

	//{
	//	_day = (day);
	//}

	//Date(int year, int month, int day, int n, int val)
	//{
	//	_year = year;  //普通成员变量可以这样初始化
	//	_n = n;        //不可以这样初始化
	//	_val = val;    //不可以这样初始化
	//} 
		
	初始化列表可以认为就是对象成员变量(年月日)定义的地方
	初始化列表是显式定义地方,
	//Date(int year, int month, int day, int n, int val, int a)
	//	:_n(n)       //只能这样采用初始化列表初始化
	//	, _val(val)  //只能这样采用初始化列表初始化
	//	, _a(a)
	//{
	//	_year = year;  //普通成员变量可以这样初始化
	// //	_n = n;        //不可以这样初始化
    //	_val = val;    //不可以这样初始化
	// //	A bb(a);
	// //	_a = bb;
	//}

	//初始化列表可以认为就是对象成员变量(年月日)定义的地方
	//初始化列表是显式定义地方,
	Date(int year, int month, int day, int n, int val, int a)
		:_n(n)       //只能这样采用初始化列表初始化
		, _val(val)  //只能这样采用初始化列表初始化
		, _a(a)      //只能这样采用初始化列表初始化
		,_year(year)   //普通成员变量可以这样初始化
		,_month(month) //普通成员变量可以这样初始化
		,_day(day)     //普通成员变量可以这样初始化
	    {}
private:        
	//声明,未开辟空间
	
	//可以在定义时初始化,也可以在定义时不初始化,后面再赋值修改
	int _year;
	int _month;
	int _day;

	//比如:只能在定义时初始化
	const int _n;
	int& _val;
	//自定义类型:
	A _a;
};

int main()
{
	//对象的整体定义
	Date d1(2022,07,26,0,0,100);
	//Date d2;
	return 0;
}

3.初始化列表:

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。 

class Time
{
public:
 Time(int hour = 0)
 :_hour(hour)
 {
 cout << "Time()" << endl;
 }
private:
 int _hour;
};
class Date
{
public:
 Date(int day)
 {}
private:
 int _day;
 Time _t;
};
int main()
{
 Date d(1);
}

 

 

 

#include<iostream>
#include<cassert>
using namespace std;

class Stack
{
public:
	//Stack的构造函数
	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
{
public:
	/*Myqueue()
	{}*/
	Myqueue(int size=20)
		:_size(size)
	{}
private:
	Stack _st1;
	Stack _st2;
	size_t _size=10;  //补丁,是缺省值,给初始化列表
};

int  main()
{
	Myqueue mq;
	return 0;
}

 

 4.初始化列表中的初始化顺序:

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


 

class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() 
{
	A aa(1);
	aa.Print();
}

A. 输出1 1

B . 程序崩溃
C . 编译不通过
D . 输出 1 随机值

1.3 explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用
class Date
{
public:
	Date(int year)
		:_year(year)
	{}

	explicit Date(int year)
		:_year(year)
	{}

private:
	int _year;
	int _month;
	int _day;
};
void TestDate()
{
	Date d1(2018);

	// 用一个整形变量给日期类型对象赋值
	// 实际编译器背后会用2019构造一个无名对象,最后用无名对象给d1对象进行赋值
	d1 = 2019;
}
 
上述代码可读性不是很好,用explicit修饰构造函数,将会禁止单参构造函数的隐式转换

 

class Date
{
public:
	Date(int year)
		:_year(year)
	{
		cout << "Date(int year)" << endl; 
	}
    //拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
private:
	int _year;
	//int _month;
	//int _day;
};
int main()
{
	Date d1(2022);//构造
	Date d2 = d1; //拷贝构造
	Date d3(d1);  //拷贝构造
	Date d4 = 2020; //本质是隐式类型的转换,本质是构造+拷贝构造,优化后:合二为一,直接为构造
	return 0;
}

 

 

 

#include<iostream>
#include<cstring>
using namespace std;

class String
{
public:
	//String支持一个构造函数
	String(const char* str)
	{}

};
void Func(const String& s)
{

}
int main()
{
	String s1("hello");
	String s2 = "world";
	Func(s1);
	Func("cpp");
	return 0;
}

2. static成员

2.1 概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰 成员函数,称之为静态成员函数静态的成员变量一定要在类外进行初始化
面试题:实现一个类,计算中程序中创建出了多少个类对象。
class A {
public:
 A() {++_scount;}
 A(const A& t) {++_scount;}
 static int GetACount() { return _scount;}
private:
 static int _scount;
};
int A::_count = 0;
void TestA()
{
 cout<<A::GetACount()<<endl;
 A a1, a2;
 A a3(a1);
 cout<<A::GetACount()<<endl; }

//实现一个类,计算中程序中创建出了多少个类对象。
#include<iostream>
//using namespace std;
using std::cin;
using std::cout;
using std::endl;
//全局变量count
int count1 = 0;
int count2 = 0;
class A
{
public:
	//构造函数
	A()
	{
		++count1;
	}
	//拷贝构造函数
	A(const A& aa)
	{
		++count2;
	}
};

A Func(A a)
{
	A copy(a);
	return copy;
}
int main()
{
	A a1;
	A a2 = Func(a1);

	cout << "调用构造函数创建的类对象个数:"<<count1 << endl;
	cout << "调用拷贝构造函数创建的类对象个数:" << count2 << endl;
	cout<< "创建类型变量的总数:" << count1 + count2 << endl;
	return 0;
}

 

 

//实现一个类,计算中程序中创建出了多少个类对象。
#include<iostream>
//using namespace std;
using std::cin;
using std::cout;
using std::endl;

全局变量count
//int count1 = 0;
//int count2 = 0;
//项目中,全局变量如果在.h文件,展开后很容易造成链接冲突
//C++中使用静态成员变量
class A
{
public:
	//构造函数
	A()
	{
		++_count1;
	}
	//拷贝构造函数
	A(const A& aa)
	{
		++_count2;
	}
//设成公有成员访问
//private:
	//静态成员变量属于整个类,在静态区,属于类的所有对象
	//在类外定义
	//不是在初始化列表构造函数去初始化
	static int _count1;  //声明
	static int _count2;  //声明
};
//静态成员变量在类外定义
int A::_count1 = 0;
int A::_count2 = 0;

A Func(A a)
{
	A copy(a);
	return copy;
}
int main()
{
	A a1;
	A a2 = Func(a1);

	//cout << "调用构造函数创建的类对象个数:"<< _count1 << endl;
	//cout << "调用拷贝构造函数创建的类对象个数:" <<_count2 << endl;
	//cout<< "创建类型变量的总数:" << _count1 + _count2 << endl;

	//cout << sizeof(a1) << endl;//计算大小可知static成员变量不会被计算
	//公有访问
	cout << a1._count1 << endl;
	cout << a2._count1 << endl;

	cout << a1._count2 << endl;
	cout << a2._count2 << endl;

	cout <<  A::_count1 << endl;
    cout <<  A::_count2 << endl;
	
	return 0;
}

 

//实现一个类,计算中程序中创建出了多少个类对象。
#include<iostream>
//using namespace std;
using std::cin;
using std::cout;
using std::endl;

全局变量count
//int count1 = 0;
//int count2 = 0;
//项目中,全局变量如果在.h文件,展开后很容易造成链接冲突
//C++中使用静态成员变量
class A
{
public:
	//构造函数
	A()
	{
		++_count1;
	}
	//拷贝构造函数
	A(const A& aa)
	{
		++_count2;
	}

	//成员函数也可以是静态static,静态成员函数无this指针
	// 设置一个函数,通过函数访问私有成员
	static int GetCount1()
	{
		//_size = 10;     //不能访问非静态成员变量,因为无this指针
		return _count1;
	}
	static int GetCount2()
	{
		return _count2;
	}
private:
	//静态成员变量属于整个类,在静态区,属于类的所有对象
	//在类外定义
	//不是在初始化列表构造函数去初始化
	static int _count1;  //声明
	static int _count2;  //声明
};
//静态成员变量在类外定义
int A::_count1 = 0;
int A::_count2 = 0;

A Func(A a)
{
	A copy(a);
	return copy;
}
int main()
{
	A a1;
	A a2 = Func(a1);

	//cout << "调用构造函数创建的类对象个数:"<< _count1 << endl;
	//cout << "调用拷贝构造函数创建的类对象个数:" <<_count2 << endl;
	//cout<< "创建类型变量的总数:" << _count1 + _count2 << endl;

	//cout << sizeof(a1) << endl;//计算大小可知static成员变量不会被计算
	//公有访问
	/*cout << a1._count1 << endl;
	cout << a2._count1 << endl;

	cout << a1._count1 << endl;
	cout << a2._count2 << endl;

	cout << A::_count1 << endl;
	cout << A::_count2 << endl;*/
	//私有成员访问:
	cout << a1.GetCount1 << endl;
	cout << a2.GetCount1 << endl;

	cout << a1.GetCount2 << endl;
	cout << a2.GetCount2 << endl;

	//静态成员函数无this指针,指定类域,
	cout <<  A::GetCount1 << endl;
	cout <<  A::GetCount2 << endl;
	return 0;
}

 

//实现一个类,计算中程序中创建出了多少个类对象。
#include<iostream>
//using namespace std;
using std::cin;
using std::cout;
using std::endl;

全局变量count
//int count1 = 0;
//int count2 = 0;
//项目中,全局变量如果在.h文件,展开后很容易造成链接冲突
//C++中使用静态成员变量
class A
{
public:
	//构造函数
	A()
	{
		++_count1;
	}
	//拷贝构造函数
	A(const A& aa)
	{
		++_count2;
	}

	//成员函数也可以是静态static,静态成员函数无this指针
	//  设置一个函数,通过函数访问私有成员
	static int GetCount1()
	{
		//_size = 10;     //不能访问非静态成员变量,因为无this指针
		return _count1;
	}
	static int GetCount2()
	{
		return _count2;
	}
private:
	//静态成员变量属于整个类,在静态区,属于类的所有对象
	//在类外定义
	//不是在初始化列表构造函数去初始化
	static int _count1;  //声明
	static int _count2;  //声明

	//int _size;
};
//静态成员变量在类外定义
int A::_count1 = 0;
int A::_count2 = 0;

A Func(A a)
{
	A copy(a);
	return copy;
}
int main()
{
	A a1;
	A a2 = Func(a1);

	//cout << "调用构造函数创建的类对象个数:"<< _count1 << endl;
	//cout << "调用拷贝构造函数创建的类对象个数:" <<_count2 << endl;
	//cout<< "创建类型变量的总数:" << _count1 + _count2 << endl;

	//cout << sizeof(a1) << endl;//计算大小可知static成员变量不会被计算
	//公有访问
	/*cout << a1._count1 << endl;
	cout << a2._count1 << endl;

	cout << a1._count1 << endl;
	cout << a2._count2 << endl;

	cout << A::_count1 << endl;
	cout << A::_count2 << endl;*/
		私有成员访问:
	cout << a1.GetCount1() << endl;
	cout << a2.GetCount1() << endl;

	cout << a1.GetCount2() << endl;
	cout << a2.GetCount2() << endl;

	//静态成员函数无this指针,指定类域,
	cout <<  A::GetCount1() << endl;
	cout <<  A::GetCount2() << endl;

	return 0;
}

 

2.2 特性

1. 静态成员为所有类对象所共享,不属于某个具体的实例
2. 静态成员变量必须在类外定义,定义时不添加static关键字
3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
【问题】
1. 静态成员函数可以调用非静态成员函数吗?
2. 非静态成员函数可以调用类的静态成员函数吗?

3.C++11 的成员初始化新玩法。

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变 量缺省值。 
 
#include<iostream>
using std::cin;
using std::cout;
using std::endl;

class B
{
public:
	B(int b = 0)
		:_b(b)
	{}
	int _b;
};
class A 
{
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b << endl;
		cout << p << endl;
	}
private:
	// 非静态成员变量,可以在成员声明时给缺省值。
	int a = 10;
	B b = 20;
	int* p = (int*)malloc(4);
	static int n;
};
int A::n = 10;
int main()
{
	A a;
	a.Print();
	return 0;
}

 

 

 

代码接口在《牛客》后台:这里只是思想:

通过访问n次构造函数,没有使用循环,递归等实现累加。

//没有使用循环,递归等实现累加,通过访问n次构造函数
class Sum
{
public:
    Sum()
    {
        _ret += _i;
        ++_i;
    }
    /*int GetRet()
    {
        return _ret;
    }*/
    static int GetRet()  //静态的可以用对象,但一般使用指定类域去访问,
                         //不需要使用对象class Sum 的对象来访问
    {
        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) 
    {
        // write code here
        Sum a[n];               //调用n次构造函数,这种需要支持变长数组
        //Sum* p = new Sum[n];  //不支持变长数组就使用这种,两种都是调用n次构造函数

        //或者通过对象去调用class的成员函数   return a[0].GetRet();
        return Sum::GetRet();
    }
};

4. 友元

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

4.1 友元函数

问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的 输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。  
 

//1.流输出重载<<在类内定义为成员函数
//问题:因为cout的输出流对象和隐含的this指针在抢占第一个参数的位置。
//this指针默认是第一个参数也就是左操作数了。
//但是实际使用中cout需要是第一个形参对象,才能正常使用。
//就会这样不直观的访问 d1 << cout;
class Date
{
public:
	//构造函数
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	//流输出重载在类内定义为成员函数
	ostream& operator<<(ostream& _cout)
	{
		_cout << _year << "-" << _month << "-" << _day<<endl;
		return _cout;
	}

private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2019,12,19);
	d1 << cout;
	return 0;
}

//2.流输出重载,在类内使用友元函数声明,在类外定义为普通函数
class Date
{
	//友元函数,但不建议多用,破坏了封装
	friend ostream& operator<<(ostream& out, const Date& d);
public:
	//流输出重载,在类内定义为成员函数,有一个隐含形参this指针
	//ostream& operator<<(ostream& out);
	//构造函数
	Date(int year, int month, int day)
		: _year(year)
		, _month(month)
		, _day(day)
	{}

private:
	int _year;
	int _month;
	int _day;
};

//流输出重载——在类内使用友元函数声明
//如果写为成员函数,就是ostream& operator<<(ostream& out);
//双操作数,虽然只写了一个形参,但因为有一个隐含形参this指针,但会存在争抢问题
//一般情况下,双操作运算符的函数,第一个参数是左操作数,第二个参数是右操作数
//写成成员函数就会d << cout;d传给this指针,占了第一个参数
//不写成类内的成员函数,就涉及到私有成员访问
//1.通过定义函数GetYear等 2.使用友元函数
//在类外使用对象访问私有,cout << d;但破坏了封装
ostream& operator<<(ostream& out,const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
	return out;
}


int main()
{
	Date d(2022,07,28);
	//d << cout;
	cout << d;
	return 0;
}

友元函数可以直接访问类的私有成员,它是定义在类外部普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加 friend 关键字。
//流输入与流输出运算符重载——使用友元函数在类内声明
class Date
{
	friend ostream& operator<<(ostream& _cout, const Date& d);
	friend istream& operator>>(istream& _cin, Date& d);
public:
	构造函数1:全缺省
	//Date(int year=2022, int month=07, int day=28)
	//	: _year(year)
	//	, _month(month)
	//	, _day(day)
	//{}
	//构造函数2:无参
	//Date()
	///*	: _year()
	//	, _month()
	//	, _day()*/
	//{}
	//构造函数3:不写,默认生成
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;   //对象d无参,调用默认构造函数,有3类
	cin >> d;
	cout << d << endl;
	return 0;
}

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

 4.2 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元关系是单向的,不具有交换性。
比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
友元关系不能传递
如果B是A的友元,C是B的友元,则不能说明C时A的友元。

//友元类
class Date; // 前置声明,编译器向上找
class Time
{
	//友元类
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	//默认构造函数(全缺省)
	Time(int hour=00, int minute=00, int second=00)
		: _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;
	}

	void PrintDateAndTime()
	{
		cout << _year << "-" << _month << "-" << _day << " "
			<< _t._hour << ":" << _t._minute << ":" << _t._second << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	
	Date d1(2022,07,28);
	//Time t1(19, 00, 00);
	d1.SetTimeOfDate(19,00,00);
	d1.PrintDateAndTime();

	return 0;

}

  

//友元类
class Date; // 前置声明,编译器向上找
class Time
{
	//声明友元函数
	friend void Func(const Date& d, const Time& t);
	//友元类
	friend class Date; // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
public:
	//默认构造函数(全缺省)
	Time(int hour = 00, int minute = 00, int second = 00)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}


private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
	//声明友元函数
	friend void Func(const Date& d, const Time& t);
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;
	}

	void PrintDateAndTime()
	{
		cout << _year << "-" << _month << "-" << _day << " "
			<< _t._hour << ":" << _t._minute << ":" << _t._second << endl;
	}

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
//这个函数访问Date和Time类的私有成员
//将这个函数生命为友元函数
void Func(const Date& d, const Time& t)
{
	cout << d._year << endl;
	cout << t._hour << endl;
}
int main()
{

	Date d1(2022, 07, 28);
	Time t1(19, 00, 00);
	d1.SetTimeOfDate(19, 00, 00);
	d1.PrintDateAndTime();
	Func(d1,t1);

	return 0;
}

5.内部类

5.1概念及特性

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。注意此时这个内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去调用内部类。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类。注意友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1. 内部类可以定义在外部类的public、protected、private都是可以的。
2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3. sizeof(外部类)=外部类,和内部类没有任何关系
//内部类
class A 
{
private:
	static int k;
	int h;
	
public:
	//内部类
	class B   //B天生就是A的友元,B可以单向访问A
	{
	public:
		void func(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}
	private:
		int _b;
	};
};
int A::k = 1;
int main()
{
	A a1;
	cout << sizeof(A) << endl;  //计算的是类的对象定义大小,只会计算int h
	
	A::B b;
	b.func(A());

	return 0;
}

//内部类
class Solution
{
private: 
    //将Sum改造成私有的内部类,只能自己使用,成为为Solution的友元
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }
    };
   
public:
    int Sum_Solution(int n)
    {
        Sum a[n];            
        return _ret;
    }
private:
        static int _i;        
        static int _ret;
};

//静态成员变量在类外定义
int Solution::_i = 1;
int Solution::_ret = 0;

5.2题目:计算日期到天数转换:

//HJ73 计算日期到天数转换
//根据输入的日期,计算是这一年的第几天。
//保证年份为4位数且日期合法。
//进阶:时间复杂度:O(n)\O(n) ,空间复杂度:O(1)\O(1)
#include <iostream>
using namespace std;

int main() 
{

	int DaysArray_N[13] = { 0,31,59,90,120,151,181,212,243,273,304,334,365 };

	int year, month, day;
	cin >> year >> month >> day;

	int n = DaysArray_N[month - 1] + day;
	if (month > 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
	{
		n += 1;
	}
	cout << n << endl;

	return 0;
}

 

5.3 关于构造+拷贝构造—>构造的优化问题:

//关于构造+拷贝构造—>构造的优化问题:
class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
};

//普通函数传值传参
void Func1(Widget u)
{

}
int main()
{
	Widget x;
	Func1(x);
	return 0;
}

 传值传参、传值返回,返回会生成一个临时变量:

//传值传参、传值返回,返回会生成一个临时变量
class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
};

//传值传参、传值返回,会生成一个临时变量
Widget Func1(Widget u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}

int main()
{
	Widget x;
	Func1(x);
	return 0;
}

 引用传参、传值返回,返回会生成一个临时变量:

//引用传参、传值返回,返回会生成一个临时变量
class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
};
//引用传参、传值返回,返回会生成一个临时变量
Widget Func1(Widget& u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}

int main()
{
	Widget x;
	Func1(x);
	return 0;
}

 引用传参、引用返回,返回不会生成一个临时变量:

//引用传参、引用返回,返回不会生成一个临时变量
class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
};
//引用传参、引用返回,返回不会生成一个临时变量
Widget& Func1(Widget& u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //引用返回,不调用拷贝构造
}
int main()
{
	Widget x;
	Func1(x);
	return 0;
}

 5.4类的匿名对象:

//传值传参、传值返回,会生成一个临时变量
class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	//析构函数
	~Widget()
	{
		cout << "~Widget()" << endl;
	}
};
//传值传参、传值返回,会生成一个临时变量
Widget Func1(Widget u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}
int main()
{
	Widget x;
	Widget();  //匿名对象,生命周期只在这一行
	Func1(x);
	return 0;
}

 5.4.1匿名对象使用场景:

class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	析构函数
	//~Widget()
	//{
	//	cout << "~Widget()" << endl;
	//}
};
//传值传参、传值返回,会生成一个临时变量
Widget Func1(Widget u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}
int main()
{
	/*Widget x;
	Func1(x);*/
	//Widget();  //匿名对象,生命周期只在这一行
	//使用匿名对象传参
	Func1(Widget());
	return 0;
}

 

 调试发现,这个s的地址与w-->d-->tmp的tmp地址相同,又ret= Func1(x);  //调用赋值重载构造函数,赋值完成后,调用析构函数释放资源。

class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	//赋值重载
	Widget& operator=(const Widget& s)
	{
		cout << "Widget& operator=(const Widget& s)" << endl;
		return *this;
	}
	//析构函数
	~Widget()
	{
		cout << "~Widget()" << endl;
	}
};
//传值传参、传值返回,会生成一个临时变量
Widget Func1(Widget u)
{
	Widget v(u);    //拷贝构造
	Widget  w = v;  //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}
int main()
{
	/*Widget x;
	Func1(x);*/
	//Widget();  //匿名对象,生命周期只在这一行
	//使用匿名对象传参
	//Func1(Widget());
	Widget x;               
	//Widget ret=Func1(x);  //调用拷贝构造函数
	Widget ret;      //调用构造函数
	ret= Func1(x);  //调用赋值重载构造函数
	return 0;
}

编译器构造+拷贝构造优化问题总结:

在传值传参和传值返回的过程中,只有是在一个表达式连续步骤中,构造、拷贝构造,会被编译器优化合并。

对于传值情况:一般情况下,一个对象拷贝构造出一个临时对象,一个临时对象再去拷贝构造另一个对象,一般编译器会进行优化。中间对象tmp优化掉。(要求:是在一个表达式执行的连续步骤中)

题目:

 这道题设计编译器的优化,而不同的编译器有不同的优化,因此将它作为理解与学习,不要太过计较答案,实际上如果没有优化,就会调用9次拷贝构造函数。

class  Widget
{
public:
	//构造函数
	Widget()
	{
		cout << "Widget()" << endl;
	}
	//拷贝构造函数
	Widget(const Widget& d)
	{
		cout << "Widget(const Widget& d)" << endl;
	}
	//赋值重载
	Widget& operator=(const Widget& s)
	{
		cout << "Widget& operator=(const Widget& s)" << endl;
		return *this;
	}
	//析构函数
	~Widget()
	{
		cout << "~Widget()" << endl;
	}
};
//传值传参、传值返回,会生成一个临时变量
Widget f(Widget u)
{
	Widget v(u);    //拷贝构造
	Widget w = v;   //拷贝构造
	return w;       //出了作用域,传值返回,调用拷贝构造
}
int main()
{
	Widget x;
	Widget y = f(f(x));
	/*Widget y;
	y = f(f(x));*/

	return 0;
}

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

数据范围: 0 < n \le 2000<n≤200
进阶: 空间复杂度 O(1)O(1) ,时间复杂度 O(n)O(n)

//题目要求:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)
//思想:使用class、内部类、友元函数、数组等n次调用构造函数实现
//内部类
class Solution
{
private: 
    //将Sum改造成私有的内部类,只能自己使用,成为为Solution的友元
    class Sum
    {
    public:
        Sum()
        {
            _ret += _i;
            ++_i;
        }
    };
   
public:
    int Sum_Solution(int n)
    {
        //Sum a[n];            1.变长数组
        Sum* p = new Sum[n]; //2.两种都是为了调用n次构造函数
        cout <<"1+2+3+...+"<<n<<"=:" << _ret << endl;
        return _ret;
    }
    
private:
        //静态成员变量在类内声明
        static int _i;         //使用静态修饰局部变量
                               //使得每次访问保证是同一个,不会每次都初始化
                               //出了作用域不会被销毁,相当于改变了局部变量的生命周期
        static int _ret;
};

//静态成员变量在类外定义
int Solution::_i = 1;
int Solution::_ret = 0;

int main()
{
    //1.传统调用
    /*Solution st1;
    st1.Sum_Solution(100);*/
    
    //2.匿名对象调用
    //Solution().Sum_Solution(5);

    Solution().Sum_Solution(105);
    //总结:
    //1和2是调用方式,但不能连续计算,因为_ret被返回,如果计算每次都要更新n的值!
    return 0;
}

6. 再次理解封装

C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。
C++通过类,将一个对象的属性与行为结合在一起,使其更符合人们对于一件事物的认知,将属于该对象的所有东西打包在一起;通过访问限定符选择性的将其部分功能开放出来与其他对象进行交互,而对于对象内部的一些实现细节,外部用户不需要知道,知道了有些情况下也没用,反而增加了使用或者维护的难度,让整个事情复杂化。
下面举个例子来让大家更好的理解封装性带来的好处,比如:乘火车出行

7.再次理解面向对象

可以看出面向对象其实是在模拟抽象映射现实世界。

 

后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!
                                                                     ——By 作者:新晓·故知

  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 26
    评论
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值