当前位置: 首页 > news >正文

哪家做网站最便宜临沂做网站的公司

哪家做网站最便宜,临沂做网站的公司,旅游网站建设的技术可行性,网站怎么做关键词流量1. 数组#xff08;array#xff09;#xff08;OK#xff09; 数组数组的概念数组是具有固定长度且拥有零个或多个相同数据类型元素的序列 i. 元素的数据类型相同 ii. 长度固定的序列 iii. 零个或多个元素的序列 与 slice 对比 由于数组的长度固定#xff0c;所以在 G…1. 数组arrayOK 数组数组的概念数组是具有固定长度且拥有零个或多个相同数据类型元素的序列 i.  元素的数据类型相同 ii. 长度固定的序列  iii. 零个或多个元素的序列 与 slice 对比 由于数组的长度固定所以在 Go 里面很少直接使用 slice 的长度可以增长和缩短故 slice 使用得更多 定义数组语法 var 数组名 [数组长度]元素类型 var arrName [arrLen]eType 访问单个元素数组中的每个元素是通过索引下标来访问的索引从 0 到数组长度减 1获取数组长度Go 内置的函数 len 可以返回数组中的元素个数代码示例 var  a  [3]int                         // 定义长度为 3 的 int 型数组 fmt.Println(a[0])                    // 输出第一个元素 fmt.Println(a[len(a) - 1])        // 输出最后一个元素 // 输出索引和元素 for i, v : range a {     fmt.Print(%d %d\n, i, v) } // 仅输出元素丢弃索引 for _, v : range a {     fmt.Printf(%d\n, v) } 数组的零值 默认情况下一个新数组中的元素初始值为元素类型的零值 数字的零值为 0字符串的零值为 布尔的零值为 false 声明并初始化 可以使用数组字面量即根据一组值来初始化一个数组 若元素个数与数组长度不一致缺省的元素为元素类型的零值 var  q [3]int [3]int{123} var r [3]int [3]int{12} fmt.Println(r[2])    // 0 确定数组长度 在声明数组的同时给出数组长度 var  a  [3]int 用数组字面量初始化如果省略号 ... 出现在数组长度的位置 那么数组的长度由初始化数组的字面量中的元素个数决定 注意省略号 ... 只能出现在数组字面量的数组长度的位置 q : [...]int{123} var p [...]int [3]int{123}  // 编译错误 数组长度示例 q : [...]int{123} fmt.Printf(%T\n, q)    // [3]int        使用 %T 来打印对象的类型 数组长度特别说明 i. 数组的长度是数组类型的一部分即长度是数组的固有属性 [3]int 和 [4]int 是两种不同的数组类型 ii. 数组的长度必须是常量表达式 这个常量表达式的值在程序编译时就必须确定 数组类型示例 q  :  [3]int{123} q [4]int{1234}    // 编译错误不可以将 [4]int 赋值给 [3]int 数组字面量的 默认值 (索引-值 初始化) 数组 、slice 、map 、结构体 的字面语法都是相似的 上面的数组例子是按顺序给出一组值 也可以向下面这样给出一组元素元素同时具有索引和值 // 声明类型别名 type  Currency  int // 定义一组常量 const (     USD  Currency  iota     EUR     GBP     RMB ) // 声明数组用数组字面量初始化 symbol  :  [...]string{USD $EUR €GBP £RMB ¥} fmt.Println(RMBsymbol[RMB])    //  3  ¥ 在这种情况下元素索引-值可以按照任意顺序出现索引有时候还能省略 没有指定值的索引位置的元素其值为数组元素类型的零值 // 下标为 99 的元素值为 -1 则前 99 个元素均为 0 r  :  [...]int{99-1}    数组的比较 如果一个数组的元素类型是可比较的那么这个数组也是可比较的 可以直接使用 操作符来比较两个同类型的数组比较的结果是两边元素的值是否完全相同 使用 ! 来比较两个数组是否不一样 不同类型长度不同 或 元素类型不同的数组不能比较否则会编译报错 代码示例 a  :  [2]int{12} b  : [...]int{12} c  : [2]int{13} fmt.Println(a b a c b c)    //    true  false  false d  :  [3]int{12}     // d[2] 0 fmt.Println(a  d)    // 编译错误 无法比较 [2]int [3]int 数组比较 真实示例 举一个更有意义的例子crypto/sha256 包里面的函数 Sum256 用来为存储在任意字节 slice 中的消息使用 SHA256 加密散列算法生成一个摘要。摘要信息为 256 位即 [32]byte 。如果两个摘要信息相同那么很有可能这两条原始消息就是相同的如果这两个摘要信息不同那么这两条原始消息就是不同的。 下面的程序输出并比较了 x 和 X 的 SHA256 散列值 import crypto/sha256 func main() {     c1  :  sha256.Sum256([]byte(x))     c2  :  sha256.Sum256([]byte(X))     fmt.Printf(%x\n%x\n%t\n%T\n, c1, c2, c1 c2, c1) } 这两个原始消息仅有一位bit之差但是它们生成的摘要消息有将近一半的位不同 注意上面的格式化字符串 %x 表示将一个数组或者 slice 里面的字节按照十六进制的方式输出%t  表示输出一个布尔值%T  表示输出一个值的类型 Go 函数默认 传值调用 当调用一个函数的时候每个传入的参数都会创建一个副本然后赋值给对应的函数变量形参所以函数接受的是一个副本而不是原始的参数 函数使用传值调用的方式接收大的数组会变得很低效 并且在函数内部对数组的任何修改都仅仅影响副本而不是原始数组 这种情况下Go 函数把数组和其他的类型都看成 值传递在其他语言中数组都是隐式第使用引用传递 传递指针给函数 直接修改原始数据 当然也可以显式地传递一个 数组的指针 给函数这样在函数内部对数组的任何修改都会反映到原始数组上数组清零程序 下面的程序演示如何将一个数组 [32]byte 的元素清零 func zero(ptr *[32]byte) {     for i : range ptr {         ptr[i] 0     } } 数组字面量 [32]byte{} 可以生成一个拥有 32 个字节元素的数组 数组中每个元素的值都是字节类型的零值即 0 另一个版本的数组清零程序 func zero(ptr *[32]byte) {     *ptr [32]byte{} } 使用数组指针 i.  使用数组指针是高效的 ii. 允许被调函数修改调用方数组中的元素 数组很少被使用 因为数组长度是固定的所以数组本身是不可变的 例如上面的 zero 函数不能接收一个 [16]byte 这样的数组指针 也无法为数组添加或删除元素 由于数组的长度不可改变除了在特殊的情况下很少使用数组 2. 切片slice sliceslice 基本概念slice 表示一个拥有相同类型元素的 可变长度 的序列 slice 通常写成  [ ]T 其中元素的类型是 T 看上去像没有长度的数组类型 底层数组数组和 slice 是紧密关联的 slice 是一种轻量级的数据结构可以用来访问数组的部分或者全部元素 而这个数组称为 slice 的 底层数组 slice 的属性slice 有 3 个属性 指针 、长度 、容量 指针 指向数组的第一个可以从 slice 中访问的元素 注意 这个元素不一定是数组的第一个元素 长度 slice 中的元素个数长度不能超过 slice 的容量容量 通常是从 slice 的起始元素到底层数组的最后一个元素之间的元素个数lenGo 的内置函数 len 用来返回 slice 的长度capGo 的内置函数 cap 用来返回 slice 的容量 一个底层数组可以对应多个 slice 这些 slice 可以引用数组的任何位置 这些 slice 彼此之间的元素还可以重叠 下图展示了月份名称的字符串数组和两个元素存在重叠的 slice 数组声明如下 // 索引式的数组字面量 months : [...]string{1January /* ... */ 12December} 所以 January 就是 months[1] December 是 months[12] 一般来讲数组中索引 0 的位置存放数组的第一个元素但由于月份是从 1 开始的 因此我们可以不设置索引为 0 的元素这样 months[0] 的值就为 创建 sliceslice 操作符 s[ i : j ] 其中 创建了一个新的 slice这个新的 slice 引用了序列  中从  到  索引位置的所有元素这里的  既可以是数组或者指向数组的指针也可以是其他的 slice 起始/结束 索引位置缺省 新 slice 的元素个数是  个 如果表达式 s[ i : j ] 中省略了 i 则新的 slice 的起始索引位置为 0 即 i 0 s[ : j ] 相当于 s[ 0 : j ]  如果表达式 s[ i : j ] 中省略了 j 则新的 slice 的结束索引位置为 len(s) - 1 即 j len(s) s[ i : ] 相当于 s[ i : len(s)  ] 说明 因此slice months[1:13] 引用了所有的有效月份 同样的写法可以是 months[1:] slice months[ : ] 引用了整个数组接下来我们定义元素重叠的 slice 分别用来表示第二季度的月份北半球的夏季月份 Q2  :  months[ 4 : 7 ]                // 一年中的第二季度是4 、5 、6 这三个月 summer  :  months[ 6 : 9 ]        // 北半球的夏季是 6 、7 、8 这三个月 fmt.Println(Q2)            // [  April   May   June  ] fmt.Println(summer)    // [   June   July   August   ] 元素 June 同时被包含在两个 slice 中 用下面的代码来输出两个 slice 中的共同元素虽然效率不高 for _s : range summer {     for _q : range Q2 {         if s q {             fmt.Printf(%s appears in both\ns)         }     } } 越界分类如果 slice 的引用超过了被引用对象的容量即 cap(s) 那么会导致程序宕机如果 slice 的引用超过了被引用对象的长度即 len(s) 那么最终 slice 会比原先的 slice 长代码示例 fmt.Println(summer[ : 20 ])               // 宕机 超过了被引用对象的边界 endlessSummer  :  summer[ : 5]    // 在 slice 容量范围内扩展了 slice fmt.Println(endlessSummer)             // [June July August Septmber October] 子串与切片 另外注意 i. 求字符串string子串操作 ii. 对字节 slice [ ]byte 做 slice 操作 这两者的相似性 相同点 它们都写作 x[m:n] 都返回原始字节的一个子序列 同时两者的底层引用方式也是相同的所以两个操作都消耗常量时间 不同点 如果 x 是字符串那么 x[m:n] 返回的是一个字符串 如果 x 是字节 slice 那么 x[m:n] 返回的是一个字节 slice 因为 slice 包含了指向数组元素的指针所以将一个 slice 传递给函数的时候 可以在函数内部修改底层数组的元素 也就是说创建一个数组的 slice 等于为数组创建了一个别名 下面的函数 reverse 就地反转了整型 slice 中的元素它适用于任意长度的整型 slice // 就地反转一个整型 slice 中的元素 func reverse( s [ ]int ) {     for i j  :  0 len(s) - 1 i j i j i 1 j - 1 {  // i j--         s[ i ] s[ j ] s[ j ] s[ i ]     } } // 这里反转整个数组 a a  :  [...]int{012345} reverse( a[ : ] ) fmt.Println( a )  //  [ 5 4 3 2 1 0 ] 将一个 slice 左移 n 个元素的简单方法 连续调用 reverse 函数三次 第一次反转前 n 个元素第二次反转剩下的元素最后对整个 slice 再做一次反转 如果将 slice 右移 n 个元素那么先做上面的第三次调用 s  :  [ ]int{012345} // 左移两个元素 reverse( s[ : 2 ] ) reverse( s[ 2 : ] ) reverse( s ) fmt.Println( s )    //  [  2 3 4 5 0 1 ] 注意初始化 slice  s 的表达式和初始化数组 a 的表达式两者的区别 slice 字面量 、数组字面量 两者很相似 都是用逗号分隔并用花括号 { } 括起来的一个元素序列 但是 slice 没有指定长度 // 声明并初始化数组 a  :  [...]int{012345} // 声明并初始化 slice s  :  [ ]int{012345} 这种隐式区别的结果分别是创建具有固定长度的数组创建指向数组的 slice和数组一样slice 可以按照顺序指定元素 也可以通过索引指定元素或两者结合 和数组不同的是slice 无法做比较 因此不能用 来测试两个 slice 是否拥有相同的元素个数以及对应值都相等 自行比较 slice 标准库里面提供了高度优化的函数 bytes.Equal 来比较两个字节 slice [ ]byte 但是对于其他类型的 slice 我们必须自己写函数来比较两个 slice func equal( xy [ ]string ) bool {     if len(x) ! len(y) {  // 长度不同则不同         return false     }     for i : range x {         if x[ i ] ! y[ i ] {             return false         }     }     return true } 这种深度比较看上去很简单并且运行的时候并不比字符串数组使用 做比较多耗费时间那为什么 slice 的比较不可以直接使用 操作符做比较呢 这里有两个原因 原因一 slice 是对底层数组的引用 和数组元素不同slice 的元素是 非直接的 有可能 slice 可以包含它自身 虽然有办法处理这种特殊的情况但是没有一种方法是简单 、高效 、直观的 原因二 由于 slice 的元素不是直接的如果底层数组元素改变则同一个 slice 在不同的时间会拥有不同的元素 由于散列表例如 Go 中的 map 类型 仅对元素的键做浅拷贝这就要求散列表里面的键在散列表的整个生命周期内必须保持不变 因为 slice 需要深度比较所以就不能用 slice 作为 map 的键 对于引用类型例如指针和通道操作符 检查的是 引用相等性 即它们是否指向同一个对象 如果有一个相似的 slice 相等性比较功能它或许会比较有用也能解决 slice 作为 map 键的问题但是如果操作符 对 slice 和数组的行为不一致会带来困扰 所以最安全的方法就是不允许直接比较 slice 比较操作slice 唯一允许的比较操作是和 nil 作比较if summer nil  { /* ... */ }slice 零值slice 类型的零值是 nil 值为 nil 的 sice 没有对应的底层数组值为 nil 的 slice 其长度为 0 其容量为 0 也有值不是 nil 的 slice 其长度和容量都是零 例如 [ ]int{ } 或 make( [ ]int 3 )[ 3 : ]   对于任何类型如果它们的值可以是 nil 那么这个类型的 nil 值可以使用一种转换表达式例如 [ ]int(nil) var  s  [ ]int         // len(s) 0 s nil s    nil               // len(s) 0 s nil s    [ ]int( nil )    // len(s) 0 s nil s    [ ]int{ }         // len(s) 0 s ! nil 空 slice  所以如果想检查一个 slice 是否为空那么使用 len(s) 0 而不是 s nil 因为 s ! nil 的情况下slice 也有可能为空 除了可以和 nil 作比较之外值为 nil 的 slice 其行为与其他长度为 0 的 slice 一样例如reverse 函数调用 reverse( nil ) 也是安全的除非文档上面说明了与此相反否则无论值是否为 nil Go 的函数都应该以相同的方式对待所有长度为 0 的 slice  内置make函数创建slice 内置函数 make 可以创建一个具有指定 元素类型 、长度 、容量 的 slice 其中容量参数可以省略则 slice 的长度和容量相等 make( [ ]Tlen ) make( [ ]Tlencap )  //  和 make( [ ]Tcap )[ : len ] 功能相同 深入研究下其实 make 创建了一个无名数组并返回了它的一个 slice 这个数组只能通过这个 slice 进行访问 // 返回的 slice 引用了整个数组 make( [ ]Tlen ) // 只引用了数组的前 len 个元素但是 slice 的容量是数组的长度预留了空间 make( [ ]Tlencap )  2.1 append 函数 append 函数 2.2 slice 就地修改 3. 字典map 字典map创建 map方式一 内置函数 make 可以用来创建一个空map students : make(map[int]string)    // 学号到名字的映射 students[Jake] 31  // 添加元素 students[Mike] 54  // 添加元素 方式二 使用 map 的字面量来新建一个带初始化键值对的字典 students : map[int]string {         1 : Alice,         2 : Bob,         3 : Charlie,         4 : David,         5 : Eva,         6 : Frank, } 方式三 直接创建一个新的空 map students : map[string]int{} students[Jake] 31  // 添加元素 students[Mike] 54  // 添加元素 添加元素 students[Jake] 31  // 添加元素 students[Mike] 54  // 添加元素 如上面的操作所示 如果键不存在则为新增元素 如果键存在则为修改元素 访问 map 元素访问元素 使用 key类似数组下标来访问 map 中对应的 value students[Mike] 55 fmt.Println(students[Mike]) map 使用给定的键来查找访问元素如果对应的元素键不存在则使用该元素键值对值为零值即 value 初始化为默认值 话句话说使用不存在的元素通过 key 访问相当于新增元素value 为默认值 示例下面的代码可以正常工作尽管 Bob 还不是 map 的键此时使用 teachers[Bob] 的值为 0 teachers[Bob] teachers[Bob] 1 fmt.Println(teachers[Bob])  //   1 复合赋值运算如 x y 和 x对 map 中的元素同样适用 teachers[Bob] 1  或  teachers[Bob] 移除元素键值对 delete 移除元素 可以使用内置函数 delete 根据键从 map 中移除一个元素键值对 即使键不在 map 中delete 操作也是安全的 语法 delete(map_namekey) delete(studentsMike)  // 移除元素 students[Mike] delete(studentsCarl)  // 键 Carl 不存在但这么操作是允许的 map 中的元素不是变量value 不是变量 但是 map 元素不是一个变量不可以获取 value 的地址 错误操作如下 _ teachers[Bob]    // 编译错误无法获取 map 元素的地址 无法获取 map 元素地址的第一个原因 map 的增长可能会导致已有元素被重新散列到新的存储位置这样的话之前获取的地址可能已经存在某个变量中与当前地址不一致前面的地址无效 遍历 map 使用迭代 for 循环遍历 map 可以使用 for 循环结合 range 关键字迭代 for 循环来遍历 map 中所有的键和对应的值 就像遍历 slice 一样range 遍历 slice 返回下标和值range 遍历 map 返回键和值 示例 循环语句的每一次迭代会将键赋予 name 将值赋予 age for nameage : range teachers {     fmt.Printf(%s\s age is %d\n nameage) } 注意 map 中元素的迭代顺序是不固定的不同的实现方法会使用不同的散列算法得到不同的元素顺序 实践中我们认为这种顺序是随机的从一个元素开始到后一个元素依次执行 这种设计是有意为之这样可以使得程序在不同的散列算法实现下更健壮 按照键的顺序 有序遍历 map 如果需要按照某种顺序来遍历 map 中的元素必须显式地来给键排序 例如如果键是字符串类型可以使用 sort 包中的 Strings 函数来进行键的排序 import sort var names [ ]string for name : range teachers {       names append(namesname)  // 先把 map 中的 Key 都保存在一个 slice 中 } sort.Strings(names)  // 使保存 key 的 slice 变得有序 for _name : range names {     // 遍历有序的 slice 依次获得 name 再通过 name 访问 map 中的 age      fmt.Printf(%s\s age is %d\nnameteachers[name]) } 优化 因为一开始就知道 slice names 的长度所以可以直接指定一个 slice 的长度这样更高效 语法 make(容器类型初始长度容器容量)                make(typelencap) 下面的语句创建了一个初始元素为空但容量足以容纳 map 中所有键的 slice names : make([ ]string 0 len(teachers)) 第一个循环中我们只需要 map teachers 的所有键所以忽略了循环中的第二个变量 第二个循环中我们需要使用 slice names 中的元素值所以使用空白符 _ 来忽略第一个变量即元素索引 range 可以返回一个值也可以返回两个值 返回一个值遍历序列则只返回下标遍历字典则只返回键 返回两个值遍历序列同时返回下标和元素遍历字典同时返回键和值 只需要第一个值则只返回一个值只需要第二个值则用 _ 来接收第一个值     m : map[int]int {         1 : 100,         2 : 200,         3 : 300,     }     s : make([]int, 0, len(m))     for v : range m {         s append(s, v)         fmt.Printf(v is %d\n, v)     }     for k : range s {         fmt.Printf(vkis %d\n, k)     }     for _, v : range s {         fmt.Printf(%d : %d\n, v, m[v])     } map 是引用类型 map 类型的零值是 nil 也就是说没有引用任何散列表 // 只是定义了 map 变量未分配内存map 是散列表的引用 // 未绑定具体的散列表所以结果是编译通过运行失败 var name carol var m map[string]int m[name] 21    //  宕机为零值 map 中的项赋值编译通过运行失败 fmt.Println(m nil)    //   true // {} 即为一个空的散列表绑定到变量 m 上 // 结果是编译通过运行成功 var name carol m : map[string]int{}    // 初始化 m[name] 21 fmt.Println(m nil)    //   false // make 函数根据类型 map[string]int 分配了一个空的散列表绑定到变量 m 上 // 结果是编译通过运行成功 var name carol m : make(map[string]int)    //  初始化 m[name] 21 fmt.Println(m nil)    //   false fmt.Printf(%s age is %d\n, name, m[name]) carols age is 21 大多数的 map 操作都可以安全地在 map 的零值 nil 上执行包括查找元素删除元素获取 map 元素个数 len 执行 range 循环等等因为这和在空 map 上的行为一致 但是向零值 map 中设置元素会导致错误 var name carol var m map[string]int    // 零值 map 未初始化 m[name] 21    //  宕机为零值 map 中的项赋值编译通过运行失败 fmt.Println(m nil)    //   true 设置元素之前必须初始化 map 访问 map 注意点 通过下标键的方式访问 map 中的元素总是会有值 如果键在 map 中则获得键对应的值 如果键不在 map 中则获得 map 值类型的零值 判断 map 中是否存在某个元素判断元素存在 有时候需要知道一个元素是否在 map 中 例如如果元素类型是数值类型需要辨别一个不存在的元素或者恰好这个元素的值是 0 ageok : teachers[Bob] if !ok {     /* Bob 不是字典中的键age 0 */ } 合并成一条语句 if ageok : teachers[Bob]!ok {     /*  ...  */ } 通过这种下标方式访问 map 中的元素输出两个值第二个值是一个布尔值用来报告该元素是否存在这个布尔值一般叫作 ok 尤其是它ok立即用在 if 条件判断中的时候 比较 map map 比较操作 和 slice 一样两个map 不可比较唯一合法的比较就是map 变量与 nil 做比较 为了判断两个 map 是否拥有相同的键和值必须写一个循环 func equal(xy map[string]bool)  bool  {         if len(x) ! len(y) {                 return false    // 两个 map 长度不等则这两个 map 不相等         }         for kxv : range x {    // 某一个键 k 和键对应的值 xv                 // 键 k 对应的值 yv 是否存在yv 存在的情况下yv 与 xv 是否相等                 // yv 不存在或 yv 存在但与 xv 不相等                 if yvok : y[k]!ok || yv ! xv {  // 错误写法 xv ! y[k]                         return false                 }         }         return true } 注意如何使用 !ok 来区分 元素不存在 和 元素存在但值为零 的情况 如果简单写成了 xv ! y[k] 那么下面的调用将错误地报告两个 map 相等 // 如果 equal 函数写法错误结果为 True equal(map[string]int{A : 0}map[string]int{B : 42}) 使用 map 构造集合类型SetGo 没有提供集合类型既然 map 的键都是唯一的就可以用 map 来实现这个功能 示例 为了模拟这个功能程序 dedup 读取一系列的行并且只输出每个不同行一次 程序 dedup 使用 map 的键来存储这些已经出现过的行来确保接下来出现的相同行不会输出 func main() {         seen : make(map[string]bool)    // 字符串集合         input : bufio.NewScanner(os.Stdin)         for input.Scan() {                 line : input.Text()                 if !seen[line] {                         seen[line] true                         fmt.Println(line)                 }         }         if err : input.Err()err ! nil {                 fmt.Fprintf(os.Stderrdedup: %v\nerr)                 os.Exit(1)         } } Go 程序员通常把这种使用 map 的方式描述成字符串集合 但是请注意并不是所有的 map[string]bool 都是简单的集合有一些 map 的值会同时包含 true 和 false 的情况 4. 结构体struct 结构体基础结构体概念 结构体是将零个或者多个任意类型的命名变量组合在一起的聚合数据类型 每个变量都叫做结构体的成员现实例子在数据处理领域结构体使用的经典实例是员工信息记录记录中有唯一 ID 、姓名 、地址 、出生日期 、职位 、薪水 、直属领导等信息所有的这些员工信息成员都作为一个整体组合在一个结构体中 结构体 整体操作 (1). 可以复制一个结构体变量 (2). 将结构体变量传递给函数  (3). 结构体变量作为函数的返回值  (4). 将结构体变量存储到数组中等等 结构体声明 示例 下面的语句定义了一个叫 Employee 的结构体和一个结构体变量 dilbert type Employee struct {     ID                 int     Name           string     Address       string     DoB             time.Time     Position       string     Salary          int     ManagerID  int } var dilbert Employee 访问成员 结构体对象的每一个成员都通过句点 . 方式进行访问 fmt.Println(dilbert.Name) 结构体对象是一个变量其所有成员也都是变量因此可以给结构体的成员赋值 dilbert.Salary - 5000    // 代码量减少降薪 获取成员变量的地址然后通过指针来访问 position : dilbert.Position *position Senior *position    // 工作外包给 Elbonia 所以升职 句号 . 同样可以用在结构体指针上 var employeeOfTheMonth *Employee dilbert employeeOfTheMonth.Position   (proactive team player) 后面一条语句等价于 (*employeeOfTheMonth).Position   (proactive team player) 函数 EmployeeID 通过给定的参数 ID 返回一个指向 Employee 结构体的指针 可以用句号 . 来访问其结构体指针成员变量 func EmployeeByID(id int) *Employee { /* ... */ } fmt.Println(EmployeeByID(dilbert.ManagerID).Position) id : dilbert.ID EmployeeByID(id).Salary 0 最后一条语句更新了函数 EmplyeeByID() 返回的指针指向的结构体 Employee 如果函数 EmployeeByID() 的返回值类型变成了 Employee 而不是 *Employee 那么代码将无法通过编译因为赋值表达式的左侧无法识别出一个变量 结构体的成员变量通常一行写一个变量名称在类型的前面 相同类型的连续成员变量可以写在一行上 type Employee struct {     ID                            int     NameAddress     string     DoB                         time.Time     Position                   string     Salary                      int     ManagerID               int } 成员变量声明的顺序对于结构体同一性是否为同一个类型很重要 如果将同为字符串类型的 Position 和 Name、Address 组合在一起或者互换了 Name 和 Address 的顺序那么就是定义了一个不同的结构体类型 一般来说我们只会组合相关的成员变量 如果一个结构体的成员变量名称首字母大写那么这个变量是可导出的public 这个是 Go 最主要的访问控制机制 一个结构体可以同时包含可导出首字母大写public和不可导出首字母小写private的成员变量 因为在结构体类型中通常一个成员变量占据一行所以结构体的定义比较长 虽然可以在每次需要它结构体完整定义的时候写出整个结构体类型定义即 匿名结构体类型 但是重复完全没必要所以通常我们会定义命名结构体类型比如 Employee 命名结构体类型 S 不可以定义一个拥有相同结构体类型 S 的成员变量也就是一个聚合类型不可以包含它自己同样的限制对数组也适用 但是 S 中可以定义一个 S 的指针类型即 *S  这样就可以创建一些递归数据结构比如链表和树 下面的代码给出了一个利用二叉树来实现插入排序的例子 type tree struct {     value         int     leftright  *tree } // 就地排序 func Sort(values [ ]int) {     var root  *tree     for _v : range values {         root add(rootv)     }     appendValues(values[:0]root) } // appendValues 将元素按照顺序追加到 values 里面然后返回结果 slice func appendValues(values [ ]intt  *tree) [ ]int {     if t ! nil {         values appendValues(valuest.left)         values append(valuest.value)         values appendValues(valuest.right)     }     return values } func add(t  *treevalue int) *tree {     if t nil {         // 等价于返回 tree{value : value}         t new(tree)         t.value value         return t     }     if value t.value {         t.left add(t.leftvalue)     } else {         t.right add(t.rightvalue)     } } 结构体的零值由结构体成员的零值组成 通常情况下我们希望零值是一个默认的、自然的、合理的值 例如在 bytes.Buffer 中结构体的初始值就是一个可以直接使用的空缓存 有时候这种合理的初始值实现简单但是有时候也需要类型的设计者花费时间来进行设计 没有任何成员变量的结构体称为 空结构体 写做 struct{ }空结构体没有长度也不携带任何信息但有时候会很有用 有一些 Go 程序员用空结构体来替代被当作集合使用的 map 中的布尔值来强调只有 Key 是有用的不需要 value 但由于这种方式节约内存很少且语法复杂所以一般尽量避免这么用 seen : make(map[string]struct{ })  // 字符串集合 // ... if _ok : seen[s]!ok {     seen[s] struct{ }     // ...首次出现 s... } 4.1 结构体字面量 结构体类型的值可以通过 结构体字面量 来设置即通过设置结构体的成员变量来设置 type Point struct{ X Y  int } p  :  Point{ 1 2 } 结构体字面量格式格式一 按照正确的顺序为每个成员变量指定一个值 type Point struct{ X Y  int } p  :  Point{ 1 2 }    //  1 是 X 2 是 Y 缺点 这会给开发和阅读代码的人增加负担因为他们必须记住每个成员变量的顺序 这也使得未来结构体成员变量扩充或者重新排列的时候代码维护性变差 应用 所以这种结构体字面量格式一般用在定义结构体类型的包中或者一些有明显的成员变量顺序约定的小型结构体中 比如 image.Point{xy} 或 color.RGBA{redgreenbluealpha} 格式二 用得更多的是第二种格式通过指定部分或者全部成员变量的名称和值来初始化结构体变量 anim  :  gif.GIF{LoopCount : nframes} 在这种初始化方式中如果某个成员没有指定初始值那么该成员的值就是该成员类型的零值 因为指定了成员变量的名字所以它们的顺序是无所谓的 注意 两种初始化方式不可以混合使用 不可以使用第一种初始化方式来绕过规则不可导出变量无法在其他包中使用 package p type T struct{ a b  int }    // a 和 b 都是不可导出的 package q import p var _ p.T{ a : 1 b : 2 }    // 编译错误无法引用 a 、b小写已提示不可用 var _ p.T{ 1 2 }              // 编译错误无法引用 a 、b小写未提示也不可用 虽然上面的最后一行代码没有显式地提到不可导出变量但是它们被隐式地引用了所以这也是不允许的 结构体类型的值可以作为参数传递给函数或者作为函数的返回值 下面的函数将 Point 缩放了一个比率 func Scale(p Point factor int) Point {     return Point{p.X * factor p.Y * factor} } fmt.Println(Scale(Point{12}5))    //  {510} 处于效率的考虑大型的结构体通常都使用结构体指针的方式直接传递给函数或者从函数中返回 传递结构体指针给函数 、函数返回结构体指针 func Bonus(e *Employee percent int) int {     return e.Salary * percent / 100 } 使用结构体指针的方式在函数需要修改结构体内容的时候是必须的 在 Go 这种按值调用的语言中调用的函数接收到的是实参的一个副本并不是实参的引用在函数内修改形参不会影响函数外实参的状态 func AwardAnnualRaise(e *Employee) {     e.Salary e.Salary * 105 / 100 } 由于通常结构体都通过指针的方式使用因此可以使用一种简单的方式来创建 、初始化一个 struct 类型的变量并获取它的地址 pp : Point{ 12 } 等价于 pp : new(Point) *pp Point{ 12 } 但是 Point{ 12 } 这种方式可以直接在一个表达式中使用例如函数调用 4.2 结构体比较 如果结构体的所有成员变量都是可以比较的那么这个结构体就是可比较的 两个结构体可以使用 或者 ! 进行比较 其中 操作符按照顺序比较两个结构体变量的成员变量 所以下面的两个输出语句是等价的 type Point struct{ X Y  int } p : Point{ 12 } q : Point{ 21 } fmt.Println(p.X q.X p.Y q.Y)    // false fmt.Println(p q)                                   // false 和其他可比较的类型一样可比较的结构体类型都可以作为 map 的键类型 type address struct {     hostname  string     port            int } hits : make(map[address]int) hits[address{golang.org 443}] 4.3 结构体嵌套和匿名成员 5. JSON 6. 文本和 HTML 模板
http://www.zqtcl.cn/news/182716/

相关文章:

  • 一个完整的网站建设网站模板去哪要
  • 烤漆 东莞网站建设水果香精东莞网站建设技术支持
  • 国家重大项目建设库网站北京网站开发外包公司
  • 建设免费网站制作二维码的软件app
  • 网站突然没收录了网站建设和运营的成本是多少钱
  • 家政公司网站模板wordpress防cc代码
  • 福田附近做网站公司网站反向链接
  • 南阳网站关键词哪做网站便宜
  • 往网站上做新东西需要什么智库网站建设
  • 网站建站系统程序做网站代理商好赚吗
  • 哪些网站是做食品dedecms转wordpress
  • 广东华迪工程建设监理公司网站网站的优化从哪里进行
  • 国产做的视频网站优秀网站首页
  • 做国际黄金看什么网站网络营销品牌推广公司
  • 手机自助建站平台手机网站开发设计报价单
  • 网站建设标书范本注册了一个域名怎么做网站
  • 行政部建设公司网站东莞市做网站
  • 网站建设开发的流程建设官方网站的主要作用
  • 怎样用模板做网站wordpress柚子皮
  • 长宁区网站建设公司内蒙古赤峰市建设局网站
  • 网站配色怎么对网站的数据库做管理
  • 企业网站效果图wap网站
  • 网站建设优化托管跨境电商怎么做流程
  • 昆明网站建站平台在线阅读网站开发教程
  • pv3d 优秀网站18种最有效推广的方式
  • 一站式网站建设顾问网站建设公司专业网站科技开发
  • python做网站比php好网站开发财务费用
  • 图片上传网站变形的处理北京网站建设有哪些公司
  • 昆山品牌网站建设wordpress 浮动二维码
  • 网站网页建设论文cms免费源码