From d8436e1d2872ea6ed23569a9ebd82ed397a807c5 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sat, 30 Aug 2025 16:55:24 +0900 Subject: [PATCH 1/8] =?UTF-8?q?wandbox=E3=81=AEAPI=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E3=81=A3=E3=81=A6C++=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/markdown.tsx | 62 +++++-- app/layout.tsx | 5 +- app/terminal/editor.tsx | 6 +- app/terminal/exec.tsx | 73 +++++++- app/terminal/wandbox/page.tsx | 34 ++++ app/terminal/wandbox/wandbox.tsx | 277 ++++++++++++++++++++++++++++++ package-lock.json | 23 +++ package.json | 1 + public/docs/cpp-2.md | 283 +++++++++++++++++++++++++++++++ 9 files changed, 741 insertions(+), 23 deletions(-) create mode 100644 app/terminal/wandbox/page.tsx create mode 100644 app/terminal/wandbox/wandbox.tsx create mode 100644 public/docs/cpp-2.md diff --git a/app/[docs_id]/markdown.tsx b/app/[docs_id]/markdown.tsx index 303b66e..4d2e560 100644 --- a/app/[docs_id]/markdown.tsx +++ b/app/[docs_id]/markdown.tsx @@ -3,8 +3,8 @@ import remarkGfm from "remark-gfm"; import { Prism as SyntaxHighlighter } from "react-syntax-highlighter"; import { PythonEmbeddedTerminal } from "../terminal/python/embedded"; import { Heading } from "./section"; -import { EditorComponent } from "../terminal/editor"; -import { ExecFile } from "../terminal/exec"; +import { AceLang, EditorComponent } from "../terminal/editor"; +import { ExecFile, ExecLang } from "../terminal/exec"; export function StyledMarkdown({ content }: { content: string }) { return ( @@ -52,21 +52,59 @@ const components: Components = { hello, world! --------------------------- */ - return ( -
- -
- ); + let execLang: ExecLang | undefined = undefined; + switch (match[1]) { + case "python": + execLang = "python"; + break; + case "cpp": + case "c++": + execLang = "cpp"; + break; + default: + console.warn(`Unsupported language for exec: ${match[1]}`); + break; + } + if (execLang) { + return ( +
+ +
+ ); + } } else if (match[3]) { // ファイル名指定がある場合、ファイルエディター + let aceLang: AceLang | undefined = undefined; + switch (match[1]) { + case "python": + aceLang = "python"; + break; + case "cpp": + case "c++": + aceLang = "c_cpp"; + break; + case "json": + aceLang = "json"; + break; + case "csv": + aceLang = "csv"; + break; + case "text": + case "txt": + aceLang = "text"; + break; + default: + console.warn(`Unsupported language for editor: ${match[1]}`); + break; + } return (
- {children} + + {children} +
diff --git a/app/terminal/editor.tsx b/app/terminal/editor.tsx index fdbe9bb..57d8c38 100644 --- a/app/terminal/editor.tsx +++ b/app/terminal/editor.tsx @@ -9,6 +9,7 @@ import "ace-builds/src-min-noconflict/theme-twilight"; import "ace-builds/src-min-noconflict/ext-language_tools"; import "ace-builds/src-min-noconflict/ext-searchbox"; import "ace-builds/src-min-noconflict/mode-python"; +import "ace-builds/src-min-noconflict/mode-c_cpp"; import "ace-builds/src-min-noconflict/mode-json"; import "ace-builds/src-min-noconflict/mode-csv"; import "ace-builds/src-min-noconflict/mode-text"; @@ -17,8 +18,11 @@ import { useSectionCode } from "../[docs_id]/section"; import clsx from "clsx"; // snippetを有効化するにはsnippetもimportする必要がある: import "ace-builds/src-min-noconflict/snippets/python"; +// mode-xxxx.js のファイル名と、AceEditorの mode プロパティの値が対応する +export type AceLang = "python" | "c_cpp" | "json" | "csv" | "text"; + interface EditorProps { - language?: string; + language?: AceLang; tabSize: number; filename: string; initContent: string; diff --git a/app/terminal/exec.tsx b/app/terminal/exec.tsx index 82ba51b..1563e5e 100644 --- a/app/terminal/exec.tsx +++ b/app/terminal/exec.tsx @@ -2,20 +2,26 @@ import chalk from "chalk"; import { usePyodide } from "./python/pyodide"; -import { clearTerminal, getRows, useTerminal } from "./terminal"; +import { clearTerminal, getRows, hideCursor, useTerminal } from "./terminal"; import { useSectionCode } from "../[docs_id]/section"; +import { useWandbox } from "./wandbox/wandbox"; + +export type ExecLang = "python" | "cpp"; interface ExecProps { - filename: string; - language: string; + /* + * Pythonの場合はメインファイル1つのみを指定する。 + * C++の場合はソースコード(.cpp)とヘッダー(.h)を全部指定し、ExecFile内で拡張子を元にソースコードと追加コードを分ける。 + */ + filenames: string[]; + language: ExecLang; content: string; } export function ExecFile(props: ExecProps) { const { terminalRef, terminalInstanceRef, termReady } = useTerminal({ getRows: (cols: number) => getRows(props.content, cols) + 1, onReady: () => { - // カーソル非表示 - terminalInstanceRef.current!.write("\x1b[?25l"); + hideCursor(terminalInstanceRef.current!); for (const line of props.content.split("\n")) { terminalInstanceRef.current!.writeln(line); } @@ -24,13 +30,17 @@ export function ExecFile(props: ExecProps) { const sectionContext = useSectionCode(); const pyodide = usePyodide(); + const wandbox = useWandbox(); let commandline: string; let exec: () => Promise | void; let runtimeInitializing: boolean; switch (props.language) { case "python": - commandline = `python ${props.filename}`; + if (props.filenames.length !== 1) { + throw new Error("Pythonの実行にはファイル名が1つ必要です"); + } + commandline = `python ${props.filenames[0]}`; runtimeInitializing = pyodide.initializing; exec = async () => { if (!pyodide.ready) { @@ -41,8 +51,12 @@ export function ExecFile(props: ExecProps) { await pyodide.init(); } clearTerminal(terminalInstanceRef.current!); - const outputs = await pyodide.runFile(props.filename); - for (const output of outputs) { + const outputs = await pyodide.runFile(props.filenames[0]); + for (let i = 0; i < outputs.length; i++) { + const output = outputs[i]; + if(i > 0) { + terminalInstanceRef.current!.writeln(""); + } // 出力内容に応じて色を変える const message = String(output.message).replace(/\n/g, "\r\n"); switch (output.type) { @@ -54,10 +68,51 @@ export function ExecFile(props: ExecProps) { break; } } - sectionContext?.setExecResult(props.filename, outputs); + sectionContext?.setExecResult(props.filenames[0], outputs); + }; + break; + case "cpp": + if (!props.filenames || props.filenames.length === 0) { + throw new Error("C++の実行には filenames プロパティが必要です"); + } + commandline = wandbox.cppOptions + ? `${wandbox.cppOptions.commandline} ${props.filenames.join(" ")} && ./a.out` + : ""; + runtimeInitializing = false; + exec = async () => { + clearTerminal(terminalInstanceRef.current!); + const namesSource = props.filenames!.filter((name) => + name.endsWith(".cpp") + ); + const namesAdditional = props.filenames!.filter( + (name) => !name.endsWith(".cpp") + ); + const outputs = await wandbox.runFiles( + "C++", + namesSource, + namesAdditional + ); + for (let i = 0; i < outputs.length; i++) { + const output = outputs[i]; + if(i > 0) { + terminalInstanceRef.current!.writeln(""); + } + // 出力内容に応じて色を変える + const message = String(output.message).replace(/\n/g, "\r\n"); + switch (output.type) { + case "error": + terminalInstanceRef.current!.write(chalk.red(message)); + break; + default: + terminalInstanceRef.current!.write(message); + break; + } + } + sectionContext?.setExecResult(props.filename!, outputs); }; break; default: + props.language satisfies never; commandline = `エラー: 非対応の言語 ${props.language}`; runtimeInitializing = false; exec = () => undefined; diff --git a/app/terminal/wandbox/page.tsx b/app/terminal/wandbox/page.tsx new file mode 100644 index 0000000..d3a201e --- /dev/null +++ b/app/terminal/wandbox/page.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { EditorComponent } from "../editor"; +import { ExecFile } from "../exec"; + +export default function WandboxPage() { + return ( +
+ + + + +
+ ); +} diff --git a/app/terminal/wandbox/wandbox.tsx b/app/terminal/wandbox/wandbox.tsx new file mode 100644 index 0000000..f513258 --- /dev/null +++ b/app/terminal/wandbox/wandbox.tsx @@ -0,0 +1,277 @@ +"use client"; + +import { + createContext, + ReactNode, + useCallback, + useContext, + useMemo, +} from "react"; +import { ReplOutput } from "../repl"; +import { useFile } from "../file"; +import useSWR, { Fetcher } from "swr"; + +type WandboxLang = "C++"; + +interface IWandboxContext { + // filesの中から、namesSourceで指定されたファイルをソースコードとして、namesAdditionalで指定されたファイルを追加コードとして渡して実行する + runFiles: ( + lang: WandboxLang, + namesSource: string[], + namesAdditional: string[] + ) => Promise; + cppOptions: WandboxOptions | null; +} +interface WandboxOptions { + compilerName: string; + compilerOptions: string[]; + commandline: string; +} + +const WandboxContext = createContext(null!); +export function useWandbox() { + const context = useContext(WandboxContext); + if (!context) { + throw new Error("useWandbox must be used within a WandboxProvider"); + } + return context; +} + +const WANDBOX = "https://wandbox.org"; +// https://github.com/melpon/wandbox/blob/ajax/kennel2/API.rst <- 古いけど、説明と例がある +// https://github.com/melpon/wandbox/blob/master/feline/src/types.rs +/* RustのVecはバイト配列ですが、serialize_with = "serialize_utf8"という指定があるため、 +JSONにシリアライズされる際にはUTF-8文字列に変換されると解釈し、TypeScriptの型はstringとします。 +by gemini +*/ +export interface SwitchOption { + name: string; + "display-flags": string; + "display-name": string; +} +export interface SwitchSingle { + type: "single"; + name: string; + "display-name": string; + "display-flags": string; + default: boolean; +} +export interface SwitchSelect { + type: "select"; + name: string; + options: SwitchOption[]; + default: string; +} +/** + * Rustの 'Switch' enum に対応するディスクリミネேテッドユニオン型です。 + * 'type' プロパティの値によって `SwitchSingle` か `SwitchSelect` かを判別できます。 + */ +export type Switch = SwitchSingle | SwitchSelect; +export interface CompilerInfo { + name: string; + version: string; + language: string; + "display-name": string; + templates: string[]; + "compiler-option-raw": boolean; + "runtime-option-raw": boolean; + "display-compile-command": string; + switches: Switch[]; +} +interface Code { + file: string; + code: string; +} +interface CompileParameter { + compiler: string; + code: string; + codes?: Code[]; + options?: string; + stdin?: string; + "compiler-option-raw"?: string; + "runtime-option-raw"?: string; + github_user?: string; + title?: string; + description?: string; + save?: boolean; + created_at?: number; + is_private?: boolean; + "compiler-info"?: CompilerInfo; +} +interface CompileResult { + status: string; + signal: string; + compiler_output: string; + compiler_error: string; + compiler_message: string; + program_output: string; + program_error: string; + program_message: string; + permlink: string; + url: string; +} + +const compilerInfoFetcher: Fetcher = () => + fetch(new URL("/api/list.json", WANDBOX)).then( + (res) => res.json() as Promise + ); + +export function WandboxProvider({ children }: { children: ReactNode }) { + const { files } = useFile(); + const { data: compilerList, error } = useSWR("list", compilerInfoFetcher); + if (error) { + console.error("Failed to fetch compiler list from Wandbox:", error); + } + const cppOptions = useMemo(() => { + if (!compilerList) { + return null; + } + const compilerListForLang = compilerList?.filter( + (c) => c.language === "C++" + ); + const compilerOptions: string[] = []; + const commandlineArgs: string[] = []; + + // headでない最初のgccを使う + const selectedCompiler = compilerListForLang.find( + (c) => c.name.includes("gcc") && !c.name.includes("head") + ); + if (!selectedCompiler) { + throw new Error("compiler not found"); + } + const compilerName = selectedCompiler.name; + // commandlineArgs.push(selectedCompiler["display-compile-command"]); + commandlineArgs.push("g++"); + + // singleオプション: + const warningSwitch = selectedCompiler.switches.find( + (s) => s.name === "warning" + ); + if (warningSwitch && warningSwitch.type === "single") { + compilerOptions.push("warning"); + commandlineArgs.push(warningSwitch["display-flags"]); + } else { + console.warn("warning switch not found"); + } + + // selectオプション: + for (const switchSelect of selectedCompiler.switches.filter( + (s) => s.type === "select" + )) { + // boostはnothing、stdは最新を選ぶ ほかはデフォルト + if (switchSelect.name.includes("boost")) { + const boostNothingOption = switchSelect.options.find((o) => + o.name.includes("nothing") + ); + if (boostNothingOption) { + compilerOptions.push(boostNothingOption.name); + commandlineArgs.push(boostNothingOption["display-flags"]); + } else { + console.warn("boost nothing option not found"); + } + } else if (switchSelect.name.includes("std")) { + const stdLatestOption = switchSelect.options + .filter((o) => o.name.startsWith("c++")) + .sort() + .reverse()[0]; + if (stdLatestOption) { + compilerOptions.push(stdLatestOption.name); + commandlineArgs.push(stdLatestOption["display-flags"]); + } else { + console.warn("std option not found"); + } + } else { + const defaultOption = switchSelect.options.find( + (o) => o.name === switchSelect.default + ); + compilerOptions.push(switchSelect.default); + commandlineArgs.push(defaultOption!["display-flags"]); + } + } + + return { + compilerName, + compilerOptions, + commandline: commandlineArgs.join(" "), + } satisfies WandboxOptions; + }, [compilerList]); + + const runFiles = useCallback( + async ( + lang: WandboxLang, + namesSource: string[], + namesAdditional: string[] + ) => { + let options: WandboxOptions | null; + switch (lang) { + case "C++": + options = cppOptions; + break; + default: + return [ + { + type: "error" as const, + message: `Unsupported language: ${lang}`, + }, + ]; + } + if (!options) { + return [ + { type: "error" as const, message: "Wandbox is not ready yet." }, + ]; + } + const result: CompileResult = await fetch( + new URL("/api/compile.json", WANDBOX), + { + method: "post", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + compiler: options.compilerName, + code: "", + codes: namesSource + .map((name) => ({ + file: name, + code: files[name] || "", + })) + .concat( + namesAdditional.map((name) => ({ + file: name, + code: files[name] || "", + })) + ), + options: options.compilerOptions.join(","), + stdin: "", + "compiler-option-raw": namesSource.join("\n"), + "runtime-option-raw": "", + save: false, + is_private: true, + } satisfies CompileParameter), + } + ).then((res) => res.json()); + + return [ + ...(result.compiler_output + ? [{ type: "stdout" as const, message: result.compiler_output }] + : []), + ...(result.compiler_error + ? [{ type: "error" as const, message: result.compiler_error }] + : []), + ...(result.program_output + ? [{ type: "stdout" as const, message: result.program_output }] + : []), + ...(result.program_error + ? [{ type: "error" as const, message: result.program_error }] + : []), + ] satisfies ReplOutput[]; + }, + [files, cppOptions] + ); + + return ( + + {children} + + ); +} diff --git a/package-lock.json b/package-lock.json index b34b94e..af61fd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "react-markdown": "^10.1.0", "react-syntax-highlighter": "^15.6.1", "remark-gfm": "^4.0.1", + "swr": "^2.3.6", "zod": "^4.0.17" }, "devDependencies": { @@ -19328,6 +19329,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.3.6.tgz", + "integrity": "sha512-wfHRmHWk/isGNMwlLGlZX5Gzz/uTgo0o2IRuTMcf4CPuPFJZlq0rDaKUx+ozB5nBOReNV1kiOyzMfj+MBMikLw==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.3", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/tailwindcss": { "version": "4.1.11", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", @@ -19837,6 +19851,15 @@ "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", "license": "MIT" }, + "node_modules/use-sync-external-store": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz", + "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", diff --git a/package.json b/package.json index 6ad4485..7c60a9c 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "react-markdown": "^10.1.0", "react-syntax-highlighter": "^15.6.1", "remark-gfm": "^4.0.1", + "swr": "^2.3.6", "zod": "^4.0.17" }, "devDependencies": { diff --git a/public/docs/cpp-2.md b/public/docs/cpp-2.md new file mode 100644 index 0000000..74001ed --- /dev/null +++ b/public/docs/cpp-2.md @@ -0,0 +1,283 @@ +# 第2章: C++の型システムとメモリ + +C++は**静的型付け言語**です。これは、コンパイル時にすべての変数の型が決定され、一度決まった型は変更できないことを意味します。この厳密な型システムは、一見すると面倒に感じるかもしれませんが、大規模なプログラムでもバグを未然に防ぎ、高いパフォーマンスを引き出すための重要な仕組みです。 + +この章では、C++の基本的な型と、他の高級言語ではあまり意識することのない「メモリ」との関係について学んでいきましょう。 + +## 基本データ型 + +他の多くの言語と同様に、C++にも数値を扱うための基本的なデータ型が用意されています。すでにご存知のものが多いと思いますが、C++における特徴と合わせて再確認しましょう。 + +| 型 (Type) | 説明 (Description) | サイズの例 (Typical Size) | 値の範囲の例 (Example Range) | +| :---------------- | :----------------------------------------------- | :---------------------- | :------------------------------------------------------- | +| `int` | 整数を格納します (Integer) | 4 bytes | `-2,147,483,648` ~ `2,147,483,647` | +| `double` | 倍精度浮動小数点数を格納します (Double-precision float) | 8 bytes | 約 `±1.7E308` (有効数字15桁程度) | +| `char` | 1文字を格納します (Character) | 1 byte | `-128` ~ `127` または `0` ~ `255` | +| `bool` | 真偽値を格納します (Boolean) | 1 byte | `true` または `false` | + +**ポイント**: C++の規格では、`int`が何バイトであるかといったサイズを厳密には定めていません。環境(OSやCPUアーキテクチャ)によって変わる可能性があります。しかし、多くのモダンな環境では上記のサイズが一般的です。 + +### 変数の宣言・代入・初期化 + +変数は、値を入れておくための「名前付きの箱」のようなものです。C++で変数を使うには、まず「どのような種類の箱(**型**)を、どんな名前で用意するか」をコンピュータに伝える必要があります。これを**宣言 (Declaration)** と呼びます。 + +```cpp +// 整数を入れるための'age'という名前の箱を宣言 +int age; +``` + +宣言した変数に値を入れることを**代入 (Assignment)** と言います。代入には `=` 記号を使います。 + +```cpp +// 宣言済みの変数 'age' に 30 を代入 +age = 30; +``` + +多くの場合、宣言と代入は同時に行います。これを**初期化 (Initialization)** と呼び、こちらの書き方が一般的で安全です。 + +```cpp:data_types.cpp +#include + +int main() { + // 宣言と同時に初期化 + int age = 30; + double pi = 3.14159; + char initial = 'A'; + bool is_student = true; + + std::cout << "Age: " << age << std::endl; + std::cout << "Pi: " << pi << std::endl; + std::cout << "Initial: " << initial << std::endl; + std::cout << "Is student? " << is_student << std::endl; // boolは通常 1 (true) または 0 (false) として出力される + + return 0; +} +``` + +```cpp-exec:data_types.cpp +Age: 30 +Pi: 3.14159 +Initial: A +Is student? 1 +``` + +## 基本的な演算 + +C++では、数値型の変数を使って基本的な算術計算ができます。 + +| 演算子 | 意味 | 例 | 結果 | +|:---:|:---|:---|:---:| +| `+` | 加算 | `5 + 2` | `7` | +| `-` | 減算 | `5 - 2` | `3` | +| `*` | 乗算 | `5 * 2` | `10` | +| `/` | 除算 | `5 / 2` | `2` | +| `%` | 剰余 | `5 % 2` | `1` | + +### ⚠️ 整数除算の注意点 + +ここで特に注意が必要なのが `/` (除算) です。**整数 (`int`) 同士の割り算の結果は、小数点以下が切り捨てられ、整数 (`int`) になります。** + +```cpp:integer_division.cpp +#include + +int main() { + int a = 7; + int b = 2; + + std::cout << "7 / 2 = " << a / b << std::endl; + + // 正しい計算結果(浮動小数点数)を得るには? + // 演算する値の少なくとも一方が浮動小数点数型である必要があります。 + double c = 7.0; + std::cout << "7.0 / 2 = " << c / b << std::endl; + + return 0; +} +``` + +```cpp-exec:integer_division.cpp +7 / 2 = 3 +7.0 / 2 = 3.5 +``` + +`7 / 2` が `3` になってしまうのは、`int` 型の `a` と `int` 型の `b` で演算した結果もまた `int` 型になる、というC++のルールのためです。小数点以下の値を得たい場合は、`7.0` のように、どちらかの値を `double` などの浮動小数点数型にする必要があります。 + +## 型を厳密に扱う + +静的型付けの恩恵を最大限に受けるために、C++には型をより安全かつ便利に扱うための仕組みがあります。 + +### `const`による不変性の保証 + +`const` (constantの略) は、変数を**読み取り専用**にするためのキーワードです。一度`const`で初期化された変数の値は、後から変更しようとするとコンパイルエラーになります。 + +なぜ`const`が重要なのでしょうか? + + * **安全性の向上**: 変更されるべきでない値を誤って変更してしまうバグを防ぎます。 + * **意図の明確化**: プログラムを読む人に対して、「この値は変わらない」という意図を明確に伝えられます。 + +円周率のように、プログラム中で決して変わることのない値に`const`を使うのが典型的な例です。 + +```cpp:const_example.cpp +#include + +int main() { + const double PI = 3.14159; + int radius = 5; + + double area = PI * radius * radius; + std::cout << "Area: " << area << std::endl; + + // PI = 3.14; // この行はコンパイルエラーになる! + + return 0; +} +``` +```cpp-exec:const_example.cpp +Area: 78.5397 +``` + +### `auto`による型推論 + +C++11から導入された`auto`キーワードを使うと、コンパイラが初期化式から変数の型を自動で推論してくれます。これにより、特に型名が長い場合にコードを簡潔に書くことができます。 + +```cpp +// autoを使わない場合 +std::vector::iterator it = my_vector.begin(); + +// autoを使う場合 +auto it = my_vector.begin(); // コンパイラが it の型を std::vector::iterator と推論してくれる +``` + +ただし、`auto`はあくまで「型を書く手間を省く」ものであり、変数が型を持たないわけではありません(動的型付け言語とは異なります)。初期化と同時に使う必要があり、型が明確な場面で適切に使うことが推奨されます。 + +```cpp +auto x = 10; // x は int型になる +auto y = 3.14; // y は double型になる +auto z = "hello"; // z は const char* (C言語スタイルの文字列) になるので注意 +``` + +## C++の文字列: `std::string` + +C言語では文字列を`char`の配列(`char*`)として扱いましたが、これは扱いにくく、バグの温床でした。モダンC++では、**`std::string`** クラスを使うのが標準的です。`std::string`は、文字列の連結、長さの取得、部分文字列の取り出しといった操作を安全かつ簡単に行うための豊富な機能を提供します。 + +`std::string`を使うには、``ヘッダをインクルードする必要があります。 + +```cpp:string_example.cpp +#include +#include // std::string を使うために必要 + +int main() { + // 文字列の宣言と初期化 + std::string greeting = "Hello"; + + // 文字列の連結 + std::string name = "C++"; + std::string message = greeting + ", " + name + "!"; + + std::cout << message << std::endl; + + // 文字列の長さを取得 + std::cout << "Length: " << message.length() << std::endl; + + return 0; +} +``` + +```cpp-exec:string_example.cpp +Hello, C++! +Length: 11 +``` + +## 変数とメモリ + +さて、C++を深く理解する上で避けて通れないのが**メモリ**の概念です。変数を宣言すると、コンピュータのメモリ上にその型に応じたサイズの領域が確保されます。 + +例えば、`int x = 10;` と書くと、 + +1. コンパイラは`int`型に必要なメモリサイズ(例: 4バイト)を判断します。 +2. プログラム実行時、メモリ上のどこかに4バイトの領域が確保されます。 +3. その領域に`x`という名前が割り当てられ、値として`10`が格納(バイナリ形式で書き込み)されます。 + +この「メモリ上のどこか」を示すのが**メモリアドレス**(単にアドレスとも)です。アドレスは、メモリ上の各バイトに割り振られた通し番号のようなもので、通常は16進数で表現されます。 + +変数名の前に`&`(アドレス演算子)を付けることで、その変数が格納されているメモリアドレスを知ることができます。 + +```cpp:memory_address.cpp +#include +#include + +int main() { + int age = 30; + double pi = 3.14; + std::string name = "Alice"; + + std::cout << "変数 'age' の値: " << age << std::endl; + std::cout << "変数 'age' のメモリアドレス: " << &age << std::endl; + std::cout << std::endl; + + std::cout << "変数 'pi' の値: " << pi << std::endl; + std::cout << "変数 'pi' のメモリアドレス: " << &pi << std::endl; + std::cout << std::endl; + + std::cout << "変数 'name' の値: " << name << std::endl; + std::cout << "変数 'name' のメモリアドレス: " << &name << std::endl; + + return 0; +} +``` +```cpp-exec:memory_address.cpp +変数 'age' の値: 30 +変数 'age' のメモリアドレス: 0x7ffee3b8c9ac + +変数 'pi' の値: 3.14 +変数 'pi' のメモリアドレス: 0x7ffee3b8c9a0 + +変数 'name' の値: Alice +変数 'name' のメモリアドレス: 0x7ffee3b8c990 +``` + +このコードを実行すると、`0x7ffee...` のようなアドレスが表示されるはずです(値は実行のたびに変わります)。今は「変数はメモリ上の特定の場所に、特定のサイズで存在している」というイメージを持つことが重要です。この概念は、第4章で学ぶポインタを理解するための基礎となります。 + +## この章のまとめ + + * C++は**静的型付け言語**であり、コンパイル時にすべての変数の型が決まる。 + * `int`, `double`, `char`, `bool` といった基本的なデータ型が存在する。 + * 変数は**宣言**してから使い、宣言と同時に値を代入する**初期化**が一般的。 + * 基本的な**四則演算**ができるが、**整数同士の除算**は結果が整数に切り捨てられる点に注意。 + * **`const`** を使うことで、変数を不変にし、プログラムの安全性を高めることができる。 + * **`auto`** を使うことで、コンパイラに型を推論させ、コードを簡潔に書くことができる。 + * モダンC++では、文字列は\*\*`std::string`\*\* クラスを使って安全かつ便利に扱う。 + * 宣言された変数は、メモリ上の特定の**アドレス**に、その型に応じた**サイズ**の領域を確保して格納される。 + +### 練習問題1 + +あなたの名前(`std::string`)、年齢(`int`)、視力(`double`)をそれぞれ変数として宣言し、コンソールに出力するプログラムを書いてください。ただし、名前は一度決めたら変わらないものとして、`const`を使って宣言してください。 + +```cpp:practice2_1.cpp +#include +#include + +int main() { + +} +``` + +```cpp-exec:practice2_1.cpp +``` + + +### 練習問題2 + +2つの`std::string`変数 `firstName` と `lastName` を宣言し、あなたの姓名で初期化してください。その後、これら2つの変数を連結してフルネームを`fullName`という新しい変数に格納し、そのフルネームと文字数(長さ)をコンソールに出力するプログラムを書いてください。 + +```cpp:practice2_2.cpp +#include +#include + +int main() { + +} +``` + +```cpp-exec:practice2_2.cpp +``` From 2ce71682baaa7c4660a3ee628070f1c9828d3b1d Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:49:29 +0900 Subject: [PATCH 2/8] =?UTF-8?q?table=E3=81=AE=E8=A6=8B=E3=81=9F=E7=9B=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/markdown.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/[docs_id]/markdown.tsx b/app/[docs_id]/markdown.tsx index 4d2e560..cacf58f 100644 --- a/app/[docs_id]/markdown.tsx +++ b/app/[docs_id]/markdown.tsx @@ -34,6 +34,11 @@ const components: Components = { strong: ({ node, ...props }) => ( ), + table: ({ node, ...props }) => ( +
+ + + ), hr: ({ node, ...props }) =>
, pre: ({ node, ...props }) => props.children, code: ({ node, className, ref, style, ...props }) => { From 1d714bc348c37b26f61c3832f8c1a66aefb812ef Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:54:55 +0900 Subject: [PATCH 3/8] =?UTF-8?q?=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E3=82=A8=E3=83=A9=E3=83=BC=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/[docs_id]/section.tsx | 1 + app/terminal/exec.tsx | 3 ++- app/terminal/python/page.tsx | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/[docs_id]/section.tsx b/app/[docs_id]/section.tsx index 138e93a..71dbf80 100644 --- a/app/[docs_id]/section.tsx +++ b/app/[docs_id]/section.tsx @@ -15,6 +15,7 @@ import { useFile } from "../terminal/file"; // セクション内に埋め込まれているターミナルとファイルエディターの内容をSection側から取得できるよう、 // Contextに保存する +// TODO: C++では複数ファイルを実行する場合がありうるが、ここではfilenameを1つしか受け付けない想定になっている interface ISectionCodeContext { addReplOutput: (command: string, output: ReplOutput[]) => void; addFile: (filename: string) => void; diff --git a/app/terminal/exec.tsx b/app/terminal/exec.tsx index 1563e5e..5ea9a9b 100644 --- a/app/terminal/exec.tsx +++ b/app/terminal/exec.tsx @@ -108,7 +108,8 @@ export function ExecFile(props: ExecProps) { break; } } - sectionContext?.setExecResult(props.filename!, outputs); + // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる + sectionContext?.setExecResult(props.filenames.join(","), outputs); }; break; default: diff --git a/app/terminal/python/page.tsx b/app/terminal/python/page.tsx index e4f8c8b..0e7f6c1 100644 --- a/app/terminal/python/page.tsx +++ b/app/terminal/python/page.tsx @@ -29,7 +29,7 @@ export default function PythonPage() { filename="main.py" initContent="print('hello, world!')" /> - + ); } From 83b8765397b762242a743be539ab70598ae74fa7 Mon Sep 17 00:00:00 2001 From: na-trium-144 <100704180+na-trium-144@users.noreply.github.com> Date: Sun, 31 Aug 2025 16:26:30 +0900 Subject: [PATCH 4/8] =?UTF-8?q?=E7=95=B0=E5=B8=B8=E7=B5=82=E4=BA=86?= =?UTF-8?q?=E6=99=82=E3=81=AE=E8=A1=A8=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/terminal/exec.tsx | 115 +++++++++++++------------------ app/terminal/repl.tsx | 56 +++++++++++---- app/terminal/terminal.tsx | 3 + app/terminal/wandbox/wandbox.tsx | 57 +++++++++++---- 4 files changed, 136 insertions(+), 95 deletions(-) diff --git a/app/terminal/exec.tsx b/app/terminal/exec.tsx index 5ea9a9b..c6b3ee7 100644 --- a/app/terminal/exec.tsx +++ b/app/terminal/exec.tsx @@ -1,10 +1,17 @@ "use client"; -import chalk from "chalk"; import { usePyodide } from "./python/pyodide"; -import { clearTerminal, getRows, hideCursor, useTerminal } from "./terminal"; +import { + clearTerminal, + getRows, + hideCursor, + systemMessageColor, + useTerminal, +} from "./terminal"; import { useSectionCode } from "../[docs_id]/section"; import { useWandbox } from "./wandbox/wandbox"; +import { ReplOutput, writeOutput } from "./repl"; +import { useState } from "react"; export type ExecLang = "python" | "cpp"; @@ -32,9 +39,16 @@ export function ExecFile(props: ExecProps) { const pyodide = usePyodide(); const wandbox = useWandbox(); + // 表示するコマンドライン文字列 let commandline: string; - let exec: () => Promise | void; + // trueの間 (初期化しています...) と表示される let runtimeInitializing: boolean; + // 初期化処理が必要な場合の関数 + let beforeExec: (() => Promise) | null = null; + // 実行中です... と表示される + const [isExecuting, setIsExecuting] = useState(false); + // 実際に実行する関数 + let exec: (() => Promise) | null = null; switch (props.language) { case "python": if (props.filenames.length !== 1) { @@ -42,34 +56,8 @@ export function ExecFile(props: ExecProps) { } commandline = `python ${props.filenames[0]}`; runtimeInitializing = pyodide.initializing; - exec = async () => { - if (!pyodide.ready) { - clearTerminal(terminalInstanceRef.current!); - terminalInstanceRef.current!.write( - chalk.dim.bold.italic("(初期化しています...しばらくお待ちください)") - ); - await pyodide.init(); - } - clearTerminal(terminalInstanceRef.current!); - const outputs = await pyodide.runFile(props.filenames[0]); - for (let i = 0; i < outputs.length; i++) { - const output = outputs[i]; - if(i > 0) { - terminalInstanceRef.current!.writeln(""); - } - // 出力内容に応じて色を変える - const message = String(output.message).replace(/\n/g, "\r\n"); - switch (output.type) { - case "error": - terminalInstanceRef.current!.writeln(chalk.red(message)); - break; - default: - terminalInstanceRef.current!.writeln(message); - break; - } - } - sectionContext?.setExecResult(props.filenames[0], outputs); - }; + beforeExec = pyodide.ready ? null : pyodide.init; + exec = () => pyodide.runFile(props.filenames[0]); break; case "cpp": if (!props.filenames || props.filenames.length === 0) { @@ -79,52 +67,47 @@ export function ExecFile(props: ExecProps) { ? `${wandbox.cppOptions.commandline} ${props.filenames.join(" ")} && ./a.out` : ""; runtimeInitializing = false; - exec = async () => { - clearTerminal(terminalInstanceRef.current!); - const namesSource = props.filenames!.filter((name) => - name.endsWith(".cpp") - ); - const namesAdditional = props.filenames!.filter( - (name) => !name.endsWith(".cpp") - ); - const outputs = await wandbox.runFiles( - "C++", - namesSource, - namesAdditional - ); - for (let i = 0; i < outputs.length; i++) { - const output = outputs[i]; - if(i > 0) { - terminalInstanceRef.current!.writeln(""); - } - // 出力内容に応じて色を変える - const message = String(output.message).replace(/\n/g, "\r\n"); - switch (output.type) { - case "error": - terminalInstanceRef.current!.write(chalk.red(message)); - break; - default: - terminalInstanceRef.current!.write(message); - break; - } - } - // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる - sectionContext?.setExecResult(props.filenames.join(","), outputs); - }; + const namesSource = props.filenames!.filter((name) => + name.endsWith(".cpp") + ); + const namesAdditional = props.filenames!.filter( + (name) => !name.endsWith(".cpp") + ); + exec = () => wandbox.runFiles("C++", namesSource, namesAdditional); break; default: props.language satisfies never; commandline = `エラー: 非対応の言語 ${props.language}`; runtimeInitializing = false; - exec = () => undefined; break; } + + const onClick = async () => { + if (exec) { + if (beforeExec) { + clearTerminal(terminalInstanceRef.current!); + terminalInstanceRef.current!.write( + systemMessageColor("(初期化しています...しばらくお待ちください)") + ); + await beforeExec(); + } + clearTerminal(terminalInstanceRef.current!); + terminalInstanceRef.current!.write(systemMessageColor("実行中です...")); + setIsExecuting(true); + const outputs = await exec(); + setIsExecuting(false); + clearTerminal(terminalInstanceRef.current!); + writeOutput(terminalInstanceRef.current!, outputs, false); + // TODO: 1つのファイル名しか受け付けないところに無理やりコンマ区切りで全部のファイル名を突っ込んでいる + sectionContext?.setExecResult(props.filenames.join(","), outputs); + } + }; return (