• 《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
    ",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
    ",a,b,c);
    	
    	int *point_1=&a,*point_2=&b,*point_3=&c;
    	
    	swap(point_1,point_2,point_3);
    	printf("%d %d %d
    ",*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
    ",a,b,c);
    	
    	int *point_1=&a,*point_2=&b,*point_3=&c;
    	
    	swap(point_1,point_2,point_3);
    	printf("%d %d %d
    ",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 的支持和帮助!

  • 相关阅读:
    EF 关系规则(一对一、一对多、多对多...)
    EF框架中加子类后出现列名 'Discriminator' 无效问题
    .net下Ueditor配置(主要讲解上传功能配置)
    同构数查找程序的优化过程 Anthony
    Effective STL 条款17 Anthony
    C 语言中的数组类型和数组指针类型. Anthony
    Effective STL 条款18 Anthony
    RDLC之自定義數據集二
    给Calendar添加标签
    Profile学习
  • 原文地址:https://www.cnblogs.com/qq952693358/p/5174156.html
Copyright © 2020-2023  润新知