• Linux系统编程--IO


    一、I/O系统调用

    1.open() 打开文件

    1.1 所需基础知识

    Linux遵循一切皆是文件的理念,因此,很多的交互工作都是通过读取和写入文件来完成。
    文件必须被打开才能被访问。文件可以以只读方式或者只写方式打开,或者两者兼有。一个打开的文件通过唯一的文件描述符进行引用,该描述符是打开文件的元数据至其本身的映射在Linux内核中,这个描述符,用一个整数表示 (int) ,简写为fd。文件描述符在用户空间中共享,允许用户程序用文件描述符直接访问文件。

    1.2. open() 详解

    #include 
    #include 
    #include 
    
    //调用成功返回文件描述符,失败返回-1
    
    int open (const char* name, int flags);
    
    int open (const char* name, int flags, mode_t mode);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    flags参数必须是一下之一:

    参数含义
    O_RDONLY只读
    O_WRONLY只写
    O_RDWR读写模式

    flags参数可以和以下一个或多个值进行按位或运算,用以修改打开文件请求的行为。

    参数含义
    O_APPEND追加模式打开,文件位置指针将被置于文件末尾
    O_ASYNC当指定文件可写或者可读时产生一个信号(默认SIGIO)
    O_CREAT当指定的name文件不存在时,将由内核来创建
    O_DIRECT打开文件用于直接I/O
    O_DIRECTORY如果name不是目录,open()调用将会失败,这个标志用于opendir()内部使用
    O_EXCL和O_CREAT一起给出的时候,如果name给定的文件已经存在,则open()调用失败,用来防止文件创建时出现竞争
    O_LARGEFILE给定文件打开时使用64位偏移量,这样大于2G的文件也能被打开
    O_NOFOLLOW如果name是一个符号链接,open()调用会失败
    O_NONBLOCK如果可以,文件将在非堵塞模式下打开
    O_SYNC打开文件用于同步IO
    O_TRUNC如果文件存在,且为普通文件,并允许写,将文件的长度截断为0

    1.3 示例代码

    以打开 /etc/services 文件为例

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	fd = open("/etc/services",O_RDONLY);
      	if(fd == -1)
     	{
      	 cerr << "open() failed";
      	 return 0;
     	}
    
      else
    	{
    
    	cout << "open success" << endl;
    	cout << "fd = " << fd << endl;
    	}
    
    	close(fd);
    	return 0;
    
    }
    
    
    • 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

    效果图:
    在这里插入图片描述

    2.read() 读取文件

    2.1.基础知识

    ssize_t 其实是有符号整数类型
    size_t 是无符号整数类型

    2.2.read() 详解

    #include 
    
    ssize_t read (int fd, void* buf, size_t len);
    
    
    • 1
    • 2
    • 3
    • 4

    2.2.1.功能:

    该系统调用从有fd指向的文件的当前偏移量至多读len个字节到buf中。成功返回写入buf中的字节数。出错则返回-1,并设置errno

    2.2.2.返回结果情况:

    1. 调用返回一个等于len的值。
    所有len个被读取字节存储在buf中。结果和预期一致。

    以 读取 /etc/services 文件为例,读取文件中20个字节

    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	
    	fd = open("/etc/services",O_RDONLY);  //以只读方式打开文件
    
    	if(fd == -1)		//文件打开失败,并提示错误
    	{
    		cerr << " open failed ";
    		return 0;
    
    	}
    
    	char* buf;
    	ssize_t length;
    	length = read(fd, buf, 20);
    	
    	if(length == -1)  //读文件失败
    	{
    	
    		cerr << "read failed" << endl;
    		close(fd);
            return 0;
    
    	}
    
    	else
    	cout << "读取字节数 = " << length << endl;  //读文件成功
    	close(fd);
    	return 0;	
    
    }
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    效果图:
    在这里插入图片描述

    2.调用返回一个大于0,但是小于len的值。

    • 读数据期间,被一个信号打断了读取的过程。
    • 读取的数据,本身就少于指定的字节数。
    • 读取的过程,出现了一个错误。

    测试样例 : 测试文件数据本身少于指定读取字节数的情况。文件本身有abcdef,而指定读取20字节数据。

    sample.txt 文件内容

    abcdef
    
    • 1
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	
    	fd = open("sample.txt",O_RDONLY);  //以只读方式打开文件
    
    	if(fd == -1)		//文件打开失败,并提示错误
    	{
    		cerr << " open failed ";
    		return 0;
    
    	}
    
    	char* buf;
    	ssize_t length;
    	length = read(fd, buf, 20);
    	
    	if(length == -1)  //读文件失败
    	{
    	
    		cerr << "read failed" << endl;
    		close(fd);
            return 0;
    
    	}
    
    	else
    	cout << "读取字节数 = " << length << endl;  //读文件成功
    
    	close(fd);
    	return 0;	
    
    }
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    效果图:
    在这里插入图片描述
    文件内容为abcdef ,6字节的数据,为什么读取字节数为7?

    我们打印一下读取的数据
    在这里插入图片描述

    内容也正确,是6个字节。为什么显示7? 难道是最后的 ‘\0’ 占了一个字节吗?

    3.调用返回0 这标志着EOF。没有可以读入的数据

    测试:
    读一个空文件。

    代码: a.txt 为空文件

    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	
    	fd = open("a.txt",O_RDONLY);
    
    	if(fd == -1)
    	{
    		cerr << " open failed ";
    		return 0;
    
    	}
    
    	char* buf;
    	ssize_t length;
    	length = read(fd, buf, 20);
    	
    	if(length == -1)
    	{
    	
    		cerr << "read failed" << endl;
    		close(fd);
            return 0;
    
    	}
    
    	else
    	cout << "读取字节数 = " << length << endl;
        cout << "内容 " << buf << endl;
    	close(fd);
    	return 0;	
    
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    效果图:
    在这里插入图片描述
    4.调用返回-1
    在这里插入图片描述

    2.3. 读入所有字节

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	
    	fd = open("/etc/services",O_RDONLY);
    
    	if(fd == -1)
    	{
    		cerr << " open failed ";
    		return 0;
    
    	}
    
    	char* buf;
    	ssize_t ret;
    	int len = 10240;  //指明读取的大小
    
    	while(len != 0 && (ret = read(fd, buf, len) != 0))
    	{
    	
    		if(ret == -1)
    		{
    			if(errno == EINTR) 
    				continue;
    
    			cerr << "read failed" << endl;
            	break;
    
    		}
    
    		else
    		{
    			len -= ret;
    			buf += ret;
    			
    		}
    		
    	}
    	close(fd);
    	cout << "len=" << len << endl; //测试程序是否真正的读好几次。
    	return 0;	
    
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    效果图:
    在这里插入图片描述

    3.write() 写文件

    3.1. 基础背景知识

    一个write() 调用从由文件描述符fd引用文件的当前位置开始,将buf中至多count个字节写入文件中。不支持定位的文件(像字符设备)总是从“开头”开始写。
    成功时,返回写入字节数,并更新文件位置。错误时,返回-1,并将errno设置为相应的值。

    3.2.write() 详解

    #include 
    
    ssize_t write(int fd, const void* buf, size_t count);
    
    • 1
    • 2
    • 3

    3.3.示例代码

    测试: 从 /etc/services 文件中读取100字节,并把这100字节写入到a.txt 文件中。

    text 1.不以追加模式写

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	fd = open("/etc/services", O_RDONLY);
    	if(fd == -1)
    	{
    		cerr << "open()" << endl;
    		return 0;
    	}
    
    	char* buf;
    	int len = read(fd,buf,100);
    	if(len == -1)
    	{
    		cerr << "read()" << endl;
    		close(fd);
    		return 0;
    	}
    	
    	int fd1;
    	fd1 = open("a.txt",O_WRONLY);
        if(fd1 == -1)
    	{
    	
    		cerr <<" open(a.txt)" << endl;
    		close(fd);
    		return 0;
    
    	}
    
    	int count = write(fd1,buf,len);
    	if(count == -1)
    	{
    		perror("write()");
    		close(fd);
    		return 0;
    	}
    	else if(count != len)
    	{
    		cerr << "write() 写入字数不对";
    		close(fd);
    		close(fd1);
    		return 0;	
    	}
    	
    	cout << "ok" << endl;
    	return 0;
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    效果图:
    在这里插入图片描述

    text2.追加模式进行写,也就是以追加模式打开文件

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    
    	const char* buf = "abcdefghijklmnopqrstuvwxyz";
    	
    	int fd1;
    	fd1 = open("a.txt",O_WRONLY |O_APPEND);
        if(fd1 == -1)
    	{
    	
    		cerr <<" open(a.txt)" << endl;
    		return 0;
    
    	}
    
    	int count = write(fd1,buf,strlen(buf));
    	if(count == -1)
    	{
    		perror("write()");
    		close(fd1);
    		return 0;
    	}
    	else if(count != strlen(buf))
    	{
    		cerr << "write() 写入字数不对";
    		close(fd1);
    		return 0;	
    	}
    	
    	cout << "ok" << endl;
    	return 0;
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    效果图:
    在这里插入图片描述

    3.4.注意点

    当一个write()调用返回时,内核已将所提供的缓冲区数据复制到了内核缓冲区中。但没有保证数据已写到了目的文件。你可以强制文件缓存写回,甚至可以将所有的写操作同步。

    3.4.1.同步IO
    1. fsync() 和fdatasync()

    最简单的确认数据写入磁盘的方法是使用fsync()系统调用,

    #include 
    
    int fsync(int fd);
    
    
    • 1
    • 2
    • 3
    • 4

    调用fsync()可以保证fd对应文件的脏数据回写到磁盘上。

    该调用回写数据以及建立的时间戳和inode中的其它属性等元数据。在驱动器确认数据已经全部写入之前不会返回。

    #include 
    
    int fdatasync(int fd);
    
    • 1
    • 2
    • 3

    这个系统调用完成的事情和fsync()一样,区别在于它仅写入数据。不保证元数据同步到磁盘上。故此可能快一些。一般来说这就够用了。

    返回值情况
    成功时,两个调用都返回0.失败时,都返回-1.并将errno设置为一下三个值之一:

    名称含义
    EBADF给定的文件描述符不是一个可以写入的合法描述符
    EINVAL给定的文件描述符对应的对象不支持同步
    EIO在同步时发生了一个底层I/O错误
    2.sync()

    sync()系统调用可以用来对磁盘上所有的缓冲区进行同步。尽管效率不高,但仍然被广泛使用。

    #include 
    
    void sync();
    
    • 1
    • 2
    • 3

    该函数没有参数,也没有返回值。它总是成功返回,并确保所有的缓冲区(包括数据和元数据)都可以写入磁盘。

    4.close() 关闭文件

    程序完成对某个文件的操作后,可以使用close()系统调用将文件描述符和对应的文件解除关联。

    #include 
    
    //调用成功返回0.错误返回-1,并设置errno为相应值
    int close(int fd); 
    
    • 1
    • 2
    • 3
    • 4

    5.lseek() 查找

    lseek()系统调用能够对给定文件描述符引用的文件位置设置指定值。除了更新文件位置,没有其它的行为,并无论如何不初始化任何I/O

    #include 
    #include 
    
    off_t lseek(int fd, off_t pos, int origin);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    lseek() 的行为依赖于初始参数,可以为一下值之一:

    名称含义
    SEEK_CUR当前文件位置fd设置为当前值加上pos,pos可以为负值,零或正值
    SEEK_END当前文件位置fd设置为当前文件长度加上pos,pos可以为负值,零或正值
    SEEK_SET当前文件位置fd设置为pos。

    调用成功时返回新文件位置。错误时返回-1,并设置适当的errno值。
    在这里插入图片描述

    6.定位读写

    Linux提供了两种read()和write()的变体来代替lseek(),每个调用都以需要读写的文件位置为参数。完成时,不修改文件位置。

    #define _XOPEN_SOURCE 500
    
    #include 
    
    ssize_t pread(int fd, void* buf, size_t count, off_t pos);
    ssize_t pwrite(int fd,const void* buf,size_t count, off_t pos);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    错误码
    成功时,两个调用返回读或写的字节数。出错时,二者均返回-1

    6.1代码示例

    示例文件 a.txt,内容为

    abcdefghigklmnopqrstuvwxyz
    ABCDEFGHIGKLMNOPQRSTUVWXYZ
    
    • 1
    • 2

    1.测试pread

    距文件开始位置5的位置开始读26个字节

    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	int fd;
    	
    	fd = open("a.txt",O_RDONLY);
    
    	if(fd == -1)
    	{
    		cerr << " open failed ";
    		return 0;
    
    	}
    
    	char* buf;
    	ssize_t length;
    	length = pread(fd, buf, 26,5);
    	
    	if(length == -1)
    	{
    	
    		cerr << "pread failed" << endl;
            return 0;
    
    	}
    
    	else
    	cout << "读取字节数 = " << length << endl;
    	cout << buf << endl;
    	return 0;	
    
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    效果图:
    在这里插入图片描述

    2.测试pwrite()

    从距文件开始位置的26处,写入-Hello,Word-

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    
    int main()
    {
    
    	const char* buf = "-Hello,Word-";
    	
    	int fd1;
    	fd1 = open("a.txt",O_WRONLY);
        if(fd1 == -1)
    	{
    	
    		cerr <<" open(a.txt)" << endl;
    		
    		return 0;
    
    	}
    
    	int count = pwrite(fd1,buf,strlen(buf),26);
    	if(count == -1)
    	{
    	
    		perror("write()");
    		return 0;
    	}
    	else if(count != strlen(buf))
    	{
    		cerr << "write() 写入字数不对";
    	
    		close(fd1);
    		return 0;	
    	}
    	
    	cout << "ok" << endl;
    	return 0;
    }
    
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    效果图:
    在这里插入图片描述
    如果原来位置上有数据,那么写入的数据会把原来的数据覆盖掉

    7.截短文件

    7.1 代码详解

    #include 
    
    int ftruncate (int fd, off_t len);
    
    • 1
    • 2
    • 3
    #include 
    #include 
    
    int truncate (const char* path, off_t len);
    
    • 1
    • 2
    • 3
    • 4

    两个系统调用都将文件截短到len指定的长度。
    二者都在成功时返回0,错误时返回-1,并设置errno为相应值。

    8.I/O多路复用

    8.1.基础知识

    在这里插入图片描述

    I/O多路复用允许应用在多个文件描述符上同时堵塞,并在其中某个可以读写时收到通知。

    I/O多路复用的设计遵循以下原则:

    1. 当任何文件描述符准备好I/O时通知我
    2. 在一个或更多文件描述符就绪前始终处于睡眠状态
    3. 唤醒:哪个准备好了?
    4. 在不阻塞的情况下处理所有I/O就绪的文件描述符
    5. 返回第一步,重新开始

    8.2.select() 实现同步I/O多路复用

    #include 
    #include 
    #include 
    
    //成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符
    // 参数n,等于所有集合中文件描述符的最大值加1.
    int select (int n, fd_set* readfds, fd_set* writefds,fd_set* exceptfds,
    			struct timeval* timeout);
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    监测的文件描述符功能
    监测 readfds 集合中的文件描述符确认其中是否有可读数据
    监测 writefds 集合中的文件描述符确认其中是否有一个写操作可以不阻塞地完成
    监测 exceptefds 中的文件描述符确认其中是否出现异常发生或者出现带外数据
    指定集合为NULLselect对此不监听
    #include 
    
    
    struct timeval{
    	long tv_sec;  //秒
    	long tv_usec; //微秒
    
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果这个参数不是null,即使此时没有文件描述符处于I/O就绪状态,select()调用也将在tv_sec秒tv_usec微秒后返回。

    集合中的文件描述符

    集合中的文件描述符是通过辅助宏来进行管理。

    
    
    FD_CLR(int fd, fd_set* set);  	//从指定集合中移除一个文件描述符
    
    FD_ISSET(int fd, fd_set* set); // 判断fd是否在set集合中,
    
    FD_SET(int fd, fd_set* set);  //向指定集合中添加一个文件描述符
    
    FD_ZERO(fd_set* set);        //从指定集合set中移除所有文件描述符
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    8.2.1.代码示例

    等待stdin的输入的阻塞时限设置为5秒钟。由于只监测了一个文件描述符,实际上不上I/O多路复用。

    #include 
    #include 
    #include 
    #include 
    
    using namespace std;
    #define TIMEOUT 5  //等待5秒
    #define BUF_SIZE 1024
    
    int main()
    {
    	struct timeval tv;
    	fd_set readfds;
    	int ret;
    
    	FD_ZERO(&readfds);
    	FD_SET(STDIN_FILENO,&readfds);
    	
    	tv.tv_sec = TIMEOUT;
    	tv.tv_usec = 0;
    
    	ret = select(STDIN_FILENO + 1,
    				 &readfds, NULL, NULL, &tv);
    
    	if(ret == -1)
    	{
    		cerr << "select()" << endl;
    		return 1;
    	} 
    	else if(!ret)
    	{
    		cout << TIMEOUT << 's' << endl;
    		return 0;	
    	}
    
       	
    	if(FD_ISSET(STDIN_FILENO, &readfds))
    	{
    		char buf[BUF_SIZE + 1];
    		int len;
    		len = read(STDIN_FILENO, buf,BUF_SIZE);
    		
    		if(len == -1)
    		{
    			cerr << "read()" << endl;
    			return 1;
    		}
    		
    		if(len)
    		{
    			buf[len] ='\0';
    			cout << buf << endl;
    
    		}
    
    		return 0;
    	}
    	
    	cerr << "this should not happen!" << endl;	
    	
    		return 0;
    
    }
    
    • 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
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    效果图

    会等待5秒,然后才输出结果

    在这里插入图片描述

  • 相关阅读:
    市面上跑步耳机哪种好、2023年最适合跑步用的耳机排名
    Unity实现简易坦克移动打炮
    数组排序,实现中间数字最大,向两边越来越小的排序
    传奇开服教程GOM传奇引擎外网全套架设教程
    利用 python 脚本 PyRdp 设置 RDS 服务器蜜罐 Honeypot
    MySQL忘记登录密码解决
    企业电子招标采购系统源码Spring Boot + Mybatis + Redis + Layui + 前后端分离 构建企业电子招采平台之立项流程图
    npm如何发布自己的插件包
    不允许你还不了解指针的那些事(二)(从入门到精通看这一篇就够了)(数组传参的本质+冒泡排序+数组指针+指针数组)
    分布式事务保姆级教程
  • 原文地址:https://blog.csdn.net/ccb1372098/article/details/132529442