C++编程经验(12):C+

最后

笔者已经把面试题和答案整理成了面试专题文档

image

image

image

image

image

image

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

using MsgHandler = std::function<void(const TcpConnectionPtr &conn,json &js,Timestamp time)>;

理解为:

typename void(const TcpConnectionPtr &conn,json &js,Timestamp time) MsgHandler ;

MsgHandler 是一个自定义数据类型,函数指针。

既然是一个数据类型,就可以被塞到容器里面:

unordered_map<int,MsgHandler> _msgHanderMap;

函数指针有什么用,它就有什么用,可以用来推迟函数的声明。


绑定器是干嘛的呢?将参数绑定到函数指针上的。

以前的绑定器只能绑定一个参数,所以我们看到的很多古老的需要函数指针做传参的函数都只有一个参数传递,但是有了新的绑定器就不一样了。

_msgHanderMap.insert({LOGIN_TYPE,std::bind(&ChatService::login,this,_1,_2,_3)});

//将三个参数绑定到login函数上,由于是在类内,所以带上一个this。

std::bind(&ChatService::login,this,_1,_2,_3)

//这三个参数使用占位符事先申明


绑定好了,现在要调用这个函数就需要在调用的时候传参,那被绑定的函数要如何取参数,这就取决于占位符的声明了。

std::placeholders决定函数占用位置取用输入参数的第几个参数。


那么现在一条脉络就很清楚了。

要使用函数指针,使用function进行函数指针模板的声明与调用;

实例化function模板所用的函数可能有不下于1个的参数,旧的绑定器已经不行了,用新的绑定器来吧;

而函数指针需要从调用函数指针的函数那里去获得传入参数,这个参数的位置排序的确定就需要靠占位符来指定了,或许可以称之为导航符吧。


lambda表达式


简单来说,Lambda函数也就是一个函数,它的语法定义如下:

capture mutable ->return-type{statement}

1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;

2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;

3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;

5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:

1.[var]表示值传递方式捕捉变量var;

2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);

3.[&var]表示引用传递捕捉变量var;

4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);

5.[this]表示值传递方式捕捉当前的this指针。

6.[]没有任何函数对象参数。

7.&a。将 a 按引用进行传递。

8.a,&b。将 a 按值传递,b 按引用进行传递。

9.=,&a,&b。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。

10.&,a,b。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。


move

对于move了解不多。

C++11为了解决这个问题,提供了std::move()方法来将左值转换为右值,从而方便应用移动语义。move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转义,没有内存拷贝。


类相关


explicit类型转换运算符

防止类构造发生默认类型转换

对这个关键字我现在持怀疑态度了,是我的VS坏了,还是我的眼睛瞎了呢?

下面三个测试案例结果都是一样的。

#include

#include

using namespace std;

//发生了转换

//class A {

//public:

// explicit A(int i,int j) {

// cout << i << endl;

// }

//

//};

//

//

//int main() {

// A a(‘a’, 20);

//}

//依旧发生了转换,有什么区别吗?

//class A {

//public:

// explicit A(int i) {

// cout << i << endl;

// }

//

//};

//

//

//int main() {

// A a(‘a’);

//}

class A {

public:

A(int i) {

cout << i << endl;

}

};

int main() {

A a(‘a’);

}


=default和=delete

如果实现了默认的构造函数,编译器则不会自动生成默认版本;可以通过使用关键字 default 来控制默认构造函数的生成,显示的指示编译器生成该函数的默认版本;

如果不想有某些默认生成的函数,就设置一个 =delete。

如果给类手动写了带参构造,那也是无法显式使用无参构造函数了。

如果没有了默认构造,子类就不能不传参给父类进行构造了。


override、final

final关键字的作用是使派生类不可覆盖它所修饰的虚函数。

override关键字的作用是使派生类被制定的函数必须是覆盖它所修饰的虚函数。


using


现在不仅仅可以用它来引用名空间了,不过现在我也不怎么用这个来引用名空间了,都是用域作用符::。

现在用它都是用来替代以前的typedef了,而且一般是和下面的function函数对象结合在一起使用,最近在整muduo,这些接触到的会比较多。


volatile


在这里插入图片描述

如上图所示,所有线程的共享变量都存储在主内存中,每一个线程都有一个独有的工作内存,每个线程不直接操作在主内存中的变量,而是将主内存上变量的副本放进自己的工作内存中,只操作工作内存中的数据。当修改完毕后,再把修改后的结果放回到主内存中。每个线程都只操作自己工作内存中的变量,无法直接访问对方工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

如果对变量 i 加上 volatile 关键字修饰的话,它可以保证当 A 线程对变量 i 值做了变动之后,会立即刷回到主内存中,而其它线程读取到该变量的值也作废,强迫重新从主内存中读取该变量的值,这样在任何时刻,AB线程总是会看到变量 i 的同一个值。

它不是原子操作的。


线程


Thread

std::thread无疑是一个重磅福利。

std::thread 在 <thread> 头文件中声明,因此使用 std::thread 时需要包含 <thread> 头文件。

线程构造

默认构造函数 thread() noexcept;

初始化构造函数 template <class Fn, class… Args>

explicit thread(Fn&& fn, Args&&… args);

拷贝构造函数 [deleted] thread(const thread&) = delete;

Move 构造函数 thread(thread&& x) noexcept;

Move 赋值操作 thread& operator=(thread&& rhs) noexcept;

拷贝赋值操作 [deleted] thread& operator=(const thread&) = delete;

默认构造函数,创建一个空的 std::thread 执行对象。

初始化构造函数,创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。

拷贝构造函数(被禁用),意味着 std::thread 对象不可拷贝构造。

Move 构造函数,,调用成功之后 x 不代表任何 std::thread 执行对象。

注意:可被 joinable 的 std::thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

Move 赋值操作(1),如果当前对象不可 joinable,需要传递一个右值引用(rhs)给 move 赋值操作;如果当前对象可被 joinable,则会调用 terminate() 报错。

拷贝赋值操作(2),被禁用,因此 std::thread 对象不可拷贝赋值。

其他方法

get_id: 获取线程 ID,返回一个类型为 std::thread::id 的对象。

joinable: 检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。另外,如果某个线程 已经执行完任务,但是没有被 join 的话,该线程依然会被认为是一个活动的执行线程,因此也是可以被 join 的。

detach: Detach 线程。 将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

swap: Swap 线程,交换两个线程对象所代表的底层句柄

thread 1 id: 1892

thread 2 id: 2584

after std::swap(t1, t2):

thread 1 id: 2584

thread 2 id: 1892

after t1.swap(t2):

thread 1 id: 1892

thread 2 id: 2584

yield: 当前线程放弃当前时间片,操作系统调度另一线程继续执行。

sleep_until: 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

sleep_for: 线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长。


threadpool示例

缩略muduo库(5):Thread、EventThread、EventThreadPool

对这份线程池我还是有自信的。


锁种

lock_guard

创建lock_guard对象时,它将尝试获取提供给它的互斥锁的所有权。当控制流离开lock_guard对象的作用域时,lock_guard析构并释放互斥量。

它的特点如下:

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

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

不能复制


unique_lock

简单地讲,unique_lock 是 lock_guard 的升级加强版,它具有 lock_guard 的所有功能,同时又具有其他很多方法,使用起来更强灵活方便,能够应对更复杂的锁定需要。

特点如下:

创建时可以不锁定(通过指定第二个参数为std::defer_lock),而在需要时再锁定

可以随时加锁解锁

作用域规则同 lock_grard,析构时自动释放锁

不可复制,可移动

条件变量需要该类型的锁作为参数(此时必须使用unique_lock)

示例:

#include

#include

#include

struct Box {

explicit Box(int num) : num_things{num} {}

int num_things;

std::mutex m;

};

void transfer(Box &from, Box &to, int num)

{

// don’t actually take the locks yet

std::unique_lockstd::mutex lock1(from.m, std::defer_lock);

std::unique_lockstd::mutex lock2(to.m, std::defer_lock);

// lock both unique_locks without deadlock

std::lock(lock1, lock2);

from.num_things -= num;

to.num_things += num;

// ‘from.m’ and ‘to.m’ mutexes unlocked in ‘unique_lock’ dtors

}

int main()

{

Box acc1(100);

Box acc2(50);

std::thread t1(transfer, std::ref(acc1), std::ref(acc2), 10);

std::thread t2(transfer, std::ref(acc2), std::ref(acc1), 5);

t1.join();

t2.join();

}


condition_variable

条件变量。

通知方:

获取 std::mutex, 通常是 std::lock_guard

修改共享变量(即使共享变量是原子变量,也需要在互斥对象内进行修改,以保证正确地将修改发布到等待线程)

在 condition_variable 上执行 notify_one/notify_all 通知条件变量(该操作不需要锁)

等待方:

获取相同的 std::mutex, 使用 std::unique_lock

执行 wait,wait_for或wait_until(该操作会自动释放锁并阻塞)

接收到条件变量通知、超时或者发生虚假唤醒时,线程被唤醒,并自动获取锁。唤醒的线程负责检查共享变量,如果是虚假唤醒,则应继续等待

std :: condition_variable仅适用于 std::unique_lock

对于只需要通知一次的情况,如初始化完成、登录成功等,建议不要使用 condition_variable,使用std::future更好。不过这个我还没有去了解。


CAS 和 atomic

在有些场景里面,是需要对一些资源进行锁定的。但是有些资源实在是太小了,锁定的粒度也太小了,不免显得上锁解锁倒成了繁琐。

比方说:

_mlock.lock();

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

算法刷题(PDF)

我的美团offer凉凉了?开发工程师(Java岗)三面结束等通知...

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

用 condition_variable,使用std::future更好。不过这个我还没有去了解。


CAS 和 atomic

在有些场景里面,是需要对一些资源进行锁定的。但是有些资源实在是太小了,锁定的粒度也太小了,不免显得上锁解锁倒成了繁琐。

比方说:

_mlock.lock();

总结

谈到面试,其实说白了就是刷题刷题刷题,天天作死的刷。。。。。

为了准备这个“金三银四”的春招,狂刷一个月的题,狂补超多的漏洞知识,像这次美团面试问的算法、数据库、Redis、设计模式等这些题目都是我刷到过的

并且我也将自己刷的题全部整理成了PDF或者Word文档(含详细答案解析)

[外链图片转存中…(img-FxgBiOZX-1715817324049)]

66个Java面试知识点

架构专题(MySQL,Java,Redis,线程,并发,设计模式,Nginx,Linux,框架,微服务等)+大厂面试题详解(百度,阿里,腾讯,华为,迅雷,网易,中兴,北京中软等)

[外链图片转存中…(img-FRpqrr55-1715817324049)]

算法刷题(PDF)

[外链图片转存中…(img-ruh2X5cZ-1715817324049)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值