指针、& 符号和指针?

Pointers, ampersands and pointers again?

我只是无法理解指针 - 我应该何时以及如何使用指针。无论我阅读了多少关于它的视频和文献,我就是不明白我应该何时以及如何使用它。明明我就是那么厚实...

让我们看看这个例子:

int main()
{
    int* ptr = new int(10);

    std::cout << ptr << std::endl; 
    std::cout << *ptr << std::endl; 
    std::cout << &ptr << std::endl;
}

这将 return 我们这样的值:

000001A68ABB5250 
10 
000000455ADCF828

这些输出是什么意思?第一个和第三个看起来像是内存中的地址编号,但它们不同,我不明白它们应该做什么?第二个是我赋的值,但是为什么要用指针符号来获取呢?

让我们看看这个:

int main()
{
    int ptr = 10;

    std::cout << ptr << std::endl; 
    //std::cout << *ptr << std::endl; //gives "Error(active)    E0075   operand of '*' must be a pointer but has type 'int'"
    std::cout << &ptr << std::endl;
}

其中分别有returns:

10
000000D9242FF5F4

上面的代码与此示例代码有何不同?为什么我不必使用指针来获取值? & 号向我们展示了内存地址,对吗?

基本上有人可以用最愚蠢、最容易理解的方式向我解释指针的意义是什么以及为什么我们在 C++ 中需要它们。

如果我们“绘制”第一个示例,它将是这样的:

+------+     +-----+     +--------------+
| &ptr | --> | ptr | --> | *ptr int(10) |
+------+     +-----+     +--------------+

所以 ptr 指向一个 int 值(初始化为 10 10)。 &ptr 指向变量 ptr.


在第二个示例中,变量名称 ptr 具有误导性,因为它不是指针。这是一个普通的 int 变量。

画出来就会像

+------+     +-------------+
| &ptr | --> | ptr int(10) |
+------+     +-------------+

当我第一次开始编程时,指针是我刚刚接受它们工作的东西之一,并最终积累了足够的经验 learn/understand 它们。

在我的一篇 类 中,一位教授说当你想修改一个对象而不是复制它时最好使用指针。按值传递给函数会创建在其中传递的对象的副本。

首先要理解的是符号*&的含义取决于上下文。

int* ptr中,*表示“这是一个指针”。

但除此之外(例如在 cout << *ptr 中),* 表示“取消引用”指针。 IE。给定一个指向 X 的指针,* returns X,不管它是什么。

&(例如在 cout << &ptr 中)“获取地址”。给定 X,它会为您提供指向 X 的指针。

&*(第二种意义上)是相反的运算,它们相互抵消。

int* ptr = new int(10); 有点 类似于 int x = 10; int *ptr = &x;,这反过来意味着 int x = 10; int *ptr; ptr = &x;(不是 *ptr = &x,因为 * 在这里意味着不同的东西,见上文)。

因此,给定 int x = 10;&x 是一个“指向 x 的指针”(打印它会给您一个地址)。在你执行 int *ptr = &x 之后,ptr 也是一个“指向 x 的指针”,因此打印它会显示相同的地址。

*ptr 给你 x,就像 *&x(这意味着 *(&x))一样,因为 *& 取消了每个其他的。


指针可以指向其他指针。 &ptr 给你一个“指向 ptr 的指针”,或“指向 x 的指针”,其类型为 int ****&ptr 因此 return x 的值。

我不是一个很好的解释者,但总体概述是:

声明和定义后int* ptr = new int(10);

当你使用ptr时,它是int*类型的名称,如int x = 5,当你使用x时,它是int类型。

ptr是一个指针所以,它是一个存储在地址中的变量,所以当你打印它时,它就是一个地址。

&ptr 将是指针的地址,是的,指针也有地址。基本上,它是 int** 类型,所以打印时也是一个地址。

*ptr 取消对指针的引用,因此它将获得存储在指针指向的内存位置中的值 10。可以把它想象成,每个地址都指向一个存储值的内存位置。

如果它只是普通的 int ptr = 10,您不能取消引用它:应用 *ptr 因为它是 int 类型而不是 int* 的指针。

但是,如果您将 &ptr 应用于它,它将获得 int ptr 的地址,该地址本质上是 int*.

类型

例如,如果您这样做:int x = 5; int *ptr = &x;, 如果您取消引用 ptr,并执行:cout << *ptr,它将打印 5.

我第一次也遇到了困难。没关系。我花了一个月左右的时间来解决这个问题。事实上,我仍然有一些事情让我对指针感到困惑。哦,好吧,我不太会解释,大概和你看的差不多吧……祝你好运。

简介:

int* ptr = new int(10);

ptr(int)就是你在右边分配的 int 的内存地址(一个数字)。

std::cout << ptr << std::endl;

然后是打印出新int的内存地址。这是因为 ptr 只是一个数字 恰好是一个内存地址。

std::cout << *ptr << std::endl;

这里你告诉C++访问ptr所在编号的内存地址。因为ptr是int的内存地址,所以会得到int的值。

std::cout << &ptr << std::endl;

最后,你告诉C++给你ptr的内存地址。如果您使用 *&ptr,那么您将得到与您的第一个打印语句相同的结果。

就后半部分而言,int在这里只是一个值,而不是指针。当 C++ 说您不能使用 * 时,它可以防止您不小心将 int 视为内存地址。但是,如果您真的想将 int 视为内存地址,

int* x = 10

将允许 *x 编译。现在因为这是在内存地址 10 访问一个 int(这不是你程序的一部分)你的 OS 几乎肯定会杀死你的程序。

真正有趣的问题是 'why pointers?' 如果你明白了,那么剩下的可能会随之而来。

旁注,C++ 有很多结构,它们构成了很多这样的东西 'hidden',但是 我描述的内容仍在幕后进行

有几种场景可以使用指针

#1 传大事

想象一下你有这个

struct Biggy{
     int Widle[500];
     char Froodle[500];
}

这是一个大对象,复制起来很昂贵,你有一个对 Biggy 对象进行操作的函数,比如说它比较 Widle 和 Froodle 数组。

bool AreTheSame(Biggy b) {....}

c 和 c++ 的工作方式是参数按值传递,这意味着它们从调用者复制到被调用函数

所以当我这样做时

Biggy b = MakeAndFillBiggy(); // we will come back to this function
bool same = AReTheSame(b);

复制所有 1000 个字节,这很昂贵

但如果我有

 bool AreTheSame(Biggy *b) {....}

那我可以

bool same = AretheTheSame(&b);

现在我只是传递一个指针。那是 4 或 8 个字节而不是 1000。

#2 允许函数修改其参数

正如指出的那样

bool same = AreTheSame(b);

将 b 的副本传递给函数,这很昂贵,但是 如果函数想要更改 b,它也不能。它只有一个副本。

想象一下,我们有一个函数 MakeTheSame(Biggy b);,它查看两个数组是否相同,如果不相同,它会改变它们,太棒了。但是这样没用,因为改的是副本,不是原件。

我们又可以做到

void MakeTheSame(Biggy *b) {..}
 ...
Biggy b = MakeAndFillBiggy(); // we will come back to this function
bool same = MakeTheSame(&b);

现在我们将指针传递给调用者 'b'

请注意,这对任何大小的对象都很有用。

你可能看过这个

 int num;
 scanf("%d", &num);

我们必须将指针传递给 num 以便 scanf 可以修改它。

#3 获取和使用动态内存

您有一个应用程序可以保留参加锦标赛的足球队名单。

struct Team{
   string name;
   string town;
   string captain;
}

容纳它的数组需要多大?你事先不知道会有多少支球队参赛。

你可以

 Team teams[100];

但这有点浪费。因此,您改为使用动态内存,每次获得新时间时,您都会在堆上分配一个新的 Team 对象。

Team *team = new Team();

'new' 从池中动态分配内存,它对于 Team 对象来说足够大,并且会被初始化。它 returns 指向这个新内存的指针。

现在您可以拥有适量的团队对象。

让我们回到这个

Biggy b = MakeAndFillBiggy(); // we will come back to this function

现在我们来做

Biggy *MakeAndFillBiggy(){
    Biggy * b = new Biggy();
    ... fill arrays somehow
    return b;
}

现在我们做

Biggy *b = MakeAndFillBiggy();

C++ 好东西

C+ 有一个叫做引用的东西。它们的行为类似于指针(传递成本低,允许函数更改其参数)但在语法上看起来像非指针。

例如

void MakeTheSame(Biggy &b) {..}    

这表示 MakeTheSame 将引用作为参数。现在我们可以做

Biggy b;
MakeTheSame(b);

引用比指针有一些优势,但它们做的事情完全一样

C++ 有 'smart pointers'

我们函数的问题

Biggy *MakeAndFillBiggy(){
    Biggy * b = new Biggy();
    ... fill arrays somehow
    return b;
}
Biggy *b = MakeAndFillBiggy();

是它返回一个指向堆上对象的指针。当我们完成它时,我们必须将它的内存释放回池中,以便它可以被重新使用。

所以某处一定有

delete b;  

问题是,在复杂的代码中,当你完成 b 时,可能很难知道。输入 'smart pointers' 他们会检测你何时使用完内存并为你释放它

所以你会看到

std::shared_ptr<Biggy> MakeAndFillBiggy(){
    std::shared_ptr<Biggy> p = std::make_shared<Biggy>();
    ... fill arrays somehow
    return b;
}
std::shared_ptr<Biggy> b = MakeAndFillBiggy();

或更简洁,因为编译器很聪明

std::shared_ptr<Biggy> MakeAndFillBiggy(){
    auto p = std::make_shared<Biggy>();
    ... fill arrays somehow
    return b;
}
auto b = MakeAndFillBiggy();

在引擎盖下发生了完全相同的事情,只是在顶部添加了一些胶水以跟踪何时不再需要该指针

里面还有ptr = new xxx和一个*p