20160210.CCPP體系詳解(0020天)

尹成發表於2016-03-01

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

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

//01.二級指標:
//  1.使用場景:
//      (1).跨函式修改一級指標變數的資料-->DLL注入技術!
//      (2).指標陣列作為函式形參,將會轉化為二級指標-->函式形參!
//  2.使用規律:
//      如何快速定位取值運算子所操作的資料是什麼?
//          指標變數(定義級數-取值級數)<==>訪問級數!
int main01(void)
{
    int a = 10;
    int b = 15;
    int * p = &a;
    int ** pp = &b;
    *pp = &b;
    **pp = 3;
    //a-->b-->*p-->**pp

    system("pause");
}

程式片段(02):01.反外掛.c
內容概要:遊戲反外掛

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

//01.關於賦值運算子的使用特點:
//  賦值運算子針對於普通變數具備自動型別轉換特性;
//  賦值運算子針對於指著變數不具備自動型別轉換的特性
//02.指標型別與指標變數型別的重要性!
//  只要涉及到指標的操作,一定要時刻注意型別的重要性
//  注:資料型別<=>解析(步長+方式)
//03.陣列名以及指向陣列首元素的指標區別:
//  1.陣列名和指向陣列首元素的指標的數值層面一樣
//  2.陣列名和指向陣列首元素的指標都存在記憶體實體
//      陣列名:記憶體實體不可修改
//          常量指標變數的指向不可改變,但是指向的資料可以改變
//      指向陣列首元素的指標(分變數指標和常量指標的情況)
//          視指標變數的const修飾關鍵字使用情況而定
//04.通過一級指標變數來模擬反外掛原理:
//  一級指標變數當中的指標指向陣列當中的某個血量刻度;
//  通過修改一級指標變數的指標指向來獲取具體的血量刻度;
//  從而實現血量不可修改,只能修改指向(實現血量的只讀許可權)
int main01(void)
{
    double  arr[100];
    for (int i = 0; i < 100; ++i)
    {
        arr[i] = i + 1;//1.0~100.0
    }
    double * p = arr + 60;//型別很重要,型別決定了(解析步長+解析方式)-->直接的一個陣列名只是具備該陣列首元素的記憶體首地址(首個位元組)
    printf("&p = %p \n", &p);
    printf("&arr = %p \n", arr);
    printf("%f \n", *p);
    while (1)
    {
        printf("此時的血=%lf \n", *p);
        Sleep(1000);
    }

    system("pause");
}

程式片段(03):01.掛.c
內容概要:

_declspec(dllexport) go01()
{
    double * p1 = 0x2ef4a4;
    double * p2 = 0x2ef4bc;
    //p1 = p2 + 92;//整數倍
}

//01.動態庫函式的匯出介面:
//  _declspec(dllexport)
//02.二級指標變數的用途:
//  注入技術修改二級指標變數的資料
//03.區分數值意義層面的整數
//  沒有資料型別的特點
//04.指標變數的指標如果只是加上
//  一個普通的數值,那麼最好是該指標所                                                                                                     
//  指向記憶體塊兒的記憶體位元組整數倍!
//  避免資料解析失敗!
//05.無論是自動型別轉換還是強制型別轉換
//  都只是在對記憶體當中的二進位制資料副本進行操作;
//  原本資料不會被修改!
_declspec(dllexport) go()                  
{
    double ** pp = 0x0018FAE4;//這裡的pp+1<=>pp+1*(所儲存的一級指標變數所佔用的記憶體尺寸)
    //double * p = 0xB9F5D4;//p+1任何指標型別的變數都可以儲存一個地址,地址本身無型別的情況之下,只能當做一個普通資料
    //*pp = 0xB9F5D4 + 0x50;//p+3普通的整數沒有型別可言,只是一個數值層面的意義
    //*pp = *pp + 2;//修改方式1(明確型別)
    //*pp = 0x0018F794 + 3 * sizeof(double);//修改方式2(不明型別)-->錯誤,因為sizeof();會返回有型別的整型數值
    *pp = 0x0018FAFC + 3 * 8;//修改方式2(正確方式)
    //int a = 10;
}

程式片段(04):01.指標型別.c
內容概要:指標型別

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

//01.指標的型別以及指標變數的型別重要性:
//  1.資料型別:決定了(解析方式+解析步長)
//  2.關於資料型別所決定的解析方式一共有三大類:
//      (有符號整型+無符號整型+浮點型),這三種型別
//      即使解析步長一樣,如果解析方式不一樣,解析結果照樣異常!
//  3.指標的型別決定了通過取值運算子進行取值運算的時候
//      需要通過指標的資料(等同於記憶體首地址),向前解析的記憶體位元組數
//  4.printf();函式不會進行資料型別的轉換,只是根據格式控制符所描述
//      的解析方式對記憶體當中的二進位制資料進行對應的解析
//  注1:賦值號對於指標變數沒有型別轉換的意義,只有數值層面的意義!-->數值意義(地址數值,沒有型別概念)
//      賦值號只對於普通變數才存在著型別轉換的意義!-->型別意義
//  注2:指標變數的型別決定了取值運算子對指標變數當中所儲存的指標的解析細節!
int main01(void)
{
    int num = 10;
    int * p1 = &num;
    void * p3 = &num;//空型別的指標變數只是用於儲存具備地址意義的數值,不含解析細節!
    double * p2 = &num;
    printf("%p, %p \n", p1, p2);//指標變數所儲存的地址數值都一樣
    printf("%lf, %d \n", *p2, *p1);//但是指標變數的型別決定了取值運算福對指標變數當中的指標解析方式

    system("pause");
}

//02.任何型別的變數進行賦值的時候,
//  都應當注意資料型別是否一致!
int main02(void)
{
    int a = 10;
    int * p1 = &a;
    int * p2 = p1;//型別一致的指標變數指標可以進行指標的賦值操作
    printf("%p, %p \n", p1, p2);//格式控制符只是描述對於記憶體二進位制資料的解析方式
    printf("%d, %d, %d \n", *p1, *p2, a);
    //*p1 = 4;
    a = 2;
    printf("%d, %d, %d \n", *p1, *p2, a);

    system("pause");
}

//03.只要指標變數的型別和指標型別不一致
//  就絕對不能正確的解析到結果
int main03(void)
{
    int num = 10;
    double db = 10.9;
    int * p1 = &num;
    double * p2 = p1;//獲取p2這個指標所指向的資料的時候,由於該指標變數(p2)的所描述的解析(方式+步長)不正確,因此解析出來的結果不可預料
    //賦值號,指標除了指標變數以外的其他變數,都會存在自動型別轉換機制,唯獨指標型別的變數特殊(只有地址意義的數值)
    //int a = 10.9;
    void *px = p1;
    px = p2;//空型別的指標變數只是用於臨時儲存地址意義的變數
    printf("%d \n", (int)db);//printf();不會進行自動型別轉換,會導致解析某些資料失敗
    printf("%lf \n", (float)num);

    //double * p = &num;
    //printf("%lf \n", *p);
    //*p = &db;
    //p = &db;
    //printf("%lf \n", *p);
    int *p = &db;
    printf("%d \n", *p);

    system("pause");
}

//04.sizeof關鍵字用於求取實際佔用的記憶體位元組數!
//  返回記憶體位元組數型別為unsigned int
//05.嚴格注意:
//  是具體給指標變數賦值還是給普通變數賦值
//  注:通過指標變數+取值運算子訪問到的可能是普通記憶體實體!
//      賦值運算子針對於普通記憶體實體也是具備自動型別轉換機制的!
//06.關於自動型別轉換機制:
//      兩種處理方式
//          同為整型:直接截斷(圖形化操作二進位制位)
//          不同型別:根據解析方式+解析步長進行決斷!
int main04(void)
{
    int num = 100;
    int * p = &num;//sizeof(int)--int **pp-->sizeof(int *)
    *p = 10.9;
    printf("%d \n", num);
    printf("%u \n", sizeof(int *));
    printf("%d \n", sizeof(double *));

    system("pause");
}

//07.三個不同型別的指標變數所儲存的地址是一樣的
//  但是由於指標變數的型別不一致,也就導致了指標型別不一致
//  因此,對於同一個地址所對應的記憶體實體的解析方式也就不一樣!
//08.對區域性變數的查詢方式:
//  區域性變數的值+根據區域性變數的值進行解析後的結果
int main05(void)
{
    int num = -1;
    int * p1 = &num;
    unsigned int * p2 = &num;
    float * p3 = &num;

    system("pause");
}

程式片段(05):01.Test.cpp+02.指標.c
內容概要:指標的運算

///01.Test.cpp
#include <stdio.h>
#include <stdlib.h>

//01.區分:變數指標和常量指標以及指標常量!
//      變數指標:可以修改的指標變數
//      常量指標:不可以修改的指標變數,不能夠修改指向,例如陣列名
//      指著常量:具備地址層面意義的地址數值!
//02.陣列名的本質:
//  如果陣列是一維陣列,那就是指向陣列當中收個元素的常量指標
//  如果陣列是高維陣列(含二維),那就首先將該陣列看作為一個一維陣列
//      而該陣列名的本質就是指向(看做成一維陣列)的首個元素的常量指標
int main01(void)
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int * p = arr;//變數指標=常量指標
    int * const px = arr;//陣列名的本質就是陣列當中第一個元素(所有陣列都看作為一維陣列進行處理!)的指標常量(地址常量|數值常量)
    *(arr + 3);
    //arr = 1;//常量指標
    //px = 1;//const所修飾的常量指標,其指向不可以進行直接修改!

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

//01.指標和指標變數都必須嚴格注重資料型別
//02.指標變數的運算必須在同一個陣列的情況之下,才具有意義!
//  1.指標變數+|-一個整數:
//      在陣列當中向前或者向後移動幾個陣列元素
//  2.指標變數-指標變數:
//      求取兩個指標變數之間相差了多少個陣列元素
//  注:指標變數沒有乘法和乘法方面的運算,因為運算結果毫無意義!
int main02(void)
{
    int a = 10;
    int b = 20;
    int * p1 = &a;
    int * p2 = &b;
    p1 = p2;//同一個t型別的一級指in針變數之間可以進行相互賦值
    int arr[5];
    p1 = arr;
    p2 = &arr[2];//指標變數賦值運算,必須注意型別的統一性(而且:指標變數的賦值操作只具備數值層面的操作意義)
    p1 - p2;//得到的結果是在同一個陣列當中的兩個陣列元素之間所相差的陣列元素個數

    system("pause");
}

//03.C語言和C++語言的編譯器特點:
//  C語言屬於"弱型別"語言;C++語言屬於"強型別"語言
//  注:強弱型別就是對資料型別是否精準匹配的嚴格校對!(編譯時期校對)
//04.關於指標變數和整型變數之間的賦值:
//  1.型別不匹配,C語言允許
//  2.給指標變數賦予的整型變數的資料具有地址層面的意義
//  注:不可以對指標變數進行隨意賦值操作!(唯恐賦予不規範記憶體地址)
int main03(void)
{
    int * p = 0x123123;//指標變數一般不允許直接賦值常規整數,因為不可預知(因為該地址所對應的記憶體塊兒不知道是否有系統進行維護!)
    *p = 1;//該指標變數當中的指標指向了作業系統所使用的記憶體,因此發生記憶體訪問錯誤情況!
    int num = p;
    //指標變數和整型變數之間可以進行賦值,可以編譯,但是整數表示的是具備地址意義層面的數值!
    printf("%x \n", num);

    system("pause");
}

void select(int const * p)
{
    //*p = 123;
    printf("銀行有%d元! \n", *p);
}

int main04(void)
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int const * p;//指標變數的指向可以改變,但是指向的資料資料不可以改變!
    int num1 = 101;//銀行的錢
    select(&num1);
    int num2 = 102;//銀行的錢
    select(&num2);

    system("pause");
}

int main05(void)
{
    int num1 = 101;//銀行的錢
    int num2 = 102;//銀行的錢
    const int * p = &num1;
    p = &num2;
    //*p = 10;

    system("pause");
}

int main06(void)
{
    int num1 = 101;//銀行的錢
    int num2 = 102;//銀行的錢
    int * const p = &num1;
    *p += 3;
    //p = &num2;
    //int * const p;//指標變數的指向可以修改,但是所指向的資料不能修改

    system("pause");
}

//04.關於const關鍵字做許可權控制的幾種情況:
//      const在*右邊:跨函式資料只讀許可權,賬戶訪問許可權
//          指標變數的指向不可修改,但所指向的資料可以修改
//      const在*左邊:跨函式賬戶只讀許可權,資料訪問許可權
//          指著變數的指向可以修改,但所指向的資料不可修改
//      雙const情況:跨函式賬戶不可修改,資料不可修改
//          指標變數的指向不可修改,所指向的資料不可修改
//  注:嚴格區分賬戶情況和資料情況
int main07(void)
{
    int num = 10;
    int num2 = 100;
    const int * const p = &num;
    //*p = 101;
    //p = &num2;

    system("pause");
}

//05.關於const做許可權控制的解決方案:
//  const在*右邊:
//      1.指向變數資料的常量指標
//      2.賬戶只讀許可權,資料訪問許可權
//  const在*左邊:
//      1.指向常量資料的變數指標
//      2.賬戶訪問許可權,資料只讀許可權
//  const在雙邊:
//      1.指向常量資料的常量指標
//      2.賬戶只讀許可權,資料只讀許可權(代理查詢)
//  注:函式形參控制賬戶+函式實體控制資料

程式片段(06):01.Run.c
內容概要:算數運算以及其他運算

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

//01.針對於陣列的兩種常用遍歷方式:
//  索引遍歷+指標遍歷
//  注:編譯實質arr[i]<=>*(arr+i)
int main01(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    //arr[i]的編譯實質:*(arr+i)
    for (int i = 0; i < 10; ++i)
    {//下標遍歷
        printf("%p, %p, %d \n", &arr[i], arr + i, arr[i]);
    }
    for (int * p = arr; p < arr + 10; ++p)
    {//指標遍歷
        printf("%p, %d \n", p, *p);
    }

    system("pause");
}

//02.運算子的優先順序:
//  1.接觸生效原理(誰先接觸誰優先)
//  2.自變運算子(++)的優先順序大於取值運算子(星號:"*")
int main02(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    int * p = &arr[3];
    p += 3;
    //printf("%d \n", *p++);//7
    //printf("%d \n", *(p++));//7
    //printf("%d \n", ++p);//8
    //printf("%d \n", *(++p));//8

    system("pause");
}

//03.指標變數的相等與不等比較特點!
int main03(void)
{
    int num = 10;
    int * p1 = &num;
    int * p2 = &num + 1;
    if (p1 == p2)
    {
        printf("情敵! \n");
    }
    else
    {
        printf("非情敵! \n");
    }

    system("pause");
}

//04.指標變數的關係運算:
//  1.陣列內:索引前後關係
//  2.陣列外:棧記憶體所屬位置
int main04(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    int * p1 = arr;
    int * p2 = arr + 3;
    //p2>p1:p2的下標比p1的下標大,陣列內部可以說明排序

    system("pause");
}

//05.編譯器的兩種編譯模式:
//  Debug模式:棧結構的開口向上
//      Debug情況之下:VC和GCC兩個編譯器的結果相同
//  Release模式:棧結構的開口向下
//      Release情況之下:存在著程式碼優化機制
//  注:(程式碼優化機制:VC和GCC編譯器都會根據變數的使用頻率,自動優化該變數在棧結構當中的所屬位置
//      某變數的使用頻率越高,那麼該變數在Release模式下越有機率被存放於棧結構開口處!)
//06.幾種進棧實質區分:
//  1.程式碼進棧
//  2.函式形參:形參變數進棧
//  3.函式實體:區域性變數進棧
int main05(void)
{
    int a; 
    int b;
    int c;
    printf("&a = %p, &b = %p, c = %p \n", &a, &b, &c);

    system("pause");
}

程式片段(07):01.Test.c+02.Run.c
內容概要:指標運算的優先順序

///01.Test.c
#include <stdio.h>
#include <stdlib.h>

int main01(void)
{
    char * p;
    printf("%d \n", sizeof(p));//所有指標型別享有共同記憶體尺寸,只是區分編譯器位數直接決定

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

//01.兩個指標變數的減法說明:
//  1.必需在同一個陣列當中兩個指標變數進行減法操作才具有實際意義!
//  2.任意兩個指標變數的減法結果推導!
//      減法結果=((指標變數1的指標所屬地址數值)-(指標變數2的指標所屬地址數值))/sizeof(指標變數1的所屬型別);
//  注:編譯器不會報錯!但是實際開發過程當中嚴謹這樣進行操作!
int main02(void)
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int * p = arr;
    int * p1 = &arr[4];
    printf("%d \n", p1 - p);//相減等同於相差的陣列元素個數
    //double * px = &arr[4];//2

    system("pause");
}

//02.內容概要:
//  1.變數指標+常量指標
//  2.指標減法=(指標1-指標2)/sizeof(*指標1);
//03.指標變數的優先順序和結合性:
//  1.接觸生效
//  2.++的優先順序高於*
int main03(void)
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int * p = arr;
    int * px = &arr[4];
    printf("%d \n", px - p);

    char * px1 = arr + 3;
    char * px2 = arr + 4;//陣列名arr的資料型別int * const p;
    printf("%d \n", px1 - px2);//4=(地址1-地址2)sizeof(char)

    *p++;//++的優先順序比*的優先順序高
    (*p)++;                                                                                                 
    ++*p;//優先順序接觸生效
    for (int i = 0; i < 5; ++i)
    {
        printf("%d \n", arr[i]);
    }

    system("pause ");
}

程式片段(08):01.Point.c
內容概要:指標與陣列

///01.Point.c
#include <stdio.h>
#include <stdlib.h>

//01.陣列名+&陣列名+*陣列名:
//  陣列名:
//      將所有陣列看做為一維陣列進行處理
//      然後陣列名就是指向該一維陣列的常量指標
//  &陣列名:
//      陣列指標常量,是一個具有(地址+型別)意義的數值
//  *陣列名:
//      根據陣列名的特點進行區分
//02.各種維度的指標區別:
//  0維指標:指向一列
//  1維指標:指向一行
//  2維指標:指向一面
//  3維指標:指向一體
int main01(void)
{
    int intArr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 , 0 };
    int intArrArr[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    printf("intArr = %p, &intArr = %p \n", intArr, &intArr);
    printf("intArr+1 = %p, &intArr + 1 = %p \n", intArr + 1, &intArr + 1);
    //intArr作為一個常量指標,指向陣列當中的首個元素
    //&intArr作為一個指標常量,指向整個陣列
    prinrf("intArrArr = %p, &intArrArr = %p, *intArrArr \n", intArrArr, &intArrArr, *intArrArr);
    printf("intArrArr+1 = %p, &intArrArr+1 = %p, *intArrArr+1 = %p \n",intArrArr + 1, &intArrArr + 1, *intArrArr + 1);
    //intArrArr作為一個常量指標,指向陣列當中的首個元素(將所有陣列當做為一維陣列進行看待的處理結果)
    //&intArrArr作為一個指標常量,指向一個面兒
    //*intArrArr作為一個指標常量,指向一個列

    system("pause");
}

程式片段(09):01.P.cpp
內容概要:指標陣列與陣列指標

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

//01.指標陣列與陣列指標
//  指標陣列:
//      1.是一個陣列
//      2.儲存的都是變數指標
//  陣列指標:垂直指向
//      1.是一個指標
//      2.儲存的是一維陣列的首地址
//  注:凡是對陣列名進行取地址操作,所獲取到的
//      都是指標都是陣列指標(指標常量)
int main01(void)
{
    int arr[10];
    int * pArr[10];//40-->指標陣列:用於儲存指標變數的陣列
    int (*p)[10];//4-->陣列指標:指向含有10個陣列元素的的陣列的指標!
    printf("%d, %d \n", sizeof(pArr), sizeof(p));

    system("pause");
    return 1;
}


//0 9   2   8   3   7   4   5   5    10
//0 9   2   8   3   7   4   6   10   5
//10    9   2   8   3   7   4   6   1    5
//02.指標陣列的常見用途:
//  1.批量管理指標(記憶體地址)
//  2.便於分塊陣列模型的構建
//注:指標陣列所儲存的指標型別通常都是(char*)型別
//      因為便於進行記憶體檢索!+字串儲存於程式碼區常量池
//03.賦值運算子針對於指標變數不具備型別轉換的特性
//  通常只是具備地址層面意義的數值
//04.比較指標變數所儲存的指標所指向資料實體
//      交換指標變數所儲存的指標!-->防止修改原始儲存資料順序!
int main02(void)
{
    int arr[10] = { 0, 9, 2, 8, 3, 7, 4, 6, 5, 10 };
    int * pArr[10];//指標陣列:該陣列當中的每一個變數都是指標變數,用於批量管理記憶體地址[指標]
    for (int i = 0; i < 10; ++i)
    {
        pArr[i] = arr + i;//變數指標儲存常量指標的地址數值
    }
    //正向的氣泡排序演算法
    //for (int i = 0; i < 10 - 1; ++i)
    //{
    //  for (int j = 0; j < 10 - 1 - i; ++j)
    //  {                                                            
    //      if (*pArr[j] < *pArr[j + 1])
    //      {
    //          int * pTemp = pArr[j];
    //          pArr[j] = pArr[j + 1];
    //          pArr[j + 1] = pTemp;
    //      }
    //  }
    //}
    //反向的氣泡排序演算法
    for (int i = 10 - 1; i > 0; --i)
    {
        for (int j = 10 - 1; j > 10 - 1 - i; --j)
        {
            if (*pArr[j] < *pArr[j - 1])
            {
                int * pTemp = pArr[j];
                pArr[j] = pArr[j - 1];
                pArr[j - 1] = pTemp;
            }
        }
    }
    for (int i = 0; i < 10; ++i)
    {
        printf("%d, %d \n", *pArr[i], arr[i]);
    }

    system("pause");
    return 1;
}

int main03(void)
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    for (int * p = arr; p < arr + 10; ++p)
    {//指標遍歷
        printf("%3d", *p);
    }
    printf("%p, %p \n", arr, &arr);
    printf("%p , %p \n", arr + 1, &arr + 1);
    int (*p)[10];//p-->p+1-->40//&一維陣列名的本質
    p = &arr;
    int * px = arr;//px-->px+1-->4

    system("pause");
}

程式片段(10):01.批量掛.c
內容概要:指標陣列掛

#include <Windows.h>

//01.通過指標陣列實現批量處理指標變數:
//  跨程式修改記憶體實體-->需要記憶體實體的所屬地址!
_declspec(dllexport) go()
{
    int * pArr[5];
    pArr[0] = 0xadfc70;
    pArr[1] = 0xae0720;
    pArr[2] = 0xae0850;
    pArr[3] = 0xae05f0;
    pArr[4] = 0xae04c0;

    while (1)
    {
        for (int i = 0; i < 5; ++i)
        {
            if (*pArr[i] < 100)
            {
                *pArr[i] = 101;
            }
        }
        Sleep(1000);
    }
}

程式片段(11):01.二維陣列.c+02.二維陣列輪詢.c
內容概要:指標與二維陣列

///01.二維陣列.c
#include <stdio.h>
#include <stdlib.h>

//01.關於線性儲存的陣列的解析方式:
//  1.可以採用指標變數的遍歷方式
//  2.該指標變數直接從陣列的首個陣列元素開始進行遍歷!
//      線性遍歷:遍歷的指標尺寸是陣列當中的不可分割的資料型別!
//  3.線性陣列的奧數操作方式!
//      外層變化快+內層變化慢!
//注:可以直接將二維陣列當中的每個陣列元素看做為單個列指標變數的
//      指標所指向!
int main01(void)
{
    int arrArr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0 };
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 5; ++j)
        {
            printf("%4d", arrArr[i][j]);
        }
        printf("\n");
    }
    for (int * p = &arrArr[0][0]; p < *arrArr + 15; ++p)
    {//線性排列-->線性遍歷
        printf("%4d, %p \n", *p, p);
    }
    printf("\n\n");
    for (int i = 0; i < 15; ++i)
    {//索引遍歷-->效能優化
        printf("%4d, %p \n", arrArr[i / 5][i % 5], &arrArr[i / 5][i % 5]);
        //00 01 02 03 04
        //10 11 12 13 14
        //20 21 22 23 24
    }

    system("pause");
}

//02.關於一維陣列二維陣列的概念區別:
//  一維陣列:int arr[5]
//      arr:int * const-->常量指標-->有記憶體實體
//      &arr:int (*pArr)[5]-->陣列指標-->沒有記憶體實體-->屬於指標常量
//      *arr:int-->零維指標-->有記憶體實體-->獲取數值
//  二維陣列:int arrArr[4][5]
//      arrArr:int (*pArr)[5]-->陣列型別的常量指標-->行指標-->有記憶體實體
//      &arrArr:int (*pArr)[4][5]-->陣列型別的指標-->面指標-->沒有記憶體實體-->屬於指標常量
//      *arrArr:int *-->普通變數的指標-->列指標-->有記憶體實體
int main02(void)
{
    //int arr[5];
    int arrArr[3][5];
    printf("%p, %p \n", arrArr, arrArr + 1);//20個位元組
    printf("%p, %p \n", *arrArr, *arrArr + 1);//4個位元組
    //int ** p = arrArr;//資料型別不匹配
    int(*parrArr)[5] = arrArr + 0;//二維陣列名的本質:將二維陣列看做為一維陣列之後,指向其中每個元素的常量指標型別
    //arrArr = 1;//陣列名一定是常量指標,絕對不可以進行改變
    int * px = *(arrArr + 0);//*arrArr-->行指標轉化為列指標(就是指向普通變數的一級一級指標)-->只是對記憶體資料的轉換
    int(*py)[3][5] = &arrArr;//&a-->一個指向固定面積的二維陣列的指著變數,二維陣列指標

    system("pause");
}
///02.二維陣列輪詢.c
#include <stdio.h>
#include <stdlib.h>

//01.關於二維陣列的指標操作方式:
//  例如:int arrArr[3][4]
//      arrArr:行指標
//      arrArr+i<=>&arrArr[i]:行指標
//      arrArr[i]+j<=>*(arrArr+i)+j;
//      *(arrArr[i]+j)<=>*(*(arrArr+i)+j)
//  注:
//      陣列名的本質:
//          將陣列看做為一維陣列,陣列名
//          就是指向該一維陣列的常量指標
//      *行指標<=>列指標
//      列指標的實質就是一級常量指標
int main03(void)
{
    int arrArr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 , 0, 0, 0, 0, 0 };
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 5; ++j)
        {
            printf("%4d, %p", arrArr[i][j], &arrArr[i][j]);
        }
        printf("\n");
    }
    printf("\n\n");
    for (int i = 0; i < 3; ++i)
    {
        printf("arrArr + %d = %p, %p \n", i, arrArr + i, &arrArr[i]);
    }
    for (int j = 0; j < 5; ++j)
    {
        printf("%p, %p \n", arrArr[0] + j, *(arrArr + 0) + j);
        printf("%p, %p \n", *(arrArr[0] + j), *(*(arrArr + 0) + j));
    }

    system("pause");
}

程式片段(12):01.Show.c
內容概要:WebShow二維陣列

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

int main(void)
{
    printf("Content-Type:text/html \n\n");//01.必須使用一個明顯的空行(需要兩個"\n\n")進行表示
    int arrArr[3][5];
    int num = 0;
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 5; ++j)
        {
            arrArr[i][j] = num;
            ++num;
            printf("%3d",*(*(arrArr+i)+j));
            printf("%3d", arrArr[i][j] = ++num);
        }
        printf("<br />");//02.html換行標籤,不能使用C語言的換行!
    }

    //system("pause");//03.不能使用系統函式
}

程式片段(13):01.動態陣列.c
內容概要:動態陣列

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

//01.動態陣列採用什麼型別的指標變數進行接收:
//  就表明動態陣列名稱屬是什麼型別!(變數指標|)
//  簡而言之:接收動態陣列的指標變數就是動態陣列名的本質!
//注:嚴格區分變數指標和常量指標
//02.區分釋放記憶體和回收記憶體之間的區別:
//  個人觀點:釋放記憶體+資料清除=回收記憶體
//      僅僅的釋放記憶體:不會自動發生資料清除操作!
int main01(void)
{
    //arr[N];
    int N;
    scanf("%d", &N);

    int * p;
    p = (int *)malloc(N * sizeof(int));
    for (int i = 0; i < N; ++i)
    {
        p[i] = i;
        printf("%d \n", p[i]);
    }
    free(p);//回收記憶體
    //free(p);//釋放記憶體,針對於同一片兒記憶體的指標,對該指標進行釋放的操作不可重複進行!

    system("pause");
}

//02.在堆記憶體開闢模擬棧記憶體的二維陣列的關鍵點在於:
//  指標變數的型別-->決定對記憶體塊兒的解析方式!
//注:可以對動態陣列的陣列名執行類似於棧記憶體二維陣列的操作!
int main02(void)
{
    int arrArr[3][10];//開闢堆記憶體動態陣列模擬這個棧記憶體二維陣列,關鍵在於指標
    int(*p)[10] = malloc(30 * sizeof(int));//p就是堆記憶體當中的動態陣列陣列名
    int num = 0;
    for (int i = 0; i < 3; ++i)
    {
        for (int j = 0; j < 10; ++j)
        {
            printf("%3d", p[i][j] = num++);//原理:p[i][j]<=>*(*(p+i)+j));
        }
        printf("\n");
    }

    system("pause");
}

//03.關於動態記憶體分配的注意事項:
//  1.引數按照無符號型別進行解析的!
//      因此不能使用特殊值-1-->會解析為極大的資料
//      無符號型別解析方式:記憶體二進位制全部看做為補碼進行逆向解析
//  2.取特殊記憶體位元組數0可以分配成功,但是並沒有
//      實際意義!
//注:解析方式尤其重要!
int main03(void)
{
    //int * p = malloc(-1);//分配失敗為0,-1補碼-->所分配的記憶體空間比較大
    int * p = malloc(0);
    printf("%p \n", p);//0能分配成功,但是沒有實際意義!

    system("pause");
}

//04.動態記憶體開闢訣竅:
//      陣列首元素的指標!
//  注:回收記憶體的操作只能執行一次,為了軟體規範
//      只要對指標變數當中所儲存的指標執行了回收操作
//      就應當置為空指標,防止重複回收記憶體錯誤!
int main04(void)
{
    int N;
    scanf("%d", &N);

    int * p;
    p = malloc(N * sizeof(N));
    printf("p = %p \n", p);
    for (int i = 0; i < N; ++i)
    {
        p[i] = i;
        printf("%d", p[i]);
    }
    free(p);//記憶體回收!
    printf("p = %p \n", p);
    //free(p);回收記憶體,回收記憶體的操作只能執行一次,不可以進行重複回收動作
    p = NULL;//被妹子回收了,為了防止迷途指標的產生,需要設定指標變數為空
    free(p);//回收空指標不會出錯!
    //*p = 123;

    system("pause");
}

//05.一定要在儲存指標的指標變數消亡之前
//  執行指標所指向記憶體區塊兒回收操作,否則會導致記憶體洩露
void run()
{
    void * p = malloc(10);//指標變數p本身儲存與棧記憶體
    //記憶體洩露,儲存地址的指標消亡,就無法進行回收動作了
    free(p);
}

//06.如何定義一個三維動態陣列?
//  採用面兒指標<=>指向二維陣列的指標
//注:定義N維陣列需要(N-1)維的陣列的指標
//  任何N維陣列都可以看做為一個一維陣列,內部的(N-1)維陣列只是一個元素!
int main05(void)
{
    int(*p)[3][5] = malloc(sizeof(int) * 15 * 2);
    int num = 0;
    for (int z = 0; z < 2; ++z)//縱座標-->控制面
    {
        for (int x = 0; x < 3; ++x)//橫座標-->控制行
        {
            for (int y = 0; y < 5; ++y)//縱座標-->控制列
            {
                printf("%3d", p[z][x][y] = num++);
            }
            printf("\n");
        }
        printf("\n\n");
    }

    system("pause");
}

程式片段(14):01.動態記憶體.c
內容概要:動態記憶體

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

//01.靜態陣列:
//  位於棧記憶體:在編譯時期就已經確定了該陣列的所含有的元素個數
int main01(void)
{
    int arr[10];//棧記憶體-->靜態陣列-->在編譯時期就已經確定了陣列的元素個數

    system("pause");
}

//02.動態陣列:
//  位於堆記憶體:在執行時期動態確定該陣列所佔有的記憶體尺寸!
int main02(void)
{
    int n;
    scanf("%d", &n);

    int * p = malloc(n * sizeof(int));
    for (int i = 0; i < n; ++i)
    {
        p[i] = i;
        printf("%d \n", p[i]);
    }

    system("pause");
}

//03.區分軟訪問與硬訪問:
//  軟訪問和硬訪問的預設最小單位是位元組
//  軟訪問可以實現的極限最下單位是二進位制位
int main03(void)
{
    while (1)
    {
        void * p = malloc(1024 * 1024 * 10);
    }

    system("pause");
}

//04.使用動態記憶體的原因:
//  靜態記憶體(棧記憶體)使用尺寸過小!
int main04(void)
{
    //int a[1024 * 1024 * 1024];//棧記憶體的大小,不能太大,設定很大不但耗費CPU,還耗費記憶體
    int a;

    system("pause");
}

程式片段(15):main.c
內容概要:Stack

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

//01.記憶體地址的高低必須區分模式:
//  模式:Debug+Release
//02.不同的模式:
//  棧記憶體的開口方向不一樣
// 程式優化程度不一樣(例如:經常訪問的變數會定義在棧口!)
int main01()
{
    int a;
        int b;
        int c;
        printf("a=%p\nb=%p\nc=%p", &a, &b, &c);
        system("pause");
    return 0;
}
()

相關文章