指针
指针是什么?通过地址能找到所需的变量单元,可以说,地址指向该变量单元。打个比方,一个房间的门口挂了一个房间号2021,这个2021就是房间的地址,或者说,2021“指向”该房间。因此,将地址形象化地称为“指针”。意思是通过它能找到以它为地址的内存单元。
指向就是通过地址来体现的。
一个变量的地址称为该变量的“指针”。例如,地址2020是变量i的指针。如果有一个变量专门用来存放另一个变量的地址(即指针),则它称为“指针变量”。
指针变量就是地址变量,用来存放地址,指针变量的值是地址(即指针)。
请区分“指针”和“指针变量”这两个概念。例如,可以说变量i的指针是2020.而不能说i的指针变量是2020,。
指针是一个地址,而指针变量是存放地址的变量。
指针变量
先举一个例子:通过指针变量访问整型变量。
include <stdio.h>
int main()
{
int a=100,b=10;
int *pointer_1, *pointer_2; // 定义指向整型数据的指针变量pointer_1, pointer_2
pointer_1=&a; // 把变量b的地址赋给指针变量pointer_1
pointer_2=&b; // 把变量b的地址赋给指针变量pointer_2
printf("a=%d,b=%d\n", a, b); // 输出变量a和b的值
// 输出变量a和b的值
pointf("pointer_1=%d,pointer_2=%d\n", *pointer_1, *pointer_2);
return 0;
}
程序中两处出现pointer_1和pointer_2,二者的含义不同。程序第5行的pointer_1和pointer_2表示定义两个指针变量pointer_1和pointer_2。它们前面的*
只是表示该变量是指针变量。程序最后一行printf函数中的*pointer_1
和*pointer_2
则代表指针变量pointer_1和pointer_2所指向的变量。
定义指针变量
形式:类型名 * 指针变量名;
如: int * *pointer_1, *pointer_2;
左端的int是在定义指针变量时必须指定的“基类型”。指针变量的基类型用来指定此指针变量可以指向的变量的类型。
说明:在定义指针变量时要注意:
- 指针变量前面的
"*"
表示该变量的类型为指针型变量。- 在定义指针变量时必须指定基类型。
一个变量的指针的含义包含两个方面,一是以存储单元编号表示的地址,一是它指向的存储单元的数据类型
- 如何表示指针类型。指向整型数据的指针类型表示
"int *"
,读作“指向int的指针”或简称“int指针”。- 指针变量中只能存放地址(指针),不要将一个整数赋给一个指针变量。
如:*pointer_1 = 100; // 错误
引用指针变量
-
给指针变量赋值
如:p=&a; // 把a的地址赋给指针变量p
指针变量p的值是变量a的地址,p指向a。 -
引用指针变量指向的变量
printf("%d", *p);
其作用是以整数形式输出指针变量p所指向的变量的值,即变量a的值。
如果有以下赋值语句:*p=1;
表示将整数1赋给p当前所指向的变量,如果p指向变量a,则相当于把1赋给a,即a=1;
-
引用指针变量的值
如:printf("%o",p);
作用是以八进制数形式输出指指针变量p的值,如果p指向了a,就是输出了a的地址,即&a。注意:掌握两个有关的运算符:
(1)&
取地址运算符。&a是变量a的地址。
(2)*
指针运算符,*p代表指针变量p指向的对象。
指针变量作为函数参数
作用是将一个变量的地址传送到另一个函数中。
程序举例:对输入的两个整数按大小顺序输出。先用函数处理,而且用指针类型的数据作为函数参数。
include <stdio.h>
int main()
{
void swap(int *p1, int *p2); // 对swap函数的声明
int a, b;
int *pointer_1, pointer_2; // 定义两个int型的指针变量
printf("pleace enter a and b:");
scanf("%d,%d",&a,&b);
pointer_1=&a; // 使pointer_1指向a
pointer_2=&b; // 使pointer_2指向b
if(a<b) swap(pointer_1, pointer_2); // 如果a<b,调用swap函数
printf("max=%d,min=%d\n", a,b);
return(0);
}
void swap(int *p1, int *p2)
{
int temp;
temp=p1; // 使p1和*p2互换
p1=p2;
*p2=temp;
}
注意:本例采取的方法是交换a和b的值,而p1和p2的值不变。
不能企图通过改变指针形参的值而使指针实参的值改变。
如以下函数是错误的
void swap(int *p1, int *p2)
{
int *p;
p=p1;
p1=p2;
p2=p;
}
指针引用数组
数组元素的指针
所谓数组元素的指针就是元素的地址。
引用数组元素可以用下标法,也可以用指针法,即通过指向数组元素的指针找到所需的元素。
使用指针法能是目标程序质量高(占内存少,运行速度快)
注意:数组名不代表整个数组,只代表数组首元素的地址。
在引用数组元素时指针的运算
可以对指针进行一下运算:
加一个整数(用+或+=),如p+1;
减一个整数(用-或-=),如p-1;
自加运算,如p++,++p;
自减运算,如p–,--p;
两个指针相减。如p1-p2(只有p1和p2都指向同一个数组中元素时才有意义)
- 如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素,
p-1指向同一个数组中的上一个元素。 - 如果p的初值为&a[0],则p+i和a+i就是数组元素a[i]的地址。
*(p+i)
或*(a+i)
是p+i或a+i所指向的数组元素,即a[i]。- 如果指针变量p1和p2都指向同一数组,如执行p2-p1,结果是p2-p1的值(两个地址之差)除以数组元素的长度。
通过指针引用数组元素
- 下标法
- 指针法,如a(a+i)或(p+i)。其中a是数组名,p是指向数组元素的指针变量,其初值p=a.
程序举例:用指针变量指向数组元素,输出数组中的全部元素。
include <stdio.h>
int main()
{
int a[10];
int *p,i;
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(p=a;p<(a+10),p++)
printf("%d", *p); // 用指针指向当前的数组元素
printf("\n");
return(0);
}
请分析以下几种情况(设p指向数组a的首元素(即p=a))
- 分析:
p++;
*p;
p++使p指向下一个元素a[1],然后若再执行*p,则得到下一个元素a[1]的值。
2.*p++;
由于++
和*
同优先级,结合方向为自右向左,因此它等价于*(p++)
先引用p的值,实现*p
的运算,然后再使p自增1。 *(p++)
与*(++p)
的作用不相同。前者是先取*p
的值,然后使p加1,。后者是先使p加1,再取*p
。- ++(*p)。表示p所指向的元素值加1。注意:是元素a[0]的值加1,而不是指针p的值加1.
- 如果p当前指向a数组中第i个元素a[i],则:
*(p--)
相当于a[i–],先对p进行"*"运算,再使p自减。
*(++p)
相当于a[++i],先使p自加,再进行*
运算。
用数组名作函数参数
假设函数的作用是将两个形参(x,y)的值交换,今有以下的函数调用:
swap(a[1], a[2]);
用数组元素a[1]和a[2]作实参的情况,与用变量作实参时一样,是"值传递"方式,将a[1]和a[2]的值单向传递给x和y。实参数组名代表该数组元素的地址,而形参是用来接收从实参传递过来的数组首元素地址的。
以变量名和数组名作为函数参数的比较
实参类型 | 变量名 | 数组名 |
---|---|---|
要求形参的类型 | 变量名 | 数组名或指针变量 |
传递的信息 | 变量的值 | 实参数组首元素的地址 |
通过函数调用能否改变实参的值 | 不能改变实参变量的值 | 能改变实参数组的值 |
说明:C语言调用函数时虚实结合的方法都是采用"值传递"方式,当用变量名作为函数参数时传递的是变量的值,当用数组名作为函数参数时,由于数组名代表的是数组首元素地址,因此传递的值是地址,所以要求形参为指针变量。
注意:实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按指针变量处理。
程序举例:将数组a中n个整数按相反顺序存放。
#include <stdio.h>
int main()
{
void inv(int *x, int n);
int i, a[10]=[3,7,9,11,0,6,7,5,4,2];
printf("The original array:\n");
for(i=0;i<10;i++)
printf("%d", a[i]);
printf("\n");
inv(a,10);
printf("The array has been inverted:\n");
for(i=0;i<10;i++)
printf("%d", a[i]);
printf("\n");
return 0;
}
void inv(int *, int n)
{
int *p, temp,*i,*j,m=(n-1)/2;
i=x;j=x+n-1;p=x+m;
for(;i<=p;i++;j--)
{
temp=*i; // *i与*j交换
*i=*j;
*j=temp;
}
return;
}
归纳分析:如果有一个实参数组,要想在函数中改变此数组中的元素的值,实参与形参的对应关系有以下4种情况:
- 形参和实参都用数组名
- 实参用数组名,形参用指针变量
- 实参形参都用指针变量
- 实参为指针变量,形参为数组名
程序举例:用指针方法对10个整数按由大到小顺序排序。
#include <stdio.h>
int main()
{
void sort(int x[], int, n); // sort函数声明
int i, *p, a[10];
p=a;
printf("please enter 10 integer numbers:");
for(i=0;i<10;i++)
scanf("%d",p++);
p=a;
sort(p,10);
for(p=a,i=1;i<10;i++)
{
printf("%d",*p);
p++;
}
printf("\n");
return 0;
}
void sort(int x[], int n)
{
int i,j,k,t;
for(i=0;i<n-1;i++)
{
k=i;
for(j=i+1;j<=n;j++)
if(x[j]>x[k]) k=j;
if(k!=j)
{t=x[i];x[i]=x[k];x[k]=t;}
}
}
通过指针引用多维数组
1. 多维数组元素的地址
可以认为二维数组是"数组的数组",即二维数组a是由3个一维数组所组成的。
int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};
a[0]和*(a+0)
等价,a[1]和*(a+1)
等价,a[i]和*(a+i)
等价。因此,a[0]+1和*(a+0)+1
都是&a[0][1]
.
务必请记住*(a+i)
和a[i]是等价的。
如果a是二维数组,则a[i]是一维数组名,它只是一个地址,并不代表某一个元素的值。
二维数组a的有关指针
表示形式 | 含义 | 地址 |
---|---|---|
a | 二维数组名,指向一维数组a[0],即0行首地址 | 2000 |
a[0], *(a+0), *a | 0行0列元素地址 | 2000 |
a+1, &a[1] | 1行首地址 | 2016 |
a[1], *(a+1) | 1行0列元素a[1][0] 的地址 | 2016 |
a[1]+2,*(a+1)+2,&a[1][2] | 1行2列元素a[1][2] 的地址 | 2024 |
*(a[1]+2) ,*(*(a+1)+2) ,a[1][2] | 1行2列元素a[1][2] 的值 | 元素值为13 |
再次强调:在指向行的指针前面加一个*
,就转换为指向列的指针。例如,a和a+1是指向行的指针,在它们的前面加一个*
就是*a
和*(a+1)
,它们就成为指向列的指针,分别指向a数组0行0列的元素和1行0列的元素。反之,在指向列的指针前面加&
,就成为指向行的指针。例如a[0]
是指向0行0列的指针,在它前面加一个&
,得&a[0]
,由于a[0]
与*(a+0)
等价,因此&a[0]
与&*a
等价,也就是与a等价,它指向二维数组的0行。
注意:&a[i]
或a+i
指向行,而a[i]
或*(a+i)
指向列。
*(a+i)
只是a[i]
的另一种表示形式,不要简单地认为*(a+i)
是a+i
所指单元中的内容。
2.指向多维数组元素的指针变量
-
指向数组元素的指针变量
程序举例:有一个3乘4的二维数组,要求用指向元素的指针变量输出二维数组各元素的值。
#include <stdio.h> int main() { int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12}; int *p; // p是int *型指针变量 for(p=a[0];p<a[0]+12;p++) //使p依次指向下一个元素 { if((p-a[0])%4==0) printf("\n"); // p移动4次后换行 printf("%4d",*p); } }
计算
a[i][j]
在数组中的相对位置的计算公式为:i*m+j
,其中m为二维数组的列数。在
a[i][j]
元素之前有i
行元素,在a[i][j]
所在行,a[i][j]
的前面还有j
个元素,因此a[i][j]
之前共有i*m+j
。 -
指向m个元素组成的一维数组的指针变量
p先指向a[0],则p+1不是指向
a[0][1]
,而是指向a[1]
,p的增值以一维数组的长度为单位。int a[4];
(a有4个元素,每个元素为整型)int (*p)[4];
表示(*p)
有4个元素,每个元素为整型。
也就是p所指的对象是有4个整型元素的数组,即p是指向一维数组的指针。
要注意指针变量的类型,从
int (*p)[4];
可以看到,p的类型不是int *
型,而是int (*)[4]
型,被p定义为指向一维整型数组的指针变量。一维数组有4个元素,因此p的基类型是一维数组,其长度是16字节。 -
用指向数组的指针作为函数参数
程序举例:有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生的成绩。
#include <stdio.h> int main() { void average(float *p, 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); // 求12个分数的平均分 search(score,2); // 求序号为2的学生的成绩 return 0; } void average(float *p,int n) // 定义求平均成绩的函数 { float *p_end; float sum=0,aver; p_end=p+n-1; for(;p<=p_end;p++) sum=sum+(*p); aver=sum/n; printf("average=%5.2f\n",aver); } void search(float(*p)[4], int n) { int i; printf("The score of No.%d are:\n",n); for(i=0;i<4;i++) printf("%5.2f",*(*(p+n)+i)); printf("\n"); }
注意:实参与形参如果是指针类型,应当注意它们的类型必须一致。不应把
int *
型的指针(即元素的地址)传给int (*)[4]
型的指针变量,反之亦然。
通过指针引用字符串
字符串的引用方式
在C程序中,字符串是存放在字符数组中的。在C语言中只有字符变量,没有字符串变量。想引用一个字符串,可以用以下两种方法。
- 用字符数组存放一个字符串,可以通过数组名和下标引用字符串中的一个字符,也可以通过数组名和格式声明
%s
输出该字符串。 - 用字符指针变量指向一个字符串常量,通过字符指针变量引用字符串常量。
说明:通过字符数组名或字符指针变量可以输出一个字符串,而对一个数值型数组,是不能企图用数组名输出它的全部元素的。
字符指针作函数参数
如果想把一个字符串从一个函数“传递”到另一个函数,可以用地址传递的办法,即用字符数组名作参数,也可以用字符指针变量作参数。在被调用的函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。
程序举例:用字符型指针变量作实参,实现字符串的复制。
#include <stdio.h>
int main()
{
void copy_string(char from[], char to[]);
char a[]="I am a teacher.";
char b[]="You are a student.";
char *from=a,*to=b;
printf("string a=%s\nstring b=%s\n",a,b);
printf("\ncopy string a to string b:\n");
copy_string(from,to);
printf("string a=%s\nstring b=%s\n", a,b);
return 0;
}
void copy_string(char from[], char to[])
{
int i=0;
while(from[i]!='\0')
{
to[i]=from[i];
i++;
}
to[i]='\0';
}
调用函数时实参与形参的对应关系
实参 | 形参 |
---|---|
字符数组名 | 字符数组名 |
字符数组名 | 字符指针变量 |
字符指针变量 | 字符指针变量 |
字符指针变量 | 字符数组名 |
使用字符指针变量和字符数组的比较
主要有一下几点:
-
字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址,绝不是将字符串放到字符指针变量中。
-
赋值方式。可以对字符指针变量赋值,但不能对数组名赋值。
-
初始化的含义。
对字符指针变量赋初值:
char *a="I love China!"
;等价于:
char *a;
a="I love China!;"
而对数组的初始化:
char str[14]="I love China!;"
不等价于
char str[14;]
str[]="I love China!;"
// 企图把字符串赋给数组中各元素,错误 -
存储单元的内容。编译时为字符数组分配若干存储单元,以存放个元素的值,而对字符指针变量,只分配一个存储单元(4字节)。
-
指针变量的值是可以改变的,而数组名代表一个固定的值,不能改变。
-
字符数组中各元素的值是可以改变的,但字符指针变量指向的字符串变量中的内容是不可以被取代的。
-
引用数组元素。对字符数组可以用下标法,也可以用地址法。
-
用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。
例如:
char *format; format="a=%d,b=%f\n"; // 是format指向一个字符串 printf(format,a,b);
它相当于:
printf("a=%d,b=%f\n",a,b);
因此只要改变指针变量format所指向的字符串,就可以改变输出输入的格式。这种printf函数称为可变格式输出函数。
也可以用字符数组实现。例如:
char format[]="a=%d,b=%f\n"; printf(format,a,b);
但使用字符数组时,只能采用在定义数组时初始化或逐个对元素赋值的方法,而不能用赋值语句对数组整体赋值。
指向函数的指针
函数指针
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址称为这个函数的指针。
例如:int (*p)(int,int);
定义p是一个指向函数的指针变量,它可以指向函数的类型为整型且有两个整型参数的函数。p的类型用int (*)(int int)
表示。
用函数指针变量调用函数
(1)通过函数名调用函数
程序举例:用函数求整数a和b中的大者。
#include <stdio.h>
int main()
{
int max(int,int); // 函数声明
int a,b,c;
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
c=max(a,b); // 通过函数名调用max函数
printf("a=%d\nb=%d\nmax=%d\n",a,b,c);
return 0;
}
int max(int x, int y)
{
int z;
z=x>y?x:y;
return(z);
}
(2)通过指针变量访问它所指向的函数
#include <stdio.h>
int main()
{
int max(int,int); // 函数声明
int (*p)(int,int); // 定义指向函数的指针变量p
int a,b,c;
p=max; // 使p指向max函数
printf("please enter a and b:");
scanf("%d,%d",&a,&b);
c=(*p)(a,b); // 通过指针变量调用max函数
printf("a=%d\nb=%d\nmax=%d\n",a,b,c);
return 0;
}
int max(int x, int y)
{
int z;
z=x>y?x:y;
return(z);
}
赋值语句"p=max"
的作用是将函数max的入口地址赋给指针变量p。
定义和使用指向函数的指针变量
定义指向函数的指针变量的一般形式为:**类型名 (*指针变量名)(函数参数表列);
这里的“类型名”是指函数返回值的类型。
要注意的是:由于优先级的关系,"*指针变量名"
要用括号括起来。
说明:
- 定义指向函数的指针变量,并不意味这这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。
- 如果要用指针调用函数,必须先使指针变量指向该函数。
- 对指向函数的指针变量不能进行算术运算,如p+n,p++等运算是无意义的。
- 用函数名调用函数,只能调用所指定的一个函数,而通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数。
用指向函数的指针作函数参数
指向函数的指针变量的一个重要用途是把函数的地址作为参数传递到其他函数。
指向函数的指针可以作为函数参数,把函数的入口地址传递给形参,这样就能在被调用的函数中使用实参函数。它的原理可以简述如下:有一个函数(假设函数名为fun),它有两个形参(x1和x2),定义x1和x2为指向函数指针变量。在调用函数fun时,实参为两个函数名f1和f2,给形参传递的是函数f1和f2的入口地址。这样在函数fun就可以调用f1和f2函数了。
返回指针值的函数
一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址。
形式:类型名 * 函数名(参数表列);
例如:"int *a(int x,int y);"
,a是函数名,调用它以后能得到一个int *
型的指针,即整型数据的地址。
程序举例:有a个学生,每个学生有b门课程的成绩。要求在用户输入学生序号后,能输出该学生的全部成绩。
#include <stdio.h>
int main()
{
float score[][4]={{60,70,80,90}.{56,89,67,88},{34,78,90,66}};
float *search(float (*pointer)[4],int n);
float *p;
int i,k;
printf("enter the number of student:");
scanf("%d",&k);
printf("The score of No.%d are:\n",k);
p=search(score,k);
for(i=0;i<4;i++)
printf("%5.2f\t",*(p+i));
printf("\n");
return0;
}
float *search(float (*pointer)[4], int n) // 形参pointer是指向一维数组的指针变量
{
float *pt;
pt= * (pointer+n); // pt的值是&score[k][0]
return(pt);
}
指针数组和多重指针
一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。
形式:类型名 * 数组名[数组长度];
例如:int *p[4];
由于[]
比*
优先级高,因此p先与[4]
结合,形成p[4]形式,表示p数组有4个元素。
程序举例:将若干字符串按字母顺序(有小到大)输出。
#include <stdio.h>
#include <string.h>
int main()
{
void sort(char *name[],int n);
void print(char *name[], int n);
char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
int n=5;
sort(name,n);
print(name,n);
return 0;
}
void sort(char *name[], int n)
{
char *temp;
for(i=0;i<n-1;i++) // 用选择法排序
{
k=i;
for(j=i+1;j<n;j++)
if(strcmp(name[k],name[j])>0) k=j;
if(k!=i)
{temp=name[i];name[i]=name[k];name[k]=temp;}
}
}
void print(char *name[], int n)
{
int i;
char *p;
p=name[0];
while(i<n)
{
p=*(name+i++);
printf("%s\n",p);
}
}
其中*(name+i++)
表示先求*(name+i)
的值,即name[i]
,然后使i加1。
指向指针数据的指针
指向指针数据的指针变量,简称为指向指针的指针。
例如:定义一个指向指针数据的指针变量:char **p;
说明:指针数组的元素也可以不指向字符串,而指向整型数据或实型数据等。
例如:
int a[5]={1,3,5,7,9};
int *num[5],i;
int **p;
for(i=0;i<5;i++)
num[i]=&a[i];
为了得到a[2]中的数据5,可以先使p=num+2
,然后输出**p
。注意*p
是num[2]的值,而num[2]的值是a[2]的地址,因此**p
是a[2]的值5。
指针数组的元素只能存放地址,不能存放整数。
例如:
int a[5]={1,3,5,7,9};
int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
不能写成:int *num[5]={1,3,5,7,9};
利用指针变量访问另一个变量就是"间接访问"。如果在一个指针变量中存放一个目标变量的地址,这就是"单级间址"。指向指针数据的指针用的是"二级间址"方法。
指针数组作main函数的形参
指针数组的一个重要应用的是作为main函数的形参。以往main函数的第一行一般写成以下形式:
int main()
或int main(void)
,表示main函数没有参数,调用main函数时不必给出实参。
在某些实际情况下,main函数可以有参数,例如:int main(int argc,char *argv[])
其中,argc
和argv
就是main函数的形参,它们是程序的"命令行参数"。argc
(argument count的缩写,意思是参数个数),argv
(argument vector缩写,意思是参数向量),它是一个* char
指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串。
main函数时操作系统调用的,实参只能由操作系统给出。
命令行的一般形式为:命令名 参数1 参数2···参数n
命令名和各参数之间用空格分隔。命令名是可执行文件名。
许多操作系统提供了echo命令,它的作用是实现"参数回送",即将echo后面的各参数(各字符串)在同一行上输出。
实现"参数回送"的C程序如下:
#include <stdio.h>
int main(int argc,char * argv[])
{
while(--argc>0) // 当命令行的参数多多于1
printf("%s%c",*++argv,(argc>1)?' ':'\n'); // 从第2个参数开始输出各字参数
return 0;
}
动态内存分配与指向它的指针变量
内存的动态分配
全局变量是分配在内存中的静态存储区,非静态的局部变量是分配在内存中的动态存储区的,这个存储区是一个称为栈。除此之外,C语言还允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放。这些数据是临时存放在一个特别的自由存储区,称为堆。可以根据需要,向系统申请所需大小的空间。由于未在声明部分定义它们为变量或数组,因此不能通过变量名或数组名去引用这些数据,只能通过指针来引用。
建立内存的动态分配
对内存的动态分配是通过系统提供的库函数来实现的,主要有malloc,calloc,free,realloc。
1.使用malloc函数
函数原型为:void * malloc(unsigned int size);
其作用是在内存的动态存储区中分配一个长度为size的连续空间。此函数的值是所分配区域的第一个字节的地址,或者说,此函数是一个指针型函数,返回的指针指向该分配域的开头位置。
注意指针的基类型为void,即不指向任何类型的数据,只提供一个地址。如果此函数未能成功地执行,则返回空指针(NULL)。
2.使用calloc函数
函数原型为:void *calloc(unsigned n,unsigned size);
其作用是在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。
n为数组元素个数,每个元素长度为size。这就是动态数组。函数返回指向所分配域的起始位置的指针;如果分配不成功,返回NULL。
3.使用free函数
函数原型为:void free(void *p);
其作用是释放指针变量p所指向的动态空间,使这部分空间能重新被其他变量使用。p应是最近一次调用calloc或malloc函数时得到的函数返回值。
如:free(p);
释放指针变量p所指向的已分配的动态空间
free函数无返回值。
4.使用realloc函数
函数原型为:void *realloc(void *p,unsigned int size);
如果已经通过malloc函数或calloc函数获得了动态空间,想改变其大小,可以用realloc函数重新分配。
如:realloc(p, 50);
将p所指向的已分配的动态空间改为50字节
注意:以上4个函数的声明在stdlib.h
头文件中,在用到这些函数时应当用"#include <stdlib.h>"
指令吧stdlib.h头文件包含到文件中。
C99标准把以上函数的基类型定位void
类型,这种指针称为无类型指针,即不指向哪一种具体的类型数据,只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址,而不能指向任何具体的对象。
void指针类型
请注意,不要把"指向void类型"理解为能指向"任何的类型",而应理解为"指向空类型"或"不指向确定的类型"的数据。
说明:当把void指针赋值给不同基类型的指针变量时,编译系统会自动进行转换,不必用户自己进行强制转换。例如:p=&a;
相当于p=void(* ) &a;
,赋值后p得到a的纯地址,但并不指向a,不能通过*p输出a的值。
程序举例:建立动态数组,输入5个学生的成绩,另外用一个函数检查其中有无低于60分的,输出不合格的成绩。
#include <stdio.h>
#include <stdlib.h>
int main()
{
void check(int * ); // 函数声明
int *p,i;
// 开辟动态内存区,将地址转换为int *型,然后放在p1中
p1=(int * )malloc(5 * sizeof(int));
for(i=0;i<5;i++)
scanf("%d",p1+i);
check(p1);
return 0;
}
void check(int *p) // 定义check函数,形参为int *指针
{
int i;
printf("They are fail:");
for(i=0;i<5;i++)
if(p[i]<60) printf("%d",p[i]);
printf("\n");
}
指针小结
-
首先要准确地弄清楚指针的含义。指针就是地址,凡是出现"指针"的地方,都可以用"地址"代替。
要区别指针和指针变量。指针就是地址本身,而指针变量是用来存放地址的变量。正确的说法是指针变量的值是一个地址。
-
什么叫"指向"?地址就意味着指向,因为通过地址能找到具有该地址的对象。对于指针变量来说,把谁的地址存放在指针变量中,就说此指针变量指向谁。但应注意:只有与指针变量的基类型相同的数据的地址才能存放在相应的指针变量中。
-
要深入掌握在对数组的操作中正确地使用指针,搞清楚指针的指向。一维数组名代表数组首元素的地址。
-
有关指针变量的归纳比较
指针变量的类型及含义
变量含义 | 类型表示 | 含义 |
---|---|---|
int i; | int | 定义整数变量i |
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是一个指针变量,它指向一个指向整型数据的指针变量 |
-
指针运算
一、指针变量加(减)一个整数。
例如:p++,p–,p+i.p+=i等均是指针变量加(减)一个整数。
将该指针变量的原值和它指向的变量所占用的存储单元的字节数相加减。
二、指针变量赋值
将一个变量地址赋给一个指针变量。
例如:
p=&a
(将变量a的地址赋给p)p=&array[i]
(将数组array的第i个元素的地址赋给p)p=max
(max为已定义的函数,将max的入口地址赋给p)注意:不应把一个整数赋给指针变量。
三、两个指针变量可以相减。
如果两个指针变量都指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数。
四、两个指针变量比较。
如果两个指针指向同一个数组的元素,则可以进行比较。不指向同一数组则比较无意义。
-
指针变量可以有空值,即该指针变量不指向任何变量,可以这样表示:
p=NULL;
,其中NULL是一个符号常量,代表整数0。 -
指针的优点:一、提高程序效率。二、在调用函数时当指针指向的变量的值改变时,这些值能够为主调函数使用,即可以从函数调用得到多个可改变的值。三、可以实现动态存储分配。