C++2.0程序员

C++ 11 常用特性的使用经验总结(一)

2019-02-12  本文已影响19人  Python编程导师

C++11已经出来很久了,网上也早有很多优秀的C++11新特性的总结文章,在编写本文之前,笔者在工作和学习中学到的关于C++11方面的知识,也得益于很多其他网友的总结。本文是在学习的基础上,加上在日常工作中的使用C++11的一些总结、经验和感悟,整理出来,分享给大家,希望对各位读者有帮助,文章中的总结可能存在很多不完整或有错误的地方,也希望读者指出。

1、关键字及新语法

C++11相比C++98增加了许多关键字及新的语法特性,很多人觉得这些语法可有可无,没有新特性也可以用传统C++去实现。

也许吧,但个人对待新技术总是抱着渴望而热衷的态度对待,也许正如很多人所想,用传统语法也可以实现,但新技术可以让你的设计更完美。这就如同在原来的维度里,你要完成一件事情,需要很多个复杂的步骤,但在新语法特性里,你可以从另外的维度,很干脆,直接就把原来很复杂的问题用很简单的方法解决了,我想着就是新的技术带来的一些编程体验上非常好的感觉。大家也不要觉得代码写得越复杂就先显得越牛B,有时候在理解的基础上,尽量选择“站在巨人的肩膀上”,可能你会站得更高,也看得更远。

本章重点总结一些常用c++11新语法特点。后续会在本人理解的基础上,会继续在本博客内更新或增加新的小章节。

1.1、auto 关键字及用法

A、auto 关键字能做什么?

auto并没有让C++成为弱类型语言,也没有弱化变量什么,只是使用auto的时候,编译器根据上下文情况,确定auto变量的真正类型。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

auto AddTest(int a, int b)

{

    return a + b;

}

int main()

{

    auto index = 10;

    auto str = "abc";

    auto ret = AddTest(1,2);

    std::cout "index:"  std::endl;

    std::cout "str:"  std::endl;

    std::cout "res:"  std::endl;

}

是的,你没看错,代码也没错,auto在C++14中可以作为函数的返回值,因此auto AddTest(int a, int b)的定义是没问题的。

运行结果:

B、auto 不能做什么?

auto作为函数返回值时,只能用于定义函数,不能用于声明函数。

//Test.h 示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

#pragma once

class Test

{

public:

    auto TestWork(int a ,int b);

};

如下函数中,在引用头文件的调用TestWork函数是,编译无法通过。

但如果把实现写在头文件中,可以编译通过,因为编译器可以根据函数实现的返回值确定auto的真实类型。如果读者用过inline类成员函数,这个应该很容易明白,此特性与inline类成员函数类似。

//Test.h 示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

#pragma once

class Test

{

public:

    auto TestWork(int a, int b)

    {

        return a + b;

    }

};

1.2、nullptr 关键字及用法

为什么需要nullptr ? NULL 有什么毛病?

我们通过下面一个小小的例子来发现NULL的一点问题:

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

class Test

{

public:

    void TestWork(int index)

    {

        std::cout "TestWork 1"  std::endl;

    }

    void TestWork(int * index)

    {

        std::cout "TestWork 2"  std::endl;

    }

};

int main()

{

    Test test;

    test.TestWork(NULL);

    test.TestWork(nullptr);

}

运行结果:

NULL在c++里表示空指针,看到问题了吧,我们调用test.TestWork(NULL),其实期望是调用的是void TestWork(int * index),但结果调用了void TestWork(int index)。但使用nullptr的时候,我们能调用到正确的函数。

1.3、for 循环语法

习惯C#或java的同事之前使用C++的时候曾吐槽C++ for循环没有想C#那样foreach的用法,是的,在C++11之前,标准C++是无法做到的。熟悉boost库读者可能知道boost里面有foreach的宏定义BOOST_FOREACH,但个人觉得看起并不是那么美观。

OK,我们直接以简单示例看看用法吧。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

int main()

{

    int numbers[] = { 1,2,3,4,5 };

    std::cout "numbers:"  std::endl;

    for (auto number : numbers)

    {

        std::cout  std::endl;

    }

}

以上用法不仅仅局限于数据,STL容器都同样适用。

2、STL 容器

C++11在STL容器方面也有所增加,给人的感觉就是越来越完整,越来越丰富的感觉,可以让我们在不同场景下能选择跟具合适的容器,提高我们的效率。

本章节总结C++11新增的一些容器,以及对其实现做一些简单的解释。

2.1、std::array

个人觉得std::array跟数组并没有太大区别,对于多维数据使用std::array,个人反而有些不是很习惯吧。

std::array相对于数组,增加了迭代器等函数(接口定义可参考C++官方文档)。

//示例代码1.0 http://www.cnblogs.com/feng-sc/p/5710724.html

#include int main()

{

    std::arrayint, 4> arrayDemo = { 1,2,3,4 };

    std::cout "arrayDemo:"  std::endl;

    for (auto itor : arrayDemo)

    {

        std::cout  std::endl;

    }

    int arrayDemoSize = sizeof(arrayDemo);

    std::cout "arrayDemo size:"  std::endl;

    return 0;

}

运行结果:

打印出来的size和直接使用数组定义结果是一样的。

2.2、std::forward_list

std::forward_list为从++新增的线性表,与list区别在于它是单向链表。我们在学习数据结构的时候都知道,链表在对数据进行插入和删除是比顺序存储的线性表有优势,因此在插入和删除操作频繁的应用场景中,使用list和forward_list比使用array、vector和deque效率要高很多。

#include int main()

{

    std::forward_listint> numbers = {1,2,3,4,5,4,4};

    std::cout "numbers:"  std::endl;

    for (auto number : numbers)

    {

        std::cout  std::endl;

    }

    numbers.remove(4);

    std::cout "numbers after remove:"  std::endl;

    for (auto number : numbers)

    {

        std::cout  std::endl;

    }

    return 0;

}

运行结果:

2.3、std::unordered_map

std::unordered_map与std::map用法基本差不多,但STL在内部实现上有很大不同,std::map使用的数据结构为二叉树,而std::unordered_map内部是哈希表的实现方式,哈希map理论上查找效率为O(1)。但在存储效率上,哈希map需要增加哈希表的内存开销。

下面代码为C++官网实例源码实例:

//webset address: http://www.cplusplus.com/reference/unordered_map/unordered_map/bucket_count/

#include

#include string>

#include int main()

{

    std::unordered_mapstring, std::string> mymap =

    {

        { "house","maison" },

        { "apple","pomme" },

        { "tree","arbre" },

        { "book","livre" },

        { "door","porte" },

        { "grapefruit","pamplemousse" }

    };

    unsigned n = mymap.bucket_count();

    std::cout "mymap has " " buckets.n";

    for (unsigned i = 0; ii)

    {

        std::cout "bucket #" " contains: ";

        for (auto it = mymap.begin(i); it != mymap.end(i); ++it)

            std::cout "[" first ":" second "] ";

        std::cout "n";

    }

    return 0;

}

运行结果:

运行结果与官网给出的结果不一样。实验证明,不同编译器编译出来的结果不一样,如下为linux下gcc 4.6.3版本编译出来的结果。或许是因为使用的哈希算法不一样,个人没有深究此问题。

2.4、std::unordered_set

std::unordered_set的数据存储结构也是哈希表的方式结构,除此之外,std::unordered_set在插入时不会自动排序,这都是std::set表现不同的地方。

我们来测试一下下面的代码:

#include

#include string>

#include

#include set>

int main()

{

    std::unordered_setint> unorder_set;

    unorder_set.insert(7);

    unorder_set.insert(5);

    unorder_set.insert(3);

    unorder_set.insert(4);

    unorder_set.insert(6);

    std::cout "unorder_set:"  std::endl;

    for (auto itor : unorder_set)

    {

        std::cout  std::endl;

    }

    std::setint> set;

    set.insert(7);

    set.insert(5);

    set.insert(3);

    set.insert(4);

    set.insert(6);

    std::cout "set:"  std::endl;

    for (auto itor : set)

    {

        std::cout  std::endl;

    }

}

运行结果:

3、多线程

在C++11以前,C++的多线程编程均需依赖系统或第三方接口实现,一定程度上影响了代码的移植性。C++11中,引入了boost库中的多线程部分内容,形成C++标准,形成标准后的boost多线程编程部分接口基本没有变化,这样方便了以前使用boost接口开发的使用者切换使用C++标准接口,把容易把boost接口升级为C++接口。

我们通过如下几部分介绍C++11多线程方面的接口及使用方法。

3.1、std::thread

std::thread为C++11的线程类,使用方法和boost接口一样,非常方便,同时,C++11的std::thread解决了boost::thread中构成参数限制的问题,我想着都是得益于C++11的可变参数的设计风格。

我们通过如下代码熟悉下std::thread使用风格。

#include void threadfun1()

{

    std::cout "threadfun1 - 1rn"  std::endl;

    std::this_thread::sleep_for(std::chrono::seconds(1));

    std::cout "threadfun1 - 2"  std::endl;

}

void threadfun2(int iParam, std::string sParam)

{

    std::cout "threadfun2 - 1"  std::endl;

    std::this_thread::sleep_for(std::chrono::seconds(5));

    std::cout "threadfun2 - 2"  std::endl;

}

int main()

{

    std::thread t1(threadfun1);

    std::thread t2(threadfun2, 10, "abc");

    t1.join();

    std::cout "join"  std::endl;

    t2.detach();

    std::cout "detach"  std::endl;

}

运行结果:

有以上输出结果可以得知,t1.join()会等待t1线程退出后才继续往下执行,t2.detach()并不会并不会把,detach字符输出后,主函数退出,threadfun2还未执行完成,但是在主线程退出后,t2的线程也被已经被强退出。

3.2、std::atomic

std::atomic为C++11分装的原子数据类型。

什么是原子数据类型?

从功能上看,简单地说,原子数据类型不会发生数据竞争,能直接用在多线程中而不必我们用户对其进行添加互斥资源锁的类型。从实现上,大家可以理解为这些原子类型内部自己加了锁。

我们下面通过一个测试例子说明原子类型std::atomic_int的特点。

下面例子中,我们使用10个线程,把std::atomic_int类型的变量iCount从100减到1。

#include

#include

#include

std::atomic_bool bIsReady = false;

std::atomic_int iCount = 100;

void threadfun1()

{

    if (!bIsReady) {

        std::this_thread::yield();

    }

    while (iCount > 0)

    {

        printf("iCount:%drn", iCount--);

    }

}

int main()

{

    std::atomic_bool b;

    std::list lstThread;

    for (int i = 0; i 10; ++i)

    {

        lstThread.push_back(std::thread(threadfun1));

    }

    for (auto& th : lstThread)

    {

        th.join();

    }

}

运行结果:

注:屏幕太短的原因,上面结果没有截完屏

从上面的结果可以看到,iCount的最小结果都是1,单可能不是最后一次打印,没有小于等于0的情况,大家可以代码复制下来多运行几遍对比看看。

3.3、std::condition_variable

C++11中的std::condition_variable就像Linux下使用pthread_cond_wait和pthread_cond_signal一样,可以让线程休眠,直到别唤醒,现在在从新执行。线程等待在多线程编程中使用非常频繁,经常需要等待一些异步执行的条件的返回结果。

OK,在此不多解释,下面我们通过C++11官网的例子看看。

// webset address: http://www.cplusplus.com/reference/condition_variable/condition_variable/%20condition_variable

// condition_variable example

#include <iostream>           // std::cout

#include <thread>             // std::thread

#include <mutex>              // std::mutex, std::unique_lock

#include <condition_variable> // std::condition_variable

std::mutex mtx;

std::condition_variable cv;

bool ready = false;

void print_id(int id) {

    std::unique_lock<std::mutex> lck(mtx);

    while (!ready) cv.wait(lck);

    // ...

    std::cout << "thread " << id << '\n';

}

void go() {

    std::unique_lock<std::mutex> lck(mtx);

    ready = true;

    cv.notify_all();

}

int main()

{

    std::thread threads[10];

    // spawn 10 threads:

    for (int i = 0; i<10; ++i)

        threads[i] = std::thread(print_id, i);

    std::cout << "10 threads ready to race...\n";

    go();                       // go!

    for (auto& th : threads) th.join();

    return 0;

}

运行结果:

上面的代码,在14行中调用cv.wait(lck)的时候,线程将进入休眠,在调用33行的go函数之前,10个线程都处于休眠状态,当22行的cv.notify_all()运行后,14行的休眠将结束,继续往下运行,最终输出如上结果。

在接触第二部分之前不妨看一下笔者介绍的编程学习群,本人从事在线教育多年,将自己的资料整合建了一个QQ群,对于有兴趣一起交流学习c/c++的初学者可以加群:941636044,里面有大神会给予解答,也会有许多的资源可以供大家学习分享,欢迎大家前来一起学习进步!

上一篇下一篇

猜你喜欢

热点阅读