目录
1.字符指针变量
字符指针 char*
一种是将字符存在字符变量中,然后再将变量的地址赋值给字符指针变量
char ch='a'; char *ph=&ch;
还有一种,是直接将一个字符常量赋值给字符指针变量
#include<stdio.h> int main() { const char* pstr = "hello bit."; printf("%p\n", pstr); return 0; } //const不是必须,因为常量字符串无法修改,用const可以让编译器就报错而不是程序运行时出现问题。 本质上将字符'h'的地址传给了字符指针变量 pstr的值就是'h'的地址 *pstr指向的就是字符常量。
注意,c/c++在存储字符常量的时候,会单独开辟一块内存空间,如果是下面的代码,是等价的
char &a1="wwwww"; char &a2="wwwww";
因为,这两个指针指向的都是同一个字符常量,存储的地址也是相同的,所以a1==a2
但如果是将同样的字符串赋值给不同的数组,如下
char a3="wwwww"; char a4="wwwww";
这时候,我们如果进行a3==a4的判决,结果是假。
因为数组创建会额外开辟内存空间,不同数组的地址都不同,而上述条件是数组名与数组名的比较,也就是两个数组的首元素地址之间的比较,但很显然是不同的。
2.数组指针变量
2.1数组指针变量
数组指针数组是指针变量
整型指针变量:int * pint ;存放整型变量的地址,是一个整型类型的指针,能指向一个整型变量
浮点指针变量:float * pf ;存放浮点型变量的地址,是一个浮点类型的指针,能指向一个浮点型的变量
数组指针变量:int(*p2)[10] ;存放一个数组的地址,是变量和数组都是整型,且对应的数组应当有10个元素。
2.2数组指针的初始化
注意:数组指针与指针数组是都不一样的。数组指针只针对一个数组,是指针,且存储的是一整个数组的地址,要用&数组名。指针数组是数组,存多个指针(元素,每个元素或指针的值都是一个地址,这个地址可以对应一个普通变量,也可以是数组,如果是数组,可以直接用数组名或&数组名)。
int (*p1)[10]=&arr;
3.二维数组传参本质
二维数组传参的本质,是传递了二维数组的第一个元素的地址,而这个地址又代表着整个一维数组的地址。
void test(int a[3][5]) 这里的形参是二维数组形式 而实参部分是这样的 test(a); 由于二维数组的数组名代表的是二维数组的第一个元素,也就是第一个一维数组的地址 所以形参接受的本质上也是地址 因此,这里形参也能写成指针形式 而一维数组整个的地址,是存储在数组指针上的,所以这里形参的写法要参考数组指针 void test(int (*a)[5]) ------- 接着,是二维数组的指针写法。 跟之前用指针数组模拟二维数组有异曲同工之妙 int *p[3]={arr1,arr2,arr3}; for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { printf("%d",*(*(p+i)+j); } } 而调用二维数组,写法一模一样,除了最开始的定义数组部分 for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { printf("%d",*(*(a+i)+j); } }
我们要理解,指针数组时,我们第一个元素是一个一维数组的首元素地址。p+i意味着,以指针数组的首元素地址也就是存储第一个一维数组的首元素地址的地址为起点,跳过i-1个int类型的字节数,改变的就是二维数组的行,而指针数组的每个元素存储的值都是一个首元素地址,所以解引用后+j,是意味着以第一个一维数组的首元素地址为起点,跳过j-1个int类型的字节,改变的就是当前行的列。
而二维数组,也可以参考指针数组来理解。
区别只是,二维数组的每个元素都是一个数组指针,存储的是一个完整的一维数组地址,而指针数组存的是一个数组的首元素地址。但由于解引用一个完整的一维数组地址得到的就是该一维数组的数组名,在值上是相同的,所以写法是相同的。
//另外,我们也可以从数组名和数组的区别来看,数组名代表首元素地址,+1只加1个元素地址,数组代表一个集合,+1,就是加上这个数组的所有元素的地址,因此,解引用对象为一个完整的一维数组,那么+1就会直接跳过这个数组,由于解引用一个完整的一维数组地址得到的就是该一维数组的数组名,那么如果以一个首元素地址开始,+1,跳过的,就是首元素的地址。//这一段有点乱,可以不看
4.函数指针变量
函数指针变量存储的是函数的地址
而函数名代表的也是函数的地址,跟&函数名,值是相同的
4.1函数指针变量的创建
int (*p) (int x,int y)=函数名/&函数名 int是p指向的函数返回的类型 *说明是指针 p是变量名 (int x,int y)代表了函数的形参类型和数量 但形参的名字不是必须得,在函数指针变量中可以省略
4.2函数指针变量的使用
int aaa(int x,int y) { return x+y; } int main() { int (*aaa)(int ,int y)=aaa/&aaa; return 0; } printf("%d",(*aaa)/aaa (6,9);
4.3简写
(*(void(*)())0)();//括号里放函数指针类型,是强制转换类型 //也就是把0这个数字代表的地址里存放的函数指针的类型变成void //且没有参数的函数指针类型。 void(*signal(int,void(*)(int)))(int);
很好,这两条代码,非常绕,生怕让人看懂了。
如果不想被人狂揍,建议用typedef关键字先简化下
4.3.1typedef关键字
typedef unsigned int ut; 这里是将unsigned int 简化成ut 普通指针也是如此,指针数组也是,int * [5]; 但数组指针和函数指针,不一样,要将简略名放在(*)内部 比如typedef int(*pa)[5]; typedef void(*pfun_t)(int);
第二条可以这么写 typedef void(*pfun_t)(int); pfun_t signal(int,pfun_t);
5.函数指针数组
5.1函数指针数组
指针数组存的是一个个指针。
函数指针数组,就是存储函数指针的数组,存的一个个函数指针。
int (*parr1[3])(); parr1[3]意味着这是一个数组,且有3个元素 而数组的类型,也就是元素的类型 就是int (*)()的函数指针 ()里的内容是参数,函数的参数
5.2转移表
接下来有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; } int main() { int x, y; int input = 1; int ret = 0; do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf(" 0:exit \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = add(x, y); printf("ret = %d\n", ret); break; case 2: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = sub(x, y); printf("ret = %d\n", ret); break; case 3: printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = mul(x, y); printf("ret = %d\n", ret); break; case 4: printf("输⼊操作数:"); scanf("%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; }
这一段代码是通过输入的数字,然后调用相应的函数来进行计算,但需要写的代码量很多,虽然是对的,但比较繁琐
#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; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; 转移表 这里就是函数指针数组的创建,一共5个元素,参数是2个int类型,符合计算器的要求 为了让输入的数字跟下面打印的菜单页面符合,0下标放个0,然后再放4个运算函数 do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf(" 0:exit \n"); printf("*************************\n"); printf( "请选择:" ); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf( "输⼊操作数:" ); scanf( "%d %d", &x, &y); ret = (*p[input])(x, y); 把大量的重复性动作直接简化掉,输入什么数字 就跟数组的下标对应,然后取相应元素,也就是函数,然后传递参数 不用写好多个条件判断,只用一个就完成了 printf( "ret = %d\n", ret); } else if(input == 0) { printf("退出计算器\n"); } else { printf( "输⼊有误\n" ); } }while (input); return 0; }
6.回调函数
例子还是上面的计算器
回调函数是通过函数指针调用的函数
假如有n+1个函数,将第n个函数的地址传递给最后一个函数,最后一个个函数中有一个形参正好是用来接收第n个函数的地址,因此,改变n的量,也就是说改变传递过去的函数指针,就可以让最后一个函数中,该形参调用时,会调用不同的函数
#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 calc(int(*pf)(int, int)) { int ret = 0; int x, y; printf("输⼊操作数:"); scanf("%d %d", &x, &y); ret = pf(x, y); printf("ret = %d\n", ret); } int main() { int input = 1; do { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf(" 0:exit \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); switch (input) { case 1: calc(add); break; case 2: calc(sub); break; case 3: calc(mul); break; case 4: calc(div); break; case 0: printf("退出程序\n"); break; default: printf("选择错误\n"); break; } } while (input); return 0; }
比如这里的计算器
在条件语句中,直接改变调用calc函数时的参数,也就是改变传递的函数指针,从而调用不同的函数,也就是不同的运算函数。
7.qsort函数的运用
#include <stdio.h> #include<stdlib.h> //qosrt函数的使⽤者得实现⼀个⽐较函数 int int_cmp(const void* p1, const void* p2) { return (*(int*)p1 - *(int*)p2); } int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; int i = 0; qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp); for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } printf("\n"); return 0; }
qsort(数组的第一个元素的地址;数组元素个数;数组一个元素的大小,字节为单位;排序的函数)(void * base , size_t nitems , size_t size , int(* compar)(const void* ,const void*)
前三个不用说明,字面意思
排序的函数是我们自己得先写的。
void *是通用的类型,放什么类型地址,就是什么类型。
8.qsort函数的模拟
这段代码是进行qsort模拟实现
#include <stdio.h> int int_cmp(const void * p1, const void * p2) { return (*( int *)p1 - *(int *) p2); } 首先是关于排序的函数,int_cmp的参数,是用来比较的两个数, 传递的是地址 在返回中,要根据数组元素的类型进行强制转换, 这样才能解引用到完整的数组元素,然后进行大小比较, 大的返回值大于0,小或相等的,返回0或小于0。 void*的类型,说明了什么类型的数组元素都能传过去, 但不同类似的数组,进行排序时,需要在下面return中,改变强制转换的类型。 void _swap(void *p1, void * p2, int size) { int i = 0; for (i = 0; i< size; i++) { char tmp = *((char *)p1 + i); *(( char *)p1 + i) = *((char *) p2 + i); *(( char *)p2 + i) = tmp; } 这里是模拟qsort函数的一部分,也就是具体改变位置的函数 首先是传递的参数,都是void*类型的,下面用时要记得强制转换相应类型 然后通过冒泡排序改变数组元素的位置。 void bubble(void *base, int count , int size, int(*cmp )(void *, void *)) { int i = 0; int j = 0; for (i = 0; i< count - 1; i++) { for (j = 0; j<count-i-1; j++) { if (int_cmp ((char *) base + j*size , (char *)base + (j + 1)*size) > 0) { _swap(( char *)base + j*size, (char *)base + (j + 1)*size, size); } } } } } 这里就是通过判断排序函数的返回值,然后确认是否执行冒泡排序 bubble(数组的第一个元素的地址;数组元素个数; 数组一个元素的大小,字节为单位;排序的函数) (void * base , size_t nitems , size_t size , int(* compar)(const void* ,const void*) 前三个不用说明,字面意思 排序的函数是我们自己得先写的。 void *是通用的类型,放什么类型地址,就是什么类型。 count在这里就是数组元素的个数,写法参考冒泡排序 唯一的区别是,里面具体的内容位置改变通过调用函数实现 解读参数: int_cmp ((char *) base + j*size , (char *)base + (j + 1)*size) >0 是确定比较函数的返回值大于还是小于0,大于0则进行排序,小于等于不执行 (char *) base + j*size 这是'比较'函数的第一个参数,也就是其中一个要比较的数 首先,base本身是void*类型的指针,但这里传递参数的时候, 给予的是int类型数组首元素的地址 所以为了配合j和i,将其类型转换成char*,这样只访问int类型的地址的第一个字节 j*size,size是一个int类型元素的占据空间,4字节, 那么+j*size,就是跳过j个int类型的数组元素 从而定位不同的元素 (char *)base + (j + 1)*size)同理,也是其中一个要比较的数 int main() { int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 }; //char *arr[] = {"aaaa","dddd","cccc","bbbb"}; int i = 0; bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp); for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++) { printf( "%d ", arr[i]); } printf("\n"); return 0; } 这里是主函数部分,比较好理解,就不多说了
注意,这里举例的是int整型数组的排序,不同类型的数组需要不同的排序函数,除了结构体的数组排序,比较函数部分的强制转换类型需要改变,其他还有一些,我还没琢磨。如果是结构体更需要不一样的排序函数了。