面试题答案
一键面试1. 区别与联系
内存分配
- 静态变量:在程序启动时分配内存,其生命周期贯穿整个程序运行期间。例如:
static MY_STATIC: i32 = 42;
- 常量:在编译时求值并内联到使用它的地方,不单独占据内存空间。例如:
const MY_CONST: i32 = 42;
- 线程本地存储(
thread_local!
):每个线程都有自己独立的实例,在每个线程首次访问时分配内存。例如:
thread_local! {
static MY_TLS: i32 = 0;
}
作用域
- 静态变量:作用域通常是整个crate(包),除非使用
pub(crate)
等修饰符限制其可见性范围。 - 常量:作用域同样取决于其定义位置,在整个crate内可见,如果
pub
修饰,在外部crate也可见。 - 线程本地存储:作用域是单个线程,在不同线程中访问同一个
thread_local!
声明的变量,实际访问的是不同的实例。
可见性
- 静态变量:默认情况下,静态变量在其定义所在的模块及其子模块中可见。可以使用
pub
关键字使其在整个crate内可见。 - 常量:与静态变量类似,默认在定义模块及其子模块可见,
pub
可使其在整个crate甚至外部crate可见。 - 线程本地存储:仅在声明它的线程及其子线程中可见。
线程安全性
- 静态变量:如果是可变的(
static mut
),访问时需要使用unsafe
代码,因为它不是线程安全的,多个线程同时读写会导致数据竞争。不可变的静态变量是线程安全的,因为它们是只读的。 - 常量:由于在编译时求值且只读,所以天生线程安全。
- 线程本地存储:每个线程都有自己的实例,因此不存在线程间的数据竞争问题,是线程安全的。
2. 应用场景选择
静态变量
当需要在整个程序生命周期中共享一个全局数据,并且该数据不需要被每个线程独立维护时,使用静态变量。例如,程序中可能有一个全局配置对象:
static CONFIG: Config = Config::new();
struct Config {
// 配置项
}
impl Config {
fn new() -> Self {
Config {
// 初始化配置项
}
}
}
常量
当数据在编译时就确定,并且在程序的多个地方需要使用相同的值时,使用常量。例如,数学计算中的常量:
const PI: f64 = 3.141592653589793;
fn calculate_circle_area(radius: f64) -> f64 {
PI * radius * radius
}
线程本地存储
当每个线程需要有自己独立的数据副本时,使用线程本地存储。例如,每个线程需要维护自己的日志记录器:
thread_local! {
static LOGGER: Logger = Logger::new();
}
struct Logger {
// 日志记录相关属性
}
impl Logger {
fn new() -> Self {
Logger {
// 初始化日志记录器
}
}
fn log(&self, message: &str) {
// 记录日志的逻辑
}
}
fn thread_function() {
LOGGER.with(|logger| {
logger.log("This is a log message from a thread.");
});
}
通过上述分析和示例,可以根据具体需求选择合适的变量类型。