GO语言的特点
- 从C语言中继承了较多理念,包括表达式语法,控制结构,基础数据结构,调用参数传值,指针等等;
- 引入包的概念,用于组织程序结构,Go语言的一个文件都要归属于一个包,而不能单独存在;
- 垃圾回收机制,内存自动回收;
- 天然并发。语言层面支持并发;goroutine,轻量级线程,可实现大并发处理,高效利用多核;基于CPS并发模型实现;
- 吸收了管道通信机制,形成Go语言特有的管道channel。通过管道channel可以实现不同的goroute之间相互通信;
- 函数支持返回多个值;
- 新的创新,例如切片slice,延时执行defer等;
开发环境(software Development Kit,SDK)搭建(windows)
https://go.dev/dl/安装包下载
环境变量
- GOROOT:指定SDK的安装路径,推荐d:/programs
- Path:添加SDK的/bin目录
- GOPATH:工作目录,go项目存放的路径
程序开发和基本结构说明
目录结构
package main //表示该文件所在的包是main包,每个文件都必须归属于一个包
import "fmt" //引入一个包,包名fmt。引入该包后,就可以使用该包的函数,例如fmt.Println
func main(){ // func为关键字,表示后续为一个函数。main是函数名,为主函数
fmt.Println("hello,world!") // 调用fmt包的函数Println输出文本
}
运行
go build -o myhello.exe hello.go # 编译,生成myhello.exe的可执行文件,命令行输入hello.exe即可运行
go run hello.go # 以脚本文件的形式直接运行源码
开发注意事项
- 执行入口为main()方法;
- 严格区分大小写;
- Go方法由一条条语句构成,默认在每个语句自动添加分号;
- 按行进行编译。不能将多条语句写在同一行;
- go定义的变量或者import的包如果没有使用到,代码不能编译通过;
转义字符
- \t 制表符
- \n 换行符
- \ 单个\
- \" 单个"
- \r 回车
编程指南
https://tour.go-zh.org/basics/1 # 官方编程网站
标识符命名规范
Golang对各种变量、方法、函数等命名时使用的字符序列(字符串)称为标识符。凡是可以自己起名字的地方都叫标识符
规则
- 由英文字母大小写,0-9和_组成;
- 不能以数字开头;
- 严格区分大小写,例如
var num int
和var Num int
中,num
和Num
是两个不同的变量; - 标识符不能包含空格;
- 下划线_是一个特殊的标识符,称为空标识符。可以代表任何其他的标识符,但是它对应的值会被忽略(例如忽略某个返回值)。所以仅能被作为占位符使用,不能作为标识符使用
- 不能以系统保留关键字(共25个 )作为标识符,例如break,if等;
注意事项
- 包名:保持package的名字和目录一致,简短有意义,不要与标准库冲突;
- 变量名、函数名、常量名:采取驼峰法;
- 如果变量名、函数名、常量名首字母大写,则可以被其他包访问;如果首字母小写,则只能在本包中使用;
常量 & 变量
数学概念:在事务的特定运动过程中,某量若保持不变,则称之为常量;反之,则称之为变量。变量分为自变量和因变量,亦称函数。
常量是指在程序编译后运行时始终都不会变化的值,比如帧率、延迟。常量和变量声明一般出现在包级别。
常量声明后程序可以不使用,变量声明后程序至少要调用1次。
常量声明 & 常量计数器iota
常量声明的值必须是一个数字值、字符串或者一个固定的boolean值。iota是golang的常量计数器,只能在常量的表达式中使用。使用iota只需记住以下两点:
- iota在const关键字出现时将被重置为0
- const中每新增一行常数声明将使iota计数一次,在定义枚举时很有用
const [常量名] [常量类型] = [value] // 常量的显式声明格式
const (
USD int = iota // 美元,值 = 0
EUR // 欧元,值 = 1
RMB // 人民币,值 = 2
)
func main() {
const a int = 3.1415 // 显式定义
const b = "Hello World" // 隐式定义
fmt.Println(a,b) // 输出'3.1415 Hello World'
symbol := [...]string{USD: "$", EUR: "€", GBP: "£", RMB: "¥"}
fmt.Println(RMB, symbol[RMB]) // 输出'3 ¥'
}
变量声明
变量(变量=变量名+数值+数据类型)表示内存中的一个存储区域。该区域有自己的名称(变量名)和类型(数据类型)。
变量使用的两种方式:
- 指定变量类型,声明后若不赋值,使用默认值;
- 根据值自行判定变量类型(类型推导,有点类似python);
func main(){
var q, w int // 定义int类型的变量,类似于JS
q = 10 // 为i赋值,若无赋值默认值为0
fmt.Println("q =", q, "w =", w) // 控制台输出'i = 10'
}
细节
在同一个作用域中不能重复定义;
在定义变量时若没有赋初值,int类型默认为0,string类型默认为空字符串,小数类型默认为0;
数据类型
整型
Go数据类型包含基本数据类型和派生/复杂数据类型,主要做一下复杂数据类型的笔记,基本数据类型各语言类似。
整数类型使用细节:
- 各整数类型分为有符号和无符号;int uint的大小和系统有关;
- 默认声明为int;
- 如何在程序查看某个变量的字节大小和数据类型;
import unsafe
fmt.Printf("n2 的类型 %T n2占用的字节数是 %d", n2, unsafe.Sizeof(n2)) // unsafe.Sizeof方法使用较多
- 遵守保小不保大的原则(在保证程序正确运行下,尽量使用占用空间小的数据类型,如年龄);
- 计算机中的最小存储单位是bit;基本存储单元是byte;1byte=8bit;
func main(){
var n1 = 100
fmt.Printf("n1 的类型 %T",n1) // %T可查看数据类型
}
浮点类型
类型 | 占用存储空间 | 表数范围 |
---|---|---|
单精度float32 | 4字节 | -3.403E38~3.403E38 |
双精度float64 | 8字节 | -1.798E308~1.798E308 |
浮点数在机器中存放形式,浮点数=符号位+指数位+尾数位
- 浮点数都是有符号(正负符号)的;
- 尾数部分可能丢失,造成精度损失,若对精度有要求,推荐使用float64;
- 默认声明为float64;
字符(char)类型
Golang中没有专门的字符类型,如果要存储单个字符(字母),一般使用byte来保存。
字符型存储到计算机中,需要将字符对应的码值找出来。例如存储时,将字符 --> 对应码值 --> 二进制 --> 存储的流程。读取时反向对应。
- 字符常量使用单引号(
''
)括起来的单个字符。例如var c1 byte = 'a'
var c2 int = '中'
var c3 byte = '9'
; - 允许使用转义字符'\'来将气候的字符转变为特殊字符型常量。例如
var c3 char= '\n' // '\n'标识换行符
; - 默认使用UTF-8编码(包含ASCII码)
- 在Go中,字符的本质是一个整数,直接输出时,是该字符对应的UTF-8编码的码值;
- 可以直接给变量赋一个数值,然后按格式化输出
%c
,会输出该数字对应的unicode
字符; - 字符类型是可以进行运算的,相当于一个整数,因为它都有对应的ASCII码;
var c1 byte = 'a'
fmt.Println("c1=",c1) // 输出c1= 97
fmt.Printf("c1=%c c1对应码值=%d \n",c1,c1) // 输出c1=a c1对应码值=97
var c3 = 10 + 'a'
fmt.Println("c3=",c3,"\n") // 输出c3= 107
布尔类型
- 只能取true或者false,不能有其他值(包括0和1);
- 占1个字节;
字符串string类型
字符串就是一串固定长度的字符连接起来的字符序列。Go的字符串是由单个字节(byte)连接起来的。Go的字符串的字节使用UTF-8编码标识Unicode文本。
- 字符串一旦赋值,字符串就不能再更改了,在Go中字符串是不可变的
- 使用双引号会识别转义字符;使用反引号会以字符串的原生格式输出,包括换行和特殊字符,可以防止攻击;
- 拼接时,需要将
+
放在上一行,因为Go会自动在每行后面添加;
基本数据类型默认值
数值类型 | 默认值 |
---|---|
int整型 | 0 |
float浮点型 | 0 |
bool布尔型 | false |
string字符串 | "" |
基本数据类型的转换
Go在不同类型的变量之间赋值时需要显式转换。也就是说在Golang中数据类型不能自动转换,必须强制转换。
被转换的是变量存储的数据,原变量本身的数据类型并没有发生变化。
var i int32 = 100
var n1 float64 = float(i) // 显式转换
fmt.Printf("i=%v n1=%v", i, n1) // %v表示原值输出
// 在转换中,如果将int64的值转换成int8,编译时不会报错,只是将转换结果按溢出处理
var num1 int64 = 999999 // 对应二进制1111 0100 0010 0011 1111
var num2 int8 = int8(num1) // 只取8位数值,结果转换为10进制时输出63
fmt.Printf("num1=%v num2=%v \n",num1,num2) // 输出num1=999999 num2=63
⭐基本数据类型和string转换
// 将其他数据类型转换为string,格式:fmt.Sprintf("%参数", 表达式) ,方式一
var mycahr byte = 'h'
var str string = fmt.Sprintf("%c",mycahr) // %c的含义查看golang fmt方法的定义
fmt.Printf("str type %T str=%q",str,str)
// 方式二
import "strconv"
var num1 int = 99
var num2 float64 = 23.456
var b bool = true
var string1 string = strconv.FormatInt(int64(num1),10)
fmt.Printf("string1 type %T string1=%q\n",string1,string1) // string1 type string string1="99"
var string2 string = strconv.FormatFloat(num2,'f',10,64)
fmt.Printf("string2 type %T string2=%q\n",string2,string2) // string2 type string string2="23.4560000000"
var string3 string = strconv.FormatBool(b)
fmt.Printf("string3 type %T string3=%q\n",string3,string3) // string3 type string string3="true" // 将string转换为其他数据类型
var str1 string = "true"
var str2 string = "123456"
var str3 string = "123.4567789"
var b1 bool b1 , _ = strconv.ParseBool(str1) fmt.Printf("b1 tyue %T b1=%v\n",b1,b1) // b1 tyue bool b1=true
var num3 int64 num3, _ = strconv.ParseInt(str2,10,64)
fmt.Printf("num3 tyue %T num3=%v\n",num3,num3) // num3 tyue int64 num3=123456
var float float64 float, _ = strconv.ParseFloat(str3, 64)
fmt.Printf("float type %T float=%v\n",float,float) // float type float64 float=123.4567789
var float32 float32 = float32(float) // 默认转换为float64,若有转换成float32的需求强制转换一下即可
fmt.Printf("float32 type %T float32=%v\n",float32,float32) // float32 type float32 float32=123.45678
派生/复杂数据类型
指针(Pointer)
- 基本数据类型,变量存的就是值,也叫值类型;
-
值类型,都有对应的指针类型,形式为
*数据类型
。例如int
对应的指针就是*int
-
获取变量的地址,用
&
。例如var num int
中,获取num
的地址:&num
- 指针类型,变量存的是一个地址,这个地址指向的空间存的才是值。例如
var ptr *int = &num
- 获取指针类型所指向的值,使用
*
。例如var ptr *int
,使用*ptr
获取p
指向的值
var i int = 10
fmt.Println("i的地址为", &i) // &i获取变量的地址
fmt.Println("i的值为", i)
var ptr *int = &i // ptr为int值类型指针变量指向&i的地址
fmt.Printf("ptr指向的地址=%v\n",ptr)
fmt.Printf("ptr自己的地址=%v\n",&ptr) // &ptr指地址
fmt.Printf("ptr指向的值=%v\n",*ptr) // *ptr解地址
⭐值类型和引用类型
说明
- 值类型包括
int系列
,float系列
,bool
,string
,数组
和结构体struct
- 引用类型包括
指针
,slice切片
,map
,管道chan
,interface
等
使用特点
- 值类型变量直接存储值,内存通常在栈中分配
- 引用类型变量存储的是一个地址,这个地址对应的空间才存储真正的值,内存通常在堆上分配。当没有任何变量引用这个地址时,该地址对应的数据空间就成为了垃圾,由GC来回收
- 不管是值传递和引用传递,传递给函数的都是变量的副本。
运算符
与各编程语言及MySQL的运算符大差不差,所以只记录不同的运算符。
算数运算符
与Python相同。不过另有自增和自减。当a=2 a++时,a的结果为3。++或--只能独立使用
func main(){
var i int = 8
var a int
i++ // i++只能独立使用,不能a = i++
a = i
fmt.Println(a)
}
细节说明:
- 当整数之间做除法时,只保留整数部分而舍弃小数部分;
- 当对一个数取模时,可以等价a%b=a-a/b*b
关系(比较)运算符
逻辑运算符
运算符 | 描述 | 实例(假定A为True,B为False) |
---|---|---|
&& | 逻辑 与 运算符。如果两边的操作数均为True,则为True,否则为False | (A && B) 为False |
|| | 逻辑 或 运算符。如果两边的操作有一个为True,则有True,否则为False | (A || B)为True |
! | 逻辑 非 运算符。如果条件为True,则逻辑为False,否则为True | !(A && B)为True |
func main(){
var age int = 40
if age > 30 && age <= 40{
fmt.Println("666")
}
}
细节说明:
&&
也叫短路与
。如果第一个条件为false,则第二个条件不会判断,最终结果为false||
也叫短路或
。如果第一个条件为true,则第二个条件不会判断,最终结果为true
赋值运算符
赋值运算符就是将某个运算后的值,赋给指定的变量。与Python相同
// a += 1 与a = a + 1的结果相同。
// 且支持 =,+=,-=,*=,/=,%=等多种赋值运算符
位运算符
计算机中的计算都是通过位运算符和移位运算符中的补码实现计算。
运算符 | 描述 | 运算规则 |
---|---|---|
& | 按位与运算符"&"是双目运算符。其功能是参与运算的两数各对应的二进位相与 | 同时为1,结果为1,否则为0 |
| | 按位或运算符"|"是双目运算符。其功能是参与运算的两数各对应的二进位相或 | 有一个为1,结果为1,否则为0 |
^ | 按位异或运算符"^"是双目运算符。其功能是参与运算的二进位相异或 | 当二进位不同时,结果为1,否则为0 |
<< | 左移运算符"<<"是双目运算符。其功能把"<<"左边的运算数的各二进位全部左移若干位,高位抛弃,低位补0.左移n位就是乘以2的n次方 | 符号位不变,低位补0 |
>> | 右移运算符">>"是双目运算符。其功能把">>"左边的运算数的各二进位全部右移若干位,右移n位就是乘以2的n次方 | 低位溢出,符号位不变,并用符号位补溢出的高位 |
原码、反码、补码
对于有符号的数:
- 二进制的最高位是符号位,0表示正数,1表示负数;1转换为二进制为0000 00001,-1转换为二进制是1000 0001
- 正数的原码,反码,补码都一样;
- 负数的反码=它的原码符号位不变,其他位取反
- 1的原码[0000 0001],反码[0000 0001],补码[0000 0001]
-
-1的原码[1000 0001],反码[1111 1110],补码[1111 1111]
-
负数的补码=它的反码+1
- 0的反码,补码都是0
- 在计算机运算的时候,都是以补码的方式来运算的
位运算符和移位运算符
2的原码[0000 0010] 2的反码[0000 0010] 2的补码[0000 0010]
3的原码[0000 0011] 3的反码[0000 0011] 3的补码[0000 0011]
2&3的结果=2 // &代表两个二进制相同时时取1
2|3的结果=3 // |代表只要有1个就取1
2^3的结果=1 // ^代表两个二进制不同时取1
-5的原码[1000 0101] -5的反码[1111 1010]
-5的补码[1111 1011] 8的原码[0000 1000]
8的反码[0000 1000] 8的补码[0000 1000]
-5&8的补码=0000 1000,反码=0000 1000,原码=0000 1000 十进制结果=8
-5|8 的补码=1111 1011,反码=1111 1010,原码=1000 0101 十进制结果=-5
-5^8的补码=1111 0011 ,反码=1111 0010,原码=1000 1101 十进制结果=-13
-5的原码[1000 0101] -5的反码[1111 1010] -5的补码[1111 1011]
4的原码[0000 0100] 4的反码[0000 0100] 4的补码[0000 0100]
-5&4的补码=0000 0000,反码=0000 0000,原码=0000 0000 十进制结果=0
-5|4 的补码=1111 1111,反码=1111 1110,原码= 1000 0001 十进制结果=-1
-5^4的补码=1111 1111,反码=1111 1110,原码= 1000 0001 十进制结果=-1
其他运算符
运算符 | 描述 | 实例 |
---|---|---|
& | 返回变量存储地址 | &a;将给出变量的实际地址 |
* | 指针变量 | *a;是一个指针变量 |
运算符优先级*
只有单目运算符、赋值运算符是从后向左运算的。优先级一览表如下:
大致的顺序如下:
- 括号,
++
,--
- 单目运算
- 算术运算符
- 移位运算
- 关系运算符
- 位运算符
- 逻辑运算符
- 赋值运算符
- 逗号