深入分析移动构造函数及其原理

2023-10-27

移动构造函数是C++11中新增加的一种构造函数,其作用是提高程序性能。今天我们就细扒一下它的工作原理,看看它是怎么提高性能的。

移动构造函数的由来

在讲解移动构造函数之间,我们先来了解一下在没有移动构造函数之前哪里有性能瓶颈吧。我们来举个例子:

#include <iostream>
#include <vector>

class A {
    public:
        A(){
            std::cout << "A construct..." << std::endl;
            ptr_ = new int(100);
        }

        A(const A & a){
            std::cout << "A copy construct ..." << std::endl;
            ptr_ = new int();
            memcpy(ptr_, a.ptr_, sizeof(int));
        }

        ~A(){
            std::cout << "A deconstruct ..." << std::endl;
            if(ptr_){
                delete ptr_;
            }
        }

        A& operator=(const A & a) {
            std::cout << " A operator= ...." << std::endl;
            return *this;
        }

        int * getVal(){
            return ptr_;
        }
    private:
        int *ptr_;
};

int main(int argc, char *argv[]){
    std::vector<A> vec;
    vec.push_back(A());
}

上面这段代码很简单对吧,就是定义了一个普通的类A。在main函数中创建一个vector,然后用A类创建一个对象,并把它放入到vector中。这样的程序在C++中是很常见,但就是这样很常见的代码确有非常大的性能问题。为什么呢?因为在将A对象放入vector时,在vector内部又创建了一个A对象,并调用了其拷贝构造函数进行了深拷贝。

我们看一下上面代码运行的结果就一目了然了,其结果如下:

A construct...          //main中创建的A对象
A copy construct ...    //vector内部创建的A对象
A deconstruct ...       //vector内部创建的A对象被析构
A deconstruct ...       //main中创建的A对象析构

上面的运行结果印证了我们之前的讨论,在vector内部确实又创建了一个A对象。如果在A对象中分配的是一个比较大的空间,且vector中要存放大量的A对象时(如 100000个),就会不断的做分配/释放堆空间的操作,这会造成多在的性能消耗呀!

有什么办法可以解决这个问题呢?这就要用到我们今天要讲的移动构造函数了。

移动构造函数的使用

从C++11开始,类中不光可以有构造函数、拷贝构造函数,还增加了一种新的构造函数即移动构造函数。移动构造函数起什么作用呢?就像它的名子一样,它可以实现指针的移动,即可以将一个对象中的指针成员转移给另一个对象。指针成员转移后,原对象中的指针成员一般要被设置为NULL,防止其再被使用。

还是以我们上面的代码为例,如果我们有了移动构造函数,那么在将A对象push到vector时,vector内部虽然还是会再分A对象,但在进行数据的拷贝时就不是深拷贝了,而变成了浅拷贝,这样就大大提高了程序的执行效率。

如何为A增加移动构造函数呢?我们来看一下代码:

class A {
    public:
        ...

        A(A && a){
            std::cout << "A move construct ..." << std::endl;
            ptr_ = a.ptr_;
            a.ptr_ = nullptr;
        }
        ...
};

在 A 类中增加上面代码即可,上面的代码看起来与普通构造函数好像没什么两样,但你细心观察可以发现该构造函数的参数是 A && a。咦!&&这在以前还真没见过对吧。它表示的是C++中的右值,也就是只有创建A对象时传入的是右值才会执行该构造函数。

对于右值后面我们还会做详细介绍,现在我们只要知道要想让这个函数起作用,就必须传给它一个右值就可以了。如么问题来了,我们这个例子中如何传递给它一个右值呢?这就要用到 std::move 函数了。

std::move可以将任何一值变成右值,所以我们不管3721,在创建A对象时直接调用std::move”造”个右值给它就好了。于是我们修改main代码如下:

int main(int argc, char *argv[]){
    std::vector<A> vec;
    vec.push_back(std::move(A()));
}

经这样修后,我们运行一下程序看现在它的结果是什么样子吧。结果如下:

A construct...          //main中创建A对象
A move construct ...    //vector内部通过移动构造函数创建A对象,减少了对堆空间的频繁操作
A deconstruct ...       //释放vector中的A对象
A deconstruct ...       //释放main中创建的A对象

从上面的结果我们可以看出我们新增加的移动构造函数确实被调用了,这样就大大减了频繁对堆空间的分配/释放操作,从而提高了程序的执行效率。这里需要注意的是,在移动构造函数操作之后原A对象的指针地址已经指向NULL了,因此此时就不能再通过其访问之前的堆空间了。

C++的左值与右值

右值是C++从C继承来的概念,最初是指=号右边的值。但现在C++中的右值已经与它最初的概念完全不一样了。在C++中右值指的的临时值或常量,更准确的说法是保存在CPU寄存器中的值为右值,而保存在内存中的值为左值。

可能有很多同学对计算机系统的底层不太了解,我们这里做一个简单的介绍。计算机是由CPU、内存、主板、总线、各种硬件等组成的,这个大家应该都清楚。而CPU又是由逻辑处理器,算术单元、寄存器等组成的。我们的程序运行时并不是直接从内存中取令运行的,因为内存相对于CPU来说太慢了。一般情况下都是先将一部分指令读到CPU的指令寄存器,CPU再从指令寄存器中取指令然后一条一条的执行。对于数据也是一样,先将数据从内存中读到数据寄存器,然后CPU从数据寄存器读数据。以Intel的CPU为例,它就包括了 EAX、EBX、ECX、EDX…多个通用寄存器,这样就可以让CPU更高效的工作。

比如说一个常数5,我们在使用它时不会在内存中为其分配一个空间,而是直接把它放到寄存器中,所以它在C++中就是一个右值。再比如说我们定义了一个变量 a,它在内存中会分配空间,因此它在C++中就是左值。那么a+5是左值还是右值呢?当然是右值对吧,因为a+5的结果存放在寄存器中,它并没有在内存中分配新空间,所以它是右值。

通过上面的描述你就应该对 C++ 中的左值和右值比较清楚了。我们来看个例子吧:

#include <iostream>

int main(int argc, char *argv[]){

    int && a = 5;  // 正确,5会被直接存放在寄存器中,所以它是右值
    int b = 10;
    int && c = b;  // 错误,b在内存中有空间,所以是左值;左值不能赋值给右值
    int && d = b + 5; // 正确,虽然 b 在内存中,但 b+5 的结果放在寄存器中,它没有在内存中分配空间,因此是右值 
}

在C++中使用&&表示右值引用,在上面的例子中,我首先将常数5赋值给右值引用a,因为常数5是右值,所以这条语句可以编译成功;紧接着我定义了变量b,因为它是左值,所以当将b赋值给右直引用c时,编译器会报错;最后一行将b+5赋值给右值引用d,由于b+5不会在内存中占用空间所以这也是右值,因此最后一句编译也没有任何问题。

接下来我们看一个有意思的情况,代码如下:

...
int && e = a;
..

这种情况是否是合法的呢?实际上当你这么做的时候编译器会报错,因为a是左值而e必须接收右值。那有没有办法将一个左值转成右值呢?这个问题我们前面其实已经回答过了,通过std::move就可以解决这个问题。我们来看一个例子:

...
int && e = std::move(a);
...

之前我们直接将a赋值给e是肯定不行的,但上面的操作编译器就不会报错了,因为通过std::move可以将一个左值转成右值。但这里有一点需要特别注意:e虽然接收的必须是右值,但它本身是左值。换句话说e是一种特殊的变量,它是只能接收右值的变量。我们再从左值的本质来看,e也是占内存空间的,所以它肯定是左值。

std::move的实现

上面我们已经看到了std::move的神奇之处,你可能很好奇std::move是如何做到的呢?实际上std::move就是一个类型转换器,将左值转换成右值而以。我们来看一下它的实现吧!

template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
    return static_case<typename remove_reference<T>::type&&>(t);
}

std::move的实现还是挺简单的就这么几行代码,但要理解这几行代码可不容易。下面我们就来对它做下详细分析。

通用引用

首先我们来看一下move的输入参数,move的输入参数类型称为通用引用类型。什么是通用引用呢?就是它既可以接收左值也可以接收右值。我们来看一下例子:

#include <iostream>

template<typename T>
void f(T&& param){
    std::cout << "the value is "<< param << std::endl;
}

int main(int argc, char *argv[]){

    int a = 123;
    auto && b = 5;   //通用引用,可以接收右值

    int && c = a;    //错误,右值引用,不能接收左值

    auto && d = a;   //通用引用,可以接收左值

    const auto && e = a; //错误,加了const就不再是通用引用了

    func(a);         //通用引用,可以接收左值
    func(10);        //通用引用,可以接收右值
}

在上面代码中有两种类型的通用引用: 一种是auto,另一种是通过模板定义的T&&。实际上auto就是模板中的T,它们是等价的。下面我们就对这段代码做下详细解读。

代码中的 a 是个左值,因为它在内存中会分配空间,这应该没什么异义;b 是通过引用。为什么呢?因为通用引用有两个条件:一,必须是T&&的形式,由于auto等价于T,所以auto && 符合这个要求;二,T类型要可以推导,也就是说它必须是个模板,而auto是模板的一种变型,因此b是通用引用。通用引用即可以接收左值,也可以接收右值,所以b=5是正确的;c不是通用引用,因为它不符合T&&的形式。所经第三行代码是错误的,右值引用只能接收右值;d是通用引用,所以给它赋值a是正确的;e不是通用引用,它多了一个const已不符合T&& 的形式,所以给它左值肯定会出错;最后两个函数调用的形参符合 T&&,又因是模板可以进行类型推导,所以是通用引用,因此给它传左值和右值它都能正确接收。

模板的类型推导

通用引用好强大呀!它既可以接收左值又可以接收右值,它是如何做到的呢?这就要讲讲模板的类型推导了。

模板的类型推导规则还是蛮复杂的,这里我们只简要说明一下,有兴趣的同学可以查一下C++11的规范。我们还是举个具体的例子吧:

template <typename T>
void f(ParamType param);

f(expr);

上面这个例子是函数模板的通用例子,其中T是根据f函数的参数推到出来的,而ParamType则是根据 T 推导出来的。T与ParamType有可能相等,也可能不等,因为ParamType是可以加修饰的。我们看下面的例子:

template <typename T>
void f(T param);

template <typename T>
void func(T& param);

template <typename T>
void function(T&& param);

int main(int argc, char *argv[]) {

    int x = 10;         // x是int
    int & rr = x;       // rr是 int &
    const int cx = x;   // cx是const int
    const int& rx = x;  // rx是const int &
    int *pp = &x;       // pp是int *

    //下面是传值的模板,由于传入参数的值不影响原值,所以参数类型退化为原始类型
    f(x);               // T是int
    f(cx);              // T是int
    f(rx);              // T是int
    f(rr);              // T是int
    f(pp);              // T是int*,指针比较特殊,直接使用

    //下面是传引用模板, 如果输入参数类型有引用,则去掉引用;如果没有引用,则输入参数类型就是T的类型
    func(x);            // T为int
    func(cx);           // T为const int
    func(rx);           // T为const int
    func(rr);           // T为int
    func(pp);           // T是int*,指针比较特殊,直接使用

    //下面是通用引用模板,与引用模板规则一致
    function(x);        // T为int&
    function(5);        // T为int
}

上面代码中可以将类型推导分成两大类:其中类型不是引用也不是指针的模板为一类; 引用和指针模板为另一类。

对于第一类其推导时根据的原则是,函数参数传值不影响原值,所以无论你实际传入的参数是普通变量、常量还是引用,它最终都退化为不带任何修修饰的原始类型。如上面的例子中,const int &类型传进去后,退化为int型了。

第二类为模板类型为引用(包括左值引用和右值引用)或指针模板。这一类在类型推导时根据的原则是去除对等数量的引用符号,其它关键字照般。还是我们上面的例子,func(x)中x的类型为 int&,它与T&放在一起可以知道T为int。另一个例子function(x),其中x为int&它与T&& 放在一起可知T为int&

根据推导原则,我们可以知道通用引用最终的结果是什么了,左值与通用引用放在一推导出来的T仍为左值,而右值与通用引用放在一起推导出来的T仍然为右值。

move 的返回类型

实际上上面通过模板推导出的T与move的返回类型息息相关的,要讲明白这一点我们先要把move的返回类型弄明白。下面我们就来讨论一下move的返回类型:

typename remove_reference<T>::type&&

move的返回类型非常奇特,我们在开发时很少会这样写,它表示的是什么意思呢?

这就要提到C++的另外一个知识点,即类型成员。你应该知道C++的类成员有成员函数、成员变量、静态成员三种类型,但从C++11之后又增加了一种成员称为类型成员。类型成员与静态成员一样,它们都属于类而不属于对象,访问它时也与访问静态成员一样用::访问。

了解了这点,我们再看move的返类型是不是也不难理解了呢?它表达的意思是返回remove_reference类的type类型成员。而该类是一个模板类,所以在它前面要加typename关键字。

remove_reference看着很陌生,接下来我们再分析一下remove_reference类,看它又起什么作用吧。其实,通过它的名子你应该也能猜个大概了,就是通过模板去除引用。我们来看一下它的实现吧。

template <typename T>
struct remove_reference{
    typedef T type;  //定义T的类型别名为type
};

template <typename T>
struct remove_reference<T&> //左值引用
{
    typedef T type;
}

template <typename T>
struct remove_reference<T&&> //右值引用
{
   typedef T type;
}

上面的代码就是remove_reference类的代码,在C++中struct与class基本是相同的,不同点是class默认成员是private,而struct默认是public,所以使用struct代码会写的更简洁一些。

通过上面的代码我们可以知道,经过remove_reference处理后,T的引用被剔除了。假设前面我们通过move的类型自动推导得到T为int&&,那么再次经过模板推导remove_reference的type成员,这样就可以得出type的类型为int了。

remove_reference利用模板的自动推导获取到了实参去引用后的类型。现在我们再回过来看move函数的时候是不是就一目了解了呢?之前无法理解的5行代码现然变成了这样:

int && move(int&& && t){
    return static_case<int&&>(t);
}

//或
int && move(int& && t){
    return static_case<int&&>(t);
}

经上面转换后,我们看这个代码就清晰多了,从中我们可以看到move实际上就是做了一个类型的强制转换。如果你是左值引用就强制转换成右值引用。

引用折叠

上面的代码我们看起来是简单了很多,但其参数int& &&int && &&还是让人觉得很别扭。因为C++编译器根本就不支持这两种类型。咦!这是怎么回事儿呢?

到这里我们就要讲到最后一个知识点引用折叠了。在C++中根本就不存 int& &&int && &&这样的语法,但在编译器内部是能将它们识别出来的。换句话说,编译器内部能识别这种格式,但它没有给我们提供相应的接口(语法)。

实际上,当编译器遇到这类形式的时候它会使用引用折叠技术,将它们变成我们熟悉的格式。其规则如下:

  • int & & 折叠为 int&
  • int & && 折叠为 int&
  • int && & 折叠为 int&
  • int && && 折叠为 int &&

总结一句话就是左值引用总是折叠为左值引用,右值引用总是折叠为右值引用。

经过这一系列的操作之后,对于一个具体的参数类型int & a,std::move就变成了下面的样子:

int && move(int& t){
    return static_case<int&&>(t);
}

这一下我们就清楚它在做什么事儿了哈!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

深入分析移动构造函数及其原理 的相关文章

  • 从 Makefile 中的 C++FLAGS 中删除一个标志?

    我有一个 Makefile 其中包含另一个设置了很多默认值的 makefile 我无法编辑包含的 makefile 并且我想更改 makefile 中 C FLAGS 的值 即使它是在包含的 makefile 中设置的 具体来说 每当 de
  • 在 JavaScript 中引用 C# 变量

    我已经阅读了很多线程 但我不明白为什么这不起作用 我正在创建一个将用作导航栏的 SharePoint Web 部件 一切都很顺利 直到我尝试在 JS 代码中引用 C 变量 这是来自 VisualWebPart1UserControl asc
  • 如何将动态数据写入 MVC 3 Razor 中的页面布局?

    我有带有 Razor 引擎的 MVC 3 C 项目 将动态数据写入 Layout cshtml 的方法和最佳实践是什么 例如 也许我想在网站的右上角显示用户名 该名称来自会话 数据库或基于用户登录的任何内容 更新 我也在寻找将某些数据渲染到
  • 从 unsigned char* 到 char* 的转换无效

    这是一个代码 1 int main int argc char argv 2 3 signed char S psc 4 unsigned char U pusc 5 char C pc 6 7 C S 8 C U 9 10 pc psc
  • 基于多线程的 RabbitMQ 消费者

    我们有一个 Windows 服务 它监听单个 RabbitMQ 队列并处理消息 我们希望扩展相同的 Windows 服务 以便它可以监听 RabbitMQ 的多个队列并处理消息 不确定使用多线程是否可以实现这一点 因为每个线程都必须侦听 阻
  • 弹出 x86 堆栈以访问函数 arg 时出现分段错误

    我正在尝试链接 x86 程序集和 C 我的C程序 extern int plus 10 int include
  • 在 T4 代码生成中,如何从引用的程序集中获取类型?

    由于 T4 在项目上下文之外运行 因此我无权访问当前程序集或其他程序集 如何注册对引用程序集的访问 然后从中获取类型 我猜您想访问项目中建筑物的程序集 我在下面的示例代码中所做的是将一个名为 TestLib 的项目添加到我的解决方案中 我将
  • C 中的双重否定:是否保证返回 0/1?

    Is x 标准保证返回0 1 请注意 我是not询问 C 其中定义了 bool 类型 是的 在 C99 中 请参阅 6 5 3 3 4 逻辑非运算符的结果 是0如果其操作数的值比较 不等于0 1如果其操作数的值比较等于 0 结果具有类型in
  • 使用正则表达式解析日志文件

    我目前正在为我们的内部日志文件 由 log4php log4net 和 log4j 生成 开发一个解析器 到目前为止 我有一个很好的正则表达式来解析日志 除了一个烦人的一点 一些日志消息跨越多行 我无法正确匹配 我现在的正则表达式是这样的
  • 公共基类打破了元组的空基类优化

    gcc 4 7 1 对元组进行空基类优化 我认为这是一个非常有用的功能 然而 这似乎有一个意想不到的限制 include
  • 如何在控制器中使用多个 DBContext

    如何在控制器中使用多个 DBContext 我尝试以不同的方式重载构造函数 一些控制器 public C1 DBContext1 a DBContext2 b DBContext3 c public C1 DBContext1 a publ
  • Bazel:将编译标志添加到默认 C++ 工具链

    我想向默认的 C 工具链添加一些编译器和链接器标志 以便我构建的所有目标 本地或导入 共享它们 我知道可以定义我自己的工具链 但我不想这样做 因为它非常复杂且容易出错 理想情况下我想要这样的东西 cc toolchain cc defaul
  • 为什么 std::atomic 比 volatile bool 慢很多?

    多年来我一直使用 volatile bool 来控制线程执行 并且效果很好 in my class declaration volatile bool stop In the thread function while stop do th
  • C# SignalR 异常 - 连接在收到调用结果之前开始重新连接

    我正在开发 2 个应用程序 第一个是 C 控制台应用程序 另一个是 Asp net Web 应用程序 我正在使用 SignalR 连接两者 这是我的 C 控制台应用程序 客户端 public class RoboHub public sta
  • 什么是 C++11 扩展 [-Wc++11-extensions]

    我需要一些帮助来了解此错误发生的位置 警告 非静态数据成员的类内初始化是 C 11 扩展 Wc 11 extensions 这是它来自的代码部分 typedef struct Hand bool straight false bool fl
  • 提升shared_from_this<>()

    有人可以用几句话概括一下如何提升shared from this lt gt 应该使用智能指针 特别是从使用绑定函数在 io service 中注册处理程序的角度来看 编辑 一些回复要求提供更多背景信息 基本上 我正在寻找 陷阱 即人们使用
  • 为什么 getch 不可移植?

    是什么使得 getch 本质上无法作为标准 C 函数包含在内 对于控制台界面来说 它是如此直观和优雅 如果没有它 要求输入单个字符总是会产生误导 因为用户可以输入多个键 更糟糕的是 您经常需要确保在读取控制台输入后清除标准输入 这甚至不是作
  • 使用 List.Contains 方法为 LINQ 构建表达式树

    Problem 我正在重构一些LINQ查询我们的 Web 应用程序中的多个报告 并且我尝试将一些重复的查询谓词移至它们自己的中IQueryable扩展方法 以便我们可以将它们重新用于这些报告以及将来的报告 正如您可能推断的那样 我已经重构了
  • 从 git 签出后 nuget dll 丢失

    I have a C solution containing different projects On those projects I have some normal nuget packages like Newtonsoft Js
  • 从最大到最小的3个整数

    我是 C 初学者 我使用 编程 使用 C 的原理与实践 第二版 问题如下 编写一个程序 提示用户输入三个整数值 然后以逗号分隔的数字顺序输出这些值 如果两个值相同 则应将它们排列在一起 include

随机推荐

  • 策略模式+Spring——让我们的代码更加高大上一点

    if else if else 代码是实际的项目代码中出现的比例还是蛮高的 特别是针对一些业务需求根据不同类型来进行不同的业务处理 针对这种业务模型 我们来试着使用策略模式结合Spring来优化我们的代码 让代码更加高大上一点 为了更好得结
  • n的阶乘求解方法

    n 的阶乘求解方法有以下三种 第一种通过递归计算 n int input def N a 定义一个函数进行阶乘计算 if a 1 return a else return N a 1 a print N n 第二种方法就是调用math库 通
  • 狂神说Mybatis最全课堂笔记

    Mybatis 自己整理的狂神说Mybatis学习笔记 环境 JDK1 8 Mysql 5 7 maven 3 6 1 IDEA 回顾 JDBC Mysql Java基础 Maven Junit SSM框架 配置文件 最好的方式 看官方文档
  • 小程序,压缩图片

    我们在小程序上 直接上传手机相册及拍照的图片时 因为图片过大 如10m 手机端不像我们再计算机上传输那么快 也没那么稳定 解决办法 在图片上传前压缩图片 压缩图片就是将图片尺寸 图片质量降低 把这两个指数降低到合适的规格 1 首先在 wxm
  • RK3399 Linux-SDK mipi屏幕驱动及调试

    一 流程及通路 我接触到的三款mipi屏幕 基本的点亮流程都是很一致的 就是背光使能 背光点亮 屏幕使能 reset引脚按指定时序 波形拉高或拉低 初始化序列命令发送 3399的linuxSDK中 包含一种类似通用的屏幕驱动 本文档以使用此
  • [Eagle API]使用python打印eagle指定文件夹下的所有子文件名

    api https www yuque com augus gsjgn eagle api pq0y2y 官方api源码 var requestOptions method GET redirect follow fetch http lo
  • mojo安装

    docker安装mojo 官网 https developer modular com login 很奇怪登录页面不显示 类似于网站劫持 docker 安装mojo带jupyterlab的方式 https hub docker com r
  • const_cast

    const cast是一种C 运算符 主要是用来去除复合类型中const和volatile属性 没有真正去除 变量本身的const属性是不能去除的 要想修改变量的值 一般是去除指针 或引用 的const属性 再进行间接修改 用法 const
  • 烂泥:查看服务器的BIOS是否开启CPU虚拟化

    本文由秀依林枫提供友情赞助 首发于烂泥行天下 有关CPU是否支持虚拟化 我们可以通过相关的命令和软件进行查看 在windows系统下 我们可以使用CPU Z这个软件 如下图 在linux系统下 我们可以通过查看 proc cpuinfo文件
  • Python图像处理之图片文字识别(OCR)

    OCR与Tesseract介绍 将图片翻译成文字一般被称为光学文字识别 Optical Character Recognition OCR 可以实现OCR 的底层库并不多 目前很多库都是使用共同的几个底层OCR 库 或者是在上面进行定制 T
  • Python自动化处理邮件

    Python处理QQ邮箱邮件 以Chrome浏览器为例 需下载chromedriver exe下载地址 根据自己电脑上的Chrome版本下载相应的chromedriver exe 打开的Chrome浏览器主界面的地址栏输入网址 chrome
  • 计算机原理-数据

    数据 二进制 十进制 人类use 十六进制 2 4 16 是权 1011 011 2 3 0 2 2 2 1 2 0 0 2 1 1 2 2 1 2 3 8 0 2 1 0 0 25 0 125 27 375 七进制转十进制 权不一样 60
  • Centos7离线安装MySQL

    前言 离线安装的方式 很多人 包括我自己是很难的 yum的方式简单轻松 作者之前离线安装oracle的时候 因为关键的rpm包不齐 通过查安装日志一个一个的下离线包 反复安装3天才完成离线模式安装 今天总结下MySQL的离线安装步骤 环境
  • 【Unreal】TArray与std::vector之间转换

    std vector data 返回一个指向内存数组的直接指针 该内存数组由vector内部用于存储其拥有的元素 TArray GetData 同理 FMemory Memcpy 则将指定位置和大小的数组的内存空间拷贝 覆盖 到另一处指定的
  • 第五周课程总结&试验报告(三)

    Java实验报告 班级 计科二班 学号 20188423 姓名 邹健 完成时间 2019 9 27 评分等级 实验三 String类的应用 一 实验目的 1 掌握类String类的使用 2 学会使用JDK帮助文档 二 实验内容 1 已知字符
  • Vcpkg介绍及使用

    Vcpkg用于在Windows Linux Mac上管理C和C 库 极大简化了第三方库的安装 它由微软开源 源码地址 https github com Microsoft vcpkg 最新发布版本为2023 04 15 Release 它的
  • 【译】Filed Play:简介

    引子 在尝试数学函数可视化的时候 发现了一个有趣的库 Field Play 对 README 中的说明进行部分翻译记录 做个初步了解 Origin My GitHub What 让我们为网格上的每个点指定一个向量 1 0 这意味着我们有一个
  • JavaScript Function、函数声明、函数表达式

    JavaScript Function 函数声明 函数表达式 Function 对象 Function 构造器会创建一个新的 Function 对象 在 JavaScript 中每个函数都是一个 Function 对象 使用 Functio
  • 智能指针之unique_ptr(删除器、尺寸)08

    一 unique ptr 1 unique ptr删除器 unique ptr和shared ptr一样 默认删除器都是使用delete 所以当我们创建的是一个数组或者文件这些时 显然delete是无法有效回收的 删除器是一个可调用对象 其
  • 深入分析移动构造函数及其原理

    移动构造函数是C 11中新增加的一种构造函数 其作用是提高程序性能 今天我们就细扒一下它的工作原理 看看它是怎么提高性能的 移动构造函数的由来 在讲解移动构造函数之间 我们先来了解一下在没有移动构造函数之前哪里有性能瓶颈吧 我们来举个例子