复习指针(17)
<1>sizeof与strlen的对比
1.1 sizeof
- 在学习操作符的时候,我们学习了sizeof , sizeof计算变量所占内存空间大小,单位是字节,如果操作数是类型的话,计算的是使用 类型创建的变量所占内存空间的大小
- sizeof只关注占用内存空间的大小,不在乎内存中存放什么数据
a.语法
sizeof(变量); sizeof(类型); sizeof 变量; // 不加括号也可以,只能变量
b.基础类型sizeof大小(32 / 64)
- char:1字节
- short:2字节
- int:4字节
- long:32位4 / 64位8
- long long:8字节
- float:4字节
- double:8字节
c.进阶类型sizeof大小
1.普通数组
int arr[10];
sizeof(arr);
- arr 代表整个数组
- 结果:元素个数 × 单个类型大小
- 上例:10×4=40
2.字符数组
char str[] = "abc";
sizeof(str);
- 字符串末尾自带 \0
- 一共:a b c \0 → 4字节
👉 sizeof 包含结束标志 \0
3.数组传参
void test(char arr[])
{
sizeof(arr); // 结果恒为4/8
}
- 数组传参退化为指针(本质传参的类型就是指针)
- sizeof算的是指针大小(固定由本身系统决定),不再是数组大小
4.指针sizeof(重)
- 32位系统:任意类型指针都是 4字节
- 64位系统:任意类型指针都是 8字节
规律:
- char* 、 int* 、 double* 、数组指针、函数指针 sizeof全部一样大 因为指针只存地址,地址长度统一
5.结构体sizeof(内存对齐)
- 成员偏移必须是自身大小整数倍
举例:Short占2个字节,就只能放在偶数的位置0 2 4 6
Int占4个字节,所以只能放在4的倍数0 4 8 12
- 整体大小必须是最大基本成员整数倍
这个挺好理解,就是类型占用的内存空间大小的倍数
- 会有空洞填充,浪费空间
struct S
{
char c; //1字节
int i; //4字节
};
内存格子:0 1 2 3 4 5 6 7
- 0号格子:放 char(满满1字节都在用)
- 1、2、3:空洞!啥都不放!空白!
- 4、5、6、7:放 完整int(4格全部存int数字,一点不空)
👉看懂区别:
- int自己内部:没有空洞,满满4字节数据
- char 和 int 中间:被迫空了3格
为什么非要空着?为什么int不能从1号格子开始放?
原因:CPU读取速度(硬件底层!必考原理)
CPU读内存不是1格1格读CPU一次天生就是读:4字节、8字节整块读取
- int是4字节数据
- CPU设计:只喜欢从 0、4、8、12…这种4倍数地址一口气抓4个格子
- 如果int强行从1号地址开始: 需要读两次内存、还要拼接数据、速度巨慢、效率极低、部分老CPU直接报错崩溃
6.sizof的核心特性
- 只看空间大小,不看存什么数据
- 不访问内存,不运算内容
- 不计算表达式运行结果
7.实例
a.
这里涉及了C语言算术类型转化规则:不同类型运算时,低精度类型会向高精度类型转化
因此int会提升为double , 所以运算结果的类型是double
b.
有无 \0 的区别
2.2 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(arr2));
return 0;
}
随机值,\0不知道在哪 3 3 4
1.3 sizeof 与 strlen的对比
<2> 数组和指针笔试题解析
数组名的意义:
- 1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小
- 2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址
- 3.除此之外,所有的数组名都表示首元素地址
2.1一维数组
int main()
{
int a[] = { 1,2,3,4 };//4 * 4 = 16
printf("%zu\n", sizeof(a));//16, a是数组名,数组名单独放在sizeof中,数组名表示整个数组,计算的是整个数组的大小,单位是字节
printf("%zu\n", sizeof(a + 0));//4/8,a是数组名,并没有单独放在sizeof内部,也没有&,所以a就是首元素的地址
//a+0,还是首元素的地址,sizeof(a + 0)计算的是一个地址的大小,4/8
printf("%zu\n", sizeof(*a));//a是数组名,并没有单独放在sizeof内部,也没有&,所以a就是首元素的地址
//*a就是首元素==a[0],就是整数1,sizeof(*a)就是4个字节
printf("%zu\n", sizeof(a + 1));//4/8, a就是首元素的地址,a+1就是第二个元素的地址,sizeof(a + 1)计算的是地址的大小
printf("%zu\n", sizeof(a[1]));//4,a[1]就是数组的第二个元素,大小是4个字节
printf("%zu\n", sizeof(&a));//&a-取出的是数组的地址,数组的地址也是地址呀,是地址大小就是4/8个字节
//&a 的特殊性体现在+-整数
//int (*p)[4] = &a;
printf("%zu\n", sizeof(*&a));//16,&a-取出的是数组的地址,他的类型是int(*)[4],对于数组指针解引用,访问的是这个数组,大小就是16个字节
//2. *&a == a, sizeof(*&a) == sizeof(a)
printf("%zu\n", sizeof(&a + 1));//4/8,&a是数组的地址,&a+1是跳过数组后的地址,是地址就是4/8个字节
printf("%zu\n", sizeof(&a[0]));//4/8,a[0]是第一个元素,&a[0]就是第一个元素的地址,大小就是4/8
printf("%zu\n", sizeof(&a[0] + 1)); //4/8, &a[0]就是第一个元素的地址,&a[0] + 1就是第二个元素的地址,大小就是4/8
return 0;
}
2.2 字符数组
#include <stdio.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%zu\n", sizeof(arr));//6, arr表示整个数组,sizeof(arr)计算的是整个数组的大小
printf("%zu\n", sizeof(arr + 0));//4/8, arr就是首元素的地址,只要是地址大小就是4/8个字节
printf("%zu\n", sizeof(*arr));//1,arr就是首元素的地址,*arr就是首元素,sizeof(*arr)计算的是首元素的大小
printf("%zu\n", sizeof(arr[1]));//1, arr[1]是第二个元素,大小就是1个字节
printf("%zu\n", sizeof(&arr));//4/8,&arr取出的是数组的地址,数组的地址也是地址,大小就是4/8个字节
printf("%zu\n", sizeof(&arr + 1));//4/8, &arr取出的是数组的地址,&arr+1是跳过数组后的地址,跳过去后还是地址,大小就是4/8个字节
printf("%zu\n", sizeof(&arr[0] + 1));//4/8,&arr[0] + 1是数组第二个元素的地址,大小就是4/8个字节
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
printf("%zu\n", strlen(arr));//随机值,arr是数组首元素的地址,字符串中没有\0
printf("%zu\n", strlen(arr + 0));//随机值,arr是数组首元素的地址,arr+0还是首元素的地址,字符串中没有\0
//printf("%zu\n", strlen(*arr));//arr是数组首元素的地址,*arr是首元素-'a'-97
//非法访问内存 - 程序就会崩溃
//printf("%zu\n", strlen(arr[1]));//err,非法访问内存,崩溃,arr[1] == 'b' == 98
printf("%zu\n", strlen(&arr));//随机值,&arr是数组的地址,从数组的地址也就是数组的起始位置开始向后数字符串的长度,要找\0,但是没有\0
//所以是随机值
printf("%zu\n", strlen(&arr + 1));//随机值,&arr+1是跳过整个数组后的地址,向后找\0,也是随机值
printf("%zu\n", strlen(&arr[0] + 1));//随机值,&arr[0] + 1是第二个元素的地址
return 0;
}
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%zu\n", sizeof(arr));//7
printf("%zu\n", sizeof(arr + 0));//4/8
printf("%zu\n", sizeof(*arr));//1
printf("%zu\n", sizeof(arr[1]));//1
printf("%zu\n", sizeof(&arr));//4/8
printf("%zu\n", sizeof(&arr + 1));//4/8
printf("%zu\n", sizeof(&arr[0] + 1));//4/8
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%zu\n", strlen(arr));//6
printf("%zu\n", strlen(arr + 0));//6
//printf("%zu\n", strlen(*arr));//err
//printf("%zu\n", strlen(arr[1]));//err
printf("%zu\n", strlen(&arr));//6
printf("%zu\n", strlen(&arr + 1));//随机值
printf("%zu\n", strlen(&arr[0] + 1));//5
return 0;
}
#include <stdio.h>
int main()
{
char* p = "abcdef";
printf("%zu\n", sizeof(p));//4/8, p是指针变量
printf("%zu\n", sizeof(p + 1));//4/8, p+1就是b的地址
printf("%zu\n", sizeof(*p));//1
printf("%zu\n", sizeof(p[0]));//1, p[0]->*(p+0) -> *p
printf("%zu\n", sizeof(&p));//4/8, &p是指针变量p的地址,是一个二级指针
printf("%zu\n", sizeof(&p + 1));//4/8
printf("%zu\n", sizeof(&p[0] + 1));//4/8, &p[0] + 1是b的地址
return 0;
}
#include <stdio.h>
#include <string.h>
int main()
{
char* p = "abcdef";
printf("%zu\n", strlen(p));//6
printf("%zu\n", strlen(p + 1));//5
//printf("%zu\n", strlen(*p));//err
//printf("%zu\n", strlen(p[0]));//err
printf("%zu\n", strlen(&p));//随机值,p的内容不确认,p变量在内存中后续的空间内容不确定
printf("%zu\n", strlen(&p + 1));//随机值
printf("%zu\n", strlen(&p[0] + 1));//5
return 0;
}
2.3 二维数组
int main()
{
int a[3][4] = { 0 };
printf("%zu\n", sizeof(a));//48
printf("%zu\n", sizeof(a[0][0]));//4
printf("%zu\n", sizeof(a[0]));//16,a[0]是第一行这个一维数组的数组名,单独放在sizeof内部了,a[0]表示第一行这个数组
//sizeof(a[0])计算的是第一行的大小
printf("%zu\n", sizeof(a[0] + 1));//4/8, a[0]就是第一行第一个元素的地址==&a[0][0], a[0]+1是第一行第二个元素的地址
printf("%zu\n", sizeof(*(a[0] + 1)));//4, *(a[0] + 1)是第一行第二个元素
printf("%zu\n", sizeof(a + 1));//4/8, a是二维数组的数组名,这里只能表示数组首元素的地址,也就是第一行的地址
//a+1 就是第二行的地址
//a --> int(*)[4]
printf("%zu\n", sizeof(*(a + 1)));//16,*(a + 1) == a[1]
//a[1]就是第2行的数组名,计算的是整个数组的大小
printf("%zu\n", sizeof(&a[0] + 1));//4/8
//a[0]是第一行的数组名
//&a[0]取出的是第1行的地址
//&a[0] + 1是第2行的地址
printf("%zu\n", sizeof(*(&a[0] + 1)));//16
printf("%zu\n", sizeof(*a));//16,a是二维数组的数组名,这里只能表示数组首元素的地址,也就是第一行的地址
//*a 就是第一行
printf("%zu\n", sizeof(a[3]));//16,不会有越界访问的
//sizeof在计算变量,数组的大小的时候,是通过类型来推导的,不会真实去访问内存空间
return 0;
}
<3> 指针运算笔试题解析
3.1 题目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;
}
//程序的结果是什么?
3.2 题目2:
//在X86环境下 1
//假设结构体的⼤⼩是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;
}
3.3 题目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;
}
3.4 题目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;
}
图解
3.5 题目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;
}
3.6 题目6:
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}
3.7 题目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;
}
图解

查看14道真题和解析