首頁 > 軟體

go語言中值型別和指標型別的深入理解

2022-03-01 19:01:00

golang這個語言用起來和java、 c#之類語言差不多,和c/c++差別比較大,有自動管理記憶體機制,省心省力。

然而,如果寫golang真的按寫java的習慣去寫,也容易出問題,因為golang中有指標的概念,雖然這個指標是c/c++的自動化版本,但是卻也有指標的特徵,如果不熟悉其中原理,寫出來的程式雖然不至於有執行BUG,效能卻不友好。

因此,不能完全以寫java的思路去寫golang,一定要注意其中差別。

我們知道,在java之中,除了基本型別之外,所有的變數型別都是參照型別,你可以隨意的將參照當作引數傳遞,也可以將參照當作返回值返回,都不會有任何問題。

public class Main {
    static class Person{
        private String name;
        private String addr;
        private int age;

        public void addAge() {
            age ++;
        }
    }

    private static Person addAge(Person person) {
        person.addAge();
        return person;//可以這麼返回,沒任何問題
    }

    public static void main(String[] args){
        Person person = new Person();
        addAge(person);//可以這麼呼叫,沒任何問題
    }
}

如果你沒寫過c/c++,會覺得這一切顯得這麼自然,彷彿這是最常規的操作。然而如果你寫過c/c++,就會發現這麼寫並不是常態,而是非常美好的事情,在c/c++裡面必須避免這麼寫。

class Person
{
private:
	string name;
	string addr;
	int age;

public:
	void addAge()
	{
		this->age++;
	}
};

Person addAge(Person person)
{
	person.addAge();
	return person; //不能直接返回,會拷貝person物件
}

int main()
{
	Person person;
	addAge(person);//不能直接傳遞,會拷貝person物件
}

如上面程式碼所示,如果將person物件直接傳遞或者返回,會拷貝物件中的資料,產生額外的開銷,因為這是按值傳遞的模式。在java中也有這種按值傳遞的拷貝,但是隻會在基本型別上起作用,而基本型別體積很小,long才8個位元組,int 4個位元組,物件都是按參照傳遞。

在c++中解決這個問題不止一種手段,但是寫出來的程式碼都非常蹩腳難看。在這裡我們用指標來解決這個拷貝問題

class Person
{
private:
	string name;
	string addr;
	int age;

public:
	void addAge()
	{
		this->age++;
	}
};

Person* addAge(Person* person)
{
	person->addAge();
	return person; //可以返回,不會拷貝整個物件,只會拷貝指標(8位元組)
}

int main()
{
	Person person;
	addAge(&person);//取地址後傳遞, 不會拷貝整個物件,只會拷貝指標(8個位元組)
	//或者
	Person* pPerson = new Person;
	addAge(pPerson);//直接傳遞指標
  delete pPerson;//動態分配必須刪除,否則有記憶體洩露風險
}

c++的做法是不是比java費事的多,所以平時我們吐槽java語法臃腫被c#、person、kotlin呼叫,而它卻能吊打c++,因為c++能讓你好好的傳遞引數和返回值都做不到。

golang整體的機制雖然偏向於java的易用性,而在變數傳遞返回這一塊,卻繼承了c++的習慣,區分按值傳遞和按指標傳遞,如果寫程式碼的時候值和指標不分,雖然程式不會報錯,但是卻會產生額外的拷貝開銷,對效能不友好。

type Person struct {
	name string
	addr string
	age int
}

func (this* Person) addAge()  {
	this.age++
}

func addAge(person Person) Person  {
	person.addAge()
	return person //不能直接返回,會拷貝person物件
}

func main()  {
	person := Person{}
	addAge(person)//不能直接傳遞,會拷貝person物件
}

上面的程式碼就是個錯誤示範,在java中這麼寫完全沒問題,在golang中卻不行,因為這是按值傳遞,會拷貝物件,就跟c/c++一樣。

type Person struct {
	name string
	addr string
	age int
}

func (this* Person) addAge()  {
	this.age++
}

func addAge(person* Person) *Person  {
	person.addAge()
	return person //可以返回,不會拷貝整個物件,只會拷貝指標(8位元組)
}

func main()  {
	person := Person{}
	addAge(&person)//取地址後傳遞, 不會拷貝整個物件,只會拷貝指標(8個位元組)
  //或
  person1 := new(Person)
	addAge(person1)//直接傳遞指標, 不會拷貝整個物件,只會拷貝指標(8個位元組)
}

這是這是正確的使用方式,按指標傳遞,就跟c/c++一樣。

於此同時,當你直接使用golang內建的map或者切片型別,不用擔心這個問題,因為make出來的map或者切片,預設就是指標型別,傳遞和返回時不會按值拷貝。

func doSome(input map[string]string) map[string]string  {
	input["hello"] = "world"
	return input //可以直接返回,不會按值拷貝,map預設是一個指標
}

func main() {
	data := make(map[string]string,5)
	doSome(data) //可直接傳遞,不會按值拷貝,map是一個指標
}

所以,如果你從java轉到golang,同時又沒有寫過c/c++,那麼要萬分注意這個問題,千萬不能完全以寫java程式碼的習慣去寫go,否則在迴圈中出現大物件只拷貝,會是效能毒藥。

總而言之,golang這門計算機語言,同時具有java和c/c++的特徵,要能好好使用,需要有這兩門語言的基礎。

總結

到此這篇關於go語言中值型別和指標型別的文章就介紹到這了,更多相關go語言值型別和指標型別內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com