Go中数组和切片

数组和切片

【1】、数组

1、什么是数组

一组数

  • 数组需要是相同类型的数据的集合

  • 数组是需要定义大小的

  • 数组一旦定义了大小是不可以改变的。

package main

import "fmt"

// 数组
// 数组和其他变量定义没什么区别,唯一的就是这个是一组数,需要给一个大小  [6]int   [10]string
// 数组是一个相同类型数据的==有序==集合,通过下标来取出对应的数据
// 数组几个特点:
// 1、长度必须是确定的,如果不确定,就不是数组,大小不可以改变
// 2、元素必须是相,同类型不能多个类型混合, [any也是类型,可以存放任意类型的数据]
// 3、数组的中的元素类型,可以是我们学的所有的类型,int、string、float、bool、array、slice、map
func main() {
	// 派生数据类型
	// 数组的定义:【数组大小size】变量的类型  我们定义了一组这个类型的数的集合,大小为size
	// 默认值是00000
	var arr1 [5]int
	arr1[0] = 1
	arr1[1] = 2
	arr1[2] = 3
	arr1[3] = 4
	arr1[4] = 5
	fmt.Println(arr1)
	for i := 0; i < len(arr1); i++ {
		fmt.Printf("%d\n", arr1[i])
	}

	// 数组中的常用方法 len()获取数组的长度  cap() 获取数组的容量
	fmt.Println("数组的长度:", len(arr1)) // 数组的长度: 5
	fmt.Println("数组的容量:", cap(arr1)) // 数组的容量: 5

	// 修改数组的值
	arr1[1] = 100
	fmt.Println(arr1)

}

2、初始化数组

package main

import "fmt"

func main() {
	// 数组的赋值初始化
	var arr1 = [5]int{0, 1, 2, 3, 4}
	fmt.Println(arr1)

	// 快速赋值
	arr2 := [5]int{0, 1, 2, 3, 4}
	fmt.Println(arr2)

	// 接受用户输入的数据,变为数组
	// ... 代表数组长度
	// Go的编译器会自动根据数组的长度来给 ... 赋值,自动推导长度
	// 注意点:这里的数组不是无限长的,也是固定的大小,大小取决于数组元素个数
	arr3 := [...]int{0, 1, 2, 3, 4, 5, 5, 5, 6, 7, 8, 9}
	fmt.Println(len(arr3), arr3)

	// 数组默认值,只给其中某几个元素赋值
	var arr4 [10]int
	fmt.Println(arr4)
	arr4[6] = 600
	arr4[5] = 500
	fmt.Println(arr4)
}

3、遍历数组元素

package main

import "fmt"

/*
1、直接通过下标获取元素 arr[index]

2、 0-len i++ 可以使用for循环来结合数组下标进行遍历

3、for range:范围   (new)
*/
func main() {
	arr3 := [...]int{0, 1, 2, 3, 4, 5, 5}
	for i := 0; i < len(arr3); i++ {
		fmt.Println(arr3[i])
	}

	// goland 快捷方式 数组.for,未来循环数组、切片很多时候都使用for    range
	// for 下标,下标对应的值  range 目标数组切片
	// 就是将数组进行自动迭代。返回两个值 index、value
	// 注意点,如果只接收一个值,这个时候返回的是数组的下标
	// 注意点,如果只接收两个值,这个时候返回的是数组的下标和下标对应的值
	for _, value := range arr3 {
		fmt.Println("value:", value)
	}
}

4、数组是值类型

image-20241111114706632

package main

import "fmt"

// 数组是值类型: 所有的赋值后的对象修改值后不影响原来的对象。
func main() {
	arr3 := [...]int{0, 1, 2, 3, 4, 5, 5}
	arr4 := [...]string{"111", "222"}
	fmt.Printf("%T\n", arr3)
	fmt.Printf("%T\n", arr4)
	arr5 := arr3
	arr3[6] = 7
	fmt.Printf("%T\n", arr3)
	fmt.Println(arr5)  // 数组是值传递,拷贝一个新的内存空间
}

5、数组排序

arr := [6]int{1,2,3,4,5,0}
// 升序 ASC  : 从小到大  0,1,2,3,4,5   A-Z    00:00-24:00
// 降序 DESC : 从大到小  5,4,3,2,1,0

// 冒泡排序
package main

import "fmt"

func main() {
	arr1 := [...]int{1, 2, 3, 4, 5, 0, 77, 95, 11, 23, 54, 88, 33, 10, 23, 19}
	//var temp int = 0
	for i := 0; i < len(arr1); i++ {
		for j := i + 1; j < len(arr1); j++ {
			if arr1[i] > arr1[j] {
				arr1[i], arr1[j] = arr1[j], arr1[i]
			}
		}
	}
	fmt.Println(arr1)
}

6、多维数组

一维数组: 线性的,一组数

二维数组: 表格性的,数组套数组

三维数组: 立体空间性的,数组套数组套数组

xxxx维数组:xxx,数组套数组套数组.....

image-20241111142601052

package main

import "fmt"

func main() {

   // 定义一个多维数组  二维

   arr := [3][4]int{
      {0, 1, 2, 3},   // arr[0]  //数组
      {4, 5, 6, 7},   // arr[1]
      {8, 9, 10, 11}, // arr[2]
   }
   // 二维数组,一维数组存放的是一个数组
   fmt.Println(arr[0])
   // 要获取这个二维数组中的某个值,找到对应一维数组的坐标,arr[0] 当做一个整体
   fmt.Println(arr[0][1])
   fmt.Println("------------------")
   // 如何遍历二维数组
   for i := 0; i < len(arr); i++ {
      for j := 0; j < len(arr[i]); j++ {
         fmt.Println(arr[i][j])
      }
   }
   // for range
   for i, v := range arr {
      fmt.Println(i, v)
   }
}

【2】、切片

Go 语言切片是对数组的抽象。

Go 数组的长度不可改变,在特定场景中这样的集合就不太适用,Go 中提供了一种灵活,功能强悍的内置类型 切片("动态数组"),与数组相比切片的长度是不固定的,可以追加元素,在追加时可能使切片的容量增大

切片是一种方便、灵活且强大的包装器,切片本身没有任何数据,他们只是对现有数组的引用。

切片与数组相比,不需要设定长度,在[]中不用设定值,相对来说比较自由

从概念上面来说 slice 像一个结构体,这个结构体包含了三个元素:

  • 指针:指向数组中 slice 指定的开始位置

  • 长度:即slice的长度

  • 最大长度:也就是 slice 开始位置到数组的最后位置的长度

1、切片的定义

package main

import "fmt"

func main() {
	var s1 []int // 变长,长度是可变的
	fmt.Println(s1)
	// 切片空的判断,初始化切片中,默认是nil
	if s1 == nil {
		fmt.Println("s1是空的")
	}

	fmt.Printf("%T\n", s1)
	s2 := []int{1, 2, 3, 4}
	fmt.Println(s2[2])

}

2、make来创建切片

package main

import "fmt"

func main() {

   // make()
   // make([]Type,length,capacity) // 创建一个切片,长度,容量
   s1 := make([]int, 5, 10)
   fmt.Println(s1)
   fmt.Println(len(s1), cap(s1))
   // 思考:容量为10,长度为5,我能存放6个数据吗?
   s1[0] = 10
   s1[7] = 200 // index out of range [7] with length 5
   // 切片的底层还是数组 [0 0 0 0 0] [2000]
   // 直接去赋值是不行的,不用用惯性思维思考
   fmt.Println(s1)
   
   // 切片扩容

}

3、切片扩容

package main

import "fmt"

func main() {
	s1 := make([]int, 0, 5)
	// 切片扩容,append()
	s1 = append(s1, 1, 2)
	fmt.Println(s1)
	
	
	s2 := []int{100, 200, 300, 400}
	// 切片扩容之引入另一个切片。
	// slice = append(slice, anotherSlice...)
	// ... 可变参数 ...xxx
	// [...] 根据长度变化数组的大小定义
	// anotherSlice... , slice...解构,可以直接获取到slice中的所有元素
	s1 = append(s1, s2...)
	fmt.Println(s1)
}

4、遍历切片

package main

import "fmt"

func main() {
	s1 := make([]int, 0, 5)
	fmt.Println(s1)
	// 切片扩容,append()
	s1 = append(s1, 1, 2)
	fmt.Println(s1)
	// 问题:容量只有5个,那能放超过5个的吗? 可以,切片是会自动扩容的。
	s1 = append(s1, 2, 3, 3, 4, 4, 5, 6, 6, 7, 7)
	fmt.Println(s1)

	// 切片扩容之引入另一个切片。
	// new : 解构   slice.. ,解出这个切片中的所有元素。
	s2 := []int{100, 200, 300, 400}
	// slice = append(slice, anotherSlice...)
	// ... 可变参数 ...xxx
	// [...] 根据长度变化数组的大小定义
	// anotherSlice... , slice...解构,可以直接获取到slice中的所有元素
	// s2... = {100,200,300,400}
	s1 = append(s1, s2...)

	// 遍历切片
	for i := 0; i < len(s1); i++ {
		fmt.Println(s1[i])
	}

	for i := range s1 {
		fmt.Println(s1[i])
	}
}

5、扩容的内存分析

// 1、每个切片引用了一个底层的数组

// 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据

// 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了这个容量,就会自动扩容,成倍的增加, copy

// - 分析程序的原理

// - 看源码

//

// 4、切片一旦扩容,就是重新指向一个新的底层数组。

package main

import "fmt"

func main() {
	s2 := []int{1, 2, 3}
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)

	s2 = append(s2, 4, 5)
	fmt.Println(len(s2), cap(s2))
	fmt.Printf("%p\n", s2)
	/*	3 3
		0xc000126078
		5 6
		0xc000144030
		7 12
		0xc000102060
		9 12
		0xc000102060
		11 12
		0xc000102060
		13 24
		0xc000152000
		15 24
		0xc000152000
	*/
}

slice扩容的具体实现

主要使用到了make方法和copy方法

package main

import "fmt"

func main() {
	s2 := []int{1, 2, 3}
	fmt.Printf("len:%d,cap:%d,%v\n", len(s2), cap(s2), s2)
	s3 := make([]int, len(s2), cap(s2)*2)
	copy(s3, s2)
	fmt.Printf("len:%d,cap:%d,%v\n", len(s3), cap(s3), s3)
}

6、使用数组创建切片

package main

import "fmt"

func main() {
	arr := [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	// 数组的截取
	// 通过数组创建切片
	s1 := arr[:5]  // 1-5
	s2 := arr[3:8] // 4-8
	s3 := arr[5:]  //  6-10
	s4 := arr[:]   //  0-10
	fmt.Println(s1)
	fmt.Println(s2)
	fmt.Println(s3)
	fmt.Println(s4)
	// 查看容量和长度
	// 截取后切片的长度就是截取数组的元素个数,切片的容量就是从截取数组的开始位置到数组的最后位置
	fmt.Printf("s1 len:%d, cap:%d\n", len(s1), cap(s1)) // s1 len:5, cap:10
	fmt.Printf("s2 len:%d, cap:%d\n", len(s2), cap(s2)) // s2 len:5, cap:7
	fmt.Printf("s3 len:%d, cap:%d\n", len(s3), cap(s3)) // s3 len:5, cap:5
	fmt.Printf("s4 len:%d, cap:%d\n", len(s4), cap(s4)) // s4 len:10, cap:10

	// 切片的内存地址,就是截取数组开始的地址 cap(切片)=len(数组) 他们的内存地址就相同
	fmt.Printf("%p,%p\n", s1, &arr)    // 0xc000016230,0xc000016230
	fmt.Printf("%p,%p\n", s2, &arr[3]) // 0xc000016248,0xc000016248
	fmt.Printf("%p,%p\n", s2, &arr[3]) // 0xc000016248,0xc000016248
	fmt.Printf("%p,%p\n", s3, &arr[5]) // 0xc000016258,0xc000016258
	fmt.Printf("%p,%p\n", s4, &arr[0]) // 0xc000016230,0xc000016230

	// 修改数组的内容, 切片也随之发生了变化 (切:切片不保存数据-->底层的数组 )
	arr[2] = 100
	fmt.Println(arr) // [1 2 100 4 5 6 7 8 9 10]
	fmt.Println(s1)  // [1 2 100 4 5]
	fmt.Println(s2)  // [4 5 6 7 8]
	fmt.Println(s3)  // [6 7 8 9 10]
	fmt.Println(s4)  // [1 2 100 4 5 6 7 8 9 10]
	fmt.Println("--------------------------------------------")
	// 修改切片的内容,发现数组也随之发生了变化。(本质:修改的都是底层的数组)
	s2[2] = 80
	fmt.Println(arr) // [1 2 100 4 5 80 7 8 9 10]
	fmt.Println(s1)  // [1 2 100 4 5]
	fmt.Println(s2)  // [4 5 80 7 8]
	fmt.Println(s3)  // [80 7 8 9 10]
	fmt.Println(s4)  // [1 2 100 4 5 80 7 8 9 10]

	fmt.Println("--------------------------------------------")
	// 切片扩容,如果容量超过了cap,那么不会影响原数组,因此此时s1指向的是一个新的数组
	s1 = append(s1, 11, 12, 13, 14, 15, 16)
	fmt.Println(arr) // [1 2 100 4 5 80 7 8 9 10]
	fmt.Printf("s1:%p,arr:%p\n", s1, &arr)  // s1:0xc0000d4000,arr:0xc0000ac0f0

}

7、切片:引用类型

package main

import "fmt"

func main() {
	arr1 := [3]int{1, 2, 3}
	arr2 := arr1
	fmt.Println("arr1:", arr1, "arr2:", arr2)
	fmt.Printf("arr1:%p,arr2:%p\n", &arr1, &arr2) // arr1:0xc0000aa078,arr2:0xc0000aa090
	arr1[1] = 10
	fmt.Println("arr1:", arr1, "arr2:", arr2) // [3 4 5] [3 4 5]
	fmt.Println("---------------------------------")

	s1 := make([]int, 0, 5)
	s1 = append(s1, 3, 4, 5)
	s2 := s1
	fmt.Println(s1, s2)
	fmt.Printf("s1:%p,s2:%p\n", s1, s2) //s1:0xc0000c8030,s2:0xc0000c8030
	s1[1] = 30
	fmt.Println(s1, s2) // [3 30 5] [3 30 5]
}

8、深拷贝、浅拷贝

深拷贝:拷贝是数据的本身

  • 值类型的数据,默认都是深拷贝,array、int、float、string、bool、struct....

浅拷贝:拷贝是数据的地址,会导致多个变量指向同一块内存。

  • 引用类型的数据: slice、map

  • 因为切片是引用类的数据,直接拷贝的是这个地址

切片怎么实现深拷贝 copy

package main

import "fmt"

func main() {
	// 将原来切片中的数据拷贝到新切片中
	s1 := []int{1, 2, 3, 4}
	s2 := make([]int, 0) // len:0 cap:0
	for i := 0; i < len(s1); i++ {
		s2 = append(s2, s1[i])
	}
	fmt.Println(s1, s2)
	fmt.Printf("%p,%p\n", s1, s2)
	s1[2] = 200
	fmt.Println(s1, s2)
}

9、函数中参数传递问题

按照数据的存储特点来分:

  • 值类型的数据:操作的是数据本身、int 、string、bool、float64、array...

  • 引用类型的数据:操作的是数据的地址 slice、map、chan....

值传递

引用传递

package main

import "fmt"

func main() {
    // 值传递
	arr := [3]int{1, 2, 3}
	fmt.Println(arr)
	update(arr)
	fmt.Println(arr)
    
    // 引用传递
	s1 := []int{4, 5, 6}
	fmt.Println(s1)
	update_s(s1)
	fmt.Println(s1)
}

func update(arr [3]int) {
	fmt.Println(arr)
	arr[1] = 100
	fmt.Println(arr)
}
func update_s(s1 []int) {
	fmt.Println(s1)
	s1[1] = 20
	fmt.Println(s1)
}

文章转载自:Linux小菜鸟

原文链接:https://www.cnblogs.com/xuruizhao/p/18544832

体验地址:引迈 - JNPF快速开发平台_低代码开发平台_零代码开发平台_流程设计器_表单引擎_工作流引擎_软件架构

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

three.js加载GLTF模型

要在three.js中正确加载和显示GLTF模型&#xff0c;需要遵循一系列步骤来确保模型的纹理和材质被正确应用。以下是加载GLTF模型的基本步骤&#xff1a; 引入必要的three.js模块&#xff1a; 引入了GLTFLoader模块&#xff0c;用来加载GLTF格式模型的类。 创建加载器实例&#…

消息中间件分类

消息中间件&#xff08;Message Middleware&#xff09;是一种在分布式系统中实现跨平台、跨应用通信的软件架构。它基于消息传递机制&#xff0c;允许不同系统、不同编程语言的应用之间进行异步通信。 常见的消息中间件类型包括&#xff1a; 1. JMS&#xff08;Java Message S…

形态学图像处理(Morphological Image Processing)

形态学图像处理(Morphological Image Processing) 前言 ‍ 本博客为个人总结数字图像处理一课所写&#xff0c;并给出适当的扩展和相应的demo。 写博客跟做 checkpoint​ 很像&#xff0c;毕竟个人还不能达到那种信手拈来的境界&#xff0c;忘了就是从零开始训练&#xff0…

LeetCode 面试经典 150 题回顾

目录 一、数组 / 字符串 1.合并两个有序数组 &#xff08;简单&#xff09; 2.移除元素 &#xff08;简单&#xff09; 3.删除有序数组中的重复项 &#xff08;简单&#xff09; 4.删除有序数组中的重复项 II&#xff08;中等&#xff09; 5.多数元素&#xff08;简单&am…

项目进度计划表:详细的甘特图的制作步骤

甘特图&#xff08;Gantt chart&#xff09;&#xff0c;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;是一种用于管理时间和任务活动的工具。 甘特图由亨利劳伦斯甘特&#xff08;Henry Laurence Gantt&#xff09;发明&#xff0c;是一种通过条状图来…

netty之内存泄露检测

写在前面 本文看下netty内存泄露检测相关内容&#xff0c;当然&#xff0c;这里的内存泄露不是bytebuf对象本身&#xff0c;是bytebuf关联的堆外内存。 1&#xff1a;实战 我们还是使用netty源码的example模块的echo例子&#xff0c;但是我们需要对server的handler稍微做些改…

服务器上部署并启动 Go 语言框架 **GoZero** 的项目

要在服务器上部署并启动 Go 语言框架 **GoZero** 的项目&#xff0c;下面是一步步的操作指南&#xff1a; ### 1. 安装 Go 语言环境 首先&#xff0c;确保你的服务器上已安装 Go 语言。如果还没有安装&#xff0c;可以通过以下步骤进行安装&#xff1a; #### 1.1 安装 Go 语…

如何去掉el-input 中 type=“number“两侧的上下按键

<el-input v-model.trim"row.length" type"number" min"0" placeholder""></el-input> // 如何去掉el-input-number两侧的上下按键 ::v-deep input::-webkit-outer-spin-button, ::v-deep input::-webkit-inner-spin-butt…

前端注册代码

代码 <template><el-card class"register" style"max-width: 480px ; background-color: aliceblue;"><template #header><div class"card-header"><span>注册</span></div></template><el…

【第六课】Rust所有权系统(二)

目录 前言 借用和引用 借用规则 切片和迭代器 总结 前言 上节课介绍了Rust中的所有权系统&#xff0c;简单回顾一下&#xff0c;rust的内存系统系统&#xff0c;每一块内存都有一个主人&#xff0c;主人对这块内存有着读写和释放的权限&#xff0c;当主人离开作用域之后&am…

1024程序员节:永无bug

引言 每年的10月24日是程序员节。这一天不仅是程序员们的节日&#xff0c;更是对整个行业的庆祝与思考。在这个特殊的日子里&#xff0c;我们不仅回顾过去一年的成就与挑战&#xff0c;也展望未来的发展与机遇。本篇文章将围绕程序员节的主题&#xff0c;探讨前端技术的最新动…

STM32设计学生宿舍监测控制系统-分享

目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 电路图采用Altium Designer进行设计&#xff1a; 三、实物设计图 四、程序源代码设计 五、获取资料内容 前言 本项目旨在利用STM32单片机为核心&#xff0c;结合传感器技术、无线通信技…

Node.js | Yarn下载安装与环境配置

一、安装Node.js Yarn 是 Node.js 下的包管理工具&#xff0c;因此想要使用 Yarn 就必须先下载 Node.js。 推荐参考&#xff1a;Node.js | npm下载安装及环境配置教程 二、Yarn安装 打开cmd&#xff0c;输入以下命令&#xff1a; npm install -g yarn检查是否安装成功&…

【MySQL】MySQL在Centos环境安装

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; MySQL 目录 &#x1f308;前言&#x1f525;卸载不要的环境&#x1f525;检查系统安装包&#x1f525;卸载这些默认安装包&#x1f525;获取mysql官方yum源&#x1f525;安装mysql yum源…

selenium元素定位校验以及遇到的元素操作问题记录

页面元素定位方法及校验 使用比较多的是通过id、class和xpath来对元素进行定位。在定位前可以现在浏览器验证是否可以找到指定的元素。这样就不用每添加一个元素定位都运行代码来检查定位方式表达式是否正确。 使用XPATH定位 在浏览器F12&#xff0c;找到元素&#xff0c;在元…

LLM文档对话 —— pdf解析关键问题

一、为什么需要进行pdf解析&#xff1f; 最近在探索ChatPDF和ChatDoc等方案的思路&#xff0c;也就是用LLM实现文档助手。在此记录一些难题和解决方案&#xff0c;首先讲解主要思想&#xff0c;其次以问题回答的形式展开。 二、为什么需要对pdf进行解析&#xff1f; 当利用L…

小试牛刀-Anchor安装和基础测试

目录 一、编写目的 二、安装步骤 2.1 安装Rust 设置rustup镜像 安装Rust 2.2 安装node.js 2.3 安装Solana-CLI 2.4 安装Anchor CLI 三、Program测试 四、可能出现的问题 Welcome to Code Blocks blog 本篇文章主要介绍了 [Anchor安装和基础测试] 博主广交技术好友&…

Ubuntu 的 ROS 操作系统 turtlebot3 导航仿真

引言 导航仿真是机器人自动化系统中不可或缺的一部分&#xff0c;能够帮助开发者在虚拟环境中测试机器人在复杂场景下的运动与路径规划。 在 Gazebo 仿真环境中&#xff0c;TurtleBot3 配合 ROS 操作系统提供了强大的导航功能。在进行导航仿真时&#xff0c;首先需要准备地图&…

基于Java Springboot网络相册系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

AI 使用心态大转变:如何让 AI 成为日常工具

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…