大家好,我是小林。
今天分享一位同学百度实习一面的面经,技术栈是 C ,由于项目没什么亮点,所以大部分内容都是在问 C 的问题,没怎么问项目问题。
操作系统
对new和malloc的理解
new和malloc都是动态内存分配函数。其中,new是C 中的操作符,malloc是C语言中的函数。new会调用对象的构造函数,而malloc不会。使用new可以简化代码,并且更加类型安全。
补充:
new和malloc区别:
- 分配内存的位置:malloc是从堆上动态分配内存,new是从自由存储区为对象动态分配内存。自由存储区的位置取决于operator new的实现。自由存储区不仅可以为堆,还可以是静态存储区,这都看operator new在哪里为对象分配内存。
- 返回类型安全性:malloc内存分配成功后返回void*,然后再强制类型转换为需要的类型;new操作符分配内存成功后返回与对象类型相匹配的指针类型;因此new是符合类型安全的操作符。
- 内存分配失败返回值:malloc内存分配失败后返回NULL。new分配内存失败则会抛异常(bac_alloc)。
- 分配内存的大小的计算:使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。
- 是否可以被重载:opeartor new /operator delete可以被重载。而malloc/free则不能重载。
new是在内存上哪一块去分配的内存
堆
补充:
new所申请的内存区域在C 中称为自由存储区。很多编译器的new/delete都是以malloc/free为基础来实现的,所以通常都是借由堆实现来实现自由存储,这时候就可以说new所申请的内存区域在堆上。
如果new内存失败了会是怎么样?
会抛出std::bad_alloc异常。
补充:
如果加上std::nothrow关键字,A* p = new (std::nothrow) A;,new 就不会抛出异常而是会返回空指针。
析构函数为什么通常是会做成一个虚函数呢
如果一个类有虚函数,就应该为其定义一个虚析构函数。这是因为在使用delete操作符释放一个指向派生类对象的基类指针时,如果基类的析构函数不是虚函数,那么只会调用基类的析构函数,而不会调用派生类的析构函数,这样就会导致内存泄漏和未定义行为的问题。通过将析构函数定义为虚函数,可以确保在释放派生类对象时,先调用派生类的析构函数,再调用基类的析构函数,从而避免内存泄漏和未定义行为的问题。
线程和进程有什么区别
进程是程序在操作系统中的一次执行过程,它拥有独立的地址空间和系统资源。线程是进程中的一个执行单元,同一进程内的多个线程共享相同的地址空间和系统资源。
补充:
- 进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个进程,进程就是运行起来的可执行程序;线程是程序执行的基本单位,每个进程中都有唯一的主线程,且只能有一个,主线程和进程是相互依存的关系,主线程结束进程也会结束。
- 每个进程有自己的独立地址空间,不与其他进程分享;一个进程里可以有多个线程,彼此共享同一个地址空间。堆内存、文件、套接字等资源都归进程管理,同一个进程里的多个线程可以共享使用。每个进程占用的内存和其他资源,会在进程退出或被杀死时返回给操作系统。
- 并发应用开发可以用多进程或多线程的方式。多线程由于可以共享资源,效率较高;反之,多进程(默认)不共享地址空间和资源,开发较为麻烦,在需要共享数据时效率也较低。但多进程安全性较好,在某一个进程出问题时,其他进程一般不受影响;而在多线程的情况下,一个线程执行了非法操作会导致整个进程退出。
右值引用有什么作用
没用过
补充:
- 右值引用是C 11引入的特性,它是指对右值进行引用的一种方式。右值引用的作用主要有两个:
- 可以通过右值引用来实现移动语义。移动语义可以在不进行深拷贝的情况下,将对象的资源所有权从一个对象转移到另一个对象,从而提高代码的效率。
- 右值引用还可以用于完美转发。在函数模板中,通过使用右值引用类型的形参来接收参数,可以实现完美转发,即保持原参数的值类别(左值还是右值),将参数传递给另一个函数。
智能指针
智能指针是C 中的一种特殊指针,它是一个对象,用来管理另一个指针所指向的对象的生命周期。智能指针可以自动地分配和释放内存,避免手动管理内存的麻烦和出错风险。
C 标准库提供了三种智能指针:
- shared_ptr:多个智能指针可以共享同一个对象,当最后一个指针被销毁时,它会释放对象的内存。
- unique_ptr:独占式智能指针,不能共享同一个对象,当智能指针被销毁时,它会释放对象的内存。
- weak_ptr:弱引用智能指针,不会增加对象的引用计数,用于避免shared_ptr循环引用时的内存泄漏问题。
在哪些场景下会应用智能指针
我自己是在在动态内存管理中,使用智能指针可以避免手动管理内存的麻烦和出错风险。
如果遇到内存泄漏这种问题,你一般是怎么去解决
打断点定位然后做处理
后来思考对方应该是想让我回答这种处理措施⬇️
- 在程序中加入必要的错误处理代码,避免程序因为异常情况而导致内存泄漏。
- 使用智能指针等RAII机制,自动管理内存,避免手动管理内存的麻烦和出错风险。
- 使用内存分析工具,检测程序中的内存泄漏,并进行相应的修复。
class中缺省的函数
没关注
补充:
在C 中,如果一个类没有显式地定义「构造函数、析构函数、拷贝构造函数、赋值运算符重载函数」,那么编译器会自动生成这些函数,这些函数被称为缺省函数。
sort函数内部是什么
sort函数内部使用快速排序算法实现,它的时间复杂度为O(nlogn),是一种非常高效的排序算法。
快排的原理
- 选择一个基准元素。
- 将小于等于基准元素的元素移动到数组左边,大于基准元素的元素移动到数组右边,这个过程称为划分。
- 递归地对划分后的左右两个子序列进行排序。
但是仔细想想还可以继续回答⬇️
在实际实现中,sort函数还有一些优化,例如:
- 当排序的元素个数小于一定阈值时,使用插入排序算法。
- 当出现大量重复元素时,使用三向划分快速排序算法。
为什么选快排
默认它的分布是比较随机的那种分布,然后快排在比较随机的分布上,表现的比较好,速度比较快
多线程锁是什么
多线程锁是一种用来保护共享资源的机制。在多线程编程中,如果多个线程同时访问同一个共享资源,可能会发生竞态条件(Race Condition),导致程序的行为出现未定义的情况。为了避免这种情况的发生,可以使用多线程锁来保护共享资源。
多线程锁的基本思想是,在访问共享资源之前先获取锁,访问完成之后再释放锁。这样可以保证同一时刻只有一个线程可以访问共享资源,从而避免竞态条件的发生。
常见的多线程锁包括互斥锁、读写锁、条件变量等。其中,互斥锁用于保护共享资源的访问,读写锁用于在读多写少的情况下提高并发性能,条件变量用于线程之间的同步和通信。
mysql的事务是什么
在数据库中,事务(Transaction)是一组操作单元,这些操作单元要么全部执行成功,要么全部执行失败。事务是保证数据库一致性的重要机制之一,它可以将一系列的操作看作一个整体,从而保证数据库的完整性和正确性。
事务具有四个特性,即ACID:
- 原子性(Atomicity):事务中的所有操作要么全部执行成功,要么全部执行失败,不会出现部分执行的情况。
- 一致性(Consistency):事务执行前后数据库的状态是一致的,即数据库中的约束和规则都得到了保持。
- 隔离性(Isolation):多个事务并发执行时,相互之间不会影响彼此的执行结果。
- 持久性(Durability):事务执行完成后,对数据库所作的修改将被永久保存到数据库中。
MySQL是一种常见的关系型数据库,支持事务的机制。在MySQL中,事务可以
通过使用事务控制语句(Transaction Control Statements)来进行管理,包括以下三个语句:
- START TRANSACTION:开始一个事务。
- COMMIT:提交一个事务,使之生效。
- ROLLBACK:回滚一个事务,使之失效。
在MySQL中,事务默认是关闭的,需要通过设置autocommit参数为0来启用事务。启用事务后,可以通过执行SQL语句来进行事务操作,
TCP连接中间会有什么操作
在TCP连接中,客户端和服务器之间会进行以下操作:
- 握手阶段:客户端向服务器发送SYN包(同步包),请求建立连接。服务器收到SYN包后,向客户端发送SYN ACK包(同步确认包),表示可以建立连接。客户端收到SYN ACK包后,再向服务器发送ACK包(确认包),表示连接建立成功。
- 数据传输阶段:连接建立成功后,客户端和服务器之间可以进行数据的传输。客户端向服务器发送数据包,服务器接收数据包并进行处理,然后向客户端发送响应包。客户端收到响应包后,可以再次向服务器发送数据包,以此类推。
- 断开连接阶段:当客户端或服务器不再需要连接时,可以发送FIN包(结束包)来请求断开连接。对方收到FIN包后,也发送FIN包进行响应,表示同意断开连接。当两端都收到对方的FIN包后,连接才真正关闭。
需要注意的是,在TCP连接中可能会出现丢包、拥塞等情况,需要进行相应的处理,例如重传丢失的数据包、调整发送窗口大小等。
算法
表内指定的区间反转
反问
部门业务;技术栈情况
面试总结
感觉:
- 感觉还行,基本上面经都回答出来了,没怎么具体问项目(因为我没有好问的项目)
不足之处:
- C 的基础知识还是不够熟练
- 项目优化