自学内容网 自学内容网

面试速通宝典——8

164. 不能做switch的参数的类型是?

‌‌‌‌  switch的参数不能是实类。

解释
‌‌‌‌  在C/C++编程语言中,switch语句是用于进行多分支选择的一种控制结构。switch语句只能用于某些特定类型的表达式。以下是不能用作switch语句条件的参数类型:

1. 浮点类型 (floatdoublelong double)

‌‌‌‌  浮点数由于表示精度和比较的复杂性,不能用作switch语句的参数。

  float f = 3.14;
  switch (f) {
      // 编译错误:`switch`表达式的类型无效
  }

2. 字符串类型

‌‌‌‌  字符串本质上是字符数组或指向字符数组的指针,在比较时涉及复杂的内存地址和内容比较,因此也不能用作switch语句的参数。

  std::string str = "hello";
  switch (str) {
      // 编译错误:`switch`表达式的类型无效
  }

3. 布尔类型 (bool)

‌‌‌‌  布尔类型通常用于简单的条件判断,尽管其值只有truefalse两个,但不能用作switch语句的参数。

  bool b = true;
  switch (b) {
      // 编译错误:`switch`表达式的类型无效
  }

4. 指针类型

‌‌‌‌  指针类型指向内存地址,比较指针涉及内存地址而不是值,因此不能用作switch语句的参数。

  int *ptr = nullptr;
  switch (ptr) {
      // 编译错误:`switch`表达式的类型无效
  }

5. 枚举外的自定义类型

‌‌‌‌  除了枚举(Enum)以外的自定义类型(如结构体、类、联合体)不能直接用于switch语句。

  struct MyStruct {
      int a;
  };

  MyStruct s = {1};
  switch (s) {
      // 编译错误:`switch`表达式的类型无效
  }

有效的switch参数类型

1. 整数类型 (intcharshortlonglong long)

‌‌‌‌  任何可以隐式转换为整数的类型都可以用作switch语句的参数。这也包括charunsigned charshortunsigned short等。

  int a = 2;
  switch (a) {
      case 1:
          // Some code
          break;
      case 2:
          // Some code
          break;
      default:
          // Some code
          break;
  }
2. 枚举类型

‌‌‌‌  枚举类型是可以用作switch语句参数的,因为枚举值实质上是整数。

  enum Colors { RED, GREEN, BLUE };
  Colors color = RED;
  switch (color) {
      case RED:
          // Some code
          break;
      case GREEN:
          // Some code
          break;
      case BLUE:
          // Some code
          break;
  }

总结

‌‌‌‌  switch语句的参数类型必须是可以转换为整数的类型,包括整数类型、枚举类型等,不能是浮点数、字符串、布尔、指针等其他复杂类型。这是因为switch语句对条件进行的是整数值比较,而非内容或地址的复杂比较。

165. 如何引用一个已经定义过的全局变量?

‌‌‌‌  答:可以引用头文件的方式,也可以用extern关键字。

  1. 如果用引用头文件方式来引用某个在头文件中声明的全局变量,假定你将那个变量写错了,那么在编译期间会报错;
  2. 如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。

解释
‌‌‌‌  在C和C++编程中,全局变量是指在所有函数之外声明的变量,这使得它们在文件的任何地方都可以访问。如果你需要引用一个已经定义的全局变量,有几种方法可以做到这一点,这取决于你是在同一个源文件中还是跨多个源文件中引用它。

同一个源文件中的全局变量引用

‌‌‌‌  在同一个源文件中引用全局变量非常简单。你只需直接使用它的名称。

#include <iostream>

int globalVariable = 42; // 全局变量定义

void printGlobalVariable() {
    std::cout << "Global Variable: " << globalVariable << std::endl; // 直接引用全局变量
}

int main() {
    printGlobalVariable(); // 输出:Global Variable: 42
    globalVariable = 100; // 修改全局变量
    printGlobalVariable(); // 输出:Global Variable: 100
    return 0;
}

跨源文件引用全局变量

‌‌‌‌  当全局变量跨多个源文件时,通常需要在头文件中声明该变量,并在一个源文件中定义它。然后,其他源文件可以通过extern关键字声明这个全局变量,来引用它。

示例:两个文件之间引用全局变量

File: globals.h (头文件)

‌‌‌‌  头文件中声明外部变量。

#ifndef GLOBALS_H
#define GLOBALS_H

extern int globalVariable; // 声明全局变量

#endif // GLOBALS_H
File: main.cpp (主文件)

‌‌‌‌  在主文件中定义全局变量,并引用它。

#include <iostream>
#include "globals.h"

int globalVariable = 42; // 定义全局变量

void printGlobalVariable();

int main() {
    printGlobalVariable(); // 调用外部函数引用全局变量
    globalVariable = 100; // 修改全局变量
    printGlobalVariable();
    return 0;
}
File: functions.cpp (另一个源文件)

‌‌‌‌  在另一个源文件中引用全局变量。

#include <iostream>
#include "globals.h"

void printGlobalVariable() {
    std::cout << "Global Variable: " << globalVariable << std::endl; // 引用全局变量
}
Compile and Link

‌‌‌‌  要编译并链接这些文件,你可以使用以下命令(假设你使用的是某个命令行编译器,如g++):

g++ main.cpp functions.cpp -o program
./program

注意事项

  1. 初始化和定义:
    • 全局变量的定义必须在一个源文件中,通常是main所在的源文件。
    • 不能在多个源文件中重复定义同一个全局变量,否则会产生链接错误。
  2. 作用域和生存期
    • 全局变量的作用域是从定义它的地方开始到整个程序结束。
    • 全局变量在程序执行期间始终存在(从程序启动到程序结束)。
  3. 命名冲突
    • 在使用全局变量时,需避免命名冲突。因此,通常使用命名规范或命名空间(在C++中)来防止冲突。

‌‌‌‌  通过这些步骤,你可以在单个源文件或多个源文件之间正确引用和使用全局变量。

‌‌‌‌  我们首先需要理解C/C++编译过程的两个重要阶段:编译(Compilation)和链接(Linking)。

编译和链接阶段

  1. 编译阶段(Compilation)
    • 每个源文件独立编译,生成目标文件(.o或.obj)。
    • 编译器检查语法和类型等问题,如果有错误,会在这个阶段报错。
  2. 链接阶段(Linking)
    • 链接器负责将多个目标文件和库文件链接在一起,生成最终的可执行文件。
    • 链接器解决函数和变量的引用和定义之间的对应关系,如果有未解析的符号,链接器会报错。

全局变量通过头文件声明和引用

‌‌‌‌  假设有一个全局变量在多个源文件中使用:

globals.h (头文件)
#ifndef GLOBALS_H
#define GLOBALS_H

extern int globalVariable; // 声明全局变量

#endif // GLOBALS_H
main.cpp (源文件,定义全局变量)
#include "globals.h"

int globalVariable = 42; // 定义全局变量

int main() {
    // 使用全局变量
    return 0;
}
functions.cpp (另一个源文件,引用全局变量)
#include "globals.h"

void useGlobalVariable() {
    // 使用全局变量
}

情况1:错误的关键字或类型(编译期间报错)

‌‌‌‌  
‌‌‌‌  如果在头文件中声明全局变量时,犯了类型错误或拼写错误,那么在包含此头文件的每个源文件编译时,编译器会发现这些错误并报错。

// globals.h
#ifndef GLOBALS_H
#define GLOBALS_H

extern int globaVariable; // 错误的声明

#endif // GLOBALS_H

// main.cpp
#include "globals.h"

int globalVariable = 42; // 定义

int main() {
    return 0;
}

// 编译错误:globaVariable在main.cpp中未声明

情况2:使用extern关键字,声明和定义不一致(链接期间报错)

‌‌‌‌  如果在使用extern关键字时,声明与定义不匹配,可能在编译时不会报错,因为编译器只检查了声明的语法,而未检查是否有真实的定义。但是,在链接时,链接器会发现有未解析的符号,因为定义和引用不匹配。

// globals.h
#ifndef GLOBALS_H
#define GLOBALS_H

extern int globaVariable; // 错误的声明

#endif // GLOBALS_H

// main.cpp
#include "globals.h"

int globalVariable = 42; // 正确的定义

int main() {
    return 0;
}

// 编译成功,但在链接期间报错:未定义引用globaVariable

总结

  • 编译时错误:在头文件中声明全局变量时,如果存在拼写或类型错误,编译器会在编译过程中报告这些错误,因为解析头文件时,编译器会检查语法和类型一致性。
  • 链接时错误:使用extern关键字在多个源文件之间引用全局变量时,如果声明和定义不匹配且声明错误,编译阶段不会发现错误(只要声明的语法正确),但在链接阶段,链接器会发现未解析的符号,从而报错。

正确的做法

  • 确保头文件中声明的全局变量名称和类型与定义完全一致。
  • 使用头文件的#ifndef #define #endif保护机制,确保文件只被包含一次,避免重复定义。
  • 避免在多个文件中定义同一个全局变量,应确保声明和定义分别在头文件和一个源文件中。

下面详细解释一下编译错误和链接错误之间的区别:

编译错误示例

globals.h (错误声明)
#ifndef GLOBALS_H
#define GLOBALS_H

extern int globaVariable; // 错误的声明,应该是globalVariable

#endif // GLOBALS_H
main.cpp (正确的定义)
#include "globals.h" // 包含错误声明的头文件

int globalVariable = 42; // 正确的定义

int main() {
    return 0;
}
functions.cpp (使用错误声明的变量)
#include "globals.h" // 包含错误声明的头文件

void useGlobalVariable() {
    int value = globaVariable; // 使用错误的声明
}

编译阶段

‌‌‌‌  在这个情况下,每个源文件独立编译,编译器会发现globaVariablemain.cpp中未定义,从而产生编译错误。

  • main.cpp 编译器会报错:globaVariable 未定义
  • functions.cpp 编译器也会报错:globaVariable 未定义

因为编译器在解析这些文件时会立即发现这些错误,从而在编译阶段就会报错,无法生成目标文件。

链接错误示例

globals.h (错误声明)
#ifndef GLOBALS_H
#define GLOBALS_H

extern int globaVariable; // 错误的声明,应该是globalVariable

#endif // GLOBALS_H
main.cpp (正确的定义)
#include "globals.h" // 包含错误声明的头文件

int globalVariable = 42; // 正确的定义,注意这里声明的是globalVariable

int main() {
    return 0;
}
functions.cpp (使用和声明不匹配的变量)
#include "globals.h" // 包含错误声明的头文件

void useGlobalVariable() {
    int value = globaVariable; // 使用错误的声明globaVariable,这与main.cpp中的定义不一致
}

编译阶段

‌‌‌‌  在编译阶段,编译器并不会检查变量的定义是否与其他源文件一致,它只会检查语法和类型。因此,这些文件都能通过编译。

  • main.cpp 编译会成功,生成目标文件(main.o)。
  • functions.cpp 编译会成功,生成目标文件(functions.o)。

链接阶段

‌‌‌‌  在链接阶段,链接器会把各个编译生成的目标文件结合起来,尝试解析所有符号(函数和变量)。由于main.o中定义的是globalVariable,而functions.o中引用的是globaVariable,链接器会发现globaVariable没有定义:

  • 链接器报错:undefined reference to 'globaVariable'

总结

‌‌‌‌  通过以上两个示例,我们可以清楚地看到,编译错误和链接错误在流程上的区别:

  1. 编译错误:当声明和定义在编译阶段有明显的语法或类型错误时,编译器会立即报错。

    • 错误示例:编译期会报错,因为globaVariablemain.cpp中未定义。
  2. 链接错误:当在声明和定义存在不匹配或引用未定义的变量、函数时,编译阶段通过,但在链接阶段发现未解析的符号,从而报错。

    • 错误示例:链接期会报错,因为globaVariable在任何地方都没有定义,而main.cpp中定义的是globalVariable

通过涵盖这些不同阶段的错误示例,我们能够更好地理解编译和链接过程中可能遇到的问题。

166. 对于一个频繁使用的短小函数,在C语言中应该用什么实现?在C++中应用什么实现?

‌‌‌‌  答:C用宏定义,C++用inline

解释
‌‌‌‌  在C和C++中,对于频繁使用的短小函数,最佳实践是使用内联函数。这能提高程序的运行效率,主要是通过减少函数调用的开销。

在C语言中使用inline关键字

C99标准引入了inline关键字,允许你建议编译器将函数内联,从而减少函数调用开销。

#include <stdio.h>

// 内联函数定义
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(2, 3); // 调用内联函数
    printf("Result: %d\n", result);
    return 0;
}

需要注意的是,虽然你可以建议编译器将函数内联,但最终决定权在编译器,特别是函数比较复杂或过大时,编译器可能会忽略这个建议。

在C++中使用inline关键字

‌‌‌‌  在C++中,inline关键字同样可用,并且通常用于在类定义中实现短小的成员函数。此外,C++还支持在类声明中定义内联函数,这使得代码更加简洁。

#include <iostream>

// 普通的内联函数
inline int add(int a, int b) {
    return a + b;
}

class MyClass {
public:
    // 在类声明中定义的内联成员函数
    inline int multiply(int a, int b) {
        return a * b;
    }
};

int main() {
    int result = add(2, 3); // 调用全局内联函数
    std::cout << "Addition Result: " << result << std::endl;

    MyClass obj;
    result = obj.multiply(4, 5); // 调用成员内联函数
    std::cout << "Multiplication Result: " << result << std::endl;

    return 0;
}

当心事项

  1. 代码膨胀
    虽然内联函数可以减少函数调用开销,但滥用会导致代码膨胀,因为编译器每次遇到内联函数调用时会插入函数体的副本。

  2. 调试复杂性
    内联函数可能会增加调试的复杂性,因为内联函数没有实际的函数调用,这会导致调用栈分析变得困难。

  3. 编译器的自由度
    inline关键字只是一个建议,编译器可能会根据其自身的优化策略选择是否真正内联这个函数。

编译器标志

‌‌‌‌  对于GCC或Clang编译器,可以使用编译器标志来进一步优化内联函数,例如-O3优化级别或更高级的优化标志,这通常能够使编译器内联更多的短小函数。

结论

  • 在C语言中,使用inline关键字来定义需要频繁调用的短小函数。
  • 在C++中,使用inline关键字,并在类定义中内联短小的成员函数以提高性能。

通过适当地使用内联函数,可以显著减少函数调用的开销,从而提高程序的运行效率。

167. C++是不是类型安全的?

‌‌‌‌  答:不是。两个不同类型的指针之间可以强制转换(reinterpret cast)。

解释

‌‌‌‌  C++不是严格意义上的类型安全语言。尽管它提供了一些类型安全的特性,比如强类型检查和类型转换,但仍然存在一些漏洞和特性,使其在某些情况下可能不够类型安全。

以下是一些使C++在某些情况下不够类型安全的特性:

  1. 类型转换:C++允许各种类型转换,包括隐式类型转换和显式类型转换(使用static_castreinterpret_castconst_castdynamic_cast等)。某些类型转换可能会导致类型不安全,比如reinterpret_cast

  2. 指针操作:C++允许直接操作指针,指针运算和类型转换可能会导致内存访问错误和未定义行为,从而破坏类型安全。

  3. 联合体:联合体允许多个数据成员共享同一块内存空间,在读取联合体的某个成员之前,程序员需要确保最近写入的是同一类型,否则可能会导致未定义行为。

  4. 原始数组:C++中的原始数组不进行边界检查,可能会导致缓冲区溢出和类型不安全。

‌‌‌‌  尽管如此,C++11及以后的标准引入了一些新特性,比如nullptrautodecltype、智能指针(如std::shared_ptrstd::unique_ptr)等,这些特性可以帮助提高代码的类型安全性和内存安全性。

‌‌‌‌  总的来说,C++提供了一些机制来支持类型安全,但由于其设计哲学和历史原因,它并不是一门严格的类型安全语言。类型安全在很大程度上依赖于程序员的自律和代码实践。

168. 当一个类A中没有声明任何成员变量与成员函数,这时sizeof(A)的值是多少?请解释一下编译器为什么没有让它为0?

答:1。
‌‌‌‌  举个反例,如果是0的话,声明一个class A[10]对象数组,而每一个对象占用的空间是0,这时候就没有办法区分A[0],A[1]…了。

解释

‌‌‌‌  在C++中,如果一个类A没有声明任何成员变量和成员函数,sizeof(A)的值通常是1。这是因为C++编译器需要确保每个实例对象有一个唯一的地址,以便在内存中进行区分

‌‌‌‌  具体原因如下:

  1. 地址唯一性:C++标准要求每个对象在内存中必须有一个唯一的地址。即使一个类没有任何数据成员,其对象也需要占据一些内存空间,以确保不同实例对象有不同的地址。如果sizeof(A)为0,那么所有实例对象的地址都相同,这就无法区分不同的对象。

  2. 数组索引:正如你提到的例子,如果sizeof(A)为0,那么声明一个包含10个对象的数组时,所有对象都会占据同一个地址,这样无法区分和访问不同的数组元素。例如,A[0]A[1]在内存中没有任何区别。通过将sizeof(A)设为1,可以确保数组中的每个元素都有不同的地址,方便索引和访问。

下面是一个具体的例子来演示这一点:

class A {};

int main() {
    A a1;
    A a2;

    std::cout << "Size of A: " << sizeof(A) << std::endl;
    std::cout << "Address of a1: " << &a1 << std::endl;
    std::cout << "Address of a2: " << &a2 << std::endl;

    A array[10];
    for (int i = 0; i < 10; ++i) {
        std::cout << "Address of array[" << i << "]: " << &array[i] << std::endl;
    }

    return 0;
}

输出示例(具体地址会有所不同):

Size of A: 1
Address of a1: 0x7ffee999d6d0
Address of a2: 0x7ffee999d6d1
Address of array[0]: 0x7ffee999d6d2
Address of array[1]: 0x7ffee999d6d3
Address of array[2]: 0x7ffee999d6d4
Address of array[3]: 0x7ffee999d6d5
Address of array[4]: 0x7ffee999d6d6
Address of array[5]: 0x7ffee999d6d7
Address of array[6]: 0x7ffee999d6d8
Address of array[7]: 0x7ffee999d6d9
Address of array[8]: 0x7ffee999d6da
Address of array[9]: 0x7ffee999d6db

‌‌‌‌  可以看到,尽管类A是一个空类,但它的实例对象在内存中依然有唯一的地址,而sizeof(A)为1确保了数组中每个元素的地址是不同的。

169. A是空指针,sizeof(A)是多少?

‌‌‌‌  在C语言或C++中,sizeof运算符用于计算类型或对象的大小(以字节为单位)。当你对一个空指针(nullptrNULL)使用 sizeof 时,计算的是指针类型的大小,而不是指针指向的对象的大小。因此,sizeof 运算的结果与指针是否为空无关。

关键点:

  • 指针的大小由体系结构(架构)的位宽决定。常见的情况是:
    • 32位系统上,指针的大小是 4字节
    • 64位系统上,指针的大小是 8字节
#include <iostream>

int main() {
    int* ptr = nullptr;  // 空指针
    std::cout << "Size of nullptr: " << sizeof(ptr) << " bytes" << std::endl;
    return 0;
}

170. 简述数组和指针的区别?

‌‌‌‌  答:数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

1. 修改内容上的区别

char a[] = “hello”;
a[0] = ‘X’;
char * p = “world”; //注意 p 指向常量字符串
p[0] = ‘X’; // 编译器不能发现该错误,运行时错误

2. 用运算符sizeof可以计算出数组的容量(字节数)。sizeof§,p是指针,得到的是一个指针变量的字节数,而不是p所指的内存容量。

解释

‌‌‌‌  数组和指针是C/C++编程中的两个非常重要的概念,尽管它们有很多相似之处,但它们本质上是不同的东西。以下是数组和指针的一些主要区别:

数组和指针的定义

数组(Array)

  • 数组是一个具有相同数据类型的元素的有序集合。在内存中,数组元素是连续存储的。
  • 数组的大小在声明时是固定的,不能在运行时动态改变。

指针(Pointer)

  • 指针是一个变量,用于存储另一个变量的内存地址。
  • 指针可以指向不同类型的数据,并且可以在运行时改变指向的地址。

区别点

1. 内存分配
  • 数组: 在编译时分配,大小固定。例如 int arr[5]; 分配5个 int 类型的连续内存空间。
  • 指针: 可以在运行时分配,大小可以动态变化。例如 int *ptr = (int*)malloc(5 * sizeof(int));分配5个 int 类型的连续内存空间。
2. 声明和初始化
  • 数组: 声明数组会自动分配指定大小的内存。例如 int arr[5];
  • 指针: 必须手动指定分配内存,初始化时通常需要一个已有的地址,例如 int *ptr = NULL; 或 int *ptr = (int*)malloc(5 * sizeof(int));
3. 访问方式
  • 数组: 可以使用数组下标直接访问元素,例如 arr[2]
  • 指针: 可以使用指针算术操作和解引用访问元素,例如 *(ptr + 2) 或者 ptr[2]
4. 类型信息
  • 数组: 类型包含了数组的大小。例如 int arr[5]; 是包含 5 个 int 类型元素的数组。
  • 指针: 类型只包含指向的数据类型,不包含大小信息。例如 int *ptr 是指向 int 类型数据的指针,但不包含指向多少个 int 元素的信息。
5. 存储方式
  • 数组: 数组名代表数组的地址,但类型上也包含了数组长度的信息(仅在声明时)。
  • 指针: 指针变量独立存在,可以指向不同的数据地址,不包含长度信息。

示例代码

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 数组示例
    int arr[5] = {1, 2, 3, 4, 5};
    printf("数组的第二个元素: %d\n", arr[1]);

    // 指针示例
    int *ptr;
    ptr = (int*)malloc(5 * sizeof(int));
    if (ptr == NULL) {
        printf("内存分配失败\n");
        return -1;
    }

    // 分配值
    for (int i = 0; i < 5; i++) {
        ptr[i] = i + 1;  // 等同于 *(ptr + i) = i + 1;
    }
    printf("指针的第二个元素: %d\n", ptr[1]);

    // 释放内存
    free(ptr);
    return 0;
}

总结

  • 数组是具有固定大小的连续内存块,声明时自动分配和初始化,可以用下标直接访问。
  • 指针是一个变量,存储其他变量的地址,可以动态分配和修改内存地址,通过指针算术和解引用来访问数据。

原文地址:https://blog.csdn.net/qq_43504141/article/details/142684399

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!