再再论指针
- 格式:pdf
- 大小:243.63 KB
- 文档页数:30
指针公认最难理解的概念,也是让很多初学者选择放弃的直接原因指针之所以难理解,因为指针本身就是一个变量,是一个非常特殊的变量,专门存放地址的变量,这个地址需要给申请空间才能装东西,而且因为是个变量可以中间赋值,这么一倒腾很多人就开始犯晕了,绕不开弯了。
C 语言之所以被很多高手所喜欢,就是指针的魅力,中间可以灵活的切换,执行效率超高,这点也是让小白晕菜的地方。
指针是学习绕不过去的知识点,而且学完C语言,下一步紧接着切换到数据结构和算法,指针是切换的重点,指针搞不定下一步进行起来就很难,会让很多人放弃继续学习的勇气。
指针直接对接内存结构,常见的C语言里面的指针乱指,数组越界根本原因就是内存问题。
在指针这个点有无穷无尽的发挥空间。
很多编程的技巧都在此集结。
指针还涉及如何申请释放内存,如果释放不及时就会出现内存泄露的情况,指针是高效好用,但不彻底搞明白对于有些人来说简直就是噩梦。
▎复杂类型说明要了解指针,多多少少会出现一些比较复杂的类型。
所以先介绍一下如何完全理解一个复杂类型。
要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样。
原则:从变量名处起,根据运算符优先级结合,一步一步分析。
下面让我们先从简单的类型开始慢慢分析吧。
•int p;这是一个普通的整型变量•int p;首先从P处开始,先与结合,所以说明P是一个指针。
然后再与int结合,说明指针所指向的内容的类型为int型,所以P是一个返回整型数据的指针•int p[3];首先从P处开始,先与[]结合,说明P是一个数组。
然后与int结合,说明数组里的元素是整型的,所以P是一个由整型数据组成的数组。
•int *p[3];首先从P处开始,先与[]结合,因为其优先级比高,所以P是一个数组。
然后再与结合,说明数组里的元素是指针类型。
之后再与int结合,说明指针所指向的内容的类型是整型的,所以P是一个由返回整型数据的指针所组成的数组。
让你不再害怕指针前言:复杂类型说明要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,所以我总结了一下其原则: 从变量名处起,根据运算符优先级结合,一步一步分析.下面让我们先从简单的类型开始慢慢分析吧:int p; //这是一个普通的整型变量int *p; //首先从P处开始,先与*结合,所以说明P是一//个指针,然后再与int结合,说明指针所指向//的内容的类型为int型.所以P是一个返回整//型数据的指针int p[3]; //首先从P处开始,先与[]结合,说明P是一个数//组,然后与int结合,说明数组里的元素是整//型的,所以P是一个由整型数据组成的数组int *p[3]; //首先从P处开始,先与[]结合,因为其优先级//比*高,所以P是一个数组,然后再与*结合,说明//数组里的元素是指针类型,然后再与int结合,//说明指针所指向的内容的类型是整型的,所以//P是一个由返回整型数据的指针所组成的数组int (*p)[3]; //首先从P处开始,先与*结合,说明P是一个指针//然后再与[]结合(与"()"这步可以忽略,只是为//了改变优先级),说明指针所指向的内容是一个//数组,然后再与int结合,说明数组里的元素是//整型的.所以P是一个指向由整型数据组成的数//组的指针int **p; //首先从P开始,先与*结合,说是P是一个指针,然//后再与*结合,说明指针所指向的元素是指针,然//后再与int结合,说明该指针所指向的元素是整//型数据.由于二级指针以及更高级的指针极少用//在复杂的类型中,所以后面更复杂的类型我们就//不考虑多级指针了,最多只考虑一级指针.int p(int); //从P处起,先与()结合,说明P是一个函数,然后进入//()里分析,说明该函数有一个整型变量的参数//然后再与外面的int结合,说明函数的返回值是//一个整型数据Int (*p)(int); //从P处开始,先与指针结合,说明P是一个指针,然后与//()结合,说明指针指向的是一个函数,然后再与()里的//int结合,说明函数有一个int型的参数,再与最外层的//int结合,说明函数的返回类型是整型,所以P是一个指//向有一个整型参数且返回类型为整型的函数的指针int *(*p(int))[3]; //可以先跳过,不看这个类型,过于复杂//从P开始,先与()结合,说明P是一个函数,然后进//入()里面,与int结合,说明函数有一个整型变量//参数,然后再与外面的*结合,说明函数返回的是//一个指针,,然后到最外面一层,先与[]结合,说明//返回的指针指向的是一个数组,然后再与*结合,说//明数组里的元素是指针,然后再与int结合,说明指//针指向的内容是整型数据.所以P是一个参数为一个//整数据且返回一个指向由整型指针变量组成的数组//的指针变量的函数.说到这里也就差不多了,我们的任务也就这么多,理解了这几个类型,其它的类型对我们来说也是小菜了,不过我们一般不会用太复杂的类型,那样会大大减小程序的可读性,请慎用,这上面的几种类型已经足够我们用了.1、细说指针指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
关于C语⾔指针的⼀些细节问题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、指针可以相加减吗可以相互加减。
但是⼀定要作有意义的运算。
当⼆个指针指向同⼀个数组的时候,它们相加减是有意义的。
如果⼆个指针分别指向⼆个不同的数组,那么指针之间的相加减就没有什么意义。
再再论指针《再再论指针》后记在这篇后记中,笔者将对三个问题进行补充:一、关于数组名取地址的问题。
c89、c99允许对数组名取地址,是由于数组符合一个对象的定义,按照一个对象的语义,对其取地址是合理的。
但矛盾在于,数组名是一个符号地址,是一个右值,对其取地址不符合&运算符的语法。
c89、c99委员会经过权衡,认为维护一个对象的合理性比一个运算符更重要、更合理,因此...2005-10-17 16:07 阅读(4461) 评论(4)再再论指针----篇首语指针是C语言规范里面一项核心内容,指针具有与生俱来的优势,利用指针可以写出许多短小精悍、效率极高的代码,它是C语言一把无可替代的利器,凭着这把利器,C语言与其它高级语言相比至少在效率方面高人一筹。
但是,由于指针的原理与使用方式跟人们通常的思维习惯有较大的差别,造成了指针比C语言其它概念难理解得多,这使得对指针认识不足成为了一种在C程序员中普遍存在的现象,这种不足必然导致程序员在指针...2005-09-17 16:19 阅读(7531) 评论(5)第一章什么是数组名?----一个让你吃惊的事实!数组是指针的基础,多数人就是从数组的学习开始指针的旅程的。
下面我节选一些在各种论坛和文章里经常见到的关于数组的文字:“一维数组是一级指针”“二维数组是二级指针”“数组名可以作为指针使用”“数组名就是..........的常量指针”“数组名就是..........的指针常量”..................................这些文字看起来非常熟悉吧?类似的文字还有许...2005-09-17 16:17 阅读(14529) 评论(113)第二章再一次吃惊----数组的数组与多维数组的区别看见这个题目,也许有些人就会嘀咕了:难道两者不是一样的吗?C语言的多维数组不就是数组的数组吗?不!两者是有区别的,而且还不小呢。
首先看看两者的共同点:1。
《再再论指针》第一章什么是数组名?--一个让你吃惊的事实!数组是指针的基础,多数人就是从数组的学习开始指针的旅程的。
下面我节选一些在各种论坛和文章里经常见到的关于数组的文字:“一维数组是一级指针”“二维数组是二级指针”“数组名可以作为指针使用”“数组名就是..........的常量指针”“数组名就是..........的指针常量”..................................这些文字看起来非常熟悉吧?类似的文字还有许多,或许你就是经常说这些话的人呢。
不过非常遗憾,这些文字都是错误的,实际上数组名永远都不会是指针!这个结论也许会让你震惊,但它的确是事实。
数组名、指针、地址这几个概念虽然是基础中的基础,但它们恰恰是被混淆和滥用得最多的概念,把数组名说成指针,是一个概念性的错误,实质是混淆了指针与地址两个概念的本质。
俗话说得好:浅水淹死人。
因此,在讨论数组之前,有必要先回过头来澄清一下什么是指针,什么是地址,什么是数组名。
指针是C语言具有低级语言特征的最直接的证据。
在汇编语言里面,指针的概念随处可见。
比如SP,SP寄存器又叫堆栈指针,它的值是地址,由于SP保存的是地址,并且SP的值是不断变化的,因此可以看作一个变量,而且是一个地址变量。
地址也是C语言指针的值,C 语言的指针跟SP这样的寄存器虽然不完全一样,但原理却是相通的。
C语言的指针也是一种地址变量,C89明确规定,指针是一个保存对象地址的变量。
这里要注意的是,指针跟地址概念的不同,指针是一种地址变量,通常也叫指针变量,统称指针。
而地址则是地址变量的值。
看到这里,也许你会觉得,这么简单的东西还用你来说吗?的确,对于p与&p来说,99%的人都能在0.1秒内脱口而出谁是指针,谁是地址,但是,又有多少人在使用指针的过程中能够始终如一毫不动摇地遵循这两个概念呢?不少人使用指针的时候就会自觉或不自觉地把指针和地址两个概念混淆得一塌糊涂了,数组名的滥用就是一个活生生的例子。
c语言指针总结C语言指针是一种强大而复杂的概念,对于初学者来说可能会感到困惑。
本文将总结C语言指针的定义、用法、特性以及常见问题,帮助读者更好地理解和应用指针。
一、指针的定义和用法:1. 定义:指针是一个变量,其值为另一个变量的地址。
可以通过指针来操作和引用存储在内存中的数据。
2. 用法:通过声明指针变量,可以将一个变量的地址赋值给指针,并通过解引用操作符‘*’来访问该地址上存储的值。
二、指针的特性:1. 指针的大小:不同类型的指针大小可能不同,但在同一平台上,所有指针的大小都固定。
2. 空指针:指针变量可以被赋值为空指针,即指向地址为0的位置。
空指针通常用来表示指针不指向任何有效的内存位置。
3. 野指针:未初始化或已经释放的指针称为野指针。
野指针可能指向任意内存位置,不可控制,因此应该避免使用。
4. 指针的算术运算:指针可以进行加、减运算,表示指针指向的位置前进或后退多少个存储单位。
5. 指针与数组:数组名可以看作是一个指向数组首元素的指针,而可以通过指针加减运算来遍历整个数组。
6. 指针与函数:指针可以作为函数参数或返回值,用于在函数之间传递数据或引用。
函数指针用于存储函数的地址,可以通过函数指针来间接调用函数。
三、指针的常见问题:1. 空指针引用:当一个指针为空指针时,解引用该指针会导致程序崩溃或未定义行为。
因此,在解引用指针之前应始终进行空指针检查。
2. 野指针问题:使用未初始化或已经释放的指针会导致不可预测的结果。
应该在使用指针之前正确初始化,并及时释放不再使用的指针。
3. 内存泄漏:如果通过动态内存分配函数(如malloc或calloc)分配内存后没有及时释放,就会导致内存泄漏。
要正确管理内存,避免出现内存泄漏问题。
4. 指针类型不匹配:指针可以指向不同类型的变量,但要注意指针的类型与其指向的变量类型要匹配,否则可能会出现类型错误的问题。
5. 指针运算错误:对指针进行错误的运算,例如指针越界、指针不合法的算术运算,可能导致程序崩溃或未定义行为。
我眼中的指针 1 —学习指针不可少的好文章第一章指针的概念指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针,需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。
让我们分别说明:先声明几个指针放着做例子:例一:(1)int *ptr;(2)char *ptr;(3)int **ptr;(4)int (*ptr)[3];(5)int *(*ptr)[4];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]在指针的算术运算中,指针所指向的类型有很大的作用。
指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。
指针(重中之重)指针的重要性表示一些复杂的数据结构快速的传递数据,减少了内存的耗用【重点】能直接访问硬件(地址就放在硬件中)是理解面向对象语言中引用的基础使函数返回一个以上的值(无指针时return是结束被调函数,只能返回一个值)总结:指针是c语言的灵魂指针的定义地址:内存单元的编号(一个内存单元就是一个编号也是是一个字节:8位)从零开始的非负整数范围:4G【0—4G-1】指针:指针就是地址,地址就是指针指针变量就是存放内存单元编号的变量,或者说指针变量就是存放地址的变量指针变量也叫地址变量,专门存放地址,不能存放某个实数,只能赋地址值指针和指针变量是两个不同的概念指针是存储单元地址,是个值,只不过这个值是内存单元的编号,是常量指针变量是存放存储单元地址的变量,能被赋予不同的指针值,有好多好多类型但是要注意:通常我们叙述时会把指针变量简称为指针,实际它们含义并不一样指针的本质就是一个操作受限的非负整数指针的分类:1、基本类型指针【重点】#include<stdio.h>int main(void){int *p;/*1、p是变量的名字,int *表示p变量存放的是int类型变量的地址2、int *p;不表示定义了一个名字叫做*p的变量3、int *p应该这样理解:p是变量名,p变量的数据类型是int *型所谓的int *类型实际就是存放int型的普通变量地址的类型,这个int *型与int型是两个不同的数据类型,这点应该特别注意4、p=i;(×),p=&i(√)*/int i=3;int j;//p=i;//error 因为类型不一致,p只能存放int类型变量的地址,不能存放int 类型变量的值//p=55;//error 原因同上p=&i;} /*1、p保存了i的地址,应因此p指向i2、p不是i,i也不是p,更准确的说:修改p的值不影响i的值,修改i的值也不影响p的值3、如果一个指针变量指向了一个普通变量,则*指针变量就完全等同于普通变量例子:如果p是个指针变量,并且p存放了普通变量i的地址则p指向了普通变量i*p 就完全等同于i,换句话说*p与i就是同一个东西,*p所代表的为p所指向的那个变量(一个存放普通类型值的变量或者单元或者说一个普通的箱子)也可以理解为相当于一个宏定义的性质的东西或者说:在所有出现*p的地方都可以替换成i在所有出现i的地方都可以替换成*p4、*p最准确的解释:*p表示的是以p的内容(i的地址)为地址的变量(*p还是变量)*p等同于i5、&与*优先级相同,且有从左到右的结合性所以&*p,&(*p)和&i三者是等价的,*&i,*(&i)和i 三者是等价的,*&i(p=&I,*p=i)即是i本身*/j=*p;//等价于j=iprintf("i=%d,j=%d\n",i,j);//i=3,j=3return 0;}2、基本类型指针常见错误解析:例子1#include <stdio.h>int main(){int *p; //第四行int i=5;*p=i; //有问题(第六行)printf("%d\n",*p);return 0;}/*此程序是错误的。
指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。
要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。
让我们分别说明。
先声明几个指针放着做例子:例一:(1)int*ptr;(2)char*ptr;(3)int**ptr;(4)int(*ptr)[3];(5)int*(*ptr)[4];指针的类型从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。
这是指针本身所具有的类型。
让我们看看例一中各个指针的类型:(1)int*ptr;//指针的类型是int*(2)char*ptr;//指针的类型是char*(3)int**ptr;//指针的类型是int**(4)int(*ptr)[3];//指针的类型是int(*)[3](5)int*(*ptr)[4];//指针的类型是int*(*)[4]怎么样?找出指针的类型的方法是不是很简单?指针所指向的类型当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。
从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。
例如:(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越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。
再再论指针篇首语指针是C语言规范里面一项核心内容,指针具有与生俱来的优势,利用指针可以写出许多短小精悍、效率极高的代码,它是C语言一把无可替代的利器,凭着这把利器,C语言与其它高级语言相比至少在效率方面高人一筹。
但是,由于指针的原理与使用方式跟人们通常的思维习惯有较大的差别,造成了指针比C语言其它概念难理解得多,这使得对指针认识不足成为了一种在C程序员中普遍存在的现象,这种不足必然导致程序员在指针的使用过程中不断遭受挫折,挫折多了,指针俨然变成一道无法逾越的难关,恐惧感也就油然而生了。
在恐惧感面前,某些程序员甚至产生了要避免使用指针的念头,这是非常不可取的。
指针是如此犀利,正是它才使得C语言威猛无比,如果就这样把它放弃了,那么C语言就算是白学了。
我们应当让指针成为你手中那把砍掉索伦手指上魔戒的举世无双的纳西尔圣剑,而不是成为你心中永远的魔戒。
本文的目的,是希望通过跟各位朋友一起讨论关于指针的几个关键概念及常见问题,以加深对指针的理解。
因此,本文并不是讲述形如int *p、struct {int i;float j;} *p等这些东西是什么的文章,阅读本文的朋友最好对指针已经具有一定的使用经验,正因如此,笔者才给文章起名叫《再再论指针》。
笔者不敢奢望能够完全解开你心中的魔结,但如果通过阅读本文,能够让你在日后的指针使用过程中减少失误,那笔者就心满意足了。
本文将讨论如下十个主题,读者最好按主题的顺序一个一个地阅读,当然,如果你只对其中某个或某几个主题感兴趣,只看那几个也未尝不可。
当你阅读本文后:如果你有不同的意见,欢迎你在评论里留下自己的见解,笔者很乐意跟你一起讨论,共同进步。
如果你觉得我说的全都是废话,那么恭喜你,你的指针已经毕业了。
如果你有太多不明白的地方,那么我介绍你先找一些关于数组与指针的读物看看,笔者推荐你阅读一本叫《C与指针》的书,看完后再回来继续思考你的问题。
第一章什么是数组名?--一个让你吃惊的事实!数组是指针的基础,多数人就是从数组的学习开始指针的旅程的。
下面我节选一些在各种论坛和文章里经常见到的关于数组的文字:“一维数组是一级指针”“二维数组是二级指针”“数组名可以作为指针使用”“数组名就是..........的常量指针”“数组名就是..........的指针常量” ..................................这些文字看起来非常熟悉吧?类似的文字还有许多,或许你就是经常说这些话的人呢。
不过非常遗憾,这些文字都是错误的,实际上数组名永远都不会是指针!这个结论也许会让你震惊,但它的确是事实。
数组名、指针、地址这几个概念虽然是基础中的基础,但它们恰恰是被混淆和滥用得最多的概念,把数组名说成指针,是一个概念性的错误,实质是混淆了指针与地址两个概念的本质。
俗话说得好:浅水淹死人。
因此,在讨论数组之前,有必要先回过头来澄清一下什么是指针,什么是地址,什么是数组名。
指针是C语言具有低级语言特征的最直接的证据。
在汇编语言里面,指针的概念随处可见。
比如SP,SP寄存器又叫堆栈指针,它的值是地址,由于SP保存的是地址,并且SP的值是不断变化的,因此可以看作一个变量,而且是一个地址变量。
地址也是C语言指针的值,C语言的指针跟SP这样的寄存器虽然不完全一样,但原理却是相通的。
C语言的指针也是一种地址变量,C89明确规定,指针是一个保存对象地址的变量。
这里要注意的是,指针跟地址概念的不同,指针是一种地址变量,通常也叫指针变量,统称指针。
而地址则是地址变量的值。
看到这里,也许你会觉得,这么简单的东西还用你来说吗?的确,对于p与&p来说,99%的人都能在0.1秒内脱口而出谁是指针,谁是地址,但是,又有多少人在使用指针的过程中能够始终如一毫不动摇地遵循这两个概念呢?不少人使用指针的时候就会自觉或不自觉地把指针和地址两个概念混淆得一塌糊涂了,数组名的滥用就是一个活生生的例子。
这一点甚至连一些经典著作也没能避免。
不过也不能全怪你自己,笔者认为某些国内教材应该承担最大的责任。
这些教材一开始就没有给读者好好地分清指针与地址的区别,相反还在讲述的过程中有意无意地混用这两个概念。
更有甚者,甚至在书中明言指针就是地址!说这话的家伙最应该在C语言这个地图上抹掉,呵呵。
两个月前我在购书中心随手翻开了某个作者主编的一本被冠以国家“十五”规划重点研究项目的书,书里就是这么写的。
当时笔者就感慨:不知道又要有多少人的思想被这家伙“强奸”了。
实际上,地址这个东西,本来就是一种基本数据类型,本应该在介绍整数、浮点、字符等基本类型的时候把地址显式地放在一起讨论,这样在后面介绍指针与数组的时候就能避免许多误解。
可惜不少教材或者根本没有谈及,或者就算提起这个类型也用了指针类型这个字眼。
这就错了,指针不是类型,真正的类型是地址,指针只是存储地址这种数据类型的变量!打个比方,对于int i=10;10是整数,而i是存储整数的变量,指针就好比这个i,地址就好比那个10。
指针能够进行加减法,原因并不是因为它是指针,加减法则不是属于指针这种变量的,而是地址这种数据类型的本能,正是因为地址具有加减的能力,所以才使指针作为存放地址的变量能够进行加减运算。
这跟整数变量因为整数能够进行加减乘除因而它也能进行加减乘除一个道理。
那么数组名又应该如何理解呢?用来存放数组的区域是一块在栈中静态分配的内存(非static),而数组名是这块内存的代表,它被定义为这块内存的首地址。
这就说明了数组名是一个地址,而且,还是一个不可修改的常量,完整地说,就是一个地址常量。
数组名跟枚举常量类似,都属于符号常量。
数组名这个符号,就代表了那块内存的首地址。
注意了!不是数组名这个符号的值是那块内存的首地址,而是数组名这个符号本身就代表了首地址这个地址值,它就是这个地址,这就是数组名属于符号常量的意义所在。
由于数组名是一种符号常量,因此它是一个右值,而指针,作为变量,却是一个左值,一个右值永远都不会是左值,那么,数组名永远都不会是指针!不管什么话,只要说数组名是一个指针的,都是错误的!就象把刚才int i=10例子中的10说成是整数变量一样,在最基本的立足点上就已经完错了。
总之要牢牢记住,数组名是一个地址,一个符号地址常量,不是一个变量,更不是一个作为变量的指针!在数组名并非指针这个问题上,通常会产生两种疑问:1。
作为形参的数组,不是会被转换为指针吗?2。
如果形参是一个指针,数组名可以作为实参传递给那个指针,难道不是说明了数组名是一个指针吗?首先,C语言之所以把作为形参的数组看作指针,并非因为数组名可以转换为指针,而是因为当初ANSI委员会制定标准的时候,从C程序的执行效率出发,不主张参数传递时复制整个数组,而是传递数组的首地址,由被调函数根据这个首地址处理数组中的内容。
那么谁能承担这种“转换”呢?这个主体必须具有地址数据类型,同时应该是一个变量,满足这两个条件的,非指针莫属了。
要注意的是,这种“转换”只是一种逻辑看法上的转换,实际当中并没有发生这个过程,没有任何数组实体被转换为指针实体。
另一方面,大家不要被“转换”这个字眼给蒙蔽了,转换并不意味着相同,实际上,正是因为不相同才会有转换,相同的话还转来干吗?这好比现在社会上有不少人“变性”,一个男人可以“转换”为一个女人,那是不是应该认为男人跟女人是相同的?这不是笑话么。
第二,函数参数传递的过程,本质上是一种赋值过程。
C89对函数调用是这样规定的:函数调用由一个后缀表达式(称为函数标志符,function designator)后跟由圆括号括起来的赋值表达式列表组成,在调用函数之前,函数的每个实际参数将被复制,所有的实际参数严格地按值传递。
因此,形参实际上所期望得到的东西,并不是实参本身,而是实参的值或者实参所代表的值!举个例来说,对于一个函数声明:void fun(int i);我们可以用一个整数变量int n作实参来调用fun,就是fun(n);当然,也正如大家所熟悉的那样,可以用一个整数常量例如10来做实参,就是fun(10);那么,按照第二个疑问的看法,由于形参是一个整数变量,而10可以作为实参传递给i,岂不就说明10是一个整数变量吗?这显然是谬误。
实际上,对于形参i 来说,用来声明i的类型说明符int,所起的作用是用来说明需要传递给i一个整数,并非要求实参也是一个整数变量,i真正所期望的,只是一个整数,仅此而已,至于实参是什么,跟i没有任何关系,它才不管呢,只要能正确给i传递一个整数就OK了。
当形参是指针的时候,所发生的事情跟这个是相同的。
指针形参并没有要求实参也是一个指针,它需要的是一个地址,谁能给予它一个地址?显然指针、地址常量和符号地址常量都能满足这个要求,而数组名作为符号地址常量正是指针形参所需要的地址,这个过程就跟把一个整数赋值给一个整数变量一样简单!在后面的章节中,笔者将严格地使用地址这一概念,该是地址时就用地址,该是指针时就用指针,以免象其它教材那样给读者一个错误的暗示。
第二章再一次吃惊--数组的数组与多维数组的区别看见这个题目,也许有些人就会嘀咕了:难道两者不是一样的吗?C语言的多维数组不就是数组的数组吗?不!两者是有区别的,而且还不小呢。
首先看看两者的共同点:1。
内存映象一样。
2。
数组引用方式一样,都是“数组名[下标][下标]........”。
3。
数组名都是数组的首地址,都是一个符号地址常量、一个右值。
由于两者的共同点主要反映在外部表现形式上,因此,从外部看来,数组的数组跟多维数组似乎是一样的,这造成了C程序员对两者的区别长期以来模糊不清。
但实际上,c语言限于本身的语言特性,实现的并非真正的多维数组,而是数组的数组。
数组的数组与多维数组的主要区别,就在于数组的数组各维之间的内在关系是一种鲜明的层级关系。
上一维把下一维看作下一级数组,也就是数组嵌套。
数组引用时需要层层解析,直到最后一维。
举个例,对于数组:int a[7][8][9];如果要访问元素a[4][5][6],首先就要计算第一维元素4的地址,也就是a+4,由于是数组的数组,元素4的值代表了一个数组,因此元素4的值就是它所代表的那个数组的首地址,我们用一个符号address1代表它,也就是address1=*(a+4),接着计算第二维,显然元素5的地址是address1+5,其值也是一个数组的首地址,用address2表示它,就是address2=*(address1+5),最后一维,由于已经到达了具体的元素,因此这个元素的地址是address2+6,其值*(address2+6)是一个整数,把address1和address2分别代入相应表达式,就成了:*(*(*(a+4)+5)+6);这就是我们熟知的[]运算符的等价表达式。