说到C语言, 很多人都是又爱又恨啊,既感到用C语言给了程序员极大的开放度和自由度,同时又对C语言的灵活性和高难度性。
就目前中国教育做法来说吧,估计大部分高校给学生选的入门级语言就是C语言, 然而经过大学几年的学习,大部分的学生也只能做到写个“HelloWord” 这样的代码。即便是计算机专业的毕业生,在离开学校后,大部分也是对C语言的掌握也只是停留在简单的应用,更不用说非计算机专业的学生了, 就像我这样非计算机专业毕业的,到现在也不会用C语言编写一个具有实际应用意义的程序。
估计C语言中最难让人摆平的估计要算是指针了, 不但难以捉摸,同时又非常复杂。尤其是当具有复杂的数据类型定义的时候。
1、指针
何谓指针,这个问题估计不需要说明了。从硬件角度来看,指针应该指的是CPU地址总线上呈现的电平状态的数字化表示,估计大家都知道经典8051中的寻址过程, 通过地址总线选择需要操作的存储地址;在8051中我们知道共有16根地址总线, 因此具有2^16方的可寻址空间,就是具有64K的寻址空间。当所有地址总线呈现低电平时选择的是0000_0000_0000_0000,即0000H的地址;而当地址总线全部呈现高电平状态则选择的是1111_1111_1111_1111,即FFFFH的地址。这个同样适合8086架构下的寻址, 如果用汇编语言编写程序,就可以直接指定要操作的地址,或者说可以直接寻址地址。
2、C语言中的指针
C语言高效的一个原因就在于可以直接对地址进行操作,虽然不如汇编语言那样的直接,但是相对于其他一些语言例如VB等语言来说,C的指针操作已经非常“高级”了。C语言的发明者真够神的, 发明了指针这样难以驾驭的指针,但是C语言中指针的定义则非常的简单。
3、C语言中指针类型定义
定义语法:
指针指向的基类型 * 指针标识符
例如: int * pValue; 这样就定义了一个可以指向int类型变量的指针,哈哈,还真神奇,这样就可以控制硬件的连线上的电平了,
4、指针用法
测试代码:
Exp1:
#include <stdio.h>
#define PINT int *
int max(int x, int y)
{
return x>y ? x : y;
}
int main(int argc, char **argv)
{
int pTest;
PINT pToInt;
int (*p)(int, int);
pToInt=&pTest;
pTest=100;
p=max;
printf("%d, %d, %d", *p,max,*pToInt);
getch();
}
这个地方发现一个编译上的差距: 在WinTC上编译 *pToInt 输出的是 7, 而在VC 6.0中输出的则是预想的100; 为什么呢? 目前没有搞明白,哎..........
同时输出的*p== max; 这段测试代码用到了一个比较特殊的指针定义,函数指针, 从输出来看与实际预想的一样。如上图所示可以知道WinTC、VC6对地址的解释不一致。唯一可以解释的就是在Win32中地址是平坦的即flat的寻址方式, 而在Win16或者Dos上就存在很多种寻址方式,比方说tiny,small等方式造成这样的,因为我在WinTC中测试的结果是 sizeof int = 2; 而在VC6.0中测试的结果是 sizeof int = 4,估计是因为这个原因导致上面的输出不一致。
在函数参数中使用指针:
int main(int argc, char **argv) // main函数的参数中就使用指针,并且是指针的指针。
等价版本的main函数原型
int main(int argc,char *argv[])
在函数参数中传递函数指针
int get_max( int x, int y, int (* p)(int x,int y) )
{
return (*p)(x,y);
}
结合上面的程序可以实现以下程序代码:
#include <stdio.h>
#define PINT int *
int max(int x,int y)
{
return x>y?x:y;
}
int get_max(int x, int y,int (*p)(int x, int y))
{
return (*p)(x,y);
}
int main(int argc,char **argv)
{
int (*p)(int,int),
test;
PINT pToInt;
p=max;
test=get_max(10,20,*p);
pToInt=&test;
printf("%d, %d, %d",*p,max,*pToInt);
getchar();
}
5、复杂指针定义:
指针常量和常量指针:
int const * p; // 定义一个指向常量的指针, 这个指针可以随意改变指向
int * const p; //定义一个指针常量, 这个指针只能指向一个变量,并且指向后不能在改变
const int * const p; //定义一个指向常量的指针常量, 指针变量本身的值不可修改,并且指针指向的变量也不能被修改。
例如:
int * const pconst=&test; //这里定义的pconst指针就不能再指向别的整型变量
const int constvalue=50; //定义一个整型常量, 等价于 int const constvalue
int const *constvar=&constvalue; //定义一个指向整型常量的指针, 指针指向的变量值不可修改,但是指针指向可以更改
const int * const constpvar=&constvalue; //定义一个指向整型常量的指针, 指针的指向不可修改。
这类指针定义的一个简单的阅读方法就看: const修饰的是什么, 当其修饰数据类型的时候则定义的是数据类型的变量不能修改;
当修饰的是指针变量的时候则指针的指向不可改变。
从上面的实例可以看出: 当没有指针存在的时候,const 的位置不会影响变量的使用,但是也可以根据其修饰的对象来理解。
指针数组和数组指针
int *p[];
int (*p)[];
这两类指针的定义着实非常令人纠结啊, 到底怎么解释和理解呢? 一团雾水啊...........
首先看第一个: int *p[ ];
如上定义: p 的左右各有一个运算符, *和[]; 指针运算符和数组运算符, 在C规范里面, 数组运算符的优先级别高于指针运算符。
在这里同样可以利用运算符的优先级来理解这个指针,
因为[ ]的优先级高于 * ,引起p应该先和[ ]结合,这里就是可以看出,p是一个数组, 然后p再与* 结合,可以看出p是一个指针,最后看数据基类型,p的数据基类型是int型的;综合上面的描述可以知道: p被定义为一个存储int型指针的数组。 即p是一个数组,其数组元素的类型是int型指针。
例如: int px;
int py;
int pz;
int *p[3];
则可以有:
p[0]=&px;
p[1]=&py;
p[2]=&pz;
如果要引用其指向的变量的内容的话,可以这样使用: int sizex=*p[0]; 这就是指针数组, 就是说数组元素全是指针。
接下来看第二个:
int (*p)[];
同样可以利用优先级别来理解: ()和[ ]具有相同的优先级, 因此 p 是一个指针, 然后再用 [ ]来修饰p; 则可以看出p将指向一个数组类型数据,这就是说 int (*p)[]是指向int型数组的指针。这个指针不能指向别的数组。
例如:
int iArray[4][5];
int (*pArray)[5];
pArray=iArray; //这样可以编译成功, 因为pArray的类型是 : int (*) [] ; 而 iArray 的类型是 int [4][5]; 可以进行数据类型的转换
但是如果:
pArray=&iArray; //编译不成功,为什么呢? 因为pArray的类型是 : int (*) [] ; 而&iArray 的类型是 int *[][];很显然数据类型不一样
如果:
pArray=&iArray[0]; // 编译成功。
我们知道在二维数组中, 可以这样理解:其行元素相当于指针,即 iArray[0]、 iArray[1]、 iArray[2]、iArray[3], 但是其存储的并不是指针,其存储的是一个具有5个元素数组的首地址。但是需要这样才能 iArray=&iArray[0]; (这里二维数组的首地址与 iArray[0] 的地址相同 )。
对于数组指针的理解,可以将变量去除后然后进行剥离得出其数据类型然后进行理解。例如:
pArray: int (*)[];
&iArray[0]: int (*)[ ];
特殊的引用方式:
int iArray[4];
int (*pArray)[4]=NULL; //指定义不初始化同样可以,但是为了防止出现游离指针,最好用NULL初始化;
pArray=&iArray; // 这个编译成功
指向函数的数组指针
int
(*a[10])(
int
); // 这个定义一个数组,数组共有10个元素,每个元素存储一个指向 int (*)(int )函数的指针, 同样利用优先级来理解。
指针的指针
int **pToPoint;
int *pToInt;
int age;
pToAge=&age;
PToPoint=&pToAge;
通常这个应用于数组和字符串的处理, 可以见两种main函数原型的定义。
哎..........虽然写了这么多但是对于这个C语言中的指针认识还是不够,尤其是数组指针那一块,估计需要慢慢琢磨才能真正的理解,
同时对于 **P和*p[]之间差别可以参照:数组地址和指针进行复合运算 。