【C++学习】C++11新特性(第三节)——可变参数模板, lambda表达式与function包装器

本文主要是介绍【C++学习】C++11新特性(第三节)——可变参数模板, lambda表达式与function包装器,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 文章前言
  • 一.可变参数模板
      • 1.什么是可变参数模板
      • 2.获取可变参数模板里参数包的方法
      • 3.可变参数模板在容器中的引用
  • 二. lambda表达式
      • 1. lambda表达式的由来
      • 2. lambda表达式
        • 1.lambda表达式语法
        • 2. 捕获列表说明
      • 3.函数对象与lambda表达式
  • 三.包装器
      • 1.***function包装器***
        • 2.普通函数,静态成员函数与非静态成员函数的包装
      • 2.bind函数

文章前言

本篇文章是C++11新特性的最后一节,主要会讲解到模板的可变参数lambda表达式function包装器的相关知识及其使用场景。

一.可变参数模板

C++11的新特性可变参数模板能够创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,而可变参数模板可以含有不固定数量的模板参数。在有些场景下较方便。

1.什么是可变参数模板

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>      //名字Args是可以改变的,自己取
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了 0到N(N>=0)个模版参数。

template <class ...Args>     
void ShowList(Args... args)
{}
int main()
{ShowList();   //可以没有参数ShowList(1);   //可以是1个参数ShowList(1.1,std::string("hello"));     //可以是2个参数....                   //可以是N个参数//编译器实例化后void ShowList(){}void ShowList(int ){}void ShowList(double ,string ){}}

在这里插入图片描述

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,下面我们讲解一些方法来获取参数包的值。

那为什么不能args[i]这样方式获取可变参数呢?
答:C语言的可变参数是运行时解析,可以在运行时用运行逻辑解析,但是这里是模板,是在编译时解析。

2.获取可变参数模板里参数包的方法

  1. 利用sizeof()可以计算出参数包里面的参数个数
template <class ...Args>      
void ShowList(Args... args)
{cout << sizeof...(args) << endl;   //可以计算出参数包里面的参数个数
}
int main()
{ShowList();   ShowList(1);   ShowList(1.1, std::string("hello"));     //运行结果:// 0// 1// 2
}
  1. 可以用递归函数方式展开参数包(编译时递归解析)
//终止函数
template<class T>
void _ShowList(T val)
{cout << val << endl;
}
template <class T,class ...Args>
void _ShowList(const T& val,Args... args)
{cout << val << " ";_ShowList(args...);
}
template <class ...Args>
void ShowList(Args... args)
{_ShowList(args...);
}
int main()
{ShowList(1,1,2,3,4,5);//运行结果://1 1 2 3 4 5return 0;
}

解析:
在这里插入图片描述
在这里插入图片描述

3.可变参数模板在容器中的引用

例如:在list中的尾插函数,emplace系列就是利用了可变参数模板。
如下图:
在这里插入图片描述
他们之间的区别:
push_back()与emplace_back都是尾插,不同的是,push_back()只能接受一个参数,而emplate可以接受多个参数(这里不是插入多个值)。
他们在这种场景下没有区别:

我们自己实现简单的类类似测试:
简易版string类代码:

namespace Test
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "拷贝构造" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移动构造string(string&& s){cout << "移动拷贝" << endl;swap(s);}// 拷贝赋值// s2 = tmpstring& operator=(const string& s){cout << "赋值拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动赋值string& operator=(string&& s){cout << "移动赋值" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; };
}

插入的类型的参数的是单个值时:

int main()
{std::list<Test::string> lt1;Test::string s1("xxxx");lt1.push_back(s1);lt1.push_back(move(s1));cout << "---------------------------------------------" << endl;Test::string s2("xxxx");lt1.emplace_back(s2);lt1.emplace_back(move(s2));cout << "---------------------------------------------" << endl;lt1.push_back("xxxx");lt1.emplace_back("xxxx");cout <<"---------------------------------------------"<< endl;return 0;
}
//运行结果:
//构造
//拷贝构造
//移动拷贝
//--------------------------------------------
//构造
//拷贝构造
//移动拷贝
//--------------------------------------------
//构造
//移动拷贝
//构造
//--------------------------------------------

总结
根据上面的测试可以发现:(对于插入类型的参数是单个的来说)

  • 当插入的是有名对象和匿名对象来讲,他们之间没有区别;
  • 当直接插入对象的参数时,区别不是很大,对于push_back只是多了一个构造;

当插入的类型的参数是多个值时,比如piar类型
如:

int main()
{std::list<pair<Test::string, Test::string>> lt2;pair<Test::string, Test::string> kv1("xxxx", "yyyy");lt2.push_back(kv1);lt2.push_back(move(kv1));cout <<"---------------------------------------------" << endl;pair<Test::string, Test::string> kv2("xxxx", "yyyy");lt2.emplace_back(kv2);lt2.emplace_back(move(kv2));cout << "---------------------------------------------" << endl;lt2.emplace_back("xxxx", "yyyy");cout << "---------------------------------------------" << endl;return 0;
}
//运行结果:
//构造
//构造
//拷贝构造
//拷贝构造
//移动拷贝
//移动拷贝
//---------------------------------------------
//构造
//构造
//拷贝构造
//拷贝构造
//移动拷贝
//移动拷贝
//---------------------------------------------
//构造
//构造
//---------------------------------------------

总结:
根据上面的测试可以发现:(对于插入类型的参数是多个的来说)

  • 当插入的是有名对象和匿名对象来讲,他们之间没有区别;
  • push_back不能直接传入类型的参数(因为类插入对象的类型的参数为多个);
  • 但是emplace_back可以直接传插入对象的参数(因为emplace_back利用的是可变参数模板),并且直接构造。在这方面上,emplace_back有优势;

emplace系列在插入操作(如上面研究的),直接传参数的时候效率更高,可以直接构造,当传有名对象与匿名对象时,差别不大;

二. lambda表达式

1. lambda表达式的由来

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

例如:

struct ComparePriceLess
{bool operator()(const Goods& gl, const Goods& gr){return gl._price < gr._price;}
};
struct ComparePriceGreater
{bool operator()(const Goods& gl, const Goods& gr){return gl._price > gr._price;}
};
struct Goods
{string _name;    // 名字double _price;   // 价格int _evaluate;   // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());}
  • 随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

2. lambda表达式

1.lambda表达式语法

lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
[捕捉列表] (参数列表) mutable -> 返回值类型 { 函数体}

  1. lambda表达式各部分说明
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。不可以省略
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。可以省略。使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。可省略。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。不可省略。
    注意:
    在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

假设我们要写一个ADD(实现两个int相加)的 lamdba 就应该这样写

[](int x, int y)->int {return x + y; };
[](int x, int y)->{return x + y; };  //返回值可以省略,编译器自动推

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
例如想要调用上面的ADD函数:

int main()
{auto func = [](int x, int y)->int {return x + y; };cout << func(3, 4);return 0;
}
//运行结果:
//  7

有了lambda表达式,上面的比较问题就可以这样解决:

int main()
{//[capture-list] (parameters) mutable -> return-type { statement }vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };//按照水果的价格排序sort(v.begin(), v.end(), [](const Goods& G1, const Goods& G2)->bool {return G1._price > G2._price; });//按照水果的销量排序sort(v.begin(), v.end(), [](const Goods& G1, const Goods& G2)->bool {return G1._evaluate > G2._evaluate; });
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个局部的匿名函数对象。

2. 捕获列表说明

捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var。
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
  • [&var]:表示引用传递捕捉变量var。
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)。
  • [this]:表示值传递方式捕捉当前的this指针。

代码演示:

  1. 传值捕捉,传值+mutable,传引用捕捉
int main()
{int x = 10,y = 20;//传值捕捉   捕捉到的是的变量的拷贝 并且不能被修改 auto ADD = [x, y]{ //x++;                //不能被修改,会报错   return x + y; };  //如果想要修改,就必须加mutable  auto ADD = [x, y]()mutable{x+=10;   //函数体里面的x是被捕捉x的拷贝,这里对x的改变不影响外面的xreturn x + y; };   //返回的是30//如果想要变量本身被修改,就要传引用捕捉auto ADD = [&x, &y]{x += 10;           //x变为20return x + y; };   //返回的是40return 0;
}
  1. 传值捕捉当前域所有对象+传引用捕捉当前域所有对象+混着使用
int main()
{//传值捕捉当前域的所有对象,也不能被修改int x = 10, y = 20, m = 1, n = 2;auto ADD = [=] {//x++;   会报错,不能修改return x + y + m + n; };    //传引用捕捉当前域的所有对象auto ADD = [&] {x += 10;return x + y + m + n; };   //可以混着使用//传值捕捉所有,传引用捕捉xauto ADD = [ = , &x ] {x++;   return x + y + m + n; };    return 0;
}

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。
    [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他所有变量。
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都 会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同
  • lambda对象紧禁了默认构造,但是可以拷贝构造

3.函数对象与lambda表达式

  1. lambda表达式的底层是怎么是实现的呢?

我们通过汇编简单的看一看:
测试代码:

int main()
{auto func1 = [] {cout << "hello world"; };func1();return 0;
}

在这里插入图片描述
我们根据上面的汇编可以看出,lambda表达式底层是通过调用operator()实现的,和仿函数一样。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator(),operator()的参数可以理解为就是捕捉列表的参数,捕捉就像是它的成员变量,访问捕捉就像是访问自己的成员变量。

扩展:思考:这两个lambda表达式类型相同吗?

auto f1 = [] {cout << "hello world"<<endl; };
f1();
auto f2 = [] {cout << "hello world"<<endl; };
f2();

我们通过看汇编(如下图):
在这里插入图片描述

从上图可以清楚的看到他们虽然实现的内容这些完全一样,但是他们底层是两个类型,这里也说明了为什么 lambda表达式之间不能相互赋值。只能通过auto去推演。

  • decltype的一个使用场景:
    假设要创建一个优先级队列,用自己的比较方式去实现大小堆的控制,这时候就需要在创建优先级队列时自己传一个函数的类型(仿函数),但是如果我们使用lambda表达式的话,就不知道它的类型,这时候只能使用decltype来推演类型了。
    举个例子:
auto func1 = [](const Date* p1, const Date* p2){//假设实现了日期类的大于比较};
int main()
{auto func1 = [](const Date* p1, const Date* p2) {//假设实现了日期类的大于比较};//priority_queue<Date* ,vector<Date*>, decltype(func1)> p1;  编不过//编译不过的原因为:lambda对象紧禁了默认构造,将它类型传给p1,会构造priority_queue<Date*, vector<Date*>, decltype(func1)> p1(func1);  //传func1给p1,这样就不会构造了,支持拷贝构造return 0;
}

三.包装器

1.function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数指针cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
//运行结果:
//count:1
//count:00007FF60E9505E0
//5.555
//count:1
//count:00007FF60E9505F0
//3.70333
//count:1
//count:00007FF60E9505F4
//2.7775

根据上面的代码运行结果进行分析,如果编译器只实例化了一份函数的话,静态变量count应该只有一份,并且地址一样,但是根据运行结果来看,编译器会将useF函数模板实例化了三份。
那怎么才能让编译器不实例化三分,而只是实例化一份呢?

有了包装器就可以很好的解决上面的问题 :

//std::function在头文件<functional> // 类模板原型如下 
template<class T> function;     
undefined template <class Ret, class...Args> 
class function<Ret(Args...)>; 
//模板参数说明: 
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参

解决方法:

int main()
{// 函数指针function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lamber表达式function<double(double)> func3 = [](double d)->double { return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}
//运行结果:
//count:1
//count:00007FF6404505E0
//5.555
//count:2
//count:00007FF6404505E0
//3.70333
//count:3
//count:00007FF6404505E0
//2.7775

图解:
在这里插入图片描述

2.普通函数,静态成员函数与非静态成员函数的包装
  1. 对于普通的函数包装,直接包装即可;
  2. 对于静态成员函数,只需要在普通函数的包装中,加一个指定类域;
  3. 对于非静态成员函数,必须在函数前面加&(静态成员函数可加可不加),并且在包装的参数里面多传一个类类型,因为成员函数的参数列表中隐藏了一个this指针,成员函数也需要用类对象的指针或则对象去调用。

代码演示:

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
int f(int a, int b)
{return a + b;
}
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{//普通函数function<int(int, int)> func1 = f;  //函数cout << func1(1, 1) << endl;//静态成员函数function<int(int, int)> func2 = Plus::plusi; //&可加可不加cout << func2(2, 2) << endl;//非静态成员函数//非静态成员函数需要对象的指针或则对象进行调用,所以非静态成员函数需要多一个参数//第一种方式Plus plus;function<int(Plus*, double, double)> func3 = &Plus::plusd; //&必须加cout << func3(&plus, 1.1 ,11.11) << endl;//第二种方式function<int(Plus, double, double)> func4 = &Plus::plusd;  //&必须加cout << func4(Plus(),11.11, 11.11) << endl;
}

2.bind函数

std::bind函数 定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象(返回值)来“适应”原对象的参数列表。

// 原型如下:
template <class Fn, class... Args>bind (Fn&& fn, Args&&... args);template <class Ret, class Fn, class... Args> // with return type (2) bind (Fn&& fn, Args&&... args);   //fn是可调用对象    //Ret返回值(是一个可调用对象)
  • 作用一:调整可调用对象的参数的顺序(价值不大)
    代码演示:

int Sub(int x, int y)
{return x - y;
}
int main()
{cout << Sub(2, 1) << endl;   //1auto func1 = bind(Sub, placeholders::_2, placeholders::_1);cout << func1(2, 1)<<endl;   //-1return 0;
}
  • 作用二:调整可调用对象的参数的个数
#define N 100
int Mul(int n,int x, int y)
{return n*(x + y);
}
int main()
{cout << Mul(N, 2, 1);cout << Mul(N, 4, 5);//假设要计算N*(x+y)的值 因为N是确定不变的值,每次传参都要传,麻烦,就可以用bindauto func1 = bind(Mul, N,placeholders::_1, placeholders::_2);cout << func1(2, 1) << endl;   //只需要传两个参数就行了return 0;
}

这篇关于【C++学习】C++11新特性(第三节)——可变参数模板, lambda表达式与function包装器的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897275

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

Python19 lambda表达式

在 Python 中,lambda 表达式是一个小型匿名函数,通常用于实现简单、单行的函数。lambda 函数可以接受任意数量的参数,但只能有一个表达式。 基本语法: lambda arguments: expression 这里,arguments 是传递给 lambda 的参数,expression 是关于这些参数的表达式,它的计算结果就是 lambda 函数的返回值。 使用

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J