目录
(2)辨:int(*parr)[10]与int* parr[10]
1.整型指针
整型指针--指向整型元素的指针
(1)声明形式
int* 指针名 (如int* pa)
例:利用指针访问数组元素
int main()
{ int arr[10] = { 10 };
int* p = arr;
for (int i = 0; i < 10; i++)
{
*(p + i) = i;//更新数组元素为1,2,3,4,5,6,7,8,9
printf("%d\n", *(p + i));
}
return 0;
}
(2)等价
arr[2]<==>p[2]<==>*(arr+2)<==>*(p+2)<==>*(2+p)<==>*(2+arr)<==>2[arr]
[ ]是一个操作符,2和arr是两个操作数,[]满足交换律arr[2]=2[arr]。
2.字符指针
指针可以指向字符串,其中存的实际为字符串首字母的首地址,但其必须为常量字符串。
例1:
int main()
{
//本质上是把“hello bit”这个字符串的首字符“h”的地址存储在了ps中
const char * ps = "hello bit";//添加const表示常量字符串,不可更改
char arr[] = "hello bit";//数组arr中存的是整个字符串“hello bit”,可以更改
printf("%c\n", *ps);//输出"h"
//输出整个字符串
printf("%s\n", ps);//输出“hello bit”
printf("%s\n", arr);//输出“hello bit”
return 0;
}
注:const修饰,放在*右边,* const修饰指针变量本身不可更改;const放在*左边,const *修饰指针指向的内容不可更改。
例2:
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 are same\n");
else
printf("str1 and str2 are not same\n");//输出str1 and str2 are not same
if (str3 == str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");//输出str3and str4 are same
return 0;
}
图解:
3.数组指针
整型指针-指向整型的指针(int *pa);字符指针-指向字符的指针(char *pc);数组指针-指向数组的指针,其中存放的是数组的地址。
(1)声明形式
指针指向元素的数据类型(*指针名)[数组大小](如double* (*pd)[5]:*pd,pd与*结合(用括号括起来)说明他是一个指针;加[5]表示其指向有5个元素的数组;double*表示其指向数组的每个元素的数据类型为浮点型指针double*)。
例:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int(*parr)[10] = &arr;//*parr表示parr是指针;[10]表示parr指向有10个元素的数组;int表示其指向数组中每个元素的类型为整型int
double* d[5];
double* (*pd)[5] = &d;//*pd,pd与*结合(用括号括起来)说明他是一个指针;加[5]表示其 指向有5个元素的数组;double*表示其指向的数组中每个元素的数据类型为浮点型指针(double*)
return 0;
}
(2)辨:arr与&arr
①&arr取出的是数组的地址
②arr-数组名是数组首元素的地址,即arr[0]的地址
int main()
{
int arr[10] = { 0 };
int* p1 = arr;//arr-数组名是数组首元素的地址,即arr[0]的地址;是int类型的地址,应放到int类型的指针中
int(* p2)[10] = &arr;//&arr取出的是数组的地址。P2指向整个数组,对pa进行解引用*p2相当于拿到了整个数组,即*p2等价于数组名arr
//p1,p2结果一样,但类型不一样,如下例测试数据:
printf("%p\n", p1);//输出0093F900
printf("%p\n", p1+1);//输出0093F904
//p1是整型指针,p1+1会跳过一个字节(即一个整型,4个字节)
printf("%p\n", p2);//输出0093F900
printf("%p\n", p2 + 1);//输出0093F928
//p2是数组指针,p2+1会跳过一个数组(因为该数组有十个整型元素,即40个字节)
return 0;
}
(3)数组名
数组名是数组首元素的地址,但有两个例外。
①sizeof(数组名)-数组名表示整个数组,计算的是整个数组的大小,单位是字节。
②&数组名-数组名表示整个数组,取出的是整个数组的地址。
(4)数组指针的应用
二维数组的数组名表示首元素地址,二维数组的首元素是:第一行。
如:int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7,} };//该二维数组可理解为,有三个元素的数组,其中每个元素的类型为int[5],其数组名arr表示首元素地址,首元素是:第一行{1,2,3,4,5}。
例:打印二维数组
①method1:用数组传参,用数组接收
void print1(int arr[3][5], int r, int c)
{//用int arr[3][5]接收数组
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
②method2:用数组传参,用一维数组指针接收
void print2(int(*p)[5], int r, int c)
{//用int(*p)[5]指向一维数组的指针,接收二维数组的首地址,即一维数组的地址
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));
}//p+i找到第i行的地址;解引用*(p+i)相当于得到第i行的数组名;
//*(p+i)+j加j得到第i行下标为j的元素地址;解引用*(*(p+i)+j)得到第i行下标为j的元素
printf("\n");
}
}
4.指针数组
指针数组是一个存放指针的数组
(1)声明形式
变量类型* 数组名[大小](如int* arr[3]:arr[3]表示是一个有3个元素的数组,int* 表示数组的每个元素是一个整型指针)
注:int arr[10];
//去掉数组名和元素个数剩下的就是数组元素类型 -- int
//去掉数组名,剩下的就是数组类型 -- int [10]
例:
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int* arr[3] = { a,b,c };//存三个数组首元素的地址,可以通过±整数获得数组中的其他元素
for (int i = 0; i < 3; i++)
{//打印三个数组a,b,c的内容
for (int j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));//arr[i]起始地址,arr[i]+1第二个元素地址,*(arr[i]+j)解引用,取出该地址存的元素。如arr[1],表示数组a[5]首元素1的地址;arr[1]+1表示数组a的第二个元素2的地址
}
printf("\n");
}
return 0;
}
注:可以理解为printf("%d ", *(arr[i] + j));等价于printf("%d ", arr[i][j]);
图解:
(2)辨:int(*parr)[10]与int* parr[10]
①int(*parr)[10] = &arr;//数组指针 *parr表示parr是指针;[10]表示parr指向有10个元素的数组;int表示数组中每个元素的类型为整型int。
②int* parr[10] = {a,b,c,d,e,f,g,h,i,j};//指针数组 parr[10]表示是一个有10个元素的数组,int* 表示数组的每个元素是一个整型指针。a,b,c,d,e,f,g,h,i,j为数组名,其中存放的为整数,如int a[5] = { 1,2,3,4,5 }···
5.函数指针
函数指针--指向函数的指针,存放函数的地址。
(1)声明形式
函数返回值类型 (*指针名)(函数参数类型),如:int (*pf)(int, int) = &Add
"*"与pf结合,表示pf是一个指针变量;(*pf)与括号(int,int)结合表示其是一个指向函数的指针,且该函数的参数为两个整型;最前方的int表示该函数的返回类型为int。即该指针变量pf指向一个有两个整型参数,返回类型也为整型的函数。
例:调用Add函数计算两数之和
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &Add;
//调用函数的三种方法
//method1
int ret = (*pf)(3, 5);//*pf解引用获得Add
//method2因为对于函数&Add=Add没有区别,函数名就是函数的地址,而&Add=pf,即有Add=pf
int ret = pf(3, 5);
//method3
int ret = Add(3, 5);
printf("%d\n", ret);//输出8
return 0;
}
注意:
数组名!=&数组名(数组名表示首元素的地址,&数组名表示整个数组的地址)
函数名==&函数名
(2)样例分析
①(*(void(*)())0)();//调用0地址处的函数,该函数无参,返回类型是void
//1.void(*)() --表示是函数指针类型
//2.(void(*)())0 -- 对0进行强制类型转换,被解释为一个函数地址
//3.*(void(*)())0 -- 对0地址进行了解引用操作
//4.(*(void(*)())0)() -- 调用0地址处的函数
②//signal是一个函数的声明
//method1
void (*signal(int, void(*)(int)))(int);
//==>等价于void(*)(int) signal(int,void(*)(int));但语法不允许这样写!!!
//1.signal和()先结合,说明signal是函数名
//2.signal函数的第一个参数的类型是int;第二个参数的类型是函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
//3.signal函数的返回类型也是一个函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
//method2,用typedef -- 对类型进行重定义
typedef void(*pfun_t)(int);//对void(*)(int)的函数指针类型重命名为pfun_t
//==>等价于typedef void(*)(int) pfun_t;但语法仍不允许这样写!!!需把名字pfun_t放括号里和*号一起
pfun_t signal(int, pfun_t);
6.函数指针数组
函数指针数组--存放函数指针的数组。
(1)声明形式
函数返回值类型 (*数组名[数组大小])(函数参数类型),如:int (*pfArr[2])(int, int) = { Add,Sub },Add,Sub 为两个函数的地址。pfArr优先与[ ]结合表示其为数组名;与int arr[10]类似,去掉数组名和元素个数剩下的就是数组元素类型 -- int,去掉数组名与元素个数pfArr[2],剩下int(*)(int,int)即为数组元素类型--指向参数为两个整型,返回类型也为整型的函数的指针。
(2)函数指针的应用
①原版://计算器 -- 实现整型变量的加、减、乘、除
#include<stdio.h>
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;
}
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;
do {
menu();
int x = 0, y = 0;
int ret = 0;
printf("请选择:>");
scanf_s("%d", &input);
switch(input)
{
case 1:
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
ret = Add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
{
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
ret = Sub(x, y);
printf("ret=%d\n", ret);
break;
}
case 3:
{
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
ret = Mul(x, y);
printf("ret=%d\n", ret);
break;
}
case 4:
{
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
ret = Div(x, y);
printf("ret=%d\n", ret);
break;
}
case 0:
{
printf("退出程序\n");
break;
}
default:
{
printf("选择错误,请重新选择!\n");
break;
}
}
} while (input);
return 0;
}
②升级1://计算器 -- 实现整型变量的加、减、乘、除
#include<stdio.h>
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;
}
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;
do {
menu();
//pfArr就是函数指针数组
//转移表
int(*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
int x = 0, y = 0, ret = 0;
printf("请选择:>");
scanf_s("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
ret = (pfArr[input])(x, y);//运用函数指针调用函数,通过不同下标访问不同元素,
//不同元素中存放的是不同函数的地址,然后传参调用不同的函数
//如:input=1,则定位指针数组下标为一的元素,即函数Add
printf("ret=%d\n", ret);
}
else if (input == 0)
{
printf("退出程序\n");
break;
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
③升级2://计算器 -- 实现整型变量的加、减、乘、除
见回调函数处
7.指向函数指针数组的指针
指向函数指针数组的指针--是一个指针指向一个数组,数组元素都是函数指针。
图解:
8.回调函数
(1)定义
回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(即函数的地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实际方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
(2)回调函数应用举例
升级2://计算器 -- 实现整型变量的加、减、乘、除
#inclede<stdio.h>
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;
}
void menu()
{
printf("*************************************\n");
printf("********** 1.add 2. sub *********\n");
printf("********** 3.mul 4. div *********\n");
printf("********** 0. exit *********\n");
printf("*************************************\n");
}
int Calc(int (*pf)(int, int))//函数指针做参数
{
int x = 0, y = 0;
printf("请输入2个操作数:>");
scanf_s("%d %d", &x, &y);
return pf(x, y);//调用函数
}
int main()
{
int input = 0;
do {
menu();
int ret = 0;
printf("请选择:>");
scanf_s("%d", &input);
switch (input)
{
case 1:
{
ret = Calc(Add);
printf("ret=%d\n", ret);
break;
}
case 2:
{
ret = Calc(Sub);
printf("ret=%d\n", ret);
break;
}
case 3:
{
ret = Calc(Mul);
printf("ret=%d\n", ret);
break;
}
case 4:
{
ret = Calc(Div);
printf("ret=%d\n", ret);
break;
}
case 0:
{
printf("退出程序\n");
break;
}
default:
{
printf("选择错误,请重新选择!\n");
break;
}
}
} while (input);
return 0;
}
(3)qsort函数及其使用
①参数形式
void qsort(void* base,//base中存放的是待排序数据中第一个对象的地址
size_t num,//排序数据元素的个数
size_t size,//排序数据中一个元素的大小,单位是字节
int (*cmp)(const void*,const void*)//是用来比较待排序数据中的2个元素的函数,第一个元素<第二个元素,返回<0的数;第一个元素=第二个元素,返回=0的数;第一个元素>第二个元素,返回>0的数。
//void* base无具体类型的指针,可以用于存放任意类型的指针变量(元素地址),但其不能直接解引用(*base)因为不知道数据类型,无法确定具体访问几个字节,base++也不知道向后跳过几个字节。因此使用时,需先强制转换成具体类型,才可运用。
②运用样例(实现升序排列)
#include<stdlib.h>
#include<string.h>
void print(int arr[], int sz)//打印数组
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)//参数表示要比较两个元素的地址
{
return *(int*)e1 - *(int*)e2;//(int*)e1强制类型转化为整型指针后,*(int*)e1解引用访问数据;若e1指向的元素>e2指向的元素,则返回>0的数
}
void test1()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
qsort(arr, sz, sizeof(arr[0]),cmp_int);//排序
print(arr,sz);//打印
}
struct Stu
{
char name[20];
int age;
};
①int sort_by_age(const void* e1, const void* e2)
{
return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//(struct Stu*)e1强制转化为结构体指针类型;((struct Stu*)e1)->age访问年龄,来进行排序
}
②int sort_by_name(const void* e1, const void* e2)
{
return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//(struct Stu*)e1强制转化为结构体指针类型;((struct Stu*)e1)->name访问名字,来进行排序
}
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",34},{"wangwu",20} };//结构体初始化
int sz = sizeof(s) / sizeof(s[0]);
//①按照年龄来排序
qsort(s, sz, sizeof(s[0]), sort_by_age);
//②按照名字来排序
qsort(s, sz, sizeof(s[0]), sort_by_name);}
int main()
{
test1();//整型数据的排序
test2();//使用qsort函数排序结构体数据
return 0;
}
③运用样例(实现降序排列)
只需改变比较函数
如:
int cmp_int(const void* e1, const void* e2)//参数表示要比较两个元素的地址
{
return *(int*)e1 - *(int*)e2;//(int*)e1强制类型转化为整型指针后,*(int*)e1解引用访问数据,若e1指向的元素>e2指向的元素,则返回>0的数
}
只需将return *(int*)e1 - *(int*)e2;改为return *(int*)e2 - *(int*)e1;即可。
④模拟qsort函数实现冒泡排序的通用算法
#include<stdio.h>
void print(int arr[], int sz)//打印数组元素
{
for (int i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int cmp_int(const void* e1, const void* e2)//参数表示要比较两个元素的地址
{
return *(int*)e1 - *(int*)e2;
}
void Swap(char* buf1, char* buf2, int width)
{
for (int i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
void bubble_sort(void* base, int sz, int width,
int (*cmp)(const void* e1, const void* e2))
{
//趟数
for (int i = 0; i < sz - 1; i++)
{
//一趟的排序
for (int j = 0; j < sz - 1 - i; j++)
{
//两个元素比较
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
//(char*)base强制类型转换为字符指针,因为字符指针长度为1,可以实现+4(1*4),+8(1*8)等任意长度的跳跃
//交换
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
void test3()
{
//整型数据的排序
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//排序
bubble_sort(arr, sz, sizeof(arr[0]),cmp_int);
//打印
print(arr,sz);
}
int main()
{
test3();
return 0;
}
9.总结
①int arr[5]:整型数组,存放的是5个整型元素。
②int *parr1[10]:整型指针数组,存放的是10个指向整型元素的指针。
③int (*parr2)[10]:数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int。
④int (*parr3[10])[5]:是一种指针数组,parr3是一个存放数组指针的数组,该数组能够存放10个数组指针;每个数组指针能够指向一个数组,该数组有5个元素,每个元素是int类型。等价于挖掉parr3[10],剩下的就是这个数组里面的东西。
⑤int(*p1)(int,int):函数指针,指针p1指向一个参数为两个整型,返回类型也为整型的函数。
⑥int(* p2[4])(int,int):是一种指针数组,p2是一个存放函数指针的数组,该数组能够存放4个函数指针;每个函数指针指向一个参数为两个整型,返回类型也为整型的函数。
⑦int(* (*p3)[4])(int,int)=&p2:函数指针数组的指针,&p2取出的是函数指针数组的地址;p3就是一个指向【函数指针数组】的指针,该数组有4个元素,每个元素类型为int(*)(int,int)。