我知道这是一个非常基本的问题,但是在用高级语言编写了一些项目之后,我才开始进行一些基本的C ++编程。
基本上我有三个问题:
- 为什么在普通变量上使用指针?
- 我应该在何时何地使用指针?
- 如何将指针与数组一起使用?
我知道这是一个非常基本的问题,但是在用高级语言编写了一些项目之后,我才开始进行一些基本的C ++编程。
基本上我有三个问题:
Answers:
简短的答案是:不。;-)指针将用于不能使用其他任何东西的地方。这是因为缺少适当的功能,缺少数据类型或纯粹是出于性能考虑。以下更多...
简短的答案是:无法使用其他任何地方。在C语言中,您不支持复杂的数据类型,例如字符串。也没有办法将变量“通过引用”传递给函数。那是您必须使用指针的地方。您也可以让他们指向几乎所有内容,链接列表,结构成员等。但是,我们不要在这里讨论。
毫不费力,却很混乱。;-)如果我们讨论诸如int和char这样的简单数据类型,则数组和指针之间几乎没有什么区别。这些声明非常相似(但不相同-例如,sizeof
将返回不同的值):
char* a = "Hello";
char a[] = "Hello";
您可以像这样到达数组中的任何元素
printf("Second char is: %c", a[1]);
由于数组以元素0开头,因此为索引1。
或者您也可以这样做
printf("Second char is: %c", *(a+1));
因为我们告诉printf我们要打印一个字符,所以需要使用指针运算符(*)。如果没有*,将打印内存地址本身的字符表示形式。现在,我们改用角色本身。如果我们使用%s而不是%c,我们将要求printf打印'a'加上一个(在上面的示例中)指向的内存地址的内容,而不必放*在前:
printf("Second char is: %s", (a+1)); /* WRONG */
但这将不会只打印第二个字符,而是会打印下一个内存地址中的所有字符,直到找到空字符(\ 0)。这就是事情开始变得危险的地方。如果不小心尝试使用%s格式化程序打印整数类型的变量而不是char指针怎么办?
char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);
这将打印在内存地址120上找到的所有内容,然后继续打印直到找到空字符为止。执行此printf语句是错误和非法的,但是它仍然可以工作,因为在许多环境中指针实际上是int类型的。想象一下,如果您改用sprintf()并将这种方式将太长的“字符数组”分配给另一个仅分配了一定有限空间的变量,可能会引起问题。您很可能最终会重写内存中的其他内容,并导致程序崩溃(如果幸运的话)。
哦,如果您在声明时未为char数组/指针分配字符串值,则必须在为其赋值之前为其分配足够的内存量。使用malloc,calloc或类似方法。这是因为您只声明了数组中的一个元素/一个指向的单个内存地址。所以这是一些例子:
char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);
请注意,在执行分配的内存的free()之后,仍然可以使用变量x,但是您不知道其中有什么。还要注意,两个printf()可能会为您提供不同的地址,因为不能保证第二次分配内存是在与第一个相同的空间中执行的。
a
是指针,第二种情况a
是数组。我已经提到了吗?这是不一样的!检查一下自己:比较sizeof(a),尝试为数组分配一个新地址。它不会工作。
char* a = "Hello";
而且char a[] = "Hello";
不一样,它们是完全不同的。一个声明一个指针,另一个声明一个数组。试试看a sizeof
,您会发现差异。
使用指针的原因之一是可以在调用的函数中修改变量或对象。
在C ++中,使用引用比使用指针更好。尽管引用本质上是指针,但是C ++在某种程度上隐藏了事实,使您似乎好像在按值传递。这使得更改调用函数接收值的方式变得容易,而不必修改传递值的语义。
考虑以下示例:
使用参考:
public void doSomething()
{
int i = 10;
doSomethingElse(i); // passes i by references since doSomethingElse() receives it
// by reference, but the syntax makes it appear as if i is passed
// by value
}
public void doSomethingElse(int& i) // receives i as a reference
{
cout << i << endl;
}
使用指针:
public void doSomething()
{
int i = 10;
doSomethingElse(&i);
}
public void doSomethingElse(int* i)
{
cout << *i << endl;
}
这是C语言中的示例:
char hello[] = "hello";
char *p = hello;
while (*p)
{
*p += 1; // increase the character by one
p += 1; // move to the next spot
}
printf(hello);
版画
ifmmp
因为它采用每个字符的值并将其递增1。
because it takes the value for each character and increments it by one
。是用ascii表示还是如何?
指针是间接引用另一个变量的一种方法。他们没有告诉变量值,而是告诉您其地址。这在处理数组时特别有用,因为使用指向数组中第一个元素(其地址)的指针,您可以通过增加指针(指向下一个地址位置)来快速找到下一个元素。
我已阅读的关于指针和指针算术的最佳解释是在K&R的C编程语言中。C ++ Primer是开始学习C ++的一本好书。
我也尝试回答这个问题。
指针与引用相似。换句话说,它们不是副本,而是一种引用原始值的方式。
任何事情之前,一个地方,你将通常必须使用指针很多是当你处理与嵌入式硬件。也许您需要切换数字IO引脚的状态。也许您正在处理中断,并且需要将值存储在特定位置。您得到图片。但是,如果您不直接处理硬件,而只是想知道要使用哪种类型,请继续阅读。
为什么使用指针而不是普通变量?当您处理复杂的类型(如类,结构和数组)时,答案会变得更加清晰。如果要使用普通变量,则可能最终要制作一个副本(编译器足够聪明,可以在某些情况下避免这种情况,C ++ 11也会提供帮助,但我们暂时不讨论该内容)。
现在,如果您要修改原始值会怎样?您可以使用如下形式:
MyType a; //let's ignore what MyType actually is right now.
a = modify(a);
那将很好地工作,并且如果您不确切知道为什么使用指针,则不应使用它们。当心“它们可能更快”的原因。运行您自己的测试,如果它们实际上更快,请使用它们。
但是,假设您要解决的问题是需要分配内存。分配内存时,需要取消分配内存。内存分配可能成功也可能不成功。这是指针有用的地方-它们使您可以测试已分配对象的存在,并允许您通过取消引用指针来访问为其分配了内存的对象。
MyType *p = NULL; //empty pointer
if(p)
{
//we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
//we can do something with our allocated array
for(size_t i=0; i!=50000; i++)
{
MyType &v = *(p+i); //get a reference to the ith object
//do something with it
//...
}
delete[] p; //we're done. de-allocate the memory
}
这就是为什么要使用指针的关键- 引用假定您所引用的元素已经存在。指针没有。
之所以使用指针(或至少最终不得不处理它们)的另一个原因是,因为指针是引用之前存在的数据类型。因此,如果最终使用库来执行您知道它们最擅长的事情,您会发现很多此类库在各处都使用了指针,这仅仅是因为它们已经使用了多长时间了(很多其中的一些是在C ++之前编写的。
如果您不使用任何库,则可以以不使用指针的方式设计代码,但是鉴于指针是语言的基本类型之一,您越快地习惯使用它们,就越能做到可移植您的C ++技能。
从可维护性的角度来看,我还应该提到,当您使用指针时,您要么必须测试它们的有效性并在它们无效的情况下进行处理,要么只是假设它们有效并接受您该假设被打破时,程序将崩溃或变得更糟。换句话说,使用指针的选择是引入代码复杂性,或者在出现问题时进行更多维护工作并且您正试图查找属于指针引入的整个错误类别的错误,例如内存损坏。
因此,如果您控制所有代码,则不要使用指针,而应使用引用,并尽可能使它们保持常量。这将迫使您考虑对象的生命周期,并最终使您的代码更易于理解。
请记住以下区别: 引用本质上是有效的指针。指针并不总是有效的。
所以我是说不可能创建无效的引用吗?不会。这完全有可能,因为C ++几乎可以做任何事情。无意识地做起来比较困难,您会惊讶于无意中有多少个错误:)
这是为什么C的许多功能有意义的略有不同但有深刻见解的观点: http //steve.yegge.googlepages.com/tour-de-babel#C
基本上,标准的CPU体系结构是冯·诺依曼体系结构,在这样的机器上能够引用数据项在内存中的位置并对其进行算术运算非常有用。如果您知道汇编语言的任何变体,您将快速了解它在底层的重要性。
C ++使指针有些混乱,因为它有时会为您管理指针并以“引用”的形式隐藏其效果。如果使用直线C,则对指针的需求会更加明显:没有其他方法可以进行按引用调用,这是存储字符串的最佳方法,是遍历数组的最佳方法,等等。
指针在很大程度上是数组(在C / C ++中)-它们是内存中的地址,并且可以根据需要像数组一样进行访问(在“正常”情况下)。
由于它们是项目的地址,因此它们很小:它们仅占用地址的空间。由于它们很小,因此将它们发送给函数很便宜。然后,它们允许该功能处理实际项目,而不是副本。
如果要进行动态存储分配(例如,用于链表),则必须使用指针,因为它们是从堆中获取内存的唯一方法。
std::array
。
在C ++中,如果要使用子类型多态性,则必须使用指针。参见这篇文章:没有指针的C ++多态。
确实,当您考虑它时,这是有道理的。最终,当您使用子类型多态性时,由于您不知道实际的类是什么,因此您不提前知道将调用该方法的哪个类或子类的实现。
这种拥有一个保存未知类对象的变量的想法与C ++在堆栈上存储对象的默认(非指针)模式不兼容,在默认模式下,分配的空间量直接对应于该类。注意:如果一个类有5个实例字段而不是3个实例字段,则需要分配更多空间。
goto
指令也可在后台使用-在目标机器的指令中。我们仍然不声称要使用它们。
这是我的助手,我不会答应成为专家,但是我发现指针在我要编写的其中一个库中很棒。在这个库中(这是带有OpenGL的图形API :-),您可以创建一个三角形,并向其中传递顶点对象。draw方法采用这些三角形对象,并根据我创建的顶点对象来绘制它们。好吧,没关系。
但是,如果我更改顶点坐标怎么办?用顶点类中的moveX()移动它还是其他东西?好吧,好的,现在我必须更新三角形,添加更多的方法,并且浪费了性能,因为每次顶点移动时我都必须更新三角形。仍然没什么大不了的,但这不是那么好。
现在,如果我有一个包含大量顶点和大量三角形的网格,并且该网格正在旋转,移动等等。我将必须更新使用这些顶点的每个三角形,可能还要更新场景中的每个三角形,因为我不知道哪个三角形使用哪个顶点。那是大量的计算机密集型工作,如果我在风景之上有多个网格物体,天哪!我有麻烦了,因为即时更新几乎每个帧的每个三角形,因为这些顶点随时都在变化!
使用指针,您不必更新三角形。
如果每个三角形类有三个* Vertex对象,我不仅节省了空间,因为无数三角形没有三个本身很大的顶点对象,而且无论这些指针如何,它们始终将指向它们要指向的顶点顶点多久更改一次。由于指针仍指向相同的顶点,因此三角形不会改变,并且更新过程更易于处理。如果我对您感到困惑,我不会怀疑,我不会假装自己是专家,只是将我的两分钱投入到讨论中。
描述了对使用C语言的指针的需求 这里了
基本思想是可以通过操纵数据的存储位置来消除语言的许多限制(例如使用数组,字符串和修改函数中的多个变量)。为了克服这些限制,在C中引入了指针。
此外,还可以看到,在将大型数据类型(例如具有多个字段的结构)传递给函数的情况下,使用指针可以更快地运行代码并节省内存。在传递之前复制此类数据类型将花费时间并消耗内存。这是程序员偏爱大数据类型的指针的另一个原因。
关于第二个问题,通常在编程时不需要使用指针,但是有一个例外,那就是创建公共API时。
人们通常用来替换指针的C ++构造问题在很大程度上取决于您使用的工具集,当您拥有对源代码的所有控制权时,这很好,但是例如,如果使用Visual Studio 2008编译静态库,并尝试在Visual Studio 2010中使用它,则将出现大量链接器错误,因为新项目是与较新版本的STL链接的,该STL不能向后兼容。如果您编译DLL并提供人们在其他工具集中使用的导入库,事情甚至会变得更糟,因为在这种情况下,您的程序迟早会由于没有明显的原因而崩溃。
因此,出于将大型数据集从一个库移动到另一个库的目的,如果您不想强迫其他人使用与您使用的相同工具,则可以考虑提供一个指向要复制数据的函数的数组的指针。这样做的好处在于,它甚至不必是C样式的数组,您可以使用std :: vector并通过给出第一个元素&vector [0]的地址来提供指针,然后使用std :: vector内部管理数组。
在C ++中再次使用指针的另一个很好的原因与库有关,请考虑让程序运行时无法加载dll,因此,如果您使用导入库,则无法满足依赖关系并且程序崩溃。例如,当您在应用程序旁边的dll中提供了一个公共api并希望从其他应用程序访问它时,就是这种情况。在这种情况下,为了使用API,您需要从其位置加载dll(通常是在注册表项中),然后需要使用函数指针才能在DLL中调用函数。有时,制作API的人会很友好,可以为您提供一个.h文件,其中包含帮助程序功能以自动执行此过程,并为您提供所需的所有功能指针,