Linux系统编程(二)---学习Linux系统函数

目录

一、Linux系统介绍

二、标准IO

1.文件信息介绍:

2.打开文件:

操作系统默认打开了三个流:

3.关闭文件:

这里介绍一下perror和printf的区别:

4.按字符读写:

cat函数:

cp函数:

5.按字符串读写:

cp函数:

cat函数:

记录文件行数:

注意:

6.按对象读写(二进制流读写)

用途:在结构体上使用这个函数读写会更便利:

cat函数:

cp函数:

7.文件定位:

移动文件位置指针

获取文件位置指示器

回到文件开头

8.格式化输入输出

讲解一下缓冲区机制:

注意:

查看缓冲区大小:

9.错误处理

三、总结


一、Linux系统介绍

linux本质上是个软件,linux系统分了内核空间和用户空间,如果要使用到linux系统的功能需要通过系统调用来使用到,系统调用就是指linux系统本身提供的函数

linux系统下,实现一个用户的应用程序:

两条路径:

1.直接通过系统调用直接实现

2.通过库的方式实现---函数库---一般是别人基于linux系统调用实现的功能更丰富使用更方便的函数

下面讲解linux本身提供的函数:标准IO

二、标准IO

首先介绍文件---一组相关信息的集合

linux将很多东西都抽象成文件

1.文件信息介绍:

- 普通文件(常规文件) 例:1.txt 图片 视频 音频 可执行文件ls -l 1.txt

d 目录文件(directory) 例:文件夹 目录 ls -l 目录

c 字符设备文件(char) 例:某一个硬件---鼠标,键盘,摄像头等 ls -l/dev/input

b 块设备文件(block) 例:硬盘等存储设备---512Bytes ls -l /dev/sda

l 软连接文件(link) 例:相当于Windows下的快捷方式 ls -l /dev/lib //浅蓝色

s 套接字文件 例:主要指的是本地socket---进程间通信

p 管道文件(pipe) 例:进程间通信 mkfifo pipe

ls -l pipe

2.打开文件:

FILE *fopen(const char *pathname, const char *mode);

参数说明:

pathname : 文件路径(相对或绝对)

mode : 打开模式

模式 说明 文件不存在 文件存在

"r" //只读 失败 从头读

"r+" //读写 失败 从头读写
"w" //只写 创建 清空内容

"w+" //读写 创建 清空内容
"a" //追加 创建 追加到末尾
"a+" //读写 创建 追加,可读任意位置

返回值:

成功: 返回 FILE * 指针

失败: 返回 NULL

示例代码:

复制代码
FILE *fp = fopen("1.txt", "r");//在当前目录打开一个1.txt文件
if (fp == NULL) {
perror("fopen fail"); // 打印错误信息
return -1;
}

简要介绍一下流的概念:

操作系统默认打开了三个流:

stdin //标准输入 //FILE*指针 //获取键盘输入的值

键盘上ctrl+d可以产生一个EOF

stdout //标准输出,输出到终端

stderr //标准出错//也是个输出

3.关闭文件:

int fclose(FILE *stream); //fclose(fp);

作用: 刷新缓冲区并关闭文件

返回值: 成功返回 0,失败返回 EOF(-1)

注意: 必须关闭文件,否则数据可能丢失!

这里介绍一下perror和printf的区别:

注意看缓冲区,主要是这里的区别!

一个是无缓冲,一个是行缓冲,不懂就去了解下面的缓冲区机制

4.按字符读写:

int fgetc(FILE *stream); //读

int fputc(int c, FILE *stream); //写

功能:从流中获得数据(一次只能读取一个字符)

返回值:

成功:返回读取到的字符的数值

失败:返回EOF(-1)当到达文件结尾也会返回EOF

练习:写一个cat和cp函数:

cat函数:

复制代码
#include <stdio.h>

// ./a.out 1.txt 
int main(int argc, const char *argv[])
{
	//命令行参数的处理 
	if (argc != 2)
	{
		printf("Usage: %s <filename>\n",argv[0]);
		return -1;
	}

	//1.打开文件 
	FILE *fp = fopen(argv[1],"r");
	if (fp == NULL)
	{
		//printf("fopen fail!\n");
		perror("fopen fail");
		return -1;
	}
	//2.读取 
	int ret = 0;

	while ((ret = fgetc(fp)) != EOF)
	{
		//printf("%c",ret);
		//printf("%c",ret);
	    fputc(ret,stdout);
	}

	//3.关闭 
	fclose(fp);
	//fclose(fp);
	
	return 0;
}

cp函数:

复制代码
#include <stdio.h>


//./a.out 1.txt 2.txt 
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <src> <dest>\n",argv[0]);
		return -1;
	}

	//1.打开 
	FILE *fp_s = fopen(argv[1],"r");
	FILE *fp_d = fopen(argv[2],"w");
	if (fp_s == NULL || fp_d == NULL)
	{
		perror("fopen fail ");
		return -1;
	}

	//2.读写 
	int ret = 0;

	while ((ret = fgetc(fp_s)) != EOF)
	{
		fputc(ret,fp_d);
	}

	//3.关闭 
	fclose(fp_s);
	fclose(fp_d);
	
	return 0;
}

5.按字符串读写:

char *fgets(char *s, int size, FILE *stream); //读

重要特性:
最多读取 size - 1 个字符
遇到换行符 \n 会停止,换行符也会被读入被保存到s指定的空间

自动在末尾添加 \0

返回值: 成功返回 s ,失败/EOF 返回 NULL
int fputs(const char *s, FILE *stream); //写

将s中的字符串输出到流中

不会自动添加换行符

返回值: 成功返回非负数,失败返回-1

练习:写一个cp函数和cat函数,和记录文件行数的函数

cp函数:

复制代码
#include <stdio.h>

void do_copy(FILE *fp_s,FILE *fp_d)
{
	//2.读写 
	char s[1024] = {0};
	while (fgets(s,sizeof(s),fp_s) != NULL)
	{
		fputs(s,fp_d);
	}

}


//./a.out 1.txt 2.txt 
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <src> <dest>\n",argv[0]);
		return -1;
	}

	//1.打开 
	FILE *fp_s = fopen(argv[1],"r");
	FILE *fp_d = fopen(argv[2],"w");
	if (fp_s == NULL || fp_d == NULL)
	{
		perror("fopen fail ");
		return -1;
	}

	//2.拷贝 
	do_copy(fp_s,fp_d);

	//3.关闭 
	fclose(fp_s);
	fclose(fp_d);
	
	return 0;
}

cat函数:

复制代码
#include <stdio.h>

int main(int argc, const char *argv[])
{
    if(argc != 2)
    {
        printf("Usege : %s <filename>\n",argv[0]);
        return -1;
    }
    FILE *fp = fopen(argv[1],"r");
    if(fp == NULL)
    {
        perror("fail");
        return -1;
    }
    char ret[100] ={0};
    while(fgets(ret,sizeof(ret),fp)!=NULL)                 
    {
        printf("%s",ret);
    }
    fclose(fp);
    return 0;
}

记录文件行数:

复制代码
 #include <stdio.h>
 #include <string.h>
 int main(int argc, const char *argv[])
 {
     if(argc != 2)                                              
     {
         printf("Usege : %s <filename>\n",argv[0]);
         return -1;
     }
     FILE *fp = fopen(argv[1],"r");
     if(fp == NULL)
     {
         perror("fail");
         return -1;
     }
     char ret[100] ={0};
     int cnt = 0;
     while(fgets(ret,sizeof(ret),fp)!=NULL)
     {
         if(ret[strlen(ret)-1] == '\n')
         {
             ++cnt;
         }
     }
     printf("%s has %d line\n",argv[1],cnt);
     fclose(fp);
     return 0;
 }

注意:

fputs 如果要输出的数据中没有换行,它不会自己输出换行

fputs要输出的是字符串,遇到\0结束

puts默认会输出换行
fgetc和fputc拷贝普通文件和二进制都没问题,但是文件大时,效率不高

fgets和fputs拷贝普通文件没问题,二进制有问题(\0)

6.按对象读写(二进制流读写)

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); //读

参数说明:

ptr : 接收数据的缓冲区 //char s[100]

size : 每个数据项的大小(字节) //一般是sizeof(char)

nmemb : 要读取的数据项个数 //一般是sizeof(s)

返回值: 实际读取的数据项个数
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); //写

ptr : 接收数据的缓冲区 //char s[100]

size : 每个数据项的大小(字节) //一般是sizeof(char)

nmemb : 要写入的数据项个数 //一般是sizeof(s)

返回值: 实际读取的数据项个数

用途:在结构体上使用这个函数读写会更便利:

例如:

cs 复制代码
typedef struct {
char name[20];
int sno;
float score;
} Student;

Student students[3] = {
{"Tom", 110, 99.5},
{"Jerry", 111, 99.9},
{"Jack", 112, 100}
};

// 写入 3 个学生记录
fwrite(students, sizeof(Student), 3, fp);

// 读取 1 个学生记录
Student s;
fread(&s, sizeof(Student), 1, fp);

练习:写一个cp函数和cat函数:

cat函数:

注意,如果用printf来输出到终端的话,需要加一个'/0',且读取元素的个数需要时sizeof(buf)-1,因为该函数不会自己补'\0',且会把缓冲区直接读满,所以需要-1给数组留一个位置加一个'\0'进去

cs 复制代码
#include <stdio.h>

int main(int argc, const char *argv[])
{                                                                   
    if(argc != 2)
    {
        printf("Usege : %s <filename>\n",argv[0]);
        return -1;
    }
    FILE *fp = fopen(argv[1],"r");
    if(fp == NULL)
    {
        perror("fail");
        return -1;
    }
    int ret = 0;
    char buf[100];
    while((ret = fread(buf,sizeof(char),sizeof(buf),fp)) != 0)
    {
    //  buf[ret] ='\0';
    //  printf("%s",buf);
        fwrite(buf,sizeof(char),ret,stdout);
    }

    fclose(fp);
    return 0;
}

cp函数:

cs 复制代码
#include <stdio.h>                                               

int main(int argc, const char *argv[])
{
    if(argc != 3)
    {
        printf("Usege : %s <filename> <filename>\n",argv[0]);
        return -1;
    }
    FILE *fp = fopen(argv[1],"r");
    if(fp == NULL)
    {
        perror("fail");
        return -1;
    }

    int ret =0;
    FILE *fp2 = fopen(argv[2],"w");
    if(fp2 == NULL)
    {
        perror("fail");
        return -1;
    }
    char s[100]={0};
    while((ret = fread(s,sizeof(char),sizeof(s),fp))!= 0)
    {
        fwrite(s,sizeof(char),ret,fp2);
    }

    fclose(fp);
    fclose(fp2);
    return 0;
}
                                                                 

7.文件定位:

移动文件位置指针

int fseek(FILE *stream, long offset, int whence);

参数说明:

offset : 偏移量(可正可负)

whence : 起始位置
SEEK_SET //文件开头
SEEK_CUR //当前位置
SEEK_END //文件末尾

返回值: 成功返回 0,失败返回 -1

cs 复制代码
// 移动到文件开头
fseek(fp, 0, SEEK_SET);
// 移动到文件末尾
fseek(fp, 0, SEEK_END);
// 向前移动 10 字节
fseek(fp, -10, SEEK_CUR);
// 移动到第 100 个字节
fseek(fp, 100, SEEK_SET);

获取文件位置指示器

long ftell(FILE *stream);

返回值: 当前位置相对于文件开头的偏移量

cs 复制代码
//示例
fseek(fp, 0, SEEK_END);
long file_size = ftell(fp);
printf("文件大小: %ld 字节\n", file_size);

回到文件开头

void rewind(FILE *stream);

等价于fseek(fp, 0, SEEK_SET)

8.格式化输入输出

int printf( const char *format, ...);//格式化输出到终端

int fprintf(FILE *stream, const char *format, ...);//格式化输出到文件

int sprintf(char *str, const char *format, ...);//格式化输出到数组

示例:

cs 复制代码
#include <stdio.h>


int main(int argc, const char *argv[])
{
    printf("%d:hello\n",2026);
    fprintf(stdout,"%d:hello\n",2026);
    char s[100];
    sprintf(s,"%d:hello\n",2026);
    printf("%s",s);                            


    return 0;
}
                                               

讲解一下缓冲区机制:

行缓存---一般与终端关联---1024字节

特点(什么时候会被刷新?)

1.遇到/n

2.缓冲区满

3.程序正常退出(1.从main函数返回 2.exit函数)

4.fflush函数 //强制刷新
全缓存 ---与普通文件关联---4096字节

特点(什么时候会被刷新?)

1.遇到/n

2.缓冲区满

3.程序正常退出(1.从main函数返回 2.exit函数)

4.fflush函数 //强制刷新
不缓存 ---错误信息的输出设计为不缓存

注意:

这里的fprintf就是写入文件就是全缓存,必须使用fflush(fp)冲刷才可以写入,不然没有输出结果

printf如果看不到结果也可能是因为程序没结束,或者没加\n

int fflush(FILE *stream); //冲刷缓冲区

cs 复制代码
printf("加载中...");
fflush(stdout); // 立即显示,不等换行符

查看缓冲区大小:

cs 复制代码
// 需要先触发缓冲区分配(如调用 getchar())
printf("stdin 缓冲区: %ld 字节\n",
stdin->_IO_buf_end - stdin->_IO_buf_base);
printf("stdout 缓冲区: %ld 字节\n",
stdout->_IO_buf_end - stdout->_IO_buf_base);
printf("stderr 缓冲区: %ld 字节\n",
stderr->_IO_buf_end - stderr->_IO_buf_base);

9.错误处理

int feof(FILE *stream); //检测文件结束 文件指针达到EOF返回真

int ferror(FILE *stream); //检测错误 文件指针不在EOF或者出错则返回真

void perror(const char *s); //打印错误信息

三、总结

字符fgetc() 、fputc() 效率低,简单

字符串fgets() 、fputs() 按行处理

二进制块fread() 、fwrite() 效率高,通用
要理解的概念:

1. FILE 结构体: 封装了文件描述符和缓冲区

2. 系统函数的功能和格式

3. 缓冲区机制: 提高 I/O 效率的关键

4. 文件指针: 记录当前读写位置

相关推荐
im_AMBER1 小时前
Leetcode 115 分割链表 | 随机链表的复制
数据结构·学习·算法·leetcode
小Pawn爷2 小时前
2.Docker的存储
运维·docker·容器
CaracalTiger2 小时前
OpenClaw-VSCode:在 VS Code 中通过 WebSocket 远程管理 OpenClaw 网关的完整方案
运维·ide·人工智能·vscode·websocket·开源·编辑器
qq_5470261792 小时前
LangChain 1.0 核心概念
运维·服务器·langchain
VekiSon2 小时前
Linux内核驱动——设备树原理与应用
linux·c语言·arm开发·嵌入式硬件
Trouvaille ~2 小时前
【Linux】进程间关系与守护进程详解:从进程组到作业控制到守护进程实现
linux·c++·操作系统·守护进程·作业·会话·进程组
生而为虫2 小时前
[Windows] 【浏览器自动化精灵V1.0】用Excel表格控制浏览器的自动化
运维·自动化
Fcy6482 小时前
Linux下 进程(二)(进程状态、僵尸进程和孤儿进程)
linux·运维·服务器·僵尸进程·孤儿进程·进程状态
小陶的学习笔记2 小时前
python~基础
开发语言·python·学习