什么是指针:
指针(pointer)是一种数据类型(整数),这种类型定义出的变量叫指针变量(简称指针)。
计算机的内存是由一个个字节组成的。内存的每个字节都有一个整数编号(十六进制),这个编号通常叫做地址。指针中存储的就是地址。
1bit 是最小的可用单位,能存储1或0;1byte = 8bit;1kb = 1024byte;1mb = 1024kb;1gb = 1024mb;1tb = 1024gb;1pb = 1024tb;
为什么使用指针:
- 堆内存不能与标识符(变量名)建立联系,必须与指针配合才能使用堆内存。
- 优化参数的传递效率,函数的传参是值传递(内存拷贝),如果只传递变量的地址(4字节),也可用达到传参的目的。
- C语言中函数是传值调用。而这个值的类型可以是非指针类型或指针类型。如果函数调用时,从函数外部传给函数的值是非指针类型(如整数,小数,字符等),那么被调函数的任何操作,都不能改变主函数的变量值。若想要在函数体中改变主函数的变量值,那么传的应该是指针类型(即传变量的地址),这样在被调用的函数中,可以利用指针间接访问来改变主函数的变量值。
注意:指针有一定的危险性,不该用的时候不要乱用。
如何使用指针:
定义指针变量:
类型* 变量名;
- 指针变量与普通变量一样默认值不确定,一般可以初始化为NULL。
- 指针变量的用法与普通变量不同,通过名字把指针变量与普通变量区分。
- 指针变量中只存储了一个字节的内存地址,当通过指针变量访问内存时由指针类型决定
- 不能连续定义指针变量。
int* p1,p2; //p1是指针,p2是int类型变量
int *p1,p2; //一个\*只能定义一个指针变量
为指针变量赋值:
int num;
int* p = # //把栈内存赋值给指针变量
p = malloc(4); //把堆内存赋值给指针变量
注意:如果指针变量存储的地址是非法的,则访问内存时就会出现段错误。
通过指针变量访问内存(解引用):
“ * ”间接访问运算符,是一个单目运算符,间接访问指针所指向的对象。“ * ”运算符的操作对象必须是指针(地址)。
注意:“ * ”有两种含义。定义指针变量时表示的是该变量是指针变量,其他情况表示对指针进行解引用,是一个间接访问符。
int num = 10;
int* p = #
// *p <=> num 是等价的
注意:%p,可以显示一个指针存储的地址值
使用指针要注意的问题:
空指针:指针变量的值为NULL,我们把这种指针称为空指针。空指针也是一种错误标志,当一个函数的返回值为NULL时表示函数执行出错。
#include <stdio.h>
#include <stdlib.h>
//函数出错,返回NULL
int main()
{
int* p = malloc(~0); //取到了一个非常大的地址
printf("%p\n",p);
}
/*
out:
(nil)
*/
注意:在大多属系统下NULL就是0地址,而0地址存储的是系统复位时的一些数据,因此对空指针解引用会引发段错误。空指针肯定是NULL,NULL不一定是0。
如何杜绝空指针导致的段错误:对来历不明的指针(如函数参数)进行解引用时要先判断是否为NULL。
野指针:
野指针变量中存储的值是不确定的。
使用野指针的后果:一切正常,段错误,脏数据。
注意:使用野指针不一定会出错,但是野指针的危害比空指针更严重,因为野指针无法通过条件判断出(只能对代码进行分析)。
如何避免野指针所造成的错误:
所有的野指针都是人为造出来的,不制造野指针也就不会有野指针。
- 定义指针时一定要初始化,如果不知道该赋什么值就赋值NULL。
- 函数不应该返回局部、块变量的地址。
- 释放指针指向的内存空间时,立即将指针重置为NULL。
- 当一块堆内存被释放后,指向它的指针应该立即置NULL。
#include <stdio.h> int* func(void) { int num = 100; int* p = # return p; //不应该返回局部变量的地址 } void test(void) { int arr[10] = {1,2,3,4,5,6,7,8,9}; } int main() { int* p = func(); printf("%d\n",*p); test(); printf("%d\n",*p); } /* out: 10 (不确定数) */
指针的运算:
注意:指针变量中存储的就是代表内存地址的整数。整数能使用的运算符,指针变量理论上来说应该都可以使用,但不是所有的运算都有意义。
对于指针类型数据,除了间接引用运算、赋值运算等操作外,还可以有指针加减整数、指针相减及指针间比较运算,除此以外的运算都是非法的。
- 指针 + 整数 = 指针 + 类型字节数 * 整数
指针 - 整数 = 指针 - 类型字节数 * 整数
(指针加减一个整数相当于前后移动) - 指针 - 指针 = (指针 - 指针)/ 类型字节数
(指针减指针可以计算出两个指针之间相隔多少个元素) - 指针 ==、!=、>、<、>=、<= 指针
(判断指针的前后位置关系)
指针与数组:
在C语言中,如果定义了一个数组,则在编译时系统会为数组分配足够大的内存单元,按元素的下标顺序依次存储所有元素。数组名就是这个内存单元的起始地址即指针,它指向数组的第一个元素。
数组名就是一种特殊的常指针(不能赋值、修改),它与数组元素的首地址是对应关系(指针是指向关系)。因为数组名就是指针,所以可以使用指针的语法,而指针也可以使用数组的语法。
int arr[] = {1,2,3,4,5};
for(int i = 0;i<5;i++)
printf("%d %d\n", *(arr+i),arr[i]);
// *(arr+i) <=> arr[i]; 两者是等价的
arr 与 &arr 的区别:
#include <stdio.h>
int main()
{
int arr[5] = {1,2,3,4,5};
int* p = &arr[0];
// 可以看到p存储的地址,arr的地址和arr存储的地址都是相同的。
printf("%p %p %p %p\n",&p,p,&arr,arr);
// 不同的是,&arr指向的是一个数组,arr指向的是一个整型。
// 当&arr+1时,它将跳过数组中所有元素,指向下一个数组。
// 当arr+1时,它将指向下一个数组元素。
int* p1 = (int*)(&arr+1);
printf("%p %p\n",p1 ,arr);
}
/*
out:
0xbff37f68 0xbff37f54 0xbff37f54 0xbff37f54
0xbff37f68 0xbff37f54 (相差了20个字节:arr定义了数组中有5个int整型)
*/
arr类型:int*(即指针)
&arr类型:int (*arr)[5](即数组指针)
数组指针:指向数组的指针。如:int (*arr)[5];
指针数组:由指针变量组成的数组。 如:int* arr[5];
指针与const:
-
const int* p :保护的是指针变量所执行的内存,不能通过解引用来修改内存中的数据。解决提高传递效率带来的安全隐患。
-
int const * p :同上。
-
int* const p :保护指针变量的值不会被显式的修改。指针将不会指向别的地址,防止变成野指针。
-
const int* const *p:既保护指针变量又保护指针变量所指向的内存。
-
int const * const p:同上。
#include <stdio.h> // 解决提高传递效率带的安全隐患 void func(const double* p) { *p = 3.14; //error } int main() { const int num = 10; const int* p = NULL; *p = num; //error int* const p1 = NULL; p1 = # //error double f = 0; func(&f); }