Go语言学习Day6:数组与切片

名人说:莫愁千里路,自有到来风。 ——钱珝
创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)

目录

      • 1. 数组
        • ① 什么是数组
        • ② 数组的声明
        • ③ 初始化数组的几种方式
        • ④ 遍历数组元素
        • ⑤ 数组为值类型
        • ⑥ 数组的排序
        • ⑦ 多维数组
      • 2. 切片(动态数组的抽象)
        • ① 定义切片
        • ② make来创建切片
        • ③ 切片扩容
        • ④ 遍历切片
        • ⑤ 扩容的内存分析
      • 3、小结

Go语言中的数组和切片是管理集合数据的重要方式,具有不同的特性和用途。在这篇博客中,我们将深入探讨数组和切片的相关知识点,包括它们的定义、操作方法及其内部机制。

1. 数组

① 什么是数组

数组是同一种数据类型元素的集合。在Go语言中,数组是值类型,数组的长度在声明时就已经确定,且不能改变。关于数组可以这样理解,有一个收纳盒,专门用于收集同一类书的,但是这个收纳盒容量是有限的,自从这个收纳盒生产出来大小就已经确定了,而且不能再改变,而这个收纳同一类型的盒子也就是数组。

② 数组的声明

数组声明的基本语法格式为:

var arr [n]Type

其中n表示数组的长度,Type表示数组中元素的类型。

案例:数组的定义、赋值、打印

package main

import "fmt"

/*
数组:相同类型数据的集合
*/

// 数组
func main() {
    //array 数组定义,变量
    //数组也是一种数据类型
    //数组的定义:[数组的大小size] 变量的类型
    //意思为:定义一组该类型的数组集合,大小为size,最多可以保存size个数
    var arr1 [5]int
    //[0,0,0,0,0]
    //给数组赋值,下标index从0开始
    arr1[0] = 100
    arr1[1] = 200
    arr1[2] = 300
    arr1[3] = 400
    arr1[4] = 500

    //打印数组
    fmt.Println(arr1)

    //取出数组中的某个元素
    fmt.Println(arr1[1])

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

    //修改数组的值,index 1 代表数组中的第二个数据
    arr1[1] = 50
    fmt.Println("修改后的数组:", arr1)
    fmt.Println(arr1[1])
}

image-20240327172446685

③ 初始化数组的几种方式
  • 指定所有元素初始化
arr := [5]int{1, 2, 3, 4, 5}
  • 部分元素初始化,未初始化元素为零值
arr := [5]int{1, 2} // 其余元素初始化为0
  • 根据初始化值自动确定数组长度
arr := [...]int{1, 2, 3, 4, 5}

案例:数组初始化

package main

import "fmt"

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

    //快速初始化 :=
    arr2 := [5]int{1, 2, 3, 4, 5}
    fmt.Println(arr2)

    //需要注意的点
    //数据假如来自用户, 但不知道究竟有多少数据
    //... 写法
    //Go的编译器会根据数组的长度来给 ...赋值,自动推导长度
    //注意点:数组不是无限长,也是固定大小,大小取决于数组元素的个数
    var arr3 = [...]int{1, 2, 3, 4, 5, 6, 7, 8, 9}
    fmt.Println(len(arr3))
    fmt.Println(arr3)

    //数组默认值,给其中的几个位置赋值
    //可使用{index:值}的方法
    var arr4 [10]int
    arr4 = [10]int{1: 100, 5: 500}
    fmt.Println(arr4) // [0 100 0 0 0 500 0 0 0 0]
}

image-20240329001959507

④ 遍历数组元素

遍历数组元素可以使用for循环或for range循环。

arr := [...]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
    fmt.Println(arr[i])
}

或者

for index, value := range arr {
    fmt.Printf("Index: %d, Value: %d\n", index, value)
}

上面那种方式大家可能比较熟悉,C、C++等语言中都是类似的写法,但是下面这种方式如果没学过python等语言,可能了解会少一些,因此在个人在这里解释一下:

这段代码实现了什么?

for循环遍历arr数组(或切片)。

这段代码该怎么理解?

可以这样看,每次迭代,for循环会将当前元素的索引赋值给index变量,将当前元素的值赋值给value变量,然后执行循环体内的语句。而其中的range arr就是负责生成索引和对应值的部分,比如在循环刚开始,数组中index=0,arr[index] = 1,那么所生成的index值 = 0,value值 = arr[index] = 1。

让我们逐步解析这段代码:

  1. for index, value := range arr { ... }

    • for关键字开始一个循环。
    • index, value是每次迭代时,range表达式返回的两个值。index是当前元素的索引,value是当前元素的值。
    • range arr表示对arr进行遍历。arr可以是数组或切片。range每次迭代都会返回当前元素的索引和值。
    • { ... }中的代码是循环体,会对arr中的每个元素执行一次。
  2. fmt.Printf("Index: %d, Value: %d\n", index, value)

    • 这行代码在循环体内,使用fmt.Printf函数打印当前元素的索引和值。%d是格式化字符串,用于表示整数。\n是换行符。
    • 在每次迭代中,indexvalue将被设置为当前遍历到的元素的索引和值,然后将它们打印出来。

举个具体的例子,如果arr是一个包含三个元素的切片[10, 20, 30],那么输出将会是:

Index: 0, Value: 10
Index: 1, Value: 20
Index: 2, Value: 30

因此,一言以蔽之,这段代码的作用就是遍历数组(或切片)arr,并打印出每个元素的索引和值。

⑤ 数组为值类型

数组在Go语言中是值类型,当数组作为函数参数传递时,传递的是数组的副本而不是其引用。

func modifyArray(arr [5]int) {
    arr[0] = 10 // 这里修改的是数组的副本
}

案例:数组,值类型

 package main
 
 import "fmt"
 
 // 数组是值类型:所有赋值后的对象修改值不影响原来的对象
 func main() {
     //数组 [size]type
     arr1 := [4]int{1, 2, 3, 4}
     arr2 := [5]string{"yueliusu", "zilihuakai"}
     fmt.Printf("%T\n", arr1) //[4]int
     fmt.Printf("%T\n", arr2) //[5]string
 
     //数组的值传递和int等基本类型一致
     arr3 := arr1
     fmt.Println(arr1)
     fmt.Println(arr3)
 
     //修改arr3观察arr1是否会变化
     arr3[0] = 12
     fmt.Println(arr1)
     fmt.Println(arr3) // 数组是值传递,拷贝一个新的内存空间
 }

image-20240328001449100

⑥ 数组的排序

可以自定义实现,也可以使用sort包可以对数组进行排序。例如,对一个整型数组进行升序排序:

package main
import "fmt"
import "sort"

func main() {
	arr := [5]int{3, 1, 4, 5, 2}
	sort.Ints(arr[:]) // 将数组转换为切片
	fmt.Println(arr)
}

image-20240329121103889

案例:自己实现排序算法,冒泡排序

package main

import "fmt"

// 冒泡:每次筛选出一个最大或者最小的数.
/*
index   0   1   2   3   4
value   12  99  79  48  55
*/
// 冒泡排序逻辑,两两比较,大的往后移或者前移。 大
// 第一轮 : 12 79 48 55 99 // 5
// 第二轮 : 12 48 55 79 99 // 4
// 第三轮 : 12 48 55 79 99 // 3 //
// 第四轮 : 12 48 55 79 99 //
// 第五轮 : 12 48 55 79 99

// 代码实践
/*
    // 两个数判断,如果一个数大,则交换位置,大放到后面
    if arr[x] > arr[x+1] {
       arr[x], arr[x+1] = arr[x+1],arr[x]
    }
    // 多轮判断,for, 循环次数 【数组大小】
*/
func main() {
    arr := [...]int{12, 99, 79, 48, 55, 1, 110, 111, 23, 44, 21, 312, 123, 21, 312}
    fmt.Println("初始数组:", arr)
    //冒泡排序
    //1、多少轮
    for i := 1; i < len(arr); i++ {
       //2、筛选出来最大数
       for j := 0; j < len(arr)-i; j++ {
          //降序排序,比较大小,改变升降序只需要改变符号即可
          if arr[j] < arr[j+1] {
             arr[j], arr[j+1] = arr[j+1], arr[j] //平行赋值,无需第三者临时变量进行交换
          }
       }
    }
    fmt.Println(arr)
}

image-20240329002137133

⑦ 多维数组

多维数组可以通过嵌套数组来声明(俗称套娃)。

一维,线性

二维,表格,数组套数组

三维,立体,数组套数组套数组

多维,继续嵌套

var multiArr [2][3]int
multiArr[0] = [3]int{1, 2, 3}
multiArr[1] = [3]int{4, 5, 6}

案例:多维数组

package main

import (
    "fmt"
)

func main() {

    //多维数组
    //定义一个二维数组
    arr := [3][4]int{
       {0, 1, 0, 0}, //arr[0]
       {0, 0, 1, 0}, //arr[1]
       {0, 2, 0, 0}, //arr[2]
    }

    //二维数组
    fmt.Println(arr[0])
    fmt.Println(arr[0][1])
    fmt.Println("----------")
    //遍历二维数组
    for i := 0; i < len(arr); i++ {
       for j := 0; j < len(arr); j++ {
          fmt.Println(arr[i][j])
       }
    }
    // for range
    for i, v := range arr {
       fmt.Println(i, v)
    }
}

image-20240329002342053

2. 切片(动态数组的抽象)

① 定义切片

切片是对数组的封装,它提供了一个可动态扩展的序列。切片不存储任何数据,它**只是对底层数组的引用。**那么它与数组相同吗?

显然,切片与数组是不同的,切片的长度是动态的,可以根据需要扩展或缩减。切片背后实际上是对数组的引用切片是引用类型。

var slice []int

补充一下关于切片的基础概念:

  • 底层数组:每个切片都指向一个底层的数组,切片通过指针引用数组的一部分元素。
  • 长度(Length):切片的长度是它所包含的元素个数。可以通过内置的len函数获取。
  • 容量(Capacity):切片的容量是从其起始元素到底层数组末尾元素的个数。可以通过内置的cap函数获取。

案例:切片的定义

package main

import "fmt"

// 定义切片
func main() {

    arr := [4]int{1, 2, 3, 4} //数组,定长
    fmt.Println(arr)

    var s1 []int //切片,不定长,长度是可变的
    fmt.Println(s1)
    //切片空片段,初始的切片中,默认为nil
    if s1 == nil {
       fmt.Println("切片是空的")
    }

    s2 := []int{1, 2, 3, 4} //切片 不定长
    fmt.Println(s2)
    fmt.Printf("%T,%T\n", arr, s2) //[4]int,[]int
    fmt.Println(s2[1])
}

image-20240329122336156

② make来创建切片

通过make函数创建切片,可以指定切片的长度和容量。

//例如,创建一个长度为5,容量为10的切片
slice := make([]int, 5, 10)

案例: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 //runtime error: index out of range [7] with length 5
    //切片的底层还是数组 [0 0 0 0 0] [2000]
    //直接去赋值是不行的,不要用常规的惯性想法来考虑,不加思索说出答案,思考的过程很重要。
    fmt.Println(s1)

}

image-20240329123232663

③ 切片扩容

当向切片追加元素超过其容量时,Go会自动进行扩容,一般会翻倍增加,比如说原本容量为2,你要放3个,放不下了,它会自动扩容为4,依次类推,扩容为8、16等

slice := make([]int, 0, 2)
for i := 0; i < 10; i++ {
    slice = append(slice, i)
}

案例:切片扩容,自动翻倍

package main

import "fmt"

func main() {
    //这段代码意味着创建了一个整型切片,初始长度为0,容量为5
    s1 := make([]int, 0, 5)
    fmt.Println(s1)
    //切片扩容,append()
    s1 = append(s1, 1, 2)
    fmt.Println(s1)
    //问题:容量只有5个,那能放超过5个的吗?当然,切片是会自动扩容的
    s1 = append(s1, 2, 3, 4, 5, 6, 7, 8, 9)
    fmt.Println(s1)

    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])
    }

}

image-20240329125405692

④ 遍历切片

切片的遍历方式和数组相同,可以使用for循环或for range循环,因为本质上切片相当于是引用了数组,数组是一种数据类型,切片引用它,扩展了它,因此遍历方式没太大差别。

slice := []int{1, 2, 3, 4, 5}
for _, value := range slice {
    fmt.Println(value)
}
⑤ 扩容的内存分析

切片的扩容会导致内存重新分配及旧数据的复制,这可能影响性能。合理规划初始容量可以减少扩容的次数,提升性能。

slice := make([]int, 0, 1024) // 提前分配足够的容量

案例:切片扩容的内存变化

package main

import "fmt"

// 切片扩容的内存分析
// 结论
// 1、每个切片引用了一个底层的数组
// 2、切片本身不存储任何数据,都是底层的数组来存储的,所以修改了切片也就是修改了这个数组中的数据
// 3、向切片中添加数据的时候,如果没有超过容量,直接添加,如果超过了容量,会自动扩容,成倍增加,copy
// - 分析程序的原理
// - 看源码
// 4、切片一旦扩容,就是重新指向一个新的底层数组

func main() {
    //1、cap每次是成倍增加的
    //2、只要容量扩容后,地址就会发生变化
    s1 := []int{1, 2, 3}
    fmt.Println(s1)
    fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) //len:3,cap:3
    fmt.Printf("%p\n", s1)                          //0xc000016108

    s1 = append(s1, 4, 5)
    fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) //len:5,cap:6
    fmt.Printf("%p\n", s1)                          //0xc000010390

    s1 = append(s1, 6, 7, 8)
    fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) //len:8,cap:12
    fmt.Printf("%p\n", s1)                          //0xc00005e060

    s1 = append(s1, 9, 10)
    fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) //len:10,cap:12
    fmt.Printf("%p\n", s1)                          //0xc00005e060

    s1 = append(s1, 11, 12, 13, 14)
    fmt.Printf("len:%d,cap:%d\n", len(s1), cap(s1)) //len:14,cap:24
    fmt.Printf("%p\n", s1)                          //0xc00010c000
}

image-20240329125625358

案例2:copy方法

package main

import "fmt"

// copy方法
func main() {
    numbers := []int{1, 2, 3}
    fmt.Printf("len=%d,cap=%d,slice=%v\n", len(numbers), cap(numbers), numbers)

    //方法一:直接使用make创建切片扩容
    numbers2 := make([]int, len(numbers), cap(numbers)*2)
    //将原来的底层数据的值拷贝到新的数组中
    //func copy(dst,src []Type)int
    copy(numbers2, numbers)
    fmt.Printf("len=%d,cap=%d,slice=%v\n", len(numbers2), cap(numbers2), numbers2)
}

image-20240329125643799

3、小结

数组

  • 一组数
  • 数组是值传递的
  • 创建数组 [size]int
  • 数组的大小事不可变的
  • 二维数组,数组套数组
  • 冒泡排序

切片

  • 切片本事是不存在数据的
  • 底层是指向了一个数组
  • 如果我们存放的数据大于切片的容量,在底层就会扩容 copy > 1024 1.25倍

很感谢你能看到这里,如有相关疑问,还请下方评论留言。
Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊)
希望本篇内容能对大家有所帮助,如果大家喜欢的话,请动动手点个赞和关注吧,非常感谢你们的支持!

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

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

相关文章

云平台教程 | 手把手教你绘制时序分析

爱基百客云平台小工具——时序分析使用教程目录 1 爱基百客云平台之时序分析 2 参数设置 3 任务查看 4 结果 01 爱基百客云平台小工具使用 首先&#xff0c;打开爱基百客官网&#xff1a;http://www.igenebook.com&#xff1b;点击菜单栏最右侧“云平台”按钮。 弹出云平…

Qt实现Kermit协议

1 概述 Kermit文件运输协议提供了一条从大型计算机下载文件到微机的途径。它已被用于进行公用数据传输。 其特性如下: Kermit文件运输协议是一个半双工的通信协议。它支持7位ASCII字符。数据以可多达96字节长度的可变长度的分组形式传输。对每个被传送分组需要一个确认。Kerm…

红米手机Redmi 不会自动弹出USB调试选项,如何处理?(红米小米均适用)

参考&#xff1a; 红米手机Redmi 不会自动弹出USB调试选项&#xff0c;如何处理&#xff1f;&#xff08;红米小米均适用&#xff09; - 知乎 以红米9A为例&#xff1b; 【设置】菜单进入后&#xff0c;找到【我的设备】&#xff0c; 选择【全部参数】&#xff0c; 对准miui版…

什么是framebuffer,怎么应用(二)————如何打印BMP图片、字幕函数、字符串

如何切换到终端模式 在昨天写的文章中&#xff0c;没有写到如何切换到终端模式&#xff0c;在编译完函数之后&#xff0c;我们需要从桌面切换到终端模式&#xff1a; ALTCTRLF3切换到终端模式后&#xff0c;登录账号名与密码&#xff0c;其余操作均有桌面终端一样。 如何切换…

机器学习概论—增强学习

机器学习概论—增强学习 强化学习(Reinforcement Learning, RL)或者说是增强学习,是机器学习的一个领域,旨在使智能体通过与环境的交互学习如何做出决策,它是关于在特定情况下采取适当的行动来最大化奖励。它被各种软件和机器用来寻找在特定情况下应采取的最佳行为或路径…

Unity类银河恶魔城学习记录11-7 p109 Aplly item modifiers源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili ItemData_Equipment.cs using System.Collections; using System.Collecti…

第三篇:3.2 广告可见性 - IAB与MRC及《增强现实广告效果测量指南1.0》

翻译计划 第一篇 概述—IAB与MRC及《增强现实广告效果测量指南》之目录、适用范围及术语第二篇 广告效果测量定义和其他矩阵之- 3.1 广告印象&#xff08;AD Impression&#xff09;第三篇 广告效果测量定义和其他矩阵之- 3.2 可见性 &#xff08;Viewability&#xf…

计算机视觉的应用26-关于Fast-R-CNN模型的应用场景,Fast-R-CNN模型结构介绍

大家好&#xff0c;我是微学AI&#xff0c;今天给大家介绍一下计算机视觉的应用26-关于Fast-R-CNN模型的应用场景&#xff0c;Fast-R-CNN模型结构介绍。Fast R-CNN是一种深度学习模型&#xff0c;主要用于目标检测任务&#xff0c;尤其适用于图像中物体的识别与定位。该模型在基…

【Qt】常用控件(输入类)

目录 一、Line Edit二、Text Edit三、ComBo四、DateTimeEdit五、Slider 一、Line Edit QLineEdit 用来表示单行输入框&#xff0c;可以输入一段文本&#xff0c;但是不能换行。 属性说明test输入框中的文本inputMask输入内容格式约束maxLength最大长度frame是否添加边框echoM…

政安晨:专栏目录【TensorFlow与Keras机器学习实战】

政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨的博客能够对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff01; 本篇是作者政安晨的专栏《TensorFlow与Keras机器…

【职场攻略】撰写求职信的艺术:如何用AI技术让你脱颖而出

AI与求职的完美结合&#xff1a;会话式AI产品如何助力你的求职之路&#xff1f; 在当今竞争激烈的求职市场中&#xff0c;一封精心准备的求职信可以为你打开通往理想工作的大门。一封好的求职信不仅能展示你的专业技能和工作经验&#xff0c;还能体现你对职位的热情和对公司文化…

labelme AI 模型运用

一、lebelme 1、界面介绍 点击上图位置&#xff0c;选择对应的模型。这里我每个模型都测试了一下&#xff0c;EfficientSam这个模型最好用&#xff0c;准确率和速度都ok。 2、使用方法 目标框标注方法&#xff1a;点左上角【编辑】-> 【Create Ai-Mask】就可以标志了&…

【智能家居项目】RT-Thread版本——DHT11获取温湿度 | MQTT上传到服务器 | 服务器控制外设

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《智能家居项目》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 这篇文章中&#xff0c;本喵将使用RT-Thread Studio来实现这个智能家居的项目&#xff0c;最终…

AXS2003B 爱协生 2.4W单通道 AB类音频功率放大器 兼容LM4890 低成本

AXS2003B 是一颗单通道AB类音频功率放大器。在5V电源供电&#xff0c;THDN10%&#xff0c;4欧姆负载上可以输出2.4W 的功率。 AXS2003B优异的噪声和THD指标可以提供高品质的音频信号放大。极少的外围元件就能提供芯片稳定工作&#xff0c;大大减少了PCB面积并降低成本。 AXS20…

SSL证书一年多少钱?有便宜的吗?

SSL安全证书的价格因其类型、品牌、验证级别、附加功能&#xff08;如多域名支持、通配符功能等&#xff09;以及购买时长&#xff08;通常以年为单位&#xff09;的不同而有所差异。以下是大致的价格范围&#xff1a; 永久免费SSL证书_永久免费https证书_永久免费ssl证书申请…

广告买量的数据驱动策略:从归因到精准投放

在广告买量场景下&#xff0c;数据驱动一定是有意义的。对中小型企业和产品而言&#xff0c;起量和精准是重点&#xff0c;毕竟他们更关注ROI&#xff08;短期利润&#xff09;&#xff0c;这也是效果广告专注中小型企业的原因。而大企业的核心是把流量合理导入自身构建的生态中…

哲学家带你深♂入了解文件操作

目录 一、文件指针 二、文件的打开与关闭 三、顺序读写函数的介绍 四、文件的随机读写 1、fseek 2、ftell 3、rewind 总结 前言 c语言中的文件操作虽然不怎么常用但也是非常重要的知识&#xff0c;今天由本哲学家带大家深♂入了解c语言文件操作。 一、文件指针 每个被使用的文…

Day24:回溯法 LeedCode 77.组合

回溯法解决的问题都可以抽象为树形结构 for循环就是遍历集合区间&#xff0c;可以理解一个节点有多少个孩子&#xff0c;这个for循环就执行多少次。 从图中看出for循环可以理解是横向遍历&#xff0c;backtracking&#xff08;递归&#xff09;就是纵向遍历&#xff0c;这样就把…

virtualbox 日常运维

前言 虽然平常以macOS和Linux作为主打工作环境&#xff0c;但还是有很多需要用到windows的时候&#xff0c;如camtasia和券商QMT软件。 在二手ThinkPad P53上安装了几个windows虚机&#xff0c;作为测试环境。Mac笔记本远程桌面连接嫌麻烦&#xff0c;还是命令行舒服。MacOS自…

SAP gui 组服务器 提示 Error service sapmsPRD unknown

/etc/hosts 追加IP地址和域名的配对关系 /etc/services 追加 sapms[sid] 3601/tcp