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

PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图)

PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图)



关于作者


作者:小白熊

作者简介:精通python、matlab、c#语言,擅长机器学习,深度学习,机器视觉,目标检测,图像分类,姿态识别,语义分割,路径规划,智能优化算法,数据分析,各类创新融合等等。

联系邮箱:xbx3144@163.com

科研辅导、知识付费答疑、个性化定制以及其他合作需求请联系作者~



前言

  在本文中,我们将详细介绍如何使用PyTorch框架,结合ResNet18模型,进行图像分类任务。这里我们选择了一个情感识别数据集——RAF-DB(Real-world Affective Faces Database),来进行实验。通过本文,你将学习到如何准备数据、构建模型、训练模型、评估模型,并可视化训练过程中的损失曲线。



1 模型理论

1.1 深度学习基础

  深度学习是机器学习的一个分支,它通过使用深层神经网络来模拟人脑的学习过程。在图像分类任务中,卷积神经网络(Convolutional Neural Network, CNN)是最常用的模型之一。CNN通过卷积层、池化层、全连接层等结构,能够自动提取图像中的特征,并进行分类。

1.2 ResNet模型

  ResNet(Residual Network)是一种深度卷积神经网络,它通过引入残差块(Residual Block)来解决深度神经网络中的梯度消失和梯度爆炸问题。残差块通过引入一个恒等映射(Identity Mapping),使得网络在训练过程中能够更容易地学习到特征。ResNet有多个版本,如ResNet18、ResNet34、ResNet50等,其中数字表示网络的层数。ResNet18作为ResNet系列中的一个轻量级模型,具备较好的性能和较低的计算复杂度,非常适合用于图像分类任务

1.3 交叉熵损失函数

  在分类任务中,交叉熵损失函数(Cross Entropy Loss)是最常用的损失函数之一。它衡量的是模型输出的概率分布与真实标签的概率分布之间的差异。交叉熵损失函数越小,表示模型的预测结果越接近真实标签。

1.4 优化器

  优化器用于更新模型的权重,以最小化损失函数。常用的优化器有SGD(随机梯度下降)、Adam等。SGD是最基础的优化器,它通过计算梯度来更新权重,但容易陷入局部最小值。Adam优化器结合了动量(Momentum)和RMSprop的思想,能够在训练过程中自适应地调整学习率,通常能够取得更好的效果。



2 代码解析

2.1 数据准备

  首先,我们需要准备数据集。这里使用的是RAF-DB数据集,它是一个情感识别数据集,包含了多种情感标签的图像。

image_path = './data/RAF-DB'  # 数据集路径,需修改  
labels_num = 7  # 标签类别数量,需修改

  我们使用datasets.ImageFolder来加载数据集,它会自动根据文件夹名称来划分标签。然后,我们定义了数据转换(data_transform),包括随机裁剪、随机水平翻转、归一化等操作。

data_transform = {  "train": transforms.Compose([transforms.RandomResizedCrop(224),  transforms.RandomHorizontalFlip(),  transforms.ToTensor(),  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),  "val": transforms.Compose([transforms.Resize((224, 224)),  transforms.ToTensor(),  transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}

2.2 模型构建

  我们选择了ResNet18作为基模型,并修改了最后一层全连接层的输出维度,以适应我们的分类任务。

net = models.resnet18()  
fc_input_feature = net.fc.in_features  
net.fc = nn.Linear(fc_input_feature, labels_num)

  然后,我们加载了预训练的权重,并删除了最后一层的权重,因为我们需要重新训练这一层。

pretrained_weight = torch.hub.load_state_dict_from_url(  url='https://download.pytorch.org/models/resnet18-5c106cde.pth', progress=True)  
del pretrained_weight['fc.weight']  
del pretrained_weight['fc.bias']  
net.load_state_dict(pretrained_weight, strict=False)

2.3 训练过程

  在训练过程中,我们使用了交叉熵损失函数和SGD优化器。同时,我们还设置了学习率调度器(scheduler),它会在每10个epoch后,将学习率乘以0.1。

criterion = nn.CrossEntropyLoss()  
LR = 0.01  
optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)  
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)

  训练过程包括前向传播、计算损失、反向传播和更新权重。我们还使用了tqdm库来显示训练进度。

for epoch in range(epochs):  # train  net.train()  running_loss = 0.0  train_bar = tqdm(train_loader, file=sys.stdout)  acc1 = 0  for step, data in enumerate(train_bar):  images, labels = data  images = images.to(device)  labels = labels.to(device)  output = net(images)  optimizer.zero_grad()  loss = criterion(output, labels)  loss.backward()  optimizer.step()  running_loss += loss.item()  train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1, epochs, loss)  predicted = torch.max(output, dim=1)[1]  acc1 += torch.eq(predicted, labels).sum().item()  train_accurate = acc1 / train_num  # validate  net.eval()  with torch.no_grad():  val_bar = tqdm(validate_loader, file=sys.stdout)  val_loss = 0.0  acc = 0  for val_data in val_bar:  val_images, val_labels = val_data  val_images = val_images.to(device)  val_labels = val_labels.to(device)  output = net(val_images)  loss = criterion(output, val_labels)  val_loss += loss.item()  predict_y = torch.max(output, dim=1)[1]  acc += torch.eq(predict_y, val_labels).sum().item()  val_accurate = acc / val_num  print('[epoch %d] train_loss: %.3f  val_accuracy: %.3f train_accuracy: %.3f' %  (epoch + 1, running_loss / train_steps, val_accurate, train_accurate))  train_losses.append(running_loss / train_steps)  if val_accurate > best_acc:  best_acc = val_accurate  save_path_epoch = os.path.join(save_path, f"resnet_{epoch + 1}_{val_accurate}.pth")  torch.save(net.state_dict(), save_path_epoch)

2.4 损失曲线绘制

  最后,我们绘制了训练过程中的损失曲线,以便观察模型的训练效果。

plt.figure(figsize=(10, 8))  
plt.plot(range(1, epochs + 1), train_losses, label='train')  
plt.xlabel('Epoch')  
plt.ylabel('Loss')  
plt.title('resnet')  
plt.legend()  
plt.savefig('./runs/loss.jpg')  
plt.show()

loss



3 文件夹结构

  为了使代码更加清晰和易于管理,建议使用以下文件夹结构:

project_root/  
│  
├── data/  
│   └── RAF-DB/  
│       ├── train/  
│       │   ├── class1/  
│       │   ├── class2/  
│       │   ...  
│       └── val/  
│           ├── class1/  
│           ├── class2/  
│           ...  
│  
├── runs/  # 保存训练好的模型权重和损失曲线图  
│   ├── loss.jpg  
│   ├── resnet_1_xxx.pth  
│   ...  
│  
├── train.py  # 训练脚本  
│  
├── class_indices.json  # 类别索引映射文件  
│  
├── requirements.txt  # 项目依赖包  
│  
└── README.md  # 项目说明文档

  在这个结构中,data文件夹用于存放数据集,数据划分参考图像分类模型数据集划分教程:如何划分训练集和验证集

  本文的class_indices.json类别索引映射文件如下:

{"0": "anger","1": "disgust","2": "fear","3": "happiness","4": "neutral","5": "sadness","6": "surprise"
}



4 完整代码

import os
import sys
import json
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import torch.nn as nn
from torchvision import transforms, datasets
import torch.optim as optim
from tqdm import tqdm
import torch
from torch.utils.data import DataLoader
import torchvision.models as modelsdef main():device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")print("using {} device.".format(device))data_transform = {"train": transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),"val": transforms.Compose([transforms.Resize((224, 224)),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])}image_path = './RAF-DB/RAF-DB'  # 数据集路径,需修改labels_num = 7  # 标签类别数量,需修改assert os.path.exists(image_path), "{} path does not exist.".format(image_path)train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"),transform=data_transform["train"])train_num = len(train_dataset)flower_list = train_dataset.class_to_idxcla_dict = dict((val, key) for key, val in flower_list.items())# write dict into json filejson_str = json.dumps(cla_dict, indent=4)with open('./class_indices.json', 'w') as json_file:  # json文件路径,需修改json_file.write(json_str)batch_size = 64  # 批处理大小,可修改nw = min([os.cpu_count(), batch_size if batch_size > 1 else 0, 8])  # number of workersprint('Using {} dataloader workers every process'.format(nw))train_loader = torch.utils.data.DataLoader(train_dataset,batch_size=batch_size, shuffle=True,num_workers=nw)validate_dataset = datasets.ImageFolder(root=os.path.join(image_path, "val"),transform=data_transform["val"])val_num = len(validate_dataset)validate_loader = torch.utils.data.DataLoader(validate_dataset,batch_size=batch_size, shuffle=False,num_workers=nw)print("using {} images for training, {} images for validation.".format(train_num,val_num))# 初始化模型net = models.resnet18()fc_input_feature = net.fc.in_featuresnet.fc = nn.Linear(fc_input_feature, labels_num)# load权重pretrained_weight = torch.hub.load_state_dict_from_url(url='https://download.pytorch.org/models/resnet18-5c106cde.pth', progress=True)del pretrained_weight['fc.weight']del pretrained_weight['fc.bias']net.load_state_dict(pretrained_weight, strict=False)net.to(device)criterion = nn.CrossEntropyLoss()  # 交叉熵损失函数LR = 0.01optimizer = optim.SGD(net.parameters(), lr=LR, momentum=0.9)scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.1)epochs = 100  # 训练轮数,可修改best_acc = 0.0save_path = './runs'train_steps = len(train_loader)train_losses = []  # 存储每个epoch的训练损失for epoch in range(epochs):# trainnet.train()running_loss = 0.0train_bar = tqdm(train_loader, file=sys.stdout)acc1 = 0for step, data in enumerate(train_bar):images, labels = dataimages = images.to(device)labels = labels.to(device)output = net(images)optimizer.zero_grad()loss = criterion(output, labels)loss.backward()optimizer.step()running_loss += loss.item()train_bar.desc = "train epoch[{}/{}] loss:{:.3f}".format(epoch + 1,epochs,loss)predicted = torch.max(output, dim=1)[1]acc1 += torch.eq(predicted, labels).sum().item()train_accurate = acc1 / train_num# validatenet.eval()with torch.no_grad():val_bar = tqdm(validate_loader, file=sys.stdout)for val_data in val_bar:val_images, val_labels = val_dataval_images = val_images.to(device)val_labels = val_labels.to(device)output = net(val_images)loss = criterion(output, val_labels)val_loss += loss.item()predict_y = torch.max(output, dim=1)[1]acc += torch.eq(predict_y, val_labels).sum().item()val_accurate = acc / val_numprint('[epoch %d] train_loss: %.3f  val_accuracy: %.3f train_accuracy: %.3f' %(epoch + 1, running_loss / train_steps, val_accurate, train_accurate))train_losses.append(running_loss / train_steps)if val_accurate > best_acc:best_acc = val_accuratesave_path_epoch = os.path.join(save_path, f"resnet_{epoch + 1}_{val_accurate}.pth")torch.save(net.state_dict(), save_path_epoch)# 绘制损失曲线plt.figure(figsize=(10, 8))plt.plot(range(1, epochs + 1), train_losses, label='train')plt.xlabel('Epoch')plt.ylabel('Loss')plt.title('resnet')plt.legend()plt.savefig('./runs/loss.jpg')plt.show()print('Finished Training')if __name__ == '__main__':main()



5 结语

  在本文中,我们深入探讨了如何使用PyTorch框架和ResNet18模型,结合RAF-DB数据集来实现图像情感识别。通过系统的数据预处理、模型构建、训练与优化,以及评估等步骤,我们成功地训练出了一个能够识别图像中人物情感的模型。

  RAF-DB数据集作为本文的核心数据资源,展现出了其独特的价值。它是一个大规模、高质量的面部表情数据库,包含了数千张精挑细选的高分辨率面部图像,每张图像都配备了精确的表情标签。这些图像覆盖了高兴、悲伤、愤怒、惊讶等多种基本及复合表情,为模型训练提供了丰富的数据支持。此外,RAF-DB数据集还具备真实性、完整性、易用性和研究驱动等特点,确保了研究的普适性和可靠性,为情感分析、人脸识别以及表情识别等领域的研究者提供了宝贵的资源。

  在模型选择方面,我们采用了ResNet18模型。ResNet(Residual Network,残差网络)是一种由微软亚洲研究院提出的深度神经网络结构,其核心在于通过残差连接(residual connections)解决了深层网络训练中的梯度消失和梯度爆炸问题。在本文中,我们利用ResNet18模型对RAF-DB数据集进行了训练,并成功地构建了一个情感识别模型。

相关文章:

PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图)

PyTorch图像分类实战——基于ResNet18的RAF-DB情感识别(附完整代码和结果图) 关于作者 作者:小白熊 作者简介:精通python、matlab、c#语言,擅长机器学习,深度学习,机器视觉,目标检测…...

【OccNeRF: Advancing 3D Occupancy Prediction in LiDAR-Free Environments】阅读笔记

【OccNeRF: Advancing 3D Occupancy Prediction in LiDAR-Free Environments】阅读笔记 1. 论文概述Abstract1. Introduction2. Related work2.1 3D Occupancy Prediction2.2 Neural Radiance Fields2.3 Self-supervised Depth Estimation 3. Method3.1 Parameterized Occupanc…...

DDRPHY数字IC后端设计实现系列专题之后端设计导入,IO Ring设计

本章详细分析和论述了 LPDDR3 物理层接口模块的布图和布局规划的设计和实 现过程,包括设计环境的建立,布图规划包括模块尺寸的确定,IO 单元、宏单元以及 特殊单元的摆放。由于布图规划中的电源规划环节较为重要, 影响芯片的布线资…...

EDA --软件开发之路

之前一直在一家做数据处理的公司,从事c开发,公司业务稳定,项目有忙有闲,时而看下c,数据库,linux相关书籍,后面跳槽到了家eda公司,开始了一段eda开发之路。 eda 是 electric design …...

51c~目标检测~合集2

我自己的原文哦~ https://blog.51cto.com/whaosoft/12377509 一、总结 这里概述了基于深度学习的目标检测器的最新发展。同时,还提供了目标检测任务的基准数据集和评估指标的简要概述,以及在识别任务中使用的一些高性能基础架构,其还涵盖了…...

计算机低能儿从0刷leetcode | 33.搜索旋转排列数组

题目:33. 搜索旋转排序数组 思路:看到时间复杂度要求是O(log N)很容易想到二分查找,普通的二分查找我们已经掌握,本题中的数组可以看作由两个分别升序的数组拼成,在完全升序的部分中进行二分查找是容易的,…...

SpringBoot+VUE2完成WebSocket聊天(数据入库)

下载依赖 <!-- websocket --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><!-- MybatisPlus --><dependency><groupId>com.ba…...

理解 CSS 中的绝对定位与 Flex 布局混用

理解 CSS 中的绝对定位与 Flex 布局混用 在现代网页设计中&#xff0c;CSS 布局技术如 flex 和绝对定位被广泛使用。然而&#xff0c;这两者结合使用时&#xff0c;可能会导致一些意想不到的布局问题。本文将探讨如何正确使用绝对定位元素&#xff0c;避免它们受到 flex 布局的…...

Redis 事务 问题

前言 相关系列 《Redis & 目录》《Redis & 事务 & 源码》《Redis & 事务 & 总结》《Redis & 事务 & 问题》 参考文献 《Redis事务详解》 Redis事务是什么&#xff1f; 标准的事务是指执行时具备原子性/一致性/隔离性/持久性的一系列操作。…...

Cpp学习手册-进阶学习

C标准库和C20新特性 C标准库概览&#xff1a; 核心库组件介绍&#xff1a; 容器&#xff1a; C 标准库提供了多种容器&#xff0c;它们各有特点&#xff0c;适用于不同的应用场景。 std::vector&#xff1a; vector&#xff1a;动态数组&#xff0c;支持快速随机访问。 #in…...

代码随想录-字符串-反转字符串中的单词

题目 题解 法一:纯粹为了做出本题&#xff0c;暴力解 没有技巧全是感情 class Solution {public String reverseWords(String s) {//首先去除首尾空格s s.trim();String[] strs s.split("\\s");StringBuilder sb new StringBuilder();//定义一个公共的字符反转…...

勒索软件通过易受攻击的 Cyber​​Panel 实例攻击网络托管服务器

一个威胁行为者&#xff08;或可能多个&#xff09;使用 PSAUX 和其他勒索软件攻击了大约 22,000 个易受攻击的 Cyber​​Panel 实例以及运行该实例的服务器上的加密文件。 PSAUX 赎金记录&#xff08;来源&#xff1a;LeakIX&#xff09; Cyber​​Panel 漏洞 Cyber​​Pane…...

Open WebUI + openai API / vllm API ,实战部署教程

介绍Open WebUI + Ollama 的使用: https://www.dong-blog.fun/post/1796 介绍vllm 的使用:https://www.dong-blog.fun/post/1781 介绍 Ollama 的使用: https://www.dong-blog.fun/post/1797 本篇博客玩个花的,Open WebUI 本身可以兼容openai 的api, 那来尝试一下。 仅供…...

InsuranceclaimsController

目录 1、 InsuranceclaimsController 1.1、 保险理赔结算 1.2、 生成预约单号 1.3、 保存索赔表 InsuranceclaimsController using QXQPS.Models; using QXQPS.Vo; using System; using System.Collections; using System.Collections.Generic; using System.Li…...

如何成为开源代码库Dify的contributor:解决issue并提交PR

前言 Dify 是一个开源的大语言模型&#xff08;LLM&#xff09;应用开发平台&#xff0c;它融合了后端即服务&#xff08;Backend as Service&#xff09;和LLMOps的理念&#xff0c;旨在简化和加速生成式AI应用的创建和部署。Dify提供了一个用户友好的界面和一系列强大的工具…...

SQL进阶技巧:巧用异或运算解决经典换座位问题

目录 0 问题描述 1 数据准备 2 问题分析 2.1 什么是异或 2.2异或有什么特性? 2.3 异或应用 2.4 本问题采用异或SQL解决方案 3 小结 0 问题描述 表 seat中有2个字段id和student id 是该表的主键(唯一值)列,student表示学生姓名。 该表的每一行都表示学生的姓名和 ID。…...

【MySQL】 运维篇—数据库监控:使用MySQL内置工具(如SHOW命令、INFORMATION_SCHEMA)进行监控

随着应用程序的增长&#xff0c;数据库的性能和稳定性变得至关重要。监控数据库的状态和性能可以帮助数据库管理员&#xff08;DBA&#xff09;及时发现问题&#xff0c;进行故障排查&#xff0c;并优化数据库的运行效率。通过监控工具&#xff0c;DBA可以获取实时的性能指标、…...

【温酒笔记】DMA

参考文档&#xff1a;野火STM32F103 1. Direct Memory Access-直接内存访问 DMA控制器独立于内核 是一个单独的外设 DMA1有7个通道DMA2有5个通道DMA有四个等级&#xff0c;非常高&#xff0c;高&#xff0c;中&#xff0c;低四个优先级如果优先等级相同&#xff0c;通道编号越…...

力扣判断字符是否唯一(位运算)

文章目录 给一个数n,判断它的二进制位中第x位是0还是1(从0开始计数)将一个数n的二进制位第X位修改为1(从0开始计数)将一个数n的二进制第x位修改为0(从0开始计数)提取一个数n二进制中最右侧的1去掉一个数n二进制表示中最右侧的1 今天我们通过判断字符是否唯一这个题来了解位运算…...

GPU和CPU区别?为什么挖矿、大模型都用GPU?

GPU(图形处理单元)和CPU(中央处理单元)是计算机中两种不同类型的处理器&#xff0c;它们在设计和功能上有很大的区别。 CPU是计算机的大脑&#xff0c;专门用于执行各种通用任务&#xff0c;如操作系统管理、数据处理、多任务处理等。它的架构设计旨在适应多种任务&#xff0c…...

生成xcframework

打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式&#xff0c;可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

SkyWalking 10.2.0 SWCK 配置过程

SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外&#xff0c;K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案&#xff0c;全安装在K8S群集中。 具体可参…...

简易版抽奖活动的设计技术方案

1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

3-11单元格区域边界定位(End属性)学习笔记

返回一个Range 对象&#xff0c;只读。该对象代表包含源区域的区域上端下端左端右端的最后一个单元格。等同于按键 End 向上键(End(xlUp))、End向下键(End(xlDown))、End向左键(End(xlToLeft)End向右键(End(xlToRight)) 注意&#xff1a;它移动的位置必须是相连的有内容的单元格…...

蓝桥杯3498 01串的熵

问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798&#xff0c; 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

AI病理诊断七剑下天山,医疗未来触手可及

一、病理诊断困局&#xff1a;刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断"&#xff0c;医生需通过显微镜观察组织切片&#xff0c;在细胞迷宫中捕捉癌变信号。某省病理质控报告显示&#xff0c;基层医院误诊率达12%-15%&#xff0c;专家会诊…...

QT3D学习笔记——圆台、圆锥

类名作用Qt3DWindow3D渲染窗口容器QEntity场景中的实体&#xff08;对象或容器&#xff09;QCamera控制观察视角QPointLight点光源QConeMesh圆锥几何网格QTransform控制实体的位置/旋转/缩放QPhongMaterialPhong光照材质&#xff08;定义颜色、反光等&#xff09;QFirstPersonC…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

C# 表达式和运算符(求值顺序)

求值顺序 表达式可以由许多嵌套的子表达式构成。子表达式的求值顺序可以使表达式的最终值发生 变化。 例如&#xff0c;已知表达式3*52&#xff0c;依照子表达式的求值顺序&#xff0c;有两种可能的结果&#xff0c;如图9-3所示。 如果乘法先执行&#xff0c;结果是17。如果5…...