#IO进程 笔记一

news2024/11/15 0:54:45

标准IO
文件IO
文件属性获取
目录操作

进程: process
线程(thread)、同步、互斥、条件变量
进程间通信: 6种(一共7种)
无名管道(pipe)、有名管道(fifo)、信号(sginal)、信号灯集(semphore)、
共享内存(shared memory)、消息队列(message queue)


标准IO

1. 概念

标准IO:是在C库中定义的一组专门用于输入输出的函数。

image.png


2. 特点

(1) 通过缓冲机制减少系统调用,提高效率
系统调用:内核向上提供的一组接口
例如:从硬盘读1KB文件,每次只能读1B

image.png


(2) 围绕流进行操作,流用FILE*来描述

image.png


(3) 标准IO默认打开三个流,stdin(标准输入)、stdout(标准输出)、stderr(标准错误)

image.png


注意: vi用ctags索引使用:
1) vi -t 查找名称
输入前面序号,回车。
2) 继续追踪:
将光标定位到要追踪的内容,ctrl+]
回退:ctrl+t
3)跳转到上次位置:ctrl+o
跳转到下次位置:ctrl+i

3. 缓存区

(1) 全缓存: 和文件相关

(2) 行缓存:和终端相关

(3) 不缓存:没有缓存,标准错误

刷新标准输出缓冲的条件:
● \n
● 程序正常退出
● 强制刷新:fflush(NULL)
● 缓冲区满

#include <stdio.h>



int main(int argc, char const *argv[])

{

printf("hello world");

//printf("hello world\n"); //\n不光是换行还可以刷新缓冲区,将输出缓冲区的内容啥刷新到终端

fflush(NULL); //强制刷新缓存区

while (1);

return 0; //程序正常退出刷新缓存

}


练习: 计算标准输出缓存区的大小KB
方法一:利用循环打印展示

image.png


方法二:利用结构体指针stdout

#include <stdio.h>

int main(int argc, char const *argv[])

{

printf("buf:");

printf("%d\n", stdout->_IO_buf_end - stdout->_IO_buf_base);

}

得到1024 B
综上:当我们每次要打印数据时,并不是将数据直接发送给标准输出设备,也就是并直接发送给显示器,而是将要打印的数据先存放到缓存区,当缓冲存数据满时,或者遇到\n,或者程序结束时,或者手动刷新缓存区时,缓冲区才会把数据传输到标准输出设备中,也就是显示器中进行输出。

4. 函数接口

1. fopen

FILE *fopen(const char *path, const char *mode);
功能:打开文件
参数:
    path:打开的文件路径
    mode:打开的方式
        r:只读,当文件不存在时报错,文件流定位到文件开头
        r+:可读可写,当文件不存在时报错,文件流定位到文件开头
        w:只写,文件不存在创建,存在则清空
        w+:可读可写,文件不存在创建,存在则清空
        
        a:追加(在末尾写),文件不存在创建,存在追加,文件流定位到文件末尾
        a+:读和追加,文件不存在创建,存在追加,读文件流定位到文件开头,写文件流定位到文件末尾
        
注:当a+的方式打开文件时,写只能在末尾进行追加,定位操作是无法改变写的位置,但是可以改变读的位置


//当以追加模式(a 或 a+)打开文件时,fseek 的行为有一些特殊之处。
//这是因为在追加模式下,所有写操作都会自动定位到文件末尾,
//无论 fseek 如何设置文件指针的位置。
//因此,在这种模式下,fseek 对于写操作来说不起作用,
//但对于读操作仍然有效。

返回值:
成功:文件流
失败:NULL,并且会设置错误码

2. fclose

int fclose(FILE* stream);
功能:关闭文件
参数:stream:文件流
#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    //打开文件
    //fp = fopen("test.txt", "r"); //只读,没有文件会报错
    fp = fopen("test.txt", "w"); //只写,没有文件创建,有则清空

    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen sucess\n");

    //关闭文件
    fclose(fp);
    return 0;
}

补充:perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。参数 s 所指的字符串会先打印出, 后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。 在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,该函数已经重新设置了errno的值。perror函数只是将你设置的一些信息和现在的errno所对应的错误一起输出。

3. 读写文件操作

3.1 每次读写一个字符:fgetc()、fputc()
每次读一个字符fgetc()
int  fgetc(FILE * stream);
功能:从文件中读取一个字符,并将当前文件指针位置向后移动一个字符。
参数:stream:文件流
返回值:成功:读到的字符
       失败或读到文件末尾:EOF(-1)

每次写一个字符fputc()
int fputc(int c, FILE * stream);
功能:向文件中写入一个字符, 成功写入后文件指针会自动向后移动一个字节位置。
参数:c:要写的字符
   	  stream:文件流
返回值:成功:写的字符的ASCII
       失败:EOF(-1)

(1) 针对文件
文件内容为:hello

#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    //打开文件
    fp = fopen("test.txt", "r+");

    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen sucess\n");

    //读写文件
    char ch = fgetc(fp);
    printf("%d %c\n", ch, ch);  //h

    ch = fgetc(fp);
    printf("%d %c\n", ch, ch);  //e

    fputc('a', fp);

    ch = fgetc(fp);
    printf("%d %c\n", ch, ch);  //l

    ch = fgetc(fp);
    printf("%d %c\n", ch, ch);  //o

    ch = fgetc(fp);
    printf("%d %c\n", ch, ch);  //EOF

    //关闭文件
    fclose(fp);
    return 0;
}


(2) 针对终端

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char ch = fgetc(stdin);
    printf("%c %d\n", ch, ch);

    ch = fgetc(stdin);
    printf("%c %d\n", ch, ch);

    fputc('b',stdout);

    return 0;
}


补充:feof和ferror

int  feof(FILE * stream);
功能:判断文件有没有到结尾,也就是当前所在位置后面还有没有字符。
返回:如果到达文件末尾,返回非零值。如果后面还有字符则返回0。


例如:

image.png

int ferror(FILE * stream);
功能:检测文件有没有出错
返回:文件出错,返回非零值。如果没有出错,返回0。

image.png


练习:cat 文件名

#include <stdio.h>

/*  cat 文件名:查看文件内容,显示到终端上
    步骤:
    1.打开文件
    2.循环用fgetc获取文件内容
    3.直到返回值是EOF就结束
    4.将读到的内容打印到终端
    5.关闭文件
*/
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("format: %s <filename>", argv[0]);
        return -1;
    }

    FILE *fp;
    //1.打开文件
    fp = fopen(argv[1], "r");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen sucess\n");

    //循环用fgetc读文件,只要读到就打印到终端上
    char ch;
    // while ((ch = fgetc(fp)) != EOF)
    //     fputc(ch, stdout);

    while (1)
    {
        ch = fgetc(fp);
        if (feof(fp))  //if(ch==EOF);break;
            break;
        fputc(ch, stdout);
    }

    //关闭文件
    fclose(fp);
    return 0;
}
3.2 每次读写一个字符串:fgets()和fputs()
char * fgets(char *s,  int size,  FILE * stream);
功能:从文件中每次读取一行字符串
参数:   s:存放字符串的地址
         size:期望一次读取的字符个数
         stream:文件流
返回值:成功:s的地址
       失败或读到文件末尾:NULL
特性:每次实际读取的字符个数最多为size-1个,会在末尾自动添加\0
     每次读一行,遇到\n或者到达文件末尾后不再继续读下一行并把它存储在s所指向的字符串内。

int  fputs(const char *s,  FILE * stream);
功能:向文件中写字符串
参数:s:要写的内容
     stream:文件流
返回值:成功:非负整数
       失败:EOF

(1) 针对终端

char buf[32] = "";
    fgets(buf, 32, stdin); //buf:hello\n\0
    printf("%s\n", buf);  

    fgets(buf,32,stdin);  //输入wolrd\n,此时buf:world\n\0
    fputs(buf,stdout);


(2) 针对文件

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "";
    FILE *fp = fopen("test.txt", "r+");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    printf("fopen success\n");

    //读写操作
    fgets(buf, 32, fp); //buf:hello\n\0
    fputs(buf, stdout);

    fgets(buf, 32, fp); //buf:world\n\0
    fputs(buf, stdout);

    fgets(buf, 32, fp); //buf:666\0d\n\0
    fputs(buf, stdout);

    return 0;
}


练习:通过fgets实现"wc -l 文件名"命令功能(计算文件行数, 相当于数有多少换行)
思路:打开文件,循环读文件,只要读到就判断是否有换行,如果有换行就累加。

#include <stdio.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    int len = 0;
    char buf[32] = "";
    FILE *fp;

    if (argc != 2)
    {
        printf("format: %s <filename>\n", argv[0]);
        return -1;
    }

    // 打开文件
    fp = fopen(argv[1], "r");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    // 失败或读到文件末尾:NULL
    // 循环用fgets读取文件内容,只要读到就判断是否有\n,有就累加
    while (fgets(buf, sizeof(buf), fp) != NULL)
    {
        // 检查最后一个字符是否是换行符
        if (buf[strlen(buf) - 1] == '\n')
            len++;
    }

    // 检查文件最后一行是否没有换行符
    if (buf[strlen(buf) - 1] != '\n' && strlen(buf) > 0)
        len++;

    printf("%d %s\n", len, argv[1]);
    fclose(fp);

    return 0;
}


练习:使用C语言编写一段程序,实现从1开始以每秒累加1的方式向文件中写入数字,写到100后停止。要求代码格式规范,输出结果清晰易懂。(北京凝思软件股份有限公司笔试题)
补充:fprintf:格式化输出到流(stream)文件中,返回值是输出的字符数,发生错误时返回一个负值.
int fprintf( FILE *stream, const char *format, ... );
睡眠1秒:sleep(1);

#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    FILE *fp;
    int i;

    if (argc != 2)
    {
        printf("Usage: %s <filename>\n", argv[0]);
        return -1;
    }

    // 打开文件,如果文件不存在则创建
    fp = fopen(argv[1], "w");
    if (fp == NULL)
    {
        perror("fopen error");
        return -1;
    }

    // 每秒向文件中写入一个递增的数字,从1到100
    for (i = 1; i <= 100; i++)
    {
        fprintf(fp, "%d\n", i);
        fflush(fp);  // 刷新缓冲区,确保数据写入文件
        sleep(1);    // 休眠1秒
    }

    fclose(fp);  // 关闭文件
    return 0;
}


练习:
编程读写一个文件test.txt,每隔1秒向文件中写入一行录入时间的数据
作业:题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行录入时间的数据,类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
思路:
1. 打开文件fopen,循环往文件写内容
2. 每隔1s写入一行,sleep(1);
3. 计算文件行数,wc -l
4. 计算当前时间,转换成年月日、时分秒,time,localtime
man 2 time
time_t time(time_t *t);
如果t是空指针,直接返回当前时间。如果t不是空指针,返回当前时间的同时,将返回值赋予t指向的内存空间。
man 3 localtime
struct tm *localtime(const time_t *timep);
5. 字符串拼接函数:strcpy/strcat(dest, src)、sprintf、fprintf

fprintf:
格式化输出到流(stream)文件中,返回值是输出的字符数,发生错误时返回一个负值.
int fprintf( FILE *stream, const char *format, ... );
sprintf:
格式化输出发送到buffer(缓冲区)中.返回值是写入的字符数量.
int sprintf( char *buffer, const char *format, ... );
思路:
FILE *fp = fopen();
//计算文件行数
while(fgets()!=NULL)
if(buf[] == '\n')
n++;
//往文件中写入内容
while(1)
{
//计算时间
time_t t = time(NULL); //time(&t);
struct tm *tm = localtime(&t);
fprintf(fp, "%d,%d");
sleep(1);
}

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    time_t t;
    struct tm *tm;
    int len = 0;
    char buf[32] = "";
    FILE *fp;
    fp = fopen(argv[1], "a+");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }

    while (fgets(buf, 32, fp) != NULL)
    {
        if (buf[strlen(buf) - 1] == '\n')
            len++;
    }

    while (1)
    {
        t=time(NULL);
        tm = localtime(&t);
        fprintf(fp, "%d,%d-%d-%d %d:%d:%d\n", ++len, tm->tm_year + 1900,
                tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec);
        fflush(NULL); // 全缓存,文件里面不能用\n刷新缓存所以需要手动刷新
        sleep(1);
    }

    fclose(fp);
    return 0;
}

3.3 二进制读写fread()和fwrite()
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:从文件流读取多个元素(将二进制数据从文件读出)
参数:  ptr :是一个指针,是存放数据的存储空间的起始地址,用来存放读取元素
       size :元素大小  sizeof(元素数据类型)
       nmemb :读取元素的个数
       stream :要读取的文件流
返回值:成功:读取的元素的个数
       读到文件尾或失败: 0

size_t fwrite(const void *ptr, size_t size, size_t nmemb,
              FILE *stream);
功能:将二进制数据写入文件
参数: ptr :是一个指针,保存要输出数据的空间的地址。
     size :要写入的字节数 sizeof(数据类型)
     nmemb : 要进行写入元素的个数
      strem: 目标文件流指针
返回值:成功:写的元素个数
              失败 :-1

a. 针对终端

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char buf[32]="";
    fread(buf, sizeof(char), 10, stdin);
    printf("%s\n", buf);
    fwrite(buf,1,10,stdout);
    return 0;
}


b. 针对文件

#include <stdio.h>

int main(int argc, char const *argv[])
{
    float arr[3] = {1.2, 3.4, 5.6};
    float data[3] = {0};
    FILE *fp;
    fp = fopen("test.txt", "r+");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }
    fwrite(arr, 4, 3, fp);

    rewind(fp); //将文件位置定位到文件开头

    fread(data, 4, 3, fp);

    printf("%f %f %f\n", data[0], data[1], data[2]);

    return 0;
}


文件定位操作:rewind(FILE *fp);

4. 其他操作

1. 重定向流到文件 freopen

freopen()用于将指定的流重定向到打开文件

FILE * freopen(const char *pathname,  const char *mode,  FILE* fp);
功能:将指定的文件流重定向到打开的文件中
参数:path:文件路径
     mode:打开文件的方式(同fopen)
     fp:文件流指针
返回值:成功:返回文件流指针
      失败:NULL

2. 文件定位操作 fseek
void rewind(FILE *stream);
功能:将文件位置指针定位到起始位置

int fseek(FILE *stream, long offset, int whence);
功能:文件的定位操作
参数:stream:文件流
     offset:偏移量:正数表示向后文件尾部偏移,负数表示向文件开头偏移
     whence:相对位置:
           SEEK_SET:相对于文件开头
           SEEK_CUR:相对于文件当前位置
           SEEK_END:相对于文件末尾
 返回值:成功:0
       失败:-1   
         
补充:其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.

例如:
把fp指针移动到离文件开头100字节处:fseek(fp,100,0);
把fp指针移动到离文件当前位置100字节处: fseek(fp,100,1);
把fp指针退回到离文件结尾100字节处: fseek(fp,-100,2);

long ftell(FILE *stream);
功能:获取当前的文件位置
参数:要检测的文件流
返回值:成功:当前的文件位置,出错:-1

笔试题:
1. 相对一个文本文件的尾部追加写入,应当在fopen何中使用的文件操作方式指示符号为 (C)(杭州快越科技笔试题)
A. r B.wb C. a D.w+
2. fseek(1, 2, 3); 这个函数是什么作用,三个参数分别是什么意思?(深圳元征信息科技)
3. 函数调用语句:fseek (fp,-10L,2);的含义是A
A 将文件位置指针从文件未尾处向文件头的方向移动10个字节
B 将文件位置指针从当前位置向文件头的方向移动10个字节
C 将文件位置指针从当前位置向文件未尾方向移动10个字节
总结:
为什么用标准IO
1. 因为读写文件通常是大量的数据(相对于底层驱动的系统调用所实现的数据操作单位),这时,使用库函数可以大大减少系统调用的次数。
2. 为了保证可移植性
关于缓存区: 库函数的缓冲区对于库函数,如果标准输出连到终端设备(直接输出到屏幕),则它是行缓冲的(遇到回车换行符或者是缓冲区满了才输出);否则(输出到文件)是全缓冲的(缓冲区填满或者是程序运行结束了才输出)。程序运行结束时,会刷新所有的缓冲区。

文件IO

1. 概念

又称系统IO,是系统调用,是操作系统提供的接口函数。
posix中定义的一组用于输入输出的函数
POSIX接口 (英语:Portable Operating System Interface)可移植操作系统接口

2. 特点

(1) 没有缓冲机制,每次调用都会引起系统调用
(2) 围绕着文件描述符进行操作,非负整数(>=0),依次分配
(3) 文件IO默认打开三个文件描述符,分别是0(标准输入)、1(标准输出)、2(标准错误)
(4) 文件任意操作除了目录以外其他类型的文件: b c - l s p

问题:打开三个文件,描述符分别是:3 4 5
关闭4以后,重新打开这个文件,描述符是几?

答:还是4
问题:一个进程的文件描述符最大到几?最多能打开多少个文件描述符?最多能打开多少个文件?
答:一个进程的文件描述符最大到1023,最多能打开1024(0-1023)个文件描述符, 最多能打开1024-3个文件。

3. 操作

打开文件:open
关闭文件: close
读写操作:read、write
定位操作: lseek

4. 函数接口

1. 打开文件open()

man 2 open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打开文件
参数:pathname:文件路径名
     flags:打开文件的方式
            O_RDONLY:只读
            O_WRONLY:只写
            O_RDWR:可读可写
            O_CREAT:不存在创建
            O_TRUNC:存在清空
            O_APPEND:追加   
返回值:成功:文件描述符
       失败:-1

当第二个参数中有O_CREAT选项时,需要给open函数传递第三个参数,指定创建文件的权限 
int open(const char *pathname, int flags, mode_t mode);
最后权限=创建出来的文件指定权限值&(~umask)  
例如:指定权限为0666(8进制)
最终权限为0666&(~umask) = 0666&0775 = 0664
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
    int fd;
    //fd = open("a.c", O_RDONLY);  //r
    fd = open("a.c", O_WRONLY|O_CREAT|O_TRUNC,0666); //w
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    printf("fd: %d\n", fd);

    return 0;
}


思考:文件IO和标准IO的打开方式的对应关系

标准IO

文件IO

r

O_RDONLY 只读

r+

O_RDWR 可读可写

w

O_WRONLY|O_CREAT|O_TRUNC, 0777
只写,不存在创建,存在清空

w+

O_RDWR|O_CREAT|O_TRUNC,0666
可读可写,不能存在创建,存在清空

a

O_WRONLY|O_CREAT|O_APPEND, 0666

a+

O_RDWR|O_CREAT|O_APPEND, 0666
可读可写,不存在创建,存在追加

注意:有O_CREAT需要加第三个参数权限

2. 关闭文件 close()

#include <unistd.h>
int close(int fd);
功能:关闭文件
参数:fd:文件描述符

3. 读写文件

读文件

ssize_t read(int fd, void *buf, size_t count);
功能:从一个已打开的可读文件中读取数据
参数: fd  文件描述符
      buf  存放位置
      count  期望的个数
返回值:成功:实际读到的个数(小于期望值说明实际没这么多)
       返回0:表示读到文件结尾
       返回-1:表示出错,并设置errno号


fgetc: 末尾或失败EOF
fgets: 末尾或失败NULL
fread: 末尾或失败 0
read: 末尾0 失败-1

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "";
    int fd;
    fd = open("a.c", O_RDWR); //r+
   
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    printf("fd: %d\n", fd);
    read(fd, buf, 10);
    printf("buf: %s\n", buf);

    close(fd);

    return 0;
}


写文件

ssize_t write(int fd, const void *buf, size_t count);
功能:向指定文件描述符中,写入 count个字节的数据。
参数:fd   文件描述符
      buf   要写的内容
      count  期望写入字节数
返回值:成功:实际写入数据的个数
              失败  : -1
//返回值小于期望值是错误行为,可能磁盘满了无法再写。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "hello";
    int fd;
    fd = open("a.c", O_RDWR); //r+
    
    if (fd < 0)
    {
        perror("open err");
        return -1;
    }
    printf("fd: %d\n", fd);
    //write(fd,"hello",5);
    write(fd,buf,10); //写入文件:hello


练习:文件IO实现cp功能。cp 源文件 新文件名
./a.out src dest
思路:打开源文件和目标文件,循环读源文件,只要读到内容就写入目标文件。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "hello";
    int fd_src, fd_dest;
    if (argc != 3)
    {
        printf("formt: %s <srcname> <destname>\n", argv[0]);
        return -1;
    }

    //打开源文件和目标文件
    fd_src = open(argv[1], O_RDONLY);
    if (fd_src < 0)
    {
        printf("open src err");
        return -1;
    }

    fd_dest = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd_dest < 0)
    {
        printf("open dest err");
        return -1;
    }

    //循环读源文件,读到就写入目标文件
    ssize_t s;
    while ((s = read(fd_src, buf, 32)) > 0)
        write(fd_dest, buf, s);

    //关闭两个文件
    close(fd_src);
    close(fd_dest);

    return 0;
}


4. 文件定位操作
练习:向文件中第 10 位置后面写一个字符,在文件此时的位置,往后移动20个位置处,写一行字符串hello进去,求此时文件的长度。

off_t lseek(int fd, off_t offset, int whence);
功能:设定文件的偏移位置
参数:fd:文件描述符
    offset: 偏移量  
        正数:向文件结尾位置移动
        负数:向文件开始位置
    whence: 相对位置
        SEEK_SET   开始位置
        SEEK_CUR   当前位置
        SEEK_END   结尾位置
补充:和fseek一样其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2.
返回值:成功:文件的当前位置
        失败:-1


5. 标准IO和文件IO总结

标准IO

文件IO

概念

C库中定义的一组用于输入输出的函数

POSIX中定义的一组用于输入输出的函数

特点

1. 有缓冲机制
2. 围绕流进行操作,FILE*
3. 默认打开三个流:stdin/stdout/stderr
4. 只能操作普通文件
5. 可移植性更好

1. 无缓冲机制
2. 围绕文件描述符操作,非负整数
3. 默认打开三个文件描述符:0/1/2
4. 可以操作除了目录以外任意类型文件
5. 可移植性相对较差

函数

打开文件:fopen、freopen
关闭文件:fclose
读文件:fgetc、fgets、fread
写文件: fputc、fputs、fwrite
定位操作:rewind、fseek、ftell

打开文件: open
关闭文件: close
读文件:read
写文件:write
定位操作:lseek

获取文件属性

1. stat函数

man 2 stat

int stat(const char *path, struct stat *buf);
功能:获取文件属性
参数:  path:文件路径名
       buf:保存文件属性信息的结构体的地址
返回值:成功:0
      失败:-1
struct stat {
        ino_t     st_ino;     /* inode号 ls -il */     
        mode_t    st_mode;    /* 文件类型和权限 */
        nlink_t   st_nlink;   /* 硬链接数 */
        uid_t     st_uid;     /* 用户ID */
        gid_t     st_gid;     /* 组ID */
        off_t     st_size;    /* 大小 */
        time_t    st_atime;   /* 最后访问时间 */
        time_t    st_mtime;   /* 最后修改时间 */
        time_t    st_ctime;   /* 最后状态改变时间 */
    };
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
    struct stat st;
    if (stat("a.c", &st) < 0)
    {
        perror("stat errr");
        return -1;
    }

    printf("inode:%lu nlink:%d size:%ld\n", st.st_ino,
           st.st_nlink, st.st_size);

    return 0;
}

文件权限和类型需要通过位操作获取:

st_mode 主要包含了 3 部分信息:
a. 15bit ~ 12bit 保存文件类型
b. 11bit ~ 9bit 保存执行文件时设置的信息(不用管)
c. 8bit ~ 0bit 保存文件访问权限

printf("st_mode is:%#o\n", st.st_mode);

2. 获取文件类型

S_IFMT是一个掩码,它的值是0170000(注意这里用的是八进制前缀为0,二进制0b001111000000000000), 可以用来把st_mode位与上掩码过滤提取出表示的文件类型的那四位(15bit~12bit位),也就是这四位原样获取其他位清零。

这四位可以表示0b0000~0b1111(八进制表示:001~014)七个值,每个值分别对应不同的文件类型:套接字文件、符号链接文件、普通文件、块设备、目录、字符设备、管道。
通过man手册可以看出,判断一个文件是不是普通文件,首先通过掩码S_IFMT把其他无关的部分置0,再与表示普通文件的数值比较,从而判断这是否是一个普通文件:


解释:

//判断文件类型
    if ((st.st_mode & S_IFMT) == S_IFREG)
        printf("- ");
    else if ((st.st_mode & S_IFMT) == S_IFDIR)
        printf("d ");

    //也可以用宏函数
    if(S_ISREG(st.st_mode))
        printf("- ");
    else if(S_ISDIR(st.st_mode))
        printf("d ");


作业:
1. 吸收完善今天所学内容整理笔记,代码敲两遍和笔记一起发群里。
2. 用标准IO实现cp功能

#include <stdio.h>

int main(int argc, char const *argv[])
{
    FILE *fp_src, *fp_dest;
    char buf[32] = "";
    if(argc != 3)
    {
        printf("Usage: %s <srcfile> <destfile>\n",argv[0]);
        return -1;
    }

    fp_src = fopen(argv[1], "r");
    if (fp_src == NULL)
    {
        perror("open src file err");
        return -1;
    }
    fp_dest = fopen(argv[2],"w");
    if (fp_dest == NULL)
    {
        perror("open dest file err");
        return -1;
    }

    // while (1)
    // {
    //     if(feof(fp_src))
    //         break;
    //     fgets(buf,32,fp_src);
    //     fputs(buf,fp_dest);
    // }
    while (fgets(buf,32,fp_src) != NULL)
    {
        fputs(buf,fp_dest);
    }
    
    fclose(fp_src);
    fclose(fp_dest);
    return 0;
}


3. 尽力实现以下练习
练习: 实现“head -n 文件名”命令的功能
实现“head -n 文件名”命令的功能
例:head -30 test.c -> ./a.out -3 test.c
atoi : "1234" -- 1234
argv[1]: "-30"
argv[1]+1: "30"
思想:循环打印,来一行+1行数,打印这行,然后判断是否达到最后一行,达到退出。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char const *argv[])
{
    FILE *fp;
    int num, len = 0;
    char buf[32] = "";
    if (argc != 3)
    {
        printf("format: %s -n <filename>\n", argv[0]);
        return -1;
    }

    fp = fopen(argv[2], "r");
    if (NULL == fp)
    {
        perror("fopen err");
        return -1;
    }

    num = atoi(argv[1] + 1); //./a.out -3 a.c, +1代表通过地址往后移动一个单位去掉-
    if(num==0)
        return 0;

//循环读文件,只要读到就打印到终端,数行数,行数len累加直到达到num就退出
    while (fgets(buf, 32, fp) != NULL)
    {
        if (buf[strlen(buf) - 1] == '\n')
            len++;
        fputs(buf, stdout);
        //printf("%s",buf);
        if(len==num)
            break;
    }
    fclose(fp);
    return 0;
}


3. 获取文件权限

0-8bit位每一位表示一个权限,所以只需要把这一位位与出来就可以判断是否有这个权限,为1说明有,为0说明没有。
比如判断个人权限是否有可读: st.st_mode&0b000000100000000(八进制:00400)
也就是利用宏: st.st_mode&S_IRUSR

//判断文件权限
    //个人权限
    if (st.st_mode & S_IRUSR)
        putchar('r');
    else
        putchar('-');

    if (st.st_mode & S_IWUSR)
        putchar('w');
    else
        putchar('-');

    if (st.st_mode & S_IXUSR)
        putchar('x');
    else
        putchar('-');


解释:

练习:编程实现“ls -l 文件名”功能
getpwuid
getgrgid
localtime或ctime
ctime函数在C库中,头文件为<time.h>
函数原型:
char *ctime (const time_t *__timer)
作用:返回一个表示当地时间的字符串,当地时间是基于参数 timer
格式例如: Wed Aug 29 19:48:54 2018

//硬连接数
    printf(" %d", st.st_nlink);

    //用户名 用getpwuid函数
    printf(" %s", getpwuid(st.st_uid)->pw_name);

    //组名 用getgrgid函数
    printf(" %s", getgrgid(st.st_gid)->gr_name);

    //文件大小
    printf(" %ld", st.st_size);

    //最后修改时间戳 用ctime函数
    printf(" %.12s", ctime(&st.st_mtime) + 4); //+4代表跳过前四位,%.12代表只打印前12个字符

    //名字
    printf(" %s\n", argv[1]);


补充: 打印字符串%.ns 代表只打印字符串的前几个字符,例如%.12s代表只打印前12个字符
stat/fstat/lstat的区别?
stat函数返回一个与此命名文件有关的信息结构
fstat函数获得已在描述符filedes上打开的文件的有关信息,也就是参数是文件描述符,其他与stat相同。
lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号连接引用的文件的信息.

目录操作

围绕目录流进行操作:DIR *
opendir
closedir
readdir
chdir

DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
       失败:NULL

struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息    
       失败:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
        ino_t   d_ino;               /* 索引节点号*/
        off_t   d_off;               /*在目录文件中的偏移*/
        unsigned short d_reclen;     /* 文件名长度*/
        unsigned char  d_type;       /* 文件类型 */
        char    d_name[256];         /* 文件名 */
};

int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流
DIR *opendir(const char *name);
功能:获得目录流
参数:要打开的目录
返回值:成功:目录流
       失败:NULL

struct dirent *readdir(DIR *dirp);
功能:读目录
参数:要读的目录流
返回值:成功:读到的信息    
       失败:NULL
返回值为结构体,该结构体成员为描述该目录下的文件信息
struct dirent {
        ino_t   d_ino;               /* 索引节点号*/
        off_t   d_off;               /*在目录文件中的偏移*/
        unsigned short d_reclen;     /* 文件名长度*/
        unsigned char  d_type;       /* 文件类型 */
        char    d_name[256];         /* 文件名 */
};

int closedir(DIR *dirp);
功能:关闭目录
参数:dirp:目录流


这里读的顺序不是按照ls排序的顺序,因为ls自身有个排序算法。这里是按照文件在磁盘的顺序读取的。
练习:实现 ls -a

#include <stdio.h>
#include <sys/types.h>
#include <dirent.h>
int main(int argc, char const *argv[])
{
    DIR *dir;
    struct dirent *d;
    dir = opendir(".");
    if (NULL == dir)
    {
        perror("opendir err");
        return -1;
    }
    while ((d = readdir(dir)) != NULL)
    {
        printf("%s\n", d->d_name);
    }
    return 0;
}


头文件:
#include<stdio.h>
#include"head.h"
<> 从系统中查找
"" 从当前目录查找,如果没有再去系统中查找
头文件就是.h结尾的文件,包含:其他头文件的引用,结构体、共用体、枚举的定义,函数声明,宏定义,重命名,外部引用,条件编译
源文件:包含main函数的.c文件
包含其他子函数的.c文件,封装的函数声明写在头文件里面
库文件(不能包括main函数)

注意:例如printf的原码已经做成库文件,但是编译的时候不需要链接。是因为系统会自动链接C库,不需要手动链接。

1. 库的定义

当使用别人的函数时除了包含头文件以外还需要有库
头文件:其他头文件的引用,结构体、共用体、枚举的定义,函数声明,宏定义,重命名,外部引用,条件编译
库:把一些常用的函数的目标文件打包在一起,提供相应的函数接口,便于程序员使用。本质上来说库是一种可执行代码的二进制形式文件。
由于windows和linux的本质不同,因此而这库的二进制是不兼容的。(Linux中的C运行库是glibc, 由GUN发布。)

2. 库的分类

静态库和动态库,本质区别是代码被载入时刻不同。
2.1 静态库
静态库在程序编译时会被复制到目标代码中, 以.a结尾。
优点:程序运行时将不再需要该静态库,运行时无需加载库,运行速度更快,可移植性好。
缺点:静态库中的代码复制到了程序中,因此体积较大,静态库升级后,程序需要重新编译链接。
2.2 动态库
动态库是在程序运行时才被载入代码中。也叫共享库,以.so结尾。
优点:程序在执行时加载动态库,代码体积小,升级简单。
不同应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例。
缺点:运行时还需要动态库的存在,移植性较差。

3. 静态库的制作

(1) 将源文件编译生成目标文件
gcc -c fun.c -o fun.o
(2) 创建静态库用ar命令,将很多.o转换成.a
ar crs libfun.a fun.o
(3) 测试使用静态库
gcc main.c -L. -lfun //-L指定库的路径,-l指定库名
执行 ./a.out

4. 动态库制作

(1) 用gcc来创建共享库
gcc -fPIC -c fun.c -o fun.o //-fPIC创建与地址无关的编译程序
gcc -shared -o libmyfun.so fun.o
(2) 测试使用动态库
gcc main.c -lmyfun
执行:./a.out
./a.out: 可以正常编译通过,但是运行时报错error while loading shared libraries: libmyadd.so: cannot open shared object file: No such file or directory
原因:当加载动态库时,系统会默认从/lib或/usr/lib路径下查找库文件,所以不用-L加路径了,所以把动态库拷贝的系统路径下,然后直接gcc main.c -lmyfun就可以了
解决方法(有三种):
1) 把库拷贝到/usr/lib和/lib目录下。(此方法编译时不需要指定库的路径)
2) 在LD_LIBRARY_PATH环境变量中加上库所在路径。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.
(终端关闭,环境变量就没在了)
3) 添加/etc/ld.so.conf.d/*.conf文件。把库所在的路径加到文件末尾,并执行ldconfig刷新
sudo vi xx.conf
添加动态库存在的路径,如:
/home/hq/work/lib

选项:
-L路径:指定库的路径
-I(大写i) 路径: 指定头文件的路径 默认系统路径/usr/include
#include<stdio.h> //从系统路径下查找
#include"head.h" //先从当前路径下查找,没有再去系统路径下查找

-l(小写l) 库名: 指定连接的库名
ldd 可执行文件名: 查看该可执行程序链接的动态库

5. 总结静态库和动态库

静态库:编译阶段,.a结尾,体积大,移植性好,升级麻烦。
动态库:执行阶段,.so结尾,体积小,移植性差,升级简单。
可以看出静态库编译出来的程序体积大:

升级演示: 改变源文件,重新制作库
静态库升级:需要重新编译,升级麻烦

注意:如果静态库和动态库同名,默认优先使用动态库,如果想使用同名静态库可以加个选项-static,这是内核规定的。
动态库升级:不需要重新编译,只需要把新的库放进系统路径就可以了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1957300.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详细分析示波器导至U盘的数据(Excel表格)示波器具体名称分析

一般由示波器导入U盘的csv文件&#xff08;即Excel表格数据&#xff09;的图如下图所示&#xff1a; 下面小编就对上表格的各个数据表示进行逐一解释 1、Memory Length&#xff1a;4000 在示波器&#xff08;Oscilloscope&#xff09;中&#xff0c;“Memory Length”&#x…

【算法】代码随想录之字符串(更新中)

文章目录 前言 一、反转字符串&#xff08;LeetCode--344&#xff09; 二、反转字符串II&#xff08;LeetCode--541&#xff09; 三、反转字符串中的单词&#xff08;LeetCode--151&#xff09; 前言 跟随代码随想录&#xff0c;学习字符串相关的算法题目&#xff0c;记录…

20240730 每日AI必读资讯

&#x1f3ac;燃爆&#xff01;奥运8分钟AI影片火了&#xff0c;巴赫主席&#xff1a;感谢中国黑科技 - 短片名为《永不失色的她》&#xff08;To the Greatness of HER&#xff09;&#xff0c;由阿里巴巴和国际奥委会联合推出。 - 百年奥运史上伟大女性的影响故事在此被浓缩…

VBA技术资料MF183:将图片导入word并调整大小

我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#xff0c;可以大大提高自己的工作效率&#xff0c;而且可以提高数据的准确度。“VBA语言専攻”提供的教程一共九套&#xff0c;分为初级、中级、高级三大部分&#xff0c;教程是对VBA的系统讲解&#…

人生最大的毛病,就是一个“ 傲 ”字

99天 傲慢之害&#xff0c;人要勤勉恭敬 51.27 先生说&#xff1a;人生最大的毛病&#xff0c;就是一个“ 傲 ”字。 当今人们的问题&#xff0c;主要就是“ 傲 ”。千罪百恶&#xff0c;都从傲而来。一个人要是傲&#xff0c;就会自高自大、自以为是&#xff0c;不肯屈于人下…

cf960(div2)

A. Submission Bait&#xff08;博弈&#xff09; 题意&#xff1a;爱丽丝和鲍勃在大小为n的数组a中进行游戏&#xff0c;他们轮流进行运算&#xff0c;爱丽丝先开始&#xff0c;不能运算的一方输&#xff0c;一开始mx0&#xff0c;每次操作&#xff0c;玩家可以选择一个牵引i…

pikachu靶场之目录遍历、敏感信息泄露

一、目录遍历 漏洞概述 在web功能设计中,很多时候我们会要将需要访问的文件定义成变量&#xff0c;从而让前端的功能便的更加灵活。 当用户发起一个前端的请求时&#xff0c;便会将请求的这个文件的值(比如文件名称)传递到后台&#xff0c;后台再执行其对应的文件。 在这个过…

汽车辐射大?技术来救它:整车辐射抗扰发射天线仿真建模及性能预测

摘要 针对车辆电磁辐射抗扰度测试条件要求高、预测难度大的问题&#xff0c;通过仿真软件建立电磁抗扰度测试发射天线&#xff08;简称抗扰发射天线&#xff09;模型及无车情况下的电磁抗扰试验场强环境&#xff0c;为整车电磁辐射抗扰性能的预测搭建了一个仿真平台。 验证试验…

第5章Excel数据分析之数据透视表遇见SQL

文章目录 第5章 数据透视表遇见SQL5-1如何在查询中使用SQL语句&#xff1f;5-2SQL查询语句&#xff08;数据透视表的辅助列&#xff09;5-3SQL常用运算符&#xff08;案例&#xff1a;添加分析维度&#xff09;5-4SQL筛选语句&#xff08;数据透视表数据源的过滤&#xff09;5-…

【单片机毕业设计选题24085】-基于STM32的心电采集系统设计

系统功能: 系统上电后&#xff0c;OLED显示“欢迎使用心电采集系统请稍后”两秒后进入正常页面显示。 第一行显示心率和血氧值。 第二行显示心率设定高值。 第三行显示心率设定低值。 第四行显示心率状态&#xff0c;"Rate OK", "Rate High", "R…

C++中的依赖注入

目录 1.概述 2.构造函数注入 3.setter方法注入 4.接口注入 5.依赖注入框架 6.依赖注入容器 7.依赖注入框架的工作原理 8.依赖注入的优势 9.总结 1.概述 依赖注入是一种设计模式&#xff0c;它允许我们在不直接创建对象的情况下为对象提供其依赖项&#xff1b;它通过将…

学习c语言第十四天(调试+练习)

一、调试 所有发生的事情都一定有迹可循&#xff0c;如果问心无愧&#xff0c;就不需要掩盖也就没有迹象了&#xff0c;如果问心有愧 就必然需要掩盖&#xff0c;那就一定会有迹象&#xff0c;迹象越多就越容易顺藤而上&#xff0c;这就是推理的途径。 顺着这条途径顺流而下就…

广东省某地区智慧水务平台全面升级启航(案例解析)

项目背景 随着物联网、人工智能、大数据、数字孪生等技术的应用&#xff0c;对精准调控、漏损控制、可视化管理、节能降耗、高效指挥等方面的要求越来越高。鉴于此&#xff0c;广东某地区的现有智慧水务平台已无法满足日益增长的智能化管理需求&#xff0c;亟待进行智慧化升级以…

工程计算与分析课程报告-Matlab

如下图所示的单自由度系统: m20kg, c30 Ns/m, and k3000 N/m. 1、应用数学、机械等理论知识推导出以下系统的数学模型&#xff1a; 其中&#xff1a; x坐标如上图所示&#xff0c;取其稳态点为零点。 M为重物质量 K为弹簧弹性系数 C为阻尼系数 F(t)为施加在重物上的外激励…

应用层协议HTTP

应用层协议HTTP 注1. HTTP协议2. 认识URL3. HTTP 协议请求与响应格式3.1.快速获取HTTP请求3.2 快速解析HTTP请求3.3 细致了解HTTP请求与响应 4. 完善Http.hpp代码&#xff0c;实现http协议4.1 http请求4.2再谈URL4.3构建http应答4.4 Content-Type属性 5.HTTP请求/响应属性5.1HT…

vscode+git解决远程分支合并冲突

1&#xff09;远程分支和远程分支不复杂情况合并 例如readme的冲突 可直接在github上解决 删到只剩下 #supergenius002 合并冲突测试1/合并测试冲突1合并测试冲突2/合并测试冲突2就行 《《《/》》》也要删掉 2&#xff09;但如果是复杂的冲突&#xff0c;让我们回到vscod…

glsl shader中实现canvas中的createRadialGradient效果

在网上找了好久&#xff0c;也没有发现有现成用shader去实现canvas radialGradient效果的.大部分都是简单的只有一个中心圆或者通过canvas绘制渐变再作为纹理图像进行贴图&#xff0c;没有类似像canvas有内圆与外圆&#xff0c;两圆心位置不一样&#xff0c;可以用实现类似焦点…

【开发问题记录】启动某个微服务时无法连接到seata(seata启动或配置异常)

问题记录 一、问题描述1.1 问题复现1.1.1 将Linux中的部分微服务启动1.1.2 在本地启动当时出错的服务 1.2 解决思路1.2.1 Nacos中seata相关的信息1.2.2 Linux中seata相关的信息 二、问题解决2.1 seata的配置错误2.1.1 Nacos中seata的配置问题2.1.2 命名空间问题的发现 2.2 网络…

Python数值计算(12)

本篇说说Neville方法。Neville方法的基础是&#xff0c;插值多项式可以递归的生成&#xff0c;有时进行插值的目的是为了计算某个点的值&#xff0c;这个时候并不需要将拟合曲线完全求出&#xff0c;而是可以通过递归的方式进行计算&#xff0c;具体操作如下&#xff1a; 例如…

代码实践思考:ROS1和ROS2

扩展 能否借助人工智能工具将ROS1批量转为ROS2如何更高效的进行ROS学习 ROS1大量案例直接批处理用智能工具转为ROS2案例 不同版本之间的工具软件直接用智能方式进行代码升级 例如OpenCV之类&#xff0c;一些函数变化&#xff0c;直接用智能工具进行批量代码优化 机器人画圆-…