【Golang - 90天从新手到大师】Day02 - 基本语法

系列文章合集
Golang - 90天从新手到大师

变量和常量

变量声明

标准声明

var name type

批量声明

var (name1 type1name2 type2...
)

声明时初始化

var name type = value

类型推导

var name = value

短变量声明

name := value

匿名声明

在使用多重赋值时,如果想要忽略某个值,可以使用匿名变量(anonymous variable)。 匿名变量用一个下划线_表示,多用于占位

func foo() (int, string) {return 10, "name"
}
func main() {x, _ := foo()_, y := foo()fmt.Println("x=", x)fmt.Println("y=", y)
}

常量声明

const identifier [type] = value    //显式类型定义
const identifier = value    //隐式类型定义

常量中的数据类型只可以是布尔型、数字型(整数型、浮点型和复数)和字符串型

iota

特殊常量,可以认为是一个可以被编译器修改的常量,代表了const声明块的行索引,可以被用作枚举值:

package mainimport "fmt"func main() {const (a = iota   //0b = iota   //1c          //2d = "ha"   //独立值,iota += 1e          //"ha"   iota += 1f = 100    //iota +=1g          //100  iota +=1h = iota   //7,恢复计数i          //8)const a1 = iota  //0fmt.Println(a,b,c,d,e,f,g,h,i)
}

结果为:0 1 2 ha ha 100 100 7 8
如果中断iota自增,则必须显式恢复;自增默认是int类型,可以自行进行显示指定类型。

指针

区别于C/C++中的指针,Go语言中的指针不能进行偏移和运算,是安全指针,指针操作只需要记住两个符号:&(取地址)和*(根据地址取值)即可。

空指针的值为nil

new

用于分配内存空间。

func new(Type) *Type —— new函数返回一个指向该类型内存地址的指针

make

make也是用于内存分配的,区别于new,它只用于slice、map以及chan的内存创建,而且它返回的类型就是这三个类型本身,而不是他们的指针类型,因为这三种类型就是引用类型,所以就没有必要返回他们的指针了。

func make(t Type, size ...IntegerType) Type
func main() {var b map[string]int              //声明map类型b = make(map[string]int, 10)      //初始化,不初始化会引发panicb["测试"] = 100                    //赋值fmt.Println(b)
}

数据类型

布尔型 bool

1.布尔类型变量的默认值为false。
2.Go 语言中不允许将整型强制转换为布尔型.
3.布尔型无法参与数值运算,也无法与其他类型进行转换.

整数型 int8、int16、int32、int64 有符号整型

uint8、uint16、uint32、uint64 无符号整型
int 32位操作系统上就是int32,64位操作系统上就是int64
uint 32位操作系统上就是uint32,64位操作系统上就是uint64
uintptr 无符号整型,用于存放一个指针
rune 代表一个 UTF-8字符,实际上是uint8
byte 代表一个ASCII码字符,实际上是int32

浮点型 float32、float64 浮点型

complex64、complex128 复数,实部和虚部各占一半

字符串型 string Go语言中的字符串以原生数据类型出现,使用字符串就像使用其他原生数据类型一样。

复合类型

1、指针类型(Pointer)

2、数组类型

3、结构化类型(struct)

4、Channel 类型

5、函数类型

6、切片类型

7、接口类型(interface)

8、Map 类型

字符串类型

字符串修改

字符串是不允许修改的,想要修改字符串,需要先将其转换成 []rune 或 []byte,修改后再转换为 string。无论哪种转换,都会重新分配内存,并复制字节数组。

字符串拼接

在Go中,字符串可以很方便的拼接:str := str1 + str2 + str3

字符串拼接在编译时都会被存放到一个切片中,拼接过程需要遍历两次切片,第一次遍历获取总长度,据此申请内存,第二次遍历会把字符串逐个拷贝过去。

为什么字符串不允许修改?

C++语言中的string,其本身拥有内存空间,修改string是支持的。但Go的实现中,string不包含内存空间,只有一个内存的指针,且通常指向字符串字面量,而字符串字面量存储位置是只读段,而不是堆或栈上,所以才有了string不可修改的约定。

[]byte 转换成 string 一定会拷贝内存吗?

在临时需要字符串的场景下,byte切片转换成string时并不会拷贝内存,而是直接返回一个string,这个string的指针(string.str)指向切片的内存。例如以下场景:

使用m[string(b)]来查找map(map是string为key,临时把切片b转成string);

字符串拼接,如"<" + “string(b)” + “>”;

字符串比较:string(b) == “foo”

Slice 类型

Slice又称动态数组,依托数组实现,可以方便的进行扩容、传递等,实际使用中比数组更灵活

Slice基本操作

切片追加

Go语言的内建函数 append() 可以为切片动态添加元素。可以一次添加一个元素,可以添加多个元素,也可以添加另一个切片中的元素。还可以使用 append() 来变相删除元素

func main(){var s []ints = append(s, 1)        // [1]s = append(s, 2, 3, 4)  // [1 2 3 4]s2 := []int{5, 6, 7}  s = append(s, s2...)    // [1 2 3 4 5 6 7]// 从切片中删除元素a := []int{30, 31, 32, 33, 34, 35, 36, 37}// 要删除索引为2的元素a = append(a[:2], a[3:]...)fmt.Println(a) //[30 31 33 34 35 36 37]
}

字符串切片

切片扩容

切片追加时,由于每个切片会指向一个底层数组,若这个数组的容量够用就添加新增元素。当底层数组不能容纳新增的元素时,切片就会自动按照一定的策略进行“扩容”,此时该切片指向的底层数组就会更换。“扩容”操作往往发生在 append() 函数调用时,所以我们通常都需要用原变量接收 append 函数的返回值。

func main() {//append()添加元素和切片扩容var numSlice []intfor i := 0; i < 10; i++ {numSlice = append(numSlice, i)fmt.Printf("%v  len:%d  cap:%d  ptr:%p\n", numSlice, len(numSlice), cap(numSlice), numSlice)}
}
输出:
[0]  len:1  cap:1  ptr:0xc0000a8000
[0 1]  len:2  cap:2  ptr:0xc0000a8040
[0 1 2]  len:3  cap:4  ptr:0xc0000b2020
[0 1 2 3]  len:4  cap:4  ptr:0xc0000b2020
[0 1 2 3 4]  len:5  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5]  len:6  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6]  len:7  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7]  len:8  cap:8  ptr:0xc0000b6000
[0 1 2 3 4 5 6 7 8]  len:9  cap:16  ptr:0xc0000b8000
[0 1 2 3 4 5 6 7 8 9]  len:10  cap:16  ptr:0xc0000b8000

可以看出:切片容量以 1、2、4、8、16 的规则再扩容,每次扩容后是扩容前的两倍。

slice扩容源码

从源码中可得:

如果新的长度(newlen)大于两倍旧容量(doublecap),最终容量(newcap)就是新的长度(newlen)

否则,如果旧容量小于256,最终容量就是两倍旧容量

否则,扩容计算方式变为 旧容量 +(旧容量 + 768)/4,也就是扩充(25%+192)的容量,不断扩容直到最终容量大于新长度

最后,若newcap溢出后,最终容量 就设为 新的长度

切片拷贝

浅拷贝

只拷贝了对象的全部内容,但不拷贝对象的引用的内容

func main() {s1 := make([]int, 3) //[0 0 0]s2 := s1             //将s1直接赋值给s2,s1和s2共用一个底层数组s2[0] = 100fmt.Println(s1) //[100 0 0]fmt.Println(s2) //[100 0 0]
}
深拷贝

使用 Go 语言内建的 copy() 函数可以轻松做到对切片的深拷贝

func main() {// copy()复制切片a := []int{1, 2, 3, 4, 5}c := make([]int, 5, 5)copy(c, a)     //使用copy()函数将切片a中的元素复制到切片cfmt.Println(a) //[1 2 3 4 5]fmt.Println(c) //[1 2 3 4 5]c[0] = 1000fmt.Println(a) //[1 2 3 4 5]fmt.Println(c) //[1000 2 3 4 5]
}

Map 类型

map是一种无序的基于key-value的数据结构,Go语言中的map是引用类型,必须初始化才能使用

使用 make()函数分配内存,语法为:

make ( map[KeyType]ValueType,  cap)

判断某个键是否存在,语法为:

value,ok := map[key]

map的遍历

Go 中使用 for range 遍历map

使用 delete() 函数删除键值对,语法为:

delete(map, key)

数据结构

hmap数据结构

了解map的大致用法后,看一下 map 的底层结构能更好的理解 map,Go 语言中 map 使用哈希表作为底层实现,map 类型的变量本质上是一个指针,指向 hamp 结构体。其数据结构如下:

源码文件:runtime/map.go line:117、134

type hmap struct {count     int     //当前元素个数flags     uint8    //当前状态B         uint8     //记录桶的数目是2^Bnoverflow uint16     //记录使用的溢出桶数量hash0     uint32     //hash种子buckets    unsafe.Pointer     //数组指针,记录桶的位置oldbuckets unsafe.Pointer    //扩容时,记录旧桶的位置nevacuate  uintptr        //渐进式扩容阶段,记录下一个迁移的旧桶编号extra *mapextra    //指向mapextra结构体,里面记录溢出桶相关信息}
type mapextra struct {overflow *[]*bmap  //记录已经使用的溢出桶的地址oldoverflow *[]*bmap  //旧桶使用的溢出桶的地址nextOverflow *bmap  //下一个空闲溢出桶的地址
}

一个哈希表可以有多个 bucket (哈希桶),而每个 bucket 能保存若干个键值对。

注意:当B大于4时,会额外创建 2^(B-4) 个桶用作溢出桶 (这些溢出桶和常规桶 在内存中是连续的)

bucket数据结构

源码文件:runtime/map.go  line:64、151

const bucketCnt  = abi.MapBucketCount     //=8 type bmap struct {tophash [bucketCnt]uint8  //存储哈希值的高8位//以下属性在编译期间生成;源码位于 src/cmd/compile/internal/reflectdata/reflect.go:MapBucketType   line:91keys     [8]keytypevalues   [8]valuetypepad      uintptr  //用于对齐内存overflow uintptr  //溢出桶的地址
}

显而易见,每个桶中定义了有8个哈希值的容量,即每个桶的容量为8,超过8个就要用到溢出桶。

除此之外,bucket 还有一些属性会在编译时动态生成,因为哈希表中可能存储不同类型的键值对,而且 Go 语言也不支持泛型,所以键值对占据的内存空间大小只能在编译时进行推导。

map扩容

触发扩容的条件有二个:

负载因子 > 6.5时,也即平均每个 bucket 存储的键值对达到6.5个。

noverflow >= 2^15(B > 15)或 noverflow >= 2^B(B <= 15) 时,也即溢出桶数量 超过 常规桶数量 或 32768 时。

条件不同触发的扩容方法也不同:

增量扩容

当负载因子过大时,就新建一个 bucket,新的 bucket 长度是原来的2倍,然后旧 bucket 数据分流到两个新的 bucket 中。

等量扩容

当溢出桶过多,但负载因子又没有超过阈值时,就进行等量扩容。(即条件2)

所谓等量扩容,实际上并不是扩大容量,而是创建和旧桶一样多的新桶,然后搬迁键值对,把松散的键值对重新排列一次,以使 bucket 的使用率更高,进而保证更快的存取。

struct 类型

结构体的定义
使用 type 和 struct 关键字来定义结构体,语法如下:

type 类型名 struct {字段名 字段类型字段名 字段类型…
}

在 Go 1.9 版本新增了类型别名的新功能,语法如下:

type 类型别名 = 类型名
结构体实例化

package mainimport ("fmt"
)type A struct {a stringb int8
}func main() {//普通实例化var a1 A  //没初始化前,成员变量都是零值a1.a = "a"a1.b = 1//匿名结构体var user struct {Name stringAge  int}user.Name = "小王子"user.Age = 18fmt.Printf("%#v\n", user)    //struct { Name string; Age int }{Name:"小王子", Age:18}//指针类型结构体var a2 = new(A)fmt.Printf("%T\n", a2) //*main.A//取结构体的地址实例化var a3 = &A{}          //相当于进行了 newa3.a = "a3"            //底层是(*a3).a = "a3",是 Go 语言的语法糖fmt.Printf("%T\n", a3) //*main.A
}

构造函数
  Go 语言的结构体并没有构造函数,但我们可以通过初始化来简便的实现构造函数。

func newA(a string, b int8) *A {return &A{a: a,b: b,}
}
//调用构造函数
func main() {a1 := NewA("a",1)fmt.Println(a1) 
}

方法
  在 Go 语言中,结构体就像是类的简化形式。但结构体内不允许定义函数,也没有类的概念,怎么定义方法呢?通过绑定类型来实现方法。

Go 语言的方法,是作用在接收者(receiver)上的一个函数,是一种特殊的函数。举个例子:

type Person struct{name stringage int
}func (this *Person) GetName() {fmt.Printf(this.Name)
}func main(){person := Person{Name: "xiaofan",Age:  18,}person.GetName()  //执行Person绑定的方法GetNamereturn
}

方法与函数的区别是,函数不属于任何类型,方法属于特定的类型。方法的调用看起来就像 C++ 中调用类的成员函数一样 —— 对象.方法。

结构体的“继承”
  Go 语言中可以通过(组合)结构体嵌套来实现“继承”。

package mainimport ("fmt"
)type Person struct { //结构体大写开头 —— 公有Name string //属性大写开头 —— 公有属性age  int    //属性小写开头 —— 私有属性
}func (p *Person) GetName() { //方法大写开头 —— 共有fmt.Printf("%s\n", p.Name)
}type Student struct {Person     //这样Student类就可以继承Person类了score  int //student类自己的属性
}func (s *Student) getscore() { //方法小写开头 —— 私有fmt.Println(s.score)
}func main() {stu := Student{}stu.Name = "小饭" //继承父类的属性stu.score = 100stu.GetName() //继承父类的方法stu.getscore()
}

Go 语言的公有是可挎包访问,在同一包内 私有属性同样可访问。

结构体标签(Tag)
  Go的struct声明允许字段附带 Tag 来对字段做一些标记。

Tag 是结构体的元信息,可以在运行的时候通过反射的机制读取出来。Tag 在结构体字段的后方定义,由一对反引号包裹起来,具体的格式如下:

key1:"value1" key2:"value2"

举个例子:

//Student 学生
type Student struct {ID     int    `json:"id"` //通过指定tag实现json序列化该字段时的keyGender string //json序列化是默认使用字段名作为keyname   string //私有不能被json包访问
}func main() {s1 := Student{ID:     1,Gender: "男",name:   "学生",}data, err := json.Marshal(s1)if err != nil {fmt.Println("json marshal failed!")return}fmt.Printf("json str:%s\n", data) //json str:{"id":1,"Gender":"男"}
}

Tag 常见用法主要是JSON数据解析、ORM映射等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/32199.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【第一性原理】邓巴数字

这里写自定义目录标题 什么是邓巴数字邓巴数背后的科学历史上各个组织的人数与邓巴数字的关系在人类进化中的意义现代社会中邓巴数字的体现邓巴数字的意义其他与沟通相关的数据注意事项结论参考 罗宾邓巴教授生于1947年&#xff0c;进化心理学家&#xff0c;牛津大学教授&#…

[信号与系统]关于LTI系统的转换方程、拉普拉斯变换和z变换

前言 本文还是作为前置知识。 LTI系统的传递函数 LTI系统的传递函数 H ( z ) H(z) H(z) 是输出信号的z变换 Y ( z ) Y(z) Y(z) 与输入信号的z变换 X ( z ) X(z) X(z) 的比值&#xff1a; H ( z ) Y ( z ) X ( z ) H(z) \frac{Y(z)}{X(z)} H(z)X(z)Y(z)​ 多项式比值表…

C++之提高篇

1.标准输入输出流 cin与cout的使用&#xff0c;就不多说了&#xff0c;说一个有关保留小数位数的操作&#xff0c;使用ostream对象的precision&#xff08;&#xff09;方法&#xff0c;表达的意思是数字总共有几位&#xff0c;注意&#xff0c;此时是包括整数部分的&#xff…

OpenAI策略:指令层级系统让大模型免于恶意攻击

现代的大模型&#xff08;LLMs&#xff09;不再仅仅是简单的自动完成系统&#xff0c;它们有潜力赋能各种代理应用&#xff0c;如网页代理、电子邮件秘书、虚拟助手等。然而&#xff0c;这些应用广泛部署的一个主要风险是敌手可能诱使模型执行不安全或灾难性的行动&#xff0c;…

使用 Python 进行测试(7)...until you make it

总结 我很懒&#xff0c;我想用最少的行动实现目标&#xff0c;例如生成测试数据。我们可以&#xff1a; 使用随机种子保证数据的一致性。 >>> random.seed(769876987698698) >>> [random.randint(0, 10) for _ in range(10)] [10, 9, 1, 9, 10, 6, 5, 10…

计算机组成原理 | 硬件电路整理

计算机组成原理 | 硬件电路整理 桶形移位器原理图 全加器逻辑框图 多位可控加减法电路逻辑框图 可级联的4位先行进位电路 4位快速加法器 16位组内并行、组间并行加法器 实现原码一位乘法的逻辑框图 补码一位乘法的逻辑框图 无符号数阵列乘法器 原码不恢复余数法硬件逻辑框图 基…

vue第一次页面加载会触发那几个钩子函数?

在 Vue.js 中&#xff0c;当页面或组件第一次加载时&#xff0c;会触发一系列的生命周期钩子。特别是关于首次加载的&#xff0c;主要的几个钩子函数是 beforeCreate、created、beforeMount、mounted。 以下是一个简单的 Vue 组件示例&#xff0c;其中包含了这些钩子函数&…

Matlab 单目相机标定(内置函数,棋盘格)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 具体的标定原理可以参阅之前的博客Matlab 单目相机标定(内置函数),这里实现对棋盘格数据的标定过程。 二、实现代码 getCameraCorners.m function [camCorners, usedImIdx, imCheckerboard] = getCameraCorners(…

规模弹性: 管理谷歌的TPUv4机器学习超级计算机(二)

本文为翻译文章&#xff0c;原文为&#xff1a; Resiliency at Scale: Managing Google’sTPUv4 Machine Learning Supercomputer。 由于字数过长&#xff0c;文章分为两期发布&#xff0c;本片涵盖原文后半部分4&#xff5e;9节&#xff0c;前三章节请参考文章&#xff1a;规…

Mybatis plus:IService接口

一、介绍 在MybatisPlus框架中&#xff0c;IService接口扮演着重要的角色。作为一个通用的服务接口&#xff0c;IService定义了一系列方法&#xff0c;包括查询、插入、更新、删除等。这些方法的定义使得在服务层进行数据库操作变得更为便捷和高效。 IService 接口是一个泛型接…

Springboot应用的信创适配-补充

Springboot应用的信创适配-CSDN博客 因为篇幅限制&#xff0c;这里补全Spring信创适配、数据库信创适配、Redis信创适配、消息队列信创适配等四个章节。 Springboot应用的信创适配 Springboot应用的信创适配&#xff0c;如上图所示需要适配的很多&#xff0c;从硬件、操作系统、…

Linux中文件常用的压缩与解压

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

大模型Transformer讲解

文章目录 1. Transformer 原理1.1 注意力机制 (Attention Mechanism)1.2 多头注意力 (Multi-Head Attention) 2. Transformer 设计2.1 编码器 (Encoder)2.2 解码器 (Decoder) 3. Transformer 应用3.1 自然语言处理 (NLP)3.2 计算机视觉 (Computer Vision) 4. Transformer 优点和…

Android 判断手机桌面是否已经存在App的快捷方式

我们需要在桌面添加快捷方式的时候&#xff0c;为了优化体验&#xff0c;有时候需要判断桌面上是否已经存在快捷方式。 经过自己探索整理&#xff0c;代码如下&#xff1a; /*** 判断桌面是否已添加快捷方式*/fun hasShortcut(context: Context): Boolean {try {var result f…

基于AT89C52单片机的温度报警系统

点击链接获取Keil源码与Project Backups仿真图: https://download.csdn.net/download/qq_64505944/89456321?spm=1001.2014.3001.5503 仿真构造:AT89C52+DS18B20温度模块+三按键+蜂鸣器+四位数码管显示+电源模块。 压缩包构造:源码+仿真图+设计文档+原理图+开题文档+元件…

Java宝藏实验资源库(3)类

一、实验目的 理解面向对象程序的基本概念。掌握类的继承的实现机制。熟悉类中成员的访问控制方法。熟悉ArrayList类的使用。 二、实验内容、过程及结果 *9.5Programming Exerc ise the GregorianCal endar class) Java API has the GregorianCalendar class in the java. uti…

民生银行北京分行金融科技校招面试记录

本文介绍2024届春招中&#xff0c;中国民生银行下属北京分行的金融科技岗位1场面试的基本情况、提问问题等。 2024年04月投递了中国民生银行下属北京分行的金融科技岗位&#xff0c;暂时不清楚所在部门。目前完成了一面与终面&#xff0c;在这里记录一下面试的相关经历。 首先&…

LayoutSystem布局系统

简介: LayoutSystem,是UGUI中由CanvasUpdateSystem发起(m_LayoutRebuildQueue中大部分都是LayoutRebuilder)的关于布局排列的处理系统。 类图: 布局过程 核心代码讲解: LayoutRebuilder

前端编程语言——JS语言结构、函数、数组、字符串、日期、对象、定时器(2)

0、前言&#xff1a; 这篇文章记录的是我自己的学习笔记。在python中通过input来获取输入&#xff0c;在JS中用prompt()&#xff0c;来获取输入。写JS代码要记得每个代码结束要加上分号。 1、JS编程语言结构&#xff1a; 顺序结构&#xff1a;从上往下依次执行分支结构&#…

【数据结构】顺序表实操——通讯录项目

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸您能阅读我的文章&#xff0c;诚请评论指点&#xff0c;欢迎欢迎 ~~ &#x1f4a5;&#x1f4a5;个人主页&#xff1a;奋斗的小羊 &#x1f4a5;&#x1f4a5;所属专栏&#xff1a;C语言 &#x1f680;本系列文章为个人学习…