目录

  • 一,指针数组与数组指针
  • 二,函数指针
  • 三,typedef关键字
  • 四,二重指针
  • 五,二维数组

一,指针数组与数组指针


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类型; 总结一下整个符号的意义就是数组指针

  • 第三个,int *(p[5]); 解析方法和结论和第一个相同,()在这里是可有可无的。

    int *p;int a[5];p = a;      // 般配的,类型匹配的,所以编译器不会警告不会报错。//p = &a;     // 类型不匹配,p是int *, &a是int (*)[5],是数组的指针;int (*p1)[5] ;p1 = &a;        // p1类型是int (*)[5],&a的类型也是int (*)[5],都是数组指针

注意:符号的优先级决定当2个符号一起作用的时候决定哪个符号先运算,哪个符号后运算。


二,函数指针


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

  • 函数指针的实质还是指针,还是指针变量。本身占4字节(在32位系统中,所有的指针都是4字节
  • 函数指针、数组指针、普通指针之间并没有本质区别,区别在于指针指向的东西是个什么玩意。
  • 函数的实质是一段代码,这一段代码在内存中是连续分布的,所以对于函数来说很关键的就是函数中的第一句代码的地址,这个地址就是所谓的函数地址,在C语言中用函数名这个符号来表示。
  • 结合函数的实质,函数指针其实就是一个普通变量,这个普通变量的类型是函数指针变量类型,它的值就是某个函数的地址(也就是它的函数名这个符号在编译器中对应的值

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

  • C语言本身是强类型语言(每一个变量都有自己的变量类型),编译器可以帮我们做严格的类型检查。
  • 所有的指针变量类型其实本质都是一样的,但是为什么在C语言中要去区分它们,写法不一样呢(譬如int类型指针就写作int *p; 数组指针就写作int (*p)[5],函数指针就得写得更复杂)
  • 假设我们有个函数是:void func(void);对应的函数指针:void (*p)(void); 类型是:void (*)(void);
  • 函数名和数组名最大的区别就是:函数名做右值时加不加&效果和意义都是一样的;但是数组名做右值时加不加&意义就不一样
  • 写一个复杂的函数指针的实例:譬如函数是strcpy函数(char *strcpy(char *dest, const char *src);),对应的函数指针是:char *(*pFunc)(char *dest, const char *src);

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

#include <stdio.h>void func1(void)
{printf("I am func1.\n");
}int main(void)
{void (*pFunc)(void);//pFunc = func1;   pFunc = &func1;       // &func1和func1做右值时是一模一样的,没任何区别pFunc();         // 用函数指针来解引用以调用该函数return 0;}
#include <stdio.h>
#include <string.h>int main(void)
{char a[5] = {0};char* (*pFunc)(char *, const char *); //定义了一个函数指针pFunc,类型是char* (*) (char*,const char*)pFunc = strcpy;                    //把strcpy函数名赋值给函数指针pFunc(a, "abc");printf("a = %s.\n", a);   //a = abc }
  • 需要注意的是:linux中命令行默认是行缓冲的,意思就是说当我们程序printf输出的时候,linux不会一个字一个字的输出我们的内容,而是将其缓冲起来放在缓冲区等一行准备完了再一次性把一行全部输出出来(为了效率)。linux判断一行有没有完的依据就是换符’\n’(windows中换行符是\r\n, linux中是\n,iOS中是\r)。也就是说你printf再多,只要没有遇到\n(或者程序终止,或者缓冲区满)都不会输出而会不断缓冲,这时候你是看不到内容输出的。因此,在每个printf打印语句(尤其是用来做调试的printf语句)后面一定要加\n,否则可能导致误判。

4、typedef关键字的用法

  • (1)typedef是C语言中一个关键字,作用是用来重命名类型
  • (2)C语言中的类型一共有2种:一种是编译器定义的原生类型(基础数据类型,如int、double之类的);第二种是用户自定义类型,不是语言自带的是程序员自己定义的(譬如数组类型、结构体类型、函数类型·····)。
  • (3)我们讲的数组指针、指针数组、函数指针等都属于用户自定义类型
  • (4)有时候自定义类型太长了,用起来不方便,所以用typedef给它重命名一个短点的名字。
  • (5)注意:typedef是给类型重命名,也就是说typedef加工出来的都是类型,而不是变量。
// 这句重命名了一种类型,这个新类型名字叫 pType,类型是:char* (*)(char *, const char *);
typedef char* (*pType)(char *, const char *);// 函数指针数组
typedef char* (*pType[5])(char *, const char *);
// 函数指针数组指针
typedef char* (*(*pType)[5])(char *, const char *);char* (*p1)(char *, const char *);   //直接定义法
pType p3;       // 利用typedef重命名之后定义变量,相当于实例化一个对象//等效于 char* (*p3)(char *, const char *);
p3 = p1;

三,typedef关键字


1、C语言的2种类型:原生类型与用户自定义类型

  • 原生类型ADT、自定义类型UDT

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

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

3、typedef与#define宏的区别

  • 讲到typedef,就不得不提#define,以便大家对比学习,将知识点编织成网。

  • 与typedef不同,define是单纯的替换,替换发生在预编译过程,此时可以把define的每个参数看成一堆字母,#define只是将一堆字母用另一堆字母替换

  • 至于字母的含义分析,在预编译过程之后。也就是说#define要做的只是傻傻地替换,至于词义的分析不在它的能力范围之内。

  • 首先,#define是没有分号的(因为如果有分号,分号也将成为替换的内容,但这明显不是我们想要的结果)。而typedef作为语句,必须是有分号的

  • 其次,它们的参数看上去是反过来的,如char*在define是作为第二个参数,而在typedef里是作为第一个参数define和typedef结构上的区别在使用时很容易导致混淆。

  • 那么如何解决这个容易混淆的地方呢?当我们用typede定义类型时,如果去掉typedef,形式上其实是一个再正常不成“变过的定义变量的语句

  • typedef char* tpChar去掉typedef之后,就是char* tpChar 所以此时char*当然在前面。

  • 然后只要记住顺序上define和typedef相反就行了说完了形式的区别,再来看看结果是否一致。

typedef char*  tpChar;
#define dpChar  char *  tpChar  P1,P2;         //p1和p2的类型都是char*
dpChar  P3,P4;          //char*p3,p4
  • dpChar是define定义的,按照替换原则,替换的结果为char*p3,p4,而tpChar是typedef给char*取的别名,此时定义出的p1和p2的类型都是char*,所以想次性定义多个指针变量,记得用typedef。

4、typedef与结构体

  • (1)结构体在使用时都是先定义结构体类型,再用结构体类型去定义变量。
// 结构体类型的定义
struct student
{char name[20];int age;
};struct student s1;            //定义一个结构体变量S1, struct student是类型;s1是变量s1.age = 12;
  • (2)C语言语法规定,结构体类型使用时必须是struct 结构体类型名 结构体变量名;这样的方式来定义变量。
// 定义了一个结构体类型,这个类型有2个名字:第一个名字是struct student,第二个类型名叫student_t
typedef struct student
{char name[20];int age;
}student_t;
  • (3)使用typedef一次定义2个类型,分别是结构体变量类型,和结构体变量指针类型
// 我们一次定义了2个类型:
// 第一个是结构体类型,有2个名字:struct teacher,teacher
// 第二个是结构体指针类型,有2个名字:struct teacher *, pTeacher
typedef struct teacher
{char name[20];int age;int mager;
}teacher, *pTeacher;teacher t1;             //相当于实例化一个对象t1.age = 23;pTeacher p1 = &t1;printf("teacher age = %d.\n", p1->age);

5、typedef与const
const int *p和int *const p是不同的。前者是p指向的变量是const,后者是p本身const

  • (1)typedef int *PINT; const PINT p2;相当于是int *const p2;
typedef int *PINT;int a = 23;
int b = 11;const PINT p2 = &a;        *p2 = 33;printf("*p2 = %d.\n", *p2);  //*p2 = 33,说明P2指向的变量可以被改变p2 = &b;                // 报错error: assignment of read-only variable ‘p2’//说明P2本身是const的

(2)typedef int *PINT; PINT const p2;相当于是int *const p2;

typedef int *PINT;int a = 23;
int b = 11;PINT const p2 = &a;        *p2 = 33;printf("*p2 = %d.\n", *p2);  //*p2 = 33,说明P2指向的变量可以被改变p2 = &b;                // 报错error: assignment of read-only variable ‘p2’//说明P2本身是const的

(3)如果确实想得到const int *p;这种效果,只能typedef const int *CPINT; CPINT p1;

typedef const int *CPINT;int a = 23;int b = 11;CPINT p = &a;*p = 33;             //报错 error: assignment of read-only location ‘*p’,P所指的变量的const的,不能被修改p = &b;

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

  • (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;

四,二重指针


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

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

2、二重指针的本质

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

3、二重指针的用法

  • (1)二重指针指向一重指针的地址
 char a;char **p1;       // 二重指针char *p2;        // 一重指针printf("sizeof(p1) = %d.\n", sizeof(p1));    //4printf("sizeof(p2) = %d.\n", sizeof(p2));     //4p2 = &a;//p1 = &a;      // p1是char **类型,&a是char *类型。// char **类型就是指针指向的变量是char *类型// char *类型表示指针指向的变量是char类型。p1 = &p2;     // p2本身是char *类型,再取地址变成char **类型,和p1兼容。
  • (2)二重指针指向指针数组
int *p1[5];
int **p3;
p3 = p1;       // p1是指针数组名,本质上是数组名,数组名做右值表示数组首元素// 首地址。数组的元素就是int *类型,所以p1做右值就表示一个int *// 类型变量的地址,所以p1就是一个int类型变量的指针的指针,所以// 它就是一个二重指针int **;
  • (3)实践编程中二重指针用的比较少,大部分时候就是和指针数组纠结起来用的。
  • (4)实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去
void func(int **p)
{*p = (int *)0x12345678;
}int main(void)
{int a = 4;int *p = &a;               // p指向aprintf("p = %p.\n", p);       // p打印出来就是a的内存地址func(&p);                   // 在func内部将p指向了别的地方printf("p = %p.\n", p);       // p已经不指向a了,所以打印出来不是a的地址*p = 23;                    // 因为此时p指向0x12345678,但是这个地址是不// 允许访问的,因此会段错误。return 0;
}

五,二维数组


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、哪个是第一维哪个是第二维

  • (1)二维数组int a[2][5]中,2是第一维,5是第二维。
  • (2)结合内存映像来理解二维数组的第一维和第二维的意义。首先第一维是最外面一层的数组,所以int a[2][5]这个数组有2个元素;其中每一个元素又是一个含有5个元素的一维数组(这个数组就是第二维)。
  • (3)总结:二维数组的第一维是最外部的那一层第一维本身是个数组,这个数组中存储的元素也是个一维数组二维数组的第二维是里面的那一层,第二维本身是个一维数组,数组中存的元素是普通元素,第二维这个一维数组本身作为元素存储在第一维的二维数组中。

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)

4、二维数组的应用和更多维数组

  • (1)最简单情况,有10个学生成绩要统计;如果这10个学生没有差别的一组,就用b[10];如果这10个学生天然就分为2组,每组5个,就适合用int a[2][5]来管理。

  • (2)最常用情况:一维数组用来表示直线,二维数组用来描述平面。数学上,用平面直角坐标系来比拟二维数组就很好理解了。

  • (3)三维数组和三维坐标系来比拟理解。三维数组其实就是立体空间。

  • (4)四维数组也是可以存在的,但是数学上有意义,现在空间中没有对应(因为人类生存的宇宙是三维的)。

  • 总结:一般常用最多就到二维数组,三维数组除了做一些特殊与数学运算有关的之外基本用不到。(四轴飞行器中运算飞行器角度、姿态时就要用到三维数组)

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

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

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

  • (1)用int *p来指向二维数组的第一维a[i],就是一个一维的指针

7、指针指向二维数组的第二维
(1)二维数组的第二维元素其实就是普通变量了(a[1][1]其实就是int类型的7),已经不能用指针类型和它相互赋值了。
(2)除非int *p = &a[i][j];,类似于指针指向二维数组的第一维。

#include <stdio.h>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]);       //9printf("a[1][3] = %d.\n", *(*(a+1)+3));   //9//int *p1 = a;       // 类型不匹配//int **p2 = a;        // 类型不匹配,会报警告// 指针指向二维数组的数组名int (*p3)[5];       // 数组指针,指针指向一个数组,数组有5个int类型元素p3 = a;             // a是二维数组的数组名,作为右值表示二维数组第一维的数组// 的首元素首地址,等同于&a[0]p3 = &a[0];printf("a[0][3] = %d.\n", *(*(p3+0)+3));      //4printf("a[1][4] = %d.\n", *(*(p3+1)+4));      //10// 指针指向二维数组的第一维//int *p4 = &a[0];      // 不可以int *p4 = a[0];          // a[0]表示二维数组的第一维的第一个元素,相当于是// 第二维的整体数组的数组名。数组名又表示数组首元素// 首地址,因此a[0]等同于&a[0][0];int *p5 = &a[0][0];  printf("a[0][4] = %d.\n", *(p4+4));     //5// 指向二维数组的第二维int *p6 = a[1];printf("a[1][1] = %d.\n", *(p6+1));     //7return 0;
}

C语言高级专题(4)-------指针和数组的高级应用相关推荐

  1. quot c语言数组压缩 quot,程序员之---C语言细节12(指针和数组细节,quot;//quot;的可移植性说明)...

    主要内容:指针和数组细节,"//"的可移植性说明 #include int main(int argc, char **argv) { int a[10]={1,2,3,4,5,6 ...

  2. C程序设计语言--第五章:指针与数组

    为什么80%的码农都做不了架构师?>>>    指针是一种保存变量地址的变量. 5.1 指针与地址 通常的机器都有一系列连续编号或编址的存储单元,这些存储单元可以单个进行操纵,也可以 ...

  3. 《C程序设计语言》笔记 (五) 指针与数组

    5.1 指针与地址 指针是一种保存变量地址的变量ANSI C使用类型void*(指向void的指针)代替char *作为通用指针的类型一元运算符&可用于取一个对象的地址: p = &c ...

  4. 【C语言】小妹不懂指针和数组的关系?那就安排指针数组关系详解

    目录 前言 一.什么是数组 二.什么是指针 三.指针变量的大小 四.数组和指针的关系 五.指针变量的自增自减运算 六.两个参数确定一个数组 七.字符型指针和字符型数组 总结 写在最后 前言 前段时间整 ...

  5. java的字符串指针数组,C语言字符串中的指针与数组

    先说一下字符串字面量(字符串常量):双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串储存在内存中.字符串常量属于静态存储类别,说明如果在函数中使用字符串常量,该字符串只会储存一次,在整个程序 ...

  6. 【牛客网】C语言入门 - CC4 利用指针遍历数组

    题目描述 题目链接:利用指针遍历数组 描述 键盘随机输入 6 个整数,将这些数据保存到数组中,利用指针遍历数组中的元素并打印. 输入描述: 键盘随机输入 6 个整数 输出描述: 输出数组中的所有元素, ...

  7. C语言结构体中指针指向数组,指向结构体类型数组的指针的使用

    指向结构体类型数组的指针的使用 定义一个结构体类型数组,其数组名是数组的首地址,下面是关于指向结构体类型数组的指针的使用具体介绍,欢迎阅读! 定义结构体类型的指针,既可以指向数组的元素,也可以指向数组 ...

  8. C语言高级编程:指针和数组

    1. 说明: 1)变量p本身的地址在编译时确定,它是不变的:变量p存的内容是一个地址,在运行时才能确定,它是变化的 2)数组arr的地址在编译时确定,它是不变的:数组arr村的内容是一个数据 3)指针 ...

  9. C语言—用结构体指针给数组赋值(结构体指针指向字符串,给字符串赋值)

    数组定义: char acBuf[sizeof(TRANS_HEAD_S) + sizeof(USER_HEAD_S) + 4] = {0}; 结构体定义: typedef struct {int i ...

最新文章

  1. 人脸对齐--Unconstrained Face Alignment without Face Detection
  2. 导出的swf文件显示 不出来地图
  3. Js中的callback机制
  4. jqgrid表格下拉搜索多选框优化—使用select下拉多选插件
  5. Java线程之间通信
  6. HBase数据模型和读写原理
  7. 用户画像之门店用户类型的体系
  8. UNITY 之FixedUpdate
  9. 基于命令行编译打包phonegap for android应用
  10. 超全的Linux基础知识思维导图(1)
  11. Excepted in :flat namespace
  12. Swift复数计算器
  13. 2020双十一AutoJs自动领喵币再次来袭【天猫、淘宝、支付宝】
  14. 计算机tpm1.2怎么启动,tpm2.0开启的方法
  15. linux chown sh,chown命令示例
  16. 北京冬奥会“科技感”拉满,有这些AI黑科技你了解吗?
  17. 服务器装系统快吗,云服务器安装系统 快吗
  18. onenote运用onetastic和notehighlight插件
  19. 打开迷你云时显示apache2.2端口被占用
  20. sql查询出两张表id不同的数据

热门文章

  1. Python 金融量化 均线系统交易策略专题(简单移动平均,加权移动平均,指数加权移动平均,异同移动平均MACD等解读与绘图)
  2. ET200SP 3964-R通讯协议 Euchner安士能CIT3SX感应识别系统
  3. 下载的mp3音频怎么转换wav格式
  4. Git Extensions 使用
  5. HTML文件命名_批量重命名工具
  6. 伪元素的本质,以及伪元素的妙用(下)
  7. 香港上市后,李想依然缺钱
  8. linux性能分析 -- top
  9. 如何下载台湾省卫星地图高清版大图
  10. java岗位面试英文自我介绍,面试外企英文自我介绍