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的地址赋给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=# 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(空类型),不指向具体的对象