《C语言程序设计》指针篇<一>

指针

  • 指针是C语言的精华,同时也是其中的难点和重点,我在近日对这一部分内容进行了重新的研读,把其中的一些例子自己重新编写和理解了一遍。此篇博客的内容即是我自己对此书例子的一些理解和总结。

一.大问题:指针是什么?

我的理解:
变量的本质即内存,指针即访问变量的地址。利用指针来 <间接访问> 变量。
定义一个指针,p是指针变量名,系统自动为其分配内存,存放的是其指向的变量(内存)的地址。
例如:

1> int a=4;
2> int *p;
3> p=&a;

上述程序定义一个变量a,系统自动为其分配内存,那么这个内存的名称就是a,其存放的值是4。
再定义一个整形指针p,系统也为其分配内存(根据指针的类型分配不同大小的内存单元,例如此处指针为int类型,计算机为其分配4个字节),该内存里存放的是指向名称为a的内存的地址

  • 变量的本质是什么?-变量名只是一个代号,变量的本质就是内存。
  • 指针保存的是什么?-指针保存的是内存地址。

这样来看,会不会对指针理解更加清楚一点呢?

草图:
待解决

二.指针变量

  • 使用指针变量
  • 定义指针变量
  • 引用指针变量
  • 指针变量作为函数参数

1.int *p1,*p2; p1-p2是什么?

#include<stdio.h>
int main()
{
	int a[4]={3,4};
	
	int *p1=&a[0],*p2=&a[1];
	printf("%d %d\n",p1,p2);
	printf("%d ",p2-p1);/*p2-p1=地址之差/数组元素的长度
	就是p1所指向的元素与p2所指向的元素差之间几个元素*/
	return 0;
}

我的理解:
p1指向数组a的首元素a[0],p2指向数组a的a[1],p1-p2代表的是a[0]与a[1]之间相隔几个元素。
输出结果:

计算机自动处理为p2-p1=地址之差/数组元素的长度.而不是简单的地址之差=4.

2.输入三个整数按从大到小的顺序输出.(指针变量作为函数参数)

  • 错误解法:
#include<stdio.h>
void swap(int *p1,int *p2,int *p3)
{
	int *p;
	if(*p1<*p2)
	{
		p=p1;
		p1=p2;
		p2=p;
	}
	if(*p1<*p3)
	{
		p=p1;
		p1=p3;
		p3=p;
	}
	if(*p2<*p3)
	{
		p=p2;
		p2=p3;
		p3=p;
	}
}
int main()
{
	int a,b,c;
	scanf("%d %d %d",&a,&b,&c);
	printf("%d %d %d\n",a,b,c);
	
	int *point_1=&a,*point_2=&b,*point_3=&c;
	
	swap(point_1,point_2,point_3);
	printf("%d %d %d\n",*point_1,*point_2,*point_3);
	return 0;
}

输入样例:7 8 9
输出结果:

  • 正确解法:
/*输入三个整数按从大到小的顺序输出*/
#include<stdio.h>
void swap(int *p1,int *p2,int *p3)
{
	int p;
	if(*p1<*p2)
	{
		p=*p1;
		*p1=*p2;
		*p2=p;
	}
	if(*p1<*p3)
	{
		p=*p1;
		*p1=*p3;
		*p3=p;
	}
	if(*p2<*p3)
	{
		p=*p2;
		*p2=*p3;
		*p3=p;
	}
}
int main()
{
	int a,b,c;
	scanf("%d %d %d",&a,&b,&c);
	printf("%d %d %d\n",a,b,c);
	
	int *point_1=&a,*point_2=&b,*point_3=&c;
	
	swap(point_1,point_2,point_3);
	printf("%d %d %d\n",a,b,c);
	return 0;
}

输入样例:7 8 9
输出结果:

我的理解:

  • 函数值是单向传递。swap函数中的指针变量p1,p2,p3交换地址,这样的变化并不会传递回原函数使原函数的指针变量的值发生改变。原函数中指针变量point_1/2/3所存储的内存地址并没有改变。
  • 错误样例的错误在于:
    原函数传递三个变量的地址到swap函数,企图通过swap交换形参p1,p2,p3这些指针变量所存储的地址,来交换实参point_1/2/3地址。然而忽略了函数调用时形参和实参是采用单向传递的值传递方式。在函数中改变了名称为p1,p2,p3的内存单元所存储的内存地址,但是主函数中名称为point_1/2/3的内存单元所存储的内存地址并没有改变。
  • 这样的一个细节问题导致了程序的错误。时刻注重细节是成为一名程序员的基本素养。
  • 使用指针来处理的优点:能够改变多个值。而普通的函数只能有一个返回值。

3.输入两个数 按从大到小的顺序输出

#include<stdio.h>
void avoid(int *p1,int *p2)
/*传递的是地址,p1,p2储存的是a,b的地址*/
{
	int p;
	if(*p1<*p2)
	{
		p=*p1;
		*p1=*p2;
		*p2=p;/*实质就是交换a,b*/
	}
}
int main()
{
	int *p1,*p2;
	int a,b;
	scanf("%d %d",&a,&b);
	p1=&a;
	p2=&b;
	if(a<b)
	avoid(p1,p2);
	printf("%d %d",a,b);
	
	return 0;
}

我的理解:
void avoid(int *p1,int *p2)函数接收内存名为a和内存名为b的内存的地址。通过交换指针(函数接收的地址)所指向的内存所存储的值来达到排序的目的。

输入样例:3 4
输出结果:

三.通过指针引用数组

  • 数组元素的指针

  • 指针的运算

  • 通过指针引用数组元素

  • 数组名作为函数参数

  • 通过指针引用多维数组

探究a+i(p+i)与&a[i]的关系 例一

#include<stdio.h>
int main()
{
	int a[10]={1,2,3,4,5,6,7,8,9,0};
	int *p=a;
	int i;
	for(i=0;i<=9;i++)
	{
		printf("%d ",*(p+i));/* *(p+i)与a[i]等价 */
	}
	return 0;
} 

输出结果:

我的理解:

  • 数组名a即是&a[0](该数组首元素的地址),将a[0]的地址赋值给指针变量p,并利用指针输出该数组的各元素。
  • for循环中的printf("%d ",*(p+i));语句里的*(p+i)a[i]无条件等价。
  • 原因:前面的语句,p的值是a数组首元素的地址,而对于(p+i),计算机系统处理成&a[0]+i*数组元素的长度,也就是说(p+i)是数组元素a[i]的地址(即&a[i])。那么*(p+i)就相当于是a[i]

基于这一原理,我们来看下一个例子

探究a+i(p+i)与&a[i]的关系 例二

#include<stdio.h>
int main()
{
	int a[15];
	int i;
	int *p;
	for(i=0;i<10;i++)
        scanf("%d",&a[i]);

	for(p=a;p<a+10/*地址小于&a[10]*/;p++)
	/*a+10等价于&a[10]*/
	{
		printf("%d ",*p);
	}
	return 0;
} 

输入样例:1 2 3 4 5 6 7 8 9 0
输出结果:

我的理解:

  • 在理解完例一之后,来看这个例子:a数组有十个元素,输入这十个元素,然后要求利用指针输出这十个元素。
  • 与例一不同的是,这里的for循环有些不一样: for(p=a;p<a+10;p++); 首先进行赋值操作,把a数组首元素的地址赋值给p,然后输出*p的值(p指向该数组元素),再执行p++(该操作的意义是:p指向该数组的下一元素),循环结束的条件是p<a+10,即当p指向a[9]时不再进行自增操作。
  • 本例利用p指针的自增来按序输出不同的数组元素,++自增运算符是C语言的一大特色。
    有关于p=a下文会有提及。

探究a+i(p+i)与&a[i]的关系 例三

#include<stdio.h>
int main()
{
	int a[15];
	int *p=a;
	int i;
	for(i=1;i<=5;i++)
	scanf("%d",p+i);/*输入地址p+i相当于&a[i]*/
	
    for(i=1;i<=5;i++)
    printf("%d ",*(p+i));
    
    return 0;
}
  • a数组里有五个元素,输入这五个元素,并利用指针按序输出。
    有了上述两例的铺垫,这个例子现在理解起来是不是比较容易呢?

有关函数参数的应用 例四

题目:有一个含有n个元素的数组,第一行输入n,第二行输入这个数组的元素,编写一个程序使该数组的元素按相反顺序存放并输出。

  • 代码1:
#include<stdio.h>
void inv(int a[],int n)/* void inv(int *p,int n) */
{
	int t,i,j;
	for(i=1,j=n;i<=j;i++,j--)
	{
		t=a[i];
		a[i]=a[j];
		a[j]=t;
	}
}
int main()
{
	int n,a[105];
	int i,j;
	int *p=a;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	scanf("%d",p+i);
	
	inv(a,n);
	
	for(i=1;i<=n;i++)
	printf("%d ",a[i]);
	
	return 0;
}

我的理解(代码1):形参和实参都用数组名

  • 首先,在主函数里定义指针p并将数组a首元素的地址(数组名 int *p=a)赋值给它,接着这个例子特别的地方在于:将数组名作为函数实参传递给inv函数。通过前面的例子我们可以知道:数组名相当于是数组首元素的地址,也就是说这里的函数实参相当于是数组首元素的地址。
  • 其次,在我们定义的函数inv中,void inv(int a[],int n); 这里我们的函数形参也用了数组名,它与 void inv(int *p,int n);等价,因为这里的形参数组名相当于指针变量,用来接收传递自主函数的地址。我比较喜欢这样的做法,这样的好处一是易于我们初学者理解,二是不像普通的int,float之类的自定义函数只能有一个返回的return值,它能够对整个数组元素进行操作。这里我个人并不是很准确的把它称为:“通过指针,inv函数接收了一个数组”。
  • 除了上述的有关于函数实参形参的问题,这段程序还有一个大家刚刚接触过的一个注意点:scanf("%d",p+i);中的 p+i
  • 代码2:
#include<stdio.h>
int main()
{
	void inv(int *p,int n);
	int n,a[105],i,j;
	int *p=a;
	scanf("%d",&n);
	for(i=0;i<n;i++)
	scanf("%d",p+i);
	
	inv(p,n);
	
        /* p=a */
        
        for(p=a;p<a+n;p++) /* p<=p+n||p<=a+n? Error */
	printf("%d ",*p);
	
	
	return 0;
}

void inv(int *p,int n)
{
	int *i,*j,t=0;
	
	for(i=p,j=p+n-1;i<=j;i++,j--)
	{
		t=*i;
		*i=*j;
		*j=t;
	}
}

我的理解(代码2):形参和实参都用指针变量

  • 与代码1有所不同的是,代码2所定义的inv函数实参和形参都使用了指针来传递地址(数组首元素的地址),这也验证了我的理解(代码1)的说法:函数形参用数组名与函数形参使用指针变量效果和目的是一样的,都是接受来自主函数的地址。不过代码2不足的地方也是在这个地方,由于在函数inv里面没有声明a数组(或者说,没有把a数组“传递过来”),无法像代码1一样直接对a数组的元素进行操作,只能通过函数inv定义的指针p(其值为数组a的首元素地址)来进行操作。

  • 代码1和代码2提供了两种情况,如果有一个实参数组,想在函数中改变此数组中元素的值,实参和形参对应的情况如下:
    (1)形参和实参均使用数组名。如代码1。
    (2)形参和实参均使用指针变量。如代码2。
    (3)形参使用数组名,实参使用指针变量。
    (4)形参使用指针变量,实参使用数组名。

  • 不管是哪一种情况,函数实参和形参传递的都是数组a首元素的地址,在本篇文章的开头我有提到,指针与内存是息息相关的。此时,我们注意到代码1这里的形参void inv(int a[],int n);用到了数组名a,但是由于数组名a相当于指针变量,计算机系统并没有真正给它分配一个数组的空间。可以这样理解:

inv函数在调用期间,形参数组和实参数组共同使用 同一段内存 ,那么对于代码1,在函数中的操作就是 直接 对主函数中数组a的各个元素的操作。

  • 代码3:
#include<stdio.h>
void swap(int *p1,int *p2)
{
	int t;/*为何此处不能用*t?*/
	t=*p1;
	*p1=*p2;
	*p2=t;
}
int main()
{
	int a[105],t,n,i,j;
	int *p=a;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	scanf("%d",p+i);
	for(i=1,j=n;i<=j;i++,j--)
	{
		swap(&a[i],&a[j]);
	}
	
	for(i=1;i<=n;i++)
	printf("%d ",a[i]);
	
	return 0;
}

我的理解(代码3):有点不同

  • 关于代码3,这是我拿到题目最初的思路和做法,其实如果要快速解题的话,大可不必利用指针这么麻烦,在主函数里面利用for循环即可快速解题。
  • 但是我为什么要贴出这一段代码呢? 原因在于这段代码的注释,这个地方也是很令我困扰。
  • 理论上,定义一个整形指针p,p指向的是一个未知的整形内存,也可以起到上面代码swap函数中中间变量t的作用:
void swap(int *p1,int *p2)
{
	int *p;
	*p=*p1;
	*p1=*p2;
	*p2=*p;
}
  • 输入样例:4 1 2 3 4
  • 输出结果:
  • 系统报错。这让我感到意外,按照上述内容来看,我的想法应该是正确的。但是我忽略了这样做的危险性。
  • 上面我定义的指针准确的名称叫做野指针,其缺省值(未修改前的初始值)是随机的,如果刚刚开始定义的指针没有明确的初始化或者被设置成null指针,它的指向可能是不合法的。如果定义一个野指针,基于其未知的危险性,计算机系统会报错。
  • 正确样例:
  • 代码3(修改):
#include<stdio.h>
void swap(int *p1,int *p2)
{
	int a=4;
        int *t=&a;
        *t=*p1;
	*p1=*p2;
	*p2=*t;
}
int main()
{
	int a[105],t,n,i,j;
	int *p=a;
	scanf("%d",&n);
	for(i=1;i<=n;i++)
	scanf("%d",p+i);
	for(i=1,j=n;i<=j;i++,j--)
	{
		swap(&a[i],&a[j]);
	}
	
	for(i=1;i<=n;i++)
	printf("%d ",a[i]);
	
	return 0;
}

感谢大家的阅读,不足之处还望提出和指正!

感谢 @thousfeet 的支持和帮助!

posted @ 2016-01-31 23:16  Wasdns  阅读(3112)  评论(3编辑  收藏  举报