嵌入式linux之高级c语言专题--指针3
2019-07-13 05:07 发布
生成海报
第 三 部分:嵌入式 linux 高级 c-- 指针3
2.1指针数组与数组指针
2.1.1字面意思来理解指针数组与数组指针
(1) 指针数组 的实质是一个数组,这个数组中存储的内容全部是指针变量。
(2) 数组指针 的实质是一个指针,这个指针指向的是一个数组。
2.1.2、分析指针数组与数组指针的表达式
看变量定义的一般规律:
第一:找核心(如果核心和 *结合,表示核心是指针;如果核心和 [] 结合,表示核心是数组;如果核心和 () 结合,表示核心是函数)。
第二:向外扩展(找完核心,继续向外找其他符号)
题目 1: int *p[5]; int
(*p)[5]; int *(p[5]);
第一题分析 :核心是 p,因为【】的优先级比 * 高,故 p 和【】结合后是一个数组,数组大小为 5 个。再和 * 号结合,则数组中的元素都是指针,指针指向的元素类型是 int 类型的;整个符号是一个指针数组。
第二题分析 :核心是 p, p 先和 * 结合,故 p 是一个指针; p 再和【】结合,则 p 是指向数组(大小为 5 )的指针;数组中存的元素是 int 类型。整个符号是一个数组指针。
第三题分析 :第三题和第一题的解析一样。即 int
*p[5]; 和 int *(p[5]);
是等价的。
2.2.函数指针与 typedef
2.2.1函数指针的实质
(1)函数指针的实质还是指针,还是指针变量。本身占 4 字节(在 32 位系统中,所有的指针都是 4 字节)
(2)函数指针、数组指针、普通指针之间并没有本质区别,区别在于指针指向的东西是个什么玩意。
(3)函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说很关键的就是函数中的第一句代码的地址,这个地址就是所谓的函数地址,在 C 语言中用函数名这个符号来表示。
综上,结合函数的实质,函数指针其实就是一个普通变量,这个普通变量的类型是函数指针变量类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值)
2.2.2函数指针的书写和分析方法
C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查。
题目 1:数组指针的定义和使
数组是 int a[10];对应的数组指针: int
(*变量名 )[10] ;类型是: int (*)[10];
#include
int main(void)
{
int a[10];
int (*p1)[10];
int (*p2)[5];
int *p3;
p1 = &a;
//注意以下的三种情况都出现以下警告(指针类型不匹配)
//warning: assignment from incompatible pointer type [enabled by default]
p1 = a;
p2 = &a;
p3 = &a;
return 0;
}
题目 2:函数指针的定义与使用
函数是: void func(void);对应的函数指针: void (*p)(void); 类型是: void
(*)(void);
#include
void func(void)
{
printf("test for function pointer
");
}
int main(void)
{
void (*pFunc)(void); //函数指针定义
pFunc = func; //第一种方法初始化指针
pFunc = &func; //第二种方法初始化指针
pFunc(); //用指针调用函数func();
return 0;
}
对比题目 1和 2 我们发现函数名和数组名最大的区别就是:函数名做右值时加不加 & 效果和意义都是一样的;但是数组名做右值时加不加 & 意义就不一样,如果直接用数组名给数组指针赋值,则会出现类型不匹配的警告。
原因就是编译器规定的!
题目 3:写一个复杂的函数指针的实例:譬如函数是 strcpy 函数( char
* strcpy(char *dest, const char *src); )
对应的函数指针是: char *(*pFunc)(char *dest, const char *src);
总结 :定义指针变量的方法(普通变量指针,数组指针,函数指针)就是讲其 核心名字 替换成 ( *+名字) 就是其对于的指针定义 .
变量/ 函数
指针
Int a ;
Int (*p) ;/Int
*p ;
Int a [10];
Int (*p) [10]
Void a (void);
Int (*p) (void)
2.2.3 typedef关键字的用法
(1)typedef是 C 语言中一个关键字,作用是用来定义(或者叫重命名类型)
(2)C语言中的类型一共有 2 种:一种是编译器定义的原生类型(基础数据类型,如 int 、 double 之类的);第二种是用户自定义类型,不是语言自带的是程序员自己定义的(譬如数组类型、结构体类型、函数类型·····)。
(3)我们今天讲的数组指针、指针数组、函数指针等都属于用户自定义类型。
(4)有时候自定义类型太长了,用起来不方便,所以用 typedef 给它重命名一个短点的名字。
(5)注意: typedef 是给类型重命名,也就是说 typedef 加工出来的都是类型,而不是变量。
定义函数 char * strcpy(char *dest, const char *src); 的指针
第一种方法: char *(*p)(char *dest, const char *src);
第二种方法:typedef char *(*pType)(char *dest, const char *src);
pType p;
2.3.函数指针实战 1
2.3.1用函数指针调用执行函数
题目 1:用函数指针来制作一个简单计算器
#include
//定义了一个类型pFunc
typedef int (*pFunc)(int, int);
int add(int a, int b);
int sub(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
int main(void)
{
pFunc p1 = NULL; //用pFunc 定义一个函数指针变量
char c=0;
int a=0,b=0;
printf("please import + , - , * , /
");
scanf("%c",&c);
switch (c)
{
case '+': p1 = add;break;
case '-': p1 = sub;break;
case '*': p1 = multiply;break;
case '/': p1 = divide;break;
default : p1 = NULL ; break;
}
printf("please import two int number
");
printf("a = ");
scanf("%d",&a);
printf("b = ");
scanf("%d",&b);
printf("a %c b = %d
",c,p1(a,b));
return 0;
}
int add(int a, int b) //加法
{
return a + b;
}
int sub(int a, int b) //减法
{
return a - b;
}
int multiply(int a, int b)// 乘法
{
return a * b;
}
int divide(int a, int b) //除法
{
return a / b;
}
2.4.结构体内嵌函数指针实现分层
第一:分层写代码的思路是:有多个层次结合来完成任务,每个层次专注各自不同的领域和任务;不同层次之间 用头文件来交互 。
第二:分层之后上层为下层提供服务,上层写的代码是为了在下层中被调用。
第三:上层注重业务逻辑,与我们最终的目标相直接关联,而没有具体干活的函数。
第四:下层注重实际干活的函数,注重为上层填充变量,并且将变量传递给上层中的函数(其实就是调用上层提供的接口函数)来完成任务。
第五:下层代码中其实核心是一个结构体变量(譬如本例中的 struct cal_t),写下层代码的逻辑其实很简单: 第一步先定义结构体变量;第二步填充结构体变量;第三步调用上层写好的接口函数,把结构体变量传给它既可 。
2.5.再论 typedef
2.5.1、 C 语言的 2 种类型:内建类型与用户自定义类型
(1)内建类型 ADT 、自定义类型 UDT
2.5.2、 typedef 定义(或者叫重命名)类型而不是变量
(1)类型是一个数据模板,变量是一个实在的数据。类型是不占内存的,而变量是占内存的。
(2)面向对象的语言中:类型就是类 class ,变量就是对象。
2.5.3、 typedef 与 #define 宏的区别
typedef char *pChar; //新的类型
#define pChar char * //只是替换代码中的pchar的内容为char*
2.5.4、 typedef 与结构体
第一种:结构体的定义与使用
//定义结构体类型:struct student
typedef struct student
{
char name[20];
int age;
};
struct student s1; //定义struct student类型的变量
第二种:结构体定义中添加 typedef
//定义结构体类型有两个名字( 1 ) struct
student ( 2 ) student_t
//定义结构体类型有两个名字( 1 ) struct
student* ( 2 ) pStudent*
typedef struct student
{
char name[20];
int age;
}student_t,*pStudent;
student_t s1; //定义struct student类型的变量
(1)结构体在使用时都是先定义结构体类型,再用结构体类型去定义变量。
(2)C语言语法规定,结构体类型使用时必须是 struct 结构体类型名 结构体变量名 ; 这样的方式来定义变量。
(3)使用 typedef 一次定义 2 个类型,分别是结构体变量类型,和结构体变量指针类型。
2.5.5、 typedef 与 const
(1)typedef int *PINT; const PINT p2; 相当于是int *const p2;
(2)typedef int *PINT; PINT const p2; 相当于是int *const p2;
(3)如果确实想得到const int *p;这种效果,只能typedef const int *CPINT; CPINT p1;
2.5.6、使用 typedef 的重要意义( 2 个:简化类型、创造平台无关类型)
(1)简化类型的描述。
char *(*)(char *, char *);
typedef char *(*pFunc)(char *, char *);
(2) 很多编程体系下,人们倾向于不使用 int、 double 等 C 语言内建类型,因为这些类型本身和平台是相关的(譬如 int 在 16 位机器上是 16 位的,在 32 位机器上就是 32 位的)。为了解决这个问题,很多程序使用自定义的中间类型来做缓冲。譬如 linux 内核中大量使用了这种技术 .
内核中先定义: typedef int size_t;然后在特定的编码需要下用 size_t 来替代 int (譬如可能还有 typedef
int len_t )
(3) STM32的库中全部使用了自定义类型,譬如 typedef
volatile unsigned int vu32
2.6.二重指针
2.6.1、二重指针与普通一重指针的区别
(1)本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
(2)一重指针变量和二重指针变量本身都占 4 字节内存空间,
2.6.2、二重指针的本质
(1)二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报错。
(2)C语言中如果没有二重指针行不行?其实是可以的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西(定义指针时用数据类型来标记,譬如 int
*p ,就表示 p 要指向 int 型数据),编译器知道指针类型之后可以帮我们做静态类型检查。编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是 C 语言给程序员提供的一种编译时的查错机制。
(3)为什么 C 语言需要发明二重指针?原因和发明函数指针、数组指针、结构体指针等一样的。
2.6.3、二重指针的用法
(1) 二重指针指向一重指针的地址( int *p1; int **p2; p2 = &p1)
(2)二重指针指向指针数组的 (int *a[10]; int **p; p = a)
(3)实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
(4)实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去
2.6.4、二重指针与数组指针
(1)二重指针、数组指针、结构体指针、一重指针、普通变量的本质都是相同的,都是变量。
(2)所有的指针变量本质都是相同的,都是 4 个字节,都是用来指向别的东西的,不同类型的指针变量只是可以指向的(编译器允许你指向的)变量类型不同。
(3)二重指针就是:指针数组指针
2.7.二维数组
2.7.1、二维数组的内存映像
一维数组在内存中是连续分布的多个内存单元组成的,而二维数组在内存中也是连续分布的多个内存单元组成的。
(1)从内存角度来看,一维数组和二维数组没有本质差别。
(2)二维数组 int a[2][5] 和一维数组 int b[10] 其实没有任何本质差别。我们可以把两者的同一单元的对应关系写下来。
a[0][0] a[0][1] a[0][4] a[1][0] a[1][1] a[1][4]
b[0] b[1] b[4] b[5] b[6] b[9]
(3)既然二维数组都可以用一维数组来表示,那二维数组存在的意义和价值在哪里?明确告诉大家:二维数组 a 和一维数组 b 在内存使用效率、访问效率上是完全一样的(或者说差异是忽略不计的)。在某种情况下用二维数组而不用一维数组,原因在于二维数组好理解、代码好写、利于组织。
(4)总结:我们使用二维数组( C 语言提供二维数组),并不是必须,而是一种简化编程的方式。想一下,一维数组的出现其实也不是必然的,也是为了简化编程。
2.7.2、哪个是第一维哪个是第二维?
(1)二维数组 int a[2][5] 中, 2 是第一维, 5 是第二维。
(2)结合内存映像来理解二维数组的第一维和第二维的意义。首先第一维是最外面一层的数组,所以 int a[2][5] 这个数组有 2 个元素;其中每一个元素又是一个含有 5 个元素的一维数组(这个数组就是第二维)。
(3)总结:二维数组的第一维是最外部的那一层,第一维本身是个数组,这个数组中存储的元素也是个一维数组;二维数组的第二维是里面的那一层,第二维本身是个一维数组,数组中存的元素是普通元素,第二维这个一维数组本身作为元素存储在第一维的二维数组中。
2.7.3、二维数组的下标式访问和指针式访问
(1)回顾:一维数组的两种访问方式。以 int b[10] 为例 , int *p
= b; 。
b[0]
等同于 *(p+0); b[9] 等同于 *(p+9); b[i] 等同于 *(p+i)
(2)二维数组的两种访问方式:以 int a[2][5] 为例, ( 合适类型的 )p
= a;
a[0][0]等同于 *(*(p+0)+0); a[i][j]等同于 *(*(p+i)+j)
2.7.4、二维数组的应用和更多维数组
(1)最简单情况,有 10 个学生成绩要统计;如果这 10 个学生没有差别的一组,就用 b[10] ;如果这 10 个学生天然就分为 2 组,每组 5 个,就适合用 int
a[2][5] 来管理。
(2)最常用情况:一维数组用来表示直线,二维数组用来描述平面。数学上,用平面直角坐标系来比拟二维数组就很好理解了。
(3)三维数组和三维坐标系来比拟理解。三维数组其实就是立体空间。
(4)四维数组也是可以存在的,但是数学上有意义,现在空间中没有对应(因为人类生存的宇宙是三维的)。
总结:一般常用最多就到二维数组,三维数组除了做一些特殊与数学运算有关的之外基本用不到。(四轴飞行器中运算飞行器角度、姿态时就要用到三维数组)
2.8.二维数组的运算和指针
2.8.1、指针指向二维数组的数组名
(1)二维数组的数组名表示二维数组的 第一维数组中首元素 (也就是第二维的数组) 的首地址
(2)二维数组的数组名 a等同于 &a[0] ,这个和一维数组的符号含义是相符的。
(3)用 数组指针 来指向 二维数组的数组名 是类型匹配的。
Int a[2][5]; //二维数组
int (*p)[5] = a(&a[0]); //数组指针
访问 a[1][3]: * ( * ( p+1 ) +3 )
2.8.2、指针指向二维数组的第一维
(1) 用 int *p来指向二维数组的第一维 a[i]
Int *p = a[0](&a[0][0])
2.8.3、指针指向二维数组的第二维
(1)二维数组的第二维元素其实就是普通变量了( a[1][1] 其实就是 int 类型的 7 ),已经不能用指针类型和它相互赋值了。
(2)除非 int *p = &a[i][j]; ,类似于指针指向二维数组的第一维。
总结:二维数组和指针的纠葛,关键就是 2点:
1、数组中各个符号的含义。
2、数组的指针式访问,尤其是二维数组的指针式访问。
打开微信“扫一扫”,打开网页后点击屏幕右上角分享按钮