【C++从0到王者】第三十九站:C++11(全文三万字,超详解)(下)

简介: 【C++从0到王者】第三十九站:C++11(全文三万字,超详解)

2.lambda表达式语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

各部分说明

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

如下代码所示

int main()
{
  int a = 0;
  int b = 2;
  auto add1 = [](int x, int y) ->int {return x + y; };
  auto add2 = [](int x, int y) {return x + y; };
  cout << add1(a, b) << endl;
  cout << add2(a, b) << endl;
  return 0;
}

除此以外还可以写多行语句等等

但是要注意的是,我们如果直接去调用其他的局部的lambda表达式的话,会报错的

但是如果是一个全局的,是可以的

那么有没有办法可以使用局部的呢?其实是有的,那就是捕捉列表,比如下面的代码

那么捕捉列表有哪些捕捉方式呢?

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

[var]:表示值传递方式捕捉变量var,捕捉后为const类型

[=]:表示值传递方式捕获所有父作用域中的变量(包括this),捕捉后为const类型

[&var]:表示引用传递捕捉变量var

[&]:表示引用传递捕捉所有父作用域中的变量(包括this)

[this]:表示值传递方式捕捉当前的this指针

注意事项:

a. 父作用域指包含lambda函数的语句块

b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割

比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量

[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量

c. 捕捉列表不允许变量重复传递,否则就会导致编译错误

比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

d. 在块作用域以外的lambda函数捕捉列表必须为空

e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都 会导致编译报错。

f. lambda表达式之间不能相互赋值,即使看起来类型相同

首先需要特别注意的是捕捉列表出来的是不可以修改的,如下代码所示

如果真的想修改捕捉到的变量,可以加上mutable

不过这里的mutable仅仅只是让这个变量可以被修改了。但是这里是传值的,里面的修改并不会影响外面的。实际上这个用处不大

如果想修改外面的,可以使用引用捕捉

我们还可以试一下下面的代码

int main()
{
  int a = 0;
  int b = 1;
  int c = 2;
  int d = 3;
  int e = 4;
  cout << a << " " << b << " " << c << " " << d << " " << e << endl;
  auto func = [&] {
    a++;
    b++;
    c++;
    d++;
    e++;
  };
  func();
  cout << a << " " << b << " " << c << " " << d << " " << e << endl;
  return 0;
}

除此之外,还可以混合着来,下面代码是错的,因为a不可以被修改,意思是除了a以外所有的变量使用引用捕捉,而a用值传递的方式捕捉。而a值捕捉以后是不可被修改的,所以错误

而且引用捕捉是可以捕捉const变量的。只不过捕捉以后无法修改,但是可以进行访问,他们的地址都是一样的

在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

3.函数对象与lambda表达式

当我们写出这样的代码的时候,我们会发现报错了

于是我们打印出他们类型来观察一下

我们会发现其实这两个对象的类型其实是不一样的。所以当然无法赋值。

其实lambda表达式的底层就是仿函数,这里的f1,f2都是一些仿函数对象,只不过他们的类型是编译器自己生成的。我们看不到而已。

我们这里通过f1去调用的其实都是仿函数的调用

我们可以用如下代码来进行观察

class Rate
{
public:
  Rate(double rate) : _rate(rate)
  {}
  double operator()(double money, int year)
  {
    return money * _rate * year;
  }
private:
  double _rate;
};
int main()
{
  // 函数对象
  double rate = 0.49;
  Rate r1(rate);
  r1(10000, 2);
  // lamber
  auto r2 = [=](double monty, int year)->double {return monty * rate * year; };
  r2(10000, 2);
  return 0;
}

下面是对于仿函数的,可以看到调用了构造函数和operator()

下面是对于lambda表达式的,我们也可以看到调用了构造函数和operator()

所以lambda表达式底层其实就是仿函数,就像范围for的底层是迭代器一样

八、可变参数模板

1.可变参数模板

我们知道,printf这个函数就是一个可变参数的

这里的三个点就代表了,可以写任意个参数

这里面其实就相当于有一个数组把这个实参存起来,然后printf会依次访问数组里面的元素。

以上就是函数的可变参数

而模板参数和函数参数是很类似的,模板参数传递的是类型,函数参数传递的是对象。函数的可变参数是传多个对象,而模板的可变参数就是可以传多个类型

Args是一个模板参数包,args是一个函数形参参数包

声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数。

template<class ...Args>
void Showlist(Args... args)
{}

如下代码所示可以计算出有多少个可变参数

template<class T, class ...Args>
void Showlist(T value, Args... args)
{
  cout << sizeof...(args) << endl;
}
int main()
{
  Showlist(1);
  Showlist(1, 1.1);
  Showlist(1, 1.1, 1.2);
  Showlist(1, 1.1, 1.3, 1.2);
  return 0;
}

我们还需要注意的是,如果我们想要访问参数包的话

不可以想当然的以为这样可以取出参数包的内容,这样是错的代码,编译不通过。

我们需要这样访问

template<class T>
void Showlist(T value)
{
  cout << value << endl;
}
template<class T, class ...Args>
void Showlist(T value, Args... args)
{
  cout << value << " ";
  Showlist(args...);
}
int main()
{
  Showlist(1);
  Showlist(1, 1.1);
  Showlist(1, 1.1, 1.2);
  Showlist(1, 1.1, 1.3, 1.2);
  return 0;
}

它这里其实就用了一个编译时的递归。

一开始会将第一个参数传给T,然后剩下的参数包都传给下一层函数。最上面就是结束条件。

在库里面就有一个类似的接口

不过它的参数只有一个参数包,那么应该如何传递呢?其实我们可以使用一个子函数

void _Showlist()
{
  cout << endl;
}
template<class T, class ...Args>
void _Showlist(T value, Args... args)
{
  cout << value << " ";
  _Showlist(args...);
}
template<class ...Args>
void Showlist(Args... args)
{
  _Showlist(args...);
}
int main()
{
  Showlist(1);
  Showlist(1, 1.1);
  Showlist(1, 1.1, 1.2);
  Showlist(1, 1.1, 1.3, 1.2);
  return 0;
}

其实像上面的几个函数组合起来,就相当于一个C++版本的print了,可以自动打印

关于这个打印,其实还可以这样玩

这里的核心逻辑就是,在Showlist中,会将参数包传给PrintArg这个函数,这个函数只会解析第一个参数,后面的逗号是一个逗号表达式,用于初始化数组,后面的三个点就是有几个参数就会相当于调用了几次PrintArg这个函数

void Showlist()
{
  cout << endl;
}
template<class T>
void PrintArg(T t)
{
  cout << t << " ";
}
template<class ...Args>
void Showlist(Args... args)
{
  int a[] = { (PrintArg(args),0)... };
  cout << endl;
}
int main()
{
  Showlist(1);
  Showlist(1, 1.1);
  Showlist(1, 1.1, 1.2);
  Showlist(1, 1.1, 1.3, 1.2, string("xxxxx"));
  return 0;
}

运行结果是

不过这段代码其实还可以稍微简化一下

不过上面的方法是一次一次取出来的,能不能一次性全部取出来呢?方便我们进行初始化等操作

如下代码所示,这样的话,我们就可以通过Create函数去调用各种情况的构造函数了,还有拷贝构造函数也可以去调用。特别灵活

class Date
{
public:
  Date(int year = 0, int month = 0, int day = 0)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
private:
  int _year;
  int _month;
  int _day;
};
template<class ...Args>
Date* Create(Args... args)
{
  Date* ret = new Date(args...);
  return ret;
}
int main()
{
  Date* p1 = Create();
  Date* p2 = Create(2023);
  Date* p3 = Create(2023, 10);
  Date* p4 = Create(2023, 10, 22);
  Date d(2023, 10, 1);
  Date* p5 = Create(d);
  return 0;
}

2.emplace系列

如下接口所示,在C++11以后,很多库里面都加上了emplace系列接口

我们先看以下代码

int main()
{
  std::list< std::pair<int, char> > mylist;
  mylist.push_back(make_pair(40, 'd'));
  mylist.push_back({ 50, 'e' });
  for (auto e : mylist)
    cout << e.first << ":" << e.second << endl;
  return 0;
}

这些代码都是我们之前的正常的尾插

现在有了emplace以后,我们就可以下面的写法了。

这是因为与前面的Date的实现是一样的,通过可变参数模板实现的。

int main()
{
  std::list< std::pair<int, char> > mylist;
  // emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
  // 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
  mylist.emplace_back(10, 'a');
  mylist.emplace_back(20, 'b');
  mylist.emplace_back(make_pair(30, 'c'));
  mylist.push_back(make_pair(40, 'd'));
  mylist.push_back({ 50, 'e' });
  for (auto e : mylist)
    cout << e.first << ":" << e.second << endl;
  return 0;
}

也许我们也会听说emplace_back的效率更高一些,但是在上面的场景是看不出来的,下面的场景可以感受出来

int main()
{
  // 下面我们试一下带有拷贝构造和移动构造的Sim::string,再试试呢
  // 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
  // 是先构造,再移动构造,其实也还好。
  std::list< std::pair<int, Sim::string> > mylist;
  mylist.emplace_back(10, "sort");
  mylist.emplace_back(make_pair(20, "sort"));
  mylist.push_back(make_pair(30, "sort"));
  mylist.push_back({ 40, "sort" });
  return 0;
}

虽然emplace效率稍高一些,但是其实还好,因为并没有太大差距,因为移动拷贝的效率很低

要是真要说的,反倒是内置类型和浅拷贝的效率可以提高一些,因为深拷贝的量级基本在一个量级。而浅拷贝是就显得差距比较大了

class Date
{
public:
  Date(int year = 0, int month = 0, int day = 0)
    :_year(year)
    , _month(month)
    , _day(day)
  {
    cout << "Date(int year = 0, int month = 0, int day = 0)" << endl;
  }
  Date(const Date& d)
    :_year(d._year)
    , _month(d._month)
    , _day(d._day)
  {
    cout << "Date(const Date& d)" << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  std::list< Date > mylist;
  mylist.push_back(Date(2023, 10, 23));
  cout << endl;
  mylist.emplace_back(2023, 10, 23);
  return 0;
}

其中最为核心的原因就是emplace可以传参数包,这就导致了它可以传对象,可以传对象过去。而push_back只能传对象

九、新的类功能

1.新增的默认成员函数

默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。

而C++11 新增了两个:移动构造函数和移动赋值运算符重载。

针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:

  • 如果你没有自己实现移动构造函数,且同时没有实现析构函数 、拷贝构造、拷贝赋值重载。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
  • 如果你没有自己实现移动赋值重载函数,且同时没有实现析构函数 、拷贝构造、拷贝赋值重载,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
  • 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

我们可以用下面的代码来进行验证

class Person
{
public:
  Person(const char* name = "", int age = 0)
    :_name(name)
    , _age(age)
  {}
  //Person(const Person& p) :_name(p._name)
  //  , _age(p._age)
  //{}
  //Person& operator=(const Person& p)
  //{
  //  if (this != &p)
  //  {
  //    _name = p._name;
  //    _age = p._age;
  //  }
  //  return *this;
  //}
  // ~Person()
  // {}
private:
  Sim::string _name;
  int _age;
};
int main()
{
  Person s1;
  Person s2 = s1;
  Person s3 = std::move(s1);
  Person s4;
  s4 = std::move(s2);
  return 0;
}

这是利用了编译器自己生成的移动拷贝

当这些类都写了的时候,调用深拷贝

如果屏蔽三个中的一个,依然是深拷贝

事实上,一般而言,我们只需判断拷贝构造、赋值重载、析构中的任意一个就可以了。因为他们三个如果要实现一般都是一起实现的,共存亡的。因为一旦写析构了必然涉及到资源的释放,涉及到了资源就必然涉及到了深拷贝。所以我们一般只要其中的一个没写那么三个基本上都不会写的。

2.一些新的关键字

  • **类成员变量初始化:**C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化。
  • 强制生成默认函数的关键字default:
    C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成

比如如下的例子

  • 禁止生成默认函数的关键字delete:
    如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

  • 继承和多态中的final与override关键字:final用于防止类被继承,不能被重写。override用于必须重写该虚函数

十、包装类

1.function包装器

lambda表达式很好用,但是它也有一些缺陷,那就是他的类型我们不知道,所以导致传参的时候非常难弄

面对C++中各种各样的类型,比如函数指针,仿函数,lambda表达式,有没有什么办法可以将他们统一起来呢?

我们先看下面的代码

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;
}

从运行结果上来看,这个模板被实例化成了三份

可见他们的类型各不相同,我们能否找一种办法使得只实例化成一份呢?

也就是说,将可调用对象存储到一个容器中

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

所以我们可以改善前面的代码

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;
  function<double(double)> f1 = f;
  function<double(double)> f2 = Functor();
  function<double(double)> f3 = [](double d)->double { return d / 4; };
  vector<function<double(double)>> v = { f1,f2,f3 };
  double n = 3.3;
  for (auto f : v)
  {
    cout << f(n++) << endl;
  }
  return 0;
}

所以这里就完美的解决了可调用对象的类型问题

我们可以将包装器用于下面题目的改造

逆波兰表达式求值

class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<int> st;
        map<string,function<int(int,int)>> cmdFuncMap = {
            {"+",[](int left, int right){return left + right;}},
            {"-",[](int left, int right){return left - right;}},
            {"*",[](int left, int right){return left * right;}},
            {"/",[](int left, int right){return left / right;}}
        };
        for(auto& str : tokens)
        {
            if(cmdFuncMap.count(str))
            {
                int right = st.top();
                st.pop();
                int left = st.top();
                st.pop();
                st.push(cmdFuncMap[str](left,right));
            }
            else
            {
                st.push(stoi(str));
            }
        }
        return st.top();
    }
};

而且这样改造之后,如果要添加运算,只需要去往map里面加数据即可,不需要做出其他改动

还是对于前面的代码,有了包装器,我们就可以将类只实例化出一份,因为可以统一成一个类型了。

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()
{
  function<double(double)> f1 = f;
  function<double(double)> f2 = Functor();
  function<double(double)> f3 = [](double d)->double { return d / 4; };
  cout << useF(f1, 11.11) << endl;
  // 函数对象
  cout << useF(f2, 11.11) << endl;
  // lamber表达式
  cout << useF(f3, 11.11) << endl;
  return 0;
}

2.bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2) 
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中的参数。

arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推

下面就是一个使用bind的例子

int Sub(int x, int y)
{
  return x - y;
}
int main()
{
  function<int(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2);
  cout << rSub1(10, 5) << endl;
  function<int(int, int)> rSub2 = bind(Sub, placeholders::_2, placeholders::_1);
  cout << rSub2(10, 5) << endl;
  return 0;
}

运行结果为

我们来详解分解一下这段代码

其实placeholders是一个命名空间,里面有很多的变量,我们先不用仔细考虑。会用就可以了

在rSub这一层第几个参数传递给下标是几的参数,但是在由进一步传入Sub的时候,是按照顺序传递的,所以导致了传递顺序的不同,从而导致了结果的不同。

所以说,这个bind的价值就是交换传递的参数顺序。

因为有一些函数的接口我们需要调整一下顺序,这时候bind就起到了很大的作用了。

而且当我们对于3个参数的函数,如果我们只想要传递两个参数,那么我们也可以用bind

double Sub(int x, int y, double rate)
{
  return (x - y) * rate;
}
int main()
{
  function<double(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2, 4.2);
  function<double(int, int)> rSub2 = bind(Sub, placeholders::_1, placeholders::_2, 4.3);
  function<double(int, int)> rSub3 = bind(Sub, placeholders::_1, placeholders::_2, 4.4);
  cout << rSub1(10, 5) << endl;
  cout << rSub2(10, 5) << endl;
  cout << rSub3(10, 5) << endl;
  return 0;
}

如果我们想要将固定的参数给到前面,那就是这样的,注意一定是从_1开始的下标

double Sub(int x, int y, double rate)
{
  return (x - y) * rate;
}
double RSub(double rate, int x, int y)
{
  return (x - y) * rate;
}
int main()
{
  function<double(int, int)> rSub1 = bind(Sub, placeholders::_1, placeholders::_2, 4.2);
  function<double(int, int)> rSub2 = bind(Sub, placeholders::_1, placeholders::_2, 4.3);
  function<double(int, int)> rSub3 = bind(Sub, placeholders::_1, placeholders::_2, 4.4);
  cout << rSub1(10, 5) << endl;
  cout << rSub2(10, 5) << endl;
  cout << rSub3(10, 5) << endl;
  function<double(int, int)> rSub4 = bind(RSub, 4.5, placeholders::_1, placeholders::_2);
  function<double(int, int)> rSub5 = bind(RSub, 4.2, placeholders::_1, placeholders::_2);
  cout << rSub4(10, 5) << endl;
  cout << rSub5(10, 5) << endl;
  return 0;
}

因为这个_1和_2其实是给rsub看的。只有他们才会去看这些下标,后面的都是直接传递的

如下所示的场景中,

我们需要注意的是,如果某个函数是某个类域里面的,我们还要记得写上访问限定符,因为它只能访问到这个局部域和全局域中的。

对于静态的函数,写上类域就可以了,但是对于非静态的,它的地址还需要加上取地址符号,静态的可以加也可以不加,除此之外,还需要传递一个this指针,为此我们需要定义一个对象,才能传过去,或者直接传递一个对象也是可以的

class SubType
{
public:
  static int Sub(int x, int y)
  {
    return (x - y);
  }
  int SSub(int x, int y, double rate)
  {
    return (x - y) * rate;
  }
};
int main()
{
  function<int(int, int)> rSub5 = bind(SubType::Sub, placeholders::_1, placeholders::_2);
  SubType sb;
  function<int(int, int)> rSub6 = bind(&SubType::SSub, &sb, placeholders::_1, placeholders::_2, 3);
  function<int(int, int)> rSub7 = bind(&SubType::SSub, SubType(), placeholders::_1, placeholders::_2, 3);
  cout << rSub5(10, 5) << endl;
  cout << rSub6(10, 5) << endl;
  cout << rSub7(10, 5) << endl;
  return 0;
}

对于类域中的非静态,传地址我们可以理解,但是为什么可以传对象呢?

其实bind的底层其实也是仿函数,在这个变量这里重载了operator(),可以根据传入的是对象还是指针去决定最终传递哪一个。所以可以可以传一个对象过去

不过要切记,不可以给匿名对象取地址,因为右值无法取地址

相关文章
|
5月前
|
编译器 C语言 C++
C++一分钟之-C++11新特性:初始化列表
【6月更文挑战第21天】C++11的初始化列表增强语言表现力,简化对象构造,特别是在处理容器和数组时。它允许直接初始化成员变量,提升代码清晰度和性能。使用时要注意无默认构造函数可能导致编译错误,成员初始化顺序应与声明顺序一致,且在重载构造函数时避免歧义。利用编译器警告能帮助避免陷阱。初始化列表是高效编程的关键,但需谨慎使用。
68 2
|
6月前
|
存储 编译器 C语言
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题(下)
从C语言到C++_34(C++11_下)可变参数+ lambda+function+bind+笔试题
94 5
|
6月前
|
存储 安全 编译器
【C++】C++11
【C++】C++11
|
3月前
|
存储 程序员 C++
【C++小知识】基于范围的for循环(C++11)
【C++小知识】基于范围的for循环(C++11)
|
3月前
|
编译器 C语言 C++
【C++关键字】指针空值nullptr(C++11)
【C++关键字】指针空值nullptr(C++11)
|
3月前
|
存储 编译器 C++
【C++关键字】auto的使用(C++11)
【C++关键字】auto的使用(C++11)
|
4月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
6月前
|
编译器 C语言 C++
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值
35 1
从C语言到C++_33(C++11_上)initializer_list+右值引用+完美转发+移动构造/赋值(中)
|
5月前
|
算法 安全 编译器
【C++航海王:追寻罗杰的编程之路】C++11(四)
【C++航海王:追寻罗杰的编程之路】C++11(四)
37 0
|
5月前
|
存储 安全 程序员
【C++航海王:追寻罗杰的编程之路】C++11(一)
【C++航海王:追寻罗杰的编程之路】C++11(一)
40 0
【C++航海王:追寻罗杰的编程之路】C++11(一)