- 1. 字符指针
- 2. 数组指针
- 3. 指针数组
- 4. 数组传参和指针传参
- 5. 函数指针
- 6. 函数指针数组
————————————————————————————————————————
回顾我们之前学过:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。 4. 指针的运算。
1.字符指针
char ch = 'w';
char* pc = &ch;//pc就是字符指针
//1.
char* p = "abcdef";//表达式的值是首元素地址
*p = 'w';//运行错误,常量字符串不能修改
//2.
char arr[] = "abcdef";
char* p = arr;//p指向的是数组的首元素,arr数组是可以修改的
*p = 'w';
printf("%s\n", arr);
首先,char*p=“abcdef” 对于初学者来说,可能认为把abcdef字符串放入指针p里面了,但是实质上是把abcdef的首元素地址"a"放入到p,同时*p存储的是一个常量字符串
所以,为了避免出错我们应该在前面加上const进行修饰:
const char*p ="abcdef";
例题:
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
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;
}
输出如下:
解释:
1.str1和str2在存储数据的时候会分别开辟两个空间存储字符串“hello bit.” ,两个数组在内存中存储的位置不同,同时数组名表示首元素地址,而str1和str2的首元素地址不相同,故str1和str2不等。
2.str3和str4是存储一个常量字符串的首地址,常量字符串又不能被改变因此在内存中共用一个常量字符串“hello bit”,所以str3和str4指向的是一个同一个常量字符串。所以str1和str2不同,str3和str4相同。
3.(C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化,不同的数组的时候就会开辟出不同的内存块)
注:&str3!=&str4;str3=str4;(因为&str3和&str4取得的都是str3和str4的首元素地址)
2.指针数组
指针数组是一个存放指针的数组。
字符数组——存放字符的数组:char arr1[10];
整形数组——存放整形的数组:int arr2[5];
字符指针数组——存放字符指针的数组:char* arr3[5];
整形指针数组——存放整形指针的数组:int* arr[6];
...
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组
例如1:
#include <stdio.h>
int main()
{
char* arr[] = { "abcdef","hehe","qwer" };
int i = 0;
for (i = 0; i < 3; i++)
{
printf("%s\n", arr[i]);
}
return 0;
}
输出结果:
arr[1]存放的是“abcdef”中的a的地址,arr[2]存放的是"hehe"中的h的地址,arr[3]存放的是"qwer"中的q的地址。
例如2:
#include<stdio.h>
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* arr[] = { arr1,arr2,arr3 };//arr是一个存放整形指针的数组
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", arr[i][j]);//也可以
printf("%d ", *(arr[i] + j));//arr[i]==*(arr+i)
}
printf("\n");
}
return 0;
}
3.数组指针
3.1数组指针的定义
数组指针是指针?还是数组? 答案是:指针。
整形指针: int * pint; 能够指向整形数据的指针,浮点型指针:float * pf; 能够指向浮点型数据的指针。那数组指针应该是:能够指向数组的指针。
下面代码哪个是数组指针?
int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
解释:
int *p1[10];
//指针数组,p先和[]结合
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合
数组指针就是用来存放地址的。
小结:
指针数组——是数组——是一种存放指针的数组
数组指针——是指针——是一种指向数组的指针——存放的是数组的地址
3.2 &数组名VS数组名
先放入结论
数组名绝大部分情况下是数组首元素的地址,但是有两个例外:
①sizeof(数组名)
sizeof内部单独放一个数组名的时候,数组名表示的整个数组,计算得到的是数组的总大小
②&arr
这里的数组名表示整个数组,取出的是整个数组的地址,从地址值的角度来讲和数组首元素的地址是一样的,但是意义不一样
问题导入
arr 和 &arr 分别是啥?
我们知道arr是数组名,数组名表示数组首元素的地址。 那&arr数组名到底是啥?
我们看下面这个代码
#include <stdio.h>
int main()
{
int arr[10] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可见数组名和&数组名打印的地址是一样的。 难道两个是一样的吗? 我们再看一段代码:
int main()
{
int arr[10] = { 0 };
//printf("%d\n", sizeof(arr));//40
printf("%p\n", arr);//int *
printf("%p\n", arr+1);//4
printf("%p\n", &arr[0]);//int*
printf("%p\n", &arr[0]+1);//4
printf("%p\n", &arr);//int(*)[10]
printf("%p\n", &arr+1);
int (*p)[10] = &arr;//p是一个数组指针
//int(*)[10]
return 0;
}
运行结果:
分析:
如下图所示,我们将输出对应起来,arr与arr+1之间相差4,arr[0]与arr[0]+1之间相差4,arr与arr+1相差0x28(0x28(十六进制)=40(十进制))。
第一组arr+1,arr类型是int*,因此arr+1跨越了4个字节;第二组arr[0]+1,类型也是int*,所以同理+1后也跨越4个字节,第三组&arr+1,&arr类型是int (*)arr[10],因此跨越40个字节。(注:如果把&arr放入指针,那么用数组指针,那么表示为int(*p)arr[10])
3.3数组指针的使用
1.打印数组
给一个数组部分代码,请完成对数组的打印:
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
}
方式1 通过下标的方式访问数组
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
方式2 使用指针来访问
int* p = arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
方式3 指针数组(不推荐)
int (* p)[10] = &arr;
for (i = 0; i < sz; i++)
{
printf("%d ", (*p)[i]);
}
//注:原因如下
//p---&arr
//*p---*&arr
//*p---arr
或者
int (* p)[10] = &arr;
for (i = 0; i < sz; i++)
{
printf("%d ", *((*p) + i));
}
这种方式不推荐的理由是有点大材小用,而且代码观感不是很好。
2.二维数组传参(简单讨论)
在讨论二维数组传参前,我们先回到一维数组传参。我们之前学习过一维数组传参有两种方式,分别是:一维数组传参,形参是数组和一维数组传参,形参是指针。
例如:如下代码,实现print函数打印数组arr[10]内的值。
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
print(arr, sz);
return 0;
}
①当一维数组传参,形参是数组时
//一维数组传参,形参是数组
void print1(int arr[10], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
②一维数组传参,形参是指针
//一维数组传参,形参是指针
void print(int *arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
//printf("%d ", arr[i]);
printf("%d ", *(arr+i));
}
printf("\n");
}
因此,二维数组传参也有两种:数组或指针
例如:如下代码,实现print函数打印数组arr[3][5]内的值。
int main()
{
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
print(arr, 3, 5);
return 0;
}
①形参是数组时
void print1(int arr[3][5], int r, int c)
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
②形参是指针时
void print(int(*arr)[5], int r, int c)
{
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
//printf("%d ", *(*(arr + i) + j));//arr[i]
printf("%d ", arr[i][j]);//arr[i]
}
printf("\n");
}
}
int(*arr)[5]的原因是:二维数组的数组名,表示首元素地址,同时二维数组的首元素是在第一行,首元素的地址就是第一行的地址,是一个一维数组的地址。因此传参需要把二维数组的首元素也就是第一行的地址传入参数,同时是一个指针故int(*arr)[5]
回顾并看看下面代码的意思
int arr[5]; 数组
int *parr1[10]; 指针数组
int (*parr2)[10]; 数组指针
int (*parr3[10])[5];parr3是数组,而数组中存放的是指针,该指针指向的又是数组(注意:不是二维数组)
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);
}
分析:
第一组:第一个可以,我们传的是参数,形参不需要规定数组的大小。第二个可以,形参传入的是数组。第三个也可以,这种判断需要我们观察传入的是什么,arr数组存的是int类型,因为arr是首元素地址,因此是int的地址,因此要用int的指针。
第二组:第一个可以,写成数组的形式。第二个可以,arr数组里面的元素是int*,而arr首元素是int*的地址,因此要放入二级指针的地址,故可以写成**arr
小结:一维数组传参,形参可以是数组,也可以是指针。同时注意当为形参时,注意类型。
4.2二维数组传参
void test(int arr[3][5])//ok1?
{}
void test(int arr[][])//ok2?
{}
void test(int arr[][5])//ok3?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok4?
{}
void test(int* arr[5])//ok5?
{}
void test(int (*arr)[5])//ok6?
{}
void test(int **arr)//ok7?
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
分析:
第一个可以,传入的是二维数组。第二个不可以,可以没有行但是必须知道有多少列,二维数组是连续存放的,只有知道多少列可以存放,才能把需要地址连续存放。因此第三个可以。
第四个不可以,二维数组的地址是首元素地址,也就是二维数组的第一行的地址,而第一行相当于一个一维数组的地址,这里arr是一个整形地址,因此不可以。第五个不可以,直接看这是一个int形的指针数组,和二维数组不是一个类型。第六个可以,第一行是一维数组的地址,因此用一个数组指针接受是可以的,同时存放5个整形。即存放5个整形的数组指针。第七个不可以,这里的写法是说来存放一级指针的地址,但是此时的二维数组存放的是一行的地址,用二级指针是不行的。
总结:
二维数组传参,参数可以是数组,也可以是指针;
如果是数组,行可以省略,但是列不可以省略;
如果是指针,传过去的是第一行的地址,形参就应该是数组指针。
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;
}
因此:一级指针形参的部分用一级指针接收即可
问:当一个函数的参数部分为一级指针的时候,函数能接收什么参数??
1、整形变量的地址 2、整形指针 3、整形数组的数组名
int a;
print(&a,10);//
int*p1 = &a;
print(pl,10);//
int arr[10];
print(arr,10);//
4.4二级指针传参
#include <stdio.h>
void test(int** ptr)//二级指针传参,参数是二级指针
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;//把一级指针p的地址放到二级指针pp里面去,二级指针存放一级指针的地址
test(pp);//形参是二级指针,传入二级指针
test(&p);//二级指针存放的是一级指针的地址
return 0;
}
二级指针传参形参就用二级指针来接收
问:当函数的参数为二级指针的时候,可以接收什么参数?
1、二级指针变量 2、一级指针变量地址 3、如果有int*arr[10]的整形数组,传入数组名是可以的
5.函数指针
整形指针-指向整形的指针 int*
字符指针-指向字符的指针 char*
数组指针-指向数组的指针 int arr[10] int(*p)[10]=&arr;
函数指针-指向函数的指针
如此类比需要存放函数的地址,函数也有地址吗?
经过在vs编译器上验证,&函数名得到的就是函数的地址。
同时注意:函数名和&函数名是都是函数的地址,没有区别。(不像数组名和&数组名)
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
可以通过函数地址来调用函数吗?答案是可以的。例如下面例子:
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;//pf函数指针来对函数进行调用
int ret = (*pf)(3, 5);//传参
printf("%d", ret);
return 0;
}
输出结果:
我们曾学过函数调用可以这样:
int ret =Add(3,5);
因此可以写成
int ret =pf(3,5);
//然后打印
printf("%d",ret);
输出结果:
但是不可以写成 int ret = *pf(3,5);
因此如果要加*号需要加括号,不加括号就不要加*;
同时(*pf)(3,5)对于编译器来说加多少个星号都是一样的。
阅读两段有趣的代码:
//代码1 (*(void (*)())0)();
//代码2 void (*signal(int , void(*)(int)))(int);
解释:
(*( void (*)() ) 0)();
//其中void (*)()是函数指针的一种类型,没有参数,返回类型为void
//放入括号( ( void (*)() ),强制类型转换
//()0,把0强制类型转换成指针的一种类型,比如int* double* float*的指针,但是这里是函数指针( void (*)() )
//*()0 ,解引用,解引用的时候去找调用0的地址的函数,然后调用函数需要传参,然后*()0(),后面未传参
1. 将0强制类型转换为void (*)() 类型的函数指针
2. 这就意味着0地址处放着一个函数,函数没参数,返回类型是void3. 调用0地址处的这个函数
4.这段代码其实是一次函数调用,调用的是0地址上的那个返回类型为void并且没有参数的函数
5.(* )()函数解引用*号可以不加,但现在是解读代码。
代码2
void (*signal(int, void(*)(int)))(int);
//signal是一个函数,其中int和void(*)(int)是函数的参数
//目前看函数缺少返回类型,而void(*)(int)是signal的返回类型
//你可以理解成void (* signal(int, void(*)(int) ) )(int);
1.上述的代码是一个函数的声明
2. 函数的名字是signal
3. signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针
4.该函数指针指向的函数参数是int,返回类型是void5.signal函数的返回类型也是一个函数指针
类型重命名实例
//类型重命名
typedef int* ptr_t;//已知
typedef void(*pf_t)(int);//写出 //因为typedef void(*)(int) pf_t不符合语法
//含义是:将void(*)(int)类型重新起个别名叫pf_t
int main()
{
//void (*signal(int, void(*)(int)))(int);//应用,将这句改写为
pf_t signal(int, pf_t);//函数参数是pf_t,其返回值也是pf_t
//注意必须定义在前才可以这样改写
}
注意:
typedef void(*pf_t2)(int);//pf_t2是类型名 void(*pf)(int);//pf是函数指针变量的名字
注:一般对类型名字重定义不会用#define
6. 函数指针数组
函数指针数组:数组的每一个元素是一个函数指针
数组是一个存放相同类型数据的存储空间,那我们已经学习了指针数组, 比如:
int *arr[10];
//数组的每个元素是int*
那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?
int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];
答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
6.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; } 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; } void menu() { printf("******************************\n"); printf("**** 1:add 2:sub ****\n"); printf("**** 3:mul 4:div ****\n"); printf("******************************\n"); } int main() { int x = 0; int y=0; int input = 0; int ret = 0; int (*pfArr[])(int, int) = {0,add,sub,mul,div };//故意选择5个元素下标分别从0-4 do { menu(); printf("请选择:>"); scanf("%d", &input); if (input == 0) { printf("退出程序\n"); break; } else if (input >= 1 && input <= 4) { printf("请输入两个操作数:>"); scanf("%d %d", &x, &y); ret = pfArr[input](x, y); printf("%d\n", ret); //如果写成printf("%d\n", &ret); 会报错;这里本来就是一个整形指针数组,数组类型为int*(刚开始写错了) //(vs警告)warning C4477: “printf”: 格式字符串“%d”需要类型“int”的参数,但可变参数 1 拥有了类型“int *” } else { printf("选择错误\n"); } } while (input); return 0; }
注:需要注意的是
1.关于(printf("%d\n", ret);)中ret没有取地址(当时编译时,我写错了)
把函数指针数组调用返回值赋值给ret(ret = pfArr[input](x, y);之后打印(print)不用再将ret取地址了,因为此时ret和数组里面的元素类型一样都是int*即(printf("%d\n", ret);)不要取地址否则会报错!
2.关于pfArr[]数组中第一个元素为0的原因
这里是想让程序是“输入‘0’退出程序”如此设定,另外也是数组下标从零开始和我们对数字的使用相差1,用来起到一个站位。
所以pfArr[]数组的下标为0的元素,可以写成“0”之外,也可以写成“NULL”."NULL"更加形象表现指针为空,为空指针。
小结:
int my_strlen(const char* str) { return 0; } int main() { //指针数组 char* arr[10]; //数组指针 int arr2[5] = { 0 }; int(*p)[5] = &arr2;//p是一个指向数组的指针变量 //函数指针 int (*pf)(const char*) = my_strlen;//pf是一个指向函数的函数指针变量 //调用:(*pf)("abcdef") //my_strlen("abcdef") //pf("abcdef") //函数指针数组 - 存放函数指针的数组 int (*pfArr[10])(const char*); return 0; }
(上片完)