赞
踩
1、new与malloc的区别
new:
new首先会去调用operator new函数,申请足够的内存(大多数底层用malloc实现),然后调用类型的构造函数来初始化变量,最后返回自定义类型的指针,delete先调用析构函数,然后调用operator delete函数释放内存(大多数底层用free实现)
__cdecl 是C Declaration的缩写(declaration,声明)
malloc:
malloc是库函数,只能申请内存,没有初始化功能
所以new与malloc最大的区别就是new能进行构造函数初始化
2、strcpy、sprintf、memcpy的区别
strcpy:用于将一个字符串复制到另一个字符串中
sprintf:sprintf函数用于将格式化的字符串输出到一个字符数组中
- char str[10];
- int num = 4;
- sprintf(str, "number is %d", num);
- printf("%s\n", str); // 输出 number is 4
memcpy:用于将一个内存地址的数据复制到另一个内存地址中
3、子弹穿墙问题
子弹向前打出一个身位长的射线,若打到了墙面则开始计算子弹与墙的距离,在通过距离除以速度算出时间,时间过后则进行碰撞
4、UE4如何切关卡后保留数据
存放在GameInstancesubsystem中,不要存在gameinstance内,这样会导致项目臃肿
5、UE4客户端能否使用AIController
不可以,在DS(dedicated server)模型下,AIController只存在于服务端,其主要是通过在服务端对Pawn进行操控,
然后再同步到客户端。
6、Blueprintable与NotBlueprintable
将C++类加入蓝图类
如果为NotBlueprintable则不能被蓝图化
7、BlueprintImplementEvent与BlueprintNativeEvent的区别
如果实现了蓝图,那么C++的Implement接口则不调用
如果没写蓝图接口则调用C++接口
而BlueprintImplementEvent只是做接口给蓝图,不拓展C++接口。
8、C++类中默认有什么函数
1、构造函数
2、拷贝构造函数
3、析构函数
4、重载赋值运算符函数
9、UE4生命周期
从先到后:UGameEngine->GameInstance->World和WorldContext->PersistentLevel->GameMode->GameState->PlayerController->PlayerState->HUD->Character
在UGameEngine(继承自UEngine)
首先UEngine的子类有UGameEngine和UEditorEngine,UEngine中的GEngine有访问UE的全局资源,是一个很重要的指针。
- void UGameEngine::Init(IEngineLoop* InEngineLoop)
- {
- DECLARE_SCOPE_CYCLE_COUNTER(TEXT("UGameEngine Init"), STAT_GameEngineStartup, STATGROUP_LoadTime);
-
-
- // Call base.
- UEngine::Init(InEngineLoop);
-
- #if USE_NETWORK_PROFILER
- FString NetworkProfilerTag;
- if( FParse::Value(FCommandLine::Get(), TEXT("NETWORKPROFILER="), NetworkProfilerTag ) )
- {
- GNetworkProfiler.EnableTracking(true);
- }
- #endif
-
- // Load and apply user game settings
- GetGameUserSettings()->LoadSettings();
- GetGameUserSettings()->ApplyNonResolutionSettings();
-
- // Create game instance. For GameEngine, this should be the only GameInstance that ever gets created.
- {
- FSoftClassPath GameInstanceClassName = GetDefault<UGameMapsSettings>()->GameInstanceClass;
- UClass* GameInstanceClass = (GameInstanceClassName.IsValid() ? LoadObject<UClass>(NULL, *GameInstanceClassName.ToString()) : UGameInstance::StaticClass());
-
- if (GameInstanceClass == nullptr)
- {
- UE_LOG(LogEngine, Error, TEXT("Unable to load GameInstance Class '%s'. Falling back to generic UGameInstance."), *GameInstanceClassName.ToString());
- GameInstanceClass = UGameInstance::StaticClass();
- }
-
- GameInstance = NewObject<UGameInstance>(this, GameInstanceClass);
-
- GameInstance->InitializeStandalone();
- }
-
- // // Creates the initial world context. For GameEngine, this should be the only WorldContext that ever gets created.
- // FWorldContext& InitialWorldContext = CreateNewWorldContext(EWorldType::Game);
-
- IMovieSceneCaptureInterface* MovieSceneCaptureImpl = nullptr;
- #if WITH_EDITOR
- if (!IsRunningDedicatedServer() && !IsRunningCommandlet())
- {
- MovieSceneCaptureImpl = IMovieSceneCaptureModule::Get().InitializeFromCommandLine();
- if (MovieSceneCaptureImpl)
- {
- StartupMovieCaptureHandle = MovieSceneCaptureImpl->GetHandle();
- }
- }
- #endif
-
- // Initialize the viewport client.
- UGameViewportClient* ViewportClient = NULL;
- if(GIsClient)
- {
- ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass);
- ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance);
- GameViewport = ViewportClient;
- GameInstance->GetWorldContext()->GameViewport = ViewportClient;
- }
-
- LastTimeLogsFlushed = FPlatformTime::Seconds();
-
- // Attach the viewport client to a new viewport.
- if(ViewportClient)
- {
- // This must be created before any gameplay code adds widgets
- bool bWindowAlreadyExists = GameViewportWindow.IsValid();
- if (!bWindowAlreadyExists)
- {
- UE_LOG(LogEngine, Log, TEXT("GameWindow did not exist. Was created"));
- GameViewportWindow = CreateGameWindow();
- }
-
- CreateGameViewport( ViewportClient );
-
- if( !bWindowAlreadyExists )
- {
- SwitchGameWindowToUseGameViewport();
- }
-
- FString Error;
- if(ViewportClient->SetupInitialLocalPlayer(Error) == NULL)
- {
- UE_LOG(LogEngine, Fatal,TEXT("%s"),*Error);
- }
-
- UGameViewportClient::OnViewportCreated().Broadcast();
- }
-
- UE_LOG(LogInit, Display, TEXT("Game Engine Initialized.") );
-
- // for IsInitialized()
- bIsInitialized = true;
- }
UGameInstance继承自FExec,Exec就是UE4的命令行
- void UGameInstance::InitializeStandalone(const FName InPackageName, UPackage* InWorldPackage)
- {
- // Creates the world context. This should be the only WorldContext that ever gets created for this GameInstance.
- WorldContext = &GetEngine()->CreateNewWorldContext(EWorldType::Game);
- WorldContext->OwningGameInstance = this;
-
- // In standalone create a dummy world from the beginning to avoid issues of not having a world until LoadMap gets us our real world
- UWorld* DummyWorld = UWorld::CreateWorld(EWorldType::Game, false, InPackageName, InWorldPackage);
- DummyWorld->SetGameInstance(this);
- WorldContext->SetCurrentWorld(DummyWorld);
-
- Init();
- }
然后在GameInstance的初始化单例中生成对应的World和一个WorldContext
同时GameInstance内对GameMode进行绑定生成,但这个动作不是在初始化函数中进行的,而是在
在PreviewScene中在构造函数中调用了SetGameMode
World中在FSeamlessTravelHandler类中进行Tick设置
去调用的
然后在GameMode里面对所有的GameState,PlayerController,PlayerState,HUD和Default Panw,Spectator进行绑定
- void UWorld::InitializeNewWorld(const InitializationValues IVS)
- {
- if (!IVS.bTransactional)
- {
- ClearFlags(RF_Transactional);
- }
-
- PersistentLevel = NewObject<ULevel>(this, TEXT("PersistentLevel"));
- PersistentLevel->Initialize(FURL(nullptr));
- PersistentLevel->Model = NewObject<UModel>(PersistentLevel);
- PersistentLevel->Model->Initialize(nullptr, 1);
- PersistentLevel->OwningWorld = this;
-
- // Create the WorldInfo actor.
- FActorSpawnParameters SpawnInfo;
-
- // Mark objects are transactional for undo/ redo.
- if (IVS.bTransactional)
- {
- SpawnInfo.ObjectFlags |= RF_Transactional;
- PersistentLevel->SetFlags( RF_Transactional );
- PersistentLevel->Model->SetFlags( RF_Transactional );
- }
- else
- {
- SpawnInfo.ObjectFlags &= ~RF_Transactional;
- PersistentLevel->ClearFlags( RF_Transactional );
- PersistentLevel->Model->ClearFlags( RF_Transactional );
- }
-
- #if WITH_EDITORONLY_DATA
- // Need to associate current level so SpawnActor doesn't complain.
- CurrentLevel = PersistentLevel;
- #endif
- SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
- // Set constant name for WorldSettings to make a network replication work between new worlds on host and client
- SpawnInfo.Name = GEngine->WorldSettingsClass->GetFName();
- AWorldSettings* WorldSettings = SpawnActor<AWorldSettings>(GEngine->WorldSettingsClass, SpawnInfo );
- // Allow the world creator to override the default game mode in case they do not plan to load a level.
- if (IVS.DefaultGameMode)
- {
- WorldSettings->DefaultGameMode = IVS.DefaultGameMode;
- }
- PersistentLevel->SetWorldSettings(WorldSettings);
- check(GetWorldSettings());
- #if WITH_EDITOR
- WorldSettings->SetIsTemporarilyHiddenInEditor(true);
- #endif
- #if INCLUDE_CHAOS
- /*FChaosSolversModule* ChaosModule = FModuleManager::Get().GetModulePtr<FChaosSolversModule>("ChaosSolvers");
- check(ChaosModule);
- FActorSpawnParameters ChaosSpawnInfo;
- ChaosSpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
- ChaosSpawnInfo.Name = TEXT("DefaultChaosActor");
- SpawnActor(ChaosModule->GetSolverActorClass(), nullptr, nullptr, ChaosSpawnInfo);
- check(PhysicsScene_Chaos);*/
- #endif
- // Initialize the world
- InitWorld(IVS);
- // Update components.
- UpdateWorldComponents( true, false );
- }
在World内对PersistentLevel进行初始化,再对PersistentLevel中的WorldSetting进行设置,这里的WorldSetting设置只是Level与WorldSetting进行绑定,WorldSetting初始化还是在GameInstance里面
然后是GameMode,它负责什么呢?
1、Class登记:记录GameMode中各种类的信息
2、Spawn:创建Pawn和PlayerController等
3、游戏进度:游戏暂停重启的逻辑
4、过场动画逻辑
5、多人游戏的步调同步
GameState?
AGameState用于保存游戏数据,如任务进度,打到了哪一个关卡什么的
10、sizeof与strlen的区别
sizeof包括最后的'\0',所以为4,strlen不包括最后的'\0',所以为3
sizeof是字节个数,不止用在字符串上,而strlen只能用在字符串上,输出字符串个数
11、重写与重载的区别
1、重写是子类覆盖父类名字相同的方法,对其进行重新实现
2、重载有两种情况,在同一个类中,同一个方法名拥有不同的参数个数,或者同一个方法名拥有参数个数相同的不同类型参数
12、构造函数和析构函数都能加Virtual吗?
虚函数
虚函数跟着对象走
函数名后面加override没有任何功能性作用,只是开发规范要求写上。
override作用或C++语言哲学:将大多数错误暴露在编译阶段
普通函数属于编译期状态,虚函数属于运行期状态。
编译器是看代码人就知道答案,运行期是看代码也不能清楚答案。
基类析构函数都要加virtual
如果析构函数不加virtual,普通成员函数跟类走,那么delete p;就会去调用p的类型的析构函数即Animal的析构函数
加了情况就一不一样:
构造函数是用来创建一个新的对象,而虚函数的运行是建立在对象的基础上,在构造函数执行时,对象尚未形成(普通函数属于编译期状态,虚函数属于运行期状态),所以不能将构造函数定义为虚函数,通常析构函数才会用virtual修饰(虚函数实际存放在对象的头部的虚函数表中的)
所以构造函数不能加virtual,析构函数子类必须加virtual
13、UCLASS、GENERATED_BODY以及为何要加 文件名.generated.h
1、GENERATED_BODY
通过当前文件ID以及行号来建立一个唯一的键值名称
2、UCLASS
与GENERATED_BODY是一样的
3、xxx.generated.h
在Intermediate(中间生成文件)中生成,这个生成是在项目文件编译成功后它会自动生成xxx.generated.h以及xxx.gen.cpp
结合之前的UCLASS和GENERATED_BODY
.generated.h里面生成了这些文件,是通过UCLASS和GENERATED_BODY来生成的
例如GENRATED_BODY在18行,CURRENT_FILE_ID为first_Source_first_SCharacter_h
注意这个DECLARE_CLASS
下一张图引用:UE4 UObject系列之UClass(二) - 知乎
这里完成了对反射类型做了操作
同时还在里面对new进行了重载
最后new出来的对象在UClass内对类进行注册,构建了基础的反射系统
我遇到的反射的问题:
1、当你生成了Intermediate中的反射文件.gen和.generated文件后,GENERATED_BODY()这种宏不能挪位置因为已经标明了对应的行号了,必须重新Generate Visual Studio project files才能继续正常运行
14、UE4内存管理
内存管理机制的三种形式
1、垃圾回收
2、智能指针
3、C++内存管理(malloc realloc calloc new)
一、对非UObject类型进行TSharedPtr管理,不能用于UObject对象,不然等TSharedPtr释放后,UObject自带的GC机制再去进行垃圾回收就会引起两次free的异常
二、以及对UObject类型进行GC机制的回收
三、UE4中的C++内存管理通常通过FMalloc以及GMalloc进行管理
- class CORE_API FMalloc :
- public FUseSystemMallocForNew,
- public FExec
- {
- public:
- virtual void* Malloc( SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT ) = 0;
- virtual void* TryMalloc( SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT );
- virtual void* Realloc( void* Original, SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT ) = 0;
- virtual void* TryRealloc(void* Original, SIZE_T Count, uint32 Alignment=DEFAULT_ALIGNMENT);
- virtual void Free( void* Original ) = 0
为了在调用new/delete能够调用ue4的自定义函数,ue4内部替换了operator new。这一替换是通过IMPLEMENT_MODULE宏引入的
FMallocBinned使用freelist机制管理空闲内存。每个空闲块的信息记录在FFreeMem结构中,显式存储。
FMallocBinned使用内存池机制,内部包含POOL_COUNT(42)个内存池和2个扩展的页内存池;其中每个内存池的信息由FPoolInfo结构体维护,记录了当前FreeMem内存块指针等,而特定大小的所有内存池由FPoolTable维护;内存池内包含了内存块的双向链表。
15、情况一: UObject对象中有UObject成员
UObject成员加入UPROPERTY,由于UObject对象有肯定是被实例化的且UObject本身就存在垃圾回收,自己是实例化出来的,引用计数肯定大于等于1,在UObject对象中又有对UObject成员的引用,则不会被自动销毁
总结:UObject本身就在垃圾回收机制内,只需加入UPROPERTY就可以将UObject成员加入垃圾回收机制且有UObject对象对UObject成员的引用
情况二: 非UObject对象中有UObject成员
方法一:由于非UObject对象并不具备垃圾回收机制,所以就必须手动将UObject对象加入引用,首先先继承自FGCObject,再重写AddReferenceObjects函数,使得UObject成员加入回收机制
- class NotObject : public FGCObject {
- public:
- UObject* CollectorObject;
-
- void AddReferencedObjects(FReferenceCollector& ReferenceCollector) override {
- ReferenceCollector.AddReferencedObject(CollectorObject);
- }
- }
当NotObject被销毁时,CollectorObject的引用也会自动清除,引用计数为0即被销毁
方法二:
- NotObject* notObject;
- notObject->AddToRoot();
将对象AddToRoot,使得变量不会被自动GC,在析构函数调用notObject->RemoveFromRoot(),防止内存泄漏
总结:非UObject对象继承自FGCObject,然后重写AddReferencedObjects函数,将对象成员加入垃圾回收机制
非UObject对象,将对象AddToRoot(),这样非UObject对象的UObject成员都加入垃圾回收机制,非UObject成员通过new/delete进行管理
16、什么时候会调用EndPlay事件
1、手动销毁Actor时
2、切换关卡时
17、UE4中Class与Struct有什么区别
1、对于C++而言,class变量函数默认为private,而struct变量函数默认为public
2、对于UE4而言,class可以将函数暴露给UE4编辑器,例如BlueprintCallable、BlueprintNativeEvent、BlueprintImplement等,而struct不能
3、对于UE4而言,class能够继承、struct不能继承,若class继承自uobject类,则class自带垃圾回收功能,就不能用智能指针,否则会造成double free,struct虽然是USTRUCT的宏修饰,但是它并不继承自uobject,且不能继承,所以它没有自带垃圾回收,可以与智能指针搭配调用
18、UE4中线程的种类
1、渲染线程
2、游戏线程
3、GPU线程
19、UE4的多线程有哪些?具体怎么使用?
1、FRunnable
2、AsyncTask
3、TaskGraph
一、FRunnable
首先它不是UObject类,记得手动delete掉,它内在函数有六个,其中五个是常用的:
1、Init(),返回值是一个bool,Run根据这个bool确定是否执行
2、Run(),若Init返回值会true且存在一个FRunnableThread线程,两者成立执行内部方法
3、Stop()
通常对Run中的while条件变为false例如:
递增了就不为1,Run的内部逻辑就停止运行
4、Exit()
唤起主线程
5、析构函数
通常对FRunnableThread指针等内容给清空
但想要真的能够让Run跑起来,还需要一个FRunnableThread
AysncTask:
UE并发-线程池和AsyncTask - 知乎 (zhihu.com)
【UE4 C++ 基础知识】<14> 多线程——AsyncTask - 砥才人 - 博客园 (cnblogs.com)
最简单的使用方法
但这个内部实现用的还是TaskGraph
- // Fill out your copyright notice in the Description page of Project Settings.
-
- #pragma once
-
- #include "CoreMinimal.h"
- #include "GameFramework/Actor.h"
- #include "Async/AsyncWork.h"
- #include "HAL/ThreadManager.h"
-
- class FAsyncTK : public FNonAbandonableTask
- {
- friend class FAutoDeleteAsyncTask<FAsyncTK>;
-
- int32 ExampleData;
- float WorkingTime;
-
- public:
- FAsyncTK(int32 InExampleData, float TheWorkingTime = 1)
- : ExampleData(InExampleData), WorkingTime(TheWorkingTime) { }
-
- ~FAsyncTK() {
- Log(__FUNCTION__);
- }
-
- // 执行任务(必须实现)
- void DoWork() {
- // do the work...
- //FPlatformProcess::Sleep(WorkingTime);
- Log(__FUNCTION__);
- }
-
- // 用时统计对应的ID(必须实现)
- FORCEINLINE TStatId GetStatId() const
- {
- RETURN_QUICK_DECLARE_CYCLE_STAT(ExampleAsyncTask, STATGROUP_ThreadPoolAsyncTasks);
- }
-
- void Log(const char* Action)
- {
- uint32 CurrentThreadId = FPlatformTLS::GetCurrentThreadId();
- FString CurrentThreadName = FThreadManager::Get().GetThreadName(CurrentThreadId);
- UE_LOG(LogTemp, Display, TEXT("%s[%d] - %s, ExampleData=%d"), *CurrentThreadName, CurrentThreadId,
- ANSI_TO_TCHAR(Action), ExampleData);
- }
- };
TaskGraph:
虚幻4之TaskGraph:十分钟上手TaskGraph - 知乎 (zhihu.com)
- // Fill out your copyright notice in the Description page of Project Settings.
-
- #pragma once
-
- #include "CoreMinimal.h"
-
- class TKGraph
- {
- FString FileContent;
- public:
- TKGraph(
- FString InFileContent) :
- FileContent(MoveTemp(InFileContent))
- {}
-
- FORCEINLINE TStatId GetStatId() const {
- RETURN_QUICK_DECLARE_CYCLE_STAT(FTaskCompletion_LoadFileToString,
- STATGROUP_TaskGraphTasks);
- }
-
- static ENamedThreads::Type GetDesiredThread() { return ENamedThreads::GameThread; }
- static ESubsequentsMode::Type GetSubsequentsMode()
- {
- return ESubsequentsMode::TrackSubsequents;
- }
-
- void DoTask(ENamedThreads::Type CurrentThread,
- const FGraphEventRef& MyCompletionGraphEvent) {
- check(IsInGameThread());
- GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Green, TEXT("TASKGRAPH"));
- }
- };
- TGraphTask<TKGraph>::CreateTask().
- ConstructAndDispatchWhenReady("pink");
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。