C/C++八股面试题(六)

目录:

1.请你说说动态链接与静态链接两者有什么区别?

2.请简单说说静态成员函数与普通成员函数的区别?

3.简述C++从代码到可执行二进制文件的过程?

4.全局变量和局部变量的区别?

5.C语言的基本类型有哪些(32位系统),占用字节空间?

6.头文件#ifndef/#define/#endif的作用?

内容:

1.请你说说动态链接与静态链接两者有什么区别?

动态链接的基本思想

  • 是把程序按照模块拆分成各个相对独立部分,在程序运行时才将它们链接在一起形成一个完整的程序。

动态链接

  • 从链接时机看:动态链接是在程序运行时完成的,即程序在运行时加载外部的共享库(如 .dll.so 文件)。
  • 从文件大小看:生成的可执行文件较小,因为它只包含调用库的引用,而不包含库的实际代码。
  • 从性能看:动态链接可能会略微影响程序启动速度,因为需要在运行时加载和解析库文件。
  • 从依赖上看:程序在运行时依赖外部的动态库,如果系统中缺少这些库或版本不匹配,程序可能无法正常启动或运行。动态链接的好处是库的更新不需要重新编译应用程序,只要保持接口一致即可。

静态链接的基本思想

  • 我们不可能将所有代码放在一个源文件中,所以会出现多个源文件,而且多个源文件之间不是独立的,而会存在多种依赖关系,如一个源文件可能要调用另一个源文件中定义的函数,但是每个源文件都是独立编译的,即每个.c文件会形成一个.o文件,所以就会有依赖关系,则需要将这些源文件产生的目标文件进行链接,从而形成一个可以执行的程序。这个链接的过程就是静态链接。

静态链接

  • 从链接时机看:静态链接在编译阶段完成,生成的可执行文件中包含了所有依赖库的代码和符号表。
  • 从文件大小看:生成的可执行文件相对较大,因为它包括了所有依赖的库文件。
  • 从性能看:由于库的代码已经被直接包含在可执行文件中,因此程序的加载速度较快,运行时不需要查找和加载外部库。
  • 从依赖上看:程序在运行时不依赖外部的库文件,减少了运行时出现版本不兼容的问题。但如果库有更新,程序需要重新编译。

总结对比

链接时机

编译时

运行时

可执行文件大小

较大

较小

启动速度

较快

较慢(需要加载共享库)

依赖管理

运行时不依赖外部库

运行时依赖外部库

库更新

库更新需要重新编译程序

库更新无需重新编译程序

共享性

每个程序都有独立的库副本

多个程序可以共享相同的库

2.请简单说说静态成员函数与普通成员函数的区别?

访问对象

  • 普通成员函数:普通成员函数是与类的实例(对象)关联的函数。它必须通过对象来调用,并且在函数内部可以访问该对象的所有成员变量和成员函数。在成员函数内部,可以使用this指针来访问对象的成员变量。obj.func()这种调用可以理解为:myClass::func(&obj)
  • 静态成员函数:静态成员函数是与类本身关联的,而不是与类的某个具体对象关联。它可以直接通过类名来调用,不需要先创建对象。

访问成员变量

  • 普通成员函数:可以访问类的所有成员,包括静态成员和非静态成员包括私有成员。
  • 静态成员函数:只能访问静态成员变量和静态成员函数。它不能访问非静态成员变量和非静态成员函数,因为静态成员函数在类的实例化对象未创建时就已经存在,它无法指向某个具体对象。

this 指针

  • 普通成员函数:每个普通成员函数都有一个隐式的this指针,指向当前调用该成员函数的对象。通过this指针,可以访问对象的成员变量和成员函数。
  • 静态成员函数:静态成员函数没有this指针,因为它不是与某个对象关联的。因此,静态成员函数无法通过this指针来访问成员变量和成员函数。

调用方式

  • 普通成员函数:必须通过类的对象来调用,或者通过指针/引用访问。
class MyClass {
public:
    void normalFunc() { // 普通成员函数
        std::cout << "This is a normal function" << std::endl;
    }
};

MyClass obj;
obj.normalFunc();  // 通过对象调用

  • 静态成员函数:可以通过类名直接调用,也可以通过对象调用,但推荐通过类名调用。
class MyClass {
public:
    static void staticFunc() { // 静态成员函数
        std::cout << "This is a static function" << std::endl;
    }
};

MyClass::staticFunc();  // 通过类名调用
MyClass obj;
obj.staticFunc();  // 也可以通过对象调用

总结对比

调用方式

必须通过对象来调用

可以通过类名或对象来调用

访问成员变量

可以访问所有非静态和静态成员

只能访问静态成员,不能访问非静态成员

this

指针

this

指针,指向当前对象

没有

this

指针

适用场景

当需要操作对象的状态或成员时使用

当需要与类本身相关,但不需要访问对象时使用

3.简述C++从代码到可执行二进制文件的过程

预处理

  • 预处理是编译过程的第一步,它的主要任务是处理所有的预处理指令(如 #include#define 等),并生成一个纯净的 C++ 源代码文件。生成一个预处理后的 .i 文件,其中包含了宏替换、头文件内容和注释已去除的代码。

编译

  • 编译阶段的主要任务是将预处理后的源代码转换成汇编语言代码。编译器会对源代码进行语法分析、词法分析、语义分析等,生成对应的汇编语言代码。生成一个汇编语言文件(.s 文件),其中包含了计算机能理解的低级汇编指令。

汇编

  • 汇编阶段将生成的汇编语言代码转化为机器码(对象代码),并生成目标文件(.o.obj 文件)。汇编器将汇编代码转换成机器语言代码,但尚未处理代码中可能存在的外部依赖(如函数调用和全局变量)。生成一个目标文件(.o.obj),它包含机器代码和符号信息,但还未能完全独立执行。

链接

  • 链接是将一个或多个目标文件与所需的库文件(如标准库、第三方库等)合并成一个完整的可执行文件的过程。链接分为两个阶段:静态链接动态链接。生成一个完整的可执行二进制文件(通常是 .exe 文件,在 Unix 系统中是没有扩展名或 .out),此时的程序可以独立运行。

可执行文件

  • 最终,经过链接生成的可执行文件是程序可以运行的形式。它包含了所有的代码、数据和必要的符号信息,操作系统可以将其加载到内存中执行。

总结:

预处理

处理宏定义、头文件包含、条件编译等

预处理后的源代码文件(.i

编译

将源代码转化为汇编语言

汇编语言文件(.s

汇编

将汇编语言转化为机器码

目标文件(.o.obj

链接

合并多个目标文件和库文件,解析符号

可执行文件(如 .exe或无扩展名)

4.全局变量和局部变量的区别?

作用域

  • 全局变量:全局变量是在函数外部声明的变量,它的作用域是整个程序,即在整个源文件或多个源文件中(如果是通过 extern 引用)都可以访问。
  • 局部变量:局部变量是在函数或代码块内部声明的变量,它的作用域仅限于该函数或代码块内,无法在函数外部访问。

例子:

#include <iostream>
int globalVar = 10;  // 全局变量
void func() {
    int localVar = 5;  // 局部变量
    std::cout << "Local variable: " << localVar << std::endl;
    std::cout << "Global variable: " << globalVar << std::endl;
}
int main() {
    func();
    // std::cout << "Local variable: " << localVar << std::endl;  // 错误:无法访问局部变量
    std::cout << "Global variable: " << globalVar << std::endl;  // 正确:全局变量可以在任何地方访问
    return 0;
}

  • globalVar 是全局变量,可以在 func()main() 中访问。
  • localVar 仅在 func() 中可访问,无法在 main() 中访问。

初始化

  • 全局变量:如果全局变量没有显式初始化,它会被自动初始化为类型的零值。例如,int 类型的全局变量默认初始化为 0,指针类型默认初始化为 nullptr
  • 局部变量:局部变量在声明时不会自动初始化。如果局部变量没有显式初始化,它的值是未定义的,使用这些未初始化的变量将导致不可预期的行为。

例子:

#include <iostream>
int globalVar;  // 全局变量,自动初始化为 0
void func() {
    int localVar;  // 局部变量,未初始化
    std::cout << "Global variable: " << globalVar << std::endl;
    std::cout << "Local variable: " << localVar << std::endl;  // 使用未初始化的局部变量是不安全的
}
int main() {
    func();
    return 0;
}

  • func() 中,globalVar 被自动初始化为 0,而 localVar 的值是未定义的,访问它可能导致未定义行为。

存储位置

  • 全局变量:全局变量通常存储在程序的 静态存储区,即程序的全局数据区域。它们的内存分配和释放由操作系统在程序启动时进行,并在程序结束时释放。
  • 局部变量:局部变量存储在 栈(stack) 中,随着函数的调用和返回,栈会相应地分配和释放内存。

生命周期

  • 全局变量:全局变量的生命周期从程序开始时就开始,到程序结束时才结束。也就是说,它在程序运行期间始终存在,并且保持其值直到程序结束。
  • 局部变量:局部变量的生命周期仅限于其所在的函数或代码块的执行期间。当函数或代码块被调用时,局部变量被创建并分配内存;当函数或代码块执行结束时,局部变量被销毁,内存释放。

例子:

#include <iostream>
int globalVar = 10;  // 全局变量
void func() {
    int localVar = 5;  // 局部变量
    std::cout << "Inside func: localVar = " << localVar << std::endl;
}
int main() {
    func();
    // std::cout << "Outside func: localVar = " << localVar << std::endl;  // 错误:无法访问局部变量
    std::cout << "Global variable: " << globalVar << std::endl;
    return 0;
}

  • 在上面的例子中,localVar 仅在 func() 执行时存在,而 globalVar 在程序整个运行期间都存在。

可见性和链接

  • 全局变量:全局变量对所有函数和文件可见。如果它们在多个源文件中被引用,通常需要使用 extern 来声明它们。
  • 局部变量:局部变量只能在其所在的函数或代码块内可见,不会影响到其他函数或代码块。

例子:

// file1.cpp
#include <iostream>
int globalVar = 10;  // 在 file1 中定义全局变量
// file2.cpp
extern int globalVar;  // 使用 extern 声明 file1 中的 globalVar
void func() {
    std::cout << "Global variable from file2: " << globalVar << std::endl;
}

  • globalVarfile1.cpp 中定义,并且通过 externfile2.cpp 中声明,使得 globalVar 在不同文件中可见。

线程安全性

  • 全局变量:全局变量在多线程程序中如果没有正确的同步机制(如互斥锁)可能导致线程安全问题。多个线程同时访问和修改全局变量时,可能会出现竞态条件,导致不一致的结果。
  • 局部变量:局部变量通常是线程安全的,因为每个线程都有自己的局部变量副本,互不干扰。

内存管理

  • 全局变量:全局变量通常由操作系统自动管理,它们的生命周期持续到程序结束。内存空间通常不会被回收,直到程序退出。
  • 局部变量:局部变量的内存由栈管理,当函数调用时,局部变量的内存被分配,当函数返回时,内存被销毁。

总结

作用域

整个程序(可以跨多个文件)

仅限于函数或代码块内

生命周期

从程序开始到程序结束

从函数调用开始,到函数结束

存储位置

静态存储区(全局数据区)

栈(函数调用栈)

初始化

默认初始化为零值(如 0

不自动初始化,使用前必须初始化

可见性

可以通过 extern 引用到其他文件

仅在声明的函数或代码块内可见

线程安全性

非线程安全(需要同步机制)

线程安全(每个线程有独立副本)

内存管理

由操作系统管理,直到程序退出

由栈管理,随着函数调用结束自动销毁

5.C语言的基本类型有哪些(32位系统),占用字节空间?

数据类型总结

char

1 字节

存储单个字符,通常有符号或无符号

short

2 字节

存储较小整数(-32,768 到 32,767)

int

4 字节

存储整数(-2,147,483,648 到 2,147,483,647)

long

4 字节

存储较大整数,范围与 int 相同

long long

8 字节

存储更大的整数,范围 -9E18 到 9E18

float

4 字节

存储单精度浮点数(6-7 位有效数字)

double

8 字节

存储双精度浮点数(15-16 位有效数字)

long double

8 字节或更多

存储高精度浮点数,具体依平台

void

无大小

表示无类型,如函数返回类型 void

6.头文件#ifndef/#define/#endif的作用?

在 C 和 C++ 编程中,#ifndef#define#endif 是常用的预处理指令,用于实现 条件编译头文件保护。这三者通常一起使用,确保头文件在程序中只被包含一次,避免重复定义和编译错误。

#ifndef

  • #ifndef 它检查给定的宏是否没有被定义,如果没有定义,则执行随后的代码。如果宏已经被定义,则跳过这些代码。

#define

  • #define 用于定义宏(宏常量或宏函数)。宏通常在代码中进行替换,或者用于条件编译控制。此指令的作用是将一个标识符(宏名)与某个值关联起来。

#endif

  • #endif 用来结束 #if#ifdef#ifndef 条件编译块。当条件成立时,包含在这些指令之间的代码才会被编译。

最常见的用法是在头文件中防止头文件的多重包含。多重包含可能导致重复定义和链接错误,特别是在大型项目中,多个源文件可能包含同一个头文件。为了避免这种情况,通常使用 #ifndef#define 来防止头文件被重复包含。

例子:

#ifndef MY_HEADER_H    // 如果 MY_HEADER_H 没有被定义
#define MY_HEADER_H    // 定义 MY_HEADER_H

// 头文件的内容
void myFunction();

#endif  // 结束条件编译

  • #ifndef MY_HEADER_H:检查 MY_HEADER_H 是否已被定义。如果没有定义,继续执行下面的代码。
  • #define MY_HEADER_H:定义 MY_HEADER_H,表示该头文件已经被处理过了。
  • #endif:结束 #ifndef 条件编译块。

这样做的结果是,每个头文件只会被包含一次。即使该头文件被多个源文件或其他头文件包含,也只会执行一次,这避免了重复定义和链接错误。

工作原理:

  • 当编译器第一次处理该头文件时,MY_HEADER_H 并没有被定义,因此会进入 #ifndef 的代码块。
  • 在代码块内,#define MY_HEADER_H 被定义,此时 MY_HEADER_H 已经存在。
  • 如果该头文件在后续的编译过程中再次被包含,编译器会发现 MY_HEADER_H 已经定义过了,#ifndef 条件为假,头文件内的代码不会被再次处理,从而避免了重复定义。

#牛客激励计划##面经##面试##嵌入式##C/C++#
嵌入式/C++八股 文章被收录于专栏

本人双飞本,校招上岸广和通。此专栏覆盖嵌入式常见面试题,有C/C++相关的知识,数据结构和算法也有嵌入式相关的知识,如操作系统、网络协议、硬件知识。本人也是校招过来的,大家底子甚至项目,可能都不错,但是在面试准备中常见八股可能准备不全。此专栏很适合新手学习基础也适合大佬备战复习,比较全面。最终希望各位友友们早日拿到心仪offer。也希望大家点点赞,收藏,送送小花。这是对我的肯定和鼓励。 持续更新中

全部评论
欢迎大家订阅此专栏,订阅点赞收藏送花后,截图si聊我,免费赠送嵌入式学习资料大礼包(含简历模版、c/c++、嵌入式等资料)
4 回复 分享
发布于 12-14 15:53 陕西
问个问题,那静态成员可以访问非静态成员吗,求解答
1 回复 分享
发布于 12-14 18:51 陕西
加更加更
1 回复 分享
发布于 12-15 22:02 陕西

相关推荐

1.&nbsp;C++中的移动语义是什么?它有什么优势?2.&nbsp;解释一下C++中的构造函数和析构函数的作用。3.&nbsp;C++中的虚函数和纯虚函数有什么区别?4.&nbsp;什么是C++中的命名空间污染?如何避免?5.&nbsp;C++中的类型转换操作符有哪些?它们的作用是什么?6.&nbsp;解释一下C++中的智能指针的使用场景和优势。7.&nbsp;C++中的多态性如何实现?请举例说明。8.&nbsp;什么是C++中的模板元编程?它有什么应用?9.&nbsp;C++中的std::vector和std::list有什么区别?10.&nbsp;解释一下C++中的异常安全性及其分类。11.&nbsp;C++中的类型擦除是什么?它是如何实现的?12.&nbsp;如何在C++中实现一个线程安全的单例模式?13.&nbsp;C++中的内存管理策略有哪些?各自的优缺点是什么?14.&nbsp;解释一下C++中的函数对象(functor)及其用途。15.&nbsp;C++中的std::map和std::unordered_map有什么区别?16.&nbsp;什么是C++中的右值引用?它有什么作用?17.&nbsp;C++中的constexpr关键字有什么用?如何使用?18.&nbsp;解释一下C++中的std::variant及其使用场景。19.&nbsp;C++中的析构函数在多重继承中的工作机制是什么?20.&nbsp;如何在C++中实现一个简单的状态机?21.&nbsp;C++中的std::string和C风格字符串有什么区别?22.&nbsp;解释一下C++中的原子操作和内存序的概念。23.&nbsp;C++中的std::function是什么?它的用途是什么?24.&nbsp;什么是C++中的协程?它的优势是什么?25.&nbsp;C++中的std::deque和std::array有什么区别?26.&nbsp;解释一下C++中的模板参数推导。27.&nbsp;C++中的std::atomic是什么?它的作用是什么?28.&nbsp;如何在C++中实现一个简单的观察者模式?29.&nbsp;C++中的强制类型转换和隐式类型转换有什么区别?30.&nbsp;解释一下C++中的设计模式及其常见类型。答案附在面经中&nbsp;&nbsp;c++/嵌入式面经专栏-牛客网 https://www.nowcoder.com/creation/manager/columnDetail/MJNwoM
点赞 评论 收藏
分享
评论
9
13
分享
牛客网
牛客企业服务