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

目录:

1.请问你用过的指针多不?简单说下都用过哪些指针?

2.指针函数和函数指针区别?

3.常量指针 指针常量 常量引用 区别是什么?

4.使用指针需要注意什么?

5.为什么使用智能指针?

6.指针和引用有什么区别?

7.什么是野指针?如何造成的?怎样避免野指针?

内容:

1.请问你用过的指针多不?简单说下都用过哪些指针?

基本指针类型

  • 定义:基本指针类型是指指向基本数据类型(如int、char、float、double等)的指针。这些指针存储的是相应基本数据类型变量的内存地址。
  • 用途:基本指针类型常用于访问和修改基本数据类型变量的值,通过指针可以实现对变量的间接访问。例如,可以在函数间传递变量的地址来修改变量的值。

数组指针

  • 定义:数组指针是一种指向数组的指针。它可以指向整个数组或者数组中的某个元素。数组指针的类型取决于数组的元素类型和数组的大小。
  • 例子:
   int arr[5] = {1, 2, 3, 4, 5};
   int (*ptr)[5] = &arr;  // ptr是一个指向包含5个元素的int数组的指针

  • 这里int (*ptr)[5]是数组指针类型,&arr是取数组arr的地址,将其赋值给ptr
  • 用途:数组指针在处理多维数组时非常有用。

指针数组

  • 定义:指针数组是一个数组,其中的每个元素都是一个指针。这些指针可以指向不同类型的数据,但是在一个指针数组中,通常指针指向的是同一种类型的数据,以便于管理和操作。
  • 例子:
   int num1 = 1, num2 = 2;
   int* ptrArray[2];  // 定义一个包含2个元素的指针数组
   ptrArray[0] = &num1;
   ptrArray[1] = &num2;

  • 这里int* ptrArray[2]是指针数组的定义,它包含两个元素,每个元素都是一个指向int类型变量的指针。
  • 用途:指针数组常用于存储一组同类型的数据地址,比如字符串数组。在 C++ 中,字符串实际上是以'\0'结尾的字符数组,所以可以使用指针数组来存储多个字符串。

函数指针

  • 定义:函数指针是指向函数的指针。函数指针的类型取决于函数的返回值类型和参数列表。函数指针可以用于在运行时动态地选择要调用的函数。
  • 例子:
   int add(int a, int b) {
       return a + b;
   }
   int subtract(int a, int b) {
       return a - b;
   }
   int (*funcPtr)(int, int);  // 定义一个函数指针
   funcPtr = add;  // 将函数指针指向add函数
   int result = funcPtr(3, 2);  // 调用add函数,result的值为5
   funcPtr = subtract;  // 将函数指针指向subtract函数
   result = funcPtr(3, 2);  // 调用subtract函数,result的值为1

  • 这里int (*funcPtr)(int, int)是函数指针的定义,它可以指向返回值为int且有两个int参数的函数。通过将函数指针指向不同的函数,可以实现动态地调用函数。

成员指针

  • 定义:成员指针是指向类的成员(成员变量或成员函数)的指针。成员指针分为指向成员变量的指针和指向成员函数的指针。成员指针的类型取决于类的类型、成员的类型以及成员是否为const等属性。
  • 例子:(指向成员变量的指针):
   class MyClass {
   public:
       int memberVar;
   };
   int main() {
       MyClass obj;
       int MyClass::* ptrToMember = &MyClass::memberVar;
       obj.*ptrToMember = 10;  // 通过成员指针访问和修改成员变量的值
       return 0;
   }

  • 这里int MyClass::* ptrToMember是指向MyClass类成员变量的指针。obj.*ptrToMember是通过对象obj和成员指针来访问成员变量。
  • 例子:(指向成员函数的指针):
   class MyClass {
   public:
       int add(int a, int b) {
           return a + b;
       }
   };
   int main() {
       MyClass obj;
       int (MyClass::* funcPtr)(int, int) = &MyClass::add;
       int result = (obj.*funcPtr)(3, 2);  // 通过成员指针调用成员函数
       return 0;
   }

  • 这里int (MyClass::* funcPtr)(int, int)是指向MyClass类成员函数的指针,通过(obj.*funcPtr)(3, 2)来调用成员函数。

2.指针函数和函数指针区别?

指针函数

指针函数是一个返回指针的函数。换句话说,指针函数的返回类型是指针类型。

语法:

return_type* function_name(parameters);

这里,return_type 是指针类型,表示该函数返回一个指针。

例子:

int* getPointer() {
    int x = 10;
    return &x;  // 返回指向 x 的指针
}

在这个例子中,getPointer 函数返回一个 int* 类型的指针,表示它返回指向整数的指针。

特点:

  • 返回指针:指针函数返回一个指向某种数据类型的指针。
  • 返回地址:指针函数通常返回局部变量或动态分配内存的地址。
  • 返回类型:函数声明中返回类型需要是一个指针类型。

函数指针

函数指针是一个指针变量,它指向一个函数。函数指针可以用来间接调用函数,通常用于回调函数、事件处理、动态链接等场景。

语法:

return_type (*function_pointer_name)(parameter_types);

  • return_type 是函数返回值的类型。
  • parameter_types 是函数的参数类型。

例子:

#include <iostream>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 定义函数指针
    int (*func_ptr)(int, int);
    
    // 将函数指针指向 add 函数
    func_ptr = add;
    
    std::cout << "Add: " << func_ptr(5, 3) << std::endl;  // 调用 add 函数
    
    // 改变函数指针指向 subtract 函数
    func_ptr = subtract;
    
    std::cout << "Subtract: " << func_ptr(5, 3) << std::endl;  // 调用 subtract 函数
    
    return 0;
}

在这个例子中,func_ptr 是一个指向函数的指针。它可以指向不同的函数(如 addsubtract),然后通过 func_ptr 来调用这些函数。

特点:

  • 指向函数:函数指针是用来指向函数的指针变量。
  • 调用函数:通过函数指针可以间接调用函数。
  • 灵活性:函数指针可以动态选择不同的函数进行调用,常用于回调、事件处理等场景。

区别总结

定义

返回指针的函数

指向函数的指针变量

返回值类型

返回类型是一个指针(例如

int*

是一个指向函数的指针,指针类型与函数类型匹配

用途

用于返回指向某个对象或数据的指针

用于间接调用函数,支持回调函数和动态选择函数

语法

int* function_name()

int (*function_ptr)(int, int)

示例

int* getPointer()

int (*func_ptr)(int, int) = add; func_ptr(5, 3);

关键点

返回指向变量或对象的指针

指向一个具有特定签名的函数,并通过指针调用该函数

3.常量指针 指针常量 常量引用 区别是什么?

常量指针

  • 常量指针概念:常量指针是一个指针,它指向的内容是常量。也就是说,通过这个指针不能修改它所指向的数据,但指针本身的值(即它指向的地址)可以改变。
  • 语法形式:const 数据类型* 指针变量名;
     int a = 10;
     int b = 20;
     const int* p = &a;
     // *p = 30; // 这是错误的,不能通过p修改它指向的值
     p = &b; // 这是正确的,p可以指向另一个变量

  • 解释:在这个例子中,p被定义为一个常量指针,指向a。我们不能通过p来修改a的值,因为p所指向的内容被视为常量。但是,p本身可以重新指向其他变量,比如让它指向b

指针常量

  • 指针常量概念:指针常量是一个常量,它一旦被初始化指向一个地址后,就不能再指向其他地址,但可以通过这个指针修改它所指向的数据。
  • 语法形式:数据类型* const 指针变量名;
     int a = 10;
     int* const p = &a;
     *p = 20; // 这是正确的,可以通过p修改它指向的值
     // p = &b; // 这是错误的,p不能再指向其他地址

  • 解释:这里p是一个指针常量,它被初始化为指向a。之后就不能再让p指向其他变量了,但是可以通过p来修改a的值,如将a的值修改为20

常量引用

  • 常量引用概念:常量引用是一种引用,它绑定到一个常量对象,通过这个引用不能修改它所绑定的对象的值。常量引用主要用于函数参数传递等场景,可以避免在函数内部意外修改参数的值。
  • 语法形式:const 数据类型& 引用变量名;
     int a = 10;
     const int& r = a;
     // r = 20; // 这是错误的,不能通过r修改它绑定的值
     int b = 30;
     // r = b; // 这也是错误的,不能通过r修改它绑定的值

  • 解释:在这个例子中,r是一个常量引用,绑定到a。不能通过r来修改a的值。即使有另一个变量b,也不能通过ra的值修改为b的值。常量引用在函数参数中很有用,比如void func(const int& param)这样的函数定义,在函数内部就不能修改param的值。

注意:并没有“引用常量”这个概念

4.使用指针需要注意什么?

初始化指针

  • 未初始化的指针(野指针)可能会指向不确定的内存位置,从而导致未定义行为。始终确保指针在使用之前被初始化。

避免悬空指针

  • 悬空指针指的是指向已释放内存的指针。使用 deletefree 后,应该将指针设为 nullptr,以避免意外访问已释放的内存。

指针越界

  • 指针越界发生在你试图访问指针指向的内存区域之外。特别是在操作数组时,越界访问会导致程序崩溃或不可预测的行为。确保指针不会超出合法内存范围。

避免野指针

  • 野指针是指一个指向已经被释放或未初始化的内存地址的指针。它通常会导致程序崩溃或数据损坏。

内存泄漏

  • 内存泄漏是指程序分配了内存但没有释放,导致内存无法回收。使用指针时,必须确保每次分配内存后都对应一次 deletefree 操作。

深拷贝与浅拷贝

  • 指针类型的变量在传递时,默认是传递指针本身(浅拷贝)。如果你希望复制的是指针所指向的数据,而不仅仅是指针本身,需要进行深拷贝。这对于避免多个指针指向同一内存区域,并在释放时发生冲突。

指针类型匹配

  • 指针在操作时,必须确保指针类型匹配。例如,不能将 int* 类型的指针赋值给 char* 类型的指针,除非你明确了解数据的布局并且使用类型转换。

指针运算

  • 指针可以进行算术运算(例如递增或递减),但必须小心使用。指针运算会根据指针的类型调整偏移量,例如,对于 int* 指针,每次递增将增加 4 字节(在 32 位机器上)。

5.为什么使用智能指针?

自动内存管理:智能指针自动管理内存,不需要显式调用 delete,避免了内存泄漏和悬挂指针问题。

异常安全:智能指针能够确保即使发生异常,内存也能被正确释放,避免了因为异常导致的资源泄漏。

简化代码:智能指针让代码更简洁,不需要手动管理内存释放,使得资源管理更直观和安全。

6.指针和引用有什么区别?

基本概念

  • 指针(Pointer):指针是一个变量,它存储的是另一个变量的内存地址。通过指针,可以间接访问该内存地址上存储的值。
  • 引用(Reference):引用是一个别名,表示对某个变量的引用。引用本身不占用内存空间,它只是一个现有变量的别名,使用引用就像使用原始变量一样。

语法区别

  • 指针:声明时使用 *。
  • 可以改变指向的对象。
  • 指针可以为空(nullptr 或 NULL)。
  • 例子:
  • int x = 10;
    int y = 20;
    int* p = &x;  // p 是指向 x 的指针
    p = &y;  // p 现在指向 y
    
  • 引用
  • 声明时使用 &
  • 引用必须在声明时初始化,并且初始化后不能改变引用的对象。
  • 引用不能为 nullptr

例子:

int x = 10;
int y = 20;
int& r = x;  // r 是 x 的引用
r = y;  // r 现在不再是 x 的引用,而是 y 的引用

能否改变目标

  • 指针:指针的值(即指向的内存地址)是可以改变的,指针可以指向不同的对象。
  • 引用:一旦引用被初始化,它就绑定到某个对象上,不能再改变引用的目标。

空值

  • 指针:指针可以为空,即指针可以不指向任何对象。通过 nullptr 可以明确表示指针不指向任何地方。
  • 引用:引用必须绑定到一个有效的对象上。C++ 中没有 "空引用" 的概念,如果没有初始化引用,编译器会报错。

解引用

  • 指针:可以使用 * 来解引用指针,访问指针所指向的值。
  • int x = 10;
    int* p = &x;
    std::cout << *p << std::endl;  // 输出 10
    
  • 引用:引用本身就是一个对象的别名,使用引用就像使用原始变量一样,无需解引用操作。
int x = 10;
int& r = x;
std::cout << r << std::endl;  // 输出 10

指向常量

  • 指针指向常量:如果指针指向的是常量数据(例如 const 修饰的变量),则需要在指针声明时使用 const
const int x = 10;
const int* p = &x;  // p 是指向常量 int 的指针
  • 引用指向常量:引用也可以绑定到常量,声明时使用 const
const int x = 10;
const int& r = x;  // r 是指向常量 int 的引用  

内存分配

  • 指针:指针本身是一个变量,存储着地址值,它通常需要自己分配内存。如果指针是动态分配的,必须显式地释放内存(如使用 delete)。
  • 引用:引用没有自己的内存空间,它只是一个别名。它通常不会直接分配额外的内存,且引用的对象生命周期由原始变量控制。

总结对比

初始化

必须初始化,但可以为空 (nullptr)

必须初始化,并且不可为空

能否改变目标

可以改变指向的对象

不能改变目标对象

语法

使用 *&

使用 &

是否需要解引用

需要使用 *解引用

不需要解引用,直接使用引用

空引用

支持空指针(nullptr

不支持空引用

内存管理

指针需要手动管理内存(newdelete

无需手动管理内存

常见用途

动态内存分配、多态、链表等

传递参数、返回值引用、简化代码

7.什么是野指针?如何造成的?怎样避免野指针?

野指针:是指一个指向已经被释放或不存在的内存位置的指针。野指针通常是因为内存被释放后,指针依然保持原来的值,导致指针指向无效的内存区域。访问或解引用这种指针会导致未定义的行为,包括程序崩溃或数据损坏。

产生野指针的原因

  • 释放了内存后仍然使用指针:当一个指针指向的内存已经被 deletefree 释放后,如果指针依然指向这块内存,那么这个指针就变成了野指针。
int* p = new int(10);
delete p;  // 释放了内存
// 这里 p 是野指针,指向已经释放的内存
std::cout << *p << std::endl;  // 未定义行为
  • 局部指针超出作用域:如果指针指向了一个局部变量,而这个局部变量在指针使用前已经超出了作用域(即局部变量被销毁),指针也会变成野指针。
int* p;
{
    int x = 10;
    p = &x;  // p 指向了 x
}  // x 在此作用域结束后销毁
// 这里 p 是野指针,指向已经销毁的变量
std::cout << *p << std::endl;  // 未定义行为
  • 返回局部变量的指针:如果一个函数返回指向局部变量的指针,当函数退出时,局部变量会被销毁,导致返回的指针变成野指针。
int* func() {
    int x = 10;
    return &x;  // 返回指向局部变量 x 的指针
}
// 返回的指针指向一个已经销毁的局部变量
int* p = func();  // p 是野指针
std::cout << *p << std::endl;  // 未定义行为
  • 重复释放内存(双重释放):如果 deletefree 被多次调用在同一块内存上,会导致程序崩溃或其他不可预料的行为。这个问题有时被认为是“野指针”问题的一种表现。
    int* p = new int(10);
    delete p;
    delete p;  // 双重释放,导致未定义行为
    

如何避免野指针

  • 避免使用悬空指针:在释放指针所指向的内存后,立即将指针设置为 nullptrNULL,这可以防止指针变成野指针并避免意外访问。
int* p = new int(10);
delete p;
p = nullptr;  // 设置为 nullptr,避免野指针
  • 使用智能指针:C++11 引入的智能指针(如 std::unique_ptrstd::shared_ptr)能够自动管理内存。当智能指针超出作用域时,会自动释放内存,从而避免野指针的发生。
std::unique_ptr<int> p = std::make_unique<int>(10);
// p 超出作用域时会自动释放内存
  • 避免返回指向局部变量的指针:通过返回指向局部变量的指针来避免野指针问题。可以通过返回指向堆上分配的内存或使用引用来避免。
int* func() {
    int* p = new int(10);  // 分配堆内存
    return p;  // 返回指向堆内存的指针
}
  • 使用 nullptr 检查指针:在使用指针之前,始终检查指针是否为 nullptr,这有助于避免解引用野指针。
if (p != nullptr) {
    std::cout << *p << std::endl;  // 解引用前检查
}
  • 避免重复释放内存:确保每个 deletefree 只调用一次。可以在 delete 后将指针设为 nullptr 来防止重复释放。
int* p = new int(10);
delete p;
p = nullptr;  // 防止再次释放
#牛客创作赏金赛##面经##面试##嵌入式##C/C++#
嵌入式/C++八股 文章被收录于专栏

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

全部评论
看上去还行
1 回复 分享
发布于 12-13 21:48 陕西
欢迎大家订阅此专栏,订阅点赞收藏送花后,截图si聊我,免费赠送嵌入式学习资料大礼包(含简历模版、c/c++、嵌入式等资料)
1 回复 分享
发布于 12-14 15:58 陕西

相关推荐

一转眼已经本科毕业五个月了,来到团子也一百多天下面是我的一些经历分享🕒&nbsp;秋招9月开始投递,到十二月签订三方秋招一共收到三家互联网offer:京东、字节跳动、团子,在最后我会分享我的面试经验仅供参考~bg太普通了可能很多人都觉得不可能:北京双非财经本科简历加分项:实习+比赛大学期间三段实习:新东方营销、五百强外企市场部、美团互联网运营科研竞赛:十几段科研竞赛获奖(有一个国一),一个北京市重点大科创负责人💼&nbsp;岗位:运营说实话,以前从来没有想到自己会做运营,只能说互联网的实习经历比重太大,后来接到的面试和offer都是运营的,所以还是建议大家多多实习,积累大厂实习经验。🫶️&nbsp;团队氛围团队氛围:美团的校招生机制对于新人很友好,会有1对1的导师带着学习,从执行类的工作到让你主R负责,可以得到全流程的学习和成长。公司文化:苦练基本功、金字塔原理、star法则在美团说话、写文档的逻辑真的很重要面试经验1.广泛的投递。广泛的投递是争取到面试机会的基础,现在就业环境不好,大家都很卷,到处都是人才,只有多投递才会增加自己简历的曝光机会。2.接到面试后仔细研究岗位bg。了解岗位要求,针对岗位有针对性的准备自己的介绍。3.熟练运用star法则介绍自己。这里我不多介绍了,这个就是一个逻辑(在美团很重要)这里是互联网职场新人~欢迎大家来交流讨论~如果觉得有一点共鸣或者帮到你了,求一朵免费的小花花呗🌸 #非技术求职现状# #我的工作日记# #我的岗位说明书# #美团求职进展汇总# #美团工作体验# #美团校招#
点赞 评论 收藏
分享
评论
11
12
分享
牛客网
牛客企业服务