當前位置:網站首頁>C語言———指針進階2

C語言———指針進階2

2022-01-27 23:37:00 脫韁的野驢、

目錄

4、數組參數、指針參數:

4.1 一維數組傳參 :

4.2 二維數組傳參 :

4.3 一級指針傳參:

4.4 二級指針傳參:

5、函數指針:


4、數組參數、指針參數:

在寫代碼的時候難免要把【數組】或者【指針】傳給函數,那函數的參數該如何設計呢?

4.1 一維數組傳參 :


int main()
{
	void test(int arr[])//ok?
	{}
	void test(int arr[10])//ok?
	{}
	void test(int *arr)//ok?
	{}
	void test2(int *arr[20])//ok?
	{}
	void test2(int **arr)//ok?
	{}
	int main()
	{
		int arr[10] = { 0 };
		int *arr2[20] = { 0 };
		test(arr);
		test2(arr2);
	}
	return 0;
}

其中,

1、2:

當實參以數組名傳參時,形參可以使用數組接收,也可以使用指針接收。

對於一維數組的話,如果形參使用數組來接收的話,,直接int arr[ ],其中,因為是一維數組,所以元素的個數可以省略,也可以不省略,即使寫錯也沒有問題,因為對

於一維數組來說,這個元素個數無關緊要,因為在形參部分根本就不會去創建數組,所以數組元素個數沒啥用,,數組名傳參相當於是地址傳參,地址傳參就需要使用

指針來接收,雖然形參部分可以使用數組形式來接收,但是它的本質上也是指針形式來接收的,只不過是寫成數組形式接收能够更加方便理解而已,不會真的在形參部

分創建一個數組,而我們形參部分寫成數組形式接收只不過是為了更加容易理解,形參部分本質上是指針進行接收,所以形參部分不會真正的去創建一個數組。

3:

當實參以數組名進行傳參的時候,在這裏數組名不是特例,本質上傳參過去的是數組首元素的地址,所以形參部分也可以用指針的類型進行接收,,又因數組首元素的

類型是int類型,所以使用整形指針變量來接收即可,即使用int* arr進行接收,因為常見的一維,二維數組在內存中都是連續存放的,只要知道數組首元素的地址,後面

得元素的地址就都知道了,所以只需要傳過去數組首元素的地址即可。

而後面的兩個都是對於整形指針數組來說的。

4:

因為我們使用數組類型接收的話,,對於arr就是int arr[ ],而我們arr2可以仿照arr來寫,,int* arr2[20],也可以寫成int *arr2[ ];

5:

因為,我們如果是一維整型數組的話, ,用指針接收就是 int*arr,,所以,我麼這裏的整型指針數組,也可以仿照arr來進行寫,,首先,int變為int*,*不變,arr變為

arr2,,就可以得到,,,,int**arr2;

我們前兩個,是arr用數組類型接收,,第三個是arr用指針類型接收,,第四個是arr2用數組類型接收,,第五個是arr2用指針類型接收,,凡是在形參部分看到了[ ],

一定是用的數組類型接收的;這是對於一維數組來說的,這是因為一維數組,數組名傳參的時候,如果用指針類型接受的話,他是用不到[ ],用不到這個符號的。

最後一個相當於因為arr2裏面存放的都是 int*,整型指針類型,已經是一級指針了,首元素的地址就是一級指針的地址,而一級指針的地址在傳過去就要使用二級指針來

接收,當實參以數組名進行傳參的時候,在這裏數組名不是特例,本質上傳參過去的是數組首元素的地址,所以形參部分也可以用指針的類型進行接收,,又因數組首

元素的類型是int*類型,所以使用二級整形指針變量來接收即可,即使用int** arr進行接收,因為常見的一維,二維數組在內存中都是連續存放的,只要知道數組首元素的

地址,後面得元素的地址就都知道了,所以只需要傳過去數組首元素的地址即可。

4.2 二維數組傳參 :

int main()
{
	void test(int arr[3][5])//ok?
	{}
	void test(int arr[][])//ok?
	{}
	void test(int arr[][5])//ok?
	{}
	//總結:二維數組傳參,函數形參的設計只能省略第一個[]的數字。
	//因為對一個二維數組,可以不知道有多少行,但是必須知道一行多少元素。
	//這樣才方便運算。
	void test(int *arr)//ok?
	{}
	void test(int* arr[5])//ok?
	{}
	void test(int(*arr)[5])//ok?
	{}
	void test(int **arr)//ok?
	{}
	int main()
	{
		int arr[3][5] = { 0 };
		test(arr);
	}
	return 0;
}

其中1.2.3都是形參部分使用數組的形式來接收的情况:

1、2、3:

對於一維數組的話,如果用數組類型來接收的話,,直接int arr[ ],其中,因為是一維數組,所以元素的個數可以省略,也可以不省略,寫錯也沒關系。

對於二維數組來說,,可以仿照一維數組進行書寫,,因為數組是int整型數組,,可以直接寫成,int arr[ 3 ][ 5 ],,也可以寫成int arr[ ][ 5];這裏只能省略行數,不可以

省略列數。

所以,第二種是錯誤的,,第一,三才是正確的;

其中4、5、6、7都是形參部分使用指針的形式來接收的情况:

arr是數組名,代錶數組首元素的地址,而我們的數組arr是一個二維數組,對於一個二維數組來說的話,,它的首元素指的就是第一行,所以數組首元素的地址即指二維

數組第一行的地址,而第一行可以看成是一個一維數組,所以第一行的地址應該存放在一個數組指針中才可以,而4中是一個整型指針,是不可以的,5中由於[ ]的優先

級高於*,,所以arr先與[5]進行結合,形成一個數組,而我們這裏需要使用指針來接收,所以也是不行的,對於6而言是一個數組指針,且是一個整形數組指針,該指針

指向了一個數組,這個數組的有5個元素,每個元素的類型都是int整型,所以正好能够指向我們二維數組中的首元素,即第一行,6是可以的,而7是一個二級指針,二級

指針和數組指針不是一回事,也是不可以的。

4.3 一級指針傳參:

 我們知道,,之前我們都是對數組名進行傳參,,那麼今天將會學習對指針進行傳參:

要知道,,在C語言中,並不是只能對數組名進行傳參,,也可以對指針進行傳參,,如果是數組名傳參,,那麼形參中可以使用數組類型接收,也可以使用指針類型

行接收,因為數組名傳參的時候一般都是代錶數組首元素的地址,本質上是地址進行傳參,所以形參部分可以使用數組或指針類型來接收,同理,這裏的指針傳參,因

為指針就是地址,地址就是指針,按理說對於指針傳參的時候,形參部分也可以使用數組和指針的形式進行接收,,但是對於指針傳參,,我們就一般使用指針進行接

收,很少情况下是使用數組形式來接收的,一級指針傳參就用一級指針變量來接收,二級指針傳參就用二級指針變量進行接收,所以則有:

#include <stdio.h>
void print(int *ptr, int sz)
{
	int i = 0;
	for (i = 0; i<sz; i++)
	{
		printf("%d ", *(ptr + i));
	}
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int *p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一級指針p,傳給函數
	print(p, sz);
	return 0;
}

p是一級指針變量,,一級指針變量傳參,就要用一級指針變量進行接收,,我們就用ptr這個指針變量進行接收,形參中的*就可以說明,ptr是一個指針變量,因為,一

級指針變量p裏面存放的就是數組首元素的地址,,一級指針變量p傳參給ptr,就是把變量p裏面的內容傳給變量ptr,說明,變量ptr裏面存放的就是數組首元素的地址

而ptr是一個指針變量,,且變量ptr裏面存放的還是數組首元素的地址,所以,ptr就指向於數組的首元素的地址。

當一個函數的形參部分為一級指針的時候,函數能接收什麼實參呢?

1、

 2、

 

4.4 二級指針傳參:

#include <stdio.h>
void test(int** ptr) 
{
	printf("num = %d\n", **ptr);
}
int main()
{
	int n = 10;
	int*p = &n;
	int **pp = &p;
	test(pp);
	test(&p);
	return 0;
}

一級指針傳參就用一級指針變量來接收,二級指針傳參就用二級指針變量進行接收;

#include <stdio.h>
void test(int** p2)
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;//pa是一級指針
	int** ppa = &pa;//ppa是二級指針
	//把二級指針進行傳參
	test(ppa);
	printf("%d\n", a);//?
	return 0;
}

首先,ppa是一個二級指針變量,,,現在對二級指針變量進行傳參,即用二級指針變量進行接收,二級指針變量ppa裏面存放的是一級指針變量pa的地址,,所以p2裏

面存放的也是一級指針變量pa的地址,即p2指向pa,函數形參部分的int**p2,,右邊的*代錶p2是一個指針變量,p2又指向pa,pa的類型是int*,,所以得到int**p2,,

現在對p2解引用即 *p2得到的是pa,,再對pa解引用,即對*p2再解引用,即**p2得到的就是a,,賦值為20,所以打印出來的a=20;

我們可以簡記,不需要每一步都推,,當一級指針傳參的時候就用一級變量來接收,,就用一個 *,,當二級指針傳參的時候就用二級指針變量來接收,,就用兩個*,

即**,,,不需要每次都推;

當一個函數的形參部分為二級指針的時候,函數能接收什麼實參呢?

void test(char **p) 
{
}
int main()
{
	char c = 'b';
	char*pc = &c;
	char**ppc = &pc;
	char* arr[10];
	test(&pc);
	test(ppc);
	test(arr);//Ok?
	return 0;
}//都可以
#include <stdio.h>
void test(int** p2)
{
	**p2 = 20;
}
int main()
{
	int a = 10;
	int* pa = &a;//pa是一級指針
	int** ppa = &pa;//ppa是二級指針
	//把二級指針進行傳參
	test(ppa);
	test(&pa);//傳一級指針變量的地址
	int* arr[10] = { 0 };
	test(arr);//傳存放一級指針變量的數組
	printf("%d\n", a);//?
	return 0;
}

對於第三個來說,這是一個整型指針數組,,數組裏面每一個元素的類型都是,int*,,arr是數組名,即數組首元素的地址,所以,數組首元素也是 int* 類型,,把一個

一級指針 int* 類型的地址傳過去,需要用指針變量接收,所以就需要用二級指針來接收;

5、函數指針:

對於所有的初始化,,不管你是什麼類型的變量,還是什麼類型的數組,都可以用0進行初始化,即:int a=0; int arr[10]={0}; char ch=0; char ch[10]={0};

int*arr[10]={0};等等,,都可以用0進行初始化;

整型指針,是指向整型變量的指針,裏面存放的是整型變量地址

字符指針,是指向字符的指針,裏面存放的是字符地址

數組指針,是指向數組的指針,裏面存放的是數組地址

函數指針,是指向函數的指針,裏面存放的是函數地址

數組名 != &數組名

函數名 == &函數名               函數名本身就是地址,函數沒有首元素的概念,

 首先,,這裏的pf是一個函數指針變量,,如果不加(),由於()的優先級會高於*,pf會和後面的()組成一個函數,,然後整體被解引用,即一個函數被解引用,

這是不對的,,為了保證pf是一個指針,,先(*pf),然後是函數指針,後面跟個(),括號裏面是函數的參數,,我們只需要寫出來參數的類型就可以了,如果要寫出類

型,再寫出參數名,也是可以的,,也不是錯誤的,例如:int(*pf)(int x,int y),,也是正確的,兩種都可以,我們一般是只寫出類型就行了, 不需要再寫出參數

的名字了,然後再在(*pf)前面寫出來該函數的返回類型,這樣就可以寫出來函數指針的類型了,與數組指針類型的書寫形式差不多;

首先,我們知道pf是一個函數指針變量,*pf解引用就可以找到該函數,找到該函數之後,這裏的*pf就等價於Add,然後要進行傳參,,,傳參要給個(),即(*pf)後

面的()是函數調用操作符,,,即得到,,(*pf)(3,5),要注意,我們應該是先對 pf 解引用後,然後再傳參,如果按照這樣寫的話,*pf(3,5),,由於()的優

先級高於*,,所以,pf會先和(3,5)結合成一個函數,然後*pf(3,5)對該函數進行解引用,,這是錯誤的,,所以,為了避免因為優先級出現的錯誤,,我們必須先

讓*pf結合,這樣就必須加個括號,得,,(*pf)(3,5),,這樣就把參數傳給了函數,由於函數返回值是一個整型,,所以可以放在一個整型變量ret中,最後打印出

來就是8;

因為對於函數來說,&函數名===函數名,都是代錶函數的地址,如果我把上圖裏面的 &Add換成Add ,結果也是一樣的;

對於: int(*pf)(int,int)=&Add ,可以變為: int(*pf)(int,int)=Add

現在的意思就是把Add函數的地址,放在了一個函數指針變量pf裏面,,所以,對於Add來說,他就是Add函數的地址,對於pf函數指針變量來說的話,它裏面存放的就

是該Add函數的地址,,所以在這裏,,Add===pf;

如果我現在使用Add函數名進行調用,使用最原始的方法應該為:

int ret =Add(3,5);

而現在Add===pf,如果我把上面公式裏面的Add換成pf,按理說結果應該是一樣的,即:

int ret =pf(3,5);

運行後發現,結果是一樣的,,所以我們發現:

int ret =(*pf)(3,5);

int ret = pf(3,5);

這兩者的運行結果都是8,,所以這兩種寫法是一樣的,,是等價的,,

本質上的原因是因為,,解引用的這個*,其實上是一個擺設,沒有起到任何實際作用,就是寫上多個*,結果都是8,都是一樣的,,因為*沒起作用,寫再多也沒有用,

只是讓我們更好的理解,,沒有實際意義;對於這種*無作用的情况只適用於函數的情况,其他的情况是不可以省略*的,在其他情况中,*是會起到實際作用的;

所以:

int ret =(*pf)(3,5);

int ret = pf(3,5);

int ret = Add(3,5);

這三種方式都是可以的,結果都是一樣的。

如果寫成:

int ret = *pf(3,5);

這種寫法是不行的,,本質原因並不是因為pf和(3,5)組成一個函數,然後對函數解引用是不對的,而是因為,pf(3,5)已經可以把函數調用出並過去參數,所以,單

pf(3,5)就已經把函數調用出來並計算出來了結果8,現在再對整體解引用 ,就相當於對數字8解引用,這是不行的, ,所以我們這裏說,對於上面的那個*他是不起作

用的,即使不寫這沒有關系,也能得出來答案,但是,只要是寫上*的話,就必須保證*和pf先進行結合才可以,否則就會出現上述所說的錯誤情况。

例題:

void test()

{

printf("hehe\n");

}

//下面pfun1和pfun2哪個有能力存放test函數的地址?

void (*pfun1)();

void *pfun2();

首先,能够存儲地址,就要求pfun1或者pfun2是指針,那哪個是指針?

答案是:pfun1可以存放。pfun1先和*結合,說明pfun1是指針,指針指向的是一個函數,指向的函數無參數,返回值類型為void,即使該函數沒有參數,也不可以把該函

數的調用函數操作符去掉,去掉就會出現錯誤。

閱讀兩段有趣的代碼:
//代碼1 
(*(void (*)())0)();

//代碼2
void (*signal(int , void(*)(int)))(int);

1、

對於, int(*pf)(int,int)=&Add 來說,,我是把Add的地址取出來放在一個函數指針變量中,,我們知道,如果只有&Add,,就是相當於把Add函數的地址取了出

來,,我們知道,地址就是一個編號,比如0x11223344,,他就是一個16進制的編號,,本質上就是一個數字,就是一個整型數字,,在此,系統就會默認把他看做一

個整型16進制的數字,,只有把該數字放到一個指針變量裏面,因為,我們把Add函數的地址放在了函數指針變量pf中,,pf又是指針類型,即該數字必須是一個指針類

型,系統才會把這個數字看成一個地址,,對於int(*pf)(int,int)=&Add 來說,,我是把Add的地址取出來放在一個函數指針變量中,這樣,系統就會把我們&Add

出來的數字看成函數Add的地址,,如果不放在一個函數指針變量中,,那麼現在的&Add就是一個整形數字;

對於 : (*(void(*)()) 0)();

我們知道, int(*pf)(int,int)=&Add,,pf就是一個函數指針變量,,如果把該指針變量的名稱即pf去掉,得到: int(*)(int,int),這個東西就是我們函數指針

變量的類型,對於數組指針也是一樣,,如果是 int(*pa)[10],,其中,pa就是一個數組指針變量,,如果把數組指針變量的名稱去掉得到: int(*)[10],這就是我

們數組指針變量的類型。

對於,數組來說,如果 int arr [10];這是一個整型數組,,如果把 數組名arr和[ 10] 去掉得到:int,這就是我們數組arr每個元素的類型。

現在這個題: (*(void(*)()) 0)();

我們從0入手,,0是一個數,他是一個int整型的數字,,,如果只把它放在代碼中,,系統就會認為他就是一個整型int數字,,現在,我如果想把這個數字0看作一個地

址,,就必須把他放在一個指針變量即放在一個指針類型中去,,這樣系統就會把該整型數字0看做一個地址,,現在沒有那種把地址放在一個變量中的代碼,,所以就

要想到强制類型轉換,,(void(*)())0,,只要能够把0轉換成一種函數指針類型,那麼系統就會把0看成一個函數的地址,就要把他强制類型轉換為一種函數指針類型

就可以了,,我們只看void(*)(),,這個整體就是一個函數指針的類型,,假設函數指針變量為p得到:void(*p)(),,(*p)就是錶明p就是一個指針,,然

後後面有個括號,代錶的是函數指針變量,,參數裏面無內容,,所以這個函數是無參的函數,前面的void說明這個函數不需要返回類型,,如果把函數指針變量p去

掉,得到的是:void(*)(),,這就是一個函數指針變量的類型,,强制轉換就是在某個東西前面加個(),括號裏面寫上類型,,那麼,void(*)()這是一個函

數指針類型,,void(*)()0,,這就代錶把數字0,强制類型轉換為,void(*)()這一個類型,,這樣系統就會把整形數字0看做是一個函數的地址,,

所以,void(*)()0 這個整體就會被系統認為是一個函數的地址, int(*pf)(int,int)=&Add,我們在這裏,如果對pf進行解引用,即*pf就可以找到函數

Add,,對函數指針變量解引用的本質就是對地址解引用,,因為函數Add的地址放在了函數指針變量pf中,對該函數指針變量解引用,本質就是對Add函數的地址解引

用,,所以,現在,,void(*)()0 這個整體就是一個函數的地址,,如果對該地址解引用,即: *void(*)()0,,,因為*的優先級低於(),所以,

先等()進行完,而强制類型轉換的括號和後面的內容0是一個整體,,所以就會先强制類型轉化,然後再解引用,,所以,解引用的時候不需要再加個大括號把*後面

的內容括起來,,所以,*void(*)()0這個整體出來的就是函數,,函數出來後要進行傳參,,所以在這後面加括號進行傳參,*void(*)()0(),,

void(*)()0是一個整體,,這裏不會被分開,,假設為M,即得到了,*M(),,如果就這樣的話,*的優先級低於(),,所以,M會和()構成一個函數,

然後再對函數解引用是不合適的,,所以,為了保證先解引用,再傳參,,要加一個大括號,讓*先和M結合,然後傳參,,即的到:(*M)(),,這樣就把函數調用

出來並進行傳參了

比如這裏的(*pf)(3,5)就是根據函數Add(int x,int y)來寫的,,,所以我們這裏的(*M)(),後面的()就是用來傳參的,因為0這個地址對應的函數是無參

的,,所以我這裏也是無參的,就用一個括號錶示即可;

最後得到的結果為:(*void(*)()0)();這就是調用以0為地址的函數,該函數無參,,返回類型為void,此時這裏的0就等價於函數指針變量p裏面的內容,

即地址,,兩者的類型是一樣的。

但是,比如,我知道一個函數的地址為0x0012ff40,現在想通過上面的方法來調用該函數,,能不能通過(*(void(*)())0x0012ff40)();來調用我這裏的函數

呢?

答案是肯定不行的,,因為系統給分配地址不是固定的,,現在是這個0x0012ff40,下一次可能就不是這個地址了,,僅僅通過地址來調用函數是不行的,,因為每一

次分配,地址都在發生變化,,不是固定不變的,我們不會直接把一個常量的值作為地址去使用;

我們自己的電腦上面,,0地址是不可以使用的,,因為系統把這些低地址都分配給系統去使用了,我們自己無法使用;

2、

對於一個數組來說,如果去掉數組名剩下的部分就是該數組的類型,如果去掉數組名和[n],剩下的就是數組裏面每個元素的類型。

對於一個函數來說,如果去掉函數名和函數名後面的參數部分,則剩下的就是該函數的返回類型,如果只去掉函數名,則剩下的部分就是該函數的類型,但是對於函數的

類型一般用的不多,記住函數的返回類型即可。

從signal這個名字進行突破,前有*後有(),,因為()的優先級高於*,,所以,signal()結合成為一個函數,即signal函數,,所以,signal是一個函數名,,,其

中,,int和void(*)(int)是signal函數的兩個參數的類型,其中,void(*)(int)是一個函數指針類型,然後,signal(int,void(*)(int) )的返回類型就是,,void

(*)(int),,是一個函數指針類型,,而signal(int,void(*)(int) )是一個函數指針變量;

 

signal既不是調用函數,也不是定義函數,名字,參數類型,函數返回類型都給了,這是一次函數的聲明;

等價於:

但是系統是不允許這樣操作的,,會報錯,,如果想讓一個函數的返回類型為一個函數指針類型,,那麼就必須要保證,返回類型中的*與函數名挨著,靠在一起,並且

是 * signal 這樣靠在一起,順序不可顛倒。

但是系統是不允許這樣操作的,,會報錯,,如果想讓一個函數的返回類型為一個函數指針類型,,那麼就必須要保證,返回類型中的*與函數名挨著,靠在一起,並且

是 * signal 這樣靠在一起,順序不可顛倒,,對於,一個函數指針來說的話,,例如:int(*p)(int,int)=&Add/Add,,就是把Add函數的地址放在函數指針變量p裏

面,由於,()的優先級高於*,,所以,(*p),先讓*與p結合,,說明,p是一個指針,往後走看到(),,說明p是一個函數指針,,函數指針p指向的這個函數,

有兩個參數,每個參數的類型是int整型,,且該函數返回類型是int類型。

我們這裏的 void(*)(int) signal(int, void(*)(int)),,系統必須讓這兩個靠在一起,,所以就只能把)(int)拿到該行代碼的最後面,既得到了:

void(* signal(int, void(*)(int)))(int) 這樣寫才不會報錯,,直接把藍色部分拿到最後即可;

所以只能寫成第一個形式,但是第一種形式理解起來比較困難,有沒有一個更好的方法,即能讓我們理解起來容易,還能不讓程序報錯呢?

typedef-對類型進行重定義或者是重命名;

我們看到上面的代碼:void(*)(int) signal(int, void(*)(int))裏面有兩個函數指針類型,,搞起來不方便,        只能寫成void(* signal(int, void(*)(int)))(int)這種形式;

我們的typedef 的用法一般是: typedef unsigned int unit 這個代碼的意思就是說,給unsigned int

重新起個名字叫做unit,,在以後如果寫unit就代錶寫了unsigned int

這代碼裏面沒有*,,所以可以這樣定義,但是對於這種函數指針類型重定義,重命名的時候,裏面有*,即

typedef void(*)(int) pfun_t 如果這樣重定義,按理說是可以的,就是用pfun_t來代替void(*)(int),,但是這樣系統就會報錯,

 這是因為我們使用的函數指針類型裏面有*,,一般在重定義,重命名的時候,如果被替代者中有星號,,就要把替代者的名字靠著星號,即

typedef void(*pfun_t)(int)即:

按照順序靠近過去即可,,這樣系統就不會再報錯了;

然後拿pfun_t就可以把void(*)(int) signal(int, void(*)(int))中的,void(*)(int)替換掉了,就得到了:

pfun_t signal(int, pfun_t),,,,這樣就可以了,現在,,

typedef void(*pfun_t)(int) + pfun_t signal(int, pfun_t) === void(* signal(int, void(*)(int)))(int)

注意:如果寫成: void (*p)(int),則此時的p是函數指針變量名,如果寫成:

typedef void (* pf_t)(int),其等價於 typedef void(*)(int) pf_t ,相當於是把void(*)(int)重新定義成pf_t,,此時,pf_t是一種新的類型名,並不是一種函數指

針變量名。

再如:

typedef struct Stu

{

char name[20];

int age;

}Stu;

此時的typedef 就把上述複雜類型重新命名為Stu,接下來直接拿著Stu去使用即可。

未完待續!

版權聲明
本文為[脫韁的野驢、]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201272336596131.html

隨機推薦