1.指针和地址的关系
1.1内存和地址
如果把电脑内存理解为一个个小房间,其最小单位为bit,也就是说每一个小房间中都“住着”一个“1”或者“0”。
那么以上就是内存中一个字节,保存数字“8”的表现形式。
地址的发明是为了好的管理内存,就好像为每个房间标上门牌号一样,地址以字节为单位为每个字节内存编址,方便内存的管理和使用。
1.2指针变量和地址
和int,char等等变量类型一样,指针也是一种变量类型,指针变量是c语言中用于存储地址的变量。
若创建指针变量a,b,c并存入地址,大概会是这样的情形:
1.3指针和地址的大小
由于指针是存放地址的变量,所以指针和地址所占用的内存大小应该相同。
32位平台下地址是32个bit位,指针变量⼤⼩是4个字节。
64位平台下地址是64个bit位,指针变量⼤⼩是8个字节。
注意:
在相同平台下,所有指针不论什么类型占用内存的大小都相同。
2.不同的指针类型(一级指针)以及用途
一般类型的指针的表示一般为在变量后加上“*”,例如:
int -------->int *整型变量的指针
char-------->char *字符变量的指针
2.1指针解引用操作符“*”和取址操作符“&”
通过解引用操作符“*”,我们可以找到指针变量所存放地址指向的内容。
#include <stdio.h>
int main()
{
int n = 1;
int* a = &n;
printf("%d\n", *a);
return 0;
}
通过取址操作符“&”,可以取出变量的地址:
#include <stdio.h>
int main()
{
int n = 1;
printf("%p\n", &n);
return 0;
}
2.1.1不同类型指针解引用的区别
不同类型指针有着不同的访问权限,例如:
int *类型指针解引用时会使用其存放地址向后的4个字节大小的空间,
char * 类型指针解引用时会使用其存放地址向后的1个字节大小的空间。
也就是说,指针所指向的变量类型占用多少空间大小,指针解引用时就可以访问多少空间大小。
如果用代码来显示其差异:
#include <stdio.h>
int main()
{
int n = 0x11223344;
int* a = &n;
char* b = &n;
short* c = &n;
printf("%x\n",*a);
printf("%x\n",*b);
printf("%x\n",*c);
return 0;
}
指针a,b,c中存放相同的内容,解引用后得到的结果如下:
这是因为在小端机器上,0x11223344的存储方式为:
是高字节位存高地址处,低字节位存低地址处,所以在解引用时产生了如下差异:
这就是存放相同地址时,不同指针类型解引用时的不同。
2.1.2不同类型指针+-运算的区别
不同类型的指针在加减运算时也有区别:
#include <stdio.h>
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("n地址:%p\n", &n);
printf("char*存储:%p\n", pc);
printf("char*存储+1:%p\n", pc + 1);
printf("int*存储:%p\n", pi);
printf("int*存储+1:%p\n", pi + 1);
return 0;
}
可以看到,指针类型为char*时,+1地址也+1,跳过一个字节;指针类型为int*时,+1地址+4,
跳过4个字节。这就是不同指针类型+-运算时的区别——
不同类型指针进行+ - 运算时,+ - 的一定是整数,并且跳过字节数是其指向空间类型的大小。
例如:
int * + 1跳过4个字节;
char* + 1跳过1个字节;
short * + 1跳过2个字节;
(32位系统) int ** + 1跳过4个字节;
(64位系统)int** + 1跳过8个字节;
3.多级指针和数组指针(一维数组和二维数组)
3.1二级指针及多级指针
二级指针和多级指针就是指针的指针,用于存放指针变量地址的变量。
以int为例,其关系大致如图:
形式:
一级指针:一般为类型后加“*”:如char *,int *,short *;
二级指针:一般为一级指针类型后加“*”:如char **,int **,short**;
多级指针:一般为n-1级指针类型后加“*”:如char ***,int****;
#include <stdio.h>
int main()
{
int a = 1;
int* p1 = &a;
int** p2 = &p1;
int*** p3 = &p2;
return 0;
}
要点:
C语言指针类型非常严格,每一种变量类型都有其对应的指针类型。
3.1数组指针
数组指针和指针数组一定要区分清楚。
数组指针是一种指针------------->指向数组类型的指针
指针数组是一种数组------------->元素为指针的数组
数组指针定义(int数组指针为例):
int (*p)[10];
表示p变量为指向一个大小为10个元素的int数组的指针变量;
指针数组定义(int*数组为例):
int *p[10];
表示p是一个大小为10个元素的数组,其中元素的类型为int *;
3.1.1数组指针和二维数组的关系
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*p)[9] = arr;//此时arr代表首元素地址------>数组名为数组首元素的地址
int* p1 = p + 1;
printf("%d\n", *p1);
return 0;
}
运行结果为:
通过分析可知,数组指针p+1跳过了9个字节的地址,也就是说:
数组指针进行+ - n运算时,实际地址会+ - 其指向数组大小的倍数*n;
数组指针+1运算时会跳过整个数组(指针类型和数组类型相对应时)来到数组末尾;
又因为int (*)[](数组指针)解引用后得到整型指针int *,
由此可以使用数组指针模拟实现二维数组。
#include <stdio.h>
int main()
{
int a[2][2] = {1,2,3,4};
int b[4] = { 1,2,3,4 };
int(*c)[2] = b;
printf("%d\n", a[1][0]);
printf("%d\n", c[1][0]);
return 0;
}
数组指针即为二维数组数组名的本质,这也是为什么函数传参传二维数组时,函数定义参数中的
二维数组行数可以省略而列数不能省略。
4.函数指针及函数指针数组
4.1函数指针
其定义方式如下:
int (*pf3) (int x, int y)
//| | ------------
//| | |
//| | pf3指向函数的参数类型和个数的交代
//| 函数指针变量名
//pf3指向函数的返回类型
int (*) (int x, int y) //pf3函数指针变量的类型
函数指针即为指向函数的指针,实际上函数名本质上就是函数指针。
所以两者在使用上类似于数组和指针,并无差异:
#include <stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int ret;
int ret1;
int (*add)(int, int) = Add;
ret = Add(1, 2);
ret1 = add(1, 2);
printf("%d\n", ret);
printf("%d\n", ret1);
return 0;
}
4.1.1回调函数
回调函数就是⼀个通过函数指针调用的函数。 如果你把函数的指针(地址)作为参数传递给另⼀个函数,当这个指针被用来调用其所指向的函数 时,被调用的函数就是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
在某些库函数例如qsort的使用中,我们需要用到自己定义的回调函数。
4.2函数指针数组
函数指针数组就是由函数指针变量组成的数组。
使用函数指针数组有时可以大大减少代码量(转移表),
以简易计算器的实现为例子:
不使用函数指针数组时:
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
printf("输⼊操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
使用函数指针数组时
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
do
{
printf("*************************\n");
printf(" 1:add 2:sub \n");
printf(" 3:mul 4:div \n");
printf(" 0:exit \n");
printf("*************************\n");
printf( "请选择:" );
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输⼊操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
printf( "ret = %d\n", ret);
}
else if(input == 0)
{
printf("退出计算器\n");
}
else
{
printf( "输⼊有误\n" );
}
}while (input);
return 0;
}
可以看到使用指针数组时类似函数的调用可以用p[input](x,y)来统一概括,大大简化了代码。