内存和地址
在计算机中,一切的运算数据都会暂存在内存中,当我们需要调用内存中某个部分的时候,就会用到指针。我们可以把计算机的内存比作一条街上的一排房屋,每个房屋都会容纳一定的数据,并可以用房号来标识;首先这个比喻是有一定的局限性的,不过对于初学者来理解,是足够的;这里就不必展开了;那这个房屋得多大呢?在计算机中内存中由数亿上计的位(bit)组成,所以,房屋储存以字节为单位;
假设把上面一个格子当作一个字节,那么每个格子就可存8个位;内存中每个位置都包含一些值;每个字节的位置用地址来标识;在这里,地址是十六进制的;为了存储更大的值,我们把两个或更多个字节合在一起作为一个更大的内存单位。我们的系统中,地址按字节编址,short类型占用2字节,double类型占用8字节;在这里,内存中的每个位置都是由独一无二的地址标识的;当我们要引用某个数据时,就要调用到某个内存,我们通过地址来找到该位置,而指针,就是来记住这个地址的;
指针
所以,指针是内存中一个最小单元的编号,也就是地址,口语中说的指针通常指的是指针变量。
如:
#include <stdio.h>
int main()
{
int a = 10;
int *p = &a;
//a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个之指针变量。
return 0;
}
对于变量a来说,会在内存中开辟一块空间,对于变量a,我们要取到它的地址,可以用到&操作符。
一般来说,在32位机器上,一个指针变量大小为4个字节,64位机器上,8个字节。
值与类型
先看下面代码:
#include <stdio.h>
int main()
{
int a=112,b=1;
float c=3.14;
int* d=&a;
int* e=&c;
return 0;
}
变量表示:
我们知道a是整型存储类型的,对于a取地址,对他解引用是它本身,但是,c是浮点型类型的,对他取地址后解引用,却是一个整数;答案是该变量包含了一系列0或1的位,对于它是整型类型或者是浮点型类型,取决于它的算术指令;所以,不能简单的通过检查一个值的位来判断它的类型;
接着看以下代码:
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL
指针定义类型为 type+*;
对于不同类型的指针,是为了存储相应类型的地址;如char类型指针存储char变量的地址。那不同类型的指针又有什么不同呢?
#include <stdio.h>
int main()
{
int n = 10;
char *pc = (char*)&n;//括号是强制转换
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);
printf("%p\n", pi);
printf("%p\n", pi+1);
return 0;
}
结果:
我们可以看到,对于char类型和int类型取地址,是位于n内存上的地址,
地址加1后,
总结:指针的类型决定了指针向前或者向后走一步有多大(距离)。
最后:
//演示实例
#include <stdio.h>
int main()
{
int n = 0x11223344;//表示地址位置
char *pc = (char *)&n;
int *pi = &n;
*pc = 0;
*pi = 0;
return 0;
}
一开始的地址:
0x11223344
pc解引用后:
0x11223300
pi解引用后:
0x00000000
在这里,电脑采用了小端的存储方式。在电脑中,数据一定是从内存的低地址依次向高地址读取和写入,小端存储,会将低位存储到低地址处,高地址存储到高地址处,也即存储时是0x44332211.所以,pc解引用后,由于它是char类型的,大小只有一个字节,所以只对一个字节内存大小赋值为0,即为44;而pi为int类型,大小4个字节,所以当它解引用后,就会使整个地址为0。
总结:
指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
指针运算
指针±整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
//指针+-整数;指针的关系运算
int main()
{
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
}
在这里,values表示一个数组,vp是一个浮点型指针,在for循环中,vp赋值为value首元素地址,每当它+1后,地址就会不断先后移动4个字节,直至与values[5]地址相同,循环停止;
指针-指针
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
这是一个求字符串长度的函数,假设有一个字符串为“abcdef" ,指针位于初始位置a地址上,p指针表示初始位置地址,当循环之后,p走到了’\0’的位置,最后相减返回,即为字符串长度6。
当然,这里给出结论:指针-指针是返回的是之间有多少个元素,也即地址差/整型字节。一般可以运用到字符串和数组中。上面例子由于是char类型,看不出来。下面给出一个例子:
#include<stdio.h>
int main()
{
int arr[10];
printf("%d",&arr[10]-&arr[0]);
return 0;
}
结果:10
当然指针-指针的前提条件是指向同一块空间。
指针与数组
先看例子
#include <stdio.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;
}
可以看出arr地址即为首元素地址,当然两种情况除外。(数组章节有说明哦).
既然arr可以当作指针,那么就有以下的操作:
#include <stdio.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]);
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 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("%d ", *(p + i));
}
return 0;
}
二级指针
指针变量也是变量,是变量就有地址。如int *p是一个指针变量,那么它的地址就由二级指针来存储。
#include<stdio.h>
int main()
{
int a=10;
int* p=&a;
int** pp=&p;
return 0;
}
可以看到,a是整型变量,p是一级指针,解引用后为10,pp是二级指针,*pp表示一级指针的地址,**pp表示解一级指针的指向的内容。
野指针
野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
所以,我们要规避野指针的情况。
1.创建指针记得初始化,当不知道指向哪里时,直接指向NULL,不能不管它;
2.不能使访问位置越界,如一个数组有10个元素,你访问到下标为10的位置,超过数组长度范围,这就是访问越界。
3. 指针指向空间释放,及时置NULL;
4. 避免返回局部变量的地址,局部变量的指针指向的地址会在函数走完后销毁,当返回到主函数时,主函数指针接收不到任何地址。
5. 指针使用之前检查有效性。