指针用法及其详解

此博客记录了博主学习C语言的路程,如果我的笔记有帮到你,那将是我的荣幸。

指针与数组

我们举一个变相引用指针的例子:数组名是数组首元素的地址。

例如:假如a是一个数组,则下面的语句成立
a = &a[0];//数组名是数组首元素 的地址

下面的等式说说明了数组和指针的关系:
data + 2 == &data[2] //相同的地址
*(data+2) ==data[2] //相同的值

C语言在描述数组的时候借助了指针,也就是说,定义a[n]的意思就是 * (a+n),可以认为*(a+n)的意思是“到内存的a位置,然后移动n个单元,检索存储在那里的值”

int a[] = {31,28,56,79,78,46};

a是数组首元素的地址,a+i是数组a[i]的地址,而*(a+i)则是该元素的值,相当于a[i]
a==&a[0] //a是数组首元素的地址
*a=a[0] //
a+i==&a[i] //a+i是数组a[i]的地址
*(a+i) ==a[i] //

数组作为参数传递

假设我们要编写一个程序去实现求一个数组所有元素的和,要求是要把数组当作一个参数传递给一个专门实现求和的函数,那么我们该怎么样才能正确传递参数呢?
假设求和函数是sum()

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int a[]={8,4,0,2}; 
    getchar();
    return 0;
}
int sum(int *ar,int n)
{
    int total = 0;
    for(int i = 0;i<n;i++)
    {
        total +=ar[i];
    }
    return total;
}

我们可以看到sum函数有两个形参,第一个形参告诉函数该数组的地址和数据类型,第二个形参告诉数组有多少元素。
关于形参还有一点要注意:只有在函数原型或者函数的定义头中,才可以用int ar[]代替int *ar

int sum(int ar[],int n); 

其实,int *arint ar[]都表示ar是一个指向int的指针,但是int ar[]只能用于声明形式参数

注意:声明数组形参
   因为数组闽南歌就是该数组首元素的地址,作为实际参数的数组名要求形式参数是一个与之匹配的指针,
   只有在这种情况下,C才会把int ar[]和int *ar解释成一样的的,也就是说,ar是指向int的指针。
   由于函数原型可以省略参数名,所以下面四个是等价的:
   int sum(int *ar,int n);
   int sum(int *,int);
   int sum(int ar[],int n);
   int sum(int [],int);
   但是,在函数定义中不能省略参数名,下面两种形式的等价:
   int sum(int *ar,int n)
   {
   		//其它代码省略

	}
	int sum(int ar[],int n);

使用指针形参

函数要处理数组必须要知道何时开始,何时结束,sum()函数用一个指针标记数组的开始,用一个整数形参表明待处理数组的元素个数(指针形参也表明了数组中的数据类型),但这并不是唯一的方法,还有一种方法是传递两个指针,一个代表数组的首地址,一个代表数组的末尾元素的地址,下面是一个示例:

#include<stdio.h>
#include<stdlib.h>
#define SIZE 10
int sump(int *start,int *end);//函数声明
int main()
{
   int a[SIZE] = {1,2,3,4,5,6,7,8,9,10};
   int answer;
   answer = sum(a,a+SIZE);
   printf("The total is %d\n",answer);
    getchar();
    return 0;
}
int sump(int *start,int *end)//求数组元素之和,用两个指针来接收
{
    int total = 0;//初始化数组总和为0
    while(start<end)
    {
        total +=*start;   //把数组元素的值加起来
        start ++;         //让指针指向下一个元素
    }
    return total;       //将值返回给answer
}

  • sum()函数把数组的元素个数作为测试终止的条件:
for(int i = 0;i < n;i++)//n表示数组的元素个数
  • sump()函数则使用第二个指针来作为测试终止的条件:
 while(start<end)

这里有一个容易让人误解的地方,因为数组的下标都是从0开始的,所以实际上传递的a+SIZE是指向数组末尾的下一个元素的地址,而不是数组的最后一个元素的地址,但是C语言却保证这种做法有效,如果硬要指向末尾的元素,则可以使用下面的代码

answer=sump(a,a+SIZE-1);

这个代码既不简洁也不好记,很容易导致编程错误。
顺带一提,虽然C语言保证了a+SIZE 有效,但未对a[SIZE]上的值作任何定义,所以程序不能访问该位置,还可以把程序压缩成以下代码:

total +=*start++;

一元运算符 *++ 的优先级相同,但 *的结合律是从右往左,所以++先求值,然后才是*start.,也就是说,start先递增然后再取出里面的值。使用后缀形式(即start++而不是++start)意味着先把指针指向的值加到total上然后再递增指针。如果使用 *++start,顺序则反过来,先递增指针,再使用里面的值,如果使用(*start)++,则先使用用start指向的值,然后递增该值,而不是递增指针,这样指针则一直指向同一个位置,虽然 *start++的写法比较常用,但是 * (start++)这样的写法更清楚。

指针表示法和数组表示法

a[i]*(a+i)是等价的,但是只有a是指针变量的时候,才能使用a++这样的表达式。指针表示法(尤其与递增运算符一起使用的时候)更接近机器语言,因此一些编译器在编译的时候能够生成效率更高的代码。

指针操作

  • 指针与整数相加(相减):可以使用+运算符把指针与整数相加(相减),整数会与指针所指向类型的大小(以字节为单位)相乘(sizeof(数据类型)*整数),然后把结果与初始地址相加(相减),因此,ptr + 4&ptr[4]等价。如果相加的结果超过了数组的范围,计算结果则是未定义的,如果刚刚超过数组末尾第一个位置,C保证该指针有效。

  • 递增(减)指针:递增指向数组元素的指针可以让该指针移动至数组下一个元素,因此ptr++,相当于把ptr的值加上4(我的系统中int为4个字节)

  • 指针求差:如果两个指针都指向同一个数组,那么他们的差值有意义,他们的差值就表示相隔了多少元素,或者与数组类型的单位相同,例如ptr1-ptr2 = 2,表示他们相差了两个int,而不是两个字节。

     千万不要解引用未初始化的指针
     例如:
     int *pt;             //未初始化的指针
     *pt = 5             //严重的错误
     double *pd;        //未初始化的指针
     *pd = 2.4;        //严重的错误
    
     这是因为创建指针的时候系统只分配了存储指针本身的内存,并未分配它将要存储数据的内存。
     因此在使用指针之前,必须先用已分配的地址初始化;还可以用动态分配的方法:比如malloc()函数
    

指针与一维数组

下面的代码将用两种方法循环遍历输出一维数组

  1. 下标
  2. 指针
  3. 下标与指针的结合使用
#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9,10};//数组名是数组首元素的地址
   for(int i = 0;i<10;i++)
   {
       printf("%d\n",a[i]);//用下标的方式遍历输出一维数组
   }
    getchar();
    return 0;
}

因为a[i]等价于*(a+i),所以,下面的代码等价于上一个

#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9,10};//数组名是数组首元素的地址
   for(int i = 0;i<10;i++)
   {
       printf("%d\n",*(a+i));//用指针的方式遍历输出一维数组
   }
    getchar();
    return 0;
}

指针的方式循环遍历输出

#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9,10};//数组名是数组首元素的地址
   for(int *p = a;p<a+10;p++)//数组名是数组首元素的地址
   {
       printf("%d\n",*p);//用指针的方式遍历输出一维数组
   }
    getchar();
    return 0;
}

此时的p是变量,所以p可以使用递增符++,不断获取下一个元素的地址,并取出元素。
当然还可以让p不递增也可以挨个取出数组元素的值,这就要结合下标与指针使用。例如:

#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9,10};//数组名是数组首元素的地址
   int *p = a;                      //p保存了该数组的首地址
   for(int i = 0;i<10;i++)        //数组名是数组首元素的地址
   {
       printf("%d\n",p[i]);     //用下标结合指针的方式遍历输出一维数组
   }
    getchar();
    return 0;
}

以上是指针与一维数组,更复杂的二维数组我们将在后面介绍。

sizeof()

虽然a&a都表示地址,但他们所指向的内容却不同,a表示指向数组首元素的地址,而&a则表示指向整个数组的地址,因此,有以下示例:

#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[10] = {1,2,3,4,5,6,7,8,9,10};//数组名是数组首元素的地址
   printf("%d %d\n",sizeof(*a),sizeof(*(&a)));
    getchar();
    return 0;
}

结果是

 sizeof(*a) = 4
sizeof(*(&a)) = 40

可以把a理解成一个行指针(有关概念我们在二维数组将继续深入讨论),行指针指向该行的所有元素,但只保存了该行第一个元素的地址,因此a存储了该行第一个元素的首地址,一维数组只有一行,因此a作为数组名,又可以理解为行指针,指向了一维数组的首元素,对应的字节数为4.
&a则表示指向整个数组,那么它指向的元素一共有10个,对应了10×4个字节。

指针与二维数组

 行指针:指向该行的所有元素,存储了该行第一个元素的地址,对应的字节数sizeof()=该行的元素数×相应的数据类型.
#include<stdio.h>
#include<stdlib.h>
int main()
{
   int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//数组名是数组首元素的地址
  /*
  行指针:指向该行的第一个元素,存储了该元素的地址:
   a(数组名)-------==&a[0][0](第一行第一个元素的地址)    
   a+1------------==&a[1][0](第二行第一个元素的地址)    
   a+2------------==&a[2][0](第三行第一个元素的地址) 

   所指向地址的字节数:sizeof()
   sizeof(*a)=16------------该行4个元素,都是int类型(4个字节),4*4=16
   sizeof(*(a+1))=16--------该行4个元素,都是int类型(4个字节),4*4=16
   sizeof(*(a+2))=16--------该行4个元素,都是int类型(4个字节),4*4=16
   而具体元素只有相应数据类型个字节数:
    sizeof(*(&a[0][0]))=4
    sizeof(*(&a[1][0]))=4
    sizeof(*(&a[2][0]))=4
   */
     printf("%d %d\n",sizeof(*a),sizeof(*(&a[0][0])));
     printf("%d %d\n",sizeof(*(a+1)),sizeof(*(&a[1][0])));
     printf("%d %d\n",sizeof(*(a+2)),sizeof(*(&a[2][0])));
        getchar();
        return 0;
}

因为二维数组也可以理解为数组的数组,即理解为一个一维数组,每个元素又为一个一维数组,因此假如有一个二维数组:int a[3][4]

数组名a该数组首元素的地址,本例中,a的首元素是一个内含3个int 类型的一维数组,每个一维数组内含四个int类型的元素,可以用a[0],a[1],a[2]来表示内含的三个一维数组,他们也是一维数组的数组名,更是a的三个元素, 因为a是数组首元素的地址,所以a的值和&a[0]的值相同,而a[0]为一维数组的数组名,所以a[0]=&a[0][0]

具体的字节数:
在这里插入图片描述
这表明虽然他们的地址相同,但是他们指向的内容却不同,行指针指向该行的所有元素,但只保留了该行第一个元素的地址。

	列指针:与行指针相同,不过要加上一个*号表示它是列指针。
		int a[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
	 a表示一个行指针,  *a表示一个列指针,指向该行的第一个元素,仍是地址
 	 a+1表示一个行指针,*(a+1)表示一个列指针,指向该行的第一个元素,仍是地址
 	 a+2表示一个行指针,*(a+2)表示一个列指针,指向该行的第一个元素,仍是地址

任意行任意列的元素的地址

这时候,我们可以行指针跟列指针结合起来,比如第i行第j列:
先用行指针表示第i行:a+i
然后用列指针表示第一个元素的地址:*(a+i)
然后我们再移动到任意列:*(a+i)+j
这就是任意行任意列的地址,当然也等价于:&a[i][j];
于是我们得到结论:*(a+i)+j==&a[i][j]
更有:*(*(a+i)+j) = a[i][j]
我们前面提到过:二维数组可以看成一维数组,是数组的数组,里面有a[0],a[1],a[2]等元素,

对于一个二维数组来说,有以下结论:

  • a[i] = &a[i][0]
  • a+i = &a[i]
  • *(a+i) = a[i]
  • *(a+i) + j = &a[i][j]
  • *( *(a+i) + j)) = a[i][j]
a   = *a //虽然表示的地址相同,但是他们本身占有的字节数不同
a+1 = *(a+1)
a+2 = *(a+2)
a+i = *(a+i)//表示的是某行第一个元素的地址
sizeof(*a) = 16;                  sizeof(**a) = 4
sizeof(*(a+1)) = 16;              sizeof(*(*(a+1))) = 4
sizeof(*(a+2)) = 16;              sizeof(*(*(a+2))) = 4
sizeof(*(a+i)) = 16;              sizeof(*(*(a+i))) = 4
a = &a[0]   
a+1 = &a[1]
a+2 = &a[2]
a+i = &a[i]
a[0] = &a[0][0]
a[1] = &a[1][0]
a[2] = &a[2][0]
a[i] = &a[i][0]
//列指针,表示该行的第一个元素的地址
*(a)   = a[0] = &a[0][0]
*(a+1) = a[1] = &a[1][0]   
*(a+2) = a[2] = &a[2][0]
*(a+i) = a[i] = &a[i][0]
*(a+i)+j = &a[i][j]     //任意行任意列的值的地址
sizeof(*(*(a+i)+j)) = sizeof(*(&a[i][j])) = 4    //字节数
*(*(a+i)+j) = a[i][j] //任意行任意列的值,用下标表示

二维数组参数传递(待更)

函数指针(待更)

结构体指针(待更)

共用体指针(待更)

/*

*/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值