C语言指针

目录

一、指针的引入

1.1 什么是指针:

1.2 什么是指针变量:

        1.2.1 如何定义一个指针变量:

        1.2.2 如何使用一个指针变量:

        1.2.3 变量访问的两种方式:

1.3 指针变量为什么要求类型:

1.4 为什么要用指针变量:

        1.4.1 为什么要用指针之场景一:

        1.4.2 为什么要用指针之场景二:

1.5 关于指针的引入作业布置:

二、通过指针引用数组

2.1 通过指针变量引入数组:

2.2 指针增量和数组的关系:

2.3 通过指针引用数组元素:

        2.3.1 下标法遍历数组:

        2.3.2 指针法遍历数组:

        2.3.3 指针法遍历数组见怪不怪:

        2.3.4 当程序中需要多次调用指针循环遍历数组时要注意:

2.4 指针和数组名见怪不怪,重要面试:

        2.4.1 指针当作数组名,下标法访问:

        2.4.2 数组名拿来+:

2.5 数组名和指针的区别:

        2.5.1 数组名++可不可行:

        2.5.2 在sizeof的时候数组名和指针的区别:

2.6 函数指针数组结合应用:

        2.6.1 用函数封装形式实现数组的初始化和遍历:

        2.6.2 函数指针数组实现将数组中的n个元素按逆序输出:

三、指针和二维数组

3.1 二维数组地址认知:

3.2 通过编程认证认知:

3.3 关于二维数组中的值表示方式树立认知:

3.4 关于二维数组和指针的嵌入式软件笔试题:

四、数组指针

4.1 数组指针认知:

        4.1.1 定义一个指针指向二维数组:

        4.1.2 定义一个数组指针指向二维数组:

        4.1.3 数组指针偏移的时候偏移了多少个字节:

4.2 数组指针和二维数组配合应用:

五、函数指针

5.1 函数地址的定义:

5.2 如何定义一个函数指针变量:

5.3 使用函数指针:

5.4 函数指针的好用之处:

5.5 回调函数的底层逻辑:

六、指针数组

6.1 指针数组的定义:

6.2 函数指针数组案例:

七、指针函数

7.1 指针函数的定义:

7.2 指针函数应用案例:

八、二级指针

8.1 二级指针认知:

8.2 为什么要用二级指针实战:

8.3 二级指针和二维数组避坑指南:

九、指针总结


一、指针的引入

1.1 什么是指针:

  • 核心的观点:指针==地址,指针就是地址,地址就是指针

  • 变量访问的两种方式:1.变量名、2.地址

#include <stdio.h>

int main()
{
	int a = 10;
	
	printf("变量名访问a = %d\n",a);		//通过变量名来访问a的值
	printf("a的地址是:0x%p\n",&a);		//a的地址
	printf("通过地址访问a = %d\n",*(&a));//通过地址来访问a的值									      						       //这里的*是取值运算符,把后面跟的地址中的数据取出来
	return 0;
}
/*课程运行代码:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
变量名访问a = 10
a的地址是:0x000000000061FE1C
通过地址访问a = 10	
*/
  • 我们知道一个变量有四种东西:类型、变量名、值、地址,而这里的地址就是指针

1.2 什么是指针变量:

  • 核心的观点:指针变量==存放地址的变量,指针变量就是用来保存别人地址的变量

1.2.1 如何定义一个指针变量:

#include <stdio.h>

int main()
{
    int a = 10;
    int *p;		//这里的*是一个标识符,告诉操作系统我是一个指针变量,是用来保存别人地址的变量
    			//这里的*只产生在指针变量定义或声明的时候
    p = &a;		//p保存a的地址
    return 0;
}

1.2.2 如何使用一个指针变量:

#include <stdio.h>

int main()
{
	int a = 10;
	int *p;								//定义一个指针变量
	
	p = &a;								//指针变量p保存着变量a的地址
	printf("变量名访问a = %d\n",a);
	printf("a的地址是:0x%p\n",&a);
	printf("通过地址访问a = %d\n",*(&a));
	printf("指针变量访问a = %d\n",*p);	//通过指针变量来访问a的值
	return 0;
}
/*
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
变量名访问a = 10
a的地址是:0x000000000061FE14
通过地址访问a = 10
指针变量访问a = 10
*/
  • 我们知道“指针变量就是用来保存别人地址的变量”,所以我们把变量a的地址赋值给p,这样指针变量p就保存了变量a的地址。

  • 通过*取值运算符可以把指针变量中的地址的值取出来,所以我们看到通过指针变量的方式,可以把a的内容取出来。

/*
	我们知道指针变量是用来保存地址的变量,假设我们定义一个指针变量,不对指针变量赋地址,直接调用指针变量会发生什么事情?
	根据程序运行现象我们发现:程序运行到指针变量访问a的时候,鼠标圆圈一下,运行卡了一下退出了,其实这里是发生了一个“段错误”,段错误就是对野指针或空指针发起访问,我们可以使用gdb调试工具观察一下,看看到底发生了什么
	Segmentation fault是段错误的意思,在程序的12行发生了段错误!
*/
#include <stdio.h>

int main()
{
	int a = 10;
	int *p;								//定义一个指针变量
	
	//p = &a;
	printf("变量名访问a = %d\n",a);
	printf("a的地址是:0x%p\n",&a);
	printf("通过地址访问a = %d\n",*(&a));
	printf("指针变量访问a = %d\n",*p);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
变量名访问a = 10
a的地址是:0x000000000061FE14
通过地址访问a = 10
*/
/*通过gdb调试工具观测野指针造成的段错误:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c -g

E:\code\一阶段C语言\第六章_指针>gdb a.exe
GNU gdb (GDB) 8.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-w64-mingw32".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from a.exe...done.
(gdb) r
Starting program: E:\code\C\_\a.exe
[New Thread 92756.0x16610]
[New Thread 92756.0x167a8]
变量名访问a = 10
a的地址是:0x000000000061FE14
通过地址访问a = 10

Thread 1 received signal SIGSEGV, Segmentation fault.
0x000000000040159d in main () at demo_pointFuXi.c:12
12              printf("a = %d\n",*p);
(gdb)
*/

1.2.3 变量访问的两种方式:

1.3 指针变量为什么要求类型:

/*
	我们在程序中定义两个不同类型的指针变量,都用来保存变量a的地址,来分析一下指针为什么要求类型
*/
#include <stdio.h>

int main()
{
	int a = 0x1234;
	int *p = &a;
	char *c = &a;
	
	printf("p = %p\n",p);
	printf("c = %p\n",c);
	
	printf("a = 0x%x\n",*p);
	printf("c = 0x%x\n",*c);
	
	printf("++p = %p\n",++p);
	printf("++c = %p\n",++c);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>a.exe
p = 000000000061FE0C
c = 000000000061FE0C
a = 0x1234
c = 0x34
++p = 000000000061FE10
++c = 000000000061FE0D
*/

我们定义了两个不同类型的指针变量,都用来保存变量a的地址。

  • 我们看到虽然两个不同类型的指针变量都可以访问的到变量a的地址,但是取值的时候出现了问题:取值运算符会根据指针变量的类型,访问不同大小的内存空间,所以我们看到两个指针变量只有int型的可以正确的访问,因为int型在内存中占了32bit,而char型只占了8bit。

  • 虽然指针变量是用来保存别人地址的变量,但是讲究类型匹配,它决定指向空间的大小,也决定你的增量!

1.4 为什么要用指针变量:

1.4.1 为什么要用指针之场景一:

/*
	封装一个函数实现两个数的交换:
*/
#include <stdio.h>

int main()
{
	int data1 = 10;
	int data2 = 20;
	int tmp;
	
	printf("交换前:data1 = %d,data2 = %d\n",data1,data2);
	
	tmp = data1;
	data1 = data2;
	data2 = tmp;
	printf("交换后:data1 = %d,data2 = %d\n",data1,data2);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
交换前:data1 = 10,data2 = 20
交换后:data1 = 20,data2 = 10
*/

第一种可以在main函数中进行定义变量,然后交换打印,两个数据是可以交换的,但是并不符合我们的题目要求。

/*
	封装一个函数实现两个数的交换:
*/
#include <stdio.h>

void chageData(int data1, int data2)
{
	int tmp;
	
	tmp = data1;
	data1 = data2;
	data2 = tmp;
}

int main()
{
	int data1 = 10;
	int data2 = 20;
	
	printf("交换前:data1 = %d,data2 = %d\n",data1,data2);
	chageData(data1,data2);
	printf("交换后:data1 = %d,data2 = %d\n",data1,data2);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
交换前:data1 = 10,data2 = 20
交换后:data1 = 10,data2 = 20
*/

第二中虽然是封装了一个chageData函数进行交换,但是在main函数中调用chageData时把data1和data2两个变量传递到chageData函数形参变量中了,但是这里发生的只是值拷贝,因为chageData函数中的data1,data2和main函数中的data1,data2是完全不同的内存空间,而两个数交换是在chageData函数中进行的,打印交换后的结果还是main函数中data1和data2两个变量的值,所以这里交换了个寂寞。

/*
	封装一个函数实现两个数的交换:
*/
#include <stdio.h>

void chageData(int *pdata1, int *pdata2)
{
	int tmp;
	
	tmp = *pdata1;
	*pdata1 = *pdata2;
	*pdata2 = tmp;
}

int main()
{
	int data1 = 10;
	int data2 = 20;
	
	printf("交换前:data1 = %d,data2 = %d\n",data1,data2);
	chageData(&data1,&data2);
	printf("交换后:data1 = %d,data2 = %d\n",data1,data2);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
交换前:data1 = 10,data2 = 20
交换后:data1 = 20,data2 = 10
*/

第三种是把data1和data2变量的地址给到chageData函数形参中pdata1和pdata2这两个指针变量中了,因为main函数中传递的是地址,能够接收地址的只有指针了,所以这里定义了两个指针变量,在chageData函数中交换的数存放的地址其实就是main函数中data1和data2两个变量的地址,这就是指针的微妙之处!

1.2.2 为什么要用指针之场景二:

/*
	定义一个指针指向固定的内存空间:
*/
#include <stdio.h>

int main()
{
	int a = 10;
	
	printf("%p\n",&a);
	volatile unsigned int *p = (volatile unsigned int *)0x000000000061FE33;
	printf("p = %p\n",p);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
000000000061FE14
p = 000000000061FE33
*/
  • 定义了一个变量a,它的内存地址是0x000000000061FE14

  • 可以通过指针来指向固定的内存地址,以后在做单片机或者ARM Bootloader的时候会用到,比如指向一个寄存器的地址。

  • 定义了一个防止被编译器优化的无符号整型指针变量 *p,

  • 这里的volatile是防止被编译器优化,指针变量*p指向了一个内存地址0x000000000061FE33,因为它是一个整型数,所以需要强制类型转换成指针类型(volatile unsigned int *)

1.5 关于指针的引入作业布置:

输入三个数a,b,c; 要求不管怎么输入,在输出的时候,a,b,c就是由大到小的顺序输出,用函数封装实现

#include <stdio.h>

void getData(int *pdata1, int *pdata2, int *pdata3)
{
	int tmp;
	
	if(*pdata1 < *pdata2){
		tmp = *pdata1;
		*pdata1 = *pdata2;
		*pdata2 = tmp;
	}
	if(*pdata1 < *pdata3){
		tmp = *pdata1;
		*pdata1 = *pdata3;
		*pdata3 = tmp;
	}
	if(*pdata2 < *pdata3){
		tmp = *pdata2;
		*pdata2 = *pdata3;
		*pdata3 = tmp;
	}
}

int main()
{
	int a;
	int b;
	int c;
	
	puts("请输入三个数:");
	scanf("%d%d%d",&a,&b,&c);
	getData(&a,&b,&c);
	printf("你输入的三个数从大到小是:a = %d,b = %d, c = %d\n",a,b,c);
	return 0;
}
/*
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入三个数:
10
20
30
你输入的三个数从大到小是:a = 30,b = 20, c = 10
*/

二、通过指针引用数组

2.1 通过指针变量引入数组:

/*
    可以用一个指针变量指向一个数组元素:
*/
    int arr[3] = {1,2,3};    //定义arr为包含3个整形数据的数组
    int *p;                    //定义p为指向整形数据的指针变量
    p = &arr[0];            //把arr[0]元素的地址赋值给p
/*
	定义一个指针变量指向一个数组元素:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = &arr[0];
	printf("数组的首元素是:%d\n",*p);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
数组的首元素是:1
*/
  • 在C语言中数组名代表数组中首元素(即序号为0的元素)的地址(不包括形参数组名,形参数组并不占据实际的内存单元)

/*下面两种都可以代表数组的首地址,使用哪一种都可以*/
int arr[3] = {1,2,3};
int *p;

p = &arr[0];	//数组中首元素的地址代表数组的首地址
p = arr;		//数组名代表数组的首地址

2.2 指针增量和数组的关系:

#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("address:0x%p,data:%d\n",(p+i),*(p+i));
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
address:0x000000000061FE04,data:1
address:0x000000000061FE08,data:2
address:0x000000000061FE0C,data:3
*/
  • 指针+1,并不代表指针里面的数+1,而是地址偏移了一个字节的类型数。

2.3 通过指针引用数组元素:

  • 数组遍历有两种方法:一是:下标法,二是:指针法

2.3.1 下标法遍历数组:

/*
	下标法遍历数组,是以前经常用的方法:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	
	for(int i=0; i<3; i++){
		printf("%d ",arr[i]);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/

2.3.2 指针法遍历数组:

/*
	通过指针偏移加取内容的方式遍历数组:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*(p+i));
	}
	
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/

2.3.3 指针法遍历数组见怪不怪:

/*
	通过指针法遍历数组见怪不怪第一种:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*p++);	//这边涉及到了一个优先级的问题,*和p先结合,然后指针在偏移
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/

/*
	通过指针法遍历数组见怪不怪第二种:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*p);
		p++;
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/

2.3.4 当程序中需要多次调用指针循环遍历数组时要注意:

/*
	当程序中需要多次调用循环遍历数组时会发生什么?
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3 0 3 6422044
*/
  • 当程序重复调用指针循环遍历数组时就会出错,会导致数组越界,那这个问题该怎么解决呢?

/*
	在程序中如果想多次调用,那么在每次调用前让指针指向数组的首地址:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	p = arr;					//指针指向数组的首地址
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3 1 2 3
*/

 指针指向数组的首地址有两种表示方式:

/*第一种:数组首元素的地址就是数组的首地址*/
int arr[3] = {1,2,3};
int *p;
p = &arr[0];	//将数组首元素的地址赋值给p
/*第二种:数组名就是数组的首地址*/
int arr[3] = {1,2,3};
int *p;
p = arr;	//数组名也表示数组的首地址

2.4 指针和数组名见怪不怪,重要面试:

2.4.1 指针当作数组名,下标法访问:

#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",p[i]);	//指针当作数组名下标法访问见怪不怪
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/

2.4.2 数组名拿来+:

#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	printf("数组的首元素:%d\n",*arr);
	for(int i=0; i<3; i++){
		printf("%d ",*(arr+i));	//数组名拿来+见怪不怪
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
数组的首元素:1
1 2 3
*/

2.5 数组名和指针的区别:

2.5.1 数组名++可不可行:

/*
	我们知道,指针拿来++是没有问题的:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
*/
/*
	既然指针拿来++是没有问题的,那么数组名拿来++会怎样?
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	for(int i=0; i<3; i++){
		printf("%d ",*arr++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
1 2 3
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c
demo_pointFuXi.c: In function 'main':
demo_pointFuXi.c:10:20: error: lvalue required as increment operand
   printf("%d ",*arr++);

*/
  • 我们可以看到这里有一个error错误,原因是:数组名arr是不能拿来++的,因为数组名是常量指针,常量指针不允许++,而p是指针变量可以++

2.5.2 在sizeof的时候数组名和指针的区别:

/*
	sizeof数组名和sizeof指针的区别:
*/
#include <stdio.h>

int main()
{
	int arr[3] = {1,2,3};
	int *p;
	
	p = arr;
	printf("sizeof arr is:%d\n",sizeof(arr));
	printf("sizeof p is:%d\n",sizeof(p));
	printf("sizeof int is:%d\n",sizeof(int));
	printf("sizeof pointer is:%d\n",sizeof(int *));
	printf("sizeof pointer is:%d\n",sizeof(char *));
	for(int i=0; i<3; i++){
		printf("%d ",*p++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
sizeof arr is:12
sizeof p is:8
sizeof int is:4
sizeof pointer is:8
sizeof pointer is:8
1 2 3
*/

数组名和指针在sizeof的时候不一样:

  • sizeof数组名的时候是计算一个数组的大小,因为数组中有3个整形元素,一个元素占了4个字节,所以sizeof数组名的时候是12个字节。

  • 而sizeof指针的时候是8个字节,因为操作系统用8个字节来表示一个内存地址,不管这个内存地址是什么类型的

2.6 函数指针数组结合应用:

2.6.1 用函数封装形式实现数组的初始化和遍历:

#include <stdio.h>

void initArray(int *parray, int size)
{
	int i;
	
	for(i=0; i<size; i++){
		printf("请输入第%d个元素的数据:\n",i+1);
		scanf("%d",parray++);
	}
	puts("数组初始化完毕!");
}

void printArray(int *parray, int size)
{
	int i;
	
	for(i=0; i<size; i++){
		printf("%d ",*parray++);
	}
	puts("\ndone");
}

int main()
{
	int arr[5];
	int size = sizeof(arr) / sizeof(arr[0]);
	
	initArray(arr,size);
	printArray(arr,size);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入第1个元素的数据:
11
请输入第2个元素的数据:
22
请输入第3个元素的数据:
33
请输入第4个元素的数据:
44
请输入第5个元素的数据:
55
数组初始化完毕!
11 22 33 44 55
done
*/

2.6.2 函数指针数组实现将数组中的n个元素按逆序输出:

#include <stdio.h>

void initArray(int *parray, int size)
{
	int i;
	
	for(i=0; i<size; i++){
		printf("请输入第%d个元素的数据:\n",i+1);
		scanf("%d",parray++);
	}
	puts("数组初始化完毕!");
}

void printArray(int *parray, int size)
{
	int i;
	
	for(i=0; i<size; i++){
		printf("%d ",*parray);
		parray++;
	}
}

void reverseArray(int *parray, int size)
{
	int i;
	int j;
	int tmp;
	
	for(i=0; i<size/2; i++){
		j = size-1-i;
		tmp = *(parray+i);
		*(parray+i) = *(parray+j);
		*(parray+j) = tmp;
	}
}

int main()
{
	int arr[5];
	int size = sizeof(arr) / sizeof(arr[0]);
	
	initArray(arr,size);
	printf("数组正序输出是:\n");
	printArray(arr,size);
	reverseArray(arr,size);
	printf("\n数组逆序输出是:\n");
	printArray(arr,size);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入第1个元素的数据:
11
请输入第2个元素的数据:
22
请输入第3个元素的数据:
33
请输入第4个元素的数据:
44
请输入第5个元素的数据:
55
数组初始化完毕!
数组正序输出是:
11 22 33 44 55
数组逆序输出是:
55 44 33 22 11
*/

三、指针和二维数组

3.1 二维数组地址认知:

  • 二维数组中上官特有说法:父子数组;

  • 二维数组本质还是数组,不同点是数组中的每一个元素还是一个数组(子数组)

int a[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
  • 我们知道数组名代表数组的地址

  • 父数组 == 行数组,数组名是:a,那么a就代表数组的第0行地址,也就是父数组的地址。

  • 子数组 == 列数组,数组名是:a[0],那么a[0]就代表数组的第0列地址,是子数组地址。

/*
	我们知道数组的首地址有两种表示方法:1是:数组名、2是:数组首元素的地址,所以下面两种表示数组的首地址是完全等价的,下次见到不要害怕😰:	
*/
a[0] == &a[0][0];		//数组中第0行第0列的地址
a[1] == &a[1][0];		//数组中第1行第0列的地址
a[2] == &a[2][0];		//数组中第2行第0列的地址

    思考一个问题:a是谁的地址,a[0]又是什么,那*a或者*(a+0)呢?
        a是谁的地址?
            答:a表示父数组的地址,也是行数组的地址    
        a[0]又是什么?
            答:a[0]表示子数组的地址,也就是列数组的地址
        *a或者*(a+0)又是什么呢?
            答:*a和*(a+0)都是代表子数组的地址,也就是列数组的地址,
            *a等价于*(a+0),同时等价于:a[0]

/*
	a[0]+1是第0行第1列的地址,是地址的意思,等价于 == *(a+0)+1,也可以说是:第0个子数组的第1个元素的地址,而第0个子数组的第1个元素的表示方式是:a[0][1],不要乱!
*/
&a[0][1] == a[0]+1 == *(a+0)+1	//第0行第1列的地址
&a[1][1] == a[1]+1 == *(a+1)+1	//第1行第1列的地址
&a[2][1] == a[2]+1 == *(a+2)+1	//第2行第1列的地址
/*以上三种是完全等价的,没啥区别,不要乱!*/

3.2 通过编程认证认知:

#include <stdio.h>

int main()
{
	int a[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	
	printf("a是父地址(行地址):%p,偏移1后是:%p\n",a,a+1);
	printf("a[0]是子地址(列地址):%p,偏移1后是:%p\n",a[0],a[0]+1);
	printf("a[0]是子地址(列地址):%p,偏移1后是:%p\n",*(a+0),*(a+0)+1);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
a是父地址(行地址):000000000061FDF0,偏移1后是:000000000061FE00
a[0]是子地址(列地址):000000000061FDF0,偏移1后是:000000000061FDF4
a[0]是子地址(列地址):000000000061FDF0,偏移1后是:000000000061FDF4
*/
  • 我们可以看到a是二维数组名,C语言中数组名代表数组的首地址,a代表数组的行地址,a+1,偏移了16个字节,a[0]也是一个数组名,代表一个一维数组名,是面向与0行0列的地址,a[0]+1偏移了4个字节。

3.3 关于二维数组中的值表示方式树立认知:

/*
	关于二维数组的循环遍历输出的几种写法:
*/
#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int i;
	int j;
	
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("address:0x%p, data:%d\n",&arr[i][j],arr[i][j]);
			printf("address:0x%p, data:%d\n",arr[i]+j,*(arr[i]+j));
			printf("address:0x%p, data:%d\n",*(arr+i)+j,*(*(arr+i)+j));
			printf("==========================================\n");
		}
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
address:0x000000000061FDE0, data:11
address:0x000000000061FDE0, data:11
address:0x000000000061FDE0, data:11
==========================================
address:0x000000000061FDE4, data:22
address:0x000000000061FDE4, data:22
address:0x000000000061FDE4, data:22
==========================================
address:0x000000000061FDE8, data:33
address:0x000000000061FDE8, data:33
address:0x000000000061FDE8, data:33
==========================================
address:0x000000000061FDEC, data:44
address:0x000000000061FDEC, data:44
address:0x000000000061FDEC, data:44
==========================================
address:0x000000000061FDF0, data:55
address:0x000000000061FDF0, data:55
address:0x000000000061FDF0, data:55
==========================================
address:0x000000000061FDF4, data:66
address:0x000000000061FDF4, data:66
address:0x000000000061FDF4, data:66
==========================================
address:0x000000000061FDF8, data:77
address:0x000000000061FDF8, data:77
address:0x000000000061FDF8, data:77
==========================================
address:0x000000000061FDFC, data:88
address:0x000000000061FDFC, data:88
address:0x000000000061FDFC, data:88
==========================================
address:0x000000000061FE00, data:99
address:0x000000000061FE00, data:99
address:0x000000000061FE00, data:99
==========================================
address:0x000000000061FE04, data:12
address:0x000000000061FE04, data:12
address:0x000000000061FE04, data:12
==========================================
address:0x000000000061FE08, data:34
address:0x000000000061FE08, data:34
address:0x000000000061FE08, data:34
==========================================
address:0x000000000061FE0C, data:56
address:0x000000000061FE0C, data:56
address:0x000000000061FE0C, data:56
==========================================
*/
/*
	二维数组的值表示方式,以下几种是完全等价的
*/
arr[0][1] == *(arr[0]+1) == *(*(arr+0)+1)	//第0行第1列的值
arr[1][1] == *(arr[1]+1) == *(*(arr+1)+1)	//第1行第1列的值
arr[2][1] == *(arr[2]+1) == *(*(arr+2)+1)	//第2行第1列的值
arr[i][j] == *(arr[i]+j) == *(*(arr+i)+j)	//第i行第j列的值

3.4 关于二维数组和指针的嵌入式软件笔试题:

  • 嵌入式软件开发中小型公司笔试考题

四、数组指针

4.1 数组指针认知:

4.1.1 定义一个指针指向二维数组:

#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int i;
	int j;
	int *p = &arr[0][0];
	
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("address:0x%p,data:%d\n",arr[i]+j,*p++);
		}
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
address:0x000000000061FDE0,data:11
address:0x000000000061FDE4,data:22
address:0x000000000061FDE8,data:33
address:0x000000000061FDEC,data:44
address:0x000000000061FDF0,data:55
address:0x000000000061FDF4,data:66
address:0x000000000061FDF8,data:77
address:0x000000000061FDFC,data:88
address:0x000000000061FE00,data:99
address:0x000000000061FE04,data:12
address:0x000000000061FE08,data:34
address:0x000000000061FE0C,data:56
*/
  • 我们知道二维数组中的每一个元素的地址是连续的,之前我们在一维数组中定义了一个指针,指向了数组中第0个元素的首地址,通过下标法或者指针法能够遍历数组。

  • 在这里我们定义了一个指针变量p来保存二维数组中第0行第0列元素的地址,通过指针偏移加取内容的方式能够把二维数组里面所有的数据打印出来,是没有问题的。

  • 我们知道能够代表数组的首地址有两种方式:第一种是取数组首元素的地址,另一种就是数组名代表数组的首地址,那我们现在把p指向数组名arr可以吗?

#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int i;
	int j;
	int *p = arr;
	
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("%d ",*p++);
		}
	}
	return 0;
}
/*
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c
demo_pointFuXi.c: In function 'main':
demo_pointFuXi.c:8:11: warning: initialization of 'int *' from incompatible pointer type 'int (*)[4]' [-Wincompatible-pointer-types]
  int *p = arr;
           ^~~

E:\code\一阶段C语言\第六章_指针>a.exe
11 22 33 44 55 66 77 88 99 12 34 56
*/
  • 我们看到这里出现了一个警告⚠,它提示这里的int *类型并不对,因为arr是二维数组名,它代表的是一个一维数组的地址,它偏移的跨度是这个数组。

4.1.2 定义一个数组指针指向二维数组:

那我能不能定义一个指针,让指针偏移的时候也偏移对应大小的数组?

数组指针,定义一个指针,指向一个数组

/*
	定义一个数组指针,指向二维数组
*/
#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int i;
	int j;
	int (*p)[4];
	
	p = arr;
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("address:0x%p, data:%d\n",*(p+i)+j,*(*(p+i)+j));
		}
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
address:0x000000000061FDE0, data:11
address:0x000000000061FDE4, data:22
address:0x000000000061FDE8, data:33
address:0x000000000061FDEC, data:44
address:0x000000000061FDF0, data:55
address:0x000000000061FDF4, data:66
address:0x000000000061FDF8, data:77
address:0x000000000061FDFC, data:88
address:0x000000000061FE00, data:99
address:0x000000000061FE04, data:12
address:0x000000000061FE08, data:34
address:0x000000000061FE0C, data:56
*/

我们编译一看,瞬间没有了警告⚠,结果也是✔的

核心观点:数组指针才是真正等同于二维数组

4.1.3 数组指针偏移的时候偏移了多少个字节:

#include <stdio.h>

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int (*p)[4];
	
	p = arr;
	printf("p = %p\n",p);
	printf("++p = %p\n",++p);	
	return 0;
}
/*
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
p = 000000000061FDE0
++p = 000000000061FDF0
*/
  • 我们可以看到,数组指针偏移的时候正好偏移了整个数组,偏移了16个字节,所以数组指针才是真正等同于二维数组的指针!

4.2 数组指针和二维数组配合应用:

  • 有一个二维数组,根据用户输入的行列数,输出对应的值

#include <stdio.h>

void tipsInputHangLie(int *pm, int *pn)
{
	puts("请输入行列值:");
	scanf("%d%d",pm,pn);
	puts("输入完毕!");
}

int getTheData(int (*p)[4], int hang, int lie)
{
	int data;
	
	data = *(*(p+hang)+lie);
	return data;
}

int main()
{
	int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,12,34,56}};
	int ihang;
	int ilie;
	int data;
	
	tipsInputHangLie(&ihang,&ilie);
	data = getTheData(arr,ihang,ilie);
	printf("第%d行第%d列的元素是:%d\n",ihang,ilie,data);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入行列值:
0
0
输入完毕!
第0行第0列的元素是:11

E:\code\一阶段C语言\第六章_指针>a.exe
请输入行列值:
0
1
输入完毕!
第0行第1列的元素是:22

E:\code\一阶段C语言\第六章_指针>a.exe
请输入行列值:
1
0
输入完毕!
第1行第0列的元素是:55

E:\code\一阶段C语言\第六章_指针>a.exe
请输入行列值:
2
3
输入完毕!
第2行第3列的元素是:56
*/
  • 同样案例,多写代码一点实现,使其体验感更好:

#include <stdio.h>

void initDoubleArray(int (*p)[4])
{
	int i;
	int j;
	
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("请输入第%d行第%d列元素的数据:\n",i+1,j+1);
			scanf("%d",*(p+i)+j);
		}
	}
	puts("数组初始化完毕!");
}

void printDoubleArray(int (*p)[4])
{
	int i;
	int j;
	
	for(i=0; i<3; i++){
		for(j=0; j<4; j++){
			printf("%d\t",*(*(p+i)+j));
		}
		putchar('\n');
	}
}

void tipsInputHangLie(int *pm, int *pn)
{
	printf("请输入行列值:\n");
	scanf("%d%d",pm,pn);
	puts("输入完毕!");
}

int getTheData(int (*p)[4], int hang, int lie)
{
	int data;
	
	data = *(*(p+hang)+lie);
	return data;
}

int main()
{
	int arr[3][4];
	int ihang;
	int ilie;
	int data;
	
	initDoubleArray(arr);
	printDoubleArray(arr);
	tipsInputHangLie(&ihang,&ilie);
	data = getTheData(arr,ihang,ilie);
	printf("第%d行第%d列的值是:%d\n",ihang,ilie,data);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入第1行第1列元素的数据:
11
请输入第1行第2列元素的数据:
22
请输入第1行第3列元素的数据:
33
请输入第1行第4列元素的数据:
44
请输入第2行第1列元素的数据:
55
请输入第2行第2列元素的数据:
66
请输入第2行第3列元素的数据:
77
请输入第2行第4列元素的数据:
88
请输入第3行第1列元素的数据:
99
请输入第3行第2列元素的数据:
12
请输入第3行第3列元素的数据:
34
请输入第3行第4列元素的数据:
56
数组初始化完毕!
11      22      33      44
55      66      77      88
99      12      34      56
请输入行列值:
1
2
输入完毕!
第1行第2列的值是:77
*/

五、函数指针

5.1 函数地址的定义:

  • 如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针

  • 那这里我要说:函数名就是地址——像之前我们说的:数组名就是地址,是一样的。

5.2 如何定义一个函数指针变量:

  • 函数指针变量和普通的变量其实是一样的:

int a = 10;
int *p = &a;

char c = '#';
char *p = &c;

int getData(int a, int b);			//假设程序中有这么一个函数
int (*p)(int a, int b) = getData;	//定义一个函数指针变量保存函数的地址
/*
	1.那这里的*p为什么需要用括号括起来呢?
		其实这里涉及到了一个优先级的问题:括号的优先级是高于*的,所以我们把(*p)用括号括起来,让*p先结		合,代表是一个指针。
		
	2.那如果*p不用括号括起来会怎样?
		如果*p不用括号括起来会成为什么:
		int* p(int a, int b);
		这样的话是错了,p现在变成了一个函数,函数的返回值是一个int *型,返回值是一个地址,也就是我们后面		要学的“指针函数”。
*/
  • 可以看到:函数指针变量和普通的整形和字符型的指针变量是一样的。

5.3 使用函数指针:

函数调用的概念和变量是一样的,都是由这两种方式:

  • 直接访问:变量名(函数名)

  • 间接访问:指针(函数指针)

/*
	变量的两种访问方式:1.变量名直接访问 2.指针间接访问
*/
#include <stdio.h>

int main()
{
    int a = 10;
    int *p = &a;
    
    printf("通过变量名直接访问:a = %d\n",a);
    printf("通过指针间接访问:a = %d\n",*p);
    return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
通过变量名直接访问:a = 10
通过指针间接访问:a = 10

*/
/*
	无参数列表、无返回值函数的两种访问方式:1.函数名直接访问 2.指针间接访问
*/
#include <stdio.h>

void printWelcome()
{
	puts("程序启动,欢迎使用!");
}

int main()
{
	void (*p)();		//定义一个函数指针变量
	
	p = printWelcome;	//指向函数
	
	printWelcome();		//函数名直接访问
	(*p)();				//函数调用
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
程序启动,欢迎使用!
程序启动,欢迎使用!

*/
/*
	一个返回值、一个参数列表函数的两种访问方式:1.函数名直接访问 2.指针间接访问
*/
#include <stdio.h>

int inCData(int data)
{
	return ++data;
}

int main()
{
	int ret;
	int (*p)(int data);		//定义一个函数指针变量
	
	p = inCData;			//指向函数
	ret = (*p)(10);			//函数调用
	printf("ret = %d\n",ret);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
ret = 11

*/
/*
	函数指针变量的定义和调用过程
*/
#include <stdio.h>

void printWelcome()
{
	puts("程序启动,欢迎使用!");
}

int inCData(int data)
{
	return ++data;
}

int main()
{
	void (*p)();			//定义一个函数指针变量
	int (*p2)(int data);
	
	p = printWelcome;		//指向函数
	p2 = inCData;
	
	(*p)();					//函数调用
	printf("ret = %d\n",(*p2)(10));
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
程序启动,欢迎使用!
ret = 11
*/

5.4 函数指针的好用之处:

  • 根据程序运行的不同情况,调用不同的函数——这个有点像Java的接口

接下来我们就做一个练习题来感受一下函数指针的好用之处:

有两个整数data 1和data 2,由用户输入1,2,3。输入1程序就给出data 1和data 2之间的大者,输入2程序就给出data 1和data 2的小者,输入3就给出两个数的和,用函数指针实现。

  • 如果这道题我们不是使用函数指针的话,是不是很简单:

/*
	函数名直接访问方式:
*/
#include <stdio.h>
#include <stdlib.h>

int getMax(int data1, int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1, int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1, int data2)
{
	return data1 + data2;
}

int main()
{
	int data1 = 10;
	int data2 = 20;
	int cmd;
	int ret;
	
	puts("请输入一个数:1(大数),2(小数),3(和)");
	scanf("%d",&cmd);
	switch(cmd){
		case 1:
			ret = getMax(data1, data2);
			break;
		case 2:
			ret = getMin(data1, data2);
			break;
		case 3:
			ret = getSum(data1, data2);
			break;
		default:
			printf("输入错误,请重新输入!\n");
			exit(-1);
			break;
	}
	printf("ret = %d\n",ret);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入一个数:1(大数),2(小数),3(和)
1
ret = 20

E:\code\一阶段C语言\第六章_指针>a.exe
请输入一个数:1(大数),2(小数),3(和)
2
ret = 10

E:\code\一阶段C语言\第六章_指针>a.exe
请输入一个数:1(大数),2(小数),3(和)
3
ret = 30

E:\code\一阶段C语言\第六章_指针>a.exe
请输入一个数:1(大数),2(小数),3(和)
0
输入错误,请重新输入!
*/
  • 使用函数指针方式来解决这道题:

/*
	首先用户输入data1和data2的值,然后根据用户输入的指令用函数指针方式间接访问不同的函数
*/
#include <stdio.h>
#include <stdlib.h>

void tipsInputTwoData(int *pdata1, int *pdata2, int *pcmd)
{
	puts("请输入两个数:");
	scanf("%d%d",pdata1,pdata2);
	printf("输入完毕!你输入这两个数的结果是:%d,%d\n",*pdata1,*pdata2);
	
	puts("请输入一个数:1(大数),2(小数),3(和)");
	scanf("%d",pcmd);
	puts("输入OK!");
}

int getMax(int data1, int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1, int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1, int data2)
{
	return data1 + data2;
}

int dataHandler(int (*pfunc)(int data1, int data2), int data1, int data2)
{
	int ret;
	
	ret = (*pfunc)(data1,data2);
	return ret;
}

int main()
{
    //定义一个函数指针变量
	void (*pInputData)(int *pdata1, int *pdata2, int *pcmd) = tipsInputTwoData;
	int (*pfunc)(int data1, int data2);
	int data1;
	int data2;
	int cmd;
	int ret;
	
	(*pInputData)(&data1, &data2, &cmd);	//调用函数
	switch(cmd){
		case 1:
			pfunc = getMax;					//指向函数
			break;
		case 2:
			pfunc = getMin;					//指向函数
			break;
		case 3:
			pfunc = getSum;					//指向函数
			break;
		default:
			printf("输入错误,请重新输入!\n");
			exit(-1);
			break;
	}
	ret = dataHandler(pfunc, data1, data2);
	printf("ret = %d\n",ret);
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数:
10
20
输入完毕!你输入这两个数的结果是:10,20
请输入一个数:1(大数),2(小数),3(和)
1
输入OK!
ret = 20

E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数:
10
20
输入完毕!你输入这两个数的结果是:10,20
请输入一个数:1(大数),2(小数),3(和)
2
输入OK!
ret = 10

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数:
10
20
输入完毕!你输入这两个数的结果是:10,20
请输入一个数:1(大数),2(小数),3(和)
3
输入OK!
ret = 30

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数:
10
20
输入完毕!你输入这两个数的结果是:10,20
请输入一个数:1(大数),2(小数),3(和)
4
输入OK!
*/
  • 同样案例,全部所有业务全部封装成函数,并通过函数指针访问

#include <stdio.h>
#include <stdlib.h>

void tipsInputData(int *pdata1, int *pdata2, int *pcmd)
{
	puts("请输入两个数据的值:");
	scanf("%d%d",pdata1,pdata2);
	puts("请输入你的指令:1,2,3");
	scanf("%d",pcmd);
}

int getMax(int data1, int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1, int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1, int data2)
{
	return data1 + data2;
}

int dataHandler(int (*pfunc)(int , int ), int data1, int data2)
{
	int ret;
	
	ret = (*pfunc)(data1,data2);
	return ret;
}

void getSwitchPfunc(int cmd, int (**pfunc)(int data1, int data2))
{
	if(*pfunc == NULL){
		switch(cmd){
		case 1:
			*pfunc = getMax;
			break;
		case 2:
			*pfunc = getMin;
			break;
		case 3:
			*pfunc = getSum;
			break;
		default:
			printf("输入错误,请重新输入!\n");
			exit(-1);
			break;
		}
	}
}

int main()
{
    //定义一个函数指针变量
	void (*pInputData)(int *pdata1, int *pdata2, int *pcmd) = tipsInputData;	
	int (*pfunc)(int data1, int data2) = NULL;
	void (*pSwitchPfunc)(int cmd, int (**pfunc)(int data1, int data2)) = getSwitchPfunc;
	int (*pdataHandler)(int (*pfunc)(int , int ), int data1, int data2) = dataHandler;
	int data1;
	int data2;
	int cmd;
	
	(*pInputData)(&data1,&data2,&cmd);	//函数调用
	(*pSwitchPfunc)(cmd,&pfunc);
	printf("ret = %d\n",(*pdataHandler)(pfunc,data1,data2));
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi3.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数据的值:
10
20
请输入你的指令:1,2,3
1
ret = 20

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数据的值:
10
20
请输入你的指令:1,2,3
2
ret = 10

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数据的值:
10
20
请输入你的指令:1,2,3
3
ret = 30
*/

5.5 回调函数的底层逻辑:

//线程
int pthread_create(pthread_t *id, const pthread_attr_t *attr,   void*(*start_rtn)(void*), void *restrict arg);
  • 在QT的信号与槽当中也会用到

六、指针数组

6.1 指针数组的定义:

  • 注意和数组指针的区别:面试的笔试题会考

/*
	定义一个指针数组,指针数组中的每一个元素都是一个指针变量,每一个指针变量都指向了一个整型数
*/
#include <stdio.h>

int main()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	
	int* p[4] = {&a,&b,&c,&d};
	
	for(int i=0; i<4; i++){
		printf("%d ",*(p[i]));
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi2.c

E:\code\一阶段C语言\第六章_指针>a.exe
10 20 30 40
*/

6.2 函数指针数组案例:

  • 我们在函数指针里面做过,输入不同数据输出两个数的大数、小数和和,这样太啰嗦了,成年人不喜欢做选择,那怎么办呢?使用函数数组指针把两个数的大数、小数和和同时都打印出来:

/*
	使用函数指针数组的方式来做这道题
*/
#include <stdio.h>

int getMax(int data1, int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1, int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1, int data2)
{
	return data1 + data2;
}

int main()
{
	int data1 = 10;
	int data2 = 20;
	int ret;
	int (*pfunc[3])(int data1, int data2) = {	//定义一个函数指针数组
		getMax,
		getMin,
		getSum
	};
	for(int i=0; i<3; i++){
		ret = (*pfunc[i])(data1,data2);			//调用函数
		printf("ret = %d\n",ret);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi2.c

E:\code\一阶段C语言\第六章_指针>a.exe
ret = 20
ret = 10
ret = 30
*/
/*
	同样案例多写点代码实现:
*/
#include <stdio.h>

void tipsInputTwoData(int *pdata1, int *pdata2)
{
	puts("请输入两个数:");
	scanf("%d%d",pdata1,pdata2);
	puts("输入完毕!");
}

int getMax(int data1, int data2)
{
	return data1>data2 ? data1:data2;
}

int getMin(int data1, int data2)
{
	return data1<data2 ? data1:data2;
}

int getSum(int data1, int data2)
{
	return data1 + data2;
}

int main()
{
	void (*pInputData)(int *pdata1, int *pdata2) = tipsInputTwoData;//定义一个函数指针变量
	int (*pfunc[3])(int data1, int data2) = {						//定义一个函数指针数组
		getMax,
		getMin,
		getSum
	};
	int data1;
	int data2;
	int ret;
	
	(*pInputData)(&data1, &data2);		//调用函数
	for(int i=0; i<3; i++){
		ret = (*pfunc[i])(data1, data2);//调用函数
		printf("ret = %d\n",ret);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi2.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入两个数:
10
20
输入完毕!
ret = 20
ret = 10
ret = 30
*/

七、指针函数

7.1 指针函数的定义:

  • 一个函数可以返回一个整形值、字符值、实型值等,也可以返回指针型的数据,也就是地址,其概念和之前类似,只不过函数返回的值得类型是指针类型而已。

int* a(int x, int y);
/*
	a是函数名,调用它之后得到一个int*型(指向整形数)的指针,也就是整形数据的地址,x和y是函数a的形参,是整形的。
	请注意⚠:*a两侧没有括号,在a的两侧分别为*运算符和()运算符,而()的优先级高于*,因此a先与()结合,显然这是函数的形式,这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整形变量。
	指针函数其实就是返回指针值得函数。
*/

7.2 指针函数应用案例:

有a个学生,每个学生有b门课程的成绩,要求用户输入学生得序号之后输出该学生得全部成绩,用指针函数实现。

#include <stdio.h>

int* getPosPerson(int pos, int (*stu)[4])
{
	int *p;
	
	p = (int *)(stu+pos);
	return p;
}

int main()
{
	int scores[3][4] = {
		{78,56,89,92},
		{13,24,45,59},
		{45,36,89,78}
	};
	int pos;
	int *ppos;
	
	puts("请输入你要找的学生号数:");
	scanf("%d",&pos);
	ppos = getPosPerson(pos,scores);
	for(int i=0; i<4; i++){
		printf("%d ",*ppos++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi3.c

E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
0
78 56 89 92
E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
1
13 24 45 59
E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
2
45 36 89 78
E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
3
*/

八、二级指针

8.1 二级指针认知:

  • 我们知道指针变量是用来保存别人的地址的,那么一级指针和多级指针有什么区别呢?

  • 一级指针是用来保存普通变量的地址的,二级指针是用来保存指针变量的地址的,其实所有的东西和一级指针是一样的,都是保存别人地址的变量,只是保存的对象不同。

/*
	一级指针和二级指针从指针变量定义写法上有什么不同:
*/
int *p;		//定义一级指针时写法
int **p;	//定义二级指针时写法
/*
	差别就是二级指针是保存指针变量的地址的,而一级指针是保存普通变量的地址的
*/
/*
	二级指针的应用
*/
#include <stdio.h>

int main()
{
    int data = 10;
	int *p = &data;
	printf("data的地址是:%p\n",&data);
	printf("p保存data的地址是:%p,内容是%d\n",p,*p);
    
    printf("p的地址是:%p\n",&p);
	int **p2 = &p;
	printf("p2保存p的地址是:%p\n",p2);
	printf("*p2是data的地址%p\n",*p2);
	printf("**p2访问data的值是:%d\n",**p2);
    return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc erji_Point.c

E:\code\一阶段C语言\第六章_指针>a.exe
data的地址是:000000000061FE14
p保存data的地址是:000000000061FE14,内容是10
p的地址是:000000000061FE08
p2保存p的地址是:000000000061FE08
*p2是data的地址000000000061FE14
**p2访问data的值是:10	
*/

8.2 为什么要用二级指针实战:

  • 我们接着做前面指针函数的题,前面是使用指针函数来解答的这道题,这次我们看看使用二级指针来做有什么妙处!

  • 有a个学生,每个学生有b门课程的成绩,要求用户输入学生的序号之后输出该学生得全部成绩:

#include <stdio.h>

void getPosPerson(int pos, int (*stu)[4], int **ppos)
{
	*ppos = (int *)(pos+stu);
}

int main()
{
	int scores[3][4] = {
		{11,22,33,44},
		{55,66,77,88},
		{99,12,34,56}
	};
	int pos;
	int *ppos;
	
	puts("请输入你要找的学生号数:");
	scanf("%d",&pos);
	getPosPerson(pos,scores,&ppos);
	for(int i=0; i<4; i++){
		printf("%d ",*ppos++);
	}
	return 0;
}
/*程序运行结果:
E:\code\一阶段C语言\第六章_指针>gcc demo_pointFuXi3.c -g

E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
1
55 66 77 88
E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
0
11 22 33 44
E:\code\一阶段C语言\第六章_指针>a.exe
请输入你要找的学生号数:
2
99 12 34 56
*/

8.3 二级指针和二维数组避坑指南:

  • 二级指针不能简单粗暴的指向二维数组

/*
	二级指针和二维数组虽然从概念上差不多,但是二级指针不能简单粗暴指向二维数组,
	应该先定义一个指针数组来保存二维数组的行地址,然后再用二级指针保存数组指针的地址
*/
#include <stdio.h>

int main()
{
	int scores[3][4] = {
		{11,22,33,44},
		{55,66,77,88},
		{99,12,34,56}
	};
	int (*p)[4] = scores;
	/*
	p = scores;						//二级指针不能简单粗暴指向二维数组
	printf("scores = %p\n",scores);
	printf("p = %p\n",p);
	printf("*p = %p\n",*p);			//*p是一个野指针,不是我们认为的编程列地址
	printf("*scores = %p\n",*scores);
	*/
	
	int **p2 = &p;
	**p2 = 100;
	printf("%d\n",scores[0][0]);	//能改的动,但是一般不这么进行
	return 0;
}

九、指针总结

  • 中小型公司指针面试考题:

#include <stdio.h>

int main()
{
	//1.一个整形数:
	int a;
	
	//2.一个指向整型数的指针:
	int *p;
	
	//3.一个指向指针的指针:
	int **p;
	
	//4.一个有10个整型数的数组:
	int a[10];
	
	//5.一个有10个指针的数组,每个指针数组指向一个整型数:
	int* a[10];
	
	//6.一个指向有10个整型数的数组的指针:
	int (*p)[10];
	
	//7.一个指向指针的指针,被指向的指针指向一个有10个整型数的数组:
	int (**p)[10];
	
	//8.一个指向数组的指针,该数组有10个整型指针:
	int* (*p)[10];
	
	//9.一个指向函数的指针,该函数有一个整型参数并返回一个整型数:
	int (*p)(int a);
	
	//10.一个有10个指针的数组,每个指针指向一个函数,该函数有一个整型参数并返回一个整型数:
	int (*p[10])(int a);
	
	//11.一个函数的指针,指向的函数的类型是有两个整型参数并且返回一个函数指针的函数,返回的函数指针指向
	//有一个整型参数且返回整型数的函数:
	int (*(*p)(int, int))(int );
	
	return 0;
}

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值