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++ 关于线程、同步、以及基于条件的线程通信的方式。

    【当你用心写完每一篇博客之后,你会发现它比你用代码实现功能更有成就感!】
  • 相关阅读:
    SharePoint 2013 图文开发系列之自定义字段
    SharePoint 2013 图文开发系列之Visual Studio 创建母版页
    SharePoint 2013 图文开发系列之代码定义列表
    SharePoint 2013 图文开发系列之计时器任务
    SharePoint 2013 图文开发系列之应用程序页
    SharePoint 2013 图文开发系列之事件接收器
    SharePoint 2013 图文开发系列之可视化WebPart
    SharePoint 2013 图文开发系列之WebPart
    SharePoint 2013 对二进制大型对象(BLOB)进行爬网
    SharePoint 2013 状态机工作流之日常报销示例
  • 原文地址:https://www.cnblogs.com/qlqwjy/p/15559000.html
Copyright © 2011-2022 走看看