当前位置: 首页 > news >正文

Linux:文件操作

引言:

C语言是怎样进行文件操作的?

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

对文件的读写需要用到 fopen、fread、fwrite 等系统底层函数,而用户进程每调用一次系统函数都要从用户态切换到内核态,等执行完毕后再返回用户态,这种切换要花费一定时间成本(对于高并发程序而言,这种状态的切换会影响到程序性能)。

读取文件:

#include <stdio.h>
#include <string.h>
int main()
{FILE* fp = fopen("test.txt", "r");if (!fp){printf("fopen error!\n");}char buf[1024];const char* msg = "hello world!\n";while (1){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;
}

fread是将文件的数据读到缓冲区里

size_t fread( void *buffer, size_t size, size_t count, FILE *stream );

buffer是自己设定的缓冲区

size是要读取数据的大小,char就是1,int就是4

count是要读取的个数

stream是文件指针

返回值是实际从文件中读取的基本单元个数

feof用检测流上的文件结束符:如果遇到文件正常结束,函数返回值为非零值;如果文件异常结束,函数返回值值为0。

C程序在启动的时候会默认启动三个输入输出流,他们分别是:

extern FILE* stdin;           //键盘
extern FILE* stdout;          //显示器,可以打印到显示器上
extern FILE* stderr;           //标准错误,收集错误,也属于显示器

他们三个在代码层面上都是文件指针的类型,也就是*FILE

很多情况下,操作系统中存在着许多被同时打开的文件。这些被打开的文件都是由磁盘打开的,操作系统需要对文件进行管理。

如何管理?

通过某种描述文件属性的数据结构,最后转换成指针之间的映射;从操作文件改为操作指针

文件=属性+内容,

以w打开文件的话,文件如果不存在,就在当前路径下新建指定文件,且默认打开文件的时候会把目标文件先清空

输出重定向伴随着文件操作

理解文件

操作文件的本质是进程在操作文件

文件一开始在磁盘上,本质上在硬件部分的存储

但是用户没有权利直接写入,操作系统是硬件的管理者,用户需要通过操作系统进行写入

操作系统为我们提供系统调用的接口,用的是C/C++对系统调用接口的封装

访问文件也是通过系统调用来访问的

系统如何访问

open:

open 系统调用中,flags 是一个由多个标志位组合而成的整数,常见的标志包括:

  • O_RDONLY: 以只读模式打开文件。
  • O_WRONLY: 以只写模式打开文件。
  • O_RDWR: 以读写模式打开文件。
  • O_CREAT: 如果文件不存在,则创建文件。需要指定 mode 参数。
  • O_EXCL: 如果文件已经存在,则返回错误(与 O_CREAT 一起使用)。
  • O_TRUNC: 如果文件已存在,并且以写模式打开,则将文件截断为零长度。
  • O_APPEND: 以追加模式打开文件,将数据写入文件末尾。

系统的接口:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT);if (fd < 0){perror("open fail");return 1;}return 0;
}

执行一下:

 在Linux中创建文件的时候要告诉它起始权限,否则将会是乱码 

这样改:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open fail");return 1;}return 0;
}

最后的权限是由我们给定的起始权限和umask掩码共同决定的

umask掩码也可以

我们自己设定:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{umask(0);//修改umaskint fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open fail");return 1;}close(fd);//记得关掉return 0;
}

关闭文件的接口:

open函数中的第二个参数:O_WRONLY | O_CREAT是宏,代表的是打开文件以读取模式

还有别的:O_WRONLY只写;O_RDWR,读写

open会 返回一个文件描述符(非负整数),用于后续的读写操作。打开文件后,记得使用

在操作系统设计中,系统调用接口可能会用到标志位来指示特定的功能或选项。使用比特位传递标志位可以有效地利用每个整数的多个位,从而在一个整数中存储多个独立的标志位,理解下来其实就像用二进制来表示还是

这种做法提高了效率,并减少了需要传递的数据量。例如,一个32位的整数可以用32个位来表示32个不同的标志位,这样只需传递一个整数,就可以传递多个开关状态。

我们也可以自己写一个具有这种传递位图标记位的函数:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)void Print(int flag)
{if (flag & ONE){printf("one\n");}if (flag & TWO){printf("two\n");}if (flag & THREE){printf("three\n");}if (flag & FOUR){printf("four\n");}
}
int main()
{Print(ONE);printf("\n");Print(ONE | TWO);printf("\n");Print(ONE | TWO | THREE);printf("\n");Print(ONE | TWO | THREE | FOUR);printf("\n");return 0;
}

可以用标记位组合的方式向一个函数传递多个标记位(比如只传递1,就只打印1;传递1、2、3和4,就启用上面函数的对应部分)

我们把文件打开以后就涉及到写入了

写入的接口函数是:write

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{umask(0);int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open fail");return 1;}const char* message = "Hello Linux file\n";write(fd, message, strlen(message));close(fd);return 0;
}

执行结果:

注意:当出现这个提示的时候说明文件权限对于你来说没有“写”权限,需要自己chmod设置一下

chmod 664 example.txt//设置权限,即rw-rw-r

不过这种方式不能在上次的基础写,每次write都会覆盖之前的

注意:是覆盖,不是清空,如果下次输入的字数比上次少,就会多出来上次的字符

举个例子:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{umask(0);int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open fail");return 1;}const char* message = "this is first time.\n";write(fd, message, strlen(message));close(fd);return 0;
}
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{umask(0);int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open fail");return 1;}const char* message = "Hello Linux file\n";write(fd, message, strlen(message));close(fd);return 0;
}

插播:

vim一直提示我们是否要恢复缓存文件,同时我们也找不到.swp文件,这意味着vim缓存问题

关掉shell重新打开就好了

如果想每次打开的时候清空文件:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{umask(0);int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);if (fd < 0){perror("open fail");return 1;}const char* message = "Hello Linux file\n";write(fd, message, strlen(message));close(fd);return 0;
}

注意我们增加了一个参数:O_TRUNC 是open系统调用中的一个标志,用于控制文件在打开时的行为。它的作用是如果文件已经存在,并且以模式(O_WRONLY或 O_RDWR)打开,则将文件截断为零长度。简而言之, O_TRUNC 会清空文件的内容。

如何在前面的基础上追加呢?

使用append这个文件模式标志

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
int main()
{int fda = open("loga.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fda:%d\n", fda);int fdb = open("logb.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdb:%d\n", fdb);int fdc = open("logc.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdc:%d\n", fdc);int fdd = open("logd.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);printf("fdd:%d\n", fdd);return 0;
}

012去哪里了?

open默认打开的文件流:

0:标准输入     键盘

1:标准输出     显示器

2:标准错误     显示器

fd的本质是什么?为什么可以通过这个东西写进显示器?

操作系统对于要打开的文件要创建内核数据结构struct_file,使用双链表来管理这些文件

从对文件的管理变为对链表的管理,指向文件内核级的缓存

  • task_struct: 代表一个进程,包含一个指向 files_struct 的指针。这个指针位于 t task_structfiles 字段中。

  • files_struct: 代表一个进程的文件描述符表,包含一个指向 struct file 的指针数组。这个指针数组用于跟踪进程打开的所有文件。

所以我们使用fd的时候其实是内核的进程在映射指针数组的下标

fd(File Descriptor)在这里就叫文件描述符,它是一个非负整数,每个打开的文件或者I/O资源在操作系统中都会对应一个唯一的文件描述符(也就是不同数字对应指针数组下的不同文件)。

无论读,还是写,都要及时让操作系统把文件的内容读到缓冲区

open在这中间做了什么呢?

1.创建文件

2.开辟该文件的缓存空间,加载文件数据

3.获取该进程的fd

4.file地址,填入对应的表下标中

5.返回下标

open的参数有很多个,上文已经提到;在不同的环境要选择不同的参数

fd=0/1/2的时候,指向的是硬件(终端相关联的输入输出设备,这些设备可以被视为硬件资源(例如,键盘和显示器)

//键盘
//函数指针
void (*read)(...)
void (*write)(...)
//函数
void k_read();
void k_write();
//显示器
void screen_read();
void screen_write();

除了键盘显示器,还有:

//鼠标
void mouse_read();
void mouse_write();

面向对象的概念中有的存在,类是对一类事物抽象出来的概念;类由属性、构造函数和方法组成

这个struct_file这样格式的结构体就是对应file的类

在OS内,系统在访问文件的时候,只认文件描述表fd

那么C是如何用FILE* 访问文件呢?

FILE是C提供的一个结构体类型

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{FILE* fp = fopen("log.txt", "w");if (fp == NULL){return 1;}printf("fd:%d\n", fp->_fileno);fwrite("hello", 5, 1, fp);fclose(fp);return 0;
}

fileno:将标准I/O库中的文件流(file)转换为底层的文件描述符(fd)。

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>int main()
{printf("stdin->fd:%d\n", stdin->_fileno);printf("stdout->fd:%d\n", stdout->_fileno);printf("stderr->fd:%d\n", stderr->_fileno);FILE* fp = fopen("log.txt", "w");if (fp == NULL){return 1;}printf("fd:%d\n", fp->_fileno);FILE* fp1 = fopen("log1.txt", "w");if (fp1 == NULL){return 1;}printf("fd:%d\n", fp1->_fileno);FILE* fp2 = fopen("log2.txt", "w");if (fp2 == NULL){return 1;}printf("fd:%d\n", fp2->_fileno);fclose(fp);return 0;
}

C语言本身具有自己的标准库,在不同的标准下有不同的标准库。虽然C语言并不是天然跨平台的语言,但是他的库是跨平台的,如果代码仅使用标准库,并避免操作系统特定的功能,那么C和C++代码可以编译并在不同平台上运行

所有语言都有跨平台性(当然,跨平台性的实现也有很多种,c/c++是通过库的跨平台性,而java是通过虚拟机等等方法)

因为所有语言都具有跨平台性,所以它们要对不同平台的系统调用进行封装,不同语言进行封装的时候,文件接口就会有差别

在c++中,cin、cout、cerr都被叫做类,这些类中包含了他们自己的文件描述符

当我们打开文件时,操作系统会创建一个结构体(在这里也是一种类),通过描述我们打开的文件的属性和方法来描述文件,这个结构体就是FILE:表示一个已经打开的文件对象(对象是类的实例)

进程执行open这个系统调用,所以必须让进程和文件关联起来

每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分包含一个指针数组,每个元素都是一个指向打开文件的指针,本质上,文件描述符就是该数组的下标

只要拿着文件描述符,就可以找到对应的文件

重定向

读文件:

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>const char* filename = "log.txt";int main()
{struct stat st;int n = stat(filename, &st);if (n < 0){return 1;}printf("file size:%lu\n", st.st_size);int fd = open(filename, O_CREAT | O_RDONLY);if (fd < 0){perror("open fail");return 1;}close(fd);return 0;
}

stat 是一个系统调用,用于检索文件的元数据。这些信息包括文件的大小、最后一次修改时间、权限、文件类型等。stat 系统调用通常与 lstatfstat 一起使用,以获取特定类型文件的信息。

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>const char* filename = "log.txt";int main()
{struct stat st;int n = stat(filename, &st);if (n < 0){return 1;}printf("file size:%lu\n", st.st_size);int fd = open(filename, O_RDONLY);if (fd < 0){perror("open fail");return 2;}printf("fd:%d\n", fd);char* file_buffer = (char*)malloc(st.st_size + 1);//根据stat返回的文件大小申请内存n = read(fd, file_buffer, st.st_size);//读取fd的内容写入file_buffer中,大小是st.st_sizeif (n > 0){file_buffer[n] = '\0';printf("%s\n", file_buffer);}free(file_buffer);close(fd);return 0;
}

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<sys/types.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
const char* filename = "log.txt";int main()
{close(0);int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);if(fd<0){perror("open fail");return 1;                                                   }                                                            printf("fd:%d\n",fd);                                        close(fd);                                                   return 0;                                                    
}          

文件描述符有一套自己的分配规则,它会查找自己的文件描述表,优先把最小的未分配的fd分配出去

printf和fprintf默认都是向显示器输出,但是我们也可以向别的地方输出

#include<stdio.h>
#include<stdlib.h>
#include <string.h>
#include<sys/types.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>const char* filename = "log.txt";int main()
{close(1);int fd=open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);if(fd<0){perror("open fail");return 1;}printf("fd:%d\n",fd);fprintf(stdout,"fprintf,fd:%d\n",fd);fflush(stdout);close(fd);return 0;
}

你看,写进log.txt里了

这就叫重定向

printf/fprintf默认是向stdout中打印的,stdout有对应的struct FILE,里面对应的_fileno==1(也就是文件描述符)

重定向的本质是改变文件描述符下的对应关系

但是我们可以发现如果不加fflush的话是默认没有打印内容的

 struct FILE*里有语言级别的缓冲区,所以需要刷新才能打印出来

来看一个函数:dup2

dup2:拷贝文件描述符下对应的文件内容

进行标准的重定向:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>const char* filename="log.txt";int main()
{int fd = open(filename,O_CREAT|O_WRONLY|O_TRUNC,0666);dup2(fd,1);printf("hello world\n");fprintf(stdout,"hello world\n");return 0;
}

那我们打开一个新的终端,就可以向另一个显示器打印了:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>const char* filename = "log.txt";int main()
{int fd = open("/dev/pts/2", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open fail\n");return 1;}dup2(fd, 1);printf("hello world\n");fprintf(stdout, "hello world\n");fflush(stdout);return 0;
}

缓冲区 

缓冲区即有用户级缓冲区,又有内核级缓冲区

功能主要有两种:解耦和提高效率

提高效率是指能提高使用者的效率

调用系统调用需要成本,可提高IO刷新的效率

缓冲区要给上层提供高效的IO体验,同时提高整体的效率

用户缓冲区是指程序在用户空间中分配的内存区域,用于存储将要发送或接收的数据。用户空间是指应用程序可以直接访问的内存区域,通常受操作系统的保护,避免直接访问硬件资源。

 内核缓冲区是操作系统内核在内核空间中分配的内存区域,用于暂时存储数据以便进行 I/O 操作。内核空间是受保护的内存区域,只有操作系统内核及其受信任的组件可以直接访问。

缓冲区的刷新策略

1.立即刷新:从内核到外设的刷新,比如fflush(stdout),再比如fsync(int fd)...

2.行刷新:一般应用于显示器(\n)

3.全缓冲,缓冲区写满,才刷新,应对普通文件

还有特殊情况是进程退出系统自动刷新

缓冲区刷新策略和用户无关,是通用的(均码)

但是当我们重定向到文件的时候,刷新策略会改变

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>const char* filename = "log.txt";int main()
{printf("hello printf\n");fprintf(stdout, "hello fprintf\n");const char* msg = "hello write\n";write(1, msg, strlen(msg));fork();return 0;
}

当重定向之后变为全缓冲,文件缓冲区并没有被写满,所以会把它暂时保存起来,在stdout对应的缓冲区,在fork时并没刷新,还在缓冲区(因为没写满);而在fork后产生子进程,子进程同时也共享父进程的缓冲区

printffprintf 是缓冲的。这意味着输出的数据首先会被存储在缓冲区中,等到缓冲区满了或者程序正常结束时才会输出。因为 fork() 之后,父进程和子进程会共享缓冲区的副本,所以如果 fork() 之前的数据还没有被刷新到输出中,它可能会在两个进程中被重复输出。

write 是非缓冲的,它直接将数据写到文件描述符,所以输出是立即可见的。

printf fwrite 库函数会自带缓冲区,write 系统调用没有带缓冲区 (均指用户级缓冲区)

为了提升整机性能,OS也会提供相关内核级缓冲区

那这个缓冲区谁提供捏?

printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,write 没有缓冲区,printf fwrite 有,该缓冲区是二次加上的,由C标准库提供 

每一个文件都有一个自己的缓冲区

这是FILE结构体:

struct _IO_FILE {int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags//缓冲区相关/* The following pointers correspond to the C++ streambuf protocol. *//* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */char* _IO_read_ptr; /* Current read pointer */char* _IO_read_end; /* End of get area. */char* _IO_read_base; /* Start of putback+get area. */char* _IO_write_base; /* Start of put area. */char* _IO_write_ptr; /* Current put pointer. */char* _IO_write_end; /* End of put area. */char* _IO_buf_base; /* Start of reserve area. */char* _IO_buf_end; /* End of reserve area. *//* The following fields are used to support backing up and undo. */char *_IO_save_base; /* Pointer to start of non-current get area. */char *_IO_backup_base; /* Pointer to first valid character of backup area */char *_IO_save_end; /* Pointer to end of non-current get area. */struct _IO_marker *_markers;struct _IO_FILE *_chain;int _fileno; //封装的文件描述符
#if 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but it's too small. */#define __HAVE_COLUMN /* temporary *//* 1+column number of pbase(); 0 is unknown. */unsigned short _cur_column;signed char _vtable_offset;char _shortbuf[1];/* char* _save_gptr; char* _save_egptr; */_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • EV代码签名证书——消除软件下载时的安全警告
  • Qt之控件介绍
  • 推荐一个开箱即用的中后台前端解决方案,基于vue3开发,私活神器(带源码)
  • 项目初始化踩坑记录
  • 【Matlab】RF随机森林回归预测算法 可预测未来数据(附代码)
  • 计算机算法设计与分析【第一章】
  • [数据集][目标检测]风力发电机叶片损伤检测数据集VOC+YOLO格式5029张8类别
  • 五种多目标优化算法(MOAHA、NSGA2、NSGA3、SPEA2、MODA)性能对比,包含47个多目标测试函数,6种评价指标,MATLAB代码
  • Java 输入与输出之 NIO【非阻塞式IO】【NIO核心原理】探索之【一】
  • C语言——字符函数、字符串函数和内存函数
  • 计算机网络面试真题总结(六)
  • QT 与 C++实现基于[ TCP ]的聊天室界面
  • led护眼台灯对眼睛好吗?台灯护眼是真的吗?一文告诉你答案
  • 单例模式在实现webserver这个项目中起到了什么作用
  • 如何完美备份自己的微博,即使是封号之后
  • JS 中的深拷贝与浅拷贝
  • (三)从jvm层面了解线程的启动和停止
  • Android Studio:GIT提交项目到远程仓库
  • EOS是什么
  • ES6--对象的扩展
  • ES6系统学习----从Apollo Client看解构赋值
  • es的写入过程
  • JS笔记四:作用域、变量(函数)提升
  • magento 货币换算
  • miniui datagrid 的客户端分页解决方案 - CS结合
  • MySQL的数据类型
  • Node.js 新计划:使用 V8 snapshot 将启动速度提升 8 倍
  • open-falcon 开发笔记(一):从零开始搭建虚拟服务器和监测环境
  • 不上全站https的网站你们就等着被恶心死吧
  • 记录:CentOS7.2配置LNMP环境记录
  • 力扣(LeetCode)21
  • 机器人开始自主学习,是人类福祉,还是定时炸弹? ...
  • # Redis 入门到精通(九)-- 主从复制(1)
  • #define 用法
  • #绘制圆心_R语言——绘制一个诚意满满的圆 祝你2021圆圆满满
  • #使用清华镜像源 安装/更新 指定版本tensorflow
  • #我与Java虚拟机的故事#连载09:面试大厂逃不过的JVM
  • (16)Reactor的测试——响应式Spring的道法术器
  • (3) cmake编译多个cpp文件
  • (a /b)*c的值
  • (arch)linux 转换文件编码格式
  • (el-Date-Picker)操作(不使用 ts):Element-plus 中 DatePicker 组件的使用及输出想要日期格式需求的解决过程
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (附源码)计算机毕业设计SSM教师教学质量评价系统
  • (考研湖科大教书匠计算机网络)第一章概述-第五节1:计算机网络体系结构之分层思想和举例
  • (力扣)1314.矩阵区域和
  • (一一四)第九章编程练习
  • (转)一些感悟
  • ***检测工具之RKHunter AIDE
  • ./include/caffe/util/cudnn.hpp: In function ‘const char* cudnnGetErrorString(cudnnStatus_t)’: ./incl
  • .NET Core实战项目之CMS 第一章 入门篇-开篇及总体规划
  • .NET Micro Framework 4.2 beta 源码探析
  • .net 打包工具_pyinstaller打包的exe太大?你需要站在巨人的肩膀上-VC++才是王道
  • .NET分布式缓存Memcached从入门到实战
  • .NET框架类在ASP.NET中的使用(2) ——QA