网站备案 互联网信息,重庆百度推广排名优化,微信团购群网站怎样做,网站后台无法审核C-Rust-一次性掌握两门语言 简介特色数据类型声明常量、变量判断与循环函数抽象化的对象#xff1a;类与接口枚举模板与泛型Lambda匿名函数表达式 简介
本文主要是通过介绍C和Rust的基础语法达成极速入门两门开发语言。 C是在C语言的基础之上添加了面向对象的类、重载、模板等… C-Rust-一次性掌握两门语言 简介特色数据类型声明常量、变量判断与循环函数抽象化的对象类与接口枚举模板与泛型Lambda匿名函数表达式 简介
本文主要是通过介绍C和Rust的基础语法达成极速入门两门开发语言。 C是在C语言的基础之上添加了面向对象的类、重载、模板等特性和大量标准库以达到让使用者更高效地进行开发工作其适用场景主要是游戏应用、游戏引擎、数据库等底层架构开发而C更适合于系统内核、云搜索等算法和内存管理要求极高的程序。 Rust则是吸取了Typescript、Python、C、Go等各类前辈的语法特色创建出来的现代化语言最重要的特点就是所有权与借用的特性使之让很多内存问题在编译阶段甚至在编写代码时就能实时告知开发者避免了众多运行时问题。
特色
C最大的特点就是使用指针*和引用来自由地操作内存地址与值但开发者使用不当也很容易造成内存泄漏。
void main() {// 直接在栈上创建实例好处是随着作用域结束而自动销毁无需手动管理User user;// 在堆上创建实例并拿到指针好处是将随着程序一直存在当不使用后需要手动进行销毁User *user new User();// 手动销毁否则若程序一日未结束则该内存块一直被占据delete user;
}Rust最大的特点是所有权与借用的概念Rust中大部分变量要不通过引用来指向同个地址要不是直接移动而非复杂变量通过严格控制来减少内存泄漏的可能性。
fn main() {/// 直接在栈上创建实例let mut user User {};/// 将执行移动使user变得不可用let user2 user;/// 提供一个只读引用指向user2相同的内存块两者都可用但user3只是可读不可写let user3 user2;/// 提供一个可写引用指向user2相同的内存块这会让user2暂时失效直到user4的作用域结束同一时间只能存在一个可写引用let mut user4 mut user2;/// 不允许存在另一个可写引用// let mut user5 mut user2;
}可以简单地理解为Rust做的各种赋值操作背后都是采用C的std::move()函数将右值放入左值或者将左值移动到左值而C自身如果不使用std::move()函数的话赋值几乎都是直接复制浅拷贝/深拷贝一个新的这样在释放内存时可能因为多次释放导致空悬指针进而报错。
Rust还有另一特点是没有Null值而是使用枚举Option::Some/None来指代结果可能为空的情况类似于C的std::optional可选值Rust大量使用Option和基于此的Result来指定参数与返回值的可选性。
以下函数展示一个第二个值可能为空根据情况进行运算并返回结果
use std::error::Error;fn plus_val(value: i32, plus: Optioni32) - Resulti32, Error {match plus { Option::Some(v) {if v 1 {return Error(plus cant be 1.)}value * v},Option::None value * 2,}
}fn test() {let result1 plus_val(2, Option::None); // 2 * 2 4let result2 plus_val(2, Option::Some(4)); // 2 * 4 8// 可以用match语法搭配枚举值处理match result1 {Ok(r1) println!({}, r1),Err(error) panic!(r1 error!)}// 也可以直接链式处理此处只处理成功情况result2.and_then(|r2| println!({}, r2));
}数据类型
两者的数据类型相差不多甚至可以说大部分开发语言的数据类型都是这些。
类型\语言CRust说明布尔型boolbool可取值只有true和false用于指代一些状态开关有符号短整型short inti32Rust使用字母i加上比特位数来表示有符号的整数类型整型变量的默认类型更短的有符号整型i8,i16有符号整型inti64C最常使用的整型变量类型有符号长整型long inti128无符号整型unsigned intu64无符号短整型unsigned short intu32无符号长整型unsigned long intu128更短的无符号整型u8, u16字符charchar注意C的字符是12个字节而Rust的字符是14个字节占据更大空间也意味着嫩表示更广的字符集或者更多字符字符串不可变const char*strC的不可变是通过指定一个常量字符指针指向字面量所在地址而Rust的定义类似是定义一个引用指向字面量地址只不过依旧可以修改引用指向的地址即重新赋值字符串std::stringString可变字符串可随时对内部字符进行增删改注意Rust的String实际是对vec的封装空值voidunit表示没有值、空值、无值的情况常用于函数的返回值定长数组int[4] 或者 std::arrayint,4[i32;4]用于存放固定数量的相同类型值这里都定义了固定数量4类型为整型。由于长度在编译期就能确定意味着可以预先分配好元组std::tupleT1,T2,…,Tn(T1,T2,…,Tn)用于存放若干个不同类型但相同用途的值类似于数组版的结构体
其他比较常用的内置类型
可变长度数组C为std::vectorRust为vec这是这类开发语言中更常用的数组类型可变长度意味着更适用于实际场景根据输入进行长度变动。哈希表C为std::unorder_mapKeyType, ValueTypeRust为HashMapKeyType, ValueType大部分算法或者要求唯一键名的用途都会大量使用哈希表。
声明常量、变量
C属于传统的强类型编程语言由于认为类型是必须在编译前就定好静态类型因此类型都是先于变量名、函数名之前指定的如声明一个变量并给其赋予初始值
// 声明常量需要在类型前面加上const关键字类型为int
const int num1 1;
// 声明变量类型为int
int num2 1;Rust吸收了现代语言的特性虽然依旧认为类型是必须的但也尽可能通过自动推断减少开发者多余的类型定义工作
fn variable() {
// 声明常量使用let关键字此处自动推断类型为i32let num1 1;
// 声明变量在let关键字后添加mut关键字此处自动推断类型为i32let mut num2 1;
}以上声明等同于let num1: i32 1;let mut num2: i32 1;
判断与循环
C使用传统的if、else、switch作为判断使用for、while作为循环
void handler(std::vectorunsigned int list) {unsigned int max 0;for (auto *it list.begin(); p ! list.end(); p) {unsigned int temp *it;switch (temp) {case 1:temp 2;break;case 3:temp 4;break;default:break;}if (temp max)max temp;}
}Rust除了有if、else、while、for之外将switch换成了match减少了多余的语法还有永久循环loop并且都可以直接获取到返回值
fn handler(list: [u32]) {let max 0;for item in list {// 注意temp可以直接拿到返回值let temp match item { 1 2,3 4,_ item};max if temp max { temp } else { max };}
}函数
C定义函数是返回值类型在前函数名称在后
void main() {// 函数体
}Rust遵从现代语言方式用关键字表示这是一个函数声明并将类型放在后面
fn main() - unit {// 函数体
}抽象化的对象类与接口
C拥有struct结构体和class类但没有interface接口且这两者实际上除了一个默认声明为public公开一个为private隐藏之外并无其他区别。 C一般是在.h头文件中声明结构体和类作为抽象化的用途。
头文件中定义结构体和类声明
struct User {std::string name;bool enable;unsigned int level;
};
// 定义接口
struct IUserController {
private:User* user;
public:virtual void init();virtual User getInfo();
};cpp文件中实现类方法和使用结构体
// 实现接口在类里如果没有说明默认都是隐藏
class UserController: public IUserController {User* user;
public:void init() override {delete user;user new User{ Way,true, 1 };}User getInfo() override {return *user;};
}cpp文件中使用类
void main() {UserController uCtrler;uCtrler.init();std::cout uCtrler.getInfo() std::endl;
}Rust中虽然有结构体但并没有类结构体是同时作为结构体和类来使用的同时提供了trait特征作为类似接口的存在提供各种抽象化。由于没有头文件的概念需要自己分隔声明、实现、调用的文件结构。
定义结构体和类声明
// 声明结构体
struct User {name: String,enable: bool,level: u64,
}// 定义接口pub trait GetInfo {fn get_info() - User;
}// 声明类
struct UserController {user: User,
}// 实现接口
impl GetInfo for UserController {fn get_info(self) - User {self.user}
}// 定义类方法
impl UserController {fn init_user_info(mut self) {self.user User { name: String::from(Way), enable: true, level: 1 };}
}使用类
fn main() {let u_ctrler UserController { user: User { name: Way, enable: true, level: 1 } };u_ctrler.init();println!({}, u_ctrler.getInfo());
}要注意特征和接口有不一样的地方就是特征只能定义方法不能定义属性/状态即变量是因为Rust更期望做好内存管理如果这么定义那每个实现特征时都有大量额外内存开辟不符合Rust对内存的严格管理。 而C更自由地让开发者直接操作内存主要也和C的接口实际是通过抽象类与头文件声明来间接实现的只要开发者知道自己在做什么C就允许。
枚举
两者差别不大不过C的枚举是忽略枚举名称的使用时是直接使用属性名
enum PlayerStatus {PLAYER_MOVE,PLAYER_STOP
}void check_status(PlayerStatus status) {std::cout status PLAYER_MOVE std::endl;
}Rust除了遵循枚举名::属性名的使用方式之外还允许在内部继续定义结构体类似Kotlin的密封类这样可以让枚举完成更多更复杂的功能方便封装状态操作等。
enum PlayerStatus {MOVE,STOP,OTHER {name: String,value: i8}
}impl PlayerStatus::OTHER {fn equal(self, value: i8) - bool {self.value value}
}fn check_status(status: PlayerStatus) {println!({}, status PlayerStatus::MOVE);
}模板与泛型
C中只有一种类似于泛型但更加强大的特性template模板其不仅可以装填类型还能附加常量值使之能用于创建多个不同版本的类或函数。
以下例子使用模板创建一个包含长度为7的整形数组的类并传递名称
templateT, Size
class TheTemplate {T *arr[Size];std::string name;
public:TheTemplate(std::string *_name, T *_arr[Size]): name(*_name), arr(_arr) {};
}void create() {TheTemplate weekint, 7{星期, new int[7]{1,2,3,4,5,6,7}};TheTemplate monthint, 12{月份, new int[7]{1,2,3,4,5,6,7,8,9,10,11,12}};
}注意该类是在编译期就根据已确定的模板创建多个副本类抹除模板的存在类似这样
class TheTemplate1 {int *arr[7];std::string name;
public:TheTemplate1(std::string *_name, int *_arr[7]): name(*_name), arr(_arr) {};
}class TheTemplate2 {int *arr[12];std::string name;
public:TheTemplate1(std::string *_name, int *_arr[12]): name(*_name), arr(_arr) {};
}void create() {TheTemplate1 week{星期};TheTemplate2 month{月份};
}而Rust中泛型除了用于装载类型外还能用于装载生命周期注解并广泛用于创建不同类型的类与函数。 生命周期注解是Rust特有的具象化生命周期概念其用单引号加小写字母的形式表示如’ab。生命周期注解并不会直接改变变量、参数、返回值本身的生命周期而是类似类型一样对其本身的生命周期进行要求和约束。 以下同样是创建一个与上述例子相同的泛型结构体并使用生命周期’a来约束入参与返回值应该具有的生命周期
struct TheGeneratorT {arr: [T],name: String,
}implT TheGeneratorT {fn getWelcomea(self, word: a str) - a String {self.name word}
}fn create() {// 注意这里的泛型也可以不用填写Rust可以根据初始值自动推断let week TheGenerator::i8 {arr: [1, 2, 3, 4, 5, 6, 7],name: 星期,};let month TheGenerator {arr: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],name: 月份,};// 符合规则时不需要显示传递生命周期注解let result month.getWelcome(hello);println!({}, result);
}以上的getWelcome方法中约束了出参的生命周期必须达到与入参一样长要注意出参的生命周期并没有被缩短还是按照原本的规则这里的规则是出参使用了结构体的name属性因此出参与结构体实例化后的生命周期一致直到
Rust的泛型也是和C的模板一样会在编译期就生成多个结构体副本抹除泛型的存在。 Rust还有一个简化版的泛型约束先看看原本约束方式
fn SeeMeT: Add(a: T, b: T) - T {a b
}fn useMe() {SeeMe(1, 2);
}以上实现了对函数参数类型约束要求两个参数都为T类型并且实现了Add相加的特征也可以简写为以下方式
fn SeeMe(a: impl Add) - impl Add {a 1
}fn useMe() {SeeMe(2);
}要求泛型同时具有多种类型除了直接使用加号T: Add Copy的方式外还可以使用where从句
use std::fmt::Display;fn some_fnT, U(a: T, b: U) - i32 where T: Add Copy, U: Add Display PartialEq {if a b {println!({}, *b);}a b
}Lambda匿名函数表达式
C版采用类型可以使用std::functionParamType,ReturnType或者直接用auto表示
void lambda(int value) {auto func [](int){ std::cout x std::endl; };func(value);
}Rust版采用竖线开始并用于分隔参数列表和函数体
fn lambda(value: i32) {let func |x: i32| println!(fn!{}, x);func(value)
}