函数
Go只有值传递
Go函数返回值的两种写法。
这个其实没太多要说的。有的教程提到了面向函数范式编程(functional programming )。这篇文章中不说。
先说个点main函数这个函数不能有返回值跟参数。同样init函数也是如果你写了返回值。就会报红(must have no arguments and no return values)。当然即使不知道这点,也不会想到给main函数来点参数跟返回值哈。这也是main函数跟其他语言的main函数的不同之处。
定义一个除法函数。
func div(a int, b int) (int, error) {
if b == 0 {
return -1, errors.New(" b can't is zero value")
}
return a / b, nil
}
我们也可以写成如下方式。这种方式叫做 命名的返回值(named return variables)
func div(a int, b int) (result int, err error) {
if b == 0 {
result = -1
err = errors.New(" b can't is zero value")
return
}
result = a /b
err = nil
return
}
我们在main中执行。
package main
import (
"errors"
. "fmt"
)
……(此处省略func div)
func main() {
if result, error := div(10, 5); error == nil {
Printf("a / b = %d\n", result)
} else {
Println(error)
}
}
ok,函数一笔带过。闭包之后会看。
struct (模拟面向对象)
当你看到struct是不是想起了C。如下:
#include<stdio.h>
typedef struct student {
char *name;
int age;
} student, *stu;
int main() {
student s1 = {"Mike", 11};
printf("s1 name: %s, age: %d\n",s1.name, s1.age);
stu s2= &s1;
printf("s2 name: %s, age: %d \n",s2->name, s2->age);
return 1;
}
C中的指针访问是要用->(是不是想起了php中的$this->)。
关于struct 可以封装成员函数与方法。我能想到C++的做法。如下:
#include<iostream>
#include<string>
using namespace std;
struct Student {
string name;
int age;
Student(string name, int age);
void intro();
};
Student::Student(string n, int a) {
name = n;
age = a;
}
void Student::intro() {
cout<<"name:"<<name<<" age:"<<age<<endl;
}
int main() {
Student st1 = {"Mike", 11};
st1.intro();
Student* st2 = new Student("James", 12);
st2->intro();
return 0;
}
st1这种写法,在不同的编译环境可能是报错的。
$ g++ -std=c++0x student.cpp -o stu
$ ./stu
name:Mike age:11
name:James age:12
OK,C++代码就不用去看了(大学学过C++可能之后就忘记了)。来看GO。我们还是写个student类。
type Student struct {
name string
age int
}
func(st Student) intro() {
Printf("name: %s, age: %d\n", st.name, st.age)
}
func main() {
var st Student = Student{"Mike", 11}
st.intro()
var st1 Student = Student{age: 12}
st1.name = "James"
st1.intro()
st2 := new(Student)
st2.name = "Prince"
st2.age = 13
st2.intro()
Println(st, st1, st2)
}
new 初始化返回的是一个指针。也就是st2保存的是一个指针。通过最后一行的Println能看出来。于是,你发现了没,C跟C++ 的struct指针都是用->
,但Go还是用.
。
{Mike 11} {James 12} &{Prince 13}
值接收者的方法与指针接收者的方法
假如说我增加一个方法
func (st *Student) setName(name string) {
st.name = name
}
var st Student = Student{age: 11}
st.setName("Mike")
st.intro()
st2 := new(Student)
st2.setName("Akon")
st2.age = 13
st2.intro()
输出
name: Mike, age: 11
name: Akon, age: 13
但如果是
func (st Student) setName(name string) {
st.name = name
}
输出
name: , age: 11
name: , age: 13
所以,指针接收者的方法,用于改变对象状态的情况(你看到,即便s2是指针,但是setName接收者为值接收者也不会改变name值)。另外,指针的好处也在于拷贝一个指针,而不是拷贝一个对象(增大开销)。通常建议使用指针方法。
封装、继承、多态
面向对象的三大特性哈。
封装
说到封装。上面的student的例子就是封装了。但还缺点什么?那就是权限控制。之前我们了解的面向对象的语言中。会了解到public、protected、private(php都还好,C++中最烦,还有公有继承什么鬼的)。那Go中,这三个统统都没有哈。简单也粗暴。大写字母开头就是Public,那小写字母开头的就可以理解为protected了。
怎么理解呢?当我再创建一个文件。
package animal
type Animal struct {
name string
Age int
}
然后引用这个animal。
func main() {
var a = Animal{Age:11}
Println(a.Age)
}
这样访问是可以的。如果Animal是小写的。那么引用的那个文件就会报红。同样如果Age属性或者Animal相关的方法都是小写开头。同样会报红。也就意味着。小写的方式它的作用域仅限于本包的作用域。
“继承”
通过嵌套组合模拟继承。
type Animal struct {
name string
age int
}
func (a Animal) intro() {
Printf("name: %s, age: %d\n", a.name, a.age)
}
type Bird struct {
Animal
category string
}
func (b Bird) intro() {
Printf("name: %s, age: %d, category: %s\n", b.name, b.age, b.category)
}
func main() {
var b Bird = Bird{Animal{"小鸟", 1} ,"文鸟科"}
b.intro()
}
通常想到“继承”,会想到多态(下面说)、重载、子方法里面调用父类方法、
OK,重载是不支持的(当然php中也没有)。你可以把bird方法intro里面传一个参数。然后,b.intro()不变。就发现intro()并不会调用父类animal的无参intro()。
并且不能这样写var a Animal = Bird{Animal{"小鸟", 1} ,"文鸟科"}。对吧,也就是无法通过赋给父类实现多态。同样也不能实现父类访问子类的属性。不像某些语言。就拿php(java、c++也都如此)来说。看下面的代码体会:
<?php
class Animal {
public $name;
public $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
public function intro() {
echo "name: {$name}, age: {$age}\n";
}
public function getCategory() {
return $this->category;
}
}
class Bird extends Animal {
public $category;
public function __construct(string $name, int $age, string $category) {
parent::__construct($name, $age);
$this->category = $category;
}
public function intro() {
echo "name: {$name}, age: {$age}, category: {$category}\n";
}
}
class Human extends Animal {
public $category;
public function __construct(string $name, int $age, string $category) {
parent::__construct($name, $age);
$this->category = $category;
}
public function intro() {
echo "name: {$name}, age: {$age}, category: {$category}\n";
}
}
function show(Animal $a) {
echo '<pre>';
var_dump($a);
var_dump($a->getCategory());
}
show(new Bird('麻雀', 1, '文鸟科'));
show(new Human('智人', 1, '人科'));
所以,Go通过嵌套组合的“继承”不是传统意义上的继承。
“多态”
通过实现接口,模拟多态。先看PHP怎么做。我声明了一个接口Behaviour (当然这个例子不是很好,eat功能是animal必备的)。
<?php
interface Behaviour {
public function eat();
}
class Animal {
public $name;
public $age;
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
public function intro() {
echo "name: {$name}, age: {$age}\n";
}
public function getCategory() {
return $this->category;
}
}
class Bird extends Animal implements Behaviour {
public $category;
public function __construct(string $name, int $age, string $category) {
parent::__construct($name, $age);
$this->category = $category;
}
public function intro() {
echo "name: {$name}, age: {$age}, category: {$category}\n";
}
public function eat() {
echo "eat worms";
}
}
class Human extends Animal implements Behaviour {
public $category;
public function __construct(string $name, int $age, string $category) {
parent::__construct($name, $age);
$this->category = $category;
}
public function intro() {
echo "name: {$name}, age: {$age}, category: {$category}\n";
}
public function eat() {
echo "eat everything";
}
}
function show(Behaviour $a) {
echo '<pre>';
var_dump($a);
var_dump($a->getCategory());
$a->eat();
}
show(new Bird('麻雀', 1, '文鸟科'));
show(new Human('智人', 1, '人科'));
那么,Go呢?既然不能通过赋给父类实现多态,也只能通过interface的这种形式了。
type Behaviour interface {
eat()
}
type Animal struct {
name string
age int
}
func (a Animal) intro() {
Printf("name: %s, age: %d\n", a.name, a.age)
}
type Bird struct {
Animal
category string
}
func (b Bird) intro() {
b.Animal.intro()
Printf("name: %s, age: %d, category: %s\n", b.name, b.age, b.category)
}
func (b Bird) eat() {
Println("eat worms")
}
type Human struct {
Animal
category string
}
func (h Human) intro() {
Printf("name: %s, age: %d, category: %s\n", h.name, h.age, h.category)
}
func (b Human) eat() {
Println("eat everything")
}
func show(b Behaviour) {
b.eat()
}
func main() {
var b Behaviour
b = Bird{Animal{"麻雀", 1}, "文鸟科"}
show(b)
b = Human{Animal{"智人", 1}, "人科"}
show(b)
b = new(Bird)
show(b)
b = new(Human)
show(b)
}
可以看出多态的实现,其实就是我实现了接口的方法就算实现了。而不是需要一个关键字“implements”。(这种实现方式也叫做duck type)
带标签的struct
带标签的struct放到最后讲下咯。
写beego的时候,我发现有struct 的字段的标签总带有json。后来知道这是json序列化的时候用的。
看如下代码(摘自网络):
type Product struct {
Name string
ProductID int64
Number int
Price float64
IsOnSale bool
}
func main() {
p := &Product{}
p.Name = "Xiao mi 6"
p.IsOnSale = true
p.Number = 10000
p.Price = 2499.00
p.ProductID = 1
data, _ := json.Marshal(p)
Println(string(data))
}
打印:
{"Name":"Xiao mi 6","ProductID":1,"Number":10000,"Price":2499,"IsOnSale":true}
但是,我们经常curd拿的数据库的字符串不是大写开头的。很多都是name,product_id这种格式的。那么我给struct加上tag
(标签)。简单的追下代码也大概猜到。通过reflect (反射)拿到tag。然后把json:后面的名字作为字段名。(这段代码可以在encode.go文件,用鼠标点入Marshal进去,typeFields方法中看到处理的过程)
type Product struct {
Name string `json:"user_id"`
ProductID int64 `json:"product_id"`
Number int `json:"number"`
Price float64 `json:"price"`
IsOnSale bool `json:"is_on_sale"`
}
打印
{"user_id":"Xiao mi 6","product_id":1,"number":10000,"price":2499,"is_on_sale":true}
在你不了解访问权限的时候,你可能会想为什么struct中不写成这种,product_id,不就不用tag了呢?注意:大写是public哈。不然小写之后就别人就无法访问了。
扩展了解:关于tag、匿名字段、字段名字冲突、多重继承等可以了解《The way to go》的相关章节。
参考资料:
- 《The way to go》中文版 https://github.com/Unknwon/the-way-to-go_ZH_CN/blob/master/eBook/directory.md
- 《Go语言入门到实战》极客时间,蔡超。(继承、重载,内容部分来自此处)
- 《Google资深工程师深度讲解Go语言》慕课网,ccmouse