​【指针与数组的恩怨情仇】

指针和数组的关系

94dbbfc7000c4bf98b72f61cf0f14597.png890011238ec94d418c107e5e31611ebf.png

  • 指针指的是指针变量,不是数组,指针变量的大小是4/8个字节,是专门来存放地址的。
  • 数组也不是指针,数组是一块连续的空间,存放一组相同类型的数据的。

 9475395abfe04e289b1757ab51821b0b.png

 没有关系,但是它们之间有比较相似的地方

以指针的形式访问和以数组的形式访问

#include <stdio.h>
#include <string.h>
int main()
{
    //这是指针
	char* str = "abcdef"; //str指针变量在栈上保存,“abcdef”在字符常量区,不可被修改
    //这是数组
	char arr[] = "abcdef"; //整个数组都在栈上保存,可以被修改

    //访问指针
	//1. 以指针的形式访问指针和以下标的形式访问指针
	printf("以指针的形式访问指针和以下标的形式访问指针\n");
	int len = strlen(str);
	for (int i = 0; i < len; i++) {
		printf("%c\t", *(str + i));
		printf("%c \n", str[i]);
	}
	printf("\n");

    //访问数组
	//2. 以指针的形式访问数组和以下标的形式访问数组
	printf("以指针的形式访问数组和以下标的形式访问数组\n");
	len = strlen(arr);
	for (int i = 0; i < len; i++) {
		printf("%c\t", *(arr + i));//数组名在大部分表达式中,代表的是首元素的地址
		printf("%c \n", arr[i]);
	}

	printf("\n");
	return 0;
}

结论:指针和数组指向或者表示一块空间的时候,访问方式是可以互通的,具有相似性。但是具有相似性,不代表是一个东西或者具有相关性。

7b41ea801ed24a8e9db584da1794367e.png

C为何要这样设计?

//我们初步阅读一下下面代码
#include <stdio.h>
#include <string.h>

//这里两种写法都行,大家都知道,函数传参是要发生降维的(为什么?)
//降维成指针。
//换句话说,arr在main里面是数组,传入InitArr函数之后,就成了数组首元素的指针变量。


//void InitArr(int arr[], int n)//这里的arr就不是数组了,它成了数组首元素的指针变量。
//降维后,此时arr就没有进行每个元素的临时拷贝,只进行了首元素地址的临时拷贝。
//在c中,任何函数调用,只要有形参实例化,必定形成临时拷贝
void InitArr(int* arr, int n)
{
    //sizeof(arr)结果为4 - 是32位平台下指针的大小。
	for (int i = 0; i < n; i++) 
	{
		*(arr + i) = i; //以指针的形式访问
	}
}

int main()
{
	int arr[5] = {1,2,3,4,5};
    //sizeof(arr)结果为20 - 是数组的大小。
    int num = sizeof(arr)/sizeof(arr[0]);//求数组元素的个数,[]为什么为0?因为0下标绝对存在
	InitArr(arr, num);
	for (int i = 0; i < num; i++) {
		printf("%d\n", arr[i]); //以数组的形式访问
	}
	return 0;
}

//上面代码其实没有什么问题,不过,不知道大家发现没有,\
如果没有将指针和数组元素访问打通,\
那么在C中(面向过程,函数是核心概念,离不开定义与调用函数)\
如果有大量的函数调用且有大量数组传参,会要求程序员进行各种访问习惯的变化。\
只要是要求人做的,那么就有提升代码出错的概率和调试的难度。


//假设指针和数组的访问方式方式不通用,\
程序员就需要不断地在不同的代码片段处,进行习惯的切换

//所以干脆,C将指针和数组的访问方式打通,让程序员在函数内,\
也好像使用数组那样进行元素访问,本质是减少了编程难度!

//打通之后,* 和[ ]两个操作符都可以对数组进行访问

//故整个降维过程,对于使用者是透明的,使用者不需要了解降维过程。

函数传参是要发生降维的

为什么?

        复杂数据类型通常需要较大的内存空间和更复杂的操作,直接传递复杂数据类型可能会导致效率低下,而且在栈上分配大块的内存也可能导致栈溢出的问题。

降维成什么?

        函数传参中的“降维”是指将复杂数据类型(如数组)降维成为指向其内部元素的指针来传递的过程。

比如:int arr[10];会降维成int类型的指针。

a 和 &a的区别  ----- 复习 + 练习

ae4f6047fbbd4b5e9fd511e4e9249e17.png

#include <stdio.h>
int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d %d\n", *(a + 1), *(ptr - 1));
	return 0;
}

b1289457e2a1461caf5df9744fac5b98.png

 a1bcd90600194134b1e80216a988f9b3.png

结论:&a叫做数组的地址,a做右值叫做数组首元素的地址,本质是类型不同,进而进行+-计算步长不同

  • &a:+1表示步长为sizeof(arr)。
  • a:+1表示步长为sizeof(arr[0])。

4922f6abc58f46ac920f955c1f4246b7.png

指针数组和数组指针

  • 指针数组:是由若干个指针所组成的数组,每个指针指向一个特定类型的变量。在指针数组中,每个元素都是一个指针变量。
  • 数组指针:是一个指向数组的指针变量。

a883755ece934399b21dd963aca51dad.png

 那么下面的到底那个是数组指针,那个是指针数组呢?

(A)、int* p1[10];

(B)、int(*p2)[10];

提示:

  • [ ]的优先级大于 *
  • ( )的优先级最大

3ad6c56362fc45d882d3b8b386871f15.png

74c21c12aee04d1791ea7241113b1dae.png

那要怎么使用它们呢?

  • 指针数组
#include <stdio.h>
int main()
{
	int* p1[5]; // 声明一个指针数组,数组中包含 5 个指向 int 类型变量的指针变量
	int a = 1, b = 2, c = 3, d = 4, e = 5;
	p1[0] = &a; // 将指针变量 p1[0] 设置为变量 a 的地址
	p1[1] = &b;
	p1[2] = &c;
	p1[3] = &d;
	p1[4] = &e;
	//这里的[]优先级比*高
	printf("%d %d %d %d %d\n", *p1[0], *p1[1], *p1[2], *p1[3], *p1[4]); // 输出 1 2 3 4 5
	return 0;
}
  • 数组指针
#include <stdio.h>
int main()
{
	int(*p2)[5]; // 声明一个数组指针,指向包含 5 个 int 类型变量的数组
	int a[5] = { 1, 2, 3, 4, 5 };
	p2 = &a; // 将指针变量 p 设置为数组 a 的首地址
	printf("%d %d %d %d %d\n", (*p2)[0], (*p2)[1], (*p2)[2], (*p2)[3], (*p2)[4]); // 输出 1 2 3 4 5
	return 0;
}

总结:

  • 整形指针数组,数组内部,后面可以放置任何类型(内置、结构体、联合体等)的内容。

        (A)、int* p1[10];

  • 整形数组指针,指针可以指向任何合法的类型变量。

        (B)、int(*p2)[10];

eb2db3edc5bb4bafbec58429b8ce8ed9.png

        我们发现我们在定义变量的都是先写变量的类型,然后再写变量名。

  • 提示:数组的数据类型是int [num]。
  • 数组指针一般使用的时候都是指向整个数组的地址。
  • 063cc2eef6484e1b83de4b87195dfb23.png
//变量类型为char,变量名为c
char c;
//变量类型为int,变量名为a
int a;
//变量类型为double*,变量名为b
double* b;


//那我们定义数组应该是
int[5] d;
//定义指针数组应该是
int*[5] p1;
//定义数组指针应该是
int[5]* p2


//但是实际上是

//变量类型是int[5]变量名是d
int d[5];
//变量类型是int*[5],变量名是p1
int *p1[5];  // ->  指针数组
//变量类型是int[5]*,变量名是p2
int (*p2)[5];// ->  数组指针

//这是c语言的规定写法

不过我们这里可以用typedef来改这个规定

#include<stdio.h>
typedef int* p_1[5];
typedef int(*p_2)[5];
int main()
{
	//变量类型是int*[5],变量名是p1
	int* p1[5];  // ->  指针数组
	p_1 p3;


	//变量类型是int[5]*,变量名是p2
	int(*p2)[5];// ->  数组指针
	p_2 p4;

	return 0;
}

f1c141a73c164274b93a846f002c42b8.png

总结:

  • 指针数组的数据类型是int *[num]。
  • 数组指针的数据类型是int[num] *。

 4bad51bd65fd4b4f9187c09954328296.png

//这两个是什么意思?

//指针数组
int *p[4];

//数组指针数组
int(*p[4])[5];
//p先和[4]结合,是一个数组,可以把这里的p[4]整体理解为上面的p,只不过此时的p是数组



//数组指针
int (*p)[4]

//数组指针数组的指针
int(*(*p)[4])[5];
//p先和*结合,是一个指针,然后指向一个[4]的数组,数组里面的内容是指针,\
而这个指针是指向一个[5]的指针

352f2032c7aa41739bfea22e773b6397.png

地址的强制转化

  • 先回答问题,强制类型转换,究竟在做什么?---> 将一种数据类型强制转换成另一种数据类型,在强制类型转换中,数据的内容不会发生改变,只是改变了数据的解释方式。

740007a342194160811fa08c1aeac8ac.png

  • 强制类型转换,和把字符串“1234”转化成int 1234的转化,有区别吗?---> 将字符串转换为整数是指将一个表示数字的字符串转换为整数类型,这种一定会改变数据本身,称为强制类型转化

结论:强制类型转化,改变的是对特定内容的看待方式,在C中,就是只改变其类型,对数               据的本身是不好发生任何变化的!

c713644f52af465ca29e8cf77231800e.png

#include<stdio.h>
int main()
{
	int a = 0x11223344;
	printf("%x\n", *(&a));

    //将整型的地址强制转化为字符型的地址,查看输出结果?
	printf("%x\n", *((char*)&a));
	
	return 0;
}

8931a5f26a8741d9b38989911f1d68fd.png

  1. 这也符合我们之前的结论,将整形指针类型的&a强制转化为字符型指针类型的&a, 但是&a的内容不会发生改变,只是改变了&a的解释方式。
  2. 然后由于char类型每次只能访问一个字节,且访问的时候都是开辟空间的众多字节中地址最小的那个字节。
  3. 由于我们目前电脑的是小端模式,拿出的是权值最低的那个字节,即44。

950b5931ed7744dc97029fc79114b2f6.png

#include <stdio.h>
struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p = (struct Test*)0x100000;
//假设p 的值为0x100000。 如下表表达式的值分别为多少?
int main()
{
	
	printf("%p\n", p + 0x1);//0x100000 + 20(0x14) == 0x100014

	//此时的p经过强制类型转化后,不再是指针了,而是一个无符号长整型的数据
	printf("%p\n", (unsigned long)p + 0x1);//0x100000(无符号长整型) + 1 = 0x100001

	//强制转换为无符号整形指针,+1跨过sizeof(int)个字节
	printf("%p\n", (unsigned int*)p + 0x1);//0x100000 + 4(0x04) = 0x100004

	return 0;
}

4e1074683da5457e83cde896daee03a6.png

#include <stdio.h>
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("0x%x,0x%x\n", ptr1[-1], *ptr2);
	//数组[]内只能为正数,这里为负数说明这里是指针,等价于*(ptr1-1)
	return 0;
}

9a5daa00984b4a21a3bfe76bb6bf176f.png

 c0f2911fed4a44138d77794b91a66a30.png

多维数组和多级指针

二维数组

基本概念

  • 几乎大部分书中所画的二维数组,都是矩阵样子,具体可以参考书中的图。
  • 但是,现在我们要在这里澄清,书中的图,最多只能称之为示意图,并非真的内存布局图。
  • 可以想象一些问题:如果按照书中矩阵样子画二维数组的话,那么三维数组,四维数组又该如何画呢?

二维数组的基本内存布局

#include <stdio.h>
int main()
{
	char a[3][4] = { 0 };
	int i = 0;
	for (i = 0; i < 3; i++) {
		int j = 0;
		for (j = 0; j < 4; j++) {
			printf("a[%d][%d] : %p\n", i, j, &a[i][j]);
		}
	}
	return 0;
}

ef0bfea80d3342cfb39100640a037c9a.png

结论:二维数组在内存地址空间排布上,也是线性连续且递增的。

二维数组如何画图

  1. 只有能够正确画出二维数组的布局图,才算真正能深刻理解二维数组的空间布局
  2. 以它为例:char a[3][4] = { 0 };

0bb0104e73f8494680d00636b6cb8423.png

  • 数组的定义是:具有相同数据元素类型的集合,特征是,数组中可以保存任意类型。
  • 那么数组中可以保存数组吗?      答案是可以!
  • 在理解上,我们甚至可以理解所有的数组都可以当成"一维数组"!(这样理解的好处,我们后面就说)
  • 就二维数组来说,我们认为二维数组,可以被看做“一维数组”,只不过内部“元素”也是一维数组
  • 那么内部一维数组是在内存中布局是“线性连续且递增”的,多个该一维数组构成另一个“一维数组”,那么整体便也是线性连续且递增的
  • 这也就解释了,上述地址为何是连续的。
  • 在强调一遍,我们认为:二维数组可以被看做内部元素是一维数组的一维数组。
#include <stdio.h>
int main()
{
	char a[3][4] = { 0 };

    //char* p = a;//a首元素(是一个数组,char[3]的地址)
    //a的类型是int(*p)[3]

    /*
        A:&a -> 二维数组的地址
        B:a -> 第一个一维数组的地址
        C:&a[0][0] -> 第一个数组的第一个元素的地址

        地址值都是:005BFAA4

        printf("%p\n", &a);
	    printf("%p\n", &a + 1);005BFAB0

	    printf("%p\n", a);
	    printf("%p\n", a+1);005BFAA8

	    printf("%p\n", &a[0][0]);
	    printf("%p\n", &a[0][0] + 1);005BFAA5
    */

    //所以这里需要强制类型转换
	char* p = (char*)a;
	for (int i = 0; i < 3 * 4; i++) {
		//printf("%p\n", &p[i]);
		printf("%p\n", p + i);
	}

	//用来对比
	for (int i = 0; i < 3; i++) {
		for (int j = 0; j < 4; j++) {
			printf("a[%d][%d] : %p\n", i, j, &a[i][j]);
		}
	}

	return 0;
}

e1e7890a985149d1a6ee95ea55c01c55.png

9d8f9055fbcc4515b5bc9d907fea0b1a.gif

#include<stdio.h>
int main()
{
    //下面的代码输出结果是什么呢?
	int a[3][4] = { 0 };

	printf("%d\n", sizeof(a)); 
	printf("%d\n", sizeof(a[0][0])); 
	printf("%d\n", sizeof(a[0])); 
	printf("%d\n", sizeof(a[0] + 1)); 
	printf("%d\n", sizeof(*(a[0] + 1))); 
	printf("%d\n", sizeof(a + 1)); 
	printf("%d\n", sizeof(*(a + 1))); 
	printf("%d\n", sizeof(&a[0] + 1)); 
	printf("%d\n", sizeof(*(&a[0] + 1))); 
	printf("%d\n", sizeof(*a)); 
	printf("%d\n", sizeof(a[3])); 

	return 0;
}

提示:数组名补充:两种情况代表整个数组,其他都是首元素地址

  1. sizeof(a):这里a必须单独出现,不能包含操作符的运算。
  2. &a

02cdaca411d143e59de6214bd8a6cb10.png

#include<stdio.h>
int main()
{
	/*
		数组名补充:两种情况代表整个数组,其他都是首元素地址
			sizeof(a) 这里a必须单独出现
			& a
	*/
	//所有的数组都可以看做成为"一维数组"
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a)); 
	//a代表整个数组,二维数组一共有3*4=12个元素,每个元素4个字节 - 48
	printf("%d\n", sizeof(a[0][0])); 
	//a[0][0]代表二维数组的第一个一维数组元素内的第一个整形  - 4
	printf("%d\n", sizeof(a[0])); 
	//a[0]代表二维数组的第一个一维数组,这个一维数组有4个元素 - 16
	printf("%d\n", sizeof(a[0] + 1)); 
	//a[0]是第一个一维数组, 由于只有两种情况代表整个数组,其他都是首元素地址,\
	a[0]+1这里进行了+1的操作,所以这里的a[0]就是一维数组的首元素地址,\
	+1跳过sizeof(int)个字节,获取的是&a[0][1]的地址 - 4
	printf("%d\n", sizeof(*(a[0] + 1))); 
	//由上一个题目可以得出来,这里的解引用获取的是a[0][1] - 4
	printf("%d\n", sizeof(a + 1)); 
	//这里是由于+1的操作,a就是首元素的地址,+1后就是第二个一维数组的地址 - 4
	printf("%d\n", sizeof(*(a + 1)));
	//由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16
	printf("%d\n", sizeof(&a[0] + 1)); 
	//&a[0]第一个数组的地址,+1就是第二个数组的地址 - 4
	printf("%d\n", sizeof(*(&a[0] + 1))); 
	//由上一个题目可以得出来,这里的解引用获取的是a[1]的所有元素 - 16
	printf("%d\n", sizeof(*a)); 
	//这里由于*操作,a就是首元素的地址,就是第一个一维数组的地址,*a - 16
	printf("%d\n", sizeof(a[3])); 
	//这里越界,代表二维数组的第四个一维数组 - 16

	return 0;
}

4da2f315141740a8afa9d46d5886ccf1.png

2f7744fbd940417598f66e0512312433.gif

数组a的定义为: int a[3][4];下面哪个不能表示a[1][1] ?

  • (A) 、*(&a[0][0]+5)
  • (B) 、*(*(a+1)+1)
  • (C)、*(&a[1]+1)
  • (D)、*(a[1]+1)

提示:

        A:&a -> 二维数组的地址
        B:a -> 第一个一维数组的地址
        C:&a[0][0] -> 第一个数组的第一个元素的地址

f153df52c5314fb0aa073f7232036a7c.png

daad2550a3f8469aa957d2da6d687c0f.png

 6fd10ba91e9148898942de98f3b09bec.png

what is the value of & p[4][2] - &a[4][2]?

#include<stdio.h>
int main()
{
	int a[5][5];//二维数组
	int(*p)[4];//数组指针
	p = (int(*)[4])a;//a是首元素地址
	printf("a_ptr = %p,p_ptr = %p\n", &a[4][2], &p[4][2]);
	printf("%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

c1977ae5fed34883ba332a52850cf98e.png

 90d2f33e0cf345ffbbbf00531bb65c22.jpeg

 二级指针

  • 指针变量是变量吗?是的
  • 变量有地址吗?有的
  • 地址是数据吗?是滴
  • 数据可以被其他变量保存吗? 当然可以

结论:指针变量也有地址。

#include <stdio.h>
int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	p = 100; //什么意思
	*p = 100; //什么意思
	pp = 100; //什么意思
	*pp = 100; //什么意思
	**pp = 100; //什么意思
	return 0;
}

af9047974f63491eabbbad7442b7f80d.png

  1. 将指针p的值赋为100,这意味着指针p现在指向内存地址100处的值。
  2. 将指针p指向的内存地址的值赋为100,即将a的值赋为100。
  3. 将指针pp的值赋为100,这意味着指针pp现在指向内存地址100处的值。
  4. 将指针pp指向的指针的值赋为100,这意味着指针p的值被修改为内存地址100。
  5. 将指针pp指向的指针所指向的内存地址的值赋为100,即将a的值赋为100。

3e35834ca83d4d88972611d4837a6c41.jpeg

数组参数和指针参数

一维数组传参

#include <stdio.h>
void show(int a[])
{
	printf("show: %d\n", sizeof(a));//4
}
int main()
{
	int a[10] = { 0 };
	printf("main: %d\n", sizeof(a));//40
	show(a);
	return 0;
} 

b56ccdeed3fd40a98a614c7cb913492a.png

  • 数组传参是要发生降维的
  • 为何要降维?不降维传数组会整体被拷贝一份,使调用函数成本变高
  • 降维成什么? 降维成指向其内部元素类型的指针
  • 有没有形成临时变量的拷贝?只要传参,就一定会发生临时变量的拷贝
//一维数组的元素个数是被忽略的。
//如何理解忽略?
#include <stdio.h>
void show(int a[], int num)
{
	printf("show: %d\n", sizeof(a));//4
	for (int i = 0; i < num; i++) {
		printf("%d\n", a[i]);
	}
}
int main()
{
	int a[10] = { 0 };
	int num = sizeof(a) / sizeof(a[0]);
	show(a, num);
	return 0;
}

一维数组的元素个数是被忽略的。

  • 如何理解忽略? 此时已结被降维成指针

一级指针传参

#include <stdio.h>
void test(char* p)
{
	//test: &p = 0093F890
	printf("test: &p = %p\n", &p);
}
int main()
{
	char* p = "hello world";
	//main: &p = 0093F964
	printf("main: &p = %p\n", &p);
	test(p);
	return 0;
}
  • 函数调用,指针作为参数,要不要发生拷贝?需要!因为指针变量,也是变量,在传参上,它也必须符合变量的要求,进行临时拷贝!
  • 结论:在C语言中,只要函数调用,必定发生拷贝。只不过要根据具体情况去决定,拷贝了什么,拷贝了多少!
  • 有没有可能直接把一个指针变量本身传递给指定函数?指针变量本身不能传递给函数,只能传递指针变量所在内存地址的副本。
  • 但是间接有可能。 通过二级指针或者返回值两种方法。
#include <stdio.h>
#define N 10
void GetStr(char* pp)
{
	pp = malloc(sizeof(char) * N);
	if (NULL != pp) {
		strcpy(pp, "hello");
	}
	else {
		//do nothing!
	}
}
int main()
{
	char* p = NULL;
	GetStr(p);//指针变量 - 传值操作
	//这里能不能打印出hello
	printf("%s\n", p);//hello
	return 0;
}
  • 在这个例子中,我们定义了一个指向char类型的指针变量p,并将其初始化为NULL。您将p作为参数传递给GetStr函数。在函数中,首先通过malloc函数为pp分配了一段内存,然后将字符串"hello"复制到pp所指向的内存中。
  • 然而,在函数结束时,pp指针变量超出了其作用域,它所指向的内存已经被释放。这意味着在GetStr函数结束后,p仍然是NULL指针,它指向的位置并没有被赋值为"hello"字符串。在main函数中,您尝试打印指针p指向的字符串,但由于p仍然是NULL指针,所以您不会看到任何输出。

9754a9bcd782415a95a498b4ba814a8a.jpeg

//二级指针的方法
#include <stdio.h>
#define N 10
void GetStr(char** pp)
{
	*pp = malloc(sizeof(char) * N);
	if (NULL != *pp) {
		strcpy(*pp, "hello");
	}
	else {
		//do nothing!
	}
}
int main()
{
	char* p = NULL;
	GetStr(&p);//指针变量的地址 - 传址操作
	//这里能不能打印出hello
	printf("%s\n", p);//hello
	return 0;
}

二维数组参数和二级指针参数

//二维数组传参
//要不要发生降维? 所有的数组传参,都要发生降维
//降维成什么? 降维成指针
//什么类型的指针呢?数组指针,仍然使指向其内部元素类型的指针
#include <stdio.h>
void show(char a[3][4])
//或者void show(char a[][4])//在二维数组传参的时候,只能一维度被省略
//所以也可以写成void show(char(*a)[4])
{
	printf("show: %d\n", sizeof(a));
}
int main()
{
	char a[3][4] = { 0 };
	printf("main: %d\n", sizeof(a));
	show(a);
	return 0;
}

9f8e438f73ed40e3bdb44ec4645cd6c5.png

#include <stdio.h>
void show(char a[3][4])
{
	printf("show: %d\n", sizeof(a));
}
int main()
{
	char a[3][4] = { 0 };
	printf("main: %d\n", sizeof(a));
	show(10); //故意写错
	return 0;
}

64a42b4839bf4a11a5355fc26c4f8955.png

结论:

  • 二维数组传参
  • 要不要发生降维? 所有的数组传参,都要发生降维
  • 降维成什么? 降维成指针
  • 什么类型的指针呢?数组指针,仍然是指向其内部元素类型的指针
  • 在二维数组传参的时候,只能一维度被省略?因为数组的下标也是数组类型的一部分,所以能二维度决定了参数的类型

任何维度的数组,传参的时候,都要发生降维,降维成指向其内部元素类型的指针。

那么,二维数组,内部“元素”是一维数组!那么降维成指向一维数组的指针。

1742b09a656347a6b6c7a54e1ef90052.jpeg

函数指针

函数指针的定义

        函数指针是指向函数的指针变量,它可以存储函数的地址,使得程序能够通过指针直接调用该函数。

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//函数是否有地址呢?
	//函数名和&函数名有区别吗?
	printf("0x%p\n", Add);
	printf("0x%p\n", &Add);
	return 0;
}

f0837a3dcb464e11bb006c0b21287d96.png

  • 函数是代码的一部分,程序运行的时候,也要加载到内存当中,以供CPU后续寻址访问,从上面的运行结果也知道,函数有地址, 函数名和&函数名没有区别,函数名和&函数名完全等价。

函数指针的使用

#include <stdio.h>
#include <string.h>
char* fun(char* s1, char* s2)
{
	int i = strcmp(s1, s2);
	if (0 == i) {
		return s1;
	}
	else {
		return s2;
	}
}
int main()
{
	//函数的数据类型是什么?
	//指针类型 (参数类型...)
	//char* (char*, char*);

	//也可以写成char* (*funp)(char*, char*) = &fun;
	char* (*funp)(char*, char*) = fun;

	//函数调用的多中方法
	char* s1 = funp("hello", "world");
	char* s2= (*funp)("hello", "world");
	char* s3 = fun("hello", "world");
	char* s4 = (*fun)("hello", "world");
	char* s5 = (&fun)("hello", "world");
	char* s6 = (*(&fun))("hello", "world");

	printf(s1);
	printf(s2);
	printf(s3);
	printf(s4);
	printf(s5);
	printf(s6);
	return 0;
}

小程序

d8b192f93304448b821eef64108314da.jpeg

#include<stdio.h>
#include<string.h>
static int flag;
void Welcome()
{
	printf("################################\n");
	printf("########欢迎来到王者荣耀########\n");
	printf("######## 1.play  0.exit ########\n");
	printf("################################\n");
	scanf("%d", &flag);
}
void GetGift()
{
	if(flag == 1)
		printf("恭喜你获取地狱火的皮肤!");
}
//王者荣耀欢迎界面和礼物赠送界面
void login(void (*Welcome)(),void(*GetGift)())
{
#define NAME "175区摘星楼"
#define PASSWD "123456"
	char name[32];
	char passwd[32];
	printf("Please Enter Your Name:>");
	scanf("%s", name);
	printf("Please Enter Your Passwd:>");
	scanf("%s", passwd);
	if ((strcmp(name, NAME) == 0) && (strcmp(passwd, PASSWD) == 0))
	{
		Welcome();
		GetGift();
	}
}
int main()
{
	login(Welcome, GetGift);
	return 0;
}

84277bdbbe2b4efca0671541440943da.png

 (*(void (*)())0)() - 这是什么

#include <stdio.h>
int main()
{
	
	int* p = 0;//NULL == (void*)0
	*p = 100;//取的是p的右值 *p == *0
	*(int*)0 = 100;//与上面等价

	(*            (void(*)())        0  )  ();//这里直接访问的是0的地址处
	//解引用    无参数函数指针类型      强转   无参数

    //严格意义上,是错的。因为参数不能传类型
	(*        (char** (*)(char**, char**))0)  (char**, char**);  
	//解引用    二级指针函数指针类型      强转    参数,参数
	return 0;
}

函数指针数组(4.7.4小节)

char* (*pf[3])(char *);

2034a0b39a234572a9668209ca57ca07.png

函数指针数组指针

char* (*(*pf)[3])(char *);

3f3f12bb4d304e34b9e3dd9dcfa95942.png

 9144ad303acf45d1a30f4d0982b580f1.jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/25764.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Netty】一行简单的writeAndFlush都做了哪些事(十八)

文章目录 前言一、源码分析1.1 ctx.writeAndFlush 的逻辑1.2 writeAndFlush 源码1.3 ChannelOutBoundBuff 类1.4 addMessage 方法1.5 addFlush 方法1.6 AbstractNioByteChannel 类 总结 前言 回顾Netty系列文章&#xff1a; Netty 概述&#xff08;一&#xff09;Netty 架构设…

好用的Chrome浏览器插件推荐(不定期更新)

好用的Chrome浏览器插件推荐 1.1 CSDN-浏览器助手1.2 Google 翻译1.3 JSON Viewer1.4 ModHeader - Modify HTTP headers1.5 Octotree - GitHub code tree 1.1 CSDN-浏览器助手 CSDN-浏览器助手 是一款集成本地书签、历史记录与 CSDN搜索(so.csdn.net) 的搜索工具 推荐&#x…

自动驾驶车载MCU开发修炼秘籍

目录 车载MCU开发修炼秘籍1、恩智浦 S32K1XX系列2、英飞凌 AURIX TC3XX3、嵌入式实时操作系统-FreeRTOS4、车载实时操作系统-AUTOSAR 车载MCU开发修炼秘籍 1、恩智浦 S32K1XX系列 S32K14X学习笔记&#xff08;一&#xff09;–S32K汽车MCU资源总结 S32K14X学习笔记&#xff1a…

第二章 数据类型、运算符与表达式

如何打开项目 如何打开已经存在的解决方案&#xff1f; 找到要打开的解决方案目录&#xff0c;进去之后双击后缀为.sln的文件即可打开该解决方案。 或者从最近打开项目中打开&#xff1a; Online Judge使用 OJ简介 在线判题系统&#xff08;Online Judge&#xff0c;缩写OJ…

WebService接口测试

WebService的理解 WebService就是Web服务的意思&#xff0c;对应的应用层协议为SOAP&#xff08;相当于HTTP协议&#xff09;&#xff0c;可理解为远程调用技术。 特点&#xff1a; 客户端发送的请求主体内容&#xff08;请求报文&#xff09;的格式为XML格式 接口返回的响…

【P36】JMeter 交替控制器(Interleave Controller)

文章目录 一、交替控制器&#xff08;Interleave Controller&#xff09;参数说明二、测试计划设计 一、交替控制器&#xff08;Interleave Controller&#xff09;参数说明 可以将内部的组件在线程迭代时交替执行&#xff1b;交替控制器内部一般会有多个取样器 选择线程组右…

黑马Redis视频教程高级篇(一:分布式缓存)

目录 分布式缓存 一、Redis持久化 1.1、RDB持久化 1.1.1、执行时机 1.1.2、RDB原理 1.1.3、小结 1.2、OF持久化 1.2.1、AOF原理 1.2.2、OF配置 1.2.3、AOF文件重写 1.3、RDB与AOF对比 二、Redis主从 2.1、搭建主从架构 2.1.1、集群结构 2.1.2、准备实例和配置 …

多层级table联动

elementui 多层级table联动&#xff1a; 引用&#xff1a; https://blog.csdn.net/weixin_44780971/article/details/130054925 https://blog.csdn.net/qq_42581563/article/details/114325920 需要了解的属性&#xff1a; select-all 全选的时候执行select &#xff1a; 选择…

linux高级---k8s中的五种控制器

文章目录 一、k8s的控制器类型二、pod与控制器之间的关系三、状态与无状态化对特点四、Deployment1、Deployment的资源清单文件2、在配置清单中调用deployment控制器3、镜像更新4、金丝雀发布5、删除Deployment 五、Statefulset六、DaemonSet1、daemonset的资源清单文件2、在配…

点到直线距离

点到直线距离最小二乘解释 推倒部分 形象描述是C到AB距离最短&#xff0c;也就是CD最短用数学语言描述是 m i n ∣ ∣ ( B − A ) λ A − C ∣ ∣ min||(B-A) \lambda A - C || min∣∣(B−A)λA−C∣∣ 其中 D ( B − A ) λ A D (B-A) \lambda A D(B−A)λA,其实本质…

使用Windbg动态调试目标进程的一般步骤及相关要点详解

目录 1、概述 2、将Windbg附加到已经启动起来的目标进程上&#xff0c;或者用Windbg启动目标程序 2.1、将Windbg附加到已经启动起来的目标进程上 2.2、用Windbg启动目标程序 2.3、Windbg关联到目标进程上会中断下来&#xff0c;输入g命令将该中断跳过去 3、分析实例说明 …

macOS Ventura 13.5beta2 (22G5038d)发布

系统介绍 黑果魏叔 6 月 1 日消息&#xff0c;苹果今日向 Mac 电脑用户推送了 macOS 13.5 开发者预览版 Beta 2 更新&#xff08;内部版本号&#xff1a;22G5038d&#xff09;&#xff0c;本次更新距离上次发布隔了 12 天。 macOS Ventura 带来了台前调度、连续互通相机、Fac…

FPGA基于AXI 1G/2.5G Ethernet Subsystem实现千兆UDP通信 提供工程源码和技术支持

目录 1、前言2、我这里已有的UDP方案3、详细设计方案传统UDP网络通信方案本方案详细设计说明UDP层设计AXIS-FIFOAXI 1G/2.5G Ethernet Subsystem&#xff1a;输出 4、vivado工程详解5、上板调试验证并演示系统配置UDP数据回环测试注意事项 6、福利&#xff1a;工程代码的获取 1…

RK3588平台开发系列讲解(项目篇)RKNN-Toolkit2 的使用

平台内核版本安卓版本RK3588Linux 5.10Android 12文章目录 一、RKNN-Toolkit2安装二、模型转换和模型推理三、性能和内存评估沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 NPU 是专门用于神经网络的处理单元。它旨在加速人工智能领域的神经网络算法,如机器视觉和自…

如何在 Linux 中进行网络地址转换 (NAT)?

网络地址转换&#xff08;Network Address Translation&#xff0c;简称NAT&#xff09;是一种在网络中使用的技术&#xff0c;它允许将私有网络中的IP地址映射到公共网络上&#xff0c;从而实现多个设备共享单个公共IP地址。在Linux系统中&#xff0c;我们可以使用一些工具和配…

《Web安全基础》01. 基础知识

基础 1&#xff1a;概念名词1.1&#xff1a;域名1.2&#xff1a;DNS1.3&#xff1a;网站开发语言1.4&#xff1a;后门1.5&#xff1a;Web1.6&#xff1a;Web 相关安全漏洞 2&#xff1a;数据包2.1&#xff1a;HTTP2.2&#xff1a;HTTPS2.3&#xff1a;请求数据包2.3.1&#xff…

MySQL 数据操纵语言 DML

文章目录 数据操纵语言 DMLINSERT 语句UPDATE 语句DELETE 语句 数据操纵语言 DML 数据操纵语言&#xff08;Data Manipulation Language&#xff0c;DML&#xff09;是 SQL 语言的核心部分之一。在添加、更新或者删除表中的数据时&#xff0c;需要执行 DML 语句。很多时候我们提…

03 【数据代理 事件处理】

03 【数据代理 事件处理】 1.数据代理 了解数据代理需要js的一些知识&#xff1a;Object.defineProperty()&#xff0c;属性标志&#xff0c;属性描述符&#xff0c;getter&#xff0c;setter。。。 1.1数据代理 建议学习文章地址&#xff1a; https://zh.javascript.info/p…

软考A计划-试题模拟含答案解析-卷十三

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff0c;以及各种资源分享&am…

docker可视化管理工具portainer忘记密码重置教程

目录 前言&#xff1a; 1 停止portainer容器 2 借助仓库 portainer/helper-reset-password 重置密码 3 重新启动portainer容器 4 验证是否修改成功 5 修改登录密码 前言&#xff1a; 由于学习的深入&#xff0c;各种账号密码实在是太多了&#xff0c;建议各位配置账号密…