1133 lines
41 KiB
HTML
1133 lines
41 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>CSP03-07 string 使用2 - 字符串进阶教学</title>
|
||
|
||
<!-- Fonts: Poppins for tech vibe -->
|
||
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&display=swap" rel="stylesheet">
|
||
|
||
<style>
|
||
/* ===========================================
|
||
CSS CUSTOM PROPERTIES (THEME)
|
||
Creative Voltage Style - Techy/Futuristic
|
||
=========================================== */
|
||
:root {
|
||
/* Colors - Deep space theme with neon accents */
|
||
--bg-primary: #0a0e27;
|
||
--bg-secondary: #111827;
|
||
--bg-card: rgba(17, 24, 39, 0.8);
|
||
--text-primary: #ffffff;
|
||
--text-secondary: #9ca3af;
|
||
--accent: #00ffcc;
|
||
--accent-glow: rgba(0, 255, 204, 0.3);
|
||
--accent-purple: #8b5cf6;
|
||
--accent-blue: #3b82f6;
|
||
|
||
/* Typography - Poppins for tech feel */
|
||
--font-display: 'Poppins', sans-serif;
|
||
--font-mono: 'Poppins Mono', monospace;
|
||
|
||
/* MUST use clamp() for responsive typography */
|
||
--title-size: clamp(2rem, 6vw, 4rem);
|
||
--subtitle-size: clamp(1.2rem, 3vw, 2rem);
|
||
--h1-size: clamp(1.8rem, 4vw, 3rem);
|
||
--h2-size: clamp(1.4rem, 3vw, 2rem);
|
||
--h3-size: clamp(1.1rem, 2vw, 1.5rem);
|
||
--body-size: clamp(0.9rem, 1.5vw, 1.1rem);
|
||
--code-size: clamp(0.8rem, 1.3vw, 1rem);
|
||
|
||
/* Spacing - MUST use clamp() */
|
||
--slide-padding: clamp(1.5rem, 4vw, 3rem);
|
||
--content-gap: clamp(1rem, 2vw, 2rem);
|
||
--element-gap: clamp(0.5rem, 1vw, 1rem);
|
||
|
||
/* Animation */
|
||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||
--duration-normal: 0.6s;
|
||
--duration-slow: 1s;
|
||
}
|
||
|
||
/* ===========================================
|
||
BASE STYLES
|
||
=========================================== */
|
||
* {
|
||
margin: 0;
|
||
padding: 0;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* Lock html/body to viewport */
|
||
html, body {
|
||
height: 100%;
|
||
overflow-x: hidden;
|
||
}
|
||
|
||
html {
|
||
scroll-snap-type: y mandatory;
|
||
scroll-behavior: smooth;
|
||
}
|
||
|
||
/* Each slide = exact viewport height */
|
||
.slide {
|
||
width: 100vw;
|
||
height: 100vh;
|
||
height: 100dvh;
|
||
overflow: hidden;
|
||
scroll-snap-align: start;
|
||
display: flex;
|
||
flex-direction: column;
|
||
position: relative;
|
||
}
|
||
|
||
/* Content container with flex for centering */
|
||
.slide-content {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
max-height: 100%;
|
||
overflow: hidden;
|
||
padding: var(--slide-padding);
|
||
}
|
||
|
||
/* ALL typography uses clamp() */
|
||
body {
|
||
font-family: var(--font-display);
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
}
|
||
|
||
h1, h2, h3 {
|
||
font-weight: 600;
|
||
margin-bottom: var(--element-gap);
|
||
line-height: 1.2;
|
||
}
|
||
|
||
h1 { font-size: var(--h1-size); }
|
||
h2 { font-size: var(--h2-size); }
|
||
h3 { font-size: var(--h3-size); }
|
||
|
||
p {
|
||
font-size: var(--body-size);
|
||
line-height: 1.6;
|
||
margin-bottom: var(--element-gap);
|
||
}
|
||
|
||
/* ===========================================
|
||
ANIMATIONS - Techy Futuristic Style
|
||
=========================================== */
|
||
.reveal {
|
||
opacity: 0;
|
||
transform: translateY(30px);
|
||
transition: opacity var(--duration-normal) var(--ease-out-expo),
|
||
transform var(--duration-normal) var(--ease-out-expo);
|
||
}
|
||
|
||
.slide.visible .reveal {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
|
||
/* Stagger children for sequential reveal */
|
||
.reveal:nth-child(1) { transition-delay: 0.1s; }
|
||
.reveal:nth-child(2) { transition-delay: 0.2s; }
|
||
.reveal:nth-child(3) { transition-delay: 0.3s; }
|
||
.reveal:nth-child(4) { transition-delay: 0.4s; }
|
||
.reveal:nth-child(5) { transition-delay: 0.5s; }
|
||
|
||
/* Neon glow effects */
|
||
.neon-glow {
|
||
text-shadow:
|
||
0 0 10px var(--accent-glow),
|
||
0 0 20px var(--accent-glow),
|
||
0 0 30px var(--accent-glow);
|
||
}
|
||
|
||
/* Code block styling */
|
||
.code-block {
|
||
background: var(--bg-secondary);
|
||
border: 1px solid rgba(0, 255, 204, 0.3);
|
||
border-radius: 8px;
|
||
padding: 1.5rem;
|
||
margin: var(--content-gap) 0;
|
||
font-family: var(--font-mono);
|
||
font-size: var(--code-size);
|
||
position: relative;
|
||
overflow-x: auto;
|
||
}
|
||
|
||
.code-block::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, var(--accent), var(--accent-purple));
|
||
}
|
||
|
||
/* Card styling */
|
||
.card {
|
||
background: var(--bg-card);
|
||
border: 1px solid rgba(0, 255, 204, 0.2);
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
backdrop-filter: blur(10px);
|
||
margin-bottom: var(--content-gap);
|
||
}
|
||
|
||
/* Feature list */
|
||
.feature-list {
|
||
list-style: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: var(--element-gap);
|
||
}
|
||
|
||
.feature-list li {
|
||
font-size: var(--body-size);
|
||
line-height: 1.5;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 0.5rem;
|
||
}
|
||
|
||
.feature-list li::before {
|
||
content: '▸';
|
||
color: var(--accent);
|
||
font-weight: bold;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* Gradient backgrounds */
|
||
.gradient-bg {
|
||
background:
|
||
radial-gradient(ellipse at 20% 80%, rgba(139, 92, 246, 0.2) 0%, transparent 50%),
|
||
radial-gradient(ellipse at 80% 20%, rgba(59, 130, 246, 0.2) 0%, transparent 50%),
|
||
var(--bg-primary);
|
||
}
|
||
|
||
/* Circuit lines effect */
|
||
.circuit-lines {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
opacity: 0.2;
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
.circuit-line {
|
||
position: absolute;
|
||
background: linear-gradient(90deg, transparent, var(--accent), transparent);
|
||
height: 1px;
|
||
animation: flow 3s linear infinite;
|
||
}
|
||
|
||
@keyframes flow {
|
||
0% { transform: translateX(-100%); }
|
||
100% { transform: translateX(100%); }
|
||
}
|
||
|
||
/* Particle system */
|
||
.particles {
|
||
position: absolute;
|
||
width: 100%;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
z-index: 1;
|
||
}
|
||
|
||
.particle {
|
||
position: absolute;
|
||
width: 3px;
|
||
height: 3px;
|
||
background: var(--accent);
|
||
border-radius: 50%;
|
||
opacity: 0;
|
||
animation: particle-rise 10s linear infinite;
|
||
}
|
||
|
||
@keyframes particle-rise {
|
||
0% {
|
||
transform: translateY(100vh) scale(0);
|
||
opacity: 0;
|
||
}
|
||
10% {
|
||
opacity: 0.8;
|
||
}
|
||
90% {
|
||
opacity: 0.8;
|
||
}
|
||
100% {
|
||
transform: translateY(-100vh) scale(1.5);
|
||
opacity: 0;
|
||
}
|
||
}
|
||
|
||
/* Tech grid pattern */
|
||
.tech-grid {
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background-image:
|
||
repeating-linear-gradient(0deg, transparent, transparent 40px, rgba(0, 255, 204, 0.03) 40px, rgba(0, 255, 204, 0.03) 41px),
|
||
repeating-linear-gradient(90deg, transparent, transparent 40px, rgba(0, 255, 204, 0.03) 40px, rgba(0, 255, 204, 0.03) 41px);
|
||
pointer-events: none;
|
||
z-index: 1;
|
||
}
|
||
|
||
/* Navigation dots */
|
||
.nav-dots {
|
||
position: fixed;
|
||
bottom: 2rem;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
display: flex;
|
||
gap: 0.5rem;
|
||
z-index: 1000;
|
||
}
|
||
|
||
.nav-dot {
|
||
width: 10px;
|
||
height: 10px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.3);
|
||
cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.nav-dot.active {
|
||
background: var(--accent);
|
||
transform: scale(1.3);
|
||
}
|
||
|
||
/* Progress bar */
|
||
.progress-bar {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
height: 3px;
|
||
background: var(--accent);
|
||
z-index: 1000;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
/* Interactive code highlight */
|
||
.code-highlight {
|
||
color: var(--accent);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* Badge styling */
|
||
.badge {
|
||
display: inline-block;
|
||
background: rgba(0, 255, 204, 0.1);
|
||
border: 1px solid var(--accent);
|
||
color: var(--accent);
|
||
padding: 0.3rem 0.8rem;
|
||
border-radius: 20px;
|
||
font-size: var(--code-size);
|
||
margin-right: 0.5rem;
|
||
}
|
||
|
||
/* Grid layout */
|
||
.grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||
gap: var(--content-gap);
|
||
margin-top: var(--content-gap);
|
||
}
|
||
|
||
/* Two-column layout */
|
||
.two-column {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: var(--content-gap);
|
||
}
|
||
|
||
/* Keyboard hint */
|
||
.keyboard-hint {
|
||
position: fixed;
|
||
bottom: 1rem;
|
||
right: 1rem;
|
||
font-size: var(--small-size);
|
||
color: var(--text-secondary);
|
||
opacity: 0.7;
|
||
z-index: 1000;
|
||
}
|
||
|
||
/* Responsive adjustments */
|
||
@media (max-height: 700px) {
|
||
:root {
|
||
--slide-padding: clamp(1rem, 3vw, 2rem);
|
||
--content-gap: clamp(0.8rem, 1.5vw, 1.5rem);
|
||
}
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.two-column {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
}
|
||
|
||
/* Inline editing styles (if enabled) */
|
||
.edit-hotzone {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 80px;
|
||
height: 80px;
|
||
z-index: 10000;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.edit-toggle {
|
||
position: fixed;
|
||
top: 1rem;
|
||
left: 1rem;
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 50%;
|
||
background: var(--accent);
|
||
border: none;
|
||
color: var(--bg-primary);
|
||
font-size: 1.2rem;
|
||
cursor: pointer;
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: opacity 0.3s ease;
|
||
z-index: 10001;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.edit-toggle.show,
|
||
.edit-toggle.active {
|
||
opacity: 1;
|
||
pointer-events: auto;
|
||
}
|
||
|
||
.contenteditable {
|
||
cursor: text;
|
||
padding: 0.2rem 0.4rem;
|
||
border-radius: 4px;
|
||
transition: background 0.3s ease;
|
||
}
|
||
|
||
.contenteditable:hover {
|
||
background: rgba(255, 255, 255, 0.05);
|
||
}
|
||
|
||
.contenteditable:focus {
|
||
outline: 2px solid var(--accent);
|
||
background: rgba(255, 255, 255, 0.1);
|
||
}
|
||
|
||
/* Prefers reduced motion */
|
||
@media (prefers-reduced-motion: reduce) {
|
||
*, *::before, *::after {
|
||
animation-duration: 0.01ms !important;
|
||
transition-duration: 0.2s !important;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Progress bar -->
|
||
<div class="progress-bar" id="progressBar"></div>
|
||
|
||
<!-- Navigation dots -->
|
||
<nav class="nav-dots" id="navDots"></nav>
|
||
|
||
<!-- Inline editing toggle -->
|
||
<div class="edit-hotzone" id="editHotzone"></div>
|
||
<button class="edit-toggle" id="editToggle" title="编辑模式 (E)">✏️</button>
|
||
|
||
<!-- Background effects -->
|
||
<div class="tech-grid"></div>
|
||
<div class="circuit-lines">
|
||
<div class="circuit-line" style="top: 20%; width: 100%; animation-delay: 0s;"></div>
|
||
<div class="circuit-line" style="top: 50%; width: 100%; animation-delay: 1s;"></div>
|
||
<div class="circuit-line" style="top: 80%; width: 100%; animation-delay: 2s;"></div>
|
||
</div>
|
||
<div class="particles" id="particles"></div>
|
||
|
||
<!-- Slides -->
|
||
|
||
<!-- Slide 1: Title -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<div class="reveal">
|
||
<div class="badge">C++ 编程进阶</div>
|
||
<h1 class="neon-glow">CSP03-07</h1>
|
||
<h2>string 使用技巧与回文判断</h2>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 2: Course Overview -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">课程目标</h2>
|
||
<ul class="feature-list reveal">
|
||
<li>掌握 <span class="code-highlight">string</span> 的子串提取(<code>substr</code>)操作</li>
|
||
<li>掌握字符串回文、对称的判断方法</li>
|
||
<li>综合运用 <code>string</code> 的各种操作解决复杂字符串问题</li>
|
||
</ul>
|
||
|
||
<h2 class="reveal" style="margin-top: 2rem;">核心知识点</h2>
|
||
<ul class="feature-list reveal">
|
||
<li><code>s.substr(pos, len)</code>:提取从 pos 开始长度为 len 的子串</li>
|
||
<li><code>s.substr(pos)</code>:提取从 pos 到末尾的子串</li>
|
||
<li>字符串对称(回文)的判断方法:双指针、逆序比较</li>
|
||
<li>string 综合应用:字符串处理的整体思路</li>
|
||
</ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 3: Knowledge Review -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<div class="card reveal">
|
||
<h3>👩🏫 教师引导</h3>
|
||
<p>上节课我们学了 string 的基础:拼接、搜索、替换、大小写转换。</p>
|
||
<p>今天继续进阶——学习如何"切割"字符串(提取子串),以及一个经典问题:<strong>回文判断</strong>!</p>
|
||
<p>最后,我们用综合题来验证这两节课学到的所有 string 技能!</p>
|
||
</div>
|
||
|
||
<div class="card reveal">
|
||
<h3>💡 互动复习</h3>
|
||
<ul class="feature-list">
|
||
<li><code>s.find("abc")</code> 找不到时返回什么?(<code>string::npos</code>)</li>
|
||
<li>如何把字符串 <code>s</code> 全部转成大写?(for 循环 + toupper)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 4: substr Introduction -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">🎬 新知导入:提取子串</h2>
|
||
<div class="card reveal">
|
||
<p>假设你有一个身份证号码字符串:<code>"110105199001234567"</code></p>
|
||
<p>你想从中提取出生年月日(第7到第14位),怎么做?</p>
|
||
<p style="color: var(--accent); font-size: 1.2rem; font-weight: 600;">用 <code>s.substr(6, 8)</code> 就能一次搞定!</p>
|
||
<p style="font-size: 0.9rem; opacity: 0.8; margin-top: 0.5rem;">(下标从0开始,第7位就是下标6)</p>
|
||
</div>
|
||
<p class="reveal" style="margin-top: 2rem; text-align: center; opacity: 0.8;">✨ 这就是 <code>substr</code> 的魔力——精准"切割"字符串的任意一段!</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 5: substr Methods -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">提取子串 substr</h2>
|
||
<div class="code-block reveal">
|
||
<pre><code>string s = "hello world";
|
||
|
||
// 格式1:s.substr(pos, len) — 从 pos 开始,截取 len 个字符
|
||
string sub1 = s.substr(0, 5); // "hello"
|
||
string sub2 = s.substr(6, 5); // "world"
|
||
|
||
// 格式2:s.substr(pos) — 从 pos 开始截到末尾
|
||
string sub3 = s.substr(6); // "world"</code></pre>
|
||
</div>
|
||
|
||
<h3 class="reveal">常见应用</h3>
|
||
<div class="code-block reveal">
|
||
<pre><code>string date = "2024-03-15";
|
||
string year = date.substr(0, 4); // "2024"
|
||
string month = date.substr(5, 2); // "03"
|
||
string day = date.substr(8, 2); // "15"</code></pre>
|
||
</div>
|
||
<p class="reveal" style="color: #ff6b6b;">⚠️ 注意:<code>pos</code> 超出字符串长度会报错!使用前要确认 <code>pos < s.size()</code>。</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 6: Palindrome Method 1 -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">字符串对称(回文)判断</h2>
|
||
<h3 class="reveal">方法一:双指针(推荐)</h3>
|
||
<div class="code-block reveal">
|
||
<pre><code>string s = "racecar";
|
||
int l = 0, r = s.size() - 1;
|
||
bool isOk = true;
|
||
while (l < r) {
|
||
if (s[l] != s[r]) {
|
||
isOk = false;
|
||
break;
|
||
}
|
||
l++; r--;
|
||
}
|
||
cout << (isOk ? "YES" : "NO") << endl;</code></pre>
|
||
</div>
|
||
<div class="card reveal">
|
||
<p>🔍 <strong>双指针原理:</strong>分别从字符串的两端向中间移动,比较对应位置的字符是否相同。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 7: Palindrome Method 2 -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h3 class="reveal">方法二:逆序字符串比较</h3>
|
||
<div class="code-block reveal">
|
||
<pre><code>string s = "racecar";
|
||
string rev = s;
|
||
reverse(rev.begin(), rev.end()); // 需要 #include <algorithm>
|
||
cout << (s == rev ? "YES" : "NO") << endl;</code></pre>
|
||
</div>
|
||
<div class="card reveal">
|
||
<p>💡 <code>reverse(s.begin(), s.end())</code> 可以直接将 string 原地逆序!</p>
|
||
</div>
|
||
<div class="two-column">
|
||
<div class="card reveal">
|
||
<h4>双指针优点</h4>
|
||
<ul class="feature-list">
|
||
<li>空间复杂度 O(1)</li>
|
||
<li>时间复杂度 O(n/2)</li>
|
||
<li>提前终止</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card reveal">
|
||
<h4>逆序比较优点</h4>
|
||
<ul class="feature-list">
|
||
<li>代码简洁</li>
|
||
<li>易于理解</li>
|
||
<li>适用于复杂场景</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 8: Iterators -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">在 string 中使用 begin/end 迭代器</h2>
|
||
<div class="card reveal">
|
||
<p><code>string</code> 支持 C++ 标准库算法,可以使用迭代器范围:</p>
|
||
</div>
|
||
<div class="code-block reveal">
|
||
<pre><code>#include <algorithm>
|
||
string s = "hello";
|
||
reverse(s.begin(), s.end()); // s = "olleh"
|
||
sort(s.begin(), s.end()); // s = "ehllo"(排序字符)</code></pre>
|
||
</div>
|
||
<div class="two-column">
|
||
<div class="card reveal">
|
||
<h4>常用算法</h4>
|
||
<ul class="feature-list">
|
||
<li><code>sort()</code> - 排序</li>
|
||
<li><code>reverse()</code> - 逆序</li>
|
||
<li><code>find()</code> - 查找</li>
|
||
</ul>
|
||
</div>
|
||
<div class="card reveal">
|
||
<h4>高级算法</h4>
|
||
<ul class="feature-list">
|
||
<li><code>count()</code> - 计数</li>
|
||
<li><code>replace()</code> - 替换</li>
|
||
<li><code>transform()</code> - 转换</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 9: Comprehensive Skills -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">字符串综合操作技巧</h2>
|
||
<div class="card reveal">
|
||
<h4>删除字符串中所有指定字符</h4>
|
||
<div class="code-block">
|
||
<pre><code>string s = "a1b2c3";
|
||
string result = "";
|
||
for (char c : s) {
|
||
if (!isdigit(c)) result += c; // 只保留非数字
|
||
}
|
||
// result = "abc"</code></pre>
|
||
</div>
|
||
</div>
|
||
<div class="card reveal">
|
||
<h4>字符串反转每个单词</h4>
|
||
<p>思路:<strong>分割 → 反转每个单词 → 重新拼接</strong></p>
|
||
<div class="code-block">
|
||
<pre><code>string line = "hello world";
|
||
// 使用字符串流分割单词
|
||
istringstream ss(line);
|
||
string word;
|
||
while (ss >> word) {
|
||
reverse(word.begin(), word.end());
|
||
// ... 重新拼接
|
||
}</code></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 10: Quick Questions -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">⚡ GESP 真题演练</h2>
|
||
|
||
<div class="card reveal">
|
||
<h3>抢答题 1(选择题)</h3>
|
||
<p><code>string s = "abcdef"; cout << s.substr(2, 3);</code> 的输出是?</p>
|
||
<div style="display: flex; gap: 1rem; margin: 1rem 0;">
|
||
<span style="padding: 0.5rem 1rem; background: rgba(255,255,255,0.1); border-radius: 8px;">A. "abc"</span>
|
||
<span style="padding: 0.5rem 1rem; background: rgba(0,255,204,0.2); border: 1px solid var(--accent); border-radius: 8px;">B. "bcd"</span>
|
||
<span style="padding: 0.5rem 1rem; background: rgba(255,255,255,0.1); border-radius: 8px;">C. "cde"</span>
|
||
<span style="padding: 0.5rem 1rem; background: rgba(255,255,255,0.1); border-radius: 8px;">D. "def"</span>
|
||
</div>
|
||
<p><strong>答案:C</strong> | 解析:从下标 2(字符 'c')开始,截取 3 个字符:"cde"。</p>
|
||
</div>
|
||
|
||
<div class="card reveal">
|
||
<h3>判断题 2</h3>
|
||
<p>判断回文串,可以将字符串逆序后与原字符串比较,相同则是回文。( )</p>
|
||
<p style="color: var(--accent); font-weight: 600; font-size: 1.2rem;">✓(正确)</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 11: Practice 1 -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">🎈 课堂练习:提取子串</h2>
|
||
<div class="card reveal">
|
||
<h3>题目描述</h3>
|
||
<p>输入字符串 s 和两个整数 l、r(1-indexed),输出 s 的第 l 到第 r 个字符(含边界)。</p>
|
||
|
||
<h3>输入格式</h3>
|
||
<p>第一行字符串 s(不含空格)<br>第二行两整数 l r</p>
|
||
|
||
<h3>输出格式</h3>
|
||
<p>提取的子串</p>
|
||
|
||
<h3>样例</h3>
|
||
<div style="background: rgba(0,0,0,0.2); padding: 1rem; border-radius: 8px;">
|
||
<p><strong>输入:</strong></p>
|
||
<pre>helloworld
|
||
2 6</pre>
|
||
<p><strong>输出:</strong></p>
|
||
<pre style="color: var(--accent);">ellow</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 12: Practice 2 -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">🎈 课堂练习:判断回文串</h2>
|
||
<div class="card reveal">
|
||
<h3>题目描述</h3>
|
||
<p>输入一个字符串,判断它是否是回文串(忽略大小写)。</p>
|
||
|
||
<h3>输入格式</h3>
|
||
<p>一行字符串(不含空格,长度 ≤ 1000)</p>
|
||
|
||
<h3>输出格式</h3>
|
||
<p><code>YES</code> 或 <code>NO</code></p>
|
||
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem;">
|
||
<div>
|
||
<h3>样例输入</h3>
|
||
<div style="background: rgba(0,0,0,0.2); padding: 1rem; border-radius: 8px;">
|
||
<pre>Racecar</pre>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<h3>样例输出</h3>
|
||
<div style="background: rgba(0,255,204,0.2); padding: 1rem; border-radius: 8px; color: var(--accent);">
|
||
<pre>YES</pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 13: Advanced Practice -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">🔥 进阶练习:最长回文子串</h2>
|
||
<div class="card reveal">
|
||
<h3>题目描述</h3>
|
||
<p>给定一个字符串 s(只含小写字母),求其中最长的回文子串。如有多个长度相同的,输出字典序最小的。</p>
|
||
|
||
<h3>输入格式</h3>
|
||
<p>一行字符串 s(长度 ≤ 500)</p>
|
||
|
||
<h3>输出格式</h3>
|
||
<p>最长回文子串</p>
|
||
|
||
<div style="background: rgba(0,0,0,0.2); padding: 1rem; border-radius: 8px;">
|
||
<p><strong>样例输入:</strong> babad</p>
|
||
<p><strong>样例输出:</strong> <span style="color: var(--accent); font-weight: 600;">aba</span></p>
|
||
</div>
|
||
</div>
|
||
<div class="card reveal" style="margin-top: 1rem;">
|
||
<h3>📌 算法思路</h3>
|
||
<p>枚举所有起始位置 i 和长度 len,提取子串,判断是否回文。</p>
|
||
<p>时间复杂度 O(n²),对 n≤1000 的字符串完全足够。</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 14: Summary -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">🌟 课堂总结</h2>
|
||
<div class="card reveal">
|
||
<h3>今天完成了 string 的进阶学习!</h3>
|
||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin: 1rem 0;">
|
||
<div style="text-align: center; padding: 1rem;">
|
||
<div style="font-size: 2rem; margin-bottom: 0.5rem;">🔤</div>
|
||
<code style="font-size: 1.1rem;">s.substr(pos, len)</code>
|
||
<p style="font-size: 0.9rem; margin-top: 0.5rem;">精准提取子串</p>
|
||
</div>
|
||
<div style="text-align: center; padding: 1rem;">
|
||
<div style="font-size: 2rem; margin-bottom: 0.5rem;">🔄</div>
|
||
<code style="font-size: 1.1rem;">reverse()</code>
|
||
<p style="font-size: 0.9rem; margin-top: 0.5rem;">原地逆序</p>
|
||
</div>
|
||
</div>
|
||
<p style="text-align: center; margin: 1.5rem 0; font-size: 1.2rem;">✨ 这两节课加起来,string 的重要操作已经全部掌握!</p>
|
||
<div style="text-align: center; background: rgba(0,255,204,0.1); padding: 1rem; border-radius: 8px;">
|
||
<p>下节课开始进入全新主题:<strong>算法篇</strong></p>
|
||
<p>从"枚举算法"开始,学习信息学竞赛的思维方法!</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Slide 15: Homework -->
|
||
<section class="slide gradient-bg">
|
||
<div class="slide-content">
|
||
<h2 class="reveal">📝 课后作业</h2>
|
||
<div class="card reveal">
|
||
<h3>作业 1:提取域名</h3>
|
||
<p>输入一个 URL,提取其中的域名(在 "://" 之后到 "/" 或末尾之前)。</p>
|
||
<p style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin: 0.5rem 0;">
|
||
<strong>样例:</strong> https://www.example.com/page → www.example.com
|
||
</p>
|
||
</div>
|
||
<div class="card reveal">
|
||
<h3>作业 2:字符串旋转</h3>
|
||
<p>输入字符串 s 和整数 k,将 s 向左旋转 k 位(前 k 字符移到末尾)。</p>
|
||
<p style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin: 0.5rem 0;">
|
||
<strong>样例:</strong> abcde, 2 → cdeab
|
||
</p>
|
||
</div>
|
||
<div class="card reveal">
|
||
<h3>作业 3:统计回文子串个数</h3>
|
||
<p>输入字符串 s,统计其中长度 ≥ 2 的回文子串的数量。</p>
|
||
<p style="background: rgba(0,0,0,0.2); padding: 0.5rem; border-radius: 4px; margin: 0.5rem 0;">
|
||
<strong>样例:</strong> aaa → 3("aa"×2 + "aaa"×1)
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Keyboard hint -->
|
||
<div class="keyboard-hint">← → 空格键导航</div>
|
||
|
||
<script>
|
||
/* ===========================================
|
||
SLIDE PRESENTATION CONTROLLER
|
||
=========================================== */
|
||
class SlidePresentation {
|
||
constructor() {
|
||
this.slides = document.querySelectorAll('.slide');
|
||
this.currentSlide = 0;
|
||
this.isAnimating = false;
|
||
|
||
this.setupParticles();
|
||
this.setupIntersectionObserver();
|
||
this.setupKeyboardNav();
|
||
this.setupTouchNav();
|
||
this.setupProgressBar();
|
||
this.setupNavDots();
|
||
this.setupInlineEditing();
|
||
}
|
||
|
||
setupParticles() {
|
||
const particlesContainer = document.getElementById('particles');
|
||
for (let i = 0; i < 30; i++) {
|
||
const particle = document.createElement('div');
|
||
particle.className = 'particle';
|
||
particle.style.left = Math.random() * 100 + '%';
|
||
particle.style.animationDelay = Math.random() * 10 + 's';
|
||
particle.style.animationDuration = (10 + Math.random() * 10) + 's';
|
||
particlesContainer.appendChild(particle);
|
||
}
|
||
}
|
||
|
||
setupIntersectionObserver() {
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
}
|
||
});
|
||
}, { threshold: 0.1 });
|
||
|
||
this.slides.forEach(slide => {
|
||
observer.observe(slide);
|
||
});
|
||
}
|
||
|
||
setupKeyboardNav() {
|
||
document.addEventListener('keydown', (e) => {
|
||
if (this.isAnimating) return;
|
||
|
||
switch(e.key) {
|
||
case 'ArrowLeft':
|
||
case 'ArrowUp':
|
||
e.preventDefault();
|
||
this.prevSlide();
|
||
break;
|
||
case 'ArrowRight':
|
||
case 'ArrowDown':
|
||
case ' ':
|
||
case 'PageDown':
|
||
e.preventDefault();
|
||
this.nextSlide();
|
||
break;
|
||
case 'PageUp':
|
||
e.preventDefault();
|
||
this.prevSlide();
|
||
break;
|
||
case 'Home':
|
||
e.preventDefault();
|
||
this.goToSlide(0);
|
||
break;
|
||
case 'End':
|
||
e.preventDefault();
|
||
this.goToSlide(this.slides.length - 1);
|
||
break;
|
||
}
|
||
});
|
||
}
|
||
|
||
setupTouchNav() {
|
||
let touchStartY = 0;
|
||
let touchEndY = 0;
|
||
|
||
document.addEventListener('touchstart', (e) => {
|
||
touchStartY = e.changedTouches[0].screenY;
|
||
});
|
||
|
||
document.addEventListener('touchend', (e) => {
|
||
touchEndY = e.changedTouches[0].screenY;
|
||
this.handleSwipe(touchStartY, touchEndY);
|
||
});
|
||
}
|
||
|
||
handleSwipe(startY, endY) {
|
||
const swipeThreshold = 50;
|
||
const diff = startY - endY;
|
||
|
||
if (Math.abs(diff) > swipeThreshold) {
|
||
if (diff > 0) {
|
||
// Swipe up - next slide
|
||
this.nextSlide();
|
||
} else {
|
||
// Swipe down - prev slide
|
||
this.prevSlide();
|
||
}
|
||
}
|
||
}
|
||
|
||
setupProgressBar() {
|
||
const progressBar = document.getElementById('progressBar');
|
||
const updateProgress = () => {
|
||
const progress = ((this.currentSlide + 1) / this.slides.length) * 100;
|
||
progressBar.style.width = `${progress}%`;
|
||
};
|
||
|
||
updateProgress();
|
||
}
|
||
|
||
setupNavDots() {
|
||
const navDots = document.getElementById('navDots');
|
||
|
||
this.slides.forEach((_, index) => {
|
||
const dot = document.createElement('div');
|
||
dot.className = 'nav-dot';
|
||
if (index === 0) dot.classList.add('active');
|
||
|
||
dot.addEventListener('click', () => {
|
||
this.goToSlide(index);
|
||
});
|
||
|
||
navDots.appendChild(dot);
|
||
});
|
||
|
||
// Update active dot on slide change
|
||
const updateActiveDot = () => {
|
||
document.querySelectorAll('.nav-dot').forEach((dot, index) => {
|
||
dot.classList.toggle('active', index === this.currentSlide);
|
||
});
|
||
};
|
||
|
||
// Store update method
|
||
this.updateActiveDot = updateActiveDot;
|
||
this.updateActiveDot();
|
||
}
|
||
|
||
setupInlineEditing() {
|
||
if (!localStorage.getItem('enableInlineEditing') === 'true') return;
|
||
|
||
const editor = new InlineEditor();
|
||
window.editor = editor; // For debugging
|
||
}
|
||
|
||
nextSlide() {
|
||
if (this.currentSlide < this.slides.length - 1) {
|
||
this.goToSlide(this.currentSlide + 1);
|
||
}
|
||
}
|
||
|
||
prevSlide() {
|
||
if (this.currentSlide > 0) {
|
||
this.goToSlide(this.currentSlide - 1);
|
||
}
|
||
}
|
||
|
||
goToSlide(index) {
|
||
if (index < 0 || index >= this.slides.length || index === this.currentSlide) return;
|
||
|
||
this.isAnimating = true;
|
||
this.slides[this.currentSlide].scrollIntoView({ behavior: 'smooth' });
|
||
this.currentSlide = index;
|
||
this.updateActiveDot();
|
||
|
||
setTimeout(() => {
|
||
this.isAnimating = false;
|
||
}, 600);
|
||
}
|
||
}
|
||
|
||
/* ===========================================
|
||
INLINE EDITOR (Opt-in only)
|
||
=========================================== */
|
||
class InlineEditor {
|
||
constructor() {
|
||
this.isActive = false;
|
||
this.editToggle = document.getElementById('editToggle');
|
||
this.editHotzone = document.getElementById('editHotzone');
|
||
this.setupHotzone();
|
||
this.setupToggle();
|
||
this.setupKeyboardShortcut();
|
||
}
|
||
|
||
setupHotzone() {
|
||
let hideTimeout = null;
|
||
|
||
const showToggle = () => {
|
||
clearTimeout(hideTimeout);
|
||
this.editToggle.classList.add('show');
|
||
};
|
||
|
||
const hideToggle = () => {
|
||
hideTimeout = setTimeout(() => {
|
||
if (!this.isActive) this.editToggle.classList.remove('show');
|
||
}, 400);
|
||
};
|
||
|
||
this.editHotzone.addEventListener('mouseenter', showToggle);
|
||
this.editHotzone.addEventListener('mouseleave', hideToggle);
|
||
this.editToggle.addEventListener('mouseenter', showToggle);
|
||
this.editToggle.addEventListener('mouseleave', hideToggle);
|
||
|
||
this.editHotzone.addEventListener('click', () => this.toggleEditMode());
|
||
}
|
||
|
||
setupToggle() {
|
||
this.editToggle.addEventListener('click', () => this.toggleEditMode());
|
||
}
|
||
|
||
setupKeyboardShortcut() {
|
||
document.addEventListener('keydown', (e) => {
|
||
if ((e.key === 'e' || e.key === 'E') && !e.target.getAttribute('contenteditable')) {
|
||
this.toggleEditMode();
|
||
}
|
||
});
|
||
}
|
||
|
||
toggleEditMode() {
|
||
this.isActive = !this.isActive;
|
||
this.editToggle.classList.toggle('active', this.isActive);
|
||
|
||
if (this.isActive) {
|
||
this.enableEditing();
|
||
} else {
|
||
this.disableEditing();
|
||
}
|
||
}
|
||
|
||
enableEditing() {
|
||
document.querySelectorAll('h1, h2, h3, p, .card').forEach(element => {
|
||
if (!element.querySelector('code')) { // Don't edit code blocks
|
||
element.contentEditable = true;
|
||
element.classList.add('contenteditable');
|
||
}
|
||
});
|
||
|
||
// Auto-save on input
|
||
document.addEventListener('input', this.saveToStorage.bind(this));
|
||
this.loadFromStorage();
|
||
}
|
||
|
||
disableEditing() {
|
||
document.querySelectorAll('.contenteditable').forEach(element => {
|
||
element.contentEditable = false;
|
||
element.classList.remove('contenteditable');
|
||
});
|
||
|
||
// Clean up
|
||
document.removeEventListener('input', this.saveToStorage.bind(this));
|
||
this.saveToStorage();
|
||
}
|
||
|
||
saveToStorage() {
|
||
const content = {};
|
||
document.querySelectorAll('h1, h2, h3, p').forEach((element, index) => {
|
||
if (element.classList.contains('contenteditable')) {
|
||
content[index] = element.innerHTML;
|
||
}
|
||
});
|
||
localStorage.setItem('slideContent', JSON.stringify(content));
|
||
}
|
||
|
||
loadFromStorage() {
|
||
const saved = localStorage.getItem('slideContent');
|
||
if (saved) {
|
||
const content = JSON.parse(saved);
|
||
Object.entries(content).forEach(([index, html]) => {
|
||
const element = document.querySelector(`.contenteditable:nth-child(${parseInt(index) + 1})`);
|
||
if (element) element.innerHTML = html;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Initialize presentation */
|
||
const presentation = new SlidePresentation();
|
||
</script>
|
||
</body>
|
||
</html> |