C++面试宝典(0)

cppfunction.h

//C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。 \
C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。 \
C++ 是由 Bjarne Stroustrup 于 1979 年在新泽西州美利山贝尔实验室开始设计开发的。C++ 进一步扩充和完善了 C 语言,最初命名为带类的C,后来在 1983 年更名为 C++。 \
C++ 是 C 的一个超集,事实上,任何合法的 C 程序都是合法的 C++ 程序。

//C++ 完全支持面向对象的程序设计,包括面向对象开发的四大特性:封装/抽象/继承/多态

//封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。 \
数据封装是一种把数据和操作数据的函数捆绑在一起的机制, \
数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。

//防止文件的重复包含和编译
#ifndef _CPPFUNCTION
#define _CPPFUNCTION

#include <stdio.h>
#include<iostream>
#include<string>
using namespace std;

extern double vals[];

//数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。\
数据抽象是一种依赖于接口和实现分离的编程(设计)技术。\
让我们举一个现实生活中的真实例子,比如一台电视机,您可以打开和关闭、切换频道、调整音量、添加外部组件(如喇叭、录像机、DVD 播放器),但是您不知道它的内部实现细节,也就是说,您并不知道它是如何通过缆线接收信号,如何转换信号,并最终显示在屏幕上。\
因此,我们可以说电视把它的内部实现和外部接口分离开了,您无需知道它的内部实现原理,直接通过它的外部接口(比如电源按钮、遥控器、声量控制器)就可以操控电视。\
就 C++ 编程而言,C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,也就是说,外界实际上并不清楚类的内部实现。

//1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。\
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。\
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数, 则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。

//创建一个抽象类:至少包含一个纯虚函数的类。抽象类不能创建对象,但可以创建指向抽象类的指针,多态机制将根据基类指针选择相应的虚函数。
//抽象类不允许创建对象。
//抽象类元素不能作为函数的返回值,不能作为函数的参数。
//抽象类的指针和引用可以做函数参数,可以做返回值,可以指向子类对象。可使用指向抽象类的指针支持运行时多态性。
//抽象类的子类必须去实现纯虚函数,如果不实现,该类还是抽象类。

//基类,雇员类(经理、技术人员、销售人员、销售经理等)抽象类
//在面向对象设计中,被定义为包含所有实体共性的class类型,被称为“基类”
class Employee//(Employee是类名)
{
public://访问修饰符:public/private/protect,默认为private \
	公有成员在程序中类的外部是可访问的。您可以不使用任何成员函数来设置和获取公有变量的值,\
	私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的。 \
	protected(受保护)成员变量或函数与私有成员十分相似,但有一点不同,protected(受保护)成员在派生类(即子类)中是可访问的。

	//*构造函数为什么一般不定义为虚函数
	//1)因为创建一个对象时需要确定对象的类型,而虚函数是在运行时确定其类型的。而在构造一个对象时,由于对象还未创建成功,编译器无法知道对象的实际类型,是类本身还是类的派生类等等
	//2)虚函数的调用需要虚函数表指针,而该指针存放在对象的内存空间中;若构造函数声明为虚函数,那么由于对象还未创建,还没有内存空间,更没有虚函数表地址用来调用虚函数即构造函数了
	Employee()//默认(无参)构造函数:每次创建类的新对象时执行。
		: mAge(0)//年龄。初始化列表
	{
		//构造函数内部赋值
		inum++;
		mID = inum;				//员工号:可以做到当每增加一个员工,它的 ID 会自动 加一。
		mName = "NoDefined";	//员工名字
		mSalary = 0.0;			//员工薪资
	}
	//以下几种情况时必须使用初始化列表 
	//1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
	//2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面 
	//3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化
	//4. 如果类存在继承关系,派生类必须在其初始化列表中调用基类的构造函数
	//初始化列表成员变量的顺序
	//成员是按照他们在类中出现的顺序进行初始化的,而不是按照他们在初始化列表出现的顺序初始化的
	//class foo
	//{
	//public:
	//	int i; int j;
	//	foo(int x) :i(x), j(i) {}; // ok, 先初始化i,后初始化j
	//};
	//class foo
	//{
	//public:
	//	int i; int j;
	//	foo(int x) :j(x), i(j) {} // i值未定义
	//};
	//这里i的值是未定义的因为虽然j在初始化列表里面出现在i前面,但是i先于j定义,所以先初始化i,而i由j初始化,此时j尚未初始化,所以导致i的值未定义。一个好的习惯是,按照成员定义的顺序进行初始化。
	Employee(int age) :mAge(age)//有形参的构造函数
	{
		inum++;
		mID = inum;				//可以做到当每增加一个员工,它的 ID 会自动 加一。
		mName = "NoDefined";
		mSalary = 0.0;
	}

	//多态:\
	在面向对象中多态性可表述为:不同的对象接收同一个消息,产生不同的行为\
	在C++程序设计中,多态性可表述为:不同功能的函数拥有相同的函数名,可以用一个函数名调用不同内容的函数\
	从系统实现看,多态性分为:静态多态性和动态多态性\
	静态多态性是指程序在编译时系统就知道所调用的函数,其通过函数重载实现\
	动态多态性是指程序在运行过程中才能动态地确定操作所针对的对象,其通过虚函数实现\
	总结:多态性是一个接口,多种方法

	//对外的接口函数:不同的计算工资方法
	virtual void GetPay() = 0;  //多态。纯虚函数:在基类中为其派生类保留一个函数的名字,以便派生类根据需要对它进行定义。作为接口而存在 纯虚函数不具备函数的功能,一般不能直接被调用。

	//对外的接口函数:显示不同人的信息
	virtual void Show()         //多态。虚函数:在基类中冠以关键字 virtual 的成员函数。 它提供了一种接口界面。允许在派生类中对基类的虚函数重新定义。
	{
		cout << "基类 姓名:" << mName << " ID:" << mID << " 工资:" << mSalary << endl;
	}

	//析构函数:删除所创建的对象时执行。 \
	析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
	//基类的析构函数一般写成虚继承,可以做到把基类和子类对象一起析构. \
	基类的析构函数为非虚函数时,删除一个基类指针指向的派生类实例时,只清理了派生类从基类继承过来的资源,而派生类自己独有的资源却没有被清理,这显然不是我们希望的。
	virtual ~Employee() { 
		cout << "基类 析构" << endl;
	}

	//如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。\
	静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。\
	静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。\
	静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。
	//静态成员函数与普通成员函数的区别:\
	静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。\
	普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
	static int getNum()//静态成员函数,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
	{
		return inum;
	}

protected:
	int mID;					//员工号,栈区
	const int mAge;				//员工年龄,const对象或引用类型只能初始化,不能对他们赋值
	string mName;				//员工名字,栈区
	double mSalary;				//员工薪水,栈区
private://对外隐藏的数据
	static int inum;            //员工索引,静态存储区。员工ID的基数,每进一个员工,在基数上加一,就为它的ID。 \
								当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。\
								静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。\
								我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,见cpp文件
};

//面向对象程序设计中最重要的一个概念是继承。继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。\
当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。\
继承代表了 is a 关系。例如,哺乳动物是动物,狗是哺乳动物,因此,狗是动物,等等。
//一个派生类继承了所有的基类方法,但下列情况除外:\
基类的构造函数、析构函数和拷贝构造函数。\
基类的重载运算符。\
基类的友元函数。
//当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。继承类型是通过上面讲解的访问修饰符 access - specifier 来指定的。\
我们几乎不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:\
公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。\
保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。\
私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。\



//经理
class Manager : virtual public Employee//子类继承虚基类,为了多重继承.虚继承:主要解决内存重复的问题,同时避免访问冲突。
{
public:
	Manager() {}
	Manager(string name)
	{
		mName = name;
		base = 8000;
	}

	//继承。重写
	virtual void GetPay()
	{
		mSalary = base;
	}
	void setBase(double num)
	{
		base = num;
	}
protected:
	double base;	//员工基础工资
private:
	double ticheng; //提成
};



//当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。默认为 private。几乎不使用 protected 或 private 继承,通常使用 public 继承。
//公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
//保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
//私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
class Technician : public Employee//技术人员类(公有继承)
{
	char* mTouxian;
public:
	Technician();
	Technician(string name, const char* strTouxian, int hour = 0) :mHoliday(0)
	{
		mName = name;
		mHour = hour;
		mTouxian = new char[strlen(strTouxian) + 1];
		strcpy_s(mTouxian, strlen(strTouxian)+1,strTouxian);
	}
	//拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量。
	//拷贝构造函数的作用:作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。
	//为什么拷贝构造函数的参数类型一般都是const ? \
	因为复制构造函数是用引用方式传递复制对象,引用方式传递的是地址,因此在构造函数内对该引用的修改会影响源对象。 \
	而你在用对象a1构造a2时,自然不希望复制构造函数会改变a1的内容,因此要防止复制构造函数内部修改该引用,所以用const声明。
	//在拷贝构造函数中为什么可以访问引用对象的私有变量? \
	所谓访问权限(如public,private),是对“类”来说的,不是对“对象”来说的,private访问权限是其它类不能访问, \
	而非这个类的不同对象不能访问。其实这也非常合理,类是自己设计的,当然自己也就知道类的内部结构,所以没有必要对自己也进行类的“封装”。  
	//没有自定义拷贝构造函数,C++编译器自动会产生一个默认的拷贝构造函数。默认拷贝构造函数是浅拷贝。
	Technician(const Technician& t1) :mHoliday(0) // 自定义拷贝构造函数。在类成员中有指针变量,一定要重载拷贝构造函数,并追加指针成员的拷贝处理。这样才会实现深拷贝。
	{
		cout << "Copy constructor for Technician" << endl;
		this->mID = t1.mID + 1;
		this->mName = t1.mName;
		this->mTouxian = new char[strlen(t1.mTouxian) + 1];
		strcpy_s(mTouxian, strlen(t1.mTouxian)+1, t1.mTouxian);
		//this指针
		//一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候, \
		编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的, \
		它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。
		//this指针是类的一个自动生成、自动隐藏的私有成员,它存在于类的非静态成员函数中,指向被调用函数所在的对象。 \
		全局仅有一个this指针,当一个对象被创建时,this指针就存放指向对象数据的首地址。
		//this只能在成员函数中使用。全局函数,静态函数都不能使用this。原因是this指的是当前对象,而静态成员函数是属于类的,要用类来访问
		//友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
	}
	//必须定义拷贝构造函数的情况: \
		只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。
	//拷贝构造函数的调用时机 \
	1. 当函数的参数为类的对象时
	/*void g_fun(Technician c)
	{
		cout << "g_func" << endl;
	}*/
	//2. 函数的返回值是类的对象
	/*Technician g_fun()
	{
		Technician temp(0);
		return temp;
	}*/
	//3. 对象需要通过另外一个对象进行初始化 
	/*Technician A(100);
	Technician B = A;*/

	//浅拷贝与深拷贝
	//浅拷贝,浅拷贝只是复制了对象的引用地址,两个对象指向同一个内存地址,所以修改其中任意的值,另一个值都会随之变化,这就是浅拷贝
	//深拷贝,深拷贝是将对象及值复制过来,两个对象修改其中任意的值另一个值不会改变,这就是深拷贝
	//当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。 \
	当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址, \
	当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。 \
	深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

	//运算符重载,就是对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型。 
	//1、运算重载符不可以改变语法结构。
	//2、运算重载符不可以改变操作数的个数。 
	//3、运算重载符不可以改变优先级。 
	//4、运算重载符不可以改变结合性。
	//一般来说,如果类需要复制构造函数,也就需要赋值操作符(=)。 
	Technician& operator = (const Technician& t1) // 重载 = 运算符,用于 Technician 对象赋值,实现深拷贝
	{
		cout << "assignment for Technician" << endl;
		if (this != &t1)
		{
			delete[]mTouxian;//赋值函数中要先删除对象自己本身的指针空间
			mTouxian = new char[strlen(t1.mTouxian) + 1];
			strcpy_s(mTouxian, strlen(t1.mTouxian)+1, t1.mTouxian);

			this->mID = (t1.mID + 1);
			this->mName = t1.mName;
			this->mSalary = t1.mSalary;
		}
		return *this;
	}
	Technician operator+(const Technician& t1)// 重载 + 运算符,用于把两个 Technician 对象相加
	{
		this->mSalary = this->mSalary + t1.mSalary;
		return *this;
	}
	//Technician t(q) 和 Technician t = q 调用的拷贝构造函数。因为t是一个未定义的对象。 \
	当t定义过了。 调用的则是 = 号操作符 \
	Technician t;     //先定义了(默认构造函数初始化基本类型成员) \
	t = q;    //调用 = 操作符  

	//重写(覆盖):是指派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。 \
	只有函数体不同(花括号内),派生类调用时会调用派生类的重写函数,不会调用被重写函数。重写的基类中被重写的函数必须有virtual修饰。 \
	被重写的函数不能是static的。
	virtual void GetPay()//继承。重写
	{
		mSalary = mHour * 100;
	}
	//重载:是指同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
	virtual void GetPay(int hour)
	{
		mSalary += hour * 1.5 * 100;
	}
	//重载和重写的区别:
	//(1)范围区别:重写和被重写的函数在不同的类中,重载和被重载的函数在同一类中。
	//(2)参数区别:重写与被重写的函数参数列表一定相同,重载和被重载的函数参数列表一定不同。
	//(3)virtual的区别:重写的基类必须要有virtual修饰,重载函数和被重载函数可以被virtual修饰,也可以没有。
	void setName(string name)
	{
		this->mName = name;
	}
	~Technician() {
		printf("~Technician");
		delete[]mTouxian;
		mTouxian = NULL;
	}
protected:
	int mHour;
	const int mHoliday;//员工假期
	//类的静态成员有两种:静态成员变量和静态成员函数。静态成员变量就是在定义时前面加了 static 关键字的成员变量;静态成员函数就是在声明时前面加了 static 关键字的成员函数。
	static int nTotalNumber;  //静态成员变量
	static void PrintTotal()//静态成员函数
	{
		cout << "Technician::PrintTotal()" << endl;
		//静态函数没有this指针
		//静态函数只能访问类中的静态成员变量
		//静态函数不需要类的实例就可以调用
	}

	void yinyong()//引用说明
	{
		// 声明简单的变量
		int    i;
		double d;
		// 声明引用变量。
		//引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。
		//引用非对象,它不占内存空间,也没有实际地址,其只是为一个已经存在的对象所起的另外一个名字。
		int&    r = i;
		double& s = d;

		int a;
		const int &ra = a;//什么时候需要使用“常引用”。如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
		//ra = 1; //错误
		//a = 1; //正确
		int ival = 42;
		int *ipoint;//定义一个指向int型对象(或说int型变量)的指针
		ipoint = &ival;//取变量ival的地址赋给指针ip,让指针ip指向变量ival,其中,&为取地址符
		int ival1 = *ipoint;//对指针ip解引用,得到指针所指向的对象,即变量ival,并把此对象的值赋给新定义的int型变量ival1,其中'*'为解引用符

		//类型别名
		//类型别名是一个名字,它是某种类型的同义词。使用类型别名的主要作用在于,让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
		typedef double wages;//定义数据类型double的别名为wages,wages是double的同义词
		typedef wages base, *p;//base是double的同义词,p是double*的同义词
		wages hourly, weekly;//等价于double hourly,weekly;

		using SI = int;//SI是int类型的同义词
		SI week;//等价于int week;
	}

	//将“引用”作为函数参数有哪些特点
	//(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
	//(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
	//(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
	void swap(int& x, int& y)//引用作为函数参数
	{
		int temp;
		temp = x; /* 保存地址 x 的值 */
		x = y;    /* 把 y 赋值给 x */
		y = temp; /* 把 x 赋值给 y  */

		return;
	}

	double& setValues(int i)
	{
		//引用作为返回值,必须遵守以下规则:
		//(1)不能返回局部变量的引用。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了"无所指"的引用,程序会进入未知状态。
		//(2)不能返回函数内部new分配的内存的引用。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用), \
		又面临其它尴尬局面。例如,被函数返回的引用只是作为一 个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak。 
		//(3)可以返回类成员的引用,但最好是const。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关, \
		因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常 量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。 
		return vals[i];   // 返回第 i 个元素的引用。
		//要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的,但是,可以返回一个对静态变量的引用。
		//int q;
		//! return q; // 在编译时发生错误
		//static int x;
		//return x;     // 安全,x 在函数作用域外依然是有效的
	}
	//引用与指针有什么区别
	//1) 引用必须被初始化,指针不必。
	//2) 引用初始化以后不能被改变,指针可以改变所指的对象。
	//3) 不存在指向空值的引用,但是存在指向空值的指针。

};

//销售人员
class SalesMan : virtual public Employee//子类继承虚基类,用于多重继承
{
public:
	SalesMan() {}
	SalesMan(string name, double count = 0)
	{
		sum += count;
		mName = name;
		mCount = count;
	}
	//类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。 \
	尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。 \
	友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。 \
	如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend

	//因为友元函数没有this指针,则参数要有三种情况: \
	要访问非static成员时,需要对象做参数;\
	要访问static成员或全局变量时,则不需要对象做参数;\
	如果做参数的对象是全局对象,则不需要对象做参数.可以直接调用友元函数,不需要通过对象或指针
	friend void printTicheng(SalesMan s);
	friend class FriendSalesMan;
	virtual void GetPay()
	{
		mSalary = mCount * 4 / 100;
	}
protected:
	double mCount;
	static double sum;//累加每个销售的业绩,然后传递给派生类。    (经典)
private:
	double mTicheng;
};

class FriendSalesMan
{
public:
	void Print(SalesMan &s)
	{
		// FriendSalesMan是SalesMan的友元类,它可以直接访问SalesMan类的任何成员
		cout << "Name:"<<s.mName << " mTicheng : " << s.mTicheng << endl;
	}
};

//销售经理(多继承,要点是: 虚继承(解决二义性))。多继承即一个子类可以有多个父类,它继承了多个父类的特性。
class SalesManager : public SalesMan, public Manager        //此处用了虚继承,解决了二义性,也是重点。
{
public:
	SalesManager()
	{

	}
	SalesManager(string name)
	{
		base = 5000;
		mName = name;
	}

	virtual void GetPay()
	{
		mSalary = base + sum * 5 / 100;
		cout << "销售总额" << sum << endl;
	}
};

//刚入职的新人
class FreshMan:public Employee
{
private:
	FreshMan() {};
	~FreshMan() {};//将构造函数和析构函数私有化的话,则此类无法被继承。
public:
	//类只能在堆上创建,原理:在类的调用时,会调用构造函数,将构造函数变成私有化时就无法调用了。\
	然后提供一个接口,用于类内部创建堆对象。如果析构函数也是私有,需要提供一个堆对象销毁函数。
	//类的使用\
	FreshMan OnlyHeap;//build error \
	FreshMan * pOnlyHeap = FreshMan::CreateObj();//OK

	static FreshMan* CreateObj()//用于构造函数
	{
		FreshMan * pInstance = new FreshMan();
		return pInstance;
	}
	static void DestroyObj(FreshMan* &pInstance)//用于析构的函数
	{
		if (NULL != pInstance)
		{
			delete pInstance;
			pInstance = NULL;
		}
	}

	virtual void GetPay()
	{
		cout << "销售总额:0" << endl;
	}
private:
	//类只能在栈上创建,原理:在堆上创建对象需要调用new,所以将operator new 设置为私有即可。
	//void * operator new(std::size_t count) {};
	//void * operator new[](std::size_t count) {};
	//void operator delete [](void *ptr) {};
	//void operator delete(void *ptr) {};
	//类的使用:\
	FreshMan* pArrStack = new FreshMan[2];//build error \
	FreshMan* pStack = new FreshMan;//build error \
	FreshMan stack; \
	stack.GetPay();//OK
};

#endif
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值