【C++八股-第六期】C++基础 ② - 24年春招特
提纲:
👉 八股:
为什么C++在编译时会考虑函数的参数类型而C语言则不会
在C++中,用于导入C函数的关键字是什么?请解释extern "C"的作用和使用场景
const关键字可以用于修饰函数的哪三个位置,并解释每个位置的含义和影响。
解释
const int *a
int const *a
const int a
int *const a
const int *const a
并详细说明每个声明的特点和使用场景。静态变量什么时候进行初始化?请详细说明不同情况静态变量的初始化时机和过程
简单介绍一下 static关键字 的作用
请介绍一下 volatile和mutable
说一下 volatile 和 mutable 的应用场景
👉 代码:
1. 为什么C++在编译时会考虑函数的参数类型而C语言则不会
编译区别
:由于C++支持函数重载,因此编译器编译函数的过程中会将函数的参数类型也加到编译后的代码中,而不仅仅是函数名;而C语言并不支持函数重载,因此编译C语言代码的函数时不会带上函数的参数类型,一般只包括函数名。
拓展(了解即可):
C 语言中,函数参数类型并不会在编译时被强制检查。函数的声明和定义只需要匹配参数的数量和顺序,而不需要确切匹配参数的类型。这意味着你可以在函数调用时传递不同类型的参数,而编译器并不会发出警告或错误,虽然这可能导致运行时错误。
C++引入了强类型的概念,即在编译时对类型进行更严格的检查。这是为了提供更安全的编程环境,减少在运行时发生类型相关的错误的可能性。在 C++ 中,函数的声明和定义必须精确匹配参数的数量、顺序和类型。如果不匹配,编译器将发出错误,阻止程序的编译。
总的来说,C++ 引入了更严格的类型检查,以帮助开发人员在编译时捕获潜在的错误,提高代码的健壮性和可维护性。而 C 语言则更注重灵活性和效率,允许开发人员更多地自行管理类型。
2.在C++中,用于导入C函数的关键字是什么?请解释extern "C"的作用和使用场景
在C++中,导入C函数的关键字是 extern
,表达形式为 extern "C"
, extern "C"
的作用是告诉C++编译器按照C语言的函数命名规则和链接规则对函数进行处理,以确保C++代码可以正确调用C语言的函数。
// 声明一个C语言风格的函数
extern "C" {
void c_function(int arg);
}
int main() {
// 在C++代码中调用C语言风格的函数
c_function(42);
return 0;
}
extern "C" 的使用场景通常包括:
-
与C语言库进行链接:
- 当你需要在C++代码中使用C语言编写的库时,使用 extern "C" 可以确保正确的链接方式,使得C++代码能够调用C语言库中的函数。
-
与其他编程语言进行交互:
- 当你的C++代码需要与其他编程语言进行交互时,例如与C、Fortran等语言进行混合编程,使用 extern "C" 可以确保函数调用的兼容性。
拓展(了解即可):
在C++中,默认情况下,函数的名称会经过一种称为名称修饰(name mangling)的过程,以包含函数的参数类型和个数等信息。这种名称修饰使得C++能够支持函数的重载和其他特性,但也导致了与C语言的函数命名不同的问题。
使用 extern "C" 可以告诉编译器,被该关键字修饰的函数使用C语言的命名和链接规则,而不进行C++的名称修饰。这对于在C++代码中调用C语言库或在C++代码中与C语言代码进行链接非常有用。
3. const关键字可以用于修饰函数的哪三个位置,并解释每个位置的含义和影响
const
关键字在C++中可以用于修饰函数的三个位置:函数参数、函数返回类型和函数体内部。
- 修饰函数参数:
void exampleFunction(const int x) {
// 函数体
}
这表示参数 x 是只读的,函数内部不能修改参数的值。这对于防止在函数内部无意间修改传入的参数很有用,并增加了代码的安全性。
- 修饰函数返回类型:
const int exampleFunction() {
// 函数体
return 42;
}
这表示函数的返回值是一个常量,即返回的值不能被修改。在调用函数后,返回值不能用于修改其他变量的值。这样的设计可以提高代码的安全性和可读性。
- 修饰函数体内部:
void exampleFunction() const {
// 函数体
}
这表示函数内部不能修改任何类的成员变量。这种用法通常出现在类的成员函数中,表明该函数不会修改调用对象的状态。这是C++中对于常量成员函数的一种声明方式,用于在编译时检查对成员变量的修改行为。
总结:
const 的使用有助于提高代码的可读性、可维护性和安全性,通过明确指定在哪些地方不允许修改数据,帮助程序员避免一些常见的错误。
4. 解释 const int *a
int const *a
const int a
int *const a
const int *const a
并详细说明每个声明的特点和使用场景
-
const int a
; //指的是a是一个常量,不允许修改。使用场景:适用于需要传递常量整数给函数,或者在函数中不希望修改传入指针所指向整数的情况。
-
const int *a
; //a指针所指向的内存里的值不变,即(*a)不变使用场景:与
const int *a
的使用场景相同,用于表示指向常量整数的指针。 -
int const *a
; //同const int *a;使用场景:适用于需要声明一个常量整数,其值在初始化后不会改变的情况。
-
int *const a
; //a指针所指向的内存地址不变,即a不变使用场景:适用于需要确保指针不会在后续代码中指向其他地址的情况,但仍然需要通过指针修改所指向的整数的值。
-
const int *const a
; //都不变,即(*a)不变,a也不变使用场景:适用于需要同时保护指针不被修改并且保护所指向的整数不被修改的情况。
5. 静态变量什么时候进行初始化?请详细说明不同情况静态变量的初始化时机和过程
全局静态变量在程序加载时初始化,而局部静态变量在首次进入声明它的函数时初始化。这种初始化方式确保了在程序执行期间静态变量的一致性和稳定性。
-
全局静态变量:
-
初始化时机: 全局静态变量在程序开始执行之前进行初始化。它们存储在数据段(data segment)中,并在程序加载时就分配了内存。初始化值为零或根据指定的初始值进行初始化。
-
初始化过程: 如果全局静态变量没有显式初始化,它们将被默认初始化为零。如果有显式初始化,初始化过程将在程序开始执行之前完成。在全局作用域中,这些变量是持久的,它们的生命周期贯穿整个程序的执行过程。
// 全局静态变量的声明和初始化 static int globalStaticVar = 42;
-
-
局部静态变量:
-
初始化时机: 局部静态变量在首次进入声明它的函数时进行初始化。与全局静态变量不同,它们存储在程序的堆栈中,但在程序的整个生命周期内都保持存在。
-
初始化过程: 局部静态变量只在第一次进入声明它的函数时进行初始化,以后的函数调用将跳过初始化步骤。初始化发生在函数第一次执行到变量声明的那一行。如果没有显式提供初始值,局部静态变量将被默认初始化为零。
// 函数内的局部静态变量声明和初始化 void exampleFunction() { static int localStaticVar = 10; // ... }
-
6. 简单介绍一下 static关键字 的作用
-
隐藏:
-
函数的隐藏: 在函数返回类型前加上 static 关键字,使得该函数只能在当前源文件中使用,无法被其他文件调用。这有助于避免命名冲突,允许在不同文件中定义同名函数而不担心冲突。
-
变量的隐藏: 在全局变量前加上 static,使得该全局变量的作用域仅限于当前源文件,无法被其他文件访问。这有助于实现模块化,避免全局变量在不同文件之间产生冲突。
-
-
保持变量内容的持久性:
- 静态变量存储在静态数据区,它在程序开始运行时就完成初始化,且只初始化一次。加上 static 关键字的全局变量和局部变量都具备这一属性,确保在程序生命周期内保持其前值。
-
默认初始化为0:
- 在静态数据区,内存中所有的字节默认值都是 0x00。因此,没有显式初始化的全局静态变量会被默认初始化为0。
-
在类中定义静态成员变量和静态成员函数:
-
静态成员变量: 类中的静态成员变量具有全局静态变量的特性,它们存储在静态数据区,被所有类的对象所共享。
-
静态成员函数: 静态成员函数不属于任何对象,而是属于类的一部分。它可以通过类名调用,而不需要创建类的实例。静态成员函数不能访问非静态成员变量和函数。
-
7. 请介绍一下 volatile和mutable
volatile:
volatile 关键字用于告诉编译器,被声明为 volatile 的变量可能会在程序的执行过程中被意外地更改,因此编译器不应该对其进行某些优化。编译器每次会从内存里重新读取这个变量的值,而不是从寄存器里读取。
编译器优化会导致读值时优先从寄存器里读值,在多线程编程中,变量的值在内存中可能已经被修改,此时编译器的值并不是最新值。
mutable:
mutable 关键字是为了突破const的限制而设置的。被mutable修饰的变量,将永远处于可变的状态,mutable在类中只能够修饰非静态数据成员。 通常,const 成员函数被认为不会修改类的成员变量,但通过 mutable 关键字可以打破这一规则。
8. 说一下 volatile 和 mutable 的应用场景
volatile:
硬件交互: 当变量表示硬件寄存器或者与硬件相关的信息时,使用 volatile 告诉编译器不要对这个变量进行优化,以确保每次访问都是实际的读取和写入。
volatile int* hardwareRegister = /* 地址 */;
多线程环境: 在多线程环境中,一个线程可能修改了某个变量,而另一个线程在没有 volatile 的情况下可能会从缓存中读取旧值,导致数据不一致。
volatile int sharedVariable = 0;
mutable:
缓存值: 当类的某个成员变量需要被缓存,即使在 const 成员函数中也需要修改,可以使用 mutable 修饰。
class MyClass {
private:
mutable int cachedValue;
public:
int getValue() const {
if (!isCached) {
cachedValue = calculateValue(); // 修改成员变量,但是被 mutable 允许
isCached = true;
}
return cachedValue;
}
};
日志记录: 当需要在 const 成员函数中记录日志,而日志记录涉及修改某些计数器等信息时,可以使用 mutable。
class Logger {
private:
mutable int logCount;
public:
void logMessage() const {
logCount++;
// 记录日志的代码
}
};
总结:
volatile 用于告知编译器不要对变量进行优化,适用于与硬件交互或者多线程环境下;而 mutable 用于修饰类的成员变量,允许在 const 成员函数中修改这些成员变量的值,适用于一些特殊场景,如缓存值或记录日志。