1.指针就是个变量,用来存放地址,地址唯一标识一块内存空间
2.指针变量的大小固定是4/8个字节(32/64位平台)
3.指针是有类型的,指针的类型决定了指针±整数的步长,指针解引用的权限
情形1:char a = ‘a’; char* p = &a; 情形2:char* p = “Mango”;
int main()
{
char ch = 'w';
char* pc = &ch;//pc指向的是一个字符常量
char* p = "hello"; //hello是一个常量字符串, p保存的是常量字符串首字符的地址
// *p = 'w';//err,常量字符串存放在常量区,内容不可以被修改
return 0;
}
由于常量字符串不可以被修改,所以可以使用const修饰更好,这样编译的时候,如果有人修改了,就直接报错
int main()
{
char str1[] = "hello bit";
char str2[] = "hello bit";
const char* str3 = "hello bit";
const char* str4 = "hello bit";
if(str1 == str2)
{
printf("str1 and str2 are same\n");
}
else
{
printf("str1 and str2 are not same\n");
}
if(str3 == str4)
{
printf("str3 and str4 are same\n");
}
else
{
printf("str3 and str4 are not same");
}
return 0;
}
打印结果:str1 and str2 are not same str3 and str4 are same
原因:
1)str1和str2是不同的数组,在栈区开辟空间,str3和str4指向的是相同的常量字符串,二者保存的都是常量字符串的首字符地址, 这里str3和str4指向的是一个同一个常量字符串,
2)C/C++会把常量字符串存储到单独的一个内存区域,当几个指针,指向同一个字符串时,他们实际会指向同一块内存,但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块,所以str1和str2不同,str3和str4不同
int arr[10];//整型数组-存放整形的数组
char arr[10];//字符数组-存放字符的数组
char* arr[5]//arr是存放字符指针的数组
int* arr[5];//arr是存放整形指针的数组
例子:
int main()
{
int a = 10;
int b = 20;
int c = 30;
int d = 40;
int* arr[4] = {&a,&b,&c,&d}; //arr2就是整形指针数组
for(int i = 0;i<4;i++)
{
//printf("%d ",*(arr+i));//err
//*(arr+i):数组i下标对于的内容,打印的是地址
printf("%d ", *(*(arr + i)));
}
}
指针数组存放整形数组数组名
int main()
{
int arr1[]={1,2,3,4,5};
int arr2[]={2,3,4,5,6};
int arr3[]={3,4,5,6,7};
int* parr[]={arr1,arr2,arr3};
//数组名是首元素地址,是int*类型
//所以用int* 的数组保存,相当于二维数组
int i= 0;
int j =0;
for(i = 0;i<3;i++)
{
for(j = 0;j<5;j++)
{
printf("%d ",parr[i][j]);
}
printf("\n");
}
}
相当于二维数组,parr[i] : 得到下标为i数组的起始地址,相当于拿到了数组名,parr[i][j]:得到下标为i的数组中下标为j的元素
arr[i] == *(arr+i);
arr[i][j] == *(*(arr+i)+j) == *(arr[i]+j)
关于arr[i][j]的理解 *(*(arr+i)+j) == arr[i][j] (arr+i):是找到arr数组中的第i个位置的地址
*(arr+i):找到指针数组中的第i个位置的元素,这个元素是一个数组名
*(*(arr+i)+j):通过数组名(数组首元素地址)偏移j个长度,再解引用就能找到这个数组名起始位置向后j位置的数据
指针数组存放字符数组数组名
int main()
{
const char* arr[5] = {"abcdef","bcdefg","hehe","haha","zhangs"};
//arr数组存放的都是常量字符串,所以用const修饰数组
int i= 0;
for(i = 0;i<5;i++)
{
printf("%s\n",arr[i]);
}
return 0;
}

打印字符串只需要提供起始地址就可以了,从起始位置向后打印 打印整形则要解引用才能里面的值
int* arr[5] //指针数组
//arr是数组名,数组名是首元素地址,int**类型
//指针数组的数组名是用二级指针存放的
int* arr[5];
int** p = arr;
//相当于int**p = &arr[0]
int** p2 = &arr[3];
int a = 10;
int* pi = &a; //整形的地址存放在整形指针中
char ch = 'w';
char* pc = ch;//字符的地址存放在字符指针中
数组指针的写法
//int *parr[10] :parr先和[]结合,是数组,数组元素类型为整形指针
//若想parr是指针->用括号括起来
int (*parr)[10] = &arr; //取出的是数组的地址,应该存放到数组指针中
//int(*parr)[10] :parr先和*结合,是指针,指向的是数组,数组有10个元素,每个元素是int类型
int* arr[10] = {0};
int* (*p)[10] = &arr; //取出数组的地址放到数组指针
int** p = arr; //数组元素为:int*类型,数组名是首元素地址,类型为:int**
//arr的类型:int*
//&arr[0]的类型:int*
//&arr的类型:int(*)[10]
指针的类型决定它加减整数跳过多少个字节
数组和指针
数组名是首元素地址 有两个例外
1.数组名单独放在sizeof内部 :sizeof(数组名),这里的数组名表达式的是整个数组,计算的是整个数组的大小
2.&数组名 此时的数组名标识的是整个数组,取出的是整个数组的地址,
数组名在传参时,会降级为首元素地址
void Print1(int arr[],int sz);
void Print2(int* arr,int sz);
//上述两种写法等价
数组传地址时,用数组指针接收
//err
void Print1(int(*parr)[10],int sz)
{
int i = 0;
for(i = 0;i<sz;i++)
{
printf("%d ",parr[i]);
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
Print1(&arr,sz);
return 0;
}
打印结果:11599140 11599180 11599220 11599260 11599300 11599340 11599380 11599420 11599460 11599500 (都是随机值)
错误原因:
此时的parr是数组指针,类型为:int(*)parr,指向的数组有10个元素,每个元素是int类型, *parr+1 :跳过40个字节!!! 指针+1的步长取决于指针指向的类型
正确写法:
把一维数组当成是一个二维数组!!!!
parr[0] == *(parr+0)->二维数组的第一行的数组名(该一维数组的数组名)
parr[0][j] == *(*(parr+0)+j) ->二维数组第一行下标为j的元素

void Print1(int(*parr)[10],int sz)
{
int i = 0;
for(i = 0;i<sz;i++)
{
printf("%d ",parr[0][i]);
//也可以写成
//printf("%d ", *(*(parr+0)+j);
//parr[0] == *(parr+0)
//parr[0][j] == *(*(parr+0)+j) == (*parr)[j]
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);
Print1(&arr,sz);
return 0;
}
parr为指向数组的指针,解引用就相当于拿到了数组名 -> *parr == arr ->所以也可以写成:
写法1:printf("%d ",(*parr)[i]); //相当于arr[i]
写法2:printf("%d ",parr[0][i]);
//错误写法:*parr[i] ->parr先和[]结合
Print1(&arr,sz);
void Print1(int(*parr)[10],int sz)
int(*p1)[5]和int(*p2)[6],
p1和p2不一样!!!
p1:数组指针,类型为int(*)[5],指向的数组有5个元素,每个元素类型为int
p2:数组指针,类型为int(*)[6],指向的数组有6个元素,每个元素类型为int
数组指针指向的元素个数不可以省略!!!
方式1:参数部分也是二维数组, 传参时:二维数组的行可以省略,列不可以省略
void Print1(int arr[][5], int r, int c)
//void Print1(int arr[3][5],int r ,int c)
{
int i= 0;
int j = 0;
for(i = 0;i<r;i++)
{
for(j = 0;j<c;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
Print1(arr,3,5);
return 0;
}
方式2:使用数组指针
void Print1(int(*p)[5],int r,int c)
{
int i= 0;
int j = 0;
for(i = 0;i<r;i++)
{
for(j = 0;j<c;j++)
{
printf("%d ",p[i][j]);
//printf("%d ",*(*(p+i)+j);
//p[i][j] == *(*(p+i)+j)
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
Print1(arr,3,5); //arr是数组名,首元素地址,二维数组的数组名是第一行的地址
return 0;
}

把二维数组的每一行看成是一维数组,所以相当于传的是一维数组的地址,->int(*p)[5] !!!指向的是一维数组的数组指针
为什么方括号内元素个数是5 不是 3 :
因为此时二维数组每一行的元素个数是5
二维数组传参写成数组形式时,可以省略行,不可以省略列!


arr:二维数组数组名,首元素地址:第一行的地址,指向第一行
arr+1:第2行的地址 ,指向第二行
*(arr+1) : 得到第二行的数组名
*(arr+1)+j : 得到第二行下标为j的元素的地址
*(*(arr+1)+j):得到第二行下标为j的元素
void Print(int(*p)[5],int r,int c)
Print1(arr,3,5);
//把arr传给p ,p有能力接收arr,说明p == arr
// p[i][j] == arr[i][j]
去掉数组名->就是数组的类型 去掉数组名+元素个数->就是数组中的元素类型
int arr[5]; //整形数组,元素类型为int,元素个数为5
int *parr1[10];//parr1先和[]结合,parr1是一个数组,parr1为数组名,去掉数组名和元素个数->元素类型为:int* ,所以数组中的元素类型为int*,所以parr1是一个存放整形指针的数组,元素个数为10个
int (*parr2[10])[5];//parr2先和*结合,是指针,指向的是数组,数组有10个元素,每个元素是int类型
int(*parr3[10])[5];//parr3先和[]结合,parr3是一个数组,数组有10个元素,去掉数组名和元素个数->元素类型为:int(*)[5],每个元素是一个数组指针,指向的数组有5个元素,每个元素是int类型
//写法1:
void test(int arr[]);
//写法2
void test(int arr[10]);
//写法3
void test(int arr[100]);//虽然语法正确,但是不建议,
//一维数组名传参会降级为指针,所以括号内[]写不写元素个数都无所谓,且元素个数可以与实际元素个数不同
//写法4
void test(int* p);
int main()
{
int arr[10] = {0};
test(arr);
return 0;
}
//写法1
void test(int* arr[]);
//写法2
void test(int* arr[10]);
//写法3
void test(int* arr[100]);
//同上,括号内写的元素个数不影响
//写法4
void test(int** p);
int main()
{
int* arr[10] = {0};//数组元素类型为int*,所以arr是首元素地址,为int**类型
test(arr);
return 0;
}
二维数组传参写成数组形式时,可以省略行,不可以省略列,
原因:对一个二维数组来说,可以不知道有多少行,但是必须知道一列有多少个元素
//写法1
void test(int arr[3][5]); //ok
//写法2
void test(int arr[][]); //err,不可以行和列同时省略
//写法3
void test(int arr[][5]);//ok,可以省略行,不可以省略列
//写法4
void test(int** p);//err
//写法5
void test(int(*p)[5]);//ok
//对于写法4和写法5:二维数组的数组名->首元素地址(第一行的地址),第一行是一个一维数组,相当于把一维数组的地址传过去,要使用数组指针接收
//所以参数写成int(*p)[5]-数组指针而不是int** p-二级指针
main()
{
int arr[3][5] = {0};
test(arr);
return 0;
}
二级指针和二维数组没有必然关系
void test(int* p)
{}
int main()
{
int a = 10;
int* p1 = &a;
int arr[10] = {0};
test(&a);
test(p1);
test(arr);
test(NULL); //慎重考虑
}
可以传递的内容
void test(int** pp)
{}
int main()
{
int a = 10;
int* pa = &a;
int** ppa = &pa;
int*arr[5];//arr是指针数组,数组中的每个元素是int*类型,数组名是首元素地址->地址类型为int
test(ppa);
test(&pa);
test(arr);
return 0;
}
可以传递的内容
可以通过函数返回的方式带回一个时间戳 也可以通过传址的方式带回一个时间戳
time_t time(time_t *timer)
参数说明:
1)timer=NULL时得到当前日历时间(从1970-01-01 00:00:00到现在的秒数),
2)timer=时间数值时,用于设置日历时间,time_t是一个long long类型,如果 timer不为空,则返回值也存储在变量 timer中,
函数功能: 得到当前日历时间或者设置日历时间
函数返回: 当前日历时间,
作用:获取当前系统的时间,会返回一个时间戳
关于time_t
time_t是一种类型:
time_t == long long ->打印:%lld size_t == unsigned int->打印:%u
传参时:
//方法1:通过函数返回的方式带回去一个时间戳
time_ tt //存放时间戳的变量tt,tt的类型为time_t
tt = time(NULL);
//方法2:通过传址方式带回一个时间戳
time_t tt,p;
p = time(&tt);
//time(&tt) ->等价于 p = time(NULL);
#include
#include
int main()
{
time_t seconds;
time_t times,p;
seconds = time(×);
printf("自 1970-01-01 起的秒数 = %lld\n", seconds );
return 0;
}
函数指针变量->存放函数的地址(指向函数)
int Add(int x ,int y)
{
return x+y;
}
int main()
{
printf("%p\n",&Add);
printf("%p\n",Add);
return 0;
}
结果发现:二者打印的结果相同

和数组对比:&数组名 -数组的地址 数组名 - 数组首元素地址 二者打印出来的地址值相同,但是含义不相同
但是函数名和&函数名一样!!
int Add(int x, int y)
{
return x + y;
}
int main()
{
int arr[10] = { 0 };
int(*parr)[10] = &arr;//parr就是数组指针变量,指向一个数组,数组有10个元素,每个元素是int类型
int(*pf)(int, int) = Add;//此时of是用来存放函数的地址-pf就是函数指针变量
//这样写也可以:int(*pf)(int, int) = &Add; //Add和&Add含义一样
//这样也可以int(*pf)(int x,int y) = &Add;
printf("%d\n", (*pf)(2, 3)); //5
printf("%d\n", pf(2, 3)); //5
return 0;
}
去掉名字就是类型
int a = 10;// a的类型为:int
int arr[10] = {0};// arr的类型为:int [10]
int(*p)[10] =&arr;//p的类型->int(*)[10]
int(*pf)(int,int) = Add;//pf为函数指针,函数指针类型为:int(*)(int,int)
注意:()优先级比*高
若写成:int*pf(int,int); //此时pf先和()结合,是函数,参数为int int 返回类型为int*
正确写法:int(*pf)(int,int);//此时pf才是指针,函数指针,指向的函数的参数是int,int,返回类型是int
int(*pf)(int,int) = Add
写法1:pf(2,3); 写法2:(*pf)(2,3) 两种写法都可以,
对于写法1:pf能接收函数Add,说明pf == Add**
*对于写法2pf指向函数(存放函数的入口地址),pf就可以调用函数, 当然了,前面写多个*也不会影响 *(***pf)(2,3) == pf(2,3) == (*pf)(2,3)
*void(ptr)() ptr是函数指针,指向的函数返回类型为void,无参数 ptr的类型为:void(*)()
double a = 10.0; int b = 0; b = (int)a; //把a强转为int类型赋给b
在变量的前面加一个类型->强制类型转换
注意:强制类型转换并不会改变变量存放在内存中的内容,只是改变了如何读取变量的内存中的内容的方式
(*(void(*)())0)()
//void(*)() -函数指针
解析:这是一次函数调用
1.代码中把0强制类型转换为类型为void(*)()的一个函数的地址
2.解引用0地址的内容,就是取0地址处的这个函数,被调用的这个函数是无参的,返回类型是void
例如:
printf("%p\n",Add);//假设Add函数的地址为:0x0012ff40, 该地址处放着一个参数为int,int,返回类型为int的函数,
分析1:
signal先和()结合->signal是函数,signal函数有两个参数,第一个是int类型,第二个是void(*)(int)类型的函数指针
去掉函数名和函数的参数->就是函数的返回类型
例如: int Add(intx,int y) ->去掉函数名和函数的参数->int
所以把signal函数的函数名和参数去掉-> void(*)(int) 所以signal函数的返回类型是函数指针类型
解析:这是一次函数声明,声明的函数名是signal
signal函数有两个参数,第一个是int类型,第二个是void(*)(int)的函数指针类型,
signal函数的返回类型是一个 void(*)(int)的函数指针类型

typedef void(*pfun_t)(int); //这样写才是正确的,pfun_t == void(*)(int) 函数种子很类型
typedef void(*)(int) pfun_t; // err 语法不支持
//语法规定:要把重命名的名字放在*里
所以void (*signal(int,void(\*)(int)))(int)可以简化成: pfun_t signal (int,pfun_t);
一个参数为函数指针类型,一个参数为int类型,返回类型为函数指针类型
int* arr[10]; //存放整形指针的数组
//函数指针数组->存放函数指针的数组
int Add(int x,int y)
{
return x+y;
}
int Sub(int x,int y)
{
return x-y;
}
int Mul(int x,int y)
{
return x*y;
}
int Div(int x,int y)
{
return x/y;
}
int main()
{
int(*pf1)(int,int) = Add;
int(*pf2)(int,int) = Sub;
int(*pf3)(int,int) = Mul;
int(*pf4)(int,int) = Div;
//pfArr就是一个函数指针数组
int(*pfArr[4])(int,int) = {Add,Sub,Mul,Div};
//pfArr先和[]结合->是数组
//去掉数组名和元素个数->元素类型为:int(*)(int,int) 是一个函数指针,指向的函数的参数为int,int,返回类型为int
//使用:
int ret = pfArr[0](2,3);
//也可以写成: int ret = (*pfArr[0])(2,3);
// pfArr[0] == pf1 ==Add == *(pfArr[0]) ==(*pf1)
printf("%d\n",ret); //5
return 0;
}
int arr[10];
int(*p)[10] = &arr; //p是一个指向整形数组的指针
int* arr[10];
int* (*p)[10] = &arr; //p是一个指向整形指针数组的指针
int Add(int x,int y)
{
return x+y;
}
int(*pf)(int,int) = Add; //pf是函数指针,指向的函数的返回类型为int,有两个参数,一个为int,一个为int
int(*pfArr[5])(int,int);//pfArr是一个函数指针的数组,(pfArr先和[]结合,是数组),去掉数组名和元素个数->int(*)(int,int) 所以元素是函数指针
int(*(*ppfArr)[5])(int,int) = &pfArr; //ppfArr先和*结合->指针 此时ppfArr就是一个指向函数指针数组的指针, *ppfArr == pfArr,,*ppfArr相当于拿到了pfArr数组的数组名,去掉数组名和元素个数->元素类型,-> int(*)(int,int) 所以ppfArr指向的数组的元素类型为函数指针类型
函数指针->函数指针数组->指向函数指针数组的指针
//技巧:在函数指针数组的数组名前加*号,然后用括号括起来,这样就是指针了
1.回调函数是什么
回调函数是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应,
基本框架
void menu()
{
printf("*********************\n");
printf("**1.add 2.sub******\n");
printf("**3.mul 4.div******\n");
printf("**0.exit ******\n");
printf("*********************\n");
}
enum Option
{
EXIT, //0
ADD, //1
SUB, //2
MUL, //3
DIV, //4
};
int main()
{
int input = 0;
do
{
menu();
printf("请输入你的选择->:");
scanf("%d", &input);
switch(input)
{
case ADD:
Calc(Add);
break;
case MUL:
Calc(Mul);
break;
case DIV:
Calc(Div);
break;
case SUB:
Calc(Sub);
break;
case EXIT:
printf("退出成功\n");
break;
default:
printf("选择错误,请重新选择\n");
break;
}
} while (input);
return 0;
}
Calc函数的参数是加减乘除函数的地址,Calc函数参数使用函数指针接收,枚举的成员值从0开始,所以可以使用枚举对应用户的选项,这样编写程序的时候更直观
加减乘除函数
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
if (y == 0)
{
printf("被除数不能为0,运算出错,自动返回-1\n");
return -1;
}
return x / y;
}
回调函数
//Calc函数的参数是一个函数指针
void Calc(int(*pf)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
//也可以写成: ret = (*pf)(x,y);
printf("运算结果为:%d\n", ret);
}
通过Calc函数调用函数指针指向的函数 pf是函数指针 *pf == 函数 == pf 地址值: 函数名 == &函数名
(pf有能力接收函数的地址-> 说明pf == 函数) ,(pf存放函数的地址-》*pf即为调用该函数)
调用的时候,既可以直接使用函数指针调用,也可以通过函数指针所指向的值去调用, (*p)所代表的就是函数指针所指向的值,也就是函数本身,这样调用自然不会有问题,有兴趣的同学可以去试一试,
加减乘除函数+菜单函数
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
if (y == 0)
{
printf("被除数不能为0,运算出错,自动返回-1\n");
return -1;
}
return x / y;
}
void menu()
{
printf("*********************\n");
printf("**1.add 2.sub******\n");
printf("**3.mul 4.div******\n");
printf("**0.exit ******\n");
printf("*********************\n");
}
主函数
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
printf("请选择:>");
scanf("%d",&input);
//转移表 - C和指针
int(*pfArr[5])(int,int) = {0,Add,Sub,Mul,Div};//函数名和&函数名意义一样,所以可以直接写成函数名 为了和选项对应上,下标为0的位置放0
if(input == 0)
{
printf("退出成功\n");
}
else if(input >= 1&& input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d",&x,&y);
ret = pfArr[input](x,y); //调用下标为input的函数,并传参
printf("%d\n",ret);
}
else
{
//相当于default
printf("选择错误\n");
}
}while(input);
return 0;
}