梦见你的名字 发表于 2024-10-24 06:59:49

C++面试速通宝典——29

543. 简述#ifdef、#else、#endif和#ifndef的作用

使用#ifdef、#endif将程序功能模块包括进去,以向特定用户提供该功能。
在不需要时用户可轻易将其屏蔽。
#ifdef MATH
#include "math.c"
#endif
在子程序前加上标记,以便于追踪和调试。
#ifdef DEBUG
printf ("Indebugging......!");
#endif


‌‌‌‌  应对硬件的限制。由于一些具体应用环境的硬件不一样,限于条件,当地缺乏这种装备,只能绕过硬件,直接写出预期结果。
‌‌‌‌  注意:虽然不消条件编译命令而直接用if语句也能到达要求,但那样做目标程序长(由于全部语句都编译),运行时间长(由于在程序运行时间对if语句举行测试)。而采用条件编译,可以减少被编译的语句,从而减少目标程序的长度,减少运行时间。
表明:
在C/C++编程中,#ifdef、#else、#endif 和 #ifndef 是常见的预处理指令(preprocessor directives),用于条件编译。它们的作用如下:

[*] #ifdef(If Defined):

[*]作用:检查某个宏(即预处理器定义的符号)是否已经被定义。
[*]用法:#ifdef MACRO_NAME
[*]如果 MACRO_NAME 已经被定义(通常使用 #define 定义),那么从 #ifdef 到下一个 #else 或 #endif 之间的代码会被编译。

#define DEBUG
#ifdef DEBUG
printf("Debugging is enabled.\n");
#endif


[*] 如果 DEBUG 被定义了,printf 语句就会被编译。
[*] #else:

[*]作用:作为 #ifdef 或 #ifndef 的条件分支,用于在前面的条件不满足时实行另一部分代码。
[*]用法:#else
[*]如果前面的 #ifdef 或 #ifndef 判断条件不成立,那么从 #else 到 #endif 之间的代码会被编译。

#ifdef DEBUG
printf("Debugging is enabled.\n");
#else
printf("Debugging is disabled.\n");
#endif


[*] 如果 DEBUG 没有被定义,那么 printf("Debugging is disabled.\n"); 就会被编译。
[*] #endif:

[*]作用:竣事一个条件编译块。
[*]用法:#endif
[*]它与 #ifdef、#ifndef、#else 等指令配对使用,标志着条件编译的竣事。

#ifdef DEBUG
printf("Debugging is enabled.\n");
#endif



[*] #endif 竣事了从 #ifdef DEBUG 开始的条件编译块。
[*] #ifndef(If Not Defined):

[*]作用:检查某个宏是否未被定义。
[*]用法:#ifndef MACRO_NAME
[*]如果 MACRO_NAME 没有被定义,那么从 #ifndef 到 #else 或 #endif 之间的代码会被编译。

#ifndef RELEASE
printf("This is a debug build.\n");
#endif

如果 RELEASE 没有被定义,printf 语句就会被编译。
544. 布局体可以直接赋值么?

‌‌‌‌  声明时可以直接初始化,同一布局体的不同对象之间也可以直接赋值,但是当布局体中含有指针“成员”时,肯定要警惕。
‌‌‌‌  注意:当有多个指针指向同一段内存时,某个指针开释这段内存可能会导致其他指针的非法操纵。因此在开释之前肯定要确保其他指针不再使用这段内存空间。
545. 一个参数可以既是const又是volatile么

可以。用const和volatile同时修饰变量,表示这个变量在程序内部是可读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量。
每次使用这个变量时,都要警惕的去内存读取这个变量的值,而不是去寄存器读取他的备份。
注意:再次肯定要注意const的意思,const不答应程序中的代码改变程序中的某一变量,其在编译期间发挥作用,并没有实际地禁止某段内存的读写特性。
546. 布局体内存对齐问题

#include<stdio.h>
struct S1 { int i:8; char j:4; int a:4; double b; };
struct S2 { int i:8; char j:4; double b; int a:4; };
struct S3 { int i; char j; double b; int a; };

int main() { printf("%d\n",sizeof(S1)); // 输出8
                  printf("%d\n",sizeof(S1); // 输出12
                  printf("%d\n",sizeof(Test3)); // 输出8
                  return 0;
}
sizeof(S1)=16
sizeof(S2)=24
sizeof(S3)=32


表明:
位域(Bit-fields
位域答应在布局体中定义占用特定位数的成员,这在节流内存或实现硬件寄存器映射时非常有用。位域的存储和对齐规则有以下几点:

[*] 存储单位:位域通常会被存储在其声明类型的存储单位中。比方,int i:8; 会被存储在一个 int 类型的存储单位中(通常4字节)。
[*] 位域的对齐:位域成员依照其声明类型的对齐要求。比方,int 类型的位域成员需要4字节对齐。
[*] 位域的打包:多个位域成员如果在同一个存储单位内,可以精密打包。比方,int i:8; char j:4; int a:4; 可以在同一个4字节的 int 存储单位内打包存储。
547. 请解析((void()())0)()的寄义


[*] void(* 0)():是一个返回值为void,参数为空得函数指针0.
[*] (void(* )())0:把0转换成一个返回值为void,参数为空的函数指针。
[*] (void()())0:在上句的根本上加* 表示整个是一个返回值为void,无参数,并且起始所在为0的函数的名字。
[*] ((void()())0)():这就是上句的函数名对应的函数的调用。
[*] 函数指针根本:

[*]void (*)() 是一个函数指针的声明,表示它指向一个返回值为 void 且不接受任何参数的函数。

[*] (void( )())0*:

[*]0 这里被强制转换为一个函数指针,类型为 void(*)()。
[*]也就是说,0 被认为是一个指向返回值为 void 且不接受参数的函数的指针。

[*] (void()())0:

[*]这个表达式其实和上面的一样,* 在这里是可选的(由于我们是在做类型转换),所以 void(*)() 和 void()() 是等价的。
[*]本质上,这一步是将 0 转换为一个函数指针类型。

[*] ((void()())0)():

[*]((void()())0) 是一个函数指针,它指向内存所在 0。
[*]在后面加上 () 意味着我们尝试去调用这个位于所在 0 的函数。

简化明白:



[*]((void()())0)() 这段代码尝试调用一个位于内存所在 0 的函数。
[*]在大多数系统中,所在 0 是无效的,不会指向任何有效的代码。因此,如许的调用通常会导致程序崩溃或产生未定义行为。
这个表达式展示了一种高级且危险的类型转换和函数指针的使用,但在实际应用中,这种操纵通常是不安全的,并且会导致严重的错误。
548. C语言的布局体和C++的类有什么区别


[*]C语言的布局体是不能有函数成员的,而C++的类可以有。
[*]C语言中的布局体中的数据成员是没有private、public、protected访问限定的。而C++的类的成员有这些访问限定。
[*]C语言的布局体是没有继承关系的,而C++的类却有丰富的继承关系。
注意:虽然C的布局体和C++的类有很大的相似度,但是类是实现面向对象的根本。而布局体只可以简单地明白为类的前身。
549. 句柄和指针的区别和联系是什么?

‌‌‌‌  句柄和指针其实是两个大相径庭的概念。
‌‌‌‌  Windows系统用句柄标记系统资源,隐藏系统的信息。
‌‌‌‌  你只要知道有这个东西,然后去调用就行了,他是个32bit的uint。指针则标记某个物理内存所在,两者是不同的概念。
表明:
‌‌‌‌  句柄(Handle)和指针(Pointer)在计算机编程中是两个不同的概念,但它们在某些情况下可以有相似的作用。下面是它们的区别和联系:
指针(Pointer)



[*]定义: 指针是一个变量,它存储的是另一个变量的内存所在。通过指针,你可以直接访问或操纵该所在上的数据。
[*]作用: 指针用于直接操纵内存中的数据,可以用来动态分配内存、遍历数组、实现函数指针等。
[*]类型安全: 指针是类型安全的,它知道本身指向的是什么类型的数据(比方 int* 指针只能指向整数类型)。
[*]直接内存访问: 使用指针可以直接访问和修改内存中的数据,这在某些情况下非常高效。
句柄(Handle)



[*]定义: 句柄是一种抽象化的概念,通常是一个整数或指针,用于引用系统资源(如文件、窗口、线程等),而无需了解这些资源的具体内存所在或内部布局。
[*]作用: 句柄用于操纵复杂系统资源,提供一种间接的访问方式。操纵系统或程序通过句柄来管理和操纵这些资源,而不暴露具体的内存所在。
[*]抽象层次: 句柄比指针更加抽象。它是系统或库提供给用户的一种资源标识符,而用户无法直接操纵句柄指向的具体内存或数据布局。
[*]类型安全: 句柄通常没有类型安全的保障,尤其在操纵系统或库中,一个句柄可能代表不同类型的资源(比方文件句柄和窗口句柄),但它们通常是不同类型的标识符,使用不当会导致错误。
联系



[*]间接访问: 在某些情况下,句柄可以被看作是指针的一个高级封装或抽象。它们都用于间接地访问或操纵数据或资源。
[*]资源管理: 指针和句柄都可以用于管理资源。在某些语言或系统中,句柄实际上可能是一个指向特定布局体的指针,只是对用户隐藏了直接内存操纵的细节。
[*]性能与安全: 指针通常提供更高的性能,由于它答应直接操纵内存,但也带来了更大的安全风险。句柄则更安全,因其隐藏了内存细节,但可能在某些情况下牺牲了一些性能。
总结

‌‌‌‌  指针和句柄的重要区别在于抽象层次:指针直接指向内存所在并操纵数据,而句柄则是对资源的一个间接引用,更加抽象和安全。指针用于需要直接内存操纵的场所,而句柄用于需要管理复杂资源而不盼望暴露底层实现的场所。
550. C++类内可以定义引用数据成员么?

‌‌‌‌  可以,必须通过成员函数初始化列表初始化。
表明:
‌‌‌‌  在C++中,类内不能直接定义引用(reference)类型的数据成员,但你可以定义引用类型的成员变量,并在构造函数的初始化列表中对其举行初始化。引用必须在定义时被初始化,因此它们不能在类内直接定义时初始化,而必须在构造函数中完成。
#include <iostream>

class MyClass {
public:
    int& ref;// 声明一个引用类型的数据成员

    // 构造函数,通过初始化列表对引用类型的数据成员进行初始化
    MyClass(int& r) : ref(r) {}
   
    void print() const {
      std::cout << "ref: " << ref << std::endl;
    }
};

int main() {
    int a = 10;
    MyClass obj(a);// 创建对象时传递引用
    obj.print();// 输出:ref: 10

    a = 20;
    obj.print();// 输出:ref: 20,引用仍然指向原始变量

    return 0;
}


[*] 定义引用类型数据成员: 在 MyClass 类中,int& ref 是一个引用类型的数据成员。
[*] 构造函数中的初始化列表: 由于引用必须在定义时初始化,MyClass 的构造函数通过初始化列表的方式将 ref 初始化为外部传递的引用 r。
[*] 引用的作用: 当 ref 被初始化后,它始终引用传递给它的那个变量(在这个例子中是 a),因此无论 a 如何改变,ref 都会反映出 a 的最新值。
注意事项



[*]引用必须在定义时初始化,因此你不能像其他类型的数据成员那样在类的构造函数中赋值,只能在初始化列表中完成。
[*]引用一旦被初始化,就不能再指向其他对象。
551. C++中类成员的访问权限

C++通过public、protected、private三个关键字来控制成员变量和成员函数的访问权限,它们分别表示公有的、受掩护的、私有的,被称为成员访问限定符。
在类的内部(定义类的代码内部),无论成员被声明为public、protected、还是private,都是可以互相访问的,没有访问权限的限制。
在类的外部(定义类的代码之外),只能通过对象访问成员,并且通过对象只能访问public属性的成员,不能访问protected和private属性的成员。
552. C++中的四种转换

C++中的四种类型转换是:static_cast、dynamic_cast、const_cast、reinterpret_cast。
1. static_cast



[*]用途: static_cast 是一种最常用的显式类型转换方式,用于在相关类型之间转换。它可以用于将基类指针或引用转换为派生类指针或引用,前提是这种转换在编译时是安全的。它也可以用于基本数据类型之间的转换,比方 int 转 float,以及指针类型之间的转换。
[*]限制: static_cast 在编译时举行检查,但它不举行运行时检查,因此在不安全的转换情况下,使用 static_cast 可能会导致未定义行为。
class Base {};
class Derived : public Base {};

Base* base = new Derived;
Derived* derived = static_cast<Derived*>(base);// 合理的 static_cast

2. dynamic_cast



[*]用途: dynamic_cast 通常用于在继承层次中举行安全的向下转换,即将基类指针或引用转换为派生类指针或引用。dynamic_cast 在运行时举行类型检查,如果转换不安全(比方转换的对象不是目标派生类类型),它会返回 nullptr(对于指针)或抛出 std::bad_cast 非常(对于引用)。
[*]要求: dynamic_cast 只实用于多态类(即具有虚函数的类),由于它依赖于运行时的类型信息(RTTI)。
class Base {
    virtual void foo() {}// 多态基类
};
class Derived : public Base {};

Base* base = new Base;
Derived* derived = dynamic_cast<Derived*>(base);// 返回 nullptr,转换失败

3. const_cast



[*]用途: const_cast 用于修改类型的 const 或 volatile 属性。它可以将 const 对象转换为非 const,从而答应对其举行修改。这种转换通常用于需要对 const 数据举行修改的场景,但这种操纵需要谨慎,由于修改常量数据可能会导致未定义行为。
[*]限制: const_cast 不能用于移除 const 之外的类型转换,只能用于修改对象的常量性。
const int a = 10;
int* p = const_cast<int*>(&a);
*p = 20;// 未定义行为,因为 a 本来是 const 的

4. reinterpret_cast



[*]用途: reinterpret_cast 用于举行低级别的重新表明类型转换,重要用于不同类型的指针之间的转换,或将指针转换为整数类型(以及反过来)。这种转换通常不考虑类型的实际内容,只是简单地重新表明比特模式。它实用于需要对内存或底层表示举行直接操纵的场景。
[*]限制: reinterpret_cast 可能非常危险,由于它完全绕过了类型系统的安全性检查。错误使用可能导致严重的错误或未定义行为。
int a = 65;
char* p = reinterpret_cast<char*>(&a);
std::cout << *p << std::endl;// 输出 'A',将整数的内存重新解释为字符

总结



[*]static_cast: 编译时转换,用于相关类型之间的转换。
[*]dynamic_cast: 运行时转换,用于安全的向下转换,多用于继承层次中的多态类。
[*]const_cast: 移除或添加 const 属性,用于修改常量性。
[*]reinterpret_cast: 低级别的类型重新表明,用于不同类型之间的位模式转换。
这些类型转换运算符各有其特定的应用场景,在实际编程中应谨慎使用,尤其是 reinterpret_cast 和 const_cast,以避免引发难以发现的错误。
553. 说一下静态成员变量

‌‌‌‌  静态成员变量是类的一个成员,他被全部对象共享,不属于任何单独实例。静态成员变量有以下特点:

[*]在类的全部对象之间共享。
[*]即使没有创建类的对象,静态成员变量也存在。
[*]必须在类的外部举行初始化(通常在类的实现文件中)。
[*]可以通过类名加作用域解析运算符(::)来访问,无需对象实例。
554. 静态成员变量在什么时间初始化

‌‌‌‌  静态成员变量在程序开始时,即在main()函数实行之前就由运行时系统初始化。他们通常在类的实现文件中举行初始化,只初始化一次。
555. 说一下堆排序

堆排序是一种基于比较的排序算法,使用二叉堆数据布局来实现。它包括两个重要步骤:

[*]构建堆:将无序数组构建成一个最大堆(或最小堆),确保全部非叶子节点都依照堆的性质。
[*]排序:依次删除堆顶元素(最大元素或最小元素),并将其移动到数组末尾,然后调整剩余元素以保持堆的性质,直到堆为空。
堆排序的时间复杂度为O(n log n),不是稳固排序。
不稳固:堆快希直选
稳固 :基冒直折归
556. 尚有哪些排序算法


[*]冒泡排序:通过重复交换相邻逆序元素,使得较小(或较大)元素渐渐浮到顶端。
[*]选择排序:逐个找出未排序部分的最大(最小)元素,放到已排序序列的末尾。
[*]插入排序:取未排序区间中的元素,在已排序序列中从前向后扫描找到相应位置并插入。
[*]快速排序:选取基准值,将数组分为大于和小于基准值两部分,递归地对这两部分举行快速排序。(把小于基准的放到基准前面)
[*]归并排序:将数组分成两半,对每一半递归地举行归并排序,然后将两个有序地部分归并成一个。
[*]希尔排序:是插入排序的一种更高效的改进版本,通过比较距离较远的元向来减少元素的移动次数。
[*]计数排序:使用数组下标统计元素的出现次数,实用于肯定范围内的整数排序。
[*]基数排序:根据数字的有效位或基数将整数分布到桶中,集合各个桶的内容得到有序序列,实用于非负整数。
[*]桶排序:将数组分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归的方式继续使用桶排序举行排序)。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: C++面试速通宝典——29