C++中指针的必要性
简单地说指针就是指向变量和对象的地址。
指针的⽤途⾮常⼴泛,⽐如如果你想通过函数改变⼀个变量的值,就得⽤指针⽽不能⽤值传递。还有在很多时候变量,特别是对象的数据量实在太⼤,程序员就会⽤指针来做形参,只需要传递⼀个地址就⾏,⼤⼤提⾼了效率。
以上还只是指针的初步应⽤,随着你在C++学习上的逐步深⼊,你会发现更多的⽤途的。指针是C语⾔的灵魂. 只有知道如何使⽤指针, 才能真正开始知道什么是C语⾔.
在C语⾔中什么是指针:
指针⽤于指向变量的地址.
指针是⼲什么⽤的:
在编程中使⽤指针,使得程序简单, 明了, 易懂, 易读.
有什么意义:
这就是C语⾔不同于其它的地⽅.  指针⽅⾯的知识,可以说在学习C语⾔中是两个阶段的分界点,在这以前的知识可以说是基础部分,(数据类型,数组,程序语句的顺序、选择和循环),以后就是以C语⾔所描述的数据结构,图象的运动...的中⾼级部分.指针是⽤来描述数据之间的关系,
到了后⾯简单的数据模式是不能⽤来描述东西了,⼴泛⽤到的是⾃定义的数据结构,⼏维数组和链表,
为了访问内存,需要对内存⾥的单元进⾏编号如0,1,(这的确很像房⼦跟房⼦的编号,如我住南新⼤街7栋(起始地址),401房(偏移地址),所以我请到南新⼤街7栋401房,当然前⾯应该还有起始地址,如某某区的南新⼤街), 为了存放这些编号(地址),就⽤了⼀个变量,即指针变量(它的值可能是0或1或2...它指向的就是⼀个具体的存储单元).
写简单的程序的话不⽤指针也⾏了吧. ⽐如, int a = 2; int* p = &a; *p = 3; 这⾥a是⼀个地址(编译后), 为了读写这个地址单元⾥的东西,可以⽤p,也可以⽤⼀般变量a.
对于指针的便利,我现在能想到的,可能是:
函数A调⽤函数B时,把⼀个内存块的地址传递过去,这样可以避免按值传递时开辟⼀个栈空间并逐⼀拷贝内存块⾥的值的开销(C++⾥的拷贝构造函数在做这件事),并且A,B函数操作的都是同⼀个内存块⾥的东西,如果这就是⽬的,那么传指针就是很有效率的做法(如果不⽤指针就可能要⽤⼀个全局的变
量了).  ⽤⼀个指针(函数指针)存放⼀系列同类型的函数名(⼊⼝地址),⽅便函数调⽤.  void*类型指针可以存放任何类型指针.  让⽗类类型指针指向任何⼦类对象可以实现动态绑定.  写着写着似乎指针还挺有⽤,呵呵.
我相信指针的使⽤⼀定要很⼩⼼,否则可能要忍受程序报错⽽总不到错在何处的痛苦. 有⼀些⽐较极端的例⼦, 如⼀个函数返回了⼀个函数⾥局部变量的指针,⼀⽤就出错; 如声明⼀个指针没有分配空间就传给别⼀函数使⽤; 如多个线程都在使⽤同⼀个内存块,你修我改,甚⾄有⼈把它删掉了或不⼩⼼让指针指向系统⽤的内存,另⼀个⼈还准备往⾥读写东西---可能要费⼼思来同步. 如指针指向的内存可能是new/malloc出来的,可能要想办法及时删除并要保证没有别的线程要⽤...
<br/><br/><font color=#0556A3>参考⽂献:</font>others Joel Spolsky认为,对指针的理解是⼀种aptitude,不是通过训练就可以达到的。虽然如此,我还是想谈⼀谈这个C/C++语⾔中最强劲也是最容易出错的要素。
  鉴于指针和⽬前计算机内存结构的关联,很多C语⾔⽐较本质的特点都孕育在其中,因此,本篇和第六、第七两篇我都将以指针为主线,结合在实际编程中遇到的问题,来详细谈谈关于指针的⼏个重要⽅⾯。
  指针类型的本质分析
  1、指针的本质
  指针的本质:⼀种复合的数据类型。下⾯我将以下⾯⼏个作为例⼦进⾏展开分析:
  a)、int *p;
  b)、int **p;
  c)、int (*parValue)[3];
  d)、int (*pFun)();
  分析:
  所谓的数据类型就是具有某种数据特征的东东,⽐如数据类型char,它的数据特征就是它所占据的内存为1个字节, 指针也很类似,指针所指向的值也占据着内存中的⼀块地址,地址的长度与指针的类型有关,⽐如对于char型指针,这个指针占据的内存就是1个字节,因此指针也是⼀种数据类型,但我们知道指针本⾝也占据了⼀个内存空间地址,地址的长度和机器的字长有关,⽐如在32位机器中,这个长度就是4个字节,因此指针本⾝也同样是⼀种数据类型,因此,我们说,指针其实是⼀种复合的数据类型,
  好了,现在我们可以分析上⾯的⼏个例⼦了。
  假设有如下定义:
int nValue;
  那么,nValue的类型就是int,也就是把nValue这个具体变量去掉后剩余的部分,因此,上⾯的4个声明可以类⽐进⾏分析:
  a)、int *
  *代表变量(指针本⾝)的值是⼀个地址,int代表这个地址⾥⾯存放的是⼀个整数,这两个结合起来,int *定义了⼀个指向整数的指针,类推如下:
  b)、int **
  指向⼀个指向整数的指针的指针。
  c)、int (*)[3]
  指向⼀个拥有三个整数的数组的指针。
  d)、int (*)()
  指向⼀个函数的指针,这个函数参数为空,返回值为整数。
  分析结束,从上⾯可以看出,指针包括两个⽅⾯,⼀个是它本⾝的值,是⼀个内存中的地址;另⼀个是指针所指向的物,是这个地址中所存放着具有各种各样意义的数据。
  2、对指针本⾝值的分析
  下⾯例⼦考察指针本⾝的值(环境为32位的计算机):
void *p = malloc( 100 );
  请计算sizeof ( p ) = ?
char str[] = “Hello” ;
char *p = str ;
  请计算sizeof ( p ) = ?
void Func ( char str[100])
{
请计算 sizeof( str ) = ? //注意,此时,str已经退化为⼀个指针,详情见
//下⼀篇指针与数组
}
  分析:上⾯的例⼦,答案都是4,因为从上⾯的讨论可以知道,指针本⾝的值对应着内存中的⼀个地址,它的size只与机器的字长有关(即它是由系统的内存模型决定的),在32位机器中,这个长度是4个字节。
  3、对指针所指向物的分析
  现在再对指针这个复合类型的第⼆部分,指针所指向物的意义进⾏分析。
  上⾯我们已经得到了指针本⾝的类型,那么将指针本⾝的类型去掉 “*”号就可得到指针所指向物的类型,分别如下:
  a)、int
  所指向物是⼀个整数。
  b)、int*
  所指向物是⼀个指向整数的指针。
  c)、int ()[3]
  ()为空,可以去掉,变为int [3],所指向物是⼀个拥有三个整数的数组。
  d)、int ()()
  第⼀个()为空,可以去掉,变为int (),所指向物是⼀个函数,这个函数的参数为空,返回值为整数。
  4、附加分析
  另外,关于指针本⾝⼤⼩的问题,在C++中与C有所不同,这⾥我也顺带谈⼀下。
  在C++中,对于指向对象成员的指针,它的⼤⼩不⼀定是4个字节,这主要是因为在引⼊多重虚拟继承以及虚拟函数的时候,有些附加的信息也需要通过这个指针进⾏传递,因此指向对象成员的指针会增⼤,不论是指向成员数据,还是成员函数都是如此,具体与编译器的实现有关,你可以编写个很⼩
的C++程序去验证⼀下。另外,对⼀个类的静态成员(static member,可以是静态成员变量或者静态成员函数)来说,指向它的指针只是普通的函数指针,⽽不是⼀个指向类成员的指针,所以它的⼤⼩不会增加,仍旧是4个字节。
类成员的指针,所以它的⼤⼩不会增加,仍旧是4个字节。
  指针运算符&和*
  “&和*”,它们是⼀对相反的操作,’&’取得⼀个物的地址(也就是指针本⾝),’*’得到⼀个地址⾥放的物(指针所指向的物)。这个东西可以是值(对象)、函数、数组、类成员(class member)等等。
  参照上⾯的分析我们可以很好地理解&与*。
  使⽤指针的好处?
  关于指针的本质和基本的运算符我们讨论过了,在这⾥,我想再笼总地谈⼀谈使⽤指针的必要性和好处,为我们今后的使⽤和对后⾯篇章的理解做好铺垫。简⽽⾔之,指针有以下好处:
  1)、⽅便使⽤动态分配的数组。
sizeof 指针  这个解释我放在本系列第六篇中进⾏讲解。
  2)、对于相同类型(甚⾄是相似类型)的多个变量进⾏通⽤访问。
  就是⽤⼀个指针变量不断在多个变量之间指来指去,从⽽使得⾮常应⽤起来⾮常灵活,不过,这招也⽐较危险,需要⼩⼼使⽤:因为出现错误的指针是编程中⾮常忌讳的事情。
  3)、变相改变⼀个函数的值传递特性。
  说⽩了,就是指针的传地址作⽤,将⼀个变量的地址作为参数传给函数,这样函数就可以修改那个变量了。
  4)、节省函数调⽤代价。
  我们可以将参数,尤其是⼤个的参数(例如结构,对象等),将他们地址作为参数传给函数,这样可以省去编译器为它们制作副本所带来的空间和时间上的开销。
  5)、动态扩展数据结构。
  因为指针可以动态地使⽤malloc/new⽣成堆上的内存,所以在需要动态扩展数据结构的时候,⾮常有⽤;⽐如对于树、链表、Hash表等,这⼏乎是必不可少的特性。
  6)、与⽬前计算机的内存模型相对应,可按照内存地址进⾏直接存取,这使得C⾮常适合于⼀些较底层的应⽤。
  这也是C/C++指针⼀个强⼤的优点,我会在后⾯讲述C语⾔的底层操作时,较详细地介绍这个优点的应⽤。
  7)、遍历数组。
  据个例⼦来说吧,当你需要对字符串数组进⾏操作时,想⼀想,你当然要⽤字符串指针在字符串上扫来扫去。
  …实在太多了,你可以慢慢来补充^_^。
  指针本⾝的相关问题
  1、问题:空指针的定义
  曾经看过有的.h⽂件将NULL定义为0L,为什么?
  答案与分析:
  这是⼀个关于空指针宏定义的问题。指针在C语⾔中是经常使⽤的,有时需要将⼀个指针置为空指针,例如在指针变量初始化的时候。
C语⾔中的空指针和Pascal或者Lisp语⾔中的NIL具有相同的地位。那如何定义空指针呢?下⾯的语句是正确的:
char *p1 = 0;
int *p2;
if (p != 0)
{
...
}
p2 = 0;
  也就是说,在指针变量的初始化、赋值、⽐较操作中,0会被编译器理解为要将指针置为空指针。⾄
于空指针的内部表⽰是否是0,则随不同的机器类型⽽定,不过通常都是0。但是在另外⼀些场合下,例如函数的参数原型是指针类型,函数调⽤时如果将0作为参数传⼊,编译器则不能将其理解为空指针。此时需要明确的类型转换,例如:
void func (char *p);
void func (char *p);
func ((char *)0);
  ⼀般情况下,0是可以放在代码中和指针关联使⽤的,但是有些程序员(数量还不少呦!也许就包括你在内)不喜欢0的直⽩,认为其不能表⽰作为指针的特殊含义,于是要定义⼀个宏NULL,来明确表⽰空指针常量。这也是对的,⼈家C语⾔标准就明确说:“ NULL应该被定义为与实现相关的空指针常量”。但是将NULL定义成什么样的值呢?我想你⼀定见过好⼏种定义NULL的⽅法:
#define NULL 0
#define NULL (char *)0
#define NULL (void *)0
  在我们使⽤的绝⼤多数计算系统上,例如PC,上述定义是能够⼯作的。然⽽,世界上还有很多其它种类的计算机,其CPU也不是Intel的。在某些系统上,指针和整数的⼤⼩和内部表⽰并不⼀致,甚⾄不同类型的指针的⼤⼩都不⼀致。为了避免这种可移植性问题,0L是⼀种最为安全的、最妥帖的定义⽅式。0L的含义是: “值为0的整数常量表达式”。这与C语⾔给出的空指针定义完全⼀致。因此,建议采⽤0L作为空指针常量NULL的值。
  其实 NULL定义值,和操作系统的的平台有关,将⼀个指针定义为 NULL,其⽤意是为了保护操作系统,因为通过指针可以访问任何⼀块地址,但是,有些数据是不许⼀般⽤户访问的,⽐如操作系统的核⼼数据。当我们通过⼀个空(NULL)的指针去⽅位数据时,系统会提⽰⾮法,那么系统⼜是如何知道的呢??
  以windows2000系统为例,该系统规定系统中每个进程的起始地址(0x00000000)开始的某个地址范围内是存放系统数据的,⽤户进程⽆法访问,所以当⽤户⽤空指针(0)访问时,其实访问的就是0x00000000地址的系统数据,由于该地址数据是受系统保护的,所以系统会提⽰错误(指针访问⾮法)。
  这也就是说NULL值不⼀定要定义成0,起始只要定义在系统的保护范围的地址空间内,⽐如定义成(0x00000001, 0x00000002)都会起到相同的作⽤,但是为了考虑到移植性,普遍定义为0 。
  2、问题:与指针相关的编程规则&规则分析
  指针既然这么重要,⽽且容易出错,那么有没有⽅法可以很好地减少这些指针相关问题的出现呢?
  答案与分析:
  减少出错的根本是彻底理解指针。
  在⽅法上,遵循⼀定的编码规则可能是最⽴竿见影的⽅法了,下⾯我来阐述⼀下与指针相关的编程规则:
  1) 未使⽤的指针初始化为NULL 。
  2) 在给指针分配空间前、分配后均应作判断。
  3) 指针所指向的内容删除后也要清除指针本⾝。
  要牢记指针是⼀个复合的数据结构这个本质,所以我们不论初始化和清除都要同时兼顾指针本⾝(上述规则1,3)和指针所指向的内容(上述规则2,3)这两个⽅⾯。
  遵循这些规则可以有效地减少指针出错,我们来看下⾯的例⼦:
void Test(void)
{
 char *str = (char *) malloc(100);
 strcpy(str, “hello”);
 free(str);
 if(str != NULL)
 {
  strcpy(str, “world”);
  printf(str);
 }
}
  请问运⾏Test函数会有什么样的结果?
  答:
  篡改动态内存区的内容,后果难以预料,⾮常危险。因为free(str);之后,str成为野指针,if(str != NULL)语句不起作⽤。
  如果我们牢记规则3,在free(str)后增加语句:
str = NULL;
  那么,就可以防⽌这样的错误发⽣。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。