对于有些低版本的插件,可以通过此方法自己编译到高版本而无需等待插件作者更新

使用工具:如图所示
img

步骤1:打开cmd,并使用cd命令切换到此目录
步骤2:输入如下指令 RunUAT.bat BuildPlugin -Plugin=“路径1” -Package=“路径2”,路径1是要编译的uplugin文件路径,路径2是生成文件的存放路径必须在UE目录外
步骤3:回车等待编译完成,编译成功会显示 BUILD SUCCESSFUL 如下图,失败会显示相应的报错,如果编译成功编译好的插件就会在路径2下
注意:如果跨的版本太多很大可能会编译失败,因为插件代码中所用的类和接口可能在当前版本被废弃或发生了改变,此时需要根据报错修改相应代码
img
img

————————————————
原文链接:https://blog.csdn.net/qq_45523399/article/details/138391840

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment

分清重写,重载和隐藏

  • 重载(overload), 它通常是指在同一个类中有两个或两个以上的函数,它们的函数名相同,但是函数签名不同,也就是说有不同的形参。

  • 重写(ovrride)的意思更接近覆盖, 在C++中是指派生类覆盖了基类的虚函数

  • 隐藏(overwrite)是指基类成员函数,无论它是是否是为虚函数,当派生类出现同名函数时,如果派生类函数签名不同于基函数,则基类函数会被隐藏。如果派生类函数签名与基函数相同,则需要确定基类函数是否为虚函数,如果时虚函数这里的概念就是重写,否则基类函数也会被隐藏。

override 说明符

  • 检查是否符合重写规则,让编译器来检查我们又没有正确覆写父类的虚函数。因此,任何子类覆写虚函数后导致函数签名的变化,都会导致编译器报错。

-如果你一直使用override,他还会给你带来一个意想不到的收获:在C++11之前,关于子类覆写父类虚函数后,子类的虚函数还要不要加virtual关键字,还是个值得争论的问题。人们一开始之所以想在子类的覆写函数上也加上virtual,就是为了提醒读代码的人这是个覆写的虚函数。但是在有了override之后,这个关键字本身就起了这个作用,之前的做法也就不是必须的了。所以建议的做法是,在最顶层的虚函数上加上virtual关键字后,其余的子类覆写后就不再加virtual了,但是要统一加上override

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base{
public:
virtual void some_func() {}
virtual void foo(int x) {}
virtual void far() const {}
void baz() {}

};

class Derived :public Base{
public:
virtual void sone_func() override {} // ERROR,编译器报警没有正确覆写Base::some_func
virtual void foo(int &x) override {} // ERROR,编译器报警没有正确覆写Base::foo
virtual void bar() override {} // ERROR,编译器报警没有正确覆写Base::far
virtual void baz() override {} // ERROR,编译器报警没有正确覆写Base::baz
};

final说明符

  • 阻止派生类去继承基类虚函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base{
public:
virtual void foo(int x) {}
};

class Derived : public Base{
public:
void foo(int x) final {};
};

class Derived2 : public Derived{
public:
void foo(int x) {};
}
  • 阻止类被作为基类
1
2
3
4
5
6
7
8
9
class Base final {
public:
virtual void foo(int x){}
};

class Derived : public Base{ //ERROR
public:
void foo(int x){} //ERROR
}

如何更新你的代码

如果你的代码已经非常冗长并且到处都是覆写的虚函数,这个时候你想用final和override来更新你的代码库,我会给你以下建议:

  • 在处理final时应该慎之又慎,需要case by case地处理。
  • 至于override,无论哪里用到了覆写,直接加上去就完事了,只会有益处而不会有害,不过看清楚你的本意到底是不是覆写哈。尤其是你加上override后编译器报错的,一定要先检查你的本意到底是不是覆写,确定你真的犯了错误后,再去修改源代码。
  • 加上override后,我建议你把子类的virtual关键字去掉,并且以后都遵循上边提到的规范:只有顶层的虚函数加上virtual,其余子类在覆写时全部用override来标记。
  • 当你看到一个函数标记了virtual时,弄清楚它是不是顶层虚函数其实是一件非常困难的事情,这个时候我建议你多用用你的编译器,当你使用override报错时,也有可能它确实就是顶层virtual函数了。

结论

override和final都能帮助我们改善虚函数相关的安全性,使用final需要更多的权衡但是override就尽管大胆地用吧。

C++ Lambda表达式的完整介绍

c++在c++11标准中引入了lambda表达式,一般用于定义匿名函数,使得代码更加灵活简洁。lambda表达式与普通函数类似,也有参数列表、返回值类型和函数体,只是它的定义方式更简洁,并且可以在函数内部定义。

什么是Lambda表达式

最常见的lambda的表达式写法如下

1
2
auto plus = [] (int v1, int v2) -> int { return v1 + v2; }
int sum = plus(1, 2);

这里只是计算两个数的和,我们一般情况下肯定是不会这么用的,更多的时候,我们都是和stl的一些算法结合使用,例如自定义一个结构体的排序规则和打印。

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
struct Item
{
Item(int aa, int bb) : a(aa), b(bb) {}
int a;
int b;
};

int main()
{
std::vector<Item> vec;
vec.push_back(Item(1, 19));
vec.push_back(Item(10, 3));
vec.push_back(Item(3, 7));
vec.push_back(Item(8, 12));
vec.push_back(Item(2, 1));

// 根据Item中成员a升序排序
std::sort(vec.begin(), vec.end(),
[] (const Item& v1, const Item& v2) { return v1.a < v2.a; });

// 打印vec中的item成员
std::for_each(vec.begin(), vec.end(),
[] (const Item& item) { std::cout << item.a << " " << item.b << std::endl; });
return 0;
}

这样的写法让我们代码更加简洁、清晰,可读性更强。

  1. [captures] (optional C++20)(params) lambda-specifiers{body}
  2. [captures] (params) trailing-return-type{body}
  3. [captures] (params) {body}
  4. [captures] lambda-specifiers (optional C++20){body}

下面介绍一下lambda的四种表达式的含义,以及表达式中各个成分的,其实说白就是在自己理解的基础上翻译一下官方文档。

###四种表达式的含义
(1)完整的lambda表达式,包含了lambda表达式的所有成分。

(2)常量lambda表达式,捕获的变量都是常量,不能在lambda表达式的body中进行修改。

(3)和(2)基本一致,唯一的区别就是,lambda表达式的函数返回值可以通过函数体推导出来。一般情况函数返回值类型明确或者没有返回值的情况下可以这样写。

(4)lambda表达式的函数没有任何参数,但是可以添加lambda-specifiers,lambda-specifiers是什么我们后续再介绍。

lambda表达式各个成员的解释

captures 捕获列表,lambda可以把上下文变量以值或引用的方式捕获,在body中直接使用。

tparams 模板参数列表(c++20引入),让lambda可以像模板函数一样被调用。

params 参数列表,有一点需要注意,在c++14之后允许使用auto左右参数类型。

lambda-specifiers lambda说明符, 一些可选的参数,这里不多介绍了,有兴趣的读者可以去官方文档上看。这里比较常用的参数就是mutable和exception。其中,表达式(1)中没有trailing-return-type,是因为包含在这一项里面的。

trailing-return-type 返回值类型,一般可以省略掉,由编译器来推导。

body 函数体,函数的具体逻辑。

捕获列表

上面介绍完了lambda表达式的各个成分,其实很多部分和正常的函数没什么区别,其中最大的一个不同点就是捕获列表。我在刚开始用lambda表达式的时候,还一直以为这个没啥用,只是用一个 [] 来标志着这是一个lambda表达式。后来了解了才知道,原来这个捕获列表如此强大,甚至我觉得捕获列表就是lambda表达式的灵魂。下面先介绍几种常用的捕获方式。

[] 什么也不捕获,无法lambda函数体使用任何

[=] 按值的方式捕获所有变量

[&] 按引用的方式捕获所有变量

[=, &a] 除了变量a之外,按值的方式捕获所有局部变量,变量a使用引用的方式来捕获。这里可以按引用捕获多个,例如 [=, &a, &b,&c]。这里注意,如果前面加了=,后面加的具体的参数必须以引用的方式来捕获,否则会报错。

[&, a] 除了变量a之外,按引用的方式捕获所有局部变量,变量a使用值的方式来捕获。这里后面的参数也可以多个,例如 [&, a, b, c]。这里注意,如果前面加了&,后面加的具体的参数必须以值的方式来捕获。

[a, &b] 以值的方式捕获a,引用的方式捕获b,也可以捕获多个。

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

int main()
{
int a = 3;
int b = 5;

// 按值来捕获
auto func1 = [a] { std::cout << a << std::endl; };
func1();

// 按值来捕获
auto func2 = [=] { std::cout << a << " " << b << std::endl; };
func2();

// 按引用来捕获
auto func3 = [&a] { std::cout << a << std::endl; };
func3();

// 按引用来捕获
auto func4 = [&] { std::cout << a << " " << b << std::endl; };
func4();
}

编译器如何看待Lambda表达式

我们把lambda表达式看成一个函数,那编译器怎么看待我们协的lambda呢?

其实,编译器会把我们写的lambda表达式翻译成一个类,并重载 operator()来实现。比如我们写一个lambda表达式为

1
2
auto plus = [] (int a, int b) -> int { return a + b; }
int c = plus(1, 2);

那么编译器会把我们写的表达式翻译为

1
2
3
4
5
6
7
8
9
10
11
class LambdaClass
{
public:
int operator () (int a, int b) const
{
return a + b;
}
};

LambdaClass plus;
int c = plus(1, 2);

调用的时候编译器会生成一个Lambda的对象,并调用opeartor ()函数。(备注:这里的编译的翻译结果并不和真正的结果完全一致,只是把最主要的部分体现出来,其他的像类到函数指针的转换函数均省略)

上面是一种调用方式,那么如果我们写一个复杂一点的lambda表达式,表达式中的成分会如何与类的成分对应呢?我们再看一个 值捕获 例子。

1
2
3
int x = 1; int y = 2;
auto plus = [=] (int a, int b) -> int { return x + y + a + b; };
int c = plus(1, 2);

编译器的翻译结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class LambdaClass
{
public:
LambdaClass(int xx, int yy)
: x(xx), y(yy) {}

int operator () (int a, int b) const
{
return x + y + a + b;
}

private:
int x;
int y;
}

int x = 1; int y = 2;
LambdaClass plus(x, y);
int c = plus(1, 2);

其实这里就可以看出,值捕获时,编译器会把捕获到的值作为类的成员变量,并且变量是以值的方式传递的。需要注意的时,如果所有的参数都是值捕获的方式,那么生成的operator()函数是const函数的,是无法修改捕获的值的,哪怕这个修改不会改变lambda表达式外部的变量,如果想要在函数内修改捕获的值,需要加上关键字 mutable。向下面这样的形式。

1
2
3
int x = 1; int y = 2;
auto plus = [=] (int a, int b) mutable -> int { x++; return x + y + a + b; };
int c = plus(1, 2);

我们再来看一个引用捕获的例子。

1
2
3
int x = 1; int y = 2;
auto plus = [&] (int a, int b) -> int { x++; return x + y + a + b;};
int c = plus(1, 2);

编译器的翻译结果为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class LambdaClass
{
public:
LambdaClass(int& xx, int& yy)
: x(xx), y(yy) {}

int operator () (int a, int b)
{
x++;
return x + y + a + b;
}

private:
int &x;
int &y;
};

我们可以看到以引用的方式捕获变量,和值捕获的方式有3个不同的地方:1. 参数引用的方式进行传递; 2. 引用捕获在函数体修改变量,会直接修改lambda表达式外部的变量;3. opeartor()函数不是const的。

针对上面的集中情况,我们把lambda的各个成分和类的各个成分对应起来就是如下的关系:

捕获列表,对应LambdaClass类的private成员

参数列表,对应LambdaClass类的成员函数的operator()的形参列表

mutable,对应 LambdaClass类成员函数 operator() 的const属性 ,但是只有在捕获列表捕获的参数不含有引用捕获的情况下才会生效,因为捕获列表只要包含引用捕获,那operator()函数就一定是非const函数。

返回类型,对应 LambdaClass类成员函数 operator() 的返回类型

函数体,对应 LambdaClass类成员函数 operator() 的函数体

引用捕获和值捕获不同的一点就是,对应的成员是否为引用类型。

Copy From

https://zhuanlan.zhihu.com/p/384314474

基本概念

左值:在内存中有可以访问的地址,对象是一个左值。

右值:不可以取地址,整数10是个右值。

引用:对象的别名,没有创建新的对象,仅仅给已经存在的对象赋予了一个新的名字。

  1. 引用是对象的别名,对于引用的一切操作都是对对象的操作;
  2. 引用自身从概念上没有大小(或者就是对象的大小);但引用在传递或需要存储时,其传递或存储的大小为地址的大小。
  3. 引用必须初始化;
  4. 引用不可能重新绑定;
  5. 将指针所指向的对象绑定到一个引用时,需要确保指针非空。
  6. 任何引用类型的变量,都是左值。

四种类型引用:

|:—-|—-:|:—-:|
|类型|例子|备注|
|const lvalue refrence|Foo foo(10); const Foo& ref = foo;| |
|const rvalue refrence|const Foo& ref = Foo(10);| |
|non-const lvalue refrence|Foo foo(10); Foo& ref = foo;| |
|non-const rvalue refrence|Foo&& ref=Foo(10);|C++11才开始有|

move语义

C++11 之前,只有 copy 语意,这对于极度关注性能的语言而言是一个重大的缺失。

对move 语意的急迫需求,到了 C++11 终于被引入。其直接的驱动力很简单:在构造或者赋值时,如果等号右侧是一个中间临时对象,应直接将其占用的资源直接 move 过来(对方就没有了)。

但问题是,如何让一个构造函数,或者赋值操作重载函数能够识别出来这是一个临时变量?

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
/////////////hello.cpp/////////////////
#include <iostream>
using namespace std;
struct Foo
{
Foo(){ cout << "Foo()" << endl; }
Foo(const Foo&ref){ cout << "Foo(const Foo&)" << endl; } // copy ctor
Foo(Foo&& ref){ cout << "Foo(Foo&&)" << endl; } // move ctor
Foo& operator=(const Foo& rhs){cout << "Foo& operator=(const Foo& rhs)" << endl; } // copy assignment
Foo& operator=(Foo&& rhs){cout << "Foo& operator=(Foo&& rhs)" << endl; } // move assignment
~Foo(){ cout << "~Foo()" << endl; }
};

int main(int argc, char* argv[])
{
cout<<"=========="<<endl;
Foo foo1 = Foo();
cout<<"=========="<<endl;
foo1 = Foo();
cout<<"=========="<<endl;
Foo foo2 = foo1;
cout<<"=========="<<endl;
foo2 = foo1;
getchar();
return 1;
}

实参类型为non-const lvalue reference、const lvalue reference、 const rvalue reference可以匹配到copy ctor和copy assignment。

实参类型为non-const rvalue reference 才能匹配到 move ctor和 move assignment 。

通过这样的方式,让 Foo foo1 = Foo()或 foo1 = Foo()这样的表达式,都可以匹配到 move 语意的版本。

与此同时,让 Foo foo2 = foo1 或 foo2 = foo1 这样的表达式,依然使用 copy 语意的版本。

达到以上效果需要编译时加上-fno-elide-constructors,以此关闭编译器省略创建一个只是为了初始化另一个同类型对象的临时对象的优化。

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
struct Foo
{
Foo(int a) :a(a){}
int a;
};

void test1(Foo&& f)
{
// 对于任何类型为 右值引用的变量(当然也包括函数参数),只能由右值来初始化;
}

void test2(Foo& f)
{
// 一个右值,不能被 T& 类型的参数匹配;毕竟这种可以修改的承诺。而修改一个调用后即消失的临时
// 对象上,没有任何意义,反而会导致程序员犯下潜在的错误,因而还是禁止了最好
}

void test3(const Foo& f)
{

}

Foo f1(1);
test1(f1); // wrong cannot bind ‘Foo’ lvalue to ‘Foo&&’ 不能将一个左值绑定到右值引用
test2(f1); // ok
test3(f1); // ok

test1(Foo{1}); // ok Foo{1}是右值
test2(Foo{1}); // wrong 这种做法无意义,invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
test3(Foo{ 1 }); // ok

// ref是左值虽然其类型是右值引用;
// 一个类型为 右值引用的变量,一旦被初始化之后,临时对象的生命将被扩展,会在其被创建的 scope 内始终有效。
// 看似 ref 被定义的类型为 右值引用,但这仅仅约束它的初始化:只能从一个 右值进行初始化。
// 但一旦初始化完成,它就和一个 左值引用再也没有任何差别:都是一个已存在对象的 标识。
Foo&& ref = Foo{1};
test1(ref); // wrong ref是左值,test1的形参为右值引用,右值引用的变量只能由右值来初始化 cannot bind ‘Foo’ lvalue to ‘Foo&&’
test2(ref); // ok
test3(ref); // ok

速亡值

只有右值临时对象可以初始化右值引用变量,从而也只有右值临时变量能够匹配参数类型为 右值引用(T&&)的函数,包括 move 构造函数。

如果程序员就是想把一个左值 move 给另外一个对象,该怎么办?最简单的选择是通过 static_cast 进行类型转换:

1
2
3
4
Foo foo{10};           // foo为左值
Foo&& ref = Foo{10}; // ref为左值 类型为右值引用
Foo obj1 = static_cast<Foo&&>(foo); // move 构 造
Foo obj2 = static_cast<Foo&&>(ref); // move 构 造

我们之前说过,只有 右值,才可以用来初始化一个 右值引用类型的变量,因而也只有 右值才能匹配 move构造。

所以,static_cast<Foo&&>(foo) 表达式,肯定是一个 右值。

但同时,它返回的类型又非常明确的是一个 引用,而这一点又不符合 右值的定义。

因为,所有的右值,都必须是一个 具体类型,不能是不完备类型,也不能是抽象类型,但 引用,无论左值引用,还是右值引用,都可以是不完备类型的引用或抽象类型的引用。这是 左值才有的特征。

对于这种既有左值特征,又和右值临时对象一样,可以用来初始化右值引用类型的变量的表达式,只能将其归为新的类别。C++11 给这个新类别命名为 速亡值 (eXpiring value,简称 xvalue)。

而将原来的 右值,重新命名为 纯右值。而 速亡值和 纯右值合在一起,称为 右值,其代表的含义是,所有可以直接用来初始化 右值引用类型变量的表达式。

同时,由于 速亡值又具备左值特征:可以是不完备类型,可以是抽象类型,可以进行运行时多态。所以,速亡值又和 左值一起被归类为 泛左值(generalized lvalue, 简称 glvalue)。

From

https://zhuanlan.zhihu.com/p/595114215

C++中extern关键字用法小结

总结C++中关于extern关键字的用法。

1.变量的生明和定义中
C++语言支持分离式编译机制,该机制允许将程序分割为若干个文件,每个文件可被独立编译。为了将程序分为许多文件,则需要在文件中共享代码,例如一个文件的代码可能需要另一个文件中中定义的变量。

为了支持分离式编译,C++允许将声明和定义分离开来。变量的声明规定了变量的类型和名字,即使一个名字为程序所知,一个文件如果想使用别处定义的名字则必须包含对那个名字的声明。定义则负责创建与名字关联的实体,定义还申请存储空间。

如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化变量:

extern int i; //声明i而非定义
int j; //声明并定义i
但我们也可以给由extern关键字标记的变量赋一个初始值,但这样就不是一个声明了,而是一个定义:

extern int v = 2;
int v = 2; //这两个语句效果完全一样,都是v的定义
注意: 变量能且只能被定义一次,但是可以被声明多次。

2.在多个文件中共享const对象
默认情况下,一个const对象仅在本文件内有效,如果多个文件中出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。

某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。我们想让这类const对象像其他非常量对象一样工作,也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。

方法是对于const变量不管是声明还是定义都添加extern关键字,这样只需要定义一次就可以了:

//file1.cpp定义并初始化和一个常量,该常量能被其他文件访问
extern const int bufferSize = function();
//file1.h头文件
extern const int bufferSize; //与file1.cpp中定义的是同一个
file1.h头文件中的声明也由extern做了限定,其作用是指明bufferSize并非本文件独有,它的定义将出现在别处。

3.模板的控制实例化
当两个或者多个独立编译的源文件中使用了相同的模板并且提供了相同的模板参数时,每个文件中都会有该模板的一个实例。

在大系统中,在多个文件中实例化相同的模板的额外开销可能非常严重,在C++11新标准中,我们可以通过显式实例化来避免这种开销。一个显式实例化具有如下形式:

extern template declaration; //实例化声明
template declaration; //实例化定义
declaration是一个类或函数的声明,其中所有的模板参数都已经被替换成为模板实参。例如:

extern template class vec; //声明
template int sum(const int, const int); //定义
当编译器遇到extern模板声明时,它不会在本文件中生成实例化代码,将一个实例化声明为extern就表示承诺在程序的其他位置有该实例化的一个非extern定义。对于一个给定的实例化版本,可能有多个extern声明,但必须只有一个定义。

由于编译器在使用一个模板时自动对其实例化,因此extern声明必须出现在任何使用此实例化吧版本的代码之前。

由于以上讨论可知:extern一般是使用在多文件之间需要共享某些代码时

https://www.cnblogs.com/broglie/p/5524932.html

顶层 const 和底层 const

从 const 指针开始说起。const int* pInt; 和 int *const pInt = &someInt;,前者是 *pInt 不能改变,而后者是 pInt 不能改变。因此指针本身是不是常量和指针所指向的对象是不是常量就是两个互相独立的问题。用顶层表示指针本身是个常量,底层表示指针所指向的对象是个常量。

拷贝与顶层和底层 const

1
2
3
4
5
6
int i = 0;
int *const p1 = &i; // 不能改变 p1 的值,这是一个顶层
const int ci = 42; // 不能改变 ci 的值,这是一个顶层
const int *p2 = &ci; // 允许改变 p2 的值,这是一个底层
const int *const p3 = p2; // 靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci; // 所有的引用本身都是顶层 const,因为引用一旦初始化就不能再改为其他对象的引用,这里用于声明引用的 const 都是底层 const

相关问题:

为什么不能在一个常量对象中调用非常成员函数?

因为在默认情况下,this的类型是指向类的非常量版本的常量指针(意思是this的值不能改变,永远指向那个对象,即“常量指针”,但是被this指向的对象本身是可以改变的,因为是非常量版本,这里this相当于是顶层const),而this尽管是隐式的,它仍然需要遵循初始化规则,普通成员函数的隐式参数之一是一个底层非const指针,在默认情况下我们无法把一个底层const的this指针转化为非const的this指针,因此我们不能在常量对象上调用普通的成员函数。因此在上例中,形参列表后的const就意味着默认this指针应该是一个底层const, 类型是 const ClassName&。而非常对象却可以调用常成员函数,因为底层非const可以默认转化为底层const。

C++11新特性:enum与enum class的区别

enum class是C++11之后出来的新特性,enum在这之后称为不限定范围的枚举,enum class称为限定范围的枚举。

文件目录

  1. 防止污染空间
  2. 可以前置声明
  3. 需要强制转换

防止污染空间

1
2
3
4
5
6
7
8
9
10
enum MyEnum
{
ME_FIRST,
ME_SECOND
}
enum MyEnum2
{
ME_FIRST, ///错误 , 不限定范围的enum不允许这么做
}

通常原先的enum的枚举 在同一作用下,重复声明是不允许这样做,但是enum class却可以这样做,因为作用域不一样了。

1
2
3
4
5
6
7
8
9
10
11
enum class MyEnum
{
ME_FIRST,
ME_SECOND
}

enum class MyEnum2
{
ME_FIRST,
ME_SECOND /// 编译通过
}

可以前置声明

这是enum class最大的优势。前置声明不知道各位熟不熟悉。前置声明最明显的优势是在多个头文件编译时,某个头文件发生变化,不需要重新编译。这个与 effective C++ 的第31条款的降低文件编译依赖性 有异曲同工之妙。

需要强制转换

我认为enum class的缺点。原始的enum具有自动隐式转换的int,虽然这个在许多人眼里觉得这样做可以防止污染空间。但是在使用的时候感觉到非常的繁琐。例如,在作为参数的时候,尤其回调的时候,有时候为了方便,不想在另一个类里面包含有枚举的类,通常是以int做参数。但是这样就会有强转的情况。或者Qt 做界面的时候,数值通常会以int之类的传递,想要enum class 包含常用的数值。

那这个时候就又要强转。effective C++ 第三十一条款 尽量少使用强制类型转换。不光是因为可能会破坏数据结构,而是效率低下,尤其 dynamic_cast 效率低下。但是有些状况下,必须强转。这时候推荐 C++ 新型的强制类型转换。在More effective C++ 提到的不仅标识度高,而且更加的安全。

  • 标准C++类型转换有四种:static_cast, dynamic_cast, reinterpret_cast , const_cast。
    • static_cast:用法:static_cast (expression)
      • 该运算符将expression转换成type-id类型,但是该运算符没有进行运行时类型检查来保证转换的安全性。
  • dynamic_cast:用法:dynamic_cast (expression)
    • 该运算符将expression转换成type-id类型,其中type-id必须是类的指针、引用或者void *。type-id和expression需要是同一种表示方式(引用对引用,指针对指针)。该运算符主要用于类层次之间的上行和下行转换,或者类之间的交叉转换。类层次间的上下行转换效果和static_cast一样,但是下行转换时,dynamic_cast具有类型检查功能,比static_cast更安全。
  • reinterpret_cast:用法:reinterpret_cast (expression)
    • type-id必须是一个指针、引用、算术类型、函数指针或成员指针,可以进行指针和整数之间的相互转换,缺点:平台移植性比较差。
  • const**_cast**:用法:const_cast (expression)
    • 该运算符用来修改类型的const或volatile属性,除了const或volatile修饰外,type-id和expression类型是一样的。如常量指针变成非常量指针,且仍指向原来对象。
      ————————————————

原文链接:https://blog.csdn.net/qq_43331089/article/details/121655801

一级标题

二级标题

三级标题

四级标题

左对齐 右对齐 居中对齐
A B C
a b c
  • 无序列表1
  • 无序列表2
  1. 有序列表1
  2. 有序列表2

加粗

斜体

粗体斜体

这是一个引用

引用套嵌


Hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<cstring>
#include<cstdio>

using namespace std;

class CMyString
{
public:
CMyString(const char* pData = nullptr);
CMyString(const CMyString& str);
~CMyString(void);

CMyString& operator = (const CMyString& str);

void Print();

private:
char* m_pData;
};

这是百度的链接

这是一个图片

世界是平坦的。 我们现在知道世界是圆的。

0%