【Linux文件篇】系统文件、文件描述符与重定向的实用指南

W...Y的主页 😊 

代码仓库分享💕 


 前言:相信大家对文件都不会太陌生、也不会太熟悉。在没有学习Linux操作系统时,我们在学习C或C++时都学过如何去创建、打开、读写等待文件的操作,知道一些语言级别的一些接口与函数。但是我相信对于没有学习操作系统的对文件的认识还很浅,今天我们就进入文件,好好学习操作系统与文件的关系。相信大家看完文件篇后会对文件有了新的认识和启发。话不多说我们开始学习。

目录

铺垫

代码回顾C文件接口

系统文件I/O 

open接口

write read close类比C文件相关接口

文件描述符fd

文件描述符的分配规则

 Linux中的重定向

 使用 dup2 系统调用

在自治shell中加入重定向 


铺垫

1.当我们创建一个文件但不对文件进行写入时,需不需要开辟一些空间呢?肯定是需要的。因为文件 = 内容 + 属性。文件的名称、创建时间、修改时间……对应的信息都需要进行存储。

2.我们访问文件之前,都得先打开文件,修改文件都是通过代码的方式完成修改的。而我们不能通过代码对磁盘中的文件直接做修改,是直接将文件加载到内存中后通过CPU进行对文件的修改。

3.我们在C语言中都知道,想要操作一个文件就必须使用fopen函数将文件打开,那是谁打开的文件呢?当我们在打开文件之前,必须要将想要访问文件的程序跑起来,所以归根结底是进程打开了文件。

4.一个进程也可以打开多个文件。所以一定时间段内,系统中存在多个进程,也可能存在更多被打开的文件。OS就要对这些被打开的文件进行管理,所以内核中一定要有描述被打开文件的结构体,并用其定义对象。

5.系统中不是所有的文件都是被打开的,被打开的文件叫内存文件,没有被打开的文件叫磁盘文件。

代码回顾C文件接口

hello.c写文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "w");if (!fp){printf("fopen error!\n");}const char *msg = "hello why!\n";int count = 5;while (count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

fopen模式中有很多模式:

r : 打开文本文件进行阅读,流位于文件的开头。

r+:开放阅读和写作,流位于文件的开头。

w:截断(缩短)文件为零长度或创建文本文件进行写入,流位于文件的开头。

w+:开放阅读和写作,如果文件不存在,则创建该文件,否则将被截断,流位于文件的开头。

a:打开以进行追加(写在文件末尾)。如果文件不存在,则创建该文件,流位于文件的末尾。

a+:打开以供读取和追加(在文件末尾写入)。如果文件不存在,则创建该文件。初始文件位置。对于阅读是在文件的开头,但输出始终附加到文件末尾。

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "r");if (!fp){printf("fopen error!\n");}char buf[1024];const char *msg = "hello bit!\n";while (1){// 注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0){buf[s] = 0;printf("%s", buf);}if (feof(fp)){break;}}fclose(fp);return 0;
}

fwrite与fread函数都是C语言提供对文件读写的接口, 其就是将对应的内容输入或输出对应的文件流。

fread函数参数:

void * buffer 也就是想要把读取的数据到的地方的地址 比如一个叫做a的整形变量的地址 也就是&a 又或许是一个结构体对象 假设他叫s 那便是&s

size_t size 这就是接收数据的变量所占空间的大小 如果他是一个int类型 那就是sizeof(int)

size_t count 要写入数据的个数

FILE*stream 这是管理文件操作的指针 一般会在开始对文件开始操作前定义 这个指针会指向我们接下来要操作的文件

fwrite函数参数与fread参数相同,只是方向刚好相反。

 输出信息到显示器,你有哪些方法:

#include <stdio.h>
#include <string.h>
int main()
{const char *msg = "hello fwrite\n";fwrite(msg, strlen(msg), 1, stdout);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");return 0;
}

 stdin & stdout & stderr

C默认会打开三个输入输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

系统文件I/O 

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

写文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);int fd = open("myfile", O_WRONLY | O_CREAT, 0644);if (fd < 0){perror("open");return 1;}int count = 5;const char *msg = "hello bit!\n";int len = strlen(msg);while (count--){write(fd, msg, len); // fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据}close(fd);return 0;
}

 读文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while(1){
ssize_t s = read(fd, buf, strlen(msg));//类比write
if(s > 0){
printf("%s", buf);
}else{
break;
}
}
close(fd);
return 0;
}

open接口

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

其实我们可以使用|的方式将flags参数进行组合,因为他们就是宏。

返回值:
成功:新打开的文件描述符
失败:-1

mode的意思就是创建目标文件时所需要的权限。

 open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

write read close类比C文件相关接口

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆一下我们讲操作系统概念时,画的一张图

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。 

文件描述符fd

 通过学习open函数我们知道在成功打开文件后会返回一个文件描述符,而这些文件描述符就是一些整数,当我们打开多个文件后这些数字会从3开始一直往后延续,那为什么0、1、2没有人用呢?

0 & 1 & 2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.0,1,2对应的物理设备一般是:键盘,显示器,显示器

在系统层面我们可以看出,这写接口都是以的是系统提供的接口所使用的参数是文件描述符,而在语言层面上使用的都是File*指针,这个File是C语言分装的一个结构体。

 

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件 。

  

上图是源码,所以文件描述符的本质就是数组下标!!! 

文件描述符的分配规则

直接看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

输出发现是 fd: 3

关闭0或者2,在看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

 发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

 Linux中的重定向

我们知道重定向可以向文件中写入内容。

但是当我们使用 > 对文件进行重定向时,多次对文件进行写入,然后进行查看我们只能看到文件中只有我们最后一次写的内容,清空了之前写的所有内容。这不就是我们fopen函数中的‘w’模式。

 当我们使用>>操作符对文件进行重定向时,每次对文件写入都不会清空文件中的内容。所以这就类比fopen函数中的‘a’模式。

当我们现在已经知道重定向的含义以及文件描述符的分配规则,并且我们知道在语言层面是stdin、stdout、strerr,在系统层面就是0、1、2,当我们关闭1,然后使用写打开一个文件时,使用stdout就可以往文件中写入,这种现象叫做输出重定向。

输出重定向代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{close(1);int fd = open("myfile", O_WRONLY | O_CREAT, 00644);if (fd < 0){perror("open");return 1;}printf("fd: %d\n", fd);fflush(stdout);close(fd);exit(0);
}

输入重定向代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{close(0);int fd = open("test.txt", O_RDONLY);if (fd < 0){perror("open");return 1;}int a = 0;scanf("%d", &a);printf("%d\n", a);close(fd);exit(0);
}

 追加重定向与输出重定向基本相同,唯一不同的就是将open函数中的flags参数改成O_WRONLY | O_CREAT|O_TRUNC即可。

 使用 dup2 系统调用

其中,oldfd表示待复制的文件描述符,newfd表示新的文件描述符。该函数返回新的文件描述符,若出错则返回-1。

dup2函数的工作原理相对简单,主要包括以下几个步骤:

  1. 检查oldfdnewfd是否相等,若相等则不进行任何操作,直接返回newfd
  2. 检查newfd的合法性,若已经打开,则先关闭;
  3. 复制oldfd的文件表项到newfd,使得两者指向同一个文件表项;
  4. 返回newfd

所以我们不需要进行close,直接使用dup2即可。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = open("./log", O_CREAT | O_RDWR);if (fd < 0){perror("open");return 1;}close(1);dup2(fd, 1);for (;;){char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

在自治shell中加入重定向 

我们在命令行中可以使用echo指令进行重定向,我们之前做的简易shell中没有重定向,现在我们将代码补充完整:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
#define STREND '\0'// 下面的都和重定向有关
#define NoneRedir  -1
#define StdinRedir  0
#define StdoutRedir 1
#define AppendRedir 2#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)int redir_type = NoneRedir;
char *filename = NULL;char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;// ls -a -l
// ls -a -l > log.txtconst char* HostName()
{char *hostname = getenv("HOSTNAME");if(hostname) return hostname;else return "None";
}const char* UserName()
{char *hostname = getenv("USER");if(hostname) return hostname;else return "None";
}const char *CurrentWorkDir()
{char *hostname = getenv("PWD");if(hostname) return hostname;else return "None";
}char *Home()
{return getenv("HOME");
}int Interactive(char out[], int size)
{// 输出提示符并获取用户输入的命令字符串"ls -a -l"printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());fgets(out, size, stdin);out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?return strlen(out);
}void CheckRedir(char in[])
{// ls -a -l// ls -a -l > log.txt// ls -a -l >> log.txt// cat < log.txtredir_type = NoneRedir;filename = NULL;int pos = strlen(in) - 1;while( pos >= 0 ){if(in[pos] == '>'){if(in[pos-1] == '>'){redir_type = AppendRedir;in[pos-1] = STREND;pos++;IgnSpace(in, pos);filename = in+pos;break;}else{redir_type = StdoutRedir;in[pos++] = STREND;IgnSpace(in, pos);filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}}else if(in[pos] == '<'){redir_type = StdinRedir;in[pos++] = STREND;IgnSpace(in, pos);filename = in+pos;//printf("debug: %s, %d\n", filename, redir_type);break;}else{pos--;}}
}void Split(char in[])
{CheckRedir(in);int i = 0;argv[i++] = strtok(in, SEP); // "ls -a -l"while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =if(strcmp(argv[0], "ls") ==0){argv[i-1] = (char*)"--color";argv[i] = NULL;}
}void Execute()
{pid_t id = fork();if(id == 0){int fd = -1;if(redir_type == StdinRedir){fd = open(filename, O_RDONLY);dup2(fd, 0);}else if(redir_type == StdoutRedir){fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC);dup2(fd, 1);}else if(redir_type == AppendRedir){fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);dup2(fd, 1);}else{// do nothing}// 让子进程执行命名execvp(argv[0], argv);exit(1);}int status = 0;pid_t rid = waitpid(id, &status, 0);if(rid == id) lastcode = WEXITSTATUS(status); //printf("run done, rid: %d\n", rid);
}int BuildinCmd()
{int ret = 0;// 1. 检测是否是内建命令, 是 1, 否 0if(strcmp("cd", argv[0]) == 0){// 2. 执行ret = 1;char *target = argv[1]; //cd XXX or cdif(!target) target = Home();chdir(target);char temp[1024];getcwd(temp, 1024);snprintf(pwd, SIZE, "PWD=%s", temp);putenv(pwd);}else if(strcmp("export", argv[0]) == 0){ret = 1;if(argv[1]){strcpy(env, argv[1]);putenv(env);}}else if(strcmp("echo", argv[0]) == 0){ret = 1;if(argv[1] == NULL) {printf("\n");}else{if(argv[1][0] == '$'){if(argv[1][1] == '?'){printf("%d\n", lastcode);lastcode = 0;}else{char *e = getenv(argv[1]+1);if(e) printf("%s\n", e);}}else{printf("%s\n", argv[1]);}}}return ret;
}int main()
{while(1){char commandline[SIZE];// 1. 打印命令行提示符,获取用户输入的命令字符串int n = Interactive(commandline, SIZE);if(n == 0) continue;// 2. 对命令行字符串进行切割Split(commandline);// 3. 处理内建命令n = BuildinCmd();if(n) continue;// 4. 执行这个命令Execute();}return 0;
}

 上面就是简易shell完整版,代码仅供参考!!!


以上就是本次全部内容,感谢大家观看。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Pytorch】一文向您详细介绍 torch.tensor() 的常见用法

【Pytorch】一文向您详细介绍 torch.tensor() 的常见用法 下滑即可查看博客内容 &#x1f308; 欢迎莅临我的个人主页 &#x1f448;这里是我静心耕耘深度学习领域、真诚分享知识与智慧的小天地&#xff01;&#x1f387; &#x1f393; 博主简介&#xff1a;985高校的普通…

盲盒小程序推广与运营策略的挑战

随着盲盒经济的兴起&#xff0c;越来越多的商家开始关注并尝试开发盲盒小程序。然而&#xff0c;在推广和运营盲盒小程序的过程中&#xff0c;我们也不可避免地会遇到一些挑战。下面&#xff0c;我将就用户获取、留存以及活跃度提升等方面&#xff0c;探讨这些挑战及可能的应对…

【Linux】生产者消费者模型——阻塞队列BlockQueue

> 作者&#xff1a;დ旧言~ > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;理解【Linux】生产者消费者模型——阻塞队列BlockQueue。 > 毒鸡汤&#xff1a;有些事情&#xff0c;总是不明白&#xff0c;所以我不会坚持。早安!…

【网络安全】网络安全基础精讲 - 网络安全入门第一篇

目录 一、网络安全基础 1.1网络安全定义 1.2网络系统安全 1.3网络信息安全 1.4网络安全的威胁 1.5网络安全的特征 二、入侵方式 2.1黑客 2.1.1黑客入侵方式 2.1.2系统的威胁 2.2 IP欺骗 2.2.1 TCP等IP欺骗 2.2.2 IP欺骗可行的原因 2.3 Sniffer探测 2.4端口扫描技术…

知识付费小程序源码系统 一键拥有属于自己的知识店铺 带完整的安装代码包以及搭建教程

系统概述 在数字化时代&#xff0c;知识已成为最具价值的资产之一。随着互联网技术的飞速发展&#xff0c;知识付费市场迎来了前所未有的发展机遇。为了帮助广大内容创作者、教育机构及个人轻松搭建专属的知识店铺&#xff0c;一款高效、易用的知识付费小程序源码系统应运而生…

架构设计-用户信息及用户相关的密码信息设计

将用户的基本信息和用户密码存放在不同的数据库表中是一种常见的安全做法&#xff0c;这种做法旨在增强数据的安全性和管理的灵活性。以下是这种做法的几个关键原因&#xff1a; 安全性增强&#xff1a; 当用户密码被单独存放在一个表中时&#xff0c;可以使用更强大的加密和哈…

超详解——Python模块文档——基础篇

目录 1. Unix起始行 示例&#xff1a; 2. 对象和类型 示例&#xff1a; 3. 一切都是对象 示例&#xff1a; 4. 理解对象和引用 示例&#xff1a; 5. 理解对象和类型 示例&#xff1a; 6. 标准类型 示例&#xff1a; 7. 其他内建类型 示例&#xff1a; 8. 类型的类…

东南亚电商Tiki、Qoo10:如何用自养号测评提升产品曝光和销量

随着互联网的普及和全球化的推进&#xff0c;跨境电商在东南亚地区日益繁荣。Tiki、Qoo10作为该地区的电商巨头&#xff0c;不仅吸引了大量消费者&#xff0c;也成为了卖家竞相角逐的战场。为了在这场竞争中脱颖而出&#xff0c;卖家们纷纷采用测评这一方式来提升产品销量。本文…

弱智吧”,人类抵御AI的最后防线

“写遗嘱的时候错过了deadline怎么办&#xff1f;” “怀念过去是不是在时间的长河里刻舟求剑&#xff1f;” “英语听力考试总是听到两个人在广播里唠嗑&#xff0c;怎么把那两个干扰我做题的人赶走&#xff1f;” 以上这些饱含哲学但好像又莫名其妙的问题&#xff0c;出自…

MySQL复习题(期末考试)

MySQL复习题&#xff08;期末考试&#xff09; 1.MySQL支持的日期类型&#xff1f; DATE,DATETIME,TIMESTAMP,TIME,TEAR 2.为表添加列的语法&#xff1f; alter table 表名 add column 列名 数据类型; 3.修改表数据类型的语法是&#xff1f; alter table 表名 modify 列名 新…

在windows10 安装子系统linux(WSL安装方式)

在 windows 10 平台采用了WSL安装方式安装linux子系统 1 查找自己想要安装的linux子系统 wsl --list --online 2 在线安装 个人用Debian比较多&#xff0c;这里选择Debian&#xff0c;如下图&#xff1a; wsl --install -d Debian 安装过程中有一步要求输入用户名与密码&…

LNMP与动静态网站介绍

Nginx发展 Nginx nginx http server Nginx是俄罗斯人 Igor Sysoev(伊戈尔.塞索耶夫)开发的一款高性能的HTTP和反向代理服务器。 Nginx以高效的epoll.kqueue,eventport作为网络IO模型&#xff0c;在高并发场景下&#xff0c;Nginx能够轻松支持5w并发连接数的响应&#xff0c;并…

【Go语言精进之路】构建高效Go程序:了解切片实现原理并高效使用

&#x1f525; 个人主页&#xff1a;空白诗 &#x1f525; 热门专栏&#xff1a;【Go语言精进之路】 文章目录 引言一、切片究竟是什么&#xff1f;1.1 基础的创建数组示例1.2 基础的创建切片示例1.3 切片与数组的关系 二、切片的高级特性&#xff1a;动态扩容2.1 使用 append …

分布式一致性理论

分布式一致性理论 1.数据库事务ACID理论 为保证事务正确可靠而必须具备的四个核心特性。这四个特性分别是&#xff1a;原子性&#xff08;Atomicity&#xff09;、一致性&#xff08;Consistency&#xff09;、隔离性&#xff08;Isolation&#xff09;和持久性&#xff08;D…

社交创新:Facebook的技术与产品发展

在当今数字化时代&#xff0c;社交网络已经渗透到我们生活的方方面面&#xff0c;成为了人们日常交流、信息获取和社交互动的主要方式。而在这个众多社交平台中&#xff0c;Facebook作为其中的佼佼者&#xff0c;其技术与产品的发展历程也是一个社交创新的缩影。本文将探索Face…

六、【源码】SQL执行器的定义和实现

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/06-sql-executor SQL执行器的定义和实现 之前的Sql执行都是耦合在SqlSession里的&#xff0c;现在要对这部分进行解耦和重构&#xff0c;引…

《软件定义安全》之三:用软件定义的理念做安全

第3章 用软件定义的理念做安全 1.不进则退&#xff0c;传统安全回到“石器时代” 1.1 企业业务和IT基础设施的变化 随着企业办公环境变得便利&#xff0c;以及对降低成本的天然需求&#xff0c;企业始终追求IT集成设施的性价比、灵活性、稳定性和开放性。而云计算、移动办公…

FiRa标准UWB MAC实现(三)——距离如何获得?

继续前期FiRa MAC相关介绍,将FiRa UWB MAC层相关细节进一步进行剖析,介绍了UWB技术中最重要的一个点,高精度的距离是怎么获得的,具体使用的测距方法都有哪些,原理又是什么。为后续FiRa UWB MAC的实现进行铺垫。 3、测距方法 3.1 SS-TWR SS-TWR为Single-Sided Two-Way Ra…

如何系统学习vue框架

前言 在软件开发的浩渺星海中&#xff0c;编程规范如同航海的罗盘&#xff0c;为我们指引方向&#xff0c;确保我们的代码之旅能够顺利、高效地到达目的地。无论是个人开发者还是大型团队&#xff0c;编程规范都是提升代码质量、保障项目成功不可或缺的一环。 因此&#xff0c…

人工智能模型对有争议的话题持相反的观点

人工智能模型对有争议的话题持相反的观点 并非所有生成式人工智能模型都是平等的&#xff0c;特别是当涉及到它们如何处理两极分化的主题时。 在2024年ACM公平、问责和透明度(FAccT)会议上发表的一项最新研究中&#xff0c;卡内基梅隆大学、阿姆斯特丹大学和人工智能初创公司h…