指针相关笔试强化

#define _CRT_SECURE_NO_WARNINGS 
/******************************************************************************************
 * 【文件名称】:	main.c
 * 【文件标识】:
 * ========================================================================================
 * 【当前作者】:	听风若依
 * 【当前版本】:	V1.0
 * 【完成日期】:	2024年  5月6日
 * 【更新内容】:
 * ========================================================================================
 * 【历史作者】:	听风若依
 * 【历史版本】:	V1.
 * 【历史日期】:	2024年  月日
 * ========================================================================================
 * 【开发平台】:	VS2022
 * 【程序内容】:指针相关笔试强化
 *******************************************************************************************/
      
      #include<stdio.h>

    int i = 14;
    //注意sizeof是操作符,strlen是函数;
    //sizeof的逻辑是直接看括号里的数据类型,括号内容不计算;
    //例如对于sizeof(3+5)来说,3和5都是int,int + int 还是 int, 所以在编译后sizeof(3 + 5)就变成了sizeof(int);
    //而表达式的运算都是在运行时进行的,(生成exe文件有三个过程: 编译 链接 运行),运行是在编译的基础上运行的,
    //运行时sizeof(3 + 5)已经变成了sizeof(int),所以sizeof括号里的表达式不参与计算。
    //当然这样做的原因是int+int仍为int ,char* + 1仍为char*, int (*)[] + 1仍为int(*)[];
    //如果不能一眼看出类型,实际算一下看结果的类型也是可以的。 

    //项目运行在x64环境下:

    void test1(void)
    {
        int a[] = { 1,2,3,4 };
        printf("%d\n", sizeof(a));//sizeof(数组名)表示整个数组,类型为int[4],所以是4*4=16字节
        printf("%d\n", sizeof(a + 0));//sizeof括号里不是只有数组名,这里表示首元素地址,加0后还是首元素地址,类型为int*,8字节
        printf("%d\n", sizeof(*a));//sizeof括号里不是只有数组名,这里a表示首元素地址,解引用后是首元素,类型为int,4字节
        printf("%d\n", sizeof(a + 1));//sizeof括号里不是只有数组名,这里a表示首元素地址,加1后是第二个元素地址,类型为int*,8字节
        printf("%d\n", sizeof(a[1]));//a[1]相当于*(a + 1),表示第二个元素,类型为int,4字节
        printf("%d\n", sizeof(&a));//&a是整个数组的地址,类型为int(*)[4],8字节
        printf("%d\n", sizeof(*&a));//两种看法:解引用和取地址过程互逆,相互抵消,相当于sizeof(a);
        //&a是整个数组的地址,类型为int(*)[4],解引用后是数组,类型为int[4],大小16字节
        printf("%d\n", sizeof(&a + 1));//&a是整个数组的地址,类型是int(*)[4],加1后还是int(*)[4],8字节;至于会不会越权访问,答案是不会,因为sizeof括
        //括里的表达式不计算
        printf("%d\n", sizeof(&a[0]));//[]优先级大于&,a先与[]结合,表示首元素,取地址后变成首元素地址,类型为int*,8字节
        printf("%d\n", sizeof(&a[0] + 1));//[]优先级大于&,a先与[]结合,表示首元素,取地址后变成首元素地址,再加1变成第二个元素地址,类型为int*,8字节
    }


    void test2(void)
    {
        char arr[] = { 'a','b','c','d','e','f' };
        printf("%d\n", sizeof(arr));//sizeof括号里只有数组名,表示整个数组,类型为char[6],6字节
        printf("%d\n", sizeof(arr + 0));//sizeof括号里除了数组名,还有其他东西,此处arr表示数组首元素地址,加0后还是数组首元素地址,类型为char(*)[6],
        //8字节
        printf("%d\n", sizeof(*arr));//arr表示数组首元素地址,类型为char*,解引用后为首元素,类型为char,1字节
        printf("%d\n", sizeof(arr[1]));//arr表示首元素地址,这里相当于*(arr + 1),表示第二个元素,类型为char,1字节
        printf("%d\n", sizeof(&arr));//&arr表示整个数组的地址,类型为char(*)[6],8字节 
        printf("%d\n", sizeof(&arr + 1));//&arr表示数组地址,加1后还是数组地址,类型为char(*)[6],8字节
        printf("%d\n", sizeof(&arr[0] + 1));//下表引用操作符优先级高于解引用操作符,arr先与[]结合,arr[0]等同*(arr + 0),取地址后为arr + 0,再加1,为第                                  //第二个元素地址,类型为char*,8字节
    }

    void test3(void)
    {
        char arr[] = { 'a','b','c','d','e','f' };
        printf("%d\n", strlen(arr));//'\0'位置未知,随机数
        printf("%d\n", strlen(arr + 0));//随机数
        //printf("%d\n", strlen(*arr));//arr表示数组首元素,解引用后是字符'a','a'的ASCll码是97,项目大概率崩溃,崩溃原因:读取该位置(0x61)时发生访问冲
        //突,该位置独属于操作系统,用户无权访问
        //printf("%d\n", strlen(arr[1]));//等同于*(arr + 1),表示数组第二个元素,'b'的ASCll码是98,项目大概率崩溃,崩溃原因:读取该位置(0x62)时发生访                                //问冲突
        printf("%d\n", strlen(&arr));//&arr表示数组地址,类型为char(*)[6],strlen形参为char*,将一个char(*)[6]的实参输入到形参char*的函数中,实参和形参                           //类型不匹配,实参类型会被强制转化成形参类型,(char*)&arr表示首元素地址,'\0'位置未知,随机数,若设其为x
        printf("%d\n", strlen(&arr + 1));//&arr表示数组地址,类型为char(*)[6],加1后跳过char[6]的长度,即6个字节,随后发生强制类型转换,(char*)(&arr +                               //1)加1跳过1个字节,'\0'位置未知,随机数,则其为x-6
        printf("%d\n", strlen(&arr[0] + 1));//[]优先级高于&,arr[0]表示*(arr + 0),取地址后是arr + 0,类型为char*加1后表示第二个元素地址,
        //还是char*,8字节,则其为x-1
    }

    void test4(void)
    {
        char arr[] = "abcdef";
        printf("%d\n", sizeof(arr));//sizeof括号里只放数组名,表示整个数组,为"abcdef\0",类型为char[7],7字节
        printf("%d\n", sizeof(arr + 0));//此处arr表示首元素地址,类型为char*,加0后还是如此,8字节
        printf("%d\n", sizeof(*arr));//此处arr表示首元素地址,解引用后为首元素,类型为char,1字节
        printf("%d\n", sizeof(arr[1]));//等同于*(arr + 1),表示第二个元素,类型为char,1字节
        printf("%d\n", sizeof(&arr));//&arr表示整个数组的地址,类型是char(*)[7],8字节
        printf("%d\n", sizeof(&arr + 1));//&arr表示整个数组的地址,类型是char(*)[7],加1后数据类型不变,8字节
        printf("%d\n", sizeof(&arr[0] + 1));//表示第二个元素地址,类型是char*,8字节
    }

    void test5(void)
    {
        char arr[] = "abcdef";
        printf("%d\n", strlen(arr));//arr表示首元素地址,6字节
        printf("%d\n", strlen(arr + 0));//arr表示首元素地址,6字节
        //printf("%d\n", strlen(*arr));//arr表示首元素地址,解引用后是字符'a',可能因访问冲突而崩溃
        //printf("%d\n", strlen(arr[1]));//arr表示首元素地址,arr[1]就是*(arr + 1)表示第二个元素,字符'b',可能因访问冲突而崩溃
        printf("%d\n", strlen(&arr));//&arr类型是char(*)[7],strlen形参为char*,会被强制类型转换,6字节
        printf("%d\n", strlen(&arr + 1));//&arr类型是char(*)[7],加1跨过char[7]的长度,越过了arr中的'\0',随机
        printf("%d\n", strlen(&arr[0] + 1));//表示第二个元素的地址,类型为char*,5字节
    }

    void test6(void)
    {
        char* p = "abcdef";
        printf("%d\n", sizeof(p));//类型char*,8字节
        printf("%d\n", sizeof(p + 1));//常量字符串中'b'的地址,char*,8字节 
        printf("%d\n", sizeof(*p));//表示字符'a',char,1字节
        printf("%d\n", sizeof(p[0]));//表示'a',char,1字节
        printf("%d\n", sizeof(&p));//表示整个常量字符串的地址,char(*)[7],8字节
        printf("%d\n", sizeof(&p + 1));//char(*)[7],8字节
        printf("%d\n", sizeof(&p[0] + 1));//表示常量字符串中'b'的地址,char*,8字节
    }

    void test7(void)
    {
        char* p = "abcdef";
        printf("%d\n", strlen(p));//6字节
        printf("%d\n", strlen(p + 1));//5字节
        //printf("%d\n", strlen(*p));//访问冲突崩溃
        //printf("%d\n", strlen(p[0]));//访问冲突崩溃
        printf("%d\n", strlen(&p));//6字节
        printf("%d\n", strlen(&p + 1));//加1跳过char[7]长度,跨过了'\0',随机值
        printf("%d\n", strlen(&p[0] + 1));//常量字符串中'b'的地址,5字节
    }

    void test8(void)
    {
        int a[3][4] = { 0 };
        printf("%d\n", sizeof(a));//sizeof括号里是数组名,表示整个数组,类型为int[3][4],48字节
        printf("%d\n", sizeof(a[0][0]));//等同于*(*(a + 0) + 0)表示第一个元素的第一个元素,int, 4字节
        printf("%d\n", sizeof(a[0]));//两种看法:a[0]就是数组名,类型为int[4];等同于*(a + 0)表示第一个元素,类型为int[4],16字节
        printf("%d\n", sizeof(a[0] + 1));//等同于(*(a + 0) + 1),表示第一个元素的第二个元素的地址,int*,8字节 
        printf("%d\n", sizeof(*(a[0] + 1)));//解引用了,表示第一个元素的第二个元素,int, 4字节
        printf("%d\n", sizeof(a + 1));//表示第二个元素的地址,类型为int(*)[4],8字节
        printf("%d\n", sizeof(*(a + 1)));//表示第二个元素,类型为int[4],16字节
        printf("%d\n", sizeof(&a[0] + 1));//等同于a+1,表示第二个元素的地址,类型为int(*)[4],8字节
        printf("%d\n", sizeof(*(&a[0] + 1)));//等同于*(a + 1),表示第二个元素,类型为int[4],16字节
        printf("%d\n", sizeof(*a));//等同于*(a),表示第一个元素,类型为int[4],16字节
        printf("%d\n", sizeof(a[3]));//等同于*(a + 3),表示第四个元素?类型int[4],16字节,实际sizeof括号内容不计算,不会越权访问
    }

    void test9(void)
    {
        int a[5] = { 1, 2, 3, 4, 5 };
        int* ptr = (int*)(&a + 1);//&a表示整个数组的地址,类型为int(*)[5],加1跨过int[5]长度,即20字节,强制类型转换后赋给ptr,ptr类型为int*,减1越过int大小,即4字节,解引用后是数组第五个元素,即5;*(a + 1)是数组第二个元素,是2
        printf("%d,%d", *(a + 1), *(ptr - 1));
    }

    void test10(void)
    {
        struct Test
        {
            int Num;
            char* pcName;
            short sDate;
            char cha[2];
            short sBa[4];
        }*p = (struct Test*)0x100000;//0x100000编译器认为是int,强制类型转换后变成了指针,类型为struct Text*,赋给了p
        printf("%p\n", p + 0x1);//p是指针,加1跨过指向内容的大小,即结构体struct Test的大小
        printf("%p\n", (unsigned long)p + 0x1);//p被强制转换成无符号长整型,加一就是加一
        printf("%p\n", (unsigned int*)p + 0x1);//p被强制转化成无符号整型指针,加1跨过指向内容长度,即4字节
    }

    void test11(void)
    {
        int a[3][2] = { (0, 1), (2, 3), (4, 5) };
        int* p;
        p = a[0];
        printf("%d", p[0]);//{}中是三个逗号表达式,逗号表达式最终结果是最后一个表达式的结果,(0,1)==1; (2,3)==3; (4,5)==5,数组内容为1,3,5,0,0,0,第一个元素是1
    }

    void test12(void)
    {
        int a[5][5];
        int(*p)[4];
        p = a;
        /*以a的视角来看,数组在内存中是这样的:
            [*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*]
            [^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^]
            a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a  a
            [0][0][0][0][0][1][1][1][1][1][2][2][2][2][2][3][3][3][3][3][4][4][4][4][4]
            [0][1][2][3][4][0][1][2][3][4][0][1][2][3][4][0][1][2][3][4][0][1][2][3][4]
                                                                            |
                                                                            |
                                                                [*][*][*][*]
                                                                |
            以p的视角来看,数组在内存中是这样的:                   |
            [*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*]
            [^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^][^]
            p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p  p
            [0][0][0][0][1][1][1][1][2][2][2][2][3][3][3][3][4][4][4][4][5][5][5][5][6]
            [0][1][2][3][0][1][2][3][0][1][2][3][0][1][2][3][0][1][2][3][0][1][2][3][0]

            指针-指针=元素个数
            &p[4][2]比 &a[4][2]小,所以&p[4][2]-&a[4][2]==-4,-4用%d的方式来表示就是-4;
            如果用%p的方式来表示,则要观察-4在计算机中的实际存储形式——补码;
            -4(x64)原码是1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0100
                    反码是1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1011
                    补码是1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1111 1100
                    不过为了方便观察,地址都是以16进制来呈现的,所以还要把-4补码转化成16进制:FFFFFFFFFFFFFFFC*/

                    //备注: p的定义是int(*p)[4],指向的是int[4]的数组,a是二维数组首元素的地址,指向的应该是int[5]的数组;
                    //指针-指针=元素个数,a和p指针类型不同,无法确定单位元素的大小,这个题目实际会有歧义,
                    //笔试基本不会遇到这种问题,如果遇到了,对于本题这种情况就把单位元素大小当成int;如果指向的是char[4]和char[5]就把单位元素大小当成char,以此类推。
        printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    }

    void test13(void)
    {
        int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        int* ptr1 = (int*)(&aa + 1);//&aa得到的是整个数组的地址,类型为int(*)[2][5],加一后跨过int[2][5]的大小,之后强制类型转换成int*赋给ptr1,所以ptr1的单位变化范围是int大小
        int* ptr2 = (int*)(*(aa + 1));//这里的aa是数组首元素的地址,类型为int(*)[5],加一后跨过int[5]的大小,之后强制类型转化成int*赋给ptr2,所以ptr2的单位变化范围是int大小
        printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//综上答案为10,5
    }

    void test14(void)
    {
        char* a[] = { "work","at","alibaba" };//a是一个指针数组,所指的指针类型都是 char*,一共三个,分别指向三个常量字符串的的首字母,char* a[] = {"work"中'w'的地址,"at"中'a'的地址,"alibaba"中的'a'的地址
        char** pa = a;//a表示数组首元素的地址,即["work"中'w'的地址]的地址
        pa++;//pa指向的是char*,加一就跳过char*的大小,由于数组中的元素地址是连续的,所以现在pa指向"at"中'a'的地址
        printf("%s\n", *pa);//综上显示at
    }

    void test15(void)
    {
        char* c[] = { "ENTER","NEW","POINT","FIRST" };//c是一个指针数组,所指的指针类型都是 char*,一共四个,分别指向四个常量字符串的的首字母
        char** cp[] = { c + 3,c + 2,c + 1,c };
        char*** cpp = cp;//cp表示数组首元素地址,即c+3的地址
        printf("%s\n", **++cpp);
        printf("%s\n", *-- * ++cpp + 3);
        printf("%s\n", *cpp[-2] + 3);
        printf("%s\n", cpp[-1][-1] + 1);
        //指针数组c中的元素:
        //下标         内容
        // 0    "ENTER"中'E'的地址   
        // 1	"NEW"  中'N'的地址
        // 2	"POINT"中'P'的地址
        // 3    "FIRST"中'F'的地址
        //**++cpp, 前置++,先加后使用,加一后cpp指向c+2的地址,第一次解引用后变成c+2,c+2是"POINT"中'P'的地址,再解引用后变成"POINT"中'P',打印POINT,现在cpp指向c+2的地址,cp数组内容不变:char** cp[] = { c + 3,c + 2,c + 1,c };
        //*-- * ++cpp + 3,在第一行的基础上++cpp,现在cpp指向c+1的地址,第一次解引用后变成c+1,再把c+1前置减一,变成c,再解引用后变成 "ENTER"中'E'的地址,对其+3,变成"ENTER"中第二个'E'的地址,打印ER,cp数组的内容发生改变:char** cp[] = { c + 3,c + 2,c,c };cpp指向第一个c的地址
        //*cpp[-2] + 3,cpp[-2]相当于*(cpp-2),cpp-2指向c+3的地址,解引用后变成c+3,c+3是"FIRST"中'F'的地址,再+3就变成"FIRST"中'S'的地址,打印ST,现在cp数组内容不变:char** cp[] = { c + 3,c + 2,c,c };cpp指向第一个c的地址
        //cpp[-1][-1] + 1,cpp[-1][-1]相当于*(*(cpp-1)-1),cpp-1指向c+2的地址,解引用后变成c+2,再对其-1,变成c+1,再对其解引用变成"NEW"  中'N'的地址,再+1就变成"NEW"  中'E'的地址,打印EW,此时cp数组内容不变:char** cp[] = { c + 3,c + 2,c,c };cpp指向第一个c的地址
        //前两行中cpp指向和cp内容发生变化,是因为使用的是自加自减(++和--)
    }


    int main()
    {
        void(*str[])(void) = { test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11, test12, test13, test14, test15 };
        (*str[i])();
        return 0;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值