最详细讲解指针

指针

一、指针变量的定义

C语言中有两种变量,其中包括普通变量(内容变量),和地址变量(指针变量)。普通变量(内容变量)存内容,地址变量(指针变量)存地址。

  1. 定义的格式

格式:类型 *指针变量名

//普通变量
int a;
//地址变量
int *a;

注:

① 定义变量(普通变量,指针变量)都必须在前面有类型名

② 在定义指针变量时,指针变量名前的"*"表示现定义的是一个指针类型的变量。“*”并不是指针变量名的一部分,只是一个标志。

③ 指针变量专门用来存放地址,禁止将一个整型直接赋值给一个指针变量。

  1. 指针变量的引用

“&”取地址运算符,通过&运算符可以取出普通变量的
地址。
*指针运算符,*可以取出指针变量所指向的普通变
量的值,( 间接引用普通量)

例题:

#include<stdio.h>
int main(){
    
    
	int a=4,b=0;
	int *p,*q,*t;
	p=&a;    //取出变量a的地址
	b=*p;
	 
	printf("a=%d\nb=%d\n*p=%d\n",a,b,*p);
	printf("\n\n\n");
	printf("a的地址为:%p\nb的地址为:%p\np的地址为:%p",&a,&b,p);
	return 0;	
}
//运行结果:
a=4
b=4
*p=4
a的地址为:000000000062FE14
b的地址为:000000000062FE10
p的地址为:000000000062FE14
--------------------------------
Process exited after 0.0017 seconds with return value 0

运行示意图:

在这里插入图片描述

注:

① 可以通过赋值使一个指针变量“指向“某一普通变量(指针变量=&普通变量)

②在C语言中正确的做法是先让指针变量指向一个确定的存储单元后,再通过该指针变量引用它所指向的存储单元,

③变量名(普通变量、指针变量)都表示其存储单元内的值。

④ 若指针变量p指向变量a,即将变量a的地址赋给了指针变量p。

⑤ 所有的指针变量在内存中分配的字节数相同。

//关于第五条的解释
#include<stdio.h>
int main(){
    
    
	int *p;
	double *q;
	float *t;
	char *r;
	printf("\t int *p = %d\n",sizeof(p));
	printf("\t double *q = %d\n",sizeof(q));
	printf("\t float *t = %d\n",sizeof(t));
	printf("\t char *r = %d\n",sizeof(r));
	return 0;	
}

运行结果:

在这里插入图片描述

二、数组指针

由于数组元素与普通一样,所以定义指向数组元素的指针变量与定义指向普通变量的指针变量完全一样

int *p;
int a[10];
//a为数组首地址,即a <=> &a[0];
p=a; 等价 p=&a[0];
p=a+1 等价 p=&(a+1)

数组名为数组首地址

//上述等价的实例
#include<stdio.h>
int main(){
    
    
	int a[10];
	int *p,*q;
	p=&a[1];
	q=a+1; 
	printf("%p\n",p);
	printf("%p\n",q);
}

运行结果:

在这里插入图片描述

注:

①在C语言中规定:数组名代表数组的首地址,而且是.个地址常量。

②当指针变量指向数组中的某-一个元素时, 指针变量加1后指向数组的下一个元素,指针变量减1时指向数组中前个元素。

③当指针变量指向数组时,下标运算([])用于数组也可用于指针变量后

④若两个指针变量指向同一个数组,则这两个指针变量可以进行大小比较如

⑤在形参中的数组实际上是一一个指针变量,并不是真正的数组, 因为该“数组名”的值是可以改变的,而真正的数组名的值是不能改变的。

⑥若形参是数组或指针变量,则在函数中可以通过该形参改变实参的值

例题1:

在这里插入图片描述

答案及解析:

在这里插入图片描述

例题2:

//运行结果是什么?
#include<stdio.h>
int main() {
    
    
	int *p,a[3];
	p=a;  //将数组a的首地址,赋值给指针p 
	for(int i=0; i<3; i++) {
    
    
		scanf("%d",p++); //因为数组名就是地址,所以不用加取地址符 
	}
	printf("\n\n");
	
	for(p=a;p<a+3;){
    
      //将数组a的首地址,赋值给指针p 
		printf("%d ",*p++); //没输出一次,就向后移动一位 
	} 
	return 0;
}

运行结果:

在这里插入图片描述

三、指向多维数组的指针变量

Ⅰ . 指针与指针的区别

如:在int a[3]中,a+1加的是一个元素,int a[3][4],其中a+1加的是一行,如下图所示
在这里插入图片描述

切记:

① 只有列指针才是“真正”指向元素。即指向某一个元素的存储单元。
② 一维数组名表示的是列指针:二维数组名表示的是行指针。

注:若a是一个二维数组,则有

a+i 是行指针,即指向的一整行。若对它加1则指向下一行。

*(a+i)a[i] 一样,都是一个列指针即指向的是一个元素。

*(a+i)+ja[i]+j一样,都表示元素a[i][j]的地址,即与&a[i][j]等价。*(a+i)+j=a[i]+j=&a[i][j]

*(*(a+i)+j)*(a[i]+j)(*(a+i))[j]a[i][j]一样,都表示元素a[i][j]

例题1:

若有以下定义:int w[2][3] 则对w的非法引用的是:

A、*(w[0]+2) B、*(w+1)[2]C、w[0][0] D、*(w[1]+2) E、 w[1]+2

解析:A代表的是w[0][2]的元素, C代表的是元素w[0][0] D代表的是w[1][2]的元素E代表的是&w[1][2]也就是说E是一个地址而不是一个元素。

​ B中,w是指w[0][0]的地址,所以w+1指的是向下移动一行,*(w+1)[2]即再向下移动两行,所以代表w[3][0]的元素,但是由于int w[2][3]所以B越界了

例题2:

int a[3][4]; int *p=&a[0][3];

则:

p+1指向元素?

p+4指向元素?

p-2指向元素?

常用于取二维数组a元素地址的方式?

分析如下:

在这里插入图片描述

例题3:

打印二维数组a[4][4](只能用一次for循环)

#include<stdio.h>
int main() {
    
    
	int a[4][4] = {
    
    
		{
    
    1, 2, 3, 4},
		{
    
    5, 6, 7, 8},
		{
    
    9, 10, 11, 12},
		{
    
    13,14,15,16}
	};
	int *p;
	for(p=a[0]; p<a[0]+12; p++) {
    
    
		printf("%d ",*p);
	}
	return 0;
}

运行结果:

在这里插入图片描述

指向由m个元素组成的一维数组的指针变量

Ⅱ . 指向由m个元素组成的一维数组的指针变量

定义指向由m个元素组成的一维数组的指针变量的格式:

​ 类型 (*指针变量名) [m]

行指针变量 列数

如:

int a[5][7];
int (*p)[7];
p=a;

char b[ 10][80];
char (*r)[80];
r=b+5;

上面伪代码解析代码如下:

/*
int a[5][7];
int (*p)[7];
p=a;
 */

#include<stdio.h>
int main() {
    
    
	int a[4][4];  //定义一个二维数组
	int (*p)[4];  //定义一个一维数组的指针变量,也就是说该数组是指向某一行第四列的一维数组
	int *c;       //普通的指针变量
	p=a;          //将二维数组的首地址赋值给p,也就是说此时的p是指向的a的首行,且(*p)[4]等价于p[0][4]
	printf("%p\n%p\n",a,p);   //打印数组a的首地址和p的首地址
	printf("%d ",sizeof(a));  //打印数组a所占的空间,也就是二维数组a[4][4]所占的空间
	printf("%d ",sizeof(*p)); //打印数组p所占的空间,也就是一维数组p[0][4]所占的空间
	printf("%d ",sizeof(c));  //打印指针变量(地址变量)c所占的空间,
	printf("\n\n");
 	return 0;
}

打印结果如下:

在这里插入图片描述

/*
char b[ 10][80];
char (*r)[80];
r=b+5;
*/

#include<stdio.h>
#include<string.h>
int main() {
    
    
	char b[10][80];  //定义一个二维数组
	char (*r)[80];   //定义一个一维数组的指针变量,也就是说该数组是指向某一行第八十列的一维数组
	r=b+5;			//将b[0][0]向下移动五行再赋值给r,也就是将a[5][0]的地址赋值给r。
	printf("%p\n%p\n",r,b[5]); //打印地址
	printf("%d ",sizeof(*r));  //查看空间
	printf("%d ",sizeof(b[5]));//查看空间
	return 0;
}

打印结果如下:
在这里插入图片描述

例题1:

下列代码运行会打印什么结果,且写出另外一种一层for循环的打印方法,及写出p=a+1;的代码,以及会打印出什么?

#include<stdio.h>
int main() {
    
    
	int a[3][4]= {
    
    1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p)[4];
	p=a;
	int *q;
	for(int i=0; i<3; i++) {
    
    
		for(int j=0; j<4; j++) {
    
    
			printf("%d\t",p[i][j]);
		}
		printf("\n");
	}
	return 0;
}

答案及解析:

#include<stdio.h>
int main() {
    
    

	int a[3][4]= {
    
    1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p)[4];
	p=a;
	int *q;
	for(int i=0; i<3; i++) {
    
    
		for(int j=0; j<4; j++) {
    
    
			printf("%d\t",p[i][j]);
		}
		printf("\n");
	}
	printf("\n\n");
	
	//一层for循环打印二维数组 
	for(q=a[0];q<a[0]+12;q++){
    
    
		printf("%d  ",*q);
	}
	printf("\n\n");
	
	
	//打印p=a+1,回打印出什么 
	p=a+1;
	for(int i=0; i<2; i++) {
    
    
		for(int j=0; j<4; j++) {
    
    
			printf("%d\t",p[i][j]);
		}
		printf("\n");
	}
	return 0;
}

运行结果(结合上面的知识点自行分析):

在这里插入图片描述

例题2:

下列代码运行会打印什么结果?

#include<stdio.h>
main() {
    
    
	int a[2][3]= {
    
    {
    
    1,2,3},{
    
    4,5,0} },(*pa)[3];
	int i;
	pa=a;
	for(int i=0;i<3;i++){
    
    
		if(i<2){
    
    	
			pa[1][i]=pa[1][i]-1;
		}
		else {
    
    
			pa[1][i]=1;
		}
	}
	printf("%d\n",a[0][1]+a[1][1]+a[1][2]);
}

答案及解析:

在这里插入图片描述

四、指向字符串的指针变量

字符串常量:C语言对字符串常量是按首地址处理字符串常量

既然是指针就要遵守指针变量的原则,同时我们知道,字符串常量是一个常量,也就不能写在赋值号左边。

对于字符串 char *p= “abc”

①首地址

②"abc"地址常量

③p中存的是第一个字符的地址

例题:

下列代码哪些是正确的,那些是错误的,请指出,并说明理由。

char str[]="China";char *p="China";     ✔   因为字符串是地址常量,所以正确

str = "Chinese";     ❌    str也是地址常量,不能放在赋值号的左边    

p="Chinese";         ✔    p是地址变量,字符串是地址常量,也是"C"的地址

char *q ={
    
    "China"};  ❌    加大括号是初始化,此时就不是地址常量了,所以错误

char s[]={
    
    "China"};  ✔    初始化,大括号可加可不加。所以✔

char *p;*p="china";*p是内容值,但是字符串是地址值,所以❌

五、指向函数的指针变量

函数名与数组名一样,是起始地址,而且是一个地址常量。

定义函数指针变量的方式:

类型名 (*指针变量名)();

例题:

下列代码运行结果是?

#include<stdio.h>
int min(int a,int b) {
    
    
	return a>b?b:a;
}
int max(int a,int b) {
    
    
	return a>b?a:b;
}
int main() {
    
    
	int x= 6,y=10;
	int (*p)(int a,int b);  //定义指向函数的指针变量
	p=max; 				   //将max函数的地址赋值给指向函数的指针变量
	printf("%d\n",max(x,y));
	printf("%d\n",p(x,y));
	p=min;				   //将min函数的地址赋值给指向函数的指针变量
	printf("%d\n",min(x,y));
	printf( "%d\n",p(x,y)); 
}

运行结果:
在这里插入图片描述

注:

(1)在定义指向函数的指针变量时,要注意有两个小括号必须要有,不需要定义形参。但是若是devc++等一些编译器,可能必须定义形参。

(2)单独的函数名代表该函数的首地址(函数的入口地址)。

(3)函数的指针变量只能指向函数的入口处(函数的首地址),不能指向函数中的某条指令。( 另对指向函数的指针变量加1是没有意义的)。

(4)给指向函数的指针变量赋值时,只写函数名即可,不必写参数。

六、返回指针的函数

返回指针的函数的定义方式为:

类型名 *函数名(形参列表){

}

例题:

#include<stdio.h>
int *fun(int *x,int *y) {
    
    
	if(*x<*y)
		return x;
	else
		return y;
}
int main() {
    
    
	int a=7,b=8,*p,*q,*r;
	p=&a;
	q=&b;
	r=fun(p,q);
	printf("%d,%d,%d\n",*p,*q,*r);
	return 0; 
}



7,8,7

--------------------------------
Process exited after 0.1331 seconds with return value 0
请按任意键继续. . .

七、指针数组和指向指针的指针变量

指针数组

若一个数组的所有元素均为指针类型(地址),则称为指针数组。

格式:

类型名 *数组名[常量表达式];

int *a[5];

注:

(1)要注意它与定义指向由m个元素组成的一维数组的指针变量之间的区别

类型名 *数组名[常量表达式]; 实质上是一个一维数组,数组里面存放的是地址常量

类型名 (*指针名)[常量表达式m];实质上是二维数组,是一个指向一维数组的指针变量

( 2 )它的每个元素都是一个指针类型(地址),即它的每个元素都相当于一个指针变量。

例题:

#include<stdio.h>
int main() {
    
    
	char ch[3][4]= {
    
    "123","456","78"},*p[3];
	int i;
	for(i=0; i<3; i++)
		p[i]=ch[i];
	for(i=0; i<3; i++){
    
    
		printf("p = %p \n" ,p[i]);
		printf("ch =%p\n" ,&ch[i][0]);
		printf("p = %s \n" ,p[i]);
		printf("ch =%s \n" ,ch[i]);
		printf("\n");
	}	
	return 0; 
}

运行结果及解析:

在这里插入图片描述

指向指针的指针变量

用来存放指针变量地址的指针变量称为指向指针的指针变量。

定义格式:

类型名 **指针变量名;

int a=3;

int *p=&a;

int **k=&p;

则:*k得到变量p (变量a的地址)。**k得到变量a的值(a的数据3)。

八、空指针

指针变量可以有空值,即指针变量不指向任何变量,不指问任何有用的存储单元。

在系统中已将NULL定义为0,即NULL的值为0。

int a,b, C, *p=NULL;

此时p的值为空指针,即p不指向任何有用的存储单元。尽管NU L L的值为0,但我们不能认为p指向了地址为0的存储单元。

注:

(1)当一个指针变量的值为空指针时,我们不能引用它所指向的存储单元。

(2)若某指针(地址)的基类型为void型,则有引用时应进行相应的强制类型置换。

九、总结

指针变量需要注意的几点

  1. int *a;中,变量名是a,而不是*a,因为变量名遵顼变量名命名规则,即字母数字下划线,且数字不能开头,所以指针变量的变量名不是*aint *是一种类型名,叫指针类型。

  2. int *a;中,a占的8个字节,当然*a是占4个字节,因为*a是int类型所以是4个字节,char *b中的b也是占8个字节,而*b占1个字节。所以无论是哪种基类型指针变量都是占8个字节,而*指针变量占该类型的字节。(值得注意的是,不同的操作系统出现的结果不尽相同,但有一点相同,就是指针变量占的字节数相同,与基类型无关)。

  3. 在指针中*&,互为逆运算,&[]互逆,也就是说可以相互抵消,比如

    int a[3]; 
    int b;
    int *a;
    int *p;
    a=&b;
    p=&a[0]; <=> p=a;
    p=&a[3]; <=> 
    p=a+3;
    
    &* 互逆
    
    *[] 等价
    
    &[] 互逆
    
  4. 针对指针有五句口诀

    ① 地址变量得地址,得谁地址指向谁

    ② 有*为内容值,不是读就是写,放在赋值号左边为写,其他都是读

    ③ 没有*为地址,地址的赋值意味着改指向

    ④ 指针变量定义且初始化后才能用

    ⑤ 地址变量存地址,内容变量存内容

  5. 重要等价

    int fun(int a[10]) <=> int fun(int *a) <=> int fun(int a[])

  6. 在指向字符串的指针变量中,对于

    char *p="qweqweqweqw";
    char a[]="qweqwewqeq";
    char *q;
    q=a; a是数组的地址,是一个地址常量,同时也是数组的首地址,即'q'的地址
    p="asdasd"; 虽说p是个地址变量,但是,由于对于字符串来说,"asdasd"也就相当于一个地址常量,也就是‘a’的地址。
    
  7. 区分

    (1)要注意它与定义指向由m个元素组成的一维数组的指针变量之间的区别

    类型名 *数组名[常量表达式]; 实质上是一个一维数组,数组里面存放的是地址常量

    类型名 (*指针名)[常量表达式m];实质上是二维数组,是一个指向一维数组的指针变量

  8. 空指针

    char *q =NULL;

  9. 针对指针有五句口诀

    ① 地址变量得地址,得谁地址指向谁

    ② 有*为内容值,不是读就是写,放在赋值号左边为写,其他都是读

    ③ 没有*为地址,地址的赋值意味着改指向

    ④ 指针变量定义且初始化后才能用

    ⑤ 地址变量存地址,内容变量存内容

  10. 重要等价

    int fun(int a[10]) <=> int fun(int *a) <=> int fun(int a[])

  11. 在指向字符串的指针变量中,对于

    char *p="qweqweqweqw";
    char a[]="qweqwewqeq";
    char *q;
    q=a; a是数组的地址,是一个地址常量,同时也是数组的首地址,即'q'的地址
    p="asdasd"; 虽说p是个地址变量,但是,由于对于字符串来说,"asdasd"也就相当于一个地址常量,也就是‘a’的地址。
    
  12. 区分

    (1)要注意它与定义指向由m个元素组成的一维数组的指针变量之间的区别

    类型名 *数组名[常量表达式]; 实质上是一个一维数组,数组里面存放的是地址常量

    类型名 (*指针名)[常量表达式m];实质上是二维数组,是一个指向一维数组的指针变量

猜你喜欢

转载自blog.csdn.net/LOL_toulan/article/details/109356397
今日推荐