- 指针是C语言的精华,同时也是其中的难点和重点,我在近日对这一部分内容进行了重新的研读,把其中的一些例子自己重新编写和理解了一遍。此篇博客的内容即是我自己对此书例子的一些理解和总结。
我的理解:
变量的本质即内存,指针即访问变量的地址。利用指针来 <间接访问> 变量。
定义一个指针,p是指针变量名,系统自动为其分配内存,存放的是其指向的变量(内存)的地址。
例如:
1> int a=4;
2> int *p;
3> p=&a;
上述程序定义一个变量a,系统自动为其分配内存,那么这个内存的名称就是a,其存放的值是4。
再定义一个整形指针p,系统也为其分配内存(根据指针的类型分配不同大小的内存单元,例如此处指针为int类型,计算机为其分配4个字节),该内存里存放的是指向名称为a的内存的地址。
- 变量的本质是什么?-变量名只是一个代号,变量的本质就是内存。
- 指针保存的是什么?-指针保存的是内存地址。
这样来看,会不会对指针理解更加清楚一点呢?
- 使用指针变量
- 定义指针变量
- 引用指针变量
- 指针变量作为函数参数
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.
- 错误解法:
#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;
}
- 正确解法:
/*输入三个整数按从大到小的顺序输出*/
#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;
}
我的理解:
- 函数值是单向传递。swap函数中的指针变量p1,p2,p3交换地址,这样的变化并不会传递回原函数使原函数的指针变量的值发生改变。原函数中指针变量point_1/2/3所存储的内存地址并没有改变。
- 错误样例的错误在于:
原函数传递三个变量的地址到swap函数,企图通过swap交换形参p1,p2,p3这些指针变量所存储的地址,来交换实参point_1/2/3地址。然而忽略了函数调用时形参和实参是采用单向传递的值传递方式。在函数中改变了名称为p1,p2,p3的内存单元所存储的内存地址,但是主函数中名称为point_1/2/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的内存的地址。通过交换指针(函数接收的地址)所指向的内存所存储的值来达到排序的目的。
- 数组元素的指针
- 指针的运算
- 通过指针引用数组元素
- 数组名作为函数参数
- 通过指针引用多维数组
#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]
。
基于这一原理,我们来看下一个例子
#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;
}
我的理解:
- 在理解完例一之后,来看这个例子:a数组有十个元素,输入这十个元素,然后要求利用指针输出这十个元素。
- 与例一不同的是,这里的for循环有些不一样:
for(p=a;p<a+10;p++);
首先进行赋值操作,把a数组首元素的地址赋值给p,然后输出*p
的值(p指向该数组元素),再执行p++(该操作的意义是:p指向该数组的下一元素),循环结束的条件是p<a+10
,即当p指向a[9]时不再进行自增操作。- 本例利用p指针的自增来按序输出不同的数组元素,++自增运算符是C语言的一大特色。
#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循环即可快速解题。
- 但是我为什么要贴出这一段代码呢? 原因在于这段代码的注释,这个地方也是很令我困扰。
void swap(int *p1,int *p2)
{
int t;/*为何此处不能用*t?*/
t=*p1;
*p1=*p2;
*p2=t;
}