C语言指针(C语言七)

三.指针

1.概念

指针变量是一种特殊的变量,它用来存储地址数据

指针变量如果记录了某个地址,就可以通过指针变量来访问这个地址,指针变量记录的地址可以改变,可以指向不同的存储位置

同一个计算机上所有类型的指针所占空间大小一样,32位系统指针大小为4字节(地址为4字节)

2.语法

(1)声明语法

指针变量的类型和记录的地址上的数据类型有关

指向的数据类型   * 指针变量名;

注:其中 指向的数据类型 *是指针变量的类型

例如: int *p;//p的类型是int *

(2)通过指针访问指向数据的语法

*指针变量名;

注:代表的是指向的数据

例如: *p = 10;

3.指针的使用

指针必须记录有效地址之后才能使用

没有记录有效地址的指针叫野指针,访问野指针可能导致不可预知的错误

int *p; //野指针

绝对不能使用野指针,如何避免使用野指针?

方法:

如果指针没有指向有效地址,就让指针指向NULL(空指针),在访问指针之前判断指针是否为NULL

if(p!=NULL)
{
	...
}

指针变量必须初始化,如果没有有效地址可初始化,那么就初始化为NULL

可以在一行中声明多个同类型的指针,但是不能省略后面的*号

int *p = NULL, *p1 = NULL; 

指针可以看成是一个身份或者职业,我们关心的是这个指针代表的存储位置能够用来做什么,不关心具体的存储位置在哪里,在我们不关心具体位置只关心能够做什么就可以使用指针

改变指针的类型不会影响其记录的地址数据,对指针进行*运算得到的数据类型和地址无关,由指针类型决定

4.通用类型指针

通用类型指针没有明确指示它所指向的数据是什么类型 (可以指向任何类型)

声明语法如下:

void *p_num = NULL;//通用类型指针 

当通用类型指针记录了一个地址之后,必须根据其他信息才能决定如何使用这个指针

使用通用类型指针之前必须进行强制类型转换(强转成它指向的数据的指针类型)

通用类型指针灵活性进一步增强,适用于记录来源不明并且可能性很多的地址数据

5.指针作为函数的参数和返回值

(1)指针作为形参

使用指针作为形参能够让被调用函数使用调用函数提供的地址数据(数组作为形参实际上就是指针作为形参)

使用指针作为形参也可以实现值的双向传递

通用类型指针作为形参可以接收不同类型的数据,增强函数的通用性

在这里插入图片描述

(2)指针作为函数的返回值

使用指针作为返回值可以将函数调用语句当做左值使用

使用指针作为返回值可以返回大量数据

使用指针作为返回值可以让调用函数访问被调用函数提供的地址

因为局部变量在函数返回后空间会被释放,所以绝对不能返回局部变量的地址(野指针)

6.地址可以参与的数学运算

地址只有以下几种运算是有意义的

地址 + 整数    地址 - 整数   地址 - 地址

前两个运算可以认为整数是有单位的,结果还是一个地址,单位是地址指向的数据的大小(由地址的类型决定)

第三个运算要求地址类型相同,结果是一个整数,这个整数也有单位,单位也是地址指向的数据的大小

指针变量记录的是地址,可以参与以上的运算

7.数组名和指针的区别,二级指针

(1)数组名和指针的区别

数组名和指针都代表地址,在很多时候是类似的,语法也基本通用,但是有如下区别:

①指针变量可以被赋值而数组名不可以

②对指针变量进行sizeof运算得到的是指针变量本身的大小(4);对数组名进行sizeof运算得到的是整个数组的大小

③对数组名取地址得到还是数组的首地址(类型变为二维数组);对指针变量取地址得到的是存储指针变量的地址(二级指针)

(2)二级指针——指向指针的指针

二级指针可以作为函数的参数的用法:

如果希望通过调用函数修改某个数据,应该传递这个数据的地址

如果修改的数据是普通的变量,传一级指针,如果修改的数据是指针,传二级指针

8.const指针

指针变量可以用const修饰,当使用const修饰指针变量时,可以有多种用法

表示记录的地址不可以修改 ------------- 指针常量(不可修改地址的指针)

    int *const p;

表示地址指向的数据不能修改 ------------- 常量指针(指向常量的指针)

    const int *p;

    int const *p;

地址和数据都不能修改 ------------- 常量指针常量

    const int *const p;

9.指针数组和数组指针

(1)指针数组

元素类型为指针的数组叫指针数组,需要存储多个同类型的地址时可以使用指针数组

语法:

指针类型 数组名[元素个数]; 

注:指针类型是 xxx *

#include <stdio.h>

int main()
{
	int i = 0;
	int num1 = 1,num2 = 2,num3 = 3,num4 = 4,num5 = 5;
	int *p_arr[6] = {&num1,&num3,&num2,&num4,&num5,NULL};//指针数组

	for(i=0; i<5; i++){
		printf("%p ",p_arr[i]);
	}
	printf("\n");

	for(i=0; i<5; i++){
		printf("%d ",*p_arr[i]);
	}
	printf("\n");//元素是数据的地址 数组名是元素的地址
	
	return 0;
}

在使用指针数组时,通常指针数组用NULL表示指针数组的有效数据结束 (以NULL结束)

在使用函数参数传递指针数组,不需要传递数组长度,只需要传递数组首地址就可以了

(2)数组指针

指向数组的指针就是数组指针

一维数组的指针是一个一级指针,等价于数组名

#include <stdio.h>

int main()
{
	int arr[] = {1,2,3,4,5};
	int *p = arr;//一维数组指针
	int  i;
	for(i=0;i<5;i++){
		//printf("%d ", *(p+i));
		//printf("%d ", p[i]);
		printf("%d ", *p++);//三种用法都可以
	}
	printf("\n");

	return 0;
}

二维数组指针不是二级指针,二位数组指针应该用如下语法声明:

元素类型 (*二位数组指针变量名)[组内元素个数];
#include <stdio.h>

int main()
{
	
	int d_arr[2][3] = {1,2,3,4,5,6};
	int (*p_arr)[3] = d_arr;//二维数组指针

	printf("%p %p\n",p_arr, p_arr+1);//加1是跨越一组的数据
	
	int i,j;
	for(i=0;i<2;i++){
		for(j=0;j<3;j++){
			//printf("%d ",*(*(p_arr+i)+j));
			printf("%d ",p_arr[i][j]);//两种用法都可以
		}
		printf("\n");
	}

	return 0;
} 

10.指针函数和函数指针

(1)指针函数

返回值为指针类型的函数叫指针函数

(2)函数指针

指向函数的指针叫函数指针

函数名是函数第一条语句的地址,函数名代表的就是函数的地址,函数指针记录的函数名代表的地址数据

函数指针声明的语法:

返回值类型 (*函数指针变量名)(形参列表);

这个函数指针的类型是: 返回值类型 (*)(形参列表)

对应类型的函数只能指向对应类型的函数 (返回值和形参列表一致)

如果两个函数的返回值和形参列表相同,那么两个函数的类型相同

当函数指针记录了某个函数的地址,通过函数指针来调用该函数

语法:

(*函数指针)(实参列表);

或者

函数指针(实参列表);
#include <stdio.h>

int add(int a,int b);
int sub(int a,int b);
void print(int num);

int main()
{
	//声明函数指针
	int (*p_func)(int a,int b) = NULL;
	void (*p_func1)(int a) = NULL;
	
	p_func = add;
	printf("%d\n", (*p_func)(1,2));
	printf("%d\n", p_func(1,2));
	 
	p_func = sub;
	printf("%d\n", (*p_func)(1,2));
	
	//p_func = print;
	p_func1 = print;
	p_func1(p_func(1,2));//print(sub(1,2))
	
	//p_func = (int (*)(int a,int b))print;//强行转换类型
	//p_func(1,2);

	return 0;
}

int add(int a,int b)
{
	return a+b;
}

int sub(int a,int b)
{
	return a-b;
}

void print(int num)
{
	printf("%d\n", num);
}

(3)回调函数

回调函数也叫钩子函数,大大提高了函数的灵活性

回调函数(callback)时让先写好的函数去调用后写好的函数,使用函数指针作为函数参数来实现的

#include <stdio.h>

void for_each(int *a,int n,void (*p_func)(int *,void *),void *arg);
void inc(int *p,void *arg);
void dec(int *p,void *arg);
void print(int *p,void *arg);
void sum(int *p,void *arg);

int main()
{
	int num = 0;
	int arr[] = {1,2,3,4,5,6,7,8,9};
	//对数组的每个元素加1
	for_each(arr,9,inc,NULL);
	for_each(arr,9,print,NULL);
	printf("\n");
	
	for_each(arr,9,dec,NULL);
	for_each(arr,9,print,NULL);
	printf("\n");
	
	for_each(arr,9,sum,&num);
	printf("求和结果为%d\n", num);
	
	return 0;
}

void inc(int *p,void *arg)
{
	(*p)++;
}

void dec(int *p,void *arg)
{
	(*p)--;
}

//练习:编写打印的回调函数
void print(int *p,void *arg)
{
	printf("%d ",*p);
}

void sum(int *p,void *arg)
{
	*(int *)arg += *p;
}

//编写一个函数,对一个数组中的所有数据依次处理,做什么处理由调用者提供
//修改每个元素,求和,打印
void for_each(int *a,int n,void (*p_func)(int *,void *),void *arg)
{
	
	int i = 0;
	for(i=0; i<n; i++){
		p_func(a+i,arg);
	}
	
	/*
	int *p_num = NULL;
	for(p_num=a;p_num<=a+n-1;p_num++){
		p_func(p_num,arg);
	}*/
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java.L

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值