hugo-theme-stack/exampleSite/content/post/2024-04-19面试复盘/index.zh-cn.md
2024-04-20 17:56:56 +08:00

319 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

+++
author = "Wxn"
title = "2024-04-19面试复盘"
date = "2024-04-19"
description = "Please read me first."
tags = [
"Dilay",
]
categories = [
"面试复盘",
]
+++
This article offers a sample of basic Markdown.
<!--more-->
# 正文开始
1.struct和class的区别
2.值传递,地址传递,引用传递(拷贝)的区别
4.指针是几个字节?
5.static关键字
6.虚函数指针,虚函数表
7.多态
8.编译器如何知道你是进行的函数重载
答:函数重载,它允许在同一个作用域中定义多个同名函数,但这些函数的<font color=red>参数列表或类型</font>必须不同。在调用这些函数时,编译器会根据传递给<font color=red>函数的参数的数量、类型或顺序</font>来确定要调用的具体函数版本。
简单来讲,就是函数重载可以是<font color=red>参数类型不同,参数顺序不同,也可以是参数个数不同</font>
编译器会在内部,把函数名称和参数列表一起存储在符号表中,这样编译器就知道了你用的是哪个重载之后的函数
就是,编译器在处理函数重载时,会将函数名称和参数列表一起存储在符号表(或类似符号表的数据结构)中。这样编译器就能够确定每个函数的唯一签名,并且在需要进行函数调用匹配时可以快速检索并确定调用哪个函数版本。
9.构造,析构的顺序
在C++中,对象的构造和析构顺序
最主要是
和对象的创建和销毁顺序相关。
一般情况下,<font color=red>对象的构造顺序与对象的创建顺序相同,对象的析构顺序与对象的销毁顺序相反。</font>
比如说,如果一个类 `A` (包含类 `B` 和类 `C` 的对象作为成员),则在创建 `A` 的对象时,首先会调用 `B` 的构造函数,然后调用 `C` 的构造函数。
然后析构顺序就与构造顺序相反。也就说当销毁对象A时析构函数会按照对象在类成员<font color=red>声明</font>中的逆序依次调用。继续上面的例子,也就是说,他首先会调用 `C` 的析构函数,然后调用 `B` 的析构函数。
还有一种情况:就是父类和子类的析构与构造关系:
比如说现在有一个子类b,他继承了父类a,那么在创建子类b的对象时,
就会先调用父类a的构造函数,
再调用自己子类b的构造函数,
在析构的时候,顺序就反过来了,
就是先析构子类b,
最后再析构父类a
```cpp
Base::fun()
Child::fun()
~Child::fun()
~Base::fun()
```
10.stlvector、list、Map的区别
11.智能指针
智能指针有auto_ptrshare_ptrunique_ptr还有shared_ptr
auto_ptr是C++98中引入的第一个智能指针但是由于他的不安全性已被C++11弃用。他其实被unique_ptr给取代了,可以理解为auto_ptr他其实是一个浅拷贝,非常容易造成多个指针指向同一块内存区域的现象,这就引发非常多的潜在的问题:比如一些常见的段错误,多次析构,访问一段未定义的空间等,比如说你在文件中访问这个指针,但是其实你已经在其他地方释放这段内存了.
然后,unique_ptr 他提供了独占所有权的智能指针,允许有且仅有一个 `unique_ptr` 拥有资源。与 `auto_ptr` 不同,`unique_ptr` 实现了更安全和更完整的独占所有权语义
然后,shared_ptr,他允许多个指针共享同一块内存,<font color=red>并且会在最后一个 shared_ptr 被销毁时释放这段内存</font>。它使用的是一种引用计数的技术来追踪资源的所有者数量,并在所有者数量为零时自动释放资源。
然后最后一个,是weak_ptr: 他是 `shared_ptr` 的伴侣类它允许你共享资源但不拥有资源。weak_ptr 不会增加引用计数,它通常用于解决 `shared_ptr` 的循环引用问题。
循环引用的问题:
然后循环引用通常是shared_ptr互相引用造成的,两个或多个对象相互持有对方的 shared_ptr导致它们的引用计数永远不会降为零然后导致资源泄漏。
我想到的一个例子是,比如说有一个双向列表,他们的每个节点对象都有两个指针,一个是前指针prior pointer一个是next指针
然后两个相邻的节点对象节点1的next指针指向节点2自身节点2的前指针prior pointer指向节点1自身然后这两个节点都用share_ptr进行创建,在创建时,节点自身这段内存引用计数+1,然后被相邻指针指向时,引用计数再+1,这样每个节点的引用计数就都是2,
然后当我们销毁节点这个share_ptr后,前后节点的引用计数都降为了1,但是前节点在等着后节点的prior pointer放手,后节点在等着前节点的next pointer放手,这就形成了死锁,
造成的现象就是这两个节点的指针已经被销毁了,但是节点的内存迟迟无法析构,就造成了"内存泄漏",解决的办法就是把这个节点的prior指针和next指针都变成weak_ptr
12.项目中如何保证线程安全?
13.死锁
14.多线程中多个信号与主线程
# 补充:
## 1)C语言链表
```c
#include <stdio.h>
#include <stdlib.h>
typedef struct Node{
int data;
struct Node * prior;
struct Node * next;
} Node;
int main ()
{
/***
* c语言像数组的链表
*/
/*
Node* list = (Node *)malloc(sizeof(Node) *5);
if (list == NULL) {
// 内存分配失败的处理
fprintf(stderr, "Memory allocation failed.\n");
return 1;
}
// 初始化链表节点
for (int i = 0; i < 5; ++i) {
list[i].data = i; // 数据域初始化为索引值或其他你选择的值
if (i == 0) {
// 第一个节点的前驱指向 NULL
list[i].prior = NULL;
} else {
// 其他节点的前驱指向前一个节点
list[i].prior = &list[i - 1];
}
if (i == 4) {
// 最后一个节点的后继指向 NULL
list[i].next = NULL;
} else {
// 其他节点的后继指向下一个节点
list[i].next = &list[i + 1];
}
}
// ... 使用链表
for(int i = 0; i< 5;i++)
{
int num = list[i].data;
printf("%d\n",num);
}
// 释放分配的内存
free(list);
*/
/******c语言链表********/
Node *n1 = (Node *)malloc(sizeof(Node));
Node *n2 = (Node *)malloc(sizeof(Node));
Node *n3 = (Node *)malloc(sizeof(Node));
n1->data = 1;
n1->prior = NULL;
n1->next = n2;
n2->data = 2;
n2->prior = n1;
n2->next = n3;
n3->data = 3;
n3->prior = n2;
n3->next = NULL;
for(Node * node = n1; node != NULL;node = node->next)
{
int num = node->data;
printf("[%d]\n",num);
}
return 0;
}
输出:
[1]
[2]
[3]
```
## 2)链表-智能指针版
```cpp
#include <iostream>
#include <memory>//智能指针
using namespace std;
struct Node{
int data;
weak_ptr<Node> prior;
weak_ptr<Node> next;
};
int main()
{
shared_ptr<Node> n1 = make_shared<Node>();
shared_ptr<Node> n2 = make_shared<Node>();
shared_ptr<Node> n3 = make_shared<Node>();
n1->data = 1;
n1->prior.reset();//智能指针设置为空应该用reset函数,而不是n1->prior = nullptr;
n1->next = n2;
n2->data = 2;
n2->prior = n1;
n2->next = n3;
n3->data = 3;
n3->prior = n2;
n3->next.reset();//n3->next = nullptr;
for(shared_ptr<Node> node = n1 ; node != nullptr ; node = node -> next.lock()){//你需要先用lock将weak_ptr提升为shared_ptr才能够进行赋值
int num = node ->data;
printf("[%d]\n",num);
}
//销毁智能指针(你也可以不销毁,因为智能指针自己非常懂事)
n1.reset();
n2.reset();
n3.reset();
return 0;
}
输出:
[1]
[2]
[3]
```
## 3)链表-普通cpp版
```cpp
#include <iostream>
using namespace std;
struct Node{
int data;
Node* prior;
Node* next;
};
int main()
{
Node * n1 = new Node;
Node * n2 = new Node;
Node * n3 = new Node;
n1->data = 1;
n1->prior = nullptr;
n1->next = n2;
n2->data = 2;
n2->prior = n1;
n2->next = n3;
n3->data = 3;
n3->prior = n2;
n3->next = nullptr;
for(Node * node = n1 ; node != nullptr ; node = node->next){
int num = node ->data;
printf("[%d]\n",num);
}
//销毁智能指针
delete n1;
delete n2;
delete n3;
return 0;
}
输出:
[1]
[2]
[3]
```