C语言的指针学习

 1、什么是指针

(1)指针的概念

指针是存放一种变量的地址的变量,其特殊性表现在类型和值上,简单来说,地址就是指针,指针就是地址。从变量角度讲,指针变量也具有变量的要素:

指针变量的命名,与一般变量命名相同,遵循C语言的命名规则。

指针变量的类型,是针变量所指向的变量的类型,而不是自身的类型。

(2)指针的大小

在64位机,指针变量在内存中占8个字节;在32位机,指针变量在内存中占4个字节

2、定义指针变量

定义指针变量与定义普通变量非常类似,不过要在变量名前面加*。格式为:

        datatype *name;

        datatype *name = value;

在定义指针变量中,*表示这是一个指针变量,如果不是定义指针变量,*代表的是取出一个地址中存放的数值,datatype表示该指针变量所指向的数据的类型。

在定义指計变量的同时对它进行初始化。并将变量的地址赋予它。此时指针变量就指向了变量的地址。

3、引用指针变量

在引用指针变量时,有以下3种情况:

(1)给指针变量赋值。如:

        p=&a; //把 a的地址赋給指针变量p

指针变量p的值是变量a的地址,p指向a.

(2)引用指针变量指向的变量。

如果已经执行p=&a;即指针变量p已经指向了变量a,则printf("%d" ,*p);其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值。

如果有赋值语句:*p=1;

表示将整数1赋给p当前所指向的变量,如果p指向变量a,则相当于把1赋给a,即a=1。

(3)引用指针变量的值。如:

        printf("%o",p);

作用是以八进制数形式输出指针变量p的值,如果p指向了a,就是输出了a的地址,即&a。

4、指针的偏移

        int a=10;

        int *p=&a;

p指向a的地址,p+1中的1是指的一个指向类型的大小,p指向整型变量的地址,整型是4个字节,所以p+1等于访问了a地址后的4个字节地址。

5、野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针造成原因:

①指针未初始化

例:int *p;//局部变量指针未初始化,赋给指针变量p的是一个随机地址

②指针越界访问

int a[10]={0};

int *p=a;//p指向数组首元素地址,也就是&a[0]

p=p+9;//p指向p+9==a[9]

*(++p)=10;//++p==a[10],对a[10]的地址进行操作,已经超出数组a的范围了,所以p是野指针

③指针指向的空间已经被释放

定义一个局部变量,将局部变量的地址传给指针变量,当程序阅读到局部变量作用域,局部变量的内存就被释放,地址也不能再被指针指向。

如何规避野指针

①定义指针时就初始化

②小心指针越界

③不知道指针只想哪里就先指向NULL

④避免返回局部变量的地址

⑤指针使用前检测其有效性

6、指针和数组

        通过上面的学习,我们知道了指针变量是用来存放一种类型变量的地址,那么我们之前学习的数组是否也能将其地址存到指针变量中呢,答案当然是可以啦,为什么呢?数组名通常就是数组的首元素地址,我们将指针变量指向数组的首元素地址,我们就可以通过*解引用操作符,取出地址中存放的数据,但是还会有人问,那数组第二、第三等等后面的元素该如何读取出来呢,这个问题就问得好,我们刚刚不就学习了指针的偏移了嘛,+1是移动的大小是指针类型,也就是我们指针存放的地址,该地址中的数据类型,例如我们下面的两行代码,定义的是存放双精度浮点数的数组(当我们定义一个数组时,系统会分配一块连续的空间(地址),数组元素类型*元素个数=该空间大小,一个数组元素后面紧接着下一个元素),那么我们将p+1,将首元素地址移动了8个字节(双精度浮点型),等于移动到下一个元素的首地址,由此我们可以通过指针间接对数组内的元素值进行访问或修改。

        double a[10]={0};//a是双精度数组名,是该数组的首元素地址

        double  *p=a;//将数组的首地址给指针变量

在 C 语言中,数组名除了以下两种情况,都是指的首元素地址。第一种情况,sizeof(数组名),这是计算数组中的元素个数,而不是数组首元素地址的大小;第二种情况,&数字名,这是整个数组的地址,虽然和首元素地址相同,但是对这两种地址进行+1操作,我们发现两者地址截然不同,一个是移动一个元素类型字节大小的地址,一个的移动的是整个数组占用内存大小的地址。除了这两种情况外,都代表数组中首元素的地址。

注意点:

1)如果指针变量 p 已指向数组中的一个元素,则 p+1 指向同一数组中的下一个元素,p-1 指向同一数组中的上一个元素。注意:执行 p+1 时并不是将 p 的值(地址)简单地加 1,而是加上一个数组元素所占用的字节数。例如,数组元素是 float 型,每个元素占 4 个字节,则 p+1 意味着使 p 的值加 4 个字节,以使它指向下一个元素。p+1 所代表的地址实际上是 p+1*d,d 是一个数组元素所占的字节数。若 p 的值是 2000,则 p+1 的值不是 2001 而是 2004。

2)如果 p 的初值为 &a[0],则 p+i 和 a+i 就是数组元素 a[i] 的地址,或者说,他们指向 a 数组序号为 i 的元素,见下图,这里注意的是 a 代表数组首元素的地址,a+1 也是地址,它的计算方法同 p+1,即它的实际地址为 a+1*d。例如,p+9 和 a+9 的值是 &a[9],它指向 a[9]。

3)p+i)和*(a+i) 是p+i或a+i所指向的元素,即a[i]。例如*(p+5)和*(a+5)就是a[5]三者等价。

7、指针变量作为函数参数

将一个指针变量p(地址)作为实参传到函数中,函数中可以对该地址进行解引用操作等。

8、数组名作为函数参数

数组名除了sizeof(数组名)和&数字名两种情况外,都代表数组中首元素的地址。所以我们可以将数组名当作指针变量传到函数中,供函数使用。 

下面通过一个例子来学习如何将数组名作为函数参数,供函数使用。

输入某班学生某门课程的成绩和学号(最多不超过40人),输入负值时表示输入结束。用函数编程通过返回数组中最大元素下标,查找并输出成绩的最高分和所属学生学号。

#include<stdio.h>
int find_max_grade(double a[],int i)//寻找最高分的下标,并将下标作为返回值返回
{
    int j,flag=0;
    double max=a[0];//将数组首元素作为擂主,遍历数组,当有元素值比擂主大的,将该元素赋给擂主,并记录下标
    for(j=1;j<i;j++)
    {
	    if(a[j]>max)
	    {
	        max=a[j];
	        flag=j;
	    }
    }
    printf("全班最高分:%.2lf\n",max);
    return flag;
}
void printf_no(int *p,int k)//该函数略显多余,只是用来再练习一下数组名传参
{
    printf("学生学号:%d\n",b[k]);
    *(p+1)=100;//修改数组第二个元素的值   
}
int main()
{
    double grade[40]={0};
    int no[40]={0},i=-1;
    do{
	    i++;
	    printf("请输入第%d个学生成绩和学号:",i+1);
	    scanf("%lf%d",&grade[i],&no[i]);
    }while(grade[i]>=0&&no[i]>=0);//输入的分数或学号为负数即为停止输入
    int flag=find_max_grade(grade,i);
    printf_no(no,flag);
    printf("%lf",grade[1]);
    return 0;
}

 数组名作为实参,形参用一个数组接收,我们发现用这种方法无法对数组中的元素进行更改,当我们在printf_no函数中,用一个指针来接收传过来的数组首元素地址,最后将*(p+1)也就是grade[1]赋值为100,发现回到主函数中打印该值,真的修改了,对比,我们发现传值调用和传址调用的两者差别还是很大的,前者只是实参的拷贝(换种说法,就是文件只读,不能对其进行写操作),所以我们可以将其中的数据读出,打印出来;而后者是真正得到数组的地址,通过地址,对地址中的数据进行更改。

9、指针变量作为函数参数
        函数的参数不仅可以是整形,浮点型等数据,也可以是指针类型。他的作用是将一个变量的地址传送到另一个函数中,其实刚刚的数组名作为函数实参就是传址。

下面通过一个例子学习如何将指针变量作为函数参数。

n个元素的数组,将数组所有元素向后移动m个元素单位,最后的m个元素移到开头。

例如:   原数组:   1 2 3 4 5 6 7 8 9 10

移动3个元素单位:10 9 8 1 2 3 4 5 6 7

#include<stdio.h>
void input_array(int *p,int sz)//输入数组元素
{
    printf("请输入数组元素:");
    for(int i=0;i<sz;i++)
    {
	    scanf("%d",p+i);
    }
}
void sort_array(int *p,int m,int sz)//将数组元素向后移动m个元素,最后的m个元素移动到数组开头
{
    int t;
    for(int i=0;i<m;i++)
    {
	    t=*(p+sz-1);         //将最后一个元素用临时变量存起来
	    for(int j=9;j>0;j--) //将前面9个元素依次往后移1位
	    {
	        *(p+j)=*(p+j-1);
	    }
	    *p=t;                //将存入临时变量中最后一个元素存入首个元素中
    }
}

void output_array(int *p,int sz)//打印结果
{
    for(int k=0;k<sz;k++)
    {
	    printf("%d ",*(p+k));
    }
    printf("\n");
}
int main()
{
    int a[10]={0};
    int *p=a,sz=sizeof(a)/sizeof(a[0]);//计算出数组长度
    input_array(p,sz);
    int m;
    scanf("%d",&m);
    sort_array(p,m,sz);
    output_array(p,sz);
    return 0;
}


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值