前言
想必c语言中的指针是大家学习c语言中碰到的大怪兽之一吧,可能会有人和我一样到现在还没有打通这个BOSS的。这个问题也是困扰了我许久的一个问题,随着学习的深入我发现,学好c语言,其他语言都能很快入手,比如说python,Matlab等等。现在没上完网课和复习好功课的我,居然在这里熬夜写博客,也是没谁了。之前,我总是想用放假的时间来弥补上这一缺陷,毕竟没有指针的c语言是没有灵魂的嘛。心动不如行动,经过大半个月的学习,我郑重宣布:我把这个BOSS打通关了!这不,立马上手博客来为大家分享一波我对指针的认识和感悟。其实,这个前言也算是我自己学习过程中的感悟吧。
文章推敲了一个星期,慢慢的干货哦!
我不敢说写得比教材更好,但是我是按照自己理解和难点的解析,相信大家通过阅读我的文章,会对c指针会有更独到的见解。话不多说,看文章!
文章比较长,但是c指针的知识可不能少!大家认真看完文章,相信一定能够掌握指针的!
我们还要目录可以索引,大家也可以根据需要选看,建议收藏保存,有问题也可以向笔者提出!
指针是什么
具体形象的说,指针就像房间的门牌号,房间里面是我们要数据。我们可以通过地址,来访问地址所指向的数据,就像通过门牌号来找到指定的房间。
话说回来,我们为什么要使用指针呢,普通的引用不香嘛?
但是,指针能够很灵活地帮助我们处理许多问题,节省使用空间,完成简单引用变量所不能完成的算法与程序,因此我们要学习使用指针。
指针变量
我们可以直接引用数据,就像
int a=5;
printf(“a=%d”,a);
或者间接地引用数据,即是用指针来完成
int *a=&5;
printf("%d",*p);
这里a是指向整型变量a的地址,其赋值也可以写作
int *a,b;
b=5;
a=&b;
当然了,指针还有很多类型,比如
float *p;
char *p;
等等
这里需要注意的是,指针都有它的类型,即整形、字符型、浮点型等等,具体来说就是int *p; 中p的就是指向一个整型数据的指针。当然这里不考虑void *p的情况,读者不必纠结于这个,这里只是点一下,让大家了解一下,我们在文章后还会对其进行解释。
现在附上一段代码让大家训练一下指针的定义和使用:
int *pointer1 , *pointer2;
int a , b ;
pointer1 = &a;
pointer2 = &b;
printf("a=%d , b=%d \n",a,b);
printf("a=%d , b=%d \n",&a,&b);
printf("a=%d , b=%d \n",*pointer1,*pointer2); //这里大家注意一下
读者可以放入c语言软件中看看结果是否一致。其中*的作用是调用指针变量中的数据,就是找到门牌号就用钥匙开门回家了,*就相当于是钥匙!
这里要说明的一点是,大家想必都会有这种情况吧:
scanf("%d",a);
大家忘记给a前面加上了&,取地址符。这时候exe程序就会无法执行,这是你就一头雾水了,哈哈。
其实,这里的scanf函数就是讲输入的通过地址保存到a中,如果你忘记加&,程序exe当然就无法执行喽。
int a = 5;
printf("%d\n",a);
printf("%d\n",&a);
大家可以根据对上面代码,对过刚才的知识点进行进一步的理解。
指针作为函数参数
接下就有一点难度了,敲黑板!
指针怎么能不与函数相结合,二者结合才是经典嘛
文字不一定能看懂,但掌握代码就掌握了精髓,看代码:
#include<stdio.h>
int main()
{
int a,b,*p1,*p2; //变量定义
int swap(int *x , int * y); //函数申明
a=5,b=10;
p1=&a; //将a和b地址给p1和p2
p2=&b;
swap(p1,p2); //调用函数
printf("a=%d,b=%d\n",a,b);
return 0;
}
int swap(int *x , int *y)
{
int temp; //定义一个存储结果的整型变量
temp = *x; //将传入的形参进行运算
*x = *y;
*y = temp;
return 0;
}
上述代码将指针变量p1和p2传入swap函数给x和y,x和p1都是指向a的值,y和p2都指向b的值,对其进行的引用,即是x=*p1=a=5 , *y=*p2=10。
所以程序就是通过指针起到了一个值交换的作用。运行结果如下
有人就发问了,能不能通过交换地址来改变值呢,将原来的swap函数进行更改后给出如下程序,我们说这是不行的
int swap(int *x , int *y)
{
int *temp;
temp=x;
x=y;
y=temp;
return 0;
}
显然结果并没有起到值交换的作用,这是为什么呢,道理是函数的实参对形参的值传递是单向的,改变形参的值并不能影响到实参。这个点希望大家都能注意一下,这也是我当时学习时候的疑难杂症。
下面是一道程序例题,加深大家对指针作为函数参数的印象
题目:输入三个整数,比较大小并交换值,再从大到小输出。
#include<stdio.h>
int main()
{
int a,b,c,*p1,*p2,*p3;
int change(int *x , int *y , int *z);
p1=&a;
p2=&b;
p3=&c;
printf("input integer number a,b,c:\n");
scanf("%d %d %d",&a,&b,&c);
change(p1,p2,p3);
printf("%d %d %d\n",a,b,c);
return 0;
}
int change(int *x ,int *y , int *z)
{
int swap(int *x , int *y);
if(*x < *y)
{
swap(x,y);
}
if(*x < *z)
{
swap(x,z);
}
if(*y < *z)
{
swap(y,z);
}
return 0;
}
int swap(int *x , int *y)
{
int temp;
temp=*x;
*x=*y;
*y=temp;
return 0;
}
读者可以自己试着写一下,或是运行程序看结果是否符合题设。
通过指针来引用数组
在之前的c语言学习中,我们知道数组可以这样定义
double a[10]={1,2,3,4,5,6,7,8,9,10};
double b[10];
int i;
for(i=0;i<10;i++)
{
scanf("%lf",&a[i]);
}
在指针中,我们可以有新的定义和引用方式,使得数据存放更加快捷、方便、整洁。
int *p,a[10];
p=a; //等价于 p=&a[0]
这就相当于定义了一个指针变量,指向数组a的第一个元素的地址。或者可直接写为
int *p=a;
在循环变量中,我们用i++,i–,--i,++i来使得循环进一步执行,指针中是否也有这一性质呢?
答案是有的!
在前面*p=a的条件下,p++指向的是下一个数组元素的地址,即a[1]。假设p指针的初始地址是2000,运算i++后,p指向的地址变为200+d*sizeof(int)
。比如int型的指针变量p的,执行p++
后,p的地址变为2000+1*4=2004
。对于其他类型的变量地址,增加的地址为指针变量增加量乘以其所占的字符大小,道理都是一样的。
下面是指针数组的引用
int *p,a[10]={1,2,3,4,5,6,7,8,9,10};
p=a;
printf("%d\n",a[5]);
printf("%d\n",*(a+5));
printf("%d\n",a+5); //输出的是地址
其中的重中之重是*,他能够使得地址转变为其指向的变量,与&的作用相反,就像是一对欢喜冤家。
下面给出一段代码,是指针来引用数组的程序,读者领会一下。
int *p,a[10]={1,2,3,4,5,6,7,8,9,10};
for(p=a;p<a+10;p++)
{
printf("%d\t",*p);
}
p=a; //敲黑板!
大家不要小看这最后一行的p=a
,他让p重新指向&a[0],否则p的指向超出了a数组的范围,而他指向的是我们所未知的,若指向电脑中重要的数据,会带来不可估计的后果!大家记得在,引用完数组的指针变量后,重新将指针指向最初的数组地址,以便于后序程序的引用。
数组名作为函数参数
人狠话不多,看程序,该程序的作用是将整型数组中的数据逆序排列:
#include<stdio.h>
int main()
{
int inv(int x[] , int n);
int a[10]={1,2,3,4,5,6,7,8,9,10};
int *p=a;
printf("the original array is:\n");
for( ; p<a+10 ; p++)
{
printf("%d\t",*p);
}
printf("\n");
p=a;
inv(a,10);
printf("the sorted array is:\n");
for( ; p<a+10 ; p++)
{
printf("%d\t",*p);
}
printf("\n");
return 0;
}
int inv(int x[] , int n)
{
int i,j,temp;
for(i=0 ; i < (n-1)/2 ; i++)
{
j=n-1-i;
temp=x[i];
x[i]=x[j];
x[j]=temp;
}
return 0;
}
读者可上机调试一下正确性,自己动手,丰衣足食。
也可以将上述的函数声明和函数该做如下
int inv(int *x ,int n); //函数声明
int inv(int *x , int n)
{
int *p,temp,*i,*j,m=(n-1)/2;
p=x;
i=x,j=x+n--1;
for( ; p < x+m ; p++,i++,j--)
{
temp=*i;
*i=*j;
*j=temp;
}
return 0;
}
上述程序阐明了两种函数中引用数组的形式,本质都是输入函数的地址,对地址进行相应运算,从而达到改变数组中元素,最终达成数组的运算。
这里要什么的是,指针之间的加减法有意义的条件是,两个指针指向的是同一数组的元素,否则无意义。
接下来看一段程序,这是用指针的方法将数组中元素从大到小排序。
#include<stdio.h>
int main()
{
int *p,a[10];
int compare(int *x , int n); //函数申明
p=a;
for (; p<a+10 ; p++)
{
scanf("%d",p); //输入参与运算的数组
}
printf("the array is :\n");
for(p=a ; p<a+10 ; p++)
{
printf("%d\t",*p); //先输出输入的数组
}
compare(a,10); //调用函数,虽然只有一句,但是是关键
printf("the sorted array is :\n");
for(p=a ; p<a+10 ; p++)
{
printf("%d\t",*p); //输出整理后的数组
}
return 0;
}
int compare(int *x , int n)
{
int i,j,k,temp;
for(i=0 ; i < n-1 ; i++) //这里除了指针的使用外,交换元素的时候就是用的冒泡法
{ //基础就是之前排序算法时候的内容
k=i; //如果大家有不懂的,可以留言,之后我会出相关的内容博客
for(j=i+1 ;j < n ;j++)
{
if(*(x+k) < *(x+j))
{
k=j;
if(k != i)
{
temp = *(x+k);
*(x+k) = *(x+i);
*(x+i) = temp;
}
}
}
}
return 0;
}
其实,很多时候,实践才能出真知,看不到程序的时候多打几遍,多调试,疑问的地方再返回来看看书,查查资料,这样进步最大,印象最深刻。程序我都调试过啦,可以用的,大家也可以改一些小地方看看能不能运行成功,自己多思考。
通过指针引用多维数组
之前讲的是一维数组的使用,那么多维数组又是如何引用的呢,在这里我们以二维数组为例
int a[2][2]={{1,2},{3,4}}; //这是通过定义的时候,直接赋予数组元素的值
我们也可以通过后面的循环来分别对数组中的元素进行定义,这里的知道点是数组那一章中,这里假设大家都已经会了,这里点一下,让大家有个回忆印象。如果不会的话也没事啦,等笔者出了相关文章就反回来复习就好啦。
这里讲指针数组,笔者讲对下表进行一一解释
先说明一点,&和*是相反的运算,一个是取地址,后者是取指针变量中的值,这一点在之前有讲过。
int a[10],*p;
p=a;
这里p就是数组a的起始地址,也是&a[0],读者也可以这样理解。
星号的作用就是取地址中的值,所以*(a+i)即表示a[i],这是对于一维数组的说明。
对于二维数组,
a[1]
代表二维数组第二行的第一个元素的地址,也是第二行的其实地址,其相当于*(a+1)
&a[2][3]
表示a[2][3]
的地址,也可用*(a+2)+3
,其中*(a+2)
找到了该元素的所在行,并指向了改行的地址,或是该行的第一个元素的地址&a[2][0]
,之后再有*(a+2)+3
,就找到了a[2][3]
的地址,相当于是&a[2][3]
。而如果我们要用指针来表示a[2][3]
的地址,有了之前的说明,不难写出
*(*(a+2)+3)
。
读者对上面这个表理解到位,就差不懂能够掌握指针数组的引用了。
为了大家能熟悉一下知识点,笔者准备了如下程序:
#include<stdio.h>
int main()
{
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
printf("%d,%d\n",a,*a);
printf("%d,%d\n",a[0],*(a+0));
printf("%d,%d\n",&a[0],&a[0][0]);
printf("%d,%d\n",a[1],a+1);
printf("%d,%d\n",&a[1][0],*((a+1)+0));
printf("%d,%d\n",a[2],*(a+2));
printf("%d,%d\n",&a[2],a+2);
printf("%d,%d\n",a[1][0],*(*(a+1)+0));
printf("%d,%d\n",*a[2],*(*(a+1)+0));
return 0;
}
调试结果如下,看看他和你想的是不是一样的呢?
指向n个元素的一维指针数组
读者先看它的定义
int (*p)[4];
注意()是不能省略的哦!否则p会优先于后面的[]结合。
看下面的程序
#include<stio.h>
int main()
{
int a[4]={1,2,3,4};
int (*p)[4];
p=&a;
printf("%d\n",(*p)[3]); //输出第三个元素3
return 0;
}
我们讲一维指针数组也可以用于函数之中,请看下面的程序:
题目:查找有一门课程不及格的学生,输出他们的全部成绩。
#include<stdio.h>
int main()
{
void search(float (*p)[4] , int n); //函数声明
float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};
search(score,3); //调用函数
return 0;
}
void search(float (*p)[4] , int n)
{
int i,j,flag;
for(j=0 ; j<n ; j++)
{
flag=0;
for(i=0 ; i<4 ; i++)
{
if(*(*(p+j)+i) < 60)
{
flag=1;
}
if(flag == 1)
{
printf("No.%d fails, and his scores are :\n",j+1);
for (i=0 ; i < 4 ; i++)
{
printf("%5.2lf",*(*(p+j)+i));
}
printf("\n");
}
}
}
}
指向n个元素的一维数组指针,把多维数组继续降维,使得问题变得更加简单,是我们在编写程序中的很好的选择,希望读者能够多看几遍,把它掌握下来。
预知后事如何,请听下回分解
本来笔者是想一口气把指针的东西一起发出来的,但是因为是第一篇文章,不知道效果如何,写得好不好,而且确实也很多,很难打。综合多种原因后,笔者决定把指针分为上中下部分发表,一来是分段学习,更好完成和接受,二来也想看到读者的反馈啦啦啦!
希望大家多多发表评论交流,笔者看到一定及时回复,我们一起学习进步呀!