在头文件<stdlib.h>中说明了用于数值转换、内存分配以及具有其他相似任务的函数。
数值转换:字符串和字符串之间的转换
内存分支:主要说的是动态内存的申请使用等
【函数1: atof】
【格式】
double atof(const char *str);
【功能】
把字符串str转换成double类型数据,只对数字字符有效。等价于:strtod(str, (char**)NULL)
【入参】
const char *str:常量字符串
【返回值】
double类型的数据,带有后六位小数
【test_code】
输出结果:
【总结】
通过验证可以看出atof函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出
3.字符串开头若不是数字,则立马退出并打印数值为0.000000
【函数2: atoi】
【格式】
int atoi(const char *str);
【功能】
把字符串str转换成int类型。等价于:(int)strtol(str, (char**)NULL, 10)
【入参】
const char *str:常量字符串
【返回值】
int类型的数据
【test_code】
输出结果:
【总结】
通过验证可以看出atoi函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出
3.字符串开头若不是数字,则立马退出并打印数值为0
【函数3: atol】
【格式】
long atol(const char *str);
【功能】
把字符串str转换成long类型。等价于:strtol(str, (char**)NULL, 10)。
【入参】
const char *str:常量字符串
【返回值】
long类型的数据
【test_code】
输出结果:
【总结】
通过验证可以看出atol函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出
3.字符串开头若不是数字,则立马退出并打印数值为0
【函数4: strtod】
【格式】
double strtod(const char *start, char **end);
【功能】
把字符串start的前缀转换成double类型。在转换中跳过start的前导空白符,然后逐个读入构成数的字符,任何非浮点数成分的字符都会终止上述过程。如果end不为NULL,则把未转换部分的指针保存在*end中。
如果结果上溢,返回带有适当符号的HUGE_VAL,如果结果下溢,那么函数返回0。在这两种情况下,errno均被置为ERANGE。
【入参】
const char *start:常量字符串(常量指针)
char **end:二级字符指针,用来存储未被转换的部分,以备使用,若不用,则可用NULL代替
【返回值】
double类型的数据
【test_code】
输出结果:
【总结】
通过验证可以看出strtod函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出,会自动跳过前导空白
3.字符串开头若不是数字,则立马退出并打印数值为0
4.二级指针参数可以入参为NULL,验证如下:
【函数5: strtol】
【格式】
long int strtol(const char *start, char **end, int radix);
【功能】
把字符串start的前缀转换成long类型,在转换中跳过start的前导空白符。如果end不为NULL,则把未转换部分的指针保存在*end中。
如果radix的值在2到36间之间,那么转换按该基数进行;如果radix为0,则基数为八进制、十进制、十六进制,以0为前导的是八进制,以0x或0X为前导的是十六进制。无论在哪种情况下,串中的字母是表示10到radix-1之间数字的字母。如果radix是16,可以加上前导0x或0X。
如果结果上溢,则依据结果的符号返回LONG_MAX或LONG_MIN,置errno为ERANGE。
【入参】
const char *str:常量字符串
char **end:二级字符指针,用来存储未被转换的部分,以备使用,若不用,则可用NULL代替
radix:简单理解就是转换规则,要以什么规则把*str转为你需要的数据
【返回值】
long int类型的数据
【test_code】
输出结果:
打印1:因为是十进制转换输出,所以输出结果为123,也就是123.8hwe = 123
打印2:str被转换后剩余的部分,也就是.8hwe,存储在work中
打印3:因为是十进制转换输出,所以输出结果为576,也就是576wowo123 = 576
打印4:因为是按十六进制转换输出,所以输出结果为8295,也就是0x2067 = 8295
打印5:因为是十进制转换输出,所以输出结果为0,也就是..2074.967 = 0
打印6:因为是八进制,所以输出结果为1253,也就是2345= 1253
注意:比如打印4:
temp = strtol(str2, work, 16);
LOGLD(temp);
此时,str2 = 2067,上面函数的意思是:把2067按照十六进制计算,然后打印出来,LOGLD其实是#define LOGLD(h) printf("%ld\n", h) 也就是说我打印的时候是按照十进制去打印的哦,所以打印出来便是8295,其他进制数打印原理类推
【总结】
通过验证可以看出strtol函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出,会自动跳过前导空白
3.字符串开头若不是数字,则立马退出并打印数值为0
4.关于radix的理解至关重要,范围是2-36,二进制 2 八进制 8 十进制 10 十六进制 16当然还有其他取值和用法,这里我就不再深究,后面 深入剖析C函数 的时候,可能会再深究,关于其他用法,还需理解深刻,便可体会之奥妙
5.二级指针参数可以入参为NULL,验证如下:
【函数6: strtoul】
【格式】
unsigned long int strtoul(const char *start, char **end, int radix);
【功能】
与strtol()类似,只是结果为unsigned long类型,溢出时值为ULONG_MAX
【入参】
const char *str:常量字符串
char **end:二级字符指针,用来存储未被转换的部分,以备使用,若不用,则可用NULL代替
radix:简单理解就是转换规则,要以什么规则把*str转为你需要的数据
【返回值】
unsigned long int类型的数据
【test_code】
输出结果:
【总结】
通过验证可以看出strtol函数运行时的一些特征:
1.只对数字参数有效
2.从字符串头开始,若遇到数字则转,一旦遇到其他字符:字母,符号,下划线,小数点等,会立马退出,会自动跳过前导空白
3.字符串开头若不是数字,则立马退出并打印数值为0
4.关于radix的理解至关重要,范围是2-36,二进制 2 八进制 8 十进制 10 十六进制 16当然还有其他取值和用法,这里我就不再深究,后面 深入剖析C函数 的时候,可能会再深究,关于其他用法,还需理解深刻,便可体会之奥妙
5.二级指针参数可以入参为NULL,验证如下:
【函数7: rand】
【格式】
int rand(void);
【功能】
产生一个0到RAND_MAX之间的伪随机整数。RAND_MAX值至少为32767
【入参】
void: 入参为空
【返回值】
int类型的数据
【test_code】
输出结果:
【总结】
1、rand()不需要入参,它返回一个从0到最大随机数的任意整数,最大随机数的大小通常是固定的一个大整数。
2、如果你要产生0~99中的一个随机数,可以写为:int num = rand() % 100; 那么,num的值就是0~99中的一个随机数了。
3、如果要产生1~100,则:int num = rand() % 100 + 1;
4、总结下,可以写成:int num = rand() % n +a;
a是起始值,n-1+a是终止值,n是整数的范围。
5、一般:rand() % (b-a+1)+ a ; 就表示 a~b 之间的一个随机整数。
6、若要产生0-1之间的小数,则可以先取得0-10的整数,然后均除以10即可得到“随机到十分位”的10个随机小数。
若要得到“随机到百分位”的随机小数,则需要先得到0~100的10个整数,然后均除以100,其它情况依 此类推
7.注意rand产生的是伪随机数,并不是真正的随机数,这个有兴趣的朋友可以了解下,在加密中会有解释
【函数8: srand】
【格式】
void srand(unsigned int seed);
【功能】
设置新的伪随机数序列的种子为seed。种子的初值为1,主要作用就是使rand出来的随机值每次都不一样
【入参】
unsigned int seed :种子值
【返回值】
无返回数据
【test_code】
输出结果:
注意:每一次执行后,随机值都不一样哦,
若不加srand随机值种子的话,每次执行都是一样的,如下:
输出结果:
【总结】
若要生成每次变化的随机值,就要使用srand函数进行“播随机种子”操作哦,这个例子中我们用到了time(NULL)函数,这个在后面的time.h中会讲到,就先简单理解就是给出随机时间作为种子
【函数9: calloc】
【格式】
void *calloc(size_t num, size_t size);
【功能】
为num个大小为size的对象组成的数组分配足够的内存,并返回指向所分配区域的第一个字节的指针;如果内存不足以满足要求,则返回NULL。
分配的内存区域中的所有位被初始化为0。
【入参】
size_t num:需要申请动态内存的个数
size_t size:单位内存的大小,一般是sizeof(int)或者sizeof(char)等,用sizeof算出类型数据的单位内存是多少
【返回值】
void*类型的数据,指向所分配区域的第一个字节的指针
【test_code】
输出结果:
【总结】
1.calloc在申请num个单位内存后,会自动的初始化为0,所以这个避免了乱码
2.注意返回值是void*类型,理论上用什么类型接都行,但是建议接的时候,尽量用的指针类型和申请内存的类型是一致的哦,不然后面子在操作使用时,会出现移位不对称,导致数据乱码哦(这个应该不牵扯的,还待验证探究)
3.也可以像下面这样用:
建议最好是像下面这样用法,比较标准清晰(代码是写出来让别人读的,所以最好清晰明了,也算是一个良好习惯的养成吧):
4.最后一点,也是最重要的一点,因为这个申请下来的是动态能存(堆内存),不会随着函数的结束而释放掉(随着函数结束而释放掉的是栈内存),所以申请使用完之后,一定要记住释放内存,free下
【函数10: malloc】
【格式】
void *malloc(size_t size);
【功能】
为大小为size的对象分配足够的内存,并返回指向所分配区域的第一个字节的指针;如果内存不足以满足要求,则返回NULL。
不对分配的内存区域进行初始化
【入参】
size_t size:需要申请动态内存的个数(大小)
【返回值】
void*类型的数据,指向所分配区域的第一个字节的指针
【test_code】
输出结果:
【总结】
1.calloc在申请num个单位内存后,会自动的初始化为0,所以这个避免了乱码
2.注意返回值是void*类型,理论上用什么类型接都行,但是建议接的时候,尽量用的指针类型和申请内存的类型是一致的哦,不然后面子在操作使用时,会出现移位不对称,导致数据乱码哦(这个应该不牵扯的,如上验证,也可以用char*的接,这个malloc应该不影响,他的入参只要申请的大小,并没有检查单位数据类型)
3.最后一点,也是最重要的一点,因为这个申请下来的是动态能存(堆内存),不会随着函数的结束而释放掉(随着函数结束而释放掉的是栈内存),所以申请使用完之后,一定要记住释放内存,free下
【函数11: realloc】
【格式】
void *realloc(void *ptr, size_t size);
【功能】
将ptr指向的内存区域的大小改为size个字节。如果新分配的内存比原内存大,那么原内存的内容保持不变,增加的空间不进行初始化。如果新分配的内存比原内存小,那么新内存保持原内存区中前size字节的内容。函数返回指向新分配空间的指针。如果不能满足要求,则返回NULL,原ptr指向的内存区域保持不变。
如果ptr为NULL,则行为等价于malloc(size)。
如果size为0,则行为等价于free(ptr)
【入参】
void *ptr:指向原来的动态内存的指针(现在要改变大小的那个动态内存的指针)
size_t size:准备再次增加(减少)申请的动态内存的大小
【返回值】
void*类型的数据,指向所分配区域的第一个字节的指针
【test_code】
验证1:增加内存长度
输出结果:
可以看出,新申请的5个单位的内存,内容并未初始化,是有乱码的哦,而且ptr的动态内存比之前增加了5个单位的长度
验证2:减少内存长度
输出结果:
不知道为何tp[4]和tp[5]的输出结果却为3和4,这里不清理解,还需要再深究下,哪个大神可以帮忙解释下?3Q
【总结】
1.realloc在申请num个单位内存后,会保留原来的内存内容并把新增加的内存长度添加上去,但是并不会自动的对增加的内存初始化为0
2.关于缩减内存长度这块儿,感觉好像没生效,没有搞的很清楚
3.最后一点,也是最重要的一点,因为这个申请下来的是动态能存(堆内存),不会随着函数的结束而释放掉(随着函数结束而释放掉的是栈内存),所以申请使用完之后,一定要记住释放内存,free下
【函数12: free】
【格式】
void free(void *ptr);
【功能】
释放ptr指向的内存空间,若ptr为NULL,则什么也不做。ptr必须指向先前用动态分配函数malloc、realloc或calloc分配的空间
【入参】
void *ptr:指针名
【返回值】
void类型的数据(也就是空)
【test_code】
输出结果:
可以看到,释放sptr之后,再打印,就是空了,所以这个sptr的内容及内存就被释放掉了,一般情况下,释放后,建议赋值为NULL,这样就不容易成为野指针
【总结】
1.free释放内存,只是针对动态内存(calloc,malloc,realloc申请的),也就是只是针对堆内存而言,栈内存一般都是函数运行结束后,系统会自动释放,所以一定要注意场景
2.free完后,建议给赋值为NULL,这样就尽可能的防止了出现使用野指针的情况,因为我们代码一般使用指针的时候,都会进行判空操作,我们释放掉,再赋值NULL,这样就不容易被作为野指针使用,当然了,这只是防止野指针的一种情况,关于野指针,后面我们有专题探讨。
3.形成一条固定记忆:但凡使用calloc,malloc,realloc申请的内存,在使用完了之后,一定要用free释放掉,不然就会形成内存泄漏问题!
【函数13: abort】
【格式】
void abort(void);
【功能】
使程序非正常终止。其功能类似于raise(SIGABRT)。
【入参】
void:空参,也就是不需要入参
【返回值】
void类型的空数据,也可以简单的理解为没有数据返回
【test_code】
输出结果:
【总结】
这个类似于断言的功能一样,可能主要用于debug吧,具体用法场景还不是很清楚,有知道的大神希望指点一二,3Q
【函数14: exit】
【格式】
void exit(int status);
【功能】
使程序正常终止。status的值如何被返回依具体的实现而定,但用0表示正常终止,也可用值EXIT_SUCCESS和EXIT_FAILURE
【入参】
status: 状态值
【返回值】
void类型的数据
【test_code】
输出结果:
【总结】
在使用exit的时候,入惨status最好用成对应的枚举值,enum,这样,在找问题的时候就很方便和快捷高效,而不是出现魔鬼数字,让人猜
【函数15: atexit】
【格式】
int atexit(void (*func)(void));
【功能】
注册在程序正常终止时所要调用的函数func。如果成功注册,则函数返回0值,否则返回非0值。atexit函数以与注册相反的顺序被调用,所有打开的文件被刷新,所有打开的流被关闭。
【入参】
void (*func)(void):函数指针,func指向对应的函数便可,为了后面调用
【返回值】
int类型的数据
【test_code】
输出结果:
【总结】
1.注意函数指针的定义及定义实体对象,如下:
typedef void(*func) ();//定义一个函数指针
func funcptr;//定义实体
funcptr = fun;//赋值函数地址,等待被调用
atexit(funcptr);//调用函数指针
2.也可以这样搞,如下:
void (*funcptr) ();
funcptr = fun;//赋值函数地址,等待被调用
atexit(funcptr);//调用函数指针
3.atexit调用函数,执行的顺序是从后往前,就是先注册的后调用运行,后注册的先调用运行
【函数16: system】
【格式】
int system(const char *str);
【功能】
把字符串str传送给执行环境。如果str为NULL,那么在存在命令处理程序时,返回0值。如果str的值非NULL,则返回值与具体的实现有关。
【入参】
const char *str:常量字符串
【返回值】
int类型的数据
【test_code】
输出结果:
【总结】
1.system(str),这个入参其实就是一条指令,根据系统的不同而不同,比如图中在linux中,入参1是ipconfig,就not found;入参为ls,便直接执行这个指令了
2.这个应该是系统调用,具体和Linux内部系统调用的区别,还待考究
【函数17: getenv】
【格式】
char *getenv(const char *name);
【功能】
返回与name相关的环境字符串。如果该字符串不存在,则返回NULL。其细节与具体的实现有关。
【入参】
const char *name:常量字符串
【返回值】
char* 类型的数据
【test_code】
输出结果:
【总结】
1.这个函数使用的话,首先要知道env变量都有哪些,linux系统的话,直接env指令,就会出现env变量,window的话,需要去在配置里查看
2.将得到的env变量的路径值返回出去,可以接,也可以直接打印出来,都行,看具体需要而定
【函数18: bsearch】
【格式】
void *bsearch(const void *key, const void *base, size_t n, size_t size, int (*compare)(const void *, const void *));
(建议先看下一个qsort函数,再回头来看这个bsearch函数)
【功能】
在base[0]...base[n-1]之间查找与*key匹配的项。size指出每个元素占有的字节数。函数返回一个指向匹配项的指针,若不存在匹配则返回NULL。
函数指针compare指向的函数把关键字key和数组元素比较,比较函数的形式为:
int func_name(const void *arg1, const void *arg2);
arg1是key指针,arg2是数组元素指针。
返回值必须如下:
- arg1 < arg2时,返回值<0;
- arg1 == arg2时,返回值==0;
- arg1 > arg2时,返回值>0。
数组base必须按升序排列(与compare函数定义的大小次序一致)。
【入参】
const void *key:要查找的项的内容
void *base:一般是个数组,int base[];或者char *base[]; float base[]均可,根据需要而定,结构数组也行
size_t n:这个表示base数组的长度,计算方法:n = sizeof(base)/sizeof(base[0]);
size_t size:这个表示单个元素的大小,计算方法:sizeof(base[0]);
int (*compare)(const void *, const void *):这个是函数指针,也是我们实现的重点,我们根据需要实现此函数为cmp_zll,在调用的时候直接将函数名调用便可,关于函数指针和指针函数,之前有写过文章已经说过,这里不在赘述。
【返回值】
void* 类型的数据
【test_code】
输出结果:
输出结果:
【总结】
1.次函数入参比较多,建议先把qsort搞熟悉,再来看这个,就比较容易了
2.记得在使用bsearch之前,一定要用qsort给base把序排了,而且只能是升序,不能搞成降序哦
3.这个就是二分查找,具体深入探索在后面会再说
【函数19: qsort】
【格式】
void qsort(void *base, size_t n, size_t size, int (*compare)(const void *, const void *));
【功能】
对由n个大小为size的对象构成的数组base进行升序排序。
比较函数compare的形式如下:
int func_name(const void *arg1, const voie *arg2);
其返回值必须如下所示:
- • arg1 < arg2,返回值<0;
- • arg1 == arg2,返回值==0;
- • arg1 > arg2,返回值>0。
【入参】
void *base:一般是个数组,int base[];或者char *base[]; float base[]均可,根据需要而定,结构数组也行
size_t n:这个表示base数组的长度,计算方法:n = sizeof(base)/sizeof(base[0]);
size_t size:这个表示单个元素的大小,计算方法:sizeof(base[0]);
int (*compare)(const void *, const void *):这个是函数指针,也是我们实现的重点,我们根据需要实现此函数为cmp_zll,在调用的时候直接将函数名调用便可
【返回值】
void类型
【test_code】
输出结果:
(注意为升序排序结果)
输出结果:
【总结】
1.qsort函数默认是按照升序排列的
2.重点是实现函数指针compare
比较函数compare的形式如下:
int func_name(const void *arg1, const voie *arg2);
实现完毕后,直接从qsort调用便可
【函数20: abs】
【格式】
int abs(int num);
【功能】
返回int变量num的绝对值。
【入参】
int num:整形变量,可正可负
【返回值】
int类型的数据
【test_code】
输出结果:
【总结】
1.abs函数就是一个取绝对值的函数
2.入参可以为单个数字,也可以为表达式,或者三目计算符,只要最后给出的是一个整形数据就行,不牵扯过程
【函数21: labs】
【格式】
long labs(long int num);
【功能】
返回long类型变量num的绝对值。
【入参】
long int num:长整形参数
【返回值】
long类型的数据
【test_code】
输出结果:
【总结】
1.labs函数就是一个取绝对值的函数
2.入参可以为单个数字,也可以为表达式,或者三目计算符,只要最后给出的是一个长整形数据就行,不牵扯过程
3.针对long int的数据打印,我查了下,网上众说纷纭,有说用%li格式打印的,有说%ld格式打印的,我试了下,这两个的话,都报warning,我用%d调试,没有报warning,所以这个还是根据自己的编译器看吧,只要结果不出差错,应该是都可以接收的
【函数22: div】
【格式】
div_t div(int numerator, int denominator);
【功能】
返回numerator/denominator的商和余数,结果分别保存在结构类型div_t的两个int成员quot和rem中。
【入参】
int numerator:入参为被除数(也就是分子)
int denominator:入参为除数(也就是分母)
【返回值】
div_t类型的数据
【test_code】
输出结果:
【总结】
1.先看解析
可以看出,div_t就是一个结构体,就是两个成员:quot和rem,前者存商值,后者存余值
2.这个div函数具体的使用情况,大家可以思考下
【函数23: ldiv】
【格式】
ldiv_t div(long int numerator, long int denominator);
【功能】
返回numerator/denominator的商和余数,结果分别保存在结构类型ldiv_t的两个long成员quot和rem中。
【入参】
long int numerator:入参为被除数(也就是分子)
long int denominator:入参为除数(也就是分母)
【返回值】
ldiv_t类型的数据
【test_code】
输出结果:
【总结】
参考div函数的总结,这个ldiv就是数据类型变为long int