0

我很难知道何时使用指针与引用。我的问题是:在 Java/C# 中,您可以将对象作为参数传递给函数,然后将此参数分配给内部类变量,以便以后可以在方法范围之外使用它。但是,在 C++ 中,我不确定如何实现相同的目标。如果我通过引用传递,我只能在该方法的范围内使用它。如果我将引用分配给相同类型的内部变量,则其中一个变量的更改不会影响另一个变量。我也不能声明未初始化的引用(可能通过构造函数)。我发现的唯一解决方案是每次我需要使用它时传入引用(作为参数)或传入一个指针而不是一次(例如

这是我用于测试的示例方法:

  1. 最初,get 和 setValue 引用的值设置为零。
  2. 我调用 Controller2.initialize(Controller &controller, Controller *controllerPtr)
  3. 我调用 Controller2::process(Controller &controller)

输出显示在下面的代码块之后

#include "Controller2.h"

Controller2::Controller2()
{
}

void Controller2::initialize(Controller &controller, Controller *controllerPtr)
{
    _controller = controller;
    _controllerPtr = controllerPtr;

    Controller &controllerRef = *_controllerPtr;

    controller.setValue(5);
    Serial.println("");
    Serial.print("_Controller in initialize(): ");
    Serial.print(_controller.getValue());
    Serial.print("  Controller in initialize(): ");
    Serial.print(controller.getValue());

    Serial.print("  Controller Ptr in initialize(): ");
    Serial.print(controllerRef.getValue());

    Serial.println();
}

void Controller2::process(Controller &controller)
{
    Serial.println("");
    Serial.print("_Controller in process(): ");
    Serial.print(_controller.getValue());
    Serial.print("  Controller in process(): ");
    Serial.print(controller.getValue());

    Controller &controllerRef = *_controllerPtr;
    Serial.print("  Controller Ptr in process(): ");
    Serial.print(controllerRef.getValue());

    Serial.println();
}

控制器2.h:

#include "Arduino.h"
#include "Controller.h"

#ifndef Controller2_h
#define Controller2_h

class Controller2
{
public:
    Controller2();
    void initialize(Controller &controller, Controller* controllerPtr);
    void manage();
    void process(Controller &controller);

private:
    Controller _controller;
    Controller* _controllerPtr;
};

#endif

控制器类:

#include "Controller.h"

Controller::Controller()
{
}

void Controller::initialize()
{
}

void Controller::setValue(int val)
{
    value = val;
}

int Controller::getValue()
{
    return value;
}

控制器.h:

#include "Arduino.h"

#ifndef Controller_h
#define Controller_h

class Controller
{
public:
    Controller();
    void initialize();
    void manage();
    void setValue(int val);
    int getValue();

private:
    int value = 0;
};

#endif

主要课程:

#include <Arduino.h>
#include <Controller.h>
#include <Controller2.h>

Controller controller;
Controller2 controller2;

void setup()
{
  Serial.begin(115200);

  Serial.println("");
  Serial.print("Controller initial: ");
  Serial.print(controller.getValue());
  Serial.println();

  controller2.initialize(controller, &controller);
  controller2.process(controller);
}

void loop()
{
}

输出结果为:

Controller initial: 0

_Controller in initialize(): 0  Controller in initialize(): 5  Controller Ptr in initialize(): 5

_Controller in process(): 0  Controller in process(): 5  Controller Ptr in process(): 5

这是正确的还是我在这里遗漏了什么?

4

3 回答 3

3

事实上,C++ 引用在许多方面都表现得像指针,但没有特定于指针的语法。对于指针和引用,您拥有指向对象的指针/引用,然后拥有对象本身,并且指针/引用的生命周期可能与它指向/引用的对象的生命周期不同,所以如果指针/引用超过对象的寿命,您必须非常小心,不要在对象被销毁后取消引用指针/引用,否则您将调用未定义的行为并且您的程序将无法正常运行。

因此,例如这是有效的:

class Controller2
{
public:
   Controller2(Controller & controllerRef) 
      : _controllerRef(controllerRef)
   {/*empty*/}

private:
   Controller & _controllerRef;
};

...并且行为与基于指针的实现大致相同:

class Controller2
{
public:
   Controller2(ControllerPtr * controllerPtr) 
      : _controllerPtr(controllerPtr)
   {/*empty*/}

private:
   Controller * _controllerPtr;
};

...主要区别在于,在基于引用的实现中,用户没有(合法的)方法可以将 NULL 引用传递给 Controller2 构造函数,因此您的代码不必担心检查 _controllerRef 来查看如果它是NULL,因为语言保证它不会是NULL(或者更具体地说,它说如果引用是NULL,那么程序已经无法修复,所以你可以假设它不是NULL)。

在这两种情况下,将原始指针/引用传递给外部对象都有一定的风险,因此除非您有其他方法来保证指向/引用的对象将比任何可能的 _controllerRef 取消引用更有效,否则您可能会更好制作对象的私有副本,或者如果这不切实际,则使用 shared_ptr 或 unique_ptr 之类的东西来保证在您停止持有对它的任何引用之前不会破坏被引用的对象。

于 2021-12-26T20:30:47.780 回答
2

试着从记忆所有权的角度来思考问题。C++ 没有垃圾收集器,因此管理谁拥有哪些内存是您的工作。

通常,内存的所有者自己维护实际内存,或者在大(或虚拟)数据的情况下,维护std::unique_ptr它。std::unique_ptr就像一个原始指针,除了它强制(a)当你完成它时清理内存,以及(b)在给定时刻只有一个指向数据的唯一指针。

如果您需要让某人借用数据(即让函数对其进行处理),那么您需要传递一个引用。MyClass&是一种查看其他人的MyClass实例并可能对其进行修改的类型。const MyClass&是一种查看别人的MyClass而不修改它的类型(出于显而易见的原因,除非需要可变性,否则您应该默认使用后者)。

如果你需要一个值来拥有多个所有者,那么首先考虑一下你是否真的需要它。很多东西都适合单一所有权模型,通常通过一些小的重组,你可以通过引用和唯一指针很好地完成。但是,如果您确实需要多所有权,则可以使用std::shared_ptrandstd::weak_ptr来获得该行为(强引用与弱引用;您可以在这些链接上阅读有关差异的更多信息)。但这应该是例外而不是常态。

MyClass*您应该几乎从不使用原始指针(即) 。事实上,作为一个 C++ 初学者,你应该永远不要使用原始指针。对于集合,使用std::vector,而不是衰减为指针的原始数组(有理由使用后者,但这些是技术性的,只会在刚开始时混淆)。对于个人数据,使用如上所述的单一所有权或多重所有权。如果你在MyClass*某处写,你应该能够用引用或智能指针(唯一、共享或弱指针)替换它。如果您编写newdelete在某处编写,您应该能够用智能指针构造函数或简单的值分配来替换它(许多 Java 开发人员开始使用 C++ 发现自己编写MyClass* x = new MyClass()什么时候MyClass x{}会做得很好)。

我的最后一个主要 C++ 项目是一个 13kloc 编程语言解释器,我在其中使用了一个原始指针(我特别记得当我做出这个让步时),在关键路径中实现一个模糊的优化技巧。有一段半的评论解释了为什么我必须这样做以及谁实际拥有内存,因为类型不再传达该信息。其他一切都是引用和智能指针。当你掌握了它的窍门时,你几乎不需要实际的原始指针。

最后,提几点建议。

  1. Java 和 C# 严重依赖null. 尽量避免nullptr/NULL在 C++ 中。如果您想要一个可能存在也可能不存在的值,这std::optional是惯用的方法(如果您无权访问 C++17,Boost 有一个仅标头库可以执行相同操作)。特别是,您永远不应该仅出于允许的目的而将值变成指针nullptr;这std::optional就是专门设计的。

  2. 您可能会在网上遇到一些使用std::auto_ptr. 不要使用这种类型。std::unique_ptr做得更好。std::auto_ptr是一个损坏的实现,永远不应该使用。

于 2021-12-26T20:33:56.347 回答
0

其他答案给出了 C++ 的观点。从 Java/C# 的角度来看,给您一个起点:

shared_ptr在 Java(或 C#)中保存对象的变量大致对应于在 C++中保存 a 的变量。存在差异,因此不要期望您应该(甚至可以)简单地“思考 Java”并使用shared_ptr. (使用 C++ 编码时应该“思考 C++”,就像使用 Java 编码时应该“思考 Java”一样。)但是,在比较 Java 代码和 C++ 代码时,将 Java 对象视为 C++ 共享指针涵盖了 80% 的情况。当您尝试将另一种语言(Java 或 C#)的语义应用于 C++ 代码时,这应该可以帮助您了解自己。

一些额外的阅读:


即使您询问“传递”(暗示函数参数),您的症状背后的真正问题似乎是_controller成员。这既不是引用也不是指针;它是一个对象。每个Controller2对象都有一个Controller对象作为成员。对象的数量Controller将至少是Controller2对象的数量。这些不共享。该行_controller = controller;没有_controller引用与 相同的对象controller。不,它将值复制到_controllerfrom controller,与分配int变量复制值的方式相同。

查看这一点的一种方法是在代码中添加一行:

    controller.setValue(5);   \\ existing line, affects parameter
    _controller.setValue(20); \\ new line, affects member

这为您的两个Controller对象提供了彼此不同的值,并且与初始值不同。(这两个Controller对象是名为的全局变量controller和名为的全局变量的_controller成员controller2。)

一些额外的阅读:

于 2021-12-26T22:44:31.320 回答