Awesome C/Cpp指针详解

一些复杂的指针类型

1
2
3
4
5
6
7
8
9
10
int p;			// 普通的整形常量
int* p; // 去掉变量名,类型是int*,所以是一个指向int的指针
int p[3]; // 去掉变量名,类型是int [3],所以是一个包含三个整型元素的数组
int *p[3]; // 去掉变量名,类型是int* [3],所以是一个包含三个整型指针的数组
int (*p)[3]; // 去掉变量名,类型是int (*)[3],其中(*)[3]表示这是一个指针,指向长度为3的int数组,int (*)[3]表示这个指针指向的是一个int数组,每个数组包含3个int元素
int **p; // 去掉变量名,类型是int **,表示这是一个二级指针,指向的是一个整形指针int*,可以通过两次解引用来获得所指向的值**p
int p(int); // 去掉变量名,类型是int (int),这是一个函数类型,表示参数列表是int,返回值也是int
int (*p)(int); // 去掉变量名,类型是int (*)(int),这是一个函数指针,表示函数的参数列表是int,返回值也是int,可以像使用函数名那样直接使用函数指针p(5)
int (*p)(int)[3]; // 去掉变量名,类型是int (*)(int)[3],这是一个函数指针,表示函数的参数列表是int,返回值是int [3],返回值是一个包含三个整型元素的数组
int *(*p)(int)[3]; // 去掉变量名,类型是int *(*)(int)[3],这是一个函数指针,表示函数的参数列表是int,返回值是int* [3],返回值是一个包含三个整型指针的数组

这里列举了一些比较复杂的指针的例子,可以多多揣摩,孰能生巧。理解了上述的概念之后,以后遇到指针都应该先问问:这个指针的类型是什么?指针指向的类型是什么?

指针本身是一个地址,在32位机器中占4个字节,在64位机器中占8个字节。

指针的算术运算

指针可以加减一个整数来进行偏移,指针在进行加减的时候是以指针类型所占的字节为基本单位的,举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>

int main() {
char a[20] = "Hello_World!";
char* ptr_char = (char*)a;
int* ptr_int = (int*)a;
char (*ptr_array)[20] = a;
ptr_int++; // 指针ptr_int的类型是int*,所以增加一次,相当于增加了一个int的大小,+4
ptr_char++; // 指针ptr_char的类型是char*,所以增加一次,相当于增加了一个char的大小,+1
ptr_array++; // 指针ptr_array的类型是char[20],所以增加一次,相当于增加了一个char[20]的大小,+20
printf("Origin addr of array is %d\n", a); // 1840248776
printf("%d\n", ptr_char); // 1840248777
printf("%d\n", ptr_int); // 1840248780
printf("%d\n", ptr_array); // 1840248800
printf("%c\n", *ptr_char); // e
printf("%c\n", *ptr_int); // o
return 0;
}

数组和指针的关系

数组名通常是指数组首元素的地址,但是有如下两个例外情况:

1、当数组名放在运算符sizeof之中的时候,数组名表示整个数组,sizeof(arr)求取整个数组的大小。
2、当对数组名取地址&arr时,表示取了整个数组的地址,类型是一种数组指针。

其余情况中直接使用数组名表示数组首元素的地址

  • 举个数组名代表首元素地址简单的例子
1
2
3
4
int array[10]={0,1,2,3,4,5,6,7,8,9},value;
value=array[0]; //也可写成:value=*array;
value=array[3]; //也可写成:value=*(array+3);
value=array[4]; //也可写成:value=*(array+4);
  • 举个求sizeof的例子
1
2
char str[6] = "Hello";
printf("%d",%sizeof(str)); // 大小为6 byte
  • 举个对数组名取地址的例子

数组名作为指针

当直接使用数组名(例如 arr)时,它通常会被转换为指向数组第一个元素的指针,类型是 T*,其中 T 是数组元素的类型。例如:

1
2
int arr[10];
int *p = arr; // 等同于 int *p = &arr[0];

**取数组名的地址 (&arr)**:

当你取数组名的地址时(&arr),表示你获取了整个数组的地址,而不是某个具体元素的地址。其类型是数组指针,即指向整个数组的指针,类型为 T (*)[N],其中 T 是数组元素的类型,N 是数组的大小。例如:

1
2
int arr[10];
int (*p)[10] = &arr; // p 是指向整个数组的指针

这里 p 的类型是 int (*)[10],它是一个指向包含 10 个 int 元素的数组的指针

关于数组和指针有个特别需要注意的地方,就是有关字符串字面量的赋值,举个例子:

1
2
3
4
5
char arr[20] = "Hello Wrold!";			// 这样赋值是正确的,相当于将字符串的每个字符都存储在数组arr中,空余的部分用0填充
char arr[] = "Hello Wrold!"; // 没有指定大小,自动确定大小
char arr[20] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'r', 'o', 'l', 'd', '!'}; // 这样使用显示的初始化列表也是正确的
char arr[] = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'r', 'o', 'l', 'd', '!'}; // 没有指定大小,自动确定大小
char arr[20]; arr = "Hello World!"; // 这种写法是错误的,因为数组arr是指向了一个大小为20类型为char的数组,而"Hello World!"声明了一个字符字面量常量,我们不能试图将一个字符串字面量的地址赋值给arr,正确的写法应该是strcpy(arr, "Hello World!");

结构体和指针的关系

可以声明一个指向结构类型对象的指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct MyStruct
{
int a;
int b;
int c;
};
struct MyStruct ss={20,30,40};
// 声明了结构对象ss,并把ss 的成员初始化为20,30 和40。
struct MyStruct *ptr=&ss;
// 声明了一个指向结构对象ss 的指针。它的类型是
// MyStruct *,它指向的类型是MyStruct。
int *pstr=(int*)&ss;
// 声明了一个指向结构对象ss 的指针。但是pstr 和
// 它被指向的类型ptr 是不同的。
// 访问ptr所指向的结构体元素
printf("%d\n", ptr->a);
printf("%d\n", ptr->b);
printf("%d\n", ptr->c);

函数和指针的关系

函数指针在 C 和 C++ 中是用来指向函数的指针。它的主要作用是能够动态地调用不同的函数,这为程序设计提供了极大的灵活性。函数指针常用于回调函数、动态函数选择等场景。

函数指针应该如何使用呢?举个例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

int add(int a, int b) {
return a + b;
}

int main() {
int(*func_ptr)(int, int) = &add;
// 也可以直接赋值,因为函数名就是指向函数代码开头的那段地址,再对其进行取地址也是这一段地址
// int(*func_ptr)(int, int) = add;

// 可以像使用函数一样使用函数指针
printf("%d\n", func_ptr(4, 5)); // 输出9
return 0;
}

函数指针的作用

  1. 动态函数调用:可以通过改变函数指针来调用不同的函数。
  2. 回调函数:通过函数指针,将某些处理逻辑传递给其他函数,从而实现回调。
  3. 提高代码的灵活性和可扩展性

例如我们可以通过函数指针来实现一个简单的计算器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>

// 定义四个基本运算函数
int add(int a, int b) {
return a + b;
}

int subtract(int a, int b) {
return a - b;
}

int multiply(int a, int b) {
return a * b;
}

int divide(int a, int b) {
if (b != 0) {
return a / b;
} else {
printf("Error: Division by zero!\n");
return 0;
}
}

int main() {
// 定义函数指针
int (*operation)(int, int);

int a = 10, b = 5;
char operator;

printf("Enter operator (+, -, *, /): ");
scanf(" %c", &operator); // 用户输入运算符

// 根据用户输入选择不同的运算函数
switch (operator) {
case '+':
operation = add;
break;
case '-':
operation = subtract;
break;
case '*':
operation = multiply;
break;
case '/':
operation = divide;
break;
default:
printf("Invalid operator!\n");
return -1;
}

// 通过函数指针调用相应的运算函数
int result = operation(a, b);
printf("Result: %d\n", result);

return 0;
}

指针练习

数组名通常是指数组首元素的地址,但是有如下两个例外情况:
1、当数组名放在运算符sizeof之中的时候,数组名表示整个数组,sizeof(arr)求取整个数组的大小。(在下文中简称情况一)
2、当对数组名取地址&arr时,表示取了整个数组的地址,类型是一种数组指针。(在下文中简称情况二)
其余情况中直接使用数组名表示数组首元素的地址

指针进行加减时,需要将整数乘以指针类型对于的字节长度,这样指针才能正确找打其应该所在的位置。

源码写在前面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# include <stdio.h>
# include <string.h>
void test1()
{
int a[] = { 1, 2, 3, 4 };
printf("%d ", sizeof(a));
printf("%d ", sizeof(a + 0));
printf("%d ", sizeof(*a));
printf("%d ", sizeof(a + 1));
printf("%d ", sizeof(a[1]));

printf("%d ", sizeof(&a));
printf("%d ", sizeof(*&a));
printf("%d ", sizeof(&a + 1));
printf("%d ", sizeof(&a[0]));
printf("%d ", sizeof(&a[0] + 1));
}

void test2()
{
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));

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));

}

void test3()
{
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));

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

void test4()
{
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));

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

void test5()
{
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]));
}

int main(void)
{
test1();
//test2();
//test3();
//test4();
//test5();

return 0;
}

第一个练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# include <stdio.h>
# include <string.h>
void test1()
{
int a[] = { 1, 2, 3, 4 };
printf("%d\n", sizeof(a)); // 16
printf("%d\n", sizeof(a + 0)); // 4/8
printf("%d\n", sizeof(*a)); // 4
printf("%d\n", sizeof(a + 1)); // 4/8
printf("%d\n", sizeof(a[1])); // 4

printf("%d\n", sizeof(&a)); // 4/8
printf("%d\n", sizeof(*&a)); // 16
printf("%d\n", sizeof(&a + 1)); // 4/8
printf("%d\n", sizeof(&a[0])); // 4/8
printf("%d\n", sizeof(&a[0] + 1)); // 4/8
}

int main(void)
{
test1();
//test2();
//test3();
//test4();
//test5();
return 0;
}

对每个练习都逐个进行解析:

  • 1、直接对a进行sizeof适用于情况一,计算的是整个数组的字节大小,每个int类型是4个字节,所以结果是16个字节。
  • 2、a代表首元素的地址,a+0任然是首元素地址,这里再取sizeof,对于32位的机器来说,地址线共有32位,需要4个字节来存储,对于64位的机器来说,地址线共有64位,需要8个字节来进行存储,所以这里的结果是4或者8。
  • 3、sizeof(*a)表示对首元素的地址处的指针进行解引用,即获得了数组的首元素,值为1是一种int类型,占4个字节所以结果为4。
  • 4、sizeof(a+1)和第三条类似,a代表首元素的地址,a+1代表指针指向下一位元素,a+1任然是一种地址指针,所以占4或者8个字节。
  • 5、sizeof(a[1])a[1]可以等价为*(a+1),这其实是表示取出a数组中索引为1,实际是第二个元素2,元素的类型为int,所以最终的结果是4个字节。
  • 6、sizeof(&a),如情况二所示,&a表示将数组的地址取出来,类型是数组指针,这道题中应该是int (*) [4]类型,仍然是一种地址,那么就应该是32位或者64位,那么就应该占4个字节或者8个字节。
  • 7、sizeof(*&a),这里一种简单的看法就是*&相互抵消了,实际上等价于sizeof(a)表示取出数组的字节长度,结果是16,另一种看法是&a先取出了a数组的地址,然后再对其解引用,实际上是获得了数组a,计算数组的字节长度,那么就是16。
  • 8、sizeof(&a + 1)&a这里获得数组a的地址,类型是int (*) [4],数组指针类型进行+1那么会跳过一个数组的长度,即跳过16个字节,那么&a+1实际指向的下一个数组的位置,但是&a+1同样是地址,那么就为32或者64位,占4或者8个字节。

img

  • 9、sizeof(&a[0]),实际上是取出a数组首元素的地址,32位或者64位,占4或者8个字节。
  • 10、sizeof(&a[0]+1)&a[0]的类型是int*,那么&a[0]+1实际是进行4个字节的加减,那么就指向了数组索引为1,第二个元素,仍然保存的是地址,32位或者64位,占4个字节或者8个字节。

第二个练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# include <stdio.h>
# include <string.h>
void test2()
{
char arr[] = { 'a', 'b', 'c', 'd', 'e', 'f' };
printf("%d\n", sizeof(arr)); // 6
printf("%d\n", sizeof(arr + 0));// 4/8
printf("%d\n", sizeof(*arr)); // 1
printf("%d\n", sizeof(arr[1])); // 1
printf("%d\n", sizeof(&arr)); // 4/8
printf("%d\n", sizeof(&arr + 1)); // 4/8
printf("%d\n", sizeof(&arr[0] + 1)); // 4/8

printf("%d\n", strlen(arr)); //随机值
printf("%d\n", strlen(arr + 0)); //随机值
printf("%d\n", strlen(*arr)); //error
printf("%d\n", strlen(arr[1])); //error
printf("%d\n", strlen(&arr)); //随机值
printf("%d\n", strlen(&arr + 1)); //随机值-6
printf("%d\n", strlen(&arr[0] + 1)); //随机值-1
}

int main(void)
{
//test1();
test2();
//test3();
//test4();
//test5();
return 0;
}
  • 1、sizeof(arr),适用于情况一,表示求数组的字节长度,那么数组arr共有6个元素,每个元素的类型都是char,占一个字节,所以数组的字节长度为6。
  • 2、sizeof(arr +0)arr这里表示数组首元素的地址,那么类型就是char*arr+0仍然指向的是首元素,这里是一个地址,那么占4个字节或者8个字节。
  • 3、sizeof(*arr)arr表示数组首元素的地址,*arr表示对其进行解引用获得了数组的首元素,值为a,是char类型,占1个字节。
  • 4、sizeof(arr[1]),实际上等价于sizeof(*(arr+1)),是取出了数组的第二个元素,值为b,是char类型,占1个字节。
  • 5、sizeof(&arr),适用于情况二,&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6],仍然是地址,那么占4个字节或者8个字节。
  • 6、sizeof(&arr + 1)&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6]&arr + 1指向的是下一个数组的地址。但是其本身仍然是一个地址,所以占4个字节或者8个字节。

img

  • 7、sizeof(&arr[0] + 1)&arr[0]表示取出arr首元素的地址,类型是char*&arr[0] + 1表示取出arr中第二个元素的地址,仍然是一种地址,占4个字节或者8个字节。
  • 8、strlen(arr)arr表示首元素的地址,那么表示从a这个值开始向下计数,直到\0停止计算了字符的长度,但是这里不知道\0在内存中什么位置,所以这里出现随机值。
  • 9、strlen(arr+0)arr+0仍然是首元素的地址,结果仍然是随机值。
  • 10、strlen(*arr)*arr表示对首元素的地址进行解引用,获得首元素的值,即a但是strlen需要传入一个指针类型的变量,这里传入了一个a,会引发报错error。
  • 11、strlen(arr[1])arr[1]表示获得数组第二个元素的值即’b’,同样的道理,也会引发报错error。
  • 12、strlen(&arr),适用于情况二,&arr表示取出数组的地址,类型是char (*)[6]&arr指向的数组的起始位置,从起始位置往下直到遇见\0停止,所以这个结果也是随机值。
  • 13、strlen(&arr + 1)&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6]&arr + 1指向的是下一个数组的地址。这里从下一个数组的起始位置开始计算,也是遇见\0停止,那么结果也是随机值,这个随机值可以写作随机值-6

img

  • 14、strlen(&arr[0] + 1)&arr[0]表示取出arr首元素的地址,类型是char*&arr[0] + 1表示取出arr中第二个元素的地址,也是遇见\0停止,那么结果也是随机值,这个随机值可以写作随机值-1

img

第三个练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# include <stdio.h>
# include <string.h>
void test3()
{
char arr[] = "abcdef";
printf("%d\n", sizeof(arr)); //7
printf("%d\n", sizeof(arr + 0)); //4/8
printf("%d\n", sizeof(*arr)); //1
printf("%d\n", sizeof(arr[1])); //1
printf("%d\n", sizeof(&arr)); //4/8
printf("%d\n", sizeof(&arr + 1)); //4/8
printf("%d\n", sizeof(&arr[0] + 1)); //4/8

printf("%d\n", strlen(arr)); //6
printf("%d\n", strlen(arr + 0)); //6
printf("%d\n", strlen(*arr)); //error
printf("%d\n", strlen(arr[1]));//error
printf("%d\n", strlen(&arr));//6
printf("%d\n", strlen(&arr + 1));//随机值
printf("%d\n", strlen(&arr[0] + 1));//5
}

int main(void)
{
//test1();
//test2();
test3();
//test4();
//test5();
return 0;
}
  • 1、sizeof(arr),适用于情况一,表示直接计算数组字节长度,这里数组其实有7个元素,所以实际长度为7 * 1,为7个字节。

img

  • 2、sizeof(arr + 0)arr这里表示数组首元素的地址,那么类型就是char*arr+0仍然指向的是首元素,这里是一个地址,那么占4个字节或者8个字节。
  • 3、sizeof(*arr)*arr这里表示对数组首元素进行解引用,获得了a,字节长度为1。
  • 4、sizeof(arr[1])arr[1]表示取出数组的第二个元素b,字节长度为1。
  • 5、sizeof(&arr),适用于情况二,&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6],仍然是地址,那么占4个字节或者8个字节。
  • 6、sizeof(&arr+1)&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6]&arr + 1指向的是下一个数组的地址。但是其本身仍然是一个地址,所以占4个字节或者8个字节。

img

  • 7、sizeof(&arr[0] + 1)&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6]&arr + 1指向的是下一个数组的地址。但是其本身仍然是一个地址,所以占4个字节或者8个字节。
  • 8、strlen(arr)arr表示首元素的地址,那么表示从a这个值开始向下计数,直到\0停止计算了字符的长度,计算长度为6。
  • 9、strlen(arr + 0)arr表示首元素的地址,arr+0同样也是指向a的指针,那么表示从a这个值开始向下计数,直到\0停止计算了字符的长度,计算长度为6。
  • 10、strlen(*arr)*arr表示对首元素的地址进行解引用,获得首元素的值,即a但是strlen需要传入一个指针类型的变量,这里传入了一个a,会引发报错error。
  • 11、strlen(arr[1])arr[1]表示获得数组第二个元素的值即’b’,同样的道理,也会引发报错error。
  • 12、strlen(&arr),适用于情况二,&arr表示取出数组的地址,类型是char (*)[6]&arr指向的数组的起始位置,从起始位置往下直到遇见\0停止,结果为6。
  • 13、strlen(&arr + 1)&arr取出的是整个数组的地址,是数组指针,类型是char (*)[6]&arr + 1指向的是下一个数组的地址。这里从下一个数组的起始位置开始计算,也是遇见\0停止,那么结果是随机值。

img

  • 14、strlen(&arr[0] + 1)&arr[0]表示取出arr首元素的地址,类型是char*&arr[0] + 1表示取出arr中第二个元素的地址,从第二个元素开始计算,到\0停止,结果为5。

第四个练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# include <stdio.h>
# include <string.h>
void test4()
{
char* p = "abcdef";
printf("%d\n", sizeof(p)); //4/8
printf("%d\n", sizeof(p + 1)); //4/8
printf("%d\n", sizeof(*p)); //1
printf("%d\n", sizeof(p[0])); //1
printf("%d\n", sizeof(&p)); //4/8
printf("%d\n", sizeof(&p + 1)); //4/8
printf("%d\n", sizeof(&p[0] + 1)); //4/8

printf("%d\n", strlen(p)); //6
printf("%d\n", strlen(p + 1)); //5
printf("%d\n", strlen(*p)); //error
printf("%d\n", strlen(p[0])); //error
printf("%d\n", strlen(&p)); //随机值
printf("%d\n", strlen(&p + 1)); //随机值
printf("%d\n", strlen(&p[0] + 1)); //5
}
int main(void)
{
//test1();
//test2();
//test3();
test4();
//test5();
return 0;
}
  • 1、sizeof(p),p是指向字符串的指针,p保存的是地址,所以值为4个字节或者8个字节。
  • 2、sizeof(p+1),p是指向字符串的指针,类型是char*,所以+1就会加上1个字节,指向第二个元素,p+1同样也是保存的地址,所以为4个字节或者8个字节。

img

  • 3、sizeof(*p)*p表示对p进行解引用获得了p指向的值a,a是一个char类型的值,所以占1个字节大小。
  • 4、sizeof(p[0]),可以等价于sizeof(*(p+0)),取出数组的第一个元素a,占一个字节大小。
  • 5、sizeof(&p)p中存放的是a的地址,&p取地址,取出的是存放p指针的地址,仍然是一个地址,大小为4或者8个字节。

img

  • 6、sizeof(&p + 1)p中存放的是a的地址,&p取地址,取出的是存放p指针的地址,pchar*类型的指针,那么&p应该是char**类型的指针,&p+1指向p后面的一个地址,仍然是一种地址,那么应该为4或者8个字节。

img

  • 7、sizeof(&p[0] + 1)p[0]等价于*(p+0),实际上的取出了数组的第一个元素,&p[0]实际获得了数组首元素的地址,那么&p[0]+1获得了数组第二个元素的地址。为4个字节或者8 个字节。

img

  • 8、strlen(p),p是指向字符串的指针,从首元素地址开始计算,长度为6。
  • 9、strlen(p+1),p是指向字符串的指针,类型是char*,所以+1就会加上1个字节,指向第二个元素,从第二个元素地址开始计算,长度为5。
  • 10、strlen(*p)*p表示对p进行解引用获得了p指向的值a,a是一个char类型的值,a不是一个地址,所以这里会出现错误error。
  • 11、strlen(p[0]),可以等价于sizeof(*(p+0)),取出数组的第一个元素a,a不是一个地址,所以这里会出现错误error。
  • 12、strlen(&p)&p取地址,取出的是存放p指针的地址,这里就不知道后续的内存中存储了什么值,直到遇见\0才停止,所以这里出现随机值。

img

  • 13、strlen(&p+1)p中存放的是a的地址,&p取地址,取出的是存放p指针的地址,pchar*类型的指针,那么&p应该是char**类型的指针,&p+1指向p后面的一个地址,这里就不知道后续的内存中存储了什么值,直到遇见\0才停止,所以这里出现随机值。

img

  • 14、strlen(&p[0] + 1)p[0]等价于*(p+0),实际上的取出了数组的第一个元素,&p[0]实际获得了数组首元素的地址,那么&p[0]+1获得了数组第二个元素的地址,从第二个元素开始计算,长度为5。

img

第五个练习

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# include <stdio.h>
# include <string.h>
void test5()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a)); //48
printf("%d\n", sizeof(a[0][0])); //4 第一行第一列的元素
printf("%d\n", sizeof(a[0])); //16 第一行的元素
printf("%d\n", sizeof(a[0] + 1)); //4/8 第一行第二个元素的地址
printf("%d\n", sizeof(*(a[0] + 1))); //4 第一行第二个元素的值
printf("%d\n", sizeof(a + 1)); //4/8 第二行的元素的地址
printf("%d\n", sizeof(*(a + 1))); //16 第二行的元素
printf("%d\n", sizeof(&a[0] + 1)); //8 第二行元素的地址,为什么和a2[0] + 1的结果不一样呢?
//这是因为&a2[0]获得的指针类型是数组指针,+1会跳过整个一行,
//而a2[0]是普通的int类型指针,+1跳过4个字节,获得下一个元素的地址。
printf("%d\n", sizeof(*(&a[0] + 1))); //16 第二行的元素
printf("%d\n", sizeof(*a)); //16 第一行的元素
printf("%d\n", sizeof(a[3])); //16 第四行的元素
}
int main(void)
{
//test1();
//test2();
//test3();
//test4();
test5();
return 0;
}
  • 1、sizeof(a),适用于情况一,计算整个数组的字节大小,数组共有12个元素,每个都是int类型占4个字节,所以总共48个字节。
  • 2、sizeof(a[0][0]),计算第一行第一列元素的大小,为int类型,所以结果为4。
  • 3、sizeof(a[0])a[0]是一个一维数组名,类型是int*,同样也是数组名适用于情况一,计算整个数组的大小,有4个元素,都是int类型占4个字节,所以结果为16。

img

  • 4、sizeof(a[0]+1)a[0]是一个一维数组名,类型是int*a[0]+1应该跳过一个int类型即4个字节,a[0]+1获得了第一行第二个元素的指针,是一个地址,占4个或者8个字节。

img

  • 5、sizeof(*(a[0]+1))a[0]是一个一维数组名,类型是int*a[0]+1应该跳过一个int类型即4个字节,a[0]+1获得了第一行第二个元素的指针,*(a[0]+1)获得了第一行第二列这个元素0,是一个int类型,占4个字节。
  • 6、sizeof(a+1),a是数组名,是指向整个数组首元素的指针,对于一个二维数组来说,他的首元素的一个一维数组,所以a+1指向第二行的元素,是一个地址,结果为4或则和8个字节。

img

  • 7、sizeof(*(a + 1)),a是数组名,是指向整个数组首元素的指针,对于一个二维数组来说,他的首元素的一个一维数组,所以a+1指向第二行的元素,*(a+1)获得了第二行的元素,所以结果为16个字节。
  • 8、sizeof(&a[0] + 1)a[0]是第一行元素,&a[0]是取出第一行元素的地址,是数组指针,类型为int (*)[4],+1会跳过16个字节的数据,所以&a[0] + 1会指向下一行数据,即指向第二行,本身是个地址,占4或者8个字节。
  • 9、sizeof(*(&a[0] + 1))a[0]是第一行元素,&a[0]是取出第一行元素的地址,是数组指针,类型为int (*)[4],+1会跳过16个字节的数据,所以&a[0] + 1会指向下一行数据,即指向第二行,*(&a[0]+1)获得第二行的元素,计算第二行的字节大小,结果为16。
  • 10、sizeof(*a),a保存的是首元素的地址,首元素是一维数组即第一行,*a获得第一行的元素,计算其字节大小,结果为16。
  • 11、sizeof(a[3]),这里需要了解一个sizeof的特性,sizeof并不会执行其中的代码,而是只计算其类型,这里a[3]明显已经越界了,但是sizeof只计算其大小并不运行代码,所以这里的结果为16个字节。