c++11特性(五)性能提升

c++11特性

  • using
    • 定义别名
    • 模版的别名
  • 可调用对象包装器和绑定器
    • 可调用对象
    • 可调用对象包装器
      • 基本用法
      • 作为回调函数使用
    • 绑定器
      • 使用方式
      • 使用示例
      • 扩展
  • friend
    • friend的概念及用法
    • friend使用示例
    • 类模版声明友元

using

看到using大家应该很熟悉吧,using namespace std; 这个应该是写c++程序时必不可少的,在c++中using用于声明命名空间,使用命名空间是为了防止命名冲突,在程序中声明了命名空间之后,就可以直接使用命名空间中的定义的类了,像stl容器,等等各种c++库文件很多都是std命名空间下的,但是在 C++11 中赋予了 using 新的功能,让 C++ 变得更年轻,更灵活。

定义别名

c++中可以通过typedef重定义一个类型,使用方法如下:
1、typedef定义别名

typedef 旧的类型名 新的类型名
typedef std::shared_ptr<> ptr 

被typedef重定义的类型并不是一个新的类型,只是给原有的类型重新起了一个名字,因为有的类型可能比较长,所以起一个短一点的别名,方便编程,C++11 中规定了一种新的方法,使用别名声明 (alias declaration) 来定义类型的别名,即使用 using。

using的使用方法如下,使用typedef定义的别名和使用using定义的别名在语义上是等效的

2、using定义别名

using 新的类型 = 旧的类型;
using ptr = std::shared_ptr<> ptr;

3、typedef和using比较
using其实在可读性方面是要强于typedef的,等号左边是别名,右边是类型,这样是更加清晰的,举个例子体现一下:

// 使用typedef定义函数指针
typedef int(*func_ptr)(int, double);

// 使用using定义函数指针
using func_ptr1 = int(*)(int, double);

大致一看就可以看出对比,typedef中func_ptr是函数指针的别名,不太熟悉函数指针可能一眼看不出来typedef中的func_ptr是一个别名,所以这就体现出来了using的清晰简洁,可读性强。

模版的别名

1、使用typedef定义模版别名
使用typedef是无法定义模版别名的,例如现在需要定义一个以int整形为key的map,value是一个模版值,此时使用typedef定义如下:

#include     
#include     
#include     
      
template<class T>    
typedef  std::map<int, T> myMp;    
      
int main(){    
    myMp<int, int> mp;                                                                                                            
    return 0;    
}    

输出结果如下:

/root/test/main.cpp:6:1: 错误:‘typedef’的模板声明
 typedef  std::map<int, T> myMp

总结:typedef不支持对于模版的重定义别名,如果非要用typedef实现上述需求,可以如下做法:需要什么类型就写一个typedef,但是这样非常复杂,麻烦。

#include     
#include     
#include     

typedef  std::map<int, int> mp;
typedef  std::map<int, std::string> mp1;
typedef  std::map<int, double> mp2;
.........
      
int main(){    
    mp<int, int> mp;                                                                                                            
    return 0;    
}    

2、使用using定义模版别名

#include 
#include 
#include 
using namespace std;

template <typename T>
using mymap = map<int, T>;

int main(void)
{
    // map的value指定为string类型
    mymap<string> m;
    m.insert(make_pair(1, "luffy"));
    m.insert(make_pair(2, "ace"));

    // map的value指定为int类型
    mymap<int> m1;
    m1.insert(1, 100);
    m1.insert(2, 200);

    return 0;
}

上面的例子中通过使用 using 给模板指定别名,就可以基于别名非常方便的给 value 指定相应的类型,这样使编写的程序变得更加灵活,看起来也更加简洁一些。

using 语法和 typedef 一样,并不会创建出新的类型,它们只是给某些类型定义了新的别名。using 相较于 typedef 的优势在于定义函数指针别名时看起来更加直观,并且可以给模板定义别名。

可调用对象包装器和绑定器

可调用对象

可调用对象定义如下

1、是一个函数指针

int print(int a, double b)
{
    cout << a << b << endl;
    return 0;
}
// 定义函数指针
int (*func)(int, double) = &print;

2、是一个具有operator()成员函数的类对象(仿函数)

#include 
#include 
#include 
using namespace std;

struct Test
{
    // ()操作符重载
    void operator()(string msg)
    {
        cout << "msg: " << msg << endl;
    }
};

int main(void)
{
    Test t;
    t("hello");	// 仿函数
    return 0;
}

3、是一个可被转换为函数指针的类对象

#include 
#include 
#include 
using namespace std;

using func_ptr = void(*)(int, string);
struct Test
{
    static void print(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }

    // 将类对象转换为函数指针
    operator func_ptr()
    {
        return print;
    }
};

int main(void)
{
    Test t;
    // 对象转换为函数指针, 并调用
    t(19, "hello");

    return 0;
}

4、是一个类成员函数指针或者类成员指针

#include 
#include 
#include 
using namespace std;

struct Test
{
    void print(int a, string b)
    {
        cout << "name: " << b << ", age: " << a << endl;
    }
    int m_num;
};

int main(void)
{
    // 定义类成员函数指针指向类成员函数
    void (Test::*func_ptr)(int, string) = &Test::print;
    // 类成员指针指向类成员变量
    int Test::*obj_ptr = &Test::m_num;

    Test t;
    // 通过类成员函数指针调用类成员函数
    (t.*func_ptr)(19, "Monkey D. Luffy");
    // 通过类成员指针初始化类成员变量
    t.*obj_ptr = 1;
    cout << "number is: " << t.m_num << endl;

    return 0;
}

在上述这些满足条件的可调用对象对应的类型称为可调用类型,C++11通过提供std::function 和 std::bind统一了可调用对象的各种操作。

可调用对象包装器

std::function是可调用对象的包装器。它是一个类模板,可以容纳除了类成员(函数)指针之外的所有可调用对象。通过指定它的模板参数,它可以用统一的方式处理函数、函数对象、函数指针,并允许保存和延迟执行它们。

基本用法

#include 
std::function<返回值类型(参数列表)> name = 可调用对象;

举例说明:
1、包装一个普通函数

#include     
#include     
    
void test(int a, int b){    
    std::cout << "a: " << a << " b: " << b << std::endl;                                                                          
}    
    
int main(){    
    std::function<void(int, int)> f1 = test;    
    f1(1, 2);    
    return 0;    
}    

2、包装类静态成员函数

#include       
#include       
      
class Test{      
public:      
    static void test(int a, int b){      
        std::cout << "a: " << a << " b: " << b << std::endl;      
    }      
};      
      
int main(){                                                                                                                       
    std::function<void(int, int)> f = Test::test;    
    f(1, 2);    
    return 0;    
}     

3、包装仿函数

#include       
#include       
      
class Test{      
public:  
    void operator() (int a, int b){  
        std::cout << "a: " << a << " b: " << b << std::endl;  
    }  
};  
  
int main(){  
    Test t;  
    std::function<void(int, int)> f = t;
    f(1, 2);                                                                                                                      
    return 0;                                                                                                     
}        

std::function 可以将可调用对象进行包装,得到一个统一的格式,包装完成得到的对象相当于一个函数指针,和函数指针的使用方式相同,通过包装器对象就可以完成对包装的函数的调用了。

作为回调函数使用

#include     
#include     
    
class Test{    
public:    
    Test(const std::function<void()>& callback)    
        :_callback(callback){}    
    void notify(){    
        _callback();    
    }    
private:    
    std::function<void()> _callback;    
};    
    
class Test1{    
public:    
    void operator() (){    
        std::cout << "hello world" << std::endl;    
    }    
};    
               
int main(){                                                                      
    Test1 t1;                                             
    Test t(t1);                        
    t.notify();                                                                                                                   
    return 0;                               
}              

使用对象包装器std::function可以把Test1类中的仿函数包装成函数指针,通过进行函数指针的传递,在其他函数的合适的位置就可以调用这个包装好的仿函数了。

绑定器

std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以使用std::function进行保存,并延迟调用到任何我们需要的时候。其具有两大作用。

  • 将可调用对象与其参数一起绑定成一个仿函数
  • 将多元(参数个数为n,n>1)可调用对象转换为一元或者(n-1)元可调用对象,即只绑定部分参数,说白了就是讲一个多参的函数生成一个新函数,新函数参数变少

使用方式

1、绑定普通函数/变量

auto f = std::bind(可调用对象地址, 绑定的参数/占位符);

2、绑定类成员函数/变量

auto f = std::bind(类函数/成员地址, 类实例对象地址, 绑定的参数/占位符);

使用示例

#include     
#include     
    
void callback(int x, const std::function<void(int)>& f){    
    f(x);    
}    
    
void test1(int x){    
    std::cout << x << std::endl;    
}    
    
void test2(int x){    
    std::cout << x << std::endl;    
}    
    
int main(){    
    auto f1 = std::bind(test1, std::placeholders::_1);    
    callback(10, f1);    
    
    auto f2 = std::bind(test2, std::placeholders::_1);    
    callback(20, f2);                                                                                                             
    return 0;                                                                      
}        

std::bind通过绑定不同的函数,得到不同的结果,std::bind绑定器返回的是一个仿函数类型,得到的返回值可以直接赋值给一个std::function,在使用的时候我们并不需要关心绑定器的返回值类型,使用auto进行自动类型推导就可以了。

placeholders::_1 是一个占位符,代表这个位置将在函数调用时被传入的第一个参数所替代。同样还有其他的占位符 placeholders::_2、placeholders::_3、placeholders::_4、placeholders::_5 等……

#include 
#include 
using namespace std;

void output(int x, int y)
{
    cout << x << " " << y << endl;
}

int main(void)
{
    // 使用绑定器绑定可调用对象和参数, 并调用得到的仿函数
    bind(output, 1, 2)();
    bind(output, placeholders::_1, 2)(10);
    bind(output, 2, placeholders::_1)(10);

    // error, 调用时没有第二个参数
    // bind(output, 2, placeholders::_2)(10);
    // 调用时第一个参数10被吞掉了,没有被使用
    bind(output, 2, placeholders::_2)(10, 20);

    bind(output, placeholders::_1, placeholders::_2)(10, 20);
    bind(output, placeholders::_2, placeholders::_1)(10, 20);


    return 0;
}

输出结果如下:

1  2		// bind(output, 1, 2)();
10 2		// bind(output, placeholders::_1, 2)(10);
2 10		// bind(output, 2, placeholders::_1)(10);
2 20		// bind(output, 2, placeholders::_2)(10, 20);
10 20		// bind(output, placeholders::_1, placeholders::_2)(10, 20);
20 10		// bind(output, placeholders::_2, placeholders::_1)(10, 20);

std::bind 可以直接绑定函数的所有参数,也可以仅绑定部分参数。在绑定部分参数的时候,通过使用 std::placeholders 来决定空位参数将会属于调用发生时的第几个参数。

扩展

直接使用对象包装器std::function包装类成员函数或者类成员变量会报错

#include     
#include     
      
class Test{    
public:    
    void print(int a, int b){    
        std::cout << "a: " << a << " b: " << b << std::endl;    
    }    
    int _number;    
};    
      
int main(){    
    // 使用bind绑定类成员函数、类成员变量并存储到function    
    Test t;    
    std::function<void(int, int)> f1 = &Test::print;    
    f1(1, 3);    
    std::function<int&(void)> f2 = &Test::_number;                                                                              
    f2() = 666;                                                                                                   
    std::cout << "_number: " << t._number << std::endl;                                                           
    return 0;                                                                                                     
}           

输出结果如下:

/root/test/main.cpp: 在函数‘int main()’中:
/root/test/main.cpp:15:47: 错误:请求从‘void (Test::*)(int, int)’转换到非标量类型‘std::function<void(int, int)>’
     std::function<void(int, int)> f1 = &Test::print;
                                               ^
/root/test/main.cpp:17:43: 错误:请求从‘int Test::*’转换到非标量类型‘std::function<int&()>’
     std::function<int&(void)> f2 = &Test::_number;

上述使用方式会报错,需要使用std::bind来进行绑定后返回函数指针存放在function才可以使用,bind会将类成员函数地址和类实例化对象绑定在一起。

可调用对象包装器 std::function 不能实现对类成员函数指针或者类成员指针的包装的,但是通过绑定器 std::bind 的配合之后,就可以完美的解决这个问题了。

#include     
#include     
    
class Test{    
public:    
    void print(int a, int b){    
        std::cout << "a: " << a << " b: " << b << std::endl;    
    }    
    int _number;    
};    
    
int main(){    
    // 使用bind绑定类成员函数、类成员变量并存储到function    
    Test t;    
    std::function<void(int, int)> f1 = std::bind(&Test::print, &t, std::placeholders::_1, std::placeholders::_2);    
    f1(1, 3);                                                                                                                     
    std::function<int&(void)> f2 = std::bind(&Test::_number, &t);    
    f2() = 666;    
    std::cout << "_number: " << t._number << std::endl;    
    return 0;    
}              

程序输出结果如下:

a: 1 b: 3
_number: 666

在用绑定器绑定类成员函数或者成员变量的时候需要将它们所属的实例对象一并传递到绑定器函数内部。f1的类型是function,通过使用std::bind将Test的成员函数output的地址和对象t绑定,并转化为一个仿函数并存储到对象f1中。

使用绑定器绑定的类成员变量m_number得到的仿函数被存储到了类型为function的包装器对象f2中,并且可以在需要的时候修改这个成员。其中int是绑定的类成员的类型,并且允许修改绑定的变量,因此需要指定为变量的引用,由于没有参数因此参数列表指定为void。

friend

friend的概念及用法

friend 关键字在 C++ 中是一个比较特别的存在。因为在大多数编程语言中是没有提供 friend 关键字的,比如 Java。friend 关键字用于声明类的友元,友元可以无视类中成员的属性( public、protected 或是 private ),友元类或友元函数都可以访问,这就完全破坏了面向对象编程中封装性的概念。但有的时候,friend 关键字确实会让程序猿少写很多代码,因此 friend 还是在很多程序中被使用到。

1、在c++98时友元函数定义方式

friend class 类名; 

2、c++11进行改进,定义方式

friend 类名

类名可以是重定义的新名字

friend使用示例

#include                                                                
#include                                                                  
                                                                                  
class Test;                                                                       
                                                                                  
class A{                                                                          
public:                                                                           
    friend Test;                                                                  
    void print(){                                                                 
        std::cout << "class A name is : " << _name << std::endl;                  
    }                                                                             
private:                                                                          
    std::string _name = "A";                                                      
};                                                                                
                                                                                  
class B{                                                                          
public:                                                                           
    friend Test;                                                                  
    void print(){                                                                 
        std::cout << "class B name is : " << _name << std::endl;                  
    }                                                                             
private:                                                                          
    std::string _name = "B";                                                      
};                                                                                
                                                                                  
class Test{                                                                       
public:                                                                           
    void print(){                                                                 
        std::cout << "class Test a name is : " << a._name << " b name is : " << b._name << std::endl;    
    }                                                                             
private:                                                                          
    A a;                                                                          
    B b;                                                                          
};                                                                                
int main(){                                                                       
    Test t;                                  
    t.print();                                                                                                                    
    return 0;    
}    

输出结果为:

class Test a name is : A b name is : B

上述代码中,类A和类B分别把类Test作为自己的友元类,所以类Test可以去访问类A和类B中的私有成员。

类模版声明友元

C++11 标准中对友元的改进不大,但却带来应用的变化 —— 程序员可以为类模板声明友元了,这在 C++98 中是无法做到的。使用方法如下:

class Tom;

template<typename T>  
class Person
{
    friend T;
};

int main()
{
    Person<Tom> p;
    Person<int> pp;
    return 0;
}

Tom时Person的友元类,而Person< int > pp作为 int 类型的模板参数,友元声明被忽略,我们可以在模板实例化时才确定一个模板类是否有友元,以及谁是这个模板类的友元。

参考:
爱编程的大丙

你可能感兴趣的:(C++语言,c++,开发语言)