学习目的
了解字符指针
了解数组指针
了解指针数组
了解数组传参和指针传参
了解函数指针
目录
指针
指针实际意义就是地址
口语中的指针是指针变量,用来存放地址,地址唯一表示一块空间。
指针的大小是固定的4/8个字符,32位平台/64位平台
指针是有类型,类型决定了步长,决定解引用时操作的权限
指针的运算
指针加减整数
指针减指针
指针的关系运算
字符指针
字符指针定义
指向字符的指针
int main()
{
char ch = 'W';
char *pc = &ch;
printf("%c\n", *pc);
*pc = 'c';
printf("%c\n", *pc);
system("pause");
return 0;
}
常量与字符指针
int main()
{
char *pc = "hello world!";
printf("%s\n", pc);
system("pause");
return 0;
}
字符指针也可以指向常量,但有几个注意事项
1、使用时直接使用指针,不能解引用
2、指向常量的指针,不能解引用修改其内容
3、使用时最好加上const,防止解引用修改
注意1
int main()
{
char *pc = "hello world!";
printf("%s\n", *pc);
system("pause");
return 0;
}
%s格式对应的是字符串。%s对应类型为char * , 即字符串。
注意2
int main()
{
char pc = "hello world!";
*pc = 'W';
/*printf("%s\n", *pc);*/
system("pause");
return 0;
}
注意3
int main()
{
const char *pc = "hello world!";
printf("%s\n", pc);
system("pause");
return 0;
}
常量的储存区域
int main()
{
const char *str1 = "HELLO WORLD!";
const char *str2 = "HELLO WORLD!";
char str3[] = "HELLO WORLD!";
char str4[] = "HELLO WORLD!";
if (str1 == str2)
{
printf("str1等于str2\n");
}
else
{
printf("str1不等于str2\n");
}
if (str3 == str4)
{
printf("str3等于str4\n");
}
else
{
printf("str3不等于str4\n");
}
system("pause");
return 0;
}
上面这道题本质是对字符指针的比较,实际比较的内容是地址
字符串字面量会存储到一个单独的区域,只读区域的常量存放区,只能读取不能改变,为了节省空间,同样的内容在常量存放区只存在一个,所以str1与str2,指向的是同一块内存区域,它们的值都是"HELLO WORLD!"的首元素‘H’的地址。
而str3和str4是两个不同的内存空间,虽然存放同样的字符串,但是空间并不一样,所以str3与str4的地址是不同的
指针数组
指针数组定义
指针数组就是存放指针的数组
int arr[10]整型数组
char ch[10]字符数组
int *arr2[10]一级指针数组
int **arr3[10]二级指针数组
指针数组的类型是int * [10]
模拟二维数组
int main()
{
int arr1[5] = { 1, 2, 3, 4, 5 };
int arr2[5] = { 6, 7, 8, 9, 10 };
int arr3[5] = { 11, 12, 13, 14, 15 };
int *arr[3] = { arr1, arr2, arr3 };
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(arr + i) + j));//arr是首元素地址,这里的arr是arr1的地址,arr1是arr1[0]的地址
}
printf("\n");
}
system("pause");
return 0;
}
数组指针
数组指针的定义
指向数组的指针
数组指针的格式
int (*arr)[10]
这里arr先与*结合,说明是指针,然后指向的是一个大小为10的整型数组
数组指针的类型
type (*) [const_n]
&数组名与数组名
数组名是首元素的地址,二维数组的首元素是第一行
例外:&数组名与sizeof(数组名)时,数组名指的是整个数组
int main()
{
int arr[10];
printf("arr =%p\n", arr);
printf("&arr =%p\n", &arr);
printf("arr+1 =%p\n", arr+1);
printf("&arr+1=%p\n", &arr+1);
system("pause");
return 0;
}
arr与&arr的地址相同是数组首元素的地址,但是arr+1时,地址只是向后前进了4个字节,因为是整型,可以理解为arr向前走了一步,这里arr可以理解为指向整型变量的指针,&arr+1向后前进了28个字节(28是十六进制,换算成十进制就是40个字节),所以&arr是整个数组的地址。
int main()
{
int arr[10];
printf("%d\n", sizeof arr);
system("pause");
return 0;
}
这里的结果是40,与int类型4个字节的结果相差十倍,所以sizeof(arr)时arr指的也是数组。
再看一组有意思的代码
int main()
{
int arr[10];
int *p = arr;
int(*pa)[10] = arr;//int(*pa)[10] = &arr;
printf("*p= %d\n", sizeof(*p));
printf("*pa=%d\n", sizeof(*pa));
printf("p= %d\n", sizeof(p));
printf("pa %d\n", sizeof(pa));
system("pause");
return 0;
}
为什么*p=4,*pa=40而p=4,pa=4呢
p与pa是指针,所以p的内容是arr的地址,pa的内容也是arr的地址,所以它们的内容是一样的,
但是类型不同,p的类型是指向整型变量的指针,所以p只是指向首元素,但是pa是指向10个元素的整型数组的指针,所以pa指向的是整个数组
那为什么要解引用才可以?
因为p和pa保存的是数组首元素的地址,先解引用才能找到相关的元素进行计算,直接计算只是计算了首元素指针所占空间的长度,结果为4(32位)/8(64位)
再看下一段代码
void print(int(*pa)[10])
{
for (int i = 0; i < 10; i++)
{
printf("%p ", *(pa + i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
print(&arr);
system("pause");
return 0;
}
结果是打印了10个地址,每个地址之间相差40个字节,这是因为pa的内容是arr传过来的地址,指向一整个数组,*(pa+i)只是解了pa的内容,得到里面的值是arr的地址
所以,相同数组指针打印数组的值,还需要再解引用一次
void print(int(*pa)[10])
{
for (int i = 0; i < 10; i++)
{
printf("%p ", *((*pa) + i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
print(&arr);
system("pause");
return 0;
}
这里 (*pa)首先是对pa解引用,找到arr的地址,然后再对(arr+i)解引用,找到数组的值,但是这样使用,非常不方便。不如直接使用int *pa,那么数组指针的正确使用场景是什么呢?
数组指针的使用
void print(int(*pa)[5])
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(pa + i) + j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 6, 7, 8, 9, 10 }, { 11, 12, 13, 14, 15 } };
print(&arr);
system("pause");
return 0;
}
上面这段代码中,*(pa+i)相当于整个二维数组的某一行, 取得地址后,*(pa+i)+j,相当于二维数组中某一个元素的地址,最后解引用得到了二维数组的值。
指向数组指针的数组
int (*pa[10])[5]
是指一个容纳数组指针的数组 ,其中int (*) [5] 是数组指针类型,pa[10]是一个数组
数组传参和指针传参
一维数组的传参
数组传参的本质是传递数组的首地址是指针,形参就是接受首地址,指针形参只能接收变量,所以int *arr,int **arr2可以接受
//一维数组的传参
void test1(int arr[])
{
printf("arr[]传址\n");
}
void test2(int arr[10])
{
printf("arr[10]传址\n");
}
void test3(int *arr)
{
printf("int *arr传址\n");
}
void test4(int *arr2[])
{
printf("int *arr[]传址\n");
}
void test5(int *arr2[10])
{
printf("int *arr[10]传址\n");
}
void test6(int **arr2)
{
printf("int **arr传址\n");
}
int main()
{
int arr[10];
int *arr2[10];
test1(arr);
test2(arr);
test3(arr);
test4(arr2);
test5(arr2);
test6(arr2);
system("pause");
return 0;
}
二维数组的传参
//二维数组传参
void test1(int arr[][5])
{
printf("int arr[][5]传参\n");
}
void test2(int arr[3][5])
{
printf("int arr[3][5]传参\n");
}
void test3(int (*arr)[5])
{
printf("int (*arr)[5]传参\n");
}
int main()
{
int arr[3][5];
test1(arr);
test2(arr);
test3(arr);
system("pause");
return 0;
}
一级指针的传参
指针传递的本质就是地址,所以任何以地址形式的参数,都能被指针形参即接收
void test1(int *p)
{
printf("&a\n");
}
void test2(int *p)
{
printf("p\n");
}
void test3(int *p)
{
printf("*pp\n");
}
void test4(int *p)
{
printf("arr\n");
}
void test5(int *p)
{
printf("&arr2[0][0]\n");
}
int main()
{
int a = 10;
int *p = &a;
int **pp = &p;
int arr[10];
int arr2[3][5];
test1(&a);
test2(p);
test3(*pp);
test4(arr);
test5(&arr2[0][0]);
system("pause");
return 0;
}
二级指针的传参
void test1(int **p)
{
printf("&p\n");
}
void test2(int **p)
{
printf("pp\n");
}
void test3(int **p)
{
printf("arr\n");
}
void test4(int **p)
{
printf("*ppp\n");
}
int main()
{
int a = 10;
int *p = &a;
int **pp = &p;
int *arr[10];
int ***ppp = &pp;
test1(&p);
test2(pp);
test3(arr);
test4(*ppp);
system("pause");
return 0;
}
函数指针
函数指针定义
函数指针就是指向函数的指针
函数指针的类型
type(*) (形参,形参,......... )
函数名与函数指针
函数名就是函数的地址,也就是函数的指针
void test()
{
;
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
system("pause");
return 0;
}
再看一段有趣的代码
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10, b = 20;
int(*c)(int, int) = add;
printf("*c=%p\n", *c);
printf("c=%p\n", c);
printf("add=%p\n", add);
printf("add=%p\n", &add);
system("pause");
return 0;
}
因为arr的本质就是函数的地址,所以对指针c进行解引用时得到的也会是函数arr的地址,函数取地址时形成了一个有趣的现象,函数名与&函数名,指向函数名的指针与解引用指针,最后都会的到函数的地址
有趣的问题1.函数是在什么时候开辟空间的
函数指针的使用
int add(int a, int b)
{
return a + b;
}
int main()
{
int a = 10, b = 20;
int(*c)(int, int) = add;
int ret = (*c)(a, b);
//printf("*c=%p\n", *c);
//printf("c=%p\n", c);
//printf("add=%p\n", add);
//printf("add=%p\n", &add);
printf("%d\n", ret);
system("pause");
return 0;
}
int(*c)(int, int)定义了一个返回类型为int,需要两个int参数的指针,指针的地址为add函数的地址,(*c)(a, b)是通过(*c)指针调用函数,同时将a和b作为实参进行传递,(*c)的本质上就是函数地址(a, b)是两个变量,所以(*c)(a, b)与add(a,b)的本质是一样的。
有趣的函数指针代码
代码1:
(*(void(*)())0)();
上面这段代码void(*)()是返回类型为空,无需参数的函数指针类型,(void(*)())0是将void(*)()的值作为一个类型,将0强制转换成(void(*)())类型,然后用被转换后的0作为指针
代码2
void(*signal(int, void(*)(int)))(int)
void(*)(int)是返回值为空,参数为int的函数指针类型,signal(int, void(*)(int))是名为signal具有一个整型参数与一个空类型参数的函数,void(*signal(int, void(*)(int)))(int)这里是返回类型为void,参数为int类型,signal函数的值为指针的声明
上面的写法太麻烦可以使用typedef进行简化
void(*signal(int, void(*)(int)))(int)
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
注意:pfun_t作为void(*)(int)的别名,定义时必须放在(*)里面
函数指针数组
函数指针数组关键词在数组,通俗的说就是保存函数指针的数组
函数指针数组的类型
type (*p[])(形参,形参.......)
函数指针数组的使用(转移表)
当一个程序需要定义大量相同返回类型,相同类型及相同个数的参数时,可以使用转移表
下面的函数通过使用转移表,极大的减少了代码量
void menu()
{
printf("**************\n");
printf("*1.加法2.减法*\n");
printf("*3.乘法4.除法*\n");
printf("**************\n");
}
int ADD(int a, int b)
{
return a + b;
}
int SUB(int a, int b)
{
return a - b;
}
int MUL(int a, int b)
{
return a * b;
}
int DIV(int a, int b)
{
return a / b;
}
int main()
{
int a, b;
int(*pa[5])(int, int) = { 0, ADD, SUB, MUL, DIV };
int input;
do
{
menu();
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:");
scanf("%d %d", &a, &b);
printf("计算结果=%d\n", (*pa[input])(a, b));
}
else if (0 == input)
{
printf("退出计算器\n");
}
else
{
printf("重新输入\n");
}
} while (input);
system("pause");
return 0;
}
指向函数指针数组的指针
指向函数指针数组的指针,就是该指针指向函数指针的数组
指向函数指针数组的指针的类型
void (*(*pa)[5])()
首先pa与*结合说明pa是一个指针,(*pa)与[5]结合,说明(*pa)是指向元素为5的数组,void(*(*pa)[5])()说明pa是指向返回类型为空,参数为空,有5个元素的函数指针数组的指针
指向函数指针数组的指针的使用
int ADD(int a, int b)
{
return a + b;
}
int SUB(int a, int b)
{
return a - b;
}
int MUL(int a, int b)
{
return a * b;
}
int DIV(int a, int b)
{
return a / b;
}
int main()
{
int a = 10, b = 20;
int(*p[5])(int,int) = { 0, ADD, SUB, MUL, DIV };//函数指针数组
int(*(*pp)[5])(int,int) = &p;
printf("%d\n", (*(*pp)[1])(a, b));
system("pause");
return 0;
}
回调函数
回调函数就是一个通过函数指针调用的函数。
如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这个时回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时,由另外一方调用的,用于对该事件或条件进行相应