LOADING

加载过慢请开启缓存 浏览器默认开启

天童爱丽丝.top

Sunshine鼠鼠的博客

一个初入计算机领域的二次元人士の小窝

C和Rust混合探索

2025/9/16

前言
先锋的同学鼓励对于C和Rust混合编程的探索,那就开始吧。
面试题内容之前我多多少少略有涉猎,但是Rust我还真没听过。
之前我一直认为C的恼人的手动管理内存和Java基本数据类型无法修改值等问题是存在于是就合理的,似乎是自它们诞生之初就无法改变的特点。但是目前看来Rust给了个解决方案。但是我觉得语言更不美了 :joy:
C和Rust混合探索
Java、Python和C++ 之间的互调用常常依赖于各自的外部函数接口(FFI)或特定的绑定库。
但是ABI主要用于静态编译(源码转机器码,机器码转可执行文件)的语言,例如 C 和 C++。所以C和Rust使用ABI进行互相调用咯。
调用问题(上)
Rust调用C
在Rust中,先要链接c文件(夹)

#[link(name = "your_c")]

需要使用extern “C”来声明”我要调用c咯”

extern "C" {
    fn add_numbers(a: i32, b: i32) -> i32;//问题:这是什么?
}

调用C语言代码时,Rust编译器无法保证内存安全,因为C语言没有内置的内存安全机制。因此,所有的调用都必须放在unsafe{ }里面。

fn main() {
    unsafe {
        let result = add_numbers(10, 20);
        println!("sum: {}", result);
    }
}

数据类型映射
好的,我们不得不先解决数据映射的问题。:worried:
众所周知,rust整数类型按位长度划分(如i8/i16/i32…u8/u16/u32…)且整数常量默认为i32,因此里面的a、b就是属于系统默认的整数类型,跟C里面的int很像对吧。
Rust的标准库提供了许多与C语言类型对应的类型,数据类型互通正如货币互通,这样两边才好说话吧。
直接叫ai帮忙造个表格~~~~懒得手打~~

C 语言类型 Rust 对应类型 备注
char c_char 通常为 i8u8
int c_int 通常为 i32
float c_float 相当于 f32
double c_double 相当于 f64
void c_void 用于指针,如 void * 对应 *mut c_void
char * *mut c_char 指向 C 字符串的可变指针。
const char * *const c_char 指向 C 字符串的不可变指针。
struct #[repr(C)]struct 必须使用 #[repr(C)] 宏保证内存布局兼容。
enum #[repr(C)]enum 必须使用 #[repr(C)] 宏保证大小和布局兼容。

调用问题(下)
好的好的,数据类型映射问题暂时解决了,继续。
C语言调用Rust函数时,首先需要将Rust函数暴露为C语言兼容的ABI。
具体方式是使用#[no_mangle]和extern “C”这两个属性来修饰函数。

#[no_mangle]//告诉编译器不要对函数名进行“名字重整”
pub extern "C" //按照C语言来编订这个函数
fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}

第二步将Rust代码编译成一个C动态链接库或者静态链接库(这样C语言才能调用)如示例:

[lib]
name = "rust_lib"
crate-type = ["cdylib"]

然后运行

cargo build --release

生成的库文件将默认位于target/release/目录下。(先锋示例好像没有,是要我自己来吗?)
最后,在C语言代码中,就可以像调用其他C函数一样调用这个Rust函数。

extern int rust_add(int a, int b);// 记得声明Rust函数

但是,先锋示例也不是这样喵。
方法是提供了个头文件,有点像接口。

// rust_functions.h
#ifndef RUST_FUNCTIONS_H
#define RUST_FUNCTIONS_H

int add_numbers(int a, int b);
int multiply_numbers(int a, int b);
#endif

这样其他文件只需要 #include 这个头文件就行了,可以调用了。好吧,这也是种省事的方法。

阅读全文

galgame尝试小记

2025/8/6

一直以来都有自己制作galgame的想法…
暑假花了一些时间尝试使用Ren’Py引擎制作自己的同人galgame《期末周与欧娜尼》
不得不说Ren’Py的语法真的好简单,几乎没啥技术门槛(可能是我不够深入)
为了配音,我还学习了GPT-SoVITS-v2pro训练ai语音的方法(还做了个五字神人小视频)
虽然配音听起来ai味确实很重,但作为练手是足够了
游戏的demo版本已经上传到github上了,感兴趣的可以尝尝(虽然比较粗糙)…

PS:JAVA期末拾遗还是比较成功的,最后拿了4.0.

阅读全文

Java期末拾遗-下半

2025/7/3

1.编译器和解释器的区别
编译器是“一次性翻译,整体执行”,解释器是“一边翻译,一边执行”。
对比图(改自GPT):

对比项 编译器(Compiler) 解释器(Interpreter)
翻译方式 一次性将整个源代码编译成可执行文件 逐行解释源代码并立即执行
输出结果 可执行文件(如 .exe.class 等) 无独立可执行文件,直接运行
执行效率 高(编译后运行快) 较低(每次运行都要解释)
调试方便程度 不易定位具体错误行(需要整个编译过程) 容易定位(出错立即停止)
第一次运行速度 慢(需要先编译) 快(直接运行)
依赖环境 编译后可独立运行,不再依赖编译器 每次运行都依赖解释器
常见语言 C、C++、Java(先编译成字节码) Python、JavaScript、Ruby

需要注意的是:Java既是编译型又是解释型语言。
这句话看起来矛盾,其实是因为 Java 有两个阶段的执行过程,结合了编译型语言和解释型语言的特点。
第一步:编译(Compiler)
Java 源代码(.java 文件)会先被 编译器(javac) 编译成 字节码文件(.class),这个过程是一次性编译,类似于 C、C++ 的编译阶段。
第二步:解释(Interpreter)或即时编译(JIT)
编译后的 .class 文件不是直接执行的,而是由 Java 虚拟机(JVM) 来运行:
JVM 内部会用 解释器 把字节码一行一行“翻译”成机器指令并执行。
同时,JVM 的 JIT(Just-In-Time)编译器 会把频繁执行的字节码“临时编译”为本地机器码,提高效率。

2.static变量
static 变量(也叫静态变量),是属于类(class)而不是对象(object)的变量。
static能够修饰成员变量和方法,内部类,也可以形成静态static代码块,不能修饰局部变量。
静态代码块:定义在成员位置,使用static修饰的代码块{},随着类的加载而执行,且执行一次,优先于main方法和构造方法的执行。
static 变量在内存中只存在一份,它是随着类的加载而加载的,且只加载一次。例如:
Java 程序的入口方法 main是一个 static(静态)方法。

public static void main(String[] args){}

它优先于对象存在,所以,可以被所有对象共享。
需要注意的是:静态方法不能访问非静态变量,因为它没有 this 对象。
静态方法在没有任何对象的情况下就可以被调用,而非静态变量属于某个对象,必须通过 this 才能访问。
this 代表当前对象的引用,而静态方法属于类本身,不属于任何对象,所以它没有 this。

static void print() {
    System.out.println(name); // ❌ 错误,name 是非静态变量
}

3.多态、重写与重载
重写:
重写是实现多态的必要前提之一。
子类出现重名的成员方法(返回值类型,方法名和参数列表都相同),访问是特殊情况,声明不变,将父类方法重新实现。
需要注意:1.子类方法覆盖父类方法,返回值类型、函数名和参数列表都要一模一样。
2.子类方法覆盖父类方法,必须要保证权限大于等于父类权限(例如父类是 public,子类不能是 protected),
子类抛出的异常不能比父类方法更宽泛。
例如:

class Animal {
    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Bark");
    }
}

重载:
在一个类中方法名相同,但参数不同(类型或个数),返回类型可以相同或不同的多个方法。

特性 内容
是否需要继承 无需继承,可在同一个类中定义
方法名 相同
参数列表 必须不同(类型、数量或顺序不同)
返回类型 可以不同,但不能靠返回类型区分方法

例如:

class Calculator {
    int add(int a, int b) {
        return a + b;
    }

    double add(double a, double b) {
        return a + b;
    }

    int add(int a, int b, int c) {
        return a + b + c;
    }
}

需要注意的是:Java支持构造函数重载(Constructor Overloading),即一个类可以有多个构造函数,只要它们的参数列表不同(参数类型、顺序或数量不同)。
多态:
多态是指同一行为,具有多个不同的表现形式。
即父类定义了一个方法,而子类方法重写该方法时方法主体不一样,
这样在不同子类里面这个方法的实现形式就不一样,能让程序更有扩展性。
需要注意:当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误,不能调用子类拥有,而父类没有的方法。想要调用子类特有的方法,必须做向下转型。
多态例如:

class Animal {
    void makeSound() { }
}

class Dog extends Animal {
    @Override
    void makeSound() {  //对于Cat子类,makeSound方法是Bark
        System.out.println("Bark");
    }
}

class Cat extends Animal {
    @Override
    void makeSound() {  //对于Cat子类,makeSound方法是Hajimi
        System.out.println("Hajimi");
    }
}

4.关系
在画类图的时候,”is-a” 和 “has-a” 是两种最基础的关系。

  1. “is-a” 关系(继承/实现),包括子类 继承 父类,或类 实现 接口。
    继承(类 → 类):空心三角箭头(子类指向父类),如:Dog ──────▷ Animal
    实现(类 → 接口):虚线空心三角箭头 或 棒棒糖表示法,如:
    Dog ⤍ «interface» Runnable或Dog ───○ Runnable
  2. “has-a” 关系(组合/聚合/关联),表示一个类包含另一个类的对象。
    组合(Composition):强依赖,部分不能脱离整体存在(如 Car 和 Engine)。
    聚合(Aggregation):弱依赖,部分可以独立存在(如 University 和 Department)。
    关联(Association):普通的持有关系(如 Student 和 Course)。
    关系类型 符号 示例 说明
    组合 实心菱形 ◆── Car ◆── Engine 生命周期绑定,强依赖
    聚合 空心菱形 ◇── University ◇── Department 生命周期独立,弱依赖
    关联 实线箭头 ───> Student ───> Course 普通持有关系

5.java易忘英语单词汇总
inherit 继承 subclass(child class)子类 equivalent 等价的、同义的
signature 署名 coexist 共存 invoke 调用 instantiate 实例化 abnormalities紊乱
execute 执行 exception handler 异常处理器 stack trace 堆栈跟踪
scope 作用域 wrapper 包装 instance 实例 arguments 参数 集合Collection
accommodate 为…提供空间 aggregation 聚合关系 index 索引(下标)
attribute 属性

6.访问权限问题
对于父类来说:
private:仅当前类可访问(子类和其他类均不可见)。
default(包私有):同一包内的类可访问(子类如果在不同包则不能访问!)。
protected:同一包内的类 + 不同包的子类可访问。
public:所有类均可访问。

7.java文件中的注释问题

注释类型 语法 用途 是否生成 Javadoc
单行注释 // 临时注释或简短说明 ❌ 否
多行注释 /* ... */ 跨行注释,适合较长说明 ❌ 否
Javadoc /** ... */ 生成 API 文档(含 @param 等标签) ✅ 是
阅读全文

Java期末拾遗-上半

2025/6/30

哎哟,又到期末了,java有一些细碎知识还不会,
故在此收集并记录,我在期末拾来的java知识。

1.传值和传址,Java与C
传值和传址
传值和传值的区别是什么?
假如现在你电脑桌面有个word文档,传值就是你把这文档创建了一个副本,
然后你是原封不动的把这个文档提交了,或是把它修改了,都跟你最初的文档一点关系没有。
然后传址就是你把原文件改了。
对于C/C++,我们可以真正传递地址(指针),通过指针修改原始变量。
而对于Java,Jesus Crime!
我们无法修改基本变量的值!
Java 中所有参数传递本质上是值传递,但分两种情况:
对于基本类型(int, double 等),传的是值的副本,对值无法改变

public class Main {
    public static void main(String[] args) {
        int x = 10;
        change(x);
        System.out.println(x);  // 输出仍是 10
    }

    static void change(int val) {
        val = 20;  // 改的是 val 的副本
    }
}

x 的值没有变,因为change()只是把 x 的副本传进去了,这个change()只是自欺欺人罢了。
但是对于对象和数组,虽然值传递的“祖宗章程”没变,但是情况有变。
如果是对象或是数组,传的是“引用的副本”,即拷贝了一份引用数据的地址,通过改变引用数据的地址可以改对象内部。

class Person {
    String name;
}

public class Demo {
    public static void change(Person p) {
        p.name = "Alice";   // 改变对象内容 ✅
        p = new Person();   // 改变引用本身 ❌(不会影响原来的 p)
        p.name = "Sunshine";     // 改变的是新对象
    }

    public static void main(String[] args) {
        Person p1 = new Person();
        p1.name = "Tom";
        change(p1);
        System.out.println(p1.name); // 输出是 Alice
    }
}

引用数据类型存储的是“对象的地址”(引用)。
在这个例子里面,name原来引用Tom的地址,使得p1的name对应为Tom。
然后再change()函数里面,将引用的数据改为“Alice”,将新的ALice的地址给name引用,从而改变了对象的内容。
但是下一步尝试将新对象赋给p,实际只是创建了一个p的副本,让这个副本指向了一个新的对象,
原来p不变,p1自然不变,还是掩耳盗铃。

2.重写euquals方法
Q1:“为什么不直接用等号 == 对比两个对象,而要写 equals() 这么麻烦?”
A:== 比较的是两个对象的内存地址是否相同(引用是否一致),即判断是否是同一个对象。
如果你不重写 equals() 方法,默认行为等同于 ==(Object类默认的实现)。
重写equals方法后,比较的是两个对象的逻辑内容是否相同(可以自定义什么算逻辑内容相同),即判断是否是“内容上相等”。
举个生活中的例子,== 相当于判断:这两张身份证是不是“完全同一张卡片”?(内存地址)
equals() 问题:这两张身份证上是不是“属于同一个人”?(内容逻辑)
Q2:正确重写equals方法示例?

public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Product product = (Product) obj;
        return initialCode.equals(product.initialCode);
    }

(当 if 语句后面只有一条语句时,可以省略大括号 {}。执行语句仅为后面的那一句,但这只是语法允许,不代表是最佳实践。)
Q3:为什么要强制转化成product类?
Object 是所有类的父类,而 equals(Object obj) 方法的参数类型是 Object。
这句代码是把一个通用的 Object 类型,强制转换成你自己定义的具体类型 Product,
否则你就无法访问 Product 类中的字段或方法,比如下一句的比较initialCode,必须是访问 Product类才可以。
所以你必须告诉编译器:”我知道这个对象其实是 Product 类型”,于是要强制转换。
由于在前面已经做了类型检查:

if (obj == null || getClass() != obj.getClass()) return false;

所以之后进行强制类型转换是安全的。

3.父类构造器与super()
Q1:父类可以没有任何构造器吗?
A:父类必须有构造器,但可以不显式写(你自己手敲)
如果你没有写任何构造器,Java 会自动帮你加一个无参构造器(默认构造器)。

class Parent {
    // 没写构造器,Java 自动添加:Parent() {}
}

一旦你写了任意一个构造器(哪怕是带参数的),Java 就不会自动添加默认构造器了。
Q2:子类怎么调用构造器?
对于子类、父类构造器而言,它只能够被调用,而不能被继承。
子类会默认调用父类的构造器,但是如果没有默认的父类构造器(即构造器是有参的),
子类必须要显示的指定父类的构造器,即使用super(),
而且必须是在子类构造器中做的第一件事(第一行代码)。
如:

super(name);  // 调用父类 Person 的构造方法 Person(String name)

父类构造器参数需要什么类型,你在括号里就填什么类型。
与此同时,如果父类有多个构造器(不同构造器参数不同)
可以重载多个子类构造器,分别调用不同的 super(…),
在括号里面填入不同父类构造器对应的数据类型即可。
补充:super(“Alice”); 和 super(name); 都是调用父类构造器的方式。
super(“Alice”); 是直接传字面量,和子类有没有参数无关。
super(name); 是传递子类构造器的参数,将子类构造器中的 name 变量传递给父类构造器。子类构造器传进什么,就传给父类,让父类帮忙构造。

4.抽象类和抽象方法
Q1:什么样的类、方法是“抽象”的?
我们把没有方法主体的方法称为抽象方法。Java语法规定,包含抽象方法的类就是抽象类,例如:

abstract class Animal //包含抽象方法,故为抽象类
 {
    abstract void makeSound(); // 所有动物都必须有叫声(抽象方法)
}

Q2:既然没有方法主体,抽象类和抽象方法有什么用?
A:有的兄弟,有的。使用抽象类和抽象方法的主要目的是建立一个“通用模板”或“规范”,
它强制子类实现特定的方法行为,从而达到代码的可扩展性、可维护性和一致性。
继承抽象类的子类必须重写父类所有的抽象方法。否则,该子类也必须声明为抽象类。最终,必须有子类实现该父类的抽象方法,否则,
从最初的父类到最终的子类都不能创建对象,失去意义。所以抽象方法抽象类给子类指派了重写所有抽象方法的任务。
就像愚公临死前给子孙说了:“一定要移走门口的大山!”,子孙就必须实现这个任务(按中国传统孝道文化来说),
父亲没实现,孙子和重孙也得努力实现,直到所有抽象方法实现,当然,怎么实现是子类的事情,父类只需要指派任务就行了。
此时的方法重写,是子类对父类抽象方法的完成实现,我们将这种方法重写的操作,也叫做实现方法,例如:

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Woof! Woof!");
    }
}

注意事项:
一.抽象类不能创建对象,如果创建,编译无法通过而报错。只能创建其非抽象子类的对象。
理解:假设创建了抽象类的对象,调用抽象的方法,而抽象方法没有具体的方法体,没有意义。
二.抽象类中,可以有构造方法,是供子类(已经重写所有抽象方法)创建对象时,初始化父类成员使用的。
三.抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。
抽象类中可以包含非抽象方法,写一些公共逻辑,这样子类可以复用,例如:

abstract class Animal {
    abstract void makeSound();
    
    public void breathe() {
        System.out.println("Breathing...");
    }
}

每种动物都能呼吸,但发声不一样,我们把通用的breathe()及其方法主体写在抽象类里,作为公共逻辑,特有的 makeSound() 让子类各自实现。

5.接口
Q1:接口与抽象类的区别?
A:如果抽象类比喻为魔园里面的魔法少女的话,虽然关键部分方法被抽象了,但还保留部分人的特征,
接口就是已经飞升到宇宙的圆神,变成圆环之理,成为指导所有引用该接口的类的规则了(哭,可爱的小圆和晓美焰)
接口没有构造函数,只能有常量(public static final),但是相比类可以实现多继承,即一个子类实现多个接口。
接口,是java语言中一种引用类型,是方法的集合,接口的内部主要就是封装了方法,
包含抽象方法、默认方法和静态方法,私有方法(JDK 9)。
接口的定义,与定义类相似,但是使用interface关键字。接口不能创建对象,但是可以被实现(implements,类似于被继承)。

 public interface InterFaceName {
 public abstract void method();
  public default void method() {
  System.out.println("This is a default method");
  // 默认方法需执行语句
}
 public static void method2() {
  System.out.println("This is a static method");
  // 抽象方法需执行语句
  }
 }

与抽象方法的子类类似,一个实现接口的类,需要实现接口中的所有的抽象方法,
然后创建该类对象,就可以调用方法了,否则它必须是一个抽象类。
非抽象子类实现接口:1.必须重写接口所有的抽象方法。
2.继承了接口的默认方法,既可以直接调用,也可重写。

class类名implements接口名{
 // 重写接口中抽象方法【必须】
// 重写接口中默认方法【可选】
}

Q2:抽象方法、默认方法和静态方法的区别?
A:抽象方法必须没有方法体,必须被重写。
默认方法类必须有方法体,可以不重写直接调用,也可以重写(注意default不能省略)。
静态方法也必须有方法体,只能用接口名调用,不可以被重写,不可以通过实现类的类名或者实现类的对象调用,例如:

interface Tool {
    static void info() {
        System.out.println("Interface static method");
    }
}

class MyTool implements Tool {}

public class Main {
    public static void main(String[] args) {
        MyTool t = new MyTool();
        //t.info();         // ❌ 编译错误:不能通过对象调用接口的静态方法

        Tool.info();          // ✅ 正确:接口名调用
    }
}
阅读全文

Javafx与MVC架构

2025/6/13

大作业的基本内容我终于堂堂完成啦!并且我学会将相关代码传送到github库里
(虽然最开始不小心传到网站的库里去了,悲)
本次大作业建设公众污染反馈系统的网格员端,使用了javafx和MVC架构。
Mvc架构分为 View、Controller、Module三个结构,结构简单而分明,适合我这种构建系统初学者,
就像两个村子和中间一条大马路。
View(视图)为前端,展示数据和用户界面。
Model(模型)为后端,用于处理数据和部分简单的业务逻辑。
Controller(控制器)是两者之间的桥梁,负责处理前端的请求,同时我在里面也处理输入数据,所以感觉偏向后端,也是手敲代码最多的地方。
同时记得也要搞个common包装着封装好的类(一段处理数据的代码反复出现就要考虑封装),能有效减少工作量,也让这个系统更健壮、更易维护。
为什么使用javafx?
javafx支持 FXML:类似 HTML 的 UI 描述语言,支持界面与逻辑分离,来构造UI。(也支持CSS)
javafx自带按钮、表格(TableView)、图表(Chart)、媒体播放器等控件,比较轮椅?
javaFX 完全使用 Java 编写,我能直接使用,面向对象编程更自然。
javafx与 MVC 结构天然适配(但是我也不是很懂为什么)喵

阅读全文

更新日志3

2025/6/9

周末两天实在是很忙啊,但是觉得更新网站很有意思。
尝试加入discussion系统在新建类型上遇到了麻烦,还好有顿悟的天兵帮忙,
总之是可以正常使用评论功能了。
周日晚上努力添加了一言功能,还好一言作者有内置javascript文件,减轻了复杂程度,
但是我太笨了先在html文件修改,导致运行后修改没了。
还好现在AI还是很好用的,询问AI在layout.ejs找到了合适添加显示一言的方法,
一路跌跌撞撞,但结果是实现了页脚一言功能,
可以安心睡觉了。

阅读全文

更新日志2

2025/6/6

今天是建站第二天,再此重新研读了此hexo主题作者的教程和相关讨论后,
学会了正确构建tags、categories页面的方法,并付诸实践。
加深了对hexo架构的些许理解。
现在两个页面实现了应有的标签功能,不再是错误放置文章的地盘。
在Rain帮助下导入鼠标点击特效。
挂上了两个朋友的友链(其他朋友似乎没有blog)
网站的结构初步明朗,未来可能尝试实现评论功能。
但是编程实训在即,zzz新大版本也开了,
新功能可能没那么快实现喵。

阅读全文

Java思考1

java(或是面向对象)就是你根据你要描述的东西做一个壳,
这个壳就是类,在这个壳里面你要定义这个东西,定义它的属性和它的方法,
属性就是它的参数,方法就是它会干什么事情。
然后呢,再根据这个壳生成一个个具体的实例,这些实例呢,就是相当于是对象嘛。(在main函数中干)
以上相当于是行动前的准备工作?
你在在main函数里面,做你真正想做的事情,生成这些对象后,然后调用他们的方法来做你想做的事情。

阅读全文

鼠鼠二次元泡面理论(逆天预警)

在二次元世界中,
女孩就好像泡面。
女孩越成熟就像泡面在开水煮了越久一样;
萝莉就像面饼没泡过开水,
只能撒一些调料当干脆面吃;
少女就像泡面刚泡了两分钟,几乎是熟了但是还有些生;
熟女就是泡面完全泡熟了有的甚至煮的有些烂了。
而鼠鼠觉得泡面刚泡两分钟又软同时也有嚼劲,
所以少女赛高好吧!

阅读全文

更新日志1

2025/6/6

2025/6/6凌晨完成
更新本网站的背景、图标、作者头像、描述以及steam社区链接。
创建了about、categories、tags等分类。
并先塞入了部分内容,但是tags仍然需要整理。

阅读全文
avatar
Sunshine

东北大学 软件学院
喜欢二次元、打游戏,努力学习中