0%

文件I/O

构造及使用

File 类

构造方法

  • File(pathname: String)
  • File(parent: String, child: String)
  • File(parent: File, child: String)

方法

  • exists(): boolean, 检测文件或文件夹是否存在
  • canRead(): boolean, 可读性
  • canWrite(): boolean, 可写性
  • length(): long, 返回文件大小
  • lastModified(): long, 最后修改时间
  • listFile(): File[], 返回当前对象路径下的文件
  • renameTo(dest: File): boolean, 重命名文件

PrintWriter类

构造方法

  • PrintWriter(file: File), 接受一个 File 对象进行构建
  • PrintWriter(filename: String)

方法

  • print(), 与 System.out.print() 一样的用法
  • println()
  • printf()

Scanner类

构造方法

  • Scanner(source: File)
  • Scanner(source: String)

方法

  • close() 关闭这个扫描器
  • hasNext(): boolean 检测这个扫描器还有无可读的内容
  • useDelimiter(pattern: String), 设置分隔模式

替换文本(待完善)

  • 需求: 编写一个程序, 从命令行接受参数, 实现对文件内容替换, 如

java ReplaceFileContents oldString newString sourceFile targetFile

将实现对 sourceFile 中的 oldString 替换为 newString, 并保存到 targetFile 中.

  • ReplaceFileContents.java
    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
    /*
    ReplaceFileContents.java
    */
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.PrintWriter;
    import java.util.Scanner;

    public class ReplaceFileContents {
    public static void main(String[] args) throws FileNotFoundException {
    String currentString = args[0];
    String replaceString = args[1];
    String oldFileName = args[2];
    String newFileName = args[3];

    File oldFile = new File(oldFileName);
    File newFile = new File(newFileName);
    PrintWriter file = new PrintWriter(newFile);
    Scanner input = new Scanner(oldFile);

    while (input.hasNext()){
    file.println(input.nextLine().replaceAll(currentString,replaceString));
    }
    file.close();
    }
    }
  • old.txt
    1
    2
    3
    my name is ljguo,
    ljguo is me,
    ljguo love gxx.

    javac ReplaceFileContents.java
    java ReplaceFileContents ljguo LJGUO old.txt new.txt

会在当前目录下生成文件new.txt, 内容为

1
2
3
my name is LJGUO,
LJGUO is me,
LJGUO love gxx.

异常处理

在 Java 中, 异常会导致程序运行时错误, 如果没有得到处理, 程序将会终止
例如

1
2
3
4
Scanner input = new Scanner(System.in);
int num1 = input.nextInt();
int num2 = input.nextInt();
System.out.println(num1 + " / " + num2 + " is " + (num1/num2));

如果为 num2 键入 0, Java 会抛出一个 ArithmeticException 的异常, 程序终止
为解决这个问题, 可以用如下的方式.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
System.out.println("Please insert two numbers for num1 and num2: ");
int num1 = input.nextInt();
int num2 = input.nextInt();
while (true) {
if (num2 != 0) {
System.out.println(num1 + " / " + num2 + " is " + (num1 / num2));
break;
} else {
System.out.println("Try insert again: ");
num2 = input.nextInt();
}
}
}
}

当然, 这是不优雅的, 没有扩展性, 我们采取 Java 的异常处理

  • try-catch

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import java.util.Scanner;

    public class Main {
    public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    System.out.println("Please insert two numbers for num1 and num2: ");
    int num1 = input.nextInt();
    int num2 = input.nextInt();
    try {
    System.out.println(num1 + " / " + num2 + " is " + (num1 / num2));
    }catch (ArithmeticException ex) {
    System.out.println("cannot be divided by zero");
    }
    }
    }

    try 块是可能发生异常的块, catch 用于捕获参数中的异常, 此处是 ArithmeticException, 一旦捕获到异常, 立马跳转到 catch

  • 创建异常
    异常也是一个类, 可以创建对象, 如下创建并抛出异常

    1
    2
    ArithmeticException ex = new ArithmeticException("Message");
    throw ex;
  • 申明异常
    如果一个方法可能出现异常, 创建时需要声明异常, 以便用户使用时能够知道此方法可能抛出异常, 既进行处理

    1
    2
    3
    public void throwTest() throws Exception{
    xxx;
    }

    throws 用于声明异常, 可以声明多个, 以逗号分隔

  • 异常类型

    • 系统错误, 由 Java 虚拟机抛出, 用 Error 表示
      • LinkageError
    • 异常, 用 Exception 表示, 这类可以被程序捕获处理
      • ClassNotFoundException, 企图使用一个不存在的类
      • IOException, 文件相关
    • 运行时异常, 用 RuntimeException 表示
      • ArithmeticException, 算术异常, 整数除以 0 导致, 注意浮点数不会
      • NullPointerException, 企图通过一个 null 引用变量访问一个对象
      • IndexOutOfBoundsException, 数组下标越界
  • finally 子句
    用于异常被捕获与否都会执行的块


多线程

  • 通过继承 Thread 类构造多线程
    1
    2
    3
    class Threadx extends Thread{

    }
  • 重写 run 方法,方法内部是要让线程做的任务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public void run() {
    while (true) {
    if (trick <= 0) {
    System.out.println("票已卖完");
    break;
    }
    System.out.println(getName() + "正在售卖第" + (100 - trick + 1) + "张票,还剩下" + (trick--) + "张票");
    }
    }
  • 创建并开启线程
    1
    2
    Threadx t = new Threadx();
    t.start();
  • 通过 Runnable 实现
    1
    2
    3
    class T implements Runnable {

    }
  • 重写 run 方法,方法内部是要让线程做的任务
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @Override
    public void run() {
    while (true) {
    if (trick <= 0) {
    System.out.println("票已卖完");
    break;
    }
    System.out.println(getName() + "正在售卖第" + (100 - trick + 1) + "张票,还剩下" + (trick--) + "张票");
    }
    }
  • 创建线程对象
    1
    2
    3
    4
    5
    6
    7
    T win1 = new T();
    Thread t1 = new Thread(win1,"窗口一");
    Thread t2 = new Thread(win1,"窗口二");
    Thread t3 = new Thread(win1,"窗口三");
    t1.start();
    t2.start();
    t3.start();

解决线程不安全

  • synchronized 锁方法
    run 方法锁起来
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    while (true) {
    synchronized (this) {
    if (trick <= 0) {
    System.out.println("票已卖完");
    break;
    }
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    System.out.println(Thread.currentThread().getName() + "正在售卖第" + (100 - trick + 1) + "张票,还剩下" (trick--) + "张票");
    }

JOptionPane 类

  • 导入包
    1
    import javax.swing.JOptionPane;
  • showMessageDialog
    1
    JOptionPane.showMessageDialog(null, x, y, JOptionPane.INFORMATION_MESSAGE);
    • 第一个参数总是 null
    • x 是要显示的字符串
    • y 是消息对话框的标题
    • JOptionPane.INFORMATION_MESSAGE 是图标
  • showInputDialog
    1
    String string = JOptionPane.showMessageDialog(null, x, y, JOptionPane.INFORMATION_MESSAGE);
    • x 是提示字符串
    • y 是标题字符串
    • 返回的是字符串, 若需要其它类型需要转换, 如
      1
      int intValue = Integer.parseInt(string); 

String

1
String str = "ljguo";

创建及使用

String 类有很多构造方法, 例如接受字符串, 字符数组等.

1
2
3
String str = new String("my name is ljguo");
char[] charArray = {'l', 'j', 'g', 'u', 'o'};
String str = new String(charArray);

诸如 "ljguo" 这种称为字符串直接量, java 为了提高效率和节约内存, 对具有相同字符串序列的字符串直接量使用同一实例(对象), 也即

1
2
3
4
5
6
7
String str1 = "ljguo";
String str2 = "ljguo";
String str3 = new String("ljguo");
/*
str1 == str2 : true
str1 == str3 : false
*/

这是因为 str1str2 在用 == 进行比较的时候比较的是引用值(类似于地址), 而二者指向同一对象, 故它们的引用值是相等的.
String 类还提供了一些方法

  • equals(s: String): boolean 比较两个字符串对象所指的字符串是否相等, 继承自 Object.
  • equalsIgnoreCase(s: String): boolean 忽略大小写的 equals.
  • compareTo(s: String): int 比较字符串, 返回一个 int.
  • startsWith(prefix: String): boolean, 返回一个 boolean, 用于检测该字符串是否以 prefix 为前缀.
  • endsWith(suffix: String): boolean, 返回一个 boolean, 用于检测该字符串是否以 suffix 为后缀.
    此外还有一些方法, 例如: toLowerCase, replace, replaceFirst, replaceAll, split, matches等, 他们都支持以正则表达式进行匹配, 更多的方法参考API.

类型转换

  • Integer.parseInt(s: String): int 将字符串转换为整数, 这是Integer类提供的方法, 其他基本类型对应的包装类也都提供了对应的方法
  • valueOf() 将其他基本数据转换为字符串.
  • toCharArray() 将字符串转换为字符数组.

StringBuilder和StringBuffer类

这两个类用来代替 String 类, 处理可变长字符串.

1
2
3
StringBuffer str1 = new StringBuffer(); // 创建长度为 16 的生成器
StringBuffer str2 = new StringBuffer(10); // length: 10
StringBuffer str3 = new StringBuffer("ljguo"); // length: "ljguo".length();

方法

  • append(d: char[]) 添加 char 数组
  • append(s: String) 添加字符串
  • append(d: char[], offset: int, length: int)
  • append(v: aPrimetiveType) 添加基本类型
  • insert(), charAt(), deleteCharAt(), reverse()
  • capacity() 返回生成器容量, 区分 length()
  • trimTosize() 可以将容量降低到实际长度(用于节省内存)

Students 类(待更新)

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
package Students;

public class Students {
private String[] elements;
private int STUDENTS_SIZE = 5;
private int numberOfStudents = 0;
public Students() {
elements = new String[STUDENTS_SIZE];
}

public void push(String name) {
if (numberOfStudents >= STUDENTS_SIZE) {
String[] temp = new String[2 * STUDENTS_SIZE];
STUDENTS_SIZE *= 2;
System.arraycopy(elements, 0, temp, 0, elements.length);
elements = temp;
}
elements[numberOfStudents++] = name;
}

public int getNumberOfStudents() {
return numberOfStudents;
}

public String getStudent(int index) {
if(index < numberOfStudents){
return elements[index];
}else {
throw new ArrayIndexOutOfBoundsException("Index " + index + " out of bounds for length " + numberOfStudents);
}

}

public String toString() {
StringBuilder tem = new StringBuilder();
for (int i = 0; i < numberOfStudents; i++) {
if (i < numberOfStudents - 1)
tem.append(elements[i]).append(",");
else
tem.append(elements[i]);
}
return "[" + tem +"]";
}

public String pop() {
return elements[--numberOfStudents];
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import Students.*;

import java.util.ArrayList;
import java.util.Date;

public class StudentsTest {
public static void main(String[] args) {

Students st = new Students();
st.push("ljguo");
st.push("张三");
st.push("李四");
st.push("王二麻子");
st.push("王八蛋");
st.push("狗头");
st.push("二蛋子");

System.out.println(st.getStudent(5));
System.out.println(st.getNumberOfStudents());

System.out.println(st.pop());
System.out.println(st);
}
}
1
2
3
4
狗头
7
二蛋子
[ljguo,张三,李四,王二麻子,王八蛋,狗头]

介绍

  • JDK Java Development Kit (Java 开发环境)

  • JRE Java Runtime Environment (Java 运行环境)

  • JVM Java Virtual Mechines (Java 虚拟机)

  • 两个编译命令

    • javac 将 java 源文件 .java 编译成 .class 字节码文件
    • java 将解析 .class 文件
  • 变量命名

    • 驼峰命名: 除了第一个单词, 后面单词首字母大写
    • 允许使用数字, 字母, 美元符号, 下划线, 数字不能作为开头
    • 不能使用关键字
  • final 用于声明常量

  • 数据类型

    • 基本数据类型

      类型 大小 范围
      byte(字节类型) 1byte(8bit) -128 - 127
      short 2byte $-2^{15}$ - $2^{15}-1$
      int 4byte $-2^{31}$ - $2^{31}-1$, $-2147483648$ - $2147483647$
      long 8byte $-2^{63}$ - $2^{63}-1$, 为其赋值需要加 L 或 l
      float (单精度) 4byte $-3.4\times 10^{38}$ - $3.4\times 10^{38}$, 为其赋值需要加 F 或 f
      double (双进度) 8byte
      char (字符型) 2byte 表示字符, unicode 码, (0 - 65535)
      boolean (布尔类型) 1byte true, false
    • 引用数据类型

      String, Integer, Double 等类

本文介绍如何在安卓手机上(本地)部署 LaTeX


写在前面:本文默认读者已经熟悉一些简单的 Linux 命令行操作,以及知道如何使用命令行来编译 LaTeX. (不会也没关系,花个10来分钟学一下)


个人本是极力反对在手机上编程的,但是有时候出门在外,身边没有电脑,又需要做一些代码的简单修改,调试. 这个东西倒也还是不错.

当然,我们有一些在线的编译网站,比较出名的 “overleaf” “texpage” etc . 但是对网络有一定的要求,总觉得还是不太好.下面就来介绍如何在安卓手机端安装一个 texlive (LaTeX 的一个发行版):

安装 Termux

介绍(废话)

Termux 是一款运行于 Android 系统的开源终端模拟器. 该软件提供了 Linux 环境,即使设备不具备 root 权限也可使用. 通过自带的包管理器(pkg、 apt),Termux 可以安装许多现代化的开发和系统维护工具,例如 zsh、Python、Ruby、NodeJS、MySQL 等软件.

如何安装

如果条件允许,可以使用 谷歌商店 或者 F-Droid 来安装它.当然也可以使用下面的百度云链接来下载安装

链接:https://pan.baidu.com/s/1b_lrPvcLXq9fIdUDAjs8MQ
提取码:1234

安装好了直接打开,出现如下界面

Image

换源

复制以下内容,输入在这个黑窗口(以下我们称终端)里面

1
2
3
4
sed -i 's@^\(deb.*stable main\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/termux-packages-24 stable main@' $PREFIX/etc/apt/sources.list
sed -i 's@^\(deb.*games stable\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/game-packages-24 games stable@' $PREFIX/etc/apt/sources.list.d/game.list
sed -i 's@^\(deb.*science stable\)$@#\1\ndeb https://mirrors.tuna.tsinghua.edu.cn/termux/science-packages-24 science stable@' $PREFIX/etc/apt/sources.list.d/science.list
pkg update

然后回车,遇到不动了,直接按回车. 直到换源结束. 如下图:

Image

美化一下终端

先安装一些基础工具,终端输入

1
2
pkg update
pkg install vim curl wget git tree -y

然后回车,直到安装成功,随后执行

1
sh -c "$(curl -fsSL https://github.com/Cabbagec/termux-ohmyzsh/raw/master/install.sh)"  

如果请求访问权限,请允许.随后会出现让你选择配色和字体,随便选择吧,后面可以改.

注意:这里访问 Github 可能会比较慢,甚至失败. 可以使用科学上网,或者换成以下命令

1
sh -c "$(curl -fsSL https://html.sqlsec.com/termux-install.sh)"  

然后重启终端,你会发现它变样了.
Image

美化到此结束吧!

安装 texlive

终端执行

1
pkg install texlive-full

然后回车,等待下载安装结束. 大概又接近 4 个G吧,请保证手机内存充足.

安装完成后如下图:
Image
这时候还没完成,我们依据提示需要执行:

1
cd $PREFIX/etc/profile.d

然后执行

1
. ./texlive.sh

Image

然后执行以下命令检查是否安装成功

1
tex -v

如果出现如下信息,则大功告成了.
Image

编译第一份文档

编辑器的选择

先重启一下Termux吧.

编辑器呢,很多,这里推荐 vim 或者 nano , 分别执行以下命令安装

1
2
pkg install vim
pkg install nano

不会 vim 的朋友可以学习一下,下面用 nano 演示一下
直接执行

1
nano main.tex

来创建一个 tex 文件, 然后将以下内容复制进去

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
\documentclass{ctexart}
\usepackage{amsmath}
\title{Welcome to \LaTeX}
\author{ljguo}
\date{\today}
\begin{document}
\maketitle

hello \LaTeX{}!
你好 \LaTeX{}!

这是勾股定理
\[
a^{2}+b^{2}=c^{2}
\]
\end{document}

复制好后如下
Image

CTRL+O来保存文件,随后按回车确认保存,注意文件名,不要被改了

Image

最后 CTRL+X,来退出编辑器,回到终端界面
ls 命令查看文件是否存在
Image

如果存在就可以编译了,执行命令

1
xelatex main.tex

开始编译,随后出现如下信息表示编译成功
Image
然后用 ls 命令查看
Image
多出了几个以 main 开头的文件,我们需要这个 pdf 文件,执行

1
termux-open ./main.pdf

来打开这个 pdf 文件,随后就会自动跳转到打开 pdf 了
Image

一些补充

使用

1
cd /sdcard

可以进入你的手机目录下,然后可以建一个文件夹来专门写 LaTeX, 同时也方便传输.


先到这里吧,有问题评论区指出.

参考

Termux 高级终端安装使用配置教程
https://www.sqlsec.com/2018/05/termux.html

我们采用镜像下载安装

下载texlive镜像文件

这里给出清华大学的镜像源

下载到一个自己能够找得到的地方,这里默认你已经熟悉一些基本的命令行操作

挂载镜像文件

这里假设你的.iso文件路径为/path/iso/textive2021-texlive20210325.iso ,在命令行窗口通过 cd 命令进入该文件夹,如下图,这是我的.iso文件所在路径
文件路径
然后我们就可以开始进行挂载镜像了

mkdir ~/iso

在命令行执行以上命令,这个命令是在 “~” 目录,也即是家(home)目录下创建一个名叫“iso”的目录,用于挂载镜像文件。这里不理解的话,其实镜像文件也相当于一个压缩包,我们创建这个文件夹就是用来将.iso文件解压在其中
创建挂载目录

sudo mount texlive2021-20210325.iso ~/iso -o loop

这条命令用于挂载镜像
挂载镜像

随后进入刚刚创建的文件夹,执行

cd ~/iso

进入挂载目录
然后用 ls 命令可以看到里面有很多文件,其中有个install-tl就是我们的安装脚本

查看内部文件

开始安装

然后执行

sudo ./install-tl

执行脚本,安装texlive

你将会看到:

选择界面

输入 i 然后回车,等待安装,界面如下

安装界面

一直等待,大概在20~30分钟,也说不准,直到出现

欢迎进入 TeX Live 的世界

或者是

welcome to texlive

等文字信息,即表示安装成功.

你以为你真的安装成功了吗???,nonono,接下来我们还需要做一些配置:

添加环境变量

执行

sudo gedit ~/.bashrc

打开 .bashrc
此命令表示使用 Gedit 编辑器打开 .bashrc 这个配置文件,如果你是WSL用户,没有自带该编辑器,可将 gedit 换成 vi 或者 vim ,打开之后将以下内容添加到里面(如果你用的zsh,可以将 .bashrc 换成 .zshrc )

注意:如果用 vim 请先了解一下 vim 编辑器的使用方法

export PATH=/usr/local/texlive/2021/bin/x86_64-linux:$PATH

export MANPATH=/usr/local/texlive/2021/texmf/doc/man:$MANPATH

export INFOPATH=/usr/local/texlive/2021/texmf/doc/info:$INFOPATH

配置 .bashrc
保存,然后命令行继续执行

sudo gedit /etc/manpath.config

同样,也是打开一个配置文件,在里面相对应的位置加入以下内容,同样也可用 vim 编辑器打开

MANPATH_MAP /usr/local/texlive/2021/bin/x86_64-linux /usr/local/texlive/2021/texmf/doc/man

配置 manpath.config

保存退出,重新打开终端,用以刷新配置文件,然后在新的终端里面执行

tex -v

得到如下界面,则表示安装成功

终于成功啦

接下来你就可以开始写 LaTeX 代码了.

开始第一份文档

终端执行

touch main.tex

新建一个 .tex 文件,用编辑器打开这个文件,在里面写入代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
\documentclass{ctexart}
\usepackage{amsmath}
\title{Welcome to \LaTeX}
\author{ljguo}
\date{\today}
\begin{document}
\maketitle

hello \LaTeX{}!
你好 \LaTeX{}!

这是勾股定理
\[
a^{2}+b^{2}=c^{2}
\]
\end{document}

写入 .tex 文件内容

保存,然后命令行执行

xelatex main.tex

编译文件

你将会看到如下内容

开始编译了

直到……

编译成功

这个过程不过1~2秒,表示编译成功了,随即使用 ls 命令来查看里面的东西

pdf文件

多了几个main开头的文件(请自动忽略我的其他文件)然而我们关系的就是这个pdf文件,请到相应的文件加打开它,如果不知道你自己所处的文件夹位置,可以使用 pwd 命令来查看.

查看所在目录

然后去打开它吧.

pdf文件显示

这时候,你应该算是大功告成了.别急着乐,在LaTeX这条路上,你只是跨入了大门.不过,嘿嘿,万事开头难嘛.加油!

其他

请记住 texdoc 命令,用于打开宏包帮助文档,使用方法如下

texdoc 宏包/文件名

例如, texdoc amsmath 查看amsmath 宏包的说明文档
另外,推荐入门必读的手册

lshort-zh-zn

install-latex-guide-zh-cn

第一份文档,入门使用,看完它之后,你的一些日常写作应当没有任何问题,强烈推荐

第二份文档,解决你安装过程中遇到的 99% 的问题,请按需仔细阅读

调出它们的方法很简单,只需要命令行执行

texdoc lshort-zh-zn

texdoc install-latex-guide-zh-cn

好了,先写到这里吧…….

介绍

texlive想必大家都已经很熟悉了,这里不做介绍;CTeX套装我不熟悉,也不做过多介绍

CTeX套装已经10年左右未更新了,CTeX套装的存在是为了解决正文在 LaTeX 中的排版,现在已经有了更好的替代品 ctex宏集 ,原则上来说,它已经完成自己的使命,应该退出历史的舞台了.

但是由于国内有些中文期刊仍然在使用 CTeX 套装,所以要想投稿这些期刊,不得不使用它,一些大佬建议不要在一台机器上装两个发行版,原因应该是当你使用编译引擎编译文档时,新手无法确定使用的是哪个发行版提供的,本文旨在简单介绍如何通过修改环境变量,使得可以控制使用哪一个发行版

安装

点击下面链接,下载CTeX套装,如何无脑下一步安装就行,建议安装位置不要放在C盘(我的安装位置是E:\CTEX)

CTeX下载地址

安装好之后,打开环境变量,具体方法是

右键此电脑 —> 属性 —> 高级系统设置 —> 环境变量

找到系统变量中的 Path 变量栏,然后打开

红框的都是 CTeX 的环境变量,篮框的是 texlive 的环境变量,原则上编译的时候都是在命令行调用可执行程序来编译. 所以它会优先选择环境变量 靠前 的路径来执行.

如上图,在 E:\CTEX\MiKTeX\miktex\binD:\texlive\2022\bin\win32 两个路径下都有 tex.exe 这个可执行文件,但是由于 CTeX 的环境变量靠前,所以会优先选择 CTeX, 如下图

当我将 texlive 的环境变量移到前面后,如下

至此,大概明白了如何选择需要的发行版来编译了

其它

其它的,比如编辑器的使用,我就不做解释了

Vscode 是一款杰出的代码编辑器,不论从代码高亮,自动补全上来讲,都是相当不错的,本文将要介绍在 Vscode 中配置 LaTeX

本文默认用户已经安装好了 texlive

下载 vscode

直接到官网下载最新版的 vscode ,当然官网需要科学上网,不然下载很慢,当然也有其他的法子,自行百度吧

我们需要记住 vscode 的安装路径,后面需要用到

安装插件

  • Chinese , 用于汉化界面
  • LaTeX Workshop , 必备插件

安装好插件我们重启一下 vscode ,点击左下角齿轮,打开设置,并打开配置文件 settings.json ,按下图操作

这样就打开了配置文件,接下来我们来修改配置文件,将下面代码粘贴到里面,如果你是新安装的用户,直接用下面的代码替换你原来的就行了

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
{
"latex-workshop.latex.autoBuild.run": "never",
"latex-workshop.showContextMenu": true,
"latex-workshop.intellisense.package.enabled": true,
"latex-workshop.message.error.show": false,
"latex-workshop.message.warning.show": false,
"latex-workshop.latex.tools": [
{
"name": "xelatex",
"command": "xelatex",
"args": [
"-synctex=1",
"-interaction=nonstopmode",
"-file-line-error",
"%DOCFILE%"
]
},
{
"name": "pdflatex",
"command": "pdflatex",
"args": [
"-synctex=1",
"-interaction=nonstopmode",
"-file-line-error",
"%DOCFILE%"
]
},
{
"name": "latexmk",
"command": "latexmk",
"args": [
"-synctex=1",
"-interaction=nonstopmode",
"-file-line-error",
"-pdf",
"-outdir=%OUTDIR%",
"%DOCFILE%"
]
},
{
"name": "bibtex",
"command": "bibtex",
"args": [
"%DOCFILE%"
]
}
],
"latex-workshop.latex.recipes": [
{
"name": "XeLaTeX",
"tools": [
"xelatex"
]
},
{
"name": "PDFLaTeX",
"tools": [
"pdflatex"
]
},
{
"name": "BibTeX",
"tools": [
"bibtex"
]
},
{
"name": "LaTeXmk",
"tools": [
"latexmk"
]
},
{
"name": "xelatex -> bibtex -> xelatex*2",
"tools": [
"xelatex",
"bibtex",
"xelatex",
"xelatex"
]
},
{
"name": "pdflatex -> bibtex -> pdflatex*2",
"tools": [
"pdflatex",
"bibtex",
"pdflatex",
"pdflatex"
]
},
],
"latex-workshop.latex.clean.fileTypes": [
"*.aux",
"*.bbl",
"*.blg",
"*.idx",
"*.ind",
"*.lof",
"*.lot",
"*.out",
"*.toc",
"*.acn",
"*.acr",
"*.alg",
"*.glg",
"*.glo",
"*.gls",
"*.ist",
"*.fls",
"*.log",
"*.fdb_latexmk"
],
"latex-workshop.latex.autoClean.run": "onFailed",
"latex-workshop.latex.recipe.default": "lastUsed",
"latex-workshop.view.pdf.internal.synctex.keybinding": "double-click",
}

这段代码已经足够您使用 vscode 来编译 LaTeX 了,下面我们讲一下如何使用外部阅读器来查看编译好的 pdf 文件

安装SumatraPdf

这里推荐 SumatraPdf 这个软件,到官网下载安装即可

同样我们需要记住 SumatraPdf 的安装路径,后面需要用到

随后将下面代码放到 settings.json 这份配置文件中,注意放的位置在最后一个花括号前面

1
2
3
4
5
6
"latex-workshop.view.pdf.viewer": "external",
"latex-workshop.view.pdf.external.viewer.command":
"D:/Code/SumatraPDF/SumatraPDF.exe", //注意修改路径
"latex-workshop.view.pdf.external.viewer.args": [
"%PDF%"
],

上面代码中已经标明需要修改路径,这个路径正是我们安装的 SumatraPdf 的路径

这样保存后即可通过外部阅读器来查看pdf了

配置反向搜索

反向搜索有利于我们快速定位到文档对应的源码位置,我们需要将下面代码加入到 settings.json 里面,同样也是最后一个花括号前面

1
2
3
4
5
6
7
8
"latex-workshop.view.pdf.external.synctex.command":
"D:/Code/SumatraPDF/SumatraPDF.exe", //注意修改路径
"latex-workshop.view.pdf.external.synctex.args": [
"-forward-search",
"%TEX%",
"%LINE%",
"%PDF%",
],

然后我们还需要在 SumatraPdf 软件中做一些配置,通过设置打开高级选项,会弹出一个配置文件,我们只需在配置文件最后加上如下代码

1
2
InverseSearchCmdLine = "D:\Code\VS code\Microsoft VS Code\Code.exe" "D:\Code\VS code\Microsoft VS Code\resources\app\out\cli.js"  --ms-enable-electron-run-as-node -r -g "%f:%l"
EnableTeXEnhancements = true

注意上面代码中 D:\Code\VS code 是我的 vscode 的安装路径,请换成你的,然后就可以通过在 pdf 文档中双击跳转到 vscode 源码部分了

以上就是在 vscode 中配置 LaTeX 的全部过程,有时间我会将其写得更加详细一点,如果有问题欢迎评论区留言

有很多老师想要实现一个师生两版的效果。这是一个什么样的效果呢,其实就是教师版的需要显示答案、提示、标注、等等,学生版则不需要。有些老师可能会选择注释法,也即是先把答案等注释掉,生成学生版,然后再来取消注释,生成教师版,但这样未免太麻烦了,其实这个很简单,我们可以使用通过设置一个布尔值来控制答案显示与否

实现细节

  • 先定义两个变量
1
2
\tl_clear_new:N \__answer_tl % 用于保存答案
\bool_new:N \__answer_bool % bool变量,用于控制答案显示与否
  • 声明两个环境
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\NewDocumentEnvironment{exercise}{+b}
{
\noindent\textbf{\textcolor{magenta}{题目:}}\par
#1
}{}
\NewDocumentEnvironment{answer}{+b}
{
\tl_set:Nn \__answer_tl
{
\noindent\textbf{\textcolor{blue}{答案:}}\par
#1
}
}
{
\par
\bool_if:NT\__answer_bool{\tl_use:N \__answer_tl}
}

其中exercise answer 两个环境分别用于输出题目和答案,在上面,可以发现这样一行代码

1
2
3
4
5
\tl_set:Nn \__answer_tl
{
\noindent\textbf{\textcolor{blue}{答案:}}\par
#1
}

这是将答案的内容写进 \__answer_tl 这个变量里面,而

1
\bool_if:NT\__answer_bool{\tl_use:N \__answer_tl}

这行代码中 \bool_if:NT 函数用于判断 \__answer_bool 这个 bool 变量的真假,如果为真则执行

1
\tl_use:N \__answer_tl

上面这行代码的作用是使 \__answer_tl 变量的值显示出来,也就是把我们保存的答案显示出来

可见这里决定答案显示与否的就是这个 bool 变量的真假了,我们通过

1
2
\bool_set_true:N \__answer_bool
\bool_set_false:N \__answer_bool

可以将该 bool 变量的设置为逻辑真逻辑假,我们来看一下完整源码

完整源码1

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
\documentclass{ctexart}
\usepackage{xcolor}
\ExplSyntaxOn
\tl_clear_new:N \__answer_tl % 用于保存答案
\bool_new:N \__answer_bool % bool变量,用于控制答案显示与否
\bool_set_true:N \__answer_bool % 将 \__answer_bool 设置为真,则显示答案,如果注释掉,则不显示
\NewDocumentEnvironment{exercise}{+b}
{
\noindent\textbf{\textcolor{magenta}{题目:}}\par
#1
}{}
\NewDocumentEnvironment{answer}{+b}
{
\tl_set:Nn \__answer_tl
{
\noindent\textbf{\textcolor{blue}{答案:}}\par
#1
}
}
{
\par
\bool_if:NT\__answer_bool{\tl_use:N \__answer_tl}
}
\ExplSyntaxOff
\begin{document}
\begin{exercise}
这是题目.....
\begin{answer}
这是答案...
\end{answer}
\end{exercise}

\begin{exercise}
这是题目.....
\begin{answer}
这是答案...
\end{answer}
\end{exercise}
\end{document}

结果

如果将第六行代码注释则不会显示答案

写进cls(推荐)

这里我需要新建一个文件,命名为 mycls.cls ,内容如下

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
\NeedsTeXFormat{LaTeX2e}
\LoadClass{ctexart}
\ProvidesClass{mycls}
\RequirePackage{xcolor}
\ExplSyntaxOn
\tl_clear_new:N \__answer_tl
\bool_new:N \__answer_bool
\keys_define:nn{answer}
{
show-answer.code:n = {\bool_set_true:N \__answer_bool}
}
\ProcessKeysOptions{answer}
\NewDocumentEnvironment{exercise}{+b}
{
\noindent\textbf{\textcolor{magenta}{题目:}}\par
#1
}{}
\NewDocumentEnvironment{answer}{+b}
{
\tl_set:Nn \__answer_tl
{
\noindent\textbf{\textcolor{blue}{答案:}}\par
#1
}
}
{
\par
\bool_if:NT\__answer_bool{\tl_use:N \__answer_tl}
}
\ExplSyntaxOff
\endinput

只对部分代码进行解读,其它的参看隔壁

1
2
3
4
\keys_define:nn{answer}
{
show-answer.code:n = {\bool_set_true:N \__answer_bool}
}

这里使用 l3keys 包提供的键值设置,定义了一个 show-answer 键,他的内容是将 bool 变量设置为逻辑真

1
\ProcessKeysOptions{answer}

这里使用 l3keys2e 包提供的 \ProcessKeysOptions 命令,它可以将所接受到的模块内的键值设置传递给我们的模板 class ,这两个包均已内嵌在 LaTeX 内核,无需显示加载,这样我们只需在源文件中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\documentclass[show-answer]{mycls}
%如果加了show-answer可选项,则显示答案,反之则不显示
\begin{document}
\begin{exercise}
这是题目.....
\begin{answer}
这是答案...
\end{answer}
\end{exercise}

\begin{exercise}
这是题目.....
\begin{answer}
这是答案...
\end{answer}
\end{exercise}
\end{document}

这里输出和前面一样

至此,大功告成,现在是 4:10 该睡觉了~

如果有问题,欢迎下方评论~

如果本文对您有帮助,您可以请我喝杯奶茶~

想法来源

今天下午在配置我的博客时,看到了这样一种效果

于是想着用 LaTeX 来实现一下

需要实现的功能

  • 定义一个环境,命名为 test ,并接受一些键值
    • colorlist = {color1,color2,color3, … }, 用于设置各个选择框的颜色
    • color = colorname, 用于接受一个颜色,指定下列所有选择框为同一颜色
    • scale = fp ,用于对框大小进行缩放
    • style = ,用于设置框的样式
  • (在test环境中)重新定义 \item 命令 和 \item* 命令,分别用于制定空框和选择框
  • \item 可以接受一个可选参数,即 \item[] \item*[] ,这个可选参数可以设置键值
    • 设置当前框颜色
    • style = , 设置当前框样式

开始动手

预定义变量

1
\int_zero_new:N \__item_int % 用于为 \item 命令设置计数器

使用 l3draw 来绘制两种框

  1. \__item_true_label: 选择样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
\cs_new:Npn \__item_true_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_fill:x{\clist_item:Nn\__color_clist{\__item_int}}
\draw_path_use:n{fill}
\draw_scope_begin:
\draw_cap_round:
\draw_join_round:
\color_select:n{white}
\draw_path_moveto:n{0.1cm,0.21cm}
\draw_path_lineto:n{0.18cm,0.08cm}
\draw_path_lineto:n{0.32cm,0.32cm}
\draw_linewidth:n{1pt}
\draw_path_use:n{stroke}
\draw_scope_end:
\draw_end:
}
  1. \__item_false_label: 未选择样式
1
2
3
4
5
6
7
8
9
10
\cs_new:Npn \__item_false_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_linewidth:n{0.8pt}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_select:x{\clist_item:Nn\__color_clist{\__item_int}}
\draw_path_use:n{stroke}
\draw_end:
}

注意上面代码中有两个函数

1
2
\color_select:x
\color_fill:x

expl3 宏包为我们提供的是 n 变体,这里我们需要对它所接受的参数进行完全展开,于是我们定义一个 x 变体

1
2
\cs_generate_variant:Nn\color_fill:n{x}
\cs_generate_variant:Nn\color_select:n{x}

定义键值

1
2
3
4
\keys_define:nn{test}
{
colorlist.clist_set:N = \__color_clist,
}

定义 test 环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
\NewDocumentEnvironment{test}{m}
{
\int_zero:N \__item_int
\keys_set:nn{test}{#1}
\seq_set_from_clist:NN \__color_seq \__color_clist
\RenewDocumentCommand{\item}{sm}
{
\int_incr:N \__item_int
\IfBooleanTF{##1}
{
\__item_true_label: \space ##2 \par
}
{
\__item_false_label: \space ##2 \par
}
}
}

至此,暂时告一段落~

完整源码

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
64
65
66
67
68
69
70
\documentclass{ctexart}
\usepackage{l3draw}
\begin{document}
\ExplSyntaxOn
\int_zero_new:N \__item_int
\cs_generate_variant:Nn\color_fill:n{x}
\cs_generate_variant:Nn\color_select:n{x}
\cs_new:Npn \__item_true_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_fill:x{\clist_item:Nn\__color_clist{\__item_int}}
\draw_path_use:n{fill}
\draw_scope_begin:
\draw_cap_round:
\draw_join_round:
\color_select:n{white}
\draw_path_moveto:n{0.1cm,0.21cm}
\draw_path_lineto:n{0.18cm,0.08cm}
\draw_path_lineto:n{0.32cm,0.32cm}
\draw_linewidth:n{1pt}
\draw_path_use:n{stroke}
\draw_scope_end:
\draw_end:
}
\cs_new:Npn \__item_false_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_linewidth:n{0.8pt}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_select:x{\clist_item:Nn\__color_clist{\__item_int}}
\draw_path_use:n{stroke}
\draw_end:
}
\keys_define:nn{test}
{
colorlist.clist_set:N = \__color_clist,
}
\NewDocumentEnvironment{test}{m}
{
\int_zero:N \__item_int
\keys_set:nn{test}{#1}
\seq_set_from_clist:NN \__color_seq \__color_clist
\RenewDocumentCommand{\item}{sm}
{
\int_incr:N \__item_int
\IfBooleanTF{##1}
{
\__item_true_label: \space ##2 \par
}
{
\__item_false_label: \space ##2 \par
}
}
}
{\vspace*{0.5cm}}
\ExplSyntaxOff
{\LARGE\bfseries 我喜欢?}\par
\begin{test}{colorlist={green,cyan,red,blue,red,yellow,red}}
\item*{熬夜}
\item{玩游戏}
\item*{打羽毛球}
\item*{LaTeX}
\item*{听歌}
\item{学习}
\item*{熬夜玩游戏}
\end{test}
\end{document}

输出结果

未完结

还有些功能没有实现,后面再更新吧 ~ 现在是凌晨 02:55 先睡了~


继续

我来继续更新啦~

我打算用 l3coffin 来实现了,最近看了一下这个模块,真的是非常厉害!

  • 首先我们需要对绘制的两种框代码做一点点修改
  1. \__item_true_label: 选择样式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
\cs_new:Npn \__item_true_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_fill:x{\__color_tl}
\draw_path_use:n{fill}
\draw_scope_begin:
\draw_cap_round:
\draw_join_round:
\color_select:n{white}
\draw_path_moveto:n{0.1cm,0.21cm}
\draw_path_lineto:n{0.18cm,0.08cm}
\draw_path_lineto:n{0.32cm,0.32cm}
\draw_linewidth:n{1pt}
\draw_path_use:n{stroke}
\draw_scope_end:
\draw_end:
}
  1. \__item_false_label: 未选择样式
1
2
3
4
5
6
7
8
9
10
\cs_new:Npn \__item_false_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_linewidth:n{0.8pt}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_select:x{\__color_tl}
\draw_path_use:n{stroke}
\draw_end:
}
  • 定义两个函数来输出 coffin
  1. \__item_label:
1
2
3
4
5
6
7
8
9
10
11
\cs_new_protected:Npn \__item_label: 
{
\par
\int_compare:nF{\__item_int = \c_zero_int}
{
\__item_content:
}
\int_incr:N \__item_int
\vcoffin_set:Nnx \__label_coffin {1cm}{\__item_tl}
\vcoffin_set:Nnw \__content_coffin {\textwidth - \__parsep_dim - 2cm}
}
  1. \__item_content:
1
2
3
4
5
6
7
\cs_new_protected:Npn \__item_content:
{
\vcoffin_set_end:
\coffin_join:NnnNnnnn \__label_coffin{T}{r}\__content_coffin{T}{l}{-10pt}{0pt}
\coffin_typeset:Nnnnn \__label_coffin{B}{l}{\__parsep_dim}{\__vsep_dim}
\par
}
  • 定义键值
1
2
3
4
5
6
7
8
9
10
\keys_define:nn{test}
{
colorlist.clist_set:N = \__color_clist, % 用于设置颜色列表
vsep.dim_set:N = \__vsep_dim, % 控制每个条目的垂直间距
vsep.initial:n = 1pt, % 设置初始值
parsep.dim_set:N = \__parsep_dim, % 控制每个条目整体水平的偏移量
parsep.initial:n = \__parindent_dim, % 设置初始值
color.str_set:N = \__color_str, % 用于设置单个颜色
color.initial:n = black % 设置初始值
}
  • 提前申明变量和定义函数变体
1
2
3
4
5
6
7
8
9
10
11
\coffin_new:N \__label_coffin
\coffin_new:N \__output_coffin
\dim_zero_new:N \__vsep_dim
\dim_zero_new:N \__parsep_dim
\dim_zero_new:N \__parindent_dim
\dim_set_eq:NN \__parindent_dim \parindent
\tl_new:N \__item_tl
\cs_generate_variant:Nn \vcoffin_set:Nnn{Nnx}
\int_zero_new:N \__item_int
\cs_generate_variant:Nn\color_fill:n{x}
\cs_generate_variant:Nn\color_select:n{x}
  • test 环境
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
\NewDocumentEnvironment{test}{O{}}
{
\group_begin:
\dim_set:Nn \parindent{0pt}
\int_zero:N \__item_int
\keys_set:nn{test}{#1}
\seq_set_from_clist:NN \__color_seq \__color_clist
\clist_if_empty:NTF \__color_clist
{
\tl_set:Nn\__color_tl
{\__color_str}
}
{
\tl_set:Nn\__color_tl {\clist_item:Nn\__color_clist{\__item_int}}
}
\RenewDocumentCommand{\item}{s}
{
\IfBooleanTF{##1}
{
\tl_gset:Nn \__item_tl
{\__item_true_label:}
}
{
\tl_gset:Nn \__item_tl
{
\__item_false_label:
}
}
\__item_label:
}
}
{
\__item_output:
\group_end:
}

这次就到这儿叭,后面在更新~

完整源码

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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
\documentclass{ctexart}
\usepackage{l3draw,expl3,xparse}
\ExplSyntaxOn

\coffin_new:N \__label_coffin
\coffin_new:N \__content_coffin
\dim_zero_new:N \__vsep_dim
\dim_zero_new:N \__parsep_dim
\dim_zero_new:N \__parindent_dim
\dim_set_eq:NN \__parindent_dim \parindent
\tl_new:N \__item_tl
\cs_generate_variant:Nn \vcoffin_set:Nnn{Nnx}
\int_zero_new:N \__item_int
\cs_generate_variant:Nn\color_fill:n{x}
\cs_generate_variant:Nn\color_select:n{x}

\cs_new:Npn \__item_true_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_fill:x{\__color_tl}
\draw_path_use:n{fill}
\draw_scope_begin:
\draw_cap_round:
\draw_join_round:
\color_select:n{white}
\draw_path_moveto:n{0.1cm,0.21cm}
\draw_path_lineto:n{0.18cm,0.08cm}
\draw_path_lineto:n{0.32cm,0.32cm}
\draw_linewidth:n{1pt}
\draw_path_use:n{stroke}
\draw_scope_end:
\draw_end:
}

\cs_new:Npn \__item_false_label:
{
\draw_begin:
\draw_baseline:n{0.5ex}
\draw_linewidth:n{0.8pt}
\draw_path_rectangle:nn{0,0}{0.4cm,0.4cm}
\color_select:x{\__color_tl}
\draw_path_use:n{stroke}
\draw_end:
}

\cs_new_protected:Npn \__item_label:
{
\par
\int_compare:nF{\__item_int = \c_zero_int}
{
\__item_content:
}
\int_incr:N \__item_int
\vcoffin_set:Nnx \__label_coffin {1cm}{\__item_tl}
\vcoffin_set:Nnw \__content_coffin {\textwidth - \__parsep_dim - 2cm}
}

\cs_new_protected:Npn \__item_content:
{
\vcoffin_set_end:
\coffin_join:NnnNnnnn \__label_coffin{T}{r}\__content_coffin{T}{l}{-10pt}{0pt}
\coffin_typeset:Nnnnn \__label_coffin{B}{l}{\__parsep_dim}{\__vsep_dim}
\par
}

\keys_define:nn{test}
{
colorlist.clist_set:N = \__color_clist, % 用于设置颜色列表
vsep.dim_set:N = \__vsep_dim, % 控制每个条目的垂直间距
vsep.initial:n = 1pt, % 设置初始值
parsep.dim_set:N = \__parsep_dim, % 控制每个条目整体水平的偏移量
parsep.initial:n = \__parindent_dim, % 设置初始值
color.str_set:N = \__color_str, % 用于设置单个颜色
color.initial:n = black % 设置初始值
}

\NewDocumentEnvironment{test}{O{}}
{
\group_begin:
\dim_set:Nn \parindent{0pt}
\int_zero:N \__item_int
\keys_set:nn{test}{#1}
\seq_set_from_clist:NN \__color_seq \__color_clist
\clist_if_empty:NTF \__color_clist
{
\tl_set:Nn\__color_tl
{\__color_str}
}
{
\tl_set:Nn\__color_tl {\clist_item:Nn\__color_clist{\__item_int}}
}
\RenewDocumentCommand{\item}{sO{}}
{
\IfBooleanTF{##1}
{
\tl_gset:Nn \__item_tl
{\__item_true_label:}
}
{
\tl_gset:Nn \__item_tl
{
\__item_false_label:
}
}
\__item_label:
}
}
{
\__item_content:
\group_end:
}
\ExplSyntaxOff

\begin{document}
我喜欢?
\begin{test}[colorlist ={ blue,red,yellow,cyan,black},]
\item 抽烟
\item* 喝酒
\item 蹦迪
\item 赌博
\item* 美女
\end{test}

\end{document}

测试

  • 测试一
    1
    2
    3
    4
    5
    6
    7
    8
    我喜欢?
    \begin{test}[colorlist ={ blue,red,yellow,cyan,black},vsep = 10pt]
    \item 抽烟
    \item* 喝酒
    \item 蹦迪
    \item 赌博
    \item* 美女
    \end{test}

  • 测试二
1
2
3
4
5
6
7
8
9
我喜欢?
\begin{test}[color = blue]
\item 吃饭
\item 睡觉
\item* 玩手机
\item* \LaTeX
\item* 编程
\item* 算法
\end{test}


如果懒得复制源码,可以到我的 GitHub 下载

下次再见~