【GoLang#3】:数据结构(切片 | map 映射)

一、切片

之前我们在数组那举过一个例子:关于 值类型 和 引用类型的,如下:

func main(){// 值类型: 数组var a1 = [...]int{1, 2, 3}a2 := a1a1[0] = 11fmt.Println(a1) // 11 2 3fmt.Println(a2) // 1 2 3// 引用类型: 切片var b1 = []int{1, 2, 3}b2 := b1b1[0] = 11fmt.Println(b1) // 11 2 3fmt.Println(b2) // 11 2 3
}

现在我们来正式了解一下切片是什么吧

定义:切片(slice)是一个拥有相同类型元素的可变长度的序列。它是基于数组类型做的一层封装。它非常灵活,支持自动扩容。切片是一个 引用类型,它的内部结构包含 地址长度容量

type slice struct {array unsafe.Pointer  // 指向底层数组的指针len   int             // 切片长度cap   int             // 切片容量
}

声明切片类型的基本语法:var name []T

  1. name:表示变量名
  2. T:表示切片中的元素类型

如下

package main
import "fmt"func main() {// 切片和数组的区别var arr [5]int           // 数组:固定长度var slice []int          // 切片:动态长度fmt.Printf("数组: %v, 长度: %d\n", arr, len(arr))fmt.Printf("切片: %v, 长度: %d\n", slice, len(slice))
}

1. 切片的创建方式

下面是多种创建切片的方法:

func main() {// 1. 从数组创建切片arr := [5]int{1, 2, 3, 4, 5}slice1 := arr[1:4]        // [2 3 4] - 从索引1到3slice2 := arr[:]          // [1 2 3 4 5] - 整个数组slice3 := arr[:3]         // [1 2 3] - 从开始到索引2slice4 := arr[2:]         // [3 4 5] - 从索引2到结束fmt.Println("slice1:", slice1)fmt.Println("slice2:", slice2)fmt.Println("slice3:", slice3)fmt.Println("slice4:", slice4)// 2. 使用 make 创建切片slice5 := make([]int, 5)        // 长度5,容量5,元素为0slice6 := make([]int, 3, 10)    // 长度3,容量10fmt.Printf("slice5: %v, 长度: %d, 容量: %d\n", slice5, len(slice5), cap(slice5))fmt.Printf("slice6: %v, 长度: %d, 容量: %d\n", slice6, len(slice6), cap(slice6))// 3. 字面量创建切片slice7 := []int{1, 2, 3, 4, 5}slice8 := []string{"hello", "world"}fmt.Println("slice7:", slice7)fmt.Println("slice8:", slice8)// 4. 使用 new 创建(不常用)slice9 := *new([]int)  // 创建空切片fmt.Printf("slice9: %v, 长度: %d, 容量: %d\n", slice9, len(slice9), cap(slice9)) // [] 0 0
}

理解:这里的数组创建切片 [] 的意思

语法格式array[low : high : max]

其中:

  • low :起始索引(包含)
  • high :结束索引(不包含
  • max :可选参数,指定切片的容量上限

而实际的切片长度和容量计算为:(在2.2 切片长度和容量那里有举例说明)

  1. 长度high - low
  2. 容量max - low(如果不指定 max,则容量到数组末尾)

2. 切片的基本操作

2.1 访问和修改元素
func main() {slice := []int{10, 20, 30, 40, 50}// 访问元素fmt.Println("第一个元素:", slice[0])fmt.Println("最后一个元素:", slice[len(slice)-1])// 修改元素slice[0] = 100fmt.Println("修改后:", slice)// 遍历切片fmt.Println("正向遍历:")for i := 0; i < len(slice); i++ {fmt.Printf("slice[%d] = %d ", i, slice[i])}fmt.Println()fmt.Println("range 遍历:")for index, value := range slice {fmt.Printf("索引 %d: 值 %d ", index, value)}fmt.Println()// 只遍历索引for index := range slice {fmt.Printf("索引: %d ", index)}fmt.Println()// 只遍历值for _, value := range slice {fmt.Printf("值: %d ", value)}
}
2.2 切片长度和容量
func printSliceInfo(name string, s []int) {fmt.Printf("%s: %v, 长度: %d, 容量: %d\n", name, s, len(s), cap(s))
}func main() {arr := [6]int{1, 2, 3, 4, 5, 6}// 不同切片的长度和容量s1 := arr[1:4]   // [2 3 4], s2 := arr[2:5]   // [3 4 5]s3 := arr[1:4:5] // [2 3 4],指定容量为5-1=4printSliceInfo("s1", s1)printSliceInfo("s2", s2)printSliceInfo("s3", s3)// make 创建的切片s4 := make([]int, 3, 10)  // 长度3,容量10printSliceInfo("s4", s4)
}

注意

  • 切片的长度表示切片中当前包含的元素个数,可以通过 len(slice) 函数获取;
  • 而切片的容量是指从切片的第一个元素开始,到其底层数组末尾的元素个数,可以通过 cap(slice)函数获取。

简单来说,长度是实际元素的数量,容量是切片在不分配新内存的情况下,最多能追加的元素数量。

2.3 切片扩容操作

① append 操作和扩容

func main() {// 初始切片slice := make([]int, 0, 2) // 长度0,容量2fmt.Printf("初始时: ")printSliceInfo(slice)// 逐步添加元素观察扩容for i := 1; i <= 5; i++ {slice = append(slice, i)fmt.Printf("添加 %d 后: ", i)printSliceInfo(slice)}
}func printSliceInfo(s []int) {fmt.Printf("长度: %d, 容量: %d, 内容: %v\n", len(s), cap(s), s)
}

结果如下

初始时: 长度: 0, 容量: 2, 内容: []
添加 1 后: 长度: 1, 容量: 2, 内容: [1]
添加 2 后: 长度: 2, 容量: 2, 内容: [1 2]
添加 3 后: 长度: 3, 容量: 4, 内容: [1 2 3]
添加 4 后: 长度: 4, 容量: 4, 内容: [1 2 3 4]
添加 5 后: 长度: 5, 容量: 8, 内容: [1 2 3 4 5]

② 切片扩容规则

func main() {// 演示扩容机制slice := make([]int, 0, 1)addresses := make([]uintptr, 0)for i := 0; i < 10; i++ {// 记录底层数组地址if len(slice) > 0 {addresses = append(addresses, getSliceAddress(slice))}slice = append(slice, i)}// 检查地址变化(表示重新分配了底层数组)fmt.Println("地址变化点:")for i := 1; i < len(addresses); i++ {if addresses[i] != addresses[i-1] {fmt.Printf("在添加第 %d 个元素时发生扩容\n", i)}}
}// 获取切片底层数组地址的辅助函数
func getSliceAddress(s []int) uintptr {if len(s) == 0 {return 0}return uintptr(unsafe.Pointer(&s[0]))
}// 输出如下:
地址变化点:
在添加第 1 个元素时发生扩容
在添加第 2 个元素时发生扩容
在添加第 4 个元素时发生扩容
在添加第 8 个元素时发生扩容

3. 切片的高级操作

3.1 切片复制和追加
func main() {// 1. append 操作slice1 := []int{1, 2, 3}slice1 = append(slice1, 4, 5)  // 添加多个元素fmt.Println("append 多个元素:", slice1)	// 1 2 3 4 5// 追加另一个切片slice2 := []int{6, 7, 8}slice1 = append(slice1, slice2...)  // 使用 ... 展开切片fmt.Println("append 切片:", slice1) // 1 2 3 4 5 6 7 8// 2. copy 操作src := []int{10, 20, 30, 40, 50}dst := make([]int, 3)n := copy(dst, src)  // 从 src 复制到 dstfmt.Printf("复制了 %d 个元素: %v\n", n, dst)// 复制到更大切片dst2 := make([]int, 10)n2 := copy(dst2, src)fmt.Printf("复制了 %d 个元素: %v\n", n2, dst2)// 3. 切片截取original := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}sub1 := original[2:5]    // [3 4 5]sub2 := original[:4]     // [1 2 3 4]sub3 := original[6:]     // [7 8 9 10]fmt.Println("原切片:", original)fmt.Println("sub1:", sub1)fmt.Println("sub2:", sub2)fmt.Println("sub3:", sub3)
}
3.2 切片删除操作
func main() {// 1. 删除指定索引的元素slice := []int{1, 2, 3, 4, 5}fmt.Println("原切片:", slice)// 删除索引为2的元素(值为3)index := 2slice = append(slice[:index], slice[index+1:]...)fmt.Println("删除索引2后:", slice)// 2. 删除第一个元素slice = slice[1:]fmt.Println("删除第一个元素后:", slice)// 3. 删除最后一个元素slice = slice[:len(slice)-1]fmt.Println("删除最后一个元素后:", slice)// 4. 按值删除(删除所有匹配的值)slice2 := []int{1, 2, 3, 2, 4, 2, 5}slice2 = removeValue(slice2, 2)fmt.Println("删除所有值为2的元素:", slice2)
}// 删除指定值的所有元素
func removeValue(slice []int, value int) []int {result := make([]int, 0)for _, v := range slice {if v != value {result = append(result, v)}}return result
}// 更高效的删除方式(保持原切片顺序)
func removeAt(slice []int, index int) []int {if index < 0 || index >= len(slice) {return slice}return append(slice[:index], slice[index+1:]...)
}

4. 切片注意事项

① 共享底层数组的问题

func main() {// 切片共享底层数组的陷阱original := []int{1, 2, 3, 4, 5}slice1 := original[1:4] // [2 3 4]fmt.Println("原始切片:", original)fmt.Println("slice1:", slice1) // [2, 3, 4]// 修改 slice1 会影响 originalslice1[0] = 100fmt.Printf("修改 slice1[0] = 100 后, ")fmt.Printf("原始切片: %d, ", original) // [1 100 3 4 5]fmt.Println("slice1: ", slice1)    // [100 3 4]// 正确的复制方式slice2 := make([]int, len(original[1:4]))copy(slice2, original[1:4]) // [100 3 4]slice2[0] = 200fmt.Printf("正确复制后修改,")fmt.Printf("原始切片: %d,", original) // [1 100 3 4 5]fmt.Println("slice2:", slice2) // [200 3 4]
}

② 切片扩容陷阱

func main() {// 切片扩容陷阱s1 := []int{1, 2, 3}s2 := s1[1:]  // [2 3]fmt.Printf("s1: %v, 地址: %p\n", s1, &s1[0])fmt.Printf("s2: %v, 地址: %p\n", s2, &s2[0])// 当 s2 扩容时,可能会重新分配底层数组s2 = append(s2, 4, 5, 6, 7, 8, 9)  // 足够多的元素触发扩容fmt.Printf("扩容后 s1: %v\n", s1)fmt.Printf("扩容后 s2: %v\n", s2)fmt.Printf("扩容后 s2 地址: %p\n", &s2[0])
}

5. 应用场景

① 字符串处理

func main() {// 字符串转切片str := "hello,world,go"parts := strings.Split(str, ",")fmt.Println("分割结果:", parts)// 切片转字符串joined := strings.Join(parts, "-")fmt.Println("连接结果:", joined)// 字符串切片操作runes := []rune("Hello, 世界")fmt.Println("字符切片:", runes)fmt.Printf("第一个字符: %c\n", runes[0])fmt.Printf("最后一个字符: %c\n", runes[len(runes)-1])
}

② 切片排序

func main() {// 整数切片排序numbers := []int{5, 2, 8, 1, 9, 3}fmt.Println("排序前:", numbers)sort.Ints(numbers)fmt.Println("排序后:", numbers)// 字符串切片排序words := []string{"banana", "apple", "cherry", "date"}fmt.Println("排序前:", words)sort.Strings(words)fmt.Println("排序后:", words)// 自定义排序type Person struct {Name stringAge  int}people := []Person{{"Alice", 30},{"Bob", 25},{"Charlie", 35},}// 按年龄排序sort.Slice(people, func(i, j int) bool {return people[i].Age < people[j].Age})fmt.Println("按年龄排序:")for _, p := range people {fmt.Printf("%s: %d岁\n", p.Name, p.Age)}
}

③ 切片搜索

func main() {// 有序切片的二分搜索sorted := []int{1, 3, 5, 7, 9, 11, 13, 15}// 使用 sort.SearchIntsindex := sort.SearchInts(sorted, 7)if index < len(sorted) && sorted[index] == 7 {fmt.Printf("找到 7,索引为: %d\n", index)} else {fmt.Println("未找到 7")}// 自定义搜索target := 6index2 := sort.Search(len(sorted), func(i int) bool {return sorted[i] >= target})if index2 < len(sorted) && sorted[index2] == target {fmt.Printf("找到 %d,索引为: %d\n", target, index2)} else {fmt.Printf("%d 应该插入到索引: %d\n", target, index2)}
}

6. 性能优化技巧

① 预分配容量

func main() {// 不预分配容量start := time.Now()var slice1 []intfor i := 0; i < 100000; i++ {slice1 = append(slice1, i)}duration1 := time.Since(start)// 预分配容量start = time.Now()slice2 := make([]int, 0, 100000)  // 预分配足够容量for i := 0; i < 100000; i++ {slice2 = append(slice2, i)}duration2 := time.Since(start)fmt.Printf("不预分配耗时: %v\n", duration1)fmt.Printf("预分配耗时: %v\n", duration2)fmt.Printf("性能提升: %.2f 倍\n", float64(duration1)/float64(duration2))
}

② 避免不必要复制

func main() {// 大切片的处理bigSlice := make([]int, 1000000)for i := range bigSlice {bigSlice[i] = i}// 错误的方式:复制整个大切片smallSlice1 := bigSlice[999990:]  // 可能复制整个底层数组// 正确的方式:创建新的小切片smallSlice2 := make([]int, 10)copy(smallSlice2, bigSlice[999990:])fmt.Printf("smallSlice1 长度: %d, 容量: %d\n", len(smallSlice1), cap(smallSlice1))fmt.Printf("smallSlice2 长度: %d, 容量: %d\n", len(smallSlice2), cap(smallSlice2))
}

二、Map 映射

Map 是 Go 语言中的键值对(key-value)数据结构,也称为字典或哈希表。它提供了快速的键值查找能力。

map 语法结构

map[键类型]值类型// 比如
map[string]string
map[string]int

1. Map 的基本操作

① 遍历 Map 的多种方式

func main() {// 1. 使用 make 创建空 mapvar map1 map[string]intmap1 = make(map[string]int)// 或者简写map2 := make(map[string]int)// 2. 字面量创建 mapmap3 := map[string]int{"张三": 25,"李四": 30,"王五": 28,}// 3. 创建空的字面量 mapmap4 := map[string]int{}// 4. 指定初始容量(可选优化)map5 := make(map[string]int, 100)  // 预分配容量fmt.Printf("map1: %v\n", map1)fmt.Printf("map2: %v\n", map2)fmt.Printf("map3: %v\n", map3)fmt.Printf("map4: %v\n", map4)fmt.Printf("map5: %v\n", map5)// 不同类型的 mapstringMap := map[string]string{"name":    "张三","city":    "北京","country": "中国",}intMap := map[int]string{1: "一",2: "二",3: "三",}fmt.Printf("stringMap: %v\n", stringMap)fmt.Printf("intMap: %v\n", intMap)
}

② 增、删、改、查操作

func main() {// 创建 mapages := make(map[string]int)// 1. 添加/修改元素(赋值操作)ages["张三"] = 25ages["李四"] = 30ages["王五"] = 28fmt.Println("添加元素后:", ages)// 2. 修改元素ages["张三"] = 26  // 修改张三的年龄fmt.Println("修改后:", ages)// 3. 查找元素age, exists := ages["李四"]if exists {fmt.Printf("李四的年龄: %d\n", age)} else {fmt.Println("未找到李四")}// 简化的查找方式(如果不需要区分零值和不存在)fmt.Printf("王五的年龄: %d\n", ages["王五"])// 4. 删除元素delete(ages, "王五")fmt.Println("删除王五后:", ages)// 删除不存在的键不会报错delete(ages, "不存在的人")fmt.Println("删除不存在的键后:", ages)
}

③ Map 的遍历

func main() {scores := map[string]float64{"数学": 95.5,"英语": 87.0,"语文": 92.5,"物理": 88.0,}fmt.Println("原始 map:", scores)fmt.Println()// 1. 基本遍历(注意:顺序不固定)fmt.Println("基本遍历:")for subject, score := range scores {fmt.Printf("%s: %.1f分\n", subject, score)}fmt.Println()// 2. 只遍历键fmt.Println("只遍历键:")for subject := range scores {fmt.Printf("科目: %s\n", subject)}fmt.Println()// 3. 只遍历值fmt.Println("只遍历值:")for _, score := range scores {fmt.Printf("分数: %.1f\n", score)}fmt.Println()// 4. 按照特定顺序遍历(需要先排序键)fmt.Println("按字母顺序遍历:")keys := make([]string, 0, len(scores))for key := range scores {keys = append(keys, key)}// 对键进行排序// 注意:需要导入 sort 包fmt.Println("keys:", keys)
}

2. Map 特性

① Map 的零值和 nil

func main() {// 1. nil mapvar nilMap map[string]intfmt.Printf("nilMap: %v, 是否为 nil: %t\n", nilMap, nilMap == nil) // map[] true// nil map 可以读取(返回零值)fmt.Printf("nilMap['key']: %d\n", nilMap["key"]) // 0// nil map 不能写入(会导致 panic)// nilMap["key"] = 1  // 这会 panic!// 2. 空 mapemptyMap := make(map[string]int)fmt.Printf("emptyMap: %v, 是否为 nil: %t\n", emptyMap, emptyMap == nil) // map[] false// 空 map 可以正常读写emptyMap["key"] = 1fmt.Printf("emptyMap['key']: %d\n", emptyMap["key"]) // 3. 检查键是否存在value, exists := emptyMap["不存在的键"] if !exists {fmt.Println("键不存在,返回零值:", value)}
}

② Map 的键类型限制

func main() {// Map 的键必须是可比较的类型// ✅ 合法的键类型intMap := map[int]string{1: "一", 2: "二"}stringMap := map[string]int{"one": 1, "two": 2}boolMap := map[bool]string{true: "真", false: "假"}// 复合键类型pointMap := map[[2]int]string{[2]int{0, 0}: "原点"}fmt.Println("intMap:", intMap)fmt.Println("stringMap:", stringMap)fmt.Println("boolMap:", boolMap)fmt.Println("pointMap:", pointMap)// ❌ 非法的键类型(编译错误)// sliceMap := map[[]int]string{}     // 错误:slice 不可比较// mapMap := map[map[string]int]string{} // 错误:map 不可比较// funcMap := map[func()]string{}     // 错误:函数不可比较
}

3. Map 嵌套和高级操作

嵌套 Map 和复杂数据结构

func main() {// 1. 嵌套 MapstudentGrades := map[string]map[string]float64{"张三": {"数学": 95.5,"英语": 87.0,"语文": 92.5,},"李四": {"数学": 88.0,"英语": 91.5,"语文": 89.0,},}fmt.Println("嵌套 Map:")for student, grades := range studentGrades {fmt.Printf("%s 的成绩:\n", student)for subject, score := range grades {fmt.Printf("  %s: %.1f\n", subject, score)}}// 访问嵌套 MapzhangsanMath := studentGrades["张三"]["数学"]fmt.Printf("\n张三的数学成绩: %.1f\n", zhangsanMath)// 2. Map + Slice 组合classStudents := map[string][]string{"一班": {"张三", "李四", "王五"},"二班": {"赵六", "钱七", "孙八"},}fmt.Println("\n班级学生:")for class, students := range classStudents {fmt.Printf("%s: %v\n", class, students)}
}

Map 函数

// 统计字符出现次数
func countChars(s string) map[rune]int {counts := make(map[rune]int)for _, char := range s {counts[char]++}return counts
}// 查找最大值对应的键
func findMaxKey(m map[string]int) (string, int) {if len(m) == 0 {return "", 0}maxKey := ""maxValue := 0for key, value := range m {if value > maxValue {maxValue = valuemaxKey = key}}return maxKey, maxValue
}func main() {// 字符统计示例text := "hello world"charCounts := countChars(text)fmt.Printf("'%s' 中字符统计: %v\n", text, charCounts)// 查找最大值scores := map[string]int{"张三": 95,"李四": 87,"王五": 92,"赵六": 89,}bestStudent, highestScore := findMaxKey(scores)fmt.Printf("最高分: %s 得分 %d\n", bestStudent, highestScore)
}

4. 并发安全

① Map 不是并发安全

func main() {regularMap := make(map[int]int) // 普通 map 在并发访问时会有问题var wg sync.WaitGroup // 并发读写会导致 panic// 启动多个 goroutine 同时写入for i := 0; i < 10; i++ {wg.Add(1)go func(id int) {defer wg.Done()for j := 0; j < 100; j++ {regularMap[id*100+j] = id}}(i)}// 启动多个 goroutine 同时读取for i := 0; i < 10; i++ {wg.Add(1)go func() {defer wg.Done()for j := 0; j < 100; j++ {_ = regularMap[j]}}()}wg.Wait()fmt.Println("普通 map 并发操作完成(可能 panic)")
}

② sync.Map 并发安全的 Map

func main() {// sync.Map 是并发安全的var safeMap sync.Map// 存储值safeMap.Store("key1", "value1")safeMap.Store("key2", "value2")// 读取值if value, ok := safeMap.Load("key1"); ok {fmt.Println("key1:", value)}// 删除值safeMap.Delete("key1")// 遍历fmt.Println("遍历 sync.Map:")safeMap.Range(func(key, value interface{}) bool {fmt.Printf("%v: %v\n", key, value)return true  // 返回 true 继续遍历,false 停止})// 原子操作if actual, loaded := safeMap.LoadOrStore("key3", "value3"); loaded {fmt.Println("key3 已存在,值为:", actual)} else {fmt.Println("key3 不存在,已存储新值")}
}

5. 性能优化

① 预分配容量优化

func main() {// 测试预分配容量的性能差异const size = 100000// 不预分配容量start := time.Now()map1 := make(map[int]int)for i := 0; i < size; i++ {map1[i] = i * 2}duration1 := time.Since(start)// 预分配容量start = time.Now()map2 := make(map[int]int, size)  // 预分配容量for i := 0; i < size; i++ {map2[i] = i * 2}duration2 := time.Since(start)fmt.Printf("不预分配耗时: %v\n", duration1)fmt.Printf("预分配耗时: %v\n", duration2)fmt.Printf("性能提升: %.2f%%\n", float64(duration1-duration2)/float64(duration1)*100)
}

② 内存使用优化

func main() {// 1. 及时删除不需要的键值对cache := make(map[string]string)// 模拟缓存使用for i := 0; i < 1000; i++ {key := fmt.Sprintf("key_%d", i)cache[key] = fmt.Sprintf("value_%d", i)}fmt.Printf("缓存大小: %d\n", len(cache))// 清理旧缓存for key := range cache {if len(key) > 10 {delete(cache, key)}}fmt.Printf("清理后大小: %d\n", len(cache))// 2. 重置 map(完全清空)// 方法1:重新创建cache = make(map[string]string)// 方法2:逐个删除(不推荐)// for key := range cache {//     delete(cache, key)// }
}

在这里插入图片描述

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

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

相关文章

Spring的深入浅出(6)--使用AOP的思想改造转账案例

在Spring中使用AOP&#xff08;基于注解&#xff09;使用AOP的思想改造转账案例业务类改造&#xff1a;移除事务管理代码public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.…

【LeetCode刷题指南】--有效的括号

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a; 《C语言》 《数据结构与算法》《C语言刷题集》《Leetcode刷题指南》 ⭐️人生格言&#xff1a;生活是默默的坚持&#xff0c;毅力是永久的…

【PyTorch】图像多分类项目

【PyTorch】图像二分类项目 【PyTorch】图像二分类项目-部署 【PyTorch】图像多分类项目 【PyTorch】图像多分类项目部署 多类图像分类的目标是为一组固定类别中的图像分配标签。 目录 加载和处理数据 搭建模型 定义损失函数 定义优化器 训练和迁移学习 用随机权重进行训…

详谈OSI七层模型和TCP/IP四层模型以及tcp与udp为什么是4层,http与https为什么是7层

一、网络模型&#xff1a;OSI七层 vs TCP/IP四层OSI七层模型 (理论参考模型):目的&#xff1a;提供一个标准化的理论框架&#xff0c;用于理解网络通信过程和各层的功能划分&#xff0c;促进不同厂商设备的互操作性。它是一个理想化的模型。分层 (从下到上):物理层&#xff1a;…

自然语言处理技术应用领域深度解析:从理论到实践的全面探索

1. 引言:自然语言处理的技术革命与应用前景 自然语言处理(Natural Language Processing,NLP)作为人工智能领域的核心分支,正在以前所未有的速度改变着我们的数字化生活。从最初的规则基础系统到如今基于深度学习的大语言模型,NLP技术经历了从理论探索到实际应用的深刻变…

Qt:qRegisterMetaType函数使用介绍

简介 在Qt中,qRegisterMetaType是一个用于向元对象系统注册自定义类型的函数。这对于需要在信号和槽中使用自定义类型(包括模板类如 std::shared_ptr)或用于排队连接(Queued Connection)非常重要。 作用: ​​使类型可用于信号与槽机制​​:特别是当信号和槽连接类型为…

《使用Qt Quick从零构建AI螺丝瑕疵检测系统》——5. 集成OpenCV:让程序拥有“视力”

目录一、概述1.1 背景介绍&#xff1a;赋予应用“视力”1.2 学习目标二、集成OpenCV2.1 安装OpenCV2.2 在Qt项目中配置CMake三、项目数据集介绍与准备四、图像的桥梁&#xff1a;ImageProvider与格式转换五、加载、转换并显示图像六、总结与展望一、概述 1.1 背景介绍&#xf…

Grafana

官网&#xff1a;https://grafana.com/zh-cn/grafana/ 文章目录GrafanaGrafana Grafana 是一个非常强大且流行的开源数据可视化和监控平台。公司能有 Grafana 平台来监控各种程序状态&#xff0c;是运维、开发和业务洞察的利器。 数据可视化&#xff1a; 这是 Grafana 的看家本…

go语言基础教程:【1】基础语法:变量

【1】基础语法 1. 注释 package mainimport "fmt"func main() {// 单行注释// 这是一个终端打印文本的功能/*这是一个多行注释这是一个多行注释这是一个多行注释*/fmt.Println("hello world!") }2. 变量 &#xff08;1&#xff09;变量的基本使用 package …

AI大模型各类概念扫盲

以下内容整理自AI&#xff0c;进行一个概念扫盲&#xff1a;Prompt&#xff08;提示词&#xff09; Prompt是用户提供给AI模型的指令或问题&#xff0c;用于引导模型生成特定输出。良好的Prompt设计能显著提升模型的任务理解能力和响应质量&#xff0c;例如通过结构化提示&…

【NLP舆情分析】基于python微博舆情分析可视化系统(flask+pandas+echarts) 视频教程 - 主页-评论用户名词云图实现

大家好&#xff0c;我是java1234_小锋老师&#xff0c;最近写了一套【NLP舆情分析】基于python微博舆情分析可视化系统(flaskpandasecharts)视频教程&#xff0c;持续更新中&#xff0c;计划月底更新完&#xff0c;感谢支持。今天讲解主页-评论用户名词云图实现 视频在线地址&…

Springmvc的自动解管理

中央转发器&#xff08;DispatcherServlet&#xff09;控制器视图解析器静态资源访问消息转换器格式化静态资源管理一、中央转发器Xml无需配置<servlet><servlet-name>chapter2</servlet-name><servlet-class>org.springframework.web.servlet.Dispatc…