【深入理解C++11:C++新特性解析】第3章 通用为本 专用为末 测试代码整理

该博客围绕C++展开,重点介绍C++11的新特性。涉及继承构造函数、成员变量初始化、委派构造函数等特性的使用与优化,还阐述了拷贝构造、移动构造、完美转发等概念,以及命名空间、类型别名、初始化列表等相关内容,同时指出使用中需注意的问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

3-1.显示声明的方法继承构造函数

3-2.用显示声明的方法("透传")继承含大量构造函数的基类

3-3.派生类通过using声明来完成对基类成员函数的使用

3-4.用C++11新特性using A:A来优化3-2中的代码

3-5.利用C++11中的继承构造函数和成员变量初始化两个新特性解决一些构造函数无法初始化的派生类成员问题

3-6.参数默认值会导致多个构造函数版本,使用有参数默认值的构造函数的基类时必须小心

3-7.继承构造函数导致的冲突,用显示定义继承类的冲突的构造函数来组织隐式带来的冲突--会冲突,但目前我的编译器无法通过显示重定义解决问题

3-8.一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了

3-9.一个构造函数代码冗余的例子

3-10.利用C++11中对成员初始化的特性改写3-10中的例子

3-11.利用委派构造函数和目标构造函数来构造类

3-12.对3-11的进一步简洁和优化

3-13-1.一个链状委托构造关系的例子

3-13-2.在委托构造的链状关系中,形成环之后,程序运行报错

*3-14. 在构造函数模板中利用委派构造产生目标构造函数

3-15.目标构造函数中抛出异常,委派构造函数的函数体部分的代码被终止执行

3-16.一个错误的示范-含在堆空间分配内存空间的指针成员变量的类中的拷贝构造函数使用默认拷贝构造函数导致的内存释放错误

3-17.自定义拷贝构造函数解决浅拷贝问题

3-18.看看g++编译器对我们的拷贝构造函数做了什么?

3-19.用移动构造函数将我们的临时对象产生的堆内存偷过来

3-20 值传递和引用传递的效率是不一样的,可比较编译器优化前后的区别

3-21.std::move转化的左值变量不能被立即析构-一个典型误用std::move的例子

3-22.一个正确使用std::move的例子(拷贝语义+移动语义的一个例子)

3-23.移动构造函数的异常-尽量编写不抛异常的移动构造函数,使用std::move_if_noexception替代move函数

3-24.一个完美转发的例子及完美转发的作用

3-25.使用很少的代码记录单个参数函数的参数传递状况

3-26.回顾一下explicit关键字保证对象的显示构造在一些(部分)情况下都是必须的

3-27.一个通过自定义类型转换判断指针是否有效的例子

3-28.C++11将explicit的使用范围扩展到了自定义的类型转换操作符上,C++98会给警告提示相关功能在C++11中被明确

3-29.C++11中用初始化列表完成对类成员的快速就地初始化

3-30.使用头文件中的initializer_list类模板初始化自定义的类

3-31.函数的参数列表也可以使用初始化列表

3-32.使用初始化列表重载操作符实现在[]中使用列表,将设置数组中的部分为一个指定的值

3-33.C++11中编译器会对使用初始化列表进行初始化的数据检查是否发生类型收窄

3-34.判断一个数据类型是否是平凡的

3-35.C++11的标准布局中要求派生类的第一个非静态成员的类型必须不同于基类

3-36.判断一个数据类型是否是一个标准布局的类型

3-37.判断一个数据类型是否是一个POD的类型

3-38.C++98中不允许非POD类型对象作为联合体Union的成员,C++11中取消了联合体对于成员类型的限制

3-39.C++98和C++11中联合体允许有静态成员函数

3.40.C++98/C++11对未赋初值的联合体的初始化往往带来疑问

3.41.C++11标准中会默认删除一些会产生疑问的非受限联合体的默认函数

3-42.自定义非受限联合体定义构造函数解决C++11标准中会默认删除一些会产生疑问的非受限联合体的默认函数的问题

3-43.C++11中匿名非受限联合体应用于类的声明中

3-44.用户想声明一个自定义类型的字面量的一个例子-传统方法

*3-45.利用字面量操作符来实现定义自定义类型的字面常量

3-46.字面量操作符也可以作用于数值

*3-47.命名空间使用不当带来一些问题的一个例子

3-48.C++98不允许在不同的命名空间对模板进行特化

3-49.内联的命名空间允许在父命名空间定义或特化子命名空间的模板(不过要注意隔离和封装性)

3-50.inline namespace的另一个例子

3-51.用using来定义类型的别名并用is_same来判断两个类型是否一致

3-52.一般化的SFINEA规则的一个举例

3-53.标准对SFINEA规则没有进行完全清晰的表述


3-1.显示声明的方法继承构造函数

struct A{A(int i){}};
struct B:A{
   B(int i):A(i),d(i){}
   int d;
};

// 编译选项:g++ 3-1-1.cpp
// page 192

3-2.用显示声明的方法("透传")继承含大量构造函数的基类

struct A{
   A(int i){}
   A(double d,int i){}
   A(float f ,int i,const char* c){}
   //...
};
struct B:A{
   B(int i):A(i){}
   B(double d,int i):A(d,i){}
   B(float f,int i, int const chat*c ):A(f,i,c){}
   //...
   virtual void ExtraInterface(){}
};

// 编译选项:g++ 3-1-2.cpp
// page 192

3-3.派生类通过using声明来完成对基类成员函数的使用

#include <iostream>
using namespace std;
struct Base{
   void f(double i){cout << "Base:"<< i << endl;}
};

struct Derived :Base{
   using Base::f;
   void f(int i ){cout << "Derived:"<< i << endl;}
};

int main(){
     Base b;
     b.f(4.5);// Base:4.5
     Derived d;
     d.f(4.5);// Base:4.5
     d.f(40); // Derived:40
     return 0;
}
// 编译选项:g++ 3-1-3.cpp
// page 193

3-4.用C++11新特性using A:A来优化3-2中的代码

struct A{
   A(int i){}
   A(double d,int i){}
   A(float f ,int i,const char* c){}
};
struct B:A{
   using A::A;// 继承构造函数
   virtual void ExtraInterface(){}
};
// 编译选项:g++ -c 3-1-4.cpp -std=c++11
// page 195

3-5.利用C++11中的继承构造函数和成员变量初始化两个新特性解决一些构造函数无法初始化的派生类成员问题

#include <iostream>
using namespace std;
struct A{
   A(int i){}
   A(double d,int i){}
   A(float f ,int i,const char* c){}
};
struct B:A{
   using A::A;// 继承构造函数
   int d{0};
};
int main(){
   B b(356); // b.d被初始化成0
   cout << "b.d = " << b.d<< endl;

}
// 编译选项:g++ -c 3-1-5.cpp -std=c++11
// page 196

3-6.参数默认值会导致多个构造函数版本,使用有参数默认值的构造函数的基类时必须小心

struct A{
   A(int a = 3,double = 2.4){}
};
struct B:A{
    using A::A;
};
// 编译选项:g++ -c 3-1-6.cpp
// page 197

3-7.继承构造函数导致的冲突,用显示定义继承类的冲突的构造函数来组织隐式带来的冲突--会冲突,但目前我的编译器无法通过显示重定义解决问题

#include <iostream>
using namespace std;
struct A{A(int){}};
struct B{B(int){}};
struct C:A,B{
    using A::A;
    using B::B;
    C(int){}
};
int main(){
   C c(1);
   return 0;
}
// 编译选项:g++ -c 3-1-7.cpp
// page 200

3-8.一旦使用了继承构造函数,编译器就不会再为派生类生成默认构造函数了

struct A{A(int){}};
struct B:A{using A::A;};
B b;//报错,一旦使用了继承构造函数,编译器就不会再为派生类生成默认的构造函数了
// g++ -c -std=c++11 3-1-8.cpp
// page 201

3-9.一个构造函数代码冗余的例子

class Info{
   public:
      Info(): type(1), name('a'){InitRest();}
      Info(int i):type(i),name('a'){InitRest();}
      Info(char e):type(1),name(e){InitRest();}
      // ...
   private:
      void InitRest(){/*其他初始化*/}
      int type;
      char name;
      // ...
};
// 编译选项:g++ -c 3-2-1.cpp
// page 202 

3-10.利用C++11中对成员初始化的特性改写3-10中的例子

class Info{
   public:
      Info() {InitRest();}
      Info(int i):type(i){InitRest();}
      Info(char e):name(e){InitRest();}
   
   private:
      void InitRest(){/*其他初始化*/}
      int type{1};
      char name{'a'};
};
// 编译选项:g++ -c 0std=c++11 3-2-2.cpp
// page 204

3-11.利用委派构造函数和目标构造函数来构造类

class Info{
   public:
      Info() {InitRest();}
      Info(int i):Info(){type = i;}
      Info(char e):Info(){name = e;}
   
   private:
      void InitRest(){/*其他初始化*/}
      int type{1};
      char name{'a'};

};
// 编译选项:g++ -c 3-2-3.cpp
// page 203 代码清单3-11

/*
注意:
(1)Info(int i)和Info(char e)称为委派构造函数,Info()称为目标构造函数;
(2)委派构造函数不能有初始化列表,委派构造函数只能在函数体中为成员变量赋初值.
*/

3-12.对3-11的进一步简洁和优化

class Info{
   public:
      Info():Info(1,'a') {}
      Info(int i):Info(i,'a'){}
      Info(char e):Info(1,e){}
   private:
      Info(int i ,char e):type(i),name(e){/*其他初始化*/}
      int type;
      char name;
};
// 编译选项:g++ -c 3-2-4.cpp
// // page 209 代码清单3-12

3-13-1.一个链状委托构造关系的例子

class Info{
   public:
      Info():Info(1) {} // 委派构造函数
      Info(int i):Info(i,'a'){} // 即是目标构造函数,也是委派构造函数
      Info(char e):Info(1,e){}
   private:
      Info(int i ,char e):type(i),name(e){/*其他初始化*/} // 目标构造函数
      int type;
      char name;
};
/*
 * 编译选项:g++ -c 3-2-5.cpp
 * page 211 代码清单3-13
 * 在委托构造的环状关系中,有一点必须注意,不能形成委托环.
 * */

3-13-2.在委托构造的链状关系中,形成环之后,程序运行报错

#include <iostream>
using namespace std;
struct Rule2{
    int i,c;
    Rule2():Rule2(2){cout<<"line= 3"<<endl;}
    Rule2(int i):Rule2('c'){cout<<"line = 4"<< endl;}
    Rule2(char c):Rule2(1){cout<<"line = 5"<< endl;}
};
int main()
{
   Rule2 obj1();// OK
   Rule2 obj2(2); // 出错
   Rule2 obj3('d'); // 出错
}
// 在我的机器上编译没有出错 运行出错了

*3-14. 在构造函数模板中利用委派构造产生目标构造函数

#include <iostream>
#include <list>
#include <vector>
#include <deque>
using namespace std;
class TDConstructed{
   template<class T> TDConstructed(T first,T last):l(first,last){cout<<"TD list Constructor"<<endl;}
   list<int> l;

public:
   TDConstructed(vector<short> &v):TDConstructed(v.begin(),v.end()){cout<<"TD Vector Constructor"<<endl;}
   TDConstructed(deque<short> &d):TDConstructed(d.begin(),d.end()){cout<<"TD Deque Construnctor"<<endl;}
};
int main()
{
   vector<short> v1{1,2,3};
   vector<short> &refV1= v1; 
   TDConstructed obj1(refV1);
    
   deque<short> v2{4,5,6};
   deque<short> &refV2 = v2; 
   TDConstructed obj2(refV2);  
   return 0;
}
/*
编译选项:g++ -std=c++11 3-2-6.cpp
page 214
在本例中,我们定义了一个构造函数模板,通过两个委派构造函数的委托,构造函数模板会被实例化,
T会被推导为vector<short>::iterator和 deque<short>::iterator两种类型,
这样我们的TDConstructed类就可以很容易的接受多种容器对其进行初始化.

委托构造使得构造函数的泛型编程成为了一种可能.

*/

3-15.目标构造函数中抛出异常,委派构造函数的函数体部分的代码被终止执行

#include <iostream>
using namespace std;
class DCExcept{
    public:
        DCExcept(double d)
             try:DCExcept(1,d){

                 cout<<"Run the body."<<endl;             
             }
             catch(...){
                 cout<<"caught exception."<<endl;
             }
    private:
        DCExcept(int i ,double d){
            cout<<"going to throw !"<<endl;
            throw 0;
        }
        int type;
        double data;
};
int main(){
    DCExcept a(1.2);
}
/*
编译选项: g++ -std=c++11 3-2-7.cpp
page 215
本例在目标构造函数DCExcept(int i ,double d)中抛出一个异常,并在委派构造函数DCExcept(double d)中进行捕获,
由于在目标构造函数中抛出异常,委派构造函数的函数体部分的代码并没有被执行
*/

3-16.一个错误的示范-含在堆空间分配内存空间的指针成员变量的类中的拷贝构造函数使用默认拷贝构造函数导致的内存释放错误

#include <iostream>
using namespace std;
class HasPtrMem{
     public:
        HasPtrMem():d(new int(0)){}
        ~HasPtrMem(){delete d;}  
        int *d;
};
int main(){
    HasPtrMem a;
    HasPtrMem b(a);
    cout<< *a.d << endl; //0
    cout<< *b.d << endl; //0

} // 正常析构


/*
注意:作为一个错误的示范代码,书中的3-16和3-17代码完全相同,所以此处我将3-6
      中重写拷贝构造函数的代码去除了.
*/

3-17.自定义拷贝构造函数解决浅拷贝问题

#include <iostream>
using namespace std;
class HasPtrMem{
     public:
        HasPtrMem():d(new int(0)){}
        HasPtrMem(const HasPtrMem &h):
               d(new int(*h.d)){}   // 拷贝构造函数,从堆中分配内存,并用*h.d初始化
        ~HasPtrMem(){delete d;}  
        int * d;
};
int main(){
    HasPtrMem a;
    HasPtrMem b(a);
    cout<< *a.d << endl; //0
    cout<< *b.d << endl; //0

} // 正常析构
/*
 编译选项:g++ 3-3-2.cpp
 page 219
自定义拷贝构造函数,解决浅拷贝带来的问题
*/

3-18.看看g++编译器对我们的拷贝构造函数做了什么?

关于g++编译器优化的-fno-elide-constructor的含义

#include <iostream>
using namespace std;
class HasPtrMem{
    public:
       HasPtrMem():d(new int(0)){
           cout<< "Construct:" << ++n_cstr<<endl;
       }
       HasPtrMem(const HasPtrMem &h):d(new int(*h.d)){
           cout<< "Copy Construct:" << ++n_cptr<<endl;
       }
       ~HasPtrMem(){
           cout<< "Destruct:" << ++n_dstr<<endl;
       }   
       int * d;
       static int n_cstr;
       static int n_dstr;
       static int n_cptr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;

HasPtrMem GetTemp(){
    return HasPtrMem();
}
int main(){
    HasPtrMem a = GetTemp();
}
/*
编译选项: g++ 3-3-3.cpp -fno-elide-constructors
          g++ 3-3-3.cpp 
page 221  清单3-18
*/

C++标准允许编译器省略创建一个只是为了初始化另一个同类型对象的临时对象的步骤.指定这个参
数(-fno-elide-constructors)将关闭这种优化,强制G++在所有情况下调用拷贝构造函数.这个选项
也导致了G++会去调用一些被内联的微不足道的函数.
elide   --省略
no-elide--不省略
默认省略,加上-fno-elide-constructors不省略.

3-19.用移动构造函数将我们的临时对象产生的堆内存偷过来

#include <iostream>
using namespace std;
class HasPtrMem{
    public:
       HasPtrMem():d(new int(3)){
           cout<< "Construct:" << ++n_cstr<<endl;
       }
       HasPtrMem(const HasPtrMem &h):d(new int(*h.d)){  
           cout<< "Copy Construct:" << ++n_cptr<<endl;  
       }
       HasPtrMem(HasPtrMem &&h):d(h.d){ // 移动构造函数,这里移动构造函数接受的h是“右值引用”的参数
           h.d = nullptr;               // 将临时值的指针置空
           cout<< "Move Construct:" << ++n_mvtr<<endl;
       }
       ~HasPtrMem(){
           cout<< "Destruct:" << ++n_dstr<<endl;
       }
       int * d;
       static int n_cstr;
       static int n_dstr;
       static int n_cptr;
       static int n_mvtr;
};
int HasPtrMem::n_cstr = 0;
int HasPtrMem::n_dstr = 0;
int HasPtrMem::n_cptr = 0;
int HasPtrMem::n_mvtr = 0;

HasPtrMem GetTemp(){
    
    HasPtrMem h;
    cout<<"Resource form "<<__func__<<":" << hex <<h.d<< endl;
    return h;
}
int main(){
    HasPtrMem a = GetTemp();
    cout<<"Resource form "<<__func__<<":" << hex <<a.d<< endl;
}

/*
 * 编译选项: g++ 3-3-4.cpp -fno-elide-constructors -std=c++11
 *            g++ 3-3-4.cpp  -std=c++11
              g++ 3-3-4.cpp  -std=c++98(不能通过编译,HasPtrMem &&右值引用是C++11新概念)
        page 226  清单3-19

   一些解释:
   这里所谓的“偷”内存,就是将本对象d执行h.d这条语句,就是d(h.d)这个;
   相应的我们还将h的成员d置为指针空值,这是我们偷内存必须要做的,
   也就是说h.d = nullptr; 这一步必须要做.因为在移动构造完成之后,临时
   对象会被立即析构,如果不改变h.d(临时对象的指针成员)的话,则临时对象
   会析构掉被我们偷来堆内存,这样一样,本对象的d指针就会变成一个“悬挂指针”,
   如果我们对指针进行解引用,就会发生严重的运行是错误.

*/

3-20 值传递和引用传递的效率是不一样的,可比较编译器优化前后的区别

#include <iostream>
using namespace std;
struct Copyable{
    Copyable(){}
    Copyable(const Copyable& o){ 
        cout<<"Copied"<<endl;
    };  
};
Copyable ReturnRvalue(){
        return Copyable();
}
void AcceptVal(Copyable){}
void AccepyRef(const Copyable &){}
int main(){
    cout<< "Pass By Value" << endl; // 临时值被拷贝传入
    AcceptVal(ReturnRvalue());
    cout<< "Pass by reference:" << endl;
    AccepyRef(ReturnRvalue()); // 临时值被作为引用传递
}
// 编译选项:g++ 3-3-5.cpp -fno-elide-constructors
// page 240

3-21.std::move转化的左值变量不能被立即析构-一个典型误用std::move的例子

#include <iostream>
using namespace std;
class Moveable{
    public:
          Moveable():i(new int(3)){}
          ~Moveable(){delete i;}
          Moveable(const Moveable & m):i(new int(*m.i)){}
          Moveable(Moveable && m):i(m.i){
              m.i = nullptr;
          }
          int *i;
};
int main(){
    Moveable a;
    Moveable c(move(a));  // 会调用移动构造函数
    cout<< *a.i << endl;  // 运行时错误
    return 0;
}

/* 
 编译选项:g++ 3-3-6.cpp -fno-elide-constructors
 page 247 代码清单3-21
 
 这个函数定义本身没有什么问题,但调用的时候,使用了Moveable c(move(a));这样的语句。
 这里的a本来是一个左值变量,通过std::move将其转换为右值。这样一来,a.i就被c的移动构造函数设置为指针空值。
 由于a的生命期实际要到main函数结束才结束,那么随后对表达式*a.i进行计算的时候,就会发生严重的运行时错误。
*/

3-22.一个正确使用std::move的例子(拷贝语义+移动语义的一个例子)

#include <iostream>
using namespace std;

class HugeMem{
public:
      HugeMem(int size):sz(size>0 ? size:1){
          c = new int[sz];
      }   
      ~HugeMem(){delete [] c;} 
      HugeMem(HugeMem && hm):sz(hm.sz),c(hm.c){
          hm.c = nullptr;
      }   
      int *c; 
      int sz; 
};

class Moveable{
    public:
          Moveable():i(new int(3)),h(1024){}
          ~Moveable(){delete i;} 
          Moveable(Moveable && m):i(m.i),h(move(m.h)){  // 强制转为右值,以调用移动构造函数
              m.i = nullptr;
          }
          int *i; 
          HugeMem h;
};

Moveable GetTemp(){
    Moveable tmp = Moveable();
    cout << hex << "Huge Mem From " << __func__<< " @"<< tmp.h.c << dec <<",   *(tmp.h.c)="<<*(tmp.h.c)<< ",    tmp.h.sz = "<< tmp.h.sz<< endl;
    return tmp;
}

int main(){
    Moveable a(GetTemp());
    cout << hex << "Huge Mem From " << __func__<< " @"<< a.h.c << dec<< ",    *(a.h.c)="<< *(a.h.c)<<",    a.h.sz = "<<a.h.sz<<  endl;
    return 0;
}

/* 
 编译选项:g++ 3-3-7.cpp -fno-elide-constructors
 page 249 代码清单3-22
一个正确使用移动语义功能的例子.
 两个问题需要关注:
 (1)生存期不对的问题
 我们定义了两个类型:HugeMem和Moveable,其中Moveable包含了一个HugeMem的对象。
 在Moveable的移动构造函数中,我们就看到了std::move函数的使用。该函数将m.h强制转化为右值,
 以迫使Moveable中的h能够实现移动构造。这里可以使用std::move,是因为m.h是m的成员,既然m将
 在表达式结束后被析构,其成员也自然会被析构,
 因此不存在代码清单3-21中的生存期不对的问题。
 (2)std::move使用的必要性,这里如果不使用std::move(m.h)这样的表达式,而是直接使用m.h这个表达式将会怎样?
 这里的m.h引用了一个确定的对象,而且m.h也有名字,可以使用&m.h取到地址,因此是个不折不扣的左值。不过这个左值
 确确实实会很快“灰飞烟灭”,因为拷贝构造函数在Moveable对象a的构造完成后也就结束了。那么这里使用std::move强制
 其为右值就不会有问题了。而且,如果我们不这么做,由于m.h是个左值,就会导致调用HugeMem的拷贝构造函数来构造
 Moveable的成员h(虽然这里没有声明,读者可以自行添加实验一下)。
 如果是这样,移动语义就没有能够成功地向类的成员传递。换言之,还是会由于拷贝而导致一定的性能上的损失。
 
*/

3-23.移动构造函数的异常-尽量编写不抛异常的移动构造函数,使用std::move_if_noexception替代move函数

#include <iostream>
#include <utility>
using namespace std;
struct Maythrow{
   Maythrow(){}
   Maythrow(const Maythrow&){
       std::cout << "Maythrow copy constructor"<<endl; // 这里在本例中被调用
   }   
   Maythrow(Maythrow&&){
        std::cout << "Maythrow move constructor"<<endl;
   };  
};
struct Nothrow{
   Nothrow(){}
   Nothrow(const Nothrow&){
       std::cout<<"Nothrow copy constructor"<<endl;
   }   
   Nothrow(Nothrow&&) noexcept{ // 与Maythrow中的移动构造函数唯一的区别就是noexcept的使用
       std::cout<<"Nothrow move constructor"<<endl; // 这里在本例中被调用
   }   
};

int main(){
    Maythrow m;
    Nothrow n;
    Maythrow mt = move_if_noexcept(m);// Maythrow copy constructor
    Nothrow nt = move_if_noexcept(n); // Nothrow move constructor
    return 0;
}

/* 编译选项:g++ -std=c++ 3-3-8.cpp
   page 258 代码清单3-23
   std::move_if_noexcept函数在类的移动构造函数没有noexcept关键字修饰时返回一个左值引用从而使变量可以使用拷贝语义,
   而在类的移动构造函数有noexcept关键字时,返回一个右值引用,从而使变量可以使用移动语义.

   可以清楚地看到move_if_noexcept的效果。事实上,move_if_noexcept是以牺牲性能保证安全的一种做法,而且要求类的开发
   者对移动构造函数使用noexcept进行描述,否则就会损失更多的性能,这是库的开发者和使用者必须协同平衡考虑的.
*/

3-24.一个完美转发的例子及完美转发的作用

#include <iostream>
using namespace std;
void RunCode(int &&m){cout<< "rvalue ref" << endl;}
void RunCode(int &m) {cout<< "lvalue ref" << endl;}
void RunCode(const int &&m){cout<< "const rvalue ref" << endl;}
void RunCode(const int &m){cout<< "const lvalue ref" << endl;}
template<typename T>
void PerfectForward(T &&t){RunCode(forward<T>(t));}
int main(){
    int a;
    int b;
    const int c = 1;
    const int d = 0;
    PerfectForward(a);
    PerfectForward(move(b));
    PerfectForward(c);
    PerfectForward(move(d));
    return 0;
}

/*
 * 问题与困惑:完美转发用处与优点.
 * 书中给的答案:(1)包装函数;
 *              (2)C++库标准库中又打脸完美转发的实际应用,一些很小巧好用的函数,减少函数版本的重复,
 *                 充分使用移动语义,从性能和代码编写的简化上都堪称完美.
 * 编译选项:g++ std=c++11 3-3-9.cpp
 * 代码清单:3-24
 */

3-25.使用很少的代码记录单个参数函数的参数传递状况

#include <iostream>
using namespace std;
template<typename T,typename U>
void PerfectForward(T &&t,U& Func){
    cout<< t << "\tforwarded...";
    Func(forward<T>(t));
}
void RunCode(double &&m){cout << "I am Runcode"<<endl;}
void RunHome(double &&h){cout << "I am RunHome"<<endl;}
void RunComp(double &&c){cout << "I am RunComp"<<endl;}
int main(){
      PerfectForward(1.5,RunCode);
      PerfectForward(8,RunHome);
      PerfectForward(1.5,RunComp);
}

/*
 * 编译选项:g++ -std=c++11 3-3-10.cpp
 * 代码清单:3-25
 * 本例功能: 
 *        使用很少的代码记录单个参数函数的参数传递状况.
 * 
 */

3-26.回顾一下explicit关键字保证对象的显示构造在一些(部分)情况下都是必须的

#include <iostream>
using namespace std;
struct Rational1{
        Rational1(int n = 0,int d = 1):num(n),den(d){
            cout<<__func__<<"("<<num << "/"<< den <<")"<< endl;
        }
        int num;
        int den;
};
struct Rational2{
        explicit Rational2(int n = 0,int d = 1):num(n),den(d){
            cout<<__func__<<"("<<num << "/"<< den <<")"<< endl;
        }
        int num;
        int den;
};
void Display1(Rational1 ra){
    cout<< "Numerator:" << ra.num << "Denominator:" << ra.den<< endl;
}
void Display2(Rational2 ra){
    cout<< "Numerator:" << ra.num << "Denominator:" << ra.den<< endl;
}
int main(){
    Rational1 r1_1 = 11; 
    Rational1 r1_2(12);
    //Rational2 r2_1 = 21; // 无法通过编译
    Rational2 r2_2(22);
    Display1(1);
    //Dispaly2(2);// 无法通过编译
    Display2(Rational2(2));
    return 0;
}

/*
 * 编译选项:g++ -std=c++11 3-4-1.cpp
 * 代码清单:3-26
 * 本例功能: 
 *        使用explicit保证对象的显示构造在一些情况下是必须的 
    explicit 英[ɪkˈsplɪsɪt] 美[ɪkˈsplɪsɪt]
    adj.清楚明白的; 易于理解的; (说话)清晰的,明确的; 直言的; 坦率的; 直截了当的; 
 * 
 */

3-27.一个通过自定义类型转换判断指针是否有效的例子

#include <iostream>
using namespace std;
template <typename T>
class Ptr{
  public:
     Ptr(T* p):_p(p){}
     operator bool()const{
         if(_p != 0)
            return true;
        else
            return false;
     }    
  private:
      T* _p; 
};
int main(){
    int a;
    Ptr<int> p(&a);
    if(p)   // 自动转换为bool型,没有问题
      cout << "valid pointer." << endl;// valid pointer
    else
      cout << "invalid pointer."<<endl;
    Ptr<double> pd(0);
    cout<<p + pd<< endl;//1,相加,语义上没有意义
    return 0;
}
/* 
编译选项:g++ 3-4-2.cpp
代码清单:3-27
在代码清单3-27中,我们定义了一个指针模板类型Ptr。为了方便判断指针是否有效,我们为指针编写
了自定义类型转换到bool类型的函数,这样一来,我们就可以通过if(p)这样的表达式来轻松地判断指
针是否有效。不过这样的转换使得Ptr<int>和Ptr<double>两个指针的加法运算获得了语法上的允许。
不过明显地,我们无法看出其语义上的意义。
*/

3-28.C++11将explicit的使用范围扩展到了自定义的类型转换操作符上,C++98会给警告提示相关功能在C++11中被明确

class ConvertTo{};
class Convertable{
   public:
     explicit   operator ConvertTo() const{return ConvertTo();}
};
void Func(ConvertTo ct){}
void test(){
    Convertable c;
    ConvertTo ct(c);// 直接初始化,通过
    ConvertTo ct2 = c;// 拷贝构造初始化,编译失败
    ConvertTo ct3 = static_cast<ConvertTo>(c);// 强制转化,通过
    Func(c); // 拷贝构造初始化,编译失败
    
}
/*
g++  3-4-3.cpp -std=c++98 -c
g++  3-4-3.cpp -std=c++11 -c
所谓显式类型转换并没完全禁止从源类型到目标类型的转换,不过由于此时拷贝构造和非显式类型转换不
被允许,那么我们通常就不能通过赋值表达式或者函数参数的方式来产生这样一个目标类型。通常通过赋
值表达式和函数参数进行的转换有可能是程序员的一时疏忽,而并非本意。那么使用了显式类型转换,这
样的问题就会暴露出来,这也是我们需要显式转换符的一个重要原因.
*/

3-29.C++11中用初始化列表完成对类成员的快速就地初始化

#include <vector>
#include <map>
using namespace std;
int a[] = {1,3,5};
int b[]  {2,4,6};
vector<int> c{1,3,5};
map<int,float> d = {{1,1.0f},{2,2.0f},{5,3.2f}};
/*
 编译选项:g++ -c -std=c++11 3-5-1.cpp
*/

3-30.使用<initializer_list>头文件中的initializer_list类模板初始化自定义的类

#include <vector>
#include <string>
using namespace std;
enum Gender{boy,girl};
class People{
   public:
       People(initializer_list<pair<string,Gender>> l){ // initializer_list的构造函数
           auto i = l.begin();
           for(;i!=l.end();++i)
                  data.push_back(*i);
       }
    private:
        vector<pair<string,Gender>> data;
};
People ship2012 = {{"Garfield",boy},{"HelloKitty",girl}};

// 编译选项:g++ -c -std=c++ 3-5-2.cpp
// initializer list 初始化列表

3-31.函数的参数列表也可以使用初始化列表

#include <initializer_list>
using namespace std;
void Fun(initializer_list<int> iv){}
int main(){
  Fun({1,2,3});
  Fun({1,2});
  Fun({});
}

/*
   想一想应用场景
   编译选项:g++ -std=c++11 3-5-3.cpp
*/

3-32.使用初始化列表重载操作符实现在[]中使用列表,将设置数组中的部分为一个指定的值

#include <iostream>
#include <vector>
using namespace std;
class Mydata{
  public:
    Mydata & operator[](initializer_list<int> l){ 
         for(auto i = l.begin(); i!=l.end(); ++i)
                idx.push_back(*i);
            return *this;
    }   
    Mydata & operator = (int v){ 
        if(idx.empty() != true){
           for(auto i = idx.begin();i!=idx.end();++i){
               d.resize((*i > d.size()) ?*i :d.size());
               d[*i-1]= v;
           }
           idx.clear();
         }
         return *this;
    }   
   
    void Print(){
          for(auto i = d.begin();i!=d.end();++i)
              cout<< *i <<" ";
          cout << endl;
    }   
  private:
     vector<int> idx; // 辅助数据,用于记录index
     vector<int> d;
};

int main(){
   Mydata d;
   d[{2,3,5}] = 7;
   d.Print();
   d[{1,4,5,8}] = 4;
   d.Print();
}

3-33.C++11中编译器会对使用初始化列表进行初始化的数据检查是否发生类型收窄

const int x = 1024; 
const int y = 10; 
char a = x;               // 收窄,但可以通过编译
char* b = new char(1024); // 收窄,但可以通过编译 
char c = {x};             // 收窄,无法通过编译 
char d = {y};             // 可以通过编译
unsigned char e {-1};     // 收窄,无法通过编译  
float f{7};               // 收窄,可以通过编译
int g {2.0f};             // 收窄,无法通过编译
float * h = new float{1e48};// 收窄,无法通过编译
float i = 1.2l;            // 可以通过编译
// 编译选项:g++ -std=c++11 3-5-5.cpp
// 代码清单:3-33

3-34.判断一个数据类型是否是平凡的

#include <iostream>
#include <type_traits>
using namespace std;
struct Trivial1{};

struct Trivial2{
   public:
      int a;
   private:
      int b;
};

struct Trivial3{
    Trivial1 a;
    Trivial2 b;
};

struct Trivial4{
    Trivial2 a[23];
};

struct Trivial5{
    int x;
    static int y;
};

struct NonTrivial1{
    NonTrivial1():z(42){}
    int z;
};

struct NonTrivial2{
    NonTrivial2();
    int w;
};
NonTrivial2::NonTrivial2() = default;
struct NonTrivial3{
    Trivial5 c;
    virtual void f();
};

int
main(){
    cout << is_trivial<Trivial1>::value<< endl;// 1
    cout << is_trivial<Trivial2>::value<< endl;// 1
    cout << is_trivial<Trivial3>::value<< endl;// 1
    cout << is_trivial<Trivial4>::value<< endl;// 1
    cout << is_trivial<Trivial5>::value<< endl;// 1

    cout << is_trivial<NonTrivial1>::value<< endl;// 0
    cout << is_trivial<NonTrivial2>::value<< endl;// 0
    cout << is_trivial<NonTrivial3>::value<< endl;// 0

    cout << is_trivial<int>::value<< endl;   // 1
    cout << is_trivial<float>::value<< endl; // 1

    return 0;
}

/*
 * 编译选项:g++ -std=c++11 3-6-1.cpp 
 * 代码清单:3-34
 * 
 */

3-35.C++11的标准布局中要求派生类的第一个非静态成员的类型必须不同于基类

#include <iostream>
using namespace std;
struct B1{};
struct B2{};

struct D1:B1{
    B1 b;  // 第一个非静态变量和基类相同
    int i;
};

struct D2:B1{
    B2 b;
    int i;
};
int main(){
    D1 d1; 
    D2 d2; 
    cout<<hex;
    cout<<reinterpret_cast<long long>(&d1)<<endl;
    cout<<reinterpret_cast<long long>(&d1.b)<<endl;
    cout<<reinterpret_cast<long long>(&d1.i)<<endl<<endl;
    
    cout<<reinterpret_cast<long long>(&d2)<<endl;
    cout<<reinterpret_cast<long long>(&d2.b)<<endl;
    cout<<reinterpret_cast<long long>(&d2.i)<<endl;
}


/*
 * 编译选项:g++ -std=c++11 3-6-2.cpp 
 * 代码清单:3-35
 * 示例的作用:C++11的标准布局中要求派生类的第一个非静态成员的类型必须不同于基类
 */

3-36.判断一个数据类型是否是一个标准布局的类型

#include <iostream>
#include <type_traits>
using namespace std;
struct SLayout1{};

struct SLayout2{
      private:
            int x;
            int y;
};

struct SLayout3:SLayout1{
    int x;
    int y;
    void f();
};

struct SLayout4:SLayout1{
    int x;
    SLayout1 y;
};

struct SLayout5: SLayout1,SLayout3{};
struct SLayout6 {static int y;};
struct SLayout7:SLayout6{int x;};


struct NonSLayout1:SLayout1{
    SLayout1 x;
    int x;
};
struct NonSLayout2:SLayout2{};
struct NonSLayout3:NonSLayout2{};
struct NonSLayout4{
    public:
        int x;
    private:
        int y;
};
int main(){
     cout<<is_standard_layout<SLayout1>::value << endl;
     cout<<is_standard_layout<SLayout2>::value << endl;
     cout<<is_standard_layout<SLayout3>::value << endl;
     cout<<is_standard_layout<SLayout4>::value << endl;
     cout<<is_standard_layout<SLayout5>::value << endl;
     cout<<is_standard_layout<SLayout6>::value << endl;
     cout<<is_standard_layout<SLayout7>::value << endl;

     cout<<is_standard_layout<NonSLayout1>::value << endl;
     cout<<is_standard_layout<NonSLayout2>::value << endl;
     cout<<is_standard_layout<NonSLayout3>::value << endl;
     cout<<is_standard_layout<NonSLayout4>::value << endl;
     return 0;

}


/*
 * 编译选项:g++ -std=c++11 3-6-3.cpp 
 * 代码清单:3-36
 * 示例的作用:判断一个数据类型是否是一个标准布局的类型
 */

3-37.判断一个数据类型是否是一个POD的类型

#include <type_traits>
#include <iostream>
using namespace std;
union U{};
union U1{ U1(){}};
enum E{};
typedef double* DA;
typedef void (*PF)(int,double);
int main(){
   cout <<"\t"<<is_pod<U>::value  <<endl;//1
   cout <<"\t"<< is_pod<U1>::value <<endl;//0
   cout <<"\t"<< is_pod<E>::value  <<endl;//1
   cout <<"\t"<< is_pod<int>::value<<endl;//1
   cout <<"\t"<< is_pod<DA>::value <<endl;//1
   cout <<"\t"<< is_pod<PF>::value <<endl;//1
   return 0;
}

/*
 编译选项:g++ -std=c++11 3-6-4.cpp
 代码清单:3-37
 代码功能:判断一些数据类型是否是POD类型(Plain Old Data)
*/

3-38.C++98中不允许非POD类型对象作为联合体Union的成员,C++11中取消了联合体对于成员类型的限制

struct Student{
    Student(bool g,int a):gender(g),age(a){}
    bool gender;
    int age;
};
union T{
   Student s;//编译失败,不是一个POD类型
   int id;
   char name[10];
};

/*
 编译选项:g++ -std=c++11 3-7-1.cpp -c
          g++ -std=c++98 3-7-1.cpp -c
 代码清单:3-38
 代码功能:判断一些数据类型是否是POD类型(Plain Old Data)
*/

3-39.C++98和C++11中联合体允许有静态成员函数

#include <iostream>
using namespace std;
union T{static long Get()
        {
            return 32;
        }
};
int main(){
    cout << T::Get()<<endl;
    return 0;
}

/*
 编译选项:g++ -std=c++11 3-7-2.cpp -c
          g++ -std=c++98 3-7-2.cpp -c
 代码清单:3-39
 代码功能:判断一些数据类型是否是POD类型(Plain Old Data)
*/

3.40.C++98/C++11对未赋初值的联合体的初始化往往带来疑问

union T{
   int x;
   double d;
   char b[sizeof(double)];
};
T t={0};  // 到底是初始化第一个成员还是所有成员呢
// 编译选项:g++ -std=c++98 -c 3-7-3.cpp
// 代码清单:3-40

3.41.C++11标准中会默认删除一些会产生疑问的非受限联合体的默认函数

#include <string>
using namespace std;
union T{
    string s;  // string有非平凡的构造函数
    int n;
};
int main(){
    T t;      // C++11中构造失败,因为T的构造函数被删了;C++98中也会失败,因为UNION对成员变量有限制
    return 0;
}


/*
 编译选项:g++ -std=c++11 3-7-4.cpp -c
          g++ -std=c++98 3-7-4.cpp -c
 代码清单:3-41
 代码功能:测试C++11标准中会默认删除一些会产生疑问的非受限联合体的默认函数
*/

3-42.自定义非受限联合体定义构造函数解决C++11标准中会默认删除一些会产生疑问的非受限联合体的默认函数的问题

#include <string>
using namespace std;
union T{
    string s;  
    int n;
    public:
       T(){new(&s) string;}
       ~T(){s.~string();}
};
int main(){
    T t;      
    return 0;
}

/*
 编译选项:g++ -std=c++11 3-7-5.cpp -c
 代码清单:3-42
 代码功能:自定义非受限联合体定义构造函数解决C++11标准中会默认删除一些会产生疑问的非受限联合
           体的默认函数的问题,此处的placement new发挥了很好的作用
*/

3-43.C++11中匿名非受限联合体应用于类的声明中

#include <cstring>
using namespace std;
struct Student{
    Student(bool g,int a):gender(g),age(a){}
    bool gender;
    int age;
};
class Singer{
  public:
    enum Type{STUDENT,NATIVE,FOREINGER};
    Singer(bool g,int a):s(g,a){t=STUDENT;}
    Singer(int i):id(i){t = NATIVE;}
    Singer(const char* n ,int s){
        int size = (s > 9) ? 9:s;
        memcpy(name,n,size);
        name[s]='\0';
        t = FOREINGER;
    }
    ~Singer(){}
    private:
        Type t;
        union {  // 匿名的非受限联合体
           Student s;
           int id;
           char name[10];
        };
};
int main(){
   Singer(true,13);
   Singer(310217);
   Singer("J Michael",9);
   return 0;
}
/*
 编译选项:g++ -std=c++11 3-7-6.cpp 
 代码清单:3-43
 代码功能:C++11中匿名非受限联合体应用于类的声明中
*/

3-44.用户想声明一个自定义类型的字面量的一个例子-传统方法

#include <iostream>
using namespace std;
typedef unsigned char uint8;
struct RGBA{
    uint8 r;
    uint8 g;
    uint8 b;
    uint8 a;
    RGBA(uint8 R,uint8 G,uint8 B,uint8 A=0):
         r(R),g(G),b(B),a(A){}
};
std::ostream &operator << (std::ostream & out,RGBA & col){
    return out << "r: "<<(int)col.r 
               << ",g: "<<(int)col.g
               << ",b: "<<(int)col.b 
               << ",a: "<<(int)col.a << endl;  
}
void blend(RGBA& col1,RGBA& col2){
    cout<<"blend" << endl<< col1<<col2<<endl;
}

int main(){
    RGBA col1(255,240,155);
    RGBA col2({15,255,10,7});
    blend(col1,col2);
    return 0;
}


/*
   编译选项:g++ -std=c++11 3-8-1.cpp
   代码清单 3-44
 */


*3-45.利用字面量操作符来实现定义自定义类型的字面常量

#include <iostream>
using namespace std;
typedef unsigned char uint8;
struct RGBA{
    uint8 r;
    uint8 g;
    uint8 b;
    uint8 a;
    RGBA(uint8 R,uint8 G,uint8 B,uint8 A=0):
         r(R),g(G),b(B),a(A){}
};

// 这里的size_t n干啥用的?这个是啥固定用法吗?
RGBA operator "" _C(const char* col,size_t n){  
     const char* p = col;
     const char* end = col + n;
     const char* r,*g,*b,*a;
     r = g = b = a = nullptr;
     for(; p != end;++p){
         if(*p == 'r') r = p;
         else if(*p == 'g') g = p;
         else if(*p == 'b') b = p;
         else if(*p == 'a') a = p;
     }   
     if((r == nullptr) || (g == nullptr) || (b == nullptr))
         throw;
     else if(a = nullptr){
         return RGBA(atoi(r+1),atoi(g+1),atoi(b+1));
     }   
     else
        return RGBA(atoi(r+1),atoi(g+1),atoi(b+1),atoi(b+1));
}

std::ostream &operator << (std::ostream & out,RGBA & col){
    return out << "r: "<<(int)col.r
               << "g: "<<(int)col.g
               << "b: "<<(int)col.b
               << "a: "<<(int)col.a << endl;
}
void blend(RGBA&& col1,RGBA&& col2){
    cout<<"blend" << endl<< col1<<col2<<endl;
}

int main(){
    blend("r255 g240 b155"_C,"r15 g255 b10 a7"_C);
    return 0;
}


/*
   编译选项:g++ -std=c++11 3-8-2.cpp
   代码清单 3-45
*/


3-46.字面量操作符也可以作用于数值

struct Watt{unsigned int v;};
Watt operator "" _w(unsigned long long v){
    return {(unsigned int) v};
}

int main(){
    Watt capacity = 1024_w;
}

/*
   编译选项:g++ -std=c++11 3-8-2.cpp
   代码清单: 3-46
   代码功能:字面量操作符也可以作用于数值

*/

*3-47.命名空间使用不当带来一些问题的一个例子

#include <iostream>
using namespace std;
// 这个是Jim表写的库,用了Jim这个名字空间
namespace Jim{
    namespace Basic{
          struct Knife{ Knife() {cout <<"Knife in Basic"<< endl;}};
          class CorkScrew{};
    }   
    namespace Toolkit{
          template<typename T> class SwissArmyKnife{};
    }   
    
    namespace Other{
         //Knife b;// 无法通过编译
         struct Knife {Knife() {cout <<"Knife in Other"<< endl;}};
         Knife c;
         Basic::Knife k;
    }   
}
// 这个是LiLei在使用Jim的库
using namespace Jim;

int main(){
     Toolkit::SwissArmyKnife<Basic::Knife> sknife;// 用Toolkit::SwissArmyKnife<Basic::Knife>来声明变量sknife
     return 0;
}


/*
   编译选项:g++ 3-9-1.cpp
   代码清单: 3-47
   代码功能:命名空间使用不当带来一些问题的一个例子

*/

3-48.C++98不允许在不同的命名空间对模板进行特化

#include <iostream>
using namespace std;
// 这个是Jim表写的库,用了Jim这个名字空间
namespace Jim{
    namespace Basic{
          struct Knife{ Knife() {cout <<"Knife in Basic"<< endl;}};
          class CorkScrew{};
    }   
    namespace Toolkit{
          template<typename T> class SwissArmyKnife{};
    }   
    
    namespace Other{
         //Knife b;// 无法通过编译
         struct Knife {Knife() {cout <<"Knife in Other"<< endl;}};
         Knife c;
         Basic::Knife k;
    }   
    using namespace Basic;
    using namespace Toolkit;
}
// LiLei决定对该class进行特化
namespace Jim{
    template<> class SwissArmyKnife<Knife>{}; // 编译失败
}


// 这个是LiLei在使用Jim的库
using namespace Jim;

int main(){
     Toolkit::SwissArmyKnife<Basic::Knife> sknife;
     return 0;
}

3-49.内联的命名空间允许在父命名空间定义或特化子命名空间的模板(不过要注意隔离和封装性)

#include <iostream>
using namespace std;

namespace Jim{
    inline namespace Basic{
          struct Knife{ Knife() {cout <<"Knife in Basic"<< endl;}};
          class CorkScrew{};
    }   
    inline namespace Toolkit{
          template<typename T> class SwissArmyKnife{};
    }   
    
    namespace Other{
         Knife b;
         struct Knife {Knife() {cout <<"Knife in Other"<< endl;}};
         Knife c;
         Basic::Knife k;
    }   
    using namespace Basic;
    using namespace Toolkit;
}

namespace Jim{
    template<> class SwissArmyKnife<Knife>{}; 
}

using namespace Jim;

int main(){
     Toolkit::SwissArmyKnife<Basic::Knife> sknife;
     return 0;
}


/*
   编译选项:g++ 3-9-3.cpp
   代码清单: 3-49
   代码功能:内联的命名空间允许在父命名空间定义或特化子命名空间的模板

*/

3-50.inline namespace的另一个例子

#include <iostream>
using namespace std;
namespace Jim{
#if __cplusplus == 201103L
    inline
#endif 
    namespace cpp11{
         struct Knife{Knife(){cout << "Knife in C++ 11"<<endl;}};
    }   
    #if __cplusplus < 201103L
         inline
    #endif 
        namespace oldcpp{
            struct Knife{Knife(){cout << "Knife in old c++"<<endl;}};
        }
}
using namespace Jim;
int main(){
   Knife a;    
   cpp11::Knife b;  
   oldcpp::Knife c;
}

/*
   编译选项:g++ 3-9-4.cpp
   代码清单: 3-50
   代码功能:inline namespace的另一个例子

*/

3-51.用using来定义类型的别名并用is_same来判断两个类型是否一致

#include <iostream>
#include <type_traits>
using namespace std;
using uint = unsigned int;
typedef unsigned int UINT;
using sint = int;
int main(){

    cout << is_same<uint,UINT>::value<<endl;
    return 0;
}
/*
   编译选项:g++ 3-10-1.cpp
   代码清单: 3-51
   代码功能:用using来定义类型的别名并用is_same来判断两个类型是否一致

*/

3-52.一般化的SFINEA规则的一个举例

struct Test{
   typedef int foo;
};
template <typename T>
void f(typename T::foo){}
template<typename T>
void f(T){}
int main(){
   f<Test>(10);
   f<int>(10);
}
/*
   编译选项:g++ 3-10-2.cpp
   代码清单: 3-52
   代码功能:一般化的SFINEA规则的一个举例,SFINEA-Substitution failure is not an error,"匹配失败不是错误"
*/

3-53.标准对SFINEA规则没有进行完全清晰的表述

template <int I> struct A{};
char xxx(int);
char xxx(float);
template<class T> A<sizeof(xxx((T)0))>f(T){}
int main(){
    f(1);
}

/*
   编译选项:g++ 3-10-3.cpp -std=c++98
            g++ 3-10-3.cpp -std=c++11
   代码清单: 3-53
   代码功能:标准对SFINEA规则没有进行完全清晰的表述
*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值