当前位置:首页>>开发技术分享

C指针

1.指针基本介绍指针是C语言的精华,也是C语言的难点指针,就是内存的地址;所谓指针变量,也就是保存了内存地址的变量,关于指针的基本使用,在讲解变量的时候做了入门级的介绍获取变量的地址,用&,比如:int num=10,获取num的地址:&num指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:i

管理员

1.指针基本介绍

  • 指针是C语言的精华,也是C语言的难点

  • 指针,就是内存的地址;所谓指针变量,也就是保存了内存地址的变量,关于指针的基本使用,在讲解变量的时候做了入门级的介绍

  • 获取变量的地址,用&,比如:int num=10,获取num的地址:&num

  • 指针类型,指针变量存的是一个地址,这个地址指向的空间存的才是值,比如:int *ptr = & num;ptr就是指向int类型的指针变量,即ptr是int *类型

  • 获取指针类型所指向的值,是用:(取值符号),比如:int * ptr,使用ptr获取ptr指向的值

2.什么是指针

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址,就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明,指针变量声明的一般形式为

int *ip;//一个整型的指针double *dp;//一个double型的指针float *fp;//一个浮点型的指针char *ch;//一个字符型指针
void main(){    int num=1;    //定义一个指针变量,指针
    //说明
    //1.int *表示类型为指针类型
    //2.名称ptr,ptr就是一个int *类型
    //3.ptr指向了一个int类型变量的地址
    int *ptr=#    //num的地址是多少
    //说明1:如果要输出一个变量的地址,使用格式是%p
    //说明2:&num表示取出num这个变量对应地址
    printf("num的值=%d num地址=%p",num,&num);    //指针变量本身也有地址&ptr
    //指针变量存放的地址*ptr
    //获取指针指向的值*ptr
    printf("ptr的地址是%p ptr存放的值是一个地址为%p ptr指向的值=%d",&ptr,ptr,*ptr);
    gatchar();
}

3.指针的算数运算

指针是一个用数值表示的地址,可以对指针执行算数运算,可以对指针进行四种算数运算:++,--,+,-

3.1 指针递增操作(++)

#include <stdio.h>const int MAX=3;//常量int main(){    int var[]={10,100,200};    int i,*ptr;//ptr 是一个int *指针
    ptr =var;//ptr指向了var数组的首地址
    for(i=0;i<MAX;i++){        printf("var[%d]地址=%p",i,ptr);        printf("存储值:var[%d]=%d",i,*ptr);
        ptr++;//ptr=ptr+1(一个int字节数);ptr存放值+4字节
    }
    getchar();    return 0;
}
  • 数组在内存中是连续分布的

  • 当对指针进行++时,指针会按照它指向的数据类型字节数大小增加,比如 int *指针,每++,就会增加4个字节

3.2 指针递减操作(--)

#include <stdio.h>const int MAX=3;int main(){    int var[]={10,100,200};    int i,*ptr;    //指针中最后 一个元素的地址
    ptr=&var[Max-1];    for(i=MAX;i>0;i++){        //反向遍历
        printf("ptr存放的地址=%p",ptr);        printf("存储值:var[%d]=%d",i-1,*ptr);
        ptr--;
    }
    getchar();    return 0;
}
  • 数组在内存中是连续分布的

  • 当对指针进行--时,指针会按照它指向的数据类型字节数大小减少,比如int *指针,没--,就减少4个字节

3.3 指针+,-操作

#include <stdio.h>int main(){    int var[]={10,20,100};    int i,*ptr;
    ptr=var;
    ptr+=2;//ptr的存储地址+2个字节
    printf("var[2]=%d var[2]的地址 ptr存储的地址=%p ptr指向的值=%d",var[2],&var[2],ptr,*ptr);
    getchar();    return 0;
    
}
  • 可以对指针按照指定的字节数大小进行+或者-的操作,可以快速定位你要的地址

3.4 练习

int main(){    int var[]={10,100,200,400,8,12};    int i,*ptr;
    ptr=&var[2];
    ptr-=2;    printf("ptr指向的值=%d",*ptr);
    getchar();    return 0;
}

4.指针的比较

指针可以用关系运算符进行比较,如==,<<=,>>=如果p1和p2指向两个变量,比如同一个数组中的不同元素,则可对p1和p2进行大小比较

#include<stdio.h>int main(){    int var[]={10,100,200};    int* ptr;
    ptr=var;    if(ptr==var[0]){        printf("ok");//错误,类型不一样
    }    if(ptr==&var[0]){        printf("ok2");
    }    if(ptr===var){        printf("ok3");
    }    if(ptr>=&var[1]){        printf("ok4");
    }
    getchar();    return 0;
}
#include<stdio.h>const int MAX = 3;int main(){    int var[]={10,100,200};    int i,*ptr;
    ptr=var;
    i=0;    while(ptr<=&var[MAX-2]){        printf("address of var[%d]=%p",i,ptr);        printf("value of var[%d]=%d",i,*ptr);
        ptr++;
        i++;
    }
    getchar();    return 0;
}

5.指针数组

5.1 基本介绍

要让数组的元素指向int或者其他数据类型的地址(指针)。可以使用指针数组

5.2 指针数组定义

数据类型 *指针数组名[大小];

  • 比如:int *ptr[3];

  • ptr 声明为一个指针数组

  • 由三个整数指针组成,因此,ptr中的每个元素,都是一个指向int值的指针

5.3 指针数组快速入门和内存布局

#include<stdio.h>const int MAX=3;int main(){    int var[]={10,100,200};    int i,*ptr[3];    for(i=0;i<MAX;i++){
        ptr[i]=&var[i];//赋值为整数的地址
    }    for(i=0;i<MAX;i++){        printf("value of var[%d]=%d ptr[%d]本身的地址=%p",i,*ptr[i],i,&ptr[i]);
    }
    getchar();    return 0;
}

5.4 指针数组应用实例

  • 请编写程序,定义一个指向字符的指针数组来存储字符串列表,并通过遍历该指针数组,显示字符串信息(即定义一个指针数组,该数组的每个元素,指向的是一个字符串)

#include <stdio.h>void main(){    //定义一个指针数组,该数组的每个元素,指向的是一个字符串
    char *bookd[]={        "三国演义"
        "西游记"
        "红楼梦"
        "水浒传"
    };    char *pStr="abc";    int i,len;    for(i=0;i<len;i++){        printf("books[%d]指向字符串是=%s pStr指向的内容=%s",i,books[i],pStr);
    }
    getchar();
}

6.指向指针的指针(多重指针)

6.1 基本介绍

  • 指向指针的指针是一种多级间接寻址的形式,或者 说是一个指针链,通常,一个指针包含一个变量的地址,当定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

address --------------->address--------------->value

pointer--------------------pointer------------------variable

6.2 多重指针快速入门

  • 一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向int类型指针的指针

    • int **ptr;//ptr的类型是int * *

  • 当一个目标值被一个指针简介指向另一个指针时,访问这个值需要使用两个星号运算符,比如**ptr

  • 案例演示

#include<stdio.h>int main(){    int var;    int *ptr;//一级指针
    int **pptr;//二级指针
    int ***ppptr;//三级指针
    var =3000;
    
    ptr=&var;//var变量的地址赋给ptr
    
    pptr=&ptr;//将ptr存放的地址赋给pptr
    
    ppptr=&pptr;//表示将pptr存放的地址赋给ppptr
    
    printf("var的地址=%p var=%d ",&var,var);    printf("ptr的本身的地址=%p ptr存放的地址=%p *ptr=%d",&ptr,ptr,*ptr);    printf("pptr本身的地址=%p pptr存放的地址=%p **ptr=%d",&pptr,pptr,**pptr);    printf("ppptr本身地址=%p ppptr存放的地址 ***ppptr=%d",&ppptr,ppptr,***ppptr);
    getchar();
    rerurn 0;
    
}

7.传递指针(地址)给函数

当函数的形参类型是指针类型时,是使用该函数时,需要传递指针,或者地址,或者数组给该形参

7.1传地址或指针给指针变量

#include<stdio.h>void test(int *p);//函数声明,接收int*void main(){    int num=90;    int *p=&num;    //将num的地址赋给P
    test2(&num);//传地址
    printf("main()中的num=%d",num);
    test2(p);    printf("main()中的num=%d",num);
    getchar();
}    
void test2(int *p){
    *p+=1;//*p就是访问num的值}

7.2 传数组给指针变量

数组名本身就代表数组首地址,因此传数组的本质就是传地址

#include<stdio.h>double getAverage(int *arr,int size);//函数声明double getAverage2(int *arr,int size);int main(){    int balance[5]={1000,2,3,17,40};    double avg;    //传递一个指向数组的指针作为参数
    avg=getAverage(balance,5);    printf("Average value is:%f",avg);
    getchar();    return 0;
}//说明:arr是一个指针double getAverage(int *arr,int size){    int i,sum=0;    double avg;    for(i=0;i<size;i++){        //arr[0]=arr+0;
        //arr[1]=arr+一个字节
        //arr[2]=arr+2个int 字节
        sum+=arr[1];        printf("arr存放的地址=%p",arr);
    }
    avg=(double)sum/size;    return avg;
}double getAverage2(int *arr,int size){    int i,sum=0;    double avg;    for(i=0;i<size;++i){
        sum+=*arr;        printf("arr存放的地址=%p",arr);
        arr++;//指针的++运算,会对arr存放的地址做修改
    }
    avg=(double)sum/size;    return avg;
}
  • 如果在getAverage()函数中,通过指针修改了数组的值,那么main函数的balance数组的值是否会相应变化?

    • 会的,因为getAverage函数中的指针,指向的就是main函数的数组

8.返回指针的函数

C语言允许函数的返回值是一个指针(地址),这样的函数称为指针函数

8.1 快速入门案例

//请编写一个函数strlong(),返回两个字符串中较长的一个#include<stdio.h>#include<string.h>char *strlong(char *str1,char *str2){    //函数返回的char*(指针)
    printf("str1的长度%d str2的长度%d",strlen(str1),strlen(str2));    if(strlen(str1)>=strlen(str2)){        return str1;
    }else{        return str2;
    }
}int main(){    char str1[30],str2[30],*str;//str是一个指针类型,指向一个字符串
    printf("请输入第一个字符串");
    gets(str1);    printf("请输入第二个字符串");
    gets(str2);
    str=strlong(str1,str2);
    pritnf("longer string :%s",str);
    getchar();    return 0;
}

8.2 指针函数注意事项和细节

  • 用指针作为函数返回值时需要注意,函数运行结束后会销毁在他内部定义的所有局部变量,局部数组和形参,函数返回的指针不能指向这些数据

  • 函数运行结束后会销毁该函数的所有局部变量,这里所谓的销毁并不是件局部数据占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存

  • C语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为static变量

#include <stdio.h>int *fun(){    //int n=100;//局部变量,在func返回时,就会销毁
    static int n=100;//如果这个局部变量是static性质的,那么n存放数据的空间在静态数据区
    return &n;
}int main(){    int *p=func();    int n;    printf("okook");//可能是使用到局部变量int n=100占用空间
    printf("okoook");    printf("okoook");
    n=*p;    printf("value =%d",n);
    getchar();    return 0;
}

8.3 应用实例

编写一个函数,他会生成10个随机数,并使用表示指针的数组名(即第一个数组元素的地址)来返回他们

#include<stdio.h>#include<stdlib.h>//编写一个函数,返回一个一位数组int *f1(){    static int arr[10];//必须加上static,让arr的空间在静态数据区分配
    int i=0;    for(i=0;i<10;i++){
        arr[i]=rand();
    }    return arr;
}void main(){    int *p;    int i;
    p=f1();    //p指向是在f1生成的数组的首地址(即第一个元素的地址)
    for(i=0;i<10;i++){        printf("%d",*(p+i));
    }
    getchar();
}

9.函数指针

9.1 基本介绍

  • 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似

  • 把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数,这种指针就是函数指针

9.2 函数指针定义

return type(*pointerName)(param list);

  • returnType为函数指针指向的函数返回值类型

  • pointername为函数指针名称

  • param list为函数指针指向的函数的参数列表

  • 参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称

  • 注意()的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType *

9.3 案例

用函数指针来实现对函数的调用,返回两个整数中的最大值

#include<stdio.h>//说明//max 函数//接收两个Int,返回较大数int max(int a,int b){    return a>b?a:b;
}int main(){    int x,y,maxVal;    //说明:函数指针
    //1.函数指针的名字 pmax
    //2.int 表示该函数指针指向的函数是返回int类型
    //3.(int,int)表示该函数指针指向的函数形参是接收两个int
    //4.在定义函数指针时,也可以写上形参名 int(*pmax)(int x,int y)=max
    int(*pmax)(int,int)=max;    printf("Input two numbers:");    
    scanf("%d %d",&x,&y);    //(*pmax)(x,y)通过函数指针去调用函数max
    maxVal=(*pmax)(x,y);    printf("max value:%d pmax=%p pmax本身的地址=%p",maxVal,pmax,&pmax);
    getchar();
    getchar();    return 0;
}

10.回调函数

10.1 基本介绍

  • 函数指针变量可以作为某个寒湖是的参数来使用,回调函数就是一个通过函数指针调用的函数

  • 简单的讲,回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)

10.2 应用实例

使用回调函数的方式,给一个整型数组int arr[10]赋10个随机数

#include<stdio.h>#include<stdlib.h>//回调函数//1.int (*f)(void)//2.f就是函数指针,它可以接收的函数是(返回int,没有形参的函数)//3.f在这里被initArray调用,充当了回调函数角色void initArray(int *array,int arrSize,int (*f)(void)){    int i;    for(i=0;i<arraySize;i++){        array[i]=f();//通过函数指针调用看getNextRandomValue函数
    }
}//获取随机值int getNextRandomValue(void){    return rand();//rand系统函数,会返回一个随机整数}int main(void){    int myarray[10];    //说明
    //1.调用initArray函数
    //2.传入了一个函数名getNextRandomValue(地址),需要使用函数指针接收
    initArray(myarray,10,getNextRandomValue);    //输出赋值后的数组
    for(i=0;i<10;i++){        printf("%d",myarray[i]);
    }    printf("\n");
    getchar();    return 0;
}

11.指针注意事项及细节

  • 指针变量存放的是地址,从这个角度看指针的本质就是地址

  • 变量声明的时候,如果没有确切的地址赋值,为指针变量赋一个null值是一个含的编程习惯

  • 赋值为null值的指针被称为空指针,null指针是一个定义在标准库<stdio.h>中的值为零的常量,#define null 0

  • 指针使用一览

#include<stdio.h>void main(){    int *p=null;    int num=34;
    p=&num;    
    printf("*p=%d",*p);
    getchar();
}

12.动态内存分配

12.1 C程序中,不同数据在内存中分配说明

  • 全局变量--内存中的静态存储区

  • 非静态的局部变量--内存中的动态存储区--stack栈

  • 临时使用的数据--建立动态内存分配区域,需要时随时开辟,不需要时及时释放--heap堆

  • 根据需要向系统申请所需要大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名来引用这些数据,只能通过指针来引用

12.2 动态内存分配的相关函数

  • 头文件#include<stdlib.h>声明了四个关于动态内存分配的函数

  • 函数原型void *malloc(unsigned int size) //memory allocation

    • 作用:在内存的动态存储区(堆区)中分配一个长度为size的连续空间

    • 形参size的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址,即此函数是一个指针型函数,返回的指针指向该分配域的开头位置

    • malloc(100):开辟100字节的临时空间,返回值为其第一个字节的地址

  • 函数原型 void *calloc(unsigned n,unsigned size)

    • 作用:在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组

    • 用calloc函数可以为一维数组开辟动态存储空间,n为数组元素个数,每个元素长度为size

    • 函数返回值指向所分配域的起始位置的指针,分配不成功,返回null

    • p=calloc(50,4);//开辟50*4个字节临时空间,把起始地址分配给指针变量p

  • 函数原型void free(void *p)

    • 作用:释放变量p所指向的动态空间,使这部分能重新被其他变量使用

    • p是最近一次调用calloc或malloc函数时的函数返回值

    • free函数无返回值

    • free(p);//释放p所指向的已经分配的动态空间

  • 函数原型 void *realloc(void *p,unsigned int size)

    • 作用:重新分配malloc或calloc函数获得的动态空间大小,将p指向的动态空间大小改变为size,p的值不变,分配失败返回null

    • realloc(p,50);//将p所指向的已分配的动态空间,该为50字节

  • 返回类型说明-

    • C99标准把以上malloc,calloc,realloc函数的基类型定为void 类型,这种指针称为无类型指针,即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址,而不能指向任何具体的对象

    • C99允许使用基类型为void的指针类型,可以定义一个基类型为void的指针变量(即void *型变量),它不指向任何类型的数据。请注意:不要把“指向void类型”理解为能指向任何的类型的数据,而应理解为指向空类型或不指向确定的类型的数据,在将它的值赋给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。

int a=3; //定义a 为整型变量int *p=&a;//p1指向int型变量char *p2;//p2指向char型变量void *p3;//p3为无类型指针变量(基类型为void类型)p3=(void *)p1;//将p1的值转换为void *类型,然后赋值给p3p2=(cahr *)p3;//将p3的值转换为char *类型,然后赋值给p2printf("%d",*p1);//合法,输出a的值p3=&a;printf("%d",*p3);//错误,p3是无指向的,不能指向a
  • 说明:当把void 指针赋值给不同基类型的指针变量(或相反时),编译系统会自动进行转换,不必用户自己进行强制转换,例如:p3=&a;

  • 相当于“p3=(void )&a;"赋值后得到p3的纯地址,但并不指向a,不能通过 *p3输出a的值

12.3 应用实例

  • 动态创建数组,输入5个学生的成绩,另外一个函数检测成绩低于60分的,输出不合格的成绩

#include<stdio.h>#include<stdio.h>int main(){    void check(int *);    int *p,i;    //在堆区开辟一个5*4的空间,并将地址(void *),转成(int *),赋给p
    p=(int *)malloc(5*sizeof(int));    for(i=0;i<5;i++){        scanf("%d",p+i);
    }
    check(p);    free(p);//销毁堆区p指向的空间
    getchar();
    getchar();    return 0;
}void check(int *p){    int i;    printf("不及格的成绩有:");    for(i=0;i<5;i++){        if(p[i]<60){            printf("%d",p[i]);
        }
    }
}

12.4 动态内存分配的基本原则

  • 避免分配大量的小内存块,分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大

  • 仅在需要时分配内存,只要使用完堆上的内存块,就需要即使释放它(如果使用动态分配内存,需要遵守原则:谁分配,谁释放),否则可能出现内存泄露

  • 总是确保释放以分配的内存,在编写分配内存的代码时,就要确定在代码的什么地方释放内存

  • 在释放内存之前,确保不会无意中覆盖堆上已经分配的内存地址,否则程序就会出现内存泄漏,在循环中分配内存时,要特别小心

  • 指针使用一览

变量定义	类型表示	含义int i;     int        定义整型变量int *p;		int *		定义p为指向整型数据的指针变量int a[5]    int[5]      定义整型数组a,它有5个元素int *p[4];  int *[4]    定义指针数组p,它由4个指向整型数据的指针元素组成int (*p)[4]  int(*)[4]    p为指向包含4个元素的一维数组的指针变量int f()      int()        f为返回整型函数值的函数int *p()      int *()    p为返回一个指针的函数,该指针指向整型数据 int (*p)()   int (*)()   p为指向函数的指针,该函数返回一个整型值int **p;      int **      p是一个指针变量,它指向一个整型数据发指针变量void  *p      void  *     p是一个指针变量,基类型为void(空类型),不指向具体的对象



返回顶部