Linux C语言常用库函数

介绍常用库函数的作用

Posted by Jerry Chen on July 19, 2019

和长度相关的库函数

  • strlen函数求字符串长度遇到结束符号’\0’截断,故strlen("ab\0cd")的结果为2;
  • sizeof求占用空间,不是函数是一种方法;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <stdio.h>
#include <string.h>

#define ARRAY_LENGTH(array) (sizeof(array) / sizeof((array)[0]))

int main(int argc,char *argv[]){
	char a[8] = "ab\0cd";//实际看作"ab\0cd\0\0\0\0",遇到\0截断
	char b[4] = "abcd";//看作"abcd",因为没有\0位置,故strlen已经不靠谱
	int c[4];

	printf("a_len is %d\n",(int)strlen(a));//2
	printf("b_len is %d\n",(int)strlen(b));//4,但是strlen不靠谱
	printf("c_len is %d\n",(int)ARRAY_LENGTH(c));//4
	
	printf("a_size is %d\n",(int)sizeof(a));//8=maxlen*1
	printf("b_size is %d\n",(int)sizeof(b));//4=maxlen*1
	printf("c_size is %d\n",(int)sizeof(c));//16=4*4
	
	printf("After making some changes:\n");
	*(b+4) = 'e';
	printf("b_len is %d\n",(int)strlen(b));//5,可以看到strlen不靠谱了
	printf("b_size is %d\n",(int)sizeof(b));//4=maxlen*1
	
	char d[] = {'a','b','c','d'};//也就是声明已经决定了和char d[4]等价
	printf("d1_size is %d\n",(int)sizeof(d));//4=maxlen*1
	printf("d1_len is %d\n",(int)strlen(d));//4,但是strlen不靠谱
	*(d+4) = 'e';
	printf("d2_size is %d\n",(int)sizeof(d));//4=maxlen*1
	printf("d2_len is %d\n",(int)strlen(d));//5,可以看到strlen不靠谱了
	
	return 0;
}

总结起来就是,字符串类型(char *a和char a[n])的长度用strlen,其他类型长度用宏,sizeof是求需求空间的。字符串占用的地址会+1存放默认值’\0’,strlen会检测’\0’(也就是数值0)判断结束位置。如果该位被使用,或者声明的数组中未出现’\0’,那么strlen将变得不靠谱。不过也不用太担心,一般变量初始值都是0,也就是不赋值的字符数组初始值是”\0\0\0…\0”,只要不用满,strlen都是有效的。

后来发现:printf函数也是根据’\0’判断%s信息的结束的,strcpy根据’\0’判断复制来源的结束区域。

额外:编写strlen函数。

和内容相关的库函数

  • bzero(buff,sizeof(buff));
  • memset(buff,0,sizeof(buff));
  • memcpy,复制指定size的内容从源到目标;
  • strcpy,把从被复制字符串地址开始直到’\0’的字符串复制到目标地址开始的空间,也就是说把20位的字符串,copy到声明10位的字符串数组也是不会报错的,但会占用额外的空间使strlen之类的变得不靠谱,所以在已知该条件下声明时尽量正确分配地址空间;
  • strncpy,从源复制n个到目标地址开始的空间;
  • strcmp,逐个字符比较,如果目标大返回正,目标小返回负,一样返回0;
  • strncmp,比较前n个字节;
  • strcat,将源字符串连接在目标字符串后;

额外:编写strcpy、strcmp、strcat

和输入相关的库函数

C语言中scanf、gets、fgets的区别?

scanf

scanf会读取标准输入缓冲区中的输入行内容直到遇到回车或空白(回车、tab、空格)。以换行为例,scanf会把换行符之前的数据全部读入到scanf指定的变量中,但回车(\n)依然保留在缓冲区,所以一般需要加入其他代码来清空缓存区:

1
2
3
4
5
//这里getchar()从缓冲区中读一个字符,直到读取到回车结束
//由于getchar()没有设置变量保存读取的字符,所以读取的字符全部被抛弃
//这样就把一行内容全部清空了(回车前面的内容包括回车)
while(getchar() != '\n')
	continue;

实验demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

int main(int argc,char *argv[]){
	char str[10];//千万不要char *str,然后scanf("%s",str);,这是错误的,因为str没有指向有效的内存,它可能指向任何非法地址
	char c;
	printf("input string:  ");
	scanf("%s",str);//变量名已经是地址
	printf("output string:%s\n",str);////填变量名

	printf("input char:  ");
	scanf("%c",&c);//填变量地址
	printf("output char:%d\n",c - '\n');////填变量名
	return 0;
}

结果果然第二个输入scanf("%c",&c);直接从缓冲区取走了’\n’,所以对于这种情况需要加入清空输入缓冲,特别是使用scanf("%c",&c);的情况下输入了不止一个字符。

但是如果两个输入均是scanf("%s",str);,会发现第一个字符是回车、tab、空格均会被丢弃,所以不会出现问题。

优化版本程序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main(int argc,char *argv[]){
	char str[10];//千万不要char *str,然后scanf("%s",str);,这是错误的,因为str没有指向有效的内存,它可能指向任何非法地址
	char c;
	
	printf("input string:  ");
	scanf("%s",str);//变量名已经是地址
	while(getchar() != '\n'); //scanf后都加这句
	printf("output string:%s\n",str);////填变量名

	printf("input char:  ");
	scanf("%c",&c);//填变量地址
	while(getchar() != '\n'); //scanf后都加这句
	printf("output char:%c\n",c);////填变量名

	return 0;
}

结果如下:

fgets

fgets使用来处理文件的,不过可以设置文件指针是stdin(标准输入,一般指键盘),它在读取标准输入时在遇到回车时会直接回车之前包括回车都读出到指定的变量中;

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>//fgets()用法

int main(int argc,char *argv[]){
	char buffer[BUFSIZ + 1];//用于保存输入的文本
	
	printf("input string:  ");
	fgets(buffer, BUFSIZ, stdin); //推荐用字符数组长度-1作为size(留空'\0')
	printf("output string:%s\n",buffer);//填变量名
	
	printf("end\n");
	return 0;
}

gets-不建议使用

简单来说就是从标准输入一行字符串(注:回车算下一行)。

因为该函数没有指定最大读取长度,有风险在新的标准中已经被替换,不建议使用该函数。编译时会报警告函数过时,建议使用fgets替代。

gets只有在遇到回车时才会把缓冲区中的内容全部读出了,然后去掉回车(\n)把回车前的内容全部写到变量中;

配合函数:puts(s);会字符串后自动加回车输出打印。

其他

int getchar() int getc(stdin) putchar(int c) putc(int c,stdout)

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(int argc,char *argv[]){
	char c;
	c = getchar();
	putchar(c);
	c = getc(stdin);
	putc(c,stdout);
	
	printf("\n>>>end>>>\n");
	return 0;
}

可以看到获取单个字符也需要看情况选择下次输入前清空输入缓冲区。

另外了解到fflush(stdin);就是刷新标准输入缓冲区,可以试验一下,是否有效果。

空间相关

进程中的变量形参存放栈中,只有该进程可以访问,进程结束自动释放;堆是所有进程均可访问的空间,不会自动释放,需要使用malloc申请、free释放等函数。

比如int p[40];int *p;p=(int *)malloc(40*sizeof(int));if(!p)...可能效果差不多,但是前者数组是放在栈中,每个进程栈大小是有限的,后者只有p指针放在栈中,空间是堆中的(申请务必做错误判断),用完后也请free掉空间(可理解为改个标志让空间可重新被malloc),最好把指向该空间的指针强制归NULL,这样确保不会再次访问到堆空间;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>

void main(int argc,char *argv[]){
	int *p,t;//进程中变量放于栈中,结束后自动释放
	
	//下面从堆(所有进程共用)中划分一段连续未使用的空间返回地址,需要手动释放
	p=(int *)malloc(40*sizeof(int));//栈指针指向堆空间
	if(!p){
		printf("\t内存已用完\t");
		exit(1);
	}
	for(t=0;t<40;++t) *(p+t)=t;
	for(t=0;t<40;++t) printf("\t%d",*(p+t));
	printf("\n");
	
	free(p);//释放指向的堆空间,这样其他malloc就可以用这部分空间了
	p = NULL;//此处是一个保险,防止再通过p访问地址对应的堆空间了
	//如果其他进程申请了这块数据,这里再访问了那么堆数据就不可靠了
}

封装自己的printx函数

均需要包含头文件: #include <stdarg.h>

封装printf函数

1
2
3
4
5
6
7
8
void my_printf(const char *format,...)
{
    va_list args;
 
    va_start(args,format);
    vprintf(format,args); //必须用vprintf
    va_end(args);
}

封装sprintf函数

1
2
3
4
5
6
7
8
void my_sprintf(char * buf, const char *format,...)
{
    va_list args;
 
    va_start(args,format);
    vsprintf(buf,format,args);
    va_end(args);
}

封装fprintf函数

1
2
3
4
5
6
7
8
void my_fprintf(FILE *fp,const char *format,...)
{
    va_list args;
 
    va_start(args,format);
    vfprintf(fp,format,args);
    va_end(args);
}

封装其他变体

这里用sprintf做个串口打印,美滋滋;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void DbgPrint(const char *format,...)
{
    char buf[256];
    va_list args;
 
    va_start(args,format);
    vsprintf(buf,format,args);
    va_end(args);
    
    //判断前几个字符作为调试等级云云...
    
    //串口发送
    sci_send(buf, strlen(buf));
}