C语言指针的奥秘
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
C语言指针的奥秘 Document number【SA80SAB-SAA9SYT-SAATC-SA6UT-SA18】
让你不再害怕指针
前言:复杂类型说明
要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则:从变量名处起,根据运算符优先级结合,一步一步分析.
下面让我们先从简单的类型开始慢慢分析吧:
intp;以P是一个返回整型数据的指针
intp[3];以P是一个指向由整型数据组成的数组的指针
int**p;于二级指针以及更高级的指针极少用在复杂的类型中,所以后面更复杂的类型我们就不考虑多级指针了,最多只考虑一级指针.
intp(int);以P是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数.说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.
1、细说指针
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:指针的类型、指针所指向的类
型、指针的值或者叫指针所指向的内存区、指针本身所占据的内存区。
让我们分别说明。
先声明几个指针放着做例子:
例一:
(1)int*ptr;
(2)char*ptr;
(3)int**ptr;
(4)int(*ptr)[3];
(5)int*(*ptr)[4];
1.指针的类型
从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
这是指针本身所具有的类型。
让我们看看例一中各个指针的类型:
(1)int*ptr;针所指向的类型
当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:
(1)int*ptr;针的值----或者叫指针所指向的内存区或地址指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。
在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。
指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区。
以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一
个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。
指针所指向的内存区和指针所指向的类型是两个完全不同的概念。
在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。
以后,每遇到一个指针,都应该问问:这个指针的类型是什么指针指的类型是什么该指针指向了哪里(重点注意)
4指针本身所占据的内存区
指针本身占了多大的内存你只要用函数sizeof(指针的类型)测一下就知道了。
在32位平台里,指针本身占据了4个字节的长度。
指针本身占据的内存这个概念在判断一个指针表达式(后面会解释)是否是左值时很有用。
__
2、指针的算术运算
指针可以加上或减去一个整数。
指针的这种运算的意义和通常的数值的加减运算的意义是不一样的,以单元为单位。
例如:
例二:
chara[20];
int*ptr=(int*)a;区二、输出答案为Y和a误解:ptr指向的是一个char*类型,当执行ptr++;时,会使指针加一个sizeof(char*)(有可能会有人认为这个值为1,那就会得到误区一的答案,这个值应该是4,参考前面内容),即&p+4;那进行一次取值运算不就指向数组中的第五个元素了吗那输出的结果不就是数组中第五个元素了吗答案是否定的.
正解:ptr的类型是char**,指向的类型是一个char*类型,该指向的地址就是p的地址(&p),当执行ptr++;时,会使指针加一个sizeof(char*),即&p+4;那*(&p+4)
指向哪呢,这个你去问上帝吧,或者他会告诉你在哪所以最后的输出会是一个随机的值,或许是一个非法操作.
总结一下:一个指针ptrold加(减)一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。
ptrnew的值将比ptrold的值增加(减少)了n乘sizeof(ptrold所指向的类型)个字节。
就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高(低)地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。
指针和指针进行加减:
两个指针不能进行加法运算,这是非法操作,因为进行加法后,得到的结果指向一个不知所向的地方,而且毫无意义。
两个指针可以进行减法操作,但必须类型相同,一般用在数组方面,不多说了。
3、运算符&和*
这里&是取地址运算符,*是间接运算符。
&a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。
*p的运算结果就五花八门了。
总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。
例六:
inta=12;intb;int*p;int**ptr;
p=&a;,
"Helloworld"
};
chars[80];
strcpy(s,str[0]);果看成指针的话,他即是常量指针,也是指针常量.str+1也是一个指针,它指向数组的第1号单元,它的类型是char**,它指向的类型是char*。
*(str+1)也是一个指针,它的类型是char*,它所指向的类型是char,它指向"Hi,goodmorning."的第一个字符'H'下面总结一下数组的数组名(数组中储存的也是数组)的问题:声明了一个数组TYPEarray[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)测出的是指针类型的大小。
在32位程序中结果是4
例十一:
intarray[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(对象)测出的都是对象自身的类型的大小,而不是别的什么类型的大小。
__
6、指针和结构类型的关系
可以声明一个指向结构类型对象的指针。
例十二:
structMyStruct
{
inta;
intb;
intc;
};
structMyStructss={20,30,40};
,建议使用前者
ptr->b;
ptr->c;
又请问怎样通过指针pstr来访问ss的三个成员变量
答案:
*pstr;include<>
(intargc,char*argv[])
3.{
4. charstr[10];
5. char*pStr=str;
6. cout<<sizeof(str)<<endl;
7. cout<<sizeof(pStr)<<endl;
8. return0;
9.}
1、数组名不是指针
我们先来推翻"数组名就是指针"的说法,用反证法。
证明数组名不是指针
假设:数组名是指针;
则:pStr和str都是指针;
因为:在WIN32平台下,指针长度为4;
所以:第6行和第7行的输出都应该为4;
实际情况是:第6行输出10,第7行输出4;
所以:假设不成立,数组名不是指针
2、数组名神似指针
上面我们已经证明了数组名的确不是指针,但是我们再看看程序的第5行。
该行程序将数组名直接赋值给指针,这显得数组名又的确是个指针!
我们还可以发现数组名显得像指针的例子:
1.#include<>
2.#include<>
(intargc,char*argv[])
4.{
5. charstr1[10]="ILoveU";
6. charstr2[10];
7. strcpy(str2,str1);
8. cout<<"stringarray1:"<<str1<<endl;
9. cout<<"stringarray2:"<<str2<<endl;
10. return0;
11.}
标准C库函数strcpy的函数原形中能接纳的两个参数都为char型指针,而我们在调用中传给它的却是两个数组名!函数输出:
stringarray1:ILoveU
stringarray2:ILoveU
数组名再一次显得像指针!
既然数组名不是指针,而为什么到处都把数组名当指针用于是乎,许多程序员得出这样的结论:数组名(主)是(谓)不是指针的指针(宾)。
整个一魔鬼。
揭密数组名
现在到揭露数组名本质的时候了,先给出三个结论:
(1)数组名的内涵在于其指代实体是一种数据结构,这种数据结构就是数组;
(2)数组名的外延在于其可以转换为指向其指代实体的指针,而且是一个指针常量;
(3)指向数组的指针则是另外一种变量类型(在WIN32平台下,长度为4),仅仅意味着数组的存放地址!
1、数组名指代一种数据结构:数组
现在可以解释为什么第1个程序第6行的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的char型数组,所以sizeof(str)的结果为这个数据结构占据的内存大小:10字节。
再看:
[10];
<<sizeof(intArray);
第2行的输出结果为40(整型数组占据的内存空间大小)。
如果C/C++程序可以这样写:
[10]intArray;
<<sizeof(intArray);
我们就都明白了,intArray定义为int[10]这种数据结构的一个实例,可惜啊,C/C++目前并不支持这种定义方式。
2、数组名可作为指针常量
根据结论2,数组名可以转换为指向其指代实体的指针,所以程序1中的第5行数组名直接赋值给指针,程序2第7行直接将数组名作为指针形参都可成立。
下面的程序成立吗?
[10];
++;
读者可以编译之,发现编译出错。
原因在于,虽然数组名可以转换为指向其指代实体的指针,但是它只能被看作一个指针常量,不能被修改。
而指针,不管是指向结构体、数组还是基本数据类型的指针,都不包含原始数据结构的内涵,在WIN32平台下,sizeof操作的结果都是4。
顺便纠正一下许多程序员的另一个误解。
许多程序员以为sizeof是一个函数,而实际上,它是一个操作符,不过其使用方式看起来的确太像一个函数了。
语句sizeof(int)就可以说明sizeof的确不是一个函数,因为函数接纳形参(一个变量),世界上没有一个C/C++函数接纳一个数据类型(如int)为"形参"。
3、数据名可能失去其数据结构内涵
到这里似乎数组名魔幻问题已经宣告圆满解决,但是平静的湖面上却再次掀起波浪。
请看下面一段程序:
1.#include<>
(charstr[])
3.{
4. cout<<sizeof(str)<<endl;
5.}
(intargc,char*argv[])
7.{
8. charstr1[10]="ILoveU";
9. arrayTest(str1);
10. return0;
11.}
程序的输出结果为4。
不可能吧?
一个可怕的数字,前面已经提到其为指针的长度!
结论1指出,数据名内涵为数组这种数据结构,在arrayTest函数体内,str是数组名,那为什么sizeof的结果却是指针的长度这是因为:
(1)数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针;
(2)很遗憾,在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
所以,数据名作为函数形参时,其全面沦落为一个普通指针!它的贵族身份被剥夺,成了一个地地道道的只拥有4个字节的平民。
以上就是结论4。
结束语
最后,笔者再次表达深深的希望,愿我和我的同道中人能够真正以谨慎的研究态度来认真思考开发中的问题,这样才能在我们中间产生大师级的程序员,顶级的开发书籍。
每次拿着美国鬼子的开发书籍,我们不免发出这样的感慨:我们落后太远了。
神圣的指针
1、
inta[3]={2,5,8};
int*p=a;
把数组名赋值给指针表示将数组的首元素的地址赋予此指针。
2、
inta[3]={2,5,8};
int*p=&a[0];
a[0]是a数组的首元素,而&则是取地址运算符,所以“&a[0]”取得的同样是a数组的首元素的地址,因此这段代码的含义和代码段是一致的。
3、
char*c1="Hello";
charc2[6]="World";
这两句不都是声明一个字符串吗有什么区别吗?
"Hello"是一个字符串,然后让char指针c1指向这个字符串的首元素的地址。
第二句是声明一个长度为6的char类型数组,并且设置数组的初始值为"World"。
注意末尾还有一个隐藏的“\0”,所以长度是6,而不是5。
4、数组指针的加减运算
对于指向数组的指针变量可以进行加减运算,比如对于指向数组的指针p,
p++、p--、p+2等都是合法的。
这里的加减运算并不是针对数组元素的,而是针对指针位置的,比如p当前指在首元素上,那么p++后就指在第二个元素上;比如p当前指在第5个元素上,那么p=p-2后,p就指在第3个元素上。
例子:
char*c1="Hello";
printf("%c\n",*c1);2”2”1”2”3”员名
(*结构指针变量).成员名
或为:
结构指针变量->成员名
应该注意(*pstu)两侧的括号不可少,因为成员符“.”的优先级高于“*”。
12、结构指针变量作函数参数
允许用结构变量作函数参数进行整体传送。
但是这种传送要将全部成员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的效率。
因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。
13、动态存储分配
注:“C语言中不允许动态数组”是C89标准中的规定,所以TC、VC6等C89等老的编译器会有这个问题,新的C99中已经不存在这个问题。
C语言中不允许动态数组类型。
例如下面的代码是错误的:
intn;
scanf("%d",&n);
inta[n];
但是在实际的编程中,往往会发生这种情况,即所需的内存空间取决于实际输入的数据。
对于这种问题,用数组的办法很难解决。
为了解决上述问题,C语言提供了一些内存管理函数,这些内存管理函数可以按需要动态地分配内存空间,也可把不再使用的空间回收。
(1)分配内存空间函数malloc
调用形式:
(类型说明符*)malloc(size)
功能:在内存的动态存储区中分配一块长度为"size"字节的连续区域。
函数的返回值为该区域的首地址。
(2)释放内存空间函数free
调用形式:
free(void*ptr);。