signed

QiShunwang

“诚信为本、客户至上”

Go 学习之数据类型及其基本使用详解

2021/6/24 17:21:33   来源:

数据类型

我愿称 golang 为究极强类型语言,虽然类型声明可省略,但变量之间的类型要求很严格,具体看数据类型转换部分。

基本类型:布尔型,数字类型,字符串类型,数组

其他类型:rune

高级类型:Map(集合),指针,结构体(go里面没有类),接口,管道(Channel):做并发常用,切片(Slice),函数

PS:下面代码块中的代码,有的并不是一整块连续的代码,而是我进行分类后,对不同的例子的验证代码,所以不要直接copy后放到IDE里运行,会报错....请根据注释+空行进行区分。

1、布尔型 : true or flase

 var vname bool = true

easy。

2、数字类型

  • 整型有:int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, byte

  • 浮点型有:float32(单精度), float64(双精度), complex64 (32 位实数和虚数), complex128 (64 位实数和虚数)

数字类型有无符号长度表示范围
int81个字节(8bit)-128 ~ 127
int162个字节(16bit)​ ~ ​
int324个字节(32bit)​ ~ ​
int648个字节(64bit)​ ~ ​
int (默认)32位操作系统:32bit 64位操作系统:64bit
byte1个字节 (8bit)0 -255
单精度float324个字节(8bit)-3.403E38 ~ 3.403E38
双精度float64(默认)8个字节(8bit)-1.798E308 ~ 1.798E308

无符号整型就不再列举

当整型溢出的时候,golang 的编译器会直接向你报错,而不是显示出溢出后的数值;当给 uint、byte 赋值带负号的时候,直接报错;

(contains overflows)

开发中浮点类型的选择推荐选择 float64。复数类型建议选择 complex128

在选择数据类型时,我们尽量选择能满足变量使用长度的最小字节长度的数据类型。

num1 := .512   // 简写0.512
 fmt.Println(num1)   // 0.512
 ​
 // 科学计数法
 num2 := 1.234E2 // = 1.234 * (10^2)
 fmt.Println(num2)  // 123.4
 ​
 /* complex64 complex128 类型的使用 */
 var num1 complex128 = complex(50,75)
 fmt.Println(num1)    //(50+75i)
 ​
 var num2 complex128 = complex(60, 80)
 ​
 fmt.Println(num1 + num2)  //(110+155i)
 ​
 // 比较 实数部分相同、虚数部分相同,则复数才相同
 fmt.Println(num1 == num2)   //false
 ​
 // 取实部、虚部的值
 var realNum = real(num1)
 var imageNum = imag(num1)
 fmt.Println("real=",realNum, "image=", imageNum)  //real= 50 image= 75

其他的复数操作方法在 math/cmplx 包中找

3、字符串类型:string,Go 语言的字符串的字节使用 UTF-8 编码标识 Unicode 文本。

Go语言中单引号和双引号是有严格的区别的,字符串类型只能用 双引号" "或反引号 ``, 字符类型用 单引号 ' '

 var str = "This is a test"

Go语言用 byte 来表示单个字母,本质是一个整数(运算啥的也是先换成整数)

 var alpha1 byte = 'a'
 var alpha2 byte = 't'
 var alpha3 byte = 'b'
 ​
 fmt.Println(alpha1) // 这个会打印整型类型:97
 fmt.Printf("%c", alpha1)   // 格式化输出字符:a
 ​
 // 注意下面两个是不同的,一个是字符0,一个是整型0
 var a1 byte = '0'
 var a2 byte = 0

Go语言采用UTF-8编码

PS:当输入的字符串超出了ASCII码范围时(如汉字),可以用 int 类型接收。

var hanzi1 int = '无'
var hanzi2 int = '心'
fmt.Println(hanzi1,hanzi2)  // 26080 24515 这分别是汉字无心的Unicode编码,Go语言的字符串是采用Unicode编码的
fmt.Printf("%c%c", hanzi1, hanzi2)  // 无心

说是整型,其实应该是rune数据类型

字符串类型,字符串以字节为单位,是由一个一个字节所构成的 (可以用 str[index] 数组形式访问到对应下标的字符):

var str1 = "无心"
var str2 string = "Golang"
var str3 = str1 + str2  // 可以用 + 拼接两个字符串
 ​
// 这样拼接也是对的
var str = "wu" +
         "xin"
 ​
// ☆但这样拼是错的....(+ "xin" evaluated but not used),所以 + 要在上面,本质还是golang会在每行后面填分号
var str = "wu"
         + "xin"
 ​
 ​
// 在go中,字符串是常量,不可变的!一旦赋值就不可再改变
str1 = "有心"   // 可以,因为这个等于直接将字符串指针指向另一个字符串
//str2[0] = "J"  // can not assign to str2[0]
fmt.Println(str1)   // 有心
 
// 用反引号 —— 字符串会以原生形式输出
var str = `wuxin\a\b\n\t\r''""\ wuxin` //中间的特殊字符均会以原生形式输出
fmt.Println(str)  //wuxin\a\b\n\t\r''""\ wuxin

4、其他类型

  • rune ≈ int32,rune表示一个Unicode码点,可以用来存放中文

5、数组

var v_name [array_size] v_type

 // 声明并初始化
 var numbers = [10]int{1,2,3,4,5,6,7,8,9,10}
 ​
 // 声明后赋值
 var numbers[5] float64
 numbers = [5]float64{1.1, 2.2, 3.3, 4.4, 5.5}

如果数组长度不确定,可以使用 ... 代替数组的长度,编译器会根据元素个数自行推断数组的长度:

numbers := [...]int{1,2,3,4,5}

可以指定下标初始化为特定值,其余位置初始化为默认值

 numbers := [...]float64{0:8.8,5:8.8}
 fmt.Println(numbers)  // 输出:[8.8 0 0 0 0 8.8]

注意虽然我初始化的时候只写了2个,但是由于指定了下标为5的元素初始值,所以编译器默认我们有6个长度,其中下标1,2,3,4都是默认值0.

访问数组中的数据:

 // 就下标访问呗...
 numbers[0]

多维数组:

var variable_name [SIZE1][SIZE2]...[SIZEN] variable_type
 var array[5][5] int = [5][5]int{{1,1,1,1,1},
     {1,1,1,1,1},
     {1,1,1,1,1},
     {1,1,1,1,1},
     {1,1,1,1,1}, }
 fmt.Println(array)

与C相同的是:不能用变量来赋值数组长度

 var i int = 10
 var numbers = [i]int{1,2,3,4,5,6,7,8,9,10}

报错:Invalid array bound 'i', must be a constant expression

6、Map(集合)

Map就是无序键值对(key->value),Map内部采用 hash 实现,因此是无序的,我们把键值对往 Map 里塞入或取出的时候,无法决定键值对在 Map 里的顺序。

Map 的声明、初始化:

 // 声明
 var vmap map[string] string
 ​
 // 使用make声明
 vmap := make(map[string]string)
 ​
 // 声明并初始化
 vmap := map[string]string{
     "name":"wuxin",
     "age":"21",
 }
 fmt.Println(vmap)   // 输出:map[age:21 name:wuxin]

Map 的赋值、取值、删除键值对:

vmap := map[string]string{
         "name":"wuxin",
         "age":"21",
 }
 ​
 // 1.赋值,可以这样就直接添加新的键值对了,也可以改现有键值对的内容(改值!)
 vmap["university"] = "HIT"
 vmap["name"] = "youxin"
 ​
 // 2.取值,第一个返回值是值,第二个返回值代表”有没有这个键?“
 name, isHas := vmap["name"]
 fmt.Println(name, isHas)   // 输出:youxin true
 if(isHas){
     fmt.Println(name)
 }
 ​
 // 3.删除键值对
 delete(vmap, "university")
 ​
 // 4.Map的遍历
 for key := range vmap {
     fmt.Println("键=", key, "值=", vmap[key])
 }

7、指针

Golang 中的指针与 C 语言里的指针用法差不多,一个 Golang 里的指针如果被定义但没有指向任何地址,则为空指针 nil。Golang 里的指针也可以指向变量、指向函数作为函数指针、指向数组作为数组指针来访问数组、指向指针(指向指针的指针),都差不多的。

 var ptr *int
 var i int = 114514
 ptr = &i //指针指向整数变量i的内存地址
 ​
 // 用指针访问i的值(其实就是直接访问内存的值)
 fmt.Println(*ptr)  // 输出:114514
 ​
 // 修改内存地址中的内容:
 *ptr = 10086
 fmt.Println(*ptr)  //输出:10086
 ​
 // 当然,直接打印指针,输出的也是地址:
 fmt.Println(ptr)  //输出:0xc00000a098

指向数组的指针 —— “数组指针” 和 “指针数组”:

// 一维数组:
 ​
 // 1.数组指针,一个指针,指向一个数组:
 a := []int{1,2,3,4,5}
 var ptr *[]int = &a
 fmt.Println(*ptr)  // [1 2 3 4 5]
 ​
 // 通过数组指针顺序、依次访问数组元素
 var i int
 for i=0 ;i<5;i++ {
     fmt.Println((*ptr)[i])
 }
 ​
 // 2.指针数组,一堆指针,分别指向不同的元素
 a := []int{1,2,3,4,5}
 var ptr [5]*int
 var i int
 ​
 // 其实是5个指针,依次指向了5个数组元素
 for i=0;i<5;i++ {
     ptr[i] = &a[i]
 }
 ​
 for i=0;i<5;i++ {
     fmt.Println(*ptr[i])
 }
  • 数组指针 var ptr *[]int

  • 指针数组 var ptr [size]*int

 // 2.二维数组
 var t_array = [2][2]int{
     {1,2},
     {3,4},
 }
 var ptr *[2][2]int = &t_array
 var ptr2 = &t_array //emmm...其实可以省略数据类型,这样因为有'&'的存在,ptr2默认就是个指针哦!
 ​
 fmt.Println(*ptr2, *ptr)
 fmt.Println(ptr2, reflect.TypeOf(ptr2))  //&[[1 2] [3 4]] *[2][2]int

8、结构体(go里面没有类)

struct 就是一堆属性的集合体,这些属性的数据类型可以不相同,但是 struct 里面不能定义函数(这点与“类”还是不同的),但我们可以在结构体外“给结构体”定义方法!

type My struct {
     name string
     age int
     university string
     major string
     vmap map[string]string // 集合啥的其他所有数据类型都可以
     hobbys [] Hobby  // 也可以嵌套,结构体数组
 ​
 }
 ​
 type Hobby struct {
     name string
     startYear int
 }

声明 struct 变量并初始化:

var my My = My{"wuxin",
                 21,
                 "hit",
                 "InformationSecurity",
                 map[string]string{
                         "key1":"value1",
                         "key2":"value2",
                         },
                 []Hobby{
                     {"Cartoon", 2013 },
                     {"Fiction", 2015},
                 }}
 fmt.Println(my)

struct 的访问、赋值、使用:

// 使用 "." 来访问结构体元素
 name := my.name
 age := my.age
 fmt.Println(name, age)
 ​
 // 当然,可以修改结构体元素值
 my.university = "Beijing University of Posts and Telecommunications"
 ​
 // 像Map, 嵌套struct这种,也是可以嵌套访问、修改
 my.vmap["key3"] = "value3"
 fmt.Println(my.hobbys[1].name)  // 输出: Fiction

给结构体定义方法

func (my My) doing() {
     fmt.Println("studying golang...")
 }
 ​
 func (my My) getName() string {
     return my.name
 }
 ​
 func (my My) setAge(int a) {
     my.age = a
 }

9、接口

 // 定义接口
 type Pet interface{
     yell()  // 无返回值,可以在后面带一个返回值类型表示有返回值
 }
 ​
 // 定义实现接口的类型
 type Cat struct {
     name string
 }
 ​
 func (cat Cat) yell() {
     fmt.Println("nya! nya!")
 }
 ​
 type Dog struct {
     name string
 }
 ​
 func (dog Dog) yell() {
     fmt.Println("wang! wang!")
 }
 ​
 ​
 func main() {
     // 定义类型Cat,这个猫实现了Pet接口,因此有其独有的yell()方法
     var cat Pet
     cat = new(Cat)
     cat.yell()
 ​
     // 同理Dog
     var dog Pet
     dog = new(Dog)
     dog.yell()
 ​
 }

这里我虽然给 Cat, Dog 一个 name 的成员变量,但是由于接口类型的 Pet 里面并没有,所以访问不到0.0 ,得通过写方法 setName(), getName() 来操作成员变量 name 了。通过给Cat, Dog结构体加方法。

10、管道(Channel):做并发常用

管道是Go语言在语言级别上提供的goroutine间的通讯方式,我们可以使用channel在多个goroutine之间传递消息。channel是进程内的通讯方式(线程间的通讯方式),是不支持跨进程通信的,如果需要进程间通讯的话,可以使用Socket等网络方式。

一个管道只能传递一种类型的值,且管道中的数据是先进先出的

声明:var chanName chan ElemType

var ch chan int // 定义一个管道变量 ch, 用来传递int类型的数据。
 // 这样也行
 ch := make(chan int)
 ​
 // 甚至可以使用一个map,让他的值是一个一个管道变量
 var vmap map[string]chan int
 ​
 // 向chan中写数据
 ch<- value
 ​
 // 从chan中读数据
 value := <-ch

更具体的在 go 并发编程里描述。

11、切片(Slice)

Slice是对数组的抽象,数组的长度是不可变的,Slice对数组进行抽象,截取数组的某一范围(不会改变原数组),slice的长度是动态的,可以对 slice 进行追加元素等操作,可以当成一个动态数组使用 (优点类似于 Java 里的 List 把,增加了灵活性)。

定义切片:

// 1.直接定义切片 —— 相当于定义了一个动态数组
 // make([]type, len, capacity) len->长度, capacity->最大长度(容量)
 s1 := make([]int, 10)
 ​
 // 或这样,所以不带长度的 []int{..}其实创建的是切片,而不是严格意义上的数组。
 s2 := []int{1,2,3,4,5,6,7,8,9,10}
 ​
 ​
 // 2.从现有数组中定义切片 —— 相当于对数组的截取使用
 var a = []int{1,2,3,4,5,6,7,8,9,10}
 ​
 // 从头到尾
 s1 := a[:]
 fmt.Println(s1)  // [1 2 3 4 5 6 7 8 9 10]
 ​
 // 从下标为2的元素截取到下标为5的前一个元素
 s2 := a[2:5]
 fmt.Println(s2)  // [3 4 5]
 ​
 // 从下标为2的元素截取到数组末尾
 s3 := a[2:]
 fmt.Println(s3)  // [3 4 5 6 7 8 9 10]
 ​
 // 从数组开始截取到下标为8的元素的前一个元素
 s4 := a[:8]
 fmt.Println(s4)  // [1 2 3 4 5 6 7 8]

切片的基本操作:

// 1. len() -> 获取切片长度
 fmt.Println(len(s4))   // 8
 ​
 // 2.cap() -> 获取切片的容量(最大长度)
 fmt.Println(cap(s4))   // 10(因为我没设置,而数组a里面有10个元素,所以他capacity默认就是10了)
 ​
 // 设置一下
 s5 := make([]int, 5, 20)
 fmt.Println(s5, cap(s5))  // [0 0 0 0 0] 20
 ​
 ​
 // 3.追加元素append()
 // 可以一次添加任意个数的元素,别忘了返回,返回值覆盖原切片才会实际上真正的在原切片末尾添加值
 s4 = append(s4,9,10)
 fmt.Println(s4)  // [1 2 3 4 5 6 7 8 9 10]
 ​
 // 当然可以添加元素后赋值给其他切片,这样原切片是不变的。
 s5 = append(s4, 9,10,11,12)
 fmt.Println(s4)  //[1 2 3 4 5 6 7 8 9 10]
 fmt.Println(s5)  //[1 2 3 4 5 6 7 8 9 10 9 10 11 12]
 ​
 // 4.切片拷贝copy()
 s7 := make([]int, 14, 500)
 copy(s7, s5)  // s5 拷贝给 s7,因为长度设置的14所以拷贝前14个(没有的设置默认值,int的默认值是0)
 fmt.Println(s7,cap(s7))  // [1 2 3 4 5 6 7 8 9 10 9 10 11 12] 500

12、函数

对于每个语言来说,函数都是最基础、最常见且广泛的东西了,在学习函数的时候具体记录把。

strconv 的使用

strconv 就是一个综合了好多格式转换函数的一个包,十分简单。看手册或源码完全就能摸懂使用方式。

strconv中有一族函数叫 FormatXxxx() ,这个是将 Xxx 类型转换为 string 类型的函数;还有一族函数叫 ParseXxx(),这个是将 string 类型转换为 Xxx 类型的函数。可以按这个规则去找,然后按需使用。

1、strconv.FormatFloat : 将 float 类型转换为 string 类型

strconv.FormatFloat(v_name, 格式标记, 小数部分精度, float32|float64)

格式标记:

  • ‘b’ (-ddddp±ddd,二进制指数)

  • ‘e’ (-d.dddde±dd,十进制指数)

  • ‘E’ (-d.ddddE±dd,十进制指数)

  • ‘f’ (-ddd.dddd,没有指数)

  • ‘g’ (‘e’:大指数,‘f’:其它情况)

  • ‘G’ (‘E’:大指数,‘f’:其它情况)

用法在 数据类型转换 里

2、strconv.ParseFloat() : 将 string 类型转换为 float 类型

strconv.ParseFloat(str, float32|float64)

3、strconv.Atoi() : string 转 int

strconv.Itoa() : int 转 string

用法在 数据类型转换 里,比较简单,不详述了,可以看手册,或者在 IDE 里 ctrl+N 看源码

ps: strconv.FormatInt() ,strconv.FormatUInt() 也是将 整数 类型转换为 string 类型的函数。

4、strconv.FormatBool() : bool 类型转为 string 类型

strconv.FormatBool(ture|false)

5、strconv.FormatComplex() : 将 complex 转换为 string 类型

数据类型转换

Golang 没有数据类型自动转换机制,都需要强制转换!无论是低精度赋值给高精度,还是高精度赋值给低精度,都需要强制转化。

高精度强制转化为低精度有时候会溢出,然后丢失数据罢了

 // float 不能直接强转 int 类型。
 // var num1 int = int64(100.50)  error。
 // var num2 int = 100.505 / 10  也不行
 // strconv 包中好像也没有 float->int 的函数诶。
 // 但是可以借助 math 包实现 float 的 “取整”,但他并不是把数据类型的float 变成了int
 f := 123.456
 var i1 float64 = math.Ceil(f) // 其实还是float64类型,不过值确实”取整“了
 var i2 float64 = math.Floor(f)
 fmt.Println(i1, i2)  // 124 123
 ​
 ​
 // 1. int 转 float
 var i int = 666
 var num2 float64 = float64(i)
 fmt.Println(num2)
 ​
 // 2.int 转 string ,借助 strconv 包(要import)
 import "strconv"
 var i = 100
 var str = strconv.Itoa(i)
 fmt.Println(str, reflect.TypeOf(str))  //输出:100 string
 ​
 // 3.string 转 int
 var str = "100"
 var i, _ = strconv.Atoi(str)   // 第二个返回值是 error code
 fmt.Println(i, reflect.TypeOf(i)) // 输出: 100 int
 ​
 // 4. float 转 string
 var num float64 = 555.666
 var str string = fmt.Sprintf("%f", num)
 fmt.Println(str, reflect.TypeOf(str))
 ​
 // 或者用 strconv.FormatFloat
 var num = 114.514
 // f -> 格式标记, 3->小数部分长度, float-> 指num是float64浮点数类型
 var str = strconv.FormatFloat(num, 'f', 3, 64)
 fmt.Println(str)
 ​
 ​
 // 5. string 转 float
 // strconv.ParseFloat:将字符串转换为浮点数,第二个参数32->float32, 64 -> float64
 var str = "114.514666"
 var num,_ = strconv.ParseFloat(str, 32)
 fmt.Println(num)
 ​
 // 6.bool 转 string 
 formatBool := strconv.FormatBool(true)
 fmt.Println(formatBool, reflect.TypeOf(formatBool)) // 输出:true string

注意是 Type(value),而不是其他语言中的(Type) value。

float64 (100) √; (float64) 100 × —— 写习惯C,Java可能会手误。

_ 表示占位符,忽略返回值但是接收了他,不能使用_

不同数据类型之间的数据不能直接相互赋值、不能直接做加减乘除。在做赋值、运算操作时,必须保证参与的变量都是同一类型。

Sprintf("%指定类型", 变量)很灵活,可以将任何指定类型的变量按照String类型输出,因此可以将任意类型的数据类型转换为string类型

Sprintf() 推荐用于将其他数据类型转换为 string 类型