C语言——指针的进阶——第1篇——(第26篇)

坚持就是胜利

文章目录

  • 一、字符指针
    • 1、面试题
  • 二、指针数组
  • 三、数组指针
    • 1、数组指针的定义
    • 2、&数组名 VS 数组名
    • 3、数组指针的使用
      • (1)二维数组传参,形参是 二维数组 的形式
      • (2)二维数组传参,形参是 指针 的形式
      • (3)总结
  • 四、数组传参和指针传参
    • 1、一维数组传参
    • 2、二维数组传参
    • 3、一级指针传参
    • 4、二级指针传参
  • 五、函数指针
    • 1、举例理解:
    • 2、分析两段有趣的代码
      • (1)代码1
      • (2)代码2
        • 学习 typedef 类型重命名
        • 代码2太复杂,简化为如下形式

初级阶段:
1、指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2、指针的大小是固定的 4/8 个字节(32位平台 / 64位平台)。
3、指针是有类型,指针的类型决定了指针的 + - 整数的步长,指针解引用操作的时候的权限。
4、指针的运算。

一、字符指针

在指针的类型中,我们知道有一种指针类型为字符类型 char*
一般使用:

#include <stdio.h>int main()
{char ch = 'w';char* ps = &ch;*ps = 'w';return 0;
}

还有一种使用方式如下:

#include <stdio.h>int main()
{char arr[] = "abcde";//[a b c d e \0]const char* p = "abcde";   //本质是:指针变量p 指向 首字符 a 的地址//并且 "abcde" 是 "常量字符串",得加上 constprintf("%s\n", p);   //结果:abcde  printf("%c\n", *p);  //结果:areturn 0;
}
代码:const char* p = "abcde";

特别容易以为是 把字符串 “abcde” 放到字符指针 p 中,
但是本质是把 字符串 abcde 首字符 的地址放到了 p 中。

在这里插入图片描述

1、面试题

#include <stdio.h>int main()
{char str1[] = "abcde";char str2[] = "abcde";const char* str3 = "abcde";const char* str4 = "abcde";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;
}

在这里插入图片描述

在这里插入图片描述
这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++ 会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际会指向同一块内存。
但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1 和 str2 不同,str3 和 str4 不同。

二、指针数组

指针数组 是一个 存放指针 的数组。

int* arr1[10];  //整型指针的数组
char* arr2[4];  //一级字符指针的数组
char** arr3[5];  //二级字符指针的数组
//使用 指针数组 模拟实现 二维数组#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[3] = { arr1,arr2,arr3 };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");}return 0;
}

在这里插入图片描述
在这里插入图片描述

三、数组指针

1、数组指针的定义

类比:
整型指针——指向整型变量的指针,存放整型变量的地址的指针变量。
字符指针——指向字符变量的指针,存放字符变量的地址的指针变量。

数组指针——指向数组的指针,存放数组的地址的指针变量。

在这里插入图片描述

2、&数组名 VS 数组名

int arr[10];

arr 和 &arr 分别是什么?
arr 是 数组名,数组名 表示 数组首元素 的地址。

那 &arr 数组名 到底是什么?

#include <stdio.h>int main()
{int arr[10] = { 0 };printf("arr = %p\n", arr);printf("&arr = %p\n", &arr);printf("arr + 1 = %p\n", arr + 1);printf("&arr + 1 = %p\n", &arr + 1);return 0;
}

在这里插入图片描述
&arr 和 arr ,虽然值是一样的,但是意义是不一样的。

实际上:
&arr 表示的是:数组的地址,而不是数组首元素的地址。
&arr 的类型是:int(*)[10],是一种数组指针类型
数组的地址 + 1,跳过整个数组的大小,所以,&arr +1 相对于 &arr 的差值是40。

#include <stdio.h>int main()
{int arr[10] = { 0 };printf("%p\n", arr);   //类型:int *printf("%p\n", &arr[0]);  //类型:int *printf("%p\n", &arr);   //类型:int(*)[10]   数组指针类型return 0;
}

在这里插入图片描述

电脑中 int[10] * ,这么写是错误的,是为了方便你理解,其实应该写成 int( * )[10] , 数组指针类型

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

#include <stdio.h>int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%d\n", sizeof(arr));return 0;
}

在这里插入图片描述

总结:(数组名的理解)
数组名是数组首元素的地址。

有2个例外:
(1)sizeof(数组名),这里的数组名不是数组首元素的地址,数组名表示整个数组,sizeof(数组名) 计算的是 整个数组的大小,单位是字节。
(2)&数组名,这里的数组名表示整个数组,&数组名 取出的是 整个数组的地址。
(3)除此之外,所有的地方的数组名都是数组首元素的地址。

3、数组指针的使用

#include <stdio.h>int main()
{char  arr[6] = "abcde";char(*p)[6] = &arr;printf("%c \n", *(*p));  //p 中放的是 &arr , *p 就是 arr(数组名), *(*p) 就是 *(arr),就是首字母,就是对首字母地址解引用printf("%s", p);return 0;
}

在这里插入图片描述

#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){printf("%d ", *(p + i));}putchar('\n');for (i = 0; i < 10; i++){printf("%d ", *(arr + i));}return 0;
}

用指针数组,来遍历输出数组的每个元素,感觉使用起来很变扭!

#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int(*p)[10] = &arr;  //*&arr 那么*&就互相抵消,*&arr 就是 arr(数组名)int i = 0;for (i = 0; i < 10; i++){                                //p 中存放的就是 &arr//由于*&arr 就是 arr(数组名)//*p 就是 *(&arr)  就是 arr(数组名)printf("%d ", *((*p) + i));  //(*p) 就是 arr(数组名) //(*p)+i  就是 arr + i  //*(arr + i) 就是 遍历数组的每一个元素putchar('\n');printf("%d ", (*p)[i]);    //arr[i]//效果一样}return 0;
}

在这里插入图片描述
所以,数组指针 不是像 上面的代码那样使用,这样反而弄巧成拙了。

问:数组指针怎么使用呢?
答:一般在 二维数组 上才方便。

(1)二维数组传参,形参是 二维数组 的形式

//二维数组传参,形参是二维数组的形式#include <stdio.h>void Print(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");}
}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)二维数组传参,形参是 指针 的形式

//二维数组传参,形参是指针的形式#include <stdio.h>void Print(int(*p)[5], int r, int c)
{int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c; j++){printf("%d ", *(*(p + 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 };//arr 是数组名,数组名表示数组首元素的地址Print(arr, 3, 5);return 0;
}

在这里插入图片描述
在这里插入图片描述

(3)总结

一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针

//一维数组传参,形参的部分可以是数组(本质还是指针),也可以是指针void test1(int arr[5], int sz)    //int arr[5] 本质还是指针,只是写成了数组的形式
{}void test2(int* p, int sz)
{}int main()
{int arr[5] = { 0 };test1(arr, 5);test2(arr, 5);return 0;
}

二维数组传参,形参的部分可以是数组,也可以是指针

//二维数组传参,形参的部分可以是数组,也可以是指针void test3(char arr[3][5], int r, int c)
{}void test4(char(*p)[5], int r, int c)
{}int main()
{char arr[3][5] = { 0 };test3(arr, 3, 5);test4(arr, 3, 5);return 0;
}

学了指针数组和数组指针,我们来一起回顾并看看下面的代码的意思:
在这里插入图片描述

四、数组传参和指针传参

在写代码的时候,难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计?

1、一维数组传参

在这里插入图片描述

#include <stdio.h>void test(int arr[])  //ok?  答:可行
{}                    void test(int arr[10])  //ok?  答:可行
{}                      //[10]中的10,可以写成100、1000都行,反正 int arr[10] 本质就是指针void test(int *arr)  //ok?  答:可行
{}void test2(int *arr[20])  //ok?  答:可行
{}                        //原因:形参实参类型保持一致void test2(int** arr)  //ok?  答:可行
{}                     //int *arr2[20] 就是数组20个元素,每个元素类型都是 int * //一级指针的地址,就是 二级指针
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);return 0;
}

2、二维数组传参

在这里插入图片描述

void test(int arr[3][5])  //ok?  答:可行
{}void test(int arr[][])  //ok?  答:不可行
{}                      //不可以省略行void test(int arr[][5])  //ok?  答:可行
{}//总结:二维数组传参,函数形参的设计只能省略第一个 [ ] 的数字
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素
//这样才方便运算void test(int* arr)  //ok?  答:不可行
{}                   //arr 是数组指针的地址,arr 是数组第一行的地址 arr == &arr[0]void test(int* arr[5])  //ok?  答:不可行
{}                      //int* arr[5] 是指针数组,有5个元素,每个元素的类型是 int*void test(int(*arr)[5])  //ok?  答:可行
{}                       //int (*arr)[5]数组指针,元素类型是 int[5],是数组某一行的指针void test(int **arr)  //ok?  答:不可行
{}                    //arr的类型是 int*,明显错误//二级指针 是 用来接收一级指针的地址的
int main()
{int arr[3][5] = { 0 };test(arr);
}

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,10 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数print(p, sz);return 0;
}

当一个函数的参数部分为一级指针的时候,函数能接受什么参数?

void test(char* p)
{}char ch = '2';
char* ptr = &ch;
char arr[] = "abcde";test(&ch);  //第一种
test(ptr);  //第二种
test(arr);  //第三种

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

当函数的参数为二级指针的时候,可以接受什么参数?

void test(char** p)
{}int main()
{char c = 'b';char* pc = &c;char** ppc = *pc;char* arr[10];   //指针数组,类型是 char*//二级指针,是一级指针的地址test(&pc);test(ppc);test(arr);  //是数组元素类型 char* 的地址return 0;
}

五、函数指针

函数指针——指向函数的指针
在这里插入图片描述
函数名 是 函数的地址
&函数名 也是 函数的地址
在这里插入图片描述

int (*pf)(int , int) 去掉 指针名 pf ,剩下的就是指针类型 int ( * )(int , int)

在这里插入图片描述

1、举例理解:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2、分析两段有趣的代码

(1)代码1

//代码1
( *( void (*)() )0 )();

1、将 0 强制转换成 void (*)() 类型的函数指针
2、调用 0 地址处的这个函数

在这里插入图片描述

//0x0012ff40 可以理解为:int
//           也可以理解为:int*//同理://0          可以理解为:int
//           也可以理解为:int*//void (*p)()  -- p是函数指针
//void (*)() 是函数指针类型//从0开始切入分析
//0 被强制类型转换为 void (*)() 类型的函数指针
// 0 此时变成了函数指针,也就是说 0 变成 地址了
//* 解引用  ()代表 函数
// 函数指针就是指针,指针就是地址
//调用 0 地址 处的这个函数//代码1
( *( void (*)() )0 )();

我们自己写的应用程序是不可以访问 0 地址的。
举例的是 系统程序。

(2)代码2

//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述

//signal 是一个函数声明
//signal 函数有 2 个参数
//第 1 个参数的类型是 int
//第 2 个参数的类型是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void 
//signal 函数的返回类型也是 void(*)(int) 函数指针类型,该函数指针指向的函数有一个 int 类型的参数,返回类型是 void //代码2
void (*signal(int , void(*)(int)))(int);
学习 typedef 类型重命名
typedef unsigned int uint;  //将 unsigned int 重命名为 uint  //正确
typedef int* ptr_r;  //将 int* 重命名为 ptr_r  //正确

数组指针,这种书写格式是错误的

//对 数组指针 进行 类型重命名typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写

应该改为这样
在这里插入图片描述

//对 数组指针 进行 类型重命名//错误的书写格式
//typedef int(*)[10] parr_t;  //这么写就是错误的,不能以这种方式来书写//正确的书写格式
typedef int(*parr_t)[10];

函数指针类型 跟上面的书写格式一致

代码2太复杂,简化为如下形式

原代码:

void (*signal(int , void(*)(int)))(int);

简化后:

typedef void(*pfun_t)(int);  //将 void (*)(int)  重命名为 pfun_t
pfun_t signal(int, pfun_t);

微软雅黑字体
黑体
3号字
4号字
红色
绿色
蓝色

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RT-Thread应用笔记】英飞凌PSoC 62 + CYW43012 WiFi延迟和带宽测试

文章目录 一、安装SDK二、创建项目三、编译下载3.1 编译代码3.2 下载程序 四、WiFi测试4.1 扫描测试4.2 连接测试 五、延迟测试5.1 ping百度5.2 ping路由器 六、带宽测试6.1 添加netutils软件包6.2 iperf命令参数6.3 PC端的iperf6.4 iperf测试准备工作6.5 进行iperf带宽测试6.6…

未来三年AI的深度发展:AIGC、视频AI与虚拟世界构建

人工智能&#xff08;AI&#xff09;正站在科技演进的前沿&#xff0c;未来三年将见证其在多领域实现更深层次的突破。以下是对AI发展方向的深度探讨以及其对各行业的深远影响&#xff1a; 1. AIGC的演进与全面提升&#xff1a; AIGC&#xff0c;即AI通用性能力&#xff0c;将…

AI前沿-YOLOV9算法

AI前沿-YOLOV9算法 关注B站查看更多手把手教学&#xff1a; 肆十二-的个人空间-肆十二-个人主页-哔哩哔哩视频 (bilibili.com) 今天我们来一起说下最近刚出的YOLOV9算法 论文和源码 该算法的原始论文地址为&#xff1a;https://arxiv.org/abs/2402.13616 该算法的原始代码地…

Muduo库编译学习(1)

1.muduo库简介 muduo是由Google大佬陈硕开发&#xff0c;是一个基于非阻塞IO和事件驱动的现代C网络库&#xff0c;原生支持one loop per thread这种IO模型&#xff0c;该库只支持Linux系统&#xff0c;网上大佬对其褒贬不一&#xff0c;作为小白用来学习就无可厚非了。 git仓库…

b站小土堆pytorch学习记录——P14 torchvision中的数据集使用

文章目录 一、前置知识如何查看torchvision的数据集 二、代码&#xff08;附注释&#xff09;及运行结果 一、前置知识 如何查看torchvision的数据集 &#xff08;1&#xff09;打开官网 https://pytorch.org/ pytorch官网 &#xff08;2&#xff09;打开torchvision 在Do…

Linux:top显示信息

简介 top命令是Linux系统中常用的性能监控工具&#xff0c;它可以实时显示系统中各个进程的CPU使用情况以及其他系统资源的使用情况。当您在终端中运行top命令时&#xff0c;它会显示一个实时更新的列表。 CPU相关的信息 CPU状态&#xff1a; us&#xff08;user&#xff09…

梯度和梯度损失

梯度主要用于计算图像融合过程中的梯度损失&#xff0c;对应的损失函数是梯度损失&#xff08;loss_grad&#xff09;。 梯度的作用&#xff1a; 图像的梯度是指图像中每个像素的灰度变化率&#xff0c;通常用于表示图像的边缘和纹理信息。在图像融合任务中&#xff0c;通过计算…

Unity游戏输入系统(新版+旧版)

使用新版还是旧版 旧版 using System.Collections; using System.Collections.Generic; using UnityEngine;public class c5 : MonoBehaviour {void Start(){}void Update(){// 注意要在游戏中 点鼠标键盘进行测试// 鼠标// 0左键 1右键 2滚轮if (Input.GetMouseButtonDown(0)…

【javaSE-语法】lambda表达式

【javaSE-语法】lambda表达式 1. 先回忆一下&#xff1a;1.1 接口不能直接通过关键字new进行实例化1.2 函数式接口1.3 匿名内部类1.31 匿名内部类在代码中长啥样&#xff1f;1.32 构造一个新的对象与构造一个扩展了某类的匿名内部类的对象&#xff0c;两者有什么区别&#xff1…

maven--->maven中的<properties>属性有什么作用?

&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c;&#x1f64c; 在Maven中&#xff0c;元素用于定义项目中可重用的属性值。这些属性值可以在项目的POM文件中被引用&#xff0c;以便在整个项目中统一管理和使用。通过使用元素&#xff0c;可以避免在POM文件…

SpringBean生命周期之InitializingBean,初始化bean

1 yml文件 weixin:appid: aaaaaapartner: 12313214partnerkey: ccccccert: C:\\Users\\lenovo\\Desktop 2 Bean初使化 import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Value; import org.springframewor…

midjourney提示词语法

更高级的提示可以包括一个或多个图像URL、多个文本短语和一个或更多个参数 Image Prompts 可以将图像URL添加到提示中&#xff0c;以影响最终结果的样式和内容。图像URL总是位于提示的前面。 https://docs.midjourney.com/image-prompts Text Prompt 要生成的图像的文本描述。…

YOLOv6、YOLOv7、YOLOv8网络结构图(清晰版)

承接上一篇博客&#xff1a;YOLOv3、YOLOv4、YOLOv5、YOLOx的网络结构图(清晰版)_yolox网络结构图-CSDN博客 1. YOLOv6网络结构图 2. YOLOv7网络结构图 3. YOLOv8网络结构图

搭建 LNMP 架构

一 理论知识 &#xff08;一&#xff09;架构图 &#xff08;二&#xff09;CGI 由来 最早的Web服务器只能简单她响应浏览器发来的HTTP请求&#xff0c;并将存储在服务器上的HTML文件返回给浏览器&#xff0c;也就是静态html文件&#xff0c;但是后期随着网站功能增多网站开…

c++阶梯之模板初阶

1. 泛型编程 void Swap(int& x, int& y) {int tmp x;x y;y tmp; }void Swap(double& x, double& y) {double tmp x;x y;y tmp; }void Swap(char& x, char& y) {char tmp x;x y;y tmp; } int main() {int a 10, b 20;double c 1.1, d 2.2…

《Spring Security 简易速速上手小册》第7章 REST API 与微服务安全(2024 最新版)

文章目录 7.1 保护 REST API7.1.1 基础知识详解7.1.2 重点案例&#xff1a;使用 JWT 进行身份验证和授权案例 Demo 7.1.3 拓展案例 1&#xff1a;API 密钥认证案例 Demo测试API密钥认证 7.1.4 拓展案例 2&#xff1a;使用 OAuth2 保护 API案例 Demo测试 OAuth2 保护的 API 7.2 …

读书笔记-三国演义-夏侯惇

夏侯惇&#xff08;公元夏侯惇&#xff08;公元190年-公元220年&#xff09;&#xff0c;字元让&#xff0c;沛国谯县&#xff08;今安徽亳州市&#xff09;人&#xff0c;是中国东汉末年至三国时期魏国重要将领之一。他是曹操麾下的得力将领&#xff0c;以勇猛忠诚而闻名于世。…

linux安装matlab获取许可证

1.点击许可证 2. 3. 4. 4.主机ID 打开linux输入 /sbin/ifconfigether后边的就是 6.计算机登录名 打开linux输入 whoami7. 8. 9.

局域网https自签名教程

们的客户是在内网环境里面&#xff0c;所以就只能用自签名证书来搞&#xff0c;我一想这还不容易&#xff0c;就迅速的百度了一下随便找了个文章开始照猫画虎&#xff0c;很快就弄完了&#xff0c;但是弄完后发现还是有问题&#xff0c;而且https 还是报不安全&#xff0c; 1、…

(规划)24届春招和25届暑假实习路线准备规划

春招&&暑假实习&#xff1a; 1.八股&#xff1a; 可以去一些八股网站上面进行阅读。 2.项目&#xff1a;至少准备1-2个项目&#xff0c;可以条理清晰的进行项目介绍和难点剖析。 3.算法&#xff1a; hot100 &#xff0c;剑指offer 能刷的很熟&#xff0c;算法关就差…