【C語言】c語言基礎知識梳理(超全)

零、概述


寫此篇博客的目的主要是爲了梳理一下c語言基礎知識,因爲不會像學c語言那樣有很多例子,或者按照初學c語言的順序,所以不推薦剛學習c語言的人閱讀。
本篇文章有什麼:

  1. 對c語言系統的梳理
  2. 對一些使用的細節進行梳理
  3. 解答對使用c語言時產生的一些疑惑
  4. 本篇內容基於“C語言程序設計-浙江大學-翁愷”、“C++ Primer”部分內容以及個人理解進行梳理,不充分之處歡迎指出


一、變量和基本類型


(一)基本類型

1、有符號常見類型大小及其範圍

1byte(字節)=8bit(位);每個bit就是一個0或者1,byte是c語言裏面數據的最小單位

常用2的次方:
27=128
28=256
215=32,768
216=65,536
231=2,147,483,648‬
232=4,294,967,296
263=9,223,372,036,854,775,808‬
264=18,446,744,073,709,551,616

類型 含義 32位編譯器中大小(一般) 64位編譯器中大小(一般) 最小值(32位) 最大值(32位)
bool(stdbool.h) 布爾類型 1byte 1byte false true
char 單個字符 1byte 1byte -27 27-1
short 短整形 2byte 2byte -215 215-1
int 整形 4byte 4byte -231 231-1
long 長整形 4byte 8byte -231 231-1
long long 長整形 8byte 8byte -263 263-1
float 單精度浮點數 4byte 4byte -2127 2128
double 雙精度浮點數 8byte 8byte -21023 21024
long double 擴展精度浮點數 12byte 16byte -216383 216384
char* 字符常量或字符串常量 4byte 8byte 無意義 無意義


2、浮點數範圍來由及其有效數字

這裏單獨討論一下浮點數的取值範圍,浮點數都遵循IEEE754標準,所以:
4*8=32位的float的第1位是符號位,第2~9位有8位是指數位,第10~32位有23位是尾數位

那麼可以很容易看到float的範圍是[-1*2127≈1.7*1038, 1*2128≈3.4*1038]

因爲轉換成IEEE754都要進行標準化,也就是將原來的整數點整數的形式轉化成二進制點二進制的形式,然後將點的位置移動到左邊第一個1(2)之後,產生指數位。規定點之後的數填在尾數位上,所以31位前暗含了1(2),所以上面的範圍會先乘個1。
32位的指數位 = 移位數(左移一位+1,右移一位-1)+ 127得出。所以負數只有127(127-127=0),而正數有128(127+128=255)


8*8=64位的double的第1位是符號位,第2~12位有11位是指數位,第13~64位有52位是尾數位
那麼可以很容易看到float的範圍是[-1*21023≈8.988*10307, 1*21024≈1.797693*10308]

12*8=96或16*8=128位的long double的第1位是符號位,第2~16位有15位是指數位,在32位系統中第17~96位有80位是尾數位,在64位系統中第17~128位有112位是尾數位,那麼範圍是[-1*216383≈5.9*104931, 1*216384≈1.1897*104932](與尾數大小無關)。

其實float的範圍就已經非常非常大了,那爲什麼還會有double甚至long double呢,這是因爲浮點數能準確記錄量級,但是無法準確記錄太長的數字,數字的有效位數值取決於尾數位的長度,我們可以總結成下表:

類型 有效位數計算 有效位數
float 223+1=16,777,216 8-1=7
double 252+1=9,007,199,254,740,992‬ 16-1=15
long double 280+1=2.4*10 24 / 2112+1=1.038*1034 25-1=24 / 35-1=34


3、字面值常量

(1)十進制字面值

對於一個20(10)=24(8)=14(16),在c語言中對應表達爲24(10進制)、024(8進制)、0x14(16進制)。
默認情況下,十進制字面值的類型是int,如果int裝不下就是long,再裝不下就是long long。

(2)浮點數字面值

對於一個浮點數可以表示爲314.159(10)也可以表示爲3.14159e+2(科學記數法)。
默認情況下,浮點數字面值是一個double

(3)指定字面值類型

整形字面值
後綴最小匹配類型例子
u / Uunsigned20u / 20U
l / Llong20l / 20L
ll / LLlong long20ll / 20LL
浮點形字面值
f / Ffloat20.0f / 20.0F
l / Llong double20.0l / 20.0L

4、字符和字符串常量

形如’a’"HelloWorld"都可以看作是常量,特別說明當例如char* s1="HelloWorld";char* s2="HelloWorld";時,s1和s2所指的都是內存中代碼段的常量,可讀不可寫且地址相同,類似於const char* s1;

字符是以單引號括住的單個字符,只佔一個char(一個byte)
字符串以雙引號"括住一系列字符,最後隱含了’\0’,所以佔n+1個char(n爲字符串有意義的長度)

特殊的常量:無窮,每個編譯器的表示都不一樣,可以使用如下代碼輸出:

printf("%f\n", 1/0.0);  // 無窮大。我的電腦上爲:1.#INF00
printf("%f\n", -1/0.0);  // 無窮小。我的電腦上爲:1.#INF00
printf("%f\n", 0.0/0.0);  // 空。我的電腦上爲:-1.#IND00


5、總結

有符號的類型(除浮點數)的範圍都是由[-2n-1, 2n-1-1](n爲類型所佔bit)
無符號的類型(除浮點數)的範圍都是由[0, 2n-1](n爲類型所佔bit)
浮點數的範圍與其指數位大小有關,爲[-2{[2^(m-1)]-1}, 2[2^(m-1)]](m爲指數位位數)


(二)變量

1、定義變量

變量由一個類型聲明符一個或多個變量名組成的列表(c99及以後可以在函數內任意位置定義變量),例如:

int a;  // a是int類型的變量
double b=1.0;  //b是double類型的變量,賦1.0爲初值
char c, d;  // c, d是char類型的變量


2、常用特殊前綴

extern int a;  // 聲明一個int類型的變量a,一般用在.h文件中聲明項目全局變量
const int b=1;  // 定義一個不可變的變量b,使用const關鍵字都要賦初值
static int c=1;  // 在函數中使用,只在第一次使用時初始化,相當於作用域在函數內的全局變量
typedef long long int;  // 把long long當作int使用


3、數組與初始化變量

int a=1;  // 給a初始化爲1
// 以下爲C99加入的特性
int b[10] = {1};  // 第一個元素初始化爲1,其他9個元素初始化爲0
int c[10] = {0};  // 全部初始化爲0,常用初始化手段
int d[10] = {[1]=1, 2, [4]=4};  // 0 1 2 0 4 0 0 0 0 0


4、變量作用域

變量有兩種類型:局部變量全局變量,變量作用域即變量可使用的範圍。

對於局部變量可以簡單理解爲大括號{}內即爲一個作用域,變量在哪個大括號內,作用域就在哪。

對於全局變量的作用域就是在當前的.c文件中,在.h文件聲明後可以在整個項目內使用。

對於同名變量,小作用域的變量會在其作用域中覆蓋大作用域的變量。


(1)局部變量與全局變量的差別

差別因素 局部變量 全局變量
初始值 初始值取決於內存裏的數(隨機) 一般爲0,指針爲null
作用域 僅限於大括號內 .c文件甚至項目


(三)字符串

字符串在c語言中有兩種形式char*和char[],這裏簡單分辨下兩者之間的不同。
char* c1="HelloWorld!"中,c1指向代碼段中的常量,只讀不寫,且常量相同,指向的地址也相同。
char c2[]="HelloWorld!"中,c2指向堆棧段中的數據,可讀可寫,相當於把代碼端的數據拷貝了出來。


(四) 自定類型——結構體

結構體可以看作是一種個基礎類型複合的類型。

// 聲明如下:
struct DATE{
	int year, month, day;
};

// 幾種定義方式如下:
struct DATE date1;  // 不賦初值
struct DATE date2={2020, 4, 5};  // 根據結構體中的順序賦初值,這裏是年月日
struct DATE date3 = {.year=2020, .day=5};  // 給單獨變量賦初值
struct DATE *date4 = &date3;  // 用指針取date3地址(指針後面會單獨說)

// 幾種賦值方式如下:
date3 = (struct DATE){.year=1999};  // 將數據強制轉換成struct DATE類型賦值
date3 = date2;  // 自動賦值
date3.year=2010;  // 對變量中單一元素賦值
date4->year=2010;  // 對指針所指變量中的單一元素賦值

// 常用聲明
typedef struct DATE2{
	int year, month, day;
} D;
D date5;  // 這樣就可以不用寫struct DATE2這麼一長串,取而代之用D來表示

擴展:union
union作爲關鍵字與struct類似,但是struct中每個成員都是單獨的內存,而union只佔最大成員變量的大小,通常用與二進制與十進制的轉換。

union DATE{
    int year;
    int month;
    int day;
};  /* 一個union DATE只有一個int大小 */

擴展:enum枚舉,枚舉類似#define,但是有體系些(c語言的枚舉不好用)

// 聲明枚舉, 默認下標RED:0, YELLOW:1, GREEN:2
enum COLOR1 {RED1, YELLOW1, GREEN1};
// 自定下標
enum COLOR2 {RED2=1, YELLOW2, GREEN2=5};



二、表達式


這裏給出運算符優先級

優先級運算符名稱或含義使用形式結合方向說明
1[]數組下標數組名[整型表達式]左到右
()圓括號(表達式)/函數名(形參表)
.成員選擇(對象)對象.成員名
->成員選擇(指針)對象指針->成員
2-負號運算符-算術類型表達式右到左單目運算符
(type)強制類型轉換(純量數據類型)純量表達式
++自增運算符++純量類型可修改左值表達式單目運算符
--自減運算符--純量類型可修改左值表達式單目運算符
*取值運算符*指針類型表達式單目運算符
&取地址運算符&表達式單目運算符
!邏輯非運算符!純量類型表達式單目運算符
~按位取反運算符~整型表達式單目運算符
sizeof長度運算符sizeof 表達式 / sizeof(類型)
3/ 表達式/表達式 左到右雙目運算符
*表達式*表達式雙目運算符
%餘數(取模)整型表達式%整型表達式雙目運算符
4+表達式+表達式左到右雙目運算符
-表達式-表達式雙目運算符
5<<左移整型表達式<<整型表達式左到右雙目運算符
>>右移整型表達式>>整型表達式雙目運算符
6>大於表達式>表達式左到右雙目運算符
>=大於等於表達式>=表達式雙目運算符
<小於表達式<表達式雙目運算符
<=小於等於表達式<=表達式雙目運算符
7==等於表達式==表達式左到右雙目運算符
!=不等於表達式!= 表達式雙目運算符
8&按位與整型表達式&整型表達式左到右雙目運算符
9^按位異或整型表達式^整型表達式左到右雙目運算符
10|按位或整型表達式|整型表達式左到右雙目運算符
11&&邏輯與表達式&&表達式左到右雙目運算符
12||邏輯或表達式||表達式左到右雙目運算符
13?:條件運算符表達式1? 表達式2: 表達式3右到左三目運算符
14=賦值運算符可修改左值表達式=表達式右到左
/=除後賦值可修改左值表達式/=表達式
*=乘後賦值可修改左值表達式*=表達式
%=取模後賦值可修改左值表達式%=表達式
+=加後賦值可修改左值表達式+=表達式
-=減後賦值可修改左值表達式-=表達式
<<=左移後賦值可修改左值表達式<<=表達式
>>=右移後賦值可修改左值表達式>>=表達式
&=按位與後賦值可修改左值表達式&=表達式
^=按位異或後賦值可修改左值表達式^=表達式
|=按位或後賦值可修改左值表達式|=表達式
15,逗號運算符表達式,表達式,…左到右從左向右順序結合
總結優先度: 特殊運算符(括號、地址類) > 單目運算符 > 算數運算符(乘除>加減) > (移位運算) > 判斷運算符(比較>相等) > (位運算) > 邏輯運算符(&&>||) > 三目運算符 > 賦值運算符 > 逗號運算符

針對十進制字面值
<<:左移數據,相當於乘2(右邊填0)
>>:右移數據,相當於除2(unsigned左填0,signed左邊填原來的最高位(因爲負數移位還要是負數))



三、語句


簡單語句;,只有一個分號的空語句。
複合語句:由{}大括號嵌套起來的語句,空塊(括號裏什麼都沒用)等於空語句。

(一)條件語句

1、if語句

if語句一般格式:if…else if…else(翻譯爲如果…否則如果…否則)

if (age>18) {  // 如果年齡大於18歲
} else if(age<18) {  // 如果年齡小於18歲
} else {  // 上面的都不滿足
}

可用搭配:
if...
if...else if...
if...eles...
if...else if...else if...else...

tips:
(1)if依據後面的條件語句的結果進行判斷,非0爲真,0爲假
(2)else不需要接條件
(3)if語句後面不要接;


2、switch語句

switch語句一般格式:

switch(ch){  // switch是傳入整數並判斷整數進行判斷
	case 'a':
		...
		break;
	case 'b':
		...
		break;
	default:
		...
}

tips:
(1)case只是入口,所以每個case都要用break;跳出
(2)default類型if語句中的else,即上面的case都不滿足時進入
(3)switch只能傳入整數進行判斷,上面的例子就是將char類型的ch轉換成了ASCII碼進行比較
(4)switch後面不要接;


(二)迭代語句

1、while語句

while語句的一般格式:

while(條件){
	語句
}

tips:
(1)while適用於不知道次數的循環
(2)while後面不要接;


2、do while語句

do while語句的一般格式:

do{
	語句
}while(條件);

tips:
(1)do while適用於不知道次數且要先做一遍的循環
(2)do while後面要接;
(3)do while後面要接;
(4)do while後面要接;


3、for語句

for語句的一般格式:

for (初始化變量; 條件; 變量操作) {
	語句
}

int n=10;  // 定義循環次數
// 例子1,循環n次
for (int i=0; i<n; i++) {
	;
}
等價於
int i=0;
while(i<n){
	;  // 語句
	i++;
}

// 例子2,多參數
for (int i=0, j=10; i<n; i++, j--) {
	;
}
等價於
int i=0, j=10;
while(i<n){
	;  // 語句
	i++;
	j--;
}

tips:
(1)for適用於知道次數的循環
(2)for後面不要接;
(3)for可以壓縮行數,簡化部分使用while的情況


(三)跳轉語句

break;直接跳出當前條件語句或迭代語句,能且只能 跳出 當前一個 語句。

continue;條件語句 中表示什麼都不做(替代空語句);在 迭代語句 中表示 直接進入下一輪循環,不執行完當前循環

goto;跳轉到指定位置(該位置必須有聲明),例子:

start:  // goto聲明
	...  // 語句
	...  // 語句
	...  // 語句
	goto start;  //跳轉到start聲明位置

!!!注意,goto僅推薦用於跳出多重循環,例如:

for (int i=0; i<n; i++) {
	for (int j=0; j<m; j++) {
		for (int k=0; k<o; k++) {
			if (跳出條件) {
				goto end;
			}
		}
	}
}
end:



四、函數


(一)函數一般格式

返回值類型 函數名(形參列表) {
	...語句...
	return 返回值;
}
// 例子,main函數
int main(){
	return 0;
}

tips:
(1)特殊的返回值類型void(什麼都不返回),此時return 返回值;寫爲return;
(2)形參列表爲空時,表明不需要傳入函數。


(二)函數聲明

函數聲明一般寫在程序開頭。儘管部分c語言編譯器沒有要求在調用函數時,就要知道函數格式。但是讓編譯器提前知道格式,就可以在編譯時進行檢查,防止我們錯誤使用了函數。

有一函數定義如下:

int f(int a){
	return a;
}

那麼函數的聲明可以寫成:

int f(int a);
或者
int f(int);

特殊的對於返回值和參數都是void的函數,聲明可以寫成:

void f(void);


(三)結構體中的函數

如果能在結構體中寫函數,那麼結構體就更接近一個類了,例子如下:

struct SHOWDATE{
	void (*show)(int year, int month, int day);
};

static void show(int year, int month, int day){
	printf("%d-%d-%d", year, month, day);
}

static SHOWDATE showDate={.show=show};

int main(){
	showDate.show(2020, 1, 1);
	return 0;
}



五、指針


(一)定義

int a=10;
int *p=&a;  // (1)(2)
*p = 12;  // (3)
int * const p1;  // (4)
const int * p2;  // (5)
int const * p3;

(1)*代表這是一個指針類型。具體到例子中就一個int*類型,代表p存放一個指向int類型的地址

(2)&代表取地址。在例子中就是取int類型變量的地址,即變量a的地址

(3)*p代表一個變量,這個變量就是p所指地址裏面的變量

(4)int * const p1;不能修改p裏的地址

(5)const int * p2;int const * p3;不能修改p裏地址對應的值


(二)結構體與指針

typedef strcut stDATE{
	int year, month, day;
} DATE;

int main(){
	DATE date1={2020, 1, 1};
	DATE* date2=&date1;
	date1.year=2019;
	date2->month=2;
	printf("%d-%d-%d", date1.year, date1.month, date1.day);
	
	return 0;
}

(1)結構體變量訪問結構體成員使用.,結構體指針變量訪問結構體成員使用->


(三)數組與指針

int a[10] = {0}  // (1)、(2)

int *p = a;  // (3)

void *q = (void*)p;  // (4)

int *new = (int*)malloc(10*sizeof(int));  // (5)
free(new);

(1)int a[10]裏面的a實際上是數組第一個變量的地址,所以可以int *p = a;
(2)*(p+n)a[n]等價。
(3)int*指針變量加上一個數n,代表指針變量的數值加上n*32(int的bit)得到偏移。
(4)q表示未定的類型(類似char*但不一樣)。
(5)使用int*指針變量來創建數組,使用完要free(程序關閉會自動釋放,如果程序長時間使用則會造成內存泄露)。

其他:
char c[][]錯誤;
char c[][10]正確且限制大小;
char *c[]正確,c[n]是一個char*的字符或字符串
char **c正確,一個指向指針的指針



六、文件讀取


(一)文本文件

函數定義:

FILE* fopen(const char* restrict path, const char* restrict mode);  // 打開文件。參數:路徑,模式
int fclose(FILE* stream);  // 關閉文件
fscanf(FILE*, ...);  // 文件讀取(類似scanf,只是加了第一個參數,具體在八)
fprintf(FILE*, ...);  // 文件輸出(類似printf,只是加了第一個參數,具體在八)

fopen的mode選項:
r:只讀
r+:讀寫,從文件頭開始
w:只寫,清空或新建
w+:讀寫,清空或新建
a:追加,追加或新建
…x:只新建,若存在則不能打開(wx、ax等)

例子:

FILE* p=fopen("file", "r");
if(fp) {
	...
	fclose(fp);
}


(二)二進制文件

函數定義(打開文件和關閉文件與文本文件一致):

// ptr:讀入字符串存放的變量、size*nmemb:讀多少字符、stream:文件句柄
size_t fread(void* ptr, size_t size, size_t nmemb, FILE* stream);
size_t fwrite(const void* ptr, size_t size, size_t nmemb, FILE* stream);

二進制文件讀寫主要是讀寫結構體,現在不常用,因爲可移植性不好,不如讀寫文件



七、編譯預處理指令與.h文件


(一)編譯預處理指令

在c語言中,所有#開頭的語句都是編譯預處理指令。
c語言編譯經過
.c——處理編譯預處理指令——》
.i——c編譯器編譯成彙編文件——》
.s——生成目標代碼文件——》
.o——生成可執行文件——》
#.out / .exe
四步得到可執行文件,第一步就是處理編譯預處理指令。

1、# include <stdio.h>

# include <stdio.h>就是將stdio.h文件裏面的內容複製到代碼裏面去


2、#define 名稱 操作(宏定義)

# define被稱爲宏定義,格式爲# define 名稱 操作
例子:

#define PI 3.14159
#define cube(x) (x*x*x)

int main(){
	cube(3);
	return 0;
}

可以用宏定義定義一個變量PI或一個方法cube

宏定義定義方法看起來很方便,但十分不推薦初學者使用,因爲宏定義的方法是沒有類型的,而且運算順序也不好理解(看上面 二、表達式 15個運算等級的表,就知道多複雜了)。

如果確實想用類似宏定義的方式來定義函數,可以使用下面的方法。


3、(補充)inline內聯函數

inline char *dbtest(int a)
{
    return (a % 2 > 0 ? "奇" : "偶");
}


(二).h文件

.h文件主要用來聲明對應.c文件裏面的變量或函數,把.c文件裏面的聲明放到.h文件就可以了(全局變量需要加上extern前綴)。

導入標準庫時推薦使用<>,如# include <stdio.h>
導入自定聲明時推薦使用"",如# include "train.h"


(三)一些常用宏

printf("%d", __LINE__);  // 輸出當前語句行號
printf("%s", __FILE__);  // 輸出當前文件名
printf("%s", __DATE__);  // 輸出編譯日期
printf("%s", __TIME__);  // 輸出編譯時間
printf("%d", __STDC__);  // 當要求程序嚴格遵循ANSIC標準時該標識符被賦值爲1
printf("%s", __FUNC__);  // 輸出當前函數名



八、常用函數


(一)常用函數

size_t = unsigned int

庫名 函數定義 解釋
默認 size_t sizeof(type_name) 返回byte
string.h size_t strlen(const char *s) 返回s的字符串長度(不包括最後的\0)
string.h int strcmp(const char *s1, const char *s2) 比較兩個字符串,返回0:相等; 1:s1>s2; -1:s1<s2
string.h int strncmp(const char *s1, const char *s2, size_t n) 安全版本比較前n個字符的大小,其他同上
string.h char* strcpy(char *restrict dst, const char *restrict src) 把src的字符串拷貝到dst,restrict表示不重疊
string.h char* strncpy(char *restrict dst, const char *restrict src, size_t n) 安全版本,拷貝n個字符,其他同上
string.h char* strcat(char *restrict s1, const char *restrict s2) 把s2拷貝到s1後面,s1要足夠大
string.h char* strncat(char *restrict s1, const char *restrict s2, size_t n) 安全版本,追加n個字符,其他同上
string.h char* strchr(const char *s, int c) 在s串中找到c第一次出現的位置,返回指針,沒有爲null
string.h char* strrchr(const char *s, int c) 在s串中從右邊找到c第一次出現的位置,返回指針,沒有爲nulltring.h
string.h char* strstr(const char *s1, const char *s2) 在字符串s1中不忽略大小寫尋找字符串s2
string.h char* strcasestr(const char *s1, const char *s2) 在字符串s1中忽略大小寫尋找字符串s2
string.h void *memset(void *s, int c, unsigned long n) 將指針變量s所指向的前n字節的內存單元用一個“整數”c替換
string.h void *memcpy(void *dest, const void *src, size_t n) 從src的開始位置拷貝n個字節的數據到dest。如果dest存在數據,將會被覆蓋。
stdlib.h system(“pause”) 暫停程序,按任意鍵繼續


(二)scanf和printf

scanf(stdio.h)函數聲明:int scanf(const char *format, …)
format格式:%[flag]type

printf(stdio.h)函數聲明:int printf(const char *format, …)
format格式:%[flags][width][.prec][hlL]type

flag 含義
- 跟width一起用,左對齊(%-9d)
+ 正數強制輸出正號(%+9d) 可以%±9d/%-+9d
正常輸出
0 數字前填充0(%09d) 不可以%-09d(因爲負號已經是左對齊了)

width / prec 含義
number 最小字符數
.number 小數點後位數。%9.2f:一共9位,小數點後2位(正數部分7位)
* 將number放到後面作爲參數使用(%*d, number, int)
.* 將.number放到後面作爲參數使用(%.*d, .number, int)

hlL 含義
hh 單個字節(char 1byte)
h short(2byte)
l long(4byte)
ll long long(8byte)
L long double(16byte)

type 用於 含義
i / d int 接受整數值並將它表示爲有符號的十進制整數,i是老式寫法
u unsigned int 無符號10進制整數
o unsigned int 無符號8進制整數(不輸出前綴0)
x / X unsigned int 無符號16進制整數,x對應的是abcdef,X對應的是ABCDEF(不輸出前綴0x)
f / F / lf double 單精度浮點數和雙精度浮點數用f(lf 在C99開始加入標準,意思和 f 相同)
e / E double 科學計數法表示的數,此處"e"的大小寫代表在輸出時用的“e”的大小寫
g / G double 有效位數,如:%.8g表示單精度浮點數保留8位有效數字。
c char 字符型。可以把輸入的數字按照ASCII碼相應轉換爲對應的字符
s / char * / wchar_t * 字符串。輸出字符串中的字符直至字符串中的空字符(字符串以’\0‘結尾,這個’\0’即空字符
p 指針(void *) 以16進制形式輸出指針
n 讀入/寫出的個數(int *) 到此字符之前爲止,一共輸出的字符個數,不輸出文本
% 不進行轉換,輸出字符‘%’(百分號)本身
m 打印errno值對應的出錯內容,(例: printf("%m\n"); )
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章