C语言中,指针式一个难点。主要表现在两个方面:1、各种类型的指针众数繁多。2、各种类型的指针如何应用。但是由于,指针有着运算速度快的优势,如果你进行操作系统,关注操作系统的内核(以linux为例),你就会发现操作系统来说更多运用到指针,究其原因,一个很重要的原因就是处理速度快。那么,今天,就把C语言的指针做个总结。
第一、 指针和变量
其表现形式只有一种:指针变量
常数是不能直接幅值给指针的,如果想给指针赋给常数,需要借用变量。具体如下操作:
int num=8;
int *pnum=#这时我们需要注意,pnum指向8存储的地址,*pnum为该地址存储的数据8。
指针变量的运算
指针变量不能相加 不能相乘 也不能相除
如果两个指针变量指向的是同一块连续空间中的不同存储单元,
则这两个指针变量才可以相减
第二、 指针和数组
1、数组名是一个指针变量,是一个地址,它存放的是数组首元素的地址。
在分析数组的时候,要注意到三点:
1)数组名代表的是什么;
2)数组元素如何表示;
3)数组作为函数参数表示什么,如何使用
如果这两个问题弄清楚了,相信在编程中就不会出错。
下标和指针的关系:如果p是个指针变量,则p[i]永远等于 *(p+i)
int arry[3]={1,3,5};
那么arry就是该数组的首地址,*arry就是该数组首地址存放的数据1,*(arry+1)则为该数组的第二个位置存放的数据3。
从以上分析可以看出:数组名可以看成指针,可以把数组名当做指针来操作。
注意:
sizeof运算与数组,指针。
char a[] = "hello"; char *p = "hello"; char b[2][3]; printf("_______%d\n",sizeof(a));//6 printf("_______%d\n",sizeof(b));//4 printf("_______%d\n",sizeof(p));//6分析:数组名虽然可以看成指针,但是本质是不一样的。
静态数组存放在全局变量区,或者栈上,是一个有固定大小的内存区域;而指针可以存放在任何区域,其内容是一个地址;所以,sizeof a 得出数组的大小;但是sizeof p只是指针的大小;sizeof b也是其数组的大小,虽然没有初始化。
2、其表现形式有三种:数组元素的指针、数组指针、指针数组
数组元素的指针:
int a[5]; int *p; p = &a[0];//p=a;作用是将数组首元素的地址赋值给p; /**** 或者 int *p=a; 作用是将数组首元素的地址赋值给p;因为数组名就是首地址; 等价于 int *p = &a[0]; *****/注意:
int a[10];int *p = &a
此时p还是指向a的首元素,但含义不同,p具有行指针的含义,p+1不再是a[1],而是跳过40的字节,指向数组末尾a[9]的下一个元素。
数组指针:a pointer to an array,即指向数组的指针,也称为行指针,注意:它是指针,不存在数组名一说
指针数组:array of pointers,即用于存储指针的数组,也就是数组元素都是指针
指针数组最频繁的用处是存放不同长度的字符数组
下面举例说明数组指针与指针数组的区别:
int (*a)[4] 数组指针 表示:一个指向一维数组的指针
元素表示:(*a)[i]
int* a[4] 指针数组 表示:一个数组元素都是指针的数组
例如:数组名:数组名a表示数组的第一个元素,即:a[0]
元素表示:*a[i] *(a[i])是一样的,因为[]优先级高于*
a 、int *data[3] 为指针数组,数组中每个元素为一个指向int型数据的指针,赋值如下:
int arry[3]={1,3,5};
int *data[3]={arry,arry+1,arry+2};
分析:
(1)数组名:data为data数组的首地址;不存在*data一说;
(2)数组元素的表示及其地址的表示:
因为指针数组中都是指针,所以,要表示数组元素的值,要加上*,否则只是地址。
元素的地址: data[0]为指向arry首地址,
data[1]为指向arry第二个元素的地址,
元素的值:*data[0]为该地址存放的数据1,
*data[1]为该地址存放的数据3。
b、int(*data)[3]为数组指针,指向int型数组,赋值如下:
int arry[3]={1,3,5};
int (*data)[3]=&arry;
分析:
(1)表示一指针,所以没有数组名一说。表示*data有3个元素,每个元素为整型,也就是说data指向一个有3个整型元素的数组。
另外,数组名可以看做指针,所以,数组指针有点二级指针的味道,不是一个,而是一组二级指针。
(2)指针所指向的数组元素的表示:
元素的地址: *data、data[0]是第一个元素的地址,
data[1]是第二个元素的地址, 但*data+1不是第二个元素的地址,它指向数组的末尾;
元素的值:**data、*data[0]就为首元素的值1,**data就是二级指针
*data[1]为第二个元素的值3。
3、二维数组
int data[3][4]:一维数组的数组
(1)数组名:因为二维数组的数组名是一个数组指针,所以data就是一个数组指针。
数组名data代表二维数组首行(一维数组)的首地址,还记得上面说过数组指针就是行指针么;
data+1代表第一行的首地址,data+2代表第二行的首地址;
(2)数组元素的的值及其地址:
地址的确定:要先使用行指针定位在哪一行,然后在这一行中定位出列;
地址及其值:
data[0]是首行数组的数组名,因为数组名代表数组首元素地址,所以
data[0]表示一维数组data[0]中第0列元素的地址,即&data[0][0];
data[0][1]和*(data[0]+1)都表示首行第二个元素;
同理
data[1]代表第二行的首地址,也代表了一维数组data[1]中第0列元素的地址,即&data[1[0];
4、数组作为函数参数
5、指针做函数参数一维数组作参数:退化为一级指针
二维数组作参数:退化为行指针
指针数组做参数:退化为二级指针
【分析】一维数组的数组名就是一级指针;二维数组的数组名就是行指针;指针数组的数组名是一级指针,而数组元素又是一个指针,所以是二级指针。例如main函数的参数。
【结论】数组作为形参,看数组名的函数含义。
一级指针作为形参:偏移量是sizeof 指针类型
二级指针作为形参:偏移量是一行
行指针作为形参:偏移量是一行
一级指针作为形参:可以用来遍历一维数组
二级指针作为形参:可以用来遍历指针数组,不可以遍历二维数组
可以给实际参数是一级指针变量申请堆空间
行指针作为形参:可以用来遍历二维数组
【结论】函数形参的类型与遍历对象对应。
第三、 指针与函数
其表现形式有两种:函数指针、指针函数
函数指针是指向函数的指针变量,即本质是一个指针变量。
指针函数是指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针。
例如:
int (*f) (int x); /* 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
类型标识符 *函数名(参数表)/* 指针函数*/
int *f(x,y)
1、指针函数:
格式:
类型说明符 * 函数名(参数)
当然了,由于返回的是一个地址,所以类型说明符一般都是int。
例如:int *GetDate();
int * aaa(int,int);
函数返回的是一个地址值,经常使用在返回数组的某一元素地址上。例如:
int * GetDate(int wk,int dy); main() { int wk,dy; do { printf(Enter week(1-5)day(1-7)\n); scanf(%d%d,&wk,&dy); } while(wk<1||wk>5||dy<1||dy>7); printf(%d\n,*GetDate(wk,dy)); } int * GetDate(int wk,int dy) { static int calendar[5][7]= { {1,2,3,4,5,6,7}, {8,9,10,11,12,13,14}, {15,16,17,18,19,20,21}, {22,23,24,25,26,27,28}, {29,30,31,-1} }; return &calendar[wk-1][dy-1]; }程序应该是很好理解的,子函数返回的是数组某元素的地址。输出的是这个地址里的值。
2、函数指针
(1)其函数类型由:返回类型和参数表决定
(2)定义和使用。因为() 运算优先级高于*,所以要给指针加上()。
函数指针在linux操作系统中很常见,现在拿个简单的例子来进行说明:
int print(int a, int b ,char *p) { printf("in put number sum is %d,input string is %s\n",a+b,p);//三个参数,int int和char* return a+b; } int main() { int (* pprint)(int,int,char *);//定义指向函数print的指针函数*pprint,三个参数,int int和char* pprint=print;//给指针函数赋值 int c; c=pprint(3,6,var); printf("%d\n",c);//输出print的return值 }
从上面这个例子中可以看出,一个函数其实就是从一个地址开始的特殊功能程序,其函数名就为该程序的首地址,所以可以这么给指针函数赋值:pprint=print;//给指针函数赋值
注意使用typedef可以简化函数指针。见下例:
#include<stdio.h> #include<stdlib.h> int inc(int a) { return (++a); } int multi(int *a,int *b,int *c) { return (*c=*a* *b); } typedef int (*FUNC1)(int in); typedef int (*FUNC2)(int*,int*,int*); void show(FUNC2 fun,int arg1,int *arg2) { FUNC1 p=&inc; int temp=p(arg1); fun(&temp,&arg1,arg2); printf("%d\n",*arg2); } int main() { int a; show(multi,10,&a); system("pause"); return 0; }输出:110。
FUNC1 p=&inc;//等价于FUNC1 p= inc;
此时,我们可以使用 p(a); 或者 inc(a);
第四、 指针与结构体
这个也是一个很有趣的方面,先看一个例子:
struct PERSON
{
char *pername;
int age;
}person;
如果想给*pername幅值,那么可以如下操作:
person.pername="jack";//用指针,可以完成赋值
如果结构体定义为
struct PERSON
{
char pername[20];
int age;
}person;
那么person.pername[20]="jack"是不能通过的,究其原因就是结构体为抽象数据类型,不分配存储单元,所以数组赋值不通过。如果采用指针赋值,那么可以解决这类问题。
第五、const与指针
有了const修饰的变量,我们不称它为变量,而称符号常量。const 的作用是不能在它处重新赋新值该变量了。
另外,我 们还要知道格式的写法。有两种:const int ic=20;与int const ic=20;。它们是完全相同的。这一 点我们是要清楚。总之,你务必要记住const 与int哪个写前都不影响语义。好了,我们现在已经搞定一个“双包胎”的问题。那么 int * const pi与前两个式子又有什么不同呢?我下面就来具体分析它们的格式与语义吧!
const int * pi(指向的变量值不能修改,但地址可以修改)int const * pi
//*************代码开始 *************** int i1=30; int i2=40; const int * pi=&i1; pi=&i2; //4.注意这里,pi可以在任意时候重新赋值一个新内存地址 i2=80; //5.想想看:这里能用*pi=80;来代替吗?当然不能 printf( “%d”, *pi ) ; //6. 输出是80 //*************代码结束***************
//*************代码开始 *************** int i1=30; int i2=40; int * const pi=&i1; //pi=&i2; 4.注意这里,pi不能再这样重新赋值了,即不能再指向另一个新地址。 //所以我已经注释了它。 i1=80; //5.想想看:这里能用*pi=80;来代替吗?可以,这 里可以通过*pi修改i1的值。 //请自行与前面一个例子比较。 printf( “% d”, *pi ) ; //6.输出是80 //***************代码结束 *********************
看了这段代码,你明白了什么?有没有发现 pi值是不能重新赋值修改了。它只能永远指向初始化时的内存地址了。
总结:
变量指针、数组指针、函数指针都是指针;
指针数组是数组,常用于二维数组;指针函数是函数,返回值是一个地址,常用int型;
const int * pi 与 int const * pi(指向的变量值不能修改,但地址可以修改)
int * const pi(地址不能修改,变量值可以修改)