前言

所思即所见,所见即所得,所得皆所想,技术从未停歇,也未曾缺乏。

目录

编程语言

Rust 翻译于官网描述

一门赋予每个人构建可靠且高效软件能力的语言。

语言优势

  • 编译的代码具有与 C/C++ 相同的性能,并且具有出色的内存和能效。
  • 可以避免 C/C++ 中70%的安全问题和大多数内存问题。
  • 强大的类型系统可以防止数据竞争,带来“无畏的并发性”(等等)。
  • 无缝与 C 进行互操作,以及数十种受支持的平台(基于 LLVM)。
  • 连续四年被评为“最受欢迎的语言”。🤷‍♀️
  • 现代工具:cargo(负责构建工作)、clippy(550+代码质量的lints)、Rustup(轻松的工具链管理)。

语言弱点

  • 陡峭的学习曲线,编译器强制执行(特别是内存)规则,这将是其他类似语言的“最佳实践”。
  • 某些域、目标平台(尤其是嵌入式平台)和IDE功能中缺少Rust原生库。
  • 编译时间比其他语言中“类似”的代码要长。
  • 没有正式的语言规范,可能会阻止在某些领域(航空、医疗等)的合法使用。
  • 一不留意的库(在自定义库中使用 unsafe)可能会很隐藏的破坏语言中的安全规范。

目录

Rust语法结构

Hello, Rust!

fn main() {
    println!("Hello, AwesomeProgram!");
}

数据结构

  • 通过关键字定义的数据类型和内存位置
例子解释
struct S {}使用命名字段定义一个 结构体
struct S { x: T }x 使用类型 T 的命名字段定义结构体
struct S (T);定义带有编号字段类型的"元组"结构体
struct S;定义 零大小 的单元结构体,不占空间
enum E {}定义一个 枚举
enum E { A, B(), C {} }定义枚举的变体,可以是 unit-A, tuple-B() 和struct-like C{}
enum E { A = 1 }如果变体只是与单元类似,则允许赋值,例如 FFI
union U {}用于兼容 FFI 的,类似 C union 的 unsafe 代码
static X: T = T();具有生命周期,单独内存位置的 全局变量
const X: T = T();定义 常量,使用时会复制到临时文件中
let x: T;在绑定为 x 的堆栈上分配 T 类型的字节,可赋值一次,不可变
let mut x: T;同上,但允许 可变 和可变借用
x = y;移动 yx,如果 T 没有实现 Copy,则 y 的所有权会失效,否则会保留所有权并把 y 复制一份
  • 创建和访问数据结构,以及一些符号类型
例子解释
S { x: y }创建 struct S {} 或者 useenum E::S {} ,将字段 x 设置为 y
S { x }同上,但使用本地变量 x 设置字段 x
S { ..s }填写剩余的字段 s,特别是与 Default 一起使用
S { 0: x }像下一行 S(x) 一样,但设置字段 .0 使用结构体语法
S​ (x)创建 struct S (T) 或者 useenum E::S () ,将字段 .0 设置为 x
S如果 S 是单元结构体 S,或者 useenum E::S, 去创建 S 的值
E::C { x: y }创建枚举变体 C ,上面其它的方法也可以
()空元组,包括字面值和类型,又名单元 unit
(x)带括号的表达式
(x,)单元素 元组 表达式
(S,)单元素元组类型
[S]未指定长度的 数组类型,即 slice,不能存储于栈中
[S; n]数组类型,固定长度是 n ,存储元素的类型是 S
[x; n]具有 xn 个副本的数组实例
[x, y]具有给定元素 xy 的数组实例
x[0]集合索引, 使用 usize 类型, 可以用 Index, IndexMut 来实现
x[..]同上,全范围,还有 x[a..b], x[a..=b], ... 以及如下等等
a..b左闭右开范围,如 1..3 意味着 1, 2
..b从起点开始的左闭右开范围
..=b从起点开始的,包含最右边元素的范围
a..=b包容最右边元素的范围,1..=3 意味着 1, 2, 3
a..a 开始到结尾的范围
..全范围,通常意味着整个集合
s.x命名字段的访问,如果 x 不是类型 S 的一部分,则可能会尝试去调用 Deref(取消引用)
s.0编号字段访问,用于元组类型 S (T)

引用和指针

  • 授权未拥有内存的访问权限,另外可参阅泛型和约束部分
例子解释
&S共享的引用(空间中可存放任何的 &s
&[S]包含地址和长度的指定切片引用
&str包含地址和长度的指定字符串切片引用
&mut S可变的独占引用(也包括 &mut [S]&mut dyn S, …)
&dyn T包含地址和 vtable 的指定 trait object 引用
&s共享的借用(如 地址,长度,vtable,...以及像 0x1234
&mut s可变的独占借用
*const S不可变 原生指针类型,无内存安全保证
&mut s可变 原生指针类型,无内存安全保证
&raw const s创建不经过引用的原生指针,ptr:addr_of!()
&raw mut s同上,是可变的,并且需要对未对齐的字段进行补齐
ref s通过引用绑定,成为绑定引用类型
let ref r = s;等价于 let r = &s
let S { ref mut x } = s;可变引用绑定,等价于 let x = &mut s.x;简洁的解构写法见模式匹配
*r解除一个引用 r 去访问它所指向的内容
*r = s;如果 r 是可变引用,则移动或复制 s 到目标内存
s = *r;如果 *rCopy 的话,使 s 等于任何 r 引用的副本
s = *r;如果 *r 不是 Copy 的话,将不起作用,且会移动并留下空赋值
s = *my_box;Box 的特殊例子,如果 *my_box 不是 Copy 的话,将会移出 Box 里的内容
'a生命周期的参数符号,表示在静态分析中的存活时间
&'a S只接受存有 S 类型的地址,存在 'a 或更长
&'a mut S同上,但是 S 类型地址的内容可变
struct S<'a> {}表示 S 包含生命周期 'a 的地址, S 的创建者决定生命周期 'a 的长短
trait T<'a> {}表示 impl T for SS 可能会包含地址
fn f<'a>(t: &'a T)同上,对于一个函数,调用者决定生命周期 'a 的长短
'static持续整个程序执行期间的固定生命周期

函数与方法

  • 定义代码单元及其抽象表达
例子解释
trait T {}定义一个可以实现的通用行为,特征
trait T : R {}Tsupertrait Rsubtrait,任何 S 必须在它 impl T 之前 impl R
impl S {}类型 S 的方法实现
impl T for S {}为类型 S 实现特征 T 中的方法
impl !T for S {}禁用自动派生 auto trait
fn f() {}定义一个 函数,或者一个 关联函数(在 impl 内)
fn f() -> S {}同上,返回类型 S 的值
fn f(&self) {}定义一个 方法,如在 impl S {}
struct S ​(T);可以定义一个 构造函数 fn S(x: T) -> S
const fn f() {}在编译时可用的常量 fn ,如 const X: u32 = f(Y)
async fn f() {}异步函数转换,使 f 返回一个 impl Future
async fn f() -> S {}同上,但使 f 返回一个 impl Future<Output=S>
async { x }在一个函数内使用,使 { x }impl Future<Output=X>
fn() -> S函数指针,内存持有可调用的地址
Fn() -> SCallable Trait (也可以是 FnMutFnOnce),由闭包实现
|| {} 可借用其 捕获闭包 (如,局部变量)
|x| {}闭包接受一个名为 x 的参数,主体是块表达式
|x| x + x同上,没有块表达式,只有一单个表达式
move |x| x + y 闭包获取其捕获的所有权,即 y 转移到闭包中
return || true 闭包看起来像是 logical ORs (返回一个闭包)
unsafe如果你喜欢放假前调试代码(代码的安全由你自己保证),unsafe code
unsafe fn f() {}会调用导致不安全的代码块,你必须检查必要条件
unsafe trait T {}不仔细的 T 实现可能会造成不安全的代码块,实现者必须仔细检查
unsafe { f(); }向编译器保证已经检查了必要条件
unsafe impl T for S {}保证 S 是表现良好的,可以安全在 S 上使用 T

控制流程

  • 在函数内执行控制
例子解释
while x {}Loop,当表达式 x 是真时一直运行
loop {}Loop indefinitely 直到 break,可以主动创建 break x
for x in iter {}循环遍历 迭代器 的语法糖
if x {} else {}如果表达式为真,则执行 条件分支
'label: loop {}循环标签,用于嵌套循环中的流程控制
breakBreak expression 可退出循环
break x同上,但使 x 为循环表达式的值(仅在实际的 loop
break 'label不仅要退出这个循环,还要退出标有 'label 的封闭循环
break 'label x同上,但使 x 为标有 'label 的封闭循环的值
continueContinue expression 跳转到此循环后的下一个循环迭代
continue 'label同上,但使标有 'label 的封闭循环替代本次循环
x?如果 xErrNonereturn and propagate
x.await只作用于 async 里,直到 Future 生成流或准备好 x
return x从函数中提前返回,更惯用的做法是在结尾使用表达式
f()调用 f,(如一个函数,闭包,函数指针, Fn )
x.f()调用成员函数,需要 fself&self,...作为第一个参数
X::f(x)x.f(),除非实现了 impl Copy for X {},否则 f 仅可被调用一次
X::f(&x)x.f()
X::f(&mut x)x.f()
S::f(&x)如果 x derefsSx.f() 一样, 即 x.f() 发现了 S 的方法
T::f(&x)如果 x impl Tx.f() 一样, 即 x.f() 在范围内发现了 T 的方法
X::f()调用关联函数,如 X::new()
<X as T>::f()X 调用特征方法 T::f() 的实现

代码组织

  • 拆分项目到更小的作用域内和最少的依赖
例子解释
mod m {}定义一个模块,从 {} 内部获取定义
mod m;定义一个模块,从 m.rsm/mod.rs 中获取定义
a::ba (mod, enum, …) 的内部,名命空间的路径到元素 b
::bcrate rootexternal preludeglobal path 搜索 b
crate::bcrate root 中搜索 b
self::b在当前的模块中搜索 b
super::b在上一级模块中搜索 b
use a::b;在范围内直接使用 b 而不需要 a 其余部分
use a::{b, c};同上,但是会把 bc 放到范围内
use a::b as x;b 放到范围,使用别名 x,也像这样 use std::error::Error as E
use a::b as _;b 作为匿名放到范围中,通常适用于命名冲突的特征
use a::*;a 内的所有接口放到范围中,仅推荐如果 a 是一些 prelude
pub use a::b;a::b 放到范围中,并且在这里重新导出
pub T如果上一级路径是可见的则公开 visibility
pub(crate) T最多在当前 crate 中可见
pub(super) T最多在上一级中可见
pub(self) T最多在当前模块中可见(默认和没有 pub 一样).
pub(in a::b) T最多在 a::b 中可见
extern crate a;声明对外部的 crate 依赖,仅在 use a::b
extern "C" {}FFI 声明外部依赖项和 ABI(如 "C")
extern "C" fn f() {}定义要使用 ABI("C")导出到 FFI 的函数
  • 子模块中的项目始终可以访问任何项目,无论是否为 pub

类型别名和强制转换

  • 类型的简写名称,以及将一种类型转换为另一种类型的方法
例子解释
type T = S;创建一个类型别名,即 S 的另一个名称
Selfimplementing type,如 fn new() -> Self
selffn f(self) {} 里的方法,与 fn f(self: Self) {} 一样
&self同上,但将 self 改为借用,与 f(self: &Self) 一样
&mut self同上,但将 self 改为可变借用,与 f(self: &mut Self) 一样
self: Box<Self>任意self类型 Arbitrary self type,给智能指针添加方法(my_box.f_of_self()
S as T消除类型 S 改为特征 T, 如 <S as T>::f()
S as Ruse符号中使用,导入 S 作为 R,如 use a::S as R
x as u32简单映射,可能会遭到意外的截断

宏和属性

  • 代码在实际编译之前进行展开生成
例子解释
m!()宏调用, 也可以 m!{}m![] (依赖宏的实现)
#[attr]外部属性,用于注释下面的条目
#![attr]内部属性,用于注释上一级和周围的条目

内部宏解释
$x:ty宏捕获,:... 片段为 $x 声明了允许的内容
$x宏替换,例如使用上面捕获的 $x:ty
$(x),*宏重复,例如在宏中重复零次或多次
$(x),?同上,但重复零次或一次
$(x),+同上,但重复一次或多次
$(x)<<+实际上除了分隔符 , 也可以接受这样的 <<

模式匹配

  • 在 match 或 let 表达式 以及函数参数中的模式构造
例子解释
match m {}初始化模式匹配,然后使用匹配分支
let S(x) = get();明显地,let 还可以解构成类似下面的形式
let S { x } = s;仅把 x 绑定到 s.x 的值
let (_, b, _) = abc;仅把 b 绑定到 abc.1 的值
let (a, ..) = abc;忽略 其余部分 也有作用
let (.., a, b) = (1, 2);指定绑定优先于 其余部分,这里 a1b2
let s @ S { x } = get();x 绑定到 s.x 时,s 绑定到 Spattern binding
let w @ t @ f = get();给每个 w t f 存储 get() 的三个副本结果
let Some(x) = get();如果是可辩驳模式不会起到作用,应使 if let 来代替
if let Some(x) = get() {}如果模式可被分配成分支(如,枚举变体),语法糖
while let Some(x) = get() {} 同上,{} 只要模式可被分配成分支,将继续调用 get()
fn f(S { x }: S)函数参数也像 let 一样使用,这里把 x 绑定到 f(s)s.x

*脱糖后如 match get() { Some(x) => {}, _ => () }

  • 匹配表达式中的模式匹配分支,左侧这些分支也可以在 let 表达式中找到
在匹配分支内解释
E::A => {}匹配枚举变体 A模式匹配
E::B ( .. ) => {}匹配枚举元组变体 B,任何索引的通配符
E::C { .. } => {}匹配枚举结构体变体,任何字段的通配符
S { x: 0, y: 1 } => {}匹配结构体与指定值(仅接受 ss.x 0s.y 1
S { x: a, y: b } => {}匹配结构体与 any(!),并且绑定 s.xas.yb
S { x, y } => {}同上,但分别地把 s.xs.y 的简写 xy 作为绑定
S { .. } => {}将 struct 与任何值匹配
D => {}如果 Duse 中,匹配枚举变体到 E::D
D => {}如果 D 不在 use 中,匹配任何东西绑定到 D,也可能是 E::D 的一种错误形式
_ => {}匹配任何内容(所有其余部分)的完全通配符。
0 | 1 => {}模式选择,or-patterns
E::A | E::Z => {}同上,但是在枚举变体上
E::C {x} | E::D {x} => {}同上,但如果所有变体都有它,则绑定 x
Some(A | B) => {}同上,可以匹配深度嵌套的 or-patterns
(a, 0) => {}a0 匹配任何值的元组
[a, 0] => {}切片模式,为 a0 匹配任何值的数组
[1, ..] => {}子切片模式,匹配数组从 1 开始,任何其余的值
[1, .., 5] => {}匹配数组从 1 开始到 5 结束中的值
[1, x @ .., 5] => {}同上,但还会绑定 x 到代表中间的切片
[a, x @ .., b] => {}同上,但分别匹配任意的第一个和最后一个,绑定为 ab
1 .. 3 => {}范围模式,这里会匹配 12,部分不稳定
1 ..= 3 => {}包含范围模式,匹配 123
1 .. => {}开放的范围模式,匹配 1 和任何更大的数
x @ 1..=5 => {}绑定匹配到 x模式绑定,此处的 x 将是 12,... 或 5
Err(x @ Error {..}) => {}也可以用于嵌套,这里 x 绑定到 Error,特别是下面有用的 if 形式
S { x } if x > 10 => {}模式 匹配保护,条件也必须为真才能匹配

泛型和约束

  • 泛型结合类型构造函数,特征和函数为你的代码提供更大的灵活性
例子解释
S<T>带有类型参数的 泛型 类型(T 表示占位符名称)
S<T: R>打印简写的 trait bound 范式(R 必须是实际的特征)
T: R, P: SIndependent trait bounds(这里一个给 T,一个给 P
T: R, S编译报错,你可能希望像使用下面的用法 R + S
T: R + SCompound trait boundT 必须满足 RS
T: R + 'a同上,但是 T 必须满足 R,如果 T 有生命周期,必须比 'a 更久
T: ?Sized选择离开预定义的 trait bound,这里是 Sized
T: 'a类型的 lifetime bound,如果 T 存在引用,则必须比 'a 更久
T: 'static同上,特别是不意味着 T 将会 'static,仅可以会 'static
'b: 'a'b 的生命周期必须存活至少为 'a
S<const N: usize>Generic const bound,类型 S 的使用者可以提供常量值 N
S<10>在使用时,常量边界可以作为原始值提供
S<{5+5}>表达式必须放在大括号中
S<T> where T: R几乎和 S<T: R> 一样,但语法越长越易于阅读
S<T> where u8: R<T>允许创建生成其它类型的条件语句
S<T = R>Default parameters,更容易使用,但也很灵活
S<const N: u8 = 0>默认参数是常量,如 f(x: S) {} 中参数 N0
S<T = u8>默认参数是类型,如 f(x: S) {} 中参数 Tu8
S<'_>anonymous lifetime,如果可以明显的看出来,会要求编译器推断出来
S<_>anonymous type,如 let x: Vec<_> = iter.collect() 一样
S::<T>Turbofish 会调用类型消除歧义,如 f::<u32>() 一样
trait T<X> {}基于 X 之上的一个特征泛型,可以有多个 impl T for S(一对 X
trait T { type X; }定义 关联类型 X,仅 impl T for S 的一种可能
type X = R;impl T for S { type X = R; } 中设置关联类型
impl<T> S<T> {}为在 S<T> 中任意的 T 实现功能,T 是类型参数
impl S<T> {}完全为 S<T> 实现功能,T 是指定类型(如 S<u32>
fn f() -> impl TExistential types,返回一个 impl T 的未知调用者 S
fn f(x: &impl T)Trait bound impl traits, 类似于 fn f<S:T>(x: &S)
fn f(x: &dyn T)dynamic dispatch 的标记,f 不会被单态化(monomorphized
fn f() where Self: R;trait T {} 中,使 f 仅可访问 impl R 的已知类型
fn f() where Self: Sized;使用 Sized 可以从 dyn T trait object vtable 中选出 f,启用 trait object
fn f() where Self: R {}其它 R 有用的 w. dflt,方法(non dflt 无论怎样都需要被实现)

Higher-Ranked 例子

  • 实际的类型和特征,抽象于一些东西,通常是生命周期
例子解释
for<'a>higher-ranked bounds 的标记
trait T: for<'a> R<'a> {}任何 impl TS 都必须为了整个生命周期满足 R
fn(&'a u8)函数指针类型持有 fn 可调用的指定的生命周期
for<'a> fn(&'a u8)Higher-ranked type 持有 fn 可调用的任何上面的子类型
fn(&'_ u8)同上,自动展开到类型 for<'a> fn(&'a u8)
fn(&u8)同上,自动展开到类型 for<'a> fn(&'a u8)
dyn for<'a> Fn(&'a u8)Higher-ranked(trait-object)类型,像上面的 fn 一样
dyn Fn(&'_ u8)同上,自动展开到类型 dyn for<'a> Fn(&'a u8)
dyn Fn(&u8)同上,自动展开到类型 dyn for<'a> Fn(&'a u8)

* for<> 是类型的一种,下面展示了 impl T for for<'a> fn(&'a u8) 这样的写法

实现特征解释
impl<'a> T for fn(&'a u8) {}对于指针 fn,其中调用接受指定的 'a, imple trait T
impl T for for<'a> fn(&'a u8) {} 对于指针 fn,其中调用接受任何的 imple trait T
impl T for fn(&u8) {}同上,简写版

字符串和字符

  • Rust 有几种创建文本值的方法
例子解释
"..."字符串文本,UTF-8 将解释 \n 为换行符
r"..."原始字符串文本,UTF-8 不会解释 \n
r#"..."#原始字符串文本,UTF-8 也可以包含 "# 数量是可变的
b"..."字节字符串文本,构造 ASCII [u8] ,不是字符串
br"...", br#"..."#原始字节字符串文本,ASCII [u8],以上的组合
'🦀'字符文本,固定 4 字节的 unicode char
b'x'ASCII 字节文本

文档注释

例子解释
///外部文档注释,通常用于类型、特征、函数
//!内行文档注释,主要用于文件到文档模块的开头
//行注释,使用这些来记录代码流或内部结构
/*...*/块注释
/**...*/外部块文档注释
/*!...*/内部块文档注释

其它杂项

  • 这些符号不属于任何其他类别,但知道了还是很好
例子解释
!始终为空
_未命名的通配符变量绑定,例如 |x, _| {}
let _ = x;未命名的分配是不执行的,不会 move out x 或保留目标值
_x变量绑定明确标记为未使用
1_234_567用于可清晰分辨的数字分隔符
1_u8数字文本 的类型说明符(还可以是 i8u16 …)
0xBEEF, 0o777, 0b1001十六进制(0x)、八进制(0o)和二进(0b)的整数文本
r#foo用于版本兼容的原始标识符
x;语句终止符或表达式

常用运算符

Rust 支持大多数运算符(+*%===,...),包括重载。因为它们在 Rust 中的行为没有什么不同,所以就不在此处列出它们。

Rust标准库

单线程

  • 常见的需求但容易忘记的代码片段
字符串 需求代码片段
拼接字符串(任何实现了 Displayformat!("{x}{y}")
追加字符串(任何实现了 Display 到 任何实现了 Writewrite!(x, "{y}")
按分隔符模式分割s.split(pattern)
  ...with &strs.split("abc")
  ...with chars.split('/')
  ...with closures.split(char::is_numeric)
按空格切割s.split_whitespace()
用换行符分割s.lines()
按正则表达式分割Regex::new(r"\s")?.split("one two three")
  1. 如果 xy 之后不打算用到,考虑使用 write!std::ops::Add
  2. 需要 regex crate

I/O 需求代码片段
创建一个新文件File::create(PATH)?
通过OpenOptions创建OpenOptions::new().create(true).write(true).truncate(true).open(PATH)?

需求代码片段
宏 可变参数macro_rules! var_args { ($($args:expr),*) => {{ }} }
  使用参数,如多次调用 f$( f($args); )*

Esoterics 需求代码片段
干净的闭包捕获wants_closure({ let c = outer.clone(); move || use_clone(c) })
try 闭包中的修复推断iter.try_for_each(|x| { Ok::<(), Error>(()) })?;
如果 T 实现了 Copy,则可迭代和编辑 &mut [T]Cell::from_mut(mut_slice).as_slice_of_cells()
获取带有长度的子切片&original_slice[offset..][..length]
敏锐的确保特征 T 是对象安全的const _: Option<&dyn T> = None;

线程安全

例子Send*!Send
Sync*大多数类型是 ...Arc<T>1,2Mutex<T>MutexGuard<T>RwLockReadGuard<T>
!SyncCell<T>2RefCell<T>Rc<T>&dyn Trait*const T*mut T

*一个 T: Send 的实例可以被移动到另一个线程,而一个 T: Sync 意味着 &t 可以被移动到移动到另一个线程

  1. 如果 TSync
  2. 如果 TSend
  3. 如果你需要去 send 一个裸指针,创建一个新类型 struct Ptr(*const u8)unsafe impl Send for Ptr {},仅需确保你可以 send 它

迭代器

获取迭代器
基本用法
假设有一个类型 C 的集合c
  • c.into_iter() — 将集合c转换为一个Iteratoriconsumesc。需要实现了 CIntoIterator。条目的类型取决于 C 是什么。获取迭代器的"标准化"方法
  • c.iter() — 一些集合提供的优雅方法,返回借用的迭代器,不会消耗 c
  • c.iter_mut() — 同上,但是可变借用的迭代器允许更改集合
  • The Iterator
    一旦你有一个 i:
  • i.next() — 返回下一个元素c提供的 Some(x),或者如果已经完成了则返回None
  • For 循环
  • for x in c {} — 语法糖,调用c.into_iter()并且循环i直到为 None
  • *如果它看起来好像没有消耗 c,那是因为类型是 Copy,如 如果你调用 (&c).into_iter(),它将在 &c 上调用 into_iter() (这将消耗引用并将其转换为迭代器),但c仍保持不变。


    实现迭代器
    基本用法
    假设你有一个 `struct Collection {}`
  • struct IntoIter {} — 创建一个结构来保存你的迭代状态(如 索引)以进行值的迭代
  • impl Iterator for IntoIter {} — 实现 Iterator::next(),这样它就可以生成元素
  • Collection<T>
    IntoIter<T> ⌾Iterator Item = T;
    共享的和可变迭代器
  • struct Iter<T> {} — 为共享迭代器去创建持有 &Collection<T> 的结构
  • struct IterMut<T> {} — 同上,为了可变迭代器去创建持有 &mut Collection<T> 的结构
  • impl Iterator for Iter {} — 实现共享迭代器
  • impl Iterator for IterMut {} — 实现可变迭代器
  • 另外你可能会希望添加一些便捷的方法
  • Collection::iter(&self) -> Iter
  • Collection::iter_mut(&mut self) -> IterMut
  • Iter<T> ⌾Iterator Item = &T;
    IterMut<T> ⌾Iterator Item = &mut T;
    使用循环的写法
  • impl IntoIterator for Collection {} — 现在for x in c {}是有用的
  • impl IntoIterator for &Collection {} — 现在for x in &c {}是有用的
  • impl IntoIterator for &mut Collection {} — 现在for x in &mut c {}是有用的 Collection<T> ⌾IntoIterator Item = T; To = IntoIter<T> Iterate over T
    &Collection<T> ⌾IntoIterator Item = &T; To = Iter<T> Iterate over &T
    &mut Collectn<T> ⌾IntoIterator Item = &mut T; To = IterMut<T> Iterate over &mut T
  • 数字转换

    需要转换成的→u8 … i128f32 / f64String
    u8 … i128u8::try_from(x)?x as f32x.to_string()
    f32 / f64x as u8x as f32x.to_string()
    Stringx.parse::<u8>()?x.parse::<f32>()?x
    1. 如果打印是正确的话 from()会直接输出,如 u32::from(my_u8)
    2. 截断(11.9_f32 as u8 gives 11),充满(1024_f32 as u8 gives 255)
    3. 可能会有不合适的数字(u64::MAX as f32)或产生 Inf (u128::MAX as f32)

    字符串转换

    • 如果你想要一个指定类型的字符串
    已经存在的类型 x要转换成 String
    Stringx
    CStringx.into_string()?
    OsStringx.to_str()?.to_string()
    PathBufx.to_str()?.to_string()
    Vec<u8>String::from_utf8(x)?
    &strx.to_string()
    &CStrx.to_str()?.to_string()
    &OsStrx.to_str()?.to_string()
    &Pathx.to_str()?.to_string()
    &[u8]String::from_utf8_lossy(x).to_string()

    已存在的类型 x要转换成 CString
    StringCString::new(x)?
    CStringx
    OsStringCString::new(x.to_str()?)?
    PathBufCString::new(x.to_str()?)?
    Vec<u8>CString::new(x)?
    &strCString::new(x)?
    &CStrx.to_owned()
    &OsStrCString::new(x.to_os_string().into_string()?)?
    &PathCString::new(x.to_str()?)?
    &[u8]CString::new(Vec::from(x))?
    *mut c_charunsafe { CString::from_raw(x) }

    已存在的类型 x要转换成 OsString
    StringOsString::from(x)
    CStringOsString::from(x.to_str()?)
    OsStringx
    PathBufx.into_os_string()
    Vec<u8>?
    &strOsString::from(x)
    &CStrOsString::from(x.to_str()?)
    &OsStrOsString::from(x)
    &Pathx.as_os_str().to_owned()
    &[u8]?

    已存在的类型 x要转换成 PathBuf
    StringPathBuf::from(x)
    CStringPathBuf::from(x.to_str()?)
    OsStringPathBuf::from(x)
    PathBufx
    Vec<u8>?
    &strPathBuf::from(x)
    &CStrPathBuf::from(x.to_str()?)
    &OsStrPathBuf::from(x)
    &PathPathBuf::from(x)
    &[u8]?

    已存在的类型 x要转换成 PathBuf
    StringPathBuf::from(x)
    CStringPathBuf::from(x.to_str()?)
    OsStringPathBuf::from(x)
    PathBufx
    Vec<u8>?
    &strPathBuf::from(x)
    &CStrPathBuf::from(x.to_str()?)
    &OsStrPathBuf::from(x)
    &PathPathBuf::from(x)
    &[u8]?

    已存在的类型 x要转换成 Vec
    Stringx.into_bytes()
    CStringx.into_bytes()
    OsString?
    PathBuf?
    Vec<u8>x
    &strVec::from(x.as_bytes())
    &CStrVec::from(x.to_bytes_with_nul())
    &OsStr?
    &Path?
    &[u8]x.to_vec()

    已存在的类型 x要转换成 &str
    Stringx.as_str()
    CStringx.to_str()?
    OsStringx.to_str()?
    PathBufx.to_str()?
    Vec<u8>std::str::from_utf8(&x)?
    &strx
    &CStrx.to_str()?
    &OsStrx.to_str()?
    &Pathx.to_str()?
    &[u8]std::str::from_utf8(x)?

    已存在的类型 x要转换成 &CStr
    StringCString::new(x)?.as_c_str()
    CStringx.as_c_str()
    OsStringx.to_str()?
    PathBuf?,4
    Vec<u8>CStr::from_bytes_with_nul(&x)?
    &str?,4
    &CStrx
    &OsStr?
    &Path?
    &[u8]?

    已存在的类型 x要转换成 PathBuf
    StringPathBuf::from(x)
    CStringPathBuf::from(x.to_str()?)
    OsStringPathBuf::from(x)
    PathBufx
    Vec<u8>?
    &strPathBuf::from(x)
    &CStrPathBuf::from(x.to_str()?)
    &OsStrPathBuf::from(x)
    &PathPathBuf::from(x)
    &[u8]CStr::from_bytes_with_nul(x)?
    *const c_charunsafe { CStr::from_ptr(x) }

    已存在的类型 x要转换成 &OsStr
    StringOsStr::new(&x)
    CString?
    OsStringx.as_os_str()
    PathBufx.as_os_str()
    Vec<u8>?
    &strOsStr::new(x)
    &CStr?
    &OsStrx
    &Pathx.as_os_str()
    &[u8]?

    已存在的类型 x要转换成 &Path
    StringPath::new(x)
    CStringPath::new(x.to_str()?)
    OsStringPath::new(x.to_str()?)
    PathBufPath::new(x.to_str()?)
    Vec<u8>?
    &strPath::new(x)
    &CStrPath::new(x.to_str()?)
    &OsStrPath::new(x)
    &Pathx
    &[u8]?

    已存在的类型 x要转换成 &[u8]
    Stringx.as_bytes()
    CStringx.as_bytes()
    OsString?
    PathBuf?
    Vec<u8>&x
    &strx.as_bytes()
    &CStrx.to_bytes_with_nul()
    &OsStrx.as_bytes()
    &Path?
    &[u8]x

    已存在的类型 x要转换成 Other( *const c_char )
    CStringx.as_ptr() ( *const c_char )

    *如果类型能被推断出来则可使用简写 x.into()

    *如果类型能被推断出来则可使用简写 x.as_ref()

    1. 如果调用了unsafe,你应该或必须确保原始数据带有字符串类型的有效表示(如 一个 UTF-8 的字符串)
    2. 仅在一些平台( std::os::<your_os>::ffi::OsStrExt )存在辅助方法去获取底层 OsStr 的原始表示。从那里使用表格的其它部分,如下
    use std::os::unix::ffi::OsStrExt;
    let bytes: &[u8] = my_os_str.as_bytes();
    CString::new(bytes)?
    1. c_char 必须来自之前的 CString,如果来自FFI,请参阅 &CStr
    2. x 因为缺少终止符 0x0,没有已知的简写,可能最好的方式是通过 CString
    3. 必须确保向量实际上以 0x0 结尾

    字符串输出

    APIs

    • Rust使用这些APIs将类型转换为字符串化输出,统称为格式宏
    输出备注
    format!(fmt)String"to String" 的转换器
    print!(fmt)Console写到标准输出
    println!(fmt)Console写到标准输出,换行
    eprint!(fmt)Console写到标准错误输出
    eprintln!(fmt)Console写到标准错误输出,换行
    write!(dst, fmt)Buffer还有 use std::io::Write;
    writeln!(dst, fmt)Buffer还有 use std::io::Write;,换行

    方法备注
    x.to_string()生成 String,实现任何的 Display 类型

    *fmt 是字符串文字,例如 "hello {}",指定输出和其他参数

    可打印的类型

    *在友好的 format! 中,类型通过 trait Display "{}"Debug "{:?}" 转换,非详尽列表如下

    类型实现
    StringDebug, Display
    CStringDebug
    OsStringDebug
    PathBufDebug
    Vec<u8>Debug
    &strDebug, Display
    &CStrDebug
    &OsStrDebug
    &PathDebug
    &[u8]Debug
    boolDebug, Display
    charDebug, Display
    u8 … i128Debug, Display
    f32, f64Debug, Display
    !Debug, Display
    ()Debug

    *简而言之,几乎所有东西都是Debug,更多特殊类型可能需要特殊处理或转换到 Display

    格式化

    • 格式宏中的每个参数指示符要么是空的 {},{argument},要么遵循基本语法
    { [argument] ':' [[fill] align] [sign] ['#'] [width [$]] ['.' precision [$]] [type] }
    元素含义
    argument数字( 0, 1, ... ),变量或名字,如 print!("{x}")
    fill如果指定了宽度,用(如 0 )来填充空白的字符
    align如果指定了宽度,则左(<)、中(^)或右(>)
    sign 可以是 +,标志总是被打印
    #代替格式化,如 美化的 Debug 格式器 ? 或前缀十六进制使用 0x
    width最小的宽度(≥0),填充(默认为空格)如果以 0 开头,则补零
    precision数字的小数位(≥0),或者形容非数字的最大宽度
    $解释宽度(width)或精度(precision)作为参数标识符,而不是允许动态格式化
    typeDebug 格式化、十六进制(x)、二进制(b)、八进制(o)、指针(p)、exp(e)

    格式化示例解释
    {}使用 Display 打印下一个参数
    {x}同上,但在范围内使用变量 x
    {:?}使用 Debug 打印下一个参数
    {2:#?}使用 Debug 格式化,较好的打印第三个参数
    {val:^2$}将命名参数 val 居中,宽度由第三个参数指定
    {:<10.3}左对齐宽度为 10,精度为 3
    {val:#x}将参数 val 格式化为十六进制,带前缀 0x (替代格式 x

    完整示例解释
    println!("{}", x)使用 Display 在标准输出打印并且追加一个新行
    println!("{x}")同上,但在范围内使用变量 x
    format!("{a:.3} {b:?}")用三位数字转换 PI,增加空格,b 使用 Debug,返回 String

    Rust 工具链

    项目组织

    • 基本的项目结构布局,通用的文件和文件夹,一般可由 cargo 生成
    入口相关的 code
    📁 .cargo/项目本地的 cargo 配置,通常会包含 config.toml
    📁 benches/通过运行 cargo bench 来为你的 crate 执行基准测试,一般需要设置默认为 nightly
    📁 examples/通过示例展示如何使用你的 crate,他们会像外部用户一样看到你的 crate
      my_example.rs运行个别的示例像 cargo run --example my_example
    📁 src/你项目的源代码
    main.rs程序的默认入口文件,被作用于 cargo run
    lib.rs库的默认入口文件,如 对于搜索 my_crate::f() 开始的地方
    📁 src/bin/存放额外的二进制文件的地方,即使是在库项目中
    extra.rs添加二进制文件,使用 cargo run --bin extra 去运行
    📁 tests/通常用于存放集成测试,通过 cargo test 来调用,单元测试一般会保留在 src/ 文件中
    .rustfmt.toml用作 cargo fmt 自定义的方式
    .clippy.tomlclippy lints 的指定配置,通过使用 cargo clippy
    build.rsPre-build script,一般在构建 C / FFI 会用到
    Cargo.toml主项目的配置,一般用于定义依赖,组件等等
    Cargo.lock可用于查看依赖关系细节,为程序添加 git,而不是库
    rust-toolchain.toml为项目定义覆盖的工具链(channel、components、targets)

    各种入口文件的小例子

    // src/main.rs (程序的默认入口文件)
    
    fn main() {
        println!("Hello, AwesomeProgram!");
    }
    // src/lib.rs (库的默认入口文件)
    
    pub fn f() {}      // 因为是根目录中的公开目录,所以可从外部访问
    
    mod m {
        pub fn g() {}  // 不是根目录中的公开路径(模块 m 不是公开的), 因此 g 不能在 crate 之外被访问
    }  
    // src/my_module.rs (单元测试可以在你项目中的任意文件)
    
    fn f() -> u32 { 0 }
    
    #[cfg(test)]
    mod test {
        use super::f;           // 需要从上级模块中导入目录,可以访问没有公开的成员
    
        #[test]
        fn ff() {
            assert_eq!(f(), 0);
        }
    }
    // tests/sample.rs (集成测试的例子)
    
    #[test]
    fn my_sample() {
        assert_eq!(my_crate::f(), 123); // 集成测试和基准测试依赖第三方的 crate,因此,他们仅看到公开的目录
    }
    // benches/sample.rs (基准测试的例子)
    
    #![feature(test)]   // 基准测试仍处于试验阶段
    
    extern crate test;  // 在 2018 版本中需要添加
    
    use test::{black_box, Bencher};
    
    #[bench]
    fn my_algo(b: &mut Bencher) {
        b.iter(|| black_box(my_crate::f())); // `black_box` 阻止 `f` 被优化掉
    }
    // build.rs (预构建脚本的例子)
    
    fn main() {
        // 你需要依赖目标 OS 的环境变量,对于当前的 OS 可以表示 `#[cfg(...)]`
        let target_os = env::var("CARGO_CFG_TARGET_OS");
    }
    // src/lib.rs (过程宏的默认入口文件)
    
    extern crate proc_macro;  // 显然需要像这样被导入
    
    use proc_macro::TokenStream;
    
    #[proc_macro_attribute]   // 可以作为 `#[my_attribute]` 被使用
    pub fn my_attribute(_attr: TokenStream, item: TokenStream) -> TokenStream {
        item
    }
    
    // Cargo.toml
    
    [package]
    name = "my_crate"
    version = "0.1.0"
    
    [lib]
    proc-macro = true

    模块树和NameSpace导入的使用

    模块树
    模块和源文件的使用方式:
  • 模块树需要显式定义,它不是从文件系统树中隐式创建的
  • Module tree root是库、应用程序等等的入口文件,如 lib.rs
  • 实际模块的定义如下:
  • 一个在文件中定义的 mod m {},当使用 mod m; 时,将会先去读 m.rs 或者 m/mod.rs
  • .rs 的路径是基于嵌套的,如 mod a { mod b { mod c; }}}a/b/c.rsa/b/c/mod.rs
  • 通过一些 mod m; 来自 Module tree root 而没有路径的文件,是不会被编译器触及到的

  • Rust 有三种 NameSpaces
    Namespace 类型Namespace 函数Namespace 宏
    mod X {}fn X() {}macro_rules! X { ... }
    X (crate)const X: u8 = 1;
    trait X {}static X: u8 = 1;
    enum X {}
    union X {}
    struct X {}
    struct X;1
    struct X();2
    1. 类型函数中,定义类型 X 和常量 X
    2. 类型函数中,定义类型 X 和函数 X
    • 在任何给定的作用域中,例如在一个模块中,每个名称空间只能存在一个项目
      • enum X {}fn X() {} 可以共存
      • struct X;const X 可以共存
    • 使用 use my_mod::X; 时,所有被 X 调用的目录都会被导入

    *由于命名约定,如 按照约定 fnmod 是小写,并且大多数开发者不会把所有东西都命名为 X, 在通常情况下你不必担心这些类型,它们可以成为设计宏时的一个因素

    Cargo

    *必须要被了解的命令和工具

    命令描述
    cargo init在最近的版本创建一个新项目
    cargo build在调试模式中构建项目( --release 适用于所有可以优化的地方)
    cargo check检查项目是否可以编译成功(执行很快)
    cargo test为项目执行测试用例
    cargo doc --open在最近的版本创建一个新项目
    cargo run如果生成了一个二进制文件( main.rs )则执行你的项目
    cargo run --bin b运行二进制文件 b,将特性与其它依赖统一在一起,可能会造成混淆
    cargo run -p w运行子空间的 w,如你期望的可以更多的面对特性
    cargo ... --timings展示是什么 crate 导致你的构建花费了这么长时间,非常有用
    cargo tree显示依赖关系图
    cargo +{nightly, stable} ...在命令中使用给定的工具链,如 用于 'nightly only' 工具
    cargo +nightly ...一些 nightly-only 的命令,使用下面的命令替换 ...
    rustc -- -Zunpretty=expanded显示展开的宏
    rustup doc打开离线 Rust 文档,包括书籍,在没有网络环境下使用很好

    *这里的 cargo build 你也可以打印 cargo b,并且 --release 意味着可以用 -r 代替

    下面这些是可选的 rustup 组件,使用 rustup component add [tool] 安装它们

    工具描述
    cargo clippy增加 lints 捕获一些常见的 滥用API不符合语言习惯 的代码
    cargo fmt自动代码格式化 rustup component add rustfmt

    *有更多可添加的 cargo 插件 点我查看

    交叉编译

    🔘检查目标系统是支持的

    🔘通过 rustup target install X 安装不同目标系统的需求

    🔘安装原生的工具链

    从目标主机供应商获取(谷歌、苹果 ...),可能并非在所有的主机都可用(如 在 Windows 上没有 iOS 的工具连)

    一些工具链需要额外的构建步骤(如 安卓系统需要 make-standalone-toolchain.sh )

    🔘像下面这样更新 ~/.cargo/config.toml

    [target.aarch64-linux-android]
    linker = "[PATH_TO_TOOLCHAIN]/aarch64-linux-android/bin/aarch64-linux-android-clang"
    [target.aarch64-linux-android]
    linker = "C:/[PATH_TO_TOOLCHAIN]/prebuilt/windows-x86_64/bin/aarch64-linux-android21-clang.cmd"

    🔘设置环境变量(可选的,等待编译器如果有报错提示后再进行设置)

    set CC=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
    set CXX=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android21-clang.cmd
    set AR=C:\[PATH_TO_TOOLCHAIN]\prebuilt\windows-x86_64\bin\aarch64-linux-android-ar.exe
    ...

    是否设置它们依赖于编译反馈的报错提示,不一定都是需要的

    一些平台的配置可能会非常敏感,取决于如何指定路径(如 \/ )和引用路径

    ✔️使用 cargo build --target=X 去编译

    工具指令

    *嵌入在源码中的特殊标记通常用于工具或者预处理

    在声明宏中,通过示例 macro_rules! 实现以下功能:
    在宏内解释
    $x:ty 宏的捕获,这里是一种类型
    $x:item 一个 item ,如 函数、结构、模块等等
    $x:block 一组语句或表达式 block {},如 { let x = 5; }
    $x:stmt 一个语句,如 let x = 1 + 1;String::new();vec![]
    $x:expr 一个表达式,如 x1 + 1String::new()vec![]
    $x:pat 一种分支模式,如 Some(t)(17, 'a')_
    $x:ty 一种类型,如 StringusizeVec<u8>
    $x:ident 一个标识符,如 在 let x = 0; 中,标识符是 x
    $x:path 一个路径,如 foostd::mem::replacetransmute::<_, int>
    $x:literal 一个字面值,如 3"foo"b"bar" 等等
    $x:lifetime 一个生命周期,如 'a'static 等等
    $x:meta 一个meta item,在这些 #[...]#![...] 里面的属性
    $x:vis 一个可见的修饰符,如 pubpub(crate)
    $x:tt 一个单独的 token tree点击查看更多
    $crate 一个卫生变量,定义宏的 crate

    文档
    在文档注释中的用法:
    在文档注释内解释
    ```...``` 包含一个文档测试,可以使用 cargo test去运行
    ```X,Y ...``` 同上,并且包括可选配置 X...Y
    rust 明确使用 Rust 去编写测试,由 Rust 工具实现的
    - 默认行为,编译测试,运行测试,如果发生恐慌则测试失败
    should_panic 编译测试,运行测试,执行期望应该是恐慌,如果不是,则失败
    no_run 编译测试,如果代码无法编译,则测试失败,不会运行测试
    compile_fail 编译测试,如果代码可以编译,则测试失败
    ignore 不会编译,不会运行,选择上面的选项
    edition2018 执行代码为Rust 2018版,默认为 2015版
    # 隐藏文档中的一行,如 # use x::hidden;
    [`S`] 创建指向结构体、枚举、特征、函数……的链接
    [`S`](crate::S) 也可以使用路径,以 markdown 链接的形式




    #![globals]
    影响整个 crate 和程序的属性:
    Opt-Out'sOn解释
    #![no_std] C 不会自动导入std,将使用core代替
    #![no_implicit_prelude] CM 不会添加prelude,需要手动导入None Vec ...
    #![no_main] C 如果是这种情况则不要在程序中写入 main()
    Opt-In'sOn解释
    #![feature(a, b, c)] C 依赖可能永远不会稳定的特性,Unstable Book.
    BuildsOn解释
    #![windows_subsystem = "x"] C 在 Windows 上,做一个控制台或者应用程序
    #![crate_name = "x"] C 指定当前 crate 的名称,如 当在不使用 cargo 的时候
    #![crate_type = "bin"] C 指定当前 crate 的类型,如 bin lib dylib cdylib ...
    #![recursion_limit = "123"] C 为 deref 和 macros 设置编译期间的递归限制
    #![type_length_limit = "456"] C 限制类型替换的最大数量
    HandlersOn解释
    #[panic_handler] F 创建一些应用程序的panic handler,如 fn f(&PanicInfo) -> !
    #[global_allocator] S 创建一些静态项目的实现,GlobalAlloc 全局分配器

    #[code]
    主要控制写出代码的属性:
    Developer UXOn解释
    #[non_exhaustive] T Future-proof structenum,暗示它以后数据将会扩大
    #[path = "x.rs"] M 从非标准库中获取模块
    CodegenOn解释
    #[inline] F 建议编译器应该在调用点时使用内联函数
    #[inline(always)] F 强烈要求编译器使用内联调用
    #[inline(never)] F 如果仍然内联函数,则指示编译器不起作用
    #[cold] F 提示该函数可能不会被调用
    #[target_feature(enable="x")] F 对于 unsafe fn 的代码强制 CPU 开启特性
    #[track_caller] F 对于更友好的 panic 输出信息,允许 fn 查找caller
    #[repr(X)] T 使用另一种表示而不是默认的rust
    #[repr(C)] T 使用 C-compatible (f. FFI)、可预测 (f. transmute) 的布局
    #[repr(C, u8)] enum enum判别器指定类型
    #[repr(transparent)] T 为单个元素类型提供与包含字段相同的布局
    #[repr(packed(1))] T 结构和包含字段的较低对齐,轻度的 UB 倾向
    #[repr(align(8))] T 将结构对齐提高到给定值,如 SIMD 类型
    *可以组合一些表示修饰符,如 #[repr(C, packed(1))]
    LinkingOn解释
    #[no_mangle] * 直接使用项目名称作为符号名称而不修改
    #[no_link] X 当想要使用宏时,不去链接extern crate
    #[link(name="x", kind="y")] X 当查找符号时,要链接的本地库
    #[link_name = "foo"] F 要查找以解析 extern fn 的符号名称
    #[link_section = ".sample"] FS 应放置项目的目标文件的部分名称
    #[export_name = "foo"] FS 以不同的名称导出 fnstatic
    #[used] S 不要优化掉 static 变量,尽管它看起来并未使用

    #[quality]
    Rust 工具用于提高代码质量的属性:
    代码模式On解释
    #[allow(X)] * 指示 rustc 或 clippy 去忽略 class X 可能的问题
    #[warn(X)] * 发出警告,与 clippy lints 在一起使用
    #[deny(X)] * 编译失败
    #[forbid(X)] * 编译失败并且阻止后续的重写
    #[deprecated = "msg"] * 让你用户知道你制造了一个设计错误
    #[must_use = "msg"] FTX 使编译器检查由调用者处理的返回值
    *对于保证高质量 crates 的最佳选择,存在一些争论。积极维护的多个开发版本的 crates 可能会受益于更具侵略性denyforbid lints;不经常更新的可能更多来自保守使用warn,因为未来的编译器或 clippy 更新可能会突然破坏其他工作代码并出现些小问题
    测试On解释
    #[test] F 将函数标记为测试,使用 cargo test
    #[ignore = "msg"] F 编译但暂时不执行一些 #[test]
    #[should_panic] F 测试必须触发 panic!() 才能实际成功
    #[bench] F 标记函数在 bench/ 作为基准测试,使用 cargo bench
    格式化On解释
    #[rustfmt::skip] * 防止 cargo fmt 清理项目
    #![rustfmt::skip::macros(x)] CM ... 从清理宏 x 开始
    #![rustfmt::skip::attributes(x)] CM ... 从清理属性 x 开始
    文档On解释
    #[doc = "Explanation"] * 与添加 /// 文档注释相同
    #[doc(alias = "other")] * 提供用户可以在文档中搜索的另一个名称
    #[doc(hidden)] * 防止项目出现在文档中
    #![doc(html_favicon_url = "")] C 为文档设置 favicon
    #![doc(html_logo_url = "")] C 文档中使用的 logo
    #![doc(html_playground_url = "")] C 生成 Run 按钮并使用给定的服务
    #![doc(html_root_url = "")] C 指向外部 crate 的链接的基本 URL
    #![doc(html_no_source)] C 防止源包含在文档中

    #[macros]
    与宏的创建和使用相关的属性:
    宏示例On解释
    #[macro_export] ! 导出 macro_rules! 作为公开的在 crate 级别
    #[macro_use] MX 让宏持久化的模块,或者从 extern crate 导入
    过程宏On解释
    #[proc_macro] F fn 标记为function-like的过程宏,可作为 m!() 调用
    #[proc_macro_derive(Foo)] F 将可以 #[derive(Foo)]fn 标记为derive macro
    #[proc_macro_attribute] F 将可以理解成新的 #[x]fn 标记为attribute macro
    DerivesOn解释
    #[derive(X)] T 让一些过程宏提供一些好的 trait X 的实现

    #[cfg]
    控制条件编译的属性:
    配置属性On解释
    #[cfg(X)] * 如果配置 X 成立,则包含项目
    #[cfg(all(X, Y, Z))] * 如果所有的选项都成立,则包含项目
    #[cfg(any(X, Y, Z))] * 如果至少有一个选项成立,则包含项目
    #[cfg(not(X))] * 如果 X 不成立,则包含项目
    #[cfg_attr(X, foo = "msg")] * 如果配置 X 成立,则应用 #[foo = "msg"]
    *注意,options 一般可以设置多次,即同一个 key 可以出现多个 value。可以同时期望 #[cfg(target_feature = "avx")]#[cfg(target_feature = "avx2")] 为真。
    已知选项On解释
    #[cfg(target_arch = "x86_64")] * 为了去编译 CPU 架构的 crate
    #[cfg(target_feature = "avx")] * 某一类特性类别的指令是否可用
    #[cfg(target_os = "macos")] * 你的代码将在其操作系统上运行
    #[cfg(target_family = "unix")] * 属于家族操作系统
    #[cfg(target_env = "msvc")] * DLLs 和函数怎样与操作系统接口连接
    #[cfg(target_endian = "little")] * 你的 zero-cost 协议失败的主要原因
    #[cfg(target_pointer_width = "64")] * 有多少位指针、usize 和 CPU
    #[cfg(target_vendor = "apple")] * 目标生产商
    #[cfg(debug_assertions)] * debug_assert!()是否会发生 panic
    #[cfg(panic = "unwind")] * 是否 unwind 或 abort 将发生恐慌
    #[cfg(proc_macro)] * 是否将 crate 编译为过程宏
    #[cfg(test)] * 是否与 cargo test一起编译
    #[cfg(feature = "serde")] * 是否 feature serde 与你的 crate 被一起编译

    build.rs
    与预构建脚本关联的环境变量与输出
    输入环境解释
    CARGO_FEATURE_X 为每一个激活的特性 X 设置环境变量
    CARGO_FEATURE_SERDE 如果特性 serde 被强制启用
    CARGO_FEATURE_SOME_FEATURE 如果有一些特性被强制启用,- 会转成 _
    CARGO_CFG_X 暴露 cfg's,加入多个选项通过,并且会把- 转换成 _
    CARGO_CFG_TARGET_OS=macos 如果 target_os 设置为了 macos
    CARGO_CFG_TARGET_FEATURE=avx,avx2 如果 target_feature 设置为了 avx 和 avx2
    OUT_DIR 输出应该放到哪个目录
    TARGET 正在编译的目标三重
    HOST 主机三重(运行这个构建脚本)
    PROFILE 可以是 debug 或 release
    通过 env::var()?build.rs中是可用的,但并未详尽列出
    输出字符串On解释
    cargo:rerun-if-changed=PATH 仅在如果 PATH 变更了,再次运行这个 build.rs
    cargo:rerun-if-env-changed=VAR 仅在如果环境变量 VAR 变更了,再次运行这个 build.rs
    cargo:rustc-link-lib=[KIND=]NAME 就像通过-l选项链接本机库一样
    cargo:rustc-link-search=[KIND=]PATH 就像通过-l选项,为本地库查找路径
    cargo:rustc-flags=FLAGS 向编译器添加指定的 flags
    cargo:rustc-cfg=KEY[="VALUE"] 发出给定的 cfg 选项以用于以后的编译
    cargo:rustc-env=VAR=VALUE 在 crate 编译期间发出可通过env!()访问的变量
    cargo:rustc-cdylib-link-arg=FLAG 当构建一个cdylib时,传递链接器标志
    cargo:warning=MESSAGE 发出编译器警告
    通过 println!()build.rs写出,但并未详尽列出

    *对于属性中的On列:

    • C 表示在 crate 级别(通常 #![my_attr] 在顶级文件中给出)
    • M 意味着在模块上
    • F 指功能上
    • S 意味着静态
    • T 表示类型
    • X 意味着一些特殊的东西
    • ! 意味着在宏上
    • * 意味着几乎任何项目

    Rust 类型操作

    类型 特征 泛型

    • 允许用户自定义类型,以避免冗余代码

    类型 与 特征

    类型

    • 一组具有给定语义、布局等等
    类型
    u8{ 0u8, 1u8, ..., 255u8 }
    char{ 'a', 'b', ... '🦀' }
    struct S(u8, char){ (0u8, 'a'), ... (255u8, '🦀') }

    类型等价与转换

    • 这可能很明显,但 u8&u8&mut u8,彼此完全不同
    • 任何 t: T 仅接受来自准确的 T 值,如
      • f(0_u8) 不能被 f(&0_u8) 调用
      • f(&mut my_u8) 不能被 f(&my_u8) 调用
      • f(0_u8) 不能被 f(0_i8) 调用

    *在数学意义上,从类型的角度来看 0 != 0,在语言意义上,==(0u8, 0u16) 仅为了阻止一些小意外而没有定义

    类型
    u8{ 0u8, 1u8, ..., 255u8 }
    u16{ 0u16, 1u16, ..., 65_535u16 }
    &u8 { 0xffaa&u8, 0xffbb&u8, ... }
    &mut u8{ 0xffaa&mut u8, 0xffbb&mut u8, ... }
    • 然而,Rust 有时可能有助于在类型之间进行转换
      • casts手动转换类型的值,0_i8 as u8
      • coercions安全时自动转换类型, let x: &u8 = &mut 0_u8;
    1. castscoercions强制转换将一个集合(如 u8)的值转换到另一个集合(如 u16),可能会添加 CPU 指令来完成此操作;这与子类型不同,意味着类型和子类型是同一个集合的一部分(如 u8u16 的子类型,跟 0_u80_u16 类似),而这种转换将纯粹是编译时检查。Rust并不对常规类型使用子类型(0_u80_u16 确实不同),而是对生命周期使用某种类型。
    2. 这里的安全不仅仅是物理概念(如 &u8 不能被强制转换为 &u128),还包括“历史表明这样的转换是否会导致编程错误”。

    Implementations — impl S { }

    impl Port {
        fn f() { ... }
    }
    • 类型通常伴随着实现,如 impl Port {},与类型相关的行为有:
      • 相关函数 Port::new(80)
      • 方法 port.close()

    *与之相关的更多的是哲学而不是技术,没有什么(除了好的品味)可以阻止 u8::play_sound() 的发生。

    特征 — trait T { }

    ⌾ Copy ⌾ Clone ⌾ Sized ⌾ ShowHex

    • Traits ...
      • 是“抽象”行为的方式
      • trait 作者在语义上声明这个 trait 意味着 X
      • 其他人可以为他们的类型实现该行为
    • 将 trait 视为类型的“成员列表”:
    Copy TraitClone TraitSized Trait
    SelfSelfSelf
    u8u8char
    u16StringPort
    .........
    trait 作为成员表,Self 是指包含的类型
    • 属于该成员名单的任何人都将遵守名单的行为
    • Traits 还可以包括相关的方法、函数、...
    trait ShowHex {
        // Must be implemented according to documentation.
        fn as_hex() -> String;
    
        // Provided by trait author.
        fn print_hex() {}
    }

    ⌾ Copy

    trait Copy { }
    • 没有方法的特征通常被称为marker traits
    • Copy 是标记特征的例子,意味着内存可以按位复制

    ⌾ Sized

    • 完全在明确控制外的一些 traits
    • Sized 由已知大小类型的编译器提供,要么是,要么不是

    Implementing Traits for Types — impl T for S { }

    impl ShowHex for Port { ... }
    • 特征是在'at some point'时为类型实现的
    • impl A for B 添加类型 B 到 trait 成员列表:
    ShowHex Trait
    Self
    Port
    • 从表面上看,你可以认为该类型因其成员资格而获得“badge”
    u8DevicePort
    impl { ... }impl { ... }impl { ... }
    ⌾Sized⌾Transport⌾Sized
    ⌾Clone⌾Clone
    ⌾Copy⌾ShowHex

    特征与接口

    接口

    • Java 中,用户Alice创建接口 Eat
    • 当用户Bob创建新类型 Venison,他必须决定是否让新类型实现 Eat
    • 换句话说,所有成员资格必须在类型定义期间详尽地说明
    • 当使用新类型 Venison 时,用户Santa可以利用 Eat 提供的以下行为:
    // Santa imports `Venison` to create it, can `eat()` if he wants.
    import food.Venison;
    
    new Venison("rudolph").eat();

    特征

    • Rust 中,用户Alice创建特征 Eat
    • 用户Bob创建新类型 Venison,并且决定不去实现 Eat,他可能甚至都不知道 Eat 的存在
    • 某用户后来决定添加 EatVenison 将是一个非常好的主意
    • 当使用 Venison 时,用户Santa必须单独导入 Eat
    // Santa needs to import `Venison` to create it, and import `Eat` for trait method.
    use food::Venison;
    use tasks::Eat;
    
    // Ho ho ho
    Venison::new("rudolph").eat();

    *为了防止两个人实现不同的 Eat,Rust 编译器将限制选择为 Alice 或 Bob 其中的一个, 也就是说 impl Eat for Venison 可能只发生在 Venison crate 或 Eat crate 中。

    Go 翻译于官网描述

    Go 语言具有很强的表达能力,它简洁、清晰而高效。得益于其并发机制, 用它编写的程序能够非常有效地利用多核与联网的计算机,其新颖的类型系统则使程序结构变得灵活而模块化。 Go 代码编译成机器码不仅非常迅速,还具有方便的垃圾收集机制和强大的运行时反射机制。 它是一个快速的、静态类型的编译型语言,感觉却像动态类型的解释型语言。

    Go 常用语法

    env

    go -h
    go env
    go version # go1.20.1 darwin/amd64
    
    # GOROOT: Go 语言的安装路径
    # GOPATH: 工作区目录的路径(第三方包保存的目录),bin, pkg(安装后的 *.a 文件, archive file), src
    # GOBIN: Go 可执行文件的路径
    
    go build # 构建
    go install # 安装
    
    go build -a # 不但目标代码包总是会被编译,它依赖的代码包也总会被编译,即使依赖的是标准库中的代码包也是如此。
    go build -n # 只查看,不执行
    go build -x # 查看执行详情
    go build -v # 查看编译的代码包的名称
    

    Hello, Go!

    // hello.go  The program entry main function must be in a code package named main.
    package main
    
    import (
      "fmt"
    )
    
    func main() {
      str := "world"
      text := fmt.Sprintf("hello %s! AwesomeProgram.", str)
      fmt.Println(text)
    }
    

    run

    // run
    go run hello.go
    go build hello.go; ./hello
    

    Command Argv

    package main
     
    import (
    	"flag"
    	"fmt"
    )
    var name string
    
    func init() {
    	flag.StringVar(&name, "name", "everyone", "The greeting object.")
    }
    
    func main() {
    	flag.Parse()
    	fmt.Printf("Hello, %s!", name)
    }
    
    //
    go run test.go -h
    go run test.go -name=123 // Hello, 123!
    

    init

    package main
    
    import "fmt"
    
    func init() {
      fmt.Println("init 1")
    }
    
    func main() {
      fmt.Println("main")
    }
    
    func init() {
      fmt.Println("init 2")
    }
    
    // init 1
    // init 2
    // main
    

    import

    import format "fmt" // format.Println()
    import "fmt" // fmt.Println()
    import . "fmt" // use Println directly, not recommended
    import _ "net/http/pprof" // used to load pprof package, it's init function will be called.
    

    struct

    type Book struct {
      title, author string
      pages         int
    }
    
    book = Book{author: "Book author", pages: 256, title: "Book Name"}
    // or
    book = Book{}
    fmt.Println(book.title)
    

    Array, Slice, Map

    [100]Book // Array, with element type Book
    
    []Book // Slide, with element type Book
    
    map[string]Book // Map, with key value: string - Book 
    
    books := [...]Book {
      {title: "title1"},
      {title: "title2"},
      {title: "title3"},
    }
    len(books) // 3
    cap(books) // 3
    

    数组

    var number_array [3]int // [0, 0, 0]
    append(number_array, 1) // Error
    number_array[0] = 1 // [1, 0, 0]
    

    切片

    var number_slice []int // []
    // Recreate to append
    number_slice = append(number_slice, 1) // [1]
    // Create a new slice from an array
    some_numbers := number_array[0:1] // [0]
    
    s0 := []int{1, 2, 3}
    s1 := append(s0, 4, 5)
    
    fmt.Println(s0, cap(s0)) // [1, 2, 3] 3
    fmt.Println(s1, cap(s1)) // [1, 2, 3, 4, 5] 6
    // Note the cap is 6, not 5
    
    s3 := append(s0, s0...) // [1, 2, 3, 1, 2, 3]
    

    字典

    m := map[string]int{"abc": 123, "xyz": 789}
    n, is_exist := m["hello"] // 0 false
    m = nil
    fmt.Println(m["abc"]) // 0
    m["dfg"] = 456
    delete(m, "dfg")
    
    your_map := make(map[string]int)
    your_map["key"] = 1
    fmt.Println(your_map["key"]) // 1
    // Remove key
    delete(your_map, "key")
    

    make

    m := make(map[string]int)) // map[]
    
    s := make([]int, 3, 5)
    fmt.Println(s, len(s), cap(s)) // [0 0 0] 3 5
    

    iterate

    for key, element = range aContainer {
      // do something
    }
    

    clone

    sClone := append(s[:0:0], s...) // s is a slice
    
    // or
    var sClone []T
    if s != nil {
      sClone = make([]T, len(s))
      copy(sClone, s)
    }
    

    strings

    import (
      "strings"
    )
    
    len("hello")
    strings.HasPrefix("helloWorld", "hello") // true
    strings.Contains("something", "some") // true
    

    function parameters

    func Sum(values ...int64) (sum int64) {
      // values's type is []int64。
      sum = 0
      for _, v := range values {
        sum += v
      }
      return sum
    }
    Sum(2, 3, 5)
    

    JSON

    type Config struct {
        Name bool `json:"name"` // OK
    
        Name bool `json: "name"` // Error
        Name bool `json:name` // Error
    }
    

    循环

    names := []string{"a", "b", "c"} // [a b c]
    for i, name := range names {
      fmt.Printf("%d. %s", i+1, name)
    }
    // 1. a
    // 2. b
    // 3. c
    

    goroutine

    go say("hello") // run say in goroutine
    

    Channel

    ch := make(chan int, 10) // create channel
    
    close(ch) // close channel
    
    ch <- v // send v to ch
    
    v = <-ch // accept v from ch
    v, sentBeforeClosed = <-ch
    
    cap(ch) // capacity
    len(ch) // length
    
    go func(ch <-chan int) {
      ch <- 123 // send 123 to ch
    }
    go func(ch chan<- int) {
      n := <-ch // get value from ch
    }
    

    Loop Channel

    for x, ok := <-c; ok; x, ok = <-c {
      fmt.Println(x)
    }
    
    for v := range aChannel {
      // do something
    }
    // equivalent
    for {
      v, ok = <-aChannel
      if !ok {
        break
      }
      // do something
    }
    

    select-case

    package main
    
    import "fmt"
    
    func main() {
      c := make(chan string, 2)
      trySend := func(v string) {
        select {
        case c <- v:
        default: // 如果c的缓冲已满,则执行默认分支。
        }
      }
      tryReceive := func() string {
        select {
        case v := <-c: return v
        default: return "-" // 如果c的缓冲为空,则执行默认分支。
        }
      }
      trySend("Hello!") // 发送成功
      trySend("Hi!")    // 发送成功
      trySend("Bye!")   // 发送失败,但不会阻塞。
      // 下面这两行将接收成功。
      fmt.Println(tryReceive()) // Hello!
      fmt.Println(tryReceive()) // Hi!
      // 下面这行将接收失败。
      fmt.Println(tryReceive()) //
    }
    

    methods

    type Book struct {
      pages int
    }
    func (b Book) Pages() int {
      return b.pages
    }
    func (b *Book) SetPages(pages int) {
      b.pages = pages
    }
    
    var book Book
    book.SetPages(123) // = (*Book).SetPages(&book, 123)
    book.Pages() // = Book.Pages(book)
    

    接口

    // res.Data is <interface {}>, res.Data > data is <[]interface {}>
    // 断言res.Data是一个interface{}类型的切片
    items, ok := res.Data.([]interface{}) 
    if !ok {
        return
    }
    
    for i, item := range items {
    	// 断言item是一个interface{}类型的map
        data, ok := item.(map[string]interface{}) 
        if !ok {
            continue 
        }
    
        id := data["id"]
    	// 断言data["id"]是一个float64值
        if idFloat, ok := id.(float64); ok {
            // use idFloat 
        } else {
            // use default id
        }
    }
    

    使用 Delve 调试

    # https://github.com/go-delve/delve
    dlv debug app.go
    

    Debugger 内部

    # Set breakpoint
    break [path/filename].go:[line_num]
    
    # Run and should pauses at the breakpoint
    continue
    
    # Print variable
    print [variable_name]
    
    # Move to next line in the source
    next
    

    Gin

    // Read Request Body in JSON
    type GitHubInput struct {
      Zen string `json:"zen"`
    }
    var input GitHubInput
    if err := c.ShouldBindJSON(&input); err != nil {
      c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
      return
    }
    input.Zen
    
    // Read Header
    c.Request.Header.Get("X-GitHub-Event")
    
    // HTTP Response
    c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
    c.String(200, input.Zen)
    

    JavaScript

    JavaScript(JS)是一种具有函数优先特性的轻量级、解释型或者说即时编译型的编程语言。虽然作为 Web 页面中的脚本语言被人所熟知,但是它也被用到了很多非浏览器环境中,例如 Node.js、Apache CouchDB、Adobe Acrobat 等。进一步说,JavaScript 是一种基于原型、多范式、单线程的动态语言,并且支持面向对象、命令式和声明式(如函数式编程)风格。

    JavaScript 的动态特性包括运行时对象的构造、变量参数列表、函数变量、动态脚本创建(通过 eval)、对象内枚举(通过 for...in 和 Object 工具方法)和源代码恢复(JavaScript 函数会存储其源代码文本,可以使用 toString() 进行检索)。

    ECMAScript 语言规范(ECMAScript Language Specification)(ECMA-262)和ECMAScript 国际化 API 规范(ECMAScript Internationalization API specification)(ECMA-402)是 Javascript 的标准。当某个 ECMAScript 新特性的提案已经被一些浏览器实现时,MDN 上的文档或示例就可能会涉及到这些新特性。大多数时候,处在 stages 3 和 4 的新特性会被收录到文档中,且收录时间通常早于其正式发布的时间。

    不要将 JavaScript 与 Java 编程语言混淆——JavaScript 不是“解释型 Java”。虽然“Java”和“JavaScript”都是 Oracle 公司在美国和其他国家注册(或未注册)的商标,但是这两门语言在语法、语义与用途方面有相当大的不同。

    TypeScript

    始于JavaScript,归于JavaScript

    TypeScript从今天数以百万计的JavaScript开发者所熟悉的语法和语义开始。使用现有的JavaScript代码,包括流行的JavaScript库,并从JavaScript代码中调用TypeScript代码。

    强大的工具构建 大型应用程序

    类型允许JavaScript开发者在开发JavaScript应用程序时使用高效的开发工具和常用操作比如静态检查和代码重构。

    类型是可选的,类型推断让一些类型的注释使你的代码的静态验证有很大的不同。类型让你定义软件组件之间的接口和洞察现有JavaScript库的行为。

    先进的 JavaScript

    TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自2015年的ECMAScript和未来的提案中的特性,比如异步功能和Decorators,以帮助建立健壮的组件。

    这些特性为高可信应用程序开发时是可用的,但是会被编译成简洁的ECMAScript3(或更新版本)的JavaScript。

    JavaScript 常用语法

    Hello, JS!

    // 在控制台输出
    console.log('AwesomeProgram!');
    
    // 在网页输出
    document.write('AwesomeProgram!');
    
    // 在网页中创建元素进行输出
    let hello = document.createElement('h1');
    hello.innerText = 'AwesomeProgram!';
    document.body.appendChild(hello); 
    
    // 使用浏览器弹窗输出
    alert('AwesomeProgram!');
    

    解构赋值

    let [first, last] = ['Awesome', 'Program']
    // or
    let {first, last} = {
      first: 'Awesome',
      last: 'Program'
    }
    

    字符串操作

    "AwesomeProgram".includes("some") // true
    "AwesomeProgram".startsWith("Awesome") // true
    "AwesomeProgram".endsWith("Program") // true
    'Awe:some:Program'.split(':') // ["Awe", "some", "Program"]
    parseFloat("123").toFixed(2) // "123.00"
    

    箭头函数

    const getName = user => user.name
    const funcName = name => {
      // do something
      return name
    }
    

    数组遍历

    const numbers = [1, 2, 3]
    numbers.map(n => n * 2) // [2, 4, 6]
    numbers.filter(n => n % 2 === 0) // [2]
    numbers.reduce((prev, next) => prev + next, 0) // 6
    numbers.find(n => n > 2) // 3
    

    数组操作

    // Delete at index
    array.splice(index, 1)
    
    // Insert at index
    array.splice(index, 0, newItem)
    
    // check exist
    [1, 2, 3].includes(3) // true
    
    // find index
    [1, 2, 3].indexOf(3) // 2; return -1 if not found
    
    // concat
    let array3 = array1.concat(array2) // [1].concat([2]) is [1, 2]
    
    // new array
    let array4 = [1, 2, 3, 4, 5].slice(2, 4) // [3, 4]
    

    展开操作符

    const array1 = [1, 2]
    const array2 = [...array1, 3, 4] // [1, 2, 3, 4]
    
    const funcName = (x, ...params) => {
      console.log(x)
      console.log(params)
    }
    
    funcName(1, 2, 3, 4)
    // 1
    // [2, 3, 4]
    

    对象展开

    const options = {
      ...defaults,
      show: true
    }
    

    数组展开

    const array3 = [
      ...array1,
      ...array2,
      'newItem'
    ]
    

    遍历

    for (const i of [1, 2, 3]) {
      console.log(i)
    }
    // 1
    // 2
    // 3
    
    for (const [index, value] of ['Awesome', 'Program', '@git'].entries()) {
      console.log(index, value)
    }
    // 0 "Awesome"
    // 1 "Program"
    // 2 "@git"
    
    const obj = {part1: 'Awesome', part2: 'Program', part3: '@git'};
    for (const key in obj) {
      console.log(key, obj[key])
    }
    // or
    for (const [key, value] of Object.entries(obj)) {
      console.log(key, value)
    }
    // part1 Awesome
    // part2 Program
    // part3 @git
    

    创建 Promise

    const funcName = params => {
      return new Promise((resolve, reject) => {
        // ....
        // do something
        // if success
        resolve(result)
        // if fail
        reject(error)
      })
    }
    funcName('test')
      .then(result => {
        // ...
      })
      .catch(error => {
        // ...
      })
      .finally(() => {
        // ...
      })
    

    收集所有 Promise 返回

    let promises = []
    // func1 and func2 returns a promise
    promises.push(func1())
    promises.push(func2())
    
    Promise.all(promises).then(allResult => {
      let [result1, resul2] = allResult
      // ...
    })
    

    Async-await

    const funcName = async () => {
      const data = await fetchData()
      return data
    }
    
    await funcName()
    

    生成器

    function * countdown(n) {
      for (let i = n; i > 0; --i) {
        yield i
      }
    }
    [...countdown(3)] // [ 3, 2, 1 ]
    
    let gen = countdown(3)
    gen.next() // 3
    gen.next() // 2
    

    浏览器

    encodeURIComponent() // Encodes a URI into UTF-8
    decodeURIComponent() // Decodes
    

    window

    const formData = new window.FormData()
    formData.append('file', data)
    
    window.localStorage.getItem(key)
    window.localStorage.setItem(key, data)
    
    window.location.origin // "https://awesomeprogram.github.io/"
    window.location.hostname // "awesomeprogram.github.io"
    window.location.href // "https://awesomeprogram.github.io/js-ts/chapter_1_1.html"
    
    window.open("https://awesomeprogram.github.io/")
    
    window.addEventListener('resize', resizeHandler)
    window.removeEventListener('resize', resizeHandler)
    

    XMLHttpRequest

    // Download excel file
    const xhr = new window.XMLHttpRequest()
    const applicationType = 'application/vnd.ms-excel; charset=UTF-8'
    xhr.open('GET', url, true)
    xhr.responseType = 'blob'
    xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8')
    xhr.onload = function(e) {
      if (this.status === 200) {
        let blob = new window.Blob([this.response], { type: applicationType })
        let downloadUrl = window.URL.createObjectURL(blob)
        let a = document.createElement('a')
        a.href = downloadUrl
        a.download = fileName
        a.click()
      }
    }
    xhr.onreadystatechange = e => {
      if (xhr.readyState === 4) {
        // 
      } else if (xhr.readyState === 3) {
        //
      })
    }
    xhr.send()
    

    Email Validation

    // From https://stackoverflow.com/questions/46155/how-to-validate-an-email-address-in-javascript
    function validateEmail(email) {
      if (email === '') {
        return true
      }
      const re = /^(([^<>()[]\.,;:s@"]+(.[^<>()[]\.,;:s@"]+)*)|(".+"))@(([[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}])|(([a-zA-Z-0-9]+.)+[a-zA-Z]{2,}))$/;
      return re.test(String(email).toLowerCase());
    }
    

    TypeScript 常用语法

    编译到 JavaScript

    tsc --outFile sample.js Test.ts

    基本

    let name: string = 'name';
    let isDone: boolean = false;
    let count: number = 10;
    let sentence: string = `Hello, ${ name }.`
    let notSure: any = 1;
    
    let list1: number[] = [1, 2, 3];
    let list2: Array<number> = [1, 2, 3];
    
    let tupleX: [string, number];
    x = ['hello', 10]
    
    enum Color {Red, Green, Blue};
    let c: Color = Color.Green;
    
    let [first, ...rest] = [1, 2, 3, 4];
    // first: 1, rest: [2, 3, 4 ]
    
    // type assert
    let strLength: number = (<string>someValue).length;
    let strLength: number = (someValue as string).length;
    

    函数

    const params = (value: string) => {
      console.log(value);
    }
    
    const paramOr = (value: string | null) => {
      console.log(value);
    }
    paramOr('1'); // OK
    paramOr(null); // OK
    paramOr(1); // Error
    
    const paramArray = ([first, second]: [string, number]) => {
      console.log(first);
      console.log(second);
    }
    
    const returnValue = (): String =>  {
      return 'return a stirng';
    }
    

    接口

    interface Config {
      color?: string;
      width?: number;
      readonly y?: number;
      [propName: string]: any;
    }
    const funcName = (config: Config) => {
      console.log(config.test);
    }
    funcName({test: 'test prop'}); // test prop
    

    class Example {
      name: string; // default public
      private priaveName: string;
      protected protectedName: string;
      readonly readonlyName: string;
      private _fullName: string;
      static baseRoute = '/basePath';
      constructor(name: string) {
        this.name = name;
        this.priaveName = name;
        this.protectedName = name;
        this.readonlyName = name;
        this._fullName = name;
      }
      hello() {
        return `Hello, ${this.name}`;
      }
      get fullName(): string {
        return this._fullName;
      }
      set fullName(newName: string) {
        // do some check
        // ...
        this._fullName = newName;
      }
    }
    let test = new Example("world");
    console.log(test.hello()); // Hello, world
    test.priaveName; // Error
    
    class NewExample extends Example {
      hi() {
        return `Hi, ${super.hello()}`;
      }
      getProtectedName() {
        return this.protectedName
      }
    }
    
    let newTest = new NewExample("new world");
    console.log(newTest.hi()); // Hi, Hello, new world
    
    newTest.protectedName; // Error
    newTest.getProtectedName(); // OK
    
    
    // Abstract Class
    abstract class AbstractExample {
      abstract hi(): void;
      say(): void {
        console.log('say something');
      }
    }
    class ActualExample extends AbstractExample {
      hi() {
        return 'hi'
      }
    }
    

    命名空间

    namespace Validation {
        export interface StringValidator {
            isAcceptable(s: string): boolean;
        }
    }
    Validation.StringValidator;
    

    泛型

    class GenericNumber<T> {
        zeroValue: T;
        add: (x: T, y: T) => T;
    }
    
    let myGenericNumber = new GenericNumber<number>();
    myGenericNumber.zeroValue = 0;
    myGenericNumber.add = function(x, y) { return x + y; };
    

    声明

    # File ends with `.d.ts`
    declare var testNumber: number;
    declare function hi(greeting: string): void;
    declare namespace myNamespace {
      function hello(s: string): string;
      let count: number;
    }
    

    C语言

    C语言是一门面向过程的编译型语言,它的运行速度极快,仅次于汇编语言。C语言是计算机产业的核心语言,操作系统、硬件驱动、关键组件、数据库等都离不开C语言;不学习C语言,就不能了解计算机底层。C 语言是一种通用的高级语言,最初是由丹尼斯·里奇在贝尔实验室为开发 UNIX 操作系统而设计的。C 语言最开始是于 1972 年在 DEC PDP-11 计算机上被首次实现。在 1978 年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)制作了 C 的第一个公开可用的描述,现在被称为 K&R 标准。UNIX 操作系统,C编译器,和几乎所有的 UNIX 应用程序都是用 C 语言编写的。由于各种原因,C 语言现在已经成为一种广泛使用的专业语言。

    • 易于学习。
    • 结构化语言。
    • 它产生高效率的程序。
    • 它可以处理底层的活动。
    • 它可以在多种计算机平台上编译。

    C 语言最初是用于系统开发工作,特别是组成操作系统的程序。由于 C 语言所产生的代码运行速度与汇编语言编写的代码运行速度几乎一样,所以采用 C 语言作为系统开发语言。下面列举几个使用 C 的实例:

    • 操作系统
    • 语言编译器
    • 汇编器
    • 文本编辑器
    • 打印机
    • 网络驱动器
    • 现代程序
    • 数据库
    • 语言解释器
    • 实体工具

    C11(也被称为C1X)指ISO标准ISO/IEC 9899:2011。在它之前的C语言标准为C99。

    • 对齐处理(Alignment)的标准化(包括_Alignas标志符,alignof运算符,aligned_alloc函数以及<stdalign.h>头文件)。

    • _Noreturn 函数标记,类似于 gcc 的 attribute((noreturn))。

    • _Generic 关键字。

    • 多线程(Multithreading)支持,包括:_Thread_local存储类型标识符,<threads.h>头文件,里面包含了线程的创建和管理函数。_Atomic类型修饰符和<stdatomic.h>头文件。

    • 增强的Unicode的支持。基于C Unicode技术报告ISO/IEC TR 19769:2004,增强了对Unicode的支持。包括为UTF-16/UTF-32编码增加了char16_t和char32_t数据类型,提供了包含unicode字符串转换函数的头文件<uchar.h>。

    • 删除了 gets() 函数,使用一个新的更安全的函数gets_s()替代。

    • 增加了边界检查函数接口,定义了新的安全的函数,例如 fopen_s(),strcat_s() 等等。

    • 增加了更多浮点处理宏(宏)。

    • 匿名结构体/联合体支持。这个在gcc早已存在,C11将其引入标准。

    • 静态断言(Static assertions),_Static_assert(),在解释 #if 和 #error 之后被处理。

    • 新的 fopen() 模式,("…x")。类似 POSIX 中的 O_CREAT|O_EXCL,在文件锁中比较常用。

    • 新增 quick_exit() 函数作为第三种终止程序的方式。当 exit()失败时可以做最少的清理工作。

    C++

    C++ 由C语言发展而来,几乎完全兼容C语言;换句话说,你编写的C语言代码几乎可以不加修改地用于 C++。

    C语言是面向过程的语言,C++ 在此基础上增加了面向对象以及泛型编程机制,因此 C++ 更适合大中型程序的开发。然而,C++ 并没有牺牲效率,如果不使用高级特性,它的效率和C语言几乎没有差异。

    C语言的语法

    Hello, C!

    #include <stdio.h>
    int main()
    {
        puts("Hello, AwesomeProgram!");
        return 0;
    }
    
    gcc main.c
    

    数据类型

    #include <stdio.h>
    #include <float.h>
     
    int main()
    {
        printf("int 存储大小 : %lu \n", sizeof(int)); // int 存储大小 : 4
        printf("float 存储最大字节数 : %lu \n", sizeof(float)); // float 存储最大字节数 : 4
        printf("float 最小值: %E\n", FLT_MIN ); // float 最小值: 1.175494E-38
        printf("float 最大值: %E\n", FLT_MAX ); // float 最大值: 3.402823E+38
        printf("精度值: %d\n", FLT_DIG ); // 精度值: 6
    
        int i = 10;
        float f = 3.14;
        double d = i + f; // 隐式将int类型转换为double类型
       
        double d = 3.14159;
        int i = (int)d; // 显式将double类型转换为int类型
    
        return 0;
    }
    

    变量

    #include <stdio.h>
     
    // 函数外定义变量 x 和 y
    int x;
    int y;
    int addtwonum()
    {
        // 函数内声明变量 x 和 y 为外部变量
        extern int x;
        extern int y;
        // 给外部变量(全局变量)x 和 y 赋值
        x = 1;
        y = 2;
        return x+y;
    }
     
    int main()
    {
        int result;
        // 调用函数 addtwonum
        result = addtwonum();
        
        printf("result 为: %d",result); // result 为: 3
        return 0;
    }
    
    // addtwonum.c 文件代码:
    #include <stdio.h>
    /*外部变量声明*/
    extern int x ;
    extern int y ;
    int addtwonum()
    {
        return x+y;
    }
    
    // test.c 文件代码:
    #include <stdio.h>
      
    /*定义两个全局变量*/
    int x=1;
    int y=2;
    int addtwonum();
    int main(void)
    {
        int result;
        result = addtwonum();
        printf("result 为: %d\n",result);
        return 0;
    }
    
    $ gcc addtwonum.c test.c -o main
    $ ./main  // result 为: 3
    

    常量

    #include <stdio.h>
     
    #define LENGTH 10   
    #define WIDTH  5
    #define NEWLINE '\n'
     
    int main()
    {
     
       int area;  
      
       area = LENGTH * WIDTH;
       printf("value of area : %d", area); // value of area : 50
       printf("%c", NEWLINE);
     
       return 0;
    }
    
    #include <stdio.h>
     
    int main()
    {
       const int  LENGTH = 10;
       const int  WIDTH  = 5;
       const char NEWLINE = '\n';
       int area;  
       
       area = LENGTH * WIDTH;
       printf("value of area : %d", area); // value of area : 50
       printf("%c", NEWLINE);
     
       return 0;
    }
    

    存储类

    // auto 存储类是所有局部变量默认的存储类。
    // 定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。
    {
       int mount;
       auto int month;
    }
    
    // register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。
    // register 存储类定义存储在寄存器,所以变量的访问速度更快,但是它不能直接取地址,因为它不是存储在 RAM 中的。在需要频繁访问的变量上使用 register 存储类可以提高程序的运行速度。
    {
       register int  miles;
    }
    
    // static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
    // static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
    // 全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。
    // 静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。
    // 以下实例演示了 static 修饰全局变量和局部变量的应用:
    #include <stdio.h>
     
    /* 函数声明 */
    void func1(void);
     
    static int count=10;        /* 全局变量 - static 是默认的 */
     
    int main()
    {
      while (count--) {
          func1();
      }
      return 0;
    }
     
    void func1(void)
    {
    /* 'thingy' 是 'func1' 的局部变量 - 只初始化一次
     * 每次调用函数 'func1' 'thingy' 值不会被重置。
     */                
      static int thingy=5;
      thingy++;
      printf(" thingy 为 %d , count 为 %d\n", thingy, count);
    }
    
     thingy 为 6 , count 为 9
     thingy 为 7 , count 为 8
     thingy 为 8 , count 为 7
     thingy 为 9 , count 为 6
     thingy 为 10 , count 为 5
     thingy 为 11 , count 为 4
     thingy 为 12 , count 为 3
     thingy 为 13 , count 为 2
     thingy 为 14 , count 为 1
     thingy 为 15 , count 为 0
    
    // extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,而只是指示编译器该变量在其他文件中定义。
    // extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
    // 当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
    // extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:
    // 第一个文件:main.c
    #include <stdio.h>
     
    int count ;
    extern void write_extern();
     
    int main()
    {
       count = 5;
       write_extern();
    }
    
    // 第二个文件:support.c
    #include <stdio.h>
     
    extern int count;
     
    void write_extern(void)
    {
       printf("count is %d\n", count);
    }
    
    $ gcc main.c support.c  // count is 5
    

    运算符

    • 算术运算符 下表显示了 C 语言支持的所有算术运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
    运算符描述实例
    +把两个操作数相加A + B 将得到 30
    -从第一个操作数中减去第二个操作数A - B 将得到 -10
    *把两个操作数相乘A * B 将得到 200
    /分子除以分母B / A 将得到 2
    %取模运算符,整除后的余数B % A 将得到 0
    ++自增运算符,整数值增加 1A++ 将得到 11
    --自减运算符,整数值减少 1A-- 将得到 9
    • 关系运算符 下表显示了 C 语言支持的所有关系运算符。假设变量 A 的值为 10,变量 B 的值为 20,则:
    运算符描述实例
    == 检查两个操作数的值是否相等,如果相等则条件为真。(A == B) 为假。
    != 检查两个操作数的值是否相等,如果不相等则条件为真。(A != B) 为真。
    > 检查左操作数的值是否大于右操作数的值,如果是则条件为真。(A > B) 为假。
    < 检查左操作数的值是否小于右操作数的值,如果是则条件为真。(A < B) 为真。
    >= 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。(A >= B) 为假。
    <= 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。(A <= B) 为真。
    • 逻辑运算符 下表显示了 C 语言支持的所有关系逻辑运算符。假设变量 A 的值为 1,变量 B 的值为 0,则:
    运算符描述实例
    &&称为逻辑与运算符。如果两个操作数都非零,则条件为真。(A && B) 为假。
    |称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。(A || B) 为真。
    !称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。!(A && B) 为真。
    • 位运算符 位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:
    pqp & qp | qp ^ q
    00000
    01011
    11110
    10011
    • 赋值运算符 下表列出了 C 语言支持的赋值运算符:
    运算符描述实例
    =简单的赋值运算符,把右边操作数的值赋给左边操作数C = A + B 将把 A + B 的值赋给 C
    +=加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数C += A 相当于 C = C + A
    -=减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数C -= A 相当于 C = C - A
    *=乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数C *= A 相当于 C = C * A
    /=除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数C /= A 相当于 C = C / A
    %=求模且赋值运算符,求两个操作数的模赋值给左边操作数C %= A 相当于 C = C % A
    <<=左移且赋值运算符C <<= 2 等同于 C = C << 2
    >>=右移且赋值运算符C >>= 2 等同于 C = C >> 2
    &=按位与且赋值运算符C &= 2 等同于 C = C & 2
    ^=按位异或且赋值运算符C ^= 2 等同于 C = C ^ 2
    |=按位或且赋值运算符C |= 2 等同于 C = C | 2
    • 杂项运算符 ↦ sizeof & 三元 下表列出了 C 语言支持的其他一些重要的运算符,包括 sizeof 和 ? :
    运算符描述实例
    sizeof()返回变量的大小。sizeof(a) 将返回 4,其中 a 是整数。
    &返回变量的地址。&a; 将给出变量的实际地址。
    *指向一个变量。*a; 将指向一个变量。
    ? :条件表达式如果条件为真 ? 则值为 X : 否则值为 Y
    • C 中的运算符优先级 下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算。
    类别运算符结合性
    后缀() [] -> . ++ - -从左到右
    一元+ - ! ~ ++ - - (type)* & sizeof从右到左
    乘除* / %从左到右
    加减+ -从左到右
    移位<< >>从左到右
    关系< <= > >=从左到右
    相等== !=从左到右
    位与 AND&从左到右
    位异或 XOR^从左到右
    位或 OR|从左到右
    逻辑与 AND&&从左到右
    逻辑或 OR||从左到右
    条件?:从右到左
    赋值= += -= *= /= %=>>= <<= &= ^= |=从右到左
    逗号,从左到右

    判断

    C 语言把任何非零和非空的值假定为 true,把零或 null 假定为 false。

    • 判断语句
    语句描述
    if 语句一个 if 语句 由一个布尔表达式后跟一个或多个语句组成。
    if...else 语句一个 if 语句 后可跟一个可选的 else 语句,else 语句在布尔表达式为假时执行。
    嵌套 if 语句您可以在一个 if 或 else if 语句内使用另一个 if 或 else if 语句。
    switch 语句一个 switch 语句允许测试一个变量等于多个值时的情况。
    嵌套 switch 语句您可以在一个 switch 语句内使用另一个 switch 语句。
    • ? : 运算符(三元运算符)
    #include<stdio.h>
     
    int main()
    {
        int num;
     
        printf("输入一个数字 : ");
        scanf("%d",&num);
        // Exp1 ? Exp2 : Exp3;
        (num%2==0)?printf("偶数"):printf("奇数");
    }
    

    循环

    • 循环类型
    循环类型描述
    while 循环当给定条件为真时,重复语句或语句组。它会在执行循环主体之前测试条件。
    for 循环多次执行一个语句序列,简化管理循环变量的代码。
    do...while 循环除了它是在循环主体结尾测试条件外,其他与 while 语句类似。
    嵌套循环您可以在 while、for 或 do..while 循环内使用一个或多个循环。
    • 循环控制语句 循环控制语句改变你代码的执行顺序。通过它你可以实现代码的跳转。C 提供了下列的循环控制语句:
    控制语句描述
    break 语句终止循环或 switch 语句,程序流将继续执行紧接着循环或 switch 的下一条语句。
    continue 语句告诉一个循环体立刻停止本次循环迭代,重新开始下次循环迭代。
    goto 语句将控制转移到被标记的语句。但是不建议在程序中使用 goto 语句。
    • 无限循环 如果条件永远不为假,则循环将变成无限循环。for 循环在传统意义上可用于实现无限循环。由于构成循环的三个表达式中任何一个都不是必需的,您可以将某些条件表达式留空来构成一个无限循环。
    #include <stdio.h>
     
    int main ()
    {
       for( ; ; )
       {
          printf("该循环会永远执行下去!\n");
       }
       return 0;
    }
    

    当条件表达式不存在时,它被假设为真。您也可以设置一个初始值和增量表达式,但是一般情况下,C 程序员偏向于使用 for(;;) 结构来表示一个无限循环。

    函数

    #include <stdio.h>
     
    /* 函数声明 */
    int max(int num1, int num2);
     
    int main ()
    {
       /* 局部变量定义 */
       int a = 100;
       int b = 200;
       int ret;
     
       /* 调用函数来获取最大值 */
       ret = max(a, b);
     
       printf( "Max value is : %d\n", ret ); // Max value is : 200
     
       return 0;
    }
     
    /* 函数返回两个数中较大的那个数 */
    int max(int num1, int num2) 
    {
       /* 局部变量声明 */
       int result;
     
       if (num1 > num2)
          result = num1;
       else
          result = num2;
     
       return result; 
    }
    
    • 函数参数 如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。当调用函数时,有两种向函数传递参数的方式:
    调用类型描述
    传值调用该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。
    引用调用通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

    默认情况下,C 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的实际参数。

    作用域规则

    • 局部变量 在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。下面是使用局部变量的实例。在这里,所有的变量 a、b 和 c 是 main() 函数的局部变量。
    #include <stdio.h>
     
    int main ()
    {
      /* 局部变量声明 */
      int a, b;
      int c;
     
      /* 实际初始化 */
      a = 10;
      b = 20;
      c = a + b;
     
      printf ("value of a = %d, b = %d and c = %d\n", a, b, c);
     
      return 0;
    }
    
    • 全局变量 全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。 全局变量可以被任何函数访问。也就是说,全局变量在声明后整个程序中都是可用的。下面是使用全局变量和局部变量的实例:
    #include <stdio.h>
     
    /* 全局变量声明 */
    int g;
     
    int main ()
    {
      /* 局部变量声明 */
      int a, b;
     
      /* 实际初始化 */
      a = 10;
      b = 20;
      g = a + b;
     
      printf ("value of a = %d, b = %d and g = %d\n", a, b, g);
     
      return 0;
    }
    
    • 形式参数 函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用。下面是一个实例:
    #include <stdio.h>
     
    /* 全局变量声明 */
    int a = 20;
     
    int main ()
    {
      /* 在主函数中的局部变量声明 */
      int a = 10;
      int b = 20;
      int c = 0;
      int sum(int, int);
     
      printf ("value of a in main() = %d\n",  a);
      c = sum( a, b);
      printf ("value of c in main() = %d\n",  c);
     
      return 0;
    }
     
    /* 添加两个整数的函数 */
    int sum(int a, int b)
    {
        printf ("value of a in sum() = %d\n",  a);
        printf ("value of b in sum() = %d\n",  b);
     
        return a + b;
    }
    
    // value of a in main() = 10
    // value of a in sum() = 10
    // value of b in sum() = 20
    // value of c in main() = 30
    
    • 全局变量与局部变量在内存中的区别:

    全局变量保存在内存的全局存储区中,占用静态的存储单元; 局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。

    • 初始化局部变量和全局变量

    当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化,如下所示:

    数据类型初始化默认值
    int0
    char'\0'
    float0
    double0
    pointerNULL

    正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量会导致一些在内存位置中已经可用的垃圾值。

    数组

    #include <stdio.h>
     
    int main ()
    {
       int n[ 10 ]; /* n 是一个包含 10 个整数的数组 */
       int i,j;
     
       /* 初始化数组元素 */         
       for ( i = 0; i < 10; i++ )
       {
          n[ i ] = i + 100; /* 设置元素 i 为 i + 100 */
       }
       
       /* 输出数组中每个元素的值 */
       for (j = 0; j < 10; j++ )
       {
          printf("Element[%d] = %d\n", j, n[j] );
       }
     
       return 0;
    }
    
    //   Element[0] = 100
    //   Element[1] = 101
    //   Element[2] = 102
    //   Element[3] = 103
    //   Element[4] = 104
    //   Element[5] = 105
    //   Element[6] = 106
    //   Element[7] = 107
    //   Element[8] = 108
    //   Element[9] = 109
    

    在 C 中,数组是非常重要的,我们需要了解更多有关数组的细节。下面列出了 C 程序员必须清楚的一些与数组相关的重要概念:

    概念描述
    多维数组C 支持多维数组。多维数组最简单的形式是二维数组。
    传递数组给函数您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。
    从函数返回数组C 允许从函数返回数组。
    指向数组的指针您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。

    enum(枚举)

    #include <stdio.h>
     
    enum DAY
    {
          MON=1, TUE, WED, THU, FRI, SAT, SUN
    };
     
    int main()
    {
        enum DAY day;
        day = WED;
        printf("%d",day); // 3
        return 0;
    }
    
    #include <stdio.h>
     
    enum DAY
    {
          MON=1, TUE, WED, THU, FRI, SAT, SUN
    } day;
    int main()
    {
        // 遍历枚举元素
        for (day = MON; day <= SUN; day++) {
            printf("枚举元素:%d \n", day);
        }
    }
    
    // 枚举元素:1 
    // 枚举元素:2 
    // 枚举元素:3 
    // 枚举元素:4 
    // 枚举元素:5 
    // 枚举元素:6 
    // 枚举元素:7
    
    以下枚举类型不连续,这种枚举无法遍历。
    
    enum
    {
        ENUM_0,
        ENUM_10 = 10,
        ENUM_11
    };
    
    #include <stdio.h>
    #include <stdlib.h>
    int main()
    {
     
        enum color { red=1, green, blue };
     
        enum  color favorite_color;
     
        /* 用户输入数字来选择颜色 */
        printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
        scanf("%u", &favorite_color);
     
        /* 输出结果 */
        switch (favorite_color)
        {
        case red:
            printf("你喜欢的颜色是红色");
            break;
        case green:
            printf("你喜欢的颜色是绿色");
            break;
        case blue:
            printf("你喜欢的颜色是蓝色");
            break;
        default:
            printf("你没有选择你喜欢的颜色");
        }
     
        return 0;
    }
    
    // 请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 1
    // 你喜欢的颜色是红色
    
    #include <stdio.h>
    #include <stdlib.h>
     
    int main()
    {
     
        enum day
        {
            saturday,
            sunday,
            monday,
            tuesday,
            wednesday,
            thursday,
            friday
        } workday;
     
        int a = 1;
        enum day weekend;
        weekend = ( enum day ) a;  //类型转换
        //weekend = a; //错误
        printf("weekend:%d",weekend); // weekend:1
        return 0;
    }
    

    指针

    #include <stdio.h>
     
    int main ()
    {
       int  var = 20;   /* 实际变量的声明 */
       int  *ip;        /* 指针变量的声明 */
     
       ip = &var;  /* 在指针变量中存储 var 的地址 */
     
       printf("var 变量的地址: %p\n", &var  );
     
       /* 在指针变量中存储的地址 */
       printf("ip 变量存储的地址: %p\n", ip );
     
       /* 使用指针访问值 */
       printf("*ip 变量的值: %d\n", *ip );
     
       return 0;
    }
    
    // var 变量的地址: 0x7ffeeef168d8
    // ip 变量存储的地址: 0x7ffeeef168d8
    // *ip 变量的值: 20
    
    • NULL 指针
    #include <stdio.h>
     
    int main ()
    {
       int  *ptr = NULL;
     
       printf("ptr 的地址是 %p\n", ptr  ); // ptr 的地址是 0x0
     
       return 0;
    }
    

    如需检查一个空指针,您可以使用 if 语句,如下所示:

    if(ptr)     /* 如果 p 非空,则完成 */
    if(!ptr)    /* 如果 p 为空,则完成 */
    
    • 指针详解
    概念描述
    指针的算术运算可以对指针进行四种算术运算:++、--、+、-
    指针数组可以定义用来存储指针的数组。
    指向指针的指针C 允许指向指针的指针。
    传递指针给函数通过引用或地址传递参数,使传递的参数在调用函数中被改变。
    从函数返回指针C 允许函数返回指针到局部变量、静态变量和动态内存分配。

    函数指针

    #include <stdio.h>
     
    int max(int x, int y)
    {
        return x > y ? x : y;
    }
     
    int main(void)
    {
        /* p 是函数指针 */
        int (* p)(int, int) = & max; // &可以省略
        int a, b, c, d;
     
        printf("请输入三个数字:");
        scanf("%d %d %d", & a, & b, & c);
     
        /* 与直接调用函数等价,d = max(max(a, b), c) */
        d = p(p(a, b), c); 
     
        printf("最大的数字是: %d\n", d);
     
        return 0;
    }
    
    // 请输入三个数字:1 2 3
    // 最大的数字是: 3
    

    回调函数

    实例中 populate_array() 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。实例中我们定义了回调函数 getNextRandomValue(),它返回一个随机值,它作为一个函数指针传递给 populate_array() 函数。populate_array() 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。

    #include <stdlib.h>  
    #include <stdio.h>
     
    void populate_array(int *array, size_t arraySize, int (*getNextValue)(void))
    {
        for (size_t i=0; i<arraySize; i++)
            array[i] = getNextValue();
    }
     
    // 获取随机值
    int getNextRandomValue(void)
    {
        return rand();
    }
     
    int main(void)
    {
        int myarray[10];
        /* getNextRandomValue 不能加括号,否则无法编译,因为加上括号之后相当于传入此参数时传入了 int , 而不是函数指针*/
        populate_array(myarray, 10, getNextRandomValue);
        for(int i = 0; i < 10; i++) {
            printf("%d ", myarray[i]);
        }
        printf("\n");
        return 0;
    }
    
    // 16807 282475249 1622650073 984943658 1144108930 470211272 101027544 1457850878 1458777923 2007237709 
    

    字符串

    其实,您不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 \0 放在字符串的末尾。

    #include <stdio.h>
     
    int main ()
    {
       char site[7] = {'R', 'U', 'N', 'O', 'O', 'B', '\0'};
     
       printf("AwesomeProgram: %s\n", site ); // 菜鸟教程: RUNOOB
     
       return 0;
    }
    
    • C 中有大量操作字符串的函数:
    序号函数 & 目的
    1strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
    2strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
    3strlen(s1); 返回字符串 s1 的长度。
    4strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
    5strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
    6strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

    下面的实例使用了上述的一些函数:

    #include <stdio.h>
    #include <string.h>
     
    int main ()
    {
       char str1[16] = "awesome";
       char str2[16] = "program";
       char str3[16];
       int  len ;
     
       /* 复制 str1 到 str3 */
       strcpy(str3, str1);
       printf("strcpy( str3, str1) :  %s\n", str3 ); // strcpy( str3, str1) :  awesome
     
       /* 连接 str1 和 str2 */
       strcat( str1, str2);
       printf("strcat( str1, str2):   %s\n", str1 ); // strcat( str1, str2):   awesomeprogram
     
       /* 连接后,str1 的总长度 */
       len = strlen(str1);
       printf("strlen(str1) :  %d\n", len ); // strlen(str1) :  12
     
       return 0;
    }
    

    结构体

    一般情况下,结构体标签、结构体成员变量、结构体变量 这 3 部分至少要出现 2 个。以下为实例:

    //此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
    //同时又声明了结构体变量s1
    //这个结构体并没有标明其标签
    struct 
    {
        int a;
        char b;
        double c;
    } s1;
     
    //此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
    //结构体的标签被命名为SIMPLE,没有声明变量
    struct SIMPLE
    {
        int a;
        char b;
        double c;
    };
    //用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
    struct SIMPLE t1, t2[20], *t3;
     
    //也可以用typedef创建新类型
    typedef struct
    {
        int a;
        char b;
        double c; 
    } Simple2;
    //现在可以用Simple2作为类型声明新的结构体变量
    Simple2 u1, u2[20], *u3;
    

    结构体的成员可以包含其他结构体,也可以包含指向自己结构体类型的指针,而通常这种指针的应用是为了实现一些更高级的数据结构如链表和树等。

    //此结构体的声明包含了其他的结构体
    struct COMPLEX
    {
        char string[100];
        struct SIMPLE a;
    };
     
    //此结构体的声明包含了指向自己类型的指针
    struct NODE
    {
        char string[100];
        struct NODE *next_node;
    };
    

    如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:

    struct B;    //对结构体B进行不完整声明
     
    //结构体A中包含指向结构体B的指针
    struct A
    {
        struct B *partner;
        //other members;
    };
     
    //结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
    struct B
    {
        struct A *partner;
        //other members;
    };
    
    • 结构体变量的初始化
    #include <stdio.h>
     
    struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    } book = {"C 语言", "RUNOOB", "编程语言", 123456};
     
    int main()
    {
        printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
    }
    
    // title : C 语言
    // author: RUNOOB
    // subject: 编程语言
    // book_id: 123456
    
    • 访问结构体成员
    #include <stdio.h>
    #include <string.h>
     
    struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    };
     
    int main( )
    {
       struct Books Book1;        /* 声明 Book1,类型为 Books */
       struct Books Book2;        /* 声明 Book2,类型为 Books */
     
       /* Book1 详述 */
       strcpy( Book1.title, "C Programming");
       strcpy( Book1.author, "Nuha Ali"); 
       strcpy( Book1.subject, "C Programming Tutorial");
       Book1.book_id = 6495407;
     
       /* Book2 详述 */
       strcpy( Book2.title, "Telecom Billing");
       strcpy( Book2.author, "Zara Ali");
       strcpy( Book2.subject, "Telecom Billing Tutorial");
       Book2.book_id = 6495700;
     
       /* 输出 Book1 信息 */
       printf( "Book 1 title : %s\n", Book1.title); // Book 1 title : C Programming
       printf( "Book 1 author : %s\n", Book1.author); // Book 1 author : Nuha Ali
       printf( "Book 1 subject : %s\n", Book1.subject); // Book 1 subject : C Programming Tutorial
       printf( "Book 1 book_id : %d\n", Book1.book_id); // Book 1 book_id : 6495407
     
       /* 输出 Book2 信息 */
       printf( "Book 2 title : %s\n", Book2.title); // Book 2 title : Telecom Billing
       printf( "Book 2 author : %s\n", Book2.author); // Book 2 author : Zara Ali
       printf( "Book 2 subject : %s\n", Book2.subject); // Book 2 subject : Telecom Billing Tutorial
       printf( "Book 2 book_id : %d\n", Book2.book_id); // Book 2 book_id : 6495700
     
       return 0;
    }
    
    • 结构体作为函数参数
    #include <stdio.h>
    #include <string.h>
     
    struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    };
     
    /* 函数声明 */
    void printBook( struct Books book );
    int main( )
    {
       struct Books Book1;        /* 声明 Book1,类型为 Books */
       struct Books Book2;        /* 声明 Book2,类型为 Books */
     
       /* Book1 详述 */
       strcpy( Book1.title, "C Programming");
       strcpy( Book1.author, "Nuha Ali"); 
       strcpy( Book1.subject, "C Programming Tutorial");
       Book1.book_id = 6495407;
     
       /* Book2 详述 */
       strcpy( Book2.title, "Telecom Billing");
       strcpy( Book2.author, "Zara Ali");
       strcpy( Book2.subject, "Telecom Billing Tutorial");
       Book2.book_id = 6495700;
     
       /* 输出 Book1 信息 */
       printBook( Book1 );
     
       /* 输出 Book2 信息 */
       printBook( Book2 );
     
       return 0;
    }
    void printBook( struct Books book )
    {
       printf( "Book title : %s\n", book.title);
       printf( "Book author : %s\n", book.author);
       printf( "Book subject : %s\n", book.subject);
       printf( "Book book_id : %d\n", book.book_id);
    }
    
    // Book title : C Programming
    // Book author : Nuha Ali
    // Book subject : C Programming Tutorial
    // Book book_id : 6495407
    // Book title : Telecom Billing
    // Book author : Zara Ali
    // Book subject : Telecom Billing Tutorial
    // Book book_id : 6495700
    
    • 指向结构的指针
    #include <stdio.h>
    #include <string.h>
     
    struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    };
     
    /* 函数声明 */
    void printBook( struct Books *book );
    int main( )
    {
       struct Books Book1;        /* 声明 Book1,类型为 Books */
       struct Books Book2;        /* 声明 Book2,类型为 Books */
     
       /* Book1 详述 */
       strcpy( Book1.title, "C Programming");
       strcpy( Book1.author, "Nuha Ali"); 
       strcpy( Book1.subject, "C Programming Tutorial");
       Book1.book_id = 6495407;
     
       /* Book2 详述 */
       strcpy( Book2.title, "Telecom Billing");
       strcpy( Book2.author, "Zara Ali");
       strcpy( Book2.subject, "Telecom Billing Tutorial");
       Book2.book_id = 6495700;
     
       /* 通过传 Book1 的地址来输出 Book1 信息 */
       printBook( &Book1 );
     
       /* 通过传 Book2 的地址来输出 Book2 信息 */
       printBook( &Book2 );
     
       return 0;
    }
    void printBook( struct Books *book )
    {
       printf( "Book title : %s\n", book->title);
       printf( "Book author : %s\n", book->author);
       printf( "Book subject : %s\n", book->subject);
       printf( "Book book_id : %d\n", book->book_id);
    }
    
    // Book title : C Programming
    // Book author : Nuha Ali
    // Book subject : C Programming Tutorial
    // Book book_id : 6495407
    // Book title : Telecom Billing
    // Book author : Zara Ali
    // Book subject : Telecom Billing Tutorial
    // Book book_id : 6495700
    

    共用体

    共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。您可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

    #include <stdio.h>
    #include <string.h>
     
    union Data
    {
       int i;
       float f;
       char  str[20];
    };
     
    int main( )
    {
       union Data data;        
     
       printf( "Memory size occupied by data : %d\n", sizeof(data)); // Memory size occupied by data : 20
     
       return 0;
    }
    
    • 访问共用体成员
    #include <stdio.h>
    #include <string.h>
     
    union Data
    {
       int i;
       float f;
       char  str[20];
    };
     
    int main( )
    {
       union Data data;        
     
       data.i = 10;
       data.f = 220.5;
       strcpy( data.str, "C Programming");
     
       printf( "data.i : %d\n", data.i); // data.i : 1917853763
       printf( "data.f : %f\n", data.f); // data.f : 4122360580327794860452759994368.000000
       printf( "data.str : %s\n", data.str); // data.str : C Programming
     
       return 0;
    }
    

    在这里,我们可以看到共用体的 i 和 f 成员的值有损坏,因为最后赋给变量的值占用了内存位置,这也是 str 成员能够完好输出的原因。现在让我们再来看一个相同的实例,这次我们在同一时间只使用一个变量,这也演示了使用共用体的主要目的:

    #include <stdio.h>
    #include <string.h>
     
    union Data
    {
       int i;
       float f;
       char  str[20];
    };
     
    int main( )
    {
       union Data data;        
     
       data.i = 10;
       printf( "data.i : %d\n", data.i); // data.i : 10
       
       data.f = 220.5;
       printf( "data.f : %f\n", data.f); // data.f : 220.500000
       
       strcpy( data.str, "C Programming");
       printf( "data.str : %s\n", data.str); // data.str : C Programming
     
       return 0;
    }
    

    位域

    有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例如在存放一个开关量时,只有 0 和 1 两种状态,用 1 位二进位即可。为了节省存储空间,并使处理简便,C 语言又提供了一种数据结构,称为"位域"或"位段"。

    所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。

    典型的实例:

    1. 用 1 位二进位存放一个开关量时,只有 0 和 1 两种状态。
    2. 读取外部文件格式——可以读取非标准的文件格式。例如:9 位的整数。

    type [member_name] : width ; 下面是有关位域中变量元素的描述:

    元素描述
    type只能为 int(整型),unsigned int(无符号整型),signed int(有符号整型) 三种类型,决定了如何解释位域的值。
    member_name位域的名称。
    width位域中位的数量。宽度必须小于或等于指定类型的位宽度。
    #include <stdio.h>
    #include <string.h>
     
    struct
    {
      unsigned int age : 3;
    } Age;
     
    int main( )
    {
       Age.age = 4;
       printf( "Sizeof( Age ) : %d\n", sizeof(Age) );
       printf( "Age.age : %d\n", Age.age );
     
       Age.age = 7;
       printf( "Age.age : %d\n", Age.age );
     
       Age.age = 8; // 二进制表示为 1000 有四位,超出
       printf( "Age.age : %d\n", Age.age );
     
       return 0;
    }
    

    当上面的代码被编译时,它会带有警告,当上面的代码被执行时,它会产生下列结果:

    // Sizeof( Age ) : 4
    // Age.age : 4
    // Age.age : 7
    // Age.age : 0
    
    #include <stdio.h>
     
    int main(){
        struct bs{
            unsigned a:1;
            unsigned b:3;
            unsigned c:4;
        } bit,*pbit;
        bit.a=1;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
        bit.b=7;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
        bit.c=15;    /* 给位域赋值(应注意赋值不能超过该位域的允许范围) */
        printf("%d,%d,%d\n",bit.a,bit.b,bit.c);    /* 以整型量格式输出三个域的内容 */
        pbit=&bit;    /* 把位域变量 bit 的地址送给指针变量 pbit */
        pbit->a=0;    /* 用指针方式给位域 a 重新赋值,赋为 0 */
        pbit->b&=3;    /* 使用了复合的位运算符 "&=",相当于:pbit->b=pbit->b&3,位域 b 中原有值为 7,与 3 作按位与运算的结果为 3(111&011=011,十进制值为 3) */
        pbit->c|=1;    /* 使用了复合位运算符"|=",相当于:pbit->c=pbit->c|1,其结果为 15 */
        printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);    /* 用指针方式输出了这三个域的值 */
    }
    

    上例程序中定义了位域结构 bs,三个位域为 a、b、c。说明了 bs 类型的变量 bit 和指向 bs 类型的指针变量 pbit。这表示位域也是可以使用指针的。

    typedef

    C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE: typedef unsigned char BYTE; 在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如: BYTE b1, b2; 您也可以使用 typedef 来为用户自定义的数据类型取一个新的名字。例如,您可以对结构体使用 typedef 来定义一个新的数据类型名字,然后使用这个新的数据类型来直接定义结构变量,如下:

    #include <stdio.h>
    #include <string.h>
     
    typedef struct Books
    {
       char  title[50];
       char  author[50];
       char  subject[100];
       int   book_id;
    } Book;
     
    int main( )
    {
       Book book;
     
       strcpy( book.title, "C 教程");
       strcpy( book.author, "Runoob"); 
       strcpy( book.subject, "编程语言");
       book.book_id = 12345;
     
       printf( "书标题 : %s\n", book.title); // 书标题 : C 教程
       printf( "书作者 : %s\n", book.author); // 书作者 : Runoob
       printf( "书类目 : %s\n", book.subject); // 书类目 : 编程语言
       printf( "书 ID : %d\n", book.book_id); // 书 ID : 12345
     
       return 0;
    }
    
    • typedef vs #define

    #define 是 C 指令,用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:

    1. typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
    2. typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
    #include <stdio.h>
     
    #define TRUE  1
    #define FALSE 0
     
    int main( )
    {
       printf( "TRUE 的值: %d\n", TRUE); // TRUE 的值: 1
       printf( "FALSE 的值: %d\n", FALSE); // FALSE 的值: 0
     
       return 0;
    }
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    
    

    C++语法

    Hello, C++!

    #include <stdio.h>
    int main()
    {
        puts("Hello, AwesomeProgram!");
        return 0;
    }
    

    gcc main.cpp -lstdc++ 或者 g++ main.cpp