大纲
指针是什么
指针和指针类型
野指针
指针运算
指针和数组
二级指针
指针数组
指针是什么?
在计算机科学中,指针(Pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为“指针”。意思是通过它能找到以它为地址的内存单元 。
指针
指针是个变量,存放内存单元的地址(编号)。
代码实例:
1 2 3 4 5 6 7 8 9 10 11 12 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 10 ; int *p = &a; printf ("%d\n" ,*p); return 0 ; }
✍总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
问题
一个小的单元到底是多大? 1个字节
如何编址?
经过仔细的计算和权衡我们发现一个字节给一个对应的地址是比较合适的。 对于32位的机器,假设有32根地址线,那么假设每根地址线在寻址的是产生一个电信号正电/负电(1或者0)
那么32根地址线产生的地址就会是:
1 2 3 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 ...11111111 11111111 11111111 11111111
那么就有2的32次方个地址。 每个地址标识一个字节,那我们就可以给(2^32Byte == 2^32/1024KB == 2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB) 4G的空闲进行编址。
这里我们就明白:
在32位的机器上 ,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
那如果在64位机器上 ,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地址。
总结: 指针是用来存放地址的,地址是唯一标示一块地址空间的。 指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针和指针类型 变量有不同的类型,整形,浮点型等。那指针有没有类型呢?
准确的说:有的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { printf ("%d\n" , sizeof (char *)); printf ("%d\n" , sizeof (int *)); printf ("%d\n" , sizeof (short *)); printf ("%d\n" , sizeof (double *)); printf ("%d\n" , sizeof (float *)); printf ("%d\n" , sizeof (long *)); return 0 ; }
这里可以看到,指针的定义方式是: type + *
。 其实: char*
类型的指针是为了存放 char
类型变量的地址。 short*
类型的指针是为了存放 short
类型变量的地址。 int*
类型的指针是为了存放int
类型变量的地址。
指针类型的意义 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 0x11223344 ; int * pa=&a; char * pc = &a; printf ("%d\n" , pa); printf ("%d\n" , pc); return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 0x11223344 ; char * pc = &a; *pa = 0 ; return 0 ; }
例2结果图示:
✍指针类型总结:
指针类型决定了指针进行解引用操作的时候,能够访问空间的大小
int*p
; *p能够访问4个字节
char*p
;*p 能够访问1个字节
double* p
; *p 能够访问8个字节
指针+-整数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 0x11223344 ; int * pa = &a; char * pc = &a; printf ("%p\n" , pa); printf ("%p\n" , pa + 1 ); printf ("%p\n" , pc); printf ("%p\n" , pc + 1 ); return 0 ; }
✍总结:指针的类型决定了指针向前或者向后走一步有多大/步长(距离)。
1 2 3 4 5 int *p; p+1 —>4 char *p;p+1 —>1 double * p; p+1 —>8
野指针
概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
野指针成因
指针未初始化
1 2 3 4 5 6 7 #include <stdio.h> int main () { int *p; *p = 20 ; return 0 ; }
指针越界访问
1 2 3 4 5 6 7 8 9 10 11 12 13 #include <stdio.h> int main () { int arr[10 ] = { 0 }; int *p = arr; int i = 0 ; for (i = 0 ; i <= 11 ; i++) { *(p++) = i; } return 0 ; }
指针指向的空间释放
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int * test () { int a = 10 ; return &a; }int main () { int *p = test(); *p = 8 ; return 0 ; }
如何规避野指针
指针初始化
小心指针越界
指针指向空间释放即使置NULL
指针使用之前检查有效性
指针运算
指针+-整数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int arr[10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int i = 0 ; int sz = sizeof (arr) / sizeof (arr[0 ]); int * p = arr; for (i = 0 ; i < sz; i++) { printf ("%d " , *p); p = p + 1 ; } return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int arr[10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; int i = 0 ; int sz = sizeof (arr) / sizeof (arr[0 ]); int * p = &arr[9 ]; for (i = 0 ; i < 5 ; i++) { printf ("%d " , *p); p -= 2 ; } return 0 ; }
指针-指针 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { char ch[5 ] = { 0 }; int arr[10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 }; printf ("%d\n" , &arr[9 ] - &arr[0 ]); printf ("%d\n" , &arr[0 ] - &arr[9 ]); printf ("%d\n" , &arr[9 ] - &ch[0 ]); 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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int my_strlen (char * str) { char * start = str; char * end = str; while (*end != '\0' ) { end++; } return end - start; }int main () { char arr[] = "bit" ; int len = my_strlen(arr); printf ("%d\n" ,len ); return 0 ; }
指针的关系运算 1 2 3 4 for (vp = &values[N_VALUES]; vp > &values[0 ];) { *--vp = 0 ; }
代码简化, 这将代码修改如下:
1 2 3 4 for (vp = &values[N_VALUES-1 ]; vp >= &values[0 ];vp--) { *vp = 0 ; }
简化方法实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证 它可行。
💠标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
解释图例:
arr为数组,p3为数组第一个指针空间之前的指针,p2为数组最后一个指针后面空间的指针,p1为数组中某个空间位置的指针
标准规定用图例来说明就是,p1可以和p2比,p1不能和p3比
指针和数组 数组名是什么?
代码实例:
1 2 3 4 5 6 7 8 9 10 11 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int arr[10 ] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 }; printf ("%p\n" , arr); printf ("%p\n" , &arr[0 ]); return 0 ; }
可见数组名和数组首元素的地址是一样的。
✍结论:
数组名表示的是数组首元素的地址。
那么这样写代码是可行的:
1 2 int arr[10 ] = {1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,0 };int *p = arr;
⭐特殊情况:
&arr- &数组名-数组名不是首元素的地址-数组名表示整个数组–&数组名取出的是整个数组的地址
sizeof(arr) - sizeof(数组名)~数组名表示的整个数组- sizeof(数组名)计算的是整个数组的大小
既然可以把数组名当成地址存放到一个指针中,我们使用指针来访问一个数组就成为可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ##define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int arr[] = { 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 0 }; int *p = arr; int sz = sizeof (arr) / sizeof (arr[0 ]); int i = 0 ; for (i = 0 ; i < sz; i++) { printf ("&arr[%d] = %p <====> p+%d = %p\n" , i, &arr[i], i, p + i); } return 0 ; }
所以p+i 其实计算的是数组arr 下标为i的地址。那我们就可以直接通过指针来访问数组。
代码实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int arr[] = {0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 }; int *p = arr; int sz = sizeof (arr) / sizeof (arr[0 ]); int i = 0 ; for (i = 0 ; i<sz; i++) { printf ("%d " , *(p + i)); } return 0 ; }
二级指针 指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是二级指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 99 ; int * pa = &a; int * * ppa = &pa; printf ("%p\n" , pa); printf ("%p\n" , ppa); return 0 ; }
a的地址存放在pa中 , pa的地址存放在ppa中。pa是一级指针,而ppa是二级指针。
对于二级指针的运算有:
*ppa
通过对ppa
中的地址进行解引用,这样找到的是pa
,*ppa
其实访问的就是pa
.
*ppa
先通过*ppa
找到pa
,然后对pa
进行解引用操作: *pa
,那找到的是a
.
指针数组 🤔指针数组是指针还是数组? 答案:是数组。是存放指针的数组。
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 #define _CRT_SECURE_NO_WARNINGS #include <stdio.h> #include <string.h> int main () { int a = 10 ; int b = 20 ; int c = 30 ; int * arr2[3 ] = { &a, &b, &c }; int i = 0 ; for (i = 0 ; i < 3 ; i++) { printf ("%d " ,*(arr2[i])); } return 0 ; }