其它数据类型 | 青训营笔记

119 阅读6分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

1. 基本数据类型

1. 整型

  1. int 32位机是32位也就是4个字节,64位机是64位也就是8个字节,有符号
  2. int8 8位,1个字节,有符号
  3. int16 16位,2个字节,有符号
  4. int32 32位,4个字节,有符号
  5. int64 64位, 8个字节,有符号
  6. uint 和int一样,不过是无符号
  7. uint8, uint16, uint32, uint64 和int一样,不过是无符号的
  8. byte uint8的别称,8位一个字节,范围为0 ~ 255
  9. rune int32的别称,32位4个字节

2. 浮点型

  1. float32:32位4个字节,注意浮点数和整型在计算机中存储的是不一样的,这个范围是要比int32大的
  2. float64:64位8个字节

3. 复数

  1. complex64:64位8个字节的复数
  2. complex128:128位16个字节的复数

4. 布尔型

  1. bool true or false, bool类型的值不会自动转换成0或者1,反之亦然,更不能强转,这点和Java是一样的

5. 字符串

  1. Go语言中的字符串是基本数据类型,是有一系列字符构成的定长序列,实际上就是一个定长byte数组
  2. Go语言中的字符串是一个个字节组成的,1 ~ 4个字节一组(这个根据实际字符情况来定,这样可以极大的简化内存),如果获取字符串中的每个字符是rune类型4个字节,使用16进制打印的时候可以看到是4位(中文)
  3. 使用关键字string定义
  4. 可以使用""和``来定义字符串
    1. ""不能跨行,里面可以有转义字符
    2. ``可以跨行,但是里面不能有转义字符
  1. 字符串的操作符
    1. [] 方括号可以直接访问到对应的字节
    2. 可以使用切片,有如下几种情况
str := "hello, world!"
fmt.Println(str[0])  // 获取第一个字节,实际上就是打印对应的字节码,8位,0 ~ 255,切记这不是字符
fmt.Println(string(str[0]))  // 打印第一个字节所对应的字符(实际上就是按照ASCII码值打印)
fmt.Println(str[0:1])  // 切片,注意左闭右开,打印第一个字符
fmt.Println(str[2:])  // 从第三个字符开始一直到最后
fmt.Println(str[:2])  // 打印第1,2个字符
fmt.Println(len(str))  // 获取字符串的字节数,注意不是字符数
fmt.Println(utf8.RuneCountInString(str))  // 获取字符串字符数,实际上就是字符串中统计rune类型
fmt.Println([]rune(str))  // 将字符串中所有字节数全部转换为码点值
  1. 字符串是不可修改的字符串序列,如果想要修改的话需要进行类型转换,转换成byte或者rune数组
    1. by := []byte("hello, world!")
    2. by := []rune("hello, world!")
    3. 然后再直接修改数组就行,输出的时候使用str(by)包裹数组就行
  1. 字符串和基本数据类型的转换
    1. 基本数据类型转换成string类型
      1. 方式一:fmt.Sprintf(format string, a...interface{})
      2. 方式二:strconv.FormatInt()等方法
    1. string转换成基本数据类型
      1. strconv.ParseInt()等,返回两个参数
// 1. 基本数据类型转换成字符串类型
// 1.1 方法一:使用Sprintf()方法
var a = 100
fmt.Println(fmt.Sprintf("%d", a))
// 1.2 方法二:使用strconv.FormatInt()方法
fmt.Println(strconv.FormatInt(int64(a), 10))

// 2. 字符串类型转换为基本数据类型
var str = "123"
fmt.Println(strconv.ParseInt(str, 10, 64))
  1. 与字符串有关的常见API
// 1. 获取字节数
str1 := "hello"
fmt.Printf(len(str1))

// 2. 字符串遍历,注意转换成[]rune切片

// 3. 基本数据类型转字符串
// 3.1 使用fmt.Sprintf()函数
fmt.Sprintf("%d", 12)
// 3.2 使用strconv.Format()函数
strconv.FormatInt(int64(1), 10)
// 3.3 使用strconv.Itoa()函数
strconv.Itoa(123)

// 4. 字符串转基本数据类型
// 4.1 使用strconv.ParseInt()方法
strconv.ParseInt("123", 10, 64)
// 4.2 使用strconv.Atoi()函数
strconv.Atoi("123")

// 5. 10进制字符串转换为2, 8, 16进制的字符串
strconv.FormatInt(int64(1), 2/8/16)

// 6. 字符串是否包含函数Contains() bool
strings.Contains(s string, substr string)

// 7. 统计一个字符串中有几个子串Count() int
strings.Count(s string, substr string)

// 8. 不区分大小写的比较字符串
strings.EqualFold(s1 string, s2 string)

// 9. 返回子串在母串中第一次出现的位置
strings.Index(s string, substr string)

// 10. 返回子串在母串中最后一次出现的位置
strings.LastIndex(s string, substr string)

// 11. 字符串替换,用子串替换母串中指定的内容,可以通过最后一个参数指定替换的个数, -1表示全部替换
strings.Replace(s string, strIns string, sustr string, num int)

// 12. 字符串切分Split()
strings.Split(s string, sep string)

// 13. 字符串大小写转换
strings.ToLower(str string)
strings.ToUpper(str string)

// 14. 将字符串两边的空格字符去掉
strings.TrimSpace(str string)

// 15. 将字符串左边/右边指定字符去掉
strings.TrimLeft(str string, cutSet string)
strings.TrimRight(str string, cutSet string)

// 16. 判断字符串是否以指定的前缀开始或者指定的后缀结束
strings.HasPrefix(str string, target string)
strings.HasSuffix(str string, target string)

2. 复合类型

1. 数组

  1. 数组是一系列相同类型的元素的集合
  2. 数组的定义有如下几种情形
// 1. 声明数组,随后赋值
var a [3]int
a = [3]int{1, 2, 3}

// 2. 数组赋值,使用...可以自动推断数组个数
var a1 = [...]int{1, 2, 3}

// 反正方括号里面要不指定具体值,要不使用...进行自动类型推断
// 方括号指定的数组大小必须大于或者等于数组中的元素值,绝对不能小于

/*
var a1 [3]int = [3]int{1, 2, 3}
var a2 = [3]int{1, 2, 3}
var a3 = [...]int{1, 2, 3}
var a4 = [...]int{0: 1, 1: 2, 2: 3}  // 这个顺序还可以打乱如下所示
var a5 = [...]int{2: 2, 0: 10, 1: 12}
*/

// 数组常见的遍历方式
// 1. 常规遍历
var a2 = [3]int{1, 2, 3}
for i := 0; i < len(a2); i++ {
    fmt.Println(a[i])
}

// 2. for-range遍历
for key, val := range a2 {
    fmt.Println(val)
}

// 下面举几个二维数组的例子
// var 数组名 [大小][大小]类型 = [大小][大小]类型{初始化}
// var 数组名 = [大小][大小]类型{初始化}
// var 数组名 = [...][大小]类型{初始化}  行数可以不指定,但是列数一定要指定,行数编译器可以进行自动类型推断
// var 数组名 [大小][大小]类型 = [...][大小]类型{初始化}

// 下面是数组遍历的两种方式
var arr = [...][3]int{{1, 2, 3}, {4, 5, 6}, {7, 8, 9}}
	// 1. 传统for循环遍历二维数组
	for i := 0; i < len(arr); i++ {
		for j := 0; j < len(arr[i]); j++ {
			fmt.Printf("%d ", arr[i][j])
		}
		fmt.Println()
	}
	fmt.Println("----------------------------------")
	// 2. 使用加强版的for循环遍历二维数组
	for _, v1 := range arr {
		for _, v2 := range v1 {
			fmt.Printf("%d ", v2)
		}
		fmt.Println()
	}
  1. Go语言中的数组是值类型,不是指针类型,数组作为参数的时候传递的是数值
  2. Go中数组的长度也是数组的一部分

2. 结构体

  1. Go中没有class类的概念,实际上结构体就相当于是一个类
  2. 结构体里面只能定义字段不能定义方法
  3. 举一个栗子
// 1. 定义一个结构体
type Student struct {
    no int
    name string
    age int
    sex byte
}

// 2. 定义结构体变量,给结构体赋初始值,可以指定key也可以不指定key,并且可以初始化一部分
obj1 = Student{10, "Tom", 10, 1}
obj2 = Student{no: 11, name: "Jerry", age: 20, sex: 0}
obj3 = Student{no: 12, name: "Dog", age: 21}

// 3. 结构体成员的访问(不管是结构体成员还是结构体变量都是通过成员运算符.来访问)
// 结构体.成员名

// 关于map的增删改查
// 1. 增加:只要添加的key事先不存在,就是添加
// 2. 修改:如果key已经存在了,那就是修改
// 3.1 删除:使用内置的delete(m map[Type][Type1], key Type)  如果键存在的话就删除,键不存在的话也不会报错,而是没有任何操作
// 3.2 还可以让原先的map指向另外一个地方,这样原来的存储空间就会被GC自动回收
// 4. 查找:直接:变量名val, ok := [key],如果这个key存在的话就会把key对应的val值赋值给val,然后ok
  1. 结构体变量和结构体指针都可以通过成员访问运算符访问结构体中的字段,Go语言编译器会自动转换成(*结构体指针).变量名进行打印
  2. 结构体创建变量的时候分配的内存空间是连续的
  3. Go语言中结构体也可以强制类型转换,但是要求强转的两个结构体字段名和类型要完全一致
  4. struct每个字段上都可以写上一个tag,这个tag可以通过反射机制获取

3. 引用类型

.1 map

  1. 语法:map[type1]type2 第一个type1是key的类型,第二个type2是value的类型
  2. map的值是动态增长的,如果没有初始化的就是nil
  3. map没有固定长度,是可以动态变化的,但是我们仍然可以在定义map的时候就制定map的容量大小,出于性能的考虑,如果事先知道map的大致容量的话最好先指定
  4. 举几个栗子
// 1. 只声明一个map但是不初始化
var m1 map[string]int  // m1 == nil,实际上这个时候m1指针是占用地址的,但是m1没有初始化结构体,所以没有指向底层的存储空间

// 2. 初始化map
m2 := map[string]int{
    "k1": 1
    "k2": 2
}

// 3. value也可以是map类型
m3 := map[string]map[string]int{
    "k1": {"k11": 1}
    "k2": {"k22": 2}
}

// 4. value还可以是数组或者是切片类型或者是指向切片的指针
m4 := map[string][3]int
m5 := map[string]*[]int


// 遍历map有如下几种方式:
// 1. 方式一:使用for-range遍历
  1. map中的键值对是无序的,无法排序,我们如果想要对map中的键值对进行排序的话,只能先使用for-range循环遍历map键值对,取出key然后保存在对应的数组中,之后对这个数组按照想要的顺序进行排序,然后取出map中的内容

2. slice

1. 什么是切片
  1. 切片就是对数组的一个连续片段的引用,所以说切片是一个引用类型
  2. 切片更类似于C/C++中的数组,亦或是Python语言中的list
  3. 切片默认指向一段连续的存储区域,这个连续的存储区域可以是数组也可以是切片本身
  4. 切片是动态类型,只能和nil比较
2. 切片有哪些部分组成
  1. 主要是三部分
    1. pointer:指向连续存储区域的起点的指针
    2. len:表示当前切片的长度
    3. cap:表示当前切片的容量(cap总是大于等于len)
3 切片举例
// 1. 切片的几种常见的起始位置和结束位置
a := [...]int{1, 2, 3}
fmt.Println(a[1:2])  // 取第二个元素
fmt.Println(a[1:])  // 取第二个元素到最后一个元素,a[1:len(a)]的简写
fmt.Println(a[:2])  // 取第一第二个元素,a[0:2]的简写
fmt.Println(a[:])  // 取所有的元素,a[0:len(a)]的简写
fmt.Println(a[0:0])  // 直接是一个空的切片


// 2. 切片除了可以从数组或者是字符串等获取以外,还可以直接声明
var slice1 []int  // 声明整型类型的切片,这个时候slice1 == nil(这是一个引用类型)
var slice2 []string  // 声明字符串类型的切片,slice2 == nil
var slice3 = []int{}  // 声明一个空的切片,但这个时候slice3 != nil

// 3. 可以使用make创建指定大小的切片
slice4 := make([]int, 6)  // len = cap = 6
slice5 := make([]int, 6, 10)  // len = 6, cap = 10,这个cap分配大一点可以在添加元素的时候减少性能消耗
// 注意使用make函数创建切片和直接从数组中截取切片的区别
// 如果切片从数组中提取的话,那么是不会有内存空间的分配的
// 如果切片使用make函数创建的话,会有内存空间的分配,底层还是创建了一个数组
a := 1

3. channel

4. pointer

  1. Go语言中也有C系语言指针的概念
  2. *是指针的解引用,&是取地址运算符,*指针实际上就相当于原变量