Initial commit: C++ courseware

This commit is contained in:
xiaolinquan committed 2026-11-05 22:51:02 +08:00
commit 4a4ee7c287
1789 files changed
+373489

No files matched your search

+538
View File
@@ -0,0 +1,538 @@
*.in
*.out
Generate.exe
# ---> C++
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
# ---> JetBrains
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
# AWS User-specific
.idea/**/aws.xml
# Generated files
.idea/**/contentModel.xml
# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml
# Gradle
.idea/**/gradle.xml
.idea/**/libraries
# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn. Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/jarRepositories.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr
# CMake
cmake-build-*/
# Mongo Explorer plugin
.idea/**/mongoSettings.xml
# File-based project format
*.iws
# IntelliJ
out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Cursive Clojure plugin
.idea/replstate.xml
# SonarLint plugin
.idea/sonarlint/
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
# Editor-based Rest Client
.idea/httpRequests
# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser
# ---> VisualStudioCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# ---> VisualStudio
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
[Ll]ogs/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
*.vbp
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
*.code-workspace
# Local History for Visual Studio Code
.history/
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
# JetBrains Rider
*.sln.iml
2.Course/5.C++_Exam/Generate Data/Answer.cpp
2.Course/5.C++_Exam/Generate Data/Answer.cpp
2.Course/5.C++_Exam/Generate Data/Answer.cpp
/2.Course/5.C++_Exam/Generate Data
@@ -0,0 +1,61 @@
### **2024 CCF 非专业级别软件能力认证第一轮 (CSP-J1) 参考答案**
#### **一、单项选择题**
1. **C**
2. **A** (原式为 $(12_{10} - 10_{10}) * 13_{10} - 13_{10} = 2 * 13 - 13 = 13$)
3. **B** (A选2人,BC各1人: C(4,2)×C(3,1)×C(3,1)=54; B选2人,AC各1人: C(4,1)×C(3,2)×C(3,1)=36; C选2人,AB各1人: C(4,1)×C(3,1)×C(3,2)=36。总计54+36+36=126)
4. **D** (格雷码的特性是相邻两个编码只有一位不同)
5. **D** (1MB = 1024 * 1024 Bytes = 1024 * 1024 * 8 bits = 8388608 bits)
6. **C** (`struct` 是构造数据类型,不是基本数据类型)
7. **D** (`repeat-until` 是 Pascal 等语言的语法)
8. **B** (字符'a'的ASCII码是97,97 + 13 = 110,110对应的字符是'n')
9. **B** (2⁹ = 512, 2¹⁰ = 1024。所以最多比较10次)
10. **A** (Notepad是记事本应用软件)
11. **B** (图论基本定理:每条边连接两个顶点,为总度数贡献2)
12. **A** (通过前序和中序遍历结果可以唯一确定一棵二叉树,从而得到其后序遍历)
13. **D** (栈的特性是后进先出,可逐项模拟验证,D项无法实现)
14. **A** (捆绑法:将3个女生视为一个整体,与5个男生进行全排列,有 `P(6,6)` 种方法。3个女生内部也有 `P(3,3)` 种排列。总数为 `6! * 3! = 720 * 6 = 4320`)
15. **B**
---
#### **二、阅读程序**
**(1)**
16. **√** (10以内的质数是2, 3, 5, 7,共4个,和为17)
17. **×** (判断逻辑依然正确,20以内的质数有8个,输出不会变为6)
18. **√**
19. **B** (1到50的质数之和为328)
20. **A** (修改后,任何大于1的数 `p` 在循环到 `i=p` 时都会因 `p % p == 0` 而被误判为非质数,导致结果错误)
**(2)**
21. **√** (模拟DP过程:dp[1]=10, dp[2]=min(10,0)+15=15, dp[3]=min(15,10)+20=30。返回min(dp[3], dp[2])即min(30,15)=15)
22. **×** (不会产生编译错误,但可能会因访问数组负数下标而产生运行时错误)
23. **×** (程序计算的是到达楼顶的最小花费路径,不一定是单个最小元素)
24. **A** (模拟DP过程,最终返回min(dp[10], dp[9]) = min(6, 104) = 6)
25. **B** (模拟DP过程,最终返回min(dp[7], dp[6]) = min(45, 30) = 30)
26. **A** (修改后模拟计算:dp[1]=5, dp[2]=dp[1]+cost[0]=5+5=10, dp[3]=dp[2]+cost[1]=10+10=20。返回min(20,10)=10)
**(3)**
27. **×** (`customFunction(2, 3)` 返回 8。题目问的是函数的返回值,而不是最终输出。最终输出 `pow(8, 2)` 是64)
28. **√** (b为负数,`b-1` 永远不会等于0,导致无限递归)
29. **√** (b越大,递归深度越大,运行时间越长)
30. **B** (该函数计算 `a * (b + 1)`,即 `5 * (4 + 1) = 25`)
31. **C** (`customFunction(3, 3)` 返回 `3 * (3 + 1) = 12`。程序最终输出 `pow(12, 2)` 即 144)
32. **D** (修改后递归计算:f(3,3) = 3+f(2,2) = 3+2+f(1,1) = 3+2+1+f(0,0) = 3+2+1+0=6。最终输出`pow(6,2)`即36)
---
#### **三、完善程序**
**(1) 判断平方数**
33. **A** (从1开始检查)
34. **B** (循环的上界是原数的平方根,没有必要超过它)
35. **C** (判断`i`的平方是否等于`num`)
36. **C** (如果找到,说明是平方数,返回`true`)
37. **D** (如果循环结束都没找到,说明不是平方数,返回`false`)
**(2) 汉诺塔问题**
38. **B** (递归的边界条件:当只剩一个盘子时,直接移动)
39. **B** (对应 `move(src, tgt)`)
40. **B** (第一步:将 `i-1` 个盘子从 `src` 经由 `tgt` 移到 `tmp`)
41. **B** (第三步的盘子数量为 `i-1`)
42. **B** (第三步:将 `i-1` 个盘子从 `tmp` 经由 `src` 移到 `tgt`)
@@ -0,0 +1,299 @@
<center><font size="6"><b>2024 CCF 非专业级别软件能力认证第一轮</b></font></center>
<center><font size="4"><b>(CSP-J1) 入门级 C++语言试题</b></font></center>
<center><font size="4"><b>认证时间:2024 年 9 月 16 日 09:30~11:30</b></font></center>
**考生注意事项:**
- 试题纸共有 10 页,答题纸共有 1 页,满分 100 分。请在答题纸上作答,写在试题纸上的一律无效。
- 不得使用任何电子设备(如计算器、手机、电子词典等)或查阅任何书籍资料。
---
#### **一、 单项选择题(共 15 题, 每题 2 分, 共计 30 分; 每题有且有一个正确选项)**
( ) 1. 32位 `int` 类型的存储范围是
A. -2147483647 ~ +2147483647
B. -2147483647 ~ +2147483648
C. -2147483648 ~ +2147483647
D. -2147483648 ~ +2147483648
( ) 2. 计算 $(14_8 - 1010_2) * D_{16} - 1101_2$ 的结果,并选择答案的十进制值
A. 13 B. 14 C. 15 D. 16
( ) 3. 某公司有 10 名员工,分为 3 个部门:A 部门有 4 名员工,B 部门有 3 名员工,C部门有 3 名员工。现需要从这 10 名员工中选出 4 名组成一个工作组,且每个部门至少要有 1 人。问有多少种选择方式?
A. 120 B. 126 C. 132 D. 238
( ) 4. 以下哪个序列对应数组0至8的4位二进制格雷码(Gray code)
A. 0000, 0001, 0011, 0010, 0110, 0111, 0101, 1000
B. 0000, 0001, 0011, 0010, 0110, 0111, 0100, 0101
C. 0000, 0001, 0011, 0010, 0100, 0101, 0111, 0110
D. 0000, 0001, 0011, 0010, 0110, 0111, 0101, 0100
( ) 5. 记 1KB 为 1024 字节(byte),1MB 为 1024KB,那么 1MB 是多少二进制位(bit)?
A. 1000000 B. 1048576 C. 8000000 D. 8388608
( ) 6. 以下哪个不是C++中的基本数据类型?
A. int B. float C. struct D. char
( ) 7. 以下哪个不是C++中的循环语句?
A. for B. while C. do-while D. repeat-until
( ) 8. 在C/C++中,`(char)('a' + 13)` 与下面的哪一个值相等
A. 'm' B. 'n' C. 'z' D. '3'
( ) 9. 假设有序表中有1000个元素,则用二分法查找元素最多需要比较\_\_\_\_次
A. 25 B. 10 C. 7 D. 1
( ) 10. 下面哪一个不是操作系统名字
A. Notepad B. Linux C. Windows D. macOS
( ) 11. 在无向图中,所有顶点的度数之和等于
A. 图的边数 B. 图的边数的两倍 C. 图的顶点数 D. 图的顶点数的两倍
( ) 12. 已知二叉树的前序遍历为 `[A,B,D,E,C,F,G]`,中序遍历为 `[D,B,E,A,F,C,G]`,求二叉树的后序遍历的结果是
A. `[D,E,B,F,G,C,A]` B. `[D,E,B,F,G,A,C]` C. `[D,B,E,F,G,C,A]` D. `[D,E,B,F,G,A,C]`
( ) 13. 给定一个空栈,支持入栈和出栈操作。若入栈操作的元素依次是1,2,3,4,5,6,其中1最先入栈,6最后入栈,下面哪种出栈顺序是不可能的
A. 6 5 4 3 2 1 B. 1 6 5 4 3 2 C. 2 4 6 5 3 1 D. 1 3 5 2 4 6
( ) 14. 有5个男生和3个女生站成一排,规定3个女生必须相邻,问有多少种不同的排列方式?
A. 4320 种 B. 5040 种 C. 3600 种 D. 2880 种
( ) 15. 编译器的主要作用是什么?
A. 直接执行源代码 B. 将源代码转换为机器代码
C. 进行代码调试 D. 管理程序运行时的内存
---
#### **二、阅读程序 (程序输入不超过数组或字符串定义的范围,判断题正确填 √,错误填 ×; 除特殊说明外,判断题 1.5 分,选择题 3 分,共计 40 分)**
**(1)**
```cpp
1 #include <iostream>
2 using namespace std;
3
4 bool isPrime(int n) {
5 if (n <= 1) {
6 return false;
7 }
8 for (int i = 2; i * i <= n; i++) {
9 if (n % i == 0) {
10 return false;
11 }
12 }
13 return true;
14 }
15
16 int countPrimes(int n) {
17 int count = 0;
18 for (int i = 2; i <= n; i++) {
19 if (isPrime(i)) {
20 count++;
21 }
22 }
23 return count;
24 }
25
26 int sumPrimes(int n) {
27 int sum = 0;
28 for (int i = 2; i <= n; i++) {
29 if (isPrime(i)) {
30 sum += i;
31 }
32 }
33 return sum;
34 }
35
36 int main() {
37 int x;
38 cin >> x;
39 cout << countPrimes(x) << " " << sumPrimes(x) << endl;
40 return 0;
41 }
```
**判断题**
( ) 16. 当输入为“10”时,程序的第一个输出为“4”,第二个输出为“17”。
( ) 17. 若将 `isPrime()` 函数中的条件改为 `i <= n/2`,输入“20”时,`countPrimes(20)` 的输出将变为“6”。
( ) 18. `sumPrimes` 函数计算的是从2到n之间的所有素数之和。
**单选题**
( ) 19. 当输入为“50”时,`sumPrimes(50)` 的输出为
A. 1060 B. 328 C. 381 D. 275
( ) 20. 如果将 `for(int i = 2; i * i <= n; i++)` 改为 `for(int i = 2; i <= n; i++)`,输入“10”时,程序的输出
A. 将不能正确计算10以内素数个数及其和
B. 仍然输出“4”和“17”
C. 输出“3”和“10”
D. 输出结果不变,但运行时间更短
**(2)**
```cpp
1 #include <iostream>
2 #include <vector>
3 using namespace std;
4
5 int compute(vector<int> &cost) {
6 int n = cost.size();
7 vector<int> dp(n + 1, 0);
8 dp[1] = cost[0];
9
10 for (int i = 2; i <= n; i++) {
11 dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i - 1];
12 }
13 return min(dp[n], dp[n - 1]);
14 }
15
16 int main() {
17 int n;
18 cin >> n;
19 vector<int> cost(n);
20 for (int i = 0; i < n; i++) {
21 cin >> cost[i];
22 }
23 cout << compute(cost) << endl;
24 return 0;
25 }
```
**判断题**
( ) 21. 当输入的 `cost` 数组为 `{10, 15, 20}` 时,程序的输出为15。
( ) 22. 如果将 `dp[i-1]` 改为 `dp[i-3]`,程序可能会产生编译错误。
( ) 23. (2分) 程序总是输出 `cost` 数组中的最小的元素。
**单选题**
( ) 24. 当输入的 `cost` 数组为 `{1, 100, 1, 1, 1, 100, 1, 1, 100, 1}` 时,程序的输出为
A. 6 B. 7 C. 8 D. 9
( ) 25. (4分) 如果输入的 `cost` 数组为 `{10, 15, 30, 5, 5, 10, 20}`,程序的输出为
A. 25 B. 30 C. 35 D. 40
( ) 26. 若将代码中的 `min(dp[i - 1], dp[i - 2]) + cost[i - 1]` 修改为 `dp[i - 1] + cost[i - 2]`,输入 `cost` 数组为 `{5, 10, 15}` 时,程序的输出为
A. 10 B. 15 C. 20 D. 25
**(3)**
```cpp
1 #include <iostream>
2 #include <cmath>
3 using namespace std;
4
5 int customFunction(int a, int b) {
6 if (b == 0) {
7 return a;
8 }
9 return a + customFunction(a, b - 1);
10 }
11
12 int main() {
13 int x, y;
14 cin >> x >> y;
15 int result = customFunction(x, y);
16 cout << pow(result, 2) << endl;
17 return 0;
18 }
```
**判断题**
( ) 27. 当输入为“2 3”时,`customFunction(2, 3)` 的返回值为“64”。
( ) 28. 当 `b` 为负数时,`customFunction(a, b)` 会陷入无限递归。
( ) 29. 当 `b` 的值越大,程序的运行时间越长。
**单选题**
( ) 30. 当输入为“5 4”时,`customFunction(5, 4)` 的返回值为
A. 5 B. 25 C. 250 D. 625
( ) 31. 如果输入 `x=3` 和 `y=3`,则程序的最终输出为
A. 27 B. 81 C. 144 D. 256
( ) 32. (4分) 若将 `customFunction` 函数改为 `return a + customFunction(a - 1, b - 1);` 并输入“3 3”,则程序的最终输出为
A. 9 B. 16 C. 25 D. 36
---
#### **三、完善程序 (程序输入不超过数组或字符串定义的范围; 每题 3 分,共计 30 分)**
**(1) (判断平方数)问题:** 给定一个正整数n,判断这个数是不是完全平方数,即存在一个正整数x,使得x的平方等于n。试补全程序。
```cpp
#include <iostream>
#include <cmath>
using namespace std;
bool isSquare(int num) {
int i = __(1)__;
int bound = __(2)__;
for (; i <= bound; ++i) {
if (__(3)__) {
return __(4)__;
}
}
return __(5)__;
}
int main() {
int n;
cin >> n;
if (isSquare(n)) {
cout << n << " is a Square number" << endl;
} else {
cout << n << " is not a Square number" << endl;
}
}
```
( ) 33. (1)处应填
A. 1 B. 2 C. 3 D. 4
( ) 34. (2)处应填
A. `(int)floor(sqrt(num) - 1)`
B. `(int)floor(sqrt(num))`
C. `floor(sqrt(num / 2)) - 1`
D. `floor(sqrt(num / 2))`
( ) 35. (3)处应填
A. `num == 2 * i` B. `num == 2 + i`
C. `num == i * i` D. `num == i`
( ) 36. (4)处应填
A. `num == 2 + i` B. `num == 2 * i` C. `true` D. `false`
( ) 37. (5)处应填
A. `num == i * i` B. `num != 2 * i` C. `true` D. `false`
**(2) (汉诺塔问题)** 给定三根柱子,分别标记为A、B和C。初始状态下,柱子A上有若干个圆盘,这些圆盘从上到下按从小到大的顺序排列。任务是将这些圆盘全部移到柱子C上,且必须保持原有顺序不变。在移动过程中,需要遵守以下规则:
1. 只能从一根柱子的顶部取出圆盘,并将其放入另一根柱子的顶部。
2. 每次只能移动一个圆盘。
3. 小圆盘必须始终在大圆盘之上。
试补全程序。
```cpp
#include <iostream>
using namespace std;
void move(char src, char tgt) {
cout << "从柱子" << src << "挪到柱子" << tgt << endl;
}
void dfs(int i, char src, char tmp, char tgt) {
if (i == __(1)__) {
move(__(2)__);
return;
}
dfs(i - 1, __(3)__);
move(src, tgt);
dfs(__(4)__, __(5)__);
}
int main() {
int n;
cin >> n;
dfs(n, 'A', 'B', 'C');
}
```
( ) 38. (1)处应填
A. 0 B. 1 C. 2 D. 3
( ) 39. (2)处应填
A. `src, tmp` B. `src, tgt` C. `tmp, tgt` D. `tgt, tmp`
( ) 40. (3)处应填
A. `src, tmp, tgt` B. `src, tgt, tmp` C. `tgt, tmp, src` D. `tgt, src, tmp`
( ) 41. (4)处应填
A. `i` B. `i - 1` C. `tmp` D. `src`
( ) 42. (5)处应填
A. `src, tmp, tgt` B. `tmp, src, tgt` C. `src, tgt, tmp` D. `tgt, src, tmp`
@@ -0,0 +1 @@
前缀和与差分
@@ -0,0 +1 @@
讲解CSPJ的例题,主要以基础题为主
@@ -0,0 +1,980 @@
# CSP-J 第二轮 核心知识讲义
## 一、要考试的范围有哪些
C++ 基础知识,基本的算法:枚举、模拟、排序、贪心、前缀和、差分、二分、搜索和动态规划、高精度运算,数据结构:栈、队列、树与常用数学知识。
## 二、C++ 基础知识
### 1. 数据类型与溢出
常见竞赛环境中的 32 位 `int` 最大值约为 21 亿,64 位 `long long` 最大值约为 $9.22\times10^{18}$。输入能装下,不代表计算结果也能装下。例如两个数都不超过十亿,各自能放进 `int`,相乘却可能达到 $10^{18}$。
```cpp
int a = 1000000000, b = 1000000000;
long long product = 1LL * a * b; // 或者(long long)a * b
```
这里的 `1LL` 是 `long long` 类型的整数 1,让右侧乘法从一开始就按 `long long` 计算。如果只写 `long long product = a * b;`,右侧可能先按 `int` 运算而溢出,再把错误结果赋给左侧变量。左侧类型再大,也无法补回已经丢失的结果。
累加时也要这样估计。十万个数,每个最多十亿,总和可达 $10^{14}$,因此答案和保存中间结果的前缀和数组,都需要足够大的类型。
整数类型要防溢出,`double` 则要留意近似误差。计数、循环次数,以及能换成整数单位的金额,尽量用整数处理。比较两个近似值时,可以用 `if(abs(a - b) < eps)` 判断它们是否足够接近。`eps` 是允许误差,例如 `1e-5`,具体取多大要看题目要求和数值范围。
### 2. 整数除法
两个整数相除,结果会向 0 截断,例如 `5 / 2` 得到 2。需要小数时,让其中一个数先变成浮点数:
```cpp
double x = 1 / 2; // 结果为 0
double y = 1.0 / 2; // 结果为 0.5
```
对非负整数 a 和正整数 b,向上取整常用下面两种方式:
```cpp
long long x = (a + b - 1) / b; // 整数方式,保证加法不溢出
long long y = ceil(1.0 * a / b); // 浮点方式,适用于数值和精度允许时
```
`ceil` 会把结果向上取整,a、b 都是整数时,要先乘 `1.0` 让除法产生小数;直接写 `ceil(a / b)`,里面已经完成了整数除法。如果 a 或 b 本来就是浮点数,才可以直接写 `ceil(a / b)`。
### 3. 条件与运算符
比较两个值时,用 `==` 判断相等,`!=` 判断不相等,大小比较使用 `<`、`<=`、`>`、`>=`。其中最容易写错的是等号:`=` 用来赋值,判断相等要写两个等号。
多个条件可以用 `&&` 表示“都成立”,用 `||` 表示“至少一个成立”,用 `!` 表示“取反”。例如判断 x 是否在闭区间内,要写 `l <= x && x <= r`,不能写成连续比较 `l <= x <= r`。
访问数组时,把边界判断放在前面:
```cpp
if (pos >= 0 && pos < n && a[pos] == target) {
// pos 合法,并且找到了目标
}
```
这样写,`&&` 前面的条件一旦不成立,后面就不再计算。下标越界时,程序不会继续访问这个数组位置。
### 4. 数组与 vector
数组通过下标访问元素。声明 `int a[100] = {};`,就建立了 100 个初值为 0 的位置,合法下标为 0~99。采用 1~n 编号时,至少开 n+1 个位置。
需要随时添加或删除末尾元素时,可以使用长度能够调整的数组 `vector`。常见的建立方式如下:
```cpp
vector<int> a; // 空容器,还没有元素
vector<int> b(5); // 5 个元素,初值都是 0
vector<int> c(5, -1); // 5 个元素,初值都是 -1
```
这几种写法建立出的长度不同:`b` 有 5 个元素,可以访问下标 0~4;`a` 还是空的,要先添加元素,才能访问 `a[0]`。添加、删除和查询长度时,常用下面这些操作:
| 操作 | 写法 | 含义 |
| --- | --- | --- |
| 在末尾添加 | `v.push_back(x)` | 新增一个值为 x 的元素,长度增加 1 |
| 按下标访问 | `v[i]` | 读取或修改已有的第 i 项,下标从 0 开始 |
| 访问最后一个元素 | `v.back()` | 读取或修改最后一个元素,容器必须非空 |
| 删除最后一个元素 | `v.pop_back()` | 长度减少 1,不返回被删除的元素,容器必须非空 |
| 查询长度 | `v.size()` | 得到当前元素个数 |
| 判断是否为空 | `v.empty()` | 没有元素时返回真 |
| 删除全部元素 | `v.clear()` | 长度变成 0,原来的下标都不能再使用 |
例如从空容器开始,依次 `push_back(2)`、`push_back(4)`,内容就变成 `{2,4}`;再执行 `pop_back()`,就只剩 `{2}`。
读入 n 个数,可以先建立 n 个元素,再按下标读入:
```cpp
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];
```
读入后,就可以按下标逐个访问。循环中用 `size()` 取得当前长度:
```cpp
for (int i = 0; i < a.size(); i++) {
cout << a[i] << ' ';
}
```
只需要依次查看元素时,也可以写 `for (int x : a)`;要排序整个容器,就用 `sort(a.begin(), a.end())`。注意读入时要分清“放进已有位置”和“在末尾追加”:如果初始化为 `vector<int> a(n)` 代表着已经有 n 个元素,再用 `push_back` 添加 n 个数,长度就会变成 2n。
### 5. 字符和字符串
字符串同样可以按下标逐个处理,不过取出来的是字符。数字字符 `'7'` 与整数 `7` 不同,已知字符 `c` 是数字时,用 `c - '0'` 才能得到对应的数值。
```cpp
string s;
cin >> s;
int n = s.size();
for (int i = 0; i < n; i++) {
// 处理 s[i]
}
```
字符串有效字符的下标为 0~n−1。`cin >> s` 读取一个不含空白的字符串;要读取含空格的一整行,就用 `getline(cin, s)`。
#### 例:读入一个整数和下一行文字
第一行给出整数 n,第二行是可能含空格的文字。在这种数字后面紧接换行的情况,就需要在 `cin >> n` 后加一次 `cin.ignore()`,再读下一行:
```cpp
int n;
string s;
cin >> n;
cin.ignore();
getline(cin, s);
```
之所以多这一步,是因为 `cin >> n` 读走数字后,会留下行末换行。`getline` 直接遇到它就结束读取,于是得到空串;`cin.ignore()` 默认丢弃一个字符,在这里正好取走换行。数字后若还有多个空格,可改用 `cin.ignore(INT_MAX, '\n');` 丢弃本行剩余内容。连续用 `getline` 读多行时,上一次已经取走了换行,不用再额外调用 `ignore()`。
除了读入方式,还要分清楚字符串的比较规则:字符串的比较是按字典序比较,在比较时,`"10"` 会排在 `"2"` 前面,和整数的大小顺序不同。
### 6. 循环与函数
`break` 退出最近的一层循环;`continue` 跳过本轮剩下的操作。对于 `for`,执行 `continue` 后仍会执行第三部分的更新;对于 `while`,则直接回到条件判断,写在后面的更新语句可能被跳过。
`return` 结束当前函数。在 `main` 中执行 `return 0;` 会结束整个程序,在搜索函数里执行它则只是返回上一层调用。
如果函数里改过一个值,回到调用处却没变,可以先查传参方式。普通的值参数保存的是副本,通过引用参数才能直接修改对应的原变量。递归时也一样,每一层的局部变量各自独立,全局数组则由各层共用,修改后是否恢复要看它的用途。
自增自减运算放进一个表达式时,`i++` 代表的先用后加,`++i` 代表的先加后用。
### 7. 输入输出
使用 `cin`、`cout` 时,可以在正式读写前设置:
```cpp
ios::sync_with_stdio(false);
cin.tie(nullptr);
```
关闭同步后,读取数据的速率会大大加快,但是就要统一使用 `cin/cout` 来输入输出,避免与 `scanf`、`printf` 交错使用。一般换行用 `'\n'`,不必每次都用会刷新缓冲区的 `endl`。
对于复赛的题目,明确要求了要使用文件输入输出,就要按题面给出的文件名设置,例如:
```cpp
freopen("task.in", "r", stdin);
freopen("task.out", "w", stdout);
```
这里的 `task` 是示意名称,必须替换成题面要求的名称,并放在正式读写之前。
## 三、通过数据范围推测算法
语法能写对之后,还要判断算法能否在规定时间内跑完。看到 n 的上限,可以先按下面的范围筛选。这里按 C++、约 1~2 秒、循环体只做简单操作来粗估;接近上限时,仍要计算实际工作量。大量输出、长字符串比较和多组数据,都会增加耗时。
| 方法 | 大致工作量 | 通常可考虑的 n 范围 |
| --- | --- | --- |
| 枚举所有子集 | $2^n$ | 通常先考虑 $n\le20$;每个子集还要遍历 n 项时,应按 $n2^n$ 再估算 |
| 枚举所有排列 | $n!$ | 通常先考虑 $n\le10$;若每个方案都要逐项处理或输出,范围还需缩小 |
| 三层各枚举 n 次 | $n^3$ | 通常先考虑 $n\le200$,此时约八百万次简单操作 |
| 枚举所有数对 | $n^2$ | 通常先考虑 $n\le3000$;循环体很简单且时间允许时,可再评估到约 5000 |
| 排序 | $n\log n$ | 普通整数排序通常可考虑 $n\le10^6$ |
| 一次遍历、前缀和 | $n$ | 简单线性处理通常可考虑 $n\le10^7$,还要确认输入量与内存是否允许 |
例如 n 的上限是十万,枚举所有数对会达到百亿量级,就要想办法用排序、前缀和等方法减少重复计算。若 n 只有 20,直接枚举各种选择也可能跑得完,指数级搜索就值得考虑。
估算时别漏掉外层调用:一次判断扫描 n 个数,二分又调用多次,两部分的工作量就要相乘。内存也要一起算,二维数组的总元素个数是两维相乘,两边看着都不大,乘起来也可能占用很多空间。
## 四、枚举与模拟
### 1. 枚举
数据范围允许时,可以把可能的情况逐个检查,这就是枚举。写循环前,先确定枚举的对象和范围,再把题目的合法条件放进去。
#### 例:统计和为目标值的数对
给定 n 个整数和目标值 target,统计有多少对不同位置的元素之和等于 target。同一对位置只算一次,交换两个位置的顺序不产生新方案。
这道题枚举的是两个位置。让第二个位置始终在第一个位置之后,就能排除同一个位置,也避免交换顺序后重复计算:
```cpp
long long ans = 0;
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
if (a[i] + a[j] == target) ans++;
}
}
```
代码中让 `j` 从 `i+1` 开始,因此 `(i,j)` 和 `(j,i)` 只会统计其中一对。
题目说“两个数”时,要分清是在选两个位置,还是选两个不同数值。相同数值出现在不同位置,是否可以同时选,要由题意决定。
对于暂时想不到优化方法得题目,可以先写出小范围暴力。之后优化时,用它核对结果;比赛中也可能拿到对应数据范围的分数,具体得分要看测试点或子任务如何组织。
### 2. 模拟
模拟题直接按照题目规则执行即可,但要注意题目得操作要求/顺序,看看应该先判断还是先修改,改过的值会不会影响本轮后面的判断。例如装物品不能超重,就应先检查加入后的总重量,再决定是否加入。
如果题目规定“同时发生变化”,就要先保留旧状态,再统一更新。否则边扫描边修改时,后面的位置可能读到本轮刚改过的新值,执行出来就和题意不同了。
举个简单例子:在一排格子中,每一轮让中间各格的数变成左右相邻两格的数之和,两端保持不变,所有格子同时变化。例如原来的数是:`1 2 3 4`。
如果直接从左往右修改原数组:
```cpp
for (int i = 1; i < n - 1; i++) {
a[i] = a[i - 1] + a[i + 1];
}
```
第二格先变成了 4。接下来计算第三格时,读到的第二格已经是新值,于是算成 `4 + 4 = 8`,最终得到:`1 4 8 4`
这就不符合“同时变化”的要求了。正确做法是用另一个数组保存本轮结果,**始终从旧数组读取,向新数组写入**,这样,计算第三格时读取的仍是旧值 2,不会受到第二格新值的影响。
## 五、排序与贪心
### 1. 排序
当原有顺序不影响答案时,可以考虑先排序,让大小关系变得清楚。常用的升序写法如下:
```cpp
sort(a + 1, a + n + 1); // 排序 a[1] 到 a[n]
sort(v.begin(), v.end()); // 排序整个 vector
```
`sort` 的范围是左闭右开:包含第一个参数指向的位置,不包含第二个参数指向的位置。要排序下标 1~n,就把右端写成 `a+n+1`;少写这个 `+1`,最后一个元素就没有参与排序。
#### 例:按成绩和编号排序
给定若干学生的成绩与编号,要求按成绩从高到低排列;成绩相同的,编号较小者在前。那么就要自己书写比较函数:
```cpp
struct Student { int score, id; };
bool cmp(const Student& a, const Student& b) {
if (a.score != b.score) return a.score > b.score;
return a.id < b.id;
}
sort(v.begin(), v.end(), cmp);
```
这个比较函数先看成绩,成绩相同才看编号。比较时使用严格的先后关系,不能写成 `a.score >= b.score`;两个元素完全相同时,比较结果应为假。
整数的取值范围很小时,也可以先用计数数组记录出现次数,再按数值顺序输出。但是开数组前要看值域有多大,值太大就不适合直接作为数组下标。
### 2. 贪心算法
有些题排序之后,还需要决定按什么规则选择,这时可以考虑贪心。它在每一步作出选择,因此需要说明:这一步选完,为什么仍能得到整体最优的结果?
#### 例:安排尽可能多的活动
给定若干活动的开始、结束时间,选择尽可能多的互不冲突活动,求最多能选几个。本例允许一个活动结束时,下一个活动立刻开始,且活动时长为正。
先按结束时间从小到大排序,每次选择当前能接上的活动。
结束得越早,留给后面的时间越多。把最优方案中第一个活动换成结束最早的可选活动,后面的活动仍然接得上;之后对剩余活动继续这样选择,就有了这个策略的依据。
```cpp
int count = 0, last = 0; // 此处约定区间起点均不小于 0
for (int i = 1; i <= n; i++) {
if (seg[i].l >= last) {
count++;
last = seg[i].r;
}
}
```
## 六、前缀和与差分
### 1. 前缀和
同一个数组上反复求区间和,每次从头相加会做很多重复工作。可以先记下前面各项的总和:令 `s[i]` 表示前 i 个数的和,`s[0]=0`,逐项累加得到 $s[i]=s[i-1]+a[i]$。
查询闭区间时,去掉左端点之前的部分:$\operatorname{sum}(l,r)=s[r]-s[l-1]$。
```cpp
vector<long long> s(n + 1, 0);
for (int i = 1; i <= n; i++) s[i] = s[i - 1] + a[i];
// 查询闭区间 [l,r]:s[r] - s[l - 1]
```
例如数组为 2、−1、4、3,要查询第 2~4 项的和。前缀和依次为 0、2、1、5、8,因此区间 `[2,4]` 的和是 `8-2=6`。去掉的是第 l 项之前的部分,所以减 `l-1` 对应的前缀和。
预处理扫描一遍,之后每次查询只做一次减法。它适合数组不变、查询很多的情况;原数组修改后,旧前缀和也可能失效。
### 2. 差分
如果题目要反复给一段数反复执行区间修改得操作,那么可以记录相邻元素的差,把整段修改变成两个位置的修改。这里从下标 0 开始:定义 `d[0]=a[0]`,对于 `i>=1`,令 `d[i]=a[i]-a[i-1]`。把这些差从头累加,就能还原原数组。
给闭区间 `[l,r]` 全部加 x,只改两处:
```cpp
d[l] += x;
d[r + 1] -= x;
```
还原时,增量从 `l` 处开始生效,到 `r+1` 处被抵消,所以中间整段都会增加 x。等全部修改完成后,再统一求前缀和,得到最终数组:
```cpp
long long value = 0;
for (int i = 0; i < n; i++) {
value += d[i];
a[i] = value;
}
```
## 七、二分
### 1. 二分查找
给定升序数组和目标值 x,找到第一个大于或等于 x 的元素下标;如果不存在,就返回 `n+1`。
由于数组 `a[1..n]` 已经升序排列,小于 x 的元素都在前面,不小于 x 的都在后面,两部分之间只有一道分界。每次检查中间位置,就能确定接下来该往哪一半找。
```cpp
int l = 1, r = n, ans = n + 1;
while (l <= r) {
int mid = l + (r - l) / 2;
if (a[mid] >= x) {
ans = mid; // 先记下这个满足条件的位置
r = mid - 1; // 再去左边找更靠前的位置
} else {
l = mid + 1;
}
}
// ans==n+1 表示不存在,否则 ans 就是所求位置
```
当 `mid` 满足条件,先把它记进 ans,再去左半边找更靠前的位置;不满足时,就去右半边。ans 始终留着已经找到的结果,后面只有找到更早的位置时才替换它。
例如数组为 `2 4 4 7`,查找第一个不小于 4 的位置,最终得到 ans=2。要判断 x 是否确实存在,可以检查 `ans <= n && a[ans] == x`,先确认下标合法,再读取元素。
这里使用闭区间 `[l,r]`,循环条件是 `l <= r`。检查过 mid 后,用 `mid-1` 或 `mid+1` 缩小范围,不再重复检查这个位置。
### 2. 二分答案
二分也可以用来找最优答案。先尝试一个值,判断它能否满足要求;如果可行与不可行之间只有一道分界,就可以不断缩小答案范围。
#### 例:分段的最大段和
给定一个包含 n 个正整数的序列,以及一个正整数 k,其中 $1\le k\le n$。请在不改变元素顺序的前提下,将整个序列分成不超过 k 个非空连续段。每个元素必须恰好属于其中一段,不能遗漏或重复使用。
对于一种分段方案,先分别计算每一段的元素之和,再取这些段和中的最大值,作为该方案的结果。请找到一种分段方案,使这个最大值尽可能小,并输出它的最小值。例如,序列为 `7 2 5 10 8`,k=2。若分成 `7 2 5` 和 `10 8` 两段,段和分别为 14、18,最大段和为 18。其他合法方案都无法让最大段和小于 18,因此答案为 18。
那么就可以假设允许的最大段和是 `limit`,可以从左到右尽量往当前段加入元素,装不下就新开一段。如果这样需要的段数不超过 k,就说明这个上限可行。
分析题目发现,如果上限越大,限制越宽松:某个值已经可行,更大的值也可行。存在着单调性,因此可以二分“第一个可行值”。
确定了单调性,再找搜索范围。一个元素不能拆开,所以答案至少是最大元素;把整串作为一段,则得到总和这个上界。代码中用 `a[1..n]` 保存正整数,约定 `1<=k<=n`,相关变量已准备好。
先写判断函数,看看上限 limit 是否能让段数不超过 k:
```cpp
bool check(long long limit) {
int count = 1;
long long sum = 0;
for (int i = 1; i <= n; i++) {
if (a[i] > limit) return false;
if (sum + a[i] > limit) {
count++;
sum = a[i];
} else {
sum += a[i];
}
}
return count <= k;
}
```
再记录已经找到的可行答案,继续尝试更小的上限:
```cpp
long long l = 0, r = 0;
for (int i = 1; i <= n; i++) {
l = max(l, (long long)a[i]);
r += a[i];
}
long long ans = r;
while (l <= r) {
long long mid = l + (r - l) / 2;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
// ans 是最大段和能达到的最小值
```
`check(mid)` 为真,说明 mid 能用,先存下来,再向左找;为假,说明上限太小,就向右找。
对于题目出现需要求“最大的最小”的时候,就可以确认单调性,再决定是否通过二分去解决。
## 八、栈、队列与搜索
### 1. 栈
栈只在一端放入、取出元素,这一端叫栈顶。像一摞盘子,通常先取走最上面、也就是最后放上去的那个。
依次压入 2、5、7,此时栈顶是 7;弹出一次后,栈顶变成 5,再弹出才轮到 2。这就是“后进先出”。
| 操作 | `stack<int> st` 的写法 | 要记住的事 |
| --- | --- | --- |
| 压入元素 | `st.push(x)` | x 成为新栈顶 |
| 读取栈顶 | `st.top()` | 只读取,不删除 |
| 弹出栈顶 | `st.pop()` | 只删除,不返回元素 |
| 判断是否为空 | `st.empty()` | 空栈不能读取或弹出 |
| 元素个数 | `st.size()` | 表示当前还留在栈中的数量 |
需要取出并保存栈顶时,先读再删:
```cpp
if (!st.empty()) {
int x = st.top();
st.pop();
// 使用 x
}
```
#### 例:判断括号是否匹配
给定一个只包含 `(`、`)`、`[`、`]` 的字符串,判断括号能否按正确的类型和嵌套顺序全部配对。
右括号要先和最近一个尚未配对的左括号匹配,这正好符合栈的顺序。遇到左括号就入栈,遇到右括号就检查栈顶。
例如 `([])`:先压入 `(`,再压入 `[`;遇到 `]` 时,配对并弹出 `[`;遇到 `)` 时,再弹出 `(`。处理完后栈为空,匹配成功。
`([)]` 则在读到 `)` 时出错:栈顶是 `[`,类型对不上,也不能越过它去找更下面的 `(`。
```cpp
bool matched(const string& s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[') {
st.push(c);
} else {
if (st.empty()) return false;
char t = st.top();
if ((c == ')' && t != '(') ||
(c == ']' && t != '[')) return false;
st.pop();
}
}
return st.empty();
}
```
除了中途检查空栈和括号类型,结束时还要确认栈为空。比如 `(()` 中途没有冲突,却留下了一个左括号,所以函数最后还要判断一次是否配完。
撤销最近一次操作、处理嵌套结构时,也常会用到栈。递归调用有类似的返回顺序:后调用的那一层先执行完,再回到调用它的位置。
### 2. 队列
队列在队尾加入元素,在队首取出元素。依次加入 2、5、7,处理顺序就是 2、5、7,这叫“先进先出”。
| 操作 | `queue<int> q` 的写法 | 要记住的事 |
| --- | --- | --- |
| 入队 | `q.push(x)` | 加在队尾 |
| 读取队首 | `q.front()` | 取得下一个待处理元素 |
| 读取队尾 | `q.back()` | 取得最后加入的元素 |
| 出队 | `q.pop()` | 删除队首,不返回元素 |
| 判断是否为空 | `q.empty()` | 读队首、队尾或出队前确认非空 |
| 元素个数 | `q.size()` | 当前队列长度 |
#### 例:循环报数
编号 1~n 的人围成一圈,从 1 开始报数,报到 k 的人出队,再从下一个人重新报 1。要求输出所有人的出队顺序,其中 n、k 均为正整数。
可以把围成一圈的报数过程放进队列:没数到 k 的人先移到队尾,报到 k 的人输出后删除。比如 1、2、3、4、5 每次数到第 3 人出队,就先移动前两个人,再删除当前队首。
```cpp
queue<int> q;
for (int i = 1; i <= n; i++) q.push(i);
while (!q.empty()) {
for (int i = 1; i < k; i++) {
int x = q.front();
q.pop();
q.push(x);
}
cout << q.front() << ' ';
q.pop();
}
```
`n=5`、`k=3` 时,出队顺序为 `3 1 5 2 4`。每轮只移动 k−1 次,紧接着的队首才是第 k 人,多移动一次就会错位。
这份写法的操作次数约为 $nk$,适合规模允许时使用。k 很大时,要留意反复移到队尾的开销。
按到达顺序办理任务,也可以用队列。BFS 就借助队列,让早发现、距离近的状态先被处理。
### 3. DFS 与回溯
如果每一步都有几种选择,可以沿着一种选择继续尝试,走到完成方案或无法继续时,再返回尝试其他选择,这就是 DFS 的过程。分支层数随输入变化时,用递归比写很多层固定循环更方便。
回溯中最容易漏掉的是恢复现场:如果这一步修改了后续分支共用的路径或标记,返回后要记得撤销修改。
#### 例一:选数凑目标和
给定 n 个整数和目标和 target,每个位置最多选一次,统计选出的数之和等于 target 的方案数。按位置区分选法,允许空集。
例如三个数为 2、3、5,目标和为 5,有两种选法:选 2 和 3,或者只选 5。
处理一个位置时,只有“选”和“不选”两个分支。可以进行搜索,令 `dfs(pos, sum)` 表示前面的位置已经决定完,现在处理第 pos 个数,当前和为 sum。
```cpp
int n;
long long target, ans = 0;
vector<long long> a; // 使用 a[1..n]
void dfs(int pos, long long sum) {
if (pos == n + 1) {
if (sum == target) ans++;
return;
}
sum += a[pos]; // 选当前位置
dfs(pos + 1, sum);
sum -= a[pos]; // 恢复本层的和,再尝试不选
dfs(pos + 1, sum);
}
// 读入数据、令 ans=0 后,调用 dfs(1, 0)
```
等每个位置都决定完,再检查和是否等于目标,这样一组选择只会统计一次。若题目要求至少选一个数,还要记录是否已经选择过元素。
代码先把当前数加进本层的 sum,试完“选”的分支后再减回来,接着尝试“不选”。下一层按值接收参数,对它自己局部变量的修改不会传回本层。另一种写法是直接传入 `sum + a[pos]`,这样本层的和没有改变,就省去了加减操作。
如果还要记录具体选了哪些数,可以用数组或 `vector` 保存路径:选时加入,递归回来后删掉。n 个位置各有两种选择,总方案数为 $2^n$,只能用于较小规模。所有数非负时,和超过目标后可以停止该分支;有负数时,后面可能把和减回来,不能这样剪枝。
#### 例二:选数组合
从整数 1~n 中选出 k 个不同的数,输出所有组合,每个组合只输出一次。这里约定 `1≤k≤n`,组合内部按递增顺序输出。
例如从 1、2、3、4 中选两个数,`1 3` 和 `3 1` 是同一种组合。让选出的数字始终递增,就只会生成一次。
```cpp
int n, k;
vector<int> path;
void choose(int start) {
if (path.size() == k) {
for (int x : path) cout << x << ' ';
cout << '\n';
return;
}
for (int x = start; x <= n; x++) {
path.push_back(x);
choose(x + 1);
path.pop_back();
}
}
// 约定 1<=k<=n,初始路径为空,调用 choose(1)
```
代码用 `start` 限定下一次可选的最小值。选了 2,下一层就从 3 开始,既不会重复选 2,也就不会回头产生顺序颠倒的组合,因此不用额外记录哪些数已经用过。
与前一个例子的局部和不同,`path` 是各层递归时共用的,所以它是一个全局变量,那么 加入后尝试深入 和 回溯后撤销 的操作就要配套:`push_back` 记录选择,返回后用 `pop_back` 撤销。比如试完 `1 2`,退回到路径只含 1 的状态,才能接着试 `1 3`。
#### 例三:迷宫路径计数
给定一个网格、起点和终点,只能上下左右移动,不能经过障碍,也不能在同一条路径中重复经过格子。求满足这些条件的路径总数。
因为每一步都可以尝试向上、下、左、右移动,因此可以用递归搜索:先决定当前这一层处理所在的格子,然后递归到下一层处理准备走到的相邻格子。如果到达终点,就说明找到了一条完整路径,将答案加 1,并结束这条分支,不再从终点继续走。不过尝试一个方向之前,要先检查下一格是否在网格范围内、是否为障碍,以及是否已经在当前路径中经过。只有这三个检查都通过,才能继续递归。
且为了避免同一条路径重复经过格子,进入一个格子时,要把它标记为“已经经过”。例如从起点向右走了一格,这一格就加入了当前路径;接下来继续搜索时,不能再走回它。
当这个格子后面的所有走法都尝试完,就要返回上一层,并取消它的访问标记。因为题目限制的是“同一条路径不能重复经过格子”,其他路径仍然可以经过这里。如果返回时不取消标记,后续路径就可能被错误地拦住,导致少算。
因此,整个搜索过程可以理解为:**进入格子并标记,尝试四个方向,搜索结束后取消标记,再返回上一层。** 每次返回,都恢复到进入这一格之前的状态,方便继续尝试其他走法,这个过程就叫作回溯。
下面约定行列从 0 开始,`grid` 是全局字符串数组,`'#'` 表示障碍;`vis` 大小为 n×m,初始全为 0,起终点均合法且可通行:
```cpp
int n, m, tx, ty;
long long ways = 0;
vector<string> grid;
vector<vector<int>> vis;
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
void walk(int x, int y) {
if (x == tx && y == ty) {
ways++;
return;
}
vis[x][y] = 1;
for (int d = 0; d < 4; d++) {
int nx = x + dx[d], ny = y + dy[d];
if (nx < 0 || nx >= n || ny < 0 || ny >= m) continue;
if (grid[nx][ny] == '#' || vis[nx][ny]) continue;
walk(nx, ny);
}
vis[x][y] = 0;
}
// ways=0,vis 清零后,调用 walk(sx, sy)
```
如果把最后的恢复语句删掉,某条路径走过的格子会挡住其他路径,统计就可能漏掉。相反,在同一条路径还没结束时就取消标记,又会允许绕圈重复进入。
### 4. BFS
BFS 改用队列安排访问顺序,先处理距离起点近的状态,再处理更远的状态。它适合无权图或每一步代价相同的最少步数问题;当边权不同时,就不能直接沿用这种最短路判断。
一层里的点处理完,才轮到下一层,所以第一次发现一个点时,就已经找到了到达它的最少边数。
#### 例:求无权图中的最少步数
给定一个无权图和起点 s,求从 s 到各点最少经过多少条边;不可达的点记为 −1。
通过邻接表 `g[u]` 保存 u 的邻居,`dist` 长度为 `n+1` 且全部初始化为 `-1`:
```cpp
queue<int> q;
dist[s] = 0;
q.push(s);
while (!q.empty()) {
int u = q.front();
q.pop();
for (int v : g[u]) {
if (dist[v] != -1) continue;
dist[v] = dist[u] + 1;
q.push(v);
}
}
```
代码在入队时设置距离,同时完成访问标记,因此同一个点不会被反复加入队列。起点距离为 0,不可达的点则一直保持 −1,两者含义不同。
对于二维网格中的 BFS 也是同样的过程,只是一个状态改成行、列。扩展邻格时,需要依次检查:坐标合法、不是障碍、尚未访问。
### 6. 图的存储与连通块
前面的 BFS 通过邻居列表查找下一步能到哪里。存图时,邻接表只记录实际存在的边,通常适合边较少的图;邻接矩阵则直接记录两点是否相连,查边方便,但空间约为点数的平方。
无向边 `u-v` 用邻接表存储时,通常要加入两个方向。遍历无向图时必须防止沿边反复返回,使用访问标记,或在确实为树的条件下跳过父节点。
统计连通块时,从每个尚未访问的点开始一次 DFS 或 BFS,每次新启动的遍历对应一个新连通块。网格中的连通区域也可以这样做,这就是常见的泛洪填充。
## 九、动态规划
### 1. 一维 DP
#### 例:爬台阶
有 n 级台阶,每次可以上 1 级或 2 级,求恰好走到第 n 级的不同走法数。步子的先后顺序不同,算不同走法。
令 `dp[i]` 表示恰好到达第 i 级的方法数。
考虑最后一步:要么从第 i−1 级走 1 级,要么从第 i−2 级走 2 级。两种走法不会重复,加起来就是 $dp[i]=dp[i-1]+dp[i-2]$。
起点设为 `dp[0]=1`,表示“还没走”这一种起始方案,另外有 `dp[1]=1`。接着从小到大计算,每次用到的前两项都已经算好。
走法数增长很快,要估计结果范围。可以先考虑 `long long`,但它也有上限;题目要求取模时,应在递推过程中及时取模,而不能只对结果取模。
### 2. 最长上升子序列
给定一个数组,可以删去一些元素,但保留剩余元素的相对顺序,求严格上升子序列的最大长度。
这次把终点固定在第 i 个数:令 `dp[i]` 表示“以第 i 个数结尾的最长严格上升子序列长度”。它前面可以接上一个位置更早、数值更小的元素,因此逐个尝试前面的 j。初值设为 1,表示只选当前这个数。
```cpp
int ans = 0;
for (int i = 1; i <= n; i++) {
dp[i] = 1;
for (int j = 1; j < i; j++) {
if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
}
ans = max(ans, dp[i]);
}
```
最长的那一段子序列可能在任何位置结束,所以答案要取所有 `dp[i]` 的最大值,不能只看 `dp[n]`。严格上升用 `<`,允许相等的不下降版本才使用 `<=`。
子序列可以跳过元素,连续子数组则不能。读题时先分清这两个要求。上面写法的工作量为平方级,使用前要看 n 的范围。
### 3. 0/1 背包
给定容量 V 和 n 件物品,第 i 件的体积为 `w[i]`、价值为 `v[i]`。每件最多选一次,求总体积不超过 V 时的最大总价值,允许一件也不选。
令 `dp[j]` 表示已考虑的物品中,总体积不超过 j 时的最大价值。
初始全为 0,表示还没选物品。处理当前物品时,不拿就保留原来的最大价值;拿一次,就从扣除当前体积后的状态转移过来,再加上它的价值:
```cpp
vector<long long> dp(V + 1, 0);
for (int i = 1; i <= n; i++) { // 考虑前 i 件物品
for (int j = V; j >= w[i]; j--) { // 考虑前 i 件物品的情况下,背包容量为 j 时能获得的最大价值
dp[j] = max(dp[j], dp[j - w[i]] + v[i]); // 对于第 i 件物品,要么拿,要么不拿取最大值
}
}
```
这里的容量从大到小更新。计算 `dp[j]` 时,较小容量的 `dp[j-w[i]]` 还没被本件物品更新,用的仍是上一轮结果(考虑前 i - 1 件物品的情况下,背包容量为 j 时能获得的最大价值),因此每件物品只会选一次。
如果从小到大更新,刚刚拿过本件物品的状态就可能再次参与转移,相当于又拿了一次。这正是下一种背包允许的情况。
答案为 `dp[V]`。
### 4. 完全背包
给定容量 V 和 n 种物品,每种体积为正、数量不限,求总体积不超过 V 时的最大总价值。
每种物品既然可以重复选择,就允许继续使用本轮已经更新过的状态(考虑前 i 件物品的情况下,背包容量为 j 时能获得的最大价值),而这个已经被更新过的状态就有可能已经拿了第 i 件物品了,符合可以重复拿取的定义,那么沿用上一节的状态定义,把容量改为正序枚举即可:
```cpp
for (int i = 1; i <= n; i++) {
for (int j = w[i]; j <= V; j++) {
dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
}
}
```
记不清循环方向时,可以思考依赖的来源状态:它必须来自上一轮,还是允许本轮重复使用?
## 十、数学与位运算
### 1. 最大公约数与最小公倍数
求两个非负整数的最大公约数,可以反复用“除数、余数”替换当前两个数,直到余数为 0。这样做不会改变最大公约数。
```cpp
long long gcd(long long a, long long b) { // a、b 非负
while (b != 0) {
long long r = a % b;
a = b;
b = r;
}
return a;
}
```
求出最大公约数后,还可以用它计算正整数 a、b 的最小公倍数 lcm:
$$\operatorname{lcm}(a,b)=\frac{a}{\gcd(a,b)}\times b$$
### 2. 质数判断与因数分解
判断一个整数 n 是否为质数,先排除小于 2 的数,再尝试 2 到平方根之间的除数。只要找到一个能整除它的数,就可以确定它是合数:
```cpp
bool isPrime(int n) {
if (n < 2) return false;
for (int d = 2; 1LL * d * d <= n; d++) {
if (n % d == 0) return false;
}
return true;
}
```
循环条件保留等号,才能检查到 9、25 这样的完全平方数的平方根。需要判断很多个数时,可以考虑筛法,减少反复试除的工作。
同样的试除思路也能用来分解质因数:找到一个因数后,反复除尽,并记录除过几次。剩余数变小,试除范围也随之缩小;结束时若还剩一个大于 1 的数,它本身就是一个质因数。
### 3. 位运算
位运算处理的是整数的二进制位。先把数位对齐,再对同一位置上的 0、1 分别计算。例如 5 写成 `101`,3 写成 `011`,就可以按下面的规则逐位运算。
| 运算 | 每一位的规则 | 例子 |
| --- | --- | --- |
| 按位与 `&` | 两位都是 1,结果才是 1 | `5 & 3` 得到 `001`,即 1 |
| 按位或 `|` | 两位至少有一个 1,结果就是 1 | `5 | 3` 得到 `111`,即 7 |
| 按位异或 `^` | 两位不同得 1,相同得 0 | `5 ^ 3` 得到 `110`,即 6 |
| 按位取反 `~` | 每一位的 0、1 互换 | 对类型的全部位取反,不只处理写出来的几位 |
| 左移 `<<`、右移 `>>` | 把二进制位向左或向右移动 | 对非负整数,`5 << 1` 为 10,`5 >> 1` 为 2 |
左移时低位补 0;非负整数右移时高位补 0,移出的低位丢弃。移位次数必须在类型允许范围内,左移后的值也要能表示。
这些规则可以用来检查某一位。`x & 1` 只看最低位,结果为 1 就是奇数;`(x >> k) & 1` 先把第 k 位移到最低位,再取出它,最低位编号为 0。异或还满足两个常用性质:相同数异或为 0,一个数与 0 异或仍是它自己。
`^` 表示异或,不是乘方。若只有一个数出现奇数次,其余数都出现偶数次,把所有数异或起来,相同的数会两两抵消,最后就剩下那个数。
## 十一、高精度运算
整数超过 `long long` 的范围时,可以用字符串保存,按照手算竖式的规则逐位计算。下面的输入均为非负整数,减法结果可以为负数。
### 1. 数位存储
按平常写数的顺序保存,例如 5079 存成字符串“5079”。计算前去掉多余前导零,全为零时保留一个 0。
加、减、乘法可以先反转字符串,让下标 0 对应个位、下标 1 对应十位,算完后再反转结果。除法从最高位开始,不需要反转。
字符串中存的是字符,参与计算时要转成对应数字,算出的数位再转回字符保存。
### 2. 高精度加法
要求计算两个非负大整数的和。
1. 反转两个字符串,在较短串末尾补零,使对应数位对齐。
2. 从个位开始,将两个对应数位与上一位的进位相加。
3. 总和除以 10 的余数作为本位,整数商作为下一位的进位。
4. 所有数位算完后,若仍有进位,就追加到结果末尾,最后反转结果。
例如 $999+1$,每一位都会产生进位,原有数位算完后还要补上最高位的 1,得到 1000。
### 3. 高精度减法
要求计算两个非负大整数的差,结果可以为负数。
1. 先比较大小:去掉前导零后,位数多的更大;位数相同,从最高位开始比较第一个不同的数位。
2. 若被减数较小,交换两数并记录负号,之后用较大的数减较小的数。
3. 反转两个字符串,给较短串末尾补零。从个位开始,计算对应数位的差,再扣除上一位的借位。
4. 本位不够减时加 10,并向更高位借 1;下一轮计算要扣掉这次借位。
5. 去掉倒序结果末尾的多余零,至少保留一位,再反转结果,按需添加负号。
例如 $1000-1=999$,要连续借位;$1-1000=-999$,先算差的绝对值,再加负号。两数相等时结果为 0。
### 4. 高精度乘法
要求计算两个非负大整数的积,采用“先累加乘积,再统一进位”的方法。
1. 任意一个数为 0,结果直接为 0。否则反转两个字符串,让下标 0 对应个位。
2. n 位数乘 m 位数,积最多 n+m 位,准备这么多个位置的整数数组,初值全为 0。
3. 每一对数位分别相乘,第 i 位与第 j 位的乘积累加到第 i+j 位,暂不进位。同一位置会收到多个乘积,累加值可以大于 9。
4. 从低位到高位统一进位:当前累加值加上上一位的进位,除以 10 的余数作为本位,整数商继续向高位传递。
5. 处理完剩余进位,去掉倒序结果末尾的多余零,再反转结果。
例如 $23\times47$,个位先累加 $3\times7=21$,十位累加 $3\times4+2\times7=26$,百位累加 $2\times4=8$。统一进位后,个位留 1、进 2,十位得到 28、留 8、进 2,百位得到 10、留 0、进 1,最终得到 1081。
两组数位分别遍历,时间复杂度为 $O(nm)$。
### 5. 高精度除以小整数
要求用非负大整数除以正整数 b,求整数商和余数。大整数用字符串保存,b 能放进 `int`。
手算除法时,先看前面的数够除几次,再留下余数、落下下一位。因此字符串也从最高位开始,向右逐位计算。
1. 余数初始为 0,从左到右依次读取数位。
2. 把上一轮余数乘 10,再加上当前数位,组成这一轮待除的数。
3. 用它除以 b,整数商作为当前一位商,余数留给下一轮。
4. 全部数位处理完后,只去掉商开头多余的零,中间的零必须保留。最后留下的余数就是最终余数。
例如计算 1234 除以 12:
| 读入数位 | 上轮余数乘 10,再加当前位 | 本位商 | 新余数 |
| --- | --- | --- | --- |
| 1 | $0\times10+1=1$ | 0 | 1 |
| 2 | $1\times10+2=12$ | 1 | 0 |
| 3 | $0\times10+3=3$ | 0 | 3 |
| 4 | $3\times10+4=34$ | 2 | 10 |
依次得到 0102,去掉开头多余的零,商为 102,余数为 10。商十位上的 0 不能省略,结果满足 $1234=12\times102+10$。
被除数小于 b 时,商为 0,余数为被除数;被除数为 0 时,商和余数均为 0。余数乘 10 的中间值可能超出 `int`,要用 `long long` 保存。整个过程扫描一次字符串,时间复杂度为 $O(n)$。
## 十二、树
### 1. 树的基本概念
树是连通且没有环的无向图。n 个节点的树有 n−1 条边,任意两个不同节点之间只有一条不重复经过节点的路径。
给树选定一个根后,就可以从根往下确定父子关系。下图以 1 为根:
<img src="C:\Users\xiaolinquan\AppData\Roaming\Typora\typora-user-images\image-20261016220317516.png" alt="image-20261016220317516" style="zoom:40%;" />
| 名称 | 怎样理解 | 图中的例子 |
| --- | --- | --- |
| 根节点 | 选定的起点,没有父节点 | 1 |
| 父节点、子节点 | 相邻两点中,靠近根的是父,远离根的是子 | 2 是 4、5 的父节点 |
| 兄弟节点 | 具有同一个父节点 | 4 和 5 |
| 叶节点 | 有根树中没有子节点的节点 | 4、5、6、7 |
| 子树 | 一个节点和它的全部后代构成的树 | 以 2 为根的子树含 2、4、5 |
| 深度 | 从根到该节点经过的边数,根深度为 0 | 2 的深度为 1,4 的深度为 2 |
例如,节点 5 的深度为 2,以 2 为根的子树包含 2、4、5。这里根的深度为 0;有些题目把根所在层记为第 1 层,要注意区分。
### 2. 树的几种存储方式
树可以用编号和数组、邻接表或动态节点保存。本节重点掌握动态创建节点的方法,前两种方式了解基本形式即可。
#### 用编号和数组保存二叉树
二叉树的每个节点最多有两个孩子,按编号保存时,可以用结构体数组记录左右孩子:
```cpp
struct TreeNode {
int left;
int right;
};
TreeNode tree[8] = {}; // 下标 1~7 对应节点,初值全为 0
tree[1].left = 2; tree[1].right = 3;
tree[2].left = 4; tree[2].right = 5;
tree[3].left = 6; tree[3].right = 7;
```
数组下标表示节点编号,`left`、`right` 保存孩子编号,0 表示没有孩子。例如 `tree[2].right` 的值为 5,表示节点 2 的右孩子是 5。
#### 用邻接表保存相邻关系
普通树可以用邻接表保存相邻节点。`neighbors[node]` 保存与 node 直接相连的节点。无向边要在两端各保存一次:
```cpp
vector<vector<int>> neighbors(n + 1);
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
neighbors[u].push_back(v);
neighbors[v].push_back(u);
}
```
### 3. 动态创建节点
动态存储时,用结构体保存节点的值和两个孩子的地址,需要新节点时用 `new` 创建,再连接到树上。
以二叉树为例,一个节点可以这样定义:
```cpp
struct Node {
int value;
Node* left;
Node* right;
};
```
`value` 保存节点的值,`left`、`right` 指向左右孩子,`nullptr` 表示没有孩子。创建根节点:
```cpp
Node* root = new Node{1, nullptr, nullptr};
```
`new` 返回新节点的地址,`root` 保存这个地址。创建并连接左孩子:
```cpp
Node* child = new Node{2, nullptr, nullptr};
root->left = child;
```
`->` 用来通过指针访问成员。也可以直接写成 `root->left = new Node{2, nullptr, nullptr};`。
#### 例一:动态建立一棵二叉树
按上面的二叉树图创建 7 个节点,并输出节点 5 的值:
```cpp
Node* root = new Node{1, nullptr, nullptr};
root->left = new Node{2, nullptr, nullptr}; root->right = new Node{3, nullptr, nullptr};
root->left->left = new Node{4, nullptr, nullptr};
root->left->right = new Node{5, nullptr, nullptr};
root->right->left = new Node{6, nullptr, nullptr};
root->right->right = new Node{7, nullptr, nullptr};
cout << root->left->right->value; // 输出 5
```
`root->left->right->value` 表示根的左孩子的右孩子的值,也就是节点 5 的值。但是访问成员前,要保证前面的指针不是 `nullptr`。
### 4. 二叉树的遍历
常见的二叉树遍历如下:
| 遍历方式 | 处理顺序 |
| --- | --- |
| 前序 | 根、左子树、右子树 |
| 中序 | 左子树、根、右子树 |
| 后序 | 左子树、右子树、根 |
#### 例二:遍历动态建立的二叉树
对例一建立的二叉树,前序序列为 `1 2 4 5 3 6 7`。前序遍历如下:
```cpp
void preOrder(Node* node) {
if (node == nullptr) return;
cout << node->value << ' ';
preOrder(node->left);
preOrder(node->right);
}
// 建好树后调用 preOrder(root),输出 1 2 4 5 3 6 7
```
中序序列为 `4 2 5 1 6 3 7`,把输出语句放在两次递归之间;后序序列为 `4 5 2 6 7 3 1`,把输出语句放在两次递归之后。空指针要先判断。
层序遍历使用队列,输出顺序为 `1 2 3 4 5 6 7`。
@@ -0,0 +1 @@
CSPJ2026题目的讲解
@@ -0,0 +1 @@
讲解类与对象
@@ -0,0 +1,393 @@
# 第一章 C++ 程序设计基础
## 本章内容
- 1.1 程序设计的目标和流程
- 1.2 简单数学运算
- 1.3 变量与常量
## 1.1 程序设计的目标和流程
### 核心知识
程序设计的目标,是把现实问题中的处理步骤转化为计算机能够准确执行的指令。一个程序通常要完成三件事:
1. **接收数据**:获得题目给出的输入。
2. **处理数据**:按照公式或规则计算。
3. **给出结果**:严格按要求输出答案。
C++ 属于高级语言。我们编写的源代码不能直接由计算机执行,必须先经过编译。完整流程可以概括为:
```text
分析问题 → 设计算法 → 编写源代码 → 编译与链接 → 运行程序 → 测试结果
```
“编译成功”只表示代码符合语法规则并成功生成程序,不表示计算结果一定正确。
### 关键语法与规则
最小的 C++ 程序结构:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
```
各部分作用:
| 代码 | 作用 |
| --- | --- |
| `#include <iostream>` | 引入标准输入输出流库,使程序可以使用 `cin`、`cout` |
| `using namespace std;` | 允许直接使用标准命名空间 `std` 中的工具 |
| `int main()` | 主函数,也是程序的执行入口 |
| `{ ... }` | 标出一个代码块的范围,左右花括号必须配对 |
| `cout << ...;` | 向控制台输出数据 |
| `return 0;` | 结束 `main`,向系统报告程序正常结束 |
书写代码时还要注意:
- C++ 区分大小写,`cout` 与 `Cout` 不是同一个名称。
- 代码中的标点应使用英文半角符号。
- 大多数语句以英文分号 `;` 结束。
- 一个可独立运行的程序必须有且只有一个 `main` 函数。
- `#include` 是预处理指令,末尾不写分号。
注释供程序员阅读,编译器会忽略注释内容:
```cpp
// 单行注释:从 // 开始到本行末尾
/*
块注释:可以跨越多行
适合说明一段代码的整体功能
*/
```
### 例题
**题目:** 输出两行文字,第一行输出 `C++ Review`,第二行输出 `Ready!`。
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "C++ Review" << endl;
cout << "Ready!" << endl;
return 0;
}
```
**说明:** 程序从 `main` 开始执行。`cout` 负责输出,`endl` 负责换行,每条输出语句都以分号结束。
### 常见错误
- 把 `cout` 使用的 `<<` 写成 `>>`。
- 使用中文分号 `;`、中文引号 `“ ”` 或中文括号。
- 漏写分号,或遗漏 `}`,使括号无法配对。
- 变量名或工具名拼写错误。
- 在 OJ 题目中擅自输出题目没有要求的内容。
- 编译器标出的行不一定是真正写错的位置。遇到括号、分号问题时,应同时向前检查。
## 1.2 简单数学运算
### 核心知识
C++ 的基本算术运算符包括:
| 运算符 | 作用 | 示例 | 结果 |
| --- | --- | --- | --- |
| `+` | 加法 | `5 + 2` | `7` |
| `-` | 减法 | `5 - 2` | `3` |
| `*` | 乘法 | `5 * 2` | `10` |
| `/` | 除法 | `7 / 2` | `3` |
| `%` | 求余 | `7 % 2` | `1` |
两个整数相除时执行**整数除法**,小数部分直接舍去,不进行四舍五入。只要参与除法的一方是浮点数,才会得到浮点结果。
### 关键语法与规则
```cpp
cout << 7 / 2; // 3:整数除法
cout << 7.0 / 2; // 3.5:浮点除法
cout << 14 % 4; // 2:求余
cout << (2 + 3) * 4; // 20:括号优先
```
常见优先级从高到低:
1. 括号 `()`
2. 乘、除、取余 `* / %`
3. 加、减 `+ -`
`cout` 中双引号会影响输出含义:
```cpp
cout << "2 + 4"; // 输出文本:2 + 4
cout << 2 + 4; // 输出计算结果:6
```
`%` 和整数除法经常配合使用:
```cpp
int n = 479;
int ones = n % 10; // 9:提取最右边一位
n = n / 10; // 47:去掉最右边一位
```
可以把 `% 10` 记作“个位提取器”,把 `/ 10` 记作“个位削减器”。
部分数学函数需要引入头文件 `<cmath>`:
```cpp
#include <cmath>
double x = sqrt(81); // 计算根号 81,结果为 9
double y = pow(2, 5); // 计算 2 的 5 次方,结果为 32
```
### 例题
**题目:** 14 个苹果平均分给 4 名同学,输出每人分到的数量和剩余数量,中间用一个空格分隔。
```cpp
#include <iostream>
using namespace std;
int main() {
cout << 14 / 4 << " " << 14 % 4;
return 0;
}
```
输出:
```text
3 2
```
**说明:** `14 / 4` 得到整数商 3,`14 % 4` 得到余数 2。空格属于文本,因此写成 `" "`。
### 常见错误
- 误以为 `7 / 2` 会得到 `3.5`。需要写成 `7.0 / 2` 或 `7 / 2.0`。
- 对 `double` 使用 `%`。初学阶段把 `%` 理解为整数求余。
- 把 `2 ^ 3` 当作 2 的 3 次方。C++ 中 `^` 不是乘方运算。
- 忘记括号,导致计算顺序与题意不同。
- 把算式放入双引号,程序只会原样输出算式文本。
- 输出多个答案时漏掉题目要求的空格或换行。
## 1.3 变量与常量
### 核心知识
变量用于保存输入数据、计算结果和程序运行过程中的中间状态。变量可以理解为内存中带有名称的数据容器。
使用变量通常包含三个动作:
1. **定义**:说明变量的类型和名称。
2. **初始化或赋值**:把数据保存到变量中。
3. **读取或更新**:使用变量中的值,或用新值覆盖旧值。
`=` 是赋值运算符。程序会先计算等号右边,再把结果保存到左边变量中。它与数学中的“相等”含义不同。
常量用于保存程序运行期间不允许改变的数据。常量使用 `const` 定义,并且必须在定义时初始化。
### 关键语法与规则
变量定义、初始化与更新:
```cpp
int a = 10; // 定义并初始化
int b, c; // 同时定义两个 int 变量
b = 3; // 给 b 赋值
a = a + 5; // 先计算右边,再把 15 赋给 a
a += 2; // 等价于 a = a + 2
```
常见的复合赋值(将运算和赋值合并,一种简便写法):
| 写法 | 等价写法 |
| --- | --- |
| `a += b;` | `a = a + b;` |
| `a -= b;` | `a = a - b;` |
| `a *= b;` | `a = a * b;` |
| `a /= b;` | `a = a / b;` |
| `a %= b;` | `a = a % b;` |
变量命名规则:
- 只能由字母、数字和下划线组成。
- 不能以数字开头。
- 不能使用 `int`、`return`、`if` 等 C++ 关键字。
- 同一作用域内不能重复定义同名变量。
- 建议变量名见名知意,例如 `score`、`totalMinutes`、`radius`。
常量定义:
```cpp
const double PI = 3.14159;
// PI = 3.14; // 错误:常量不能再次赋值
```
### 典型例题
**题目:** 输入圆的半径,输出圆的周长和面积。
```cpp
#include <iostream>
using namespace std;
int main() {
const double PI = 3.14159;
double r;
cin >> r;
double length = 2 * PI * r;
double area = PI * r * r;
cout << length << " " << area;
return 0;
}
```
**说明:** 半径和计算结果可能含小数,因此使用 `double`。圆周率在程序运行期间不应改变,因此定义为常量。
**变量覆盖示例:猴子吃桃倒推。**
```cpp
int peach = 1; // 第四天剩 1 个
peach = (peach + 1) * 2; // 倒推第三天
peach = (peach + 1) * 2; // 倒推第二天
peach = (peach + 1) * 2; // 倒推第一天
cout << peach; // 22
```
每次赋值都先使用 `peach` 的旧值完成右侧计算,再用结果覆盖旧值。
### 常见错误
- 变量没有定义就使用。
- 变量未初始化就参与计算,可能得到不可预测的值。
- 把 `=` 当作判断相等。赋值写 `a = 5;`,判断相等写 `a == 5`。
- 写 `a + 1;` 却以为 `a` 会自动变化。更新变量应写 `a = a + 1;`、`a += 1;` 或 `a++;`。
- 常量没有初始化,或在定义后再次赋值。
- 变量名以数字开头、包含中文或使用了关键字。
## 本章练习
### 一、基础知识
1. C++ 程序从哪个地方开始执行?
<br><br>
2. `#include <iostream>` 的作用是什么?它的末尾是否需要分号?
<br><br>
3. 单行注释和块注释分别怎样书写?
<br><br>
4. 分别写出加、减、乘、除、求余对应的运算符。
<br><br>
5. 计算下列表达式的结果:
- `13 / 5`
- `13 % 5`
- `13.0 / 5`
- `(2 + 3) * 4`
6. 判断下列变量名是否合法,并说明原因:
- `score1`
- `1score`
- `total_score`
- `double`
- `studentAge`
7. `=` 在 C++ 中表示什么?`a = a + 1;` 应怎样理解?
<br><br>
8. 为什么 `const double PI = 3.14159;` 必须在定义时给出初始值?
<br><br>
### 二、代码阅读
1. 写出程序的输出结果:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "3 + 4" << " " << 3 + 4;
return 0;
}
```
<br><br><br>
2. 写出程序的输出结果:
```cpp
int a = 5;
a = a + 3;
a *= 2;
cout << a;
```
<br><br><br>
3. 找出下面代码中的错误并改正:
```cpp
#include <iostream>;
using namespace std;
int Main {
cout >> "Hello";
return 0
}
```
<br><br>
4. 写出 `ones` 和 `n` 的最终值:
```cpp
int n = 628;
int ones = n % 10;
n = n / 10;
```
<br><br>
### 三、程序设计
1. **两数运算**
输入两个整数 `a` 和 `b`,依次输出它们的和、差、积,三个结果之间用一个空格分隔。
- 样例输入:`8 3`
- 样例输出:`11 5 24`
2. **平均分苹果**
输入苹果总数 `m` 和学生人数 `n`,输出每人分到的苹果数以及剩余苹果数,中间用一个空格分隔。题目保证 `n > 0`。
- 样例输入:`17 5`
- 样例输出:`3 2`
3. **圆的周长与面积**
输入圆的半径 `r`,使用常量 `PI = 3.14159`,输出圆的周长和面积,中间用一个空格分隔。
@@ -0,0 +1,504 @@
# 第二章 数据类型与顺序结构程序设计
## 本章内容
- 2.1 变量的数据类型
- 2.2 变量的输入与输出
- 2.3 顺序结构程序设计案例
- 2.4 提交评测与错误自查
## 2.1 变量的数据类型
### 核心知识
数据类型决定变量能够保存什么数据、占用多少存储空间,以及参与运算时采用什么规则。基础课程中常用的数据类型包括整数、浮点数、字符和布尔值。
| 分类 | 类型 | 主要用途 | 示例 |
| --- | --- | --- | --- |
| 整数 | `short` | 较小范围整数(-128~127) | `short level = 10;` |
| 整数 | `int` | 中范围整数,**最常用**(-21亿~21亿) | `int age = 15;` |
| 整数 | `long long` | 大范围整数(非常大,万兆以上) | `long long total = 80000000;` |
| 浮点数 | `float` | 单精度小数 | `float rate = 0.5;` |
| 浮点数 | `double` | 双精度小数,**小数最常用** | `double price = 12.5;` |
| 字符 | `char` | 单个字符 | `char grade = 'A';` |
| 布尔值 | `bool` | 真或假 | `bool passed = true;` |
选择类型时要同时考虑数据范围和计算精度。题目可能出现小数时,一般使用 `double`;整数范围可能很大时,需要考虑 `long long`。
### `char`、转义字符与 ASCII
`char` 用来保存单个字符,字符值必须使用单引号:
```cpp
char letter = 'A';
char digit = '7'; // 数字字符,不是整数 7
char op = '*';
char blank = ' ';
```
字符串使用双引号。`'A'` 是一个字符,`"A"` 是一个字符串,两者不能混用。
字符在内存中以整数编码保存。基础题通常使用 ASCII 规律处理英文字母和数字字符:
| 字符范围 | ASCII 值 |
| --- | --- |
| `'0'`~`'9'` | 48~57 |
| `'A'`~`'Z'` | 65~90 |
| `'a'`~`'z'` | 97~122 |
常用转换:
```cpp
char c = '8';
int number = c - '0'; // 数字字符转整数,得到 8
char upper = 'B';
char lower = upper + ('a' - 'A'); // 大写字母转小写字母,得到 'b'
int code = 65;
char letter = (char)code; // 强制转换后得到字符 'A'
char x = 'A';
int ascii = (int)x; // 强制转换后得到整数 65
```
转义序列以反斜杠 `\` 开头:
| 写法 | 含义 |
| --- | --- |
| `\n` | 换行 |
| `\t` | 水平制表符(Tab键, 一般代表的 4 个空格) |
| `\\` | 一个反斜杠 |
| `\"` | 一个双引号 |
| `\'` | 一个单引号 |
示例:
```cpp
cout << "小明说:\"what is \\n\"" << '\n';
```
输出:
```text
小明说:"what is \n"
```
### 类型转换
当表达式中出现不同数据类型时,编译器可能进行自动类型转换。一般而言,整数与浮点数混合运算时,整数会转换为浮点类型:
```cpp
int a = 5;
double b = 2.0;
double result = a / b; // 2.5
```
把 `double` 转为 `int` 会丢弃小数部分:
```cpp
double value = 3.9;
int n = (int)value; // 3,不进行四舍五入
```
如果要转换整个表达式,应把表达式完整括起来:
```cpp
char result = (char)(a + b);
```
### 典型例题
**题目:** 输入一个大写英文字母,输出对应的小写字母。
```cpp
int main() {
char upper;
cin >> upper;
char lower = (upper - 'A') + 'a'; // 也可通过加减 32 来实现, 大写转小写需要加 32
cout << lower;
return 0;
}
```
**说明:** ASCII 中大写字母和小写字母各自连续排列,而对应大小写字母的差值则一定相同。
### 常见错误
- 把单个字符写成 `"A"`。`char` 值应使用单引号 `'A'`。
- 混淆整数 `7` 与字符 `'7'`。`'7' - '0'` 才得到整数 7。
- 让 `int` 保存需要保留小数的计算结果。
- 强制类型转换前没有考虑数据范围和精度损失。
- 直接假设任何字符都能通过加减 32 转换大小写。该规律只适用于对应的英文字母。
- 想输出一个反斜杠时只写一个反斜杠。字符串中应写 `"\\"`。
## 2.2 变量的输入与输出
### 核心知识
`cin`、`cout` 是 C++ 的流式输入输出工具,通过 `<iostream>` 使用。`scanf`、`printf` 是格式化输入输出函数,需要添加 `<cstdio>` 使用。
输入多个数据时,变量的数量、类型和顺序必须与题意一致。
输出时不仅要保证答案数值正确,还要严格匹配题目要求的空格、换行、小数位数和文字内容。
### `cin` 与 `cout`
```cpp
int a, b;
cin >> a >> b; // 依次读入两个整数
cout << a << " " << b; // 输出两个整数,中间一个空格
cout << a + b << '\n'; // 输出计算结果并换行
```
使用规则:
- `cin` 使用提取运算符 `>>`,把输入保存到变量。
- `cout` 使用插入运算符 `<<`,把数据发送到控制台。
- `cin >> a >> b;` 会跳过前导的空白字符,输入中的空格、Tab、回车通常都能分隔数据。
- `cout << "a+b";` 输出文本 `a+b`;`cout << a + b;` 输出计算结果。
- `endl` 和 `'\n'` 都能换行。
### `scanf` 与 `printf`
```cpp
#include <cstdio>
int age;
double height;
char grade;
scanf("%d%lf %c", &age, &height, &grade);
printf("%d %.2f %c\n", age, height, grade);
```
常用格式说明符:
| 数据类型 | `scanf` 读取 | `printf` 输出 |
| --- | --- | --- |
| `int` | `%d` | `%d` |
| `long long` | `%lld` | `%lld` |
| `float` | `%f` | `%f` |
| `double` | `%lf` | `%f` |
| `char` | `%c` | `%c` |
输出小数位数:
```cpp
double value = 3.1415926;
printf("%.2f\n", value); // 3.14
printf("%.3f\n", value); // 3.142
```
`scanf` 要把数据写入变量,因此变量名前通常要加取地址符 `&`:
```cpp
int n;
scanf("%d", &n);
```
格式字符串中的普通字符必须与输入对应:
```cpp
scanf("%d%d", &a, &b); // 可输入:10 20
scanf("%d,%d", &a, &b); // 必须输入:10,20
```
`%c` 默认不会跳过空白字符。需要跳过已有的空格或换行时,可在 `%c` 前留一个空格:
```cpp
char op;
scanf(" %c", &op);
```
### 典型例题
**题目:** 输入总水量 `t`(毫升)和人数 `n`。输出每人分到的水量,保留 3 位小数;第二行输出需要的杯子数,每人 2 个杯子。
```cpp
int main() {
double t;
int n;
scanf("%lf%d", &t, &n);
printf("%.3f\n", t / n);
printf("%d\n", n * 2);
return 0;
}
```
**说明:** `scanf` 读取 `double` 使用 `%lf`;`printf` 输出 `double` 使用 `%f`;`%.3f` 表示保留 3 位小数。
### 常见错误
- 把方向写反:`cin` 使用 `>>`,`cout` 使用 `<<`。
- `scanf` 漏写 `&`,导致无法把输入正确保存到变量。
- 格式说明符与变量类型不匹配。此问题不只是“精度降低”,还可能导致读取失败或未定义行为。
- 混淆 `double` 的格式:`scanf` 读取用 `%lf`,`printf` 输出用 `%f`。
- `scanf("%d,%d", ...)` 要求输入中真的出现逗号。
- 用 `%c` 读取字符时,意外读入了前一次输入留下的空格或换行。
- OJ 输出中多了提示语、单位、标点或空格。
## 2.3 顺序结构程序设计案例
### 核心知识
顺序结构程序按照代码书写顺序逐条执行。基础题通常可以分成“输入、处理、输出”三层:
```cpp
// 1. 输入
cin >> ...;
// 2. 处理
// 计算公式、更新变量、转换单位
// 3. 输出
cout << ...;
```
面对程序设计题时,可以使用七步法:
1. **读懂题目**:明确最终要计算什么。
2. **理解输入**:确认数据的数量、类型、顺序和单位。
3. **设计逻辑思路**:先写公式或处理步骤。
4. **翻译成代码**:定义变量,把思路逐步写成 C++ 语句。
5. **处理输出**:安排顺序、空格、换行和精度。
6. **编译查错**:先解决语法错误。
7. **样例验证**:输入样例并对照输出,再补充边界数据。
常见解题方法包括:
- 把复杂计算拆成多个中间结果,用变量保存。
- 统一不同单位,再进行计算。
- 使用临时变量,防止原数据在赋值时被覆盖。
- 在关键位置临时输出中间变量,与手算结果逐步核对。
交换变量时需要第三个变量中转:
```cpp
int a = 3, b = 4;
int temp = a;
a = b;
b = temp;
```
### 例题:小鱼的游泳时间
**题目:** 小鱼从当天 `a` 时 `b` 分游到 `c` 时 `d` 分。输入四个整数,输出游泳时长的小时数和分钟数。本题保证结束时间不早于开始时间。
```cpp
#include <iostream>
using namespace std;
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
int startMinutes = a * 60 + b;
int endMinutes = c * 60 + d;
int duration = endMinutes - startMinutes;
int hours = duration / 60;
int minutes = duration % 60;
cout << hours << " " << minutes;
return 0;
}
```
**思路说明:**
1. 输入开始时间和结束时间,共 4 个整数。
2. 把两个时刻统一换算为分钟。
3. 用结束总分钟数减去开始总分钟数。
4. 用 `/ 60` 得到小时,用 `% 60` 得到剩余分钟。
5. 输入 `12 50 19 10` 时,应输出 `6 20`。
如果结果错误,可以临时输出中间变量:
```cpp
cout << "start=" << startMinutes << '\n';
cout << "end=" << endMinutes << '\n';
cout << "duration=" << duration << '\n';
```
提交 OJ 前要删除这些调试输出。
### 补充案例:按格式反转数字
输入形如 `123.4` 的固定格式数据,将其输出为 `4.321`。可以把输入看作 4 个字符和一个固定的小数点:
```cpp
#include <cstdio>
int main() {
char a, b, c, d;
scanf("%c%c%c.%c", &a, &b, &c, &d);
printf("%c.%c%c%c\n", d, c, b, a);
return 0;
}
```
这里的 `.` 是格式字符串中的普通字符,因此输入中也必须在对应位置出现小数点。
### 常见错误
- 把样例数据直接写死,程序无法处理其他合法输入。
- 时间差分别计算小时差和分钟差,却没有处理借位。
- 交换变量时直接写 `a = b; b = a;`,使原值丢失。
- 复杂公式全部写在一行,出错后难以定位。
- 只测试样例,不测试整点、临界值、最小值等情况。
- 调试输出未删除,导致答案格式不匹配。
## 2.4 提交评测与错误自查
### 提交流程
1. 在本地完成代码并成功编译。
2. 输入题目样例,确认输出内容和格式一致。
3. 把完整源代码提交到 OJ。
4. 根据评测结果定位问题,修改后重新测试和提交。
OJ 只检查程序输出的字符序列是否符合要求。不要擅自添加输入提示、答案说明或单位。
### 常见评测结果
| 结果 | 含义 | 优先检查 |
| --- | --- | --- |
| **AC(Accepted)** | 通过全部测试数据 | 程序已通过本题评测 |
| **CE(Compile Error)** | 编译错误 | 分号、括号、变量名、头文件、中文标点 |
| **WA(Wrong Answer)** | 答案错误 | 公式、条件、类型、边界情况、输出格式 |
| RE(Runtime Error) | 运行时错误 | 除数为 0、非法访问、错误输入函数用法 |
| TLE(Time Limit Exceeded) | 超出时间限制 | 算法效率、循环终止条件 |
### 错误自查顺序
#### 出现 CE
1. 查看编译器给出的第一条错误信息和行号。
2. 检查该行以及前一行是否漏写分号。
3. 检查 `()`、`{}`、引号是否成对。
4. 检查变量是否先定义后使用,名称是否拼写一致。
5. 检查是否误用了中文标点或缺少头文件。
#### 出现 WA
1. 先检查输出格式:空格、换行、小数位数、文字大小写。
2. 用草稿纸手算样例和一组自选数据。
3. 分步输出中间变量,找出第一个与手算不一致的位置。
4. 检查整数除法、数据类型和运算优先级。
5. 检查最小值、最大值、临界值和特殊情况。
#### 出现 RE(目前较少遇见)
1. 检查除数是否可能为 0。
2. 检查 `scanf` 的格式说明符和 `&`。
## 本章练习
### 一、基础知识
1. 为下列数据选择合适的类型:学生人数、商品价格、考试等级字符、条件真假、较大的整数总数。
<br><br>
2. 说明字符 `'7'` 与整数 `7` 的区别。
<br><br>
3. 写出 `'0'`、`'A'`、`'a'` 的 ASCII 值。
<br><br>
4. 写出以下转换表达式:
- 数字字符 `ch` 转为对应的整数数字。
- 大写英文字母 `upper` 转为对应的小写字母。
- 整数 `code` 强制转换为字符。
5. 写出下列转义序列的作用:`\n`、`\t`、`\\`、`\"`。
<br><br>
7. `scanf` 读取 `int`、`double`、`char` 时分别使用什么格式说明符?
<br><br>
8. `printf` 输出 `double` 并保留两位小数时,格式说明符应怎样写?
<br><br>
9. `scanf` 中为什么通常要在变量名前加 `&`?
<br><br>
10. 分别说明 AC、CE、WA 的含义。
<br><br>
### 二、代码阅读与改错
1. 写出程序的输出结果:
```cpp
char c = '6';
int n = c - '0';
cout << n + 4;
```
<br><br>
2. 写出程序的输出结果:
```cpp
char upper = 'C';
char lower = upper + ('a' - 'A');
cout << lower;
```
<br>
<br>
3. 找出格式说明符的错误并改正:
```cpp
int age;
double height;
scanf("%lf%d", &age, &height);
printf("%d %.2d\n", age, height);
```
<br><br>
4. 找出变量交换失败的原因并改正:
```cpp
int a = 3, b = 8;
a = b;
b = a;
cout << a << " " << b;
```
<br><br>
### 三、程序设计
1. **字符与 ASCII**
输入一个字符,输出该字符以及它对应的 ASCII 值,中间用一个空格分隔。
- 样例输入:`A`
- 样例输出:`A 65`
2. **数字字符转整数**
输入一个 `'0'`~`'9'` 的数字字符,将它转换成整数后加 5,并输出结果。
- 样例输入:`7`
- 样例输出:`12`
6. **格式化平均值**
输入三个 `double` 数据,输出它们的平均值,结果需要严格保留 3 位小数。
@@ -0,0 +1,628 @@
# 第三章 关系、逻辑与分支结构
## 本章内容
- 3.1 关系表达式与逻辑表达式
- 3.2 分支语句
- 3.3 分支嵌套
- 3.4 分支程序设计案例
## 3.1 关系表达式与逻辑表达式
### 核心知识
关系运算用于比较两个值,结果是 `bool` 类型,即 `true` 或 `false`。而逻辑运算符可以把多个真假条件组合成更复杂的判断。
C++ 在需要判断真假的位置,会把数值 `0` 视为假,把非零值视为真。`cout` 默认把 `true` 输出为 `1`,把 `false` 输出为 `0`。
### 关系运算符
| 运算符 | 含义 | 示例 |
| --- | --- | --- |
| `==` | 等于 | `score == 100` |
| `!=` | 不等于 | `x != 0` |
| `>` | 大于 | `a > b` |
| `<` | 小于 | `a < b` |
| `>=` | 大于等于 | `age >= 18` |
| `<=` | 小于等于 | `score <= 60` |
关系表达式示例:
```cpp
bool result1 = (10 > 5); // true
bool result2 = (3 == 4); // false
bool result3 = (7 != 0); // true
```
### 逻辑运算符
| 表达式 | 含义 | 何时为真 |
| --- | --- | --- |
| `A && B` | A 并且 B | A、B 都为真 |
| `A || B` | A 或者 B | A、B 至少一个为真 |
| `!A` | 不是 A | A 为假 |
自然语言与 C++ 表达式对照:
```cpp
// x 在闭区间 [10, 20] 内
(x >= 10) && (x <= 20)
// x 小于 0 或大于 100
(x < 0) || (x > 100)
// x 是 3 的倍数,但不是偶数
(x % 3 == 0) && (x % 2 != 0)
// x 不等于 10
!(x == 10) // 或者 x != 10
```
构建逻辑表达式时,可以按三个步骤完成:
1. **拆解条件**:把长规则拆成独立的小判断。
2. **寻找连接词**:“并且”对应 `&&`,“或者”对应 `||`,“不”对应 `!`。
3. **组合表达式**:使用括号明确每一组条件之间的关系。
### 运算优先级
常用优先级从高到低可概括为(**非算关与或**):
```text
括号 → 逻辑非 ! → 算术运算 → 关系运算 → && → ||
```
对于一些复杂表达式应主动使用括号,不要只依赖优先级记忆,例如:
```cpp
(math == 100 || english == 100) && homeworkFinished
```
### 短路求值
对于 `A && B`:
- 如果 A 为假,整个表达式一定为假,B 不再执行。
对于 `A || B`:
- 如果 A 为真,整个表达式一定为真,B 不再执行。
```cpp
int x = 0;
int y = 10;
bool a = (x != 0) && (100 / x > 2);
// 左侧为 false,右侧不执行,避免除以 0
bool b = (y > 0) || (++y > 20);
// 左侧为 true,右侧不执行,y 仍为 10
```
短路既能减少不必要的计算,也能保护右侧表达式不在非法条件下执行。
### 典型例题:闰年表达式
闰年规则为:年份能被 4 整除但不能被 100 整除,或者能被 400 整除。
拆解:
- A:`year % 4 == 0`
- B:`year % 100 != 0`
- C:`year % 400 == 0`
组合:
```cpp
bool leap = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
```
### 常见错误
- 判断相等时写成 `=`。`=` 是赋值,`==` 才是比较。
- 写成数学连写 `5 < x < 10`。正确写法是 `x > 5 && x < 10`。
- 把逻辑与、逻辑或写成 `&`、`|`。应使用 `&&`、`||`。
- 区间题漏掉端点的等号。
- 复杂条件没有括号,导致实际的计算优先级与题意不同。
- 在逻辑表达式右侧使用 `x++` 等有副作用的操作,却忽略短路可能导致它不执行。
## 3.2 分支语句
### 核心知识
分支结构让程序根据条件选择不同的执行路径:
- `if`:只关心条件成立时需要做什么。
- `if-else`:处理二选一。
- `if-else if-else`:处理多个互斥选项,只执行首次满足的分支。
- 多个独立 `if`:每个条件分别判断,可能执行多个分支。
- `switch`:处理同一个整型或字符型表达式的多路等值选择。
### 单分支和双分支
```cpp
if (condition) {
// condition 为 true 时执行
}
```
```cpp
if (condition) {
// 条件成立时执行
} else {
// 条件不成立时执行
}
```
奇偶判断:
```cpp
if (number % 2 == 0) {
cout << "偶数";
} else {
cout << "奇数";
}
```
### 多分支
```cpp
if (condition1) {
// 第一种情况
} else if (condition2) {
// 第一种情况不成立,第二种情况成立
} else {
// 以上条件都不成立
}
```
多分支按照从上到下的顺序判断。一旦某个条件为真,程序执行对应代码块,然后跳过整条分支链。因此,条件书写顺序会影响结果。
例如对于成绩分级的实例,应首先判断更高的分数:
```cpp
if (score >= 90) {
cout << "A";
} else if (score >= 80) {
cout << "B";
} else if (score >= 60) {
cout << "C";
} else {
cout << "D";
}
```
而且对于多分支而言,后面的条件隐含了前面条件不成立。例如在执行 `score >= 80` 的分支时,已经知道 `score < 90`。
### 独立 `if` 与 `else if`
多个独立 `if` 属于“多选多”:
```cpp
if (x % 2 == 0) {
cout << "even ";
}
if (x % 3 == 0) {
cout << "multiple of 3";
}
```
当 `x` 为 6 时,两个条件都成立,两段代码都会执行。
`if-else if` 属于“多选一”:前面的分支一旦命中,后面的分支不再判断。
### `switch` 多路选择
`switch` 适合对同一个表达式进行多个等值匹配:
```cpp
switch (expression) {
case constant1:
// expression 等于 constant1
break;
case constant2:
// expression 等于 constant2
break;
default:
// 没有任何 case 匹配
break;
}
```
规则:
- `switch` 后的 `expression` 表达式应为整型、字符型的值。
- `case` 后的 `constant` 所匹配的值必须是常量,不能写范围条件,不能写变量。
- `break` 会立即跳出 `switch`。
- `default` 是可选的保底分支,作用类似多分支最后的 `else`。
- 缺少 `break` 时,程序会从匹配位置继续向下执行直到遇到 `break` 为止,这种情况称为** `case` 穿透**。
在一些特殊情况,多个 `case` 可以有意使用穿透来共用一段逻辑:
```cpp
switch (letter) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
cout << "元音";
break;
default:
cout << "非元音";
break;
}
```
### 常见错误
- 在 `if` 条件后误写分号:
```cpp
if (x > 0); { // 错误:分号提前结束了 if
cout << x;
}
```
- 省略花括号后添加第二条语句,误以为两条语句都受 `if` 控制。
- 把多选一写成多个独立 `if`,造成多个分支同时输出。
- 把允许多个结果的情况写成 `else if`,导致后续条件被跳过。
- 多分支先写宽松条件,导致严格条件永远没有机会命中。
- 用 `switch` 处理 `score >= 60` 等范围条件。
- `case` 后写变量、范围或重复的常量值。
- 漏写 `break`,造成意外穿透。
## 3.3 分支嵌套
### 核心知识
分支嵌套,是在一个 `if`、`else if` 或 `else` 的代码块内部再放入一个完整的分支结构。
嵌套适合“先判断大前提,再处理细分情况”的问题:
- 外层分支确定主要类别或进入资格。
- 内层分支只在外层条件成立后继续判断。
- 如果外层条件不成立,内层条件不会执行。
### 语法与执行流程
```cpp
if (外层条件) {
if (内层条件) {
// 外层条件和内层条件都成立
} else {
// 外层条件成立,但内层条件不成立
}
} else {
// 外层条件不成立
}
```
执行过程:
1. 先判断外层条件。
2. 外层条件为假时,跳过整个外层代码块。
3. 外层条件为真时,进入代码块,再判断内层条件。
4. 内层分支执行完成后,程序继续执行嵌套结构后面的代码。
### 典型例题:会员折扣
商场规则如下:
- 非会员按原价结算。
- 会员消费满 500 元打 8 折。
- 会员消费不足 500 元打 9 折。
```cpp
#include <iostream>
using namespace std;
int main() {
double amount;
bool isMember;
cin >> amount >> isMember;
double payment = amount;
if (isMember) {
if (amount >= 500) {
payment = amount * 0.8;
} else {
payment = amount * 0.9;
}
}
cout << payment;
return 0;
}
```
**说明:** 外层先判断会员资格,只有会员才进入内层判断消费金额。非会员不进入内层,`payment` 保持原价。
### 常见错误
- 缩进混乱,无法看清 `else` 与哪个 `if` 配对。
- 外层条件不成立时仍然期待内层判断执行。
- 把所有条件都塞进多层嵌套,使代码难以阅读。能用清晰逻辑表达式解决时,不必强行嵌套。
- 忘记为各层代码块补齐花括号。
- 在不同层级重复或遗漏条件,导致某些情况没有处理。
## 3.4 分支程序设计案例
解决分支题时,可以采用四步流程:
1. **找到分岔点**:哪些条件会改变程序执行路径?
2. **写出条件表达式**:把自然语言准确翻译为关系或逻辑表达式。
3. **选择分支结构**:单选、二选一、多选一、多选多、嵌套或 `switch`。
4. **实现各分支代码**:在每个分支写入对应处理代码。
### 案例一:BMI 分类
输入体重 `mass`(kg)和身高 `height`(m),计算 BMI。小于 18.5 输出 `Underweight`;小于 24 输出 `Normal`;否则输出 `Overweight`。
```cpp
#include <iostream>
using namespace std;
int main() {
double mass, height;
cin >> mass >> height;
double bmi = mass / (height * height);
if (bmi < 18.5) {
cout << "Underweight";
} else if (bmi < 24) {
cout << "Normal";
} else {
cout << "Overweight";
}
return 0;
}
```
第二个分支隐含了第一个条件不成立,即 `bmi >= 18.5`,所以只需继续判断 `bmi < 24`。
### 案例二:闰年判断
输入年份。如果是闰年,输出该年份;否则输出 `No`。
```cpp
#include <iostream>
using namespace std;
int main() {
int year;
cin >> year;
bool leap = ((year % 4 == 0) && (year % 100 != 0))
|| (year % 400 == 0);
if (leap) {
cout << year;
} else {
cout << "No";
}
return 0;
}
```
本题先用关系和逻辑运算构建一个完整条件,再用 `if-else` 根据真假输出。
### 案例三:简易计算器
输入形如 `7 * 2` 的表达式,根据运算符完成计算。
```cpp
#include <iostream>
using namespace std;
int main() {
double a, b;
char op;
cin >> a >> op >> b;
switch (op) {
case '+':
cout << a + b;
break;
case '-':
cout << a - b;
break;
case '*':
cout << a * b;
break;
case '/':
if (b == 0) {
cout << "Error";
} else {
cout << a / b;
}
break;
default:
cout << "Unknown operator";
break;
}
return 0;
}
```
`switch` 负责对字符 `op` 做等值匹配;除法分支中再用嵌套 `if` 检查除数是否为 0。
## 本章练习
### 一、表达式练习
用 C++ 表达式表示下列条件:
1. `x` 大于等于 10 且小于等于 20。
<br>
2. `x` 小于 0 或大于 100。
<br>
3. `n` 是 5 的倍数。
<br>
4. `n` 能被 3 整除,但不能被 2 整除。
<br>
5. 字符 `ch` 是大写英文字母。
<br>
6. 年龄 `age` 不在 7~15 岁的范围内。
<br>
7. 数学或英语至少有一门达到 90 分,并且作业已经完成。变量分别为 `math`、`english`、`homeworkFinished`。
<br>
9. `a`、`b`、`c` 三个条件中至少有两个为真。
<br>
### 二、代码阅读
1. 写出程序的输出结果:
```cpp
int x = 8;
if (x > 5) {
cout << "A";
}
if (x % 2 == 0) {
cout << "B";
}
```
<br><br>
2. 写出程序的输出结果,并说明原因:
```cpp
int score = 85;
if (score >= 60) {
cout << "及格";
} else if (score >= 80) {
cout << "良好";
} else if (score >= 90) {
cout << "优秀";
}
```
<br><br>
3. 写出 `y` 的最终值:
```cpp
int x = 5;
int y = 10;
bool result = (x > 1) || (++y > 20);
```
<br><br>
4. 写出程序的输出结果:
```cpp
int n = 2;
switch (n) {
case 1:
cout << "A";
case 2:
cout << "B";
case 3:
cout << "C";
break;
default:
cout << "D";
}
```
<br><br>
5. 写出程序的输出结果:
```cpp
bool loggedIn = true;
bool isAdmin = false;
if (loggedIn) {
if (isAdmin) {
cout << "管理员";
} else {
cout << "普通用户";
}
} else {
cout << "未登录";
}
```
<br><br>
6. 判断表达式 `5 < x < 10` 能否正确表示 `x` 在 5 和 10 之间,并说明计算过程。
<br><br>
### 三、程序改错
1. 改正下面的判断语句:
```cpp
if (score = 100); {
cout << "Full marks";
}
```
2. 下面的程序想判断 `x` 是否在闭区间 [10, 20] 内,请改正:
```cpp
if (10 <= x <= 20) {
cout << "Yes";
}
```
3. 下面的成绩分级程序可以正确的区分等级吗?
```cpp
if (score >= 60) {
cout << "C";
} else if (score >= 80) {
cout << "B";
} else if (score >= 90) {
cout << "A";
} else {
cout << "D";
}
```
### 四、程序设计
1. **奇偶判断**
输入一个整数,偶数输出 `Even`,奇数输出 `Odd`。
2. **景点门票**
某景点全价票 50 元。年龄不超过 6 岁或不低于 60 岁时票价为 25 元,其余情况为 50 元。输入年龄,输出票价。
- 样例输入:`65`
- 样例输出:`25`
3. **三个数的最大值**
输入三个整数,使用分支语句输出其中的最大值。
- 样例输入:`6 5 2`
- 样例输出:`6`
@@ -0,0 +1 @@
while 循环
@@ -0,0 +1,6 @@
while 循环的余下部分,把while循环的一尺之锤的例题讲解并让学生课堂完成,讲解ppt while循环的余下部分
@@ -0,0 +1,2 @@
while 循环的余下部分,讲解循环n次,讲解ppt while循环的累加与遍历和筛选模式
@@ -0,0 +1,2 @@
复习讲解循环n次,while循环的累加,讲解遍历和筛选模式
@@ -0,0 +1,3 @@
OJ 题目 P0058 翻倍达标、P0059 阶梯存钱、P0060 筛选编号
作业: B0055 小玉在游泳
@@ -0,0 +1 @@
数学运算初步
@@ -0,0 +1 @@
继续讲解数学运算初步,整除除法和小数除法,取余数,完成例题1-3,1-4,学会OJ系统的使用
@@ -0,0 +1 @@
完成例题1-4,讲解变量初步
@@ -0,0 +1 @@
完成例题1-4,讲解变量初步
@@ -0,0 +1 @@
讲解并完成书本例题 例1-10 猴子吃桃
@@ -0,0 +1,395 @@
# 第一章 C++ 程序设计基础
## 本章内容
- 1.1 程序设计的目标和流程
- 1.2 简单数学运算
- 1.3 变量与常量
## 1.1 程序设计的目标和流程
### 核心知识
程序设计的目标,是把现实问题中的处理步骤转化为计算机能够准确执行的指令。一个程序通常要完成三件事:
1. **接收数据**:获得题目给出的输入。
2. **处理数据**:按照公式或规则计算。
3. **给出结果**:严格按要求输出答案。
C++ 属于高级语言。我们编写的源代码不能直接由计算机执行,必须先经过编译。完整流程可以概括为:
```text
分析问题 → 设计算法 → 编写源代码 → 编译与链接 → 运行程序 → 测试结果
```
“编译成功”只表示代码符合语法规则并成功生成程序,不表示计算结果一定正确。
### 关键语法与规则
最小的 C++ 程序结构:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
```
各部分作用:
| 代码 | 作用 |
| --- | --- |
| `#include <iostream>` | 引入标准输入输出流库,使程序可以使用 `cin`、`cout` |
| `using namespace std;` | 允许直接使用标准命名空间 `std` 中的工具 |
| `int main()` | 主函数,也是程序的执行入口 |
| `{ ... }` | 标出一个代码块的范围,左右花括号必须配对 |
| `cout << ...;` | 向控制台输出数据 |
| `return 0;` | 结束 `main`,向系统报告程序正常结束 |
书写代码时还要注意:
- C++ 区分大小写,`cout` 与 `Cout` 不是同一个名称。
- 代码中的标点应使用英文半角符号。
- 大多数语句以英文分号 `;` 结束。
- 一个可独立运行的程序必须有且只有一个 `main` 函数。
- `#include` 是预处理指令,末尾不写分号。
注释供程序员阅读,编译器会忽略注释内容:
```cpp
// 单行注释:从 // 开始到本行末尾
/*
块注释:可以跨越多行
适合说明一段代码的整体功能
*/
```
### 例题
**题目:** 输出两行文字,第一行输出 `C++ Review`,第二行输出 `Ready!`。
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "C++ Review" << endl;
cout << "Ready!" << endl;
return 0;
}
```
**说明:** 程序从 `main` 开始执行。`cout` 负责输出,`endl` 负责换行,每条输出语句都以分号结束。
### 常见错误
- 把 `cout` 使用的 `<<` 写成 `>>`。
- 使用中文分号 `;`、中文引号 `“ ”` 或中文括号。
- 漏写分号,或遗漏 `}`,使括号无法配对。
- 变量名或工具名拼写错误。
- 在 OJ 题目中擅自输出题目没有要求的内容。
- 编译器标出的行不一定是真正写错的位置。遇到括号、分号问题时,应同时向前检查。
## 1.2 简单数学运算
### 核心知识
C++ 的基本算术运算符包括:
| 运算符 | 作用 | 示例 | 结果 |
| --- | --- | --- | --- |
| `+` | 加法 | `5 + 2` | `7` |
| `-` | 减法 | `5 - 2` | `3` |
| `*` | 乘法 | `5 * 2` | `10` |
| `/` | 除法 | `7 / 2` | `3` |
| `%` | 求余 | `7 % 2` | `1` |
两个整数相除时执行**整数除法**,小数部分直接舍去,不进行四舍五入。只要参与除法的一方是浮点数,才会得到浮点结果。
### 关键语法与规则
```cpp
cout << 7 / 2; // 3:整数除法
cout << 7.0 / 2; // 3.5:浮点除法
cout << 14 % 4; // 2:求余
cout << (2 + 3) * 4; // 20:括号优先
```
常见优先级从高到低:
1. 括号 `()`
2. 乘、除、取余 `* / %`
3. 加、减 `+ -`
`cout` 中双引号会影响输出含义:
```cpp
cout << "2 + 4"; // 输出文本:2 + 4
cout << 2 + 4; // 输出计算结果:6
```
`%` 和整数除法经常配合使用:
```cpp
int n = 479;
int ones = n % 10; // 9:提取最右边一位
n = n / 10; // 47:去掉最右边一位
```
可以把 `% 10` 记作“个位提取器”,把 `/ 10` 记作“个位削减器”。
部分数学函数工具需要引入头文件 `<cmath>`:
```cpp
#include <cmath>
double x = sqrt(81); // 计算根号 81,结果为 9,sqrt(x) 的意义为计算根号x
double y = pow(2, 5); // 计算 2 的 5 次方,结果为 32,pow(a, b) 的意义为计算 a 的 b 次方
```
### 例题
**题目:** 14 个苹果平均分给 4 名同学,输出每人分到的数量和剩余数量,中间用一个空格分隔。
```cpp
#include <iostream>
using namespace std;
int main() {
cout << 14 / 4 << " " << 14 % 4;
return 0;
}
```
输出:
```text
3 2
```
**说明:** `14 / 4` 得到整数商 3,`14 % 4` 得到余数 2。空格属于文本,因此写成 `" "`。
### 常见错误
- 误以为 `7 / 2` 会得到 `3.5`。需要写成 `7.0 / 2` 或 `7 / 2.0`。
- 对 `double` 使用 `%`。初学阶段把 `%` 理解为整数求余。
- 把 `2 ^ 3` 当作 2 的 3 次方。C++ 中 `^` 不是乘方运算。
- 忘记括号,导致计算顺序与题意不同。
- 把算式放入双引号,程序只会原样输出算式文本。
- 输出多个答案时漏掉题目要求的空格或换行。
## 1.3 变量与常量
### 核心知识
变量用于保存输入数据、计算结果和程序运行过程中的中间状态。变量可以理解为内存中带有名称的数据容器。
使用变量通常包含三个动作:
1. **定义**:要求说明变量的类型和名称。
2. **初始化或赋值**:把数据保存到变量中。
3. **读取或更新**:使用变量中的值,或用新值覆盖旧值。
`=` 是赋值运算符。程序会先计算等号右边,再把结果保存到左边变量中。它与数学中的“相等”含义不同。
常量用于保存程序运行期间不允许改变的数据。常量使用 `const` 定义,并且必须在定义时初始化。
### 关键语法与规则
变量定义、初始化与更新:
```cpp
int a = 10; // 定义并初始化, 语法为:数据类型 + 变量名 = 初始值(其中 = 初始值的部分可以省略)
int b, c; // 同时定义两个 int 变量,未赋值
b = 3; // 给 b 赋值
a = a + 5; // 先计算右边,再把 15 赋给 a
a += 2; // 等价于 a = a + 2
```
常见的复合赋值(将运算和赋值合并,一种简便写法):
| 写法 | 等价写法 |
| --- | --- |
| `a += b;` | `a = a + b;` |
| `a -= b;` | `a = a - b;` |
| `a *= b;` | `a = a * b;` |
| `a /= b;` | `a = a / b;` |
| `a %= b;` | `a = a % b;` |
变量命名规则:
- 只能由字母、数字和下划线组成。
- 不能以数字开头。
- 不能使用 `int`、`return`、`if` 等 C++ 关键字。
- 同一作用域内不能重复定义同名变量。
- 建议变量名见名知意,例如 `score`、`totalMinutes`、`radius`。
常量定义:
```cpp
const double PI = 3.14159;
// PI = 3.14; // 错误:常量不能再次赋值
```
### 典型例题
**题目:** 输入圆的半径,输出圆的周长和面积。
```cpp
#include <iostream>
using namespace std;
int main() {
const double PI = 3.14159;
double r;
cin >> r;
double length = 2 * PI * r;
double area = PI * r * r;
cout << length << " " << area;
return 0;
}
```
**说明:** 半径和计算结果可能含小数,因此使用 `double`。圆周率在程序运行期间不应改变,因此定义为常量。
**变量覆盖示例:书本例1-10 猴子吃桃(P11)。**
```cpp
int peach = 1; // 第四天剩 1 个
peach = (peach + 1) * 2; // 倒推第三天
peach = (peach + 1) * 2; // 倒推第二天
peach = (peach + 1) * 2; // 倒推第一天
cout << peach; // 22
```
每次赋值都先使用 `peach` 的旧值完成右侧计算,再用结果覆盖旧值。
### 常见错误
- 变量没有定义就使用。
- 变量未初始化就参与计算,可能得到不可预测的值。
- 把 `=` 当作判断相等。赋值写 `a = 5;`,判断相等写 `a == 5`。
- 写 `a + 1;` 却以为 `a` 会自动变化。更新变量应写 `a = a + 1;`、`a += 1;` 或 `a++;`。
- 常量没有初始化,或在定义后再次赋值。
- 变量名以数字开头、包含中文或使用了关键字。
## 本章练习
### 一、基础知识
1. C++ 程序从哪个地方开始执行?
<br><br>
2. `#include <iostream>` 的作用是什么?它的末尾是否需要分号?
<br><br>
3. 单行注释和块注释分别怎样书写?
<br><br>
4. 分别写出加、减、乘、除、求余对应的运算符。
<br><br>
5. 计算下列表达式的结果:
- `13 / 5`
- `13 % 5`
- `13.0 / 5`
- `(2 + 3) * 4`
6. 判断下列变量名是否合法,并说明原因:
- `score1`
- `1score`
- `total_score`
- `double`
- `studentAge`
7. `=` 在 C++ 中表示什么?`a = a + 1;` 应怎样理解?
<br><br>
8. 为什么 `const double PI = 3.14159;` 必须在定义时给出初始值?
<br><br>
### 二、代码阅读
1. 写出程序的输出结果:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "3 + 4" << " " << 3 + 4;
return 0;
}
```
<br><br><br>
2. 写出程序的输出结果:
```cpp
int a = 5;
a = a + 3;
a *= 2;
cout << a;
```
<br><br><br>
3. 找出下面代码中的错误并改正:
```cpp
#include <iostream>;
using namespace std;
int Main {
cout >> "Hello";
return 0
}
```
<br><br>
4. 写出 `ones` 和 `n` 的最终值:
```cpp
int n = 628;
int ones = n % 10;
n = n / 10;
```
<br><br>
### 三、程序设计
注:**两数运算**要求写上基础的程序结构,而其他的两题只需要从 `int main()` 开始写即可。
1. **两数运算**
输入两个整数 `a` 和 `b`,依次输出它们的和、差、积,三个结果之间用一个空格分隔。
- 样例输入:`8 3`
- 样例输出:`11 5 24`
2. **平均分苹果**
输入苹果总数 `m` 和学生人数 `n`,输出每人分到的苹果数以及剩余苹果数,中间用一个空格分隔。题目保证 `n > 0`。
- 样例输入:`17 5`
- 样例输出:`3 2`
3. **圆的周长与面积**
输入圆的半径 `r`,使用常量 `PI = 3.14159`,输出圆的周长和面积,中间用一个空格分隔。
@@ -0,0 +1,504 @@
# 第二章 数据类型与顺序结构程序设计
## 本章内容
- 2.1 变量的数据类型
- 2.2 变量的输入与输出
- 2.3 顺序结构程序设计案例
- 2.4 提交评测与错误自查
## 2.1 变量的数据类型
### 核心知识
数据类型决定变量能够保存什么数据、占用多少存储空间,以及参与运算时采用什么规则。基础课程中常用的数据类型包括整数、浮点数、字符和布尔值。
| 分类 | 类型 | 主要用途 | 示例 |
| --- | --- | --- | --- |
| 整数 | `short` | 较小范围整数(-128~127) | `short level = 10;` |
| 整数 | `int` | 中范围整数,**最常用**(-21亿~21亿) | `int age = 15;` |
| 整数 | `long long` | 大范围整数(非常大,万兆以上) | `long long total = 80000000;` |
| 浮点数 | `float` | 单精度小数 | `float rate = 0.5;` |
| 浮点数 | `double` | 双精度小数,**小数最常用** | `double price = 12.5;` |
| 字符 | `char` | 单个字符 | `char grade = 'A';` |
| 布尔值 | `bool` | 真或假 | `bool passed = true;` |
选择类型时要同时考虑数据范围和计算精度。题目可能出现小数时,一般使用 `double`;整数范围可能很大时,需要考虑 `long long`。
### `char`、转义字符与 ASCII
`char` 用来保存单个字符,字符值必须使用单引号:
```cpp
char letter = 'A';
char digit = '7'; // 数字字符,不是整数 7
char op = '*';
char blank = ' ';
```
字符串使用双引号。`'A'` 是一个字符,`"A"` 是一个字符串,两者不能混用。
字符在内存中以整数编码保存。基础题通常使用 ASCII 规律处理英文字母和数字字符:
| 字符范围 | ASCII 值 |
| --- | --- |
| `'0'`~`'9'` | 48~57 |
| `'A'`~`'Z'` | 65~90 |
| `'a'`~`'z'` | 97~122 |
常用转换:
```cpp
char c = '8';
int number = c - '0'; // 数字字符转整数,得到 8
char upper = 'B';
char lower = upper + ('a' - 'A'); // 大写字母转小写字母,得到 'b'
int code = 65;
char letter = (char)code; // 强制转换后得到字符 'A'
char x = 'A';
int ascii = (int)x; // 强制转换后得到整数 65
```
转义序列以反斜杠 `\` 开头:
| 写法 | 含义 |
| --- | --- |
| `\n` | 换行 |
| `\t` | 水平制表符(Tab键, 一般代表的 4 个空格) |
| `\\` | 一个反斜杠 |
| `\"` | 一个双引号 |
| `\'` | 一个单引号 |
示例:
```cpp
cout << "小明说:\"what is \\n\"" << '\n';
```
输出:
```text
小明说:"what is \n"
```
### 类型转换
当表达式中出现不同数据类型时,编译器可能进行自动类型转换。一般而言,整数与浮点数混合运算时,整数会转换为浮点类型:
```cpp
int a = 5;
double b = 2.0;
double result = a / b; // 2.5
```
把 `double` 转为 `int` 会丢弃小数部分:
```cpp
double value = 3.9;
int n = (int)value; // 3,不进行四舍五入
```
如果要转换整个表达式,应把表达式完整括起来:
```cpp
char result = (char)(a + b);
```
### 典型例题
**题目:** 输入一个大写英文字母,输出对应的小写字母。
```cpp
int main() {
char upper;
cin >> upper;
char lower = (upper - 'A') + 'a'; // 也可通过加减 32 来实现, 大写转小写需要加 32
cout << lower;
return 0;
}
```
**说明:** ASCII 中大写字母和小写字母各自连续排列,而对应大小写字母的差值则一定相同。
### 常见错误
- 把单个字符写成 `"A"`。`char` 值应使用单引号 `'A'`。
- 混淆整数 `7` 与字符 `'7'`。`'7' - '0'` 才得到整数 7。
- 让 `int` 保存需要保留小数的计算结果。
- 强制类型转换前没有考虑数据范围和精度损失。
- 直接假设任何字符都能通过加减 32 转换大小写。该规律只适用于对应的英文字母。
- 想输出一个反斜杠时只写一个反斜杠。字符串中应写 `"\\"`。
## 2.2 变量的输入与输出
### 核心知识
`cin`、`cout` 是 C++ 的流式输入输出工具,通过 `<iostream>` 使用。`scanf`、`printf` 是格式化输入输出函数,需要添加 `<cstdio>` 使用。
输入多个数据时,变量的数量、类型和顺序必须与题意一致。
输出时不仅要保证答案数值正确,还要严格匹配题目要求的空格、换行、小数位数和文字内容。
### `cin` 与 `cout`
```cpp
int a, b;
cin >> a >> b; // 依次读入两个整数
cout << a << " " << b; // 输出两个整数,中间一个空格
cout << a + b << '\n'; // 输出计算结果并换行
```
使用规则:
- `cin` 使用提取运算符 `>>`,把输入保存到变量。
- `cout` 使用插入运算符 `<<`,把数据发送到控制台。
- `cin >> a >> b;` 会跳过前导的空白字符,输入中的空格、Tab、回车通常都能分隔数据。
- `cout << "a+b";` 输出文本 `a+b`;`cout << a + b;` 输出计算结果。
- `endl` 和 `'\n'` 都能换行。
### `scanf` 与 `printf`
```cpp
#include <cstdio>
int age;
double height;
char grade;
scanf("%d%lf %c", &age, &height, &grade);
printf("%d %.2f %c\n", age, height, grade);
```
常用格式说明符:
| 数据类型 | `scanf` 读取 | `printf` 输出 |
| --- | --- | --- |
| `int` | `%d` | `%d` |
| `long long` | `%lld` | `%lld` |
| `float` | `%f` | `%f` |
| `double` | `%lf` | `%f` |
| `char` | `%c` | `%c` |
输出小数位数:
```cpp
double value = 3.1415926;
printf("%.2f\n", value); // 3.14
printf("%.3f\n", value); // 3.142
```
`scanf` 要把数据写入变量,因此变量名前通常要加取地址符 `&`:
```cpp
int n;
scanf("%d", &n);
```
格式字符串中的普通字符必须与输入对应:
```cpp
scanf("%d%d", &a, &b); // 可输入:10 20
scanf("%d,%d", &a, &b); // 必须输入:10,20
```
`%c` 默认不会跳过空白字符。需要跳过已有的空格或换行时,可在 `%c` 前留一个空格:
```cpp
char op;
scanf(" %c", &op);
```
### 典型例题
**题目:** 输入总水量 `t`(毫升)和人数 `n`。输出每人分到的水量,保留 3 位小数;第二行输出需要的杯子数,每人 2 个杯子。
```cpp
int main() {
double t;
int n;
scanf("%lf%d", &t, &n);
printf("%.3f\n", t / n);
printf("%d\n", n * 2);
return 0;
}
```
**说明:** `scanf` 读取 `double` 使用 `%lf`;`printf` 输出 `double` 使用 `%f`;`%.3f` 表示保留 3 位小数。
### 常见错误
- 把方向写反:`cin` 使用 `>>`,`cout` 使用 `<<`。
- `scanf` 漏写 `&`,导致无法把输入正确保存到变量。
- 格式说明符与变量类型不匹配。此问题不只是“精度降低”,还可能导致读取失败或未定义行为。
- 混淆 `double` 的格式:`scanf` 读取用 `%lf`,`printf` 输出用 `%f`。
- `scanf("%d,%d", ...)` 要求输入中真的出现逗号。
- 用 `%c` 读取字符时,意外读入了前一次输入留下的空格或换行。
- OJ 输出中多了提示语、单位、标点或空格。
## 2.3 顺序结构程序设计案例
### 核心知识
顺序结构程序按照代码书写顺序逐条执行。基础题通常可以分成“输入、处理、输出”三层:
```cpp
// 1. 输入
cin >> ...;
// 2. 处理
// 计算公式、更新变量、转换单位
// 3. 输出
cout << ...;
```
面对程序设计题时,可以使用七步法:
1. **读懂题目**:明确最终要计算什么。
2. **理解输入**:确认数据的数量、类型、顺序和单位。
3. **设计逻辑思路**:先写公式或处理步骤。
4. **翻译成代码**:定义变量,把思路逐步写成 C++ 语句。
5. **处理输出**:安排顺序、空格、换行和精度。
6. **编译查错**:先解决语法错误。
7. **样例验证**:输入样例并对照输出,再补充边界数据。
常见解题方法包括:
- 把复杂计算拆成多个中间结果,用变量保存。
- 统一不同单位,再进行计算。
- 使用临时变量,防止原数据在赋值时被覆盖。
- 在关键位置临时输出中间变量,与手算结果逐步核对。
交换变量时需要第三个变量中转:
```cpp
int a = 3, b = 4;
int temp = a;
a = b;
b = temp;
```
### 例题:小鱼的游泳时间
**题目:** 小鱼从当天 `a` 时 `b` 分游到 `c` 时 `d` 分。输入四个整数,输出游泳时长的小时数和分钟数。本题保证结束时间不早于开始时间。
```cpp
#include <iostream>
using namespace std;
int main() {
int a, b, c, d;
cin >> a >> b >> c >> d;
int startMinutes = a * 60 + b;
int endMinutes = c * 60 + d;
int duration = endMinutes - startMinutes;
int hours = duration / 60;
int minutes = duration % 60;
cout << hours << " " << minutes;
return 0;
}
```
**思路说明:**
1. 输入开始时间和结束时间,共 4 个整数。
2. 把两个时刻统一换算为分钟。
3. 用结束总分钟数减去开始总分钟数。
4. 用 `/ 60` 得到小时,用 `% 60` 得到剩余分钟。
5. 输入 `12 50 19 10` 时,应输出 `6 20`。
如果结果错误,可以临时输出中间变量:
```cpp
cout << "start=" << startMinutes << '\n';
cout << "end=" << endMinutes << '\n';
cout << "duration=" << duration << '\n';
```
提交 OJ 前要删除这些调试输出。
### 补充案例:按格式反转数字
输入形如 `123.4` 的固定格式数据,将其输出为 `4.321`。可以把输入看作 4 个字符和一个固定的小数点:
```cpp
#include <cstdio>
int main() {
char a, b, c, d;
scanf("%c%c%c.%c", &a, &b, &c, &d);
printf("%c.%c%c%c\n", d, c, b, a);
return 0;
}
```
这里的 `.` 是格式字符串中的普通字符,因此输入中也必须在对应位置出现小数点。
### 常见错误
- 把样例数据直接写死,程序无法处理其他合法输入。
- 时间差分别计算小时差和分钟差,却没有处理借位。
- 交换变量时直接写 `a = b; b = a;`,使原值丢失。
- 复杂公式全部写在一行,出错后难以定位。
- 只测试样例,不测试整点、临界值、最小值等情况。
- 调试输出未删除,导致答案格式不匹配。
## 2.4 提交评测与错误自查
### 提交流程
1. 在本地完成代码并成功编译。
2. 输入题目样例,确认输出内容和格式一致。
3. 把完整源代码提交到 OJ。
4. 根据评测结果定位问题,修改后重新测试和提交。
OJ 只检查程序输出的字符序列是否符合要求。不要擅自添加输入提示、答案说明或单位。
### 常见评测结果
| 结果 | 含义 | 优先检查 |
| --- | --- | --- |
| **AC(Accepted)** | 通过全部测试数据 | 程序已通过本题评测 |
| **CE(Compile Error)** | 编译错误 | 分号、括号、变量名、头文件、中文标点 |
| **WA(Wrong Answer)** | 答案错误 | 公式、条件、类型、边界情况、输出格式 |
| RE(Runtime Error) | 运行时错误 | 除数为 0、非法访问、错误输入函数用法 |
| TLE(Time Limit Exceeded) | 超出时间限制 | 算法效率、循环终止条件 |
### 错误自查顺序
#### 出现 CE
1. 查看编译器给出的第一条错误信息和行号。
2. 检查该行以及前一行是否漏写分号。
3. 检查 `()`、`{}`、引号是否成对。
4. 检查变量是否先定义后使用,名称是否拼写一致。
5. 检查是否误用了中文标点或缺少头文件。
#### 出现 WA
1. 先检查输出格式:空格、换行、小数位数、文字大小写。
2. 用草稿纸手算样例和一组自选数据。
3. 分步输出中间变量,找出第一个与手算不一致的位置。
4. 检查整数除法、数据类型和运算优先级。
5. 检查最小值、最大值、临界值和特殊情况。
#### 出现 RE(目前较少遇见)
1. 检查除数是否可能为 0。
2. 检查 `scanf` 的格式说明符和 `&`。
## 本章练习
### 一、基础知识
1. 为下列数据选择合适的类型:学生人数、商品价格、考试等级字符、条件真假、较大的整数总数。
<br><br>
2. 说明字符 `'7'` 与整数 `7` 的区别。
<br><br>
3. 写出 `'0'`、`'A'`、`'a'` 的 ASCII 值。
<br><br>
4. 写出以下转换表达式:
- 数字字符 `ch` 转为对应的整数数字。
- 大写英文字母 `upper` 转为对应的小写字母。
- 整数 `code` 强制转换为字符。
5. 写出下列转义序列的作用:`\n`、`\t`、`\\`、`\"`。
<br><br>
7. `scanf` 读取 `int`、`double`、`char` 时分别使用什么格式说明符?
<br><br>
8. `printf` 输出 `double` 并保留两位小数时,格式说明符应怎样写?
<br><br>
9. `scanf` 中为什么通常要在变量名前加 `&`?
<br><br>
10. 分别说明 AC、CE、WA 的含义。
<br><br>
### 二、代码阅读与改错
1. 写出程序的输出结果:
```cpp
char c = '6';
int n = c - '0';
cout << n + 4;
```
<br><br>
2. 写出程序的输出结果:
```cpp
char upper = 'C';
char lower = upper + ('a' - 'A');
cout << lower;
```
<br>
<br>
3. 找出格式说明符的错误并改正:
```cpp
int age;
double height;
scanf("%lf%d", &age, &height);
printf("%d %.2d\n", age, height);
```
<br><br>
4. 找出变量交换失败的原因并改正:
```cpp
int a = 3, b = 8;
a = b;
b = a;
cout << a << " " << b;
```
<br><br>
### 三、程序设计
1. **字符与 ASCII**
输入一个字符,输出该字符以及它对应的 ASCII 值,中间用一个空格分隔。
- 样例输入:`A`
- 样例输出:`A 65`
2. **数字字符转整数**
输入一个 `'0'`~`'9'` 的数字字符,将它转换成整数后加 5,并输出结果。
- 样例输入:`7`
- 样例输出:`12`
6. **格式化平均值**
输入三个 `double` 数据,输出它们的平均值,结果需要严格保留 3 位小数。
@@ -0,0 +1,628 @@
# 第三章 关系、逻辑与分支结构
## 本章内容
- 3.1 关系表达式与逻辑表达式
- 3.2 分支语句
- 3.3 分支嵌套
- 3.4 分支程序设计案例
## 3.1 关系表达式与逻辑表达式
### 核心知识
关系运算用于比较两个值,结果是 `bool` 类型,即 `true` 或 `false`。而逻辑运算符可以把多个真假条件组合成更复杂的判断。
C++ 在需要判断真假的位置,会把数值 `0` 视为假,把非零值视为真。`cout` 默认把 `true` 输出为 `1`,把 `false` 输出为 `0`。
### 关系运算符
| 运算符 | 含义 | 示例 |
| --- | --- | --- |
| `==` | 等于 | `score == 100` |
| `!=` | 不等于 | `x != 0` |
| `>` | 大于 | `a > b` |
| `<` | 小于 | `a < b` |
| `>=` | 大于等于 | `age >= 18` |
| `<=` | 小于等于 | `score <= 60` |
关系表达式示例:
```cpp
bool result1 = (10 > 5); // true
bool result2 = (3 == 4); // false
bool result3 = (7 != 0); // true
```
### 逻辑运算符
| 表达式 | 含义 | 何时为真 |
| --- | --- | --- |
| `A && B` | A 并且 B | A、B 都为真 |
| `A || B` | A 或者 B | A、B 至少一个为真 |
| `!A` | 不是 A | A 为假 |
自然语言与 C++ 表达式对照:
```cpp
// x 在闭区间 [10, 20] 内
(x >= 10) && (x <= 20)
// x 小于 0 或大于 100
(x < 0) || (x > 100)
// x 是 3 的倍数,但不是偶数
(x % 3 == 0) && (x % 2 != 0)
// x 不等于 10
!(x == 10) // 或者 x != 10
```
构建逻辑表达式时,可以按三个步骤完成:
1. **拆解条件**:把长规则拆成独立的小判断。
2. **寻找连接词**:“并且”对应 `&&`,“或者”对应 `||`,“不”对应 `!`。
3. **组合表达式**:使用括号明确每一组条件之间的关系。
### 运算优先级
常用优先级从高到低可概括为(**非算关与或**):
```text
括号 → 逻辑非 ! → 算术运算 → 关系运算 → && → ||
```
对于一些复杂表达式应主动使用括号,不要只依赖优先级记忆,例如:
```cpp
(math == 100 || english == 100) && homeworkFinished
```
### 短路求值
对于 `A && B`:
- 如果 A 为假,整个表达式一定为假,B 不再执行。
对于 `A || B`:
- 如果 A 为真,整个表达式一定为真,B 不再执行。
```cpp
int x = 0;
int y = 10;
bool a = (x != 0) && (100 / x > 2);
// 左侧为 false,右侧不执行,避免除以 0
bool b = (y > 0) || (++y > 20);
// 左侧为 true,右侧不执行,y 仍为 10
```
短路既能减少不必要的计算,也能保护右侧表达式不在非法条件下执行。
### 典型例题:闰年表达式
闰年规则为:年份能被 4 整除但不能被 100 整除,或者能被 400 整除。
拆解:
- A:`year % 4 == 0`
- B:`year % 100 != 0`
- C:`year % 400 == 0`
组合:
```cpp
bool leap = ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
```
### 常见错误
- 判断相等时写成 `=`。`=` 是赋值,`==` 才是比较。
- 写成数学连写 `5 < x < 10`。正确写法是 `x > 5 && x < 10`。
- 把逻辑与、逻辑或写成 `&`、`|`。应使用 `&&`、`||`。
- 区间题漏掉端点的等号。
- 复杂条件没有括号,导致实际的计算优先级与题意不同。
- 在逻辑表达式右侧使用 `x++` 等有副作用的操作,却忽略短路可能导致它不执行。
## 3.2 分支语句
### 核心知识
分支结构让程序根据条件选择不同的执行路径:
- `if`:只关心条件成立时需要做什么。
- `if-else`:处理二选一。
- `if-else if-else`:处理多个互斥选项,只执行首次满足的分支。
- 多个独立 `if`:每个条件分别判断,可能执行多个分支。
- `switch`:处理同一个整型或字符型表达式的多路等值选择。
### 单分支和双分支
```cpp
if (condition) {
// condition 为 true 时执行
}
```
```cpp
if (condition) {
// 条件成立时执行
} else {
// 条件不成立时执行
}
```
奇偶判断:
```cpp
if (number % 2 == 0) {
cout << "偶数";
} else {
cout << "奇数";
}
```
### 多分支
```cpp
if (condition1) {
// 第一种情况
} else if (condition2) {
// 第一种情况不成立,第二种情况成立
} else {
// 以上条件都不成立
}
```
多分支按照从上到下的顺序判断。一旦某个条件为真,程序执行对应代码块,然后跳过整条分支链。因此,条件书写顺序会影响结果。
例如对于成绩分级的实例,应首先判断更高的分数:
```cpp
if (score >= 90) {
cout << "A";
} else if (score >= 80) {
cout << "B";
} else if (score >= 60) {
cout << "C";
} else {
cout << "D";
}
```
而且对于多分支而言,后面的条件隐含了前面条件不成立。例如在执行 `score >= 80` 的分支时,已经知道 `score < 90`。
### 独立 `if` 与 `else if`
多个独立 `if` 属于“多选多”:
```cpp
if (x % 2 == 0) {
cout << "even ";
}
if (x % 3 == 0) {
cout << "multiple of 3";
}
```
当 `x` 为 6 时,两个条件都成立,两段代码都会执行。
`if-else if` 属于“多选一”:前面的分支一旦命中,后面的分支不再判断。
### `switch` 多路选择
`switch` 适合对同一个表达式进行多个等值匹配:
```cpp
switch (expression) {
case constant1:
// expression 等于 constant1
break;
case constant2:
// expression 等于 constant2
break;
default:
// 没有任何 case 匹配
break;
}
```
规则:
- `switch` 后的 `expression` 表达式应为整型、字符型的值。
- `case` 后的 `constant` 所匹配的值必须是常量,不能写范围条件,不能写变量。
- `break` 会立即跳出 `switch`。
- `default` 是可选的保底分支,作用类似多分支最后的 `else`。
- 缺少 `break` 时,程序会从匹配位置继续向下执行直到遇到 `break` 为止,这种情况称为** `case` 穿透**。
在一些特殊情况,多个 `case` 可以有意使用穿透来共用一段逻辑:
```cpp
switch (letter) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
cout << "元音";
break;
default:
cout << "非元音";
break;
}
```
### 常见错误
- 在 `if` 条件后误写分号:
```cpp
if (x > 0); { // 错误:分号提前结束了 if
cout << x;
}
```
- 省略花括号后添加第二条语句,误以为两条语句都受 `if` 控制。
- 把多选一写成多个独立 `if`,造成多个分支同时输出。
- 把允许多个结果的情况写成 `else if`,导致后续条件被跳过。
- 多分支先写宽松条件,导致严格条件永远没有机会命中。
- 用 `switch` 处理 `score >= 60` 等范围条件。
- `case` 后写变量、范围或重复的常量值。
- 漏写 `break`,造成意外穿透。
## 3.3 分支嵌套
### 核心知识
分支嵌套,是在一个 `if`、`else if` 或 `else` 的代码块内部再放入一个完整的分支结构。
嵌套适合“先判断大前提,再处理细分情况”的问题:
- 外层分支确定主要类别或进入资格。
- 内层分支只在外层条件成立后继续判断。
- 如果外层条件不成立,内层条件不会执行。
### 语法与执行流程
```cpp
if (外层条件) {
if (内层条件) {
// 外层条件和内层条件都成立
} else {
// 外层条件成立,但内层条件不成立
}
} else {
// 外层条件不成立
}
```
执行过程:
1. 先判断外层条件。
2. 外层条件为假时,跳过整个外层代码块。
3. 外层条件为真时,进入代码块,再判断内层条件。
4. 内层分支执行完成后,程序继续执行嵌套结构后面的代码。
### 典型例题:会员折扣
商场规则如下:
- 非会员按原价结算。
- 会员消费满 500 元打 8 折。
- 会员消费不足 500 元打 9 折。
```cpp
#include <iostream>
using namespace std;
int main() {
double amount;
bool isMember;
cin >> amount >> isMember;
double payment = amount;
if (isMember) {
if (amount >= 500) {
payment = amount * 0.8;
} else {
payment = amount * 0.9;
}
}
cout << payment;
return 0;
}
```
**说明:** 外层先判断会员资格,只有会员才进入内层判断消费金额。非会员不进入内层,`payment` 保持原价。
### 常见错误
- 缩进混乱,无法看清 `else` 与哪个 `if` 配对。
- 外层条件不成立时仍然期待内层判断执行。
- 把所有条件都塞进多层嵌套,使代码难以阅读。能用清晰逻辑表达式解决时,不必强行嵌套。
- 忘记为各层代码块补齐花括号。
- 在不同层级重复或遗漏条件,导致某些情况没有处理。
## 3.4 分支程序设计案例
解决分支题时,可以采用四步流程:
1. **找到分岔点**:哪些条件会改变程序执行路径?
2. **写出条件表达式**:把自然语言准确翻译为关系或逻辑表达式。
3. **选择分支结构**:单选、二选一、多选一、多选多、嵌套或 `switch`。
4. **实现各分支代码**:在每个分支写入对应处理代码。
### 案例一:BMI 分类
输入体重 `mass`(kg)和身高 `height`(m),计算 BMI。小于 18.5 输出 `Underweight`;小于 24 输出 `Normal`;否则输出 `Overweight`。
```cpp
#include <iostream>
using namespace std;
int main() {
double mass, height;
cin >> mass >> height;
double bmi = mass / (height * height);
if (bmi < 18.5) {
cout << "Underweight";
} else if (bmi < 24) {
cout << "Normal";
} else {
cout << "Overweight";
}
return 0;
}
```
第二个分支隐含了第一个条件不成立,即 `bmi >= 18.5`,所以只需继续判断 `bmi < 24`。
### 案例二:闰年判断
输入年份。如果是闰年,输出该年份;否则输出 `No`。
```cpp
#include <iostream>
using namespace std;
int main() {
int year;
cin >> year;
bool leap = ((year % 4 == 0) && (year % 100 != 0))
|| (year % 400 == 0);
if (leap) {
cout << year;
} else {
cout << "No";
}
return 0;
}
```
本题先用关系和逻辑运算构建一个完整条件,再用 `if-else` 根据真假输出。
### 案例三:简易计算器
输入形如 `7 * 2` 的表达式,根据运算符完成计算。
```cpp
#include <iostream>
using namespace std;
int main() {
double a, b;
char op;
cin >> a >> op >> b;
switch (op) {
case '+':
cout << a + b;
break;
case '-':
cout << a - b;
break;
case '*':
cout << a * b;
break;
case '/':
if (b == 0) {
cout << "Error";
} else {
cout << a / b;
}
break;
default:
cout << "Unknown operator";
break;
}
return 0;
}
```
`switch` 负责对字符 `op` 做等值匹配;除法分支中再用嵌套 `if` 检查除数是否为 0。
## 本章练习
### 一、表达式练习
用 C++ 表达式表示下列条件:
1. `x` 大于等于 10 且小于等于 20。
<br>
2. `x` 小于 0 或大于 100。
<br>
3. `n` 是 5 的倍数。
<br>
4. `n` 能被 3 整除,但不能被 2 整除。
<br>
5. 字符 `ch` 是大写英文字母。
<br>
6. 年龄 `age` 不在 7~15 岁的范围内。
<br>
7. 数学或英语至少有一门达到 90 分,并且作业已经完成。变量分别为 `math`、`english`、`homeworkFinished`。
<br>
9. `a`、`b`、`c` 三个条件中至少有两个为真。
<br>
### 二、代码阅读
1. 写出程序的输出结果:
```cpp
int x = 8;
if (x > 5) {
cout << "A";
}
if (x % 2 == 0) {
cout << "B";
}
```
<br><br>
2. 写出程序的输出结果,并说明原因:
```cpp
int score = 85;
if (score >= 60) {
cout << "及格";
} else if (score >= 80) {
cout << "良好";
} else if (score >= 90) {
cout << "优秀";
}
```
<br><br>
3. 写出 `y` 的最终值:
```cpp
int x = 5;
int y = 10;
bool result = (x > 1) || (++y > 20);
```
<br><br>
4. 写出程序的输出结果:
```cpp
int n = 2;
switch (n) {
case 1:
cout << "A";
case 2:
cout << "B";
case 3:
cout << "C";
break;
default:
cout << "D";
}
```
<br><br>
5. 写出程序的输出结果:
```cpp
bool loggedIn = true;
bool isAdmin = false;
if (loggedIn) {
if (isAdmin) {
cout << "管理员";
} else {
cout << "普通用户";
}
} else {
cout << "未登录";
}
```
<br><br>
6. 判断表达式 `5 < x < 10` 能否正确表示 `x` 在 5 和 10 之间,并说明计算过程。
<br><br>
### 三、程序改错
1. 改正下面的判断语句:
```cpp
if (score = 100); {
cout << "Full marks";
}
```
2. 下面的程序想判断 `x` 是否在闭区间 [10, 20] 内,请改正:
```cpp
if (10 <= x <= 20) {
cout << "Yes";
}
```
3. 下面的成绩分级程序可以正确的区分等级吗?
```cpp
if (score >= 60) {
cout << "C";
} else if (score >= 80) {
cout << "B";
} else if (score >= 90) {
cout << "A";
} else {
cout << "D";
}
```
### 四、程序设计
1. **奇偶判断**
输入一个整数,偶数输出 `Even`,奇数输出 `Odd`。
2. **景点门票**
某景点全价票 50 元。年龄不超过 6 岁或不低于 60 岁时票价为 25 元,其余情况为 50 元。输入年龄,输出票价。
- 样例输入:`65`
- 样例输出:`25`
3. **三个数的最大值**
输入三个整数,使用分支语句输出其中的最大值。
- 样例输入:`6 5 2`
- 样例输出:`6`
@@ -0,0 +1,230 @@
# C++ 编程讲义 — 初识C++
工欲善其事,必先利其器。
编程本质上是我们通过计算机向机器下达精确指令的过程。因此,在正式编写第一行代码之前,我们必须先熟练掌握并使用最核心的工具——电脑的基本操作与 Windows 操作系统。对于初次接触计算机的同学,我们需要从最基础的物理设备开始学习。
---
## 第一部分:计算机基础设备操作
### 一、鼠标的基本操作
鼠标是我们在图形界面中向计算机发送位置与选择指令的设备。
* **左键单击**:用食指按一下鼠标左键并立即松开。用于选定目标(例如选中一段代码或定位光标)。
* **左键双击**:快速、连续地按两次鼠标左键。通常用于打开软件或选中一整个单词。
* **右键单击**:用中指按一下鼠标右键。用于唤出快捷菜单,可以在其中找到复制、粘贴等功能。
* **拖拽**:按住左键不放,移动鼠标到目标位置后再松开。用于移动窗口或选中多行文本。
### 二、熟悉键盘按键
键盘是编写代码时最重要的输入设备。标准键盘一般包括26个英文字母键、10个数字键、12个功能键(F1~F12)、方向键以及其他的一些功能键。
所有按键在物理布局上主要分为5个区:
1. **主键盘区**:最大的一块区域,包含字母、数字和常用标点符号,是我们书写代码的核心区域。
2. **功能键区(F1~F12)**:位于键盘最上方,在不同的软件中有不同的快捷功能。
3. **编辑/控制键区**:包含方向键、删除键(Delete)等,用于在代码中移动光标和修改文本。
4. **小键盘区**:位于最右侧,主要用于快速输入纯数字。
5. **键盘指示灯**:提示当前键盘的状态(如大小写锁定、数字键盘锁定等)。
### **编程必须掌握的按键指令:**
* **Shift 键(上档键)**:用于切换中英文标点符号,或输入按键上半部分的符号(例如按住 Shift 再按数字 9,可以打出左括号 `(` )。
* **Enter 键(回车键)**:在代码中用于换行,使指令结构清晰。
* **Backspace 键(退格键)**:用于删除光标前面的字符,是修改代码错误的常用键。
---
## 第二部分:什么是程序与计算机语言?
大家目前可能对软件中复杂的程序代码感到难以理解,但是,大家了解人类生活中的程序吗?有什么程序是大家在日常生活中经历过的呢?
**例如:开学典礼的程序**
主持人会宣布,今天典礼的程序如下:
* 第一项:升国旗;
* 第二项:领导介绍;
* 第三项:校长致辞;
* 第四项:教师代表发言;
* 第五项:学生代表发言。
这其实就是人类社会的程序。映射到计算机科学中,**计算机的程序被定义为:为了得到某种结果,通过计算机语言表达的指令序列。** 计算机通过严格执行你下达的指令,最终得出预期的结果。
**假如我们要编写一个让计算机“把大象塞进冰箱”的程序,相当于依次下达以下指令:**
1. 打开冰箱门;
2. 把大象塞进冰箱;
3. 关上冰箱门。
### 计算机语言的演变与分类
为了把上述指令传达给计算机,我们需要使用计算机语言。计算机语言主要分为三种:
1. **机器语言(二进制语言)**:由 `0` 和 `1` 组成,这是计算机底层唯一能直接听懂的语言,但人类极难阅读和编写。
2. **汇编语言**:使用一些简短的英文字母代替二进制代码,比机器语言稍易读,但依然非常晦涩。
3. **高级语言(接近英语/自然语言)**:使用人类容易理解的英文字母、单词和数学符号编写。
我们即将学习的 **C++ 课程就属于高级语言**。它的本质是由一套特定的英文单词和符号组成的指令集合。然而,计算机底层只能听懂二进制,这就意味着,**必须要将高级语言“翻译”成机器语言,计算机才能执行。**
---
## 第三部分:C++ 开发工具 (Dev-C++) 与执行过程
那我们应该在什么地方、如何去书写这些高级语言呢?这就需要用到专业的集成开发环境(IDE)。
### 一、初识 Dev-C++
对于初学者,我们使用 **Dev-C++**。这是一款专门用于书写和运行 C/C++ 高级语言的工具。它提供了一个干净的文本编辑区,并且内置了代码高亮等便捷功能,极大地规范和方便了我们的书写。
### 二、程序的编译(翻译)与报错机制
当我们在 Dev-C++ 中书写好高级语言代码后,此时代码还只是一堆英文字符,系统在等待着“翻译官”进行翻译。
**这个“翻译”的过程,在计算机科学中被称为“编译 ”。**
* **严格的语法**:翻译官只能翻译它看得懂的、符合严格规则的代码。如果代码存在拼写错误,或者不符合语法要求,翻译官就会看不懂并拒绝工作。
* **编译报错(出现红色的 ×)**:如果下方窗口出现红色的 `×` 或错误提示,说明代码有错误,翻译失败。**只有在代码完全无误,翻译官顺利翻译后,程序才能被执行。**
* **标点符号规范**:因为 C++ 是一门基于英文环境诞生的语言,它的指令**无法识别中文标点符号**。如果误用了中文的逗号(,)或分号(;),就等同于你自创了一个不存在的单词,翻译官会直接报错。编写代码时,必须确保输入法处于**英文半角**状态。
### 三、编译并运行(快捷键 F11)
当确保代码书写完毕且无误后,我们需要让计算机去执行命令。
在 Dev-C++ 中,**按下键盘上的 `F11` 键,即可一键开始“编译并运行”程序。** 软件会自动先将代码翻译为机器语言,紧接着让计算机执行它。
### 四、控制台(黑框框)
按下 `F11` 并且编译成功后,屏幕上会弹出一个黑色的独立窗口。这个窗口被称为**控制台**。
控制台的作用是**展示程序的最终运行结果**,或者在程序需要用户输入数据时,**与程序进行交互**。当控制台弹出并显示出预期的结果时,就意味着你的指令被计算机成功执行了。
---
## 第四部分:初识 C++ —— 第一个程序 Hello World
了解了工具后,我们将正式开始编写全人类学习编程时的第一个经典程序:向世界问好。
### 一、代码示例
在 Dev-C++ 的代码编辑区中,输入以下代码:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
```
### 二、代码解析
这段看似复杂的英文,其实每一行都有其含义:
* **`#include <iostream>` —— 引入输入输出流库**
* **含义**:告诉计算机“我需要使用标准的输入和输出工具”。如果没有这一句指令,计算机就无法理解后续代码中用于在屏幕上显示文字的命令。
* 就像做披萨前,要先准备好烤箱和工具,没有烤箱肯定无法制作披萨。
* 如果没有这一句,电脑就不知道什么是 `cout`,没有用来输出文字的工具,自然就没法输出文字。
* **`using namespace std;` —— 使用标准命名空间**
* **含义**:告诉计算机“本程序默认使用名为 `std` 的标准工具库”。
* 比如:A 工具箱和 B 工具箱都有个锤子,你就要先说清楚是哪个工具箱的,不然计算机就会搞混。
* `std` 就像 工具箱,`cout` 就像锤子。
* 写了这一句,相当于告诉电脑:“我默认要锤子的时候都用的是 `std` 这个工具箱。”,我们以后要用的所有的工具基本都是在`std`这个工具箱里的。
* **`int main() {` —— 主程序入口**
* **含义**:这是所有 C++ 程序的起点。程序执行时,必定从 `main` 函数后方的大括号 `{` 开始,到对应的大括号 `}` 结束。一个完整的 C++ 程序必须有且仅有一个 `main` 。`int` 是单词 **integer** 的缩写,意思是“整数”,规定这个函数最终执行完毕后,必须向计算机汇报一个整数作为结果。
* 好比一个“主任务清单”,电脑会从这里开始做事情。
* **`cout << "Hello, World!";` —— 屏幕输出指令**
* `cout`:用于输出数据的工具,相当于一个“喇叭”,把数据告诉计算机让它来输出。
* `<<`:像传送带,把右边的东西送到左边。
* **`"Hello, World!"`**:被英文双引号包裹的内容,即我们要原样显示在屏幕上的具体文字数据。
* **整句话的意思**:把 "Hello, World!" 交给输出系统,让它把这句话输出在控制台上。
* *注意:这一行的末尾必须以英文分号 `;` 结束,C++ 里 `;` 代表一条指令的执行完毕。*
* **`return 0;` —— 正常结束程序**
* **含义**:返回一个数字 `0` 给系统。`0` 表示一切正常。
* 好比告诉老师:“作业完成了,没有出错。”
* **`}` —— 程序结束**
* **含义**:与前文的 `{` 对应,标志着整个 `main` 函数的指令序列到此彻底结束。
---
以下是这段代码中涉及的英文单词与缩写的中文翻译对照表(理解英语含义后,对于代码的理解也会非常有帮助):
### 第一个C++程序 基础词汇翻译表(务必记忆!)
| **代码单词** | **英文全拼** | **中文含义** |
| ------------- | ------------------- | -------------------- |
| **include** | include | 包含、引入 |
| **iostream** | input output stream | 输入输出数据流 |
| **using** | using | 使用 |
| **namespace** | name space | 命名空间 |
| **std** | standard | 标准 |
| **int** | integer | 整数 |
| **main** | main | 主要的(程序的起点) |
| **cout** | console output | 控制台输出 |
| **return** | return | 返回 |
------
## 第五部分:程序员的注释
### 一、什么是注释?
注释是专门写给人看的说明文字,计算机的翻译官(编译器)在将代码翻译成机器语言时,会**完全跳过并忽略**这些内容。
可以将注释理解为我们在课本空白处书写的学习笔记:老师(代表计算机)在批改作业时不会检查这些笔记,但这些文字能极大地帮助你自己或他人理清程序的逻辑与思路。
### 二、注释的两种标准写法
在 C++ 语言中,为了满足不同的说明需求,提供了两种添加注释的语法规范:
**1. 单行注释 (`//`)**
- **语法规则**:在代码中使用两个连续的斜杠 `//`。从这两个斜杠开始,直到本行结束的所有文字,都会被编译器自动忽略。通常用于对某单一行的代码进行简短解释。
- **代码示例**:
```cpp
cout << "Hello"; // 这是一句单行注释,专门用于解释左侧这行代码的作用
```
**2. 多行注释 (`/* ... */`)**
- **语法规则**:从 `/*` 符号作为起点开始,直到 `*/` 符号作为终点结束。这两个符号中间包裹的所有文字内容(无论中间跨越了多少行),都会被系统统一认定为注释。此结构非常适合在程序开头书写长篇的整体功能说明。
- **代码示例**:
```C++
/* 这是多行注释块
此处可以详细书写第一点、第二点...
计算机在执行时会完全无视这部分内容
*/
cout << "World";
```
*(注:在 Dev-C++ 等专业软件中,注释部分通常会被自动标记为绿色或其他浅色,以便与正常执行的代码做出明显的视觉区分。)*
### 三、为什么在软件开发中要书写注释?
初学者往往为了节省时间而跳过书写注释的步骤,但书写注释是非常重要的,主要是因为:
- **防遗忘**:人类的短期记忆是有限的。经过数周或数月后再次阅读自己编写的代码时,如果没有注释辅助回忆,当时设计的复杂计算逻辑往往难以迅速被重新理解。
- **团队协作**:在真实的科技企业中,软件开发是一个团队项目,你的代码必定需要交接给其他工程师查阅或修改。清晰、规范的注释是保证团队成员之间能够快速读懂彼此逻辑。
@@ -0,0 +1,38 @@
### **C++ 初识C++ 课后习题 - 答案**
#### **一、 填空题**
1. 高级,编译
2. `F11`
3. 控制台
4. `main`
5. 控制台输出,整数
6. 分号 (`;`)
7. 单行,多行
8. 输入输出流库 (或 标准输入输出功能)
#### **二、 单项选择题**
1. **B**. 程序是一系列有序的指令。
2. **B**. 编译时出现红色错误,意味着编译器(翻译官)读不懂代码,是语法层面的问题。
3. **C**. `int main()` 是C++程序的起点和入口。
4. **C**. `cout` 用于输出,使用 `<<` 运算符,要输出的文字需用英文双引号包裹。
5. **C**. `endl` 是 `end of line` 的缩写,用于换行。
6. **B**. 注释是给程序员看的,编译器会忽略。
7. **C**. `std` 是 `standard` (标准) 的缩写。
8. **A**. `cout` 会紧挨着输出,代码中没有空格也没有 `endl`,所以会连在一起。
9. **C**. `return 0;` 代表程序顺利完成任务,正常退出。
10. **B**. 使用 `//` 和 `/* */` 注释掉的代码行都不会被编译和执行,所以只有 `Step 2` 会被输出。
#### **三、 判断题**
1. × (高级语言必须经过编译,翻译成机器语言后才能被计算机执行。)
2. √ (C++的语法规定,所有标点必须是英文半角状态。)
3. × (所有C++程序都必须有且仅有一个 `main` 函数作为入口。)
4. × (`cout` 使用的符号是 `<<`。)
5. √ (`#include` 是预处理指令,不是C++语句,所以末尾没有分号。)
6. √ (这是单行注释的定义。)
7. × (意思是“使用标准命名空间std”。)
8. √ (`endl` 会在输出"Hello"后换行,所以"World"会显示在第二行。)
9. √ (在文本编辑器中,回车键用于换行。)
10. √ (这是C++多行注释的正确语法。)
@@ -0,0 +1,95 @@
### **C++ 初识C++ 课后习题**
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 我们学习的C++属于\_\_\_\_\_\_\_\_\_\_\_\_语言,它需要通过一个叫做“\_\_\_\_\_\_\_\_\_\_\_\_”的过程,翻译成计算机唯一能直接听懂的机器语言。
2. 在Dev-C++中,我们可以按键盘上的\_\_\_\_\_\_\_\_\_\_\_\_键来一键“编译并运行”程序。
3. 程序编译成功并运行后,会弹出一个黑色的窗口用来显示结果,这个窗口叫做\_\_\_\_\_\_\_\_\_\_\_\_。
4. 所有C++程序都从\_\_\_\_\_\_\_\_\_\_\_\_开始执行,这是程序的入口。
5. `cout` 来自英文 `console output`,是\_\_\_\_\_\_\_\_\_\_\_\_的意思;`int` 来自英文 `integer`,是\_\_\_\_\_\_\_\_\_\_\_\_的意思。
6. 在C++代码中,每一条独立的指令语句的末尾都必须使用英文的\_\_\_\_\_\_\_\_\_\_作为结束标记。
7. 我们可以在代码中书写两种注释:以 `//` 开始的\_\_\_\_\_\_\_\_\_\_注释,和以 `/*` `*/` 包裹的\_\_\_\_\_\_\_\_\_\_注释。
8. `#include <iostream>` 这行代码的作用是引入\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_,这样我们才能使用 `cout` 工具。
#### **二、 单项选择题**
( &nbsp; ) 1. 讲义中“把大象塞进冰箱”的例子,是为了说明程序是什么?
A. 一种文件格式 B. 一系列需要严格按顺序执行的指令
C. 一种计算机硬件 D. 一种只能由 `0` 和 `1` 组成的代码
( &nbsp; ) 2. 在Dev-C++的编辑区写好C++代码后,如果点击“编译”时,下方出现了红色的错误提示,最可能的原因是?
A. 电脑没有连接网络
B. 代码存在语法错误,例如拼写错误或使用了中文标点
C. 程序运行时间太长
D. 程序逻辑太复杂
( &nbsp; ) 3. 每一个C++程序都必须有且仅有一个的入口是?
A. `#include <iostream>` B. `using namespace std;`
C. `int main()` D. `return 0;`
( &nbsp; ) 4. 在C++中,要在屏幕上原样输出一行文字“I love C++”,应该使用下面哪条指令?
A. `cout >> "I love C++";` B. `cin << "I love C++";`
C. `cout << "I love C++";` D. `main << "I love C++";`
( &nbsp; ) 5. `endl` 在 `cout` 语句中的主要作用是什么?
A. 表示程序结束
B. 在输出的末尾添加一个空格
C. 输出一个换行符,让后续的输出从下一行开始
D. 清除屏幕上所有已有的文字
( &nbsp; ) 6. 关于代码中的注释,下列说法正确的是?
A. 注释是写给计算机执行的指令
B. 注释是写给人看的说明文字,计算机会自动忽略它
C. 写了注释会让程序运行变慢
D. C++只支持 `//` 这一种注释
( &nbsp; ) 7. 根据讲义中的词汇表,`std` 是哪个单词的缩写?
A. student (学生) B. study (学习)
C. standard (标准) D. start (开始)
( &nbsp; ) 8. 阅读以下代码片段,它的最终输出是什么?
```cpp
cout << "Apple";
cout << "Banana";
```
​ A. AppleBanana
​ B. Apple Banana
​ C. Apple (然后换行) Banana
​ D. BananaApple
( &nbsp; ) 9. `return 0;` 这句代码在 `main` 函数中的作用是什么?
A. 输出数字0到屏幕上
B. 告诉系统程序出错了
C. 告诉系统程序正常结束了
D. 让程序重新从头执行一次
( &nbsp; ) 10. 阅读以下代码,控制台最终会显示什么内容?
```cpp
// cout << "Step 1" << endl;
cout << "Step 2" << endl;
/*
cout << "Step 3" << endl;
*/
```
​ A. Step 1 B. Step 2
​ C. Step 3 D. Step 1 (然后换行) Step 2 (然后换行) Step 3
#### **三、 判断题 (对的打“√”,错的打“×”)**
1. ( &nbsp; ) C++是一门高级语言,计算机可以直接理解并执行C++代码,无需翻译。
2. ( &nbsp; ) 在编写C++代码时,输入法必须处于英文半角状态,不能使用中文标点符号。
3. ( &nbsp; ) 一个C++程序可以没有 `main` 函数。
4. ( &nbsp; ) `cout` 使用的符号是 `>>`,代表数据从变量流向屏幕。
5. ( &nbsp; ) `#include <iostream>` 这行代码的末尾不需要加分号 `;`。
6. ( &nbsp; ) `//` 符号后面的所有内容,直到这一行的末尾,都会被当作注释处理。
7. ( &nbsp; ) `using namespace std;` 的意思是“使用一个名叫std的学生”。
8. ( &nbsp; ) 以下代码会在屏幕上分两行输出"Hello"和"World"。
```cpp
cout << "Hello" << endl;
cout << "World";
```
9. ( &nbsp; ) 计算机键盘上的 `Enter` 键在代码编辑区的主要作用是换行。
10. ( &nbsp; ) `/* 我是注释 */` 这种写法是C++中一种正确的注释方式。
@@ -0,0 +1,151 @@
# C++ 数学运算与在线评测 讲义
## 一、 双引号 —— 复读机
在开始让计算机进行数学运算前,我们必须先记住 `cout` 输出时的规则——**双引号的作用**。
- **带有双引号 `""`**:计算机将其视为**纯文本**。无论里面写了什么(就算是数学公式),编译器都会像复读机一样原样输出。
- **没有双引号**:计算机将其视为**数学公式**。编译器会在后台先计算出精确结果,然后再将结果输出。
> `cout` 完全可以输出数学运算的结果,前提是**不要**给算式加双引号。
------
## 二、 基础运算与编程题目(书本 P4 页 例 1-2)
**题目回顾(书本 P4 页):** 10 个苹果,小 A 拿走 2 个,Uim 拿走 4 个,八尾勇拿走剩下的。求:①小 A 和 Uim 共拿走多少?②八尾勇拿走多少?(要求:输出两个数字,中间用空格隔开)。
### 1. 任务拆解与算式转化
- **第一问**:小 A 和 Uim 一共拿走的数量 $\rightarrow$ 转化为算式:`2 + 4`
- **第二问**:八尾勇拿走剩下的数量 $\rightarrow$ 转化为算式:`10 - 2 - 4`
### 2. 书写代码
我们的目标是输出,因此必须调用 `cout` 工具。因为要输出两个数字,我们就可以把两个答案拼在一起:
- **答案一**:`2 + 4`
- **分隔符**:`" "`(注意:空格是纯文本,**必须**用双引号包裹,让编译器原样输出)
- **答案二**:`10 - 2 - 4`
**代码语句:**
```C++
cout << 2 + 4 << " " << 10 - 2 - 4;
```
*最后将这行核心的逻辑填入到 `int main() { ... }` 的大括号内部即可完成代码编写。*
------
## 三、 实战:将本题代码提交至 OJ 系统 (zmwl.online)
代码在本地电脑(Dev-C++)运行成功后,我们需要将其提交给**在线评测系统(OJ,考官)进行打分**,在之后的考试或者竞赛中,都是通过将代码提交到在线评测系统中打分的方式来获取成绩。
### 步骤 1:本地验证
在正式提交前,先在本地按下 **F11** 观察 Dev-C++ 的控制台。我们通过数学运算能知道最终的答案是 `6` 与 `4`,而题目要求的格式是输出两个数字,中间用空格隔,所以最终显示的数字必须是 `6 4`,不能有多余的数据或标点。
### 步骤 2:登录
1. **打开网站**:浏览器地址栏输入 `zmwl.online` 并回车。
2. **账号登录**:输入用户名,初始密码统一为 `zmwl0000`。
3. **寻找考题**:点击上方“题库”,在列表中点击编号为 **B0001** 的题目进入详情页。
### 步骤 3:代码复制搬运 (快捷键操作)
切换回 Dev-C++ 软件窗口,执行以下键盘指令:
- **全选**:按住 **Ctrl** 键不放,按 **A** 键。(代码区背景变蓝,全部圈中)
- **复制**:按住 **Ctrl** 键不放,按 **C** 键。(代码存入电脑的剪贴板中)
### 步骤 4:递交与查收结果
1. **准备粘贴**:回到浏览器 B0001 题目页,点击“**递交**”按钮,进入一个大空白输入框。
2. **执行粘贴**:鼠标左键点击空白框确保光标闪烁,按住 **Ctrl** 键不放,按 **V** 键。
3. **提交检验**:确认代码无误后,点击系统底部的递交按钮,等待系统判定正误。
------
## 四、 C++ 的特殊数学法则:除法与取余
C++ 中的加、减、乘以及运算优先级与小学数学一致,但**除法 (`/`)** 存在差异。
### 1. 除法 (`/`) 的两种形态
| **形态** | **触发条件** | **运算规则** | **代码示例** | **输出结果** |
| -------------- | ------------------------ | ------------------------------------ | ------------------ | ------------ |
| **整数相除** | 除数和被除数**都是整数** | 小数部分被**直接截断**(不四舍五入) | `cout << 7 / 2;` | **3** |
| **浮点数相除** | 至少有一个数是**小数** | 自动开启浮点除法,保留小数部分 | `cout << 7.0 / 2;` | **3.5** |
> **总结:**
>
> 1. 整数乘/除一个浮点数,结果是浮点数。
>
> 2. 整数加上一个浮点数,结果也是浮点数。
>
> *(例:6 元和 5 元平分,算式应写为 `(6 + 5) / 2.0` 以获取精确的小数结果)*
### 2. 取余运算 (`%`)
百分号 `%` 在 C++ 中是**取模运算(求余数)**。
- **`/` 关心商**:每个人平均分到几个?
- **`%` 关心余数**:大家都分完一样多后,还剩下几个?
**例 1-3 分苹果(题目编号 B0002,书本 P6 页):**
14 个苹果均分给 4 名同学。
```C++
#include <iostream>
using namespace std;
int main(){
cout << 14 / 4 << endl; // 每人分得几个? (输出 3)
cout << (14 / 4) * 4 << endl; // 一共分出去多少?(输出 12)
cout << 14 % 4 << endl; // 放回冰箱几个(即求余数)?(输出 2)
return 0;
}
```
------
## 五、 科学表示法与数学工具调用
### 1. 科学计数法(例 1-5)
在 C++ 中处理极大或极小的数字时,经常遇到包含字母 `e` 的表示法:
- `5e6` 代表 $5 \times 10^6$(即 5000000)。
- `5e-6` 代表 $5 \times 10^{-6}$(即 0.000005)。
### 2. 物理与几何题(例 1-6 & 例 1-7)
- **火车相遇问题(例 1-6)**:纯基础运算。相对速度与相对距离公式 $\rightarrow$ `cout << (260 + 220) / (12 + 20);`
- **对角线问题(例 1-7)**:长宽为 6 和 9 的长方形,根据勾股定理求对角线 $\sqrt{6^2+9^2}$。
### 3. 引入高级数学工具:`<cmath>`
C++ 内部预制了大量可以直接使用的数学工具(函数)。要使用它们,**必须在基础代码顶部加上 `#include <cmath>`**。
- **开平方工具:`sqrt()`**
- **用法**:把需要开根号的数字丢进括号里。
- **示例**:`cout << sqrt(9);` (输出 3)
- **求次方工具:`pow(x, y)`**
- **用法**:计算 $x$ 的 $y$ 次方,数字之间用逗号隔开。
- **示例**:`cout << pow(2, 3);` (计算 $2^3$,输出 8)
**例1-7 对角线问题代码:**
```C++
#include <iostream>
#include <cmath> // 必须引入数学的工具箱
using namespace std;
int main(){
// 计算过程:先算出 6 的 2 次方加上 9 的 2 次方,再对总和进行开根号
cout << sqrt(pow(6, 2) + pow(9, 2)) << endl;
return 0;
}
```
@@ -0,0 +1,29 @@
### 参考答案
#### **一、 填空题**
1. `5 + 3`
2. `8`
3. `4`
4. `1`
5. `4.5`
#### **二、 单项选择题**
1. **(B)** (带双引号,原样输出)
2. **(A)** (没有双引号,计算结果)
3. **(C)** (整数除法,`13 / 5` 商2余3,结果为2)
4. **(C)**
5. **(C)** (至少有一个数是小数,才能触发浮点数除法)
6. **(B)** (先计算 `2+4` 得 `6`,然后输出空格,再计算 `10-2-4` 得 `4`)
7. **(B)**
#### **三、 判断题**
1. **(×)** (带双引号会原样输出 "3 * 4")
2. **(√)** (讲义中明确指出是“直接截断”,不四舍五入)
3. **(√)** (这是触发浮点数除法的条件)
4. **(×)** (讲义中强调,提交前最好先在本地验证)
5. **(√)** (这是 `%` 运算符的定义)
6. **(×)** (这是求余数,`5` 除以 `2` 商 `2` 余 `1`,结果是 `1`)
7. **(√)** (空格是文本,需要用双引号包围)
@@ -0,0 +1,89 @@
### C++ 数学运算与评测 课后习题
姓名: _________________________________________________________
#### **一、 填空题**
1. 代码 `cout << "5 + 3";` 的输出结果是:\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_。
2. 代码 `cout << 5 + 3;` 的输出结果是:\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_。
3. 在 C++ 中,两个整数相除得到的结果会\_\_\_\_\_\_\_\_\_\_\_(填写 舍弃/保留)小数。
4. `%` 符号在 C++ 中的意义是:\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_。
5. `cout << 9 / 2;` 的输出结果是:\_\_\_\_\_\_\_\_\_\_。
6. `cout << 15 / 2.0;` 的输出结果是:\_\_\_\_\_\_\_\_\_\_。
7. `cout << 9 % 2;` 的输出结果是:\_\_\_\_\_\_\_\_\_\_。
#### **二、 单项选择题**
( ) 1. 执行 `cout << "10 / 2";` 后,屏幕上会显示什么?
A. `5` B. `10 / 2` C. `10/2` D. `2`
( ) 2. 执行 `cout << 10 / 2;` 后,屏幕上会显示什么?
A. `5` B. `10 / 2` C. `10/2` D. `2`
( ) 3. 执行 `cout << 13 / 5;` 后,屏幕上会显示什么?
A. `3` B. `2.6` C. `2` D. `1`
( ) 4. 在 C++ 中,`%` 运算符代表什么?
A. 百分比 B. 除法 C. 取余数 D. 乘法
( ) 5. 如何计算 `10` 除以 `4` 才能得到带有小数的结果 `2.5`?
A. `cout << 10 / 4;`
B. `cout << 10 % 4;`
C. `cout << 10.0 / 4;`
D. `cout << "10 / 4";`
( ) 6. 对于代码代码是 `cout << 2 + 4 << " " << "10 - 2 - 4";`,它的最终输出结果是?
A. `6 10-2-4` B. `6 4` C. `64` D. `6, 4`
( ) 7. 在线评测系统 (OJ) 的主要作用是什么?
A. 帮助我们写代码的软件
B. 用来运行和评判我们代码对错的“考官”
C. 一个只能存储代码的网站
D. C++ 的一个内置功能
#### **三、 判断题**
( ) 1. `cout << "3 * 4";` 这行代码会输出 12。
( ) 2. 整数除法 `7 / 3` 的结果是 `2`,因为小数部分被直接舍弃,而不是四舍五入。
( ) 3. 在 C++ 中,只要除法算式中有一个数字是小数(如 `5.0 / 2`),计算结果就会保留小数。
( ) 4. 把代码提交到在线评测系统(OJ)之前,不需要在自己的电脑上运行测试。
( ) 5. `%` 运算符可以用来计算一个数除以另一个数的余数。
( ) 6. `cout << 5 % 2;` 的结果是 2.5。
( ) 7. 在 `cout` 语句中,如果想输出一个空格,必须写成 `cout << " ";`。
#### **四、 默写题**
请在下方的空白处,默写出基本程序的框架代码,并简要说明每一句代码的意义。
```cpp
```
@@ -0,0 +1,203 @@
# C++ 变量与常量 讲义
## 一、 为什么我们需要变量?
**例 1-8:存钱问题(参考书本 P9 1.3 节)**
Uim 的银行账户里原有 100 元。经过了以下操作:
1. 往里面存了 10 元;
2. 购物花掉了 20 元;
3. 把里面的钱全部取出。
*需求:在每次操作后输出账户余额。*
**分析:** 这个问题无法用一个简单的算式一次性解决。我们需要在每一步操作后,把算出来的**中间结果**保存下来,留给下一步继续计算。在编程中,用来**存储计算中间结果**的工具,就叫做**变量 **。
---
## 二、 计算机里的容器
在生活中,我们用**容器**(如碗、盘子)来存放物品。在计算机中,我们用**变量**来存放数据。
**做菜的比喻(参考书本 P15 第一自然段):**
* **原材料** = 我们要处理的数据(数字、字符等)。
* **容器** = 变量(用来装数据)。
* **操作台** = 计算机的内存。
**内存的物理形态:**
内存就像一排排的小抽屉,一个抽屉就是一个**字节 (Byte)**。常用 1 字节 = 8 位(bit),每一位只能存 0 或 1。位是最小的数据单位。不同的数据类型,占用的字节数可能不同。
---
## 三、 声明变量:挑个柜子写上自己的名字
如果你想占用内存里的“抽屉”,必须先向计算机**声明**,否则别人(其他程序)可能也会去用这个抽屉,导致数据大乱斗。
**声明变量的格式:** `数据类型 + 变量名;`
### 1. 数据类型(选多大的柜子)
*参考书本 P16 表 2-1*
做菜时,装高汤不能用菜篓子。编程时,不同的数据也要选不同类型的柜子:
| 分类 | 类型名称 | 关键字 | 说明 / 存储范围 |
| ----------------- | ------------ | ----------- | ----------------------------------------------------- |
| **整型 (整数)** | 短整型 | `short` | -32768 ~ 32767 |
| | **基本整型** | `int` | **-2147483648 ~ 2147483647 (最常用)** |
| | 长整型 | `long long` | 存非常大的整数 |
| **浮点型 (小数)** | 单精度型 | `float` | 通常约有 6~7 位十进制有效数字 |
| | **双精度型** | `double` | **通常约有 15~16 位十进制有效数字 (最常用)** |
| **字符型** | 字符型 | `char` | 单个字符,例如 `A`, `!`, `?`, `b` 等 |
| **布尔型** | 布尔型 | `bool` | 只有真 (`true` 正确的) 和假 (`false` 错误的) 两种状态 |
### 2. 变量名命名规则(贴什么名字)
*参考书本 P11 第三自然段*
变量名必须做到**见名知意**(具有自解释性,比如用 `balance` 表示余额,用 `age` 表示年龄)。
* **强制法则:** 只能由字母、数字和下划线(`_`)组成。
* **数字禁忌:** 数字**绝对不能**出现在第一个位置上!(`num1` 正确,`1num` 错误)。
* **避开关键字:** 不能使用 C++ 自带的指令词(如 `const`, `return`, `int`)。
* **不可重复:** 同一作用域(范围)内,名字不能重复,你不能命名两个柜子都叫做`pingguo`。
* **语言限制:** 英文不好可以用拼音(如 `pingguo`),但**严禁使用中文**!
---
## 四、 赋值与初始化:往柜子里装东西
声明了柜子后,我们如何把数据放进去呢?这就需要用到**赋值符 (`=`)**。
**注意:** 在 C++ 中,`=` 不叫“等于”,它叫“赋值”。它的动作是:**将等号右边的数值,粘贴(覆盖)给左边的变量。**
### 1. 变量的初始化
初始化指的是**在定义变量的同时就给它赋值**,避免柜子里残留以前的垃圾数据。比方说之前有人往这个柜子里放了化学试剂,如果你直接往里放食材的话,肯定就会有危险!
```cpp
int num; // 未初始化,如果直接去读取里面的数据,会读取到随机的垃圾数字
int a = 10; // 安全:定义整数 a,并初始化为 10
double b = 3.123; // 安全:定义小数 b,并初始化为 3.123
```
### 2. 连续定义与赋值(补充知识,请记在书本 P15 页)
如果我们需要同时申请多个同类型的变量,可以用逗号 `,` 连写:
```cpp
int a, b; // 同时申请两个空柜子 a 和 b
int x = 10, y = 20; // 同时申请两个柜子 x 和 y 并装入初始值
```
### 3. 赋值的覆盖特性与累加
对变量的赋值是一个**覆盖**过程。新数据装进去,旧数据就彻底消失了。
**变量的自我更新:**
```cpp
int num1 = 1;
num1 = num1 + 7; // 此时 num1 变成了 8
```
**简单写法(书本 P10 第六自然段):**
为了让代码更简洁,程序员发明了简写形式:
* `num1 += 7;` 完全等同于 `num1 = num1 + 7;`
* `a += b;` 完全等同于 `a = a + b;`
---
## 五、 常量
*例 1-9(P10 页):求圆的周长面积和体积*
在计算圆周率 $\pi$ 时,我们需要一个容器来装 `3.14159`。但是,圆周率是一个自然界的真理,在程序运行期间我们**不允许它被意外修改**。
这时,我们需要使用关键字 **`const`** 来声明一个**常量**。因为在实际的工程中,一个软件是由很多程序员开发的,定义常量就能让别的程序员知道我定义的这个变量不能修改,改了会出问题。
**常量的规则:**
1. **必须在声明时就给它初始化**,否则编译直接报错。
2. **终生不可更改**,不能再通过赋值符 `=` 修改它。
```cpp
// 声明一个双精度浮点数常量 PI
const double PI = 3.14159;
// PI = 3.0; // 如果写这行代码,计算机会报错!
```
---
## 六、 变量与字符串的区别
在结合 `cin` 和 `cout` 使用时,初学者最容易在输出格式上栽跟头。请大家仔细观察以下四种写法的输出区别(假设前面已经运行了 `int x = 5, y = 6;`):
| 代码写法 | 控制台实际输出 | 解析 |
| -------------------------- | -------------- | ----------------------------------------- |
| `cout << x + y;` | **11** | 没有双引号,执行后台数学计算。 |
| `cout << "x+y";` | **x+y** | 有双引号,纯文本复读机,原样输出。 |
| `cout << x << "+" << y;` | **5+6** | 变量(抽出数字)与纯文本(加号)的拼接。 |
| `cout << "x+y=" << x + y;` | **x+y=11** | 前面输出纯文本提示音 + 后面输出计算结果! |
---
## 六、 综合实战演练
**练习:理解并练习书本例 1-10(P11 页),猴子吃桃问题**
**题目描述:** 猴子第一天吃了所有桃子的一半又多吃了一个;第二天又吃了剩下的一半多一个;第三天同理。第四天发现只剩下一个了。问原来有几个?
**解题思维(逆向):**
正向推导较为复杂,但如果运用倒推法,问题就能较为简单的解决。
* 第四天:剩下 1 个。
* 第三天:把第四天的数量加上它贪嘴多吃的那 1 个(吐出来),然后再翻倍。
* 第二天、第一天:重复同样的动作。
*我们可以利用变量来持续覆盖、更新当前的桃子数。*
**代码思路:**
- **第一步:`int peach = 1;`**
- 计算机在内存里找了一个整数大小的空抽屉,在抽屉外面贴上了一个标签,名字叫 `peach`(桃子)。然后,把第四天最后剩下的数字 **1**,放进了抽屉里。 *【此时 peach =1】*
- **第二步:`peach = (peach + 1) * 2;`**
- 这行代码在数学上是不成立的,但在编程里,我们要**先看等号右边,再看等号左边**。 计算机先执行右边的计算:它拉开名为 `peach` 的抽屉,发现里面是 1。于是它在后台的大脑里计算 `(1 + 1) * 2`,算出来的结果是 4。 接着,执行等号(赋值)的动作:计算机地把抽屉里原来的 1 扔掉,把刚刚算出来的 **4** 装了进去!这代表第三天开始时有 4 个桃子。 *【此时 peach = 4】*
- **第三步:`peach = (peach + 1) * 2;`**
- 代码跟第二步长得一模一样,但结果完全不同! 计算机再次拉开 `peach` 抽屉,此时里面装的已经是 4 了。计算机在大脑里计算 `(4 + 1) * 2`,得到 10。 然后再次执行覆盖:把 4 扔掉,把 **10** 装进抽屉。这代表第二天开始时有 10 个桃子。 *【此时 peach = 10】*
- **第四步:`peach = (peach + 1) * 2;`**
- 最后一次倒推。拉开抽屉拿出 10,计算 `(10 + 1) * 2`,得到 22。 扔掉 10,把 **22** 放进抽屉里。这代表第一天小猴子刚买回来的桃子总数。 *【此时 peach = 22】*
- **第五步:`cout << peach;`**
- 任务完成!我们 `cout` 输出 `peach` 抽屉,把它现在装的最终数字展示在屏幕上。屏幕上将打印出:**22**。
**我们全程只用了一个名叫 `peach` 的抽屉,但通过反复使用 `=`(赋值符),我们让抽屉里的数据根据题意进行了变化和覆盖。**
**程序代码:**
```cpp
#include <iostream>
using namespace std;
int main() {
// 1. 申请抽屉并放入第四天的数量
int peach = 1;
// 2. 倒推第三天的数量
peach = (peach + 1) * 2;
// 3. 倒推第二天的数量
peach = (peach + 1) * 2;
// 4. 倒推第一天(刚买回来时)的数量
peach = (peach + 1) * 2;
// 5. 展示抽屉里最终的结果
cout << peach << endl;
return 0;
}
```
@@ -0,0 +1,29 @@
### 参考答案
#### **一、 填空题**
1. 变量
2. `int`
3. `double`
4. 赋值
5. `100`
6. `+=`
7. `const`
#### **二、 单项选择题**
1. C (变量名不能以数字开头)
2. C
3. B
4. B
5. C
6. D (`count = 1;` 是直接赋值,不是在原有基础上加1)
#### **三、 判断题**
1. √
2. × (在C++中 `=` 是赋值,`==` 才是判断相等)
3. × (常量必须在定义时就初始化)
4. × (未初始化的变量里存放的是不确定的“垃圾值”)
5. √
6. √
@@ -0,0 +1,73 @@
### C++ 变量与常量 课后习题
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 在编程中,我们用来存放数据的“容器”,叫做 \_\_\_\_\_\_\_\_。
2. 如果想定义一个可以存放整数的变量 `age`,代码应该写成:\_\_\_\_\_\_\_\_ `age;`
3. 如果想定义一个可以存放小数(例如 `1.75`)的变量 `height`,最常用的数据类型关键字是 \_\_\_\_\_\_\_\_。
4. 在 C++ 中,符号 `=` 不叫“等于”,而叫做 \_\_\_\_\_\_\_\_符。
5. 阅读代码 `int score = 90; score = 100;`,执行后 `score` 变量中最终存放的值是 \_\_\_\_\_\_\_\_。
6. 为了让代码 `num = num + 5;` 更简洁,我们可以使用简写形式:`num` \_\_\_\_\_\_\_\_ `5;`
7. 如果想定义一个值固定为 `3.14` 且不允许被修改的常量 `PI`,需要在定义的最前面加上关键字 \_\_\_\_\_\_\_\_。
#### **二、 单项选择题**
( ) 1. 下列哪个是**不合法**的变量名?
A. `my_score`
B. `num1`
C. `1st_place`
D. `age`
( ) 2. 我们用什么数据类型来存放单个字符,比如 `'A'`?
A. `int`
B. `double`
C. `char`
D. `bool`
( ) 3. `int num = 10;` 这行代码中的 `=` 是什么意思?
A. 判断 `num` 是否等于 `10`
B. 将 `10` 这个数值放进 `num` 变量里
C. 声明 `num` 是一个常量
D. 将 `num` 的值加上 `10`
( ) 4. `const` 关键字定义出来的常量,有什么特点?
A. 它的值在程序运行中可以被多次修改
B. 它必须在定义的时候就给定一个初始值,并且之后不能再修改
C. 它只能用来存放整数
D. 它不需要指定数据类型
( ) 5. 阅读以下代码:
```cpp
int apples = 10;
apples = apples - 3;
cout << apples;
```
​ 程序的输出结果是?
​ A. `10`
​ B. `3`
​ C. `7`
​ D. `13`
( ) 6. 哪条语句和 `count = count + 1;` 是**不等价**的?
A. `count += 1;`
B. `count++;`
C. `++count;`
D. `count = 1;`
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. C++ 的变量名可以由字母、数字、下划线组成,但不能以数字开头。
( ) 2. 在数学中 `=` 表示等于,在 C++ 中也完全一样,表示判断两边是否相等。
( ) 3. 定义一个常量时可以不给它初始值,等需要的时候再赋值。
( ) 4. `int num;` 这样定义出来的变量,里面存放的数值一定是 `0`。
( ) 5. 当我们写 `score = 95;` 时,如果 `score` 变量里原来存了 `80`,那么 `80` 会被 `95` 覆盖掉。
( ) 6. 猴子吃桃问题中,`peach = (peach + 1) * 2;` 这行代码利用了变量值可以被反复更新覆盖的特性。
@@ -0,0 +1,82 @@
# C++ 输入 (`cin`) 讲义
## 一、 程序只能算固定数据吗?
我们之前写的代码,所有的数字都是直接写在程序里的,比如 `int a = 5; int b = 3;`。这种写法把**数据写死**了。
大家思考一下,如果用户明天想算 `100 + 200` 怎么办?难道每次都要让程序员打开代码,改完数字再重新算一次吗?这显然是不现实的!
一个真正有价值的程序,应该是**智能的**——它能够随时接收用户提供的新数据,并自动计算出结果。今天,我们就要学习如何让程序接收用户提供的数据。
## 二、 认识 `cin`:控制台的数据接收员
为了让程序接收用户的键盘输入,C++ 提供了一个强大的工具:**`cin`**。
* **含义**:**`cin`** 是 Console Input(控制台输入)的缩写, c 对应着 Console, in 对应着 input。
* **作用**:**让用户在黑框框里==用键盘==输入数据**,然后程序会自动把这个数据==存入==我们提前准备好的==变量==中。
* **`cin` 使用 >> 运算符跟上准备好的变量来接收用户输入**。
### 1. `cin` 的运作过程
我们来看看 `cin` 是如何与我们之前学习的变量配合的:
```cpp
int num;
cin >> num;
cout << num;
```
1. **`int num;`** $\rightarrow$ 程序向内存申请了一个整数类型的空柜子,并在外面贴上名字标签 `num`。
2. **`cin >> num;`** $\rightarrow$ 程序==暂停运行==,等待用户操作。用户在键盘上敲下数字 `7` 并按下回车。**`>>`(称为提取符)**就像传送带一样,把键盘上的 `7` 抓过来,放进了名为 `num` 的抽屉里。
3. **`cout << num;`** $\rightarrow$ 获取名为 `num` 抽屉里的数据,并将其展示在屏幕上(输出 7)。
### 2. 语法与规则
输入单个数据:
**==语法格式==:** `cin >> 变量名;`
如果你想一次性让用户输入多个数据,那就需要准备多个变量,然后把传送带连起来用:
**==语法格式==:** `cin >> 变量名 >> 变量名 >> 变量名;` (可以通过 `>> 变量名` 的格式继续扩展)
```cpp
int a, b, c;
cin >> a; // 接收用户输入的一个整数到变量 a 中
cin >> b >> c; // 依次接收用户输入的两个整数到变量 b, c 中
```
> **输入规则:**
> 当使用 `cin >> a >> b;` 时,用户在黑框框里输入数据,**必须使用“空格”或者“回车”来隔开两个数字**(比如输入 `5 10`)。`cin` 它只要一遇到空格或回车,就知道第一个数据输入结束了,准备把下一个数据放进第二个抽屉里。
---
## 三、 解决题目中要求有输入的题目
现在我们拥有了所有的基础工具(变量、加减乘除、输入、输出)。接下来,我们要做一道完整的带有输入程序题。务必要记住:
==**"输入 -> 处理 -> 输出" 是完成程序题目的三个基本步骤。**==
**题目描述:请你实现输入两个整数,输出这两个整数的差。**
*(样例输入:2 3 $\rightarrow$ 样例输出:-1;样例输入:10 2 $\rightarrow$ 样例输出:8)*
**分析思路**:
1. **提取有效信息**:题目需要什么?(**==输入两个数==** $\rightarrow$ 相减 $\rightarrow$ 输出结果)。
2. **整理思路**:
* 准备两个变量(空抽屉)装输入的数。
* 用 `cin` 把用户敲的字放进抽屉。
* 处理数据:拿出来相减,结果放进第三个抽屉。
* 用 `cout` 展示第三个抽屉。
3. **将思路一步步翻译为C++ 代码**:
```cpp
int num1, num2; // 准备抽屉
cin >> num1 >> num2; // 接收输入
int diff = num1 - num2; // 计算并存入新抽屉
cout << diff; // 输出结果
```
4. **编译运行**:按下 F11。如果有报错,不要慌,检查是不是漏了分号,或者 `>>` 和 `<<` 的方向写反了。
5. **输入测试数据,验证结果**:在弹出的黑框里输入测试用例 `2 3`,看看结果是不是 `-1`。
6. **修正错误**:如果输出不对(比如输出了 5),说明语法没问题,但是**逻辑错了**。回去检查代码,是不是把减号写成加号了?修改后重新测试,直到完全正确为止。
@@ -0,0 +1,37 @@
### **C++ `cin` 输入 课后习题 - 答案**
#### **一、 填空题**
1. Console Input (或 控制台输入)
2. `>>` , 提取运算符
3. 输入 , 处理
4. 暂停 (或 阻塞)
5. 空格 , 回车
6. 定义
7. 放进
#### **二、 单项选择题**
1. **D**. `cin` 的核心就是获取键盘输入。
2. **C**. `cin` 使用提取运算符 `>>`,方向是从 `cin` 流向变量。
3. **C**. 链式输入使用 `>>` 连接多个变量。
4. **C**. `cin` 默认使用空白字符(空格、回车等)作为分隔符,逗号不是空白字符,无法正确分割。
5. **B**. `cin` 使得程序可以处理运行时才给定的数据,变得通用和灵活。
6. **B**. 程序先定义变量,然后通过 `cin` 获取值,再进行计算和输出,是完整的“输入-处理-输出”流程。
7. **C**. 两种写法在功能上完全相同,都能读取两个变量,但链式写法更为常用和简洁。
8. **B**. 程序能运行但结果不对,说明语法正确(不是CE),但计算或处理的思路(逻辑)错了。
9. **B**. `cin >> a;` 只会读取一个整数。它读到 `5`,遇到空格,就认为第一个数的输入结束了,所以 `a` 的值是 `5`。`6` 会留在输入缓冲区中等待下一次 `cin` 读取。
10. **C**. `int a, b;` 定义变量,`cin >> a >> b;` 使程序暂停,等待输入。
#### **三、 判断题**
1. × ( `c` 代表 "Console" (控制台)。)
2. √ (必须先准备好“抽屉”,才能往里面放东西。)
3. √ (`cin` 是输入,`cout` 是输出,方向和作用都相反。)
4. √ (`cin` 在读取数据前会跳过所有空白字符。)
5. × (回车也是一种空白分隔符,所以可以一个数字一行,分两行输入。)
6. √ (这是讲义中强调的程序设计基本结构。)
7. √ (这正是引入 `cin` 的核心价值。)
8. × (讲义中的6步法强调,应该先整理思路,再翻译成代码。)
9. √ (提取运算符 `>>` 的作用就是将读取到的值赋给变量。)
10. × ( `cout` 应该使用插入运算符 `<<`,正确的写法是 `cout << "请输入一个数字";`。)
@@ -0,0 +1,95 @@
### **C++ `cin` 输入 课后习题**
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 在C++中,`cin` 是 \_\_\_\_\_\_\_\_\_\_\_\_\_\_ 的英文缩写,它的主要作用是接收用户的键盘输入。
2. `cin` 使用的运算符是 \_\_\_\_\_\_\_\_\_\_,这个运算符被称为 \_\_\_\_\_\_\_\_\_\_,它的箭头方向形象地表示数据从键盘流向变量。
3. 一个有输入功能的程序,其基本流程可以分为三个步骤:\_\_\_\_\_\_\_\_\_\_、处理和输出。
4. 当程序执行到 `cin >> a;` 语句时,会\_\_\_\_\_\_\_\_\_\_运行,并等待用户输入数据。
5. `cin` 通过空白字符来区分多个输入数据,最常见的两种空白字符是\_\_\_\_\_\_\_\_\_\_和\_\_\_\_\_\_\_\_\_\_。
6. 在使用 `cin >> num;` 之前,必须先\_\_\_\_\_\_\_\_\_\_变量 `num`,为输入的数据准备好存放空间。
7. 讲义中用生活中的例子做比喻,把变量比作“抽屉”,那么 `cin` 的作用就是把外面的数据\_\_\_\_\_\_\_\_\_\_“抽屉”里。
#### **二、 单项选择题**
( &nbsp; ) 1. 在C++中,`cin` 的核心功能是什么?
A. 在屏幕上打印文字和符号
B. 让程序暂停指定的时间
C. 定义新的变量类型
D. 从键盘读取数据并存入变量
( &nbsp; ) 2. 下面哪条语句可以正确地从键盘读取一个整数并存放到变量 `score` 中?
A. `cin << score;`
B. `cout >> score;`
C. `cin >> score;`
D. `cout << score;`
( &nbsp; ) 3. 如果要用一条语句连续读取用户的两个整数,分别存入变量 `width` 和 `height`,应该怎么写?
A. `cin >> width, height;`
B. `cin >> width; height;`
C. `cin >> width >> height;`
D. `cin << width << height;`
( &nbsp; ) 4. 对于语句 `cin >> x >> y;`,用户在键盘上**不正确**的输入方式是?
A. 输入 `10`,按空格键,再输入 `20`,按回车键
B. 输入 `10`,按回车键,再输入 `20`,按回车键
C. 输入 `10,20`,按回车键
D. 输入 `10`,按多个空格键,再输入 `20`,按回车键
( &nbsp; ) 5. `cin` 这个工具主要解决了程序中的什么根本性问题?
A. 计算速度太慢的问题
B. 只能计算和处理“写死”在代码里的固定数据的问题
C. 无法输出中文的问题
D. 代码必须从 `main` 函数开始的问题
( &nbsp; ) 6. 阅读以下代码,它的功能是什么?
```cpp
int base, height;
cin >> base >> height;
int area = base * height / 2;
cout << area;
```
​ A. 计算一个固定三角形的面积
​ B. 输入两个整数作为底和高,然后计算并输出三角形的面积
​ C. 只输入两个整数,不进行任何计算
​ D. 这段代码有语法错误,无法运行
( &nbsp; ) 7. `cin >> a; cin >> b;` 和 `cin >> a >> b;` 这两种写法是什么关系?
A. 前者正确,后者错误
B. 两者功能完全不同
C. 两者功能等价,但后者更简洁
D. 后者正确,前者错误
( &nbsp; ) 8. 编程的6个步骤中,如果在“输入测试数据,验证结果”时发现输出与预期不符,这通常说明发生了什么错误?
A. 语法错误
B. 逻辑错误
C. 编译错误
D. 硬件故障
( &nbsp; ) 9. 假如程序是 `int a; cin >> a;`,而用户输入了 `5 6` 然后按回车,那么变量 `a` 中存储的值是多少?
A. 56
B. 5
C. 6
D. 11
( &nbsp; ) 10. `int a, b; cin >> a >> b;` 这两行代码执行时,程序的行为是?
A. 立刻计算 a+b 的值
B. 在屏幕上输出 a 和 b
C. 定义了两个变量,然后暂停等待用户输入两个由空白字符隔开的数据
D. 定义了两个变量,然后程序直接结束
#### **三、 判断题 (对的打“√”,错的打“×”)**
1. `cin` 的 `c` 代表 "C++",`in` 代表 "input"。 ( &nbsp; )
2. 必须先定义变量,然后才能使用 `cin` 把数据存入这个变量。 ( &nbsp; )
3. `cin` 使用的运算符是 `>>`,`cout` 使用的运算符是 `<<`,两者方向相反,作用也相反。 ( &nbsp; )
4. `cin` 会自动忽略输入数据前多余的空格、Tab键或回车。 ( &nbsp; )
5. 如果写 `cin >> x >> y;`,用户必须在同一行内输入两个数字,不能换行输入。 ( &nbsp; )
6. “输入 -> 处理 -> 输出”是解决编程题目的三个基本步骤。 ( &nbsp; )
7. 一个好的程序应该通过 `cin` 接收用户的任意合法输入,而不是每次都让用户去修改源代码。 ( &nbsp; )
8. 在分析一个编程问题时,正确的做法是先动手写代码,再边写边想思路。 ( &nbsp; )
9. `cin >> a;` 这行代码执行后,变量 `a` 的值就变成了用户刚刚从键盘输入的值。 ( &nbsp; )
10. `cout >> "请输入一个数字";` 这行代码是正确的。 ( &nbsp; )
@@ -0,0 +1,143 @@
### **C++ 字符类型 `char` 与转义字符 讲义**
#### **第一部分:字符类型 `char` (存储单个字符的盒子)**
##### **1. 问题引入:除了数字,程序还能处理什么?**
在之前的学习中,我们已经学会了用 `int` 存储整数,用 `double` 存储小数。但我们的世界是由丰富多彩的文字和符号构成的。比如,我们要编写一个程序,实现一个简易计算器,用户会输入 `7 * 2` 这样的算式。
* `7` 和 `2`,我们可以用 `int` 类型的变量来存储。
* 但是中间的那个乘号 `*` 呢?它不是一个数字,我们该用什么类型的“盒子”来装它呢?
这时候,`char` 类型就登场了。
##### **2. 什么是 `char` 类型?**
`char` 是 "character" (翻译:字符) 的缩写。它是一种专门用来存放**单个字符**的数据类型。
你可以把它想象成键盘上的**任意一个按键**,它可以是:
* 一个英文字母 (大写或小写): `'A'`, `'b'`, `'Z'`
* 一个数字字符: `'0'`, `'1'`, `'9'` (注意:这和数字 `0, 1, 9` 是不同的)
* 一个标点或符号: `','`, `'.'`, `'?'`, `'+'`, `'*'`
* 一个空格: `' '`
##### **3. 如何在 C++ 中使用 `char`?**
`char` 的使用方式和 `int` 基本一致,但有一个**极其重要**的区别:
> **在为 `char` 变量赋值或进行比较时,字符值必须被一对单引号 `' '` 包围。**
* **正确的写法**:
```cpp
char c1 = 'A'; // 正确!用单引号包围
char c2 = '*';
char c3 = ' '; // 空格也是一个字符
```
* **错误的写法**:
```cpp
char c4 = A; // 错误!编译器会认为 A 是一个变量名,而不是字符'A'
char c5 = "A"; // 错误!双引号 "" 是用来表示字符串的,不能用于单个字符
```
##### **4. 实战演练:简易计算器**
* **问题**:输入两个整数和一个乘号 `*`,输出这两个整数的乘积。
* *输入样例*: `7 * 2`
* *输出样例*: `14`
* **逻辑分析**:
1. **准备“盒子”**:我们需要两个盒子装整数,一个盒子装中间的 `*` 号。
* `int num1, num2;`
* `char op;` (`op` 是 operator/操作符的英文缩写)
2. **接收“原料”**:C++ 的 `cin` 非常智能,它会根据你变量的顺序和类型,自动读取并分割输入。
* `cin >> num1 >> op >> num2;` 当用户输入 `7 * 2` 时,`cin` 会把 `7` 存入 `num1`,把 `*` 存入 `op`,把 `2` 存入 `num2`。
3. **加工计算**:`int result = num1 * num2;`
4. **交付产品**:`cout << result;`
* **完整代码**:
```cpp
#include <iostream>
using namespace std;
int main() {
int num1, num2;
char op; // 准备一个char类型的盒子来装运算符
cout << "请输入一个乘法表达式 (如 7 * 2): ";
cin >> num1 >> op >> num2;
int result = num1 * num2;
cout << "计算结果是: " << result << endl;
return 0;
}
```
---
#### **第二部分:转义字符**
##### **1. 观察代码**
我们已经知道,放在双引号 `""` 里的内容,通常会被原样输出。但请观察以下代码:
```cpp
cout << "hello\nworld";
```
按照我们的直觉,屏幕上应该会打印出 `hello\nworld`。但实际运行结果却是:
```
hello
world
```
我们发现:
1. 字符 `\` 和 `n` 在屏幕上“神秘地消失”了。
2. `world` 被打印到了新的一行。
**原因解析**:这并不是 Bug,而是因为 `\n` 是 C++ 语法中的一个**转义字符。
##### **2. 转义字符的原理与应用**
* **什么是转义字符?**
转义字符是一种特殊的符号组合,它以反斜杠 `\` 开头,用于在代码中表示一些**无法直接打印的控制功能**(如换行、制表符)或**具有特殊语法含义的字符本身**(如双引号、反斜杠自己)。
你可以把反斜杠 `\` 想象成一个“**魔法棒**”。当它出现在一个字符前面时,它会**改变**后面那个字符的本来面目,赋予它一个特殊的魔法效果。
* **常用转义字符对照表**
| 转义符 | 术语 | 实际作用与说明 |
| -------- | -------------- | ------------------------------------------------------------ |
| **`\n`** | **换行符** | 效果:**换行**。将光标移动到下一行的行首。 |
| **`\t`** | **水平制表符** | 效果:**对齐**。将光标向前跳跃一个制表位的距离(通常相当于几个空格)。 |
| **`\\`** | **反斜杠字符** | 效果:让它变回一个普通的反斜杠字符并打印出来,即输出一个反斜杠本身。 |
| **`\"`** | **双引号字符** | 效果:让它变回一个普通的双引号字符并打印出来,即输出一个双引号本身。 |
| **`\'`** | **单引号字符** | 用于打印单引号本身。 |
##### **3. 综合应用实例**
* **任务目标**:在屏幕上输出下面这句话:
> `小明说:"what is \n"`
* **逻辑推演**:
1. **基础结构**:首先,整句话是一个字符串需要通过 `cout` 输出,那么需要用一对双引号包围。
`cout << "小明说:"what is \n"";` <-- 如果这样直接写,就全部乱套了!
2. **解决内部双引号冲突**:目标文本中的双引号 `"` 会让编译器误以为字符串提前结束了。我们需要用 `\"` 来告诉编译器:这个双引号只是一个普通字符,我想要输出它本身,不是字符串的边界。
3. **解决 `\` 的冲突**:我们想输出 `\` 本身。于是我们需要用 `\\` 来打印出那个反斜杠本身。
`cout << "小明说:\"what is \\n\"";` <-- 解决!
* **C++ 实现代码**:
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "小明说:\"what is \\n\"" << endl;
return 0;
}
```
#### **总结**
* **`char`** 是用来存储**单个**字符的“小盒子”,它的值必须用**单引号`''`**括起来。
* **转义字符**以反斜杠 `\` 开头,它能赋予紧随其后的字符一个**特殊的功能**或让其**变回普通字符**。
* `\n` (换行) 和 `\t` (制表) 是最常用的功能性转义字符。
* 当你需要在字符串中打印 `"` 或 `\` 本身时,必须在它们前面再加上一个 `\`,即使用 `\"` 和 `\\`。
@@ -0,0 +1,142 @@
# C++字符与 ASCII 码 讲义
## 1. 概念
在计算机的世界里,一切皆是 0 和 1(二进制)。计算机天生只认识数字,不认识字母、标点符号。
为了让计算机能显示和处理文字,人类制定了一套规则:**给每一个字符指定一个唯一的数字编号**。
* **字符 (Character)**:我们在屏幕上看到的符号(如 `'A'`, `'@'`, `'1'`)。
* **ASCII 码**:一种最基础的编码标准,规定了字符和数字之间的对应关系。
---
## 2. C++ 中的字符类型 (`char`)
在 C++ 中,我们学习了如何储存一个整数或者小数,但是字符是使用 `char` 关键字来定义并储存字符变量的。
### 2.1 基本语法
* **书写方式**:必须使用**单引号** `' '` 括起来。
* **本质**:`char` 类型在内存中实际上存储的是一个**整数**(即该字符对应的 ASCII 码值)。
```cpp
char letter = 'A'; // 正确
char numChar = '5'; // 正确,这是字符5,不是数字5
// char error = "A"; // 错误!双引号代表字符串
```
### 2.2 使用 `char`
**题目描述:请你实现普通计算器的功能,输入两个整数和一个*,输出这两个整数的乘积。**
**样例输入: 7 * 2**
**样例输出: 14**
---
**逻辑解析:**
- **定义容器**:我们需要两个 `int` 类型的变量来存放参与运算的数字,**以及一个 `char` 类型的变量来存放中间的运算符 `*`**,我们就可以定义:`int num1, num2; char ch; `
- **获取数据**:接受用户输入,将三个值分别放入三个变量中,并且按照顺序读取 `cin >> num1 >> ch >> num2;`
- **读取 `num1`**:读取的第一个是整数变量,于是`cin` 接着期待一个整数,例如它读取了 `7`,接着遇到了空格。所以它会**自动结束读取**,将 `7` 存入 `num1` 中。
- **读取 `op`**:接着是一个字符类型变量,于是`cin` 接着期待一个字符。拿到了字符 `*`,并存入字符变量 `op` 中。
- **读取 `num2`**:最后,`cin` 再次期待一个整数。它跳过空格,读取到数字 `2`,存入 `num2`。
- **计算结果:**`num1` 与 `num2` 已经存入了输入的两个整数,最后仅需按照题目要求输出这两个整数的乘积即可:`cout << num1 * num2;`
## 3. 什么是 ASCII 码?
**全称**:American Standard Code for Information Interchange(美国信息交换标准代码)。
因为计算机却只认识数字,不认识字符,那计算机怎么存储“Hello”这个单词呢?
为了解决这个问题,人类制定了一张**强制转换表**,这就是 **ASCII** **码表**。 它规定了每一个符号对应一个特定的整数。字符在计算机内部,本质上就是一个整数。
它是一张“对照表”,规定了 128 个字符对应的数字。
- 当你按下键盘上的 `A`。
- 计算机查表:哦,`A` 对应的是 `65`。
- 计算机存储:在内存里存下 `65` 。
- 当你 `cout` 时:计算机读取 `65`,查表发现是 `A`,然后就输出一个 `A` 在屏幕上
### 3.1 ASCII 码表的构成
1. **0 - 31 (控制字符)**:不可打印,用于控制外围设备,这部分**了解**即可,一般不会用到。
* 例如在我们键盘上的:换行、回车、响铃、退格。
2. **32 - 126 (可打印字符)**:键盘上能打出来的符号。
* 包括:空格、标点、数字、大写字母、小写字母。**这部分需要知道掌握**。
3. **127 (Delete)**:删除控制符,**了解**即可。
---
## 4. 必须记住的关键 ASCII 值
你不需要背诵整个表,但以下几个数值应当记住,因为它们常用于编程逻辑中。
| 字符类别 | 起始字符 | ASCII 值 | 结束字符 | ASCII 值 | 备注 |
| :----------- | :------: | :------: | :------: | :------: | :-------------------------------- |
| **数字** | `'0'` | **48** | `'9'` | **57** | 注意:`'0'` 和 `0` 不是同一个东西 |
| **大写字母** | `'A'` | **65** | `'Z'` | **90** | |
| **小写字母** | `'a'` | **97** | `'z'` | **122** | |
### 规律总结:
1. 数字、大写字母、小写字母都是**连续**排列的。
2. **大小写转换规律**:小写字母的 ASCII 值比对应的大写字母大 **32**。
* `'a' - 'A' = 32`
* `'A' + 32 = 'a'`
---
## 5. 字符与整数的互换 (C++ 的自动类型转换特性)
在 C++ 中,其实`char` 和 `int` 可以自由转换和运算。
1. 源类型向目标类型转换时。如果目标类型能够容纳源类型的值,且转换是安全的,编译器就会自动完成。
- **例如**:`int` 赋给 `double` 是安全的(`int` 转换为 `double`)。
- **但请注意:** `double` 赋给 `int` 时,虽然编译器会尝试转换(截断小数部分),但这通常被认为是**不安全的**,因为它会丢失精度。在严格的编译环境下可能会有警告,甚至在某些情况下需要显式转换(后续会讲解强制类型转化)。
- **整型和浮点型混合运算时,整型会向浮点型转换以避免数据丢失**。**核心原则是** “向高精度、大范围类型转换”,以避免数据丢失。例如,`int` 和 `double` 相加时,`int` 会被转换为 `double`。
简单来说,C++的自动类型转换是尽力让你在不同类型之间进行操作时,得到一个合理的结果,并且尽量不丢失数据。
### 5.1 查看字符的 ASCII 码
**使用 `int` 类型的变量来接住 `char` 类型的值。**
```cpp
#include <iostream>
using namespace std;
int main() {
char c = 'A';
cout << "字符: " << c << endl; // 输出 A
int num = c;
cout << "ASCII值: " << num << endl; // 输出 65
return 0;
}
```
### 5.2 字符运算 (算术操作)
**因为字符本质是整数(通过ASCII码来进行对照),所以可以进行加减乘除。**
==**案例 1:大小写转换(重要,必须掌握)**==
利用小写与小写字符中的差值为 32 进行转化。
```cpp
char upper = 'B';
char lower = upper + 32; // 66 + 32 = 98 ('b')
cout << lower << endl; // 输出 b
```
==**案例 2:数字字符 转 整数数字**==
这是算法题中非常常见的操作。因为数字字符在ASCII码里是连续的,所以任意一个 字符数字 和 字符0 之间的差值就是字符数字对应的整数数字。
例如`'7' - '0' = 7, '4' - '0' = 4, '0' - '0' = 0`。
```cpp
char c_num = '8';
int i_num = c_num - '0'; // '8'(56) - '0'(48) = 8
cout << i_num << endl; // 输出数字 8
```
@@ -0,0 +1,168 @@
## **C++ 类型转换 讲义**
### 1. 引言
* **字符的储存**
字符 `'A'` 在计算机底层又是用哪个数字存储的呢?计算机的本质是处理数字,我们看到的所有文本、字符,在它眼中都是一个个特定的数字。最著名的编码表就是 **ASCII** (美国信息交换标准代码),在书本P17页有部分的ASCII编码表可做参考。
### 2. 核心思想
**自动类型转换**
我们有一个整数 `int number = 65;`。我们想得到它对应的字符。一个想法是:C++ 那么聪明,我直接把 `int` 赋值给 `char` 会怎么样?其实在 C++ 中,`char` 和 `int` 可以自由转换和运算。
```cpp
int number = 65;
char c = number; // C++ 允许这样做,它会自动转换
cout << c; // 输出 'A'
```
这种由编译器自动完成的转换,叫做 **自动类型转化(隐式转换)**。在很多简单场景下,它完全没有问题!但是,这种有时会隐藏一些问题。如果代码很复杂,**其他人读到 `char c = 65;` 时,可能会疑惑:这是一个笔误吗?还是故意这么写的?为什么两边的数据类型不一样?**
因为正常情况下,**赋值符号的右边的数据类型**应该和**左边的变量数据类型相匹配**。
但是有些情况也可以不一致。源类型向目标类型转换时(例子中 `char c = number;` 源类型就是 `int` , 目标类型是`char`)。如果源类型和目标类型不一致,**在编译器知道怎么转换的情况下,会进行自动类型转换!**
- **例如**:`int` 赋给 `double` 是安全的(`int` 转换为 `double`)。
- **但请注意:** `double` 赋给 `int` 时,虽然编译器会尝试转换(截断小数部分),但这通常被认为是**不安全的**,因为它会丢失精度(小数点后的数据没有了)。
- **整型和浮点型混合运算时,整型会向浮点型转换以避免数据丢失**。**核心原则是** “向高精度、大范围类型转换”,以避免数据丢失。例如,`int` 和 `double` 相加时,`int` 会被转换为 `double`。
==**简单来说,C++的自动类型转换是尽力让你在不同的数据类型之间进行操作时,得到一个合理的结果,并且尽量不丢失数据。**==
**强制类型转换(推荐使用!)**
为了让我们的代码意图更加清晰,我们常常需要**强制类型转换**。
这相当于我们明确地告诉编译器:**我需要在这里把这个值的类型变一下!**
在 C++ 中,==强制类型转换语法==是:
**`(目标类型) 变量名 或 数值`**
- 例1:要把变量 `int number` 转换为 `char` 类型,我们就可以写:`(char)number`。
- 例2:要把变量 `int number` 转换为 `double` 类型,我们就可以写:`(double)number`。
**请注意:**如果我们想对 `a + b` 这种==**整个表达式的计算结果**==进行转换时,**就必须**使用括号 `()` 将表达式完整地包裹起来,写成 `(char)(a + b)`,以此来明确地告诉编译器:先算括号里的,再把算出来的最终结果进行转化!
而不能写成 `(char)a + b`,如果这样写,**它会先把变量 `a` 转换为 `char` 类型**。然后再去去和变量 `b` 相加。和我们的目的:将 `a + b` 的结果转化为 `char` 类型就不符了。
**步骤拆解**
使用强制类型转换将数字转为字符的步骤很简单:
1. **准备一个整数**:定义一个 `int` 类型的变量,并存入一个在 ASCII 码范围内的数字。
2. **执行转换**:使用 `(char)(你的整数变量)` 来进行转换。
3. **存储结果**:将转换后的结果赋值给一个 `char` 类型的变量。
4. **输出验证**:打印输出这个 `char` 变量,看看结果是否符合预期。
### 3. 实例
* **具体案例**
让我们把数字 `97` 转换为它对应的 ASCII 字符。
* **分步演示**
1. **准备一个整数**:
```cpp
int number = 97;
```
2. **执行转换**:我们使用强制类型转换。
```cpp
(char)number // 这个表达式的结果就是字符 'a'
```
3. **存储结果**:
```cpp
char res = (char)number;
```
4. **输出验证**:
```cpp
cout << res; // 屏幕上将会打印出 a
```
### 4. 代码实现
现在,让我们把上面的步骤整合起来,写一个完整的程序。这个程序的作用是输入一个数字,输出这个数字在ASCII 码表中对应的字符。
* **完整代码**
```cpp
#include <iostream>
using namespace std;
int main() {
cout << "请输入一个整数 (例如 65 到 90 之间): ";
// 定义变量并接收用户的输入
int number;
cin >> number;
// 使用强制类型转换
// 将 int 类型的 number 转换为 char 类型
char ascii_char = (char)number;
// 输出结果
cout << "数字 " << number << " 在 ASCII 码表中对应的字符是: " << ascii_char << endl;
return 0;
}
```
### 5. 验证
让我们用几个例子来测试一下我们的程序,看看会发生什么。
* **正常情况**:
* 输入 `65`,输出 `'A'`。
* 输入 `97`,输出 `'a'`。
* 输入 `48`,输出 `'0'`。
* **常见问题**
* **问**:为什么不直接用 `char c = number;`?
* **答**:在简单情况下可以,但它属于自动类型转化转换,意图不明确。像 `(char)number` 这样的强制转换,能让你的代码意图更清晰,一眼就能让人知道你是故意这样做的。
* **问**:强制类型转换安全吗?
* **答**:不一定。比方说: `double` 强制转换为 `int` 类型时,虽然编译器会转换(但是会截断小数部分)。所以,在转换前你要对数据的范围有的认识。例如你想转化一个整数到`char`字符类型,那就要确保这个整数的数值在ASCII码表中有对应的字符,如果这个数字根本不存在对应的字符,那计算机就也不知道应该如何转化了。
---
### **6. C++ 字符大小写转换(两种办法)**
#### 一:直接观察法
* 观察 ASCII 码表,你会发现一个规律:
* `'A'` 是 65, `'a'` 是 97
* `'B'` 是 66, `'b'` 是 98
* ...
* `'Z'` 是 90, `'z'` 是 122
**任意一个相同字母的小写形式,其 ASCII 码值都比大写形式大 32!** (`'a' - 'A' = 97 - 65 = 32`)
基于这个规律,我们有实现方式:
* **==小写转大写==**:`大写字符 = 小写字符 - 32`
* **==大写转小写==**:`小写字符 = 大写字符 + 32`
* **案例:大小写转换**
```cpp
char upper = 'B';
char lower = upper + 32; // 66 + 32 = 98 ('b')
cout << lower; // 输出 b
```
#### **二:差值计算法**:
例如想要将大写字符 `'C'`转为小写
1. **计算偏移量**:首先,我们需要知道目标字母 `'C'` 相对于大写字母起点 `'A'` 的“位置”或“差值量”。
* 这个偏移量可以通过减法计算得出:`'C' - 'A'`。我们知道结果是 `2`。这告诉我们,`'C'` 是排在 `'A'` 后面的第2个字母(从0开始计数)。
2. **找到新起点**:我们的目标是小写字母,那么小写字母的“起点”是谁?是 `'a'`。
3. **应用偏移量**:我们在新的起点 `'a'` 上,加上刚才计算出的差值量 `2`,就能得到我们想要的小写字母。
* `'a' + ('C' - 'A')`
* `'a' + 2` --> 结果就是 `'c'`。
* **公式**:
* **大写转小写**:`小写字母 = 任意大写字母 - 'A' + 'a'`
* **小写转大写**:`大写字母 = 任意小写字母 - 'a' + 'A'`
### 总结
1. 字符在计算机底层是以数字(如 ASCII 码)存储的。
2. `char` 和 `int` 之间可以相互转换。
3. 使用 `(目标类型)值` 的形式可以进行**强制类型转换**,这能让代码的意图更清晰。
4. 进行类型转换时,要注意目标类型的**取值范围**,防止数据**溢出**,例如你想转化一个整数到`char`字符类型,那就要确保这个整数的数值在ASCII码表中有对应的字符,如果这个数字根本不存在对应的字符,那计算机就也不知道应该如何转化了。
5. 英文字母的大小写形式在 ASCII 码表中具有固定的数值差 (`32`)。
@@ -0,0 +1,32 @@
### **C++ 转义字符与字符类型 课后习题 - 答案**
#### **一、 填空题**
1. `char`
2. 单引号
3. `100`
4. `\\`
5. `\"Hi\"`
6. `':'`
#### **二、 单项选择题**
1. **C**. `char` 类型的字面量必须用单引号包裹,且只能是单个字符。
2. **C**. `\n` 是换行符,会让 `Tom` 显示在下一行。
3. **B**. 反斜杠 `\` 本身是转义字符,要输出它自身,需要写成 `\\`。
4. **A**. `cin` 会依次读取一个整数存入 `a`,一个字符存入 `op`,一个整数存入 `b`。输入 `5+3` 时,`op` 会接收到字符 `'+'`。
5. **B**. `\"` 是双引号的转义序列,用于在字符串内部表示一个双引号字符。
6. **D**. `char` 变量只能存放单个字符,`'Go'` 包含两个字符,是错误的。
7. **A**. 程序读取一个整数 `h`,然后需要读取一个字符,最后读取一个整数 `m`。
8. **C**. `\\` 是 `\` 的转义序列,所以 `\\` 会让 `\` 失去转义功能,变回它本身。因此 `\\n` 会输出 `\n` 这两个字符。
#### **三、 判断题**
1. × (必须用单引号 `' '`。)
2. √ (两者最终在屏幕上看到的换行效果是一样的。)
3. √ (`cin` 默认通过空白字符(空格、回车等)分隔数据,读到 'A' 后遇到空格,就完成了对 `c` 的读取。)
4. √ (这正是转义字符的定义。)
5. × ( `char` 只能存放单个字符。)
6. √ (`cin` 会先读取数字部分 `99` 存入 `a`,然后接着读取非数字字符 `'A'` 存入 `c`。)
7. √ ( `"` 是特殊字符,在字符串中必须通过 `\"` 来表示。)
8. √ (如 `cin >> a >> op >> b;` 所示,`cin` 可以很灵活地连续读取不同类型的数据。)
@@ -0,0 +1,78 @@
### **C++ 转义字符与字符类型 课后习题**
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. C++中,用于存放单个字符(如 'A', '?')的数据类型是 \_\_\_\_\_\_\_\_\_\_。
2. 在为 `char` 类型变量赋值时,该字符必须被一对 \_\_\_\_\_\_\_\_\_\_(填“单引号”或“双引号”)包围。
3. 代码 `cout << "Score:\n100";` 的输出结果是:第一行显示 `Score:`,第二行显示 \_\_\_\_\_\_\_\_\_\_。
4. 如果想在屏幕上显示出路径 `D:\Data`,那么在代码的字符串中应该写作 `D:________Data`。
5. 如果想在屏幕上显示出带引号的文字 `He said: "Hi"`,那么代码中表示 `"Hi"` 的部分应该写作 \_\_\_\_\_\_\_\_\_\_。
6. 对于代码 `int h; char c; cin >> h >> c;`,如果用户输入 `12:59` 并回车,那么整型变量 `h` 的值是 `12`,而字符型变量 `c` 的值是 \_\_\_\_\_\_\_\_\_\_。
#### **二、 单项选择题**
( &nbsp; ) 1. 下面哪个是定义 `char` 变量并正确赋值的语句?
A. `char c = "A";`
B. `char c = A;`
C. `char c = 'A';`
D. `char c = 'AB';`
( &nbsp; ) 2. 执行 `cout << "Name:\nTom";` 后,屏幕上会显示什么?
A. `Name:\nTom`
B. `Name: Tom`
C. 两行,第一行 `Name:`,第二行 `Tom`
D. `Name:Tom`
( &nbsp; ) 3. 哪句代码可以在屏幕上正确输出路径 `C:\` ?
A. `cout << "C:\";`
B. `cout << "C:\\";`
C. `cout << "C:/";`
D. `cout << 'C:\';`
( &nbsp; ) 4. 阅读以下代码,如果用户输入 `5+3` 并回车,变量 `op` 的值是多少?
```cpp
int a, b;
char op;
cin >> a >> op >> b;
```
​ A. `'+'`
​ B. `+`
​ C. `" "` (空格)
​ D. 程序会报错
( &nbsp; ) 5. 执行 `cout << "He said \"Go!\"";` 后,屏幕上会显示什么?
A. `He said Go!`
B. `He said "Go!"`
C. `He said \"Go!\"`
D. `He said: "Go!"`
( &nbsp; ) 6. 以下哪个**不能**被正确地赋值给一个 `char` 类型的变量?
A. `'?'`
B. `' '`
C. `'7'`
D. `'Go'`
( &nbsp; ) 7. 有代码 `int h, m; char c; cin >> h >> c >> m;`,为了让程序成功读入小时 `11` 和分钟 `59`,不正确的输入格式是?
A. `11 59`
B. `11:59`
C. `11,59`
D. `11;59`
( &nbsp; ) 8. 执行 `cout << "\\n";` 后,屏幕上会显示什么?
A. 一个空行
B. `n`
C. `\n`
D. `\`
#### **三、 判断题 (对的打“√”,错的打“×”)**
( &nbsp; ) 1. 在C++中,定义一个字符变量时,字符必须用双引号 `"` 包起来。
( &nbsp; ) 2. `\n` 和 `endl` 都能在 `cout` 中实现换行的视觉效果。
( &nbsp; ) 3. 对于 `char c; cin >> c;`,如果用户输入 `A B` 并回车,变量 `c` 的值会是 `'A'`。
( &nbsp; ) 4. 转义字符 `\` 本身通常不会被显示,它的作用是改变其后字符的含义。
( &nbsp; ) 5. 一个 `char` 类型的变量可以存放一个单词,比如 `'Hello'`。
( &nbsp; ) 6. 对于 `int a; char c; cin >> a >> c;`,如果用户输入 `99A`,那么 `a` 的值是 `99`,`c` 的值是 `'A'`。
( &nbsp; ) 7. 想输出双引号 `"`,代码必须写成 `cout << "\"";`。
( &nbsp; ) 8. `cin` 可以一次性读取不同类型的变量,例如 `int` 和 `char`。
@@ -0,0 +1,28 @@
### 参考答案
#### **一、 填空题**
1. `char`
2. 单引号
3. `66`
4. `32`
5. `5`
6. `'+'`
#### **二、 单项选择题**
1. C
2. C
3. B (字符'C'的ASCII码是67,加1后是68,68对应的字符是'D')
4. B
5. B
6. A
#### **三、 判断题**
1. × (一个是字符,一个是数字,它们的ASCII码和数值都不同)
2. √
3. √
4. × (从'A'到'Z',ASCII码是递增的)
5. √
6. × (`char` 只能存放单个字符)
@@ -0,0 +1,70 @@
### C++ 字符与 ASCII 码 课后习题
姓名: \_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_\_
#### **一、 填空题**
1. 在 C++ 中,我们使用关键字 \_\_\_\_\_\_\_\_ 来定义一个存放单个字符的变量。
2. 定义一个字符变量时,字符本身必须用 \_\_\_\_\_\_\_\_ (填“单引号”或“双引号”) 括起来。
3. 字符 `'A'` 对应的 ASCII 码值是 `65`,那么字符 `'B'` 对应的 ASCII 码值是 \_\_\_\_\_\_\_\_。
4. 小写字母 `'a'` 的 ASCII 码值比大写字母 `'A'` 的 ASCII 码值大 \_\_\_\_\_\_\_\_。
5. 阅读代码 `char c = '5'; int n = c - '0';`,执行后,整型变量 `n` 中存放的值是 \_\_\_\_\_\_\_\_。
6. 在 `cin >> num1 >> op >> num2;` 中,如果用户输入 `10 + 20`,那么字符变量 `op` 中存入的值是 \_\_\_\_\_\_\_\_。
#### **二、 单项选择题**
( ) 1. 下列哪个是**正确**的字符变量定义语句?
A. `char c = "A";`
B. `char c = A;`
C. `char c = 'A';`
D. `int c = 'A';`
( ) 2. 执行 `int ch = 'a'; cout << ch;` 后,屏幕上会显示什么?
A. `a`
B. `A`
C. `97`
D. `65`
( ) 3. 阅读以下代码:
```cpp
char letter = 'C';
letter = letter + 1;
cout << letter;
```
​ 程序的输出结果是?
​ A. `C`
​ B. `D`
​ C. `67`
​ D. `68`
( ) 4. 如果想把大写字母 `'E'` 转换为对应的小写字母,正确的代码是?
A. `'E' - 32`
B. `'E' + 32`
C. `'E' - 'A'`
D. `'E' + 'a'`
( ) 5. 在计算机内部,`char` 类型的变量实际上存放的是什么?
A. 字符的形状
B. 字符对应的 ASCII 码(一个整数)
C. 一段文字
D. 一个小数
( ) 6. `int a; char op; cin >> a >> op;` 如果用户输入 `9*`,那么变量 `a` 和 `op` 的值分别是?
A. `a` 是 `9`,`op` 是 `'*'`
B. `a` 是 `'9'`,`op` 是 `'*'`
C. `a` 是 `9`,`op` 的值不确定
D. 读取会失败
#### **三、 判断题 (对的打“√”,错的打“×”)**
( ) 1. 在 C++ 中,字符 `'8'` 和整数 `8` 是完全一样的。
( ) 2. 计算机能识别和处理文字,是因为有一张像 ASCII 码这样的“字符-数字”对照表。
( ) 3. 数字字符是连续排列的,例如 `'1'` 的 ASCII 码比 `'0'` 的大 1。
( ) 4. 大写字母 `'Z'` 的 ASCII 码比 `'A'` 的小。
( ) 5. 因为字符本质上是数字,所以我们可以对 `char` 类型的变量进行加减运算。
( ) 6. `char c = 'ab';` 这样的写法是正确的。
Loaded 100 of 1789 files, more files were not shown because too many files have changed in this diff. Show more