20160129.CCPP體系詳解(0008天)

尹成發表於2016-02-16

程式片段(01):函式.c+call.c+測試.cpp
內容概要:函式

///函式.c
#include <stdio.h>
#include <stdlib.h>

//01.函式:
//  1.函式的作用:重用程式碼,重用功能
//      表象:程式碼的封裝,程式碼的重用
//      實質:功能的封裝,功能的重用
int main01(void)
{
    system("tasklist");

    system("pause");
}

//02.函式的宣告與定義淺析:
//  1.函式的宣告的定義的使用時機不同:
//      函式的宣告用於編譯時期-->編譯檢查
//      函式的定義用於連結時期-->連結檢查
//      函式的真正使用:宣告和定義缺一不可
//  2.由於編譯器的不同所導致的差異:
//      C語言編譯器:寬泛(VS2015自動配置了編譯器所需要的靜態庫Lib的目錄)
//          可以既沒有宣告也沒有定義(只是沒有顯式的在程式碼中標註,但是
//          編譯器能夠自動識別到),編譯器當中有兩個配置選項(庫目錄+附加依賴項)
//          但是,如果編譯器當中沒有配置這兩項(庫目錄和附加依賴項)就會編譯報錯
//          注意:編譯時期需要宣告,連結時期需要實體
//      C++語言編譯器:嚴格
//          必須既有宣告也有定義,必須顯式的在程式碼中進行標註
//          編譯時期需要宣告,連結時期需要定義
//  3.在程式碼當中函式宣告和定義出現的時機:
//      標準做法:函式宣告必須出現在函式呼叫之前
//      函式宣告的位置既可以獨立形式出現,也可以出現於函式體內部,但必須出現
//      在呼叫之前(CCPP同時支援的規則)
//  4.關於形參是否存在形參名稱的問題:
//      函式宣告的時候可以沒有形參名稱,
//      但是,函式實現的時候必須有函式的形參名稱
int getres(int a, int b, int c);//函式的宣告

int main01(void)
{
    //程式碼重用
    int x = 11, y = 12, z = 13;
    x = x*x*x;
    y = y*y*y;
    z = z*z*z;
    int res = x + y + z;
    res = getres(x, y, z);
    printf("%d \n", res);

    int a = 10, b = 12, c = 13;
    a = a*a*a;
    b = b*b*b;
    c = c*c*c;
    int res1 = a + b + c;
    res1 = getres(a, b, c);//函式通過程式碼的重用實現了功能的重用
    printf("res1 = %d \n", res1);

    system("pause");
}

//03.為了讓程式能夠連線成功,在函式進行宣告之後就必須進行函式的定義
//      函式的宣告:只是表明函式存在,你可以使用這個函式的名稱(表明有,可以形式用)
//      函式的定義:確切表明函式存在,你可以使用這個函式的本身(確實有,可以實際用)
int getres(int a, int b, int c)
{
    return a*a*a + b*b*b + c*c*c;
}
///call.c
//01.編譯器的不同特點測試:
//  1.VS2015的編譯器,預設進行了編譯器所需的靜態庫(LIb)的配置:
//      因此,雖然沒有函式的具體宣告,但是C語言程式卻可以靜態庫(Lib)
//      的配置選項進行函式的定位,最終找到函式實體本身
//      注:C語言由於預設對靜態庫(Lib)的配置,因此C語言的編譯比較寬泛
//          有靜態庫(LIb)的路徑,可以自動定義,不以來與函式的確切宣告
//  2.C++的編譯器:嚴格控制
//      要求必須有函式的宣告和定義,才能夠打包成為應用程式
//          編譯的時候需要檢測函式的宣告是否存在?
//          連結的時候需要檢測函式的實現是否存在?
int main02(void)
{
    system("calc");//系統庫函式,標準庫函式

    system("pause");
}
///測試.cpp
#include <stdio.h>
#include <stdlib.h>

int add(int a, int b);//函式宣告

//01.自定義函式的宣告和定義特點:
//  1.剛剛那個是針對於系統函式的宣告和定義的區別
//      自定義函式相對於系統函式而言,就是沒有標準庫(Lib)而已
//  2.由於編譯器所導致的不同:
//      C語言編譯器特點:
//          沒有函式宣告,但是有靜態庫(Lib)配置-->編譯通過
//          沒有函式宣告,有定義,且實體在任意位置-->可能編譯通過,可能編譯不通過
//              放在呼叫之前的定義,編譯通過
//              放在呼叫之後的定義,可能通過,可能不通過
//                  由於C語言編譯器的特點,所以可能檢測的出來,也可能檢測不出來
//                  函式的宣告和定義都可以缺掉,只要靜態庫(Lib)中包含,模糊匹配
//      C++語言編譯器:
//          沒有函式宣告,但是又靜態庫(Lib)配置-->編譯不通過
//          沒有函式宣告,有定義,且實體在呼叫之前可以呼叫
//              要求:函式的宣告和定義缺一不可,準確匹配
int main03(void)
{
    add(1, 2);
    printf("%d \n", add(10, 20));

    system("pause");
}

int add(int a, int b)
{
    return a + b;
}

程式片段(02):C語言函式呼叫例項.c+函式.c+run.c
內容概要:函式的分割+函式的劃分

///C語言函式呼叫例項.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

//01.函式的使用特點:
//  1.C語言當中的異常處理函式abort();
//      (1).用於表明某個位置出現了錯誤
//          (提示方式:以windows彈窗作為異常提醒)
//      (2).函式特點:
//          只是一個提示,點選彈窗之後,程式依然可以繼續執行
//          不會直接中斷整個應用程式(直接表明異常函式abort()的呼叫)
//  2.C語言當中的函式特點:
//      不可以進行函式的巢狀定義!
//      C++語言當中不允許函式的直接巢狀,但是允許間接的通過
//          Lambda表示式實現函式的巢狀形式
int main01(void)
{
    int a;
    int b;

    scanf("%d,%d", &a, &b);
    if (b == 0)
    {
        abort();//處理程式的異常
    }
    printf("%d \n", a / b);

    //void go()
    //{

    //}

    system("pause");
}
///函式.c
#include <stdio.h>
#include <stdlib.h>//std:表明標準靜態庫-->跨平臺靜態庫-->C語言標準跨平臺靜態函式庫(Lib)
#include <Windows.h>//第三方靜態庫:僅僅適用於Windows的靜態函式庫

void run(char *path)//外部函式,C語言當中的程式碼重用(功能重用),主要依賴於函式的使用特點
{//被調函式
    ShellExecuteA(0, "open", path, 0, 0, 1);//預設視窗開啟方式
}

//01.區分主調函式和被掉函式的概念:
//  在那個函式程式碼塊兒中寫其他函式的呼叫語句,那麼:
//      那個函式就是所謂的主調函式
//      其他函式就是所謂的被調函式
int main02(void)
{//主調函式
 // run("\"C:\\Program Files\\Tencent\\QQ\\QQProtect\\Bin\\QQProtect.exe\"");
 // run("C:\\Users\\yincheng01\\AppData\\Roaming\\baidu\\BaiduYun\\baiduyun.exe");

    system("pause");//庫函式,不加標頭檔案,C語言可以,但是為了程式碼規範,還是要新增上標頭檔案的
}
///run.c
#include <stdio.h>
#include <stdlib.h>

//01.函式的組成元素分析:
// 函式的宣告:int getmax(int a, int b);-->末尾的函式宣告結束符(";")分號不允許省略
//  函式的實現:int getmax(int a, int b){return a > b ? a : b;}-->程式碼塊兒("{}")當中的語句就是函式實現語句
//  返回值型別:int-->限制函式的返回值最終型別
//  函式名稱:getmax-->實質即使函式指標-->函式存放函式宣告的地址-->另外還有函式定義的地址(兩個地址不用)
//      C語言當中goto語句的實現原理就如同組合語言當中jump原理-->通過反彙編可以區分(函式宣告地址和函式實現地址的不同)
//      C語言當中的應用程式在應用程式被載入進記憶體之後,就會新建一張函式表(類似於變數表)-->裡面記錄了函式定義的地址
//          於是我們就可以通過函式宣告的地址找到具體函式定義的地址(這是實現劫持的原理:函式指標)
//          改變函式指標的指向,可以讓其具備不同的行為,以至於沒有行為也是通過這個進行控制的
//      函式宣告變數-->儲存函式定義(實體變數<==>普通變數)的地址-->所以函式宣告變數叫做函式指標(存放地址的變數叫做指標變數)
//          所以:函式宣告變數叫做函式指標
//  形式參數列:(int a, int b)-->int a,int b代表的就是實際的引數本身
//  函式執行體:{return a > b ? a : b;}-->函式實體的程式碼塊兒內容
//  函式返回值:return a > b ? a : b;-->return語句表明函式的具體反回值
int getmax(int a, int b);//函式的語句塊兒不允許宣告,所以通過空語句分號(";")進行表示

int getmax(int a, int b)
{
    //int a;//函式體內部定義變數不可以和形式引數名稱重名
    return a > b ? a : b;
}

int main03(void)
{
    printf("%p \n", getmax);

    system("pause");
}

程式片段(03):void.c+fun.c
內容概要:函式的使用和引數

///void.c
#include <stdio.h>
#include <stdlib.h>

int add(int a, int b);//遵循軟體工程規範,在函式呼叫之前必須明確函式的宣告

//01.void型別的使用特點:
//  1.出現位置的不同,意義不同
//      返回值型別位置:
//          表明函式不需要返回值,不用通過return關鍵字顯式的將返回值帶出
//      函式形式引數位:
//          表明該函式無需引數值,明確函式不需要傳入實際引數
//  2.void的使用注意事項:
//      可以用來定義指標型別-->void *-->俗稱乾地址-->沒有明確解析方式的地址
//          -->但是由於地址的大小已經確定(要麼4位元組|要麼8位元組)-->編譯器決定
//              所以知曉儲存一個地址需要開闢多少個位元組
//          -->因此,指標變數的記憶體地址開闢成功
//      不能用來定義變數型別-->void---->因為沒有明確型別,沒有明確的解析方式
//          -->不能描述變數所需要開闢的儲存空間究竟需要多大?
//          -->導致開闢普通變數的記憶體空間失敗
//02.返回值和返回值型別的使用注意事項:
//  1.返回值的型別要求與返回值的型別保持一致!
//      如果不一致將會發生資料型別的轉換(自動型別轉換+自動型別轉換)
//  2.如果返回值的型別採用void描述:
//      C語言採用其他型別的返回值進行返回,那麼編譯器不會報錯,但是返回的值卻可能是不正確的
//      C++語言採用其他型別的返回值進行返回,那麼編譯器直接進行報錯,說型別的不匹配
//03.return關鍵字的作用:
//  1.返回值:將值從被調函式當中帶出
//  2.中斷多層巢狀迴圈的執行(區分於goto語句的實現特點)
//  3.中斷函式的執行
//04.所有的函式,預設的返回值型別都是int型別
//  包括特殊的main函式的預設返回值型別也是int型別
//  只是函式若是沒有明確的宣告返回值型別,而進行返回異常的整數
int main01(void)
{
    printf("%d \n", add(10, 20));
    //void a;//"a":非法使用"void"型別,代表任何型別

    return 1;//返回值應該與返回值型別一致
    //如果函式申明為void,卻用return返回一個其他型別的值,那麼C++編譯器報錯,由於型別不匹配
    //但是C語言的編譯器不會進行報錯

    system("pause");
}

add(int a, int b)
{
    return a + b;
}

int main02(void)
{
    getchar();//根據函式呼叫找到找到函式實體-->函式宣告-->函式實體
    getchar();//引數即使為空,函式的呼叫依然需要新增上小括號("")

    system("pause");
}
///fun.c
#include <stdio.h>
#include <stdlib.h>

void change(int a)//函式的副本機制:int a-->形式引數的宣告
{//讀取暫存器當中的整型值,構建當前函式所需使用的記憶體變數值
    a = 3;
    printf("&change = %p, change = %d \n", &a, a);
}

int main03(void)
{
    //主調函式當中傳遞給被調函式的引數叫做實際引數,簡稱實參
    change(10);//副本,開闢記憶體容納暫存器的的值-->暫存器當中的值可以直接進行讀取使用

    system("pause");
}

//01.函式引數的特點:
//  1.主調函式和被調函式當中的引數是不同的概念:
//      (1).所處的位置不同:
//          棧記憶體不同,不同的函式處於不同的執行時堆疊
//          所以即使名稱相同,也是不同的變數
//      (2).不可以跨函式訪問區域性變數
//          執行時堆疊的不可見特點
//          上下層(執行時堆疊)當中的區域性變數不可以誇堆疊訪問
//  2.主調函式傳遞給被調函式的實際引數的副本可能的儲存位置:
//      未接收-->暫存器-->快取器-->未經使用的常量資料
//      接收了-->棧記憶體-->儲存普通的副本資料,棧記憶體容得下
//      接收了-->堆記憶體-->如果副本資料很大,就必須採用堆記憶體空間進行儲存
int main04(void)
{
    int a = 10;

    printf("&main = %p, main = %d \n", &a ,a);
    change(a);
    printf("%d \n", a);

    system("pause");
}

程式片段(04):輸入輸出.c+return.c
內容概要:return與引數

///輸入輸出.c
#include <stdio.h>
#include <stdlib.h>

int add(int a, int b)//int a, int b這兩個形式引數,只有在被呼叫的時候,才會涉及到自動分配和自動釋放
{
    printf("add1 a = %d, b = %d \n", a, b);
    a = 19;
    b = 29;//修改的是當前被調函式當中的區域性變數,也就是主調函式傳遞進來的實際引數的副本資料
    printf("add2 a = %d, b = %d \n", a, b);

    return a + b;
}

//01.在我看來,傳值和傳址都是一樣的:
//  只不過一個被賦予了普通變數的解析特點->其他
//  一個唄賦予了指標變數的解析特點而已-->陣列
int main01(void)
{
    int a = 10;
    int b = 20;

    printf("%d \n", add(a, b));
    printf("%d \n", add(11, 12));//函式的引數除了陣列以外,都是副本(區別於指標變數接收,還是普通變數接收)
    printf("main a = %d, b = %d \n", a, b);

    system("pause");
}

//02.引數傳遞特點:
//  add_debug(1, 2, 3);-->實參太多
//      C語言引數過多隻會發出警告,結果不保證絕對正確,引數剛好合適,能夠保證結果正確
//  add_debug(1);------->實參太少
//      直接發生變異報錯
//  注:函式引數進棧的順序是從右往左,提取函式引數的資料是從上往下進行提取的
//      例如:(int a, int b);
//          進棧順序:b--->a
//      區分:函式形式引數的進棧順序和函式區域性變數的進棧順序
//03.函式引數進棧的順序嚴格區分:Release環境下進行的測試
//      函式形式引數的進棧順序:
//          資料進棧:從右往左,依次進棧,
//          資料對映:從左往右
//          舉例:傳遞資料1, 2, 3
//              (int a, int b)
//          資料進棧:           資料對映:
//              棧底: 3       ->    丟掉|編譯器預置資料
//                      2       ->     b
//              棧頂:1        ->     a
//      函式區域性變數的進棧順序:
//          從下往上-->程式碼進棧-->掃描區域性變數的時候,變數由下往上進行宣告的
int add_debug(int a, int b)
{
    printf("a = %p, b = %p \n", &a, &b);
    printf("a = %d, b = %d \n", a, b);
    a = 1;
    b = 2;
    int x = 3;
    int y = 4;
    printf("x = %p, y = %p \n", &x, &y);
    printf("x = %d, y = %d \n", x, y);

    printf("\n");
}

int main02(void)
{
    //printf("%d \n", add_debug(1));
    //printf("%d \n", add_debug(1, 2));
    //printf("%d \n", add_debug(3, 12));
    printf("error = %d \n",add_debug(1, 2, 3, 4, 5) );

    system("pause");
}

//04.引數傳遞的注意事項:
//  C語言編譯器中,主調函式傳遞給被調函式的實際引數如果過多:
//      多得資料會被忽略掉,
//      引數個數如果一致,型別一致,書序一致能夠保證結果正確
//  C語言編譯器中,實參和形參的型別要儘量一致,個數也要一致
//      由於C語言編譯器過於寬泛,所以不怎麼嚴格
int add_test(int a, int b)//int a = 11.0賦值的操作,賦值回自動完成型別轉換
{
    return a + b;
}

int main03(void)
{
    printf("%d", add_test(11.9, 2, 3, 5, 10, 12));

    system("pause");
}

//05.小數型別在進行整數的過程當中:
//      只會進行取整運算,不涉及到四捨五入的情景
int add_test1(int a, int b)//return也會完成資料型別的轉換
{
    return 13.9;
}

int main04(void)
{
    printf("%d \n", add_test1(1, 2));

    //int a = 10;
    //a + 1 = 9;

    system("pause");
}
///return.c
#include <stdio.h>
#include <stdlib.h>

//01.C語言編譯器當中的函式特點:
//      如果函式表明了需要返回值型別,需要返回值
//      你如果不通過return關鍵字正確的返回值,那麼編譯器不會進行報錯
//      但是程式最終的結果不正確結果自負
int addx()
{
    return 1;
}

int main05(void)
{
    printf("%d \n", addx());

    system("pause");
}

//02.函式形式引數和返回值詳解:
//  (1).都存在有副本機制:
//      副本資料可能的儲存位置(暫存器-->快取器-->棧記憶體-->堆記憶體)
//  (2).都存在資料型別轉換:
//      自動型別轉換(小-->大)+強制型別轉換(大-->小)
int getnum()
{
    int num;

    printf("%p \n", &num);

    num = 10;

    return 10.9;//return有副本機制,在暫存器,快取,記憶體,堆記憶體(編譯器根據資料特點決定)
    system("notepad");//當前的函式塊兒語句,由於處於return關鍵字之後,所以不會有被執行到的機會
    //int data = num;//副本機制模擬
    //正規化副本機制,都會通過賦值,賦值就會發生自動型別轉換|強制型別轉換特點
}

void show()
{
    system("notepad");

    return;
}

//03.結束多層迴圈的方式特點:
//  goto:結束多層迴圈,但是函式並未彈棧(還未出棧)
//  return:結束多層迴圈,函式發生彈棧(直接出棧)
void showx()
{
    for (int i = 1; i < 100; i++)
    {
        if (i % 13 == 0)
        {
            printf("%d ", i);
            return;//迴圈內部,結束迴圈-->區別於goto迴圈的跳轉特點
        }
    }
}

int main06(void)
{
    //return;//main函式意味著退出

    printf("%d \n", getnum());

    showx();

    system("pause");
}

//04.&getnum();所涉及到的問題分析:
//  1.函式返回的副本資料的原本可能存在於:
//      暫存器-->快取器-->棧記憶體-->堆記憶體
//  1.getnum();這個函式返回的是一個資料
//      具體的一個資料,不涉及到變數概念,不涉及到記憶體概念
//      所以不能通過取地址符進行操作(資料:是右值,不是左值)
int main07(void)
{
    //printf("%d \n", &getnum());//不是左值,右值,右值存在於暫存器內部

    system("pause");
}

int add(int a, int b)//add(int a, int b)-->int
{
    return a + b;
}

int main08(void)
{
    printf("%d \n", add(add(add(1, 2), 3), 4));

    system("pause");
}

內容概要(05):過程.c
內容概要:函式執行過程

#include <stdio.h>
#include <stdlib.h>

int main01(void)
{
    printf("main 上 \n");
    void print1();//C語言建議新增宣告,新增了宣告只有一定不會出錯,沒有宣告可能會出錯
    print1();
    printf("%d \n", add(-1, 0));//引數多了可以編譯,但是不能保證結果正確,引數少了不可以編譯正確
    printf("main 下 \n");

    system("pause");
}

int add(int a, int b)
{
    return a + b;
}

void print1()
{
    printf("print1 上 \n");
    printf("print1 下 \n");
}

程式片段(06):go.c
內容概要:函式引數的運算順序

#include <stdio.h>
#include <stdlib.h>

void show(int a, int b)
{
    printf("a = %d, b = %d \n", a, b);
}

int main01(void)
{
    int a = 5;
    show(a, a++);//6,5

    system("pause");
}

//01.函式形式引數和區域性變數特點詳解:
//  1.所以測試環境均為Release環境(標準)
//  2.分特點詳解:
//      形式引數:
//          資料進棧順序:決定實參執行順序
//                  從右往左
//          資料對映順序:
//                  從左往右
//              資料進棧:       資料對映:
//                  3                丟失(多餘)|垃圾(少了)
//                  2       ->         b
//                  1       ->         a
//          資料地址順序:
//                  先進棧的形式引數位於高地址
//                  後進棧的形式引數位於地地址
//      區域性變數:
//          由於程式碼是從下往上進行進棧的
//          所以變數也是從下往上進行壓棧的
//              先壓棧的變數位於高地址
//              後壓棧的變數位於地地址
//          壓棧過程當中只是第一次初始化資料
//              高地址到低地址的壓棧過程
//          壓棧之後的執行過程決定最終的資料特點
//              低地址往高地址的執行特點(正好適應程式的從上往下執行特點)
int add(int a, int b)
{
    printf("&a = %d, &b = %d \n", &a, &b);
    printf("a = %d, b = %d \n", a, b);
    int x = 1;
    int y = 2;
    printf("&x = %d, &y =%d \n", &x, &y);
    printf("x = %d, b = %d \n", x, y);
    return a + b;
}

int main02(void)
{
    //printf("%d \n", add(1, 2));
    printf("%d \n", add(1, 2, 3));

    system("pause");
}

程式片段(07):main.c
內容概要:CodeBlocks測試

#include <stdio.h>
#include <stdlib.h>


void show(int a, int b)
{
    printf("a=%d,b=%d", a, b);
}

int main()
{//效果一致
    int a = 5;
    show(a, a++);

    getchar();
}

程式片段(08):可變引數.c
內容概要:可變引數

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>//標準引數:模擬可變引數必需的標頭檔案

int add(int num, ...)//...代表可變引數
{
    int res = 0;//結果

    va_list argp;//儲存引數開始的地址
    va_start(argp, num);//從首地址開始,讀取num後面的資料
    for (int i = 0; i < num; i++)
    {
        res += va_arg(argp, int);//讀取一個資料並且按照int型別進行二進位制資料的解析
    }
    va_end(argp);//結束讀取

    return res;
}

int main01(void)
{
    printf("%d \n", add(3, 1, 2, 3));
    printf("%d \n", add(4, 1, 2, 3, 4));
    printf("%d \n", add(5, 1, 2, 3, 4, 5));

    system("pause");
}

int main02(void)
{
    printf("%d, %d, %d \n", 1, 2, 3);
    printf("%d, %s, %c, %d \n", 1, "123", 'A', 4);

    system("pause");
}

//01.可變引數使用方式一:
//  1.將第一個引數作為確定可變引數列表當中所儲存的引數總個數
//  2.可變引數使用流程總結:
//      (1).包含標頭檔案:
//          #include <stdarg.h>
//      (2).確定函式宣告:
//          void vatest(int count, ...);
//      (3).進行引數使用:
//          va_list argp;//儲存可變引數列表的首地址(類似於陣列的特點)
//          va_start(argp, count);//從首地址開始,讀取count個引數
//          va_arg(argp, type);//按照type型別讀取當前可變引數列表當中讀取到的位置所在的資料
//          va_end(argp);//結束可變引數列表的讀取狀態
void go(int num, ...)
{
    va_list argp;//儲存可變引數列表開始的首地址
    va_start(argp, num);//從首地址開始,讀取num個的資料
    for (int i = 0; i < num; i++)
    {
        char str[50];
        //sprintf(str, "%s", va_arg(argp, char *));
        //system(str);//只要呼叫一次va_arg就從可變引數列表當中讀取一個引數
        system(va_arg(argp,char *));
        //讀取一個二進位制資料並且按照char *型別解析
    }
    va_end(argp);//結束讀取
}

int main03(void)
{
    go(3, "notepad", "calc", "tasklist & pause");

    system("pause");
}

//02.可變引數的使用方式二:
//  1.不採用可變引數前置引數作為讀取結束條件,而是按照可變引數列表的結束特點進行讀取
//  2.不定長可變引數列表的使用特點:
//      (1).引入標頭檔案:
//          #include <stdarg.h>//支援可變引數列表的使用
//      (2).函式宣告特點:
//          void vatest(int start, ...);//可變引數列表的函式宣告
//          va_list argp;//儲存可變引數列表的首地址(類似於陣列原理)
//          vastart(argp, start);//暫定可變引數列表當中引數的讀取個數
//          int argvalue = start;//確定可變引數列表的首個引數
//          do
//          {
//              int value=argvalue;//使用可變引數列表當中的資料
//              argvalue = va_arg(argp, int);//不斷的按照指定型別進行讀取
//          }while(argvaue != -1);
//          va_end(argp);結束讀取
void showint(int start, ...)
{
    va_list argp;//儲存引數開始的地址
    va_start(argp, start);//從首地址開始讀取資料,暫定為讀取start個資料
    int argvalue = start;//第一步初始化
    do
    {
        printf("\n %d", argvalue);
        argvalue = va_arg(argp, int);//不斷讀取
    } while (argvalue != -1);
    va_end(argp);//結束讀取
}

int main04(void)
{
    //showint(1, 2, 3, 4, -1);
    showint(1, 2, 3, 4, 5, -1);

    system("pause");
}           

程式片段(09):C宣告.c+函式宣告.c+int.c+全域性與區域性衝突
內容概要:C語言函式宣告+全域性變數與區域性變數

///C宣告.c
#include <stdio.h>
#include <stdlib.h>

//01.使用函式的特點:
//  1.()用於對函式進行標識
//  2.進行函式呼叫必須明確呼叫的型別:
//      區分變數訪問和函式呼叫("()")
//02.C語言的編譯器特點:
//  1.由於VC2015這個編譯器當中自動包含了庫目錄和附加依賴項
//      所以使用C語言函式的時候,可以沒有宣告語句,因為C語言編譯器
//      VC2015會自動到靜態庫目錄和附加依賴項當中去進行查詢,
//      自動查詢所需呼叫的函式,引數多了,少了都可以進行呼叫(前期,可以;後期,不可以)
//  2.函式呼叫觸發了C語言編譯器VC2015的自動定位功能
//03.庫函式的查詢:
//  分為系統庫函式和定義庫函式的查詢
//      C語言編譯器VC2015支援自動查詢
//      C++編譯器不支援自動查詢
int main01(void)
{
    printf("Hello China! \n");//()是個函式
    printf;//引用函式必須要進行宣告

    system("pause");
}

int main02(void)
{
    add(2, 3);
    print();
    //add;

    system("pause");
}

int print(){}

int add(int a, int b)
{
    return a + b;
}
///函式宣告.c
#include <stdio.h>
#include <stdlib.h>

//01.C語言當中的宣告和定義特點:
//  1.宣告可以有多個,但是定義只能有一個
//  2.函式宣告的時候可以不用指明形參的名稱
//      但是定義的時候必須指定形參的名稱
//      並且函式宣告的形參名稱和函式實現的形參名稱可以不同
//  3.函式的宣告和定義與變數的宣告和定義類似
int add(int a, int b);//宣告
int add(int x, int y);//宣告
int add(int h, int j);//宣告
int add(int k, int l);//宣告

int main03(void)
{
    printf("%d", add(1, 2));

    system("pause");
}

int add(int a, int b)
{
    return a + b;//函式的定義
}

//int add(int a, int b)
//{//宣告可以有多個,但是定義只能有一個
//  return a + b;//函式的定義
//}
///int.c
#include <stdio.h>
#include <stdlib.h>

//全域性變數:int a = 10;
//int a;//全域性變數當做宣告看待,如果沒有初始化,將會被編譯器預設賦予0
int a;
//int a = 9;//int a;//全域性變數宣告,int a = 10;//全域性變數定義,宣告可以有多個,定義只能有一個

int main04(void)
{
    printf("%d \n", a);

    system("pause");
}

//01.區域性變數和全域性變數的使用總結:
//  1.是否具備宣告和定義之間的區別:
//      函式和全域性變數都有區別
//      區域性變數沒有區別(都當做定義來對待)
//  2.全域性變數的生命週期:
//      程式程式碼一旦載入進程式碼區就已經存在了
//      全域性變數優先於main函式的存在
//  3.全域性變數的作用域:
//      從當前檔案的定義位置開始,到跨檔案的範圍
//      內都可以進行訪問的到
int main05(void)
{
    //區域性變數沒有宣告和定義的區別
    //int a = 10;//變數重名,區域性變數

    //int a;
    ////int a = 10;//區域性變數
    //int a;
    //int a;
    //int a;

    a = 9;

    system("pause");
}

void go()
{
    a = 11;
}
///全域性與區域性衝突.c
#include <stdio.h>
#include <stdlib.h>

//01.全域性變數和區域性變數內容總結:
//  1.全域性變數很容易被區域性變數覆蓋
//  2.全域性變數可以被多個函式所共享,方便於讀寫操作
//  3.全域性變數在如果只是進行宣告瞭,但是沒有被定義
//      那麼系統會為其定製一個預設的初始化值0
//  4.全域性變數可以在跨檔案的情況下進行呼叫:
//      容易出現全域性變數重合(型別相同,名稱相同)      
//  5.區域性變數和全域性變數重名的情況之下,會覆蓋掉
//      全域性變數
//  6.當區域性程式碼塊兒當中存在和全域性變數相同的變數
//      那麼區域性程式碼塊兒的操作將遮蔽對全域性變數的操作
//      相當於對全域性變數的操作無效
int a;
int a;
int a = 3;

int main06(void)
{
    printf("%d \n", a);

    system("pause");
}

int main07(void)
{
    int a = 10;
    printf("%d \n", a);//區域性變數覆蓋全域性變數,重名
    {
        printf("%d \n", a);
        int a = 13;
        printf("%d \n", a);//內部塊兒語句會遮蔽外部變數
    }
    printf("%d \n", a);//區域性變數覆蓋全域性變數,重名

    system("pause");
}

程式片段(10):test.cpp
內容概要:宣告與定義差別

#include <stdio.h>
#include <stdlib.h>

//01.函式的宣告和定義詳解:
//  1.函式的宣告可以有多個,定義只能有一個
//  2.函式的宣告可以沒有引數名稱,但是必須有引數型別
//  3.函式宣告的引數名稱可以和函式的定義的引數名稱不一致
//      但是要求型別必須一一對應

int add(int a, int b);//宣告要與定義相匹配
int add(int x, int y);//宣告的變數名可以省略,可以和定義的變數名不一致,但是要求型別必須一致

int add(int a, int b)
{
    return a + b;
}

int main01(void)
{
    add(1, 2);

    system("pause");
}

程式片段(11):baidu.c+stack.c
內容概要:函式呼叫流程簡單遞迴

///baidu.c
#include <Windows.h>

//01.動態庫(Dll)知識+遞迴呼叫知識:
//  (1).Dll注入技術可以讓任何程式掛掉
//  (2).針對於像360這樣的安全軟體
//      需要採用sys層面的技術進行破壞
//      因為360安全衛士是基於驅動層面開發
//  (3).如何匯出動態庫(Dll)?
//      1).在原始函式宣告之前新增
//          _declspec(dllexport)
//      2).配置專案屬性(配置型別)
//          動態庫(.dll)
_declspec(dllexport) void go()
{
    Sleep(1);
    go();
}
///stack.c
#include <stdio.h>
#include <stdlib.h>

//線性遞迴001:將一個整數進行逆序輸出
//  遞迴函式的規律總結:
//      是否需要返回值?
//          如果有累變(加,減,乘,除)效果,就需要返回值型別,否則一般情況之下是不需要返回值型別的
//      是否需要形式引數?
//          如果涉及到遞迴函式當中每層遞迴函式呼叫的資料使用,只是數值意義上的使用,就需形式引數
//              資料使用等同於資料關聯,等同於遞迴函式呼叫層當中的資料傳遞,形參變數資料傳遞
//          是否需要類似於for迴圈結構的迴圈初始化條件?如果有,就需要形式引數,如果沒有,則無需
//      是否逐漸逼近類似於for迴圈的迴圈終止條件?
//          遞迴入口+遞迴出口
//      是否涉及到資料的列印顯示順序?
//          列印語句如果需要順序,就寫於遞迴呼叫之前;
//          列印語句如果需要逆序,就謝宇遞迴呼叫之後.
void revInt(unsigned int value)//類似於for迴圈的迴圈初始化條件
{
    unsigned int quotient = value / 10;//空間複雜度1+時間複雜度1
    if (quotient != 0)//類似於for迴圈的迴圈判斷條件
        revInt(quotient);//類似於重複一次for迴圈的迴圈執行體
    putchar(value % 10 + '0');//列印順序為逆序(由於執行時堆疊的即時列印特點決定)-->這樣列印的原因是因為跨平臺性可移植性比較好!
}

//線性遞迴002:輸入9,就順序|逆序列印從1~9之間的整數
void printInt1(int value)
{
    //putchar(value + '0');//逆序列印
    if (value - 1 > 0)//時間複雜度2
        printInt1(value - 1);
    putchar(value + '0');//順序列印
}

//線性遞迴003:列印任意一個區間[value1,value2]之間的整數
//  要求一:(順序|逆序)
//  要求二:從value1-->value2|value2-->value1
void printInt2(int value1, int value2)
{
    printf("%d \n", value1);
    if ((value1 + 1) <= value2)
        printInt2(value1 + 1, value2);
}

//線性遞迴004:列印字串當中的每一個字元(反轉效果)
//  1.嚴格區分字元陣列和字元指標陣列之間的區別
//  2.putchar();和printf();函式之間的區別
//      putchar();不具備處理字元指標所指向的實體的作用
//      printf();具備處理字串指標所指向的實體的作用
//  3.putchar();每次只會列印單個字元,所有的字元拼裝在
//      一起之後,就是一個字串
//  4.putchar();遇到字元就直接列印字元本身,不會出現變故
//      放在括號內與括號外是有區別的(是否具備判斷效果)
//      決定最後一次列印的特點
void printStr(char *str)//類似於for迴圈的迴圈初始化條件
{
    if (*str)//類似於for迴圈的迴圈判斷條件
    {
        //printStr(str + 1);//類似於for迴圈的迴圈趨於結束的條件
        printStr(++str);//簡化形式
        putchar(*str);//類似於for迴圈的迴圈執行體-->putchar();不具備處理字元指標所指向的實體的作用
    }//如果不將putchar(*str);放在括號的內部,那麼最後一層遞迴函式在進行列印的時候會將NUT|('\0')|0給列印出來
    //也就是最終多列印了一個不可見字元
}

//線性遞迴005:列印任意一個整數的階乘結果
unsigned int calFact1(unsigned int value)//int表明遞迴函式的一層函式呼叫就能返回該階乘結果,unsigned int num表明類似於for迴圈的迴圈初始化條件,或者說要做一件事情,直接所需的引數
{//如同:我要求取某個數的階乘,你就得給我這個資料,我就根據這個資料算出一個階乘結果反饋給你
    if (0 == value || 1 == value)//類似於無限迴圈的結束條件,也就是遞迴函式的出口,結束最後一層遞迴函式的呼叫,不用再進行遞迴呼叫,而且不用再執行最後一層遞迴函式剩餘的語句(直接出結果)
        return 1;//由於return關鍵字的特殊性,所以最後一層遞迴函式的執行依賴於它-->return直接終止函式,所以不會在執行一層遞迴呼叫以及一層遞迴呼叫之後的語句
    calFact1(value - 1);//讓無限迴圈不斷的執行下去,至於迴圈的終止條件我們無需關注,因為上面一段兒已經決定了
    return value * calFact1(value - 1);//對於該行語句,不用關注其執行流程,只需關注,value=value*value!,只是用於一次遞迴函式的呼叫就能完成意向功能,剩餘遞推關係讓計算機去做,我們不關注
}

unsigned int calFact2(unsigned int value)
{
    if (0 == value || 1 == value)
        return 1;
    else//原理:一次求解,絕對有結果,至於結果的遞推關係我們無需去關注,只需要關注的是一次遞迴函式的呼叫到底能夠完成什麼樣兒的功能,至於如何遞推,如果計算,那都是計算機的事情
        return value * calFact2(value - 1);
}

//線性遞迴006:將一個正整數轉化為其的二進位制表現形式列印出來
void printIntToBin1(unsigned long value)//使用long型別意味著更好的程式跨平臺性(可移植性)-->不像int型別(16位佔用2個位元組(short),32位以上佔用4個位元組(long))-->long始終佔用4個位元組
{
    unsigned long quotient = value / 2;//空間複雜度1+時間複雜度1
    if (0 != quotient)
        printIntToBin1(quotient);//不斷的執行列印除以2之後的餘數(二進位制位)
    putchar((value % 2) + '0');//餘數逆置,一次遞迴呼叫意味著逆序列印一個二進位制位,即使商為0,也需要列印出這個商為0情況之下的餘數0
}

void printIntToBin2(unsigned long value)
{
    unsigned long remainder = value % 2;
    if (value / 2 > 0)//時間複雜度2
        printIntToBin2(value / 2);
    //putchar(0 + i);
    putchar(remainder ? '1' : '0');
}

//線性遞迴007:迴圈轉遞迴剖析
//  1.任何一個迴圈都可以轉換為遞迴
//  2.任何一個遞迴都可以轉化為迴圈+棧
void loopToRecursion(long value)
{
    printf("%d, %p \n", value, &value);
    if (value < 9)
        loopToRecursion(value + 1);
}

//01.遞迴的分類:
//  1.函式呼叫方式:
//      直接呼叫自己-->直接遞迴-->簡單遞迴
//      間接呼叫自己-->間接遞迴-->複雜遞迴
//  2資料結構模型:
//      線性遞迴:f(n)=f(n-1)+n;
//      樹狀遞迴:f(n)=f(n-1)+f(n-2);
//      圖狀遞迴:
//02.遞迴的要點:
//  1.遞迴的滿足要點:
//      遞迴入口+遞迴出口
//  2.遞迴的函式要點:
//      執行時堆疊
//03.所涉及到的知識點:
//  任何一個(0~1)之間的整數加上一個('0');
//  那麼該表示式所獲得的最終結果就是該
//  整數所對應的ASCII碼值
int main01(void)
{
    //revInt(1234);

    //printInt1(9);

    //printInt2(-55, 55);

    //printStr("123456789");
    //char str[10] = "123456789";//區分字元陣列和字元指標陣列
    //printStr(str);

    //printf("%d \n", calFact1(10));
    //printf("%d \n", calFact2(10));

    //printIntToBin1(106);
    //printIntToBin2(106);

    //loopToRecursion(0);

    system("pause");
}


//04.採用無線迴圈列印一段兒字串
int main02(void)
{
    //system("notepad");//同步函式,一次只開啟一個記事本,需要等待使用者結束這個記事本在往下執行
    printf("12345");
    main02();
}

//05.輸入一個整數,就列印整數個字串
void intPrintStr(char *str, unsigned long value)
{
    if (0 == value)
        return;
    if (value - 1 > 0)
        intPrintStr(str, --value);
    printf("%s \n", str);
}

//06.控制Notepad的執行次數
void printNontepad(unsigned long value)//void:只列印資料,不需要返回 value:for迴圈的初始化條件
{
    if (0 == value)//value:判斷for迴圈是否啟用迴圈執行體
    {
        return;
    }
    else
    {//5->4->3->2->1:五個對映對-->執行五次
        system("notepad");//這句話放在前面還是後面都是一樣的
        printNontepad(value - 1);//重複執行一次回退的遞迴迴圈層
    }
}

//07.輸入任意一個正整數N,用遞迴實現從1~N之間的累加
unsigned long addNum(unsigned long value)
{
    if (1 == value)
        return 1;
    return addNum(value - 1) + value;
}

int main03(void)
{
    //intPrintStr("notepad", 5);

    //printNontepad(5);

    //printf("%d \n", addNum(100));

    system("pause");
}

程式片段(12):線性遞迴.c+樹狀遞迴.c+漢諾塔.c
內容概要:遞迴

///線性遞迴.c
#include <stdio.h>
#include <stdlib.h>

//01.遞迴運算常用解析思想:
//  1.數學歸納法
//  2.例如:對等差數列的描述
//      f(0)=0;
//      f(n)=f(n-1)+x;
//      注:關於關係式的遞迴推導我們不用關心,因為計算機內部自己會去進行推導
//          記住!計算機最大的用處不是思考,而是計算
//02.main函式的特點:
//  如果為main函式定義一個int型別的變數,
//      那麼編譯器會自動為該int型別的變數預設初始化一個1(預設初始化)
//      至於手動初始化需要通過命令列對程式進行啟動
void main01(void)
{
    main01();//通過遞迴實現的死迴圈
}


//03.通過遞迴模擬迴圈實現迴圈次數的控制:
void loopToRecursion(unsigned int value)
{
    if (value >= 5)
        return;
    else
        loopToRecursion(value + 1);
    system("notepad");
}

//04.求取0到任何一個正整數的之間的所有正整數的和?
//  f(100)=100+f(99);
//  f(100)=100+99+f(98);
//  f(100)=100+99+98+f(97);
//  f(n)=n+f(n-1);
unsigned long countInt(unsigned long value)
{
    if (value == 1)
        return 1;
    return value + countInt(value - 1);
}

void uLongToBin(unsigned long value)
{
    unsigned long   quotient = value / 2;
    if (quotient != 0)
        uLongToBin(quotient);
    putchar(value % 2 ? '1' : '0');
}

int main02(void)
{
    //loopToRecursion(0);

    //printf("%lu \n", countInt(100));

    uLongToBin(100);

    system("pause");
}
///樹狀遞迴.c
#include <stdio.h>
#include <stdlib.h>

//斐波那契數列:
//  實際問題:
//      1對兔子,2個月以後可以生育,從可以開始生育之後,每個月都能生育一對兔子
//  數學描述:
//      1------->1
//      2------->1
//      3------->2
//      4------->3
//      5------->5
//      6------->8
//      f(n)=f(n-2)+f(n-1);
//  數學描述的特點:
//      f(n-2):
//          描述的是這個月較上個月能夠多增加的兔子數目
//          相差2的原因是因為一對兔子只有相隔兩個月才具備生育一對兔子的能力;
//              也就才會生育一對兔子
//      f(n-1):
//          描述的是上個月所有的兔子總計數目
//      f(n):
//          本月總共的兔子數目
unsigned long countRabbit1(unsigned long month)
{
    if (1 == month || 2 == month)
        return 1;
    return countRabbit1(month - 2) + countRabbit1(month - 1);
}

void countRabbit2(unsigned long month)
{
    if (1 == month || 2 == month)
        printf("1 \n");
    else
    {
        int f1 = 1;
        int f2 = 2;
        int f3 = f1 + f2;
        for (int i = 3; i < month; i++)
        {//通過迴圈輪替的方式向前進行推進計算(計算機處理計算問題)
            f3 = f1 + f2;
            f1 = f2;
            f2 = f3;
        }
        printf("f3= %lu \n", f3);
    }
}


//02.樹狀遞迴內容總結:
//  1.樹狀遞迴速度很慢,遞迴很慢,函式的呼叫和返回都需要時間
//  2.任何遞迴都可以轉換為迴圈加棧
//      遞迴=迴圈+棧
int main01(void)
{
    printf("%lu \n", countRabbit1(40));

    countRabbit2(40);

    system("pause");
}
///漢諾塔.c
#include <stdio.h>
#include <stdlib.h>

//01.遞迴解決問題的思想:
//      1.明確需要解決的問題是什麼?
//          確定遞迴函式的宣告
//      2.明確解決問題的重複步驟是什麼?
//          確定遞迴函式的實現
//      3.明確遞迴的入口和出口條件?
//          什麼時候開始遞迴;
//          什麼時候結束遞迴
void hanoiTower(unsigned long num, char x, char y, char z)//類似於for迴圈的迴圈初始化條件
{//只需要列印結果,不需要累變特點-->void;函式的實際意義-->hannoTower(unsigned long num, char x, char y, char z);
    if (1 == num)
    {//類似於for迴圈的迴圈判斷條件-->遞迴終止繼續執行的條件
        //printf("%c-->%c \n", 'A', 'C');//直接搬動
        printf("%c-->%c \n", x, z);
        return;
    }//遞迴狀態時刻被保留與堆疊當中-->當前函式在執行時所能訪問的內容只有執行時堆疊當中的內容
    //類似於for迴圈的迴圈執行體內容-->通用問題化解方式
    hanoiTower(num - 1, x, z, y);//A-->B                                                   
    printf("%c-->%c \n", x, z);//A-->C
    hanoiTower(num - 1, y, x, z);//B-->C
}

int main04(void)
{
    hanoiTower(4, 'A', 'B', 'C');

    system("pause");
}

相關文章