【C进阶】指针进阶(1)_二次复习版(下)

简介: 【C进阶】指针进阶(1)_二次复习版(下)

4. 数组参数、指针参数


4.1 一维数组传参

#include <stdio.h>
void test(int arr[])//1 ok
{}
void test(int arr[10])//2 ok
{}
void test(int* arr)//3 ok
{}
void test2(int* arr[20])//4 ok
{}
void test2(int** arr)//5 ok
{}
int main()
{
  int arr[10] = { 0 };
  int* arr2[20] = { 0 };
  test(arr);//&arr[0]
  test2(arr2);//&arr2[0]
}


1.数组传参,形参部分写出数组,可以不指定大小,指定类型即可


2.数组传参,数组接收,大小指定10个元素,可以


总结:1和2是数组传参数数组接收


3.arr[10]数组10个元素,每个元素都是int,数组名表示首元素地址(整型的地址),就应该放到int指针里去 3.是数组传参指针接收


4.数组传参可以写成一样的,大小可以不写:int *arr[]


5.数组名表示首元素地址,每个元素都是int*的地址,是一级指针地址,可以用二级指针接收,不可以写成int* arr


总结:

一维数组传参,形参可以是数组,也可以是指针的当时形参是指针的时候,要注意类型


4.2 二维数组传参

void test(int arr[3][5])//1 ok
{}
void test(int arr[][])//2 no
{}
void test(int arr[][5])//3 ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。//这样才方便计算
void test(int *arr)//4 no
{}
void test(int* arr[5])//5 no
{}
void test(int (*arr)[5])//6 ok
{}
void test(int **arr)//7 no
{}
int main()
{
 int arr[3][5] = {0};
 test(arr);
//二维数组传参,参数可以是指针,也可以是数组
//如果是数组,行可以省略,但是列不能省略
//如果是指针,传过去的是第一行的地址,形参就应该是数组指针
}


1.可以写成一模一样的


2.二维数组传参,函数形参的设计只能省略第一个[]的数字。因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素,为什么❓


3.正确写法。


4.数组传参,要么写成数组要么写成指针,一级指针,不对,应该是数组指针


5.数组传参,形参要么写成数组,要么指针。但是这个形参是个数组,但又不是二维数组,能存指针但又不是指针。(指针数组)


6.数组前有(*arr),说明是指针,指针指向的[]是数组,数组5个元素,每个元素是int(一行五个元素)


7.看传过来的类型是什么,二级指针是用来接收一级指针的地址的,而arr传的是第一行的地址


总结:

二维数组传参,参数可以是指针,也可以是数组

  1. 如果是数组,行可以省略,但是列不能省略
  2. 如果是指针,传过去的是第一行的地址,形参就应该是数组指针

     对2.的解释

在许多编程语言中,对于二维数组(或者说是矩阵),通常需要知道每一行包含多少元素。这是因为二维数组在内存中通常是连续存储的,所以我们需要知道每行有多少元素来正确地在内存中定位元素。


例如,如果我们有一个二维数组,每行有n个元素,那么第i行第j个元素在内存中的位置就是i * n + j。这种计算方式基于数组的第一个元素位置为0,以及数组的行和列都是从0开始计数。


这就是为什么在声明和使用二维数组时,必须知道一行有多少元素的原因。如果你不知道每行的元素数,你就无法正确地在内存中定位到你要访问的元素。


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;
}


p是一级指针传参,一级指针接收即可

d0e2574cee6c4edd85e6d8b494bd0037.png

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;
 test(pp);//二级指针,指向了一级指针的地址
 test(&p);//一级指针地址
 return 0;
}


二级指针传参,可以传一级指针地址,也可以传二级指针。

241a6bcfb90d4b98b3d8b09fd0a0ee39.png


5.函数指针

类比:

整型指针–指向整型的指针int*

字符指针–指向字符的指针char*

数组指针–指向数组的指针int arr[10];

函数指针–指向函数的指针

数组: 数组名和&数组名是有区别的

函数:函数名和&函数名都是函数的地址,没有区别

int Add(int x,int y)
{
  return x + y;
}
int main()
{
    printf("%p\n", &Add);
  printf("%p\n", Add);
  return 0;
}

4b64c30078864fa69177c04171cf0437.png

可看到以上代码两种表示方法结果是一样的,函数没有什么首元素的地址,数组才有,所以函数指针用 & 和 不用& 结果都是一样的。

总结:&函数名和函数名都是函数的地址


创建和使用一个函数指针变量

int Add(int x, int y)
{
  return x + y;
}
int main()
{
  //pf就是函数指针
  int (* pf)(int, int) = Add;//函数的地址要存起来,就得放在【函数指针变量】中
  int ret1 = (*pf)(3, 5);
  int ret2 = Add(3, 5);
  int ret3 = pf(3, 5);
  printf("%d\n", ret1);
  printf("%d\n", ret2);
  printf("%d\n", ret3);
  return 0;
}


pf是一个存放函数地址的指针变量 - 函数指针 ,和数组指针写法相似,注意:函数指针变量名是pf,不是*pf,*只是告诉我们pf是指针,,要用*pf一定要加(),否则*pf(2,3),求其结果是*5


执行:

c4fea2850b9f41b6b269797533dd4a6b.png

总结:pf(3,5) == (*pf)(3,5) ==Add(3,5)


阅读两段有趣的代码:


例题1. 0地址函数调用

int main()
{
  //代码1
  (*(void (*)())0)();
  return 0;
}


整体看下来,这样分析:


1. 将0强制类型转换为void (*)() 类型的函数指针

2. 这就意味着0地址处放着一个函数,函数没参数,返回类型是void

3. 调用0地址处的这个函数


细致分析:


从0开始下手,0是整型int,若在(void(*)())放p,为void(*p)()说明是函数指针,(void(*)())0说明是把0强制转换成函数指针类型(0被当成一个地址,0地址,放的参数是无参,返回类型是void的函数),


在前面加*,写成*(void(*)())0,说明对0地址做解引用操作,那个函数是无参的,所以最后面的括号写成()没有传任何参数


总结:该代码是一次函数调用,调用的0地址处的一个函数,首先代码中将0强制类型转换为类型void(*)()函数指针,然后去调用0地址处的函数--------来自《C陷阱和缺陷》


可用上述代码类比一下

e31c87caa63b4278909bcc0e9fcedbff.png

例题2 函数声明

int main(){
void(*signal(int, void(*)(int)))(int);
return 0;
}


从signal开始,*和signal没()一起,说明不是个指针,而signal括号包含两个参数类型,一个int,另一个是函数指针,说明signal第一个参数是整型,第二个为函数指针,函数除了函数名和参数外,还有返回类型,把函数名和参数类型删去,即为返回类型:void(*)(int)


虽然这样更容易理解,但这样写是错误的:void(*)(int)signal(int,void(*)(int));(只用于方便理解)


总结:

上述的代码是一个函数的声明

函数的名字是signal


signal函数的参数第一个是int类型,第二个是void(*)(int)类型的函数指针

该函数指针指向的函数参数是int,返回类型是void

 

signal函数的返回类型也是一个函数指针

该函数指针指向的函数参数是int,返回类型是void

为了看起来方便,可以用typedef重命名类型

typedef int* ptr_t;

typedef void(*pf_t)(int);   //将void(*)(int)类型重新起个别名叫pf_t


所以可以写成这样:


                            pf_t signal(int, pf_t);


区别:


typedef void(*pf_t2)(int); //pf_t2是类型名


void(*pf)(int);//pf是函数指针变量的名字

接下来还有指针进阶(2),上述文章如有错误欢迎大佬指针,感谢你的来访。

相关文章
|
4天前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
4天前
|
C语言
指针进阶(回调函数)(C语言)
指针进阶(回调函数)(C语言)
|
4天前
|
存储 C语言 C++
指针进阶(函数指针)(C语言)
指针进阶(函数指针)(C语言)
|
4天前
|
编译器 C语言
指针进阶(数组指针 )(C语言)
指针进阶(数组指针 )(C语言)
|
9天前
|
搜索推荐
指针进阶(2)
指针进阶(2)
26 4
|
9天前
指针进阶(3)
指针进阶(3)
19 1
|
9天前
|
C++
指针进阶(1)
指针进阶(1)
26 1
|
2天前
|
Java 程序员 Linux
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
探索C语言宝库:从基础到进阶的干货知识(类型变量+条件循环+函数模块+指针+内存+文件)
9 0
|
25天前
|
存储 安全 编译器
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
C++进阶之路:何为引用、内联函数、auto与指针空值nullptr关键字
14 2
|
25天前
|
存储 安全 编译器
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
C++进阶之路:探索访问限定符、封装与this指针的奥秘(类与对象_上篇)
9 0