C++常见面试题/笔试收录(一)

发布于:2025-07-25 ⋅ 阅读:(18) ⋅ 点赞:(0)

==========================================

真实面试环境中,被问到的相关问题,感兴趣的可以看下

==========================================

1. c和c++的联系和区别?

联系:

  • C++ 是基于 C 语言发展的,基本兼容 C 语言代码;

  • 两者都支持过程式编程都能操作底层硬件资源

  • 语法上很多关键字和结构相似

区别 

  •  C语言是面向过程的,而C++是面向对象的
  • C语言的标准库有限,但C++的标准库丰富
  • C语言不支持函数重载,而C++支持函数重载
  • C语言需要手动管理内存资源,而C++可以通过RALL管理资源,还支持对象的构造析构
  • C语言不支持异常处理,而C++中有异常捕获处理机制

2. 说一下c中malloc和c++ new的区别?  

  • 相同点:都是从堆上申请空间,并且需要用户手动释放
  • 不同点:
    malloc和free是函数,new和delete是操作符
    malloc申请的空间不会初始化,new申请的空间会初始化,即调用构造函数
    malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可
    malloc的返回值是void*,在使用时必须强转,new不需要,因为new后跟的是空间的类型malloc申请失败时,返回的是NULL,因此使用时必须判空,而new不需要,但是new需要捕获异常

3. 说一下c中的static和c++ static的联系和区别? 

  • C 中主要用于限制作用域延长生命周期
  • C++ 中除了这些,还用于定义类的静态成员变量和函数

 4. C++ 面向对象的三大特征是?

封装: 把数据和操作数据的函数绑定在一起,隐藏内部实现,只暴露必要接口,提升安全性和可维护性

继承:子类可以复用父类的成员,实现代码复用和结构扩展

多态:通过基类指针或引用调用派生类的函数,实现“同一接口多种实现”。

5. 什么是重载、重写(覆盖)、重定义(隐藏)? 

  • 重载 : a. 两个函数在同一作用域,b.函数名相同,参数不同(类型,顺序,个数)
  • 重写(覆盖):
    两个函数分别在基类和派生类的作用域
    函数名/参数/返回值都必须相同(协变除外) -> 简称三同
    特殊点1: 两个函数都必须是虚函数(其实子类可以不用加virtual)
    特殊点2: 如果返回值不同,则必须是父类的指针或引用
  •  重定义(隐藏)
    两个函数分别在基类和派生类的作用域
    函数名相同

    两个基类和派生类的同名函数不构成重写,那么就是重定义(隐藏)

6. 虚函数和纯虚函数的联系和区别?

联系: 

  • 都定义在基类中,允许派生类重写(override)。

  • 都通过基类指针或引用调用派生类的函数,实现运行时多态

  • 都必须使用关键字 virtual 声明。

 虚函数是可选重写,能实例化,纯虚函数是必须重写,不能实例化;

包含纯虚函数的类是抽象类,不能直接创建对象。

7. STL/用过哪些c++库哪些?

我用过 STLBoostQtOpenCV,还有 SQLite 等库,主要用于界面开发、数据处理和通信相关功能

8. -1的源码 反码 补码

  • 源码:1000 0000 0000 0000 0000 0000 0000 0001
  • 反码:1111 1111 1111 1111 1111 1111  1111 1110 
  • 补码:1111 1111 1111 1111 1111 1111  1111 1111

9. static关键字,在函数中声明静态变量和在类中声明静态变量的区别

  • 在函数中时,改变了变量的生命周期和作用域,使其只能在当前文件中访问
  • 在类中时只能在类外进行定义和初始化,且不需要使用private、public、protected 访问规则
    被类的所有对象所共享

3. 声明和定义的区别 

  • 声明是告诉编译器名字的存在

  • 而定义是为名字分配内存并实现其功能

  • 在使用变量或函数之前,必须先进行声明后定义

4. 数组和链表的区别 

  • 数组支持随机访问,链表不支持
  • 但是链表的插入删除操作效率高,数组的插入删除效率低

5. 最小堆的插入删除查找的时间复杂度 

  • 插入O(logN),删除O(logN),查找O(n)

6. 请实现归并排序

void my_sort(vector<int>& nums,int l,int r){
        if(l==r){
            return;
        }
        int mid = (l+r)>>1;
        my_sort(nums,l,mid);
        my_sort(nums,mid+1,r);
        int begin1 = l,end1 = mid;
        int begin2 = mid+1,end2 = r;
        vector<int> tmp;
        while(begin1 <= end1 && begin2 <= end2){
            if(nums[begin1] < nums[begin2]){
                tmp.push_back(nums[begin1++]);
            }
            else{
                tmp.push_back(nums[begin2++]);
            }
        }
        while(begin1 <= end1){
            tmp.push_back(nums[begin1++]);
        }
        while(begin2 <= end2){
            tmp.push_back(nums[begin2++]);
        }
        // 拷贝回去
        for(int i = 0;i < tmp.size();i++){
            nums[l+i] = tmp[i];
        }
    }

 7. 说一下const关键字

  • const 修饰成员变量,定义成 const 常量,相较于宏常量,可进行类型检查,节省内存空间,提高了效率。
  • const 修饰函数参数,使得传递过来的函数参数的值不能改变
  • const 修饰成员函数,使得成员函数不能修改任何类型的成员变量(mutable 修饰的变量除外),也不能调用非 const 成员函数,因为非 const 成员函数可能会修改成员变量。

8. 关于malloc的几个常见没有释放内存的错误

  1.  对空指针进行解引用操作

  2. 对开辟出来的空间的越界访问

  3. 内存使用完,没有进行释放(内存泄漏)

  4. 释放内存时,指针没有指向原来空间的首地址(释放开辟的空间的部分内存)

  5. 非动态开辟的内存进行释放

  6. 对同一块内存的多次释放

9. 说一下extern 关键字

  •  extern外部变量: 叫做外部声明,被extern修饰的变量,会告诉编译器这个变量的定义需要再其他文件中查找 

 10. 实现strcpy

char* my_strcpy(char* des, const char* sou)
{
	assert(des && sou);
	char* ret = des;
	while (*des++ = *sou++)
	{
		;
	}
	return ret;
}

11. 找出不大于n的最大质数

sqrt优化被除数

bool isPrimeNum(int i)
{
    // 使用sqrt缩短范围
    int j = 0;
    for (j = 2; j < sqrt(i) + 1; j++) {
        if (i % j == 0) {
            return false;
        }
    }
    return true;
}

 12. 1000个数范围是[0,999],有2个相同的数,请设计算法找出来

关键点:使用容器bitset,使用test函数,set函数 

#include <bitset>
int function(vector<int>&v)
{
    bitset<1024*8>bt;
    for (auto e : v) {
        if (bt.test(e)) {
            return e;
        }
        bt.set(e);
    }
    return 0;
}

13. 如何解决hash冲突

链地址法: 对于相同的哈希值,使用链表进行挂起
再哈希法:提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值
建立公共溢出区: 将哈希表分为基本表和溢出表两部分,凡是和基本表发生冲突的元素,一律填入溢出表
开放定址法:将冲突的hash值不停的映射成新的哈希值,直到不冲突为止

  • 线性探测再散列(向后面不断遍历)
  • 平方探测再散列(平方跳动)
  • 伪随机探测再散列(随机跳动)

15. map底层实现 

  • map是关联式容器,底层使用红黑树作为底层数据结构实现,而红黑树是一种自平衡的二叉搜索树,可以保持元素的有序,并且插入,删除,查找等操作的时间复杂度都是O(logN)

16. 哈希底层是什么 

  • 哈希表使用哈希函数将元素映射到一个桶中,每个桶中存储一个链表或者红黑树,哈希表的插入,删除,查找等操作的平均时间复杂度是O(1),但是最坏情况下可能达到O(N);

17. 各大排序时间复杂度 

  • 插入希尔都是O(N^2)
  • 堆是O(N*logN),选择O(N^2)
  • 冒泡是O(N^2),快速排序O(N*logN)
  • 归并是O(N*logN);

18. 各大排序应用场景 

  • 插入排序:小规模,部分有序的数据
  • 希尔排序: 中小规模数据
  • 堆排序:任意规模的数据
  • 选择排序: 小规模数据,实际效率较低
  • 冒泡排序:小规模数据,实际效率较低
  • 快速排序:任意规模的数据

19. 各个容器应用场景 

  •  vector常用于需要频繁添加或删除元素尾部的情况
  • list 适用于频繁插入或删除元素
  • map 和 unordered_map常用于字典或配置项存储
  • set 和 unordered_set适用于去重或集合运算 
  • stack和queue: 只允许在一端进行插入和删除

20.  什么是野指针,以及如何避免

  • 野指针: 不确定指向地址空间的指针,和未进行初始化的指针
  • 指针定义时进行初始化,动态分配内存后,必须及时手动释放内存

21. 说一下结构体对齐 

编译器将程序中的每个“数据单元”安排在字的整数倍的地址指向的内存之中
内存对齐的原则:

  • 结构体变量的首地址 = min(最宽基本类型大小,对齐基数)的整除
  • 其他成员的地址偏移量 = min(成员大小,对齐基数)的整数倍 + 填充字节
  • 总大小 = min(最宽基本类型大小,对齐基数) + 填充字节

 22. 代码编译过程

  • 预处理:头文件的展开,宏替换,去注释
  • 编译:把C/C++语言变成汇编代码;
  • 汇编:通过汇编变成以.o结尾的目标二进制文件(不可执行)
  • 链接:多个目标文件连接库进行链接的,从而生成可执行的程序 .exe 文件。

 23. 代码如何判断大小端  

​
int main() {
	int a = 1;
	char* p = (char*) & a;//char* -- 访问一个字节(第一个地址)
	if (*p == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}
  • 小端地址在地址
  • 大端:低地址在高地址

24. 用2个栈实现队列

class Solution
{
public:
	void push(int value) {
		in_st.push(value);
	}
	int pop() {
		int tmp = 0;
		if (!out_st.empty()) {
			tmp = out_st.top();
			out_st.pop();
		}
		else {
			// 转移数据
			while (!in_st.empty()) {
				out_st.push(in_st.top());
				in_st.pop();
			}
			tmp = out_st.top();
			out_st.pop();
		}
		return tmp;
	}
private:
	stack<int>in_st;
	stack<int>out_st;
};

设计思路: 

  • 一个in_stack,一个out_statck
  • push数据,直接push进in_stack;
  • pop数据,如果out_stack有数据之间pop,没有就数据转移将in_stack中的数据转换到out_stack中,再pop 

 25. 用2个队列实现栈

class Solution
{
public:
	void push(int value) {
		if (q1.empty()) {
			q1.push(value);
		}
		else {
			q2.push(value);
		}
	}
	void pop() {
		if (q1.empty() && q2.empty()) {
			cout << "没有数据" << endl;
		}
		else if (q1.empty()) {
			while (q2.size() != 1) {
				q1.push(q2.front());
				q2.pop();
			}
			cout << q2.front() << endl;
		}
		else {
			while (q1.size() != 1) {
				q2.push(q1.front());
				q1.pop();
			}
			cout << q1.front() << endl;
		}
	}
private:
	queue<int>q1;
	queue<int>q2;
};

设计思路:

  •  一个q1,一个q2
  • push数据,直接找一个空队列,直接push
  • pop数据,找一个非空队列,只留一个数据非空队列中,其他数据转移到另一个空队列
    最后再将这个数据pop出来

26. 说一下C++读文件和写文件

28. 什么是多态 

就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会,产生出不同的状态

  • 派生类对象的地址可以赋值给基类指针。对于通过基类指针调用基类和派生类中都有的同名、同参数表的虚函数的语句,编译时并不确定要执行的是基类还是派生类的虚函数;
  • 当程序运行到该语句时,如果基类指针指向的是一个基类对象,则基类的虚函数被调用,
  • 如果基类指针指向的是一个派生类对象,则派生类的虚函数被调用。这种机制就叫作“多态(polymorphism)

静态多态(编译阶段确定) 

  • 函数重载函数模板 的使用

 动态多态(运行阶段确定) 

  • 派生类 虚函数 的使用

 29. 继承和多态区别与联系?

  • 不同点: 继承是子类使用父类的方法,而多态则是父类使用子类的方法。 
  • 继承是为了重用代码,有效实现代码重用,减少代码冗余
  • 多态是一种接口继承,增强接口的扩展性

    31. 多态的实现原理?  

    • 每一个类中都有一个vfptr,它是一张虚函数的表,本质就是一个函数指针数组
    • 多态调用:运行时去指向对象的虚表中找到函数地址,并进行调用(在符合多态的两个条件时,)

    32. inline函数可以是虚函数吗? 

    • 不可以,但是inline只是一个建议,当一个函数是虚函数以后,多态调用中,inline会直接失效

    33. 静态成员可以是虚函数吗?  

    • 不可以 ,因为 静态成员函数没有this指针 ,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表

    34. 构造函数可以是虚函数吗

    • 不可以,virtual函数为了实现多态,运行时去虚表中找对应虚函数进行调用
    • 而对象中虚表指针都是构造函数初始化列表阶段才初始化的
    • 所以构造函数的虚函数是没有意义的

    35.  析构函数可以是虚函数吗 

    • 可以,建议基类的析构函数定义成虚函数
    • 析构函数名都会被处理成destructor,所以这里析构函数完成了虚函数重写
    • 当基类的指针指向派生类对象的时候,发生多态,然后基类和派生类就会统一析构
      如果不将基类的析构函数定义为虚函数的话,那么派生类的析构函数就无法执行

    36.  拷贝构造 和 operator=可以是虚函数? 

    •  拷贝构造不可以,拷贝构造也是构造函数,同上
    • operator=可以但是没有什么实际价值

    37. 对象访问普通函数快还是虚函数更快? 

    • 不构成多态, 是一样快的。
    • 构成多态,则调用的普通函数快
    • 因为构成多态,运行时调用虚函数需要到虚函数表中去查找

    38. 虚函数是在什么阶段生成的,存在哪的?

    • 编译阶段就生成好的,存在代码段(常量区)

    39. 什么是抽象类?抽象类的作用?

    • 在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类
    • 抽象类强制重写了虚函数,另外抽象类体现出了 接口继承关系 。
    • 统一接口、实现多态

    40. 什么是菱形继承?菱形继承的问题是什么?

    • 菱形继承是多继承的一种特殊情况,两个子类继承同一个父类,而又有子类同时继承这两个子类,我们称这种继承关系为菱形继承 
    • 菱形继承因为子类对象当中会有两份父类的成员,因此会导致数据冗余二义性的问题

     41. 进程的虚拟内存分配模型

    • 栈区:由编译器自动分配和释放,存放为运行函数分配的局部变量,函数参数,返回数据,返回地址等,其操作类似于数据结构中的栈。
    • 堆区:一般由程序员自动分配,如果程序员没有释放,程序结束时可能有OS回收。其分配类似于链表。
    • 全局区(静态区static):存放全局变量,静态变量,常量。结束后由系统释放
    • 常量区(字符串常量区):存放常量字符串,程序结束后由系统释放。
    • 代码区:存放函数体(类成员函数和全局区)的二进制代码

     42. 堆和栈的区别 

    • 存储位置:栈存储局部变量和函数调用,位于内存的栈区;堆用于动态分配内存,位于堆区
    • 分配方式:栈由系统自动管理,分配和释放速度快;堆需要程序员手动分配和释放,速度较慢
    • 内存管理:栈内存自动回收;堆内存需要显式释放,否则可能发生内存泄漏
    • 大小限制:栈的内存较小,容易溢出;堆的内存较大,但可能导致碎片化。 

     43. final(使该类不可被子类继承) -> 加在父类上  

    说明一下:

    • 在一个父类的成员函数的后面+final使其变成不可被子类继承 

    44. override(判读子类是否发生了虚函数的重写) ->加在子类上 

    #include <iostream>
    using namespace std;
     
    class Car{
    public:
    	virtual void Drive1() {}
    	void Drive2(){}
    };
     
    class Benz :public Car {
    public:
    	// 检查子类虚函数是否完成重写
    	virtual void Drive1() override { cout << "Benz-舒适" << endl; }
    	virtual void Drive2() override { cout << "Benz-舒适" << endl; }
    };
     
    int main()
    {
    	Benz A;
    	return 0;
    }

    • 在子类后面+override判断这个子类的虚函数是否发生了虚函数的重写 

    45. 什么是内存泄漏 

    • 由于疏忽或错误导致的程序未能释放已经不再使用的内存

    46. 智能指针有哪几种?智能指针的实现原理? 

    •  unique_ptr: 禁掉了拷贝构造和赋值重载

    • shared_ptr: 共同管理一段空间,引用计数 记录管理者 

    • weak_ptr: 为了解决shared_ptr循环引用的问题,使它的nextprev不增加计数 

    47. C++11 引入了什么新增加的内容 

     智能指针, auto类型推导, lambda表达式,右值引用 

    48. sizeof 和 strlen 的区别 

    1. strlen 是头文件 中的函数sizeof 是 C++ 中的运算符
    2. strlen 测量的是字符串的实际长度(其源代码如下),以 \0 结束
      而 sizeof 测量的是字符数组的分配大小
    3. sizeof会计算\0

    49. explicit 的作用

    #include <iostream>
    #include <cstring>
    using namespace std;
     
    class A
    {
    public:
        int var;
        explicit A(int tmp)
        {
            var = tmp;
            cout << var << endl;
        }
    };
    int main()
    {
        A ex(100);
        A ex1 = 10; // error: conversion from 'int' to non-scalar type 'A' requested
        return 0;
    }
    • 禁掉隐式类型转换

    50. static修饰静态成员变量 

    • 静态成员变量是在类内进行声明,在类外进行定义和初始化,在类外进行定义和初始化的时候不要出现 static 关键字和private、public、protected 访问规则。

    • 静态成员变量相当于类域中的全局变量,被类的所有对象所共享,包括派生类的对象。

    51. static修饰静态成员函数 

    • 静态成员函数不能调用非静态成员变量或者非静态成员函数,因为静态成员函数没有 this 指针。
    • 静态成员函数能做为类作用域的全局函数
    • 静态成员函数不能声明成虚函数(virtual)、const 函数和 volatile 函数。 

    52. define 和 typedef 的区别

    • define不进行类型安全的检查,而typedef会进行类型安全的检查
    • define没有作用域的限制,而typedef有作用域的限制

      54. malloc 的原理?malloc 的底层实现? 

      • 当开辟的空间小于 128K 时,调用 brk() 函数,通过移动 _enddata 来实现;
      • 当开辟空间大于 128K 时,调用 mmap() 函数,通过在虚拟地址空间中开辟一块内存空间来实现

      55. C 和 C++ struct 的区别?

      • 一个是自定义数据类型,而另一个是抽象数据类型
      • C语言中的struct不能包含成员函数,C++中的struct可以包含成员函数,区别在于访问控制 

      55. volatile 的作用?是否具有原子性,对编译器有什么影响?

      • volatile 的作用:
        当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 violatile,
        告知编译器不应对这样的对象进行优化
        volatile不具有原子性
      • volatile 对编译器的影响:
        使用该关键字后,编译器不会对相应的对象进行优化,应该在真实的物理地址空间中拿数据
        不会将变量从内存缓存到寄存器中
      • 防止多个线程有可能使用内存中的变量,也有可能使用寄存器中的变量,从而导致程序错误。

      56.  extern C 的作用?

      当 C++ 程序 需要调用 C 语言编写的函数

      57.  为什么用成员初始化列表会快一些? 

      • C++ 规定,对象的成员变量的初始化动作发生在进入构造函数本体之前
      • 所以初始化列表在没有进入函数体之前,就将成员变量设为初始值了
      • 总之就是一个进入了函数体,另一个没有进入函数体

      58.  友元函数的作用 

      • 作用: 让普通的成员函数可以访问类中的私有成员和保护成员

      60. 左值和右值的区别?两者之前如何转换  

      #include <iostream>
      #include <utility>
      using namespace std;
       
      int main()
      {
      	// 左值: 能取地址
      	int* p = new int(0);
      	int b = 1;
      	const int c = 2;
       
      	// 对左值的左值引用
      	int*& rp = p;
      	int& rb = b;
      	const int& rc = c;
      	int& pvalue = *p;
       
      	// 右值:不能取地址
      	10;
      	1.1 + 2.2;
       
      	// 对右值的右值引用
      	int&& rr1 = 10;
      	double&& rr2 = 1.1 + 2.2;
       
      	// 对右值的左值引用
      	// double& r1 = 1.1 + 2.2;// error
      	const double& r1 = 1.1 + 2.2;
       
      	// 对左值的右值引用
      	//int&& rr5 = b;// error
      	int&& rr5 = move(b);
       
      	return 0;
      }
      • 能取地址的叫左值,不能取地址的叫右值
      • 如果对右值使用左值引用,需要加上const
      • 如果对左值使用右值引用,需要是使用move函数

      61. 什么是野指针和悬空指针? 

      • 野指针: 不确定指向地址空间的指针,和未进行初始化的指针
      • 悬空指针: 这个指针指向的地址空间已经被释放了,但这个指针还是指向原来的那段地址空间

      62. 指针和引用的区别 

      从使用场景来说

      • 引用和指针使用场景基本一样,但是链表的链式结构是引用无法代替的,只能使用指针

      从语法特性来说 

      • 引用在定义时必须初始化,指针没有要求
      • 引用在初始化时引用一个实体后,就不能再引用其他实体,
        而指针可以在任何时候指向任何一个同类型实体。
      • 没有NULL引用但有NULL指针
      • 在sizeof中的含义不同:引用的结果为引用类型的大小
        但指针始终是4或者8字节 
      • 引用进行自增操作就相当于实体增加1
        而指针进行自增操作是指针向后偏移一个类型的大小
      • 有多级指针,但是没有多级引用。
      • 访问实体的方式不同,引用是编译器自己处理,而指针需要显示解引用
      • 引用比指针使用起来相对更安全

      63. 说一说c++的强制类型转换

      • static_cast关键字 -> 隐式类型转换 

      • reinterpret_cast关键字 -> 强制类型转换

      • const_cast关键字->取消变量的const属性

      • dynamic_cast关键字->父类指针 转换 子类指针(保证安全性)

      64.  写一下单例模式

      class Singleton
      {
      public:
      	static Singleton* GetInstance()
      	{
      		return &m_instance;
      	}
      private:
      	// 构造函数私有
      	Singleton() {};
       
      	// C++11 : 防拷贝
      	Singleton(Singleton const&) = delete;
      	Singleton& operator=(Singleton const&) = delete;
       
      	static Singleton m_instance;// 声明
      };
       
      Singleton Singleton::m_instance;// 定义

      多线程下

      #include<iostream>
      #include<thread>
      #include<mutex>
      using namespace std;
      class Singleton
      {
      public:
      	static Singleton* GetInstance()
      	{
      		// 保护第一次,后续不需要加锁
      		// 双检查加锁
      		if (_pInstance == nullptr)
      		{
      			unique_lock<mutex> lock(_mtx);
      			if (_pInstance == nullptr)
      			{
      				_pInstance = new Singleton;
      			}
      		}
       
      		return _pInstance;
      	}
       
      private:
      	// 构造函数私有
      	Singleton(){};
       
      	// C++11
      	Singleton(Singleton const&) = delete;
      	Singleton& operator=(Singleton const&) = delete;
       
      	static Singleton* _pInstance;
      	static mutex _mtx;
      };
       
      Singleton* Singleton::_pInstance = nullptr;
      mutex Singleton::_mtx; 
       
      int main()
      {
      	Singleton::GetInstance();
      	Singleton::GetInstance();
       
      	return 0;
      }

      65. 用过哪些c++的库

      标准库 STLBoost Qt OpenCV MySQL C++ API

      67.  c++什么时候用智能指针 

      当需要自动管理动态内存,避免手动 delete 时,用智能指针。

      68. 简述指针常量与常量指针区别? 

      指针常量不可以改变指针指向,解引用

      常量指针可以改变指针指向,但不能解引用

      69. 说一下c++面向对象的几个特征 

      • 封装保护数据,继承复用代码,多态提高扩展性。

      70.  如何让一个类不能实例化?

      • 删除构造函数
      • 抽象基类中的纯虚函数
      • 私有构造 + 无友元 

       71. C++的空类有哪些成员函数

      • 构造函数 析构函数 拷贝构造 赋值重载

      • 移动构造函数(C++11 起)移动赋值运算符(C++11 起)

       72. 动态绑定是如何实现的? 

       动态绑定主要通过虚函数表(vtable)和虚指针(vptr)实现。

      • 每一个类中都有一个vfptr,它是一张虚函数的表,本质就是一个函数指针数组
      • 多态调用:运行时去指向对象的虚表中找到函数地址,并进行调用(在符合多态的两个条件时,)

       73. 如果析构函数里面抛出异常会怎么样? 

      • 调用 std::terminate()程序直接崩溃,异常终止

       74. new 失败会怎样,不想抛出异常该怎么办 ?

      • 默认情况下,new 申请内存失败会抛出 std::bad_alloc 异常

      • 如果不想抛异常,可以用nothrow 版本的 new

       75.  c++中是如何管理内存空间的

       C++ 通过自动管理局部变量,由程序员手动申请释放,静态区放全局和静态变量,代码区存放程序指令 

       76.  宏函数和内联函数的区别?

      • 宏函数是简单替换,不安全;内联函数由编译器优化,更安全、更推荐

       77.  栈的原理你有了解过吗?

      • 栈通过先进后出方式管理局部变量和函数调用,自动分配释放底层靠栈帧和栈指针实现,效率高但空间有限 

       78.  用过哪些算法?

      • 快速、归并、堆排、二分、哈希表等等

       79. 迭代器是怎么用的?

      • 迭代器像“智能指针”begin() 取起点,end() 取终点,++ 走、* 解引用,
      • 更多的使用场景是:用范围 for 高效、安全地遍历和操作容器。 

       82. 实现快速排序

      void QuickSort(int* a, int left, int right)
      {
      	if (left >= right)
      		return;
       
      	// ... 单趟排序
      	int keyi = left;
      	int prev = left, cur = left + 1;
      	while (cur <= right)
      	{
      		if (a[cur] < a[keyi]
      			&& ++prev != cur)
      		{
      			Swap(&a[prev], &a[cur]);
      		}
       
      		++cur;
      	}
       
      	Swap(&a[keyi], &a[prev]);
      	keyi = prev;
       
      	// [left, right]
      	// [left, pivot-1] pivot [pivot+1, right]
      	// 左子区间和右子区间有序,我们就有序了,如果让他们有序呢? 分治递归
       
      	QuickSort(a, left, keyi - 1);
      	QuickSort(a, keyi + 1, right);
      }

      83. 直接插入排序

      void InsertSort(int* a, int n)
      {
      	for (int i = 0; i < n - 1; ++i)
      	{
      		//一个数的插入
      		int end = i;
      		int tmp = a[end + 1];//记录需要插入的值
      		while (end >= 0)
      		{
      			if (a[end] > tmp)
      			{
      				//向后移动
      				a[end + 1] = a[end];
      				--end;
      			}
      			else
      			{
      				break;
      			}
      		}
      		//end有可能会减到-1
      		a[end + 1] = tmp;
      		// [0, end]有序  end+1位置的值插入[0, end],让[0, end+1]有序
      	}
      }

      84. 红黑树五大性质

      1. 每个结点不是红色就是黑色
      2. 根结点是黑色的
      3. 如果一个结点是红色的,则它的两个孩子结点是黑色的(红色的俩俩不能相见)
      4. 对于每个结点,从该结点到其所有后代叶子结点的简单路径上,均包含相同数目的黑色结点 
      5.  每个叶子结点都是黑色的(哨兵节点为黑色)

      85. 请简要描述一下什么是回调函数,有什么用途 

      函数作为参数传入另一函数中的函数,在特定事件发生时被调用。

      可以用在事件响应比如如按钮点击回调,异步处理比如网络请求返回时触发回调

      再比如:排序算法时传入比较函数 

      86. 请描述C语言的头文件有那些作用和用途 

      • 函数声明:告诉编译器某个函数的存在(如 printf()

      • 宏定义:提供常量、简化代码(如 #define PI 3.14

      • 数据类型定义:自定义结构体、枚举等

      • 外部变量声明代码模块化

      87. 模拟实现memmove

      void* my_memmove(void* des,const void* cou, size_t num)
      {
      	assert(des && cou);//断言
      	void* ret = des;//记录地址
      	//后面的向开始
      	if (des >= cou)
      	{
      		while (num--)
      		{
      			*((char*)des + num) = *((char*)cou + num);//从后开始访问
      		}
      	}
      	//前面的先开始
      	else
      	{
      		while (num--)
      		{
      			*(char*)des = *(char*)cou;
      			des = (char*)des + 1;
      			cou = (char*)cou + 1;
      		}
      	}
      	return ret;
      }

       88. 写String类的构造,析构,拷贝,赋值函数

      namespace cl
      {
          // 模拟实现string类
          class string
          {
          public:
              typedef char *iterator;
              typedef const char *const_iterator;
      
              // 默认成员函数
              // 构造函数
              string(const char *str = "")
              {
                  _size = strlen(str);            // 初始时,字符串大小设置为字符串长度
                  _capacity = _size;              // 初始时,字符串容量设置为字符串长度
                  _str = new char[_capacity + 1]; // 为存储字符串开辟空间(多开一个用于存放'\0')
                  strcpy(_str, str);              // 将C字符串拷贝到已开好的空间
              }
      
              // 拷贝构造
              string(const string &s)
                  : _str(nullptr), _size(0), _capacity(0)
              {
                  string tmp(s._str); // 调用构造函数,构造出一个C字符串为s._str的对象
                  swap(tmp);     // 交换这两个对象
              }
      
              // 赋值重载
              string &operator=(string s) // 编译器接收右值的时候自动调用拷贝构造函数
              {
                  swap(s);      // 交换这两个对象
                  return *this; // 返回左值(支持连续赋值)
              }
      
              // 析构函数
              ~string()
              {
                  delete[] _str;  // 释放_str指向的空间
                  _str = nullptr; // 及时置空,防止非法访问
                  _size = 0;      // 大小置0
                  _capacity = 0;  // 容量置0
              }
      
          private:
              char *_str;       // 存储字符串
              size_t _size;     // 记录字符串当前的有效长度
              size_t _capacity; // 记录字符串当前的容量
          };
      }

        89. 给一个a.txt的文本,从文本中获取数据,计算其中’C++''出现的次数

      #include <fstream>
      #include <iostream>
      #include <string>
      
      std::size_t count_substr(const std::string& s, const std::string& sub)
      {
          if (sub.empty()) return 0;
          std::size_t cnt = 0, pos = 0;
          while ((pos = s.find(sub, pos)) != std::string::npos) {
              ++cnt;
              ++pos;                       // 允许重叠
          }
          return cnt;
      }
      
      int main()
      {
          std::ifstream fin("a.txt", std::ios::in );
          if (!fin) {
              std::cerr << "无法打开 a.txt\n";
              return 1;
          }
      
          std::size_t total = 0;
          std::string word;
          while (fin >> word) {            // 每次读一个“单词”
              total += count_substr(word, "C++");
          }
          std::cout << "\"C++\" 出现次数: " << total << '\n';
      }
      

        90. char(*a[10])(char)代表什么

      • 一个函数指针数组,数组有 10 个元素,每个元素是一个指向参数为 char,返回值为 char* 的函数的指针

      91. 多线程怎么实现共享内存 

      • 在 全局/堆 上创建共享数据结构(如队列、计数器)
      • 把指针或引用传给各线程
      • pthread_mutex / std::mutex、读写锁、条件变量等保护临界区;
        或用 std::atomic实现无锁并发。

      92. 同步I0 和异步10的区别? 

      • 同步 I/O:发起I/O后,必须等待操作完成才能继续执行
      • 异步 I/O:发起I/O后,不等待,继续执行,操作完成后通过回调、事件或通知机制处理结果。

      96. 树的特点

      • 层级结构 唯一根节点  每个节点只有一个父节点 可以有多个子节点
      • 无环结构 递归定义

      97. 图的特点

      • 顶点(节点)和组成
      • 分为:有向图和无向图,环图和无环图,连通图和非连通图,权重图和非权重图
      • 结构复杂,可表示任意关系

      98. C++多线程

      C++ 多线程:通过 std::thread 创建多个线程并发执行任务,配合 mutexcondition_variable 等实现线程同步与通信,提升程序效率。

        0. 快排和堆排的原理

        快速排序

        • 通过选一个基准值,把数组划分为左右两部分(小于基准、大于基准),递归排序左右部分。核心是分治+递归

        堆排序

        • 利用这种数据结构,先建堆再将堆顶元素取出放到数组末尾然后调整堆。核心是选择排序+堆结构维护

         1. C++11/14/17新特性你用过哪些?为什么用?

        C++11:

        • auto:简化类型声明,提升可读性。

        • lambda 表达式:简洁定义函数对象,用于回调、算法

        • move 语义:减少拷贝,提升性能。

        • unique_ptr/shared_ptr:智能指针管理资源,防止内存泄漏。

        • threadmutex:支持多线程开发


        C++14:

        • make_unique:更安全高效创建 unique_ptr

        • 泛型 lambda:lambda 参数支持自动类型推导。


        C++17:

        • if constexpr:编译期条件判断,配合模板更灵活。

        • 结构化绑定auto [a, b] = pair;):解构赋值,提高可读性。

        • std::optional:表示可有可无的返回值,替代 nullptr/null

        • std::variant:类型安全的联合体。

        • std::filesystem:处理文件系统更方便。

         2. 智能指针(unique_ptr / shared_ptr)的工作机制和区别?

        工作机制:

        • unique_ptr独占式所有权,对象只能被一个指针管理,转移所有权用 std::move

        • shared_ptr共享所有权,内部有引用计数,最后一个引用释放时析构对象。

        • 底层通过析构器自动释放资源,避免手动 delete

        区别:

        特性 unique_ptr shared_ptr
        所有权 唯一 多个指针可共享
        拷贝 不可拷贝(可移动) 可拷贝
        内存开销 有引用计数器,开销大一些
        用途 独占资源(RAII) 多方共享资源

        总结:能用 unique_ptr 就不用 shared_ptr,性能更好。

        3. RAII 是什么?你在项目中怎么用的? 

        RAII(Resource Acquisition Is Initialization)是一种资源权限转移的思想,资源获取即初始化。对象生命周期控制资源生命周期,在构造函数中申请资源,在析构中释放,避免泄漏

        4. 拷贝构造 vs 移动构造

        构造方式 含义 特点
        拷贝构造函数 用已有对象复制一个新对象 深拷贝,资源复制,开销大
        移动构造函数 把资源从旧对象“搬”到新对象 不复制资源,快,旧对象置空

         5.虚函数表机制、虚析构函数为什么必须写? 

        虚函数表机制(vtable):

        • 类中有虚函数 → 编译器生成虚函数表(vtable)。

        • 每个对象有指向 vtable 的指针(vptr)

        • 通过 vptr 实现运行时多态,调用正确的派生类函数。

        为什么要写虚析构函数:

        • 父类指针指向子类对象时,删除对象只调用父类析构 → 资源泄漏

        • 虚析构能确保先调用子类析构,再调用父类析构,释放完整资源。

        总结:只要类有虚函数,析构函数必须为虚,否则可能导致内存泄漏。

         6. TCP 三次握手流程、TIME_WAIT 出现的原因?

        TCP 三次握手流程:

        1. 客户端 → 服务端:发送 SYN,进入 SYN_SENT 状态。

        2. 服务端 → 客户端:回复 SYN+ACK,进入 SYN_RCVD

        3. 客户端 → 服务端:发送 ACK,进入 ESTABLISHED,服务端接收后也进入 ESTABLISHED

        TIME_WAIT 出现原因:

        • 主动关闭连接的一方进入 TIME_WAIT 状态

        • 等待 2 倍最大报文生存时间(MSL),确保对方收到最后 ACK。

        • 防止旧连接的延迟报文影响后续新连接。

        总结TIME_WAIT为连接安全清理而设计的机制

        7. select 和 epoll 区别?

        select vs epoll

        对比项 select epoll
        监视上限 FD 索引最多 1024/2048(受 FD_SETSIZE 理论无限(取决于内核和内存)
        事件通知方式 每次线性扫描 FD 集合 内核通过事件列表直接返回就绪 FD
        工作模式 始终水平触发(Level‑Triggered) 支持 LT ET(Edge‑Triggered)
        内核/用户态拷贝 每次调用需复制 FD 集合 就绪列表一次返回,无额外拷贝
        性能随连接数变化 O(N) O(1),与连接数弱相关
        适用场景 低并发、移植性好 高并发、高性能 Linux 服务器

        8. 多线程同步机制?mutex、condition_variable用法? 

        常见多线程同步机制:

        • std::mutex:互斥锁,保护临界区。

        • std::recursive_mutex:允许同一线程多次加锁。

        • std::lock_guard / std::unique_lock:RAII 自动加解锁。

        • std::condition_variable:线程间通信/等待。

        • std::atomic:原子变量,锁-free 方案。

        • std::future/promise:线程结果传递。

        9. 线程安全怎么保证?

        互斥锁std::mutex:防止多个线程同时访问共享资源。

        原子操作std::atomic:适合简单变量如计数器,无需加锁。

        线程局部存储thread_local:每个线程独立变量,避免共享。

        条件变量std::condition_variable:配合锁,实现线程间同步等待。

        避免共享:设计时尽量避免多个线程同时访问同一资源(如消息队列、线程池中工作隔离)

        10. C++观察者模式 和 工厂模式

        • 观察者模式:一种订阅-通知机制,一个对象状态变化后,自动通知所有依赖它的对象(观察者)。
        • 工厂模式:用一个工厂类/函数封装对象的创建逻辑,通过传参决定创建哪种子类对象,实现解耦和扩展性

         11. TCP沾包问题怎么解决

        常见原因

        • 发送端发送速度太快,连续发送多个小包 
        • 接收端读取不及时或读取缓冲区较大

        固定长度:每条消息规定长度 

        特殊分隔符:在每条消息结尾添加特定字符,如 \n\r\n

        消息头加长度字段:每条消息前添加一个固定长度的头部字段

        高级协议封装:使用其他协议封装TCP 


        网站公告

        今日签到

        点亮在社区的每一天
        去签到