1.字符指针变量
字符指针是char*类型,下边是基本用法
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { char ch = 'd'; char* pc = &ch; return 0; }这就是最基础的用法,一个字符类型存一个字符,取地址后地址保存在pc字符指针变量中;还有其他用法。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { char* pstr = "hello world"; return 0; }这里是把字符串的地址放在pstr中了吗?其实并不是,他只是把“hello world”的首字符地址存在pstr中了
可以发现上面结论是正确的,因为首字符地址和整个字符串的地址是一样的。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { char pstr1[20] = { "hello world" }; char pstr2[20] = { "hello world" }; char* pstr3 = "hello world"; char* pstr4 = "hello world"; if (pstr1 == pstr2) { printf("pstr1==pstr2\n"); } else { printf("pstr1!=pstr2\n"); } if (pstr3 == pstr4) { printf("pstr3==pstr4\n"); } else { printf("pstr3!=pstr4\n"); } return 0; }看一下这个代码,他的输出结果是什么?
为什么有这个结果?我一步步解释,首先我们创建了两个字符数组,用来存储字符串,在内存中创建了两块空间,分别存放这两个数组,即使他们的内容是一样的,其实这个也很好理解吧,主要是第二个,之所以说pstr3==pstr4是因为用字符指针指定的字符串并非数组,他是不能改变内容的,既然我们不能对它做修改,那内存为了省空间,自然也没必要专门开辟两块空间存储两个完全一样的内容。
2.数组指针
数组指针是指针,本质和整形指针、字符指针是一样的,都是存储对应类型的指针,顾名思义,数组指针存储的整个数组的地址,下边是一个基本的数组指针变量
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int main() { int arr[10] = { 0 }; int (*parr)[10] = &arr; return 0; }如代码所见,创建一个数组,将数组的地址传给数组指针变量parr,parr的类型是int(*)[10]
从上边的例子也可以看到,数组指针变量初始化的时候,需要用对应的数组地址赋值,所谓对应用上述例子来说明,其实前边的int很好理解,但需要注意的是后边的'10',这个大小是一定要相等的,以往我们初始化数组的时候为了避免越界可以给大空间不完全初始化,但这里是需要严格对应的。
那接下来我就要讲一下二维数组了,请大家回忆一下,我们说过二维数组也是数组,数组名也是首元素地址,二维数组的首元素是一个一维数组,所以,二维数组数组名的类型其实是一个数组指针。
那么整个数组的地址和数组首元素地址区别大吗?首先需要明确,他们都是从数组的首元素开始的,只不过在+1或者时,数组首元素指针跳过数组类型大小的空间,而数组地址直接跳过整个数组,指向数组后边的那一块空间,加其他数同理。
3.函数指针变量
内存可以存储我们常见的变量,比如说指针变量、整型变量等等,但万物都并非凭空出现,函数既然能运行,那他必有一个实体存在,换句话说,同样有一个类型的指针存储函数的地址,以便调用对应函数,而存储这个函数地址的变量就叫做函数指针变量。
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> void test() { printf("hello world\n"); } int main() { void (*pf1)() = test; void (*pf2)() = &test; printf("pf1==%p\n", pf1); printf("pf2==%p\n", pf2); return 0; }下边这段代码是为了演示两种函数指针类型的初始化方式,也就是说函数名和&函数名其实是等价的。
这里的函数指针类型就是void (*)(),同理,假如一个返回值是int,参数是两个int的函数,他对应的函数指针类型是int (*)(int,int)或者int (*)(int x,int y),前提是函数定义里两个形参名字是x和y。
那么问题来了,函数指针类型有啥用?
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h> int add(int a, int b) { return a + b; } int main() { int (*pf)(int, int) = add; int a = 3; int b = 5; printf("a+b=%d\n", pf(a, b)); printf("a+b=%d\n", (*pf)(a, b)); return 0; }如你所见,可以用来调用函数,那可能你就要接着问了,为什么不直接函数名传参调用呢?这不是多此一举吗?
那么我给大家看一段代码
#define _CRT_SECURE_NO_WARNINGS 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("输入1加法\n"); printf("输入2减法\n"); printf("输入3乘法\n"); printf("输入4除法\n"); printf("输入0退出\n"); } void comp(int(*pf)(int, int),int a,int b) { printf("%d\n", pf(a, b)); } int main() { int input = 0; do { int a = 0; int b = 0; printf("输入操作的两个数:"); scanf("%d %d", &a, &b); menu(); scanf("%d", &input); switch (input) { case 1: comp(add, a, b); break; case 2: comp(sub, a, b); break; case 3: comp(mul, a, b); break; case 4: comp(div, a, b); break; default: break; } } while (input); return 0; }你仔细看看我这个代码,思考一下,如果用传统方式传参,调用函数,那么代码会多么冗余?这就是函数指针变量的意义所在。
相信你也看到了一些细节,比如我用了函数指针数组存储加减乘除四个函数的地址,那么函数指针数组又是什么?
顾名思义,函数指针数组就是存储函数指针的数组,跟整型数组、字符数组等其实没有本质区别
int(*parr[10])(int,int) parr就是一个函数指针数组。
可能你会觉得有一些混淆,因为确实数组指针也好函数指针也好,写起来都有点麻烦,那么我要介绍一种类型重定义技巧,可以帮忙简化这些问题,其实就是利用typedef
typedef unsigned int uint; typedef int(*pf)(int, int); typedef int(*parr)[10];上述是常见的typedef使用方式,注意数组指针和函数指针都是把重定义的类型名放在中间了。