面经知识点

C++中结构体与类的区别

1、成员的访问权限不同

结构体在默认状态下是共有的(public)类在默认状态下是私有的(private)

结构体不用一定加public,结构体中成员可以在结构体外直接被访问

类一定要加public,否则默认访问权限是私有的,不能在类外进行访问

2、继承

C++中结构体默认继承为public,类的默认继承为private

3、类型

struct是值类型,class是引用类型

4、效率

由于堆栈(就是栈)的执行效率要比堆的执行效率高,但是堆栈的资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑

5、关系

struct不仅能继承也能被继承,而且可以实现接口,不过class可以完全扩展,内部结构有区别,struct只能添加带参的构造函数

叙述一下多态

多态就是多种形态,当类之间有层次结构并且有继承关系时会用到多态

多态可以分为静态多态和动态多态,静态多态像函数重载,运算符重载,模板,是在编译期间就能确定的多态

动态多态指的是父类的指针或引用指向子类对象,父类通过指针或引用调用子类重写父类的虚函数,在运行期间才能确定具体调用哪个函数

启动动态多态的条件:有继承关系,子类重写父类虚函数并且父类指针调用子类重写的虚函数

多态的实现

多态的实现主要决定于虚函数表,虚函数表在编译时创建虚函数的时候就被创建出来了,虚表是存虚函数指针的数组,对象内部包括了一个虚表指针(占四个字节),虚函数通过虚表指针指向虚函数表中函数的地址,通过地址指向函数

虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可,同一个类的所有对象都是用同一个虚表

动态绑定

动态绑定(动态联编)指的是编译的程序在编译阶段不能确切的知道要调用的函数,只有在函数执行的的时候才能确定要调用的参数,为此要确切的摘掉将要调用的函数,要求联编工作在程序运行时进行,这种在程序运行时进行的联编工作成为动态联编

动态联编是在虚函数的支持下实现的

动态联编必须包括以下方面 :

成员函数必须声明为virtual

如果基类中声明了为虚函数,则派生类中不必再声明

调用方式:

通过对象的指针或者引用调用成员函数,或通过成员函数调用,反之就无法实现动态联编

特点:灵活,问题的抽象性和易维护性

内存分配的三种方式:(百度一面)

1、全局存储分配:指的是代码在编译阶段就对对象分配内存,比如静态static变量,全局变量

2、栈区分配:程序在执行函数的时候会为其局部变量分配内存,在函数执行结束后内存会被回收

3、堆区分配:也成为动态内存分配,运行期间可以通过new和malloc分配堆区内存,由程序员决定什么时候delete或者free

虚函数和纯虚函数的区别(一面百度)

虚函数的定义形式:成员函数前添加virtual关键字,纯虚函数在虚函数后面添加“=0”

含有纯虚函数的类称为抽象类,只含有虚函数的类不能成为抽象类

虚函数既可以直接使用,也可以被子类重载实现后,以多态的形式调用;而纯虚函数必须被子类重载实现才能以多态的形式调用,因为纯虚函数在基类只有声明

无论是虚函数还是纯虚函数,定义中都不可能有static关键字,因为static关键字修饰的内容在编译前就要确定,而虚函数,纯虚函数是在运行时动态绑定的

模板类如何实现、用法、使用场景(一面百度)

————————————————补————充——————————————————

内存泄漏及其解决方案(一面百度)

内存泄漏是由于疏忽或者错误造成了程序未能释放掉不再使用的内存的情况,这种情况会失去对该段内存的控制,因此造成内存的浪费

解决内存泄漏的最好方法:智能指针(还没学)

new和malloc的区别(一面百度)

1 new / delete是运算符,malloc / free是函数

2 new会返回特定类型的指针自动计算大小,而malloc需要自己计算申请的空间大小

3 new可以对申请到的内存初始化,malloc不能对申请到的内存初始化

4 new会调用构造函数,malloc不会调用构造函数

5 delete会调用析构函数,free不会调用析构函数

6 new在申请空间发生错误时会抛出异常,malloc则会返回NULL

匿名管道和命名管道的区别(快手C++)

匿名通道通过打开的文件描述符来表示的(主要用于具有亲缘关系进程之间的通信

命名通道主要借助于文件系统实现,文件系统中的路径名是全局的,各个进程都可以访问,因此可以用文件系统中的路径名来表示一个IPC通道 (主要用于具有非亲缘关系进程之间的通信

Struct和Class中是否有成员函数(快手C++)

Struct在C语言中不包含成员函数,在C++中经过扩充可以包含成员函数

Class可以包含成员函数

重载和重写的区别(滴滴C++)

重载是在同一作用域内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据传入的参数决定调用哪个函数,重载不关心函数返回值类型

重写指的是在子类中存在重新定义父类具有相同函数名,参数列表,返回值类型的函数,所有的函数必须和基类中被重写的函数结构一致,只有函数体内不同,子类调用时会调用子类重写的函数,不会调用父类被重写的函数,重写的父类中被重写的函数必须有virtual修饰

vector 扩容用的是new还是malloc(快手C++二面)

malloc

孤儿、僵尸进程(快手一面)

孤儿进程是指一个父进程退出,而它的一个或者多个子进程还在运行,那么这些子进程就会成为孤儿进程,孤儿进程将被init进程(进程号为1)收养,由init进程对他们完成状态收集工作

僵尸进程是指一个进程使用fork()创建子进程,如果子进程退出但父进程并未调用wait'或者waitpid获取子进程状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称为僵尸进程

空对象指针为什么能调用函数(快手一面)

与成员变量不同,成员函数并不存储在对象内部,而是位于程序的二进制代码里,所以与对象的地址无关,所以即使在空指针上访问也不会出错

成员变量初始化顺序(快手一面)

成员变量在使用初始化参数列表初始化时,与构造函数中初始化成员的顺序无关,只与定义成员变量的顺序有关

如果不使用初始化参数列表初始化,在构造函数内初始化时,与成员变量在构造函数中的位置有关

类中的const成员常量必须在构造函数初始化列表中初始化

类中static成员变量,必须在类外初始化

如何判断MySQL中的索引有没有生效

1 使用explain语句查看执行计划,看看是否使用了索引,在执行语句后,可以查看到查询执行计划,其中会显示是否使用了索引

2 使用show index语句来查看表的索引信息例如show index from table_name 执行该语句后可以查看表的索引名称,索引类型,索引包含的列等

3 使用Mysql自带的性能分析工具如Mysqldumpslow,mysqlslap等

说一说软连接和硬链接的区别

软连接:也称为符号链接,新建的文件以路径的形式来表示另一个文件,和快捷方式相似,它可以是任意文件或目录,可以连接不同系统的文件,在对文件进行读写操作时,系统会自动把该操作转换为对源文件的操作,一般不占用内存

硬链接:相当于为已经存在的文件取了一个别名,当源文件删除时,新建的硬链接文件仍可正常使用,只有当删除最后一个硬链接时才会认为文件已被删除

说一声Linux中fork()函数的作用

fork函数事在已经存在的进程中创建了一个子进程,其中这个已经存在的这个进程被称为父进程

说一说STL中常见容器的实现原理

STL容器分为关联性容器和序列性容器

序列性容器分为vector list deque queue stack slist heap prior_queue

vector由数组实现,当现有内存不足时申请新的内存,每次新增一倍当前容量的内存

deque翻译为双端队列,但他有一个中央控制器map实现,deque的数据零零散散的储存在多个数组中,而map中保存着一个指针,指针分别指向这些数组

deque先从map的中间位置获取指针,然后移到具体的数组存放数据

stack queue都基于heap

heap 完全二叉树,使用大堆顶实现,然后进行排序,以vector形式存放

piior_queue 优先级队列,基于heap

list 双向环形链表

slist单链表

关联式容器:set map mutiset mutimap- 基于红黑树

mysql三个存储引擎是什么(迅雷服务端开发 一面)

1 InnoDB存储引擎

2 MySQl存储引擎

3 MEMORY存储引擎

进程间通信方式

1 管道 pipe

2 命名管道 FIFO

3 消息队列 MessageQueue

4 共享内存 SharedMemory

5 信号量 Semaphore

6 套接字 Socket

7 信号 Signal

访问空指针或野指针的后果

访问指针的时候虚拟地址就会向物理地址映射,此时页表会查看这块地址,而这块地址被存放在只读区,当页表发现地址是无效地址就会反映给操作系统,操作系统会发送11号信号终止此进程,所以进程异常终止程序崩溃

设计模式

工厂模式

单例模式

装饰模式

策略模式

代理模式

观察者模式

堆和栈的区别

1 管理方式不同 栈是由操作系统自动分配释放,而堆需要程序员手动申请释放,容易发生内存泄漏

2 空间大小不同 每个进程所拥有的栈大小要远远小于堆大小

3 生长方向不同 堆的生长方向向上,地址由低到高;栈的生长方向向下,地址由高到低

4 分配方式不同 堆都是动态分配,没有静态分配。栈既有静态分配又有动态分配

5 分配效率不同 栈由操作系统自动分配,会在硬件层级上对栈提供支持,分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,所以栈的效率比较高,堆是由C/C++提供的库函数或运算符来完成申请和管理,机制较为复杂,频繁申请内存易产生内存碎片,所以堆的效率要比栈低

6 存放内容不同

死锁产生条件

1 互斥条件

  这个资源是只能被一个进程独占,而进程相互互斥且排他的使用这些资源

2 占有和等待条件

  进程在请求资源等待时 不释放原来的资源

3 不剥夺条件

  不可抢占条件,已经获取到的资源只能由进程资源释放,不能被其他进程掠夺

4 循环等待条件

  环路条件,每个进程都在等待链中等待下一个进程所持有的资源造成这组进程处于永远等待状态

十大排序算法( 7 / 10)

稳定:冒泡排序、插入排序、归并排序、桶排序

不稳定:选择排序、快速排序、堆排序

排序方法      时间复杂度      空间复杂度

冒泡排序         O(n²)                O(1)

选择排序         O(n²)                O(1)

插入排序         O(n²)                O(1)

归并排序       O(nlogn)             O(n)

快速排序       O(nlogn)           O(logn)

  堆排序        O(nlogn)             O(1)

  桶排序         O(n+k)             O(n+k)

1 冒泡排序

//冒泡排序算法
//算法思路:依次比较,遇到前面大于后面就改变顺序
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void MaoPao_Sort(vector<int>& nums) {
	for (int i = 1; i < nums.size(); i++) {
		for (int j = 0; j < nums.size() - i; j++) {
			if (nums[j] > nums[j + 1]) swap(nums[j],nums[j + 1]);
		}
	}
}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	MaoPao_Sort(vec);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

2 选择排序

//选择排序算法
//算法思路:从最大向最小排序
//设置初始最大下标,作为最大数下标
//最大数下标从0下标开始向后遍历
//已找到比nums[MaxIndex]大的数就
//改变MaxIndex下标数值为j
//在一次大遍历后替换最大值和边界值
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void Select_Sort(vector<int>& nums) {
	cout << "nums的长度:" << nums.size() <<endl;
	for (int i = 0; i < nums.size(); i++) {
		int MaxIndex = 0;
		int j = 1;
		for (; j < nums.size() - i; j++) {
			if (nums[MaxIndex] < nums[j])
			{
				MaxIndex = j;
			}
			//算法运行到这里的时候j会+1
		}
		swap(nums[j - 1], nums[MaxIndex]);
	}
	
}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	cout << "vec的长度:" << vec.size() <<endl;
	Select_Sort(vec);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

3 插入排序

//插入排序算法
//算法思路:以下标为1的数为标记(temp)
//比较nums[j]和temp的值大小,如果大于则
//将j+1的数更改为j,如果发现小于(小于之前的数都已排序好)
//则将temp给nums[j+1]即可(插入过程)
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void Insert_Sort(vector<int>& nums) {
	for (int i = 1; i < nums.size(); i++) {
		int temp = nums[i];
		int j = i - 1;
		for (; j >= 0; j--) {
			if (nums[j] > temp)
			{
				nums[j + 1] = nums[j];
			}
			else break;
		}
		nums[j + 1] = temp;
	}

}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	cout << "vec的长度:" << vec.size() << endl;
	Insert_Sort(vec);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

4 归并排序

//归并排序算法
//算法思路:递归思想
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void Merg(vector<int>& nums, int L, int mid, int R) {
	vector<int> temp(R - L + 1);
	int i = L;
	int j = mid + 1;
	int index = 0;
	while (i <= mid && j <= R) {
		if (nums[i] <= nums[j]) {
			temp[index++] = nums[i++];
		}
		else {
			temp[index++] = nums[j++];
		}
	}
	while (j <= R) {
		temp[index++] = nums[j++];
	}
	while (i <= mid) {
		temp[index++] = nums[i++];
	}
	index = L;
	for (int i = 0; i < (R - L + 1); i++) {
		nums[index++] = temp[i];
	}
}
void Merg_Sort(vector<int>& nums,int L, int R) {
	if (L >= R) return;
	int mid = (R - L) / 2 + L;
	Merg_Sort(nums, L, mid);
	Merg_Sort(nums, mid + 1, R);
	Merg(nums, L, mid, R);

}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	cout << "vec的长度:" << vec.size() << endl;
	Merg_Sort(vec, 0, vec.size() - 1);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

5 快速排序


//快速排序算法
//算法思路:递归
#include <iostream>
#include <vector>
#include <string>
using namespace std;
pair<int, int> Quick(vector<int>& nums, int L, int R) {
	int i = L - 1;
	int j = R + 1;
	int temp = nums[L];
	int index = L;
	while (index < j) {
		if (nums[index] == temp) {
			index++;
		}
		else if (nums[index] > temp) {
			swap(nums[index], nums[--j]);
		}
		else {
			swap(nums[index++], nums[++i]);
		}
	}
	return make_pair(i, j);
	
}
void Quick_Sort(vector<int>& nums,int L, int R) {
	if (L >= R) return;
	pair<int, int> p = Quick(nums, L, R);
	Quick_Sort(nums, L, p.first);
	Quick_Sort(nums, p.second, R);
}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	cout << "vec的长度:" << vec.size() << endl;
	Quick_Sort(vec, 0, vec.size() - 1);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

6 堆排序

void Adjust(vector<int> &vec,int start,int end)
{
	int father = start;
	int child = father * 2 + 1;
	while (child + 1 <= end)
	{
		if(child <= end && vec[child] < vec[child + 1])
		{
			child++;
		}
		if (vec[father] < vec[child])
		{
			swap(vec[father], vec[child]);
			father = child;
			child = father * 2 + 1;
		}
		else
		{
			break;
		}
	}
}
void Tree::Heap_Sort(vector<int>& vec)
{
	int n = vec.size();
	for (int i = n / 2 + 1; i >= 0;i--)
	{
		Adjust(vec, i, n - 1);
	}
	for (int i = n - 1; i >= 0; i--)
	{
		swap(vec[0], vec[i]);
		Adjust(vec, 0, i - 1);
	}

}

7 桶排序

//桶排序算法
//算法思路:见代码
#include <iostream>
#include <vector>
#include <string>
using namespace std;
void Bucket_Sort(vector<int>& nums) {
	int max = nums[0];  //设置最大值为第一个元素
	int s = nums.size(); 
	for (int i = 1; i < s; i++) {  //寻找整个数组里最大数
		if (nums[i] > max)
		{
			max = nums[i];
		}
	}
	vector<int> res(max + 1);  //创建最大数值+1的数组
	for (int i = 0; i < nums.size(); i++) { //将nums[i]里的数字作为res数组下标存储
		res[nums[i]]++;
	}
	int index = 0; //遍历res,在前面哪个格子里有数字说明此下标为顺序每次遍历减去一个直到为0
	for (int i = 0; i < res.size(); i++) {
		while (res[i] > 0) {
			nums[index++] = i;
			res[i]--;
		}
	}

}
int main() {
	vector<int> vec = { 2,3,4,1,3,8,4 };
	cout << "vec的长度:" << vec.size() << endl;
	Bucket_Sort(vec);
	for (auto it : vec) {
		cout << it << endl;
	}
	return 0;
}

全局变量和局部变量的区别

全局变量:

在函数外部声明的变量,整个程序都可以访问

声明时会被默认初始化,可以在任何函数中使用

声明周期长,整个程序执行期间都存在

全局变量存储在全局数据区中

局部变量:

在函数内部或代码块内部声明的变量,只能在所属的函数或代码块中访问

声明时没有默认初始化,需要手动赋值才能使用

声明周期短,只在所属的函数或代码块的执行期间存在

局部变量存储在栈区

int main(int argc, char** argv)

argc是整型参数,表示命令行参数的个数

(由于程序自身的名称也算一个参数,所以至少为1)

argv是字符指针数组,用来存储命令行参数的字符串

argv[0]指向程序名称 argv[1]指向第一个参数..

野指针

指的是没有初始化过的指针

悬挂指针

指的是内存已经被释放的指针

NULL和nullptr的区别

类型不同:NULL是宏定义或者整数值0,而nullptr是C++11引入的关键字,表示空指针

安全性不同:NULL可能导致二义性的问题,nullptr更安全,不会被错误解释为整型

上下文匹配不同:NULL可以用于整型类型的上下文,nullptr只能用于指针类型的上下文

虚拟内存和物理内存的区别

物理内存是计算机中的实际硬件内存,由RAM芯片组成

虚拟内存是对物理内存的扩展,使用磁盘空间来模拟更大的内存容量

区别包括:

大小:物理内存的空间是固定的,而虚拟内存的大小可以超过物理内存的容量

访问速度:物理内存的访问速度较快,而虚拟内存的访问速度相对较慢。因为它需要与磁盘交互

地址空间:物理内存使用物理地址进行访问,而虚拟内存使用虚拟地址,通过内存管理单元MM u映射到物理内存

管理方式:物理内存的管理相对简单,而虚拟内存的管理设计页表和页面置换等技术

可用空间:物理内存的可用空间有限,而虚拟内存可以提供更大的可用空间,因为它可以使用磁盘作为扩展

重载、重写和隐藏的区别

重载:

重载是在同一个作用域内定义多个相同名称但参数列表不同的函数或方法

重载函数可以根据不同的参数数量或类型来执行不同的操作

重载通过函数名和参数列表来区分不同的函数

重写:

重写是指子类重新定义父类继承的虚函数,使其具有不同的实现

重写的函数(名,参数列表和返回类型)必须与被重写函数相同

在运行时,根据具体的对象类型,调用的是子类重写的版本

隐藏:

隐藏是指在派生类中定义与父类具有相同名称的成员函数,使其隐藏父类的同名函数

隐藏函数与父类的函数没有多态性,只有通过对象的实际调用时才会调用相应的函数

面向对象的三大特性

封装

封装是将数据和操作封装在一个单元(类)中的机制

通过封装,实现类的成员变量和成员函数作为一个整体进行管理和操作

封装隐藏了数据的具体实现细节,只暴露出必要的接口,提供了更好的安全性和可维护性

通过访问修饰符(public private protect)控制对类的成员的访问权限

继承

继承是通过创建派生类来扩展和重用已有类的机制

基类是已经定义的类,派生类继承了基类的属性和方法

子类可以自定义新的属性和方法,也可以覆盖或拓展继承父类的属性和方法

继承实现了类和类之间的关系,实现了代码的重用和扩展

多态

多态是指同一个接口可以由不同的对象以不同的方式进行实现和响应的能力

多态允许使用基类的指针或引用来引用派生类的对象,实现了多种形态的使用

编译时多态使用函数重载和运算符重载;运行时多态通过虚函数实现(动态绑定)

多态提高了代码的灵活性和可扩展性,使得程序更具有可读性和可维护性

编译期:静态多态:函数重载(运算符重载) 模板

动态多态:父类指针或引用指向子类对象,通过指针或引用调用子类重写的虚函数,在运行期间才能确定具体调用哪个函数

启动动态多态的条件:有继承关系 子类重写父类虚函数并且父类指针调用子类重写的虚函数

动态链接与静态链接?二者有什么区别?

动态链接:

在程序运行时进行链接,加载共享库文件

节省空间,可以多个程序共享库文件

灵活性高,可以动态加载不同版本的库文件

维护方便,只需更新库文件本身

静态链接:

在编译时进行链接,将库函数赋值到可执行文件中

独立的可执行文件,不依赖外部库文件

可执行文件较大,可能有冗余代码

维护复杂,更新库函数需要重新编译和分发可执行文件

使用场景和优点:

动态链接适合节省资源、可升级和灵活性要求高的场景,但在运行时有一定开销

静态链接适合独立部署和简化依赖关系的场景,但可执行文件较大且维护复杂

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值