9,模板、类模板、高级宏替换、单例、内联inline、强制内联FORCEINLINE、重命名typedf

模板

  • const
  • 模板
    • 函数模板和任意参数结合的高级运用
      • 指针可以被当作数组使用
  • 类模板
    • 类模板的匹配
      • 普通的类模板实例化的匹配
      • 类模板内存在函数的匹配
  • hpp文件
      • 模板指针
  • 类模板和虚继承
    • FORCEINLINE强制内联
    • inline内联函数
    • typedef重命名
  • 类模板和多态
  • 高级宏替换和模板
    • 高级宏替换
    • 高级宏替换和模板连用
      • __VA_ARGS __
    • 高级宏替换和模板连用案例设计模式“单例”
      • 单例
      • typedef
      • 高级宏替换和类模板实例1
      • 高级宏替换和类模板实例2
        • 枚举enum
        • unit8

const

#include 
using namespace std;
class FTestConst
{
public:
	void Init()const 
	{
		//a = 0;//因为被const修饰了所以不能更改类内的变量,但是可以修饰类外的
		Init2();//类内的其他函数也需要加上const才能被调用
	}
	void Init2() const{}
private:
	int a;
};

int main()
{
	const FTestConst* A = new FTestConst();
	A->Init();//如果类FTestConst中Init函数后没加const会报错
	return 0;
}

模板

如下,一种函数需要写很多次,维护时修改也需要多次修改,所以提出了模板

#include 
using namespace std;
class vector
{
public:
	void Add();
	void Remove();
private:
	int* data;
};

class vector1
{
public:
	void Add();
	void Remove();
private:
	float* data;
};

class vector2
{
public:
	void Add();
	void Remove();
private:
	double* data;
};

int main()
{

	return 0;
}

可以使用模板简化成下列代码,编译器会自动生成上述代码,可以生成不同类包括自定义类,也可以生成指针

#include 
using namespace std;

template<class T>//模板,动态的生成类
class vector
{
public:

private:
	T* Data;
};

class Hello {};//自定义类

int main()
{
	vector<int>array_int;//使用模板后,编译器会自动生成上述数量众多的代码,int的生成int的,float生成float的
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;//自定义类也可以
	vector<Hello*>array_Hello1;//指针也可以

	return 0;
}

函数模板和任意参数结合的高级运用

传数组时一定传const加vector加引用地址的方式&,这样不会存在拷贝

通用函数模板

#include
template<class T>
class vector
{
public:
	const T*operator[](int Index)const//保证,值和返回的值不能被修改所以用const
	{//第一个const保证指针的内容不能修改,第二个const保证函数内不能被修改
		return &Data[Index];//指针可以当作数组,用地址方式传递
	}
	int Num() const { return 0; }//获取容器里有多少个元素
private:
	T* Data;
};

template<typename T>// Printf_Array如果不封装为函数模板,只能接收vector类型的参数,限制了通用性
void Call_Array(const vector<T>& InArray)// Printf_Array封装成模板可以实现对不同类型的vector进行同样的打印操作
{
	for (int i=0;i<InArray.Num();i++)
	{
		T*Ptr = InArray[i];//使用指针取内容
		//现在没有重载操作符,需要再vector内进行重载操作符
		//因为vector是自定义的模板类,它表示一个储存任意类型元素的动态数组

		//逻辑……
	}
}

int main()
{
	vector<int>array_int;
	return 0;
}

指针可以被当作数组使用

在C语言中,数组名称本质上是指向数组第一个元素的指针。因此,可以通过将数组名称赋给一个指针变量,然后使用指针来访问数组元素。

注意,当将指针用作数组时,需要确保指针指向的内存空间足够容纳需要访问的元素。

int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;  // 将数组名称赋给指针变量

printf("%d", *ptr);  // 输出第一个元素的值
printf("%d", *(ptr + 2));  // 输出第三个元素的值
#include 
using namespace std;

template<class T>//模板,动态的生成类
class vector
{
public:
	const T* operator[](int Index)const
	{
		return &Data[Index];
	}
	int Num() const{ return 0; }//因为当前传过来的容器是const,所以调过来的函数等各方面都是不能修改的,所以加const
private:
	T* Data;
};

class Hello {};

template<typename T>
void Call_Array(const vector<T>& InArray)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];//如果想去掉const不要忘了可以用const_cast
	}
}

template<typename T>
void Call_Array_arg(const vector<T>& InArray, ...)//任意参
{//泛化了。增加了代码的实用性
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

int main()
{
	vector<int>array_int;
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;
	vector<Hello*>array_Hello1;

    Call_Array<int>(array_int);//如果参数有类似于vector加一个模板可以省略此T,如下省略int等,编译器挥拳自动找到int(UE就是这么干的)
	Call_Array(array_int);//省略了
	Call_Array(array_float);//省略了
	Call_Array(array_double);//省略了,编译器会自动更改类型
	Call_Array(array_Hello1);//结构指针也可以放

	int a = 10;
	float c = 10.f;
	Call_Array_arg(array_int, a,c);//传入任意参

	return 0;
}

类模板

模板分为函数模板和类模板,下面是一个函数就是函数模板,下面是一个类就是类模板

可以定义多个类型

template<class T1,class T2>
class vector
{
public:
};

里面也可以包含类模板

#include 
using namespace std;

template<class T/*,class T1*/>
class vector
{
public:
	const T* operator[](int Index)const
	{
		return &Data[Index];
	}
	int Num() const { return 0; }

	template<class T2>//模板里可以套用模板
	void Fun()
	{
		T2 a;
	}

	//template//和上面一样可以去掉
	void Call_Array_arg1(const vector<T>& InArray, ...)
	{

	}
private:
	T* Data;
	//T1* Data;
};

class Hello {};

template<typename T>
void Call_Array(const vector<T>& InArray)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

template<typename T>
void Call_Array_arg(const vector<T>& InArray, ...)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

int main()
{
	vector<int>array_int;
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;
	vector<Hello*>array_Hello1;

	Call_Array<int>(array_int);
	Call_Array(array_float);
	Call_Array(array_double);
	Call_Array(array_Hello1);

	//array_int.Fun();//不可以直接调用,需要传入类型,如下
	array_int.Fun<int>();

	int a = 10;
	float c = 10.f;
	Call_Array_arg(array_int, a, c);

	return 0;
}

添加一个.cpp和.h文件
类模板的注意事项和技巧

class.h

#pragma once
template<class T>
class FHelloHello
{
public:
	void HelloC();//实现转到了cpp中,隐藏了当前函数
	
	template<class T1>
	T1* HelloC_c()
	{
		return nullptr;
	}
private:
	T* Data;

};

class.cpp

#include"class.h"

template<class T>
void FHelloHello<T>::HelloC()//指明是哪一个模板的
{
}

学习.cpp

#include 
#include"class.cpp"

template<class T/*,class T1*/>
class vector
{
public:
	const T* operator[](int Index)const
	{
		return &Data[Index];
	}
	int Num() const { return 0; }

	template<class T2>//模板里可以套用模板
	void Fun()
	{
		T2 a;
	}

	//template//和上面一样可以去掉
	void Call_Array_arg1(const vector<T>& InArray, ...)
	{

	}
private:
	T* Data;
	//T1* Data;
};

class Hello {};

template<typename T>
void Call_Array(const vector<T>& InArray)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

template<typename T>
void Call_Array_arg(const vector<T>& InArray, ...)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

int main()
{
	vector<int>array_int;
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;
	vector<Hello*>array_Hello1;

	Call_Array<int>(array_int);
	Call_Array(array_float);
	Call_Array(array_double);
	Call_Array(array_Hello1);

	//array_int.Fun();//不可以直接调用,需要传入类型,如下
	array_int.Fun<int>();

	FHelloHello<int>Array_CC;//记得包含头文件

	float*p=Array_CC.HelloC_c<float>();//因为HelloC_c在class.h中是T1类型的指针所以要加上类型

	int a = 10;
	float c = 10.f;
	Call_Array_arg(array_int, a, c);

	return 0;
}

类模板的匹配

类模板的匹配分为两种情况,一种是类模板内存在函数的匹配,另一种是普通的类模板实例化的匹配

UE4多线程会大量使用

普通的类模板实例化的匹配

在C++中,类模板匹配是指根据提供的模板参数,编译器将选择对应的类模板实例化。类模板是一种通用的类定义,它包含了一个或多个模板参数,用于表示类型或值。当使用类模板创建对象或调用成员函数时,编译器会根据提供的模板参数来确定要使用的具体类模板实例。

类模板匹配可以在编译时进行,编译器会根据模板参数的类型和值,匹配合适的类模板实例。如果存在多个符合条件的类模板实例,编译器会选择最佳的匹配。

例如,下面是一个简单的类模板示例:

template <typename T>
class MyClass {
public:
    MyClass(T value) {
        // 构造函数的实现
    }
    
    void printValue() {
        // 打印成员变量的值
        cout << value << endl;
    }
    
private:
    T value;
};

假设我们有下面两个调用:

MyClass<int> obj1(10);
MyClass<double> obj2(3.14);

在第一个调用中,模板参数是int,编译器会根据模板参数实例化一个MyClass的类模板。在第二个调用中,模板参数是double,编译器会根据模板参数实例化一个MyClass的类模板。这样,每个对象都拥有自己特定的数据类型。

类模板匹配的过程是在编译时发生的,对于不同的模板参数,会生成对应的类模板实例。这种机制使得类模板在编写通用代码时非常有用,可以适应各种不同的类型和值。

类模板内存在函数的匹配

类模板的匹配:当写类模板的时候,里面执行函数,那么当前这个类里面必须写一个如下列代码中的Work的函数。否则就会编译出错

class.h

#pragma once
template<class T>
class FHelloHello
{
public:
	void HelloC();
	
	template<class T1>
	T1* HelloC_c()
	{
		T Data;
		bool bWork = Data.Work();//会调用Work(),需要添加一个Work,在学习.cpp中添加

		return nullptr;
	}
private:
	T* Data;

};

class.cpp

#include"class.h"

template<class T>
void FHelloHello<T>::HelloC()//指明是哪一个模板的
{
}

学习.cpp

#include 
#include"class.cpp"

template<class T/*,class T1*/>
class vector
{
public:
	const T* operator[](int Index)const
	{
		return &Data[Index];
	}
	int Num() const { return 0; }

	template<class T2>//模板里可以套用模板
	void Fun()
	{
		T2 a;
	}

	//template//和上面一样可以去掉
	void Call_Array_arg1(const vector<T>& InArray, ...)
	{

	}
private:
	T* Data;
	//T1* Data;
};

class Hello {};

template<typename T>
void Call_Array(const vector<T>& InArray)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

template<typename T>
void Call_Array_arg(const vector<T>& InArray, ...)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

class FTest
{
public:
	bool Work()//Work的左边必须有类
	{//在class.h中会调用Work,没有Work会报错,所以创建一个Work
		std::cout << "Work" << std::endl;
		return true;
	}
};

int main()
{
	vector<int>array_int;
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;
	vector<Hello*>array_Hello1;

	Call_Array<int>(array_int);
	Call_Array(array_float);
	Call_Array(array_double);
	Call_Array(array_Hello1);

	array_int.Fun<int>();

	FHelloHello<int>Array_CC;//因为下面Array_CC要调用Work,所以更改类型
	float*p=Array_CC.HelloC_c<float>();

	int a = 10;
	float c = 10.f;
	Call_Array_arg(array_int, a, c);

	return 0;
}

hpp文件

创建文件,更改后缀为.hpp
.hpp代表既是.h又是.cpp的合体

c1.hpp

#pragma once

template<class T>
class FHelloHello1
{
public:
	void HelloC()
	{

	}

	template<class T1>
	T1* HelloC_c()
	{
		T Data;
		bool bWork = Data.Work();

		return nullptr;
	}
private:
	T* Data;

};

class.h

#pragma once
template<class T>
class FHelloHello
{
public:
	void HelloC();
	
	template<class T1>
	T1* HelloC_c()
	{
		T Data;
		bool bWork = Data.Work();

		return nullptr;
	}
private:
	T* Data;

};

class.cpp

#include"class.h"

template<class T>
void FHelloHello<T>::HelloC()
{
}

学习.cpp

#include 
#include"class.cpp"
#include"c1.hpp"

template<class T>
class vector
{
public:
	const T* operator[](int Index)const
	{
		return &Data[Index];
	}
	int Num() const { return 0; }

	template<class T2>
	void Fun()
	{
		T2 a;
	}

	void Call_Array_arg1(const vector<T>& InArray, ...)
	{

	}
private:
	T* Data;
};

class Hello {};

template<typename T>
void Call_Array(const vector<T>& InArray)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

template<typename T>
void Call_Array_arg(const vector<T>& InArray, ...)
{
	for (int i = 0; i < InArray.Num(); i++)
	{
		const T* Ptr = InArray[i];
	}
}

class FTest
{
public:
	bool Work()
	{
		std::cout << "Work" << std::endl;
		return true;
	}
};

int main()
{
	vector<int>array_int;
	vector<float>array_float;
	vector<double>array_double;
	vector<Hello>array_Hello;
	vector<Hello*>array_Hello1;

	Call_Array<int>(array_int);
	Call_Array(array_float);
	Call_Array(array_double);
	Call_Array(array_Hello1);

	array_int.Fun<int>();

	FHelloHello<FTest>Array_CC;
	float*p=Array_CC.HelloC_c<float>();

	FHelloHello1<FTest>Array_CC1;
	p = Array_CC1.HelloC_c<float>();
	//p指针已经被定义过了,此处使用是为了方便,是float类型,所以类模板也显示float类型
	//int p1=Array_CC1.HelloC_c();也可以

	int a = 10;
	float c = 10.f;
	Call_Array_arg(array_int, a, c);

	return 0;
}

boost库(一个专门写客户端的库,由多个独立的模块组成的一个库集合(里面内容很多,所以编译很慢,一般自己写库))用的多,里面写的是函数的用法,类等。(相当于重新学了一编c++)

模板指针

FHello<int>*pt=&a;

类模板和虚继承

模板可以使用多态

FORCEINLINE强制内联

FORCEINLINE 宏定义了一个平台相关的内联提示,如果是在 Microsoft Visual C++ 编译器下编译,它会将 FORCEINLINE 展开为 __forceinline,表示强制内联;其他编译器则将其展开为普通的 inline。

#ifdef _MSC_VER // 适用于 Microsoft Visual C++ 编译器
    #define FORCEINLINE __forceinline
#else // 其他编译器
    #define FORCEINLINE inline
#endif

FORCEINLINE int add(int a, int b)
{
    return a + b;
}

inline内联函数

使用 inline 关键字可以用于声明内联函数,但是编译器并不一定会将指定为 inline 的函数进行内联展开。为了强制编译器进行内联展开

inline int add(int a, int b)
{
    return a + b; // 内联函数的定义
}

内联函数之所以在性能上比普通函数调用更高效,主要有以下几个原因:

  1. 减少函数调用开销:普通函数调用涉及参数传递、栈帧的创建与销毁等开销。而内联函数在调用点展开,避免了这些开销,将函数的代码直接嵌入到调用点处执行。这样可以减少函数调用的额外开销,提高执行效率。

  2. 避免跳转和保留返回地址:普通函数调用需要保存调用位置的返回地址,并在函数执行完毕后返回到调用位置。而内联函数的展开消除了这种跳转,直接在调用点处执行内联函数的代码,避免了保存和恢复返回地址的开销。

  3. 更好的优化可能性:内联函数的展开可以提供更大的优化可能性。函数展开后的代码与调用者的上下文紧密相关,编译器可以更好地进行优化,如常量折叠、无用代码消除、寄存器分配等。这种上下文相关的优化是在函数调用中难以实现的。

内联函数的定义通常放在头文件中,以便在需要调用的地方进行展开。
内联函数的定义与声明必须在同一个文件中,否则可能会导致链接错误。
内联函数不适合于复杂的函数,包含大量的代码或涉及控制流程的情况。
对于递归函数或虚函数,编译器一般不会将其内联展开。
在类的定义中,成员函数默认情况下是内联的,不需要显式使用 inline 关键字声明。

正常函数的调用过程

在正常的函数调用过程中,函数的执行流程会按照一定的规则进行。函数调用的底层原理通常涉及以下几个步骤:

  1. 参数传递:调用函数时,将参数的值传递给被调函数。参数可以通过值传递、引用传递或指针传递的方式进行传递。

  2. 保存返回地址:在调用函数之前,会将函数调用位置的返回地址保存起来。这样,在函数执行完毕后,可以返回到调用位置继续执行。

  3. 保存当前函数的上下文:这包括函数局部变量的值、临时变量、寄存器状态等。保存上下文的方式通常是通过栈帧(stack frame)实现,将相关信息压入栈中。

  4. 跳转到被调函数:将控制权转移至被调函数的代码段,开始执行被调函数。在被调函数执行期间,它会处理传递的参数,并执行函数体内的逻辑操作。

  5. 返回结果:在被调函数执行完毕后,将返回值传递给调用函数。同时,会回到之前保存的返回地址,并从那里继续执行调用函数剩余的代码。

函数调用的过程并不涉及函数的拷贝。相反,函数在内存中只有一份代码,被所有的调用者共享。当函数被调用时,控制权会转移至函数,并在函数执行完毕后返回到调用位置。

需要注意的是,函数调用和内联展开是两种不同的机制。内联函数会尝试将函数的代码插入到调用点处,从而避免函数调用的开销。而正常的函数调用过程中,会按照一定的规则进行参数传递、保存返回地址、保存上下文等操作。

需要特别注意的是,编译器对于函数调用的优化是一项复杂的任务,可能会使用各种优化策略来提高性能。因此,在不同的编译器和编译设置下,函数调用的具体实现细节可能会有所不同。

typedef重命名

typedef是用于创建类型别名的关键字,而不是用于重命名变量

typedef FTHello<int> Super; 

表示将 FTHello 类型定义为 Super。

这里使用了 typedef 关键字来创建类型别名。FTHello 是一个模板类 FTHello 的特化,其中模板参数为 int。通过 typedef 将其定义为 Super,相当于给 FTHello 这个类型取了一个别名 Super。

使用 Super 关键字时,就相当于使用 FTHello 类型。这样可以简化代码,方便重复使用该类型。例如:

Super obj; // 等价于 FTHello obj;

这样做的好处是,在后续的代码中可以直接使用 Super 来代表 FTHello 类型,提高代码可读性和可维护性。

类模板和多态

class.h

#pragma once
#include 

template<class T>
class FTHello
{
public:
	virtual void Init() 
	{
		std::cout << "FTHello::Init" << std::endl;
	}
	virtual void Destroy()
	{
		std::cout << "FTHello::Destroy" << std::endl;
	}

	//测试调用重载,结果为支持
	void Hello() 
	{
		std::cout << "Hello1" << std::endl;
	}
	void Hello(int a)
	{
		std::cout << "Hello2" << std::endl;
	}
	void Hello(int a,int b)
	{
		std::cout << "Hello3" << std::endl;
	}

	//测试调用任意参,结果为支持
	void Hello1(...)
	{
		std::cout << "..." << std::endl;
	}
private:
	T* A;

};

class FChello:public FTHello<int>
{
	typedef FTHello<int>Super;
public:
	virtual void Init()
	{
		std::cout << "FChello::Init" << std::endl;
		Super::Init();
	}
	virtual void Destroy()
	{
		std::cout << "FChello::Destroy" << std::endl;
		Super::Destroy();
	}
};

class.cpp

#include"class.h"

学习.cpp

#include 
#include"class.cpp"
int main()
{
	FChello a;
	FTHello<int>* pt = &a;

	pt->Init();//输出结果:FChello::Init实现的是子类
	//加上typedef后输出结果为:先实现子类,再实现父类(模板支持虚函数,支持多态)
	//FChello::Init
	//FTHello::Init
	pt->Hello();//Hello1
	pt->Hello(1);//Hello2
	pt->Hello(1, 2);//Hello3

	pt->Hello1(1, 'a');//...
	return 0;
}

高级宏替换和模板

高级宏替换

在C++中,高级宏替换是使用预处理器宏来进行代码替换和代码生成的一种技术。高级宏替换通常使用预处理器中的一特性,如宏参数、字符串化运算符和连接运算符等。

以下是一些常见的高级宏替换用法示例:

  1. 宏参数:宏参数允许你在宏的定义中使用参数,并根据需要进行替换。例如:
#define MAX(a, b) ((a) > (b) ? (a) : (b))

int maxNum = MAX(10, 20); // 替换为 int maxNum = ((10) > (20) ? (10) : (20));

这个宏定义可以返回两个数中的较大值。

  1. 字符串化运算符(#):字符串化运算符允许你将参数转换为字符串。例如:
#define STRINGIFY(x) #x

const char* str = STRINGIFY(Hello World); // 替换为 const char* str = "Hello World";

这个宏定义将参数转换为字符串常量。

  1. 连接运算符(##):连接运算符允许你将多个参数连接在一起,形成一个单独的标识符。例如:
#define CONCAT(a, b) a ## b

int num = CONCAT(12, 34); // 替换为 int num = 1234;

这个宏定义将两个数字连接在一起形成一个新的数字。

  1. 变长参数宏:高级宏替换还支持使用变长参数列表的宏定义。例如:
#include 
#include 

#define PRINTF(format, ...) std::printf(format, ##__VA_ARGS__)

int main() {
    PRINTF("%s %d", "Hello", 123); // 替换为 std::printf("%s %d", "Hello", 123);
    return 0;
}

这个宏定义允许你像使用printf函数一样输出格式化的字符串。

注意,尽管高级宏替换可以用于一些特定的情况,但过度使用宏可能导致代码难以调试和理解。在实际编程中,应谨慎使用高级宏替换,并确保它们不会引入不必要的复杂性。

注意在宏定义中,反斜杠\用于将一个宏定义分为多行。这样做可以提高代码的可读性,使宏定义更清晰易懂。在每行\后不能写注释,否则相当于把下一行注释了

高级宏替换和模板连用

ue4中别直接写宏替换,有几率会崩溃,先写点别的

当你使用#define关键字定义一个宏时,后面的代码部分就是宏的定义内容

下列代码是一段标准的宏定义模板

class.h

#pragma once
#include 
using namespace std;

#define HELLO_CLASS(ClassName,Code) \
template<class T>\
class F##ClassName/*令实例化的名字前必须加F*/ \
{\
public:\
    void Work()\
    {\
        Code;\
    }\
private:\
    T* A;\
};
//HELLO_CLASS(WWWW, { cout << "WWWW" << endl; })
//也可以放在这,也可以放在上层业务处

学习.cpp

#include 
#include"class.h"

//左边定义名字,右边定义逻辑
HELLO_CLASS(WWWW, { cout << "WWWW" << endl; })//定义一个模板,可以定义在上层业务也就是此处,也可以放在宏定义下面
int main()
{
	FWWWW<int>W;//因为再class.h中F##ClassName \左F代表名字前添加F
	W.Work();

	return 0;
}

#define 宏定义名字(定义名字,逻辑)

应用:可以定义一套框架,(可能是一套线程),例如线程的框架,右边就是线程执行的方法,左边给线程起名字(可以直接执行方法,也可以通过调用名字执行方法)

__VA_ARGS __

__VA_ARGS__是一个预定义的宏,在C和C++中用于表示宏的可变参数。

#define PRINT_VALUES(...) \
    printf(__VA_ARGS__)

在这个例子中,PRINT_VALUES是一个宏,其参数被表示为__VA_ARGS__。当你使用这个宏时,传递给宏的参数将替换成printf( __ VA_ARGS__)中的__VA_ARGS__部分。

class.h

#pragma once
#include 
using namespace std;

//左边定义名字,右边定义逻辑,F代表格式(其实是字符),...任意参
#define HELLO_CLASS(ClassName,Code,Fcc,...) \
template<class T>\
class F##ClassName \
{\
public:\
    void Work()\
    {\
        printf(Fcc,__VA_ARGS__);/*F加一个#号转成字符*/\
        Code;\
    }\
private:\
    T* A;\
};
//HELLO_CLASS(WWWW, { cout << "WWWW" << endl; })
//也可以放在这,也可以放在上层业务处

学习.cpp

#include 
#include"class.h"

//左边定义名字,右边定义逻辑
HELLO_CLASS(WWWW, { cout << "WWWW" << endl; }," % s HHH", "HelloC")//定义一个模板,可以定义在上层业务也就是此处,也可以放在宏定义下面
int main()
{
	FWWWW<int>W;//因为再class.h中F##ClassName \左F代表名字前添加F
	W.Work();

	return 0;
}

输出结果: HelloC HHHWWWW

高级宏替换和模板连用案例设计模式“单例”

单例

单例模式是一种创建型设计模式,用于确保类只有一个实例,并提供全局访问点。

在实现单例模式时,通常会使用以下步骤:

  1. 将类的构造函数设置为私有,以防止直接通过实例化类创建多个对象。

  2. 在该类中创建一个私有静态成员变量,用于保存该类的唯一实例。

  3. 创建一个公共的静态方法,用于获取(或创建)该类的实例。这个方法应该检查私有静态变量,如果变量是空的,就创建一个新的实例并将其赋值给私有静态变量,然后返回该实例。

下面是一个简单的单例模式的示例:

class Singleton {
private:
    static Singleton* instance;

    // 私有构造函数,防止直接实例化
    Singleton() {}

public:
    // 获取唯一实例的方法
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    // 示例方法
    void doSomething() {
        // 实现
    }
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;

使用单例模式时,可以通过Singleton::getInstance()方法获取 Singleton 的唯一实例,并调用其他公共方法。

Singleton* singleton = Singleton::getInstance();
singleton->doSomething();

需要注意的是,单例模式可以影响代码的可测试性和可扩展性,因为实例化的对象往往会隐藏在代码的背后。因此,在使用单例模式时要谨慎并根据实际需求评估其适用性。

(写单例是需要用通用的类型,可以用模板的方式代替)

typedef

"typedef"是C语言中的一个关键字,用于定义新的数据类型别名
下面是typedef的语法格式:

typedef 原数据类型名 别名;

例如,我们可以使用typedef来定义一个整数类型的别名:

typedef int Age;

定义了"Age"之后,可以使用"Age"作为整数类型的别名,如下所示:

Age myAge = 25;

这样就定义了一个整数变量"myAge",并且使用"Age"作为类型别名。通过typedef,我们可以提高代码的可读性和可维护性。

高级宏替换和类模板实例1

单例实例

实例:

class.h

#pragma once
#include
using namespace std;

#define D_THREAD(TName,TInstance)/*名字,实例*/\
template<class T>\
class FThread_##TName/*单例,此代码可以看作一个线程*/\
{\
public:\
	static T* Get() \
	{\
		if (!Instance)\
		{\
			Instance = new T();\
		}\
		return Instance; \
	}\
	static void Destroy() \
	{\
		delete Instance; \
		Instance = NULL; \
	}\
private:\
	static T* Instance;/*被分配到静态区永远不变*/\
};\
template<class T>\
T *FThread_##TName<T>::Instance = NULL;/*声明,所有静态变量都需要声明一下*/\
typedef FThread_##TName<TInstance> TName;

学习.cpp

#include 
#include"class.h"

class FHelloC
{
public:
	void Hello()
	{
		cout << "Hello" << endl;
	}
};

class FHelloC1
{
public:
	void Hello()
	{
		cout << "Hello" << endl;
	}
};

D_THREAD(GThread, FHelloC)
D_THREAD(GThread1, FHelloC1)
/*typedef FThreadGThread;//类型FThread定义了一个别名为GThread
typedef FThreadGThread1;*/
int main()
{
	FHelloC* ccc = GThread::Get();//需要初始化
	ccc->Hello();

	FHelloC1* ccc1 = GThread1::Get();
	ccc1->Hello();

	GThread1::Destroy();
	GThread::Destroy();

	return 0;
}

解释分析代码

这段代码的作用是定义了一个线程类模板,单例设计模式FThread_##TName,并通过D_THREAD宏实例化了两个具体的线程类GThreadGThread1

在class.h中的#pragma once是一个预编译指令,表示只包含一次当前的头文件,避免重复包含。

以下是代码的解释:

  1. #pragma once: 避免重复包含头文件。

  2. #include:包含iostream头文件,用于输入输出操作。

  3. using namespace std;:使用std命名空间,避免使用std::来限定标准库中的标识符。

  4. #define D_THREAD(TName,TInstance) ...: 定义了一个宏D_THREAD,宏参数分别是线程类名字TName和实例类型TInstance

  5. template\class FThread_##TName{...};: 定义了一个模板类FThread_##TName,模板参数是类T

  6. static T* Get() {...}: 定义了一个静态成员函数Get(),用于获取线程类的实例。

  7. static void Destroy() {...}: 定义了一个静态成员函数Destroy(),用于销毁线程类的实例。

  8. static T* Instance;: 定义了一个静态成员变量Instance,用于存储线程类的实例。

  9. typedef FThread_##TName TName;: 通过typedef定义了一个别名TName,用于指代实例化后的线程类。

  10. #include : 包含了iostream头文件。

  11. #include "class.h": 包含了class.h头文件。

  12. class FHelloC {...}: 定义了一个类FHelloC,具有成员函数Hello(),用于输出"Hello"。

  13. class FHelloC1 {...}: 定义了一个类FHelloC1,具有成员函数Hello(),用于输出"Hello"。

  14. D_THREAD(GThread, FHelloC): 使用宏D_THREAD实例化了一个名为GThread的线程类,类类型为FHelloC

  15. D_THREAD(GThread1, FHelloC1): 使用宏D_THREAD实例化了一个名为GThread1的线程类,类类型为FHelloC1

  16. FHelloC* ccc = GThread::Get();: 创建FHelloC类型的指针ccc,并通过GThread::Get()获取线程类GThread的实例。

  17. ccc->Hello();: 调用ccc指针所指向的对象的成员函数Hello(),输出"Hello"。

  18. FHelloC1* ccc1 = GThread1::Get();: 创建FHelloC1类型的指针ccc1,并通过GThread::Get()获取线程类GThread1的实例。

  19. ccc1->Hello();: 调用ccc1指针所指向的对象的成员函数Hello(),输出"Hello"。

  20. GThread1::Destroy();: 销毁线程类GThread1的实例。

  21. GThread::Destroy();: 销毁线程类GThread的实例。

  22. return 0;: 返回0,表示程序正常结束。

好处:

在这段代码中,静态成员变量Instance的作用是在类的所有实例之间共享一个实例。使用静态成员变量可以确保在不同的实例中访问到的是同一个对象,而不是每次创建新的对象。

在这里,FThread_##TName::Instance是一个静态成员变量,每一个具体的线程类都有一个对应的静态成员变量Instance,用于存储该线程类的唯一实例。

使用静态成员变量的好处有以下几点:

  1. 全局可访问:静态成员变量存储在静态存储区域,可以被类的所有实例共享,以便在任何地方都可以访问到。

  2. 共享数据:当多个实例需要共享数据时,静态成员变量可以在各个实例之间保存共享的信息。

  3. 节省内存空间:静态成员变量只占用一份内存空间,在内存中只存在一个实例,避免了每个实例都拥有一个副本的开销。

  4. 保持状态:静态成员变量的值在类的所有实例之间保持一致,可以用于保存状态信息。

在这段代码中,Instance作为静态成员变量,用于保存线程类的唯一实例,确保在调用Get()函数获取实例时,返回的都是同一个对象。这种设计模式通常被称为单例模式,通过静态成员变量可以方便地实现单例模式的功能。

高级宏替换和类模板实例2

设计模式

先定义一段模板的骨架

#pragma once

typedef unsigned char uint8;//unsigned char 是一个 8 位无符号整数类型,范围为 0 到 255

template<uint8 MessageType>//无符号
class NerControlMessage
{

};

一些技能都可以用着这种模板

#pragma once

typedef unsigned char uint8;//unsigned char 是一个 8 位无符号整数类型,范围为 0 到 255

template<uint8 MessageType>//无符号
class NetControlMessage
{

};

//控制网络信息的信息
class FNetControlMessgeInfo
{
public:
	//接收
	template<class ... ParamTypes>//任意参的模板
	static void ReceiveParams(ParamTypes &... Params) 
	{
		//实现
	}
	//发送
	template<class ... ParamTypes>//任意参的模板
	static void SendsParams(ParamTypes &... Params)
	{
		//实现
	}
};

//协议(先定义一个枚举)
//enum MyEnum{ NMT_AAA = 0 };//MyEnum是一个枚举类型的名称,可以通过 MyEnum::NMT_AAA 来引用枚举值
enum { NMT_AAA = 0 };//协议号。代表全局,可以通过 NMT_AAA 来引用
template<>class NetControlMessage<0>
{
public:
	template<class ... ParamTypes>
	static void Send(ParamTypes &... Param)
	{
		FNetControlMessgeInfo::SendsParams(Param ...);
	}
	//接收
	template<class ... ParamTypes>
	static void Receive(ParamTypes &... Param)
	{
		FNetControlMessgeInfo::ReceiveParams(Param ...);
	}
};

上面代码需要一个协议号就得写一次,用模板简化

advanced.h

#pragma once

typedef unsigned char uint8;//unsigned char 是一个 8 位无符号整数类型,范围为 0 到 255

template<uint8 MessageType>//无符号
class NetControlMessage
{

};

//控制网络信息的信息
class FNetControlMessgeInfo
{
public:
	//接收
	template<class ... ParamTypes>//任意参的模板
	static void ReceiveParams(ParamTypes &... Params) 
	{
		//实现
	}
	//发送
	template<class ... ParamTypes>//任意参的模板
	static void SendsParams(ParamTypes &... Params)
	{
		//实现
	}
};

#define DEFINE_CONTROL_CHANNEL_MESSAGE(Name,Index, ...)\
enum { NMT_##Name = 0 };/*协议号*/\
template<>\
class NetControlMessage<Index>\
{\
public:\
	template<class ... ParamTypes>\
	static void Send(ParamTypes &... Param)\
	{\
		FNetControlMessgeInfo::SendsParams(Param ...);\
	}\
	/*接收*/\
	template<class ... ParamTypes>\
	static void Receive(ParamTypes &... Param)\
	{\
		FNetControlMessgeInfo::ReceiveParams(Param ...);\
	}\
};

//定义协议行为
DEFINE_CONTROL_CHANNEL_MESSAGE(Hello, 0, int ,float, char);//定义协议号是0
DEFINE_CONTROL_CHANNEL_MESSAGE(Welcome, 1, int, char);//定义协议号是1
DEFINE_CONTROL_CHANNEL_MESSAGE(Login, 2, int, char, char);//定义协议号是2

学习.cpp

#include 
#include"advanced.h"
#include
int main()
{
	//客户端
	int a;
	float b;
	char c;
	NetControlMessage<NMT_Login>::Send(a, b, c);
	NetControlMessage<NMT_Welcome>::Send(a, c);//<>内是协议号
	//也可以写作NetControlMessage<1>::Send(a, c);
	//服务器
	int a1;
	float b1;
	char c1;
	NetControlMessage<NMT_Login>::Receive(a1, b1, c1);
	NetControlMessage<NMT_Welcome>::Receive(a1, b1, c1);//<>内是协议号
	NetControlMessage<NMT_Hello>::Receive(a1, b1, c1);

	return 0;
}

注意1

在宏定义 DEFINE_CONTROL_CHANNEL_MESSAGE(Hello, 0, int, float, char) 中,参数 0 代表了协议的索引或标识,也可以用Hello协议名调用该协议

注意2

枚举enum

在C++中,enum是一种枚举类型,用于定义一组具名的整型常量。枚举类型允许为一组相关的值指定一个友好的名称,以提高代码的可读性和可维护性。

下面是一个定义和使用枚举类型的示例:

#include 

// 定义一个枚举类型
enum Color {
    RED,    // 0
    GREEN,  // 1
    BLUE    // 2
};

int main() {
    // 声明一个枚举变量
    Color color = GREEN;

    // 使用枚举值
    if (color == RED) {
        std::cout << "Color is red" << std::endl;
    } else if (color == GREEN) {
        std::cout << "Color is green" << std::endl;
    } else if (color == BLUE) {
        std::cout << "Color is blue" << std::endl;
    }

    return 0;
}

在上面的示例中,enum Color定义了一个名为Color的枚举类型,其中包含三个枚举值REDGREENBLUE,它们分别被赋予了默认的整数值0、1和2。

main()函数中,我们声明了一个名为color的变量,并将其赋值为GREEN,然后根据color的值使用条件语句输出对应的颜色。

枚举类型的优点之一是可以使用具名的枚举值来提高代码的可读性,同时也可以避免使用不相关的整型值来表示特定的概念。

注意3

unit8

unsigned char 是一个 8 位无符号整数类型,范围为 0 到 255
代码中使用typedf重命名为uint8
(在UE4引擎中uint8 是一个无符号的8位整数数据类型,主要用于表示像素数据、颜色值、布尔值等需要小范围整数的情况)

uint8 Red = 255;
uint8 Green = 128;
uint8 Blue = 0;

// 使用 uint8 值构建一个颜色
FColor Color(Red, Green, Blue);

// 检查 uint8 值是否大于某个阈值
bool IsGreaterThanThreshold(uint8 Value, uint8 Threshold)
{
    return Value > Threshold;
}

注意4

enum { NMT_##Name = Index };

以上代码中

= Index 是为了给枚举成员设置一个初始值,使其与宏定义中的 Index 的值相等。
如果写= 0,它是将枚举成员的整数值设置为 0,通常用于表示默认值或无效值
如果写= “ad”,它是将枚举成员的值设置为字面值 “ad”,这在 C++11 引入了对枚举类的支持后是合法的,需要注意版本

注意5

template<uint8 MessageType>

注意6

在C++中,当你使用template<>语法时,你可以对一个已经存在的模板进行特化。特化是指为特定类型或特定模板参数提供额外的实现或行为。

当你使用template<>而不添加内容时,它表示你正在对模板进行显式的全特化,即为整个模板提供了一个特定的实现。在这种情况下,你需要提供模板参数的具体实现,以覆盖模板的通用实现。

例如,假设你有以下的模板类和特化实例:

template <typename T>
class MyTemplate {
public:
    void foo() {
        // 通用实现
    }
};

// 对模板进行特化
template <>
class MyTemplate<int> {
public:
    void foo() {
        // int类型的特化实现
    }
};

在上述代码中,MyTemplate是一个通用的模板类,它具有一个foo成员函数的通用实现。然后,通过使用template<>语法,我们进行了对MyTemplate的特化,提供了一个特定于int类型的实现。

因此,当你使用template<>语法,但不添加内容时,它表示你正在为整个模板提供特化实现,而没有覆盖通用的实现。这样可以根据具体的特化需求来改变模板的行为或实现。

MessageType作为模板参数的目的是为了在使用NetControlMessage类模板时能够提供一种标识或区分不同类型实例的机制。

在这个特定的代码示例中,MessageType被用作标识控制网络信息的消息类型的协议号。每个消息类型都有一个特定的协议号,通过在实例化模板时提供不同的协议号作为MessageType,可以生成不同类型的NetControlMessage类,用于处理特定的协议号对应的消息。

注意6

template<class ... ParamTypes>

以上代码中ParamTypes在此处表示模板参数包,允许在模板中接受任意数量的模板参数

解释代码

这段代码是一个网络控制消息的定义和实现。让我逐行解释给你听:

  1. #pragma once: 这是一个预处理指令,保证头文件只被编译一次。

  2. typedef unsigned char uint8: 这是一个类型定义,将unsigned char命名为uint8,表示一个8位无符号整数类型,取值范围是0到255。

  3. template class NetControlMessage: 这是一个模板类的定义,MessageType是一个模板参数,代表控制消息的类型。

  4. class FNetControlMessgeInfo: 这是一个网络控制消息类,用于处理接收和发送消息的方法。

  5. template static void ReceiveParams(ParamTypes &... Params): 这是一个模板函数,用于接收参数,参数可以是任意类型。

  6. template static void SendsParams(ParamTypes &... Params): 这是一个模板函数,用于发送参数,参数可以是任意类型。

  7. #define DEFINE_CONTROL_CHANNEL_MESSAGE(Name,Index, ...): 这是一个宏定义,用于定义控制通道消息的协议号和相关操作。

  8. enum { NMT_##Name = Index }: 这是一个枚举类型的定义,在宏展开时生成一个协议号的常量。

  9. template<> class NetControlMessage: 这是一个特化模板类的定义,指定了模板参数为协议号。

  10. template static void Send(ParamTypes &... Param): 这是一个函数模板特化,用于发送消息。

  11. template static void Receive(ParamTypes &... Param): 这是一个函数模板特化,用于接收消息。

  12. DEFINE_CONTROL_CHANNEL_MESSAGE(Hello, 0, int ,float, char): 宏展开,定义了一个名为Hello的协议,协议号为0,参数类型为int、float和char。

  13. DEFINE_CONTROL_CHANNEL_MESSAGE(Welcome, 1, int, char): 宏展开,定义了一个名为Welcome的协议,协议号为1,参数类型为int和char。

  14. DEFINE_CONTROL_CHANNEL_MESSAGE(Login, 2, int, char, char): 宏展开,定义了一个名为Login的协议,协议号为2,参数类型为int和两个char。

你可能感兴趣的:(进阶c++,c++,开发语言)