美文网首页嵌入式 Linux C ARM C语言&嵌入式
2019-09-18C语言复杂表达式与指针高级应用

2019-09-18C语言复杂表达式与指针高级应用

作者: 嵌入式Linux小白 | 来源:发表于2019-09-25 12:49 被阅读0次

1.指针数组与数组指针

1.1、字面意思来理解指针数组和数组指针

(1)指针数组的实质是一个数组,这个数组中存储的内容全部都是指针变量。
(2)数组指针的实质是一个指针,这个指针指向的是一个数组。

1.2、分析指针数组与数组指针的表达式

(1)int *p[5]; int (*p)[5]; int *(p[5]);
(2)一般规律:
int p; p是一个指针
int p[5]; p是一个数组
总结:
我们在定义一个符号时,关键在于,首先要搞清楚你定义的符号是谁(第一步:找核心);其次,再来看谁跟核心最近,谁跟核心结合(第二步:找结合);以后继续向外扩展(第三步:继续向外结合知直到整个符号完)。
(3)如果核心和
结合,表示核心是指针;如果核心和[]结合,表示核心是数组;如果核心和()结合,表示核心是函数。
(4)用一般规律来分析3个符号:
①第一个:int *p[5];
核心是p,p是一个数组,数组有5个元素大小,数组中的元素都是指针,指针指向的元素类型是int类型;整个符号是一个指针数组。
②第二个:int (*p)[5];
核心是p,p是一个指针,指针指向一个数组,数组有5个元素,数组中存的元素是int类型,整个符号是一个数组指针。也就是p指向一个有5个int类型元素的数组。
③第三个:int *(p[5]);
与第一个相同,()在这里可有可无。
注意:符号的优先级到底有什么用?
其实是决定当2个符号一起作用的时候决定哪个符号先运算,哪个符号后运算。
遇到优先级问题怎么办?第一,查优先级表,第二,自己记住()[] . →这几个优先级是比较高的。

1.3、总结1:优先级和结合性是分析符号的关键

在分析C语言问题时不要胡乱去猜测规律,不要总觉得C语言无从捉摸,从已知的规律出发按照既定的规则去做即可。

1.4、总结2:学会逐层剥离的分析方法

找到核心后从内到外逐层的进行结合,结合之后可以把已经结合的部分当成一个整体,再去和整体外面的继续结合。

1.5、总结3:基础理论和原则是关键,没有无缘无故的规则

做好自己能做到的部分,就能获得很大的成就。

2.函数指针与typedef

2.1、函数指针的实质(还是指针变量)

(1)函数指针的实质还是指针,还是指针变量。本身占4字节(在32位系统中,所有指针都是4字节)。
(2)函数指针、数组指针、普通指针之间并没有本质区别,区别在于指针指向的东西不同。
(3)函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说很关键的就是函数中的第一句代码的地址,这个地址就是所谓的函数地址,在C语言中用函数名这个符号来表示。
(4)结合函数的实质,函数指针其实就是一个普通变量,这个普通变量的类型是函数指针变量的类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值)。

2.2、函数指针的书写和分析方法

(1)C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查。
(2)所有的指针变量类型其实本质都是一样的,但是为什么在C语言中要去区分它们,写法不一样呢?(比如int类型指针就写成int p;数组指针就写成int (p)[5],函数指针就写的更复杂)。
(3)假设我们有个函数是:void func(void)
对应的函数指针:void (p)(void);
类型是:void (
)(void);

2.3、typedef关键字的用法

(1)typedef是C语言中一个关键字,作用是用来定义(或者重命名类型)。
(2)C语言中的类型一共2种:一种是编译器定义的原生类型(基本数据类型,如int、double之类的);第二种是用户自定义类型,不是语言自带的,是程序员自己定义的(比如数组类型、结构体类型、函数类型……)。
(3)有时自定义类型太长了,用起来很不方便,所以用typedef给它重命名一个短的名字。
(4)注意;typedef是给类型重命名,也就是typedef加工出来的都是类型,而不是变量。
举例:
typedef char *(*pType) (char *, const char *);
这一句重命名了一种类型,这个新类型的名字叫做pType,类型是char *(*) (char *, const char *);
typedef char *(*pType[5]) (char *, const char *);
这是函数指针数组
typedef char *(*(*pType)[5]) (char *, const char *);
这是函数指针数组指针

3.函数指针实战1

3.1、用函数指针调用执行函数

(1)typedef int (*pFunc) (int, int)
定义了一个pFunc类型,这个函数指针类型指向一种特定参数列表和返回值的函数,如int add(int a, int b)
(2)当程序出现段错误时,第一步先定位段错误。定位的方法就是在可疑处加打印信息,从而锁定导致段错误的语句,然后集中分析这句为什么会发生段错误。
(3)Linux中命令行默认是行缓冲的,意思就是说当我们程序printf输出的时候,Linux不会一个字一个字的输出我们的内容,而是将其缓冲起来的缓冲区等一行准备完了再一次性把一行全部输出来(为了效率)。Linux判断一行有没有完的依据就是换行符'\n'。(Windows的换行符是\r\n,Linux的换行符是\n,iOS中是\r)。

4.函数指针实战2

主题:结构体内嵌函数指针实现分层
(1)程序为什么要分层?因为复杂程度东西太多一个人搞不定,需要更多协同工作,于是乎就要分工。要分工先分层,分层之后各层次由不同的人完成,然后彼此调用组合共同工作。
(2)本程序要完成一个计算器,我们设计了2个层次:上层是framwork.c,实现应用程序框架;下层是cal.c,实现计算器。实际工作时cal.c是直接完成工作的,但是cal.c中的关键部分是调用的framwork.c中的函数来完成的。
(3)先写framwork.c,由一个来完成。这个人在framwork.c中需要完成计算器的业务逻辑,并且把相应的接口写在对应的头文件中发出来,将来别的层次的人用这个头文件来协同工作。
(4)另一个人来完成cal.c,实现具体的计算器功能,这个人需要framwork层的工作人员提供的头文件来工作(但是不需要framwork.c)。
(5)总结:分层写代码的思路是,有多个层次结合起来完成任务,每个层次专注各自不同的领域和任务,不同层次之间用头文件来交互。
分层之后上层为下层提供服务,上层写的diamante是为了被下层调用。上层注重业务逻辑,与我们最终的的目标直接关联,而没有具体干活的函数。下层注重实际干活的函数,注重为上层填充变量,并且将变量传递给上层中的函数(其实就是调用上层提供的接口函数)来完成任务。下层代码中其实核心式结构体变量,写下层代码的逻辑其实很简单:
第一步,先定义结构体变量;
第二步,填充结构体变量;
第三步,调用上层写好的接口函数,把结构体变量传给它即可。

5.再论typedef

5.1、C语言的2种类型:内建类型与用户自定义类型

(1)内建类型ADT
(2)用户自定义类型UDT

5.2、typedef定义(或者叫重命名)类型而不是变量

(1)类型是一个数据模板,变量是一个实在的数据,类型不占内存的,而变量是占内存的。
(2)面向对象的语言中:类型就是类class,变量就是对象

5.3、typedef与#define宏的区别

typedef char *pChar;

define pChar char *

5.4、typedef与结构体

(1)结构体类型定义

struct sturdent
{
    char name[20];
    int age;
};
struct student s1;

(2)结构体定义并重命名

typedef struct student
{
    char name[20];
    int age;
}student;
struct student s1;
student s2;

定义了一个结构体类型,这个类型有2个名字:第一个名字是struct student,第二个类型名叫student。
(3)结构体定义并重命名和声明指针类型

typedef struct teacher
{
    char name[20];
    int age;
    int major;
}teacher, *pTeacher;
// 使用:
teacher t1;
t1.age = 23;
pTeacher p1 = &t1;
printf("teacher age = %d.\n", p1 ->age);

第一个是结构体类型,有2个名字:struct teacher和teacher
第二个是结构体指针类型,有2个名字:struct teacher * 和 pTeacher

5.5、typedef与const

(1)typedef int * PINT; const PINT p2;相当于int * const p2;也就是p2指针本身是不可变的。而p2指向的变量是可变的。

p2指针本身是不可变
p2指向的变量是可变的
(2)typedef int * PINT; PINT const p2;相当于int * const p2;同上,也是p2指针本身是不可变的。而p2指向的变量是可变的。
p2指针本身是不可变的
p2指向的变量是可变的
(3)如果确实想得到const int *p;这种效果(p指向的变量不可变),只能typedef const int * CPINT; CPINT p1;,也就是说,要想限制p指向的变量不可变,则需要把const写进typedef重命名语句中。
p指向的变量不可变

5.6、使用typedef的重要意义(2个:简化类型、创造平台无关类型)

(1)简化类型的描述
typedef char * (*pFunc) (char *, char *);pFunc简化了char * (*) (char *, char *)类型
(2)很多编程体系下,人们倾向于不使用int、double等C语言内建类型,因为这些类型本身和平台是相关的(比如int在16位机器上是16位的,在32位机器上是32位的)。为了解决这个问题,很多程序使用自定义的中间类型来做缓冲。比如Linux内核中大量使用这种技术:typedef int size_t;
然后在特定的编码需要下用size_t来代替int。
(3)STM32的库中全部使用了自定义类型,比如typedef volatile unsigned int vu32;

6.二重指针

6.1、二重指针与普通一重指针的区别

(1)从本质上来说,二重指针与一重指针的本质都是指针变量,指针变量的本质就是变量。
(2)一重指针变量和二重指针变量本身都占4字节内存空间。

6.2、二重指针的本质

(1)二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态检查。一旦发现运算时数据类型不匹配,编译器就会报错。
(2)C语言中如果没有二重指针行不行?其实是可以的。一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西(定义指针时用数据类型来标记,比如int *p,就表示p要指向int型数据),编译器知道指针类型后可以帮我们做静态类型检查。编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C语言给程序员提供的一种编译时的查错机制。

6.3、二重指针的用法

(1)二重指针指向一重指针的地址
(2)二重指针指向指针数组
int *p1[5]; int **p2; p2 = p1;
p1是指针数组名,本质上是数组名,数组名做右值时表示数组首元素首地址。数组的元素就是int *类型的,所以p1做右值时就表示一个int *类型变量的地址,所以p1就是一个int类型变量的指针的指针。
(3)实践编程中二重指针用的比较少,大部分时间就是和指针数组结合起来用的。
(4)实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去。

7.二维数组

7.1、二维数组的内存映像

(1)一维数组在内存中是连续分布的对个内存单元组成的,而二维数组在内存中也是连续分布的多个内存单元组成的。


二维数组的内存映像

(2)从内存角度来看,一维数组和二维数组没有本质差别。

(3) 内存对应表
(4)总结:一维与二维数组访问效率上一样,有些情况下二维数组便于理解、编程。

7.2、二维数组的下标访问与指针式访问

(1)一维
b[0] 对应*(p+0),b[9]对应*(p+9),总结:b[i]对应*(p+i)
(2)二维
a[0][0]对应*(*(p+0)+0),a[i][j]对应*(*(p+i)+j)

8.二维数组的运算和指针

8.1、指针指向二维数组的数组名

(1)二维数组的数组名表示二维数组的第一维数组中首元素(也就是第二维的数组)的首地址。
(2)二维数组的数组名a等同于&a[0],这个和一维数组的符号含义是相符的。
(3)用数组指针来指向二维数组的数组名是类型匹配的。

int main(void)
{
    int a[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}};
    printf("a[1][3] = %d.\n", a[1][3]);
    printf("a[1][3] = %d.\n",*(*(a+1)+3));
    // int *p1 = a;  // 类型不匹配
    // int **p2 = a; // 类型不匹配
    int (*p3)[5];
    p3 = a;  // 类型匹配
    p3 = &a[0];
    printf("a[1][3] = %d.\n",*(*(p3+1)+3));
    return 0;
}

数组指针,指针指向一个数组,数组类型是有5个int类型元素的,a是二维数组的数组名,作为右值表示二维数组第一维的数组的首元素首地址,等同于&a[0]。

8.2、指针指向二维数组的第一维

int main(void)
{
    int a[2][5] = {{1, 2, 3, 4, 5},{6, 7, 8, 9, 10}};
    // int *p4 = &a[0];  // 类型不匹配
    int p4 = a[0];
    int *p5 = &a[0][0];
    printf("a[0][4] = %d.\n",*(p4+4));
    int *p6 = a[1]; 
    printf("a[1][1] = %d.\n",*(p6+4));
    return 0;
}

a[0]表示二维数组的第一维的第一个元素,相当于第二维的整体数组的数组名。数组名又表示数组元素首地址,因此a[0]等同于&a[0][0]。

8.3、指针指向二维数组的第二维

(1)二维数组的第二维元素其实就是普通变量了(a[1][1]其实就是int类型的7),已经不能用指针类型和它相互赋值了。
(2)除非int *p = &a[i][j];类似于指针指向二维数组的第一维。这时候可以修改普通变量值。
(3)总结:二维数组与指针关系的关键2点
①数组中各个符号的含义
②数组的指针访问,,尤其是二维数组的指针式访问。

相关文章

  • 2019-09-18C语言复杂表达式与指针高级应用

    1.指针数组与数组指针 1.1、字面意思来理解指针数组和数组指针 (1)指针数组的实质是一个数组,这个数组中存储的...

  • 嵌入式第九天:指针

    c语言相比其他高级语言来说,更接近于对计算机硬件的操作,而指针的应用更是为我们对硬件的操作插上了翅膀,所以指针是嵌...

  • 软件设计师考试 | 第十二章 软件系统分析与设计 | 算法分析与

    (一)C程序设计语言与实现 指针是C语言的精华部分,它极大地丰富了C语言的功能。通过利用指针,可以描述复杂的数据机...

  • 02-C语言的指针

    02-C语言的指针 目标 C语言指针释义 指针用法 指针与数组 指针与函数的参数 二级指针 函数指针 指针在C中很...

  • SWIG高级应用之智能指针

    SWIG高级应用之智能指针 SWIG 作为C++到其他语言包装的万能胶水语言,无疑是非常强大的,对于C++研发人员...

  • C语言6 运算符和表达式

    C语言6 运算符和表达式 运算符与表达式 什么是运算符?什么是表达式? 表达式的结果 表达式不论怎么复杂。最终只有...

  • C语言复杂指针类型

    1. int *p[10]; //p是一个数组,他有10个元素,每个元素是指向整型变量的指针 2. int (*p...

  • C语言05- 指针

    C语言05- 指针 13:指针 指针是C语言中的精华,也是C语言程序的重点和难点。 13.1:指针定义与使用 指针...

  • 数据结构与算法-C语言7-链式存储结构之静态链表

    数据结构与算法-目录 前言    C语言具有指针能力,使得它可以非常容易地操作内存中的地址和数据,这比其他高级语言...

  • 前端表达式

    EL与JSTL表达式 EL介绍 什么是EL 表达式语言 EL的功能 替代JSP页面中数据访问时的复杂编码 EL特点...

网友评论

    本文标题:2019-09-18C语言复杂表达式与指针高级应用

    本文链接:https://www.haomeiwen.com/subject/cmrkuctx.html