Go语言基础

一  变量

1.1 变量的声明

1.1.1 标准格式

var 变量名 变量类型

var a int // 声明一个整形类型变量
var b string // 声明一个字符串类型变量
var c []float32 // 声明一个32位的浮点类型的变量
var d func() bool // 声明一个函数变量,变量值是bool,将函数以变量形式保存
// 声明一个结构体的变量

var e struct{
  
x int
}

1.1.2 批量格式

var (

变量名 变量类型

)

var (

   x int

   y string

   z []float32

)

 

1.2 初始化变量

我们可以在声明变量的时候对变量进行赋值,如果不赋值,也会进行初始化。

// go语言在声明变量的时候,自动对变量对应的内存区域进行初始化操作,每一个变量会初始化其类型的默认值

// 整形和浮点型默认值是0

// 字符串默认值是空串

// 布尔类型默认为bool

// 切片、函数和指针变脸默认为nil

注意:我们在使用C语言的时候,如果声明了变量,没有初始化,可能该变量指向的内存区域的值没有被清理,容易产生问题。所以开发者在C中需要习惯对变量进行初始化。

1.2.1 标准初始化格式

var 变量名 类型 = 变量值

var a int = 100

var b string = "hello"
 

1.2.2 编译器推导类型

// 编译器推导类型(省略数据类型,由编译器自己确定)

var x = 122.5

var y = a * 2

 

1.2.3 短变量声明并初始化

还有一种更为精简的写法,var关键字都不需要

p := 200
由于使用:= 所以左边的变量必须是没有定义过的,如果是这样的就不允许:
var p int

p := 200
编译就会报错
 
使用方法:
import "net"
func main() {

   conn,err := net.Dial("tcp","127.0.0.1")

}
 
如果是多个短变量,至少有一个是新声明的变量,其他都是已经声明过得,也不会报错。
 
1.2.4 多个变量同时赋值
 
以前的时候交换值如下:
var m int = 100

var n int = 20

var t int

t = m

m = n

n = t
 

后面有了更精简的交换:

func main() {

   var m int = 100

   var n int = 20



   m = m ^ n

   n = n ^ m

   m = m ^ n

   fmt.Println(m,n)

}

 

Go语言,现在写法更加简单,可以进行多重复值

格式:m,n=n,m

多重赋值的时候,是变量先从左边开始赋值,然后到右边,这里就是先把n的值赋给m,再把m的值赋给n

func main() {

   var m int = 100

   var n int = 20



   m,n = n,m

   fmt.Println(m,n)

}

 

1.2.5 匿名变量(没有名字的变量)

就是使用下划线,表示匿名变量,比如在多重赋值的时候,如果左边值中某一变量不需要接收变量,则可以使用_匿名变量替换即可

func main() {

   c,_ := getData()

   _,d := getData()

   fmt.Println(c,d)

}
func getData()(int,int) {

   return 200,500;

}

 

二 数据类型以及类型转换

2.1 基本数据类型

2.1.1 整形

int 主要分为8位、16位、32位、64位,即int8/int16/int32/int64,也就是1字节、2字节、4字节、8字节

还有对应的无符号整形:uint8/unit16/uint32/uint64

无符号和有符号区别:即最高位是否可以是否表示符号,比如1字节,最大值就是2^8=256,最小就是1,用无符号uint8表示就是[0,255]; 有符号就是[-128,127] 个数也是256个。

2.1.2 浮点型

Go语言支持2种浮点类型:float32和float64,可以使用常亮max.MaxFloat32和max.MaxFloat64,如果使用格式化参数替换的时候,要是用%f

import "fmt"
func main() {
  
var a float32 = 100.3456
  
var b float64 = 10.2
  
fmt.Printf("%f\n",a)
  
fmt.Printf("%.2f\n",a)
  
fmt.Printf("%.2f\n",b)
}

100.345596

100.35

10.20

2.1.3 布尔型

bool变量只有true和false两种值,午饭参与运算,也无法进行类型转换

 

2.1.4 字符串

用双引号括起来的就是字符串,比如:

var c string="hello"
var d = "cool"
e := "honey"
fmt.Print(c,d,e)

 

转义字符:

\r: 回车符 \n:换行符 \t: 制表符 \'单引号 \"双引号 \\ 反斜杠/

 

定义多行字符串:通过反引号,将原始内容全部赋值给变量,不进行转义

var multi_line string = `
       hello word
       cool boy
       \r\n\t
   `

fmt.Print(multi_line)

hello word

cool boy

\r\n\t

 

2.1.5 字符

Go语言中有两种表示字符的数据类型:byte和rune

byte: 是一个int8类型,表示单个拉丁字母

rune: 是一个int32类型,表示 UTF-8字符,比如中文、日文等

var x byte = 'a'
var y rune = '好'
fmt.Printf("%d %T\n",x,x)
fmt.Printf("%d %T\n",y,y)

97 uint8

22909 int32

 

UTF-8和Unicode有何区别?

Unicode是字符集,ASCII也是字符集。

字符集为每一个字符分配一个唯一的ID,我们使用到的字符在Unicode中都有唯一的ID和她对应,比如你在Unicode中编码为20320,但是在不同的国家中,你的ID会不同,但是Unicode中的字符ID是不会变化

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

#1 0xxxxx表示符号0-127,兼容ASCII字符集

#2 从128 到0x10ffff表示其他字符

 

 

2.1.6 切片

切片,类似于数组,表示相同类型元素的容器,和数组不同的是,数组长度是不可变的,切片是可变长的,声明方式如下:

var arr []int

// 创建长度为3切片,没有值
arr := make([]int,3)
// 为切片元素赋值
arr[0] = 1
arr[1] = 2
arr[2] = 3

//
声明字符串
str := "hello world"
//
根据字符串从指定位置开始进行切片
fmt.Printf(str[6:])

 

2.2 类型转化

Go语言使用类型前置括号的方式进行类型转换,一般格式如下:

T(表达式)

其中T表示需要转换的类型,表达式包括变量,复杂算子和函数返回值等

fmt.Println("int8 range: ", math.MaxInt8,math.MaxInt8)
fmt.Println("int16 range: ", math.MaxInt16,math.MaxInt16)
fmt.Println("int31 range: ", math.MaxInt32,math.MaxInt32)
fmt.Println("int64 range: ", math.MaxInt64,math.MaxInt64)

// 初始化一个32位整数值
var m int32 = 1047483647
//
输出16进制和10进制
fmt.Println("int32: 0x%x %d\n", m,m)

// m转化为16进制
n := int16(m)
fmt.Println("int16: 0x%x %d\n", n,n)

// 将常亮保存为float32
var p float32 = math.Pi
// 转化为int类型,浮点发生精度丢失
fmt.Println(int(p))

 

三 指针

Go语言的指针区别于C、C++,不能进行便宜和运算,是一种安全的指针。要搞明白Go语言中的指针,需要搞懂以下几个概念:

指针类型:表示某变量是真真类型,用 *数据类型 表示指针类型,我们不能对指针类型赋其他值,比如int或者float之类的。创建指针类型有2种方式:

#1 通过*数据类型

#2 new(数据类型)

 

import "fmt"
func main() {
  
var foo int
  
var bar *int
  
// 获取变量值和变量类型
  
fmt.Printf("%v %T\n",foo,foo)
  
fmt.Printf("%v %T\n",bar,bar)
}

0 int

<nil> *int

 

func main() {
  
// 第二种创建指针的方式 new
  
str := new(string)
  
num := new(int16)
  
fmt.Println(str,num)
}

 

 

指针地址:变量在内存中的地址,我们可以通过 &变量名 获取该变量的地址,即指针指向的内存地址

import "fmt"
func main() {
  
var foo int
  
var bar *int
  
// 获取变量foo的指针地址
  
bar = &foo

  
// 获取变量值和变量类型
  
fmt.Printf("%v %T\n",foo,foo)
  
fmt.Printf("%v %T\n",bar,bar)
}

0 int

0xc00000a0a8 *int

 

指针取值:就是取该变量对应指针的值,通过 *变量名 就可以获取这个变量的指针的值。

import "fmt"
func main() {
  
var foo int = 10
  
var bar *int
  
// 获取变量foo的指针地址
  
bar = &foo

  
// 获取变量值和变量类型
  
fmt.Printf("%v %T\n",foo,foo)
  
// 通过*bar获取bar类型的值
  
fmt.Printf("%v %T\n",*bar,*bar)
}

10 int

10 int

我们可以对指针取值进行赋值,改变原来的值

import "fmt"
func main() {
  
var foo int = 10
  
var bar *int
  
// 获取变量foo的指针地址
  
bar = &foo
  
// 对获取foo变量指针地址重新赋值,这样就会修改foo的值
  
*bar = 100
   //
获取变量值和变量类型
  
fmt.Printf("%v %T\n",foo,foo)
  
// 通过*bar获取bar类型的值
  
fmt.Printf("%v %T\n",*bar,*bar)
}

 

100 int

100 int

 

公式:*&x = x

综合测试:

func test() {
  
var a int = 1
  
var b *int = &a
  
// 因为是**int 表示直接把后面的值赋给变量,即var c = &b
  
var c **int = &b
  
var d int = *b

  
fmt.Println("a = ", a) // a = 1
  
fmt.Println("&a = ", &a) // 取a的内存地址(指针地址) 0xc000062080
  
fmt.Println("*&a = ",*&a) // 对&a表示的指针地址进行取值,也就是1
  
fmt.Println("b = ", b) // 因为&a赋给了b,所以b=&a即a的内存地址 0xc000062080
  
fmt.Println("&b = ", &b) // &b就是取b这个变量的地址 0xc00008e018
  
fmt.Println("*&b = ", *&b) // 对&b表示的指针地址取值,也就是b的值,即a的指针地址 0xc000062080
  
fmt.Println("*b = ", *b) // 对b表示的a内存地址取值,也就是1
  
fmt.Println("c = ", c) // 因为是**int 表示直接把后面的值赋给变量,即 c = &b,即b的地址 0xc00008e018
  
fmt.Println("*c = ", *c) // 对变量c去指针地址的值,就等于*&b,即取b指针地址的值,而b指针的值就是a的指针地址 0xc000062080
  
fmt.Println("&c = ", &c) // 取c的指针地址 0xc00008e020
  
fmt.Println("*&c = ", *&c) // 取c指针地址的值,即c的值,c值就是b的地址 0xc00008e018
  
fmt.Println("**c = ", **c) // 先取变量c指针地址,即a的指针地址,再对a的指针地址取值,即1
  
fmt.Println("***&*&*&*&c= ",***&*&*&*&c) // 因为*&c= c,推出这个可以简化为**c即1
  
fmt.Println("d = ", d) // 对b表示的a内存地址取值,也就是1
}

 

 

四 变量生命周期

变量的生命周期指的是变量使用范围。在C或者C++,程序员需要考虑变量内存分配问题,哪些变量需要使用栈进行内存分配,哪些使用堆内存分配。一般而言,函数体内的私有线程的变量一般使用栈进行分批;全局变量、结构体成员适合使用堆分配。Go语言将整个过程合并到编译器中,叫做变量逃逸分析,程序员对于内存分配过程无感知。

 

五 字符串应用

5.1 计算字符长度

ASCII字符串使用len计算长度;如果包含Unicode字段则使用utf8.RuneCountInString()。

len()函数可以用来获取切片、字符串、通道的长度。

foo := "love you,and love me"
bar := "四川省"

 

// 长度为19
fmt.Println("计算字符长度: ",len(foo))
// 长度为9, why? 难道不是3吗? go语言中字符串都是以UTF-8格式保存,每一个中文占用3个字节,所以三个中文字符就是9个字节长度
fmt.Println("计算字符长度: ",len(bar))
// 那如果按照中国习惯该如何使用呢,go提供了utf8.RuneCountInString函数
fmt.Println("计算字符长度: ", utf8.RuneCountInString(bar))

 

5.2 遍历字符串,获取每一个字符串元素

5.2.1 遍历每一个ASCII字符,直接使用下标

for i := 0; i < len(foo); i++{
  
fmt.Printf("ASCII: %c %d\n",foo[i],foo[i])
}

 

5.2.2 遍历带Unicode字符串的每一个字符,需要使用for range

for _,s:= range bar {
  
fmt.Printf("Unicode: %c %d\n",s,s)
}

 

5.3 获取指定字符串索引和截取字符串

5.3.1 strings.Index:获取某个字符的索引位置(正向搜索)

comma := strings.Index(foo,",")
fmt.Println("正向索引:",foo[comma:])
// 获取comma位置开始截取之后字符串,包括该位置的元素
fmt.Println("正向截取字符串:",foo[comma:])
// 截取指定开始位置和结束位置的元素
fmt.Println("正向截取字符串:",foo[comma+5:comma+9])
pos := strings.Index(foo[comma:],"love")
fmt.Println("正向截取字符串:",foo[comma+pos:])

 

5.3.2 strings.LastIndex:获取某个字符的索引位置(反向搜索)

idx := strings.LastIndex(foo,"me");
fmt.Println("反向索引",idx)
fmt.Println("反向截取字符串:",foo[idx:])

 

5.4 修改字符串

Go无法将字符串直接进行修改,只能通过重新构造新的字符串赋予给原来的字符串变量实现。

Go语言和字符串和其他高级语言,比如Java一样,都是Immutable不可变的,这样可以保证天生线程安全,大家使用的都是只读对象。无需加锁,而且方便共享内存。

怎么修改呢? 可以将字符串转化为byte数组,因为byte数组是可以变化的,本身是一个切片,然后转化完成再将byte数组转换成字符串。

angle := "heros never die"
angleBytes := []byte(angle)
for i:=5; i<=10;i++ {
  
angleBytes[i] = ' '
}
fmt.Println("修改字符串",string(angleBytes))

 

5.5 拼接字符串

Go语言可以使用+来连接字符串,但是和Java一样,此种方式效率不高,Go语言中也提供了StringBuilder的机制来进行高效的拼接

one := "我爱你"
two := ","
three := "成都"
// 声明字节缓冲区

var builder bytes.Buffer
// 调用WriteString,把字符串写入缓冲区
builder.WriteString(one)
builder.WriteString(two)
builder.WriteString(three)

// 调用String(),将缓冲以字符串形式输出
fmt.Println(builder.String())

 

5.6 格式化字符串

// %v: 按照值本来输出
// %+v:在%v基础上对结构体字段名和值进行展开
// %#v:输出Go语言语法格式的值
// %T:输出类型
// %b:二进制格式输出;%o: 八进制 %d:十进制 %x 十六进制 %X: 16进制,字母大写 %U:Unicode字符 %f: 浮点数 %p指针

// 按照指定格式
name := "我"
num := 999
word := fmt.Sprintf("你的酒馆对%s打了烊,但是还是送了你%d朵玫瑰",name,num)
fmt.Println("[字符串格式化]",word)

// 直接输出值
pi := 3.14159
variant := fmt.Sprintf("%v,%v,%v","思密达",pi,true)
fmt.Println("[字符串格式化]",variant)

profile := &struct {
  
Name string
  
HP int

}{
  
Name: "rat",
  
HP: 150,
}

fmt.Printf("使用'%%+v' %+v\n",profile)
fmt.Printf("使用'%%#v' %#v\n",profile)
fmt.Printf("使用'%%T' %T\n",profile)

 

[字符串格式化] 你的酒馆对我打了烊,但是还是送了你999朵玫瑰

[字符串格式化] 思密达,3.14159,true

使用'%+v' &{Name:rat HP:150}

使用'%#v' &struct { Name string; HP int }{Name:"rat", HP:150}

使用'%T' *struct { Name string; HP int }

 

5.7 去掉多余的空白字符

/** 7 去掉多余的空白字符 */
fmt.Println(strings.TrimSpace(keys));

 

5.8 对字符串切片,分成不同部分

/** 8 对字符串切片,分成不同部分 */
text
:= "中国:四川:成都";
splits := strings.Split(text,":");
for i := 0; i < len(splits);i++{
  
fmt.Println(splits[i])
}

 

六 常量和类型别名

6.1 常量

6.1.1 普通常量

func main() {
  
// 常量的声明把var换成了const
  
const pi = 3.14159
  
const e = 2.718281
  
const NUM int32 = 16
  
const TOKEN string = "token"
}

 

6.1.2 枚举

const (
  
SPRING = 0
  
SUMMER = 1
  
AUTUMN = 2
  
WINTER = 3
)
fmt.Println("[枚举类型]",SPRING,SUMMER,AUTUMN,WINTER)

 

 

 

 

6.2 类型别名

type 别名 = 类型(Type)

6.2.1 区分类型别名和类型定义

// 自定义类型,声明的变量是自定义类型
type NewInt int16

// 定义类型别名,声明的变量是还是原来的类型
type NewString = string

var a NewInt = 10
var b NewString = "NewString"
//
自定义类型,声明的变量是自定义类型,所以结果是NewInt
fmt.Printf("a type: %T\n",a)
// 定义类型别名,声明的变量是还是原来的类型,所以结果是string
fmt.Printf("b type: %T\n",b)

 

6.2.2 不能使用别名类型在非本地中定义方法

type MyDuration = time.Duration

// Cannot define new methods on non-local type time.Duration
func (m MyDuration) EasySet(a string){

}

非本地方法指的是在time.Duration 以外的其他包下,比如这里的main包,和time.Duration不在同一个包。那如果我一定要使用呢,有没有什么办法?

第一:我们可以将别名定义为自定义类型,即 type MyDuration time.Duration

type MyDuration  time.Duration

func (m MyDuration) EasySet(a string){

}

第二:将这个别名类型定义在time包中

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值