C语言 13 指针

指针可以说是整个 C 语言中最难以理解的部分了。

什么是指针

还记得在前面谈到的通过函数交换两个变量的值吗?

#include <stdio.h>void swap(int, int);int main() {int a = 10, b = 20;swap(a, b);printf("a = %d, b = %d", a, b);
}void swap(int a, int b){// 这里对a和b的值进行交换int tmp = a;   a = b;b = tmp;
}

实际上这种写法是错误的,因为交换的并非是真正的 a 和 b,而是函数中的局部变量。

那么有没有办法能够直接对函数外部的变量进行操作呢?这就需要指针的帮助了。

程序中使用的变量实际上都是在内存中创建的,每个变量都会被保存在内存的某一个位置上(具体哪个位置由系统分配),所有的变量在对应的内存位置上都有一个地址(地址是独一无二的),可以通过这个地址寻找到这个变量本体,比如 int 占据 4 字节,因此 int 类型变量的地址就是这 4 个字节的起始地址,后面 32 个 bit 位全部都是用于存放此变量的值的。

这里的0x是十六进制的表示形式(10 - 15 用字母 A - F 表示)

如果能够知道变量的内存地址,那么无论身在何处,都可以通过地址找到这个变量了。

而指针的作用,就是专门用来保存这个内存地址的。

来看看如何创建一个指针变量用于保存变量的内存地址:

#include <stdio.h>int main() {int a = 10;// 指针类型需要与变量的类型相同,且后面需要添加一个*符号(注意这里不是乘法运算),表示是对于类型的指针// 这里的&并不是进行按位与运算,而是取地址操作,也就是拿到变量a的地址int* p = &a;                         // 地址使用%p表示printf("a在内存中的地址为:%p", p);  
}
a在内存中的地址为:00000000005ffe84

可以看到,通过取地址操作&,将变量 a 的地址保存到了一个地址变量p中。

拿到指针之后,就可以很轻松地获取指针所指地址上的值:

#include <stdio.h>int main() {int a = 666;int* p = &a;// 可以在指针变量前添加一个*号(间接运算符,也可以叫做解引用运算符)来获取对应地址存储的值printf("内存%p上存储的值为:%d", p, *p);  
}
内存00000000005ffe84上存储的值为:666

注意这里访问指针所指向地址的值时,是根据类型来获取的,比如 int 类型占据 4 个字节,那么就读取地址后面 4 个字节的内容作为一个 int 值,如果指针是 char 类型的,那么就只读取地址后面 1 个字节作为 char 类型的值。

同样的,也可以直接像这样去修改对应地址存放的值:

#include <stdio.h>int main() {int a = 666;int* p = &a;// 通过*来访问对应地址的值,并通过赋值运算对其进行修改*p = 999;  printf("a的值为:%d", a);
}
a的值为:999

实际上拿到一个变量的地址之后,完全不需要再使用这个变量,而是可以通过它的指针来对其进行各种修改。

因此,现在想要实现对两个变量的值进行交换的函数就很简单了:

#include <stdio.h>// 这里是两个指针类型的形参,其值为实参传入的地址,
// 虽然依然是值传递,但是这里传递的是地址
// 只要知道地址改变值就很容易了
void swap(int* a, int* b) {// 先暂存一下变量a地址上的值int tmp = *a;  // 将变量b地址上的值赋值给变量a地址上的值*a = *b;       // 最后将a的值赋值给b地址上的值,这样就成功交换两个变量的值了*b = tmp;      
}int main() {int a = 10, b = 20;// 只需要把a和b的内存地址给过去就行了,这里取一下地址swap(&a, &b);  printf("a = %d, b = %d", a, b);
}
a = 20, b = 10

通过地址操作,就轻松实现了使用函数交换两个变量的值了。


了解了指针的相关操作之后,再来看看scanf函数,实际上就很好理解了:

#include <stdio.h>int main(){int a;// 这里就是取地址,需要告诉scanf函数变量的地址,这样它才能通过指针访问变量的内存地址,对变量的值进行修改,这也是为什么scanf里面的变量(除数组外)前面都要进行一个取地址操作scanf("%d", &a);   printf("%d", a);
}

当然,和变量一样,要是不给指针变量赋初始值的话,就不知道指向哪里了,因为指针变量也是变量,存放的对应变量的地址值也在内存中保存,如果不给初始值,那么存放变量地址的这块内存可能在其他地方使用过,这样就不知道初始值是多少了(那么指向的地址可能是一个很危险的地址,随意使用可能导致会出现严重错误),所以一定要记得给个初始值或是将其设定为 NULL,表示空指针,不指向任何内容。

#include <stdio.h>int main(){int* a = NULL;
}

接着来看看const类型的指针,这种指针比较特殊:

#include <stdio.h>int main() {int a = 9, b = 10;const int* p = &a;// 报错,因为被const标记的指针,所指地址上的值不允许发生修改*p = 20;// 但是指针指向的地址是可以发生改变的p = &b;
}

再来看另一种情况:

#include <stdio.h>int main() {int a = 9, b = 10;// const关键字被放在了类型后面int* const p = &a;// 允许修改所指地址上的值*p = 20;// 报错,不允许修改指针存储的地址值,其实就是反过来了p = &b;
}

当然也可以双管齐下:

#include <stdio.h>int main(){int a = 9, b = 10;const int * const p = &a;*p = 20;   //两个都直接报错,都不让改了p = &b;
}

指针与数组

前面介绍了指针的基本使用,来回顾一个问题,为什么数组可以原身在函数之间进行传递呢?

先说结论,数组表示法实际上是在变相地使用指针,甚至可以理解为数组变量其实就是一个指针变量,它存放的就是数组中第一个元素的起始地址

为什么这么说?

#include <stdio.h>int main() {char str[] = "Hello World!";// 为什么能直接把数组作为地址赋值给指针变量char* p = str;  // 还能正常使用,打印出第一个字符printf("%c", *p);  
}
H

还能这样玩:

int main() {char str[] = "Hello World!";char* p = str;// 还可以像在使用数组一样用指针printf("%c", p[1]);
}
e

怎么数组和指针还能这样混着用呢?先来看看数组在内存中是如何存放的:

数组在内存中是一块连续的空间,所以为什么声明数组一定要明确类型和大小,因为这一块连续的内存空间生成后就固定了。

而数组变量实际上存放的就是首元素的地址,而实际上之前一直使用的都是数组表示法来操作数组,这样可以很方便地对内存中的各个元素值进行操作:

int main(){char str[] = "Hello World!";// 直接在中括号中输入对应的下标就能访问对应位置上的数组了printf("%c", str[0]);   
}

而实际上str表示的就是数组的首地址,所以完全可以将其赋值给一个指针变量,因为指针变量也是存放的地址:

char str[] = "Hello World!";
// 直接把str代表的首元素地址给到p
char* p = str;   

而使用指针后,实际上可以使用另一种表示法来操作数组,这种表示法叫做指针表示法

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 通过指针也可以表示对应位置上的值printf("第一个元素值为:%c,第二个元素值为:%c", *p, *(p + 1));
}
第一个元素值为:H,第二个元素值为:e

比如现在需要表示数组中的第二个元素:

  • 数组表示法:str[1]
  • 指针表示法:*(p+1)

虽然写法不同,但是他们表示的意义是完全相同的,都代表了数组中的第二个元素,其中指针表示法使用了p+1的形式表示第二个元素,这里的+1操作并不是让地址+1,而是让地址+ 一倍的对应类型大小,也就是说地址后移一个char 的长度,所以正好指向了第二个元素,然后通过*取到对应的值(注意这种操作仅对数组是有意义的,如果是普通的变量,虽然也可以获得后一个 char 的长度的数据,但是毫无意义)

这两种表示法都可以对内存中存放的数组内容进行操作,只是写法不同罢了,所以数组和指针混用也就不奇怪了。

了解了这些东西之后,再来看看下面的各个表达式分别代表什么:

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 数组的第一个元素printf("*p的值:%c\n", *p);   // 数组的第一个元素的地址printf("p的值:%p\n", p);// 肯定是真,因为都是数组首元素地址printf("p == str的值:%d\n", p == str);   // 因为str就是首元素的地址,所以这里对地址加*就代表第一个元素,使用的是指针表示法printf("*str的值:%c\n", *str);    // 这里得到的实际上还是首元素的地址printf("&str[0]的值:%p\n", &str[0]);   // 代表第二个元素printf("*(p + 1)的值:%c\n", *(p + 1));   // 第二个元素的内存地址printf("p + 1的值:%p\n", p + 1);    // 注意*的优先级比+要高,所以这里代表的是首元素的值+1,得到字符'I'printf("*p + 1的值:%c\n", *p + 1);    
}
*p的值:H
p的值:00000000005ffe7b
p == str的值:1
*str的值:H
&str[0]的值:00000000005ffe7b
*(p + 1)的值:e
p + 1的值:00000000005ffe7c
*p + 1的值:I

所以不难理解,为什么printf函数的第一个参数是const char*了,实际上就是需要传入一个字符串而已,只不过这里采用的是指针表示法而已。

当然指针也可以进行自增和自减操作,比如:

#include <stdio.h>int main() {char str[] = "Hello World!";char* p = str;// 自增后相当于指针指向了第二个元素的地址p++;// 所以这里打印的就是第二个元素的值了printf("%c", *p);
}
e

一维数组看完了,再来看看二维数组,那么二维数组在内存中是如何表示的呢?

int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};

这是一个2x3的二维数组,其中存放了两个能够容纳三个元素的数组,在内存中,是这样的:

所以也可以使用指针来进行访问:

#include <stdio.h>int main() {int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};// 因为是二维数组,注意这里要指向第一个元素,需要降一个维度才能正确给到指针int* p = arr[0];  // 同理如果这里是arr[1]的话那么就表示指向二维数组中第二个数组的首元素// 实际上这两种访问形式都是一样的printf("%d = %d", *(p + 4), arr[1][1]);  
}
5 = 5

多级指针

实际上指针本身也是一个变量,它存放的是目标的地址,但是它本身作为一个变量,也要将地址信息保存到内存中,所以,实际上当有指针之后:

实际上,还可以继续创建一个指向指针变量地址的指针,甚至可以创建更多级(比如指向指针的指针的指针)

比如现在要创建一个指向指针的指针:

落实到代码中:

#include <stdio.h>int main() {int a = 20;// 指向普通变量的指针int* p = &a;// 因为现在要指向一个int *类型的变量,所以类型为int* 再加一个*// 指向指针的指针(二级指针)int** pp = &p;// 指向指针的指针的指针(三级指针)int*** ppp = &pp;// 使用一次*表示二级指针指向的指针变量,继续使用一次*会继续解析成指针变量所指的普通变量printf("p = %p, a = %d", *pp, **pp);  
}
p = 00000000005ffe84, a = 20

本质其实就是一个套娃而已,只要把各个层次分清楚,实际上还是很好理解的。

特别提醒: 一级指针可以操作一维数组,那么二级指针是否可以操作二维数组呢?不能!因为二级指针的含义都不一样了,它是表示指针的指针,而不是表示某个元素的指针了。下面会认识数组指针,准确的说它才更贴近于二维数组的形式。

指针数组与数组指针

前面了解了指针的一些基本操作,包括它与数组的一些关系。接着来看指针数组和数组指针,这两词语看着就容易搞混,不过哪个词在后面就哪个,先来看指针数组,虽然名字很像数组指针,但是它本质上是一个数组,不过这个数组是用于存放指针的数组。

#include <stdio.h>int main() {int a, b, c;// 可以看到,实际上本质还是数组,只不过存的都是地址int* arr[3] = {&a, &b, &c};// []运算符的优先级更高,所以这里先通过[0]取出地址,然后再使用*将值赋值到对应的地址上*arr[0] = 999;  printf("%d", a);
}

当然也可以用二级指针变量来得到指针数组的首元素地址:

#include <stdio.h>int main(){int * p[3];   //因为数组内全是指针int ** pp = p;  //所以可以直接使用指向指针的指针来指向数组中的第一个指针元素
}

实际上指针数组还是很好理解的,那么数组指针呢?可以看到指针在后,说明本质是一个指针,不过这个指针比较特殊,它是一个指向数组的指针(注意它的目标是整个数组,和之前认识的指针不同,之前认识的指针是指向某种类型变量的指针)

数组指针表示指向整个数组:

// 注意这里需要将*p括起来,因为[]的优先级更高
int (*p)[3];   

注意它的目标是整个数组,而不是普通的指针那样指向的是数组的首个元素:

int arr[3] = {111, 222, 333};
// 直接对整个数组再取一次地址(因为数组指针代表的是整个数组的地址,虽然和普通指针一样都是指向首元素地址,但是意义不同)
int (*p)[3] = &arr;  

那么现在已经取到了指向整个数组的指针,该怎么去使用呢?

#include <stdio.h>int main() {int arr[3] = {111, 222, 333};// 直接对整个数组再取一次地址int(*p)[3] = &arr;  // 要获取数组中的每个元素,稍微有点麻烦printf("%d, %d, %d", *(*p + 0), *(*p + 1), *(*p + 2));  
}
111, 222, 333

注意此时:

  • p代表整个数组的地址
  • *p表示所指向数组中首元素的地址
  • *p + i表示所指向数组中第i个(0 开始)元素的地址(实际上这里的 *p 就是指向首元素的指针)
  • *(*p + i)就是取对应地址上的值了

虽然在处理一维数组上感觉有点麻烦,但是它同样也可以处理二维数组:

#include <stdio.h>int main() {int arr[][3] = {{111, 222, 333}, {444, 555, 666}};// 二维数组不需要再取地址了,因为现在维度提升,数组指针指向的是二维数组中的其中一个元素(因为元素本身就是一个数组)int (*p)[3] = arr;// 现在想要访问第一个数组的第二个元素// 因为上面直接指向的就是第一个数组,所以想要获取第一个数组的第二个元素和之前是一模一样的printf("%d\n", *(*p + 1));// 现在想要获取第二个数组中的最后一个元素// 首先*(p + 1)为一个整体,表示第二个数组(因为是数组指针,所以这里 +1 一次性跳一个数组的长度),然后再到外层 +2 表示数组中的第三个元素,最后再取地址,就是第二个数组的第三个元素了printf("%d\n", *(*(p + 1) + 2));// 当然也可以使用数组表示法// 这就是二维数组的用法,甚至可以认为这两个是同一个东西printf("%d\n", p[1][2]);
}
222
666
666

指针函数与函数指针

函数可以返回一个指针类型的结果,这种函数就称为指针函数

#include <stdio.h>// 函数的返回值类型是int*指针类型的
int* test(int* a) {  return a;
}int main() {int a = 10;// 使用指针去接受函数的返回值int* p = test(&a);  printf("%d\n", *p);// 当然也可以直接把间接运算符在函数调用前面表示直接对返回的地址取地址上的值printf("%d\n", *test(&a));  
}
10
10

不过要注意指针函数不要尝试去返回一个局部变量的地址:

#include <stdio.h>int* test(int a) {int i = a;// 返回局部变量i的地址return &i;
}int main() {// 连续调用两次test函数int* p = test(20);  test(30);// 这里会报错printf("%d", *p);
}

为什么会这样呢?因为函数一旦返回,那么其中的局部变量就会全部销毁了,至于这段内存之后又会被怎么去使用,就不得而知了。


接着来看函数指针,实际上指针除了指向一个变量之外,也可以指向一个函数,当然函数指针本身还是一个指针,所以依然是用变量表示,但是它代表的是一个函数的地址(编译时系统会为函数代码分配一段存储空间,这段存储空间的首地址称为这个函数的地址)

来看看如何定义:

#include <stdio.h>int sum(int a, int b) {return a + b;
}int main() {// 类型 (*指针变量名称)(函数参数...)  // 注意一定要把*和指针变量名称括起来,不然优先级不够int (*p)(int, int) = sum;printf("%p", p);
}
00007ff6524713b4

这样就拿到了函数的地址,既然拿到函数的地址,就可以通过函数的指针调用这个函数了:

#include <stdio.h>int sum(int a, int b) {return a + b;
}int main() {int (*p)(int, int) = sum;// 就像正常使用函数那样,(*p)表示这个函数,后面依然是在小括号里面填上实参int result1 = (*p)(1, 2);printf("%d\n", result1);// 当然也可以直接写函数指针变量名称,效果一样int result2 = p(1, 2);printf("%d\n", result2);
}
3
3

有了函数指针,就可以编写函数回调了(所谓回调就让别人去调用提供的函数,而不是主动来调别人的函数)

比如现在定义了一个函数,不过这个函数需要参数通过一个处理的逻辑才能正常运行,所以就还要给他一个其他函数的地址:

#include <stdio.h>// 将函数指针作为参数传入
int sum(int (*p)(int, int), int a, int b) {// 函数回调return p(a, b);
}// 这个函数实现了a + b
int sumImpl(int a, int b) {  return a + b;
}int main() {// 拿到实现那个函数的地址int (*p)(int, int) = sumImpl;  printf("%d", sum(p, 10, 20));
}
30

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

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

相关文章

循环神经网络RNN+长短期记忆网络LSTM 学习记录

循环神经网络&#xff08;RNN) RNN的的基础单元是一个循环单元&#xff0c;前部序列的信息经处理后&#xff0c;作为输入信息传递到后部序列 x为输入向量&#xff0c;y为输出向量&#xff0c;a为上一隐藏层的a与x通过激活函数得到的值&#xff0c;简言之&#xff0c;每一层神…

华为 HCIP-Datacom H12-821 题库 (23)

&#x1f423;博客最下方微信公众号回复题库,领取题库和教学资源 &#x1f424;诚挚欢迎IT交流有兴趣的公众号回复交流群 &#x1f998;公众号会持续更新网络小知识&#x1f63c; 1.以下关于 VRRP 基本概念的描述&#xff0c;错误的是哪些选项&#xff1f; A、一个虚拟路由器…

S32K3 工具篇6:如何将RTD EB工程导入到S32DS

S32K3 工具篇6&#xff1a;如何将RTD EB工程导入到S32DS 1. MCAL_Plugins->Link Source Resource Filters2. Includes3. Preprocessor4. Linker5. optimization6. main.c 这个主题实际上&#xff0c;之前已经有多人写过&#xff0c;并且写的很好&#xff0c;只是实际操作中&…

qt-creator-10.0.2之后版本的jom.exe编译速度慢下来了

1、Qt的IDE一直在升级&#xff0c;qt-creator的新版本下载地址 https://download.qt.io/official_releases/qtcreator/ 2、本人一直用的是qt-creator-10.0.2版本&#xff0c;官网历史仓库可以下载安装包qt-creator-opensource-windows-x86_64-10.0.2.exe https://download.qt…

URP 线性空间 ui资源制作规范

前言&#xff1a; 关于颜色空间的介绍&#xff0c;可参阅 unity 文档 Color space URP实现了基于物理的渲染&#xff0c;为了保证光照计算的准确&#xff0c;需要使用线性空间&#xff1b; 使用线性空间会带来一个问题&#xff0c;ui资源在unity中进行透明度混合时&#xff…

COMP 6714-Info Retrieval and Web Search笔记week1

哭了哭了&#xff0c;这周唯一能听懂的就这门 目录 IR&#xff08;Information Retrieval)是什么&#xff1f;IR的基本假设Unstructured (text) vs. structuredDocuments vs. Database Records比较文本&#xff08;Comparing Text&#xff09;IR的范围(Dimensions of IR)IR的任…

YoloV10改进策略:上采样改进|动态上采样|轻量高效,即插即用(适用于分类、分割、检测等多种场景)

摘要 本文使用动态上采样改进YoloV10,动态上采样是今天最新的上采样改进方法,具有轻量高效的特点,经过验证,在多个场景上均有大幅度的涨点,而且改进方法简单,即插即用! 论文:《DySample:Learning to Upsample by Learning to Sample》 论文:https://arxiv.org/pdf/…

fmql之ubuntu移植

官方资料&#xff1a;ubuntu18的压缩包 目的&#xff1a;放到SD卡中启动ubuntu&#xff08;官方是放在emmc中&#xff09; 教程&#xff1a;99_FMQL45_大黄蜂开发板跑ubuntu18.04.docx 所需文件 其中&#xff0c;format_emmc_ext4.txt对emmc的分区是512M&#xff08;放上述文…

C++ | Leetcode C++题解之第397题整数替换

题目&#xff1a; 题解&#xff1a; class Solution { public:int integerReplacement(int n) {int ans 0;while (n ! 1) {if (n % 2 0) {ans;n / 2;}else if (n % 4 1) {ans 2;n / 2;}else {if (n 3) {ans 2;n 1;}else {ans 2;n n / 2 1;}}}return ans;} };

如何查看串口被哪个程序占用?截止目前最方便的方法

痛点&#xff1a;串口因为某种原因被占用&#xff0c;如何找到罪魁祸首&#xff1f; 做开发的小伙伴们&#xff0c;经常会遇到这样的问题&#xff1a;串口因为某种原因被占用&#xff0c;导致无法通讯&#xff0c;但是又找不到被哪个程序占用。只有重启电脑&#xff0c;才能解…

CSS“多列布局”(补充)——WEB开发系列35

多列布局是一种非常常见的布局方式&#xff0c;适用于内容丰富的页面&#xff0c;如新闻网站、杂志或博客。 一、CSS多列布局概述 CSS多列布局允许我们将内容分成多个垂直列&#xff0c;使页面布局更加灵活和多样化。多列布局的主要属性包括 ​​column-count​​、​​column…

「数组」堆排序 / 大根堆优化(C++)

目录 概述 核心概念&#xff1a;堆 堆结构 数组存堆 思路 算法过程 up() down() Code 优化方案 大根堆优化 Code(pro) 复杂度 总结 概述 在「数组」快速排序 / 随机值优化|小区间插入优化&#xff08;C&#xff09;中&#xff0c;我们介绍了三种基本排序中的冒泡…

Java工具插件

一、springboot集成mqtt订阅 阿里云MQTT使用教程_复杂的世界311的博客-CSDN博客_阿里云mqtt 阿里云创建MQTT服务 先找到产品与服务,然后选择物联网平台,找到公共实例,创建一个产品。 创建产品 然后在左侧下拉栏找到设备管理,在设备管理下拉栏找到设备,然后添加设备。添加…

博客建站9 - hexo网站如何提升markdown文档的编辑效率和体验

1. 本网站的系统架构2. 场景概述3. 影响效率的问题和解决方案 3.1. 图片插入-根据文章来分类管理 3.1.1. 效率问题3.1.2. 解决方案 3.2. 图片插入-从剪贴板中插入图片 3.2.1. 效率问题3.2.2. 解决方案 3.3. 图片插入-在VSCode中预览图片 3.3.1. 效率问题3.3.2. 解决方案 3.4. 提…

【软考】设计模式之责任链模式

目录 1. 说明2. 应用场景3. 结构图4. 构成5. 适用性6. 优点7. 缺点8. java示例 1. 说明 1.使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。2.将这些对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为…

个人学习笔记7-5:动手学深度学习pytorch版-李沐

#人工智能# #深度学习# #语义分割# #计算机视觉# #神经网络# 计算机视觉 13.10 转置卷积 例如&#xff0c;卷积层和汇聚层&#xff0c;通常会减少下采样输入图像的空间维度&#xff08;高和宽&#xff09;。然而如果输入和输出图像的空间维度相同&#xff0c;在以像素级分类…

c++基础入门二

C基础入门(二) 一、函数重载 在自然语言中&#xff0c;一句话或者一个词有不同的意思。例如&#xff1a;国乒和别人比赛是“谁也赢不了”&#xff0c;而国足和别人比赛是“谁也赢不了” 函数重载&#xff1a;是函数的一种特殊情况&#xff0c;C允许在同一作用域中声明几个功…

浪潮信息金风慧能:打造智慧新能源运营平台

近来&#xff0c;浪潮信息携手北京金风慧能技术有限公司&#xff08;简称“金风慧能”&#xff09;&#xff0c;共同发布了新能源场站集控中心的创新解决方案。该方案深度融合了浪潮信息的前沿服务器技术、软硬件一体化超融合方案及边缘计算产品与金风慧能自主研发的GW SCADA S…

C++进阶:多态

✨✨所属专栏&#xff1a;C✨✨ ✨✨作者主页&#xff1a;嶔某✨✨ 多态的概念 多态(polymorphism)的概念&#xff1a;通俗来说&#xff0c;就是多种形态。多态分为编译时多态(静态多态)和运⾏时多态(动态多态)&#xff0c;这⾥我们重点讲运⾏时多态。 编译时多态(静态多态)主…

车机中 Android Audio 音频常见问题分析方法实践小结

文章目录 前言1. 无声2. 断音3. 杂音4. 延迟播放5. 焦点问题6. 无声问题(连上 BT )其他完善中…… 前言 本文主要总结了一下车机开发中遇到的 Audio 有关的问题&#xff0c;同时参考网上的一案例&#xff0c;由于Audio 模块出现音频问题的场景很多&#xff0c;对每一个出现的问…