<input id="ohw05"></input>
  • <table id="ohw05"><menu id="ohw05"></menu></table>
  • <var id="ohw05"></var>
  • <code id="ohw05"><cite id="ohw05"></cite></code>
    <label id="ohw05"></label>
    <var id="ohw05"></var>
  •   

    C 語言基礎

    目錄

    C 語言語法

    一、 基礎

    1、 第一個程序

    // 導入一個文件,std是一個標準庫,io是輸入輸出
    /*
    <> 表示導入系統文件
    "" 表示導入自定義的文件
    */
    #include <stdio.h>
    #include <stdlib.h>  // 里面包含了system函數
    
    int main() {
        printf("Hello, World!\n");  // 打印輸出hello world,行注釋
        system("pause");  // 按任意鍵繼續
        /* 塊注釋 */
        return 0;  // 表示函數的返回值
    }
    

    system:使用系統命令是,成功返回0

    2、 程序編譯步驟

    C 代碼編譯成可執行程序經過4步:

    (1)預處理:宏定義展開、頭文件展開、條件編譯等,同時將代碼中的注釋刪除,這里并不會檢查語法

    (2)編譯:檢查語法,將預處理后文件編譯生成匯編文件

    (3)匯編:將匯編文件生成目標文件(二進制文件)

    (4)鏈接:C語言寫的程序是需要依賴各種庫的,所以編譯之后還需要把庫鏈接到最終的可執行程序中去

    使用gcc查看編譯過程

    gcc -E hello.c -o hello.i    # 預處理
    gcc -S hello.i -o hello.s    # 編譯
    gcc -c hello.s -o hello.o    # 匯編
    gcc    hello.o -o hello.exe  # 鏈接
    :<<!
    # 一步編譯的代碼:
    !
    gcc -o hello.exe h1.c h2.c ...  # 后面可以編譯多個代碼,同時生成一個hello.exe 文件
    

    -o:表示輸出文件的地址

    3、 匯編語言

    mov 移動
    add 添加
    push 入棧
    pop 出棧
    call 調用
        
    eax 32位寄存器
    

    二、 數據類型

    1、 常量與變量

    • 關鍵字:C語言里面有32個關鍵字

    • 數據類型

    • 常量

      • 在程序運行過程中,其值不能被改變的量
      • 常量一般出現在表達式或賦值語句中

      定義方式

      #define MAX 20
      const int max_ = 23;  // 不安全
      
    • 變量

      • 在程序運行過程中,其值可以發生改變的量
      • 變量在使用時必須先定義,定義變量前必須有相應的數據類型

      命名規則

      • 標識符不能是關鍵字
      • 標識符只能由字母、數字、下劃線組成
      • 第一個字符必須為字母或下劃線
      • 標識符中字符區分大小寫

      變量特點

      • 變量在編譯時為其分配相應的內存空間
      • 可以通過其名字和地址訪問相應的內存

      定義方式

      int max_ = 23;  // 其是可以改變的
      

    2、 整型

    2.1 格式化輸出

    占位符 含義
    %d 輸出一個有符號的十進制整型數據
    %o 輸出八進制的整型數據
    %x 輸出十六進制的整型數據,字母以小寫輸出
    %X 輸出十六進制的整型數據,字母以大寫輸出
    %u 輸出一個十進制的無符號數

    2.2 定義

    // 無符號 unsigned;有符號 signed
    int a = -10;  // 有符號代表有正負,默認為有符號
    unsigned int b = 10;  // 無符號數,只能為正數,計算結果也要為正數
    
    int c = 0123;  // 定義八進制數據,以 0 開頭
    int d = 0x234ba;  // 定義十六進制數據,以 0x 開頭
    

    C 不能直接書寫二進制數據的形式

    例如:

    #include <stdio.h>
    
    int main() {
        int a;
        scanf_s("%d", &a);  // 通過鍵盤輸入賦值
        printf_s("%d \n", a);  // 輸出值
        return 0;
    }
    

    使用scanf會出現安全問題,使用scanf_s,安全輸入

    使用不同的關鍵字定義整型,其開辟的空間是不一樣的,所占字節數與所選擇的操作系統有關,可以使用sizeof(int)來查看所占空間(BYTE

    3、 字符型

    3.1 定義

    字符型變量用于存儲一個單一字符,在C 語言中用char表示,其中每個字符變量都會占用1個字節。在給字符型變量賦值時,需要使用單引號

    字符型變量實際上并不是把該字符本身放到變量的內存單元中去,而是將該字符對應的acsii編碼放到變量的存儲單元中。char的本質就是一個1字節大小的整型

    特殊字符:

    • 轉義字符
    char a = 'a';
    

    3.2 輸入

    #include <stdio.h>
    
    int main() {
        char a;
        scanf_s("%c", &a);  // 安全輸入
        printf_s("%c \n", a);  // 安全輸出
        return 0;
    }
    

    4、 浮點型

    浮點型變量也可以稱為實型變量,浮點型變量是用來存儲小數值的。在 C 語言中,浮點型變量分為兩種:單精度浮點數(float)、雙精度浮點數(double),但是double型變量所表示的浮點數比float型變量更準確

    由于浮點型變量是由有限的存儲單元組成的,因此只能提供有限的有效數字。在有效位以外的數字將被舍去,這樣可能會產生一些誤差。

    不以f結尾的常量是double類型,以f結尾的常量(如:3.14f)是float類型

    5、 類型限定符

    限定符 含義
    extern 聲明一個變量,extern聲明的變量沒有建立存儲空間
    const 定義一個常量,常量的值不能修改
    Volatile 防止編譯器優化代碼
    register 定義寄存器變量,提高效率

    6、 字符串

    6.1 字符串常量

    • 字符串是內存中一段連續的char空間,以'\0'結尾
    • 字符串常量是有雙引號括起來的字符序列

    字符串常量和字符常量不同

    • 每個字符串的結尾,編譯器會自動添加一個結束標志位'\0'

    定義

    char* a = "hello";  // 使用指針來定義
    char b[] = "hello";  // 使用字符數組來定義
    printf("%s\n", a);  // 輸出字符串
    

    6.3 printf 和 putchar

    printf是輸出一個字符串,putchar輸出一個字符

    6.3.1 printf

    占位符:

    格式 含義
    %a,%A 浮點數、十六進制數字和p-計數法
    %c 一個字符
    %C 一個ISO寬字符
    %d 有符號十進制整數(int)(%ld ,%Ld為:長整型數據,%hd:短整型數)
    %e,%E 浮點數,e-計數法,E-計數法
    %f 單精度浮點數
    %g,%G 根據數值不同自動選擇%f或%e
    %i 有符號十進制數(與%d相同)
    %o 無符號八進制整數
    %p 指針
    %s 對應字符串char*(%s = %hs = %hS 輸出 窄字符)
    %S 對應寬字符串WCAHR*(%ws = %S 輸出寬字符串)
    %u 無符號十進制整數(unsigned int)
    %x,%X 使用十六進制數字0xf的無符號十六進制整
    %% 打印一個%號
    %I64d 用于int64 或者 long long
    %I64u 用于uint64 或者unsigned long long
    %I64x 用于64 位16進制數字

    附加格式:

    符號 含義
    - 左對齊
    + 右對齊
    .n 對于小數點,保留n位小數
    # 對c,s,d,u無影響,對o類輸出前加綴為o,對x類,在輸出前綴加0x,對e,g,f當結果有小數時給出小數點
    m 代表數據的最小寬度
    6.3.2 putchar
    char ch = '0';
    putchar(ch);  // 輸出一個字符
    putchar('\n');
    putchar(97);
    

    輸出字符可以是變量、字符、數字或者轉義字符

    6.4 scanf_s 和 getchar

    getchar是從標準輸入設備讀取一個字符;scanf_s通過%轉義的方式可以得到用戶通過標準輸入設備輸入的數據

    6.4.1 scanf_s
    int a, b;
    scanf_s("%d %d", &a, &b);  // 輸入兩個整型數據,使用空格(分隔符)或換行
    
    6.4.2 getchar
    char ch;
    ch = getchar();  // 只會接收一個字符,如果有其他的,可以使用while循環遍歷,也可以用來作為暫時停留界面
    putchar(ch);  
    

    7、 類型轉換

    數據有不同的類型,不同類型數據之間進行混合運算時必然涉及到類型的轉換問題

    7.1 隱式轉換

    自動轉換:遵循一定的規則,由編譯系統自動完成

    char a = 2;
    int c = a;  // a 變成了int類型,有符號數會轉換成無符號數
    

    小范圍的類型會自動轉換成大范圍的類型運算

    7.2 顯式轉換

    把表達式的運算結果強制轉換成所需的數據類型

    數據類型2 變量2 = (數據類型2)變量1  // 其不會四舍五入,直接丟失后面的數據
    

    其會造成精度的丟失

    三、 運算符

    1、 算術運算符

    用于處理四則運算

    +:加 -:減 *:乘 /:除 %:取余 ++:自增 --:自減
    // 前自增先賦值,后運算;后自增相反
    

    2、 賦值運算符

    用于將表達式的值賦給變量

    += -= /= *= = 
    

    3、 比較運算符

    用于表達式的比較,并返回一個真值(true)或假值(false)

    == != > < >= <= 
    

    4、 邏輯運算符

    用于根據表達式的值返回真值或假值

    !:非 &&:與 ||:或
    

    所有非零的值都是真值,非真即假

    四、 流程結構

    C/C++支持最基本的三種程序運行結構:順序結構、選擇結構、循環結構

    • 順序結構:程序按順序執行,不發生跳轉
    • 選擇結構:依據條件是否滿足,有選擇的執行相應功能
    • 循環結構:依據條件是否滿足,循環多次執行某段代碼

    1、選擇結構

    1.1 if語句

    作用:執行滿足條件的語句

    if語句的三種形式

    • 單行格式if語句
    • 多行格式if語句
    • 多條件的if語句
    1.1.2 單行格式
    #include <stdio.h>
    int main() {
        // 選擇結構 單行if語句
    
        // 用戶輸入一個數字
        int num = 0;
        printf_s("請輸入一個數字:");
        scanf_s("%d", &num);
    
        // 判斷數字是否大于100,是則輸出原數
        if (num >= 100){  // 注意if語句后面不要加分號,否則,if將不會進行判斷
            printf_s("%d\n", num);
        }
        return 0;
    }
    
    1.1.3 多行if語句
    #include <stdio.h>
    int main() {
        // 選擇結構 單行if語句
    
        // 用戶輸入一個數字
        int num = 1;
        printf_s("請輸入一個數字:");
        scanf_s("%d", num);
    
        // 判斷數字是否大于100,是則輸出原數;否則,輸出0
        if (num >= 100) {  // 注意if語句后面不要加分號,否則,if將不會進行判斷
            printf_s("%d", num);
        }
        else {
            printf_s("%d", 0);
        }
        return 0;
    }
    
    1.1.4 多條件if語句
    #include <stdio.h>
    int main() {
        // 選擇結構 單行if語句
    
        // 用戶輸入一個數字
        int num = 1;
        printf_s("請輸入一個數字:");
        scanf_s("%d", num);
    
        // 判斷數字是否大于100,是則輸出原數;否則,輸出0
        if (num >= 600) {  // 注意if語句后面不要加分號,否則,if將不會進行判斷
            printf_s("%d", 600);
        }
        else if (num >= 100) {  // 注意if語句后面不要加分號,否則,if將不會進行判斷
            printf_s("%d", num);
        }
        else {
            printf_s("%d", 0);
        }
        return 0;
    }
    

    if ( 條件1 ) { 條件1滿足執行語句} else if ( 條件2 ) { 條件2滿足,同時條件1不滿足,執行的語句 }··· else { 都不滿足執行的語句 }

    1.1.5 嵌套if語句

    在if語句中,可以嵌套使用if語句,達到更加精確的條件判斷

    案例:輸入3個數字,判斷出最大的數字

    #include <stdio.h>
    int main() {
        int num = 1;
        int num1 = 1;
        int num2 = 1;
        printf_s("請輸入三個數字:");
        scanf_s("%d,%d,%d", &num, &num1, &num2);
    
        if ( num > num1 ) {  // 判斷 num 和 num1
            if ( num > num2 ) {  // 判斷 num 和 num2
                printf_s("%d最大\n", num);
            }
            else {
                printf_s("%d最大\n", num2);
            }
        }
        else {
            if (num1 > num2) {
                printf_s("%d最大\n", num1);
            }
            else {
                printf_s("%d最大", num2);
            }
            printf_s("判斷完成");
        }
        return 0;
    }
    

    1.2 三目運算符

    作用:通過三目運算實現簡單的判斷

    語法:表達式1 ? 表達式2 : 表達式3

    #include <stdio.h>
    int main() {
    	
    	// 三目運算
    	
    	// 將 a 和 b 做比較,將大的值賦值給c
    	int a = 10;
    	int b = 20;
    	int c = 0;
    
    	c = a > b ? a : b;  // 如果 a 比 b 大,則將a賦值給c
        
        /*
        if ( a > b ) {
        	c = a;
        }
        else {
        	c = b
        }
        */
        return 0;
    }
    

    在C++中,三目運算符返回的是變量,可以繼續賦值

    1.3 switch語句

    作用:執行多條件分支語句

    語法:

    switch ( 表達式 ) {
        case 結果1: 執行語句; break;  // switch里面不一定每個case都要對應break,break的作用的向外跳出一層
        ······
        default: 執行語句; break;  // 不一定需要default判斷
    } 
    

    示例

    #include <stdio.h>
    int main() {
    
        // switch 語句
    
        // 電影打分
        int score = 0;
        printf_s("請輸入分數:");
        scanf_s("%d", &score);
        printf_s("您打的分數為:%d\n", score);
    
        switch (score) {
            case 10:
                printf_s("是經典電影\n");
                break;  // 退出當前分支,如果沒有break,則會繼續向下運行
            default:  // 當所有條件不滿足時,執行該語句
                printf_s("普通\n");
        }
    
    }
    

    缺點:判斷的時候,只能是整型或者字符型,不可以是一個區間

    優點:結構清晰,執行效率高

    注意

    • switch語句中表達式類型只能是整型或者字符型
    • case里如果沒有break,那么程序會一直向下執行

    2、 循環結構

    2.1 while循環語句

    作用:滿足循環條件,執行循環語句

    語法:while ( 循環條件 ) { 循環語句 }

    解釋:只要循環條件的結果為真,就執行循環語句

    #include <stdio.h>
    int main() {
    
    	int nu = 0;
    		// 在屏幕中打印0到9的數字
    	while ( nu < 10 )
    	{
    		printf_s("%d\n",nu);
    		nu++;
    	}
    }
    

    2.2 do···while循環語句

    作用:滿足循環條件,執行循環語句

    語法:do {循環語句} while (循環條件);

    注意:與while的區別在于do...while會先執行一次循環時間,在判斷循環條件

    #include <stdio.h>
    
    int main() {
    
    	// do...while語句
    	// 在屏幕中輸出 0 到 9 這10個數字
    	int num = 0;
    
    	do {
    		printf_s("%d\n", num);
    		num++;
    	} 
    	while (num <= 9);
    }
    

    2.3 for循環語句

    作用:滿足循環條件,執行循環語句

    語法:for (起始表達式;條件表達式;末尾循環體) { 循環語句; }

    #include <stdio.h>
    
    int main() {
    
    	// for循環
    	// 從數字0打印到9
    
    	for (int i = 0; i < 10; i++ ) {
    		printf_s("%d\n", i);
    	}
    
    	// 也可以
    	int i = 0;
    	for (;;) {
    		if (i >= 10) {
    			break;
    		}
    		printf_s("%d\n", i++);
    	}
    }
    

    2.4 嵌套循環

    作用:在循環中在嵌套一層循環,解決一些實際問題

    #include <stdio.h>
    
    int main() {
    
    	// 嵌套循環
    	for (int i = 0; i < 5; i++) {
    		for (int j = 0; j < 5; j++) {
    			printf_s(" * ");
    		}
    		printf_s("\n");
    	}
    
    }
    

    3、 跳轉語句

    3.1 break語句

    作用:用于跳出選擇結構或者循環結構

    break使用的時機:

    • 出現在switch語句中,作用是終止case并跳出switch
    • 出現在循環語句中,作用是跳出當前的循環語句
    • 出現在嵌套循環中,跳出最近的內層循環語句

    3.2 continue語句

    作用:在循環語句中,跳過本次循環中余下尚未執行的語句,繼續執行下一次循環

    #include <stdio.h>
    
    int main() {
    	
    	for (int i = 0; i <= 10; i++) {
    		if (i % 2 == 0) {
    			continue;
    		}
    		else {
    			ptintf_s("%d\t", i);
    		}
    	}
    
    }
    

    3.3 goto語句

    作用:可以無條件跳轉語句

    語法:goto 標記;

    解釋:如果標記的名稱存在,執行到goto語句時,會跳轉到標記的位置

    #include <stdio.h>
    
    int main() {
    	
    	// goto
    	printf_s("hello\n");
    	printf_s("hello\n");
    	printf_s("hello\n");
    	goto flag;
    	printf_s("hello\n");
    	printf_s("hello\n");
    flag:
    	printf_s("world\n");
    }
    

    五、 數組和字符串

    1、 概述

    在程序設計中,為了方便處理數據把具有相同類型的若干變量按有序形式組織起來——稱為數組

    數組就是在內存中連續的相同類型的變量空間。同一個數組所有的成員都是相同的數據類型,同時所有的成員在內存中的地址是連續的

    數組屬于構造數據類型

    數組名是地址常量

    2、 定義數組

    int arr[] = { 1, 2, 4, 5, 6, 7 };
    int arr1[6] = { 1, 2, 3, 4, 5, 6 };
    int len = sizeof(arr) / sizeof(arr[0]);  // 獲取數組的長度
    

    整型數組默認初始化的值為0,

    3、 冒泡排序

    作用:最常用的排序算法,對數組內的元素進行排序

    1. 比較相鄰的元素。如果第一個比第二個大,就交換他們兩個
    2. 對每一對相鄰元素做同樣的工作,執行完畢后,找到第一個最大值
    3. 重復以上的步驟,每次比較次數-1,直到不需要比較
    #include <stdio.h>
    
    int main() {
        // 利用冒泡排序,實現升序排序
        int arr[ ] = {4, 2, 8, 0, 5, 7, 1, 3, 9};
        printf_s("排序前:");
        for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++) {
            printf_s("%d\t", arr[i]);
        }
        for (int i = 0; i < (sizeof(arr) / sizeof(arr[0]) - 1); i++) {  // 排序次數為元素個數減一
            for (int j = 0; j < (sizeof(arr) / sizeof(arr[0]) - 1 - i); j++) {  // 內層循環對比次數元素個數 - 排序次數 - 1
                if (arr[j] > arr[j + 1]) {  // 如果前一個數字比第二個數字大,交換順序
                    int temp = arr[j];
                    arr[j] = arr[j + 1];
                    arr[j + 1] = temp;
                }
            }
        }
        printf_s("\n排序后:");
        for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++) {
            printf_s("%d\t", arr[i]);
        }
    }
    

    4、 二維數組

    二維數組定義的一般形式是:

    類型說明符 數組名[常量表達式1][常量表達式2];
    

    其中常量表達式1表示行數,常量表達式2表示列數

    5、 字符串

    char arr[5] = { 'h', 'e', 'l', 'l', 'o' };  // 字符數組
    char* a = "world";  // 字符串
    

    如果字符數組有'\0'的標志,則可以認為其是一個字符串,字符串是字符數組的特例

    字符數組和字符串的區別:

    • C 語言中沒有字符串這種數據類型,可以通過char的數組來替代
    • 字符串一定是一個char的數組,但char的數組未必是字符串
    • 數字0(和字符'\0'等價)結尾的char數組就是一個字符串,但如果char數組沒有以數字0結尾,那么就不是一個字符串,只是普通字符數組,所以字符串是一種特殊的char數組

    5.1 字符串的輸入

    char str[100];
    scanf_s("%s", str);  // 默認使用空格分隔
    
    // gets_s()
    char* gets_s(char* s);
    // 功能:從標準輸入讀取字符,并保存到指定的內存空間,直到出現換行或讀到文件結尾為止,成功返回字符串,失敗返回空
    
    // fgets_s()
    char* fgets(char* s, int size, FILE* stream);
    // 功能:從stream指定的文件中讀入字符,保存到所指定的內存空間,直到出現換行字符、讀到文件結尾或是已讀了size-1個字符為止,最后會自動加上'\0',作為字符串結束,成功返回字符串,失敗返回空。其通過鍵盤輸入時,包括了最后的'\n'換行
    

    scanf_sgets_s的區別:

    • scanf_s不允許輸入有空格;gets_s允許輸入有空格

    fgets里面的stream是文件操作指針

    例如,獲取鍵盤輸入的字符串:

    char ch[101];
    sacnf_s("%s", ch);
    
    gets_s(ch);
    
    fgets(ch, sizeof(ch), stdin);
    

    上面的代碼都是獲取從鍵盤輸入的字符串

    5.2 字符串的輸出

    printf_s("%s\n", str);
    
    int puts(const char* s);
    // 功能:標準設備輸出字符串,在輸出完成后自動輸出一個'\n'
    
    int fputs(const char* s, FILE* stream);
    // 功能:將s所指定的字符串寫入到stream指定的文件中,字符串結束符'\0'不寫入文件,同時不會自動換行
    

    例如:

    char c[] = "hello world";
    puts(c);
    fputs(c, stdout);
    

    5.3 字符串長度

    #include <string.h>
    int strlen(const char* s);
    // 功能:計算指定字符串的長度,不包括'\0'
    

    六、 函數

    1、 概述

    1.1 函數的分類

    C 程序是由函數構成的,我們寫的代碼都是由主函數 main() 開始執行的。函數是 C 程序的基本模塊,是用于完成特定任務的程序代碼單元。

    從函數定義的角度看,函數可分為系統函數和用戶定義函數兩種:

    • 系統函數,即庫函數:這是編譯系統提供的,用戶不必自己定義這些函數,可以直接使用它們
    • 用戶定義的函數:用以解決用戶的專門需要

    從函數執行結果的角度來看, 函數可分為有返回值函數和無返回值函數兩種

    • 有返回值函數:此類函數被調用執行完后將向調用者返回一個執行結果,稱為函數返回值。(必須指定返回值類型和使用return關鍵字返回對應數據)
    • 無返回值函數:此類函數用于完成某項特定的處理任務,執行完成后不向調用者返回函數值。(返回值類型為void, 不用使用return關鍵字返回對應數據)

    從主調函數和被調函數之間數據傳送的角度看,又可分為無參函數和有參函數兩種

    • 無參函數:在函數定義及函數說明及函數調用中均不帶參數。主調函數和被調函數之間不進行參數傳送
    • 有參函數:在函數定義及函數說明時都有參數,稱為形式參數(簡稱為形參)。在函數調用時也必須給出參數,稱為實際參數(簡稱為實參)

    1.2 函數的作用

    1. 函數的使用可以省去重復代碼的編寫,降低代碼重復率
    2. 函數可以讓程序更加模塊化,從而有利于程序的閱讀,修改和完善

    1.3 函數的調用:獲得隨機數

    當調用函數時,需要關心5要素:

    • 頭文件:包含指定的頭文件
    • 函數名字:函數名字必須和頭文件聲明的名字一樣
    • 功能:需要知道此函數能干嘛后才調用
    • 參數:參數類型要匹配
    • 返回值:根據需要接收返回值
    #include <time.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    int getRandNum() {
        srand((unsigned int)time(NULL));  // 使用隨機種子
        // srand((size_t)time(NULL));
        int n = rand() % 10;  // 產生0到9的隨機數
        // rand() % (max - min + 1) + min  取得 min ~ max 的隨機數
        return n;
    }
    

    2、 函數定義和使用

    2.1 函數的定義

    • 定義函數的目的

      • 將一個常用的功能封裝起來,方便以后調用
    • 自定義函數的書寫形式

      返回值類型 函數名(參數類型 形參1, 參數類型 形參2, ...) {
          函數體;
          返回值;
      }
      int get_num(int a, int b) {
          int sum = a + b;
          return sum;
      }
      

      在函數調用過程中傳遞的參數稱為實參,有具體的值

      在函數的定義中參數稱為形參,形式參數

      在函數調用過程中實參傳遞給形參

      在函數調用結束后,函數會在內存中銷毀

    2.2 具體介紹

    2.2.1 函數名

    理論上,函數名是可以隨意起名字,見名知義,應該讓用戶看到這個函數名字就知道這個函數的功能。注意,函數名的后面要加括號,代表這個是函數,而不是普通的變量

    2.2.2 形參列表

    在定義函數時,指定形參,在未出現函數調用時,它們并不占內存中的存儲單元。因此,稱它們是形式參數或虛擬參數,簡稱形參,表示它們并不是實際存在的數據,所以,形參里的變量不能賦值

    int sum(int a, int b = 20);  // 這會報錯,形參在C語言里面不能賦值
    

    在定義函數時指定的形參,必須是類型+變量的形式

    int sum(int a, int b); // right
    
    2.2.3 函數體

    花括號里面的內容即為函數體的內容,這里為函數功能實現的過程,這和以前的寫代碼沒太大區別,以前我們把代碼寫在main()函數里,現在只是把這個寫到別的函數里面

    2.2.4 返回值

    函數的返回值是通過函數中的return語句獲得的,return后面的值也可以是一個表達式

    1. 盡量保證return語句中表達式的值和函數返回類型是同一類型
    2. 如果函數返回的類型和return語句中表達式的值不一致,則以函數返回類型為準,即函數返回類型決定返回值類型。對數值型數據,可以自動進行類型轉換。

    2.3 函數調用

    定義函數后,我們需要調用此函數才能執行到這個函數里面的代碼段。這和main()函數不一樣,main()為編譯器設定好自動調用的主函數,無需人為調用,我們都是在main()函數里調用別的函數,一個C程序里有且只有一個main()函數

    2.3.1 函數執行流程
    1. 進入main函數
    2. 調用test函數
      • 它會在main()函數的前面尋找有沒有一個名字叫做test的函數定義
      • 如果找到,接著檢查函數的參數,這里調用函數是沒有傳參,函數定義也沒有形參,參數類型匹配
      • 開始執行test()函數,這時候,main()函數李曼的執行會阻停在test()這一行代碼,等待函數執行完成
    3. 函數執行完成后,main()函數繼續執行
    2.3.2 形參和實參
    • 形參出現在函數定義中,在整個函數體內都可以使用,離開該函數則不能使用
    • 實參出現在主調用函數中,進入被調函數后,實參也不能使用
    • 實參變量對形參變量的數據傳遞是"值傳遞",即單向傳遞,只由實參傳給形參,而不能由形參傳回來給實參
    • 在調用函數時,編譯系統臨時給形參分配存儲單元。調用結束后,形參單元被釋放
    • 實參單元與形參單元是不同的單元。調用結束后,形參單元被釋放,函數調用結束返回主函數后則不能再使用該形參變量。實參單元仍保留并維持原值。因此,在執行一個被調用函數時,形參的值如果發生改變,并不會改變主調函數中實參的值

    2.4 函數聲明

    #include <stdlib.h>
    #include <stdio.h>
    
    int getSum(int, int);  // extern int getSum(int, int);
    
    int main() {
        int sum = getSum(1, 2);
        printf_s("%d\n", sum);
        system("pause");
        return 0;
    }
    
    int getSum(int a, int b) {
        return a + b;
    }
    

    從廣義的角度來講,聲明中包含著定義,即定義是聲明的一個特例,所以并非所有的聲明都是定義:

    • int b它既是聲明,同時又是定義
    • 對于extern b來講,它只是聲明,不是定義

    一般的情況下,把建立存儲空間的聲明稱之為“定義”,而把不需要建立存儲空間的聲明稱之為“聲明”

    4、 多文件編程

    一個文件聲明函數

    如,創建一個hello.h

    #ifndef NEW_HELLO_H  // 如果沒有這個文件,可以防止頭文件包含
    // 也可以使用 #pragma once  來防止頭文件包含只能在Windows中使用
    #define NEW_HELLO_H
    
    int getSum(int, int);  // 可以把extern省略
    
    #endif //NEW_HELLO_H
    

    一個文件實現函數hello.c

    #include "hello.h"
    
    int getSum(int a, int b) {
        return a + b;
    }
    

    一個文件調用函數main.c

    #include <stdlib.h>
    #include <stdio.h>
    #include "hello.h"
    
    
    int main() {
        int sum = getSum(1, 2);
        printf_s("%d\n", sum);
        system("pause");
        return 0;
    }
    

    編譯代碼

    gcc -o hello.exe main.c hello.c hello.h
    

    5、 main函數

    int main(int argc, const char * argv[]) {
       	printf_s("命令行傳入參數的個數:%d,第一個參數為:%s\n", argc, argv[0]);
        system("pause");
        return 0;
    }
    

    main的含義:

    • main是函數的名稱,和我們自定義的函數名稱一樣,也是一個標識符
    • 只不過main這個名稱比較特殊,程序一啟動就會自動調用它

    return 0的含義:

    • 告訴系統main函數是否正確的被執行了
    • 如果main函數的執行正常,那么就返回0
    • 如果main函數的執行不正常,那么就返回一個非0的函數

    返回值類型

    • 一個函數return 后面寫的是什么類型,函數的返回值類型就必須是什么類型,所以寫int

    形參列表的含義:

    1. int argc
      • 系統在啟動程序是調用main函數時傳遞給argv的值的個數
    2. const char* argv[]
      • 系統在啟動程序時傳入的值,默認情況下系統會傳入一個值,這個值就是main函數執行文件的路徑
      • 也可以通過命令行或項目設置傳入其它參數

    6、 遞歸函數

    什么是遞歸函數?

    • 一個函數在它的函數體內調用它自身稱為遞歸調用

    遞歸函數構成條件

    • 自己調用自己
    • 存在一個條件能夠讓遞歸結束
    • 問題的規模能夠縮小

    實例:求一個數的累加和

    #include <stdlib.h>
    #include <stdio.h>
    int getSum(int n) {
        return n == 1 ? 1 : getSum(n - 1) + n;
    }
    int main() {
        int n;
        scanf_s("%d", &n);
        printf_s("累加和為:%d\n", getSum(n));
        system("pause");
        return 0;
    }
    

    遞歸和循環區別

    • 能用循環實現的功能,用遞歸都可以實現
    • 遞歸常用于"回溯", “樹的遍歷”,"圖的搜索"等問題
    • 但代碼理解難度大,內存消耗大(易導致棧溢出), 所以考慮到代碼理解難度和內存消耗問題, 在企業開發中一般能用循環都不會使用遞歸

    七、 指針

    1、 概述

    1.1 內存

    內存含義:

    • 存儲器:計算機的組成,用來存儲程序和數據,輔助CPU進行運算處理的重要部分
    • 內存:內部存儲器,暫存程序/數據——掉電丟失
    • 外存:外部存儲器,長時間保存程序/數據——掉電不丟失

    內存是溝通CPU與硬盤的橋梁:

    • 暫存放CPU中的運算數據
    • 暫存與硬盤等外部存儲器交換的數據

    1.2 物理存儲器和存儲地址空間

    有關內存的兩個概念:物理存儲器和存儲地址空間

    物理存儲器:實際存在的具體存儲芯片

    • 主板上裝插的內存條
    • 顯示卡上的顯示RAM芯片
    • 各種適配卡上的RAM芯片和ROM芯片

    存儲地址空間:對存儲器編碼的范圍。我們在軟件上常說的內存是指這一層含義

    • 編碼:對每個物理存儲單元分配一個號碼
    • 尋址:可以根據分配的號碼找到相應的存儲單元,完成數據的讀寫

    1.3 內存地址

    • 將內層抽象成一個很大的一維字符數組
    • 編碼就是對內存的每一個字節分配成一個32位或64位的編號
    • 這個內存編號我們稱之為內存地址

    內存中的每一個數據都會分配相應的地址:

    • char:占一個字節分配一個地址
    • int:占四個字節分配四個地址

    1.4 指針和指針變量

    C 語言中把地址形象地稱作指針

    獲取地址的方法:

    int a = 10;
    printf("%x\n", &a);  // & 為取址運算符
    

    可以保存地址值(指針)的變量稱為指針變量,因為指針變量中保存的是地址值,故可以把指針變量形象地比喻成地址箱

    2、 指針基礎知識

    2.1 指針變量的定義和使用

    • 指針也是一種數據類型,指針變量也是一種變量
    • 指針變量指向誰,就把誰的地址賦值給指針變量
    // 定義一個指針變量
    int* p;  
    int a = 10;
    // 給指針變量賦值
    p = &a;
    

    2.2 指針變量間接修改變量的值

    使用取值運算符來修改指針變量所對應的值

    printf("修改前:%d", a);
    *p = 100;
    printf("修改后:%d", a);
    

    2.3 指針大小

    • 使用sizeof()測量指針的大小,得到的總是:4或8
    • sizeof()測的是指針變量指向內存地址的大小
    • 在32位平臺,所有指針地址都是32位(4bit)
    • 在64位平臺,所有指針地址都是64位(8bit)
    printf("%d\n", sizeof(int*));
    

    2.4 野指針和空指針

    指針變量也是變量,是變量就可以任意賦值,不要越界即可,但是任何數值賦值給指針變量沒有意義,因為這樣的指針就成了野指針,此指針指向的區域是未知的(操作系統不允許此指針指向內存區域)。所以,也指針不會直接引發錯誤,操作指針指向的內存區域才會出問題。

    int* p = 100;  // 野指針 -> 指針變量指未知的空間
    
    // 操作系統將0~255作為系統占用空間,不允許訪問操作
    // 操作野指針對應的空間可能報錯
    printf("%d\n", *p);
    

    但是,野指針和有效指針變量保存的都是數值,為了標志此指針變量沒有指向任何變量。C語言中,可以把NULL賦值給此指針,這樣就標志此指針為空指針,沒有任何指針。

    int* p = NULL;  // 空指針是指內存地址編號為0的內存空間
    

    2.5 萬能指針

    void*指針可以指向任意變量的內存空間:

    int a = 10;
    // 萬能指針可以接收任意類型變量的內存地址
    void* p = &a;
    
    // *p = 100;  // 報錯了,非法的間接尋址
    // 在通過萬能指針修改變量的值時,需要找到變量對應的指針類型
    *(int*)p = 100;
    
    printf_s("%d\n", a);
    

    2.6 const修飾指針變量

    int a = 100;
    int b = 200;
    // 常量指針
    // 修飾 *,指針指向內存區域不能修改,指針指向可以改變
    const int* p1 = &a;
    // *p1 = 2;
    p1 = &b;
    // 指針常量
    //  修飾 p2,指針指向不能改變,指針指向的內存可以修改
    int* const p2 = &a;
    // p2 = &b;
    *p2 = 2;
    

    3、 指針和數組

    3.1 數組指針

    指針操作數組

    數組名字是數組的首元素地址,但它是一個常量

    int arr[] = {1, 2, 3};
    // arr = 10;  // 其報錯,因為a相當于一個指針常量,其指向不能改變
    // 數組名是數組第一個元素的首地址
    int* p = arr;  // p 和 arr 等價,指向數組的指針,數組指針
    for (int i = 0; i < 3; ++i) {
        printf_s("%d\n", *p++);  // 使用指針偏移
        // printf_s("%d\n", *(p+i));  // 相當于索引取值
        // printf_s("%d\n", p[i]);  // 索引取值
    }
    int step = p - arr;  // 兩指針相減,得到的是指針偏移的步長
    printf_s("%d\", step);
    

    數組作為函數參數會退化為指針,丟失了數組的精度

    指針操作數組時,下標允許是負數

    3.2 指針加減運算

    • 指針計算不是簡單的整數相加
    • 如果是一個int*,+1的結果是增加一個int的大小
    • 如果是一個char*,+1的結果是增加一個char的大小
    #include <stdio.h>
    
    void my_strcpy1(char* dest, char* src) {
        int i = 0;
        while (*(src+i)) {
            *(dest+i) = *(src+i);
            i++;
        }
        *(dest+i) = 0;
    }
    void my_strcpy2(char* dest, char* src) {
        // 純指針偏移
        while (*src) {
            *dest++ = *src++;
        }
        *dest = 0;
    }
    void my_strcpy3(char* dest, char* src) {
        // 純指針偏移
        while (*dest++ = *src++);
    }
    
    int main() {
        char str_[] = "hello world";
        char dest[100];
        my_strcpy3(dest, str_);
        printf_s("%s\n", dest);
        return 0;
    }
    

    兩個指針進行運算會變成野指針,其為沒有意義的操作(兩數組指針相減,其為偏移量);但是可以進行比較運算

    3.3 指針數組

    指針數組,它是數組,數組的每個元素都是指針類型

    int a = 1;
    int b = 2;
    int c = 3;
    int* d = &a;
    int* e = &b;
    int* f = &c;
    int* arr[] = {d, e, f};  // 指針數組,存儲指針的數組
    

    指針數組里面也可以存儲數組,指針數組是一個特殊的二維數組模型

    #include <stdio.h>
    
    int main() {
        int a[] = {1, 2, 3};
        int b[] = {4, 5, 6};
        int c[] = {7, 8, 9};
        int* d[] = {&a, &b, &c};  // int** d = int* d[]; 
        for (int i = 0; i < 3; ++i) {
            for (int j = 0; j < 3; ++j) {
                // 訪問指針數組里面的內容
                // printf_s("%d\t", d[i][j]);
                // printf_s("%d\t", *(d[i] + j));
                // printf_s("%d\t", *(*(d+i) + j));
            }
            puts("");
        }
        return 0;
    }
    

    4、 二級指針

    二級指針相當于指針數組

    二級指針加偏移量相當于跳過了一維數組的大小

    #include <stdio.h>
    
    int main() {
        int a[] = {1, 2, 3};
        int b[] = {4, 5, 6};
        int c[] = {7, 8, 9};
        int* d[] = {&a, &b, &c};
        int** p = d;
        // 二級指針加偏移量,相當于跳過了一個一維數組
        printf_s("%d", **(p + 1));
        // 一級指針加偏移量,相當于跳過了一個元素
        printf_s("%d", *(*p + 1));
        return 0;
    }
    

    5、 指針和函數

    5.1 地址傳遞

    #include <stdio.h>
    void test(int* a) {
        *a = 20;
    }
    
    
    int main() {
       int a = 10;
       printf_s("調用函數前:%d\n", a);
       test(&a);
       printf_s("調用函數后:%d\n", a);
       return 0;
    }
    

    5.2 數組名做函數參數

    數組名做函數參數,函數的形參會退化成指針

    #include <stdio.h>
    #ifndef bool
    typedef int bool;
    #define true 1
    #define false 0
    #endif
    void bubble(int* arr, int len){
        // 升序排序
        for (int i = 0; i < len; ++i) {
            for (int j = 0; j < len - i - 1; ++j) {
                if (arr[j] > arr[j+1]) {
                    int temp = arr[j];
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
    }
    
    
    int main() {
        int a[] = {4, 1, 5, 9, 6, 8, 7};
        bubble(a, 7);
        for (int i = 0; i < 7; ++i) {
            printf_s("%d\t", *(a + i));
        }
        puts("");
        return 0;
    }
    

    5.3 指針函數

    指針作為函數返回值

    #include <stdio.h>
    #ifndef bool
    typedef int bool;
    #define true 1
    #define false 0
    #endif
    
    char* my_strchr(char* str_, char ch) {
        while (*str_) {
            if (*str_ == ch) {
                return str_;  // 返回切片后的字符數組
            }
            *str_++;
        }
        return NULL;  // 返回沒有找到要開始切片的元素
    }
    
    
    int main() {
        char* str_ = "hello world";
        char* b = my_strchr(str_, 'l');
        printf_s("%s\n", b);
        return 0;
    }
    

    5.4 函數指針

    如果在程序中定義了一個函數,那么在編譯時系統就會為這個函數代碼分配一段存儲空間,這段存儲空間的首地址稱為這個函數的地址。而且函數名表示的就是這個地址。既然是地址我們就可以定義一個指針變量來存放,這個指針變量就叫作函數指針變量,簡稱函數指針。

    函數指針不可以進行運算

    #include <stdio.h>
    #ifndef bool
    typedef int bool;
    #define true 1
    #define false 0
    #endif
    
    void (*pointer) ();  // 定義一個函數指針
    void func() {
        printf_s("hello");
    }
    void print_hello(void (*p)()) {
        p();
    }
    
    int main() {
        pointer = func;  // 將函數的地址賦值給函數指針
        pointer();  // 調用函數
        
        
        // 也可以將函數指針作為參數傳遞給函數
        print_hello(func);  
        /*
         等價于
         void (*p)();  // 定義函數指針
         p = func;  // 給函數指針賦值
         p();  // 調用函數指針
         */
        return 0;
    }
    

    函數指針一般用于傳遞回調函數上面

    6、 指針和字符串

    6.1 字符串出現的次數

    使用strstr方法,來統計出現的次數

    #include <stdio.h>
    #include <string.h>
    
    int main() {
        char ch[] = "hello world";
        char a[] = "l";
        char* p = strstr(ch, a);  // 返回第一次出現字符串的地址
        int count = 0;
        while (p) {  // 如果字符串不為空
            count++;
            p += strlen(a);  // 跳過要查找的字符串
            p = strstr(p, a);  // 再次截取字符串
        }
        printf_s("%d\n", count);
        return 0;
    }
    

    6.2 常用函數

    1. char* strcpy(char* dest, char* src);
      • 功能:將源字符串,拷貝給目標字符串
      • 參數:
        • dest:目標字符串
        • src:源字符串
      • 返回值:
        • 成功返回目標字符串
        • 失敗返回NULL
    2. char* strncpy(char* dest, char* src, size_t n);
      • 功能:將源字符串的前n個字符拷貝給目標字符串
    3. int strcmp(char* s1, char* s2);
      • 功能:查看兩個字符串是否一樣
      • 參數:
        • s1:字符串1
        • s2:字符串2
      • 返回值:
        • s1 = s2:返回值等于0
        • s1 > s2:返回值大于0
        • s1 < s2:返回值小于0
    4. int strncmp(char* s1, char* s2, size_t n);
      • 功能:查看兩個字符串的前n個字符是否相同
    5. char* strcat(char* dest, char* src);
      • 功能:將src字符串連接到dest的尾部,\0也會追加過去
      • 參數:
        • dest:目標字符串的首地址
        • src:源字符串的首地址
      • 返回值:
        • 成功返回目標字符串
        • 失敗返回NULL
    6. char* strncat(char* dest, char* src, size_t n);
      • 功能:將src字符串前n個字符連接到dest的尾部,\0也會追加過去
    7. int sprintf(char* str, const* format, ...);
      • 功能:根據參數format字符串來轉換格式化數據,然后將結果輸出到str指定的空間中,直到出現字符串結束符\0為止
      • 參數:
        • str:字符串首地址
        • format:字符串格式化,用法和printf()一樣
      • 返回值:
        • 成功返回實際格式化的字符個數
        • 失敗返回NULL
    8. char* strchr(const char* s, char c);
      • 功能:在字符串s中查找字符c出現的位置
      • 參數:
        • s:字符串首地址
        • c:字符
      • 返回值:
        • 成功返回第一次出現c的地址
        • 失敗返回NULL
    9. char* strstr(const char* dest, const char* src);
      • 功能:在字符串dest中查找字符串src第一次出現的位置
      • 參數:
        • dest:原字符串的首地址
        • src:匹配字符串的首地址
      • 返回值:
        • 成功返回第一次出現字符串的地址
        • 失敗返回NULL
    10. char* strtok_s(char* str, const char* delim, char** context);
      • 功能:將字符串以字符串delim分割,返回分割第一個字符串,其余字符串放入context中
      • 參數:
        • str:要分割的字符串
        • delim:分割符
        • context:一個字符串數組的第一個元素的首地址
      • 返回值:
        • 成功返回分割后的字符串
        • 失敗返回NULL
    11. int atoi(const char* nptr);
      • 功能:atoi()會掃描nptr,跳過前面的空格字符,知道遇到數字或正負號才開始轉換,而遇到非數字或字符串結束符,才會結束轉換,并將結果返回
      • 參數:
        • nptr:待轉換的字符串
      • 返回自:
        • 成功轉換后的整數
      • 類似的還有:
        • atof()/atol()
      • 注意:其在#include <stdlib.h>里面

    八、 內存管理

    1、 作用域

    C語言變量的作用域分別為:

    • 代碼塊作用域
    • 函數作用域
    • 文件作用域

    1.1 局部變量

    局部變量也叫auto自動變量,auto可不寫,一般情況下代碼塊內部定義的變量都是自動變量,它有如下特點:

    • 在一個函數內定義,只在函數范圍內有效
    • 在復合語句中定義,只在復合語句中有效
    • 隨著函數調用的結束或復合語句的結束局部變量的聲明,聲明周期也結束
    • 如果沒有賦初值,內容為隨機
    • 存儲在棧區

    1.2 全局變量

    • 在函數外定義,可被本文件及其它文件中的函數所共用,若其它文件中的函數調用此變量,須用extern聲明,extern int a;這里是聲明,而不是定義
    • 全局變量的生命周期和程序運行周期一樣
    • 不同文件的全局變量不可重名
    • 生命周期,從程序創建到程序銷毀
    • 存儲在數據區

    1.3 靜態變量

    靜態局部變量

    • static局部變量的作用域也是在定義的函數內有效
    • static局部變量的生命周期和程序運行周期一樣,同時static局部變量的值只初始化一次,但可以多次賦值
    • static局部變量若未賦以初值,則由系統自動賦值,數值型變量自動賦初值0,字符串變量賦空字符
    • 存儲在數據區

    靜態全局變量

    • 作用域:定義所在的文件中
    • 生命周期:從程序創建到程序銷毀
    • 存儲位置:存儲在數據區

    1.4 全局函數和靜態函數

    在C語言中,函數默認都是全局的,使用關鍵字static可以將函數聲明為靜態,函數定義為static就意味著這個函數只能在定義這個函數的文件中使用,在其他文件中不能調用,即使在其他文件中聲明這個函數也沒法使用

    對于不同文件中的static函數名字可以相同

    注意:

    • 允許在不同的函數中使用相同的變量名,它們代表不同的對象,分配不同的單元,互不干擾
    • 同一源文件中,允許全局變量和局部變量同名,在局部變量的作用域內,全局變量不起作用
    • 所有的函數默認都是全局的,意味著所有的函數都不能重名,但如果是static函數,那么作用域是文件級的,所以不同的文件static函數名是可以相同的

    2、 內存布局

    2.1 內存分區

    C 代碼經過預處理、編譯、匯編、鏈接,4步后生成一個可執行程序

    代碼區:

    • 程序執行二進制碼(程序指令)
    • 共享、只讀

    數據區:

    • 初始化數據區(data)
    • 未初始化數據區(bss)
    • 常量區

    棧區:

    • 系統為每一個程序分配一個臨時的空間
    • 存儲局部變量、函數信息、函數參數、數組
    • 棧區大小為:1M;在Windows中可以擴展到10M;在Linux中可以擴展到16M

    堆區:

    • 存儲大數據、圖片、音樂、視頻
    • 手動開辟:malloc colloc realloc
    • 手動釋放:free()

    程序在加載到內存前,代碼區和全局區的大小就是固定的,程序運行期間不能改變。然后,執行可執行程序,系統把程序加載到內存,除了根據可執行程序的信息分出代碼區、數據區和未初始化數據區之外,還額外增加了棧區、堆區

    2.2 堆空間開辟和釋放

    #include <stdio.h>
    #include <stdlib.h>
    
    int main() {
        // 開辟堆空間存儲數據
        int* p = (int *)malloc(sizeof(int)*1000);  // 開辟1000個整型數據的空間
        // 使用堆空間
        *p = 123;
        printf_s("%d", *p);
        // 釋放堆空間
        free(p);
        // 將指針置空,防止出現野指針
        p = NULL;
        return 0;
    }
    

    注意:

    • 釋放指針要釋放同一個指針

      #include <stdlib.h>
      
      int main() {
          int* p = (int *)malloc(sizeof(int)*1000);
          p = 123;  // 改變了指針的地址,同時指針偏移也會改變指針的地址,其為無主指針
          free(p);  // 釋放指針會報錯
          return 0;
      }
      

    2.3 內存操作函數

    #include <string.h>

    1. void* memset(void* s, int c, size_t n);

      • 功能:將s的內存區域的前n個字節以參數c填入
      • 參數:
        • s:需要操作內存s的首地址
        • c:填充的字符,unsigned char—— 0 ~ 255
        • n:指定需要設置的大小,單位是字節
      • 返回值:s的首地址
    2. void* memcpy(void* dest, void* src, size_t n);

      • 功能:拷貝src所指的內存內容的前n個字節到dest所指的內存地址上
      • 參數:
        • dest:目標內存首地址
        • src:原內存首地址,注意:首地址不可以重疊
        • n:需要拷貝的字節數
      • 返回值:dest的首地址

      memmove()功能用法和memcpy()一樣,區別在于:當內存空間重疊時,memmove()仍然能處理,不過執行效率更低

    3. void* memcmp(const void* s1, const void* s2, size_t n);

      • 功能:比較s1和s2所指向內存區域的前n個字節
      • 參數:
        • s1:內存首地址s1
        • s2:內存首地址s2
        • n:需比較的前n個字節
      • 返回值:
        • s1 = s2:返回值等于0
        • s1 > s2:返回值大于0
        • s1 < s2:返回值小于0
    posted @ 2022-06-28 14:58  A-L-Kun  閱讀(7)  評論(0編輯  收藏  舉報
    国产美女a做受大片观看