C++ 重要特性探究
shared_from_this
使用分析
- 场景
- 类的成员函数需要获取指向自身的shared_ptr的时候
- 类成员函数传递shared_ptr给其他函数或者对象的时候,目的是为了管理对象生命周期
- 使用方法
- 首先类必须继承 std::enable_shared_from_this<T>
- 必须使用 shared_from_this 获取指向自身的shared_ptr

#include<iostream>
#include<memory>using namespace std;class MyClass :public std::enable_shared_from_this<MyClass>
{
public:void show(){cout << "MyClass : show 运行" << endl;}//返回一个shared_ptr指向自身std::shared_ptr<MyClass> getSharedPtr(){return shared_from_this();}//获取shared_Ptr然后调用它void process(){std::shared_ptr<MyClass> ptr = shared_from_this();Funcaion(ptr);}void Funcaion(std::shared_ptr<MyClass>ptr){cout << "函数内部传入的shared_ptr指针使用次数:" << ptr.use_count() << endl;ptr->show();}
};int main()
{//创建一个指向Myclass的share_ptr指针std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();//调用类内成员函数,获取shared_ptr自身std::shared_ptr<MyClass> selfptr = obj->getSharedPtr();//验证shared_ptr的使用次数cout << "shared_ptr use_count:" << selfptr.use_count() << endl;//cout << obj.use_count() << endl;//两个指针都可以调用类内成员函数验证obj->process();selfptr->process();
}
左值和右值
左值:表达式的结果是一个持久对象,也就是该值可以取地址(内存区域是确定的)。左值通常出现在赋值操作符左侧的值

右值: 表达式的结果是一个临时值,不可以取地址,通常是赋值操作符右侧的数值

左值引用:利用&表示引用的类型,可以绑定到左值

右值引用:&&表示的引用类型,目的是操控临时对象,减少拷贝临时对象而产生性能消耗

移动语义:也就是不用复制对象,直接将资源进行移动即可,减少性能消耗
#include <iostream>
#include <vector>std::vector<int> createVector() {std::vector<int> temp = {1, 2, 3};return temp; // 返回的是一个右值
}int main() {std::vector<int> vec = createVector(); // 这里使用移动语义for(int i : vec) {std::cout << i << " ";}return 0;
}
左值转换成右值场景分析
- 当左值出现在赋值运算符右侧的时候,会自动将其转化为右值
- 当左值作为算法的数时,会自动将其转换为右值
- 左值作为参数的时候,传递给一个按值传递的函数时候,会自动转换为右值
注意:转换过程中,左值本身是没有消失的,而是生成了一个右值,表示左值所指向的信息(后面的代码会验证)


#include <iostream>int main() {int a = 10; // a 是一个左值int b = a; // 这里发生了左值到右值的转换,a 的值(10)被赋给 bstd::cout << "a: " << a << std::endl; // a 是左值std::cout << "b: " << b << std::endl; // b 是左值,打印出来的值是 10int* p = &a; // 获取 a 的地址std::cout << "Address of a: " << p << std::endl;return 0;
}
引用折叠
- 作用:复杂引用类型中,确定最终的引用类型是什么
- 规则总结
T& &变为T&T& &&变为T&T&& &变为T&T&& &&变为T&& (特点:只有双&&时,最终才是&&,剩下情况都是&)
事例分析(完美转发)
- 功能:将一个参数完整的转发给另一个函数,也就是说不改变值的类型,原本是左值就还是左值,右值还是右值。(具体理解看函数实现吧)
- 具体实现:结合引用折叠规则,必须传入左值,当T被推导的时候,就是 & &&最终得到的也就是 &

#include <iostream>
#include <utility> // for std::forward// 接受左值引用
void process(int& x) {std::cout << "左值引用: " << x << std::endl;
}// 接受右值引用
void process(int&& x) {std::cout << "右值引用: " << x << std::endl;
}// 用于完美转发
template <typename T>
void forwarder(T&& arg) {process(std::forward<T>(arg));
}int main() {int a = 10;forwarder(a); // 调用 process(int&) - 传递左值forwarder(20); // 调用 process(int&&) - 传递右值forwarder(std::move(a)); // 调用 process(int&&) - 传递右值return 0;
}
std::move
- 使用方法
- 将一个对象的所有权从一个地方转移到另一个地方,本质是移动而不是复制资源
- 实现原理(结合函数原型理解)
- 模板参数类型是&&万能引用,所以可以根据传入的参数类型,T可以是任意的成为左值引用或者右值引用
- remove_reference是类型萃取,主要是用于去除T的引用,无论是左值引用还是右值引用最终都变成原型
- static_cast:将参数显式的转换为右值引用,也就是说即使传递的是一个左值,move最终也会将其变成一个右值

C++指针
指针和引用的区别
- 指针是一个存储地址的变量,可以为空,同样也可以重新指向不同的对象,也可以进行指针算术运算
- 引用则是一个对象的别名,必须在声明的时候初始化,不可以重新绑定,更不可以为空
- 使用场景不同
- 指针常用在需要动态分配内存或者需要表示未初始化状态的时候
- 引用则是需要传递函数参数,避免大量复制数据,或者函数返回的时候,避免造成内存消耗
重要的区别如下

- 函数指针
- 参数说明(参考下面函数指针定义一同理解)
- return_type:函数返回的类型
- pointer_name:指针变量名称(可以理解为就是函数名)
- parameter_list:函数的参数列表
- 具体使用参考下面的类型




函数指针数组

#include <iostream>
int add(int a, int b) {return a + b;
}int subtract(int a, int b) {return a - b;
}int multiply(int a, int b) {return a * b;
}int main() {// 创建一个函数指针数组int (*operations[3])(int, int) = { add, subtract, multiply };int a = 10, b = 5;for (int i = 0; i < 3; ++i) {std::cout << "Result: " << operations[i](a, b) << std::endl;}return 0;
}
可以使用typedef简化函数指针的声明和使用
typedef int (*Operation)(int, int);int add(int a, int b) {return a + b;
}int multiply(int a, int b) {return a * b;
}int main() {Operation op;op = add;std::cout << "Add: " << op(5, 3) << std::endl;op = multiply;std::cout << "Multiply: " << op(5, 3) << std::endl;return 0;
}
函数的三种传递方式(值传递、引用传递、指针传递)
值传递
- 含义:在使用值传递的时候,函数收到的参数是一个拷贝后的数据,函数内部无论对形参做什么操作都不会影响原始变量的数值
- 特点
- 首先是不会改变原始变量的数值
- 适用于传输比较小的数据类型
void modifyValue(int x) {x = 20; // 仅修改了局部变量 x 的值
}int main() {int a = 10;modifyValue(a);std::cout << "a: " << a << std::endl; // 输出 10,a 未被修改return 0;
}
引用传递
- 含义:函数接收的是变量引用,如果对该引用进行修改会影响到之前的原始变量
- 特点
- 在传递数据的时候,不需要复制对象,从而可以减少性能消耗
void modifyValue(int& x) {x = 20; // 修改了原始变量的值
}int main() {int a = 10;modifyValue(a);std::cout << "a: " << a << std::endl; // 输出 20,a 被修改return 0;
}
指针传递
- 含义:函数中接受的参数是一个地址,函数内可以通过对该地址解应用,从而修改原始变量的数值
- 特点
- 使用之前需要进行指针安全性检查,防止空指针
- 可以改变原始变量的数值
void modifyValue(int* x) {if (x != nullptr) {*x = 20; // 通过指针修改原始变量的值}
}int main() {int a = 10;modifyValue(&a);std::cout << "a: " << a << std::endl; // 输出 20,a 被修改return 0;
}
迭代器
迭代器的含义与作用
- 含义:STL库中提供的一种访问遍历容器中数据的通用方法
- 作用
- 遍历容器:提供一种统一的方法去遍历各种容器,不需要关注容器的底层实现细节
- 访问元素:通过迭代器可以访问容器中的元素,堆目标元素进行读取和修改
- 插入和删除:特定位置的元素插入和删除操作
迭代器类型
- 输入迭代器:只读访问,单向移动,用于一次性读取输入
- 输出迭代器:写访问,用于输出操作
- 前向迭代器:读写访问,但是只支持单向移动
- 双向迭代器:读写访问,支持双向移动
野指针和悬空指针
野指针
- 含义:未被释放的指针或者已经被释放了内存但是没有被设置为NULL的指针
- 未初始化的指针:指针在声明的时候,如果没有初始化,那么指针指向的位置是不可控的,所以会造成野指针
- 内存释放但是指针没有置为NULL:因此该指针还是指向那片被释放的内存,但是该内存已经被释放,所以成为了野指针
- 野指针的危害:
- 访问未定义的内存,有可能引起程序崩溃
- 无法预料指针的行为,可能指向重要的内存区域,从而导致数据破坏

悬空指针
- 含义:指针指向已经被释放或者不再有效的内存位置的指针
- 产生原因
- 内存释放后继续使用:该指针指向的内存已经被释放了,但是该指针仍然时被继续使用的
- 局部变量的作用域:指向局部变量的指针在该局部变量作用域结束后继续使用(结合事例理解)
- 悬空指针的危害:
- 悬空指针会导致对无效内存的访问,这样会导致程序崩溃或者未定义的行为
- 也有可能会覆盖其他数据,破坏数据的完整性

四种强制类型转换
static_cast:相同类型之间进行转换
- 特点
- 编译时会进行类型检查
- 运行时不会进行类型检查,向下转换时需要注意(结合事例理解向下转换)

dynamic_cast:多态中进行类型转换,比如基类到派生类
- 特点
- 运行是会进行类型检查,会确保转换的安全性
- 只有当基类有虚函数的时候才会有效(注意)
- 转换失败时,会返回空指针或者抛出异常
const_cast:主要用于移除const或者volatile属性,或者是为非const变量添加这些属性
- 特点
- 只可以改变const和volatile属性,不可以用于其他类型的转换
- 不会改变底层的数据结构

reinterpret_cast:用于任何类型的转化,该转换不会进行任何检查,使用的时候需要注意转换的安全性和正确性
- 特点
- 限制少,可以用于任何类型的转换
- 没有类型检查,不保证其安全性,可能会存在未定义的行为

类型萃取
- 含义:利用模版的特定,根据传入的不同参数,判断其不同的类型,主要是用于STL中的一种技术(比较复杂,该处不深究)
- 应用场景
- 泛型编程:根据类型特性进行不同的处理,从而提高代码的通用性和灵活性
- 类型安全检查:编译期间进行类型检查,从而避免运行时的错误,从而提高程序的安全性
- 基础类型萃取
std::is_integral<T>:判断类型T是否为整型。std::is_floating_point<T>:判断类型T是否为浮点型。std::is_pointer<T>:判断类型T是否为指针类型(根据下面事例进一步理解)

结构体相等判断方法分析
结构体逐个成员进行比较:粗暴直接的比较每一个成员是否相等
struct MyStruct {int a;float b;std::string c;
};bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {return lhs.a == rhs.a && lhs.b == rhs.b && lhs.c == rhs.c;
}
重载比较运算符:利用运算符重载,从而判断两个结构体是否相等
struct MyStruct {int a;float b;std::string c;bool operator==(const MyStruct& other) const {return a == other.a && b == other.b && c == other.c;}
};
使用标准库函数memcmp,只适用于简单的数据类型,结构体中如果出现指针等就不可以
#include <cstring>struct MyStruct {int a;float b;
};bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {return std::memcmp(&lhs, &rhs, sizeof(MyStruct)) == 0;
}
拓展,直接使用Boost库可以实现两个结构体比较
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/equal_to.hpp>struct MyStruct {int a;float b;std::string c;
};BOOST_FUSION_ADAPT_STRUCT(MyStruct, a, b, c)bool areEqual(const MyStruct& lhs, const MyStruct& rhs) {return boost::fusion::equal_to(lhs, rhs);
}
C++常用的四种模板简述
函数模板: 定义一个通用的函数,函数可以根据指定的类型来推导

类模板:定义一个类的通用版本,类的类型参数可以在实例化的时候再进行指定

别名模板:给模板起一个别名,让模版更加方便的使用

变量模板:用于定义可以接受任何类型参数的常量,很少用

switch语句的case分支是否可以直接定义变量分析
- 结果:不可以,会导致异常编译错误
- 原因:因为switch语句中所有的case标签共享同一个作用域,不声明其作用域会造成命名冲突
- 解决:在case后面加一对{},来创建一个新的作用域就可以了

#include <iostream>void example(int n) {switch (n) {case 1:int a = 10; // 定义变量 astd::cout << "Case 1: " << a << std::endl;break;case 2:int b = 20; // 这里也定义了变量 bstd::cout << "Case 2: " << b << std::endl;break;default:std::cout << "Default case" << std::endl;}
}int main() {example(1);example(2);return 0;
}

#include <iostream>void example(int n) {switch (n) {case 1: {int a = 10; // 在新的作用域中定义变量 astd::cout << "Case 1: " << a << std::endl;break;}case 2: {int b = 20; // 在新的作用域中定义变量 bstd::cout << "Case 2: " << b << std::endl;break;}default:std::cout << "Default case" << std::endl;}
}int main() {example(1);example(2);return 0;
}
可变参数模版
- 含义:C++11的一种新模板特性,允许定义任意数量的参数
- 使用:具体参考代码事例理解
函数模版中可变参数模板的使用事例

#include <iostream>// 基础情况:不接受任何参数时终止递归
void print() {std::cout << std::endl;
}// 可变参数模板函数
template <typename T, typename... Args>
void print(T first, Args... args) {std::cout << first << " ";print(args...); // 递归调用
}int main() {print(1, 2, 3.5, "hello", 'A');return 0;


#include <iostream>// 基础情况:没有参数时返回0
int sum() {return 0;
}// 可变参数模板函数
template <typename T, typename... Args>
int sum(T first, Args... args) {return first + sum(args...); // 递归调用
}int main() {std::cout << sum(1, 2, 3, 4, 5) << std::endl; // 输出: 15return 0;
}
类模板的可变参数


#include <iostream>
#include <string>// 基础情况:空的元组
template <typename... Values>
class Tuple;// 可变参数模板类
template <typename Head, typename... Tail>
class Tuple<Head, Tail...> : private Tuple<Tail...> {
public:Tuple(Head head, Tail... tail) : Tuple<Tail...>(tail...), head_(head) {}Head head() const { return head_; }const Tuple<Tail...>& tail() const { return *this; }private:Head head_;
};// 终止递归的空元组特化
template <>
class Tuple<> {};int main() {Tuple<int, double, std::string> t(42, 3.14, "hello");std::cout << t.head() << std::endl; // 输出: 42std::cout << t.tail().head() << std::endl; // 输出: 3.14std::cout << t.tail().tail().head() << std::endl; // 输出: helloreturn 0;
}
相关文章:
C++ 重要特性探究
shared_from_this 使用分析 场景 类的成员函数需要获取指向自身的shared_ptr的时候类成员函数传递shared_ptr给其他函数或者对象的时候,目的是为了管理对象生命周期使用方法 首先类必须继承 std::enable_shared_from_this<T>必须使用 shared_from_this 获取指…...
c++_游戏_狼人杀
思路主要包括以下几个部分: 角色分配:代码中通过随机数的方式给狼人、平民、预言家和法师等角色进行分配,保证每个角色的数量和身份的随机性。 游戏进行:根据狼人、平民、预言家和法师等角色的身份,游戏进行了夜晚和白…...
MySQL——数据类型、索引的建立、数据的约束
文章目录 数据类型索引的建立普通索引唯一索引使用ALTER 命令添加和删除索引使用ALTER 命令添加和删除主键显示索引信息 数据的约束非空约束:not null,值不能为null唯一约束:unique,值不能重复主键约束:primary key外键…...
常见框架漏洞详解③!!
Apache Apache 是世界使⽤排名第⼀的 Web 服务器软件。它可以运⾏在⼏乎所有⼴泛使⽤的计算 机平台上,由于其跨平台和安全性被⼴泛使⽤,是最流⾏的 Web 服务器端软件之⼀。 apache⽬录结构: bin:存放常⽤命令⼯具,如h…...
大数据基础知识
大数据(Big Data)是指无法用传统数据处理工具和技术有效处理的大规模、复杂的数据集。大数据技术通过对这些数据进行存储、处理和分析,从中提取有价值的信息和见解。 1. 大数据的特点 大数据通常具有以下四个主要特点,被称为“4…...
SQL Server 的透明数据加密
透明数据加密是SQL Server数据库安全众多特性中的一个,本文只针对透明数据加密。 在此测试之前,已经按照文档如何快速获得一个测试用SQL Server企业版创建了一个SQL Server 2019,并按照文档为SQL Server安装示例数据库AdventureWorks安装了…...
Windows图形界面(GUI)-MFC-C/C++ - 列表视图(List Control) - CListCtrl
公开视频 -> 链接点击跳转公开课程博客首页 -> 链接点击跳转博客主页 目录 列表视图(List Control) - CListCtrl 创建列表视图 设置列表视图属性 成员函数 注意事项 示例代码 列表视图(List Control) - CListCtrl 创建列表视图 在对话框编辑器中ÿ…...
一机两用的简单介绍
电子政务外网终端使用过程的风险与挑战 1、终端防护弱,失陷风险大 政务外网终端具备访问互联网能力,造成政务外网终端极易感染僵木蠕病毒,破坏正常办公 政务外网终端易被攻击失陷,成为从互联网攻击政务外网的跳板机 2、VPN漏洞…...
uniapp离线打包热更新失败-AndroidStudio离线打包apk后无法下载打开-热更新失败-plus.runtime.install失败
效果图 仅安卓 前言 1.plus.runtime.install一直fail(20240808), uni.openDocument可以打开本地apk文件 2.权限问题需小心 跑通前提 1.先确定apk地址有效,浏览器中手动下载可安装 2.确保已添加离线打包AndroidStudio的“android.permission.INSTALL_PACKAGES”权…...
深植根基、蓬勃向上 | openKylin 2.0正式发布!
2024年8月8日,openKylin 2.0版本正式发布!该版本默认搭载Linux 6.6 LTS内核,完成180操作系统核心组件自主选型升级,深度融合AI技术,上线麒麟AI助手等实用AI功能,并为用户带来包括开明软件包格式、不可变系统…...
【Material-UI】按钮组:尺寸与颜色详解
文章目录 一、按钮组概述1. 组件介绍2. 基本用法 二、按钮组的尺寸(Sizes)1. 小尺寸(Small)2. 中等尺寸(Medium)3. 大尺寸(Large) 三、按钮组的颜色(Colors)1…...
app抓包 burp配置
证书导出 模拟器安装证书 点击安装证书 将证书直接拖进来就行 配置代理 打开浏览器抓包...
图像与像素:利用ImageJ分析荧光显微镜图像|QuPath基础教程1|24-08-08
内容概要 数字图像由像素组成,每个像素具有一个数值,通常与检测到的光线相关。相同的像素值可以通过不同的方式进行显示。在科学图像处理中,可以通过修改查找表来独立于像素值改变图像外观。 一、引言 图像由其最小的组成单位——像素构成。…...
Prompt Fuzzer:用于增强 GenAI 应用程序的开源工具
Prompt Fuzzer 是一个开源工具,可以评估GenAI应用程序的系统提示针对基于动态 LLM 的威胁的安全性。 Prompt Fuzzer 功能: 1. 模拟十几种类型的 GenAI 攻击。 2. 该工具会根据系统提示自动进行情境化,针对与 GenAI 应用程序相关的特定主题或行…...
Vision Pro使用GLFT 加载模型shader错误解决办法
Glft shader在vision pro上加载错误 前言相关背景解决办法 参考文章 前言 之前在Vision Pro上尝试加载Glb文件,但是加载完成后发现加载出来的Glb文件材质不正确。材质是黑色的。因此整理一下解决方案。 相关背景 使用Unity开发,Glb的加载插件为gltf F…...
Netty技术全解析:MessageToMessageCodec类深度解析
❃博主首页 : 「码到三十五」 ,同名公众号 :「码到三十五」,wx号 : 「liwu0213」 ☠博主专栏 : <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 :…...
Three 【3D车模换肤】
目录 🌟前言🌟先看效果🌟实现代码🌟写在最后 🌟前言 哈喽小伙伴们,最近工作比较忙一直没有给大家更新,新的专栏 Three.js第三篇,记录一下博主学习Three.js的过程;一起来…...
语言模型简介和Ngram模型(1)
语言模型介绍一 语言模型语言模型概念语言模型应用-语音识别声纹特征提取语言模型挑选成句 语言模型应用-手写识别语言模型应用-输入法语言模型分类语言模型评价指标-困惑度PPL N-gram语言模型马尔科夫假设平滑问题平滑问题解决一平滑问题解决二 插值优化语言模型应用-文本纠错…...
MessageBox弹框替代系统自带的alert、confirm -- 高仿ElementUI MessageBox
MessageBox 弹框 MessageBox 的作用是替代系统自带的 alert、confirm ,仅适合展示较为简单的内容。如果需要弹出较为复杂的内容,请使用定制的弹窗。基本仿照ElementUI的同名组件。 原生,无依赖项,自适应布局,双端通用&…...
数据结构一排序算法
排序算法总结 冒泡排序 冒泡排序(Bubble Sort)也是一种简单直观的排序算法。假设长度为n的数组arr,要按照从小到大排序。则冒泡排序的具体过程可以描述为:首先从数组的第一个元素开始到数组最后一个元素为止,对数组中…...
19c补丁后oracle属主变化,导致不能识别磁盘组
补丁后服务器重启,数据库再次无法启动 ORA01017: invalid username/password; logon denied Oracle 19c 在打上 19.23 或以上补丁版本后,存在与用户组权限相关的问题。具体表现为,Oracle 实例的运行用户(oracle)和集…...
大数据学习栈记——Neo4j的安装与使用
本文介绍图数据库Neofj的安装与使用,操作系统:Ubuntu24.04,Neofj版本:2025.04.0。 Apt安装 Neofj可以进行官网安装:Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
Git 3天2K星标:Datawhale 的 Happy-LLM 项目介绍(附教程)
引言 在人工智能飞速发展的今天,大语言模型(Large Language Models, LLMs)已成为技术领域的焦点。从智能写作到代码生成,LLM 的应用场景不断扩展,深刻改变了我们的工作和生活方式。然而,理解这些模型的内部…...
HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...
小木的算法日记-多叉树的递归/层序遍历
🌲 从二叉树到森林:一文彻底搞懂多叉树遍历的艺术 🚀 引言 你好,未来的算法大神! 在数据结构的世界里,“树”无疑是最核心、最迷人的概念之一。我们中的大多数人都是从 二叉树 开始入门的,它…...
保姆级【快数学会Android端“动画“】+ 实现补间动画和逐帧动画!!!
目录 补间动画 1.创建资源文件夹 2.设置文件夹类型 3.创建.xml文件 4.样式设计 5.动画设置 6.动画的实现 内容拓展 7.在原基础上继续添加.xml文件 8.xml代码编写 (1)rotate_anim (2)scale_anim (3)translate_anim 9.MainActivity.java代码汇总 10.效果展示 逐帧…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
goreplay
1.github地址 https://github.com/buger/goreplay 2.简单介绍 GoReplay 是一个开源的网络监控工具,可以记录用户的实时流量并将其用于镜像、负载测试、监控和详细分析。 3.出现背景 随着应用程序的增长,测试它所需的工作量也会呈指数级增长。GoRepl…...
React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?
系列回顾: 在上一篇《React核心概念:State是什么?》中,我们学习了如何使用useState让一个组件拥有自己的内部数据(State),并通过一个计数器案例,实现了组件的自我更新。这很棒&#…...

