
最近一段时间没有坚持写学习记录,总是给自己上班最晚,工作更忙这些原因来自我慰藉,回想来属实有些羞愧,总是暗示自己没有时间,但是真正空闲的时间仍在我一边安慰自己一边做着一些没啥太大意义的事情的过程中流逝了。所以应想使自己无法有进步,不逼自己一把是不行的。
本节学习分为四部分:
一、结构体定义
学习过C语言的朋友都不会陌生,结构体在C中的使用不亚于指针的使用,是非常好用的一种组织不同类型数据的构架,同时既可以集合一些结构体的方式,对于非面向对象的C语言来说,结构体就类似 类 的作用。
话说回来,Rust中的结构体的含义基本与C中大体相同。下面看一下Rust中怎样定义一个普通的结构体:
struct Rect {
width: i32,
height: i32,
}
//也可以定义空的结构体,仅仅用于需要实现结构体方法且不需要成员变量时使用
struct EmptyStruct {}
结构体使用 struct 关键字定义,后面跟随结构体名称,如果使用IDE,会建议使用大驼峰来命名结构体名称,结构体内成员在定义时必须标明类别。
结构体既可以定义在原则之外(直接定义在更外层),默认情况下,可以允许当前文件内的所有步骤使用:
#[derive(Debug)]
struct Rect {
width: i32,
height: i32,
} //注意:这里不可以使用分号
fn main() {
//使用结构体
}
fn other_fn() {
//使用结构体
}
结构体定义时,如果是定义在原则体外,结尾是不可以加分号的,而定义在原则体内既可以加也可以不加分号。
fn main() {
struct Rect {
width: i32,
height: i32,
};
struct Rect1 {
width: i32,
height: i32,
}
}
以上这两种均可以,所以直接就都不需要加分号了。(方法体内定义结构体不加分号,不代表是会返回一个结构体,下面会说到)
二、结构体使用
使用结构体也非常简洁,下面定义一个结构体变量:
let square = Rect{ width: 10, height: 10};
let square1 = Rect {
width: 100,
height: 100
};

如果必须设置结构体成员则必须使用mut模式:
let mut square = Rect {
width: 30,
height: 30
};
square.width = 40;
square.height = 40;
从数组返回结构体:有时我们会写一个公共的变量来专门用来初始化结构体,来简化代码,对于维护跟阅读最友好:
fn create_rect() -> Rect {
Rect {
width: 100,
height: 100
}
//#这里不写分号才会返回一个结构体变量,也可以使用return
}
这里要求 create_rect 方法与结构体定义进入同一作用域中,否则就必须公有化结构体并借助 use 引入了,当前我们默认此方式可以直接访问结构体。
简化写法,定义结构体变量时Rust还提供了一些方便的写法,使用与成员名称相近的变量可以直接复制,省去成员名称的重复书写:
let (width, height) = (20, 20);
let square = Rect{
width,
height
};
上面的举例中存在一个解包的写法,这样可以一次性定义一串相关函数,让代码看起来比较简介,下面两种写法是同样效果的:
let (mut width, mut height) = (20, 20); let mut width = 20; let mut height = 20;
结构体也相同有一个类似解包的操作。
拆解赋值使用场景:当存在一个或多个相似类别的结构体变量,基于已存在的结构体变量内数据构建新的结构体数据时。对于一些初始值总是相同的成员数据则可以使用这个结构体的句型来简化赋值操作:
let square = Rect{
width:30,
height:30
};
let rect_new = Rect {
..square
};
可以想上面那样全部使用已有的结构体的值,可以使用部分的值:
let rect_new = Rect {
width: 40,
..square
};
let rect_new1 = Rect {
height: 3,
..square
};
//下面这种写法无意义,**(编译器不会报警告)**
//这里..square没有起到作用,
//这里只是证明是允许这样写的
let rect_new2 = Rect {
width: 3,
height: 3,
..square
};
需要注意的是这个 ..<struct expr> 的词义必须写在结构体尾部,且仅可在这里出现一次,下面是错误的:

let rect_new = Rect {
..square,
width: 30,
};
let rect_new1 = Rect {
width:30,
..square,
..square
};
这里 .. 运算符后方的通配符既可以是一个结构体变量,也可以是一个返回结构体变量的方式:
let rect_new = Rect {
height: 30,
..create_rect()
};
三、元组结构体
元组的定义跟使用在上面就早已介绍过了,而元组结构体与元组的差别就在于它提供了一个名称,对元组有了一个名称的定义,语义化更友好。
看下元组结构体如何定义:
struct RGB (u8, u8, u8); struct EmptyStruct(); //同样支持空的元组结构体
这里通过元组结构体非常清晰的定义了一个RGB色彩的结构体,相比与直接使用元组来定义应极其容易阅读,语义明确。
创建元组结构体变量的写法没有什么特殊之处:
let red = RGB(255, 0, 0);
let yellow = RGB(255, 255, 0);
//可以使用一般结构体的形式创建变量。需要标明下标。
let green = RGB{0:0, 1:255, 2:0};
同样,元组结构体也是支持结构体拆解赋值的句型,这里跟通常结构体的写法略有不同,虽然在建立变量使用的是小括号,但是在使用拆解时,必须使用大括号,在对个别值主动赋值时必须使用元组的数组进行异或:
let some_color = RGB{..red};
let some_color1 = RGB {1: 23, ..red};
let some_color2 = RGB {0: 100, 2: 255, ..yellow};
元组结构体的读写与元组无异:
let mut black = RGB(1,1,1);
black.0 = 0;
black.1 = 0;
black.2 = 0;
println!("{}", black.0);
关注:在后面的这些实例里面在结构体上面紧挨着有一个注解 #[derive(Debug)] ,这个注释作用是为结构体实现Debug这个trait,效果就是可以借助debug模式( {:?} )打印结构体的内容,可以在自己实验的之后除去注解以及去除格式化模式看一下会有哪些错误提示。

四、结构体内存
在C语言中,结构体的存储分配是连续的,从结构体的起始地址也就是第一个结构体成员的起始位置,依次向上排列,并且此处也有一个内存对齐的问题:
struct test_st{
char address;
double name[5];
char a;
};
C中的对齐规则:
所以后面的举例:test_st结构体占用56字节。计算如下:
对于char address位于起始,偏移量为0,变量自身长度1字节。
double name[5],double在C中占用8字节,address仅占用1字节,所以从偏移位置8开始name[5]的存储,于是address和name[5]之间多了这些空余的存储空间,name[5]自身占用8 * 5 = 40字节,当前总长40+8 = 48。
char a,char类型占用1字符,起始位置满足整数倍,自身占用1字节,当前总长48+1 = 49 。
所有成员中double类型最长,大于49的8的最小整数倍是56,所以补齐内存到56字节,在这56个字节内的偏移地址都是test_st的势力范围。
说回到Rust里面的结构体,看下边的举例:
use std::mem;
#[derive(Debug)]
struct Rect {
name: char,
width: u8,
height: [i64; 4],
}
fn main() {
let rect = Rect{width: 1, height:[1,2,3,4], name:'a'};
println!("mem rect:{}", mem::size_of_val(&rect));
println!("mem name:{}", mem::size_of_val(&(rect.name)));
println!("mem width:{}", mem::size_of_val(&(rect.width)));
println!("mem height:{}", mem::size_of_val(&(rect.height)));
println!("rect start {:p}", &rect);
println!("name:{:p}", &(rect.name));
println!("width:{:p}", &(rect.width));
println!("height:{:p}", &(rect.height));
}
--输出--
mem rect:40
mem name:4
mem width:1
mem height:32
rect start 0x7ffee84f9218
name:0x7ffee84f9238
width:0x7ffee84f923c
height:0x7ffee84f9218
Rust中char占用4字节,u8占用1字节,i64占用8字节
通过输出可以看到,Rust内也有会有内存对齐的规则,而且与C中的对齐规则同样。
唯一有差别的地方是Rust中还做了一个处理,就是会手动把结构体成员根据种类占用的字节进行顺序,长度最大的类别放到结构体起始的位置,占用最小的放在结尾,如果占用字节相同则根据成员在结构体中定义的次序排列。
占用字节最多的类别放在最前面的好处就是,结构体成员是可以紧密排列的,后面的类型均可以落在自身大小的整数倍的位置上,类型安排合理是可以完全防止空余数据的对齐。

扩展:这里没有使用String或者Vec类型成为实例,是因为它们实际较为复杂,String本质是一个结构体,内部是一个Vec<u8>类型,而Vec<T>类型在64位的机器上占用24字节结构体结构体,内部结构:
pub struct Vec<T> {
buf: RawVec<T>,
len: usize, //64位机器占用8字节。
}
RawVec<T>的结构如下:
#[allow(missing_debug_implementations)]
pub struct RawVec<T, A: Alloc = Global> {
ptr: Unique<T>,
cap: usize,
a: A,
}
这里Unique<T>内部是
pub struct Unique<T: ?Sized> {
pointer: *const T,
// NOTE: this marker has no consequences for variance, but is necessary
// for dropck to understand that we logically own a `T`.
//
// For details, see:
// https://github.com/rust-lang/rfcs/blob/master/text/0769-sound-generic-drop.md#phantom-data
_marker: PhantomData<T>,
}
Unique<T>内部pointer是一个裸指针,占用8字节。
可以使用 std::mem::size_of::<* const u8>() 输出查看。
PhantomData<T>是一个空结构体,占用0字节。所以Unique<T>占用8字节。
所以RawVec<T>中的ptr占用8字节,cap在64位机器中占用8字节,成员a是一个实现了Alloc trait的类库,默认是Global,Global是一个空结构体,占用0字节。
所以RawVec<T>占用8+8=16字节。
回到Vec<T>中则得出16 + 8 = 24字节。
因为String内部使用的Vec<u8>,所以String在64位机器中占用24字节。
pub struct String {
vec: Vec<u8>,
}
最后,结构体的成员针对一些引用类别包括 &str 和指针类型包括 Box<T> 在这里没有提到,以及结构体方法的推动都没有提到,后面在学习至生命周期、智能指针跟trait、泛型的再回过头来一起学习一下。
留一个小问题: &str 类型通过mem::size_of::<&str>()进行打印存储,始终为16字节,感兴趣的朋友可以研究一下~~
完
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/jisuanjixue/article-140553-1.html
日俄战争日本伤亡比俄军多几万人
两个危机可同时化解
酒水暴力又打