Golang学习-CH2 基本语法

2.1 Go语言变量的声明

Go语言是静态类型语言,因此变量(variable)是有明确类型的,编译器也会检查变量类型的正确性。

从计算机系统实现角度来看,变量是一段或多段用来存储数据的内存。


Go语言的基本类型:

  • bool
  • string
  • int、int8、int16、int32、int64
  • uint、uint8、uint16、uint32、uint64、uintptr
  • byte // uint8 的别名
  • rune // int32 的别名 代表一个 Unicode 码
  • float32、float64
  • complex64、complex128

Go语言声明:

  1. 声明变量的一般形式是使用var关键字:
var name type //var为声明变量关键字,type为变量类型,name为变量名

通过声明变量时将变量的类型放在变量名称后面,避免类型C语言中的含糊不清:int *a, b ;,其中a为指针,b不是。

var a,b int*	//均为指针
  1. 批量声明格式:使用关键字var和括号,可以将一组变量定义放在一起。
var (
	a int
    b string
    c [ ]float32
    d func( ) bool
    e struct {
		x int
    }
)
  1. 简短格式,形式为名字 := 表达式
i, j := 0, 1

简短模式有以下限制:

  • 定义变量,同时显式初始化。
  • 不能提供数据类型。
  • 只能用在函数内部。

2.2 Go语言变量的初始化

Go语言在声明变量时,自动对变量对应的内存区域进行初始化操作。每个变量会初始化其类型的默认值,例如:整型和浮点型变量的默认值为 0 和 0.0。字符串变量的默认值为空字符串。布尔型变量默认为 bool。切片、函数、指针变量的默认为 nil。

C语言中,变量在声明时,并不会对变量对应内存区域进行清理操作。

微软的 VC 编译器会将未初始化的栈空间以 16 进制的 0xCC 填充,而未初始化的堆空间使用 0xCD 填充,而 0xCCCC 和 0xCDCD 在中文的 GB2312 编码中刚好对应“烫”和“屯”字。


Go语言变量初始化:

  1. 变量初始化的标准格式:
var hp int = 100
  1. 编译器推导格式的类型,对上述标识格式的简化:
var hp = 100

将int省略后,编译器会尝试根据等号右边的右值表达式推导变量类型。需要注意的是,对于浮点型会自动推导为float64类型。

  1. 短变量声明并初始化,对应2.1中的简短格式:
hp := 100

注意事项同2.1中说明,这种方式为Go语言的推导声明写法,由于使用了:=,而不是赋值符号=,因此推导声明的左值变量必须是没有定义过的变量

var hp int
hp:=100 //报错

实际使用角度来说,短变量声明的形式比较多:

conn, err := net.Dial("tcp", "127.0.0.1:8080")
conn2, err := net.Dial("tcp", "127.0.0.1:8080")
  • 一方面避免了需要先var,再使用(对于部分变量的应用场景而言)
  • 多个短变量声明和赋值中,至少有一个新变量的声明出现在左值即可,即使其他变量是重复声明的。

2.3 Go语言多个变量同时赋值

对于Go语言来说,这个阶段的开发环境中,内存不再是稀缺资源,所以对于交换变量而言:

var a int =100
var b int =200
a, b = b, a
fmt.Println( a, b )

多重赋值时,变量的左值和右值按从左到右的顺序赋值。

2.4 Go语言匿名变量

通过使用匿名变量,在一些应用场景下增强代码的灵活性。

Go语言的匿名变量的特点是一个下划线 __ 本身就是一个特殊的标识符,被称为空白标识符

它可以像其他标识符那样用于变量的声明或赋值(任何类型都可以赋值给它),但任何赋给这个标识符的值都将被抛弃,因此这些值不能在后续的代码中使用,也不可以使用这个标识符作为变量对其它变量进行赋值或运算。

  • 匿名变量不占用内存空间,不会分配内存。匿名变量与匿名变量之间也不会因为多次声明而无法使用。

类似Linux中重定向/dev/null的作用。匿名变量在某些函数接口中充当占位,而后续又不需要该接口返回的信息。

提示:在 Lua 等编程语言里,匿名变量也被叫做哑元变量。

func GetData() (int, int) {
	return 100, 200
}

func main() {
	a, _ := GetData()	//通过匿名变量,200被丢弃
	_, b := GetData()	//通过匿名变量,100被丢弃
	fmt.Println(a, b)
}

2.5 Go语言变量的作用域

一个变量(常量、类型或函数)在程序中都有一定的作用范围,称之为作用域。

学习过C++的应该能很快理解。

作用域之间的嵌套影响同C++,内层屏蔽外层。

根据变量定义位置(作用范围)的不同,可以分为以下三个类型:

  • 函数内定义的变量称为局部变量:作用域只在函数体内,函数的参数和返回值变量都属于局部变量。局部变量不是一直存在的,它只在定义它的函数被调用后存在,函数调用结束后这个局部变量随着栈空间的消失就会被销毁。
  • 函数外定义的变量称为全局变量:全局变量只需要在一个源文件中定义,就可以在所有源文件中使用,当然,不包含这个全局变量的源文件需要使用“import”关键字引入全局变量所在的源文件之后才能使用这个全局变量。
  • 函数定义中的变量称为形式参数:形式参数只在函数调用时才会生效,函数调用结束后就会被销毁,在函数未被调用时,函数的形参并不占用实际的存储单元,也没有实际值(即函数调用栈仍未形成)。

2.6 Go语言整型

Go语言的数值类型分为:整数、浮点数、复数。

Go语言同时提供了有符号无符号的整数类型,其中包括:

  • int8、int16、int32 和 int64 四种大小截然不同的有符号整数类型,分别对应 8、16、32、64 bit(二进制位)大小的有符号整数
  • 与此对应的是 uint8、uint16、uint32 和 uint64 四种无符号整数类型
  • 此外还有两种整数类型 int 和 uint,它们分别对应特定 CPU 平台的字长。根据不同平台可能为4字节或者8字节。
  • Go语言中有符号整数采用补码形式,int8的范围为-128到127
  • 用来表示 Unicode 字符的 rune 类型和 int32 类型是等价的,通常用于表示一个 Unicode 码点。这两个名称可以互换使用。
  • 同样,byteuint8也是等价类型,byte 类型一般用于强调数值是一个原始的数据而不是一个小的整数。

在涉及不同平台时,为了减少编译目标平台字节长度的影响,不要使用intunit


math包中可以查看不同数值类型的最大和最小值:

fmt.Println(math.MaxInt32)

2.7 Go语言浮点类型

Go语言提供了两种精度的浮点数 float32float64,它们的算术规范由 IEEE754 浮点数国际标准定义:

  • 一个 float32 类型的浮点数可以提供大约 6 个十进制数的精度,而 float64 则可以提供约 15 个十进制数的精度,通常应该优先使用 float64 类型,因为 float32 类型的累计计算误差很容易扩散。
var f float32 = 16777216 // 1 << 24
fmt.Println(f == f+1)    // "true"! 精度误差
  • 常见的浮点数声明形式:
//只写小数或者整数部分
const e = .71828 // 0.71828
const f = 1.     // 1
//使用科学计数法
const Avogadro = 6.02214129e23  // 阿伏伽德罗常数
const Planck   = 6.62606957e-34 // 普朗克常数

2.8 Go语言复数

在计算机中,复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)

Go语言中复数的类型有两种,分别是 complex128(64 位实数和虚数)和 complex64(32 位实数和虚数),其中 complex128 为复数的默认类型。实部和虚部部分都为浮点类型。

  • 复数的定义声明:
var name complex128 = complex(32.1, 42,1) //complex 为Go语言的内置函数用于为复数赋值
name:=complex(x, y) //简化模式
  • 获得复数的实部和虚部:
var x complex128 = complex(1, 2) // 1+2i
fmt.Println(real(x))      	//实部
fmt.Println(imag(x))      //虚部
  • 复数也有相应的运算规则,也可以使用==!=进行比较。
  • Go语言内置的 math/cmplx 包中提供了很多操作复数的公共方法,实际操作中建议大家使用复数默认的 complex128 类型,因为这些内置的包中都使用 complex128 类型作为参数。

2.9 Go语言bool类型

  • 一个布尔类型的值只有两种:true 或 false。if 和 for 语句的条件部分都是布尔类型的值,并且==<等比较操作也会产生布尔型的值。

  • 布尔值可以和 &&(AND)和 ||(OR)操作符结合,并且有短路行为:如果运算符左边的值已经可以确定整个布尔表达式的值,那么运算符右边的值将不再被求值。

  • &&的优先级比||高(&& 对应逻辑乘法,|| 对应逻辑加法)

  • Go语言中不允许将整型强制转换为布尔型。布尔型无法参与数值运算,也无法与其他类型进行转换。如果有需要,可以自己将该功能封装成函数。

    var n bool
    fmt.Println(int(n) * 2) //编译错误
    

2.10 Go语言字符串

Unicode和UTF-8

Unicode 与 ASCII 类似,都是一种字符集。字符集为每个字符分配一个唯一的 ID,我们使用到的所有字符在 Unicode 字符集中都有一个唯一的 ID。

UTF-8 是变长编码规则,将 Unicode 中字符的 ID 以某种方式进行编码,从 1 到 4 个字节不等。编码规则如下:

  • 0xxxxxx 表示文字符号 0~127,兼容 ASCII 字符集。
  • 从 128 到 0x10ffff 表示其他字符。

根据这个规则,拉丁文语系的字符编码一般情况下每个字符占用一个字节,而中文每个字符占用 3 个字节。

Go语言中的字符串

  • 使用""双引号来定义字符串,字符串中可以使用常用的转义字符

  • 使用反引号来定义跨行字符串,其中的所有转义字符均无效,用于内嵌源码等

    const str = `第一行
    第二行
    第三行
    \r\n
    `
    
  • 字符串拼接符号:

    str := "Beginning of the string " +
    "second part of the string"
    //因为编译器会在行尾自动补全分号,所以拼接字符串用的加号“+”必须放在第一行末尾。
    
  • 一般的比较运算符(==、!=、<、<=、>=、>)是通过在内存中按字节比较来实现字符串比较的

  • 字符串所占的字节长度可以通过函数 len() 来获取,索引下标从0开始(对纯ASCII码字符串有意义),不允许获取某个其中某个字节的地址&str[ i ]

计算字符串长度:len 和 RuneCountlnString

Go 语言的内建函数 len(),可以用来获取切片、字符串、通道(channel)等的长度。

但是由于Go 语言的字符串都以 UTF-8 格式保存,每个中文占用 3 个字节,使用这种方式获取的字符串长度往往并不符合实际预期。使用RuneCountInString() 函数,统计 Uncode 字符数量。

fmt.Println(utf8.RuneCountInString("忍者"))

遍历字符串

  • ASCII方式使用for循环遍历:
theme := "狙击 start"
for i := 0; i < len(theme); i++ {
    fmt.Printf("ascii: %c  %d\n", theme[i], theme[i])
}
//这种方式下单个字节单个字节取内存,汉字显示为乱码
  • Unicode方式遍历:使用for range
theme := "狙击 start"
for _, s := range theme {
    fmt.Printf("Unicode: %c  %d\n", s, s)
}

字符串截取和搜索

利用 strings.Index() 函数和截取切片在字符串中搜索另外一个子串

tracer := "死神来了, 死神bye bye"
comma := strings.Index(tracer, ", ") //返回找到的位置
pos := strings.Index(tracer[comma:], "死神")
fmt.Println(comma, pos, tracer[comma+pos:])
  • strings.Index:正向搜索子字符串。
  • strings.LastIndex:反向搜索子字符串。
  • 搜索的起始位置可以通过切片偏移制作。

Go语言修改字符串

Go 语言的字符串无法直接修改每一个字符元素,只能通过重新构造新的字符串数组并赋值给原来的字符串变量实现。请参考下面的代码:

angel := "Heros never die"
angleBytes := []byte(angel)
for i := 5; i <= 10; i++ {
    angleBytes[i] = ‘ ‘
}
fmt.Println(string(angleBytes))
  • 字符串不可变有很多好处,如天生线程安全
  • 代码中实际修改的是字符串数组 []byte,[]byte 在 Go 语言中是可变的,本身就是一个切片。
  • 修改字符串时,可以将字符串转换为 []byte 进行修改。
  • []byte 和 string 可以通过强制类型转换互转。

Go语言字符串拼接

  • 使用+可以进行拼接操作,但是效率并不高
  • 使用类似于 StringBuilder 的机制来进行高效的字符串连接
hammer := "吃我一锤"
sickle := "死吧"
// 声明字节缓冲
var stringBuilder bytes.Buffer
// 把字符串写入缓冲
stringBuilder.WriteString(hammer)
stringBuilder.WriteString(sickle)
// 将缓冲以字符串形式输出
fmt.Println(stringBuilder.String())

Go语言格式化输出

fmt.Sprintf(格式化样式, 参数列表…)

  • 格式化样式:字符串形式,格式化动词以%开头。
  • 参数列表:多个参数以逗号分隔,个数必须与格式化样式中的个数一一对应,否则运行时会报错。
  • 整体风格上延续了C的方式
动 词 功 能
%v 按值的本来值输出
%+v 在 %v 基础上,对结构体字段名和值进行展开
%#v 输出 Go 语言语法格式的值
%T 输出 Go 语言语法格式的类型和值
%% 输出 % 本体
%b 整型以二进制方式显示
%o 整型以八进制方式显示
%d 整型以十进制方式显示
%x 整型以十六进制方式显示
%X 整型以十六进制、字母大写方式显示
%U Unicode 字符
%f 浮点数
%p 指针,十六进制方式显示

2.11 Go语言字符类型 byte and rune

Go语言的字符有以下两种:

  • 一种是 uint8 类型,或者叫 byte 型,代表了 ASCII 码的一个字符。
  • 另一种是 rune 类型,代表一个 UTF-8 字符,当需要处理中文、日文或者其他复合字符时,则需要用到 rune 类型。rune 类型等价于 int32 类型。

Unicode 包中内置了一些用于测试字符的函数,这些函数的返回值都是一个布尔值,如下所示(其中 ch 代表字符):

  • 判断是否为字母:unicode.IsLetter(ch)
  • 判断是否为数字:unicode.IsDigit(ch)
  • 判断是否为空白符号:unicode.IsSpace(ch)

2.12 Go语言数据类型转换

Go语言不存在隐式类型转换,因此所有的类型转换都必须显式的声明:

valueOfTypeB = typeB(valueOfTypeA)
  • 只有相同底层类型的变量之间可以进行相互转换,不同底层类型的变量相互转换时会引发编译错误(如将 bool 类型转换为 int 类型)
  • 从取值范围大到小的类型转换会丢失精度

2.13 Go语言指针

指针(pointer)在Go语言中可以被拆分为两个核心概念:

  • 类型指针,允许对这个指针类型的数据进行修改,传递数据可以直接使用指针,而无须拷贝数据,类型指针不能进行偏移和运算。
  • 切片,由指向起始元素的原始指针、元素数量和容量组成。

可以理解为前者为指针const,提供访问特定内存的方式;后者为指向的数据const,为迭代器的功能

  • 当一个指针被定义后没有分配到任何变量时,它的默认值为 nil

  • 当使用&操作符对普通变量进行取地址操作并得到变量的指针后,可以对指针使用*操作符,也就是指针取值

  • 创建指针的另一种方法——new() 函数,new() 函数可以创建一个对应类型的指针,创建过程会分配内存,被创建的指针指向默认值。

2.14 Go语言变量逃逸分析

  • 栈空间由于BSPESP指针的存在,对于整块内存的分配和回收会比较迅速
  • 堆分配内存和栈分配内存相比,堆适合不可预知大小的内存分配。但是为此付出的代价是分配速度较慢,而且会形成内存碎片。
  • 在使用Go语言进行编程时,Go语言的设计者不希望开发者将精力放在内存应该分配在栈还是堆的问题上,编译器会自动帮助开发者完成这个纠结的选择,但变量逃逸分析也是需要了解的一个编译器技术。编译器觉得变量应该分配在堆和栈上的原则是:
    • 变量是否将在其他代码块中被使用;

2.15 Go语言变量的生命周期

生命周期和作用域为两个不同的概念,可能在其生命周期内却无法被使用了。

  • 全局变量:它的生命周期和整个程序的运行周期是一致的;
  • 局部变量:它的生命周期则是动态的,从创建这个变量的声明语句开始,到这个变量不再被引用为止;
  • 形式参数和函数返回值:它们都属于局部变量,在函数被调用的时候创建,函数调用结束后被销毁。
  • 但实际上还是得看是否有逃逸情况的出现

2.16 Go语言常量和const关键字

Go语言中的常量使用关键字 const 定义,用于存储不会改变的数据,常量是在编译时被创建的。定义常量的表达式必须为能被编译器求值的常量表达式。

const b string = "abc"	//显式类型定义
const b="abc"				//隐式类型定义

如果没有显式指明类型,那么将从右边的表达式推断类型

const noDelay time.Duration = 0
const timeout = 5 * time.Minute
fmt.Printf("%T %[1]v\n", noDelay)     // "time.Duration 0"
fmt.Printf("%T %[1]v\n", timeout)     // "time.Duration 5m0s"
fmt.Printf("%T %[1]v\n", time.Minute) // "time.Duration 1m0s"

批量声明常量:

const (
    a = 1
    b
    c = 2
    d
)
fmt.Println(a, b, c, d) // "1 1 2 2"

iota常量生成器:

type Weekday int
const (
    Sunday Weekday = iota
    Monday
    Tuesday
    Wednesday
    Thursday
    Friday
    Saturday
)

常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量,但是不用每行都写一遍初始化表达式。在一个 const 声明语句中,在第一个声明的常量所在的行,iota 将会被置为 0,然后在每一个有常量声明的行加一。

无类型常量:

编译器为没有明确的基础类型的数字常量提供比基础类型更高精度的算术运算,可以认为至少有 256bit 的运算精度。这里有六种未明确类型的常量类型,分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。

通过延迟明确常量的具体类型,不仅可以提供更高的运算精度,而且可以直接用于更多的表达式而不需要显式的类型转换

var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi

2.17 Go语言类型别名 type关键字

区别类型别名类型定义

type TypeAlias = Type	//类型别名
type TypeNew Type 		//类型定义

类型别名规定:TypeAlias 只是 Type 的别名,本质上 TypeAlias 与 Type 是同一个类型

package main
import (
    "fmt"
)
// 将NewInt定义为int类型
type NewInt int
// 将int取一个别名叫IntAlias
type IntAlias = int
func main() {
    // 将a声明为NewInt类型
    var a NewInt
    // 查看a的类型名
    fmt.Printf("a type: %T\n", a)	// NewInt
    // 将a2声明为IntAlias类型
    var a2 IntAlias
    // 查看a2的类型名
    fmt.Printf("a2 type: %T\n", a2)		//int
}
  • 非本地类型不能定义方法
import (
    "time"
)
// 定义time.Duration的别名为MyDuration
type MyDuration = time.Duration
// 为MyDuration添加一个函数
func (m MyDuration) EasySet(a string) {
}

编译器提示:不能在一个非本地的类型 time.Duration 上定义新方法,非本地类型指的就是 time.Duration 不是在 main 包中定义的,而是在 time 包中定义的,与 main 包不在同一个包中,因此不能为不在一个包中的类型定义方法。

2.18 Go语言注释

  • //单行注释
  • /**/多行注释
  • 在 package 语句之前的注释内容将被默认认为是这个包的文档说明。
  • godoc工具:可以从 Go 程序和包文件中提取*声明的首行注释以及每个对象的相关注释,并生成相关文档

2.19 Go语言关键字与标识符

Go语言的词法元素包括 5 种,分别是标识符(identifier)、关键字(keyword)、操作符(operator)、分隔符(delimiter)、字面量(literal),它们是组成Go语言代码和程序的最基本单位。

  • 标识符的命名第一个字符必须为字母,为变量、函数、常量命名时采用驼峰命名法,例如 stuName、getVal;
  • Go语言中的变量、函数、常量名称的首字母也可以大写,如果首字母大写,则表示它可以被其它的包访问(类似于 Java 中的 public);如果首字母小写,则表示它只能在本包中使用 (类似于 Java 中 private)。

2.20 Go语言运算符的优先级

http://c.biancheng.net/view/5559.html

2.21 Go语言字符串和数值类型的相互转换:strconv

strconv 包中常用的函数包括 Atoi()、Itia()、parse 系列函数、format 系列函数、append 系列函数等

  • Itoa():整型转字符串
func main() {
    num := 100
    str := strconv.Itoa(num)
    fmt.Printf("type:%T value:%#v\n", str, str)
}
  • Atoi():字符串转整型
func Atoi(s string) (i int, err error)	//err 在转换成功是为空转换失败时为相应的错误信息。

Parse 系列函数用于将字符串转换为指定类型的值,其中包括 ParseBool()、ParseFloat()、ParseInt()、ParseUint()。

Format 系列函数实现了将给定类型数据格式化为字符串类型的功能,其中包括 FormatBool()、FormatInt()、FormatUint()、FormatFloat()。

http://c.biancheng.net/view/5112.html
------------恢复内容结束------------

Golang学习-CH2 基本语法

上一篇:Qt:QDateTime


下一篇:DRF之权限和频率限制