2025年CC++程序员常见笔试题含解析及答案_第1页
2025年CC++程序员常见笔试题含解析及答案_第2页
2025年CC++程序员常见笔试题含解析及答案_第3页
2025年CC++程序员常见笔试题含解析及答案_第4页
2025年CC++程序员常见笔试题含解析及答案_第5页
已阅读5页,还剩17页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

2025年CC++程序员常见笔试题含解析及答案1.智能指针自定义删除器题目:设计一个使用`std::unique_ptr`管理文件描述符(int类型)的场景,要求当`unique_ptr`析构时自动关闭文件描述符。需写出关键代码并说明自定义删除器的实现逻辑。解析:`std::unique_ptr`默认删除器是调用`delete`释放堆内存,但管理非堆资源(如文件描述符)时需自定义删除器。删除器类型需作为`unique_ptr`的第二个模板参数,且删除器对象需能通过`operator()`接收被管理的指针(或直接是资源句柄)。答案:```cppinclude<memory>include<unistd.h>//close函数声明//自定义删除器类型,用于关闭文件描述符structFileDescriptorDeleter{voidoperator()(intfd)const{if(fd&&fd!=-1){close(fd);//关闭文件描述符fd=-1;//避免重复关闭}deletefd;//释放存储文件描述符的堆内存(注意:这里管理的是堆上的int)}};//另一种更直接的方式(管理非堆资源时,可直接持有句柄而非指针)usingUniqueFd=std::unique_ptr<int,decltype([](intfd){if(fd&&fd!=-1){close(fd);fd=-1;}deletefd;})>;//使用示例intmain(){intfd_ptr=newint(open("test.txt",O_RDONLY));//假设open成功返回非-1值std::unique_ptr<int,FileDescriptorDeleter>uptr(fd_ptr);//或使用lambda作为删除器(C++14起支持泛型lambda)autodeleter=[](intfd){if(fd&&fd!=-1){close(fd);fd=-1;}deletefd;};std::unique_ptr<int,decltype(deleter)>uptr2(fd_ptr,deleter);return0;}```关键逻辑:删除器需正确处理资源释放(如调用`close`),并避免重复释放。若直接管理句柄(而非堆指针),可使用`std::unique_ptr<int,void()(int)>`,但需注意`unique_ptr`默认管理的是指针类型,因此需将句柄存储在堆内存中(或使用`std::experimental::observer_ptr`配合自定义删除器,但非标准)。2.虚函数与多继承的内存布局题目:已知类继承关系如下,画出`D`类对象的内存布局(假设32位系统,无虚函数覆盖,无成员变量):```cppclassA{virtualvoida(){}};classB:virtualpublicA{virtualvoidb(){}};classC:virtualpublicA{virtualvoidc(){}};classD:publicB,publicC{virtualvoidd(){}};```解析:虚继承用于解决菱形继承的二义性问题,派生类会包含虚基类表(vbtable)指针,指向虚基类的偏移量。每个有虚函数的类有独立的虚函数表(vtable)指针。32位系统中指针占4字节。答案:`D`对象的内存布局(从低地址到高地址):-B的vtable指针(指向B的vtable,包含B::b()和D::d(),因D覆盖了B的虚函数?不,题目中D的d()是新虚函数,未覆盖任何基类。实际B的vtable包含B::b()和A::a()(若未覆盖),但题目假设无虚函数覆盖,因此B的vtable包含B::b()和A的a()?需明确:每个类的vtable包含自身定义的虚函数,未覆盖的基类虚函数仍保留在基类vtable中。正确布局应为:-B子对象:-B的vtable指针(指向B的vtable:包含B::b())-虚基类表指针(指向B的虚基类表,记录到A的偏移量)-C子对象:-C的vtable指针(指向C的vtable:包含C::c())-虚基类表指针(指向C的虚基类表,记录到A的偏移量)-D的vtable指针(若D新增虚函数,需扩展最近的基类vtable?实际D的虚函数会被添加到其第一个直接非虚基类(B)的vtable中,因此可能不单独存在D的vtable指针)-共享的A子对象(因虚继承):-A的vtable指针(指向A的vtable:包含A::a())注:实际布局因编译器而异(如MSVC和GCC的虚表布局不同),但核心是虚继承导致虚基类表指针的存在,且虚基类A在D中只存在一份。3.vector的迭代器失效问题题目:分析以下代码的输出结果,并说明迭代器失效的原因。```cppinclude<vector>include<iostream>usingnamespacestd;intmain(){vector<int>vec={1,2,3,4,5};autoit=vec.begin();vec.push_back(6);//操作1cout<<it<<endl;//输出?vec.insert(it,0);//操作2cout<<vec.size()<<endl;//输出?return0;}```解析:vector的`push_back`可能导致扩容(当当前容量不足时),此时会重新分配内存,原迭代器、指针、引用失效。`insert`操作若在非end位置插入元素,可能导致插入点之后的迭代器失效(若未扩容),但插入点之前的迭代器是否失效取决于是否扩容。答案:操作1中,`vec`原容量为5(假设初始容量为5),`push_back(6)`需要扩容(容量变为10,假设),因此原迭代器`it`失效。此时`it`为未定义行为(可能崩溃或输出随机值)。操作2中,`it`已失效,使用失效的迭代器调用`insert`会导致未定义行为(如程序崩溃)。因此实际运行中,代码可能在操作2处崩溃,无法输出第二个结果。扩展:vector的迭代器失效规则:-插入(`push_back`/`insert`等):若导致扩容,所有迭代器失效;否则,插入点之后的迭代器失效,之前的有效。-删除(`erase`/`pop_back`等):删除点之后的迭代器失效,之前的有效(`pop_back`仅使end()失效)。4.手写strncpy函数(考虑内存重叠)题目:实现`charstrncpy(chardest,constcharsrc,size_tn)`,要求处理源串长度小于n时填充'\0',且处理`dest`与`src`内存重叠的情况。解析:标准`strncpy`不保证目标串以'\0'结尾(除非源串长度小于n),且不处理内存重叠(可能导致覆盖)。需先判断是否重叠,若重叠则从后向前复制。答案:```cppcharstrncpy(chardest,constcharsrc,size_tn){if(dest==nullptr||src==nullptr||n==0)returndest;chardest_start=dest;size_tsrc_len=0;//计算源串长度(不超过n)while(src_len<n&&src[src_len]!='\0')src_len++;//判断是否内存重叠:dest在[src,src+n)区间内if(dest>src&&dest<src+n){//重叠,从后向前复制for(size_ti=src_len;i>0;--i){dest[i-1]=src[i-1];}//填充剩余的'\0'(若src_len<n)for(size_ti=src_len;i<n;++i){dest[i]='\0';}}else{//非重叠,从前向后复制for(size_ti=0;i<src_len;++i){dest[i]=src[i];}//填充剩余的'\0'for(size_ti=src_len;i<n;++i){dest[i]='\0';}}returndest_start;}```关键点:-先计算源串实际长度`src_len`(不超过n),避免越界。-内存重叠判断:若`dest`在`src`和`src+n`之间,说明复制会覆盖未读取的源数据,需反向复制。-源串长度小于n时,目标串后续字节填充'\0'(与标准`strncpy`一致)。5.多线程下的原子操作与内存顺序题目:分析以下代码是否线程安全,若不安全请说明原因并给出修复方案。```cppinclude<atomic>include<thread>std::atomic<int>count(0);voidincrement(){for(inti=0;i<1000;++i){count.fetch_add(1,std::memory_order_relaxed);}}intmain(){std::threadt1(increment);std::threadt2(increment);t1.join();t2.join();//期望count为2000,实际是否可能小于2000?return0;}```解析:`fetch_add`是原子操作,保证计数的原子性,但`memory_order_relaxed`仅保证操作本身的原子性,不提供线程间的同步。但在此场景中,两个线程仅修改同一个原子变量,最终结果应为2000,因为`fetch_add`的原子性保证了每次递增的可见性。答案:代码是线程安全的,最终`count`的值一定是2000。`std::memory_order_relaxed`在此处足够,因为没有其他共享变量需要同步,仅需保证`fetch_add`的原子性即可。扩展:若存在其他共享状态(如标志位),则需使用更严格的内存顺序(如`memory_order_acq_rel`)保证操作的顺序性。例如,若一个线程写入数据后设置标志位,另一个线程读取标志位后读取数据,此时需`memory_order_release`(写标志位)和`memory_order_acquire`(读标志位)保证数据先于标志位被写入和读取。6.模板元编程:编译期计算斐波那契数题目:使用C++模板元编程实现编译期计算斐波那契数列的第n项(n≥0),要求支持n=0时返回0,n=1时返回1。解析:模板元编程通过递归模板特化实现编译期计算。斐波那契数列的递推式为`fib(n)=fib(n-1)+fib(n-2)`,边界条件为`fib(0)=0`,`fib(1)=1`。答案:```cpptemplate<size_tn>structFibonacci{staticconstexprsize_tvalue=Fibonacci<n-1>::value+Fibonacci<n-2>::value;};template<>structFibonacci<0>{staticconstexprsize_tvalue=0;};template<>structFibonacci<1>{staticconstexprsize_tvalue=1;};//使用示例constexprsize_tfib_5=Fibonacci<5>::value;//fib_5=5(0,1,1,2,3,5)```关键点:-主模板处理n≥2的情况,递归调用`n-1`和`n-2`。-特化模板处理边界条件n=0和n=1。-`constexpr`保证值在编译期计算。扩展:C++14起支持`constexpr`函数,可更简洁地实现编译期斐波那契计算:```cppconstexprsize_tfibonacci(size_tn){return(n==0)?0:(n==1)?1:fibonacci(n-1)+fibonacci(n-2);}constexprsize_tfib_5=fibonacci(5);//同样得到5```7.内存对齐与结构体大小计算题目:计算以下结构体在64位Linux(GCC)下的大小(假设`int`占4字节,`long`占8字节,`char`占1字节):```cppstructExample{chara;intb;longc;chard;inte;longf;};```解析:GCC默认对齐规则为结构体成员的对齐数取自身大小与编译器最大对齐数(64位为8字节)的较小值。结构体整体大小需是最大成员对齐数的整数倍。各成员偏移量计算:-`a`(char,1字节):偏移0,对齐1,满足。-`b`(int,4字节):需偏移量是4的倍数,因此填充3字节(偏移0+1=1,下一个可用偏移4),`b`在偏移4。-`c`(long,8字节):需偏移量是8的倍数,当前偏移4+4=8,满足,`c`在偏移8。-`d`(char,1字节):偏移8+8=16,对齐1,满足。-`e`(int,4字节):需偏移量是4的倍数,当前偏移16+1=17,填充3字节(偏移20),`e`在偏移20。-`f`(long,8字节):需偏移量是8的倍数,当前偏移20+4=24,满足,`f`在偏移24。结构体总大小:24+8=32字节。最大对齐数是8(long的大小),32是8的倍数,无需额外填充。答案:结构体`Example`的大小为32字节。验证:各成员偏移量为0(a)、4(b)、8(c)、16(d)、20(e)、24(f),总大小24+8=32。8.野指针的产生与避免题目:列举野指针的三种常见产生原因,并说明至少两种避免方法。解析:野指针指指向无效内存的指针,无法通过简单判空(`nullptr`)检测。答案:产生原因:1.指针未初始化:定义指针变量后未赋值,默认指向随机内存(如`intp;p=10;`)。2.指针指向的内存被释放后未置空:`deletep`后,`p`成为悬空指针(danglingpointer),若后续再次访问`p`即形成野指针。3.指针越界访问:如访问数组`arr[10]`时,指针指向`arr[10]`(假设数组大小为10,索引0-9),超出有效范围。避免方法:1.指针初始化时赋值为`nullptr`(或有效地址),避免未初始化。2.释放内存后立即将指针置为`nullptr`(如`deletep;p=nullptr;`),后续访问时可通过`if(p)`判断是否有效。3.使用智能指针(如`std::unique_ptr`、`std::shared_ptr`)自动管理内存生命周期,避免手动释放。4.避免返回局部变量的指针或引用(局部变量生命周期结束后内存被回收)。9.const与volatile的联合使用题目:说明`constvolatileintp`的含义,并举例说明其使用场景。解析:`const`修饰指针指向的内容不可修改(但指针本身可修改),`volatile`修饰指针指向的内容可能被编译器未知的因素(如硬件、其他线程)修改,禁止编译器优化对该变量的读取。答案:`constvolatileintp`表示`p`是一个指向`constvolatileint`的指针。指针本身可以修改(非`const`),但指针指向的内容既不可被当前代码修改(`const`),又可能被外部因素修改(`volatile`),因此每次访问都需从内存读取,不能缓存。使用场景:访问硬件寄存器(如嵌入式系统中的只读状态寄存器)。例如,某硬件状态寄存器的值由外部电路更新(如传感器数据),程序只能读取不能修改,此时可用`constvolatileint`指针指向该寄存器地址,确保编译器不优化读取操作,且防止程序误修改。示例代码:```cpp//假设硬件状态寄存器地址为0x1000,只读且值可能被硬件修改constvolatileintstatus_reg=reinterpret_cast<constvolatileint>(0x1000);intget_status(){returnstatus_reg;//每次读取都从内存(寄存器)获取最新值,不使用缓存}```

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论