使用指针可以分离硬件层驱动程序(GPIO、串口、定时器)和应用层程序,达到程序分离、提高程序可移植性的目的,但是容易出bug。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> int main () { int a; int *p; a = 10 ; p = &a; printf ("//-----------------变量a地址-------------------//\r\n" ); printf ("&a=0x%x\r\n" ,&a); printf ("p=0x%x\r\n" ,p); printf ("&p=0x%x\r\n\r\n" ,&p); printf ("//----------------变量a的值--------------------//\r\n" ); printf ("a=%d\r\n" ,a); printf ("*p=%d\r\n\r\n" ,*p); printf ("//----------通过指针变量改变a的值---------------//\r\n" ); *p = 11 ; printf ("a=%d\r\n" ,a); printf ("*p=%d\r\n\r\n" ,*p); printf ("//-----通过a的内存地址改变a的值(每次的地址不同)---------------//\r\n" ); *(unsigned int *)(0x61fe1c ) = 12 ; printf ("a=%d\r\n" ,a); printf ("*p=%d\r\n\r\n" ,*p); return 0 ; }
或者可以将 *p 理解为一个变量,只有当 *p 和 a 的数据类型相同时,p 中才可以存放 &a。
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ .\Pointer.exe //-----------------变量a地址-------------------// &a=0x61fe1c p=0x61fe1c &p=0x61fe10 //----------------变量a的值--------------------// a=10 *p=10 //----------通过指针变量改变a的值---------------// a=11 *p=11 //-----通过a的内存地址改变a的值(每次的地址不同)---------------// a=12 *p=12
数组与指针
编译器会分配连续地址的内存来存储数组里的元素,如果把数组地址赋值给指针变量,那么就可以通过指针变量读写数组中的元素。
以下有两种方法,均可将数组buff地址赋值给指针变量p1,p2。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <stdio.h> int main () { int buff[5 ] = {1 ,2 ,3 ,4 ,5 }; int *p1,*p2; p1 = buff; p2 = &buff[0 ]; printf ("buff = 0x%x\r\n" ,buff); printf ("&buff = 0x%x\r\n" ,&buff); printf ("p1_addr = 0x%x\r\n" ,p1); printf ("p2_addr = 0x%x\r\n\r\n" ,p2); printf ("buff[0] = %d\r\n" ,buff[0 ]); printf ("*p1 = %d\r\n" ,*p1); printf ("*p2 = %d\r\n" ,*p2); return 0 ; }
输出结果如下:
1 2 3 4 5 6 7 8 9 $ .\array_and_pointer_define.exe buff = 0x61fdf0 &buff = 0x61fdf0 p1_addr = 0x61fdf0 p2_addr = 0x61fdf0 buff[0] = 1 *p1 = 1 *p2 = 1
指针的自加减运算
指针变量除了可以获取内存地址的值,还可以用来进行加减运算,此时加减的是地址。
指针变量的类型也会影响自加减运算后指针所存储的地址的值。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <stdio.h> int main () { unsigned char buff[5 ] = {1 ,2 ,3 ,4 ,5 }; unsigned char *p; p = &buff[0 ]; printf ("&buff[0] = 0x%x buff[0] = %d\r\n" ,&buff[0 ],buff[0 ]); printf ("&buff[1] = 0x%x buff[1] = %d\r\n" ,&buff[1 ],buff[1 ]); printf ("&buff[2] = 0x%x buff[2] = %d\r\n" ,&buff[2 ],buff[2 ]); printf ("&buff[3] = 0x%x buff[3] = %d\r\n" ,&buff[3 ],buff[3 ]); printf ("&buff[4] = 0x%x buff[4] = %d\r\n\r\n" ,&buff[4 ],buff[4 ]); printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p); return 0 ; }
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 $ .\array_and_pointer_++or--.exe &buff[0] = 0x61fe1b buff[0] = 1 &buff[1] = 0x61fe1c buff[1] = 2 &buff[2] = 0x61fe1d buff[2] = 3 &buff[3] = 0x61fe1e buff[3] = 4 &buff[4] = 0x61fe1f buff[4] = 5 p = 0x61fe1b *p = 1 &p = 0x61fe10 p = 0x61fe1c *p = 2 &p = 0x61fe10 p = 0x61fe1d *p = 3 &p = 0x61fe10 p = 0x61fe1e *p = 4 &p = 0x61fe10 p = 0x61fe1f *p = 5 &p = 0x61fe10
此时p使得指针地址偏移1byte,因为unsigned char类型占据1byte的内存空间;假如指针变量指向值的数据类型为int,p 会使指针地址偏移4byte,因为int类型占据4byte的内存空间。
二维数组与指针
对于二维数组,不能直接以"p = buff"赋值,必须使用以下格式赋值
二维数组与指针的操作也是一样的,都是分配连续地址存储数据。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <stdio.h> int main () { unsigned char buff[2 ][3 ] = {{1 ,2 ,3 },{4 ,5 ,6 }}; unsigned char *p; p = &buff[0 ][0 ]; printf ("&buff[0][0] = 0x%x buff[0][0] = %d\r\n" ,&buff[0 ][0 ],buff[0 ][0 ]); printf ("&buff[0][1] = 0x%x buff[0][1] = %d\r\n" ,&buff[0 ][1 ],buff[0 ][1 ]); printf ("&buff[0][2] = 0x%x buff[0][2] = %d\r\n" ,&buff[0 ][2 ],buff[0 ][2 ]); printf ("&buff[1][0] = 0x%x buff[1][0] = %d\r\n" ,&buff[1 ][0 ],buff[1 ][0 ]); printf ("&buff[1][1] = 0x%x buff[1][1] = %d\r\n" ,&buff[1 ][1 ],buff[1 ][1 ]); printf ("&buff[1][2] = 0x%x buff[1][2] = %d\r\n\r\n" ,&buff[1 ][2 ],buff[1 ][2 ]); printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p);p++; printf ("p = 0x%x *p = %d &p = 0x%x\r\n" ,p,*p,&p); return 0 ; }
输出结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 $ .\two-dimensionalArrayPointer.exe &buff[0][0] = 0x61fe1a buff[0][0] = 1 &buff[0][1] = 0x61fe1b buff[0][1] = 2 &buff[0][2] = 0x61fe1c buff[0][2] = 3 &buff[1][0] = 0x61fe1d buff[1][0] = 4 &buff[1][1] = 0x61fe1e buff[1][1] = 5 &buff[1][2] = 0x61fe1f buff[1][2] = 6 p = 0x61fe1a *p = 1 &p = 0x61fe10 p = 0x61fe1b *p = 2 &p = 0x61fe10 p = 0x61fe1c *p = 3 &p = 0x61fe10 p = 0x61fe1d *p = 4 &p = 0x61fe10 p = 0x61fe1e *p = 5 &p = 0x61fe10 p = 0x61fe1f *p = 6 &p = 0x61fe10
指向指针变量所属地址的指针(双重指针)
指针变量可以指向整型变量或字符型变量,当然也可以指向指针变量的存储地址,简称为双重指针。
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #include <stdio.h> int main () { unsigned char a; unsigned char *p1; unsigned char **p2; a = 10 ; p1 = &a; p2 = &p1; printf ("&a = 0x%x a = %d\r\n" ,&a,a); printf ("&p1 = 0x%x p1 = 0x%x\r\n" ,&p1,p1); printf ("&p2 = 0x%x p2 = 0x%x\r\n" ,&p2,p2); printf ("*p1 = %d\r\n" ,*p1); printf ("*p2 = 0x%x\r\n" ,*p2); printf ("**p1 = %d\r\n" ,**p2); return 0 ; }
输出结果如下:
1 2 3 4 5 6 7 $ .\double_pointer.exe &a = 0x61fe1f a = 10 &p1 = 0x61fe10 p1 = 0x61fe1f &p2 = 0x61fe08 p2 = 0x61fe10 *p1 = 10 *p2 = 0x61fe1f **p1 = 10
指针变量作为函数形参
一般情况下以字符型、整型、数组等作为函数的形参输入,指针也能作为函数的形参输入,主要目的是改变指针指向地址的值,专业术语是通过形参改变实参的值。(起到参数传递的作用)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <stdio.h> unsigned char a;int SetValue (unsigned char *p) { *p = 20 ; return 0 ; } int main () { SetValue (&a); printf ("a = %d\r\n" ,a); return 0 ; }
输出结果如下:
1 2 $ .\pointer_parameter.exe a = 20
函数指针
如果在程序中定义了一个函数,那么在编译时系统就会为这个函数分配一段存储空间,这段存储空间的首地址称为这个函数的地址。在C语言中,函数名代表的就是函数的地址 。既然是地址我们就可以定义指针变量来存放,那么这个指针变量就叫做函数指针变量,简称函数指针。
定义格式:
函数返回值类型(*指针变量名)(形参列表);
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> unsigned char (*func1) (unsigned char ,unsigned char ) ;unsigned char (**func2) (unsigned char ,unsigned char ) ;unsigned char sum (unsigned char v1,unsigned char v2) { return (v1+v2); } int main () { unsigned char a; func2 = &func1; func1 = ∑ a = (*func2)(1 ,2 ); printf ("a = %d\r\n" ,a); return 0 ; }
输出结果如下:
1 2 $ .\function_pointers.exe a = 3
定义了一个指向无符号字符类型函数且有两个形参的函数指针。
在使用的时候,我们将函数的地址赋值给函数指针,然后就可以用函数指针来表示这个函数。
函数指针数组
字符型和整型都可以单独定义,也可以定义数组,同样函数指针也能定义数组。
定义格式:
函数返回值类型(*指针变量名[数组大小])(形参列表);
例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 #include <stdio.h> unsigned char func1 () ;unsigned char func2 () ;unsigned char func3 () ;unsigned char (*func[3 ]) () = {func1,func2,func3};unsigned char func1 () { printf ("func1 running!\r\n" ); return 0 ; } unsigned char func2 () { printf ("func2 running!\r\n" ); return 0 ; } unsigned char func3 () { printf ("func3 running!\r\n" ); return 0 ; } int main () { func[0 ](); func[1 ](); func[2 ](); return 0 ; }
输出结果如下:
1 2 3 4 $ .\array_of_function_Pointers.exe func1 running! func2 running! func3 running!
以上代码块中定义了三个函数和一个函数指针数组,这样就将函数放入到函数指针数组中,想要调用funct1时,不必写出函数,而是直接执行func[3] ();
大大提高了代码的简明性。
函数指针数组控制LED
接下来用一个例子来体现函数指针的优越性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 #include <stdio.h> enum { LED1, LED2, LED3, LED_sum }; unsigned char driveLED1 (unsigned char sta) ;unsigned char driveLED2 (unsigned char sta) ;unsigned char driveLED3 (unsigned char sta) ;unsigned char driveLEDSum (unsigned char sta) ;unsigned char (*driveLED[4 ]) (unsigned char sta) = {driveLED1,driveLED2,driveLED3,driveLEDSum};unsigned char driveLED1 (unsigned char sta) { if (sta) { printf ("LED1 ON!\r\n" ); } else { printf ("LED1 OFF!\r\n" ); } return 0 ; } unsigned char driveLED2 (unsigned char sta) { if (sta) { printf ("LED2 ON!\r\n" ); } else { printf ("LED2 OFF!\r\n" ); } return 0 ; } unsigned char driveLED3 (unsigned char sta) { if (sta) { printf ("LED3 ON!\r\n" ); } else { printf ("LED3 OFF!\r\n" ); } return 0 ; } unsigned char driveLEDSum (unsigned char sta) { if (sta) { printf ("LED all ON!\r\n" ); } else { printf ("LED all OFF!\r\n" ); } return 0 ; } int main () { driveLED[LED1](1 ); driveLED[LED2](0 ); driveLED[LED3](1 ); driveLED[LED_sum](0 ); return 0 ; }
输出结果如下:
1 2 3 4 5 $ .\LEDArrayOfFuncPointers.exe LED1 ON! LED2 OFF! LED3 ON! LED all OFF!
使用函数指针似乎使代码更加复杂,实际上增强了代码的可移植性,降低了调用的难度,所以单片机厂商常常使用这种方法来构建他们的库函数,不然难以称之为一款合格的单片机。
而单片机应用开发者也应当尽量使用这种方法,因为这样不但能使后期维护更加方便,而且还能轻易地移植到别的单片机上,开发者在开发过程中应当形成自己的编码体系,而传统的面向过程编程难以形成完整的体系,无法形成核心竞争力。形成自身编码体系之后,好似以不变应万变,开发速度将有飞跃性提升,因为绝大多数代码都是从前写过的,移植即可。