指针习题笔记(较难,可用于思维锻炼)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 指针习题笔记(较难,可用于思维锻炼)

分析

(一)

(1)一维数组


int a[]={1,2,3,4};
char arr[]={'a','b','c','d','e','f'};//sizeof
int main()
{
//数组名是首元素地址
//除了
//1.sizeof(数组名)-数组名表示整个数组
//2.&数组名-数组名表示整个数组
//
//一维数组
    int a[]={1,2,3,4};
    printf("%d\n",sizeof(a));//sizeof(数组名)-计算的是数组总大小-单位是字节-16
    printf("%d\n"sizeof(a+0));//32位平台,4个字节,64位平台8个字节
    printf("%d\n",sizeof(*a));//4,a是首元素地址,首元素的解引用4个字节
    printf("%d\n",sizeof(a+1));//4,与sizeof(a+0)相同,只是a+1是第二个元素地址
    printf("%d\n",sizeof(&a));//取地址a取出的是数组的地址,数组的地址也是地址,地址的大小都是4个/8个字节
    printf("%d\n",sizeof(*&a));//整个数组的地址解引用,返回的是整个数组,sizeof计算的是数组的大小,单位是字节,所以为16,等价于sizeof(a)
    printf("%d\n",sizeof(&a+1));//跳过了整个数组,但是(&a+1)还是一个地址,补充,若是a+1,则只是跳过一个元素
    printf("%d\n",sizeof(&a[0]));//是地址就是4/8个字节
    printf("%d\n",sizeof(&a[0]+1));//4/8个字节
}
char arr[]={'a','b','c','d','e','f'};
printf("%d\n",sizeof(arr));//sizeof计算的是整个数组的大小,6*1=6个字节
printf("%d\n",sizeof(arr+0));arr没有单独放在sizeof中,所以arr表示的是首元素的地址(arr+0)也是首元素的地址,是地址就是4/8个字节
printf("%d\n",sizeof(*arr));//arr是首元素地址,*arr就是首元素,首元素是一个字符,所以是一个字节
printf("%d\n",sizeof(&arr));//取地址arr,虽然是数组的地址,但是还是地址4/8
printf("%d\n",sizeof(&arr+1));//与下图的含义相同,跳过整个数组后的地址,但是还是地址4/8
printf("%d\n",sizeof(&arr[0]+1));//取第一个元素地址+1就是第二个元素的地址,如果是arr[0]+1则跳过一个元素



char arr[]={a,b,c,d,e,f};//strlen


char arr[]={a,b,c,d,e,f};
printf("%d\n",strlen(arr));//遇到'\0'才会停,这里不知道在哪里会遇到'\0'所以是随机值
printf("%d\n",strlen(arr+0)); //数组名是首元素地址,+0还是首元素地址,所以和第一个答案相同,随机值
printf("%d\n",strlen(*arr));//arr是首元素地址,*arr是首元素,%d表示是97,那么strlen想要的是地址,就会把97当作地址,向后取元素,那么就是非法访问
printf("%d\n",strlen(arr[1]));//也是错误的
printf("%d\n",strlen(&arr));//  虽然取的是数组的地址,但是也是从数组首元素开始取,所以也是随机值
printf("%d\n",strlen(&arr+1));//跳过的是一个数组,也就是从‘f’之后开始找,与&arr的差值是6,随机值-6
printf("%d\n",strlen(&arr[0]+1));//从'b'开始取地址,比之前的地址(&arr)少1,随机值-1

char arr[]="abcdef"; //sizeof


int main()
{
    //char arr[]={'a','b','c'};
    char arr[]= "abcdef";
   
    printf("%d\n",sizeof(arr));//数组名单独放在sizeof中,这时候的数组表示整个数组,整个数组大小,那这个数组是7个元素,"abcdef\0",7*1=7
    printf("%d\n",sizeof(arr+0));//首元素地址+0还是首元素地址,4/8个字节
    printf("%d\n",sizeof(*arr));//既没有取地址也没有单独放在sizeof后面,所以是首元素地址,首元素地址解引用就是首元素,那么他的大小就是1
    printf("%d\n",sizeof(arr[1]));//第二个元素,大小也是1
    printf("%d\n",sizeof(&arr));//取地址arr,虽然是数组的地址,但是也是地址,地址大小是4/8
    printf("%d\n",sizeof(&arr+1));//跳过了一整个数组,但是还是一个数组的地址,4/8
    printf("%d\n",sizeof(&arr[0]+1));//取出第一个元素的地址+1,指向第二个元素的地址,还是一个地址4/8
    return 0;
}

char arr[]="abcdef";//strlen


int main()
{
    char arr[]="abcdef";
    printf("%d\n",strlen(arr));//遇到'\0'就停止,所以长度为6
    printf("%d\n",strlen(arr+0));//首元素地址+0,还是首元素地址,那么依然是从'a'向后数,得到6个元素
    printf("%d\n",strlen(*arr));//strlen接收的是一个地址,而这里是解引用arr,*arr表示传进去的是字符a,a的ascll码是97,97作为一个地址使用就是非法访问
    printf("%d\n",strlen(arr[1]));//与上面问题相同,非法访问
    printf("%d\n",strlen(&arr));//相当于strlen(arr),取地址arr,从a开始向后取,直到遇到'\0'
//但是这里会警告,因为&arr ---数组的地址---数组指针  char(*p)[7]=&arr
//&arr的类型是char(*)[7]
//而strlen的地址是const char *
//强制赋值也能输出,输出长度是6
    printf("%d\n",strlen(&arr+1));//与上面相同,也会发出警告,&arr+1,跳过这个数组后还是数组的地址,但是这里是随机值,因为跳过整个字符数组了
    printf("%d\n",strlen(&arr[0]+1));//遍历bcdef这5个字符,所以长度是5
}



char *p="abcdef";//sizeof


char *p="abcdef";//其实里面只放了a的地址
printf("%d\n",sizeof(p));//计算指针变量p的大小,4/8个字节
printf("%d\n",sizeof(p+1));//p里面存放的是a的地址,那么p+1就是b的地址,4/8个字节
printf("%d\n",sizeof(*p));//*p其实就是字符串的第一个字符,就是字符a,大小为1
printf("%d\n",sizeof(p[0]));//int arr[10];    arr[0]==*(arr+0)    p[0]=*(p+0),就是第一个元素字符a,大小为1
printf("%d\n",sizeof(&p));//&p,取地址p,是地址就是4/8个字节
printf("%d\n",sizeof(&p+1));//取地址还是4/8个字节
printf("%d\n",sizeof(&p[0]+1));//&p[0]='a'的地址,&p[0]+1就是p的地址
char *p="abcdef";//strlen
char *p="abcdef";//其实里面只放了a的地址
printf("%d\n",strlen(p));//p中包含的是a的地址,就是相当于将a的地址传过去,从a向后数6个字符
printf("%d\n",strlen(p+1));//从b向后数就是5
printf("%d\n",strlen(*p));//报错,把97作为地址传过去
printf("%d\n",strlen(p[0]));//也报错

特殊说明


printf("%d\n",strlen(&p));


这里是从p往后数,不知道什么时候会遇到’\0‘,而不是从a往后数,所以是随机值


例如a这块空间的地址是0x0012ff44,将其存放到p中,小端倒着存(44 ff 12 00);在00前出现了3个字符,strlen=3;



printf("%d\n",strlen(&p+1));//跳过存放a的地址的空间后,继续往后数,也是随机值

printf("%d\n",strlen(&p[0]+1));//取第一个元素地址+1,就是第二个元素的地址,长度是5



(2)二维数组


int a[3][4]={0};    
printf("%d\n",sizeof(a));//计算的是数组总大小,3*4*4=48
printf("%d\n",sizeof(a[0][0]));//一个整型元素的大小,4
printf("%d\n",sizeof(a[0]));//a[0]相当于第一行作为一维数组的数组名
//sizeof(arr[0])把数组名单独放在suzeof()内,计算的是第一行的大小
printf("%d\n",sizeof(a[0]+1));//a[0]没有单独放在sizeof内部,前面也没有&符号,所以a[0]代表首元素地址,第一行第一个元素地址+1,就是a[0][1]的地址,地址大小是4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));//a[0][1]的地址解引用就是第二个元素的地址
printf("%d\n",sizeof(a+1));//a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素地址,而二维数组的首元素是其第一行(把二维数组看成一维数组),a+1就是第二行数组的地址,4/8
printf("%d\n",sizeof(*(a+1)));//第二行的地址解引用,就是4*4=16
printf("%d\n",sizeof(&a[0]+1));//&a[0]表示第一行的地址,第一行的地址+1,就是第二行地址,4/8
printf("%d\n",sizeof(*(&a[0]+1)));//第二行的地址解引用,就是4*4=16
printf("%d\n",sizeof(*a));//a是首元素地址,就是第一行地址,*a就是第一行大小,16
printf("%d\n",sizeof(a[3]));//虽然没有第四行的数据,a[3]不会进行真的访问,所以和a[0]的大小相同,16


以下代码输出结果



解析


(&a+1)是跳过整个数组,但是代表数组的地址,在进行强制类型转换得到int *ptr=(int *)(&a+1)


(ptr-1)指向的是5的地址,解引用5的地址,就是5


(a+1)中的a表示的是第一个元素的地址,所以*(a+1)就是解引用2的地址


答案


2,5



解析


p+0x1,p代表整个结构体的地址,p+1代表跳过整个结构体,0x100000+20->转换为16进制,%p打印地址,需要显示前面的00,即0x00100014


(unsigned long)p,转换成10进制--1048576+1=1048577---再转换为16进制,即0x100001


(unsigned int*)p,p强转为无符号整型,无符号整型是4个字节,所以0x100000+4=0x100004


答案


0x00100014


0x00100001


0x00100004


解析



对于(&a+1)


&a表示整个数组的地址加1


对于(int *)(int)a+1


假设首元素的地址是0x00 00 00 05,强制类型转化为int,就是5,5+1=6


再强转化为地址0x00 00 00 06


ptr1[-1]=*(ptr1+(-1))=*(ptr1-1),向前挪动一个整型指针,所以打印0x4


*ptr2解引用可以向后访问4个字节



对于上图来说就是02 00 00 00


答案


4


02 00 00 00



解析


(0,1),(2,3),(4,5)这些逗号表达式的结果只算最后一个即1,3,5



a[0]是第一行的数组,表示首元素的地址,就是1的地址,p[0]=*(p+0)=*p,所以就是1


答案


1



解析

int(*p)[4]:p是指向4个整形元素的指针


p=a;a代表首元素的地址,就是第一行的地址,则a的类型是int(*)[5],p的类型是int(*),会出现警告,这里强行将a赋给了p,所以a的结果赋给p,类型不赋给p


p[4][2]:*(*(p+4)+2),其位置表示下图绿色方块



地址相减,得到的是指针和指针之间的元素,&p[4][2]小,&a[4][2]大,所以%d,是-4


对于%p(地址),表示的是无符号数,其地址就是-4的补码的值fffc


 


答案


0x FF FF FF  FC


-4



解析


整个二维数组的地址+1,相当于跳过整个二维数组


&aa+1是一个数组指针,不能直接赋给  int * ptr


对于*(aa+1)中的aa代表的是一维数组的地址,就是第一行的地址,第一行的地址+1,就指向第二行的地址,*(aa+1)对(aa+1)解引用操作,就相当于找到了第二行


*(aa+1)=aa[1],aa[1]是第二行的数组名,就是第二行的首元素地址,他的类型就是int *


虽然写了(int  *)(*(aa+1)):但是这里的强制类型转换是多余的



*(ptr1-1),ptr1本来指向的是10后面的位置,(ptr1-1)就跳到了10的位置,*(ptr1-1)就是10


*(ptr2-1),ptr2-1指向5的地址,解引用得到5


补充


在进行arr[2]计算的时候,arr是首元素地址,arr的地址+2,就是向后偏移两个整型,再解引用,即*(arr+2)



答案


10


5



提示



这里是将常量字符串“abcdef”首字符a的地址放到指针变量p中


回到题中,a[i]是字符指针,那么里面存的是w,a,a(三个字符串首字符的地址)



上图描述的是,a数组的每一个元素是char *,数组名表示的是首元素地址,对于a来说首元素的地址是绿色框框的地址,pa的类型是char**,即char *的地址


char* *pa:*pa表示pa是一个指针,char *表示pa指向的元素是char类型的指针,pa++跳过的是char*这样的变量


对于


int * p:* p表示p是一个指针,int表示指向的变量是整型


p+1:跳过一个人int型变量


同理


char **pa指向的是char *的变量


pa++跳过的是一个char *


数组里面的第一个元素是char *,那么+1就是指向第二个元素,pa指向的是第二个元素的地址,*pa就是第二个元素的内容,就是char *的地址,即a的地址,a的地址用%s打印,打印的就是at类型的字符串

解析

数组c



上图表示,将E的地址传入第一个char *,将N的地址传入第二个char,以此类推


数组cp



c是数组名,表示首元素的地址,cp指向的是c数组的第四个元素的地址,以此类推


cpp  


cpp指向的是cp数组首元素的地址


第一个 **++cpp


++cpp,cpp指向数组的第二个元素,*++p,相当于就是对第二个元素的地址解引用,就是拿到c+2,c+2又是第三个char*地址,**++p就是char*地址解引用,即char *,就是p的地址,再用%s形式打印,那么就会打印“point”



第二个 *--*++cpp+3


++cpp,cpp指向了cp数组第三个元素的地址,就是c+1的地址解引用,得到c+1,再--,得到c,而c指向的是c数组的第一个元素的地址,再*解引用,得到第一个元素的内容,即char *的内容,就是E的地址,再+3,就得到第二个E,以%s的形式打印就是ER



第三个*cpp[-2]+3---->**(cpp+(-2))+3---->**(cpp-2)+3


cpp-2得到cp第一个元素的地址,*(cpp-2)得到第一个元素的内容,即c+3,就是c数组第四个元素的地址,再**(cpp-2),得到char *,就是F的地址,+3就是S的地址


第四个 cpp[-1][-1]+1---->*(*(cpp-1)-1)+1


cpp-1指向cp第二个元素的地址,解引用操作得到c+2,c+2是c数组第3个元素的地址,这时已经进行到*(cpp-1),接下来再-1,得到c数组第2个元素的地址,再解引用,得到N的地址,N的地址+1,就是E的地址,再用%s打印,得到的是‘EW’


目录
相关文章
|
存储 C++
【指针笔试题的笔记】
【指针笔试题的笔记】
44 0
|
算法 编译器 C++
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(中)
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(中)
72 0
|
6月前
|
机器学习/深度学习 搜索推荐 算法
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
【再识C进阶2(下)】详细介绍指针的进阶——利用冒泡排序算法模拟实现qsort函数,以及一下习题和指针笔试题
|
存储 算法 编译器
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(上)
类与对象知识总结+封闭类+const+this指针 C++程序设计与算法笔记总结(三) 北京大学 郭炜(上)
56 0
|
7月前
|
C++
指针(笔记2)一
本文介绍了C++中`const`关键字修饰指针的两种情况:当`const`位于星号(*)左侧时,它限制指针所指向的内容不可修改,但指针自身可变;当`const`位于星号(*)右侧时,它限制指针变量不可改变,但可通过该指针修改其指向的内容。此外,文章还讨论了指针的基本运算,包括指针加减整数(用于遍历数组),指针减指针(计算两者间元素个数)以及指针的关系运算(在循环中控制指针移动)。
48 1
|
7月前
入门后指针进阶习题深度分析
入门后指针进阶习题深度分析
43 1
|
6月前
|
C++
指针习题练习
指针习题练习
27 0
|
7月前
|
程序员 编译器 C语言
指针(笔记2)二
这篇内容主要讲解了指针和野指针的概念以及如何避免野指针的问题。野指针是指针未初始化、越界访问或指向已释放内存的情况。避免野指针的方法包括初始化指针、避免指针越界和在不再使用时将指针设为NULL。此外,文章提到了`assert`断言在调试中的作用,它可以帮助检测程序运行时的错误条件。最后,讨论了函数调用中的传值调用和传址调用,指出传址调用允许函数直接修改实参变量的值。
38 0
|
7月前
|
存储 编译器
指针(笔记1) 二
这篇内容介绍了指针的相关概念,包括解引用操作符、指针变量的大小、指针的解引用、指针加整数以及void*指针的使用。解引用操作符允许通过指针访问和修改变量,指针变量的大小取决于地址总线的宽度(32位系统为4字节,64位系统为8字节)。指针加整数时,不同类型的指针会按相应类型大小移动。void*指针可以存储任何类型的数据地址,但不能直接解引用或进行指针运算,通常用于函数参数以实现泛型编程。最后强调了指针变量应指向相同类型的变量,并在类型不匹配时进行强制转换。
38 0
|
7月前
|
存储 C语言 Perl
指针(笔记1)一
本文介绍了计算机内存和地址的基本概念。内存由存储单元组成,每个单元有唯一地址,内存地址是无符号整数。在32位系统中,地址空间为4GB。内存被划分为字节单元,每个变量在内存中占据特定长度的空间,例如字符占1字节,整型占4字节。指针是存储变量地址的变量,通过取地址操作符(&)获取变量的地址。指针变量需要先赋值才能使用,并且只能指向定义时的变量类型。
35 0