C++简单入门

2021年11月23日 阅读数:4
这篇文章主要向大家介绍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!java

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++中*表明指针,而&表明引用,而*&表明指针引用ios

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

2. 多线程

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

#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) 使用lambdadom

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