记录部分从牛客网上看到的面筋

1.声明一个vector,当vector过大时会栈溢出吗?

答案是会。

默认申请的vector是放在栈区的,栈区的大小<<堆区的大小。所以如果我们在栈区中开辟的vector中插入巨量的数据,是会导致栈溢出的。

解决办法是将数据存放到堆区上(使用new来创建vector,而不是直接创建)

2.如何实现多次运行程序但只有一个后台进程?

使用命名互斥锁,程序启动前申请锁。

  • 如果锁没有被申请,代表是第一个进程,可以正常运行
  • 如果锁已经被占用,代表已经有进程了,直接退出当前进程(这里要使用try_lock避免阻塞等待)

在Linux下可以用命名信号量来实现类似进程共享锁的操作。这部分可以去学习进程通信中信号量的部分。

咨询了发这篇面筋的大佬,说是用文件保存之前进程的PID,读取出来将之前的进程kill掉。

3.二分法的前提是什么?

  • 数据有序
  • 数据结构支持随机访问

4.互斥锁和自旋锁有什么区别

  • 互斥锁是在内核态进行阻塞等待
  • 自旋锁是在用户态不断循环沦陷检测锁的状态

如果使用场景是较长运行的共享资源,那么就使用互斥锁。避免自旋锁不断沦陷检测消耗大量CPU资源。

如果使用场景的共享资源访问速度快,那么可以使用自旋锁。避免互斥锁频繁进行用户、内核态的转换而造成消耗(这里指其他需要获取锁的进程得进入内核态阻塞等待)

5.TCP三次握手除了序列号还发了什么其他东西?

  • SYN和ACK这些表记位(具体复习三次握手每个阶段的发送)
  • 双方服务进程的端口号
  • 起始序列号和对对方发送的SYN报文的应答序列号
  • TCP校验和
  • TCP窗口大小

后续建立连接后,就会根据双方的窗口大小和数据的序列号开始相互通信。

6.子类重写父类函数,子类中该函数声明为private,能否重写成功?

用下面这个毛坯房来进行测试,在默认情况下,我们子类的重写函数都和父类有相同的作用域声明符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
class A
{
public:
virtual void test()
{
foo1();
foo2();
foo3();
}

virtual void foo1()
{
cout << "A::foo1" << endl;
}

protected:
virtual void foo2()
{
cout << "A::foo2" << endl;
}

private:
virtual void foo3()
{
cout << "A::foo3" << endl;
}
};

class B : public A
{
public:
void test()
{
foo1();
foo2();
foo3();
}
virtual void foo1() override
{
cout << "B::foo1" << endl;
}

protected:
virtual void foo2() override
{
cout << "B::foo2" << endl;
}

private:
virtual void foo3() override
{
cout << "B::foo3" << endl;
}
};

int main()
{
B b;
b.test();
cout << "----" << endl;
A *bb = &b;
bb->test();

return 0;
}

直接运行,结果也符合预期,目前调用的是子类重写后的虚函数,所有函数都重写成功。

1
2
3
4
5
6
7
B::foo1
B::foo2
B::foo3
----
B::foo1
B::foo2
B::foo3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class B : public A
{
public:
void test()
{
foo1();
foo2();
foo3();
}
virtual void foo2() override
{
cout << "B::foo2" << endl;
}


protected:
virtual void foo3() override
{
cout << "B::foo3" << endl;
}
private:

virtual void foo1() override
{
cout << "B::foo1" << endl;
}
};
// 编译能通过,运行输出和上方没区别

我尝试了各种修改作用域的方式,包括将子类中foo3函数改成公有,foo1函数改成私有,都能正常完成重写。这里的作用域声明符只是会改变子类外是否能调用这个函数,和能否完成虚函数重写无关!

请注意,如果你将继承方式由public改成private,那么就无法在类外使用父类指针指向子类对象了

1
2
3
4
╰─ g++ test2.cpp -o test
test2.cpp: In function ‘int main()’:
test2.cpp:316:11: error: ‘A’ is an inaccessible base of ‘B’
A *bb = &b;

但这依旧不影响子类函数重写父类函数(我的依据是override关键字没有报错)

7.pthread_create能传入类成员函数的指针吗

可以,但是必须是静态成员函数。

如果是普通成员函数,那就需要用中间函数来处理,比如下面的示例代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <functional>
#include <pthread.h>

void* threadFunc(void* arg) {
std::function<void()> func = *reinterpret_cast<std::function<void()>*>(arg);
func();
return nullptr;
}

int main() {
std::function<void()> myFunc = []() {
std::cout << "Hello from thread!" << std::endl;
};

pthread_t thread;
pthread_create(&thread, nullptr, &threadFunc, reinterpret_cast<void*>(&myFunc));

// 等待线程结束
pthread_join(thread, nullptr);

return 0;
}

我们可以用std::function包装一个类的成员函数,并用std::bind将对象的this指针绑定到第一个参数上。外层再套上一个用于执行该函数的void函数,就能传给pthread_create

std::function的对象不能直接传给C语言的函数指针,即便参数对应。会报错。

8.函数内static变量的作用

在函数内定义一个static变量,该变量只会在进入这个函数的时候初始化一次。

1
2
3
4
5
6
// 计算某一月的1号是一年的第几天(不考虑闰年)
int DayOfYear(int month)
{
static int day_array = {0,31,59,90,120,151,181,212,243,273,304,334};
return day_array[month];
}

比如上面的函数,我们定义的day数组就只会在第一次进入这个函数的时候初始化。之后进入这个函数将不在初始化,就节省了初始化一个数组的消耗。出了这个函数后,该数组变量依旧存在。

请注意,这个static语句并不只是变量只初始化一次,实际上这一行语句在该函数中都只会进行一次

1
2
3
4
5
6
7
bool checkFlag()
{
static bool flag = false;
if(!flag){
// ..进行对应修改
}
}

比如上面的代码中,如果用正常思维来理解,你会觉得这个if语句每次都会判断为真而进入其中。但实际上flag的定义只会被定义一次,只要我们在if中将其改成了true,那么下一次进入该函数的时候,flag依旧会是truestatic bool flag = false;语句会被直接跳过,并不会再次执行赋值!

我之前理解的就是flag变量只会被创建一次,但static后的赋值依旧会执行。这个理解是错误的!

9.私有static成员函数的意义?

这个问题比较有意思,我们知道static函数属于整个类,可以直接通过类名作用域调用。且static函数中无法访问任何非static的成员变量。

但是,如果给你个static的私有成员函数,它又有什么意义呢?

  • 私有static成员是无法通过类名调用的
  • 他也没有办法访问类中非static成员变量

可以这么理解:我有一个方法只在这个类里面需要,这个方法不需要使用成员变量,可以通过传参实现(比如计算什么的)

但是,我又不想它的命名污染父作用域。

那么,我就可以把它写为类的私有成员函数,并加上static告诉其他人,这个函数是一个单纯的方法类,不需要使用类的成员变量。

当然,加上static只是一个编程习惯罢了,实际上这种情况不写static也无所谓。

10.写出数组指针的定义,指针数组的定义,函数指针的定义,指针函数的定义,函数指针数组的定义

1
2
3
4
5
数组指针:int (*p)[10];  (一个指向存放10个int元素数组的指针)
指针数组:int *p[ ]; (一个数组,数组内元素是指针)
函数指针:int (*p)(int);(一个指向函数地址的指针,函数返回值是int,参数是一个int)
指针函数:int* p(int); (一个返回指针的函数)
函数指针数组:int (*p[ ])(int);(一个数组,数组内存放着指向函数地址的指针)

11.面向对象和面向过程的区别?

C++是面向对象的语言,C是面向过程的语言。

基本理念:

  • 面向对象OOP:基于对象的概念,将数据和操作方法封装在一起,以创建对象。对象可以被看做具有特定功能的独立个体,他们之间可以通过消息传递进行通信。
  • 面向过程:将问题划分为一系列步骤,定义函数来实现每个步骤。代码以函数组成,函数依照顺序调用来完成任务。

这里还需要记住面向对象的三大特性:封装、基础、多态。

  • 封装:封装类内部成员和函数实现,只提供接口供外部调用;
  • 继承:允许子类在继承的基础上扩展,代码编写更容易;
  • 多态:用父类调用子类的函数,即不同对象对同一个功能做出不同的响应,提高灵活性和扩展性。

面向过程是顺序性(函数定义顺序执行任务)和功能性(主要关注功能实现,强调函数和过程的设计以及调用)