我们在指针初阶已经了解了指针的概念,
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算
那今天来了解一下指针进阶部分。
1. 字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char*;
例:
int main() { char ch = 'w'; char *pc = &ch; *pc = 'w'; return 0; }
还有
int main() { const char* pstr = "hello world";//这里是把一个字符串放到pstr指针变量里了吗? printf("%s\n", pstr); return 0; }
const char* pstr = "hello world" 这一句代码是将整个hello world 存入字符指针 pstr 中了吗? 事实上pstr仅仅只是存了 ‘ h ’ 的地址,它是通过 ‘ h ’ 的地址去找后面其他字母的地址。
看这样一个面试题
#include <stdio.h> int main() { char str1[] = "hello world"; char str2[] = "hello world"; const char* str3 = "hello world"; const char* str4 = "hello world"; 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\n"); return 0; }
事实上 == 是比较左右两边的地址。那么str1 和 str2 好理解,开辟了两个空间,必然不相等。
而str3 和str4它们存储的都是首字符的地址,所以它们的地址相同。
2. 指针数组
我们已经在之前讲到过了,这里就简单复习一下
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
3. 数组指针
3.1定义
数组指针是指向数组的指针,他的本质是指针。我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
我们得先了解* 和 [ ] 谁的优先级高,可以查一下,是 [ ] 的优先级高于 * 所以 p1 会先和 [ ] 结合,再与 * 结合 那么第一行代码就相当于 (int *) p1 [ ] ,是一个数组,每个元素都是一个 int * 类型。第二行代码 ,我们先让 p2 与 * 结合,表示它是一指针,而指针指向的是一个int [ 10 ] 的数组。
3.2 &数组名VS数组名
假设:
int arr[10];
&arr 和 arr 有什么区别呢?
其实,我们之前也已经说到过了 &arr 和 arr都表示首元素的地址,但是它们的含义不一样
可见arr + 1 只跳过了四个字节,而&arr + 1 却跳过了 40 个字节,刚刚好为整个数组。
3.3 数组指针的使用
那数组指针是怎么使用的呢?
既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p //但是我们一般很少这样写代码 return 0; }
一个数组指针的使用:
#include <stdio.h> void print_arr1(int arr[3][5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int(*arr)[5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { for (int j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print_arr2(arr, 3, 5); return 0; }
4. 数组参数、指针参数
在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢
4.1 一维数组传参
#include <stdio.h> void test(int arr[])//ok? {} void test(int arr[10])//ok? {} void test(int *arr)//ok? {} void test2(int *arr[20])//ok? {} void test2(int **arr)//ok? {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; test(arr); test2(arr2); }
这些形式都是正确的。
4.2 二维数组传参
void test(int arr[3][5])//ok? {} void test(int arr[][])//ok? 不ok,连编译都无法通过 {} void test(int arr[][5])//ok? {} //总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。 //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。 //这样才方便运算。 void test(int* arr)//ok? {} void test(int* arr[5])//ok? {} void test(int(*arr)[5])//ok? 这种写法是数组指针的形式 {} void test(int** arr)//ok? {} int main() { int arr[3][5] = { 0 }; test(arr); }
4.3 一级指针传参
#include <stdio.h> void print(int *p, int sz) { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(p+i)); } } int main() { int arr[10] = {1,2,3,4,5,6,7,8,9}; int *p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //一级指针p,传给函数 print(p, sz); return 0; }
5. 函数指针
先看一段代码
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
我们可以看到,&函数名和函数名都表示同一个地址。 那我们的函数的地址要想保存起来,怎么保存?
下面我们看代码:
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
答案是:
pfun1可以存放。pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
6. 函数指针数组
数组是一个存放相同类型数据的存储空间,那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
答案是第一个, [ ]先和parr1结合,说明parr1是个数组,它的类型是int (*)() 类型的函数指针。
函数指针数组的用途:转移表
例子:(计算器)
#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("*************************\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 }; //转移表 while (input) { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y); } else printf("输入有误\n"); printf("ret = %d\n", ret); } return 0; }
我们用函数指针数组去保存了加减乘除函数的地址,很大程度的减低了代码冗余。