1.指针是什么
指针是编程语言中的一个对象,利用地址它的值直接指向存在电脑储存器中另一个地方的值。简单地说,指针变量是存放另一个变量地址的变量,该指针变量通过所存放地址可直接找到另一变量的值。
例:
int a = 4 a( 4 ) 0xFFFFFFFF
int *p =a p(0xFFFFFFFF) 0xFFFFFFFE
注:括号左边为变量名,括号内是内存中所存放内容,括号右边是该块儿内存的地址。
总结:指针就是一个变量,但它所存放的内容是地址。(存放在指针中的值都会被当作地址来处理)
2.指针类型
变量有许多类型,int,char,float等等。指针也是变量也同样的有许多类型,如:int *p,chat *p,float *p,doublt *p等等。但是二者有着很大的不同之处,如下图:
通过以上两段程序不难发现,无论是哪种类型的指针变量 他的字节大小都为4。其实当定义一个指针变量后,电脑就会将变量名当作地址来处理,如果你运行如小程序时也会得到同样结果:
1. #include<stdio.h> 2. int main() 3. { 4. char a='1'; 5. int b=1; 6. float c=1; 7. printf("%d\n",sizeof(&a)); 8. printf("%d\n",sizeof(&b)); 9. printf("%d\n",sizeof(&c)); 10. return 0; 11. }
地址的所占字节的大小与类型无关,他与电脑的操作系统有关,32位操作系统中地址所占字节大小为4,64位操作系统中地址所占字节大小为8。
那么不同的指针类型有什么区别呢?
第一点:指针类型决定了指针进行解引用操作时,能访问的空间大小。
1. #include<stdio.h> 2. int main() 3. { 4. int a = 4; 5. char *p = &a; 6. *p = 2; 7. printf("%d",a); 8. }
在某些编译器中编译该段程序时会直接崩溃,在某些编译器中虽然可以成功编译,但并不能得到正确的值。原因是a为整型变量,占用4个字节,指针p为字符型变量,将a的地址存放在p的空间中。若想通过指针改变a的值,*p一次只能访问1个字节,故并不能达到理想效果。
第二点:指针类型决定了指针走一步的大小。
首先阅读以下程序:
通过以上程序就会发现整形指针加1地址就会加4,字符型指针加1地址就会加1。同样的,单精度指针加1,地址就会加4;双精度指针加1,地址就会加8。
3.野指针
概念:指针指向的位置是不可知的(随机的、不确定的、没有确定限制的)。
下面我们来讲述以下野指针的成因:
(1)指针未初始化
1. #include<stdio.h> 2. int main() 3. { 4. int *p; 5. return 0; 6. }
这里的指针p并没有它初始化,那么部分编译器就会给它赋予一个随机值,这样一个野指针就诞生了。
(2)指针越界访问
1. #include<stdio.h> 2. int main() 3. { 4. int arr[]={1,2,3,4,5}; 5. int *p=arr; 6. //此时想要通过for循环将数组arr中值全部改为0 7. int i=0; 8. for(i=0;i<=5;i++) 9. { 10. *p=0; 11. p++; 12. } 13. return 0; 14. 15. }
观察以上这段程序,数组arr中只有5个值,但是for循环却进行了6次,它将数组内存中的值全部改为0后,指针继续往后访问,访问了一个非法空间,那么这也是一个野指针。
(3)指针指向的空间被释放
阅读以下代码:
1. #include<stdio.h> 2. int* test() 3. { 4. int a = 2; 5. return &a; 6. } 7. int main() 8. { 9. int* p = test(); 10. printf("%d", *p); 11. }
接下来我们对这段代码进行分析:在自定义函数test()中,向计算机申请开辟了一块儿空间用来存放a的值,最后将a的地址返回,用指针p来接收。但是计算机为a开辟的这块儿空间仅存在于函数test()中,一旦出了这个函数,a的空间就会归还给电脑。在主函数中,想要通过a的地址找到这块儿已经消失的空间·,从而找到a的值,并将其打印出来,这样是完全不可行的。我们将像p这样的指针也称为野指针。
那么野指针我们应该如何避免呢?
(1)指针使用切记一定要初始化。
(2)小心指针越界。
(3)将指向已被释放的空间的指针置空。
(4)将自己不准备再使用的指针置空,以防成为野指针。
4.指针运算
(1)指针 + / - 整数
其实我们已经在说明指针类型时使用过指针的加发了,接下来我们再用一段代码理解以下指针的减法。
1. #include<stdio.h> 2. int main() 3. { 4. int arr[9] = { 1,2,3,4,5,6,7,8,9 }; 5. int* p = &arr[8];//数组最后一个元素的下标 6. int i = 0; 7. for (i = 0; i <9; i++)//倒着打印数组中的每一个值 8. { 9. printf("%d ", *p); 10. p = p - 1; 11. } 12. }
(注:此程序仅用于来理解指针的减法)
此处我们不再进行过多阐述
(2)指针 - 指针
两指针相减多用于数组中,结果为中间元素的个数。(包含被减指针所对应元素)下来我们用一段代码来理解:
那我们思考是否也可以用arr[0] - arr[9]来计算中间元素的个数呢?
我们用编译器来实现一下:
我们发现,想要知道中间元素个数,就必须用后面的地址减去前面的地址。
我们接下来通过用指针减指针的方式模拟实现一下函数strlen(),来求一下字符串的长度。
1. #include<stdio.h> 2. int my_strlen(char* str) 3. { 4. char* start = str; 5. char* end = str; 6. while (*end != '\0') 7. end++; 8. return end - start; 9. } 10. int main() 11. { 12. char arr[] = {"abcde"}; 13. int a=my_strlen(arr); 14. printf("%d", a); 15. }
认真阅读以上程序能够很好的理解指针减指针。
(3)指针的关系运算
我们仍然采用一段代码的形式来理解指针的关系运算
1. #include<stdio.h> 2. int main() 3. { 4. int arr[5] = { 1,2,3,4,5 }; 5. int* p; 6. for (p = &arr[5]; p >&arr[0]; ) 7. { 8. --p; 9. *p = 0; 10. } 11. }
现在我们将这段代码修改一下
1. #include<stdio.h> 2. int main() 3. { 4. int arr[5] = { 1,2,3,4,5 }; 5. int* p; 6. for (p = &arr[4]; p >=&arr[0];p-- ) 7. { 8. *p = 0; 9. } 10. }
(注:这两段代码只会拿越界指针进行关系运算,故此指针不为野指针)
对比两段代码 ,可以发现,第一段代码会拿arr[4]的后一项与arr[0]比较,而第二段代码会拿arr[0]的前一项与arr[0]比较。实际上大多数编译器两种方法都是可以顺利完成任务的,然而我们还是应该避免第二种操作方式,因为能行不代表可行。
标准规定:允许指向数组元素的指针与指向数组最后一项元素后面那个内存位置的指针做比较’但是不允许与指向第一个元素之前元素所在的空间位置的指针做比较。
5.二级指针
所谓二级指针就是存放指针变量地址的指针。
例:
1. #include<stdio.h> 2. int main() 3. { 4. int a = 1; 5. int* p1 = &a; 6. int** p2 = &p1; 7. printf("%d", **p2); 8. }
上面这个程序就很好的展示了二级指针的使用。
同样的还存在三级指针(int***p),四级指针(int****p)等,用法与二级指针类似。
6.指针数组
指针数组的本质是数组,是用来存放地址的数组,注意与数组指针(指针)区分开。下面我们用一段代码来演示:
1. #include<stdio.h> 2. int main() 3. { 4. int a = 1, b = 2, c = 3; 5. int* arr[3] = { &a,&b,&c }; 6. int i = 0; 7. for (i = 0; i < 3; i++) 8. printf("%d ", *arr[i]); 9. }