﻿1
00:00:04,350 --> 00:00:09,720
‫所以在这节课上, 我们将讨论C++中的虚方法｡

2
00:00:09,720 --> 00:00:13,170
‫我们以前见过它, 我们用过它, 但我们完全理解它了吗？

3
00:00:13,170 --> 00:00:14,100
‫让我想想｡

4
00:00:14,100 --> 00:00:20,460
‫所以虚方法的思想是它允许我们重写基类中的函数｡

5
00:00:20,460 --> 00:00:29,880
‫我们已经在虚幻中看到了像开始､ 播放和抽搐这样的东西, 但是让我们进一步了解一下为什么它们被标记为虚拟的｡

6
00:00:29,880 --> 00:00:38,340
‫它允许我们改变基类中的功能, 它做了一个叫做动态调度的事情, 我们稍后会讨论这个, 来理解它的真正含义,

7
00:00:38,340 --> 00:00:44,550
‫以及为什么虚方法在C++中不是默认功能,

8
00:00:44,550 --> 00:00:50,820
‫就像在很多其他语言中一样, 比如C Sharp.

9
00:00:50,820 --> 00:00:52,320
‫让我们来看看这个｡

10
00:00:52,440 --> 00:00:59,910
‫我会在CPP. S-H网站上做这个, 这样我们就不会在这里做虚幻了｡

11
00:00:59,910 --> 00:01:06,090
‫我们只是看一下C++的原始功能, 这样你就可以理解这个语言特性以及它是如何工作的｡

12
00:01:06,150 --> 00:01:10,680
‫现在, 我要做的第一件事就是清除main方法｡

13
00:01:10,680 --> 00:01:19,680
‫在我们的用例中, 我们将考虑一个枪支类, 因为这是一个非常常见的游戏用例｡

14
00:01:19,680 --> 00:01:26,760
‫让我们来看看一个叫gun的类, 它的类体是public的,

15
00:01:26,760 --> 00:01:31,860
‫还有一个void的shoot方法.

16
00:01:32,520 --> 00:01:39,310
‫我将在这里声明shoot方法的主体以使事情变得非常简单｡

17
00:01:39,330 --> 00:01:44,550
‫顺便说一句, 如果你以前没见过, 你也可以这么做, 但一般来说,

18
00:01:44,550 --> 00:01:49,770
‫我们希望在一个单独的C++文件中做一些事情, 以保持独立, 使编译时间更快, 等等｡

19
00:01:49,770 --> 00:01:55,290
‫但这里只是一个玩具的例子, 这是好的, 所以我们要做一个STD C｡

20
00:01:55,290 --> 00:02:04,980
‫所以用C++作为输出字符串的东西, 当我们调用这个方法的时候, 我们会发出砰的一声, 然后我们会结束这一行｡

21
00:02:05,570 --> 00:02:07,850
‫而这就是他们对我们的身体射击的方法｡

22
00:02:07,850 --> 00:02:11,720
‫我们可以用花括号和分号来结束这个类｡

23
00:02:11,960 --> 00:02:14,750
‫所以我们来使用这个类｡

24
00:02:14,750 --> 00:02:23,510
‫我将在main中创建一个my gun变量, 并调用my gun dot shoot,

25
00:02:23,510 --> 00:02:25,910
‫让我们看看这里的一切是否正常.

26
00:02:25,940 --> 00:02:28,070
‫按下运行按钮然后砰｡

27
00:02:28,160 --> 00:02:30,530
‫正如我们所期望的那样, 它正在调用方法｡

28
00:02:30,980 --> 00:02:39,470
‫现在, 如果我想延长我的枪, 也许使步枪或手枪会发生什么？

29
00:02:39,470 --> 00:02:40,420
‫就叫它手枪吧｡

30
00:02:40,430 --> 00:02:47,180
‫所以类手枪是一种从枪上继承下来的东西, 哪会这样子做｡

31
00:02:47,180 --> 00:02:56,330
‫如果你以前没看过因为你可能没写过, 你应该做一个从guns的公共继承这样其他类就能看到你是从guns继承的.

32
00:02:56,600 --> 00:03:05,540
‫然后我们在公共部分, 我们想为手枪创建自己的射击方法来覆盖这个功能｡

33
00:03:05,540 --> 00:03:13,220
‫我们不想在结尾处印“砰”的一声, 而是印“丸”的一声, 因为这是一把更小的枪, 它发出的声音更小,

34
00:03:13,220 --> 00:03:17,420
‫这就是我们的射击方法｡

35
00:03:17,420 --> 00:03:20,150
‫让我们结束这堂课吧｡

36
00:03:20,360 --> 00:03:23,300
‫我们要做的就是给自己造一把手枪｡

37
00:03:23,300 --> 00:03:28,010
‫所以手枪我的手枪在这里的主要方法｡

38
00:03:28,670 --> 00:03:34,010
‫因为它们只存在于main方法中, 所以我们不使用指向它们的指针.

39
00:03:34,010 --> 00:03:38,450
‫这就是为什么我们在这里使用了shot操作符而不是箭头｡

40
00:03:38,750 --> 00:03:42,260
‫所以我的手枪没有开枪｡

41
00:03:42,260 --> 00:03:43,460
‫让我们看看它有什么用｡

42
00:03:43,610 --> 00:03:47,280
‫我希望它能打印PPU, 它确实做到了｡

43
00:03:47,300 --> 00:03:47,930
‫好的, 谢谢

44
00:03:48,260 --> 00:03:51,530
‫好的, 我们先移除呼叫的方法｡

45
00:03:51,530 --> 00:03:57,050
‫直接拍摄这些, 让我们看看如何与指针的事情会表现｡

46
00:03:57,290 --> 00:04:01,880
‫所以我将创建一个名为Gun的枪指针｡

47
00:04:02,660 --> 00:04:03,640
‫彼得彼得

48
00:04:04,570 --> 00:04:06,910
‫在下一行我可以在同一行上做｡

49
00:04:06,910 --> 00:04:08,890
‫但为了简单起见, 我将在下一行中执行此操作｡

50
00:04:08,890 --> 00:04:18,310
‫我将把枪指针设置为指向的地址, 这是操作符的地址, 我的枪变量的地址｡

51
00:04:18,580 --> 00:04:23,830
‫因为这是一个指针, 我们需要使用箭头操作符来调用shoot方法.

52
00:04:23,830 --> 00:04:30,400
‫我用我的枪跟注shoot, 然后单击run, 结果是bang｡

53
00:04:30,400 --> 00:04:31,120
‫好的, 谢谢

54
00:04:31,210 --> 00:04:34,450
‫如果我做同样的事会怎样？

55
00:04:34,450 --> 00:04:40,840
‫但是我没有把枪指针设置成指向我的枪, 而是把它设置成指向我的手枪｡

56
00:04:40,990 --> 00:04:42,550
‫我们现在就按“运行”｡

57
00:04:42,880 --> 00:04:43,240
‫好吧, 我会的

58
00:04:43,240 --> 00:04:45,190
‫这是一个有趣的行为｡

59
00:04:45,190 --> 00:04:51,880
‫这里的情况是它在两个实例中都调用了第一个方法｡

60
00:04:51,880 --> 00:04:55,510
‫那么, 为什么没有调用第二个shoot方法呢？

61
00:04:55,510 --> 00:04:58,030
‫我们这里有把手枪作为指示器｡

62
00:04:58,300 --> 00:05:07,960
‫如果你没有虚方法的话, C++会默认使用指针的类型, 这是一个枪指针.

63
00:05:07,960 --> 00:05:11,890
‫请记住, 它将使用该类型来查找要调用的方法｡

64
00:05:11,890 --> 00:05:18,130
‫所以它会在顶部调用这个shoot方法, 而不是pistle中被覆盖的方法｡

65
00:05:18,130 --> 00:05:25,630
‫若要让它呼叫Pistle中覆写的和方法, 它必须呼叫, 而且必须有Pistle型别的指标｡

66
00:05:25,960 --> 00:05:27,520
‫现在这很好｡

67
00:05:27,520 --> 00:05:29,350
‫你可以看到他们为什么这样做｡

68
00:05:29,350 --> 00:05:30,430
‫这是为了提高效率｡

69
00:05:30,430 --> 00:05:32,410
‫C++的核心就是效率｡

70
00:05:32,410 --> 00:05:40,600
‫如果某个东西在编译和运行时会花费更多的成本, 那么C++在默认情况下不会这样做, 除非你要求它这样做｡

71
00:05:40,630 --> 00:05:47,620
‫所以C++很容易就能查到, 这是一个枪指针, 我把它叫做枪指针方法.

72
00:05:47,620 --> 00:05:50,080
‫这就是动态调度的用武之地｡

73
00:05:50,080 --> 00:05:54,070
‫这个花哨的术语基本上意味着我们不希望它这样做｡

74
00:05:54,070 --> 00:05:59,950
‫我们想让它说, 等一下, 枪的指针里到底储存了什么？

75
00:05:59,950 --> 00:06:02,140
‫真实的的类型是什么？

76
00:06:02,380 --> 00:06:06,250
‫我们意识到它的真实的类型是一把手枪｡

77
00:06:06,250 --> 00:06:12,520
‫所以我们要问自己, 是否有一个方法, 用于被覆盖的类型？

78
00:06:12,520 --> 00:06:13,600
‫是的, 有｡

79
00:06:13,600 --> 00:06:20,230
‫那么我们如何给予C++权限, 让它的效率稍微低一点, 并让我们能够重写,

80
00:06:20,230 --> 00:06:26,110
‫而不必改变指针类型, 这并不总是可能的｡

81
00:06:26,110 --> 00:06:32,470
‫事实上, 在许多情况下这是不可能的, 当您拥有自己的自定义actor类时就是这种情况｡

82
00:06:32,590 --> 00:06:41,290
‫Unreal没有设置指针指向你的特定act类, 这将要求他们在每次你引入一个新类时改变整个引擎｡

83
00:06:41,290 --> 00:06:43,120
‫不, 这不可能｡

84
00:06:43,120 --> 00:06:46,360
‫他们有一个指向演员星星类型的指针｡

85
00:06:46,360 --> 00:06:51,010
‫所以当它们调用开始play时, 你希望它调用你的begin play方法｡

86
00:06:51,010 --> 00:06:52,630
‫所以它必须是虚拟的｡

87
00:06:52,630 --> 00:06:54,340
‫这就是关键所在｡

88
00:06:54,340 --> 00:07:02,500
‫如果我们在父类的方法前面加上virtual, 这会通知C++挂起一秒钟, 如果我在一个gun指针类型的指针上调用shoot,

89
00:07:02,500 --> 00:07:10,120
‫我需要检查它是否是, 比如说, 手枪,

90
00:07:10,120 --> 00:07:14,350
‫并且这个shoot方法已经被覆盖了.

91
00:07:14,350 --> 00:07:19,390
‫如果我们现在继续按run, 您可以看到我们得到bang, 然后pill｡

92
00:07:19,420 --> 00:07:24,070
‫因此, 它的工作正如我们对动态调度的预期｡

93
00:07:24,070 --> 00:07:33,430
‫这是这个东西的术语, 我们的意思是检查指针中到底是什么类型的, 然后调用相关的方法.

94
00:07:33,940 --> 00:07:40,450
‫现在有一个额外的语法, 你会经常看到使用, 那就是override关键字｡

95
00:07:40,450 --> 00:07:45,520
‫你把它放在一个你试图重写为虚拟的方法之后｡

96
00:07:45,790 --> 00:07:56,200
‫基本上, override关键字并不是绝对必要的, 也不是把virtual再次放在gun的子类上｡

97
00:07:56,200 --> 00:07:59,350
‫所以你不必装虚, 但做起来也无妨｡

98
00:07:59,560 --> 00:08:07,030
‫但是override特别有用, 因为它允许我们确保我们的命名是正确的｡

99
00:08:07,030 --> 00:08:10,690
‫所以如果我点击运行, 一切都像以前一样运行, 当我有覆盖｡

100
00:08:10,690 --> 00:08:17,290
‫但是假设我把这个方法的名字弄错了, 所以我不做覆盖｡

101
00:08:17,290 --> 00:08:19,490
‫我说这是不可能的｡

102
00:08:19,870 --> 00:08:22,750
‫比如, 我把名字抄错了｡

103
00:08:22,750 --> 00:08:24,520
‫我把它打了进去, 而不是复印｡

104
00:08:24,520 --> 00:08:29,230
‫如果我现在按run, 那么你会看到, 我们又遇到了同样的错误, 因为我们试图跟注投牌｡

105
00:08:29,230 --> 00:08:32,350
‫我们错误地覆盖了它｡

106
00:08:32,350 --> 00:08:33,820
‫砰, 砰｡

107
00:08:33,910 --> 00:08:37,840
‫所以我可以避免这种情况的方法是说, 我正在尝试覆盖一些东西｡

108
00:08:37,840 --> 00:08:44,170
‫所以我在这里放了override关键字, 如果我现在编译它, 你会看到shooter被标记为override, 但是没有方法, 没有虚拟方法可以重写,

109
00:08:44,170 --> 00:08:50,260
‫在这种情况下, 我可以说, 哦, 是的,

110
00:08:50,260 --> 00:08:52,360
‫我打错了.

111
00:08:52,360 --> 00:08:57,340
‫所以我将继续更正我的代码, 然后让它返回拍摄｡

112
00:08:57,340 --> 00:08:59,320
‫而现在它正如我所想的那样工作｡

113
00:08:59,320 --> 00:09:04,150
‫所以把这个超驰装置放在那里, 就有了一点额外的安全性｡

114
00:09:04,240 --> 00:09:09,170
‫它还可以防止我们试图覆盖的东西不是虚拟的｡

115
00:09:09,190 --> 00:09:14,680
‫如果我尝试这样做, 你可以看到它说它被标记为重写, 但它没有重写,

116
00:09:14,680 --> 00:09:19,410
‫因为那个父类没有shoot的虚函数｡

117
00:09:19,420 --> 00:09:28,330
‫所以希望这已经给了你们一些关于C++如何使用和实现虚函数的概述, 以及为什么我们必须对它们如此明确｡

118
00:09:28,600 --> 00:09:32,890
‫现在, 让我们考虑一下一般的虚函数的更多用例｡

119
00:09:32,890 --> 00:09:34,750
‫我们已经从引擎上看到了一些｡

120
00:09:34,750 --> 00:09:37,630
‫您以前在哪里见过虚拟应用？

121
00:09:38,450 --> 00:09:40,190
‫你能想象在哪里使用它吗？

122
00:09:40,220 --> 00:09:46,880
‫我们刚刚看到了一个用例示例, 其中您可能有一把枪, 以及以不同方式实现射击的不同覆盖｡

123
00:09:47,300 --> 00:09:55,530
‫当您想到这两个问题的答案时, 您还能想到使用覆盖父类中的功能的虚函数吗？

124
00:09:55,550 --> 00:10:01,290
‫点击链接, 进入本课程的讨论, 并在那里发表你的答案｡

125
00:10:01,310 --> 00:10:06,010
‫使用案例和您以前使用过它们的地方, 看看其他人提出了什么｡

126
00:10:06,020 --> 00:10:11,250
‫也许这会帮助你巩固你对虚拟方法的理解｡

127
00:10:11,270 --> 00:10:12,280
‫这节课到此为止｡

128
00:10:12,290 --> 00:10:15,710
‫下一节我们将在愤怒中使用虚方法｡

