0. 复习
0.1 继承的基本语法
class 类A
{
};
class 类B: public 类A
{
};
类B如果继承了类A,类B称之为派生类(子类),类A就是基类(父类)。
类B继承了类A,就会拥有类A的所有成员。
0.2 继承中的权限问题
继承的权限有3种
public: 接口继承,父类中的对外接口,在子类种依然是接口
protected: 实现继承,父类中的接口,在子类中,都变成了保护属性,不能通过对象去访问了。可以在子类的类内函数中去使用。
只能在内部使用,就称为内部实现了。
private: 实现继承,父类中的接口,在子类中,都变成了私有属性,不能通过对象去访问了。可以在子类的类内函数中去使用。
只能在内部使用,就称为内部实现了。因为是私有属性,再往下继承的时候,孙子类就不能访问了。
补充复习:C++ 接口(抽象类)
接口描述了类的行为和功能,而不需要完成类的特定实现。
C++ 接口是使用抽象类来实现的,抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的,如下所示:
#include
using namespace std;
// 基类
class Shape
{
public:
// 提供接口框架的纯虚函数
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
// 派生类
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
// 输出对象的面积
cout << "Total Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
// 输出对象的面积
cout << "Total Triangle area: " << Tri.getArea() << endl;
return 0;
}
0.3 重定义问题
父类中和子类中的成员同名了。此时就会发生重定义。在使用子类对象的时候,默认使用的就是子类的成员。
如果要使用父类的成员,可以通过作用域符号 ::
昨天的复习课,提到了作用域,图片来自这里,点击可见
(今天的虚构函数就可以很好解决这一问题,虚构函数是一个关键字,加上以后就可以很好的执行子类,不执行父类)
0.4 构造析构的顺序问题
构造顺序:父类,成员,自己
析构顺序:自己,成员,父类
如果有多个成员:
class CTest:public CA,public CB
{
}
0.5 多继承
#include
class CBase1
{
public:
CBase1(int n) :m_Base1(n)
{
std::cout << "我是1父类构造" << std::endl;
}
~CBase1()
{
std::cout << "我是1父类析构" << std::endl;
}
int m_Base1;
};
class CBase2
{
public:
CBase2(int m) :m_Base2(m)
{
std::cout << "我是2父类构造" << std::endl;
}
~CBase2()
{
std::cout << "我是2父类析构" << std::endl;
}
int m_Base2;
};
class CA :public CBase1, public CBase2
{
public:
CA() : m_A(2), CBase2(6), CBase1(5)
{
std::cout << "我是子类构造" << std::endl;
}
~CA()
{
std::cout << "我是子类析构" << std::endl;
}
public:
int m_A;
};
int main()
{
//子类多继承的时候,和单继承差不多的,拥有所有父类的所有成员
CA obj;
obj.m_Base1 = 10; //从CBase1继承来的
obj.m_Base2 = 20; //从CBase2继承来的
obj.m_A = 30; //这个是它自己的
return 0;
}
1. 虚继承
#include
class Cpeople
{
public:
Cpeople() :m_szName("puck"), m_nGender(1), nAge(2)
{
}
void Run()
{
std::cout << "我在快乐的跑步" << std::endl;
}
public:
char m_szName[8];
int m_nGender;
int nAge;
};
class CSpider//蜘蛛
{
public:
CSpider() :nAge(3)
{
}
void Tusi()
{
std::cout << "Piu" << std::endl;
}
public:
int nAge;
};
class CSuperHero
{
public:
void SaveWorld()
{
std::cout << "拯救世界" << std::endl;
}
int nAge;
};
class CSPiderMan :public Cpeople, public CSpider
{
public:
CSPiderMan():m_nPower(5)
{
}
void BianShen()
{
std::cout << "Piu" << std::endl;
}
public:
int m_nPower;
};
int main()
{
CSPiderMan obj;
obj.Run();
obj.Tusi();
obj.BianShen();
return 0;
}
年龄确实有2份。
我们抽象一个更高的父类,将重复成员放到父类中,能不能解决问题呢??
#include
class CAnimal
{
public:
CAnimal() :m_nAge(9)
{
}
public:
int m_nAge;
};
class Cpeople:public CAnimal
{
public:
Cpeople() :m_szName("puck"), m_nGender(1)
{
}
void Run()
{
std::cout << "我在快乐的跑步" << std::endl;
}
public:
char m_szName[8];
int m_nGender;
//int nAge;
};
class CSpider :public CAnimal
{
public:
CSpider():mNum(2)
{
}
void Tusi()
{
std::cout << "Piu" << std::endl;
}
public:
int mNum;
//int nAge;
};
class CSuperHero
{
public:
void SaveWorld()
{
std::cout << "拯救世界" << std::endl;
}
int nAge;
};
class CSPiderMan :public Cpeople, public CSpider
{
public:
CSPiderMan():m_nPower(5)
{
}
void BianShen()
{
std::cout << "Piu" << std::endl;
}
public:
int m_nPower;
};
int main()
{
CSPiderMan obj;
obj.Run();
obj.Tusi();
obj.BianShen();
return 0;
}
使用一个虚继承的机制,可以解决这个问题:
#include
class CAnimal
{
public:
CAnimal() :m_nAge(9)
{
}
public:
int m_nAge;
};
//在继承的时候,加上一个virtual,就是虚继承了
class Cpeople :virtual public CAnimal
{
public:
Cpeople() :m_szName("puck"), m_nGender(1)
{
}
void Run()
{
std::cout << "我在快乐的跑步" << std::endl;
}
public:
char m_szName[8];
int m_nGender;
//int nAge;
};
class CSpider :virtual public CAnimal
{
public:
CSpider() :mNum(2)
{
}
void Tusi()
{
std::cout << "Piu" << std::endl;
}
public:
int mNum;
//int nAge;
};
class CSuperHero
{
public:
void SaveWorld()
{
std::cout << "拯救世界" << std::endl;
}
int nAge;
};
class CSPiderMan :public Cpeople, public CSpider
{
public:
CSPiderMan() :m_nPower(5)
{
}
void BianShen()
{
std::cout << "Piu" << std::endl;
}
public:
int m_nPower;
};
int main()
{
CSPiderMan obj;
obj.Run();
obj.Tusi();
obj.BianShen();
return 0;
}
总结一下:
1.哪个类的成员可能会重复,哪个类的子类就是虚继承
2.虚继承之后,父类称之为 虚基类
3.虚继承之后,虚基类中的成员,就只有1份了,从而避免了二义性。
来自网上多继承和虚继承的资料补充
多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A-->B-->D 这条路径,另一份来自 A-->C-->D 这条路径。
在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B-->D 这条路径,还是来自 A-->C-->D 这条路径。下面是菱形继承的具体实现:
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: public A{
protected:
int m_b;
};
//直接基类C
class C: public A{
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //命名冲突
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
这段代码实现了上图所示的菱形继承,第 25 行代码试图直接访问成员变量 m_a,结果发生了错误,因为类 B 和类 C 中都有成员变量 m_a(从 A 类继承而来),编译器不知道选用哪一个,所以产生了歧义。
为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:
void seta(int a){ B::m_a = a; }
这样表示使用 B 类的 m_a。当然也可以使用 C 类的: void seta(int a){ C::m_a = a; }
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承,请看下面的例子:
//间接基类A
class A{
protected:
int m_a;
};
//直接基类B
class B: virtual public A{ //虚继承
protected:
int m_b;
};
//直接基类C
class C: virtual public A{ //虚继承
protected:
int m_c;
};
//派生类D
class D: public B, public C{
public:
void seta(int a){ m_a = a; } //正确
void setb(int b){ m_b = b; } //正确
void setc(int c){ m_c = c; } //正确
void setd(int d){ m_d = d; } //正确
private:
int m_d;
};
int main(){
D d;
return 0;
}
这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
现在让我们重新梳理一下本例的继承关系,如下图所示:
察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。
换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
在实际开发中,位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下,使用虚继承的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是虚基类,况且新类的开发者也无法改变已经存在的类体系。
C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。
虚基类成员的可见性
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
以图2中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。
可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,C++ 之后的很多面向对象的编程语言,例如 Java、C#、PHP 等,都不支持多继承。
2. 多态机制
2.1 静态联编和动态联编
先理解 编译时和运行时
编译时:程序在编译的一段时间
运行时:程序运行起来的这么一段时间
有一些东西是在编译时能够确定的:
静态联编:函数的调用关系,在编译时期,就能够确定,叫做静态联编。大部分函数都是静态联编。
动态联编:函数的调用关系,在运行时,才能确定的,叫做动态联编。
2.2 虚函数
虚函数实现了一个什么机制,一句话总结:
通过父类指针指向子类对象,调用虚函数,优先调用子类的虚函数。当子类中没有这个虚函数的时候,调用父类的虚函数。
#include
class CBase
{
public:
CBase()
{
}
//在函数之前加上一个virtual 就是虚函数
virtual void Fun1()
{
std::cout << "我是CBase的Fun1" << std::endl;
}
virtual void Fun2()
{
std::cout << "我是CBase的Fun2" << std::endl;
}
void Fun3()
{
std::cout << "我是CBase的Fun3" << std::endl;
}
};
class CTest1:public CBase
{
public:
virtual void Fun1()
{
std::cout << "我是CTest1的Fun1" << std::endl;
}
void Fun3()
{
std::cout << "我是CBase1的Fun3" << std::endl;
}
};
class CTest2 :public CBase
{
public:
virtual void Fun1()
{
std::cout << "我是CTest2的Fun1" << std::endl;
}
};
int main()
{
//CTest1 obj;
总结:
虚函数的特点,运行到调用函数的位置,才能确定具体调用的是哪一个函数。这种调用关系是在运行时确定的,是动态联编的一种。
也叫做 动态的多态
2.3 虚函数的用处
虚函数是多态的重要语法。
封装性是基础,继承是关键,多态是扩充。
在某些特定情况下,使用虚函数,效果会更好。
比如说,动物园 动物好声音的比赛
#include
class CCat
{
public:
void SingASong()
{
std::cout << "miao miao miao" << std::endl;
}
};
class CDog
{
public:
void SingASong()
{
std::cout << "wang wang wang" << std::endl;
}
};
class CMouse
{
public:
void SingASong()
{
std::cout << "zhi zhi zhi" << std::endl;
}
};
void Battle(
CCat* pCat,int nCatCount,
CDog* pDog, int nDogCount,
CMouse* pMouse,int nMouseCount)
{
//1.开始比赛了
for (int i = 0; i < nCatCount; i++)
{
pCat[i].SingASong();
}
for (int i = 0; i < nDogCount; i++)
{
pDog[i].SingASong();
}
for (int i = 0; i < nMouseCount; i++)
{
pMouse[i].SingASong();
}
}
int main()
{
CCat objCat[3];
CDog objDog[4];
CMouse objMouse[10];
Battle(objCat,3, objDog,4, objMouse,10);
}
这么实现,是可以的。
但是此时如果多了一个动物,那么修改起来,变动很大
假如我们使用了虚函数机制:
#include
class CAnimal
{
public:
virtual void SingASong()
{
}
};
class CCat:public CAnimal
{
public:
void SingASong()
{
std::cout << "miao miao miao" << std::endl;
}
};
class CDog :public CAnimal
{
public:
void SingASong()
{
std::cout << "wang wang wang" << std::endl;
}
};
class CMouse :public CAnimal
{
public:
void SingASong()
{
std::cout << "zhi zhi zhi" << std::endl;
}
};
class CWolf :public CAnimal
{
public:
void SingASong()
{
std::cout << " aowu aowu" << std::endl;
}
};
class CTiger :public CAnimal
{
public:
void SingASong()
{
std::cout << "wa " << std::endl;
}
};
void Battle(CAnimal* arrAnimal[20],int nCount)
{
for (int i = 0; i < nCount; i++)
{
arrAnimal[i]->SingASong();
}
}
int main()
{
CAnimal* arrAnimal[20];
srand(time(NULL));
//动物来报名
for (int i = 0; i < 20; i++)
{
int n = rand() % 5 + 1;
switch (n)
{
case 1:
arrAnimal[i] = new CCat;
break;
case 2:
arrAnimal[i] = new CDog;
break;
case 3:
arrAnimal[i] = new CMouse;
break;
case 4:
arrAnimal[i] = new CWolf;
break;
case 5:
arrAnimal[i] = new CTiger;
break;
default:
break;
}
}
Battle(arrAnimal, 20);
}
2.4 虚析构函数
**构造函数不能是虚函数
析构函数可以是虚函数**
当我们在继承关系中,使用到了虚函数,那么此时,也应该将析构函数设置为虚函数。
因为我们经常会使用父类指针操作子类对象,当释放的时候,会直接delete 父类指针。如果不是虚析构的化,就只会调用父类的析构函数。会造成子类资源无法释放,就泄漏了。
#include
class CAnimal
{
public:
virtual void SingASong()
{
}
virtual ~CAnimal()
{
std::cout << "我是CAnimal的析构函数"<SingASong();
}
}
int main()
{
CAnimal* arrAnimal[20];
srand(time(NULL));
//动物来报名
for (int i = 0; i < 20; i++)
{
int n = rand() % 5 + 1;
switch (n)
{
case 1:
arrAnimal[i] = new CCat;
break;
case 2:
arrAnimal[i] = new CDog;
break;
case 3:
arrAnimal[i] = new CMouse;
break;
case 4:
arrAnimal[i] = new CWolf;
break;
case 5:
arrAnimal[i] = new CTiger;
break;
default:
break;
}
}
Battle(arrAnimal, 20);
for (int i = 0; i < 20; i++)
{
delete arrAnimal[i];
}
}
2.5 纯虚函数和抽象类
父类为了管理子类对象,需要有一个虚函数,但是父类中又没有号的虚函数的实现方式。此时就可以将父类中的这种函数实现为纯虚函数。
包含纯虚函数的类,又叫抽象类。抽象类不能定义对象
抽象类不能定义对象,但是可以定义指针。
#include
class CAnimal
{
public:
virtual void SingASong() = 0;
virtual ~CAnimal(){
std::cout << "我是CAnimal的析构函数" << std::endl;
}
};
class CCat :public CAnimal{
public:
void SingASong()
{
std::cout << "miao miao miao" << std::endl;
}
~CCat()
{
std::cout << "我是CCat的析构函数" << std::endl;
}
};
class CDog :public CAnimal
{
public:
void SingASong()
{
std::cout << "wang wang wang" << std::endl;
}
~CDog()
{
std::cout << "我是CDog的析构函数" << std::endl;
}
};
class CMouse :public CAnimal
{
public:
void SingASong()
{
std::cout << "zhi zhi zhi" << std::endl;
}
~CMouse()
{
std::cout << "我是CMouse的析构函数" << std::endl;
}
};
class CWolf :public CAnimal
{
public:
void SingASong()
{
std::cout << " aowu aowu" << std::endl;
}
~CWolf()
{
std::cout << "我是CWolf的析构函数" << std::endl;
}
};
class CTiger :public CAnimal
{
public:
void SingASong()
{
std::cout << "wa " << std::endl;
}
~CTiger()
{
std::cout << "我是CTiger的析构函数" << std::endl;
}
};
void Battle(CAnimal* arrAnimal[20], int nCount)
{
for (int i = 0; i < nCount; i++)
{
arrAnimal[i]->SingASong();
}
}
int main()
{
CAnimal* arrAnimal[20];
srand(time(NULL));
//动物来报名
for (int i = 0; i < 20; i++)
{
int n = rand() % 5 + 1;
switch (n)
{
case 1:
arrAnimal[i] = new CCat;
break;
case 2:
arrAnimal[i] = new CDog;
break;
case 3:
arrAnimal[i] = new CMouse;
break;
case 4:
arrAnimal[i] = new CWolf;
break;
case 5:
arrAnimal[i] = new CTiger;
break;
default:
break;
}
}
Battle(arrAnimal, 20);
for (int i = 0; i < 20; i++)
{
delete arrAnimal[i];
}
}
3. 模板技术
分为函数模板和类模板
3.1 函数模板的使用
//交换两个数
//void swap(int& a, int& b)
//{
// int nTemp = a;
// a = b;
// b = nTemp;
//}
//void swap(double & a, double& b)
//{
// double nTemp = a;
// a = b;
// b = nTemp;
//}
//void swap(char& a, char& b)
//{
// char nTemp = a;
// a = b;
// b = nTemp;
//}
template
void swap(T& a, T& b)
{
T nTemp = a;
a = b;
b = nTemp;
}
int main()
{
int n = 10;
int m = 20;
double l = 10.5;
double q = 20.6;
char x = 'a';
char y = 'b';
swap(n, m);
swap(l,q);
swap(x, y);
return 0;
}
3.2 函数模板技术的基本原理
当我们看底层实现的时候,swap这个函数,实际上有3个。
我们实现的模板,只是一个模子,编译器会根据这个模板,自己去创建对应的函数。
我们实现的代码,叫做 函数模板。
编译器根据我们的模板创建出来的函数,叫做模板函数。
函数模板是模板,模板函数是函数。
3.3 模板的特化
针对特殊情况,不能使用通用代码的时候,可能需要单独实现代码,这就叫做模板的特化。
注意:特化的代码不写template<>也能运行,但是此时就是函数重载。
特化的模板,不被调用的话,不会生成代码
重载是一定会生成代码
#include
//交换两个数
template
void swap(T* a, T* b)
{
T nTemp = *a;
*a = *b;
*b = nTemp;
}
template <>
void swap(char* a, char* b)
{
int nLenth1 = strlen(a) + 1;
int nLenth2 = strlen(b) + 1;
char* buf1 = new char[nLenth1] {};
char* buf2 = new char[nLenth2] {};
strcpy_s(buf1, nLenth1, a);
strcpy_s(buf2, nLenth2, b);
strcpy_s(b, nLenth1, buf1);
strcpy_s(a, nLenth2, buf2);
delete []buf1;
delete []buf2;
return;
}
int main()
{
int a = 10;
int b = 20;
swap(&a,&b);
char* buf1 = new char[20]{ "hello" };
char* buf2 = new char[20]{ "world"};
swap(buf1, buf2);
return 0;
}
3.4 类模板
基本语法:
template <模板参数>
class 类名
{
}
#include
template
class CData
{
public:
void SetNum(T Num)
{
m_num = Num;
}
T GetNum()
{
return m_num;
}
private:
T m_num;
};
//class CData
//{
//public:
// void SetNum(int Num)
// {
// m_num = Num;
// }
// int GetNum()
// {
// return m_num;
// }
//private:
// int m_num;
//};
int main()
{
CData obj1;
obj1.SetNum(10);
int n = obj1.GetNum();
CData obj2;
obj2.SetNum('a');
char cCh = obj2.GetNum();
return 0;
}
思考:使用函数模板,实现一个通用的排序函数。
本题在我的这篇帖子中有类似(持续更新,已更新至2022年11月17日)C语言经典题集合
#include
template
void sort(T* arr, int nCount)
{
for (int j = 1; j < 9; j++)
{
for (int i = 0; i < nCount - j; i++)
{
if (arr[i] > arr[i + 1])
{
T nTemp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = nTemp;
}
}
}
}
int main()
{
int arr[10] = { 4,2,3,1,5,7,6,9,0 };
sort(arr, 10);
double arr1[10] = { 4.5,2.8,3.4,1.1,5.9,7,6,9,0 };
sort(arr1, 10);
return 0;
}
狼类中有一个成员是攻击力 Attacks value 人类中也有一个攻击力Attacks value。由狼类和人类共同的派生类狼人类中,该如何消除二义性,请用代码实现。
#include
using namespace std;
class animal
{
public:
int attack;
};
class wolf :virtual public animal
{
public:
void wolfmember()
{
cout << "狼人攻击" << endl;
}
};
class people :virtual public animal
{
public:
void peoplemember()
{
cout << "人类攻击" << endl;
}
};
class wolfman :public wolf, public people
{
public:
void wolfmanattack()
{
cout << "wolfman attack is" << attack << endl;
}
};
int main()
{
wolfman keen; //对狼人下定义
keen.attack = 10;
keen.wolfmanattack();
return 0;
}