概述
善于利用指针
- 一、指针是什么
- 二、指针变量
- 指针变量作为函数参数
- 三、通过指针引用数组
- 数组元素的指针
- 在引用数组元素时的指针运算
- 利用指针引用数组元素
- 用数组名作函数参数
- 以变量名和数组名作为函数参数的比较
- 通过指针引用多维数组
- 多维数组的地址
- 指向多维数组元素的指针变量
- 四、通过指针引导字符串
- 字符串的引用方式
- 五、指向函数的指针
- 什么是函数的指针
- 用函数指针调用函数
- 用指向函数的指针作为函数的参数
- 六、返回函数指针的函数(指针函数)
- 七、指针数组和多重指针
- 八、动态内存分配与指向它的指针变量
- 建立内存的动态分配
- void指针
一、指针是什么
内存区的每一个字节都有一个编号,这就是“地址”。通过地址能找到所需的变量单元,可以说地址指向该变量单元。形象化的将地址称为“指针”
数据是分类型的,对不同类型的数据,在内存中分配的存储单元的大小(字节数)和存储方式是不同的(如整数是以二进制补码的形式存放,实数则以指数形式存放)
C语言中的地址包括位置信息(内存编号、或称纯地址)和它所指向的数据的类型信息,或者说是“带类型的地址”
指针和指针变量是两个概念:指针是一个地址,而指针变量是存放地址的变量。
二、指针变量
所有的指针类型存储的都是内存地址,内存地址都是一个无符号十六进制整形数。(32位操作系统占4字节,64位操作系统占8字节)
定义指针变量:*类型名 指针变量名;左边的类型名是“基类型”,用来指定此指针变量可以指向的变量的类型。
指针变量作为函数参数
实例:按从大到小的顺序输出三个整数
#include<stdio.h>
int main()
{
void sort(int *a,int *b,int *c);
int a,b,c;
printf("please enter three numbers:");
scanf("%d%d%d",&a,&b,&c);
sort(&a,&b,&c);
printf("The sorted numbers are:%d,%d,%d",a,b,c);
return 0;
}
void sort(int *a,int *b,int *c) //形参是指针变量,定义sort函数,对三个整数进行排序
{
void swap(int *x,int *y);
if(*a<*b)
swap(a,b);
if(*a<*c)
swap(a,c);
if(*b<*c)
swap(b,c);
}
void swap(int *x,int *y) //形参是指针变量,定义swap函数交换*x和*y的值
{
int temp;
//整型变量temp作为临时辅助变量实现*x和*y的值交换
temp=*x;
*x=*y;
*y=temp;
}
/*
void swap(int *x,int *y)
{
int *temp;
//*temp是指针变量temp所指向的值,未给temp赋值(野指针),*temp指向的值不可预见
*temp=*x;
//对*temp赋值是向一个未知的储存单元赋值,这个储存单元里面可能存着一个有用的数据,可能会破坏系统的正常工作状态
*x=*y;
*y=*temp;
}
void swap(int *x,int *y) //不能企图改变指针形参的值而改变指针实参的值
{
int *temp;
temp=a;
a=b;
b=temp
}
*/
三、通过指针引用数组
数组元素的指针
数组元素的指针就是数组元素的地址,引用数组元素可以用下标法(如a[3]),也可以用指针法(通过数组元素的指针找到所需的元素)
数组名不能代表整个数组,只代表数组中首元素的地址,是一个指针型常量。
在引用数组元素时的指针运算
在指针已指向一个数组元素时可以进行以下运算
- 加一个整数(用+或+=),如p+1;
- 减一个整数(用-或-=),如p-1;
- 自加运算,如p++,++p
- 自减运算,如p–,–p
- 两个指针相减,如p1-p2(只有p1和p2都指向同一数组中的元素时才有意义)
- 数组元素是float型,每个元素占四字节。p+1意味着使p的值(是地址)加四个字节,以使他指向下一个元素。
- p2-p1的结果是:p2-p1的值(两个地址之差)初以数组元素的长度
利用指针引用数组元素
设p开始时指向数组a的首元素(即p=a)
-
p++; *p;
p++使p指向下一元素a[1],然后在执行*p,则得到下一个元素a[1]的值
-
*p++
由于*和++同级优先,结合方向为自右向左,因此等价于*(p++)
for(i=0;i<10;i++,p++) printf("%d",*p) 等价于: for(i=0;i<10;i++) printf("%d",*p++)
作用都是先输出*p的值,再使p加1
-
*(p++)和*(++p)作用是不一样的,*(p++)是先取p的值,再使p加1。*(++p)是先使p加1,再取*p
-
*(arr+i)和arr[i]是无条件等价的
用数组名作函数参数
函数的形参会退化为指针,失去精度(不知道数组元素个数了)
fun(int arr[],int n)
{
……
}
等价于
fun(int *arr,int n)
{
……
}
以变量名和数组名作为函数参数的比较
实参类型 | 变量名 | 数组名 |
---|---|---|
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组首元素的地址 |
通过函数调用是否能改变实参的值 | 不能改变实参变量的值 | 能改变实参数组的值 |
例:将数组arr中的n个整数按相反顺序存放
#include<stdio.h>
int main()
{
void exchange(int arr[],int n);
int arr[]={10,9,8,7,6,5,4,3,2,1};
int i;
printf("The original array:n");
for(i=0;i<10;i++)
printf("%d ",arr[i]);
exchange(arr,10);
//数组名是数组首元素地址,注意arr不是指针变量,是一个指针类型常量
printf("nThe array have been exchanged:n");
for(i=0;i<10;i++)
printf("%d ",arr[i]);
return 0;
}
void exchange(int arr[],int n) //exchange函数中的n用来接收需要处理的元素的个数,形参用数组名表示
//函数原型还可以写成void exchange(int *arr,int n),形参用指针变量表示
{
int i,j,temp;
for(i=0,j=9;i<10;i++,j--)
{temp=0;
if(i<j)
{
temp=arr[i];
arr[i]=arr[j];
arr[j]=temp;
}else
break;
}
}
如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参有以下四种对应关系。(实际上都是地址的传递)
- 实参和形参都用数组名
- 实参用数组名,形参用指针变量
- 实参形参都用指针变量
- 实参为指针变量,形参为数组名
例:用指针的方法对十个整数按由大到小的顺序排序
#include<stdio.h>
#define N 10
int main()
{
void sort(int *x,int n);
int arr[N]={6,3,9,2,5,7,4,8,1,10};
int i,*p;
p=arr;
//指针变量指向a[0]
printf("n The original numbers are:n");
for(i=0;i<10;i++)
printf("%d ",arr[i]);
sort(p,N);
printf("n The sorted numbers are:n");
for(i=0;i<10;i++)
printf("%d ",arr[i]);
return 0;
}
void sort(int x[],int n) //定义sort函数,x是形参数组名
{
int i,j,temp;
for(i=0;i<N-1;i++)
{temp=0;
for(j=0;j<N-i-1;j++)
{
if(x[j]<x[j+1])
{
temp=x[j];
x[j]=x[j+1];
x[j+1]=temp;
}
}
}
}
通过指针引用多维数组
多维数组的地址
int a[3][4]={{1,3,5,7},{9,11,13,17},{19,23,27,31}};
a是二维数组名。a包含3行,即3个行元素:a[0],a[1],a[2]。每个行元素又是一个一维数组,它包含4个元素(即4个列元素),可以认为二维数组是“数组的数组”。
从二维数组角度看a代表的是二维数组首元素的地址,a+1代表序号为1的行的起始地址(即a[1]),a+2代表a[2]的起始地址。假设a=2000,则a+1=a[1]=2016,a+2=a[2]=2032
a[0],a[1],a[2]是一维数组名,代表首元素地址。
a[0]+0,a[0]+1,a[0]+2,a[0]=+3 分别是a[0][0],a[0][1],a[0][2],a[0][3]元素的地址(即&a[0][0],&a[0][1],&a[0][2],&a[0][3])
a[0]和*(a+0)等价,a[1]和*(a+1)等价,a[i]和*(a+i)等价。因此a[0]+1和*(a+0)+1等价都是&a[0][1]
(a[0]+1)是a[0][1]的值,同理*(*(a+0)+1)或*(*a+1)也是a[0][1]的值,即**(a[i]+j)或*(*(a+i)+J)是a[i][j]的值**
**C语言的地址信息中既包含位置信息(内存编号),还包含他所指向的数据的类型信息。**a[0]是一维数组名,它是一维数组中起始元素的地址;a是二维数组名,它是二维数组首行起始地址,二者的纯地址是相同的,但它们的基类型是不同的,即它们所指向的数据的类型不同,前者是整型数据,后者是一维数组。如果用一个指针变量pt来指向此一维数组,应当这样定义:int (*pt)[4];
在二维数组中,a+i,a[i],*(a+i),&a[i],&a[i][0]的值相同,都表示同一地址,但基类型不同。
例:输出二维数组有关数据(地址和元素的值)
#include<stdlib.h>
int main()
{
int a[3][4]={{1,3,5,7},{9,11,13,17},{19,23,27,31}};
printf("%d,%dn",a,*a);
//0行起始地址和0行0列元素地址
printf("%d,%dn",a[0],*(a+0));
//0行0列元素地址
printf("%d,%dn",&a[0],&a[0][0]);
//0行起始地址和0行0列元素地址
printf("%d,%dn",a[1],a+1);
//1行0列元素地址和1行起始地址
printf("%d,%dn",&a[1][0],*(a+1)+0); //1行0列元素地址
printf("%d,%dn",a[2],*(a+2));
//2行0列元素地址
printf("%d,%dn",&a[2],a+2);
//2行起始地址
printf("%d,%dn",a[1][0],*(*(a+1)+0)); //1行0列元素的值
printf("%d,%dn",*a[2],*(*(a+2)+0)); //2行0列元素的值
return 0;
}
/*结果是:
6422000,6422000
6422000,6422000
6422000,6422000
6422016,6422016
6422016,6422016
6422032,6422032
6422032,6422032
9,9
19,19
*/
指向多维数组元素的指针变量
-
指向数组元素的指针变量
int *p;
-
指向由m个元素组成的一维数组的指针变量
int (*P)[m];
#include<stdio.h>
int main()
{
int a[4]={1,3,5,7};
//定义一维数组a,里面包含4个元素
int (*p)[4];
//定义指向包含4个元素的一维数组的指针变量
p=&a;
//使p指向一维数组,注意不要写成p=a;这样写p的值是&a[0],指向首元素a[0]
printf("%dn",(*p)[3]);//输出a[3],输出整数7
return 0;
}
- 用指向数组的指针作函数参数
用指针变量作为形参,以接受实参数组名传递来的地址。有两种方法:1、用指向变量的指针变量。2、用指向一维数组的指针变量。
#include<stdio.h>
int main()
{
void average(float *arr,int n);
void search(float (*p)[4],int n);
float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};
average(*score,12);
//*score即score[0],也就是&score[0][0]
search(score,2);
return 0;
}
void average(float *arr,int n) //形参是指向整型变量的指针变量
{
float *p_end;
float sum=0,aver;
p_end=arr+n-1;
for(;arr<=p_end;arr++)
sum=sum+(*arr);
aver=sum/n;
printf("average=%5.2fn",aver);
}
void search(float (*p)[4],int n) //形参p的基类型是float (*)[4]
{
int i;
printf("The score of No.%d are:n",n);
for(i=0;i<4;i++)
printf("%5.2f ",*(*(p+n)+i)); //*(*(p+n)+i)是search[n][i]的值
printf("n");
}
四、通过指针引导字符串
C语言只有字符变量,没有字符串变量。字符串是存放在字符数组中的。还要注意的是字符串是以’ ’结束
字符串的引用方式
1、字符数组存放一个字符串,可以通过数组名和下标引用字符串中的一个字符,也可以通过数组名和格式声明“%s”输出该字符串。
字符指针做函数参数:实参和形参可以是指针变量也可以是字符数组名
例:函数调用实现字符串的复制
#include<stdio.h>
int main()
{
void copy_string(char x[],char b[]);
char a[]="I am chinese!";
char b[]="I love china!";
printf("string a:%snstring b:%sn",a,b);
copy_string(b,a);
printf("ncopy staing b to string a:n");
printf("string a:%snstring b:%sn",a,b);
return 0;
}
void copy_string(char from[],char to[]) //等效于void copy_string(char *from,char *to)
{
int i=0;
while (from[i]!='