在初阶指针部分我们已经学过指针了,可以去我主页去找。
我们在解决初阶指针的时候,我们知道了char* 类型的指针。那么我们来看看char* 指针的进阶。
我们来看一个代码:
#include
int main()
{
//一般的使用方法
char ch = 'w';
char* pc = &ch;
*pc = 's';
//进阶的使用方法
//这种使用方法,是把hello world 的首字母的地址放到了ch1里面
//hello world 是一个常量,所以不能改变。为避免用户出错,我们
//可以这样写。
char* ch1 = "hello world";
const char* ch2 = "hello world";
//加上const以后,就不能对ch2所指向的内容修改。
//ch1和ch2指针变量指向的是同一块空间。
printf("%s\n", ch1);
return 0;
}
int main()
{
char arr1[] = "hello world";
char arr2[] = "hello world";
char* arr3 = "hello world";
char* arr4 = "hello world";
if (arr1 == arr2)
{
printf("arr1 and arr2 are same\n");
}
else
{
printf("arr1 and arr2 are not same\n");
//arr1 and arr2 are not same arr1和arr2指向的是两个
//内存空间
}
if (arr3 == arr4)
{
printf("arr3 and arr4 are same\n");
//arr3 and arr4 are same arr3 and arr4 指向的
//是同一块内存空间
}
else
{
printf("arr3 and arr4 are not same\n");
}
return 0;
}
这个在指针初阶部分已经讲过了,指针数组是一个存放 指针的数组。
它的定义是这样的
int* arr1[10]; //整型指针数组
char* arr2[10]; //字符型指针的数组
char** arr3[10]; //二级字符指针的数组
我们来看这个代码:
int main()
{
char a = 'a';
char b = 'b';
char c = 'c';
//一级指针
char* p = &a;
char* p1 = &b;
char* p2 = &c;
//二级指针
char** pp = &p;
char** pp1 = &p1;
char** pp2 = &p2;
char** chp[3] = { pp,pp1,pp2 };
//访问二级字符指针的数组
for (int i = 0; i < 3; i++)
{
printf("%c\n", **chp[i]);
//找到数组元素为二级指针,解引用变为一级指针,再解引用
//访问到指针所指向的内容。
}
return 0;
}
数组指针就是指向数组的指针,可以简单的理解为它是一个数组类型的指针。
可以看一下这个代码,熟悉数组指针的定义:
int* p1[10]; //指针数组
int (*p2)[10];
//数组指针,注意和指针数组的差别。数组和指针谁在后面就是什么。
//解释:p2和*首先结合,p2就变成了指针变量,然后向外看,它是一个数组类型(int [10])的指针变量,简称数组指针。指向的是一个数组。
//[]的优先级要高于*的优先级,所以必须加上()来让p2先和*结合
我们来看下面这个代码:
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
//打印出来的地址是一样的,但是我们使arr和&arr加上一个1
//我们再来看打印出的地址。
printf("%p\n", arr+1);
printf("%p\n", &arr+1);
return 0;
}
实际上:&数组名表示的是整个数组的地址,而不是数组首元素的地址。
整个数组的地址,需要用数组指针来存储。本例的数组指针可以这样定义:
int (*p)[10] = &arr;
前面提到了数组指针,接下来我们看它的使用:可以使用数组指针为二维数组传参,但是我们可以使用更容易理解的方法进行传参。请看下面的代码:
void print_arr1(int arr[3][3], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int(*arr)[3], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };
//数组名传参用数组接受,天经地义
print_arr1(arr, 3, 3);
//这里来解释一下二维数组的数组名是什么
//其实二维数组的数组名就是首行数组的地址。也就是
//一维数组的地址,数组的地址用数组指针来接受。int(*arr)[3]。
print_arr2(arr, 3, 3);
return 0;
}
学习了以上内容,我们来回顾以下代码的意思
int arr[5];
存放整型数据的数组
int* parr1[10];
存放整型指针的数组 - 指针数组
int (*parr2)[10];
存放数组地址的指针 - 数组指针
int (*parr3[10])[5];
最后一个我们可以这样 把它分解为两部分,一部分为:int (* )[5],一部分为:parr3[10]。我们可以把它理解为一个数组,里面存放的数据类型是数组指针。可以称它为数组指针的数组。
在写代码的时候我们可以把数组或者指针传给函数,那么函数的参数该如何设计请看下面的代码:
#include
void test(int arr[])//ok? //数组名传参,可以用数组接收,但是它接收是一个数组的地址
{}
void test(int arr[10])//ok? //这一个和前一个一样,只不过是加了一个数组的大小
{}
void test(int *arr)//ok? //数组名是数组首元素的地址,自然可以使用指针来接收
{}
void test2(int *arr[20])//ok? //这个参数是可以的,它是一个指针数组。而arr2也是一个指针数组,所以我们可以用相同类型的参数进行接收
{}
void test2(int **arr)//ok? //这个也是可以的,指针数组里面存放的是一个变量的地址,然后数组名是数组首元素的地址,而指针的地址就是二级指针。可以用二级指针来接收参数。
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
void test(int arr[3][5])//ok? //可以
{}
void test(int arr[][])//ok? //这个是不可以的,因为它省略了列数,二维数组只能省略行
{}
void test(int arr[][5])//ok? //可以
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok? 这个是不可以传参的,因为二维数组的数组名代表的是第一行的地址,第一行一个数组,所以它是一个数组指针。
{}
void test(int* arr[5])//ok? //这个是不可以的,因为这个参数是一个指针数组,而二维数组名只是一个一维数组的地址,注意是数组的地址。应该使用数组指针来进行传参。
{}
void test(int (*arr)[5])//ok? //这个是可以的,因为它是一个数组指针。二维数组名是第一行数组的地址,&arr。
{}
void test(int **arr)//ok? //这个是不可以的。二级指针是一个指针的地址,而二维数组名是一个数组地址。
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
二维数组传参,传过去的是一个数组指针,那么形参也应该由数组指针来接收。
#include
print(char* p, int sz)
{
for (int i = 0; i < sz; i++)
{
printf("%c ", *(p + i));
}
}
int main()
{
char arr[10] = "abcdefghi";
char* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
print(p, sz);
return 0;
}
当一个函数的参数部分是一个一级指针的时候,函数可以接收一个变量的地址,一个一级指针变量,一维数组。
#include
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
test(&p);
return 0;
return 0;
}
总体来说,当函数的参数为二级指针的时候,函数可以接收一个指针的变量的地址,一个二级指针变量,还有指针数组名。
这里我们来类比前面所学的类型
int* arr; int类型的指针
int (*p)[5] 数组指针
我们可以发现,不同的指针变量的类型可以式int,可以是数组,也可以是指针,所以我们可以得到一个规律,数组指针是数组类型的指针,整型指针式整型类型的指针,而函数指针就是函数类型的指针,那么我们就可以这样定义:
返回类型 (指针变量名)(函数的参数列表)
比如:
一个函数的参数列表是一个整型和一个char 指针构成,返回值为空,那么我们就可以这样定义:
void (*p)(int ,char*) = &printf;
我们来思考这么一个问题,&函数名,和函数名所代表的意义是否是相同的。是不是像数组那样是不同的,这个答案是相同的,取地址函数名和函数名的意义是一样的都是表示函数的地址。
我们来看两个有意思的代码:
int main()
{
//代码1:
//把一个整数0,强制类型转换为函数指针类型,
//外层解引用相当于调用函数。我们可以
//通过反汇编来观察
(* (void(*)()) 0)();
//代码2:
//signal(int, void(*)(int))是一个函数,它的函数是一个
//int,另一个是一个函数指针类型,该函数的返回值为空,参数列表
//为int,然后signal函数的返回类型是一个函数指针类型,该函数的
//返回值为空参数类型为int;所以这个语句是声明了一个返回类型为函数指针,参数
//为int和函数指针的一个函数。
void (*signal(int, void(*)(int)))(int);
return 0;
}
我们可以将一个函数指针类型用typedef关键字取一个别名,但是这个别名必须和 * 挨在一块。
请看下面的代码:
typedef void(*DATETYPE)(int)
//typedef void(*)(int) DATA //这样写是错误的
那么代码2就可以写成:
void (*signal(int, void(*)(int)))(int);
DATETYPE signal(int, DATETYPE);
函数指针数组,首先它是一个数组,数组里面存放的是数组指针。
它是这样定义的:
int(*parr1[10])()
parr1[10]是一个数组然后每一个元素是一个指针数组。
我们来看一下函数指针数组的使用。首先,我们来编写一个加减乘除的计算器。代码是这样的:
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;
}
我们可以看到以上的代码非常的冗余,那么有没有什么简单的办法呢,那就是函数指针数组,也称转移表。
我们来看修改后的代码:
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 input = 1;
int x = 0;
int y = 0;
int ret = 0;
//定义函数指针数组并且初始化
int(*parr[5])(int, int) = { NULL,add,sub,mul,div };
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
//请输入两个操作数
printf("请输入两个操作数>\n");
scanf("%d %d", &x, &y);
ret = (parr[input])(x, y);
printf("结果为:%d\n", ret);
}
else
{
printf("输入有误\n");
}
} while (input);
return 0;
}
好的今天的内容就到这里。