Programming

搞清楚指针!

指针简介

什么是指针?

指针其实就是一个特殊的变量,虽然叫做指针,但如果理解成“指向”的话就很难理解,事实上,“指针”通常用于保存一个地址,而这个地址的类型在声明指针变量时确定

来看一个例子

int a;    //声明一个int型变量
int *b;   //声明一个指针变量b,用于保存一个地址
          //地址所保存的数据为int型
int *c;   //同上
b = &a;   //把变量a的地址赋给b,“&”是取变量地址的操作
c = b;    //将b的值赋给c,现在b和c的值都是a的地址

 

指针的类型

只需要把指针声明语句中的指针名字去掉,剩下的部分就是指针的类型

int *a;   //指针类型为int *
char *a;  //指针类型为char *
int **a;  //指针类型为int **
int(*a)[3]; //指针类型为int(*)[3]
int*(*a)[3]; //指针类型为int*(*)[3]

指针所指向的类型

指针所指向的类型决定了编译器将那片内存区里的内容当做什么来看待

只需要把指针声明语句中的指针名字和左边的*去掉,就是指针所指向的类型

int *a;    //指针所指向的类型为int
char *a;   //指针所指向的类型为char
int **a;   //指针所指向的类型为int *
int*(*a)[4] //指针所指向的类型为int*()[4]

注意,指针的类型指针所指向的类型是两个概念

指针的值

指针的值是指针本身存储的数值,又叫指针所指向的内存区或地址,这个值被编译器当做一个地址,而不是一个一般的数值

在32位程序中,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。

指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为sizeof(指针所指向的类型)的一片内存区域。

我们说一个指针指向了某块内存区域,就相当于说该指针的值就是这块内存区域的首地址。

应该注意到,指针所指向的内存区指针所指向的类型是两个完全不同的概念

两种运算符

&取地址运算符

&a 的运算结果是一个指针,指针的类型是a的类型加上*,指针所指向的类型是a的类型,指针所指向的地址就是a的地址

*间接运算符

*a就是引用这个a的地址所保存的变量,类型是a指向的类型,占用的地址是a所指向的地址,

来看例子!

int a = 12;
int b;
int *c;
int **d;
c = &a; //&a的结果是一个指针,类型是int *,指向的类型是int,
        //指向的地址是a的地址
*c = 24;//*c的结果,类型是int,占用的地址是c所指向的地址
        //显然,*c就是变量a
d = &c; //&c的结果是一个指针,类型是c的类型加一个*,也就是
        //int **,该指针所指向的类型是c的类型,也就是int *
        //所指向的地址就是c的地址
*d = &b;//*d是一个指针,&b也是一个指针,且*d的类型为&c所指向的
        //类型,为int *,所以*d和&b这两个指针的类型和所指向的类型
        //是一样的,所以可以进行赋值
**d = 36;//*d是一个指针,在这里再做一次*运算,就是一个int类型
        //的变量

再来看一个结构指针的例子

struct node{
    int data;
    struct node *next;
};    //定义结构类型node
int main(void)
{
    struct node a;
    struct node *p = &a;    //将结构体a的所占用内存的首地址
                            //赋给了结构指针p
    printf("%d",(*p).data); //利用取地址符运算取出指针p所指
                            //向结构体a中的data值
    //(*p).data可替换为p->data
}

 

指针的算术运算

指针可以加上或减去一个整数,这种运算和通常数值的加减运算的意义不一样,它是以单元为单位的。

来看例子

char a[20];
int *ptr = (int *)a;
ptr++;

指针ptr的类型为int *,指向的类型为int,初始化为指向整形变量a。

指针ptr加了1,编译器就把指针ptr的值加上了sizeof(int)

我们可以用一个指针和一个循环来遍历一个数组

int array[20] = {0};
int *ptr = array;
for(i=0;i<20;i++)
{
    (*ptr)++;
    ptr++;
}

这个例子就是将整型数组中每个单元的值加1,然后再将指针ptr加一个单元,使之能够访问数组的下一个单元

再来看一个例子

int main(void)
{
    char a[20]="you are a student";
    char *p=a;
    char **ptr = &p;
    printf("**ptr=%c\n",**ptr);
    ptr++;
    printf("**ptr=%c\n",**ptr);
}

ptr的类型为char **,指向的类型为char *,指向的地址就是p的地址,当执行ptr++时,会使指针加上sizeof(char *),也就是&p+4,那 * (&p+4)指向哪呢?这个我们就不知道了

所以最后的输出可能是一个随机的值,也有可能是一个非法操作

总结

一个指针ptrold加(减)一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同,ptrnew的值将比ptrold的值增加(减少)n*sizeof(ptrold所指向的类型)个字节

指针与数组

数组的数组名其实就可以看作一个指针

int array[10]={0,1,2,3,4,5,6,7,8,9};
int value;
value = array[0];    //也可以写成:value=*array
value = array[3];    //也可以写成:value=*(array+3)
value = array[9];    //也可以写成:value=*(array+9)

如果把array看成指针的话,它指向数组的第0个单元,类型是int *,所指向的类型自然就是int

char *str[3]={
    "Hello,this is a example",
    "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 example”的第一个字符的地址,即’H’的地址

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

*(str+1)也是一个指针,它的类型是char *,它所指向的类型是char,它指向”Hi,good morning”的第一个字符’H’

 

当我们声明一个数组TYPE array[n]时,数组名称array就有了两重含义

  • 它代表整个数组,类型是TYPE[n]
  • 它是一个常量指针,指针的类型是TYPE *,指向的类型是TYPE,指向的内存区是数组第0号单元

再来看不同表达式中array不同的作用

  • 在sizeof(array)中,array代表数组本身,因此此时sizeof函数测出的是整个数组的大小
  • 在*array中,array是指针,因此 * array的结果是数组第0号单元的值,sizeof(*array)测出的是数组单元的大小
  • array+n中,arrray是指针,array+n也是指针,它指向数组第n号元素

指针与函数

指针作为函数的参数

通过将地址当做实参传给函数中形参(指针),完成赋值

例如冒泡排序

void exchange(int *p,int *q)
{
    int temp;
    temp = *q;
    *q = *p;
    *p = temp;
}

int main(){
    int a[10]={3,4,5,1,6,7,8,9,10,0};
	for(int i=0;i<10;i++){
		for(int j=9-i;j>=0;j--){
			if(a[j]<a[j-1]){
				exchange(&a[j-1],&a[j]);
			}
		}
	}	
	for(int i=0;i<10;i++){
		printf("%d",a[i]);
	} 
}

指针作为函数的返回值

输入一个字符串和一个字符,如果该字符在字符串中,就从该字符首次出现的位置开始输出字符串中的数字

char *match(char str[],char a){
	int i=0;
	char *p;
	while(1){
		if(str[i]==a){
			p=&str[i];
			return p;
		} 
		i++;
	}
}

int main(){
	char str[20];
	int i=0;
	char *q;
	 
	while((str[i]=getchar())!='\n'){
		i++;
	}
	str[i]='\0';
	q=match(str,'a');  //调用match函数,寻找相同的值 	
	printf("%s",q);    
}

指向函数的指针

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

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;;)
    {
        num+=*s;s++;
    }
    return num;
}

函数fun统计一个字符串中各个字符的ASCII码值之和

指针与结构类型

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

struct Mystruct
{
    int a;
    int b;
    int c;
};
struct Mystruct ss={20,30,40};
struct Mystruct *ptr = &ss;
//声明一个指向结构对象ss的指针
//类型是Mystruct *,指向类型是Mystruct
//通过ptr访问三个成员变量
ptr->a;
ptr->b;
ptr->c;
int *pstr = (int *)&ss;
//声明一个指向结构对象ss的指针
//但指向的类型与ptr不同
//通过pstr访问三个成员变量
*pstr;  //访问a
*(pstr+1);  //访问b
*(pstr+2);  //访问c
//注意,这样用pstr来访问实际上是不正规的
//因为在存放结构对象的各个成员时,有些情况
//需要在相邻两个成员之间加若干个“填充字节”
//这就导致*(pstr+1)不一定能访问到结构成员b

复杂类型的指针

int p;//整型变量
int *p;
/*  从p开始,p先跟*结合,说明p是一个指针
再与int结合,说明指针指向的内容的类型为int
所以p是一个返回整型数据的指针   */
int p[3];
/*  p先跟[]结合,说明p是一个数组,再与int
结合,说明数组中的元素都是整型
所以p是一个由整型数据组成的数组   */
int *p[3];
/*  p先跟[]结合,说明p是一个数组,再与*结合
说明数组中的元素是指针类型,再与int结合,说明
数组里的元素是指针类型,再与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结合,说明指针指向的内容是整型数据
所以p是一个参数为一个整型数据且返回一个指向由整型指针
变量组成的数组的指针变量的函数*/

 

后记

因为水平有限,所以可能有些地方讲的不是很清楚或者不充分,主要是鉴于自己在指针这方面有一些不理解,所以参考了网上的一些资料后整理下来的,多有纰漏,还望多多指教

写了这么多,最重要的还是要会用指针解决一些问题,所以后续可能会添加一些题目,就先这样了。

发表评论

电子邮件地址不会被公开。 必填项已用*标注