个人的能力有限,希望大家对文章内容有疑问或者建议或者是有比较好的面试题可以放在评论区,谢谢!
21.C代码中引用C++代码有时候会报错为什么?
C代码中引用C++代码会导致以下问题:
-
名称空间问题:C语言没有命名空间namespace的概念,而C++有。因此,在引用C++代码时,需要使用特定的方式指定命名空间。如果未正确指定命名空间,则会出现名称冲突和编译错误。
-
C++语法问题:C++中有一些语法是在C语言中不存在的,因此在引用C++代码时需要注意,如C++的模板,泛型,C语言中是不存在的。
-
类型转换问题:由于C和C++对类型转换的机制不同,因此在引用C++代码时需要注意类型转换是否正确。
-
C和C++的标准库不同:C和C++的标准库也有很多不同之处,比如C++中输入输出使用的是iostream库,而C中使用的是stdio.h库。在引用C++代码时可能会出现标准库不兼容的情况。
-
链接问题:在将C代码和C++代码链接在一起时,可能会出现链接问题,比如找不到对应的库文件或符号等。
-
编译器差异:由于不同编译器实现不同,在编译混合代码时可能会遇到一些编译器相关的问题。
解决以上问题的方法:
-
在头文件中使用extern “C”声明来告知编译器该部分为纯粹的“C”语言,以免发生函数命名错误或者变量名重复等情况。
-
使用extern关键字:在C++代码中可以使用extern关键字来声明C语言函数,以便在C代码中引用这些函数。提醒编译器以C语言的方式生成C++代码。
-
避免使用C++的异常处理:C语言代码无法处理C++的异常
-
写一个适配层,将C++代码封装为C语言的代码接口
-
将被调用函数放入一个独立的源文件中进行编写,并将其链接到应用程序。
-
在调用 C++ 函数时尽量不要涉及 C++ 特有的内容(例如类、模板等),尽可能地使用 C 风格的语言特性,以免出现类型转换问题。
-
确保使用相同版本的编译器和链接器,并且在项目中使用统一的编译选项。
22.C++ 中 struct 和 class 区别?
-
struct的存储结构是栈存储,数据访问效率高。struct是一种值类型,默认的访问权限是public,默认继承方式是public,声明时不可以进行初始化操作。
-
class使用堆进行存储,数据访问效率较低。class是引用类型,默认的访问权限是private,默认的继承方式也是private,声明时就可以进行初始化。class本质上是类型,类型必须被实例化之后,有了一个对象才能被操作。
23.如何防止一个头文件 include 多次?
有2种方法可以防止一个头文件被include多次:
- 使用头文件保护宏(Header Guard):在头文件的开头和结尾分别加上宏定义,防止头文件被多次包含。clion根据cpp文件生成的头文件都会自动使用头文件保护宏。例如:
#ifndef MY_HEADER_H #define MY_HEADER_H // 头文件内容 #endif // MY_HEADER_H
- 使用#pragma once 指令:在头文件的开头加上#pragma once 指令,可以确保头文件只被包含一次。需要注意的是,使用预编译指令#pragma once的优点是更简洁,不需要额外的宏定义,但是不是所有编译器都支持,如gcc和vs都是支持的,bcc则不支持#pragma once这个指令。visual studio如果创建新的头文件会自动使用#pragma once 指令。例如:
#pragma once // 头文件内容
24.lambda表达式的理解,它可以捕获哪些类型?
lambda表达式是C++11引入的一种匿名函数,可以在需要函数对象的地方使用,比如作为函数参数、返回值或赋值给变量等。lambda表达式可以捕获任何外部变量,包括局部变量、全局变量、静态变量等。需要注意的是,按引用捕获时要确保外部变量在lambda表达式执行时仍然有效,避免悬空引用。
lambda表达式可以捕获外部变量,捕获的方式有三种:
-
值捕获:捕获外部变量的值,在lambda表达式创建时拷贝一份外部变量的值。
-
引用捕获:捕获外部变量的引用,在lambda表达式使用时直接引用外部变量。
-
隐式捕获(implicit capture):可以自动捕获一定范围内的外部变量,包括按值捕获和按引用捕获。
int x = 10; auto func = [=](){ return x; }; // 按值捕获所有外部变量 auto func2 = [&](){ return x; }; // 按引用捕获所有外部变量
25.友元friend介绍?
一般在类定义开始的地方集中声明友元函数,在函数前加friend关键字即可。也可以声明友元类。每个类负责控制自己的友元函数和友元类,友元关系不具有传递性,class A 与class B是友元类,class B 与class C是友元类,class A 与class C不一定是友元类。friend使用举例:
#include <iostream>
class A {
private:
int x;
public:
A(int num) : x(num) {}
friend void display(A obj); // 声明友元函数
};
void display(A obj) {
std::cout << "The value of x is: " << obj.x << std::endl;
}
int main() {
A a(10);
display(a); // 友元函数可以访问类的私有成员
return 0;
}
26.move函数 ?
move作用主要是可以将一个左值转换成右值引用,从而可以调用C++11右值引用的拷贝构造函数,在对象拷贝的时候,在运行时,move函数可以减少资源创建和释放。
#include <utility>
int main() {
int x = 42;
int y = std::move(x); // 将 x 强制转换为右值引用,不进行拷贝操作
return 0;
}
27.模版类的作用 ?
模板类的作用:模板类可以用于基本数据结构,在编译时进行数据类型的检查,保证类型安全。模板类是类型无关的,所以具有可移植性和高复用性,可用来创建动态增长和减小的数据结构。模板类允许用户根据需要实例化不同的数据类型,从而适应不同的需求。
#include <iostream>
template <typename T>
class Stack {
private:
T* data; // 存储栈元素的数组
int size; // 栈的大小
int capacity; // 栈的容量
public:
// 构造函数:初始化栈的容量和大小,创建存储数据的数组
Stack(int capacity) : capacity(capacity), size(0) {
data = new T[capacity];
}
// 析构函数:释放动态分配的数组内存
~Stack() {
delete[] data;
}
// 压栈操作:向栈中压入元素
void push(T element) {
if (size < capacity) {
data[size++] = element;
} else {
std::cout << "栈已满,无法压入元素。" << std::endl;
}
}
// 出栈操作:从栈中弹出元素
T pop() {
if (size > 0) {
return data[--size];
} else {
std::cout << "栈为空,无法弹出元素。" << std::endl;
return T(); // 返回默认值
}
}
// 获取栈的大小
int getSize() {
return size;
}
};
int main() {
// 实例化一个存储整数类型的栈
Stack<int> intStack(5);
// 压入元素
intStack.push(1);
intStack.push(2);
intStack.push(3);
// 输出栈的大小
std::cout << "栈的大小: " << intStack.getSize() << std::endl;
// 弹出元素并输出
std::cout << "弹出的元素: " << intStack.pop() << std::endl;
std::cout << "弹出的元素: " << intStack.pop() << std::endl;
return 0;
}
28.模版和泛型的区别?
泛型与 C++ 模板的主要区别:
-
泛型是一种比较广泛的概念,用于实现通用性和灵活性。泛型编程是一种编程范式,旨在编写通用的代码,以便在不同的数据类型或数据结构上重复使用。模板则是C++的一种语言特性,用于实现泛型编程。模板可以是函数模板或者是类模板。函数模板允许编写通用的函数,而类模板允许编写通用的类。
-
泛型在运行时被类型替换前一直都是泛型。 模板在编译时专用化,所以在运行时仍不是参数化类型
-
公共语言运行时在 MSIL 中专门支持泛型。 因为运行时知道泛型,所以在引用包含泛型类型的程序集时,可以用特定类型替换泛型类型。 相比之下,模板在编译时解析为普通类型,所以生成的类型可能不会在其他程序集中专用化。
-
在两个包含相同类型参数的不同程序集中专用化的泛型为相同类型。 运行时将在两个包含相同类型参数的不同程序集中专用化的模板视为不同类型。
-
泛型作为一段可执行代码生成,用于所有引用类型参数(这不适用于值类型,每个值类型都有一个唯一实现)。 JIT 编译器知道泛型,并能为用作类型参数的引用类型或值类型优化代码。 模板为每个专用化单独生成运行时代码。
-
泛型禁止使用非类型模板参数(如
template <int i> C {}
)。 但模板允许使用。 -
泛型禁止使用显式专用化(即特定类型的模板的自定义实现)。 但模板允许使用。
-
泛型禁止使用部分专用化(即部分类型参数的自定义实现)。 但模板允许使用。
-
泛型禁止将类型参数用作泛型类型的基类。 但模板允许使用。
-
模板支持模板-模板参数(例如
template<template<class T> class X> class MyClass
),但泛型不支持。
29.C++的new和malloc的区别?
-
new分配内存是分配在自由存储区(可以是堆,静态存储区,甚至是不分配内存),分配内存时由编译器通过计算得出需要分配的内存大小。new是类型安全的。分配内存失败时会抛出bad_alloc异常。new会调用对象的构造函数/析构函数以完成对象的构造/析构,支持对数据类型的处理(new[])。new是支持函数重载的,标准库中提供了8种可重载的版本。但是new无法直观地扩充内存。
-
//这些版本可能抛出异常 void * operator new(size_t); void * operator new[](size_t); void * operator delete (void * )noexcept; void * operator delete[](void *0)noexcept; //这些版本承诺不抛出异常 void * operator new(size_t ,nothrow_t&) noexcept; void * operator new[](size_t, nothrow_t& ); void * operator delete (void *,nothrow_t& )noexcept; void * operator delete[](void *0,nothrow_t& )noexcept; //这个版本的new不能重载 void * operator new (size_t,void *);
-
malloc分配内存是分配到堆中,分配失败返回void*。分配内存时需要明确指定分配内存的大小,处理数组时需要用户计算数组的大小后进行内存分配,可以灵活实现分配内存的扩充。malloc无法处理异常,不支持函数重载,也不会调用构造函数与析构函数。
30.C++中的map和unordered_map的区别和使用场景 ?他们是线程安全的吗?
-
map是基于红黑树实现的有序关联容器,它会根据键的大小自动进行排序,插入和查找操作的平均时间复杂度为 O(log n),适合需要元素有序存储,并且需要频繁进行插入和删除操作的场景。map的空间占用率高。map不是线程安全的,可以使用shared_mutex保护容器的访问。
-
unordered_map是基于哈希表实现的无序关联容器,它使用哈希函数将键映射到存储桶中,不会自动排序,插入和查找操作的平均时间复杂度为 O(1),适合需要快速查找元素,并且不需要元素有序存储的场景。unordered_map查找速度快,建立哈希表时间长,占用内存高。总的来说是以空间换时间的一种容器。unordered_map也不是线程安全的,可以使用shared_mutex保护容器的访问,也可以用一些第三方的线程安全库如boost thread。