史上最全的C++面试宝典(三)—— 指针和引用

本文是C++面试系列的第三部分,详细介绍了指针和引用的概念、用法及其在面试中常见的问题。包括指针的定义、使用、常用操作,以及引用作为变量别名的特性,与指针的区别,并给出了多个面试题及其答案,帮助读者深入理解这两部分内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考:https://www.runoob.com/cplusplus/cpp-tutorial.html

本教程旨在提取最精炼、实用的C++面试知识点,供读者快速学习及本人查阅复习所用。

目录

第三章  指针和引用

3.1  指针

3.2  引用

3.3  相关面试题


第三章  指针和引用

3.1  指针

3.1.1  指针定义

指针是一个变量,其值为另一个变量的内存地址。指针变量声明的一般形式为:

type *var-name;
int *ip;    /* 一个整型的指针 */

所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

3.1.2  指针的使用

使用指针时会频繁进行以下几个操作:定义一个指针变量、把变量地址赋值给指针、访问指针变量中可用地址的值。这些是通过使用一元运算符 * 来返回位于操作数所指定地址的变量的值。

#include <iostream>
 
using namespace std;
 
int main ()
{
   int  var = 20;   // 实际变量的声明
   int  *ip;        // 指针变量的声明
 
   ip = &var;       // 在指针变量中存储 var 的地址
 
   cout << "Value of var variable: ";
   cout << var << endl;
 
   // 输出在指针变量中存储的地址
   cout << "Address stored in ip variable: ";
   cout << ip << endl;
 
   // 访问指针中地址的值
   cout << "Value of *ip variable: ";
   cout << *ip << endl;
 
   return 0;
}

其结果为:

Value of var variable: 20
Address stored in ip variable: 0xbfc601ac
Value of *ip variable: 20

3.1.3  常用指针操作

//空指针
int  *ptr = NULL;
cout << "ptr 的值是 " << ptr ;  //结果是:ptr 的值是 0

//指针递增
int  var[3] = {10, 100, 200};
ptr = var;   //数组的变量名代表指向第一个元素的指针
ptr++;

//指向指针的指针
int  var;
int  *ptr;
int  **pptr;

var = 3000;
// 获取 var 的地址    
ptr = &var;
// 使用运算符 & 获取 ptr 的地址
pptr = &ptr;

3.2  引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。引用很容易与指针混淆,它们之间有三个主要的不同:

  1. 不存在空引用。引用必须连接到一块合法的内存。
  2. 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  3. 引用必须在创建时被初始化。指针可以在任何时间被初始化。
// 声明简单的变量
int i;
double d;
 
// 声明引用变量
int& r = i;
double& s = d;

3.3  相关面试题

Q:C/C++ 中指针和引用的区别?

A:

  1. 指针有自己的一块空间,而引用只是一个别名;
  2. 指针可以被初始化为NULL,而引用必须被初始化;
  3. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变;
  4. 指针可以有多级指针(**p),而引用只有一级;

Q:指针函数和函数指针?

A:

  • 指针函数本质上是一个函数,函数的返回值是一个指针;
  • 函数指针本质上是一个指针,C++在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址,有了函数指针后,就可用该指针变量调用函数。
char * fun(char * p)  {…}       //  指针函数fun
char * (*pf)(char * p);             //  函数指针pf
pf = fun;                        // 函数指针pf指向函数fun
pf(p);                        // 通过函数指针pf调用函数fun

Q:在什么时候需要使用“常引用”?

A:如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。

Q:C++中的四个智能指针: shared_ptr、unique_ptr、weak_ptr、auto_ptr

A:智能指针出现的原因:智能指针的作用就是用来管理一个指针,将普通的指针封装成一个栈对象,当栈对象的生命周期结束之后,会自动调用析构函数释放掉申请的内存空间,从而防止内存泄露。(https://www.cnblogs.com/WindSun/p/11444429.html

  1. shared_ptr实现共享式拥有概念。多个智能指针指向相同对象,该对象和其相关资源会在最后一个引用被销毁时被释放。
  2. unique_ptr实现独占式拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
  3. weak_ptr 是一种共享但不拥有对象的智能指针, 它指向一个 shared_ptr 管理的对象。进行该对象的内存管理的是那个强引用的 shared_ptr,weak_ptr只是提供了对管理对象的一个访问手段,它的构造和析构不会引起引用计数的增加或减少。weak_ptr 设计的目的是为协助 shared_ptr工作的,用来解决shared_ptr相互引用时的死锁问题。注意的是我们不能通过weak_ptr直接访问对象的方法,以通过调用lock函数来获得shared_ptr,再通过shared_ptr去调用对象的方法。
  4. auto_ptr采用所有权模式,C++11中已经抛弃。

Q:shared_ptr的底层实现

A:

template <typename T>
class smart_ptrs {
public:
    smart_ptrs(T*); //用普通指针初始化智能指针
    smart_ptrs(smart_ptrs&); // 拷贝构造
    T* operator->(); //自定义指针运算符
    T& operator*(); //自定义解引用运算符
    smart_ptrs& operator=(smart_ptrs&); //自定义赋值运算符
    ~smart_ptrs(); //自定义析构函数
private:
    int *count; //引用计数
    T *p; //智能指针底层保管的指针
};

//构造函数
template <typename T>
smart_ptrs<T>::smart_ptrs(T *p): count(new int(1)), p(p) {}

//对普通指针进行拷贝,同时引用计数器加1,因为需要对参数进行修改,所以没有将参数声明为const
template <typename T>
smart_ptrs<T>::smart_ptrs(smart_ptrs &sp): count(&(++*sp.count)), p(sp.p)  {}

//指针运算符
template <typename T>
T* smart_ptrs<T>::operator->() {return p;}

//定义解引用运算符
template <typename T>
T& smart_ptrs<T>::operator*() {return *p;}

//定义赋值运算符,左边的指针计数减1,右边指针计数加1,当左边指针计数为0时,释放内存:
template <typename T>
smart_ptrs<T>& smart_ptrs<T>::operator=(smart_ptrs& sp) {
    ++*sp.count;
    if (--*count == 0) { //自我赋值同样能保持正确
        delete count;
        delete p;
    }
    this->p = sp.p;
    this->count = sp.count;
    return *this;
}

// 定义析构函数:
template <typename T>
smart_ptrs<T>::~smart_ptrs() {
    if (--*count == 0) {
        delete count;
        delete p;
    }
}

Q:野指针

A:野指针就是指向一个已销毁或者访问受限内存区域的指针。产生野指针通常是因为几种疏忽:

  1. 指针变量未被初始化;
  2. 指针释放后未置空;
  3. 指针操作超越变量作用域(例如变量被释放了,指针还是指向它)。

Q:什么时候会发生段错误?

A:段错误通常发生在访问非法内存地址的时候,具体来说分为以下几种情况:

  • 使用了野指针
  • 试图修改字符串常量的内容
  • 数组越界导致栈溢出Q:什么是右值引用,跟左值又有什么区别?

A:左值:能对表达式取地址的具名对象/变量等。一般指表达式结束后依然存在的持久对象。

右值:不能对表达式取地址的字面量、函数返回值、匿名函数或匿名对象。一般指表达式结束就不再存在的临时对象。

右值引用和左值引用的区别在于:

  • 通过&获得左值引用,左值引用只能绑定左值。
  • 通过&&获得右值引用,右值引用只能绑定右值,基于右值引用可以实现移动语义和完美转发,右值引用的好处是减少右值作为参数传递时的复制开销,提高效率。

Q:什么是std::move()以及什么时候使用它?

A:std::move()是C ++标准库中用于转换为右值引用的函数。当需要在其他地方“传输”对象的内容时使用std :: move,对象可以在不进行复制的情况下获取临时对象的内容,避免不必要的深拷贝。

Q:C++类的内部可以定义引用数据成员吗?

A:可以,必须通过成员函数初始化列表初始化

class MyClass
{
public:
    MyClass(int &i):  a(1),   b(i){         //  构造函数初始化列表中是初始化工作
        //   在这里做的是赋值而非初始化工作
    }
private:
    const int a;
    int &b;  // 引用数据成员b,必须通过列表初始化!
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值