深入理解指针 (5) -------学习笔记分享

目录

(1)sizeof和strlen的对比

(2)数组和指针笔试题解析

(3)指针运算笔试题解析

1. sizeof和strlen的对比

sizeof是操作符,不是函数

strlen是函数,求字符串长度,且只能针对字符串/字符数组,统计的是字符串中'\0'之前字符的个数

1.1 sizeof(是操作符,不是函数)

sizeof计算变量所占内存空间大小的,单位是字节,如果操作数是类型的话,计算的是适用类型创建的变量所占内存空间的大小

sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据

1.2 strlen

strlen是c语言库函数(string.h),统计的是参数str这个地址开始往后,'\0'之前字符串中字符的个数,strlen函数会一直往后找'\0'字符,直到找到为止,所以可能存在越界查找。

函数原型如下:

size_t strlen ( const char * str );

返回 C 字符串 str 的长度。

C 字符串的长度由终止 null 字符决定:C 字符串的长度等于字符串开头和终止 null 字符之间的字符数(不包括终止 null 字符本身)。

这不应与保存字符串的数组的大小相混淆。例如:

char mystr[100]=“test string”;

定义一个大小为 100 个字符的字符数组,但初始化 mystr 的 C 字符串的长度只有 11 个字符。因此,sizeof(mystr) 的计算结果为 100,而 strlen(mystr) 返回 11

在 C++ 中,char_traits::length 实现相同的行为。

#include <stdio.h>
#include <string.h>
int main ()
{
  char szInput[256];
  printf ("Enter a sentence: ");
  gets (szInput);
  printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
  return 0;
}
2. 数组和指针笔试题解析
2.1 一维数组
int a[] = {1,2,3,4};
printf("%zd\n",sizeof(a));//16
//数组a单独放在sizeof内部,a表示整个数组,计算的是整个数组的大小,单位是字节
​
printf("%zd\n",sizeof(a+0));//8(x64环境下)、4(x86环境下)
//这里的a是数组名,表示首元素的地址,a+0还是首元素的地址,计算的是首元素地址的大小
​
printf("%zd\n",sizeof(*a));//4
//a依旧是首元素的地址,*a就是a[0],计算的结果就是a[0]在内存中所占空间的大小
​
printf("%zd\n",sizeof(a+1));//8(x64环境下)、4(x86环境下)
//这里的a是数组名,表示首元素的地址,a+1是第二个元素(a[1])的地址,计算的是首元素地址的大小
​
printf("%zd\n",sizeof(a[1]));//4
​
printf("%zd\n",sizeof(&a));//8(x64环境下)、4(x86环境下)
//&a-这里的数组名a表示整个数组,&a是整个数组的地址
//数组的地址也是地址,是地址就是4/8个字节长度
​
printf("%zd\n",sizeof(*&a));//16
//角度1:*&a,这里的*和&抵消了,所以sizeof(*&a) 等价于 sizeof(a) 
//角度2:&a - 这是数组的地址,类型是(int*)[4],*&a是对(int*)[4]的解引用,访问的就是这个数组
​
printf("%zd\n",sizeof(&a+1));//8(x64环境下)、4(x86环境下)
//&a是数组的地址,&a+1是跳过这个数组之后的那个位置的地址
//&a+1 师弟地址,地址都是4/8个字节
​
printf("%zd\n",sizeof(&a[0]));//8(x64环境下)、4(x86环境下)
//&a[0]就是数组中第一个元素的地址
​
printf("%zd\n",sizeof(&a[0]+1));//8(x64环境下)、4(x86环境下)
//&a[0]+1就是数组中第二个元素的地址
2.2 字符数组

例1:

char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n",sizeof(arr));//6
printf("%zd\n",sizeof(arr+0));// 4/8
printf("%zd\n",sizeof(*arr));//1
printf("%zd\n",sizeof(arr[1]));//1
printf("%zd\n",sizeof(&arr));// 4/8
printf("%zd\n",sizeof(&arr+1));// 4/8
printf("%zd\n",sizeof(&arr[0]+1));// 4/8

例2:

char arr[] = {'a','b','c','d','e','f'};
printf("%zd\n",strlen(arr));//随机值a
​
printf("%zd\n",strlen(arr+0));//随机值a
​
printf("%zd\n",strlen(*arr));//程序崩溃
//*arr是首元素 —— 'a' —— 97,传递给strlen后,strlen会认为97是地址,然后去访问内存
​
printf("%zd\n",strlen(arr[1]));//程序崩溃
​
printf("%zd\n",strlen(&arr));//随机值a
​
printf("%zd\n",strlen(&arr+1));//随机值 a+6
printf("%zd\n",strlen(&arr[0]+1));//随机值 a-1

例3:

char arr[] = "abcdef";//末尾隐藏了'\0'
printf("%zd\n",sizeof(arr));//7
//计算的是数组的大小,记得还有'\0'
​
printf("%zd\n",sizeof(arr+0));// 4/8
//arr+0是数组首元素的地址,既然是地址,大小就是4/8字节
​
printf("%zd\n",sizeof(*arr));//1
//*arr是首元素,sizeof(*arr) -- 1
​
printf("%zd\n",sizeof(arr[1]));//1
​
printf("%zd\n",sizeof(&arr));// 4/8
//&arr取出的是整个数组的地址,既然是地址就是4/8
​
printf("%zd\n",sizeof(&arr+1));// 4/8
//&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后的那个位置的地址,所以&arr+1还是地址
​
printf("%zd\n",sizeof(&arr[0]+1));// 4/8
//&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,依然是地址

例4:

char arr[] = "abcdef";//末尾隐藏了'\0'
printf("%zd\n",strlen(arr));//6
//strlen是从第一个元素开始统计'\0'之前字符的个数
​
printf("%zd\n",strlen(arr+0));//6
//arr+0是数组首元素的地址 —— 6
​
printf("%zd\n",strlen(*arr));//程序崩溃
//*arr是首元素,'a' —— 97,97作为地址传入,strlen从内存中访问97对应内容,程序崩溃
​
printf("%zd\n",strlen(arr[1]));//程序崩溃
​
printf("%zd\n",strlen(&arr));//6
//&arr是数组的地址,数组的地址和数组首元素的地址是指向同一个位置的
​
printf("%zd\n",strlen(&arr+1));//随机值
//&arr是数组的地址,&arr+1跳过了整个数组,指向了数组后的那个位置的地址,所以&arr+1还是地址,具体指向的数据是啥由于没有初始化所以是随机值
​
printf("%zd\n",strlen(&arr[0]+1));//5
//&arr[0]是首元素的地址,&arr[0]+1是第二个元素的地址,那么strlen从数组的第二个元素开始往后统计字符的个数直到遇到'\0'

例5:

char* p = "abcdef";//末尾隐藏了'\0'
printf("%zd\n",sizeof(p));// 4/8
//p是指针变量,计算的是指针变量p的大小,4/8个字节
​
printf("%zd\n",sizeof(p+1));// 4/8
//p+1是第二个元素的地址,地址的大小就是4/8个字节
​
printf("%zd\n",sizeof(*p));//1
//p是char* 所以*p只能访问1个字节
​
printf("%zd\n",sizeof(p[0]));//1
//p[0] ——>*(p+0) —— *p —— 只能访问一个字节 —— 1
​
printf("%zd\n",sizeof(&p));// 4/8
//&p取出的是指针变量p的地址,既然是地址就是4/8
//*p —— char** —— 二级指针
​
printf("%zd\n",sizeof(&p+1));// 4/8
//&p是p的地址,&p+1是跳过p变量,指向了p的后边,&p+1还是地址,所以就是4/8
​
printf("%zd\n",sizeof(&p[0]+1));// 4/8
//&p[0]+1就是b的地址
//p[0]-- *(p+0) —— *p

例6:

char* p = "abcdef";//末尾隐藏了'\0'
printf("%zd\n",strlen(p));//6
​
printf("%zd\n",strlen(p+1));//5
​
printf("%zd\n",strlen(*p));//程序崩溃 *p -- a -- 97
​
printf("%zd\n",strlen(p[0]));//程序崩溃
​
printf("%zd\n",strlen(&p));//随机值
//&p strlen统计的是p变量里面的字符个数,p是指针变量
​
printf("%zd\n",strlen(&p+1));//随机值
​
printf("%zd\n",strlen(&p[0]+1));//5

上面题考来考去就是指针的运算和指针类型的理解

2.3 二维数组

数组名的意义:

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

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

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

int main()
{
    int a[3][4] = {0};
    printf("%zd\n",sizeof(a));//4*(3*4)= 48
    //a作为数组名,单独放在sizeof内部了,a表示的是整个数组,计算的是整个数组的大小,单位是字节
    
    printf("%zd\n",sizeof(a[0][0]));//4
    //a[0][0]是第一行第一个元素,大小是四个字节
    
    printf("%zd\n",sizeof(a[0]));//4*4 = 16
    //a[0]是第一行的数组名,单独放在sizeof内部,计算的是数组的大小,计算的第一行数组的大小
    
    printf("%zd\n",sizeof(a[0]+1));// 4/8
    //a[0]没有单独放在sizeof内部,所以a[0]是数组首元素地址,那就是第一行第一个元素的地址
    //a[0] + 1 等价于 &a[0][0]+1 --第一行第二个元素的地址,是地址那就是4/8
    
    printf("%zd\n",sizeof(*(a[0]+1)));//4
    //a[0] + 1 等价于 &a[0][0]+1 --第一行第二个元素的地址,*之后就是第一行第二个元素咯,大小是4字节
    
    printf("%zd\n",sizeof(a+1));// 4/8
    //a是二维数组的数组名,并没有单独放在sizeof内部,a表示首元素的地址-也就是第一行的地址
    //a+1 是第二行的地址,既然是地址,所以大小是4/8字节
    
    printf("%zd\n",sizeof(*(a+1)));//16
    //角度1:*(a+1) -- a[1] -- 单独放在sizeof内部,计算的是第二行的大小
    //角度2:a+1 是第二行的地址,类型是int(*)[4],数组指针,*之后就是第二行元素的大小,就是4*4 = 16
    
    printf("%zd\n",sizeof(&a[0]+1));// 4/8
    //a[0]是第一行的数组名,&数组名就是第一行的地址,&a[0] -- 第一行的地址;&a[0]+1 -- 第二行的地址
    
    printf("%zd\n",sizeof(*(&a[0]+1)));//16
    //*(第二行的地址),访问的是第二行的元素
    
    printf("%zd\n",sizeof(*a));//16
    //角度1:a没有单独放在sizeof内部,所以a是首元素地址(第一行地址),*a就是访问第一行的元素
    //角度2:*a就是*(a+0) -- a[0]
    
    printf("%zd\n",sizeof(a[3]));//16
    //但是a[3]数组越界了,但程序不会崩溃 -- sizeof内部的表达式是不会真实运算的
    //a[3] -- 第四行的数组名,类型是int(*)[4],sizeof根据类型推断长度
}

回忆:sizeof内部的表达式是不会真实运算的

int main()
{
    short s = 8;//short是两个字节
    int n = 12;
    printf("%zd\n",sizeof(s = n + 5));//打印2
    printf("%d\n",s);//打印8;s = n + 5没有计算
    return 0;
}
3. 指针运算的笔试题解析
3.1 题目1
int main()
{
    int a[5] = {1,2,3,4,5};
    int* ptr = (int*)(&a + 1);
    printf("%d,%d",*(a + 1),*(ptr - 1));//打印2,5
    //ptr是整型指针,指向的是a数组的结尾处的地址;
    //由于ptr是int*类型,-1相当于回退一个整型的地址,指向a最后一个元素就是5
    //a是数组首元素的地址,a+1就是数组第二个元素的地址,*后访问第二个元素,打印2
    return 0;
}
3.2 题目2
//在x86环境下
//假设结构体的大小是20个字节
//程序输出的结果是啥?
struct Test
{
    int Num;
    char* pcName;
    short sDate;
    char cha[2];
    short sBa[4];
}*p = (struct Test*)0x100000;
​
//指针 + 1 和类型有关系
//整数 + 1 就是+ 1
int main()
{
    //%p打印的是十六进制的地址
    printf("%p\n",p + 0x1);//p是结构体指针,+1就是跳过一个结构体,一个结构体是20个字节
    //20在十六进制下是14,所以最后结果是0x100014
    
    printf("%p\n",(unsigned long)p + 0x1);//p被强制转换为整型了哈,+1就是 0x100001
    printf("%p\n",(unsigned int*)p + 0x1);//p被强制转换为另一类指针,+1跳过一个unsigned int类型的变量,就是四个字节,0x100004
    return 0;
}
3.3 题目3
int main()
{
    int a[3][2] = {(0,1),(2,3),(4,5)};//注意这里是();
    //用的是(),那这里就是逗号表达式了,从左向右依次计算,但整个表达式的结果取决于最后一个表达式结果
    //(0,1)表达式的结果就是1
    //所以数组实际上是{{1,3},{5,0},{0,0}};
    int* p;
    p = a[0];//数组名a[0]是第一行的地址,也就是第一行第一个元素的地址,p指向1所在的位置
    printf("%d",p[0]);//p[0] -- *(p+0),结果就是1
    return 0;
}
3.4 题目4
//假设环境是x86环境,程序输出的结果是啥
int main()
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf("%p,%d\n",&p[4][2]-&a[4][2],&p[4][2]-&a[4][2]);//打印FFFFFFFC,-4
    return 0;
}

此题有一点点抽象成分,我们需要学会自己画内存的情况来分析(大致如下,不好语言描述,大家借助图标理解)

两个地址相减得到的两个地址间元素的个数,而 &p[4][2]-&a[4][2]是小-大得到-4,得到的是负值。
但用%p打印的是地址,所以就把-4在内存里的值直接就当成地址,而-4在地址中以补码形式储存:11111111111111111111111111111100,在x86环境下转换为十六进制就是FFFFFFFC
用%p打印的时候,直接就把-4在内存中的补码当作地址(转化为十六进制)了
而%d打印的时候是有符号整型就会将-4的补码转化为十进制打印出来

3.5 题目5
int main()
{
    int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
    int* ptr1 = (int*)(&aa + 1);
    int* ptr2 = (int*)(*(aa + 1));
    printf("%d,%d",*(ptr1 - 1),*(ptr2 - 1));//打印 10,5
    return 0;
}
//&aa + 1,跳过整个二维数组,就是二维数组aa的末尾处的地址,还是个二维数组的地址;于是有将他强制转换为int*的操作进而赋给ptr1;
//ptr1 - 1就是ptr1地址向前挪一个整型大小,*之后访问一个整型就是10;
//aa是数组名,就是数组第一行第一个元素的地址,aa + 1就是数组第二行第一个元素的地址,*(aa + 1)就是访问第二行这个数组,这个本身就是int*的,所以前面的强制转换(int*)就只是迷惑人的;
//ptr2和aa+1指向的都是6,ptr2 - 1之后指向的就是5;

3.6 题目6(alibaba曾经出的一个题 )
int main()
{
    char* a[] = {"work","at","alibaba"};
    //数组里面存的是三个单词的第一个字母的地址
    char** pa = a;
    //a是数组名,就是首元素的地址,pa要存放这个地址的话就要用二级指针,也就是char**
    pa++;
    //pa+1之后就是单词at首字母的地址
    printf("%s\n",*pa);//解引用pa,访问pa内的字符串"at",打印at
    //%s 打印的时候就需要地址
    return 0;
}
3.7 题目7
int main()
{
    char* c[] = {"ENTER","NEW","POINT","FIRST"};
    char** cp[] = {c + 3,c + 2,c + 1,c};
    char*** cpp = cp;
    printf("%s\n",**++cpp);//语句1
    printf("%s\n",*-- * ++cpp + 3);//语句2
    printf("%s\n",*cpp[-2] + 3);//语句3
    printf("%s\n",cpp[-1][-1] + 1);//语句4
    return 0;
}
c(char*)cp(char**)cpp(char***) --- cp
ENTERE的地址 - cc + 3
NEWN的地址 - c + 1c + 2
POINTP的地址 - c + 2c + 1
FIRSTF的地址 - c + 3c

对于语句1:

printf("%s\n",**++cpp);//打印POINT
//++cpp是指向cp的c+2处的地址的指针变量;
//*++cpp就找到c+2的地址;
//c+2又是指向字符串首字母P地址的指针变量;
//*(c+2)就找到POINT字符串的首字母的地址;(%s需要字符串首元素的地址,即可打印出字符串)
//而*(c+2)也就是**++cpp;

对于语句2:

printf("%s\n",*-- * ++cpp + 3);//打印ER
//+3优先级很低,最后再+3;
//++cpp是指向cp的c+1处的地址的指针变量;(经历语句1之后cpp已经是指向cp的c+2处地址的指针变量了)
//*++cpp就找到c+1的地址;
//--*++cpp就是c的地址;
//c又是指向字符串首字母E地址的指针变量;
//*--*++cpp就找到ENTER字符串首字母E的地址;
//*--*++cpp + 3,由于此处的类型是char*,+3就相当于指针往后跳三个字节,指向第二个E;最后打印ER

对于语句3:

printf("%s\n",*cpp[-2] + 3);//打印ST
//cpp[-2]等价于*(cpp-2),*cpp[-2]等价于*(*(cpp-2));
//cpp-2就是是指向cp的c+3处的地址的指针变量;(经历语句2之后cpp已经是指向cp的c+1处地址的指针变量了)
//*(cpp-2)就找到c+3的地址
//*(*(cpp-2))就找到FIRST字符串首字母F的地址;
//*(*(cpp-2)) + 3后指向S,打印ST;

对于语句4:

printf("%s\n",cpp[-1][-1] + 1);//
//cpp[-1]等价于*(cpp-1);cpp[-1][-1]等价于*(*(cpp-1)-1);
//cpp-1就是指向cp的c+2处地址的指针变量;(经历语句2、3之后cpp已经是指向cp的c+1处地址的指针变量了);
//*(cpp-1)就找到c+2的地址;
//*(cpp-1) - 1就找到cp中c+1的地址;
//*(*(cpp-1)-1)就找到NEW字符串首字母N的地址;
//*(*(cpp-1)-1) + 1后指向E;于是打印EW;

上述题目,看似很难但实际上还是指针运算和指针类型的理解

要达到学以致用,任重而道远

Mais iln’ya pas de limites pour aimer et que m’importe de mal éteindre si je peux tout embrasser

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值