#Go即将支持泛型#关于泛型,你应该了解的一些细节

GoLangzkbhj 发表了文章 • 0 个评论 • 1345 次浏览 • 2020-06-17 10:48 • 来自相关话题

什么是泛型?

“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”

 什么是自然语言?

“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”


这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?

“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”

 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}
比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。 查看全部
什么是泛型?


“泛型是程序设计语言的一种特性。允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”


 什么是自然语言?


“自然语言通常是指一种自然地随文化演化的语言。英语、汉语、日语为自然语言的例子,而世界语则为人造语言,即是一种由人蓄意为某些特定目的而创造的语言。 不过,有时所有人类使用的语言(包括上述自然地随文化演化的语言,以及人造语言) 都会被视为“自然”语言,以相对于如编程语言等为计算机而设的“人造”语言。这一种用法可见于自然语言处理一词中。自然语言是人类交流和思维的主要工具。 自然语言是人类智慧的结晶,自然语言处理是人工智能中最为困难的问题之一,而对自然语言处理的研究也是充满魅力和挑战的。 ”



这是百度百科的解释,通俗地说我们日常交流使用的语言都是自然语言,比如汉语、英语、法语、藏语等等。
 
什么是程序设计语言?


“程序设计语言,programming language。用于书写计算机程序的语言。语言的基础是一组记号和一组规则。根据规则由记号构成的记号串的总体就是语言。在程序设计语言中,这些记号 串就是程序。程序设计语言有3个方面的因素,即语法、语义和语用。语法表示程序的结构或形式,亦即表示构成语言的各个记号之间的组合规律,但不涉及这些记 号的特定含义,也不涉及使用者。语义表示程序的含义,亦即表示按照各种方法所表示的各个记号的特定含义,但不涉及使用者。语用表示程序与使用者的关系。 ”


 泛型不是自然语言里的概念,那么它们之间有关系吗?泛型在自然语言里找到的出处吗?
 
有关系。因为泛型是面向对象里的概念,而面向对象是一种对现实世界理解和抽象的方法,自然语言也是对现实世界的一种理解,所以它们之间是有关系的。
 
比如这么一段程序,就实现了泛型。
class Test<T>
{
public T obj;
public Set(T t)
{
this.obj = t;
}
}

比如说C#里的List,它是一个泛型类,把它翻译成中文就是列表。

List<T> ;

T是占位类型。List就像是一个容器,可以向里面放任何类型。

创建一个List是这样List<string> list = new List<string>();

如果有一个学生类型,那么可以这样List<学生> list ;

如果用中文表示,可以这样声明 列表<学生>list,

去掉符号就是 学生列表list

“学生列表”这是符合自然语言的偏正短语。

这样就证明了自然语言是支持泛型。最大的不同是类型名称和占位类型的前后位置不同,在程序设计语言是列表<学生>,在自然语言中是学生列表。

自然语言也支持两个泛型参数的泛型类。

比如Dictionary,根据它的功能用准确点的称呼“键值表”。比如声明一个Dictionary<姓名,学生>,就是声明一个姓名学生键值表。“姓名学生键值表”这也是符合汉语语法的短语。

所以自然语言是支持程序设计语言中的泛型的。
 
重要说明~
 
泛型,一般指编译期间,由编译器去认知的类型识别。注意两个词:时间点--“编译期”,服务目标--“编译器”。编译器干完活后,出来的就是精准而无冗余的代码

另一种叫“运行时”识别,消耗一定的内存空间,来存放和记录类型属性,这种一般不叫泛型。比如 Go 的 interface 可认为就是这种。

顺序一致性内存模型是个啥

GoLangzkbhj 发表了文章 • 0 个评论 • 1557 次浏览 • 2020-06-16 11:10 • 来自相关话题

顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
一个线程中的所有操作必定按照程序的顺序来执行。所有的线程都只能看到一个单一的执行顺序,不管是否同步。每个操作都必须原子执行且立即对所有程序可见。
 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。





加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。





未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
  查看全部
顺序一致性是多线程环境下的理论参考模型,为程序提供了极强的内存可见性保证,在顺序一致性执行过程中,所有动作之间的先后关系与程序代码的顺序一致。
 
特性
 
  1. 一个线程中的所有操作必定按照程序的顺序来执行。
  2. 所有的线程都只能看到一个单一的执行顺序,不管是否同步。
  3. 每个操作都必须原子执行且立即对所有程序可见。

 
举例
 
假设有两个线程A和B并发执行。其中A线程有3个操作,他们在程序中的顺序是:A1→A2→A3。B线程也有3个操作,他们在程序中的顺序是:B1→B2→B3。

假设这两个线程使用监视器锁来正确同步:A线程的3个操作执行后释放监视器锁,随后B线程获取同一个监视器锁。那么程序在顺序一致性模型中的执行效果将如下图所示。

201910291005_1.png

加锁
 
现在我们再假设这两个线程没有做同步,下面是这个未同步程序在顺序一致性模型中的执行示意图,如下图所示。

201910291005_2.png

未加锁
 
未同步程序在顺序一致性模型中虽然整体执行顺序是无序的,但所有线程都只能看到一个一致的整体执行顺序。以上图为例,线程A和B看到的执行顺序都是:B1→A1→A2→B2→A3→B3。之所以能得到这个保证是因为顺序一致性内存模型中的每个操作必须立即对任意线程可见。
 
参考文档
https://blog.csdn.net/en_joker ... 24331
 

#2020学习打卡##Go语言高级编程# 什么是第一类对象?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2304 次浏览 • 2020-06-09 17:23 • 来自相关话题

#2020学习打卡##Go语言高级编程# Go语言中的函数闭包

GoLangzkbhj 发表了文章 • 0 个评论 • 1194 次浏览 • 2020-06-09 16:54 • 来自相关话题

今天的学习内容是Go函数,这里面会涉及一个概念——闭包!复习一下~
 
闭包的概念

是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。

各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。

在javascript语言或者go中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。

闭包的价值 

闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回。


闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。 
总结

闭包并不是一门编程语言不可缺少的功能,但闭包的表现形式一般是以匿名函数的方式出现,就象上面说到的,能够动态灵活的创建以及传递,体现出函数式编程的特点。所以在一些场合,我们就多了一种编码方式的选择,适当的使用闭包可以使得我们的代码简洁高效。

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
 下面用一个例子体现闭包
package main

import "fmt"
/*
李逵和武松的Study方法的逻辑是几乎一模一样的
然而为了分别保存两人的学习进度,需要开辟两个全局变量,函数内部的需要使用两条分支结构才能完成业务逻辑
如果是108将都来学习。。。
此时代码的可复用性很差
*/
var progress int
func Study(name string, hours int) ( int) {
fmt.Printf("%s学习了%d小时",name,hours)
progress += hours
return hours
}
func main081() {
progress := Study("黑旋风",5)
fmt.Printf("李逵的学习进度%d/10000",progress)
}

/*
使用闭包函数优化Study
每个人有不同的学习进度,将这个进度保存在【各自的闭包】中
*/
/*
闭包函数:返回函数的函数
闭包函数的好处:使用同一份内层函数的代码,创建出任意多个不同的函数对象,这些对象各自的状态都被保存在函数闭包(外层函数)中,各行其道,互不干扰
*/

func GetStudy(name string) func(int) int{
var progress int
study := func(hours int) int {
fmt.Printf("%s学习了%d小时\n", name ,hours)
progress += hours
return progress
}
return study
}

func main() {
studyFunc := GetStudy("李逵")
studyFunc(3)
studyFunc(5)
likuiProgress := studyFunc(2)
fmt.Printf("李逵的学习进度%d/10000\n",likuiProgress)
studyFunc1 := GetStudy("宋江")
studyFunc1(9)
studyFunc1(8)
songjiangProgress1 := studyFunc1(5)
fmt.Printf("宋江的学习进度%d/10000\n",songjiangProgress1)
}李逵学习了3小时
李逵学习了5小时
李逵学习了2小时
李逵的学习进度10/10000
宋江学习了9小时
宋江学习了8小时
宋江学习了5小时
宋江的学习进度22/10000
 
 
 
参考文档:
https://www.cnblogs.com/cxying93/p/6103375.html
https://www.cnblogs.com/hzhuxin/p/9199332.html
https://www.cnblogs.com/yunweiqiang/p/11796135.html
  查看全部
今天的学习内容是Go函数,这里面会涉及一个概念——闭包!复习一下~
 
闭包的概念


是可以包含自由(未绑定到特定对象)变量的代码块,这些变量不在这个代码块内或者任何全局上下文中定义,而是在定义代码块的环境中定义。要执行的代码块(由于自由变量包含在代码块中,所以这些自由变量以及它们引用的对象没有被释放)为自由变量提供绑定的计算环境(作用域)。


各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数

在javascript语言或者go中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成“定义在一个函数内部的函数“。

所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁

闭包的价值 


闭包的价值在于可以作为函数对象或者匿名函数,对于类型系统而言,这意味着不仅要表示数据还要表示代码。支持闭包的多数语言都将函数作为第一级对象,就是说这些函数可以存储到变量中作为参数传递给其他函数,最重要的是能够被函数动态创建和返回



闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在函数调用后被自动清除。

Go语言中的闭包同样也会引用到函数外的变量。闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。 
总结

闭包并不是一门编程语言不可缺少的功能,但闭包的表现形式一般是以匿名函数的方式出现,就象上面说到的,能够动态灵活的创建以及传递,体现出函数式编程的特点。所以在一些场合,我们就多了一种编码方式的选择,适当的使用闭包可以使得我们的代码简洁高效

使用闭包的注意点

由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包
 下面用一个例子体现闭包
package main

import "fmt"
/*
李逵和武松的Study方法的逻辑是几乎一模一样的
然而为了分别保存两人的学习进度,需要开辟两个全局变量,函数内部的需要使用两条分支结构才能完成业务逻辑
如果是108将都来学习。。。
此时代码的可复用性很差
*/
var progress int
func Study(name string, hours int) ( int) {
fmt.Printf("%s学习了%d小时",name,hours)
progress += hours
return hours
}
func main081() {
progress := Study("黑旋风",5)
fmt.Printf("李逵的学习进度%d/10000",progress)
}

/*
使用闭包函数优化Study
每个人有不同的学习进度,将这个进度保存在【各自的闭包】中
*/
/*
闭包函数:返回函数的函数
闭包函数的好处:使用同一份内层函数的代码,创建出任意多个不同的函数对象,这些对象各自的状态都被保存在函数闭包(外层函数)中,各行其道,互不干扰
*/

func GetStudy(name string) func(int) int{
var progress int
study := func(hours int) int {
fmt.Printf("%s学习了%d小时\n", name ,hours)
progress += hours
return progress
}
return study
}

func main() {
studyFunc := GetStudy("李逵")
studyFunc(3)
studyFunc(5)
likuiProgress := studyFunc(2)
fmt.Printf("李逵的学习进度%d/10000\n",likuiProgress)
studyFunc1 := GetStudy("宋江")
studyFunc1(9)
studyFunc1(8)
songjiangProgress1 := studyFunc1(5)
fmt.Printf("宋江的学习进度%d/10000\n",songjiangProgress1)
}
李逵学习了3小时
李逵学习了5小时
李逵学习了2小时
李逵的学习进度10/10000
宋江学习了9小时
宋江学习了8小时
宋江学习了5小时
宋江的学习进度22/10000

 
 
 
参考文档:
https://www.cnblogs.com/cxying93/p/6103375.html
https://www.cnblogs.com/hzhuxin/p/9199332.html
https://www.cnblogs.com/yunweiqiang/p/11796135.html
 

#2020学习打卡##Go语言高级编程# Go语言中的Nil到底是什么?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1992 次浏览 • 2020-06-08 17:26 • 来自相关话题

#2020学习打卡##Go语言高级编程# 什么是鸭子对象模型?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 2065 次浏览 • 2020-06-08 16:01 • 来自相关话题

#2020学习打卡##Go语言高级编程# 详细理解数组array、字符串string和切片slice的异同

GoLangzkbhj 发表了文章 • 0 个评论 • 821 次浏览 • 2020-06-08 12:37 • 来自相关话题

Go语言中数组、字符串和切片三者是密切相关的数据结构。这3种数据类型,在底层原始数据有着相同的内存结构,在上层,因为语法的限制而有着不同的行为表现。

Go语言的数据是一种值类型,虽然数组的元素可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。

Go语言字符串底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导师底层数据的复制。

切片的底层数据虽然也是对应数据类型的数组,但是每个切片还有独立的长度和容量信息,切片赋值和函数的传参时也是将切片头信息部分按传值方式处理。因为切片头含有底层数据的指针,所以它的赋值也不会导致底层数据的复制。
 
数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一个部分,不同长度或不同类型的数据组成的数组都是不同的类型,因此在Go语言中很少直接使用数组(不同长度的数组因为类型不同无法直接赋值)。和数组对应的类型是切片,切片是可以动态增长和收缩的序列,切片的功能也更加灵活,但是要理解切片的工作原理还是要先理解数组。

定义方式
var a [3]int // 定义长度为3的int型数组, 元素全部为0
var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。

第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。

第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和 map[int]Type 类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。

第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
 数组与指针

Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a // b 是指向数组的指针
fmt.Println(a[0], a[1]) // 打印数组的前2个元素
fmt.Println(b[0], b[1]) // 通过数组指针访问数组元素的方式和数组类似可以将数组看作一个特殊的结构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数 len 可以用于计算数组的长度, cap 函数可以用于计算数组的容量。不过对于数组类型来说, len 和 cap 函数返回的结果始终是一样的,都是对应数组类型的长度。

遍历for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}用 for range 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。

字符串

一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。

Go语言字符串的底层结构在 reflect.StringHeader 中定义:
type StringHeader struct {
Data uintptr
Len int
}字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。

字符串其实是一个结构体,因此字符串的赋值操作也就是 reflect.StringHeader 结构体的复制过程。

字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量):
s := "hello, world"
hello := s[:5]
world := s[7:]
s1 := "hello, world"[:5]
s2 := "hello, world"[7:]切片

切片(slice)是一种简化版的动态数组。切片的结构定义, reflect.SliceHeader :
type SliceHeader struct {
Data uintptr
Len int
Cap int
}
 





 定义方式
var (
a []int // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
c = []int{1, 2, 3} // 有3个元素的切片, len和cap都为3
d = c[:2] // 有2个元素的切片, len为2, cap为3
e = c[0:2:cap(c)] // 有2个元素的切片, len为2, cap为3
f = c[:0] // 有0个元素的切片, len为0, cap为3
g = make([]int, 3) // 有3个元素的切片, len和cap都为3
h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
)和数组一样,内置的len()函数返回切片中有效元素的长度,内置的cap()函数返回切片容量的大小,容量必须大于或等于切片的长度。
 
遍历
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}在对切片本身赋值或参数传递时,和数组指针的操作方式类似,只是复制切片头信息( reflect.SliceHeader ),并不会复制底层的数据。

添加
 
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包    注意,在容量不足的情况下,append()操作会导致重新分配内幕才能,可能导致巨大的内存分配和复制数据的代价。及时容量足够,依然需要用append()函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。 
删除切片元素

    根据要删除的元素的位置,有从开头位置删除、从中间位置删除和从尾部删除3种情况,其中删除切片尾部的元素最快:
a = []int{1, 2, 3}
a = a[:len(a) - 1] //删除尾部1个元素
a = a[:len(a) - n] //删除尾部n个元素 删除开头的元素可以直接移动数据指针:
a = []int{1, 2, 3}
a = a[1:] //删除开头1个元素
a = a[N:] //删除开头N个元素
多维切片
 
同数组一样,切片也可以有多个维度。
package main

import (
"fmt"
)


func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}数据结果:C C++
JavaScript
Go Rust
更多参考文档:
https://www.jianshu.com/p/4eb3e490abe1
https://blog.51cto.com/14073476/2478276
https://cloud.tencent.com/developer/article/1354294
  查看全部
Go语言中数组、字符串和切片三者是密切相关的数据结构。这3种数据类型,在底层原始数据有着相同的内存结构,在上层,因为语法的限制而有着不同的行为表现。

Go语言的数据是一种值类型,虽然数组的元素可以被修改,但是数组本身的赋值和函数传参都是以整体复制的方式处理的。

Go语言字符串底层数据也是对应的字节数组,但是字符串的只读属性禁止了在程序中对底层字节数组的元素的修改。字符串赋值只是复制了数据地址和对应的长度,而不会导师底层数据的复制。

切片的底层数据虽然也是对应数据类型的数组,但是每个切片还有独立的长度和容量信息,切片赋值和函数的传参时也是将切片头信息部分按传值方式处理。因为切片头含有底层数据的指针,所以它的赋值也不会导致底层数据的复制。
 
数组

数组是一个由固定长度的特定类型元素组成的序列,一个数组可以由零个或多个元素组成。数组的长度是数组类型的组成部分。因为数组的长度是数组类型的一个部分,不同长度或不同类型的数据组成的数组都是不同的类型,因此在Go语言中很少直接使用数组(不同长度的数组因为类型不同无法直接赋值)。和数组对应的类型是切片,切片是可以动态增长和收缩的序列,切片的功能也更加灵活,但是要理解切片的工作原理还是要先理解数组。

定义方式
var a [3]int // 定义长度为3的int型数组, 元素全部为0
var b = [...]int{1, 2, 3} // 定义长度为3的int型数组, 元素为 1, 2, 3
var c = [...]int{2: 3, 1: 2} // 定义长度为3的int型数组, 元素为 0, 2, 3
var d = [...]int{1, 2, 4: 5, 6} // 定义长度为6的int型数组, 元素为 1, 2, 0, 0, 5, 6
第一种方式是定义一个数组变量的最基本的方式,数组的长度明确指定,数组中的每个元素都以零值初始化。

第二种方式定义数组,可以在定义的时候顺序指定全部元素的初始化值,数组的长度根据初始化元素的数目自动计算。

第三种方式是以索引的方式来初始化数组的元素,因此元素的初始化值出现顺序比较随意。这种初始化方式和 map[int]Type 类型的初始化语法类似。数组的长度以出现的最大的索引为准,没有明确初始化的元素依然用0值初始化。

第四种方式是混合了第二种和第三种的初始化方式,前面两个元素采用顺序初始化,第三第四个元素零值初始化,第五个元素通过索引初始化,最后一个元素跟在前面的第五个元素之后采用顺序初始化。
 数组与指针

Go语言中数组是值语义。一个数组变量即表示整个数组,它并不是隐式的指向第一个元素的指针(比如C语言的数组),而是一个完整的值。当一个数组变量被赋值或者被传递的时候,实际上会复制整个数组。如果数组较大的话,数组的赋值也会有较大的开销。为了避免复制数组带来的开销,可以传递一个指向数组的指针,但是数组指针并不是数组。
var a = [...]int{1, 2, 3} // a 是一个数组
var b = &a // b 是指向数组的指针
fmt.Println(a[0], a[1]) // 打印数组的前2个元素
fmt.Println(b[0], b[1]) // 通过数组指针访问数组元素的方式和数组类似
可以将数组看作一个特殊的结构体,结构的字段名对应数组的索引,同时结构体成员的数目是固定的。内置函数 len 可以用于计算数组的长度, cap 函数可以用于计算数组的容量。不过对于数组类型来说, len 和 cap 函数返回的结果始终是一样的,都是对应数组类型的长度。

遍历
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}
用 for range 方式迭代的性能可能会更好一些,因为这种迭代可以保证不会出现数组越界的情形,每轮迭代对数组元素的访问时可以省去对下标越界的判断。

字符串

一个字符串是一个不可改变的字节序列,字符串通常是用来包含人类可读的文本数据。和数组不同的是,字符串的元素不可修改,是一个只读的字节数组。每个字符串的长度虽然也是固定的,但是字符串的长度并不是字符串类型的一部分。

Go语言字符串的底层结构在 reflect.StringHeader 中定义:
type StringHeader struct {
Data uintptr
Len int
}
字符串结构由两个信息组成:第一个是字符串指向的底层字节数组,第二个是字符串的字节的长度。

字符串其实是一个结构体,因此字符串的赋值操作也就是 reflect.StringHeader 结构体的复制过程。

字符串虽然不是切片,但是支持切片操作,不同位置的切片底层也访问的同一块内存数据(因为字符串是只读的,相同的字符串面值常量通常是对应同一个字符串常量):
s := "hello, world"
hello := s[:5]
world := s[7:]
s1 := "hello, world"[:5]
s2 := "hello, world"[7:]
切片

切片(slice)是一种简化版的动态数组。切片的结构定义, reflect.SliceHeader :
type SliceHeader struct {
Data uintptr
Len int
Cap int
}

 

QQ截图20200608123151.jpg

 定义方式
var (
a []int // nil切片, 和 nil 相等, 一般用来表示一个不存在的切片
b = []int{} // 空切片, 和 nil 不相等, 一般用来表示一个空的集合
c = []int{1, 2, 3} // 有3个元素的切片, len和cap都为3
d = c[:2] // 有2个元素的切片, len为2, cap为3
e = c[0:2:cap(c)] // 有2个元素的切片, len为2, cap为3
f = c[:0] // 有0个元素的切片, len为0, cap为3
g = make([]int, 3) // 有3个元素的切片, len和cap都为3
h = make([]int, 2, 3) // 有2个元素的切片, len为2, cap为3
i = make([]int, 0, 3) // 有0个元素的切片, len为0, cap为3
)
和数组一样,内置的len()函数返回切片中有效元素的长度,内置的cap()函数返回切片容量的大小,容量必须大于或等于切片的长度。
 
遍历
for i := range a {
fmt.Printf("a[%d]: %d\n", i, a[i])
}
for i, v := range b {
fmt.Printf("b[%d]: %d\n", i, v)
}
for i := 0; i < len(c); i++ {
fmt.Printf("c[%d]: %d\n", i, c[i])
}
在对切片本身赋值或参数传递时,和数组指针的操作方式类似,只是复制切片头信息( reflect.SliceHeader ),并不会复制底层的数据。

添加
 
var a []int
a = append(a, 1) // 追加1个元素
a = append(a, 1, 2, 3) // 追加多个元素, 手写解包方式
a = append(a, []int{1,2,3}...) // 追加一个切片, 切片需要解包
    注意,在容量不足的情况下,append()操作会导致重新分配内幕才能,可能导致巨大的内存分配和复制数据的代价。及时容量足够,依然需要用append()函数的返回值来更新切片本身,因为新切片的长度已经发生了变化。 
删除切片元素

    根据要删除的元素的位置,有从开头位置删除、从中间位置删除和从尾部删除3种情况,其中删除切片尾部的元素最快:
a = []int{1, 2, 3}
a = a[:len(a) - 1] //删除尾部1个元素
a = a[:len(a) - n] //删除尾部n个元素
 删除开头的元素可以直接移动数据指针:
a = []int{1, 2, 3}
a = a[1:] //删除开头1个元素
a = a[N:] //删除开头N个元素

多维切片
 
同数组一样,切片也可以有多个维度。
package main

import (
"fmt"
)


func main() {
pls := [][]string {
{"C", "C++"},
{"JavaScript"},
{"Go", "Rust"},
}
for _, v1 := range pls {
for _, v2 := range v1 {
fmt.Printf("%s ", v2)
}
fmt.Printf("\n")
}
}
数据结果:
C C++  
JavaScript
Go Rust

更多参考文档:
https://www.jianshu.com/p/4eb3e490abe1
https://blog.51cto.com/14073476/2478276
https://cloud.tencent.com/developer/article/1354294
 

#2020学习打卡##Go语言高级编程# 数组初始化时,索引初始化怎么理解?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 1963 次浏览 • 2020-06-07 15:34 • 来自相关话题

#2020学习打卡##C程序设计语言# C语言中的随机数函数解析

C语言zkbhj 发表了文章 • 0 个评论 • 907 次浏览 • 2020-05-25 12:02 • 来自相关话题

在计算机中并没有一个真正的随机数发生器,但是可以做到使产生的数字重复率很低,这样看起来好象是真正的随机数,实现这一功能的程序叫伪随机数发生器。
 
有关如何产生随机数的理论有许多,如果要详细地讨论,需要厚厚的一本书的篇幅。不管用什么方法实现随机数发生器,都必须给它提供一个名为“种子”的初始值。而且这个值最好是随机的,或者至少这个值是伪随机的。“种子”的值通常是用快速计数寄存器或移位寄存器来生成的。
 在实际编程中,我们经常会用到随机数这个概念,其实也是一个伪随机数,实际上并不是一个真正的随机数,但是也足够我们使用了。在C语言中,编写一些关于游戏之类的程序时就需要用到随机数了。同时C语言也提供了一个标准库里面一个函数来产生随机数,而对于随机数的产生是根据种子(根据一个数值按照某种公式计算的)来变化的,种子 与随机数之间符合正态分布(高斯分布)。





 
生成随机数

在C语言中,我们一般使用 <stdlib.h> 头文件中的 rand() 函数来生成随机数,它的用法为:int rand (void);【void是指不需要传递参数】
rand() 会随机生成一个位于 0 ~ RAND_MAX 之间的整数。而对RAND_MAX 是 <stdlib.h> 头文件中的一个宏,它用来指明 rand() 所能返回的随机数的最大值。C语言标准并没有规定 RAND_MAX 的具体数值,只是规定它的值至少为 32767。/**
* 第35堂课示例:随机数
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>

int main()
{

int rands;

rands = rand();

printf("rand number is %d\n", rands);

printf("rand number2 is %d\n", rand());

return 0;
}但是这个随机数一旦编译之后就固定了,并不能满足我们的实际需求,前面提到了只是一个伪随机数,我们需要对产生随机数的种子进行不断的重播,从而达到我们实际需求的随机数效果。我们可以通过 srand() 函数来重新“播种”,这样种子就会发生改变。
 
srand() 的用法为:void srand (unsigned int seed);
它需要一个 unsigned int 类型的参数。在实际开发中,我们可以用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同,通常我们采用 <time.h> 头文件中的 time() 函数即可得到当前的时间【精准到秒】srand((unsigned)time(NULL));/**
* 第35堂课示例:随机数
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{

int rands;

srand((unsigned)time(NULL));

rands = rand();

printf("rand number is %d\n", rands);

printf("rand number2 is %d\n", rand());

return 0;
}小提示:根据种子与随机数的符合高斯分布的关系可知,生成的随机数是逐渐增大或者逐渐减小!
 
生成一定范围随机数

在实际编程开发中,实际需求往往是一定范围内的随机数,对于产生一定范围的随机数,就需要使用一定的技巧了,常用的方法是取模运算,再加上一个加法运算:int a = rand() % 10; //产生0~9的随机数,注意10会被整除
如果要规定上下限:int a = rand() % 51 + 100; //产生100~150的随机数
分析:取模即取余,rand()%51+13,看成两部分:rand()%51是产生 0~50 的随机数,后面+100保证 a 最小只能是 100,最大就是 50+100=150。/**
* 第35堂课示例:有区间的随机数
* 例如:100~150之间的数字
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{

int rands;

srand((unsigned)time(NULL));

rands = rand() % 51 + 100;

printf("rand number is %d\n", rands);

return 0;
}




 
根据种子与随机数的符合高斯分布的关系可知,生成的随机数是逐渐增大或者逐渐减小。 查看全部
在计算机中并没有一个真正的随机数发生器,但是可以做到使产生的数字重复率很低,这样看起来好象是真正的随机数,实现这一功能的程序叫伪随机数发生器
 
有关如何产生随机数的理论有许多,如果要详细地讨论,需要厚厚的一本书的篇幅。不管用什么方法实现随机数发生器,都必须给它提供一个名为“种子”的初始值。而且这个值最好是随机的,或者至少这个值是伪随机的。“种子”的值通常是用快速计数寄存器或移位寄存器来生成的。
 在实际编程中,我们经常会用到随机数这个概念,其实也是一个伪随机数,实际上并不是一个真正的随机数,但是也足够我们使用了。在C语言中,编写一些关于游戏之类的程序时就需要用到随机数了。同时C语言也提供了一个标准库里面一个函数来产生随机数,而对于随机数的产生是根据种子(根据一个数值按照某种公式计算的)来变化的,种子 与随机数之间符合正态分布(高斯分布)。

565EG.jpg

 
生成随机数

在C语言中,我们一般使用 <stdlib.h> 头文件中的 rand() 函数来生成随机数,它的用法为:
int rand (void);【void是指不需要传递参数】

rand() 会随机生成一个位于 0 ~ RAND_MAX 之间的整数。而对RAND_MAX 是 <stdlib.h> 头文件中的一个宏,它用来指明 rand() 所能返回的随机数的最大值。C语言标准并没有规定 RAND_MAX 的具体数值,只是规定它的值至少为 32767。
/**
* 第35堂课示例:随机数
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>

int main()
{

int rands;

rands = rand();

printf("rand number is %d\n", rands);

printf("rand number2 is %d\n", rand());

return 0;
}
但是这个随机数一旦编译之后就固定了,并不能满足我们的实际需求,前面提到了只是一个伪随机数,我们需要对产生随机数的种子进行不断的重播,从而达到我们实际需求的随机数效果。我们可以通过 srand() 函数来重新“播种”,这样种子就会发生改变。
 
srand() 的用法为:
void srand (unsigned int seed);

它需要一个 unsigned int 类型的参数。在实际开发中,我们可以用时间作为参数,只要每次播种的时间不同,那么生成的种子就不同,最终的随机数也就不同,通常我们采用 <time.h> 头文件中的 time() 函数即可得到当前的时间【精准到秒】srand((unsigned)time(NULL));
/**
* 第35堂课示例:随机数
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{

int rands;

srand((unsigned)time(NULL));

rands = rand();

printf("rand number is %d\n", rands);

printf("rand number2 is %d\n", rand());

return 0;
}
小提示:根据种子与随机数的符合高斯分布的关系可知,生成的随机数是逐渐增大或者逐渐减小!
 
生成一定范围随机数

在实际编程开发中,实际需求往往是一定范围内的随机数,对于产生一定范围的随机数,就需要使用一定的技巧了,常用的方法是取模运算,再加上一个加法运算:
int a = rand() % 10; //产生0~9的随机数,注意10会被整除

如果要规定上下限:
int a = rand() % 51 + 100; //产生100~150的随机数

分析:取模即取余,rand()%51+13,看成两部分:rand()%51是产生 0~50 的随机数,后面+100保证 a 最小只能是 100,最大就是 50+100=150。
/**
* 第35堂课示例:有区间的随机数
* 例如:100~150之间的数字
* 郑凯
* 2020年5月25日
* */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{

int rands;

srand((unsigned)time(NULL));

rands = rand() % 51 + 100;

printf("rand number is %d\n", rands);

return 0;
}

QQ截图20200525115709.jpg

 
根据种子与随机数的符合高斯分布的关系可知,生成的随机数是逐渐增大或者逐渐减小。

如何在go项目中使用go mod?

回复

GoLangzkbhj 回复了问题 • 1 人关注 • 1 个回复 • 3051 次浏览 • 2020-05-22 17:09 • 来自相关话题