大厂C++面试---同城旅行
借鉴链接https://mp.weixin.qq.com/s/l8ajchkWzvGOOzqRFdQMrA1、const关键字作用const常量可以修饰指针的两个方面指针所指向的变量和指针本身。指针常量当const位于星号左侧如const char *p表示p指向的内容是不可修改的常量常量指针当const位于星号右侧如char * const p表示指针p本身的地址不可变但其所指内容可变而char const *p与const char *p同义都是指指针所指的字符不可变。1、sizeof与strlen的区别1.1sizeof是C/C中的操作符用于获取一个数据类型或变量所占用的字节数。它是在编译时计算的返回的是数据类型或变量的字节数而不是字符数。主要用于静态分配内存和获取数据类型的大小。intarr[5];size_t sizesizeof(arr);// size 20 (5 * 4 字节)1.2strlen是C/C中的函数用于获取一个以null为终止字符数组C字符串的长度即字符数。它是在运行时计算的遍历字符数组直到遇到null终止符。主要用于计算C字符串的长度。constchar*strHello, World!;size_t lengthstrlen(str);// length 13需要注意的是sizeof通常用于获取数据类型的大小而strlen用于计算C字符串的长度。2、运算符和函数有什么区别a. 运算符是用于执行操作的特殊符号而函数是自包含代码单元用于执行一系列操作。b. 运算符通常与基本数据类型一起使用而函数可以用于各种操作包括处理复杂的数据结构和对象。c. 运算符具有内置的语法和行为而函数的行为需要在函数体内定义。d. 运算符重载是C中的一个特性允许用户自定义运算符的行为而函数是C中的基本构建块之一用于组织和执行代码。3、new和malloc3.1 newa. new 是C中的运算符而不是函数。它使用对象的构造函数来分配内存。b. 当使用 new 分配内存时它会执行以下操作1分配足够的内存以容纳对象或对象数组2调用对象的构造函数来初始化内存中的对象3返回指向已分配内存的指针。c.new 不需要显式指定分配的内存大小因为它会自动计算对象或数组的大小。3.2 malloca. malloc 是C语言标准库函数也可以在C中使用。它不执行对象的构造函数;b. 当使用 malloc 分配内存时它会执行以下操作1分配指定大小的内存块2返回指向已分配内存的指针。c. malloc 需要显式指定分配的内存大小通常使用 sizeof 运算符来计算对象或数组的大小。3.3 区别1new 会调用对象的构造函数来初始化内存中的对象而 malloc 不会。这使得 new 更适合用于C类对象。2new 不需要显式指定内存大小而 malloc 需要显式指定内存大小。3new 是类型安全的因为它知道要分配的是什么类型的对象。malloc 不了解对象的类型。4new 返回指向已分配内存的对象指针而malloc 返回 void指针需要进行类型转换*。4、内存泄漏与规避方法内存泄漏Memory Leak是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放造成系统内存的浪费导致程序运行速度减慢甚至系统崩溃等严重后果。规避方法a C中的智能指针如std::shared_ptr和std::unique_ptr可以自动管理内存。它们会在不再需要时自动释放内存从而避免了手动释放内存的错误。b 创建对象时分配资源并在对象生命周期结束时自动释放资源。例如使用std::fstream来打开文件它将在退出作用域时自动关闭文件。c使用标准库容器类如std::vector、std::map等它们在元素不再需要时会自动处理内存的释放。dC11引入了std::shared_ptr和std::unique_ptr的容器如std::vectorstd::shared_ptr以便更容易管理动态分配的对象。e在使用智能指针时小心循环引用问题。循环引用可能导致内存泄漏。为了避免这种情况可以使用std::weak_ptr来打破引用环。f如果你使用new分配内存则应该使用delete释放它如果使用new[]分配数组则应使用delete[]释放。在分配和释放内存时一定要保持一致。g使用工具如ValgrindLinux/Unix、Dr. MemoryWindows、AddressSanitizerClang编译器、或MemorySanitizerClang编译器来检测内存泄漏。h定期进行代码审查以寻找可能导致内存泄漏的问题。i为了调试可以记录内存分配和释放的情况以便更容易识别内存泄漏。5、悬空指针和野指针5.1 悬空指针Dangling Pointera. 定义悬空指针是指已经被释放的内存或对象的指针但仍然保留了指向该内存或对象的地址。b. 产生原因悬空指针通常由于在指针指向的内存被释放后未将指针重置为nullptr或其他有效值。c. 危害使用悬空指针可能导致读取无效内存修改已经释放的内存或调用已经销毁的对象的方法从而引发崩溃或未定义的行为。5.2 野指针a. 定义野指针是指未初始化或赋值的指针它包含一个未知的地址通常指向内存中的随机位置。b. 产生原因野指针通常由于在创建指针后未初始化或在释放内存后未将指针置为nullptr。c. 危害使用野指针可能导致程序访问随机内存引发崩溃或未定义的行为。所以使用指针的时候需要注意以下几点1在创建指针后确保初始化它或者将其设置为nullptr。2在释放内存后立即将指针置为nullptr以避免悬空指针。3使用智能指针如std::shared_ptr和std::unique_ptr来管理资源从而避免手动释放内存减少悬空指针的风险。4遵循良好的内存管理实践定期检查代码以查找并修复悬空指针和野指针问题。5使用工具如ValgrindLinux/Unix、Dr. MemoryWindows、或编译器的内存检测工具来检测和修复指针问题。6、手撕冒泡排序6.1 思路通过多次遍历待排序的元素比较相邻的两个元素如果它们的顺序不正确例如如果要升序排序当前元素比下一个元素大则交换它们的位置直到整个序列有序。具体来说冒泡排序的过程如下a. 从数组的第一个元素开始比较它与下一个元素。b. 如果当前元素大于下一个元素如果要升序排序则交换它们的位置。c. 移动到下一个元素重复步骤1和2直到遍历整个数组一次。此时最大的元素已经被推到了数组的末尾。d. 重复步骤a至c但忽略已经排序好的末尾元素继续对剩余的元素进行遍历和比较。e. 重复以上步骤直到没有需要交换的元素整个数组已经排好序。冒泡排序的主要特点是它多次遍历数组每次遍历都会将一个最大或最小根据排序顺序的元素冒泡到正确的位置因此称为冒泡排序。这个算法的时间复杂度为O(n^2)其中n是待排序元素的数量。尽管它不是最高效的排序算法但它非常简单容易理解适用于小型数据集或已接近排序状态的数据。6.2 参考代码#includeiostream#includevectorvoidbubbleSort(std::vectorintarr){intnarr.size();boolswapped;for(inti0;in-1;i){swappedfalse;for(intj0;jn-i-1;j){if(arr[j]arr[j1]){// 交换arr[j]和arr[j1]inttemparr[j];arr[j]arr[j1];arr[j1]temp;swappedtrue;}}// 如果在一轮遍历中没有发生交换说明数组已经有序可以提前结束if(!swapped){break;}}}intmain(){std::vectorintarr{64,34,25,12,22,11,90};std::cout原始数组: ;for(intnum:arr){std::coutnum ;}bubbleSort(arr);std::cout\n排序后的数组: ;for(intnum:arr){std::coutnum ;}return0;}7、说一下map在C中map内部实现了一个红黑树红黑树是非严格平衡二叉搜索树而AVL是严格平衡二叉搜索树红黑树具有自动排序的功能因此map内部的所有元素都是有序的红黑树的每一个节点都代表着map的一个元素。因此对于map进行的查找删除添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树又名二叉查找树、二叉排序树特点就是左子树上所有节点的键值都小于根节点的键值右子树所有节点的键值都大于根节点的键值存储的使用中序遍历可将键值按照从小到大遍历出来。优缺点优点a有序性这是map结构最大的优点其元素的有序性在很多应用中都会简化很多的操作。b红黑树内部实现一个红黑树使得map的很多操作在lgn的时间复杂度下就可以实现因此效率非常的高。缺点空间占用率高因为map内部实现了红黑树虽然提高了运行效率但是因为每一个节点都需要额外保存父节点、孩子节点和红/黑性质使得每一个节点都占用大量的空间。使用示例#includeiostream#includemapintmain(){std::mapstd::string,intwordCount;// 插入键-值对wordCount[apple]5;wordCount[banana]3;// 查找键的值std::coutCount of apple: wordCount[apple]std::endl;// 遍历mapfor(constautopair:wordCount){std::coutpair.first: pair.secondstd::endl;}return0;}8、STL是否线程安全C11有什么保证线程安全的特性STL标准模板库在C中不是线程安全的。STL容器和算法通常不提供内置的线程安全机制因此在多线程环境中使用STL容器和算法时需要采取额外的措施来确保线程安全。aC11引入了互斥量mutex以及其包装类std::lock_guard它们可以用来在多线程环境中同步对共享数据的访问。通过在对STL容器和算法的访问前后使用互斥量可以实现线程安全。bC11引入了std::atomic模板用于实现原子操作以确保多线程环境下对共享变量的安全操作。它包括一系列原子类型如std::atomic、std::atomic等。cC11引入了std::condition_variable它允许线程在等待特定条件成立时阻塞直到其他线程满足条件并通知它。dstd::future 和 std::promise这些C11特性用于实现异步编程可以在多线程环境中方便地返回和获取异步操作的结果。