【C语言】带你层层深入指针——指针详解2

【C语言】带你层层深入指针------指针详解2

前言:
书接上回,由于文章过长,所以分了几篇文
若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持
谢谢大家 ! ! !

四、各种指针变量类型

1.字符指针变量

将常量字符串的地址串给指针时,
指针实际接收的是字符串的首字符地址

如下代码演示:

c 复制代码
#include <stdio.h>

int main()
{
	char* p = "abcdefg";
	//将字符串首字符地址给p
	printf("%c\n\n", *p);
	//p是首字符地址,故会打印" a "
	printf("%s\n\n", p);
	//使用%s打印字符串时,
	// 只需提供首字符的地址就行
	//无需 *p

	return 0;
}

运行结果:

另外:
当多个指针指向同一个常量字符串时它们的地址值相同

代码演示:

c 复制代码
#include <stdio.h>

int main()
{
	char* p1 = "abcdefg";
	char* p2 = "abcdefg";
	//p1,p2指向同一个常量字符串
	
	if (p1 == p2)
	{
		printf("p1==p2\n\n");
	}
	else
		printf("p1!=p2\n\n");

	return 0;
}

运行结果:

结果显示:
当多个指针指向同一个常量字符串时,它们的地址值相同,
同一个常量字符串他们在内存中占用一块空间

2.数组指针变量

(1)指针与数组

数组名是首元素地址
arr == &arr[0] ;
但是也有例外:

1.sizeof( arr ) ;这里计算的是整个数组

2.&arr; 这里 arr 表示整个数组,取出的是整个数组的地址

用以下各个值 +1 为例:
代码演示:

c 复制代码
#include <stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };

	printf("%p\n", arr);
	printf("%p\n\n", arr + 1);

	printf("%p\n", &arr[0]);
	printf("%p\n\n", &arr[0] + 1);

	printf("%p\n", &arr);
	printf("%p\n\n", &arr + 1);

	return 0;
}

运行结果:

(地址为十六进制)

通过运行结果,可以看到arr 和 &arr[ 0 ] 结果一致

可以见得数组名是首元素地址,二者可等价(除去例外)

但是,&arr是整个数组地址,+1 跳过整个数组

故地址 + 40(10 个 int 类型)

指针访问数组
int arr[10] = { 1,2,3,4,5,6 };
int * p = arr ;
printf( " %d " , *( p + 2) ) ;
*( arr + i ) <=> arr [ i ] ;

(2)数组指针变量

创建一个数组指针变量:

c 复制代码
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int (*p)[10] = &arr;

在上面代码中,p 是数组指针变量,存放的是数组的地址
p 和 &arr 的类型是 int ( * ) [ 10 ] ;

(3) 一维数组传参本质

首先, arr 是数组名,是首元素地址
一维数组传参的本质是传递数组首元素的地址,而非整个数组
即使形参写成数组形式,本质上仍然是一个指针变量

以以下代码为例:
形参声明为int arr[10]时,编译器实际处理为int arr

c 复制代码
#include <stdio.h>

void test(int arr[10])
{
		int sz = sizeof(arr) / sizeof(arr[0]);
		printf("%d\n\n", sz);
}

int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	
	test(arr);

	return 0;
}

运行结果:

为什么结果 sz 为 1 呢?

难道在该函数中 arr = arr [ 0 ] ?

确实如此,看似形参是 int arr [ 10 ] ,但是本质上是一个指向 arr 首元素地址的指针,也就是 arr [ 0 ] 。

所以,
一维数组传参的本质是传递数组首元素的地址,而非整个数组
即使形参写成数组形式,本质上仍然是一个指针变量

且注意:
不能像上述代码中在函数内部使用形参计算数组元素个数

(4) 二维数组传参本质

类比一维数组,一维数组传参时数组名是首元素地址,形参可写成数组或指针形式
二维数组名也是首元素地址,但二维数组相当于一维数组。
故二维数组的数组的数组名就是第一行一维数组的地址
因此二维数组传参时,形参可以写成数组形式或指向一维数组的指针。

代码演示(内有注释):

c 复制代码
#include <stdio.h>

void test(int (*arr)[5], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(arr + i) + j));
			//此处等价于 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 };

	test(arr, 3, 5);
	//传参本质是第一行地址
	//故用int (*arr)[5]接收
	//[5]表示五行一维数组

	return 0;
}

运行结果:

二维数组名也是首元素地址,二维数组相当于一维数组

所以对于 int arr [ 3 ] [ 5 ] 来说:

第一行{1,2,3,4,5}是第一个元素

第二行{2,3,4,5,6}是第二个元素

第三行{3,4,5,6,7}是第三个元素

int arr [ 0 ] [ ] 就是第一行

int arr [ 1 ] [ ] 就是第二行

且注意:
arr + i 得到第i行的地址

*( arr + i ) 解引用得到第i行数组名

*( arr + i ) + j 得到第i行第j列元素的地址
最后再解引用 * ( * ( arr + i ) + j ) 得到访问具体元素
通过双重解引用 * ( * ( arr + i ) + j ) 访问具体元素,等价于 arr [ i ] [ j ] 的写法。

3.函数指针变量

函数指针变量用于存放函数地址,通过地址能够调用函数

与整型指针、数组指针类比,函数指针也是一种特殊的指针类型。

那么该如何使用函数指针变量呢?

  • 声明语法 :函数指针声明需包含返回类型参数列表,如 void ( * pf ) ( int , int ) 表示指向返回值为 void 类型且接受两个 int 参数的函数的指针。
  • 赋值方式 :可以直接将函数名赋给指针变量(函数名即地址),如 pf = Add
  • 调用方法:通过指针调用函数时,可以使用 ( * pf ) ( arg1, arg2 ) 或简写 pf ( arg1 , arg2) 两种等效形式。相当于 Add ( arg1 , arg2 ) .

代码演示:

c 复制代码
#include <stdio.h>

int Add(int a, int b)
{
	return a + b;
}

int main()
{
	int (*pf)(int, int) = &Add;

	printf("%d\n\n", pf(4, 5));

	return 0;
}

运行结果:

五、指针的使用

1.传址调用

现在,我们要写一个函数,直接使主函数变量的值改变
那么该怎么写呢?

我们可以想到将主函数变量传参到函数中直接改变,如下代码:

c 复制代码
#include <stdio.h>

void F(int g)
{
	g = 100;
}

int main()
{
	int g = 10;
	F(g);

	printf("%d\n\n", g);

	return 0;
}

这样我们好像成功将 g 的值改为了100,可事实真的如此吗?
运行结果:

结果仍然是 10

这是为什么呢?

因为,上述方法是传值调用,当实参传给形参时,修改形参不会影响实参,形参只是实参的临时拷贝

而要使主函数变量的值直接改变就要用到传址调用
将主函数变量的地址传给函数,形参指向实参的内存,再在函数中通过地址对变量进行直接修改
代码演示:

c 复制代码
#include <stdio.h>

void F(int* p)
{
	*p = 100;
}

int main()
{
	int g = 10;
	int* p = &g;//拿到g的地址

	F(p);//将g的地址传给函数

	printf("%d\n\n", g);

	return 0;
}

运行结果:

可以见得,在传址调用下,g 的值直接被改变了

故传址调用可以让函数和主函数建立真正的联系,若要在函数中修改主函数的值,只能用传址调用

2.指针数组

用于存放指针的数组,每个元素都是指针类型

例如:

c 复制代码
int * arr[3] = { &a,&b,&c };

知道了指针数组,现在我们就可以用指针数组来模拟二维数组。
代码演示:

c 复制代码
#include <stdio.h>

int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 6,7,8,9,10 };
	int arr3[5] = { 11,12,13,14,15 };
	int* arr[3] = { arr1,arr2,arr3 };
	//创造一个二维数组
	
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
		{
			printf("%d ", arr[i][j]);//打印二维数组
		}
		printf("\n");
	}

	return 0;
}

运行结果:

3.函数指针数组(简易的计算器的实现)

有了数组指针,可以放置地址的数组,那么是不是可以将函数的地址放入数组中呢?

这就是函数指针数组

现在有 +(加) -(减) * (乘) / (除)四个函数

我们可以创建一个数组将其容纳,这就是函数指针数组

c 复制代码
float (*Calc[5])(float, float) = { 0,Add,Sub,Mul,Div };

float ( * Calc [ 5 ] ) ( float , float ) 就是函数指针数组
前面的 flaot 表示函数的返回值类型是 float 类型
后面的两个float 表示函数的两个参数是 float 类型

里面存放了加减乘除四种函数,可以进行四种计算,所以我们可以用该函数指针数组来做一个简易的计算器
代码演示:

c 复制代码
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

float Add(float x, float y)//加法
{
	return x + y;
}

float Sub(float x, float y)//减法
{
	return x - y;
}

float Mul(float x, float y)//乘法
{
	return x * y;
}

float Div(float x, float y)//除法
{
	return x / y;
}

void menu()
{
	printf("******************************\n"); 
	printf("******  简 易 计 算 器  ******\n");
	printf("******   1.Add   2.Sub  ******\n");
	printf("******   3.Mul   4.Div  ******\n");
	printf("******   0.exit         ******\n");
	printf("******************************\n");
	printf("请输入:");
}

int main()
{
	int input;
	float  x, y;
	float (*Calc[5])(float, float) = { 0,Add,Sub,Mul,Div };//给数组前巧妙加一个0可以更方便地调用函数
	do 
	{
		menu();
		scanf("%d", &input);
		if (input == 1 || input == 2 || input == 3 || input == 4)
		{
			printf("请输入要计算的值:");
			scanf("%f %f", &x, &y);
			printf("结果是:%.2f\n\n", Calc[input](x, y));
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输入错误,请重新输入 \n");
		}
	} while (input);

	return 0;
}

运行结果:

最后注意:

函数指针数组中的所有函数的参数和类型必须要一致

结语

本期资料来自于:

https://legacy.cplusplus.com/

OK,本期的指针详解到这里就结束了
由于文章过长,所以分了几篇文
若内容对大家有所帮助,可以收藏慢慢看,感谢大家支持
本文有若有不足之处,希望各位兄弟们能给出宝贵的意见。谢谢大家!!!
新人,本期制作不易希望各位兄弟们能动动小手,三连走一走!!!
支持一下(三连必回QwQ)

相关推荐
狮子座的男孩1 小时前
js函数高级:04、详解执行上下文与执行上下文栈(变量提升与函数提升、执行上下文、执行上下文栈)及相关面试题
前端·javascript·经验分享·变量提升与函数提升·执行上下文·执行上下文栈·相关面试题
cookies_s_s1 小时前
项目--协程库(C++)前置知识篇
linux·服务器·c++
koo3641 小时前
pytorch深度学习笔记
pytorch·笔记·深度学习
Monly211 小时前
Java八股文:Redis篇
java·开发语言·redis
爱学习的程序媛1 小时前
《JavaScript权威指南》核心知识点梳理
开发语言·前端·javascript·ecmascript
帮帮志1 小时前
【AI大模型对话】流式输出和非流式输出的定义和区别
开发语言·人工智能·python·大模型·anaconda
陈奕昆1 小时前
n8n实战营Day1课时2:核心概念拆解+天气提醒工作流实操
开发语言·人工智能·n8n
zmzb01031 小时前
C++课后习题训练记录Day39
数据结构·c++·算法
jquerybootstrap1 小时前
大地2000转经纬度坐标
linux·开发语言·python