在计算机中每个存储单元都有一个地址标号,在定义变量时计算机会随机分配一个地址,并把定义的内容存到该变量单元里。而变量的地址也可以作为内容存到另一个地址的变量单元里,这个存储地址的变量称为指针
如图所示:
#include
int main()
{
int a = 10; //在内存中开辟一块空间
int *p = &a; //这里我们对变量a,取出它的地址,可以使用&操作符。
//将a的地址存放在p变量中,p就是一个指针变量。
return 0;
}
而一个单元的大小为一字节,那么在32位的机器,假设有32根地址线,那么假设每根地址线在寻址时产生一个电信号正电/负电(1或 者0)
32位机器就是:
00000000 00000000 00000000 00000000
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000010
...
11111111 11111111 11111111 11111111
这里就有2的32次方个地址
每个地址标识一个字节,那我们就可以给
(2^32Byte == 2^32/1024KB == 2^32/1024/1024MB == 2^32/1024/1024/1024GB == 4GB)
4G的空闲进行编址。
那地址就得用4个字节的空间来存储,所以一个指针变量的大小就应该是4个字节。
则64位就是8G,以此类推针变量的大小就应该是8个字节。
总结:
1.指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
2.指针的大小在32位平台是4个字节,在64位平台是8个字节。
既然指针的大小是由计算机操作平台决定的,那指针类型是什么呢?
指针的类型代表着指针指向什么类型的数据,如本章开头示例:
int* p;int是类型,p是变量,说明这个指针变量是指向 (int型) 4个字节大小的存储单元(int 为了存放 int 型的变量)
char* pc;char是类型,pc是变量,说明这个指针变量指向(char型)1个字节大小的存储单元(char 为了存放 char 型的变量)
这里引入一个问题:指针+1,不同的类型会有什么现象
int main()
{
int a = 10;
short b = 20;
char c = 30;
int* pa = &a;
short* pb = &b;
char* pc = &c;
printf("pa = %p,(pa+1) = %p\n",pa,pa+1);
printf("pb = %p,(pb+1) = %p\n",pb,pb+1);
printf("pc = %p,(pc+1) = %p\n",pc,pc+1);
return 0;
}
由运行结果可以看出:
pa+1,移动4个字节
pb+1,移动2个字节
pc+1,移动1个字节
所以指针加的是单元格,+1就是下一个数据元素的首地址(数据元素的大小就取决于指向的类型,int就是4个单元格,char就是一个单元格),并不是让原地址基础上加1。
即:指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1(并不是改变指针自身的地址)。至于真实的地址加了多少,要看原来指针指向的数据类型是什么。
问题2:指针+1,把int类型的指针用char接收,会有什么现象
int main()
{
int a = 111;
char* pc = &a;
*(pc+1) = 0;
printf("%d",a);
}
为什么会这样呢?
首先:int占4个单元格(小端存储),111存在第一个单元格中其二进制:0110 1111;而指针接收a变量指向的是char,char只占一个单元格,指针+1则跳到第二个单元格,当给这个单元格赋值为1,二进制是 0000 0001;但最后打印a时,因为a只是把地址给了char型指针,并未改变本来的类型,所以还是以int型输出,那么此时a变量4个单元格中的第二个单元格为1,所以a的二进制为:00000000 00000000 00000001 01101111 = 367
所以当定义指针时,最好要对应好要指向的类型,不然会更改原本的数据
这里解释一下小端存储与大端存储
大小端存储是计算机的存储机制,如图所示:
示例:
#include
int main()
{
int a = 0x11223344;
char *pc = (char*)&a;
*pc = 0;
printf("%x\n", a);
return 0;
}
a = 0x11223300,char型指针pc接收a的地址,再通过解引用给a赋值0,但是char是1字节大小,int是4字节大小,所以char只改变了1个字节也就是int首地址存的44变00,改变的是低字节,由此可知是小端存储
总结:指针的类型决定了指针向前或者向后走一步有多大(单元格/字节),定义指针时,最好要对应好要指向的类型,不然可能会更改原本的数据
示例:
#include
int main()
{
int arr[] = {1,2,3,4,5};
short *p = (short*)arr;
int i = 0;
for(i=0; i<4; i++)
{
*(p+i) = 0;
}
for(i=0; i<5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
arr[i] = 0,0,3,4,5,因为short占2个字节,一共进行4次赋值,共8个字节,也就是int arr本身前两个元素
野指针就是 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
例如1:没赋初始值
#include
int main()
{
int *p; //局部变量指针未初始化,默认为随机值
*p = 20;
return 0; }
因为没有初始化,可能会生成随机数,当赋值20时,可能只是把4个字节中的第一个字节赋为20,当打印时可能不是想要的结果。
例2:越界访问
#include
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
数组只定义了10个有效元素,但是下标是从0开始的,所以在10,11时就会越界。
还有动态内存也会导致野指针,是申请动态内存时没有释放等因素,这里暂时先不做详细解释
如何避免野指针
int my_strlen(char* s)
{
char* p = s;
while(*p++ != '\0')
{
;
}
return p-s;//因为char是1个字节,所以相减可以求出一共几个字节(长度)
}
这个函数,通过使一个指针传递首地址,一个指针计数,最后相减的值就是字符串的长度
例2:指针自增
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
先取首地址给指针,之后先赋值,在让指针自增,移动至下一个位置,当指针移动到5时退出,这样就给浮点数组全部初始化0
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
此代码与上述同理,不同的是,先自减后取内容并赋值,要注意操作符的优先级
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
这种写法在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
注意:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。例如 p[5],if( p[0] == p[6] ),但不可 if( p[-1] == p[0] )
本质上数组就是指针,请看代码
#include
int main()
{
int i;
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[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 的地址
所以可通过指针来访问数组
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里? 这就是 二级指针 。
int a = 10; a是int型的变量
int* p p是指向int*的变量
int** ppa ppa是指向int**的变量
ppa = &p ppa存放的是指针p自己的地址
p = &a p存放的是a的地址
*ppa 可以解引用p,例如 *ppa+1 <==> p+1 (a的下一个数据的首地址)
**ppa 可以解引用a 例如 **ppa <==> 10
#include
int main()
{
int i;
int arr[] = {1,2,3,4,5,6,7,8,9,0};
int** pa;
int *p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr)/sizeof(arr[0]);
pa = &p;
for(i=0; i<sz; i++)
{
printf("&arr[%d] = %p <====> *(pa+%d) = %p\n", i, &arr[i], i, *pa+i);
}
return 0;
}
结果可知,*pa+i< == > p+i < == > arr[i]的地址
二级指针变量第一解引用等于一级指针变量(*pa = p)
但 *(pa+1)解引用只是 下一个地址里的存的内容(比如p的地址是0x12345678,下一个地址就是0x1234567c,而0x1234567c并不知道是什么,因为还没有创建,更不用说它里面的内容是什么),并不是p指针指向的下一个地址,因为p指针只是指向下一个地址并不是改变自己本身的地址,而且下一个地址标号的变量还未分配,所以不确定是什么。
总结:
多级指针以此类推
指针数组:就是存放指针的数组
整型数组: int a[5];
字符型数组: char [5];
指针数组: int* pa[5] 或 char* pc[5],
指针数组,数组里的5个元素就是5个指针
1.指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)
2.指针的大小在32位平台是4个字节,在64位平台是8个字节。
3.指针+1并不是改变指针本身的地址,而是使指针指向存储的首地址的下一个同类型大小的地址,是改变的指针的位置
4.指针的类型决定了指针向前或者向后走一步有多大(单元格/字节),定义指针时,最好要对应好要指向的类型,不然可能会更改原本的数据
5.注意大小端存储
6.如何避免野指针
(1) 指针初始化(不使用的指针设置为NULL)
(2) 小心指针越界
(3) 指针指向空间释放即使置NULL
(4) 指针使用之前检查有效性(if判断,或assert函数等)
7.数组中不允许与指向第一个元素之前的那个内存位置的指针进行比较
8.所以可通过指针来访问数组
9.二级指针是存放一级指针的地址,注意 *(*p+1)与 *p+1的问题
10.指针数组,数组里的元素就都是指针