继承
- 2.1结构体成员权限
- 2.1.1访问权限
- 2.1.2类与结构体
- 2.2类的成员函数
- 2.2.1类内规则
- 2.2.2类成员内联函数inline
- 2.3类的继承
- 2.3.1类的继承与成员函数
- 2.3.2类的多继承
- 2.3.2.1类的多继承:菱形问题提出
- 2.3.3类的虚继承(关键字virtual)
- 2.4友元
- 2.4.1友元类
- 2.4.2友元函数
- 2.5构造函数和析构函数
- 2.6初始化列表
2.1结构体成员权限
2.1.1访问权限
访问权限有三种:
public 公共权限(类内可以访问,类外可以访问)
protected 保护权限(类内可以访问,类外不可以访问)(子类可以访问父类中的保护内容)
private 私有权限(类内可以访问,类外不可以访问)(子类不可以访问父类中的私有内容)
注意:protected 和 private 的设计是为了继承
2.1.2类与结构体
类:
类在游戏开发中用的最多
如果没有在类中显式定义构造函数和析构函数并指定访问权限,则默认情况下它们的权限都是公有的(public)
结构体:
结构体一般用于储存变量或数据
结构体也有构造(初始化)和析构(释放内存等),不写编译器就会默认生成,在游戏中对对象的生命周期的操作
#include<iostream>
using namespace std;
struct Test01
{
public:
int a;
void test01() {}
protected:
int b;
void test02() {}
private:
float c;
void test03() {}
};
class Test02
{
public:
int d;
void test04() {}
protected:
int e;
void test05() {}
private:
float f;
void test06() {}
};
int main()
{
Test01 A;
A.a = 10;
int num1 = A.a;
A.test01();
//A.c;//因为设置了保护权限,所以不可以访问
//A.test03();//因为设置了私有权限,所以不可以访问
Test02 B;
B.d = 20;
int num2 = B.d;
B.test04();
//B.e;//因为设置了保护权限,所以不可以访问
//B.test06();//因为设置了私有权限,所以不可以访问
system("pause");
return 0;
}
2.2类的成员函数
2.2.1类内规则
类内可以写静态函数、成员函数
静态函数不可以调用成员函数,成员函数可以调用静态函数
静态函数内不可以包含类内的成员信息(如果想调用需要使成员信息也变为静态)
全局的调用方式::
#include<iostream>
using namespace std;
int Fun1()
{
return 1;
}
class Test01
{
public:
Test01();
~Test01();
static int a;
static int b;
static int Fun1()//类内可以添加静态函数
{
return a+b; //无法直接调用int a;和int b;需要转换为static形式
}
int Fun2()//类内可以添加成员函数
{
Test01::Fun1();//成员函数可以调用静态函数
return 3;
}
};
int Test01::a = 0;
int Test01::b = 0;
Test01::Test01()
{
int num1 = ::Fun1();//::调用全局范围中的
cout << num1 << endl;
}
Test01::~Test01()
{
}
struct Test02
{
public:
static void Fun1();
};
void Test02::Fun1() {}
int main()
{
Test01 A;//实例化
Test01::Fun1();
A.Fun1();
A.Fun2();
Test02::Fun1();
system("pause");
return 0;
}
2.2.2类成员内联函数inline
为什么inline的效率会高效,因为编译时编译器会把代码副本放在每个调用函数的地方
可以使用在类成员内,也可以使用在全局中,也可以使用在结构体内
将简短的、频繁调用的函数声明为内联函数可以获得较好的效果。但是,对于复杂的函数或者在循环中调用的函数,使用内联可能导致代码膨胀,反而会影响性能如for、switch、递归等。
ue4中的FORCEINLINE也代表着内敛的效果,使用FORCEINLINE使需要包含头文件“CoreMinimal.h”
#include<iostream>
using namespace std;
class Test01
{
public:
//想要获取私有成员,不想私有成员被赋值可以用inline
//比下面注释中的代码高效很多
inline int Geta()const
{
return a;
}
/*int Geta()const
{
return a;
}*/
private:
int a;
};
inline int Getb()
{
return 0;
}
struct Test02
{
public:
inline int Getc()const
{
return c;
}
private:
int c;
};
int main()
{
Test01 A;
A.Geta();
Getb();
Test02 C;
C.Getc();
system("pause");
return 0;
}
2.3类的继承
2.3.1类的继承与成员函数
父类也成为基类,子类也称为派生类
封装SDK(插件)时只需要封装基类,其他人使用时只需要继承即可使用其中的方法,或者去扩展
游戏开发时大部分用的都是公共继承
//public
// public public
// protected protected
// private private
//provected
// public protected
// protected protected
// private private
//private
// public private
// protected private
// private private
2.3.2类的多继承
使用场景:如下列代码,将每一个系统部分封装到各个模块中,需要使用时直接调用即可,非常方便
可以将对象转换成某个模块
不需要直接转换成类,可以直接调接口,三个指针指向不同的继承接口,所以可以直接通过指针
#include<iostream>
using namespace std;
class UObject
{
};
class AActor :public UObject//只管和对象有关的
{
public:
void Start() {}
void End() {}
void Net() {}
};
class IPhysics//只管物理
{
public:
void Simulate() {}
};
class IAttack//攻击接口
{
public:
void AttackTarget(ACharacter* InTarget) {}
};
class ACharacter :public AActor,public IPhysics,public IAttack
{
};
bool IsSimulate(IPhysics* p)//好处是不需要直接转换成类,可以直接调接口
{
if (p)
{
p->Simulate();
}
return true;
}
int main()
{
ACharacter A;//多继承
ACharacter B;
A.Start();
A.End();
A.Simulate();
A.AttackTarget(&B);
//A为多继承的,继承了IPhysics、IAttack、AActor
IPhysics* p = &A;
//因为A继承了IPhysics接口(通过ACharacter类的多继承),
//A对象可以被视为一个IPhysics对象
//将A对象的地址赋值给指针p,就可以通过指针p来访问A对象的IPhysics接口
IAttack* p1 = &A;
AActor* p2 = &A;
IsSimulate(&A);
system("pause");
return 0;
}
2.3.2.1类的多继承:菱形问题提出
A的类型为ACharacter,同时继承了AActor和UHello,而AActor和UHello同时继承了UObject,用A调用UObject时会出现二义性,不知道调用的是AActor的UObject还是UHello的UObject
A.Destroy();调用的是UHello的还是AActor的?
这既是菱形问题,要避免
解决菱形问题需要用到虚函数,在下文会解决
#include<iostream>
using namespace std;
class UObject
{
public:
void Destroy() {}
};
class AActor :public UObject//只管和对象有关的
{
public:
void Start() {}
void End() {}
void Net() {}
};
class UHello :public UObject
{
};
class IPhysics//只管物理
{
public:
void Simulate() {}
};
class ACharacter;//因为class ACharacter在后面,所以先声明,也可以在IAttak内部使用前加class
class IAttack//攻击接口
{
public:
void AttackTarget(class ACharacter* InTarget) {}
};
class ACharacter :public AActor,public IPhysics,public IAttack,public UHello
{
};
bool IsSimulate(IPhysics* p)//好处是不需要直接转换成类,可以直接调接口
{
if (p)
{
p->Simulate();
}
return true;
}
int main()
{
ACharacter A;
IPhysics* p = &A;
IAttack* p1 = &A;
AActor* p2 = &A;
IsSimulate(&A);
//A.Destroy();//会报错,类型不明确
//调用的是UHello的还是AActor的
system("pause");
return 0;
}
2.3.3类的虚继承(关键字virtual)
虚继承使class B和class C继承的public A变成一个共享的类,因此class D只用调用一次A
尽量避免虚继承问题
#include<iostream>
using namespace std;
class A
{
public:
A()
{
printf("A\n");
}
void Hello()
{
printf("Hello\n");
}
};
class B :virtual public A
{
public:
B() :A() {}
};
class C :virtual public A
{
public:
C() :A() {}
};
class D :public B, public C
{
public:
D()//c保错,编译器不知道构造B还是C
{
}
~D()
{
}
};
int main()
{
D d;
d;
//没虚继承前输出结果为A输出了两次
//A
//A
d.Hello();
//正常情况下会报错,在class B和C的继承public A前加上virtual变为虚继承
//输出结果为
//A
//Hello
system("pause");
return 0;
}
2.4友元
2.4.1友元类
使用友元可以访问另一个类内的所有内容
#include<iostream>
using namespace std;
class FTestClass
{
friend class FTest2;//友元
public:
void Hello() {}
private:
void Hello1() {}
void Hello2() {}
void Hello3() {}
protected:
void Hello4() {}
};
class FTest2
{
public:
void Init()
{
Class.Hello();
Class.Hello1();//因为是private权限,正常会报错,使用友元不会报错
Class.Hello4();//因为是protected权限,正常会报错,使用友元不会报错
}
private:
FTestClass Class;
};
int main()
{
FTest2 Test2;
Test2.Init();
system("pause");
return 0;
}
2.4.2友元函数
友元不能被继承
优点:提高了程序的运行效率
缺点:破坏了类的封装以及稳点性
友元关系本身不具备继承性
#include<iostream>
using namespace std;
class FTest1
{
public:
friend void Printf_f(FTest1 &T)
{
T.Hello();
printf("%d\n", T.a);
printf("%d\n", T.b);
}
void Printf_a()
{
printf("%d\n", a);
printf("%d\n", b);
printf("%d\n", c);
}
int c = 100;
static void Printf_b(FTest1& T)
{
T.Hello();
printf("%d\n", T.a);
printf("%d\n", T.b);
}
private:
void Hello()
{
a = 0;//其实是this->a=0;编译器省略了
b = 10;
}
private:
int a;
int b;
};
int main()
{
FTest1 A;
Printf_f(A);
A.Printf_a();
FTest1::Printf_b(A);//静态函数
system("pause");
return 0;
}
2.5构造函数和析构函数
#include<iostream>
using namespace std;
class FTest1
{
public:
};
class FTestA
{
public:
FTestA();
FTestA(int ina, int inb, int inc);
~FTestA();
public:
int a;
int b;
int c;
FTest1* T;
};
FTestA::FTestA()
{
a = 1;
b = 2;
c = 3;
}
FTestA::FTestA(int ina, int inb, int inc)
{
a = ina;
b = inb;
c = inc;
T = new FTest1();//分配内存
}
FTestA::~FTestA()
{
if (T)
{
delete T;
T = nullptr;//将指针类型的变量 T 初始化为空指针
}
}
int main()
{
FTestA A;//会走FTestA::FTestA()
cout << A.a << " " << A.b << " " << A.c << endl;
FTestA B(10,20,30);//会走FTestA(int ina, int inb, int inc);
cout << B.a << " " << B.b << " " << B.c << endl;
system("pause");
return 0;
}
2.6初始化列表
初始化列表的语法是在构造函数的参数列表后使用冒号 “:”,接着是多个初始化器,每个初始化器由成员变量名和对应的初始值用逗号分隔。
有父类时父类排在最前面
一定要按照变量声明的顺序排列
初始化列表的优点:
初始化非静态常量成员变量:对于非静态常量成员变量,它们只能在初始化列表中进行赋值,不能在构造函数中赋值。
初始化引用成员变量:引用成员变量必须在构造函数中进行初始化,并且只能使用初始化列表进行初始化。
初始化基类成员变量:如果派生类包含一个基类,那么初始化列表可以用于调用基类的构造函数,并初始化基类成员变量。
性能优化:使用初始化列表可以避免先创建再赋值的过程,提高效率。
注意:
初始化列表只能在构造函数中使用
初始化顺序固定
初始化列表只能在编译时确定初始值,无法在运行时根据条件或变量的值来决定初始值。
初始化列表中的初始化操作无法直接处理异常或错误。
#include<iostream>
using namespace std;
class FTest1
{
public:
};
class FHello_F{};
class FTestA:public FHello_F
{
public:
FTestA();
FTestA(int ina, int inb, int inc);
~FTestA();
public:
int a;
int b;
int c;
FTest1* T;
};
FTestA::FTestA()
{
a = 1;
b = 2;
c = 3;
}
FTestA::FTestA(int ina, int inb, int inc)
//初始化列表
:FHello_F()//有父类时放在最前面
,a(ina)//一定要按照变量声明的顺序排列
,b(inb)
,c(inc)
{
T = new FTest1();//分配内存
}
FTestA::~FTestA()
{
if (T)
{
delete T;
T = nullptr;
}
}
int main()
{
FTestA A;//会走FTestA::FTestA()
cout << A.a << " " << A.b << " " << A.c << endl;
FTestA B(10,20,30);//会走FTestA(int ina, int inb, int inc);
cout << B.a << " " << B.b << " " << B.c << endl;
system("pause");
return 0;
}