学习go语言编程之数据类型
数据类型概述
Golang语言内置了如下基础数据类型:
- 布尔类型:bool
 - 整型:int8,unit8,int16,uint16,int32,uint32,int64,uint64,int,uint,uintptr
 - 浮点类型:float32,float64
 - 复数类型:complex64,complex128
 - 字符串:string
 - 字符类型:rune
 - 错误类型:error
 
同时,Golang还支持如下复合类型:
- 指针:pointer
 - 数组:array
 - 切片:slice
 - 字典:map
 - 通道:chan
 - 结构体:struct
 - 接口:inerface
 
布尔类型
布尔类型的关键字为bool,可赋值为预定义的true和false。
var v1 bool
v1 = true
v2 := (1 == 2) // v2会被推导为bool类型
注意:bool类型不能接受其他类型的赋值,不支持自动或强制类型转换。
var b bool
b = 1 // 编译报错
b = bool(1) // 编译报错
整型
Golang支持多个整型,如下表:
| 类型 | 长度(字节) | 值范围 | 
|---|---|---|
| int8 | 1 | -128 ~ 127 | 
| uint8(即byte) | 1 | 0 ~ 255 | 
| int16 | 2 | -32768 ~ 32767 | 
| uint16 | 2 | 0 ~ 65535 | 
| int32 | 4 | -2147483648 ~ 2147483647 | 
| uint32 | 4 | 0 ~ 4294967295 | 
| int64 | 8 | -9223372036854775808 ~ 9223372036854775807 | 
| uint64 | 8 | 0 ~ 18446744073709551615 | 
| int | 平台相关 | 平台相关 | 
| uint | 平台相关 | 平台相关 | 
| uintptr | 同指针 | 在32位平台下为4字节,64位平台下为8字节 | 
类型表示
在Golang中,int和int32被认为是两种不同的类型,编译器也不会自动做类型转换。
var value2 int32
value1 := 64 // 编译器自动推到为int类型
value2 = value1 // 编译报错
value2 = (int32)value1 // 明确使用强制类型转换,编译通过
数值运算
Golang支持的整数运算有:+,-,*,/,%。
比较运算
Golang支持的比较运算有:>,<,==,>=,<=,!=。
注意:两个不同类型的整型数不能直接比较:
var i int32 = 1
var j int64 = 2
if i == j { // 编译报错
    // TODO
}
但是不同类型的整型变量都可以与字面常量进行比较:
var i int32 = 1
var j int64 = 2
if i == 1 || j == 2 { // 编译通过
    // TODO
}
位运算
Golang支持的位运算如下表:
| 运算 | 含义 | 示例 | 
|---|---|---|
<< | 
左移 | 124 << 2 // 结果为496 | 
>> | 
右移 | 124 >> 2 // 结果为31 | 
^ | 
异或 | 124 ^ 2 // 结果为126 | 
& | 
与 | 124 & 2 // 结果为0 | 
| | 
或 | 124 | 2 // 结果为126 | 
^x | 
取反 | ^2 // 结果为-3 | 
浮点型
浮点型用于表示包含小数点的数据,Golang中的浮点型采用IEEE-754标准的表达方式。
浮点数表示
Golang定义了两个浮点类型:float32,float64。
var f1 float32
f1 = 12
f2 := 12.0 // 会被自动设定为float64,如果不加小数点会被推导为int整型
浮点数比较
浮点数因为涉及精度问题,不能直接使用==来判断是否相等。
使用math包中的Fdim函数进行比较:
import "math"
math.Fdim(f1, f2) < p // f1和f2为两个浮点数,p为自定义的精度,如:0.00001
复数类型
复数有2个实数构成,一个表示实部,一个表示虚部。
复数表示
var v1 complex64
v1 = 3.2 + 12i        // 实部为3.2,虚部为12i
v2 := 3.2 + 12i       // v2为complex128类型
v3 = complex(3.2, 12) // v3结果同v2
实部与虚部
对于一个复数z = complex(x, y),可以通过内置函数real(z)获得实部,通过imag(z)获得虚部。
字符串
声明和初始化字符串:
var str string          // 声明一个字符串变量
str = "Hello, World!"   // 为字符串变量赋值
ch := str[0]            // 取字符串得第一个字符
注意: 虽然可以通过下标访问字符串中的字符,但是字符串的内容不能在初始化之后修改。
s := "string"
s[0] = 'S'             // 编译报错
字符串操作
Golang支持的字符串操作如下表:
| 运算 | 含义 | 示例 | 
|---|---|---|
x + y | 
字符串连接 | "Hello" + " World!" // 结果为:Hello World! | 
len(s) | 
字符串长度 | len("Hello") // 结果为5 | 
s[i] | 
取字符串中指定下标的字符,从0开始 | "Hello"[0] // 结果为'H' | 
字符串遍历
Golang支持两种字符串遍历方式。
方式1:以字节数组遍历
str := "Hello, 世界"
n := len(str)
for i := 0; i < n; i++ {
    ch := str[i]
    fmt.Println(i, ch)
}
输出:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 228
8 184
9 150
10 231
11 149
12 140
从输出结果看一共是13个字节,但是从直观上看应该只有9个字符,这是因为每个中文字符在UTF-8编码中占3个字节。
方式2:以Unicode字符遍历
str := "Hello, 世界"
for i, ch := range str {
    fmt.Println(i, ch)
}
输出:
0 72
1 101
2 108
3 108
4 111
5 44
6 32
7 19990
10 30028
以Unicode字符方式遍历时,每个字符的类型是rune,而不是byte,所以一共是9个字符。
字符类型
在Golang中支持两个字符类型,一个是byte(实际上是uint8的别名),代表UTF-8字符串的单个字节的值;另一个是rune,代表单个Unicode字符。
数组
数组是指一系列同一类型数据的集合。
数组的声明方法如下:
[32]byte  // 长度为32的数组,每个元素为一个字节
[2*N] struct {x, y int32} // 复杂类型数组
[1000] *float64  // 指针数组
[3][5]int  // 二维数组
[2][2][2]float64 // 等同于[2]([2][2]float64)
数组长度在定义后就不可更改,使用内置函数len()获取数组长度。
arrLen := len(arr)
元素访问
可以使用下标来访问数组中的元素,从0开始。
arr := [3]int{1, 2, 3}
for i := 0; i < len(arr); i++ {
    fmt.Println(arr[i])
}
使用关键字range遍历容器中的元素:
arr := [3]int{1, 2, 3}
for i, v := range arr {
    fmt.Println(i, v)
}
值类型
Golang中的数组是一个值类型!所有的值类型变量在赋值和作为参数传递时都将产生一次复制动作。
如果将数组作为函数的参数类型,则在函数调用时该参数将发生数据复制。
// 在函数中修改数组元素值,不会影响到原始数组
func modifyArr(arr [3]int)  {
	arr[0] = 10 // 修改数组元素
	fmt.Println("In modifyArr(),arr: ", arr)
}
func main() {
    arr := [3]int{1, 2, 3}
	modifyArr(arr)
	fmt.Println("In main(),arr: ", arr)
}
输出:
In modifyArr(),arr:  [10 2 3]
In main(),arr:  [1 2 3]
显然,在函数中修改数组元素的值,并未影响到原始数组。
数组切片
数组的长度在定义后就不可更改,数组是值类型,每次传递都将产生一个副本。
数组切片可以随时动态扩充存放空间,可以被随意传递而不会导致所管理的元素被重复复制。
创建数组切片
创建数组切片的方法主要有两种:基于数组和直接创建。
- 基于数组
 
数组切片可以基于一个已经存在的数组创建,数组切片可以只使用数组的一部分元素或者整个数组来创建,甚至可以创建一个比所基于的数组还要大的数组切片。
// 先定义一个数组
var arr [10]int = [10]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 基于数组创建一个切片
var arrSlice []int = arr[:5]
fmt.Println(arr)
fmt.Println(arrSlice)
输出:
[1 2 3 4 5 6 7 8 9 10]
[1 2 3 4 5]
Golang支持使用arr[start:end]这种方式来基于数组生成一个数组切片,而且这种用法还非常灵活。
如下语法都是正确的:
var arrSlice1 []int = arr[:]  // 基于数组所有元素创建数组切片
var arrSlice2 []int = arr[:5] // 基于数组的前5个元素创建数组切片
var arrSlice3 []int = arr[5:] // 基于从第5个元素开始的所有元素创建数组切片
- 直接创建
 
使用内置函数make()可以灵活地创建数组切片。
// 创建一个初始元素个数为5的数组切片,元素初始值都为0
arrSlice1 := make([]int, 5)
fmt.Println(arrSlice1, len(arrSlice1), cap(arrSlice1))
// 创建一个初始元素个数为5的数组切片,元素初始值都为0,并预留10个元素的存储空间
arrSlice2 := make([]int, 5, 10)
fmt.Println(arrSlice2, len(arrSlice2), cap(arrSlice2))
// 直接创建并初始化包含5个元素的数组切片
arrSlice3 := []int{1, 2, 3, 4, 5}
fmt.Println(arrSlice3, len(arrSlice3), cap(arrSlice3))
以上示例中,有意思的是arrSlice1和arrSlice2的长度一样,但是存储空间却不同:arrSlice1的长度和存储空间都是5,而arrSlice2得长度为5(表示当前存储5个元素),存储空间却是10(表示最大可以存储10个元素)。
- 基于数组切片
 
类似于数组切片可以基于一个数组创建,数组切片也可以基于另一个数组切片创建。
oldSlice := []int{1, 2, 3, 4, 5}
newSlice := oldSlice[:3] // 基于oldSlice的前三个元素创建新的数组切片
fmt.Println(newSlice)    // [1 2 3]
有意思的是,如上示例中选择的oldSlice元素范围甚至可以超过所包含的元素个数,比如newSlice可以基于oldSlice的前6个元素创建,虽然oldSlice只包含5个元素。只要这个选择的范围不超过oldSlice存储能力(即cap()返回的值),那么这个创建程序就是合法的。newSlice中超出oldSlice元素的部分都会填上0。
// 创建一个初始容量为5,最大空间为10的数组切片
oldSlice := make([]int, 5, 10)
for i := 0; i < len(oldSlice); i++ {
    oldSlice[i] = i + 1
}
fmt.Println("oldSlice:", oldSlice)
// 选择的元素范围超过了oldSlice的元素个数5,但是小于其最大空间10
newSlice := oldSlice[:6]
// newSlice超出oldSlice元素的部分都会设置为0
fmt.Println("newSlice:", newSlice)
输出:
oldSlice: [1 2 3 4 5]
newSlice: [1 2 3 4 5 0]
元素遍历
操作数组元素的所有方法都适用于数组切片。
arrSlice := []int{1, 2, 3, 4, 5}
// 使用下表访问数组切片元素
for i := 0; i < len(arrSlice); i++ {
    fmt.Print(arrSlice[i], " ")
}
fmt.Println()
// 使用range关键字访问数组切片元素
for _, v := range arrSlice {
    fmt.Print(v, " ")
}
fmt.Println()
动态增减元素
使用内置函数append()给数组切片动态增加元素。
arrSlice := make([]int, 5)
fmt.Println(arrSlice)
arrSlice = append(arrSlice, 1) // 使用append()函数动态添加元素之后生成一个新的数组切片
fmt.Println(arrSlice)
输出:
[0 0 0 0 0]    # 在增加元素之前,数组切面元素为初始化的5个
[0 0 0 0 0 1]  # 动态增加一个元素
函数append()的第二个参数其实是一个不定参数,可以按需求添加若干个元素,甚至直接将一个数组切片追加到另一个数组切片的末尾。
arrSlice := make([]int, 5)
fmt.Println(arrSlice)
arrSlice2 := []int{1,2,3}
arrSlice = append(arrSlice, arrSlice2...) // 将arrSlice2追加到arrSlice的末尾,第二个参数后面的三个省略号非常重要
fmt.Println(arrSlice)
输出:
[0 0 0 0 0]
[0 0 0 0 0 1 2 3]
数组切片会自动处理存储空间不足的问题,如果追加的内容长度超过当前已分配的存储空间(即cap()函数值大小),数组切片会自动分配一块足够大的内存。
内容复制
使用内置函数copy()可以将内容从一个数组切片复制到另一个数组切片。
如果两个数组切片不一样大,按其中较小的那个数组切片的元素个数进行复制。
arrSlice1 := []int{1, 2, 3, 4, 5, 6}
arrSlice2 := []int{7, 8, 9}
fmt.Println("Before Copy,arrSlice1:", arrSlice1)
copy(arrSlice1, arrSlice2) // 复制arrSlice2中的全部元素到arrSlice1的前三个位置
fmt.Println("After Copy,arrSlice1:", arrSlice1)
输出:
Before Copy,arrSlice1: [1 2 3 4 5 6]
After Copy,arrSlice1: [7 8 9 4 5 6]  # 复制后arrSlice1的前三个元素被arrSlice2的三个元素替换
arrSlice1 := []int{1, 2, 3, 4, 5, 6}
arrSlice2 := []int{7, 8, 9}
fmt.Println("Before Copy,arrSlice2:", arrSlice2)
copy(arrSlice2, arrSlice1) // 复制arrSlice1的前3个元素到arrSlice2中替换替换掉原来的三个元素
fmt.Println("After Copy,arrSlice2:", arrSlice2)
输出:
Before Copy,arrSlice2: [7 8 9]
After Copy,arrSlice2: [1 2 3] # 只复制了arrSlice1的前三个元素到arrSlice2中并替换掉原来的三个元素
字典
字典map是一堆键值对的未排序集合。
假设存在一个自定义数据结构PersonInfo,定义如下:
// 自定义数据结构
type PersonInfo struct {
	ID string
	Name string
	Address string
}
变量声明
// 定义一个键类型为string,值类型为PersonInfo的字典变量
// myMap是声明的map变量名,string是键的类型,PersonInfo则是其中所存放的值类型
var myMap map[string] PersonInfo
创建
使用内置函数make()创建一个新的map。
myMap = make(map[string] PersonInfo)
也可以选择是否在创建时指定该map的初始存储能力:
// 创建一个初始大小为100的map
myMap = make(map[string] PersonInfo, 100)
还可以在创建map的时候进行初始化:
myMap := map[string] PersonInfo {
    "12345": {"12345", "zhangsan", "beijing"},
}
元素赋值
// 网map中添加元素
personDB["1235"] = PersonInfo{"12345", "zhangsan", "beijing"}
元素删除
使用内置函数delete()删除map元素:
delete(myMap, "12345")
元素查找
value, ok := myMap["12345"]
if ok {
    // 找到元素了
} else {
    // 元素未找到
}
												
											学习go语言编程之数据类型的更多相关文章
- 如何轻松学习C语言编程!
		
C语言是面向过程的,而C++是面向对象的 C和C++的区别: C是一个结构化语言,它的重点在于算法和数据结构.C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现 ...
 - 学习go语言编程系列之定义变量
		
package main import ( "fmt" "math") func main() { // 1. 定义变量名age,不初始化,使用对应类型的默认值 ...
 - 学习go语言编程系列之helloworld
		
1. 下载https://golang.org/dl/ # Go语言官网地址,在国内下载太慢,甚至都无法访问.通过如下地址下载:https://golangtc.com/download. 2. 安装 ...
 - R语言编程艺术# 数据类型向量(vector)
		
R语言最基本的数据类型-向量(vector) 1.插入向量元素,同一向量中的所有的元素必须是相同的模式(数据类型),如整型.数值型(浮点数).字符型(字符串).逻辑型.复数型等.查看变量的类型可以用t ...
 - Linux学习--- C语言关键字、数据类型
		
关键字: sizeof为关键字,并不是函数. 作用:编译器给我们查看内存空间容量的一个工具. eg:int a: printf("the size is %d\n",sizeof ...
 - 换个角度带你学C语言的基本数据类型
		
摘要: C语言的基本数据类型,大家从学生时代就开始学习了,但是又有多少人会试图从底层的角度去学习呢?这篇文章会用一问一答的形式,慢慢解析相关的内容和困惑. 本文分享自华为云社区<从深入理解底层的 ...
 - C语言编程入门之--第四章C语言基本数据类型
		
导读:C语言程序中经常涉及一些数学计算,所以要熟悉其基本的数据类型.数据类型学习起来比较枯燥,不过结合之前的内存概念,以及本节的字节概念,相信数据类型也就不难理解了.本章从二进制的基本概念开始,然 ...
 - C/C++编程笔记:C语言写推箱子小游戏,大一学习C语言练手项目
		
C语言,作为大多数人的第一门编程语言,重要性不言而喻,很多编程习惯,逻辑方式在此时就已经形成了.这个是我在大一学习 C语言 后写的推箱子小游戏,自己的逻辑能力得到了提升,在这里同大家分享这个推箱子小游 ...
 - Linux C语言编程学习笔记 (1)进程控制入门
		
想进行Linux系统开发已经很久了,一直没有付诸实践.今日终于开始学习Linux下的C语言编程,研究一天,终于大概弄明白了Linux系统进程管理的一些基本概念和编程方法,总结下来以方便大家学习和自己实 ...
 - Android JNI编程(二)——C语言的基本数据类型,输出函数,输入函数
		
版权声明:本文出自阿钟的博客,转载请注明出处:http://blog.csdn.net/a_zhon/. 目录(?)[+] 在学习C语言数据类型之前,我们先来回顾一下Java中的基本数据类型和其特点 ...
 
随机推荐
- 人工智能GPT科普知识的简单总结
			
人工智能GPT相关知识的简单总结 背景 工作已经很久, 工作十几年来有过好多波新的技术浪潮. 但是每次都离技术前沿比较远. 最近发现只低头拉车是一个没有前途的行为. 人生很短, 选择很重要, 不仅要低 ...
 - 物联网浏览器(IoTBrowser)-Web串口自定义开发
			
物联网浏览器(IoTBrowser)-Web串口自定义开发 工控系统中绝大部分硬件使用串口通讯,不论是原始串口通讯协议还是基于串口的Modbus-RTU协议,在代码成面都是使用System.IO.Po ...
 - Flutter开发桌面应用的一些探索
			
引言 在移动应用开发领域,Flutter已经赢得了广泛的认可和采用,成为了跨平台移动应用开发的瑞士军刀.然而,Flutter的魅力并不仅限于移动平台,它还可以用于开发桌面应用程序,为开发人员提供了一种 ...
 - Redis极简教程
			
简介 Redis 是用C语言开发完全开源免费的,遵守BSD协议的,一个高性能的,key-value型的,NOSQL数据库. 特点 可以将内存中的数据持久化到硬盘中,重启的时候可以从硬盘中再次加载 拥有 ...
 - 让你彻底理解Typescript中静态成员和抽象方法
			
1.Typescript中static详解 静态成员:在类中通过 static 修饰的属性或者方法 那么就是静态的属性静态方法 也称之为:静态成员(静态成员包含静态属性和静态方法) 静态成员在使用的时 ...
 - 【JS 逆向百例】W店UA,OB反混淆,抓包替换CORS跨域错误分析
			
关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...
 - linux如何配置ssh密钥登录
			
为什么要用ssh密钥登录 购买的服务器设置密码很容易被暴力破解,用密钥登录安全很多.root用户新建的用户也要用密钥登录更安全,如果一直su - 用户名登录 不方便 用xftp等服务上传文件到用户使用 ...
 - 你的代码已被埋在北极冰雪之下,保存千年——GitHub北极代码保险库
			
GitHub存档计划:北极代码保险库 在2019 GitHub 宇宙大会(GitHub Universe 2019)上,他们提到了一个问题,1000年后的软件会是什么样?人类会是什么样子?对此我们只能 ...
 - 3.3 Windows驱动开发:内核MDL读写进程内存
			
MDL内存读写是一种通过创建MDL结构体来实现跨进程内存读写的方式.在Windows操作系统中,每个进程都有自己独立的虚拟地址空间,不同进程之间的内存空间是隔离的.因此,要在一个进程中读取或写入另一个 ...
 - Metasploit 生成各种后门
			
Metasploit 是一款开源的安全漏洞检测工具,可以帮助安全和IT专业人士识别安全性问题,验证漏洞的缓解措施,同时该工具也是渗透测试环境中的利器,它支持多平台Payload的生成具有完全的跨平台性 ...