指针

概念理解

int a=1;
int *pointer_a=&a;	//pointer_a=&a;

(1)地址和指针的定义

内存区中每一个字节都有一个“编号”。
地址包括一个编号信息和一个数据类型信息。
C语言中地址=位置信息(纯地址)+所指向数据的数据类型=指针=&a
指针就是一个变量的地址,只能用&获取。

(2)指针变量定义

指针是一个地址,指向存储变量的内存空间所在,带数据类型。
指针变量是存放该指针(地址)的变量。

(3)指针变量定义

a、一个完整的地址&a,包括一个纯地址信息和一个数据类型。因此在定义指针变量时,必须指明基类型。
b、*符号表示这是一个指针变量。*后面的pointer_a是指针变量名。
c、pointer_a存放了整型变量a的地址&a(数据类型+位置信息)。

(4)运算符

&:取地址运算符,&a是变量a的地址;
pointer_a:是指针变量,里面存储了一个带数据类型的地址,可以说pointer_a指向一个地址。
*:指针运算符(间接访问符号),*pointer_a代表指针变量pointer_a指向地址存储的数据。

(5)变量理解

a、编译时,为整型变量a分配一个内存空间,这个空间有一个地址。
b、编译后,c语言变为机器语言,此时变量名a已经没有了,变成了一个带数据类型的地址 信息。变量名a只是在编写代码时给我们看的。
c、在运行程序时,我们对变量名a的任何操作(包括赋值、读数据),都会被自动转化为对这个地址中存储数据的操作。
d、&a取到的是这个地址,而不是地址中存放的数据。此时就可以理解为什么scanf()的输入是&a(或者指针变量)而不是a。对于scanf()来说,我们希望从键盘读取到的数据,被存放到某个地址中去,因此函数输入参数是一个地址变量,如果直接写a,我们输入的不是地址,而是这个地址中存储的数据。函数中处理的是一个单向值传递的局部变量。如果直接输入值,不会对实参发生影响。因此要将地址作为值传递进函数中,才能对实参进行实际修改。&a等价与pointer_a,因此也可以:

scanf(%d”, pointer_a)==scanf(%d”,&a)

(6)图解

在这里插入图片描述
编译后变量名a已经不存在了,他变成了一个地址信息(整型变量类型+地址信息2000)。此时对a的全部操作,变成了对这个地址中存储数据1的操作。
编译后指针变量pointer_a变成了地址信息(指针变量类型+地址信息2012),其中存放了整型变量a的地址&a。我们可以说指针变量pointer_a指向一个整型。此时可以用指针运算符*来间接访问地址&a存储的数据。

引用指针变量

(1)直接访问和间接访问

对变量值的访问都是通过地址进行的。

a、直接访问:

直接使用整型变量a的变量名来引用。系统直接根据地址找到值。

a=1;
printf(%d”,a);

b、间接访问:

通过指针变量存放的地址信息,来访问这个地址中的值。
a的地址&a赋值给了指针变量pointer_a,即pointer_a指向的地址中存放了&a。
此时可以使用*pointer_a来使用&a存储的数据。这是间接访问。

(2)引用指针变量

a、赋值:

*pointer_a=1;		//将整数1赋值给pointer_a当前指向的地址,即a。

b、引用指针变量的值

printf(%o”,pointer_a)//以八进制形式输出一个地址&a。

(3)指针变量作为函数的值

用函数对变量的值进行写操作(包括赋值、交换、输入等)都需要通过指针来实现。这是因为函数实参到形参的传递是一个单向的值传递。如果直接对值进行处理,函数结束后函数中的局部变量就释放了,不会实际影响到主调函数中的值。因此需要将指针即地址传入函数中,才能实际对这个地址中存储的值进行写操作。
需要注意的是,指针变量也是一个变量。如果在被调函数中改变指针变量的值,而不是指针变量所指向数据的值,也是不可以实现的。因此改变指针变量的值,在函数调用完成后,不会对主调函数中的指针变量产生影响。

程序举例:

#include<stdio.h>
int main(){
	int a=1;
	int b=2;
	int *pointer_a=&a;
	int *pointer_b=&b;
	printf("交换前:a=%d,b=%d\n",a,b);
	void swap(int *,int *);
	swap(pointer_a,pointer_b);
	printf("使用swap()交换后:a=%d,b=%d\n",a,b);
	void swap1(int *,int *);
	//初始化
	a=1,b=2;
	pointer_a=&a;
	pointer_b=&b;
	swap1(pointer_a,pointer_b);
	printf("使用swap1()交换后:a=%d,b=%d\n",a,b);
	void swap2(int *,int *);
	//初始化
	a=1,b=2;
	pointer_a=&a;
	pointer_b=&b;
	swap2(pointer_a,pointer_b);
	printf("使用swap2()交换后:a=%d,b=%d",a,b);
	return 0;
}
	/*
	这种方式是对的。交换的是指针变量所指向的地址中值。函数中的变量是指针变量,但是我们操作的是这个指针变量所指向的值,而不是这个指针变量。
	*/
void swap(int *pointer_a,int *pointer_b){
	int temp;
	temp=*pointer_a;
	*pointer_a=*pointer_b;
	*pointer_b=temp;
}
	/*
	这个函数的问题在于,将指针变量pointer_a赋值给了指针变量pointer_temp。之后指针pointer_temp指向的地址也变成了a。*pointer_a=*pointer_b改变了a的值,所以*pointer_temp的值也变了,起不到temp的作用。
	*/
void swap1(int *pointer_a,int *pointer_b){
	int *pointer_temp;
	pointer_temp=pointer_a;
	*pointer_a=*pointer_b;
	*pointer_b=*pointer_temp;
}
	/*
	改变指针变量的值,无法影响到主调函数中指针变量的值。因此没有效果。
	*/
void swap2(int *pointer_a,int *pointer_b){
	int *temp;
	temp=pointer_a;
	pointer_a=pointer_b;
	pointer_b=temp;
}	

输出结果:

交换前:a=1,b=2;
使用swap()交换后:a=2,b=1
使用swap()1交换后:a=2,b=2
使用swap()2交换后:a=1,b=2

指针与数组

(1)概念

int a[10]={0,1,2,3,4,5,6,7,8,9};
int *pointer_a;
pointer_a=a;     ==    pointer_a=&a[0];	//将数组首元素的地址赋值给指针变量 

数组元素的指针就是数组元素的地址。
使用指针法比使用下标法占内存少,运行速度快。
数组名不代表整个数组,而是指向数组首元素的地址,&a[0]。实际上并不存在这么一个指针变量的实际存储单元,他只是一种地址的计算方式,因此这个指针变量是不能被重新赋值的。
&a和a单纯比较数值是一样的。但是他们的含义不同。a是这个一维数组第一个元素a[0]的地址,而&a则代表这整个数组a[10]的地址。a+1跳过个一个基类型的字节数,而a+1跳过整个数组元素个数的字节数

(2)引用数组元素

a、下标法

最慢的方法,每次都需要计算地址*(a+i)。
a[0],a[1]……

b、地址法

数组名a实际上就是一个指针型常量。

int a[10];
*(a+1);		==    a[1];

c、指针法

int *pointer_a=a;
*pointer          ==    a[0]
*(pointer_a++)    ==    a[1]

a++是不行的,因为数组名是一个指针型常量,但是pointer_a++是可以的。

(3)地址+i的理解

a、因为地址元素是包含了数据类型的,数组也包含了数据类型,pointer+1的含义是加上一个数组元素类型所占用的字节数,这个字节数由指针变量的基类型决定。
pointer+i == pointer+i*d d是基类型的字节数
b、a[0]中的[]实际上是变址符,a[i]其实就是按照a+i计算地址。
c、如果int *p1=&a[0],int *p2=&a[5],那么p2-p1=5。
地址相减有意义,但是相加没有意义

(4)运算符

*和++的优先级相同,因此自右往左结合

*p++ == *(p++) 		//先*p,再p++
*(++p)			//先p++,再*p

(5)比较大小

指针是可以相互比较大小的。

int a[10];
int *pointer_a=a;
for(;pointer_a<a+10;pointer_a++)
	printf(%d”,*pointer_a);

此时比较的是一个地址。

数组作为函数参数

(1)函数改变原数组值的原理

void swap(int a[])
int a[2];
swap(a);			//数组名作为参数,函数调用完后,主调函数中数组也发生变化。

因为数组名是一个指针。
两者等价:

void function(int arr[])  ==  void function(int *a)

在该函数被调用时,处理的是指针变量所指向的值。因此可以改变原数组值

void function(int a[]){
		a[0]++;	==(*a)++	
	}

(2)函数中数组可以重新赋值的原因

数组名是一个常量指针,是不能改变和赋值的。
形参为数组时,传入实参数组时,传入的实际上是一个指针。被调函数处理的是一个指针变量,而不是一个数组。

int a[3]={0,1,2};
//a++;			//这是错误的~~ 
void swap(int a[]){
	a++		//这是可以的
	//……
}

(3)归纳总结:

如果希望通过调用函数改变一个数组的值:
a、函数形参为数组,实参为数组
b、函数形参为数组,实参为指针
c、函数形参为指针,实参为数组
d、函数形参为指针,实参为指针

指针与二维数组

int a[3][3]={0,1,2,3,4,5,6,7,8};

(1)图解

在这里插入图片描述

(2)概念理解:

	二维数组名a指向行的,a+i实际跳过整行的全部字节数。
	一维数组名a[i]指向列的,a[i]+j是跳过了一个基本类型的字节数。
a和a[i]都是常数型指针变量,都是存储的地址信息,但是实际上并不存在a[i]这样一个实际的数据存储单元,它们只是一种地址的计算方式,单纯的比较它们的值,都是没有意义的,它们指向的值才是实际存储的数据。	
	在指向行的指针a前面加上一个*,就变成了指向列的指针。即:*(a+0)等价于a[0],仍然指向第一行第一列的元素;
	
	在指向列的指针a[0]前面加上一个&,就变成了指向行的指针。即&a[0]等价于&*a等价于a,仍然指向第一行第一列的元素。
	
	在指向行的指针a前面加上一个&,它的数值不便。但是他的含义已经变了。a代表这个二维数组首行的地址,&a[0];而&a则代表整个二维数组int a[i][j]的首地址。&a+1跳过一整个数组的字节数。不存在&(a+i).
	
	在指向列的指针a[0]前面加一个*,则变成了实际存储的元素。*a[0]=*(*a)=0。
	
	虽然它们的值没有变,但是它们的含义已经变了。因为它们加1的值变化了。
	综上所述,不必去纠结于a,&a,*a等等的值(它们的纯数值都是相等的),只要明白它们指向的元素即可,理解他们之间的等价性。对于a和a[i]来说,重要的是理解它们+1之后的变化。

(3)单纯比较值

行             列                列            行
a+i       = *(a+i)           = a[i]     = &a[i]    

列加j,跳过j个基类型:

*(a+i)+j=a[i]+j=&a[i][j]

行加j,跳过j行元素:

&a[i]+j=a+i+j

(4)指向二维数组的指针变量(基类型是一位数组)

a、用指针指向列

int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int *p=a;   		//int *p=a[0];int *p=&a[0][0];都是一样的效果。
for(;p<a[0]+12;p++){	//逐个元素指过去
	printf(%d”,*p);	
}

p是一个指向列的指针,因此直接p就等与a[0].

b、用指针指向行

基类型 (*指针名)[m];
int (*p)[m];

这样定义的意义是:定义了一个指向一维整型数组的指针变量。p的类型是int()[4]型,p的基类型是一维数组。
此时p指向这个指针数组的首元素;p+i跳过i个数组,即i
m*4个字节。
用行来理解,即p+i跳过了i行(每行m列)。
如果令p=a,则p变成了二维数组的第一行,是一个指向行的指针。

int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int (*p)[4],i,j;
p=a;				//此时p指向这个二维数组的第0行
for(;p<a[0]+12;p++){		//输出为每行首个元素
	printf(%d”,**p);	
}
printf(“第2行第3列元素为%d”,*(*(p+i)+j));

需要注意的是,p变成了一个指向行的指针。

*p!=*a[0],而是将p转变为列指针,即*p=&a[0]=*a
**p=*&a[0]=**a=a[0][0]
(p+i)=a[i]; 
*(p+i)+j=a[i]+j

(5)总结

对于二维数组

int stu[m][n];
stu是行指针;
stu+i,stu[i]是第i行;
*(stu+i)+j,stu[i]+j是第i行第j列;
&stu[0]是行指针;
int (*p)[n]是行指针,和stu效果一样;*p列指针,*(*(p+i)+j)=stu[i][j]
int *p=*stu是列指针,和stu[0]效果一样。

指针与字符数组

(1)定义指针指向一个字符数组

char string[]=”I love u”;
char *pointer_string=string;

等价于

char *string=”I love u”;

系统会将字符串按字符数组处理,可以不需要数组名,直接用指针来访问。这个指针指向这个字符串的第一个字符。

(2)输入字符串

char *string,str[10];
string=str;			
scanf(%s”,string);

不能直接对string使用scanf,这是因为编译系统虽然已经给指针变量string分配了内存,但是分配内存的多少是不可预知的。因此需要string=str.

(3)输出字符串

char string1[]=”I love u”;
char *pointer_string1=string;
char *string2=”I love u”;
printf(%s”,string1);
printf(%s”,pointer_string1);
printf(%s”,string2);

这三种方法都可以输出字符串,如果加了*,则输出的是一个字符的值。因为无论是数组名还是指针,都指向第一个字符变量。

(4)两者区别

a、赋值差别,数组不能重新赋值,指针可以重新赋值。
b、存储区别,编译时为字符串数组分配若干个存储单元;但只为数组分配一个存储单元(一般指针变量是4个字节)
c、变值区别,字符串数组中的任意一个字符变量可变;而指针指向的是一个字符串常量的首个字符,这个字符串常量中的字符不能变化。

指向函数的指针

返回类型 (*指针变量名)(形参1,形参2……);

编译系统会将函数的源代码转换为可执行代码,并分配一段存储空间,这个存储空间的首地址就是这个函数的指针。函数名就是函数的指针。每次调用函数都会以该地址作为入口执行代码。

(1)用指向函数的指针调用函数

int main(){
int max(int,int);
int (*pointer_max)(int,int);
	pointer_max=max;
	int a=10;
	int b=6;
	int max;
	max=(*pointer_max)(a,b);	//等价于直接调用函数
	//……
}
int max(int a,int b){
	//……
}
函数声明:返回类型 函数名(形参1,形参2……);
指针定义:返回类型 (*指针变量名)(形参1,形参2……);
指针变量名=函数名;

使用指针调用函数,即以这个地址作为入口调用函数。这个指针只能指向规定返回值,规定形参类型的函数。
在给这个指针变量赋值时,只需要给出函数名,不需要给出参数。因为函数名就是这个函数的起始地址,类似于数组名。

(2)用指向函数的指针作为函数参数

指向函数的指针的用途,就是用作一个函数的实参。
在某个函数中,调用另一个函数,但是调用的函数在不同情况下不同,此时就可以用指针实现。
这样就可以编写一个通用接口,来执行不同的专用功能。

int main(){
		int fun(int,int,int(*p)(int,int));
		int max(int,int);
		int min(int,int);
		int add(int,int);
		int x=34,int y=-21,n;
		scanf(%d”,n);
		if(n==1) fun(x,y,max);		//将函数名作为参数输入,也就是函数的执行入口
		else if(n==2) fun(x,y,min);
		else fun(x,y,add);
		return 0;

}
/*fun函数是一个统一处理函数,根据不同的情况,输入不同的函数来对值进行处理。*/
int fun(int a,int b,int(*p)(int,int)){
	//……
}	
int max(int a,int b){}
int min(int a,int b){}
int add(int a,int b){}

函数返回指针值

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

首先这是一个函数,其次它的返回值是一个指向类型名的指针。
注意和指向函数的指针的区别:

返回类型 (*指针名)(参数列表);

有了括号,使他首先是一个指针,其次才是指向一个函数。

指针数组

类型名 *数组名[数组长度]			//实质是一个数组

首先这是一个数组,其中存放的都是指针变量。

类型名 (*指针变量名)[长度]			//实质是一个指针变量
返回类型 *函数名(参数列表)			//实质是一个函数

注意三者的区别。

(1)指针数组用来指向字符串数组

可以用来保存多个字符串。指针数组中的元素指向这些字符串。

char *name[]={“aaa”,”bbb”,”ccc”,”ddd”};

name是一个指向这个数组第一字符串的指针。因此*name=”aaa”。
等价于

char *name1=”aaa”;				
char *name2=”bbb”;		
char *name3=”ccc”;
char *name4=”ddd”;
char *name[4]={name1,name2,name3,name4};
name[1]=name1=&”aaa”=*(name+0);   
**(name+0)='a';

(2)指向指针的指针

char *name[]={“aaa”,”bbb”,”ccc”,”ddd”};

这里的name是一个数组名,实际上是一个指针。这个指针指向一个数组的首位元素的地址。这个数组的首位元素是一个字符串,实际上也是一个指针变量。因此name就是一个指向指针的指针。

char **p=name;		//说明p是一个指向指针的指向

等价于

char **p;
p=name;
*(p+i)=*(name+i)=name[i]=第i个指针=第i个字符串;

(3)指针数组作为main函数的形参

int main(int argc,char *argv[]){}

argc是参数个数,argv则是一个指针数组,指向一系列参数字符号。
注意,这里的argc包括文件名。

./a.out aaa bbb ccc        //argc=4

总结

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值