当前位置: 首页 > news >正文

[算法与数据结构][c++]:左值、右值、左值引用、右值引用和std::move()

左值、右值、左值引用、右值引用和std::move

    • 1. 什么是左值、右值
    • 2. 什么是左值引用、右值引用
    • 3. **右值引用和std::move的应用场景**
      • 3.1 实现移动语义
      • 3.2 **实例:vector::push_back使用std::move提高性能**
    • **4. 完美转发 std::forward**
    • 5. Reference

写在前面: 如果你也被左值、右值、左值引用、右值引用和std::move搞得焦头烂额,相关概念和理解不够深入,或者认识模棱两可,那么这篇文章将非常的适合你,耐心阅读,相信一定会有所收获~~


1. 什么是左值、右值

左值: 可以取地址、位于等号左边 – 表达式结束后依然存在的持久对象(代表一个在内存中占有确定位置的对象)

右值: 没法取地址、位于等号右边 – 表达式结束时不再存在的临时对象(不在内存中占有确定位置的表达式)

便携方法:对表达式取地址,如果能,则为左值,否则为右值

int val;
val = 4; // 正确 ①
4 = val; // 错误 ②

上述例子中,由于在之前已经对变量val进行了定义,故在栈上会给val分配内存地址,运算符=要求等号左边是可修改的左值,4是临时参与运算的值,一般在寄存器上暂存,运算结束后在寄存器上移除该值,故①是对的,②是错的

2. 什么是左值引用、右值引用

引用本质是别名,可以通过引用修改变量的值,传参时传引用可以避免拷贝,其实现原理和指针类似。

左值引用:指向左值的引用,称为左值引用

int a = 5;
int &ref_a = a; // 左值引用指向左值,编译通过
int &ref_a = 5; // 左值引用指向了右值,会编译失败

引用是变量的别名,由于右值没有地址,没法被修改,所以左值引用无法指向右值

那么const左值引用可不可以指向右值呢?

​ 可以!!!

const int &ref_a = 5;

const左值引用不会修改指向值,因此可以指向左值和右值,这也是为什么要使用const &作为函数参数的原因之一,如std::vectorpush_back函数原型:

void push_back (const value_type& val); 
//如果没有const,vec.push_back(5)这样的代码就无法编译通过了。
//因为5是右值

右值引用:右值引用的标志是&&,可以指向右值,不可以指向左值。

int &&ref_a_right = 5; // okint a = 5;
int &&ref_a_left = a; // 编译不过,右值引用不可以指向左值ref_a_right = 6; // 右值引用的用途:可以修改右值

自然而然就会出现这样一个问题:右值引用有办法指向左值吗?右值引用有啥作用?

有办法,std::move()

int a = 5; // left value
int &ref_a_l = a; // left reference.
int &&ref_a_r = std::move(a); //rvalue reference.
std::cout << ref_a_r << std::endl;

左值a通过std::move移动到了右值ref_a_right中,那是不是a里边就没有值了?并不是,打印出a的值仍然是5.

std::move是一个非常有迷惑性的函数,不理解左右值概念的人往往以为它能把一个变量里的内容移动到另一个变量,但事实上std::move移动不了什么,唯一的功能是把左值强制转化为右值,让右值引用可以指向左值。其实现等同于一个类型转换:static_cast<T&&>(lvalue)。 所以,单纯的std::move(xxx)不会有性能提升!!!!

那么左值引用、右值引用本身是左值还是右值?

被声明出来的左值引用和右值引用都是左值,因为他们都是有地址的,也位于等号左边,这符合我们刚刚的定义。

右值引用既可以是左值也可以是右值,如果有名称则为左值,否则是右值作为函数返回值的 && 是右值,直接声明出来的 && 是左值

左右值引用的区别

  1. 从性能上讲,左右值引用没有区别,传参使用左右值引用都可以避免拷贝。
  2. 右值引用可以直接指向右值,也可以通过std::move指向左值;而左值引用只能指向左值(const左值引用也能指向右值)。
  3. 作为函数形参时,右值引用更灵活。虽然const左值引用也可以做到左右值都接受,但它无法修改,有一定局限性。

3. 右值引用和std::move的应用场景

按上文分析,std::move只是类型转换工具,不会对性能有好处;右值引用在作为函数形参时更具灵活性,看上去还是挺鸡肋的。他们有什么实际应用场景吗?

3.1 实现移动语义

在实际场景中,右值引用和std::move被广泛用于在STL和自定义类中实现移动语义,避免拷贝,从而提升程序性能。 在没有右值引用之前,一个简单的数组类通常实现如下,有构造函数拷贝构造函数赋值运算符重载析构函数等。

class Array {
public:Array(int size) : size_(size) {data_ = new int[size_];}// 深拷贝构造->当代码中有指针开辟堆内存时,// 		必须显式定义拷贝构造函数,开辟新的堆内存,存储拷贝后的指针数据,// 		否则两个对象的指针会指向同一个堆内存地址,当某一个对象析构后,// 		相应的堆内存就会释放掉,导致另一个对象内的指针成为悬浮指针!!!// 浅拷贝->不涉及指针的拷贝Array(const Array& temp_array) {size_ = temp_array.size_;data_ = new int[size_];for (int i = 0; i < size_; i ++) {data_[i] = temp_array.data_[i];}}// 深拷贝赋值 const引用避免了传参拷贝,但是堆内存仍然需要深拷贝,所以需要用到std::move实现移动赋值Array& operator=(const Array& temp_array) {delete[] data_;size_ = temp_array.size_;data_ = new int[size_];for (int i = 0; i < size_; i ++) {data_[i] = temp_array.data_[i];}}~Array() {delete[] data_;}public:int *data_;int size_;
};

该类的拷贝构造函数、赋值运算符重载函数已经通过使用左值引用传参来避免一次多余拷贝了,但是内部实现要深拷贝,无法避免。 这时,有人提出一个想法:是不是可以提供一个移动构造函数,把被拷贝者的数据移动过来,被拷贝者后边就不要了,这样就可以避免 深拷贝 了,

关于深拷贝和浅拷贝的区别和联系,后续也会出一篇文章,链接在:深拷贝与浅拷贝
深拷贝构造->当代码中有指针开辟堆内存时,必须显式定义拷贝构造函数,开辟新的堆内存,存储拷贝后的指针数据,否则两个对象的指针会指向同一个堆内存地址,当某一个对象析构后,相应的堆内存就会释放掉,导致另一个对象内的指针成为悬浮指针!!!
浅拷贝->不涉及指针的拷贝

如:

class Array {
public:Array(int size) : size_(size) {data_ = new int[size_];}// 深拷贝构造Array(const Array& temp_array) {...}// 深拷贝赋值Array& operator=(const Array& temp_array) {...}// 移动构造函数(重载深拷贝构造函数),可以浅拷贝-> 形参是const& 构造函数内,对temp_array赋值,编译不通过~Array(const Array& temp_array, bool move) {data_ = temp_array.data_;size_ = temp_array.size_;// 为防止temp_array析构时delete data,提前置空其data_      temp_array.data_ = nullptr;}~Array() {delete [] data_;}public:int *data_;int size_;
};

这么做有2个问题:

  • 不优雅,表示移动语义还需要一个额外的参数(或者其他方式)。-> 重载拷贝构造函数
  • 无法实现!temp_array是个const左值引用,无法被修改,所以temp_array.data_ = nullptr;这行会编译不过。当然函数参数可以改成非const:Array(Array& temp_array, bool move){...},这样也有问题,由于左值引用不能接右值,Array a = Array(Array(), true);这种调用方式就没法用了。

可以发现左值引用真是用的很不爽右值引用的出现解决了这个问题,在STL的很多容器中,都实现了以 右值引用为参数移动构造函数移动赋值重载函数,或者其他函数,最常见的如std::vector的push_backemplace_back。参数为左值引用意味着拷贝,为右值引用意味着移动。

class Array {
public:......// 优雅Array(Array&& temp_array) {data_ = temp_array.data_;size_ = temp_array.size_;// 为防止temp_array析构时delete data,提前置空其data_      temp_array.data_ = nullptr;}public:int *data_;int size_;
};

如何判断一个对象是否是可以移动的?

在C++中,是否可以移动一个对象,取决于该对象的类是否定义了移动构造函数或移动赋值运算符。 以下是一些判断一个对象是否可以被移动的方法:

  1. 检查类的定义:如果一个类定义了移动构造函数或移动赋值运算符,那么这个类的对象就可以被移动。移动构造函数和移动赋值运算符的声明通常如下:

    class MyClass {
    public:MyClass(MyClass&& other); // 移动构造函数MyClass& operator=(MyClass&& other); // 移动赋值运算符// ...
    };
    

    注意,这两个函数的参数都是右值引用。

  2. 使用std::is_move_constructiblestd::is_move_assignable:这两个模板在<type_traits>头文件中定义,可以用来检查一个类型是否有可用的移动构造函数或移动赋值运算符:

    std::cout << std::is_move_constructible<MyClass>::value << std::endl; // 如果MyClass可移动,输出1,否则输出0
    std::cout << std::is_move_assignable<MyClass>::value << std::endl; // 如果MyClass可移动赋值,输出1,否则输出0
    
  3. 使用std::move_if_noexcept:这个模板函数可以用来判断是否可以无异常地移动一个对象。如果移动操作可能抛出异常,它会选择拷贝操作。这在一些容器操作中非常有用,例如std::vector的重新分配。

需要注意的是,即使一个对象可以被移动,也不意味着应该总是移动它。在某些情况下,例如当你知道一个对象将在移动操作后立即被销毁,或者你想避免昂贵的拷贝操作时,移动是有意义的。在其他情况下,移动可能会导致难以追踪的错误,例如,如果你错误地移动了一个仍然需要使用的对象。

3.2 实例:vector::push_back使用std::move提高性能

// std::move会调用到移动语义函数,避免了深拷贝。
int main() {std::string str1 = "aacasxs";std::vector<std::string> vec;vec.push_back(str1); // 传统方法,copyvec.push_back(std::move(str1)); // 调用移动语义的push_back方法,避免拷贝,str1会失去原有值,变成空字符串vec.emplace_back(std::move(str1)); // emplace_back效果相同,str1会失去原有值vec.emplace_back("axcsddcas"); // 当然可以直接接右值
}// std::vector方法定义
void push_back (const value_type& val);
void push_back (value_type&& val); // 内部调用了emplace_backvoid emplace_back (Args&&... args);

可移动对象在<需要拷贝且被拷贝者之后不再被需要>的场景,建议使用std::move触发移动语义,提升性能。

moveable_objecta = moveable_objectb; 
改为: 
moveable_objecta = std::move(moveable_objectb);

还有些STL类是move-only的,比如unique_ptr,这种类只有移动构造函数,因此只能移动(转移内部对象所有权,或者叫浅拷贝),不能拷贝(深拷贝):

std::unique_ptr<A> ptr_a = std::make_unique<A>();std::unique_ptr<A> ptr_b = std::move(ptr_a); // unique_ptr只有‘移动赋值重载函数‘,参数是&& ,只能接右值,因此必须用std::move转换类型std::unique_ptr<A> ptr_b = ptr_a; // 编译不通过

std::move本身只做类型转换,对性能无影响。 我们可以在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。

4. 完美转发 std::forward

std::move一样,它的兄弟std::forward也充满了迷惑性,虽然名字含义是转发,但他并不会做转发,同样也是做类型转换.

与move相比,forward更强大,move只能转出来右值,forward都可以。

std::forward(u)有两个参数:T与 u。 a. 当T为左值引用类型时,u将被转换为T类型的左值; b. 否则u将被转换为T类型右值。

举个例子,有main,A,B三个函数,调用关系为:main->A->B,建议先看懂2.3节对左右值引用本身是左值还是右值的讨论再看这里:

void B(int&& ref_r) {ref_r = 1;
}// A、B的入参是右值引用
// 有名字的右值引用是左值,因此ref_r是左值
void A(int&& ref_r) {B(ref_r);  // 错误,B的入参是右值引用,需要接右值,ref_r是左值,编译失败B(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过B(std::forward<int>(ref_r));  // ok,std::forward的T是int类型,属于条件b,因此会把ref_r转为右值
}int main() {int a = 5;A(std::move(a));
}

例2:

void change2(int&& ref_r) {ref_r = 1;
}void change3(int& ref_l) {ref_l = 1;
}// change的入参是右值引用
// 有名字的右值引用是 左值,因此ref_r是左值
void change(int&& ref_r) {change2(ref_r);  // 错误,change2的入参是右值引用,需要接右值,ref_r是左值,编译失败change2(std::move(ref_r)); // ok,std::move把左值转为右值,编译通过change2(std::forward<int &&>(ref_r));  // ok,std::forward的T是右值引用类型(int &&),符合条件b,因此u(ref_r)会被转换为右值,编译通过change3(ref_r); // ok,change3的入参是左值引用,需要接左值,ref_r是左值,编译通过change3(std::forward<int &>(ref_r)); // ok,std::forward的T是左值引用类型(int &),符合条件a,因此u(ref_r)会被转换为左值,编译通过// 可见,forward可以把值转换为左值或者右值
}int main() {int a = 5;change(std::move(a));
}

上边的示例在日常编程中基本不会用到,std::forward最主要运于模版编程的参数转发中,想深入了解需要学习万能引用(T &&)引用折叠(eg:& && → ?)等知识,本文就不详细介绍这些了。

5. Reference

https://zhuanlan.zhihu.com/p/374392832

https://zhuanlan.zhihu.com/p/335994370

https://www.cnblogs.com/shadow-lr/p/Introduce_Std-move.html

相关文章:

[算法与数据结构][c++]:左值、右值、左值引用、右值引用和std::move()

左值、右值、左值引用、右值引用和std::move 1. 什么是左值、右值2. 什么是左值引用、右值引用3. **右值引用和std::move的应用场景**3.1 实现移动语义3.2 **实例&#xff1a;vector::push_back使用std::move提高性能** **4. 完美转发 std::forward**5. Reference 写在前面&…...

【QT】day3

1.登陆界面 2.登陆失败 3.登陆成功弹窗 4.点击OK后跳转 #include "mainwindow.h" #include "ui_mainwindow.h"MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow) {ui->setupUi(this); }MainWindow::~MainWindow…...

c++ fork, execl 参数 logcat | grep

Linux进程编程(PS: exec族函数、system、popen函数)_linux popen函数会新建进程吗-CSDN博客 execvp函数详解_如何在C / C 中使用execvp&#xff08;&#xff09;函数-CSDN博客 C语言的多进程fork()、函数exec*()、system()与popen()函数_c语言 多进程-CSDN博客 Linux---fork…...

QT:单例

单例的定义 官方定义&#xff1a;单例是指确保一个类在任何情况下都绝对只有一个实例&#xff0c;并提供一个全局访问点。 单例的写法 抓住3点&#xff1a; 构造函数私有化&#xff08;确保只有一个实例&#xff09;提供一个可以获取构造实例的接口&#xff08;提供唯一的实…...

IPv6路由协议---IPv6动态路由(OSPFv3-4)

OSPFv3的链路状态通告LSA类型 链路状态通告是OSPFv3进行路由计算的关键依据,链路状态通告包含链路状态类型、链路状态ID、通告路由器三元组唯一地标识了一个LSA。 OSPFv3的LSA头仍然保持20字节,但是内容变化了。在LSA头中,OSPFv2的LS age、Advertising Router、LS Sequence…...

移动通信原理与关键技术学习(4)

1.小尺度衰落 Small-Scale Fading 由于收到的信号是由通过不同的多径到达的信号的总和&#xff0c;接收信号的增强有一定的减小。 小尺度衰落的特点&#xff1a; 信号强度在很小的传播距离或时间间隔内的快速变化&#xff1b;不同多径信号多普勒频移引起的随机调频&#xff…...

第二百五十八回

文章目录 1. 概念介绍2. 思路与方法2.1 实现思路2.2 实现方法 3. 示例代码4. 内容总结 我们在上一章回中介绍了"模拟对话窗口的页面"相关的内容&#xff0c;本章回中将介绍如何创建一个可以输入内容的对话框.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念…...

freesurfer-reconall后批量提取TIV(颅内总体积)

#提取TIV #singleline=$(grep Estimated Total Intracranial Volume /usr/local/freesurfer/subjects/bect-3d+bold-wangjingchen-4.9y-2/stats/aseg.sta...

【GO】如何用 Golang 的 os/exec 执行 pipe 替换文件

背景 主要记录一下怎么用 Golang 的 os/exec 去执行一个 cmd 的 pipeline&#xff0c;就是拿 cmdA 的输出作为 cmdB 的输入&#xff0c;这里记录了两种方法去替换文件里面的字符串。 pipe 那个逻辑在 demo1 里。 另外一种是直接读文件做替换&#xff0c;一不小心两个都放进来了…...

基于Spring-boot-websocket的聊天应用开发总结

目录 1.概述 1.1 Websocket 1.2 STOMP 1.3 源码 2.Springboot集成WS 2.1 添加依赖 2.2 ws配置 2.2.1 WebSocketMessageBrokerConfigurer 2.2.2 ChatController 2.2.3 ChatInRoomController 2.2.4 ChatToUserController 2.3 前端聊天配置 2.3.1 index.html和main.j…...

2023年度总结 - 职业生涯第一个十年

2023年只剩下最后一周&#xff0c;又到了一年一度该做年末总结的时候了。 回想起去年&#xff0c;还有人专门建立了一个关于年度总结文章汇总的仓库。读了很多篇别人写的&#xff0c;给了我很多的触动和感想。这里的每篇文章都是关于某个人这一整年的生活和工作的轨迹啊。即使你…...

setup 语法糖

只有vue3.2以上版本可以使用 优点&#xff1a; 更少的样板内容&#xff0c;更简洁的代码 能够使用纯 Typescript 声明props 和抛出事件 更好的运行时性能 更好的IDE类型推断性能 在sciprt标识上加上setup 顶层绑定都可以使用 不需要return &#xff0c;可以直接使用 使用组件…...

Javaweb之Mybatis的基础操作的详细解析

1. Mybatis基础操作 学习完mybatis入门后&#xff0c;我们继续学习mybatis基础操作。 1.1 需求 需求说明 通过分析以上的页面原型和需求&#xff0c;我们确定了功能列表&#xff1a; 查询 根据主键ID查询 条件查询 新增 更新 删除 根据主键ID删除 根据主键ID批量删除 …...

知名开发者社区Stack Overflow发布《2023 年开发者调查报告》

Stack Overflow成立于2008年&#xff0c;最知名的是它的公共问答平台&#xff0c;每月有超过 1 亿人访问该平台来提问、学习和分享技术知识。是世界上最受欢迎的开发者社区之一。每年都会发布一份关于开发者的调查报告&#xff0c;来了解不断变化的开发人员现状、正在兴起或衰落…...

vue element plus Form 表单

表单包含 输入框, 单选框, 下拉选择, 多选框 等用户输入的组件。 使用表单&#xff0c;您可以收集、验证和提交数据。 TIP Form 组件已经从 2. x 的 Float 布局升级为 Flex 布局。 典型表单# 最基础的表单包括各种输入表单项&#xff0c;比如input、select、radio、checkbo…...

zmq_connect和zmq_poll

文章内容&#xff1a; 介绍函数zmq_connect和zmq_poll的使用 zmq_connect zmq_connect函数是ZeroMQ库中的一个函数&#xff0c;用于在C语言中创建一个与指定地址的ZeroMQ套接字的连接。该函数的原型如下&#xff1a; int zmq_connect(void *socket, const char *endpoint);其…...

TinyLog iOS v3.0接入文档

1.背景 为在线教育部提供高效、安全、易用的日志组件。 2.功能介绍 2.1 日志格式化 目前输出的日志格式如下&#xff1a; 日志级别/[YYYY-MM-DD HH:MM:SS MS] TinyLog-Tag: |线程| 代码文件名:行数|函数名|日志输出内容触发flush到文件的时机&#xff1a; 每15分钟定时触发…...

react-native 配置@符号绝对路径配置和绝对路径没有提示的问题

这里需要用到vscode的包 yarn add babel-plugin-module-resolver 找到根目录里的babel.config.js 在页面添加plugins配置 直接替换 module.exports {presets: [module:metro-react-native-babel-preset],plugins: [[module-resolver,{root: [./src],alias: {/utils: ./src/…...

element的Table表格组件树形数据与懒加载简单使用

目录 1. 代码实现2. 效果图3. 解决新增、删除、修改之后树节点不刷新问题。&#xff08;[参考文章](https://blog.csdn.net/weixin_41549971/article/details/135504471)&#xff09; 1. 代码实现 <template><div><!-- lazy 是否懒加载子节点数据 --><!-…...

游戏、设计选什么内存条?光威龙武系列DDR5量大管饱

如果你是一位PC玩家或者创作者&#xff0c;日常工作娱乐中&#xff0c;确实少不了大容量高频内存的支持&#xff0c;这样可以获得更高的工作效率&#xff0c;光威龙武系列DDR5内存条无疑是理想之选。它可以为计算机提供强劲的性能表现和稳定的运行体验&#xff0c;让我们畅玩游…...

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周&#xff0c;有很多同学在写期末Java web作业时&#xff0c;运行tomcat出现乱码问题&#xff0c;经过多次解决与研究&#xff0c;我做了如下整理&#xff1a; 原因&#xff1a; IDEA本身编码与tomcat的编码与Windows编码不同导致&#xff0c;Windows 系统控制台…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销&#xff0c;平衡网络负载&#xff0c;延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践

6月5日&#xff0c;2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席&#xff0c;并作《智能体在安全领域的应用实践》主题演讲&#xff0c;分享了在智能体在安全领域的突破性实践。他指出&#xff0c;百度通过将安全能力…...

管理学院权限管理系统开发总结

文章目录 &#x1f393; 管理学院权限管理系统开发总结 - 现代化Web应用实践之路&#x1f4dd; 项目概述&#x1f3d7;️ 技术架构设计后端技术栈前端技术栈 &#x1f4a1; 核心功能特性1. 用户管理模块2. 权限管理系统3. 统计报表功能4. 用户体验优化 &#x1f5c4;️ 数据库设…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...

uniapp手机号一键登录保姆级教程(包含前端和后端)

目录 前置条件创建uniapp项目并关联uniClound云空间开启一键登录模块并开通一键登录服务编写云函数并上传部署获取手机号流程(第一种) 前端直接调用云函数获取手机号&#xff08;第三种&#xff09;后台调用云函数获取手机号 错误码常见问题 前置条件 手机安装有sim卡手机开启…...

从面试角度回答Android中ContentProvider启动原理

Android中ContentProvider原理的面试角度解析&#xff0c;分为​​已启动​​和​​未启动​​两种场景&#xff1a; 一、ContentProvider已启动的情况 1. ​​核心流程​​ ​​触发条件​​&#xff1a;当其他组件&#xff08;如Activity、Service&#xff09;通过ContentR…...

MySQL的pymysql操作

本章是MySQL的最后一章&#xff0c;MySQL到此完结&#xff0c;下一站Hadoop&#xff01;&#xff01;&#xff01; 这章很简单&#xff0c;完整代码在最后&#xff0c;详细讲解之前python课程里面也有&#xff0c;感兴趣的可以往前找一下 一、查询操作 我们需要打开pycharm …...