1.什么是指针
指针 == 地址 == 内存单元的编号
注1.内存被划分为一个个单元,一个单元大小是1字节。
2.每个内存单元都给一个编号
2.指针有关操作符
1.取地址操作符 ‘&’:
找到起始位置地址
int a = 0;
int* p = &a;
2.解引用操作符 ‘*’:
解引用指针变量,找到指针指向地址所存储的数据
如下,利用指针给整形变量a赋值1
int a = 0;
int* p = &a;
*p = 1;
3.指针变量
指针变量即为存放地址(指针)的变量
指针变量的创建:
int * p = NULL;
指针变量的使用:通过指针变量p间接给变量赋值、进行运算
int a=1;
int *p = &a;
*p = 2;
*p = *p + 1;
3.1指针变量大小
指针变量大小是多少一般取决于一个地址的存放需要多大空间
-32位平台,一个地址占32个bit位(4个字节)
-64位平台,一个地址占64个bit位(8个字节)
-指针变量大小与类型无关,指针变量在相同平台下大小相同
3.2指针变量类型
1.指向数据的指针
*说明是指针变量,int说明指针指向的对象类型,
int* p // 即int* 为指向int对象的指针类型
char* p //char* 即为指向char对象的指针类型
float* p //float* 即为指向float对象的指针类型
以此类推可推出指向各种数据类型的指针如long* 、 double*等
2.指向数组的指针:数组指针
int (*p)[10] //p是指针,指向的是数组,数组有10个元素每个元素类型是int
//p先与*结合说明p是指针,再与int [10]结合说明指向的是数组
/*错误数组指针写法*/
int * p[10] //[]运算符的优先级大于*运算符的优先级,故p会先与[]结合说明p是数组
//再与*结合说数组的元素为int*,故p实质为指针数组
可将 int(* )[ ] 理解为指针变量的类型
以此类推数组指针的类型有char(* )[ ] 、float(* )[ ] 、double(* )[ ]等
-字符指针:
cahr* p = "abcdef"
(1.可以想象为一个字符数组
2.当字符串出现在表达式中时,值是第一个字符的地址)
3.指向函数的指针:函数指针
int add(int a,int b) //创建函数,可将函数类型理解为int (int,int)
{
return a+b;
}
/*函数指针的定义*/
int (*pf)(int,int)=add; //创建函数指针pf,可理解其指针类型为int(* )(int ,int )
//并且将函数add的地址赋给函数指针pf
/*函数指针的调用*/
int a=1;
int b=2;
(*pf)(a,b);
pf(a,b);
/*错误调用*/
*pf(a,b); //因为( )的优先级大于*,所以pf会先与( )结合,再与*结合
可理解pf指针类型为int(* )(int ,int )
4.void* 指针
指向的元素类型不确定
优点:可以接受任意类型的指针
缺点:void* 类型的指针不能直接进行解引用操作、+-1运算、指针运算
一般void* 类型指针用在函数参数部分
5.其他
除上述指针外还有结构体指针等,在此不仔细讲解
3.3指针变量类型的意义
指针类型决定了对指针解引用有多大的权限(一次能访问几个字节)
int* 的指针可以访问4个字节,即8个十六进制位,故可以将n赋值为0
char* 的指针只能访问1个字节,即2个十六进制位,故只能将一个字节的数据赋为0
所以指针类型所指定的类型(int [10]、char [10])占几个字节,一次就只能访问几个字节
指针类型决定了指针向前走或向后走一步有多大(即+1或-1移动几个字节)
运行下面一段代码
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("n %p\n", &n);
printf("pc %p\n", pc);
printf("pc + 1 %p\n", pc + 1);
printf("pi %p\n", pi);
printf("pi + 1 %p\n", pi + 1);
return 0;
}
结果如下
可观察出char* 型指针加1只移动一个字节,int* 型指针加1移动4个字节
所以指针类型所指定的类型(int [10]、char [10])占几个字节,一次+1或-1就移动几个字节
4.const修饰指针(常属性(属性不变))
1.const在* 左边:
int const * p = &a;
限制的是*p,意思是不能通过指针变量p修改p所指向的空间的内容,但是p不守限制
即
*p = 20;//错误
p = &a + 1;//正确
2.const在* 右边:
int * const p = &a;
限制的是p变量,意思是p变量不能被修改了,没办法再指向其他地址了,但是*p不受限制,还可以通过p来修改怕所指向的对象的内容
p = &a + 1;//错误
*p = 20;//正确
5.指针运算
5.1指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));//p+i 这⾥就是指针+整数
}
return 0;
}
运行结果
5.2指针-指针
指针 - 指针前提条件是两个指针指向同一块区域
int my_strlen(char *s)//模拟strlen函数
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;//指针-指针,结果为两个指针之间的元素的个数
}
int main()
{
printf("%d\n", my_strlen("abc"));
return 0;
}
运行结果
5.3指针的关系运算
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
while(p<arr+sz) //指针的⼤⼩⽐较,当p到达数组最后一个元素后停止循环
{
printf("%d ", *p);
p++;
}
return 0;
}
结果
6.野指针
6.1概念:
野指针就是指向位置不可知的(随机的、不正确的、没有明确限制的)指针
6.2成因:
1.指针未初始化
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2.指针越界访问
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3.指针指向空间被释放
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();//变量n所申请的空间在出函数时就已经被释放
printf("%d\n", *p);
return 0;
}
6.3如何规避野指针
6.3.1指针初始化
1.明确指定pa指向a
int a = 0;
int* pa = &a;
2.指针置空
若暂时无法确定p初始化谁的地址,给指针初始化为NULL
int* p = NULL;
NULL是c语言中定义的一个标识常量值为0,0也是地址(代表空值)这个地址无法使用,读写该地址会报错
为什么要用NULL呢?因为将常量赋给指针是非法的如 p = 1000; 非法
NULL在<stdio.h>中预定义
6.3.2小心指针越界
⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。
6.3.3防止指向被释放空间
1.指针变量不再使用时及时置NULL,使用之前及时检查有效性。
2.避免返回局部变量的地址
7.assert断言
assert.h 头⽂件定义了宏 assert( ) ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。
使用:
assert(p != NULL);
上⾯代码在程序运⾏到这⼀⾏语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序 继续运⾏,否则就会终⽌运⾏,并且给出报错信息提⽰。
如图
缺点:引⼊了额外的检查,增加了程序的运⾏时间
⼀般我们可以在 Debug 中使⽤,在 Release 版本中选择禁⽤ assert 就⾏,在 VS 这样的集成开 发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题, 在 Release 版本不影响⽤⼾使⽤时程序的效率