指针是C语言的灵魂,C语言因为指针而在众多语言中所向披靡,也使得C语言一直盛行到现在。因为指针的存在也使得C语言的可操作性极大提升,学好指针是十分必要的,下面将为大家来细致的讲解C语言中指针的知识点也是一些易混易错知识点。
C语言定义变量我想大家都很熟悉了,假如说我想定义一个整型变量 a 。
int a;
其实要明确的一点是,我们在C语言中只要是定义的变量它都是存储在内存中的,不管是普通变量,结构体变量还是指针变量,都是存储在内存中的。在32位操作系统中,一个程序的内存有4G。当然这里所指的内存是虚拟内存。
在C语言中每块内存都是以字节为单位,每个内存都有一个编号,每一个编号都是以32位二进制数表示, 我们把这个编号就称之为地址,如下图所示。并且通过下图我们能够确定,每个地址的值都是唯一的。那么指针是用来干嘛的呢?其实指针就是用来存储地址的。
其实通过上面的信息我们能够验算两个问题。
问题一: 为什么32位操作系统中,指针都是4字节?
因为指针存储的是地址,而地址是一个32位二进制数,一个字节是8位二进制数,所以 32 / 8 = 4(字节)。
问题二: 为什么32位操作系统中,虚拟内存是4GB?
因为每块内存的1字节,每个内存编号是32位二进制数表示,所以内存一共有 2^32 字节大小,换算下单位 2^32(Byte) = 4,294,967,296(Byte) = 4,194,304(KB) = 4,096(MB) = 4(GB)。
常量是存储在变量中,变量是存储在内存中的,每个内存都有一个编号,我们称这个编号为地址,指针就是用来存储地址的。 这句话大家一定要牢记。
站在编译器的角度,假如定义一个字符类型的变量a,并对其赋值为10,其实编译器就会在内存中分配出一块内存,假如这块内存的地址是 0x12345678 ,那么这块内存里面存储的数值就是10, 同时这块内存就叫做a,通过 a 我们就能读取到内存里面存储的内容,通过取地址符&,我们就能得到这块内存的地址或者也可以说变量的地址,即 &a 的值为 0x12345678 。
在介绍指针之前,需要先介绍以下大端模式和小端模式,大端模式和小端模式其实就是数据的存储方式不一样。像ARM,摩托罗拉,MIPS等采用的是大端模式的存储方式,像Intel等采用的就是小端模式。
大端模式:高地址存储数据的低位,低地址存储数据的高位。
小端模式:高地址存储数据的高位,低地址存储数据的低位。
图解如上,假如变量a的地址是0x000000a1,那么对于大端模式,地址0x000000a1存储的是数值0x12;对于小端模式,地址0x000000a1存储的是数值0x78。
指针的定义:
<存储类型> <数据类型> *<变量名>
指针的初始化:
1.先定义,后初始化
<存储类型> <数据类型> *<变量名>;
<变量名> = <地址>;
2.在定义时初始化
<存储类型> <数据类型> *<变量名> = <地址>;
int a = 10;
int *p = a;
p是一个指针变量,它里面存储的是一个地址。
*p在定义时是表示p是一个指针变量,在后面的运算中*p是一个值,存储的一个地址。
&p是一个常量,表示p所占用的内存地址。
源代码:
#include
int main()
{
/*a是指针变量,b是int型变量*/
/*a被定义时未初始化,此时a是野指针,a里面存储的值是编译器随机分配的,直接使用野指针会造成代码的不确定性*/
int *a, b;
/*将指针变量a指向b的地址*/
a = &b;
/*对变量b的内存里面的内容写成10*/
b = 10;
/*取出指针变量里面的内容用取值运算符*号*/
/*除了在定义时的*号+变量表示该变量是一个指针以外,在之后遇到的*号+变量都是表明是取出指针变量里面的内容的*/
/*因为a是一个地址,所以用%p打印,又因为*a是一个值,是表示取出a中存储的地址的值,所以用%d打印*/
printf("&a = %p, a = %p, *a = %d, &b = %p, b = %d\n", &a, a, *a, &b, b);
return 0;
}
运行结果:
&a = 0061FE18, a = 0061FE14, *a = 10, &b = 0061FE14, b = 10
图示:
如图所示,定义变量 b 的内容是10,b 的地址是 0x0061FE14,a 存储的是 b 的地址,所以 a 内存里面的内容就是b的地址值,所以 a 里面存储的数值为0x0061FE14,通过取值运算符,*a 表示的是取出a中存储的地址里面的内容,a中存储的地址是0x0061FE14,地址0x0061FE14中存储的值是 10,所以 *a 的值是10。
指针的运算其实本质上就是地址的运算。
例如:
char *a;
short *b;
int *c;
源代码:
#include
int main()
{
char a = 10;
int b = 20;
char *pa = &a;
int *pb = &b;
/*通过修改指针间接修改变量a的内容,通过修改变量,指针获取的值也会发生变化*/
*pa = 11;
b = 22;
/* *(&b)通过地址运算符先取出b的内存地址,然后通过取值运算符取出地址里面的值,也就是 *(&b) 和 b 这两种取值的方式是完全等价的*/
printf("a = %d, *pb = %d, *(&b) = %d\n", a, *pb, *(&b));
/*将字符指针指向整型变量*/
/*22的二进制数是0x00000016, 如果打印结果是0x16,那么该机器是小端模式,如果打印结果是0x00,那么该机器是大端模式*/
pa = &b;
printf("*pa = %#x\n", *pa);
return 0;
}
运行结果:
a = 11, *pb = 22, *(&b) = 22
*pa = 0x16
我们知道一级指针是用来存储变量的地址的,那么一级指针变量其实也是个变量,那么它同样的也应该有他的地址,==存储普通变量地址的变量我们称之为一级指针变量,存储一级指针变量地址的变量我们就称之为二级指针变量。==依此类推,存储二级指针变量地址的变量我们就称之为三级指针变量… ,尽管如此,但我们平时用到最多的还是一级指针和二级指针,更高级的指针在工作中用得可以说是很少了,并且,更高级的指针也使得代码的可读性变低,所以我们就不需要对其讨论,并且当掌握了二级指针,后面更高级别的指针都是以此类推的。
其实二级指针的一些属性和一级指针基本差不多,我们直接通过一个实例来对其进行讲解,讲解都在代码中。
源代码:
#include
int main()
{
int a = 10;
/*pa是一级指针变量,&pa是取出一级指针变量的地址,一级指针变量的地址要二级指针来存储*/
int *pa = &a;
/*定义二级指针变量ppa, 用来存储二级一级指针变量的地址*/
int **ppa = &pa;
/*
*ppa 和 pa 是等效的,
**ppa 和 *pa 是等效的,又因为 *pa 和 a 是等效的,所以 **ppa, *pa, a 是等效的
1. &ppa 是二级指针变量的地址,需要用三级指针来存储。
2. ppa 是存储了一级指针变量的地址,ppa 等价于 &pa, 所以 ppa是一个地址。
3. *ppa 是一级指针变量里面的内容,一级指针变量里面的内容是普通变量的地址,所以 *ppa 和 pa 等价,所以 *ppa 也是一个地址
4. **ppa 是一个值。
*/
printf("&ppa = %p, ppa = %p, *ppa = %p, **ppa = %d, &pa = %p, pa = %p, *pa = %d, &a = %p, a = %d\n",
&ppa, ppa, *ppa, **ppa, &pa, pa, *pa, &a, a);
return 0;
}
运行结果:
&ppa = 0xbfc496ac, ppa = 0xbfc496a8, *ppa = 0xbfc496a4, **ppa = 10, &pa = 0xbfc496a8, pa = 0xbfc496a4, *pa = 10, &a = 0xbfc496a4, a = 10