动态内存分配
动态变量
所谓的动态变量是指:在写程序时无法确定它们的存在,只有当程序运行起来,随着程序的运行,根据程序的需求动态产生和消亡的变量。因此,动态变量的访问需要通过指向动态变量的指针变量来进行间接访问。当程序运行结束时,系统会自动回收指针占用的空间,但并不会回收指针指向的动态变量的空间,动态变量的空间需要程序员在程序中释放。因此,要实现动态内存分配,程序设计语言必须提供3个功能:
- 定义指针变量。
- 申请动态变量的空间。
- 回收动态变量的空间。
动态变量的创建
1.malloc函数
malloc创建一个动态变量。
语法:
void *malloc(unsigned int size);
其作用是在内存称为堆(heap)的区域申请一块长度为size的空间,函数的返回值是这块空间的首地址。如果函数没有成功地申请到空间,则返回值为空指针NULL。
如:
int *p1;
p1=(int *)malloc(sizeof(int));
double *p2;
p2=(double *)malloc(sizeof(double));
//一旦申请成功,就可以通过间接地方式访问动态变量。
*p1=5;
*p2=*p1+0.5;
2.calloc函数
calloc用于创建一个动态数组。
语法:
void *calloc(unsigned n,unsigned size);
其作用是申请n个长度为size的连续空间,即申请n个元素的数组,每个数组元素均占用size个字节。函数返回这块连续空间的起始地址。如果申请失败,返回空指针NULL。如:
int *array;
array=(int *)calloc(10,sizeof(int)); //此时,array指向这块空间的首地址,相当于一个数组名,可以通过下标变量形式访问该动态数组。
动态数组和普通数组的最大区别在于,它的规模可以是程序运行过程中某一变量的值或某一表达式的计算结果,而普通数组的长度必须是在编译时就能确定的常量。
动态变量的消亡
一旦申请了动态变量,如果不明确给出指令是不会消亡的。要回收动态变量的空间,就必须显示地使之消亡。
回收某个动态变量,可以调用函数free。语法如下:
// void free(void *p);
double *p=(double *)malloc(sizeof(double));
free (p);
一旦释放了内存区域,堆管理器将重新收回这些区域。如果再间接访问这块空间,将导致程序异常终止。
内存泄漏
所谓的内存泄漏是指用动态变量机制申请了一个动态变量,而后不再需要这个动态变量时没有释放它;或者把一个动态变量的地址放入一个指针变量,而在此变量没有释放之前又让指针指向另一个动态变量。这样原来那块空间就丢失了。堆管理器认为程序在继续使用它们,而程序又不知道它们在哪里。为了避免出现这种情况,在动态变量不再使用时,应该用free函数明白地告诉堆管理器这些区域不再使用。
查找malloc和calloc的失误
由于计算机的空间内存是有限的,堆空间最终也可能耗尽,此时malloc和calloc就会失败,为保险起见,在调用它们之后最好检查一下操作是否成功。可根据返回值来确定。如果成功,返回申请到的堆空间的一个地址;如果不成功,则返回一个空指针。
#include<stdio.h>
int main(){
int *p;
p=(int *)malloc(sizeof(int));
if(p==NULL){
printf("allocation failure!");
return 1;
}
*p=20;
printf("%d\n",*p);
free(p);
return 0;
}
也可以利用assert宏直接退出程序。
#include<stdio.h>
#include<assert.h>
int main(){
int *p,i;
p=(int *)calloc(10,sizeof(int));
assert(p!=0);
for(i=0;i<10;i++)
p[i]=2*i;
for(i=0;i<10;++i)
printf("%d\t",p[i]);
free(p);
return 0;
}
动态变量应用
案例1:统计某次考试的平均成绩和均方差。
#include<stdio.h>
#include<math.h>
int main(){
int *score,num,i;//score为存放成绩的数组名
double average=0,variance=0;//average为平均数,variance为方差
printf("请输入参加考试的人数:");
scanf("%d",&num);
score=(int *)calloc(num,sizeof(int));
printf("请输入成绩:");
for(i=0;i<num;++i)
scanf("%d",&score[i]);
for(i=0;i<num;++i)
average+=score[i];
average/=num;
for(i=0;i<num;++i)
variance+=(average-score[i])*(average-score[i]);
variance=sqrt(variance)/num;
free(score);
printf("平均分:%f\n方差:%f\n",average,variance);
return 0;
}
案例2:计算两个n维向量的数量积。
#include<stdio.h>
int main(){
int i,scale;
double result=0,*v1,*v2;
printf("请输入向量的维数:");
scanf("%d",&scale);
v1=(double *)calloc(scale,sizeof(double));
v2=(double *)calloc(scale,sizeof(double));
printf("请输入第一个向量的%d个分量:",scale);
for(i=0;i<scale;++i){
scanf("%lf",&v1[i]);
}
printf("请输入第二个向量的%d个分量:",scale);
for(i=0;i<scale;++i){
scanf("%lf",&v2[i]);
}
for(i=0;i<scale;++i)
result+=v1[i]*v2[i];
printf("向量的数量积是:%f\n",result);
free(v1);
free(v2);
return 0;
}
result=(1*3)+(2*4)=3+8=11
指针与字符串
用指向字符的指针变量表示字符串
C语言中的字符串有两种基本表示:字符数组表示和指向字符的指针表示。
字符串作为函数的参数
字符串的本质上是用一个字符数组来存储的。
当传递的是一个字符串,通常使用指向字符的指针。
字符串有一个特定的结束标志‘\0’。
传递一个字符串只需要一个参数,即指向字符串中第一个字符的指针,而不需要指出字符串的长度。
案例1:编写一个统计字符串中单词个数的函数。
#include<stdio.h>
int Number(char *s)
{
int count=0;
while(*s!='\0'){
while(*s==' ')
++s;
if(*s!='\0'){
++count;
while(*s != ' '&& *s != '\0'){
++s;
}
}
}
return count;
}
int main(){
char *s="This is my English teacher";
printf("该字符串的单词个数为:%d",Number(s));
return(0);
}
案例2:实现string库中的strcmp函数。
【解释:C 库函数 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。】
#include<stdio.h>
int strcmp(char *s1,char *s2){
while(*s1 && *s2){
if(*s1>*s2) return 1;
if(*s1<*s2) return -1;
++s1;
++s2;
}
if(*s1=='\0'&&*s2=='\0') return 0;
if(*s1=='\0') return -1;else return 1;
}
int main(){
char *s1="abc",*s2="cde";
int cmp;
cmp=strcmp(s1,s2);
if(cmp){
printf("%s%s%s\n",s1,(cmp>0)?"大于":"小于",s2);
}else{
printf("%s等于%s\n",s1,s2);
}
cmp=strcmp(s1,s1);
if(cmp){
printf("%s%s%s\n",s1,(cmp>0)?"大于":"小于",s1);
}else{
printf("%s等于%s\n",s1,s1);
}
return(0);
}
返回字符串的函数
字符串常用的表示方法是采用指向字符的指针。返回一个字符串的函数的返回值可以定义为指向字符的指针。
案例:设计一个函数从一个字符串中提取一个子串。
#include<stdio.h>
char *subString(char *s, int start,int end)
{
int len;
char *sub;
for(len=0;s[len]!='\0';++len);
if(start<0 || start >=len || end<0 || end>=len ||start >end) return NULL;
sub=(char *)malloc(end-start+2);
for(len=start;len<=end;++len)
sub[len-start]=s[len];
sub[end-start+1]='\0';
return sub;
}
int main(){
char *s1="abcdefg";
char *sub;
sub=subString(s1,2,5);
printf("%s的第2个字符到第5个字符是%s是\n",s1,sub);
if(sub) free(sub);
return(0);
}