20160208.CCPP體系詳解(0018天)

尹成發表於2016-02-21

程式片段(01):main.c
內容概要:PointWithOutInit

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

//01.野指標詳解:
//  1.野指標:沒有進行初始化操作的指標-->由於該指標變數內部所儲存的地址是個隨機值,因此是野地址(型別含義:指標)
//    注:指標型別的變數必須在其被建立的時候就需要進行初始化操作,否則就成了野指標,所謂野指標就是亂指向的指標,形成的就是一個隨機垃圾地址
//  2.胡亂使用野指標所造成的現象:
//    (1).指向驅動程式所屬記憶體,將會出現藍屏現象
//       (2).指向普通的應用程式,會將本身的這個應用程式結束掉
int main01(void)
{
  int * p;
  printf("%d \n", *p);//

    system("pause");
}

程式片段(02):01.地址.c+02.內容.c+03.指標實戰.c+04.指標副本機制.c
內容概要:指標地址與取值運算子

///01.地址.c
#include <stdio.h>
#include <stdlib.h>

int get()
{
    return 10;
}

//01.嚴格進行變數區分:
//  1.普通變數和指標變數
//  2.嚴格的變數型別:
//      常規型別+特殊型別
//02.對變數取地址的操作發起於暫存器當中
//  因此地址也生成於暫存器變數當中,C語言無法直接訪問
//03.關於取地址符不能操作的兩種情況:
//      &&intNum;-->C語言無法直接操作暫存器變數
//      &get();-->返回值儲存與暫存器的(cache)區,或者程式的(備份記憶體),這兩塊兒記憶體都不可直接訪問
int main01(void)
{
    int intNum;
    &intNum;//這裡獲取一個宣告但沒有初始化的普通變數的所屬地址可以,但是如果是指標型別的變數就不行了
    //&&intNum;//&intNum這個表示式所代表操作執行於暫存器當中,在暫存器當中使用暫存器變數進行儲存,因此C語言無法進行暫存器變數的操作
    //  而且&intNum所獲得的資料來自於CPU-->暫存器-->程式碼區-->符號表中,因此不能通過取地址運算子進行操作(C語言相對於組合語言的缺點)
    //&get();//get();的返回值,也就是函式的返回值可能儲存於兩個常見位置:暫存器的緩衝區(cache),應用程式的備份記憶體,因此返回值不可取地址

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

//01.取值運算子("*")與取地址運算子("&"):
//  1.星號("*")表示取值運算子,也就是根據指標訪問記憶體塊兒
//  2.與號("&")表示取地址運算子,也就是根據變數的記憶體實體獲取變數的地址
//02.對指標變數使用取值運算子(星號:"*")的使用規律:
//  用原始指標型別的指標級別減去取地址運算子的個數;
//  就是當前所訪問的具體變數(普通變數和指標變數)
//      結果為0-->普通變數
//      結果為1-->一級指標變數
//      結果為2-->二級指標變數         
//      結果為n-->N級指標變數
int main02(void)
{
    //*(001) = 1;//由於常量讀取於程式碼區符號表,產生於CPU暫存器,因此C語言不能夠直接進行訪問
    int intVar = 10;
    *(&intVar) = 3;//星號(*)表示取值運算子,也就是根據指標訪問記憶體區塊兒
    printf("%d \n", intVar);

    system("pause");
}

//03.取值運算子(星號:"*")的操作物件實質必須是指標
//  注:*&intVar-->嚴格區分執行流程
int main03(void)
{
    int intVar = 10;
    *&intVar = 3;//*&這兩個符號可以同時進行出現,操作流程為:取地址運算子(與號:"&")先進行取地址操作,再根據取值運算子(星號:"*")根據指標獲取該指標所指向的記憶體實體|資料實體
    //**&intVar;//編譯報錯-->由於*&intVar之後的結果為普通變數型別(int)-->取值運算子的操作物件必須是指標型別,也就是含有(星號:"*")的型別
    printf("%d \n", intVar);

    system("pause");
}

//04.凡是涉及到的操作和運算的步驟都是由CPU在暫存器當中進行執行的,這裡的強制型別轉換屬於"讀取"並"轉換"的操作
//  因此屬於暫存器操作,要進行間接修改資料,必須得先獲取到相應的指標,而且指標型別必須是可以修改的,這樣就有可能涉及到
//  指標變數的型別轉換操作
//05.*(int *)&num這段兒程式碼的詳細解釋:
//  0.這些符號都是用於對變數名進行操作的
//      注:對變數進行的操作實質其實是對記憶體實體(資料實體)直接進行操作
//  1.取地址運算子(與號:"&")最接近於變數名稱,所以優先執行
//  2.強制型別轉換操作(int *)次接近於變數名稱,所以其次執行
//  3.取值運算子(星號:"*")再次接近於變數名稱,所以再其次執行
int main04(void)
{
    const int num = 3;//在C語言中,Const關鍵字在處理普通變數的時候,只能夠避免對變數的直接修改,但是避免不了間接修改(普通變數適用,指標變數稍有差別)
    //num = 1;//直接修改,編譯報錯
    (int)num;//對任何變數的強制型別轉換操作,強制轉換之後的數值產生於CPU的暫存器
    *(int *)&num = 10;//int *型別轉換,這裡要特別注意,const int需要被看做為整體的一個型別進行處理
    printf("%d \n", num);

    system("pause");
}

//06.關於空型別的指標與空指標的區分:
//  空型別的指標:
//      嚴格說是空型別的指標變數-->該指標變數沒有具體型別,只是用於儲存地址(沒有型別含義)而已
//      因此不能直接對空型別的指標變數進行資料實體的訪問操作,沒有明確解析步長和解析方式
//  空指標:
//      就是用於標識指標變數沒有儲存任何有實際意義的地址-->空置的指標變數而已
//      實質形式:NULL-->(void *)0
//  區分:空型別的指標是左值,空指標屬於右值
int main05(void)
{
    int intVar = 10;
    intVar = 3;
    void * pIntVar = &intVar;//空型別的指標多用於多執行緒的資料傳送處理
    //printf("%d \n", *p);//不可以直接操作空型別的指標

    system("pause");
}

void change01(int * pNum)//副本機制,陣列除外
{
    *pNum = 10;
}

int main06(void)
{
    int num = 20;
    change01(&num);
    printf("%d \n", num);

    system("pause");
}
///03.指標實戰.c
#include <stdio.h>
#include <stdlib.h>

//01.如何快速確定一個變數的所屬型別?
//  變數名稱對稱法(直接看宣告變數的程式碼塊兒,進行直觀對稱得出)
int main07(void)
{
    double db = 10.8;
    double dbl = 10.9;
    double * p = &db;
    printf("%d \n", sizeof(p));//指標變數-->型別(double *)-->佔用4個記憶體位元組
    printf("%d \n", sizeof(*p));//資料實體-->型別(double)-->佔用8個記憶體位元組
    *p = 10.6;
    printf("db = %lf, dbl = %lf, *p = %lf \n", db, dbl, *p);//10.6 10.9 10.6
    p = &dbl;
    printf("db = %lf, dbl = %lf, *p = %lf \n", db, dbl, *p);//10.6 10.9 10.9
    *p = 9.2;
    printf("db = %lf, dbl = %lf, *p = %lf \n", db, dbl, *p);//10.6 9.2 9.2
    dbl = 2.1;
    printf("db = %lf, dbl = %lf, *p = %lf \n", db, dbl, *p);//10.6 2.1 2.1

    system("pause");
}
///04.指標副本機制.c
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>

//01.函式形式引數的副本機制:
//  1.在C語言當中,函式形式引數傳參的副本機制只有陣列例外,因為提升效率,節約記憶體拷貝
//  2.跨函式改變變數(普通變數|指標變數)的傳參要素
//      被掉函式修改主調函式當中的普通變數,需要普通變數的地址
//      被掉函式修改主調函式當中的指標變數,需要指標變數的地址
//  注:總之在被掉函式當中修改主調函式的變數,無論是普通變數還是指標變數,必須傳遞變數的地址
//      否則絕對不行!!!
//void change02(int a, int b);//副本機制,被掉函式需要修改主調函式當中的變數,需要由主調函式向被掉函式傳遞該待修改變數的所屬地址
void change02(int * pa, int * pb)
{
    printf("change02:&pa = %p, &pb = %p \n", &pa, &pb);
    //int * pTemp;//某些編譯器會編譯不通過!
    int * pTemp = pa;
    pa = pb;
    pb = pTemp;
}

//02.區分指標變數和指標:
//  指標變數:含記憶體塊兒
//      用於儲存指標的變數
//  指標:不含有記憶體塊兒
//      是一個具有型別含義的地址
//      是一個具有型別含義的數值
//  注:使用指標變數的實質就是使用指標!
int main08(void)
{
    int a;
    int b;
    int * pa = &a, *pb = &b;
    //scanf("%d%d", &a, &b);//scanf();函式後面所需的是"指標"
    scanf("%d%d", pa, pb);
    printf("main: &pa = %p, &pb = %p \n", &pa, &pb);
    if (*pa > *pb)
    {
        change02(pa, pb);//間接修改無法成功
        int temp = a;//直接修改
        a = b;
        b = temp;
    }
    printf("a = %d, b = %d \n", a, b);

    system("pause");
}

int main09(void)
{
    int a;
    int b;
    int * pa = &a, *pb = &b;
    scanf("%d%d", pa, pb);
    if (*pa > *pb)//對應指向的資料實體
    {
        int * pTemp = pa;//交換指標
        pa = pb; 
        pb = pTemp;
    }
    printf("a = %d, b = %d \n", a, b);
    printf("pa= %d, pb = %d \n", *pa, *pb);

    system("pause");
}

void change03(int * p1, int * p2)
{
    printf("change03: &p1 = %p, &p2 = %p \n", &p1, &p2);
    int pTemp = *p1;
    *p1 = *p2;
    *p2 = pTemp;
}

void change04(int * p1, int * p2)
{
    printf("change04: &p1 = %p, &p2 = %p \n", &p1, &p2);
    //int * pTemp;//野指標:沒有經過初始化的指標-->某些編譯器直接編譯報錯
    int * pTemp = NULL;//使用空指標來標識指標變數沒有指向任何位置
    *pTemp = *p1;//錯誤:訪問了不該訪問的地址0
    *p1 = *p2;
    *p2 = *pTemp;
}

int main10(void)
{   
    int a;
    int b;
    int * pa = &a, * pb = &b;
    scanf("%d %d", pa, pb);
    if (*pa > *pb)//對比指向的資料實體
    {
        //change03(pa, pb);
        change02(*pa, *pb);//函式傳遞資料,傳遞的都只是副本而已
    }
    printf("a = %d, b = %d \n", a, b);
    printf("*pa = %d, *pb= %d \n", *pa, *pb);

    system("pause");
}

void change05(int * p1, int * p2)
{
    printf("change05: &p1 = %p, &p2 = %p \n", &p1, &p2);
    int * pTemp = p1;
    p1 = p2;
    p2 = pTemp;
}

//03.無論是指標變數的操作還是對指標的操作:
//  都必須明確其型別
int main11(void)
{
    int a;
    int b;
    int * pa = &a, *pb = &b;
    scanf("%d%d", pa, pb);
    printf("main: &pa =%p, &pb = %p \n", &pa, &pb);
    printf("main pa = %p, pb= %p", pa, pb);

    system("pause");
}

程式片段(03):01.執行緒.c
內容概要:多執行緒引數傳遞

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <process.h>

//01.空型別的指標-->空型別的指標變數:
//  只是標識該型別的變數具備儲存地址的能力,嚴格區分地址與指標的不同
void run(void * p)//採用空型別的一級指標變數可以儲存任何型別的地址:只是具備儲存地址的能力
{
    int * pTemp = p;//為該地址賦予實際的意義(地址-->指標)-->具備(解析步長+解析方式)的能力
    char str[100] = { 0 };
    sprintf(str, "鋤禾日當午%d \n", *pTemp);
    MessageBoxA(0, str, "天朝很苦!", 0);
}

//02.關於單執行緒和多執行緒的實現問題:
//  1.要看CPU到底有多少個邏輯核心?
//      邏輯核心只有一個無法實現多執行緒的
//  2.要看使用的的執行緒技術?
//      使用單執行緒技術無法實現多執行緒訪問
//      使用多執行緒技術既可以實現單執行緒訪問也可以實現多執行緒訪問
//03.明確多執行緒情況下的一個應用程式的堆疊使用情況:
//  1.單程式情況之下的堆疊使用情況
//  2.各條執行緒共享單程式的堆記憶體
//  3.各條執行緒獨享各自的棧記憶體
//  注:當主執行緒開啟輔助輔助執行緒的時候!
//      主執行緒所佔用的棧記憶體相對於其他輔助執行緒就是共享的堆記憶體!
int main01(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    for (int i = 0; i < 10; ++i)
    {
        //多執行緒同一時刻訪問同一個變數的衝突問題(詳解:多條執行緒全部開啟,大家都讀取到了變數i的地址,然後同一時刻訪問了變數i的資料,因此相同)
        //  此時的主執行緒資料相對於其它輔助執行緒的資料就如同堆記憶體當中的資料,各條執行緒共享該資料-->因此多執行緒訪問衝突
        HANDLE hd = _beginthread(run , 0 , &i);
        WaitForSingleObject(hd, INFINITE);//無限等待,意思就是知道直到上一條工作執行緒執行完畢之後,再繼續執行此行程式碼之後的程式碼
        //_beginthread(run, 0, &intArr[i]);//多執行緒傳遞引數的方式-->此時每條執行緒當問的不是同一個資料,因此不會出現多執行緒訪問衝突問題
    }

    system("pause");
}

程式片段(04):01.指標運算.c+02.陣列.cpp+03.陣列實戰.c
內容概要:指標與陣列

///01.指標運算.c
#include <stdio.h>
#include <stdlib.h>


//01.指標變數的運算子特點:
//  1.指標變數的運算只有在所屬陣列的情況下才具備實際意義
//  2.指標變數的運算與其所指向的資料實體的型別密切相關
//  3.沒有明確型別的指標變數完全無法進行訪問(讀取|修改)
//      只是具備這儲存地址這麼一個唯一的功能
//02.指標變數的運算規律總結:
//  1.p+1<==>p+sizeof(*p)*1;
//  2.空型別的指標變數必須明確資料型別之後才能進行具體操作
//      否則只是具備容器的特點
int main01(void)
{
    int intNum = 10;
    int * pIntNum = &intNum;
    //pIntNum+1;<>pIntNum+sizeof(*pIntNum)*1;
    printf("%p, %p \n", pIntNum, pIntNum + 1);

    char ch = "X";
    char *pCh = &ch;
    printf("%p, %p, %p \n", pCh, pCh + 1, pCh - 1);//指標變數的運算與型別密切相關

    void * ppCh = &ch;
    //pv+1;//沒有明確指標型別的指標變數,不能進行指標變數的算術運算

    system("pause");
}

int main02(void)
{
    int intNum = 10;
    int * pIntNum = &intNum;
    printf("%p, %p, %p \n", pIntNum - 1, pIntNum, pIntNum + 1);
    printf("%d, %d, %d \n", *(pIntNum - 1), *pIntNum, *(pIntNum + 1));
    //如果指標變數所儲存的指標所描述的地址不在陣列當中,那麼指標變數的加減運演算法則沒有任何意義
    //  因為運算結果可以和陣列索引相互掛鉤

    system("pause");
}

//02.關於指標變數的運算特點:
//  1.參與運算的指標變數所儲存的指標必須指向同一個陣列記憶體情況下
//  2.指標變數的運算範圍:
//      (1).指標變數既可以加上一個整數或者也可以減去一個整數(指標結果不要越過陣列)
//      (2).指著變數值可以減去一個指標變數,但不可以加上一個指標變數
//      (3).指標變數既不可以執行乘法運算也不可執行除法運算,因為運算結果毫無實際意義!
int main03(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int * p1 = &intArr[0];
    int * p2 = &intArr[6];
    printf("%d \n", p2 - p1);//在同一個陣列當中的兩個指標變數(指標變數p1-指標變數p2<=>(指標1-指標2)/sizeof(*p1)<=>相差的元素個數)
    //printf("%d \n", *(p1 -2));

    system("pause");
}
///02.陣列.cpp
#include <stdio.h>
#include <stdlib.h>
#include <iostream>

using namespace std;

//01.關於陣列名和&陣列名的內容詳解:
//  1.陣列名的本質:將所有陣列看作為一維陣列,那麼陣列名就是該一維陣列當中首個陣列元素的地址
//      常量指標!-->含有記憶體實體,是個偽常量,可以避免直接修改,但是無法避免間接修改
//  2.&陣列名的本質:獲取指向整個陣列整體的指標型別
//      指標常量!-->沒有記憶體實體,是個真常量,既不可以直接修改,也不可以間接修改
//  因此:無論是常量指標還是指標常量所對應的值都是指標(是具備型別含義的地址)
//      陣列名+1<=>常量指標+1<=>常量指標的值+sizeof(*常量指標)
//      &陣列名+1<=>指標常量+1<=>指標常量的值+sizeof(*指標常量)
//  訣竅:對任何型別的變數(無論是真變數還是偽變數)進行取地址操作,都屬於指標型別
//      也就統統佔用4個位元組的記憶體空間
int main04(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    printf("intArr = %p, &intArr = %p \n", intArr, &intArr);
    printf("%d, %d \n", intArr, intArr + 1);//陣列的第一個元素的地址,長度為int型別的位元組數
    printf("%d ,%d \n", &intArr, &intArr + 1);//整個陣列的型別,長度為整個陣列的長度(4*10),這裡說明取地址運算子在進行運算的時候同時獲取了指標的地址和型別
    printf("sizeof(intArr) = %lu, sizeof(&intArr) = %lu \n", sizeof(intArr), sizeof(&intArr));
    printf("%s ,%s", typeid(intArr).name(), typeid(&intArr).name());

    system("pause");
    return 1;
}
///03.陣列實戰.c
#include <stdio.h>
#include <stdlib.h>

//01.函式的形式引數如果是陣列名稱,就取消了副本機制!
//  取消副本機制的原理-->將陣列名稱當做了指標變數的名稱進行處理
//  該指標變數具備著實際引數陣列的所有資訊(包括型別資訊)
//  注:這裡的指標變數的型別是將所有陣列型別看做為一維陣列,那麼
//      該一維陣列當中的所有元素的型別就是該指標變數所指向的記憶體
//      實體的型別
void testFun(int * p)//藉助指標變數訪問外界陣列
{
    printf("testFun sizeof(p) = %d \n", sizeof(p));
    for (int * px = p; px < p + 10; px++)
    {
        *px = 12;
        printf("%d ,%p \n", *px, px);
    }
}

//02.sizeof(物件)運算訣竅:
//  sizeof(*&物件);<==>絕對正確的一個結果
int main05(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    printf("main: sizof(intArr) = %d \n", sizeof(intArr));//sizeof(陣列名)<=>sizeof(*&陣列名);
    testFun(intArr);
    printf("原生陣列: \n");
    for (int * p = intArr; p < intArr + 10; ++p)
    {
        printf("%d ,%p \n", *p, p);
    }

    system("pause");
}

//03.對於陣列的變數需要藉助指標變數進行:
//  指標變數作為迴圈變數進行運算
int main06(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    //int * pIntArr = &intArr;//型別不匹配:&intArr-->指向陣列的指標
    //intArr++;//陣列名稱intArr作為一個常量指標,是不能夠直接進行自增自減運算的
    for (int * p = intArr + 2; p < intArr + 10; ++p)
    {
        printf("%d, %p \n", *p, p);
    }
    for (int * p = intArr + 10; p > intArr - 1; --p)
    {
        printf("%d, %p \n", *p, p);
    }

    system("pause");
}

//04.sizeof();訣竅:
//  sizeof(ob);<=>sizeof(*&obj);
int main07(void)
{
    double * pDb;//在C語言當中,指標變數必須進行初始化,否則就會成為野指標,使用的時候容易出現問題,某些編譯器不會通過的
    printf("%d \n", sizeof(*pDb));//對稱法則:double  | * pDb-->8個位元組
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    printf("intArr = %p, &intArr = %p \n", intArr, &intArr);
    //printf("sizeof(intArr) = %lu, sizeof(&intArr) = %lu \n", sizeof(intArr), sizeof(&intArr));
    //printf("sizeof(*intArr) = %lu, sizeof(*&intArr) = %lu", sizeof(*intArr), sizeof(*&intArr));
    printf("%d \n", sizeof(intArr));//sizoef();使用訣竅

    system("pause");
}

程式片段(05):thread.c
內容概要:多執行緒切割

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#include <process.h>

typedef struct
{
    int * pStart;//待檢索首地址
    int sLength;//待檢索長度
    int dFindValue;//待檢索值
    int tID;//當前檢索執行緒ID
} thTask;
int isFind = 0;//執行緒檢索成功標識,用於多執行緒之間的通訊

//01.多執行緒檢索原理:
//  1.將多個執行緒任務放置到執行緒佇列當中去
//  2.然後排程多條執行緒執行執行緒佇列當中的任務程式碼
//  3.執行緒隨機排程:
//      那條執行緒最閒置,那麼該執行緒最容易優先執行到
#define M 100//待檢索的資料量
#define N 4//總共所使用的檢索執行緒數目(最好是CPU邏輯核心的倍數,負載均衡原理)

//02.多執行緒檢索資料最容易遇到的問題:
//  資料量並不是單條執行緒所處理資料量的整數倍?-->需要解決這個負載均衡問題
//      讓N-1執行緒所處理的資料量一致,讓最後一條執行緒處理量不一致
//  舉例說明:
//      1.當執行緒處理資料量剛好平分的情況:
//          100%4=0
//      2.當執行緒處理資料量不好平分的情況:
//          100%7!=0
//          100/(7-1)=16...4-->前6條執行緒,每條的資料處理量是16條資料
//          100%(7-1)=4----->第7條執行緒的資料處理量為4條
void thSearchTask(void * pThTask)
{
    thTask * thTaskBlock = pThTask;//地址->指標
    printf("執行緒%d開始查詢! \n", thTaskBlock->tID);
    for (int * pf = (*thTaskBlock).pStart; pf < (*thTaskBlock).pStart + (*thTaskBlock).sLength; ++pf)
    {
        if (1 == isFind)
        {
            printf("執行緒%d結束查詢,其他執行緒已經找到! \n", (*thTaskBlock).tID);
            return;
        }
        if ((*thTaskBlock).dFindValue == *pf)
        {
            printf("結束查詢狀態!執行緒%d已經找到資料%d,地址為%p",(*thTaskBlock).tID, (*thTaskBlock).dFindValue, pf);
            isFind = 1;
            return;
        }
        Sleep(500);
    }
    printf("執行緒%d結束查詢,沒有找到! \n", (*thTaskBlock).tID);
}

int main01(void)
{
    int intArr[M] = { 0 };
    srand((unsigned int)time(NULL));
    for (int i = 0; i < 100; ++i)
    {
        intArr[i] = rand() % 100 + 1;
        printf("%4d", intArr[i]);
        if (0 == (i + 1) % 100)
        {
            printf("\n");
        }
    }

    int num = 0;
    scanf("%d", &num);//待查詢的資料
    thTask thTaskArgS[N];//執行緒任務引數集
    if (0 == M%N)
    {
        for (int i = 0; i < N; ++i)
        {
            thTaskArgS[i].pStart = intArr + M / N *i;
            thTaskArgS[i].sLength = M / N;
            thTaskArgS[i].tID = i;
            thTaskArgS[i].dFindValue = num;
            HANDLE hd = _beginthread(thSearchTask, 0, &thTaskArgS[i]);//開啟執行緒
            //WaitForSingleObject(hd, INFINITE);
        }
    }
    else
    {
        for (int i = 0; i < N - 1; ++i)
        {
            thTaskArgS[i].pStart = intArr + M / (N - 1)*i;
            thTaskArgS[i].sLength = M / (N - 1);
            thTaskArgS[i].tID = i;
            thTaskArgS[i].dFindValue = num;
            HANDLE hd = _beginthread(thSearchTask, 0, &thTaskArgS[i]);
            //WaitForSingleObject(hd, INFINITE);
        }
        int i = N - 1;
        thTaskArgS[i].pStart = intArr + M / (N - 1)*i;
        thTaskArgS[i].sLength = M % (N - 1);
        thTaskArgS[i].tID = i;
        thTaskArgS[i].dFindValue = num;
        HANDLE hd = _beginthread(thSearchTask, 0, &thTaskArgS[i]);
    }

    system("pause");
}

程式片段(06):多執行緒檢索.c
內容概要:多執行緒檢索

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
#include <time.h>
#include <process.h>

typedef struct
{
    int tID;
    int * pStart;
    int sLength;
    int sValue;
} thTaskInfo;
int isFind = 0;

void searchTaskFun(void * pTemp)
{
    thTaskInfo * thTaskArg = pTemp;
    printf("ID為%d的執行緒開始進行檢索任務! \n", (*thTaskArg).tID);
    for (int * pf = (*thTaskArg).pStart; pf < (*thTaskArg).pStart + (*thTaskArg).sLength; ++pf)
    {
        if (1 == isFind)
        {
            printf("ID為%d的執行緒結束檢索狀態,其他執行緒已經檢索到%d! \n", (*thTaskArg).tID, (*thTaskArg).sValue);
            return;
        }
        if ((*thTaskArg).sValue == *pf)
        {
            isFind = 1;
            printf("ID為%d的執行緒已檢索到資料%d,該值所在地址為%p \n", (*thTaskArg).tID, (*thTaskArg).sValue, pf);
            return;
        }
        Sleep(500);
    }
    printf("ID為%d的執行緒未能檢索到資料%d,結束檢索狀態! \n", (*thTaskArg).tID, (*thTaskArg).sValue);
}

int main01(void)
{
    int intArr[100] = { 0 };
    srand((unsigned int)time(NULL));
    for (int i = 0; i < 100; ++i)
    {
        printf("%4d", intArr[i] = rand() % 100 + 1);
        if (0 == (i + 1) % 10)
            printf("\n");
    }

    int value = 0;
    scanf("%d", &value);
    thTaskInfo thTaskArgS[10];
    for (int i = 0; i < 10; ++i)
    {
        thTaskArgS[i].tID = i;
        thTaskArgS[i].pStart = intArr + 10 * i;
        thTaskArgS[i].sLength = 10;
        thTaskArgS[i].sValue = value;
        HANDLE hd = _beginthread(searchTaskFun, 0, &thTaskArgS[i]);
    }

    system("pause");
}

程式片段(07):二級指標.c
內容概要:二級指標

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

//01.指標變數規律:
//  1.指標變數必須注意指標的型別
//  2.指標變數必須注意指標所指向實體的型別
//  3.針對於指標變數的(加減一個整數)規律:
//      實質:必須根據指標變數所儲存的指標所指向的實體型別來定斷
//      形式:p+1<=>p+sizeof(*p);-->必需帶上記憶體實體才能準確!
//  4.指著變數的級數規律:
//      (1).如何表明當前指標變數儲存的是什麼型別的變數的地址?
//          double **pp-->二級指標變數PP儲存的是(double * p)這個一級指標變數的地址
//          規律:指標變數的宣告級數減去一級就是該指標變數所儲存的變數的地址
//      (2).如何確定取值運算子(星號:"*")當前所訪問的記憶體實體是什麼?
//          double **pp-->**pp-->(指標變數宣告級數-當前取值運算子操作的級數)
//              就是該取值運算子所訪問的變數的資料
//          例如:**-**=0-->變數的資料
int main01(void)
{
    double db = 10.8;
    double * p = &db;
    //double * pT = &p;//指標型別不匹配
    double ** pp = &p;
    //printf("%p, %p \n", pT, pT + 1);//PT結果不正確
    printf("%p, %p \n", pp, pp + 1);
    printf("%lf \n", **pp);//普通變數

    system("pause");
}

int a = 10;
int b = 20;
//02.函式形參的副本機制要點:
//  只是針對於陣列型別沒有副本機制,其他所有型別的形參都有副本機制
//  原理:就是將陣列型別進行退化為指標("指向陣列元素的指標"),以避免副本機制產生
//  注:指向陣列元素的指標,明確該陣列當中所儲存的到底是什麼型別的資料!
void changeP(int * p)//指標變數也有副本機制
{
    printf("change: &p = %p \n", &p);
    p = &b;
}

void changePP(int ** pp)
{
    *pp = &b;
}

//03.要想讓被掉函式修改主調函式當中的變數資料:
//  訣竅:至少必須傳遞主調函式當中的變數地址(至少得是直接地址)
int main02(void)
{
    int * p = &a;
    printf("main: &p = %p \n", &p);
    printf("%d \n", *p);//10
    //p = &b;
    //changeP(p);
    changePP(&p);
    printf("%d \n", *p);

    system("pause");
}

//04.集團體系架構關係:
//  職稱體系可以通過指標級數進行描述
//  例如:司令採用的是10級指標描述,裡面儲存的軍長的地址
int main03(void)
{
    int a = 10;
    int * p = &a;
    int ** pp = &p;
    int *** ppp = &pp;
    printf("%p \n", &ppp);
    //集團體系架構關係可以通過指標級數進行描述

    system("pause");
}

//05.字元型別的指標如果指向的是程式碼區符號表實體:
//  那是不允許通過字元指標間接修改程式碼區符號表內容的!
//  注:程式碼區符號表的內容只能夠進行讀取操作,不能進行寫入操作
int main04(void)
{
    char * p = "123";//"123"儲存於程式碼區符號表
    *p = 'A';//*p所訪問的記憶體實體位於程式碼區符號表,因此不能直接進行修改
    printf("%p \n", p);

    system("pause");
}

void run(char * arr[5])
{
    printf("run: sizeof(arr) = %d \n", sizeof(arr));//sizeof(char **)
    for (int i = 0; i < 5; ++i)
    {
        system(arr[i]);
    }
}

//06.陣列作為形參究竟退化為什麼型別的指標?
//  實質:退化為指向該陣列當中所有元素的指標!
//int arr[5]-->arr作形參,退化為-->int * arr
//char * arr[5]-->arr作形參,退化為-->char ** arr
void runT(char **arr)//指標陣列作為形參,將會退化成為二級指標
{
    printf("runT: sizeof(arr) = %d \n", sizeof(arr));
    for (int i = 0; i < 5; ++i)
    {
        system(arr[i]);//arr[i]的實質其實是一級指標
    }
}

//07.陣列作為形參的時候,會具體轉化為指向該陣列當中每個元素的指標型別:
//  在訪問的時候,採取該指標型別變數名加上中括號("[]")等同於陣列訪問方式!
//  實質:arr[i]<=>*(arr+i)-->效果等價
int main05(void)
{
    //int arr[10];
    char * arr[5] = { "calc", "notepad", "mspaint", "tasklist", "pause" };
    printf("main: sizeof(arr) = %d \n", sizeof(arr));//訣竅:sizeof(varName)<=>sizeof(*&varName)
    run(arr);
    //runT(arr);

    system("pause");
}

程式片段(08):陣列.c
內容概要:指標結合陣列

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

//01.幾組概念區分:
//  1.普通變數和指標變數:
//      是否具有地址意義
//  2.變數指標和常量指標
//      指標變數和陣列名的區分
//  3.常量指標和指標常量
//      陣列名和地址意義上的數值
//  注:通常意義上的指標實質上是指著變數
//02.函式形參使用陣列宣告形式以及指標宣告形式
//  其訪問實參的方式都是相同的(陣列形式<=>指標形式)
int main01(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    int * p = arr;//變數指標接收常量指標的值
    for (int i = 0; i < 10; ++i)
    {
        printf("%d, %d, %d, %d \n", arr[i], *(arr + i), p[i], *(p + i));
    }
    //接收同樣一個陣列實參
    //  採用陣列宣告形式和採用指標宣告形式的訪問方式完全一樣
    //  例如:arr[i]<=>p[i];*(p+i)<=>*(arr+i)

    system("pause");
}

//03.陣列作為形參的函式標準宣告形式!
//  指向陣列元素的指標+陣列元素個數
void revArr(int * p, int n)
{
    for (int i = 0; i < n / 2; ++i)//節省效率的反轉方式
    {
        p[i] = p[i] ^ p[n - 1 - i];
        p[n - 1 - i] = p[i] ^ p[n - 1 - i];
        p[i] = p[i] ^ p[n - 1 - i];
    }
}

//04.通過指標方式來對陣列進行遍歷!
void revArrT(int * p, int n)
{
    for (int * pHead = p, *pTail = p + n - 1; pHead < pTail; ++pHead, --pTail)
    {//奇數:重合退出+偶數:交叉退出
        *pHead = *pHead ^ *pTail;
        *pTail = *pHead ^ *pTail;
        *pHead = *pHead ^ *pTail;
    }
}

//05.傳遞實質是什麼就可以以何種形式進行資料訪問!
//  這裡傳遞的實質是陣列,因此可以採用陣列方式訪問
void showArr(int * p, int n)
{
    for (int i = 0; i < n; ++i)
    {
        printf("%2d", p[i]);
    }
}

int main02(void)
{
    int arrA[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };//10/2=5..0--><=5-->0~4(前0~4與後0~4)對稱
    int arrB[9] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };//9/2=4..1--><=4-->0~3(前0~3與後0~4)對稱
    revArr(arrA, sizeof(arrA) / sizeof(arrA[0]));
    showArr(arrA, sizeof(arrA) / sizeof(arrA[0]));
    putchar('\n');
    revArrT(arrB, sizeof(arrB) / sizeof(arrB[0]));
    showArr(arrB, sizeof(arrB) / sizeof(arrB[0]));

    system("pause");
}

//06.特殊常量指標訪問陣列方式:
//  原理:arr[i]<=>*(arr+i)
//  因此:i[arr]<=>*(i+arr)
//  注:所有陣列的解析方式都是按照arr[i]-->*(arr+i)-->
//      所有荒唐用法i[arr]也可以進行替換為-->*(i+arr)
int main03(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    printf("% \n", 8[arr]);
    //arr[1]<==>*(arr+1)

    system("pause");
}

程式片段(09):queue.h&queue.c+01.多執行緒.c+02.多執行緒排隊.c
內容概要:多執行緒平行計算

///queue.h
#define EN 100

typedef struct
{
    int data[EN];
    int head;
    int tail;
} Queue;

void initQueue(Queue * pQueue);
int isEmpty(Queue * pQueue);
int isFull(Queue * pQueue);
void enQueue(Queue * pQueue, int value);
void deQueue(Queue * pQueue);
int getLast(Queue * pQueue);
void showQueue(Queue * pQueue);
//#undef N//結束巨集名為N的作用域
///queue.c
#include "queue.h"
#include <memory.h>
#include <stdio.h>

void initQueue(Queue * pQueue)
{
    memset((*pQueue).data, 0, sizeof(int)*EN);//資料清零,模擬回收
    (*pQueue).head = (*pQueue).tail = -1;
}

int isEmpty(Queue * pQueue)
{
    if ((*pQueue).head == (*pQueue).tail)
        return 1;
    return 0;
}

int isFull(Queue * pQueue)
{
    if (EN - 1 == (*pQueue).tail)
        return 1;
    return 0;
}

void enQueue(Queue * pQueue, int value)
{
    if (1 == isFull(pQueue))
        return;
    if (1 == isEmpty(pQueue))
    {
        (*pQueue).data[0] = value;
        (*pQueue).tail = 0;
    }
    else
    {
        for (int i = (*pQueue).tail; i > 0; --i)
        {
            (*pQueue).data[i] = (*pQueue).data[i - 1];
        }
        (*pQueue).data[0] = value;
        ++((*pQueue).tail);
    }
}

void deQueue(Queue * pQueue)
{
    if (isEmpty(pQueue))
        return;
    --((*pQueue).tail);
}

int getLast(Queue * pQueue)
{
    return (*pQueue).data[((*pQueue).tail)--];
}

void showQueue(Queue * pQueue)
{
    for (int i = 0; i <= (*pQueue).tail; ++i)
    {
        printf("%2d", (*pQueue).data[i]);
    }
    printf("\n");
}
///01.多執行緒.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <process.h>

#define N 1024
int main01(void)
{
    srand((unsigned int)time(NULL));
    int data[N] = { 0 };
    for (int i = 0; i < N; ++i)
    {
        data[i] = rand() % 1000 + 1;
    }

    int sum = 0;
    for (int i = 0; i < N; ++i)
    {
        sum += data[i];
    }
    printf("單執行緒序列結果為%d! \n", sum);

    system("pause");
}

typedef struct
{
    int tID;
    int * pStart;
    int sLength;
    int cSum;
} thTaskInfo;
int main02(void)
{
    thTaskInfo thTask;
    thTaskInfo * pThTask = &thTask;
    printf("%d \n", (*pThTask).sLength = 100);
    //結構體訪問的等價關係!
    //  (*thTask).sLength <=>thTask->sLength
    printf("%d \n", (*pThTask).sLength);

    system("pause");
}


void thTaskFun(void * p)
{
    thTaskInfo * thTask = p;
    for (int i = 0; i < (*thTask).sLength; ++i)
    {
        (*thTask).cSum += (*thTask).pStart[i];
    }
    printf("ID為%d的執行緒計算結果為%d! \n", (*thTask).tID, (*thTask).cSum);
}

int main03(void)
{
    srand((unsigned int)time(NULL));
    int data[N] = { 0 };
    for (int i = 0; i < N; ++i)
    {
        data[i] = rand() % 1000 + 1;
    }

    int sum = 0;
    for (int i = 0; i < N; ++i)
    {
        sum += data[i];
    }
    printf("單執行緒序列計算結果為%d \n", sum);

    thTaskInfo thTaskArgs[4] = { 0 };
    for (int i = 0; i < 4; ++i)
    {
        thTaskArgs[i].tID = i;
        thTaskArgs[i].pStart = data + (N / 4) * i;
        thTaskArgs[i].sLength = N / 4;
        thTaskArgs[i].cSum = 0;
        _beginthread(thTaskFun, 0, &thTaskArgs[i]);
    }

    system("pause");//等待所有平行計算執行緒完成計算操作
    int lastNum = 0;
    for (int i = 0; i < 4; ++i)
    {
        lastNum += thTaskArgs[i].cSum;
    }
    printf("多執行緒平行計算結果為%d! \n", lastNum);

    system("pause");
}
///02.多執行緒排隊.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include "queue.h"
#include <process.h>

#define N 1024
typedef struct
{
    int tID;
    int * pStart;
    int sLength;
    int cSum;
} thTaskInfo;
Queue thQueue;//管理執行緒的佇列,全域性變數,所有執行緒共享

//01.C語言當中的static關鍵字使用:
//  static關鍵字用於限定函式或者變數只能作用域當前檔案!
static void thTaskFun(void * p)
{
    thTaskInfo * thTask = p;
    for (int i = 0; i < (*thTask).sLength; ++i)
    {
        (*thTask).cSum += (*thTask).pStart[i];
    }
    enQueue(&thQueue, (*thTask).tID);//執行緒ID進隊,便於順序列印結果
    printf("ID為%d的執行緒計算結果為%d! \n", (*thTask).tID, (*thTask).cSum);
}

int main(void)
{
    srand((unsigned int)time(NULL));
    int data[N] = { 0 };
    for (int i = 0; i < N; ++i)
    {
        data[i] = rand() % 1000 + 1;
    }
    int sum = 0;
    for (int i = 0; i < N; ++i)
    {
        sum += data[i];
    }
    printf("單執行緒序列計算的結果為%d! \n", sum);

    initQueue(&thQueue);
    thTaskInfo thTaskArgs[4] = { 0 };
    for (int i = 0; i < 4; ++i)
    {
        thTaskArgs[i].tID = i;
        thTaskArgs[i].pStart = data + (N / 4) *i;
        thTaskArgs[i].sLength = N / 4;
        thTaskArgs[i].cSum = 0;
        _beginthread(thTaskFun, 0, &thTaskArgs[i]);
    }
    system("pause");//讓主執行緒等待所有輔助執行緒完成平行計算
    showQueue(&thQueue);

    int lastNum = 0;
    for (int i = 0; i < 4; ++i)
    {
        lastNum += thTaskArgs[i].cSum;
    }
    printf("多執行緒並行運算的結果為%d! \n", lastNum);

    system("pause");
}

程式片段(10):main.c
內容概要:TestC

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

//01.格式字串規律:
//  格式字串當中的兩個%等同於一個非輸入格式控制符
//  注:嚴格區分格式控制符與非格式控制符以及轉義字元
int main01(void)
{
    int a = 2, b = 5;
    printf("a = %%d, b = %%d \n", a, b);

    system("pause");
}

程式片段(11):test.c
內容概要:Test

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

//01.自變運算子的特殊性:
//  相同的自變運算子如果在表示式當中同時出現;
//  那就需要進行同時整體取值運算操作!
//  例如:
//      出現幾個(i++),就同一時間提出幾個(i++)的值
//          但是提取的是第一個(i++)的值
//      出現幾個(++I),就同一時間提出幾個(++i)的值
//          但是提取的是最後一個(++i)的值
//02.溢位規律特點:
//  原來99塊錢,兩位數
//       99
//  加上1塊錢,三位數
//      100
//  但是,只能有兩位數
//      所以剩下兩位是00
int main01(void)
{
    float x = 1, y;
    y = ++x*++x;
    printf("%f \n", y);

    float i = 1, j;
    j = i+++i++;
    printf("%f \n" ,j);

    unsigned short num = 65535 + 1;
    printf("%d \n", num);

    int a, b;
    float c;
    //c = a = b;

    system("pause");
}

相關文章