C语言----深入理解指针(5)(二)

简介: C语言----深入理解指针(5)

C语言----深入理解指针(5)(一)https://developer.aliyun.com/article/1544407

sizeof---二维数组

//二维数组
int main()
{
    int a[3][4] = { 0 };//3行4列----每一行都输一维数组
    //第一行的数组名叫arr[0]---每个元素的访问a[0][i]
    //第一行的数组名叫arr[1]---每个元素的访问a[1][i]
    //第一行的数组名叫arr[2]---每个元素的访问a[2][i]
 
 
 
    printf("%d\n", sizeof(a));//48
    //a是二维数组的数组名,单独放在sizeof内部,a表示的是整个数组,计算的就是整个数组的大小,单位是字节
    //12个元素,每个元素4个字节,总共就是48个字节大小
 
 
 
    printf("%d\n", sizeof(a[0][0]));//4
    //a[0][0]就是第一行第一个元素,大小是4个字节
 
    printf("%d\n", sizeof(a[0]));//16
    //a[0]是这个二维数组的第一行的数组名
    //第一行的数组名单独放在sizeof内部,计算的就是整个一维数组的大小,就是4*4=16个字节的大小
 
 
 
    printf("%d\n", sizeof(a[0] + 1));//4
    //a[0]是第一行的数组名,但是没有单独放在sizeof里面,就说明这里的a[0]是第一行首元素的地址,那么首元素地址+1就是第一行第二个元素的地址
    //就是a[0][0]的地址
    //那么a[0] + 1--->&arr[0][0]+1--->&a[0][1]
 
    //因为这里是地址,那么就是4个字节大小
 
 
 
    printf("%d\n", sizeof(*(a[0] + 1)));4
    //上面说a[0]+1得到的是第一行第二个元素的地址
    //那么我们解引用得到的就是第一行第二个元素,是一个整型,大小是4个字节
 
 
 
    printf("%d\n", sizeof(a + 1));//4
    //a + 1
    //a是二维数组的数组名,并且没有单独放在sizeof内
    //说明这个a是这个二维数组的首元素地址
    //就是第一行的地址,那么a+1就是第二行的地址
    //因为a+1是第二行的地址,传给sizeof,因为是地址,所以大小是4个字节
 
    //
 
 
    printf("%d\n", sizeof(*(a + 1)));//16
    //*(a+1)--->a[1]--第二行的数组名,单独放在sizeof内部,那么sizeof计算的就是第二行的大小
 
    //因为a+1得到的是第二行的地址,那么对其进行解引用,得到的就是第二行整个数组
    //a+1是第二行的地址,类型是int(*)[4],数组指针,基因用访问的是这个数组,大小是16个字节
 
 
    printf("%d\n", sizeof(&a[0] + 1));//4
    //a[0]是第一行的数组名,&数组名就是第一行的地址,&a[0]就是取出了第一行的地址
    //那么&a[0] + 1得到的就是第二行的地址
    //因为是地址,那么大小就是4个字节
 
 
    printf("%d\n", sizeof(*(&a[0] + 1)));//16
    //上面说到&a[0] + 1得到的是第二行的地址,那么对第二行的地址进行解引用得到的就是第二行数组
    //
 
 
 
    printf("%d\n", sizeof(*a));//16
    //a作为数组名没有单独放在sizeof内,那么a就不是整个数组
    //a就是这个二维数组的首元素的地址
    //对其进行解引用得到的就是第一行整个数组
    //4*4=16
    //*a-->*(a+0)-->a[0]
 
 
    printf("%d\n", sizeof(a[3]));//16
    //这里是否越界
    //sizeof内部的表达式是不会真实计算的,也不会真实去访问的
    // 仅仅只是根据类型来推断长度的
    //a[3]是第四行的数组名---那么计算的是第四行的大小
    //第四行的类型是int[4]
 
 
 
 
    return 0;
}
 
//数组名在sizeof内单独使用,表示的就是整个数组
//如果不是单独使用,我们就要考虑一下这个数组名是数组首元素的地址
 
//sizeof内部的表达式是不会进行运算的
 
int main()
{
    short s = 8;//short类型的数据是2个字节
    int n = 12;//4个字节
    printf("%zd\n", sizeof(s = n + 5));//2
    //sizeof内部的表达式并没有进行运算
    //sizeof(s = n + 5)这个表达式 最终结果取决于s
 
    printf("%zd\n", s);//8
    return 0;
}

数组名的意义:

1.sizeof(数组名),这里的数组名表示的是整个数组,计算的是整个数组的大小

2.&数组名,这里的数组名表示整个数组,取出的是整个数组的地址

3.除此之外所有的数组名都表示数组首元素的地址

3.指针运算笔试题解析

题目一

#include <stdio.h>
int main()
{
    int a[5] = { 1, 2, 3, 4, 5 };
    int* ptr = (int*)(&a + 1);
    //&a是取出真个数组的地址,那么&a+1就是跳过了整个数组,指向的是这个数组的末尾的位置
    //&a+1的地址类型是数组类型的地址int(*)[5]
    //将这个地址强制类型转换为int*类型的
    //准换位int*类型的地址再赋值给ptr,那么ptr指向的也是这个数组的末尾位置
 
 
 
 
    printf("%d,%d", *(a + 1), *(ptr - 1));
    //这里的a是数组名--数组首元素的地址,那么a+1得到的就是第二个元素的地址,就是2的地址
    //解引用得到的就是2
 
 
 
    //因为ptr现在是整型指针,那么-1就是往回退,那么指向的就是5的位置
    //*(ptr - 1)得到的就是5
    return 0;
}
//&a+1就是取出整个数组再跳过这个数组,就是那么这个&a+1指向的就是这个数组的末尾
//再将这个强制类型转换为int *类型的地址,再赋值给ptr
//那么ptr也是指向这个数组的末尾的位置
//因为ptr的类型是int *,-1就是往后退一个整型的距离,那么就是指向的5的位置

题目二

//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
struct Test
{                            
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p = (struct Test*)0x100000;
//在这里括号前面的是结构体,加上*就是结构体指针类型,创建变量p
//先将0x100000强制转换为结构体指针,再为b进行赋值,现在p里面放的就是0x100000这个地址
 
 
 
int main()
{
    printf("%p\n", p + 0x1);//00100014
    //指针+1和类型有关系
    //整数+1就是+1
 
    //0x1是16进制的1
    //因为p是一个结构体指针,因为前面说了结构体的大小是20字节
    // 那么p+1就是就是跳过跳过一个结构体20个字节
 
    //0x100014
//因为这个是16进制的,所以加20个字节就是在这个基础上+14
//4*16的0次方+1*16的1次方就是20
 
    //00100014
 
    printf("%p\n", (unsigned long)p + 0x1);//0x00100001
    //现在将p强制类型转换为整型,那么整型+1就是+1
    //那么得到的就是0x00100001
 
 
 
    printf("%p\n", (unsigned int*)p + 0x1);//00100004
    //将p强制类型转换为int *
    //+1跳过一个unsigned int*类型的指针变量,是4个字节
 
 
    return 0;
}

题目三

#include <stdio.h>
int main()
{
    int a[3][2] = { (0, 1), (2, 3), (4, 5) };
    //仔细看,这里用的是小括号不是大括号
    //那么这里就成逗号表达式了
    //(0, 1)的结果就是1
    //(2, 3)的结果就是3
    //(4, 5)的结果就是5
    //int a[3][2] = { 1,3,5 };
    //那么这个二维数组剩下的位置就被0给填充了
    //1  3
    //5  0
    //0  0
 
 
 
 
 
    int* p;//指针
    p = a[0];
    //a[0]是第一行的数组名,
    // 没有sizeof
    // 没有&
    // 
    // 那么表示的就是首元素的地址
    //那么p里面存的就是1的地址
 
    printf("%d", p[0]);
    //p[0]===*p(0)--指向的还是1的位置
    //那么打印出来的就是1
    return 0;
}

题目四

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
int main()
{
    int a[5][5];//5行5列
    //每一行的数组名分别是a[0]、a[1]、a[2]、a[3]、a[4]
 
 
    int(*p)[4];//p是一个数组指针,p指向的数组有4个元素,每个元素是int
    p = a;
    //a是数组名,这个二维数组的数组名,就是第一行的地址,第一行整个一维数组的地址
 
    //第一行的地址的类型就是一个数组指针int(*)[5]
    //p的类型是int(*)[4]   a的类型是int(*)[5]
 
    //不管a原来是什么类型,现在赋值给p,那么现在就是p说的算
 
    //因为a指向的是第一行的位置,那么p同样也指向那里
    //对于p来说,一行只有4个元素
    //p+1就是跳过一行,每行四个元素
 
    printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    //FFFFFFFC,-4
 
    //p[4][2]==*(*(p+4)+2)-----解引用p+4就相当于拿到那一行的数组名
    //两个地址相减是地址直接的元素个数,这中间差了4个元素
    //小地址减去大地址得到的是-4
 
    //第一个代码是打印地址的,因为相减的结果是-4,存在内存里面,那么直接把存放-4的地址打印出来了
    //-4在内存里面存的是补码
    //11111111111111111111111111111100
    //打印地址直接将内存里面的这个二进制补码直接当成地址
    //4个二进制的1就是一个F,最后剩下的1100就是12,转换成6进制就是C
    return 0;
}
//考察的是指针的运算,数组指针+1跳过的是多少
//就是跳过一个指针类型,在这里p+1就是跳过一行

题目五

#include <stdio.h>
int main()
{
    int aa[2][5] = { 1, 2, 3, 4, 5,    
                     6, 7, 8, 9, 10 };//两行5列
    int* ptr1 = (int*)(&aa + 1);
    //&aa就是取出整个二维数组的地址,+1就是跳过整个二维数组
    //&aa+1得到的还是二维数组的地址,将这个地址强制类型转换为int *
    //因为&aa+1跳过的是整个数组,那么指向的位置就是这个二维数组的末尾
    //因为被强制转换为int *后再赋值给ptr1,那么赋值后ptr1指向的位置也是这个二维数组的末尾
    //
 
 
    int* ptr2 = (int*)(*(aa + 1));//aa+1--->aa[1]---第二行的数组名---第二行首元素的地址
    //aa是数组名,表示的是首元素的地址,就是第一行的地址
    //那么aa+1就是第一行的地址+1指向的就是第二行的地址
    //那么对其进行解引用得到的就是第二行整个一维数组
    //因为这个第二行的一维数组本身就是整型,那么前面的int *可以忽略
 
    //现在将(int*)(*(aa + 1))赋值给ptr2,那么ptr2也是指向的第二行的数组
    //aa + 1可以理解为aa[1],就是第二行的数组名,就是第二行数组首元素的地址,就是指向的6
 
 
    printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));//10,5
    //在这里,因为ptr1指向的是数组的末尾,并且ptr1是整型指针,那么ptr1-1指向的就是10的位置
    //那么对其进行解引用得到的就是10
 
    //因为ptr2指向的是6,那么ptr2-1得到的就是5的地址,对其进行解引用得到的就是5
 
    return 0;
}

题目六

#include <stdio.h>
int main()
{
    char* a[] = { "work","at","alibaba" };
    //正常是不能这么放的,那么就是把每个字符串的首元素地址存在这个指针数组里面了
 
 
    //这里定义的指针数组里面存的分别是w、a、a的地址
 
 
    char** pa = a;//a是数组名,表示的是首元素的地址,就是第一个元素的地址
    //首元素的地址是char*类型的,那么存在pa里面,那么pa就是一个二级指针
    //那么char**pa指向的就是首元素
    pa++;
    //pa指向的是第一个元素,那么++就是指向的第二个元素,就是跳过提个char*类型的指针
    //那么下面的*pa得到的就是第二个元素的地址
    printf("%s\n", *pa);//at
    //得到了地址,那么就从地址开始打印对饮的字符串
    return 0;
}

题目七--最难的

#include <stdio.h>
int main()
{
    char* c[] = { "ENTER","NEW","POINT","FIRST" };//每个元素是char*
    //存放的是每个字符串首字符的地址,
    //分别是E、N、P、F
 
 
    char** cp[] = { c + 3,c + 2,c + 1,c };
    //c是上面的指针数组的数组名,指向的就是首元素的地址,指向的就是第一个字符串
    //c指向的就是"ENTER"
    //c+1指向的就是"NEW"
    //c+2指向的就是"POINT"
    //c+3指向的就是"FIRST"
 
    //将c里面首元素的地址存放在cp里面,那么cp的类型就是一个二级指针
 
 
 
 
    char*** cpp = cp;
    //cp是数组名,表示的是首元素的地址,将二级指针的地址放在三级指针内
    //cpp指向的是首元素的地址,就是c+3
 
 
    //下面的++和--会有副作用的,会改变指
    //int a=5    ++a   这个动作完了之后a的值就变为6了
 
 
    printf("%s\n", **++cpp);//POINT
    //*  *  ++  cpp
    //先算++cpp,cpp本来放的是c+3的地址,那么现在进行++之后,就指向了c+2的地址
    // 然后解引用,因为cpp现在指向的是c+2的地址,那么第一次解引用就得到了c+2
    // 第二次解引用就是对c+2解引用,得到的就是"POINT"
    //
    //
    //
 
 
    printf("%s\n", *-- * ++cpp + 3);//ER
    //因为加法的优先级比较低,所以先算前面的
    // 所以先算++,再解引用,再--,再解引用
    // 
    // 因为上面的cpp已经进行++操作了,指向的是c+2,那么再次++就是指向的c+1了
    // 那么现在已经得到了c+1的地址了
    // 现在进行解引用通过地址找到c+1,
    // 
    // 原本放的是c+1,现在--就变成c了,就没有c+2了,--就是自减,c+1变成c了
    // c是指向的是"ENTER"这个字符串首元素E的地址,那么解引用完+3指向的就是E
    // //那么就是从E开始打印,所以只能打印ER
    // 
    // 
    // 
    // 
    //
    //
 
 
 
 
    printf("%s\n", *cpp[-2] + 3);//ST
    //*cpp[-2]是**(cpp+(-2))就是**(cpp-2)
    //
    // 一开始cpp指向的是原先的c+1的地址,但是c+1自减了1,就成c了,那么现在cpp指向的就是c的地址了
    // 那么cpp-2得到的就是c+3的地址
    // 这里的cpp-2不会导致cpp的改变,还是指向c
    // 
    // 那么cpp-2得到的是c+2的地址,解引用得到的就是c+3
    // c+3指向的是这串字符串首元素字符F的地址---"FIRST"
    // 
    // 最后F的地址+3就是S
    // 那么打印出来的就是ST
    // 
    // 这里最关键的就是*cpp[-2]这个的理解
    //
 
 
 
    printf("%s\n", cpp[-1][-1] + 1);// EW
    //cpp[-1][-1] + 1
    //转换形式
    // *(*(cpp-1)-1)+1
    // 这里的cpp指向的是c的位置
    // 那么cpp-1就指向了c+2的位置
    // 
    // *(cpp-1)拿到了c+2
    // 那么就变成下面这样了
    // *((c+2)-1)+1
    // c+2-1得到了c+1,就是现在的cpp指向的位置
    // 
    // c+1指向的就是"NEW"
    //对c+1进行解引用就拿到了"NEW"--N的地址
 
    //然后N的地址+1得到的就是E的地址
    //然后打印出来的就是EW
    return 0;
}
相关文章
|
3月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
63 0
|
1月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
84 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
1月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
54 9
|
1月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
45 7
|
2月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
153 13
|
2月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
2月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
129 3
|
2月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
62 11
|
2月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
2月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
44 1