前言:
本篇文章讨论的不定输入,是旨在研究“任意个数的字符或整型都可以被成功读取,并存放在数组中”。也就是想输入多少就输入多少,且成功读取的 “输入方式”。
在前文中我们,探讨了字符了字符字符的不定输入,以下为链接:
字符的不定输入之正则输入和gets( )函数-CSDN博客文章浏览阅读75次,点赞3次,收藏2次。本篇文章讨论的不定输入,是旨在研究“任意个数的字符或整型都可以被成功读取,并存放在数组中”。也就是想输入多少就输入多少,且成功读取的 “输入方式”。https://blog.csdn.net/Isaiah_Cohen/article/details/136415024?spm=1001.2014.3001.5501那么该怎么完成整型的不定输入呢?今天,我们就关于这个做一个简单的分析。
闲言少叙,书归正传,我们来看一看代码。
一、静态数组
代码
int main()
{
int a[70];
int size=0;
char ch;
do{
scanf("%d",&a[size++]);
}while((ch=getchar())!='\n');
for(int i=0;i<size;i++)
{
printf("%d ",a[i]);
}
return 0;
}
基本思路:
首先,我们预估一个大致的容量,比如上述代码中的“70”,就是预估出来的大小。有了预估的容量之后,我们需要定义一个size,来表示数组中元素的多少。当每读取一个整型字符,变量size++,时刻保证size与数组元素个数的一致相等。
此外,由于scanf("%d",&a[size++])只能读取整型,输入的空格和换行符'\n',被保留在缓存区中,所以基于此我们可以定义一个字符变量 ch=getchar( ),读取缓存区中的空格和换行符。
若是空格则继续读取,若为换行符则结束循环。
不过,要值得注意的是,因为getchar( )和scanf( )读取字符都是按照顺序一次读取,所以必须利用do…while循环,后置判断。如果是先前置判断,那么数字就会提前被getchar( )读取,造成数据丢失。
可是,提前预估一个数组大小不可避免地会造成误差。可能会造成空间的浪费或者空间不足的情况发生,那么我们该如何防止这样的惨剧发生呢?哎!要是数组能动起来就好了,于是我们想到了动态内存管理。
二、动态内存管理
代码
int main(){
int count=1;
int size=0;
char ch;
int* arr=(int*)malloc(sizeof(int)*count);
do{
if(size==count){
int newCount=count*2;
int* tmp=(int*)realloc(arr,newCount*sizeof(int));
if(tmp==NULL){
printf("%s\n",strerror(errno));
exit(-1);
}
else{
arr=tmp;
count=newCount;
}
}
scanf("%d",&arr[size++]);
}while((ch=getchar())!='\n');
printf("%d",count);
free(arr);
arr=NULL;
return 0;
}
首先我们先了解一下里面提到动态内存相关的库函数。
malloc( )
头文件:#include<stdlib.h> 或者 #include<malloc.h>
原型
extern void * malloc(unsigned int num_byte)
- 功能:分配长度为num_byte字节的内存块
- 参数:需要分配的内存字节数,如果内存池中的可用内存可以满足这个需求,malloc就返回一个指向被分配的内存块起始位置的指针
- 返回值:如果分配成功则返回指向被分配内存的指针,否则返回空指针NULL
知道了这个,我们来拆分相关的上述代码吧 !
int count=1;
int* arr=(int*)malloc(sizeof(int)*count);
malloc向系统申请分配指定 sizeof(int)*count 个字节的内存空间,也就是可以存放一个元素的连续空间。此外,malloc返回的指针类型是void *类型。void *类型表示未确定类型的指针。因此,C、C++规定,void *类型可以强制转换为任何其他类型的指针。所以,在这里相当于创建了一个数组名为arr,大小为1 的整型数组只不过,这个数组大小被改变。
realloc( )
头文件:#include<stdlib.h>
原型
void* realloc(void* memblock, size_t size)
- 功能:将原先memblock处的空间扩展到 size大小。
- 参数:memblock是先前开辟的内存块的指针(也就是malloc或calloc之前申请的那块内存空间,即需要调整大小的内存空间)size_t size指的是New size in bytes,新的字节数,注意不是增加的字节数,而是新开辟的那块内存空间的字节数。
- 返回值:为调整之后的内存的起始地址,如果后续的内存能够满足开辟那么就返回原地址,如果不满足开辟,则将原来的数据复制到新地址中,并将新地址返回,如果开辟失败就返回空指针NULL。
if(size==count)
{
int newCount=count*2;
int* tmp=(int*)realloc(arr,newCount*sizeof(int));
if(tmp==NULL)
{
printf("%s\n",strerror(errno));
exit(-1);
}
else
{
arr=tmp;
count=newCount;
}
}
当数组的元素数量size与我们所开辟的内存相等时,我们就需要开辟新空间进行扩容。
我们定义了一个newCount = count*2,表示新开辟空间的大小。
然后, realloc(arr,newCount*sizeof(int))表示将原来的空间扩增到原来的两倍,由于realloc函数返回的指针类型为viod*,所以我们要将他强制转化成 int*,存放到整型指针tmp中。
那么问题来了,既然realloc有可能返回原地址,那我们为什么不直接用原指针存储呢?
arr=(int*)realloc(arr,newCount*sizeof(int))
其实,使用tmp接收是有一定道理的。因为,如果开辟失败,用arr接收返回NULL就将原本的指针内容清零,arr就再也找不回原来的数据。也就是说原来开辟的空间没有被释放就与arr断开连接这样无疑对计算机会造成不可逆转的伤害。放在tmp中就可以检验tmp是否为空指针,不会对原来的数据造成威胁。
接下来我们来认识一下如果是空指针该怎么办。
代码
if(tmp==NULL)
{
printf("%s\n",strerror(errno));
exit(-1);
}
else
{
arr=tmp;
count=newCount;
}
strerror( )
头文件:#include<string.h>和#include<errno.h>
原型
char * strerror ( int errnum );
- 功能:我们可以使用strerror函数来获取与指定错误码相对应的错误消息字符串,当程序出现错误时,对了解错误的具体信息、调试和修复问题至关重要。
- 参数:errnum参数是一个表示错误码的整数值。
- 返回值:strerror函数会返回一个指向错误消息字符串的指针。这个指针指向的字符串通常是一个静态的字符串常量,因此不应该尝试修改它。
errno
Linux中系统调用的错误都存储于errno中,errno由操作系统维护,存储就近发生的错误,即下一次的错误码会覆盖掉上一次的错误。
头文件:#include<errno.h>
用于表示最近一个函数调用是否产生了错误。若为0,则无错误,其它值均表示一类错误。
exit( )
头文件:#include<stdlib.h>
原型
void exit(int status)
- 功能:用于退出程序的函数
- 参数:exit(0),即status=0,表示正常退出,相当于return 0;其余,status为其他值时,均表示异常退出,此外,值得注意的是,括号内的参数都将返回给操作系统。
接下来,我们再回到代码中吧!
if(tmp==NULL)
{
printf("%s\n",strerror(errno));
exit(-1);
}
else
{
arr=tmp;
count=newCount;
}
当扩容失败,tmp指针为空指针时,strerror(errno)返回错误的原因,并将其打印再操作台上,并exit(-1),退出程序。 当扩容成功检查不为空指针后,将扩容后的数组大小,赋给arr,并将新扩充的容量,赋给count,这样以方便后续读取和判断元素个数与数组容量是否相等。