变量名、函数名、常量名 首字母大写,则可被其他包引用
java则是通过访问修饰符
public、protected(包内和子类)、default(包)、private(当前类)
// 基本数据类型
整型(int、int8、int16、int32、int64、uint、uint8、uint16、uint32、byte)
浮点型(float32、float64) // 精度损失 浮点数默认为float64
布尔型(bool)。//不可以用0或非0值替代false和true,Java中也不行,与c/c++不同
字符串(string)
单个字符使用byte存储(utf-8)(英文1个字节,汉字3个字节)
// 复杂数据类型
指针(Pointer)、数组、结构体(struct)、管道(Channel)、函数(go中函数也是一种类型)、切片(slice)、接口(interface)、map
// Java中四类八种
byte、short、int、long
float、double
char
boolean
例:
var n int64 = 10
fmt.Printf("n 的数据类型是%T 占用字节数是%d",n,unsafe.Sizeof(n))
1、go必须显示转换 //Java可以自动转换
2、可以从范围小的 -> 范围大的,也可以从范围大的 -> 范围小的
3、范围大的 -> 范围小的,编译不会报错,但转换结果和我们希望的不一样
例:
var n1 int32 = 12
var n3 int8
var n4 int8
n4 = int8(n1) + 127 // 编译通过,但结果按溢出处理
n3 = int8(n1) + 128 // 编译不过 128超出n3的范围
// go中为基本数据类型 不可以为nil ,同样不可变
// go中字符串由字节组成
// 字符串不可变,若想修改,将字符串转换为[]byte,修改之后再转换为字符串,
// 但是不能处理中文,因为中文是三个字节,若想修改为中文,字符串转换为
// []rune,修改之后再转换为字符串
// "" 和 ``
s := ""
var s string //默认为“” 字符串不可以为nil
var s = ""
var s string = ""
strings.Join(arr[1:], "") //对切片用空格拼接
//字符串拼接
var str = "aaa" + "bbb" + //此加号必须写在上面,否则报错
"ccc"
str += "ddd"
// Java中字符串由字符组成
String s = null;
String a = "";
String str = new String("aa");
//字符串拼接( + 和 StringBuffer的append方法 )
String str =" world";
StringBuffer sb = new StringBuffer("hello");
sb.append(str);
1、基本数据类型 转 string ( 两种)
var b bool = true
str = fmt.Sprintf("%q", b) //格式化字符串, 结果为 "true"
strconv.Format..(b ..)string
2、字符串 转 基本数据类型
// 返回值为bool、float64、int64、uint64等,需要int32 则需要自己 显示转换
strconv.ParseInt(str string)(value Int64 , err error)
// 若把 “hello”字符串转化为int、float、bool,结果为0、0、false
type myInt int //相当于别名 但是go认为是两种类型
type myFunType func(int,int)int
可以i++ 和 i-- // 是语句,不像c是表达式
不可以--i 和 ++i 和 j = i++
Java则都可以
取模本质
a%b -> a - a/b*b
for是go中唯一的循环,没有while
for 中加 if 实现 while 与 do while
匹配项后面不用加break (Java后面需要)
值类型:int、float、bool、string、数组、struct
引用类型:指针、slice、map、chan、interface
1、
go函数 不可以重载
返回值可以命名
func cal(n1 int, n2 int)(sum int, sub int) {
...
}
_,sub = cal(10, 20) //忽略返回值
2、go支持可变参数 (args是slice切片)Java也支持
func sum(args... int)sum int {
...
}
3、init函数细节
若一个文件含有全局变量定义、init函数、main函数
则执行流程为 变量定义 init main
若main.go含有全局变量定义、init函数、main函数,同时引入另一个utils.go文件
则执行流程为utils.go的 变量定义 init函数 main.go的 变量定义 init函数 main函数
4、闭包
//累加器
//返回一个匿名函数,但此匿名函数引用到函数外的n,这个匿名函数就和n形成一个整体 闭包
func AddUpper() func (int) int {
var n int = 10
return func (x int) int {
n = n + x
return n
}
}
func main() {
f := AddUpper()
fmt.Println(f(1)) //11
fmt.Println(f(2)) //13
fmt.Println(f(3)) //16
}
5、defer(主要作用:释放资源)
defer函数会入defer栈(放入栈时相关值也会同时入栈) 先入后出
6、字符串相关函数…
7、时间日期相关函数… “2006/01/02 15:04:05”
8、内置函数
new: 用来分配内存,主要用来分配值类型 int、float32、struct…返回的是指针
make:用来分配内存,主要用来分配引用类型 chan、map、slice…
1、
panic + recover + defer (defer中使用recover捕获panic异常 并 处理异常)
panic //调用一个函数,函数可能会panic异常,造成程序终止,应该怎样 defer中recover处理,避免程序终止 (一般 Must...函数都会panic异常)
2、自定义错误
errors.New("错误说明") //返回error类型的值
package main
import "fmt"
func main() {
fmt.Println("array coding........")
//var array [5]int
//array[2] = 2
//var array = [5]int{0, 1, 2, 3, 4}
//array := [5]int{0, 1, 2, 3, 4}
array := [...]int{0, 1, 2, 3, 4}
//array := []int{1: 1, 3: 3} //[]中没有东西的是切片
//a := 9
//array := [5]*int{0: new(int)}
//*array[0] = 10
//array[1] = &a
fmt.Println(array)
}
java中初始化数组
// 第一种方法:动态
int [] arr = new int [6];
int intValue = arr [5];
System.out.println(intValue); // 0
//第二种方法:静态
int[] arr1 = {1,2,3,4,5};
//第三种方法:静态
int[] arr2 = new int [] {1,2,3,4,5};
package main
import (
"fmt"
)
func main() {
fmt.Println("slice coding........")
//slice := make([]string, 3, 5)
//slice[0] = "aaa"
//slice := []int{10, 20, 30, 40, 50}
//newSlice := slice[1:3] //长度为2,容量为4 两个切片共享内存
//newSlice = append(newSlice, 60) //长度为3,容量为4 两个切片共享内存
//slice := []int{10, 20, 30, 40, 50}
//newSlice := append(slice, 60) //newSlice拥有全新的底层数组,容量变为原来的两倍,容量超过1000 按照1.25增长
//source := []string{"aaa", "bbb", "ccc", "ddd", "eee"}
//slice := source[2:3:4] //长度为1,容量为2 //slice := source[2:3:4] 长度为1,容量为3 [i:j:k]
//好处:若 slice := source[2:3:3] append时会让slice有自己的底层数组,不会改变原有的切片
//s1 := []int{1, 2}
//s2 := []int{3, 4}
//fmt.Println(append(s1, s2...)) //将一个切片追加到另一个切片
//slice := []int{10, 20, 30, 40}
//遍历一
//for index, value := range slice { //range创建每个元素的副本,不是直接返回对该元素的引用
// fmt.Printf("Index: %d , Value: %d\n", index, value)
//}
//遍历二
//for _, value := range slice { // _忽略索引值
// fmt.Printf("Value: %d\n", value)
//}
//遍历三 传统方式
//for index := 0; index < len(slice); index++ {
// fmt.Println(slice[index])
//}
}
package main
import (
"fmt"
)
func main() {
fmt.Println("map coding........")
//dict := make(map[string]int,10) //第二个参数是长度,超出长度,自动扩容
dict := map[string]string{"a": "aaa", "b": "bbb"} //dict := map[string]string{}
//dict := map[string][]string{}
dict["c"] = "ccc"
//方式一
//value, exists := dict["a"]
//if exists {
// fmt.Println(value)
//}
//方式二
//value := dict["b"]
//if value != "" {
// fmt.Println(value)
//}
delete(dict, "b")
//map在函数间传递映射时,不会制造副本,函数修改map,所有对这个映射的引用都会察觉到这个修改
fmt.Println(dict)
}
//对map排序,可以先把key遍历放入切片,之后对切片排序,最后根据切片中key,拿数据
变量赋值
方法调用时,主要看接收者类型,编译时会自动优化
(1) go通过 在结构体中嵌入匿名结构体 实现继承的概念,那就是嵌入
// 创建结构体实例的时候,可以直接指定匿名结构体字段的值
//自定义类型 DataResponse 中的属性是两个自定义类型
type DataResponse struct {
*tchttp.BaseResponse // 访问起中的属性或方法可以简写
Response *struct {
Data []*MData
RequestId *string
}
}
(2) 若通过 在结构体中嵌入有名结构体 实现继承的概念,那就是组合
//访问组合的结构体或方法时,必须带上结构体名,不可以简写
type DataResponse struct {
aa *tchttp.BaseResponse
Response *struct {
Data []*MData
RequestId *string
}
}
1、golang语言中的继承是通过内嵌或组合来实现的,所以可以说,在go语言中,相比较于继承,组合更受青睐。
2、通过类型 实例名.int 来获取存储在匿名字段中的数据,于是可以得出一个结论:在一个结构体中对于每一种数据类型只能有一个匿名字段。
3、匿名内嵌的结构体可以直接访问其成员变量 如,ins.a.b.c的访问可以简化为ins.c
1、方法集
1.1、如果使用 指针接收者 来实现一个接口,那么只有指向那个类型的指针才能实现对应的接口
1.2、如果使用 值接收者 go来实现一个接口,那么那个类型的值和指针接收者都能够实现对应的接口
2、嵌入类型
内部类型实现接口,内部类型会被提升
如果外部类型同样实现了该接口,内部类型不会被提升
3、interface类型默认是一个指针(引用类型)
4、所有的类型都实现了 interface{} 空接口
5、类型断言
//a是接口类型变量 //b类型实现了a接口
b = a.(Point) //表示判断a是否是指向Point类型的变量,如果是就转成Point类型并赋值给b变量,否则报错
注意:
如果类型不匹配,就会报panic,( 带上检查机制,不要报panic,如下 )
b, ok = a.(Point)
if ok == false {
fmt.println("convert fail")
}
...
1、os.Args
os包下 os.Args 是一个string的切片,用来存储命令行参数
func main() {
fmt.Println("命令行参数有%v个",len(os.Args))
// args[0]是程序名,后面是参数名
for i, v := range os.Args {
fmt.Printf("args[%v]=%v",i, v)
}
}
2、flag包 命令行参数解析
...
var(
user string
port int
)
func init() {
flag.StringVar(&user, "u", "", "用户名,默认为空")
flag.IntVar(&port, "port", "3306", "端口号,默认为3306")
flag.Parse() // 必须
}
func main() {
...
}
...
对结构体序列化( tag标签 )
type People struct {
Name string `json:"name"` //反射机制
Age int `json:"age,omitempty"` //序列化时忽略0值或空值
}
p := People{Name: "zs", Age: 18}
data, _ := json.Marshal(p)
fmt.Println(data) //[123 34 110 97 109 101 34 58 34 122 115 34 44 34 97 103 101 34 58 49 56 125]
fmt.Println(string(data)) //{"name":"zs","age":18}
对结构体反序列化
str := "{\"name\":\"zs\",\"age\":18}"
var p1 People
err := json.Unmarshal([]byte(str), &p1)
if err != nil {
...
}
cal.go
package cal
//被测试的函数
func addUpper(n int) int {
res := 0
for i := 1; i <= n - 1; i++ {
res += i
}
return res
}
//被测试的函数
func getSub(n1 int, n2 int) int {
return n1 - n2
}
cal_test.go
package cal
import (
"fmt"
"testing" //引入go 的testing框架包
)
//编写要给测试用例,去测试addUpper是否正确
func TestAddUpper(t *testing.T) {
//调用
res := addUpper(10)
if res != 55 {
//fmt.Printf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
t.Fatalf("AddUpper(10) 执行错误,期望值=%v 实际值=%v\n", 55, res)
}
//如果正确,输出日志
t.Logf("AddUpper(10) 执行正确...")
}
//所有TestXxx函数都会被执行
func TestHello(t *testing.T) {
fmt.Println("TestHello被调用..")
}
sub_test.go
package cal
import (
_ "fmt"
"testing"
)
func TestGetSub(t *testing.T) {
res := getSub(10, 3)
if res != 7 {
t.Fatalf("getSub(10, 3) 执行错误,期望值=%v 实际值=%v\n", 7, res)
}
//如果正确,输出日志
t.Logf("getSub(10, 3) 执行正确!!!!...")
}
1、Go协程的特点
(1) 有独立的栈空间
(2) 共享程序堆空间
(3) 调度由程序员控制
(4) 协程是轻量级的线程
2、不同goroutine 之间如何通讯
(1) 全局变量的互斥锁
import "sync"
var lock sync.Mutex
lock.Lock()
...
lock.Unlocl()
//不利于多个协程对全局变量的读写操作
(2) 使用管道 channel (本质队列) ( 多goroutine访问时,不需要加锁,channel本身线程安全)
package main
import (
"fmt"
)
func main() {
//创建一个可以存放 3 个 int 类型的管道 var intChan chan int
intChan = make(chan int, 3)
fmt.Printf("intChan 的值=%v intChan 本身的地址=%p\n", intChan, &intChan)
intChan<- 10
num := 20
intChan<- num
intChan<- 50 //给管写入数据时,不能超过其容量
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan))// 3, 3
//从管道中读取数据
var num2 int
num2 = <-intChan
fmt.Println("num2=", num2)
fmt.Printf("channel len= %v cap=%v \n", len(intChan), cap(intChan))// 2, 3
num3 := <-intChan
num4 := <-intChan
//在没有使用协程的情况下,如果我们的管道数据已经全部取出,再取就会报告 deadlock
num5 := <-intChan
fmt.Println("num3=", num3, "num4=", num4, "num5=", num5)
}
注意:interface{} 的channel 可以存放任意类型变量,但是取出数据时注意类型断言
close(intChan) // intChan管道关闭后,不可以再写入数据,但是可以读数据
遍历管道,使用for range
1、若遍历取出数据时,channel没有关闭,取完数据则会一直阻塞,出现deadlock的错误(若同时有协程写入数据,则不会死锁)
2、若遍历时,channel已经关闭,则会正常遍历数据,遍历完后,退出遍历
1、管道可以是只读或只写
var intChan <-chan int
intChan = make(chan int, 3)
var intChan chan<- int
intChan = make(chan int, 3)
2、使用select可以解决从管道取数据的阻塞问题
3、goroutine中使用recover,解决协程中出现panic,导致程序奔溃问题