C指针
指针
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 *类型,然后赋值给p3
p2=(cahr *)p3;//将p3的值转换为char *类型,然后赋值给p2
printf("%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(空类型),不指向具体的对象

浙公网安备 33010602011771号