文档章节

C++中指针详解

青灯夜
 青灯夜
发布于 2015/09/01 22:09
字数 4057
阅读 41
收藏 1

和其它变量一样,指针是基本的变量,所不同的是指针包含一个实际的数据,该数据代表一个可以找到实际信息的内存地址。这是一个非常重要的概念。许多程序和思想依靠指针作为他们设计的基础。

开始

       怎样定义一个指针呢?除了你需要在变量的名称前面加一个星号外,其它的和别的变量定义一样。举个例子,以下代码定义了两个指针变量,它们都指向一个整数。

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。这个星号表示解除指针的参照,应该读作“什么什么指向的内存区域”。

到现在我们学到了什么?举个例子

哟,有许多东西需要理解。我的建议是,如果你有哪个概念没有弄清楚的话,那么,不妨再看一遍。指针是个复杂的对象,可能需要花费一段时间来掌握它。

这儿有一个例子示范上面所将的概念。这是用C写的,没有C++扩展。

#i nclude <stdio.h>

void main()

{

    // 申明变量

    int nNumber;

    int *pPointer;

    //赋值

    nNumber = 15;

    pPointer = &nNumber;

    // 输出nNumber的值

    printf("nNumber is equal to : %d\n", nNumber);

    // 通过pPointer修改nNumber的值

    *pPointer = 25;

// 证明nNumber已经被改变了

// 再次打印nNumber的值

    printf("nNumber is equal to : %d\n", nNumber);

}

       通读一遍,并且编译样例代码,确信你理解了它为什么这样工作。如果你准备好了,那么继续。

一个陷阱!

看看你能否发现下面这段程序的毛病:

#i nclude <stdio.h>

int *pPointer;

void SomeFunction();

{

    int nNumber;

    nNumber = 25;   

    //将pPointer指向nNumber

    pPointer = &nNumber;

}

void main()

{

    SomeFunction(); //用pPointer做些事情

    // 为什么会失败?

    printf("Value of *pPointer: %d\n", *pPointer);

}

这段程序先调用SomeFunction函数,该函数创建一个叫做nNumber的变量,并将pPointer指向它。那么,问题是,当函数退出时,nNumber被删除了,因为它是一个局部变量。当程序执行到局部变量定义的程序块以外时,局部变量总是被删除了。这就意味着,当SomeFunction函数返回到main函数时,局部变量将被删除,因此pPointer将指向原先nNumber的地址,但这个地址已经不再属于这段程序了。如果你不理解这些,那么重新阅读一遍关于局部变量和全局变量的作用范围是明智的选择。这个概念也是非常重要的。

       那么,我们如何解决这个问题呢?答案是使用大家都知道的一个方法:动态分配。请明白C和C++的动态分配是不同的。既然现在大多数程序员都使用C++,那么下面这段代码就是常用的了。

动态分配

       动态分配可以说是指针的关键所在。不需要通过定义变量,就可以将指针指向分配的内存。也许这个概念看起来比较模糊,但是确实比较简单。下面的代码示范如何为一个整数分配内存:

int *pNumber;

pNumber = new int;

       第一行申明了一个指针pNumber,第二行分配一个整数内存,并且将pNumber指向这个新内存。下面是另一个例子,这次用一个浮点数:

double *pDouble;

pDouble = new double;

       动态分配有什么不同的呢?当函数返回或者程序运行到当前块以外时,你动态分配的内存将不会被删除。因此,如果我们用动态分配重写上面的例子,可以看到现在能够正常工作了。

#i nclude <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;

       需要做的就是这些。但是你必须确定,你删除的是一个指向你实际分配的内存的指针,而不是其它任何垃圾。尝试用delete已经释放的内存是危险的,并且可能导致程序崩溃。

       这里再次举个例子,这次修改以后就不会有内存浪费了。

#i nclude <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;

}

只有一行不同,但这行是要点。如果你不删除内存,就会导致“内存泄漏”,内存将逐渐减少,除非应用程序重新启动,否则将不能再生。

向函数传递指针

传递指针给函数非常有用,但不容易掌握。如果我们写一个程序,传递一个数值并且给它加上5,我们也许会写出如下的程序:

#i nclude <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只是为变量的拷贝增加了5,而不是最初的在main()函数中的变量。当然,你可以运行程序,以证明这一点。

       为了将值传递出去,我们可以传递这个变量的指针到函数中,但我们需要修改一下函数,以便传递数值的指针而不是数值。因此将void AddFive(int Number)修改为void AddFive(int *Number),增加了一个星号。下面是修改了的函数,注意,我们必须确认传递了nMyNumber的地址,而不是它本身。这通过增加&符号来完成,通常读作“什么什么的地址”。

#i nclude <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变量前那个重要的星号。只是必须的,用来告诉编译器我们想将5加到变量Number指向的数值,而不是将5加到指针本身。

关于函数最后需要注意的是你也可以返回一个指针。比如:

int * MyFunction();

       在这个例子中,MyFunction函数返回一个指向整数的指针。

类的指针

关于指针还有两个需要注意的问题。其中一个是结构或者类。你可以如下定义一个类:

class MyClass

{

public:

    int m_Number;

    char m_Character;

};

然后,你可以如下方式定义一个类变量:

MyClass thing;

       你应该已经知道这些了,如果还不知道的话,那么再将上面的内容读一遍。定义MyClass的指针应该这么写:

MyClass *thing;

       然后你需要分配内存,并将指针指向这个内存

       thing = new MyClass;

       问题来了,你如何使用这个指针呢?一般的,我们写thing.m_Number,但你不能对指针用’.’操作,因为thing不是一个MyClass对象。只是指向一个MyClass对象的指针。因此,指针thing不包含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的指针,指向一个包含6个元素的数组。另一种构造的方法是使用动态分配,如下:

int *pArray;

int MyArray[6];

pArray = &MyArray[0];

       注意,你这里也可以不用&MyArray[0],而直接使用&MyArray取代。当然,这仅仅适用于数组。

使用指向数组的指针

       一旦你有了指向数组的指针,那么如何使用它呢?现在假设你有一个指向整数数组的指针,那么指针开始时将指向第一个整数。举例如下:

#i nclude <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),因为编译器不能检查你是否将指针移到了数组以外,因此你可以很容易的将系统搞崩溃了。以下是个例子,显示我们设置的三个值:

#i nclude <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这样的方式来向前移动2个位置。不管是加或者减,你必须保证不是对指针所指向的数据的操作。这种操作指针和数组的方式在循环中是最常用的。例如for和while循环。

       另外要提的是,如果你有一个指针比如int pNumberSet,你也可以把它当成数组。例如pNumberSet[0]等于*pNumberSet,并且pNumberSet[1]等于*(pNumberSet+1)。

       对于数组,还有一点要注意的,如果你用new为数组分配内存,比如:

int *pArray;

pArray = new int[6];

你必须用以下方式进行删除:

delete[] pArray;

注意delete后的[],它告诉编译器,这是删除整个数组,而不仅仅是第一个元素。对于数组你必须使用这种方法,否则就会有内存泄漏。

总结

一条要注意的:你不能删除不是用new分配的内存。比如以下例子:

void main()

{

    int number;

    int *pNumber = number;

   

    delete pNumber; // wrong - *pNumber wasn't allocated using new.

}

一、相关概念

1.堆对象:在程序运行过程中根据需要随时可以建立或删除的对象。这种堆对象被创建在内存一些空闲存储单元中,这些存储单元被称为堆。它们可以被创建的堆对象占有,也可以通过删除堆对象而获得释放。

2.内存泄露:通常所指的内存泄漏是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的,使用完后必须显示释放的内存。应用程序一般使用malloc(),realloc(),new等函数从堆中分配到一块内存,使用完后,程序必须负责相应的调用free()或delete释放该内存块,否则,这块内存就不能被再次使用,我们就说这块内存泄漏了。

我们在程序调试或运行的过程中,有时会突然弹出一个对话框,显示信息:

User breakpointcalled from code at 0x77fa018c

或者DAMAGE: after Normal block(#63) at0x003B3FE0

或者Unhandled exception at 0x77f767cd(ntdll.dll) in myapp.exe: Userbreakpoint.

则意味着我们需要对内存的分配和删除指令做一个仔细全面的检查了!

3.new:用来动态地创建堆对象,相当于C语言中的malloc()函数。

指针变量名=new 类型名[下标表达式];

aa=new float[100];

4.delete:用来删除使用new创建的对象或一般类型的指针,相当于C语言中的free()函数。

delete []指针变量名;

delete []aa;

如果aa不是指向数组而是指向一个普通对象或者变量,则可以直接delete aa;不用加 [ ]。

注意:方括号非常重要的,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的,会产生回收不彻底的问题(只回收了第一个元素所占空间),我们通常叫它“内存泄露”,加了方括号后就转化为指向数组的指针,回收整个数组。delete []的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。<<Thinkin c++>>上说以前的delete []方括号中是必须添加个数的,后来由于很容易出错,所以后来的版本就改进了这个缺陷,不需要指定回收的数组元素个数。

动态分配内存空间是有显著好处的:仅在用户使用相关功能时才分配内存,避免不必要的内存开销。

二、用法与注意事项

1.指针变量或对象的初始化

aa=NULL;

在定义aa的类的构造函数中,把它赋为空指针,用来判断用户在使用过程中是否分配过堆内存给它。

2.指针变量或对象的释放

if(aa)

    delete []aa;

释放之前需要用if语句判断程序运行时是否分配过内存给aa,直接delete []aa;是危险的!释放一个没有创建过的内存空间,必然会报错。

3.动态分配的指针变量或对象的生存期

指针变量或对象一经分配,便不会自动释放,一定要在程序退出之前手动使用delete释放之,否则便会导致经典的内存泄露事件。释放的原则就是用完就删!至于何时用完,需要程序编写者自行判断。一般来说,全局变量可能在很多函数成员中使用,建议在类的析构函数中释放;局部变量则必须在定义它的函数的最后释放。

4.不正当的释放

同一空间重复释放也是危险的,因为该空间可能已另有分配,而这个时候又去释放的话,程序会报错。

有种情况很头疼,释放的位置正确,也没有重复释放,仍然报告内存访问溢出的错误。此时极有可能是数组的使用错误,即程序中实际使用的数组元素总数超过了用户的分配值。应该仔细检查使用过程中数组的下标最大值是否超过了动态分配值,保险的做法是分配一些多余空间给数组。


尊重原创,转载自:http://blog.163.com/toplcx@yeah/blog/static/926673832009751923282/

© 著作权归作者所有

青灯夜
粉丝 2
博文 35
码字总数 21708
作品 0
朝阳
程序员
私信 提问
C++11 中值得关注的几大变化(详解)

源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 (and Why You Should Care),赖勇浩做了一个中文翻译在这里。所以,我就不翻译了,我在这里仅对文中提到的这些变...

红薯
2011/08/19
3.3K
14
C++ 智能指针

智能指针(Smart Pointer),是一个来用存储指向动态分配(堆)对象指针的类。简单的说,它本身是一个类,这个类是用来存储对象指针。 一、智能指针的介绍 智能指针,就是具备指针功能同时提...

长平狐
2013/01/06
814
0
C++11 中值得关注的几大变化(详解)

转自:http://coolshell.cn/articles/5265.html 源文章来自前C++标准委员会的 Danny Kalev 的 The Biggest Changes in C++11 (and Why You Should Care),赖勇浩做了一个中文翻译在这里。所以...

长平狐
2013/01/11
185
0
C语言程序设计编程学习—内存操作函数库mem.h相关知识详解

C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到...

小辰带你看世界
2018/03/24
0
0
《数据结构与算法系列》合集整理

《数据结构与算法系列》合集整理 整理来自博客园skywang12345,以下摘自作者介绍: “最近抽空整理了"数据结构和算法"的相关文章。在整理过程中,对于每种数据结构和算法分别给出"C"、"C++"...

kaixin_code
2018/12/01
150
0

没有更多内容

加载失败,请刷新页面

加载更多

CentOS7.6中安装使用fcitx框架

内容目录 一、为什么要使用fcitx?二、安装fcitx框架三、安装搜狗输入法 一、为什么要使用fcitx? Gnome3桌面自带的输入法框架为ibus,而在使用ibus时会时不时出现卡顿无法输入的现象。 搜狗和...

技术训练营
昨天
5
0
《Designing.Data-Intensive.Applications》笔记 四

第九章 一致性与共识 分布式系统最重要的的抽象之一是共识(consensus):让所有的节点对某件事达成一致。 最终一致性(eventual consistency)只提供较弱的保证,需要探索更高的一致性保证(stro...

丰田破产标志
昨天
8
0
docker 使用mysql

1, 进入容器 比如 myslq1 里面进行操作 docker exec -it mysql1 /bin/bash 2. 退出 容器 交互: exit 3. mysql 启动在容器里面,并且 可以本地连接mysql docker run --name mysql1 --env MY...

之渊
昨天
10
0
python数据结构

1、字符串及其方法(案例来自Python-100-Days) def main(): str1 = 'hello, world!' # 通过len函数计算字符串的长度 print(len(str1)) # 13 # 获得字符串首字母大写的...

huijue
昨天
6
0
PHP+Ajax微信手机端九宫格抽奖实例

PHP+Ajax结合lottery.js制作的一款微信手机端九宫格抽奖实例,抽奖完成后有收货地址添加表单出现。支持可以设置中奖概率等。 奖品列表 <div class="lottery_list clearfix" id="lottery"> ......

ymkjs1990
昨天
5
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部