基础篇一:变量类型

在Go语言中,变量的关键字和C语言的关键字大体相同,但在Go语言中没有关键字“double”类型。在Go语言中“float”类型分为float32和float64,两者的区别在后文展开。Go语言的变量如表1所示。

基础类型关键字复合类型
布尔类型bool指针(pointer)
整形int8、byte、int16、unit、uintptr数组(array)
浮点类型float32、float64切片(slice)
复数类型complex64、complex128字典(map)
字符串string通道(chan)
字符类型rune结构体(struct)
错误类型error接口(interface)
表1 Go语言支持的数据类型

1 布尔类型

bool为布尔型用作逻辑判断,零表示false,非零表示true。不接受其他类型的赋值,不支持自动或强制的类型转换。以下是bool类型的用法示例:

package main
import "fmt"
func main() {
  var success bool
  if 5 == 5 {
    success = true
  } else {
    success = false
  }
  fmt.Println(success)
}

2 整型

整形是所有编程语言里最基础的数据类型。Go语言支持的整形类型如下表2所示。

类型长度(字节)值范围
int81-128 ~ 127
uint8(即byte)10 ~ 225
int162-32 768 ~ 32 767
uint1620 ~ 65 535
int324-2 147 483 648 ~ 2 147 483 647
uint3240 ~ 4 294 967 295
int648-9 223 372 036 854 775 808 ~ 9 223 372 036 854 775 807
int平台相关平台相关
uint平台相关平台相关
uintptr同指针在32位平台下为4字节,64位平台下为8字节
表2 Go语言支持的整形类型

2.1 类型表示

int和int32在Go语言里被认为是两种不同的类型,编译器不会自动做类型转换,使用强制类型转换可以解决这个问题,但要注意数据损失和溢出的问题(在这里不做具体分析),样例如下:

package main
import "fmt"
func main() {
  var v1 int32
  v2 := 64
  v1 = int32(v2) //编译正确
  //v1 = v2 //编译错误
  fmt.Println(v1)
}

2.2 数值运算

编程语言都涉及数值运算这一基本操作,在Go语言支持的数值运算如下表3所示。

符号功能例子
+加法运算6 + 3  =   9
减法运算6 – 3  =   3
*乘法运算6 * 3  = 18
/除法运算6 / 3  =   2
%取余运算6 % 3 =   0
表3 Go语言支持的数值运算

2.3 比较运算

Go语言支持以下的几种比较运算符:>、<、==、>=、<=和!=。和大多数其他编程语言相同,在这里就不做具体说明了。(需要注意的是不同类型的整形数不能直接比较,但可以直接与字面常量进行比较)

2.4 位运算

Go语言支持表4所示的运算符。

运算含义样例
x << y左移124 << 2   //结果为496
x >> y右移124 >> 2   //结果为31
x ^ y异或124 ^ 2     //结果为126
x & y124 & 2    //结果为0
x | y124 | 2      //结果为126
^x取反^124        //结果为-125
表4 Go语言支持的运算符

3 浮点型

浮点型用于表示包含小数点的数据,比如1.234就是一个浮点型数据。Go语言中的浮点类型采用IEEE-754标准的表达方式。

3.1 浮点数表示

Go语言定义了两个类型float32和float64,分别等价于C语言的float类型和double类型,样例如下:

package main
import "fmt"
func main() {
  var fvalue1 float 32
  fvalue1 = 12
  fvalue2 := 12.0 //如果不加小数点,fvalue2会被推导为整形
  fvalue3 := float32(fvalue2) //必须使用强制类型转换,直接赋值将导致编译错误
  fmt.Println(fvalue3)
}

3.2 浮点数比较

因为浮点数不是一种精确的表达方式,所以直接使用“==”来判断两个浮点数是否相等会导致不稳定的结果。样例如下:

package main
import "fmt"
func main() {
	var f32value float32 = math.Pi
	var f64value float64 = math.Pi
	fmt.Println("float32 Pi = ", f32value, ", float64 Pi = ", f64value)
	fmt.Println("float32 Pi to float64 = ", float64(f32value), ", float64 Pi = ", f64value)
	fmt.Println(f64value == float64(f32value))
	fmt.Println("float32 Pi = ", f32value, ", float64 Pi to float32 = ", float32(f64value))
	fmt.Println(float32(f64value) == f32value)
}
/*
打印结果如下:
float32 Pi =  3.1415927 , float64 Pi =  3.141592653589793
float32 Pi to float64 =  3.1415927410125732 , float64 Pi =  3.141592653589793
false                                                                        
float32 Pi =  3.1415927 , float64 Pi to float32 =  3.1415927                 
true 
/*

在上诉的例子中,不难看出,float32转换成float64类型的数据时,出现数据丢失的现象。所以在使用“==”比较这俩中类型时,编辑器会有“无效运算: f64value == f32value(类型 float64 和 float32 不匹配)”的警告。

4 复数类型

复数实际上由两个实数(在计算机中用浮点数表示)构成,一个表示实部(real),一个表示虚部(imag)。样例如下:

package main //必须有一个main包
 
import "fmt"
 
func main() {
    var cvalue1 complex128 //声明
    cvalue1  = 2.1 + 3.14i  //赋值
    fmt.Println("cvalue1  = ", cvalue1 )
 
    //自动推导类型
    cvalue2 := 3.3 + 4.4i
    fmt.Printf("cvalue2 type is %T\n", cvalue2)
 
    //通过内建函数,取实部和虚部
    fmt.Println("real(cvalue2) = ", real(cvalue2), ", imag(cvalue2) = ", imag(cvalue2))
 
}
/*
打印结果如下:
cvalue1  =  (2.1+3.14i)
cvalue2 type is complex128                 
real(cvalue2) =  3.3 , imag(cvalue2) =  4.4
*/

5 字符串

在Go语言中,字符串也是一种基本类型。相比之下,C/C++语言并不存在原生的字符串类型,通常使用字符数组来表示,并以字符指针来传递。字符串的声明和使用的样例如下:

package main

import "fmt"

func main() {
   var str string
   str = "Hello world"
   fmt.Println(str)
}

字符串的内容可以用类似于数组下标的方式获取,但与数组不同,字符串的内容不能在初始化后被修改。

5.1 字符串操作

平时常用的字符串操作如表5所示。

运算含义样例
x + y字符串连接“Hello” + “World”     // 结果为HelloWorld
len(str)字符串长度len(“HelloWorld”)    // 结果为10              
s[i]取字符“HelloWorld”[1]        // 结果为’e’     
表5 字符串操作方法对照表

5.2 字符串遍历

Go语言支持两种方式遍历字符串,样例如下表所示:

字节数组遍历:

package main

import "fmt"

func main() {
   str := "Hello,世界"
   n := len(str)
   for i := 0 ; i < n ; i++ {
      fmt.Println(i, str[i])
   }
}
/* 运行结果
0 72
1 101
2 108
3 108
4 111
5 44
6 228
7 184
8 150
9 231
10 149
11 140
*/

Unicode字节遍历:

package main

import "fmt"

func main() {
   str := "Hello,世界"
   for i , ch := range str{
      fmt.Println(i, ch)
   }
}

/* 运行结果
0 72
1 101
2 108
3 108
4 111
5 44
6 19990
9 30028
*/

6 字符类型

在Go语言中支持两种字符类型,一个是byte(实际上是unit8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。

7 数组

数组是Go语言编程中最常用的数据结构之一。顾名思义,数组就是指一系列同一类型数据的集合。数组中包含的每个数据被称为数组元素(element),一个数组包含的元素个数被称为数组的长度。以下为一些常规的数组声明方法:

var b [32]byte          //长度为32的数组,每个元素为一个字节
var s [32] struct{x,y int}   //复杂类型数组
var f1 [32] *float64       //指针数组
var f2 [2][4][4] float64     //等同于[2]([2]([2] float64))
var i [4][8] int         //二维数组

8 结构体

Go 语言中数组只能存储同一类型的数据,但在结构体中我们可以为不同属性定义不同的数据类型。结构体是由一系列具有相同类型或不同类型的数据构成的数据集合。

8.1 定义结构体

结构体定义需要使用 type 和 struct 语句。struct 语句定义一个新的数据类型,结构体中有一个或多个成员。type 语句设定了结构体的名称。结构体的格式如下:

type StudentInfo struct {
	Name      string
	ClassRoom string
}

定义好结构体后,就能用于变量的声明,语法格式如下:

variable_name := structure_variable_type {value1, value2, ..., valuen}
或
variable_name := structure_variable_type { key1: value1, key2: value2, ..., keyn: valuen}

样例如下:

import 
"fmt"

type StudentInfo struct {
	Name      string
	ClassRoom string
}

func main() {
	var students map[int]StudentInfo
	students = make(map[int]StudentInfo)

	//添加元素
	students[0] = StudentInfo{"小明", "三年级(1)班"}
	students[1] = StudentInfo{"小红", "三年级(1)班"}
	students[2] = StudentInfo{"小李", "三年级(2)班"}
	students[3] = StudentInfo{"小王", "三年级(2)班"}
	students[4] = StudentInfo{"小赵", "三年级(3)班"}

	//遍历元素
	fmt.Println("学生信息如下:")
	for _, student := range students {
		fmt.Println(student)
	}
}

9 指针

一个指针变量指向了一个值的内存地址。类似于变量和常量,在使用指针前你需要声明指针。指针声明格式如下:

var var_name *var-type

var-type 为指针类型,var_name 为指针变量名,* 号用于指定变量是作为一个指针。以下是有效的指针声明:

var ip *int        // 指向整型
var fp *float32    // 指向浮点型

指针的使用流程:

  • 定义指针变量
  • 为指针变量赋值
  • 访问指针变量中指向地址的值

使用指针操作数组的样例如下:

package main

import (
	"fmt"
	"math"
	"math/rand"
	"time"
)

func main() {
	var p *int // 定义指针变量
	var num [10]int
	var temp int
	p = &temp // 为指针变量赋值
	// 一般使用系统时间的不确定性来进行初始化
	rand.Seed(time.Now().Unix())
	fmt.Print("数组初始化:")
	for i := 0; i < len(num); i++ {
		temp = rand.Intn(100)
		num[i] = *p // 访问指针变量中指向地址的值
		fmt.Print(num[i], " ")
	}
	fmt.Println()
	fmt.Print("数组中的最大值:")
	temp = num[0]
	for i := 1; i < len(num); i++ {
		if temp < num[i] {
			temp = num[i]
		}
	}
	fmt.Println(*p)
	fmt.Print("数组中的最小值:")
	temp = num[0]
	for i := 1; i < len(num); i++ {
		if temp > num[i] {
			temp = num[i]
		}
	}
	fmt.Println(*p)
}
/*
运行的一次结果为:
数组初始化:7 97 62 77 20 79 65 19 66 83 
数组中的最大值:97                       
数组中的最小值:7
*/

使用指针操作单链表的样例如下:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

type Node struct {
	Value int
	Next  *Node
}

func AddNote(t *Node, v int) int {
	if head == nil {
		t = &Node{v, nil}
		head = t
		return 0
	}
	if v == t.Value {
		fmt.Println("节点已存在:", v)
		return -1
	}
	// 如果当前节点下一个节点为空
	if t.Next == nil {
		t.Next = &Node{v, nil}
		return -2
	}
	// 如果当前节点下一个节点不为空
	return AddNote(t.Next, v)
}

func DelNote(head *Node, v int) *Node {
	if FindNote(head, v) == true {
		if head.Value == v {
			return head.Next
		}

		pre := head

		for head.Next.Value != v {
			head = head.Next
		}

		head.Next = head.Next.Next
		return pre
	}
	return head
}

func FindNote(t *Node, v int) bool {
	if t == nil {
		return false
	} else {
		for t != nil {
			if v == t.Value {
				return true
			}
			t = t.Next
		}
		return false
	}
	return false
}

func UpdateNote(head *Node, src int, dst int) *Node {
	if FindNote(head, src) == true {
		if head.Value == src {
			head.Value = dst
			return head
		}
		temp := head
		for temp != nil {
			if temp.Value == src {
				temp.Value = dst
			}
			temp = temp.Next
		}
	}
	return head
}

func ShowNote(t *Node) {
	if t == nil {
		fmt.Println("空链表!")
	} else {
		for t != nil {
			fmt.Print(" ", t.Value)
			t = t.Next
		}
		fmt.Println()
	}
}

var head = new(Node)

func main() {
	head = nil
	var num [10]int
	rand.Seed(time.Now().Unix())
	for i := 0; i < len(num); i++ {
		num[i] = rand.Intn(1000)
		AddNote(head, num[i])
	}
	fmt.Print("链表初始值:")
	ShowNote(head)
	fmt.Print("删除第五个:")
	ShowNote(DelNote(head, num[4]))
	fmt.Print("更新第三个:")
	ShowNote(UpdateNote(head, num[2], 100))
}
/*
运行的一次结果为:
链表初始值: 120 549 663 704 324 236 415 434 885 747
删除第五个: 120 549 663 704 236 415 434 885 747    
更新第三个: 120 549 100 704 236 415 434 885 747 
*/

10 数组切片

数组的长度在定义之后无法再次修改;数组是值类型,每次传递都将产生一份副本。这些都是数组的特点,但这种数据结构无法完全满足开发者的真实需求。Go语言提供了数组切片(slice)这种数据类型来弥补数组的不足。

数字切片就像一个指向数组的指针,数组切片的数据结构可以抽象为以下3个变量:

  • 一个指向原生数组的指针;
  • 数组切片中的元素个数;
  • 数组切片已分配的存储空间。

10.1 创建数组切片

基于数组创建数组切片的方法样例如下:

package main

import "fmt"

func main() {
   //定义数组
   var array [10]int = [10]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

   //基于前五个数组元素创建一个切片
   var slice []int = array[:5]
   //从第五个数组元素创建一个切片   array[5:]
   //数组转为切片 array[:]
   fmt.Println("Array:")
   for _, v := range array {
      fmt.Print(v, " ")
   }
   fmt.Println("")
   fmt.Println("Slice:")
   for _, v := range slice {
      fmt.Print(v, " ")
   }
}

/*
运行结果为:
Array:
0 1 2 3 4 5 6 7 8 9 
Slice:              
0 1 2 3 4
*/

直接创建数组切片的方法样例如下:

package main

import "fmt"

func main() {
   //使用make()直接创建数组切片
   //创建一个初始元素个数为5的数组切片,元素初始值为0
   slice1 := make([]int, 5)
   fmt.Println(len(slice1))
   //创建一个初始元素个数为5的数组切片,元素初始值为0,并预留10个元素的存储空间
   slice2 := make([]int, 5, 10)
   fmt.Println(len(slice2))
   //直接创建并初始化包含5个元素的数组切片
   slice3 := []int{0, 1, 2, 3, 4}
   fmt.Println(len(slice3))
}
/*
运行结果为:
5
5
5
*/

10.2 切片常用的方法

10.2.1 遍历

操作数组元素的所有方法都适用于数组切片,比如数组切片也可以按下标读写元素,用len()函数获取元素的个数,并支持使用range关键字来快速遍历所有元素。

传统的元素遍历如下:

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

使用range关键字可以让遍历代码显得更整洁。range表达式有两个返回值,第一个是索引,第二个是元素的值,代码如下:

slice := []int{1, 2, 3, 4, 5}
for i, v := range slice {
   fmt.Println("d[", i, "]=", v)
}

10.2.2 增减元素

使用append()操作切片,代码如下:

package main

import "fmt"

func main() {
   a := []int{1, 2, 3, 4, 5}
   fmt.Println("a =", a, "len=", len(a))
   d := make([]int, 0)
   //添加切片a的前2个元素,添加切片a下标从3开始之后的元素
   //总结一下:冒号在前是个数;冒号在后是下标,保留的是冒号没有数字的一侧
   //只有(int):和:(int)的用法。
   //添加一个切片要用...展开运算符
   d = append(a[:2], a[3:]...)
   fmt.Println("d =", d, "len=", len(d))
}
//删除切片中的第三个元素
//打印结果如下:
//a = [1 2 3 4 5] len= 5
//d = [1 2 4 5] len= 4

10.2.3 扩容

package main

import "fmt"

func main() {
   a := make([]int, 0)

   fmt.Printf("当前切片的长度:%d,容量:%d\n", len(a), cap(a))

   for i := 0; i < 10; i++ {
      a = append(a, i)
      fmt.Printf("当前切片的长度:%d,容量:%d\n", len(a), cap(a))
   }
}
//打印结果如下:
//当前切片的长度:0,容量:0
//当前切片的长度:1,容量:1  
//当前切片的长度:2,容量:2  
//当前切片的长度:3,容量:4  
//当前切片的长度:4,容量:4  
//当前切片的长度:5,容量:8  
//当前切片的长度:6,容量:8  
//当前切片的长度:7,容量:8  
//当前切片的长度:8,容量:8  
//在添加元素之前判断切片是否有剩余,如果没有剩余且容量小于1024,成倍增加容量
//在没有添加元素之前的容量是8,所以扩容量为8
//当前切片的长度:9,容量:16 
//当前切片的长度:10,容量:16
  • 如果容量小于1024的时候,是成倍数的添加容量
  • 如果容量大于1024的时候,是之前的1/4的来添加容量

11 map

Map 是一种无序的键值对的集合。Map 最重要的一点是通过 key 来快速检索数据,key 类似于索引,指向数据的值。

Map 是一种集合,所以我们可以像迭代数组和切片那样迭代它。不过,Map 是无序的,我们无法决定它的返回顺序,这是因为 Map 是使用 hash 表来实现的。样例如下:

package main

import "fmt"

type StudentInfo struct {
   Name      string
   ClassRoom string
}

func main() {
   var students map[int]StudentInfo
   students = make(map[int]StudentInfo)

   //添加元素
   students[0] = StudentInfo{"小明", "三年级(1)班"}
   students[1] = StudentInfo{"小红", "三年级(1)班"}
   students[2] = StudentInfo{"小李", "三年级(2)班"}
   students[3] = StudentInfo{"小王", "三年级(2)班"}
   students[4] = StudentInfo{"小赵", "三年级(3)班"}

   //遍历元素
   fmt.Println("学生信息如下:")
   for _, student := range students {
      fmt.Println(student)
   }

   //查找元素
   stua, ok := students[3]
   if ok {
      fmt.Println("下标是3的学生信息:", stua)
   } else {
      fmt.Println("查找失败!")
   }

   //删除元素
   delete(students, 1)
   fmt.Println("新的学生信息如下:")
   for _, student := range students {
      fmt.Println(student)
   }

   //修改元素
   stub, ok := students[2]
   if ok {
      stub.ClassRoom = "三年级(3)班"
   } else {
      fmt.Println("查找失败!")
   }
   fmt.Println("修改后的学生信息:", stub)

}
发表回复 0

Your email address will not be published. Required fields are marked *