面试之CPP基础知识-7


#static_cast比C语言中的转换强在哪里?

  • 在编译期间就能发现转换错误
  • 更容易检索到何处强制转换
  • 不同的转换类型体现程序员的意图

https://blog.csdn.net/liji_digital/article/details/123148607

#成员函数里memset(this,0,sizeof(*this))会发生什么

有时候类里面定义了很多int,char,struct等c语言里的那些类型的变量,但是一句句的写太麻烦,在构造函数用memset(this, 0, sizeof *this);中将基本类型成员变量一次性初始化为0,但是下面几种情形是不可以这么使用的:

  • 类含有虚函数表:这么做会破坏虚函数表,后续对虚函数的调用都将出现异常;
  • 类中含有C++类型的对象:例如,类中定义了一个list的对象,由于在构造函数体的代码执行之前就对list对象完成了初始化,假设list在它的构造函数里分配了内存,那么我们这么一做就破坏了list对象的内存。

https://www.nowcoder.com/search/all?query=memset%28this,%200,%20sizeof%28%2athis%29%29&type=all

#回调函数是?作用?

回调函数是做为参数传递的一种函数

回调函数的创建步骤大概为:

1,声明一个函数指针类型。

2,实现一个使用回调函数的函数,将函数指针作为参数传递。

3,实现一个符合函数指针类型的函数,将该函数的指针作为参数传递给使用它的函数。

或者使用C++标准库的function和bind。std::funtion定义函数类型(输入、输出),std::bind绑定特定的函数(具体的要调用的函数)(对于普通函数直接将函数名给function即可)。

作用:把调用者与被调用者分开。调用者不关心谁是被调用者,它只需知道存在一个满足其需求的被调用函数。

 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
// std::function<returnType(argType, argType,...)> func;
// std::bind(&funcName, std::placeholders::_1, ...);
// 类:std::bind(&className::funcName, classPtr, std::placeholders::_1, ...);
class BrainToolBox
{
public:
    int Add(int a, int b) { return a + b; };
};

bool CompareInt(int a, int b) {
    return a > b;
}

int main()
{
    int a = 4;
    int b = 6;

	std::function<bool(int, int)> compareFunc = CompareInt;

    std::shared_ptr<BrainToolBox> brain = std::make_shared<BrainToolBox>();
    std::function<int(int, int)> addFunc = std::bind(&BrainToolBox::Add, brain, std::placeholders::_1, std::placeholders::_2);
    
   int c = addFunc(a, b);
    std::cout << "c Value: " << c << std::endl;

    return EXIT_SUCCESS;
}

https://www.airchip.org.cn/index.php/2022/03/05/cpp-example-callback/

https://blog.csdn.net/zhoupian/article/details/119495949

#什么是一致性哈希?

大厂双非硕简历过不了,不看。小厂就说不知道。

#C++从代码到可执行程序经历了什么?

(1)预编译

对源代码文件中的头文件、预编译语句(宏定义)进行分析和替换,生成预编译.ii文件。 处理规则见下:

  1. 删除所有的#define,展开所有的宏定义。
  2. 处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
  3. 处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。
  4. 删除所有的注释,“//”和“/**/”。
  5. 保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。
  6. 添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。

(2)编译

把预编译生成的文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码.s文件。

  1. 词法分析:利用类似于“有限状态机”的算法,将源代码程序输入到扫描机中,将其中的字符序列分割成一系列的记号。
  2. 语法分析:语法分析器对由扫描器产生的记号,进行语法分析,产生语法树。由语法分析器输出的语法树是一种以表达式为节点的树。
  3. 语义分析:语法分析器只是完成了对表达式语法层面的分析,语义分析器则对表达式是否有意义进行判断,其分析的语义是静态语义——在编译期能分期的语义,相对应的动态语义是在运行期才能确定的语义。
  4. 优化:源代码级别的一个优化过程。
  5. 目标代码生成:由代码生成器将中间代码转换成目标机器代码,生成一系列的代码序列——汇编语言表示。
  6. 目标代码优化:目标代码优化器对上述的目标机器代码进行优化:寻找合适的寻址方式、使用位移来替代乘法运算、删除多余的指令等。

(3)汇编

将汇编文件转化成机器码,生成可重定位目标文件xxx.o(Linux下)、xxx.obj(Window下)。只是根据汇编指令和机器指令的对照表一一翻译过来,汇编过程有汇编器as完成。

(4)链接

将多个目标文件及所需要的库链接成最终的可执行目标文件(.out或.exe文件)。 又分为静态链接和动态链接

  • 目标文件

    • 可执行目标文件:可以直接在内存中执行;
    • 可重定位目标文件:可与其它可重定位目标文件在链接阶段合并,创建一个可执行目标文件;
    • 共享目标文件:这是一种特殊的可重定位目标文件,可以在运行时被动态加载进内存并链接;
  • 静态链接

    • 函数和数据被编译进一个二进制文件。在使用静态库的情况下,在链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其它模块组合起来创建最终的可执行文件。
    • 空间浪费:因为每个可执行程序中对所有需要的目标文件都要有一份副本,所以如果多个程序对同一个目标文件都有依赖,会出现同一个目标文件都在内存存在多个副本;
    • 更新困难:每当库函数的代码修改了,这个时候就需要重新进行编译链接形成可执行程序。
    • 运行速度快:但是静态链接的优点就是,在可执行程序中已经具备了所有执行程序所需要的任何东西,在执行的时候运行速度快。
  • 动态链接

    • 动态链接的基本思想是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序,而不是像静态链接一样把所有程序模块都链接成一个单独的可执行文件。
    • 共享库:每个程序都依赖同一个库,但是该库不会像静态链接那样在内存中存在多份副本,而是这多个程序在执行时共享同一份副本;
    • 更新方便:更新时只需要替换原来的目标文件,而无需将所有的程序再重新链接一遍。当程序下一次运行时,新版本的目标文件会被自动加载到内存并且链接起来,程序就成了升级的目标。
    • 性能损耗:因为把链接推迟到了程序运行时,所以每次执行程序都需要进行链接,所以性能会有一定损失。

#为什么友元函数必须在类内部声明?

因为这样友元函数才能知道访问的是哪个类的私有成员。

#友元函数和友元类的基本情况

通过友元可以访问类中的私有成员和保护成员。友元的正确使用能提高程序的运行效率(即减少了类型检查和安全性检查等需要的时间开销),但同时也破坏了类的封装性和隐藏性,导致程序可维护性变差。

友元关系是单向的(若要互为友元,必须显式地互相声明为友元),不可继承、不可相互推导

1)友元函数

友元函数分为普通函数和类成员函数。普通函数只需要在类内声明友元函数,并在类外实现函数即可。类成员函数需要先实现一个需要访问另一个类的私有成员的类,然后再使用friend关键字在另一个类中声明这些成员函数。

具体过程如下:

  1. 声明一个Me对象
  2. 在MyFriend类中定义一个使用Me的私有成员的成员函数
  3. 在Me类中声明MyFriend类中某些函数为友元函数
  4. 这是在h,cpp分别实现的过程
 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
//MyFriend.h  声明 包含类成员函数作为友元函数 的类
#pragma once

class Me;
//声明包含类成员友元函数的类
class MyFriend
{
public:
     MyFriend(void){};
     void printMe(const Me &pt);
     double calcDist(const Me &a, const Me &b);
};
// ----------------------------------
//Me.h  声明原始类Me
#pragma once
#include "MyFriend.h"

class Me
{
     //若此处不声明友元函数,则MyFriend类里对应的函数报错:不能访问Me私有变量
     friend void     MyFriend::printMe(const Me &pt);
     friend double   MyFriend::calcDist(const Me &a, const Me &b);
     friend void     globalFunc(const Me &a);
 public:
     Me(double x, double y);
 private:
     double mX;
     double mY;
};
// ----------------------------------
//Me.cpp 定义原始类
#include "Me.h"
Me::Me(double x, double y)
{
    mX = x;
    mY = y;
}
// ----------------------------------
//MyFriend.cpp
#include "MyFriend.h"
#include <cmath>
#include <iostream>
using namespace std;
//在cpp中包含 原始类 头文件
#include "Me.h"

void MyFriend::printMe(const Me &pt)
{
     cout<< "(" << pt.mX << ", " << pt.mY << ")" << endl;
}

double MyFriend::calcDist(const Me &a, const Me &b)
{
     double deltX = a.mX - b.mX;
     double deltY = a.mY - b.mY;
     return sqrt( deltX * deltX - deltY * deltY );
}
// ----------------------------------
//main.cpp
#include <iostream>
#include "Me.h"
using namespace std;

void globalFunc(const Me &a)
{
    cout << "friend Global Func " << a.mX << " " << a.mY << endl;
}

int main(void)
{
    Me p1(1.0, 4.0);
    Me p2(6.0, 8.0);

    MyFriend pc;
    pc.printMe(p1);
    pc.printMe(p2);
    double dist = pc.calcDist(p2, p1);
    cout<<"Distance between p1 and p2 is "<<dist<<endl;
    globalFunc(p1);
    return 0;
}

一个函数可以是多个类的友元函数,但是每个类中都要声明这个函数。

2)友元类

当在某类中声明一个友元类,则该类中的所有成员均可以被友元类访问。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

class A {
public:
	friend class C;  // 这是友元类的声明
	private:
	int data;
};

class C {  // 友元类定义,为了访问类A中的成员
public:
	void set_show(int x, A &a) { 
		a.data = x; 
		cout << a.data << endl;
	}
};

int main() {
	class A a;
	class C c;
	c.set_show(1, a);
	return 0;
}

https://blog.csdn.net/softworlds/article/details/101053577

#C实现C++的三大特性

  • 封装:使用函数指针把属性与方法封装到结构体中
  • 继承:结构体嵌套
  • 多态:子类使用函数指针绑定父类同名函数

C语言实现C++的三大特性

#动态编译与静态编译

  1. 静态编译:所有代码和库函数的实现都被复制到最终的可执行文件中,使可执行文件在运行时不需要依赖于外部库和环境。优点是运行速度快,缺点是空间浪费大,更新代码不方便,每次需要重新编译。

  2. 动态编译:可执行文件在运行时,需要调用对应动态链接库的命令。优点是缩小了执行文件的体积,以及加快了编译速度,节省了系统资源。缺点是必须有对应动态库才能运行。

https://www.cnblogs.com/lisuyun/p/3953589.html


静态链接器以一组可重定位目标文件为输入,生成一个完全链接的可执行目标文件作为输出。链接器主要完成以下两个任务:

  • 符号解析:每个符号对应于一个函数、一个全局变量或一个静态变量,符号解析的目的是将每个符号引用与一个符号定义关联起来。
  • 重定位:链接器通过把每个符号定义与一个内存位置关联起来,然后修改所有对这些符号的引用,使得它们指向这个内存位置。

静态库有以下两个问题:

  • 当静态库更新时那么整个程序都要重新进行链接;
  • 对于 printf 这种标准函数库,如果每个程序都要有代码,这会极大浪费资源。

共享库是为了解决静态库的这两个问题而设计的,在 Linux 系统中通常用 .so 后缀来表示,Windows 系统上它们被称为 DLL。它具有以下特点:

  • 在给定的文件系统中一个库只有一个文件,所有引用该库的可执行目标文件都共享这个文件,它不会被复制到引用它的可执行文件中;
  • 在内存中,一个共享库的 .text 节(已编译程序的机器代码)的一个副本可以被不同的正在运行的进程共享。

#hello.c 程序的编译过程

这个过程大致如下:

  • 预编译阶段:对源代码文件中的头文件、预编译语句(宏定义)进行分析和替换,生成预编译.ii文件;
  • 编译阶段:把预编译生成的文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码.s文件;
  • 汇编阶段:将编译阶段生成的汇编文件转化成机器码,生成可重定位目标文件;
  • 链接阶段:将多个目标文件及所需要的库链接成最终的可执行目标文件(.out或.exe文件)。

源代码-->预处理-->编译-->优化-->汇编-->链接-->可执行文件

g++ 编译单个文件和多个文件

#介绍一下几种典型的锁

读写锁

  • 多个读者可以同时进行读
  • 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
  • 写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

互斥锁

一次只能一个线程拥有互斥锁,其他线程只能休眠等待

条件变量

条件变量用于阻塞线程,当条件满足就会唤醒一个被阻塞线程。(使用while判断条件,防止线程被虚假唤醒)

https://blog.csdn.net/yizhiniu_xuyw/article/details/109635912

自旋锁

如果线程无法取得锁,线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁那么自旋就是在浪费CPU做无用功。但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。

#delete和delete[]区别?

  • delete只会调用一次析构函数。

  • delete[]会调用数组中每个元素的析构函数。

#为什么不能把所有的函数写成内联函数?

内联函数以代码复杂为代价,省去函数调用的开销来提高执行效率。所以一方面如果内联函数体内代码执行时间相比函数调用开销较大,则没有太大的意义;另一方面每一处内联函数的调用都要复制代码,消耗更多的内存空间,因此以下情况不宜使用内联函数:

  • 函数体内的代码比较长,将导致内存消耗代价

  • 函数体内有循环,函数执行时间要比函数调用开销大

#为什么C++没有垃圾回收机制?这点跟Java不太一样。

  1. 性能:垃圾回收机制需要额外的内存和计算资源,这会引入一定的开销。C++的设计目标之一是提供高效的性能,垃圾回收机制可能会对程序的性能产生负面影响。因此,C++没有内置的垃圾回收机制,以便程序员自行管理内存等资源。
  2. 控制:C++强调程序的可预测性和确定性。垃圾回收机制会增加程序的复杂性和不确定性。
  3. 生命周期管理:C++注重对资源的显式管理,可以精确地控制资源的生命周期,避免资源的浪费。

https://www.cnblogs.com/codemagiciant/p/17601843.html