C++ STL标准模板库入门学习与应用

C++ 标准模板库的核心包括以下三个组件:

  1. 容器(Containers)
    • deque、list、vector、map等
  2. 算法(Algorithms)
    • 算法作用于容器。它们提供了执行各种操作的方式,包括对容器内容执行初始化、排序、搜索和转换等操作
  3. 迭代器(iterators)
    • 迭代器用于遍历对象集合的元素。这些集合可能是容器,也可能是容器的子集

C++ 11 新特新

T&& 右值引用 std::move

右值引用出现之前我们只能用const引用来关联临时对象(右值)所以我们不能修临时对象的内容,右值引用的出现就让我们可以取得临时对象的控制权,终于可以修改临时对象了!

int main()
{
    int i = 42;
    int &r = i; // ok: r refers to i
    int &&rr = i;   // error: cannot bind an rvalue reference to an lvalue
    int &r2 = i * 42;   // error: i * 42 is an rvalue
    const int &r3 = i * 42; // ok: we can bind a reference to  const  to an rvalue
    int &&rr2 = i * 42;
    int &&rr3 = rr2;   // error: the expression rr2 is an lvalue!
    return 0;
}

即凡是可以 vartype varname; 这样定义出来的变量(variable)其自身都是左值。

std::move相关。 右值引用因为绑定对象即将被销毁,意味着没有人会继续访问他们,所以就可以把他们(的资源)steal(偷)过来。 虽然不能将右值引用绑在左值上,但通过利用utility头文件新增的函数模板move,它返回传入对象的右值引用,可以达到 steal的效果。

    int &&rr3 = std::move(rr2); // ok

再提醒:一旦使用了move,编译器就默认传入对象已经不打算使用了,是可以被销毁的,move之后该对象的值已经不确定,不要再访问。还有由于对象偷取与复制的差别巨大,不注意会产生非常难定位的bug,所以所有使用move的地方一定要使用全称std::move,给大家以提醒。(其实c++11在algorithm头文件也新增了一个move,参数与意义都与此截然不同)。

#include <iostream>
using namespace std;

class HugeMem{
    public:
        HugeMem(int size): sz(size > 0 ? size : 1) {
            c = new int[sz];
        }
        ~HugeMem() { cout<<"HugeMem 析构\n";delete [] c; }
        HugeMem(HugeMem && hm): sz(hm.sz), c(hm.c) {
            cout<<"HugeMem move 构造\n";
            hm.c = nullptr;
        }
        int * c;
        int sz;
};
class Moveable{
    public:
        Moveable():i(new int(3)), h(1024) {}
        ~Moveable() { cout<<"Moveable 析构\n";delete i; }
        Moveable(Moveable && m):
            i(m.i), h(move(m.h)) {      // 强制转为右值,以调用移动构造函数
                m.i = nullptr;
            }
        int* i;
        HugeMem h;
};

Moveable GetTemp() {
    //Moveable tmp = Moveable();
    Moveable tmp;
    cout << hex << "Huge Mem from " << __func__
        << " @" << tmp.h.c << endl; // Huge Mem from GetTemp @0x603030
    return tmp;
}

int main() {
    Moveable a(GetTemp());
    cout << hex << "Huge Mem from " << __func__
        << " @" << a.h.c << endl;   // Huge Mem from main @0x603030
}

早在C++11之前编译器就把优化几乎做到了极致——局部变量返回到函数外部并赋值给外部变量这个过程基本上不存在任何多余的临时变量构造和析构,这比move机制更加高效。显式指定move以后,return std::move(localvar)这里会强行从localvar移动构造一个临时变量temp,然后return temp(temp这里会有RVO优化)。

auto for循环

需要注意的是,auto不能用来声明函数的返回值。但如果函数有一个尾随的返回类型时,auto是可以出现在函数声明中返回值位置。这种情况下,auto并不是告诉编译器去推断返回类型,而是指引编译器去函数的末端寻找返回值类型。在下面这个例子中,函数的返回值类型就是operator+操作符作用在T1、T2类型变量上的返回值类型。

template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> **decltype**(t1 + t2)
{
   return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double

auto与for配合使用

std::map<std::string, std::vector<int>> map;
std::vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
map["one"] = v;

for(const auto& kvp : map)
{
  std::cout << kvp.first << std::endl;

  for(auto v : kvp.second)
  {
     std::cout << v << std::endl;
  }
}

int arr[] = {1,2,3,4,5};
for(int& e : arr)
{
  e = e*e;
}

std::lambda

“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。

C++11 的 lambda 表达式规范如下:

  • [ capture ] ( params ) mutable exception attribute -> ret { body } (1)
  • [ capture ] ( params ) -> ret { body } (2)
  • [ capture ] ( params ) { body } (3)
  • [ capture ] { body } (4)

其中

(1) 是完整的 lambda 表达式形式, (2) const 类型的 lambda 表达式,该类型的表达式不能改捕获("capture")列表中的值。 (3)省略了返回值类型的 lambda 表达式,但是该 lambda 表达式的返回类型可以按照下列规则推演出来: 如果 lambda 代码块中包含了 return 语句,则该 lambda 表达式的返回类型由 return 语句的返回类型确定。 如果没有 return 语句,则类似 void f(...) 函数。 省略了参数列表,类似于无参函数 f()。

[] // 不引用外部变量 [x, &y] // x引用方式 ,y 传值 [&] // 任何使用的外部变量都是引用方式。 [=] // 任何被使用到的外部都是传值方式。 [&, x] // 除x传值以外其他的都以引用方式。 [=, &z] // 除z引用以外其他的都是以传值方式使用。

int main()
{
    std::vector<int> c { 1,2,3,4,5,6,7 };
    int x = 5;
    c.erase(std::remove_if(c.begin(), c.end(), [x](int n) { return n < x; } ), c.end());

    std::cout << "c: ";
    for (auto i: c) {
        std::cout << i << ' ';
    }
    std::cout << '\n';

    // 可以用auto 接收一个lambda 表达式。
    auto func1 = [](int i) { return i+4; };
    std::cout << "func1: " << func1(6) << '\n';

    // std::function 也可以接收lambda 表达式。

    std::function<int(int)> func2 = [](int i) { return i+4; };
    std::cout << "func2: " << func2(6) << '\n';

    std::function<int()> func3 = [x]{return x;};
    std::cout << "func3: " << func3() << '\n';


    std::vector<int> someList = {1,2,3};  //这里是c++11
    int total = 0;
    double sum = 0.0f;
    std::for_each(someList.begin(), someList.end(), [&total](int x) { total += x; });
    std::cout << total << '\n';
    std::for_each(someList.begin(), someList.end(), [&](int x){ total += x; sum += x;});
    std::cout << total << '\n';
    std::cout << sum << '\n';

    //再写一种简单的lambda
    [](){std::cout<<"就地展开的lambda\n";}();

}

bind

std::bind是STL实现函数组合概念的重要手段,std::bind绑定普通函数(函数指针)、lambda表达式、成员函数、成员变量、模板函数等

#include <iostream>
#include <functional>

void f(int n1, int n2, int n3, const int& n4, int n5)
{
        std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n';
}

int g(int n1)
{
        return n1;
}

struct Foo {
    void print_sum(int n1, int n2)
    {
        std::cout << n1+n2 << '\n';
    }
    static void static_func(std::function<int(int)> f,int n)
    {
        std::cout<<"call static_func\n";
        std::cout<<"f(n):\t"<<f(n)<<"\n";
    }
    int data = 10; //c++11 支持声明是就初始化值
};

int main()
{
    using namespace std::placeholders;

    // std::cref(n) 表示要把n以引用的方式传入  
    int n = 7;
    auto f1 = std::bind(f, _2, _1, 42, std::cref(n), n);
    n = 10;
    f1(1, 2, 1001); // 1 is bound by _1, 2 is bound by _2, 1001 is unused


    // 绑定一个子表达式,用_3替换了 其他位置的变量
    // std::bind(g, _3) 在这里已经表示int
    auto f2 = std::bind(f, _4, std::bind(g, _4), _4, 4, 5);
    f2(10, 11, 12 ,13);

    // 绑定成员函数
    Foo foo;
    auto f3 = std::bind(&Foo::print_sum, foo, 95, _1);
    f3(5);

    // 绑定成员变量
    auto f4 = std::bind(&Foo::data, _1);
    std::cout << f4(foo) << '\n';


    // 绑定静态成员函数
    auto f5 = std::bind(&Foo::static_func,g,_1);

    f5(3);
}

std::function

通过std::function对C++中各种可调用实体(普通函数、Lambda表达式、函数指针、以及其它函数对象等)的封装,形成一个新的可调用的std::function对象;让我们不再纠结那么多的可调用实体。

转换后的std::function对象的参数能转换为可调用实体的参数; 可调用实体的返回值能转换为std::function对象的返回值。 std::function对象最大的用处就是在实现函数回调(实际工作中就是用到了这一点),使用者需要注意,它不能被用来检查相等或者不相等,但是可以与NULL或者nullptr进行比较。

#include <functional>
#include <iostream>
using namespace std;

std::function< int(int)> Functional;

// 普通函数
int TestFunc(int a)
{
return a;
}

// Lambda表达式
auto lambda = [](int a)->int{ return a; };

// 仿函数(functor)
class Functor
{
public:
int operator()(int a)
{
return a;
}
};

// 1.类成员函数
// 2.类静态函数
class TestClass
{
public:
int ClassMember(int a) { return a; }
static int StaticMember(int a) { return a; }
};

int main()
{
// 普通函数
Functional = TestFunc;
int result = Functional(10);
cout << "普通函数:"<< result << endl;

// Lambda表达式
Functional = lambda;
result = Functional(20);
cout << "Lambda表达式:"<< result << endl;

// 仿函数
Functor testFunctor;
Functional = testFunctor;
result = Functional(30);
cout << "仿函数:"<< result << endl;

// 类成员函数
TestClass testObj;
Functional = std::bind(&TestClass::ClassMember, testObj, std::placeholders::_1);
result = Functional(40);
cout << "类成员函数:"<< result << endl;

// 类静态函数
Functional = TestClass::StaticMember;
result = Functional(50);
cout << "类静态函数:"<< result << endl;

return 0;
}

initializer_list

过往,我们这样给vector赋值:

std::vector v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);

需要感谢的是,C++11让你更方便。

std::vector v = { 1, 2, 3, 4 };

这就是所谓的initializer list。更进一步,有一个关键字叫initializer list

#include <iostream>
#include <vector>
#include <map>
#include <string>
#include <typeinfo>

class MyNumber
{
public:
    MyNumber(const std::initializer_list<int> &v) {
        for (auto itm : v) {
            mVec.push_back(itm);
        }
    }

    void print() {
        for (auto itm : mVec) {
            std::cout << itm << " ";
        }
    }
private:
    std::vector<int> mVec;
};

class Test {
public:
    void show()
    {
        for(auto kv : nameToBirthday)
        {
            std::cout<<"key:\t"<<kv.first<<"\tvalue:\t"<<kv.second<<"\n";
        }
    }
private:
    static std::map<std::string, std::string> nameToBirthday;
};
std::map<std::string,std::string> Test::nameToBirthday  = {
    {"lisi", "18841011"},
    {"zhangsan", "18850123"},
    {"wangwu", "18870908"},
    {"zhaoliu", "18810316"}
};

class CompareClass
{
public:
    CompareClass (int,int)
    {std::cout<<"call old const\n";}
    CompareClass (std::initializer_list <int> )
    {std::cout<<"call initializer_list const\n";}
};


int main()
{
    MyNumber m = { 1, 2, 3, 4 };
    m.print();  // 1 2 3 4

    Test t;
    t.show();

    std::map<int,int> ii_map = {{1,1},{2,2}};

    CompareClass foo {10,20};  // calls initializer_list ctor
    CompareClass bar (10,20);  // calls first constructor


    for(auto kv : ii_map)
    {
        std::cout<<"key:\t"<<typeid(kv.first).name()<<"\n";
    }
    return 0;
}

注意

本文内容转自博客:http://blog.csdn.net/tangliguantou/article/details/50549751

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容