百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux编程入门(6)-读取目录与文件的属性

liebian365 2024-10-27 13:21 16 浏览 0 评论

前言

上一篇文章学习了如何读取目录内容,并通过编程简单实现了 ls 指令。上篇文章链接为

《Linux编程入门(5)-读取目录》

ls 指令如果有选项 -l ,会显示文件的详细信息(如文件大小、文件所有者、修改时间等)。那如何获取这些信息呢?下面,让我们来一起分析、学习。

学习目标

通过分析指令 ls -l ,来学习 Linux 目录和文件属性相关的知识。

代码实验环境

操作系统:Ubuntu 18.04 LTS

编译器 gcc 版本:gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

分析 ls -l

ls -l 要做两件事:一是列出目录的内容,二是显示文件的详细信息。列出文件内容,前边已经分析过,从目录中读取即可。但是文件信息不包含在目录中,需要从另外的途径获得。

我们先看看标准 ls -l 的输出内容:

$ ls -l
total 2097260
drwxr-xr-x   2 root root       4096 9月  26 16:19 bin
drwxr-xr-x   3 root root       4096 11月 10 09:11 boot
drwxrwxr-x   2 root root       4096 4月  18  2021 cdrom
drwxr-xr-x  18 root root       4180 11月 17 08:58 dev
drwxr-xr-x 147 root root      12288 11月 17 08:59 etc
drwxr-xr-x   3 root root       4096 6月  22 14:43 home
lrwxrwxrwx   1 root root         34 11月  9 10:21 initrd.img -> boot/initrd.img-4.15.0-162-generic
lrwxrwxrwx   1 root root         34 11月  9 10:21 initrd.img.old -> boot/initrd.img-4.15.0-161-generic
drwxr-xr-x  22 root root       4096 4月  18  2021 lib
drwxr-xr-x   2 root root       4096 4月  18  2021 lib32
drwxr-xr-x   2 root root       4096 4月  18  2021 lib64
drwx------   2 root root      16384 4月  18  2021 lost+found
drwxr-xr-x   4 root root       4096 4月  21  2021 media
drwxr-xr-x   2 root root       4096 4月  27  2018 mnt
drwxr-xr-x   3 root root       4096 10月 29 13:08 opt
dr-xr-xr-x 242 root root          0 11月 17 08:58 proc
drwx------   7 root root       4096 5月  21 09:39 root
drwxr-xr-x  31 root root       1000 11月 17 09:03 run
drwxr-xr-x   2 root root      12288 11月 17 08:59 sbin
drwxr-xr-x  15 root root       4096 9月  29 08:53 snap
drwxr-xr-x   2 root root       4096 4月  27  2018 srv
-rw-------   1 root root 2147483648 4月  18  2021 swapfile
dr-xr-xr-x  13 root root          0 11月 17 10:16 sys
drwxrwxrwt  13 root root       4096 11月 17 20:09 tmp
drwxr-xr-x  15 root root       4096 4月  18  2021 usr
drwxr-xr-x  15 root root       4096 8月  13 17:37 var
lrwxrwxrwx   1 root root         31 11月  9 10:21 vmlinuz -> boot/vmlinuz-4.15.0-162-generic
lrwxrwxrwx   1 root root         31 11月  9 10:21 vmlinuz.old -> boot/vmlinuz-4.15.0-161-generic

每一行都包含 7 个字段

  • 模式(mode) 第一个字符表示文件类型。“-” 表述普通文件;“d” 表示目录。其他类型后续分析。接下来的 9 个字符表示文件的访问权限。三种访问权限:读、写、执行。分别针对 3 种对象:文件所有者、同组用户、其他用户。因此,用需要 9 位来表示。
  • 链接数(links)链接数指的是该文件被引用次数。这方面内容后续会进行学习。
  • 文件所有者(owner) 指出文件所有者的用户名。
  • 组(group)指的是文件所有者所在的组。
  • 大小(size)上面的显示信息中,所有的目录大小相等,都是 4096 个字节。因为目录所占空间的分配是以块为单位的,每个块 512 个字节,因此目录的大小经常是相等的。如果是一般文件,则显示文件中数据的实际字节数。
  • 最后修改时间文件最后的修改时间。如果是较新的文件,会列出 月、日和时刻。
  • 文件名

获取文件信息

系统调用函数 stat() 可以获取文件的详细信息。其函数原型如下:

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

int stat(const char *pathname, struct stat *statbuf);

参数 pathname,为文件名。

参数 statbuf,指向存储文件信息缓冲区的指针。

如果要读取文件属性信息,需要定义一个结构 struct stat,然后调用 stat(),告诉内核把文件属性存放到这个结构中。即 stat() 把 pathname 的属性信息复制到指针 statbuf 所指的结构中。

函数调用成功,返回 0 。调用遇到错误,则返回 -1 。

结构体 struct stat 类型定义如下:

struct stat 
{
  dev_t     st_dev;         /* ID of device containing file */
  ino_t     st_ino;         /* i节点号 */
  mode_t    st_mode;        /* 文件类型和许可权限 */
  nlink_t   st_nlink;       /* 文件链接数 */
  uid_t     st_uid;         /* 文件所有者用户ID */
  gid_t     st_gid;         /* 所属组的ID */
  dev_t     st_rdev;        /* Device ID (if special file) */
  off_t     st_size;        /* 文件所占的字节数 */
  blksize_t st_blksize;     /* Block size for filesystem I/O */
  blkcnt_t  st_blocks;      /* Number of 512B blocks allocated */

  struct timespec st_atim;  /* 文件最后修改时间 */
  struct timespec st_mtim;  /* 文件最后访问时间 */
  struct timespec st_ctim;  /* 文件属性最后修改时间 */

  /* 向后兼容 */
  #define st_atime st_atim.tv_sec  /* 文件最后修改时间 */
  #define st_mtime st_mtim.tv_sec  /* 文件最后访问时间 */
  #define st_ctime st_ctim.tv_sec  /* 文件属性最后修改时间 */
};

根据结构体信息字段可知,属性信息字段均是数字形式的。其中链接数和文件大小直接显示没有问题。最后修改时间、文件模式、文件用户、组等信息需要显示为字符串形式。

最后修改时间可以通过 ctime()函数转化成字符串,然后打印显示。

模式、用户名和组如何转化为字符串,需要进一步分析处理。

模式字段转换为字符

文件类型和许可权限存储在 st_mode 中,这是一个 16 位的二进制数,文件类型和权限被编码在这个数中,如下图所示

前 4 位用来表示文件类型,最多可以标识 16 种类型,目前已经使用了 7 个。

接下来的 3 位是文件特殊属性,1 代表具有某个属性,0 代表没有。这三位分别是 set-user-ID位、set-group-ID位、sticky位。

最后 9 位 是许可权限,分为 3 组,对应 3 种用户,分别是文件所有者、同组用户和其他用户。其他用户指的是与用户不再同一个组中的用户。每组 3 位,分别是读、写、执行的权限。如果对应位是 1,说明该用户拥有对应的权限,0 代表没有。

解码得到文件类型

使用掩码技术来解析文件类型。文件类型在模式字段的前 4 位,可以通过将其他部分置为 0 ,从而得到类型值。

为了比较,把不需要的地方置 0 ,需要的字段值不发生变化,这种技术称为掩码。与 0 做位与(&)操作可以将相应的 bit 置为 0 。

在 <sys/stat.h> 头文件中有以下定义:

#define S_IFMT     0170000   /* 文件类型掩码 */
#define S_IFSOCK   0140000   /* 套接字 */
#define S_IFLNK    0120000   /* 符号链接 */
#define S_IFREG    0100000   /* 普通文件 */
#define S_IFBLK    0060000   /* 块设备 */
#define S_IFDIR    0040000   /* 目录 */
#define S_IFCHR    0020000   /* 字符设备 */
#define S_IFIFO    0010000   /* 管道或FIFO */

S_IFMT 是一个掩码,值为 0170000(八进制数),可以用来过滤出 st_mode 前 4 位表示的文件类型。用模式字段 st_mode 与 S_IFMT 进行位与(&)运算,将结果与一系列常量进行比较,可以确定文件类型。示例代码:

if((statbuf.st_mode & S_IFMT) == S_IFREG)
{
  printf("regular file\n");
  /* 处理普通文件 */
}

有没有简单的方法进行文件类型判断呢?当然有。Linux 提供了标准宏来确定文件类型,这些宏的参数都是 stat 结构中的 st_mode 成员:

测试宏

文件类型

S_ISREG(m)

普通文件

S_ISDIR(m)

目录文件

S_ISCHR(m)

字符特殊文件(字符设备)

S_ISBLK(m)

块特殊文件(块设备)

S_ISFIFO(m)

管道或FIFO

S_ISLNK(m)

符号链接

S_ISSOCK(m)

套接字

使用这些宏,可以这样写代码:

if(S_ISREG(statbuf.st_mode))
{
  printf("regular file\n");
  /* 处理普通文件 */
}

解码得到许可权限

模式字段的最低 9 位是许可权限,标识了文件所有者、组用户、其他用户的读、写、执行权限。ls -l 将这些位转换为短横线和字母组成的字符串。

同样的,Linux 在头文件 <sys/stat.h> 定义了权限位常量:

#define S_IRWXU     00700   /* 文件所有者的权限掩码 */
#define S_IRUSR     00400   /* 文件所有者具有读权限 */
#define S_IWUSR     00200   /* 文件所有者具有写权限 */
#define S_IXUSR     00100   /* 文件所有者具有执行权限 */

#define S_IRWXG     00070   /* 用户组的权限掩码 */
#define S_IRGRP     00040   /* 组用户具有读权限 */
#define S_IWGRP     00020   /* 组用户具有写权限 */
#define S_IXGRP     00010   /* 组用户具有执行权限 */

#define S_IRWXO     00007   /* 其他用户的权限掩码 */
#define S_IROTH     00004   /* 其他用户具有读权限 */
#define S_IWOTH     00002   /* 其他用户具有写权限 */
#define S_IXOTH     00001   /* 其他用户具有执行权限 */

通过与模式字段 st_mode 进行位与(&)运算,可以判断特定权限位是否开启。

编程实现模式字段的转换

void mode_to_letters(int mode, char str[])
{
  /* 默认值 */
  strcpy(str, "----------");

  /* 目录 */
  if(S_ISDIR(mode))  str[0] = 'd';
  /* 字符设备 */
  if(S_ISCHR(mode))  str[0] = 'c';
  /* 块设备 */
  if(S_ISBLK(mode))  str[0] = 'b';
  /* 符号链接 */
  if(S_ISLNK(mode))  str[0] = 'l';

  /* 文件所有者权限 */
  if(mode & S_IRUSR)  str[1] = 'r';
  if(mode & S_IWUSR)  str[2] = 'w';
  if(mode & S_IXUSR)  str[3] = 'x';

  /* 用户组权限 */
  if(mode & S_IRGRP)  str[4] = 'r';
  if(mode & S_IWGRP)  str[5] = 'w';
  if(mode & S_IXGRP)  str[6] = 'x';

  /* 其他用户权限 */
  if(mode & S_IROTH)  str[7] = 'r';
  if(mode & S_IWOTH)  str[8] = 'w';
  if(mode & S_IXOTH)  str[9] = 'x';
}

用户和组ID转换成字符串

在结构 struct stat 中,文件所有者和组是以 ID 形式存在的,然而 ls -l 要求输出用户名和组名,如何根据 ID 找到用户名和组名呢?

包含用户列表的文件 /etc/passwd

这个文件包含了系统中所有用户信息。针对系统中每个账号,系统密码文件 /etc/passwd 会有专列一行进行描述。每行都包含 7 个字段,字段之间用冒号 ":" 隔开,如下所示:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
user:x:1000:1000:user,,,:/home/user:/bin/bash

这 7 个字段依次为:用户名(登录名)、经过加密的密码、用户 ID(UID)、组ID(GID)、用户描述信息、主目录、用户使用的 shell 程序。所有用户对这个文件都具有读权限。

注意,在单机系统中,所有用户信息都存储在 /etc/passwd 文件中。在网络计算机系统中,如果使用了 NIS (网络信息系统)或 LDAP(轻型目录访问协议)进行用户身份验证,那么部分密码信息可能会由远端系统保存。所有需要用户信息的程序也从 NIS 上获取。

shadow 密码文件 /etc/shadow

此文件的设计理念是,用户所有非敏感信息存放与 “人人可读” 的密码文件中,而经过加密处理的密码则有 shadow 密码文件单独维护,仅供具有特权的程序读取

shadow 密码文件包含有登录名(用户名)、经过加密的密码,以及其他若干与安全相关的字段。

注意,要是系统启用了 shadow 密码(这是常规做法),系统将不会解析 /etc/passwd 文件中的密码字段,这个密码字段会包含字母 “x”(也可以是其他非空字符串)。这就解析了上述 /etc/passwd 文件中密码字段为什么是 “x”。

组文件 /etc/group

此文件是一个保存所有的组信息的文本文件。系统中每个组在文件 /etc/group 中都对应着一条记录。每条记录包含 4 个字段,之间用冒号 “:” 分隔,如下所示:

root:x:0:
daemon:x:1:
user:x:1000:
tech:x:1002:avr,rbl,alc

组信息的 4 个字段分别为:组名、组密码、组 ID(GID)、组中成员列表。其中,组成员列表各个用户名以都好分隔。

好了,用户名和用户组的信息存储位置分析清楚了。那我们可以读取 /etc/passwd 和 /etc/group 这两个文件,搜索对应的用户 ID 和组 ID,就可以得到对应的字符串名字了。实际上是这样吗?答案是否定的。

Linux 提供了专门获取这些信息的函数调用。

获取用户和组的信息

系统函数 getpwuid() 可以获取用户信息记录。如果用户信息存在 /etc/passwd中,那么 getpwuid() 会查找 /etc/passwd 的内容;如果用户信息在 NIS 中,getpwuid() 会从 NIS 中获取信息。其函数原型如下:

#include <sys/types.h>
#include <pwd.h>

struct passwd *getpwuid(uid_t uid);

参数 uid,为用户 ID。

调用成功,则返回一个指向 struct passwd 的指针;调用失败,则返回NULL。

结构体 struct passwd 包含了与密码记录相对应的信息:

 struct passwd 
 {
   char   *pw_name;       /* 用户名 */
   char   *pw_passwd;     /* 用户密码 */
   uid_t   pw_uid;        /* 用户 ID */
   gid_t   pw_gid;        /* group ID */
   char   *pw_gecos;      /* 用户信息 */
   char   *pw_dir;        /* 主目录 */
   char   *pw_shell;      /* shell 程序 */
 };

系统函数 getgrgid() 可以获取组信息记录。同样的,在网络计算机系统中,如果组信息也被保存在 NIS 中,此函数也可从 NIS 中获取组信息。其函数原型如下:

#include <sys/types.h>
#include <grp.h>

struct group *getgrgid(gid_t gid);

参数 gid,为组 ID。

调用成功,则返回一个指向 struct group 的指针;调用失败,则返回NULL。

结构体 struct group 定义:

struct group 
{
  char   *gr_name;        /* 组名 */
  char   *gr_passwd;      /* 组密码 */
  gid_t   gr_gid;         /* 组 ID */
  char  **gr_mem;         /* 组用户成员列表指针 */
};

编写代码实现 ls -l

编写代码 ls2.c 如下:

#include<stdio.h>
#include<sys/types.h>
#include<dirent.h>
#include<sys/stat.h>
#include<pwd.h>
#include<grp.h>
#include<time.h>
#include<string.h>

void do_ls(char dirname[]);
void do_stat(char *dirname, char *filename);
void show_file_info(char *filename, struct stat *info_p);
void mode_to_letters(int mode, char str[]);
char *uid_to_name(uid_t uid);
char *gid_to_name(gid_t gid);

int main(int argc, char *argv[])
{
	if(argc == 1)
	{
		do_ls(".");
	}
	else
	{
		while(--argc)
		{
			printf("%s:\n", *++argv);
			do_ls(*argv);
		}
	}

	return 0;
}

void do_ls(char dirname[])
{
	DIR *dir_ptr = NULL;
	struct dirent *direntp = NULL;

	if((dir_ptr = opendir(dirname)) == NULL)
	{
		fprintf(stderr, "ls2: cannot open %s\n", dirname);
	}
	else
	{
		while((direntp = readdir(dir_ptr)) != NULL)
		{
			do_stat(dirname, direntp->d_name);
		}
		closedir(dir_ptr);
	}
}

void do_stat(char *dirname, char *filename)
{
	struct stat info;
	char pathname[256];
	
	sprintf(pathname, "%s//%s", dirname, filename);
	
	if(stat(pathname, &info) == -1)
	{
		perror(filename);
	}
	else
	{
		show_file_info(filename, &info);
	}
}

void show_file_info(char *filename, struct stat *info_p)
{
	char modestr[11];

	mode_to_letters(info_p->st_mode, modestr);

	printf("%-12s", modestr);
	printf("%-8d", (int)info_p->st_nlink);
	printf("%-8s", uid_to_name(info_p->st_uid));
	printf("%-8s", gid_to_name(info_p->st_gid));
	printf("%-16ld", (long)info_p->st_size);
	printf("%-16.12s", 4 + ctime(&info_p->st_mtime));
	printf("%s\n", filename);
}

void mode_to_letters(int mode, char str[])
{
	strcpy(str, "----------");

	if(S_ISDIR(mode))  str[0] = 'd';
	if(S_ISCHR(mode))  str[0] = 'c';
	if(S_ISBLK(mode))  str[0] = 'b';
	if(S_ISLNK(mode))  str[0] = 'l';

	if(mode & S_IRUSR)  str[1] = 'r';
	if(mode & S_IWUSR)  str[2] = 'w';
	if(mode & S_IXUSR)  str[3] = 'x';

	if(mode & S_IRGRP)  str[4] = 'r';
	if(mode & S_IWGRP)  str[5] = 'w';
	if(mode & S_IXGRP)  str[6] = 'x';

	if(mode & S_IROTH)  str[7] = 'r';
	if(mode & S_IWOTH)  str[8] = 'w';
	if(mode & S_IXOTH)  str[9] = 'x';
}

char *uid_to_name(uid_t uid)
{
	struct passwd *pw_ptr = NULL;
	static char numstr[10];

	if((pw_ptr = getpwuid(uid)) == NULL)
	{
		sprintf(numstr, "%d", uid);
		return numstr;
	}
	else
	{
		return pw_ptr->pw_name;
	}
}

char *gid_to_name(gid_t gid)
{
	struct group *grp_ptr = NULL;
	static char numstr[10];

	if((grp_ptr = getgrgid(gid)) == NULL)
	{
		sprintf(numstr, "%d", gid);
	}
	else
	{
		return grp_ptr->gr_name;
	}
}

编译运行

$ gcc ls2.c -o ls2

./ls2 /
/:
drwxrwxr-x  2       root    root    4096            Apr 18 15:05    cdrom
drwxr-xr-x  18      root    root    4180            Nov 19 08:58    dev
drwxr-xr-x  2       root    root    12288           Nov 19 08:59    sbin
drwxr-xr-x  25      root    root    4096            Nov  9 10:21    .
drwxrwxrwx  13      root    root    4096            Nov 19 13:56    tmp
dr-xr-xr-x  13      root    root    0               Nov 19 08:57    sys
dr-xr-xr-x  242     root    root    0               Nov 19 08:57    proc
drwxr-xr-x  22      root    root    4096            Apr 18 23:24    lib
-rw-------  1       root    root    8453792         Oct 18 18:37    vmlinuz
drwx------  7       root    root    4096            May 21 09:39    root
drwxr-xr-x  147     root    root    12288           Nov 19 08:59    etc
drwx------  2       root    root    16384           Apr 18 15:03    lost+found
drwxr-xr-x  2       root    root    4096            Apr 18 20:21    lib64
-rw-r--r--  1       root    root    42709349        Oct 29 13:09    initrd.img.old
drwxr-xr-x  15      root    root    4096            Aug 13 17:37    var
drwxr-xr-x  2       root    root    4096            Apr 18 23:25    lib32
drwxr-xr-x  3       root    root    4096            Jun 22 14:43    home
drwxr-xr-x  2       root    root    4096            Apr 27 02:18    mnt
drwxr-xr-x  31      root    root    1000            Nov 19 09:03    run
drwxr-xr-x  3       root    root    4096            Oct 29 13:08    opt
drwxr-xr-x  3       root    root    4096            Nov 10 09:11    boot
drwxr-xr-x  15      root    root    4096            Apr 18 23:25    usr
drwxr-xr-x  15      root    root    4096            Sep 29 08:53    snap
drwxr-xr-x  25      root    root    4096            Nov  9 10:21    ..
-rw-------  1       root    root    2147483648      Apr 18 15:03    swapfile
drwxr-xr-x  4       root    root    4096            Apr 21 16:56    media
drwxr-xr-x  2       root    root    4096            Apr 27 02:18    srv
drwxr-xr-x  2       root    root    4096            Sep 26 16:19    bin
-rw-r--r--  1       root    root    42715926        Nov  9 10:21    initrd.img
-rw-------  1       root    root    8453792         Oct 15 21:19    vmlinuz.old

ls2 的输出看起来接近标准 ls -l 的输出了。模式字段、用户名和组名的处理已经完成。当然,跟标准 ls 相比,还差好多功能没实现。比如,记录总数、按文件名排序、不支持 -a 选项 等等。

不过,我们的学习目的达到了,这些待完善的功能,如果有兴趣,可以自己尝试去实现。

小结

通过分析 ls -l 的实现过程,学习了 Linux 目录和文件很多相关的知识:

  • 目录与文件目录是特殊的文件,其内容是文件和子目录的名字。
  • 用户与组每个用户都有用户名和用户 ID,系统通过 UID 区分不同用户的文件。每个用户都属于至少一个组,每个组都有组名和组 ID。
  • 文件属性每个文件都有很多属性,程序可以通过系统调用来得到文件的属性信息。
  • 许可权限文件的许可权限决定了哪种用户可以进行哪些操作。

学习的系统函数有:

  • stat()
  • getpwuid()
  • getgrgid()
  • 文件类型判断的宏

至此,我们详细分析了目录和文件属性信息的部分内容,下篇继续学习一下剩余没讲解的属性信息,这样就齐活了。

OK,精彩继续!

相关推荐

4万多吨豪华游轮遇险 竟是因为这个原因……

(观察者网讯)4.7万吨豪华游轮搁浅,竟是因为油量太低?据观察者网此前报道,挪威游轮“维京天空”号上周六(23日)在挪威近海发生引擎故障搁浅。船上载有1300多人,其中28人受伤住院。经过数天的调...

“菜鸟黑客”必用兵器之“渗透测试篇二”

"菜鸟黑客"必用兵器之"渗透测试篇二"上篇文章主要针对伙伴们对"渗透测试"应该如何学习?"渗透测试"的基本流程?本篇文章继续上次的分享,接着介绍一下黑客们常用的渗透测试工具有哪些?以及用实验环境让大家...

科幻春晚丨《震动羽翼说“Hello”》两万年星间飞行,探测器对地球的最终告白

作者|藤井太洋译者|祝力新【编者按】2021年科幻春晚的最后一篇小说,来自大家喜爱的日本科幻作家藤井太洋。小说将视角放在一颗太空探测器上,延续了他一贯的浪漫风格。...

麦子陪你做作业(二):KEGG通路数据库的正确打开姿势

作者:麦子KEGG是通路数据库中最庞大的,涵盖基因组网络信息,主要注释基因的功能和调控关系。当我们选到了合适的候选分子,单变量研究也已做完,接着研究机制的时便可使用到它。你需要了解你的分子目前已有哪些...

知存科技王绍迪:突破存储墙瓶颈,详解存算一体架构优势

智东西(公众号:zhidxcom)编辑|韦世玮智东西6月5日消息,近日,在落幕不久的GTIC2021嵌入式AI创新峰会上,知存科技CEO王绍迪博士以《存算一体AI芯片:AIoT设备的算力新选择》...

每日新闻播报(September 14)_每日新闻播报英文

AnOscarstatuestandscoveredwithplasticduringpreparationsleadinguptothe87thAcademyAward...

香港新巴城巴开放实时到站数据 供科技界研发使用

中新网3月22日电据香港《明报》报道,香港特区政府致力推动智慧城市,鼓励公私营机构开放数据,以便科技界研发使用。香港运输署21日与新巴及城巴(两巴)公司签署谅解备忘录,两巴将于2019年第3季度,开...

5款不容错过的APP: Red Bull Alert,Flipagram,WifiMapper

本周有不少非常出色的app推出,鸵鸟电台做了一个小合集。亮相本周榜单的有WifiMapper's安卓版的app,其中包含了RedBull的一款新型闹钟,还有一款可爱的怪物主题益智游戏。一起来看看我...

Qt动画效果展示_qt显示图片

今天在这篇博文中,主要实践Qt动画,做一个实例来讲解Qt动画使用,其界面如下图所示(由于没有录制为gif动画图片,所以请各位下载查看效果):该程序使用应用程序单窗口,主窗口继承于QMainWindow...

如何从0到1设计实现一门自己的脚本语言

作者:dong...

三年级语文上册 仿写句子 需要的直接下载打印吧

描写秋天的好句好段1.秋天来了,山野变成了美丽的图画。苹果露出红红的脸庞,梨树挂起金黄的灯笼,高粱举起了燃烧的火把。大雁在天空一会儿写“人”字,一会儿写“一”字。2.花园里,菊花争奇斗艳,红的似火,粉...

C++|那些一看就很简洁、优雅、经典的小代码段

目录0等概率随机洗牌:1大小写转换2字符串复制...

二年级上册语文必考句子仿写,家长打印,孩子照着练

二年级上册语文必考句子仿写,家长打印,孩子照着练。具体如下:...

一年级语文上 句子专项练习(可打印)

...

亲自上阵!C++ 大佬深度“剧透”:C++26 将如何在代码生成上对抗 Rust?

...

取消回复欢迎 发表评论: