1、指针的概念
系统给内存的每一字节分配一个编号,这个编号就是内存地址。
指针就是记录内存地址编号的一个变量。
在计算机内部存储器(简称内存)中,每一个字节单元,都有一个编号,称为地址。在C语言中,内存单元的地址称为指针,专门用来存放地址的变量,称为指针变量。
&:取一个变量的地址
*:在定义指针变量的时候,起到标识作用
除了定义指针变量以外,都标识取一个指针变量的内容
int a = 10;
int *p = &a;
p --> &a
*p --> a
&p --> p的地址
2、指针变量
指针变量:本质是一个变量 ,只是这个变量不是存放的普通数据,而是存放的内存的编号。
1、定义指针变量
void test01()
{
//普通变量的空间
int num = 10;
printf("&num = %p\n", &num);//%p是以十六进制输出地址编号
//定义一个指针变量 保存 num的地址
//指针变量 变量名为p 在定义的时候 *修饰p为指针变量
int *p;
printf("sizeof(p) = %d\n", sizeof(p));
//指针变量 和 普通变量的地址 建立关系
p = #// num的地址赋予 P
printf("p = %p\n", p);
printf("&num = %p\n", &num);
}
2、指针变量使用
void test02()
{
int num = 10;
//在定义中 *修饰p为 指针变量
int *p;
p = #
//*p 在使用中 *修饰p 表示 取p所保存的地址编号对应空间的内容
//*p == num
//p == &num
//printf("num = %d\n",num);
printf("num = %d\n",*p);//10
//num = 100
*p = 100;
printf("num = %d\n", num);
//scanf("%d", &num);
scanf("%d", p);//p == &num
printf("num = %d\n", num);
}
3、指针变量的类型
指针变量自身的类型
int *p;
指针变量自身的类型:int 型指针类型
指针变量指向的类型:int型数据类型
指针变量的跨度:由指针变量指向的类型大小决定
void test03()
{
char *p1 = NULL;
printf("p1 = %u\n",p1);
printf("p1 + 1 = %u\n",p1 + 1);
short *p2 = NULL;
printf("p2 = %u\n",p2);
printf("p2 + 1 = %u\n",p2 + 1);
int *p3 = NULL;
printf("p3 = %u\n",p3);
printf("p3 + 1 = %u\n",p3 + 1);
int **p3 = NULL;
printf("p4 = %u\n",p4);
printf("p4 + 1 = %u\n",p4 + 1);
}
下图为64位Linux系统打印结果
4、指针变量的初始化
void test04()
{
int num = 10;
//此处的* 只是说明P为指针变量;&num仅仅是给P赋值,而不是将值赋予*p;
int *p = #
//如果一个指针变量开始不知道指向谁 可以先赋予NULL
int *p1 = NULL;
//指针变量本质是变量 指针变量是可以更改指向;
int num1 = 100;
int *p2 = NULL;
p2 = &num1;
//将p2指向num1的地址
printf("*p2 = %d\n",*p2);//100
int num2 = 200;
p2 = &num2;
printf("*p2 = %d\n",*p2);//200
}
5、指针注意事项
void test05()
{
//1、void 不能用于定义变量
//void num;//err
//2、void *可以定义变量
//p的类型为void *在32位平台 4B,所以可以定义变量p
void *p;//万能指针-->可以保存任意类型的地址
int num = 10;
p = #
//float f = 3.14f;
//p = &f;
//不能通过 万能指针 范围所存储的空间对应的内容
// printf("*p = %d\n", *p);//err 不能使用*p
//必须对 万能指针 强制类型转换 才能取对应空间的内容
printf("*p = %d\n", *(int *)p);
//3、不要操作未初始化的指针变量
//p1保存的是一个随机地址 容易访问非法内容 造成段错误
int *p1;
//printf("*p1 = %d\n", *p1);//段错误
//4、不要操作 初始化 为NULL的指针变量
int *p2 = NULL;
//printf("*p2 = %d\n", *p2);//段错误
//5、不要给指针变量赋 无意义的数值
int *p3 = 2000;//可能不存在地址编号为2000的内存
printf("*p3 = %d\n", *p3);//段错误
//6、不要操作越界的空间
int data = 100;
int *p4 = &data;
p4 = p4+1;
printf("*p4 = %d\n", *p4);//非法操作
}
3、指针与数组
1、指针与数组关系密切,通过一段程序具体分析
void test06()
{
int arr[5] = {10,20,30,40,50};
int n = sizeof(arr)/sizeof(arr[0]);
int *p = NULL;
//p保存数组第0个元素地址
//p = arr;//与下面表达式等价,arr代表的是数组首元素地址等价于&arr[0];
p = &arr[0];
printf("*p = %d\n",*p);//10
//数组元素的指针变量+1 则是指向下一个元素
p ++;
printf("p = %d\n",*p);//20
p = &arr[0];
int i = 0;
for ( i = 0; i < n; i++)
{
//p + i 代表第i个元素的地址
//*(p + i)代表的是第 i个元素的值
//printf("%d",arr[i]);
//与下面表达式等价arr[i]<===>*(p + i);
printf("%d",*(p + i));
}
printf("\n");//10 20 30 40 50
}
总结
[ ]就是 * ( ) 的缩写
arr == &arr[0] == &(arr+0) == arr+0 == arr
p+i == &arr[i] == &(arr+i)==arr+i == p+i
2、arr和&arr的区别
void test07()
{
int arr[5] = {10,20,30,40,50};
int *p1 = arr;
int *p2 = arr+3;
//1、指向同一数组的两个指针变量相减 返回的是相差元素的个数
printf("%d\n", p2-p1);//3
//2、[]里面 在特殊情况下可以为负数
int *p3 = arr+2;
printf("%d\n", p3[-1]);//20
printf("%d\n", p3[2]);//50
//3、指向同一数组的两个指针变量 可以判断大小(指针变量的位置)
//p1>p2 p1在p2的右边 p1 == p2 两指针指向同一处
if(p1 > p2)
{
printf("ok\n");
}
else
{
printf("no\n");
}
//4、指向同一数组的两个指针变量 不要相加
printf("%d\n", p1+p2);//无意义
}
注意:数组名是符号常量,不能被赋值,不能±操作;
3、指针数组和数组指针
1、指针数组
int *arr[4];
指针数组存放的是int * 型地址,本质是数组 存放的是指针类型(地址)
void test08()
{
int num1 = 10;
int num2 = 20;
int num3 = 30;
int num4 = 40;
//指针数组
int *arr[4] = {&num1,&num2,&num3,&num4};
int n = sizeof(arr)/sizeof(arr[0]);
int i = 0;
for ( i = 0; i < n; i++)
{
printf("%d",*arr[i]);//10 20 30 40
}
printf("\n");
}
指针数组存放char *类型
char * arr[4];
存放的字符串的首地址
void test09()
{
//arr仅仅存放的是 各个字符串的首元素地址
char *arr[4] = {"hehe", "haha","heihei", "henhen"};
int n = sizeof(arr)/sizeof(arr[0]);
int i=0;
for ( i = 0; i < n; i++)
{
printf("%s\n", arr[i]);
}
//取出 "heihei"中的第二个字符'e'
printf("%c\n", *(arr[2] + 4) );
printf("%c\n", arr[2][4] );
//*(arr[2] + 4) = 'E';//err "heihei"在文字常量区 不允许 写操作
}
2、数组指针
int (*p)[5] = NULL;
本质是指针 指向数组首地址
void test10()
{
int arr[5] = {10,20,30,40,50};
int (*p)[5] = NULL;
printf("%d\n",sizeof(p));//8受操作系统的位数影响
printf("p = %u\n",p);//0
printf("p + 1 = %u\n",p + 1);//20
//*p == *&arr== arr(首元素地址)
p = &arr;//将数组arr地址赋予指针数组P的首地址
printf("arr[2] = %d\n",*(*p + 2));
printf("arr[2] = %d\n",*(*[p + 0] + 2));
printf("arr[2] = %d\n",p[0][2]);
//烧脑的来了
printf("%d\n",*((int *)(p+1)-2));//40
}
总结:
指针数组:int *p[5];本质是数组 每个元素为指针
数组指针: int (*p)[5];本质是指针 保存的是数组的首地址
4、指针与结构体
PS:等待完善 结构体部分
5、指针与函数
指针函数和函数指针是C语言比较重要的方法,实际应用中十分广泛。与数组和结构体能够灵活结合用法灵巧多变这些以后在做总结。下面就简单说一下指针函数和函数指针。
1、指针函数
本质是函数,不过返回值是一个指针。
void *fun(args,...);
fun是一个函数,args是形参,void*作为一个整体,是fun函数的返回值,是一个指针的形式。
# include <stdio.h>
# include <stdlib.h>
int * func_sum(int n)
{
static int sum = 0;
int *p = ∑
for (int i = 0; i < n; i++)
{
sum += i;
}
return p;
}
int main(void)
{
int num = 5;
int *p = func_sum(num);
printf("sum:%d\n", *p);
return 0;
}
上述例子,int *func_sum(int n)是一个指针函数,根据传入参数n,计算0-n求和,通过指针的形式返回给调用方。
注意:
当我们使用指针函数返回一个数据的地址时,需要将此数据用static修饰为静态类型,因为局部变量存放于栈区,当此函数调用结束,这个变量寿命周期已结束,会被释放回收,这时返回的改地址内的内容已经改变。一定要避免返回局部变量指针的情况。使用static修饰的变量存储在全局区,生命周期为整个进程,程序没结束此变量一直存在。
2、函数指针
本质是指针,该指针指向的是函数的入口地址。
函数的定义式存在于代码段,因此,每个函数在代码段中,有着自己的入口地址。
red (*P)(args,...);
其中,red为返回值类型,*p作为一个整体,代表的是指向该函数的指针,args为形参列表。p为指针变量。
例如:
#include <stdio.h>
int max(int a, int b)
{
return a > b ? a : b;
}
int main(void)
{
int (*p)(int, int); //函数指针的定义
//int (*p)(); //函数指针的另一种定义方式,不过不建议使用
//int (*p)(int a, int b); //也可以使用这种方式定义函数指针
p = max; //函数指针初始化
int ret = p(10, 15); //函数指针的调用
//int ret = (*max)(10,15);
//int ret = (*p)(10,15);//常见使用此种调用方式
printf("max = %d \n", ret);
return 0;
}
值得注意的是函数指针的调用方式,例子中提供了三种调用方式都可以使用,常见使用第一中和第三种。调用时可以通过函数指针指向的值去调用,也可以直接使用函数指针调用。*p所代表的的就是函数指针所指向的值,也就是函数本身。
函数指针的优点
在比较简单的代码中看不出来函数指针的优越性,当项目较大,代码复杂以后,函数指针就体现出来其优点。函数指针一个非常经典的应用就是回调函数。
回调函数就是通过一个指针指向一个函数。其将此指针作为一个参数,传递给另一个函数。
回调函数并不是由实现方直接调用,而是在特定的事件或条件发生时由另一个方法来调用的。在大的工程中能够很好的去耦合。
例如:
#include<stdio.h>
#include<stdlib.h>
//函数功能:实现累加求和
int func_sum(int n)
{
int sum = 0;
if (n < 0)
{
printf("n must be > 0\n");
exit(-1);
}
for (int i = 0; i < n; i++)
{
sum += i;
}
return sum;
}
//这个函数是回调函数,其中第二个参数为一个函数指针,通过该函数指针来调用求和函数,并把结果返回给主调函数
int callback(int n, int (*p)(int))
{
return p(n);
}
int main(void)
{
int n = 0;
printf("please input number:");
scanf("%d", &n);
printf("the sum from 0 to %d is %d\n", n, callback(n, func_sum)); //此处直接调用回调函数,而不是直接调用func_sum函数
return 0;
}
上列就是一个简单的回调函数的例子。在这个过程中callback无需关心func_sum是怎么实现的,只需要去调用即可。
这样的好处就是,如果以后有对此方法优化,新写了一个func_sum2函数的实现,我们只需要在调用回调函数的地方将指针指向func_sum2即可,无序去修改callback函数内部;
函数指针、回调函数,经常用于Linux内核编程的项目中,对驱动编程有了解的应该不陌生。
总结
如何区分变量是函数指针,指针函数,数组指针,指针数组?
个人理解,按照结合优先级去读,先看最先结合一起的是什么(最外层结合),然后看后结合成什么(内层结合)由外而内去读。
int *a[3];//* 先与变量a结合为指针再与[]结合为数字,连起来就是指针数组
int (*a)[3];//先看最外层,()与[]结合是数组,小括号内是* 与变量a结合为指针,数组指针
int *fun(void);//最外层 * 与变量fun结合为指针在与()结合为函数。指针函数
int (*fun)(void);//最外层 ()与()结合为函数,内部*与fun结合为指针,函数指针