关于指针的总结:
指针(*)是C语言中的一种数据类型,指针变量中保存的值将会被解析成内存地址。
通常情况下,一个变量函数的地址,是用其所占用的整个内存空间中的低地址来表示的。
C语言中之所以能够使用指针来间接访问另一块内存,是因为CPU本身在设计时就提供了间接寻址的方式;
可以得出结论:编程语言中是否能够通过指针来间接访问内存,不是由语言的高低来决定的,
而是由CPU本身的设计决定的,CPU支持间接寻址方式同时也是为了给软件开发人员提供多种编程方法,
比C语言更高级语言(Java、C#等)中只是经过再次封装后,对软件开发人员屏蔽了用指针来编程的方法,
因为用指针来编程,会带来一定的安全隐患。
指针所指向的变量的数据类型,必须与定义该指针变量时使用的数据类型相同(不考虑void类型指针时)。
(void类型指针指向的内存块,可以通过强制类型转化来解析内存块中存储的数据)
内存本身没有数据类型这一属性,而是由C语言编译器规定的(规定了 数据类型占用的内存字节数以及解析方法),
编译时再转化成相应的汇编指令对与变量关联的内存块进行读写操作。
指针变量的定义与初始化:
int a = 0; //定义一个整型变量a,并初始化为0
int *p = &a; //定义一个指向整型数据的指针p,并且初始化为变量a的地址
引用指针所指变量的值:
int b = *p; //定义一个整型变量b,并且用p所指向的变量的值(也就是变量a的值0)初始化
//*p 称为指针变量的解引用操作
指针变量定义时的类型修饰符,描述的是指针将来要指向的变量的类型。
指针变量所占的内存字节数与指针变量定义时的类型无关,只与机器字长有关(一般是1个机器字长)。
例如:在32位编译器中,(char *) 与 (int *) 都占4个字节的内存空间;
通过强制类型转换可以给指针变量赋与内存中的任何一个地址,但不能对非法的地址进行解引用操作。
例如:char *p = (char *)0x12345678; 但不能使用 *p 对指针进行解引用操作,因为有可能地址是非法地址;
野指针是指程序中某个指针变量指向了一个使用不明确的内存地址,如果通过该指针来访问所指向的内存是不安全的,
可能会引起段访问错误、或带来更改严重的后果。
如何预防野指针提高程序的安全性:
(1) 定义指针变量后最好初始化为NULL或者一个合法的内存地址;
(2) 指针变量使用完后应该将其值设为NULL,来标识为指针为空指针之后将不能对该指针进行解引用;
(3) 在对指针变量进行解引用之前,最好先检查该指针是否为空指针,以免程序因段访问错误面终止运行;
例如: int *p = NULL; //定义时初始化为NULL
if(NULL != p) //使用前先判断是否为NULL
{
*p = xxx; //经过前面的判断保证对p的解引用是安全的
}
p = NULL; //用完后就设为NULL
C/C++中的NULL:
NULL实质上是一个宏定义:
#ifdef __cplusplus //如果__cplusplus被定义了,说明当前是C++环境
#define NULL 0 //在C++中NULL就是0
#else
#define NULL ((void *)0) //在C中NULL是强制类型转化后的0
#endif
0地址的意义:
在操作系统环境下,0地址一般被操作系统占用,如果对0地址进行解引用操作,
程序在运行后当执行到该处时就会报段错误来提示开发人员,可能对一个空指针进行了解引用操作;
指针数组与数组指针:
指针数组 -- 实质上是一个数组,数组中所有元素的类型都为指针类型。
定义格式:int *p[N]; //定义一个包含N个int类型指针元素的数组p
数组指针 -- 实质上是一个指针,指针所指向的内容是一个数组。
定义格式:int (*p)[N]; //定义一个指向 包含N个int类型元素的数组 的指针p
指针函数与函数指针:
指针函数 -- 实质上是一个函数,该函数返回一个指针类型的值。
定义格式:int *func(void); //一个函数声明,func函数没有参数且返回值为int类型指针
函数指针 -- 实质上是一个指针,该指针指向一个函数的首地址。
定义格式:int *(*p)(void); //定义一个指向 参数为空且返回值为整型指针的函数 的指针p
初始化: p = func; //等效语句p = &func;
使用时: p(); //等效语句(*p)();
//因为函数名(函数类型)最终都会被转换成指针类型的方式来使用
二重指针:
二重指针 -- 实质上是一个指针,该指针指向另一个指针变量的地址。
定义格式与初始化:
int a = 0; //定义一个整型变量a,并初始化0
int *p = &a; //定义一个整型指针变量p,并初始化为变量a的地址
int **pp = &p; //定义一个二重指针变量pp,并初始化指针变量p的地址
如何使用二重指针:(以下表达式都是指作左值时的情况)
pp //返回变量pp中保存的值,也就是变量p的地址
*pp //返回变量pp所指向的指针变量p中保存的值,也就是变量a的地址
**pp //因为运算符*是右结合性,所以可以看成*(*pp)
//由上可知*(*pp)能用*(p)替换,也就是返回变量a的值
指针与const关键字:
const关键字修饰普通变量时,表示该变量在初始化后将不能更改变量中保存的值,类似于定义一个常量。
不同平台的编译器可能对const关键字修饰符的实现有所不同。
( 有些平台下只是在编译阶段对const常量的更改进行了限制,const常量本身还是放在可被更改的数据区;
而有些平台下是将const常量放在不可更改的内存段,如代码段、只读数据段等;)
const用于修饰指针的几种用法:
(1) int const *p; //定义一个只能指向常量的指针变量p,但通过更改p的值后可以指向另一个常量,
//不能使用 *p = xxx; 因为*p表示的是一个常量,不能被更改
(2) const int *p; //作为同上。
(3) int * const p; //定义一个常量指针p,p被初始化后将不能指向其它的变量,即不能使用 p = xxx;
//如果p所指向的是一个变量则变量的值是可能更改的,可以使用 *p = xxx; 来更改
(4) const int * const p; //定义一个常量指针p,并且该指针只能指向一个常量,
//p被初始化后,即不能使用 p = xxx; 也不能使用 *p = xxx;
指针与void关键字:
void是C语言中定义的一种类型,称为空类型。
当用于修饰指针类型(void指针)时,任何时候都可以用其它类型的指针来代替void指针,
或用void指针来替代其它类型的指针,并不需要强制转换。
void类型不能修饰非指针类型的变量,只能以(void *)的形式来修饰一个变量,表示指针所指向的内存还未确定类型。
当用void来修饰函数的形参列表或返回值时,表示该函数没有参数或没有返回值。
指针变量的运算:
指针变量取地址运算 -- 返回的结果是指针变量的内存地址,其类型为二重(或多重)指针类型(只能作为右值使用);
指针变量解引用运算 -- (作为左值时)表示指针所指向的那块内存空间,(作为右值时)表示指针所指向的内存块中的数据;
(以下几种运算多数情况下用于数组中,因为数组的地址是连续分布的)
指针变量自增运算 -- 对指针变量进行自增(加1),表示指针向高地址方向移动 1*sizeof(指针所指类型) 个地址单元;
指针变量自减运算 -- 对指针变量进行自减(减1),表示指针向低地址方向移动 1*sizeof(指针所指类型) 个地址单元;
指针变量与整数N进行加减运算 -- 与自增、自减类似,只是移动 N*sizeof(指针所指类型) 个地址单元;
两个指针变量加法运算 -- 一般不这样使用,因为没有实际意义;
两个指针变量减法运算 -- 表示两个指针之间的元素个数(一般用于指向同一数组内的地址的两个指针);
两个指针变量比较运算 -- 表示两个地址的高低,或是否指向同一地址单元(一般要求两个指针所指类型相同);
指针与一维数组:
如何使用指针来访问数组元素:
例如:int a[N] = {0}; //定义一个包含N个整型元素的数组a,并且所有元素都初始化为0
int *p = a; //定义一个整型指针变量p,并初始化为数组a的首元素的首地址(为整型指针类型);
//因为数组名a作右值时表示数组a的首元素的首地址
(&p) //不等效于 (&a),(&a)表示整个数组a的首地址,表达式的值为数组指针类型(int(*)[]);
//而(&p)表示指针变量p的地址,表达式的值为二重指会类型(int **)
(p + i) //等效于 (&a[i]),表示数组a中第i个元素的首地址,表达式的值为整型指针类型(int *)
*(p + i) //等效于 (a[i]),表示数组a中第i个元素的值,表达式的值为整型(int)
数组指针与二维数组:
如何使用数组指针来访问数组元素:
例如:int a[M][N]; //定义一个包含M个元素的数组a,数组a中的所有元素都是一个一维数组(包含N个元素)
int (*p)[N] = a; //定义一个指向一维数组(包含N个元素)的指针p,并初始化为二维数组a的首元素的首地址,
//因为二维数组a的首元素(a[0])是一个一维数组,而一维数组的首地址类型为(int(*)[]),
//所以等效语句为 int (*p)[N] = (&a[0]);
(a[i]) //相当于二维数组a中第i个一维数组的数组名,作右值时表示一维数组首元素的首地址,
//所以等效于表达式 (&a[i][0])或(*(a + i)),表达式的值为整型指针类型(int *)
(p + i) //相当于二维数组a中第i个一维数组的首地址,所以等效于表达式 (&(a[i]))或(a + i),
//表达式的值为数组指针类型(int (*)[N])
*(p + i) //相当于二维数组a中第i个一维数组的数组名,作右值时表示一维数组首元素的首地址,
//所以等效于表达式 (&a[i][0])或(*(a + i)),表达式的值为整型指针类型(int *)
(*(p + i) + j) //相当于二维数组a中第i个一维数组中的第j个元素的首地址,
//所以等效于表达式 (&a[i][j])或(*(a + i) + j),
//表达式的值为整型指针类型(int *)
(*(*(p + i) + j)) //相当于二维数组a中第i个一维数组中的第j个元素的值,
//所以等效于表达式 (a[i][j])或(*(*(a + i) + j)),
//表达式的值为整型(int)
C语言中还支持下面的表达式:
(*(p + i))[j] //等效于 ((*(a + i))[j]) 或 a[i][j]
(*(p[i] + j)) //等效于 (*(a[i] + j)) 或 a[i][j]