当前位置: 首页 > 文档资料 > C 语言程序设计 >

指针

优质
小牛编辑
144浏览
2023-12-01

指针

01.取地址操作符(&)详解:

1.操作对象:变量名(实质:内存实体|数据实体) 2.操作特点:属于寄存器操作(操作结果不是内存实体) ​ (1).取地址操作(&变量名)是在CPU的寄存器区域所完成的操作; ​ (2).地址数据不占用内存,内存地址是在CPU核心构成组件寄存器产生的,内存地址的数值和内存存储没有实质关系; 3.计算机地址总线:连接CPU与硬件内存的总线 ​ (1).总线数目与CPU位数+操作系统位数有关 ​ (2).地址数值作为一个常量恒久不变 ​ (3).地址作为常量恒值并不会占用内存。内存地址是在CPU的寄存器当中产生的,因此不具备内存实体,也就不会占用内存 ​ (4).总线的个数决定着位数的不同:32条总线对应于32位操作系统;64条总线对应于64位操作系统

02.对指针变量的解释:

P一个指针变量,用于存储普通变量的地址数值,然后通过操作指针变量的方式间接操作普通变量

03.对于指针所占用的字节数分析:

Win32平台所编译的程序的指针类型占用4个字节 Win64平台所编译的程序的指针类型占用8个字节

///01.指针
#include <stdio.h>
#include <stdlib.h>

int main01(void)
{
    int num = 10;
    int data = 20;
    //P是一个指针变量,可以存储相同类型的不同变量的内存地址,常用于做间接访问变量本身(内存实体)操作
    int * pNum = &num;//0x12312312
    int * pData = &data;
    printf("%d \n", sizeof(pNum));//指针变量所占用的内存尺寸为4个字节(Win32平台所编译的程序)
    printf("%p \n", pNum);//直接打印指针变量(pNum)的数值,相当于间接打印了普通变量(num)的地址
    printf("%p \n", &pNum);//表示获取"指针"变量的地址,类型为int **

    system("pause");
}

//04.指针变量内容详解:
//  1.类型:
//      既决定了内存实体步长,也决定了内存实体解析方式
//  2.数值:
//      就是一个地址数值
//  3.场景:(携带*号的情况)
//      赋值号左边:
//          给内存实体写入数据
//      赋值号右边:
//          从内存实体读取数据
int main02(void)
{
    int num1 = 1100;
    int num2 = 1200;//变量num都分配了内存,内存实体(数据实体)的数值代表变量的数据
    int * pNum = &num1;//pNum1是int类型的一级指针变量,&num1是一级指针真常量数值(含有类型含义)
    pNum = &num2;//pNum的值是num2这个变量(内存实体|数据实体)的内存地址,该内存地址统一占用4个字节,int * 地址,指针变量的长度统一为4
    //int * 对称于pNum,指针类型既决定了内存实体的访问字节步长,也决定了内存实体的解析方式,特殊点:浮点型数据的特殊存储形式,既是指数形式存储还涉及到无符号类型(存储实质阶码)
    *pNum = 12;//通过指针变量间接的访问了普通变量的内存实体//根据指针数值找到内存地址,根据指针类型既决定了存储步长也决定了存储方式
    printf("%p \n", &pNum);//&pNum表示获取一级指针变量pNum的内存地址,该地址数值存储于CPU核心组件寄存器区域,完全不会占用内存存储
    printf("%d \n", num2);
    printf("num2 = %d \n", num2);

    system("pause");
}

//05.野指针:就是没有进行初始化操作的指针变量
//  VC:编译检测,GCC编译不检测
//06.解析方式的不同含义:
//  有无符号:signed/unsigned
//  存储原理:补码原理/阶码原理
//07.指针变量所占用的内存尺寸只跟编译平台有关:
//  Win32平台:4字节-->Win64平台:8字节
int main03(void)
{
    //int * p;//没有对象的野指针,定义指针变量的时候必须进行指针变量的初始化操作
    //printf("%p \n", p);//打印指针变量当中所存储的内存地址-->编译不通过

    //解析方式的不同特点:
    //  signed/unsigned(只针对于10进制的整数)+float指数形式存储(阶码实质存储)
    int num1 = 10;
    int num2 = 20;
    //float * pNum;
    //pNum = &num1;
    //printf("%f", *pNum);//指针变量的类型决定了指针变量对其所存储的地址数值的解析步长(字节尺寸)以及解析方式(补码阶码)
    int * pNum;
    pNum = &num1;
    printf("%d \n", *pNum);//对内存地址数值的正确解析步长和正确解析方式
    printf("%d \n", sizeof(pNum));//指针变量所占用的字节长度在Win32平台之下统一占用4个字节的内存尺寸,任何类型的地址都一样采用int类型的统一字节尺寸存储
    printf("%d \n", sizeof(*pNum));//尺寸,方式

    system("pause");
}

//08.指针认识规律:
//  *pNum<=>num
int main04(void)
{
    int num = 20;
    int data = 30;
    int * pNum = &num;//int类型的一级指针变量存储了int类型的普通变量的内存地址
    //&num = 1;//内存地址不允许操作,由于&num这个表达式是在CPU的寄存器当中所进行的操作,然而C语言是不能够直接修改寄存器当中的数据的
    //pNum = &data;
    num = 3;
    printf("%d,%d \n", *pNum, num);//*pNum和num是完全等价的
    *pNum = 7;
    printf("%d,%d \n", *pNum, num);

    system("pause");
}

间接赋值

///02.间接赋值.c
#include <stdio.h>
#include <stdlib.h>

//01.函数形参具有副本机制:
//  1.传值和传指的数据层面意思都一样;
//      只不过对同样的数值有着不同的解析方式!
//  1.函数的副本机制总是存在;
//      无论是传值还是传地!
void change05(int num)//函数形参具有副本机制
{
    //函数形参具有副本机制,因此,无论是传值还是传指,都具备这样的特性:
    //  传值传递的是副本,传指传递的是地址,因此出现不一样的修改效果
    //  实质:都是副本,只不过对待同样的数值解析方式不同而已
    num = 3;
    printf("change05 = %d \n", num);
}

int main05(void)
{
    int num = 10;
    change05(num);
    printf("%d \n", num);

    system("pause");
}

void change06(int * pNum)
{
    *pNum = 3;
}

int main07(void)
{
    int num = 10;
    change06(&num);//&num-->表达式-->没有内存实体-->位于寄存器-->C语言不具备修改寄存器的权限
    printf("%d \n", num);

    system("pause");
}

外挂原理

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

//01.外挂修改数值原理
//  1.注入DLL模块儿
//  2.根据指针进行跨函数访问内存
_declspec(dllexport) void go()
{
    int * p = (int *)0xae0720;
    *p = 77;
}

//02.遍历内存查找原理:
//  1.单字节内存遍历
//  2.待查找尺寸读取
_declspec(dllexport) void run()
{
    typedef char * Char;
    for (Char  pStart = 0xae000000,pEnd = 0xae100000; pStart < pEnd; ++pStart)//单字节遍历指定内存范围
    {
        int * p = (int *)p;//指针类型转换,待查找尺寸读取数据进行匹配,以便于相同数值进行内存检索
        if (77 == *p)
        {
            *p = 86;
        }
    }
}

Test

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

#define EN 1024
int flag = 0;
char * filePath = "E:\\Resource\\TestData\\BigDB\\Test.txt";

//数组赋值
int intArr[EN];
void initArr(int intArr[EN])
{
    //time_t te;
    //unsigned int seed = (unsigned int)(time(&te));
    //srand(seed);
    srand((unsigned int)(time(NULL)));
    for (int i = 0; i < EN; ++i)
    {
        intArr[i] = rand() % EN + 1;
    }
}

//数组显示
void showArr(int intArr[EN])
{
    for (int i = 0; i < EN; ++i)
    {
        printf("%4d \n", intArr[i]);
    }
}

//冒泡排序
void bubbleSortArr(int intArr[EN])
{
    for (int i = 0; i < EN - 1; ++i)
    {
        for (int j = 0; j < EN - 1 - i; ++j)
        {
            if (intArr[j] > intArr[j + 1])
            {
                intArr[j] = intArr[j] ^ intArr[j + 1];
                intArr[j + 1] = intArr[j] ^ intArr[j + 1];
                intArr[j] = intArr[j] ^ intArr[j + 1];
            }
        }
    }
}

//选择排序
void selectSortArr(int intArr[EN])
{
    int minIndex = 0;
    int minNum = 0;
    for (int i = 0; i < EN - 1; ++i)
    {
        minIndex = i;
        minNum = intArr[i];
        for (int j = i + 1; j < EN; ++j)
        {
            if (minNum > intArr[j])
            {
                minIndex = j;
                minNum = intArr[j];
            }
        }
        if (i != minIndex)
        {
            intArr[i] = intArr[i] ^ intArr[minIndex];
            intArr[minIndex] = intArr[i] ^ intArr[minIndex];
            intArr[i] = intArr[i] ^ intArr[minIndex];
        }
    }
}

//插入排序
void insertSortArr(int intArr[EN])
{
    int currentIndex = 0;
    int currentValue = 0;
    for (int i = 1; i < EN; ++i)
    {
        currentIndex = i;
        currentValue = intArr[currentIndex];
        while (0 < currentIndex && intArr[currentIndex - 1] > currentValue)
        {
            intArr[currentIndex] = intArr[currentIndex - 1];
            --currentIndex;
        }
        intArr[currentIndex] = currentValue;
    }
}

//二分查找
int binarySearch(int intArr[EN], int value)
{
    int minIndex = 0;
    int midIndex = 0;
    int maxIndex = EN - 1;
    while (minIndex <= maxIndex)
    {
        midIndex = (minIndex + maxIndex) / 2;
        if (value == intArr[midIndex])
        {
            return midIndex;
        }
        else if (value < intArr[midIndex])
        {
            maxIndex = midIndex - 1;
        }
        else
        {
            minIndex = midIndex + 1;
        }
    }
    return -1;
}

//拉格朗日查找
int lagrangeSearch(int intArr[EN], int value)
{
    int minIndex = 0;
    int ratioIndex = 0;
    int maxIndex = EN - 1;
    while (minIndex <= maxIndex)
    {
        //midIndex = minIndex + (maxIndex - minIndex) / 2;
        ratioIndex = minIndex + (maxIndex - minIndex)*(value - intArr[minIndex]) / (intArr[maxIndex] - intArr[minIndex]);
        if (value == intArr[ratioIndex])
        {
            return ratioIndex;
        }
        else if (value < intArr[ratioIndex])
        {
            maxIndex = ratioIndex - 1;
        }
        else
        {
            minIndex = ratioIndex + 1;
        }
    }
    return -1;
}

int main01(void)
{
    initArr(intArr);
    showArr(intArr);

    insertSortArr(intArr);
    printf("binSearch Index = %d \n", binarySearch(intArr, 880));
    printf("lagrangeSearch index = %d \n", lagrangeSearch(intArr, 880));

    system("pause");
}

指针类型

#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");
}