转载自:http://blog.csdn.net/060/article/details/1248648,再次万分感谢!!
(发现这个作者的CSDN博客就两篇翻译文章,但是质量很好唉,用心良苦啊)
A Beginner's guide to Pointers
这是我翻译的一篇文章,主要向初学者介绍C/C++的灵魂——指针。
原作者是:Andrew Peace
原文链接:http://www.codeproject.com/cpp/pointers.asp
What are Pointers?
指针是什么?
基本上,指针同其他变量是一样的.只是,它们的不同之处在于:其它变量包含实际的数据,而指针包含一个指示器,这个指示器指向一块能够找到信息的内存区域.这是一个非常重要的概念,许多程序和思想依赖于指针把指针作为设计的基础,比如链表.
Getting Started
我如何定义一个指针?Well, 像定义其它变量一样, 只是需要在它的名字前加一个星号(*).例如,下面的代码创建了两个指针, 它们都指向一个整型.
int * pNumberTwo;
注意到两个变量名前的前缀 'p'了吗?这是一个习惯的用法, 指出一个变量是一个指针.
现在,让这些指针实际地指向一些东西:
& 标记应当读作"...的地址"( 'the address of'),因为得到了一个变量的存储区域的地址,而不是变量本身.所以,在这个例子里, pNumberOne 被设置为等于some_number的地址, pNumberOne现在指向some_number.
pNumberTwo = & some_other_number;
What we've learnt so far: an example:
Phew! 有许多需要注意的地方,我建议如果你没有理解这些概念,你应当再读一次. 指针是一个复杂的主题,需要花一段时间才能掌握.
这儿是一个例子,示范上面讨论的一些概念思想.它是用C语言写的, 不是C++(C的扩充).
void main()
{
// 声明变量:
int nNumber;
int *pPointer;
// 现在, 给变量赋值:
nNumber = 15;
pPointer = &nNumber;
// 输出 :
printf("nNumber is equal to : %d ", nNumber);
// 现在, 通过pPointer 修改nNumber:
*pPointer = 25;
// 再次输出nNumber的值,证明nNumber的值被改变:
printf("nNumber is equal to : %d ", nNumber);
}
通读上面的代码示例,并编译, 确定你理解它是如何工作的.然后, 准备好,继续!
A trap!
看看你能不能找出下面程序的错误:
int * pPointer;
void SomeFunction();
{
int nNumber;
nNumber = 25;
// 使pPointer 指向 to nNumber:
pPointer = &nNumber;
}
void main()
{
SomeFunction(); // 使pPointer 指向 某个东西
// 为什么失败?
printf("Value of *pPointer: %d ", *pPointer);
}
这段程序首先调用SomeFunction()函数,SomeFunction()首先创建一个名叫nNumber的变量,然后使pPointer指向它.然后,但是,问题出现了.当这个函数执行完毕,nNumber被释放了,因为它是一个局部变量.当程序的执行离开定义局部变量的块时,局部变量总是被释放.这意味着,当SomeFunction()返回main()时,这个变量被释放了, 所以pPointer 指向的地方过去属于,但是以后不再属于这个程序.如果你不理解这个, 可以回去复习一下关于局部变量和全局变量的知识,以及作用域.这个概念同样重要.
那么如何结局问题?答案是, 使用一个叫做动态分配(dynamic allocation)的技术.请首先明白C和C++的不同.因为多数开发人员现在使用C++, 下面使用的代码是C++的一类.
动态分配(dynamic allocation)
动态分配或许是指针的关键.它用来分配(申请)内存, 不必定义一个变量并使指针指向它.尽管这个概念容易产生混淆,它实际上很简单.下面的代码演示如何为整型分配内存.
pNumber = new int ;
第一行声明了一个变量, pNumber.然后,第二行为一个整型分配了内存,并使pNumber指向这块内存.这里是另一个例子,这次使用 double:
pDouble = new double ;
The formula is the same every time, so you can't really fail with this bit.
动态分配的特点, 你分配的这块内存不会在函数返回或作用域结束的时候被释放.所以,如果我们使用动态分配重写上面的例子,可以看到它正常工作:
int * pPointer;
void SomeFunction()
{
// make Pointer point to a new integer
pPointer = new int;
*pPointer = 25;
}
void main()
{
SomeFunction(); // make pPointer point to something
printf("Value of *pPointer: %d ", *pPointer);
}
通读上面的代码示例,并编译, 确定你理解它是如何工作的.当SomeFunction 被调用,它分配了一些内存.并使pPointer指向它.这次,当函数返回时,新的内存仍然是完整的, 所以pPointer还是指向有用的东西.这使用了动态分配!一定要理解这个, 并继续往下读来学习更多的技巧和指导上面代码的一个严重错误.
Memory comes, memory goes
这儿有一个并发症,并且会变得非常严重,尽管它很容易补救.这个问题是,尽管你使用动态分配得到的内存仍然完整,但是它没有被自动的释放.就是,内存始终被占用,直到你告诉计算机你不再使用它.结果是,如果你不告诉计算机你不再使用这块内存, 将浪费空间.最后将导致你的系统因内存耗尽而崩溃, 所以它非常重要.当你不再使用它时, 释放是简单的:
这就是所有需要做的.你一定要小心, 确保传递一个有效的指针,也就是它确实指向你分配的某块内存, 而不是任何的垃圾.试图 delete 一块已经释放的内存是危险的,可能导致程序崩溃.
现在重新写出例子, 这次不会浪费任何内存:
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 ", *pPointer);
delete pPointer;
}
有一行不同,但是这一行是精华!如果你不释放内存, 将产生 内存泄露('memory leak') ,内存空间逐渐的被泄露, 并且不会被重新利用指导应用程序关闭.
Passing pointers to functions
向函数传递指针的能力是非常有用的,但是很容易掌握.如果我们要做一个程序, 得到一个数, 并把这个数加5 , 可能编写类似下面的代码:
void AddFive( int Number)
{
Number = Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d ", nMyNumber);
AddFive(nMyNumber);
printf("My new number is %d ", nMyNumber);
}
然而, 问题在于 AddFive 引用的 Number 是 传递给函数的变量 nMyNumber 的副本(拷贝), 不是变量本身 .因此, 这行 'Number = Number + 5', 对变量的副本加5, 而原来在main()的变量没有变化.试着运行以下程序, 证明这个问题.
要解决这个问题, 我们可以传递一个指向保持在内存里的数的指针给函数, 但是我们必须修改函数使它接受指向一个数的指针, 而不是一个数.所以,我们把 void AddFive(int Number)改成void AddFive(int* Number), 加上星号.列出修改后的程序.注意我们是否确实传递了nMyNumber的地址而不是它本身? 这是通过加上&记号完成的, 你应当把它读作"...的地址" ('the address of').
试着写出你自己的程序,验证一下.注意在AddFive函数里的Number前面的星号的重要性了吗?这是告诉编译器我们要给一个数加5, 变量Number指向这个数,而不是给指针本身加5.
void AddFive( int * Number)
{
*Number = *Number + 5;
}
void main()
{
int nMyNumber = 18;
printf("My original number is %d ", nMyNumber);
AddFive(&nMyNumber);
printf("My new number is %d ", nMyNumber);
}
最后注意一下这个函数,你可以从函数返回指针:
在这个例子里, MyFunction返回指向整数的指针.
Pointers to Classes
这里还有有几个关于指针的告诫, 一个是关于结构体和类.你可以像下面一样定义一个类:
{
public:
int m_Number;
char m_Character;
} ;
然后, 可以像下面一样定义一个MyClass类型的变量:
你应当已经知道这个, 如果不知道应该重新读一下上面的内容.定义指向MyClass的指针:
像你预见的一样.你可以分配一些内存,并使这个指针指向这块内存:
又有一个问题, 你应当怎样使用这个指针.Well, 一般情况下,你像这样写'thing.m_Number',但是你不能这样使用一个指针因为 thing 不是一个MyClass, 而是一个指向MyClass的指针,所以thing本身不包含一个叫做德变量m_Number;是指针指向的内容包含m_Number.因而,我们必须使用不同的约定.就是用'->'替换 '.' .下面是例子:
{
public:
int m_Number;
char m_Character;
} ;
void main()
{
MyClass *pPointer;
pPointer = new MyClass;
pPointer->m_Number = 10;
pPointer->m_Character = 's';
delete pPointer;
}
Pointers to Arrays
你也可以是指针指向数组.像下面这样写:
pArray = new int [ 6 ];
这将创建一个指针,pArray, 并且使它指向有六个元素的数组.有另外的方法, 不使用动态分配,像下下面这样:
int MyArray[ 6 ];
pArray = & MyArray[ 0 ];
注意, 你可以简单的写MyArray, 而不是&MyArray[0].当然, 这个仅仅用于数组, 是C/C++语言实现数组方法的结果.一个常见的缺陷是,这样写: pArray = &MyArray;但这是错误的.如果你这样写, 将得到指向一个数组的指针的指针(确实很拗口), 这不是你想要的.
Using pointers to arrays
一旦有一个指向数组的指针, 应当如何使用它?Well, 有一个指向 int型数组的指针.指针将初始指向数组的第一个值, 像下面的示例:
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 ", *pArray);
}
为了使指针移动到数组里的下一个值, 我们可以用pArray++.就像你能够猜到的一样,我们也可以 pArray + 2, 这将把指针移动两个元素.小心, 有需要指导这个数组的上界(在这个例子里是 3), 因为使用指针时编译器不会检查越过数组的边界, 所以你很容易让系统崩溃.在下面的例子里, 注意我们设置沟娜 鲋?
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 ", *pArray);
pArray++;
printf("pArray points to the value %d ", *pArray);
pArray++;
printf("pArray points to the value %d ", *pArray);
}
你也可以使用减, pArray - 2, 指针pArray从当前位置移动了两个元素.但是,确定你是对指针进行加减而不是它的值.这类使用指针和数组的操作在循环时有很大的作用, 比如for或while循环.
再注意一点, 如果你有一个指向某个值的指针, 如int* pNumberSet, 你可以把它当作一个数组, 例如pNumberSet[0] 和 *pNumberSet相等, pNumberSet[1] 和 *(pNumberSet + 1)相等.
最后关于数组的警告是, 如果你使用new为数组动态分配了内存空间:
pArray = new int [ 6 ];
你必须用下面的方法释放内存:
注意delete后面的[] ! 这告诉编译器, 释放整个数组,而不是一个元素. 无论如何你必须使用这种方法, 否则将导致内存泄露.
Last Words
最后要注意:没有使用 new 分配的内存, 你一定不要把它释放, 就像下面这样:
{
int number;
int *pNumber = number;
delete pNumber; // wrong - *pNumber wasn't allocated using new.
}
Common Questions and FAQ
Q:在new 和 delete上, 为什么出现(没有定义的标志符)'symbol undefined' 的错误?
A:这最可能是因为你的源文件被编译器看作是通常的 C 文件, 而new 和 delete运算符是C++的新特性.通常的解决办法是保证你的源文件使用 .cpp扩展名.
Q: new 和 malloc 有什么不同?
A: new是一个仅在C++中出现的关键字, 并且现在是分配内存的标准方法.你不应当在C++应用程序中再使用malloc, 除非确实需要.因为 malloc 不是为C++的面向对象特性设计的, 对一个类使用malloc分配内存时将阻止调用这个类的构造函数, 这将产生一个问题程序.
Q:我能 一块使用free 和 delete吗?
A:你应当使用与分配方式对应的方式来释放内存.例如,使用free释放malloc分配的内存, delete仅仅用于 new分配的内存.
References - 引用
引用,在某种程度上超出了本文的范围.但是,读这些内容的人经常问我, 我有必要简单的讨论一下.如果你回忆一下上面的内容, 我说与运算符(&) 读作'...的地址', 除非在声明里.当它出现在声明里的情况下, 像下面的一样, 它应当被读作'...的引用'('a reference to')
Number = 25 ;
这个引用像一个指向myOtherNumber的指针, 处了它会自动的被解除引用('dereferenced'), 所以它的行为就像一个实际的值类型而不是一个指针.使用指针的等价代码如下:
* pNumber = 25 ;
指针和引用的另一个不同是, 你不能重置('reseat')一个引用, 就是说声明一个引用后你不能修改它的指向. 例如, 下面的代码将输出 '20':
int mySecondNumber = 20 ;
int & myReference = myFirstNumber;
myReference = mySecondNumber;
printf( " %d " , myFristNumber);
在类里,引用的值必须在类的构造函数里设置, 使用如下的方式:
{
// constructor code here
}
Summary
开始,这个主题很难掌握,所以很有必要仔细看至少两次:大多数人不会马上理解.下面要点:
1.指针是一个变量,这个变量指向一块内存区域.要定义一个指针,在变量名前加个星号(*), 比如:int *number.
2.你可以通过在变量前加个&,得到任何变量的地址, 如:pNumber = &my_number.
3.不是用在声明语句里的星号(*), 应当读作 'the memory location pointed to by'.
4.不用再声明语句里的&应当读作'...的地址'('the address of').
5.你可以使用new运算符分配内存
6.指针必须与你想要这个指针指向的变量有相同的类型, 所以int *number 不能指向一个MyClass.
7.你可以把指针传递给函数.
8.你必须使用'delete'关键字释放分配的内存.
9.你可以使用&array[0]之指针指向一个已经存在的数组.
10.要释放使用动态分配方式得到的数组, 必须使用delete[],而不是delete.
这不是绝对完整的指针使用指导, 这里涉及了少数指针更详细的细节, 如指向指针的指针; 还有一个东西这里根本没有涉及到, 如指向函数的指针,我认为这些内容对这篇面向初学者的文章来说有点复杂.
就这些!试着运行一下这里写出的程序, 并写出一些你自己的东西.