前言:
647目录如下:
Go语言基础(一)—— 简介、环境配置、HelloWorld
Go语言基础(三)—— 面向对象编程
Go语言基础(四)—— 优质的容错处理
Go语言基础(五)—— 并发编程
Go语言基础(六)—— 测试、反射、Unsafe
Go语言基础(七)—— 架构 & 常见任务
Go语言基础(八)—— 性能调优
本篇将介绍如下内容:
1.如何编写一个 Go 测试程序?
2.变量、常量的定义
3.基本数据类型
4.指针类型
5.运算符
6.条件与循环
7.数组与切片
8.Map
9.字符串
10.函数
(注:可根据数字快速定位本篇内容)
为了接下来更方便的测试我们的Go代码(Go服务), 首先,介绍一下Go语言中如何测试我们的程序。
一、起步:如何编写一个测试程序?
要求:
-
源码文件以
_test结尾:xxx_test.go -
测试方法名以
Test开头:func TestXXX(t *testing.T){...}
实际步骤:
-
创建一个
first_test.go文件。 -
编写如下代码:
package try_test
import "testing"
func TestFirstTry(t *testing.T) {
t.Log("My first try!")
}
复制代码
在终端下运行:
go test first_test.go -v 复制代码
Demo:动手实现一个斐波那切(Fibonacci)数列
1,1,2,3,5,8,13,...
-
创建一个
fibonacci_test.go文件。 -
编写如下代码:
package fibonacci
import (
"testing"
)
func TestFibList(t *testing.T) {
// var a int = 1
// var b int = 1
// var (
// a int = 1
// b int = 1
// )
a := 1
b := 1
for i := 0; i < 5; i++ {
t.Log(" ", b)
tmp := a
a = b
b = tmp + a
}
}
复制代码
- 在终端下运行:
go test fibonacci_test.go -v 复制代码
二、变量、常量的定义
1.变量
变量的定义,有3种方式:
- 第一种,常规逐一声明:
var a int = 1 var b int = 1 复制代码
- 第二种:统一声明:
var ( a int = 1 b int = 1 ) 复制代码
- 第三种:快速声明,编译器会根据所附的值推断出该变量的类型。
a := 1 b := 1 复制代码
2.常量
- 常规赋值:
const abc = 2 复制代码
- 快速设置连续常量值:
// 连续位常量赋值 const ( Monday = iota + 1 Tuesday Wednesday Thursday Friday Saturday Sunday ) 复制代码
- 快速设置连续比特位常量值:
// 比特位常量赋值 const ( Readabele = 1 << iota Writeable Executable ) 复制代码
三、基本数据类型
| 类型 | 语法 |
|---|---|
| 布尔型 | bool |
| 字符型 | string |
| 整型 | int、int8、int16、int32、int64 |
| 无负号整型 | uint、uint8、uint16、uint32、uint64、uintptr |
| 浮点型 | float32、float64 |
| 字符型 | byte(相当于uint8) |
| Unicode或utf-8编码 | rune(相当于int32) |
| 复数型 | complex64、complex128 |
注意:Go语言不支持**“隐式类型转换” ,只支持 “显式类型转换” 。 (甚至连 “别名类型” 与 “原有类型”**之间也不支持隐式类型转换。)
举例:
var a int32 = 1 var b int64 b = a // 错误:隐式类型转换,会报编译错误错误。 b = int64(a) // 正确:显式类型转换,成功。 复制代码
常用典型的预定义值:
| 预定义 | 语法 |
|---|---|
| 最大Int64 | math.MaxInt64 |
| 最小Int64 | math.MinInt64 |
| 最小Int32 | math.MaxInt32 |
| 最小Int32 | math.MinInt32 |
| 最大float64 | math.MaxFloat64 |
| 最大float32 | math.MaxFloat32 |
| ... | ... |
四、指针类型
与其他编程语言的差异:
-
不支持指针运算。
-
string是值类型,其默认的初始化值为 空字符串 ,而不是nil。
举例:
func TestPoint(t *testing.T) {
a := 1
aPoint := &a
// aPoint = aPoint + 1 // 错误:Go不支持指针运算。
t.Log(a, aPoint)
t.Logf("%T %T", a, aPoint)
}
func TestString(t *testing.T) {
var s string
t.Log("字符串:" + s + "?")
t.Log(len(s))
if s == "" { // 判断字符串有无初始化不能判nil,因为string类型的初始化默认是空字符串
t.Log("s并未初始化")
}
}
复制代码
五、运算符
1.算数运算符:
| 运算符 | 描述 |
|---|---|
| + | 加 |
| - | 减 |
| * | 乘 |
| / | 除 |
| % | 取余 |
| a++ | 后置自增 |
| a-- | 后置自减 |
注:Go语言中没有前置的自增(++)和自减(--)。
2.比较运算符:
| 运算符 | 描述 |
|---|---|
| == | 判断值是否 “相等” |
| != | 判断值是否 “不相等” |
| > | 判断是否 “大于” |
| < | 判断是否 “小于” |
| >= | 判断是否 “大于等于” |
| <= | 判断是否 “小于等于” |
注:两个数组之间的 == 比较的条件? (还是值比较,并非引用比较) 1.两个数组拥有 “相同的元素个数” 。 2.两个数组中的 “每个元素的值都相等” ,才会返回 true 。
我们写个小 demo ,测试一下:
a := [...]int{1, 2, 3, 4}
b := [...]int{1, 3, 4, 5}
c := [...]int{1, 2, 3, 4}
d := [...]int{1, 2, 3, 4, 5}
t.Log(a == b) // false
t.Log(a == c) // true
// t.Log(a == d) // 编译报错,数组元素不一致
t.Log(d)
复制代码
3.逻辑运算符:
和别的语言没太大差别,简单提一下。
| 运算符 | 描述 |
|---|---|
| && | AND(与)运算符,同 true 为 true ,否则 false 。 |
| \ | | |
| ! | NOT(非)运算符,取反, true 为 false , false 为 true 。 |
4.位运算符:(新增 &^ 按位清零运算符)
| 运算符 | 描述 |
|---|---|
| & | 二进制“与”运算。 |
| \ | |
| ^ | 二进制“异或”运算。 |
| << | 二进制“左移”运算。 |
| >> | 二进制“右移”运算。 |
| &^ | 将二进制 “按位清零” 。将右边所有为1的位数全置为0。(对左边数进行操作) |
注意:多了一个 &^ ,按位清零运算符。
举个栗子:
// 连续位常量赋值
const (
Monday = iota + 1
Tuesday
Wednesday
Thursday
Friday
Saturday
Sunday
)
// 测试按位清零
func TestBitClear(t *testing.T) {
a := 5 // 0101
t.Log("按位清零前:", a) // 0101
a = a &^ Wednesday // 5 &^ 3
t.Log("按位清零后:", a) // 0100
}
复制代码
六、条件与循环
1.条件:if
与别的编程语言的区别:
bool
类似这样:
if var declaration; condition {
//...
}
复制代码
举个简单例子:
if a := 647; a == 647 {
t.Log(a)
}
复制代码
如果写了个请求,类似就成了这样:
if v, err := someFunc(); err == nil {
// 无error,成功!do something!
} else {
// 有error,失败!do something!
}
复制代码
2.条件:switch
switch与其他主流编程语言有些区别,主要是变得更方便、更快捷了。
- 默认
break,不需要主动写break。(如果想要贯穿,才用fallthrough关键字,这点与swift很像。注意:但我测试结果走fallthrough并不会去判断下一个case条件,而是直接执行下一个case里的代码) - 条件表达式不限制“常量”或“整数”。
- 单个
case支持多个结果选项,用逗号隔开。 - 可以不设定
switch之后的条件表达式,这样与多个if、else逻辑相同。
举个例子:
func TestSwitchCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch i {
case 0, 2:
t.Log("Even")
case 1, 3:
t.Log("Odd")
default:
t.Log("it is not 0-3")
}
}
}
func TestSwitchCaseCondition(t *testing.T) {
for i := 0; i < 5; i++ {
switch {
case i%2 == 0:
t.Log(i)
// fallthrough
case i%2 == 1:
t.Log(i)
// fallthrough
default:
t.Log("unknown")
}
}
}
复制代码
3.循环:Go只支持for关键字。
举个例子:
func TestWhileLoop(t *testing.T) {
/* 模仿while循环 */
n := 0
for n < 5 {
t.Log(n)
n++
}
// for循环
for n := 0; n < 5; n++ {
t.Log(n)
}
/* 死循环 */
for {
//...
}
}
复制代码
七、数组与切片
1.数组
- 数组的声明:
var a [3]int // 声明并初始化为默认值0
a[0] = 1 // 简单赋值
b := [3]int{1, 2, 3} // 声明的同时进行初始化
c := [2][2]int{{1, 2}, {3, 4}} // 声明多维数组进行初始化
复制代码
- 数组的遍历:
func TestArrayTravel(t *testing.T) {
arr := [...]int{1, 3, 4, 5}
for i := 0; i < len(arr); i++ {
t.Log(arr[i])
}
for _, element := range arr {
t.Log(element)
}
for index, element := range arr {
t.Log(index, element)
}
}
复制代码
- 数组的截取:
func TestArraySection(t *testing.T) {
arr := [...]int{1, 2, 3, 4, 5}
arr_sec := arr[1:2]
t.Log(arr_sec)
}
复制代码
2.切片
切片是一种可变长的结构体。(有点类似于iOS中的 MutableArray )
- 切片的声明:
var s0 []int // 声明
s0 = append(s0, 1) // 追加
s := []int{} // 声明
s1 := []int{1, 2, 3} // 声明并提供初始化值
s2 := make([]int, 2, 4) //声明并提供初始化个数:2,max最大元素个数:4。
/*
语法:make([]type, len, cap)
其中len个元素会被初始化为默认值0,未初始化的元素不可访问。
*/
复制代码
1. 快速声明语法: make([]type, len, cap) 其中,前 len 个元素会被初始化为默认值 0 ,未初始化的元素不可访问。(这是个容易造成crash的点,不能越界操作。)
2. 追加元素语法: s = append(s , element) 其中 s 代表切片, element 代表追加的元素。
- 切片的简单使用: 直接上demo,
func TestSliceInit(t *testing.T) {
var s0 []int
t.Log(len(s0), cap(s0))
s0 = append(s0, 1)
t.Log(len(s0), cap(s0))
s1 := []int{1, 2, 3, 4} // 快速初始化切片
t.Log(len(s1), cap(s1))
s2 := make([]int, 3, 5) // 初始化3个,max最大个数为5
t.Log(len(s2), cap(s2))
t.Log(s2[0], s2[1], s2[2])
s2 = append(s2, 1) // 添加元素
t.Log(s2[0], s2[1], s2[2], s2[3])
// t.Log(s2[0], s2[1], s2[2], s2[3], s2[4])
}
复制代码
- 切片的原理:切片的共享存储结构。 当切片元素个数
len超过cap时,会进行2倍扩容。
八、Map(键值对Key: Value)
1.Map基本操作:
- Map的三种初始化方式:
/*
第一种初始化方式:有初始值
*/
m1 := map[string]int{"Key1": 1, "Key2": 2, "Key3": 3}
t.Log(m1)
t.Logf("len m1 = %d", len(m1))
/*
第二种初始化方式:无初始值
*/
m2 := map[string]int{}
m2["Key"] = 16
t.Logf("len m2 = %d", len(m2))
/*
第三种初始化方式:初始化cap大小(最大容量),但len依然为0。(理由如下,map不像数组有默认值0,所以len依然为0)
*/
m3 := make(map[string]int, 10)
t.Logf("len m3 = %d", len(m3))
复制代码
- Map元素的访问:
当访问的 Map 不存在指定的 Key 时,会默认返回 0 值。 因此,Go语言中,不能通过 Value 是否为空来判断 Key 是否存在。
如果想要判断 Value 是否存在,可用如下方式:
if value, ok := map[key]; ok {
// 有值
} else {
// 无值
}
复制代码
Demo:
func TestAccessNotExistingKey(t *testing.T) {
m1 := map[int]int{} // 初始化一个空map
t.Log(m1[1]) // 随便访问一个Key?打印结果为0
m1[2] = 0 // 设置一个Key(2)和Value(0)。
t.Log(m1[2]) // 打印一下还是0
if value, ok := m1[3]; ok { // var v = m1[3], ok是表达式的bool值
t.Logf("Key 3‘s value is %d", value)
} else {
t.Log("Key 3 is not existing.")
}
}
复制代码
- Map的遍历:
与遍历数组for-range类似,但不同点在于 “数组返回的是index,Map返回的是Key” 。
func TestTravelMap(t *testing.T) {
map1 := map[string]int{"Key1": 1, "Key2": 2, "Key3": 3}
for key, value := range map1 {
t.Log(key, value)
}
}
复制代码
2.Map与工厂模式
Value Dock type
func TestMapWithFunValue(t *testing.T) {
m := map[int]func(op int) int{}
m[1] = func(op int) int { return op }
m[2] = func(op int) int { return op * op }
m[3] = func(op int) int { return op * op * op }
t.Log(m[1](2), m[2](2), m[3](3))
}
复制代码
3.在Go语言中实现Set
Go没有Set的实现,但可以通过 map[type]bool 来实现。
- 元素的唯一性
- 基本操作(添加元素、判断元素是否存在、删除元素、元素个数)
func TestMapForSet(t *testing.T) {
mySet := map[int]bool{} // 初始化map
mySet[1] = true // 设置key、value
n := 3
if mySet[n] {
t.Logf("%d is existing", n)
} else {
t.Logf("%d is not existing", n)
}
mySet[3] = true // 设置Key、value
t.Log(len(mySet))
delete(mySet, 1) // 删除Key为1的map
t.Log(len(mySet))
}
复制代码
九、字符串
与其他编程语言的差异:
-
string是 “值类型” ,而不是引用或指针类型。因此,默认初始化时,会是一个""空的字符串。(而不是nil) -
string是只读的byte slice,len函数返回的是string中的byte数。 -
string类型的byte数组可以存放任何数据。
例如:
s = "\xE4\xB8\xA5" // 可以存储任何二进制数据 复制代码
- 用
len求出来的是byte数,并不是字符数。
问:Unicode 与 UTF8 的关系?
答:
Unicode 是一种字符集。( code point )
UTF-8 是 Unicode 的存储实现。(转换为字节序列的规则)
举个例子:
| 编码 | 存储 |
|---|---|
| 字符 | "中" |
| Unicode | 0x4E2D |
| UTF-8 | 0xE4B8AD |
| string/[]byte | [0xE4, 0xB8, 0xAD] |
- 使用Go语言支持的
strings、strconv库:
首先,要导入strings和strconv库:
import ( "strconv" "strings" ) 复制代码
字符串的分割与拼接的demo:
// 测试strings库
func TestStringFunc(t *testing.T) {
s := "A,B,C"
parts := strings.Split(s, ",") // 字符串按","分割
for _, part := range parts {
t.Log(part)
}
t.Log(strings.Join(parts, "-")) // 字符串按"-"拼接
}
复制代码
字符串转型demo:
// 测试strconv库
func TestConv(t *testing.T) {
s := strconv.Itoa(10) // Int转string
t.Log("str" + s)
if value, err := strconv.Atoi("10"); err == nil {
t.Log(10 + value)
} else {
t.Log("转换不成功!")
}
// t.Log(10 + strconv.Atoi("10")) // string转Int,编译错误
}
复制代码
十、函数:是一等公民(可作为变量、参数、返回值)
与其他编程语言的区别:
-
函数可以有 多个返回值 。
-
所有参数都是 值传递 ,不是引用传递。
slice、map、channel会有传引用的错觉。(slice、map、channel本身是一个结构体,其中会包含下一块slice、map、channel的指针地址。因此我们作为参数传入函数的slice、map、channel实际上被复制了一份,但本身操作的其他对象依然会是原slice、map和channel的内存地址。)
这块可能稍微有点绕,可以看下slice的实现原理。(PS:可以参考“第七条”切片原理相关内容)
-
函数可以作为**“变量”**的值。
-
函数可以作为**“参数” 和 “返回值”**。
重点:函数式编程(Go)
因为在Go语言中,函数可以作为变量、参数、返回值。 因此,在编程习惯上与别的编程语言有些差异。 需要开发者去慢慢适应。
实战小技巧
- 支持函数可变长参数
语法: [参数名] ...[参数类型]
PS:不指定参数个数,但需要指定参数类型。
举个例子,我们想写个求和函数,就可以这么写:
// 求和函数
func sum(ops ...int) int {
s := 0
for _, op := range ops {
s += op
}
return s
}
复制代码
- 支持函数延时执行,多用于释放资源(解锁)
还是举个例子:
// 模仿释放资源的函数
func Clear() {
fmt.Println("Clear resources.")
}
// 测试函数延时执行:多用于释放资源
func TestDefer(t *testing.T) {
defer func() {
// 用于释放一些资源(锁)
Clear()
}()
fmt.Println("Started")
panic("Fatal error") // panic:程序异常中断,defer仍会执行
}
复制代码
最后,本系列我是在蔡超老师的 技术分享 下总结、实战完成的, 感谢蔡超老师的 技术分享 。
PS:另附上,分享链接: 《Go语言从入门到实战》 祝大家学有所成,工作顺利。谢谢!
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Go 语言基础系列:基础语法
- Go 语言基础系列:基础语法
- go语言学习初探(二)基础语法
- go语言基础语法:变量的使用及注意事项
- 蜗牛爬行日记——记Python语法基础与C语言的异同(二)
- Go 语言基础
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
中国网络媒体的第一个十年
彭兰 / 清华大学出版社 / 2005-7 / 35.00元
此书对中国网络媒体的第一个十年这一重要的历史阶段首次进行了全景式、全程式的历史记录,并进行了全面深入的研究,在一定程度上填补了中国网络媒体发展史宏观研究方面的空白。对于网络新闻传播的研究,以及当代中国媒体发展的研究.具有重要的意义。 ——方汉奇 图书目录 绪论 1 第一章 投石问路:中国网络媒体萌芽(1994一1995年) 9 第一节 从实验室走向市场:互联网兴起 10 ......一起来看看 《中国网络媒体的第一个十年》 这本书的介绍吧!