C语言指针5
文章目录
- 1.sizeof和strlen对比
- 1.1sizeof
- 1.2strlen
- 1.3sizeof和strlen的对比
- 2.数组和指针的笔试题
- 2.1一维数组
- 2.2字符数组
- 2.3二维数组
- 3.指针运算笔试题
1.sizeof和strlen对比
1.1sizeof
在学习操作符的时候,我们学习了 sizeof。sizeof 用于计算变量所占内存空间的大小,单位是字节。如果操作数是类型,sizeof 计算的是使用该类型创建的变量所占内存空间的大小。sizeof 只关注占用内存空间的大小,不在乎内存中存放什么数据。
#include<stdio.h>
int main()
{int a = 10;printf("%zd\n", sizeof(a));printf("%zd\n", sizeof(int));return 0;
}
1.2strlen
strlen
是库函数是用来求 字符串长度。
size_t strlen ( const char * str )
;
该函数是从str
这个地址开始查找,查找到\0
结束,并记录从str
到\0
之前元素个数。
因为strlen
会一直向后查找\0
,直到找到为止,所以可能存在越界行为。
#include <stdio.h>
#include<string.h>
int main()
{char arr1[3] = { 'a', 'b', 'c' };char arr2[] = "abc";printf("%zd\n", strlen(arr1));printf("%zd\n", strlen(arr2));printf("%zd\n", sizeof(arr1));printf("%zd\n", sizeof(arr2));return 0;
}
这里str1就是因为一开始没有写入\0所以产生了越界查找,找到了第36位才找到系统中的随机位置的\0,所以原本长度为3记录成了35。
1.3sizeof和strlen的对比
sizeof | strlen |
---|---|
1. sizeof是操作符 | 1. strlen是库函数,使用时需要包含头文件srring.h |
2.sizeof计算操作数占内存空间大小,单位是字节 | 2. strlen计算的是字符串的长度,统计的是\0之前字符的长度 |
3.不关注内存中存放什么数据 | 3. 关注内存中是否有\0 |
2.数组和指针的笔试题
2.1一维数组
- 指针在32位编译器下内存大小为4,64位为8。
#include <stdio.h>
#include<string.h>
int main()
{int a[] = { 1,2,3,4 };printf("%zd\n", sizeof(a));//sizeof(数组名)计算整个数组内存空间大小printf("%zd\n", sizeof(a + 0));//计算首地址空间大小(这里是64位)printf("%zd\n", sizeof(*a));//计算首元素空间大小printf("%zd\n", sizeof(a + 1));//计算第二位数据地址空间大小printf("%zd\n", sizeof(a[1]));//计算第二个数据空间大小printf("%zd\n", sizeof(&a));//计算首地址空间大小printf("%zd\n", sizeof(*&a));//sizeof(数组名)计算整个数组内存空间大小printf("%zd\n", sizeof(&a + 1));//计算第二位数据地址空间大小printf("%zd\n", sizeof(&a[0]));//计算首地址空间大小printf("%zd\n", sizeof(&a[0] + 1));//计算第二位数据地址空间大小
}
补充:
这里的
&a+1一共跳过了多少字节?
答案:16 &a取出的是整个数组,+1相应的就跳过了整个数组,要和a+1做区分
2.2字符数组
例一
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", sizeof(arr));//sizeof(数组名)计算整个数组内存空间大小
printf("%zd\n", sizeof(arr + 0));//计算首地址空间大小(这里是64位)
printf("%zd\n", sizeof(*arr));//计算首元素空间大小
printf("%zd\n", sizeof(arr[1]));//计算首元素空间大小
printf("%zd\n", sizeof(&arr));//计算首地址空间大小
printf("%zd\n", sizeof(&arr + 1));//计算第二位数据地址空间大小
printf("%zd\n", sizeof(&arr[0] + 1));//计算第二位数据地址空间大小
例二
char arr[] = { 'a','b','c','d','e','f' };
printf("%zd\n", strlen(arr));//随机值;arr是首元素的地址,数组中没有\0,一直往后找,什么时候遇到\0不清楚
printf("%zd\n", strlen(arr + 0));//随机值;arr是首元素的地址,arr+0依然是首元素的地址
//printf("%zd\n", strlen(*arr));//*arr是首元素,是'a' - 97,传给strlen函数,97会被当做地址.
//以97作为地址,会形成非法访问,程序会崩溃
//printf("%zd\n", strlen(arr[1]));//arr[1]是第2个元素,就是'b' - 98,传给strlen函数,98会被当做地址.同上,程序崩溃
printf("%zd\n", strlen(&arr));//&arr是数组的地址,虽然是数组的地址,值和首元素的地址一样.strlen依然是从第一个
//字符的位置开始向后找\0,会得到随机值.
printf("%zd\n", strlen(&arr + 1));//&arr + 1是f后面的地址,什么时候遇到\0,依然不知道.随机值.
printf("%zd\n", strlen(&arr[0] + 1));//&arr[0]+1就是第二个元素的地址,得到的也是随机值
例三
char arr[] = "abcdef";
printf("%zd\n", sizeof(arr));//arr表示整个数,计算的是整个数组的大小单位字节,7*1 = 7(不用忘记\0)
printf("%zd\n", sizeof(arr + 0));//arr就是数组首元素的地址,arr+0还是数组首元素的地址 4 / 8(32位/64位)
printf("%zd\n", sizeof(*arr));//1,arr就是数组首元素的地址,*arr是首元素,大小就是1个字节
printf("%zd\n", sizeof(arr[1]));//arr[1]数组的第二个元素,计算的就是第二个元素的大小,单位是字节 - 1
printf("%zd\n", sizeof(&arr));//arr表示整个数组,&arr取出的是整个数组的地址,是地址大小就是4/8个字节
printf("%zd\n", sizeof(&arr + 1));//&arr + 1是跳过这个数组后的地址,是地址大小就是4/8个字节
printf("%zd\n", sizeof(&arr[0] + 1));//&arr[0]是数组首元素的地址,&arr[0]+1是数组第二个元素的地址:4 / 8
例四
char arr[] = "abcdef";
printf("%zd\n", strlen(arr));//6: arr是数组首元素的地址,从第一个元素开始,统计\0之前字符的个数
printf("%zd\n", strlen(arr + 0));//6:arr是数组首元素的地址,arr+0还是数组首元素的地址,结果同上
//printf("%zd\n", strlen(*arr));//arr是数组首元素的地址,*arr就是首元素了,*arr == 'a' == 97
//非法访问内存,导致程序崩溃
//printf("%zd\n", strlen(arr[1]));//arr[1]是第二个元素 == 'b' == 98, 道理同上
printf("%zd\n", strlen(&arr));//6:&arr取出的是数组的地址,数组的地址和首元素的地址是同一个值
//strlen也是从第一个字符开始向后统计\0之前的字符个数。
printf("%zd\n", strlen(&arr + 1));//随机值
printf("%zd\n", strlen(&arr[0] + 1)); //5:&arr[0] + 1是第二个元素的地址,\0之前有5个元素
例五
const char* p = "abcdef";
printf("%zd\n", sizeof(p));//4/8
//p是一个指针变量,大小就是4/8个字节
printf("%zd\n", sizeof(p + 1));//4/8
//p中存放的是'a'的地址,p+1是'b'的地址
//大小就是4/8个字节
printf("%zd\n", sizeof(*p));//1: *p=='a'
printf("%zd\n", sizeof(p[0]));//1:p[0]==*(p+0)==*p
//结果就同上
printf("%zd\n", sizeof(&p));//&p是指针变量p的地址
//是地址大小就是4/8个字节
printf("%zd\n", sizeof(&p + 1));//&p + 1还是地址大小是4/8个字节
//&p+1是指向p变量的后边
printf("%zd\n", sizeof(&p[0] + 1));//&p[0] + 1是'b'的地址
//大小是4/8个字节
例六
char* p = "abcdef";
printf("%zd\n", strlen(p));//6: p里边存放是'a'的地址
printf("%zd\n", strlen(p + 1));//5: p+1是'b'的地址
//printf("%zd\n", strlen(*p));//非法访问, *p是'a'
//printf("%zd\n", strlen(p[0]));//非法访问,效果同上
printf("%zd\n", strlen(&p));//随机值
//&p是p这个变量的地址,strlen就是从p这块空间的起始地址开始向后找\0的
//p中存放的地址是不确定的,所有有没有\0,什么时候会遇到\0都不确定
printf("%zd\n", strlen(&p + 1));//随机值
//&p+1是p变量后边的地址,从这个位置向后的内存数据不知道
//什么时候会遇到\0都不确定
printf("%zd\n", strlen(&p[0] + 1));//5
2.3二维数组
int a[3][4] = { 0 };
printf("%zd\n", sizeof(a));//48: a是数组名,单独放在sizeof内部,表示整个数组,计算的是整个数组的大小,单位是字节3*4*4 = 48
printf("%zd\n", sizeof(a[0][0]));//4: a[0][0]是第一行第一个元素
printf("%zd\n", sizeof(a[0]));//16: a[0]是第一行这个一维数组的数组名,数组名单独放在sizeof内部,计算的是第一行这个一维数组的大小
printf("%zd\n", sizeof(a[0] + 1));//4/8
//a[0]是数组名,这里表示数组首元素的地址,是第一行第一个元素的地址
//a[0] + 1就是第一行第二个元素的地址,是地址大小就是4/8
printf("%zd\n", sizeof(*(a[0] + 1)));//*(a[0] + 1))是第一行第二个元素-大小是4个字节
printf("%zd\n", sizeof(a + 1));//a+1就是第二行的地址,是地址就是4/8个字节
//a是二维数组的数组名,在这里表示首元素的地址,也就是第一行的地址
//a+1就是第二行的地址
printf("%zd\n", sizeof(*(a + 1)));//16
//a + 1是第二行的地址,*(a+1)得到的就是第二行
//int(*)[4] 对数组指针解引用,放一个数组,就是一行的一维数组
//*(a+1) == a[1], a[1]是第二行的数组名,sizeof(arr[1])计算的是第二行的大小
//
printf("%zd\n", sizeof(&a[0] + 1));//4/8:
//a[0]是第一行的数组名,&a[0]取出的是第一行这个一维数组的地址
//&a[0]+1就是第二行的地址
printf("%zd\n", sizeof(*(&a[0] + 1)));//*(&a[0] + 1)是第二行,计算的是第二行的大小,16个字节
printf("%zd\n", sizeof(*a));//16: a是二维数组的数组名,a是首元素的地址,就是第一行的地址,*a就是第一行
//计算的是第一行的大小,16个字节
//*a == *(a+0) == a[0]
printf("%zd\n", sizeof(a[3]));//16: 没有越界访问,sizeof内部的表达式是不计算的.
//sizeof(int) - 4
//sizeof(4+3);--4
数组名的意义:
- sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小。
- &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。
- 除此之外,所有的数组名都表示首元素的地址。
3.指针运算笔试题
接下来的这些题,如果上面的知识掌握牢固是很简单的。我只会在略难的地方再次讲解。
题目1
#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
//在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?struct Test{int Num;char* pcName;short sDate;char cha[2];short sBa[4];}*p = (struct Test*)0x100000;printf("%zd\n", sizeof(unsigned long));printf("%p\n", p + 0x1);printf("%p\n", (unsigned long)p + 0x1);//这里加1加的是数字了printf("%p\n", (unsigned int*)p + 0x1);return 0;
- 第一个输出:4
解析:X86(32 位)环境中,unsigned long 类型的大小为 4 字节,因此 sizeof(unsigned long) 的结果是 4。 - 第二个输出:0x100014
解析:p 是指向 struct Test 类型的指针,该结构体大小为 20 字节(题目已说明)。指针运算中,p + 0x1 表示移动一个结构体的大小(20 字节)。初始地址是 0x100000,加上 20(十六进制为 0x14),结果为 0x100000 + 0x14 = 0x100014。 - 第三个输出:0x100001
解析:(unsigned long)p 将指针地址转换为无符号长整数,此时的 +0x1 是单纯的数值加 1。初始地址 0x100000 加 1,结果为 0x100001。 - 第四个输出:0x100004
解析:(unsigned int*)p 将指针转换为指向 unsigned int 类型的指针,X86 环境中 unsigned int 大小为 4 字节。指针运算中,+0x1 表示移动 4 字节。初始地址 0x100000 加 4,结果为 0x100004。
题目3
#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;
}
这段代码的输出结果是 1。
关键问题出在二维数组的初始化方式上:
这里使用了逗号表达式 (0, 1) 而不是大括号 {0, 1}。逗号表达式的结果是最后一个表达式的值,因此:
(0, 1) 的结果是 1
(2, 3) 的结果是 3
(4, 5) 的结果是 5
剩余未显式初始化的元素会被自动初始化为 0,因此数组实际存储为:
a[0][0] = 1, a[0][1] = 3
a[1][0] = 5, a[1][1] = 0
a[2][0] = 0, a[2][1] = 0
指针 p 指向数组首行 a[0],因此 p[0] 等价于 a[0][0],其值为 1。
题目4
//假设环境是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;
}
在 x86 环境下,程序的输出结果是:fffffffc,-4
- a 是一个 5×5 的二维数组,数组名 a 代表首元素地址(即 a[0] 的地址)
- p 是一个指向 “包含 4 个 int 元素的数组” 的指针
- 指针运算分析:
p = a 后,p 指向数组 a 的起始位置
p[4] 等价于 *(p+4),由于 p 是指向 4 个 int 的数组指针,所以 p+4 实际移动 4×4×4 = 64 字节(x86 中 int 为 4 字节)
p[4][2] 是上述位置向后再移动 2 个 int(8 字节)的位置 - 地址差值计算:
a[4][2] 实际地址:从起始位置移动 4×5×4 + 2×4 = 88 字节
p[4][2] 实际地址:从起始位置移动 4×4×4 + 2×4 = 72 字节
两者差值:72 - 88 = -16 字节,换算为 int 类型的个数(每个 int4 字节)就是 -4
题目5
#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;
}
题目6
#include <stdio.h>
int main()
{char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;
}
题目7
#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;
}
最后一题非常有助于考察大家对上面所有知识的理解程度,这里先不做解析,大家好好思考。