这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天,在今天的学习当中,主要是学习了Go语言语法基础方面的内容,同时也学习了一些与GC相关的知识。
Go
第一段代码:
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
* go build hello.go 生成exe文件
* go build -o world.exe hello.go 生成自定义名字的exe文件
* go run hello.go 即可直接运行
* gofmt 格式化源代码 几乎不用
*/
package main;
import "fmt";
func main() {
fmt.Print("hello world");
}
变量使用
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import "fmt"
// 全局变量
var v = "fang"
var (
a = 1
b = 2.3
c = "netty"
)
func main() {
// 定义变量
var age int = 18
// 自动类型推导
var name = "FANG" // 也可写成 name := "FANG"
/*
多个变量同时声明
var n1, n2, n3 int
var n1, name, n5 = 10, "fang", 7.8
*/
fmt.Println("age = ", age)
fmt.Println("name = ", name)
}
Go数据类型
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import "fmt"
func main() {
var age = 18
// 强制类型转换
fmt.Println(float32(age))
}
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
// import (
// "fmt"
// "strconv" 引入strconv包进行类型转换
// )
func main() {
// strconv.FormatBool()
// strconv.ParseInt()
// string 转向其它数据类型 如果没有成功会输出其类型的默认值
}
流程控制
package main
import "fmt"
func main() {
if count := 20; count < 30 {
fmt.Println("sorry")
}
var sum int = 0
for i := 0; i <= 5; i++ {
sum += i
}
var str = "hello 你好"
for i, value := range str {
fmt.Printf("索引: %d, 值: %c \n", i, value)
}
}
函数
- int、float、bool、array、sturct等在函数传参时是值传递
- slice、map、interface、channel在函数传参时是引用传递
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import "fmt"
func sum(a int, b int) int {
return a + b
}
type Func func(int, int) int // 取别名
func test(myFunc Func) { // 函数做参数
fmt.Println(myFunc(11, 20))
}
func init() { // init函数会在main函数执行之前被执行
fmt.Println("init函数执行");
}
func main() {
test(sum)
}
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import "fmt"
func init() {
fmt.Println("asdkfj")
}
func test() int {
fmt.Println("test--------------")
return 10
}
var t int = test()
// 执行顺序 引入包的定义的全局变量 init执行 本包定义的全局变量 init的执行
func main() {
fmt.Println(t)
result := func(a int, b int) int { // 匿名函数
return a + b
}(10, 20)
add := func(a int, b int) int { // 匿名函数
return a + b
}
result1 := add(0, 2)
fmt.Println(result + result1)
}
包
略......
闭包
- 闭包就是一个函数与其相关的引用环境组成的一个整体
- 特点
- 返回一个匿名函数但这个匿名函数会引用到函数外的变量两者形成一个整体,构成闭包
- 闭包中使用的变量会一直保留在内存中,所以闭包不能滥用
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import "fmt"
func getSum() func(int) int {
var sum int = 0
return func(num int) int {
sum += num
return sum
}
}
func main() {
f := getSum()
fmt.Println(f(1))
fmt.Println(f(2))
fmt.Println(f(3))
}
defer
- 遇到defer关键字 不会立即执行后面的语句 而是将其压入一个栈中 然后执行后面的语句
日期时间函数
/*
* @Author: FANG
* @Date: 2022-03-16 15:27:05
*/
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
f := now.Format("2006/01/02 15/04/05") // 对应 年月日 时分秒
fmt.Println(f)
}
内置函数
// 内建函数len返回v的长度取决于v的类型
// 数组:v中的元素的数量 数组指针:*v中的元素的数量(v位nil时为panic)
// 切片 映射:v中元素的数量 若v为nil len(v)即为零
// 字符串:v中的字节数
// 通道:通道缓存中队列(未读取)元素的数量 若为nil len(v)为零
func len(v Type) int
// 追加元素到切片的末尾 若它有足够的容量 其目标就会重新切片以容纳新的元素
// 否则 就会分配一个新的基本数组 append返回更新后的切片
func append(slice []Type, elems ...Type) []Type
// 分配内存 主要用来分配值类型(int系列 float系列 bool string 数组和结构体struct)
// 返回值为一个指向该类型的新分配的零值的指针
func new(Type) *Type
错误处理机制
// defer + recover
package main
import "fmt"
import "errors"
func test() {
defer func() {
err := recover()
if err != nil {
fmt.Println("error")
fmt.Println(err)
}
}()
a := 10
b := 0
res := a / b
fmt.Println(res)
}
func test1() err error {
a := 10
b := 0
if b == 0 {
return errors.New("除数不能为零哦")
} else {
res := a / b
fmt.Println(res)
return nil
}
}
func main() {
// test()
err := test1()
if err != nil {
fmt.Println("自定义错误")
panic(err) // 终止Go程
}
}
数组
- 数组默认是值传递
package main
import "fmt"
func main() {
var scores [5]int
// 数组初始化
var arr1 [3]int = [3]int{3, 6, 9}
var arr2 = [3]int{1, 4, 7}
var arr3 = [...]int{1, 3, 7, 9}
var arr4 = [...]int{3: 77, 0: 12, 1: 13, 2: 98}
var arr5 = [3][3]int{{1, 4, 7}, {3, 6, 9}, {2, 4, 8}}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
fmt.Println(arr4)
// 读入
for i := 0; i < len(scores); i++ {
fmt.Scanln(&scores[i])
}
// 一维数组遍历
for i := 0; i < len(scores); i++ {
fmt.Println(scores[i])
}
for _, val := range scores {
fmt.Println(val)
}
// 二维数组遍历
for i := 0; i < len(arr5); i++ {
for j := 0; j < len(arr5[i]); j++ {
fmt.Print(arr5[i][j], "\t")
}
fmt.Println()
}
for _, val := range arr5 {
for _, value := range val {
fmt.Println(value)
}
}
}
切片
package main
import "fmt"
func main() {
var intArr = [6]int{1, 2, 3, 4, 5, 6}
// 定义切片
var slice []int = intArr[1:3]
// 切片底层是一个含有一个指向数组的指针 一个切片长度 一个切片容量的数据结构
// 切片遍历同数组
// 向切片中添加值
slice = append(slice, 88)
fmt.Println(slice)
}
type slice struct { // 切片定义
array unsafe.Pointer
len int
cap int
}
// 切片扩容
// 当原 slice 容量小于 1024 的时候,新 slice 容量变成原来的 2 倍;原 slice 容量超过 1024,新 slice 容量变成原来的1.25// // 倍。然后再进行一个内存对齐的操作,实际的容量是会大于2倍和1.25倍.
// 函数传参
// 函数传参如果传入slice更新底层的数据,会反应到实际的参数.底层数据在 slice 结构体里是一个指针,仅管 slice 结构体自身不会被改
// 变,也就是说底层数据地址不会被改变。append操作不会反应到实际的参数.
map
package main
import "fmt"
func main() {
// 第一种声明map
var MyMap map[string]string
if MyMap == nil {
fmt.Println("map is empty")
}
MyMap = make(map[string]string, 10)
MyMap["one"] = "java"
// 第二种声明map
MyMap1 := make(map[string]string)
MyMap1["key"] = "c++"
// 第三种声明map
MyMap2 := map[string]string{
"one": "php",
"two": "java",
"three": "go",
}
fmt.Println(MyMap2)
// 删除key
delete(MyMap2, "one")
// 遍历
for key, value := range MyMap2 {
fmt.Println("key = ", key)
fmt.Println("value = ", value)
}
}
结构体
package main
import "fmt"
type Person struct { // 定义一个结构体
name string
master string
age int
}
func main() {
p := Person{
name: "威少",
master: "FANG",
age: 18,
}
fmt.Println(p)
}
结构体函数
package main
import "fmt"
type Person struct {
name string
master string
age int
}
func (person *Person) GetName() string {
return person.name
}
func (person *Person) GetMaster() string {
return person.master
}
func (person *Person) GetAge() int {
return person.age
}
func (person *Person) SetName(name string) {
person.name = name
}
func (person *Person) SetMaster(master string) {
person.master = master
}
func (person *Person) SetAge(age int) {
person.age = age
}
func main() {
var person Person
person.SetName("熊")
person.SetMaster("FANG")
person.SetAge(18)
fmt.Println(person.GetMaster())
}
继承
package main
import "fmt"
type Person struct {
name string
age int
}
type Hero struct {
// 继承
Person
scores int
}
func (person *Person) Eat() {
fmt.Println("Eating ...")
}
func (person *Person) Walk() {
fmt.Println("Walking")
}
// 重写方法
func (hero Hero) Eat() {
fmt.Println("hero is Eating")
}
func main() {
hero := Hero{
Person: Person{"fang", 18},
scores: 99,
}
hero.Eat()
}
多态
package main
import "fmt"
type Mammal interface { // 一个结构体实现了接口中的方法 就认为是继承了这个接口
Say()
}
type Dog struct{}
type Cat struct{}
type Human struct{}
func (d Dog) Say() {
fmt.Println("woof")
}
func (c Cat) Say() {
fmt.Println("meow")
}
func (h Human) Say() {
fmt.Println("speak")
}
func main() {
var m Mammal
m = Dog{}
m.Say()
m = Cat{}
m.Say()
m = Human{}
m.Say()
}
接口
- Go 语言提供了另外一种数据类型即接口,它把所有的具有共性的方法定义在一起,任何其他类型只要实现了这些方法就是实现了这个接口
- interface{}空接口默认是所有类型都实现了 类似Java中的Object
- value, ok := x.(T)类型断言
- 类型断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok,可根据该布尔值判断 x 是否为 T 类型:
- 如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T。
- 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值。
- 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。
变量的内置pair结构
package main
import "fmt"
func main() {
var a string
// pair<type:string, value:"acel">
a = "acel"
// pair<type:string, value:"acel">
// 无论a赋值给谁 pair<type:string, value:"acel">会永远连续传递
var allType interface{}
allType = a
str, _ := allType.(string)
fmt.Println(str)
}
package main
import "fmt"
type Reader interface {
ReadBook()
}
type Writer interface {
WriteBook()
}
type Book struct{}
func (book *Book) ReadBook() {
fmt.Println("read the book")
}
func (book *Book) WriteBook() {
fmt.Println("write the book")
}
func (book Book) SetName() {
}
func main() {
// pair<type:Book, value:Book{}地址>
b := &Book{}
// pair<type: , value: >
var r Reader
// pair<type:Book, value:Book{}地址>
r = b
r.ReadBook()
// pair<type:Book, value:Book{}地址>
var w Writer
w = r.(Writer)
w.WriteBook()
}
反射
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int
Name string
Age int
}
func (user User) GetName() string {
return user.Name
}
func DoFiledAndMethod(input interface{}) {
// 获取input的type
inputType := reflect.TypeOf(input)
// 获取input的value
inputValue := reflect.ValueOf(input)
// 通过type获取里面的字段
// 1. 获取interface的reflect.Type 通过Type得到NumFiled 进行遍历
// 2. 得到每个filed 数据类型
// 3. 通过filed的Interface()方法得到对应的value
for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i)
value := inputValue.Field(i).Interface()
fmt.Printf("%s: %v = %v\n", field.Name, field.Type, value)
}
// 通过type获取方法
// fmt.Println(inputType.NumMethod())
for i := 0; i < inputType.NumMethod(); i++ {
m := inputType.Method(i)
fmt.Printf("%s: %v\n", m.Name, m.Type)
}
}
func main() {
user := User{1, "fang", 18}
DoFiledAndMethod(user)
}
// 结构体tag
package main
import (
"fmt"
"reflect"
)
type User struct {
Id int `info:"id"` // tag
Name string `info:"名字"`
Age int `info:"年龄"`
}
func (user User) GetName() string {
return user.Name
}
func findTag(input interface{}) {
t := reflect.TypeOf(input).Elem()
for i := 0; i < t.NumField(); i++ {
tagInfo := t.Field(i).Tag.Get("info")
fmt.Println(tagInfo)
}
}
func main() {
user := User{1, "fang", 18}
findTag(&user)
}
json转换
package main
import (
"encoding/json"
"fmt"
"log"
)
type Animal struct {
Name string `json:"name,omitempty"` // 值为空就忽略
Age int `json:"-"` // 忽略
Color bool `json:"color"`
}
func main() {
a := Animal {
Age: 80,
Color: true,
}
// 转为json
buf, err := json.Marshal(a)
// json.Unmarshal() 解析json
if err != nil {
log.Fatalln(err)
}
fmt.Println(string(buf))
}
goroutine
package main
import (
"fmt"
"time"
)
func newTask() {
i := 0
for {
i++
fmt.Printf("Goroutine : i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
func main() {
// 创建一个go程
go newTask()
for i := 0; ; i++ {
fmt.Printf("Main : i = %d\n", i)
time.Sleep(1 * time.Second)
}
}
channel
- 不带缓冲的channel
package main
import (
"fmt"
)
func main() {
// 定义一个channel
c := make(chan int)
go func() {
defer fmt.Println("goroutine exit")
fmt.Println("goroutine running ...")
c <- 666 // 将666发送给c
}()
num := <-c // 从c中接收数据赋值给num
fmt.Println(num)
fmt.Println("main exit")
}
- 有缓冲的channel
package main
import (
"fmt"
"time"
)
func main() {
// 定义一个带有缓冲的channel
c := make(chan int, 3)
fmt.Println("len(c) = ", len(c), ", cap(c) = ", cap(c))
go func() {
defer fmt.Println("goroutine exit")
for i := 0; i < 3; i++ {
fmt.Println("goroutine running ...")
c <- i // 将i发送给c
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
num := <-c
fmt.Println(num)
}
// 使用range获取
// for data := range c {
// fmt.Println(data)
// }
fmt.Println("main exit")
}
- 使用close可以关闭channel
select {
case <-chan1:
// 如果chan1读取到数据 则执行case语句
case chan2 <- 1:
// 如果成功向chan2写入数据 则执行case语句
default:
// 如果上面都没成功 则进入defalut处理流程
}
SQL
/*
* @Author: FANG
* @Date: 2022-03-18 23:35:20
*/
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql" //导入mysql包
)
var database string = "root:ff1639579565@tcp(127.0.0.1:3306)/fang?charset=utf8&parseTime=True"
func main() {
db, err := sql.Open("mysql", database)
if err != nil {
fmt.Println("数据库链接错误", err)
return
}
//延迟到函数结束关闭链接
defer db.Close()
// 单行查询
var user User
row := db.QueryRow("select * from user where id = ?", 1)
row.Scan(&user.Id, &user.Username, &user.Password)
// json
// buf, _ := json.Marshal(user)
// fmt.Println(string(buf))
// 多行查询
var users []User
rows, _ := db.Query("select * from user")
for rows.Next() {
var user User
rows.Scan(&user.Id, &user.Username, &user.Password)
users = append(users, user)
}
fmt.Println(users)
// 插入数据
result, _ := db.Exec("insert into user (username, password) values (?, ?)", "wei", "wei")
newID, _ := result.LastInsertId() // 新增数据的id
affectedNum, _ := result.RowsAffected() // 受到影响的行数
fmt.Printf("newID: %v\n", newID)
fmt.Printf("affectedNum: %v\n", affectedNum)
// 修改数据
result2, _ := db.Exec("update user set password=? where id = ?", "pass", 1)
i2, _ := result2.RowsAffected() //受影响行数
fmt.Printf("受影响行数:%d \n", i2)
// 删除数据
result3, _ := db.Exec("delete from user where username = ?", "xiong")
i3, _ := result3.RowsAffected()
fmt.Printf("受影响行数:%d \n", i3)
// 事务处理
tx, _ := db.Begin()
result4, _ := tx.Exec("update user set age = age + 1 where username = ?", "fang")
result5, _ := tx.Exec("update user set age = age + 1 where username = ?", "ding")
//影响行数,为0则失败
i4, _ := result4.RowsAffected()
i5, _ := result5.RowsAffected()
if i4 > 0 && i5 > 0 {
//2条数据都更新成功才提交事务
err = tx.Commit()
if err != nil {
fmt.Println("事务提交失败", err)
return
}
fmt.Println("事务提交成功")
} else {
//否则回退事务
err = tx.Rollback()
if err != nil {
fmt.Println("回退事务失败", err)
return
}
fmt.Println("回退事务成功")
}
}
// SQL操作封装
package main
import (
"database/sql"
"fmt"
)
type Manager struct {
handle *sql.DB
}
func New(name, password, url, db string) (*Manager, error) {
handle, err := sql.Open("mysql",
fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8",
name,
password,
url,
db,
),
)
if err != nil {
return nil, err
}
handle.Ping()
return &Manager{handle}, handle.Ping()
}
func (m *Manager) IsPhoneExists(phone string) bool {
var count int
_ = m.handle.QueryRow(
"select count(*) from user where phone = ?",
phone,
).Scan(&count)
return count != 0
}
Redis
// connect to redis
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
re, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("Connection redis error : ", err)
return
}
fmt.Println("Connection redis successfully")
defer re.Close()
}
// string get set
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
re, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed,", err)
return
}
defer re.Close()
_, err = re.Do("Set", "abc", 100)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.Int(re.Do("Get", "abc"))
if err != nil {
fmt.Println("get abc failed,", err)
return
}
fmt.Println(r)
}
// string mset mget
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed,", err)
return
}
defer c.Close()
_, err = c.Do("MSet", "abc", 100, "efg", 300)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.Ints(c.Do("MGet", "abc", "efg"))
if err != nil {
fmt.Println("get abc failed,", err)
return
}
for _, v := range r {
fmt.Println(v)
}
}
// list
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed,", err)
return
}
defer c.Close()
_, err = c.Do("lpush", "book_list", "abc", "ceg", 300)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.String(c.Do("lpop", "book_list"))
if err != nil {
fmt.Println("get abc failed,", err)
return
}
fmt.Println(r)
}
// hash
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed,", err)
return
}
defer c.Close()
_, err = c.Do("HSet", "books", "abc", 100)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.Int(c.Do("HGet", "books", "abc"))
if err != nil {
fmt.Println("get abc failed,", err)
return
}
fmt.Println(r)
}
// expire
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
func main() {
c, err := redis.Dial("tcp", "localhost:6379")
if err != nil {
fmt.Println("conn redis failed,", err)
return
}
defer c.Close()
_, err = c.Do("expire", "abc", 10) // 设置10s后过期
if err != nil {
fmt.Println(err)
return
}
}
// reids pool
package main
import (
"fmt"
"github.com/garyburd/redigo/redis"
)
var pool *redis.Pool //创建redis连接池
func init() {
pool = &redis.Pool{ //实例化一个连接池
MaxIdle: 16, //最初的连接数量
// MaxActive:1000000, //最大连接数量
MaxActive: 0, //连接池最大连接数量,不确定可以用0(0表示自动定义),按需分配
IdleTimeout: 300, //连接关闭时间 300秒 (300秒不使用自动关闭)
Dial: func() (redis.Conn, error) { //要连接的redis数据库
return redis.Dial("tcp", "localhost:6379")
},
}
}
func main() {
//从连接池,取一个链接
c := pool.Get()
//函数运行结束 ,把连接放回连接池
defer c.Close()
_, err := c.Do("Set", "abc", 200)
if err != nil {
fmt.Println(err)
return
}
r, err := redis.Int(c.Do("Get", "abc"))
if err != nil {
fmt.Println("get abc faild :", err)
return
}
fmt.Println(r)
//关闭连接池
pool.Close()
}
Go 知识点
类型别名 & 类型定义
- 类型别名
类型别名:
1. type MyString = string
2. 使用类型别名定义的类型与原类型等价 Go 语言内建的基本类型中就存在两个别名类型
1. byte 是 uint8 的别名类型
2. rune 是 int32 的别名类型
3. 别名类型与源类型是完全相同的
4. 别名类型与源类型可以在源类型支持的条件下进行相等判断 比较判断 与 nil 是否相等判断等
func main() {
type MyString = string
str := "hello"
a := MyString(str)
b := MyString("A" + str)
fmt.Printf("str type is %T\n", str)
fmt.Printf("a type is %T\n", a)
fmt.Printf("a == str is %t\n", a == str)
fmt.Printf("b > a is %t\n", b > a)
}
// 从输出得出结论 MyString 与 string 是完全相同的两个类型
str type is string
a type is string
a == str is true
b > a is false
func main() {
type MyString = string
strs := []string{"aa", "bb", "cc"}
a := []MyString(strs)
fmt.Printf("strs type is %T\n", strs)
fmt.Printf("a type is %T\n", a)
fmt.Printf("a == nil is %t\n", a == nil)
fmt.Printf("strs == nil is %t\n", strs != nil)
}
// []string 与 []MyString 等价
strs type is []string
a type is []string
a == nil is false
strs == nil is true
- 类型定义
类型定义:
1. type MyString string
2. 类型定义是定义一种新的类型,它与源类型是不一样的
func main() {
type MyString string
str := "hello"
a := MyString(str)
b := MyString("A" + str)
fmt.Printf("str type is %T\n", str)
fmt.Printf("a type is %T\n", a)
fmt.Printf("a value is %#v\n", a)
fmt.Printf("b value is %#v\n", b)
// fmt.Printf("a == str is %t\n", a == str)
fmt.Printf("b > a is %t\n", b > a)
}
// string 的 type 是string 而 MyString 的 type 是main.MyString
str type is string
a type is main.MyString
a value is "hello"
b value is "Ahello"
b > a is false
func main() {
type MyString string
strs := []string{"E", "F", "G"}
// cannot convert strs (type []string) to type []MyString
myStrs := []MyString(strs)
fmt.Println(myStrs)
}
对于这里的类型再定义来说,
string可以被称为MyString2的潜在类型。潜在类型的含义是,某个类型在本质上是哪个类型。潜在类型相同的不同类型的值之间是可以进行类型转换的。因此,MyString2 类型的值与 string 类型的值可以使用类型转换表达式进行互转。但对于集合类的类型[]MyString2 与 []string 来说这样做却是不合法的,因为 []MyString2 与 []string 的潜在类型不同,分别是 MyString2 和 string 。