| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 6921 人关注过本帖, 2 人收藏
标题:[分享]指针(转载)
只看楼主 加入收藏
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
结帖率:50%
收藏(2)
 问题点数:0 回复次数:43 
[分享]指针(转载)

第一章。指针的概念

指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。要搞清一个指针需要搞清指针
的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身
所占据的内存区。让我们分别说明。 

先声明几个指针放着做例子: 

例一: 

(1)int *ptr; 

(2)char *ptr; 

(3)int **ptr; 

(4)int (*ptr)[3]; 

(5)int *(*ptr)[4]; 

如果看不懂后几个例子的话,请参阅我前段时间贴出的文章<<如何理解c和c

++的复杂类型声明>>。 

 

1。 指针的类型。 

从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针
本身所具有的类型。让我们看看例一中各个指针的类型: 

(1)int *ptr; //指针的类型是int * 

(2)char *ptr; //指针的类型是char * 

(3)int **ptr; //指针的类型是 int ** 

(4)int (*ptr)[3]; //指针的类型是 int(*)[3]

(5)int *(*ptr)[4]; //指针的类型是 int *(*)[4]

怎么样?找出指针的类型的方法是不是很简单? 

 

2。指针所指向的类型。 

当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当
做什么来看待。

从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指
向的类型。例如: 

(1)int *ptr; //指针所指向的类型是int 

(2)char *ptr; //指针所指向的的类型是char 

(3)int **ptr; //指针所指向的的类型是 int *

(4)int (*ptr)[3]; //指针所指向的的类型是 int()[3]

(5)int *(*ptr)[4]; //指针所指向的的类型是 int *()[4]

在指针的算术运算中,指针所指向的类型有很大的作用。 

指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把
与指针搅和在一起的“类型”这个概念分成“指针的类型”和“指针所指向的类型”两个概念,是精通指
针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起
书来前后矛盾,越看越糊涂。

3。 指针的值,或者叫指针所指向的内存区或地址。 

指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序
里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一
片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我
们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有
了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指向的类型是什么?该指针指向了哪
里? 

4。 指针本身所占据的内存区。 

指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身
占据了4个字节的长度。

指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

 

第二章。指针的算术运算

 

指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例
如: 

例二: 

1。 char a[20]; 

2。 int *ptr=a; 

... 

... 

3。 ptr++; 

在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句
中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被
加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个
字节。

由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了
数组a中从第4号单元开始的四个字节。

我们可以用一个指针和一个循环来遍历一个数组,看例子: 

例三: 

int array[20]; 

int *ptr=array; 

... 

//此处略去为整型数组赋值的代码。 

... 

for(i=0;i<20;i++) 

{ 

(*ptr)++; 

ptr++; 

} 

这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的
下一个单元。再看例子: 

例四: 

1。 char a[20]; 

2。 int *ptr=a; 

... 

... 

3。 ptr+=5; 

在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序
中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址
来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字
节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是
可以的。这也体现出了指针的灵活性。 

如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr
指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。

总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类
型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘
sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地
址方向移动了n乘sizeof(ptrold所指向的类型)个字节。一个指针ptrold减去一个整数n后,结果是一个新
的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相
同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向
的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

搜索更多相关主题的帖子: 指针 分享 
2006-10-07 19:45
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

第三章。运算符&和*

 

这里&是取地址运算符,*是...书上叫做“间接运算符”。&a的运算结果是一个指针,指针的类型是a的类
型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。*p的运算结果就五花八
门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址
是p所指向的地址。

例五: 

int a=12; 

int b; 

int *p; 

int **ptr; 

p=&a;//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。

*p=24;//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。

ptr=&p;//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int**。该指针所指向的类型是p
的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。

*ptr=&b;//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用
&b来给*ptr赋值就是毫无问题的了。

**ptr=34;//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是
一个int类型的变量。

 

第四章。指针表达式。

 

一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表达式。下面是一些指针表达式的例
子: 

例六: 

int a,b; 

int array[10]; 

int *pa; 

pa=&a;//&a是一个指针表达式。 

int **ptr=&pa;//&pa也是一个指针表达式。 

*ptr=&b;//*ptr和&b都是指针表达式。 

pa=array; 

pa++;//这也是指针表达式。 

例七: 

char *arr[20]; 

char **parr=arr;//如果把arr看作指针的话,arr也是指针表达式

char *str; 

str=*parr;//*parr是指针表达式 

str=*(parr+1);//*(parr+1)是指针表达式 

str=*(parr+2);//*(parr+2)是指针表达式 

由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所
指向的类型,指针指向的内存区,指针自身占据的内存。

好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一
个左值,否则就不是一个左值。

在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占
据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位
置。

 

第五章。数组和指针的关系

 

如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何理解c和c++的复杂类型声
明>>。 数组的数组名其实可以看作一个指针。看下例:

例八: 

int array[10]={0,1,2,3,4,5,6,7,8,9},value; 

... 

... 

value=array[0];//也可写成:value=*array; 

value=array[3];//也可写成:value=*(array+3); 

value=array[4];//也可写成:value=*(array+4); 

上例中,一般而言数组名array代表数组本身,类型是int [10],但如果把array看做指针的话,它指向数
组的第0个单元,类型是int *,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪
了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。

例九: 

char *str[3]={ 

"Hello,this is a sample!", 

"Hi,good morning.", 

"Hello world" 

}; 

char s[80]; 

strcpy(s,str[0]);//也可写成strcpy(s,*str); 

strcpy(s,str[1]);//也可写成strcpy(s,*(str+1)); 

strcpy(s,str[2]);//也可写成strcpy(s,*(str+2)); 

上例中,str是一个三单元的数组,该数组的每个单元都是一个指针,这些指针各指向一个字符串。把指
针数组名str当作一个指针的话,它指向数组的第0号单元,它的类型是char**,它指向的类型是char *。

*str也是一个指针,它的类型是char*,它所指向的类型是char,它指向的地址是字符串"Hello,this is
a sample!"的第一个字符的地址,即'H'的地址。

str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char *。

*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,good morning."的第一
个字符'H',等等。 

下面总结一下数组的数组名的问题。声明了一个数组TYPE array[n],则数组名称array就有了两重含义:
第一,它代表整个数组,它的类型是TYPE [n];第二,它是一个指针,该指针的类型是TYPE*,该指针指
向的类型是TYPE,也就是数组单元的类型,该指针指向的内存区就是数组第0号单元,该指针自己占有单
独的内存区,注意它和数组第0号单元占据的内存区是不同的。该指针的值是不能修改的,即类似array++
的表达式是错误的。 

在不同的表达式中数组名array可以扮演不同的角色。 

在表达式sizeof(array)中,数组名array代表数组本身,故这时sizeof函数测出的是整个数组的大小。

在表达式*array中,array扮演的是指针,因此这个表达式的结果就是数组第0号单元的值。sizeof
(*array)测出的是数组单元的大小。 

表达式array+n(其中n=0,1,2,....。)中,array扮演的是指针,故array+n的结果是一个指针,它的
类型是TYPE*,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大
小。 

例十: 

int array[10]; 

int (*ptr)[10]; 

ptr=&array; 

上例中ptr是一个指针,它的类型是int (*)[10],他指向的类型是int [10],我们用整个数组的首地址来
初始化它。在语句ptr=&array中,array代表数组本身。 

本节中提到了函数sizeof(),那么我来问一问,sizeof(指针名称)测出的究竟是指针自身类型的大小呢还
是指针所指向的类型的大小?答案是前者。例如:

int (*ptr)[10]; 

则在32位程序中,有: 

sizeof(int(*)[10])==4 

sizeof(int [10])==40 

sizeof(ptr)==4 

实际上,sizeof(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。

 

第六章。指针和结构类型的关系

 

可以声明一个指向结构类型对象的指针。 

例十一: 

struct MyStruct 

{ 

int a; 

int b; 

int c; 

} 

MyStruct ss={20,30,40};//声明了结构对象ss,并把ss的三个成员初始化为20,30和40。

MyStruct *ptr=&ss;//声明了一个指向结构对象ss的指针。它的类型是

MyStruct*,它指向的类型是MyStruct。

int *pstr=(int*)&ss;//声明了一个指向结构对象ss的指针。但是它的类型和它指向的类型和ptr是不同
的。

请问怎样通过指针ptr来访问ss的三个成员变量? 

答案: 

ptr->a; 

ptr->b; 

ptr->c; 

又请问怎样通过指针pstr来访问ss的三个成员变量? 

答案: 

*pstr;//访问了ss的成员a。 

*(pstr+1);//访问了ss的成员b。 

*(pstr+2)//访问了ss的成员c。 

呵呵,虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规
的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:

例十二: 

int array[3]={35,56,37}; 

int *pa=array; 

通过指针pa访问数组array的三个单元的方法是: 

*pa;//访问了第0号单元 

*(pa+1);//访问了第1号单元 

*(pa+2);//访问了第2号单元 

从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间
没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别
的什么对齐,需要在相邻两个成员之间加若干个“填充字节”,这就导致各个成员之间可能会有若干个字
节的空隙。

所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能
访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些
填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字
节,嘿,这倒是个不错的方法。 

通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

 

第七章。指针和函数的关系

 

 

可以把一个指针声明成为一个指向函数的指针。 

int fun1(char*,int); 

int (*pfun1)(char*,int); 

pfun1=fun1; 

.... 

.... 

int a=(*pfun1)("abcdefg",7);//通过函数指针调用函数。

可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。

例十三: 

int fun(char*); 

int a; 

char str[]="abcdefghijklmn"; 

a=fun(str); 

... 

... 

int fun(char*s) 

{ 

int num=0; 

for(int i=0;i
{ 

num+=*s;s++; 

} 

return num; 

} 

这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指
针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和
str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味
着同时对str进行了自加1运算。 

 



倚天照海花无数,流水高山心自知。
2006-10-07 19:46
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

第八章。指针类型转换

 

当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达
式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向
的类型和指针表达式所指向的类型是一样的。 

例十四: 

1。 float f=12.3; 

2。 float *fptr=&f; 

3。 int *p; 

在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?

p=&f; 

不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是
float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指
针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试
试。为了实现我们的目的,需要进行“强制类型转换”: 

p=(int*)&f; 

如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*和TYPE,那么语法格式是:

(TYPE*)p; 

这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址
就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。

一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型
的转换。

例十五: 

void fun(char*); 

int a=125,b; 

fun((char*)&a); 

... 

... 

void fun(char*s) 

{ 

char c; 

c=*(s+3);*(s+3)=*(s+0);*(s+0)=c; 

c=*(s+2);*(s+2)=*(s+1);*(s+1)=c; 

} 

注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的
四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int
*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结
合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译
器进行转换的过程:编译器先构造一个临时指针 char*temp,然后执行temp=(char*)&a,最后再把temp的
值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。

我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可
以把一个整数当作指针的值直接赋给指针呢?就象下面的语句: 

unsigned int a; 

TYPE *ptr;//TYPE是int,char或结构类型等等类型。 

... 

... 

a=20345686; 

ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制)

ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制)

编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:

unsigned int a; 

TYPE *ptr;//TYPE是int,char或结构类型等等类型。 

... 

... 

a=某个数,这个数必须代表一个合法的地址; 

ptr=(TYPE*)a;//呵呵,这就可以了。 

严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的
值当作一个地址来看待。 

上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。

想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完全可以。下面的例子演示了把
一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:

例十六: 

int a=123,b; 

int *ptr=&a; 

char *str; 

b=(int)ptr;//把指针ptr的值当作一个整数取出来。 

str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 

好了,现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一
个指针。 

 

第九章。指针的安全问题

看下面的例子: 

例十七: 

char s='a'; 

int *ptr; 

ptr=(int*)&s; 

*ptr=1298; 

指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占
一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向
的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也
许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马
虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。让我们再来看一例:

例十八: 

1。 char a; 

2。 int *ptr=&a; 

... 

... 

3。 ptr++; 

4。 *ptr=115; 

该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整
形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要
的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使
用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。

在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。

在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么
在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类
型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想
一想,应该会明白的。


倚天照海花无数,流水高山心自知。
2006-10-07 19:46
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

C/C++中数组和指针类型的关系
 一个整数类型数组如下进行定义:

int a[]={1,2,3,4};
  如果简单写成:

a;//数组的标识符名称
  这将代表的是数组第一个元素的内存地址,a;就相当于&a[0],它的类型是数组元素类型的指针,在这个例子中它的类型就是int*

  如果我们想访问第二个元素的地址我们可以写成如下的两种方式:

&a[1];

a+1//注意这里的表示就是将a数组的起始地址向后进一位,移动到第二个元素的地址上也就是a[0]到a[1]的过程!
  数组名称和指针的关系其实很简单,其实数组名称代表的是数组的第一个元素的内存地址,这和指针的道理是相似的!

  下面我们来看一个完整的例子,利用指针来实现对数组元素的循环遍历访问!

#include <iostream>
using namespace std;

void main(void)
{
int a[2]={1,2};

int *pb=a; //定义指针*pb的地址为数组a的开始地址

int *pe=a+2; //定义指针*pb的地址为数组a的结束地址

cout << a << "|" << a[0] << "|" << *(a+1) << "|" << pb << "|" << *pb <<endl;

while (pb!=pe) //利用地址进行逻辑判断是否到达数组的结束地址
{
cout << *pb << endl;
pb++; //利用递增操作在循环中将pb的内存地址不断向后递增
}
cin.get();
}


倚天照海花无数,流水高山心自知。
2006-10-07 19:49
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 


C/C++中字符指针数组及指向指针的指针的含义

就指向指针的指针,很早以前在说指针的时候说过,但后来发现很多人还是比较难以理解,这一次我们再次仔细说一说指向指针的指针。

  先看下面的代码,注意看代码中的注解:

#include <iostream>
#include <string>
using namespace std;

void print_char(char* array[],int len);//函数原形声明

void main(void)
{
//-----------------------------段1-----------------------------------------
char *a[]={"abc","cde","fgh"};//字符指针数组
char* *b=a;//定义一个指向指针的指针,并赋予指针数组首地址所指向的第一个字符串的地址也就是abc\0字符串的首地址
cout<<*b<<"|"<<*(b+1)<<"|"<<*(b+2)<<endl;
//-------------------------------------------------------------------------

//-----------------------------段2-----------------------------------------
char* test[]={"abc","cde","fgh"};//注意这里是引号,表示是字符串,以后的地址每加1就是加4位(在32位系统上)
int num=sizeof(test)/sizeof(char*);//计算字符串个数
print_char(test,num);
cin.get();
//-------------------------------------------------------------------------
}

void print_char(char* array[],int len)//当调用的时候传递进来的不是数组,而是字符指针他每加1也就是加上sizeof(char*)的长度
{
for(int i=0;i<len;i++)
{
cout<<*array++<<endl;
}
}
  下面我们来仔细说明一下字符指针数组和指向指针的指针,段1中的程序是下面的样子:

char *a[]={"abc","cde","fgh"};
char* *b=a;
cout<<*b<<"|"<<*(b+1)<<"|"<<*(b+2)<<endl;
  char *a[]定义了一个指针数组,注意不是char[], char[]是不能同时初始化为三个字符的,定义以后的a[]其实内部有三个内存位置,分别存储了abc\0,cde\0,fgh\0,三个字符串的起始地址,而这三个位置的内存地址却不是这三个字符串的起始地址,在这个例子中a[]是存储在栈空间内的,而三个字符串却是存储在静态内存空间内的const区域中的,接下去我们看到了char* *b=a;这里是定义了一个指向指针的指针,如果你写成char *b=a;那么是错误的,因为编译器会返回一个无法将char* *[3]转换给char *的错误,b=a的赋值,实际上是把a的首地址赋给了b,由于b是一个指向指针的指针,程序的输出cout<<*b<<"|"<<*(b+1)<<"|"<<*(b+2)<<endl;

  结果是

abc
cde
fgh
  可以看出每一次内存地址的+1操作事实上是一次加sizeof(char*)的操作,我们在32位的系统中sizeof(char*)的长度是4,所以每加1也就是+4,实际上是*a[]内部三个位置的+1,所以*(b+1)的结果自然就是cde了,我们这时候可能会问,为什么输出是cde而不是c一个呢?答案是这样的,在c++中,输出字符指针就是输出字符串,程序会自动在遇到\0后停止.

  我们最后分析一下段2中的代码,段2中我们调用了print_array()这个函数,这个函数中形式参数是char *array[]和代码中的char *test[]一样,同为字符指针,当你把参数传递过来的时候,事实上不是把数组内容传递过来,test的首地址传递了进来,由于array是指针,所以在内存中它在栈区,具有变量一样的性质,可以为左值,所以我们输出写成了,cout<<*array++<<endl;当然我们也可以改写为cout<<array[i]<<endl,这里在循环中的每次加1操作和段1代码总的道理是一样的,注意看下面的图!

  到这里这两个非常重要的知识点我们都说完了,说归说,要想透彻理解希望读者多动手,多观察,熟能生巧。

  下面是内存结构示意图:


图片附件: 游客没有浏览图片的权限,请 登录注册


倚天照海花无数,流水高山心自知。
2006-10-07 19:52
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

C/C++中利用空指针简化代码,提高效率
这里的写法,可以避免使用 for 循环,减少栈空间内存的使用和减少运行时的计算开销!

#include <iostream>
#include <string>
using namespace std;

void print_char(char* array[]);//函数原形声明

void main(void)
{
char* test[]={"abc","cde","fgh",NULL};//这里添加一个NULL,表示不指向任何地址,值为0
print_char(test);
cin.get();
}

void print_char(char* array[])
{
while(*array!=NULL)
{
cout<<*array++<<endl;
}
}



C/C++中函数指针的含义
函数存放在内存的代码区域内,它们同样有地址,我们如何能获得函数的地址呢?

  如果我们有一个int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。

  定义一个指向函数的指针用如下的形式,以上面的test()为例:

int (*fp)(int a);//这里就定义了一个指向函数的指针

  函数指针不能绝对不能指向不同类型,或者是带不同形参的函数,在定义函数指针的时候我们很容易犯如下的错误。

int *fp(int a);//这里是错误的,因为按照结合性和优先级来看就是先和()结合,然后变成了一个返回整形指针的函数了,而不是函数指针,这一点尤其需要注意!

  下面我们来看一个具体的例子:

#include <iostream>
#include <string>
using namespace std;

int test(int a);

void main(int argc,char* argv[])
{
cout<<test<<endl;//显示函数地址
int (*fp)(int a);
fp=test;//将函数test的地址赋给函数学指针fp
cout<<fp(5)<<"|"<<(*fp)(10)<<endl;
//上面的输出fp(5),这是标准c++的写法,(*fp)(10)这是兼容c语言的标准写法,两种同意,但注意区分,避免写的程序产生移植性问题!
cin.get();
}

int test(int a)
{
return a;
}
  typedef定义可以简化函数指针的定义,在定义一个的时候感觉不出来,但定义多了就知道方便了,上面的代码改写成如下的形式:

#include <iostream>
#include <string>
using namespace std;

int test(int a);

void main(int argc,char* argv[])
{
cout<<test<<endl;
typedef int (*fp)(int a);//注意,这里不是生命函数指针,而是定义一个函数指针的类型,这个类型是自己定义的,类型名为fp
fp fpi;//这里利用自己定义的类型名fp定义了一个fpi的函数指针!
fpi=test;
cout<<fpi(5)<<"|"<<(*fpi)(10)<<endl;
cin.get();
}

int test(int a)
{
return a;
}
  函数指针同样是可以作为参数传递给函数的,下面我们看个例子,仔细阅读你将会发现它的用处,稍加推理可以很方便我们进行一些复杂的编程工作。

//-------------------该例以上一个例子作为基础稍加了修改-----------------------------
#include <iostream>
#include <string>
using namespace std;

int test(int);

int test2(int (*ra)(int),int);

void main(int argc,char* argv[])
{
cout<<test<<endl;
typedef int (*fp)(int);
fp fpi;
fpi=test;//fpi赋予test 函数的内存地址

cout<<test2(fpi,1)<<endl;//这里调用test2函数的时候,这里把fpi所存储的函数地址(test的函数地址)传递了给test2的第一个形参
cin.get();
}

int test(int a)
{
return a-1;
}

int test2(int (*ra)(int),int b)//这里定义了一个名字为ra的函数指针
{
int c=ra(10)+b;//在调用之后,ra已经指向fpi所指向的函数地址即test函数
return c;
}
  利用函数指针,我们可以构成指针数组,更明确点的说法是构成指向函数的指针数组,这么说可能就容易理解的多了。

#include <iostream>
#include <string>
using namespace std;

void t1(){cout<<"test1";}
void t2(){cout<<"test2";}
void t3(){cout<<"test3";}
void main(int argc,char* argv[])
{
void* a[]={t1,t2,t3};
cout<<"比较t1()的内存地址和数组a[0]所存储的地址是否一致"<<t1<<"|"<<a[0]<<endl;

cout<<a[0]();//错误!指针数组是不能利用数组下标操作调用函数的

typedef void (*fp)();//自定义一个函数指针类型
fp b[]={t1,t2,t3}; //利用自定义类型fp把b[]定义趁一个指向函数的指针数组
b[0]();//现在利用指向函数的指针数组进行下标操作就可以进行函数的间接调用了;
cin.get();
}
  仔细看上面的例子可能不用我多说大家也会知道是怎么一会事情了,最后我们做一个重点小结,只要记住这一点,对于理解利用函数指针构成数组进行函数间接调用就很容易了!

void* a[]={t1,t2,t3};
cout<<"比较t1()的内存地址和数组a[0]所存储的地址是否一致"<<t1<<"|"<<a[0]<<endl;

cout<<a[0]();//错误!指针数组是不能利用数组下标操作调用函数的
  上面的这一小段中的错误行,为什么不能这么调用呢?

  前一篇教程我们已经说的很清楚了,不过在这里我们还是复习一下概念,指针数组元素所保存的只是一个内存地址,既然只是个内存地址就不可能进行a[0]()这样地址带括号的操作,而函数指针不同它是一个例外,函数指针只所以这么叫它就是因为它是指向函数指向内存的代码区的指针,它被系统授予允许与()括号操作的权利,进行间接的函数调用,既然函数指针允许这么操作,那么被定义成函数指针的数组就一定是可以一样的操作的。


倚天照海花无数,流水高山心自知。
2006-10-07 19:54
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

关于指针和内存的几个问题
一、"delete p" 会删去 "p" 指针,还是它指到的资料,"*p" ?

该指针指到的资料。"delete" 真正的意思是:「删去指针指到的东西」(delete the thing pointed to by)。同样的英文误用也发生在 C 语言的「释放」指标所指向的记忆体("free(p)"真正的意思是:"free_the_stuff_pointed_to_by(p)" )。

二、能 "free()" 掉由 "new" 配置到的、"delete" 掉由 "malloc()" 配置到的记忆体吗?

不行。在同一个程式里,使用 malloc/free 及 new/delete 是完全合法、合理、安全的;但 free 掉由 new 配置到的,或 delete 掉由 malloc 配置到的指标则是不合法、不合理的。


三、为什麽该用 "new" 而不是 malloc() ?

建构子/解构子、型别安全性、可被覆盖(overridability)。建构子/解构子:和 "malloc(sizeof(Fred))" 不同,"new Fred()" 还会去呼叫Fred 的建构子。同理,"delete p" 会去呼叫 "*p" 的解构子。
型别安全性:malloc() 会传回一个不具型别安全的 "void*",而 "new Fred()" 则会传回正确型态的指标(一个 "Fred*")。
可被覆盖:"new" 是个可被物件类别覆盖的运算子,而 "malloc" 不是以「各个类别」作为覆盖的基准。

四、为什麽 C++ 不替 "new" 及 "delete" 搭配个 "realloc()" ?

避免你产生意外。当 realloc() 要拷贝配置区时,它做的是「逐位元 bitwise」的拷贝,这会弄坏大
部份的 C++ 物件。不过 C++ 的物件应该可以自我拷贝才对:用它们自己的拷贝建构子或设定运算子。

五、该怎样配置/释放阵列?

用 new[] 和 delete[] :

Fred* p = new Fred[100];
//...
delete [] p;

每当你在 "new" 运算式中用了 "[...]" 的话,你就 *!*必须*!* 在 "delete" 陈述中使用 "[]" 。这语法是必要的,因为「指向单一元素的指标」与「指向一个阵列的指标」在语法上并无法区分开来。

六、万一我忘了将 "[]" 用在 "delete" 由 "new Fred[n]" 配置到的阵列,会发生什麽事?

灾难。这是程式者的--而不是编译器的--责任,去确保 new[] 与 delete[] 的正确配对。若你弄错了,编译器不会产生任何编译期或执行期的错误讯息。堆积(heap)被破坏是最可能的结局,或是更糟的,你的程式会当掉。

七、成员函数做 "delete this" 的动作是合法的(并且是好的)吗?

只要你小心的话就没事。所谓的「小心」是:
1) 你得 100% 确定 "this" 是由 "new" 配置来的(而非 "new[]",亦非自订的 "new" 版本,一定要是最原始的 "new")。
2) 你得 100% 确定该成员函数是此物件最後一个会去呼叫的。
3) 做完自杀的动作 ("delete this;") 後,你不能再去碰 "this" 的物件了,包括资料及运作行为在内。
4) 做完自杀的动作 ("delete this;") 後,你不能再去碰 "this" 指标了。换句话说,你不能查看它、将它与其他指标或是 NULL 相比较、印出其值、对它转型、对它做任何事情。

很自然的,这项警告也适用於:当 "this" 是个指向基底类别的指标,而解构子不是virtual 的场合。

八、该怎麽用 new 来配置多维阵列?

有很多方法,端视你对阵列大小的伸缩性之要求而定。极端一点的情形,如果你在编译期就知道所有阵列的维度,你可以静态地配置(就像 C 一样):

class Fred { /*...*/ };

void manipulateArray()
{
Fred matrix[10][20];

//使用 matrix[i][j]...

//不须特地去释放该阵列
}

另一个极端情况,如果你希望该矩阵的每个小块都能不一样大,你可以在自由记忆体里配置之:

void manipulateArray(unsigned nrows, unsigned ncols[])
//'nrows' 是该阵列之列数。
//所以合法的列数为 (0, nrows-1) 开区间。
//'ncols[r]' 则是 'r' 列的行数 ('r' 值域为 [0..nrows-1])。
{
Fred** matrix = new Fred*[nrows];
for (unsigned r = 0; r < nrows; ++r)
matrix[r] = new Fred[ ncols[r] ];

//使用 matrix[i][j]...

//释放就是配置的反动作:
for (r = nrows; r > 0; --r)
delete [] matrix[r-1];
delete [] matrix;
}

九、怎样确保某类别的物件都是用 "new" 建立的,而非区域或整体/静态变数?

确定该类别的建构子都是 "private:" 的,并定义个 "friend" 或 "static" 函数,来传回一个指向由 "new" 建造出来的物件(把建构子设成 "protected:",如果你想要有衍生类别的话)。

class Fred { //只允许 Fred 动态配置出来
public:
static Fred* create() { return new Fred(); }
static Fred* create(int i) { return new Fred(i); }
static Fred* create(const Fred& fred) { return new Fred(fred); }
private:
Fred();
Fred(int i);
Fred(const Fred& fred);
virtual ~Fred();
};

main()
{
Fred* p = Fred::create(5);
...
delete p;
}


倚天照海花无数,流水高山心自知。
2006-10-07 19:55
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

浅谈指针的特点

什么是指针?

  其实指针就像是其它变量一样,所不同的是一般的变量包含的是实际的真实的数据,而指针是一个指示器,它告诉程序在内存的哪块区域可以找到数据。这是一个非常重要的概念,有很多程序和算法都是围绕指针而设计的,如链表。


开始学习

  如何定义一个指针呢?就像你定义一个其它变量一样,只不过你要在指针名字前加上一个星号。我们来看一个例子:
  下面这个程序定义了两个指针,它们都是指向整型数据。


int* pNumberOne;
int* pNumberTwo;

  你注意到在两个变量名前的“p”前缀了吗?这是程序员通常在定义指针时的一个习惯,以提高便程序的阅读性,表示这是个指针。现在让我们来初始化这两个指针:
pNumberOne = &some_number;
pNumberTwo = &some_other_number;
  &号读作“什么的地址”,它表示返回的是变量在内存中的地址而不是变量本身的值。在这个例子中,pNumberOne 等于some_number的地址,所以现在pNumberOne指向some_number。 如果现在我们在程序中要用到some_number,我们就可以使用pNumberOne。


我们来学习一个例子:

  在这个例子中你将学到很多,如果你对指针的概念一点都不了解,我建议你多看几遍这个例子,指针是个很复杂的东西,但你会很快掌握它的。
  这个例子用以增强你对上面所介绍内容的了解。它是用C编写的(注:原英文版是用C写的代码,译者重新用C++改写写了所有代码,并在DEV C++ 和VC++中编译通过!)


#include <iostream.h>

void main()
{
// 声明变量:
int nNumber;
int *pPointer;


// 现在给它们赋值:
nNumber = 15;
pPointer = &nNumber;

//打印出变量nNumber的值:
cout<<"nNumber is equal to :"<< nNumber<<endl;

// 现在通过指针改变nNumber的值:
*pPointer = 25;

//证明nNumber已经被上面的程序改变
//重新打印出nNumber的值:
cout<<"nNumber is equal to :"<<nNumber<<endl;
}

  通读一下这个程序,编译并运行它,务必明白它是怎样工作的。如果你完成了,准备好,开始下一小节。


陷井!

  试一下,你能找出下面这段程序的错误吗?

#include <iostream.h>

int *pPointer;

void SomeFunction();
{
int nNumber;
nNumber = 25;


//让指针指向nNumber:
pPointer = &nNumber;
}

void main()
{
SomeFunction(); //为pPointer赋值

//为什么这里失败了?为什么没有得到25
cout<<"Value of *pPointer: "<<*pPointer<<endl;
}

  这段程序先调用了SomeFunction函数,创建了个叫nNumber的变量,接着让指针pPointer指向了它。可是问题出在哪儿呢?当函数结束后,nNumber被删掉了,因为这一个局部变量。局部变量在定义它的函数执行完后都会被系统自动删掉。也就是说当SomeFunction 函数返回主函数main()时,这个变量已经被删掉,但pPointer还指着变量曾经用过的但现在已不属于这个程序的区域。如果你还不明白,你可以再读读这个程序,注意它的局部变量和全局变量,这些概念都非常重要。
  但这个问题怎么解决呢?答案是动态分配技术。注意这在C和C++中是不同的。由于大多数程序员都是用C++,所以我用到的是C++中常用的称谓。


动态分配

  动态分配是指针的关键技术。它是用来在不必定义变量的情况下分配内存和让指针去指向它们。尽管这么说可能会让你迷惑,其实它真的很简单。下面的代码就是一个为一个整型数据分配内存的例子:
int *pNumber;
pNumber = new int;
  第一行声明一个指针pNumber。第二行为一个整型数据分配一个内存空间,并让pNumber指向这个新内存空间。下面是一个新例,这一次是用double双精型:
double *pDouble;
pDouble = new double;
  这种格式是一个规则,这样写你是不会错的。
  但动态分配又和前面的例子有什么不同呢?就是在函数返回或执行完毕时,你分配的这块内存区域是不会被删除的所以我们现在可以用动态分配重写上面的程序:
#include <iostream.h>

int *pPointer;

void SomeFunction()
{
// 让指针指向一个新的整型
pPointer = new int;
*pPointer = 25;
}

void main()
{
SomeFunction(); // 为pPointer赋值

cout<<"Value of *pPointer: "<<*pPointer<<endl;
}
  通读这个程序,编译并运行它,务必理解它是怎样工作的。当SomeFunction 调用时,它分配了一个内存,并让pPointer指向它。这一次,当函数返回时,新的内存区域被保留下来,所以pPointer始终指着有用的信息,这是因为了动态分配。但是你再仔细读读上面这个程序,虽然它得到了正确结果,可仍有一个严重的错误。


分配了内存,别忘了回收

  太复杂了,怎么会还有严重的错误!其实要改正并不难。问题是:你动态地分配了一个内存空间,可它绝不会被自动删除。也就是说,这块内存空间会一直存在,直到你告诉电脑你已经使用完了。可结果是,你并没有告诉电脑你已不再需要这块内存空间了,所以它会继续占据着内存空间造成浪费,甚至你的程序运行完毕,其它程序运行时它还存在。当这样的问题积累到一定程度,最终将导致系统崩溃。所以这是很重要的,在你用完它以后,请释放它的空间,如:
delete pPointer;
  这样就差不多了,你不得不小心。在这你终止了一个有效的指针(一个确实指向某个内存的指针)。
  下面的程序,它不会浪费任何的内存:

#include <iostream.h>

int *pPointer;

void SomeFunction()
{
// 让指针指向一个新的整型
pPointer = new int;
*pPointer = 25;
}

void main()
{
SomeFunction(); //为pPointer赋值
cout<<"Value of *pPointer: "<<*pPointer<<endl;

delete pPointer;
} 

  只有一行与前一个程序不同,但就是这最后一行十分地重要。如果你不删除它,你就会制造一起“内存漏洞”,而让内存逐渐地泄漏。
  (译者:假如在程序中调用了两次SomeFunction,你又该如何修改这个程序呢?请读者自己思考)

传递指针到函数

  传递指针到函数是非常有用的,也很容易掌握。如果我们写一个程序,让一个数加上5,看一看这个程序完整吗?:
#include <iostream.h>

void AddFive(int Number)
{
Number = Number + 5;
}

void main()
{
int nMyNumber = 18;

cout<<"My original number is "<<nMyNumber<<endl;
AddFive(nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
//得到了结果23吗?问题出在哪儿?
}
  问题出在函数AddFive里用到的Number是变量nMyNumber的一个副本而传递给函数,而不是变量本身。因此, " Number = Number + 5" 这一行是把变量的副本加了5,而原始的变量在主函数main()里依然没变。试着运行这个程序,自己去体会一下。
  要解决这个问题,我们就要传递一个指针到函数,所以我们要修改一下函数让它能接受指针:把'void AddFive(int Number)' 改成 'void AddFive(int* Number)' 。下面就是改过的程序,注意函数调用时要用&号,以表示传递的是指针:
#include <iostream.h>
void AddFive(int* Number)
{
*Number = *Number + 5;
}

void main()
{
int nMyNumber = 18;

cout<<"My original number is "<<nMyNumber<<endl;
AddFive(&nMyNumber);
cout<<"My new number is "<<nMyNumber<<endl;
}


  试着自己去运行它,注意在函数AddFive的参数Number前加*号的重要性:它告诉编译器,我们是把指针所指的变量加5。而不并指针自己加5。

  最后,如果想让函数返回指针的话,你可以这么写:
int * MyFunction();
  在这句里,MyFunction返回一个指向整型的指针。


指向类的指针

  指针在类中的操作要格外小心,你可以用如下的办法定义一个类:
class MyClass
{
  public:
  int m_Number;
  char m_Character;
};
  接着你就可以定义一个MyClass 类的变量了:
MyClass thing;
  你应该已经知道怎样去定义一个指针了吧:
MyClass *thing;
  接着你可以分配个内存空间给它:
thing = new MyClass;
  注意,问题出现了。你打算怎样使用这个指针呢,通常你可能会写'thing.m_Number',但是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,让它指向一个有六个元素的数组。另外一种方法,不用动态分配:
int *pArray;
int MyArray[6];
pArray = &MyArray[0];
  注意,&MyArray[0] 也可以简写成 MyArray ,都表示是数组的第一个元素地址。但如果写成pArray = &MyArray可能就会出问题,结果是 pArray 指向的是指向数组的指针(在一维数组中尽管与&MyArray[0]相等),而不是你想要的,在多维数组中很容易出错。


在数组中使用指针

  一旦你定义了一个指向数组的指针,你该怎样使用它呢?让我们来看一个例子,一个指向整型数组的指针:

#include <iostream.h>

void main()
{
int Array[3];
Array[0] = 10;
Array[1] = 20;
Array[2] = 30;

int *pArray;
pArray = &Array[0];

cout<<"pArray points to the value %d\n"<<*pArray<<endl;
}

  如果让指针指向数组元素中的下一个,可以用pArray++.也可以用你应该能想到的pArray + 1,都会让指针指向数组的下一个元素。要注意的是你在移动指针时,程序并不检查你是否已经移动地超出了你定义的数组,也就是说你很可能通过上面的简单指针加操作而访问到数组以外的数据,而结果就是,可能会使系统崩溃,所以请格外小心。
  当然有了pArray + 1,也可以有pArray - 1,这种操作在循环中很常用,特别是while循环中。
  另一个需要注意的是,如果你定义了一个指向整型数的指针:int* pNumberSet ,你可以把它当作是数组,如:pNumberSet[0] 和 *pNumberSet是相等的,pNumberSet[1]与*(pNumberSet + 1)也是相等的。
  在这一节的最后提一个警告:如果你用 new 动态地分配了一个数组,
int *pArray;
pArray = new int[6];
  别忘了回收,
delete[] pArray;
  这一句是告诉编译器是删除整个数组而不一个单独的元素。千万记住了。


后话

  还有一点要小心,别删除一个根本就没分配内存的指针,典型的是如果没用new分配,就别用delete:

void main()
{
  int number;
  int *pNumber = number;

  delete pNumber; // 错误 - *pNumber 没有用new动态分配内存.
}

常见问题解答

Q:为什么我在编译程序时老是在 new 和 delete语句中出现'symbol undefined' 错误?
A:new 和 delete都是C++在C上的扩展,这个错误是说编译器认为你现在的程序是C而不C++,当然会出错了。看看你的文件名是不是.cpp结尾。

Q:new 和 malloc有什么不同?
A:new 是C++中的关健字,用来分配内存的一个标准函数。如果没有必要,请不要在C++中使用malloc。因为malloc是C中的语法,它不是为面向对象的C++而设计的。

Q:我可以同时使用free 和 delete吗?
A:你应该注意的是,它们各自所匹配的操作不同。free只用在用malloc分配的内存操作中,而delete只用在用new分配的内存操作中。


引用(写给某些有能力的读者)

  这一节的内容不是我的这篇文章的中心,只是供某些有能力的读者参考。
  有些读者经常问我关于引用和指针的问题,这里我简要地讨论一下。
  在前面指针的学习中,我们知道(&)是读作“什么的地址”,但在下面的程序中,它是读作“什么的引用”

int& Number = myOtherNumber;
Number = 25;
  引用有点像是一个指向myOtherNumber的指针,不同的是它是自动删除的。所以他比指针在某些场合更有用。与上面等价的代码是:
int* pNumber = &myOtherNumber;
*pNumber = 25;
  指针与引用另一个不同是你不能修改你已经定义好的引用,也就是说你不能改变它在声明时所指的内容。举个例子:
int myFirstNumber = 25;
int mySecondNumber = 20;
int &myReference = myFirstNumber;

myReference = mySecondNumber;//这一步能使myReference 改变吗?

cout<<myFristNumber<<endl;//结果是20还是25?

  当在类中操作时,引用的值必须在构造函数中设定,例:

CMyClass::CMyClass(int &variable) : m_MyReferenceInCMyClass(variable)
{
  // constructor code here
}


总结

  这篇文章开始可能会较难掌握,所以最好是多读几遍。有些读者暂时还不能理解,在这儿我再做一个简要的总结:
  指针是一个指向内存区域的变量,定义时在变量名前加上星号(*)(如:int *number)。
  你可以得到任何一个变量的地址,只在变量名前加上&(如:pNumber = &my_number)。
  你可以用'new' 关键字动态分配内存。指针的类型必须与它所指的变量类型一样(如:int *number 就不能指向 MyClass)。
  你可以传递一个指针到函数。必须用'delete'删除你动态分配的内存。
  你可以用&array[0]而让指针指向一个数组。
  你必须用delete[]而不是delete来删除动态分配的数组。

  文章到这儿就差不多结束了,但这些并不就是指针所有的东西,像指向指针的指针等我还没有介绍,因为这些东西对于一个初学指针的人来说还太复杂了,我不能让读者一开始就被太复杂的东西而吓走了。好了,到这儿吧,试着运行我上面写的小程序,也多自己写写程序,你肯定会进步不小的!


倚天照海花无数,流水高山心自知。
2006-10-07 19:56
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

问与答(指针)

 1、什么是指针
  指针是一种数据类型,与其它的数据类型不同的是指针是一种“用来存放地址值的”变量。举一个简单的例子:
如果定义了一个整型变量,根据整型变量的特点,它可以存放的数是整数。
如:int a; a=100; 这样就把整型常量赋给了变量a。但是如果写成这样:a=123.33;就会出问题,最后输出变量a的值结果是123。现在说到指针,其实地址值也是一个整型数,如某某变量的地址值为36542,说明这个变量被分配在内存地址值为36542的地方。能不能这样进行推理,既然地址值也是整型数,整型变量正好可以用来存放整型数,那不是一个整型变量可以用来存放地址的值吗。程序写成下面这样:
  int a,b;
a=&b;
很明显,这样写是错误的。原因在于不能简单地把地址理解为整型数。
应有这样的对应关系: 地址值<--->指针;  整型数<--->int 型变量。
所以有这样的说法:“指针就是地址”(指针就是存放地址值的一种数据类型)
  下面是一段正确的程序:
  int a,*p;
p=&a; /*把变量a的地址值赋给指针p*/

2、什么是void指针
  void的意思就是“无值”或“无类型”。void指针一般称为“通用指针”或“泛指针”。之所以有这样的名字是因为使用void指针可以很容易地把void指针转换成其它数据类型的指针。例如在为一个指针分配内存空间的时候:
int *p;
p=(int *)malloc(......); 本来函数malloc的返回值是void类型,在这里通过在前面加上一个带括号的int*就把void*类型转换成了int*类型。
  所以不能简单的把void看成“无”的意思。void数据类型是一种很重要的数据类型。

  3、指针可以相加减吗
  可以相互加减。但是一定要作有意义的运算。当二个指针指向同一个数组的时候,它们相加减是有意义的。如果二个指针分别指向二个不同的数组,那么指针之间的相加减就没有什么意义。指向同一个数组时,其相加减的结果为二个指针之间的元素数目。

  4、什么是NULL指针
  NULL指针是不指向任何一个地址的指针。这样的指针一般是允许的。当一个指针为NULL的时候,不要对它进行存取。

  5、什么是“野”指针
  野指针是不由程序员或操作者所能控制的指针。当在一个程序里面定义了一个指针而又没有给这个指针一个具体地址指向的时候,这个指针会随意地指向一个地址,这样的指针就是一个野指针。如果这个地址后面的内存空间没有什么重要的数据则不会造成不好的后果,但是一旦这里面存放了有用的数据,那么这些数据随时都有被野指针存取的危险,如果这样,数据就会被破坏,程序也会崩溃。所以在程序里面是一定要禁止任何野指针的存在。当定义了一个指针的时候,要马上给这个指针分配一个内存地址的指向。这样程序才不会因为指针而出现意外。

  6、NULL的值是什么
  NULL不是被定义为0就是被定义成(void *)0,这二种值基本上是一样的。
  如有这样的语句: if(p==NULL) 或者写成 if(p==0) 其作用是一样。

7、什么是“内存泄漏”
  当定义了一个指针的时候,立即要为这个指针分配一个内存空间。这只防止了野指针的产生。当一个指针使用完毕要立即释放掉这个指针所占用的内存空间---这有二方面的意义:  1)避免了内存空间的泿费; 2)防止了内存泄漏。为什么会产生内存泄漏:如果没有及时释放掉指针所占用的内存空间,而在下次使用这个指针时又给这个指针分配了内存空间,这样的次数一多,内存空间就慢慢被消耗掉了。所以形象地称这种现象为内存泄漏。
  如下面这样一个程序:
  void *p;
for(;;)
p=malloc(20); /*这20个字节的内存空间是随意指定的*/
这样的一个小程序,大家不要随便运行它。你可以在集成环境中单步调试运行,可以看一下每步运行后的结果。可以看到,每一次循环都会“吃掉”20个字节的内存,无数次之后,再多的内存也慢慢地“泄漏”,最后没有内存可用就死机。(与这个程序配合需要一段检测整机总的内存容量的程序,以观察内存总量的变化。这里虽然没有这一段程序,但是看得到每次分配的内存地址值是不相同的)

8、near指针和far指针
在DOS下(实模式)地址是分段的,每一段的长度为64K字节,刚好是16位(二进制的十六位)。
near指针的长度是16位的,所以可指向的地址范围是64K字节,通常说near指针的寻址范围是64K。
far指针的长度是32位,含有一个16位的基地址和16位的偏移量,将基地址乘以16后再与偏移量相加,(所以实际上far指针是20位的长度。)即可得到far指针的1M字节的偏移量。所以far指针的寻址范围是1M字节,超过了一个段64K的容量。例如一个far指针的段地址为0x7000,偏移量为0x1244,则该指针指向地址0x71224.如果一个far指针的段地址是0x7122,偏移量为0x0004,则该指针也指向地址0x71224。
如果没有指定一个指针是near或far,那么默认是near。所以far指针要显式指定。far指针工作起来要慢一些,因为每次访问一个far指针时,都要将数据段或程序段的数据交换出来。另外,far指针的运算也比较反常,例如上面讲到的far指针指向同一个地址,但是比较的结果却不相同。

9、什么时候使用far指针
当使用小代码或小数据存储模式时,不能编译一个有很多代码或数据的程序。因为在64K的一个段中,不能放下所有的代码与数据。为了解决这个问题,需要指定以far函数或far指针来使用这部分的空间(64K以外的空间)。许多库函数就是显式地指定为far函数的形式。far指针通常和farmalloc()这样的内存分配函数一起使用。 


倚天照海花无数,流水高山心自知。
2006-10-07 19:57
nuciewth
Rank: 14Rank: 14Rank: 14Rank: 14
来 自:我爱龙龙
等 级:贵宾
威 望:104
帖 子:9786
专家分:208
注 册:2006-5-23
收藏
得分:0 

正确使用指针



指针就是地址。
按类型来分指针有 int、char 、float等基本类型。
对于扩充的数据类型则有struct 等。
指针的类型决定了指针操作时该指针指向地址变化的规律。
例: int a, *p; //定义了一个整型指针以后就可以写为 p=&a; 这个好理解。
麻烦的是指针与数组结构等结合起来了之后情况就变得复杂起来,如下例:
int arr[10], *p; p=arr; 此时把数组的地址赋给了指针p,指针p就指向了数组的首地址。现在假设数组的首地址值是3452,则指针p的值必然是3452。那么 p+1 表示指针移动指向了数组的下一个元素,那么p+1的值是什么?初学指针时对这一点很容易搞错不加思索的回答既然p==3452,那么p+1就等于3452+1==3453,
如果这样去认识指针就大错特错了。这里的p+1不是简单的算术运算,它表示这样一个意思——指针移动了一个元素准确地说是指针移动了一个整型元素。一个整型变量占多少字节内存:2 个字节,所以在这里指针的地址变化为一个整型变量那么它的地址自然要在原来的地址值上加 2 ,所以指针移动一个整型元素后地址值应为3452+2,即指针p的值为3454。
上面讲的是整型指针的情形,对于字符型指针呢?其实只要对上面所讲的道理真正理解了,字符型指针也就好理解了。例子如下:
char aa[10], *p; p=aa; 同样假设数组的首地址为3452,那么p+1 的值可以这样考虑,指针移动一个字符的地址,而一个字符占一个字节的内存,所以p+1的值就为3452+1=3453。

可以说上面的二种情形还好理解,对于指向二维数组的指针以及指向结构的指针又如何去正确理解呢?

当指针与二维数组连在一起的时候情形就变得复杂了许多。因为数组名代表了
数组的起始地址,如 char arr[5][6]; 那么数组名 arr就是这个二维数组的首地
址。初学指针的朋友对这个问题总是弄不明白,既然二维数组名arr是一个地址,而
指针变量就是存放地址的,把二维数组的地址赋给同样数据类型的指针不就可以了
吗,于是就有这样的写法:char arr[5][6], *p; p=arr; 这样写肯定是错误的,有的
朋友可能对这样写是错误的也明白,他们基于这样的理解:一个二维数组里的数组
元素也是表示一个地址,于是得出结论,二维数组名是一个二级指针,是地址的地
址,进而引申出如下写法:
char arr[5][6]; char **p; p=arr; 然而很对不起,这样写同样是错误
的,如果你不相信,你可以把你认为正确的代码输入到里面,编译一下,肯定是通
不过的。

那么为什么上面的这些理解是错误的呢?错在对指针基本概念理解停留在表面。下
面为了把这个问题说清楚一点,我们可以把指针的类型归纳为二个特征:
1、基本数据类型如(char、int、 float等);
2、扩充数据类型(如一维数组、二维数组、结构等)
例1:
int arr[4][5]; //定义了一个二维数组
int *p; //定义了一个整型指针
// 下面该怎样把数组的地址赋给指针?因为定义的是一个int 指针,所以只能写为
p=arr[0]; //想一想为什么?

这里要讲的就是把地址赋给指针时要注意的问题,p是 int 类型的指针,它只能指向 int 这个基本数据类型。有的朋友或许要问,这个二维数组不也是 int 类型吗?是的但是这个二维数组除了是 int 之外,它的类型全称应该是 int 二维数组,arr[0]是int 一维数组,arr[0]这个一维数组的各元素才是基本的 int 数据类型, p=arr[0]就是把这个一维数组第一个元素的地址赋给了int 类型的指针 p,. 数据类型完全一样才能赋值。那么显而易见可以有下面的写法,注意指针是怎样指向各数组元素的:
char arr[4][5]={"abc","def","ghi","jkl","mno"};
char *p=arr[0];
for(i=0;i<20;i++)
printf("%c", *(p+i) ); //仔细观察输出的值是怎样变化的
//因为定义的是一个字符型指针,那么1、必须使这个指针指向与其对应的字符型数
据类型; 2、指针每增加一个单位的地址值 ,如p+1表示指向下一个字符的地
址。所以printf()语句输出的结果为" abcdefghijklmno" ,是一个字符一个字符输出的。
。。
下面举一个整型指针的例子:
int arr[3][3]={ {1,2,3},{4,5,6},{7,8,9}};
int *p=arr[0]; //数据类型相同,可以赋地址值
for(i=0;i<9;i++)
printf("%d", *(p+i)); // 逐个的输出数组元素

。。
--------------------------------------------------------------
下面将要讲的是直接指向一个二维数组的指针它有哪些特点:
一个二维数组,它的每一个数组元素都是一个一维数组,一个整型二维数组可以写为:
int arr[3][3]; 即 {arr[0], arr[1], arr[2] }
我们现在想定义一个指针,使得这个指针有这样的特性—— 指针 p指向arr[0],
指针 p+1 指向arr[1] ,指针 p+2指向arr[2], 也就是指针每移动一个单位的地址就指向下一个一维数组,那么这个指针必须满足下面二个条件:1、必须是整型 2、必须每移动一个单位的地址时实际上移动一个一维数组的长度即3个整型量。那么这个指针可定义为如下形式:
int (* p) [3] ; // 定义了一个指向二维数组的指针,这个二维数组中的一维数组有3个元素。
p=arr; // 把二维数组的地址赋给指针 p
如果二维字符数组初始化是 char arr[3][4]={"abc","def","ghi"};
所以可以如下写:
*(p+0) //是数驵 a[0] 的首地址 printf("%s", *p); 输出字符串 “"abc"
*(p+1) //数组a[1]的首地址 printf("%s", *(p+1)); 输出字符串 "def"
*(p+2) //数组 a[2]的首地址 printf("%s",*(p+2)); 输出字符串 "ghi"

如果要用这个二维数组的指针逐个的输出字符可以写为:
*(*(p+0)+0) //第一个字符 a
*(*(p+0)+1) //第二个字符 b
*(*(p+0)+2) //第三个字符 c
*(*(p+0)+3) //第四个字符 d
*(*(p+0)+4) //第五个字符 e
.................. 依此类推

----------------------------------

当指针指向结构时的情形。。。

如果对指向数组的指针完全理解了,那么对指向结构的指针也就很好理解了。实际上一个指向结构的指针更容易理解。

设定义了一个结构如下,有一个结构数组,三个结构。
struct student{ int a;char *b;}stru[3]={{1,"abc"},{2,"def"},{3,"ghi"}};
struct student *p=stru;

p+0 //第一个结构的地址
p+1 //第二个结构的地址
p+2 //第三个结构的地址


倚天照海花无数,流水高山心自知。
2006-10-07 19:58
快速回复:[分享]指针(转载)
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.015629 second(s), 8 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved