现在的位置: 首页 > 综合 > 正文

指针入门指导 — A Beginner’s guide to Pointers

2018年02月21日 ⁄ 综合 ⁄ 共 8214字 ⁄ 字号 评论关闭
A Beginner's guide to Pointers

这是我翻译的一篇文章,主要向初学者介绍C/C++的灵魂——指针。

原作者是:Andrew Peace

原文链接:http://www.codeproject.com/cpp/pointers.asp
 
What are Pointers?
指针是什么?

基本上,指针同其他变量是一样的.只是,它们的不同之处在于:其它变量包含实际的数据,而指针包含一个指示器,这个指示器指向一块能够找到信息的内存区域.这是一个非常重要的概念,许多程序和思想依赖于指针把指针作为设计的基础,比如链表.
 
Getting Started

我如何定义一个指针?Well, 像定义其它变量一样, 只是需要在它的名字前加一个星号(*).例如,下面的代码创建了两个指针, 它们都指向一个整型.
 

    int* pNumberOne;    
    
int* pNumberTwo;     

注意到两个变量名前的前缀 'p'了吗?这是一个习惯的用法, 指出一个变量是一个指针.
现在,让这些指针实际地指向一些东西:
& 标记应当读作"...的地址"( 'the address of'),因为得到了一个变量的存储区域的地址,而不是变量本身.所以,在这个例子里, pNumberOne 被设置为等于some_number的地址, pNumberOne现在指向some_number.   

    pNumberOne = &some_number;
    pNumberTwo 
= &some_other_number; 

What we've learnt so far: an example:  

   
Phew! 有许多需要注意的地方,我建议如果你没有理解这些概念,你应当再读一次. 指针是一个复杂的主题,需要花一段时间才能掌握.  

这儿是一个例子,示范上面讨论的一些概念思想.它是用C语言写的, 不是C++(C的扩充).

 

 

#include <stdio.h>

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!

看看你能不能找出下面程序的错误:
 

#include <stdio.h>

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)

动态分配或许是指针的关键.它用来分配(申请)内存, 不必定义一个变量并使指针指向它.尽管这个概念容易产生混淆,它实际上很简单.下面的代码演示如何为整型分配内存.

int *pNumber;
pNumber 
= new int;

 

第一行声明了一个变量, pNumber.然后,第二行为一个整型分配了内存,并使pNumber指向这块内存.这里是另一个例子,这次使用 double:
   

double *pDouble;
pDouble 
= new double;

 

The formula is the same every time, so you can't really fail with this bit.

动态分配的特点, 你分配的这块内存不会在函数返回或作用域结束的时候被释放.所以,如果我们使用动态分配重写上面的例子,可以看到它正常工作:
   

#include <stdio.h>

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 pPointer;

这就是所有需要做的.你一定要小心, 确保传递一个有效的指针,也就是它确实指向你分配的某块内存, 而不是任何的垃圾.试图 delete 一块已经释放的内存是危险的,可能导致程序崩溃.

现在重新写出例子, 这次不会浪费任何内存:
   

#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 "*pPointer);

    delete pPointer;
}

 

有一行不同,但是这一行是精华!如果你不释放内存, 将产生 内存泄露('memory leak') ,内存空间逐渐的被泄露, 并且不会被重新利用指导应用程序关闭.

Passing pointers to functions

向函数传递指针的能力是非常有用的,但是很容易掌握.如果我们要做一个程序, 得到一个数, 并把这个数加5 , 可能编写类似下面的代码:

#include <stdio.h>

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.

#include <stdio.h>
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);
}

最后注意一下这个函数,你可以从函数返回指针:

 int * MyFunction();

在这个例子里, MyFunction返回指向整数的指针.

Pointers to Classes

这里还有有几个关于指针的告诫, 一个是关于结构体和类.你可以像下面一样定义一个类:

 

class MyClass
{
public:
    
int m_Number;
    
char m_Character;
}
;

 

然后, 可以像下面一样定义一个MyClass类型的变量:

MyClass thing;

 

你应当已经知道这个, 如果不知道应该重新读一下上面的内容.定义指向MyClass的指针: 

MyClass *thing; 

 

像你预见的一样.你可以分配一些内存,并使这个指针指向这块内存:
 

thing = new MyClass; 

 

又有一个问题, 你应当怎样使用这个指针.Well, 一般情况下,你像这样写'thing.m_Number',但是你不能这样使用一个指针因为 thing 不是一个MyClass, 而是一个指向MyClass的指针,所以thing本身不包含一个叫做德变量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;
}


Pointers to Arrays

你也可以是指针指向数组.像下面这样写:

int *pArray;
pArray 
= new int[6];

 

这将创建一个指针,pArray, 并且使它指向有六个元素的数组.有另外的方法, 不使用动态分配,像下下面这样:

int *pArray;
int MyArray[6];
pArray 
= &MyArray[0]; 

 

注意, 你可以简单的写MyArray, 而不是&MyArray[0].当然, 这个仅仅用于数组, 是C/C++语言实现数组方法的结果.一个常见的缺陷是,这样写: pArray = &MyArray;但这是错误的.如果你这样写, 将得到指向一个数组的指针的指针(确实很拗口), 这不是你想要的.

Using pointers to arrays

一旦有一个指向数组的指针, 应当如何使用它?Well, 有一个指向 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 "*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 "*pArray);
    pArray
++;
    printf(
"pArray points to the value %d "*pArray);
    pArray
++;
    printf(
"pArray points to the value %d "*pArray);
}
 

 

你也可以使用减, pArray - 2, 指针pArray从当前位置移动了两个元素.但是,确定你是对指针进行加减而不是它的值.这类使用指针和数组的操作在循环时有很大的作用, 比如forwhile循环.

再注意一点, 如果你有一个指向某个值的指针, 如int* pNumberSet, 你可以把它当作一个数组, 例如pNumberSet[0]*pNumberSet相等, pNumberSet[1]*(pNumberSet + 1)相等.
最后关于数组的警告是, 如果你使用new为数组动态分配了内存空间:

int *pArray;
pArray 
= new int[6];

你必须用下面的方法释放内存:

delete[] pArray;

注意delete后面的[] ! 这告诉编译器, 释放整个数组,而不是一个元素. 无论如何你必须使用这种方法, 否则将导致内存泄露.

Last Words

最后要注意:没有使用 new 分配的内存, 你一定不要把它释放, 就像下面这样:

void main()
{
    
int number;
    
int *pNumber = number;
    
    delete pNumber; 
// wrong - *pNumber wasn't allocated using new.
}

Common Questions and FAQ

Q:在newdelete上, 为什么出现(没有定义的标志符)'symbol undefined' 的错误?
A:这最可能是因为你的源文件被编译器看作是通常的 C 文件, 而newdelete运算符是C++的新特性.通常的解决办法是保证你的源文件使用 .cpp扩展名.

Q: newmalloc 有什么不同?
A: new是一个仅在C++中出现的关键字, 并且现在是分配内存的标准方法.你不应当在C++应用程序中再使用malloc, 除非确实需要.因为 malloc 不是为C++的面向对象特性设计的, 对一个类使用malloc分配内存时将阻止调用这个类的构造函数, 这将产生一个问题程序.

Q:我能 一块使用freedelete吗?
A:你应当使用与分配方式对应的方式来释放内存.例如,使用free释放malloc分配的内存, delete仅仅用于 new分配的内存.

References - 引用

引用,在某种程度上超出了本文的范围.但是,读这些内容的人经常问我, 我有必要简单的讨论一下.如果你回忆一下上面的内容, 我说与运算符(&) 读作'...的地址', 除非在声明里.当它出现在声明里的情况下, 像下面的一样, 它应当被读作'...的引用'('a reference to')

int& Number = myOtherNumber;
Number 
= 25;

这个引用像一个指向myOtherNumber的指针, 处了它会自动的被解除引用('dereferenced'), 所以它的行为就像一个实际的值类型而不是一个指针.使用指针的等价代码如下:

int* pNumber = &myOtherNumber;
*pNumber = 25;

指针和引用的另一个不同是, 你不能重置('reseat')一个引用, 就是说声明一个引用后你不能修改它的指向. 例如, 下面的代码将输出 '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
}

 

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.

这不是绝对完整的指针使用指导, 这里涉及了少数指针更详细的细节, 如指向指针的指针; 还有一个东西这里根本没有涉及到, 如指向函数的指针,我认为这些内容对这篇面向初学者的文章来说有点复杂.  

就这些!试着运行一下这里写出的程序, 并写出一些你自己的东西.

 

抱歉!评论已关闭.