diff --git a/exampleSite/content/post/2024-04-19面试复盘/index.zh-cn.md b/exampleSite/content/post/2024-04-19面试复盘/index.zh-cn.md index 70aa314..f5f2da8 100644 --- a/exampleSite/content/post/2024-04-19面试复盘/index.zh-cn.md +++ b/exampleSite/content/post/2024-04-19面试复盘/index.zh-cn.md @@ -17,19 +17,113 @@ This article offers a sample of basic Markdown. # 正文开始 -1.struct和class的区别 +在线编程工具: -2.值传递,地址传递,引用传递(拷贝)的区别 +https://www.bejson.com/runcode/c740/ -4.指针是几个字节? -5.static关键字 -6.虚函数指针,虚函数表 +sizeof(空类) = 1; -7.多态 +静态成员变量和成员函数都不占类的大小(sizeof),也就是说静态成员变量和成员函数都不属于类 -8.编译器如何知道你是进行的函数重载 + + +``` +继承下面说了 + +封装:将变量设置为protected或者provate,限制这些变量在类外的直接访问,我们可以使用get常量函数来完成对这个变量的封装 + +protected:当一个成员被定义为protected类型时,仅能在类内、友元和派类访问。 + +private:当一个成员被定义为private类型时,仅能在类内、友元访问。 + +多态下面说了 +``` + + + +## 1.struct和class的区别 + +首先,class是cpp独有的,c语言里面没有, + +然后就是在cpp中,class的默认访问权限是private,而struct是public; + +还有就是class默认都是私有继承,而sruct默认是公有继承. + +最后一点是,在语法上的区别,你使用cpp中 , + +不管是struct还是class,都可以直接使用类名. + +在c语言中,要么写 struct A a;(带上struct),要么你用typedef struct A A;一下 + + + +涉及知识:https://blog.csdn.net/weixin_52668597/article/details/136666085?spm=1001.2014.3001.5501 + + + +补充:公有继承,保护继承与私有继承下,类内的元素的权限变化: + +- 公有继承:原来啥权限就还是啥权限,一点不变 +- 保护继承:原来公有变保护,其他两个->原来啥权限就还是啥权限 +- 私有继承:原来公有变私有,原来保护变私有,原来私有还是私有 + +## 2.值传递,地址传递,引用传递(拷贝)的区别 + +值传递你无法在函数中改变这个变量的值 + +地址传递和引用你可以在函数中改变这个变量的值 + +值传递相当于是你建立了一个副本,你是你,他是他,你的改变无法影响到他,他的改变无法影响到你. + +地址传递的话,传进去的就是本人了,此时修改地址上的数据会影响到原始数据. + +而引用传递相当于是给该变量取的别名,相当于是一个大名一个小名比如鲁迅和周树人,本质上还是一个人,所以改变引用传递的变量也可以改变变量的原始数据 + +## 4.指针是几个字节? + +64位系统是8字节 + +32位系统是4字节 + +## 5.static关键字 + +static的用法分为静态变量与静态函数 + +静态变量将变量限定在当前文件下,且生命周期和该文件运行的周期一样长. + +然后再细分就是局部静态变量与全局静态变量:局部静态变量被限定在一个代码块内(一般是一个函数),而全局静态变量只要是在这个文件内,都可以访问. + + + +再者就是静态函数了,静态成员函数只能访问静态成员变量 + + + +然后就是,类中的静态成员变量并不属于这个类,你用sizeof去算,他不算这个类的大小 + + + +涉及知识:cpp编译单元 https://blog.csdn.net/weixin_52668597/article/details/137575061?spm=1001.2014.3001.5501 + +## 6.虚函数指针,虚函数表 + +涉及知识:https://blog.csdn.net/weixin_52668597/article/details/137447982?spm=1001.2014.3001.5501 + +只要你在类中写了一个虚函数,那么就会产生一个虚函数表. + +而且不管你写多少个虚函数,这些虚函数在类中所占的内存就只有8个字节,而占的这八个字节就是虚函数指针,也就是说,这些虚函数都放在一个数组中,这个数组就是虚函数表 + +## 7.多态 + +涉及知识:https://blog.csdn.net/weixin_52668597/article/details/137447982?spm=1001.2014.3001.5501 + + + +多态其实就是 父类指针指向子类对象,如果子类重写了父类的方法,那么这个父类指针就会呈现出不同的现象 + +## 8.编译器如何知道你是进行的函数重载 答:函数重载,它允许在同一个作用域中定义多个同名函数,但这些函数的参数列表或类型必须不同。在调用这些函数时,编译器会根据传递给函数的参数的数量、类型或顺序来确定要调用的具体函数版本。 @@ -41,7 +135,7 @@ This article offers a sample of basic Markdown. -9.构造,析构的顺序 +## 9.构造,析构的顺序 在C++中,对象的构造和析构顺序 @@ -80,9 +174,48 @@ Child::fun() -10.stl:vector、list、Map的区别 +## 10.stl:vector、list、Map的区别 -11.智能指针 +vector:底层是一个动态数组,可以通过下标进行快速随机访问,占据连续的内存,在不扩容的情况下,在尾部增删很快.在扩容的情况下,需要先开辟原来容器大小的1.5倍,再将原数据拷贝到新的内存,最后在释放原内存 + +list:是一个双向链表,他虽然也可以用下标访问,但是那是因为他重载了中括号运算符,他访问一个元素的复杂度是o(n),他的内存是不连续的,节点之间通过指针连接.他唯一的优势是可以快速的在任意位置进行增删,但前提是先经过o(n)的复杂度找到这个位置 + +Map:是一个键值对,类似于json那种结构,底层是红黑树实现. + +每个键都是唯一的(键和值可以为null,但是键只能出现一次), + +map在插入好删除时会自动排序,他对元素的访问、插入和删除操作,这些操作的时间复杂度通常是对数时间(即`O(log n)`)。 + + + +```cpp +#include +#include + +using namespace std; + +class A{}; + +int main() +{ + map m1,m2; + m1[nullptr] = 1; + A *a = new A(); + m2[a] = 2; + + printf("%d,%d",m1[nullptr],m2[a]); + + map m3; + m3[nullptr] = nullptr; + return 0; +} + + +``` + + + +## 11.智能指针 智能指针有auto_ptr,share_ptr,unique_ptr还有shared_ptr @@ -109,11 +242,17 @@ auto_ptr是C++98中引入的第一个智能指针,但是由于他的不安全 -12.项目中如何保证线程安全? +## 12.项目中如何保证线程安全?(如何避免死锁) -13.死锁 +加锁:**互斥锁**,**读写锁**,cpp中特有的智能锁 -14.多线程中多个信号与主线程 +## 13.死锁 + +死锁产生的条件: + +![1713633820870](图片/1713633820870.png) + +## 14.多线程中多个信号与主线程 主线程一般是负责控制和协调其他线程的任务。比如说我这个共享单车项目中的服务器,他的主线程主要是负责监听客户端的连接,一旦有客户端来进行连接,我们就会把这个客户端通过libevent变成一个事件,并把他加入到事件集合中去,然后由其他线程来监听该客户端的读写事件,并通过轮询的方式拿到线程池中的一个线程,然后由这个线程通过调用它自己的回调函数来处理这个读写事件.相当于主线程就是是负责统筹规划的,他只复制监听客户端,剩下的交给其他线程. @@ -127,38 +266,122 @@ auto_ptr是C++98中引入的第一个智能指针,但是由于他的不安全 常见模式有: -1. **条件变量(Condition Variables)**:相当于是一种基于互斥锁的线程间同步机制,通常情况下是和锁一起使用的。主线程可以使用条件变量来等待特定的条件达成,而其他线程则可以通过发送信号来通知主线程条件的改变。一旦条件满足,主线程就会被唤醒并继续执行。 +1). **条件变量(Condition Variables)**:相当于是一种基于互斥锁的线程间同步机制,通常情况下是和锁一起使用的。主线程可以使用条件变量来等待特定的条件达成,而其他线程则可以通过发送信号来通知主线程条件的改变。一旦条件满足,主线程就会被唤醒并继续执行。 -2. **事件(Events)**:事件也是一种用于线程间同步的通信机制,通常用于向其他线程发出信号以触发特定事件的发生,然后执行事件的回调函数。主线程可以等待某个事件的发生,其他线程可以通过发送信号来触发该事件。 +2). **事件(Events)**:事件也是一种用于线程间同步的通信机制,通常用于向其他线程发出信号以触发特定事件的发生,然后执行事件的回调函数。主线程可以等待某个事件的发生,其他线程可以通过发送信号来触发该事件。 -3. **信号量(Semaphores)**: +3). **信号量(Semaphores)**: - 信号量维护一个计数器,表示可用资源的数量。线程可以通过请求(等待)和释放(发信号)操作来获取和释放资源。当计数器为正时,线程可以继续执行,否则线程会被阻塞直到资源可用。就是我们常说的那个操作系统pv操作,经常用来解决“生产者消费者的问腿” + 信号量维护一个计数器,表示可用资源的数量。线程可以通过请求(等待)和释放(发信号)操作来获取和释放资源。当计数器为正时,线程可以继续执行,否则线程会被阻塞直到资源可用。就是我们常说的那个操作系统pv操作,经常用来解决“生产者消费者的问题”,当然信号量也是可以的 伪代码: - ```cpp - 待补充。。。 - ``` +```cpp +生产者和消费者都可以有很多个 - +情况有三种: +1.队列已满,生产者停止生产商品 -> 生产者wait条件变量 +2.队列为空,消费者无商品可取 -> 消费者wait条件变量 +3.队列不满但非空,生产者正常生产商品,消费者正常取商品 -4. **消息队列(Message Queues)**:主线程可以通过发送消息到队列中来通知其他线程,而其他线程则可以从队列中接收消息并执行相应的操作。 +#include "需要的头文件" + +//wait()-> P()操作 -- +//signal()-> V()操作 ++ + +typedef struct{ + int value; //剩余资源数 + struct process *list;//等待队列 +} semaphore; + +semaphore empty,full;//空位和产品的数量 空位数+产品数=最大容量 +Mutex mutex1,mutex2;//两个互斥锁 + +//申请使用资源 +void wait(semaphore s) +{ + s.value --; + if(s.value < 0)//剪完1后,可能等于0(表示资源刚好用完,但是够用,所以不阻塞),也可能<0(资源早就用完了,再减一就没得用了,所以要阻塞) + { + block(s.list); + } +} + +//使用完释放资源 +void signal(semaphore s) +{ + s.value ++; + if(s.value <= 0)//刚释放完一个资源(也就是说现在有一个资源可用),发现资源数==0,说明说明之前是<0的,表示队列已经阻塞了;如果发现资源数<0,那就更不用说了,肯定有消费者在等待 + { + wakeup(s.list); + } +} +// 生产者函数 +void producer() { + while (1) { + produce();//生产1个东西 + wait(empty);//空位减1个 + wait(mutex1);//上锁 + + array[空位] = 产品;//给一个空位放产品 + + signal(mutex1);//解锁 + signal(full);//产品数+1 + + } +} + +// 消费者函数 +void consumer() { + while (1) { + wait(full);//产品数-1 + wait(mutex2);//上锁 + + array[产品数] = null;//拿走一个产品 + + signal(mutex2);//解锁 + signal(empty);//空位+1 + + comsum();//消费这个产品 + } +} + +int main() { + std::thread prod(producer); + std::thread cons(consumer); + prod.join(); + cons.join(); + return 0; +} + + +``` + +``` +信号量(Semaphore): +信号量是一种用于控制对共享资源的访问的同步机制。它通常用于实现进程或线程间的协调。信号量主要包含两种操作:wait()(或称为P()操作)和 signal()(或称为V()操作)。wait()操作会减少信号量的计数,如果结果为负数,则线程会阻塞。signal()操作会增加信号量的计数,如果有线程正在阻塞,它会被唤醒。 +``` + + +4). **消息队列(Message Queues)**:主线程可以通过发送消息到队列中来通知其他线程,而其他线程则可以从队列中接收消息并执行相应的操作。 +15.qt信号槽中有第五个参数,有了解过吗? +信号槽中有第五个参数支持qt中,信号与槽进行跨跨线程通信,他是一个枚举值, +默认是自动连接,也比较智能,如果信号与槽是在同一个线程,就直接调用槽函数,如果不在同一个线程,会自动使用QueuedConnection. +然后支持多线程的枚举值其实有两个,一个是Queued Connection,另一个是BlockingQueuedConnection,这两个支持信号与槽的异步通信,唯一的区别是: +Queued Connection是异步非阻塞的,BlockingQueuedConnection是异步阻塞的,然后非阻塞是运行后直接返回,阻塞是运行结束后才返回. +Queued Connection用的比较多,他可以保证槽函数的调用是在接收者的线程中进行的,所以就不会有多个线程同时访问槽函数中的共享资源的情况发生(除非槽函数内部又进行了不安全的跨线程操作),这样就避免线程竞争,避免了线程安全的问题。 - - - # 补充: diff --git a/exampleSite/content/post/2024-04-19面试复盘/图片/1713633820870.png b/exampleSite/content/post/2024-04-19面试复盘/图片/1713633820870.png new file mode 100644 index 0000000..e5ea543 Binary files /dev/null and b/exampleSite/content/post/2024-04-19面试复盘/图片/1713633820870.png differ