看之前必须得掌握有一定指针的知识,不然会看不懂,如果有不懂的可以看我博客 指针1,指针2,指针3 这三个讲了指针全部的基础知识超级详细,这篇只要是讲一些指针练习题也是非常详细
1. sizeof和strlen的对⽐
1. 基本定义和用途
sizeof:sizeof是一个运算符,不是函数。它用于计算数据类型或变量所占用的内存字节数。这个计算是在编译时完成的,与程序运行时的数据内容无关。strlen:strlen是一个标准库函数,定义在<string.h>(C)或<cstring>(C++)头文件中。它用于计算以'\0'结尾的字符串的实际长度,即字符串中字符的个数(不包括字符串结束符'\0'),这个计算是在程序运行时进行的。
2. 语法和使用示例
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "Hello";
// 使用 sizeof 计算数组占用的内存字节数
size_t size = sizeof(str);
// 使用 strlen 计算字符串的实际长度
size_t length = strlen(str);
printf("sizeof(str) = %zu\n", size);
printf("strlen(str) = %zu\n", length);
return 0;
}
输出结果:
sizeof(str) = 6
strlen(str) = 5
在这个例子中,sizeof(str) 返回 6,因为数组 str 包含 5 个字符 'H'、'e'、'l'、'l'、'o' 以及一个字符串结束符 '\0',每个字符占 1 字节,所以总共 6 字节。而 strlen(str) 返回 5,因为它只计算字符串中实际的字符个数,不包括 '\0'。
3. 对不同数据类型的处理
sizeof- 对于基本数据类型,如
int、char、double等,sizeof返回该数据类型在当前系统中占用的字节数。例如,在 32 位系统中,sizeof(int)通常返回 4,sizeof(char)返回 1。 - 对于数组,
sizeof返回整个数组占用的内存字节数。 - 对于指针,
sizeof返回指针变量本身占用的内存字节数,而不是指针所指向的对象的大小。例如,在 32 位系统中,所有指针类型的sizeof结果通常都是 4 字节,在 64 位系统中通常是 8 字节。
- 对于基本数据类型,如
#include <stdio.h>
int main()
{
int arr[10];
int *ptr = arr;
printf("sizeof(arr) = %zu\n", sizeof(arr));
printf("sizeof(ptr) = %zu\n", sizeof(ptr));
return 0;
}
strlen:strlen只能用于以'\0'结尾的字符串。如果传递给strlen的参数不是一个有效的以'\0'结尾的字符串,会导致未定义行为,因为strlen会一直向后查找'\0',直到找到为止。
4. 性能差异
sizeof:由于sizeof是在编译时计算的,不会产生运行时开销,因此性能非常高。strlen:strlen需要在运行时遍历字符串,直到找到'\0'为止,因此其时间复杂度为 ,其中 是字符串的长度。当处理长字符串时,strlen的性能会受到一定影响。
5. 总结
- 如果需要知道数据类型或变量占用的内存大小,应该使用
sizeof。 - 如果需要知道以
'\0'结尾的字符串的实际长度,应该使用strlen。
2. 数组和指针笔试题解析
通过上面对strlen和sizeof的了解,结合我之前发的指针1和指针2,指针3的知识,我们来做一些综合性的题目。
2.1 ⼀维数组
认真思考哦
#include<stdio.h>
int main()
{
int a[] = { 1,2,3,4 };
// 1. sizeof(a)
printf("%d\n", sizeof(a));
// 2. sizeof(a + 0)
printf("%d\n", sizeof(a + 0));
// 3. sizeof(*a)
printf("%d\n", sizeof(*a));
// 4. sizeof(a + 1)
printf("%d\n", sizeof(a + 1));
// 5. sizeof(a[1])
printf("%d\n", sizeof(a[1]));
// 6. sizeof(&a)
printf("%d\n", sizeof(&a));
// 7. sizeof(*&a)
printf("%d\n", sizeof(*&a));
// 8. sizeof(&a + 1)
printf("%d\n", sizeof(&a + 1));
// 9. sizeof(&a[0])
printf("%d\n", sizeof(&a[0]));
// 10. sizeof(&a[0] + 1)
printf("%d\n", sizeof(&a[0] + 1));
return 0;
}
我们接下来来分析一下:
我们来回顾一下我之前提到过的:
数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名
1. sizeof(a)
a是一个包含 4 个int类型元素的数组。sizeof运算符作用于数组名时,返回整个数组占用的内存字节数。- 每个
int类型占 4 个字节,数组有 4 个元素,所以sizeof(a)的结果是4 * 4 = 16字节。
2. sizeof(a + 0)
- 根据数组名的特性,当数组名出现在表达式中(除了作为
sizeof或&运算符的操作数),它会隐式转换为指向数组首元素的指针(int *类型)。 a + 0等价于指向数组首元素的指针,指针在 32 位系统下占 4 个字节,在 64 位系统下占 8 个字节。所以sizeof(a + 0)的结果是指针的大小。
3. sizeof(*a)
a隐式转换为指向数组首元素的指针,*a表示解引用该指针,得到数组的首元素,即a[0]。a[0]是int类型,int类型占 4 个字节,所以sizeof(*a)的结果是 4 字节。
4. sizeof(a + 1)
a隐式转换为指向数组首元素的指针,a + 1是指向数组第二个元素的指针(int *类型)。- 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以
sizeof(a + 1)的结果是指针的大小。
5. sizeof(a[1])
a[1]是数组的第二个元素,类型为int。int类型占 4 个字节,所以sizeof(a[1])的结果是 4 字节。
6. sizeof(&a)
&a是一个指向整个数组的指针(int (*) [4]类型),虽然它和指向数组首元素的指针在数值上可能相同,但类型不同。- 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以
sizeof(&a)的结果是指针的大小。
7. sizeof(*&a)
&a是指向整个数组的指针,*&a等价于a,也就是整个数组。- 整个数组包含 4 个
int类型元素,每个int占 4 个字节,所以sizeof(*&a)的结果是4 * 4 = 16字节。
8. sizeof(&a + 1)
&a是指向整个数组的指针(int (*) [4]类型),&a + 1是指向下一个与a同类型数组的指针。- 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以
sizeof(&a + 1)的结果是指针的大小。
9. sizeof(&a[0])
&a[0]是指向数组首元素的指针(int *类型)。- 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以
sizeof(&a[0])的结果是指针的大小。
10. sizeof(&a[0] + 1)
&a[0]是指向数组首元素的指针(int *类型),&a[0] + 1是指向数组第二个元素的指针(int *类型)。- 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以
sizeof(&a[0] + 1)的结果是指针的大小。
2.2 字符数组
接下来我们来几组字符数组来练习练习一下吧
代码1:
#include <stdio.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
// 1. sizeof(arr)
printf("%d\n", sizeof(arr));
// 2. sizeof(arr + 0)
printf("%d\n", sizeof(arr + 0));
// 3. sizeof(*arr)
printf("%d\n", sizeof(*arr));
// 4. sizeof(arr[1])
printf("%d\n", sizeof(arr[1]));
// 5. sizeof(&arr)
printf("%d\n", sizeof(&arr));
// 6. sizeof(&arr + 1)
printf("%d\n", sizeof(&arr + 1));
// 7. sizeof(&arr[0] + 1)
printf("%d\n", sizeof(&arr[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
我们来回顾一下我之前提到过的:
数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名
1. sizeof(arr)
arr是一个字符数组,当sizeof运算符作用于数组名时,它返回整个数组所占用的内存字节数。- 该数组包含 6 个
char类型的元素,每个char类型通常占用 1 个字节,所以sizeof(arr)的结果是6 * 1 = 6。
2. sizeof(arr + 0)
- 当数组名
arr出现在表达式中(除了作为sizeof或&运算符的操作数)时,它会隐式转换为指向数组首元素的指针(char*类型),即arr退化为char *类型的指针。 arr + 0仍然是指向数组首元素的指针,sizeof计算的是指针的大小。在 32 位系统中,指针大小通常为 4 字节;在 64 位系统中,指针大小通常为 8 字节。
3. sizeof(*arr)
- 由于
arr退化为指向数组首元素的指针,*arr表示对该指针进行解引用,得到数组的首元素arr[0]。 arr[0]是char类型,char类型占用 1 个字节,所以sizeof(*arr)的结果是 1。
4. sizeof(arr[1])
arr[1]是数组的第二个元素,其类型为char。- 因此,
sizeof(arr[1])的结果同样是 1 字节。
5. sizeof(&arr)
&arr是一个指向整个数组的指针,其类型为char (*)[6],表示指向包含 6 个char元素的数组的指针。- 但无论指针指向何种类型的对象,
sizeof计算的都是指针本身的大小。所以在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
6. sizeof(&arr + 1)
&arr是指向整个数组的指针(char (*)[6]),&arr + 1表示对该指针进行偏移,使其指向下一个与arr同类型的数组的起始位置。- 它仍然是一个指针,所以
sizeof(&arr + 1)的结果和sizeof(&arr)一样,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
7. sizeof(&arr[0] + 1)
&arr[0]是数组首元素的地址,类型为char *。&arr[0] + 1是指向数组第二个元素的指针,同样是char *类型。- 所以
sizeof(&arr[0] + 1)计算的是指针的大小,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
代码2:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };
// 1. strlen(arr)
printf("%d\n", strlen(arr));
// 2. strlen(arr + 0)
printf("%d\n", strlen(arr + 0));
// 3. strlen(*arr)
printf("%d\n", strlen(*arr));
// 4. strlen(arr[1])
printf("%d\n", strlen(arr[1]));
// 5. strlen(&arr)
printf("%d\n", strlen(&arr));
// 6. strlen(&arr + 1)
printf("%d\n", strlen(&arr + 1));
// 7. strlen(&arr[0] + 1)
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
我们来回顾一下我之前提到过的:
数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名
1. strlen(arr)
arr是一个字符数组,但是该数组初始化时没有包含字符串结束符'\0'。strlen函数会从arr指向(char*类型)的地址开始,逐个字符向后查找'\0',直到找到为止。由于arr中没有'\0',strlen会继续访问数组后面的内存,这会导致未定义行为,其输出结果是不确定的,可能是一个随机值,甚至可能引发程序崩溃。
2. strlen(arr + 0)
arr + 0等同于arr,因为数组名arr在表达式中(除了作为sizeof或&运算符的操作数)会隐式转换为指向数组首元素的指针(char*类型),加上 0 后仍然指向数组首元素。- 同样,由于数组中没有
'\0',这也会导致未定义行为,输出结果是一个随机值。
3. strlen(*arr)
*arr对arr进行解引用,得到数组的首元素'a',其 ASCII 值为 97。strlen函数期望的参数是一个指向以'\0'结尾的字符串的指针(char *类型),而这里传递的是一个char类型的值(97)。将这个值当作指针来处理是错误的,会导致未定义行为,97作为地址传给给了strlen,可能会引发程序崩溃。
4. strlen(arr[1])
arr[1]是数组的第二个元素'b',其 ASCII 值为 98。- 与
strlen(*arr)类似,strlen函数期望的是一个指针,而这里传递的是一个char类型的值,会导致未定义行为,98作为地址传给给了strlen,可能会引发程序崩溃。
5. strlen(&arr)
&arr是一个指向整个数组的指针,其类型为char (*)[6]。strlen函数期望的参数是char *类型的指针,这里传递的指针类型不匹配。将char (*)[6]类型的指针当作char *类型的指针来使用会导致未定义行为,可能会引发程序崩溃,也可能是一个随机值。
6. strlen(&arr + 1)
&arr是指向整个数组的指针char (*)[6],&arr + 1会跳过整个数组,指向下一个与arr同类型的数组的起始位置的地址。- 同样,
strlen函数期望的是char *类型的指针,这里指针类型不匹配,并且该位置的内存内容是不确定的,会导致未定义行为,可能会引发程序崩溃,也可能是一个随机值。
7. strlen(&arr[0] + 1)
&arr[0]是数组首元素的地址,&arr[0] + 1是指向数组第二个元素的指针(char*类型)。- 由于数组中没有
'\0',strlen会从第二个元素开始向后查找'\0',但一直找不到,会继续访问数组后面的内存,这会导致未定义行为,输出结果不确定,也可能是一个随机值。
代码3
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
// 1. sizeof(arr)
printf("%d\n", sizeof(arr));
// 2. sizeof(arr + 0)
printf("%d\n", sizeof(arr + 0));
// 3. sizeof(*arr)
printf("%d\n", sizeof(*arr));
// 4. sizeof(arr[1])
printf("%d\n", sizeof(arr[1]));
// 5. sizeof(&arr)
printf("%d\n", sizeof(&arr));
// 6. sizeof(&arr + 1)
printf("%d\n", sizeof(&arr + 1));
// 7. sizeof(&arr[0] + 1)
printf("%d\n", sizeof(&arr[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
我们来回顾一下我之前提到过的:
数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名
1. sizeof(arr)
arr是一个字符数组,使用字符串字面量"abcdef"进行初始化。字符串字面量在 C 语言中会自动在末尾添加一个字符串结束符'\0'。- 所以数组
arr实际上包含了 7 个字符:'a','b','c','d','e','f','\0'。 sizeof运算符作用于数组名时,返回整个数组所占用的内存字节数。每个char类型占 1 个字节,因此sizeof(arr)的结果是 7。
2. sizeof(arr + 0)
- 当数组名
arr出现在表达式中(除了作为sizeof或&运算符的操作数)时,它会隐式转换为指向数组首元素的指针,即arr退化为char *类型的指针。 arr + 0仍然指向数组的首元素,本质上还是一个指针。sizeof计算的是指针的大小。在 32 位系统中,指针大小通常为 4 字节;在 64 位系统中,指针大小通常为 8 字节。
3. sizeof(*arr)
- 由于
arr退化为指向数组首元素的指针,*arr表示对该指针进行解引用,得到数组的首元素arr[0],也就是字符'a'。 arr[0]是char类型,char类型占用 1 个字节,所以sizeof(*arr)的结果是 1。
4. sizeof(arr[1])
arr[1]是数组的第二个元素,其类型为char。- 因此,
sizeof(arr[1])的结果同样是 1 字节。
5. sizeof(&arr)
&arr是一个指向整个数组的指针,其类型为char (*)[7],表示指向包含 7 个char元素的数组的指针。- 但无论指针指向何种类型的对象,
sizeof计算的都是指针本身的大小。所以在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
6. sizeof(&arr + 1)
&arr是指向整个数组的指针(char (*)[7]),&arr + 1表示对该指针进行偏移,使其指向下一个与arr同类型的数组的起始位置。- 它仍然是一个指针,所以
sizeof(&arr + 1)的结果和sizeof(&arr)一样,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
7. sizeof(&arr[0] + 1)
&arr[0]是数组首元素的地址,类型为char *。&arr[0] + 1是指向数组第二个元素的指针,同样是char *类型。- 所以
sizeof(&arr[0] + 1)计算的是指针的大小,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
代码4
#include <stdio.h>
#include <string.h>
int main()
{
// 定义并初始化字符数组 arr
char arr[] = "abcdef";
// 1. strlen(arr)
printf("%d\n", strlen(arr));
// 2. strlen(arr + 0)
printf("%d\n", strlen(arr + 0));
// 3. strlen(*arr)
printf("%d\n", strlen(*arr));
// 4. strlen(arr[1])
printf("%d\n", strlen(arr[1]));
// 5. strlen(&arr)
printf("%d\n", strlen(&arr));
// 6. strlen(&arr + 1)
printf("%d\n", strlen(&arr + 1));
// 7. strlen(&arr[0] + 1)
printf("%d\n", strlen(&arr[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
我们来回顾一下我之前提到过的:
数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名
1 printf(strlen(arr));
arr作为数组名,在strlen函数调用时,它会隐式转换为指向数组首元素的指针(这是数组在表达式中的一种退化行为),也就是指向字符'a'的指针(char*)。strlen函数会从该指针指向的位置开始,逐个字符向后查找,直到遇到'\0'为止。从'a'开始,依次是'b'、'c'、'd'、'e'、'f',再到'\0',总共 6 个有效字符(不包含'\0')。所以strlen(arr)的返回值是 6,程序会输出6。
2 printf(strlen(arr + 0));
arr + 0本质上和arr是一样的,因为指针(char*)加上 0 并不会改变其指向。所以arr + 0依然指向数组的首元素'a'。- 因此,
strlen(arr + 0)的计算过程和strlen(arr)相同,返回值也是 6,程序会输出6。
3 printf(strlen(*arr));
*arr是对arr这个指针进行解引用操作,得到的是数组的首元素'a'。'a'的 ASCII 码值是 97。- 但
strlen函数期望的参数是一个指向以'\0'结尾的字符串的指针(char *类型)。这里把一个char类型的值(97)当作指针(地址)传递给strlen,这会导致未定义行为。程序可能会尝试从内存地址 97 开始查找'\0',这可能会引发段错误(因为程序可能没有访问该内存地址的权限),程序崩溃或者输出一个无意义的结果。
4 printf(strlen(arr[1]));
arr[1]表示数组的第二个元素,即字符'b',其 ASCII 码值为 98。- 同样,
strlen函数需要的是指针(char*)类型的指针参数,这里传递的是char类型的值,98作为地址传给给了strlen会导致未定义行为,程序可能崩溃或者给出错误的输出。
5 printf(strlen(&arr));
&arr是一个指向整个数组的指针,其类型为char (*)[7](因为数组arr有 7 个元素)。- 而
strlen函数要求的参数是char *类型的指针。将char (*)[7]类型的指针当作char *类型的指针使用是错误的但也可以算出是6,也会导致未定义行为,程序可能无法正常运行。
6 printf(strlen(&arr + 1));
&arr指向整个数组(char (*)[7]),&arr + 1会使指针跳过整个数组arr,指向下一个和arr同类型数组应该存放的起始位置。- 这个位置的内存内容是不确定的,而且传递的指针类型与
strlen函数期望的char *类型不匹配,会引发未定义行为,程序可能出现异常。
7 printf(strlen(&arr[0] + 1));
&arr[0]是数组首元素的地址,&arr[0] + 1是指向数组第二个元素'b'的指针(char*)。strlen函数从这个指针指向的位置开始查找'\0'。从'b'开始,依次是'c'、'd'、'e'、'f',再到'\0',一共有 5 个有效字符。所以strlen(&arr[0] + 1)的返回值是 5,程序会输出5。
代码5
#include <stdio.h>
int main()
{
char* p = "abcdef";
// 1. sizeof(p)
printf("%d\n", sizeof(p));
// 2. sizeof(p + 1)
printf("%d\n", sizeof(p + 1));
// 3. sizeof(*p)
printf("%d\n", sizeof(*p));
// 4. sizeof(p[0])
printf("%d\n", sizeof(p[0]));
// 5. sizeof(&p)
printf("%d\n", sizeof(&p));
// 6. sizeof(&p + 1)
printf("%d\n", sizeof(&p + 1));
// 7. sizeof(&p[0] + 1)
printf("%d\n", sizeof(&p[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
怕大家搞混这边补充一些指针知识:
1.
p的类型及含义在代码
char* p = "abcdef";中,p是一个字符指针,其类型为char *。它指向字符串字面量"abcdef"的首字符'a'的地址。2.
p[0]的含义在 C 语言里,对于指针
p,p[i]等价于*(p + i)。当i为 0 时,p[0]等价于*(p + 0),也就是*p。所以p[0]表示的是指针p所指向的字符,在这里就是字符'a',其类型为char。3.
&p[0]的类型推导
&是取地址运算符,&p[0]表示取p[0]的地址。由于p[0]是char类型的字符'a',那么&p[0]就是指向这个char类型字符的指针,其类型为char *。4.&p的含义
&是取地址运算符,当它作用于指针变量p时,&p得到的是指针变量p自身在内存中的存储地址。由于p本身是char *类型(指向char的指针),那么指向p的指针就需要能够存储p的地址,这个指针指向的对象类型是char *,所以&p的类型是指向char *类型的指针,即char **。从本质上来说,
&p[0]和p指向的是同一个地址,即字符串"abcdef"的首字符地址,只是它们在概念上稍有不同:p是一个指针变量,而&p[0]是通过对数组(这里可以把指针p当作指向字符串数组首元素的指针)首元素取地址得到的指针。综上所述,p是char *类型,p[0]是char类型,&p[0]是char *类型,&p是char **类型。。
1 sizeof(p)
p是一个(char *类型)指针变量(不是数组名),它存储的是字符串"abcdef"的首地址('a')。sizeof操作符用于计算指针变量本身所占用的内存大小。在 32 位系统中,指针通常占用 4 个字节;在 64 位系统中,指针通常占用 8 个字节。所以sizeof(p)的结果是 4(32 位系统)或 8(64 位系统)。
2 sizeof(p + 1)
p + 1是一个指针运算,它会让指针p向后偏移一个char类型的大小,从而指向字符串中的第二个字符'b'。- 但无论指针指向何处,
sizeof计算的都是指针本身的大小。所以sizeof(p + 1)的结果和sizeof(p)一样,是 4(32 位系统)或 8(64 位系统)。
3 sizeof(*p)
*p是对指针p进行解引用操作,得到的是指针p所指向的字符,也就是字符串的首字符'a'。'a'是char类型,char类型在 C 语言中通常占用 1 个字节。所以sizeof(*p)的结果是 1。
4 sizeof(p[0])
p[0](char类型)等价于*(p + 0),也就是指针p所指向的字符,同样是'a'。- 因此,
sizeof(p[0])的结果和sizeof(*p)相同,为 1。
5 sizeof(&p)
&p是取指针变量p本身的地址,它是一个指向指针的指针(类型为char **)。- 不管指针指向的是什么类型,
sizeof计算的都是指针本身的大小。所以sizeof(&p)的结果是 4(32 位系统)或 8(64 位系统)。
6 sizeof(&p + 1)
&p是指向指针p的指针,&p + 1会让这个指针向后偏移一个char **类型的大小,指向下一个char **类型的位置。- 但
sizeof计算的依然是指针本身的大小,所以sizeof(&p + 1)的结果和sizeof(&p)一样,是 4(32 位系统)或 8(64 位系统)。
7 sizeof(&p[0] + 1)
p[0](char类型) 是字符串的首字符'a',&p[0]是首字符'a'的地址,也就是指针p所指向的地址。&p[0] + 1会让这个指针向后偏移一个char类型的大小,指向字符串中的第二个字符'b'。- 它仍然是一个指针(
&p[0]char *类型),所以sizeof(&p[0] + 1)的结果和sizeof(p)相同,是 4(32 位系统)或 8(64 位系统)。
代码6
#include<stdio.h>
#include<string.h>
int main()
{
char* p = "abcdef";
//1.strlen(p)
printf("%d\n", strlen(p));
//2.strlen(p + 1)
printf("%d\n", strlen(p + 1));
//3.strlen(*p)
printf("%d\n", strlen(*p));
//4.strlen(p[0])
printf("%d\n", strlen(p[0]));
//strlen(&p)
printf("%d\n", strlen(&p));
//6.strlen(&p + 1)
printf("%d\n", strlen(&p + 1));
//7.strlen(&p[0] + 1)
printf("%d\n", strlen(&p[0] + 1));
return 0;
}
接下来我们来详细的分析一下吧:
-
strlen(p)p(char*类型)是一个指向字符串常量"abcdef"的指针,strlen(p)会从p所指向的字符开始,逐个字符计数,直到遇到字符串结束符'\0'为止。- 字符串
"abcdef"的长度是 6,所以输出结果为6。
-
strlen(p + 1)p + 1(char*类型)指向字符串"abcdef"的第二个字符'b'。strlen(p + 1)会从'b'开始计数,直到遇到字符串结束符'\0',所以输出结果为5。
-
strlen(*p)*p(char类型)是解引用操作,它得到的是p所指向的第一个字符'a',其 ASCII 码值为 97。strlen函数的参数应该是一个指向字符串的指针,而这里传递的是一个字符(实际上是一个整数),这会导致未定义行为,程序可能会崩溃或输出错误的结果。
-
strlen(p[0])p[0](char类型)等价于*p,同样得到的是字符'a'。- 传递字符给
strlen函数会导致未定义行为,程序可能会崩溃或输出错误的结果。
-
strlen(&p)&p(char**类型)是指针p的地址,它指向的并不是一个以'\0'结尾的字符串。- 传递指针
p的地址给strlen函数会导致未定义行为,程序可能会崩溃或随机值的结果。
-
strlen(&p + 1)&p + 1(char**类型)是指针p的地址向后偏移一个char*类型的大小。- 它指向的同样不是一个以
'\0'结尾的字符串,传递该地址给strlen函数会导致未定义行为,程序可能会崩溃或随机值的结果。
-
strlen(&p[0] + 1)&p[0](char*类型)等价于p,指向字符串"abcdef"的第一个字符'a'。&p[0] + 1指向字符串的第二个字符'b'。strlen(&p[0] + 1)会从'b'开始计数,直到遇到字符串结束符'\0',所以输出结果为5。
2.3 ⼆维数组
#include<stdio.h>
int main()
{
// 定义一个 3 行 4 列的二维数组 a,并初始化为全 0
int a[3][4] = { 0 };
// 1.sizeof(a)
printf("%d\n", sizeof(a));
// 2.sizeof(a[0][0])
printf("%d\n", sizeof(a[0][0]));
// 3.sizeof(a[0])
printf("%d\n", sizeof(a[0]));
// 4.sizeof(a[0] + 1)
printf("%d\n", sizeof(a[0] + 1));
// 5.sizeof(*(a[0] + 1))
printf("%d\n", sizeof(*(a[0] + 1)));
// 6.sizeof(a + 1)
printf("%d\n", sizeof(a + 1));
// 7.sizeof(*(a + 1))
printf("%d\n", sizeof(*(a + 1)));
// 8.sizeof(&a[0] + 1)
printf("%d\n", sizeof(&a[0] + 1));
// 9.sizeof(*(&a[0] + 1))
printf("%d\n", sizeof(*(&a[0] + 1)));
// 10.sizeof(*a)
printf("%d\n", sizeof(*a));
// 11.sizeof(a[3])
printf("%d\n", sizeof(a[3]));
return 0;
}
接下来我们来详细的分析一下吧:
-
sizeof(a)a是一个3行4列的二维数组,每个元素是int类型。- 在大多数系统中,
int类型占4个字节,所以整个数组的元素个数为3 * 4 = 12个。 - 因此,
sizeof(a)的结果是3 * 4 * sizeof(int) = 48字节。
-
sizeof(a[0][0])a[0][0]表示数组a的第一个元素,它是int类型。- 通常
int类型占4个字节,所以sizeof(a[0][0])的结果是4字节。
-
sizeof(a[0])a[0](int *类型) 表示数组a的第一行,它可以看作是一个包含4个int元素的一维数组。- 所以
sizeof(a[0])的结果是4 * sizeof(int) = 16字节。
-
sizeof(a[0] + 1)a[0]是第一行数组的首地址,a[0] + 1是第一行第二个元素的地址。- 地址本质上是一个指针,在大多数系统中,指针的大小是固定的,通常为
4或8字节(取决于系统是 32 位还是 64 位)。 - 所以
sizeof(a[0] + 1)的结果是指针的大小,一般为4或8字节。
-
sizeof(*(a[0] + 1))*(a[0] + 1)表示对第一行第二个元素的地址进行解引用,得到的是第一行第二个元素。- 该元素是
int类型,所以sizeof(*(a[0] + 1))的结果是4字节。
-
sizeof(a + 1)a(int (*)[4]类型)是二维数组名,a + 1表示跳过第一行,指向第二行的首地址。- 它是一个指针,所以
sizeof(a + 1)的结果是指针的大小,一般为4或8字节。
-
sizeof(*(a + 1))*(a + 1)表示对指向第二行的指针进行解引用,得到的是第二行数组。- 第二行数组包含
4个int元素,所以sizeof(*(a + 1))的结果是4 * sizeof(int) = 16字节。
-
sizeof(&a[0] + 1)&a[0](int (*)[4]类型) 是第一行数组的地址,&a[0] + 1表示跳过第一行,指向第二行的地址。- 它是一个指针,所以
sizeof(&a[0] + 1)的结果是指针的大小,一般为4或8字节。
-
sizeof(*(&a[0] + 1))*(&a[0] + 1)表示对指向第二行的地址进行解引用,得到的是第二行数组。- 所以
sizeof(*(&a[0] + 1))的结果是4 * sizeof(int) = 16字节。
-
sizeof(*a)*a(int *类型) 等价于a[0],表示第一行数组。- 所以
sizeof(*a)的结果是4 * sizeof(int) = 16字节。
-
sizeof(a[3])- 虽然数组
a只有3行(索引从0到2),a[3]属于越界访问。 - 但
sizeof是在编译时计算大小,它不会真正访问数组元素,a[3]被看作是一个包含4个int元素的一维数组。 - 所以
sizeof(a[3])的结果是4 * sizeof(int) = 16字节。
- 虽然数组
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;
}
详细讲解:
&a:这里的&a表示整个数组a的地址,它的类型是int (*)[5],即指向包含 5 个整型元素的数组的指针。&a + 1:由于&a是指向整个数组的指针,&a + 1会跳过整个数组的内存空间。在这个例子中,数组a包含 5 个整型元素,每个整型元素通常占 4 个字节(取决于系统),所以&a + 1会跳过5 * 4 = 20个字节,指向数组a之后的内存地址。(int *):这是一个强制类型转换操作,将&a + 1的类型从int (*)[5]转换为int *,这样ptr就变成了一个指向整型的指针。
printf 函数输出
*(a + 1):数组名a代表数组首元素的地址,a + 1表示数组首元素地址向后偏移一个整型元素的位置,即指向数组的第二个元素。*(a + 1)是对该地址进行解引用操作,得到该地址存储的值,也就是数组的第二个元素 2。*(ptr - 1):ptr指向数组a之后的内存地址,ptr - 1表示将指针ptr向前偏移一个整型元素的位置,即指向数组a的最后一个元素。*(ptr - 1)是对该地址进行解引用操作,得到该地址存储的值,也就是数组的最后一个元素 5。
3.2 题⽬2
/在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
#include<stdio.h>
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>引入标准输入输出库,以便后续使用printf函数进行输出。 - 结构体定义:定义了
struct Test结构体,它包含了不同类型的成员变量。 - 指针初始化:声明了一个指向
struct Test类型的指针p,并将其初始化为地址0x100000。这里通过强制类型转换(struct Test*)把0x100000转换为struct Test*类型。
1 printf("%p\n", p + 0x1);
- 指针运算规则:在 C 语言里,当对指针进行加减运算时,偏移量是根据指针所指向的数据类型的大小来计算的。
p是指向struct Test类型的指针,已知struct Test结构体大小为 20 字节。 - 具体计算:
p + 0x1表示将指针p向后偏移 1 个struct Test结构体的大小,也就是偏移 20 字节。因为十六进制中,20 字节对应的十六进制数是0x14(十进制 20 转换为十六进制为0x14)。所以p + 0x1得到的地址是0x100000 + 0x14 = 0x100014。 - 输出结果:这行代码会输出
0x100014。
2 printf("%p\n", (unsigned long)p + 0x1);
- 类型转换与运算:首先将指针
p强制转换为unsigned long类型,此时p就变成了一个无符号长整型数值。在进行(unsigned long)p + 0x1运算时,只是简单地对这个无符号长整型数值进行加法操作,即把地址值0x100000加上1(整数加1)。 - 输出结果:所以这行代码会输出
0x100001。
3 printf("%p\n", (unsigned int*)p + 0x1);
- 类型转换与指针运算:先把指针
p强制转换为unsigned int*类型,也就是指向无符号整型的指针。在 X86 环境下,无符号整型unsigned int通常占用 4 个字节。当对unsigned int*类型的指针进行加法运算时,偏移量是根据无符号整型的大小来计算的。 - 具体计算:
(unsigned int*)p + 0x1表示将指针向后偏移 1 个无符号整型的大小,即偏移 4 个字节。所以得到的地址是0x100000 + 0x4 = 0x100004。 - 输出结果:这行代码会输出
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;
}
详细讲解:
- 这里定义了一个 3 行 2 列的二维数组
a。但需要注意的是,初始化列表中使用的是逗号表达式(0, 1)、(2, 3)和(4, 5)。 - 在 C 语言里,逗号表达式会从左到右依次计算每个表达式的值,并且整个逗号表达式的值是最后一个表达式的值。所以
(0, 1)的值是 1,(2, 3)的值是 3,(4, 5)的值是 5。 - 因此,数组
a实际的初始化情况是:
a[0][0] = 1;
a[0][1] = 3;
a[1][0] = 5;
a[1][1] = 未初始化的值(默认是随机值);
a[2][0] = 未初始化的值(默认是随机值);
a[2][1] = 未初始化的值(默认是随机值);
- 定义了一个整型指针
p。 a[0]是二维数组a第一行的首地址,它的类型是int *。将a[0]赋值给p后,p就指向了数组a的第一行的第一个元素。p[0]等价于*(p + 0),也就是p所指向的元素的值。由于p指向数组a的第一行的第一个元素,而该元素的值是 1,所以这行代码会输出 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;
}

详细讲解:
- 计算
&p[4][2]的地址:p是指向包含 4 个整型元素的数组的指针,p[4]表示p向后移动 4 行,由于每行有 4 个整型元素,所以p向后移动了4 * 4 = 16个整型元素的位置。p[4][2]表示在p[4]这一行的基础上再向后移动 2 个整型元素的位置。因此,&p[4][2]相对于p所指向的起始地址偏移了16 + 2 = 18个整型元素的位置。
- 计算
&a[4][2]的地址:a是一个 5 行 5 列的二维数组,a[4]表示第 5 行(数组下标从 0 开始)的首地址,相对于a的起始地址向后移动了4 * 5 = 20个整型元素的位置。a[4][2]表示在第 5 行的基础上再向后移动 2 个整型元素的位置。所以,&a[4][2]相对于a的起始地址偏移了20 + 2 = 22个整型元素的位置。
- 计算地址差值:
&p[4][2] - &a[4][2]表示两个地址之间相差的整型元素个数。这里&p[4][2]相对于起始地址偏移了 18 个整型元素,&a[4][2]相对于起始地址偏移了 22 个整型元素,所以它们的差值为18 - 22 = -4。
- 输出结果:
%p是用于输出指针地址的格式说明符,但在输出指针相减的结果时,它会将差值以十六进制的形式输出。由于差值是 -4,在计算机中以补码形式存储,%p不会对存储的二进制转换,所以存储的是什么就输出什么,对于 32 位系统,-4 的补码是0xFFFFFFFC(没有转换为原码,所以是一个非常大的数)。%d是用于输出十进制整数的格式说明符,会直接输出 -4。
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;
}

详细讲解:
指针 ptr1 的定义与初始化
&aa:这里的&aa表示整个二维数组aa的地址,其类型是int (*)[2][5],即指向一个包含2行5列整型元素数组的指针。&aa + 1:因为&aa是指向整个二维数组的指针,&aa + 1会跳过整个数组aa所占用的内存空间。在这个例子中,数组aa包含2 * 5 = 10个整型元素,每个整型元素通常占4个字节(取决于系统),所以&aa + 1会指向数组aa之后的内存地址。(int *):这是一个强制类型转换操作,将&aa + 1的类型从int (*)[2][5]转换为int *,这样ptr1就变成了一个指向整型的指针,它指向数组aa之后的第一个整型位置。
指针 ptr2 的定义与初始化
aa:数组名aa在大多数表达式中会隐式转换为指向其首元素的指针,对于二维数组aa,它会转换为指向第一行的指针,类型为int (*)[5]。aa + 1:由于aa是指向第一行的指针,aa + 1会指向数组的第二行,其类型仍然是int (*)[5]。*(aa + 1):对aa + 1进行解引用操作,得到第二行数组的首地址,其类型是int *,也就是指向第二行第一个元素的指针。- 这里的强制类型转换
(int *)实际上是多余的,因为*(aa + 1)本身就是int *类型。ptr2最终指向数组aa第二行的第一个元素。
printf 函数输出
*(ptr1 - 1):ptr1指向数组aa之后的内存地址,ptr1 - 1表示将指针ptr1向前偏移一个整型元素的位置,即指向数组aa的最后一个元素。*(ptr1 - 1)是对该地址进行解引用操作,得到该地址存储的值,也就是10。*(ptr2 - 1):ptr2指向数组aa第二行的第一个元素,ptr2 - 1表示将指针ptr2向前偏移一个整型元素的位置,即指向数组aa第一行的最后一个元素。*(ptr2 - 1)是对该地址进行解引用操作,得到该地址存储的值,也就是5。
3.6 题⽬6
#include <stdio.h>
int main()
{
char *a[] = {"work","at","alibaba"};
char**pa = a;
pa++;
printf("%s\n", *pa);
return 0;
}

详细讲解:
- 这里定义了一个指针数组
a。指针数组是指数组的每个元素都是指针类型。在这个例子中,数组a的元素是char *类型,也就是字符指针。 - 初始化列表中的
"work"、"at"、"alibaba"是字符串常量。在 C 语言里,字符串常量会被存储在只读内存区域,并且在使用时会隐式转换为指向其首字符的指针。所以,a[0]指向字符串"work"的首字符'w',a[1]指向字符串"at"的首字符'a',a[2]指向字符串"alibaba"的首字符'a'。 - 定义了一个二级指针
pa,二级指针就是指向指针的指针。 - 数组名
a在大多数表达式中会隐式转换为指向其首元素的指针。由于a的元素是char *类型,所以a会转换为char **类型的指针,指向a[0]。因此,这里将a赋值给pa后,pa就指向了指针数组a的第一个元素a[0]。 - 因为
pa是char **类型的指针,对其进行++操作会让它指向下一个char *类型的元素。也就是说,pa原本指向a[0],执行pa++后,pa指向了a[1]。 *pa是对pa进行解引用操作,由于pa指向a[1],所以*pa就相当于a[1],而a[1]是指向字符串"at"首字符的指针。%s是printf函数用于输出字符串的格式说明符,它会从给定的指针所指向的字符开始,依次输出字符,直到遇到字符串结束符'\0'。所以,这里会输出字符串"at"。
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;
}

详细讲解:
字符指针数组 c 的定义与初始化
- 定义了一个字符指针数组
c,数组中的每个元素都是一个char *类型的指针,分别指向字符串常量"ENTER"、"NEW"、"POINT"和"FIRST"的首字符。
二级字符指针数组 cp 的定义与初始化
- 定义了一个二级字符指针数组
cp,数组中的元素是char **类型的指针。 c+3指向c数组的第 4 个元素(下标从 0 开始),即指向"FIRST"的指针;c+2指向c数组的第 3 个元素,即指向"POINT"的指针;c+1指向c数组的第 2 个元素,即指向"NEW"的指针;c指向c数组的第 1 个元素,即指向"ENTER"的指针。
三级字符指针 cpp 的定义与初始化
- 定义了一个三级字符指针
cpp,并将其初始化为指向二级字符指针数组cp的首元素。
第一个 printf 语句
++cpp:先将cpp指针向后移动一个位置,使其指向cp数组的第 2 个元素(原本指向cp[0],现在指向cp[1])。*++cpp:对移动后的cpp进行解引用,得到cp[1],即c+2,它指向c数组中"POINT"的指针。**++cpp:再对*++cpp进行解引用,得到"POINT"字符串的首地址。- 最终输出字符串
"POINT"。
第二个 printf 语句
++cpp:再次将cpp指针向后移动一个位置,使其指向cp数组的第 3 个元素(即cp[2])。*++cpp:对移动后的cpp进行解引用,得到cp[2],即c+1,它指向c数组中"NEW"的指针。--*++cpp:将*++cpp指向的指针向前移动一个位置,即指向c数组的第 1 个元素(c[0]),也就是"ENTER"的指针。*--*++cpp:对--*++cpp进行解引用,得到"ENTER"字符串的首地址。*--*++cpp+3:将"ENTER"字符串的首地址向后移动 3 个位置,指向字符'E'后面的第 3 个字符'E'。- 最终输出从该位置开始的字符串
"ER"。
第三个 printf 语句
cpp[-2]:等价于*(cpp - 2),将cpp指针向前移动 2 个位置,指向cp数组的第 1 个元素(cp[0]),即c+3,它指向c数组中"FIRST"的指针。*cpp[-2]:对cpp[-2]进行解引用,得到"FIRST"字符串的首地址。*cpp[-2]+3:将"FIRST"字符串的首地址向后移动 3 个位置,指向字符'R'。- 最终输出从该位置开始的字符串
"ST"。
第四个 printf 语句
cpp[-1]:等价于*(cpp - 1),由于cpp指向cp数组的第 3 个元素,所以cpp - 1指向cp数组的第 2 个元素(即c + 2)。cpp[-1][-1]:等价于*(*(cpp - 1) - 1),*(cpp - 1)得到c + 2,*(cpp - 1) - 1得到c + 1,再解引用得到指向"NEW"的指针。cpp[-1][-1] + 1:将指向"NEW"的指针向后移动 1 个位置,指向字符'E',从这个位置开始输出字符串,所以输出"EW"。



















