Unreal Engine 游戏开发学习笔记 20181107


本篇博文实为在”系统”学习 Unreal Engine 4 游戏开发后整理的学习笔记。笔记按照每天的学习内容进行整理记录备忘。

本文为 Week3 Class3! 其中 表示上午的学习内容, 表示下午的学习内容。从本周开始连续三周学习 C++ 基础。同时强烈推荐像我一样的初学者看下”菜鸟教程”的 C++ 部分,本周的笔记绝大部分整理的内容均来自菜鸟笔记,只是内容按照上课老师所讲并在笔记中穿插了一些老师上课所讲实例,感谢菜鸟笔记网站管理员的无私分享。

今日内容摘要(知识点梳理):

1. 昨日作业的补充
2. 函数参数的传递方式
3. 类的有元函数
4. 静态属性 静态函数 静态类
5. 类的多态
6. 指针在类和函数中的应用
7. new 动态创建指针变量



上午

函数参数传递方式

如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。

当调用函数时,有两种向函数传递参数的方式:

传值调用

向函数传递参数的传值调用方法,把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
默认情况下,C++ 使用传值调用方法来传递参数。一般来说,这意味着函数内的代码不会改变用于调用函数的实际参数。函数 swap() 定义如下:

1
2
3
4
5
6
7
8
9
10
11
// 函数定义
void swap(int x, int y)
{
int temp;

temp = x; /* 保存 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */

return;
}

现在,让我们通过传递实际参数来调用函数 swap():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

// 函数声明
void swap(int x, int y);

int main ()
{
// 局部变量声明
int a = 100;
int b = 200;

cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;

// 调用函数来交换值
swap(a, b);

cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 100
交换后,b 的值: 200

上面的实例表明了,虽然在函数内改变了 a 和 b 的值,但是实际上 a 和 b 的值没有发生变化。

引用调用

向函数传递参数的引用调用方法,把引用的地址复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
按引用传递值,参数引用被传递给函数,就像传递其他值给函数一样。因此相应地,在下面的函数 swap() 中,您需要声明函数参数为引用类型,该函数用于交换参数所指向的两个整数变量的值。

1
2
3
4
5
6
7
8
9
10
// 函数定义
void swap(int &x, int &y)
{
int temp;
temp = x; /* 保存地址 x 的值 */
x = y; /* 把 y 赋值给 x */
y = temp; /* 把 x 赋值给 y */

return;
}

现在,让我们通过引用传值来调用函数 swap():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

// 函数声明
void swap(int &x, int &y);

int main ()
{
// 局部变量声明
int a = 100;
int b = 200;

cout << "交换前,a 的值:" << a << endl;
cout << "交换前,b 的值:" << b << endl;

/* 调用函数来交换值 */
swap(a, b);

cout << "交换后,a 的值:" << a << endl;
cout << "交换后,b 的值:" << b << endl;

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
交换前,a 的值: 100
交换前,b 的值: 200
交换后,a 的值: 200
交换后,b 的值: 100

课上案例

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
// 按值传递参数方式【两大种四小种】:
// 1.传递过来的参数是拷贝的(需要额外消耗内存)
int Plus(int a, int b) {
// 形式参数为局部变量 离开函数失效
a++;
return a + b;
}

// 2. 按引用传递参数:直接传递原值 不会传递复制参数
int Subtract(int& aaa, int& bbb) {
aaa++;
return aaa + bbb;
}

// 3.按const引用传递参数,直接传递原值,不允许修改传递过来的参数 (最佳)
int Multiply(const int& a, const int b) {
return a * b;
}

// 4. 按指针方式传递(避免拷贝) // 把原值的地址传递过来 可以直接更改原值
int Divide(int* a, int* b) {
if (*b != 0) {
*a++;
return *a / *b;
}
else {
return -1;
}
}

C++ 友元函数

类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend,如下所示:

1
2
3
4
5
6
7
8
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};

声明类 ClassTwo 的所有成员函数作为类 ClassOne 的友元,需要在类 ClassOne 的定义中放置如下声明:

1
friend class ClassTwo;

请看下面的程序:

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
#include <iostream>

using namespace std;

class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};

// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}

// 程序的主函数
int main( )
{
Box box;

// 使用成员函数设置宽度
box.setWidth(10.0);

// 使用友元函数输出宽度
printWidth( box );

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
Width of box : 10

友元函数的使用

因为友元函数没有this指针,则参数要有三种情况:

  • 要访问非static成员时,需要对象做参数;
  • 要访问static成员或全局变量时,则不需要对象做参数;
  • 如果做参数的对象是全局对象,则不需要对象做参数,可以直接调用友元函数,不需要通过对象或指针

实例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class INTEGER
{
friend void Print(const INTEGER& obj);//声明友元函数
};

void Print(const INTEGER& obj)
{
//函数体
}

void main()
{
INTEGER obj;
Print(obj);//直接调用
}

静态成员 静态成员函数 静态类

C++ 类的静态成员

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。
下面的实例有助于更好地理解静态成员数据的概念:

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
#include <iostream>

using namespace std;

class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};

// 初始化类 Box 的静态成员
int Box::objectCount = 0;

int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2

// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
Constructor called.
Constructor called.
Total objects: 2

静态成员函数

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

1
2
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 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
#include <iostream>

using namespace std;

class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};

// 初始化类 Box 的静态成员
int Box::objectCount = 0;

int main(void)
{

// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;

Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2

// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2

静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存

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
#include <iostream>

using namespace std;

class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};

// 初始化类 Box 的静态成员 其实是定义并初始化的过程
int Box::objectCount = 0;
//也可这样 定义却不初始化
//int Box::objectCount;
int main(void)
{
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2

// 输出对象的总数
cout << "Total objects: " << Box::objectCount << endl;

return 0;
}

输出结果:

1
2
3
Constructor called.
Constructor called.
Total objects: 2

可以使用静态成员变量清楚了解构造与析构函数的调用情况。

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
#include <iostream>
using namespace std;

class Cpoint{
public:
static int value;
static int num;
Cpoint(int x,int y){
xp=x;yp=y;
value++;
cout << "调用构造:" << value << endl;
}

~Cpoint(){num++; cout << "调用析构:" << num << endl;}

private:
int xp,yp;
};

int Cpoint::value=0;
int Cpoint::num=0;
class CRect{
public:
CRect(int x1,int x2):mpt1(x1,x2),mpt2(x1,x2) {cout << "调用构造\n";}
~CRect(){cout << "调用析构\n";}
private:
Cpoint mpt1,mpt2;
};

int main()
{
CRect p(10,20);
cout << "Hello, world!" << endl;
return 0;
}

运行结果:

1
2
3
4
5
6
7
调用构造:1
调用构造:2
调用构造
Hello, world!
调用析构
调用析构:1
调用析构:2

静态类

当一个类完全脱离实例数据和对象时可以使用静态类,静态类可以有自己的成员变量和成员函数,但都必须是静态的。对静态成员变量只能是程序运行开始时初始化,不能在类内初始化。静态类没有基类。

静态类和类成员用于创建无需创建类的实例就能够访问的数据和函数。静态类成员可用于分离独立于任何对象标识的数据和行为:无论对象发生什么更改,这些数据和函数都不会随之变化。当类中没有依赖对象标识的数据或行为时,就可以使用静态类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 静态类 不能实例化 一般情况只在静态类中声明常量和静态成员
// 当做工具来使用,一般都是公开的
// 静态类内部成员都是静态的
static class Tool {
public:
// 功能性作用和实例化的object没有关系
static void LoadResources() {

}
static void LoadTexture() {

}
static void ArrayToString() {

}
};

类的多态 指针在类及函数中应用 动态创建内存

C++ 类的多态

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

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
class Animation {
protected:
EAnimType AnimType;
Player* Owner = NULL;
float AnimDuration = 0.f;
bool IsLoop = false;
float Speed = 0.f;
EAnimMode AnimMode;
public:
EAnimMode GetAnimMode() {
return AnimMode;
}
public:
Animation(Player* Owner,EAnimType AT)
{
this->Owner = Owner;
this->AnimType = AT; // this表示构建函数构建的当前对象

}
public:
/*
// 第一中方式播放动画
void Play() {
if (AnimMode == Third) {
cout << "在第三人称模型上播放动画" << endl << endl;
}
else {
cout << "在第一人称模型上播放动画" << endl << endl;
}
}
*/
void Play() {
cout << "Play Animation" << endl << endl;
}
};

// 在子类调用父类并传递参数到父类设置人称动画
// 父类声明变量 子类赋值
// 父类有Play函数 子类也有自己的Play函数
class ThirdAnimation :public Animation
{
public:
ThirdAnimation(Player* Owner, EAnimType AT):Animation(Owner,AT) // 把子类的两个参数,传递给父类的这两个参数
{
AnimMode = Third;
}
public:
void Play() {
cout << "Play Third Animation" << endl << endl;
}
};
class FirstAnimation :public Animation
{
public:
FirstAnimation(Player* Owner, EAnimType AT) :Animation(Owner, AT) // 子类调用父类的构造函数 并传递参数
{
AnimMode = First;
}
public:
void Play() {
cout << "Play First Animation" << endl << endl;
}
};

指针在类和函数中的应用

指针在函数中应用

1
2
int aa = 3;                                    // main函数的局部变量 在栈内分配内存
int* Paa = &aa; // main函数的指针变量 在栈内分配内存

指针在类中的应用

1
2
3
4
5
6
7
8
9
10
11
12
// 子类对象调用自己子类函数 父类对象调用父类的函数  
Player TestPlayer;
Player* PTestPlayer = &TestPlayer;
// ThirdAnimation ThirdAnim(&TestPlayer, Walk); // 取地址符调用玩家地址(指针)
ThirdAnimation ThirdAnim(PTestPlayer, Walk);
cout << ThirdAnim.GetAnimMode() << endl << endl;
ThirdAnim.Play();

// 调用父类Play函数
Player ParentPlayer; // 创建Animation类玩家
Animation AnimPar(&ParentPlayer, Idle); // 创建Animation类实例
AnimPar.Play(); // 调用Animation类函数

C++ 动态内存

了解动态内存在 C++ 中是如何工作的是成为一名合格的 C++ 程序员必不可少的。C++ 程序中的内存分为两个部分:

  • 栈:在函数内部声明的所有变量都将占用栈内存。
  • 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
    在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。
    如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。

new 和 delete 运算符

下面是使用 new 运算符来为任意的数据类型动态分配内存的通用语法:

1
new data-type;

在这里,data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。让我们先来看下内置的数据类型。例如,我们可以定义一个指向 double 类型的指针,然后请求内存,该内存在执行时被分配。我们可以按照下面的语句使用 new 运算符来完成这点:

1
2
double* pvalue  = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存

如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:

1
2
3
4
5
6
7
double* pvalue  = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);

}

在任何时候,当您觉得某个已经动态分配内存的变量不再需要使用时,您可以使用 delete 操作符释放它所占用的内存,如下所示:

1
delete pvalue;        // 释放 pvalue 所指向的内存

下面的实例中使用了上面的概念,演示了如何使用 new 和 delete 运算符:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>
using namespace std;

int main ()
{
double* pvalue = NULL; // 初始化为 null 的指针
pvalue = new double; // 为变量请求内存

*pvalue = 29494.99; // 在分配的地址存储值
cout << "Value of pvalue : " << *pvalue << endl;

delete pvalue; // 释放内存

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

1
Value of pvalue : 29495

常用各种数据类型动态内存的创建及初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int aa = 3;                                    // main函数的局部变量 在栈内分配内存
int* Paa = &aa; // main函数的指针变量 在栈内分配内存
int* Pb = new int; // new 是一种运算符 在堆内开辟空间 并返回地址


int Arrary[5] = { 1,2,3,4,5 }; // 原始方式创建数组

//指针创建函数一定要记得释放内存
int* Pc = new int(10); //
delete Pc; // 占用的内存自由了 其他的代码可以用了
Pc = 0; // 将内存指向Pc的连接打断,0为空指针不指向任何地址

int* PArray = new int[5]{1,2,3,4,5}; // new的方式创建数组
delete[] PArray;
PArray = 0;

string* Name = new string("LiMing"); //
delete Name;
Name = 0;

double* Power = new double(1.0); //
delete Power;
Power = 0;
delete 与 delete[] 区别
针对简单类型

针对简单类型 使用 new 分配后的不管是数组还是非数组形式内存空间用两种方式均可 如:

1
2
3
int *a = new int[10];   
delete a;
delete [] a;

此种情况中的释放效果相同,原因在于:分配简单类型内存时,内存大小已经确定,系统可以记忆并且进行管理,在析构时,系统并不会调用析构函数, 它直接通过指针可以获取实际分配的内存空间,哪怕是一个数组内存空间(在分配过程中 系统会记录分配内存的大小等信息,此信息保存在结构体_CrtMemBlockHeader中, 具体情况可参看VC安装目录下CRT\SRC\DBGDEL.cpp)

针对类Class,两种方式体现出具体差异

针对类Class,两种方式体现出具体差异
当你通过下列方式分配一个类对象数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class A
{
private:
char *m_cBuffer;
int m_nLen;
public:
A(){ m_cBuffer = new char[m_nLen]; }
~A() { delete [] m_cBuffer; }
};
A *a = new A[10];

// 仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放 从而造成内存泄漏
delete a;

// 调用使用类对象的析构函数释放用户自己分配内存空间并且 释放了a指针指向的全部内存空间
delete [] a;

所以总结下就是,如果ptr代表一个用new申请的内存返回的内存空间地址,即所谓的指针,那么:

  • delete ptr – 代表用来释放内存,且只用来释放ptr指向的内存。
    • delete[] rg – 用来释放rg指向的内存,!!还逐一调用数组中每个对象的 destructor!!
      对于像 int/char/long/int*/struct 等等简单数据类型,由于对象没有 destructor,所以用 delete 和 delete [] 是一样的!但是如果是C++ 对象数组就不同了!

数组的动态内存分配

假设我们要为一个字符数组(一个有 20 个字符的字符串)分配内存,我们可以使用上面实例中的语法来为数组动态地分配内存,如下所示:

1
2
char* pvalue  = NULL;   // 初始化为 null 的指针
pvalue = new char[20]; // 为变量请求内存

要删除我们刚才创建的数组,语句如下:

1
delete [] pvalue;        // 删除 pvalue 所指向的数组

下面是 new 操作符的通用语法,可以为多维数组分配内存,如下所示:

1
2
3
4
5
// 动态分配,数组长度为 m
int *array=new int [m];

//释放内存
delete [] array;

对象的动态内存分配

对象与简单的数据类型没有什么不同。例如,请看下面的代码,我们将使用一个对象数组来理清这一概念:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};

int main( )
{
Box* myBoxArray = new Box[4];

delete [] myBoxArray; // 删除数组
return 0;
}

如果要为一个包含四个 Box 对象的数组分配内存,构造函数将被调用 4 次,同样地,当删除这些对象时,析构函数也将被调用相同的次数(4次)。
当上面的代码被编译和执行时,它会产生下列结果:

1
2
3
4
5
6
7
8
调用构造函数!
调用构造函数!
调用构造函数!
调用构造函数!
调用析构函数!
调用析构函数!
调用析构函数!
调用析构函数!

可能想问吧

今天上午讲解的主要是上午的作业辅导及函数基础,并没有很难,暂时没有想问的!


一起练习吧

写两个类要有完整的类成员 两个子类 构成多态 使用静态 创建有元函数



上课代码笔记

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
// Week3_Class3.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

using namespace std;

// 按值传递参数方式【两大种四小种】:
// 1.传递过来的参数是拷贝的(需要额外消耗内存)
int Plus(int a, int b) {
// 形式参数为局部变量 离开函数失效
a++;
return a + b;
}

// 2. 按引用传递参数:直接传递原值 不会传递复制参数
int Subtract(int& aaa, int& bbb) {
aaa++;
return aaa + bbb;
}

// 3.按const引用传递参数,直接传递原值,不允许修改传递过来的参数 (最佳)
int Multiply(const int& a, const int b) {
return a * b;
}

// 4. 按指针方式传递(避免拷贝) // 把原值的地址传递过来 可以直接更改原值
int Divide(int* a, int* b) {
if (*b != 0) {
*a++;
return *a / *b;
}
else {
return -1;
}
}


class Player {
private:
// 类的属性成员可以直接在定义属性的时候初始化,但一般不会那么写
int Lifes = 3;
char* Name;
int* AP;
int* HP;
public:
static int Counter;
public:
// 声明有元函数
friend void GetHP(const Player& SomePlayer);
friend string Compare(const Player& A, const Player& B);
public:
char* GetName() {
return Name;
}
int GetAP() {
if (AP) {
return *AP;
}
else {
return -1;
}
}

int GetLifes() {
return Lifes;
}

void SetAP(int* a) {
// 对空指针解除引用会导致程序崩溃
if (AP) {
*AP = *a;
}
}


public:
Player() {
Name = new char[2];
strcpy_s(Name, 2, "a");
AP = new int;
*AP = 0;
HP = new int;
*HP = 1;
Counter++;
cout << "无参构造函数被调用..." << endl << endl;
}
Player(char* NewName) {
int len = strlen(NewName);
Name = new char[len + 1];
strcpy_s(Name, len + 1, NewName);
Counter++;
cout << "Player有一个采参数的构造函数被调用" << endl << endl;
}
Player(const char* NewName,int* a,int* b) {
int len = strlen(NewName);
Name = new char[len + 1];
strcpy_s(Name, len + 1, NewName);

AP = new int;
*AP = *a;

HP = new int;
*HP = *b;

cout << "Player有三个参数的构造函数被调用..." << endl << endl;
}

// 作用于代码运行完成自动运行,本例为main函数运行完成及调用
~Player() {
// 释放之前必须new
if (Name) {
delete Name;
Name = 0;
}
if (AP) {
delete AP;
AP = 0;
}
if (HP) {
delete HP;
HP = 0;
}
Counter--;
cout << "析构函数被调用..." << endl << endl;
}

private:
void Attack() {

}
void Move() {

}
void Reload() {

}

public:
// 静态函数不属于某个对象,属于类,C++中也可以使用对象调用
// 在没有实例化对象之前就可以调用函数, 不能再静态函数中访问类的非静态成员 非静态函数中可以调用静态函数
//
static string GetRootPath() {
// cout << "Counter" << Counter << endl << endl;
return "/Game/";
}
};

// 静态数据的初始化要早于任何一个对象实例化之前
int Player::Counter = 0;

// 类的有元函数 用的不是很多 破坏类的封闭性

// 把Player当做常量引用参数引用 不能修改 把类的对象当做SomePlayer的参数
void GetHP(const Player& SomePlayer) //
{
SomePlayer.HP;
}


// 下面这个函数有错误
void GetHP(Player SomePlayer,int a)
{
// SomePlayer.HP;
}

string Compare(const Player& A, const Player& B)
{
return A.HP > B.HP ? "A>B" : "B>A";
}



// 静态类 不能实例化 一般情况只在静态类中声明常量和静态成员
// 当做工具来使用,一般都是公开的
static class Tool {
public:
// 功能性作用和实例化的object没有关系
static void LoadResources() {

}
static void LoadTexture() {

}
static void ArrayToString() {

}
};

enum EAnimType {
Idle,
Walk,
Run,
Jump,
Punch
};
enum EAnimMode {
First,
Third
};

class Animation {
protected:
EAnimType AnimType;
Player* Owner = NULL;
float AnimDuration = 0.f;
bool IsLoop = false;
float Speed = 0.f;
EAnimMode AnimMode;
public:
EAnimMode GetAnimMode() {
return AnimMode;
}
public:
Animation(Player* Owner,EAnimType AT)
{
this->Owner = Owner;
this->AnimType = AT; // this表示构建函数构建的当前对象

}
public:
/*
// 第一中方式播放动画
void Play() {
if (AnimMode == Third) {
cout << "在第三人称模型上播放动画" << endl << endl;
}
else {
cout << "在第一人称模型上播放动画" << endl << endl;
}
}
*/
void Play() {
cout << "Play Animation" << endl << endl;
}
};

// 在子类调用父类并传递参数到父类设置人称动画
// 父类声明变量 子类赋值
// 父类有Play函数 子类也有自己的Play函数
class ThirdAnimation :public Animation
{
public:
ThirdAnimation(Player* Owner, EAnimType AT):Animation(Owner,AT) // 把子类的两个参数,传递给父类的这两个参数
{
AnimMode = Third;
}
public:
void Play() {
cout << "Play Third Animation" << endl << endl;
}
};
class FirstAnimation :public Animation
{
public:
FirstAnimation(Player* Owner, EAnimType AT) :Animation(Owner, AT) // 子类调用父类的构造函数 并传递参数
{
AnimMode = First;
}
public:
void Play() {
cout << "Play First Animation" << endl << endl;
}
};





int maisadasdasn()
{
/*
int a = 3, b = 5;
// cout << Plus(a, b) << endl;

int c = 6, d = 9;
// cout << Subtract(c, d) << endl;

int e = 50;
int* Pe = &e; //Pe本质是变量 其实指针 int* 为指针Pe的类型
*Pe++; //*pe表示指针指向的数据 //Pe++, // Pe=PE+1 //Pe+=1


// 数组名就是数组指针,也是数组第一个元素的地址

int IntArray[5] = {1,3,5,7,9};
int* PIntArray = IntArray;
int Sum = 0;
for (int i = 0; i < 5; i++) {
// cout << *(PIntArray+i) << endl << endl; //在指针前加* 表示"解除对指针的引用"
Sum += *(PIntArray + i);
}
cout << "Sum: " << Sum << endl;

const char* Data = "10000"; // const表Data指向常量“10000”
Data = "20000"; // Data 指向Data重新指向“20000”
cout << Data << endl << endl;
const char* const Data1 = "10000"; // 常量指针 不能修改指针 不能修改常量 // 前一个const 表示指向常量 不能修改常量 后一个const 表示常量指针 不能修改指针
cout << Data1 << endl << endl;

string Str = "1000"; //string 变量 "1000" 不能被修改 只能存储另外一个字符串
Str = "2000"; // String 实际上是类 Str是类对象
string* PString = &Str;
cout << *PString << endl;


Player MyPlayer;
// 下面这一行如果 调用有错误
// GetHP(MyPlayer, 5); //直接将Myplayer当做参数传递过来 其实使用的是Player中的复制构造函数
cout << Player::Counter << endl << endl;

int g = 4;
int h = 6;
Player MyPlayer1("hello",&g,&h);
cout << "Name: " << MyPlayer1.GetName() << endl << endl;
cout << "AP: " << MyPlayer1.GetAP() << endl << endl;
int i = 200;
MyPlayer1.SetAP(&i);
cout << "AP: " << MyPlayer1.GetAP() << endl << endl;
cout << "Lifes: " << MyPlayer1.GetLifes() << endl << endl;

// 静态函数直接通过类名访问
// C++ 中也可以通过对象访问静态函数
// 在没有实例化对象之前就可以调用函数, 不能再静态函数中访问类的非静态成员 非静态函数中可以调用静态函数
cout << Player::GetRootPath() << endl << endl;
*/


//多态:不同的子类有不同的表现

// 子类对象调用自己子类函数 父类对象调用父类的函数
Player TestPlayer;
Player* PTestPlayer = &TestPlayer;
// ThirdAnimation ThirdAnim(&TestPlayer, Walk); // 取地址符调用玩家地址(指针)
ThirdAnimation ThirdAnim(PTestPlayer, Walk);
cout << ThirdAnim.GetAnimMode() << endl << endl;
ThirdAnim.Play();

// 调用父类Play函数
Player ParentPlayer; // 创建Animation类玩家
Animation AnimPar(&ParentPlayer, Idle); // 创建Animation类实例
AnimPar.Play(); // 调用Animation类函数

// 创建一个子类对象会先调用父类的构造函数,在调用子类的构造函数 无参子类构造函数会自动调用无参的父类函数(没有参数传递)
// 子类实例创建一个带参数的构造函数头后加上":父类名称(参数1,参数2)" 参数是同样的。

int aa = 3; // main函数的局部变量 在栈内分配内存
int* Paa = &aa; // main函数的指针变量 在栈内分配内存
int* Pb = new int; // new 是一种运算符 在堆内开辟空间 并返回地址


int Arrary[5] = { 1,2,3,4,5 }; // 原始方式创建数组

//指针创建函数一定要记得释放内存
int* Pc = new int(10); //
delete Pc; // 占用的内存自由了 其他的代码可以用了
Pc = 0; // 将内存指向Pc的连接打断,0为空指针不指向任何地址

int* PArray = new int[5]{1,2,3,4,5}; // new的方式创建数组
delete[] PArray;
PArray = 0;

string* Name = new string("LiMing"); //
delete Name;
Name = 0;

double* Power = new double(1.0); //
delete Power;
Power = 0;

return 0;
}




void week3_class3_zhishidian () {
// 1. 函数参数传递的两大类四小类方式
// 2. Static的三种用法 静态数据 静态类 静态函数
// 3. 有元函数(函数处在类外部,在类内部通过friend 进行声明)
// 4. 类的多态
// 5. 使用new创建指针变量及释放
// 下周,字典 列表,算法


}




我的作业提交

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
// Week3_Class3_Homework.cpp: 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include <iostream>
#include <string>

using namespace std;
enum ENationality {
PRC, // 中国
USA, // 美国
GBR, // 英国
CA, // 加拿大
AU // 澳大利亚
};

class ProfessionalAthlete {
protected:
string Name;
int* Age;
int Seniority; // 从业年限
ENationality Nationality;
public:
string GetName() {
return Name;
}
int GetAge() {
return *Age;
}
int GetSeniority() {
return Seniority;
}
int GetNationality() {
return Nationality;
}
void SetAge(int* NewAge) {
if (Age) {
*Age = *NewAge;
}
}
void SetSeniority(int NewSeniority) {
Seniority = NewSeniority;
}
void SetNationality(ENationality NewNationality) {
Nationality = NewNationality;
}
public:
ProfessionalAthlete() {
Age = new int;
*Age = 20;
cout << "无参构造函数被调用..." << endl << endl;
}
ProfessionalAthlete(string NewName, int* NewAge) {
this->Name = NewName;
Age = new int;
*Age = *NewAge;
cout << "有2个参数的构造函数被调用..." << endl << endl;
}
ProfessionalAthlete(string NewName, int* NewAge, int NewSeniority) :Name(NewName), Seniority(NewSeniority)
{
Age = new int;
*Age = *NewAge;
cout << "有3个参数的构造函数被调用..." << endl << endl;
}

ProfessionalAthlete(const ProfessionalAthlete& AProfessionalAthlete) {
Name = AProfessionalAthlete.Name;
Age = new int;
*Age = *AProfessionalAthlete.Age;
Seniority = AProfessionalAthlete.Seniority;
Nationality = AProfessionalAthlete.Nationality;
}

~ProfessionalAthlete() {
if (Age) {
delete Age;
Age = 0;
}
cout << "析构函数被调用..." << endl << endl;
}
public:
void MakeAPreCompetitionSpeech() {
cout << "每一个人都应享有从事体育运动的可能性,而不受任何形式的歧视,并体现相互理解、友谊、团结和公平竞争的奥林匹克精神!" << endl << endl;
}

void GoToHurdleRace(string NewName) {
cout << NewName << "在参加跨栏比赛..." << endl << endl;
}

};


class SportsMan :public ProfessionalAthlete {
public:
SportsMan(string NewName, int* NewAge) :ProfessionalAthlete(NewName, NewAge) {
cout << "对!我就是专业运动员中的很有名的那个男运动员: " << NewName << endl << endl;
}
public:
void MakeAPreCompetitionSpeech() {
cout << "作为一个男性运动员,I Have a Dream!" << endl << endl;
}
};

class SportWoman :public ProfessionalAthlete {
public:
SportWoman(string NewName, int* NewAge) :ProfessionalAthlete(NewName, NewAge) {
cout << "是的!我为我是一名专业的女运动员而无比骄傲,我是: " << NewName << endl << endl;
}
public:
void MakeAPreCompetitionSpeech() {
cout << "作为一个专业女运动员,我同样想说:“I Have a Dream!”" << endl << endl;
}
void GoToTableTennisRace(string NewName, int* NewAge) {
cout << "兵乓球比赛进行中,场上有位" << *NewAge << "岁运动员名叫" << NewName << endl << endl;
}
};

enum ETriathlonType {
Swimming,
Bike,
Run
};

class SportsCompetition {
public:
int Track; // 赛道
int AthleteNumber; // 运动员编号
ProfessionalAthlete Player;
ETriathlonType TriathlonType;
protected:
float Score;
public:
int GetTrack() {
return Track;
}
int GetAthleteNumber() {
return AthleteNumber;
}
void SetTrack(int NewTrack) {
Track = NewTrack;
}
void SetAthleteNumber(int NewAthleteNumber) {
AthleteNumber = NewAthleteNumber;
}
public:
SportsCompetition() {
cout << "无参构造函数被调用..." << endl << endl;
};
SportsCompetition(ProfessionalAthlete NewPlayer, int NewTrack) :Player(NewPlayer), Track(NewTrack)
{
cout << "有2个参数的构造函数被调用..." << endl << endl;

}
SportsCompetition(ProfessionalAthlete NewPlayer, int NewTrack, int NewAthleteNumber, ETriathlonType NewTriathlonType) :Player(NewPlayer),
Track(NewTrack), AthleteNumber(NewAthleteNumber), TriathlonType(NewTriathlonType) {
cout << "有4个参数的构造函数被调用..." << endl << endl;
}
public:
void OverTake(int NewTrack, string Name) {
cout << Name << "完成赶超并在第" << NewTrack << "赛道继续向冠军冲刺!!" << endl << endl;
}
};

class ChinaStadium :public SportsCompetition {
public:
ChinaStadium(ProfessionalAthlete NewPlayer, int NewTrack) :SportsCompetition(NewPlayer, NewTrack) {
TriathlonType = Swimming;
SwimmingCompetition();

}
public:
void SwimmingCompetition() {
cout << "游泳运动员已在" << Track << "赛道就位!" << endl << endl;
cout << "泳池里的水不好喝!" << endl << endl;
}
void OverTake(int NewTrack, string Name) {
cout << "游泳运动员: " << Name << "完成赶超并在第" << NewTrack << "赛道继续向冠军冲刺!!" << endl << endl;
}
};

class USAStadium :public SportsCompetition {
public:
USAStadium(ProfessionalAthlete NewPlayer, int NewTrack) :SportsCompetition(NewPlayer, NewTrack) {
TriathlonType = Run;
RunCompetition();

}
public:
void RunCompetition() {
cout << "马拉松运动员已在" << Track << "赛道就位!" << endl << endl;
cout << "渴死了想游泳了!!" << endl << endl;
}
void OverTake(int NewTrack, string Name) {
cout << "马拉松运动员: " << Name << "完成赶超并在第" << NewTrack << "赛道继续向冠军冲刺!!" << endl << endl;
}
};


int mfdsgfdgain()
{
cout << "==================================================================================================================================" << endl << endl;
cout << "专业运动员父类测试:" << endl << endl;
// 专业运动员赛前父类
int NewAge = 25;
ProfessionalAthlete LiuXiang("刘翔", &NewAge);
cout << "Age: " << LiuXiang.GetAge() << endl << endl;
LiuXiang.MakeAPreCompetitionSpeech();
cout << endl << endl;

cout << "专业运动员赛前子类之男运动员类:" << endl << endl;
int LiNingAge = 32;
SportsMan LiNing("李宁", &LiNingAge);
cout << endl << endl;

cout << "专业运动员赛前子类之女运动员类:" << endl << endl;
int DengYaPingAge = 35;
SportWoman DengYaPing("邓亚萍", &DengYaPingAge);
DengYaPing.GoToTableTennisRace("邓亚萍", &DengYaPingAge);

cout << "==================================================================================================================================" << endl << endl;
cout << "铁人三项(体育比赛)父类测试:" << endl << endl;

SportsCompetition ParentCompetition;
ParentCompetition.OverTake(3, "刘婷");
cout << endl << endl;

cout << "铁人三项(体育比赛)子类中国赛区类测试:" << endl << endl;
int SunYangAge = 27;
ProfessionalAthlete SunYang("孙杨", &SunYangAge);
ChinaStadium FristCompetition(SunYang, 6);
FristCompetition.OverTake(2, SunYang.GetName());
cout << endl << endl;

cout << "铁人三项(体育比赛)子类美国赛区类测试:" << endl << endl;
int RyanHallAge = 50;
ProfessionalAthlete RyanHall("瑞恩·霍尔", &RyanHallAge);
USAStadium SecondCompetition(RyanHall, 12);
SecondCompetition.OverTake(5, RyanHall.GetName());
cout << "==================================================================================================================================" << endl << endl;
return 0;
}


----------本文结束感谢您的阅读----------

本文标题:Unreal Engine 游戏开发学习笔记 20181107

文章作者:XIANVFX

发布时间:2018年11月07日 - 09:11

最后更新:2019年05月21日 - 09:05

原始链接:www.xianvfx.top/UE4/Training/Week3_CPP2/Unreal_Engine_Learning_Notes_W3_3.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

(*❦ω❦) 感谢您的支持! (*❦ω❦)
0%