Rust - 字符串:str 与 String

在其他语言中,字符串通常都会比较简单,例如 “hello, world” 就是字符串章节的几乎全部内容了。

但是Rust中的字符串与其他语言有所不同,若带着其他语言的习惯来学习Rust字符串,将会波折不断。

所以最好先忘记脑中已有的关于字符串的知识,从头开始学习Rust字符串。

首先来看段逻辑简单的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

greet 函数接受一个字符串类型的 name 参数,然后打印到终端控制台中,非常好理解,那么这段代码能不能通过编译呢?

error[E0308]: mismatched types--> src/main.rs:3:11|
3 |     greet(my_name);|           ^^^^^^^|           ||           expected struct `std::string::String`, found `&str`|           help: try using a conversion method: `my_name.to_string()`error: aborting due to previous error

编译后发现报错了。编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。思考分析这个错误,它表明的信息是:我们写下的 let my_name = “Pascal”; 语句,my_name并不是 String 类型,而是 “&str” 类型。

相信如果按照其他编程语言的思路,此时就像遇到了无法理解的现象,我们需要抛开这种成见,从头开始学习Rust字符串的原理。

在学习字符串之前,先来看看什么是切片。

(一)Slice(切片)

如果你有其他编程语言的经验更好理解切片。

切片并不是 Rust 独有的概念,在 Go 语言中就非常流行。Rust切片它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。即“从一个大蛋糕中切出一小块来”。

我们以数组为例,从a数组中引用“ [2, 3] ”,语句是这么写的:

let a = [1, 2, 3, 4, 5];
let slice = &a[1..3];
println!("{:?}", slice); // Rust 使用 {:?} 来打印数组切片
//执行结果:[2, 3]

请先不要纠结切片的语法,接下来我们会详细说明。

而对于 Rust 字符串的切片而言,从原理上讲即 “对 String 类型中某一部分的引用”,语句是这么写的:

let s = String::from("hello world");let hello = &s[0..5];
let world = &s[6..11];

hello 没有引用整个 String s,而是引用了 s 的一部分内容,通过 [0…5] 的方式来指定。

这就是创建切片的语法,使用方括号包括的一个序列:[开始索引…终止索引],其中开始索引是切片中第一个元素的索引位置,而终止索引是最后一个元素后面的索引位置,也就是这是一个 右半开区间 [a, b)

在切片数据结构内部会保存开始的位置和切片的长度,其中长度是通过 终止索引 - 开始索引 的方式计算得来的。

对于 let world = &s[6…11]; 来说,world 是一个切片,该切片的指针指向 s 的第 7 个字节(索引从 0 开始, 6 是第 7 个字节),且该切片的长度是 5 个字节。

在使用 Rust 的 …range 序列语法 时,如果我们想从索引 0 开始,可以使用如下的方式,这两个写法是等效的:

let s = String::from("hello");let slice = &s[0..2];
let slice = &s[..2];

同样的,如果我们的的切片想要包含 String 的最后一个字节,则可以这样使用:

let s = String::from("hello");let len = s.len();let slice = &s[4..len];
let slice = &s[4..];

我们也可以截取完整的 String 切片:

let s = String::from("hello");let len = s.len();let slice = &s[0..len];
let slice = &s[..];

在对字符串使用切片语法时需要格外小心,切片的索引必须落在字符之间的边界位置,也就是 UTF-8 字符的边界。

例如中文在 UTF-8 中占用三个字节,下面的代码就会崩溃:

 let s = "中国人";let a = &s[0..2];println!("{}",a);  //索引是一个右半开区间 [a, b),[0..2]显然拿不到完整的字符

注意:slice 是一种引用,所以它没有所有权。

这点并不难理解,结合上面我们已经提到的例子,slice 它允许我们引用集合中部分连续的元素序列,而不是引用整个集合。

而引用并不涉及 所有权转移,所以它并没有所有权。

(二)字符串Slice:&str

现在我们已经了解了Rust中的切片。接下来我们要引出Rust中很重要的一个概念:在Rust中,字符串切片的类型标识就是 &str,而我们说的“字符串字面量”类型标识也是 &str

现在,我们就能回过头再审视一下我们最初写下的这段报错的代码:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: String) {println!("Hello, {}!", name);
}

编译器提示 greet 函数需要一个 String 类型的字符串,却传入了一个 &str 类型的字符串。

即:my_name的本质上其实是字符串字面量(&str类型),他并不是一个 动态字符串(String类型)。

也就是说,下面两行代码是写法不同但本质相同的代码:

let my_name = "Pascal";
let my_name: &str = "Pascal";

也许你会有一个问题,既然 my_name 是一个字符串切片引用,它引用的来源是谁呢,是从谁身上切的?

在 Rust 中,字符串字面量(如我们的 "Pascal")是在编译时由编译器处理的。当我们在代码中写入一个字符串字面量时,编译器会将其视为一个静态分配的字符串。这些字符串字面量通常存储在程序的只读数据段(在内存中)。

当你执行 let my_name = "Pascal"; 时,my_name 是一个对字符串字面量的引用。这个引用是一个字符串切片(&str),它指向存储在只读内存段中的 “Pascal” 字符串。

字符串切片 &str 是一个引用,它不拥有数据,只是指向数据。

切片指向了程序可执行文件中的某个点,这也是为什么字符串字面量是不可变的,因为 &str 是一个引用,引用是不能改变它的数据的。

根据这些内容,我们小小修改一下代码,就可以执行成功了:

fn main() {let my_name = "Pascal";greet(my_name);
}fn greet(name: &str) {println!("Hello, {}!", name);
}
//Hello, Pascal!

所有权、引用、借用、slice 这些概念都是为了让 Rust 程序在编译时确保了内存安全。

(三)Rust字符串的定义

顾名思义,字符串是由字符组成的连续集合,但是在上一节中我们提到过,Rust 中的字符是 Unicode 类型,因此每个字符占据 4 个字节内存空间,但是在字符串中不一样,字符串是 UTF-8 编码,也就是字符串中的字符所占的字节数是变化的(1 - 4),这样有助于大幅降低字符串所占用的内存空间。

Rust 在语言级别上,只有一种字符串类型: str

而它通常是以引用类型出现 &str,也就是上文提到的字符串切片。虽然语言级别只有上述的 str 类型,但是在标准库里,还有多种不同用途的字符串类型,其中使用最广的即是 String 类型,即动态字符串。

str 类型是硬编码进可执行文件,也无法被修改,但是 String 则是一个可增长、可改变且具有所有权的 UTF-8 编码字符串(动态字符串),当 Rust 用户提到字符串时,往往指的就是 Strin 类型和 &str 字符串切片类型,这两个类型都是 UTF-8 编码

除了 String 类型的字符串,Rust 的标准库还提供了其他类型的字符串,例如 OsString,OsStr,CsString 和CsStr 等,注意到这些名字都以 String 或者 Str 结尾了吗?

  • OsString 拥有字符串数据,而 OsStr 是其对应的借用类型。
  • CsString 拥有字符串数据,而 CsStr 是其对应的借用类型。

(四)动态字符串:String

(1)String 与 &str 的转换

在之前的代码中,已经见到好几种从 &str 类型生成 String 类型的操作:

let s = String::from("hello,world")
let s = "hello,world".to_string()

那么如何将 String 类型转为 &str 类型呢?答案很简单,取引用即可:

fn main() {let s = String::from("hello,world!");say_hello(&s);say_hello(&s[..]);say_hello(s.as_str()); //s.as_str() 创建了一个s字符串的引用
}fn say_hello(s: &str) {println!("{}",s);
}//结果:
//hello,world!
//hello,world!
//hello,world!
(2)字符串索引

在其它语言中,使用索引的方式访问字符串的某个字符或者子串是很正常的行为,但是在 Rust 中就会报错:

let s1 = String::from("hello");
let h = s1[0];
//报错:`String` cannot be indexed by `{integer}`

这是因为 Rust 强调内存安全和数据完整性。在 Rust 中,字符串是以 UTF-8 编码存储的,这意味着一个字符可能由多个字节组成。直接通过字节索引访问可能会导致字符串在逻辑上被错误地分割,从而破坏字符的完整性。

而要在 Rust 中安全地访问字符串中的字符或子串,我们可以使用以下几种方法,简单了解一下:

  1. 使用 chars() 方法:这个方法返回一个迭代器,迭代器中的每个元素都是字符串中的一个 Unicode 字符。你可以通过迭代器来访问每个字符。
let s = "hello";
for c in s.chars() {println!("{}", c);
}
  1. 使用 char_indices() 方法:这个方法返回一个迭代器,迭代器中的元素是 (byte_index, char) 形式的元组,其中 byte_index 是字符在字符串中的字节索引。
let s = "hello";
for (i, c) in s.char_indices() {println!("{}: {}", i, c);
}
  1. 使用 get() 方法:这个方法允许你通过字节索引来安全地访问字符串中的字符。如果索引有效,它会返回 Some(&char),否则返回 None。
let s = "hello";
if let Some(c) = s.get(0) {println!("{}", c);
} else {println!("Index out of bounds");
}
  1. 使用切片语法:如果你需要访问子串,可以使用切片语法 &s[start…end]。这里的 start 和 end 是字节索引,但 Rust 会确保切片操作不会在字符边界上切割。
let s = "hello";
let slice = &s[0..2]; // "he"
println!("{}", slice);
  1. 使用 split_off() 方法:如果你需要从字符串中分离出一部分,可以使用 split_off() 方法,它会返回一个新的 String,包含从指定索引开始的所有字符。
let mut s = String::from("hello");
let part = s.split_off(2); // s 现在是 "he", part 是 "llo"
println!("s: {}, part: {}", s, part);
(3)字符串操作

由于 String 是可变字符串,下面介绍 Rust String字符串的修改,添加,删除等常用方法。

(a)追加

在字符串尾部可以使用 push() 方法追加字符 char,也可以使用 push_str() 方法追加字符串字面量。这两个方法都是在原有的字符串上追加,并不会返回新的字符串

由于字符串追加操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰。

fn main() {let mut s = String::from("Hello ");s.push_str("rust");println!("追加字符串 push_str() -> {}", s);s.push('!');println!("追加字符 push() -> {}", s);
}//运行结果:
//追加字符串 push_str() -> Hello rust
//追加字符 push() -> Hello rust!
(b)插入

可以使用 insert() 方法插入单个字符 char,也可以使用 insert_str() 方法插入字符串字面量。与 push() 方法不同,这俩方法都需要传入两个参数,第一个参数是字符(串)插入位置的索引,第二个参数是要插入的字符(串)。

索引从 0 开始计数,如果越界则会发生错误。由于字符串插入操作要修改原来的字符串,则该字符串必须是可变的,即字符串变量必须由 mut 关键字修饰

fn main() {let mut s = String::from("Hello rust!");s.insert(5, ',');println!("插入字符 insert() -> {}", s);s.insert_str(6, " I like");println!("插入字符串 insert_str() -> {}", s);
}//运行结果:
//插入字符 insert() -> Hello, rust!
//插入字符串 insert_str() -> Hello, I like rust!
(c)替换

如果想要把字符串中的某个字符串替换成其它的字符串,那可以使用 replace() 方法。与替换有关的方法有三个。

  • replace
  • replacen
  • replace_range

replace 方法可用于 String&str 类型。

replace() 方法接收两个参数,第一个参数是要被替换的字符串,第二个参数是新的字符串。该方法会替换所有匹配到的字符串。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = String::from("I like rust. Learning rust is my favorite!");let new_string_replace = string_replace.replace("rust", "RUST");println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning RUST is my favorite!

replacen 方法可用于 String&str 类型。

replacen() 方法接收三个参数,前两个参数与 replace() 方法一样,第三个参数则表示替换的个数。

该方法是返回一个新的字符串,而不是操作原来的字符串

fn main() {let string_replace = "I like rust. Learning rust is my favorite!";let new_string_replacen = string_replace.replacen("rust", "RUST", 1);println!("{}",new_string_replace);
}//运行结果:
//I like RUST. Learning rust is my favorite!

replace_range 方法仅适用于 String 类型。

replace_range 接收两个参数,第一个参数是要替换字符串的范围(Range),第二个参数是新的字符串。

该方法是直接操作原来的字符串,不会返回新的字符串。该方法需要使用 mut 关键字修饰

fn main() {let mut string_replace_range = String::from("I like rust!");string_replace_range.replace_range(7..8, "R");println!("{}",new_string_replace);
}
//运行结果:
//I like Rust!
(d)删除

与字符串删除相关的方法有 4 个,他们分别是 pop()remove()truncate()clear()。这四个方法仅适用于 String 类型。

pop —— 删除并返回字符串的最后一个字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是一个 Option (枚举) 类型,如果字符串为空,则返回 None

fn main() {let mut string_pop = String::from("rust pop 中文!");let p1 = string_pop.pop();let p2 = string_pop.pop();dbg!(p1);dbg!(p2);dbg!(string_pop);
}/*
p1 = Some('!',
)
p2 = Some('文',
)
string_pop = "rust pop 中"
*/

PS:dbg!() 宏是一个非常有用的调试工具。它允许你在运行时打印变量的值,而不需要添加任何额外的打印语句。

remove —— 删除并返回字符串中指定位置的字符

该方法是直接操作原来的字符串。但是存在返回值,其返回值是删除位置的字符串,只接收一个参数,表示该字符起始索引位置。remove 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_remove = String::from("测试remove方法");println!("string_remove 占 {} 个字节",std::mem::size_of_val(string_remove.as_str()));// 删除第一个汉字string_remove.remove(0);// 下面代码会发生错误// string_remove.remove(1);// 直接删除第二个汉字// string_remove.remove(3);dbg!(string_remove);
}//string_remove 占 18 个字节
//string_remove = "试remove方法"

truncate —— 删除字符串中从指定位置开始到结尾的全部字符

该方法是直接操作原来的字符串。无返回值。该方法 truncate() 方法是按照字节来处理字符串的,如果参数所给的位置不是合法的字符边界,则会发生错误。

fn main() {let mut string_truncate = String::from("测试truncate");string_truncate.truncate(3);dbg!(string_truncate);
}//string_truncate = "测"

clear —— 清空字符串

该方法是直接操作原来的字符串。调用后,删除字符串中的所有字符,相当于 truncate() 方法参数为 0 的时候。

fn main() {let mut string_clear = String::from("string clear");string_clear.clear();dbg!(string_clear);
}//string_clear = ""
(e)连接

使用 + 或者 += 连接字符串

使用 + 或者 += 操作符连接字符串时,要求操作符右边的参数必须为字符串的切片引用(Slice)类型。

其实当调用 + 的操作符时,相当于调用了 std::string 标准库中的 add() 方法,这里 add() 方法的第二个参数是一个引用的类型。因此我们在使用 + 时, 必须传递切片引用类型,不能直接传递 String 类型。

+ 是返回一个新的字符串,所以变量声明可以不需要 mut 关键字修饰

fn main() {let string_append = String::from("hello ");let string_rust = String::from("rust");let result = string_append + &string_rust; // &string_rust会自动解引用为&strlet result = result + "!"; // `result + "!"` 中的 `result` 是不可变的println!("连接字符串 + -> {}", result);
}//连接字符串 + -> hello rust!

使用 format! 连接字符串

format! 这种方式适用于 String&strformat! 的用法与 print! 的用法类似。

fn main() {let s1 = "hello";let s2 = String::from("rust");let s = format!("{} {}!", s1, s2);println!("{}", s);
}//hello rust!

(五)String与&str总结

在 Rust 中,String&str 都是处理字符串数据的类型,但它们之间的关系和用途有所不同。以下是它们之间的关系和区别:

  1. 所有权与借用
    • String 拥有其数据,它是一个动态分配在堆上的可变字符串类型。这意味着 String 可以增长和缩小,并且可以被修改。
    • &str 是一个字符串切片,它是一个对字符串数据的不可变引用。它不拥有数据,而是借用了其他字符串数据的一部分或全部。
  2. 内存管理
    • String 需要管理其在堆上的内存,因此它需要负责分配和释放内存。这使得 String 可以动态地改变其大小。
    • &str 只是一个引用,它不需要管理内存,因为它指向的数据可能来自 String、字符串字面量或其他数据源。
  3. 可变性
    • String 是可变的,你可以更改其内容,如添加、删除或修改字符。
    • &str 是不可变的,你不能更改它指向的数据。如果你需要修改数据,你必须在 String 上进行操作。
  4. 使用场景
    • 当你需要一个可以修改的字符串时,使用 String
    • 当你需要读取或处理字符串数据,但不需要修改它时,使用 &str
  5. 转换
    • 你可以从 String 创建一个 &str 引用,只需通过引用 String 实例即可,如 &my_string
    • 你也可以从字符串字面量创建一个 &str 引用,因为字符串字面量本身就是 &str 类型。
    • &str 创建 String 可以通过 to_string() 方法或 String::from() 函数,这涉及到从不可变引用到可变数据的转换,可能需要复制数据。
  6. 生命周期
    • String 拥有其数据,因此它不受生命周期的限制。
    • &str 是一个引用,它依赖于数据的生命周期。如果 &str 引用的数据超出了作用域,那么 &str 引用将不再有效。

总的来说,String&str 在 Rust 中提供了灵活的字符串处理机制,允许你根据需要选择最合适的类型。String 提供了可变性和动态内存管理,而 &str 提供了对字符串数据的不可变引用,这两者在不同的场景下都非常有用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/54624.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

华为---代理ARP简介及示例配置

目录 1. 概念 2. 前提条件 3. 使用环境 4. 工作过程 5. 优点 6. 缺点 7. 示例配置 7.1 示例场景 7.2基本配置 7.3 配置端口隔离 7.4 开启代理ARP 7.4.1 VLAN内代理ARP 7.4.2 VLAN间代理ARP 7.4.3路由式ARP代理 1. 概念 代理ARP(Proxy ARP)&…

C#使用实体类Entity Framework Core操作mysql入门:从数据库反向生成模型

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github:codetoys,所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的,可以在任何平台上使用。 源码指引:github源…

matlab恢复默认窗口布局

1.点击主页,选择布局 2.选择默认,即可恢复到默认的窗口布局

LIN总线CAPL函数—— 设置LIN报文字节间隔长度(linSetInterByteSpace)

🍅 我是蚂蚁小兵,专注于车载诊断领域,尤其擅长于对CANoe工具的使用🍅 寻找组织 ,答疑解惑,摸鱼聊天,博客源码,点击加入👉【相亲相爱一家人】🍅 玩转CANoe&…

利士策分享,如何培养良好的工作习惯?

利士策分享,如何培养良好的工作习惯? 在这个快节奏、高压力的职场环境中,培养良好的工作习惯不仅关乎个人职业发展的顺畅度, 更是提升工作效率、保持身心健康的关键。 以下是一些实用的建议,帮助你在日常工作中逐步构…

智慧水利采砂船在线监控平台:构建高效、智能的河道采砂监管体系

随着科技的不断发展,水利行业的智慧化转型也日益受到重视。智慧水利采砂船在线监控平台便是这一转型的重要成果之一。该平台主要服务于水政执法人员,针对取得河道采砂许可证的采砂公司及采砂船,实施在线自动监控,旨在提高监管效率…

iptables限制网速

1、使用hashlimit来限速 #从eth0网卡进入INPUT链数据,使用模块hashlimit 限制网速为100kb/s或2mb/s,超过限制的数据包会被DROP。OUTPUT链同理,mode为srcip,有4个mode选项: srcip(默认匹配每个源地址IP,配置指定源地址…

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【上篇】

【STM32开发笔记】移植AI框架TensorFlow【上篇】 一、TFLM是什么?二、TFLM开源项目2.1 下载TFLM源代码2.2 TFLM基准测试说明2.3 TFLM基准测试命令 三、TFLM初步体验3.1 PC上运行Keyword基准测试3.2 PC上运行Person detection基准测试3.3 No module named numpy问题解…

保障电气安全的电气火灾监控系统主要组成有哪些?

电气火灾是什么? 电气火灾一般是指由于电气线路、用电设备、器具以及供配电设备出现故障性释放的热能:如高温、电弧、电火花以及非故障性释放的能量;如电热器具的炽热表面,在具备燃烧条件下引燃本体或其他可燃物而造成的火灾&…

递归基础训练-路径总和

路径总和 给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。 我们可以把之前的…

计算机组成原理(笔记4)

定点加减法运算 补码加法&#xff1a; 补码减法&#xff1a; 求补公式&#xff1a; 溢出的概念 在定点小数机器中,数的表示范围为|&#xff58;|<1。在运算过程中如出现大于1的现象,称为 “溢出”。 上溢&#xff1a;两个正数相加&#xff0c;结果大于机器所能表示的最…

数据结构-线性表的单链式存储结构图解及C语言实现

概念 链式存储&#xff1a;结点在存储器中的位置是任意的&#xff0c;即逻辑相邻的数据元素在物理上不一定相邻 链式存储结构也称非顺序映像或链式映像 图解 链式存储结构中结点一般有两个部分组成&#xff0c;即数据域(data)和指针域&#xff0c;数据域是用于存放数据的&…

开源ids snort (windows版)

Snort-IPS-on-Windows-main资源-CSDN文库 GitHub - eldoktor1/Snort-IPS-on-Windows: A comprehensive guide to installing and configuring Snort IPS on Windows, ensuring robust network security 手动打造Snortbarnyard2BASE可视化告警平台 - FreeBuf网络安全行业门户 …

JavaWeb--小白笔记07:servlet对表单数据的简单处理

这里的servlet对表单数据的处理是指使用IDEA创建web工程&#xff0c;再创建html和class文件进行连接&#xff0c;实现html创建一个表单网页&#xff0c;我们对网页中的表单进行填充&#xff0c;可以通过class文件得到网页我们填充的内容进行打印到控制台。 一登录系统页面---h…

Linux网络之UDP与TCP协议详解

文章目录 UDP协议UDP协议数据报报头 TCP协议确认应答缓冲区 超时重传三次握手其他问题 四次挥手滑动窗口流量控制拥塞控制 UDP协议 前面我们只是说了UDP协议的用法,但是并没有涉及到UDP协议的原理 毕竟知道冰箱的用法和知道冰箱的原理是两个层级的事情 我们首先知道计算机网…

怎么用gitee做一个图片仓库,在md文档中用这个图片网络地址,然后显示图片

痛因&#xff1a;我为什么要这样做&#xff0c;呃&#xff0c;我一开始图片都是存本地地址的&#xff0c;放在和这个md文档同级的assets文件夹下面&#xff0c;这样子确实当时很方便&#xff0c;复制粘贴什么也不用管&#xff0c;但是想把这个文档分享给别的人的时候&#xff0…

美信监控易的优势:长期稳定运行

美信监控易作为一款运维产品&#xff0c;其显著的优势在于能够长期稳定运行。在IT运维领域&#xff0c;系统的稳定性是至关重要的&#xff0c;它直接关系到企业的业务连续性和客户满意度。美信监控易通过其自研的数据库和先进的监测技术&#xff0c;确保了系统的高可用性&#…

HarmonyOS鸿蒙开发实战(5.0)悬浮窗拖拽和吸附动画实践

鸿蒙HarmonyOS NEXT开发实战往期文章必看&#xff08;持续更新......&#xff09; HarmonyOS NEXT应用开发性能实践总结 HarmonyOS NEXT应用开发案例实践总结合集 最新版&#xff01;“非常详细的” 鸿蒙HarmonyOS Next应用开发学习路线&#xff01;&#xff08;从零基础入门…

OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【Perf调测】

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ 持续更新中…… 基本概念 Perf为性能分析工具&#xff0c;依赖PMU&#xff08;Per…

Qt --- 常用控件的介绍 --- 其他控件

一、QPushButton QWidget中设计到的各种属性/函数/使用方法&#xff0c;针对接下来要介绍的Qt的各种控件都是有效的。 使用QPushButton表示一个按钮&#xff0c;这也是当前我们最熟悉的一个控件了。这个类继承了QAbstractButton&#xff0c;这个类是一个抽象类&#xff0c;是…