面试专区|【30道C++ 基础高频题整理(附答案背诵版)】

2024-05-24 17:04

本文主要是介绍面试专区|【30道C++ 基础高频题整理(附答案背诵版)】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1. C和C++有什么区别?

C++是C语言的超集(我看网上很多文章说这是不对的),这意味着几乎所有的C程序都可以在C++编译器中编译和运行。然而,C++引入了许多新的概念和特性,使得两种语言在一些关键点上有显著的区别。

以下是C和C++的一些主要区别:

  1. 面向对象编程:C++支持面向对象编程(OOP),包括类、对象、继承、封装、多态等特性。这使得C++更适合大型软件项目,因为OOP可以提高代码的重用性和可读性。C语言是一种过程性语言,没有这些特性。
  2. STL(Standard Template Library):C++提供了STL,这是一套强大的模板类和函数的库,包括列表、向量、队列、栈、关联数组等。这些可以大大提高开发效率。C语言没有内置的数据结构库。
  3. 异常处理:C++提供了异常处理机制,可以更优雅地处理错误情况。C语言处理错误通常依赖于函数返回值。
  4. 构造函数和析构函数:C++支持构造函数和析构函数,这些特殊的函数允许对象在创建和销毁时执行特定的代码。C语言没有这个概念。
  5. 运算符重载:C++允许运算符重载,这意味着开发者可以更改已有运算符的行为,或者为用户自定义类型添加新的运算符。C语言不支持运算符重载。

例如,如果我们要创建一个复数类并对其进行算术运算,C++的面向对象和运算符重载特性就非常有用。我们可以定义一个复数类,然后重载+、-和*运算符以执行复数的加法、减法和乘法。这样,我们就可以像处理内置类型一样处理复数对象。反观C语言,我们需要定义结构体来存储复数,并且需要写一堆函数来处理复数的加法、减法和乘法。

2. C语言的结构体和C++的有什么区别

C语言的结构体和C++的结构体在基本的使用上是相似的,都是用来封装多个不同或相同类型的数据。然而,C++中的结构体继承了C++面向对象的特性,与C语言中的结构体有一些关键性的区别:

  1. 成员函数:C++的结构体可以包含成员函数(包括构造函数和析构函数),而C语言的结构体不能。
  2. 访问控制:C++的结构体支持公有(public)、保护(protected)和私有(private)三种访问控制级别,而C语言的结构体中的所有成员都是公有的。
  3. 继承:C++的结构体可以从其他结构体或类继承,而C语言的结构体不能继承。

举个例子,假设我们需要创建一个表示日期的结构体,包含年、月、日这三个字段,并且需要一个函数来检查日期是否有效。在C语言中,我们需要定义一个结构体和一个独立的函数:

struct Date {int year;int month;int day;
};// 独立函数
int is_valid_date(struct Date date) {// 验证日期的逻辑
}

在C++中,我们可以将这个函数作为结构体的成员函数:

struct Date {int year;int month;int day;bool is_valid() const {// 验证日期的逻辑}
};

这样,在C++中使用时,我们可以直接调用成员函数 is_valid(),代码更加清晰和易于维护。

Date date;
//...
if (date.is_valid()) {//...
}

3. C 语言的关键字 static 和 C++ 的关键字 static 有什么区别

在C和C++中,static关键字有三个主要的用途,但其在C++中的用法更加丰富:

  1. 在函数内部:在C和C++中,static关键字可用于函数内部变量。此时,此变量的生命周期将贯穿整个程序,即使函数执行结束,这个变量也不会被销毁。每次调用这个函数时,它都不会重新初始化。这可以用于实现一些需要保持状态的函数。
  2. 在函数外部或类内部:在C和C++中,static关键字可以用于全局变量或函数。此时,此变量或函数的作用域被限制在定义它的文件内,无法在其他文件中访问。这可以防止命名冲突或不必要的访问。
  3. 在类内部:只有C++支持此用法。在C++中,static关键字可以用于类的成员变量或成员函数。对于静态成员变量,无论创建多少个类的实例,都只有一份静态成员变量的副本。静态成员函数则可以直接通过类名调用,而无需创建类的实例。

以下是一个C++中使用static的例子:

class MyClass {
public:static int count; // 静态成员变量,所有实例共享一份MyClass() {count++; // 每次创建实例,计数加1}static int getCount() { // 静态成员函数,通过类名直接调用return count;}
};int MyClass::count = 0; // 静态成员变量的初始化int main() {MyClass a;MyClass b;MyClass c;std::cout << MyClass::getCount(); // 输出3,因为创建了3个实例
}

这个例子中,我们创建了一个名为MyClass的类,它有一个静态成员变量count和一个静态成员函数getCount()。每创建一个MyClass的实例,count就会增加1。我们可以直接通过类名调用getCount()来获取count的值,而无需创建类的实例。

4. C++ 和 Java有什么核心区别?

  1. C++和Java都是广泛使用的编程语言,但它们在设计理念、功能和用途上有很大的不同。以下是C++和Java的几个核心区别:
  2. 运行环境:Java是一种解释型语言,它的代码在JVM(Java虚拟机)上运行,这使得Java程序可以在任何安装有JVM的平台上运行,实现了“一次编写,到处运行”的理念。而C++是一种编译型语言,其代码直接编译成目标机器的机器码运行,因此需要针对特定平台编译。
  3. 内存管理:Java有自动内存管理和垃圾回收机制,程序员不需要直接管理内存。而在C++中,程序员需要手动进行内存的分配和释放,这提供了更大的控制力,但同时也增加了内存泄漏的风险。
  4. 面向对象编程:Java是一种纯面向对象的编程语言,所有的代码都需要包含在类中。与此不同,C++支持面向对象编程,但它也允许过程式编程。
  5. 错误处理:Java使用异常处理机制进行错误处理,而C++既支持异常处理,也支持通过返回值进行错误处理。
  6. 多线程:Java内置了对多线程的支持,而C++在C++11标准之后引入了对多线程的支持。
  7. 性能:因为C++的代码直接编译为机器码,所以它通常比Java程序运行得更快。但是,Java的跨平台能力和内置的垃圾回收机制使其在开发大型企业级应用时更具优势。

例如,如果你正在开发一个需要直接访问硬件,或者需要高性能数学计算的应用(比如游戏,图形渲染,科学计算),C++可能是一个更好的选择。而如果你正在开发一个大型的企业级web应用,Java的跨平台能力,内置的垃圾回收和强大的类库可能会更有优势。

5. C++中,a和&a有什么区别?

在C++中,a和&a表示的是两种完全不同的概念:

  1. a:当你在代码中写a时,你正在引用变量a的值。例如,如果你之前写的int a = 10;,那么a的值就是10。
  2. &a:&是一个地址运算符,它给出了变量a在内存中的位置。这被称作a的引用或者是指向a的指针。例如,如果你写int* p = &a;,那么p就是一个指向a的指针,你可以通过*p来访问或修改a的值。

这是C++中的一种基础概念,被称为指针和引用。通过指针和引用,你可以直接操作内存,这在很多情况下都非常有用,例如,动态内存分配,函数参数传递,数据结构(如链表和树)等等。

6. C++中,static关键字有什么作用?

在C++中,static关键字有多个用途,它的作用主要取决于它在哪里被使用:

  1. 在函数内部:如果static被用于函数内部的变量,那么它会改变该变量的生命周期,使其在程序的整个运行期间都存在,而不是在每次函数调用结束时被销毁。这意味着,这个变量的值在函数调用之间是保持的。
  2. 在函数外部:如果static被用于函数外部的全局变量或函数,那么它会将这个变量或函数的链接范围限制在它被定义的文件内。换句话说,这个变量或函数不能在其他文件中被直接访问。这可以帮助减少命名冲突,而且能提供一种控制变量和函数可见性的方式。
  3. 在类中:如果static被用于类的成员变量,那么该变量将会成为这个类的所有实例共享的变量,也就是说,类的每个实例都能访问到这个同样的变量。如果static被用于类的成员函数,那么这个函数可以直接通过类来调用,而不需要创建类的实例。

以下是一个C++中使用static的例子:

class MyClass {
public:static int count; // 静态成员变量,所有实例共享一份MyClass() {count++; // 每次创建实例,计数加1}static int getCount() { // 静态成员函数,通过类名直接调用return count;}
};int MyClass::count = 0; // 静态成员变量的初始化int main() {MyClass a;MyClass b;MyClass c;std::cout << MyClass::getCount(); // 输出3,因为创建了3个实例
}

在这个例子中,MyClass有一个静态成员变量count和一个静态成员函数getCount()。每次创建一个MyClass的实例,count就会增加1。我们可以直接通过类名调用getCount()来获取count的值,而无需创建类的实例。

7. C++中,#define和const有什么区别?

#define和const都可以用来定义常量,但它们在实现方式和使用上有一些区别。

  1. 预处理器与编译器:#define是预处理器指令,在编译前会被预处理器替换,它只是简单的文本替换,不进行类型检查,也不会分配内存。而const是编译器处理的,它会在编译时进行类型检查,确保你不会意外地改变它的值。
  2. 作用域:#define没有作用域的概念,一旦定义,到文件结束都有效。而const常量有作用域,它的作用范围限制在定义它的块或者文件中。
  3. 调试:在调试时,#define定义的宏常量无法查看,因为在预处理阶段就已经被替换掉了。而const定义的常量在调试过程中是可以查看的。

例如,考虑以下的代码:

#define PI 3.14
const double Pi = 3.14;double area1 = PI * r * r; // 使用#define定义的常量
double area2 = Pi * r * r; // 使用const定义的常量

在这个例子中,PI是一个预处理器定义的宏,而Pi是一个const定义的常量。两者都可以用来计算圆的面积,但Pi在编译时进行类型检查,并且在调试过程中可以查看其值。

9. 静态链接和动态链接有什么区别?

静态链接和动态链接是两种不同的程序链接方式,它们主要的区别在于链接的时间和方式。

  1. 静态链接:在静态链接中,所有代码(包括程序本身的代码和它依赖的库的代码)都会在编译时期被合并为一个单一的可执行文件。这个可执行文件包含了程序运行所需的所有信息,因此它不依赖于任何外部的库文件。静态链接的优点是部署简单,因为不需要额外的依赖,只需要一个文件就可以运行。缺点是可执行文件通常会比动态链接的大,因为它包含了所有需要的代码,而且如果库更新,程序需要重新编译和链接。
  2. 动态链接:在动态链接中,程序的代码和它依赖的库的代码被分开。程序的可执行文件只包含了程序本身的代码和一些标记,这些标记表示程序在运行时需要链接到哪些库。当程序运行时,操作系统会负责加载这些库并进行链接。动态链接的优点是可执行文件更小,因为它不包含库的代码,而且多个程序可以共享同一份库,节省内存。此外,如果库更新,只需要替换库文件,程序无需重新编译和链接。缺点是部署稍微复杂一些,因为需要确保运行环境中有所需的库文件。

例如,假设我们有一个程序,它使用了一个数学库。如果我们静态链接这个库,那么所有的数学函数都会被包含在我们的可执行文件中,我们可以将这个文件复制到任何地方运行。如果我们动态链接这个库,那么我们的可执行文件就会小得多,但如果我们想在另一台机器上运行这个程序,我们就需要确保那台机器上也安装了这个数学库。

10. 变量的声明和定义有什么区别

在C++中,变量的声明和定义是两个不同的概念。

声明是告诉编译器某个变量的存在,以及它的类型。声明并不分配存储空间。例如,外部变量的声明extern int a;,这里只是告诉编译器有一个类型为int的变量a存在,具体的a在哪里定义的,编译器此时并不知道。

定义是声明的延伸,除了声明变量的存在和类型以外,还分配了存储空间。例如,int a;就是一个定义,编译器在这里为a分配了足够的存储空间来存储一个整数。

在C++中,一个变量可以被声明多次,但只能被定义一次。例如,我们可以在多个文件中声明同一个变量,但只能在一个文件中定义它。如果在多个地方定义同一个变量,编译器会报错。

举个例子,假设我们正在编写一个大型程序,这个程序有一个全局变量需要在多个文件中使用。我们可以在一个文件中定义这个变量,然后在其他需要使用这个变量的文件中声明它。这样,所有的文件都可以访问到这个变量,但只有一个文件负责管理它的存储空间。

11. typedef 和define 有什么区别

typedef和#define都是C++中用于定义别名的关键字,但它们的用途和行为有所不同。

typedef是C++的一个关键字,用于为现有的类型创建一个新的名称(别名)。例如,如果我们想要为unsigned long int创建一个更简单的别名,我们可以写typedef unsigned long int ulong;,然后在代码中就可以使用ulong来代替unsigned long int。typedef只能为类型定义别名,不能为值定义别名。

#define是预处理器的一个指令,用于创建宏。宏可以是一个值,也可以是一段代码。例如,#define PI 3.14159就定义了一个名为PI的宏,它的值是3.14159。#define的作用范围更广,它不仅可以为类型定义别名,也可以为值定义别名,甚至可以定义一段代码。

两者的主要区别在于:

  1. typedef仅作用于类型,而#define可以定义类型、值或者代码。
  2. typedef是由编译器解析的,而#define是由预处理器处理的。因此,typedef的作用范围是局部的,只在定义它的文件或作用域内有效,而#define的作用范围是全局的,一旦定义即在整个源代码中有效。
  3. typedef定义的别名会受到类型检查,而#define定义的宏不会。例如,如果你试图使用typedef为一个函数类型定义别名,然后使用这个别名定义一个整数,编译器会报错。但是如果你使用#define定义一个函数类型的宏,然后使用这个宏定义一个整数,预处理器会默默地接受。
  4. typedef可以处理模板化的类型,而#define不能。例如,typedef std::vector int_vector;是合法的,但是使用#define来做同样的事情就会出现问题。
  5. typedef定义的别名在调试时更友好。因为它是编译器处理的,所以在调试时可以看到别名。而#define定义的宏在预处理阶段就被替换掉了,所以在调试时看不到宏的名称,只能看到宏的值。

12. final和override关键字

final和override是C++11引入的两个关键字,主要用于类的继承和虚函数的覆盖。

  1. final:如果一个类被声明为final,那么它不能被继承。例如,class Base final { … };,此时任何试图继承Base的类都会导致编译错误。此外,如果一个虚函数被声明为final,那么它不能在派生类中被覆盖。例如,virtual void fun() final;,此时任何派生类试图覆盖fun()函数都会导致编译错误。
  2. override:如果一个虚函数被声明为override,那么编译器会检查这个函数是否真的覆盖了基类中的一个虚函数。如果没有,编译器会报错。这个关键字可以帮助我们避免因为拼写错误或者函数签名错误而导致的错误。例如,void fun() override;,如果基类中没有一个函数的签名和fun()完全匹配,那么编译器就会报错。

例如,假设我们有一个基类Animal和一个派生类Dog。Animal有一个虚函数make_sound(),Dog需要覆盖这个函数。如果我们在Dog的make_sound()函数声明中加上了override关键字,那么如果我们不小心将函数名拼写成了mkae_sound(),编译器就会因为找不到对应的基类函数而报错,帮助我们及时发现错误。

13. 宏定义和函数有何区别?

宏定义(#define)和函数是两种常见的在C++中编写代码的方式,但它们有一些重要的区别:

  1. 编译阶段:宏定义是在预处理阶段展开的,而函数是在编译阶段处理的。这意味着使用宏定义的代码在编译前就已经被预处理器替换掉了,而函数在编译阶段会生成对应的函数调用。
  2. 类型检查:函数在编译时会进行类型检查,而宏定义不会。这可能会导致宏定义在使用时出现错误,而在编译阶段并不会被发现。
  3. 效率:由于宏定义在预处理阶段就被替换,因此它没有函数调用的开销(如堆栈操作),所以在某些情况下可能更快。然而,过度使用宏定义可能会导致编译后的代码体积增大,因为每次使用宏都会插入一份宏的代码副本。
  4. 封装:函数提供了更好的封装,使得代码更易于阅读和维护。而宏定义由于其替换性质,可能会在复杂的表达式中产生不易察觉的错误。

例如,考虑一个简单的宏定义和函数,它们都用于计算两个值的最大值:

#define MAX(a, b) ((a) > (b) ? (a) : (b))int max(int a, int b) {return a > b ? a : b;
}

如果我们使用MAX宏定义来计算MAX(i++, j++),由于宏展开,i和j可能会增加两次,这是一个副作用,可能导致不可预见的结果。而使用函数max(i++, j++),则不会有这个问题,因为函数参数的求值顺序是确定的,i和j只会增加一次。

14. sizeof 和strlen 的区别

sizeof和strlen是两个在C++中常用的函数,但它们的功能和用途有所不同:

  1. sizeof 是一个编译时操作符,它返回一个对象或者类型所占用的字节数。例如,sizeof(int)将返回4(在大多数现代系统中),sizeof(char)将返回1。如果你对一个数组使用sizeof,它将返回整个数组的大小,而不是数组中的元素个数。例如,int arr[10]; sizeof(arr);将返回40(在大多数现代系统中,因为一个int通常占用4个字节,所以10个int占用40个字节)。
  2. strlen 是一个运行时函数,它返回一个以空字符终止的字符串的长度。该长度不包括终止的空字符。例如,strlen(“hello”)将返回5。注意,strlen只能用于字符数组,且该字符数组必须以空字符(‘\0’)终止,否则strlen会继续读取内存,直到遇到一个空字符,这可能会导致未定义的行为。

举个例子,如果我们有一个字符数组char arr[10] = “hello”;,那么sizeof(arr)将返回10(因为arr是一个10个字符的数组,每个字符占用1个字节),而strlen(arr)将返回5(因为字符串"hello"的长度是5,不包括终止的空字符)。

15. 简述strcpy、sprintf 与memcpy 的区别

strcpy, sprintf, 和 memcpy 都是 C/C++ 标准库中的函数,用于处理字符串和内存,但它们的作用是不同的:

  1. strcpy:这个函数用于将源字符串复制到目标字符串。它会复制源字符串的所有字符,直到遇到终止的空字符,并在目标字符串的末尾添加一个空字符。例如,char dest[10]; strcpy(dest, “hello”);,这将会把字符串 “hello”(包括终止的空字符)复制到 dest 中。需要注意的是,如果源字符串的长度(包括终止的空字符)超过了目标字符串的大小,那么会导致缓冲区溢出,这可能会引发安全问题。
  2. sprintf:这个函数用于将格式化的数据写入字符串。它可以接受多个输入参数,并按照指定的格式将这些参数格式化为一个字符串,然后将这个字符串写入目标字符串。例如,char str[50]; sprintf(str, “Hello, %s!”, “world”);,这将会把 “Hello, world!” 写入 str。同样,如果格式化后的字符串长度(包括终止的空字符)超过了目标字符串的大小,那么会导致缓冲区溢出。
  3. memcpy:这个函数用于复制内存区域。它会从源内存区域复制指定数量的字节到目标内存区域。例如,char dest[10]; char src[10] = “hello”; memcpy(dest, src, 6);,这将会把 src 的前6个字节(包括终止的空字符)复制到 dest。memcpy 不会因为遇到空字符而停止复制,它总是复制指定的字节数,因此,如果指定的字节数超过了目标内存区域的大小,那么会导致缓冲区溢出。

总的来说,strcpy 和 sprintf 是处理以空字符终止的字符串的函数,而 memcpy 是处理内存的函数。在使用这些函数时,需要特别注意缓冲区溢出的问题。

16. volatile有什么作用

volatile是C++中的一个关键字,它告诉编译器对象的值可能会以程序未明确指示的方式被改变。这通常发生在以下几种情况:

硬件寄存器:例如,一个状态寄存器,它的值可能会在任何时间被硬件改变。
中断服务程序:一个在主程序中定义的变量可能会被一个中断服务程序改变。
多线程:一个在主线程中定义的变量可能会被其他线程改变。
在默认情况下,编译器会对程序进行优化,以提高程序的运行效率。这些优化可能包括:缓存变量的值,删除未使用的变量,重新排序无关的指令等。然而,如果一个变量被声明为volatile,那么编译器就不能对这个变量进行这些优化。每次访问volatile变量时,都会直接从它的内存地址中读取值,而不是从寄存器或其他地方读取可能的缓存值。

这里有一个例子:

volatile int timer; // 假设这个变量会被硬件定时器改变void wait_for_timer() {int start = timer;while (timer == start) {// 等待定时器改变}
}

在这个例子中,如果timer没有被声明为volatile,那么编译器可能会认为在while循环中timer的值没有被改变,所以它会将timer的值缓存起来,导致程序进入无限循环。如果timer被声明为volatile,那么编译器就会知道timer的值可能会被改变,所以它每次都会从内存中读取timer的值,而不是使用缓存值,这样程序就能正确地等待定时器改变。

17. 一个参数可以既是const又是volatile吗

是的,一个参数(或变量)可以同时被声明为const和volatile。这两个关键字可以同时用于一个对象,但它们的含义是完全不同的。

const表示该对象的值不能被修改(也就是只读)。
volatile告诉编译器不要对该对象进行优化,因为它可能会被程序未明确指示的方式改变。
这里有一个例子来说明如何使用const和volatile:

const volatile int registerValue; // 假设这个变量映射到一个硬件寄存器
C++
在这个例子中,registerValue可能会被硬件随时改变,因此我们要求编译器不要对它进行优化,所以我们用了volatile。同时,我们的程序不能修改这个值,所以我们用了const。

这种情况常见于嵌入式系统编程,其中有些硬件寄存器是只读的,但它们的值可能会随时改变(例如,一个状态寄存器)。这时我们就需要用到const和volatile。

18. 全局变量和局部变量有什么区别?操作系统和编译器是怎么知道的?

全局变量和局部变量主要有以下几点区别:

声明位置:全局变量在函数外部声明,可以在程序的任何地方访问;局部变量在函数内部声明,只能在声明它的函数内部访问。
生命周期:全局变量的生命周期是整个程序的运行期间;局部变量的生命周期是它所在的函数被调用的期间,函数结束后局部变量就会被销毁。

存储位置:全局变量通常存储在程序的数据段;局部变量通常存储在栈上。

初始值:全局变量有默认的初始值,例如,对于整数类型,它的默认值是0;局部变量如果没有初始化,它的值是未定义的。

编译器和操作系统如何知道变量是全局的还是局部的呢?

编译器:编译器在编译时会根据变量的声明位置确定它是全局变量还是局部变量。全局变量的声明在所有函数外部,局部变量的声明在函数内部。编译器也会根据这些信息生成相应的代码来创建和销毁局部变量,或者来访问全局变量。
操作系统:操作系统通常不关心变量是全局的还是局部的。操作系统管理的是进程和内存,而不是变量。当程序运行时,操作系统会为程序分配一片内存空间,程序如何使用这片内存空间(例如,哪部分用于全局变量,哪部分用于局部变量)完全取决于程序本身。这些信息通常在程序的可执行文件中,操作系统在加载可执行文件时会根据这些信息来设置内存。

19. 什么是C++中的指针和引用?它们有什么区别?

在C++中,指针和引用都是可以指向其他变量或对象的类型,但它们的工作方式有一些重要的区别。

指针是一个变量,它存储的是另一个变量的内存地址。你可以更改指针指向的地址,也可以更改指针指向的地址处的值。下面是一个简单的例子:

int x = 10;
int* p = &x;  // p是一个指向x的指针
*p = 20;      // 更改p指向的地址处的值,现在x的值变为20
C++
引用是另一个变量的别名,一旦一个引用被初始化指向一个变量,就不能改变引用指向的变量,也就是说,引用始终指向它最初被绑定的对象。下面是一个简单的例子:int x = 10;
int& r = x;  // r是x的引用
r = 20;      // 更改r的值,现在x的值变为20

下面是指针和引用的一些主要区别:

可重新赋值:指针可以重新赋值以指向另一个对象,但引用一旦被绑定到一个对象,就不能重新绑定到另一个对象。
空值:指针可以为null,表示它不指向任何对象;引用必须始终绑定到一个对象,不能为null。

间接性:通过指针访问对象需要解引用指针;通过引用访问对象就像直接访问对象一样。

语法:指针和引用在语法上有所不同,例如,指针需要用*来解引用,而引用不需要。

总的来说,指针更加灵活,但也更容易出错;引用更安全,语法也更简洁。在可能的情况下,建议优先使用引用。

20. 数组名和指针(这里为指向数组首元素的指针)区别?

数组名和指向数组首元素的指针在许多情况下是可以互换的,但它们是不同的概念,有一些重要的区别。

类型:如果arr是一个数组,那么表达式arr的类型是数组,而不是指针。但在大多数表达式中,数组会自动地被转换为指向它的第一个元素的指针。
可赋值性:指针是一个变量,你可以改变它的值使其指向另一个对象。但数组名不是一个变量,你不能改变它的值。例如,如果ptr是一个指针,arr是一个数组,那么ptr = arr是合法的,但arr = ptr是不合法的。

sizeof运算符:sizeof运算符对数组和指针的处理方式不同。如果arr是一个数组,sizeof(arr)会得到整个数组的大小(以字节为单位);如果ptr是一个指针,sizeof(ptr)会得到指针本身的大小,而不是它所指向的数组的大小。

例如:

int arr[10];
int* ptr = arr;std::cout << sizeof(arr) << std::endl;  // 输出40(假设int占4字节)
std::cout << sizeof(ptr) << std::endl;  // 输出8(假设指针占8字节)

指针运算:你可以对指针进行加法或减法运算,使其指向数组中的其他元素。但对数组名进行这样的运算是不合法的。
总的来说,虽然数组名和指向数组首元素的指针在许多情况下可以互换使用,但它们有一些关键的区别,需要根据具体的情况选择使用哪一个。

21. 一个指针占用多少字节?

在C++中,指针的大小并不是固定的,而是取决于系统的架构。在32位系统中,指针通常占用4个字节,而在64位系统中,指针通常占用8个字节。这是因为指针实际上保存的是内存地址,所以它的大小对应于系统可以寻址的内存空间的大小。

这个特性的一个典型应用场景是在创建数据结构(如链表、树等)时。例如,你在创建链表时,每个节点包含数据和指向下一个节点的指针。在这种情况下,指针的大小对于整个数据结构的内存占用是非常重要的。

struct Node {int data;Node* next;
};

在这个例子中,Node 结构的大小将取决于 int 数据的大小和 Node* next 指针的大小。在32位系统中,如果我们假设 int 类型也占用4个字节,那么每个 Node 结构将占用8个字节(4个字节用于数据,4个字节用于指针)。在64位系统中,每个 Node 结构将占用12个字节(4个字节用于数据,8个字节用于指针)。

22. 什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?

智能指针是C++中的一种对象,它像常规指针一样工作,但有更多的功能。最主要的是,智能指针通过自动管理内存来帮助防止内存泄露。它们在销毁对象时自动释放对象所占用的内存,从而使得内存管理更为安全和方便。

C++标准库提供了以下几种类型的智能指针:

std::auto_ptr:这是C++98中的智能指针,但在C++11中已经被弃用。它的主要问题是所有权语义的设计不正确,当复制或赋值时,所有权会从一个指针转移给另一个指针,这可能会引发一些意外的问题。
std::unique_ptr:这是C++11引入的智能指针,它具有独占所有权语义,也就是说,在任何时候,一个std::unique_ptr对象都是它所指向对象的唯一所有者。这使得std::unique_ptr非常适合用于管理资源的唯一性,例如文件句柄,网络连接,或者自定义的资源。

std::shared_ptr:这也是C++11引入的智能指针,它允许多个std::shared_ptr对象共享对同一个对象的所有权。std::shared_ptr使用引用计数来跟踪有多少个智能指针共享同一个对象,当最后一个std::shared_ptr被销毁时,它会自动删除所指向的对象。std::shared_ptr适用于需要多个指针共享所有权的场合,例如实现一个树或图数据结构。

std::weak_ptr:这是一种特殊的智能指针,它是std::shared_ptr的配套工具。std::weak_ptr持有一个非持久的对象引用,不会增加引用计数。它主要用于防止std::shared_ptr引发的循环引用问题。

以下是一个 std::unique_ptr 和 std::shared_ptr 的示例:

#include <memory>struct Foo {Foo() { std::cout << "Foo::Foo\n"; }~Foo() { std::cout << "Foo::~Foo\n"; }void bar() { std::cout << "Foo::bar\n"; }
};void unique_ptr_example() {std::unique_ptr<Foo> p1(new Foo);  // p1 owns Fooif (p1) p1->bar();{std::unique_ptr<Foo> p2(std::move(p1));  // now p2 owns Foof(p2.get());p1 = std::move(p2);  // ownership returns to p1std::cout << "destroying p2...\n";}if (p1) p1->bar();// Foo instance will be destroyed when p1 goes out of scope
}void shared_ptr_example() {std::shared_ptr<int> p1 = std::make_shared<int>(5);std::shared_ptr<int> p2 = p1; // Both now own the memory.std::cout << *p1 << std::endl; // prints "5"*p2 = 10;std::cout << *p1 << std::endl; // prints "10"// Memory will be automatically deallocated when p1 and p2 goes out of scope
}

在这个例子中,你可以看到 std::unique_ptr 和 std::shared_ptr 如何自动管理内存,让你不必手动调用 delete。

23. shared_ptr是如何实现的?

std::shared_ptr 是一个 C++ 智能指针,它是通过引用计数来实现的,能够多个指针对象共享所有权。具体来说,它包含两个主要的元素:

指向管理的实际对象的指针。
指向控制块(Control Block)的指针。这个控制块包含两个引用计数(一个用于 shared_ptr,一个用于 weak_ptr)和管理对象的析构函数。

当你创建一个新的 shared_ptr 并使其指向一个对象,会创建一个新的控制块,并将 shared_ptr 的引用计数设为1。如果你创建了另一个 shared_ptr 并使其指向同一个对象(也就是复制构造或赋值),引用计数会增加。每当 shared_ptr 的一个实例被销毁(例如,它离开了其作用域),引用计数会减少。

当引用计数变为0,表示没有 shared_ptr 指向这个对象了,控制块就会自动调用析构函数并释放对象的内存。然后,控制块会检查 weak_ptr 的引用计数,如果也为0,就会销毁控制块。

std::shared_ptr 还有一个配套的 std::weak_ptr 类型,它也指向控制块,但是不会增加 shared_ptr 的引用计数。weak_ptr 的主要用途是防止 shared_ptr 的循环引用问题。

这是一个 std::shared_ptr 的简单示例:

#include <iostream>
#include <memory>struct Foo {Foo() { std::cout << "Foo::Foo\n"; }~Foo() { std::cout << "Foo::~Foo\n"; }
};void func() {std::shared_ptr<Foo> sp1(new Foo);  // shared_ptr reference counter is 1std::cout << "sp1 use count: " << sp1.use_count() << '\n';{std::shared_ptr<Foo> sp2 = sp1;  // shared_ptr reference counter is 2std::cout << "sp1 use count: " << sp1.use_count() << '\n';std::cout << "sp2 use count: " << sp2.use_count() << '\n';}  // sp2 is out of scope. shared_ptr reference counter becomes 1std::cout << "sp1 use count: " << sp1.use_count() << '\n';
}  // sp1 is out of scope. shared_ptr reference counter becomes 0, Foo object is deletedint main() {func();return 0;
}

这个例子展示了如何使用 std::shared_ptr,以及当 shared_ptr 的引用计数变为0时,对象是如何自动被删除的。

24. 右值引用有什么作用?

右值引用是C++11中引入的一个新特性,主要用于支持移动语义和完美转发。

移动语义:对于一些资源消耗类型的对象(如大型数组或者是使用了大量内存的对象),拷贝构造可能非常昂贵。在这种情况下,如果我们明确知道某个对象不会再被使用,我们可以简单地“移动”其内部资源到新对象,而不是创建一个完全新的拷贝。这可以通过使用右值引用实现,提高程序的性能。
完美转发:右值引用也可以用来实现函数模板中的完美转发,保持函数参数的原始类型(包括它是左值还是右值)。这是通过使用 std::forward 函数实现的。

以下是一个简单的例子,说明了如何使用右值引用来实现移动构造函数,从而实现移动语义:

#include <iostream>class BigArray {int* array;size_t size;public:BigArray(size_t size) : size(size), array(new int[size]) {std::cout << "Constructed\n";}// Move constructorBigArray(BigArray&& other) noexcept : size(other.size), array(other.array) {other.size = 0;other.array = nullptr;std::cout << "Moved\n";}// Copy constructorBigArray(const BigArray& other) : size(other.size), array(new int[other.size]) {std::copy(other.array, other.array + other.size, array);std::cout << "Copied\n";}~BigArray() {delete[] array;}
};BigArray createBigArray(size_t size) {return BigArray(size);
}int main() {BigArray ba = createBigArray(1000000);  // Moved, not Copied!return 0;
}

此例中,BigArray 对象通过移动构造函数从 createBigArray 函数移动到 main 函数,而不是通过复制构造函数复制,这使得程序运行更高效。

25. 悬挂指针与野指针有什么区别?

悬挂指针(Dangling Pointer) 和 野指针(Wild Pointer) 都是编程中可能导致错误或未定义行为的两种不正确的指针使用方式。它们的定义和差异如下:

悬挂指针:悬挂指针是指向已经释放(例如,通过调用 delete 或 free)或者已经超出作用域的内存的指针。当我们试图通过这样的指针访问或操作内存时,就可能导致未定义行为,因为那块内存可能已经被操作系统重新分配给其他程序进行使用了。例如:

int* ptr = new int(5);
delete ptr;
// Now ptr is a dangling pointer.
*ptr = 10;  // Undefined behavior.

野指针:野指针是一个未初始化的指针,也就是说,它的值是未知的,可能指向任意内存地址。如果我们试图通过这样的指针访问或操作内存,同样可能导致未定义行为:

int* ptr;  // ptr is a wild pointer.
*ptr = 10;  // Undefined behavior.

总的来说,悬挂指针是在其指向的内存已经被释放或失效后仍被使用,而野指针则是一开始就未经初始化就被使用的指针。两者都可能导致程序崩溃或数据损坏,因此在编程时需要特别小心。

26. 指针常量与常量指针区别

指针常量(Constant Pointers)和常量指针(Pointers to Constants)是两种不同的C++指针类型,它们的区别在于常量性(constness)的位置和应用:

指针常量(Constant Pointers):这是一个指针,一旦初始化后,就不能再改变指向其它位置。然而,你可以改变指针所指向的内容。定义指针常量的格式如下:

int value = 5;
int *const ptr = &value;

在这个例子中,ptr是一个指针常量,指向value。你不能改变ptr的值使其指向其他位置,但你可以改变value的值。

int anotherValue = 10;
ptr = &anotherValue;  // Error! ptr is a constant pointer.
*ptr = 10;  // OK. Changing the value where ptr points to is fine.

常量指针(Pointers to Constants):这是一个指针,它指向的内容不能被改变,但是你可以改变指针指向的位置。定义常量指针的格式如下:

int value = 5;
const int *ptr = &value;

在这个例子中,ptr是一个常量指针,指向value。你不能通过ptr改变value的值,但你可以改变ptr的值使其指向其他位置。

int anotherValue = 10;
ptr = &anotherValue;  // OK. Changing the pointer to point somewhere else is fine.
*ptr = 10;  // Error! ptr is a pointer to constant.

总的来说,指针常量是不能改变指向的指针,而常量指针是不能改变所指向的值的指针。

27. 如何避免“野指针”

以下是一些避免野指针(未初始化的指针)的常见建议:

初始化指针:在声明指针时,最好立即初始化它,使其指向一个已知的有效对象,或者如果暂时没有有效对象,就设置为 nullptr。

int* ptr = nullptr;

不要对未初始化的指针进行解引用操作:在你确定指针已经被正确初始化并指向一个有效对象之前,不要试图通过指针访问对象。
使用智能指针:C++11 引入了一些智能指针类型,如 std::unique_ptr 和 std::shared_ptr。这些类型在它们的生命周期结束时会自动释放所管理的资源,所以可以减少野指针和悬挂指针的风险。

std::unique_ptr<int> ptr(new int(5));

在删除或释放指针后,立即将其设置为 nullptr:这样可以防止不小心使用已经释放的指针。

delete ptr;
ptr = nullptr;

避免使用裸指针:在可能的情况下,尽量使用引用或智能指针代替裸指针。
通过遵循以上的建议,你可以大大减少在你的代码中遇到野指针的风险。

28. 句柄和指针的区别和联系是什么?

指针和句柄都是用于引用其他资源的抽象,但它们在具体实现和用途上有所不同。

指针:指针是一个变量,其值为另一个变量的地址,即,直接引用内存中的位置。通过指针,可以直接访问和操作其指向的内存。因此,指针提供了一种强大且灵活的机制,用于操作数据和创建复杂的数据结构(如链表和树)。
句柄:句柄是一个抽象,它提供了一个间接引用或者是对某种资源的抽象引用。句柄可能是一个指针、一个整数、一个对象或者其他代表资源的标识符。操作系统通常使用句柄来管理和引用系统资源,如文件、窗口、连接等。句柄的实际实现通常对用户透明,用户只需要知道如何使用它。

指针和句柄的主要区别在于,指针直接引用内存位置,而句柄则是对资源的抽象引用。句柄提供了一种更高级别的抽象,可以隐藏资源的实现细节,使得资源的管理更加灵活和强大。例如,通过使用句柄,操作系统可以在不影响用户的情况下,改变资源的物理位置,或者在需要时,控制对资源的访问。

在某些情况下,句柄可能实际上就是一个指针,但是用户通常不需要(也不应该)知道这个细节,也不应该试图像操作指针那样直接操作句柄。

29. 说一说extern“C”

extern “C” 是一种链接指定符(linkage specification),用于在 C++ 代码中包含 C 语言的函数或者变量。

C++ 支持函数重载,即可以有多个名称相同但参数类型或数量不同的函数。为了支持这种重载,C++ 在编译时会对函数名进行”名字修饰”(name mangling)或”名字装饰”(name decoration),即根据函数的参数类型和数量生成一个唯一的名字。这样,链接器可以区分不同的重载函数。

然而,C 语言并不支持函数重载,也就没有名字修饰的概念。因此,如果你在 C++ 程序中直接调用 C 语言的函数,由于名字修饰的不匹配,链接器可能无法找到正确的函数。

extern “C” 的作用就是告诉 C++ 编译器,按照 C 语言的方式来处理被 extern “C” 修饰的函数或变量,即不进行名字修饰,从而实现 C++ 和 C 语言的互操作。

以下是一个例子:

// This is a C function.
// compiles this function with C linkage
extern "C" void foo(int a, int b) {// function body...
}

在这个例子中,函数 foo 将按照 C 语言的方式进行编译和链接,即它的名字不会被修饰。

同样,extern “C” 也可以用于包含整个 C 语言的头文件,例如:

extern "C" {
#include "c_header.h"
}

在这个例子中,c_header.h 中的所有函数和变量都将按照 C 语言的方式进行编译和链接。

30. 对c++中的smart pointer四个智能指针:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘了释放,造成内存泄漏。使用智能指针可以很大程度上避免这个问题,因为智能指针就是一个类,当超出了类的作用域时,类会自动调用析构函数,析构函数会自动释放资源。所以只能指针的作用原理就是在函数结束时候自动释放内存空间,不需要手动释放内存空间。

智能指针是代理模式的具体运用,它使用 RAII 技术代理了裸指针,能够自动释放内存,无需程序干预。

如果指针是“独占”使用,就应该选择 unique_ptr,它为裸指针添加了很多限制,更加安全。

如果指针是“共享”使用,就应该选择 shared_ptr,它的功能完善,用法几乎和原始指针一样。

shared_ptr 有少量的管理成本,也会引发一些难以排查的错误,所以不要过度使用。

unique_ptr
unique_ptr 是最简单、最容易使用的一个智能指针,在声明的时候必须用模板参数指定类型。

unique_ptr 实现独占式拥有或严格拥有的概念,保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄漏(例如“以 new 创建对象后因为发生异常而忘记调用 delete“)特别有用。

unique_ptr<int> ptr1(new int(10));		// 直接初始化
unique_ptr<int> ptr2 = new int();		// error,构造函数是 explicit
ptr1 += 1;		// error,因为unique_ptr 实际上是个对象,所以不能对其进行指针移动
unique_ptr<int> ptr3;
ptr1 = ptr3;	// 报错,编译器避免了ptr1不再指向有效数据的问题。

另外 unique_ptr 还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器禁止这么做,比如

unique_ptr<string> pu1(new string("hello world"));
unique_ptr<string> pu2;
pu2 = pu1;				// 不允许,留下了悬挂的unique_ptr(pu1),可能会出现问题。
unique_ptr<string> pu3;
pu3 = unique_ptr<string>(new string("hello"));		// 允许,不会留下悬挂的unique_ptr,因为它调用unique_ptr的构造函数,该构造函数创建的临时对象在其所有权让个pu3后就会被销毁。

注意:如果确实想执行类似指针赋值的操作,要安全的重用这种指针,可给它赋新值。C++ 有一个标准库函数 std::move(),让你能够将一个 unique_ptr 赋值给另一个。

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("hhhhhh");

虽然 unique_ptr 名字叫指针,用起来也很像,但是它实际上并不是指针,而是一个对象。所以不能对他调用 detele,它会自动管理初始化的指针,在离开作用域时析构释放内存。

未初始化的 unique_ptr 表示空指针,对其进行使用会导致程序崩溃。

unique_ptr<int> ptr2;
*ptr2 = 2;		// 导致崩溃,因为没有进行初始化,操作了空指针

一个对象只能被单个 unique_ptr 所引用,所以禁止拷贝,而且在向另一个 unique_ptr 赋值的时候,要特别留意,必须用 std::move() 函数显式地声明所有权转移。赋值操作之后,指针的所有权就被转走了,原来的 unique_ptr 变成了空指针,新的 unique_ptr 接替了管理权,保证了所有权的唯一性。不过还是尽量少用 unique_ptr 执行赋值操作,保证其自动化管理。

unique_ptr<int> ptr3(ptr1);		// error,不允许拷贝

shared_ptr
shared_ptr 实现共享式拥有的概念。多个智能指针可以指向相同的对象,该对象和其相关的资源会在“最后一个引用被销毁”时候释放。

shared_ptr 通过引用计数的方式,实现安全共享,可以通过成员函数 use_count() 来查看资源的所有者个数。引用计数最开始时候是 1,表示只有一个使用者,如果发生拷贝赋值(共享)的时候,引用计数增加,发生析构的时候,引用计数减少,只有当引用计数减少到 0 的时候,它才会真正调用 delete 释放内存。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性 (auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针。

成员函数:

use_count() 返回引用计数的个数;unique 返回是否是独占所有权(use_count 为 1)swap 交换两个 shared_ptr 对象(即交换所拥有的对象)reset 放弃内部对象的所有权或拥有对象的变更,会引起原有对象的引用计数的减少。get 返回内部对象(指针),由于已经重载了()方法,因此和直接使用对象是一样的,如 shared_ptr sp(new int(1)); sp 与 sp.get() 是等价的。因为 shared_ptr 支持拷贝赋值,所以它可以在任何场合替代原始指针,而不再担心资源回收的问题。shared_ptr<int> ptr1(new int(10));		// 直接初始化
shared_ptr<int> ptr3(ptr1);		// 允许拷贝
shared_ptr 是线程安全的。

同一个 shared_ptr 被多个线程读是安全的,被多个线程写是不安全的,共享引用计数的不同的 shared 被多个线程写是安全的。

虽然 shared_ptr 更为智能,但是维护引用计数的存储和运算都是需要成本的,虽然因为 shared ptr 内部有比较好的优化,成本比较低。

shared_ptr 的引用计数也导致“循环引用”的问题,这在把 shared_ptr 作为类成员的时候最容易出现。

如果有容易产生“循环引用”的场合,可以考虑使用 weak_ptr,weak_ptr 是专门为了打破循环引用而设计,它只观察指针,不会增加引用计数,但是在需要的时候,可以调用成员函数 lock(),获取 shared_ptr。

weak_ptr
weak_ptr 是一种不控制对象生命周期的智能指针,它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr。weak_ptr 只是提供了对管理对象的一个访问手段。

weak_ptr 设计的目的是为了配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作,它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造,它的构造和析构不会引起引用计数的增加或减少。

weak_ptr 是用来解决 shared_ptr 相互引用时的思索问题,如果说两个 shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为 0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的技术引用,和 shared_ptr 之间可以相互转化,shared_ptr 可以直接复制它,它可以通过 lock 函数来获得 shared_ptr。

class B;
chass A;
{
public:shared_ptr<B> pb_;~A(){count<<"A delete";}
};class B
{
public:shared_ptr<A> pa_;~B(){count<<"B delete";}
};void fun()
{shared_ptr<B> pb(new B());shared_ptr<A> pa(new A());pb->pa_ = pa;pa->pb_ = pb;count<<pb.use_count()<<endl;count<<pa.use_count()<<endl;
}int main()
{fun();return 0;
}

// 可以看到fun函数中pa,pb之间相互引用,两个资源的引用计数为2,当要跳出函数时,
// 智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A B的析构函数没有被调用),
// 如果把其中一个改为weak_ptr就可以了,我们把A里面的shared_ptr pb; 改为 weak_ptr pb;
// 这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变成0,B得到释放,B释放的同时也会使A的计数减1,
// 同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。

// 注意:
// 不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问:pa->pb->print();
// 因为pb是一个weak_ptr,应该先把它转化为shared_ptr,shared_ptr p = pa->pb_.lock(); p->print();

auto_ptr
auto_ptr 被 C++11 弃用,原因是缺乏语言特性如“针对构造和赋值”的 std::move 语义,以及其他瑕疵。

auto_ptr 与 unique_ptr 比较
auto_ptr 可以拷贝赋值,复制拷贝后所有权转移;unique_ptr 无拷贝赋值语义,但实现了 move 语义。
auto_ptr 对象不能管理数组(析构调用 delete),unique_ptr 可以管理数组(析构调用 delete[])。

这篇关于面试专区|【30道C++ 基础高频题整理(附答案背诵版)】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/999004

相关文章

C++中使用vector存储并遍历数据的基本步骤

《C++中使用vector存储并遍历数据的基本步骤》C++标准模板库(STL)提供了多种容器类型,包括顺序容器、关联容器、无序关联容器和容器适配器,每种容器都有其特定的用途和特性,:本文主要介绍C... 目录(1)容器及简要描述‌php顺序容器‌‌关联容器‌‌无序关联容器‌(基于哈希表):‌容器适配器‌:(

C++中实现调试日志输出

《C++中实现调试日志输出》在C++编程中,调试日志对于定位问题和优化代码至关重要,本文将介绍几种常用的调试日志输出方法,并教你如何在日志中添加时间戳,希望对大家有所帮助... 目录1. 使用 #ifdef _DEBUG 宏2. 加入时间戳:精确到毫秒3.Windows 和 MFC 中的调试日志方法MFC

mysql-8.0.30压缩包版安装和配置MySQL环境过程

《mysql-8.0.30压缩包版安装和配置MySQL环境过程》该文章介绍了如何在Windows系统中下载、安装和配置MySQL数据库,包括下载地址、解压文件、创建和配置my.ini文件、设置环境变量... 目录压缩包安装配置下载配置环境变量下载和初始化总结压缩包安装配置下载下载地址:https://d

MySQL中my.ini文件的基础配置和优化配置方式

《MySQL中my.ini文件的基础配置和优化配置方式》文章讨论了数据库异步同步的优化思路,包括三个主要方面:幂等性、时序和延迟,作者还分享了MySQL配置文件的优化经验,并鼓励读者提供支持... 目录mysql my.ini文件的配置和优化配置优化思路MySQL配置文件优化总结MySQL my.ini文件

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

字节面试 | 如何测试RocketMQ、RocketMQ?

字节面试:RocketMQ是怎么测试的呢? 答: 首先保证消息的消费正确、设计逆向用例,在验证消息内容为空等情况时的消费正确性; 推送大批量MQ,通过Admin控制台查看MQ消费的情况,是否出现消费假死、TPS是否正常等等问题。(上述都是临场发挥,但是RocketMQ真正的测试点,还真的需要探讨) 01 先了解RocketMQ 作为测试也是要简单了解RocketMQ。简单来说,就是一个分

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数