分辨指针数组与数组指针:
找核心->找结合->继续向外结合直到整个符号结束
int *p; //核心是p,与*结合,是指针;与int结合,是整型指针
int p[5]; //核心是p,p先与中括号[]结合成数组,int结合,是整型数组
如果核心和小括号结合,表示是函数
int *p[5],核心是p,p先跟[]结合,表明是数组,*表示数组中的元素是指针,所以是一个指针数组。
int(*p)[5],核心是p,p是指针,指针指向一个数组,所以是一个数组指针。
函数指针的实质(还是指针变量)
函数指针的实质还是指针,本身占4个字节(在32位系统中,所有的指针都是4字节)。
函数的实质是一段代码,这一段代码在内存中是连续分布的(一个函数的大括号括起来的所有语句将来编译出来生成的可执行程序是连续的),所以对于函数来说很关键的就是函数中第一句代码的地址。————这个地址就是所谓的函数地址。
函数指针的书写和分析方法
C语言本身就是强类型语言(每一个变量都有自己的变量类型)。
在一些弱类型语言中,他的变量是没有类型的。如脚本,它就是个弱类型语言。Makefile也是弱类型语言,在里面没有类型。
强类型语言中每一个变量、每一个符号都有自己的类型,类型本身是否匹配编译器可以帮助我们检查的。
假设有个函数void func(void), 该函数的传参是void类型,对应函数指针:void (*p)(void).p这个名字是随便起的,类型是void(*)(void)
#include
void funcl(void)
{
printf("I am funcl.\n");
}
int main(void)
{
void (*pFunc)(void);
pFunc = funcl;
pFunc();
return 0;
}
函数名做右值时加不加&效果和意义是一样的。
写一个复杂函数指针的实例:
strcpy函数 char *strcpy(char *dest, const char *src);
对应函数指针 char*(*pFunc)(char* dest, const char *src);
#include
#include
int main(void)
{
char a[5] = {0};
char* (*pFunc)(char*, const char*);
pFunc = strcpy;
pFunc(a,"abc");
printf("a = %s\n",a);
return 0;
}
typedef 关键字的用法
typedef 用于定义新的类型(或者叫类型的重命名)
#include
int add(int a, int b);
int sub(int a, int b);
int multiply(int a, int b);
int divide(int a, int b);
//定义了一个类型pFunc, 这个函数指针类型指向一种特定参数列表和返回值的函数
typedef int (*pFunc)(int, int);
int main(void)
{
pFunc p1 = NULL;
char c = 0;
int a = 0; b = 0; result = 0;
printf("请输入要操作的2个整数:\n");
scanf("%d %d", &a, &b);
printf("请输入操作类型:+ | - | * | / \n");
do{
scanf("%c", &c);
}while(c == '\n');
//加一句调试
//printf("a = %d, b = %d, c = %d. \n");
switch(c)
{
case '+':
p1 = add; break;
case '-':
p1 = sub; break;
case '*':
p1 = multiply; break;
case '/':
p1 = divide; break;
default:
p1 = NULL; break;
}
result = p1(a, b);
printf("%d %c %d = %d.\n", a, c,b,result);
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;
}
scanf和系统的标准输入打交道,ptintf和标准输出打交道。
我们在输入内容时会以'\n'结尾,但是程序中scanf的时候不会去接收最后的\n,导致这个回车符还留在标准输入中,下次scanf时会被先拿出来,导致错误。
完成一个计算器,设计了两个层次:上层framework.c 实现程序框架;下层是cal.c,实现计算器。
程序一、cal.h
#ifndef _CAL_H_
#define _CAL_H_
typedef int (*pFunc)(int, int);
//结构体是用来做计算器的,计算器工作时需要原材料
struct cal_t
{
int a;
int b;
int p;
}
//函数原型声明
struct cal_t{
int a;
int b;
pFunc p;
};
//函数原型声明
int calculator(const struct cal_t *p);
#endif
程序二、framework.c
#include "cal.h"
//计算器函数
int calculator(const struct cal_t *p)
{
return p->p(p->a,p->b);
}
cal.c
#include "cal.h"
#include
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;
}
int main(void)
{
int ret = 0;
struct cal_t myCal;
myCal.a = 12;
myCal.b = 4;
myCal.p = divide;
ret = calculator(&myCal);
printf("ret = %d.\n", ret );
retrurn 0;
}
typedef int size; //typedef
int i; //原型行
size_t i; //应用行
typedef char Line[81]; //typedef行
char t[81]; //原型行
Line t; //应用行
则Line 类型代表了具有81个元素的字符数组
typedef int (*fun_ptr)(int , int) //typedef
int (*fp)(int,int) //原型行
fun_ptr fp; //应用行
原型行和应用行的编译结果是一样的,都创建了一个类型位int(*)(int,int)的函数指针fp。只是fun_ptr fp 比int(*fp)(int,int)更简洁。
需要的原型: char t[81];
得到类型: typedef char T[81];
应用: T t;
T是之前定义的类型,t是通过该类型定义的变量,等同于char t[81]
注意:typedef在语法上是一个存储类的关键字(如auto\extern\static\register),而变量只能被一种存储类的关键字修饰。
#define dpChar char*
#define char* tpChar;
dpChar p1, p2;
tpChar p3, p4;
(1)#define 是没有分号的,而typedef作为语句是有分号的;
(2)其参数看上去是反过来的,为了便于区分,牢记“如果去掉typedef,那么形式上就是一个正常的定义变量语句”
(3)根据替换规则,#define 定义结果:char *p1, charp2;
而tpChar是char*的别名,p3和p4类型都是char*;
struct node{}; //定义了一个node的结构体类型
struct node n; //在申请node的变量时,必须带上struct
配合结构体类型有以下几种用法:
利用结构体类型申请变量时就可以省略掉struct这个关键字
typedef struct node{} Node; //给struct node{}类型取别名
Node n; //利用结构体类型申请变量
使用typedef 一次定义两个类型
typedef struct node{} Node, *pNode;
//typedef 类型 Node; 和typedef 类型* pNode;
typedef int *PINT;
const PINT p2; // 相当于 int *const p2;
typedef int *PINT;
PINT const p2; //相当于int *const p2;
typedef cosnt int *CPINT;
CPINT p1; //相当于 p1;
简化类型,让程序便于理解和书写
创造平台无关类型,便于移植;
用法:二重指针存放一重指针的地址;二重指针指向指针数组;在函数传参时为了通过函数内部改变外部变量的一个指针,回传递这个指针变量(也就是二重指针)的地址进去。
数组名代表数组首元素的地址。
对于二位数组a[2][5],数组名a就表示a[0]的地址,即数组名等价于&a[0]。
接着看a[0],a[0]有两种身份:
在二维数组的第一个维度里a[0]时数组的首元素;而在第二维度里,a[0]本身就是个数组,该数组的首元素是a[0][0],所以此时a[0]也代表一个数组名。
因此,可以得出a[0]等价于&a[0][0],又a等价于&a[0],所以a等价于&&a[0][0]。
二维数组的访问
a[i][j]等同于*(*(p+i)+j);
p不解引用,对p加减是在第一维里偏移地址;
p解一次引用,对*p加减是在第二维里偏移地址;
p二次解引用,才能访问值,如*(*p);