首页 > 其他分享 >从Super类型看UHT功能

从Super类型看UHT功能

时间:2024-04-02 21:55:07浏览次数:17  
标签:BODY const TEXT UClass GENERATED UHT 类型 TClass Super

问题

在UE的代码中,经常可以看到对于父类类型的引用,因为很多情况下都要先调用父类的同名函数。例如随便看下UE的部分代码,在实现自己序列化函数的时候先调用基类的序列化函数。

但是,尽管Super是一个非常顺数的功能(在行为树库behaviac数中也有super定义),但是C++并没有实现这种功能。这个其实也很容易理解:在多重继承中,super不能位于确定一个基类。这也意味着类似于super这种基类的实现要通过某种约定来消除歧义性。

void UAIGraph::Serialize(FArchive& Ar)
{
	// Overridden to flags up errors in the behavior tree while cooking.
	Super::Serialize(Ar);

	if (Ar.IsSaving() || Ar.IsCooking())
	{
		// Logging of errors happens in UpdateDeprecatedClasses
		UpdateDeprecatedClasses();
	}
}

从UE的代码可以看到,Super的定义位于ObjectMacros.h文件,每个类的基类又是DECLARE_CLASS宏传入的参数。因为DECLARE_CLASS的调用是UHT生成的,所以还要再从UHT中查看宏调用时传入的TSuperClass是什么。

///@file:Engine\Source\Runtime\CoreUObject\Public\UObject\ObjectMacros.h
/*-----------------------------------------------------------------------------
	Class declaration macros.
-----------------------------------------------------------------------------*/

#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI  ) \
private: \
    TClass& operator=(TClass&&);   \
    TClass& operator=(const TClass&);   \
	TRequiredAPI static UClass* GetPrivateStaticClass(); \
public: \
	/** Bitwise union of #EClassFlags pertaining to this class.*/ \
	enum {StaticClassFlags=TStaticFlags}; \
	/** Typedef for the base class ({{ typedef-type }}) */ \
	typedef TSuperClass Super;\
	/** Typedef for {{ typedef-type }}. */ \
	typedef TClass ThisClass;\

Super处理

Super解析

UClass类的解析位于FHeaderPreParser::ParseClassDeclaration函数,函数解析的时候只是简单的把第一个基类作为类的BaseClassNameToken。

void FHeaderPreParser::ParseClassDeclaration(const TCHAR* Filename, const TCHAR* InputText, int32 InLineNumber, const TCHAR* StartingMatchID, FName& out_StrippedClassName, FString& out_ClassName, FString& out_BaseClassName, TArray<FHeaderProvider>& out_RequiredIncludes, const TArray<FSimplifiedParsingClassInfo>& ParsedClassArray)
{
///...
	// Skip optional final keyword
	MatchIdentifier(TEXT("final"), ESearchCase::CaseSensitive);

	// Handle inheritance
	if (MatchSymbol(TEXT(':')))
	{
		// Require 'public'
		RequireIdentifier(TEXT("public"), ESearchCase::CaseSensitive, ErrorMsg);

		// Inherits from something
		FToken BaseClassNameToken;
		if (!GetIdentifier(BaseClassNameToken, true))
		{
			FError::Throwf(TEXT("Expected a base class name"));
		}

		out_BaseClassName = BaseClassNameToken.Identifier;

		int32 InputLineLocal = InputLine;
		auto AddDependencyIfNeeded = [Filename, InputLineLocal, &ParsedClassArray, &out_RequiredIncludes, &ClassNameWithoutPrefixStr](const FString& DependencyClassName)
		{
			if (!Algo::FindBy(ParsedClassArray, DependencyClassName, &FSimplifiedParsingClassInfo::GetClassName))
			{
				FString DependencyClassNameWithoutPrefixStr = GetClassNameWithPrefixRemoved(DependencyClassName);

				if (ClassNameWithoutPrefixStr == DependencyClassNameWithoutPrefixStr)
				{
					FFileLineException::Throwf(Filename, InputLineLocal, TEXT("A class cannot inherit itself or a type with the same name but a different prefix"));
				}

				FString StrippedDependencyName = DependencyClassName.Mid(1);

				// Only add a stripped dependency if the stripped name differs from the stripped class name
				// otherwise it's probably a class with a different prefix.
				if (StrippedDependencyName != ClassNameWithoutPrefixStr)
				{
					out_RequiredIncludes.Add(FHeaderProvider(EHeaderProviderSourceType::ClassName, MoveTemp(StrippedDependencyName)));
				}
			}
		};

		AddDependencyIfNeeded(out_BaseClassName);

		// Get additional inheritance links and rack them up as dependencies if they're UObject derived
		while (MatchSymbol(TEXT(',')))
		{
			// Require 'public'
			RequireIdentifier(TEXT("public"), ESearchCase::CaseSensitive, ErrorMsg);

			FToken InterfaceClassNameToken;
			if (!GetIdentifier(InterfaceClassNameToken, true))
			{
				FFileLineException::Throwf(Filename, InputLine, TEXT("Expected an interface class name"));
			}

			AddDependencyIfNeeded(FString(InterfaceClassNameToken.Identifier));
		}
	}
///...
}

Super记录

解析结果放入数组中,可以看到,数组中存储的是类名和第一个基类的名字。注意:在FSimplifiedParsingClassInfo类的构造函数中传入的基类BaseClassName就是第一个类的名字。

// Performs a preliminary parse of the text in the specified buffer, pulling out useful information for the header generation process
void FHeaderParser::SimplifiedClassParse(const TCHAR* Filename, const TCHAR* InBuffer, TArray<FSimplifiedParsingClassInfo>& OutParsedClassArray, TArray<FHeaderProvider>& DependentOn, FStringOutputDevice& ClassHeaderTextStrippedOfCppText)
{
///...
					FName StrippedInterfaceName;
					Parser.ParseClassDeclaration(Filename, StartOfLine + (UInterfaceMacroDecl - Str), CurrentLine, TEXT("UINTERFACE"), /*out*/ StrippedInterfaceName, /*out*/ ClassName, /*out*/ BaseClassName, /*out*/ DependentOn, OutParsedClassArray);
					OutParsedClassArray.Add(FSimplifiedParsingClassInfo(MoveTemp(ClassName), MoveTemp(BaseClassName), CurrentLine, true));
					if (!bFoundExportedClasses)
					{
						if (FClassDeclarationMetaData* Found = GClassDeclarations.Find(StrippedInterfaceName))
						{
							bFoundExportedClasses = !(Found->ClassFlags & CLASS_NoExport);
						}
					}
///...
}

Super设置

在文件解析完成之后,通过名字查找找到每个UClass对象的基类,然后通过UClass::SetSuperStruct()函数设置/修改UClass的基类。


/**
 * Tries to resolve super classes for classes defined in the given
 * module.
 *
 * @param Package Modules package.
 */
void ResolveSuperClasses(UPackage* Package)
{
	TArray<UObject*> Objects;
	GetObjectsWithPackage(Package, Objects);

	for (UObject* Object : Objects)
	{
		if (!Object->IsA<UClass>() || Object->HasAnyFlags(RF_ClassDefaultObject))
		{
			continue;
		}

		UClass* DefinedClass = Cast<UClass>(Object);

		if (DefinedClass->HasAnyClassFlags(CLASS_Intrinsic | CLASS_NoExport))
		{
			continue;
		}

		const FSimplifiedParsingClassInfo& ParsingInfo = GTypeDefinitionInfoMap[DefinedClass]->GetUnrealSourceFile().GetDefinedClassParsingInfo(DefinedClass);

		const FString& BaseClassName         = ParsingInfo.GetBaseClassName();
		const FString& BaseClassNameStripped = GetClassNameWithPrefixRemoved(BaseClassName);

		if (!BaseClassNameStripped.IsEmpty() && !DefinedClass->GetSuperClass())
		{
			UClass* FoundBaseClass = FindObject<UClass>(Package, *BaseClassNameStripped);

			if (FoundBaseClass == nullptr)
			{
				FoundBaseClass = FindObject<UClass>(ANY_PACKAGE, *BaseClassNameStripped);
			}

			if (FoundBaseClass == nullptr)
			{
				// Don't know its parent class. Raise error.
				FError::Throwf(TEXT("Couldn't find parent type for '%s' named '%s' in current module (Package: %s) or any other module parsed so far."), *DefinedClass->GetName(), *BaseClassName, *GetNameSafe(Package));
			}

			DefinedClass->SetSuperStruct(FoundBaseClass);
			DefinedClass->ClassCastFlags |= FoundBaseClass->ClassCastFlags;
		}
	}
}

Super导出

从生成的UClass对象中获得一个对象的BaseClass,并把该对象的名字作为基类。

void FNativeClassHeaderGenerator::ExportClassFromSourceFileInner(
	FOutputDevice&           OutGeneratedHeaderText,
	FOutputDevice&           OutCpp,
	FOutputDevice&           OutDeclarations,
	FReferenceGatherers&     OutReferenceGatherers,
	FClass*                  Class,
	const FUnrealSourceFile& SourceFile,
	EExportClassOutFlags&    OutFlags
) const
{
///...
	FClass* SuperClass = Class->GetSuperClass();

	// the name for the C++ version of the UClass
	const FString ClassCPPName = FNameLookupCPP::GetNameCPP(Class);
	const FString SuperClassCPPName = (SuperClass ? FNameLookupCPP::GetNameCPP(SuperClass) : TEXT("None"));
	///...
		Boilerplate.Logf(TEXT("\tDECLARE_CLASS(%s, %s, COMPILED_IN_FLAGS(%s%s), %s, TEXT(\"%s\"), %s_API)\r\n"),
			*ClassCPPName,
			*SuperClassCPPName,
			Class->HasAnyClassFlags(CLASS_Abstract) ? TEXT("CLASS_Abstract") : TEXT("0"),
			*GetClassFlagExportText(Class),
			bCastedClass ? *FString::Printf(TEXT("CASTCLASS_%s"), *ClassCPPName) : TEXT("CASTCLASS_None"),
			*FClass::GetTypePackageName(Class),
			*APIArg);
///...
}

同header多UClass

UHT还有一个神奇的功能:同一个头文件可以包含多个UClass的声明,并且每个UClass声明中都包含了相同的

GENERATED_UCLASS_BODY

宏,这些宏都没有参数,那么在同一个文件中有多个UClass的时候同名的宏如何各自展开呢?

GENERATED_UCLASS_BODY扫描

在UHT中,对于"GENERATED_UCLASS_BODY"字符串的扫描和对"UClass"的扫描处理是同一级别的,也是关键的、需要特殊关注的定界符。

其中的代码逻辑的主要功能就是记录该宏所在的行号(及所在的UClass类)

ClassData->SetGeneratedBodyLine(InputLine);
//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup, FToken& Token)
{
///...
if (Token.Matches(TEXT("GENERATED_UINTERFACE_BODY"), ESearchCase::CaseSensitive) || (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Interface))

	if (Token.Matches(TEXT("GENERATED_UCLASS_BODY"), ESearchCase::CaseSensitive) || (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive) && TopNest->NestType == ENestType::Class))
	{
		if (TopNest->NestType != ENestType::Class)
		{
			FError::Throwf(TEXT("%s must occur inside the class definition"), Token.Identifier);
		}

		FClassMetaData* ClassData = GetCurrentClassData();

		if (Token.Matches(TEXT("GENERATED_BODY"), ESearchCase::CaseSensitive))
		{
			if (!ClassDefinitionRanges.Contains(GetCurrentClass()))
			{
				ClassDefinitionRanges.Add(GetCurrentClass(), ClassDefinitionRange());
			}

			ClassDefinitionRanges[GetCurrentClass()].bHasGeneratedBody = true;

			ClassData->GeneratedBodyMacroAccessSpecifier = CurrentAccessSpecifier;
		}
		else
		{
			CurrentAccessSpecifier = ACCESS_Public;
		}

		RequireSymbol(TEXT('('), Token.Identifier);
		CompileVersionDeclaration(GetCurrentClass());
		RequireSymbol(TEXT(')'), Token.Identifier);

		ClassData->SetGeneratedBodyLine(InputLine);

		bClassHasGeneratedBody = true;
		return true;
	}
///...
}

GENERATED_UCLASS_BODY导出

在UHT生成宏名字的时候,添加了扫描时记录的所在文件的行号。

也就是说,UHT生成的文件中并没有直接定义GENERATED_UCLASS_BODY的内容,而是根据行号引入了新的宏定义。

///@file: UnrealSourceFile.cpp
FString FUnrealSourceFile::GetGeneratedMacroName(FClassMetaData* ClassData, const TCHAR* Suffix) const
{
	return GetGeneratedMacroName(ClassData->GetGeneratedBodyLine(), Suffix);
}

FString FUnrealSourceFile::GetGeneratedMacroName(int32 LineNumber, const TCHAR* Suffix) const
{
	if (Suffix != nullptr)
	{
		return FString::Printf(TEXT("%s_%d%s"), *GetFileId(), LineNumber, Suffix);
	}

	return FString::Printf(TEXT("%s_%d"), *GetFileId(), LineNumber);
}

GENERATED_UCLASS_BODY定义

编译器看到的GENERATED_UCLASS_BODY宏是再次经过了预处理,预处理之后的宏和UHT定义的宏精确匹配,都包含了文件名对应的ID和所在的行号。

///@file:ObjectMacros.h
// This pair of macros is used to help implement GENERATED_BODY() and GENERATED_USTRUCT_BODY()
#define BODY_MACRO_COMBINE_INNER(A,B,C,D) A##B##C##D
#define BODY_MACRO_COMBINE(A,B,C,D) BODY_MACRO_COMBINE_INNER(A,B,C,D)

// Include a redundant semicolon at the end of the generated code block, so that intellisense parsers can start parsing
// a new declaration if the line number/generated code is out of date.
#define GENERATED_BODY_LEGACY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY_LEGACY);
#define GENERATED_BODY(...) BODY_MACRO_COMBINE(CURRENT_FILE_ID,_,__LINE__,_GENERATED_BODY);

#define GENERATED_USTRUCT_BODY(...) GENERATED_BODY()
#define GENERATED_UCLASS_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_UINTERFACE_BODY(...) GENERATED_BODY_LEGACY()
#define GENERATED_IINTERFACE_BODY(...) GENERATED_BODY_LEGACY()

CURRENT_FILE_ID

在生成的generated.h文件中,包含了约定的头文件ID的定义,并且位于头文件的最后一行;在定义该宏之前和undef了CURRENT_FILE_ID宏。由于这个限制,对于头文件对应的"generated.h"文件的包含必须位于GENERATED_CLASS_BODY宏所在文件的最后一行(否则会是最后一个包含的generated.h定义的CURRENT_FILE_ID)。

这种机制也是可以实现不同头文件可以互相包含的底层支持机制(不同的头文件有不同的CURRENT_FILE_ID)。

正是这种基于UHT导出的唯一文件ID CURRENT_FILE_ID 和 编译器内置宏定义的行号 LINE 一起唯一确定了GENERATED_UCLASS_BODY的唯一位置

///@file:AIGraph.generated.h
#undef CURRENT_FILE_ID
#define CURRENT_FILE_ID Engine_Source_Editor_AIGraph_Classes_AIGraph_h


PRAGMA_ENABLE_DEPRECATION_WARNINGS

Archive和Record

阅读UHT的代码,可以发现其中还有不少内容是对序列化和反序列化的处理。而序列化和反序列化在UE中的确也广泛使用,包括网络间的同步,对象到磁盘的存储/加载等都会涉及到序列化。

宏定义

预定义了对于结构(Record)的处理:从实现上看,是将“裸数据”UClass的基础上加上了特定的UClass定界符的操作。

///@file:ObjectMacros.h
#define IMPLEMENT_FARCHIVE_SERIALIZER( TClass ) void TClass::Serialize(FArchive& Ar) { TClass::Serialize(FStructuredArchiveFromArchive(Ar).GetSlot().EnterRecord()); }
#define IMPLEMENT_FSTRUCTUREDARCHIVE_SERIALIZER( TClass ) void TClass::Serialize(FStructuredArchive::FRecord Record) { FArchiveUObjectFromStructuredArchive Ar(Record.EnterField(SA_FIELD_NAME(TEXT("BaseClassAutoGen")))); TClass::Serialize(Ar.GetArchive()); Ar.Close(); }
#define DECLARE_FARCHIVE_SERIALIZER( TClass, API ) virtual API void Serialize(FArchive& Ar) override;
#define DECLARE_FSTRUCTUREDARCHIVE_SERIALIZER( TClass, API ) virtual API void Serialize(FStructuredArchive::FRecord Record) override;

扫描

在源代码扫描的过程中,如果扫描到Serialize函数,并且参数是FArchive类型,则自动生成一个对应的FRecord类型声明。

//
// Compile a declaration in Token. Returns 1 if compiled, 0 if not.
//
bool FHeaderParser::CompileDeclaration(FClasses& AllClasses, TArray<UDelegateFunction*>& DelegatesToFixup, FToken& Token)
{
///...

	// Determine if this statement is a serialize function declaration
	if (bEncounteredNewStyleClass_UnmatchedBrackets && IsInAClass() && TopNest->NestType == ENestType::Class)
	{
		while (Token.Matches(TEXT("virtual"), ESearchCase::CaseSensitive) || FString(Token.Identifier).EndsWith(TEXT("_API"), ESearchCase::CaseSensitive))
		{
			GetToken(Token);
		}

		if (Token.Matches(TEXT("void"), ESearchCase::CaseSensitive))
		{
			GetToken(Token);
			if (Token.Matches(TEXT("Serialize"), ESearchCase::CaseSensitive))
			{
				GetToken(Token);
				if (Token.Matches(TEXT('(')))
				{
					GetToken(Token);

					ESerializerArchiveType ArchiveType = ESerializerArchiveType::None;
					if (Token.Matches(TEXT("FArchive"), ESearchCase::CaseSensitive))
					{
///...
}

生成例子

#define Engine_Source_Editor_AIGraph_Classes_AIGraph_h_15_ARCHIVESERIALIZER \
	DECLARE_FSTRUCTUREDARCHIVE_SERIALIZER(UAIGraph, NO_API)

gen.cpp内容

UHT生成的宏声明通常以DECLARE开始,包含在对应的generated.h中;对应地,这些声明的实现通常位于对应的gen.cpp文件中,这些内容和DECLARE对应,通常以IMPLENT开始。

同样是以AIGraph.gen.cpp为例,其中包含了对应的宏调用。

这意味着UHT生成的内容主要包括两部分:

原始类的扩充

同样以UAIGraph类为例,生成的代码就包括了扩展的StaticRegisterNativesUAIGraph函数

	void UAIGraph::StaticRegisterNativesUAIGraph()
	{
	}

通过DECLARE_CLASS展开/扩展的StaticClass、StaticPackage、StaticClassCastFlags方法定义

#define DECLARE_CLASS( TClass, TSuperClass, TStaticFlags, TStaticCastFlags, TPackage, TRequiredAPI  ) \
private: \
    TClass& operator=(TClass&&);   \
    TClass& operator=(const TClass&);   \
	TRequiredAPI static UClass* GetPrivateStaticClass(); \
public: \
	/** Bitwise union of #EClassFlags pertaining to this class.*/ \
	enum {StaticClassFlags=TStaticFlags}; \
	/** Typedef for the base class ({{ typedef-type }}) */ \
	typedef TSuperClass Super;\
	/** Typedef for {{ typedef-type }}. */ \
	typedef TClass ThisClass;\
	/** Returns a UClass object representing this class at runtime */ \
	inline static UClass* StaticClass() \
	{ \
		return GetPrivateStaticClass(); \
	} \
	/** Returns the package this class belongs in */ \
	inline static const TCHAR* StaticPackage() \
	{ \
		return TPackage; \
	} \
	/** Returns the static cast flags for this class */ \
	inline static EClassCastFlags StaticClassCastFlags() \
	{ \
		return TStaticCastFlags; \
	} \
	/** For internal use only; use StaticConstructObject() to create new objects. */ \
	inline void* operator new(const size_t InSize, EInternal InInternalOnly, UObject* InOuter = (UObject*)GetTransientPackage(), FName InName = NAME_None, EObjectFlags InSetFlags = RF_NoFlags) \
	{ \
		return StaticAllocateObject(StaticClass(), InOuter, InName, InSetFlags); \
	} \
	/** For internal use only; use StaticConstructObject() to create new objects. */ \
	inline void* operator new( const size_t InSize, EInternal* InMem ) \
	{ \
		return (void*)InMem; \
	}

以及通过

IMPLEMENT_CLASS(UAIGraph, 3896509080);

展开的GetPrivateStaticClass方法定义


// Register a class at startup time.
#define IMPLEMENT_CLASS(TClass, TClassCrc) \
	static TClassCompiledInDefer<TClass> AutoInitialize##TClass(TEXT(#TClass), sizeof(TClass), TClassCrc); \
	UClass* TClass::GetPrivateStaticClass() \
	{ \
		static UClass* PrivateStaticClass = NULL; \
		if (!PrivateStaticClass) \
		{ \
			/* this could be handled with templates, but we want it external to avoid code bloat */ \
			GetPrivateStaticClassBody( \
				StaticPackage(), \
				(TCHAR*)TEXT(#TClass) + 1 + ((StaticClassFlags & CLASS_Deprecated) ? 11 : 0), \
				PrivateStaticClass, \
				StaticRegisterNatives##TClass, \
				sizeof(TClass), \
				alignof(TClass), \
				(EClassFlags)TClass::StaticClassFlags, \
				TClass::StaticClassCastFlags(), \
				TClass::StaticConfigName(), \
				(UClass::ClassConstructorType)InternalConstructor<TClass>, \
				(UClass::ClassVTableHelperCtorCallerType)InternalVTableHelperCtorCaller<TClass>, \
				&TClass::AddReferencedObjects, \
				&TClass::Super::StaticClass, \
				&TClass::WithinClass::StaticClass \
			); \
		} \
		return PrivateStaticClass; \
	}

原始类对应的UClass对象

另一方面,生成代码还包含了一个可以通过扩展的StaticClass接口获得的、描述该类markup信息的UClass对象

	const UE4CodeGen_Private::FClassParams Z_Construct_UClass_UAIGraph_Statics::ClassParams = {
		&UAIGraph::StaticClass,
		nullptr,
		&StaticCppClassTypeInfo,
		DependentSingletons,
		nullptr,
		Z_Construct_UClass_UAIGraph_Statics::PropPointers,
		nullptr,
		UE_ARRAY_COUNT(DependentSingletons),
		0,
		UE_ARRAY_COUNT(Z_Construct_UClass_UAIGraph_Statics::PropPointers),
		0,
		0x001000A0u,
		METADATA_PARAMS(Z_Construct_UClass_UAIGraph_Statics::Class_MetaDataParams, UE_ARRAY_COUNT(Z_Construct_UClass_UAIGraph_Statics::Class_MetaDataParams))
	};

标签:BODY,const,TEXT,UClass,GENERATED,UHT,类型,TClass,Super
From: https://www.cnblogs.com/tsecer/p/18111590

相关文章

  • gdscript学习笔记2-变量及变量类型
    extendsNode2Dvarmy_nil=nullvarmy_bool=truevarmy_int=1varmy_real=3.1314varmy_string="stringexample"varmy_vector2=Vector2(1,2)#Calledwhenthenodeentersthescenetreeforthefirsttime.func_ready(): print(typeof......
  • python变量和简单的数据类型[第 2 章(上)]
    2.1运行解释文件扩展名:结尾的.py用于指出文件内容是Python代码Python解释器:读取整个文件,确定其中每一行的含义并执行例如,当解释器看到print,就会将括号中的内容打印到屏幕上。语法高亮:用不同的颜色,区分出程序代码中的不同部分 2.2变量修改我们在上一章中写的代......
  • C++bitset类型
    bitset类型我们介绍了将整型运算对象当作二进制位集合处理的一些内置运算符。标准库还定义了bitset类,使得位运算的使用更为容易,并且能够处理超过最长整型类型大小的位集合。bitset类定义在头文件bitset中。定义和初始化bitsetbitset类是一个类模板,它类似array类,具有固定的......
  • C++tuple类型
     tuple类型tuple是类似pair的模板。每个pair的成员类型都不相同,但每个pair都恰好有两个成员。不同tuple类型的成员类型也不相同,但一个tuple可以有任意数量的成员。每个确定的tuple类型的成员数目是固定的,但一个tuple类型的成员数目可以与另一个tuple类型不同。当我们希望......
  • rust语法super、self和Self
    当调用模块的函数时,需要指定完整的路径。1)use关键字包括范围内的所有模块。因此,可以直接调用函数,而不必在调用函数时包含模块。Rust中的use关键字缩短了调用函数的长度,使函数的模块在范围内。2)使用*运算符*运算符用于将所有项目放入范围,这也称为glob运算符。如果使用glob运算......
  • redis特殊数据类型-Geospatial(地理位置)用法
    一 Geospatial(地理位置)介绍使用经纬度定位地理坐标并用一个有序集合zset保存,所以zset命令也可以使用有效的经度从-180度到180度。有效的纬度从-85.05112878度到85.05112878度。二 Geospatial应用场景        通过georadius就可以完成附近的人功能withcoo......
  • 部分数据类型的内置方法及字符串内置方法
    昨日内容回顾【一】循环结构【1】while循环break:退出当前循环coutinue:退出本次循环tag:标志位,可以通过最里层的标志位直接将最外层的while循环断掉【2】for循环遍历可迭代类型(可以被索引取值的都可以被迭代,可一个个取值的就是可迭代的)遍历的意思就是将被需要遍历的......
  • Spring Data Elasticsearch String类型不指定filed 索引创建情况
    对于String类型的字段如果不指定类型会默认创建两种倒排索引       "itemSkuCodes":{         "type":"text",         "fields":{           "keyword":{             "type":"keyword",           ......
  • Python零基础教学(数据类型)
    文章目录数据类型数字类型文字类型(字符串)数字和文字的区别文字相加文字乘法布尔类型(条件判断)布尔变量数据类型在python中,有很多类型。数据类型是用来区分不同的数据的,他们的操作也不同。数据类型:数字、文字、布尔······今天就想先讲这三个类型·,数字和......
  • java,postgresql,python中各种数据类型的占位长度,取值范围
    Java数据类型Java中的数据类型分为两类:基本数据类型和引用数据类型。基本数据类型数据类型占位长度取值范围byte1字节-128127short2字节-3276832767int4字节-21474836482147483647long8字节-92233720368547758089223372036854775807float4字节1.4E-453.4028235E38double8字节4.......