C程序指针

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 = &sum;
    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结合为指针,函数指针
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值