指针说明(C ++)
版本1.0
作者:VirtualEntity <lafeyette_management [at] comast [dot] net>
我只是没有得到C的指针。帮助我,在这里!
什么是指针?
指针与任何其他变量基本相同。 然而,它们的不同之处在于,它不包含实际的数据,它们包含一个指向可以找到信息的内存位置的指针。 这是一个非常重要的概念,许多程序和想法依靠指针作为其设计的基础,例如链表。
入门
如何定义一个指针? 那么,和任何其他变量一样,除了在它的名字之前添加一个星号。
所以,例如,以下代码创建两个指针,它们都指向一个整数:
int* pNumberOne;
int* pNumberTwo;
注意两个变量名前面的'p'前缀? 这是一个用于指示变量是一个指针的约定。
现在,让这些指针实际上指向一些东西:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
&(和号)符号应该被读取为'地址',并且导致存储变量的地址而不是变量本身。 所以在这个例子中,pNumberOne被设置为等于some_number的地址,所以pNumberOne现在指向some_number;
现在,如果要引用some_number的地址,可以使用pNumberOne。 如果我们想从pNumberOne引用some_number的值,我们将不得不说* pNumberOne。 *取消引用指针,并且应该被读为'由'指向的内存位置',除非在声明中,如“int * pNumber”行所示。
到目前为止我们学到了一个例子:
唷! 这是很多要采取的,我建议,如果你不明白任何这些概念,给它另一个阅读; 指针是一个复杂的主题,可能需要一段时间来掌握它们。
这是一个演示上述想法的例子。 它是用C编写的,没有C ++扩展。
#include <stdio.h>
void main()
{
// declare the variables:
int nNumber;
int *pPointer;
// now, give a value to them:
nNumber = 15;
pPointer = &nNumber;
// print out the value of nNumber:
printf("nNumber is equal to : %d\n", nNumber);
// now, alter nNumber through pPointer:
*pPointer = 25;
// prove that nNumber has changed as a result of the above by
// printing its value again:
printf("nNumber is equal to : %d\n", nNumber);
}
阅读并编译上述代码示例,并确保您了解为什么它可以工作。
然后,当你准备好了,请继续阅读!
陷阱!
看看您是否可以在下面的程序中发现故障:
#include <stdio.h>
int *pPointer;
void SomeFunction();
{
int nNumber;
nNumber = 25;
// make pPointer point to nNumber:
pPointer = &nNumber;
}
void main()
{
SomeFunction(); // make pPointer point to something
// why does this fail?
printf("Value of *pPointer: %d\n", *pPointer);
}
该程序首先调用SomeFunction函数,该函数创建一个名为nNumber的变量,然后使pPointer指向它。 然而,那是问题所在。 当函数离开时,nNumber被删除,因为它是一个局部变量。 当执行离开它们被定义的块时,局部变量总是被删除。这意味着当SomeFunction返回到main()时,变量被删除,所以pPointer指向变量所在的位置,不再属于这个程序。 如果你不明白这一点,回读本地和全局变量以及范围可能是明智的。 这个概念也很重要。
那么问题怎么解决呢? 答案是使用一种称为动态分配的技术。 请注意,C和C ++之间是不同的。 由于大多数开发人员正在使用C ++,所以这是下面的代码使用的方言。
动态分配
动态分配可能是指针的关键。 它用于分配内存而不必定义变量,然后指针指向它们。 虽然这个概念可能会令人困惑,但实际上很简单。 以下代码演示如何为整数分配内存:
int *pNumber;
pNumber = new int;
第一行声明指针pNumber。 然后第二行为整数分配内存,然后使pNumber指向这个新内存。 这是另一个例子,这个时候使用一个double:
double *pDouble;
pDouble = new double;
公式是相同的,所以你不能真的失败与这一点。
然而,动态分配有什么不同之处在于,当函数返回时,或者当执行离开当前块时,您分配的内存不会被删除。 所以,如果我们使用动态分配重写上面的例子,我们可以看到它现在可以正常工作:
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// make pPointer point to a new integer
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // make pPointer point to something
printf("Value of *pPointer: %d\n", *pPointer);
}
阅读并编译上述代码示例,并确保您了解为什么它可以工作。 当SomeFunction被调用时,它分配一些内存,并使pPointer指向它。 这一次,当函数返回时,新的内存保持不变,所以pPointer仍然指向有用的东西。 这就是动态分配! 确保你明白这一点,然后阅读,了解软膏中的飞行,以及为什么在上面的代码中仍然存在严重错误。
内存来了,内存就来了
总是有一个复杂的事情,而且这可能会变得相当严重,虽然很容易补救。 问题是,尽管您使用动态分配分配的内存方便地保持原样,但实际上并不会自动删除。 也就是说,内存将保持分配,直到您告诉计算机您已完成它。 这样做的结果是,如果您不告诉计算机您已经完成内存,则会浪费其他应用程序或应用程序其他部分可能使用的空间。 这最终将导致系统崩溃,所有的内存都被用尽,所以这是非常重要的。 完成后释放内存非常简单:
delete pPointer;
这就是它的一切。 但是,您必须小心,您传递一个有效的指针,即这是一个实际指向您分配的内存的指针,而不仅仅是任何旧的垃圾。 尝试删除已经释放的内存是危险的,可能会导致程序崩溃。
所以这里是这个例子,这次更新,所以不浪费任何记忆:
#include <stdio.h>
int *pPointer;
void SomeFunction()
{
// make pPointer point to a new integer
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // make pPointer point to something
printf("Value of *pPointer: %d\n", *pPointer);
delete pPointer;
}
所有这一切都是一回事,但这条线是必不可少的。 如果不删除内存,会得到所谓的“内存泄漏”,其中内存逐渐泄漏,除非应用程序关闭,否则无法重新使用。
传递函数的指针
将指针传递给函数的能力非常有用,但很容易掌握。 如果我们要制作一个需要一个数字并增加五个的程序,我们可能会写下如下内容:
#include <stdio.h>
void AddFive(int Number)
{
Number = Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d\n", nMyNumber);
AddFive(nMyNumber);
printf("My new number is %d\n", nMyNumber);
}
但是,这样做的问题是AddFive中引用的Number是传递给函数的变量nMyNumber的副本,而不是变量本身。 因此,“Number = Number + 5”行在变量的副本中添加五个,在main()中留下原始变量不受影响。 尝试运行程序来证明这一点。
为了解决这个问题,我们可以传递一个指针到内存中保存到该函数的位置,但是我们必须改变这个函数,以期望一个指向一个数字而不是一个数字的指针。 为此,我们将'void AddFive(int Number)'更改为'void AddFive(int * Number)',添加星号。 这是程序再次,随着变化。 请注意,我们必须确保我们传递nMyNumber的地址而不是数字本身? 这是通过添加&符号(你会记得)被读作“地址”来完成的。
#include <stdio.h>
void AddFive(int* Number)
{
*Number = *Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d\n", nMyNumber);
AddFive(&nMyNumber);
printf("My new number is %d\n", nMyNumber);
}
试试看一下自己的例子来证明这一点。 注意在AddFive函数中的*号的重要性? 需要告诉编译器我们要为变量Number指向的数字添加五个,而不是向指针本身添加五个。
关于函数的最后一件事是你可以从中返回指针,如下所示:
int * MyFunction();
在这个例子中,MyFunction返回一个指向整数的指针。
指针
还有一些其他带有指针的注意事项,其中一个是结构或类。 您可以按如下方式定义一个类:
class MyClass
{
public:
int m_Number;
char m_Character;
};
然后,您可以如下定义MyClass类型的变量:
MyClass thing;
你应该已经知道了,如果不尝试在这方面阅读。 要定义一个指向MyClass的指针,您将使用:
MyClass *thing;
正如你所料。 然后,您将分配一些内存,并使此指针指向内存:
thing = new MyClass;
这是问题出现的地方,那么你将如何使用这个指针。 那么通常你会写'thing.m_Number',但是你不能用一个指针,因为事物不是MyClass,而是指向它的指针,所以事物本身不包含一个名为m_Number的变量; 它是指向包含m_Number的结构。 因此,我们必须使用不同的约定。 这是替换'。' (点)与 - >(破折号后跟大于符号)。 显示如下的示例如下:
class MyClass
{
public:
int m_Number;
char m_Character;
};
void main()
{
MyClass *pPointer;
pPointer = new MyClass;
pPointer->m_Number = 10;
pPointer->m_Character = 's';
delete pPointer;
}
数组指针
您还可以指向数组的指针。 这样做如下:
int *pArray;
pArray = new int[6];
这将创建一个指针pArray,并使其指向六个元素的数组。 另一种方法,不使用动态分配,如下所示:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
请注意,而不是写MyArray [0],您可以简单地编写MyArray。 这当然只适用于数组,是C / C ++语言实现的结果。 一个常见的缺陷是编写pArray =&MyArray;但是这是不正确的。 如果你写这个,你会得到一个指向数组的指针(没有打字错误),这当然不是你想要的。
使用指向数组的指针
一旦你有一个指向数组的指针,你如何使用它? 那么,让我们说一个指向一个int数组的指针。 指针最初将指向数组中的第一个值,如下例所示:
#include <stdio.h>
void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;
int *pArray;
pArray = &Array[0];
printf("pArray points to the value %d\n", *pArray);
}
要使指针移动到数组中的下一个值,我们可以说pArray ++。 我们也可以像现在有些人猜到的那样,说pArray + 2,它将数组指针移动两个元素。 要注意的是,你知道数组的上限是(在这个例子中是3),因为当你使用指针时,编译器不能检查你没有超过数组的结尾,所以你可以容易造成系统崩溃。 这是这个例子,显示了这次我们设置的三个值:
#include <stdio.h>
void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;
int *pArray;
pArray = &Array[0];
printf("pArray points to the value %d\n", *pArray);
pArray++;
printf("pArray points to the value %d\n", *pArray);
pArray++;
printf("pArray points to the value %d\n", *pArray);
}
您也可以减去值,所以pArray - 2是pArray当前指向的2个元素。 但请确保您添加或减去指针,而不是其值。 使用指针和数组的这种操作在循环中使用时非常有用,例如for或while循环。
请注意,如果您有一个指向值的指针,例如int * pNumberSet,则可以将其视为数组,例如pNumberSet [0]等同于* pNumberSet,pNumberSet [1]等效于*(pNumberSet + 1)。
数组的最后一个警告就是如果使用new为数组分配内存,如下例所示:
int *pArray;
pArray = new int[6];
它必须使用以下内容删除:
delete[] pArray;
注意删除后的[]。 这告诉编译器它正在删除整个数组,而不只是一个单一的项目。 当数组涉及时,您必须使用此方法,否则将导致内存泄漏。
最后的话
最后一个注意事项:您不能删除您没有使用新分配的内存,如以下示例所示:
void main()
{
int number;
int *pNumber = number;
delete pNumber; // wrong - *pNumber wasn't allocated using new.
}
常见问题和常见问题
问:为什么在新的和删除的时候会得到“符号未定义”的错误?
A:这很可能是由您的源文件由编译器解释为纯C文件引起的,而新的和删除操作符是C ++的一个新功能。 通常可以通过确保您在源代码文件上使用.cpp扩展名来解决这个问题。
问:new和malloc有什么区别?
A:new是一个仅存在于C ++中的关键字,现在是分配内存的标准方式(使用Windows的内存分配例程)。 除非绝对必要,否则不应该在C C ++应用程序中使用malloc。 因为malloc不是面向C ++的面向对象的特性而设计的,所以使用它来为类分配内存将会阻止被调用的类的构造函数,这只是可能出现的问题的一个例子。 由于使用malloc和free而产生的问题,由于现在所有的意图和目的已经过时,所以在本文中没有详细讨论,我将尽可能地禁止使用它们。
问:我可以一起使用免费和删除吗?
A:你应该用与分配它相同的例程释放内存。 例如,只有在使用malloc分配的内存上才可以使用,只能在使用new等分配的内存上进行删除。
参考文献
参考文献在一定程度上超出了本文的范围。 但是,由于阅读这些关于他们的人多次被问到,我会简要讨论一下。 它们与指针非常相关,因为在许多情况下,它们可以用作更简单的替代方法。 如果你回想起来,我提到和&())被看作'地址',除非在声明中。 如果存在如下所示的声明,则应将其读为“引用”。
int& Number = myOtherNumber;
Number = 25;
引用就像一个指向myOtherNumber的指针,除了它被自动取消引用,所以它的行为就像实际的值类型而不是一个指针类型。 使用指针的等效代码如下所示:
int* pNumber = &myOtherNumber;
*pNumber = 25;
指针和引用之间的另一个区别是,你不能“重新引用”引用,也就是说,在声明之后你不能改变它所指向的内容。 例如,以下代码将输出'20':
int myFirstNumber = 25;
int mySecondNumber = 20;
int &myReference = myFirstNumber;
myReference = mySecondNumber;
printf("%d", myFristNumber);
在一个类中,引用的值必须由构造函数以下列方式设置:
CMyClass::CMyClass(int &variable) : m_MyReferenceInCMyClass(variable)
{
// constructor code here
}
概要
这个话题最初很难掌握,所以至少要两次看,大多数人不会立即理解。 以下是要点:
指针是指向内存区域的变量您可以通过在变量名前添加一个星号(*)来定义一个指针(即int * number)。
2.您可以通过在其前面加上一个&()号(即pNumber =&my_number))来获取任何变量的地址。
3.星号除非在声明中(如int *号),应该被读为'指向的内存位置'。
4.符号,除非在声明(如int&number)中,应该被读为'地址'。
你可以使用'new'关键字来分配内存。
指针必须与您想要指向的变量相同,所以int *号不会指向MyClass。
您可以将指针传递给函数。
8.您必须删除使用“删除”关键字分配的内存。
你可以通过使用&array [0]获得一个已经存在的数组的指针。
10.您必须删除使用delete []动态分配的数组,而不仅仅是删除。
这不是一个绝对完整的指针指南,还有一些其他的东西,我可以更详细地介绍,比如指针的指针,还有我选择的东西,根本没有涵盖,如函数指针,我相信对于初学者的文章来说太复杂了,很少使用的东西很少,以至于初学者不会因为这些笨拙的细节而变得更好。
而已! 尝试运行这里提供的一些程序,并提出一些你自己的例子。
更新
* 2002年7月10日 - 常见问题补充说明,整篇文章进行了一些细微的修改,以提高清晰度,更新不正确放置的源代码片段,并添加了引用部分。
关于安德鲁·和平
安德鲁目前是剑桥大学的一名学生,致力于计算机科学学士学位。 “工作”一词在这里是含糊的,希望读者能意识到同一句话包含“学生”一词。
除了计算,他对音乐有浓厚的兴趣,并享受户外活动,特别是当天气允许的时候,他们在...安德鲁,骑自行车是有趣的,航行更有趣,奇怪的露营和步行肯定是什么医生下令。
在编程经验方面,他首先使用BASIC开始为Commodore Amiga编写程序,之后他学习了C的乐趣,从未回头过。 既然他已经将C ++和C#添加到了他的复制品中,还有一大堆其他疯狂的语言,如ML,他们喜欢在大学里教书。