一、指针数组与数组指针
1、分析指针数组与数组指针表达式
- int *p[5]; int (*p)[5]; int *(p[5])
- 一般规律:int *p;(p 是一个指针) int p[5];(p 是一个数组)
我们在定义一个符号时,关键在于:首先要搞清楚你定义的符号是谁(第一步:找核心);其次再来看谁跟核心最接近(第二步:找结合);之后继续向外扩展直到整个符号完结。 - 如果核心和*结合,表示核心是指针;如果核心和[]结合,表示核心是数组;如果核心
和()结合,表示核心是函数。 - 用一般规律来分析 3 个符号:
- int *p[5]; 核心是 p,p 是一个数组,数组有 5 个元素大,数组中的元素都是指针。指针指向的元素都是 int 类型的。整个符号是一个指针数组。
- int (*p)[5]; 核心是 p,p 是指针,指向一个数组,数组有 5 个元素大,数组中的元素都是int 类型的。整个符号是一个数组指针。
- int *(p[5]); 指针数组,解析方法与①相同,()在这里可有可无
- 注意:遇到符号优先级问题,查优先级表,自己要记住一些常用的优先级
二、函数指针与 typede
1、函数指针的实质
- 函数指针是一个普通的指针变量,它的值是某个函数的地址,也就是函数名这个符号
在编译器中对应的值。
2、函数指针的书写和分析方法
- C 语言是强类型语言(每一个变量都有自己的变量类型),编译器会做严格的类型检查。
- 假设有个函数是:void func(void);对应的函数指针:void (*p)(void);类型是:void( * )(void)。
- 函数名和数组名最大的区别是:函数名作右值时加不加&效果都是一样的,但是数组
名则不一样。 - 写出 strcpy 函数 char *strcpy(char *dest, const char *src)的指针:
char * (*pfunc)(char *dest,const char *src)#include <stdio.h> int add(int a,int b); int sub(int a,int b); int multiply(int a,int b); int divide(int a,int b); typedef int (*pFunc)(int,int) pFunc; int main(void) { pFunc p1=NULL;//防止变成野指针 int a,b,b; char c; printf("please enter two number:\n"); scanf("%d %d",&a,&b); printf("please enter flag:+-*/\n") do{ scanf("%c",&c); }while(c == '\n'); switch(c){ case '+':p1=add;break; case '-':p1=sub;break; case '*':p1=multiply;break; case '/':p1=divide;break; } d=p1(a,b); printf("%d%c%d=%d\n"a,c,b,d); }
3、typedef 关键字的用法
- 用来定义或者重命名类型。
- C 语言中的类型一共有 2 种:一种是编译器定义的内件类型(ADT),另一种是用户自
定义类型(UDT)。 - 数组指针、指针数组、函数指针都属于用户自定义类型。
- typedef 与 const
- typedef int * PINT; const PINT p2; // 相当于const int *p2;
- typedef int * PINT; PINT const p2; // 相当于 int *const p2;
- typedef const int *CPINT; CPINT p2; // 相当于 const int *p2;
4、使用 typedef的重要意义
- 简化类型的描述。
- 创造平台无关类型,譬如 32 位系统 Linux 内核中定义了 typedef unsigned int size_t,用size_t 来表示 32 位无符号整形;若移植到 64 位平台,则只需要改成 typedef unsignedlong size_t,就能使 size_t 表示 64 位无符号整形,而不用改很多代码。
三、Linux 命令行
1、Linux 命令行默认是行缓冲的
- 即printf输出时,Linux不会一个字一个字地输出内容,而是放在缓冲区,遇到\n再一次性输出。因为这样效率高。
- 所以使用 printf 最后一定要加\n。
2、scanf 相关
- 我们在输入内容时都会以\n 结尾,但 scanf 不会去接收最后的’\n’,导致这个回车符还留在标准输入中。下次 scanf 时就会被先拿出来,如果 scanf 中是%d、%s 还好,这时会自动跳过空格、制表符、回车符;如果 scanf 中是%c,那么就会将’\n’读走并返回。
四、二重指针
1、二重指针与普通一重指针的区别
- 二重指针是一重指针的指针。
- 两者本质上都是指针,都占四个字节。
2、C 语言中没有二重指针行不行
- 其实是可以的一重指针完全可以做二重指针做的事情,之所以发明二重指针,就是为了让编译器了解这个指针被定义时的用途,帮程序员做静态类型检查。
- 发明二重指针跟发明函数指针、数组指针、结构体指针的原因是一样的
3、二重指针的用法
- 二重指针指向一重指针的地址:在函数传参时为了通过函数内部操作改变外部的指针变量。
- 二重指针指向指针数组:通过二重指针调用指针数组的元素。
五、二维数组
1、二维数组的内存映像
- 一维数组在内存中是连续分布的多个内存单元,二维数组也是。
- 二维数组和一维数组在内存使用效率、访问效率上的差异可以忽略不计,使用二维数组是因为某种情况下它好理解、代码好写、利于组织。
2、哪个是第一维,哪个是第二维
- 二维数组 int a[2][5]中,2 是第一维,5 是第二维。
- 结合内存映像来理解,第一维是最外面一层数组,有 2 个元素;第二维是里面的那一层,对应第一维的一个元素时第二维有 5 个元素
3、二维数组的下标式访问和指针式访问
- 回顾:一维数组的两种访问方式,以 int b[10]为例,int * p = b;
b[0]等同于 * (p + 0); b[9]等同于 * (p + 9); b[i]等同于 * (p + i) - 二维数组的两种访问方式:以 int a[2][5]为例,int (* p)[5] = a;
a[0][0]等同于 * (* (p + 0) + 0); a[i][j]等同于 * ( * ( p + i) + j)
4、二维数组的应用和更多维数组
- 最简单的情况:有 10 个学生的成绩要统计,如果这 10 个学生是没有差别的一组,就用 int b[10];如果这 10 个学生是两个组的,每组 5 个,就适合用 int a[2][5]
- 最常用的情况:一维数组用来表示直线,二维数组用来表示平面。
- 三维数组类似于三维坐标系,表示立体空间
- 四维数组也是存在的,在数学上有意义,但在空间中没有对应
六、二维数组指针访问
1、数组指针指向二维数组的数组名
- 二维数组的数组名表示第一维数组中首元素的首地址,也就是数组地址
- 二维数组名 a 等同于&a[0],解引用后为 a[0],即第一维数组的首元素,表示一个数组,相当于是第二维数组的数组名,即第二维数组首元素的首地址,因此 a[0]等同于&a[0][0]。
2、指针指向二维数组的第一维元素
- int *p = a[0]; // a[0]表示第二维数组首元素的首地址,是一个变量地址。
3、指针指向二维数组的第二维元素
- 二维数组的第二维元素其实就是普通变量了,不能用指针类型与其相互赋值。
- 但可以用它的地址来给指针赋值,int *p = &a[i][j]。