给大家分享一句我很喜欢我话:
知不足而奋进,望远山而前行!!!
铁铁们,成功的路上必然是孤独且艰难的,但是我们不可以放弃,远山就在前方,但我们能力仍然不足,所有我们更要奋进前行!!!
今天我们更新了指针进阶(3)内容,
🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
一、数组指针变量
1.1数组指针变量是什么?
之前我们学习的指针数组,数组中存放的是一种数组,数组中存放的是地址(指针)。
数组指针变量是指针变量?还是数组?
答案是:指针!
例如我们前面所学的:
字符指针:char*指向字符的指针
整形指针:int*指向整形的指针
浮点型指针:float*指向浮点型的指针
那么数组指针呢,应该就是指向数组的指针,
#include<stdio.h>
int main()
{
int a = 10;
int* pa=&a;
char ch = 'w';
char* pc = &ch;
int arr[10] = { 0 };
int* p= &arr;//取出的是数组的地址
return 0;
}
所以数组指针变量就是:存放的是数组的地址,能够指向数组的指针变量。
大家看一下下面这两个哪个是数组指针?
int* p1[10];
int (*p2)[10];
我们来分析一下:
第一个是不带(),然后p1首先和方块结合,然后与*结合,所以这个p1就是一个数组,并且是存放指针的数组,即指针数组。
第二个p2与*在一起,所以p2是一个指针变量,指向的是数组。即数组指针。
1.2指针变量的初始化
-
指针也是一种数据类型,指针变量也是一种变量
-
指针变量指向谁,就把谁的地址赋值给指针变量
-
“*”操作符操作的是指针变量指向的内存空间
#include <stdio.h>
int main()
{
int a = 0;
char b = 100;
printf("%p, %p\n", &a, &b); //打印a, b的地址
//int *代表是一种数据类型,int*指针类型,p才是变量名
//定义了一个指针类型的变量,可以指向一个int类型变量的地址
int *p;
p = &a;//将a的地址赋值给变量p,p也是一个变量,值是一个内存地址编号
printf("%d\n", *p);//p指向了a的地址,*p就是a的值
char *p1 = &b;
printf("%c\n", *p1);//*p1指向了b的地址,*p1就是b的值
return 0;
}
注意:&可以取得一个变量在内存中的地址。但是,不能取寄存器变量,因为寄存器变量不在内存里,而在CPU里面,所以是没有地址的。
1.3二维数组的传参
按我们之前所学的,二维数组的传参一般都是按下面这种形式去传参:
#include<stdio.h>
void print(int arr[3][5],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][5] = { {1,2,3,4,5},{2,3,4,5,6} ,{3,4,5,6,7} };
print(arr, 3, 5);
return 0;
}
这样便实现了数组的传参。
如果我们想要利用数组进行传参的话该怎么去做呢?
其实,二维数组的数组名也是数组首元素的地址,那么到底是谁的地址?
首元素其实就是第一行,首元素的地址就是第一行的地址,第一行的地址就是一维数组的地址!
将新的函数写成这种形式即可:
#include<stdio.h>
int print(int (*arr)[5], 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][5] = { {1,2,3,4,5},{2,3,4,5,6} ,{3,4,5,6,7} };
print(arr, 3, 5);
return 0;
}
这样也可以得到上面那种效果。
二、函数指针变量
函数指针是什么呢?是指向函数的指针吗?存放的是函数的地址吗?
一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。
然后通过指针变量就可以找到并调用这个函数。
我们把这种指向函数的指针变量称为“函数指针变量”。
2.1函数指针变量的创建
函数编译之后,也会占用一定的内存空间,那么也就具有地址。把这个函数的地址,就叫做函数的指针。把函数的指针就叫做函数的入口地址。
定义的一般形式:函数的返回值类型 (*指针变量名)(形式参数1, 形式参数2, …);
int sum(int a, int b)
{
return a+b;
}
int main(void)
{
// 定义一个指针变量p,指向sum函数
int (*p)(int a, int b) = sum;
// 或者 int (*p)(int, int) = sum;
// 或者 int (*p)() = sum;
// 利用指针变量p调用函数
int result = (*p)(1, 3);
// 或者 int result = p(1, 3);
printf("%d\n", result);//4
return 0;
}
2.2函数指针变量的使用
这里我们举一个使用函数指针的例子:
#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");
}
void calc(int (*pf)(int, int)) {
int x = 0, y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d%d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
int main()
{
int input = 0;
do {
menu();
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:
break;
default:
printf("输入错误,请重新选择。");
break;
}
} while (input);
return 0;
}
这是一个简易的计算机模型,大家自己看一下是怎么实现的。
2.3两段有趣代码
第一段:
int main()
{
(*(void(*)() ) 0 )();
return 0;
}
大家一起来看一下这串代码,先自己分析一下,然后我带大家一起来分析。
我们先来看最里面的一段,void (*p)(),这一段其实就是一段利用指针进行函数传参的过程。
然后( void(*)() )--这是在进行 强制类型转换
(void (*)())0 --将0强制转化为void(*)()的函数指针类型
//这些就意味着我们假设0 地址处放着无参,返回类型是void的函数
//意味着是调用0地址处放的函数
第二段:
void (*signal(int,void(*)(int)))(int);
这段代码是一个函数声明,它声明了一个名为
signal
的函数。函数的返回类型是一个指向函数指针的指针。参数列表包括一个int
类型的参数和一个指向函数的指针,该函数接受一个int
类型的参数并返回void
。简单来说,这个函数
signal
允许我们传入一个整数和一个函数指针,并返回一个指向函数指针的指针。这个函数指针可用于处理信号的响应。通常情况下,
signal
函数用于设置信号处理函数,以便在接收到特定信号时执行相应的操作。
2.4 typedef 关键字
本科学过C语言的朋友都知道数据类型的概念。C语言中有整型,如char、int、short等等;也有浮点类型,如float、double。
1.typedef关键字就是给这些数据类型起一个别的名字,然后用这个自己起的名字来定义变量。比如如下语句就把char类型起了个新的名字叫int8。然后用int8定义了一个变量a1。
typedef char int8;
int8 a1;
2.简化复杂的类型声明:typedef 可以简化复杂的类型声明,使得类型声明更加简洁明了。
例如,下面的语句将一个指向函数的指针类型定义为 FUNC_PTR:
typedef int (*FUNC_PTR)(int, int);
然后就可以在程序中使用 FUNC_PTR 来代替这个复杂的类型声明,例如:
FUNC_PTR fp = add; // add 是一个函数,与 FUNC_PTR 类型匹配
3.提高代码可移植性:由于不同的编译器对数据类型的定义可能存在差异,使用 typedef 可以提高代码的可移植性。
例如,如果使用 short int 类型来定义变量,可能在不同的编译器上会有不同的长度,导致代码的可移植性受到影响。使用 typedef 定义一个新的类型名称,可以确保该类型在不同的编译器上都具有相同的长度和语义。
总之,typedef 是一个非常有用的关键字,它可以提高代码的可读性、简化复杂的类型声明,以及提高代码的可移植性。