个人主页:C++忠实粉丝
欢迎 点赞👍 收藏✨ 留言✉ 加关注💓本文由 C++忠实粉丝 原创C语言深入理解指针(4)
收录于专栏【C语言学习】
本专栏旨在分享学习C语言学习的一点学习笔记,欢迎大家在评论区交流讨论💌
目录
1. sizeof和strlen的对⽐
1.1 sizeof
1.2 strlen
1.3 sizeof 和strlen的对⽐
2. 数组和指针笔试题解析
2.1 ⼀维数组
2.2 字符数组
示例一:
示例二:
示例三:
示例四:
示例五:
实例六:
2.3 ⼆维数组
3. 指针运算笔试题解析
3.1 题⽬1:
3.2 题⽬2
3.3 题⽬3
3.4 题⽬4
3.5 题⽬5
3.6 题⽬6
3.7 题⽬7
1. sizeof和strlen的对⽐
1.1 sizeof
在学习操作符的时候,我们学习了 sizeof , sizeof 计算变量所占内存内存空间⼤⼩的,单位是 字节,如果操作数是类型的话,计算的是使⽤类型创建的变量所占内存空间的⼤⼩。
sizeof 只关注占⽤内存空间的⼤⼩,不在乎内存中存放什么数据。
比如:
#include <stdio.h>
int main()
{int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));return 0;
}
输出结果:
1.2 strlen
strlen 是C语⾔库函数,功能是求字符串⻓度。函数原型如下:
size_t strlen ( const char * str );
统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \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;
}
输出结果:
注意35是随机的,当没有\0时,strlen 会⼀直向后找 \0 字符,直到找到为⽌,所以是不确定的
1.3 sizeof 和strlen的对⽐
sizeof | strlen |
1. sizeof是操作符 2. sizeof计算操作数所占内存的⼤⼩, 单位是字节 3. 不关注内存中存放什么数据 | 1. strlen是库函数,使⽤需要包含头⽂件 string.h 2. srtlen是求字符串⻓度的,统计的是 \0 之前字符的隔个数 3. 关注内存中是否有 \0 ,如果没有 \0 ,就会持续往后找,可 能会越界 |
2. 数组和指针笔试题解析
2.1 ⼀维数组
int main()
{int a[] = { 1,2,3,4 };printf("%d\n", sizeof(a));printf("%d\n", sizeof(a + 0));printf("%d\n", sizeof(*a));printf("%d\n", sizeof(a + 1));printf("%d\n", sizeof(a[1]));printf("%d\n", sizeof(&a));printf("%d\n", sizeof(*&a));printf("%d\n", sizeof(&a + 1));printf("%d\n", sizeof(&a[0]));printf("%d\n", sizeof(&a[0] + 1));
}
输出结果:
解析如下:di
第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个整数为四个字节,四个整数即为16个字节
第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8
第三个:对首元素的指针进行解引用,即求1的内存,所以为4个字节
第四个:代表的是首元素后面一个元素的地址大小,所以为8
第五个:求第二个元素的内存,所以为4
第六个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8
第七个:对整个数组进行解引用,所以为16
第八个:求得是数组地址下一个地址,是地址内存就是固定得,为8
第九个:求得是地址,固定为8
第十个:也是一样,求的是地址,为8
2.2 字符数组
示例一:
int main()
{char arr[] = { 'a','b','c','d','e','f' };printf("%d\n", sizeof(arr));printf("%d\n", sizeof(arr + 0));printf("%d\n", sizeof(*arr));printf("%d\n", sizeof(arr[1]));printf("%d\n", sizeof(&arr));printf("%d\n", sizeof(&arr + 1));printf("%d\n", sizeof(&arr[0] + 1));
}
输出结果:
解析如下:
第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个字符为1个字节,6个字符即为6个字节
第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8
第三个:对首元素的指针进行解引用,即求1的内存,所以为1个字节
第四个:求第二个元素的内存,所以为1
第五个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8
第六个:求得是数组地址下一个地址,是地址内存就是固定得,为8
第七个:求得是数组地址下一个地址,是地址内存就是固定得,为8
示例二:
int main()
{char arr[] = { 'a','b','c','d','e','f' };printf("%d\n", strlen(arr));printf("%d\n", strlen(arr + 0));printf("%d\n", strlen(*arr));printf("%d\n", strlen(arr[1]));printf("%d\n", strlen(&arr));printf("%d\n", strlen(&arr + 1));printf("%d\n", strlen(&arr[0] + 1));
}
strlen 统计的是从 strlen 函数的参数 str 中这个地址开始向后, \0 之前字符串中字符的个数。 strlen 函数会⼀直向后找 \0 字符,直到找到为⽌,所以可能存在越界查找。
所以我们使用 strlen 时一定要注意这个问题!
示例三:
int main()
{char arr[] = "abcdef";printf("%d\n", sizeof(arr));printf("%d\n", sizeof(arr + 0));printf("%d\n", sizeof(*arr));printf("%d\n", sizeof(arr[1]));printf("%d\n", sizeof(&arr));printf("%d\n", sizeof(&arr + 1));printf("%d\n", sizeof(&arr[0] + 1));
}
输出结果:
调式结果:
注意:这样得初始化会自动补上"\0"
解析如下:
第一个:sizeof加数组名,在上一节说过,代表求整个数组的内存大小,一个字符为1个字节,7个字符即为7个字节(注意"\0"也算)
第二个:a+0代表的是首元素的地址,指针的大小只与平台有关,我这里是X64所以是8
第三个:对首元素的指针进行解引用,即求1的内存,所以为1个字节
第四个:求第二个元素的内存,所以为1
第五个:返回的是整个数组的地址,地址的内存只与平台有关,所以为8
第六个:求得是数组地址下一个地址,是地址内存就是固定得,为8
第七个:求得是数组地址下一个地址,是地址内存就是固定得,为8
示例四:
int main()
{char arr[] = "abcdef";printf("%d\n", strlen(arr));printf("%d\n", strlen(arr + 0));printf("%d\n", strlen(*arr));printf("%d\n", strlen(arr[1]));printf("%d\n", strlen(&arr));printf("%d\n", strlen(&arr + 1));printf("%d\n", strlen(&arr[0] + 1));
}
输出结果:
解析如下:
第一个:strlen加数组名,数组名就是首元素得地址,所以strlen会一直找,直到找到\0,所以为6
第二个:strlen接收得也是首元素得地址,所以也为6
第三个和第四个:传入得不是地址,而是一个值,所以打印不出结果
第五个:返回的是整个数组的地址,返会得其实也是地址首元素得地址,所以也为6
第六个:求得是数组地址下一个地址,内容不确定,随机
第七个:求得是数组地址首元素得下一个地址,所以为5
示例五:
int main()
{char* p = "abcdef";printf("%d\n", sizeof(p));printf("%d\n", sizeof(p + 1));printf("%d\n", sizeof(*p));printf("%d\n", sizeof(p[0]));printf("%d\n", sizeof(&p));printf("%d\n", sizeof(&p + 1));printf("%d\n", sizeof(&p[0] + 1));
}
输出结果:
解析如下:
第一个:sizeof求得时指针p得大小,所以为8
第二个:求得是字符指针p下一个地址,是地址内存就是固定得,为8
第三个:对字符指针p进行解引用,即求a的内存,所以为1个字节
第四个:求第一个元素的内存,所以为1
第五个:对指针p进行取地址,地址内存就是固定得,为8
第六个:求得是对指针p进行取地址得下一个地址,是地址内存就是固定得,为8
第七个:求得是字符数组首元素地址下一个地址,是地址内存就是固定得,为8
实例六:
int main()
{char* p = "abcdef";printf("%d\n", strlen(p));printf("%d\n", strlen(p + 1));printf("%d\n", strlen(*p));printf("%d\n", strlen(p[0]));printf("%d\n", strlen(&p));printf("%d\n", strlen(&p + 1));printf("%d\n", strlen(&p[0] + 1));
}
输出结果:
第一个:strlen加字符数组指针名,字符数组指针名就是首元素得地址,所以strlen会一直找,直到找到\0,所以为6
第二个:strlen接收得也是首元素下一个地址,所以为5
第三个和第四个:传入得不是地址,而是一个值,所以打印不出结果
第五个:传入p得地址,strlen会一直找,直到找到\0,所以为6
第六个:传入p地址的下一个,随机不确定
第七个:传入p首元素的下一个地址,所以为5
2.3 ⼆维数组
int main()
{int a[3][4] = { 0 };printf("%d\n", sizeof(a));printf("%d\n", sizeof(a[0][0]));printf("%d\n", sizeof(a[0]));printf("%d\n", sizeof(a[0] + 1));printf("%d\n", sizeof(*(a[0] + 1)));printf("%d\n", sizeof(a + 1));printf("%d\n", sizeof(*(a + 1)));printf("%d\n", sizeof(&a[0] + 1));printf("%d\n", sizeof(*(&a[0] + 1)));printf("%d\n", sizeof(*a));printf("%d\n", sizeof(a[3]));
}
输出结果
这里的分析和一维数组一样,我这里只说第三个和第二个:
printf("%d\n", sizeof(a[0]));
sizeof加数组名求得是整个数组得内存,而这里求得是第一行所有元素得内存,即为16
printf("%d\n", sizeof(a[0] + 1));这里应该a[0] + 1先算所以,他这里返回得是地址,答案为8
数组名的意义:
1. sizeof(数组名),这⾥的数组名表⽰整个数组,计算的是整个数组的⼤⼩。
2. &数组名,这⾥的数组名表⽰整个数组,取出的是整个数组的地址。
3. 除此之外所有的数组名都表⽰⾸元素的地址。
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;
}
//程序的结果是什么?
程序的结果是什么?
这里得ptr指向的是整个数组地址的下一个,应为&a取得是整个数组的地址
所以它打印的结果应该为2,5
*(a + 1):数组名代表首元素的地址,再加一,即取得是第二个元素得地址,在进行解引用:
*(ptr - 1) :
输出结果:
3.2 题⽬2
在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;
}
p + 0x1
:这是一个指针运算,它将p
指针向后移动了一个结构体的大小(20 字节),这里注意:0x100014
是16进制。因此,它将指向0x100014
地址。(unsigned long)p + 0x1
:这是一个类型转换,将指针p
转换为unsigned long
类型,然后再加上0x1
。这将得到0x100001
。(unsigned int*)p + 0x1
:这也是一个类型转换,将指针p
转换为unsigned int*
类型,然后再加上0x1
。这将得到0x100004
。
输出结果:
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;
}
- 首先,我们定义了一个二维整数数组
a
,其中包含了三个子数组,每个子数组有两个整数元素。注意,这里的初始化方式有点特殊,使用了逗号运算符,但实际上只有最后一个值被赋给了数组元素。因此,a
的内容实际上是{1, 3, 5}
。- 然后,我们创建了一个指向整数的指针
p
,并将其指向了a[0]
,即第一个子数组的首地址。- 接下来,我们打印了
p[0]
的值。由于p
指向了a[0]
,所以p[0]
实际上是a[0][0]
,即第一个子数组的第一个元素,也就是1
。
输出结果:
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;
}
解析:
这里注意a和p的类型是不一样的
a的类型为int(*)[5]
p的类型为int(*)[4]
当返回%d时,就是返回了两个地址相差的距离,也是就-4
而用地址打印时,就是返回-4的补码,用16进制表示
3.5 题⽬5
#include <stdio.h>int main(){char *a[] = {"work","at","alibaba"};char**pa = a;pa++;printf("%s\n", *pa);return 0;}
- 首先,我们定义了一个字符型指针数组
a[]
,其中包含三个字符串元素:“work”、“at” 和 “alibaba”。- 然后,我们定义了一个指向字符型指针的指针
pa
,并将其初始化为a
数组的首元素地址,即pa = a;
。- 接下来,我们执行了
pa++
操作,将pa
指向了数组a
的第二个元素,即指向了字符串 “at” 的首元素地址。- 最后,我们使用
printf("%s\n", *pa);
打印了*pa
所指向的字符串,即输出了 “at”。
输出结果
3.6 题⽬6
#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;
}
- 首先,我们定义了一个二维整型数组
aa[2][5]
,并初始化了其中的元素。- 然后,我们定义了两个指针
ptr1
和ptr2
:
ptr1
的值被设置为(&aa + 1)
,即指向了aa
数组之后的内存位置。ptr2
的值被设置为*(aa + 1)
,即指向了aa
数组的第二行的首元素地址。- 接下来,我们使用
printf("%d,%d", *(ptr1 - 1), *(ptr2 - 1));
打印了两个值:
*(ptr1 - 1)
表示ptr1
指向的内存位置之前的整型值。*(ptr2 - 1)
表示ptr2
指向的地址之前的整型值。现在让我们具体分析一下:
ptr1
指向了aa
数组之后的内存位置,因此*(ptr1 - 1)
实际上是访问了aa
数组之后的内存位置之前的整型值。由于aa
数组是一个二维数组,其内存布局是连续的,所以这个值应该是10
。ptr2
指向了aa
数组的第二行的首元素地址,即指向了整型值6
的地址。因此,*(ptr2 - 1)
实际上是访问了6
之前的整型值,即5
。因此,程序的输出结果应该是
10,5
。
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;
}
这里注意CPP指针,只有在++,--时发生了改变,CPP-2并未对指针发生改变