当前位置: 首页 > news >正文

认识所有权

专栏简介:本专栏作为Rust语言的入门级的文章,目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言,虽然历史没有C++、和python历史悠远,但是它的优点可以说是非常的多,既继承了C++运行速度,还拥有了Java的内存管理,就我个人来说,还有一个优点就是集成化的编译工具cargo,语句风格和C++极其相似,所以说我本人还是比较喜欢这个语言,特此建立这个专栏,作为学习的记录分享。

日常分享:每天努力一点,不为别的,只是为了日后,能够多一些选择,选择舒心的日子,选择自己喜欢的人!


目录

Rust独有的所有权

所有权

所有权的规则

String类型

内存与分配

 变量数据的移动

字面值

String类型

变量数据的克隆与拷贝

克隆

栈上数据的拷贝

所有权与函数

返回值与作用域

引用与借用

可变引用

悬垂引用

引用的规则

Slice类型

字符串字面值是slice

 字符串slice作为参数

其他类型的slice

 总结


Rust独有的所有权

所有权这个特性时Rust独有的,前面第一章我们说了,Rust语言集合了C++,java的优点,而为了解决C++中垃圾无法回收,容易造成内存泄漏的特点,Rust中提出了所有权这个概念。所以说,认识所有权才是掌握Rust的必不可少的部分。

所有权

Rust的核心功能之一是所有权。下面我们来认识认识所有权。

学习过c++和java的人应该知道,在这两种语言中,c++需要开发者自己分配和释放内存,这种情况下很多开发者会在使用了内存而不释放,导致内存泄漏。而Java则是解决了这种问题,他采用的是垃圾回收机制,在程序运行的时候自动的寻找不再使用的内存。而Rust使用的则是所有权管理,简单的来说,就是在程序编译时就会进行规格检查,提前检测出可能会存在内存泄漏等问题。其实这个有点和微软公司下的visual studio编译器很相似,对内存管理很严格。

提到栈,很多人应该能想到数据结构中的栈,栈的特点就是“先进后出”,栈的内存是连续的,存放数据总是按照顺序方式存入,而在栈中,存入的数据必须是大小固定的,且已经知道的,在C++中,在开辟内存空间的时候,一般都是在栈上开辟的,所以必须要指明数据类型,以此来告诉系统开辟的空间是固定且已知大小的。

我们定义的指针则是在堆区,因为我们是不知道具体的内存大小的,只能分配一个足够大的内存空间。。 堆是缺乏组织的:当向堆放入数据时,你要请求一定大小的空间。内存分配器(memory allocator)在堆的某处找到一块足够大的空位,把它标记为已使用,并返回一个表示该位置地址的 针(pointer)。

所以说,当我们定义数据的内存大小不会变的时候就在栈区开辟空间,如果不确定会不会改变数据的大小,则在堆区开辟空间。

所有权的规则

  1. Rust 中的每一个值都有一个 所有者owner)。
  2. 值在任一时刻有且只有一个所有者。
  3. 当所有者(变量)离开作用域,这个值将被丢弃。

记住,和其他语言一样,变量(所有者)值只在对应的作用域起作用,超出作用域,则无法使用。

String类型

这里我们将String类型提出来单独讲解,其实是因为String类型有点独特,它是可变的,不再是固定的,所以说,将String类单独提出来讲解,而不是归于字面值。String类型的数据被分配在堆区,所以大小可以随时变化。

fn main()
{let mut s=String::from("Hello"); //从字面值中获取字符串println!("{}", s); s.push_str(",World");  //在字符串s后追加字符串 println!("{}",s);
}

对于String类型中的一些函数, 大家可以去官网查看,这里就不过多介绍,至于String类型中函数的调用方法,会在后面将到。

内存与分配

前面说了,String类型是在堆区上分配内存的,最开始系统并不知道你需要多大的内存,就分配一个很大的内存空间,

  • 必须在运行时向内存分配器(memory allocator)请求内存。
  • 需要一个当我们处理完 String 时将内存返回给分配器的方法。

第一个部分是我们完成,在获取字面值的时候就请求分配内存,这在任何一门语言中均适用。

第二部分就需要根据不同语言的特性来进行操作了。比如说Java有垃圾回收机制,他会记录并清除不再使用的内存,我们不需要太多的关心内存。然而,像C++,Python这种没有垃圾回收机制的语言,需要自己去释放,比如说,C++中的析沟函数,会在类创建并使用完毕后自动释放内存,这是由于析沟函数自动调用了drop()函数。对于一些变量,我们也需要人为的去释放,使用drop()或者delete()函数。

但是在Rust中,当变量离开他所在的作用域的时候就会自动释放。Rust自动调用了特殊的函数,drop()函数,也可以自己手动调用。

fn main()
{let mut s=String::from("Hello"); //从字面值中获取字符串println!("{}", s); s.push_str(",World");  //在字符串s后追加字符串 println!("{}",s);drop(s); //释放掉内存,所有者s不再存在
}

 变量数据的移动

字面值

fn main()
{let s1=2;let s2=s1;println!("{}", s1);println!("{}", s2);
}

如上述例子,先定义了一个变量s1,然后绑定到数值5上,再定义一个变量s2绑定到s1上,也就是说,两个变量的值都是5.由于他们在定义的时候就已经指明了数值大小,所以这两个变量存放在栈区,所以是按照顺序存放。两个变量都有效。那如果是存放在堆区的又该如何?

String类型

fn main()
{let s1=String::from("Hello!");let s2=s1;println!("{}", s1);println!("{}", s2);
}

上述例子,运行你会发现报错了,显示s1值不存在,这是为什么?我们先来介绍一下String类型的数据的概念,然后在来解释这个问题。

我们以s1为例:

Four tables: two tables representing the stack data for s1 and s2, and each points to its own copy of string data on the heap.

 

 

string类型的数据由三部分组成: 一个指向存放字符串内容内存的指针,一个长度,和一个容量。这一组数据存储在栈上。右侧则是堆上存放内容的内存部分。

长度表示 String 的内容当前使用了多少字节的内存。容量是 String 从分配器总共获取了多少字节的内存。

当我们将s2绑定到s1上的时候:

图示

也就是说,只拷贝了栈上的数据,而堆上的数据则没有被拷贝,两个变量共同指向堆区数值。 回到上面的问题,为什么s1会不存在,这是由于,如果s1存在,那么将有两个变量同时拥有同一个字面值,在离开作用域时,系统会自动调用drop()函数,这时就会出现两个变量都会被释放,就出现了二次释放double free)的错误。这是不被允许的。所以Rust在s2绑定到s1上时,就将s1清除。从而不能再使用。

 Compiling number v0.1.0 (/home/ddb/文档/number)
error[E0382]: borrow of moved value: `s1`
 --> src/main.rs:5:18
  |
3 |   let s1=String::from("Hello!");
  |       -- move occurs because `s1` has type `String`, which does not implement the `Copy` trait
4 |   let s2=s1;
  |          -- value moved here
5 |   println!("{}", s1);
  |                  ^^ value borrowed here after move
  |
  = note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0382`.
error: could not compile `number` due to previous error

变量数据的克隆与拷贝

克隆

和其他语言一样,Rust也提供了克隆的方法。可以理解为深拷贝,将堆区的数据也拷贝一份。

fn main()
{let s1=String::from("Hello!");let s2=s1.clone();println!("{}", s1);println!("{}", s2);
}

栈上数据的拷贝

fn main()
{let s1=10;let s2=s1;println!("{}", s1);println!("{}", s2);
}

这里的一个例子,两个变量都是在栈上,所以他们的数据之间进行的是拷贝,两个变量都可以存在。可以实现copy的数据类型有:

  • 所有整数类型,比如 u32
  • 布尔类型,bool,它的值是 truefalse
  • 所有浮点数类型,比如 f64
  • 字符类型,char
  • 元组,当且仅当其包含的类型也都实现 Copy 的时候。比如,(i32, i32) 实现了 Copy,但 (i32, String) 就没有。

所有权与函数

将值传递给函数与给变量赋值的时候原理极为相似。所以,向函数传递值可能会移动或者复制。

fn main()
{let s=String::from("Hello, world!"); //s进入作用域String_move(s); //s的值移动到函数里,后面s不再有效let x=10;//x进入作用域number_copy(x); //x进入函数里,但是x是i32的,所以是复制进来,后面继续有效.println!("{}", x);
}
fn String_move(something: String) //something进入作用域
{println!("{}",something);
} //离开作用域,调用drop函数,something失效。
fn number_copy(number:i32) //number进入作用域,开始起作用
{println!("{}",number);
}//移出作用域

返回值与作用域

fn main()
{let s1=givs_ownership();let s2=String::from("test");let s3=takes_and_gives_back_ownership(s2); //s2被移动到函数中,返回值给s3,不再起作用.} //s1,s2,s3均离开作用域,不起作用,但s2已被移走,不会发生什么。
fn givs_ownership()->String
{let something=String::from("something");  // "something"进入作用域return something;//返回"something",并移出给调用的函数
}
fn takes_and_gives_back_ownership(a_String:String)->String
{a_String; //返回a_String,并移出给调用函数。
}

上面两个例子都是在说明所有权问题,一个值只能有一个所有者,所以,上面不同的函数,不同的数据类型之间使用的是不同的方法,有的是移动,有的是克隆。在这一点上,一定要注意区分。

注意,函数在返回值上面,可以返回多个值,但是是以元组的方式返回。

fn main()
{let s1=String::from("hello world");let (num,s2)=string_length(s1);println!("{} {}",num,s2);}
fn string_length(s:String)->(usize,String) 
{let length=s.len();return (length,s);
}

引用与借用

引用reference)像一个指针,因为它是一个地址,我们可以由此访问储存于该地址的属于其他变量的数据。 与指针不同,引用确保指向某个特定类型的有效值。

fn main() 
{let s=String::from("hello");let length:usize =string_length(&s);println!("the length of the string s is: {}",length);}
fn string_length(s: &String) -> usize
{return s.len();}

 上面函数中“&s”表示的是建立一个引用,指向s的数据值,但是并不拥有它,也就不会有所有权这个东西。所以在离开作用域后对原本的数据值没什么影响。

在Rust中,我们把创建一个引用的行为称为借用

可变引用

fn main() 
{let mut s=String::from("hello");let length:String =string_length(&mut s);println!("{}",s);println!("position is  {}",length);}
fn string_length(s: &mut String) ->String
{s.push_str(",world!");let m:String = String::from("Successfull!");return m;
}

上面的代码就是一个可变引用的实现,我们可以看到,可变引用就是在引用前加一个mut关键字。

上面我们说过,引用只是暂时借用数据,并不拥有所有权,所以,一个变量被创建了可变引用的时候,他只能被创建一次,否则会报错,这是由于,当你创建了多个可变引用的时候,他们都可以更改原本的数据,这个时候系统就不会知道那一个改变在前面,那一个在后面。就会出现混乱。

例如:

ddb@ddb-NBLK-WAX9X:~/文档/number$ cargo run
   Compiling number v0.1.0 (/home/ddb/文档/number)
error[E0499]: cannot borrow `sln` as mutable more than once at a time
 --> src/main.rs:5:10
  |
4 |   let m1=&mut sln;
  |          -------- first mutable borrow occurs here
5 |   let m2=&mut sln;
  |          ^^^^^^^^ second mutable borrow occurs here
6 |   println!("{},{}",m1,m2);
  |                    -- first borrow later used here

For more information about this error, try `rustc --explain E0499`.
error: could not compile `number` due to previous error

 出现上面这种情况,在官方的说法中是数据竞争。导致这种情况的原因:

  • 两个或更多指针同时访问同一数据。
  • 至少有一个指针被用来写入数据。
  • 没有同步数据访问的机制。

 除了不能同时拥有多个可变引用,还不能同时存在可变与不可变引用。举个简单的例子,你有一个玩具,有人来借,然后会原样还回,但是有人却改变了他的模样,你还回去的玩具和他的不一样,是不是就会出现矛盾。

 let mut sln=String::from("I like Rust!");let m1=&sln;let m2=&sln;let m3=&mut sln;println!("{},{},{}",m1,m2,m3);

像上面这种情况就会出现报错。

悬垂引用

在具有指针的语言中,很容易通过释放内存时保留指向它的指针而错误地生成一个 悬垂指针dangling pointer),所谓悬垂指针是其指向的内存可能已经被分配给其它持有者。相比之下,在 Rust 中编译器确保引用永远也不会变成悬垂状态:当你拥有一些数据的引用,编译器确保数据不会在其引用之前离开作用域。

fn main() {let reference_to_nothing = dangle();
}fn dangle() -> &String {let s = String::from("hello");&s
}

这里出现了报错,这是由于s在离开作用域后就失去了作用,不再有任何的作用,所以引用肯定也没作用了。

引用的规则

  • 在任意给定时间,要么 只能有一个可变引用,要么 只能有多个不可变引用。
  • 引用必须总是有效的。

Slice类型

slice 允许你引用集合中一段连续的元素序列,而不用引用整个集合。slice 是一类引用,所以它没有所有权。


fn main()
{let mut s=String::from("hello world");let world=first_world(&s);println!("{}", world);s.clear();  //清空字符串
}
fn first_world(s: &String) -> usize {let bytes = s.as_bytes();for (i, &item) in bytes.iter().enumerate() {if item == b' ' {return i;}}s.len()
}

看上面,上面的这段代码表示的是找到空格的所在位置,对于这个函数,使用了很多的库函数才求出索引值,那么有没有简单方法?

这里我们必须提到字符串slice,在Python中,我们可以直接使用索引值求的字符串中的部分字符串,而在Rust中,也有这种机制,


fn main() {let s = String::from("hello world");let hello = &s[0..5];let world = &s[6..11];println!("{},{}", world, hello);
}

和Python一样,他的索引方式也有很多,比如s[..3],s[..],s[2..]他们分别表示的是从0下标开始到3位置,从0开始到尾部结束,从2开始到结束。

字符串字面值是slice

前面我们说过,字符串字面值和String的区别,字符串字面值是不可变的,这是由于字符串字面值的数据类型就是&str。

例如:

let s = "Hello, world!";

这里 s 的类型是 &str:它是一个指向二进制程序特定位置的 slice。这也就是为什么字符串字面值是不可变的;&str 是一个不可变引用。

 字符串slice作为参数

slice也可以作为函数的返回数据类型和参数类型:


fn main() {let s=String::from("Hello,world");let mun=&s[..];let a=Slice_from(mun);println!("{}",a);
}
fn Slice_from(s:&str)->&str {let sl:String = String::from("Hello, world");let world = &sl[6..11];let hello=&s[0..5];println!("{}", world);return hello;
}

其他类型的slice

字符串 slice,正如你想象的那样,是针对字符串的。不过也有更通用的 slice 类型。考虑一下这个数组:

let a = [1, 2, 3, 4, 5]; 

就跟我们想要获取字符串的一部分那样,我们也会想要引用数组的一部分。我们可以这样做:

let a = [1, 2, 3, 4, 5]; 
let slice = &a[1..3];assert_eq!(slice, &[2, 3]); 

 总结

本节内容比较多,主体意思就是说在Rust中,Rust 语言提供了跟其他系统编程语言相同的方式来控制你使用的内存,但拥有数据所有者在离开作用域后自动清除其数据的功能意味着你无须额外编写和调试相关的控制代码。所以说,只要知道Rust语言的特有机制,学起来就会简单很多。

相关文章:

认识所有权

专栏简介:本专栏作为Rust语言的入门级的文章,目的是为了分享关于Rust语言的编程技巧和知识。对于Rust语言,虽然历史没有C、和python历史悠远,但是它的优点可以说是非常的多,既继承了C运行速度,还拥有了Java…...

恒盛策略:怎样看k线图实图详解如何看懂k线图?

K线图是股票剖析中常用的一种图表,它能够反映一段时间内股票价格的变化状况,对于股票投资者来说非常重要。但是,由于k线图并不是很好理解,很多投资者并不知道怎样看懂它。那么,咱们就从多个视点来看看怎样看k线图实图&…...

物联网的定义、原理、示例、未来

什么是物联网? 物联网 (IoT) 是指由嵌入传感器、软件和网络连接的物理设备、车辆、电器和其他物理对象组成的网络,允许它们收集和共享数据。这些设备(也称为“智能对象”)的范围可以从简单的“智能家居”设备(如智能恒温器)到可穿戴设备(如智能手表和支持RFID的服…...

Vue 整合 Element UI 、路由嵌套和参数传递(五)

一、整合 Element UI 1.1 工程初始化 使用管理员的模式进入 cmd 的命令行模式,创建一个名为 hello-vue 的工程,命令为: # 1、目录切换 cd F:\idea_home\vue# 2、项目的初始化,记得一路的 no vue init webpack hello-vue 1.2 安装…...

Git全栈体系(四)

第七章 IDEA 集成 Git 一、配置 Git 忽略文件 1. Eclipse 特定文件 2. IDEA 特定文件 3. Maven 工程的 target 目录 4. 问题 4.1 为什么要忽略他们? 与项目的实际功能无关,不参与服务器上部署运行。把它们忽略掉能够屏蔽 IDE 工具之间的差异。 4.2 …...

数据结构初阶--二叉树的链式结构

目录 一.二叉树链式结构的概念 二.二叉树链式结构的功能实现 2.1.链式二叉树的定义 2.2.链式二叉树的构建 2.3.链式二叉树的遍历 2.3.1.先序遍历 2.3.2.中序遍历 2.3.3.后序遍历 2.3.4.层序遍历 2.4.链式二叉树的求二叉树的结点数量 法一:计数法 法二&a…...

Taro UI中的AtTabs

TaroUI 中的 AtTabs 是一个用于创建标签页(tab)组件的组件。它提供了一种简单的方式来切换显示不同的内容。 AtTabs 的使用方式如下: 首先,引入 AtTabs 组件和必要的样式: import { AtTabs, AtTabsPane } from taro-ui import taro-ui/dis…...

ChatGPT FAQ指南

问:chatgpt 国内不开放注册吗? OpenAI不允许大陆和香港用户注册访问 openai可以的,chatGPT不行 以下国家IP不支持使用 中国(包含港澳台) 俄罗斯 乌克兰 阿富汗 白俄罗斯 委内瑞拉 伊朗 埃及 问:ChatGPT和GPT-3什么关系? GPT-3是OpenAI推出的AI大语言模型 ChatGPT是在G…...

在矩池云使用ChatGLM-6B ChatGLM2-6B

ChatGLM-6B 和 ChatGLM2-6B都是基于 General Language Model (GLM) 架构的对话语言模型,是清华大学 KEG 实验室和智谱 AI 公司于 2023 年共同发布的语言模型。模型有 62 亿参数,一经发布便受到了开源社区的欢迎,在中文语义理解和对话生成上有…...

7.2 手撕VGG11模型 使用Fashion_mnist数据训练VGG

VGG首先引入块的思想将模型通用模板化 VGG模型的特点 与AlexNet,LeNet一样,VGG网络可以分为两部分,第一部分主要由卷积层和汇聚层组成,第二部分由全连接层组成。 VGG有5个卷积块,前两个块包含一个卷积层&#xff0c…...

docker安装ES

拉取镜像文件 sudo docker pull elasticsearch:7.12.0 创建容器挂载目录 sudo mkdir -p /home/elasticsearch/config sudo mkdir -p /home/elasticsearch/data sudo mkdir -p /home/elasticsearch/plugins elasticsearch.yml http.host: 0.0.0.0 创建容器 sudo docker r…...

python爬虫实战(2)--爬取某博热搜数据

1. 准备工作 使用python语言可以快速实现,调用BeautifulSoup包里面的方法 安装BeautifulSoup pip install BeautifulSoup完成以后引入项目 2. 开发 定义url url https://s.微博.com/top/summary?caterealtimehot定义请求头,微博请求数据需要cookie…...

k8s的Namespace详解

简介 在一个K8s集群中可以拥有多个命名空间,它们在逻辑上彼此隔离 namespaces是对一组资源和对象的抽象集合,比如可以将系统内部的对象划分为不同的项目组或用户组 K8s在集群启动之后,会默认创建几个namespace默认namespace default&#xff…...

【Redis】Redis内存过期策略和内存淘汰策略

【Redis】Redis内存过期策略和内存淘汰策略 文章目录 【Redis】Redis内存过期策略和内存淘汰策略1. 过期策略1.1 惰性删除1.2 周期删除1.2.1 SLOW模式1.2.2 FAST模式 2. 淘汰策略 1. 过期策略 Redis本身是一个典型的key-value内存存储数据库,因此所有的key、value都…...

技术干货 | cilium 原理之sock_connect

1.背景 在集群网络使用cilium之后,最明显的情况就是:服务暴露vipport,在集群内怎么测试都正常,但集群外访问可能是有问题的。而这就在于cilium所使用的ebpf科技。 2.引子:curl请求的路程 相对底层一点的语言&#xf…...

K8S之Pod详解与进阶

Pod详解与进阶 文章目录 Pod详解与进阶一、Pod详解1.pod定义2.pause容器作用3.Pod 的 3 种类型4.Pod 的 3 种容器5.Pod 的 3 种镜像拉取策略6.Pod 的 3 种容器重启策略 二、Pod进阶1.资源限制2.Pod 容器的 3 种探针(健康检查)3.探针的 3 种探测方式探针参…...

【小曾同学赠书活动】开始啦—〖测试设计思想〗

文章目录 ❤️ 赠书 —《测试设计思想》🌟 书籍介绍🌟 作者简介图书链接❤️ 活动介绍 — 赠送 3 本 ❤️ 赠书 —《测试设计思想》 首先提问 你知道测试设计思想有哪几类吗?你想奠定扎实的测试理论基础吗?你想改变关于你当前测试…...

【Docker晋升记】No.1--- Docker工具核心组件构成(镜像、容器、仓库)及性能属性

文章目录 前言🌟一、Docker工具🌟二、Docker 引擎🌏2.1.容器管理:🌏2.2.镜像管理:🌏2.3.资源管理:🌏2.4.网络管理:🌏2.5.存储管理:&am…...

ROBOGUIDE教程:FANUC机器人X型焊枪气动点焊焊接

目录 概述 机器人系统创建 X型点焊焊枪安装与配置 机器人组输出(GO)信号配置 气动点焊初始设置 点焊设备设置 点焊设备I/O信号设置 焊接控制器I/O信号设置 X型点焊焊枪运动控制配置 气动焊枪手动运行操作 气动点焊焊接指令介绍 机器人点焊焊接程序编写 机器人仿…...

二、 根据用户行为数据创建ALS模型并召回商品

二 根据用户行为数据创建ALS模型并召回商品 2.0 用户行为数据拆分 方便练习可以对数据做拆分处理 pandas的数据分批读取 chunk 厚厚的一块 相当大的数量或部分 import pandas as pd reader pd.read_csv(behavior_log.csv,chunksize100,iteratorTrue) count 0; for chunk in …...

C++性能优化指南

思维导图(转载) https://www.processon.com/view/5e5b3fc5e4b03627650b1f42 第 1 章 优化概述 1.1 优化是软件开发的一部分 优化更像是一门实验科学。 1.2 优化是高效的 1.3 优化是没有问题的 **90/10 规则:**程序中只有 10% 的代码…...

SQL导出Excel支持正则脱敏

SQL to Excel Exporter 源码功能特性核心功能性能优化安全特性 快速开始环境要求安装运行 API 使用说明1. 执行SQL并导出Excel2. 下载导出文件3. 获取统计信息4. 清理过期文件 数据脱敏配置支持的脱敏类型脱敏规则配置示例 配置说明应用配置数据库配置 测试运行单元测试运行集成…...

代码随想录算法训练营第60期第六十天打卡

大家好,今天因为有数学建模比赛的校赛,今天的文章可能会简单一点,望大家原谅,我们昨天主要讲的是并查集的题目,我们复习了并查集的功能,我们昨天的题目其实难度不小,尤其是后面的有向图&#xf…...

PublishSubject、ReplaySubject、BehaviorSubject、AsyncSubject的区别

python容易编辑,因此用pyrx代替rxjava3做演示会比较快捷。 pyrx安装命令: pip install rx 一、Subject(相当于 RxJava 的 PublishSubject) PublishSubject PublishSubject 将对观察者发送订阅后产生的元素,而在订阅前…...

AUTOSAR实战教程--DoIP_01_配置项解释

配置项 解释 备注 DoIPChannelSARef 引用DoIP Tester的源地址,就是你在DoIP Tester这个Containter中配置的Tester实例。 DoIPChannelTARef 引用目标地址。就是你在DoIPTargetAddress这个Container中的配置。 DoIPPduRRxPduId 为该pdu设置一个ID用于DoIP…...

面壁智能推出 MiniCPM 4.0 端侧大模型,引领端侧智能新变革

在 2025 智源大会期间,面壁智能重磅发布了开源模型 MiniCPM 4.0 的两个新版本(0.5B、8B),代号「前进四」。此次发布在人工智能领域引发了广泛关注,标志着端侧大模型技术取得了重大突破。 卓越性能,树立行业…...

pandas 字符串存储技术演进:从 object 到 PyArrow 的十年历程

文章目录 1. 引言2. 阶段1:原始时代(pandas 1.0前)3. 阶段2:Python-backed StringDtype(pandas 1.0 - 1.3)4. 阶段3:PyArrow初次尝试(pandas 1.3 - 2.1)5. 阶段4&#xf…...

意识上传伦理前夜:我们是否在创造数字奴隶?

当韩国财阀将“数字永生”标价1亿美元准入权时,联合国预警的“神经种姓制度”正从科幻步入现实。某脑机接口公司用户协议中“上传意识衍生算法归公司所有”的隐藏条款,恰似德里达预言的当代印证:“当意识沦为可交易数据流,主体性便…...

AI是如何换装的?

AI换装是一种基于计算机视觉、深度学习和生成对抗网络(GAN)的技术,能够通过算法自动识别人像并更换服饰,实现虚拟换装的效果。这项技术广泛应用于电商服装试穿、虚拟偶像、影视特效、社交媒体滤镜等领域。 AI换装的核心技术 1. 图像分割与人体解析 换装的第一步是图像分…...

QGraphicsView中鼠标点击与移动事件传递给MainWindow

在Qt图形应用程序开发中,QGraphicsView和QGraphicsScene框架提供了强大的2D图形显示功能。然而,当我们需要在主窗口(MainWindow)中处理这些视图中的鼠标事件。 问题背景 在典型的Qt图形应用程序架构中: MainWindow └── QGraphicsView└── QGraphicsScene└── QGra…...