C语言指针(5):strlen与sizeof的区别及指针笔试题练习

1、sizeof和strlen的对比

sizeof

        sizeof计算变量所占内存内存空间⼤⼩的,单位是字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。简单来说,sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。

#inculde <stdio.h>
int main()
{
 int a = 10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof a);//从这里可以看出sizeof是操作符而不是函数,因为函数调用必须有函数调用操作符()。
 printf("%d\n", sizeof(int));
 
 return 0;
}

strlen

        strlen是C语言中的一个库函数,功能是求字符串的长度。函数原型为:

size_t     strlen ( const char * str );
        strlen 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。如果找不到 \0 ,就会一直向后访问,直到找到为止,所以可能存在越界访问查找。
        
#include <stdio.h>
int main()
{
 char arr1[3] = {'a', 'b', 'c'};
 char arr2[] = "abc";
 printf("%d\n", strlen(arr1));
 printf("%d\n", strlen(arr2));
 
 printf("%d\n", sizeof(arr1));
 printf("%d\n", sizeof(arr1));
 return 0;
}

        如果多次输出结果,你会发现第一个值会不断变化,并且是随机的。这里就体现了strlen的性质,因为arr1中存放的只有abc三个字符,后面没有跟 \0 ,所以函数没有停止,会在c的地址继续向后访问。

sizeof 和 strlen的对比

sizeof
1. sizeof是操作符
2. sizeof计算操作数所占内存的⼤⼩,单位是字节(指针大小:32位:4  /64位:8)
3. 不关注内存中存放什么数据

 strlen

1. strlen是库函数,使⽤需要包含头⽂件 string.h
2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数
3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可能会越界

 数组和指针笔试题分析

        

        一维数组

int a[] = {1,2,3,4};
//arr是有四个整型元素的数组
printf("%d\n",sizeof(a));
//sizeof(a)是计算的整个数组的大小,16个字节
printf("%d\n",sizeof(a+0));
//sizeof(a+0)是计算的数组首元素的地址的大小,4/8个字节
printf("%d\n",sizeof(*a));
//sizeof(*a)是计算的数组首元素的大小,4个字节
printf("%d\n",sizeof(a+1));
//sizeof(a+1)是计算的数组第二个元素的地址的大小,4/8个字节
printf("%d\n",sizeof(a[1]));
//sizeof(a[1]) == sizeof(*(a+1))
//计算的是数组第二个元素的大小,4个字节
printf("%d\n",sizeof(&a));
//sizeof(&a)是计算的整个数组的地址的大小,4/8个字节
printf("%d\n",sizeof(*&a));
//sizeof(*&a) == sizeof(a)
//计算的是整个数组的大小,16个字节
printf("%d\n",sizeof(&a+1));
//sizeof(&a+1)计算的是a数组后相同空间的地址的大小,4/8个字节
printf("%d\n",sizeof(&a[0]));
//sizeof(&a[0])是计算的第一个元素的地址的大小,4/8个字节
printf("%d\n",sizeof(&a[0]+1));
//sizeof(&a[0]+1)是计算的第二个元素的地址的大小,4/8个字节

字符数组

代码段1:

char arr[] = {'a','b','c','d','e','f'};
//arr数组是存放了a~f 6个字符的数组
printf("%d\n", sizeof(arr));
//sizeof(arr)计算的是整个数组的大小,6个字节
printf("%d\n", sizeof(arr+0));
//sizeof(arr+0)计算的是数组首元素的地址的大小,4/8个字节
printf("%d\n", sizeof(*arr));
//sizeof(*arr)计算的是数组首元素的大小,1个字节
printf("%d\n", sizeof(arr[1]));
//sizeof(arr[1]) == sizeof(*(arr+1))
//计算的是数组第二个元素的大小,1个字节
printf("%d\n", sizeof(&arr));
//sizeof(&arr)计算的是整个数组的地址的大小,4/8个字节
printf("%d\n", sizeof(&arr+1));
//sizeof(&arr+1)计算的是数组地址后与数组大小相同的空间内的地址的大小,4/8个字节
printf("%d\n", sizeof(&arr[0]+1));
//sizeof(&arr[0]+1)计算的是数组第二个元素的地址的大小,4/8个字节

代码段2:

char arr[] = {'a','b','c','d','e','f'};
//arr数组是存放了a~f 6个字符的字符数组
printf("%d\n", strlen(arr));
//strlen(arr)计算的是从字符a开始到首个\0之前的元素个数,因为\0位置随机,所以结果为随机值x。
printf("%d\n", strlen(arr+0));
//strlen(arr+0)计算的是从字符a开始到首个\0之前的元素个数,因为\0位置随机,所以结果为随机值x。
printf("%d\n", strlen(*arr));
//strlen(*arr)表示的是a字符即ascll值97的地址位置为起始,首个\0之前的元素个数,但因为起始地址为不可访问地址,所以会报错,运行失败。
printf("%d\n", strlen(arr[1]));
//strlen(arr[1])表示的是b字符即ascll值98的地址位置为起始,首个\0之前的元素个数,但因为起始地址为不可访问地址,所以会报错,运行失败。
printf("%d\n", strlen(&arr));
//strlen(&arr):&arr表示整个数组的地址,同样表示数组首元素的地址,所以计算的是从字符a开始到首个\0之前的元素个数,因为\0位置随机,所以结果为随机值x。
printf("%d\n", strlen(&arr+1));
//strlen(&arr+1):&arr+1表示数组后与数组空间相同大小的内存处的地址,所以计算的是从字符f后一个位置开始到首个\0之前的元素个数,因为\0位置随机,所以结果为随机值x-6。
printf("%d\n", strlen(&arr[0]+1));
//strlen(&arr[0]+1):&arr[0]+1表示的是数组第二个元素的地址,所以计算的是从字符b开始到首个\0之前的元素个数,因为\0位置随机,所以结果为随机值x-1。

将两个报错代码屏蔽后:

字符串数组

代码段1:

char arr[] = "abcdef";
//arr数组是存放了字符串“abcdef\0”的字符串数组。
printf("%d\n", sizeof(arr));
//sizeof(arr)计算的是整个数组的元素大小,7个字节
printf("%d\n", sizeof(arr+0));
//sizeof(arr+0)计算的是数组首元素的地址的大小,4/8个字节
printf("%d\n", sizeof(*arr));
//sizeof(*arr)计算的是数组首元素的大小,1个字节
printf("%d\n", sizeof(arr[1]));
//sizeof(arr[1])计算的是数组第二个元素的大小,1个字节
printf("%d\n", sizeof(&arr));
//sizeof(&arr+1)表示的是整个数组的地址的大小,4/8个字节
printf("%d\n", sizeof(&arr+1));
//sizeof(&arr+1)表示的是整个数组地址+1的地址的大小,4/8个字节
printf("%d\n", sizeof(&arr[0]+1));
//sizeof(&arr[0]+1))表示的是数组第二个元素的地址,4/8个字节

代码段2:

char arr[] = "abcdef";
//arr数组是存放了字符串“abcdef\0”的字符串数组。
printf("%d\n", strlen(arr));
//strlen(arr)计算的是从a字符到第一个\0之前的元素个数,因为字符串末尾隐藏了一个\0,所以大小为6
printf("%d\n", strlen(arr+0));
//strlen(arr+0)计算的是从数组首元素的地址到\0之前的元素个数,大小为6
printf("%d\n", strlen(*arr));
//strlen(*arr)计算的是数组从以首元素的ascll值为地址的元素到第一个\0之前的元素个数,因为该地址不可访问,所以会报错。
printf("%d\n", strlen(arr[1]));
//计算的是数组从以第二个元素的ascll值为地址的元素到第一个\0之前的元素个数,因为该地址不可访问,所以会报错。
printf("%d\n", strlen(&arr));
//strlen(&arr):表示的是整个数组的地址到第一个\0之前的元素个数,整个数组的地址 == 首元素地址。
//大小为6
printf("%d\n", strlen(&arr+1));//因为起始位置是字符串末尾的\0之后,下一个\0位置未知,所以结果为随机值
printf("%d\n", strlen(&arr[0]+1));
//strlen(&arr[0]+1)表示的是数组第二个元素到字符串末尾\0之前的元素个数,大小为5.

将报错代码注释掉后:

代码段3:

char *p = "abcdef";
//p为指针变量,指向的是字符串的首元素a的地址。
printf("%d\n", sizeof(p));
//sizeof(p):p为指针变量,所以大小为4/8个字节
printf("%d\n", sizeof(p+1));
//sizeof(p+1)表示的是字符串中第二个元素b的地址的大小,4/8个字节
printf("%d\n", sizeof(*p));
//sizeof(*p)表示的是字符串首元素a的大小,1个字节(char)
printf("%d\n", sizeof(p[0]));
//sizeof(p[0])表示的是a的大小,1个字节(char)
printf("%d\n", sizeof(&p));
//sizeof(&p)表示的是指针变量p的地址的大小,4/8个字节
printf("%d\n", sizeof(&p+1));
//表示的是p的地址+1处的地址的大小,4/8个字节
printf("%d\n", sizeof(&p[0]+1));
//sizeof(&p[0]+1)表示的是字符串内第二个元素的地址的大小,4/8个字节

代码段4:

char *p = "abcdef";
//p是指针变量,指向字符串的元素的的地址
printf("%d\n", strlen(p));
//strlen(p)计算的是以p指针指向的地址为起始位置,到第一个\0之前的元素个数,大小为6
printf("%d\n", strlen(p+1));
//strlen(p+1)计算的是以p指针+1指向的地址为起始位置,到第一个\0之前的元素个数,大小为5
printf("%d\n", strlen(*p));//报错,解释同前
printf("%d\n", strlen(p[0]));//报错,解释同前
printf("%d\n", strlen(&p));
//strlen(&p)表示的是p指针变量的地址,\0位置不确定,所以大小为随机值
//从p这个指针变量的起始位置开始向后数的,p变量存放的地址是什么,不知道,所以答案是随机值
printf("%d\n", strlen(&p+1));
//strlen(&p+1)表示的是p指针变量+1的地址,\0位置不确定,所以大小为随机值
printf("%d\n", strlen(&p[0]+1));
//strlen(&p[0]+1)表示的是b的地址,计算的是以b的地址为起始位置,到第一个\0之前的元素个数,大小为5

二维数组

二维数组名的意义:
1. sizeof(数组名),这⾥的数组名表⽰整个二维数组,计算的是整个二维数组的⼤⼩。
2. &数组名,这⾥的数组名表⽰整个二维数组,取出的是整个二维数组的地址。
3. 除此之外所有的数组名都表⽰⾸元素(首行)的地址。
int a[3][4] = {0};
//a是三行四列的二维数组,并且全部初始化为0
printf("%d\n",sizeof(a));
//sizeof(a)计算的是整个二维数组的大小,12*4 = 48个字节
printf("%d\n",sizeof(a[0][0]));
//sizeof(a[0][0])计算的是数组首行首元素的大小,大小为4个字节
printf("%d\n",sizeof(a[0]));
//sizeof(a[0])计算的是数组首行的元素的大小,大小为16个字节
printf("%d\n",sizeof(a[0]+1));
//sizeof(a[0]+1)计算的是数组首行第二个元素的地址的大小,大小为4/8个字节
printf("%d\n",sizeof(*(a[0]+1)));
//计算的是数组首行第二个元素的大小,4个字节
printf("%d\n",sizeof(a+1));
//sizeof(a+1)计算的是数组第二行的地址的大小4/8个字节
printf("%d\n",sizeof(*(a+1)));
//sizeof(*(a+1))计算的是数组第二行的元素的大小,16个字节
printf("%d\n",sizeof(&a[0]+1));
//sizeof(&a[0]+1)计算的是数组第二行的地址的大小,4/8个字节
printf("%d\n",sizeof(*(&a[0]+1)));
//sizeof(*(&a[0]+1))计算的是数组第二行元素的大小,16个字节
printf("%d\n",sizeof(*a));
//sizeof(*a)计算的是数组首行元素的大小,16个字节
printf("%d\n",sizeof(a[3]));
//表示的是数组越界访问的第四行的元素大小,16个字节

3、指针运算笔试题解析  

题目一:

# include <stdio.h>
int main ()
{
int a[ 5 ] = { 1 , 2 , 3 , 4 , 5 };
int *ptr = ( int *)(&a + 1 );
printf ( "%d,%d" , *(a + 1 ), *(ptr - 1 ));
return 0 ;
}
// 程序的结果是什么?
2  5

 题目二:

// X86 环境下
// 假设结构体的⼤⼩是 20 个字节
// 程序输出的结构是啥?
struct Test
{
int Num;
char *pcName;
short sDate;
char cha[ 2 ];
short sBa[ 4 ];
}*p = ( struct Test*) 0x100000 ;
int main ()
{
printf ( "%p\n" , p + 0x1 );
printf ( "%p\n" , ( unsigned long )p + 0x1 );
printf ( "%p\n" , ( unsigned int *)p + 0x1 );
return 0 ;
}

 题目三:

# include <stdio.h>
int main ()
{
int a[ 3 ][ 2 ] = { ( 0 , 1 ), ( 2 , 3 ), ( 4 , 5 ) };
int *p;
p = a[ 0 ];
printf ( "%d" , p[ 0 ]);
return 0 ;
}
        这道题很容易让人进坑。
        题目中给的数组是由三个逗号表达式组成的数组,本质上是赋了3个初值,如果你当成了6个数,那就翻车了。
        结果:1

 题目四:

// 假设环境是 x86 环境,程序输出的结果是啥?
# include <stdio.h>
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 ]);
return 0 ;
}

因为二维数组在内存中是连续存放的,所以图如下:

         p是int (*)[4]类型,说明p一行只能存四个元素。也就是说,数组指针变量p的第一行是a[0]的第一行的前四个元素,p的第二行是a[0]的第五个元素,和a[1]的前三个元素,依次向后。
         &p[ 4 ][ 2 ] - &a[ 4 ][ 2 ] 是指针-指针的运算,之前有讲过指针-指针的绝对值得到的是指针和指针之间的元素个数。&p[4][2] == &a[3][3],所以  &p[ 4 ][ 2 ] - &a[ 4 ][ 2 ]的结果为-4.
-4用%d打印的结果就是-4,用%p打印需要进行原反补码的转换,
-4的原码是10000000000000000000000000000100  除符号位,其他位按位取反
-4的反码是111111111111111111111111111111111011  反码+1
-4的补码是111111111111111111111111111111111100  
因为是32位的环境转换成十六进制就是FFFFFFFC

题目五:

 #include <stdio.h>

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 ));
return 0 ;
}

 

结果:

题目六:

# include <stdio.h>
int main ()
{
char *a[] = { "work" , "at" , "alibaba" };
char **pa = a;
pa++;
printf ( "%s\n" , *pa);
return 0 ;
}
上图就是变量初始化后的结果,pa++使其指向a[1],*pa即a[1]指向的'a'的地址,用%s打印,遇到\0才结束,所以打印结果位“at”。

题目七:

# include <stdio.h>
int main ()
{
char *c[] = { "ENTER" , "NEW" , "POINT" , "FIRST" };
char **cp[] = {c+ 3 ,c+ 2 ,c+ 1 ,c};
char ***cpp = cp;
printf ( "%s\n" , **++cpp);
printf ( "%s\n" , *--*++cpp+ 3 );
printf ( "%s\n" , *cpp[ -2 ]+ 3 );
printf ( "%s\n" , cpp[ -1 ][ -1 ]+ 1 );
return 0 ;
}

 

(1) printf("%s\n", **++cpp);

++cpp 使cpp指向cp[1],两次解引用,表示为’p‘的地址,%s打印结果为POINT.

(2)printf("%s\n", * -- * ++ cpp + 3);

+加法操作符的优先级最小,所以+3最后计算。

++cpp 使cpp指向cp[2],一次解引用表示的使cp[2]中的内容c+1,前置--使内容变为c,解引用指向c[0],即’E‘的地址,+3指向第二个’E‘,%s打印的结果为"ER"

(3)printf("%s\n", *cpp[-2]+3);

cpp[-2] == *(cpp-2),并不会改变cpp的指向的地址,过程与上一问一致结果为ST。

(4)printf("%s\n", cpp[-1][-1]+1);

cpp[-1][1] == *(*(cpp-1)-1) ,先把cpp指向cp[1],然后解引用,表示内容c+2,-1使内容变为c+1,解引用指向c[1],结果为NEW.

  • 24
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值