这是一篇C++复习笔记。参考力扣上的收费教程 C++ 面试突破 整理而成【侵删】,根据我的理解对部分内容做了删减和调整,比如删掉了c++20等比较新的内容(暂时还用不到),过滤了比较老的C++语法,重难点增加了自己的理解。每段代码我都自己调试过,除了编译器导致的差异,基本没问题。
想读完整的原版内容,请移步力扣官网C++ 面试突破
c++语言特性相关
左值、右值
- 从实践角度讲,它能够完美解决 C++ 中长久以来为人所诟病的临时对象效率问题
- 左值:指表达式结束后依然存在的持久对象。可以取地址,可以通过内置(不包含重载) & 来获取地址,我们可以将一个右值赋给左值。
- 右值:表达式结束就不再存在的临时对象。不可取地址,不可以通过内置(不包含重载) & 来获取地址。由于右值不可取地址,因此我们不能将任何值赋给右值。
- 使用 = 进行赋值时,= 的左边必须为左值,右值只能出现在 = 的右边。
// x 是左值,666 为右值
int x = 666; // ok
int *y = x; // ok
int *z = &666 // error
666 = x; // error
int a = 9; // a 为左值
int b = 4; // b 为左值
int c = a + b // c 为左值 , a + b 为右值
a + b = 42; // error
std::move() std::forward()实现原理
- 引用折叠原理
- 右值传递给上述函数的形参 T&& 依然是右值,即 T&& && 相当于 T&&。
- 左值传递给上述函数的形参 T&& 依然是左值,即 T&& & 相当于 T&。
- 我们已经知道折叠原理,通过引用折叠原理可以知道,move() 函数的形参既可以是左值也可以是右值。
-
万能模板
万能模板既可以接受左值作为实参也可以接受右值作为实参 -
std::move 原理
- 通过remove_reference 移除引用,得到参数的具体类型
- 通过static_cast<> 进行强制类型转换,返回type&& 右值引用
move代码:
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
return static_cast<typename remove_reference<T>::type &&>(t);
}
remove_reference实现
//原始的,最通用的版本
template <typename T> struct remove_reference{
typedef T type; //定义 T 的类型别名为 type
};
//部分版本特例化,将用于左值引用和右值引用
template <class T> struct remove_reference<T&> //左值引用
{ typedef T type; }
template <class T> struct remove_reference<T&&> //右值引用
{ typedef T type; }
//举例如下,下列定义的a、b、c三个变量都是int类型
int i;
remove_refrence<decltype(42)>::type a; //使用原版本,
remove_refrence<decltype(i)>::type b; //左值引用特例版本
remove_refrence<decltype(std::move(i))>::type b; //右值引用特例版本
- forward的实现
- forward 保证了再转发时左值右值不会被更改,实现完美转发。主要解决引用函数参数为右值时,传进来之后又了变量名变成了左值,比如下面代码:
#include <iostream>
using namespace std;
template<typename T>
void fun(T&& tmp)
{
cout << "fun rvalue bind:" << tmp << endl;
}
template<typename T>
void fun(T& tmp)
{
cout << "fun lvalue bind:" << tmp << endl;
}
template<typename T>
void test(T&& x) {
fun(x); // 10为右值,调用fun(x)时,变成了左值
fun(std::forward<T>(x));
}
int main()
{
int a = 10;
test(10);
test(a);
return 0;
}
/*
fun lvalue bind:10
fun rvalue bind:10
fun lvalue bind:10
fun lvalue bind:10
*/
仔细体会下上面的代码,调用test(10),test函数知道x为右值,但是调用fun(x)时,x已经成了左值。通过std::forward(x) 强行在x前面再加上&或者&&,以匹配对应的模板函数。
和move的区别是
- forward有两个模板函数,分别对应左值和右值
- move 在进行类型转换时,利用 remove_reference 将外层的引用全部去掉,这样可以将 t 强制转换为指定类型的右值引用,而 forward 则利用引用折叠的技巧,巧妙的保留了变量原有的属性。
指针
指针的基本概念比较好理解,函数指针稍微复杂点,重点提下:
- 普通函数指针
#include <iostream>
using namespace std;
int add(int a, int b){
return a + b;
}
typedef int (*fun_p)(int, int);
int main(void)
{
fun_p fn = add;
cout << fn(1, 6) << endl;
return 0;
}
- 对象函数指针
定义指向成员函数的指针时,要标明指针所属的类。
#include <iostream>
using namespace std;
class A
{
public:
int var1, var2;
static int x;
static int get() {
return 100;
}
int add(){
return var1 + var2;
}
};
int main()
{
A ex;
ex.var1 = 3;
ex.var2 = 4;
int *p = &ex.var1; // 指向对象成员变量的指针
cout << *p << endl;
int (A::*fun_p)();
int (*fun_q)();
fun_p = &A::add; // 指向对象非静态成员函数的指针 fun_p
fun_q = A::get; // 指向对象静态成员函数的指针 fun_q
cout << (ex.*fun_p)() << endl;
cout << (*fun_q)() << endl;
return 0;
}
全局函数、类函数、类静态成员函数的指针均有点不一样,没有深究,或许c++编译器就这么定义的。
#include <iostream>
using namespace std;
int fun1(int tmp1, int tmp2)
{
return tmp1 * tmp2;
}
int fun2(int tmp1, int tmp2)
{
return tmp1 / tmp2;
}
int main()
{
int (*fun)(int x, int y);
fun = fun1; // ok
fun = &fun1; // ok 两种写法均可以
cout << fun(15, 5) << endl;
fun = fun2;
cout << fun(15, 5) << endl;
cout<<sizeof(fun1)<<endl; // error
cout<<sizeof(&fun1)<<endl; // 8
return 0;
}
/*
运行结果:
75
3
*/
- 函数名 fun1 存放的是函数的首地址,它是一个函数类型 void,&fun1 表示一个指向函数对象 fun1 的地址,是一个指针类型。它的类型是 int (*)(int,int),因此 fun1 和 &fun1 的值是一样的;
- &fun1 是一个表达式,函数此时作为一个对象,取对象的地址,该表达式的值是一个指针。
- 通过打印 sizeof 即可知道 fun1 与 &fun1 的区别;不能对函数调用sizeof,会报错"Invalid application of ‘sizeof’ to a function type"
野指针和悬空指针
- 悬空指针:若指针指向一块内存空间,当这块内存空间被释放后,该指针依然指向这块内存空间
void *p = malloc(size);
free(p); // 此时,p 指向的内存空间已释放, p 就是悬空指针。
p = NULL;
- “野指针” 是指不确定其指向的指针,未初始化的指针为“野指针”
void *p;
// 此时 p 是“野指针”。
强制类型抓换
- static_cast,静态转换,即编译器转换,失败会抛编译错误
- 数据转换
- 基本数据类型转换
- 基类<->派生类转换:派生类->基类(上行)是安全的;基类->派生类(下行)是不安全的,编译器不做校验,所以下行最好用dynamic_cast
- 可以将空指针转化成目标类型的空指针
- 可以将任何类型的表达式转化成 void 类型
-
const_cast
主要用于 const -> 非const; volatile -> 非volatile转换,是单向的,反之不行,即只能作用于常量,将常量、常量指针变成非常量、非常量指针 -
reinterpret_cast
改变指针或引用的类型
- 指针或引用–>足够长度的整型
- 整型–>指针或引用
- dynamic_cast
- 动态转换,运行时
- 向上转换和static_cast是一样的效果,只是改变指针的类型,并没有改变所指的内容
- 向下转换时,指针指向的对象必须是派生类,此时转换成功,否则失败。
即子类对象可用父类指针指引,基类对象不能用子类指引,因为基类可能没有子类的方法,子类指针调用的函数可能不存在会crash
看个例子,基类转子类的两种情况,一种成功,一种失败
#include <iostream>
#include <cstring>
using namespace std;
class Base
{
public:
virtual void fun()
{
cout << "Base::fun()" << endl;
}
};
class Derive : public Base
{
public:
virtual void fun()
{
cout << "Derive::fun()" << endl;
}
};
int main()
{
Base *p1 = new Derive();
Base *p2 = new Base();
Derive *p3 = new Derive();
//转换成功
p3 = dynamic_cast<Derive *>(p1);
if (p3 == NULL)
{
cout << "NULL" << endl;
}
else
{
cout << "NOT NULL" << endl; // 输出
}
//转换失败
p3 = dynamic_cast<Derive *>(p2);
if (p3 == NULL)
{
cout << "NULL" << endl; // 输出
}
else
{
cout << "NOT NULL" << endl;
}
return 0;
}
什么是类型萃取
参考std::move中用到的remove_reference函数,里面typedef _Tp type就是萃取。在编译器获取了模板参数_Tp的类型
/// remove_reference
template<typename _Tp>
struct remove_reference
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference<_Tp&>
{ typedef _Tp type; };
template<typename _Tp>
struct remove_reference < _Tp&& >
{ typedef _Tp type; };
结构体相等的判断方式
需要重载操作符 == 判断两个结构体是否相等,不能用函数 memcmp 来判断两个结构体是否相等,因为 memcmp 函数是逐个字节进行比较的,而结构体存在内存空间中保存时存在字节对齐,字节对齐时补的字节内容是随机的,会产生垃圾值,所以无法比较。
#include <iostream>
using namespace std;
struct A
{
char c;
int val;
A(char c_tmp, int tmp) : c(c_tmp), val(tmp) {}
friend bool operator==(const A &tmp1, const A &tmp2); // 友元运算符重载函数
};
bool operator==(const A &tmp1, const A &tmp2)
{
return (tmp1.c == tmp2.c && tmp1.val == tmp2.val);
}
int main()
{
A ex1('a', 90), ex2('b', 80);
if (ex1 == ex2)
cout << "ex1 == ex2" << endl;
else
cout << "ex1 != ex2" << endl; // 输出
return 0;
}
不用友元也可以,结构体的函数是public的
struct A {
char c;
int val;
A(char c_tmp, int tmp) : c(c_tmp), val(tmp) {}
bool operator==(const A &tmp2) {
return c == tmp2.c && val == tmp2.val;
};
};
函数模板和类模板的区别
- 默认参数:函数模板不允许有默认参数,类模板在模板参数列表中可以有默认参数
- 特化:函数模板只能全特化;而类模板可以全特化,也可以偏特化。
- 调用方式不同:函数模板可以进行类型推导,可以隐式调用,也可以显式调用;类模板只能显式调用。
函数模板调用方式举例:
#include<iostream>
using namespace std;
template <typename T>
T add_fun(const T & tmp1, const T & tmp2){
return tmp1 + tmp2;
}
int main(){
int var1, var2;
cin >> var1 >> var2;
cout << add_fun<int>(var1, var2); // 显式调用
double var3, var4;
cin >> var3 >> var4;
cout << add_fun(var3, var4); // 隐式调用
return 0;
}
模板特化
定义函数模板的特化版本,本质上是接管了编译器的工作,为原函数模板定义了一个特殊实例
#include <iostream>
#include <cstring>
using namespace std;
//函数模板
template <class T>
bool compare(T t1, T t2)
{
cout << "通用版本:";
return t1 == t2;
}
template <> //函数模板特化
bool compare(char *t1, char *t2)
{
cout << "特化版本:";
return strcmp(t1, t2) == 0;
}
int main(int argc, char *argv[])
{
char arr1[] = "hello";
char arr2[] = "abc";
cout << compare(123, 123) << endl;
cout << compare(arr1, arr2) << endl;
return 0;
}
/*
运行结果:
通用版本:1
特化版本:0
*/
可变参数模板
可变参数函数通常是递归的,第一个版本的 print_fun 负责终止递归并打印初始调用中的最后一个实参。第二个版本的 print_fun 是可变参数版本,打印绑定到 t 的实参,并用来调用自身来打印函数参数包中的剩余值。
#include <iostream>
using namespace std;
template <typename T>
void print_fun(const T &t)
{
cout << t << endl; // 最后一个元素
}
template <typename T, typename... Args>
void print_fun(const T &t, const Args &...args)
{
cout << t << " ";
print_fun(args...);
}
int main()
{
print_fun("Hello", "world", "!");
return 0;
}
/*运行结果:
Hello wolrd !
*/
C++ I/O与进程同步
C++条件变量
- 多线程基本概念
- 线程
- c++11 之后标准库里加入了多线程 #include std::thread
- thread使用方法 std::thread t1(runnable, 1),一个任务函数、一个是参数。也可以放lambda表达式
std::thread t([](){ std::cout << "hello world." << std::endl; }); t.join();
- thread声明即开始run了,thead.join()表示当前线程要等子线程运行完,不加join会报错
- thread如果不想join,则必须调用thread.detach,表示不等他了,随他自己去吧
- std::this_thread::sleep_for() 当前线程sleep一段时间
- std::this_thread::get_id() 获取当前线程id,可以用来判断当前线程是否为主线程
- 锁
- mutex #include,互斥量,最原始的锁,但是使用不太方便[lock, unlock]成对使用
- 基于mutex,有std::lock_guard, 满足RAII,只需要声明,用完自动释放
std::mutex mtx; std::lock_guard<std::mutex> lock(mtx);
- 基于lock_guard,又出现了std::unique_lock,融合了mutex[lock, unlock]和lock_guard的功能
如果对c++多线程不了解,可以先看看我之前写的一篇入门文章,《C++ 多线程入门》
https://mp.weixin.qq.com/s/b1HhzqBkt7mxo9v7pQJZHg
- wait-基于条件变量 #include <condition_variable> // std::condition_variable
- std::condition_variable::wait 必须与unique_lock配合使用 lock作为wait参数
std::unique_lock<std::mutex> lock(mtx); std::condition_variable cv: cv.wait(lock); // cv必须指定,你等的是哪一把锁啊,不然咋知道你和哪个线程争夺执行权呢
- chrono #include //辅助工具,计时。配合其他类使用
std::this_thread::sleep_for(std::chrono::seconds(1));
- 案例, 几个非常好的条件变量 wait notify的例子
- 例1,三个线程wait,第四个线程notify,熟悉wait_for的用法,代码里有详细注释。
#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>
using namespace std::chrono_literals;
std::condition_variable cv; // 条件变量 wait
std::mutex cv_m; //原始互斥量 lock
int i;
void waits(int idx)
{
std::unique_lock<std::mutex> lk(cv_m);
// wait_for(lock, 超时duration, Predicate)
// Predicate有函数调用功能即可,作为唤醒时附加的判断条件,predicate成立才能真的唤醒,重新获得锁和执行权,此处判断i==1
// 这里有三个线程调用,分别等待100ms,200ms,300ms
if (cv.wait_for(lk, idx*100ms, []{return i == 1;})) {
std::cerr << "Thread " << idx << " finished waiting. i == " << i << '\n';
} else {
std::cerr << "Thread " << idx << " timed out. i == " << i << '\n';
}
}
void signals()
{
// 等120ms,让第一个线程等100ms超时,看看超时的效果
std::this_thread::sleep_for(120ms);
std::cerr << "Notifying...\n";
// 唤醒,第二个、三个线程分别唤醒后 predicate不满足,又继续睡了
// 好比你妈把你喊醒,叫你上学,你醒了一看才5点,不到6点。又继续睡了
cv.notify_all(); // 唤醒所有cv.wait
// 当前线程sleep,一共sleep220ms了
// 此时第二个线程等了200ms,没人叫他,自己超时醒了
std::this_thread::sleep_for(100ms);
{
// 注意这是在代码块里面,lock_guard出了代码块会unlock
std::lock_guard<std::mutex> lk(cv_m);
i = 1; //修改i == 1,让第三个线程能predicate成功
}
std::cerr << "Notifying again...\n";
// 第二次唤醒,第一个、第二个线程已经超时了,第三个线程唤醒了,而且i == 1,那就真的醒了
cv.notify_all();
}
int main()
{
std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
t1.join(); // t1 等待 100ms 后未被唤醒,自动超时;
t2.join(); // t2 在 120 ms 处被唤醒,但 condition 未满足,再此进入阻塞,200ms 时由于超时返回
t3.join(); // t3 在 120 ms 处被唤醒,但 condition 未满足,再此进入阻塞,220ms 时被 notify ,正常返回。
t4.join();
}
/*
Thread 1 timed out. i == 0
Notifying...
Thread 2 timed out. i == 0
Notifying again...
Thread 3 finished waiting. i == " 1
*/
-
例2 notify_one notify_all
- notify_one随机唤醒一个,即只有一个等待的线程醒了去上学了
- notify_all唤醒了所有的线程。
注意,门开锁了,也得一个个出去,即一个线程执行完了,释放锁了,其他的线程才能接着获得线程执行权跟着出门。简言之,notify_one最终只有一个等待线程激活,notify_all最终会激活所有等待线程。
notify_one
// condition_variable::notify_one
#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 produce,consume;
int cargo = 0; // shared value by producers and consumers
void consumer () {
std::unique_lock<std::mutex> lck(mtx);
while (cargo==0) consume.wait(lck);
std::cout << cargo << '\n';
cargo=0;
produce.notify_one();
}
void producer (int id) {
std::unique_lock<std::mutex> lck(mtx);
while (cargo!=0) produce.wait(lck);
cargo = id;
consume.notify_one();
}
int main ()
{
std::thread consumers[10],producers[10];
// spawn 10 consumers and 10 producers:
for (int i=0; i<10; ++i) {
consumers[i] = std::thread(consumer);
producers[i] = std::thread(producer,i+1);
}
// join them back:
for (int i=0; i<10; ++i) {
producers[i].join();
consumers[i].join();
}
return 0;
}
notify_all
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable_any cv;
std::mutex cv_m; // This mutex is used for three purposes:
// 1) to synchronize accesses to i
// 2) to synchronize accesses to std::cerr
// 3) for the condition variable cv
int i = 0;
void waits(int idx)
{
std::unique_lock<std::mutex> lk(cv_m);
std::cerr << "Waiting... \n";
cv.wait(lk, []{return i == 1;});
std::cerr << "thread:"<< idx <<" ...finished waiting. i == 1\n";
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lk(cv_m);
std::cerr << "Notifying...\n";
}
cv.notify_all();
std::this_thread::sleep_for(std::chrono::seconds(1));
{
std::lock_guard<std::mutex> lk(cv_m);
i = 1;
std::cerr << "Notifying again...\n";
}
cv.notify_all();
}
int main()
{
std::thread t1(waits, 1), t2(waits, 2), t3(waits, 3), t4(signals);
t1.join();
t2.join();
t3.join();
t4.join();
}
- 假唤醒及解决方案
举例说明假唤醒:生产者-消费者模型下,消费者有3个线程处于wait
- 1)生产者生产一个"面包"后notify_all
- 2)线程1、2、3均被激活,逐个执行
- 3)假设线程1先获得锁,把刚生产的"面包"消费了,释放锁
- 4)假设线程2接着获得锁,也来吃面包,发现面包没了,那可怎么办?那不是要干仗!!
- 5)接着线程3获得锁,重复线程2的惨剧~~无面包可消费
实际工程中涉及的共享数据和流程比较复杂,很容易出现异常。所以此时最好的办法就是,判断面包还在吗?不在的话我接着睡吧,别起来捣乱了。
lock(mutex);
while( !面包还在吗) { // 注意"!"否定
// 面包不在了
condition_wait( cond, mutex);
}
// 面包还在,退出while循环,起来吃面包了
do(something);
// 吃完面包,释放锁
unlock(mutex);
线程同步与异步
mutex 比较简单,不再赘述。补充几个线程同步的知识点。
- std::cout 是线程不安全的,多线程中使用cout会乱序,可以考虑加锁实现线程安全的cout
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
void print_block (int n, char c) {
mtx.lock();
for (int i=0; i<n; ++i) { std::cout << c; }
std::cout << '\n';
mtx.unlock();
}
int main ()
{
std::thread th1 (print_block,50,'*');
std::thread th2 (print_block,50,'$');
th1.join();
th2.join();
return 0;
}
- recursive_mutex 。同一个线程可重复访问,递归的场景使用
std::recursive_mutex mtx;
void fun1() {
mtx.lock();
mtx.unlock();
}
void fun2() {
mtx.lock();
fun1(); // recursive lock
mtx.unlock();
};
- 共享互斥量 c++17新特性(互斥锁)
shared_mutex + (unique_lock、shared_lock)共同实现。
实现场景:多个线程同时读,只有一个线程能写入
#include <iostream>
#include <mutex> // For std::unique_lock
#include <shared_mutex>
#include <thread>
class ThreadSafeCounter {
public:
ThreadSafeCounter() = default;
// 多个线程可以同时读取 countter 计数
unsigned int get() const {
std::shared_lock lock(mutex_);
return value_;
}
// 只有1个线程可以修改 countter 计数
void increment() {
std::unique_lock lock(mutex_);
value_++;
}
// 只有1个线程可以修改 countter 计数
void reset() {
std::unique_lock lock(mutex_);
value_ = 0;
}
private:
mutable std::shared_mutex mutex_;
unsigned int value_ = 0;
};
int main() {
ThreadSafeCounter counter;
auto increment_and_print = [&counter]() {
for (int i = 0; i < 3; i++) {
counter.increment();
std::cout << std::this_thread::get_id() << ' ' << counter.get() << '\n';
// Note: Writing to std::cout actually needs to be synchronized as well
// by another std::mutex. This has been omitted to keep the example small.
}
};
std::thread thread1(increment_and_print);
std::thread thread2(increment_and_print);
thread1.join();
thread2.join();
}
/*
139677317637888 2
139677317637888 3
139677309245184 4
139677309245184 5
139677309245184 6
139677317637888 6
*/
- call_once ,标记仅调用一次
有些场景,比如仅触发一次,还挺有用的,避免冗余的判断逻辑。
#include <iostream>
#include <thread>
#include <mutex>
std::once_flag flag1, flag2;
void simple_do_once()
{
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
}
/*
Simple example: called once
*/
### 线程高级用法
1. 成员函数异步调用另一个成员函数,需加上this参数
```c++
class Test{
private:
void func1(const std::string s1, int i) {
std::cout << s1 << std::endl;
}
public:
void func2() {
std::thread t1(&Test::func1, this, "test", 0);
t1.detach();
std::cout << "func2" << std::endl;
}
类成员函数默认会有个this参数,编译器帮我们干了,换了个线程,找不到this,所以要手动传进去
- std::async,可以方便的获取结果
template<class Fn, class... Args>
future<typename result_of<Fn(Args...)>::type> async(launch policy, Fn&& fn, Args&&...args);
- 第一个参数:异步执行还是同步执行
- std::launch::async 传递的可调用对象异步执行;
- std::launch::deferred 传递的可调用对象同步执行;
- std::launch::async | std::launch::deferred 可以异步或是同步,取决于操作系统,我们无法控制;
- 如果我们不指定策略,则相当于(3)
- 第二个参数:接收一个可调用对象
- 仿函数
- lambda表达式
- 类成员函数、普通函数…
- 第三个参数:函数的参数
看个简单的例子:
#include "pch.h"
#include <iostream>
#include <string>
#include <chrono>
#include <thread>
#include <future>
using namespace std::chrono;
std::string fetchDataFromDB(std::string recvData) {
std::cout << "fetchDataFromDB start" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(seconds(5));
return "DB_" + recvData;
}
std::string fetchDataFromFile(std::string recvData) {
std::cout << "fetchDataFromFile start" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(seconds(3));
return "File_" + recvData;
}
int main() {
std::cout << "main start" << std::this_thread::get_id() << std::endl;
//获取开始时间
system_clock::time_point start = system_clock::now();
std::future<std::string> resultFromDB = std::async(std::launch::async, fetchDataFromDB, "Data");
//从文件获取数据
std::future<std::string> fileData = std::async(std::launch::deferred, fetchDataFromFile, "Data");
//知道调用get函数fetchDataFromFile才开始执行
std::string FileData = fileData.get();
//如果fetchDataFromDB()执行没有完成,get会一直阻塞当前线程
std::string dbData = resultFromDB.get();
//获取结束时间
auto end = system_clock::now();
auto diff = duration_cast<std::chrono::seconds>(end - start).count();
std::cout << "Total Time taken= " << diff << "Seconds" << std::endl;
//组装数据
std::string data = dbData + " :: " + FileData;
//输出组装的数据
std::cout << "Data = " << data << std::endl;
return 0;
}
- std::future
std::async 返回结果即为一个 future
-
可以通过查询 future 的状态(future_status)来获取异步操作的结果。future_status 有三种状态:
- deferred:异步操作还没开始;
- ready:异步操作已经完成;
- timeout:异步操作超时;
-
获取 future 结果有三种方式:
-
get
-
wait
-
wait_for
其中 get 等待异步操作结束并返回结果,wait 只是等待异步操作完成,没有返回值,wait_for 是超时等待返回结果
c++ I/O操作-重定向
I/O放到任何语言可以说的话题都很多,这段只是简单提下。
- C++ 中的 Streams 对象主要分为三种类型:
- istream :这种类型的流对象只能从流中执行输入操作;
- ostream :这些对象只能用于输出操作;
- iostream :可用于输入和输出操作;
- 标准输入输出
- 标准输出流(cout):通常标准输出设备是显示器。C++ cout 语句是 ostream 类的实例。
- 标准输入流(cin):通常计算机中的输入设备是键盘。C++ cin 语句是 istream 类的实例,用于从标准输入设备(通常是键盘)读取输入。
- I/O重定向
所有流对象都有一个关联的数据成员类 streambuf,是流的缓冲区。
当我们从流中读取数据时,我们不会直接从源中读取数据,而是从链接到源的缓冲区中读取数据
C++ 允许我们为任何流设置流缓冲区,因此重定向流的任务简单地减少为更改与流关联的流缓冲区。
stream_object.rdbuf()://获取返回指向stream_object的流缓冲区的指针
stream_object.rdbuf(streambuf * p)://将流缓冲区设置为p指向的
- 例:实现cout重定向输出到文件。同理也可以将输入定向到文件。
// Cpp program to redirect cout to a file
#include <fstream>
#include <iostream>
#include <string>
using namespace std;
int main()
{
fstream file;
file.open("cout.txt", ios::out);
string line;
// 保存 cin 和 cout 的缓冲区 buffer
streambuf* stream_buffer_cout = cout.rdbuf();
streambuf* stream_buffer_cin = cin.rdbuf();
// 获取文件 file 的缓冲区 buffer
streambuf* stream_buffer_file = file.rdbuf();
// cout 重定向文件,会生成一个cout.txt文件
cout.rdbuf(stream_buffer_file);
cout << "This line written to file" << endl;
// cout 恢复重定
cout.rdbuf(stream_buffer_cout);
cout << "This line is written to screen" << endl;
file.close();
return 0;
}
- 例,清除缓冲区
在各种情况下,可能需要清除不需要的缓冲区,以便在所需的程序中立即获取下一个输入
下面这个程序不能得到正确的效果,ch为空,因为\n保留在缓冲区,作为下一个输入读取
#include<iostream>
#include<vector>
using namespace std;
int main()
{
int a;
char ch[80];
cin >> a;
cin.getline(ch,80);
cout << a << endl;
cout << ch << endl;
return 0;
}
有两种方法修改:
- cin.ignore(numeric_limits::max(),‘\n’); ,表示从\n不忽略,max表示尽可能多的忽略
- cin>>ws
修改后:
#include<iostream>
#include<ios> //used to get stream size
#include<limits> //used to get numeric limits
using namespace std;
int main()
{
int a;
char ch[80];
cin >> a;
cin.ignore(numeric_limits<streamsize>::max(),'\n'); //方法一
cint >> ws;// 方法二
cin.getline(ch,80);
cout << a << endl;
cout << ch << endl;
return 0;
}
设计模式
单例模式
实现思路:
- 私有化三个构造函数默认构造、拷贝构造、赋值远算
- 线程安全
- static 全局单例
- 最简单的懒汉模式
class singleton {
private:
static singleton* instance;
singleton(const singleton& temp) {}
singleton& operator=(const singleton& temp){}
protected:
singleton() {}
public:
static singleton* getInstance() {
return instance;
}
}
// 类静态变量要在类内声明,类外初始化
// 初始化可以访问构造函数
singleton::instance = new singleton();
- 不安全的饿汉模式
class Singleton{
private:
static Singleton* instance;
Singleton(const Singleton& temp) {}
Singleton& operator=(const Singleton& temp){}
Singleton() {}
public:
static Singleton* getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
Singleton::instance = NULL;
- 加锁
加锁也容易出现阻塞
class Singleton {
private:
static std::mutex mtx;
static Singleton* instance;
Singleton(){}
Singleton&(const Singleton& temp){}
Singleton& operator=(const Singleton& temp){}
public:
static Singleton* getInstance() {
mtx.lock();
if (instance == NULL) {
instance = new Singleton();
}
return instance;
mtx.unlock();
}
}
- 内部静态变量,最优解决方案,多线程访问是会自动挂起
class Singleton{
private:
Singleton(){}
Singleton&(const Singleton& tmp){}
Singleton& operator=(const Singleton& tmp){}
public:
static Singleton* getInstance() {
static Singleton instance;
return &instance;
}
}
工厂模式
“简单工厂”、“工厂方法”、“抽象工厂”
下面以加减乘除的实现为例
- 简单工厂
将创建和初始化的逻辑封装到工厂类内, 把每一种操作封装成一个类,继承一个基类。
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
// Here is the Factory class
class Factory
{
public:
static Operation *CreateProduct(char op)
{
switch (op)
{
case '+':
return new Add_Operation();
case '-':
return new Sub_Operation();
case '*':
return new Mul_Operation();
case '/':
return new Div_Operation();
default:
return new Add_Operation();
}
}
};
int main()
{
int a, b;
cin >> a >> b;
Operation *p = Factory::CreateProduct('+');
p->var1 = a;
p->var2 = b;
cout << p->GetResult() << endl;
p = Factory::CreateProduct('*');
p->var1 = a;
p->var2 = b;
cout << p->GetResult() << endl;
return 0;
}
- 工厂方法模式
新增加分类,要修改工厂里的代码,不符合开闭原则。工厂方法在次基础上,将每一个对象的生成都抽出一个工厂
每个工厂对应一个一个类, 基于面向接口编程,所有的工厂都继承同一个工厂基类
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation : public Operation
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
class Factory
{
public:
virtual Operation *CreateProduct() = 0;
};
class Add_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Add_Operation();
}
};
class Sub_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Sub_Operation();
}
};
class Mul_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Mul_Operation();
}
};
class Div_Factory : public Factory
{
public:
Operation *CreateProduct()
{
return new Div_Operation();
}
};
int main()
{
int a, b;
cin >> a >> b;
Add_Factory *p_fac = new Add_Factory();
Operation *p_pro = p_fac->CreateProduct();
p_pro->var1 = a;
p_pro->var2 = b;
cout << p_pro->GetResult() << endl;
Mul_Factory *p_fac1 = new Mul_Factory();
Operation *p_pro1 = p_fac1->CreateProduct();
p_pro1->var1 = a;
p_pro1->var2 = b;
cout << p_pro1->GetResult() << endl;
return 0;
}
- 抽象工厂
在工厂方法的基础上,可能有多个产品,可以在工厂基类里声明多个虚函数。每个工厂实现类,都需要实现能生产多个类别的产品。
看起来感觉就多了一个产品大类
我之前学习李建忠设计模式里,将抽象工厂,以数据库为例,是非常复杂的,但是应对复杂项目,后期威力巨大。并不是这么简单的拓展一个接口即可。
#include <iostream>
#include <vector>
using namespace std;
// Here is the product class
class Operation_Pos
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 + var2;
}
};
class Sub_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 - var2;
}
};
class Mul_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 * var2;
}
};
class Div_Operation_Pos : public Operation_Pos
{
public:
virtual double GetResult()
{
return var1 / var2;
}
};
/*********************************************************************************/
class Operation_Neg
{
public:
int var1, var2;
virtual double GetResult()
{
double res = 0;
return res;
}
};
class Add_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 + var2);
}
};
class Sub_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 - var2);
}
};
class Mul_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 * var2);
}
};
class Div_Operation_Neg : public Operation_Neg
{
public:
virtual double GetResult()
{
return -(var1 / var2);
}
};
/*****************************************************************************************************/
// Here is the Factory class
class Factory
{
public:
virtual Operation_Pos *CreateProduct_Pos() = 0;
virtual Operation_Neg *CreateProduct_Neg() = 0;
};
class Add_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Add_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Add_Operation_Neg();
}
};
class Sub_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Sub_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Sub_Operation_Neg();
}
};
class Mul_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Mul_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Mul_Operation_Neg();
}
};
class Div_Factory : public Factory
{
public:
Operation_Pos *CreateProduct_Pos()
{
return new Div_Operation_Pos();
}
Operation_Neg *CreateProduct_Neg()
{
return new Div_Operation_Neg();
}
};
int main()
{
int a, b;
cin >> a >> b;
Add_Factory *p_fac = new Add_Factory();
Operation_Pos *p_pro = p_fac->CreateProduct_Pos();
p_pro->var1 = a;
p_pro->var2 = b;
cout << p_pro->GetResult() << endl;
Add_Factory *p_fac1 = new Add_Factory();
Operation_Neg *p_pro1 = p_fac1->CreateProduct_Neg();
p_pro1->var1 = a;
p_pro1->var2 = b;
cout << p_pro1->GetResult() << endl;
return 0;
}
观察者模式
使用场景
- 状态发生改变,一对多的调用
- 一般发出消息的是基础模块,不方便直接拿到业务模块去更新,需要业务模块将更新的逻辑注册到被观察者
一个例子:
#include <iostream>
#include <string>
#include <list>
using namespace std;
class Subject;
//观察者 基类 (内部实例化了被观察者的对象sub)
class Observer
{
protected:
string name;
Subject *sub;
public:
Observer(string name, Subject *sub)
{
this->name = name;
this->sub = sub;
}
virtual void update() = 0;
};
class StockObserver : public Observer
{
public:
StockObserver(string name, Subject *sub) : Observer(name, sub)
{
}
void update();
};
class NBAObserver : public Observer
{
public:
NBAObserver(string name, Subject *sub) : Observer(name, sub)
{
}
void update();
};
//被观察者 基类 (内部存放了所有的观察者对象,以便状态发生变化时,给观察者发通知)
class Subject
{
protected:
list<Observer *> observers;
public:
string action; //被观察者对象的状态
virtual void attach(Observer *) = 0;
virtual void detach(Observer *) = 0;
virtual void notify() = 0;
};
class Secretary : public Subject
{
void attach(Observer *observer)
{
observers.push_back(observer);
}
void detach(Observer *observer)
{
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end())
{
if ((*iter) == observer)
{
observers.erase(iter);
return;
}
++iter;
}
}
void notify()
{
list<Observer *>::iterator iter = observers.begin();
while (iter != observers.end())
{
(*iter)->update();
++iter;
}
}
};
void StockObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭股票,装做很认真工作的样子!" << endl;
}
}
void NBAObserver::update()
{
cout << name << " 收到消息:" << sub->action << endl;
if (sub->action == "梁所长来了!")
{
cout << "我马上关闭NBA,装做很认真工作的样子!" << endl;
}
}
int main()
{
Subject *dwq = new Secretary();
Observer *xs = new NBAObserver("xiaoshuai", dwq);
Observer *zy = new NBAObserver("zouyue", dwq);
Observer *lm = new StockObserver("limin", dwq);
dwq->attach(xs);
dwq->attach(zy);
dwq->attach(lm);
dwq->action = "去吃饭了!";
dwq->notify();
cout << endl;
dwq->action = "梁所长来了!";
dwq->notify();
return 0;
}
设计原则
我们一般记成SOLID
其中(里氏替换原则和迪米特法则的首字母都是L)
-
单一职责原则 (Single Responsibility Principle)
每个类、函数的功能定义尽量单一、精简,提高复用和可维护性,避免膨胀 -
开闭原则(Open Closed Principle)
对拓展开放,对修改闭合。尽量拓展,减少对原有类的直接修改,新增的功能尽量不要影响原有的功能,降低耦合风险 -
里氏替换原则(Liskov Subsititution Principle)
子类要实现父类的功能,可以完全替换父类。 -
迪米特法则(Law of Demeter),又叫"最少知道法则"
尽量对外面暴露少的接口,降低业务方使用难度 -
接口隔离原则(Interface Segregation Principle)
业务方尽量不要依赖它不需要的接口,这就要求基础模块能做基本的拆分,业务方按需要依赖 -
依赖倒置原则(dependence Inversion Principle)
依赖接口,不要依赖实现,减少耦合,增加灵活度