C/Cpp指针详解
Awesome C/Cpp指针详解
一些复杂的指针类型
1 | int p; // 普通的整形常量 |
这里列举了一些比较复杂的指针的例子,可以多多揣摩,孰能生巧。理解了上述的概念之后,以后遇到指针都应该先问问:这个指针的类型是什么?指针指向的类型是什么?
指针本身是一个地址,在32位机器中占4个字节,在64位机器中占8个字节。
指针的算术运算
指针可以加减一个整数来进行偏移,指针在进行加减的时候是以指针类型所占的字节为基本单位的,举个例子:
1 |
|
数组和指针的关系
数组名通常是指数组首元素的地址,但是有如下两个例外情况:
1、当数组名放在运算符sizeof
之中的时候,数组名表示整个数组,sizeof(arr)
求取整个数组的大小。
2、当对数组名取地址&arr
时,表示取了整个数组的地址,类型是一种数组指针。
其余情况中直接使用数组名表示数组首元素的地址
- 举个数组名代表首元素地址简单的例子
1 | int array[10]={0,1,2,3,4,5,6,7,8,9},value; |
- 举个求
sizeof
的例子
1 | char str[6] = "Hello"; |
- 举个对数组名取地址的例子
数组名作为指针:
当直接使用数组名(例如 arr
)时,它通常会被转换为指向数组第一个元素的指针,类型是 T*
,其中 T
是数组元素的类型。例如:
1 | int arr[10]; |
**取数组名的地址 (&arr
)**:
当你取数组名的地址时(&arr
),表示你获取了整个数组的地址,而不是某个具体元素的地址。其类型是数组指针,即指向整个数组的指针,类型为 T (*)[N]
,其中 T
是数组元素的类型,N
是数组的大小。例如:
1 | int arr[10]; |
这里 p
的类型是 int (*)[10]
,它是一个指向包含 10 个 int
元素的数组的指针
关于数组和指针有个特别需要注意的地方,就是有关字符串字面量的赋值,举个例子:
1 | char arr[20] = "Hello Wrold!"; // 这样赋值是正确的,相当于将字符串的每个字符都存储在数组arr中,空余的部分用0填充 |
结构体和指针的关系
可以声明一个指向结构类型对象的指针
1 | struct MyStruct |
函数和指针的关系
函数指针在 C 和 C++ 中是用来指向函数的指针。它的主要作用是能够动态地调用不同的函数,这为程序设计提供了极大的灵活性。函数指针常用于回调函数、动态函数选择等场景。
函数指针应该如何使用呢?举个例子
1 |
|
函数指针的作用
- 动态函数调用:可以通过改变函数指针来调用不同的函数。
- 回调函数:通过函数指针,将某些处理逻辑传递给其他函数,从而实现回调。
- 提高代码的灵活性和可扩展性
例如我们可以通过函数指针来实现一个简单的计算器
1 |
|
指针练习
数组名通常是指数组首元素的地址,但是有如下两个例外情况:
1、当数组名放在运算符sizeof
之中的时候,数组名表示整个数组,sizeof(arr)
求取整个数组的大小。(在下文中简称情况一)
2、当对数组名取地址&arr
时,表示取了整个数组的地址,类型是一种数组指针。(在下文中简称情况二)
其余情况中直接使用数组名表示数组首元素的地址
指针进行加减时,需要将整数乘以指针类型对于的字节长度,这样指针才能正确找打其应该所在的位置。
源码写在前面:
1 |
|
第一个练习
1 |
|
对每个练习都逐个进行解析:
- 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个字节。
- 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 |
|
- 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个字节。
- 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
。
- 14、
strlen(&arr[0] + 1)
,&arr[0]
表示取出arr首元素的地址,类型是char*
,&arr[0] + 1
表示取出arr中第二个元素的地址,也是遇见\0
停止,那么结果也是随机值,这个随机值可以写作随机值-1
。
第三个练习
1 |
|
- 1、
sizeof(arr)
,适用于情况一,表示直接计算数组字节长度,这里数组其实有7个元素,所以实际长度为7 * 1,为7个字节。
- 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个字节。
- 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
停止,那么结果是随机值。
- 14、
strlen(&arr[0] + 1)
,&arr[0]
表示取出arr首元素的地址,类型是char*
,&arr[0] + 1
表示取出arr中第二个元素的地址,从第二个元素开始计算,到\0
停止,结果为5。
第四个练习
1 |
|
- 1、
sizeof(p)
,p是指向字符串的指针,p保存的是地址,所以值为4个字节或者8个字节。 - 2、
sizeof(p+1)
,p是指向字符串的指针,类型是char*
,所以+1就会加上1个字节,指向第二个元素,p+1
同样也是保存的地址,所以为4个字节或者8个字节。
- 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个字节。
- 6、
sizeof(&p + 1)
,p
中存放的是a的地址,&p
取地址,取出的是存放p指针的地址,p
是char*
类型的指针,那么&p
应该是char**
类型的指针,&p+1
指向p后面的一个地址,仍然是一种地址,那么应该为4或者8个字节。
- 7、
sizeof(&p[0] + 1)
,p[0]
等价于*(p+0)
,实际上的取出了数组的第一个元素,&p[0]
实际获得了数组首元素的地址,那么&p[0]+1
获得了数组第二个元素的地址。为4个字节或者8 个字节。
- 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
才停止,所以这里出现随机值。
- 13、
strlen(&p+1)
,p
中存放的是a的地址,&p
取地址,取出的是存放p指针的地址,p
是char*
类型的指针,那么&p
应该是char**
类型的指针,&p+1
指向p后面的一个地址,这里就不知道后续的内存中存储了什么值,直到遇见\0
才停止,所以这里出现随机值。
- 14、
strlen(&p[0] + 1)
,p[0]
等价于*(p+0)
,实际上的取出了数组的第一个元素,&p[0]
实际获得了数组首元素的地址,那么&p[0]+1
获得了数组第二个元素的地址,从第二个元素开始计算,长度为5。
第五个练习
1 |
|
- 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。
- 4、
sizeof(a[0]+1)
,a[0]
是一个一维数组名,类型是int*
,a[0]+1
应该跳过一个int类型即4个字节,a[0]+1
获得了第一行第二个元素的指针,是一个地址,占4个或者8个字节。
- 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个字节。
- 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个字节。