目录:
(1)数组?指针?
书中一开始提出这样三个问题:
(1)什么是数组?
(2)什么是指针?
(3)数组和指针之间有何关系?
回答第一个问题:
数组是用于储存多个相同类型数据的集合, 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。
回答第二个问题:
变量、常量、自定义类型甚至函数的创建都在内存中占用一定空间的大小,而指针用于存放该空间起始位置的地址。
回答第三个问题:
回答这个问题先来看两段代码
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int i = 0;
for (i=0; i<10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
int i = 0;
int *p = arr;
for (i=0; i<10; i++)
{
printf("%d ", *(p+i));
}
return 0;
}
都完成了循环遍历整个数组,如果你现在认为数组等于指针,或者数组和指针有一定的关系那就大错特错了
书中明确指出,数组和指针之间没有任何关系
- 指针就是指针,指针变量在 32 位系统下,永远占 4 个 byte,其值为某一个内存的地址。 指针可以指向任何地方,但是不是任何地方你都能通过这个指针变量访问到。
- 数组就是数组,其大小与元素的类型和个数有关。定义数组时必须指定其元素的类型 和个数。数组可以存任何类型的数据,但不能存函数
上面的两段代码只是数组的两种不同的访问格式:
以下标形式访问
以指针形式访问
两种访问格式本质上没有区别,在编译器看来是完全相同的两种书写格式。
所以就引出了另一个问题,既然是两种书写格式,为什么不统一起来。
(2)数组和指针的区别(两种形式并存的必要)
书中引入了两个概念:
完全匿名访问
具名+匿名访问
何出此言,原因在于若定义两个变量
char *p = "abcdef";
char a[]= "123456";
p是一个指针变量,存放的是在文字常量区创建的"abcdef"的首元素地址,这块内存没有名字,所以不管以何种形式访问,在编译器看来都是通过首元素地址+偏移量来访问,故为完全匿名访问
数组a的创建在栈上,把整个数组创建的7byte大小空间称为数组a,a是这块内存的名字,在其访问内存中,在编译器看来有一个解引用的过程,此时a降级为数组的首元素地址,然后再通过偏移量来访问
所以不管是完全匿名访问还是具名+匿名访问取决于访问对象,与访问形式无关
再者若统一了两种书写格式,将会在以下的场景中出现问题:
(2.1)定义为指针,声明为数组
定义成什么,然后做相应声明程序必然无误
若在一个源文件中将字符串常量定义成指针
char *p = "abcdef";
在另一个源文件中声明成数组后并使用
#include <stdio.h>
extern char p[];
int main()
{
printf("%s\n", p);//输出错误
printf("%s\n", *(int*)p);//输出正确
return 0;
}
如图所示,第一个输出的值由声明的数组p决定,它找到了存放"abcdef"首元素地址的那块内存(4byte)的起始地址,然后将p[0]、p[1]、p[2]存储整形数字ASCLL码对应字符输出了,地址小端存储,大多数情况下地址均在0x00xxxxxx区域,所以找到了p[3]即当作\0停了下来
如果想正确输出,改变指针的访问权限,强制类型转换为int *后解引用,刚好拿出"abcdef"的首元素地址
(2.2)定义为数组,声明为指针
如果反过来呢,会不会出错?又是如何出错的?
若在一个源文件中将字符串常量定义成数组
char arr[] = "abcdef";
在另一个源文件中声明成指针后使用
#include <stdio.h>
extern char* arr;
int main()
{
printf("%s\n", arr);//程序崩溃
printf("%s\n", &arr);//输出正确
return 0;
}
定义为数组,声明为指针,此时的“指针”名其访问权限是4byte,得到一块已知的非法地址,访问其程序必然崩溃
一般情况下我们只考虑指针变量存放的地址,但指针在创建的时候也有空间,在32位系统下占4byte(不过我的电脑是64位操作系统,为何依然是4byte,而不是8byte,没搞懂望指点),这4byte的空间也有地址,而我们一般情况下不关心这个地址,但编译器关心。定义为数组,声明为指针,&“指针名”拿到了这4byte的起始地址,刚好就是数组名在做相关操作时降级处理的首元素地址,依然能输出定义的字符串
经过上面的分析足以见得指针和数组没有关系,就连声明的时候也要做相应声明,否则将会自掘坟墓
接下来就讲到了几个比较拗口的名字,但说白了就是不同类型的指针或者数组而已,抓住上面指针和数组的定义就错不了
(3)指针数组
- 指针数组:首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身 决定。它是“储存指针的数组”的简称。
#include <stdio.h>
int main()
{
char* arr[3] = {"hello", "bit", "world"};
int i = 0;
for(i=0; i<3; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
如上代码,创建了一个拥有三个元素的数组arr,每个元素都是char *类型的指针,具体的值是"hello", "bit", "world"三个字符串的首元素地址,所以arr就被称为指针数组
(4)数组指针
- 数组指针:首先它是一个指针,它指向一个数组。在 32 位系统下永远是占 4 个字节, 至于它指向的数组占多少字节,不知道。它是“指向数组的指针”的简称
指针的类型决定了指针的访问权限和步长,而数组指针的类型是int(*)[],指针+1跳过所指向数组的空间大小,通过+偏移量后解引用可以拿到数组中的元素
如下代码,一个二维数组在函数参数部分用一个数组指针接收,得到 arr元素的首元素首地址,arr[i][j]可以通过arr+i*sizeof(int)5+j*sizeof(int)。同样,可以换 算成以指针的形式表示:(*(arr+i)+j)
#include <stdio.h>
void print(int (*p)[5], int x, int y)
{
int i = 0;
int j = 0;
for(i=0; i<x; i++)
{
for(j=0; j<y; j++)
{
printf("%d ", *(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = {1,2,3,4,5,5,4,3,2,1,6,7,8,9,0};
print(arr, 3, 5);
return 0;
}
二维数组进行传参,传的是首元素的地址,即第一行的地址,用于接收的函数参数应该有能力找到这个二维数组的每一行,所以定义成int(*)[5]类型
(5)函数指针
函数也有地址,如果需要一个变量存放函数的地址固然想到了函数指针
函数指针在定义的时候需要注意的是,必须指明函数的参数个数及类型
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int main()
{
int (*p)(int, int) = &Add;
printf("%d\n", (*p)(2,3));
return 0;
}
printf("%d\n", (*p)(2,3))(*p)取出存在这个地址上的函数,通过函数参数的调用形式完成一次函数调用
(6)函数指针数组
与指针数组类似,函数指针数组首先是一个数组,数组中的每一个元素都是一个函数的地址
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
int main()
{
int (*pfun[4])(int, int) = {Add, Sub, Mul, Div};
int i = 0;
for(i=0; i<4; i++)
{
printf("%d\n", (*(*(pfun+i)))(9, 3));
}
return 0;
}
定义了一个函数指针数组,有4个元素,数组中每个元素分别存放了函数Add, Sub, Mul, Div的地址
如果想要利用上面的代码实现一个计算器:
(6.1)计算器常规实现
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
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;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
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;
}
(6.2)计算器函数封装实现
为了避免在错误输出和选择退出时不提示输出不打印结果,必须在每一个正常选择的case语句中写入相同的代码块,所以代码显得十分冗余,有没有办法解决?我们首先想到了用函数来封装
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
void menu()
{
printf("******************************\n");
printf("** 1. add 2. sub **\n");
printf("** 3. mul 4. div **\n");
printf("** 0.exit **\n");
printf("******************************\n");
}
void calc(int (*pfun)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = pfun(x, y);
printf("ret = %d\n", ret);
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
menu();
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;
}
用一个参数为函数指针的函数将重复的代码进行封装,目的是能够接收传递过来的函数地址
(6.3)计算器转移表实现
有没有更简单的实现方式?那就需要函数指针数组帮忙!
#include <stdio.h>
int Add(int x, int y)
{
return x+y;
}
int Sub(int x, int y)
{
return x-y;
}
int Mul(int x, int y)
{
return x*y;
}
int Div(int x, int y)
{
return x/y;
}
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;
int x = 0;
int y = 0;
int ret = 0;
//转移表
int (*pfun[5])(int , int) = {0, Add, Sub, Mul, Div};
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
if (input>=1 && input <=4)
{
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = pfun[input](x, y);
printf("ret = %d\n", ret);
}
else if (!input)
{
printf("程序退出!\n");
break;
}
else
{
printf("输入有误,重新输入!\n");
continue;
}
}while(input);
return 0;
}
(7)指向函数指针数组的指针
指向函数指针数组的指针是一个指针
指针指向一个数组,数组的元素都是函数指针
拿上面的转移表(函数指针数组)来说明问题
int (*pfun[4])(int, int) = {Add, Sub, Mul, Div};
如果想要创建一个可以指向这个数组的指针,该如何书写
int (*(*ppfun)[4])(int, int);
ppfun = &pfun;
以上内容均属学习小结,供日后反刍,如有错误望指出,不胜感激!

被折叠的 条评论
为什么被折叠?



