zoukankan      html  css  js  c++  java
  • C++简单入门

    1. 简单入门

    1. helloworld

    // study1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include <iostream>
    
    int main()
    {
        std::cout << "Hello World!\n";
        return 0;
    }
    
    // 运行程序: Ctrl + F5 或调试 >“开始执行(不调试)”菜单
    // 调试程序: F5 或调试 >“开始调试”菜单
    
    // 入门使用技巧: 
    //   1. 使用解决方案资源管理器窗口添加/管理文件
    //   2. 使用团队资源管理器窗口连接到源代码管理
    //   3. 使用输出窗口查看生成输出和其他消息
    //   4. 使用错误列表窗口查看错误
    //   5. 转到“项目”>“添加新项”以创建新的代码文件,或转到“项目”>“添加现有项”以将现有代码文件添加到项目
    //   6. 将来,若要再次打开此项目,请转到“文件”>“打开”>“项目”并选择 .sln 文件

    结果会打印hello world!

    2. 调用方法实现求和

    // & - 指针运算符,返回变量的地址。例如 &a; 将给出变量的实际地址。
    // * - 指针运算符.指向一个变量。例如,*var; 将指向变量 var。
    #include <iostream>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    // 简单的相加
    int addNum(int i) {
        return i+1;
    }
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        int temp = *i;
        *i = temp+1;
    }
    
    int main()
    {
        int num = 0;
        int num2 = addNum(num);
        cout << num2 << endl; // 1
        cout << num << endl; // 0
    
        addNum2(&num);
        addNum2(&num);
        cout << num << endl; // 2
    
    }

      在C++中*代表指针,而&代表引用,而*&代表指针引用

      指针是一个变量(它的值是一个地址),而指针引用指的是这个变量的引用;在C++中如果参数不是引用的话会调用参数对象的拷贝构造函数(习惯java语法的函数调用直接引用传递就好了),所以如果有需求想改变指针所指的对象(换句话说,就是要改变指针里面存的地址),就要使用指针引用。

    2. 多线程

    c++11 提供了新的创建线程的方式。

    #include <thread>
    // 创建一个thread 对象,名称为t。 fun是需要调用的函数的名称, param 是调用的参数
    thread t(fun, param)
    
    t.detach(); // 异步操作
    t.join(); // 等待上面fun 函数结束后执行, 相当于java 的join

    也可以使用lambda 表达式,两者结合一起实现一个线程。

    #include <thread>
    // 创建一个thread 对象,名称为t。 fun是需要调用的函数的名称, param 是调用的参数
    thread t([闭包变量](paramType param) {
        // code
    }, param);
    
    t.detach(); // 异步操作
    t.join(); // 等待上面fun 函数结束后执行, 相当于java 的join

    例如:

    (1) 不使用lambda

    // pro1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include <iostream>
    #include <thread>
    using namespace std;
    
    int addNum(int i) {
        cout << std::this_thread::get_id() << endl;
        return i + 1;
    }
    
    int main()
    {
        cout << "main" << std::this_thread::get_id() << endl;
        // 创建一个thread 对象,名称为t。 fun是需要调用的函数的名称, param 是调用的参数
        thread t(addNum, 1);
        // t.detach(); // 异步操作
        t.join(); // 等待上面fun 函数结束后执行, 相当于java 的join
    }

    (2) 使用lambda

    // pro1.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include <iostream>
    #include <thread>
    using namespace std;
    
    int main()
    {
        cout << "main" << std::this_thread::get_id() << endl;
        // 创建一个thread 对象,名称为t。 fun是需要调用的函数的名称, param 是调用的参数
        int num = 1;
        thread t([num](int num2) {
            cout << std::this_thread::get_id() << endl;
            cout << num2 + num << endl; // 4
            }, 3);
        // t.detach(); // 异步操作
        t.join(); // 等待上面fun 函数结束后执行, 相当于java 的join
    }

    1. 开100个线程实现求和

    #include <iostream>
    #include <thread>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        *i = ++ * i;
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

    结果:

    num     100

    2. 存在线程安全问题:

    #include <iostream>
    #include <thread>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        int temp = *i;
        temp++;
        cout << "addNum2" << std::this_thread::get_id() << endl;
        *i = temp;
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

    这里num 一直为2. 猜测是因为cout, 耗时比较长。 所以30个线程同时拿到为0的数据加一后在cout执行长时间操作后都改为1.

    3. 线程安全问题

      上面多线程求和有线程安全的问题,在java 里面一般会使用原子类或者使用synchronized、lock 进行同步控制。下面研究c++ 的线程安全机制。

    1. std::mutex 加锁

    #include <iostream>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    int main()
    {
        std::mutex _mutex;
        _mutex.lock();
        cout << "getLock: " << std::this_thread::get_id << endl;
        _mutex.unlock();
        cout << "unlock: " << std::this_thread::get_id << endl;
    }

    结果:

       这个锁好像不支持重入,也就是一个线程不能多次lock。

    加锁解决上面的问题:

    #include <iostream>
    #include <thread>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    std::mutex _mutex;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        _mutex.lock();
        int temp = *i;
        temp++;
        cout << "addNum2" << std::this_thread::get_id() << endl;
        *i = temp;
        _mutex.unlock();
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

     2. lock_guard 加锁

      lock_guard 用来管理一个 std::mutex对象,通过定义一个 lock_guard 一个对象来管理 std::mutex 的上锁和解锁。在 lock_guard 初始化的时候进行上锁,然后在 lock_guard 析构的时候进行解锁。这样避免对 std::mutex 的上锁和解锁的管理。

    它的特点如下:

    (1) 创建即加锁,作用域结束自动析构并解锁,无需手工解锁

    (2) 不能中途解锁,必须等作用域结束才解锁

    (3) 不能复制

    #include <iostream>
    #include <thread>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    std::mutex _mutex;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        const std::lock_guard<std::mutex> lock(_mutex);
        int temp = *i;
        temp++;
        cout << "addNum2" << std::this_thread::get_id() << endl;
        *i = temp;
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

      效果同上面加锁一样。

    查看其源码:

        explicit lock_guard(_Mutex& _Mtx) : _MyMutex(_Mtx) { // construct and lock
            _MyMutex.lock();
        }
    
        lock_guard(_Mutex& _Mtx, adopt_lock_t) : _MyMutex(_Mtx) {} // construct but don't lock
    
        ~lock_guard() noexcept {
            _MyMutex.unlock();
        }

    3. unique_lock

      unique_lock 是通用互斥包装器,允许延迟锁定、锁定的有时限尝试、递归锁定、所有权转移和与条件变量一同使用。unique_lock比lock_guard使用更加灵活,功能更加强大。使用unique_lock需要付出更多的时间、性能成本。

    1. 自动加锁解锁:

    #include <iostream>
    #include <thread>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    std::mutex _mutex;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        std::unique_lock<std::mutex> lock(_mutex); // 等价于 std::lock_guard<std::mutex> lock(_mutex); 自动加锁解锁
        int temp = *i;
        temp++;
        cout << "addNum2" << std::this_thread::get_id() << endl;
        *i = temp;
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

    2. 手动加锁解锁

    #include <iostream>
    #include <thread>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    std::mutex _mutex;
    
    // 传引用后,对引用的值自增
    void addNum2(int* i) {
        std::unique_lock<std::mutex> lock(_mutex, std::defer_lock);
        lock.lock();
        int temp = *i;
        temp++;
        cout << "addNum2" << std::this_thread::get_id() << endl;
        *i = temp;
        lock.unlock(); // 这句可以不写,让析构函数自动释放锁
    }
    
    int main()
    {
        int num = 0;
        for (int i = 0; i < 100; i++) {
            thread t(addNum2, &num);
            // 允许后台执行
            t.detach();
        }
    
        // 主线程休眠3s, 等待上面线程执行完毕
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "num\t" << num << endl;
        return 0;
    
    }

    补充: 递归锁的使用

    #include <iostream>
    #include <mutex>
    // 当使用<iostream>时,该头文件没有定义全局命名空间,必须使用namespace std,这样才能使用类似于cout这样的C++标识符
    using namespace std;
    
    std::recursive_mutex _mutex;
    
    int main()
    {
        std::cout << "Hello World!\n";
        cout << "currentThreadId: " << std::this_thread::get_id() << endl;
        _mutex.lock();
        _mutex.lock();
        _mutex.unlock();
        _mutex.unlock();
        std::cout << "Hello World!\n";
        return 0;
    
    }

     4. 线程间通信

       实现一个有界阻塞队列, 也可以理解为生产者消费者模式的实现。使用锁加条件变量实现线程安全加线程间通信。

    #include <iostream>
    #include <mutex>
    #include <vector>
    #include <condition_variable>
    #include <list>
    
    using namespace std;
    
    class MyBlockingList {
        private:
            int capacity = 3;
            std::list<int> datas;
            std::mutex _mutex;
            std::condition_variable not_full_cond;
            std::condition_variable not_emp_cond;
        public:
            int getCapacity() {
                return capacity;
            }
            void setCapacity(int capacityParam) {
                capacity = capacityParam;
            }
            MyBlockingList(int capacityParam) : capacity(capacityParam) {}
            void add(int num) {
                std::unique_lock<std::mutex> lock(_mutex);
                while (capacity == datas.size()) {
                    not_full_cond.wait(lock);
                }
    
                cout << std::this_thread::get_id() << " produce: " << num << "\n";
                datas.push_back(num);
                not_emp_cond.notify_all();
            }
            int consume() {
                std::unique_lock<std::mutex> lock(_mutex);
                while (datas.size() == 0) {
                    not_emp_cond.wait(lock);
                }
    
                int num = datas.front();
                datas.pop_front();
                not_full_cond.notify_all();
                cout << std::this_thread::get_id() << " consume: " << num << "\n";
                return num;
            }
    };
    
    // 通过函数传递需要传递引用
    void test(MyBlockingList& listData) {
        cout << "main currentThreadId: " << listData.getCapacity() << "\n";
        listData.setCapacity(8);
    }
    
    int main()
    {
        MyBlockingList myblocking(30);
        vector<thread> threads;
        int num = 0;
        for (int i = 0; i < 5; i++) {
            if (i % 2 == 0) {
                threads.push_back(thread([&myblocking, &num]() {
                    while (true) {
                        this_thread::sleep_for(std::chrono::seconds(2));
                        num++;
                        // cout << std::this_thread::get_id() << " prepare produce: " << num << "\n";
                        myblocking.add(num);
                    }
                }));
            }
            else {
                threads.push_back(thread([&myblocking]() {
                    while (true) {
                        this_thread::sleep_for(std::chrono::seconds(2));
                        myblocking.consume();
                    }
                 }));
            }
        }
    
        for (int i = 0; i < 3; i++) {
            threads.at(i).join();
        }
        return 0;
    
    }

      上面代码实际类似于java 中的下面代码。 生产和消费的时候获取锁,获取到锁之后进行生产消费。到达队列最大大小或者队列为空后分别进行等待。

    package com.xm.ggn.test;
    
    import org.apache.commons.lang3.RandomStringUtils;
    
    import java.util.LinkedList;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class PlainTest {
    
    
        public static void main(String[] args) throws InterruptedException {
            int maxPoolSize = 5;
            ContainContext containContex = new ContainContext(maxPoolSize);
            // 基于wait notify 实现生产者消费者
            int producerNum = 3;
            int consumerNum = 1;
            // producer
            for (int index = 0; index < producerNum; index++) {
                Thread producer = new Thread(() -> {
                    while (true) {
                        try {
                            Thread.sleep(1 * 1000);
                        } catch (InterruptedException e) {
                        }
                        containContex.addElement(RandomStringUtils.randomNumeric(3));
                    }
                });
                producer.setName("producer" + index);
                producer.start();
            }
    
            // producer
            for (int index = 0; index < consumerNum; index++) {
                Thread producer = new Thread(() -> {
                    while (true) {
                        try {
                            Thread.sleep(1 * 1000);
                        } catch (InterruptedException e) {
                        }
                        containContex.removeFirst();
                    }
                });
                producer.setName("consumer" + index);
                producer.start();
            }
        }
    }
    
    class ContainContext {
        private ReentrantLock lock = new ReentrantLock();
        private Condition producerCondition = lock.newCondition();
        private Condition consumerCondition = lock.newCondition();
        private LinkedList<String> container = new LinkedList<>();
        private int maxSize;
    
        public ContainContext(int maxSize) {
            this.maxSize = maxSize;
        }
    
        public void addElement(String t) {
            lock.lock();
            try {
                // 达到最大值,阻塞生产者
                while (container.size() == maxSize) {
                    producerCondition.await();
                    consumerCondition.signalAll();
                }
    
                container.add(t);
                System.out.println("tName: " + Thread.currentThread().getName() + " 生产消息: " + t);
                consumerCondition.signalAll();
            } catch (Exception e) {
                // ignore
            } finally {
                lock.unlock();
            }
        }
    
        public String removeFirst() {
            lock.lock();
            try {
                while (container.size() == 0) {
                    consumerCondition.await();
                    producerCondition.signalAll();
                }
    
                String removed = container.remove();
                System.out.println("tName: " + Thread.currentThread().getName() + " 消费消息: " + removed);
                consumerCondition.signalAll();
                return removed;
            } catch (Exception e) {
                // ignore
            } finally {
                lock.unlock();
            }
            return "";
        }
    }

    补充:关于cond1.notify_all 如果不手动释放锁,是在等锁作用域结束自动释放后才会notify

    #include <iostream>
    #include <mutex>
    #include <condition_variable>
    #include <thread>
    
    using namespace std;
    
    
    std::mutex _mutex;
    std::condition_variable cond1;
    
    
    void addNum() {
        std::unique_lock<std::mutex> lock(_mutex);
        cout << std::this_thread::get_id() << " wait" << endl;
        cond1.wait(lock);
        cout << std::this_thread::get_id() << " end wait" << endl;
    }
    
    int main()
    {
        cout << "main " << std::this_thread::get_id() << endl;
        thread t(addNum);
        t.detach();
    
        cout << "main " << std::this_thread::get_id() << " sleep" << endl;
        this_thread::sleep_for(std::chrono::seconds(3));
    
        std::unique_lock<std::mutex> lock(_mutex);
        cond1.notify_all();
        lock.unlock(); // 这里必须手动释放锁, 否则会等到锁作用域结束锁自动释放才会进行notify。 
        this_thread::sleep_for(std::chrono::seconds(3));
        cout << "main " << std::this_thread::get_id() << " end" << endl;
        return 0;
    }

      简单了解下c++ 关于线程、同步、以及基于条件的线程通信的方式。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    《转》2013年那些深入人心的小故事
    sklearn学习2-----LogisticsRegression
    sklearn学习汇总
    sklearn学习1----sklearn.SVM.SVC
    树(5)-----判断两颗树一样或者一棵树是否是另外一颗的子树
    树(4)-----树的高度
    面试题1-----SVM和LR的异同
    算法19-----(位运算)找出数组中出现只出现一次的数
    树(3)-----栈(迭代)
    python中的全局变量、局部变量、实例变量
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15559000.html
Copyright © 2011-2022 走看看