diff --git a/.gitignore b/.gitignore index 7eaedd6..11f8e33 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,12 @@ /.open-next /cloudflare-env.d.ts +# generated docs section file lists (regenerated by npm run generateSections) +/public/docs/**/sections.yml + +# generated languages list (regenerated by npm run generateLanguages) +/public/docs/languages.yml + # dependencies /node_modules /.pnp diff --git a/README.md b/README.md index 9e9d64b..ad4d460 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,49 @@ npm run lint 現在は本番環境(my-code.utcode.net)はCoolifyでデプロイしています。 Cloudflare Worker のビルドログとステータス表示が見れますが、そちらは使っていません。 -## ベースとなるドキュメントの作り方 +## ドキュメント + +* ドキュメントはセクション(見出し)ごとにわけ、 public/docs/言語id/ページid/並び替え用連番-セクション名.md に置く。 +* ページはディレクトリの名前によらず 言語id/index.yml に書かれている順で表示される。 +* セクションはセクションIDによらずファイル名順で表示される。 +* 各セクションのfrontmatter (各ページ最初のセクション(-intro.md)を除く) + ```yml + id: 一意なセクションID。ファイル名・ディレクトリ名と一致していなくても良い。バックエンドがこのセクションIDで識別するので、一度コミットしたら変更不可(ファイル名は何度変えても良い) + title: セクションタイトルと同じものをmd記法を使わずに書いた文字列 + level: セクションの見出しレベル(2〜6が使用可) + ``` +* コード例はそれが配置されているセクションの内容と関連するようにする。 + ````md + ## 制御構文 + ### if + if文の説明… + ### switch + switch文の説明… + + ifとswitch共通の注意事項... → ## 制御構文 に移す + ``` + ifとswitchを使ったコード例… → 2つのコード例に分割する + (分割が難しい(かつ説明が短い)場合は、1つのセクションで全部説明してコード例を載せるのでもよい) + ``` + ```` +* コード例や注意事項などを不必要に独立したセクションにするのは避ける + ````md + ## hoge + hogeの説明… + ### hogeの使用場面 →この見出しいらない + **hogeの使用場面** はok + - 説明 + - 説明 + ### hogeの使用例 →この見出しいらない + ``` + hogeを使ったコード例… + ``` + ```` +* REPLのコード例は1セクションに最大1つまで。 + * コードエディターとコード実行ブロックはいくつでも置けます。 +* ページ0以外の各ページの最後はレベル2見出し「この章のまとめ」と、レベル3見出し「練習問題n」を置く + +### ベースとなるドキュメントの作り方 - web版の ~~Gemini2.5Pro~~ Gemini3Pro を用いる。 - 以下のプロンプトで章立てを考えさせる @@ -107,7 +149,6 @@ Cloudflare Worker のビルドログとステータス表示が見れますが Hello ``` ```` - - 練習問題の見出しは「この章のまとめ」の直下のレベル3見出しで、 `### 練習問題n` または `### 練習問題n: タイトル` とする - 練習問題のファイル名は不都合がなければ `practice(章番号)_(問題番号).拡張子` で統一。空でもよいのでファイルコードブロックとexecコードブロックを置く - 1章にはたぶん練習問題要らない。 diff --git a/app/[docs_id]/page.tsx b/app/[docs_id]/page.tsx deleted file mode 100644 index 87470a0..0000000 --- a/app/[docs_id]/page.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import { Metadata } from "next"; -import { notFound } from "next/navigation"; -import { getCloudflareContext } from "@opennextjs/cloudflare"; -import { readFile } from "node:fs/promises"; -import { join } from "node:path"; -import { splitMarkdown } from "./splitMarkdown"; -import { PageContent } from "./pageContent"; -import { ChatHistoryProvider } from "./chatHistory"; -import { getChatFromCache, initContext } from "@/lib/chatHistory"; -import { getLanguageName, pagesList } from "@/pagesList"; -import { isCloudflare } from "@/lib/detectCloudflare"; - -async function getMarkdownContent(docs_id: string): Promise { - try { - if (isCloudflare()) { - const cfAssets = getCloudflareContext().env.ASSETS; - const res = await cfAssets!.fetch( - `https://assets.local/docs/${docs_id}.md` - ); - if (!res.ok) { - notFound(); - } - return await res.text(); - } else { - return await readFile( - join(process.cwd(), "public", "docs", `${docs_id}.md`), - "utf-8" - ); - } - } catch (e) { - console.error(e); - notFound(); - } -} - -export async function generateMetadata({ - params, -}: { - params: Promise<{ docs_id: string }>; -}): Promise { - const { docs_id } = await params; - const mdContent = await getMarkdownContent(docs_id); - const splitMdContent = splitMarkdown(mdContent); - - // 先頭の 第n章: を除いたものをタイトルとする - const title = splitMdContent[0]?.title?.split(" ").slice(1).join(" "); - - const description = splitMdContent[0].content; - - const chapter = docs_id.split("-")[1]; - - return { - title: `${getLanguageName(docs_id)}-${chapter}. ${title}`, - description, - }; -} - -export default async function Page({ - params, -}: { - params: Promise<{ docs_id: string }>; -}) { - const { docs_id } = await params; - - if ( - !pagesList - .find((lang) => docs_id.startsWith(`${lang.id}-`)) - ?.pages.find((page) => docs_id.endsWith(`-${page.id}`)) - ) { - notFound(); - } - - const mdContent = getMarkdownContent(docs_id); - const splitMdContent = mdContent.then((text) => splitMarkdown(text)); - const context = await initContext(); - const initialChatHistories = getChatFromCache(docs_id, context); - - return ( - - - - ); -} diff --git a/app/[docs_id]/splitMarkdown.ts b/app/[docs_id]/splitMarkdown.ts deleted file mode 100644 index 0af31c8..0000000 --- a/app/[docs_id]/splitMarkdown.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { unified } from "unified"; -import remarkParse from "remark-parse"; -import remarkGfm from "remark-gfm"; - -export interface MarkdownSection { - level: number; - title: string; - content: string; - rawContent: string; // 見出しも含めたもとのmarkdownの内容 -} -/** - * Markdownコンテンツを見出しごとに分割し、 - * 見出しのレベルとタイトル、内容を含むオブジェクトの配列を返す。 - */ -export function splitMarkdown(content: string): MarkdownSection[] { - const tree = unified().use(remarkParse).use(remarkGfm).parse(content); - // console.log(tree.children.map(({ type, position }) => ({ type, position: JSON.stringify(position) }))); - const headingNodes = tree.children.filter((node) => node.type === "heading"); - const splitContent = content.split("\n"); - const sections: MarkdownSection[] = []; - for (let i = 0; i < headingNodes.length; i++) { - const startLine = headingNodes.at(i)?.position?.start.line; - if (startLine === undefined) { - continue; - } - let endLine: number | undefined = undefined; - for (let j = i + 1; j < headingNodes.length; j++) { - if (headingNodes.at(j)?.position?.start.line !== undefined) { - endLine = headingNodes.at(j)!.position!.start.line; - break; - } - } - sections.push({ - title: splitContent[startLine - 1].replace(/#+\s*/, "").trim(), - content: splitContent - .slice(startLine - 1 + 1, endLine ? endLine - 1 : undefined) - .join("\n") - .trim(), - level: headingNodes.at(i)!.depth, - rawContent: splitContent - .slice(startLine - 1, endLine ? endLine - 1 : undefined) - .join("\n") - .trim(), - }); - } - return sections; -} diff --git a/app/[docs_id]/chatForm.tsx b/app/[lang]/[pageId]/chatForm.tsx similarity index 98% rename from app/[docs_id]/chatForm.tsx rename to app/[lang]/[pageId]/chatForm.tsx index fa96b15..85d8138 100644 --- a/app/[docs_id]/chatForm.tsx +++ b/app/[lang]/[pageId]/chatForm.tsx @@ -8,7 +8,7 @@ import { useState, FormEvent, useEffect } from "react"; // } from "../actions/questionExample"; // import { getLanguageName } from "../pagesList"; import { DynamicMarkdownSection } from "./pageContent"; -import { useEmbedContext } from "../terminal/embedContext"; +import { useEmbedContext } from "@/terminal/embedContext"; import { useChatHistoryContext } from "./chatHistory"; import { askAI } from "@/actions/chatActions"; diff --git a/app/[docs_id]/chatHistory.tsx b/app/[lang]/[pageId]/chatHistory.tsx similarity index 100% rename from app/[docs_id]/chatHistory.tsx rename to app/[lang]/[pageId]/chatHistory.tsx diff --git a/app/[docs_id]/loading.tsx b/app/[lang]/[pageId]/loading.tsx similarity index 100% rename from app/[docs_id]/loading.tsx rename to app/[lang]/[pageId]/loading.tsx diff --git a/app/[docs_id]/markdown.tsx b/app/[lang]/[pageId]/markdown.tsx similarity index 97% rename from app/[docs_id]/markdown.tsx rename to app/[lang]/[pageId]/markdown.tsx index 4cf98e5..05b446c 100644 --- a/app/[docs_id]/markdown.tsx +++ b/app/[lang]/[pageId]/markdown.tsx @@ -2,8 +2,8 @@ import Markdown, { Components, ExtraProps } from "react-markdown"; import remarkGfm from "remark-gfm"; import removeComments from "remark-remove-comments"; import remarkCjkFriendly from "remark-cjk-friendly"; -import { EditorComponent, getAceLang } from "../terminal/editor"; -import { ExecFile } from "../terminal/exec"; +import { EditorComponent, getAceLang } from "@/terminal/editor"; +import { ExecFile } from "@/terminal/exec"; import { JSX, ReactNode } from "react"; import { getRuntimeLang } from "@/terminal/runtime"; import { ReplTerminal } from "@/terminal/repl"; @@ -64,6 +64,8 @@ export function Heading({ children: ReactNode; }) { switch (level) { + case 0: + return null; case 1: return

{children}

; case 2: diff --git a/app/[lang]/[pageId]/page.tsx b/app/[lang]/[pageId]/page.tsx new file mode 100644 index 0000000..d4391ad --- /dev/null +++ b/app/[lang]/[pageId]/page.tsx @@ -0,0 +1,63 @@ +import { Metadata } from "next"; +import { notFound } from "next/navigation"; +import { PageContent } from "./pageContent"; +import { ChatHistoryProvider } from "./chatHistory"; +import { getChatFromCache, initContext } from "@/lib/chatHistory"; +import { getMarkdownSections, getPagesList } from "@/lib/docs"; + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string; pageId: string }>; +}): Promise { + const { lang, pageId } = await params; + const pagesList = await getPagesList(); + const langEntry = pagesList.find((l) => l.id === lang); + const pageEntry = langEntry?.pages.find((p) => p.slug === pageId); + if (!langEntry || !pageEntry) notFound(); + + const sections = await getMarkdownSections(lang, pageId); + const description = sections[0].rawContent; + + return { + title: `${langEntry!.name}-${pageEntry.index}. ${pageEntry.title}`, + description, + }; +} + +export default async function Page({ + params, +}: { + params: Promise<{ lang: string; pageId: string }>; +}) { + const { lang, pageId } = await params; + const pagesList = await getPagesList(); + const langEntry = pagesList.find((l) => l.id === lang); + const pageEntry = langEntry?.pages.find((p) => p.slug === pageId); + if (!langEntry || !pageEntry) notFound(); + + const docsId = `${lang}/${pageId}`; + const sections = await getMarkdownSections(lang, pageId); + + // AI用のドキュメント全文(rawContentを結合) + const documentContent = sections.map((s) => s.rawContent).join("\n"); + + const context = await initContext(); + const initialChatHistories = await getChatFromCache(docsId, context); + + return ( + + + + ); +} diff --git a/app/[docs_id]/pageContent.tsx b/app/[lang]/[pageId]/pageContent.tsx similarity index 81% rename from app/[docs_id]/pageContent.tsx rename to app/[lang]/[pageId]/pageContent.tsx index f5968db..fd8ac65 100644 --- a/app/[docs_id]/pageContent.tsx +++ b/app/[lang]/[pageId]/pageContent.tsx @@ -1,22 +1,25 @@ "use client"; import { Fragment, useEffect, useRef, useState } from "react"; -import { MarkdownSection } from "./splitMarkdown"; import { ChatForm } from "./chatForm"; import { Heading, StyledMarkdown } from "./markdown"; import { useChatHistoryContext } from "./chatHistory"; -import { useSidebarMdContext } from "../sidebar"; +import { useSidebarMdContext } from "@/sidebar"; import clsx from "clsx"; +import { MarkdownSection, PageEntry } from "@/lib/docs"; // MarkdownSectionに追加で、ユーザーが今そのセクションを読んでいるかどうか、などの動的な情報を持たせる export type DynamicMarkdownSection = MarkdownSection & { inView: boolean; - sectionId: string; }; interface PageContentProps { documentContent: string; splitMdContent: MarkdownSection[]; + pageEntry: PageEntry; + lang: string; + pageId: string; + // TODO: チャット周りのid管理をsectionIdに移行し、docs_idパラメータを削除 docs_id: string; } export function PageContent(props: PageContentProps) { @@ -26,23 +29,21 @@ export function PageContent(props: PageContentProps) { const [dynamicMdContent, setDynamicMdContent] = useState< DynamicMarkdownSection[] >( - props.splitMdContent.map((section, i) => ({ + props.splitMdContent.map((section) => ({ ...section, inView: false, - sectionId: `${props.docs_id}-${i}`, })) ); useEffect(() => { // props.splitMdContentが変わったときにローカルstateとcontextの両方を更新 - const newContent = props.splitMdContent.map((section, i) => ({ + const newContent = props.splitMdContent.map((section) => ({ ...section, inView: false, - sectionId: `${props.docs_id}-${i}`, })); setDynamicMdContent(newContent); - setSidebarMdContent(props.docs_id, newContent); - }, [props.splitMdContent, props.docs_id, setSidebarMdContent]); + setSidebarMdContent(props.lang, props.pageId, newContent); + }, [props.splitMdContent, props.lang, props.pageId, setSidebarMdContent]); const sectionRefs = useRef>([]); // sectionRefsの長さをsplitMdContentに合わせる @@ -69,14 +70,14 @@ export function PageContent(props: PageContentProps) { // ローカルstateとcontextの両方を更新 setDynamicMdContent(updateContent); - setSidebarMdContent(props.docs_id, updateContent); + setSidebarMdContent(props.lang, props.pageId, updateContent); }; window.addEventListener("scroll", handleScroll); handleScroll(); return () => { window.removeEventListener("scroll", handleScroll); }; - }, [setSidebarMdContent, props.docs_id]); + }, [setSidebarMdContent, props.lang, props.pageId]); const [isFormVisible, setIsFormVisible] = useState(false); @@ -89,23 +90,31 @@ export function PageContent(props: PageContentProps) { gridTemplateColumns: `1fr auto`, }} > + + 第{props.pageEntry.index}章: {props.pageEntry.title} + +
{dynamicMdContent.map((section, index) => ( - +
{ sectionRefs.current[index] = el; }} > {/* ドキュメントのコンテンツ */} - {section.title} - +
{/* 右側に表示するチャット履歴欄 */} {chatHistories - .filter((c) => c.sectionId === section.sectionId) + .filter((c) => c.sectionId === section.id) .map(({ chatId, messages }) => (
{ } // TODO: どのセクションへの回答にするかをAIに決めさせる const targetSectionId = - sectionContent.find((s) => s.inView)?.sectionId || ""; + sectionContent.find((s) => s.inView)?.id || ""; const newChat = await addChat(params.docsId, targetSectionId, [ { role: "user", content: userQuestion }, { role: "ai", content: text }, diff --git a/app/layout.tsx b/app/layout.tsx index bd0f049..8139b10 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -12,6 +12,7 @@ import { EmbedContextProvider } from "./terminal/embedContext"; import { AutoAnonymousLogin } from "./accountMenu"; import { SidebarMdProvider } from "./sidebar"; import { RuntimeProvider } from "./terminal/runtime"; +import { getPagesList } from "@/lib/docs"; export const metadata: Metadata = { title: { @@ -20,9 +21,10 @@ export const metadata: Metadata = { }, }; -export default function RootLayout({ +export default async function RootLayout({ children, }: Readonly<{ children: ReactNode }>) { + const pagesList = await getPagesList(); return ( @@ -36,7 +38,7 @@ export default function RootLayout({ className="drawer-toggle" />
- + {children} @@ -47,7 +49,7 @@ export default function RootLayout({ aria-label="close sidebar" className="drawer-overlay" /> - +
diff --git a/app/lib/docs.ts b/app/lib/docs.ts new file mode 100644 index 0000000..a59c81c --- /dev/null +++ b/app/lib/docs.ts @@ -0,0 +1,148 @@ +import { getCloudflareContext } from "@opennextjs/cloudflare"; +import { readFile, readdir } from "node:fs/promises"; +import { join } from "node:path"; +import yaml from "js-yaml"; +import { isCloudflare } from "./detectCloudflare"; +import { notFound } from "next/navigation"; + +export interface MarkdownSection { + id: string; + level: number; + title: string; + rawContent: string; // 見出しも含めたもとのmarkdownの内容 +} + +export interface PageEntry { + index: number; + slug: string; + name: string; + title: string; +} + +export interface LanguageEntry { + id: string; + name: string; + description: string; + pages: PageEntry[]; +} + +interface IndexYml { + name: string; + description: string; + pages: { + slug: string; + name: string; + title: string; + }[]; +} + +async function readPublicFile(path: string): Promise { + try { + if (isCloudflare()) { + const cfAssets = getCloudflareContext().env.ASSETS; + const res = await cfAssets!.fetch(`https://assets.local/${path}`); + if (!res.ok) { + console.error( + `Failed to fetch ${path}: ${res.status} ${await res.text()}` + ); + notFound(); + } + return await res.text(); + } else { + return await readFile(join(process.cwd(), "public", path), "utf-8"); + } + } catch (e) { + console.error(`Failed to read file ${path}: ${e}`); + notFound(); + } +} + +async function getLanguageIds(): Promise { + if (isCloudflare()) { + const raw = await readPublicFile("docs/languages.yml"); + return yaml.load(raw) as string[]; + } else { + const docsDir = join(process.cwd(), "public", "docs"); + const entries = await readdir(docsDir, { withFileTypes: true }); + return entries + .filter((e) => e.isDirectory()) + .map((e) => e.name) + .sort(); + } +} + +export async function getPagesList(): Promise { + const langIds = await getLanguageIds(); + return await Promise.all( + langIds.map(async (langId) => { + const raw = await readPublicFile(`docs/${langId}/index.yml`); + const data = yaml.load(raw) as IndexYml; + return { + id: langId, + name: data.name, + description: data.description, + pages: data.pages.map((p, index) => ({ + ...p, + index, + })), + }; + }) + ); +} + +/** + * public/docs/{lang}/{pageId}/ 以下のmdファイルを結合して MarkdownSection[] を返す。 + */ +export async function getMarkdownSections( + lang: string, + pageId: string +): Promise { + const sectionsYml = await readPublicFile( + `docs/${lang}/${pageId}/sections.yml` + ); + const files = yaml.load(sectionsYml) as string[]; + + const sections: MarkdownSection[] = []; + for (const file of files) { + const raw = await readPublicFile(`docs/${lang}/${pageId}/${file}`); + if (file === "-intro.md") { + // イントロセクションはフロントマターなし・見出しなし + sections.push({ + id: `${lang}-${pageId}-intro`, + level: 1, + title: "", + rawContent: raw, + }); + } else { + sections.push(parseFrontmatter(raw, file)); + } + } + return sections; +} + +/** + * YAMLフロントマターをパースしてid, title, level, bodyを返す。 + * フロントマターがない場合はid/titleを空文字、levelを0で返す。 + */ +function parseFrontmatter(content: string, file: string): MarkdownSection { + if (!content.startsWith("---\n")) { + throw new Error(`File ${file} is missing frontmatter`); + } + const endIdx = content.indexOf("\n---\n", 4); + if (endIdx === -1) { + throw new Error(`File ${file} has invalid frontmatter`); + } + const fm = yaml.load(content.slice(4, endIdx)) as { + id?: string; + title?: string; + level?: number; + }; + // TODO: validation of frontmatter using zod + const rawContent = content.slice(endIdx + 5); + return { + id: fm?.id ?? "", + title: fm?.title ?? "", + level: fm?.level ?? 2, + rawContent, + }; +} diff --git a/app/navbar.tsx b/app/navbar.tsx index 7a759e0..6e7548f 100644 --- a/app/navbar.tsx +++ b/app/navbar.tsx @@ -2,11 +2,11 @@ import Link from "next/link"; import { AccountMenu } from "./accountMenu"; -import { ThemeToggle } from "./[docs_id]/themeToggle"; +import { ThemeToggle } from "./themeToggle"; import { usePathname } from "next/navigation"; -import { pagesList } from "./pagesList"; +import { LanguageEntry } from "@/lib/docs"; -function PageTitle() { +function PageTitle({ pagesList }: { pagesList: LanguageEntry[] }) { const pathname = usePathname(); if(pathname === "/"){ @@ -14,19 +14,20 @@ function PageTitle() { } const currentDocsId = pathname.replace(/^\//, ""); - const currentGroup = pagesList.find((group) => currentDocsId.startsWith(group.id)); - const currentPage = currentGroup?.pages.find((page) => `${currentGroup.id}-${page.id}` === currentDocsId); + const currentGroup = pagesList.find((group) => currentDocsId.startsWith(`${group.id}/`)); + const pageIndex = currentGroup?.pages.findIndex((page) => `${currentGroup.id}/${page.slug}` === currentDocsId) ?? -1; + const currentPage = pageIndex >= 0 ? currentGroup?.pages[pageIndex] : undefined; if(currentPage){ return <> - {currentGroup?.lang}-{currentPage.id}. - {currentPage.title} + {currentGroup?.name}-{pageIndex + 1}. + {currentPage.name} } console.warn(`navbar page name is not defined for pathname ${pathname}`); return null; } -export function Navbar() { +export function Navbar({ pagesList }: { pagesList: LanguageEntry[] }) { return ( <> @@ -66,7 +67,7 @@ export function Navbar() { my.code();
- +
diff --git a/app/page.tsx b/app/page.tsx index c9788d1..fd3ead3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,6 +1,6 @@ import { Metadata } from "next"; import Link from "next/link"; -import { pagesList } from "./pagesList"; +import { getPagesList } from "@/lib/docs"; import clsx from "clsx"; export const metadata: Metadata = { @@ -9,7 +9,8 @@ export const metadata: Metadata = { "環境構築不要、その場で実践。AIアシスタントとの対話履歴があなただけの教材へと進化する、新しいプログラミング学習サイトです。", }; -export default function Home() { +export default async function Home() { + const pagesList = await getPagesList(); return (
@@ -41,14 +42,14 @@ export default function Home() { return (
-

{group.lang}

+

{group.name}

{group.description}

はじめる
diff --git a/app/pagesList.ts b/app/pagesList.ts deleted file mode 100644 index b4cb327..0000000 --- a/app/pagesList.ts +++ /dev/null @@ -1,123 +0,0 @@ -// docs_id = `${group.id}-${page.id}` -export const pagesList = [ - { - id: "python", - lang: "Python", - // TODO: これをいい感じの文章に変える↓ - description: "Pythonの基礎から応用までを学べるチュートリアル", - pages: [ - { id: 1, title: "環境構築と基本思想" }, - { id: 2, title: "基本構文とデータ型" }, - { id: 3, title: "リスト、タプル、辞書、セット" }, - { id: 4, title: "制御構文と関数" }, - { id: 5, title: "モジュールとパッケージ" }, - { id: 6, title: "オブジェクト指向プログラミング" }, - { - id: 7, - title: "ファイルの入出力とコンテキストマネージャ", - }, - { id: 8, title: "例外処理" }, - { id: 9, title: "ジェネレータとデコレータ" }, - ], - }, - { - id: "ruby", - lang: "Ruby", - description: "hoge", - pages: [ - { id: 1, title: "rubyの世界へようこそ" }, - { id: 2, title: "基本構文とデータ型" }, - { id: 3, title: "制御構造とメソッド定義" }, - { id: 4, title: "すべてがオブジェクト" }, - { id: 5, title: "コレクション (Array, Hash, Range)" }, - { id: 6, title: "ブロックとイテレータ" }, - { id: 7, title: "クラスとオブジェクト" }, - { id: 8, title: "モジュールとMix-in" }, - { id: 9, title: "Proc, Lambda, クロージャ" }, - { id: 10, title: "標準ライブラリの活用" }, - { id: 11, title: "テスト文化入門" }, - { id: 12, title: "メタプログラミング入門" }, - ], - }, - { - id: "javascript", - lang: "JavaScript", - description: "hoge", - pages: [ - { id: 1, title: "JavaScriptへようこそ" }, - { id: 2, title: "基本構文とデータ型" }, - { id: 3, title: "制御構文" }, - { id: 4, title: "関数とクロージャ" }, - { id: 5, title: "'this'の正体" }, - { id: 6, title: "オブジェクトとプロトタイプ" }, - { id: 7, title: "クラス構文" }, - { id: 8, title: "配列とイテレーション" }, - { id: 9, title: "非同期処理①: Promise" }, - { id: 10, title: "非同期処理②: Async/Await" }, - ], - }, - { - id: "typescript", - lang: "TypeScript", - description: "にゃー", - pages: [ - { id: 1, title: "TypeScriptへようこそ" }, - { id: 2, title: "基本的な型と型推論" }, - { id: 3, title: "オブジェクト、インターフェース、型エイリアス" }, - { id: 4, title: "関数の型定義" }, - { id: 5, title: "型を組み合わせる" }, - { id: 6, title: "ジェネリクス" }, - { id: 7, title: "クラスとアクセス修飾子" }, - { id: 8, title: "非同期処理とユーティリティ型" }, - ], - }, - { - id: "cpp", - lang: "C++", - description: "C++の基本から高度な機能までを学べるチュートリアル", - pages: [ - { id: 1, title: "C++の世界へようこそ" }, - { id: 2, title: "型システムと制御構造" }, - { id: 3, title: "データ集合とモダンな操作" }, - { id: 4, title: "ポインタとメモリ管理" }, - { id: 5, title: "関数と参照渡し" }, - { id: 6, title: "プロジェクトの分割とビルド" }, - { id: 7, title: "クラスの基礎" }, - { id: 8, title: "クラスを使いこなす" }, - { id: 9, title: "継承とポリモーフィズム" }, - { id: 10, title: "テンプレート" }, - { id: 11, title: "STL ①:コンテナ" }, - { id: 12, title: "STL ②:アルゴリズムとラムダ式" }, - { id: 13, title: "RAIIとスマートポインタ" }, - ], - }, - { - id: "rust", - lang: "Rust", - description: "a", - pages: [ - { id: 1, title: "Rustの世界へようこそ" }, - { id: 2, title: "基本構文と「不変性」" }, - { id: 3, title: "関数と制御フロー" }, - { id: 4, title: "所有権" }, - { id: 5, title: "借用とスライス" }, - { id: 6, title: "構造体とメソッド構文" }, - { id: 7, title: "列挙型とパターンマッチ" }, - { id: 8, title: "モジュールシステムとパッケージ管理" }, - { id: 9, title: "コレクションと文字列" }, - { id: 10, title: "エラーハンドリング" }, - { id: 11, title: "ジェネリクスとトレイト" }, - { id: 12, title: "ライフタイム" }, - ], - }, -] as const; - -// ${lang_id}-${page_id} から言語名を取得 -export function getLanguageName(docs_id: string) { - const lang_id = docs_id.split("-")[0]; - const lang = pagesList.find((lang) => lang.id === lang_id)?.lang; - if (!lang) { - throw new Error(`Unknown language id: ${lang_id}`); - } - return lang; -} diff --git a/app/sidebar.tsx b/app/sidebar.tsx index a8d00b3..6aead65 100644 --- a/app/sidebar.tsx +++ b/app/sidebar.tsx @@ -1,9 +1,9 @@ "use client"; import Link from "next/link"; import { usePathname } from "next/navigation"; -import { pagesList } from "./pagesList"; +import { LanguageEntry } from "@/lib/docs"; import { AccountMenu } from "./accountMenu"; -import { ThemeToggle } from "./[docs_id]/themeToggle"; +import { ThemeToggle } from "./themeToggle"; import { createContext, ReactNode, @@ -12,16 +12,17 @@ import { useEffect, useState, } from "react"; -import { DynamicMarkdownSection } from "./[docs_id]/pageContent"; +import { DynamicMarkdownSection } from "./[lang]/[pageId]/pageContent"; import clsx from "clsx"; import { LanguageIcon } from "./terminal/icons"; import { RuntimeLang } from "./terminal/runtime"; export interface ISidebarMdContext { - loadedDocsId: string; + loadedDocsId: { lang: string; pageId: string } | null; sidebarMdContent: DynamicMarkdownSection[]; setSidebarMdContent: ( - docsId: string, + lang: string, + pageId: string, content: | DynamicMarkdownSection[] | ((prev: DynamicMarkdownSection[]) => DynamicMarkdownSection[]) @@ -48,15 +49,19 @@ export function SidebarMdProvider({ children }: { children: ReactNode }) { const [sidebarMdContent, setSidebarMdContent_] = useState< DynamicMarkdownSection[] >([]); - const [loadedDocsId, setLoadedDocsId] = useState(""); + const [loadedDocsId, setLoadedDocsId] = useState<{ + lang: string; + pageId: string; + } | null>(null); const setSidebarMdContent = useCallback( ( - docsId: string, + lang: string, + pageId: string, content: | DynamicMarkdownSection[] | ((prev: DynamicMarkdownSection[]) => DynamicMarkdownSection[]) ) => { - setLoadedDocsId(docsId); + setLoadedDocsId({ lang, pageId }); setSidebarMdContent_(content); }, [] @@ -74,39 +79,43 @@ export function SidebarMdProvider({ children }: { children: ReactNode }) { ); } -export function Sidebar() { +export function Sidebar({ pagesList }: { pagesList: LanguageEntry[] }) { const pathname = usePathname(); - const currentDocsId = pathname.replace(/^\//, ""); // ちょっと遅延がある + const pathnameMatch = pathname.match(/^\/([\w-_]+)\/([\w-_]+).*?/); + const currentLang = pathnameMatch?.[1]; + const currentPageId = pathnameMatch?.[2]; const sidebarContext = useSidebarMdContext(); // sidebarMdContextの情報が古かったら使わない const sidebarMdContent = - sidebarContext.loadedDocsId === currentDocsId + sidebarContext.loadedDocsId && + sidebarContext.loadedDocsId.lang === currentLang && + sidebarContext.loadedDocsId.pageId === currentPageId ? sidebarContext.sidebarMdContent : []; // 現在表示中のセクション(最初にinViewがtrueのもの)を見つける - const currentSectionIndex = sidebarMdContent.findIndex( + const currentSectionId = sidebarMdContent.find( (section, i) => i >= 1 && section.inView - ); + )?.id; // 目次の開閉状態 const [detailsOpen, setDetailsOpen] = useState([]); - const currentGroupIndex = pagesList.findIndex((group) => - currentDocsId.startsWith(`${group.id}-`) + const currentLangIndex = pagesList.findIndex( + (group) => currentLang === group.id ); useEffect(() => { // 表示しているグループが変わったときに現在のグループのdetailsを開く - if (currentGroupIndex !== -1) { + if (currentLangIndex !== -1) { setDetailsOpen((detailsOpen) => { const newDetailsOpen = [...detailsOpen]; - while (newDetailsOpen.length <= currentGroupIndex) { + while (newDetailsOpen.length <= currentLangIndex) { newDetailsOpen.push(false); } - newDetailsOpen[currentGroupIndex] = true; + newDetailsOpen[currentLangIndex] = true; return newDetailsOpen; }); } - }, [currentGroupIndex]); + }, [currentLangIndex]); return (
@@ -166,43 +175,43 @@ export function Sidebar() { className="w-4 h-4" lang={group.id as RuntimeLang} /> - {group.lang} + {group.name}
    {group.pages.map((page) => ( -
  • +
  • - - {page.id}. - + {page.index}. - {page.title} + {page.name} - {`${group.id}-${page.id}` === currentDocsId && + {group.id === currentLang && + page.slug === currentPageId && sidebarMdContent.length > 0 && (
      - {sidebarMdContent.slice(1).map((section, idx) => { - // idx + 1 は実際のsectionIndexに対応(slice(1)で最初を除外しているため) - const isCurrentSection = - idx + 1 === currentSectionIndex; + {sidebarMdContent.slice(1).map((section) => { return (
    • {section.title} diff --git a/app/terminal/editor.tsx b/app/terminal/editor.tsx index e280f3b..4def9ed 100644 --- a/app/terminal/editor.tsx +++ b/app/terminal/editor.tsx @@ -2,10 +2,10 @@ import { lazy, Suspense, useEffect, useState } from "react"; import clsx from "clsx"; -import { useChangeTheme } from "../[docs_id]/themeToggle"; +import { useChangeTheme } from "@/themeToggle"; import { useEmbedContext } from "./embedContext"; import { langConstants } from "./runtime"; -import { MarkdownLang } from "@/[docs_id]/styledSyntaxHighlighter"; +import { MarkdownLang } from "@/[lang]/[pageId]/styledSyntaxHighlighter"; // https://github.com/securingsincity/react-ace/issues/27 により普通のimportができない const AceEditor = lazy(async () => { diff --git a/app/terminal/page.tsx b/app/terminal/page.tsx index 20f4ffb..a5dcafb 100644 --- a/app/terminal/page.tsx +++ b/app/terminal/page.tsx @@ -1,6 +1,6 @@ "use client"; -import { Heading } from "@/[docs_id]/markdown"; +import { Heading } from "@/[lang]/[pageId]/markdown"; import "mocha/mocha.css"; import { Fragment, useEffect, useRef, useState } from "react"; import { useWandbox } from "./wandbox/runtime"; diff --git a/app/terminal/repl.tsx b/app/terminal/repl.tsx index 286d35e..bca122b 100644 --- a/app/terminal/repl.tsx +++ b/app/terminal/repl.tsx @@ -16,7 +16,7 @@ import type { Terminal } from "@xterm/xterm"; import { useEmbedContext } from "./embedContext"; import { emptyMutex, langConstants, RuntimeLang, useRuntime } from "./runtime"; import clsx from "clsx"; -import { InlineCode } from "@/[docs_id]/markdown"; +import { InlineCode } from "@/[lang]/[pageId]/markdown"; export type ReplOutputType = | "stdout" diff --git a/app/terminal/runtime.tsx b/app/terminal/runtime.tsx index 90c29cb..5fe2e75 100644 --- a/app/terminal/runtime.tsx +++ b/app/terminal/runtime.tsx @@ -10,7 +10,7 @@ import { RubyContext, useRuby } from "./worker/ruby"; import { JSEvalContext, useJSEval } from "./worker/jsEval"; import { WorkerProvider } from "./worker/runtime"; import { TypeScriptProvider, useTypeScript } from "./typescript/runtime"; -import { MarkdownLang } from "@/[docs_id]/styledSyntaxHighlighter"; +import { MarkdownLang } from "@/[lang]/[pageId]/styledSyntaxHighlighter"; /** * Common runtime context interface for different languages diff --git a/app/terminal/terminal.tsx b/app/terminal/terminal.tsx index efa3057..79d614d 100644 --- a/app/terminal/terminal.tsx +++ b/app/terminal/terminal.tsx @@ -6,7 +6,7 @@ import type { FitAddon } from "@xterm/addon-fit"; import "@xterm/xterm/css/xterm.css"; import chalk from "chalk"; chalk.level = 3; -import { useChangeTheme } from "../[docs_id]/themeToggle"; +import { useChangeTheme } from "@/themeToggle"; /** * 文字列の幅を計算する。 diff --git a/app/[docs_id]/themeToggle.tsx b/app/themeToggle.tsx similarity index 100% rename from app/[docs_id]/themeToggle.tsx rename to app/themeToggle.tsx diff --git a/package-lock.json b/package-lock.json index a3097bb..f4252e5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "comlink": "^4.4.2", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", + "js-yaml": "^4.1.1", "mocha": "^11.7.4", "next": "^15.5.11", "object-inspect": "^1.13.4", @@ -47,6 +48,7 @@ "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", "@types/chai": "^5.2.3", + "@types/js-yaml": "^4.0.9", "@types/mocha": "^10.0.10", "@types/node": "^20", "@types/object-inspect": "^1.13.0", @@ -12548,6 +12550,13 @@ "@types/unist": "*" } }, + "node_modules/@types/js-yaml": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz", + "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", diff --git a/package.json b/package.json index 3d63df2..29e0826 100644 --- a/package.json +++ b/package.json @@ -4,12 +4,14 @@ "private": true, "type": "module", "scripts": { - "dev": "npm run cf-typegen && npm run copyAllDTSFiles && npm run removeHinting && next dev", - "build": "npm run cf-typegen && npm run copyAllDTSFiles && npm run removeHinting && next build", + "dev": "npm run cf-typegen && npm run generateLanguages && npm run generateSections && npm run copyAllDTSFiles && npm run removeHinting && next dev", + "build": "npm run cf-typegen && npm run generateLanguages && npm run generateSections && npm run copyAllDTSFiles && npm run removeHinting && next build", "start": "next start", "lint": "npm run cf-typegen && next lint", "tsc": "npm run cf-typegen && tsc", "format": "prettier --write app/", + "generateLanguages": "tsx ./scripts/generateLanguagesList.ts", + "generateSections": "tsx ./scripts/generateSectionsList.ts", "copyAllDTSFiles": "tsx ./scripts/copyAllDTSFiles.ts", "removeHinting": "tsx ./scripts/removeHinting.ts", "cf-preview": "opennextjs-cloudflare build && opennextjs-cloudflare preview --port 3000", @@ -34,6 +36,7 @@ "comlink": "^4.4.2", "dotenv": "^17.2.3", "drizzle-orm": "^0.44.7", + "js-yaml": "^4.1.1", "mocha": "^11.7.4", "next": "^15.5.11", "object-inspect": "^1.13.4", @@ -55,6 +58,7 @@ "devDependencies": { "@eslint/eslintrc": "^3", "@tailwindcss/postcss": "^4", + "@types/js-yaml": "^4.0.9", "@types/chai": "^5.2.3", "@types/mocha": "^10.0.10", "@types/node": "^20", diff --git a/public/docs/1.md b/public/docs/1.md deleted file mode 100644 index 3cd2797..0000000 --- a/public/docs/1.md +++ /dev/null @@ -1,3 +0,0 @@ -## sample markdown file - -hello, world! diff --git a/public/docs/cpp-1.md b/public/docs/cpp-1.md deleted file mode 100644 index 4bc6e9a..0000000 --- a/public/docs/cpp-1.md +++ /dev/null @@ -1,129 +0,0 @@ -# 第1章: C++の世界へようこそ - -C++プログラミングの全体像を掴む章へようこそ! 🎉 この章では、あなたがこれまでに培ってきたプログラミングの知識を土台に、C++ならではの特徴や文化に触れていきます。他の言語との違いを意識しながら、まずは「**ソースコードを書き、コンパイルして実行する**」というC++の基本的な開発フローを体験しましょう。 - -## C++とは? - -C++は、C言語を拡張して作られた、非常にパワフルで汎用性の高いプログラミング言語です。その歴史は古く、1983年にBjarne Stroustrupによって開発が始まりました。 - -### 特徴 - -他の言語と比較したC++の特徴は以下の通りです。 - - * **パフォーマンス 🚀:** C++は、OSやハードウェアに近い低レベルな操作が可能で、実行速度が非常に高速です。このため、パフォーマンスが最重要視される場面で絶大な信頼を得ています。 - * **静的型付け:** 変数の型はコンパイル時に決定されます。これにより、実行前に多くのエラーを発見でき、大規模な開発でもコードの安全性を保ちやすくなります。 - * **マルチパラダイム:** 手続き型プログラミング、オブジェクト指向プログラミング、ジェネリックプログラミングなど、様々なプログラミングスタイルをサポートしています。これにより、問題の性質に合わせて最適なアプローチを選択できます。 - * **C言語との互換性:** C言語のコードの多くは、ほとんどそのままC++のコードとしてコンパイルできます。C言語で書かれた膨大なソフトウェア資産を活用できるのは大きな利点です。 - -### C++が使われる分野 - -その高いパフォーマンスと柔軟性から、C++は以下のような幅広い分野の第一線で活躍しています。 - - * **ゲーム開発:** Unreal EngineやUnity(一部)など、多くの有名ゲームエンジンがC++で開発されています。キャラクターの物理演算やリアルタイムグラフィックス描画など、速度が求められる処理に不可欠です。 - * **OS開発:** Windows, macOS, Linuxといった主要なオペレーティングシステムのカーネルやシステムコンポーネントの多くがC++で記述されています。 - * **金融システム:** 1ミリ秒を争う高頻度取引(HFT)システムなど、超低遅延が求められる金融アプリケーションで使用されています。 - * **組み込みシステム・IoT:** 自動車のエンジン制御ユニット(ECU)や家電、産業用ロボットなど、リソースが限られた環境でも高速に動作する必要があります。 - -## 開発環境のセットアップ - -C++プログラムを実行するには、**コンパイラ**が必要です。コンパイラは、人間が書いたC++のソースコードを、コンピュータが理解できる機械語に翻訳するツールです。 - -このウェブサイト上ではブラウザ上でコードを編集し実行できる環境を提供しており、特別なセットアップは不要です。しかし、ローカル環境でC++を学びたい場合は、以下のようなコンパイラとIDE(統合開発環境)をインストールすることをお勧めします。 - -### コンパイラとIDE - -* **コンパイラ:** - * **GCC (GNU Compiler Collection):** Linuxで標準的に使われるコンパイラ。Windows (MinGW/MSYS2) やmacOSでも利用可能です。 - * **Clang:** AppleのXcodeで標準的に使われているコンパイラ。エラーメッセージが分かりやすいと評判です。 - * **MSVC (Microsoft Visual C++):** MicrosoftのVisual Studioに付属するコンパイラ。Windows開発の標準です。 -* **IDE (統合開発環境) / エディタ:** - * **Visual Studio (Windows):** コンパイラ、エディタ、デバッガなど、C++開発に必要な全てが詰まった強力なIDEです。初心者には特におすすめです。 - * **Visual Studio Code (Windows/macOS/Linux):** 軽量なエディタですが、拡張機能を入れることで強力なC++開発環境を構築できます。 - -### おすすめのセットアップ - - * **Windows:** **Visual Studio Community** をインストールするのが最も簡単です。インストーラーで「C++によるデスクトップ開発」ワークロードを選択すれば、必要なものがすべて揃います。 - * **macOS:** ターミナルで `xcode-select --install` を実行し、**Xcode Command Line Tools** をインストールします。これにはClangコンパイラが含まれます。エディタは **Visual Studio Code** がおすすめです。 - * **Linux (Ubuntu/Debian系):** ターミナルで `sudo apt update && sudo apt install build-essential g++` を実行してGCCコンパイラをインストールします。エディタは **Visual Studio Code** がおすすめです。 - - -## 最初のプログラム - -環境が整ったら、さっそく定番の "Hello, World\!" プログラムを作成し、C++開発の流れを掴みましょう。 - -`main.cpp` という名前でファイルを作成し、以下のコードを記述してください。 - -```cpp:main.cpp -// 画面に "Hello, World!" と表示するプログラム - -#include - -int main() { - std::cout << "Hello, World!" << std::endl; - return 0; -} -``` - -### コンパイルと実行 - -このコードを実行するには、まず**コンパイル**して実行可能ファイルを生成する必要があります。ターミナル(Windowsの場合はコマンドプロンプトやPowerShell)で以下のコマンドを実行します。 - -```bash -# g++ (GCC) または clang++ (Clang) を使ってコンパイル -# -o main は出力ファイル名を main にするという意味 -g++ main.cpp -o main - -# 生成された実行可能ファイルを実行 -./main -``` - -```powershell -# Visual C++ (MSVC) を使う場合 -cl main.cpp /Fe:main.exe - -# 生成された実行可能ファイルを実行 -main.exe -``` - -このウェブサイト上の実行環境で動かす場合は、以下の実行ボタンをクリックしてください。 -実行すると、ターミナルに以下のように表示されるはずです。 - -```cpp-exec:main.cpp -Hello, World! -``` - -PythonやJavaScriptのようなインタプリタ言語とは異なり、C++では「コンパイル」という一手間が必要です。このステップにより、実行前にコード全体がチェックされ、高速なネイティブコードが生成されます。 - -## C++の基本構造 - -先ほどの "Hello, World\!" プログラムには、C++の基本的な要素が詰まっています。一つずつ見ていきましょう。 - -### `#include ` - ヘッダファイルのインクルード - -`#include` は、他のファイルに書かれた機能を利用するための**プリプロセッサ命令**です。ここでは、コンソールへの入出力機能を提供する `iostream` という標準ライブラリのヘッダファイルを読み込んでいます。これにより、`std::cout` などが使えるようになります。 - - * `< >` で囲む: 標準ライブラリのヘッダファイルをインクルードする場合 - * `" "` で囲む: 自分で作成したヘッダファイルをインクルードする場合 (後の章で学びます) - -### `int main()` - main関数 - -`main`関数は、C++プログラムのエントリーポイント(**実行開始点**)です。OSはプログラムを実行するとき、まずこの`main`関数を呼び出します。 - - * `int`: `main`関数が整数 (integer) の値を返すことを示します。この戻り値はプログラムの終了ステータスとしてOSに渡され、`0` は**正常終了**を意味するのが慣例です。 - * `()`: 関数が引数を取らないことを示しています。(引数を取ることも可能です) - -### `std::cout` と `std::endl` - 名前空間 (namespace) - - * `std::cout`: "character output stream" の略で、コンソールへの標準出力を担当します。 - * `<<`: **ストリーム挿入演算子**と呼ばれ、右側のデータを左側のストリーム(ここでは `std::cout`)に流し込むイメージです。 - * `std::endl`: "end line" の略で、改行を出力し、出力バッファをフラッシュ(強制的に出力)します。 - * `std::`: `cout` や `endl` が `std` という**名前空間 (namespace)** に属していることを示します。名前空間は、大規模なプログラムで関数や変数の名前が衝突するのを防ぐための仕組みです。`std` はC++の標準ライブラリが定義されている名前空間です。 - -## この章のまとめ - - * C++は、**パフォーマンス**と**多機能性**を両立した強力なプログラミング言語です。 - * C++の開発フローは、「**ソースコード作成 → コンパイル → 実行**」が基本です。 - * すべてのC++プログラムは `main` 関数から実行が始まります。 - * `#include` で外部ライブラリの機能を読み込み、`namespace` で名前の衝突を避けます。 - -これであなたもC++プログラマの仲間入りです!次の章では、C++の根幹をなす型システムとメモリの仕組みについて学んでいきましょう。 diff --git a/public/docs/cpp-10.md b/public/docs/cpp-10.md deleted file mode 100644 index 8e4fa58..0000000 --- a/public/docs/cpp-10.md +++ /dev/null @@ -1,224 +0,0 @@ -# 第10章: テンプレートによる汎用プログラミング - -これまでの章では、`int`や`double`、あるいは自作の`Car`クラスのように、特定の型に対して処理を行う関数やクラスを作成してきました。しかし、プログラムが複雑になるにつれて、「型は違うけれど、行いたい処理は全く同じ」という状況が頻繁に発生します。例えば、2つの値の大きい方を返す`max`という関数を考えてみましょう。 - -```cpp -int max_int(int a, int b) { - return (a > b) ? a : b; -} - -double max_double(double a, double b) { - return (a > b) ? a : b; -} -``` - -このように、型ごとに同じロジックの関数をいくつも用意するのは非効率的ですし、バグの温床にもなります。 - -この問題を解決するのが**テンプレート**です。テンプレートを使うと、具体的な型を "仮引数" のように扱い、様々な型に対応できる関数やクラスの「設計図」を作ることができます。このような、型に依存しないプログラミングスタイルを**ジェネリックプログラミング(汎用プログラミング)**と呼びます。 - -## 関数テンプレート: intでもdoubleでもstringでも動く関数を作る - -関数テンプレートを使うと、先ほどの`max`関数の問題をエレガントに解決できます。 - -```cpp:function_template_intro.cpp -#include -#include - -// Tという名前で型を仮引数として受け取るテンプレートを宣言 -template -T max_value(T a, T b) { - return (a > b) ? a : b; -} - -int main() { - // int型でmax_valueを呼び出す - std::cout << "max(10, 20) = " << max_value(10, 20) << std::endl; - - // double型でmax_valueを呼び出す - std::cout << "max(3.14, 1.41) = " << max_value(3.14, 1.41) << std::endl; - - // std::string型でも動作する! - std::string s1 = "world"; - std::string s2 = "hello"; - std::cout << "max(\"world\", \"hello\") = " << max_value(s1, s2) << std::endl; - - return 0; -} -``` - -```cpp-exec:function_template_intro.cpp -max(10, 20) = 20 -max(3.14, 1.41) = 3.14 -max("world", "hello") = world -``` - -### テンプレートの仕組み - -`template `という部分が、この関数がテンプレートであることを示しています。 - - * **`template <...>`**: テンプレートの宣言を開始します。 - * **`typename T`**: `T`という名前の「型引数」を定義しています。`typename`の代わりに`class`と書くこともできますが、意味は同じです。`T`は、このテンプレートが実際に使われるときに具体的な型(`int`や`double`など)に置き換えられます。 - -`main`関数で`max_value(10, 20)`のように呼び出すと、コンパイラは引数の型が`int`であることから、`T`を`int`だと自動的に判断します(これを**テンプレート引数推論**と呼びます)。そして、内部的に以下のような`int`版の関数を生成してくれるのです。 - -```cpp -// コンパイラが内部的に生成するコードのイメージ -int max_value(int a, int b) { - return (a > b) ? a : b; -} -``` - -同様に、`double`や`std::string`で呼び出されれば、それぞれの型に対応したバージョンの関数が自動的に生成されます。これにより、私たちは一つの「設計図」を書くだけで、様々な型に対応できるのです。 - -## クラステンプレート: 様々な型のデータを格納できるクラスを作る - -テンプレートの力は、クラスにも適用できます。これにより、様々な型のデータを格納できる汎用的なクラス(コンテナなど)を作成できます。例えば、「2つの値をペアで保持する」クラスを考えてみましょう。 - -```cpp:class_template_intro.cpp -#include -#include - -// 2つの型 T1, T2 を引数に取るクラステンプレート -template -class Pair { -public: - T1 first; - T2 second; - - // コンストラクタ - Pair(T1 f, T2 s) : first(f), second(s) {} - - void print() { - std::cout << "(" << first << ", " << second << ")" << std::endl; - } -}; - -int main() { - // T1=int, T2=std::string としてPairクラスのオブジェクトを生成 - Pair p1(1, "apple"); - p1.print(); - - // T1=std::string, T2=double としてPairクラスのオブジェクトを生成 - Pair p2("pi", 3.14159); - p2.print(); - - // 違う型のPair同士は当然、別の型として扱われる - // p1 = p2; // これはコンパイルエラーになる - - return 0; -} -``` - -```cpp-exec:class_template_intro.cpp -(1, apple) -(pi, 3.14159) -``` - -### クラステンプレートの仕組み - -関数テンプレートと基本的な考え方は同じですが、いくつか重要な違いがあります。 - -1. **明示的な型指定**: - 関数テンプレートではコンパイラが型を推論してくれましたが、クラステンプレートの場合は、オブジェクトを生成する際に`Pair`のように、開発者が明示的に型を指定する必要があります。 - -2. **インスタンス化**: - `Pair`のように具体的な型を指定してオブジェクトを作ることを、テンプレートの**インスタンス化**と呼びます。コンパイラは、この指定に基づいて`T1`を`int`に、`T2`を`std::string`に置き換えた、以下のような新しいクラスを内部的に生成します。 - - ```cpp - // コンパイラが内部的に生成するクラスのイメージ - class Pair_int_string { // クラス名は実際には異なります - public: - int first; - std::string second; - - Pair_int_string(int f, std::string s) : first(f), second(s) {} - - void print() { - std::cout << "(" << first << ", " << second << ")" << std::endl; - } - }; - ``` - - `Pair`と`Pair`は、コンパイルされると全く別のクラスとして扱われることに注意してください。 - -クラステンプレートは、C++の強力なライブラリである**STL (Standard Template Library)**の根幹をなす技術です。次章で学ぶ`vector`や`map`といった便利なコンテナは、すべてクラステンプレートで実装されています。 - -## この章のまとめ - - * **ジェネリックプログラミング**は、特定の型に縛られない、汎用的なコードを書くための手法です。 - * **テンプレート**は、C++でジェネリックプログラミングを実現するための機能です。 - * **関数テンプレート**を使うと、様々な型の引数に対して同じ処理を行う関数を定義できます。呼び出し時には、コンパイラが**テンプレート引数推論**によって型を自動的に決定します。 - * **クラステンプレート**を使うと、様々な型を扱える汎用的なクラスを定義できます。オブジェクトを生成する際には、`< >`内に具体的な型を**明示的に指定**してインスタンス化する必要があります。 - -テンプレートを使いこなすことで、コードの再利用性が劇的に向上し、より柔軟で堅牢なプログラムを記述できるようになります。 - -### 練習問題1: 汎用的なprint関数 - -任意の型の配列(ここでは`std::vector`を使いましょう)を受け取り、その要素をすべて画面に出力する関数テンプレート`print_elements`を作成してください。 - -```cpp:practice10_1.cpp -#include -#include -#include - -// ここに関数テンプレート print_elements を実装してください - - -int main() { - std::vector v_int = {1, 2, 3, 4, 5}; - std::cout << "Integers: "; - print_elements(v_int); - - std::vector v_str = {"C++", "is", "powerful"}; - std::cout << "Strings: "; - print_elements(v_str); - - return 0; -} -``` - -```cpp-exec:practice10_1.cpp -Integers: 1 2 3 4 5 -Strings: C++ is powerful -``` - -### 練習問題2: 汎用的なスタッククラス - -後入れ先出し(LIFO)のデータ構造であるスタックを、クラステンプレート`SimpleStack`として実装してください。以下のメンバ関数を持つようにしてください。 - - * `void push(T item)`: スタックに要素を追加する - * `T pop()`: スタックの先頭から要素を取り出す - * `bool is_empty()`: スタックが空かどうかを返す - -`std::vector`を内部のデータ格納場所として利用して構いません。`int`型と`char`型で動作を確認してください。 - -```cpp:practice10_2.cpp -#include -#include -#include - -// ここにクラステンプレート SimpleStack を実装してください - -int main() { - SimpleStack int_stack; - int_stack.push(10); - int_stack.push(20); - std::cout << "Popped from int_stack: " << int_stack.pop() << std::endl; // 20 - std::cout << "Popped from int_stack: " << int_stack.pop() << std::endl; // 10 - - SimpleStack char_stack; - char_stack.push('A'); - char_stack.push('B'); - std::cout << "Popped from char_stack: " << char_stack.pop() << std::endl; // B - std::cout << "Popped from char_stack: " << char_stack.pop() << std::endl; // A - - return 0; -} -``` - -```cpp-exec:practice10_2.cpp -Popped from int_stack: 20 -Popped from int_stack: 10 -Popped from char_stack: B -Popped from char_stack: A -``` diff --git a/public/docs/cpp-11.md b/public/docs/cpp-11.md deleted file mode 100644 index 2f97046..0000000 --- a/public/docs/cpp-11.md +++ /dev/null @@ -1,252 +0,0 @@ -# 第11章: 標準テンプレートライブラリ (STL) ①:コンテナ - -C++の大きな魅力の一つに、**標準テンプレートライブラリ (Standard Template Library, STL)** の存在があります。STLは、よく使われるデータ構造やアルゴリズムを、汎用的かつ効率的に実装したライブラリ群です。この章では、STLの心臓部である**コンテナ**に焦点を当て、データの格納と管理を劇的に楽にする方法を学びます。 - -## STLの全体像: コンテナ、アルゴリズム、イテレータ - -STLは、主に3つの要素から構成されています。 - -1. **コンテナ (Containers)**: データを格納するためのデータ構造です。`vector`(可変長配列)や`map`(連想配列)など、様々な種類があります。 -2. **アルゴリズム (Algorithms)**: ソート、検索、変換など、コンテナ上のデータに対して操作を行う関数群です。 -3. **イテレータ (Iterators)**: コンテナの要素を指し示し、アルゴリズムがコンテナの種類に依存せずに各要素にアクセスするための統一的なインターフェースを提供します。ポインタを一般化したようなものです。 - -これら3つが連携することで、C++プログラマは効率的で再利用性の高いコードを素早く書くことができます。この章では「コンテナ」を、次の章では「アルゴリズム」と、それらをつなぐ「イテレータ」の応用を詳しく見ていきます。 - -## std::vector: 最もよく使う可変長配列 - -`std::vector`は、最も基本的で最もよく使われるコンテナです。他の言語でいうところの「リスト」や「動的配列」に相当し、要素を連続したメモリ領域に格納します。 - -**主な特徴**: - - * **動的なサイズ**: 必要に応じて自動的にサイズが拡張されます。 - * **高速なランダムアクセス**: インデックス(添字)を使って `[i]` の形式で要素に高速にアクセスできます (`O(1)`)。 - * **末尾への高速な追加・削除**: `push_back()` や `pop_back()` を使った末尾への操作は非常に高速です。 - -`std::vector`を使うには、``ヘッダをインクルードする必要があります。 - -```cpp:vector_example.cpp -#include -#include -#include - -int main() { - // string型の要素を格納するvectorを作成 - std::vector names; - - // push_backで末尾に要素を追加 - names.push_back("Alice"); - names.push_back("Bob"); - names.push_back("Charlie"); - - // インデックスを使った要素へのアクセス - std::cout << "Index 1: " << names[1] << std::endl; - - // 範囲for文 (range-based for loop) を使った全要素の走査 - std::cout << "\nAll names:" << std::endl; - for (const std::string& name : names) { - std::cout << "- " << name << std::endl; - } - - // size()で現在の要素数を取得 - std::cout << "\nCurrent size: " << names.size() << std::endl; - - // pop_backで末尾の要素を削除 - names.pop_back(); // "Charlie"を削除 - - std::cout << "\nAfter pop_back:" << std::endl; - for (const std::string& name : names) { - std::cout << "- " << name << std::endl; - } - std::cout << "Current size: " << names.size() << std::endl; - - return 0; -} -``` - -```cpp-exec:vector_example.cpp -Index 1: Bob - -All names: -- Alice -- Bob -- Charlie - -Current size: 3 - -After pop_back: -- Alice -- Bob -Current size: 2 -``` - -`std::vector`は、どのコンテナを使うか迷ったら、まず最初に検討すべきデフォルトの選択肢と言えるほど万能です。 - -## std::map: キーと値のペアを管理する連想配列 - -`std::map`は、キー (key) と値 (value) のペアを管理するためのコンテナです。他の言語の「辞書 (dictionary)」や「ハッシュマップ (hash map)」に似ています。キーを使って値を高速に検索、追加、削除できます。 - -**主な特徴**: - - * **キーによる高速な検索**: キーに基づいて要素が自動的にソートされて格納されるため、検索、挿入、削除が高速です (`O(log n)`)。 - * **一意なキー**: `std::map`内のキーは重複しません。同じキーで値を挿入しようとすると、既存の値が上書きされます。 - -`std::map`を使うには、``ヘッダをインクルードする必要があります。 - -```cpp:map_example.cpp -#include -#include -#include - -int main() { - // キーがstring型、値がint型のmapを作成 - std::map scores; - - // []演算子で要素を追加・更新 - scores["Alice"] = 95; - scores["Bob"] = 88; - scores["Charlie"] = 76; - - // []演算子で値にアクセス - std::cout << "Bob's score: " << scores["Bob"] << std::endl; - - // 新しいキーで追加 - scores["David"] = 100; - - // 既存のキーの値を更新 - scores["Alice"] = 98; - - // 範囲for文を使った全要素の走査 - // autoキーワードを使うと型推論が効いて便利 - std::cout << "\nAll scores:" << std::endl; - for (const auto& pair : scores) { - std::cout << "- " << pair.first << ": " << pair.second << std::endl; - } - - // count()でキーの存在を確認 - std::string search_key = "Charlie"; - if (scores.count(search_key)) { - std::cout << "\n" << search_key << " is in the map." << std::endl; - } - - // erase()で要素を削除 - scores.erase("Bob"); - - std::cout << "\nAfter erasing Bob:" << std::endl; - for (const auto& pair : scores) { - std::cout << "- " << pair.first << ": " << pair.second << std::endl; - } - - return 0; -} -``` - -```cpp-exec:map_example.cpp -Bob's score: 88 - -All scores: -- Alice: 98 -- Bob: 88 -- Charlie: 76 -- David: 100 - -Charlie is in the map. - -After erasing Bob: -- Alice: 98 -- Charlie: 76 -- David: 100 -``` - -`std::map`は、キーと値のペアを効率的に管理したい場合に非常に強力なツールです。 - -## その他: 目的に応じたコンテナ - -STLには、他にも特定の目的に特化したコンテナが多数用意されています。ここでは代表的なものをいくつか紹介します。 - - * `std::list`: 双方向リスト。要素の途中への挿入・削除が非常に高速 (`O(1)`) ですが、ランダムアクセスはできません(先頭から順番にたどる必要があります)。``ヘッダが必要です。 - * `std::set`: 重複しない要素の集合を管理します。要素は自動的にソートされます。特定の要素が集合内に存在するかどうかを高速に判定したい場合 (`O(log n)`) に便利です。``ヘッダが必要です。 - * `std::unordered_map`: `std::map`と同様にキーと値のペアを管理しますが、内部的にハッシュテーブルを使うため、平均的な検索・挿入・削除がさらに高速 (`O(1)`) です。ただし、要素はソートされません。``ヘッダが必要です。 - * `std::queue`, `std::stack`: それぞれ先入れ先出し (FIFO)、後入れ先出し (LIFO) のデータ構造を実装するためのコンテナアダプタです。 - -どのコンテナを選択するかは、プログラムの要件(データのアクセスパターン、挿入・削除の頻度など)によって決まります。まずは`std::vector`を基本とし、必要に応じて他のコンテナを検討するのが良いアプローチです。 - -## この章のまとめ - - * **STL**は、**コンテナ**、**アルゴリズム**、**イテレータ**の3つの主要コンポーネントから構成される、C++の強力な標準ライブラリです。 - * **コンテナ**は、データを格納するためのクラスです。 - * `std::vector`は、最も一般的に使われる動的配列で、高速なランダムアクセスと末尾への簡単な要素追加が特徴です。 - * `std::map`は、キーと値のペアを管理する連想配列で、キーによる高速な検索が可能です。 - * 他にも`std::list`, `std::set`など、特定の用途に合わせた様々なコンテナが用意されています。 - -### 練習問題1: 数値ベクタの操作 - -`std::vector`型の整数のリストに対して、以下の処理を行うプログラムを作成してください。 - -1. ベクタに格納されている全ての数値の合計値を計算して表示する。 -2. ベクタの中の最大値を検索して表示する。 -3. ベクタの要素を逆順にして、その内容を表示する。(ヒント:新しいベクタを作っても良いですし、`std::swap`を使っても構いません) - -```cpp:practice11_1.cpp -#include -#include - -int main() { - std::vector numbers = {3, 5, 2, 8, 6}; - - // 1. 合計値の計算 - - - // 2. 最大値の検索 - - - // 3. 要素の逆順表示 - - - return 0; -} -``` - -```cpp-exec:practice11_1.cpp -Sum: 24 -Max: 8 -Reversed: 6 8 2 5 3 -``` - - -### 練習問題2: 簡単な単語カウンター - -英文(スペースで区切られた単語の列)を読み込み、各単語が何回出現したかをカウントするプログラムを`std::map`を使って作成してください。最後に、出現した全単語とその出現回数をアルファベット順に表示してください。 - -> 文字列を単語ごとに分割するには、以下のように`std::istringstream`を使うと便利です。 -```cpp -#include - -std::string text = "this is a sample text"; -std::istringstream iss(text); -std::string word; -while (iss >> word) { - // wordには1単語ずつ格納される -} -``` - - - -```cpp:practice11_2.cpp -#include -#include -#include -#include - -int main() { - std::string text = "cpp is fun and cpp is powerful"; - - -} -``` -```cpp-exec:practice11_2.cpp -and: 1 -cpp: 2 -fun: 1 -is: 2 -powerful: 1 -``` diff --git a/public/docs/cpp-12.md b/public/docs/cpp-12.md deleted file mode 100644 index 62dd606..0000000 --- a/public/docs/cpp-12.md +++ /dev/null @@ -1,301 +0,0 @@ -# 第12章: 標準テンプレートライブラリ (STL) ②:アルゴリズムとラムダ式 - -前の章ではSTLのコンテナについて学び、様々なデータを効率的に格納する方法を見てきました。しかし、データを格納するだけではプログラムは完成しません。そのデータを並べ替えたり、検索したり、特定の処理を施したりといった「操作」が必要です。 - -この章では、STLのもう一つの強力な柱である**アルゴリズム**ライブラリを学びます。これらのアルゴリズムは、コンテナ内のデータを操作するための汎用的な関数群です。そして、アルゴリズムをさらに柔軟かつ強力にするための現代的なC++の機能、**ラムダ式**についても解説します。これらをマスターすれば、驚くほど少ないコードで複雑なデータ操作が実現できるようになります。 - -## イテレータ:コンテナとアルゴリズムを繋ぐ架け橋 - -アルゴリズムは、特定のコンテナ(`std::vector` や `std::list` など)に直接依存しないように設計されています。では、どうやってコンテナ内の要素にアクセスするのでしょうか?そこで登場するのが**イテレータ (Iterator)** です。 - -イテレータは、コンテナ内の要素を指し示す「ポインタのような」オブジェクトです。ポインタのように `*` で要素の値を参照したり、`++` で次の要素に進んだりできます。 - -ほとんどのコンテナは、以下の2つの重要なイテレータを取得するメンバ関数を持っています。 - - * `begin()`: コンテナの先頭要素を指すイテレータを返す。 - * `end()`: コンテナの**最後の要素の次**を指すイテレータを返す。これは有効な要素を指していない「番兵」のような役割を果たします。 - -アルゴリズムは、この `begin()` と `end()` から得られるイテレータのペアを使い、操作対象の「範囲」を指定します。範囲は半開区間 `[begin, end)` で表され、`begin` が指す要素は範囲に含まれ、`end` が指す要素は含まれません。 - -簡単な例を見てみましょう。イテレータを使って `vector` の全要素を表示するコードです。 - -```cpp:iterator_example.cpp -#include -#include - -int main() { - std::vector numbers = {0, 1, 2, 3, 4}; - - // イテレータを使ってコンテナを走査 - std::cout << "Numbers: "; - for (auto it = numbers.begin(); it != numbers.end(); ++it) { - std::cout << *it << " "; // *it で要素の値にアクセス - } - std::cout << std::endl; - - // C++11以降の範囲ベースforループ (内部ではイテレータが使われている) - std::cout << "Numbers (range-based for): "; - for (int num : numbers) { - std::cout << num << " "; - } - std::cout << std::endl; - - return 0; -} -``` - -```cpp-exec:iterator_example.cpp -Numbers: 0 1 2 3 4 -Numbers (range-based for): 0 1 2 3 4 -``` - -このように、イテレータはコンテナの種類を問わず、統一的な方法で要素にアクセスする仕組みを提供します。これが、アルゴリズムが様々なコンテナに対して汎用的に機能する理由です。 - -## 便利なアルゴリズム - -C++の標準ライブラリには、`` ヘッダと `` ヘッダに数多くの便利なアルゴリズムが用意されています。ここでは、特によく使われるものをいくつか紹介します。 - -### `std::sort`: 要素を並べ替える - -名前の通り、指定された範囲の要素をソートします。デフォルトでは昇順に並べ替えます。 - -```cpp:sort_example.cpp -#include -#include -#include // std::sort のために必要 -#include - -int main() { - std::vector numbers = {5, 2, 8, 1, 9}; - - // numbers.begin() から numbers.end() の範囲をソート - std::sort(numbers.begin(), numbers.end()); - - std::cout << "Sorted numbers: "; - for (int num : numbers) { - std::cout << num << " "; - } - std::cout << std::endl; - - std::vector words = {"banana", "apple", "cherry"}; - std::sort(words.begin(), words.end()); - - std::cout << "Sorted words: "; - for (const auto& word : words) { - std::cout << word << " "; - } - std::cout << std::endl; - - return 0; -} -``` - -```cpp-exec:sort_example.cpp -Sorted numbers: 1 2 5 8 9 -Sorted words: apple banana cherry -``` - -### `std::find`: 要素を検索する - -指定された範囲から特定の値を持つ要素を検索します。 - - * **見つかった場合**: その要素を指すイテレータを返します。 - * **見つからなかった場合**: 範囲の終端を示すイテレータ (`end()`) を返します。 - -この性質を利用して、要素が存在するかどうかをチェックできます。 - -```cpp:find_example.cpp -#include -#include -#include // std::find のために必要 - -int main() { - std::vector numbers = {10, 20, 30, 40, 50}; - int value_to_find = 30; - - // numbers の中から 30 を探す - auto it = std::find(numbers.begin(), numbers.end(), value_to_find); - - if (it != numbers.end()) { - // 見つかった場合 - std::cout << "Found " << *it << " at index " << std::distance(numbers.begin(), it) << std::endl; - } else { - // 見つからなかった場合 - std::cout << value_to_find << " not found." << std::endl; - } - - value_to_find = 99; - it = std::find(numbers.begin(), numbers.end(), value_to_find); - - if (it != numbers.end()) { - std::cout << "Found " << *it << std::endl; - } else { - std::cout << value_to_find << " not found." << std::endl; - } - - return 0; -} -``` - -```cpp-exec:find_example.cpp -Found 30 at index 2 -99 not found. -``` - -### `std::for_each`: 各要素に処理を適用する - -指定された範囲の全ての要素に対して、特定の関数(処理)を適用します。ループを書くよりも意図が明確になる場合があります。 - -```cpp -// 3番目の引数に関数を渡す -std::for_each(numbers.begin(), numbers.end(), print_function); -``` - -ここで「特定の処理」をその場で手軽に記述する方法が**ラムダ式**です。 - -## ラムダ式:その場で書ける無名関数 - -ラムダ式(Lambda Expression)は、C++11から導入された非常に強力な機能です。一言で言えば、「**その場で定義して使える名前のない小さな関数**」です。これにより、アルゴリズムに渡すためだけの短い関数をわざわざ定義する必要がなくなり、コードが非常に簡潔になります。 - -ラムダ式の基本的な構文は以下の通りです。 - -```cpp -[キャプチャ](引数リスト) -> 戻り値の型 { 処理本体 } -``` - - * `[]` **キャプチャ句**: ラムダ式の外にある変数を取り込んで、式の中で使えるようにします。 - * `[]`: 何もキャプチャしない。 - * `[=]`: 外の変数を全て値渡し(コピー)でキャプチャする。 - * `[&]`: 外の変数を全て参照渡しでキャプチャする。 - * `[x, &y]`: 変数 `x` は値渡し、変数 `y` は参照渡しでキャプチャする。 - * `()` **引数リスト**:通常の関数と同じ引数を取ることができます。 - * `-> 戻り値の型`: 戻り値の型を指定します。多くの場合、コンパイラが推論できるため省略可能です。 - * `{}` **処理本体**: 関数の処理内容を記述します。 - -`std::for_each` とラムダ式を組み合わせた例を見てみましょう。 - -```cpp:for_each_lambda_example.cpp -#include -#include -#include - -int main() { - std::vector numbers = {1, 2, 3, 4, 5}; - - // 各要素を2倍して表示する - std::cout << "Doubled numbers: "; - std::for_each(numbers.begin(), numbers.end(), [](int n) { - std::cout << n * 2 << " "; - }); - std::cout << std::endl; - - // 外部の変数をキャプチャする例 - int sum = 0; - // `&sum` で sum を参照キャプチャし、ラムダ式内で変更できるようにする - std::for_each(numbers.begin(), numbers.end(), [&sum](int n) { - sum += n; - }); - - std::cout << "Sum: " << sum << std::endl; - - return 0; -} -``` - -```cpp-exec:for_each_lambda_example.cpp -Doubled numbers: 2 4 6 8 10 -Sum: 15 -``` - -このコードは、`for_each` の3番目の引数に直接処理を書き込んでいます。非常に直感的で読みやすいと思いませんか? - -ラムダ式は、特に `std::sort` のソート順をカスタマイズする際に真価を発揮します。例えば、数値を降順にソートしたい場合、比較ルールをラムダ式で与えることができます。 - -```cpp:lambda_sort_example.cpp -#include -#include -#include - -int main() { - std::vector numbers = {5, 2, 8, 1, 9}; - - // 比較関数としてラムダ式を渡す - // a > b であれば true を返すことで降順ソートになる - std::sort(numbers.begin(), numbers.end(), [](int a, int b) { - return a > b; - }); - - std::cout << "Sorted in descending order: "; - for (int num : numbers) { - std::cout << num << " "; - } - std::cout << std::endl; - - return 0; -} -``` - -```cpp-exec:lambda_sort_example.cpp -Sorted in descending order: 9 8 5 2 1 -``` - -## この章のまとめ - -この章では、STLのアルゴリズムとラムダ式について学びました。 - - * **イテレータ**は、コンテナの要素を指し示すオブジェクトであり、アルゴリズムとコンテナの間のインターフェースとして機能します。 - * `` ヘッダには、**`std::sort`** (ソート)、**`std::find`** (検索)、**`std::for_each`** (繰り返し処理) といった、汎用的で強力なアルゴリズムが多数用意されています。 - * **ラムダ式**は、その場で定義できる無名関数であり、アルゴリズムに渡す処理を簡潔かつ直感的に記述することができます。 - * **キャプチャ**機能を使うことで、ラムダ式の外にある変数を取り込んで処理に利用できます。 - -コンテナ、イテレータ、アルゴリズム、そしてラムダ式。これらを組み合わせることで、C++におけるデータ処理は、他の多くの言語に引けを取らない、あるいはそれ以上に表現力豊かで効率的なものになります。 - -### 練習問題1: 文字列の長さでソート - -`std::vector` を用意し、格納されている文字列を、文字数が短い順にソートして、結果を出力するプログラムを作成してください。`std::sort` とラムダ式を使用してください。 - -**ヒント**: ラムダ式は2つの文字列を引数に取り、1つ目の文字列の長さが2つ目の文字列の長さより短い場合に `true` を返すように実装します。 - - -```cpp:practice12_1.cpp -#include -#include -#include - -int main() { - std::vector words = {"apple", "banana", "kiwi", "cherry", "fig", "grape"}; - - return 0; -} -``` - -```cpp-exec:practice12_1.cpp -fig kiwi grape apple banana cherry -``` - -### 練習問題2: 条件に合う要素のカウント - -`std::vector` に整数をいくつか格納します。その後、ラムダ式と `std::for_each`(または他のアルゴリズム)を使って、以下の2つの条件を満たす要素がそれぞれいくつあるかを数えて出力してください。 - -1. 正の偶数である要素の数 -2. 負の奇数である要素の数 - -**ヒント**: カウント用の変数を2つ用意し、ラムダ式のキャプチャ句で参照キャプチャ (`[&]`) して、式の中でインクリメントします。 - -```cpp:practice12_2.cpp -#include -#include -#include - -int main() { - std::vector numbers = {3, -1, 4, -5, 6, -7, 8, 0, -2}; - - - return 0; -} -``` - -```cpp-exec:practice12_2.cpp -Positive even count: 3 -Negative odd count: 3 -``` diff --git a/public/docs/cpp-13.md b/public/docs/cpp-13.md deleted file mode 100644 index a5f5f04..0000000 --- a/public/docs/cpp-13.md +++ /dev/null @@ -1,396 +0,0 @@ -# 第13章: モダンC++の流儀:RAIIとスマートポインタ - -これまでの章で、`new` と `delete` を使った動的なメモリ管理を学びました。しかし、これらの手動管理は `delete` の呼び忘れによるメモリリークや、複雑なコードでのリソース管理の煩雑さを引き起こす原因となりがちです。 - -C++11以降の「モダンC++」では、こうした問題を解決するための洗練された仕組みが導入されました。この章では、エラーハンドリングのための**例外処理**、リソース管理の基本思想である **RAIIイディオム**、そしてそれを具現化する**スマートポインタ** (`std::unique_ptr`, `std::shared_ptr`) について学び、より安全で堅牢なコードを書くための流儀を身につけます。 - -## 例外処理: try, catch を使ったエラーハンドリング - -プログラムでは、ファイルの読み込み失敗やメモリ確保の失敗など、予期せぬエラーが発生することがあります。C++では、このような状況を処理するために**例外 (Exception)** という仕組みが用意されています。 - -例外処理は、以下の3つのキーワードで構成されます。 - - * `throw`: 例外的な状況が発生したことを知らせるために、例外オブジェクトを「投げる」。 - * `try`: 例外が発生する可能性のあるコードブロックを囲む。 - * `catch`: `try` ブロック内で投げられた例外を「捕まえて」処理する。 - -基本的な構文を見てみましょう。 - -```cpp:exception_basic.cpp -#include -#include // std::runtime_error のために必要 - -// 0で割ろうとしたら例外を投げる関数 -double divide(int a, int b) { - if (b == 0) { - // エラー内容を示す文字列を渡して例外オブジェクトを作成し、投げる - throw std::runtime_error("Division by zero!"); - } - return static_cast(a) / b; -} - -int main() { - int a = 10; - int b = 0; - - try { - // 例外が発生する可能性のあるコード - std::cout << "Trying to divide..." << std::endl; - double result = divide(a, b); - std::cout << "Result: " << result << std::endl; // この行は実行されない - } catch (const std::runtime_error& e) { - // std::runtime_error 型の例外をここで捕まえる - std::cerr << "Caught an exception: " << e.what() << std::endl; - } - - std::cout << "Program finished." << std::endl; - return 0; -} -``` - -```cpp-exec:exception_basic.cpp -Trying to divide... -Caught an exception: Division by zero! -Program finished. -``` - -`divide` 関数内で `b` が0だった場合に `throw` が実行され、関数の実行は即座に中断されます。制御は呼び出し元の `catch` ブロックに移り、そこでエラー処理が行われます。これにより、エラーが発生してもプログラム全体がクラッシュすることなく、安全に処理を続行できます。 - -### 例外とリソースリーク - -ここで、`new` と `delete` を使った手動のメモリ管理と例外処理が組み合わさると、問題が発生します。 - -```cpp:raw_pointer_problem.cpp -#include -#include - -void process_data() { - int* data = new int[100]; // リソース確保 - std::cout << "Data allocated." << std::endl; - - // 何らかの処理... - bool something_wrong = true; - if (something_wrong) { - throw std::runtime_error("Something went wrong during processing!"); - } - - // 例外が投げられると、この行には到達しない - std::cout << "Deleting data..." << std::endl; - delete[] data; // リソース解放 -} - -int main() { - try { - process_data(); - } catch (const std::runtime_error& e) { - std::cerr << "Error: " << e.what() << std::endl; - } - // process_data内で確保されたメモリは解放されないままである! - return 0; -} -``` - -```cpp-exec:raw_pointer_problem.cpp -Data allocated. -Error: Something went wrong during processing! -``` - -この例では、`process_data` 関数内で `throw` が実行されると、関数の実行が中断され `catch` ブロックにジャンプします。その結果、`delete[] data;` の行が実行されず、確保されたメモリが解放されない**メモリリーク**が発生します。 - -この問題を解決するのが、C++の最も重要な設計思想の一つである **RAII** です。 - -## RAIIイディオム - -**RAII (Resource Acquisition Is Initialization)** は、「リソースの確保は、オブジェクトの初期化時に行い、リソースの解放は、オブジェクトの破棄時に行う」という設計パターンです。日本語では「リソース取得は初期化である」と訳されます。 - -C++では、オブジェクトがそのスコープ(変数が宣言された `{}` の範囲)を抜けるときに、そのオブジェクトの**デストラクタ**が自動的に呼び出されます。この仕組みは、関数が正常に終了した場合だけでなく、**例外が投げられてスコープを抜ける場合でも保証されています**。 - -RAIIはこの性質を利用して、リソースの解放処理をデストラクタに記述することで、リソースの解放を自動化し、`delete` の呼び忘れや例外発生時のリソースリークを防ぎます。 - -簡単なRAIIクラスの例を見てみましょう。 - -```cpp:raii_concept.cpp -#include - -class ResourceWrapper { -private: - int* m_data; - -public: - // コンストラクタでリソースを確保 - ResourceWrapper() { - m_data = new int[10]; - std::cout << "Resource acquired." << std::endl; - } - - // デストラクタでリソースを解放 - ~ResourceWrapper() { - delete[] m_data; - std::cout << "Resource released." << std::endl; - } -}; - -void use_resource() { - ResourceWrapper rw; // オブジェクトが生成され、コンストラクタでリソースが確保される - std::cout << "Using resource..." << std::endl; - - // この関数が終了するとき (正常終了でも例外でも)、 - // rwのデストラクタが自動的に呼ばれ、リソースが解放される -} - -int main() { - std::cout << "Entering main." << std::endl; - use_resource(); - std::cout << "Exiting main." << std::endl; - return 0; -} -``` - -```cpp-exec:raii_concept.cpp -Entering main. -Resource acquired. -Using resource... -Resource released. -Exiting main. -``` - -`use_resource` 関数が終了すると、`rw` オブジェクトがスコープを抜けるため、`ResourceWrapper` のデストラクタが自動的に呼び出され、`delete[]` が実行されます。もし `use_resource` の中で例外が発生したとしても、デストラクタは保証付きで呼び出されます。 - -この強力なRAIIイディオムを、動的メモリ管理のために標準ライブラリが提供してくれているのが**スマートポインタ**です。 - -## スマートポインタ: new/deleteを自動化する - -スマートポインタは、RAIIを実装したクラステンプレートで、生ポインタ (`int*` など) のように振る舞いながら、リソース (確保したメモリ) の所有権を管理し、適切なタイミングで自動的に解放してくれます。 - -モダンC++では、メモリ管理に生ポインタを直接使うことはほとんどなく、スマートポインタを使うのが基本です。主に2種類のスマートポインタを使い分けます。 - -### `std::unique_ptr` - -`std::unique_ptr` は、管理するオブジェクトの**所有権を唯一に保つ**スマートポインタです。つまり、あるオブジェクトを指す `unique_ptr` は、常に一つしか存在できません。 - - * **唯一の所有権**: コピーが禁止されています。オブジェクトの所有権を別の `unique_ptr` に移したい場合は、**ムーブ (`std::move`)** を使います。 - * **軽量**: ポインタ一つ分のサイズしかなく、オーバーヘッドが非常に小さいです。 - -`unique_ptr` を作成するには、`std::make_unique` を使うのが安全で推奨されています。 - -```cpp:unique_ptr_example.cpp -#include -#include // スマートポインタのために必要 -#include // std::moveのために必要 - -struct MyData { - MyData() { std::cout << "MyData constructor" << std::endl; } - ~MyData() { std::cout << "MyData destructor" << std::endl; } - void greet() { std::cout << "Hello from MyData!" << std::endl; } -}; - -void process_ptr(std::unique_ptr ptr) { - std::cout << "Inside process_ptr" << std::endl; - ptr->greet(); - // ptrがこの関数のスコープを抜けるときにデストラクタが呼ばれる -} - -int main() { - std::cout << "--- Block 1 ---" << std::endl; - { - // std::make_unique を使ってオブジェクトを生成し、unique_ptrで管理 - std::unique_ptr u_ptr1 = std::make_unique(); - - // 生ポインタと同じように -> や * でメンバにアクセスできる - u_ptr1->greet(); - - // コピーはコンパイルエラーになる - // std::unique_ptr u_ptr2 = u_ptr1; // ERROR! - - // 所有権を u_ptr3 に移動 (ムーブ) - std::unique_ptr u_ptr3 = std::move(u_ptr1); - - // ムーブ後、u_ptr1 は空(nullptr)になる - if (u_ptr1 == nullptr) { - std::cout << "u_ptr1 is now empty." << std::endl; - } - - u_ptr3->greet(); - } // ブロックを抜けると u_ptr3 が破棄され、MyDataのデストラクタが呼ばれる - - std::cout << "\n--- Block 2 ---" << std::endl; - { - auto u_ptr4 = std::make_unique(); - // 関数の引数に渡すことで所有権を譲渡する - process_ptr(std::move(u_ptr4)); - std::cout << "Returned from process_ptr" << std::endl; - } - - std::cout << "\nProgram finished." << std::endl; - return 0; -} -``` - -```cpp-exec:unique_ptr_example.cpp ---- Block 1 --- -MyData constructor -Hello from MyData! -u_ptr1 is now empty. -Hello from MyData! -MyData destructor - ---- Block 2 --- -MyData constructor -Inside process_ptr -Hello from MyData! -MyData destructor -Returned from process_ptr - -Program finished. -``` - -`unique_ptr` は、オブジェクトの所有者が誰であるかが明確な場合に最適です。基本的にはまず `unique_ptr` を使うことを検討しましょう。 - -### `std::shared_ptr` - -`std::shared_ptr` は、管理するオブジェクトの**所有権を複数のポインタで共有できる**スマートポインタです。 - - * **共有された所有権**: `shared_ptr` は自由にコピーできます。コピーされるたびに、内部の**参照カウンタ**が増加します。 - * **自動解放**: `shared_ptr` が破棄される(デストラクタが呼ばれる)と参照カウンタが減少し、**参照カウンタが0になったとき**に、管理しているオブジェクトが解放(`delete`)されます。 - * **オーバーヘッド**: 参照カウンタを管理するための追加のメモリと処理が必要なため、`unique_ptr` よりもわずかにオーバーヘッドが大きいです。 - -`shared_ptr` を作成するには、`std::make_shared` を使うのが効率的で安全です。 - -```cpp:shared_ptr_example.cpp -#include -#include -#include - -struct MyResource { - MyResource() { std::cout << "MyResource constructor" << std::endl; } - ~MyResource() { std::cout << "MyResource destructor" << std::endl; } -}; - -int main() { - std::shared_ptr s_ptr1; // 空のshared_ptr - - std::cout << "--- Block 1 ---" << std::endl; - { - // std::make_shared を使ってオブジェクトを生成し、shared_ptrで管理 - s_ptr1 = std::make_shared(); - std::cout << "Use count: " << s_ptr1.use_count() << std::endl; // 1 - - { - // s_ptr2 は s_ptr1 と同じオブジェクトを指す - std::shared_ptr s_ptr2 = s_ptr1; - std::cout << "Use count: " << s_ptr1.use_count() << std::endl; // 2 - std::cout << "Use count: " << s_ptr2.use_count() << std::endl; // 2 - } // s_ptr2がスコープを抜ける。参照カウンタが1に減る - - std::cout << "Use count after s_ptr2 is gone: " << s_ptr1.use_count() << std::endl; // 1 - } // s_ptr1がスコープを抜ける。参照カウンタが0になり、オブジェクトが破棄される - - std::cout << "\n--- Block 2 ---" << std::endl; - { - auto shared_res = std::make_shared(); - std::cout << "Initial use count: " << shared_res.use_count() << std::endl; // 1 - - std::vector> ptr_vec; - ptr_vec.push_back(shared_res); // コピー。参照カウンタは2 - ptr_vec.push_back(shared_res); // コピー。参照カウンタは3 - - std::cout << "Use count after pushing to vector: " << shared_res.use_count() << std::endl; // 3 - } // shared_resとptr_vecがスコープを抜ける。 - // 全てのshared_ptrが破棄され、最後に参照カウンタが0になり、オブジェクトが破棄される - - std::cout << "\nProgram finished." << std::endl; - return 0; -} -``` - -```cpp-exec:shared_ptr_example.cpp ---- Block 1 --- -MyResource constructor -Use count: 1 -Use count: 2 -Use count: 2 -Use count after s_ptr2 is gone: 1 -MyResource destructor - ---- Block 2 --- -MyResource constructor -Initial use count: 1 -Use count after pushing to vector: 3 -MyResource destructor - -Program finished. -``` - -`shared_ptr` は、オブジェクトの寿命が単一のスコープや所有者に縛られず、複数のオブジェクトから共有される必要がある場合に便利です。ただし、所有権の関係が複雑になりがちなので、本当に共有が必要な場面に限定して使いましょう。 - -## この章のまとめ - - * **例外処理**は `try`, `catch`, `throw` を使い、エラーが発生してもプログラムを安全に継続させるための仕組みです。 - * 手動のメモリ管理下で例外が発生すると、**リソースリーク**を引き起こす危険があります。 - * **RAIIイディオム**は、リソースの確保をコンストラクタ、解放をデストラクタで行うことで、リソース管理を自動化するC++の重要な設計思想です。 - * **スマートポインタ**はRAIIを動的メモリ管理に適用したもので、`new` と `delete` の手動管理を不要にします。 - * **`std::unique_ptr`** はオブジェクトの**唯一の所有権**を管理します。軽量であり、所有権が明確な場合に第一の選択肢となります。 - * **`std::shared_ptr`** はオブジェクトの**所有権を共有**します。参照カウントによって管理され、最後の所有者がいなくなったときにオブジェクトを解放します。 - -モダンC++プログラミングでは、`new` と `delete` を直接書くことは極力避け、RAIIとスマートポインタを全面的に活用することが、安全でメンテナンス性の高いコードへの第一歩です。 - -### 練習問題1: `unique_ptr` と所有権の移動 - -`Employee` という名前のクラスを作成してください。このクラスは、コンストラクタで社員名を受け取って表示し、デストラクタで「(社員名) is leaving.」というメッセージを表示します。 - -`main` 関数で、`"Alice"` という名前の `Employee` オブジェクトを `std::make_unique` で作成し、その `unique_ptr` を `promote_employee` という関数に渡してください。`promote_employee` 関数は `unique_ptr` を引数として受け取り(所有権が移動します)、「(社員名) has been promoted\!」というメッセージを表示します。 - -プログラムを実行し、コンストラクタとデストラクタのメッセージが期待通りに表示されることを確認してください。 - -```cpp:practice13_1.cpp -#include -#include -#include - -// ここにEmployeeクラスを定義 - - -int main() { - - -} -``` - -```cpp-exec:practice13_1.cpp -Employee Alice has joined the company. -Alice has been promoted! -Employee Alice is leaving. -``` - -### 問題2: `shared_ptr` と所有権の共有 - -`Project` という名前のクラスを作成してください。コンストラクタでプロジェクト名を受け取り、デストラクタで「Project (プロジェクト名) is finished.」と表示します。 - -`main` 関数で、`"Project Phoenix"` という名前の `Project` オブジェクトを `std::make_shared` で作成してください。 -次に、`std::vector>` を作成し、作成した `shared_ptr` を2回 `push_back` してください。 -その後、`shared_ptr` の参照カウント (`use_count()`) を表示してください。 -最後に、`vector` を `clear()` して、再度参照カウントを表示してください。 -プログラムの実行が終了するときに `Project` のデストラクタが呼ばれることを確認してください。 - -```cpp:practice13_2.cpp -#include -#include -#include -#include - -// ここにProjectクラスを定義 - - -int main() { - - -} -``` - -```cpp-exec:practice13_2.cpp -Project Project Phoenix is started. -Initial use count: 1 -Use count after pushing to vector: 3 -Use count after clearing vector: 1 -Project Project Phoenix is finished. -``` diff --git a/public/docs/cpp-2.md b/public/docs/cpp-2.md deleted file mode 100644 index ebdf94c..0000000 --- a/public/docs/cpp-2.md +++ /dev/null @@ -1,253 +0,0 @@ -# 第2章: C++の型システムと制御構造:静的型付けとスコープを再確認する - -C++は**静的型付け言語**です。PythonやJavaScriptのような動的型付け言語とは異なり、コンパイル時に変数の型が確定している必要があります。これにより、実行時のエラーを未然に防ぎ、高いパフォーマンスを実現します。 - -## 基本的なデータ型 - -C++には多くの型がありますが、まずは以下の基本型を押さえましょう。 - - * **整数型**: `int` (通常4バイト), `long long` (8バイト, 大きな整数) - * **浮動小数点型**: `double` (倍精度, 基本的にこれを使う), `float` (単精度) - * **文字型**: `char` (1バイト文字), `std::string` (文字列クラス。厳密には基本型ではありませんが、実用上必須) - * **ブール型**: `bool` (`true` または `false`) - -C++では変数のサイズ(ビット幅)が環境によって異なる場合がありますが、現代的な環境では `int` は32bit以上であることが保証されています。 - -## 変数の初期化:ユニフォーム初期化 `{}` - -C++には変数を初期化する方法がいくつもありますが、C++11以降では **波括弧 `{}` を使った初期化(ユニフォーム初期化)** が推奨されています。 - -なぜ `{}` が良いのでしょうか? それは、**縮小変換(Narrowing Conversion)** を防げるからです。例えば、少数のデータを整数型変数に無理やり入れようとした時、`=` なら黙って切り捨てられますが、`{}` ならコンパイルエラーにしてくれます。 - -```cpp:initialization.cpp -#include - -int main() { - // 推奨:波括弧による初期化 - int age{25}; // int age = 25; と同じだがより安全 - double weight{65.5}; - bool is_student{false}; - - // 縮小変換の防止(コメントアウトを外すとコンパイルエラーになります) - // int height{170.5}; // エラー! doubleからintへの情報の欠落を防ぐ - - // 従来の方法(=を使う)も間違いではありませんが、警告が出ないことがあります - int rough_height = 170.9; // 170に切り捨てられる(エラーにならない) - - std::cout << "Alice is " << age << " years old." << std::endl; - std::cout << "Height (rough): " << rough_height << std::endl; - - return 0; -} -``` - -```cpp-exec:initialization.cpp -Alice is 25 years old. -Height (rough): 170 -``` - -## 型を厳密に扱う - -静的型付けの恩恵を最大限に受けるために、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言語スタイルの文字列) になるので注意 -``` - -## コンソール入出力 (`std::cin`, `std::cout`) - -C言語の `printf`/`scanf` と異なり、C++ではストリーム(データの流れ)として入出力を扱います。型指定子(`%d`など)を覚える必要がなく、型安全です。 - - * `std::cout << 値`: 出力(Console OUT) - * `std::cin >> 変数`: 入力(Console IN) - * `std::endl`: 改行を行い、バッファをフラッシュする。 - -> my.code(); の実行環境には入力機能がないので、コード例だけ示します: - -```cpp -#include -#include - -int main() { - int id; - std::string name; - - // 複数の値を出力する場合、<< で連結します - std::cout << "Enter ID and Name: "; - - // キーボードから "101 Bob" のように入力されるのを待つ - std::cin >> id >> name; - - std::cout << "User: " << name << " (ID: " << id << ")" << std::endl; - // User: Bob (ID: 101) - - return 0; -} -``` - -## 制御構文:if, switch, while, for - -他のC系言語(Java, C\#, JSなど)とほぼ同じですが、いくつか注意点があります。 - -### if文 - -`if (条件式)` の条件式は `bool` に変換可能なものである必要があります。C++では `0` は `false`、それ以外は `true` とみなされます。 - -### switch文とフォールスルー - -`switch` 文は `break` を書かない限り、次の `case` へ処理が流れます(フォールスルー)。意図的なフォールスルーでない限り、`break` を忘れないように注意が必要です。C++17以降では `[[fallthrough]];` 属性をつけることで、「意図的なものである」とコンパイラに伝え、警告を抑制できます。 - -### ループ構文 - -`while`, `for` も標準的です。 - -```cpp:control.cpp -#include - -int main() { - // --- if文 --- - const int score = 85; - if (score >= 90) { - std::cout << "Grade: A" << std::endl; - } else if (score >= 80) { - std::cout << "Grade: B" << std::endl; - } else { - std::cout << "Grade: C or below" << std::endl; - } - - // --- switch文 --- - const int rank = 2; - std::cout << "Rank " << rank << ": "; - - switch (rank) { - case 1: - std::cout << "Gold" << std::endl; - break; - case 2: - std::cout << "Silver" << std::endl; - // breakを忘れるとcase 3も実行される - [[fallthrough]]; // C++17: 意図的に下に流すことを明示 - case 3: - std::cout << "(Medalist)" << std::endl; - break; - default: - std::cout << "Participant" << std::endl; - } - - // --- 基本的なforループ --- - std::cout << "Countdown: "; - for (int i = 3; i > 0; --i) { - std::cout << i << " "; - } - std::cout << "Start!" << std::endl; - - return 0; -} -``` - -```cpp-exec:control.cpp -Grade: B -Rank 2: Silver -(Medalist) -Countdown: 3 2 1 Start! -``` - -## この章のまとめ - - * **型システム**: `int`, `double`, `bool` などの基本型があり、静的に管理される。 - * **初期化**: `int x{10};` のような波括弧 `{}` を使う初期化が推奨される(縮小変換の防止)。 - * **型推論と定数**: `auto` で型推論を行い、変更しない変数には `const` を付ける。 - * **入出力**: `std::cout`, `std::cin` を使い、`<<`, `>>` 演算子でデータを流す。 - * **制御構文**: 基本は他言語と同じだが、`switch` のフォールスルー挙動などに注意する。 - - -## 練習問題1:うるう年判定機 - -西暦(整数)を変数 `year` に代入し、その年がうるう年かどうかを判定して結果を出力するプログラムを書いてください。 - -> **うるう年の条件** -> -> 1. 4で割り切れる年はうるう年である。 -> 2. ただし、100で割り切れる年はうるう年ではない。 -> 3. ただし、400で割り切れる年はうるう年である。 - -```cpp:practice2_1.cpp -#include - -int main() { - const int year = 2025; - - // ここにコードを書く -} -``` - -```cpp-exec:practice2_1.cpp -``` - -### 練習問題2:FizzBuzz(C++スタイル) - -1から20までの整数を順に出力するループを作成してください。ただし、以下のルールに従ってください。 - - * 数値が3で割り切れるときは数値の代わりに "Fizz" と出力。 - * 数値が5で割り切れるときは数値の代わりに "Buzz" と出力。 - * 両方で割り切れるときは "FizzBuzz" と出力。 - * それ以外は数値をそのまま出力。 - -出力はスペース区切りまたは改行区切りどちらでも構いません。変数の初期化には `{}` を、ループカウンタの型には `auto` を使用してみてください。 - -```cpp:practice2_2.cpp -#include - -int main() { - -} -``` - -```cpp-exec:practice2_2.cpp -``` diff --git a/public/docs/cpp-3.md b/public/docs/cpp-3.md deleted file mode 100644 index 6cb1ae6..0000000 --- a/public/docs/cpp-3.md +++ /dev/null @@ -1,267 +0,0 @@ -# 第3章: データ集合とモダンな操作:「配列」ではなく「コンテナ」としてデータを扱う - -他の言語(Python, JavaScript, C\#など)経験者がC++を学び始めるとき、最も躓きやすいのが「文字や配列の扱い」です。古いC言語の教科書では、ポインタ操作やメモリ管理が必須となる「Cスタイル」のやり方から入ることが多いのですが、**現代の実務的なC++(モダンC++)では、もっと安全で便利な「クラス(コンテナ)」を使います。** - -この章では、ポインタの複雑な話を抜きにして、他の高級言語と同じくらい直感的にデータを扱えるツールを紹介します。 - -## 文字列の扱い:std::string - -C言語では文字列を扱うために `char*` や `char[]` を使い、ヌル終端文字 `\0` を意識する必要がありました。これはバグの温床です。 -C++では、標準ライブラリの `std::string` クラスを使用します。これはPythonの `str` や Javaの `String` のように直感的に扱えます。 - -### 主な機能 - - * **代入・初期化**: 文字列リテラルをそのまま代入可能。 - * **結合**: `+` 演算子で結合可能。 - * **比較**: `==`, `!=` などで中身の文字列比較が可能(C言語の `strcmp` は不要)。 - * **サイズ取得**: `.size()` または `.length()` メソッドを使用。 - - - -```cpp:string_demo.cpp -#include -#include // std::stringを使うために必要 - -int main() { - // 初期化 - std::string greeting = "Hello"; - std::string target = "World"; - - // 文字列の結合 - std::string message = greeting + ", " + target + "!"; - - // 出力 - std::cout << message << std::endl; - - // 長さの取得 - std::cout << "Length: " << message.size() << std::endl; // .length()でも同じ - - // 文字列の比較 - if (greeting == "Hello") { - std::cout << "Greeting matches 'Hello'." << std::endl; - } - - // 特定の文字へのアクセス(配列のようにアクセス可能) - message[0] = 'h'; // 先頭を小文字に変更 - std::cout << "Modified: " << message << std::endl; - - return 0; -} -``` - -```cpp-exec:string_demo.cpp -Hello, World! -Length: 13 -Greeting matches 'Hello'. -Modified: hello, World! -``` - -> **Note:** `std::string` は必要に応じて自動的にメモリを拡張します。プログラマがメモリ確保(malloc/free)を気にする必要はありません。 - -## 可変長配列:std::vector - -「データの個数が事前にわからない」「途中でデータを追加したい」という場合、C++で最も頻繁に使われるのが `std::vector` です。これは「動的配列」や「可変長配列」と呼ばれ、Pythonの `list` や Javaの `ArrayList` に相当します。 - -### 基本操作 - - * **宣言**: `std::vector<型> 変数名;` - * **追加**: `.push_back(値)` で末尾に追加。 - * **アクセス**: `変数名[インデックス]` または `.at(インデックス)`。 - * **サイズ**: `.size()`。 - - - -```cpp:vector_demo.cpp -#include -#include // std::vectorを使うために必要 - -int main() { - // 整数を格納するvector(初期サイズは0) - std::vector numbers; - - // データの追加 - numbers.push_back(10); - numbers.push_back(20); - numbers.push_back(30); - - // サイズの確認 - std::cout << "Size: " << numbers.size() << std::endl; - - // 要素へのアクセス - std::cout << "First element: " << numbers[0] << std::endl; - - // .at() を使うと範囲外アクセスの時に例外を投げてくれる(安全) - try { - std::cout << numbers.at(100) << std::endl; // 範囲外 - } catch (const std::out_of_range& e) { - std::cout << "Error: " << e.what() << std::endl; - } - - // 初期化リストを使った宣言(C++11以降) - std::vector prices = {10.5, 20.0, 33.3}; - std::cout << "Price list size: " << prices.size() << std::endl; - - return 0; -} -``` - -```cpp-exec:vector_demo.cpp -Size: 3 -First element: 10 -Error: vector::_M_range_check: __n (which is 100) >= this->size() (which is 3) -Price list size: 3 -``` - -## 固定長配列:std::array - -データの個数が決まっている場合(例えば、3次元座標、RGB値、固定バッファなど)は、`std::vector` よりも `std::array` が適しています。 - -「なぜ昔ながらの `int arr[5];` を使わないの?」と思われるかもしれません。 -Cスタイルの配列は、他のコンテナ(vectorなど)と操作感が異なり、サイズ情報を自分で管理しなければならないなどの欠点があります。`std::array` はC配列のパフォーマンス(スタック確保)と、コンテナの利便性(`.size()`などが使える)を両立させたものです。 - -```cpp:array_demo.cpp -#include -#include // std::arrayを使うために必要 - -int main() { - // int型でサイズ3の配列を宣言・初期化 - // std::array<型, サイズ> - std::array coords = {10, 20, 30}; - - std::cout << "X: " << coords[0] << std::endl; - std::cout << "Y: " << coords[1] << std::endl; - std::cout << "Z: " << coords[2] << std::endl; - - // vectorと同じようにsize()が使える - std::cout << "Dimension: " << coords.size() << std::endl; - - return 0; -} -``` - -```cpp-exec:array_demo.cpp -X: 10 -Y: 20 -Z: 30 -Dimension: 3 -``` - -## 範囲ベース for ループ (Range-based for) - -`std::vector` や `std::array` の中身を順番に処理する場合、インデックス `i` を使った `for (int i = 0; i < n; ++i)` は書くのが面倒ですし、境界外アクセスのリスクがあります。 - -モダンC++では、PythonやC\#の `foreach` に相当する **範囲ベース for ループ** が使えます。 - -### 基本構文 - -```cpp -for (要素の型 変数名 : コンテナ) { - // 処理 -} -``` - -ここで便利なのが、**`auto` キーワード**です。`auto` を使うと、コンパイラが型を自動推論してくれるため、型名を詳しく書く必要がなくなります。 - -```cpp:range_for_demo.cpp -#include -#include -#include - -int main() { - std::vector inventory = {"Sword", "Shield", "Potion"}; - - std::cout << "--- Inventory List ---" << std::endl; - - // string item : inventory と書いても良いが、autoが楽 - for (auto item : inventory) { - std::cout << "- " << item << std::endl; - } - - // 数値の計算例 - std::vector scores = {80, 65, 90, 72}; - int total = 0; - - for (auto score : scores) { - total += score; - } - - std::cout << "Total Score: " << total << std::endl; - - return 0; -} -``` - -```cpp-exec:range_for_demo.cpp ---- Inventory List --- -- Sword -- Shield -- Potion -Total Score: 307 -``` - -> **Advanced Hint:** -> 上記の `auto item` は、要素を「コピー」して取り出します。`std::string` のような大きなデータを扱う場合、コピーコストを避けるために `const auto& item` (参照)を使うのが一般的ですが、これについては**第5章**で詳しく解説します。今の段階では「`auto` でループが回せる」と覚えておけば十分です。 - -# この章のまとめ - -1. **文字列**: `char*` ではなく `std::string` を使う。結合や比較が簡単で安全。 -2. **動的配列**: データの増減がある場合は `std::vector` を使う。`push_back()` で追加できる。 -3. **固定配列**: サイズ固定の場合は `std::array` を使う。Cスタイル配列のモダンな代替。 -4. **ループ**: コンテナの全要素走査には「範囲ベース for ループ」と `auto` を使うとシンプルに書ける。 - -これらの「標準ライブラリ(STL: Standard Template Library)」のコンテナを活用することで、メモリ管理の苦労を飛ばして、アプリケーションのロジックに集中できるようになります。 - -### 練習問題1: 数値リストの統計 - -`std::vector` を使用して、好きな整数を5つほど格納してください(コード内で直接初期化して構いません)。 -その後、範囲ベース for ループを使用して、その数値の「合計」と「最大値」を求めて出力するプログラムを作成してください。 - -```cpp:practice3_1.cpp -#include -#include - -int main() { - // ここに整数リストを初期化してください - std::vector numbers = {12, 45, 7, 23, 89}; - - - // 結果を出力 - std::cout << "Sum: " << sum << std::endl; - std::cout << "Max Value: " << max_value << std::endl; - - return 0; -} -``` - -```cpp-exec:practice3_1.cpp -Sum: 176 -Max Value: 89 -``` - -### 練習問題2: 単語のフィルタリング - -以下の単語リスト `words` の中から、**文字数(長さ)が5文字より大きい単語だけ**を選んで表示するプログラムを作成してください。 -(ヒント:`std::string` の `.size()` または `.length()` メソッドと `if` 文を使用します) - -```cpp:practice3_2.cpp -#include -#include -#include - -int main() { - std::vector words = {"Apple", "Banana", "Cherry", "Date", "Elderberry"}; - - std::cout << "Words longer than 5 characters:" << std::endl; - - // ここにコードを書く - - return 0; -} -``` - -```cpp-exec:practice3_2.cpp -Words longer than 5 characters: -Banana -Cherry -Elderberry -``` diff --git a/public/docs/cpp-4.md b/public/docs/cpp-4.md deleted file mode 100644 index 1d0a05c..0000000 --- a/public/docs/cpp-4.md +++ /dev/null @@ -1,305 +0,0 @@ -# 第4章: ポインタとメモリ管理の深淵 - -ようこそ、C++学習の最大の山場へ。 -第3章までは、`std::vector`や`std::string`といった便利な機能を使ってきましたが、今回はその「裏側」で何が起きているかを覗き込みます。 - -他の言語(Java, Python, C\#など)では言語機能やガベージコレクション(GC)が隠蔽してくれている「メモリ」という物理的なリソースを、C++では直接操作することができます。これがC++の強力な武器であり、同時にバグの温床でもあります。 - -ここを理解すれば、第3章の機能がいかに偉大だったか、そしてコンピュータが実際にどう動いているかが手に取るようにわかるようになります。 - -## ポインタの基礎 - -ポインタとは、変数の「値」ではなく、その変数がメモリ上の**どこにあるか(アドレス)**を格納する変数です。 - -変数の型に応じて、対応するポインタの型が存在します。例えば、`int`型の変数のアドレスを格納するなら `int*` 型、`double`型の変数のアドレスを格納するなら `double*` 型のポインタを使います。アスタリスク `*` がポインタ型であることを示します。 - -> ポインタ変数の宣言時に `*` を型の横に付けるか、変数名の横に付けるかは好みが分かれますが、意味は同じです (`int* p;` と `int *p;` は等価)。このチュートリアルでは `int* p;` のように型の側に付けます。 - -### アドレスと間接参照 - - * **アドレス演算子 `&`**: 変数のメモリ上の住所(アドレス)を取得します。 - * **間接参照演算子 `*`**: ポインタが指し示している住所に行き、その中身(値)にアクセスします。 - - - -```cpp:basic_pointer.cpp -#include - -int main() { - int number = 42; - // numberのアドレスを取得して ptr に格納 - // int* は「int型へのポインタ」という意味 - int* ptr = &number; - - std::cout << "numberの値: " << number << std::endl; - std::cout << "numberのアドレス (&number): " << &number << std::endl; - std::cout << "ptrの値 (アドレス): " << ptr << std::endl; - - // アドレスの中身を見る(間接参照) - std::cout << "ptrが指す中身 (*ptr): " << *ptr << std::endl; - - // ポインタ経由で値を書き換える - *ptr = 100; - std::cout << "書き換え後のnumber: " << number << std::endl; - - return 0; -} -``` - -```cpp-exec:basic_pointer.cpp -numberの値: 42 -numberのアドレス (&number): 0x7ffedffe3adc -ptrの値 (アドレス): 0x7ffedffe3adc -ptrが指す中身 (*ptr): 42 -書き換え後のnumber: 100 -``` - -※ アドレス(0x...)は実行環境ごとに異なります。 - -### `nullptr` の使用 - -ポインタが「どこも指していない」ことを示したい場合、C++では `nullptr` を使用します。 -古いC++やC言語では `NULL` や `0` が使われていましたが、モダンC++では型安全な `nullptr` を使うのが鉄則です。初期化されていないポインタは不定な場所を指すため、必ず初期化しましょう。 - -```cpp:pointer_declaration.cpp -#include - -int main() { - // ポインタの宣言 - // 初期化していないポインタは不定なアドレスを指す可能性がある。 - int* p; - std::cout << "p の初期値(アドレス): " << p << std::endl; - - // *p = 10; // 【危険】未初期化ポインタの間接参照は未定義動作 - - // どの変数も指していないことを示す特別な値 nullptr - // ポインタを初期化する際は nullptr を使うのが安全です - p = nullptr; - std::cout << "p の値(アドレス): " << p << std::endl; - - if (p == nullptr) { - std::cout << "p は何も指していません。" << std::endl; - } - - // *p = 10; // 【危険】nullptrはどこも指していないので、やっぱり未定義動作 - - return 0; -} -``` - -```cpp-exec:pointer_declaration.cpp -p の初期値(アドレス): 0x7ffedffe3ab8 -p の値(アドレス): 0 -p は何も指していません。 -``` - -## 配列とポインタの関係 - -第3章では `std::vector` を使いましたが、C++にはC言語互換の「生の配列(Cスタイル配列)」も存在します。これはサイズが固定で、機能が制限されています。 - -### 配列名の減衰(Decay) - -実は、配列の名前は式の中で使うと、**「先頭要素へのポインタ」**として扱われます。これを「減衰(Decay)」と呼びます。 - -### ポインタ演算 - -ポインタに対して数値を足し引きすると、**「その型のサイズ分」**だけアドレスが移動します。 -`int`(通常4バイト)のポインタに `+1` すると、メモリアドレスは4増えます。 - -```cpp:array_decay.cpp -#include - -int main() { - // Cスタイル配列の宣言(サイズ固定) - int primes[] = {2, 3, 5, 7}; - - // 配列名 primes は &primes[0] とほぼ同じ意味になる - int* ptr = primes; - - std::cout << "先頭要素 (*ptr): " << *ptr << std::endl; - - // ポインタ演算 - // ptr + 1 は次のint要素(メモリ上で4バイト隣)を指す - std::cout << "2番目の要素 (*(ptr + 1)): " << *(ptr + 1) << std::endl; - - // 配列添字アクセス primes[2] は、実は *(primes + 2) のシンタックスシュガー - std::cout << "3番目の要素 (primes[2]): " << primes[2] << std::endl; - std::cout << "3番目の要素 (*(primes + 2)): " << *(primes + 2) << std::endl; - - return 0; -} -``` - -```cpp-exec:array_decay.cpp -先頭要素 (*ptr): 2 -2番目の要素 (*(ptr + 1)): 3 -3番目の要素 (primes[2]): 5 -3番目の要素 (*(primes + 2)): 5 -``` - -## 文字列の正体(Legacy) - -`std::string` が登場する前、文字列は単なる `char` 型の配列でした。これを「Cスタイル文字列」と呼びます。 -現在でも、ライブラリとの連携などで頻繁に目にします。 - -### 文字列リテラルと `char*` - -Cスタイル文字列は、文字の並びの最後に「終端文字 `\0`(ヌル文字)」を置くことで終わりを表します。 - -```cpp:legacy_string.cpp -#include -#include - -int main() { - // 文字列リテラルは const char 配列 - const char* c_str = "Hello"; - - // std::string から Cスタイル文字列への変換 - std::string cpp_str = "World"; - const char* converted = cpp_str.c_str(); // .c_str() を使う - - std::cout << "C-Style: " << c_str << std::endl; - std::cout << "C++ String: " << cpp_str << std::endl; - std::cout << "Converted to C-Style: " << converted << std::endl; - - // 注意: c_str は配列なのでサイズ情報を持っていない - // 終端文字 '\0' まで読み進める必要がある - - return 0; -} -``` - -```cpp-exec:legacy_string.cpp -C-Style: Hello -C++ String: World -Converted to C-Style: World -``` - -**重要:** モダンC++では基本的に `std::string` を使いましょう。`char*` は参照用やAPI互換のために使います。 - -## 動的なメモリ確保 - -ここがメモリ管理の核心です。プログラムが使うメモリ領域には大きく分けて「スタック」と「ヒープ」があります。 - -### スタック (Stack) - - * これまでの変数は主にここに置かれます。 - * 関数のスコープ `{ ... }` を抜けると**自動的に消滅**します。 - * 管理が楽で高速ですが、サイズに制限があります。 - -### ヒープ (Heap) - - * プログラマが**手動で確保・解放**する領域です。 - * 広大なサイズを使えますが、管理を怠ると危険です。 - -### `new` と `delete` - -ヒープ領域を使うには `new` 演算子を使用し、使い終わったら必ず `delete` 演算子でメモリをOSに返却(解放)する必要があります。 - -```cpp:heap_memory.cpp -#include - -int main() { - // ヒープ上に整数を1つ確保 - int* pInt = new int(10); - - // ヒープ上に配列を確保 (サイズ100) - // std::vectorを使わない場合、サイズは動的に決められるが管理は手動 - int size = 5; - int* pArray = new int[size]; - - // 配列への書き込み - for(int i = 0; i < size; ++i) { - pArray[i] = i * 10; - } - - std::cout << "ヒープ上の値: " << *pInt << std::endl; - std::cout << "ヒープ上の配列[2]: " << pArray[2] << std::endl; - - // 【重要】使い終わったら必ず解放する! - delete pInt; // 単体の解放 - delete[] pArray; // 配列の解放 (delete[] を使うこと) - - // 解放後のアドレスには触ってはいけない(ダングリングポインタ) - // 安全のため nullptr にしておく - pInt = nullptr; - pArray = nullptr; - - return 0; -} -``` - -```cpp-exec:heap_memory.cpp -ヒープ上の値: 10 -ヒープ上の配列[2]: 20 -``` - -### 恐怖の「メモリリーク」 - -もし `delete` を忘れるとどうなるでしょう? -確保されたメモリは、プログラムが終了するまで「使用中」のまま残ります。これを**メモリリーク**と呼びます。長時間動くサーバーなどでこれが起きると、メモリを食いつぶしてシステムがクラッシュします。 - -**第3章の振り返り:** -`std::vector` や `std::string` は、内部で `new` と `delete` を自動的に行ってくれています。 - - * 作成時に `new` で確保。 - * スコープを抜けるときに自動で `delete`(デストラクタ)。 - これがC++のクラスの強力な機能(RAII)です。生の `new/delete` を直接使うことは、モダンC++では「最後の手段」あるいは「ライブラリを作る側の仕事」と考えられています。 - - -## この章のまとめ - -1. **ポインタ**はメモリアドレスを保持する変数。`&`で取得、`*`でアクセス。 -2. ポインタの初期化には `nullptr` を使う。 -3. **配列名**は先頭要素へのポインタとして振る舞う(減衰)。 -4. `ptr + i` は、`ptr` の指す型 `i` 個分先のアドレスを指す。 -5. **ヒープメモリ**は `new` で確保し、必ず `delete` で解放する。 -6. `delete` を忘れると**メモリリーク**になる。これを防ぐために `std::vector` などのコンテナクラスが存在する。 - -## 練習問題1: ポインタによる配列操作 - -`int` 型のCスタイル配列 `arr` について、 `int*` 型のポインタを使って走査し、**すべての値を2倍に書き換えてください**(`[]` 演算子は使わず、ポインタ演算 `*` と `++` または `+` を使用すること)。 - -```cpp:practice4_1.cpp -#include - -int main() { - int arr[] = {10, 20, 30, 40, 50}; - - // ここにコードを書く - - - std::cout << "配列の値を2倍にしました: "; - for (int i = 0; i < 5; ++i) { - std::cout << arr[i] << " "; - } - return 0; -} -``` - -```cpp-exec:practice4_1.cpp -配列の値を2倍にしました: 60 80 100 120 140 -``` - -### 問題2:手動メモリ管理の体験 - -`n` 個の整数を格納できる配列を**ヒープ領域(`new`)**に確保してください。 -その配列に 0 から `n-1` までの数値を代入し、合計値を計算して表示してください。 -最後に、確保したメモリを適切に解放してください。 - -```cpp:practice4_2.cpp -#include - -int main() { - const int n = 5; - - // ここにコードを書く - - - return 0; -} -``` - -```cpp-exec:practice4_2.cpp -配列の合計値は: 10 -``` diff --git a/public/docs/cpp-5.md b/public/docs/cpp-5.md deleted file mode 100644 index 1428ee7..0000000 --- a/public/docs/cpp-5.md +++ /dev/null @@ -1,349 +0,0 @@ -# 第5章: 関数の設計とデータの受け渡し:コピー、参照、ポインタ - -前章(第4章)では、C++のメモリモデルの核心である「ポインタ」について学びました。ポインタは強力ですが、構文が複雑になりがちで、バグの温床にもなりえます。 - -C++では、C言語から受け継いだポインタに加え、より安全で直感的な**「参照(Reference)」**という概念が導入されています。本章では、関数の設計を通して、この「参照」がいかに強力な武器になるかを学びます。「データをどう渡すか」は、C++のパフォーマンスと設計の良し悪しを決める最も重要な要素の一つです。 - -## 関数の宣言と定義 - -PythonやJavaScriptのような言語では、関数をどこに書いても(あるいは実行時に解決されて)呼び出せることが多いですが、C++のコンパイラはコードを上から下へと一直線に読みます。そのため、**「使用する前に、その関数が存在すること」**をコンパイラに知らせる必要があります。 - -### プロトタイプ宣言 - -関数を `main` 関数の後に定義したい場合、事前に「こういう名前と引数の関数がありますよ」と宣言だけしておく必要があります。これを**プロトタイプ宣言**と呼びます。 - -```cpp:declaration_intro.cpp -#include - -// プロトタイプ宣言 -// 戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, ...); -// 本体({}の中身)は書かず、セミコロンで終わる -void greet(int times); - -int main() { - std::cout << "main関数開始" << std::endl; - - // 定義は下にあるが、宣言があるので呼び出せる - greet(3); - - return 0; -} - -// 関数の定義 -void greet(int times) { - for (int i = 0; i < times; ++i) { - std::cout << "Hello C++!" << std::endl; - } -} -``` - -```cpp-exec:declaration_intro.cpp -main関数開始 -Hello C++! -Hello C++! -Hello C++! -``` - -実際の開発では、プロトタイプ宣言をヘッダーファイル(`.h`)に書き、定義をソースファイル(`.cpp`)に書くことで、大規模なプログラムを管理します(これについては次章で詳しく解説します)。 - -### 戻り値がない場合: `void`型 - -関数が何も値を返す必要がない場合もあります。例えば、「画面にメッセージを表示するだけ」といった関数です。その場合、戻り値の型として `void` という特別なキーワードを使います。 - -```cpp -void printMessage(std::string message); -``` - -第2章で学んだように、`int`や`double`などの型は変数を定義するために使えましたが、`void`は「型がない」ことを示す特殊な型なので、`void my_variable;` のように変数を定義することはできません。あくまで関数の戻り値の型としてのみ使います。 - -## 引数の渡し方(パフォーマンスと安全性) - -ここが本章のハイライトです。他の言語では言語仕様として決まっていることが多い引数の渡し方を、C++ではプログラマが意図的に選択できます。 - -### 1\. 値渡し (Pass by Value) - -特に何も指定しない場合のデフォルトです。変数の**コピー**が作成され、関数に渡されます。 - - * **メリット:** 安全。関数内で値を変更しても、呼び出し元の変数には影響しない。 - * **デメリット:** コストが高い。巨大な配列やオブジェクトを渡す際、丸ごとコピーするためメモリと時間を浪費する。 - - - -```cpp:pass_by_value.cpp -#include - -// 値渡し:xは呼び出し元のコピー -void attemptUpdate(int x) { - x = 100; // コピーを変更しているだけ - std::cout << "関数内: " << x << " (アドレス: " << &x << ")" << std::endl; -} - -int main() { - int num = 10; - std::cout << "呼び出し前: " << num << " (アドレス: " << &num << ")" << std::endl; - - attemptUpdate(num); - - // numは変わっていない - std::cout << "呼び出し後: " << num << std::endl; - return 0; -} -``` - -```cpp-exec:pass_by_value.cpp -呼び出し前: 10 (アドレス: 0x7ff...) -関数内: 100 (アドレス: 0x7ff...) <-- アドレスが違う=別の領域(コピー) -呼び出し後: 10 -``` - -### 2\. ポインタ渡し (Pass by Pointer) - -C言語からある手法です。第4章で学んだポインタ(アドレス)を渡します。 - - * **メリット:** コピーが発生しない(アドレス値のコピーのみ)。呼び出し元のデータを変更できる。 - * **デメリット:** 呼び出す際に `&` を付ける必要がある。関数内で `*` や `->` を使う必要があり、構文が汚れる。`nullptr` チェックが必要になることがある。 - - - -```cpp:pass_by_pointer.cpp -#include - -// ポインタ渡し:アドレスを受け取る -void updateByPointer(int* ptr) { - if (ptr != nullptr) { - *ptr = 200; // アドレスの指す先を書き換える - } -} - -int main() { - int num = 10; - - // アドレスを渡す - updateByPointer(&num); - - std::cout << "ポインタ渡し後: " << num << std::endl; - return 0; -} -``` - -```cpp-exec:pass_by_pointer.cpp -ポインタ渡し後: 200 -``` - -### 3\. 参照渡し (Pass by Reference) - -C++の真骨頂です。**「参照(Reference)」**とは、既存の変数に別の名前(エイリアス)をつける機能です。引数の型に `&` を付けるだけで宣言できます。 - - * **メリット:** コピーが発生しない。**構文は「値渡し」と同じように書ける**(`*`や`&`を呼び出し側で意識しなくていい)。`nullptr` になることがないため安全性が高い。 - * **デメリット:** 関数内で値を変更すると、呼び出し元も変わる(意図しない変更に注意)。 - - - -```cpp:pass_by_ref.cpp -#include - -// 参照渡し:引数に & をつける -// ref は呼び出し元の変数の「別名」となる -void updateByRef(int& ref) { - ref = 300; // 普通の変数のように扱えるが、実体は呼び出し元 -} - -int main() { - int num = 10; - - // 値渡しと同じように呼び出せる(&num と書かなくていい!) - updateByRef(num); - - std::cout << "参照渡し後: " << num << std::endl; - return 0; -} -``` - -```cpp-exec:pass_by_ref.cpp -参照渡し後: 300 -``` - -### 4\. const 参照渡し (Pass by const Reference) - -これが**C++で最も頻繁に使われるパターン**です。「コピーはしたくない(重いから)。でも、関数内で書き換えられたくもない」という要求を満たします。 - - * **構文:** `const 型& 引数名` - * **用途:** `std::string`、`std::vector`、クラスのオブジェクトなど、サイズが大きくなる可能性があるデータ。 - - - -```cpp:const_ref.cpp -#include -#include -#include - -// const参照渡し -// textの実体はコピーされないが、書き換えも禁止される -void printMessage(const std::string& text) { - // text = "Modified"; // コンパイルエラーになる - std::cout << "Message: " << text << std::endl; -} - -int main() { - std::string bigData = "This is a potentially very large string..."; - - // コピーコストゼロで渡す - printMessage(bigData); - - return 0; -} -``` - -```cpp-exec:const_ref.cpp -Message: This is a potentially very large string... -``` - -> **ガイドライン:** -> -> * `int` や `double` などの基本型 → **値渡し** でOK。 -> * 変更させたいデータ → **参照渡し** (`T&`)。 -> * 変更しないがサイズが大きいデータ(string, vectorなど) → **const参照渡し** (`const T&`)。 - -## 関数の機能拡張 - -C++には関数をより柔軟に使うための機能が備わっています。 - -### オーバーロード (Function Overloading) - -引数の**型**や**数**が異なれば、同じ名前の関数を複数定義できます。C言語では関数名はユニークである必要がありましたが、C++では「名前+引数リスト」で区別されます。 - -```cpp:overloading.cpp -#include -#include - -// int型を受け取る関数 -void print(int i) { - std::cout << "Integer: " << i << std::endl; -} - -// double型を受け取る関数(同名) -void print(double d) { - std::cout << "Double: " << d << std::endl; -} - -// 文字列を受け取る関数(同名) -void print(const std::string& s) { - std::cout << "String: " << s << std::endl; -} - -int main() { - print(42); - print(3.14); - print("Overloading"); - return 0; -} -``` - -```cpp-exec:overloading.cpp -Integer: 42 -Double: 3.14 -String: Overloading -``` - -### デフォルト引数 - -引数が省略された場合に使われるデフォルト値を設定できます。これはプロトタイプ宣言(または最初にコンパイラが見る定義)に記述します。 -※デフォルト引数は**後ろの引数から順に**設定する必要があります。 - -```cpp:default_args.cpp -#include - -// power: 指数を省略すると2乗になる -// verbose: 詳細出力を省略するとfalseになる -int power(int base, int exponent = 2, bool verbose = false) { - int result = 1; - for (int i = 0; i < exponent; ++i) { - result *= base; - } - - if (verbose) { - std::cout << base << " の " << exponent << " 乗を計算しました。" << std::endl; - } - return result; -} - -int main() { - std::cout << power(3) << std::endl; // 3^2, verbose=false - std::cout << power(3, 3) << std::endl; // 3^3, verbose=false - std::cout << power(2, 4, true) << std::endl; // 2^4, verbose=true - return 0; -} -``` - -```cpp-exec:default_args.cpp -9 -27 -2 の 4 乗を計算しました。 -16 -``` - -## この章のまとめ - - * **プロトタイプ宣言**を使うことで、関数の定義順序に依存せずに記述できる。 - * **値渡し**は安全だが、大きなオブジェクトではコピーコストがかかる。 - * **参照渡し (`&`)** は、ポインタのような効率性を持ちながら、変数のエイリアスとして直感的に扱える。 - * **`const` 参照渡し (`const T&`)** は、大きなデータを「読み取り専用」で効率的に渡すC++の定石である。 - * **オーバーロード**により、同じ名前で異なる引数を受け取る関数を作れる。 - * **デフォルト引数**で、呼び出し時の記述を省略できる。 - -## 練習問題1: 値の入れ替え(Swap) - -2つの `int` 変数を受け取り、その値を入れ替える関数 `mySwap` を作成してください。 -ポインタではなく、**参照渡し**を使用してください。 - -```cpp:practice5_1.cpp -#include - -// ここにmySwap関数を実装してください - - -// main関数 -int main() { - int a = 10; - int b = 20; - std::cout << "Before: a = " << a << ", b = " << b << std::endl; - mySwap(a, b); - std::cout << "After: a = " << a << ", b = " << b << std::endl; - return 0; -} -``` - -```cpp-exec:practice5_1.cpp -(期待される実行結果) -Before: a = 10, b = 20 -After: a = 20, b = 10 -``` - -### 問題2:ベクター統計 - -`std::vector` を受け取り、その中の「最大値」を見つけて返す関数 `findMax` を作成してください。 -ただし、以下の条件を守ってください。 - -1. ベクターはコピーされないようにしてください(**参照渡し**)。 -2. 関数内でベクターの内容が変更されないことを保証してください(**const**)。 -3. ベクターが空の場合は `0` を返すなどの処理を入れてください。 - - - -```cpp:practice5_2.cpp -#include -#include -#include // maxを使うなら便利ですが、for文でも可 - -// ここに findMax を作成 - - -int main() { - std::vector data = {10, 5, 8, 42, 3}; - std::cout << "Max: " << findMax(data) << std::endl; - return 0; -} -``` -```cpp-exec:practice5_2.cpp -Max: 42 -``` diff --git a/public/docs/cpp-6.md b/public/docs/cpp-6.md deleted file mode 100644 index 93ba385..0000000 --- a/public/docs/cpp-6.md +++ /dev/null @@ -1,293 +0,0 @@ -# 第6章: プロジェクトの分割とビルド - -これまでの章では、すべてのコードを1つの `.cpp` ファイルに記述してきました。しかし、プログラムが大規模で複雑になるにつれて、このアプローチは現実的ではなくなります。コードの可読性が低下し、少しの変更でもプログラム全体の再コンパイルが必要になり、開発効率が大きく損なわれるからです。 - -この章では、プログラムを複数のファイルに分割し、それらを効率的に管理・ビルドする方法を学びます。これは、小さなプログラムから一歩進み、本格的なソフトウェア開発を行うための重要なステップです。 - -## ヘッダファイルとソースファイル - -C++では、プログラムを**ヘッダファイル**と**ソースファイル**という2種類のファイルに分割するのが一般的です。 - - * **ヘッダファイル (`.h` または `.hpp`)**: 「宣言」を置く場所です。クラスの定義、関数のプロトタイプ宣言、定数、テンプレートなどを記述します。他のファイルに対して「何ができるか(インターフェース)」を公開する役割を持ちます。 - * **ソースファイル (`.cpp`)**: 「実装」を置く場所です。ヘッダファイルで宣言された関数の具体的な処理内容などを記述します。ヘッダファイルが公開したインターフェースを「どのように実現するか」を記述する役割を持ちます。 - -### なぜ分割するのか? 🤔 - -1. **関心の分離**: インターフェース(何ができるか)と実装(どうやるか)を分離することで、コードの見通しが良くなります。他の開発者はヘッダファイルを見るだけで、その機能の使い方がわかります。 -2. **コンパイル時間の短縮**: ソースファイルを変更した場合、再コンパイルはそのファイルだけで済みます。プロジェクト全体を再コンパイルする必要がないため、大規模なプロジェクトでは開発サイクルが劇的に速くなります。 -3. **再利用性の向上**: よく使う関数やクラスをまとめておけば、別のプロジェクトでそのファイルをインクルードするだけで簡単に再利用できます。 - -### 分割の例 - -簡単な足し算を行う関数を別のファイルに分割してみましょう。 - -まず、関数の「宣言」をヘッダファイルに記述します。 - -```cpp:math_utils.h -// 関数の宣言を記述するヘッダファイル - -// この関数が他のファイルから参照されることを示す -int add(int a, int b); -``` - -次に、この関数の「実装」をソースファイルに記述します。 - -```cpp:math_utils.cpp -// 関数の実装を記述するソースファイル - -#include "math_utils.h" // 対応するヘッダファイルをインクルード - -int add(int a, int b) { - return a + b; -} -``` - -最後に、`main`関数を含むメインのソースファイルから、この`add`関数を呼び出します。 - -```cpp:math_app.cpp -#include -#include "math_utils.h" // 自作したヘッダファイルをインクルード - -int main() { - int result = add(5, 3); - std::cout << "The result is: " << result << std::endl; - return 0; -} -``` - -```cpp-exec:math_app.cpp,math_utils.cpp -The result is: 8 -``` - -ここで注目すべき点は、`math_app.cpp`が`add`関数の具体的な実装を知らないことです。`math_utils.h`を通じて「`int`を2つ受け取って`int`を返す`add`という関数が存在する」ことだけを知り、それを利用しています。 - -## インクルードガード - -複数のファイルから同じヘッダファイルがインクルードされる状況はよくあります。例えば、`A.h`が`B.h`をインクルードし、ソースファイルが`A.h`と`B.h`の両方をインクルードするような場合です。 - -もしヘッダファイルに何の対策もしていないと、同じ内容(クラス定義や関数宣言)が複数回読み込まれ、「再定義」としてコンパイルエラーが発生してしまいます。 - -```cpp:A.h -#include "B.h" // B.hをインクルード - -// A.hの内容 -``` - -```cpp:B.h -class B { - // Bクラスの内容 -}; -``` - -```cpp:bad_include_app.cpp -#include "A.h" -#include "B.h" // B.hが二重にインクルードされる - -int main() { - [[maybe_unused]] B b; // Bクラスを使う - - return 0; -} -``` - -```cpp-exec:bad_include_app.cpp -In file included from bad_include_app.cpp:2: -B.h:1:7: error: redefinition of 'class B' - 1 | class B { - | ^ -In file included from A.h:1, - from bad_include_app.cpp:1: -B.h:1:7: note: previous definition of 'class B' - 1 | class B { - | ^ -``` - -この問題を解決するのが**インクルードガード**です。インクルードガードは、ヘッダファイルの内容が1つの翻訳単位(ソースファイル)内で一度しか読み込まれないようにするための仕組みです。 - -### 伝統的なインクルードガード - -プリプロセッサディレクティブである `#ifndef`, `#define`, `#endif` を使います。 - -```cpp -#ifndef MATH_UTILS_H // もし MATH_UTILS_H が未定義なら -#define MATH_UTILS_H // MATH_UTILS_H を定義する - -// --- ヘッダファイルの中身 --- -int add(int a, int b); -// ------------------------- - -#endif // MATH_UTILS_H -``` - - * **最初のインクルード**: `MATH_UTILS_H` は未定義なので、`#define` が実行され、中身が読み込まれます。 - * **2回目以降のインクルード**: `MATH_UTILS_H` は既に定義されているため、`#ifndef` から `#endif` までのすべてが無視されます。 - -マクロ名 (`MATH_UTILS_H`) は、ファイル名に基づいて一意になるように命名するのが慣習です。 - -### \#pragma once - -より現代的で簡潔な方法として `#pragma once` があります。多くのモダンなコンパイラがサポートしています。 - -```cpp -#pragma once - -#include - -std::string to_upper(const std::string& str); -``` - -この一行をヘッダファイルの先頭に書くだけで、コンパイラがそのファイルが一度しかインクルードされないように処理してくれます。特別な理由がない限り、現在では `#pragma once` を使うのが主流です。 - -## プロジェクトのビルド - -複数のソースファイル(`.cpp`)は、それぞれがコンパイルされて**オブジェクトファイル**(`.o` や `.obj`)になります。その後、**リンカ**がこれらのオブジェクトファイルと必要なライブラリを結合して、最終的な実行可能ファイルを生成します。 - -この一連の作業を**ビルド**と呼びます。ファイルが増えてくると、これを手動で行うのは非常に面倒です。そこで、ビルド作業を自動化する**ビルドシステム**が使われます。 - -### 手動でのビルド (g++) - -先ほどの`math_app.cpp`と`math_utils.cpp`を例に、g++コンパイラで手動ビルドする手順を見てみましょう。 - -```bash -# 1. 各ソースファイルをコンパイルしてオブジェクトファイルを生成する (-c オプション) -g++ -c math_app.cpp -o main.o -g++ -c math_utils.cpp -o math_utils.o - -# 2. オブジェクトファイルをリンクして実行可能ファイルを生成する -g++ main.o math_utils.o -o my_app - -# 3. 実行する -./my_app -``` - -または、以下のように1回のg++コマンドで複数ソースファイルのコンパイルとリンクを同時に行うこともできます。 - -```bash -g++ math_app.cpp math_utils.cpp -o my_app -./my_app -``` - -### Makefileによる自動化 - -`make`は、ファイルの依存関係と更新ルールを記述した`Makefile`というファイルに従って、ビルドプロセスを自動化するツールです。 - -以下は、非常にシンプルな`Makefile`の例です。 - -```makefile -# コンパイラを指定 -CXX = g++ -# コンパイルオプションを指定 -CXXFLAGS = -std=c++17 -Wall - -# 最終的なターゲット(実行可能ファイル名) -TARGET = my_app - -# ソースファイルとオブジェクトファイル -SRCS = math_app.cpp math_utils.cpp -OBJS = $(SRCS:.cpp=.o) - -# デフォルトのターゲット (makeコマンド実行時に最初に実行される) -all: $(TARGET) - -# 実行可能ファイルの生成ルール -$(TARGET): $(OBJS) - $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) - -# オブジェクトファイルの生成ルール (%.o: %.cpp) -# .cppファイルから.oファイルを作るための汎用ルール -%.o: %.cpp - $(CXX) $(CXXFLAGS) -c $< -o $@ - -# 中間ファイルなどを削除するルール -clean: - rm -f $(OBJS) $(TARGET) -``` - -この`Makefile`があるディレクトリで、ターミナルから`make`と入力するだけで、必要なコンパイルとリンクが自動的に実行されます。`math_app.cpp`だけを変更した場合、`make`は`main.o`だけを再生成し、再リンクするため、ビルド時間が短縮されます。 - -### CMakeによるモダンなビルド管理 - -`Makefile`は強力ですが、OSやコンパイラに依存する部分があり、複雑なプロジェクトでは管理が難しくなります。 - -**CMake**は、`Makefile`やVisual Studioのプロジェクトファイルなどを自動的に生成してくれる、クロスプラットフォーム対応のビルドシステムジェネレータです。`CMakeLists.txt`という設定ファイルに、より抽象的なビルドのルールを記述します。 - -```cmake -# CMakeの最低要求バージョン -cmake_minimum_required(VERSION 3.10) - -# プロジェクト名を設定 -project(MyAwesomeApp) - -# C++の標準バージョンを設定 -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# 実行可能ファイルを追加 -# add_executable(実行ファイル名 ソースファイル1 ソースファイル2 ...) -add_executable(my_app math_app.cpp math_utils.cpp) -``` - -この`CMakeLists.txt`を使ってビルドする一般的な手順は以下の通りです。 - -```bash -# 1. ビルド用の中間ファイルを置くディレクトリを作成し、移動する -mkdir build -cd build - -# 2. CMakeを実行して、ビルドシステム(この場合はMakefile)を生成する -cmake .. - -# 3. make (または cmake --build .) を実行してビルドする -make - -# 4. 実行する -./my_app -``` - -CMakeは、ライブラリの検索、依存関係の管理、テストの実行など、大規模プロジェクトに必要な多くの機能を備えており、現在のC++開発における標準的なツールとなっています。 - -## この章のまとめ - - * **プロジェクトの分割**: プログラムは「宣言」を記述する**ヘッダファイル** (`.h`) と、「実装」を記述する**ソースファイル** (`.cpp`) に分割することで、保守性や再利用性が向上します。 - * **インクルードガード**: ヘッダファイルの多重インクルードによる再定義エラーを防ぐために、`#pragma once` や `#ifndef`/`#define`/`#endif` を使用します。 - * **ビルドシステム**: 複数のファイルをコンパイル・リンクするプロセスを自動化するために、`make` や `CMake` といったツールが使われます。特に **CMake** はクロスプラットフォーム開発におけるデファクトスタンダードです。 - -### 練習問題1: 電卓クラスの分割 - -`Calculator` というクラスを作成してください。このクラスは、加算、減算、乗算、除算のメンバ関数を持ちます。 - -* `Calculator.h`: `Calculator`クラスの定義を記述します。 -* `Calculator.cpp`: 各メンバ関数の実装を記述します。 -* `practice6_1.cpp`: `Calculator`クラスのインスタンスを作成し、いくつかの計算を行って結果を表示します。 - -これらのファイルをg++で手動ビルドして、プログラムを実行してください。 - -```cpp:Calculator.h - -``` - -```cpp:Calculator.cpp - -``` - -```cpp:practice6_1.cpp -#include -#include "Calculator.h" - -int main() { - Calculator calc; - - std::cout << "3 + 5 = " << calc.add(3, 5) << std::endl; - std::cout << "10 - 2 = " << calc.subtract(10, 2) << std::endl; - std::cout << "4 * 7 = " << calc.multiply(4, 7) << std::endl; - std::cout << "20 / 4 = " << calc.divide(20, 4) << std::endl; - return 0; -} -``` - -```cpp-exec:practice6_1.cpp,Calculator.cpp -3 + 5 = 8 -10 - 2 = 8 -4 * 7 = 28 -20 / 4 = 5 -``` diff --git a/public/docs/cpp-7.md b/public/docs/cpp-7.md deleted file mode 100644 index 32eb09b..0000000 --- a/public/docs/cpp-7.md +++ /dev/null @@ -1,340 +0,0 @@ -# 第7章: オブジェクト指向の入口:クラスの基礎 - -これまでの章では、C++の基本的な文法やメモリの扱い方について学んできました。この章からは、C++の最も強力な機能の一つである**オブジェクト指向プログラミング (Object-Oriented Programming, OOP)** の世界に足を踏み入れます。OOPの考え方を身につけることで、より大規模で複雑なプログラムを、現実世界の「モノ」の概念に近い形で、直感的に設計・実装できるようになります。その第一歩として、OOPの中核をなす**クラス**の基礎を学びましょう。 - -## クラスとは?: データ(メンバ変数)と処理(メンバ関数)のカプセル化 - -他のプログラミング言語でオブジェクト指向に触れたことがあるなら、「クラスはオブジェクトの設計図」という説明を聞いたことがあるかもしれません。C++でもその考え方は同じです。クラスは、ある「モノ」が持つべき**データ(属性)**と、そのデータに対する**処理(操作)**を一つにまとめたものです。 - - - **データ(属性)**: クラス内に定義された変数のことで、**メンバ変数 (member variables)** または**データメンバ**と呼びます。 - - **処理(操作)**: クラス内に定義された関数のことで、**メンバ関数 (member functions)** または**メソッド**と呼びます。 - -このように、関連するデータと処理を一つのクラスにまとめることを、OOPの重要な概念の一つである**カプセル化 (encapsulation)** と呼びます。💊 - -例として、「人」を表す`Person`クラスを考えてみましょう。「人」は「名前」や「年齢」といったデータ(メンバ変数)を持ち、「自己紹介する」といった処理(メンバ関数)を行うことができます。 - -```cpp -class Person { -public: - // メンバ変数 - std::string name; - int age; - - // メンバ関数 - void introduce() { - std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; - } -}; -``` - -`class Person { ... };` という構文でクラスを定義します。クラス定義の最後にはセミコロン`;`が必要なので忘れないようにしましょう。現時点では、`public:`というキーワードは「これらのメンバは外部からアクセスできます」という意味だと考えておいてください。詳細は後ほど説明します。 - -## インスタンスの生成: クラスからオブジェクトを作ってみる - -クラスはあくまで「設計図」です。実際にプログラムで利用するためには、この設計図をもとに実体を作る必要があります。クラスから作られた実体のことを**オブジェクト (object)** または**インスタンス (instance)** と呼び、オブジェクトを作ることを**インスタンス化 (instantiation)** と言います。 - -インスタンス化の構文は、変数の宣言とよく似ています。 - -```cpp:instantiation.cpp -#include -#include - -// Personクラスの定義 -class Person { -public: - std::string name; - int age; - - void introduce() { - std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; - } -}; - -int main() { - // Personクラスのインスタンスを生成 - Person taro; - - // メンバ変数に値を代入 (ドット演算子 . を使用) - taro.name = "Taro"; - taro.age = 30; - - // メンバ関数を呼び出す - taro.introduce(); // "My name is Taro, and I am 30 years old." と出力される - - // 別のインスタンスを生成 - Person hanako; - hanako.name = "Hanako"; - hanako.age = 25; - hanako.introduce(); // "My name is Hanako, and I am 25 years old." と出力される - - return 0; -} -``` - -```cpp-exec:instantiation.cpp -My name is Taro, and I am 30 years old. -My name is Hanako, and I am 25 years old. -``` - -このように、`クラス名 インスタンス名;` という形でインスタンスを生成できます。インスタンスのメンバ変数やメンバ関数にアクセスするには、`インスタンス名.メンバ名` のように**ドット演算子 (`.`)** を使います。`taro`と`hanako`は同じ`Person`クラスから作られたインスタンスですが、それぞれが独立したデータを持っていることがわかります。 - -## アクセス制御: public と private による情報の隠蔽 - -先ほどの`Person`クラスの例では、`main`関数から`taro.age = 30;`のようにメンバ変数に直接アクセスできました。これは手軽ですが、問題を引き起こす可能性があります。例えば、年齢にマイナスの値や非現実的な値を設定できてしまうかもしれません。 - -```cpp -Person jiro; -jiro.name = "Jiro"; -jiro.age = -5; // 本来ありえない値が設定できてしまう! -jiro.introduce(); -``` - -このような意図しない操作を防ぐために、C++には**アクセス制御**の仕組みがあります。クラスのメンバは、外部からのアクセスの可否を指定できます。 - - - **`public`**: クラスの外部(`main`関数など)から自由にアクセスできます。 - - **`private`**: そのクラスのメンバ関数からしかアクセスできません。外部からはアクセス不可です。 - -アクセス制御の基本は、**メンバ変数は`private`にし、メンバ関数は`public`にする**ことです。これにより、データの不正な書き換えを防ぎ、クラスの内部実装を外部から隠蔽します。これを**情報の隠蔽 (information hiding)** と呼び、カプセル化の重要な目的の一つです。 - -`private`なメンバ変数に安全にアクセスするために、`public`なメンバ関数(**ゲッター**や**セッター**と呼ばれる)を用意するのが一般的です。 - -```cpp:access_control.cpp -#include -#include - -class Person { -private: - // メンバ変数は外部から隠蔽する - std::string name; - int age; - -public: - // セッター: メンバ変数に値を設定する - void setName(const std::string& newName) { - name = newName; - } - - void setAge(int newAge) { - if (newAge >= 0 && newAge < 150) { // 不正な値をチェック - age = newAge; - } else { - std::cout << "Error: Invalid age value." << std::endl; - } - } - - // ゲッター: メンバ変数の値を取得する - std::string getName() const { - return name; - } - - int getAge() const { - return age; - } - - // このメンバ関数はクラス内部にあるので、privateメンバにアクセスできる - void introduce() { - std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; - } -}; - -int main() { - Person saburo; - - // saburo.name = "Saburo"; // エラー! privateメンバには直接アクセスできない - // saburo.age = -10; // エラー! - - // publicなメンバ関数を経由して安全に値を設定 - saburo.setName("Saburo"); - saburo.setAge(28); - - saburo.introduce(); - - saburo.setAge(-10); // エラーメッセージが出力される - - // publicなメンバ関数経由で値を取得 - std::cout << "Name: " << saburo.getName() << std::endl; - - return 0; -} -``` - -```cpp-exec:access_control.cpp -My name is Saburo, and I am 28 years old. -Error: Invalid age value. -Name: Saburo -``` - -`setAge`関数内で値の妥当性チェックを行っている点に注目してください。このように、クラスの利用者は内部の実装を気にすることなく、提供された`public`なインターフェース(メンバ関数)を通じて安全にオブジェクトを操作できます。 - -> `const`キーワード: `getName() const` のようにメンバ関数の後ろに`const`を付けると、その関数がメンバ変数を変更しないことをコンパイラに約束します。このような関数を**constメンバ関数**と呼びます。 - -## コンストラクタとデストラクタ: オブジェクトが生まれてから消えるまで - -オブジェクトは生成され、利用され、やがて破棄されます。このライフサイクルに合わせて特別な処理を自動的に実行するための仕組みが**コンストラクタ**と**デストラクタ**です。 - -### コンストラクタ (Constructor) - -**コンストラクタ**は、インスタンスが生成されるときに**自動的に呼び出される**特別なメンバ関数です。主な役割は、メンバ変数の初期化です。 - -コンストラクタには以下の特徴があります。 - - - 関数名がクラス名と全く同じ。 - - 戻り値の型を指定しない(`void`も付けない)。 - - 引数を取ることができ、複数定義できる(オーバーロード)。 - -```cpp:constructor.cpp -class Person { -private: - std::string name; - int age; - -public: - // 引数付きコンストラクタ - Person(const std::string& initName, int initAge) { - std::cout << "Constructor called for " << initName << std::endl; - name = initName; - age = initAge; - } - // ... -}; - -int main() { - // インスタンス生成時にコンストラクタが呼ばれ、引数が渡される - Person yuko("Yuko", 22); // この時点でコンストラクタが実行される - yuko.introduce(); -} -``` - -```cpp-exec:constructor.cpp -Constructor called for Yuko -My name is Yuko, and I am 22 years old. -``` - -このように、インスタンス生成時に`()`で初期値を渡すことで、オブジェクトを生成と同時に有効な状態にできます。`set`関数を別途呼び出す手間が省け、初期化忘れを防ぐことができます。 - -### デストラクタ (Destructor) - -**デストラクタ**は、インスタンスが破棄されるとき(例えば、変数のスコープを抜けるとき)に**自動的に呼び出される**特別なメンバ関数です。主な役割は、オブジェクトが使用していたリソース(メモリやファイルなど)の後片付けです。 - -デストラクタには以下の特徴があります。 - - - 関数名が `~` + クラス名。 - - 戻り値も引数も取らない。 - - 1つのクラスに1つしか定義できない。 - -```cpp:constructor_destructor.cpp -#include -#include - -class Person { -private: - std::string name; - int age; - -public: - // コンストラクタ - Person(const std::string& initName, int initAge) { - std::cout << "Constructor called for " << initName << "." << std::endl; - name = initName; - age = initAge; - } - - // デストラクタ - ~Person() { - std::cout << "Destructor called for " << name << "." << std::endl; - } - - void introduce() { - std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; - } -}; - -void create_person_scope() { - std::cout << "--- Entering scope ---" << std::endl; - Person kenji("Kenji", 45); // kenjiはこのスコープ内でのみ生存 - kenji.introduce(); - std::cout << "--- Exiting scope ---" << std::endl; -} // ここでkenjiのスコープが終わり、デストラクタが呼ばれる - -int main() { - create_person_scope(); - - std::cout << "--- Back in main ---" << std::endl; - - return 0; -} -``` - -```cpp-exec:constructor_destructor.cpp ---- Entering scope --- -Constructor called for Kenji. -My name is Kenji, and I am 45 years old. ---- Exiting scope --- -Destructor called for Kenji. ---- Back in main --- -``` - -実行結果を見ると、`kenji`オブジェクトが生成されたときにコンストラクタが、`create_person_scope`関数のスコープを抜けるときにデストラクタが自動的に呼び出されていることがわかります。動的に確保したメモリの解放など、クリーンアップ処理はデストラクタに書くのが定石です。この考え方は、今後の章で学ぶRAII(Resource Acquisition Is Initialization)という重要な概念に繋がります。 - -## この章のまとめ - -この章では、C++におけるオブジェクト指向プログラミングの第一歩として、クラスの基本的な概念を学びました。 - - - **クラス**は、データ(**メンバ変数**)と処理(**メンバ関数**)を一つにまとめた「設計図」です。 - - クラスから実体である**オブジェクト(インスタンス)**を生成して使用します。 - - **カプセル化**は、関連するデータと処理をまとめることです。 - - **アクセス制御**(`public`, `private`)により、外部からアクセスされたくないメンバを保護します(**情報の隠蔽**)。 - - **コンストラクタ**は、オブジェクト生成時に自動で呼ばれ、初期化を行います。 - - **デストラクタ**は、オブジェクト破棄時に自動で呼ばれ、後片付けを行います。 - -クラスを使いこなすことで、プログラムの部品化が進み、再利用性やメンテナンス性が格段に向上します。次の章では、クラスのさらに進んだ機能について学んでいきましょう。 - -### 練習問題1: 長方形クラス - -幅(`width`)と高さ(`height`)をメンバ変数として持つ`Rectangle`クラスを作成してください。 - - - メンバ変数は`private`で定義してください。 - - コンストラクタで幅と高さを初期化できるようにしてください。 - - 面積を計算して返す`getArea()`メソッドと、周の長さを計算して返す`getPerimeter()`メソッドを`public`で実装してください。 - - `main`関数で`Rectangle`クラスのインスタンスをいくつか生成し、面積と周の長さを表示するプログラムを作成してください。 - -```cpp:practice7_1.cpp -#include -#include -// ここにRectangleクラスを定義してください - -int main() { - // ここでRectangleクラスのインスタンスを生成し、面積と周の長さを表示してください - - return 0; -} -``` - -```cpp-exec:practice7_1.cpp -``` - - -### 練習問題2: 書籍クラス - -タイトル(`title`)、著者(`author`)、ページ数(`pages`)をメンバ変数として持つ`Book`クラスを作成してください。 - - - メンバ変数は`private`で定義してください。 - - コンストラクタで、タイトル、著者、ページ数を初期化できるようにしてください。 - - 本の情報を整形してコンソールに出力する`printInfo()`メソッドを`public`で実装してください。(例: `Title: [タイトル], Author: [著者], Pages: [ページ数] pages`) - - `main`関数で`Book`クラスのインスタンスを生成し、その情報を表示してください。 - -```cpp:practice7_2.cpp -#include -#include -// ここにBookクラスを定義してください - -int main() { - // ここでBookクラスのインスタンスを生成し、情報を表示してください - - return 0; -} -``` - -```cpp-exec:practice7_2.cpp -Title: The Great Gatsby, Author: F. Scott Fitzgerald, Pages: 180 pages -``` diff --git a/public/docs/cpp-8.md b/public/docs/cpp-8.md deleted file mode 100644 index 3db56d3..0000000 --- a/public/docs/cpp-8.md +++ /dev/null @@ -1,503 +0,0 @@ -# 第8章: クラスを使いこなす - -第7章では、C++のオブジェクト指向プログラミングの核となる`class`の基本的な使い方を学びました。しかし、クラスを真に強力なツールとして使いこなすには、もう少し知識が必要です。この章では、オブジェクトのコピー、演算子のオーバーロード、クラスで共有されるメンバなど、より実践的でパワフルな機能について掘り下げていきます。これらの概念をマスターすることで、あなたの書くクラスはより安全で、直感的で、再利用性の高いものになるでしょう。 - -## オブジェクトのコピー: コピーコンストラクタと代入演算子 - -オブジェクトをコピーしたい場面は頻繁にあります。例えば、関数の引数にオブジェクトを渡すとき(値渡し)や、既存のオブジェクトで新しいオブジェクトを初期化するときなどです。 - -```cpp -Vector2D v1(1.0, 2.0); -Vector2D v2 = v1; // ここでコピーが発生! -``` - -多くの場合、コンパイラが自動的に生成するコピー機能で十分です。しかし、クラスがポインタなどでリソース(メモリなど)を管理している場合、単純なコピーでは問題が発生します。 - -### 何もしないとどうなる? (浅いコピー) - -まず、コピーの機能を自分で作らなかった場合に何が起きるか見てみましょう。 -コンパイラは、メンバ変数を単純にコピーするだけの「浅いコピー」を行います。 - -ここでは、`int`へのポインタを一つだけ持つ`ResourceHolder`(リソース保持者)というクラスを考えます。 - -```cpp:shallow_copy.cpp -// 悪い例:浅いコピーの問題点 -#include - -class ResourceHolder { -private: - int* m_data; // 動的に確保したデータへのポインタ -public: - ResourceHolder(int value) { - m_data = new int(value); // メモリを確保 - std::cout << "Resource " << *m_data << " created. (at " << m_data << ")" << std::endl; - } - ~ResourceHolder() { - std::cout << "Resource " << *m_data << " destroyed. (at " << m_data << ")" << std::endl; - delete m_data; // メモリを解放 - } - // コピーコンストラクタや代入演算子を定義していない! -}; - -int main() { - ResourceHolder r1(10); - ResourceHolder r2 = r1; // 浅いコピーが発生! - // r1.m_data と r2.m_data は同じアドレスを指してしまう - - // main()終了時、r1とr2のデストラクタが呼ばれる - // 同じメモリを2回deleteしようとしてクラッシュ!💥 - return 0; -} -``` -```cpp-exec:shallow_copy.cpp -Resource 10 created. (at 0x139f065e0) -Resource 10 destroyed. (at 0x139f065e0) -Resource 107521 destroyed. (at 0x1a4012b0) -free(): double free detected in tcache 2 -``` - -この例では、`r2`が作られるときに`r1`のポインタ`m_data`の値(メモリアドレス)だけがコピーされます。その結果、2つのオブジェクトが1つのメモリ領域を指してしまいます。プログラム終了時にそれぞれのデストラクタが呼ばれ、同じメモリを2回解放しようとしてエラーになります。 - -### 解決策:コピー機能を自作する (深いコピー) - -この問題を解決するために、**コピーコンストラクタ**と**コピー代入演算子**を自分で定義して、「深いコピー」を実装します。深いコピーとは、ポインタの指す先の実体(データそのもの)を新しく作ってコピーすることです。 - -```cpp:resource_holder.cpp -#include - -class ResourceHolder { -private: - int* m_data; // リソースとして動的に確保したintへのポインタ - -public: - // コンストラクタ: intを1つ動的に確保し、値を設定 - ResourceHolder(int value) { - m_data = new int(value); - std::cout << "Resource " << *m_data << " created. (at " << m_data << ")" << std::endl; - } - - // デストラクタ: 確保したメモリを解放 - ~ResourceHolder() { - if (m_data != nullptr) { - std::cout << "Resource " << *m_data << " destroyed. (at " << m_data << ")" << std::endl; - delete m_data; - } - } - - // --- ここからが本題です --- - - // 1. コピーコンストラクタ (深いコピー) - // ResourceHolder r2 = r1; のように、オブジェクトの作成と同時にコピーするときに呼ばれる - ResourceHolder(const ResourceHolder& other) { - // ① 新しいメモリを確保する - // ② otherの「値」(*other.m_data)を、新しいメモリにコピーする - m_data = new int(*other.m_data); - std::cout << "COPY CONSTRUCTOR: New resource " << *m_data << " created. (at " << m_data << ")" << std::endl; - } - - // 2. コピー代入演算子 (深いコピー) - // r3 = r1; のように、既存のオブジェクトに代入するときに呼ばれる - ResourceHolder& operator=(const ResourceHolder& other) { - std::cout << "COPY ASSIGNMENT OPERATOR called." << std::endl; - - // ① 自己代入のチェック (a = a; のような無駄な処理を防ぐ) - if (this == &other) { - return *this; // 何もせず自分自身を返す - } - - // ② 自分が元々持っていた古いリソースを解放する - delete m_data; - - // ③ 新しいリソースを確保し、相手の値をコピーする - m_data = new int(*other.m_data); - - return *this; // 自分自身を返すことで、a = b = c; のような連続代入が可能になる - } - - void print() const { - std::cout << "Value: " << *m_data << ", Address: " << m_data << std::endl; - } -}; - -int main() { - std::cout << "--- rh1の作成 ---" << std::endl; - ResourceHolder rh1(10); - - std::cout << "\n--- rh2をrh1で初期化 ---" << std::endl; - ResourceHolder rh2 = rh1; // コピーコンストラクタが呼ばれる - - std::cout << "\n--- rh3の作成 ---" << std::endl; - ResourceHolder rh3(20); - - std::cout << "\n--- rh3にrh1を代入 ---" << std::endl; - rh3 = rh1; // コピー代入演算子が呼ばれる - - std::cout << "\n--- 各オブジェクトの状態 ---" << std::endl; - std::cout << "rh1: "; rh1.print(); - std::cout << "rh2: "; rh2.print(); // rh1とは別のメモリを持っている - std::cout << "rh3: "; rh3.print(); // rh1とは別のメモリを持っている - - std::cout << "\n--- main関数終了 ---" << std::endl; - return 0; // ここでrh1, rh2, rh3のデストラクタが呼ばれ、それぞれが確保したメモリを安全に解放する -} -``` - -```cpp-exec:resource_holder.cpp ---- rh1の作成 --- -Resource 10 created. (at 0x139f065e0) - ---- rh2をrh1で初期化 --- -COPY CONSTRUCTOR: New resource 10 created. (at 0x139f06600) - ---- rh3の作成 --- -Resource 20 created. (at 0x139f06620) - ---- rh3にrh1を代入 --- -COPY ASSIGNMENT OPERATOR called. - ---- 各オブジェクトの状態 --- -rh1: Value: 10, Address: 0x139f065e0 -rh2: Value: 10, Address: 0x139f06600 -rh3: Value: 10, Address: 0x139f06640 - ---- main関数終了 --- -Resource 10 destroyed. (at 0x139f06640) -Resource 10 destroyed. (at 0x139f06600) -Resource 10 destroyed. (at 0x139f065e0) -``` - -*(メモリアドレスは実行するたびに変わります)* - -実行結果を見ると、`rh1`, `rh2`, `rh3` はそれぞれ異なるメモリアドレス (`Address`) を持っていることがわかります。これにより、各オブジェクトは独立したリソースを管理でき、プログラム終了時にそれぞれのデストラクタが安全にメモリを解放できます。 - -| 機能 | いつ呼ばれるか | 何をするか | -| :--- | :--- | :--- | -| **コピーコンストラクタ** | オブジェクトが**作られる時**に、他のオブジェクトで初期化される場合
      `ResourceHolder r2 = r1;` | 新しいリソースを確保し、元のオブジェクトの**値**をコピーする。 | -| **コピー代入演算子** | **既にあるオブジェクト**に、他のオブジェクトを代入する場合
      `r3 = r1;` | 1. 自分が持っている古いリソースを解放する。
      2. 新しいリソースを確保し、元のオブジェクトの**値**をコピーする。 | - -このように、ポインタでリソースを管理するクラスでは、安全なコピーを実現するためにこの2つの関数を自分で定義することが不可欠です。 - -## 演算子のオーバーロード - -C++では、`+`, `-`, `==`, `<<` などの組み込み演算子を、自作のクラスで使えるように**再定義(オーバーロード)**できます。これにより、クラスのインスタンスをあたかも組み込み型(`int`や`double`など)のように直感的に扱えるようになります。 - -例えば、2次元ベクトルを表す `Vector2D` クラスがあるとします。`v3 = v1 + v2;` のように、ベクトル同士の足し算を自然に記述できると便利ですよね。 - -演算子のオーバーロードは、メンバ関数または非メンバ関数(グローバル関数)として定義します。 - -| 演算子 | メンバ関数での定義 | 非メンバ関数での定義 | -| :--- | :--- | :--- | -| 二項演算子 (`+`, `==` etc.) | `T operator+(const U& rhs);` | `T operator+(const T& lhs, const U& rhs);` | -| 単項演算子 (`-`, `!` etc.) | `T operator-();` | `T operator-(const T& obj);` | - -### 実装例 - -`Vector2D` クラスで `+`(加算)、`==`(等価比較)、`<<`(ストリーム出力)をオーバーロードしてみましょう。 - -```cpp:operator_overloading.cpp -#include - -class Vector2D { -public: - double x, y; - - Vector2D(double x = 0.0, double y = 0.0) : x(x), y(y) {} - - // メンバ関数として + 演算子をオーバーロード - Vector2D operator+(const Vector2D& rhs) const { - return Vector2D(this->x + rhs.x, this->y + rhs.y); - } - - // メンバ関数として == 演算子をオーバーロード - bool operator==(const Vector2D& rhs) const { - return (this->x == rhs.x) && (this->y == rhs.y); - } -}; - -// 非メンバ関数として << 演算子をオーバーロード -// 第1引数が std::ostream& なので、メンバ関数にはできない -std::ostream& operator<<(std::ostream& os, const Vector2D& v) { - os << "(" << v.x << ", " << v.y << ")"; - return os; -} - -int main() { - Vector2D v1(1.0, 2.0); - Vector2D v2(3.0, 4.0); - - // operator+ が呼ばれる - Vector2D v3 = v1 + v2; - std::cout << "v1: " << v1 << std::endl; // operator<< - std::cout << "v2: " << v2 << std::endl; // operator<< - std::cout << "v3 = v1 + v2: " << v3 << std::endl; // operator<< - - // operator== が呼ばれる - if (v1 == Vector2D(1.0, 2.0)) { - std::cout << "v1 is equal to (1.0, 2.0)" << std::endl; - } - - return 0; -} -``` - -```cpp-exec:operator_overloading.cpp -v1: (1, 2) -v2: (3, 4) -v3 = v1 + v2: (4, 6) -v1 is equal to (1.0, 2.0) -``` - -`operator<<` は、左辺のオペランドが `std::ostream` 型(`std::cout` など)であるため、`Vector2D` のメンバ関数としては定義できません。そのため、非メンバ関数として定義するのが一般的です。 - -## staticメンバ - -通常、クラスのメンバ変数はオブジェクトごとに個別のメモリ領域を持ちます。しかし、あるクラスの**全てのオブジェクトで共有したい**情報もあります。例えば、「これまでに生成されたオブジェクトの総数」などです。このような場合、**staticメンバ**を使用します。 - -### staticメンバ変数 - -`static` キーワードを付けて宣言されたメンバ変数は、特定のオブジェクトに属さず、クラスそのものに属します。そのため、全オブジェクトでただ1つの実体を共有します。これを**クラス変数**と呼ぶこともあります。 - - * **宣言**: クラス定義の中で `static` を付けて行います。 - * **定義**: クラス定義の外(ソースファイル)で、メモリ上の実体を確保し、初期化します。 - -### staticメンバ関数 - -`static` キーワードを付けて宣言されたメンバ関数は、特定のオブジェクトに依存せずに呼び出せます。そのため、`this` ポインタ(後述)を持ちません。 - - * **アクセス**: staticメンバ変数や他のstaticメンバ関数にはアクセスできますが、非staticなメンバ(インスタンスごとのメンバ変数やメンバ関数)にはアクセスできません。 - * **呼び出し**: `クラス名::関数名()` のように、オブジェクトを生成しなくても呼び出せます。 - -### 実装例 - -ゲームに登場する `Player` クラスがあり、現在何人のプレイヤーが存在するかを管理する例を見てみましょう。 - -```cpp:static_members.cpp -#include -#include - -class Player { -private: - std::string name; - // (1) staticメンバ変数の宣言 - static int playerCount; - -public: - Player(const std::string& name) : name(name) { - playerCount++; // オブジェクトが生成されるたびにインクリメント - std::cout << name << " がゲームに参加しました。現在のプレイヤー数: " << playerCount << std::endl; - } - - ~Player() { - playerCount--; // オブジェクトが破棄されるたびにデクリメント - std::cout << name << " がゲームから退出しました。現在のプレイヤー数: " << playerCount << std::endl; - } - - // (2) staticメンバ関数の宣言 - static int getPlayerCount() { - // name などの非staticメンバにはアクセスできない - return playerCount; - } -}; - -// (3) staticメンバ変数の定義と初期化 -int Player::playerCount = 0; - -int main() { - // オブジェクトがなくてもstaticメンバ関数を呼び出せる - std::cout << "ゲーム開始時のプレイヤー数: " << Player::getPlayerCount() << std::endl; - std::cout << "---" << std::endl; - - Player p1("Alice"); - Player p2("Bob"); - - { - Player p3("Charlie"); - std::cout << "現在のプレイヤー数 (p1経由): " << p1.getPlayerCount() << std::endl; - } // p3のスコープが終わり、デストラクタが呼ばれる - - std::cout << "---" << std::endl; - std::cout << "ゲーム終了時のプレイヤー数: " << Player::getPlayerCount() << std::endl; - - return 0; -} -``` - -```cpp-exec:static_members.cpp -ゲーム開始時のプレイヤー数: 0 ---- -Alice がゲームに参加しました。現在のプレイヤー数: 1 -Bob がゲームに参加しました。現在のプレイヤー数: 2 -Charlie がゲームに参加しました。現在のプレイヤー数: 3 -現在のプレイヤー数 (p1経由): 3 -Charlie がゲームから退出しました。現在のプレイヤー数: 2 ---- -ゲーム終了時のプレイヤー数: 2 -Alice がゲームから退出しました。現在のプレイヤー数: 1 -Bob がゲームから退出しました。現在のプレイヤー数: 0 -``` - -`playerCount` は `p1`, `p2`, `p3` の全てで共有されており、一つの値が更新されていることがわかります。 - -## thisポインタ - -非staticなメンバ関数が呼び出されるとき、その関数は「どのオブジェクトに対して呼び出されたか」を知る必要があります。コンパイラは、そのメンバ関数に対して、呼び出し元のオブジェクトのアドレスを暗黙的に渡します。このアドレスを保持するのが `this` ポインタです。 - -`this` は、メンバ関数内で使用できるキーワードで、自分自身のオブジェクトを指すポインタです。 - -`this` ポインタが主に使われるのは、以下のような場面です。 - -1. **メンバ変数と引数の名前が同じ場合** - コンストラクタの初期化子リストを使わない場合など、引数名とメンバ変数名が同じになることがあります。その際、`this->` を付けることでメンバ変数であることを明示できます。 - - ```cpp - void setX(double x) { - this->x = x; // this->x はメンバ変数, x は引数 - } - ``` - -2. **自分自身の参照やポインタを返す場合** - コピー代入演算子で `return *this;` としたように、オブジェクト自身を返したい場合に使います。これにより、**メソッドチェーン**(`obj.setX(10).setY(20);` のような連続したメソッド呼び出し)が可能になります。 - -### 実装例 - -メソッドチェーンを実現する簡単な例を見てみましょう。 - -```cpp:this_pointer.cpp -#include - -class Point { -private: - int x, y; - -public: - Point(int x = 0, int y = 0) : x(x), y(y) {} - - // 自身の参照を返すことで、メソッドチェーンを可能にする - Point& setX(int newX) { - this->x = newX; - return *this; // 自分自身の参照を返す - } - - Point& setY(int newY) { - this->y = newY; - return *this; // 自分自身の参照を返す - } - - void print() const { - std::cout << "(" << this->x << ", " << this->y << ")" << std::endl; - } -}; - -int main() { - Point p; - - // メソッドチェーン - p.setX(10).setY(20); - - p.print(); - - return 0; -} -``` - -```cpp-exec:this_pointer.cpp -(10, 20) -``` - -`setX` が `p` 自身の参照を返すため、その返り値に対して続けて `.setY(20)` を呼び出すことができます。 - -## この章のまとめ - -この章では、クラスをより効果的に利用するための応用的な機能を学びました。 - - * **オブジェクトのコピー**: ポインタなどリソースを管理するクラスでは、**コピーコンストラクタ**と**コピー代入演算子**を定義し、**深いコピー**を実装することが重要です。これにより、リソースの二重解放などの問題を未然に防ぎます。 - * **演算子のオーバーロード**: `+` や `==` などの演算子を自作クラスに対して定義することで、コードの可読性を高め、直感的な操作を可能にします。 - * **staticメンバ**: `static`メンバ変数やメンバ関数は、クラスの全オブジェクトで共有されるデータや機能を提供します。オブジェクトを生成しなくてもアクセスできるのが特徴です。 - * **thisポインタ**: 非staticメンバ関数内で、呼び出し元のオブジェクト自身を指すポインタです。メンバ変数と引数の区別や、メソッドチェーンの実装に役立ちます。 - -これらの機能を組み合わせることで、C++のクラスは単なるデータの入れ物から、振る舞いを伴った洗練された部品へと進化します。 - -### 練習問題1: 複素数クラス - -実部 (real) と虚部 (imaginary) を`double`型で持つ複素数クラス `Complex` を作成してください。以下の要件を満たすものとします。 - -1. コンストラクタで実部と虚部を初期化できるようにする。 -2. 複素数同士の足し算 (`+`) と掛け算 (`*`) を演算子オーバーロードで実装する。 - * 加算: $(a+bi) + (c+di) = (a+c) + (b+d)i$ - * 乗算: $(a+bi) \* (c+di) = (ac-bd) + (ad+bc)i$ -3. `std::cout` で `(a + bi)` という形式で出力できるように、`<<` 演算子をオーバーロードする。(虚部が負の場合は `(a - bi)` のように表示されるとより良い) - -```cpp:practice8_1.cpp -#include - -// ここに Complex クラスを実装してください - -int main() { - Complex c1(1.0, 2.0); // 1 + 2i - Complex c2(3.0, 4.0); // 3 + 4i - Complex sum = c1 + c2; - Complex product = c1 * c2; - - std::cout << "c1: " << c1 << std::endl; - std::cout << "c2: " << c2 << std::endl; - std::cout << "c1 + c2 = " << sum << std::endl; - std::cout << "c1 * c2 = " << product << std::endl; - return 0; -} -``` - -```cpp-exec:practice8_1.cpp -c1: (1 + 2i) -c2: (3 + 4i) -c1 + c2 = (4 + 6i) -c1 * c2 = (-5 + 10i) -``` - -### 練習問題2: 動的配列クラスのコピー制御 - -整数 (`int`) の動的配列を管理するクラス `IntArray` を作成してください。このクラスは、コンストラクタで指定されたサイズの配列を `new` で確保し、デストラクタで `delete[]` を使って解放します。 - -この `IntArray` クラスに対して、**深いコピー**を正しく行うための**コピーコンストラクタ**と**コピー代入演算子**を実装してください。 - -```cpp:practice8_2.cpp -#include - -// ここに IntArray クラスを実装してください - -int main() { - IntArray arr1(5); // サイズ5の配列を作成 - for (int i = 0; i < 5; ++i) { - arr1.set(i, i * 10); // 0, 10, 20, 30, 40 - } - - IntArray arr2 = arr1; // コピーコンストラクタ - IntArray arr3(3); - arr3 = arr1; // コピー代入演算子 - - std::cout << "arr1: "; - for (int i = 0; i < 5; ++i) { - std::cout << arr1.get(i) << " "; - } - std::cout << std::endl; - - std::cout << "arr2 (コピー): "; - for (int i = 0; i < 5; ++i) { - std::cout << arr2.get(i) << " "; - } - std::cout << std::endl; - - std::cout << "arr3 (代入): "; - for (int i = 0; i < 5; ++i) { - std::cout << arr3.get(i) << " "; - } - std::cout << std::endl; - - return 0; -} -``` - -```cpp-exec:practice8_2.cpp -arr1: 0 10 20 30 40 -arr2 (コピー): 0 10 20 30 40 -arr3 (代入): 0 10 20 30 40 -``` diff --git a/public/docs/cpp-9.md b/public/docs/cpp-9.md deleted file mode 100644 index 25ce4d6..0000000 --- a/public/docs/cpp-9.md +++ /dev/null @@ -1,281 +0,0 @@ -# 第9章: 継承とポリモーフィズム - -オブジェクト指向プログラミング(OOP)の真の力を解放する時が来ました!💪 この章では、OOPの強力な柱である「**継承 (Inheritance)**」と「**ポリモーフィズム (Polymorphism) / 多態性**」を学びます。これらの概念をマスターすることで、コードの再利用性を高め、柔軟で拡張性の高いプログラムを設計できるようになります。 - -## クラスの継承 - -**継承**とは、既存のクラス(**親クラス**または**基底クラス**と呼びます)の機能を引き継いで、新しいクラス(**子クラス**または**派生クラス**と呼びます)を作成する仕組みです。これにより、共通の機能を何度も書く必要がなくなり、コードの重複を避けられます。 - -例えば、「動物」という大まかなクラスがあり、その特徴を引き継いで「犬」や「猫」といった具体的なクラスを作ることができます。「犬」も「猫」も「動物」が持つ「食べる」という共通の機能を持っていますよね。 - -C++では、クラス名の後に `: public 親クラス名` と書くことで継承を表現します。 - -```cpp:inheritance_basic.cpp -#include -#include - -// 親クラス (基底クラス) -class Animal { -public: - std::string name; - - void eat() { - std::cout << name << " is eating." << std::endl; - } -}; - -// 子クラス (派生クラス) -// Animalクラスのpublicメンバを引き継ぐ -class Dog : public Animal { -public: - void bark() { - std::cout << name << " says Woof!" << std::endl; - } -}; - -int main() { - Dog my_dog; - my_dog.name = "Pochi"; - - // 親クラスから継承したメンバ変数・メンバ関数 - my_dog.eat(); - - // Dogクラス独自のメンバ関数 - my_dog.bark(); - - return 0; -} -``` - -```cpp-exec:inheritance_basic.cpp -Pochi is eating. -Pochi says Woof! -``` - -この例では、`Dog`クラスは`Animal`クラスを継承しています。そのため、`Dog`クラスのオブジェクト `my_dog` は、`Animal`クラスで定義されたメンバ変数 `name` やメンバ関数 `eat()` を、まるで自分のものであるかのように利用できます。 - -## 仮想関数 (virtual) とポリモーフィズム - -継承の最も強力な側面は、**ポリモーフィズム(多態性)**を実現できることです。ポリモーフィズムとは、ギリシャ語で「多くの形を持つ」という意味で、プログラミングにおいては「**同じインターフェース(指示)で、オブジェクトの種類に応じて異なる振る舞いをさせる**」ことを指します。 - -これを実現するのが **仮想関数 (virtual function)** です。親クラスの関数宣言の前に `virtual` キーワードを付けると、その関数は仮想関数になります。 - -親クラスのポインタや参照は、子クラスのオブジェクトを指すことができます。このとき、呼び出された仮想関数は、ポインタが指している**オブジェクトの実際の型**に基づいて決定されます。 - -言葉だけでは難しいので、コードで見てみましょう。 - -```cpp:polymorphism_example.cpp -#include -#include - -class Animal { -public: - // speak() を仮想関数として宣言 - virtual void speak() { - std::cout << "Some generic animal sound..." << std::endl; - } -}; - -class Dog : public Animal { -public: - // 親クラスの仮想関数を上書き (オーバーライド) - void speak() override { // overrideキーワードについては後述 - std::cout << "Woof!" << std::endl; - } -}; - -class Cat : public Animal { -public: - // 親クラスの仮想関数を上書き (オーバーライド) - void speak() override { - std::cout << "Meow!" << std::endl; - } -}; - -// Animalへのポインタを受け取る関数 -void make_animal_speak(Animal* animal) { - animal->speak(); // ポインタが指す先の実際のオブジェクトに応じて、適切な speak() が呼ばれる -} - -int main() { - Animal generic_animal; - Dog dog; - Cat cat; - - std::cout << "Calling through function:" << std::endl; - make_animal_speak(&generic_animal); - make_animal_speak(&dog); // Dogオブジェクトを渡す - make_animal_speak(&cat); // Catオブジェクトを渡す - - return 0; -} -``` - -```cpp-exec:polymorphism_example.cpp -Calling through function: -Some generic animal sound... -Woof! -Meow! -``` - -`make_animal_speak` 関数は `Animal*` 型の引数を取りますが、`Dog`オブジェクトや`Cat`オブジェクトのアドレスを渡すことができています。そして、`animal->speak()` を呼び出すと、`animal` ポインタが実際に指しているオブジェクトの `speak()` が実行されます。これがポリモーフィズムです。もし `Animal`クラスの `speak()` に `virtual` が付いていなければ、どのオブジェクトを渡しても `Animal` の `speak()` が呼ばれてしまいます。 - -## オーバーライド (override) - -先ほどの例で `override` というキーワードが登場しましたね。これはC++11から導入されたもので、子クラスの関数が**親クラスの仮想関数を上書き(オーバーライド)する意図があることを明示する**ためのものです。 - -`override` を付けておくと、もし親クラスに対応する仮想関数が存在しない場合(例えば、関数名をタイプミスした場合など)に、コンパイラがエラーを検出してくれます。 - -```cpp -class Dog : public Animal { -public: - // もし親クラスのspeakがvirtualでなかったり、 - // speek() のようにタイプミスしたりすると、コンパイルエラーになる。 - void speak() override { - std::cout << "Woof!" << std::endl; - } -}; -``` - -意図しないバグを防ぐために、仮想関数をオーバーライドする際は必ず `override` を付ける習慣をつけましょう。 - -## 抽象クラス - -時には、「具体的な実装を持たず、子クラスに実装を強制するための設計図」としてのみ機能するクラスを定義したい場合があります。これが**抽象クラス (Abstract Class)** です。 - -抽象クラスは、**純粋仮想関数 (pure virtual function)** を1つ以上持つクラスです。純粋仮想関数は、末尾に `= 0` を付けて宣言します。 - -```cpp -virtual void function_name() = 0; // これが純粋仮想関数 -``` - -抽象クラスは以下の特徴を持ちます。 - - * インスタンス化(オブジェクトの作成)ができない。 - * 抽象クラスを継承した子クラスは、全ての純粋仮想関数をオーバーライド(実装)しなければならない。さもなければ、その子クラスもまた抽象クラスとなる。 - -```cpp:abstract_class_example.cpp -#include - -// Shapeは純粋仮想関数 draw() を持つため、抽象クラスとなる -class Shape { -public: - // 純粋仮想関数 - // このクラスを継承するクラスは、必ず draw() を実装しなければならない - virtual void draw() = 0; - - // 仮想デストラクタ (継承を扱う際は重要。詳しくは今後の章で) - virtual ~Shape() {} -}; - -class Circle : public Shape { -public: - void draw() override { - std::cout << "Drawing a circle: ○" << std::endl; - } -}; - -class Square : public Shape { -public: - void draw() override { - std::cout << "Drawing a square: □" << std::endl; - } -}; - -int main() { - // Shape my_shape; // エラー!抽象クラスはインスタンス化できない - - Circle circle; - Square square; - - Shape* shape1 = &circle; - Shape* shape2 = □ - - shape1->draw(); - shape2->draw(); - - return 0; -} -``` - -```cpp-exec:abstract_class_example.cpp -Drawing a circle: ○ -Drawing a square: □ -``` - -`Shape` クラスは「図形なら描画できるはずだ」というインターフェース(契約)を定義し、具体的な描画方法は子クラスである `Circle` や `Square` に任せています。このように、抽象クラスはプログラムの骨格となる設計を強制するのに非常に役立ちます。 - -## この章のまとめ - - * **継承**: 既存のクラスの機能を引き継ぎ、コードの再利用性を高める仕組みです。`(子クラス) : public (親クラス)` のように書きます。 - * **ポリモーフィズム**: 「同じ指示でも、オブジェクトの種類によって異なる振る舞いをさせる」性質です。 - * **仮想関数 (`virtual`)**: ポリモーフィズムを実現するための鍵です。親クラスの関数に `virtual` を付けると、ポインタや参照経由で呼び出した際に、オブジェクトの実際の型に応じた関数が実行されます。 - * **オーバーライド (`override`)**: 子クラスで親クラスの仮想関数を上書きする意図を明示します。コンパイラがチェックしてくれるため、安全性が向上します。 - * **抽象クラス**: 1つ以上の**純粋仮想関数 (`virtual ... = 0;`)** を持つクラスです。インスタンス化できず、継承されるための設計図として機能します。 - -### 練習問題1:乗り物の階層構造 - -`Vehicle` という親クラスを作成し、`move()` というメンバ関数を持たせましょう。次に、`Vehicle` を継承して `Car` クラスと `Motorcycle` クラスを作成し、それぞれが独自の `move()` の振る舞いをするようにオーバーライドしてください。 - -`main` 関数では、`Vehicle` のポインタの配列を作成し、`Car` と `Motorcycle` のオブジェクトを格納して、ループでそれぞれの `move()` を呼び出してください。 - -```cpp:practice9_1.cpp -#include -#include - - -// ここに Vehicle, Car, Motorcycle クラスを定義してください - - -int main() { - // Vehicleのポインタの配列を作成 - Vehicle* vehicles[2]; - - Car my_car; - Motorcycle my_motorcycle; - - vehicles[0] = &my_car; - vehicles[1] = &my_motorcycle; - - // それぞれのmove()を呼び出す - for (int i = 0; i < 2; ++i) { - vehicles[i]->move(); - } - - return 0; -} -``` - -```cpp-exec:practice9_1.cpp -``` - -### 問題2: 従業員の給与計算 - -`Employee` という抽象クラスを定義してください。このクラスは、従業員の名前を保持し、給与を計算するための純粋仮想関数 `calculate_salary()` を持ちます。 - -次に、`Employee` を継承して、`FullTimeEmployee`(月給制)と `PartTimeEmployee`(時給制)の2つのクラスを作成します。それぞれのクラスで `calculate_salary()` を具体的に実装してください。 - -`main` 関数で、それぞれのクラスのインスタンスを作成し、給与が正しく計算されることを確認してください。 - -```cpp:practice9_2.cpp -#include -#include - -// ここに Employee, FullTimeEmployee, PartTimeEmployee クラスを定義してください - - -int main() { - FullTimeEmployee full_time_emp("Alice", 3000); // 月給3000ドル - PartTimeEmployee part_time_emp("Bob", 20, 80); // 時給20ドル、80時間勤務 - - std::cout << full_time_emp.get_name() << "'s Salary: $" << full_time_emp.calculate_salary() << std::endl; - std::cout << part_time_emp.get_name() << "'s Salary: $" << part_time_emp.calculate_salary() << std::endl; - - return 0; -} -``` - -```cpp-exec:practice9_2.cpp -Alice's Salary: $3000 -Bob's Salary: $1600 -``` diff --git a/public/docs/cpp/0-intro/-intro.md b/public/docs/cpp/0-intro/-intro.md new file mode 100644 index 0000000..e2f1a1a --- /dev/null +++ b/public/docs/cpp/0-intro/-intro.md @@ -0,0 +1 @@ +C++プログラミングの全体像を掴む章へようこそ! 🎉 この章では、あなたがこれまでに培ってきたプログラミングの知識を土台に、C++ならではの特徴や文化に触れていきます。他の言語との違いを意識しながら、まずは「**ソースコードを書き、コンパイルして実行する**」というC++の基本的な開発フローを体験しましょう。 diff --git a/public/docs/cpp/0-intro/1-0-about.md b/public/docs/cpp/0-intro/1-0-about.md new file mode 100644 index 0000000..aac98ff --- /dev/null +++ b/public/docs/cpp/0-intro/1-0-about.md @@ -0,0 +1,9 @@ +--- +id: cpp-intro-about +title: C++とは? +level: 2 +--- + +## C++とは? + +C++は、C言語を拡張して作られた、非常にパワフルで汎用性の高いプログラミング言語です。その歴史は古く、1983年にBjarne Stroustrupによって開発が始まりました。 diff --git a/public/docs/cpp/0-intro/1-1-feature.md b/public/docs/cpp/0-intro/1-1-feature.md new file mode 100644 index 0000000..8db707a --- /dev/null +++ b/public/docs/cpp/0-intro/1-1-feature.md @@ -0,0 +1,14 @@ +--- +id: cpp-intro-feature +title: 特徴 +level: 3 +--- + +### 特徴 + +他の言語と比較したC++の特徴は以下の通りです。 + + * **パフォーマンス 🚀:** C++は、OSやハードウェアに近い低レベルな操作が可能で、実行速度が非常に高速です。このため、パフォーマンスが最重要視される場面で絶大な信頼を得ています。 + * **静的型付け:** 変数の型はコンパイル時に決定されます。これにより、実行前に多くのエラーを発見でき、大規模な開発でもコードの安全性を保ちやすくなります。 + * **マルチパラダイム:** 手続き型プログラミング、オブジェクト指向プログラミング、ジェネリックプログラミングなど、様々なプログラミングスタイルをサポートしています。これにより、問題の性質に合わせて最適なアプローチを選択できます。 + * **C言語との互換性:** C言語のコードの多くは、ほとんどそのままC++のコードとしてコンパイルできます。C言語で書かれた膨大なソフトウェア資産を活用できるのは大きな利点です。 diff --git a/public/docs/cpp/0-intro/1-2-usage.md b/public/docs/cpp/0-intro/1-2-usage.md new file mode 100644 index 0000000..d9e1e96 --- /dev/null +++ b/public/docs/cpp/0-intro/1-2-usage.md @@ -0,0 +1,14 @@ +--- +id: cpp-intro-usage +title: C++が使われる分野 +level: 3 +--- + +### C++が使われる分野 + +その高いパフォーマンスと柔軟性から、C++は以下のような幅広い分野の第一線で活躍しています。 + + * **ゲーム開発:** Unreal EngineやUnity(一部)など、多くの有名ゲームエンジンがC++で開発されています。キャラクターの物理演算やリアルタイムグラフィックス描画など、速度が求められる処理に不可欠です。 + * **OS開発:** Windows, macOS, Linuxといった主要なオペレーティングシステムのカーネルやシステムコンポーネントの多くがC++で記述されています。 + * **金融システム:** 1ミリ秒を争う高頻度取引(HFT)システムなど、超低遅延が求められる金融アプリケーションで使用されています。 + * **組み込みシステム・IoT:** 自動車のエンジン制御ユニット(ECU)や家電、産業用ロボットなど、リソースが限られた環境でも高速に動作する必要があります。 diff --git a/public/docs/cpp/0-intro/2-0-env-about.md b/public/docs/cpp/0-intro/2-0-env-about.md new file mode 100644 index 0000000..a3904f2 --- /dev/null +++ b/public/docs/cpp/0-intro/2-0-env-about.md @@ -0,0 +1,11 @@ +--- +id: cpp-intro-env-about +title: 開発環境のセットアップ +level: 2 +--- + +## 開発環境のセットアップ + +C++プログラムを実行するには、**コンパイラ**が必要です。コンパイラは、人間が書いたC++のソースコードを、コンピュータが理解できる機械語に翻訳するツールです。 + +このウェブサイト上ではブラウザ上でコードを実行できる環境を埋め込んでおり、特別なセットアップは不要です。しかし、ローカル環境でC++を学びたい場合は、以下のようなコンパイラとIDE(統合開発環境)をインストールすることをお勧めします。 diff --git a/public/docs/cpp/0-intro/2-1-ide.md b/public/docs/cpp/0-intro/2-1-ide.md new file mode 100644 index 0000000..88423c7 --- /dev/null +++ b/public/docs/cpp/0-intro/2-1-ide.md @@ -0,0 +1,15 @@ +--- +id: cpp-intro-ide +title: コンパイラとIDE +level: 3 +--- + +### コンパイラとIDE + +* **コンパイラ:** + * **GCC (GNU Compiler Collection):** Linuxで標準的に使われるコンパイラ。Windows (MinGW/MSYS2) やmacOSでも利用可能です。 + * **Clang:** AppleのXcodeで標準的に使われているコンパイラ。エラーメッセージが分かりやすいと評判です。 + * **MSVC (Microsoft Visual C++):** MicrosoftのVisual Studioに付属するコンパイラ。Windows開発の標準です。 +* **IDE (統合開発環境) / エディタ:** + * **Visual Studio (Windows):** コンパイラ、エディタ、デバッガなど、C++開発に必要な全てが詰まった強力なIDEです。初心者には特におすすめです。 + * **Visual Studio Code (Windows/macOS/Linux):** 軽量なエディタですが、拡張機能を入れることで強力なC++開発環境を構築できます。 diff --git a/public/docs/cpp/0-intro/2-2-setup.md b/public/docs/cpp/0-intro/2-2-setup.md new file mode 100644 index 0000000..f3441c7 --- /dev/null +++ b/public/docs/cpp/0-intro/2-2-setup.md @@ -0,0 +1,11 @@ +--- +id: cpp-intro-setup +title: おすすめのセットアップ +level: 3 +--- + +### おすすめのセットアップ + + * **Windows:** **Visual Studio Community** をインストールするのが最も簡単です。インストーラーで「C++によるデスクトップ開発」ワークロードを選択すれば、必要なものがすべて揃います。 + * **macOS:** ターミナルで `xcode-select --install` を実行し、**Xcode Command Line Tools** をインストールします。これにはClangコンパイラが含まれます。エディタは **Visual Studio Code** がおすすめです。 + * **Linux (Ubuntu/Debian系):** ターミナルで `sudo apt update && sudo apt install build-essential g++` を実行してGCCコンパイラをインストールします。エディタは **Visual Studio Code** がおすすめです。 diff --git a/public/docs/cpp/0-intro/3-0-helloworld.md b/public/docs/cpp/0-intro/3-0-helloworld.md new file mode 100644 index 0000000..1e03ec8 --- /dev/null +++ b/public/docs/cpp/0-intro/3-0-helloworld.md @@ -0,0 +1,22 @@ +--- +id: cpp-intro-helloworld +title: 最初のプログラム +level: 2 +--- + +## 最初のプログラム + +環境が整ったら、さっそく定番の "Hello, World\!" プログラムを作成し、C++開発の流れを掴みましょう。 + +`main.cpp` という名前でファイルを作成し、以下のコードを記述してください。 + +```cpp:main.cpp +// 画面に "Hello, World!" と表示するプログラム + +#include + +int main() { + std::cout << "Hello, World!" << std::endl; + return 0; +} +``` diff --git a/public/docs/cpp/0-intro/3-1-run.md b/public/docs/cpp/0-intro/3-1-run.md new file mode 100644 index 0000000..d0d0019 --- /dev/null +++ b/public/docs/cpp/0-intro/3-1-run.md @@ -0,0 +1,35 @@ +--- +id: cpp-intro-run +title: コンパイルと実行 +level: 3 +--- + +### コンパイルと実行 + +このコードを実行するには、まず**コンパイル**して実行可能ファイルを生成する必要があります。ターミナル(Windowsの場合はコマンドプロンプトやPowerShell)で以下のコマンドを実行します。 + +```bash +# g++ (GCC) または clang++ (Clang) を使ってコンパイル +# -o main は出力ファイル名を main にするという意味 +g++ main.cpp -o main + +# 生成された実行可能ファイルを実行 +./main +``` + +```powershell +# Visual C++ (MSVC) を使う場合 +cl main.cpp /Fe:main.exe + +# 生成された実行可能ファイルを実行 +main.exe +``` + +このウェブサイト上の実行環境で動かす場合は、以下の実行ボタンをクリックしてください。 +実行すると、ターミナルに以下のように表示されるはずです。 + +```cpp-exec:main.cpp +Hello, World! +``` + +PythonやJavaScriptのようなインタプリタ言語とは異なり、C++では「コンパイル」という一手間が必要です。このステップにより、実行前にコード全体がチェックされ、高速なネイティブコードが生成されます。 diff --git a/public/docs/cpp/0-intro/4-0-basic.md b/public/docs/cpp/0-intro/4-0-basic.md new file mode 100644 index 0000000..aa5e412 --- /dev/null +++ b/public/docs/cpp/0-intro/4-0-basic.md @@ -0,0 +1,9 @@ +--- +id: cpp-intro-basic +title: C++の基本構造 +level: 2 +--- + +## C++の基本構造 + +先ほどの "Hello, World\!" プログラムには、C++の基本的な要素が詰まっています。一つずつ見ていきましょう。 diff --git a/public/docs/cpp/0-intro/4-1-include.md b/public/docs/cpp/0-intro/4-1-include.md new file mode 100644 index 0000000..3cd871d --- /dev/null +++ b/public/docs/cpp/0-intro/4-1-include.md @@ -0,0 +1,12 @@ +--- +id: cpp-intro-include +title: '#include - ヘッダファイルのインクルード' +level: 3 +--- + +### `#include ` - ヘッダファイルのインクルード + +`#include` は、他のファイルに書かれた機能を利用するための**プリプロセッサ命令**です。ここでは、コンソールへの入出力機能を提供する `iostream` という標準ライブラリのヘッダファイルを読み込んでいます。これにより、`std::cout` などが使えるようになります。 + + * `< >` で囲む: 標準ライブラリのヘッダファイルをインクルードする場合 + * `" "` で囲む: 自分で作成したヘッダファイルをインクルードする場合 (後の章で学びます) diff --git a/public/docs/cpp/0-intro/4-2-main.md b/public/docs/cpp/0-intro/4-2-main.md new file mode 100644 index 0000000..6d16ccf --- /dev/null +++ b/public/docs/cpp/0-intro/4-2-main.md @@ -0,0 +1,12 @@ +--- +id: cpp-intro-main +title: int main() - main関数 +level: 3 +--- + +### `int main()` - main関数 + +`main`関数は、C++プログラムのエントリーポイント(**実行開始点**)です。OSはプログラムを実行するとき、まずこの`main`関数を呼び出します。 + + * `int`: `main`関数が整数 (integer) の値を返すことを示します。この戻り値はプログラムの終了ステータスとしてOSに渡され、`0` は**正常終了**を意味するのが慣例です。 + * `()`: 関数が引数を取らないことを示しています。(引数を取ることも可能です) diff --git a/public/docs/cpp/0-intro/4-3-namespace.md b/public/docs/cpp/0-intro/4-3-namespace.md new file mode 100644 index 0000000..62ee532 --- /dev/null +++ b/public/docs/cpp/0-intro/4-3-namespace.md @@ -0,0 +1,12 @@ +--- +id: cpp-intro-namespace +title: 'std::cout と std::endl - 名前空間 (namespace)' +level: 3 +--- + +### `std::cout` と `std::endl` - 名前空間 (namespace) + + * `std::cout`: "character output stream" の略で、コンソールへの標準出力を担当します。 + * `<<`: **ストリーム挿入演算子**と呼ばれ、右側のデータを左側のストリーム(ここでは `std::cout`)に流し込むイメージです。 + * `std::endl`: "end line" の略で、改行を出力し、出力バッファをフラッシュ(強制的に出力)します。 + * `std::`: `cout` や `endl` が `std` という**名前空間 (namespace)** に属していることを示します。名前空間は、大規模なプログラムで関数や変数の名前が衝突するのを防ぐための仕組みです。`std` はC++の標準ライブラリが定義されている名前空間です。 diff --git a/public/docs/cpp/0-intro/5-0-summary.md b/public/docs/cpp/0-intro/5-0-summary.md new file mode 100644 index 0000000..7ba8fb2 --- /dev/null +++ b/public/docs/cpp/0-intro/5-0-summary.md @@ -0,0 +1,14 @@ +--- +id: cpp-intro-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * C++は、**パフォーマンス**と**多機能性**を両立した強力なプログラミング言語です。 + * C++の開発フローは、「**ソースコード作成 → コンパイル → 実行**」が基本です。 + * すべてのC++プログラムは `main` 関数から実行が始まります。 + * `#include` で外部ライブラリの機能を読み込み、`namespace` で名前の衝突を避けます。 + +これであなたもC++プログラマの仲間入りです!次の章では、C++の根幹をなす型システムとメモリの仕組みについて学んでいきましょう。 diff --git a/public/docs/cpp/1-types-control/-intro.md b/public/docs/cpp/1-types-control/-intro.md new file mode 100644 index 0000000..b6d0979 --- /dev/null +++ b/public/docs/cpp/1-types-control/-intro.md @@ -0,0 +1 @@ +C++は**静的型付け言語**です。PythonやJavaScriptのような動的型付け言語とは異なり、コンパイル時に変数の型が確定している必要があります。これにより、実行時のエラーを未然に防ぎ、高いパフォーマンスを実現します。 diff --git a/public/docs/cpp/1-types-control/1-0-basic-types.md b/public/docs/cpp/1-types-control/1-0-basic-types.md new file mode 100644 index 0000000..1a56582 --- /dev/null +++ b/public/docs/cpp/1-types-control/1-0-basic-types.md @@ -0,0 +1,16 @@ +--- +id: cpp-types-basic +title: 基本的なデータ型 +level: 2 +--- + +## 基本的なデータ型 + +C++には多くの型がありますが、まずは以下の基本型を押さえましょう。 + + * **整数型**: `int` (通常4バイト), `long long` (8バイト, 大きな整数) + * **浮動小数点型**: `double` (倍精度, 基本的にこれを使う), `float` (単精度) + * **文字型**: `char` (1バイト文字), `std::string` (文字列クラス。厳密には基本型ではありませんが、実用上必須) + * **ブール型**: `bool` (`true` または `false`) + +C++では変数のサイズ(ビット幅)が環境によって異なる場合がありますが、現代的な環境では `int` は32bit以上であることが保証されています。 diff --git a/public/docs/cpp/1-types-control/2-0-uniform-init.md b/public/docs/cpp/1-types-control/2-0-uniform-init.md new file mode 100644 index 0000000..d868d0b --- /dev/null +++ b/public/docs/cpp/1-types-control/2-0-uniform-init.md @@ -0,0 +1,38 @@ +--- +id: cpp-types-uniform-init +title: '変数の初期化:ユニフォーム初期化 {}' +level: 2 +--- + +## 変数の初期化:ユニフォーム初期化 `{}` + +C++には変数を初期化する方法がいくつもありますが、C++11以降では **波括弧 `{}` を使った初期化(ユニフォーム初期化)** が推奨されています。 + +なぜ `{}` が良いのでしょうか? それは、**縮小変換(Narrowing Conversion)** を防げるからです。例えば、少数のデータを整数型変数に無理やり入れようとした時、`=` なら黙って切り捨てられますが、`{}` ならコンパイルエラーにしてくれます。 + +```cpp:initialization.cpp +#include + +int main() { + // 推奨:波括弧による初期化 + int age{25}; // int age = 25; と同じだがより安全 + double weight{65.5}; + bool is_student{false}; + + // 縮小変換の防止(コメントアウトを外すとコンパイルエラーになります) + // int height{170.5}; // エラー! doubleからintへの情報の欠落を防ぐ + + // 従来の方法(=を使う)も間違いではありませんが、警告が出ないことがあります + int rough_height = 170.9; // 170に切り捨てられる(エラーにならない) + + std::cout << "Alice is " << age << " years old." << std::endl; + std::cout << "Height (rough): " << rough_height << std::endl; + + return 0; +} +``` + +```cpp-exec:initialization.cpp +Alice is 25 years old. +Height (rough): 170 +``` diff --git a/public/docs/cpp/1-types-control/3-0-modifier.md b/public/docs/cpp/1-types-control/3-0-modifier.md new file mode 100644 index 0000000..c9a273d --- /dev/null +++ b/public/docs/cpp/1-types-control/3-0-modifier.md @@ -0,0 +1,9 @@ +--- +id: cpp-types-modifier +title: 型を厳密に扱う +level: 2 +--- + +## 型を厳密に扱う + +静的型付けの恩恵を最大限に受けるために、C++には型をより安全かつ便利に扱うための仕組みがあります。 diff --git a/public/docs/cpp/1-types-control/3-1-const.md b/public/docs/cpp/1-types-control/3-1-const.md new file mode 100644 index 0000000..eae3728 --- /dev/null +++ b/public/docs/cpp/1-types-control/3-1-const.md @@ -0,0 +1,35 @@ +--- +id: cpp-types-const +title: constによる不変性の保証 +level: 3 +--- + +### `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 +``` diff --git a/public/docs/cpp/1-types-control/3-2-auto.md b/public/docs/cpp/1-types-control/3-2-auto.md new file mode 100644 index 0000000..d705abb --- /dev/null +++ b/public/docs/cpp/1-types-control/3-2-auto.md @@ -0,0 +1,25 @@ +--- +id: cpp-types-auto +title: autoによる型推論 +level: 3 +--- + +### `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言語スタイルの文字列) になるので注意 +``` diff --git a/public/docs/cpp/1-types-control/4-0-console.md b/public/docs/cpp/1-types-control/4-0-console.md new file mode 100644 index 0000000..ac6a7ef --- /dev/null +++ b/public/docs/cpp/1-types-control/4-0-console.md @@ -0,0 +1,36 @@ +--- +id: cpp-types-console +title: 'コンソール入出力 (std::cin, std::cout)' +level: 2 +--- + +## コンソール入出力 (`std::cin`, `std::cout`) + +C言語の `printf`/`scanf` と異なり、C++ではストリーム(データの流れ)として入出力を扱います。型指定子(`%d`など)を覚える必要がなく、型安全です。 + + * `std::cout << 値`: 出力(Console OUT) + * `std::cin >> 変数`: 入力(Console IN) + * `std::endl`: 改行を行い、バッファをフラッシュする。 + +> my.code(); の実行環境には入力機能がないので、コード例だけ示します: + +```cpp +#include +#include + +int main() { + int id; + std::string name; + + // 複数の値を出力する場合、<< で連結します + std::cout << "Enter ID and Name: "; + + // キーボードから "101 Bob" のように入力されるのを待つ + std::cin >> id >> name; + + std::cout << "User: " << name << " (ID: " << id << ")" << std::endl; + // User: Bob (ID: 101) + + return 0; +} +``` diff --git a/public/docs/cpp/1-types-control/5-0-control.md b/public/docs/cpp/1-types-control/5-0-control.md new file mode 100644 index 0000000..48475ae --- /dev/null +++ b/public/docs/cpp/1-types-control/5-0-control.md @@ -0,0 +1,9 @@ +--- +id: cpp-control +title: 制御構文:if, switch, while, for +level: 2 +--- + +## 制御構文:if, switch, while, for + +他のC系言語(Java, C\#, JSなど)とほぼ同じですが、いくつか注意点があります。 diff --git a/public/docs/cpp/1-types-control/5-1-if.md b/public/docs/cpp/1-types-control/5-1-if.md new file mode 100644 index 0000000..7ef3c24 --- /dev/null +++ b/public/docs/cpp/1-types-control/5-1-if.md @@ -0,0 +1,31 @@ +--- +id: cpp-control-if +title: if文 +level: 3 +--- + +### `if`文 + +`if (条件式)` の条件式は `bool` に変換可能なものである必要があります。C++では `0` は `false`、それ以外は `true` とみなされます。 + +```cpp:control-if.cpp +#include + +int main() { + // --- if文 --- + const int score = 85; + if (score >= 90) { + std::cout << "Grade: A" << std::endl; + } else if (score >= 80) { + std::cout << "Grade: B" << std::endl; + } else { + std::cout << "Grade: C or below" << std::endl; + } + + return 0; +} +``` + +```cpp-exec:control-if.cpp +Grade: B +``` diff --git a/public/docs/cpp/1-types-control/5-2-switch.md b/public/docs/cpp/1-types-control/5-2-switch.md new file mode 100644 index 0000000..083c34a --- /dev/null +++ b/public/docs/cpp/1-types-control/5-2-switch.md @@ -0,0 +1,41 @@ +--- +id: cpp-control-switch +title: switch文とフォールスルー +level: 3 +--- + +### `switch`文とフォールスルー + +`switch` 文は `break` を書かない限り、次の `case` へ処理が流れます(フォールスルー)。意図的なフォールスルーでない限り、`break` を忘れないように注意が必要です。C++17以降では `[[fallthrough]];` 属性をつけることで、「意図的なものである」とコンパイラに伝え、警告を抑制できます。 + +```cpp:control-switch.cpp +#include + +int main() { + // --- switch文 --- + const int rank = 2; + std::cout << "Rank " << rank << ": "; + + switch (rank) { + case 1: + std::cout << "Gold" << std::endl; + break; + case 2: + std::cout << "Silver" << std::endl; + // breakを忘れるとcase 3も実行される + [[fallthrough]]; // C++17: 意図的に下に流すことを明示 + case 3: + std::cout << "(Medalist)" << std::endl; + break; + default: + std::cout << "Participant" << std::endl; + } + + return 0; +} +``` + +```cpp-exec:control-switch.cpp +Rank 2: Silver +(Medalist) +``` diff --git a/public/docs/cpp/1-types-control/5-3-loop.md b/public/docs/cpp/1-types-control/5-3-loop.md new file mode 100644 index 0000000..cb1e40b --- /dev/null +++ b/public/docs/cpp/1-types-control/5-3-loop.md @@ -0,0 +1,28 @@ +--- +id: cpp-control-loop +title: ループ構文 +level: 3 +--- + +### ループ構文 + +`while`, `for` も標準的です。 + +```cpp:control-loop.cpp +#include + +int main() { + // --- 基本的なforループ --- + std::cout << "Countdown: "; + for (int i = 3; i > 0; --i) { + std::cout << i << " "; + } + std::cout << "Start!" << std::endl; + + return 0; +} +``` + +```cpp-exec:control-loop.cpp +Countdown: 3 2 1 Start! +``` diff --git a/public/docs/cpp/1-types-control/6-0-summary.md b/public/docs/cpp/1-types-control/6-0-summary.md new file mode 100644 index 0000000..7a85a9c --- /dev/null +++ b/public/docs/cpp/1-types-control/6-0-summary.md @@ -0,0 +1,13 @@ +--- +id: cpp-types-control-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **型システム**: `int`, `double`, `bool` などの基本型があり、静的に管理される。 + * **初期化**: `int x{10};` のような波括弧 `{}` を使う初期化が推奨される(縮小変換の防止)。 + * **型推論と定数**: `auto` で型推論を行い、変更しない変数には `const` を付ける。 + * **入出力**: `std::cout`, `std::cin` を使い、`<<`, `>>` 演算子でデータを流す。 + * **制御構文**: 基本は他言語と同じだが、`switch` のフォールスルー挙動などに注意する。 diff --git a/public/docs/cpp/1-types-control/6-1-practice1.md b/public/docs/cpp/1-types-control/6-1-practice1.md new file mode 100644 index 0000000..e5b5559 --- /dev/null +++ b/public/docs/cpp/1-types-control/6-1-practice1.md @@ -0,0 +1,28 @@ +--- +id: cpp-types-control-practice1 +title: 練習問題1:うるう年判定機 +level: 3 +--- + +## 練習問題1:うるう年判定機 + +西暦(整数)を変数 `year` に代入し、その年がうるう年かどうかを判定して結果を出力するプログラムを書いてください。 + +> **うるう年の条件** +> +> 1. 4で割り切れる年はうるう年である。 +> 2. ただし、100で割り切れる年はうるう年ではない。 +> 3. ただし、400で割り切れる年はうるう年である。 + +```cpp:practice2_1.cpp +#include + +int main() { + const int year = 2025; + + // ここにコードを書く +} +``` + +```cpp-exec:practice2_1.cpp +``` diff --git a/public/docs/cpp/1-types-control/6-2-practice2.md b/public/docs/cpp/1-types-control/6-2-practice2.md new file mode 100644 index 0000000..de227b1 --- /dev/null +++ b/public/docs/cpp/1-types-control/6-2-practice2.md @@ -0,0 +1,27 @@ +--- +id: cpp-types-control-practice2 +title: 練習問題2:FizzBuzz(C++スタイル) +level: 3 +--- + +### 練習問題2:FizzBuzz(C++スタイル) + +1から20までの整数を順に出力するループを作成してください。ただし、以下のルールに従ってください。 + + * 数値が3で割り切れるときは数値の代わりに "Fizz" と出力。 + * 数値が5で割り切れるときは数値の代わりに "Buzz" と出力。 + * 両方で割り切れるときは "FizzBuzz" と出力。 + * それ以外は数値をそのまま出力。 + +出力はスペース区切りまたは改行区切りどちらでも構いません。変数の初期化には `{}` を、ループカウンタの型には `auto` を使用してみてください。 + +```cpp:practice2_2.cpp +#include + +int main() { + +} +``` + +```cpp-exec:practice2_2.cpp +``` diff --git a/public/docs/cpp/10-stl-containers/-intro.md b/public/docs/cpp/10-stl-containers/-intro.md new file mode 100644 index 0000000..e12e2c9 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/-intro.md @@ -0,0 +1 @@ +C++の大きな魅力の一つに、**標準テンプレートライブラリ (Standard Template Library, STL)** の存在があります。STLは、よく使われるデータ構造やアルゴリズムを、汎用的かつ効率的に実装したライブラリ群です。この章では、STLの心臓部である**コンテナ**に焦点を当て、データの格納と管理を劇的に楽にする方法を学びます。 diff --git a/public/docs/cpp/10-stl-containers/1-0-about.md b/public/docs/cpp/10-stl-containers/1-0-about.md new file mode 100644 index 0000000..0847d20 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/1-0-about.md @@ -0,0 +1,15 @@ +--- +id: cpp-stl-containers-about +title: 'STLの全体像: コンテナ、アルゴリズム、イテレータ' +level: 2 +--- + +## STLの全体像: コンテナ、アルゴリズム、イテレータ + +STLは、主に3つの要素から構成されています。 + +1. **コンテナ (Containers)**: データを格納するためのデータ構造です。`vector`(可変長配列)や`map`(連想配列)など、様々な種類があります。 +2. **アルゴリズム (Algorithms)**: ソート、検索、変換など、コンテナ上のデータに対して操作を行う関数群です。 +3. **イテレータ (Iterators)**: コンテナの要素を指し示し、アルゴリズムがコンテナの種類に依存せずに各要素にアクセスするための統一的なインターフェースを提供します。ポインタを一般化したようなものです。 + +これら3つが連携することで、C++プログラマは効率的で再利用性の高いコードを素早く書くことができます。この章では「コンテナ」を、次の章では「アルゴリズム」と、それらをつなぐ「イテレータ」の応用を詳しく見ていきます。 diff --git a/public/docs/cpp/10-stl-containers/2-0-vector.md b/public/docs/cpp/10-stl-containers/2-0-vector.md new file mode 100644 index 0000000..a421975 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/2-0-vector.md @@ -0,0 +1,74 @@ +--- +id: cpp-stl-containers-vector +title: 'std::vector: 最もよく使う可変長配列' +level: 2 +--- + +## `std::vector`: 最もよく使う可変長配列 + +`std::vector`は、最も基本的で最もよく使われるコンテナです。他の言語でいうところの「リスト」や「動的配列」に相当し、要素を連続したメモリ領域に格納します。 + +**主な特徴**: + + * **動的なサイズ**: 必要に応じて自動的にサイズが拡張されます。 + * **高速なランダムアクセス**: インデックス(添字)を使って `[i]` の形式で要素に高速にアクセスできます (`O(1)`)。 + * **末尾への高速な追加・削除**: `push_back()` や `pop_back()` を使った末尾への操作は非常に高速です。 + +`std::vector`を使うには、``ヘッダをインクルードする必要があります。 + +```cpp:vector_example.cpp +#include +#include +#include + +int main() { + // string型の要素を格納するvectorを作成 + std::vector names; + + // push_backで末尾に要素を追加 + names.push_back("Alice"); + names.push_back("Bob"); + names.push_back("Charlie"); + + // インデックスを使った要素へのアクセス + std::cout << "Index 1: " << names[1] << std::endl; + + // 範囲for文 (range-based for loop) を使った全要素の走査 + std::cout << "\nAll names:" << std::endl; + for (const std::string& name : names) { + std::cout << "- " << name << std::endl; + } + + // size()で現在の要素数を取得 + std::cout << "\nCurrent size: " << names.size() << std::endl; + + // pop_backで末尾の要素を削除 + names.pop_back(); // "Charlie"を削除 + + std::cout << "\nAfter pop_back:" << std::endl; + for (const std::string& name : names) { + std::cout << "- " << name << std::endl; + } + std::cout << "Current size: " << names.size() << std::endl; + + return 0; +} +``` + +```cpp-exec:vector_example.cpp +Index 1: Bob + +All names: +- Alice +- Bob +- Charlie + +Current size: 3 + +After pop_back: +- Alice +- Bob +Current size: 2 +``` + +`std::vector`は、どのコンテナを使うか迷ったら、まず最初に検討すべきデフォルトの選択肢と言えるほど万能です。 diff --git a/public/docs/cpp/10-stl-containers/3-0-map.md b/public/docs/cpp/10-stl-containers/3-0-map.md new file mode 100644 index 0000000..88f95fd --- /dev/null +++ b/public/docs/cpp/10-stl-containers/3-0-map.md @@ -0,0 +1,83 @@ +--- +id: cpp-stl-containers-map +title: 'std::map: キーと値のペアを管理する連想配列' +level: 2 +--- + +## `std::map`: キーと値のペアを管理する連想配列 + +`std::map`は、キー (key) と値 (value) のペアを管理するためのコンテナです。他の言語の「辞書 (dictionary)」や「ハッシュマップ (hash map)」に似ています。キーを使って値を高速に検索、追加、削除できます。 + +**主な特徴**: + + * **キーによる高速な検索**: キーに基づいて要素が自動的にソートされて格納されるため、検索、挿入、削除が高速です (`O(log n)`)。 + * **一意なキー**: `std::map`内のキーは重複しません。同じキーで値を挿入しようとすると、既存の値が上書きされます。 + +`std::map`を使うには、``ヘッダをインクルードする必要があります。 + +```cpp:map_example.cpp +#include +#include +#include + +int main() { + // キーがstring型、値がint型のmapを作成 + std::map scores; + + // []演算子で要素を追加・更新 + scores["Alice"] = 95; + scores["Bob"] = 88; + scores["Charlie"] = 76; + + // []演算子で値にアクセス + std::cout << "Bob's score: " << scores["Bob"] << std::endl; + + // 新しいキーで追加 + scores["David"] = 100; + + // 既存のキーの値を更新 + scores["Alice"] = 98; + + // 範囲for文を使った全要素の走査 + // autoキーワードを使うと型推論が効いて便利 + std::cout << "\nAll scores:" << std::endl; + for (const auto& pair : scores) { + std::cout << "- " << pair.first << ": " << pair.second << std::endl; + } + + // count()でキーの存在を確認 + std::string search_key = "Charlie"; + if (scores.count(search_key)) { + std::cout << "\n" << search_key << " is in the map." << std::endl; + } + + // erase()で要素を削除 + scores.erase("Bob"); + + std::cout << "\nAfter erasing Bob:" << std::endl; + for (const auto& pair : scores) { + std::cout << "- " << pair.first << ": " << pair.second << std::endl; + } + + return 0; +} +``` + +```cpp-exec:map_example.cpp +Bob's score: 88 + +All scores: +- Alice: 98 +- Bob: 88 +- Charlie: 76 +- David: 100 + +Charlie is in the map. + +After erasing Bob: +- Alice: 98 +- Charlie: 76 +- David: 100 +``` + +`std::map`は、キーと値のペアを効率的に管理したい場合に非常に強力なツールです。 diff --git a/public/docs/cpp/10-stl-containers/4-0-other.md b/public/docs/cpp/10-stl-containers/4-0-other.md new file mode 100644 index 0000000..86b8fe6 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/4-0-other.md @@ -0,0 +1,16 @@ +--- +id: cpp-stl-containers-other +title: 'その他: 目的に応じたコンテナ' +level: 2 +--- + +## その他: 目的に応じたコンテナ + +STLには、他にも特定の目的に特化したコンテナが多数用意されています。ここでは代表的なものをいくつか紹介します。 + + * `std::list`: 双方向リスト。要素の途中への挿入・削除が非常に高速 (`O(1)`) ですが、ランダムアクセスはできません(先頭から順番にたどる必要があります)。``ヘッダが必要です。 + * `std::set`: 重複しない要素の集合を管理します。要素は自動的にソートされます。特定の要素が集合内に存在するかどうかを高速に判定したい場合 (`O(log n)`) に便利です。``ヘッダが必要です。 + * `std::unordered_map`: `std::map`と同様にキーと値のペアを管理しますが、内部的にハッシュテーブルを使うため、平均的な検索・挿入・削除がさらに高速 (`O(1)`) です。ただし、要素はソートされません。``ヘッダが必要です。 + * `std::queue`, `std::stack`: それぞれ先入れ先出し (FIFO)、後入れ先出し (LIFO) のデータ構造を実装するためのコンテナアダプタです。 + +どのコンテナを選択するかは、プログラムの要件(データのアクセスパターン、挿入・削除の頻度など)によって決まります。まずは`std::vector`を基本とし、必要に応じて他のコンテナを検討するのが良いアプローチです。 diff --git a/public/docs/cpp/10-stl-containers/5-0-summary.md b/public/docs/cpp/10-stl-containers/5-0-summary.md new file mode 100644 index 0000000..5a65458 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/5-0-summary.md @@ -0,0 +1,13 @@ +--- +id: cpp-stl-containers-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **STL**は、**コンテナ**、**アルゴリズム**、**イテレータ**の3つの主要コンポーネントから構成される、C++の強力な標準ライブラリです。 + * **コンテナ**は、データを格納するためのクラスです。 + * `std::vector`は、最も一般的に使われる動的配列で、高速なランダムアクセスと末尾への簡単な要素追加が特徴です。 + * `std::map`は、キーと値のペアを管理する連想配列で、キーによる高速な検索が可能です。 + * 他にも`std::list`, `std::set`など、特定の用途に合わせた様々なコンテナが用意されています。 diff --git a/public/docs/cpp/10-stl-containers/5-1-practice1.md b/public/docs/cpp/10-stl-containers/5-1-practice1.md new file mode 100644 index 0000000..d46b184 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/5-1-practice1.md @@ -0,0 +1,39 @@ +--- +id: cpp-stl-containers-practice1 +title: '練習問題1: 数値ベクタの操作' +level: 3 +--- + +### 練習問題1: 数値ベクタの操作 + +`std::vector`型の整数のリストに対して、以下の処理を行うプログラムを作成してください。 + +1. ベクタに格納されている全ての数値の合計値を計算して表示する。 +2. ベクタの中の最大値を検索して表示する。 +3. ベクタの要素を逆順にして、その内容を表示する。(ヒント:新しいベクタを作っても良いですし、`std::swap`を使っても構いません) + +```cpp:practice11_1.cpp +#include +#include + +int main() { + std::vector numbers = {3, 5, 2, 8, 6}; + + // 1. 合計値の計算 + + + // 2. 最大値の検索 + + + // 3. 要素の逆順表示 + + + return 0; +} +``` + +```cpp-exec:practice11_1.cpp +Sum: 24 +Max: 8 +Reversed: 6 8 2 5 3 +``` diff --git a/public/docs/cpp/10-stl-containers/5-2-practice2.md b/public/docs/cpp/10-stl-containers/5-2-practice2.md new file mode 100644 index 0000000..80f8954 --- /dev/null +++ b/public/docs/cpp/10-stl-containers/5-2-practice2.md @@ -0,0 +1,43 @@ +--- +id: cpp-stl-containers-practice2 +title: '練習問題2: 簡単な単語カウンター' +level: 3 +--- + +### 練習問題2: 簡単な単語カウンター + +英文(スペースで区切られた単語の列)を読み込み、各単語が何回出現したかをカウントするプログラムを`std::map`を使って作成してください。最後に、出現した全単語とその出現回数をアルファベット順に表示してください。 + +> 文字列を単語ごとに分割するには、以下のように`std::istringstream`を使うと便利です。 +```cpp +#include + +std::string text = "this is a sample text"; +std::istringstream iss(text); +std::string word; +while (iss >> word) { + // wordには1単語ずつ格納される +} +``` + + + +```cpp:practice11_2.cpp +#include +#include +#include +#include + +int main() { + std::string text = "cpp is fun and cpp is powerful"; + + +} +``` +```cpp-exec:practice11_2.cpp +and: 1 +cpp: 2 +fun: 1 +is: 2 +powerful: 1 +``` diff --git a/public/docs/cpp/11-stl-algorithms/-intro.md b/public/docs/cpp/11-stl-algorithms/-intro.md new file mode 100644 index 0000000..6e0f403 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/-intro.md @@ -0,0 +1,3 @@ +前の章ではSTLのコンテナについて学び、様々なデータを効率的に格納する方法を見てきました。しかし、データを格納するだけではプログラムは完成しません。そのデータを並べ替えたり、検索したり、特定の処理を施したりといった「操作」が必要です。 + +この章では、STLのもう一つの強力な柱である**アルゴリズム**ライブラリを学びます。これらのアルゴリズムは、コンテナ内のデータを操作するための汎用的な関数群です。そして、アルゴリズムをさらに柔軟かつ強力にするための現代的なC++の機能、**ラムダ式**についても解説します。これらをマスターすれば、驚くほど少ないコードで複雑なデータ操作が実現できるようになります。 diff --git a/public/docs/cpp/11-stl-algorithms/1-0-about.md b/public/docs/cpp/11-stl-algorithms/1-0-about.md new file mode 100644 index 0000000..c38f7be --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/1-0-about.md @@ -0,0 +1,52 @@ +--- +id: cpp-stl-algorithms-about +title: イテレータ:コンテナとアルゴリズムを繋ぐ架け橋 +level: 2 +--- + +## イテレータ:コンテナとアルゴリズムを繋ぐ架け橋 + +アルゴリズムは、特定のコンテナ(`std::vector` や `std::list` など)に直接依存しないように設計されています。では、どうやってコンテナ内の要素にアクセスするのでしょうか?そこで登場するのが**イテレータ (Iterator)** です。 + +イテレータは、コンテナ内の要素を指し示す「ポインタのような」オブジェクトです。ポインタのように `*` で要素の値を参照したり、`++` で次の要素に進んだりできます。 + +ほとんどのコンテナは、以下の2つの重要なイテレータを取得するメンバ関数を持っています。 + + * `begin()`: コンテナの先頭要素を指すイテレータを返す。 + * `end()`: コンテナの**最後の要素の次**を指すイテレータを返す。これは有効な要素を指していない「番兵」のような役割を果たします。 + +アルゴリズムは、この `begin()` と `end()` から得られるイテレータのペアを使い、操作対象の「範囲」を指定します。範囲は半開区間 `[begin, end)` で表され、`begin` が指す要素は範囲に含まれ、`end` が指す要素は含まれません。 + +簡単な例を見てみましょう。イテレータを使って `vector` の全要素を表示するコードです。 + +```cpp:iterator_example.cpp +#include +#include + +int main() { + std::vector numbers = {0, 1, 2, 3, 4}; + + // イテレータを使ってコンテナを走査 + std::cout << "Numbers: "; + for (auto it = numbers.begin(); it != numbers.end(); ++it) { + std::cout << *it << " "; // *it で要素の値にアクセス + } + std::cout << std::endl; + + // C++11以降の範囲ベースforループ (内部ではイテレータが使われている) + std::cout << "Numbers (range-based for): "; + for (int num : numbers) { + std::cout << num << " "; + } + std::cout << std::endl; + + return 0; +} +``` + +```cpp-exec:iterator_example.cpp +Numbers: 0 1 2 3 4 +Numbers (range-based for): 0 1 2 3 4 +``` + +このように、イテレータはコンテナの種類を問わず、統一的な方法で要素にアクセスする仕組みを提供します。これが、アルゴリズムが様々なコンテナに対して汎用的に機能する理由です。 diff --git a/public/docs/cpp/11-stl-algorithms/2-0-algo.md b/public/docs/cpp/11-stl-algorithms/2-0-algo.md new file mode 100644 index 0000000..f0d3990 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/2-0-algo.md @@ -0,0 +1,9 @@ +--- +id: cpp-stl-algorithms-algo +title: 便利なアルゴリズム +level: 2 +--- + +## 便利なアルゴリズム + +C++の標準ライブラリには、`` ヘッダと `` ヘッダに数多くの便利なアルゴリズムが用意されています。ここでは、特によく使われるものをいくつか紹介します。 diff --git a/public/docs/cpp/11-stl-algorithms/2-1-sort.md b/public/docs/cpp/11-stl-algorithms/2-1-sort.md new file mode 100644 index 0000000..e6f838d --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/2-1-sort.md @@ -0,0 +1,45 @@ +--- +id: cpp-stl-algorithms-sort +title: 'std::sort: 要素を並べ替える' +level: 3 +--- + +### `std::sort`: 要素を並べ替える + +名前の通り、指定された範囲の要素をソートします。デフォルトでは昇順に並べ替えます。 + +```cpp:sort_example.cpp +#include +#include +#include // std::sort のために必要 +#include + +int main() { + std::vector numbers = {5, 2, 8, 1, 9}; + + // numbers.begin() から numbers.end() の範囲をソート + std::sort(numbers.begin(), numbers.end()); + + std::cout << "Sorted numbers: "; + for (int num : numbers) { + std::cout << num << " "; + } + std::cout << std::endl; + + std::vector words = {"banana", "apple", "cherry"}; + std::sort(words.begin(), words.end()); + + std::cout << "Sorted words: "; + for (const auto& word : words) { + std::cout << word << " "; + } + std::cout << std::endl; + + return 0; +} +``` + +```cpp-exec:sort_example.cpp +Sorted numbers: 1 2 5 8 9 +Sorted words: apple banana cherry +``` diff --git a/public/docs/cpp/11-stl-algorithms/2-2-find.md b/public/docs/cpp/11-stl-algorithms/2-2-find.md new file mode 100644 index 0000000..bd2c911 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/2-2-find.md @@ -0,0 +1,52 @@ +--- +id: cpp-stl-algorithms-find +title: 'std::find: 要素を検索する' +level: 3 +--- + +### `std::find`: 要素を検索する + +指定された範囲から特定の値を持つ要素を検索します。 + + * **見つかった場合**: その要素を指すイテレータを返します。 + * **見つからなかった場合**: 範囲の終端を示すイテレータ (`end()`) を返します。 + +この性質を利用して、要素が存在するかどうかをチェックできます。 + +```cpp:find_example.cpp +#include +#include +#include // std::find のために必要 + +int main() { + std::vector numbers = {10, 20, 30, 40, 50}; + int value_to_find = 30; + + // numbers の中から 30 を探す + auto it = std::find(numbers.begin(), numbers.end(), value_to_find); + + if (it != numbers.end()) { + // 見つかった場合 + std::cout << "Found " << *it << " at index " << std::distance(numbers.begin(), it) << std::endl; + } else { + // 見つからなかった場合 + std::cout << value_to_find << " not found." << std::endl; + } + + value_to_find = 99; + it = std::find(numbers.begin(), numbers.end(), value_to_find); + + if (it != numbers.end()) { + std::cout << "Found " << *it << std::endl; + } else { + std::cout << value_to_find << " not found." << std::endl; + } + + return 0; +} +``` + +```cpp-exec:find_example.cpp +Found 30 at index 2 +99 not found. +``` diff --git a/public/docs/cpp/11-stl-algorithms/2-3-foreach.md b/public/docs/cpp/11-stl-algorithms/2-3-foreach.md new file mode 100644 index 0000000..0ea4388 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/2-3-foreach.md @@ -0,0 +1,16 @@ +--- +id: cpp-stl-algorithms-foreach +title: 'std::for_each: 各要素に処理を適用する' +level: 3 +--- + +### `std::for_each`: 各要素に処理を適用する + +指定された範囲の全ての要素に対して、特定の関数(処理)を適用します。ループを書くよりも意図が明確になる場合があります。 + +```cpp +// 3番目の引数に関数を渡す +std::for_each(numbers.begin(), numbers.end(), print_function); +``` + +ここで「特定の処理」をその場で手軽に記述する方法が**ラムダ式**です。 diff --git a/public/docs/cpp/11-stl-algorithms/3-0-lambda.md b/public/docs/cpp/11-stl-algorithms/3-0-lambda.md new file mode 100644 index 0000000..e5c7be4 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/3-0-lambda.md @@ -0,0 +1,91 @@ +--- +id: cpp-stl-algorithms-lambda +title: ラムダ式:その場で書ける無名関数 +level: 2 +--- + +## ラムダ式:その場で書ける無名関数 + +ラムダ式(Lambda Expression)は、C++11から導入された非常に強力な機能です。一言で言えば、「**その場で定義して使える名前のない小さな関数**」です。これにより、アルゴリズムに渡すためだけの短い関数をわざわざ定義する必要がなくなり、コードが非常に簡潔になります。 + +ラムダ式の基本的な構文は以下の通りです。 + +```cpp +[キャプチャ](引数リスト) -> 戻り値の型 { 処理本体 } +``` + + * `[]` **キャプチャ句**: ラムダ式の外にある変数を取り込んで、式の中で使えるようにします。 + * `[]`: 何もキャプチャしない。 + * `[=]`: 外の変数を全て値渡し(コピー)でキャプチャする。 + * `[&]`: 外の変数を全て参照渡しでキャプチャする。 + * `[x, &y]`: 変数 `x` は値渡し、変数 `y` は参照渡しでキャプチャする。 + * `()` **引数リスト**:通常の関数と同じ引数を取ることができます。 + * `-> 戻り値の型`: 戻り値の型を指定します。多くの場合、コンパイラが推論できるため省略可能です。 + * `{}` **処理本体**: 関数の処理内容を記述します。 + +`std::for_each` とラムダ式を組み合わせた例を見てみましょう。 + +```cpp:for_each_lambda_example.cpp +#include +#include +#include + +int main() { + std::vector numbers = {1, 2, 3, 4, 5}; + + // 各要素を2倍して表示する + std::cout << "Doubled numbers: "; + std::for_each(numbers.begin(), numbers.end(), [](int n) { + std::cout << n * 2 << " "; + }); + std::cout << std::endl; + + // 外部の変数をキャプチャする例 + int sum = 0; + // `&sum` で sum を参照キャプチャし、ラムダ式内で変更できるようにする + std::for_each(numbers.begin(), numbers.end(), [&sum](int n) { + sum += n; + }); + + std::cout << "Sum: " << sum << std::endl; + + return 0; +} +``` + +```cpp-exec:for_each_lambda_example.cpp +Doubled numbers: 2 4 6 8 10 +Sum: 15 +``` + +このコードは、`for_each` の3番目の引数に直接処理を書き込んでいます。非常に直感的で読みやすいと思いませんか? + +ラムダ式は、特に `std::sort` のソート順をカスタマイズする際に真価を発揮します。例えば、数値を降順にソートしたい場合、比較ルールをラムダ式で与えることができます。 + +```cpp:lambda_sort_example.cpp +#include +#include +#include + +int main() { + std::vector numbers = {5, 2, 8, 1, 9}; + + // 比較関数としてラムダ式を渡す + // a > b であれば true を返すことで降順ソートになる + std::sort(numbers.begin(), numbers.end(), [](int a, int b) { + return a > b; + }); + + std::cout << "Sorted in descending order: "; + for (int num : numbers) { + std::cout << num << " "; + } + std::cout << std::endl; + + return 0; +} +``` + +```cpp-exec:lambda_sort_example.cpp +Sorted in descending order: 9 8 5 2 1 +``` diff --git a/public/docs/cpp/11-stl-algorithms/4-0-summary.md b/public/docs/cpp/11-stl-algorithms/4-0-summary.md new file mode 100644 index 0000000..d2cec9f --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/4-0-summary.md @@ -0,0 +1,16 @@ +--- +id: cpp-stl-algorithms-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、STLのアルゴリズムとラムダ式について学びました。 + + * **イテレータ**は、コンテナの要素を指し示すオブジェクトであり、アルゴリズムとコンテナの間のインターフェースとして機能します。 + * `` ヘッダには、**`std::sort`** (ソート)、**`std::find`** (検索)、**`std::for_each`** (繰り返し処理) といった、汎用的で強力なアルゴリズムが多数用意されています。 + * **ラムダ式**は、その場で定義できる無名関数であり、アルゴリズムに渡す処理を簡潔かつ直感的に記述することができます。 + * **キャプチャ**機能を使うことで、ラムダ式の外にある変数を取り込んで処理に利用できます。 + +コンテナ、イテレータ、アルゴリズム、そしてラムダ式。これらを組み合わせることで、C++におけるデータ処理は、他の多くの言語に引けを取らない、あるいはそれ以上に表現力豊かで効率的なものになります。 diff --git a/public/docs/cpp/11-stl-algorithms/4-1-practice1.md b/public/docs/cpp/11-stl-algorithms/4-1-practice1.md new file mode 100644 index 0000000..ba96075 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/4-1-practice1.md @@ -0,0 +1,28 @@ +--- +id: cpp-stl-algorithms-practice1 +title: '練習問題1: 文字列の長さでソート' +level: 3 +--- + +### 練習問題1: 文字列の長さでソート + +`std::vector` を用意し、格納されている文字列を、文字数が短い順にソートして、結果を出力するプログラムを作成してください。`std::sort` とラムダ式を使用してください。 + +**ヒント**: ラムダ式は2つの文字列を引数に取り、1つ目の文字列の長さが2つ目の文字列の長さより短い場合に `true` を返すように実装します。 + + +```cpp:practice12_1.cpp +#include +#include +#include + +int main() { + std::vector words = {"apple", "banana", "kiwi", "cherry", "fig", "grape"}; + + return 0; +} +``` + +```cpp-exec:practice12_1.cpp +fig kiwi grape apple banana cherry +``` diff --git a/public/docs/cpp/11-stl-algorithms/4-2-practice2.md b/public/docs/cpp/11-stl-algorithms/4-2-practice2.md new file mode 100644 index 0000000..eb1d300 --- /dev/null +++ b/public/docs/cpp/11-stl-algorithms/4-2-practice2.md @@ -0,0 +1,32 @@ +--- +id: cpp-stl-algorithms-practice2 +title: '練習問題2: 条件に合う要素のカウント' +level: 3 +--- + +### 練習問題2: 条件に合う要素のカウント + +`std::vector` に整数をいくつか格納します。その後、ラムダ式と `std::for_each`(または他のアルゴリズム)を使って、以下の2つの条件を満たす要素がそれぞれいくつあるかを数えて出力してください。 + +1. 正の偶数である要素の数 +2. 負の奇数である要素の数 + +**ヒント**: カウント用の変数を2つ用意し、ラムダ式のキャプチャ句で参照キャプチャ (`[&]`) して、式の中でインクリメントします。 + +```cpp:practice12_2.cpp +#include +#include +#include + +int main() { + std::vector numbers = {3, -1, 4, -5, 6, -7, 8, 0, -2}; + + + return 0; +} +``` + +```cpp-exec:practice12_2.cpp +Positive even count: 3 +Negative odd count: 3 +``` diff --git a/public/docs/cpp/12-raii-smart-ptrs/-intro.md b/public/docs/cpp/12-raii-smart-ptrs/-intro.md new file mode 100644 index 0000000..2558189 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/-intro.md @@ -0,0 +1,3 @@ +これまでの章で、`new` と `delete` を使った動的なメモリ管理を学びました。しかし、これらの手動管理は `delete` の呼び忘れによるメモリリークや、複雑なコードでのリソース管理の煩雑さを引き起こす原因となりがちです。 + +C++11以降の「モダンC++」では、こうした問題を解決するための洗練された仕組みが導入されました。この章では、エラーハンドリングのための**例外処理**、リソース管理の基本思想である **RAIIイディオム**、そしてそれを具現化する**スマートポインタ** (`std::unique_ptr`, `std::shared_ptr`) について学び、より安全で堅牢なコードを書くための流儀を身につけます。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/1-0-trycatch.md b/public/docs/cpp/12-raii-smart-ptrs/1-0-trycatch.md new file mode 100644 index 0000000..a369206 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/1-0-trycatch.md @@ -0,0 +1,57 @@ +--- +id: cpp-trycatch +title: '例外処理: try, catch を使ったエラーハンドリング' +level: 2 +--- + +## 例外処理: `try`, `catch` を使ったエラーハンドリング + +プログラムでは、ファイルの読み込み失敗やメモリ確保の失敗など、予期せぬエラーが発生することがあります。C++では、このような状況を処理するために**例外 (Exception)** という仕組みが用意されています。 + +例外処理は、以下の3つのキーワードで構成されます。 + + * `throw`: 例外的な状況が発生したことを知らせるために、例外オブジェクトを「投げる」。 + * `try`: 例外が発生する可能性のあるコードブロックを囲む。 + * `catch`: `try` ブロック内で投げられた例外を「捕まえて」処理する。 + +基本的な構文を見てみましょう。 + +```cpp:exception_basic.cpp +#include +#include // std::runtime_error のために必要 + +// 0で割ろうとしたら例外を投げる関数 +double divide(int a, int b) { + if (b == 0) { + // エラー内容を示す文字列を渡して例外オブジェクトを作成し、投げる + throw std::runtime_error("Division by zero!"); + } + return static_cast(a) / b; +} + +int main() { + int a = 10; + int b = 0; + + try { + // 例外が発生する可能性のあるコード + std::cout << "Trying to divide..." << std::endl; + double result = divide(a, b); + std::cout << "Result: " << result << std::endl; // この行は実行されない + } catch (const std::runtime_error& e) { + // std::runtime_error 型の例外をここで捕まえる + std::cerr << "Caught an exception: " << e.what() << std::endl; + } + + std::cout << "Program finished." << std::endl; + return 0; +} +``` + +```cpp-exec:exception_basic.cpp +Trying to divide... +Caught an exception: Division by zero! +Program finished. +``` + +`divide` 関数内で `b` が0だった場合に `throw` が実行され、関数の実行は即座に中断されます。制御は呼び出し元の `catch` ブロックに移り、そこでエラー処理が行われます。これにより、エラーが発生してもプログラム全体がクラッシュすることなく、安全に処理を続行できます。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/1-1-leak.md b/public/docs/cpp/12-raii-smart-ptrs/1-1-leak.md new file mode 100644 index 0000000..f0be0f8 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/1-1-leak.md @@ -0,0 +1,48 @@ +--- +id: cpp-trycatch-leak +title: 例外とリソースリーク +level: 3 +--- + +### 例外とリソースリーク + +ここで、`new` と `delete` を使った手動のメモリ管理と例外処理が組み合わさると、問題が発生します。 + +```cpp:raw_pointer_problem.cpp +#include +#include + +void process_data() { + int* data = new int[100]; // リソース確保 + std::cout << "Data allocated." << std::endl; + + // 何らかの処理... + bool something_wrong = true; + if (something_wrong) { + throw std::runtime_error("Something went wrong during processing!"); + } + + // 例外が投げられると、この行には到達しない + std::cout << "Deleting data..." << std::endl; + delete[] data; // リソース解放 +} + +int main() { + try { + process_data(); + } catch (const std::runtime_error& e) { + std::cerr << "Error: " << e.what() << std::endl; + } + // process_data内で確保されたメモリは解放されないままである! + return 0; +} +``` + +```cpp-exec:raw_pointer_problem.cpp +Data allocated. +Error: Something went wrong during processing! +``` + +この例では、`process_data` 関数内で `throw` が実行されると、関数の実行が中断され `catch` ブロックにジャンプします。その結果、`delete[] data;` の行が実行されず、確保されたメモリが解放されない**メモリリーク**が発生します。 + +この問題を解決するのが、C++の最も重要な設計思想の一つである **RAII** です。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/2-0-raii.md b/public/docs/cpp/12-raii-smart-ptrs/2-0-raii.md new file mode 100644 index 0000000..f42e8d2 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/2-0-raii.md @@ -0,0 +1,64 @@ +--- +id: cpp-raii +title: RAIIイディオム +level: 2 +--- + +## RAIIイディオム + +**RAII (Resource Acquisition Is Initialization)** は、「リソースの確保は、オブジェクトの初期化時に行い、リソースの解放は、オブジェクトの破棄時に行う」という設計パターンです。日本語では「リソース取得は初期化である」と訳されます。 + +C++では、オブジェクトがそのスコープ(変数が宣言された `{}` の範囲)を抜けるときに、そのオブジェクトの**デストラクタ**が自動的に呼び出されます。この仕組みは、関数が正常に終了した場合だけでなく、**例外が投げられてスコープを抜ける場合でも保証されています**。 + +RAIIはこの性質を利用して、リソースの解放処理をデストラクタに記述することで、リソースの解放を自動化し、`delete` の呼び忘れや例外発生時のリソースリークを防ぎます。 + +簡単なRAIIクラスの例を見てみましょう。 + +```cpp:raii_concept.cpp +#include + +class ResourceWrapper { +private: + int* m_data; + +public: + // コンストラクタでリソースを確保 + ResourceWrapper() { + m_data = new int[10]; + std::cout << "Resource acquired." << std::endl; + } + + // デストラクタでリソースを解放 + ~ResourceWrapper() { + delete[] m_data; + std::cout << "Resource released." << std::endl; + } +}; + +void use_resource() { + ResourceWrapper rw; // オブジェクトが生成され、コンストラクタでリソースが確保される + std::cout << "Using resource..." << std::endl; + + // この関数が終了するとき (正常終了でも例外でも)、 + // rwのデストラクタが自動的に呼ばれ、リソースが解放される +} + +int main() { + std::cout << "Entering main." << std::endl; + use_resource(); + std::cout << "Exiting main." << std::endl; + return 0; +} +``` + +```cpp-exec:raii_concept.cpp +Entering main. +Resource acquired. +Using resource... +Resource released. +Exiting main. +``` + +`use_resource` 関数が終了すると、`rw` オブジェクトがスコープを抜けるため、`ResourceWrapper` のデストラクタが自動的に呼び出され、`delete[]` が実行されます。もし `use_resource` の中で例外が発生したとしても、デストラクタは保証付きで呼び出されます。 + +この強力なRAIIイディオムを、動的メモリ管理のために標準ライブラリが提供してくれているのが**スマートポインタ**です。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/3-0-smartptr.md b/public/docs/cpp/12-raii-smart-ptrs/3-0-smartptr.md new file mode 100644 index 0000000..2f37923 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/3-0-smartptr.md @@ -0,0 +1,11 @@ +--- +id: cpp-smartptr +title: 'スマートポインタ: new/deleteを自動化する' +level: 2 +--- + +## スマートポインタ: new/deleteを自動化する + +スマートポインタは、RAIIを実装したクラステンプレートで、生ポインタ (`int*` など) のように振る舞いながら、リソース (確保したメモリ) の所有権を管理し、適切なタイミングで自動的に解放してくれます。 + +モダンC++では、メモリ管理に生ポインタを直接使うことはほとんどなく、スマートポインタを使うのが基本です。主に2種類のスマートポインタを使い分けます。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/3-1-uniqueptr.md b/public/docs/cpp/12-raii-smart-ptrs/3-1-uniqueptr.md new file mode 100644 index 0000000..5995934 --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/3-1-uniqueptr.md @@ -0,0 +1,87 @@ +--- +id: cpp-smartptr-uniqueptr +title: 'std::unique_ptr' +level: 3 +--- + +### `std::unique_ptr` + +`std::unique_ptr` は、管理するオブジェクトの**所有権を唯一に保つ**スマートポインタです。つまり、あるオブジェクトを指す `unique_ptr` は、常に一つしか存在できません。 + + * **唯一の所有権**: コピーが禁止されています。オブジェクトの所有権を別の `unique_ptr` に移したい場合は、**ムーブ (`std::move`)** を使います。 + * **軽量**: ポインタ一つ分のサイズしかなく、オーバーヘッドが非常に小さいです。 + +`unique_ptr` を作成するには、`std::make_unique` を使うのが安全で推奨されています。 + +```cpp:unique_ptr_example.cpp +#include +#include // スマートポインタのために必要 +#include // std::moveのために必要 + +struct MyData { + MyData() { std::cout << "MyData constructor" << std::endl; } + ~MyData() { std::cout << "MyData destructor" << std::endl; } + void greet() { std::cout << "Hello from MyData!" << std::endl; } +}; + +void process_ptr(std::unique_ptr ptr) { + std::cout << "Inside process_ptr" << std::endl; + ptr->greet(); + // ptrがこの関数のスコープを抜けるときにデストラクタが呼ばれる +} + +int main() { + std::cout << "--- Block 1 ---" << std::endl; + { + // std::make_unique を使ってオブジェクトを生成し、unique_ptrで管理 + std::unique_ptr u_ptr1 = std::make_unique(); + + // 生ポインタと同じように -> や * でメンバにアクセスできる + u_ptr1->greet(); + + // コピーはコンパイルエラーになる + // std::unique_ptr u_ptr2 = u_ptr1; // ERROR! + + // 所有権を u_ptr3 に移動 (ムーブ) + std::unique_ptr u_ptr3 = std::move(u_ptr1); + + // ムーブ後、u_ptr1 は空(nullptr)になる + if (u_ptr1 == nullptr) { + std::cout << "u_ptr1 is now empty." << std::endl; + } + + u_ptr3->greet(); + } // ブロックを抜けると u_ptr3 が破棄され、MyDataのデストラクタが呼ばれる + + std::cout << "\n--- Block 2 ---" << std::endl; + { + auto u_ptr4 = std::make_unique(); + // 関数の引数に渡すことで所有権を譲渡する + process_ptr(std::move(u_ptr4)); + std::cout << "Returned from process_ptr" << std::endl; + } + + std::cout << "\nProgram finished." << std::endl; + return 0; +} +``` + +```cpp-exec:unique_ptr_example.cpp +--- Block 1 --- +MyData constructor +Hello from MyData! +u_ptr1 is now empty. +Hello from MyData! +MyData destructor + +--- Block 2 --- +MyData constructor +Inside process_ptr +Hello from MyData! +MyData destructor +Returned from process_ptr + +Program finished. +``` + +`unique_ptr` は、オブジェクトの所有者が誰であるかが明確な場合に最適です。基本的にはまず `unique_ptr` を使うことを検討しましょう。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/3-2-sharedptr.md b/public/docs/cpp/12-raii-smart-ptrs/3-2-sharedptr.md new file mode 100644 index 0000000..054c82c --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/3-2-sharedptr.md @@ -0,0 +1,82 @@ +--- +id: cpp-smartptr-sharedptr +title: 'std::shared_ptr' +level: 3 +--- + +### `std::shared_ptr` + +`std::shared_ptr` は、管理するオブジェクトの**所有権を複数のポインタで共有できる**スマートポインタです。 + + * **共有された所有権**: `shared_ptr` は自由にコピーできます。コピーされるたびに、内部の**参照カウンタ**が増加します。 + * **自動解放**: `shared_ptr` が破棄される(デストラクタが呼ばれる)と参照カウンタが減少し、**参照カウンタが0になったとき**に、管理しているオブジェクトが解放(`delete`)されます。 + * **オーバーヘッド**: 参照カウンタを管理するための追加のメモリと処理が必要なため、`unique_ptr` よりもわずかにオーバーヘッドが大きいです。 + +`shared_ptr` を作成するには、`std::make_shared` を使うのが効率的で安全です。 + +```cpp:shared_ptr_example.cpp +#include +#include +#include + +struct MyResource { + MyResource() { std::cout << "MyResource constructor" << std::endl; } + ~MyResource() { std::cout << "MyResource destructor" << std::endl; } +}; + +int main() { + std::shared_ptr s_ptr1; // 空のshared_ptr + + std::cout << "--- Block 1 ---" << std::endl; + { + // std::make_shared を使ってオブジェクトを生成し、shared_ptrで管理 + s_ptr1 = std::make_shared(); + std::cout << "Use count: " << s_ptr1.use_count() << std::endl; // 1 + + { + // s_ptr2 は s_ptr1 と同じオブジェクトを指す + std::shared_ptr s_ptr2 = s_ptr1; + std::cout << "Use count: " << s_ptr1.use_count() << std::endl; // 2 + std::cout << "Use count: " << s_ptr2.use_count() << std::endl; // 2 + } // s_ptr2がスコープを抜ける。参照カウンタが1に減る + + std::cout << "Use count after s_ptr2 is gone: " << s_ptr1.use_count() << std::endl; // 1 + } // s_ptr1がスコープを抜ける。参照カウンタが0になり、オブジェクトが破棄される + + std::cout << "\n--- Block 2 ---" << std::endl; + { + auto shared_res = std::make_shared(); + std::cout << "Initial use count: " << shared_res.use_count() << std::endl; // 1 + + std::vector> ptr_vec; + ptr_vec.push_back(shared_res); // コピー。参照カウンタは2 + ptr_vec.push_back(shared_res); // コピー。参照カウンタは3 + + std::cout << "Use count after pushing to vector: " << shared_res.use_count() << std::endl; // 3 + } // shared_resとptr_vecがスコープを抜ける。 + // 全てのshared_ptrが破棄され、最後に参照カウンタが0になり、オブジェクトが破棄される + + std::cout << "\nProgram finished." << std::endl; + return 0; +} +``` + +```cpp-exec:shared_ptr_example.cpp +--- Block 1 --- +MyResource constructor +Use count: 1 +Use count: 2 +Use count: 2 +Use count after s_ptr2 is gone: 1 +MyResource destructor + +--- Block 2 --- +MyResource constructor +Initial use count: 1 +Use count after pushing to vector: 3 +MyResource destructor + +Program finished. +``` + +`shared_ptr` は、オブジェクトの寿命が単一のスコープや所有者に縛られず、複数のオブジェクトから共有される必要がある場合に便利です。ただし、所有権の関係が複雑になりがちなので、本当に共有が必要な場面に限定して使いましょう。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/4-0-summary.md b/public/docs/cpp/12-raii-smart-ptrs/4-0-summary.md new file mode 100644 index 0000000..eca07ac --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/4-0-summary.md @@ -0,0 +1,16 @@ +--- +id: cpp-raii-smartptr-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **例外処理**は `try`, `catch`, `throw` を使い、エラーが発生してもプログラムを安全に継続させるための仕組みです。 + * 手動のメモリ管理下で例外が発生すると、**リソースリーク**を引き起こす危険があります。 + * **RAIIイディオム**は、リソースの確保をコンストラクタ、解放をデストラクタで行うことで、リソース管理を自動化するC++の重要な設計思想です。 + * **スマートポインタ**はRAIIを動的メモリ管理に適用したもので、`new` と `delete` の手動管理を不要にします。 + * **`std::unique_ptr`** はオブジェクトの**唯一の所有権**を管理します。軽量であり、所有権が明確な場合に第一の選択肢となります。 + * **`std::shared_ptr`** はオブジェクトの**所有権を共有**します。参照カウントによって管理され、最後の所有者がいなくなったときにオブジェクトを解放します。 + +モダンC++プログラミングでは、`new` と `delete` を直接書くことは極力避け、RAIIとスマートポインタを全面的に活用することが、安全でメンテナンス性の高いコードへの第一歩です。 diff --git a/public/docs/cpp/12-raii-smart-ptrs/4-1-practice1.md b/public/docs/cpp/12-raii-smart-ptrs/4-1-practice1.md new file mode 100644 index 0000000..c22797e --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/4-1-practice1.md @@ -0,0 +1,33 @@ +--- +id: cpp-raii-smartptr-practice1 +title: '練習問題1: unique_ptr と所有権の移動' +level: 3 +--- + +### 練習問題1: `unique_ptr` と所有権の移動 + +`Employee` という名前のクラスを作成してください。このクラスは、コンストラクタで社員名を受け取って表示し、デストラクタで「(社員名) is leaving.」というメッセージを表示します。 + +`main` 関数で、`"Alice"` という名前の `Employee` オブジェクトを `std::make_unique` で作成し、その `unique_ptr` を `promote_employee` という関数に渡してください。`promote_employee` 関数は `unique_ptr` を引数として受け取り(所有権が移動します)、「(社員名) has been promoted\!」というメッセージを表示します。 + +プログラムを実行し、コンストラクタとデストラクタのメッセージが期待通りに表示されることを確認してください。 + +```cpp:practice13_1.cpp +#include +#include +#include + +// ここにEmployeeクラスを定義 + + +int main() { + + +} +``` + +```cpp-exec:practice13_1.cpp +Employee Alice has joined the company. +Alice has been promoted! +Employee Alice is leaving. +``` diff --git a/public/docs/cpp/12-raii-smart-ptrs/4-2-practice2.md b/public/docs/cpp/12-raii-smart-ptrs/4-2-practice2.md new file mode 100644 index 0000000..6771c5c --- /dev/null +++ b/public/docs/cpp/12-raii-smart-ptrs/4-2-practice2.md @@ -0,0 +1,38 @@ +--- +id: cpp-raii-smartptr-practice2 +title: '問題2: shared_ptr と所有権の共有' +level: 3 +--- + +### 問題2: `shared_ptr` と所有権の共有 + +`Project` という名前のクラスを作成してください。コンストラクタでプロジェクト名を受け取り、デストラクタで「Project (プロジェクト名) is finished.」と表示します。 + +`main` 関数で、`"Project Phoenix"` という名前の `Project` オブジェクトを `std::make_shared` で作成してください。 +次に、`std::vector>` を作成し、作成した `shared_ptr` を2回 `push_back` してください。 +その後、`shared_ptr` の参照カウント (`use_count()`) を表示してください。 +最後に、`vector` を `clear()` して、再度参照カウントを表示してください。 +プログラムの実行が終了するときに `Project` のデストラクタが呼ばれることを確認してください。 + +```cpp:practice13_2.cpp +#include +#include +#include +#include + +// ここにProjectクラスを定義 + + +int main() { + + +} +``` + +```cpp-exec:practice13_2.cpp +Project Project Phoenix is started. +Initial use count: 1 +Use count after pushing to vector: 3 +Use count after clearing vector: 1 +Project Project Phoenix is finished. +``` diff --git a/public/docs/cpp/2-data-containers/-intro.md b/public/docs/cpp/2-data-containers/-intro.md new file mode 100644 index 0000000..eebfba3 --- /dev/null +++ b/public/docs/cpp/2-data-containers/-intro.md @@ -0,0 +1,3 @@ +他の言語(Python, JavaScript, C\#など)経験者がC++を学び始めるとき、最も躓きやすいのが「文字や配列の扱い」です。古いC言語の教科書では、ポインタ操作やメモリ管理が必須となる「Cスタイル」のやり方から入ることが多いのですが、**現代の実務的なC++(モダンC++)では、もっと安全で便利な「クラス(コンテナ)」を使います。** + +この章では、ポインタの複雑な話を抜きにして、他の高級言語と同じくらい直感的にデータを扱えるツールを紹介します。 diff --git a/public/docs/cpp/2-data-containers/1-0-string.md b/public/docs/cpp/2-data-containers/1-0-string.md new file mode 100644 index 0000000..affc17a --- /dev/null +++ b/public/docs/cpp/2-data-containers/1-0-string.md @@ -0,0 +1,59 @@ +--- +id: cpp-data-containers-string +title: '文字列の扱い:std::string' +level: 2 +--- + +## 文字列の扱い:`std::string` + +C言語では文字列を扱うために `char*` や `char[]` を使い、ヌル終端文字 `\0` を意識する必要がありました。これはバグの温床です。 +C++では、標準ライブラリの `std::string` クラスを使用します。これはPythonの `str` や Javaの `String` のように直感的に扱えます。 + +**主な機能** + + * **代入・初期化**: 文字列リテラルをそのまま代入可能。 + * **結合**: `+` 演算子で結合可能。 + * **比較**: `==`, `!=` などで中身の文字列比較が可能(C言語の `strcmp` は不要)。 + * **サイズ取得**: `.size()` または `.length()` メソッドを使用。 + + + +```cpp:string_demo.cpp +#include +#include // std::stringを使うために必要 + +int main() { + // 初期化 + std::string greeting = "Hello"; + std::string target = "World"; + + // 文字列の結合 + std::string message = greeting + ", " + target + "!"; + + // 出力 + std::cout << message << std::endl; + + // 長さの取得 + std::cout << "Length: " << message.size() << std::endl; // .length()でも同じ + + // 文字列の比較 + if (greeting == "Hello") { + std::cout << "Greeting matches 'Hello'." << std::endl; + } + + // 特定の文字へのアクセス(配列のようにアクセス可能) + message[0] = 'h'; // 先頭を小文字に変更 + std::cout << "Modified: " << message << std::endl; + + return 0; +} +``` + +```cpp-exec:string_demo.cpp +Hello, World! +Length: 13 +Greeting matches 'Hello'. +Modified: hello, World! +``` + +> **Note:** `std::string` は必要に応じて自動的にメモリを拡張します。プログラマがメモリ確保(malloc/free)を気にする必要はありません。 diff --git a/public/docs/cpp/2-data-containers/2-0-vector.md b/public/docs/cpp/2-data-containers/2-0-vector.md new file mode 100644 index 0000000..1b72c97 --- /dev/null +++ b/public/docs/cpp/2-data-containers/2-0-vector.md @@ -0,0 +1,59 @@ +--- +id: cpp-data-containers-vector +title: '可変長配列:std::vector' +level: 2 +--- + +## 可変長配列:`std::vector` + +「データの個数が事前にわからない」「途中でデータを追加したい」という場合、C++で最も頻繁に使われるのが `std::vector` です。これは「動的配列」や「可変長配列」と呼ばれ、Pythonの `list` や Javaの `ArrayList` に相当します。 + +**基本操作** + + * **宣言**: `std::vector<型> 変数名;` + * **追加**: `.push_back(値)` で末尾に追加。 + * **アクセス**: `変数名[インデックス]` または `.at(インデックス)`。 + * **サイズ**: `.size()`。 + + + +```cpp:vector_demo.cpp +#include +#include // std::vectorを使うために必要 + +int main() { + // 整数を格納するvector(初期サイズは0) + std::vector numbers; + + // データの追加 + numbers.push_back(10); + numbers.push_back(20); + numbers.push_back(30); + + // サイズの確認 + std::cout << "Size: " << numbers.size() << std::endl; + + // 要素へのアクセス + std::cout << "First element: " << numbers[0] << std::endl; + + // .at() を使うと範囲外アクセスの時に例外を投げてくれる(安全) + try { + std::cout << numbers.at(100) << std::endl; // 範囲外 + } catch (const std::out_of_range& e) { + std::cout << "Error: " << e.what() << std::endl; + } + + // 初期化リストを使った宣言(C++11以降) + std::vector prices = {10.5, 20.0, 33.3}; + std::cout << "Price list size: " << prices.size() << std::endl; + + return 0; +} +``` + +```cpp-exec:vector_demo.cpp +Size: 3 +First element: 10 +Error: vector::_M_range_check: __n (which is 100) >= this->size() (which is 3) +Price list size: 3 +``` diff --git a/public/docs/cpp/2-data-containers/3-0-array.md b/public/docs/cpp/2-data-containers/3-0-array.md new file mode 100644 index 0000000..33795d2 --- /dev/null +++ b/public/docs/cpp/2-data-containers/3-0-array.md @@ -0,0 +1,39 @@ +--- +id: cpp-data-containers-array +title: '固定長配列:std::array' +level: 2 +--- + +## 固定長配列:`std::array` + +データの個数が決まっている場合(例えば、3次元座標、RGB値、固定バッファなど)は、`std::vector` よりも `std::array` が適しています。 + +「なぜ昔ながらの `int arr[5];` を使わないの?」と思われるかもしれません。 +Cスタイルの配列は、他のコンテナ(vectorなど)と操作感が異なり、サイズ情報を自分で管理しなければならないなどの欠点があります。`std::array` はC配列のパフォーマンス(スタック確保)と、コンテナの利便性(`.size()`などが使える)を両立させたものです。 + +```cpp:array_demo.cpp +#include +#include // std::arrayを使うために必要 + +int main() { + // int型でサイズ3の配列を宣言・初期化 + // std::array<型, サイズ> + std::array coords = {10, 20, 30}; + + std::cout << "X: " << coords[0] << std::endl; + std::cout << "Y: " << coords[1] << std::endl; + std::cout << "Z: " << coords[2] << std::endl; + + // vectorと同じようにsize()が使える + std::cout << "Dimension: " << coords.size() << std::endl; + + return 0; +} +``` + +```cpp-exec:array_demo.cpp +X: 10 +Y: 20 +Z: 30 +Dimension: 3 +``` diff --git a/public/docs/cpp/2-data-containers/4-0-range-based-for.md b/public/docs/cpp/2-data-containers/4-0-range-based-for.md new file mode 100644 index 0000000..a2ab72b --- /dev/null +++ b/public/docs/cpp/2-data-containers/4-0-range-based-for.md @@ -0,0 +1,60 @@ +--- +id: cpp-data-containers-range-based-for +title: 範囲ベース for ループ (Range-based for) +level: 2 +--- + +## 範囲ベース `for` ループ (Range-based for) + +`std::vector` や `std::array` の中身を順番に処理する場合、インデックス `i` を使った `for (int i = 0; i < n; ++i)` は書くのが面倒ですし、境界外アクセスのリスクがあります。 + +モダンC++では、PythonやC\#の `foreach` に相当する **範囲ベース for ループ** が使えます。 + +```cpp +for (要素の型 変数名 : コンテナ) { + // 処理 +} +``` + +ここで便利なのが、**`auto` キーワード**です。`auto` を使うと、コンパイラが型を自動推論してくれるため、型名を詳しく書く必要がなくなります。 + +```cpp:range_for_demo.cpp +#include +#include +#include + +int main() { + std::vector inventory = {"Sword", "Shield", "Potion"}; + + std::cout << "--- Inventory List ---" << std::endl; + + // string item : inventory と書いても良いが、autoが楽 + for (auto item : inventory) { + std::cout << "- " << item << std::endl; + } + + // 数値の計算例 + std::vector scores = {80, 65, 90, 72}; + int total = 0; + + for (auto score : scores) { + total += score; + } + + std::cout << "Total Score: " << total << std::endl; + + return 0; +} +``` + +```cpp-exec:range_for_demo.cpp +--- Inventory List --- +- Sword +- Shield +- Potion +Total Score: 307 +``` + +> **Advanced Hint:** +> 上記の `auto item` は、要素を「コピー」して取り出します。`std::string` のような大きなデータを扱う場合、コピーコストを避けるために `const auto& item` (参照)を使うのが一般的ですが、これについては**第5章**で詳しく解説します。今の段階では「`auto` でループが回せる」と覚えておけば十分です。 + diff --git a/public/docs/cpp/2-data-containers/5-0-summary.md b/public/docs/cpp/2-data-containers/5-0-summary.md new file mode 100644 index 0000000..ef7a724 --- /dev/null +++ b/public/docs/cpp/2-data-containers/5-0-summary.md @@ -0,0 +1,14 @@ +--- +id: cpp-data-containers-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +1. **文字列**: `char*` ではなく `std::string` を使う。結合や比較が簡単で安全。 +2. **動的配列**: データの増減がある場合は `std::vector` を使う。`push_back()` で追加できる。 +3. **固定配列**: サイズ固定の場合は `std::array` を使う。Cスタイル配列のモダンな代替。 +4. **ループ**: コンテナの全要素走査には「範囲ベース for ループ」と `auto` を使うとシンプルに書ける。 + +これらの「標準ライブラリ(STL: Standard Template Library)」のコンテナを活用することで、メモリ管理の苦労を飛ばして、アプリケーションのロジックに集中できるようになります。 diff --git a/public/docs/cpp/2-data-containers/5-1-practice1.md b/public/docs/cpp/2-data-containers/5-1-practice1.md new file mode 100644 index 0000000..063792d --- /dev/null +++ b/public/docs/cpp/2-data-containers/5-1-practice1.md @@ -0,0 +1,32 @@ +--- +id: cpp-data-containers-practice1 +title: '練習問題1: 数値リストの統計' +level: 3 +--- + +### 練習問題1: 数値リストの統計 + +`std::vector` を使用して、好きな整数を5つほど格納してください(コード内で直接初期化して構いません)。 +その後、範囲ベース for ループを使用して、その数値の「合計」と「最大値」を求めて出力するプログラムを作成してください。 + +```cpp:practice3_1.cpp +#include +#include + +int main() { + // ここに整数リストを初期化してください + std::vector numbers = {12, 45, 7, 23, 89}; + + + // 結果を出力 + std::cout << "Sum: " << sum << std::endl; + std::cout << "Max Value: " << max_value << std::endl; + + return 0; +} +``` + +```cpp-exec:practice3_1.cpp +Sum: 176 +Max Value: 89 +``` diff --git a/public/docs/cpp/2-data-containers/5-2-practice2.md b/public/docs/cpp/2-data-containers/5-2-practice2.md new file mode 100644 index 0000000..02a8f6b --- /dev/null +++ b/public/docs/cpp/2-data-containers/5-2-practice2.md @@ -0,0 +1,33 @@ +--- +id: cpp-data-containers-practice2 +title: '練習問題2: 単語のフィルタリング' +level: 3 +--- + +### 練習問題2: 単語のフィルタリング + +以下の単語リスト `words` の中から、**文字数(長さ)が5文字より大きい単語だけ**を選んで表示するプログラムを作成してください。 +(ヒント:`std::string` の `.size()` または `.length()` メソッドと `if` 文を使用します) + +```cpp:practice3_2.cpp +#include +#include +#include + +int main() { + std::vector words = {"Apple", "Banana", "Cherry", "Date", "Elderberry"}; + + std::cout << "Words longer than 5 characters:" << std::endl; + + // ここにコードを書く + + return 0; +} +``` + +```cpp-exec:practice3_2.cpp +Words longer than 5 characters: +Banana +Cherry +Elderberry +``` diff --git a/public/docs/cpp/3-pointers/-intro.md b/public/docs/cpp/3-pointers/-intro.md new file mode 100644 index 0000000..00082db --- /dev/null +++ b/public/docs/cpp/3-pointers/-intro.md @@ -0,0 +1,6 @@ +ようこそ、C++学習の最大の山場へ。 +第3章までは、`std::vector`や`std::string`といった便利な機能を使ってきましたが、今回はその「裏側」で何が起きているかを覗き込みます。 + +他の言語(Java, Python, C\#など)では言語機能やガベージコレクション(GC)が隠蔽してくれている「メモリ」という物理的なリソースを、C++では直接操作することができます。これがC++の強力な武器であり、同時にバグの温床でもあります。 + +ここを理解すれば、第3章の機能がいかに偉大だったか、そしてコンピュータが実際にどう動いているかが手に取るようにわかるようになります。 diff --git a/public/docs/cpp/3-pointers/1-0-basic.md b/public/docs/cpp/3-pointers/1-0-basic.md new file mode 100644 index 0000000..4de1675 --- /dev/null +++ b/public/docs/cpp/3-pointers/1-0-basic.md @@ -0,0 +1,13 @@ +--- +id: cpp-pointers-basic +title: ポインタの基礎 +level: 2 +--- + +## ポインタの基礎 + +ポインタとは、変数の「値」ではなく、その変数がメモリ上の**どこにあるか(アドレス)**を格納する変数です。 + +変数の型に応じて、対応するポインタの型が存在します。例えば、`int`型の変数のアドレスを格納するなら `int*` 型、`double`型の変数のアドレスを格納するなら `double*` 型のポインタを使います。アスタリスク `*` がポインタ型であることを示します。 + +> ポインタ変数の宣言時に `*` を型の横に付けるか、変数名の横に付けるかは好みが分かれますが、意味は同じです (`int* p;` と `int *p;` は等価)。このチュートリアルでは `int* p;` のように型の側に付けます。 diff --git a/public/docs/cpp/3-pointers/1-1-address-operator.md b/public/docs/cpp/3-pointers/1-1-address-operator.md new file mode 100644 index 0000000..8f3b3d6 --- /dev/null +++ b/public/docs/cpp/3-pointers/1-1-address-operator.md @@ -0,0 +1,46 @@ +--- +id: cpp-pointers-address-operator +title: アドレスと間接参照 +level: 3 +--- + +### アドレスと間接参照 + + * **アドレス演算子 `&`**: 変数のメモリ上の住所(アドレス)を取得します。 + * **間接参照演算子 `*`**: ポインタが指し示している住所に行き、その中身(値)にアクセスします。 + + + +```cpp:basic_pointer.cpp +#include + +int main() { + int number = 42; + // numberのアドレスを取得して ptr に格納 + // int* は「int型へのポインタ」という意味 + int* ptr = &number; + + std::cout << "numberの値: " << number << std::endl; + std::cout << "numberのアドレス (&number): " << &number << std::endl; + std::cout << "ptrの値 (アドレス): " << ptr << std::endl; + + // アドレスの中身を見る(間接参照) + std::cout << "ptrが指す中身 (*ptr): " << *ptr << std::endl; + + // ポインタ経由で値を書き換える + *ptr = 100; + std::cout << "書き換え後のnumber: " << number << std::endl; + + return 0; +} +``` + +```cpp-exec:basic_pointer.cpp +numberの値: 42 +numberのアドレス (&number): 0x7ffedffe3adc +ptrの値 (アドレス): 0x7ffedffe3adc +ptrが指す中身 (*ptr): 42 +書き換え後のnumber: 100 +``` + +※ アドレス(0x...)は実行環境ごとに異なります。 diff --git a/public/docs/cpp/3-pointers/1-2-nullptr.md b/public/docs/cpp/3-pointers/1-2-nullptr.md new file mode 100644 index 0000000..3ce839e --- /dev/null +++ b/public/docs/cpp/3-pointers/1-2-nullptr.md @@ -0,0 +1,42 @@ +--- +id: cpp-pointers-nullptr +title: nullptr の使用 +level: 3 +--- + +### `nullptr` の使用 + +ポインタが「どこも指していない」ことを示したい場合、C++では `nullptr` を使用します。 +古いC++やC言語では `NULL` や `0` が使われていましたが、モダンC++では型安全な `nullptr` を使うのが鉄則です。初期化されていないポインタは不定な場所を指すため、必ず初期化しましょう。 + +```cpp:pointer_declaration.cpp +#include + +int main() { + // ポインタの宣言 + // 初期化していないポインタは不定なアドレスを指す可能性がある。 + int* p; + std::cout << "p の初期値(アドレス): " << p << std::endl; + + // *p = 10; // 【危険】未初期化ポインタの間接参照は未定義動作 + + // どの変数も指していないことを示す特別な値 nullptr + // ポインタを初期化する際は nullptr を使うのが安全です + p = nullptr; + std::cout << "p の値(アドレス): " << p << std::endl; + + if (p == nullptr) { + std::cout << "p は何も指していません。" << std::endl; + } + + // *p = 10; // 【危険】nullptrはどこも指していないので、やっぱり未定義動作 + + return 0; +} +``` + +```cpp-exec:pointer_declaration.cpp +p の初期値(アドレス): 0x7ffedffe3ab8 +p の値(アドレス): 0 +p は何も指していません。 +``` diff --git a/public/docs/cpp/3-pointers/2-0-array.md b/public/docs/cpp/3-pointers/2-0-array.md new file mode 100644 index 0000000..748256a --- /dev/null +++ b/public/docs/cpp/3-pointers/2-0-array.md @@ -0,0 +1,43 @@ +--- +id: cpp-pointers-array +title: 配列とポインタの関係 +level: 2 +--- + +## 配列とポインタの関係 + +第3章では `std::vector` を使いましたが、C++にはC言語互換の「生の配列(Cスタイル配列)」も存在します。これはサイズが固定で、機能が制限されています。 + +* **配列名の減衰(Decay):** 配列の名前は式の中で使うと、**「先頭要素へのポインタ」**として扱われます。これを「減衰(Decay)」と呼びます。 +* **ポインタ演算:** ポインタに対して数値を足し引きすると、**「その型のサイズ分」**だけアドレスが移動します。`int`(通常4バイト)のポインタに `+1` すると、メモリアドレスは4増えます。 + +```cpp:array_decay.cpp +#include + +int main() { + // Cスタイル配列の宣言(サイズ固定) + int primes[] = {2, 3, 5, 7}; + + // 配列名 primes は &primes[0] とほぼ同じ意味になる + int* ptr = primes; + + std::cout << "先頭要素 (*ptr): " << *ptr << std::endl; + + // ポインタ演算 + // ptr + 1 は次のint要素(メモリ上で4バイト隣)を指す + std::cout << "2番目の要素 (*(ptr + 1)): " << *(ptr + 1) << std::endl; + + // 配列添字アクセス primes[2] は、実は *(primes + 2) のシンタックスシュガー + std::cout << "3番目の要素 (primes[2]): " << primes[2] << std::endl; + std::cout << "3番目の要素 (*(primes + 2)): " << *(primes + 2) << std::endl; + + return 0; +} +``` + +```cpp-exec:array_decay.cpp +先頭要素 (*ptr): 2 +2番目の要素 (*(ptr + 1)): 3 +3番目の要素 (primes[2]): 5 +3番目の要素 (*(primes + 2)): 5 +``` diff --git a/public/docs/cpp/3-pointers/3-0-legacy-string.md b/public/docs/cpp/3-pointers/3-0-legacy-string.md new file mode 100644 index 0000000..6bc86f0 --- /dev/null +++ b/public/docs/cpp/3-pointers/3-0-legacy-string.md @@ -0,0 +1,43 @@ +--- +id: cpp-pointers-legacy-string +title: 文字列リテラルと char* +level: 2 +--- + +## 文字列リテラルと `char*` + +`std::string` が登場する前、文字列は単なる `char` 型の配列でした。これを「Cスタイル文字列」と呼びます。 +現在でも、ライブラリとの連携などで頻繁に目にします。 + +Cスタイル文字列は、文字の並びの最後に「終端文字 `\0`(ヌル文字)」を置くことで終わりを表します。 + +```cpp:legacy_string.cpp +#include +#include + +int main() { + // 文字列リテラルは const char 配列 + const char* c_str = "Hello"; + + // std::string から Cスタイル文字列への変換 + std::string cpp_str = "World"; + const char* converted = cpp_str.c_str(); // .c_str() を使う + + std::cout << "C-Style: " << c_str << std::endl; + std::cout << "C++ String: " << cpp_str << std::endl; + std::cout << "Converted to C-Style: " << converted << std::endl; + + // 注意: c_str は配列なのでサイズ情報を持っていない + // 終端文字 '\0' まで読み進める必要がある + + return 0; +} +``` + +```cpp-exec:legacy_string.cpp +C-Style: Hello +C++ String: World +Converted to C-Style: World +``` + +**重要:** モダンC++では基本的に `std::string` を使いましょう。`char*` は参照用やAPI互換のために使います。 diff --git a/public/docs/cpp/3-pointers/4-0-memory-area.md b/public/docs/cpp/3-pointers/4-0-memory-area.md new file mode 100644 index 0000000..292b552 --- /dev/null +++ b/public/docs/cpp/3-pointers/4-0-memory-area.md @@ -0,0 +1,9 @@ +--- +id: cpp-pointers-memory-area +title: 動的なメモリ確保 +level: 2 +--- + +## 動的なメモリ確保 + +ここがメモリ管理の核心です。プログラムが使うメモリ領域には大きく分けて「スタック」と「ヒープ」があります。 diff --git a/public/docs/cpp/3-pointers/4-1-stack.md b/public/docs/cpp/3-pointers/4-1-stack.md new file mode 100644 index 0000000..143663c --- /dev/null +++ b/public/docs/cpp/3-pointers/4-1-stack.md @@ -0,0 +1,11 @@ +--- +id: cpp-pointers-stack +title: スタック (Stack) +level: 3 +--- + +### スタック (Stack) + + * これまでの変数は主にここに置かれます。 + * 関数のスコープ `{ ... }` を抜けると**自動的に消滅**します。 + * 管理が楽で高速ですが、サイズに制限があります。 diff --git a/public/docs/cpp/3-pointers/4-2-heap.md b/public/docs/cpp/3-pointers/4-2-heap.md new file mode 100644 index 0000000..1230f83 --- /dev/null +++ b/public/docs/cpp/3-pointers/4-2-heap.md @@ -0,0 +1,50 @@ +--- +id: cpp-pointers-heap +title: ヒープ (Heap) 領域と new, delete +level: 3 +--- + +### ヒープ (Heap) 領域と `new`, `delete` + + * プログラマが**手動で確保・解放**する領域です。 + * 広大なサイズを使えますが、管理を怠ると危険です。 + +ヒープ領域を使うには `new` 演算子を使用し、使い終わったら必ず `delete` 演算子でメモリをOSに返却(解放)する必要があります。 + +```cpp:heap_memory.cpp +#include + +int main() { + // ヒープ上に整数を1つ確保 + int* pInt = new int(10); + + // ヒープ上に配列を確保 (サイズ100) + // std::vectorを使わない場合、サイズは動的に決められるが管理は手動 + int size = 5; + int* pArray = new int[size]; + + // 配列への書き込み + for(int i = 0; i < size; ++i) { + pArray[i] = i * 10; + } + + std::cout << "ヒープ上の値: " << *pInt << std::endl; + std::cout << "ヒープ上の配列[2]: " << pArray[2] << std::endl; + + // 【重要】使い終わったら必ず解放する! + delete pInt; // 単体の解放 + delete[] pArray; // 配列の解放 (delete[] を使うこと) + + // 解放後のアドレスには触ってはいけない(ダングリングポインタ) + // 安全のため nullptr にしておく + pInt = nullptr; + pArray = nullptr; + + return 0; +} +``` + +```cpp-exec:heap_memory.cpp +ヒープ上の値: 10 +ヒープ上の配列[2]: 20 +``` diff --git a/public/docs/cpp/3-pointers/4-4-memory-leak.md b/public/docs/cpp/3-pointers/4-4-memory-leak.md new file mode 100644 index 0000000..8d86e2e --- /dev/null +++ b/public/docs/cpp/3-pointers/4-4-memory-leak.md @@ -0,0 +1,17 @@ +--- +id: cpp-pointers-memory-leak +title: 恐怖の「メモリリーク」 +level: 3 +--- + +### 恐怖の「メモリリーク」 + +もし `delete` を忘れるとどうなるでしょう? +確保されたメモリは、プログラムが終了するまで「使用中」のまま残ります。これを**メモリリーク**と呼びます。長時間動くサーバーなどでこれが起きると、メモリを食いつぶしてシステムがクラッシュします。 + +**第3章の振り返り:** +`std::vector` や `std::string` は、内部で `new` と `delete` を自動的に行ってくれています。 + + * 作成時に `new` で確保。 + * スコープを抜けるときに自動で `delete`(デストラクタ)。 + これがC++のクラスの強力な機能(RAII)です。生の `new/delete` を直接使うことは、モダンC++では「最後の手段」あるいは「ライブラリを作る側の仕事」と考えられています。 diff --git a/public/docs/cpp/3-pointers/5-0-summary.md b/public/docs/cpp/3-pointers/5-0-summary.md new file mode 100644 index 0000000..3111d8b --- /dev/null +++ b/public/docs/cpp/3-pointers/5-0-summary.md @@ -0,0 +1,14 @@ +--- +id: cpp-pointers-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +1. **ポインタ**はメモリアドレスを保持する変数。`&`で取得、`*`でアクセス。 +2. ポインタの初期化には `nullptr` を使う。 +3. **配列名**は先頭要素へのポインタとして振る舞う(減衰)。 +4. `ptr + i` は、`ptr` の指す型 `i` 個分先のアドレスを指す。 +5. **ヒープメモリ**は `new` で確保し、必ず `delete` で解放する。 +6. `delete` を忘れると**メモリリーク**になる。これを防ぐために `std::vector` などのコンテナクラスが存在する。 diff --git a/public/docs/cpp/3-pointers/5-1-practice1.md b/public/docs/cpp/3-pointers/5-1-practice1.md new file mode 100644 index 0000000..feaa4e5 --- /dev/null +++ b/public/docs/cpp/3-pointers/5-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: cpp-pointers-practice1 +title: '練習問題1: ポインタによる配列操作' +level: 3 +--- + +### 練習問題1: ポインタによる配列操作 + +`int` 型のCスタイル配列 `arr` について、 `int*` 型のポインタを使って走査し、**すべての値を2倍に書き換えてください**(`[]` 演算子は使わず、ポインタ演算 `*` と `++` または `+` を使用すること)。 + +```cpp:practice4_1.cpp +#include + +int main() { + int arr[] = {10, 20, 30, 40, 50}; + + // ここにコードを書く + + + std::cout << "配列の値を2倍にしました: "; + for (int i = 0; i < 5; ++i) { + std::cout << arr[i] << " "; + } + return 0; +} +``` + +```cpp-exec:practice4_1.cpp +配列の値を2倍にしました: 60 80 100 120 140 +``` diff --git a/public/docs/cpp/3-pointers/5-2-practice2.md b/public/docs/cpp/3-pointers/5-2-practice2.md new file mode 100644 index 0000000..236e75a --- /dev/null +++ b/public/docs/cpp/3-pointers/5-2-practice2.md @@ -0,0 +1,28 @@ +--- +id: cpp-pointers-practice2 +title: 問題2:手動メモリ管理の体験 +level: 3 +--- + +### 問題2:手動メモリ管理の体験 + +`n` 個の整数を格納できる配列を**ヒープ領域(`new`)**に確保してください。 +その配列に 0 から `n-1` までの数値を代入し、合計値を計算して表示してください。 +最後に、確保したメモリを適切に解放してください。 + +```cpp:practice4_2.cpp +#include + +int main() { + const int n = 5; + + // ここにコードを書く + + + return 0; +} +``` + +```cpp-exec:practice4_2.cpp +配列の合計値は: 10 +``` diff --git a/public/docs/cpp/4-functions/-intro.md b/public/docs/cpp/4-functions/-intro.md new file mode 100644 index 0000000..580dbf6 --- /dev/null +++ b/public/docs/cpp/4-functions/-intro.md @@ -0,0 +1,3 @@ +前章(第4章)では、C++のメモリモデルの核心である「ポインタ」について学びました。ポインタは強力ですが、構文が複雑になりがちで、バグの温床にもなりえます。 + +C++では、C言語から受け継いだポインタに加え、より安全で直感的な**「参照(Reference)」**という概念が導入されています。本章では、関数の設計を通して、この「参照」がいかに強力な武器になるかを学びます。「データをどう渡すか」は、C++のパフォーマンスと設計の良し悪しを決める最も重要な要素の一つです。 diff --git a/public/docs/cpp/4-functions/1-0-basic.md b/public/docs/cpp/4-functions/1-0-basic.md new file mode 100644 index 0000000..ae819d4 --- /dev/null +++ b/public/docs/cpp/4-functions/1-0-basic.md @@ -0,0 +1,9 @@ +--- +id: cpp-functions-basic +title: 関数の宣言と定義 +level: 2 +--- + +## 関数の宣言と定義 + +PythonやJavaScriptのような言語では、関数をどこに書いても(あるいは実行時に解決されて)呼び出せることが多いですが、C++のコンパイラはコードを上から下へと一直線に読みます。そのため、**「使用する前に、その関数が存在すること」**をコンパイラに知らせる必要があります。 diff --git a/public/docs/cpp/4-functions/1-1-prototype-declaration.md b/public/docs/cpp/4-functions/1-1-prototype-declaration.md new file mode 100644 index 0000000..67eadab --- /dev/null +++ b/public/docs/cpp/4-functions/1-1-prototype-declaration.md @@ -0,0 +1,43 @@ +--- +id: cpp-functions-prototype-declaration +title: プロトタイプ宣言 +level: 3 +--- + +### プロトタイプ宣言 + +関数を `main` 関数の後に定義したい場合、事前に「こういう名前と引数の関数がありますよ」と宣言だけしておく必要があります。これを**プロトタイプ宣言**と呼びます。 + +```cpp:declaration_intro.cpp +#include + +// プロトタイプ宣言 +// 戻り値の型 関数名(引数の型1 引数名1, 引数の型2 引数名2, ...); +// 本体({}の中身)は書かず、セミコロンで終わる +void greet(int times); + +int main() { + std::cout << "main関数開始" << std::endl; + + // 定義は下にあるが、宣言があるので呼び出せる + greet(3); + + return 0; +} + +// 関数の定義 +void greet(int times) { + for (int i = 0; i < times; ++i) { + std::cout << "Hello C++!" << std::endl; + } +} +``` + +```cpp-exec:declaration_intro.cpp +main関数開始 +Hello C++! +Hello C++! +Hello C++! +``` + +実際の開発では、プロトタイプ宣言をヘッダーファイル(`.h`)に書き、定義をソースファイル(`.cpp`)に書くことで、大規模なプログラムを管理します(これについては次章で詳しく解説します)。 diff --git a/public/docs/cpp/4-functions/1-2-void.md b/public/docs/cpp/4-functions/1-2-void.md new file mode 100644 index 0000000..cf31514 --- /dev/null +++ b/public/docs/cpp/4-functions/1-2-void.md @@ -0,0 +1,15 @@ +--- +id: cpp-functions-void +title: '戻り値がない場合: void型' +level: 3 +--- + +### 戻り値がない場合: `void`型 + +関数が何も値を返す必要がない場合もあります。例えば、「画面にメッセージを表示するだけ」といった関数です。その場合、戻り値の型として `void` という特別なキーワードを使います。 + +```cpp +void printMessage(std::string message); +``` + +第2章で学んだように、`int`や`double`などの型は変数を定義するために使えましたが、`void`は「型がない」ことを示す特殊な型なので、`void my_variable;` のように変数を定義することはできません。あくまで関数の戻り値の型としてのみ使います。 diff --git a/public/docs/cpp/4-functions/2-0-argument.md b/public/docs/cpp/4-functions/2-0-argument.md new file mode 100644 index 0000000..201c8b6 --- /dev/null +++ b/public/docs/cpp/4-functions/2-0-argument.md @@ -0,0 +1,9 @@ +--- +id: cpp-functions-argument +title: 引数の渡し方(パフォーマンスと安全性) +level: 2 +--- + +## 引数の渡し方(パフォーマンスと安全性) + +ここが本章のハイライトです。他の言語では言語仕様として決まっていることが多い引数の渡し方を、C++ではプログラマが意図的に選択できます。 diff --git a/public/docs/cpp/4-functions/2-1-arg-by-value.md b/public/docs/cpp/4-functions/2-1-arg-by-value.md new file mode 100644 index 0000000..ad63458 --- /dev/null +++ b/public/docs/cpp/4-functions/2-1-arg-by-value.md @@ -0,0 +1,41 @@ +--- +id: cpp-functions-arg-by-value +title: 1. 値渡し (Pass by Value) +level: 3 +--- + +### 1\. 値渡し (Pass by Value) + +特に何も指定しない場合のデフォルトです。変数の**コピー**が作成され、関数に渡されます。 + + * **メリット:** 安全。関数内で値を変更しても、呼び出し元の変数には影響しない。 + * **デメリット:** コストが高い。巨大な配列やオブジェクトを渡す際、丸ごとコピーするためメモリと時間を浪費する。 + + + +```cpp:pass_by_value.cpp +#include + +// 値渡し:xは呼び出し元のコピー +void attemptUpdate(int x) { + x = 100; // コピーを変更しているだけ + std::cout << "関数内: " << x << " (アドレス: " << &x << ")" << std::endl; +} + +int main() { + int num = 10; + std::cout << "呼び出し前: " << num << " (アドレス: " << &num << ")" << std::endl; + + attemptUpdate(num); + + // numは変わっていない + std::cout << "呼び出し後: " << num << std::endl; + return 0; +} +``` + +```cpp-exec:pass_by_value.cpp +呼び出し前: 10 (アドレス: 0x7ff...) +関数内: 100 (アドレス: 0x7ff...) <-- アドレスが違う=別の領域(コピー) +呼び出し後: 10 +``` diff --git a/public/docs/cpp/4-functions/2-2-arg-by-pointer.md b/public/docs/cpp/4-functions/2-2-arg-by-pointer.md new file mode 100644 index 0000000..b1c8589 --- /dev/null +++ b/public/docs/cpp/4-functions/2-2-arg-by-pointer.md @@ -0,0 +1,39 @@ +--- +id: cpp-functions-arg-by-pointer +title: 2. ポインタ渡し (Pass by Pointer) +level: 3 +--- + +### 2\. ポインタ渡し (Pass by Pointer) + +C言語からある手法です。第4章で学んだポインタ(アドレス)を渡します。 + + * **メリット:** コピーが発生しない(アドレス値のコピーのみ)。呼び出し元のデータを変更できる。 + * **デメリット:** 呼び出す際に `&` を付ける必要がある。関数内で `*` や `->` を使う必要があり、構文が汚れる。`nullptr` チェックが必要になることがある。 + + + +```cpp:pass_by_pointer.cpp +#include + +// ポインタ渡し:アドレスを受け取る +void updateByPointer(int* ptr) { + if (ptr != nullptr) { + *ptr = 200; // アドレスの指す先を書き換える + } +} + +int main() { + int num = 10; + + // アドレスを渡す + updateByPointer(&num); + + std::cout << "ポインタ渡し後: " << num << std::endl; + return 0; +} +``` + +```cpp-exec:pass_by_pointer.cpp +ポインタ渡し後: 200 +``` diff --git a/public/docs/cpp/4-functions/2-3-arg-by-reference.md b/public/docs/cpp/4-functions/2-3-arg-by-reference.md new file mode 100644 index 0000000..4ec7870 --- /dev/null +++ b/public/docs/cpp/4-functions/2-3-arg-by-reference.md @@ -0,0 +1,38 @@ +--- +id: cpp-functions-arg-by-reference +title: 3. 参照渡し (Pass by Reference) +level: 3 +--- + +### 3\. 参照渡し (Pass by Reference) + +C++の真骨頂です。**「参照(Reference)」**とは、既存の変数に別の名前(エイリアス)をつける機能です。引数の型に `&` を付けるだけで宣言できます。 + + * **メリット:** コピーが発生しない。**構文は「値渡し」と同じように書ける**(`*`や`&`を呼び出し側で意識しなくていい)。`nullptr` になることがないため安全性が高い。 + * **デメリット:** 関数内で値を変更すると、呼び出し元も変わる(意図しない変更に注意)。 + + + +```cpp:pass_by_ref.cpp +#include + +// 参照渡し:引数に & をつける +// ref は呼び出し元の変数の「別名」となる +void updateByRef(int& ref) { + ref = 300; // 普通の変数のように扱えるが、実体は呼び出し元 +} + +int main() { + int num = 10; + + // 値渡しと同じように呼び出せる(&num と書かなくていい!) + updateByRef(num); + + std::cout << "参照渡し後: " << num << std::endl; + return 0; +} +``` + +```cpp-exec:pass_by_ref.cpp +参照渡し後: 300 +``` diff --git a/public/docs/cpp/4-functions/2-4-arg-by-const-reference.md b/public/docs/cpp/4-functions/2-4-arg-by-const-reference.md new file mode 100644 index 0000000..ff95352 --- /dev/null +++ b/public/docs/cpp/4-functions/2-4-arg-by-const-reference.md @@ -0,0 +1,46 @@ +--- +id: cpp-functions-arg-by-const-reference +title: 4. const 参照渡し (Pass by const Reference) +level: 3 +--- + +### 4\. const 参照渡し (Pass by const Reference) + +これが**C++で最も頻繁に使われるパターン**です。「コピーはしたくない(重いから)。でも、関数内で書き換えられたくもない」という要求を満たします。 + + * **構文:** `const 型& 引数名` + * **用途:** `std::string`、`std::vector`、クラスのオブジェクトなど、サイズが大きくなる可能性があるデータ。 + + + +```cpp:const_ref.cpp +#include +#include +#include + +// const参照渡し +// textの実体はコピーされないが、書き換えも禁止される +void printMessage(const std::string& text) { + // text = "Modified"; // コンパイルエラーになる + std::cout << "Message: " << text << std::endl; +} + +int main() { + std::string bigData = "This is a potentially very large string..."; + + // コピーコストゼロで渡す + printMessage(bigData); + + return 0; +} +``` + +```cpp-exec:const_ref.cpp +Message: This is a potentially very large string... +``` + +> **ガイドライン:** +> +> * `int` や `double` などの基本型 → **値渡し** でOK。 +> * 変更させたいデータ → **参照渡し** (`T&`)。 +> * 変更しないがサイズが大きいデータ(string, vectorなど) → **const参照渡し** (`const T&`)。 diff --git a/public/docs/cpp/4-functions/3-0-features.md b/public/docs/cpp/4-functions/3-0-features.md new file mode 100644 index 0000000..dd6de26 --- /dev/null +++ b/public/docs/cpp/4-functions/3-0-features.md @@ -0,0 +1,9 @@ +--- +id: cpp-functions-features +title: 関数の機能拡張 +level: 2 +--- + +## 関数の機能拡張 + +C++には関数をより柔軟に使うための機能が備わっています。 diff --git a/public/docs/cpp/4-functions/3-1-overload.md b/public/docs/cpp/4-functions/3-1-overload.md new file mode 100644 index 0000000..145b278 --- /dev/null +++ b/public/docs/cpp/4-functions/3-1-overload.md @@ -0,0 +1,42 @@ +--- +id: cpp-functions-overload +title: オーバーロード (Function Overloading) +level: 3 +--- + +### オーバーロード (Function Overloading) + +引数の**型**や**数**が異なれば、同じ名前の関数を複数定義できます。C言語では関数名はユニークである必要がありましたが、C++では「名前+引数リスト」で区別されます。 + +```cpp:overloading.cpp +#include +#include + +// int型を受け取る関数 +void print(int i) { + std::cout << "Integer: " << i << std::endl; +} + +// double型を受け取る関数(同名) +void print(double d) { + std::cout << "Double: " << d << std::endl; +} + +// 文字列を受け取る関数(同名) +void print(const std::string& s) { + std::cout << "String: " << s << std::endl; +} + +int main() { + print(42); + print(3.14); + print("Overloading"); + return 0; +} +``` + +```cpp-exec:overloading.cpp +Integer: 42 +Double: 3.14 +String: Overloading +``` diff --git a/public/docs/cpp/4-functions/3-2-default-arg.md b/public/docs/cpp/4-functions/3-2-default-arg.md new file mode 100644 index 0000000..4f1612f --- /dev/null +++ b/public/docs/cpp/4-functions/3-2-default-arg.md @@ -0,0 +1,42 @@ +--- +id: cpp-functions-default-arg +title: デフォルト引数 +level: 3 +--- + +### デフォルト引数 + +引数が省略された場合に使われるデフォルト値を設定できます。これはプロトタイプ宣言(または最初にコンパイラが見る定義)に記述します。 +※デフォルト引数は**後ろの引数から順に**設定する必要があります。 + +```cpp:default_args.cpp +#include + +// power: 指数を省略すると2乗になる +// verbose: 詳細出力を省略するとfalseになる +int power(int base, int exponent = 2, bool verbose = false) { + int result = 1; + for (int i = 0; i < exponent; ++i) { + result *= base; + } + + if (verbose) { + std::cout << base << " の " << exponent << " 乗を計算しました。" << std::endl; + } + return result; +} + +int main() { + std::cout << power(3) << std::endl; // 3^2, verbose=false + std::cout << power(3, 3) << std::endl; // 3^3, verbose=false + std::cout << power(2, 4, true) << std::endl; // 2^4, verbose=true + return 0; +} +``` + +```cpp-exec:default_args.cpp +9 +27 +2 の 4 乗を計算しました。 +16 +``` diff --git a/public/docs/cpp/4-functions/4-0-summary.md b/public/docs/cpp/4-functions/4-0-summary.md new file mode 100644 index 0000000..988c0a8 --- /dev/null +++ b/public/docs/cpp/4-functions/4-0-summary.md @@ -0,0 +1,14 @@ +--- +id: cpp-functions-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **プロトタイプ宣言**を使うことで、関数の定義順序に依存せずに記述できる。 + * **値渡し**は安全だが、大きなオブジェクトではコピーコストがかかる。 + * **参照渡し (`&`)** は、ポインタのような効率性を持ちながら、変数のエイリアスとして直感的に扱える。 + * **`const` 参照渡し (`const T&`)** は、大きなデータを「読み取り専用」で効率的に渡すC++の定石である。 + * **オーバーロード**により、同じ名前で異なる引数を受け取る関数を作れる。 + * **デフォルト引数**で、呼び出し時の記述を省略できる。 diff --git a/public/docs/cpp/4-functions/4-1-practice1.md b/public/docs/cpp/4-functions/4-1-practice1.md new file mode 100644 index 0000000..92e97ef --- /dev/null +++ b/public/docs/cpp/4-functions/4-1-practice1.md @@ -0,0 +1,33 @@ +--- +id: cpp-functions-practice1 +title: '練習問題1: 値の入れ替え(Swap)' +level: 2 +--- + +### 練習問題1: 値の入れ替え(Swap) + +2つの `int` 変数を受け取り、その値を入れ替える関数 `mySwap` を作成してください。 +ポインタではなく、**参照渡し**を使用してください。 + +```cpp:practice5_1.cpp +#include + +// ここにmySwap関数を実装してください + + +// main関数 +int main() { + int a = 10; + int b = 20; + std::cout << "Before: a = " << a << ", b = " << b << std::endl; + mySwap(a, b); + std::cout << "After: a = " << a << ", b = " << b << std::endl; + return 0; +} +``` + +```cpp-exec:practice5_1.cpp +(期待される実行結果) +Before: a = 10, b = 20 +After: a = 20, b = 10 +``` diff --git a/public/docs/cpp/4-functions/4-2-practice2.md b/public/docs/cpp/4-functions/4-2-practice2.md new file mode 100644 index 0000000..0ee2fd0 --- /dev/null +++ b/public/docs/cpp/4-functions/4-2-practice2.md @@ -0,0 +1,34 @@ +--- +id: cpp-functions-practice2 +title: 問題2:ベクター統計 +level: 3 +--- + +### 問題2:ベクター統計 + +`std::vector` を受け取り、その中の「最大値」を見つけて返す関数 `findMax` を作成してください。 +ただし、以下の条件を守ってください。 + +1. ベクターはコピーされないようにしてください(**参照渡し**)。 +2. 関数内でベクターの内容が変更されないことを保証してください(**const**)。 +3. ベクターが空の場合は `0` を返すなどの処理を入れてください。 + + + +```cpp:practice5_2.cpp +#include +#include +#include // maxを使うなら便利ですが、for文でも可 + +// ここに findMax を作成 + + +int main() { + std::vector data = {10, 5, 8, 42, 3}; + std::cout << "Max: " << findMax(data) << std::endl; + return 0; +} +``` +```cpp-exec:practice5_2.cpp +Max: 42 +``` diff --git a/public/docs/cpp/5-project-build/-intro.md b/public/docs/cpp/5-project-build/-intro.md new file mode 100644 index 0000000..dd9302a --- /dev/null +++ b/public/docs/cpp/5-project-build/-intro.md @@ -0,0 +1,3 @@ +これまでの章では、すべてのコードを1つの `.cpp` ファイルに記述してきました。しかし、プログラムが大規模で複雑になるにつれて、このアプローチは現実的ではなくなります。コードの可読性が低下し、少しの変更でもプログラム全体の再コンパイルが必要になり、開発効率が大きく損なわれるからです。 + +この章では、プログラムを複数のファイルに分割し、それらを効率的に管理・ビルドする方法を学びます。これは、小さなプログラムから一歩進み、本格的なソフトウェア開発を行うための重要なステップです。 diff --git a/public/docs/cpp/5-project-build/1-0-header-and-source.md b/public/docs/cpp/5-project-build/1-0-header-and-source.md new file mode 100644 index 0000000..30458b9 --- /dev/null +++ b/public/docs/cpp/5-project-build/1-0-header-and-source.md @@ -0,0 +1,18 @@ +--- +id: cpp-project-build-header-and-source +title: ヘッダファイルとソースファイル +level: 2 +--- + +## ヘッダファイルとソースファイル + +C++では、プログラムを**ヘッダファイル**と**ソースファイル**という2種類のファイルに分割するのが一般的です。 + + * **ヘッダファイル (`.h` または `.hpp`)**: 「宣言」を置く場所です。クラスの定義、関数のプロトタイプ宣言、定数、テンプレートなどを記述します。他のファイルに対して「何ができるか(インターフェース)」を公開する役割を持ちます。 + * **ソースファイル (`.cpp`)**: 「実装」を置く場所です。ヘッダファイルで宣言された関数の具体的な処理内容などを記述します。ヘッダファイルが公開したインターフェースを「どのように実現するか」を記述する役割を持ちます。 + +**なぜ分割するのか? 🤔** + +1. **関心の分離**: インターフェース(何ができるか)と実装(どうやるか)を分離することで、コードの見通しが良くなります。他の開発者はヘッダファイルを見るだけで、その機能の使い方がわかります。 +2. **コンパイル時間の短縮**: ソースファイルを変更した場合、再コンパイルはそのファイルだけで済みます。プロジェクト全体を再コンパイルする必要がないため、大規模なプロジェクトでは開発サイクルが劇的に速くなります。 +3. **再利用性の向上**: よく使う関数やクラスをまとめておけば、別のプロジェクトでそのファイルをインクルードするだけで簡単に再利用できます。 diff --git a/public/docs/cpp/5-project-build/1-2-split-example.md b/public/docs/cpp/5-project-build/1-2-split-example.md new file mode 100644 index 0000000..7e27e14 --- /dev/null +++ b/public/docs/cpp/5-project-build/1-2-split-example.md @@ -0,0 +1,49 @@ +--- +id: cpp-project-build-split-example +title: 分割の例 +level: 3 +--- + +### 分割の例 + +簡単な足し算を行う関数を別のファイルに分割してみましょう。 + +まず、関数の「宣言」をヘッダファイルに記述します。 + +```cpp:math_utils.h +// 関数の宣言を記述するヘッダファイル + +// この関数が他のファイルから参照されることを示す +int add(int a, int b); +``` + +次に、この関数の「実装」をソースファイルに記述します。 + +```cpp:math_utils.cpp +// 関数の実装を記述するソースファイル + +#include "math_utils.h" // 対応するヘッダファイルをインクルード + +int add(int a, int b) { + return a + b; +} +``` + +最後に、`main`関数を含むメインのソースファイルから、この`add`関数を呼び出します。 + +```cpp:math_app.cpp +#include +#include "math_utils.h" // 自作したヘッダファイルをインクルード + +int main() { + int result = add(5, 3); + std::cout << "The result is: " << result << std::endl; + return 0; +} +``` + +```cpp-exec:math_app.cpp,math_utils.cpp +The result is: 8 +``` + +ここで注目すべき点は、`math_app.cpp`が`add`関数の具体的な実装を知らないことです。`math_utils.h`を通じて「`int`を2つ受け取って`int`を返す`add`という関数が存在する」ことだけを知り、それを利用しています。 diff --git a/public/docs/cpp/5-project-build/2-0-include-guard.md b/public/docs/cpp/5-project-build/2-0-include-guard.md new file mode 100644 index 0000000..db1a979 --- /dev/null +++ b/public/docs/cpp/5-project-build/2-0-include-guard.md @@ -0,0 +1,48 @@ +--- +id: cpp-project-build-include-guard +title: インクルードガード +level: 2 +--- + +## インクルードガード + +複数のファイルから同じヘッダファイルがインクルードされる状況はよくあります。例えば、`A.h`が`B.h`をインクルードし、ソースファイルが`A.h`と`B.h`の両方をインクルードするような場合です。 + +もしヘッダファイルに何の対策もしていないと、同じ内容(クラス定義や関数宣言)が複数回読み込まれ、「再定義」としてコンパイルエラーが発生してしまいます。 + +```cpp:A.h +#include "B.h" // B.hをインクルード + +// A.hの内容 +``` + +```cpp:B.h +class B { + // Bクラスの内容 +}; +``` + +```cpp:bad_include_app.cpp +#include "A.h" +#include "B.h" // B.hが二重にインクルードされる + +int main() { + [[maybe_unused]] B b; // Bクラスを使う + + return 0; +} +``` + +```cpp-exec:bad_include_app.cpp +In file included from bad_include_app.cpp:2: +B.h:1:7: error: redefinition of 'class B' + 1 | class B { + | ^ +In file included from A.h:1, + from bad_include_app.cpp:1: +B.h:1:7: note: previous definition of 'class B' + 1 | class B { + | ^ +``` + +この問題を解決するのが**インクルードガード**です。インクルードガードは、ヘッダファイルの内容が1つの翻訳単位(ソースファイル)内で一度しか読み込まれないようにするための仕組みです。 diff --git a/public/docs/cpp/5-project-build/2-1-include-guard-by-define.md b/public/docs/cpp/5-project-build/2-1-include-guard-by-define.md new file mode 100644 index 0000000..edfb28c --- /dev/null +++ b/public/docs/cpp/5-project-build/2-1-include-guard-by-define.md @@ -0,0 +1,25 @@ +--- +id: cpp-project-build-include-guard-by-define +title: 伝統的なインクルードガード +level: 3 +--- + +### 伝統的なインクルードガード + +プリプロセッサディレクティブである `#ifndef`, `#define`, `#endif` を使います。 + +```cpp +#ifndef MATH_UTILS_H // もし MATH_UTILS_H が未定義なら +#define MATH_UTILS_H // MATH_UTILS_H を定義する + +// --- ヘッダファイルの中身 --- +int add(int a, int b); +// ------------------------- + +#endif // MATH_UTILS_H +``` + + * **最初のインクルード**: `MATH_UTILS_H` は未定義なので、`#define` が実行され、中身が読み込まれます。 + * **2回目以降のインクルード**: `MATH_UTILS_H` は既に定義されているため、`#ifndef` から `#endif` までのすべてが無視されます。 + +マクロ名 (`MATH_UTILS_H`) は、ファイル名に基づいて一意になるように命名するのが慣習です。 diff --git a/public/docs/cpp/5-project-build/2-2-pragma-once.md b/public/docs/cpp/5-project-build/2-2-pragma-once.md new file mode 100644 index 0000000..14416ed --- /dev/null +++ b/public/docs/cpp/5-project-build/2-2-pragma-once.md @@ -0,0 +1,19 @@ +--- +id: cpp-project-build-pragma-once +title: '#pragma once' +level: 3 +--- + +### \#pragma once + +より現代的で簡潔な方法として `#pragma once` があります。多くのモダンなコンパイラがサポートしています。 + +```cpp +#pragma once + +#include + +std::string to_upper(const std::string& str); +``` + +この一行をヘッダファイルの先頭に書くだけで、コンパイラがそのファイルが一度しかインクルードされないように処理してくれます。特別な理由がない限り、現在では `#pragma once` を使うのが主流です。 diff --git a/public/docs/cpp/5-project-build/3-0-build.md b/public/docs/cpp/5-project-build/3-0-build.md new file mode 100644 index 0000000..3c8d68a --- /dev/null +++ b/public/docs/cpp/5-project-build/3-0-build.md @@ -0,0 +1,11 @@ +--- +id: cpp-project-build-build +title: プロジェクトのビルド +level: 2 +--- + +## プロジェクトのビルド + +複数のソースファイル(`.cpp`)は、それぞれがコンパイルされて**オブジェクトファイル**(`.o` や `.obj`)になります。その後、**リンカ**がこれらのオブジェクトファイルと必要なライブラリを結合して、最終的な実行可能ファイルを生成します。 + +この一連の作業を**ビルド**と呼びます。ファイルが増えてくると、これを手動で行うのは非常に面倒です。そこで、ビルド作業を自動化する**ビルドシステム**が使われます。 diff --git a/public/docs/cpp/5-project-build/3-1-gcc-manual.md b/public/docs/cpp/5-project-build/3-1-gcc-manual.md new file mode 100644 index 0000000..bf0e279 --- /dev/null +++ b/public/docs/cpp/5-project-build/3-1-gcc-manual.md @@ -0,0 +1,28 @@ +--- +id: cpp-project-build-gcc-manual +title: 手動でのビルド (g++) +level: 3 +--- + +### 手動でのビルド (g++) + +先ほどの`math_app.cpp`と`math_utils.cpp`を例に、g++コンパイラで手動ビルドする手順を見てみましょう。 + +```bash +# 1. 各ソースファイルをコンパイルしてオブジェクトファイルを生成する (-c オプション) +g++ -c math_app.cpp -o main.o +g++ -c math_utils.cpp -o math_utils.o + +# 2. オブジェクトファイルをリンクして実行可能ファイルを生成する +g++ main.o math_utils.o -o my_app + +# 3. 実行する +./my_app +``` + +または、以下のように1回のg++コマンドで複数ソースファイルのコンパイルとリンクを同時に行うこともできます。 + +```bash +g++ math_app.cpp math_utils.cpp -o my_app +./my_app +``` diff --git a/public/docs/cpp/5-project-build/3-2-makefile.md b/public/docs/cpp/5-project-build/3-2-makefile.md new file mode 100644 index 0000000..a13614d --- /dev/null +++ b/public/docs/cpp/5-project-build/3-2-makefile.md @@ -0,0 +1,43 @@ +--- +id: cpp-project-build-makefile +title: Makefileによる自動化 +level: 3 +--- + +### Makefileによる自動化 + +`make`は、ファイルの依存関係と更新ルールを記述した`Makefile`というファイルに従って、ビルドプロセスを自動化するツールです。 + +以下は、非常にシンプルな`Makefile`の例です。 + +```makefile +# コンパイラを指定 +CXX = g++ +# コンパイルオプションを指定 +CXXFLAGS = -std=c++17 -Wall + +# 最終的なターゲット(実行可能ファイル名) +TARGET = my_app + +# ソースファイルとオブジェクトファイル +SRCS = math_app.cpp math_utils.cpp +OBJS = $(SRCS:.cpp=.o) + +# デフォルトのターゲット (makeコマンド実行時に最初に実行される) +all: $(TARGET) + +# 実行可能ファイルの生成ルール +$(TARGET): $(OBJS) + $(CXX) $(CXXFLAGS) -o $(TARGET) $(OBJS) + +# オブジェクトファイルの生成ルール (%.o: %.cpp) +# .cppファイルから.oファイルを作るための汎用ルール +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# 中間ファイルなどを削除するルール +clean: + rm -f $(OBJS) $(TARGET) +``` + +この`Makefile`があるディレクトリで、ターミナルから`make`と入力するだけで、必要なコンパイルとリンクが自動的に実行されます。`math_app.cpp`だけを変更した場合、`make`は`main.o`だけを再生成し、再リンクするため、ビルド時間が短縮されます。 diff --git a/public/docs/cpp/5-project-build/3-3-cmake.md b/public/docs/cpp/5-project-build/3-3-cmake.md new file mode 100644 index 0000000..c12ca63 --- /dev/null +++ b/public/docs/cpp/5-project-build/3-3-cmake.md @@ -0,0 +1,46 @@ +--- +id: cpp-project-build-cmake +title: CMakeによるモダンなビルド管理 +level: 3 +--- + +### CMakeによるモダンなビルド管理 + +`Makefile`は強力ですが、OSやコンパイラに依存する部分があり、複雑なプロジェクトでは管理が難しくなります。 + +**CMake**は、`Makefile`やVisual Studioのプロジェクトファイルなどを自動的に生成してくれる、クロスプラットフォーム対応のビルドシステムジェネレータです。`CMakeLists.txt`という設定ファイルに、より抽象的なビルドのルールを記述します。 + +```cmake +# CMakeの最低要求バージョン +cmake_minimum_required(VERSION 3.10) + +# プロジェクト名を設定 +project(MyAwesomeApp) + +# C++の標準バージョンを設定 +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# 実行可能ファイルを追加 +# add_executable(実行ファイル名 ソースファイル1 ソースファイル2 ...) +add_executable(my_app math_app.cpp math_utils.cpp) +``` + +この`CMakeLists.txt`を使ってビルドする一般的な手順は以下の通りです。 + +```bash +# 1. ビルド用の中間ファイルを置くディレクトリを作成し、移動する +mkdir build +cd build + +# 2. CMakeを実行して、ビルドシステム(この場合はMakefile)を生成する +cmake .. + +# 3. make (または cmake --build .) を実行してビルドする +make + +# 4. 実行する +./my_app +``` + +CMakeは、ライブラリの検索、依存関係の管理、テストの実行など、大規模プロジェクトに必要な多くの機能を備えており、現在のC++開発における標準的なツールとなっています。 diff --git a/public/docs/cpp/5-project-build/4-0-summary.md b/public/docs/cpp/5-project-build/4-0-summary.md new file mode 100644 index 0000000..d43dec9 --- /dev/null +++ b/public/docs/cpp/5-project-build/4-0-summary.md @@ -0,0 +1,11 @@ +--- +id: cpp-project-build-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **プロジェクトの分割**: プログラムは「宣言」を記述する**ヘッダファイル** (`.h`) と、「実装」を記述する**ソースファイル** (`.cpp`) に分割することで、保守性や再利用性が向上します。 + * **インクルードガード**: ヘッダファイルの多重インクルードによる再定義エラーを防ぐために、`#pragma once` や `#ifndef`/`#define`/`#endif` を使用します。 + * **ビルドシステム**: 複数のファイルをコンパイル・リンクするプロセスを自動化するために、`make` や `CMake` といったツールが使われます。特に **CMake** はクロスプラットフォーム開発におけるデファクトスタンダードです。 diff --git a/public/docs/cpp/5-project-build/4-1-practice1.md b/public/docs/cpp/5-project-build/4-1-practice1.md new file mode 100644 index 0000000..f43ba99 --- /dev/null +++ b/public/docs/cpp/5-project-build/4-1-practice1.md @@ -0,0 +1,45 @@ +--- +id: cpp-project-build-practice1 +title: '練習問題1: 電卓クラスの分割' +level: 3 +--- + +### 練習問題1: 電卓クラスの分割 + +`Calculator` というクラスを作成してください。このクラスは、加算、減算、乗算、除算のメンバ関数を持ちます。 + +* `Calculator.h`: `Calculator`クラスの定義を記述します。 +* `Calculator.cpp`: 各メンバ関数の実装を記述します。 +* `practice6_1.cpp`: `Calculator`クラスのインスタンスを作成し、いくつかの計算を行って結果を表示します。 + +これらのファイルをg++で手動ビルドして、プログラムを実行してください。 + +```cpp:Calculator.h + +``` + +```cpp:Calculator.cpp + +``` + +```cpp:practice6_1.cpp +#include +#include "Calculator.h" + +int main() { + Calculator calc; + + std::cout << "3 + 5 = " << calc.add(3, 5) << std::endl; + std::cout << "10 - 2 = " << calc.subtract(10, 2) << std::endl; + std::cout << "4 * 7 = " << calc.multiply(4, 7) << std::endl; + std::cout << "20 / 4 = " << calc.divide(20, 4) << std::endl; + return 0; +} +``` + +```cpp-exec:practice6_1.cpp,Calculator.cpp +3 + 5 = 8 +10 - 2 = 8 +4 * 7 = 28 +20 / 4 = 5 +``` diff --git a/public/docs/cpp/6-classes-basics/-intro.md b/public/docs/cpp/6-classes-basics/-intro.md new file mode 100644 index 0000000..00eb389 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/-intro.md @@ -0,0 +1 @@ +これまでの章では、C++の基本的な文法やメモリの扱い方について学んできました。この章からは、C++の最も強力な機能の一つである**オブジェクト指向プログラミング (Object-Oriented Programming, OOP)** の世界に足を踏み入れます。OOPの考え方を身につけることで、より大規模で複雑なプログラムを、現実世界の「モノ」の概念に近い形で、直感的に設計・実装できるようになります。その第一歩として、OOPの中核をなす**クラス**の基礎を学びましょう。 diff --git a/public/docs/cpp/6-classes-basics/1-0-about.md b/public/docs/cpp/6-classes-basics/1-0-about.md new file mode 100644 index 0000000..8c334aa --- /dev/null +++ b/public/docs/cpp/6-classes-basics/1-0-about.md @@ -0,0 +1,32 @@ +--- +id: cpp-classes-basics-about +title: 'クラスとは?: データ(メンバ変数)と処理(メンバ関数)のカプセル化' +level: 2 +--- + +## クラスとは?: データ(メンバ変数)と処理(メンバ関数)のカプセル化 + +他のプログラミング言語でオブジェクト指向に触れたことがあるなら、「クラスはオブジェクトの設計図」という説明を聞いたことがあるかもしれません。C++でもその考え方は同じです。クラスは、ある「モノ」が持つべき**データ(属性)**と、そのデータに対する**処理(操作)**を一つにまとめたものです。 + + - **データ(属性)**: クラス内に定義された変数のことで、**メンバ変数 (member variables)** または**データメンバ**と呼びます。 + - **処理(操作)**: クラス内に定義された関数のことで、**メンバ関数 (member functions)** または**メソッド**と呼びます。 + +このように、関連するデータと処理を一つのクラスにまとめることを、OOPの重要な概念の一つである**カプセル化 (encapsulation)** と呼びます。💊 + +例として、「人」を表す`Person`クラスを考えてみましょう。「人」は「名前」や「年齢」といったデータ(メンバ変数)を持ち、「自己紹介する」といった処理(メンバ関数)を行うことができます。 + +```cpp +class Person { +public: + // メンバ変数 + std::string name; + int age; + + // メンバ関数 + void introduce() { + std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; + } +}; +``` + +`class Person { ... };` という構文でクラスを定義します。クラス定義の最後にはセミコロン`;`が必要なので忘れないようにしましょう。現時点では、`public:`というキーワードは「これらのメンバは外部からアクセスできます」という意味だと考えておいてください。詳細は後ほど説明します。 diff --git a/public/docs/cpp/6-classes-basics/2-0-instance.md b/public/docs/cpp/6-classes-basics/2-0-instance.md new file mode 100644 index 0000000..1b8abc2 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/2-0-instance.md @@ -0,0 +1,54 @@ +--- +id: cpp-classes-basics-instance +title: 'インスタンスの生成: クラスからオブジェクトを作ってみる' +level: 2 +--- + +## インスタンスの生成: クラスからオブジェクトを作ってみる + +クラスはあくまで「設計図」です。実際にプログラムで利用するためには、この設計図をもとに実体を作る必要があります。クラスから作られた実体のことを**オブジェクト (object)** または**インスタンス (instance)** と呼び、オブジェクトを作ることを**インスタンス化 (instantiation)** と言います。 + +インスタンス化の構文は、変数の宣言とよく似ています。 + +```cpp:instantiation.cpp +#include +#include + +// Personクラスの定義 +class Person { +public: + std::string name; + int age; + + void introduce() { + std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; + } +}; + +int main() { + // Personクラスのインスタンスを生成 + Person taro; + + // メンバ変数に値を代入 (ドット演算子 . を使用) + taro.name = "Taro"; + taro.age = 30; + + // メンバ関数を呼び出す + taro.introduce(); // "My name is Taro, and I am 30 years old." と出力される + + // 別のインスタンスを生成 + Person hanako; + hanako.name = "Hanako"; + hanako.age = 25; + hanako.introduce(); // "My name is Hanako, and I am 25 years old." と出力される + + return 0; +} +``` + +```cpp-exec:instantiation.cpp +My name is Taro, and I am 30 years old. +My name is Hanako, and I am 25 years old. +``` + +このように、`クラス名 インスタンス名;` という形でインスタンスを生成できます。インスタンスのメンバ変数やメンバ関数にアクセスするには、`インスタンス名.メンバ名` のように**ドット演算子 (`.`)** を使います。`taro`と`hanako`は同じ`Person`クラスから作られたインスタンスですが、それぞれが独立したデータを持っていることがわかります。 diff --git a/public/docs/cpp/6-classes-basics/3-0-access-control.md b/public/docs/cpp/6-classes-basics/3-0-access-control.md new file mode 100644 index 0000000..4ea217e --- /dev/null +++ b/public/docs/cpp/6-classes-basics/3-0-access-control.md @@ -0,0 +1,95 @@ +--- +id: cpp-classes-basics-access-control +title: 'アクセス制御: public と private による情報の隠蔽' +level: 2 +--- + +## アクセス制御: public と private による情報の隠蔽 + +先ほどの`Person`クラスの例では、`main`関数から`taro.age = 30;`のようにメンバ変数に直接アクセスできました。これは手軽ですが、問題を引き起こす可能性があります。例えば、年齢にマイナスの値や非現実的な値を設定できてしまうかもしれません。 + +```cpp +Person jiro; +jiro.name = "Jiro"; +jiro.age = -5; // 本来ありえない値が設定できてしまう! +jiro.introduce(); +``` + +このような意図しない操作を防ぐために、C++には**アクセス制御**の仕組みがあります。クラスのメンバは、外部からのアクセスの可否を指定できます。 + + - **`public`**: クラスの外部(`main`関数など)から自由にアクセスできます。 + - **`private`**: そのクラスのメンバ関数からしかアクセスできません。外部からはアクセス不可です。 + +アクセス制御の基本は、**メンバ変数は`private`にし、メンバ関数は`public`にする**ことです。これにより、データの不正な書き換えを防ぎ、クラスの内部実装を外部から隠蔽します。これを**情報の隠蔽 (information hiding)** と呼び、カプセル化の重要な目的の一つです。 + +`private`なメンバ変数に安全にアクセスするために、`public`なメンバ関数(**ゲッター**や**セッター**と呼ばれる)を用意するのが一般的です。 + +```cpp:access_control.cpp +#include +#include + +class Person { +private: + // メンバ変数は外部から隠蔽する + std::string name; + int age; + +public: + // セッター: メンバ変数に値を設定する + void setName(const std::string& newName) { + name = newName; + } + + void setAge(int newAge) { + if (newAge >= 0 && newAge < 150) { // 不正な値をチェック + age = newAge; + } else { + std::cout << "Error: Invalid age value." << std::endl; + } + } + + // ゲッター: メンバ変数の値を取得する + std::string getName() const { + return name; + } + + int getAge() const { + return age; + } + + // このメンバ関数はクラス内部にあるので、privateメンバにアクセスできる + void introduce() { + std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; + } +}; + +int main() { + Person saburo; + + // saburo.name = "Saburo"; // エラー! privateメンバには直接アクセスできない + // saburo.age = -10; // エラー! + + // publicなメンバ関数を経由して安全に値を設定 + saburo.setName("Saburo"); + saburo.setAge(28); + + saburo.introduce(); + + saburo.setAge(-10); // エラーメッセージが出力される + + // publicなメンバ関数経由で値を取得 + std::cout << "Name: " << saburo.getName() << std::endl; + + return 0; +} +``` + +```cpp-exec:access_control.cpp +My name is Saburo, and I am 28 years old. +Error: Invalid age value. +Name: Saburo +``` + +`setAge`関数内で値の妥当性チェックを行っている点に注目してください。このように、クラスの利用者は内部の実装を気にすることなく、提供された`public`なインターフェース(メンバ関数)を通じて安全にオブジェクトを操作できます。 + +> `const`キーワード: `getName() const` のようにメンバ関数の後ろに`const`を付けると、その関数がメンバ変数を変更しないことをコンパイラに約束します。このような関数を**constメンバ関数**と呼びます。 diff --git a/public/docs/cpp/6-classes-basics/4-0-constructor-destructor.md b/public/docs/cpp/6-classes-basics/4-0-constructor-destructor.md new file mode 100644 index 0000000..4f1a657 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/4-0-constructor-destructor.md @@ -0,0 +1,9 @@ +--- +id: cpp-classes-basics-constructor-destructor +title: 'コンストラクタとデストラクタ: オブジェクトが生まれてから消えるまで' +level: 2 +--- + +## コンストラクタとデストラクタ: オブジェクトが生まれてから消えるまで + +オブジェクトは生成され、利用され、やがて破棄されます。このライフサイクルに合わせて特別な処理を自動的に実行するための仕組みが**コンストラクタ**と**デストラクタ**です。 diff --git a/public/docs/cpp/6-classes-basics/4-1-constructor.md b/public/docs/cpp/6-classes-basics/4-1-constructor.md new file mode 100644 index 0000000..30b95ec --- /dev/null +++ b/public/docs/cpp/6-classes-basics/4-1-constructor.md @@ -0,0 +1,45 @@ +--- +id: cpp-classes-basics-constructor +title: コンストラクタ (Constructor) +level: 3 +--- + +### コンストラクタ (Constructor) + +**コンストラクタ**は、インスタンスが生成されるときに**自動的に呼び出される**特別なメンバ関数です。主な役割は、メンバ変数の初期化です。 + +コンストラクタには以下の特徴があります。 + + - 関数名がクラス名と全く同じ。 + - 戻り値の型を指定しない(`void`も付けない)。 + - 引数を取ることができ、複数定義できる(オーバーロード)。 + +```cpp:constructor.cpp +class Person { +private: + std::string name; + int age; + +public: + // 引数付きコンストラクタ + Person(const std::string& initName, int initAge) { + std::cout << "Constructor called for " << initName << std::endl; + name = initName; + age = initAge; + } + // ... +}; + +int main() { + // インスタンス生成時にコンストラクタが呼ばれ、引数が渡される + Person yuko("Yuko", 22); // この時点でコンストラクタが実行される + yuko.introduce(); +} +``` + +```cpp-exec:constructor.cpp +Constructor called for Yuko +My name is Yuko, and I am 22 years old. +``` + +このように、インスタンス生成時に`()`で初期値を渡すことで、オブジェクトを生成と同時に有効な状態にできます。`set`関数を別途呼び出す手間が省け、初期化忘れを防ぐことができます。 diff --git a/public/docs/cpp/6-classes-basics/4-2-destructor.md b/public/docs/cpp/6-classes-basics/4-2-destructor.md new file mode 100644 index 0000000..15b7f37 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/4-2-destructor.md @@ -0,0 +1,69 @@ +--- +id: cpp-classes-basics-destructor +title: デストラクタ (Destructor) +level: 3 +--- + +### デストラクタ (Destructor) + +**デストラクタ**は、インスタンスが破棄されるとき(例えば、変数のスコープを抜けるとき)に**自動的に呼び出される**特別なメンバ関数です。主な役割は、オブジェクトが使用していたリソース(メモリやファイルなど)の後片付けです。 + +デストラクタには以下の特徴があります。 + + - 関数名が `~` + クラス名。 + - 戻り値も引数も取らない。 + - 1つのクラスに1つしか定義できない。 + +```cpp:constructor_destructor.cpp +#include +#include + +class Person { +private: + std::string name; + int age; + +public: + // コンストラクタ + Person(const std::string& initName, int initAge) { + std::cout << "Constructor called for " << initName << "." << std::endl; + name = initName; + age = initAge; + } + + // デストラクタ + ~Person() { + std::cout << "Destructor called for " << name << "." << std::endl; + } + + void introduce() { + std::cout << "My name is " << name << ", and I am " << age << " years old." << std::endl; + } +}; + +void create_person_scope() { + std::cout << "--- Entering scope ---" << std::endl; + Person kenji("Kenji", 45); // kenjiはこのスコープ内でのみ生存 + kenji.introduce(); + std::cout << "--- Exiting scope ---" << std::endl; +} // ここでkenjiのスコープが終わり、デストラクタが呼ばれる + +int main() { + create_person_scope(); + + std::cout << "--- Back in main ---" << std::endl; + + return 0; +} +``` + +```cpp-exec:constructor_destructor.cpp +--- Entering scope --- +Constructor called for Kenji. +My name is Kenji, and I am 45 years old. +--- Exiting scope --- +Destructor called for Kenji. +--- Back in main --- +``` + +実行結果を見ると、`kenji`オブジェクトが生成されたときにコンストラクタが、`create_person_scope`関数のスコープを抜けるときにデストラクタが自動的に呼び出されていることがわかります。動的に確保したメモリの解放など、クリーンアップ処理はデストラクタに書くのが定石です。この考え方は、今後の章で学ぶRAII(Resource Acquisition Is Initialization)という重要な概念に繋がります。 diff --git a/public/docs/cpp/6-classes-basics/5-0-summary.md b/public/docs/cpp/6-classes-basics/5-0-summary.md new file mode 100644 index 0000000..3147bf0 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/5-0-summary.md @@ -0,0 +1,18 @@ +--- +id: cpp-classes-basics-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、C++におけるオブジェクト指向プログラミングの第一歩として、クラスの基本的な概念を学びました。 + + - **クラス**は、データ(**メンバ変数**)と処理(**メンバ関数**)を一つにまとめた「設計図」です。 + - クラスから実体である**オブジェクト(インスタンス)**を生成して使用します。 + - **カプセル化**は、関連するデータと処理をまとめることです。 + - **アクセス制御**(`public`, `private`)により、外部からアクセスされたくないメンバを保護します(**情報の隠蔽**)。 + - **コンストラクタ**は、オブジェクト生成時に自動で呼ばれ、初期化を行います。 + - **デストラクタ**は、オブジェクト破棄時に自動で呼ばれ、後片付けを行います。 + +クラスを使いこなすことで、プログラムの部品化が進み、再利用性やメンテナンス性が格段に向上します。次の章では、クラスのさらに進んだ機能について学んでいきましょう。 diff --git a/public/docs/cpp/6-classes-basics/5-1-practice1.md b/public/docs/cpp/6-classes-basics/5-1-practice1.md new file mode 100644 index 0000000..68e5331 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/5-1-practice1.md @@ -0,0 +1,29 @@ +--- +id: cpp-classes-basics-practice1 +title: '練習問題1: 長方形クラス' +level: 3 +--- + +### 練習問題1: 長方形クラス + +幅(`width`)と高さ(`height`)をメンバ変数として持つ`Rectangle`クラスを作成してください。 + + - メンバ変数は`private`で定義してください。 + - コンストラクタで幅と高さを初期化できるようにしてください。 + - 面積を計算して返す`getArea()`メソッドと、周の長さを計算して返す`getPerimeter()`メソッドを`public`で実装してください。 + - `main`関数で`Rectangle`クラスのインスタンスをいくつか生成し、面積と周の長さを表示するプログラムを作成してください。 + +```cpp:practice7_1.cpp +#include +#include +// ここにRectangleクラスを定義してください + +int main() { + // ここでRectangleクラスのインスタンスを生成し、面積と周の長さを表示してください + + return 0; +} +``` + +```cpp-exec:practice7_1.cpp +``` diff --git a/public/docs/cpp/6-classes-basics/5-2-practice2.md b/public/docs/cpp/6-classes-basics/5-2-practice2.md new file mode 100644 index 0000000..7006be7 --- /dev/null +++ b/public/docs/cpp/6-classes-basics/5-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: cpp-classes-basics-practice2 +title: '練習問題2: 書籍クラス' +level: 3 +--- + +### 練習問題2: 書籍クラス + +タイトル(`title`)、著者(`author`)、ページ数(`pages`)をメンバ変数として持つ`Book`クラスを作成してください。 + + - メンバ変数は`private`で定義してください。 + - コンストラクタで、タイトル、著者、ページ数を初期化できるようにしてください。 + - 本の情報を整形してコンソールに出力する`printInfo()`メソッドを`public`で実装してください。(例: `Title: [タイトル], Author: [著者], Pages: [ページ数] pages`) + - `main`関数で`Book`クラスのインスタンスを生成し、その情報を表示してください。 + +```cpp:practice7_2.cpp +#include +#include +// ここにBookクラスを定義してください + +int main() { + // ここでBookクラスのインスタンスを生成し、情報を表示してください + + return 0; +} +``` + +```cpp-exec:practice7_2.cpp +Title: The Great Gatsby, Author: F. Scott Fitzgerald, Pages: 180 pages +``` diff --git a/public/docs/cpp/7-classes-advanced/-intro.md b/public/docs/cpp/7-classes-advanced/-intro.md new file mode 100644 index 0000000..490d0e0 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/-intro.md @@ -0,0 +1 @@ +第7章では、C++のオブジェクト指向プログラミングの核となる`class`の基本的な使い方を学びました。しかし、クラスを真に強力なツールとして使いこなすには、もう少し知識が必要です。この章では、オブジェクトのコピー、演算子のオーバーロード、クラスで共有されるメンバなど、より実践的でパワフルな機能について掘り下げていきます。これらの概念をマスターすることで、あなたの書くクラスはより安全で、直感的で、再利用性の高いものになるでしょう。 diff --git a/public/docs/cpp/7-classes-advanced/1-0-copy.md b/public/docs/cpp/7-classes-advanced/1-0-copy.md new file mode 100644 index 0000000..5859444 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/1-0-copy.md @@ -0,0 +1,16 @@ +--- +id: cpp-classes-advanced-copy +title: 'オブジェクトのコピー: コピーコンストラクタと代入演算子' +level: 2 +--- + +## オブジェクトのコピー: コピーコンストラクタと代入演算子 + +オブジェクトをコピーしたい場面は頻繁にあります。例えば、関数の引数にオブジェクトを渡すとき(値渡し)や、既存のオブジェクトで新しいオブジェクトを初期化するときなどです。 + +```cpp +Vector2D v1(1.0, 2.0); +Vector2D v2 = v1; // ここでコピーが発生! +``` + +多くの場合、コンパイラが自動的に生成するコピー機能で十分です。しかし、クラスがポインタなどでリソース(メモリなど)を管理している場合、単純なコピーでは問題が発生します。 diff --git a/public/docs/cpp/7-classes-advanced/1-1-shallow-copy.md b/public/docs/cpp/7-classes-advanced/1-1-shallow-copy.md new file mode 100644 index 0000000..dae939d --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/1-1-shallow-copy.md @@ -0,0 +1,50 @@ +--- +id: cpp-classes-advanced-shallow-copy +title: 何もしないとどうなる? (浅いコピー) +level: 3 +--- + +### 何もしないとどうなる? (浅いコピー) + +まず、コピーの機能を自分で作らなかった場合に何が起きるか見てみましょう。 +コンパイラは、メンバ変数を単純にコピーするだけの「浅いコピー」を行います。 + +ここでは、`int`へのポインタを一つだけ持つ`ResourceHolder`(リソース保持者)というクラスを考えます。 + +```cpp:shallow_copy.cpp +// 悪い例:浅いコピーの問題点 +#include + +class ResourceHolder { +private: + int* m_data; // 動的に確保したデータへのポインタ +public: + ResourceHolder(int value) { + m_data = new int(value); // メモリを確保 + std::cout << "Resource " << *m_data << " created. (at " << m_data << ")" << std::endl; + } + ~ResourceHolder() { + std::cout << "Resource " << *m_data << " destroyed. (at " << m_data << ")" << std::endl; + delete m_data; // メモリを解放 + } + // コピーコンストラクタや代入演算子を定義していない! +}; + +int main() { + ResourceHolder r1(10); + ResourceHolder r2 = r1; // 浅いコピーが発生! + // r1.m_data と r2.m_data は同じアドレスを指してしまう + + // main()終了時、r1とr2のデストラクタが呼ばれる + // 同じメモリを2回deleteしようとしてクラッシュ!💥 + return 0; +} +``` +```cpp-exec:shallow_copy.cpp +Resource 10 created. (at 0x139f065e0) +Resource 10 destroyed. (at 0x139f065e0) +Resource 107521 destroyed. (at 0x1a4012b0) +free(): double free detected in tcache 2 +``` + +この例では、`r2`が作られるときに`r1`のポインタ`m_data`の値(メモリアドレス)だけがコピーされます。その結果、2つのオブジェクトが1つのメモリ領域を指してしまいます。プログラム終了時にそれぞれのデストラクタが呼ばれ、同じメモリを2回解放しようとしてエラーになります。 diff --git a/public/docs/cpp/7-classes-advanced/1-2-deep-copy.md b/public/docs/cpp/7-classes-advanced/1-2-deep-copy.md new file mode 100644 index 0000000..90e1a57 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/1-2-deep-copy.md @@ -0,0 +1,124 @@ +--- +id: cpp-classes-advanced-deep-copy +title: 解決策:コピー機能を自作する (深いコピー) +level: 3 +--- + +### 解決策:コピー機能を自作する (深いコピー) + +この問題を解決するために、**コピーコンストラクタ**と**コピー代入演算子**を自分で定義して、「深いコピー」を実装します。深いコピーとは、ポインタの指す先の実体(データそのもの)を新しく作ってコピーすることです。 + +```cpp:resource_holder.cpp +#include + +class ResourceHolder { +private: + int* m_data; // リソースとして動的に確保したintへのポインタ + +public: + // コンストラクタ: intを1つ動的に確保し、値を設定 + ResourceHolder(int value) { + m_data = new int(value); + std::cout << "Resource " << *m_data << " created. (at " << m_data << ")" << std::endl; + } + + // デストラクタ: 確保したメモリを解放 + ~ResourceHolder() { + if (m_data != nullptr) { + std::cout << "Resource " << *m_data << " destroyed. (at " << m_data << ")" << std::endl; + delete m_data; + } + } + + // --- ここからが本題です --- + + // 1. コピーコンストラクタ (深いコピー) + // ResourceHolder r2 = r1; のように、オブジェクトの作成と同時にコピーするときに呼ばれる + ResourceHolder(const ResourceHolder& other) { + // ① 新しいメモリを確保する + // ② otherの「値」(*other.m_data)を、新しいメモリにコピーする + m_data = new int(*other.m_data); + std::cout << "COPY CONSTRUCTOR: New resource " << *m_data << " created. (at " << m_data << ")" << std::endl; + } + + // 2. コピー代入演算子 (深いコピー) + // r3 = r1; のように、既存のオブジェクトに代入するときに呼ばれる + ResourceHolder& operator=(const ResourceHolder& other) { + std::cout << "COPY ASSIGNMENT OPERATOR called." << std::endl; + + // ① 自己代入のチェック (a = a; のような無駄な処理を防ぐ) + if (this == &other) { + return *this; // 何もせず自分自身を返す + } + + // ② 自分が元々持っていた古いリソースを解放する + delete m_data; + + // ③ 新しいリソースを確保し、相手の値をコピーする + m_data = new int(*other.m_data); + + return *this; // 自分自身を返すことで、a = b = c; のような連続代入が可能になる + } + + void print() const { + std::cout << "Value: " << *m_data << ", Address: " << m_data << std::endl; + } +}; + +int main() { + std::cout << "--- rh1の作成 ---" << std::endl; + ResourceHolder rh1(10); + + std::cout << "\n--- rh2をrh1で初期化 ---" << std::endl; + ResourceHolder rh2 = rh1; // コピーコンストラクタが呼ばれる + + std::cout << "\n--- rh3の作成 ---" << std::endl; + ResourceHolder rh3(20); + + std::cout << "\n--- rh3にrh1を代入 ---" << std::endl; + rh3 = rh1; // コピー代入演算子が呼ばれる + + std::cout << "\n--- 各オブジェクトの状態 ---" << std::endl; + std::cout << "rh1: "; rh1.print(); + std::cout << "rh2: "; rh2.print(); // rh1とは別のメモリを持っている + std::cout << "rh3: "; rh3.print(); // rh1とは別のメモリを持っている + + std::cout << "\n--- main関数終了 ---" << std::endl; + return 0; // ここでrh1, rh2, rh3のデストラクタが呼ばれ、それぞれが確保したメモリを安全に解放する +} +``` + +```cpp-exec:resource_holder.cpp +--- rh1の作成 --- +Resource 10 created. (at 0x139f065e0) + +--- rh2をrh1で初期化 --- +COPY CONSTRUCTOR: New resource 10 created. (at 0x139f06600) + +--- rh3の作成 --- +Resource 20 created. (at 0x139f06620) + +--- rh3にrh1を代入 --- +COPY ASSIGNMENT OPERATOR called. + +--- 各オブジェクトの状態 --- +rh1: Value: 10, Address: 0x139f065e0 +rh2: Value: 10, Address: 0x139f06600 +rh3: Value: 10, Address: 0x139f06640 + +--- main関数終了 --- +Resource 10 destroyed. (at 0x139f06640) +Resource 10 destroyed. (at 0x139f06600) +Resource 10 destroyed. (at 0x139f065e0) +``` + +*(メモリアドレスは実行するたびに変わります)* + +実行結果を見ると、`rh1`, `rh2`, `rh3` はそれぞれ異なるメモリアドレス (`Address`) を持っていることがわかります。これにより、各オブジェクトは独立したリソースを管理でき、プログラム終了時にそれぞれのデストラクタが安全にメモリを解放できます。 + +| 機能 | いつ呼ばれるか | 何をするか | +| :--- | :--- | :--- | +| **コピーコンストラクタ** | オブジェクトが**作られる時**に、他のオブジェクトで初期化される場合
      `ResourceHolder r2 = r1;` | 新しいリソースを確保し、元のオブジェクトの**値**をコピーする。 | +| **コピー代入演算子** | **既にあるオブジェクト**に、他のオブジェクトを代入する場合
      `r3 = r1;` | 1. 自分が持っている古いリソースを解放する。
      2. 新しいリソースを確保し、元のオブジェクトの**値**をコピーする。 | + +このように、ポインタでリソースを管理するクラスでは、安全なコピーを実現するためにこの2つの関数を自分で定義することが不可欠です。 diff --git a/public/docs/cpp/7-classes-advanced/2-0-operator-overload.md b/public/docs/cpp/7-classes-advanced/2-0-operator-overload.md new file mode 100644 index 0000000..388b31e --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/2-0-operator-overload.md @@ -0,0 +1,75 @@ +--- +id: cpp-classes-advanced-operator-overload +title: 演算子のオーバーロード +level: 2 +--- + +## 演算子のオーバーロード + +C++では、`+`, `-`, `==`, `<<` などの組み込み演算子を、自作のクラスで使えるように**再定義(オーバーロード)**できます。これにより、クラスのインスタンスをあたかも組み込み型(`int`や`double`など)のように直感的に扱えるようになります。 + +例えば、2次元ベクトルを表す `Vector2D` クラスがあるとします。`v3 = v1 + v2;` のように、ベクトル同士の足し算を自然に記述できると便利ですよね。 + +演算子のオーバーロードは、メンバ関数または非メンバ関数(グローバル関数)として定義します。 + +| 演算子 | メンバ関数での定義 | 非メンバ関数での定義 | +| :--- | :--- | :--- | +| 二項演算子 (`+`, `==` etc.) | `T operator+(const U& rhs);` | `T operator+(const T& lhs, const U& rhs);` | +| 単項演算子 (`-`, `!` etc.) | `T operator-();` | `T operator-(const T& obj);` | + +`Vector2D` クラスで `+`(加算)、`==`(等価比較)、`<<`(ストリーム出力)をオーバーロードしてみましょう。 + +```cpp:operator_overloading.cpp +#include + +class Vector2D { +public: + double x, y; + + Vector2D(double x = 0.0, double y = 0.0) : x(x), y(y) {} + + // メンバ関数として + 演算子をオーバーロード + Vector2D operator+(const Vector2D& rhs) const { + return Vector2D(this->x + rhs.x, this->y + rhs.y); + } + + // メンバ関数として == 演算子をオーバーロード + bool operator==(const Vector2D& rhs) const { + return (this->x == rhs.x) && (this->y == rhs.y); + } +}; + +// 非メンバ関数として << 演算子をオーバーロード +// 第1引数が std::ostream& なので、メンバ関数にはできない +std::ostream& operator<<(std::ostream& os, const Vector2D& v) { + os << "(" << v.x << ", " << v.y << ")"; + return os; +} + +int main() { + Vector2D v1(1.0, 2.0); + Vector2D v2(3.0, 4.0); + + // operator+ が呼ばれる + Vector2D v3 = v1 + v2; + std::cout << "v1: " << v1 << std::endl; // operator<< + std::cout << "v2: " << v2 << std::endl; // operator<< + std::cout << "v3 = v1 + v2: " << v3 << std::endl; // operator<< + + // operator== が呼ばれる + if (v1 == Vector2D(1.0, 2.0)) { + std::cout << "v1 is equal to (1.0, 2.0)" << std::endl; + } + + return 0; +} +``` + +```cpp-exec:operator_overloading.cpp +v1: (1, 2) +v2: (3, 4) +v3 = v1 + v2: (4, 6) +v1 is equal to (1.0, 2.0) +``` + +`operator<<` は、左辺のオペランドが `std::ostream` 型(`std::cout` など)であるため、`Vector2D` のメンバ関数としては定義できません。そのため、非メンバ関数として定義するのが一般的です。 diff --git a/public/docs/cpp/7-classes-advanced/3-0-static-member.md b/public/docs/cpp/7-classes-advanced/3-0-static-member.md new file mode 100644 index 0000000..6679247 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/3-0-static-member.md @@ -0,0 +1,88 @@ +--- +id: cpp-classes-advanced-static-member +title: staticメンバ +level: 2 +--- + +## staticメンバ + +通常、クラスのメンバ変数はオブジェクトごとに個別のメモリ領域を持ちます。しかし、あるクラスの**全てのオブジェクトで共有したい**情報もあります。例えば、「これまでに生成されたオブジェクトの総数」などです。このような場合、**staticメンバ**を使用します。 + +`static` キーワードを付けて宣言されたメンバ変数は、特定のオブジェクトに属さず、クラスそのものに属します。そのため、全オブジェクトでただ1つの実体を共有します。これを**クラス変数**と呼ぶこともあります。 + + * **宣言**: クラス定義の中で `static` を付けて行います。 + * **定義**: クラス定義の外(ソースファイル)で、メモリ上の実体を確保し、初期化します。 + +`static` キーワードを付けて宣言されたメンバ関数は、特定のオブジェクトに依存せずに呼び出せます。そのため、`this` ポインタ(後述)を持ちません。 + + * **アクセス**: staticメンバ変数や他のstaticメンバ関数にはアクセスできますが、非staticなメンバ(インスタンスごとのメンバ変数やメンバ関数)にはアクセスできません。 + * **呼び出し**: `クラス名::関数名()` のように、オブジェクトを生成しなくても呼び出せます。 + +ゲームに登場する `Player` クラスがあり、現在何人のプレイヤーが存在するかを管理する例を見てみましょう。 + +```cpp:static_members.cpp +#include +#include + +class Player { +private: + std::string name; + // (1) staticメンバ変数の宣言 + static int playerCount; + +public: + Player(const std::string& name) : name(name) { + playerCount++; // オブジェクトが生成されるたびにインクリメント + std::cout << name << " がゲームに参加しました。現在のプレイヤー数: " << playerCount << std::endl; + } + + ~Player() { + playerCount--; // オブジェクトが破棄されるたびにデクリメント + std::cout << name << " がゲームから退出しました。現在のプレイヤー数: " << playerCount << std::endl; + } + + // (2) staticメンバ関数の宣言 + static int getPlayerCount() { + // name などの非staticメンバにはアクセスできない + return playerCount; + } +}; + +// (3) staticメンバ変数の定義と初期化 +int Player::playerCount = 0; + +int main() { + // オブジェクトがなくてもstaticメンバ関数を呼び出せる + std::cout << "ゲーム開始時のプレイヤー数: " << Player::getPlayerCount() << std::endl; + std::cout << "---" << std::endl; + + Player p1("Alice"); + Player p2("Bob"); + + { + Player p3("Charlie"); + std::cout << "現在のプレイヤー数 (p1経由): " << p1.getPlayerCount() << std::endl; + } // p3のスコープが終わり、デストラクタが呼ばれる + + std::cout << "---" << std::endl; + std::cout << "ゲーム終了時のプレイヤー数: " << Player::getPlayerCount() << std::endl; + + return 0; +} +``` + +```cpp-exec:static_members.cpp +ゲーム開始時のプレイヤー数: 0 +--- +Alice がゲームに参加しました。現在のプレイヤー数: 1 +Bob がゲームに参加しました。現在のプレイヤー数: 2 +Charlie がゲームに参加しました。現在のプレイヤー数: 3 +現在のプレイヤー数 (p1経由): 3 +Charlie がゲームから退出しました。現在のプレイヤー数: 2 +--- +ゲーム終了時のプレイヤー数: 2 +Alice がゲームから退出しました。現在のプレイヤー数: 1 +Bob がゲームから退出しました。現在のプレイヤー数: 0 +``` + +`playerCount` は `p1`, `p2`, `p3` の全てで共有されており、一つの値が更新されていることがわかります。 diff --git a/public/docs/cpp/7-classes-advanced/4-0-this.md b/public/docs/cpp/7-classes-advanced/4-0-this.md new file mode 100644 index 0000000..70f77c1 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/4-0-this.md @@ -0,0 +1,71 @@ +--- +id: cpp-classes-advanced-this +title: thisポインタ +level: 2 +--- + +## thisポインタ + +非staticなメンバ関数が呼び出されるとき、その関数は「どのオブジェクトに対して呼び出されたか」を知る必要があります。コンパイラは、そのメンバ関数に対して、呼び出し元のオブジェクトのアドレスを暗黙的に渡します。このアドレスを保持するのが `this` ポインタです。 + +`this` は、メンバ関数内で使用できるキーワードで、自分自身のオブジェクトを指すポインタです。 + +`this` ポインタが主に使われるのは、以下のような場面です。 + +1. **メンバ変数と引数の名前が同じ場合** + コンストラクタの初期化子リストを使わない場合など、引数名とメンバ変数名が同じになることがあります。その際、`this->` を付けることでメンバ変数であることを明示できます。 + + ```cpp + void setX(double x) { + this->x = x; // this->x はメンバ変数, x は引数 + } + ``` + +2. **自分自身の参照やポインタを返す場合** + コピー代入演算子で `return *this;` としたように、オブジェクト自身を返したい場合に使います。これにより、**メソッドチェーン**(`obj.setX(10).setY(20);` のような連続したメソッド呼び出し)が可能になります。 + +メソッドチェーンを実現する簡単な例を見てみましょう。 + +```cpp:this_pointer.cpp +#include + +class Point { +private: + int x, y; + +public: + Point(int x = 0, int y = 0) : x(x), y(y) {} + + // 自身の参照を返すことで、メソッドチェーンを可能にする + Point& setX(int newX) { + this->x = newX; + return *this; // 自分自身の参照を返す + } + + Point& setY(int newY) { + this->y = newY; + return *this; // 自分自身の参照を返す + } + + void print() const { + std::cout << "(" << this->x << ", " << this->y << ")" << std::endl; + } +}; + +int main() { + Point p; + + // メソッドチェーン + p.setX(10).setY(20); + + p.print(); + + return 0; +} +``` + +```cpp-exec:this_pointer.cpp +(10, 20) +``` + +`setX` が `p` 自身の参照を返すため、その返り値に対して続けて `.setY(20)` を呼び出すことができます。 diff --git a/public/docs/cpp/7-classes-advanced/5-0-summary.md b/public/docs/cpp/7-classes-advanced/5-0-summary.md new file mode 100644 index 0000000..390364e --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/5-0-summary.md @@ -0,0 +1,16 @@ +--- +id: cpp-classes-advanced-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、クラスをより効果的に利用するための応用的な機能を学びました。 + + * **オブジェクトのコピー**: ポインタなどリソースを管理するクラスでは、**コピーコンストラクタ**と**コピー代入演算子**を定義し、**深いコピー**を実装することが重要です。これにより、リソースの二重解放などの問題を未然に防ぎます。 + * **演算子のオーバーロード**: `+` や `==` などの演算子を自作クラスに対して定義することで、コードの可読性を高め、直感的な操作を可能にします。 + * **staticメンバ**: `static`メンバ変数やメンバ関数は、クラスの全オブジェクトで共有されるデータや機能を提供します。オブジェクトを生成しなくてもアクセスできるのが特徴です。 + * **thisポインタ**: 非staticメンバ関数内で、呼び出し元のオブジェクト自身を指すポインタです。メンバ変数と引数の区別や、メソッドチェーンの実装に役立ちます。 + +これらの機能を組み合わせることで、C++のクラスは単なるデータの入れ物から、振る舞いを伴った洗練された部品へと進化します。 diff --git a/public/docs/cpp/7-classes-advanced/5-1-practice1.md b/public/docs/cpp/7-classes-advanced/5-1-practice1.md new file mode 100644 index 0000000..3639888 --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/5-1-practice1.md @@ -0,0 +1,41 @@ +--- +id: cpp-classes-advanced-practice1 +title: '練習問題1: 複素数クラス' +level: 3 +--- + +### 練習問題1: 複素数クラス + +実部 (real) と虚部 (imaginary) を`double`型で持つ複素数クラス `Complex` を作成してください。以下の要件を満たすものとします。 + +1. コンストラクタで実部と虚部を初期化できるようにする。 +2. 複素数同士の足し算 (`+`) と掛け算 (`*`) を演算子オーバーロードで実装する。 + * 加算: $(a+bi) + (c+di) = (a+c) + (b+d)i$ + * 乗算: $(a+bi) \* (c+di) = (ac-bd) + (ad+bc)i$ +3. `std::cout` で `(a + bi)` という形式で出力できるように、`<<` 演算子をオーバーロードする。(虚部が負の場合は `(a - bi)` のように表示されるとより良い) + +```cpp:practice8_1.cpp +#include + +// ここに Complex クラスを実装してください + +int main() { + Complex c1(1.0, 2.0); // 1 + 2i + Complex c2(3.0, 4.0); // 3 + 4i + Complex sum = c1 + c2; + Complex product = c1 * c2; + + std::cout << "c1: " << c1 << std::endl; + std::cout << "c2: " << c2 << std::endl; + std::cout << "c1 + c2 = " << sum << std::endl; + std::cout << "c1 * c2 = " << product << std::endl; + return 0; +} +``` + +```cpp-exec:practice8_1.cpp +c1: (1 + 2i) +c2: (3 + 4i) +c1 + c2 = (4 + 6i) +c1 * c2 = (-5 + 10i) +``` diff --git a/public/docs/cpp/7-classes-advanced/5-2-practice2.md b/public/docs/cpp/7-classes-advanced/5-2-practice2.md new file mode 100644 index 0000000..908d70d --- /dev/null +++ b/public/docs/cpp/7-classes-advanced/5-2-practice2.md @@ -0,0 +1,54 @@ +--- +id: cpp-classes-advanced-practice2 +title: '練習問題2: 動的配列クラスのコピー制御' +level: 3 +--- + +### 練習問題2: 動的配列クラスのコピー制御 + +整数 (`int`) の動的配列を管理するクラス `IntArray` を作成してください。このクラスは、コンストラクタで指定されたサイズの配列を `new` で確保し、デストラクタで `delete[]` を使って解放します。 + +この `IntArray` クラスに対して、**深いコピー**を正しく行うための**コピーコンストラクタ**と**コピー代入演算子**を実装してください。 + +```cpp:practice8_2.cpp +#include + +// ここに IntArray クラスを実装してください + +int main() { + IntArray arr1(5); // サイズ5の配列を作成 + for (int i = 0; i < 5; ++i) { + arr1.set(i, i * 10); // 0, 10, 20, 30, 40 + } + + IntArray arr2 = arr1; // コピーコンストラクタ + IntArray arr3(3); + arr3 = arr1; // コピー代入演算子 + + std::cout << "arr1: "; + for (int i = 0; i < 5; ++i) { + std::cout << arr1.get(i) << " "; + } + std::cout << std::endl; + + std::cout << "arr2 (コピー): "; + for (int i = 0; i < 5; ++i) { + std::cout << arr2.get(i) << " "; + } + std::cout << std::endl; + + std::cout << "arr3 (代入): "; + for (int i = 0; i < 5; ++i) { + std::cout << arr3.get(i) << " "; + } + std::cout << std::endl; + + return 0; +} +``` + +```cpp-exec:practice8_2.cpp +arr1: 0 10 20 30 40 +arr2 (コピー): 0 10 20 30 40 +arr3 (代入): 0 10 20 30 40 +``` diff --git a/public/docs/cpp/8-inheritance/-intro.md b/public/docs/cpp/8-inheritance/-intro.md new file mode 100644 index 0000000..7234282 --- /dev/null +++ b/public/docs/cpp/8-inheritance/-intro.md @@ -0,0 +1 @@ +オブジェクト指向プログラミング(OOP)の真の力を解放する時が来ました!💪 この章では、OOPの強力な柱である「**継承 (Inheritance)**」と「**ポリモーフィズム (Polymorphism) / 多態性**」を学びます。これらの概念をマスターすることで、コードの再利用性を高め、柔軟で拡張性の高いプログラムを設計できるようになります。 diff --git a/public/docs/cpp/8-inheritance/1-0-inheritance.md b/public/docs/cpp/8-inheritance/1-0-inheritance.md new file mode 100644 index 0000000..ceb9345 --- /dev/null +++ b/public/docs/cpp/8-inheritance/1-0-inheritance.md @@ -0,0 +1,57 @@ +--- +id: cpp-inheritance-inheritance +title: クラスの継承 +level: 2 +--- + +## クラスの継承 + +**継承**とは、既存のクラス(**親クラス**または**基底クラス**と呼びます)の機能を引き継いで、新しいクラス(**子クラス**または**派生クラス**と呼びます)を作成する仕組みです。これにより、共通の機能を何度も書く必要がなくなり、コードの重複を避けられます。 + +例えば、「動物」という大まかなクラスがあり、その特徴を引き継いで「犬」や「猫」といった具体的なクラスを作ることができます。「犬」も「猫」も「動物」が持つ「食べる」という共通の機能を持っていますよね。 + +C++では、クラス名の後に `: public 親クラス名` と書くことで継承を表現します。 + +```cpp:inheritance_basic.cpp +#include +#include + +// 親クラス (基底クラス) +class Animal { +public: + std::string name; + + void eat() { + std::cout << name << " is eating." << std::endl; + } +}; + +// 子クラス (派生クラス) +// Animalクラスのpublicメンバを引き継ぐ +class Dog : public Animal { +public: + void bark() { + std::cout << name << " says Woof!" << std::endl; + } +}; + +int main() { + Dog my_dog; + my_dog.name = "Pochi"; + + // 親クラスから継承したメンバ変数・メンバ関数 + my_dog.eat(); + + // Dogクラス独自のメンバ関数 + my_dog.bark(); + + return 0; +} +``` + +```cpp-exec:inheritance_basic.cpp +Pochi is eating. +Pochi says Woof! +``` + +この例では、`Dog`クラスは`Animal`クラスを継承しています。そのため、`Dog`クラスのオブジェクト `my_dog` は、`Animal`クラスで定義されたメンバ変数 `name` やメンバ関数 `eat()` を、まるで自分のものであるかのように利用できます。 diff --git a/public/docs/cpp/8-inheritance/2-0-polymorphism.md b/public/docs/cpp/8-inheritance/2-0-polymorphism.md new file mode 100644 index 0000000..da5aa42 --- /dev/null +++ b/public/docs/cpp/8-inheritance/2-0-polymorphism.md @@ -0,0 +1,71 @@ +--- +id: cpp-inheritance-polymorphism +title: 仮想関数 (virtual) とポリモーフィズム +level: 2 +--- + +## 仮想関数 (virtual) とポリモーフィズム + +継承の最も強力な側面は、**ポリモーフィズム(多態性)**を実現できることです。ポリモーフィズムとは、ギリシャ語で「多くの形を持つ」という意味で、プログラミングにおいては「**同じインターフェース(指示)で、オブジェクトの種類に応じて異なる振る舞いをさせる**」ことを指します。 + +これを実現するのが **仮想関数 (virtual function)** です。親クラスの関数宣言の前に `virtual` キーワードを付けると、その関数は仮想関数になります。 + +親クラスのポインタや参照は、子クラスのオブジェクトを指すことができます。このとき、呼び出された仮想関数は、ポインタが指している**オブジェクトの実際の型**に基づいて決定されます。 + +言葉だけでは難しいので、コードで見てみましょう。 + +```cpp:polymorphism_example.cpp +#include +#include + +class Animal { +public: + // speak() を仮想関数として宣言 + virtual void speak() { + std::cout << "Some generic animal sound..." << std::endl; + } +}; + +class Dog : public Animal { +public: + // 親クラスの仮想関数を上書き (オーバーライド) + void speak() override { // overrideキーワードについては後述 + std::cout << "Woof!" << std::endl; + } +}; + +class Cat : public Animal { +public: + // 親クラスの仮想関数を上書き (オーバーライド) + void speak() override { + std::cout << "Meow!" << std::endl; + } +}; + +// Animalへのポインタを受け取る関数 +void make_animal_speak(Animal* animal) { + animal->speak(); // ポインタが指す先の実際のオブジェクトに応じて、適切な speak() が呼ばれる +} + +int main() { + Animal generic_animal; + Dog dog; + Cat cat; + + std::cout << "Calling through function:" << std::endl; + make_animal_speak(&generic_animal); + make_animal_speak(&dog); // Dogオブジェクトを渡す + make_animal_speak(&cat); // Catオブジェクトを渡す + + return 0; +} +``` + +```cpp-exec:polymorphism_example.cpp +Calling through function: +Some generic animal sound... +Woof! +Meow! +``` + +`make_animal_speak` 関数は `Animal*` 型の引数を取りますが、`Dog`オブジェクトや`Cat`オブジェクトのアドレスを渡すことができています。そして、`animal->speak()` を呼び出すと、`animal` ポインタが実際に指しているオブジェクトの `speak()` が実行されます。これがポリモーフィズムです。もし `Animal`クラスの `speak()` に `virtual` が付いていなければ、どのオブジェクトを渡しても `Animal` の `speak()` が呼ばれてしまいます。 diff --git a/public/docs/cpp/8-inheritance/3-0-override.md b/public/docs/cpp/8-inheritance/3-0-override.md new file mode 100644 index 0000000..641f924 --- /dev/null +++ b/public/docs/cpp/8-inheritance/3-0-override.md @@ -0,0 +1,24 @@ +--- +id: cpp-inheritance-override +title: オーバーライド (override) +level: 2 +--- + +## オーバーライド (override) + +先ほどの例で `override` というキーワードが登場しましたね。これはC++11から導入されたもので、子クラスの関数が**親クラスの仮想関数を上書き(オーバーライド)する意図があることを明示する**ためのものです。 + +`override` を付けておくと、もし親クラスに対応する仮想関数が存在しない場合(例えば、関数名をタイプミスした場合など)に、コンパイラがエラーを検出してくれます。 + +```cpp +class Dog : public Animal { +public: + // もし親クラスのspeakがvirtualでなかったり、 + // speek() のようにタイプミスしたりすると、コンパイルエラーになる。 + void speak() override { + std::cout << "Woof!" << std::endl; + } +}; +``` + +意図しないバグを防ぐために、仮想関数をオーバーライドする際は必ず `override` を付ける習慣をつけましょう。 diff --git a/public/docs/cpp/8-inheritance/4-0-abstract-class.md b/public/docs/cpp/8-inheritance/4-0-abstract-class.md new file mode 100644 index 0000000..107f59c --- /dev/null +++ b/public/docs/cpp/8-inheritance/4-0-abstract-class.md @@ -0,0 +1,71 @@ +--- +id: cpp-inheritance-abstract-class +title: 抽象クラス +level: 2 +--- + +## 抽象クラス + +時には、「具体的な実装を持たず、子クラスに実装を強制するための設計図」としてのみ機能するクラスを定義したい場合があります。これが**抽象クラス (Abstract Class)** です。 + +抽象クラスは、**純粋仮想関数 (pure virtual function)** を1つ以上持つクラスです。純粋仮想関数は、末尾に `= 0` を付けて宣言します。 + +```cpp +virtual void function_name() = 0; // これが純粋仮想関数 +``` + +抽象クラスは以下の特徴を持ちます。 + + * インスタンス化(オブジェクトの作成)ができない。 + * 抽象クラスを継承した子クラスは、全ての純粋仮想関数をオーバーライド(実装)しなければならない。さもなければ、その子クラスもまた抽象クラスとなる。 + +```cpp:abstract_class_example.cpp +#include + +// Shapeは純粋仮想関数 draw() を持つため、抽象クラスとなる +class Shape { +public: + // 純粋仮想関数 + // このクラスを継承するクラスは、必ず draw() を実装しなければならない + virtual void draw() = 0; + + // 仮想デストラクタ (継承を扱う際は重要。詳しくは今後の章で) + virtual ~Shape() {} +}; + +class Circle : public Shape { +public: + void draw() override { + std::cout << "Drawing a circle: ○" << std::endl; + } +}; + +class Square : public Shape { +public: + void draw() override { + std::cout << "Drawing a square: □" << std::endl; + } +}; + +int main() { + // Shape my_shape; // エラー!抽象クラスはインスタンス化できない + + Circle circle; + Square square; + + Shape* shape1 = &circle; + Shape* shape2 = □ + + shape1->draw(); + shape2->draw(); + + return 0; +} +``` + +```cpp-exec:abstract_class_example.cpp +Drawing a circle: ○ +Drawing a square: □ +``` + +`Shape` クラスは「図形なら描画できるはずだ」というインターフェース(契約)を定義し、具体的な描画方法は子クラスである `Circle` や `Square` に任せています。このように、抽象クラスはプログラムの骨格となる設計を強制するのに非常に役立ちます。 diff --git a/public/docs/cpp/8-inheritance/5-0-summary.md b/public/docs/cpp/8-inheritance/5-0-summary.md new file mode 100644 index 0000000..097c881 --- /dev/null +++ b/public/docs/cpp/8-inheritance/5-0-summary.md @@ -0,0 +1,13 @@ +--- +id: cpp-inheritance-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **継承**: 既存のクラスの機能を引き継ぎ、コードの再利用性を高める仕組みです。`(子クラス) : public (親クラス)` のように書きます。 + * **ポリモーフィズム**: 「同じ指示でも、オブジェクトの種類によって異なる振る舞いをさせる」性質です。 + * **仮想関数 (`virtual`)**: ポリモーフィズムを実現するための鍵です。親クラスの関数に `virtual` を付けると、ポインタや参照経由で呼び出した際に、オブジェクトの実際の型に応じた関数が実行されます。 + * **オーバーライド (`override`)**: 子クラスで親クラスの仮想関数を上書きする意図を明示します。コンパイラがチェックしてくれるため、安全性が向上します。 + * **抽象クラス**: 1つ以上の**純粋仮想関数 (`virtual ... = 0;`)** を持つクラスです。インスタンス化できず、継承されるための設計図として機能します。 diff --git a/public/docs/cpp/8-inheritance/5-1-practice1.md b/public/docs/cpp/8-inheritance/5-1-practice1.md new file mode 100644 index 0000000..38705c0 --- /dev/null +++ b/public/docs/cpp/8-inheritance/5-1-practice1.md @@ -0,0 +1,41 @@ +--- +id: cpp-inheritance-practice1 +title: '練習問題1:乗り物の階層構造' +level: 3 +--- + +### 練習問題1:乗り物の階層構造 + +`Vehicle` という親クラスを作成し、`move()` というメンバ関数を持たせましょう。次に、`Vehicle` を継承して `Car` クラスと `Motorcycle` クラスを作成し、それぞれが独自の `move()` の振る舞いをするようにオーバーライドしてください。 + +`main` 関数では、`Vehicle` のポインタの配列を作成し、`Car` と `Motorcycle` のオブジェクトを格納して、ループでそれぞれの `move()` を呼び出してください。 + +```cpp:practice9_1.cpp +#include +#include + + +// ここに Vehicle, Car, Motorcycle クラスを定義してください + + +int main() { + // Vehicleのポインタの配列を作成 + Vehicle* vehicles[2]; + + Car my_car; + Motorcycle my_motorcycle; + + vehicles[0] = &my_car; + vehicles[1] = &my_motorcycle; + + // それぞれのmove()を呼び出す + for (int i = 0; i < 2; ++i) { + vehicles[i]->move(); + } + + return 0; +} +``` + +```cpp-exec:practice9_1.cpp +``` diff --git a/public/docs/cpp/8-inheritance/5-2-practice2.md b/public/docs/cpp/8-inheritance/5-2-practice2.md new file mode 100644 index 0000000..5505525 --- /dev/null +++ b/public/docs/cpp/8-inheritance/5-2-practice2.md @@ -0,0 +1,36 @@ +--- +id: cpp-inheritance-practice2 +title: '問題2: 従業員の給与計算' +level: 3 +--- + +### 問題2: 従業員の給与計算 + +`Employee` という抽象クラスを定義してください。このクラスは、従業員の名前を保持し、給与を計算するための純粋仮想関数 `calculate_salary()` を持ちます。 + +次に、`Employee` を継承して、`FullTimeEmployee`(月給制)と `PartTimeEmployee`(時給制)の2つのクラスを作成します。それぞれのクラスで `calculate_salary()` を具体的に実装してください。 + +`main` 関数で、それぞれのクラスのインスタンスを作成し、給与が正しく計算されることを確認してください。 + +```cpp:practice9_2.cpp +#include +#include + +// ここに Employee, FullTimeEmployee, PartTimeEmployee クラスを定義してください + + +int main() { + FullTimeEmployee full_time_emp("Alice", 3000); // 月給3000ドル + PartTimeEmployee part_time_emp("Bob", 20, 80); // 時給20ドル、80時間勤務 + + std::cout << full_time_emp.get_name() << "'s Salary: $" << full_time_emp.calculate_salary() << std::endl; + std::cout << part_time_emp.get_name() << "'s Salary: $" << part_time_emp.calculate_salary() << std::endl; + + return 0; +} +``` + +```cpp-exec:practice9_2.cpp +Alice's Salary: $3000 +Bob's Salary: $1600 +``` diff --git a/public/docs/cpp/9-templates/-intro.md b/public/docs/cpp/9-templates/-intro.md new file mode 100644 index 0000000..b21c290 --- /dev/null +++ b/public/docs/cpp/9-templates/-intro.md @@ -0,0 +1,15 @@ +これまでの章では、`int`や`double`、あるいは自作の`Car`クラスのように、特定の型に対して処理を行う関数やクラスを作成してきました。しかし、プログラムが複雑になるにつれて、「型は違うけれど、行いたい処理は全く同じ」という状況が頻繁に発生します。例えば、2つの値の大きい方を返す`max`という関数を考えてみましょう。 + +```cpp +int max_int(int a, int b) { + return (a > b) ? a : b; +} + +double max_double(double a, double b) { + return (a > b) ? a : b; +} +``` + +このように、型ごとに同じロジックの関数をいくつも用意するのは非効率的ですし、バグの温床にもなります。 + +この問題を解決するのが**テンプレート**です。テンプレートを使うと、具体的な型を "仮引数" のように扱い、様々な型に対応できる関数やクラスの「設計図」を作ることができます。このような、型に依存しないプログラミングスタイルを**ジェネリックプログラミング(汎用プログラミング)**と呼びます。 diff --git a/public/docs/cpp/9-templates/1-0-function-template.md b/public/docs/cpp/9-templates/1-0-function-template.md new file mode 100644 index 0000000..b4d9846 --- /dev/null +++ b/public/docs/cpp/9-templates/1-0-function-template.md @@ -0,0 +1,41 @@ +--- +id: cpp-templates-function-template +title: '関数テンプレート: intでもdoubleでもstringでも動く関数を作る' +level: 2 +--- + +## 関数テンプレート: intでもdoubleでもstringでも動く関数を作る + +関数テンプレートを使うと、先ほどの`max`関数の問題をエレガントに解決できます。 + +```cpp:function_template_intro.cpp +#include +#include + +// Tという名前で型を仮引数として受け取るテンプレートを宣言 +template +T max_value(T a, T b) { + return (a > b) ? a : b; +} + +int main() { + // int型でmax_valueを呼び出す + std::cout << "max(10, 20) = " << max_value(10, 20) << std::endl; + + // double型でmax_valueを呼び出す + std::cout << "max(3.14, 1.41) = " << max_value(3.14, 1.41) << std::endl; + + // std::string型でも動作する! + std::string s1 = "world"; + std::string s2 = "hello"; + std::cout << "max(\"world\", \"hello\") = " << max_value(s1, s2) << std::endl; + + return 0; +} +``` + +```cpp-exec:function_template_intro.cpp +max(10, 20) = 20 +max(3.14, 1.41) = 3.14 +max("world", "hello") = world +``` diff --git a/public/docs/cpp/9-templates/1-1-function-template-detail.md b/public/docs/cpp/9-templates/1-1-function-template-detail.md new file mode 100644 index 0000000..1bfe7b9 --- /dev/null +++ b/public/docs/cpp/9-templates/1-1-function-template-detail.md @@ -0,0 +1,23 @@ +--- +id: cpp-templates-function-template-detail +title: テンプレートの仕組み +level: 3 +--- + +### テンプレートの仕組み + +`template `という部分が、この関数がテンプレートであることを示しています。 + + * **`template <...>`**: テンプレートの宣言を開始します。 + * **`typename T`**: `T`という名前の「型引数」を定義しています。`typename`の代わりに`class`と書くこともできますが、意味は同じです。`T`は、このテンプレートが実際に使われるときに具体的な型(`int`や`double`など)に置き換えられます。 + +`main`関数で`max_value(10, 20)`のように呼び出すと、コンパイラは引数の型が`int`であることから、`T`を`int`だと自動的に判断します(これを**テンプレート引数推論**と呼びます)。そして、内部的に以下のような`int`版の関数を生成してくれるのです。 + +```cpp +// コンパイラが内部的に生成するコードのイメージ +int max_value(int a, int b) { + return (a > b) ? a : b; +} +``` + +同様に、`double`や`std::string`で呼び出されれば、それぞれの型に対応したバージョンの関数が自動的に生成されます。これにより、私たちは一つの「設計図」を書くだけで、様々な型に対応できるのです。 diff --git a/public/docs/cpp/9-templates/2-0-class-template.md b/public/docs/cpp/9-templates/2-0-class-template.md new file mode 100644 index 0000000..8d9416e --- /dev/null +++ b/public/docs/cpp/9-templates/2-0-class-template.md @@ -0,0 +1,49 @@ +--- +id: cpp-templates-class-template +title: 'クラステンプレート: 様々な型のデータを格納できるクラスを作る' +level: 2 +--- + +## クラステンプレート: 様々な型のデータを格納できるクラスを作る + +テンプレートの力は、クラスにも適用できます。これにより、様々な型のデータを格納できる汎用的なクラス(コンテナなど)を作成できます。例えば、「2つの値をペアで保持する」クラスを考えてみましょう。 + +```cpp:class_template_intro.cpp +#include +#include + +// 2つの型 T1, T2 を引数に取るクラステンプレート +template +class Pair { +public: + T1 first; + T2 second; + + // コンストラクタ + Pair(T1 f, T2 s) : first(f), second(s) {} + + void print() { + std::cout << "(" << first << ", " << second << ")" << std::endl; + } +}; + +int main() { + // T1=int, T2=std::string としてPairクラスのオブジェクトを生成 + Pair p1(1, "apple"); + p1.print(); + + // T1=std::string, T2=double としてPairクラスのオブジェクトを生成 + Pair p2("pi", 3.14159); + p2.print(); + + // 違う型のPair同士は当然、別の型として扱われる + // p1 = p2; // これはコンパイルエラーになる + + return 0; +} +``` + +```cpp-exec:class_template_intro.cpp +(1, apple) +(pi, 3.14159) +``` diff --git a/public/docs/cpp/9-templates/2-1-class-template-detail.md b/public/docs/cpp/9-templates/2-1-class-template-detail.md new file mode 100644 index 0000000..a5f6e46 --- /dev/null +++ b/public/docs/cpp/9-templates/2-1-class-template-detail.md @@ -0,0 +1,34 @@ +--- +id: cpp-templates-class-template-detail +title: クラステンプレートの仕組み +level: 3 +--- + +### クラステンプレートの仕組み + +関数テンプレートと基本的な考え方は同じですが、いくつか重要な違いがあります。 + +1. **明示的な型指定**: + 関数テンプレートではコンパイラが型を推論してくれましたが、クラステンプレートの場合は、オブジェクトを生成する際に`Pair`のように、開発者が明示的に型を指定する必要があります。 + +2. **インスタンス化**: + `Pair`のように具体的な型を指定してオブジェクトを作ることを、テンプレートの**インスタンス化**と呼びます。コンパイラは、この指定に基づいて`T1`を`int`に、`T2`を`std::string`に置き換えた、以下のような新しいクラスを内部的に生成します。 + + ```cpp + // コンパイラが内部的に生成するクラスのイメージ + class Pair_int_string { // クラス名は実際には異なります + public: + int first; + std::string second; + + Pair_int_string(int f, std::string s) : first(f), second(s) {} + + void print() { + std::cout << "(" << first << ", " << second << ")" << std::endl; + } + }; + ``` + + `Pair`と`Pair`は、コンパイルされると全く別のクラスとして扱われることに注意してください。 + +クラステンプレートは、C++の強力なライブラリである**STL (Standard Template Library)**の根幹をなす技術です。次章で学ぶ`vector`や`map`といった便利なコンテナは、すべてクラステンプレートで実装されています。 diff --git a/public/docs/cpp/9-templates/3-0-summary.md b/public/docs/cpp/9-templates/3-0-summary.md new file mode 100644 index 0000000..b95a9f9 --- /dev/null +++ b/public/docs/cpp/9-templates/3-0-summary.md @@ -0,0 +1,14 @@ +--- +id: cpp-templates-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **ジェネリックプログラミング**は、特定の型に縛られない、汎用的なコードを書くための手法です。 + * **テンプレート**は、C++でジェネリックプログラミングを実現するための機能です。 + * **関数テンプレート**を使うと、様々な型の引数に対して同じ処理を行う関数を定義できます。呼び出し時には、コンパイラが**テンプレート引数推論**によって型を自動的に決定します。 + * **クラステンプレート**を使うと、様々な型を扱える汎用的なクラスを定義できます。オブジェクトを生成する際には、`< >`内に具体的な型を**明示的に指定**してインスタンス化する必要があります。 + +テンプレートを使いこなすことで、コードの再利用性が劇的に向上し、より柔軟で堅牢なプログラムを記述できるようになります。 diff --git a/public/docs/cpp/9-templates/3-1-practice1.md b/public/docs/cpp/9-templates/3-1-practice1.md new file mode 100644 index 0000000..e51653e --- /dev/null +++ b/public/docs/cpp/9-templates/3-1-practice1.md @@ -0,0 +1,35 @@ +--- +id: cpp-templates-practice1 +title: '練習問題1: 汎用的なprint関数' +level: 3 +--- + +### 練習問題1: 汎用的なprint関数 + +任意の型の配列(ここでは`std::vector`を使いましょう)を受け取り、その要素をすべて画面に出力する関数テンプレート`print_elements`を作成してください。 + +```cpp:practice10_1.cpp +#include +#include +#include + +// ここに関数テンプレート print_elements を実装してください + + +int main() { + std::vector v_int = {1, 2, 3, 4, 5}; + std::cout << "Integers: "; + print_elements(v_int); + + std::vector v_str = {"C++", "is", "powerful"}; + std::cout << "Strings: "; + print_elements(v_str); + + return 0; +} +``` + +```cpp-exec:practice10_1.cpp +Integers: 1 2 3 4 5 +Strings: C++ is powerful +``` diff --git a/public/docs/cpp/9-templates/3-2-practice2.md b/public/docs/cpp/9-templates/3-2-practice2.md new file mode 100644 index 0000000..9dbab9a --- /dev/null +++ b/public/docs/cpp/9-templates/3-2-practice2.md @@ -0,0 +1,46 @@ +--- +id: cpp-templates-practice2 +title: '練習問題2: 汎用的なスタッククラス' +level: 3 +--- + +### 練習問題2: 汎用的なスタッククラス + +後入れ先出し(LIFO)のデータ構造であるスタックを、クラステンプレート`SimpleStack`として実装してください。以下のメンバ関数を持つようにしてください。 + + * `void push(T item)`: スタックに要素を追加する + * `T pop()`: スタックの先頭から要素を取り出す + * `bool is_empty()`: スタックが空かどうかを返す + +`std::vector`を内部のデータ格納場所として利用して構いません。`int`型と`char`型で動作を確認してください。 + +```cpp:practice10_2.cpp +#include +#include +#include + +// ここにクラステンプレート SimpleStack を実装してください + +int main() { + SimpleStack int_stack; + int_stack.push(10); + int_stack.push(20); + std::cout << "Popped from int_stack: " << int_stack.pop() << std::endl; // 20 + std::cout << "Popped from int_stack: " << int_stack.pop() << std::endl; // 10 + + SimpleStack char_stack; + char_stack.push('A'); + char_stack.push('B'); + std::cout << "Popped from char_stack: " << char_stack.pop() << std::endl; // B + std::cout << "Popped from char_stack: " << char_stack.pop() << std::endl; // A + + return 0; +} +``` + +```cpp-exec:practice10_2.cpp +Popped from int_stack: 20 +Popped from int_stack: 10 +Popped from char_stack: B +Popped from char_stack: A +``` diff --git a/public/docs/cpp/index.yml b/public/docs/cpp/index.yml new file mode 100644 index 0000000..46883fb --- /dev/null +++ b/public/docs/cpp/index.yml @@ -0,0 +1,42 @@ +name: C++ +description: C++の基本から高度な機能までを学べるチュートリアル +pages: +- slug: 0-intro + name: C++の世界へようこそ + title: C++の世界へようこそ +- slug: 1-types-control + name: 型システムと制御構造 + title: C++の型システムと制御構造:静的型付けとスコープを再確認する +- slug: 2-data-containers + name: データ集合とモダンな操作 + title: データ集合とモダンな操作:「配列」ではなく「コンテナ」としてデータを扱う +- slug: 3-pointers + name: ポインタとメモリ管理 + title: ポインタとメモリ管理の深淵 +- slug: 4-functions + name: 関数と参照渡し + title: 関数の設計とデータの受け渡し:コピー、参照、ポインタ +- slug: 5-project-build + name: プロジェクトの分割とビルド + title: プロジェクトの分割とビルド +- slug: 6-classes-basics + name: クラスの基礎 + title: オブジェクト指向の入口:クラスの基礎 +- slug: 7-classes-advanced + name: クラスを使いこなす + title: クラスを使いこなす +- slug: 8-inheritance + name: 継承とポリモーフィズム + title: 継承とポリモーフィズム +- slug: 9-templates + name: テンプレート + title: テンプレートによる汎用プログラミング +- slug: 10-stl-containers + name: STL ①:コンテナ + title: 標準テンプレートライブラリ (STL) ①:コンテナ +- slug: 11-stl-algorithms + name: STL ②:アルゴリズムとラムダ式 + title: 標準テンプレートライブラリ (STL) ②:アルゴリズムとラムダ式 +- slug: 12-raii-smart-ptrs + name: RAIIとスマートポインタ + title: モダンC++の流儀:RAIIとスマートポインタ diff --git a/public/docs/javascript-1.md b/public/docs/javascript-1.md deleted file mode 100644 index 590cbea..0000000 --- a/public/docs/javascript-1.md +++ /dev/null @@ -1,108 +0,0 @@ -# 第1章: JavaScriptへようこそ - -本章では、JavaScript(以下JS)がどのような思想で設計され、JavaやPython、C\#といった他の言語とどう異なるのか、その全体像を把握します。また、学習に必要な環境構築と最初のコード実行を行います。 - -## JavaScriptとは? - -JavaScriptは1995年、Netscape社のBrendan Eichによってわずか10日間でプロトタイプが作成されました。当初はWebページに軽い動きをつけるための言語でしたが、現在では**ECMAScript (ES)** として標準化され、フロントエンドからバックエンド、モバイルアプリまで幅広く利用されています。 - -経験豊富なエンジニアが押さえておくべき特徴は以下の3点です。 - -1. **動的型付け (Dynamic Typing):** - 変数は型を持ちません。値が型を持ちます。コンパイル時ではなく実行時に型が決まるため、柔軟ですが、実行時エラーのリスク管理が必要です(現代ではTypeScriptで補うのが一般的です)。 -2. **マルチパラダイム:** - 命令型の手続き記述はもちろん、**関数型プログラミング**(第一級関数、クロージャ)や、**プロトタイプベースのオブジェクト指向**をサポートします。クラスベースの言語に慣れていると、ここの概念モデルの違いに驚くかもしれません。 -3. **シングルスレッド & ノンブロッキングI/O:** - JSのランタイムは基本的にシングルスレッドです。しかし、**イベントループ**という仕組みにより、重いI/O操作(ネットワーク通信やファイル読み込み)を非同期で処理し、メインスレッドをブロックせずに高い並行性を実現します。 - -## 実行環境: ブラウザ vs Node.js - -JavaScriptはどこで動くのでしょうか? かつてはブラウザの中だけでしたが、現在は大きく分けて2つの環境があります。 - - * **Webブラウザ (クライアントサイド):** - - * DOM (Document Object Model) 操作により、HTML/CSSを動的に書き換えます。 - * `window` オブジェクトがグローバルスコープです。 - * セキュリティ上の制約(サンドボックス)があり、ローカルファイルへの直接アクセスなどは制限されています。 - - * **Node.js (サーバーサイド):** - - * ChromeのV8 JavaScriptエンジンをブラウザの外に取り出したランタイムです。 - * OSの機能(ファイルシステム、ネットワーク)にアクセス可能です。 - * DOMは存在しません。Webサーバー構築やCLIツールの作成に使われます。 - -言語仕様(コア機能)は同じですが、**「何ができるか(API)」は環境に依存する**という点を意識してください。 - -## 他言語との比較 - -あなたが既に知っている言語とJSを比較してみましょう。 - -| 特徴 | Java / C\# | Python | JavaScript | -| :--- | :--- | :--- | :--- | -| **型システム** | 静的型付け (強い型付け) | 動的型付け (強い型付け) | **動的型付け (弱い型付け)** | -| **並行処理** | マルチスレッド | マルチスレッド (GILあり) | **シングルスレッド + イベントループ** | -| **OOP** | クラスベース | クラスベース | **プロトタイプベース** (class構文はシンタックスシュガー) | -| **実行方式** | コンパイル (JVM/CLR) | インタープリタ | **JITコンパイル** (多くのエンジン) | - - * **Java/C\#ユーザーへの注記:** JSの`class`は見た目は似ていますが、裏側の仕組み(プロトタイプチェーン)は全く異なります。また、コンパイルエラーで弾かれるようなコードも、JSでは実行できてしまう(そして実行時に落ちる)ことがあります。 - * **Pythonユーザーへの注記:** Pythonの`asyncio`に似ていますが、JSは**デフォルトで非同期**を前提としています。また、インデントではなく波括弧 `{}` でブロックを定義します。 - -## "Hello, World\!" - -実際にコードを動かしてみましょう。ここでは2つの方法を紹介します。 - -### REPL (Read-Eval-Print Loop) での実行 - -ちょっとした動作確認にはREPLが便利です。Node.jsのREPLを起動するには、ターミナルで `node` と入力して起動します。 - -このウェブサイトではドキュメント内にJavaScriptの実行環境を埋め込んでおり、以下のように緑枠で囲われたコード例には自由にJavaScriptコードを書いて試すことができます。ただしNode.jsとは環境が異なり、Node.js特有の機能は使用できません。 - -```js-repl:1 -> console.log("Hello, World from REPL!"); -Hello, World from REPL! -undefined -> 1 + 2 -3 -``` - -※ `undefined` は `console.log` 関数の戻り値が表示されています。 - -### ソースファイルからの実行 - -本格的なプログラムはファイルに記述します。 - -まず、以下の内容で `hello.js` というファイルを作成してください。 - -```js:hello.js -// 変数定義 (後述しますが、現代ではconstを使います) -const greeting = "Hello, World!"; -const target = "Node.js"; - -// テンプレートリテラル (バッククォート ` を使用) -console.log(`${greeting} I am running on ${target}.`); -``` - -ターミナルでファイルのあるディレクトリに移動し、`node` コマンドで実行します。 - -```js-exec:hello.js -Hello, World! I am running on Node.js. -``` - -### ブラウザでの実行 (参考) - -ブラウザで動かす場合は、HTMLファイルが必要です。 -`index.html` を作成し、以下のように記述してブラウザで開いてみてください。 - -```html - - - - - - -``` - -ブラウザの開発者ツール(Consoleタブ)にメッセージが表示され、ポップアップウィンドウが出れば成功です。 diff --git a/public/docs/javascript-10.md b/public/docs/javascript-10.md deleted file mode 100644 index 11f7cc8..0000000 --- a/public/docs/javascript-10.md +++ /dev/null @@ -1,247 +0,0 @@ -# 第10章: 非同期処理(2)- Async/Await と Fetch API - -前回(第9章)では、JavaScriptの非同期処理の要である `Promise` について学びました。しかし、`.then()` チェーンが長く続くと、コードの可読性が下がる(いわゆる「コールバック地獄」に近い状態になる)ことがあります。 - -第10章では、この課題を解決するために導入された **Async/Await** 構文と、現代的なHTTP通信の標準である **Fetch API** について解説します。他の言語で同期的なコード(ブロッキング処理)に慣れ親しんだ方にとって、Async/Await は非常に直感的で扱いやすい機能です。 - -## Async/Await 構文 - -`async` と `await` は、ES2017で導入された `Promise` の**シンタックスシュガー(糖衣構文)**です。これを使うことで、非同期処理をあたかも「同期処理」のように上から下へと流れるコードとして記述できます。 - -### `async` 関数 - -関数宣言の前に `async` キーワードを付けると、その関数は自動的に **Promiseを返す** ようになります。値を `return` した場合、それは `Promise.resolve(値)` と同じ意味になります。 - -```js-repl:1 -> async function getMessage() { return "Hello, Async!"; } -undefined -> // async関数は常にPromiseを返す -> getMessage() -Promise { 'Hello, Async!' } - -> // 通常のPromiseと同じくthenで値を取り出せる -> getMessage().then(v => console.log(v)) -Promise { } -Hello, Async! -``` - -### `await` 式 - -`async` 関数の内部(またはモジュールのトップレベル)でのみ使用できるキーワードです。 -`await` は、右側の Promise が **Settled(解決または拒否)されるまで関数の実行を一時停止** します。Promiseが解決されると、その結果の値を返して実行を再開します。 - -これは、C\# の `async/await` や Python の `asyncio` に慣れている方にはおなじみの挙動でしょう。 - -```js-repl:2 -> function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -undefined -> async function run() { -... console.log("Start"); -... await delay(1000); // 1秒待機(ここで実行が一時停止) -... console.log("End"); -... } -undefined -> run() -Promise { } -// (1秒後に表示) -Start -End -``` - -## try...catch によるエラーハンドリング - -生の `Promise` では `.catch()` メソッドを使ってエラーを処理しましたが、Async/Await では、他の言語と同様に標準的な `try...catch` 構文を使用できます。これにより、同期エラーと非同期エラーを同じ構文で扱えるようになります。 - -```js:async_try_catch.js -// ランダムに成功・失敗する非同期関数 -function randomRequest() { - return new Promise((resolve, reject) => { - setTimeout(() => { - const success = Math.random() > 0.5; - if (success) { - resolve("Success: データ取得完了"); - } else { - reject(new Error("Failure: サーバーエラー")); - } - }, 500); - }); -} - -async function main() { - console.log("処理開始..."); - try { - // awaitしているPromiseがrejectされると、例外がスローされる - const result = await randomRequest(); - console.log(result); - } catch (error) { - // ここでエラーを捕捉 - console.error("エラーが発生しました:", error.message); - } finally { - console.log("処理終了"); - } -} - -main(); -``` - -```js-exec:async_try_catch.js -処理開始... -エラーが発生しました: Failure: サーバーエラー -処理終了 -``` - -*(※注: 実行結果はランダムで成功する場合もあります)* - -## Fetch API によるHTTPリクエスト - -JavaScript(特にブラウザ環境や最近のNode.js)でHTTPリクエストを行うための標準APIが `fetch` です。以前は `XMLHttpRequest` という扱いづらいAPIが使われていましたが、現在は `fetch` が主流です。 - -`fetch` 関数は `Promise` を返します。 - -基本的な流れは以下の通りです: - -1. `fetch(url)` を実行し、レスポンスヘッダーが届くのを待つ。 -2. Responseオブジェクトを受け取る。 -3. Responseオブジェクトからメソッド(`.json()`, `.text()`など)を使ってボディを読み込む(これも非同期)。 - -```js:fetch_basic.js -// 外部APIからJSONデータを取得する例 -// (Node.js 18以上ではfetchが標準で使用可能です) - -async function getUserData(userId) { - const url = `https://jsonplaceholder.typicode.com/users/${userId}`; - - try { - // 1. リクエスト送信 (ネットワークエラー以外はrejectされない) - const response = await fetch(url); - - // 2. HTTPステータスコードの確認 - if (!response.ok) { - throw new Error(`HTTP Error: ${response.status}`); - } - - // 3. レスポンスボディをJSONとしてパース (これもPromiseを返す) - const data = await response.json(); - - console.log(`Name: ${data.name}`); - console.log(`Email: ${data.email}`); - - } catch (error) { - console.error("Fetch failed:", error.message); - } -} - -getUserData(1); -``` - -```js-exec:fetch_basic.js -Name: Leanne Graham -Email: Sincere@april.biz -``` - -### JSONデータの送信 (POST) - -データを送信する場合は、第2引数にオプションオブジェクトを渡します。 - -```js-repl:3 -> const postData = { title: 'foo', body: 'bar', userId: 1 }; -> await fetch('https://jsonplaceholder.typicode.com/posts', { -... method: 'POST', -... headers: { 'Content-Type': 'application/json' }, -... body: JSON.stringify(postData) -... }).then(res => res.json()) -{ title: 'foo', body: 'bar', userId: 1, id: 101 } -``` - -## Promise.all() と Promise.race() - -Async/Await は便利ですが、単純に `await` を連発すると、処理が**直列(シーケンシャル)**になってしまい、パフォーマンスが落ちる場合があります。複数の独立した非同期処理を行う場合は、並列実行を検討します。 - -### 直列実行(遅いパターン) - -```javascript -// Aが終わってからBを開始する -const user = await fetchUser(); -const posts = await fetchPosts(); -``` - -### Promise.all() による並列実行 - -複数のPromiseを配列として受け取り、**全て完了するのを待って**から結果の配列を返します。一つでも失敗すると全体が失敗(reject)します。 - -```js:promise_all.js -const wait = (ms, value) => new Promise(r => setTimeout(() => r(value), ms)); - -async function parallelDemo() { - console.time("Total Time"); - - // 2つの処理を同時に開始 - const p1 = wait(1000, "User Data"); - const p2 = wait(1000, "Post Data"); - - try { - // 両方の完了を待つ - const [user, post] = await Promise.all([p1, p2]); - console.log("Result:", user, "&", post); - } catch (e) { - console.error(e); - } - - // 本来なら直列だと2秒かかるが、並列なので約1秒で終わる - console.timeEnd("Total Time"); -} - -parallelDemo(); -``` - -```js-exec:promise_all.js -Result: User Data & Post Data -Total Time: 1.008s -``` - -### Promise.race() - -複数のPromiseのうち、**最も早く完了(または失敗)したもの**の結果だけを返します。タイムアウト処理の実装などによく使われます。 - -```js-repl:4 -> const fast = new Promise(r => setTimeout(() => r("Fast"), 100)); -> const slow = new Promise(r => setTimeout(() => r("Slow"), 500)); -> await Promise.race([fast, slow]) -'Fast' -``` - -## この章のまとめ - - * **Async/Await**: `Promise` をベースにした糖衣構文。非同期処理を同期処理のように記述でき、可読性が高い。 - * **Error Handling**: 同期コードと同じく `try...catch` が使用可能。 - * **Fetch API**: モダンなHTTP通信API。`response.ok` でステータスを確認し、`response.json()` でボディをパースする2段構えが必要。 - * **並列処理**: 独立した複数の非同期処理は `await` を連続させるのではなく、`Promise.all()` を使用して並列化することでパフォーマンスを向上させる。 - -## 練習問題 - -### 問題1: ユーザー情報の取得と表示 - -以下の要件を満たす関数 `displayUserSummary(userId)` を作成してください。 - -1. `https://jsonplaceholder.typicode.com/users/{userId}` からユーザー情報を取得する。 -2. `https://jsonplaceholder.typicode.com/users/{userId}/todos` からそのユーザーのTODOリストを取得する。 -3. 上記2つのリクエストは、**パフォーマンスを考慮して並列に実行**すること。 -4. 取得したデータから、「ユーザー名」と「完了済み(completed: true)のTODOの数」を出力する。 -5. 通信エラー時は適切にエラーメッセージを表示する。 - -```js:practice10_1.js -``` - -```js-exec:practice10_1.js -``` - -### 問題2: タイムアウト付きFetch - -指定したURLからデータを取得するが、一定時間内にレスポンスが返ってこない場合は「タイムアウト」としてエラーにする関数 `fetchWithTimeout(url, ms)` を作成してください。 -*ヒント: `fetch` のPromiseと、指定時間後に reject するPromiseを `Promise.race()` で競走させてください。* - -```js:practice10_2.js -``` - -```js-exec:practice10_2.js -``` diff --git a/public/docs/javascript-2.md b/public/docs/javascript-2.md deleted file mode 100644 index e49db21..0000000 --- a/public/docs/javascript-2.md +++ /dev/null @@ -1,250 +0,0 @@ -# 第2章: 基本構文とデータ型 - -プログラミング経験者であるあなたにとって、変数の概念やデータ型の存在自体は目新しいものではありません。しかし、JavaScriptには「動的型付け」や「歴史的経緯による特殊なスコープ仕様」、「独特な型変換」といった、他言語経験者が特に躓きやすいポイントがあります。 - -本章では、モダンなJavaScript(ES6以降)における標準的な記述方法を中心に、レガシーな仕様との違いや、バグを生みやすい落とし穴について解説します。 - -## 変数宣言: let, const, var - -現代のJavaScript開発において、変数宣言のルールは非常にシンプルです。 -**「基本は `const`、再代入が必要な場合のみ `let` を使い、`var` は決して使わない」** これが鉄則です。 - -### const と let (ブロックスコープ) - -ES6(2015年)で導入された `const` と `let` は、C++やJava、C\#などと同様に**ブロックスコープ**を持ちます。 - - * **const**: 再代入不可能な変数を宣言します。定数だけでなく、再代入しない変数はすべてこれで宣言します。 - * **let**: 再代入可能な変数を宣言します。ループカウンタや、状態が変わる値に使用します。 - -### var の危険性 (関数スコープと巻き上げ) - -なぜ `var` を使うべきではないのでしょうか。それは `var` が**関数スコープ**であり、意図しない変数の共有や「巻き上げ(Hoisting)」によるバグを引き起こしやすいからです。 - -以下のコードで、スコープの違いを確認してみましょう。 - -```js:scope_demo.js -function checkScope() { - if (true) { - var functionScoped = "I am visible outside this block"; - let blockScoped = "I am NOT visible outside this block"; - const constantValue = "I am also block scoped"; - } - - console.log("var output:", functionScoped); // 参照可能(関数スコープのため) - - try { - console.log("let output:", blockScoped); // ReferenceError - } catch (e) { - console.error("let error:", e.message); - } -} - -checkScope(); -``` - -```js-exec:scope_demo.js -var output: I am visible outside this block -let error: blockScoped is not defined -``` - -## データ型: プリミティブ型 - -JavaScriptは動的型付け言語であり、変数は特定の型に紐付きませんが、値自体は型を持っています。JavaScriptの値は大きく分けて「プリミティブ型」と「オブジェクト(参照)型」に分類されます。 - -プリミティブ型はイミュータブル(変更不可)であり、以下の7種類が存在します。 - -1. **String**: 文字列。ES6から導入された「テンプレートリテラル(バッククォート `` ` ``)」を使うと、変数の埋め込みが容易です。 -2. **Number**: 数値。整数と浮動小数点数の区別はなく、すべて倍精度浮動小数点数(IEEE 754)として扱われます。 -3. **Boolean**: `true` または `false`。 -4. **undefined**: 「値が未定義である」ことを表す型。変数を宣言して値を代入していない状態です。 -5. **null**: 「値が存在しない」ことを意図的に示す型。 -6. **Symbol**: 一意で不変な識別子。オブジェクトのプロパティキーなどに使われます。 -7. **BigInt**: `Number`型では表現できない巨大な整数を扱います(末尾に `n` をつけます)。 - -### null と undefined の違い - -他言語経験者にとって混乱しやすいのがこの2つです。 - - * **undefined**: システム(JavaScriptエンジン)が「値がまだない」ことを示すために使うことが多い。 - * **null**: プログラマが「ここには値がない」ことを明示するために使うことが多い。 - -```js-repl:1 -> let unassigned; -undefined -> unassigned -undefined -> let empty = null; -undefined -> typeof unassigned -'undefined' -> typeof empty // JSの有名なバグ(仕様)で 'object' が返りますが、実際はプリミティブです -'object' -``` - -## データ型: オブジェクト型 - -プリミティブ以外のすべての値は**オブジェクト(参照型)**です。これらはメモリ上のアドレス(参照)として扱われます。 - -### const とオブジェクトの変更 - -重要な点として、`const` で宣言した変数は「再代入」ができませんが、中身がオブジェクトの場合、**プロパティの変更は可能**です。これは `const` が「参照先のメモリアドレス」を固定するものであり、ヒープ領域にあるデータそのものを不変にするわけではないためです。 - -```js:object_mutation.js -const user = { - name: "Alice", - id: 1 -}; - -// 再代入はエラーになる -// user = { name: "Bob" }; // TypeError: Assignment to constant variable. - -// プロパティの変更は可能 -user.name = "Bob"; -console.log(user); - -// 配列もオブジェクトの一種 -const colors = ["Red", "Green"]; -colors.push("Blue"); -console.log(colors); -``` - -```js-exec:object_mutation.js -{ name: 'Bob', id: 1 } -[ 'Red', 'Green', 'Blue' ] -``` - -主なオブジェクト型には以下があります。 - - * **Object**: キーと値のペアの集合(辞書、ハッシュマップに近い)。 - * **Array**: 順序付きリスト。 - * **Function**: JavaScriptでは関数もオブジェクトであり、変数に代入したり引数として渡すことができます(第一級関数)。 - -## 演算子と等価性 (== vs ===) - -JavaScriptにおける最大の落とし穴の一つが「等価演算子」です。 - -### 厳密等価演算子 (===) を使う - -常に `===` (および `!==`)を使用してください。これは「値」と「型」の両方が等しいかを比較します。 - -### 等価演算子 (==) の罠 - -`==` は、比較する前に**暗黙的な型変換**を行います。これにより、直感的ではない結果が生じることがあります。 - -```js-repl:2 -> 1 === "1" // 型が違うので false(推奨) -false -> 1 == "1" // 文字列が数値に変換されて比較されるため true(非推奨) -true -> 0 == false // true -true -> null == undefined // true(ここだけは例外的に許容するスタイルもあるが、基本は避ける) -true -> [] == ![] // 非常に難解な挙動(trueになる) -true -``` - -## 型変換(暗黙的な型変換の罠) - -JavaScriptは文脈に応じて勝手に型を変換しようとします。 - -### 加算演算子 (+) の挙動 - -`+` 演算子は、数値の加算だけでなく文字列の連結にも使われます。片方が文字列であれば、もう片方も文字列に変換されて連結されます。 - -```js-repl:3 -> 10 + 20 -30 -> 10 + "20" // 数値が文字列 "10" に変換され連結される -'1020' -> "10" + 20 -'1020' -> 10 - "2" // 減算は数値計算しかないので、文字列 "2" が数値に変換される -8 -``` - -### Falsyな値 - -条件式(if文など)で `false` とみなされる値を「Falsyな値」と呼びます。これ以外はすべて `true`(Truthy)として扱われます。 - -**Falsyな値のリスト:** - -1. `false` -2. `0` (数値のゼロ) -3. `-0` -4. `0n` (BigIntのゼロ) -5. `""` (空文字) -6. `null` -7. `undefined` -8. `NaN` (Not a Number) - -**注意:** 空の配列 `[]` や空のオブジェクト `{}` は **Truthy** です。 - -```js:falsy_check.js -const values = [0, "0", [], null, undefined, ""]; - -values.forEach(val => { - if (val) { - console.log(`Value: [${val}] is Truthy`); - } else { - console.log(`Value: [${val}] is Falsy`); - } -}); -``` - -```js-exec:falsy_check.js -Value: [0] is Falsy -Value: [0] is Truthy -Value: [] is Truthy -Value: [null] is Falsy -Value: [undefined] is Falsy -Value: [] is Falsy -``` - -## この章のまとめ - - * 変数は `const` をデフォルトとし、再代入が必要な場合のみ `let` を使う。`var` は使用しない。 - * プリミティブ型は値渡し、オブジェクト型(配列含む)は参照渡しである。 - * `const` で宣言したオブジェクトの中身は変更可能である。 - * 比較には必ず `===`(厳密等価演算子)を使用し、`==` による暗黙の型変換を避ける。 - * `0`, `""`, `null`, `undefined` などが「Falsyな値」として扱われることを理解する。 - -### 練習問題1: テンプレートリテラルと型変換 - -ユーザーの年齢(数値)と名前(文字列)を受け取り、自己紹介文を作成する関数を作成してください。 -ただし、年齢が `null` または `undefined` の場合は「不明」と表示するようにしてください。論理和演算子 `||` または Null合体演算子 `??` を活用してみましょう。 - -```js:practice2_1.js -// 以下の関数を完成させてください -function introduce(name, age) { - // ここにコードを記述 -} - -console.log(introduce("Tanaka", 25)); -console.log(introduce("Sato", null)); -``` - -```js-exec:practice2_1.js -My name is Tanaka and I am 25 years old. -My name is Sato and I am 不明 years old. -``` - -### 練習問題2: オブジェクトの操作と参照 - -以下のコードにはバグ(意図しない挙動)があります。 -`originalList` の内容を保持したまま、新しい要素を追加した `newList` を作成したいのですが、現状では `originalList` も変更されてしまいます。 -スプレッド構文 `...` などを使い、`originalList` を変更せずに `newList` を作成するように修正してください。 - -```js:practice2_2.js -const originalList = ["Apple", "Banana"]; - -// 参照コピーになっているため originalList も変わってしまう -const newList = originalList; -newList.push("Orange"); - -console.log("Original:", originalList); // ["Apple", "Banana"] と出力させたい -console.log("New:", newList); // ["Apple", "Banana", "Orange"] と出力させたい -``` - -```js-exec:practice2_2.js -``` diff --git a/public/docs/javascript-3.md b/public/docs/javascript-3.md deleted file mode 100644 index 4540435..0000000 --- a/public/docs/javascript-3.md +++ /dev/null @@ -1,264 +0,0 @@ -# 第3章: 制御構文 - -他の言語での開発経験がある方にとって、JavaScriptの制御構文の多くは馴染み深いものでしょう。しかし、JavaScript特有の「真偽値の評価(Truthy/Falsy)」や「反復処理(Iteration)の種類の多さ」は、バグを生みやすいポイントでもあります。 - -この章では、構文そのものだけでなく、JavaScriptならではの挙動やベストプラクティスに焦点を当てて解説します。 - -## 条件分岐 (if, switch) - -### if文とTruthy / Falsy - -基本的な `if` 文の構造はC言語やJavaと同様です。しかし、条件式における評価はJavaScript特有の**Truthy(真と見なされる値)**と**Falsy(偽と見なされる値)**の概念を理解する必要があります。 - -厳密な `true` / `false` だけでなく、あらゆる値が条件式の中で真偽判定されます。 - -**Falsyな値(falseとして扱われるもの):** - - * `false` - * `0`, `-0`, `0n` (BigInt) - * `""` (空文字) - * `null` - * `undefined` - * `NaN` - -**Truthyな値(trueとして扱われるもの):** - - * 上記Falsy以外すべて - * **注意:** 空の配列 `[]` や 空のオブジェクト `{}` は **Truthy** です(Pythonなどの経験者は注意が必要です)。 - * 文字列の `"0"` や `"false"` もTruthyです。 - -```js-repl:1 -> if (0) { 'True'; } else { 'False'; } -'False' - -> if ("") { 'True'; } else { 'False'; } -'False' - -> if ([]) { 'True'; } else { 'False'; } // 空配列は真! -'True' - -> const user = { name: "Alice" }; -> if (user) { `Hello ${user.name}`; } -'Hello Alice' -``` - -### switch文 - -`switch` 文も標準的ですが、比較が **厳密等価演算子 (`===`)** で行われる点に注意してください。型変換は行われません。 - -```js:switch_example.js -const status = "200"; // 文字列 - -switch (status) { - case 200: // 数値の200と比較 -> false - console.log("OK (Number)"); - break; - case "200": // 文字列の"200"と比較 -> true - console.log("OK (String)"); - break; - default: - console.log("Unknown status"); -} -``` - -```js-exec:switch_example.js -OK (String) -``` - -## 繰り返し (for, while) - -`while`, `do...while`, および古典的な `for` ループは、C/Java/C++等の構文とほぼ同じです。 - -### 古典的な for ループ - -```js-repl:2 -> for (let i = 0; i < 3; i++) { console.log(i); } -0 -1 -2 -``` - -### while ループ - -```js-repl:3 -> let count = 0; -> while (count < 3) { console.log(count++); } -0 -1 -2 -``` - -## イテレーション: for...of と for...in の違い - -現代のJavaScript開発において、最も重要なのがこの2つのループの使い分けです。これらは似ていますが、役割が明確に異なります。 - -### for...in ループ(プロパティ名の列挙) - -`for...in` はオブジェクトの **キー(プロパティ名)** を列挙するために設計されています。 -配列に対して使用すると、インデックス("0", "1", ...)が文字列として返ってくるだけでなく、プロトタイプチェーン上のプロパティまで列挙してしまうリスクがあるため、**配列への使用は推奨されません**。 - -```js:for_in_example.js -const user = { - name: "Bob", - age: 30, - role: "admin" -}; - -// オブジェクトのキーを列挙する -for (const key in user) { - console.log(`${key}: ${user[key]}`); -} - -// 配列に対する for...in(非推奨の例) -const colors = ["Red", "Green"]; -Array.prototype.badProp = "Do not do this"; // プロトタイプ汚染のシミュレーション - -console.log("--- Array via for...in ---"); -for (const index in colors) { - console.log(index); // "0", "1", "badProp" が出力される可能性がある -} -``` - -```js-exec:for_in_example.js -name: Bob -age: 30 -role: admin ---- Array via for...in --- -0 -1 -badProp -``` - -### for...of ループ(反復可能オブジェクトの走査) - -ES2015 (ES6) で導入された `for...of` は、**値(Values)** を反復します。 -配列、文字列、Map、Setなどの **Iterable(反復可能)** なオブジェクトに対して使用します。配列の中身を順番に処理したい場合は、こちらが正解です。 - -```js:for_of_example.js -const languages = ["JavaScript", "Python", "Go"]; - -// 配列の値を直接取得できる -for (const lang of languages) { - console.log(lang); -} - -// 文字列もIterable -const word = "AI"; -for (const char of word) { - console.log(char); -} -``` - -```js-exec:for_of_example.js -JavaScript -Python -Go -A -I -``` - -### 使い分けのまとめ - -| 構文 | 取得するもの | 対象 | 推奨ユースケース | -| :--- | :--- | :--- | :--- | -| **`for...in`** | **キー (Key)** | Object | オブジェクトのプロパティ調査 | -| **`for...of`** | **値 (Value)** | Array, String, Map, Set | 配列やリストデータの処理 | - -> **Tips:** オブジェクトの中身を `for...of` で回したい場合は、`Object.keys()`, `Object.values()`, `Object.entries()` を使うのがモダンな手法です。 - -```js-repl:4 -> const obj = { a: 1, b: 2 }; -> for (const [key, val] of Object.entries(obj)) { console.log(key, val); } -a 1 -b 2 -``` - -## 例外処理 (try...catch...finally) - -JavaScriptの例外処理は `try...catch...finally` 構文を使用します。 - -### 基本的なエラーハンドリング - -実行時にエラーが発生すると、処理が中断され `catch` ブロックに移行します。 - -```js:try_catch.js -function parseJson(jsonString) { - try { - const result = JSON.parse(jsonString); - console.log("パース成功:", result); - return result; - } catch (e) { - // エラーオブジェクトが e に入る - console.error("パース失敗:", e.name, e.message); - } finally { - console.log("処理終了(成功・失敗に関わらず実行)"); - } -} - -parseJson('{"valid": true}'); -console.log("---"); -parseJson('Invalid JSON'); -``` - -```js-exec:try_catch.js -パース成功: { valid: true } -処理終了(成功・失敗に関わらず実行) ---- -パース失敗: SyntaxError Unexpected token I in JSON at position 0 -処理終了(成功・失敗に関わらず実行) -``` - -### throw について - -JavaScriptでは `throw` で例外を投げることができます。`Error` オブジェクトを投げるのが一般的ですが、技術的には文字列や数値など、任意の値を投げることが可能です(ただし、スタックトレースが取れなくなるため推奨されません)。 - -```js-repl:5 -> try { throw new Error("Something went wrong"); } catch (e) { console.log(e.message); } -Something went wrong - -> // プリミティブ値を投げることも可能だが非推奨 -> try { throw "Just a string"; } catch (e) { console.log(typeof e, e); } -string Just a string -``` - -## この章のまとめ - - * **条件分岐:** `if` 文での `[]` や `{}` は Truthy であることに注意。`switch` は厳密等価 (`===`) で判定される。 - * **繰り返し:** 古典的な `for`, `while` は他のC系言語と同じ。 - * **for...in:** オブジェクトの **キー** を列挙する。配列には使わないこと。 - * **for...of:** 配列やコレクションの **値** を反復する。リスト処理の標準。 - * **例外処理:** `try...catch...finally` で行う。`throw` は任意の値を投げられるが、通常は `Error` オブジェクトを使用する。 - -### 練習問題1: 配列のフィルタリングと集計 - -以下の数値が入った配列 `numbers` があります。 -`for...of` ループを使用して、**偶数かつ10より大きい数値** だけを抽出し、その合計値を計算してコンソールに出力するプログラムを書いてください。 - -```js:practice3_1.js -const numbers = [5, 12, 8, 20, 7, 3, 14, 30]; -// ここにコードを書く -``` - -```js-exec:practice3_1.js -``` - -### 問題 2: 簡易コマンドディスパッチャ - -以下の仕様を満たす関数 `executeCommand(command)` を `switch` 文と `try...catch` を用いて作成してください。 - -1. 引数 `command` は文字列を受け取る。 -2. `"start"` の場合、"System starting..." を出力。 -3. `"stop"` の場合、"System stopping..." を出力。 -4. それ以外の文字列の場合、`Error` オブジェクトを `throw` する(メッセージは "Unknown command")。 -5. `try...catch` ブロックを用いてこの関数を呼び出し、エラーが発生した場合は "Error caught: Unknown command" のように出力する。 - -**ヒント:** `command` が `null` や `undefined` の場合もエラーとして処理されるように実装してください。 - -```js:practice3_2.js -function executeCommand(command) { - -} -``` - -```js-exec:practice3_2.js -``` diff --git a/public/docs/javascript-4.md b/public/docs/javascript-4.md deleted file mode 100644 index 5b37f3b..0000000 --- a/public/docs/javascript-4.md +++ /dev/null @@ -1,245 +0,0 @@ -# 第4章: 関数とクロージャ - -JavaScriptにおいて関数はオブジェクトの一種です。つまり、変数に代入したり、他の関数の引数として渡したり、関数から戻り値として返したりすることができます。この柔軟性が、JavaScriptの設計パターンの核心を担っています。 - -## 関数の定義(関数宣言 vs 関数式) - -JavaScriptには関数を定義する方法が主に2つあります。「関数宣言」と「関数式」です。これらは似ていますが、**巻き上げ(Hoisting)** の挙動が異なります。 - -### 1\. 関数宣言 (Function Declaration) - -古くからある定義方法です。スクリプトの実行前に読み込まれるため、定義する前の行から呼び出すことができます。 - -```js:function_declaration.js -console.log(greet("Alice")); // 定義前でも呼び出せる - -function greet(name) { - return `Hello, ${name}!`; -} -``` - -```js-exec:function_declaration.js -Hello, Alice! -``` - -### 2\. 関数式 (Function Expression) - -変数に関数を代入するスタイルです。変数の代入は実行時に行われるため、定義する前に呼び出すとエラーになります。現代のJavaScript開発では、意図しない巻き上げを防ぐためにこちら(または後述のアロー関数)が好まれる傾向にあります。 - -```js:function_expression.js -// 定義前に呼び出すと... ReferenceError: Cannot access 'sayHi' before initialization -// console.log(sayHi("Bob")); - -const sayHi = function(name) { - return `Hi, ${name}!`; -}; - -console.log(sayHi("Bob")); -``` - -```js-exec:function_expression.js -Hi, Bob! -``` - -## アロー関数 (=\>) の構文と特徴 - -ES2015 (ES6) で導入されたアロー関数は、関数式をより短く記述するための構文です。Javaのラムダ式やPythonのlambdaに似ていますが、いくつか独自の特徴があります。 - -### 基本構文 - -`function` キーワードを省略し、`=>` (矢印) を使って定義します。 - -```js:arrow_function.js -// 従来の関数式 -const add = function(a, b) { - return a + b; -}; - -// アロー関数 -const addArrow = (a, b) => { - return a + b; -}; - -console.log(addArrow(3, 5)); -``` - -```js-exec:arrow_function.js -8 -``` - -### 省略記法 - -アロー関数には強力な省略記法があります。 - -1. **引数が1つの場合**: カッコ `()` を省略可能。 -2. **処理が1行でreturnする場合**: 中括弧 `{}` と `return` キーワードを省略可能(暗黙のreturn)。 - -```js-repl:4 -> const square = x => x * x; // 引数の()とreturnを省略 -> square(5); -25 - -> const getUser = (id, name) => ({ id: id, name: name }); // オブジェクトを返す場合は()で囲む -> getUser(1, "Gemini"); -{ id: 1, name: 'Gemini' } -``` - -> **注意:** アロー関数は単なる短縮記法ではありません。「`this` を持たない」という重要な特徴がありますが、これについては**第5章**で詳しく解説します。 - -## 引数:デフォルト引数、Restパラメータ (...) - -関数の柔軟性を高めるための引数の機能を見ていきましょう。 - -### デフォルト引数 - -引数が渡されなかった場合(または `undefined` の場合)に使用される初期値を設定できます。 - -```js:default_args.js -const connect = (host = 'localhost', port = 8080) => { - console.log(`Connecting to ${host}:${port}...`); -}; - -connect(); // 両方省略 -connect('127.0.0.1'); // portはデフォルト値 -connect('example.com', 22); // 両方指定 -``` - -```js-exec:default_args.js -Connecting to localhost:8080... -Connecting to 127.0.0.1:8080... -Connecting to example.com:22... -``` - -### Restパラメータ (残余引数) - -引数の数が不定の場合、`...` を使うことで、残りの引数を**配列として**受け取ることができます。以前は `arguments` オブジェクトを使っていましたが、Restパラメータの方が配列メソッド(`map`, `reduce`など)を直接使えるため便利です。 - -```js:rest_params.js -const sum = (...numbers) => { - // numbersは本物の配列 [1, 2, 3, 4, 5] - return numbers.reduce((acc, curr) => acc + curr, 0); -}; - -console.log(sum(1, 2, 3)); -console.log(sum(10, 20, 30, 40, 50)); -``` - -```js-exec:rest_params.js -6 -150 -``` - -## スコープチェーンとレキシカルスコープ - -JavaScriptの変数の有効範囲(スコープ)を理解するために、「レキシカルスコープ」という概念を知る必要があります。 - - * **レキシカルスコープ (Lexical Scope):** 関数が「どこで呼び出されたか」ではなく、**「どこで定義されたか」**によってスコープが決まるというルールです。 - * **スコープチェーン (Scope Chain):** 変数を探す際、現在のスコープになければ、定義時の外側のスコープへと順番に探しに行く仕組みです。 - -```js:scope.js -const globalVar = "Global"; - -function outer() { - const outerVar = "Outer"; - function inner() { - const innerVar = "Inner"; - // innerの中からouterVarとglobalVarが見える(スコープチェーン) - return `${globalVar} > ${outerVar} > ${innerVar}`; - } - return inner(); -} - -console.log(outer()); -``` - -```js-exec:scope.js -Global > Outer > Inner -``` - -## クロージャ:関数が状態を持つ仕組み - -クロージャ (Closure) は、この章の最重要トピックです。 -一言で言えば、**「外側の関数のスコープにある変数を、外側の関数の実行終了後も参照し続ける関数」**のことです。 - -通常、関数(`createCounter`)の実行が終わると、そのローカル変数(`count`)はメモリから破棄されます。しかし、その変数を参照している内部関数(`increment`)が存在し、その内部関数が外部に返された場合、変数は破棄されずに保持され続けます。 - -### クロージャの実例:カウンタ - -プライベートな変数を持つカウンタを作ってみましょう。 - -```js:closure_counter.js -const createCounter = () => { - let count = 0; // この変数は外部から直接アクセスできない(プライベート変数的な役割) - - return () => { - count++; - console.log(`Current count: ${count}`); - }; -}; - -const counterA = createCounter(); // counterA専用のスコープ(環境)が作られる -const counterB = createCounter(); // counterB専用のスコープが別に作られる - -counterA(); // 1 -counterA(); // 2 -counterA(); // 3 - -console.log("--- switching to B ---"); - -counterB(); // 1 (Aの状態とは独立している) -``` - -```js-exec:closure_counter.js -Current count: 1 -Current count: 2 -Current count: 3 ---- switching to B --- -Current count: 1 -``` - -### なぜクロージャを使うのか? - -1. **カプセル化 (Encapsulation):** 変数を隠蔽し、特定の関数経由でしか変更できないようにすることで、予期せぬバグを防ぎます。 -2. **状態の保持:** グローバル変数を使わずに、関数単位で永続的な状態を持てます。 -3. **関数ファクトリ:** 設定の異なる関数を動的に生成する場合に役立ちます。 - -## この章のまとめ - - * **関数定義:** 巻き上げが起こる「関数宣言」と、起こらない「関数式(アロー関数含む)」がある。 - * **アロー関数:** `(args) => body` の形式で記述し、`this` の挙動が従来と異なる。 - * **引数:** デフォルト引数とRestパラメータ(`...args`)で柔軟な引数処理が可能。 - * **レキシカルスコープ:** 関数は「定義された場所」のスコープを記憶する。 - * **クロージャ:** 内部関数が外部関数の変数を参照し続ける仕組み。データの隠蔽や状態保持に使われる。 - -## 練習問題1: アロー関数への書き換え - -以下の関数宣言を、アロー関数 `isEven` に書き換えてください。ただし、省略可能な記号(カッコやreturnなど)は可能な限り省略して最短で記述してください。 - -```js:practice4_1.js -function isEven(n) { - return n % 2 === 0; -} -``` - -```js-exec:practice4_1.js -``` - -### 問題2: クロージャによる掛け算生成器 - -`createMultiplier` という関数を作成してください。この関数は数値 `x` を引数に取り、呼び出すたびに「引数を `x` 倍して返す関数」を返します。 - -**使用例:** - -```js:practice4_2.js -// ここに関数を作成 - - -const double = createMultiplier(2); -console.log(double(5)); // 10 - -const triple = createMultiplier(3); -console.log(triple(5)); // 15 -``` - -```js-exec:practice4_2.js -``` diff --git a/public/docs/javascript-5.md b/public/docs/javascript-5.md deleted file mode 100644 index ef3f490..0000000 --- a/public/docs/javascript-5.md +++ /dev/null @@ -1,229 +0,0 @@ -# 第5章: 'this'の正体 - -JavaScriptの学習、お疲れ様です。他の言語(Java, C\#, Pythonなど)の経験がある方にとって、JavaScriptの `this` は最も直感に反し、バグの温床となりやすい機能の一つです。 - -多くの言語において `this`(または `self`)は「そのクラスのインスタンス」を指す静的なものですが、**JavaScriptの `this` は「関数がどのように呼ばれたか」によって動的に変化します。** - -この章では、その複雑な挙動を解き明かし、自在にコントロールする方法を学びます。 - -## 1\. 'this' は呼び出し方で決まる - -JavaScriptにおける関数(アロー関数を除く)の `this` は、**「定義された場所」ではなく「呼び出された場所(Call Site)」**によって決定されます。 - -大きく分けて、以下の3つのパターンを理解すれば基本は押さえられます。 - -### パターンA: メソッド呼び出し - -オブジェクトのプロパティとして関数を呼び出した場合(`obj.method()`)、`this` は**ドットの左側のオブジェクト**(レシーバ)になります。これは他の言語のメンバ関数に近い挙動です。 - -### パターンB: 関数呼び出し - -関数を単体で呼び出した場合(`func()`)、`this` は**グローバルオブジェクト**(ブラウザでは `window`、Node.jsでは `global`)になります。 -ただし、**Strict Mode(`"use strict"`)**では、安全のため `undefined` になります。 - -### パターンC: コンストラクタ呼び出し - -`new` キーワードをつけて呼び出した場合、`this` は**新しく生成されたインスタンス**になります(これは第6章、第7章で詳しく扱います)。 - -以下のコードで、同じ関数でも呼び出し方によって `this` が変わる様子を確認しましょう。 - -```js:dynamic-this.js -"use strict"; // Strict Modeを有効化 - -function showThis() { - console.log(`this is: ${this}`); -} - -const person = { - name: "Alice", - show: showThis, - toString: function() { return this.name; } // コンソール出力用に設定 -}; - -// 1. メソッド呼び出し -console.log("--- Method Call ---"); -person.show(); - -// 2. 関数呼び出し(変数に代入してから実行) -console.log("--- Function Call ---"); -const standaloneShow = person.show; -standaloneShow(); -``` - -```js-exec:dynamic-this.js ---- Method Call --- -this is: Alice ---- Function Call --- -this is: undefined -``` - -> **ポイント:** `person.show` を `standaloneShow` に代入した時点で、オブジェクトとの結びつきは失われます。そのため、`standaloneShow()` と実行すると「関数呼び出し」扱いとなり、`this` は `undefined`(非Strict Modeならグローバルオブジェクト)になります。これが「`this` が消える」現象の正体です。 - -## 2\. 'this' を固定する: bind, call, apply - -「関数呼び出し」でも特定のオブジェクトを `this` として扱いたい場合があります。JavaScriptには、`this` を明示的に指定(束縛)するためのメソッドが3つ用意されています。 - -### call と apply - -これらは関数を**即座に実行**します。第一引数に `this` としたいオブジェクトを渡します。 - - * `call(thisArg, arg1, arg2, ...)`: 引数をカンマ区切りで渡す。 - * `apply(thisArg, [argsArray])`: 引数を配列として渡す。 - -```js-repl:1 -> function greet(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; } -undefined -> const user = { name: "Bob" }; -undefined -> // callの使用例 -> greet.call(user, "Hello", "!"); -'Hello, Bob!' -> // applyの使用例 -> greet.apply(user, ["Hi", "?"]); -'Hi, Bob?' -``` - -### bind - -`bind` は関数を実行せず、**`this` を固定した新しい関数**を返します。これは、イベントリスナーやコールバック関数としてメソッドを渡す際に非常に重要です。 - -```js:bind-example.js -const engine = { - type: "V8", - start: function() { - console.log(`Starting ${this.type} engine...`); - } -}; - -// そのまま渡すと this が失われる(関数呼び出しになるため) -const brokenStart = engine.start; -// brokenStart(); // エラー: Cannot read property 'type' of undefined - -// bind で this を engine に固定する -const fixedStart = engine.start.bind(engine); -fixedStart(); -``` - -```js-exec:bind-example.js -Starting V8 engine... -``` - -## 3\. アロー関数と 'this' - -ES2015 (ES6) で導入されたアロー関数は、これまで説明したルールとは全く異なる挙動をします。 - -アロー関数には**独自の `this` が存在しません**。アロー関数内部の `this` は、**その関数が定義されたスコープ(レキシカルスコープ)の `this`** をそのまま参照します。 - -これは、「コールバック関数内で `this` が変わってしまう問題」を解決するのに最適です。 - -### 従来の関数 vs アロー関数 - -`setTimeout` などのコールバック内でメソッドを使いたい場面を比較してみましょう。 - -```js:arrow-vs-function.js -class Timer { - constructor() { - this.seconds = 0; - } - - // 従来の方法: 失敗例 - startLegacy() { - setTimeout(function() { - // ここでの this はグローバルまたはundefined(setTimeoutの仕様) - // そのため this.seconds にアクセスできずNaNなどになる - try { - this.seconds++; - console.log("Legacy:", this.seconds); - } catch (e) { - console.log("Legacy: Error -", e.message); - } - }, 100); - } - - // アロー関数: 成功例 - startModern() { - setTimeout(() => { - // アロー関数は定義時のスコープ(startModern内のthis = インスタンス)を捕獲する - this.seconds++; - console.log("Modern:", this.seconds); - }, 100); - } -} - -const timer = new Timer(); -timer.startLegacy(); -timer.startModern(); -``` - -```js-exec:arrow-vs-function.js -Legacy: Error - Cannot read properties of undefined (reading 'seconds') -Modern: 1 -``` - -> **注意:** アロー関数は便利な反面、`this` を動的に変更することができません(`call` や `bind` を使っても無視されます)。そのため、動的なコンテキストが必要な場合(例:オブジェクトのメソッド定義そのものや、ライブラリ等で `this` を注入される場合)には通常の関数式を使います。 - -# この章のまとめ - -JavaScriptの `this` は、他の静的な言語とは異なり「呼び出し時」に解決されます。 - -1. **メソッド呼び出し (`obj.method()`)**: `this` は `obj`。 -2. **関数呼び出し (`func()`)**: `this` は `undefined` (Strict Mode) またはグローバルオブジェクト。 -3. **明示的な指定**: `call`, `apply` で一時的に、`bind` で永続的に `this` を指定可能。 -4. **アロー関数**: 独自の `this` を持たず、外側のスコープの `this` をそのまま使う(レキシカルスコープ)。 - -次の章では、この `this` を活用してオブジェクト指向プログラミングの核心である「オブジェクトとプロトタイプ」について学びます。 - -# 練習問題1: 失われたコンテキストの修復 - -以下のコードは、ボタンクリック時(ここではシミュレーション)にユーザー名を表示しようとしていますが、エラーになります。 - -1. `bind` を使って修正してください。 -2. `greet` メソッド自体をアロー関数に変更するアプローチではなく、呼び出し側を修正する形で解答してください。 - -```js:practice5_1.js -const user = { - name: "Tanaka", - greet: function() { - console.log(`Hello, ${this.name}`); - } -}; - -// クリックイベントのシミュレーター(変更不可) -function simulateClick(callback) { - // 内部で単なる関数呼び出しとして実行される - callback(); -} - -// --- 以下を修正してください --- -simulateClick(user.greet); -``` - -```js-exec:practice5_1.js -``` - -### 問題2: アロー関数の特性 - -以下の `calculator` オブジェクトにはバグがあります。`multiply` メソッドが正しい結果(配列の各要素を `factor` 倍する)を返すように修正してください。 -ヒント:`map` の中のコールバック関数に注目してください。 - -```js:practice5_2.js -const calculator = { - factor: 2, - numbers: [1, 2, 3], - multiply: function() { - return this.numbers.map(function(n) { - // ここで this.factor が読めない! - return n * this.factor; - }); - } -}; - -try { - console.log(calculator.multiply()); -} catch(e) { - console.log("Error:", e.message); -} -``` - -```js-exec:practice5_2.js -``` diff --git a/public/docs/javascript-6.md b/public/docs/javascript-6.md deleted file mode 100644 index 2ab1029..0000000 --- a/public/docs/javascript-6.md +++ /dev/null @@ -1,250 +0,0 @@ -# 第6章: オブジェクトとプロトタイプ - -他の言語(Java, C\#, Pythonなど)の経験がある方にとって、JavaScriptの「オブジェクト」と「継承」のモデルは最も混乱しやすい部分の一つです。JavaScriptはクラスベースではなく、**プロトタイプベース**のオブジェクト指向言語です。 - -本章では、ES6(ECMAScript 2015)以降の`class`構文(第7章で扱います)の裏側で実際に何が起きているのか、その仕組みの根幹である「プロトタイプチェーン」について解説します。 - -## オブジェクトリテラルとプロパティ - -JavaScriptにおけるオブジェクトは、基本的にはキー(プロパティ名)と値のコレクション(連想配列やハッシュマップに近いもの)です。最も一般的な生成方法は**オブジェクトリテラル** `{...}` を使うことです。 - -```js-repl:1 -> const book = { -... title: "JavaScript Primer", -... "page-count": 350, // ハイフンを含むキーは引用符が必要 -... author: { -... name: "John Doe", -... age: 30 -... } -... }; -undefined -> book.title -'JavaScript Primer' -> book["page-count"] // 識別子として無効な文字を含む場合はブラケット記法 -350 -> book.author.name -'John Doe' -``` - -### プロパティの追加・削除 - -動的な言語であるJavaScriptでは、オブジェクト作成後にプロパティを追加・削除できます。 - -```js-repl:2 -> const config = { env: "production" }; -undefined -> config.port = 8080; // 追加 -8080 -> delete config.env; // 削除 -true -> config -{ port: 8080 } -``` - -## メソッドと this(復習) - -オブジェクトのプロパティには関数も設定できます。これを**メソッド**と呼びます。 -第5章で学んだ通り、メソッド呼び出しにおける `this` は、「ドットの左側にあるオブジェクト(レシーバ)」を指します。 - -```js-repl:3 -> const counter = { -... count: 0, -... increment: function() { -... this.count++; -... return this.count; -... }, -... // ES6からの短縮記法(推奨) -... reset() { -... this.count = 0; -... } -... }; -undefined -> counter.increment(); -1 -> counter.increment(); -2 -> counter.reset(); -undefined -> counter.count -0 -``` - -## プロトタイプとは何か? - -ここからが本章の核心です。JavaScriptのすべてのオブジェクトは、自身の親となる別のオブジェクトへの隠されたリンクを持っています。このリンク先のオブジェクトを**プロトタイプ**と呼びます。 - -オブジェクトからプロパティを読み取ろうとしたとき、そのオブジェクト自身がプロパティを持っていなければ、JavaScriptエンジンは自動的にプロトタイプを探しに行きます。 - -### `__proto__` と `Object.getPrototypeOf` - -歴史的経緯により、多くのブラウザで `obj.__proto__` というプロパティを通じてプロトタイプにアクセスできますが、現在の標準的な方法は `Object.getPrototypeOf(obj)` です。 - -```js-repl:4 -> const arr = [1, 2, 3]; -undefined -> // 配列の実体はオブジェクトであり、Array.prototypeを継承している -> Object.getPrototypeOf(arr) === Array.prototype -true -> // Array.prototypeの親はObject.prototype -> Object.getPrototypeOf(Array.prototype) === Object.prototype -true -> // Object.prototypeの親はnull(チェーンの終端) -> Object.getPrototypeOf(Object.prototype) -null -``` - -## プロトタイプチェーンによる継承の仕組み - -あるオブジェクトのプロパティにアクセスした際、JavaScriptは以下の順序で探索を行います。 - -1. そのオブジェクト自身(Own Property)が持っているか? -2. 持っていなければ、そのオブジェクトのプロトタイプが持っているか? -3. それでもなければ、プロトタイプのプロトタイプが持っているか? -4. `null` に到達するまで繰り返し、見つからなければ `undefined` を返す。 - -この連鎖を**プロトタイプチェーン**と呼びます。クラス継承のように型定義をコピーするのではなく、**リンクを辿って委譲(Delegation)する**仕組みです。 - -以下のコードで、具体的な動作を確認してみましょう。 - -```js:prototype_chain.js -const animal = { - eats: true, - walk() { - console.log("Animal walks"); - } -}; - -const rabbit = { - jumps: true, - __proto__: animal // 注意: __proto__への代入は学習目的以外では非推奨 -}; - -const longEar = { - earLength: 10, - __proto__: rabbit -}; - -// 1. longEar自身は walk を持っていない -> rabbitを見る -// 2. rabbitも walk を持っていない -> animalを見る -// 3. animal が walk を持っている -> 実行 -longEar.walk(); - -// 自身のプロパティ -console.log(`Jumps? ${longEar.jumps}`); // rabbitから取得 -console.log(`Eats? ${longEar.eats}`); // animalから取得 - -// プロパティの追加(シャドーイング) -// longEar自身に walk を追加すると、animalの walk は隠蔽される -longEar.walk = function() { - console.log("LongEar walks simply"); -}; - -longEar.walk(); -``` - -```js-exec:prototype_chain.js -Animal walks -Jumps? true -Eats? true -LongEar walks simply -``` - -## Object.create() とコンストラクタ関数 - -`__proto__` を直接操作するのはパフォーマンスや標準化の観点から推奨されません。プロトタイプを指定してオブジェクトを生成する正しい方法は2つあります。 - -### 1\. Object.create() - -指定したオブジェクトをプロトタイプとする新しい空のオブジェクトを生成します。 - -```js-repl:5 -> const proto = { greet: function() { return "Hello"; } }; -undefined -> const obj = Object.create(proto); -undefined -> obj.greet(); -'Hello' -> Object.getPrototypeOf(obj) === proto -true -``` - -### 2\. コンストラクタ関数(new演算子) - -ES6の `class` が登場する前、JavaScriptでは関数をコンストラクタとして使用し、`new` 演算子を使ってインスタンスを生成していました。これは現在でも多くのライブラリの内部で使用されている重要なパターンです。 - - * 関数オブジェクトは `prototype` という特別なプロパティを持っています(`__proto__`とは別物です)。 - * `new Func()` すると、作られたインスタンスの `__proto__` に `Func.prototype` がセットされます。 - -```js:constructor_pattern.js -// コンストラクタ関数(慣習として大文字で始める) -function User(name) { - // this = {} (新しい空のオブジェクトが暗黙的に生成される) - this.name = name; - // return this (暗黙的にこのオブジェクトが返される) -} - -// すべてのUserインスタンスで共有したいメソッドは -// User.prototype に定義する(メモリ節約のため) -User.prototype.sayHi = function() { - console.log(`Hi, I am ${this.name}`); -}; - -const user1 = new User("Alice"); -const user2 = new User("Bob"); - -user1.sayHi(); -user2.sayHi(); - -// 仕組みの確認 -console.log(user1.__proto__ === User.prototype); // true -console.log(user1.sayHi === user2.sayHi); // true (同じ関数を共有している) -``` - -```js-exec:constructor_pattern.js -Hi, I am Alice -Hi, I am Bob -true -true -``` - -> **重要な区別:** -> -> * `obj.__proto__`: オブジェクトの実の親(リンク先)。 -> * `Func.prototype`: その関数を `new` したときに、生成されるインスタンスの `__proto__` に代入される**テンプレート**。 - -## この章のまとめ - -1. JavaScriptはクラスベースではなく、**プロトタイプベース**の継承を行う。 -2. オブジェクトは隠しプロパティ(`[[Prototype]]`)を持ち、プロパティが見つからない場合にそこを探索する(プロトタイプチェーン)。 -3. `Object.create(proto)` は、特定のプロトタイプを持つオブジェクトを直接生成する。 -4. コンストラクタ関数と `new` 演算子を使うと、`Func.prototype` を親に持つインスタンスを生成できる。これがJavaなどの「クラス」に近い振る舞いを模倣する仕組みである。 - -## 練習問題1: 基本的なプロトタイプ継承 - -`Object.create()` を使用して、以下の要件を満たすコードを書いてください。 - -1. `robot` オブジェクトを作成し、`battery: 100` というプロパティと、バッテリーを10減らして残量を表示する `work` メソッドを持たせる。 -2. `robot` をプロトタイプとする `cleaningRobot` オブジェクトを作成する。 -3. `cleaningRobot` 自身に `type: "cleaner"` というプロパティを追加する。 -4. `cleaningRobot.work()` を呼び出し、正しく動作(プロトタイプチェーンの利用)を確認する。 - -```js:practice6_1.js -``` - -```js-exec:practice6_1.js -``` - -### 練習問題2: コンストラクタ関数 - -コンストラクタ関数 `Item` を作成してください。 - -1. `Item` は引数 `name` と `price` を受け取り、プロパティとして保持する。 -2. `Item.prototype` に `getTaxIncludedPrice` メソッドを追加する。これは税率10%を加えた価格を返す。 -3. `new Item("Apple", 100)` でインスタンスを作成し、税込価格が110になることを確認する。 - -```js:practice6_2.js -``` - -```js-exec:practice6_2.js -``` - diff --git a/public/docs/javascript-7.md b/public/docs/javascript-7.md deleted file mode 100644 index 3bd0dbd..0000000 --- a/public/docs/javascript-7.md +++ /dev/null @@ -1,235 +0,0 @@ -# 第7章: クラス構文 (ES6+) - -前章では、JavaScriptのオブジェクト指向の核心である「プロトタイプ」について学びました。他の言語(Java, C\#, Pythonなど)の経験者にとって、プロトタイプチェーンによる継承は柔軟ですが、少し直感的ではない部分もあったかもしれません。 - -ES6 (ECMAScript 2015) から導入された **`class` 構文** は、プロトタイプベースの継承メカニズムを隠蔽し、一般的なクラスベースのオブジェクト指向言語に近い記述を可能にするものです。これを「糖衣構文(Syntactic Sugar)」と呼びます。 - -この章では、現代のJavaScript開発で標準となっているクラスの定義方法、継承、そして比較的新しい機能であるプライベートフィールドについて解説します。 - -## クラスの定義とコンストラクタ - -JavaScriptのクラスは `class` キーワードを使って定義します。初期化処理は `constructor` という特別なメソッド内で行います。 - -基本的に、クラス定義の内部は自動的に **Strict Mode (`'use strict'`)** で実行されます。 - -```js-repl:1 -> class User { -... constructor(name, age) { -... this.name = name; -... this.age = age; -... } -... } -undefined -> const user1 = new User("Alice", 30); -undefined -> user1.name -'Alice' -> typeof User // クラスの実態は関数 -'function' -``` - -### クラス式 - -関数と同様に、クラスも式として変数に代入できます(あまり頻繁には使われませんが、知識として持っておくと良いでしょう)。 - -```js-repl:2 -> const Item = class { -... constructor(price) { -... this.price = price; -... } -... }; -undefined -> new Item(100).price -100 -``` - -## メソッド、ゲッター、セッター - -クラス構文の中では、プロトタイプへのメソッド定義を簡潔に書くことができます。`function` キーワードは不要です。また、プロパティへのアクセスを制御するゲッター (`get`) とセッター (`set`) も直感的に記述できます。 - -```js:rectangle.js -class Rectangle { - constructor(width, height) { - this.width = width; - this.height = height; - } - - // 通常のメソッド(プロトタイプメソッドになります) - calcArea() { - return this.width * this.height; - } - - // ゲッター: プロパティのようにアクセス可能 - get description() { - return `${this.width} x ${this.height} Rectangle`; - } - - // セッター: 値の検証などに利用 - set width(value) { - if (value <= 0) { - console.log("幅は0より大きくある必要があります"); - return; - } - this._width = value; - } - - get width() { - return this._width; - } -} - -const rect = new Rectangle(10, 20); - -console.log(rect.calcArea()); // メソッド呼び出し -console.log(rect.description); // ゲッター呼び出し(()は不要) - -rect.width = -5; // セッターによるバリデーション -rect.width = 15; -console.log(rect.calcArea()); -``` - -```js-exec:rectangle.js -200 -10 x 20 Rectangle -幅は0より大きくある必要があります -300 -``` - -> **Note:** セッター内で `this.width = value` とすると無限再帰になるため、慣習的に内部プロパティには `_`(アンダースコア)を付けることがよくありましたが、現在は後述するプライベートフィールド(`#`)を使うのがモダンな方法です。 - -## 継承 (extends と super) - -他の言語同様、`extends` キーワードを使用して既存のクラスを継承できます。親クラスのコンストラクタやメソッドには `super` を使ってアクセスします。 - -ここで重要なルールが1つあります。**子クラスの `constructor` 内では、`this` を使用する前に必ず `super()` を呼び出す必要があります。** - -```js:inheritance.js -class Animal { - constructor(name) { - this.name = name; - } - - speak() { - return `${this.name} makes a noise.`; - } -} - -class Dog extends Animal { - constructor(name, breed) { - // thisを使う前に親のコンストラクタを呼ぶ必須ルール - super(name); - this.breed = breed; - } - - // メソッドのオーバーライド - speak() { - // 親クラスのメソッド呼び出し - const parentSound = super.speak(); - return `${parentSound} But specifically, ${this.name} barks!`; - } -} - -const d = new Dog("Pochi", "Shiba"); -console.log(d.speak()); -console.log(d instanceof Dog); // true -console.log(d instanceof Animal); // true -``` - -```js-exec:inheritance.js -Pochi makes a noise. But specifically, Pochi barks! -true -true -``` - -## 静的メソッド (static) とプライベートフィールド (\#) - -### 静的メソッド (static) - -インスタンスではなく、クラス自体に紐付くメソッドです。ユーティリティ関数やファクトリーメソッドによく使われます。 - -### プライベートフィールド (\#) - -長らくJavaScriptには「真のプライベートプロパティ」が存在せず、`_variable` のような命名規則に頼っていました。しかし、ES2019以降、`#` をプレフィックスにすることで、**クラス外から完全にアクセス不可能なフィールド**を定義できるようになりました。 - -```js:private_static.js -class BankAccount { - // プライベートフィールドの宣言 - #balance; - - constructor(initialBalance) { - this.#balance = initialBalance; - } - - deposit(amount) { - if (amount > 0) { - this.#balance += amount; - console.log(`Deposited: ${amount}`); - } - } - - getBalance() { - return this.#balance; - } - - // 静的メソッド - static createZeroAccount() { - return new BankAccount(0); - } -} - -const account = BankAccount.createZeroAccount(); -account.deposit(1000); - -console.log(`Current Balance: ${account.getBalance()}`); - -// 外部からのアクセスを試みると、 Syntax Error になる -// console.log(account.#balance); - -// 従来のプロパティアクセスのように見えても... -console.log(account.balance); // undefined -``` - -```js-exec:private_static.js -Deposited: 1000 -Current Balance: 1000 -undefined -``` - -## この章のまとめ - -1. **`class` 構文** はプロトタイプ継承の糖衣構文であり、`constructor` で初期化を行います。 -2. **メソッド定義** は `function` キーワードが不要で、`get` / `set` でアクセサを定義できます。 -3. **継承** は `extends` を使い、子クラスのコンストラクタ内では必ず `this` に触れる前に `super()` を呼ぶ必要があります。 -4. **`static`** で静的メソッドを、**`#`** プレフィックスでハードプライベートフィールド(外部からアクセス不可)を定義できます。 - -クラス構文を使うことで、コードの構造がより明確になり、他の言語の経験者にとっても読みやすいコードになります。しかし、裏側ではプロトタイプチェーンが動いていることを忘れないでください。 - -### 練習問題 1: シンプルなRPGキャラクター - -以下の仕様を満たす `Character` クラスを作成してください。 - - * `name` (名前) と `hp` (体力) をコンストラクタで受け取る。 - * `attack(target)` メソッドを持つ。実行すると `target` の `hp` を 10 減らし、コンソールに攻撃メッセージを表示する。 - * `hp` はプライベートフィールド (`#hp`) として管理し、0未満にならないようにする。現在のHPを取得するゲッター `hp` を用意する。 - -```js:practice7_1.js -``` - -```js-exec:practice7_1.js -``` - - -### 練習問題 2: 図形の継承 - -以下の仕様を満たすクラスを作成してください。 - - * 親クラス `Shape`: コンストラクタで `color` を受け取る。`info()` メソッドを持ち、「色: [color]」を返す。 - * 子クラス `Circle`: `Shape` を継承。コンストラクタで `color` と `radius` (半径) を受け取る。`info()` メソッドをオーバーライドし、「[親のinfo], 半径: [radius]」を返す。 - * それぞれのインスタンスを作成し、`info()` の結果を表示する。 - -```js:practice7_2.js -``` - -```js-exec:practice7_2.js -``` - diff --git a/public/docs/javascript-8.md b/public/docs/javascript-8.md deleted file mode 100644 index 5cb6ef4..0000000 --- a/public/docs/javascript-8.md +++ /dev/null @@ -1,251 +0,0 @@ -# 第8章: 配列とイテレーション - -他の言語経験者にとって、JavaScriptの配列は「動的配列」や「リスト」、「ベクター」に近い存在です。サイズは可変であり、異なるデータ型を混在させることも可能です(通常は同じ型で統一しますが)。 - -本章では、基本的な操作から、モダンなJavaScript開発において必須となる「宣言的なデータ処理」(`map`, `filter`, `reduce`など)に焦点を当てます。従来の`for`ループよりもこれらのメソッドが好まれる理由と使い方を習得しましょう。 - -## 配列リテラルと基本的な操作 - -JavaScriptの配列は`Array`オブジェクトですが、通常はリテラル `[]` を使用して生成します。 -基本的な操作として、スタック操作(`push`, `pop`)やキュー操作に近いこと(`shift`, `unshift`)、そして万能な要素操作メソッド`splice`があります。 - -### 基本操作 (REPL) - -```js-repl:1 -> const fruits = ['Apple', 'Banana']; -undefined -> // 末尾に追加 (push) -> fruits.push('Orange'); -3 -> fruits -[ 'Apple', 'Banana', 'Orange' ] - -> // 末尾から削除 (pop) -> const last = fruits.pop(); -undefined -> last -'Orange' - -> // 先頭に追加 (unshift) -> fruits.unshift('Grape'); -3 -> fruits -[ 'Grape', 'Apple', 'Banana' ] - -> // インデックスによるアクセス -> fruits[1] -'Apple' -``` - -### 破壊的な操作: splice - -`splice`は要素の削除、置換、挿入をすべて行える強力なメソッドですが、**元の配列を変更(破壊)する**点に注意が必要です。 - -```js-repl:2 -> const numbers = [1, 2, 3, 4, 5]; -undefined -> // インデックス1から、2つの要素を削除し、そこに99, 100を挿入 -> numbers.splice(1, 2, 99, 100); -[ 2, 3 ] -> numbers -[ 1, 99, 100, 4, 5 ] -``` - -## スプレッド構文 (...) とデストラクチャリング(分割代入) - -モダンJavaScript(ES2015+)では、配列の操作をより簡潔に記述するための構文が導入されました。これらはReactやVueなどのフレームワークでも多用されます。 - -### スプレッド構文 (...) - -配列を展開する構文です。配列の結合や、\*\*浅いコピー(Shallow Copy)\*\*の作成によく使われます。 - -```js-repl:3 -> const part1 = [1, 2]; -undefined -> const part2 = [3, 4]; -undefined -> // 配列の結合(新しい配列を作成) -> const combined = [...part1, ...part2]; -undefined -> combined -[ 1, 2, 3, 4 ] - -> // 配列のコピー(新しい参照を作成) -> const copy = [...part1]; -undefined -> copy === part1 -false -``` - -### デストラクチャリング(分割代入) - -配列から要素を取り出して変数に代入する操作を簡潔に書くことができます。 - -```js-repl:4 -> const users = ['Alice', 'Bob', 'Charlie']; -undefined -> // 1つ目と2つ目の要素を変数に代入 -> const [first, second] = users; -undefined -> first -'Alice' -> second -'Bob' - -> // 3つ目だけを取り出す(最初の2つはスキップ) -> const [, , third] = users; -undefined -> third -'Charlie' - -> // 変数の値を入れ替える(スワップ)テクニック -> let a = 1; -> let b = 2; -> [a, b] = [b, a]; -[ 2, 1 ] -> a -2 -``` - -## 高階関数によるイテレーション - -JavaScriptでは、`for`文や`while`文を書く頻度は減っています。代わりに、配列のメソッドとして提供される**高階関数**(関数を引数に取る関数)を使用して、処理の意図(変換、抽出、集約など)を明確にします。 - -主なメソッドは以下の通りです。 - - * **`forEach`**: 単なる反復処理(戻り値なし)。副作用(ログ出力やDB保存など)を起こすために使う。 - * **`map`**: 全要素を変換し、**新しい配列**を返す。 - * **`filter`**: 条件に一致する要素のみを抽出し、**新しい配列**を返す。 - * **`reduce`**: 要素を一つずつ処理して、**単一の値**(合計、オブジェクトなど)に集約する。 - -以下は、これらのメソッドを使って商品リストを処理するスクリプトです。 - -```js:shopping_cart.js -const cart = [ - { id: 1, name: 'Laptop', price: 1000, category: 'Electronics' }, - { id: 2, name: 'Mouse', price: 25, category: 'Electronics' }, - { id: 3, name: 'Coffee', price: 5, category: 'Food' }, - { id: 4, name: 'Keyboard', price: 100, category: 'Electronics' }, -]; - -console.log('--- 1. map: 商品名のリストを作成 ---'); -const itemNames = cart.map(item => item.name); -console.log(itemNames); - -console.log('\n--- 2. filter: 電子機器(Electronics)のみ抽出 ---'); -const electronics = cart.filter(item => item.category === 'Electronics'); -console.log(electronics); - -console.log('\n--- 3. reduce: 合計金額を計算 ---'); -// 第2引数の 0 はアキュムレータ(sum)の初期値 -const totalPrice = cart.reduce((sum, item) => sum + item.price, 0); -console.log(`Total: $${totalPrice}`); - -console.log('\n--- 4. メソッドチェーン(組み合わせ) ---'); -// 電子機器の価格のみを抽出して合計する -const electronicsTotal = cart - .filter(item => item.category === 'Electronics') - .map(item => item.price) - .reduce((acc, price) => acc + price, 0); - -console.log(`Electronics Total: $${electronicsTotal}`); -``` - -実行結果: - -```js-exec:shopping_cart.js ---- 1. map: 商品名のリストを作成 --- -[ 'Laptop', 'Mouse', 'Coffee', 'Keyboard' ] - ---- 2. filter: 電子機器(Electronics)のみ抽出 --- -[ - { id: 1, name: 'Laptop', price: 1000, category: 'Electronics' }, - { id: 2, name: 'Mouse', price: 25, category: 'Electronics' }, - { id: 4, name: 'Keyboard', price: 100, category: 'Electronics' } -] - ---- 3. reduce: 合計金額を計算 --- -Total: $1130 - ---- 4. メソッドチェーン(組み合わせ) --- -Electronics Total: $1125 -``` - -> **Note:** `map`や`filter`は元の配列を変更せず(イミュータブル)、新しい配列を返します。これにより、予期せぬ副作用を防ぐことができます。 - -## その他の便利なメソッド:find, some, every - -特定の要素を探したり、条件チェックを行ったりする場合に特化したメソッドです。これらもコールバック関数を受け取ります。 - - * **`find`**: 最初に見つかった要素自体を返す(見つからなければ `undefined`)。 - * **`findIndex`**: 最初に見つかった要素のインデックスを返す(見つからなければ `-1`)。 - * **`some`**: 条件を満たす要素が**一つでもあれば** `true` を返す。 - * **`every`**: **すべての要素**が条件を満たせば `true` を返す。 - -```js-repl:5 -> const scores = [85, 92, 45, 78, 90]; -undefined - -> // 最初の合格者(80点以上)を探す -> const starStudent = scores.find(score => score >= 90); -undefined -> starStudent -92 - -> // 赤点(50点未満)があるか? (some) -> const hasFailure = scores.some(score => score < 50); -undefined -> hasFailure -true - -> // 全員が合格(40点以上)か? (every) -> const allPassed = scores.every(score => score >= 40); -undefined -> allPassed -true -``` - -## この章のまとめ - - * 配列は動的で、`push`/`pop`などのメソッドで伸縮可能です。 - * `splice`は配列を直接変更(破壊)するため、使用には注意が必要です。 - * スプレッド構文 `...` と分割代入を使うと、配列のコピー、結合、要素の抽出が宣言的に記述できます。 - * ループ処理には `for` 文よりも高階関数(`map`, `filter`, `reduce`)を使用することが推奨されます。これらは処理の意図を明確にし、メソッドチェーンによる可読性の向上に寄与します。 - * 検索や検証には `find`, `some`, `every` を活用しましょう。 - -### 練習問題 1: データの加工 - -以下の数値配列があります。 -`const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];` - -この配列に対して、以下の処理を行うコードを書いてください(メソッドチェーンを使用しても構いません)。 - -1. 偶数のみを取り出す。 -2. 取り出した偶数をそれぞれ2乗する。 -3. 結果の配列をコンソールに表示する。 - -```js:practice8_1.js -``` - -```js-exec:practice8_1.js -``` - - -### 練習問題 2: 集計処理 - -以下のユーザーリストから「アクティブ(`active: true`)なユーザーの年齢の平均値」を計算して表示してください。 -(ヒント: まずアクティブなユーザーを絞り込み、次に年齢の合計と人数を使って平均を算出します) - -```js:practice8_2.js -const users = [ - { name: 'Alice', age: 25, active: true }, - { name: 'Bob', age: 30, active: false }, - { name: 'Charlie', age: 35, active: true }, - { name: 'Dave', age: 40, active: false } -]; - -``` - -```js-exec:practice8_2.js -``` - diff --git a/public/docs/javascript-9.md b/public/docs/javascript-9.md deleted file mode 100644 index 3bb753f..0000000 --- a/public/docs/javascript-9.md +++ /dev/null @@ -1,216 +0,0 @@ -# 第9章: 非同期処理(1)- Promise - -JavaScriptは基本的にシングルスレッドで動作します。つまり、一度に一つの処理しか実行できません。しかし、ネットワークリクエストやタイマーなどの重い処理を行っている間、ブラウザがフリーズしたりサーバーが応答しなくなったりしては困ります。 - -そこでJavaScriptは、重い処理をバックグラウンド(Web APIsやNode.jsのC++レイヤー)に任せ、完了通知を受け取ることで並行処理のような動きを実現しています。 - -本章では、JavaScriptの非同期処理の基盤となるメカニズムと、それを現代的に扱うための標準APIである **Promise** について解説します。 - -## 同期処理 vs 非同期処理 - -まず、挙動の違いを確認しましょう。 - - * **同期処理 (Synchronous):** コードが上から下へ順番に実行されます。前の処理が終わるまで次の処理は待たされます(ブロッキング)。 - * **非同期処理 (Asynchronous):** 処理の完了を待たずに、即座に次のコードへ進みます(ノンブロッキング)。処理結果は後でコールバックなどを通じて受け取ります。 - -以下のコードは、`setTimeout`(非同期API)を使用した例です。他言語の経験者であれば、「Start」→「1秒待機」→「Timer」→「End」と予想するかもしれませんが、JavaScriptでは異なります。 - -```js:async_demo.js -console.log('1. Start'); - -// 1000ミリ秒後にコールバックを実行する非同期関数 -setTimeout(() => { - console.log('2. Timer fired'); -}, 1000); - -console.log('3. End'); -``` - -```js-exec:async_demo.js -1. Start -3. End -2. Timer fired -``` - -`setTimeout` は「タイマーをセットする」という命令だけを出し、即座に制御を返します。そのため、タイマーの発火を待たずに `3. End` が出力されます。 - -## イベントループとコールバックキューの仕組み - -なぜシングルスレッドで非同期処理が可能なのか、その裏側にあるのが **イベントループ (Event Loop)** という仕組みです。 - -JavaScriptのランタイムは主に以下の要素で構成されています: - -1. **コールスタック (Call Stack):** 現在実行中の関数が積まれる場所。LIFO(後入れ先出し)。 -2. **Web APIs / Node APIs:** ブラウザやOSが提供する機能(タイマー、Fetch、DOMイベントなど)。非同期処理はここで実行されます。 -3. **コールバックキュー (Callback Queue):** 非同期処理が完了した後、実行待ちのコールバック関数が並ぶ列。 -4. **イベントループ (Event Loop):** コールスタックとキューを監視する仕組み。 - -**処理の流れ:** - -1. `setTimeout` がコールスタックで実行されると、ブラウザのタイマーAPIに処理を依頼し、スタックから消えます。 -2. 指定時間が経過すると、タイマーAPIはコールバック関数を **コールバックキュー** に入れます。 -3. **イベントループ** は、「コールスタックが空(メインの処理が完了)」かつ「キューにタスクがある」場合、キューからタスクを取り出してコールスタックへ移動させます。 - -この仕組みにより、メインスレッドをブロックすることなく非同期処理を実現しています。 - -## コールバック地獄の問題点 - -Promiseが登場する以前(ES5時代まで)は、非同期処理の順序制御を行うために、コールバック関数を入れ子にする手法が一般的でした。 - -例えば、「処理Aが終わったら処理B、その後に処理C...」というコードを書こうとすると、以下のようにネストが深くなります。 - -```js:callback_hell.js -function delay(ms, callback) { - setTimeout(callback, ms); -} - -console.log('Start'); - -delay(1000, () => { - console.log('Step 1 finished'); - - delay(1000, () => { - console.log('Step 2 finished'); - - delay(1000, () => { - console.log('Step 3 finished'); - console.log('End'); - }); - }); -}); -``` - -```js-exec:callback_hell.js -Start -Step 1 finished -Step 2 finished -Step 3 finished -End -``` - -これはいわゆる **「コールバック地獄 (Callback Hell)」** と呼ばれる状態で、可読性が低く、エラーハンドリングも困難です。これを解決するために導入されたのが **Promise** です。 - -## Promiseの概念 - -**Promise** は、非同期処理の「最終的な完了(または失敗)」とその「結果の値」を表すオブジェクトです。未来のある時点で値が返ってくる「約束手形」のようなものと考えてください。 - -Promiseオブジェクトは以下の3つの状態のいずれかを持ちます。 - -1. **Pending (待機中):** 初期状態。処理はまだ完了していない。 -2. **Fulfilled (履行):** 処理が成功し、値を持っている状態。(`resolve` された) -3. **Rejected (拒否):** 処理が失敗し、エラー理由を持っている状態。(`reject` された) - -Promiseの状態は一度 Pending から Fulfilled または Rejected に変化すると、二度と変化しません(Immutable)。 - -## Promiseの使い方 - -### Promiseの作成 - -`new Promise` コンストラクタを使用します。引数には `(resolve, reject)` を受け取る関数(Executor)を渡します。 - -```js-repl:1 -> const myPromise = new Promise((resolve, reject) => { -... // ここで非同期処理を行う -... const success = true; -... if (success) { -... resolve("OK!"); // 成功時 -... } else { -... reject(new Error("Failed")); // 失敗時 -... } -... }); -undefined -> myPromise -Promise { 'OK!' } -``` - -### .then(), .catch(), .finally() - -Promiseオブジェクトの結果を受け取るには、以下のメソッドを使用します。 - - * **`.then(onFulfilled)`**: PromiseがFulfilledになった時に実行されます。 - * **`.catch(onRejected)`**: PromiseがRejectedになった時に実行されます。 - * **`.finally(onFinally)`**: 成功・失敗に関わらず、処理終了時に実行されます。 - -先ほどのコールバック地獄の例を、Promiseを使って書き直してみましょう。 - -```js:promise_chain.js -// Promiseを返す関数を作成 -function delay(ms) { - return new Promise((resolve) => { - setTimeout(() => { - resolve(`Waited ${ms}ms`); - }, ms); - }); -} - -console.log('Start'); - -delay(1000) - .then((message) => { - console.log('Step 1:', message); - // 次のPromiseを返すことでチェーンをつなぐ - return delay(1000); - }) - .then((message) => { - console.log('Step 2:', message); - return delay(1000); - }) - .then((message) => { - console.log('Step 3:', message); - console.log('End'); - }) - .catch((error) => { - // チェーンのどこかでエラーが起きればここに飛ぶ - console.error('Error:', error); - }); -``` - -```js-exec:promise_chain.js -Start -Step 1: Waited 1000ms -Step 2: Waited 1000ms -Step 3: Waited 1000ms -End -``` - -**重要なポイント:** - -1. `.then()` の中で新しい Promise を返すと、次の `.then()` はその新しい Promise の完了を待ちます。これにより、非同期処理を **フラットな連鎖** として記述できます。 -2. エラー処理は最後の `.catch()` に集約できます。`try-catch` ブロックに近い感覚で扱えるようになります。 - -## この章のまとめ - - * JavaScriptはシングルスレッドで動作し、**イベントループ** という仕組みを使って非同期処理を管理しています。 - * 非同期処理の完了を待つために、昔はコールバック関数が多用されていましたが、ネストが深くなる問題がありました。 - * **Promise** は非同期処理の状態(Pending, Fulfilled, Rejected)を管理するオブジェクトです。 - * `.then()` をチェーンさせることで、非同期処理を直列に、読みやすく記述できます。 - * エラーハンドリングは `.catch()` で一括して行えます。 - -次章では、このPromiseをさらに同期処理のように書ける構文糖衣 **async/await** について学びます。 - -## 練習問題 - -### 問題1: ランダムな成功/失敗 - -`Math.random()` を使い、50%の確率で成功(Resolve)、50%の確率で失敗(Reject)するPromiseを返す関数 `coinToss` を作成してください。 -それを使用し、成功時は "Win\!"、失敗時は "Lose..." とコンソールに表示するコードを書いてください。 - -```js:practice9_1.js -``` - -```js-exec:practice9_1.js -``` - -### 問題2: 擬似的なデータ取得フロー - -以下の仕様を満たすコードを作成してください。 - -1. 関数 `fetchUser(userId)`: 1秒後に `{ id: userId, name: "User" + userId }` というオブジェクトでresolveする。 -2. 関数 `fetchPosts(userName)`: 1秒後に `["Post 1 by " + userName, "Post 2 by " + userName]` という配列でresolveする。 -3. これらをPromiseチェーンで繋ぎ、ユーザーID `1` でユーザーを取得した後、その名前を使って投稿を取得し、最終的に投稿リストをコンソールに表示してください。 - -```js:practice9_2.js -``` - -```js-exec:practice9_2.js -``` diff --git a/public/docs/javascript/0-intro/-intro.md b/public/docs/javascript/0-intro/-intro.md new file mode 100644 index 0000000..a588c82 --- /dev/null +++ b/public/docs/javascript/0-intro/-intro.md @@ -0,0 +1 @@ +本章では、JavaScript(以下JS)がどのような思想で設計され、JavaやPython、C\#といった他の言語とどう異なるのか、その全体像を把握します。また、学習に必要な環境構築と最初のコード実行を行います。 diff --git a/public/docs/javascript/0-intro/1-0-about.md b/public/docs/javascript/0-intro/1-0-about.md new file mode 100644 index 0000000..b3c7060 --- /dev/null +++ b/public/docs/javascript/0-intro/1-0-about.md @@ -0,0 +1,18 @@ +--- +id: javascript-intro-about +title: JavaScriptとは? +level: 2 +--- + +## JavaScriptとは? + +JavaScriptは1995年、Netscape社のBrendan Eichによってわずか10日間でプロトタイプが作成されました。当初はWebページに軽い動きをつけるための言語でしたが、現在では**ECMAScript (ES)** として標準化され、フロントエンドからバックエンド、モバイルアプリまで幅広く利用されています。 + +経験豊富なエンジニアが押さえておくべき特徴は以下の3点です。 + +1. **動的型付け (Dynamic Typing):** + 変数は型を持ちません。値が型を持ちます。コンパイル時ではなく実行時に型が決まるため、柔軟ですが、実行時エラーのリスク管理が必要です(現代ではTypeScriptで補うのが一般的です)。 +2. **マルチパラダイム:** + 命令型の手続き記述はもちろん、**関数型プログラミング**(第一級関数、クロージャ)や、**プロトタイプベースのオブジェクト指向**をサポートします。クラスベースの言語に慣れていると、ここの概念モデルの違いに驚くかもしれません。 +3. **シングルスレッド & ノンブロッキングI/O:** + JSのランタイムは基本的にシングルスレッドです。しかし、**イベントループ**という仕組みにより、重いI/O操作(ネットワーク通信やファイル読み込み)を非同期で処理し、メインスレッドをブロックせずに高い並行性を実現します。 diff --git a/public/docs/javascript/0-intro/2-0-environment.md b/public/docs/javascript/0-intro/2-0-environment.md new file mode 100644 index 0000000..3ea0003 --- /dev/null +++ b/public/docs/javascript/0-intro/2-0-environment.md @@ -0,0 +1,23 @@ +--- +id: javascript-intro-environment +title: '実行環境: ブラウザ vs Node.js' +level: 2 +--- + +## 実行環境: ブラウザ vs Node.js + +JavaScriptはどこで動くのでしょうか? かつてはブラウザの中だけでしたが、現在は大きく分けて2つの環境があります。 + + * **Webブラウザ (クライアントサイド):** + + * DOM (Document Object Model) 操作により、HTML/CSSを動的に書き換えます。 + * `window` オブジェクトがグローバルスコープです。 + * セキュリティ上の制約(サンドボックス)があり、ローカルファイルへの直接アクセスなどは制限されています。 + + * **Node.js (サーバーサイド):** + + * ChromeのV8 JavaScriptエンジンをブラウザの外に取り出したランタイムです。 + * OSの機能(ファイルシステム、ネットワーク)にアクセス可能です。 + * DOMは存在しません。Webサーバー構築やCLIツールの作成に使われます。 + +言語仕様(コア機能)は同じですが、**「何ができるか(API)」は環境に依存する**という点を意識してください。 diff --git a/public/docs/javascript/0-intro/3-0-comparison.md b/public/docs/javascript/0-intro/3-0-comparison.md new file mode 100644 index 0000000..fc4f4cb --- /dev/null +++ b/public/docs/javascript/0-intro/3-0-comparison.md @@ -0,0 +1,19 @@ +--- +id: javascript-intro-comparison +title: 他言語との比較 +level: 2 +--- + +## 他言語との比較 + +あなたが既に知っている言語とJSを比較してみましょう。 + +| 特徴 | Java / C\# | Python | JavaScript | +| :--- | :--- | :--- | :--- | +| **型システム** | 静的型付け (強い型付け) | 動的型付け (強い型付け) | **動的型付け (弱い型付け)** | +| **並行処理** | マルチスレッド | マルチスレッド (GILあり) | **シングルスレッド + イベントループ** | +| **OOP** | クラスベース | クラスベース | **プロトタイプベース** (class構文はシンタックスシュガー) | +| **実行方式** | コンパイル (JVM/CLR) | インタープリタ | **JITコンパイル** (多くのエンジン) | + + * **Java/C\#ユーザーへの注記:** JSの`class`は見た目は似ていますが、裏側の仕組み(プロトタイプチェーン)は全く異なります。また、コンパイルエラーで弾かれるようなコードも、JSでは実行できてしまう(そして実行時に落ちる)ことがあります。 + * **Pythonユーザーへの注記:** Pythonの`asyncio`に似ていますが、JSは**デフォルトで非同期**を前提としています。また、インデントではなく波括弧 `{}` でブロックを定義します。 diff --git a/public/docs/javascript/0-intro/4-0-helloworld.md b/public/docs/javascript/0-intro/4-0-helloworld.md new file mode 100644 index 0000000..f4c9a9e --- /dev/null +++ b/public/docs/javascript/0-intro/4-0-helloworld.md @@ -0,0 +1,9 @@ +--- +id: javascript-intro-helloworld +title: '"Hello, World!"' +level: 2 +--- + +## "Hello, World\!" + +実際にコードを動かしてみましょう。ここでは2つの方法を紹介します。 diff --git a/public/docs/javascript/0-intro/4-1-repl.md b/public/docs/javascript/0-intro/4-1-repl.md new file mode 100644 index 0000000..a83c32d --- /dev/null +++ b/public/docs/javascript/0-intro/4-1-repl.md @@ -0,0 +1,21 @@ +--- +id: javascript-intro-repl +title: REPL (Read-Eval-Print Loop) での実行 +level: 3 +--- + +### REPL (Read-Eval-Print Loop) での実行 + +ちょっとした動作確認にはREPLが便利です。Node.jsのREPLを起動するには、ターミナルで `node` と入力して起動します。 + +このウェブサイト上ではブラウザ上でコードを実行できる環境を埋め込んでおり、以下のように緑枠で囲われたコード例には自由にJavaScriptコードを書いて試すことができます。ただしNode.jsとは環境が異なり、Node.js特有の機能は使用できません。 + +```js-repl +> console.log("Hello, World from REPL!"); +Hello, World from REPL! +undefined +> 1 + 2 +3 +``` + +※ `undefined` は `console.log` 関数の戻り値が表示されています。 diff --git a/public/docs/javascript/0-intro/4-2-run-source.md b/public/docs/javascript/0-intro/4-2-run-source.md new file mode 100644 index 0000000..2fb9f94 --- /dev/null +++ b/public/docs/javascript/0-intro/4-2-run-source.md @@ -0,0 +1,27 @@ +--- +id: javascript-intro-run-source +title: ソースファイルからの実行 +level: 3 +--- + +### ソースファイルからの実行 + +本格的なプログラムはファイルに記述します。 + +まず、以下の内容で `hello.js` というファイルを作成してください。 + +```js:hello.js +// 変数定義 (後述しますが、現代ではconstを使います) +const greeting = "Hello, World!"; +const target = "Node.js"; + +// テンプレートリテラル (バッククォート ` を使用) +console.log(`${greeting} I am running on ${target}.`); +``` + +ターミナルでファイルのあるディレクトリに移動し、`node` コマンドで実行します。 +このウェブサイト上の実行環境で動かす場合は、以下の実行ボタンをクリックしてください。 + +```js-exec:hello.js +Hello, World! I am running on Node.js. +``` diff --git a/public/docs/javascript/0-intro/4-3-browser.md b/public/docs/javascript/0-intro/4-3-browser.md new file mode 100644 index 0000000..9e816cf --- /dev/null +++ b/public/docs/javascript/0-intro/4-3-browser.md @@ -0,0 +1,24 @@ +--- +id: javascript-intro-browser +title: ブラウザでの実行 (参考) +level: 3 +--- + +### ブラウザでの実行 (参考) + +ブラウザで動かす場合は、HTMLファイルが必要です。 +`index.html` を作成し、以下のように記述してブラウザで開いてみてください。 + +```html + + + + + + +``` + +ブラウザの開発者ツール(Consoleタブ)にメッセージが表示され、ポップアップウィンドウが出れば成功です。 diff --git a/public/docs/javascript/1-basics/-intro.md b/public/docs/javascript/1-basics/-intro.md new file mode 100644 index 0000000..9b056b9 --- /dev/null +++ b/public/docs/javascript/1-basics/-intro.md @@ -0,0 +1,3 @@ +プログラミング経験者であるあなたにとって、変数の概念やデータ型の存在自体は目新しいものではありません。しかし、JavaScriptには「動的型付け」や「歴史的経緯による特殊なスコープ仕様」、「独特な型変換」といった、他言語経験者が特に躓きやすいポイントがあります。 + +本章では、モダンなJavaScript(ES6以降)における標準的な記述方法を中心に、レガシーな仕様との違いや、バグを生みやすい落とし穴について解説します。 diff --git a/public/docs/javascript/1-basics/1-0-variable.md b/public/docs/javascript/1-basics/1-0-variable.md new file mode 100644 index 0000000..6ce8f0e --- /dev/null +++ b/public/docs/javascript/1-basics/1-0-variable.md @@ -0,0 +1,45 @@ +--- +id: javascript-basics-variable +title: '変数宣言: let, const, var' +level: 2 +--- + +## 変数宣言: let, const, var + +現代のJavaScript開発において、変数宣言のルールは非常にシンプルです。 +**「基本は `const`、再代入が必要な場合のみ `let` を使い、`var` は決して使わない」** これが鉄則です。 + +ES6(2015年)で導入された `const` と `let` は、C++やJava、C\#などと同様に**ブロックスコープ**を持ちます。 + + * **const**: 再代入不可能な変数を宣言します。定数だけでなく、再代入しない変数はすべてこれで宣言します。 + * **let**: 再代入可能な変数を宣言します。ループカウンタや、状態が変わる値に使用します。 + + +なぜ `var` を使うべきではないのでしょうか。それは `var` が**関数スコープ**であり、意図しない変数の共有や「巻き上げ(Hoisting)」によるバグを引き起こしやすいからです。 + +以下のコードで、スコープの違いを確認してみましょう。 + +```js:scope_demo.js +function checkScope() { + if (true) { + var functionScoped = "I am visible outside this block"; + let blockScoped = "I am NOT visible outside this block"; + const constantValue = "I am also block scoped"; + } + + console.log("var output:", functionScoped); // 参照可能(関数スコープのため) + + try { + console.log("let output:", blockScoped); // ReferenceError + } catch (e) { + console.error("let error:", e.message); + } +} + +checkScope(); +``` + +```js-exec:scope_demo.js +var output: I am visible outside this block +let error: blockScoped is not defined +``` diff --git a/public/docs/javascript/1-basics/2-0-primitive-type.md b/public/docs/javascript/1-basics/2-0-primitive-type.md new file mode 100644 index 0000000..22c79ff --- /dev/null +++ b/public/docs/javascript/1-basics/2-0-primitive-type.md @@ -0,0 +1,19 @@ +--- +id: javascript-basics-primitive-type +title: 'データ型: プリミティブ型' +level: 2 +--- + +## データ型: プリミティブ型 + +JavaScriptは動的型付け言語であり、変数は特定の型に紐付きませんが、値自体は型を持っています。JavaScriptの値は大きく分けて「プリミティブ型」と「オブジェクト(参照)型」に分類されます。 + +プリミティブ型はイミュータブル(変更不可)であり、以下の7種類が存在します。 + +1. **String**: 文字列。ES6から導入された「テンプレートリテラル(バッククォート `` ` ``)」を使うと、変数の埋め込みが容易です。 +2. **Number**: 数値。整数と浮動小数点数の区別はなく、すべて倍精度浮動小数点数(IEEE 754)として扱われます。 +3. **Boolean**: `true` または `false`。 +4. **undefined**: 「値が未定義である」ことを表す型。変数を宣言して値を代入していない状態です。 +5. **null**: 「値が存在しない」ことを意図的に示す型。 +6. **Symbol**: 一意で不変な識別子。オブジェクトのプロパティキーなどに使われます。 +7. **BigInt**: `Number`型では表現できない巨大な整数を扱います(末尾に `n` をつけます)。 diff --git a/public/docs/javascript/1-basics/2-1-null-undefined.md b/public/docs/javascript/1-basics/2-1-null-undefined.md new file mode 100644 index 0000000..a7d9e94 --- /dev/null +++ b/public/docs/javascript/1-basics/2-1-null-undefined.md @@ -0,0 +1,25 @@ +--- +id: javascript-basics-null-undefined +title: null と undefined の違い +level: 3 +--- + +### null と undefined の違い + +他言語経験者にとって混乱しやすいのがこの2つです。 + + * **undefined**: システム(JavaScriptエンジン)が「値がまだない」ことを示すために使うことが多い。 + * **null**: プログラマが「ここには値がない」ことを明示するために使うことが多い。 + +```js-repl +> let unassigned; +undefined +> unassigned +undefined +> let empty = null; +undefined +> typeof unassigned +'undefined' +> typeof empty // JSの有名なバグ(仕様)で 'object' が返りますが、実際はプリミティブです +'object' +``` diff --git a/public/docs/javascript/1-basics/3-0-object.md b/public/docs/javascript/1-basics/3-0-object.md new file mode 100644 index 0000000..b904441 --- /dev/null +++ b/public/docs/javascript/1-basics/3-0-object.md @@ -0,0 +1,15 @@ +--- +id: javascript-basics-object +title: 'データ型: オブジェクト型' +level: 2 +--- + +## データ型: オブジェクト型 + +プリミティブ以外のすべての値は**オブジェクト(参照型)**です。これらはメモリ上のアドレス(参照)として扱われます。 + +主なオブジェクト型には以下があります。 + + * **Object**: キーと値のペアの集合(辞書、ハッシュマップに近い)。 + * **Array**: 順序付きリスト。 + * **Function**: JavaScriptでは関数もオブジェクトであり、変数に代入したり引数として渡すことができます(第一級関数)。 diff --git a/public/docs/javascript/1-basics/3-1-const-object.md b/public/docs/javascript/1-basics/3-1-const-object.md new file mode 100644 index 0000000..5ce7e07 --- /dev/null +++ b/public/docs/javascript/1-basics/3-1-const-object.md @@ -0,0 +1,33 @@ +--- +id: javascript-basics-const-object +title: const とオブジェクトの変更 +level: 3 +--- + +### const とオブジェクトの変更 + +重要な点として、`const` で宣言した変数は「再代入」ができませんが、中身がオブジェクトの場合、**プロパティの変更は可能**です。これは `const` が「参照先のメモリアドレス」を固定するものであり、ヒープ領域にあるデータそのものを不変にするわけではないためです。 + +```js:object_mutation.js +const user = { + name: "Alice", + id: 1 +}; + +// 再代入はエラーになる +// user = { name: "Bob" }; // TypeError: Assignment to constant variable. + +// プロパティの変更は可能 +user.name = "Bob"; +console.log(user); + +// 配列もオブジェクトの一種 +const colors = ["Red", "Green"]; +colors.push("Blue"); +console.log(colors); +``` + +```js-exec:object_mutation.js +{ name: 'Bob', id: 1 } +[ 'Red', 'Green', 'Blue' ] +``` diff --git a/public/docs/javascript/1-basics/4-0-equality.md b/public/docs/javascript/1-basics/4-0-equality.md new file mode 100644 index 0000000..0386506 --- /dev/null +++ b/public/docs/javascript/1-basics/4-0-equality.md @@ -0,0 +1,28 @@ +--- +id: javascript-basics-equality +title: 演算子と等価性 (== vs ===) +level: 2 +--- + +## 演算子と等価性 (== vs ===) + +JavaScriptにおける最大の落とし穴の一つが「等価演算子」です。 + +**厳密等価演算子 (`===`) **は「値」と「型」の両方が等しいかを比較します。 + +**等価演算子 (`==`) **は、比較する前に**暗黙的な型変換**を行います。これにより、直感的ではない結果が生じることがあります。 + +基本的には常に `===` (および `!==`)を使用するようにしてください。 + +```js-repl +> 1 === "1" // 型が違うので false(推奨) +false +> 1 == "1" // 文字列が数値に変換されて比較されるため true(非推奨) +true +> 0 == false // true +true +> null == undefined // true(ここだけは例外的に許容するスタイルもあるが、基本は避ける) +true +> [] == ![] // 非常に難解な挙動(trueになる) +true +``` diff --git a/public/docs/javascript/1-basics/5-1-add-operator.md b/public/docs/javascript/1-basics/5-1-add-operator.md new file mode 100644 index 0000000..d93cbfc --- /dev/null +++ b/public/docs/javascript/1-basics/5-1-add-operator.md @@ -0,0 +1,22 @@ +--- +id: javascript-basics-add-operator +title: 加算演算子 (+) での暗黙的な型変換 +level: 2 +--- + +## 加算演算子 (+) での暗黙的な型変換 + +JavaScriptは文脈に応じて勝手に型を変換しようとします。 + +`+` 演算子は、数値の加算だけでなく文字列の連結にも使われます。片方が文字列であれば、もう片方も文字列に変換されて連結されます。 + +```js-repl +> 10 + 20 +30 +> 10 + "20" // 数値が文字列 "10" に変換され連結される +'1020' +> "10" + 20 +'1020' +> 10 - "2" // 減算は数値計算しかないので、文字列 "2" が数値に変換される +8 +``` diff --git a/public/docs/javascript/1-basics/6-0-summary.md b/public/docs/javascript/1-basics/6-0-summary.md new file mode 100644 index 0000000..94000f8 --- /dev/null +++ b/public/docs/javascript/1-basics/6-0-summary.md @@ -0,0 +1,12 @@ +--- +id: javascript-basics-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * 変数は `const` をデフォルトとし、再代入が必要な場合のみ `let` を使う。`var` は使用しない。 + * プリミティブ型は値渡し、オブジェクト型(配列含む)は参照渡しである。 + * `const` で宣言したオブジェクトの中身は変更可能である。 + * 比較には必ず `===`(厳密等価演算子)を使用し、`==` による暗黙の型変換を避ける。 diff --git a/public/docs/javascript/1-basics/6-1-practice1.md b/public/docs/javascript/1-basics/6-1-practice1.md new file mode 100644 index 0000000..200b76b --- /dev/null +++ b/public/docs/javascript/1-basics/6-1-practice1.md @@ -0,0 +1,25 @@ +--- +id: javascript-basics-practice1 +title: '練習問題1: テンプレートリテラルと型変換' +level: 3 +--- + +### 練習問題1: テンプレートリテラルと型変換 + +ユーザーの年齢(数値)と名前(文字列)を受け取り、自己紹介文を作成する関数を作成してください。 +ただし、年齢が `null` または `undefined` の場合は「不明」と表示するようにしてください。論理和演算子 `||` または Null合体演算子 `??` を活用してみましょう。 + +```js:practice2_1.js +// 以下の関数を完成させてください +function introduce(name, age) { + // ここにコードを記述 +} + +console.log(introduce("Tanaka", 25)); +console.log(introduce("Sato", null)); +``` + +```js-exec:practice2_1.js +My name is Tanaka and I am 25 years old. +My name is Sato and I am 不明 years old. +``` diff --git a/public/docs/javascript/1-basics/6-2-practice2.md b/public/docs/javascript/1-basics/6-2-practice2.md new file mode 100644 index 0000000..b9fef4a --- /dev/null +++ b/public/docs/javascript/1-basics/6-2-practice2.md @@ -0,0 +1,25 @@ +--- +id: javascript-basics-practice2 +title: '練習問題2: オブジェクトの操作と参照' +level: 3 +--- + +### 練習問題2: オブジェクトの操作と参照 + +以下のコードにはバグ(意図しない挙動)があります。 +`originalList` の内容を保持したまま、新しい要素を追加した `newList` を作成したいのですが、現状では `originalList` も変更されてしまいます。 +スプレッド構文 `...` などを使い、`originalList` を変更せずに `newList` を作成するように修正してください。 + +```js:practice2_2.js +const originalList = ["Apple", "Banana"]; + +// 参照コピーになっているため originalList も変わってしまう +const newList = originalList; +newList.push("Orange"); + +console.log("Original:", originalList); // ["Apple", "Banana"] と出力させたい +console.log("New:", newList); // ["Apple", "Banana", "Orange"] と出力させたい +``` + +```js-exec:practice2_2.js +``` diff --git a/public/docs/javascript/2-control/-intro.md b/public/docs/javascript/2-control/-intro.md new file mode 100644 index 0000000..1462d73 --- /dev/null +++ b/public/docs/javascript/2-control/-intro.md @@ -0,0 +1,3 @@ +他の言語での開発経験がある方にとって、JavaScriptの制御構文の多くは馴染み深いものでしょう。しかし、JavaScript特有の「真偽値の評価(Truthy/Falsy)」や「反復処理(Iteration)の種類の多さ」は、バグを生みやすいポイントでもあります。 + +この章では、構文そのものだけでなく、JavaScriptならではの挙動やベストプラクティスに焦点を当てて解説します。 diff --git a/public/docs/javascript/2-control/1-1-if-truthy.md b/public/docs/javascript/2-control/1-1-if-truthy.md new file mode 100644 index 0000000..f6232a4 --- /dev/null +++ b/public/docs/javascript/2-control/1-1-if-truthy.md @@ -0,0 +1,41 @@ +--- +id: javascript-control-if-truthy +title: if文とTruthy / Falsy +level: 2 +--- + +## `if`文とTruthy / Falsy + +基本的な `if` 文の構造はC言語やJavaと同様です。しかし、条件式における評価はJavaScript特有の**Truthy(真と見なされる値)**と**Falsy(偽と見なされる値)**の概念を理解する必要があります。 + +厳密な `true` / `false` だけでなく、あらゆる値が条件式の中で真偽判定されます。 + +**Falsyな値(falseとして扱われるもの):** + + * `false` + * `0`, `-0`, `0n` (BigInt) + * `""` (空文字) + * `null` + * `undefined` + * `NaN` + +**Truthyな値(trueとして扱われるもの):** + + * 上記Falsy以外すべて + * **注意:** 空の配列 `[]` や 空のオブジェクト `{}` は **Truthy** です(Pythonなどの経験者は注意が必要です)。 + * 文字列の `"0"` や `"false"` もTruthyです。 + +```js-repl +> if (0) { 'True'; } else { 'False'; } +'False' + +> if ("") { 'True'; } else { 'False'; } +'False' + +> if ([]) { 'True'; } else { 'False'; } // 空配列は真! +'True' + +> const user = { name: "Alice" }; +> if (user) { `Hello ${user.name}`; } +'Hello Alice' +``` diff --git a/public/docs/javascript/2-control/1-2-switch.md b/public/docs/javascript/2-control/1-2-switch.md new file mode 100644 index 0000000..49494c7 --- /dev/null +++ b/public/docs/javascript/2-control/1-2-switch.md @@ -0,0 +1,28 @@ +--- +id: javascript-control-switch +title: switch文 +level: 2 +--- + +## `switch`文 + +`switch` 文も標準的ですが、比較が **厳密等価演算子 (`===`)** で行われる点に注意してください。型変換は行われません。 + +```js:switch_example.js +const status = "200"; // 文字列 + +switch (status) { + case 200: // 数値の200と比較 -> false + console.log("OK (Number)"); + break; + case "200": // 文字列の"200"と比較 -> true + console.log("OK (String)"); + break; + default: + console.log("Unknown status"); +} +``` + +```js-exec:switch_example.js +OK (String) +``` diff --git a/public/docs/javascript/2-control/2-0-loop.md b/public/docs/javascript/2-control/2-0-loop.md new file mode 100644 index 0000000..ca89529 --- /dev/null +++ b/public/docs/javascript/2-control/2-0-loop.md @@ -0,0 +1,9 @@ +--- +id: javascript-control-loop +title: 繰り返し (for, while) +level: 2 +--- + +## 繰り返し (for, while) + +`while`, `do...while`, および古典的な `for` ループは、C/Java/C++等の構文とほぼ同じです。 diff --git a/public/docs/javascript/2-control/2-1-for.md b/public/docs/javascript/2-control/2-1-for.md new file mode 100644 index 0000000..7020490 --- /dev/null +++ b/public/docs/javascript/2-control/2-1-for.md @@ -0,0 +1,14 @@ +--- +id: javascript-control-for +title: 古典的な for ループ +level: 3 +--- + +### 古典的な for ループ + +```js-repl +> for (let i = 0; i < 3; i++) { console.log(i); } +0 +1 +2 +``` diff --git a/public/docs/javascript/2-control/2-2-while.md b/public/docs/javascript/2-control/2-2-while.md new file mode 100644 index 0000000..52980bc --- /dev/null +++ b/public/docs/javascript/2-control/2-2-while.md @@ -0,0 +1,15 @@ +--- +id: javascript-control-while +title: while ループ +level: 3 +--- + +### while ループ + +```js-repl +> let count = 0; +> while (count < 3) { console.log(count++); } +0 +1 +2 +``` diff --git a/public/docs/javascript/2-control/3-0-iteration.md b/public/docs/javascript/2-control/3-0-iteration.md new file mode 100644 index 0000000..e77f339 --- /dev/null +++ b/public/docs/javascript/2-control/3-0-iteration.md @@ -0,0 +1,14 @@ +--- +id: javascript-control-iteration +title: 'イテレーション: for...of と for...in の違い' +level: 2 +--- + +## イテレーション: `for...of` と `for...in` の違い + +現代のJavaScript開発において、最も重要なのがこの2つのループの使い分けです。これらは似ていますが、役割が明確に異なります。 + +| 構文 | 取得するもの | 対象 | 推奨ユースケース | +| :--- | :--- | :--- | :--- | +| **`for...in`** | **キー (Key)** | Object | オブジェクトのプロパティ調査 | +| **`for...of`** | **値 (Value)** | Array, String, Map, Set | 配列やリストデータの処理 | diff --git a/public/docs/javascript/2-control/3-1-for-in.md b/public/docs/javascript/2-control/3-1-for-in.md new file mode 100644 index 0000000..6ec4f1b --- /dev/null +++ b/public/docs/javascript/2-control/3-1-for-in.md @@ -0,0 +1,42 @@ +--- +id: javascript-control-for-in +title: for...in ループ(プロパティ名の列挙) +level: 3 +--- + +### `for...in` ループ(プロパティ名の列挙) + +`for...in` はオブジェクトの **キー(プロパティ名)** を列挙するために設計されています。 +配列に対して使用すると、インデックス("0", "1", ...)が文字列として返ってくるだけでなく、プロトタイプチェーン上のプロパティまで列挙してしまうリスクがあるため、**配列への使用は推奨されません**。 + +```js:for_in_example.js +const user = { + name: "Bob", + age: 30, + role: "admin" +}; + +// オブジェクトのキーを列挙する +for (const key in user) { + console.log(`${key}: ${user[key]}`); +} + +// 配列に対する for...in(非推奨の例) +const colors = ["Red", "Green"]; +Array.prototype.badProp = "Do not do this"; // プロトタイプ汚染のシミュレーション + +console.log("--- Array via for...in ---"); +for (const index in colors) { + console.log(index); // "0", "1", "badProp" が出力される可能性がある +} +``` + +```js-exec:for_in_example.js +name: Bob +age: 30 +role: admin +--- Array via for...in --- +0 +1 +badProp +``` diff --git a/public/docs/javascript/2-control/3-2-for-of.md b/public/docs/javascript/2-control/3-2-for-of.md new file mode 100644 index 0000000..09293b7 --- /dev/null +++ b/public/docs/javascript/2-control/3-2-for-of.md @@ -0,0 +1,42 @@ +--- +id: javascript-control-for-of +title: for...of ループ(反復可能オブジェクトの走査) +level: 3 +--- + +### `for...of` ループ(反復可能オブジェクトの走査) + +ES2015 (ES6) で導入された `for...of` は、**値(Values)** を反復します。 +配列、文字列、Map、Setなどの **Iterable(反復可能)** なオブジェクトに対して使用します。配列の中身を順番に処理したい場合は、こちらが正解です。 + +```js:for_of_example.js +const languages = ["JavaScript", "Python", "Go"]; + +// 配列の値を直接取得できる +for (const lang of languages) { + console.log(lang); +} + +// 文字列もIterable +const word = "AI"; +for (const char of word) { + console.log(char); +} +``` + +```js-exec:for_of_example.js +JavaScript +Python +Go +A +I +``` + +> **Tips:** オブジェクトの中身を `for...of` で回したい場合は、`Object.keys()`, `Object.values()`, `Object.entries()` を使うのがモダンな手法です。 + +```js +const obj = { a: 1, b: 2 }; +for (const [key, val] of Object.entries(obj)) { + console.log(key, val); +} +``` diff --git a/public/docs/javascript/2-control/4-0-try-catch.md b/public/docs/javascript/2-control/4-0-try-catch.md new file mode 100644 index 0000000..5af3554 --- /dev/null +++ b/public/docs/javascript/2-control/4-0-try-catch.md @@ -0,0 +1,38 @@ +--- +id: javascript-control-try-catch +title: 例外処理 (try...catch...finally) +level: 2 +--- + +## 例外処理 (`try...catch...finally`) + +JavaScriptの例外処理は `try...catch...finally` 構文を使用します。 + +実行時にエラーが発生すると、処理が中断され `catch` ブロックに移行します。 + +```js:try_catch.js +function parseJson(jsonString) { + try { + const result = JSON.parse(jsonString); + console.log("パース成功:", result); + return result; + } catch (e) { + // エラーオブジェクトが e に入る + console.error("パース失敗:", e.name, e.message); + } finally { + console.log("処理終了(成功・失敗に関わらず実行)"); + } +} + +parseJson('{"valid": true}'); +console.log("---"); +parseJson('Invalid JSON'); +``` + +```js-exec:try_catch.js +パース成功: { valid: true } +処理終了(成功・失敗に関わらず実行) +--- +パース失敗: SyntaxError Unexpected token I in JSON at position 0 +処理終了(成功・失敗に関わらず実行) +``` diff --git a/public/docs/javascript/2-control/4-2-throw.md b/public/docs/javascript/2-control/4-2-throw.md new file mode 100644 index 0000000..306a27a --- /dev/null +++ b/public/docs/javascript/2-control/4-2-throw.md @@ -0,0 +1,18 @@ +--- +id: javascript-control-throw +title: throw について +level: 3 +--- + +### `throw` について + +JavaScriptでは `throw` で例外を投げることができます。`Error` オブジェクトを投げるのが一般的ですが、技術的には文字列や数値など、任意の値を投げることが可能です(ただし、スタックトレースが取れなくなるため推奨されません)。 + +```js-repl +> try { throw new Error("Something went wrong"); } catch (e) { console.log(e.message); } +Something went wrong + +> // プリミティブ値を投げることも可能だが非推奨 +> try { throw "Just a string"; } catch (e) { console.log(typeof e, e); } +string Just a string +``` diff --git a/public/docs/javascript/2-control/5-0-summary.md b/public/docs/javascript/2-control/5-0-summary.md new file mode 100644 index 0000000..f9bb212 --- /dev/null +++ b/public/docs/javascript/2-control/5-0-summary.md @@ -0,0 +1,13 @@ +--- +id: javascript-control-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **条件分岐:** `if` 文での `[]` や `{}` は Truthy であることに注意。`switch` は厳密等価 (`===`) で判定される。 + * **繰り返し:** 古典的な `for`, `while` は他のC系言語と同じ。 + * **for...in:** オブジェクトの **キー** を列挙する。配列には使わないこと。 + * **for...of:** 配列やコレクションの **値** を反復する。リスト処理の標準。 + * **例外処理:** `try...catch...finally` で行う。`throw` は任意の値を投げられるが、通常は `Error` オブジェクトを使用する。 diff --git a/public/docs/javascript/2-control/5-1-practice1.md b/public/docs/javascript/2-control/5-1-practice1.md new file mode 100644 index 0000000..05b2f95 --- /dev/null +++ b/public/docs/javascript/2-control/5-1-practice1.md @@ -0,0 +1,18 @@ +--- +id: javascript-control-practice1 +title: '練習問題1: 配列のフィルタリングと集計' +level: 3 +--- + +### 練習問題1: 配列のフィルタリングと集計 + +以下の数値が入った配列 `numbers` があります。 +`for...of` ループを使用して、**偶数かつ10より大きい数値** だけを抽出し、その合計値を計算してコンソールに出力するプログラムを書いてください。 + +```js:practice3_1.js +const numbers = [5, 12, 8, 20, 7, 3, 14, 30]; +// ここにコードを書く +``` + +```js-exec:practice3_1.js +``` diff --git a/public/docs/javascript/2-control/5-2-practice2.md b/public/docs/javascript/2-control/5-2-practice2.md new file mode 100644 index 0000000..be8ff50 --- /dev/null +++ b/public/docs/javascript/2-control/5-2-practice2.md @@ -0,0 +1,26 @@ +--- +id: javascript-control-practice2 +title: '問題 2: 簡易コマンドディスパッチャ' +level: 3 +--- + +### 問題 2: 簡易コマンドディスパッチャ + +以下の仕様を満たす関数 `executeCommand(command)` を `switch` 文と `try...catch` を用いて作成してください。 + +1. 引数 `command` は文字列を受け取る。 +2. `"start"` の場合、"System starting..." を出力。 +3. `"stop"` の場合、"System stopping..." を出力。 +4. それ以外の文字列の場合、`Error` オブジェクトを `throw` する(メッセージは "Unknown command")。 +5. `try...catch` ブロックを用いてこの関数を呼び出し、エラーが発生した場合は "Error caught: Unknown command" のように出力する。 + +**ヒント:** `command` が `null` や `undefined` の場合もエラーとして処理されるように実装してください。 + +```js:practice3_2.js +function executeCommand(command) { + +} +``` + +```js-exec:practice3_2.js +``` diff --git a/public/docs/javascript/3-functions-closures/-intro.md b/public/docs/javascript/3-functions-closures/-intro.md new file mode 100644 index 0000000..f777463 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/-intro.md @@ -0,0 +1 @@ +JavaScriptにおいて関数はオブジェクトの一種です。つまり、変数に代入したり、他の関数の引数として渡したり、関数から戻り値として返したりすることができます。この柔軟性が、JavaScriptの設計パターンの核心を担っています。 diff --git a/public/docs/javascript/3-functions-closures/1-0-function.md b/public/docs/javascript/3-functions-closures/1-0-function.md new file mode 100644 index 0000000..fb8b748 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/1-0-function.md @@ -0,0 +1,9 @@ +--- +id: javascript-functions-basic +title: 関数の定義(関数宣言 vs 関数式) +level: 2 +--- + +## 関数の定義(関数宣言 vs 関数式) + +JavaScriptには関数を定義する方法が主に2つあります。「関数宣言」と「関数式」です。これらは似ていますが、**巻き上げ(Hoisting)** の挙動が異なります。 diff --git a/public/docs/javascript/3-functions-closures/1-1-function-declaration.md b/public/docs/javascript/3-functions-closures/1-1-function-declaration.md new file mode 100644 index 0000000..f09462f --- /dev/null +++ b/public/docs/javascript/3-functions-closures/1-1-function-declaration.md @@ -0,0 +1,21 @@ +--- +id: javascript-function-declaration +title: 1. 関数宣言 (Function Declaration) +level: 3 +--- + +### 1\. 関数宣言 (Function Declaration) + +古くからある定義方法です。スクリプトの実行前に読み込まれるため、定義する前の行から呼び出すことができます。 + +```js:function_declaration.js +console.log(greet("Alice")); // 定義前でも呼び出せる + +function greet(name) { + return `Hello, ${name}!`; +} +``` + +```js-exec:function_declaration.js +Hello, Alice! +``` diff --git a/public/docs/javascript/3-functions-closures/1-2-function-expression.md b/public/docs/javascript/3-functions-closures/1-2-function-expression.md new file mode 100644 index 0000000..75f4c83 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/1-2-function-expression.md @@ -0,0 +1,24 @@ +--- +id: javascript-function-expression +title: 2. 関数式 (Function Expression) +level: 3 +--- + +### 2\. 関数式 (Function Expression) + +変数に関数を代入するスタイルです。変数の代入は実行時に行われるため、定義する前に呼び出すとエラーになります。現代のJavaScript開発では、意図しない巻き上げを防ぐためにこちら(または後述のアロー関数)が好まれる傾向にあります。 + +```js:function_expression.js +// 定義前に呼び出すと... ReferenceError: Cannot access 'sayHi' before initialization +// console.log(sayHi("Bob")); + +const sayHi = function(name) { + return `Hi, ${name}!`; +}; + +console.log(sayHi("Bob")); +``` + +```js-exec:function_expression.js +Hi, Bob! +``` diff --git a/public/docs/javascript/3-functions-closures/2-0-arrow-function.md b/public/docs/javascript/3-functions-closures/2-0-arrow-function.md new file mode 100644 index 0000000..d785025 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/2-0-arrow-function.md @@ -0,0 +1,29 @@ +--- +id: javascript-arrow-function-basic +title: 'アロー関数 (=>) の構文と特徴' +level: 2 +--- + +## アロー関数 (`=>`) の構文と特徴 + +ES2015 (ES6) で導入されたアロー関数は、関数式をより短く記述するための構文です。 +`function` キーワードを省略し、`=>` (矢印) を使って定義します。 +Javaのラムダ式やPythonのlambdaに似ていますが、いくつか独自の特徴があります。 + +```js:arrow_function.js +// 従来の関数式 +const add = function(a, b) { + return a + b; +}; + +// アロー関数 +const addArrow = (a, b) => { + return a + b; +}; + +console.log(addArrow(3, 5)); +``` + +```js-exec:arrow_function.js +8 +``` diff --git a/public/docs/javascript/3-functions-closures/2-2-arrow-function-simple.md b/public/docs/javascript/3-functions-closures/2-2-arrow-function-simple.md new file mode 100644 index 0000000..de01221 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/2-2-arrow-function-simple.md @@ -0,0 +1,24 @@ +--- +id: javascript-arrow-function-simple +title: 省略記法 +level: 3 +--- + +### 省略記法 + +アロー関数には強力な省略記法があります。 + +1. **引数が1つの場合**: カッコ `()` を省略可能。 +2. **処理が1行でreturnする場合**: 中括弧 `{}` と `return` キーワードを省略可能(暗黙のreturn)。 + +```js-repl +> const square = x => x * x; // 引数の()とreturnを省略 +> square(5); +25 + +> const getUser = (id, name) => ({ id: id, name: name }); // オブジェクトを返す場合は()で囲む +> getUser(1, "Gemini"); +{ id: 1, name: 'Gemini' } +``` + +> **注意:** アロー関数は単なる短縮記法ではありません。「`this` を持たない」という重要な特徴がありますが、これについては**第5章**で詳しく解説します。 diff --git a/public/docs/javascript/3-functions-closures/3-0-argument.md b/public/docs/javascript/3-functions-closures/3-0-argument.md new file mode 100644 index 0000000..7e8b9f2 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/3-0-argument.md @@ -0,0 +1,9 @@ +--- +id: javascript-functions-argument +title: 引数:デフォルト引数、Restパラメータ (...) +level: 2 +--- + +## 引数:デフォルト引数、Restパラメータ (...) + +関数の柔軟性を高めるための引数の機能を見ていきましょう。 diff --git a/public/docs/javascript/3-functions-closures/3-1-default-arg.md b/public/docs/javascript/3-functions-closures/3-1-default-arg.md new file mode 100644 index 0000000..fb97136 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/3-1-default-arg.md @@ -0,0 +1,25 @@ +--- +id: javascript-functions-default-arg +title: デフォルト引数 +level: 3 +--- + +### デフォルト引数 + +引数が渡されなかった場合(または `undefined` の場合)に使用される初期値を設定できます。 + +```js:default_args.js +const connect = (host = 'localhost', port = 8080) => { + console.log(`Connecting to ${host}:${port}...`); +}; + +connect(); // 両方省略 +connect('127.0.0.1'); // portはデフォルト値 +connect('example.com', 22); // 両方指定 +``` + +```js-exec:default_args.js +Connecting to localhost:8080... +Connecting to 127.0.0.1:8080... +Connecting to example.com:22... +``` diff --git a/public/docs/javascript/3-functions-closures/3-2-rest-arg.md b/public/docs/javascript/3-functions-closures/3-2-rest-arg.md new file mode 100644 index 0000000..2fee881 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/3-2-rest-arg.md @@ -0,0 +1,24 @@ +--- +id: javascript-functions-rest-arg +title: Restパラメータ (残余引数) +level: 3 +--- + +### Restパラメータ (残余引数) + +引数の数が不定の場合、`...` を使うことで、残りの引数を**配列として**受け取ることができます。以前は `arguments` オブジェクトを使っていましたが、Restパラメータの方が配列メソッド(`map`, `reduce`など)を直接使えるため便利です。 + +```js:rest_params.js +const sum = (...numbers) => { + // numbersは本物の配列 [1, 2, 3, 4, 5] + return numbers.reduce((acc, curr) => acc + curr, 0); +}; + +console.log(sum(1, 2, 3)); +console.log(sum(10, 20, 30, 40, 50)); +``` + +```js-exec:rest_params.js +6 +150 +``` diff --git a/public/docs/javascript/3-functions-closures/4-0-scope-chain.md b/public/docs/javascript/3-functions-closures/4-0-scope-chain.md new file mode 100644 index 0000000..24526ec --- /dev/null +++ b/public/docs/javascript/3-functions-closures/4-0-scope-chain.md @@ -0,0 +1,32 @@ +--- +id: javascript-functions-scope-chain +title: スコープチェーンとレキシカルスコープ +level: 2 +--- + +## スコープチェーンとレキシカルスコープ + +JavaScriptの変数の有効範囲(スコープ)を理解するために、「レキシカルスコープ」という概念を知る必要があります。 + + * **レキシカルスコープ (Lexical Scope):** 関数が「どこで呼び出されたか」ではなく、**「どこで定義されたか」**によってスコープが決まるというルールです。 + * **スコープチェーン (Scope Chain):** 変数を探す際、現在のスコープになければ、定義時の外側のスコープへと順番に探しに行く仕組みです。 + +```js:scope.js +const globalVar = "Global"; + +function outer() { + const outerVar = "Outer"; + function inner() { + const innerVar = "Inner"; + // innerの中からouterVarとglobalVarが見える(スコープチェーン) + return `${globalVar} > ${outerVar} > ${innerVar}`; + } + return inner(); +} + +console.log(outer()); +``` + +```js-exec:scope.js +Global > Outer > Inner +``` diff --git a/public/docs/javascript/3-functions-closures/5-0-closure.md b/public/docs/javascript/3-functions-closures/5-0-closure.md new file mode 100644 index 0000000..4f03421 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/5-0-closure.md @@ -0,0 +1,50 @@ +--- +id: javascript-closures +title: クロージャ:関数が状態を持つ仕組み +level: 2 +--- + +## クロージャ:関数が状態を持つ仕組み + +クロージャ (Closure) は、この章の最重要トピックです。 +一言で言えば、**「外側の関数のスコープにある変数を、外側の関数の実行終了後も参照し続ける関数」**のことです。 + +**なぜクロージャを使うのか?** + +1. **カプセル化 (Encapsulation):** 変数を隠蔽し、特定の関数経由でしか変更できないようにすることで、予期せぬバグを防ぎます。 +2. **状態の保持:** グローバル変数を使わずに、関数単位で永続的な状態を持てます。 +3. **関数ファクトリ:** 設定の異なる関数を動的に生成する場合に役立ちます。 + +プライベートな変数を持つカウンタを作ってみましょう。 + +通常、関数(`createCounter`)の実行が終わると、そのローカル変数(`count`)はメモリから破棄されます。しかし、その変数を参照している内部関数(`increment`)が存在し、その内部関数が外部に返された場合、変数は破棄されずに保持され続けます。 + +```js:closure_counter.js +const createCounter = () => { + let count = 0; // この変数は外部から直接アクセスできない(プライベート変数的な役割) + + return () => { + count++; + console.log(`Current count: ${count}`); + }; +}; + +const counterA = createCounter(); // counterA専用のスコープ(環境)が作られる +const counterB = createCounter(); // counterB専用のスコープが別に作られる + +counterA(); // 1 +counterA(); // 2 +counterA(); // 3 + +console.log("--- switching to B ---"); + +counterB(); // 1 (Aの状態とは独立している) +``` + +```js-exec:closure_counter.js +Current count: 1 +Current count: 2 +Current count: 3 +--- switching to B --- +Current count: 1 +``` diff --git a/public/docs/javascript/3-functions-closures/6-0-summary.md b/public/docs/javascript/3-functions-closures/6-0-summary.md new file mode 100644 index 0000000..70887ac --- /dev/null +++ b/public/docs/javascript/3-functions-closures/6-0-summary.md @@ -0,0 +1,13 @@ +--- +id: javascript-functions-closures-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **関数定義:** 巻き上げが起こる「関数宣言」と、起こらない「関数式(アロー関数含む)」がある。 + * **アロー関数:** `(args) => body` の形式で記述し、`this` の挙動が従来と異なる。 + * **引数:** デフォルト引数とRestパラメータ(`...args`)で柔軟な引数処理が可能。 + * **レキシカルスコープ:** 関数は「定義された場所」のスコープを記憶する。 + * **クロージャ:** 内部関数が外部関数の変数を参照し続ける仕組み。データの隠蔽や状態保持に使われる。 diff --git a/public/docs/javascript/3-functions-closures/6-1-practice1.md b/public/docs/javascript/3-functions-closures/6-1-practice1.md new file mode 100644 index 0000000..3c1d87e --- /dev/null +++ b/public/docs/javascript/3-functions-closures/6-1-practice1.md @@ -0,0 +1,18 @@ +--- +id: javascript-functions-closures-practice1 +title: '練習問題1: アロー関数への書き換え' +level: 3 +--- + +### 練習問題1: アロー関数への書き換え + +以下の関数宣言を、アロー関数 `isEven` に書き換えてください。ただし、省略可能な記号(カッコやreturnなど)は可能な限り省略して最短で記述してください。 + +```js:practice4_1.js +function isEven(n) { + return n % 2 === 0; +} +``` + +```js-exec:practice4_1.js +``` diff --git a/public/docs/javascript/3-functions-closures/6-2-practice2.md b/public/docs/javascript/3-functions-closures/6-2-practice2.md new file mode 100644 index 0000000..9eef041 --- /dev/null +++ b/public/docs/javascript/3-functions-closures/6-2-practice2.md @@ -0,0 +1,25 @@ +--- +id: javascript-functions-closures-practice2 +title: '問題2: クロージャによる掛け算生成器' +level: 3 +--- + +### 問題2: クロージャによる掛け算生成器 + +`createMultiplier` という関数を作成してください。この関数は数値 `x` を引数に取り、呼び出すたびに「引数を `x` 倍して返す関数」を返します。 + +**使用例:** + +```js:practice4_2.js +// ここに関数を作成 + + +const double = createMultiplier(2); +console.log(double(5)); // 10 + +const triple = createMultiplier(3); +console.log(triple(5)); // 15 +``` + +```js-exec:practice4_2.js +``` diff --git a/public/docs/javascript/4-this/-intro.md b/public/docs/javascript/4-this/-intro.md new file mode 100644 index 0000000..c51fe1c --- /dev/null +++ b/public/docs/javascript/4-this/-intro.md @@ -0,0 +1,5 @@ +JavaScriptの学習、お疲れ様です。他の言語(Java, C\#, Pythonなど)の経験がある方にとって、JavaScriptの `this` は最も直感に反し、バグの温床となりやすい機能の一つです。 + +多くの言語において `this`(または `self`)は「そのクラスのインスタンス」を指す静的なものですが、**JavaScriptの `this` は「関数がどのように呼ばれたか」によって動的に変化します。** + +この章では、その複雑な挙動を解き明かし、自在にコントロールする方法を学びます。 diff --git a/public/docs/javascript/4-this/1-0-this-basic.md b/public/docs/javascript/4-this/1-0-this-basic.md new file mode 100644 index 0000000..4f8b5b2 --- /dev/null +++ b/public/docs/javascript/4-this/1-0-this-basic.md @@ -0,0 +1,50 @@ +--- +id: javascript-this-basic +title: "this は呼び出し方で決まる" +level: 2 +--- + +## `this` は呼び出し方で決まる + +JavaScriptにおける関数(アロー関数を除く)の `this` は、**「定義された場所」ではなく「呼び出された場所(Call Site)」**によって決定されます。 + +大きく分けて、以下の3つのパターンを理解すれば基本は押さえられます。 + +* **メソッド呼び出し:** オブジェクトのプロパティとして関数を呼び出した場合(`obj.method()`)、`this` は**ドットの左側のオブジェクト**(レシーバ)になります。これは他の言語のメンバ関数に近い挙動です。 +* **関数呼び出し:** 関数を単体で呼び出した場合(`func()`)、`this` は**グローバルオブジェクト**(ブラウザでは `window`、Node.jsでは `global`)になります。 +ただし、**Strict Mode(`"use strict"`)**では、安全のため `undefined` になります。 +* **コンストラクタ呼び出し:** `new` キーワードをつけて呼び出した場合、`this` は**新しく生成されたインスタンス**になります(これは第6章、第7章で詳しく扱います)。 + +以下のコードで、同じ関数でも呼び出し方によって `this` が変わる様子を確認しましょう。 + +```js:dynamic-this.js +"use strict"; // Strict Modeを有効化 + +function showThis() { + console.log(`this is: ${this}`); +} + +const person = { + name: "Alice", + show: showThis, + toString: function() { return this.name; } // コンソール出力用に設定 +}; + +// 1. メソッド呼び出し +console.log("--- Method Call ---"); +person.show(); + +// 2. 関数呼び出し(変数に代入してから実行) +console.log("--- Function Call ---"); +const standaloneShow = person.show; +standaloneShow(); +``` + +```js-exec:dynamic-this.js +--- Method Call --- +this is: Alice +--- Function Call --- +this is: undefined +``` + +> **ポイント:** `person.show` を `standaloneShow` に代入した時点で、オブジェクトとの結びつきは失われます。そのため、`standaloneShow()` と実行すると「関数呼び出し」扱いとなり、`this` は `undefined`(非Strict Modeならグローバルオブジェクト)になります。これが「`this` が消える」現象の正体です。 diff --git a/public/docs/javascript/4-this/2-0-bind-basic.md b/public/docs/javascript/4-this/2-0-bind-basic.md new file mode 100644 index 0000000..e931fbc --- /dev/null +++ b/public/docs/javascript/4-this/2-0-bind-basic.md @@ -0,0 +1,9 @@ +--- +id: javascript-this-bind-basic +title: "this を固定する: bind, call, apply" +level: 2 +--- + +## `this` を固定する: bind, call, apply + +「関数呼び出し」でも特定のオブジェクトを `this` として扱いたい場合があります。JavaScriptには、`this` を明示的に指定(束縛)するためのメソッドが3つ用意されています。 diff --git a/public/docs/javascript/4-this/2-1-call-apply.md b/public/docs/javascript/4-this/2-1-call-apply.md new file mode 100644 index 0000000..e7919e8 --- /dev/null +++ b/public/docs/javascript/4-this/2-1-call-apply.md @@ -0,0 +1,25 @@ +--- +id: javascript-this-call-apply +title: call と apply +level: 3 +--- + +### call と apply + +これらは関数を**即座に実行**します。第一引数に `this` としたいオブジェクトを渡します。 + + * `call(thisArg, arg1, arg2, ...)`: 引数をカンマ区切りで渡す。 + * `apply(thisArg, [argsArray])`: 引数を配列として渡す。 + +```js-repl +> function greet(greeting, punctuation) { return `${greeting}, ${this.name}${punctuation}`; } +undefined +> const user = { name: "Bob" }; +undefined +> // callの使用例 +> greet.call(user, "Hello", "!"); +'Hello, Bob!' +> // applyの使用例 +> greet.apply(user, ["Hi", "?"]); +'Hi, Bob?' +``` diff --git a/public/docs/javascript/4-this/2-2-bind.md b/public/docs/javascript/4-this/2-2-bind.md new file mode 100644 index 0000000..db18a07 --- /dev/null +++ b/public/docs/javascript/4-this/2-2-bind.md @@ -0,0 +1,30 @@ +--- +id: javascript-this-bind +title: bind +level: 3 +--- + +### bind + +`bind` は関数を実行せず、**`this` を固定した新しい関数**を返します。これは、イベントリスナーやコールバック関数としてメソッドを渡す際に非常に重要です。 + +```js:bind-example.js +const engine = { + type: "V8", + start: function() { + console.log(`Starting ${this.type} engine...`); + } +}; + +// そのまま渡すと this が失われる(関数呼び出しになるため) +const brokenStart = engine.start; +// brokenStart(); // エラー: Cannot read property 'type' of undefined + +// bind で this を engine に固定する +const fixedStart = engine.start.bind(engine); +fixedStart(); +``` + +```js-exec:bind-example.js +Starting V8 engine... +``` diff --git a/public/docs/javascript/4-this/3-0-arrow-func.md b/public/docs/javascript/4-this/3-0-arrow-func.md new file mode 100644 index 0000000..5a1dc0d --- /dev/null +++ b/public/docs/javascript/4-this/3-0-arrow-func.md @@ -0,0 +1,58 @@ +--- +id: javascript-this-arrow-func +title: "アロー関数と this" +level: 2 +--- + +## アロー関数と `this` + +ES2015 (ES6) で導入されたアロー関数は、これまで説明したルールとは全く異なる挙動をします。 + +アロー関数には**独自の `this` が存在しません**。アロー関数内部の `this` は、**その関数が定義されたスコープ(レキシカルスコープ)の `this`** をそのまま参照します。 + +これは、「コールバック関数内で `this` が変わってしまう問題」を解決するのに最適です。 + + +`setTimeout` などのコールバック内でメソッドを使いたい場面を比較してみましょう。 + +```js:arrow-vs-function.js +class Timer { + constructor() { + this.seconds = 0; + } + + // 従来の方法: 失敗例 + startLegacy() { + setTimeout(function() { + // ここでの this はグローバルまたはundefined(setTimeoutの仕様) + // そのため this.seconds にアクセスできずNaNなどになる + try { + this.seconds++; + console.log("Legacy:", this.seconds); + } catch (e) { + console.log("Legacy: Error -", e.message); + } + }, 100); + } + + // アロー関数: 成功例 + startModern() { + setTimeout(() => { + // アロー関数は定義時のスコープ(startModern内のthis = インスタンス)を捕獲する + this.seconds++; + console.log("Modern:", this.seconds); + }, 100); + } +} + +const timer = new Timer(); +timer.startLegacy(); +timer.startModern(); +``` + +```js-exec:arrow-vs-function.js +Legacy: Error - Cannot read properties of undefined (reading 'seconds') +Modern: 1 +``` + +> **注意:** アロー関数は便利な反面、`this` を動的に変更することができません(`call` や `bind` を使っても無視されます)。そのため、動的なコンテキストが必要な場合(例:オブジェクトのメソッド定義そのものや、ライブラリ等で `this` を注入される場合)には通常の関数式を使います。 diff --git a/public/docs/javascript/4-this/4-0-summary.md b/public/docs/javascript/4-this/4-0-summary.md new file mode 100644 index 0000000..0e11c91 --- /dev/null +++ b/public/docs/javascript/4-this/4-0-summary.md @@ -0,0 +1,16 @@ +--- +id: javascript-this-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +JavaScriptの `this` は、他の静的な言語とは異なり「呼び出し時」に解決されます。 + +1. **メソッド呼び出し (`obj.method()`)**: `this` は `obj`。 +2. **関数呼び出し (`func()`)**: `this` は `undefined` (Strict Mode) またはグローバルオブジェクト。 +3. **明示的な指定**: `call`, `apply` で一時的に、`bind` で永続的に `this` を指定可能。 +4. **アロー関数**: 独自の `this` を持たず、外側のスコープの `this` をそのまま使う(レキシカルスコープ)。 + +次の章では、この `this` を活用してオブジェクト指向プログラミングの核心である「オブジェクトとプロトタイプ」について学びます。 diff --git a/public/docs/javascript/4-this/4-1-practice1.md b/public/docs/javascript/4-this/4-1-practice1.md new file mode 100644 index 0000000..e9e4cd1 --- /dev/null +++ b/public/docs/javascript/4-this/4-1-practice1.md @@ -0,0 +1,33 @@ +--- +id: javascript-this-practice1 +title: '練習問題1: 失われたコンテキストの修復' +level: 3 +--- + +### 練習問題1: 失われたコンテキストの修復 + +以下のコードは、ボタンクリック時(ここではシミュレーション)にユーザー名を表示しようとしていますが、エラーになります。 + +1. `bind` を使って修正してください。 +2. `greet` メソッド自体をアロー関数に変更するアプローチではなく、呼び出し側を修正する形で解答してください。 + +```js:practice5_1.js +const user = { + name: "Tanaka", + greet: function() { + console.log(`Hello, ${this.name}`); + } +}; + +// クリックイベントのシミュレーター(変更不可) +function simulateClick(callback) { + // 内部で単なる関数呼び出しとして実行される + callback(); +} + +// --- 以下を修正してください --- +simulateClick(user.greet); +``` + +```js-exec:practice5_1.js +``` diff --git a/public/docs/javascript/4-this/4-2-practice2.md b/public/docs/javascript/4-this/4-2-practice2.md new file mode 100644 index 0000000..659bde2 --- /dev/null +++ b/public/docs/javascript/4-this/4-2-practice2.md @@ -0,0 +1,32 @@ +--- +id: javascript-this-practice2 +title: '問題2: アロー関数の特性' +level: 3 +--- + +### 問題2: アロー関数の特性 + +以下の `calculator` オブジェクトにはバグがあります。`multiply` メソッドが正しい結果(配列の各要素を `factor` 倍する)を返すように修正してください。 +ヒント:`map` の中のコールバック関数に注目してください。 + +```js:practice5_2.js +const calculator = { + factor: 2, + numbers: [1, 2, 3], + multiply: function() { + return this.numbers.map(function(n) { + // ここで this.factor が読めない! + return n * this.factor; + }); + } +}; + +try { + console.log(calculator.multiply()); +} catch(e) { + console.log("Error:", e.message); +} +``` + +```js-exec:practice5_2.js +``` diff --git a/public/docs/javascript/5-objects-prototype/-intro.md b/public/docs/javascript/5-objects-prototype/-intro.md new file mode 100644 index 0000000..45b5818 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/-intro.md @@ -0,0 +1,3 @@ +他の言語(Java, C\#, Pythonなど)の経験がある方にとって、JavaScriptの「オブジェクト」と「継承」のモデルは最も混乱しやすい部分の一つです。JavaScriptはクラスベースではなく、**プロトタイプベース**のオブジェクト指向言語です。 + +本章では、ES6(ECMAScript 2015)以降の`class`構文(第7章で扱います)の裏側で実際に何が起きているのか、その仕組みの根幹である「プロトタイプチェーン」について解説します。 diff --git a/public/docs/javascript/5-objects-prototype/1-0-obj-literal.md b/public/docs/javascript/5-objects-prototype/1-0-obj-literal.md new file mode 100644 index 0000000..8656771 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/1-0-obj-literal.md @@ -0,0 +1,27 @@ +--- +id: javascript-objects-literal +title: オブジェクトリテラルとプロパティ +level: 2 +--- + +## オブジェクトリテラルとプロパティ + +JavaScriptにおけるオブジェクトは、基本的にはキー(プロパティ名)と値のコレクション(連想配列やハッシュマップに近いもの)です。最も一般的な生成方法は**オブジェクトリテラル** `{...}` を使うことです。 + +```js-repl +> const book = { +... title: "JavaScript Primer", +... "page-count": 350, // ハイフンを含むキーは引用符が必要 +... author: { +... name: "John Doe", +... age: 30 +... } +... }; +undefined +> book.title +'JavaScript Primer' +> book["page-count"] // 識別子として無効な文字を含む場合はブラケット記法 +350 +> book.author.name +'John Doe' +``` diff --git a/public/docs/javascript/5-objects-prototype/1-1-properties.md b/public/docs/javascript/5-objects-prototype/1-1-properties.md new file mode 100644 index 0000000..8ad4b05 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/1-1-properties.md @@ -0,0 +1,20 @@ +--- +id: javascript-objects-properties +title: プロパティの追加・削除 +level: 3 +--- + +### プロパティの追加・削除 + +動的な言語であるJavaScriptでは、オブジェクト作成後にプロパティを追加・削除できます。 + +```js-repl +> const config = { env: "production" }; +undefined +> config.port = 8080; // 追加 +8080 +> delete config.env; // 削除 +true +> config +{ port: 8080 } +``` diff --git a/public/docs/javascript/5-objects-prototype/2-0-method-this-recall.md b/public/docs/javascript/5-objects-prototype/2-0-method-this-recall.md new file mode 100644 index 0000000..e6a2b10 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/2-0-method-this-recall.md @@ -0,0 +1,33 @@ +--- +id: javascript-objects-method-this-recall +title: メソッドと this(復習) +level: 2 +--- + +## メソッドと this(復習) + +オブジェクトのプロパティには関数も設定できます。これを**メソッド**と呼びます。 +第5章で学んだ通り、メソッド呼び出しにおける `this` は、「ドットの左側にあるオブジェクト(レシーバ)」を指します。 + +```js-repl +> const counter = { +... count: 0, +... increment: function() { +... this.count++; +... return this.count; +... }, +... // ES6からの短縮記法(推奨) +... reset() { +... this.count = 0; +... } +... }; +undefined +> counter.increment(); +1 +> counter.increment(); +2 +> counter.reset(); +undefined +> counter.count +0 +``` diff --git a/public/docs/javascript/5-objects-prototype/3-0-prototype-basic.md b/public/docs/javascript/5-objects-prototype/3-0-prototype-basic.md new file mode 100644 index 0000000..08936fc --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/3-0-prototype-basic.md @@ -0,0 +1,11 @@ +--- +id: javascript-prototype-basic +title: プロトタイプとは何か? +level: 2 +--- + +## プロトタイプとは何か? + +ここからが本章の核心です。JavaScriptのすべてのオブジェクトは、自身の親となる別のオブジェクトへの隠されたリンクを持っています。このリンク先のオブジェクトを**プロトタイプ**と呼びます。 + +オブジェクトからプロパティを読み取ろうとしたとき、そのオブジェクト自身がプロパティを持っていなければ、JavaScriptエンジンは自動的にプロトタイプを探しに行きます。 diff --git a/public/docs/javascript/5-objects-prototype/3-1-getprototypeof.md b/public/docs/javascript/5-objects-prototype/3-1-getprototypeof.md new file mode 100644 index 0000000..f686e8c --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/3-1-getprototypeof.md @@ -0,0 +1,23 @@ +--- +id: javascript-prototype-getprototypeof +title: __proto__ と Object.getPrototypeOf +level: 3 +--- + +### `__proto__` と `Object.getPrototypeOf` + +歴史的経緯により、多くのブラウザで `obj.__proto__` というプロパティを通じてプロトタイプにアクセスできますが、現在の標準的な方法は `Object.getPrototypeOf(obj)` です。 + +```js-repl +> const arr = [1, 2, 3]; +undefined +> // 配列の実体はオブジェクトであり、Array.prototypeを継承している +> Object.getPrototypeOf(arr) === Array.prototype +true +> // Array.prototypeの親はObject.prototype +> Object.getPrototypeOf(Array.prototype) === Object.prototype +true +> // Object.prototypeの親はnull(チェーンの終端) +> Object.getPrototypeOf(Object.prototype) +null +``` diff --git a/public/docs/javascript/5-objects-prototype/4-0-prototype-chain.md b/public/docs/javascript/5-objects-prototype/4-0-prototype-chain.md new file mode 100644 index 0000000..3de73bd --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/4-0-prototype-chain.md @@ -0,0 +1,61 @@ +--- +id: javascript-prototype-chain +title: プロトタイプチェーンによる継承の仕組み +level: 2 +--- + +## プロトタイプチェーンによる継承の仕組み + +あるオブジェクトのプロパティにアクセスした際、JavaScriptは以下の順序で探索を行います。 + +1. そのオブジェクト自身(Own Property)が持っているか? +2. 持っていなければ、そのオブジェクトのプロトタイプが持っているか? +3. それでもなければ、プロトタイプのプロトタイプが持っているか? +4. `null` に到達するまで繰り返し、見つからなければ `undefined` を返す。 + +この連鎖を**プロトタイプチェーン**と呼びます。クラス継承のように型定義をコピーするのではなく、**リンクを辿って委譲(Delegation)する**仕組みです。 + +以下のコードで、具体的な動作を確認してみましょう。 + +```js:prototype_chain.js +const animal = { + eats: true, + walk() { + console.log("Animal walks"); + } +}; + +const rabbit = { + jumps: true, + __proto__: animal // 注意: __proto__への代入は学習目的以外では非推奨 +}; + +const longEar = { + earLength: 10, + __proto__: rabbit +}; + +// 1. longEar自身は walk を持っていない -> rabbitを見る +// 2. rabbitも walk を持っていない -> animalを見る +// 3. animal が walk を持っている -> 実行 +longEar.walk(); + +// 自身のプロパティ +console.log(`Jumps? ${longEar.jumps}`); // rabbitから取得 +console.log(`Eats? ${longEar.eats}`); // animalから取得 + +// プロパティの追加(シャドーイング) +// longEar自身に walk を追加すると、animalの walk は隠蔽される +longEar.walk = function() { + console.log("LongEar walks simply"); +}; + +longEar.walk(); +``` + +```js-exec:prototype_chain.js +Animal walks +Jumps? true +Eats? true +LongEar walks simply +``` diff --git a/public/docs/javascript/5-objects-prototype/5-0-object-create-basic.md b/public/docs/javascript/5-objects-prototype/5-0-object-create-basic.md new file mode 100644 index 0000000..6558541 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/5-0-object-create-basic.md @@ -0,0 +1,9 @@ +--- +id: javascript-prototype-object-create-basic +title: Object.create() とコンストラクタ関数 +level: 2 +--- + +## Object.create() とコンストラクタ関数 + +`__proto__` を直接操作するのはパフォーマンスや標準化の観点から推奨されません。プロトタイプを指定してオブジェクトを生成する正しい方法は2つあります。 diff --git a/public/docs/javascript/5-objects-prototype/5-1-object-create.md b/public/docs/javascript/5-objects-prototype/5-1-object-create.md new file mode 100644 index 0000000..2d1f49c --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/5-1-object-create.md @@ -0,0 +1,20 @@ +--- +id: javascript-prototype-object-create +title: 1. Object.create() +level: 3 +--- + +### 1\. Object.create() + +指定したオブジェクトをプロトタイプとする新しい空のオブジェクトを生成します。 + +```js-repl +> const proto = { greet: function() { return "Hello"; } }; +undefined +> const obj = Object.create(proto); +undefined +> obj.greet(); +'Hello' +> Object.getPrototypeOf(obj) === proto +true +``` diff --git a/public/docs/javascript/5-objects-prototype/5-2-new-constructor.md b/public/docs/javascript/5-objects-prototype/5-2-new-constructor.md new file mode 100644 index 0000000..cb8d2a7 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/5-2-new-constructor.md @@ -0,0 +1,49 @@ +--- +id: javascript-prototype-new-constructor +title: 2. コンストラクタ関数(new演算子) +level: 3 +--- + +### 2\. コンストラクタ関数(new演算子) + +ES6の `class` が登場する前、JavaScriptでは関数をコンストラクタとして使用し、`new` 演算子を使ってインスタンスを生成していました。これは現在でも多くのライブラリの内部で使用されている重要なパターンです。 + + * 関数オブジェクトは `prototype` という特別なプロパティを持っています(`__proto__`とは別物です)。 + * `new Func()` すると、作られたインスタンスの `__proto__` に `Func.prototype` がセットされます。 + +```js:constructor_pattern.js +// コンストラクタ関数(慣習として大文字で始める) +function User(name) { + // this = {} (新しい空のオブジェクトが暗黙的に生成される) + this.name = name; + // return this (暗黙的にこのオブジェクトが返される) +} + +// すべてのUserインスタンスで共有したいメソッドは +// User.prototype に定義する(メモリ節約のため) +User.prototype.sayHi = function() { + console.log(`Hi, I am ${this.name}`); +}; + +const user1 = new User("Alice"); +const user2 = new User("Bob"); + +user1.sayHi(); +user2.sayHi(); + +// 仕組みの確認 +console.log(user1.__proto__ === User.prototype); // true +console.log(user1.sayHi === user2.sayHi); // true (同じ関数を共有している) +``` + +```js-exec:constructor_pattern.js +Hi, I am Alice +Hi, I am Bob +true +true +``` + +> **重要な区別:** +> +> * `obj.__proto__`: オブジェクトの実の親(リンク先)。 +> * `Func.prototype`: その関数を `new` したときに、生成されるインスタンスの `__proto__` に代入される**テンプレート**。 diff --git a/public/docs/javascript/5-objects-prototype/6-0-summary.md b/public/docs/javascript/5-objects-prototype/6-0-summary.md new file mode 100644 index 0000000..f8c48bd --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/6-0-summary.md @@ -0,0 +1,12 @@ +--- +id: javascript-objects-prototype-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +1. JavaScriptはクラスベースではなく、**プロトタイプベース**の継承を行う。 +2. オブジェクトは隠しプロパティ(`[[Prototype]]`)を持ち、プロパティが見つからない場合にそこを探索する(プロトタイプチェーン)。 +3. `Object.create(proto)` は、特定のプロトタイプを持つオブジェクトを直接生成する。 +4. コンストラクタ関数と `new` 演算子を使うと、`Func.prototype` を親に持つインスタンスを生成できる。これがJavaなどの「クラス」に近い振る舞いを模倣する仕組みである。 diff --git a/public/docs/javascript/5-objects-prototype/6-1-practice1.md b/public/docs/javascript/5-objects-prototype/6-1-practice1.md new file mode 100644 index 0000000..c0a0023 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/6-1-practice1.md @@ -0,0 +1,20 @@ +--- +id: javascript-objects-prototype-practice1 +title: '練習問題1: 基本的なプロトタイプ継承' +level: 3 +--- + +### 練習問題1: 基本的なプロトタイプ継承 + +`Object.create()` を使用して、以下の要件を満たすコードを書いてください。 + +1. `robot` オブジェクトを作成し、`battery: 100` というプロパティと、バッテリーを10減らして残量を表示する `work` メソッドを持たせる。 +2. `robot` をプロトタイプとする `cleaningRobot` オブジェクトを作成する。 +3. `cleaningRobot` 自身に `type: "cleaner"` というプロパティを追加する。 +4. `cleaningRobot.work()` を呼び出し、正しく動作(プロトタイプチェーンの利用)を確認する。 + +```js:practice6_1.js +``` + +```js-exec:practice6_1.js +``` diff --git a/public/docs/javascript/5-objects-prototype/6-2-practice2.md b/public/docs/javascript/5-objects-prototype/6-2-practice2.md new file mode 100644 index 0000000..e7ecd28 --- /dev/null +++ b/public/docs/javascript/5-objects-prototype/6-2-practice2.md @@ -0,0 +1,19 @@ +--- +id: javascript-objects-prototype-practice2 +title: '練習問題2: コンストラクタ関数' +level: 3 +--- + +### 練習問題2: コンストラクタ関数 + +コンストラクタ関数 `Item` を作成してください。 + +1. `Item` は引数 `name` と `price` を受け取り、プロパティとして保持する。 +2. `Item.prototype` に `getTaxIncludedPrice` メソッドを追加する。これは税率10%を加えた価格を返す。 +3. `new Item("Apple", 100)` でインスタンスを作成し、税込価格が110になることを確認する。 + +```js:practice6_2.js +``` + +```js-exec:practice6_2.js +``` diff --git a/public/docs/javascript/6-classes/-intro.md b/public/docs/javascript/6-classes/-intro.md new file mode 100644 index 0000000..ef0c648 --- /dev/null +++ b/public/docs/javascript/6-classes/-intro.md @@ -0,0 +1,5 @@ +前章では、JavaScriptのオブジェクト指向の核心である「プロトタイプ」について学びました。他の言語(Java, C\#, Pythonなど)の経験者にとって、プロトタイプチェーンによる継承は柔軟ですが、少し直感的ではない部分もあったかもしれません。 + +ES6 (ECMAScript 2015) から導入された **`class` 構文** は、プロトタイプベースの継承メカニズムを隠蔽し、一般的なクラスベースのオブジェクト指向言語に近い記述を可能にするものです。これを「糖衣構文(Syntactic Sugar)」と呼びます。 + +この章では、現代のJavaScript開発で標準となっているクラスの定義方法、継承、そして比較的新しい機能であるプライベートフィールドについて解説します。 diff --git a/public/docs/javascript/6-classes/1-0-class-constructor.md b/public/docs/javascript/6-classes/1-0-class-constructor.md new file mode 100644 index 0000000..d07727d --- /dev/null +++ b/public/docs/javascript/6-classes/1-0-class-constructor.md @@ -0,0 +1,27 @@ +--- +id: javascript-classes-constructor +title: クラスの定義とコンストラクタ +level: 2 +--- + +## クラスの定義とコンストラクタ + +JavaScriptのクラスは `class` キーワードを使って定義します。初期化処理は `constructor` という特別なメソッド内で行います。 + +基本的に、クラス定義の内部は自動的に **Strict Mode (`'use strict'`)** で実行されます。 + +```js-repl +> class User { +... constructor(name, age) { +... this.name = name; +... this.age = age; +... } +... } +undefined +> const user1 = new User("Alice", 30); +undefined +> user1.name +'Alice' +> typeof User // クラスの実態は関数 +'function' +``` diff --git a/public/docs/javascript/6-classes/1-1-class-expr.md b/public/docs/javascript/6-classes/1-1-class-expr.md new file mode 100644 index 0000000..022c08e --- /dev/null +++ b/public/docs/javascript/6-classes/1-1-class-expr.md @@ -0,0 +1,20 @@ +--- +id: javascript-classes-class-expr +title: クラス式 +level: 3 +--- + +### クラス式 + +関数と同様に、クラスも式として変数に代入できます(あまり頻繁には使われませんが、知識として持っておくと良いでしょう)。 + +```js-repl +> const Item = class { +... constructor(price) { +... this.price = price; +... } +... }; +undefined +> new Item(100).price +100 +``` diff --git a/public/docs/javascript/6-classes/2-0-method.md b/public/docs/javascript/6-classes/2-0-method.md new file mode 100644 index 0000000..d91a23c --- /dev/null +++ b/public/docs/javascript/6-classes/2-0-method.md @@ -0,0 +1,59 @@ +--- +id: javascript-classes-method +title: メソッド、ゲッター、セッター +level: 2 +--- + +## メソッド、ゲッター、セッター + +クラス構文の中では、プロトタイプへのメソッド定義を簡潔に書くことができます。`function` キーワードは不要です。また、プロパティへのアクセスを制御するゲッター (`get`) とセッター (`set`) も直感的に記述できます。 + +```js:rectangle.js +class Rectangle { + constructor(width, height) { + this.width = width; + this.height = height; + } + + // 通常のメソッド(プロトタイプメソッドになります) + calcArea() { + return this.width * this.height; + } + + // ゲッター: プロパティのようにアクセス可能 + get description() { + return `${this.width} x ${this.height} Rectangle`; + } + + // セッター: 値の検証などに利用 + set width(value) { + if (value <= 0) { + console.log("幅は0より大きくある必要があります"); + return; + } + this._width = value; + } + + get width() { + return this._width; + } +} + +const rect = new Rectangle(10, 20); + +console.log(rect.calcArea()); // メソッド呼び出し +console.log(rect.description); // ゲッター呼び出し(()は不要) + +rect.width = -5; // セッターによるバリデーション +rect.width = 15; +console.log(rect.calcArea()); +``` + +```js-exec:rectangle.js +200 +10 x 20 Rectangle +幅は0より大きくある必要があります +300 +``` + +> **Note:** セッター内で `this.width = value` とすると無限再帰になるため、慣習的に内部プロパティには `_`(アンダースコア)を付けることがよくありましたが、現在は後述するプライベートフィールド(`#`)を使うのがモダンな方法です。 diff --git a/public/docs/javascript/6-classes/3-0-extends-super.md b/public/docs/javascript/6-classes/3-0-extends-super.md new file mode 100644 index 0000000..1271660 --- /dev/null +++ b/public/docs/javascript/6-classes/3-0-extends-super.md @@ -0,0 +1,49 @@ +--- +id: javascript-classes-extends-super +title: 継承 (extends と super) +level: 2 +--- + +## 継承 (`extends` と `super`) + +他の言語同様、`extends` キーワードを使用して既存のクラスを継承できます。親クラスのコンストラクタやメソッドには `super` を使ってアクセスします。 + +ここで重要なルールが1つあります。**子クラスの `constructor` 内では、`this` を使用する前に必ず `super()` を呼び出す必要があります。** + +```js:inheritance.js +class Animal { + constructor(name) { + this.name = name; + } + + speak() { + return `${this.name} makes a noise.`; + } +} + +class Dog extends Animal { + constructor(name, breed) { + // thisを使う前に親のコンストラクタを呼ぶ必須ルール + super(name); + this.breed = breed; + } + + // メソッドのオーバーライド + speak() { + // 親クラスのメソッド呼び出し + const parentSound = super.speak(); + return `${parentSound} But specifically, ${this.name} barks!`; + } +} + +const d = new Dog("Pochi", "Shiba"); +console.log(d.speak()); +console.log(d instanceof Dog); // true +console.log(d instanceof Animal); // true +``` + +```js-exec:inheritance.js +Pochi makes a noise. But specifically, Pochi barks! +true +true +``` diff --git a/public/docs/javascript/6-classes/4-0-static-private.md b/public/docs/javascript/6-classes/4-0-static-private.md new file mode 100644 index 0000000..ce652a0 --- /dev/null +++ b/public/docs/javascript/6-classes/4-0-static-private.md @@ -0,0 +1,7 @@ +--- +id: javascript-classes-static-private +title: '静的メソッド (static) とプライベートフィールド (#)' +level: 2 +--- + +## 静的メソッド (static) とプライベートフィールド (\#) diff --git a/public/docs/javascript/6-classes/4-1-static.md b/public/docs/javascript/6-classes/4-1-static.md new file mode 100644 index 0000000..49aad3d --- /dev/null +++ b/public/docs/javascript/6-classes/4-1-static.md @@ -0,0 +1,46 @@ +--- +id: javascript-classes-static +title: 静的メソッド (static) +level: 3 +--- + +### 静的メソッド (static) + +インスタンスではなく、クラス自体に紐付くメソッドです。ユーティリティ関数やファクトリーメソッドによく使われます。 + +```js:private_static_1.js +class BankAccount { + constructor(initialBalance) { + this.balance = initialBalance; + } + + deposit(amount) { + if (amount > 0) { + this.balance += amount; + console.log(`Deposited: ${amount}`); + } + } + + getBalance() { + return this.balance; + } + + // 静的メソッド + static createZeroAccount() { + return new BankAccount(0); + } +} + +const account = BankAccount.createZeroAccount(); +account.deposit(1000); + +console.log(`Current Balance: ${account.getBalance()}`); + +console.log(account.balance); +``` + +```js-exec:private_static_1.js +Deposited: 1000 +Current Balance: 1000 +1000 +``` diff --git a/public/docs/javascript/6-classes/4-2-private.md b/public/docs/javascript/6-classes/4-2-private.md new file mode 100644 index 0000000..6e5a431 --- /dev/null +++ b/public/docs/javascript/6-classes/4-2-private.md @@ -0,0 +1,53 @@ +--- +id: javascript-classes-private +title: 'プライベートフィールド (#)' +level: 3 +--- + +### プライベートフィールド (\#) + +長らくJavaScriptには「真のプライベートプロパティ」が存在せず、`_variable` のような命名規則に頼っていました。しかし、ES2019以降、`#` をプレフィックスにすることで、**クラス外から完全にアクセス不可能なフィールド**を定義できるようになりました。 + +```js:private_static_2.js +class BankAccount { + // プライベートフィールドの宣言 + #balance; + + constructor(initialBalance) { + this.#balance = initialBalance; + } + + deposit(amount) { + if (amount > 0) { + this.#balance += amount; + console.log(`Deposited: ${amount}`); + } + } + + getBalance() { + return this.#balance; + } + + // 静的メソッド + static createZeroAccount() { + return new BankAccount(0); + } +} + +const account = BankAccount.createZeroAccount(); +account.deposit(1000); + +console.log(`Current Balance: ${account.getBalance()}`); + +// 外部からのアクセスを試みると、 Syntax Error になる +// console.log(account.#balance); + +// 従来のプロパティアクセスのように見えても... +console.log(account.balance); // undefined +``` + +```js-exec:private_static_2.js +Deposited: 1000 +Current Balance: 1000 +undefined +``` diff --git a/public/docs/javascript/6-classes/5-0-summary.md b/public/docs/javascript/6-classes/5-0-summary.md new file mode 100644 index 0000000..39b6fd5 --- /dev/null +++ b/public/docs/javascript/6-classes/5-0-summary.md @@ -0,0 +1,14 @@ +--- +id: javascript-classes-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +1. **`class` 構文** はプロトタイプ継承の糖衣構文であり、`constructor` で初期化を行います。 +2. **メソッド定義** は `function` キーワードが不要で、`get` / `set` でアクセサを定義できます。 +3. **継承** は `extends` を使い、子クラスのコンストラクタ内では必ず `this` に触れる前に `super()` を呼ぶ必要があります。 +4. **`static`** で静的メソッドを、**`#`** プレフィックスでハードプライベートフィールド(外部からアクセス不可)を定義できます。 + +クラス構文を使うことで、コードの構造がより明確になり、他の言語の経験者にとっても読みやすいコードになります。しかし、裏側ではプロトタイプチェーンが動いていることを忘れないでください。 diff --git a/public/docs/javascript/6-classes/5-1-practice1.md b/public/docs/javascript/6-classes/5-1-practice1.md new file mode 100644 index 0000000..1f9aa6e --- /dev/null +++ b/public/docs/javascript/6-classes/5-1-practice1.md @@ -0,0 +1,19 @@ +--- +id: javascript-classes-practice1 +title: '練習問題 1: シンプルなRPGキャラクター' +level: 3 +--- + +### 練習問題 1: シンプルなRPGキャラクター + +以下の仕様を満たす `Character` クラスを作成してください。 + + * `name` (名前) と `hp` (体力) をコンストラクタで受け取る。 + * `attack(target)` メソッドを持つ。実行すると `target` の `hp` を 10 減らし、コンソールに攻撃メッセージを表示する。 + * `hp` はプライベートフィールド (`#hp`) として管理し、0未満にならないようにする。現在のHPを取得するゲッター `hp` を用意する。 + +```js:practice7_1.js +``` + +```js-exec:practice7_1.js +``` diff --git a/public/docs/javascript/6-classes/5-2-practice2.md b/public/docs/javascript/6-classes/5-2-practice2.md new file mode 100644 index 0000000..a5e7b6f --- /dev/null +++ b/public/docs/javascript/6-classes/5-2-practice2.md @@ -0,0 +1,19 @@ +--- +id: javascript-classes-practice2 +title: '練習問題 2: 図形の継承' +level: 3 +--- + +### 練習問題 2: 図形の継承 + +以下の仕様を満たすクラスを作成してください。 + + * 親クラス `Shape`: コンストラクタで `color` を受け取る。`info()` メソッドを持ち、「色: [color]」を返す。 + * 子クラス `Circle`: `Shape` を継承。コンストラクタで `color` と `radius` (半径) を受け取る。`info()` メソッドをオーバーライドし、「[親のinfo], 半径: [radius]」を返す。 + * それぞれのインスタンスを作成し、`info()` の結果を表示する。 + +```js:practice7_2.js +``` + +```js-exec:practice7_2.js +``` diff --git a/public/docs/javascript/7-arrays/-intro.md b/public/docs/javascript/7-arrays/-intro.md new file mode 100644 index 0000000..87bacfa --- /dev/null +++ b/public/docs/javascript/7-arrays/-intro.md @@ -0,0 +1,3 @@ +他の言語経験者にとって、JavaScriptの配列は「動的配列」や「リスト」、「ベクター」に近い存在です。サイズは可変であり、異なるデータ型を混在させることも可能です(通常は同じ型で統一しますが)。 + +本章では、基本的な操作から、モダンなJavaScript開発において必須となる「宣言的なデータ処理」(`map`, `filter`, `reduce`など)に焦点を当てます。従来の`for`ループよりもこれらのメソッドが好まれる理由と使い方を習得しましょう。 diff --git a/public/docs/javascript/7-arrays/1-0-array-literal.md b/public/docs/javascript/7-arrays/1-0-array-literal.md new file mode 100644 index 0000000..afe7383 --- /dev/null +++ b/public/docs/javascript/7-arrays/1-0-array-literal.md @@ -0,0 +1,36 @@ +--- +id: javascript-arrays-basic +title: 配列リテラルと基本的な操作 +level: 2 +--- + +## 配列リテラルと基本的な操作 + +JavaScriptの配列は`Array`オブジェクトですが、通常はリテラル `[]` を使用して生成します。 +基本的な操作として、スタック操作(`push`, `pop`)やキュー操作に近いこと(`shift`, `unshift`)、そして万能な要素操作メソッド`splice`があります。 + +```js-repl +> const fruits = ['Apple', 'Banana']; +undefined +> // 末尾に追加 (push) +> fruits.push('Orange'); +3 +> fruits +[ 'Apple', 'Banana', 'Orange' ] + +> // 末尾から削除 (pop) +> const last = fruits.pop(); +undefined +> last +'Orange' + +> // 先頭に追加 (unshift) +> fruits.unshift('Grape'); +3 +> fruits +[ 'Grape', 'Apple', 'Banana' ] + +> // インデックスによるアクセス +> fruits[1] +'Apple' +``` diff --git a/public/docs/javascript/7-arrays/1-2-array-splice.md b/public/docs/javascript/7-arrays/1-2-array-splice.md new file mode 100644 index 0000000..e67745b --- /dev/null +++ b/public/docs/javascript/7-arrays/1-2-array-splice.md @@ -0,0 +1,19 @@ +--- +id: javascript-arrays-array-splice +title: '破壊的な操作: splice' +level: 3 +--- + +### 破壊的な操作: `splice` + +`splice`は要素の削除、置換、挿入をすべて行える強力なメソッドですが、**元の配列を変更(破壊)する**点に注意が必要です。 + +```js-repl +> const numbers = [1, 2, 3, 4, 5]; +undefined +> // インデックス1から、2つの要素を削除し、そこに99, 100を挿入 +> numbers.splice(1, 2, 99, 100); +[ 2, 3 ] +> numbers +[ 1, 99, 100, 4, 5 ] +``` diff --git a/public/docs/javascript/7-arrays/2-0-spread-destructure-basic.md b/public/docs/javascript/7-arrays/2-0-spread-destructure-basic.md new file mode 100644 index 0000000..3188209 --- /dev/null +++ b/public/docs/javascript/7-arrays/2-0-spread-destructure-basic.md @@ -0,0 +1,9 @@ +--- +id: javascript-arrays-spread-destructure-basic +title: スプレッド構文 (...) とデストラクチャリング(分割代入) +level: 2 +--- + +## スプレッド構文 (...) とデストラクチャリング(分割代入) + +モダンJavaScript(ES2015+)では、配列の操作をより簡潔に記述するための構文が導入されました。これらはReactやVueなどのフレームワークでも多用されます。 diff --git a/public/docs/javascript/7-arrays/2-1-spread.md b/public/docs/javascript/7-arrays/2-1-spread.md new file mode 100644 index 0000000..0f4ef1d --- /dev/null +++ b/public/docs/javascript/7-arrays/2-1-spread.md @@ -0,0 +1,27 @@ +--- +id: javascript-arrays-spread +title: スプレッド構文 (...) +level: 3 +--- + +### スプレッド構文 (...) + +配列を展開する構文です。配列の結合や、**浅いコピー(Shallow Copy)**の作成によく使われます。 + +```js-repl +> const part1 = [1, 2]; +undefined +> const part2 = [3, 4]; +undefined +> // 配列の結合(新しい配列を作成) +> const combined = [...part1, ...part2]; +undefined +> combined +[ 1, 2, 3, 4 ] + +> // 配列のコピー(新しい参照を作成) +> const copy = [...part1]; +undefined +> copy === part1 +false +``` diff --git a/public/docs/javascript/7-arrays/2-2-destructuring.md b/public/docs/javascript/7-arrays/2-2-destructuring.md new file mode 100644 index 0000000..190de54 --- /dev/null +++ b/public/docs/javascript/7-arrays/2-2-destructuring.md @@ -0,0 +1,35 @@ +--- +id: javascript-arrays-destructuring +title: デストラクチャリング(分割代入) +level: 3 +--- + +### デストラクチャリング(分割代入) + +配列から要素を取り出して変数に代入する操作を簡潔に書くことができます。 + +```js-repl +> const users = ['Alice', 'Bob', 'Charlie']; +undefined +> // 1つ目と2つ目の要素を変数に代入 +> const [first, second] = users; +undefined +> first +'Alice' +> second +'Bob' + +> // 3つ目だけを取り出す(最初の2つはスキップ) +> const [, , third] = users; +undefined +> third +'Charlie' + +> // 変数の値を入れ替える(スワップ)テクニック +> let a = 1; +> let b = 2; +> [a, b] = [b, a]; +[ 2, 1 ] +> a +2 +``` diff --git a/public/docs/javascript/7-arrays/3-0-higher-order-func.md b/public/docs/javascript/7-arrays/3-0-higher-order-func.md new file mode 100644 index 0000000..ba05802 --- /dev/null +++ b/public/docs/javascript/7-arrays/3-0-higher-order-func.md @@ -0,0 +1,71 @@ +--- +id: javascript-arrays-higher-order-func +title: 高階関数によるイテレーション +level: 2 +--- + +## 高階関数によるイテレーション + +JavaScriptでは、`for`文や`while`文を書く頻度は減っています。代わりに、配列のメソッドとして提供される**高階関数**(関数を引数に取る関数)を使用して、処理の意図(変換、抽出、集約など)を明確にします。 + +主なメソッドは以下の通りです。 + + * **`forEach`**: 単なる反復処理(戻り値なし)。副作用(ログ出力やDB保存など)を起こすために使う。 + * **`map`**: 全要素を変換し、**新しい配列**を返す。 + * **`filter`**: 条件に一致する要素のみを抽出し、**新しい配列**を返す。 + * **`reduce`**: 要素を一つずつ処理して、**単一の値**(合計、オブジェクトなど)に集約する。 + +以下は、これらのメソッドを使って商品リストを処理するスクリプトです。 + +```js:shopping_cart.js +const cart = [ + { id: 1, name: 'Laptop', price: 1000, category: 'Electronics' }, + { id: 2, name: 'Mouse', price: 25, category: 'Electronics' }, + { id: 3, name: 'Coffee', price: 5, category: 'Food' }, + { id: 4, name: 'Keyboard', price: 100, category: 'Electronics' }, +]; + +console.log('--- 1. map: 商品名のリストを作成 ---'); +const itemNames = cart.map(item => item.name); +console.log(itemNames); + +console.log('\n--- 2. filter: 電子機器(Electronics)のみ抽出 ---'); +const electronics = cart.filter(item => item.category === 'Electronics'); +console.log(electronics); + +console.log('\n--- 3. reduce: 合計金額を計算 ---'); +// 第2引数の 0 はアキュムレータ(sum)の初期値 +const totalPrice = cart.reduce((sum, item) => sum + item.price, 0); +console.log(`Total: $${totalPrice}`); + +console.log('\n--- 4. メソッドチェーン(組み合わせ) ---'); +// 電子機器の価格のみを抽出して合計する +const electronicsTotal = cart + .filter(item => item.category === 'Electronics') + .map(item => item.price) + .reduce((acc, price) => acc + price, 0); + +console.log(`Electronics Total: $${electronicsTotal}`); +``` + +実行結果: + +```js-exec:shopping_cart.js +--- 1. map: 商品名のリストを作成 --- +[ 'Laptop', 'Mouse', 'Coffee', 'Keyboard' ] + +--- 2. filter: 電子機器(Electronics)のみ抽出 --- +[ + { id: 1, name: 'Laptop', price: 1000, category: 'Electronics' }, + { id: 2, name: 'Mouse', price: 25, category: 'Electronics' }, + { id: 4, name: 'Keyboard', price: 100, category: 'Electronics' } +] + +--- 3. reduce: 合計金額を計算 --- +Total: $1130 + +--- 4. メソッドチェーン(組み合わせ) --- +Electronics Total: $1125 +``` + +> **Note:** `map`や`filter`は元の配列を変更せず(イミュータブル)、新しい配列を返します。これにより、予期せぬ副作用を防ぐことができます。 diff --git a/public/docs/javascript/7-arrays/4-0-other-method.md b/public/docs/javascript/7-arrays/4-0-other-method.md new file mode 100644 index 0000000..0c7e66a --- /dev/null +++ b/public/docs/javascript/7-arrays/4-0-other-method.md @@ -0,0 +1,37 @@ +--- +id: javascript-arrays-other-method +title: その他の便利なメソッド:find, some, every +level: 2 +--- + +## その他の便利なメソッド:`find`, `some`, `every` + +特定の要素を探したり、条件チェックを行ったりする場合に特化したメソッドです。これらもコールバック関数を受け取ります。 + + * **`find`**: 最初に見つかった要素自体を返す(見つからなければ `undefined`)。 + * **`findIndex`**: 最初に見つかった要素のインデックスを返す(見つからなければ `-1`)。 + * **`some`**: 条件を満たす要素が**一つでもあれば** `true` を返す。 + * **`every`**: **すべての要素**が条件を満たせば `true` を返す。 + +```js-repl +> const scores = [85, 92, 45, 78, 90]; +undefined + +> // 最初の合格者(80点以上)を探す +> const starStudent = scores.find(score => score >= 90); +undefined +> starStudent +92 + +> // 赤点(50点未満)があるか? (some) +> const hasFailure = scores.some(score => score < 50); +undefined +> hasFailure +true + +> // 全員が合格(40点以上)か? (every) +> const allPassed = scores.every(score => score >= 40); +undefined +> allPassed +true +``` diff --git a/public/docs/javascript/7-arrays/5-0-summary.md b/public/docs/javascript/7-arrays/5-0-summary.md new file mode 100644 index 0000000..43e0331 --- /dev/null +++ b/public/docs/javascript/7-arrays/5-0-summary.md @@ -0,0 +1,13 @@ +--- +id: javascript-arrays-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * 配列は動的で、`push`/`pop`などのメソッドで伸縮可能です。 + * `splice`は配列を直接変更(破壊)するため、使用には注意が必要です。 + * スプレッド構文 `...` と分割代入を使うと、配列のコピー、結合、要素の抽出が宣言的に記述できます。 + * ループ処理には `for` 文よりも高階関数(`map`, `filter`, `reduce`)を使用することが推奨されます。これらは処理の意図を明確にし、メソッドチェーンによる可読性の向上に寄与します。 + * 検索や検証には `find`, `some`, `every` を活用しましょう。 diff --git a/public/docs/javascript/7-arrays/5-1-practice1.md b/public/docs/javascript/7-arrays/5-1-practice1.md new file mode 100644 index 0000000..5b31ec5 --- /dev/null +++ b/public/docs/javascript/7-arrays/5-1-practice1.md @@ -0,0 +1,22 @@ +--- +id: javascript-arrays-practice1 +title: '練習問題 1: データの加工' +level: 3 +--- + +### 練習問題 1: データの加工 + +以下の数値配列があります。 +`const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];` + +この配列に対して、以下の処理を行うコードを書いてください(メソッドチェーンを使用しても構いません)。 + +1. 偶数のみを取り出す。 +2. 取り出した偶数をそれぞれ2乗する。 +3. 結果の配列をコンソールに表示する。 + +```js:practice8_1.js +``` + +```js-exec:practice8_1.js +``` diff --git a/public/docs/javascript/7-arrays/5-2-practice2.md b/public/docs/javascript/7-arrays/5-2-practice2.md new file mode 100644 index 0000000..63f1a08 --- /dev/null +++ b/public/docs/javascript/7-arrays/5-2-practice2.md @@ -0,0 +1,23 @@ +--- +id: javascript-arrays-practice2 +title: '練習問題 2: 集計処理' +level: 3 +--- + +### 練習問題 2: 集計処理 + +以下のユーザーリストから「アクティブ(`active: true`)なユーザーの年齢の平均値」を計算して表示してください。 +(ヒント: まずアクティブなユーザーを絞り込み、次に年齢の合計と人数を使って平均を算出します) + +```js:practice8_2.js +const users = [ + { name: 'Alice', age: 25, active: true }, + { name: 'Bob', age: 30, active: false }, + { name: 'Charlie', age: 35, active: true }, + { name: 'Dave', age: 40, active: false } +]; + +``` + +```js-exec:practice8_2.js +``` diff --git a/public/docs/javascript/8-promise/-intro.md b/public/docs/javascript/8-promise/-intro.md new file mode 100644 index 0000000..16f3088 --- /dev/null +++ b/public/docs/javascript/8-promise/-intro.md @@ -0,0 +1,5 @@ +JavaScriptは基本的にシングルスレッドで動作します。つまり、一度に一つの処理しか実行できません。しかし、ネットワークリクエストやタイマーなどの重い処理を行っている間、ブラウザがフリーズしたりサーバーが応答しなくなったりしては困ります。 + +そこでJavaScriptは、重い処理をバックグラウンド(Web APIsやNode.jsのC++レイヤー)に任せ、完了通知を受け取ることで並行処理のような動きを実現しています。 + +本章では、JavaScriptの非同期処理の基盤となるメカニズムと、それを現代的に扱うための標準APIである **Promise** について解説します。 diff --git a/public/docs/javascript/8-promise/1-0-sync-vs-async.md b/public/docs/javascript/8-promise/1-0-sync-vs-async.md new file mode 100644 index 0000000..a369dbf --- /dev/null +++ b/public/docs/javascript/8-promise/1-0-sync-vs-async.md @@ -0,0 +1,33 @@ +--- +id: javascript-promise-sync-vs-async +title: 同期処理 vs 非同期処理 +level: 2 +--- + +## 同期処理 vs 非同期処理 + +まず、挙動の違いを確認しましょう。 + + * **同期処理 (Synchronous):** コードが上から下へ順番に実行されます。前の処理が終わるまで次の処理は待たされます(ブロッキング)。 + * **非同期処理 (Asynchronous):** 処理の完了を待たずに、即座に次のコードへ進みます(ノンブロッキング)。処理結果は後でコールバックなどを通じて受け取ります。 + +以下のコードは、`setTimeout`(非同期API)を使用した例です。他言語の経験者であれば、「Start」→「1秒待機」→「Timer」→「End」と予想するかもしれませんが、JavaScriptでは異なります。 + +```js:async_demo.js +console.log('1. Start'); + +// 1000ミリ秒後にコールバックを実行する非同期関数 +setTimeout(() => { + console.log('2. Timer fired'); +}, 1000); + +console.log('3. End'); +``` + +```js-exec:async_demo.js +1. Start +3. End +2. Timer fired +``` + +`setTimeout` は「タイマーをセットする」という命令だけを出し、即座に制御を返します。そのため、タイマーの発火を待たずに `3. End` が出力されます。 diff --git a/public/docs/javascript/8-promise/2-0-event-callback.md b/public/docs/javascript/8-promise/2-0-event-callback.md new file mode 100644 index 0000000..74ce313 --- /dev/null +++ b/public/docs/javascript/8-promise/2-0-event-callback.md @@ -0,0 +1,24 @@ +--- +id: javascript-promise-event-callback +title: イベントループとコールバックキューの仕組み +level: 2 +--- + +## イベントループとコールバックキューの仕組み + +なぜシングルスレッドで非同期処理が可能なのか、その裏側にあるのが **イベントループ (Event Loop)** という仕組みです。 + +JavaScriptのランタイムは主に以下の要素で構成されています: + +1. **コールスタック (Call Stack):** 現在実行中の関数が積まれる場所。LIFO(後入れ先出し)。 +2. **Web APIs / Node APIs:** ブラウザやOSが提供する機能(タイマー、Fetch、DOMイベントなど)。非同期処理はここで実行されます。 +3. **コールバックキュー (Callback Queue):** 非同期処理が完了した後、実行待ちのコールバック関数が並ぶ列。 +4. **イベントループ (Event Loop):** コールスタックとキューを監視する仕組み。 + +**処理の流れ:** + +1. `setTimeout` がコールスタックで実行されると、ブラウザのタイマーAPIに処理を依頼し、スタックから消えます。 +2. 指定時間が経過すると、タイマーAPIはコールバック関数を **コールバックキュー** に入れます。 +3. **イベントループ** は、「コールスタックが空(メインの処理が完了)」かつ「キューにタスクがある」場合、キューからタスクを取り出してコールスタックへ移動させます。 + +この仕組みにより、メインスレッドをブロックすることなく非同期処理を実現しています。 diff --git a/public/docs/javascript/8-promise/3-0-callback-hell.md b/public/docs/javascript/8-promise/3-0-callback-hell.md new file mode 100644 index 0000000..8f0aa61 --- /dev/null +++ b/public/docs/javascript/8-promise/3-0-callback-hell.md @@ -0,0 +1,42 @@ +--- +id: javascript-promise-callback-hell +title: コールバック地獄の問題点 +level: 2 +--- + +## コールバック地獄の問題点 + +Promiseが登場する以前(ES5時代まで)は、非同期処理の順序制御を行うために、コールバック関数を入れ子にする手法が一般的でした。 + +例えば、「処理Aが終わったら処理B、その後に処理C...」というコードを書こうとすると、以下のようにネストが深くなります。 + +```js:callback_hell.js +function delay(ms, callback) { + setTimeout(callback, ms); +} + +console.log('Start'); + +delay(1000, () => { + console.log('Step 1 finished'); + + delay(1000, () => { + console.log('Step 2 finished'); + + delay(1000, () => { + console.log('Step 3 finished'); + console.log('End'); + }); + }); +}); +``` + +```js-exec:callback_hell.js +Start +Step 1 finished +Step 2 finished +Step 3 finished +End +``` + +これはいわゆる **「コールバック地獄 (Callback Hell)」** と呼ばれる状態で、可読性が低く、エラーハンドリングも困難です。これを解決するために導入されたのが **Promise** です。 diff --git a/public/docs/javascript/8-promise/4-0-promise-basic.md b/public/docs/javascript/8-promise/4-0-promise-basic.md new file mode 100644 index 0000000..b961c0c --- /dev/null +++ b/public/docs/javascript/8-promise/4-0-promise-basic.md @@ -0,0 +1,17 @@ +--- +id: javascript-promise-basic +title: Promiseの概念 +level: 2 +--- + +## Promiseの概念 + +**Promise** は、非同期処理の「最終的な完了(または失敗)」とその「結果の値」を表すオブジェクトです。未来のある時点で値が返ってくる「約束手形」のようなものと考えてください。 + +Promiseオブジェクトは以下の3つの状態のいずれかを持ちます。 + +1. **Pending (待機中):** 初期状態。処理はまだ完了していない。 +2. **Fulfilled (履行):** 処理が成功し、値を持っている状態。(`resolve` された) +3. **Rejected (拒否):** 処理が失敗し、エラー理由を持っている状態。(`reject` された) + +Promiseの状態は一度 Pending から Fulfilled または Rejected に変化すると、二度と変化しません(Immutable)。 diff --git a/public/docs/javascript/8-promise/5-0-promise-usage.md b/public/docs/javascript/8-promise/5-0-promise-usage.md new file mode 100644 index 0000000..5e4622d --- /dev/null +++ b/public/docs/javascript/8-promise/5-0-promise-usage.md @@ -0,0 +1,7 @@ +--- +id: javascript-promise-usage +title: Promiseの使い方 +level: 2 +--- + +## Promiseの使い方 diff --git a/public/docs/javascript/8-promise/5-1-promise-create.md b/public/docs/javascript/8-promise/5-1-promise-create.md new file mode 100644 index 0000000..2d7b315 --- /dev/null +++ b/public/docs/javascript/8-promise/5-1-promise-create.md @@ -0,0 +1,24 @@ +--- +id: javascript-promise-create +title: Promiseの作成 +level: 3 +--- + +### Promiseの作成 + +`new Promise` コンストラクタを使用します。引数には `(resolve, reject)` を受け取る関数(Executor)を渡します。 + +```js-repl +> const myPromise = new Promise((resolve, reject) => { +... // ここで非同期処理を行う +... const success = true; +... if (success) { +... resolve("OK!"); // 成功時 +... } else { +... reject(new Error("Failed")); // 失敗時 +... } +... }); +undefined +> myPromise +Promise { 'OK!' } +``` diff --git a/public/docs/javascript/8-promise/5-2-promise-method.md b/public/docs/javascript/8-promise/5-2-promise-method.md new file mode 100644 index 0000000..810d0ae --- /dev/null +++ b/public/docs/javascript/8-promise/5-2-promise-method.md @@ -0,0 +1,60 @@ +--- +id: javascript-promise-method +title: .then(), .catch(), .finally() +level: 3 +--- + +### `.then()`, `.catch()`, `.finally()` + +Promiseオブジェクトの結果を受け取るには、以下のメソッドを使用します。 + + * **`.then(onFulfilled)`**: PromiseがFulfilledになった時に実行されます。 + * **`.catch(onRejected)`**: PromiseがRejectedになった時に実行されます。 + * **`.finally(onFinally)`**: 成功・失敗に関わらず、処理終了時に実行されます。 + +先ほどのコールバック地獄の例を、Promiseを使って書き直してみましょう。 + +```js:promise_chain.js +// Promiseを返す関数を作成 +function delay(ms) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(`Waited ${ms}ms`); + }, ms); + }); +} + +console.log('Start'); + +delay(1000) + .then((message) => { + console.log('Step 1:', message); + // 次のPromiseを返すことでチェーンをつなぐ + return delay(1000); + }) + .then((message) => { + console.log('Step 2:', message); + return delay(1000); + }) + .then((message) => { + console.log('Step 3:', message); + console.log('End'); + }) + .catch((error) => { + // チェーンのどこかでエラーが起きればここに飛ぶ + console.error('Error:', error); + }); +``` + +```js-exec:promise_chain.js +Start +Step 1: Waited 1000ms +Step 2: Waited 1000ms +Step 3: Waited 1000ms +End +``` + +**重要なポイント:** + +1. `.then()` の中で新しい Promise を返すと、次の `.then()` はその新しい Promise の完了を待ちます。これにより、非同期処理を **フラットな連鎖** として記述できます。 +2. エラー処理は最後の `.catch()` に集約できます。`try-catch` ブロックに近い感覚で扱えるようになります。 diff --git a/public/docs/javascript/8-promise/6-0-summary.md b/public/docs/javascript/8-promise/6-0-summary.md new file mode 100644 index 0000000..970184a --- /dev/null +++ b/public/docs/javascript/8-promise/6-0-summary.md @@ -0,0 +1,15 @@ +--- +id: javascript-promise-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * JavaScriptはシングルスレッドで動作し、**イベントループ** という仕組みを使って非同期処理を管理しています。 + * 非同期処理の完了を待つために、昔はコールバック関数が多用されていましたが、ネストが深くなる問題がありました。 + * **Promise** は非同期処理の状態(Pending, Fulfilled, Rejected)を管理するオブジェクトです。 + * `.then()` をチェーンさせることで、非同期処理を直列に、読みやすく記述できます。 + * エラーハンドリングは `.catch()` で一括して行えます。 + +次章では、このPromiseをさらに同期処理のように書ける構文糖衣 **async/await** について学びます。 diff --git a/public/docs/javascript/8-promise/7-1-practice1.md b/public/docs/javascript/8-promise/7-1-practice1.md new file mode 100644 index 0000000..b9c4796 --- /dev/null +++ b/public/docs/javascript/8-promise/7-1-practice1.md @@ -0,0 +1,16 @@ +--- +id: javascript-promise-practice1 +title: '練習問題1: ランダムな成功/失敗' +level: 3 +--- + +### 練習問題1: ランダムな成功/失敗 + +`Math.random()` を使い、50%の確率で成功(Resolve)、50%の確率で失敗(Reject)するPromiseを返す関数 `coinToss` を作成してください。 +それを使用し、成功時は "Win\!"、失敗時は "Lose..." とコンソールに表示するコードを書いてください。 + +```js:practice9_1.js +``` + +```js-exec:practice9_1.js +``` diff --git a/public/docs/javascript/8-promise/7-2-practice2.md b/public/docs/javascript/8-promise/7-2-practice2.md new file mode 100644 index 0000000..4abcd1e --- /dev/null +++ b/public/docs/javascript/8-promise/7-2-practice2.md @@ -0,0 +1,19 @@ +--- +id: javascript-promise-practice2 +title: '練習問題2: 擬似的なデータ取得フロー' +level: 3 +--- + +### 練習問題2: 擬似的なデータ取得フロー + +以下の仕様を満たすコードを作成してください。 + +1. 関数 `fetchUser(userId)`: 1秒後に `{ id: userId, name: "User" + userId }` というオブジェクトでresolveする。 +2. 関数 `fetchPosts(userName)`: 1秒後に `["Post 1 by " + userName, "Post 2 by " + userName]` という配列でresolveする。 +3. これらをPromiseチェーンで繋ぎ、ユーザーID `1` でユーザーを取得した後、その名前を使って投稿を取得し、最終的に投稿リストをコンソールに表示してください。 + +```js:practice9_2.js +``` + +```js-exec:practice9_2.js +``` diff --git a/public/docs/javascript/9-async-await/-intro.md b/public/docs/javascript/9-async-await/-intro.md new file mode 100644 index 0000000..d2086d9 --- /dev/null +++ b/public/docs/javascript/9-async-await/-intro.md @@ -0,0 +1,3 @@ +前回(第9章)では、JavaScriptの非同期処理の要である `Promise` について学びました。しかし、`.then()` チェーンが長く続くと、コードの可読性が下がる(いわゆる「コールバック地獄」に近い状態になる)ことがあります。 + +第10章では、この課題を解決するために導入された **Async/Await** 構文と、現代的なHTTP通信の標準である **Fetch API** について解説します。他の言語で同期的なコード(ブロッキング処理)に慣れ親しんだ方にとって、Async/Await は非常に直感的で扱いやすい機能です。 diff --git a/public/docs/javascript/9-async-await/1-0-async-await.md b/public/docs/javascript/9-async-await/1-0-async-await.md new file mode 100644 index 0000000..ea5a61c --- /dev/null +++ b/public/docs/javascript/9-async-await/1-0-async-await.md @@ -0,0 +1,9 @@ +--- +id: javascript-async-await-async-await +title: Async/Await 構文 +level: 2 +--- + +## Async/Await 構文 + +`async` と `await` は、ES2017で導入された `Promise` の**シンタックスシュガー(糖衣構文)**です。これを使うことで、非同期処理をあたかも「同期処理」のように上から下へと流れるコードとして記述できます。 diff --git a/public/docs/javascript/9-async-await/1-1-async.md b/public/docs/javascript/9-async-await/1-1-async.md new file mode 100644 index 0000000..1850f9f --- /dev/null +++ b/public/docs/javascript/9-async-await/1-1-async.md @@ -0,0 +1,22 @@ +--- +id: javascript-async-await-async +title: async 関数 +level: 3 +--- + +### `async` 関数 + +関数宣言の前に `async` キーワードを付けると、その関数は自動的に **Promiseを返す** ようになります。値を `return` した場合、それは `Promise.resolve(値)` と同じ意味になります。 + +```js-repl +> async function getMessage() { return "Hello, Async!"; } +undefined +> // async関数は常にPromiseを返す +> getMessage() +Promise { 'Hello, Async!' } + +> // 通常のPromiseと同じくthenで値を取り出せる +> getMessage().then(v => console.log(v)) +Promise { } +Hello, Async! +``` diff --git a/public/docs/javascript/9-async-await/1-2-await.md b/public/docs/javascript/9-async-await/1-2-await.md new file mode 100644 index 0000000..e0fad2d --- /dev/null +++ b/public/docs/javascript/9-async-await/1-2-await.md @@ -0,0 +1,28 @@ +--- +id: javascript-async-await-await +title: await 式 +level: 3 +--- + +### `await` 式 + +`async` 関数の内部(またはモジュールのトップレベル)でのみ使用できるキーワードです。 +`await` は、右側の Promise が **Settled(解決または拒否)されるまで関数の実行を一時停止** します。Promiseが解決されると、その結果の値を返して実行を再開します。 + +これは、C\# の `async/await` や Python の `asyncio` に慣れている方にはおなじみの挙動でしょう。 + +```js-repl +> function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } +undefined +> async function run() { +... console.log("Start"); +... await delay(1000); // 1秒待機(ここで実行が一時停止) +... console.log("End"); +... } +undefined +> run() +Promise { } +// (1秒後に表示) +Start +End +``` diff --git a/public/docs/javascript/9-async-await/2-0-try-catch.md b/public/docs/javascript/9-async-await/2-0-try-catch.md new file mode 100644 index 0000000..538c40c --- /dev/null +++ b/public/docs/javascript/9-async-await/2-0-try-catch.md @@ -0,0 +1,49 @@ +--- +id: javascript-async-await-try-catch +title: try...catch によるエラーハンドリング +level: 2 +--- + +## `try...catch` によるエラーハンドリング + +生の `Promise` では `.catch()` メソッドを使ってエラーを処理しましたが、Async/Await では、他の言語と同様に標準的な `try...catch` 構文を使用できます。これにより、同期エラーと非同期エラーを同じ構文で扱えるようになります。 + +```js:async_try_catch.js +// ランダムに成功・失敗する非同期関数 +function randomRequest() { + return new Promise((resolve, reject) => { + setTimeout(() => { + const success = Math.random() > 0.5; + if (success) { + resolve("Success: データ取得完了"); + } else { + reject(new Error("Failure: サーバーエラー")); + } + }, 500); + }); +} + +async function main() { + console.log("処理開始..."); + try { + // awaitしているPromiseがrejectされると、例外がスローされる + const result = await randomRequest(); + console.log(result); + } catch (error) { + // ここでエラーを捕捉 + console.error("エラーが発生しました:", error.message); + } finally { + console.log("処理終了"); + } +} + +main(); +``` + +```js-exec:async_try_catch.js +処理開始... +エラーが発生しました: Failure: サーバーエラー +処理終了 +``` + +*(※注: 実行結果はランダムで成功する場合もあります)* diff --git a/public/docs/javascript/9-async-await/3-0-fetch.md b/public/docs/javascript/9-async-await/3-0-fetch.md new file mode 100644 index 0000000..a4dc0f6 --- /dev/null +++ b/public/docs/javascript/9-async-await/3-0-fetch.md @@ -0,0 +1,52 @@ +--- +id: javascript-async-await-fetch +title: Fetch API によるHTTPリクエスト +level: 2 +--- + +## Fetch API によるHTTPリクエスト + +JavaScript(特にブラウザ環境や最近のNode.js)でHTTPリクエストを行うための標準APIが `fetch` です。以前は `XMLHttpRequest` という扱いづらいAPIが使われていましたが、現在は `fetch` が主流です。 + +`fetch` 関数は `Promise` を返します。 + +基本的な流れは以下の通りです: + +1. `fetch(url)` を実行し、レスポンスヘッダーが届くのを待つ。 +2. Responseオブジェクトを受け取る。 +3. Responseオブジェクトからメソッド(`.json()`, `.text()`など)を使ってボディを読み込む(これも非同期)。 + +```js:fetch_basic.js +// 外部APIからJSONデータを取得する例 +// (Node.js 18以上ではfetchが標準で使用可能です) + +async function getUserData(userId) { + const url = `https://jsonplaceholder.typicode.com/users/${userId}`; + + try { + // 1. リクエスト送信 (ネットワークエラー以外はrejectされない) + const response = await fetch(url); + + // 2. HTTPステータスコードの確認 + if (!response.ok) { + throw new Error(`HTTP Error: ${response.status}`); + } + + // 3. レスポンスボディをJSONとしてパース (これもPromiseを返す) + const data = await response.json(); + + console.log(`Name: ${data.name}`); + console.log(`Email: ${data.email}`); + + } catch (error) { + console.error("Fetch failed:", error.message); + } +} + +getUserData(1); +``` + +```js-exec:fetch_basic.js +Name: Leanne Graham +Email: Sincere@april.biz +``` diff --git a/public/docs/javascript/9-async-await/3-1-fetch-post.md b/public/docs/javascript/9-async-await/3-1-fetch-post.md new file mode 100644 index 0000000..dd6d2f3 --- /dev/null +++ b/public/docs/javascript/9-async-await/3-1-fetch-post.md @@ -0,0 +1,19 @@ +--- +id: javascript-async-await-fetch-post +title: JSONデータの送信 (POST) +level: 3 +--- + +### JSONデータの送信 (POST) + +データを送信する場合は、第2引数にオプションオブジェクトを渡します。 + +```js-repl +> const postData = { title: 'foo', body: 'bar', userId: 1 }; +> await fetch('https://jsonplaceholder.typicode.com/posts', { +... method: 'POST', +... headers: { 'Content-Type': 'application/json' }, +... body: JSON.stringify(postData) +... }).then(res => res.json()) +{ title: 'foo', body: 'bar', userId: 1, id: 101 } +``` diff --git a/public/docs/javascript/9-async-await/4-0-promise-all-race.md b/public/docs/javascript/9-async-await/4-0-promise-all-race.md new file mode 100644 index 0000000..7fc7066 --- /dev/null +++ b/public/docs/javascript/9-async-await/4-0-promise-all-race.md @@ -0,0 +1,9 @@ +--- +id: javascript-async-await-promise-all-race +title: Promise.all() と Promise.race() +level: 2 +--- + +## `Promise.all()` と `Promise.race()` + +Async/Await は便利ですが、単純に `await` を連発すると、処理が**直列(シーケンシャル)**になってしまい、パフォーマンスが落ちる場合があります。複数の独立した非同期処理を行う場合は、並列実行を検討します。 diff --git a/public/docs/javascript/9-async-await/4-1-sequential.md b/public/docs/javascript/9-async-await/4-1-sequential.md new file mode 100644 index 0000000..53fc560 --- /dev/null +++ b/public/docs/javascript/9-async-await/4-1-sequential.md @@ -0,0 +1,13 @@ +--- +id: javascript-async-await-sequential +title: 直列実行(遅いパターン) +level: 3 +--- + +### 直列実行(遅いパターン) + +```javascript +// Aが終わってからBを開始する +const user = await fetchUser(); +const posts = await fetchPosts(); +``` diff --git a/public/docs/javascript/9-async-await/4-2-promise-all.md b/public/docs/javascript/9-async-await/4-2-promise-all.md new file mode 100644 index 0000000..f7b5f9f --- /dev/null +++ b/public/docs/javascript/9-async-await/4-2-promise-all.md @@ -0,0 +1,39 @@ +--- +id: javascript-async-await-promise-all +title: Promise.all() による並列実行 +level: 3 +--- + +### `Promise.all()` による並列実行 + +複数のPromiseを配列として受け取り、**全て完了するのを待って**から結果の配列を返します。一つでも失敗すると全体が失敗(reject)します。 + +```js:promise_all.js +const wait = (ms, value) => new Promise(r => setTimeout(() => r(value), ms)); + +async function parallelDemo() { + console.time("Total Time"); + + // 2つの処理を同時に開始 + const p1 = wait(1000, "User Data"); + const p2 = wait(1000, "Post Data"); + + try { + // 両方の完了を待つ + const [user, post] = await Promise.all([p1, p2]); + console.log("Result:", user, "&", post); + } catch (e) { + console.error(e); + } + + // 本来なら直列だと2秒かかるが、並列なので約1秒で終わる + console.timeEnd("Total Time"); +} + +parallelDemo(); +``` + +```js-exec:promise_all.js +Result: User Data & Post Data +Total Time: 1.008s +``` diff --git a/public/docs/javascript/9-async-await/4-3-promise-race.md b/public/docs/javascript/9-async-await/4-3-promise-race.md new file mode 100644 index 0000000..b4ee72b --- /dev/null +++ b/public/docs/javascript/9-async-await/4-3-promise-race.md @@ -0,0 +1,16 @@ +--- +id: javascript-async-await-promise-race +title: Promise.race() +level: 3 +--- + +### `Promise.race()` + +複数のPromiseのうち、**最も早く完了(または失敗)したもの**の結果だけを返します。タイムアウト処理の実装などによく使われます。 + +```js-repl +> const fast = new Promise(r => setTimeout(() => r("Fast"), 100)); +> const slow = new Promise(r => setTimeout(() => r("Slow"), 500)); +> await Promise.race([fast, slow]) +'Fast' +``` diff --git a/public/docs/javascript/9-async-await/5-0-summary.md b/public/docs/javascript/9-async-await/5-0-summary.md new file mode 100644 index 0000000..09f23a9 --- /dev/null +++ b/public/docs/javascript/9-async-await/5-0-summary.md @@ -0,0 +1,12 @@ +--- +id: javascript-async-await-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **Async/Await**: `Promise` をベースにした糖衣構文。非同期処理を同期処理のように記述でき、可読性が高い。 + * **Error Handling**: 同期コードと同じく `try...catch` が使用可能。 + * **Fetch API**: モダンなHTTP通信API。`response.ok` でステータスを確認し、`response.json()` でボディをパースする2段構えが必要。 + * **並列処理**: 独立した複数の非同期処理は `await` を連続させるのではなく、`Promise.all()` を使用して並列化することでパフォーマンスを向上させる。 diff --git a/public/docs/javascript/9-async-await/6-1-practice1.md b/public/docs/javascript/9-async-await/6-1-practice1.md new file mode 100644 index 0000000..7b7fc48 --- /dev/null +++ b/public/docs/javascript/9-async-await/6-1-practice1.md @@ -0,0 +1,21 @@ +--- +id: javascript-async-await-practice1 +title: '練習問題1: ユーザー情報の取得と表示' +level: 3 +--- + +### 練習問題1: ユーザー情報の取得と表示 + +以下の要件を満たす関数 `displayUserSummary(userId)` を作成してください。 + +1. `https://jsonplaceholder.typicode.com/users/{userId}` からユーザー情報を取得する。 +2. `https://jsonplaceholder.typicode.com/users/{userId}/todos` からそのユーザーのTODOリストを取得する。 +3. 上記2つのリクエストは、**パフォーマンスを考慮して並列に実行**すること。 +4. 取得したデータから、「ユーザー名」と「完了済み(completed: true)のTODOの数」を出力する。 +5. 通信エラー時は適切にエラーメッセージを表示する。 + +```js:practice10_1.js +``` + +```js-exec:practice10_1.js +``` diff --git a/public/docs/javascript/9-async-await/6-2-practice2.md b/public/docs/javascript/9-async-await/6-2-practice2.md new file mode 100644 index 0000000..8965abe --- /dev/null +++ b/public/docs/javascript/9-async-await/6-2-practice2.md @@ -0,0 +1,16 @@ +--- +id: javascript-async-await-practice2 +title: '練習問題2: タイムアウト付きFetch' +level: 3 +--- + +### 練習問題2: タイムアウト付きFetch + +指定したURLからデータを取得するが、一定時間内にレスポンスが返ってこない場合は「タイムアウト」としてエラーにする関数 `fetchWithTimeout(url, ms)` を作成してください。 +*ヒント: `fetch` のPromiseと、指定時間後に reject するPromiseを `Promise.race()` で競走させてください。* + +```js:practice10_2.js +``` + +```js-exec:practice10_2.js +``` diff --git a/public/docs/javascript/index.yml b/public/docs/javascript/index.yml new file mode 100644 index 0000000..e697523 --- /dev/null +++ b/public/docs/javascript/index.yml @@ -0,0 +1,33 @@ +name: JavaScript +description: hoge +pages: +- slug: 0-intro + name: JavaScriptへようこそ + title: JavaScriptへようこそ +- slug: 1-basics + name: 基本構文とデータ型 + title: 基本構文とデータ型 +- slug: 2-control + name: 制御構文 + title: 制御構文 +- slug: 3-functions-closures + name: 関数とクロージャ + title: 関数とクロージャ +- slug: 4-this + name: '''this''の正体' + title: '''this''の正体' +- slug: 5-objects-prototype + name: オブジェクトとプロトタイプ + title: オブジェクトとプロトタイプ +- slug: 6-classes + name: クラス構文 + title: クラス構文 (ES6+) +- slug: 7-arrays + name: 配列とイテレーション + title: 配列とイテレーション +- slug: 8-promise + name: '非同期処理①: Promise' + title: 非同期処理(1)- Promise +- slug: 9-async-await + name: '非同期処理②: Async/Await' + title: 非同期処理(2)- Async/Await と Fetch API diff --git a/public/docs/python-1.md b/public/docs/python-1.md deleted file mode 100644 index 17010e2..0000000 --- a/public/docs/python-1.md +++ /dev/null @@ -1,230 +0,0 @@ -# 第1章: Pythonへようこそ:環境構築と基本思想 - -プログラミング経験者であっても、言語ごとのツールや流儀を最初に理解することは重要です。この章では、Pythonの開発環境を整え、基本的なツールの使い方を学びます。 - -## Pythonの思想と特徴: 「読みやすさ」は最優先 - -他の言語(Java, C++, PHPなど)と比較したとき、Pythonが最も重視するのは**コードの可読性(Readability)**です。 - -* **シンプルな文法:** C言語やJavaのような `{}`(波括弧)によるブロックや、行末の `;`(セミコロン)を必要としません。 -* **強制的なインデント:** Pythonは、**インデント(字下げ)**そのものでコードブロックを表現します。これは構文的なルールであり、オプションではありません。これにより、誰が書いても(ある程度)同じような見た目のコードになり、可読性が劇的に向上します。 -* **動的型付け (Dynamic Typing):** JavaやC++のように `int num = 10;` と変数の型を明示的に宣言する必要がありません。`num = 10` と書けば、Pythonが実行時に自動的に型を推論します。(これはJavaScriptやPHPと似ていますが、Pythonは型付けがより厳格(Strong Typing)で、例えば文字列と数値を暗黙的に連結しようとするとエラーになります) -* **豊富な標準ライブラリ**: 「Batteries Included(バッテリー同梱)」という思想のもと、OS操作、ネットワーク、データ処理、JSON、正規表現など、多くの機能が最初から標準ライブラリとして提供されています。 - -**💡 The Zen of Python (Pythonの禅)** Pythonの設計思想は、`import this` というコマンドでいつでも確認できます。 - -* Beautiful is better than ugly. (醜いより美しいほうがいい) -* Explicit is better than implicit. (暗黙的より明示的なほうがいい) -* Simple is better than complex. (複雑であるよりシンプルなほうがいい) - -## Pythonのインストール方法 - -手元の環境で本格的に開発を進めるために、Pythonのインストール方法を紹介します。 - -### Windows - -WindowsでPythonをインストールするには、主に2つの方法があります。 - -1. **[Python公式インストーラ](https://www.python.org/downloads/)**: Pythonの公式サイトからインストーラをダウンロードする方法が最も一般的です。インストール時に「Add Python to PATH」のチェックを入れると、コマンドプロンプトやPowerShellから `python` コマンドを直接実行できるようになり便利です。 -2. **Microsoft Store**: Microsoft Storeからも手軽にPythonをインストールできます。 - -### macOS / Linux - -macOSでは、**Homebrew** というパッケージマネージャを使ってインストールするのが簡単です。 -`brew install python` - -もちろん、Windowsと同様に公式サイトからインストーラをダウンロードすることも可能です。多くのLinuxディストリビューションには初めからPythonがインストールされていますが、最新版を使いたい場合はディストリビューションのパッケージマネージャ(`apt`, `yum`など)を利用するのが一般的です。 - -### バージョン管理と環境管理ツール - -より高度な開発や、複数のプロジェクトを並行して進める場合は、バージョン管理ツールや統合的な環境管理ツールの利用が推奨されます。 - - * **[pyenv](https://github.com/pyenv/pyenv)**: 複数のPythonバージョン(例: 3.9と3.11)を一つのPCに共存させ、プロジェクトごとに切り替えるためのツールです。 - * **[Conda](https://docs.conda.io/en/latest/)**: 特にデータサイエンスの分野で人気のあるツールです。**Conda** はPythonのバージョン管理だけでなく、パッケージ管理、仮想環境の管理までを一つでこなせるオールインワンのソリューションです。 - -## 対話モード(REPL)でPythonを体験しよう - -**REPL**(Read-Eval-Print Loop)は、入力したコードをその場で実行し、結果をすぐに見ることができる強力な学習・デバッグツールです。 - -### ブラウザで今すぐ試す - -このウェブサイトではドキュメント内にPython3の実行環境を埋め込んでいます。 -以下のように緑枠で囲われたコード例には自由にPythonコードを書いて試すことができます。気軽に利用してください。 - -```python-repl:1 ->>> message = "Hello, Python!" ->>> print(message) -Hello, Python! ->>> 1 + 2 * 3 -7 -``` - -### 自分のPCで使う - -インストールが完了したら、自分のPCのターミナル(コマンドプロンプトやPowerShellなど)で `python` と入力すれば、同じ対話モードを起動できます。 - -``` -$ python -Python 3.11.5 (...) -Type "help", "copyright", "credits" or "license" for more information. ->>> -``` - -`>>>` というプロンプトが表示されたら準備完了です。 - -### REPL の基本的な使い方 - -* **計算:** 数式を直接入力すると、計算結果が返ってきます。 -```python-repl:2 ->>> 10 * 5 + 3 -53 -``` -* **変数と関数の利用:** 変数を定義したり、`print()`のような組み込み関数を呼び出したりできます。 -```python-repl:3 ->>> greeting = "Hi there" ->>> print(greeting) -Hi there -``` -* **ヘルプ機能:** `help()` と入力するとヘルプが表示されます。調べたいモジュールや関数名(例: `str`)を入力するとドキュメントが表示されます。 - * PCのターミナルで起動したREPLでは、対話的なヘルプモードが起動します。ヘルプモードを抜けるには `quit` と入力します。 -```python-repl:4 ->>> help(str) -Help on class str in module builtins: - -class str(object) - | str(object='') -> str - | str(bytes_or_buffer[, encoding[, errors]]) -> str - | ... -``` -* **終了方法:** REPLを終了するには、`exit()` と入力するか、ショートカットキー(macOS/Linuxでは `Ctrl + D`、Windowsでは `Ctrl + Z` を押してからEnter)を使用します。 - * このウェブサイトに埋め込まれているREPLは、終了できません。 - -## スクリプトの実行方法 - -一連の処理をまとめて実行する場合は、`.py` という拡張子を持つファイルにコードを記述します。例えば、`hello.py` というファイルを以下のように作成します。 -REPLでは式を入力するだけでも結果が表示されていましたが、スクリプトで結果を表示するには `print()` 関数を使う必要があります。 - -```python:hello.py -print("Hello from a Python script!") -``` - -このスクリプトを実行するには、ターミナルで `python hello.py` のようにコマンドを入力します。 - -このウェブサイト上では以下のように実行ボタンをクリックするとスクリプトの実行結果が表示されます。上の hello1.py のコードを変更して再度実行すると結果も変わるはずです。試してみてください。 - -```python-exec:hello.py -Hello from a Python script! -``` - -### __main__ について - -前述の hello.py のようにファイルの1行目から処理を書いても問題なく動作しますが、一般的には以下のようなお決まりの書き方が用いられます。 - -```python:hello2.py -def main(): - print("Hello from a Python script!") - -if __name__ == "__main__": - main() -``` - -```python-exec:hello2.py -Hello from a Python script! -``` - -なぜわざわざ `if __name__ == "__main__":` を使うのでしょうか? -それは、**書いたコードを「スクリプトとして直接実行する」場合と、「他のファイルから部品(モジュール)として読み込んで使う」場合の両方に対応できるようにするため**です。 - -Pythonでは、ファイルは他のファイルから `import` 文で読み込むことができます。このとき、読み込まれたファイル(モジュール)は上から順に実行されます。 - -`if __name__ == "__main__":` を使うと、**「このファイルがコマンドラインから直接 `python a.py` のように実行された時だけ、このブロックの中の処理を実行してね」** という意味になります。 - -**例:再利用可能な関数を持つスクリプト** - -```python:my_utils.py -def say_hello(name): - """挨拶を返す関数""" - return f"Hello, {name}!" - -# このファイルが直接実行された時だけ、以下のテストコードを実行する -if __name__ == "__main__": - print("--- Running Test ---") - message = say_hello("Alice") - print(message) - print("--- Test Finished ---") -``` - -このファイルを2通りの方法で使ってみます。 - -1. **直接スクリプトとして実行する** - - ```python-exec:my_utils.py - --- Running Test --- - Hello, Alice! - --- Test Finished --- - ``` - -2. **他のファイルからモジュールとして読み込む** - - ```python:main_app.py - # my_utils.py から say_hello 関数だけを読み込む - from my_utils import say_hello - - print("--- Running Main App ---") - greeting = say_hello("Bob") - print(greeting) - ``` - - ```python-exec:main_app.py - --- Running Main App --- - Hello, Bob! - ``` - - `my_utils.py` のテストコード(`--- Running Test ---`など)は実行されず、`say_hello` 関数だけを部品として利用できました。 - -このように、`if __name__ == "__main__":` は、**再利用可能な関数やクラスの定義**と、**そのファイル単体で動かすための処理**をきれいに分離するための、Pythonにおける非常に重要な作法です。 - -## パッケージ管理ツール `pip` と仮想環境 `venv` - -Pythonの強力なエコシステムは、豊富なサードパーティ製パッケージ(ライブラリ)によって支えられています。これらのパッケージを管理するのが **`pip`** です。 - -しかし、プロジェクトごとに異なるバージョンのパッケージを使いたい場合、依存関係の衝突が問題になります。これを解決するのが **仮想環境** で、Pythonでは **`venv`** モジュールを使って作成するのが標準的です。 - -**仮想環境とは?** 🚧 -プロジェクト専用の独立したPython実行環境です。ここでインストールしたパッケージはシステム全体には影響を与えず、そのプロジェクト内に限定されます。 - -**基本的な流れ:** - -1. **仮想環境の作成**: - - ```bash - # .venvという名前の仮想環境を作成 - python -m venv .venv - ``` - -2. **仮想環境の有効化(Activate)**: - - ```bash - # macOS / Linux - source .venv/bin/activate - - # Windows (PowerShell) - .\.venv\Scripts\Activate.ps1 - ``` - - 有効化すると、ターミナルのプロンプトに `(.venv)` のような表示が付きます。 - -3. **パッケージのインストール**: - 有効化された環境で `pip` を使ってパッケージをインストールします。 - - ```bash - (.venv) $ pip install requests - ``` - -4. **仮想環境の無効化(Deactivate)**: - - ```bash - (.venv) $ deactivate - ``` - -**`pyenv` でPythonバージョンを固定し、`venv` でプロジェクトのパッケージを隔離する** のが、現代的なPython開発の基本スタイルです。(前述の **Conda** は、このPythonバージョン管理と環境・パッケージ管理を両方とも行うことができます。) \ No newline at end of file diff --git a/public/docs/python-2.md b/public/docs/python-2.md deleted file mode 100644 index 499f0c3..0000000 --- a/public/docs/python-2.md +++ /dev/null @@ -1,169 +0,0 @@ -# 第2章: Pythonの基本構文とデータ型 - -他の言語でのプログラミング経験がある方を対象に、Pythonの基本的な文法と組み込みデータ型を解説します。多くの静的型付け言語(Java, C++, C\#など)との違いを意識しながら、Pythonの特徴を素早く掴んでいきましょう。特に、**動的型付け**は重要なコンセプトです。 - -## 変数宣言と動的型付け - -Pythonの最も大きな特徴の一つは、変数の型を宣言する必要がないことです。変数への代入が、変数の作成と型の決定を同時に行います。 - -```python-repl:1 ->>> # 変数 message を作成し、文字列を代入 ->>> message = "Hello, Python!" ->>> print(message) -Hello, Python! ->>> # 変数 number を作成し、整数を代入 ->>> number = 100 ->>> print(number) -100 -``` - -さらに、Pythonは**動的型付け**言語です。これは、一度作成した変数に、異なる型のデータを再代入できることを意味します。`type()`関数を使うと、変数が現在どの型を参照しているかを確認できます。 - -```python-repl:2 ->>> x = 10 ->>> type(x) - ->>> # 同じ変数 x に文字列を再代入 ->>> x = "Hello" ->>> type(x) - -``` - -この柔軟性は、静的型付け言語に慣れていると少し奇妙に感じるかもしれませんが、Pythonの簡潔さと書きやすさの源泉となっています。 - -## 基本的なデータ型 - -Pythonには多くの組み込みデータ型がありますが、ここでは最も基本的なものを紹介します。 - -### 数値(int, float) - -Pythonは整数 (`int`) と浮動小数点数 (`float`) を区別します。 - -```python-repl:3 ->>> # 整数 (int) ->>> a = 10 ->>> type(a) - ->>> # 浮動小数点数 (float) ->>> b = 3.14 ->>> type(b) - -``` - -四則演算は直感的に行えます。注意点として、除算 (`/`) は常に `float` を返します。整数除算を行いたい場合は (`//`) を使います。 - -```python-repl:4 ->>> 10 / 3 -3.3333333333333335 ->>> 10 // 3 -3 ->>> # べき乗 ->>> 2 ** 4 -16 ->>> # 剰余 ->>> 10 % 3 -1 -``` - -### 文字列(str) - -文字列はシングルクォート (`'`) またはダブルクォート (`"`) で囲んで作成します。 - -```python-repl:5 ->>> name = "Guido" ->>> greeting = 'Hello' -``` - -文字列の連結は `+` 演算子、繰り返しは `*` 演算子を使います。 - -```python-repl:6 ->>> full_greeting = greeting + ", " + name + "!" ->>> print(full_greeting) -Hello, Guido! ->>> print("-" * 10) ----------- -``` - -変数の値を文字列に埋め込む際には、**f-string (フォーマット済み文字列リテラル)** が非常に便利で推奨されています。文字列の前に `f` を付け、埋め込みたい変数を `{}` で囲みます。 - -```python-repl:7 ->>> name = "Ada" ->>> age = 36 ->>> message = f"My name is {name} and I am {age} years old." ->>> print(message) -My name is Ada and I am 36 years old. -``` - -### 真偽値(bool) - -真偽値は `True` と `False` の2つの値を持ちます(先頭が大文字であることに注意してください)。論理演算子には `and`, `or`, `not` を使います。 - -```python-repl:8 ->>> is_active = True ->>> has_permission = False ->>> type(is_active) - ->>> # 論理積 (AND) ->>> is_active and has_permission -False ->>> # 論理和 (OR) ->>> is_active or has_permission -True ->>> # 否定 (NOT) ->>> not is_active -False -``` - -## 型ヒント(Type Hints)の紹介 - -動的型付けは柔軟ですが、コードが大規模になると変数がどの型を期待しているのかが分かりにくくなることがあります。そこで導入されたのが**型ヒント**です。これは、変数や関数の引数、返り値に「期待される型」を注釈として付与する機能です。 - -`変数名: 型` のように記述します。 - -```python-repl:9 ->>> # 型ヒントを付けた変数宣言 ->>> user_name: str = "Alice" ->>> user_id: int = 123 -``` - -**重要な注意点:** 型ヒントはあくまで「ヒント」であり、**Pythonの実行エンジンはこれを強制しません**。つまり、型ヒントと異なる型の値を代入してもエラーにはなりません。 - -```python-repl:10 ->>> user_id: int = 123 ->>> type(user_id) - ->>> # int型とヒントを付けたが、文字列を代入できてしまう ->>> user_id = "abc-789" ->>> type(user_id) - -``` - -型ヒントは、コードの可読性を高めたり、MyPyのような静的解析ツールや統合開発環境(IDE)がコードのバグを事前に発見したりするために利用されます。 - -## この章のまとめ - - * Pythonでは変数の型宣言は不要で、値の代入によって型が自動的に決まる(**動的型付け**)。 - * 基本的なデータ型として**数値** (`int`, `float`)、**文字列** (`str`)、**真偽値** (`bool`) がある。 - * 文字列に変数を埋め込むには、簡潔で強力な **f-string** を使うのが一般的。 - * **型ヒント** (`変数名: 型`) は、コードの可読性を向上させるための注釈であり、実行時に型の強制力はない。 - -### 練習問題1 - -`item_name` という変数に商品名(文字列)、`price` という変数に価格(整数)、`stock` という変数に在庫数(整数)をそれぞれ代入してください。その後、f-stringを使って「商品: [商品名], 価格: [価格]円, 在庫: [在庫数]個」という形式の文字列にし、 `print()` で出力するコードを書いてみましょう。 - -```python:practice2_1.py -``` - -```python-exec:practice2_1.py -(出力例) 商品: 高性能マウス, 価格: 4500円, 在庫: 2個 -``` - -### 練習問題2 - -`is_adult` という変数に `bool` 型の型ヒントを付けて `True` を代入し、`type()` で型を確認してください。その後、同じ変数に数値の `20` を代入し、再度 `type()` で型を確認してください。この結果から、型ヒントが実行時の動作にどのような影響を与える(あるいは与えない)か考察してみましょう。 - -```python:practice2_2.py -``` - -```python-exec:practice2_2.py -``` diff --git a/public/docs/python-3.md b/public/docs/python-3.md deleted file mode 100644 index a09126d..0000000 --- a/public/docs/python-3.md +++ /dev/null @@ -1,299 +0,0 @@ -# 第3章: Pythonを使いこなす:リスト、タプル、辞書、セット - -Pythonのプログラミングにおいて、データを効率的に扱う能力は非常に重要です。この章では、Pythonに組み込まれている強力なコレクション(データ構造)である**リスト**、**タプル**、**辞書**、**セット**を学びます。これらのデータ構造は、他の言語における配列、ハッシュマップ、集合などと似ていますが、Pythonならではの便利な特性やメソッドを持っています。これらを理解し、使いこなすことが「Pythonらしい」コードを書くための第一歩です。 - -## リスト (List):ミュータブルなシーケンス - -リストは、複数の要素を順序付けて格納できるコレクションです。他の言語における「動的配列」に最も近い存在です。 - - * **ミュータブル (Mutable)**: 作成後に要素の追加、変更、削除が可能です。 - * **順序あり (Ordered)**: 要素は格納された順序を保持します。 - * **多様な要素**: 数値、文字列、さらには他のリストなど、異なるデータ型の要素を混在させることができます。 - -**基本的な使い方 (REPL実行例)** - -```python-repl:1 ->>> # リストの作成 ->>> fruits = ['apple', 'banana', 'cherry'] ->>> fruits -['apple', 'banana', 'cherry'] - ->>> # 要素へのアクセス (インデックスは0から) ->>> fruits[1] -'banana' - ->>> # 要素の変更 ->>> fruits[0] = 'apricot' ->>> fruits -['apricot', 'banana', 'cherry'] - ->>> # 要素の追加 (末尾に) ->>> fruits.append('mango') ->>> fruits -['apricot', 'banana', 'cherry', 'mango'] - ->>> # 要素の削除 (指定したインデックス) ->>> removed_fruit = fruits.pop(1) ->>> removed_fruit -'banana' ->>> fruits -['apricot', 'cherry', 'mango'] -``` - -リストは非常に柔軟性が高く、Pythonで最も頻繁に使われるデータ構造の一つです。 - -## タプル (Tuple):イミュータブルなシーケンス - -タプルはリストと非常によく似ていますが、最大の違いは**イミュータブル (Immutable)**、つまり一度作成したら変更できない点です。 - - * **イミュータブル (Immutable)**: 要素の変更、追加、削除はできません。 - * **順序あり (Ordered)**: リスト同様、順序を保持します。 - -**なぜタプルを使うのか?** 🤔 - -1. **安全なデータ**: 変更されたくないデータを安全に保持できます(例: 関数の引数、定数セット)。 -2. **パフォーマンス**: 一般的にリストよりわずかに高速で、メモリ効率が良いとされています。 -3. **辞書のキーとして使用可能**: ミュータブルなリストは辞書のキーになれませんが、イミュータブルなタプルはキーとして使えます。 - -**基本的な使い方 (REPL実行例)** - -```python-repl:2 ->>> # タプルの作成 (丸括弧を使用) ->>> coordinates = (10, 20) ->>> coordinates -(10, 20) - ->>> # 要素へのアクセス ->>> coordinates[0] -10 - ->>> # 変更しようとするとエラーが発生する ->>> coordinates[0] = 5 -Traceback (most recent call last): - File "", line 1, in -TypeError: 'tuple' object does not support item assignment - ->>> # アンパッキング (複数の変数に要素を一度に代入) ->>> x, y = coordinates ->>> x -10 ->>> y -20 -``` - -## 辞書 (Dictionary):キーと値のペア - -辞書は、他の言語の**ハッシュマップ**や**連想配列**に相当します。順序ではなく、一意な「キー」を使って「値」にアクセスします。 - - * **キーと値のペア**: `key: value` の形式でデータを格納します。 - * **一意なキー**: キーは辞書内で重複してはいけません。 - * **順序**: Python 3.7以降では、要素が追加された順序が保持されます。 - * **ミュータブル**: 要素の追加、変更、削除が可能です。 - -**基本的な使い方 (REPL実行例)** - -```python-repl:3 ->>> # 辞書の作成 ->>> person = {'name': 'Taro Yamada', 'age': 30, 'city': 'Tokyo'} ->>> person -{'name': 'Taro Yamada', 'age': 30, 'city': 'Tokyo'} - ->>> # 値へのアクセス (キーを使用) ->>> person['name'] -'Taro Yamada' - ->>> # 値の変更 ->>> person['age'] = 31 ->>> person['age'] -31 - ->>> # 新しいキーと値のペアの追加 ->>> person['job'] = 'Engineer' ->>> person -{'name': 'Taro Yamada', 'age': 31, 'city': 'Tokyo', 'job': 'Engineer'} - ->>> # キーと値のペアをまとめて取得 ->>> person.items() -dict_items([('name', 'Taro Yamada'), ('age', 31), ('city', 'Tokyo'), ('job', 'Engineer')]) -``` - -## セット (Set):ユニークな要素のコレクション - -セットは、**順序がなく、重複した要素を持たない**コレクションです。数学の「集合」の概念に近く、和集合や積集合といった集合演算を高速に行えます。 - - * **ユニークな要素**: 同じ要素を複数含めることはできません。 - * **順序なし (Unordered)**: 要素の格納順は保証されません。 - * **ミュータブル**: 要素の追加、削除は可能です。 - -**使いどころ** - - * リストなどから重複した要素を効率的に削除したい場合。 - * 二つのコレクションに共通する要素(積集合)や、全ての要素(和集合)を求めたい場合。 - -**基本的な使い方 (REPL実行例)** - -```python-repl:4 ->>> # セットの作成 (重複した4は自動的に無視される) ->>> numbers = {1, 2, 3, 4, 4, 5} ->>> numbers -{1, 2, 3, 4, 5} - ->>> # 要素の追加 ->>> numbers.add(6) ->>> numbers -{1, 2, 3, 4, 5, 6} - ->>> # 重複削除への応用 ->>> my_list = ['a', 'b', 'c', 'a', 'b'] ->>> unique_elements = set(my_list) ->>> unique_elements -{'c', 'a', 'b'} - ->>> # 集合演算 ->>> set_a = {1, 2, 3, 4} ->>> set_b = {3, 4, 5, 6} - ->>> # 和集合 (A ∪ B) ->>> set_a | set_b -{1, 2, 3, 4, 5, 6} - ->>> # 積集合 (A ∩ B) ->>> set_a & set_b -{3, 4} -``` - -## スライシングによる部分的な要素の取得 - -スライシングは、リストやタプルのようなシーケンスから、部分的な要素を効率的に取り出すための非常に強力な機能です。構文は `[start:stop:step]` です。 - -**REPL実行例** - -```python-repl:5 ->>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - ->>> # インデックス1から4の手前まで ->>> numbers[1:4] -[1, 2, 3] - ->>> # 最初からインデックス5の手前まで ->>> numbers[:5] -[0, 1, 2, 3, 4] - ->>> # インデックス6から最後まで ->>> numbers[6:] -[6, 7, 8, 9] - ->>> # 2つおきに要素を取得 ->>> numbers[::2] -[0, 2, 4, 6, 8] - ->>> # 逆順にする ->>> numbers[::-1] -[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] -``` - -## 内包表記 (Comprehensions)による効率的な生成 - -内包表記は、既存のイテラブルから新しいリスト、辞書、セットを簡潔かつ効率的に生成するためのPythonらしい構文です。`for`ループを使うよりも短く、可読性が高いコードを書くことができます。 - -**リスト内包表記** - -`for`ループで書く場合と、リスト内包表記で書く場合を比較してみましょう。 - -```python-repl:6 ->>> # forループの場合 ->>> squares_loop = [] ->>> for i in range(10): -... squares_loop.append(i * i) -... ->>> squares_loop -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] - ->>> # リスト内包表記の場合 (簡潔!) ->>> squares_comp = [i * i for i in range(10)] ->>> squares_comp -[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] - ->>> # 条件付きも可能 (偶数のみ2乗) ->>> even_squares = [i * i for i in range(10) if i % 2 == 0] ->>> even_squares -[0, 4, 16, 36, 64] -``` - -**辞書内包表記** - -```python-repl:7 ->>> # 数値をキー、その2乗を値とする辞書を作成 ->>> square_dict = {x: x*x for x in range(5)} ->>> square_dict -{0: 0, 1: 1, 2: 4, 3: 9, 4: 16} -``` - -**セット内包表記** - -```python-repl:8 ->>> # リスト内のユニークな数値の2乗のセットを作成 ->>> numbers = [1, 2, 2, 3, 4, 4, 5] ->>> square_set = {x*x for x in numbers} ->>> square_set -{1, 4, 9, 16, 25} -``` - -## この章のまとめ - -この章では、Pythonでデータを扱うための基本的な4つのコレクションを学びました。それぞれの特性を理解し、状況に応じて適切に使い分けることが重要です。 - -| データ構造 | 構文例 | 変更可能性 | 順序 | 重複 | 主な用途 | -| :--- | :--- | :--- | :--- | :--- | :--- | -| **リスト (List)** | `[1, 'a', 2]` | **可能** (ミュータブル) | **あり** | 許可 | 順序があり、変更が必要な要素の集まり。 | -| **タプル (Tuple)** | `(1, 'a', 2)` | **不可能** (イミュータブル) | **あり** | 許可 | 変更しない(させたくない)データの集まり、辞書のキー。 | -| **辞書 (Dictionary)** | `{'key': 'value'}` | **可能** (ミュータブル) | **あり** (Python 3.7+) | キーは不許可 | キーと値のペアでデータを管理。 | -| **セット (Set)** | `{1, 'a', 2}` | **可能** (ミュータブル) | **なし** | 不許可 | 重複を除き、要素の存在確認や集合演算を高速に行う。 | - -加えて、**スライシング**を使えばシーケンス(リストやタプル)から部分的な要素を柔軟に取得でき、**内包表記**を利用すれば、これらのコレクションを一行で効率的かつPythonらしく生成できます。これらのツールは、あなたのコードをより簡潔で強力なものにしてくれるでしょう。 - -### 練習問題1: 商品のフィルタリング - -ある店舗の商品のリストがあります。このリストから、価格が500円以上の商品だけを抽出し、その名前だけを新しいリストに格納してください。 - -**ヒント:** -リスト内包表記と、辞書の値にアクセスする方法 (`product['price']`) を組み合わせ、`if` 条件を追加してみましょう。 - -```python:practice3_1.py -products = [ - {'name': 'Apple', 'price': 150}, - {'name': 'Banana', 'price': 100}, - {'name': 'Melon', 'price': 600}, - {'name': 'Orange', 'price': 120}, - {'name': 'Grape', 'price': 550} -] - -``` - -```python-exec:practice3_1.py -(出力例) ['Melon', 'Grape'] -``` - -### 練習問題2: クラブ活動のメンバー分析 - -2つのクラブ活動、「数学クラブ」と「科学クラブ」のメンバーリストがあります。セット(集合)の機能を使って、以下のメンバーリストを作成してください。 - -a. 両方のクラブに所属しているメンバー -b. 少なくともどちらか一方のクラブに所属している全メンバー -c. 数学クラブにのみ所属しているメンバー - -**ヒント:** -セットの積集合 (`&`)、和集合 (`|`)、差集合 (`-`) 演算子を使います。 - -```python:practice3_2.py -math_club = {'Alice', 'Bob', 'Charlie', 'David'} -science_club = {'Charlie', 'David', 'Eve', 'Frank'} - -``` - -```python-exec:practice3_2.py -(出力例) -a. {'Charlie', 'David'} -b. {'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'} -c. {'Alice', 'Bob'} -``` diff --git a/public/docs/python-4.md b/public/docs/python-4.md deleted file mode 100644 index 6994738..0000000 --- a/public/docs/python-4.md +++ /dev/null @@ -1,284 +0,0 @@ -# 第4章: 制御構文と関数:Pythonらしい書き方 - -この章では、Pythonの基本的な制御構文(条件分岐、ループ)と関数の定義方法について学びます。他の言語にも同様の機能はありますが、特に`for`ループの振る舞いや、柔軟な引数の渡し方はPythonの大きな特徴です。これらの「Pythonらしい」書き方をマスターすることで、より簡潔で読みやすいコードを書けるようになります。 - -## if/elif/elseによる条件分岐 - -Pythonの条件分岐は`if`、`elif`(else ifの略)、`else`を使って記述します。C言語やJavaのような波括弧`{}`は使わず、**コロン`:`とインデント(通常は半角スペース4つ)**でコードブロックを表現するのが最大の特徴です。 - -```python-repl:1 ->>> score = 85 ->>> if score >= 90: -... print('優') -... elif score >= 80: -... print('良') -... elif score >= 70: -... print('可') -... else: -... print('不可') -... -良 -``` - -条件式に`and`や`or`、`not`といった論理演算子も使用できます。 - -```python-repl:2 ->>> temp = 25 ->>> is_sunny = True ->>> if temp > 20 and is_sunny: -... print("お出かけ日和です") -... -お出かけ日和です -``` - -## forループとrange()、enumerate() - -Pythonの`for`ループは、他の言語の`for (int i = 0; i < 5; i++)`といったカウンタ変数を使うスタイルとは少し異なります。リストやタプル、文字列などの**イテラブル(反復可能)オブジェクト**から要素を1つずつ取り出して処理を実行します。これは、Javaの拡張for文やC\#の`foreach`に似ています。 - -```python-repl:3 ->>> fruits = ['apple', 'banana', 'cherry'] ->>> for fruit in fruits: -... print(f"I like {fruit}") -... -I like apple -I like banana -I like cherry -``` - -### `range()` 関数 - -決まった回数のループを実行したい場合は、`range()`関数が便利です。`range(n)`は0からn-1までの連続した数値を生成します。 - -```python-repl:4 ->>> for i in range(5): -... print(i) -... -0 -1 -2 -3 -4 -``` - -### `enumerate()` 関数 - -ループ処理の中で、要素のインデックス(番号)と値の両方を使いたい場合があります。そのような時は`enumerate()`関数を使うと、コードが非常にスッキリします。これは非常にPythonらしい書き方の一つです。 - -```python-repl:5 ->>> fruits = ['apple', 'banana', 'cherry'] ->>> for i, fruit in enumerate(fruits): -... print(f"Index: {i}, Value: {fruit}") -... -Index: 0, Value: apple -Index: 1, Value: banana -Index: 2, Value: cherry -``` - -## whileループ - -`while`ループは、指定された条件が`True`である間、処理を繰り返します。ループを途中で抜けたい場合は`break`を、現在の回の処理をスキップして次の回に進みたい場合は`continue`を使用します。 - -```python-repl:6 ->>> n = 0 ->>> while n < 5: -... print(n) -... n += 1 -... -0 -1 -2 -3 -4 -``` - -## 関数の定義 (def) - -関数は`def`キーワードを使って定義します。ここでもコードブロックはコロン`:`とインデントで示します。値は`return`キーワードで返します。 - -```python-repl:7 ->>> def greet(name): -... """指定された名前で挨拶を返す関数""" # これはDocstringと呼ばれるドキュメント文字列です -... return f"Hello, {name}!" -... ->>> message = greet("Alice") ->>> print(message) -Hello, Alice! -``` - -引数と返り値に**型アノテーション(型ヒント)**を付けることもできます。これはコードの可読性を高め、静的解析ツールによるバグの発見を助けますが、実行時の動作に直接影響を与えるものではありません。 -型アノテーションは `引数名: 型` のように記述し、返り値の型は `-> 型:` のように記述します。 - -```python-repl:8 ->>> # typingモジュールからList型をインポート ->>> from typing import List ->>> def greet(name: str) -> str: -... """指定された名前で挨拶を返す関数""" -... return f"Hello, {name}!" -... ->>> message = greet("Alice") ->>> print(message) -Hello, Alice! -``` - -## 引数の渡し方(位置引数、キーワード引数、デフォルト引数値) - -Pythonの関数は、非常に柔軟な引数の渡し方ができます。型アノテーションと組み合わせることで、どのような型の引数を期待しているかがより明確になります。 - - * **位置引数 (Positional Arguments):** 最も基本的な渡し方で、定義された順序で値を渡します。 - * **キーワード引数 (Keyword Arguments):** `引数名=値`の形式で渡します。順序を問わないため、可読性が向上します。 - * **デフォルト引数値 (Default Argument Values):** 関数を定義する際に引数にデフォルト値を設定できます。呼び出し時にその引数が省略されると、デフォルト値が使われます。 - -```python-repl:9 ->>> def describe_pet(animal_type: str, pet_name: str, owner_name: str = "Taro") -> None: -... # この関数は何も値を返さないため、返り値の型は None となります -... print(f"私には {animal_type} がいます。") -... print(f"名前は {pet_name} で、飼い主は {owner_name} です。") -... ->>> # 位置引数のみで呼び出し ->>> describe_pet("ハムスター", "ジャンボ") -私には ハムスター がいます。 -名前は ジャンボ で、飼い主は Taro です。 ->>> # キーワード引数で呼び出し(順序を逆にしてもOK) ->>> describe_pet(pet_name="ポチ", animal_type="犬") -私には 犬 がいます。 -名前は ポチ で、飼い主は Taro です。 ->>> # デフォルト引数を持つ引数を指定して呼び出し ->>> describe_pet("猫", "ミケ", "Hanako") -私には 猫 がいます。 -名前は ミケ で、飼い主は Hanako です。 -``` - -**注意点:** デフォルト引数を持つ引数は、持たない引数の後に定義する必要があります。 - -## 可変長引数 (*args, **kwargs) - -関数の引数の数が可変である場合に対応するための仕組みです。型アノテーションを使う場合は、`typing`モジュールから`Any`などをインポートすると便利です。 - -### `*args` - -任意の数の**位置引数**をタプルとして受け取ります。型アノテーションでは `*args: 型` のように表現します。 - -```python-repl:10 ->>> def sum_all(*numbers: int) -> int: -... print(f"受け取ったタプル: {numbers}") -... total = 0 -... for num in numbers: -... total += num -... return total -... ->>> print(sum_all(1, 2, 3)) -受け取ったタプル: (1, 2, 3) -6 ->>> print(sum_all(10, 20, 30, 40, 50)) -受け取ったタプル: (10, 20, 30, 40, 50) -150 -``` - -### `**kwargs` - -任意の数の**キーワード引数**を辞書として受け取ります。型アノテーションでは `**kwargs: 型` のように表現します。どのような型の値も受け付ける場合は `Any` を使います。 - -```python-repl:11 ->>> from typing import Any ->>> def print_profile(**user_info: Any) -> None: -... print(f"受け取った辞書: {user_info}") -... for key, value in user_info.items(): -... print(f"{key}: {value}") -... ->>> print_profile(name="Sato", age=28, city="Tokyo") -受け取った辞書: {'name': 'Sato', 'age': 28, 'city': 'Tokyo'} -name: Sato -age: 28 -city: Tokyo -``` - -## ラムダ式(Lambda expressions) - -`lambda`キーワードを使うと、名前のない小さな**無名関数**を定義できます。 - -構文: `lambda 引数: 式` - -```python-repl:12 ->>> # 通常の関数で2つの数を足す ->>> def add(x: int, y: int) -> int: -... return x + y -... ->>> # ラムダ式で同じ処理を定義 ->>> add_lambda = lambda x, y: x + y ->>> print(add_lambda(3, 5)) -8 ->>> # sorted関数のキーとして利用する例 ->>> students = [('Taro', 80), ('Jiro', 95), ('Saburo', 75)] ->>> # 成績(タプルの2番目の要素)でソートする ->>> sorted_students = sorted(students, key=lambda student: student[1], reverse=True) ->>> print(sorted_students) -[('Jiro', 95), ('Taro', 80), ('Saburo', 75)] -``` - -## この章のまとめ - -この章では、Pythonの制御構文と関数の基本を学びました。他の言語の経験がある方にとって、特に以下の点はPythonの特徴として重要です。 - - * **インデントが構文の一部**: 波括弧`{}`の代わりにインデントでコードブロックを定義するため、自然と誰が書いても読みやすいコードスタイルになります。 - * **`for`ループはイテラブルを巡る**: `for item in collection:` の形が基本です。インデックスが必要な場合は、`for i, item in enumerate(collection):` のように`enumerate()`を使うのがPythonらしい書き方です。 - * **柔軟な関数引数**: **キーワード引数**、**デフォルト引数値**、そして**可変長引数 (`*args`, `**kwargs`)** を使いこなすことで、非常に柔軟で再利用性の高い関数を作成できます。 - * **型アノテーション**: 引数や返り値に型ヒントを付けることで、関数の意図が明確になり、コードの信頼性が向上します。 - * **ラムダ式**: ちょっとした処理をその場で関数として渡したい場合に、`lambda`はコードを簡潔に保つのに役立ちます。 - -これらの機能を理解し使いこなすことが、より効率的で「Pythonic(パイソニック)」なコードを書くための第一歩となります。 - -### 練習問題1: 偶数とそのインデックスの発見 - -数値のリストが与えられたとき、そのリストに含まれる**偶数**とその**インデックス(位置番号)**だけを出力するプログラムを書いてください。 - -**ヒント:** - - * `for`ループと`enumerate()`を組み合わせます。 - * 数値が偶数かどうかは、`%`(剰余)演算子を使って、2で割った余りが0になるかで判定できます (`number % 2 == 0`)。 - -```python:practice4_1.py -numbers: list[int] = [8, 15, 22, 37, 40, 51, 68] - -``` - -```python-exec:practice4_1.py -(出力例) -インデックス: 0, 値: 8 -インデックス: 2, 値: 22 -インデックス: 4, 値: 40 -インデックス: 6, 値: 68 -``` - -### 練習問題2: ユーザープロフィール作成関数 - -ユーザーのプロフィール情報を出力する関数 `create_profile` を作成してください。引数と返り値には型アノテーションを付けてください。 - -**要件:** - -1. `name`(名前)は`str`型で、必須の引数とします。 -2. `age`(年齢)と `city`(都市)は`str`型で、キーワード引数として任意に受け取れるようにします。もし指定されなかった場合は、年齢は「秘密」、都市は「不明」と表示されるようにしてください。 -3. この関数は値を返さないものとします。 -4. 関数を呼び出し、異なるパターンでプロフィールが出力されることを確認してください。 - -**ヒント:** - - * `age`と`city`にはデフォルト引数値を設定します。 - * 値を返さない関数の返り値の型アノテーションは `-> None` です。 - -```python:practice4_2.py -def create_profile( -``` - -```python-exec:practice4_2.py -(出力例) ---- プロフィール --- -名前: Tanaka -年齢: 秘密 -都市: 不明 --------------------- ---- プロフィール --- -名前: Sato -年齢: 32 -都市: Osaka --------------------- -``` diff --git a/public/docs/python-5.md b/public/docs/python-5.md deleted file mode 100644 index d5f8744..0000000 --- a/public/docs/python-5.md +++ /dev/null @@ -1,333 +0,0 @@ -# 第5章: コードの整理術:モジュールとパッケージ - -プログラミングを進めていくと、コードは必然的に長くなり、一つのファイルで管理するのが難しくなってきます。機能ごとにファイルを分割し、再利用しやすく、メンテナンスしやすい構造にすることが、効率的な開発の鍵となります。この章では、Pythonでコードを整理するための**モジュール**と**パッケージ**という仕組み、そしてPythonの強力な武器である**標準ライブラリ**の活用方法について学びます。 - -## モジュール:コードを部品化する - -Pythonでは、**1つの `.py` ファイルが1つのモジュール**として扱われます。モジュールを使うことで、関連する関数やクラスを一つのファイルにまとめ、他のプログラムから再利用可能な「部品」として扱うことができます。これは、他の言語におけるライブラリやソースファイルのインポート機能に似ています。 - -### `import`文の基本 - -モジュールを利用するには `import` 文を使います。Pythonには多くの便利なモジュールが標準で用意されています(これらを**標準ライブラリ**と呼びます)。例えば、数学的な計算を行う `math` モジュールを使ってみましょう。 - -```python-repl:1 ->>> # mathモジュールをインポート ->>> import math ->>> # mathモジュール内の変数や関数を利用する ->>> print(math.pi) # 円周率π -3.141592653589793 ->>> print(math.sqrt(16)) # 16の平方根 -4.0 -``` - -出力: - -``` -3.141592653589793 -4.0 -``` - -毎回 `math.` と書くのが面倒な場合は、いくつかの書き方があります。 - - * **`from ... import ...`**: モジュールから特定の関数や変数だけを取り込む - - ```python-repl:2 - >>> from math import pi, sqrt - >>> - >>> print(pi) # 直接piを参照できる - 3.141592653589793 - >>> print(sqrt(16)) # 直接sqrtを参照できる - 4.0 - ``` - - * **`as` (別名)**: モジュールに別名をつけて利用する - - ```python-repl:3 - >>> import math as m - >>> - >>> print(m.pi) - 3.141592653589793 - >>> print(m.sqrt(16)) - 4.0 - ``` - -> **注意** ⚠️: `from math import *` のようにアスタリスク (`*`) を使うと、そのモジュールのすべての名前(関数、変数、クラス)が現在の名前空間にインポートされます。一見便利に見えますが、どの名前がどこから来たのか分からなくなり、意図しない名前の上書きを引き起こす可能性があるため、**特別な理由がない限り避けるべき**です。 - -### 自作モジュールの作成と利用 - -自分でモジュールを作成するのも簡単です。関連する関数をまとめた `.py` ファイルを作成するだけです。ここからは複数のファイルが必要になるため、再びスクリプトファイルで説明します。 - -1. **`utils.py` の作成**: - まず、便利な関数をまとめた `utils.py` というファイルを作成します。 - - ```python:utils.py - def say_hello(name): - """指定された名前で挨拶を返す""" - return f"Hello, {name}!" - - def get_list_average(numbers): - """数値リストの平均を計算する""" - if not numbers: - return 0 - return sum(numbers) / len(numbers) - - # このファイルが直接実行された場合にのみ以下のコードを実行 - if __name__ == "__main__": - print("This is a utility module.") - print(say_hello("World")) - avg = get_list_average([10, 20, 30]) - print(f"Average: {avg}") - ``` - - ```python-exec:utils.py - This is a utility module. - Hello, World! - Average: 20.0 - ``` - - > **`if __name__ == "__main__":` の重要性** - > この記述はPythonの定型句です。 - - > * スクリプトが**直接実行された**場合、そのスクリプトの `__name__` という特殊変数は `"__main__"` になります。 - > * スクリプトが**モジュールとして `import` された**場合、`__name__` はファイル名(この場合は `"utils"`)になります。 - > これにより、モジュールとしてインポートされた際には実行したくないテストコードやデモコードを記述することができます。他言語経験者にとっては、プログラムの「エントリーポイント」を定義する `main` 関数のような役割と考えると分かりやすいでしょう。 - -2. **`main.py` からの利用**: - 次に、`utils.py` と同じディレクトリに `main.py` を作成し、`utils` モジュールをインポートして使います。 - - ```python:main.py - # 自作のutilsモジュールをインポート - import utils - - greeting = utils.say_hello("Alice") - print(greeting) - - scores = [88, 92, 75, 100] - average_score = utils.get_list_average(scores) - print(f"Your average score is: {average_score}") - ``` - - ```python-exec:main.py - Hello, Alice! - Your average score is: 88.75 - ``` - -このように、機能ごとにファイルを分割することで、コードの見通しが良くなり、再利用も簡単になります。 - - -## パッケージ:モジュールをまとめる - -プロジェクトがさらに大きくなると、多数のモジュールを管理する必要が出てきます。**パッケージ**は、複数のモジュールをディレクトリ構造で階層的に管理するための仕組みです。 - -### パッケージの概念と `__init__.py` - -パッケージは、簡単に言うと**モジュールが入ったディレクトリ**です。Pythonにそのディレクトリをパッケージとして認識させるために、`__init__.py` という名前のファイルを置きます(近年のPythonでは必須ではありませんが、互換性や明示性のために置くのが一般的です)。 - -以下のようなディレクトリ構造を考えてみましょう。 - -``` -my_project/ -├── main.py -└── my_app/ - ├── __init__.py - ├── models.py - └── services.py -``` - - * `my_app` がパッケージ名になります。 - * `__init__.py` は空でも構いません。このファイルが存在することで、`my_app` ディレクトリは単なるフォルダではなく、Pythonのパッケージとして扱われます。 - * `models.py` と `services.py` が、`my_app` パッケージに含まれるモジュールです。 - -`main.py` からこれらのモジュールをインポートするには、`パッケージ名.モジュール名` のように記述します。 - -```python -# パッケージ内のモジュールをインポート -from my_app import services - -# servicesモジュール内の関数を実行 (仮の関数) -# user_data = services.fetch_user_data(user_id=123) -# print(user_data) -``` - -`__init__.py` には、パッケージがインポートされた際の初期化処理を記述することもできます。例えば、特定のモジュールから関数をパッケージのトップレベルにインポートしておくと、利用側でより短い記述でアクセスできるようになります。 - -```python -# my_app/__init__.py - -# servicesモジュールからfetch_user_data関数をインポート -from .services import fetch_user_data - -print("my_app package has been initialized.") -``` - -このようにしておくと、`main.py` から以下のように直接関数をインポートできます。 - -```python -# main.py - -# __init__.pyで設定したおかげで、短いパスでインポートできる -from my_app import fetch_user_data - -user_data = fetch_user_data(user_id=123) -print(user_data) -``` - -## 標準ライブラリ:Pythonに備わった強力なツール群 - -Pythonの大きな魅力の一つは、その「**バッテリー同梱 (Batteries Included)**」という哲学です。これは、Pythonをインストールしただけで、追加のインストールなしにすぐに使える膨大で強力な**標準ライブラリ**が付属していることを意味します。 - -### 標準ライブラリの探索 - -どんなライブラリがあるかを知るには、公式ドキュメントが最も信頼できます。 - - * [**The Python Standard Library — Python 3.x documentation**](https://docs.python.org/3/library/index.html) - -また、REPLの `help()` や `dir()` を使うと、モジュールの内容を簡単に確認できます。 - -```python-repl:4 ->>> import datetime ->>> # datetimeモジュールが持つ属性や関数のリストを表示 ->>> dir(datetime) -['MAXYEAR', 'MINYEAR', '__all__', '__builtins__', ..., 'date', 'datetime', 'time', 'timedelta', 'timezone', 'tzinfo'] ->>> ->>> # dateクラスのヘルプドキュメントを表示 ->>> help(datetime.date) -Help on class date in module datetime: - -class date(builtins.object) - | date(year, month, day) --> a date object - | - | Methods defined here: -(ヘルプ情報が続く) ... -``` - -### よく使われる標準ライブラリの例 - -ここでは、日常的によく使われる標準ライブラリをいくつか紹介します。 - - * **`os`**: オペレーティングシステムと対話するための機能を提供します。ファイルやディレクトリの操作、環境変数の取得などができます。 - - ```python-repl:5 - >>> import os - >>> # カレントディレクトリのファイル一覧を取得 - >>> os.listdir('.') - ['hello.py', 'utils.py', 'main.py'] - >>> # OSに依存しない安全なパスの結合 - >>> os.path.join('data', 'file.txt') # Windowsなら 'data\\file.txt' - 'data/file.txt' - ``` - - * **`sys`**: Pythonインタプリタ自体を制御するための機能を提供します。コマンドライン引数の取得や、Pythonの検索パスの確認などができます。 - - ```python-repl:6 - >>> import sys - >>> # Pythonのバージョンを表示 - >>> sys.version # 環境により異なります - '3.11.4 (main, Jun 7 2023, 10:13:09) [GCC 12.3.0]' - ``` - - * **`datetime`**: 日付や時刻を扱うための機能を提供します。 - - ```python-repl:7 - >>> import datetime - >>> # 現在の日時を取得 (実行時刻による) - >>> now = datetime.datetime.now() - >>> print(now) - 2025-08-12 18:26:06.123456 - >>> # 日時をフォーマットして文字列にする - >>> now.strftime('%Y-%m-%d %H:%M:%S') - '2025-08-12 18:26:06' - ``` - - * **`json`**: Web APIなどで広く使われているデータ形式であるJSONを扱うための機能を提供します。 - - ```python-repl:8 - >>> import json - >>> # Pythonの辞書型データ - >>> user = {"id": 1, "name": "Ken", "email": "ken@example.com"} - >>> # 辞書型をJSON形式の文字列に変換 (dumps: dump string) - >>> json_string = json.dumps(user, indent=2) - >>> print(json_string) - { - "id": 1, - "name": "Ken", - "email": "ken@example.com" - } - >>> # JSON形式の文字列をPythonの辞書型に変換 (loads: load string) - >>> loaded_user = json.loads(json_string) - >>> loaded_user['name'] - 'Ken' - ``` - -これらの他にも、正規表現を扱う `re`、乱数を生成する `random`、HTTPリクエストを送信する `urllib.request` など、数え切れないほどの便利なモジュールが標準で提供されています。何かを実装したいと思ったら、まずは「Python 標準ライブラリ 〇〇」で検索してみると、車輪の再発明を防ぐことができます。 - -## この章のまとめ - -この章では、Pythonのコードが複雑になるにつれて重要性を増す、整理と再利用のテクニックを学びました。ここで学んだ概念は、小さなスクリプトから大規模なアプリケーションまで、あらゆるレベルのPythonプログラミングで役立ちます。 - - * **モジュール**: 1つの `.py` ファイルは1つの**モジュール**です。関連する関数やクラスをモジュールにまとめることで、コードを論理的な単位に分割できます。他のファイルからは `import` 文を使ってその機能を再利用できます。 - * **パッケージ**: **パッケージ**は、複数のモジュールを階層的なディレクトリ構造で管理する仕組みです。これにより、大規模なプロジェクトでも名前の衝突を避け、関連するコードをまとめて整理することができます。 - * **標準ライブラリ**: Pythonには「**バッテリー同梱**」という思想があり、`datetime` (日時)、`os` (OS機能)、`json` (データ形式) など、すぐに使える便利なモジュールが豊富に揃っています。これらを活用することで、開発を大幅に効率化できます。 - -### 練習問題1: 計算モジュールを作ろう 🔢 - -四則演算を行うための自作モジュールを作成し、別のファイルから利用してみましょう。 - -1. `calculator.py` というファイルを作成し、以下の4つの関数を定義してください。 - * `add(a, b)`: aとbの和を返す - * `subtract(a, b)`: aとbの差を返す - * `multiply(a, b)`: aとbの積を返す - * `divide(a, b)`: aをbで割った商を返す。ただし、`b` が `0` の場合は「ゼロで割ることはできません」という文字列を返すようにしてください。 -2. `practice5_1.py` というファイルを作成し、作成した `calculator` モジュールをインポートします。 -3. `practice5_1.py` の中で、`calculator` モジュールの各関数を少なくとも1回ずつ呼び出し、結果を `print` 関数で表示してください。 - -```python:calculator.py -def add(a, b): - -``` - -```python:practice5_1.py -``` - -```python-exec:practice5_1.py -(出力例) -10 + 5 = 15 -10 - 5 = 5 -10 * 5 = 50 -10 / 2 = 5.0 -10 / 0 = ゼロで割ることはできません -``` - -### 練習問題2:日報データをJSONで作成しよう 📝 - -標準ライブラリの `datetime` と `json` を使って、簡単な日報データを作成するプログラムを書いてみましょう。 - -1. Pythonスクリプトを作成します。 -2. `datetime` モジュールを使って、**現在の日付**を `YYYY-MM-DD` 形式の文字列として取得します。 -3. 以下の情報を含むPythonの辞書を作成します。 - * `author`: あなたの名前 (文字列) - * `date`: 手順2で取得した日付文字列 - * `tasks`: その日に行ったタスクのリスト (例: `["会議", "資料作成", "メール返信"]`) -4. `json` モジュールを使い、手順3で作成した辞書を人間が読みやすい形式 (インデント付き) のJSON文字列に変換します。 -5. 変換後のJSON文字列を `print` 関数で表示してください。 - -**ヒント**: `datetime.datetime.now()` で現在時刻を取得し、`.strftime('%Y-%m-%d')` メソッドで日付をフォーマットできます。`json.dumps()` の `indent` 引数を指定すると、出力がきれになります。 - -```python:practice5_2.py -import datetime -import json -``` - -```python-exec:practice5_2.py -(出力例) -{ - "author": "山田 太郎", - "date": "2025-08-17", - "tasks": [ - "Pythonのモジュール学習", - "練習問題の実装", - "チームミーティング" - ], - "status": "完了" -} -``` diff --git a/public/docs/python-6.md b/public/docs/python-6.md deleted file mode 100644 index c1c3b22..0000000 --- a/public/docs/python-6.md +++ /dev/null @@ -1,312 +0,0 @@ -# 第6章: Pythonicなオブジェクト指向プログラミング - -Pythonのオブジェクト指向プログラミング(OOP)は、他の言語と考え方は似ていますが、よりシンプルで柔軟な構文を持っています。この章では、クラスの定義から継承、そしてPython特有の「マジックメソッド」まで、その基本を学びます。 - -## `class`の定義とインスタンス化 - -Pythonでは、`class`キーワードを使ってクラスを定義します。JavaやC++のように波括弧`{}`は使わず、インデントでブロックを示します。非常にシンプルです。 - -クラスを定義したら、関数を呼び出すように`クラス名()`と書くことで、そのクラスの**インスタンス**(オブジェクト)を生成できます。 - -```python:dog1.py -class Dog: - pass # passは何もしないことを示す文 - -# Dogクラスのインスタンスを作成 -my_dog = Dog() - -print(my_dog) -``` - -```python-exec:dog1.py -<__main__.Dog object at 0x10e85a4d0> -``` - -## コンストラクタ (`__init__`) と `self` - -Pythonのクラスでは、`__init__`という名前の特殊なメソッドがコンストラクタの役割を果たします。このメソッドは、クラスがインスタンス化される際に自動的に呼び出されます。 - -メソッドの最初の引数には`self`を書くのが慣習です。これはインスタンス自身への参照であり、JavaやC++の`this`に相当します。ただし、Pythonでは`self`を明示的に引数として記述する必要があります。 - -```python:dog2.py -class Dog: - # インスタンス生成時に呼び出されるコンストラクタ - def __init__(self, name, breed): - print(f"{name}という名前の犬が作成されました。") - # self.変数名 の形でインスタンス変数を定義 - self.name = name - self.breed = breed - -# インスタンス化する際に__init__のself以外の引数を渡すと、 -# `__init__`メソッドが `self`に`my_dog`インスタンス、`name`に`"ポチ"`、`breed`に`"柴犬"`を受け取って実行される -my_dog = Dog("ポチ", "柴犬") - -# インスタンス変数にアクセス -print(f"名前: {my_dog.name}") -print(f"犬種: {my_dog.breed}") -``` - -```python-exec:dog2.py -ポチという名前の犬が作成されました。 -名前: ポチ -犬種: 柴犬 -``` - -## インスタンス変数とクラス変数 - -Pythonのクラスには、2種類の変数があります。 - - * **インスタンス変数**: `self.変数名`のように`__init__`内などで定義され、**各インスタンスに固有**の値を持ちます。上の例の`name`や`age`がこれにあたります。 - * **クラス変数**: クラス定義の直下に書かれ、そのクラスから作られた**全てのインスタンスで共有**されます。 - -```python:dog3.py -class Dog: - # このクラスから作られるインスタンス全てで共有されるクラス変数 - species = "イヌ科" - - def __init__(self, name): - # このインスタンス固有のインスタンス変数 - self.name = name - -dog1 = Dog("ポチ") -dog2 = Dog("ハチ") - -# インスタンス変数へのアクセス -print(f"{dog1.name}も{dog2.name}も、") - -# クラス変数へのアクセス (インスタンス経由でもクラス経由でも可能) -print(f"種は同じく {dog1.species} です。") -print(f"Dogクラスの種は {Dog.species} です。") - -# クラス変数を変更すると、全てのインスタンスに影響が及ぶ -Dog.species = "ネコ科" -print(f"{dog1.name}の種は {dog1.species}") -print(f"{dog2.name}の種は {dog2.species}") -``` - -```python-exec:dog3.py -ポチもハチも、 -種は同じく イヌ科 です。 -Dogクラスの種は イヌ科 です。 -ポチの種は ネコ科 -ハチの種は ネコ科 -``` - -## メソッドの定義 - -クラス内で定義される関数をメソッドと呼びます。インスタンスのデータ(インスタンス変数)を操作するために使用します。 -メソッドを定義する際も、最初の引数には必ず`self`を指定する必要があります。これにより、メソッド内から`self`を通じてインスタンス変数にアクセスできます。 - -```python:dog4.py -class Dog: - def __init__(self, name): - self.name = name - - # barkというメソッドを定義 - # selfを介してインスタンス変数nameにアクセスする - def bark(self): - return f"{self.name}: ワン!" - -my_dog = Dog("ポチ") -print(my_dog.bark()) # メソッドの呼び出し -``` - -```python-exec:dog4.py -ポチ: ワン! -``` - -### クラスメンバーの型アノテーション - -型安全性を高めるために、クラス変数やインスタンス変数にも型アノテーション(型ヒント)を付けることができます。 - - * **クラス変数**: `変数名: 型 = 値` のように記述します。 - * **インスタンス変数**: `__init__`内で `self.変数名: 型 = 値` のように記述するか、クラス直下で `変数名: 型` と宣言だけしておくこともできます。 - -```python:dog5.py -class Dog: - # クラス変数の型アノテーション - species: str = "イヌ科" - - # インスタンス変数の型を宣言 - name: str - age: int - - def __init__(self, name: str, age: int): - self.name = name - self.age = age - - # メソッドの戻り値の型アノテーション - def bark(self) -> str: - return f"{self.name}: ワン!" - -my_dog = Dog("ポチ", 3) -``` - -```python-exec:dog5.py -``` - -## 継承 - -あるクラスの機能を引き継いだ新しいクラスを作成することを継承と呼びます。Pythonでは、クラス定義の際に`()`内に親クラス(基底クラス)を指定することで継承を行います。 - -子クラス(派生クラス)は親クラスのメソッドや変数を全て利用でき、必要に応じて上書き(オーバーライド)することも可能です。親クラスのメソッドを呼び出したい場合は`super()`を使います。 - -```python:dog6.py -# 親クラス -class Animal: - def __init__(self, name: str): - print("Animalの__init__が呼ばれました") - self.name = name - - def eat(self) -> str: - return f"{self.name}は食事中です。" - - def speak(self) -> str: - return "..." - -# Animalクラスを継承した子クラス -class Dog(Animal): - def __init__(self, name: str, breed: str): - print("Dogの__init__が呼ばれました") - # super()で親クラスの__init__を呼び出し、nameを初期化 - super().__init__(name) - self.breed = breed # Dogクラス独自のインスタンス変数を追加 - - # 親のメソッドをオーバーライド - def speak(self) -> str: - return f"{self.name}: ワン!" - - -dog = Dog("ポチ", "柴犬") - -# 親クラスのメソッドも使える -print(dog.eat()) -# オーバーライドしたメソッドが呼ばれる -print(dog.speak()) -``` - -```python-exec:dog6.py -Dogの__init__が呼ばれました -Animalの__init__が呼ばれました -ポチは食事中です。 -ポチ: ワン! -``` - -## 基本的なマジックメソッド (`__str__`, `__repr__`) - -`__init__`のように、アンダースコア2つで囲まれた特殊なメソッドを**マジックメソッド**(または**ダンダーメソッド**)と呼びます。これらを定義することで、Pythonの組み込み関数の挙動をカスタマイズできます。 - - * `__str__(self)` - * `print()`関数や`str()`でオブジェクトを文字列に変換する際に呼び出されます。 - * 目的は、**人間にとって読みやすい**、非公式な文字列表現を返すことです。 - * `__repr__(self)` - * `repr()`関数で呼び出されるほか、`__str__`が定義されていない場合の`print()`や、インタラクティブシェルでオブジェクトを評価した際に使われます。 - * 目的は、**曖昧さのない**、公式な文字列表現を返すことです。理想的には、その文字列を評価すると同じオブジェクトを再作成できるような表現(例: `MyClass(arg1=1, arg2='B')`)が望ましいです。 - -```python:dog7.py -class Dog: - def __init__(self, name, age): - self.name = name - self.age = age - - # print()で表示したときの振る舞いを定義 - def __str__(self): - return f"名前: {self.name}, 年齢: {self.age}" - - # REPLでの評価やrepr()での振る舞いを定義 - def __repr__(self): - return f"Dog(name='{self.name}', age={self.age})" - -dog = Dog("ポチ", 3) - -# print()は__str__を呼び出す -print(dog) - -# str()も__str__を呼び出す -print(str(dog)) - -# repr()は__repr__を呼び出す -print(repr(dog)) - -# REPLやJupyter Notebookなどで変数をそのまま評価すると__repr__が表示される -# >>> dog -# Dog(name='ポチ', age=3) -``` - -```python-exec:dog7.py -名前: ポチ, 年齢: 3 -名前: ポチ, 年齢: 3 -Dog(name='ポチ', age=3) -``` - -## この章のまとめ - - * **クラス定義**: `class ClassName:` で定義する。 - * **コンストラクタ**: `__init__(self, ...)` メソッドで、インスタンス化の際に初期化処理を行う。 - * **`self`**: インスタンス自身を指す参照。メソッドの第一引数として必ず記述する。 - * **変数**: インスタンスごとに持つ**インスタンス変数**と、全インスタンスで共有する**クラス変数**がある。 - * **継承**: `class Child(Parent):` でクラスの機能を引き継ぐ。親のメソッドは`super()`で呼び出せる。 - * **マジックメソッド**: `__str__`や`__repr__`などを定義することで、オブジェクトの振る舞いをカスタマイズできる。 - -## この章のまとめ - -この章では、Pythonにおけるオブジェクト指向プログラミングの基本を学びました。 - - * **クラスとインスタンス**: `class`キーワードでクラスという「設計図」を定義し、`クラス名()`でインスタンスという「実体」を作成します。 - * **`__init__`と`self`**: `__init__`はインスタンス化の際に呼ばれるコンストラクタです。第一引数の`self`はインスタンス自身を指し、`self.変数名`の形でインスタンスごとにユニークな**インスタンス変数**を定義します。 - * **クラス変数**: クラス直下に定義され、全てのインスタンスで共有される変数です。 - * **メソッド**: クラス内で定義される関数で、インスタンスの振る舞いを表します。メソッドの第一引数も必ず`self`です。 - * **継承**: `class 子クラス(親クラス):`と書くことで、親クラスの機能を引き継いだ新しいクラスを作成できます。`super()`を使うことで、親クラスのメソッドを呼び出せます。 - * **マジックメソッド**: `__str__`や`__repr__`のように`__`で囲まれた特殊なメソッドで、`print()`などの組み込み関数の挙動をカスタマイズできます。 - -PythonのOOPは、JavaやC++に比べてシンプルで直感的な構文が特徴です。しかし、その裏側にある「すべてがオブジェクトである」という思想は一貫しており、非常に強力なプログラミングパラダイムです。 - -### 練習問題1: `Book`クラスの作成 - -書籍の情報を管理する`Book`クラスを作成してください。 - -**要件:** - -1. インスタンス化する際に、`title`(タイトル)と`author`(著者)を引数で受け取る。 -2. `info()`というメソッドを持ち、呼び出すと`「{タイトル}」- {著者}`という形式の文字列を返す。 -3. `print()`でインスタンスを直接表示した際に、`info()`メソッドと同じ文字列が表示されるようにする。 - -```python:practice6_1.py -class Book: - - -if __name__ == "__main__": - harry_potter = Book("ハリー・ポッターと賢者の石", "J.K. ローリング") - print(harry_potter.info()) - print(harry_potter) -``` - -```python-exec:practice6_1.py -「ハリー・ポッターと賢者の石」- J.K. ローリング -「ハリー・ポッターと賢者の石」- J.K. ローリング -``` - -#### 練習問題2: 継承を使った`EBook`クラスの作成 - -問題1で作成した`Book`クラスを継承して、電子書籍を表す`EBook`クラスを作成してください。 - -**要件:** - -1. `Book`クラスを継承する。 -2. インスタンス化の際に、`title`、`author`に加えて`file_size`(ファイルサイズ、MB単位)も引数で受け取る。 -3. `info()`メソッドを**オーバーライド**し、呼び出すと`「{タイトル}」- {著者} (ファイルサイズ: {file_size}MB)`という形式の文字列を返すように変更する。 - -```python:practice6_2.py -from practice6_1 import Book - -class EBook(Book): - - -if __name__ == "__main__": - ebook_version = EBook("Python実践入門", "掌田 津耶乃", 24) - print(ebook_version.info()) -``` - -```python-exec:practice6_2.py -「Python実践入門」- 掌田 津耶乃 (ファイルサイズ: 24MB) -``` diff --git a/public/docs/python-7.md b/public/docs/python-7.md deleted file mode 100644 index ed69636..0000000 --- a/public/docs/python-7.md +++ /dev/null @@ -1,277 +0,0 @@ -# 第7章: ファイルの入出力とコンテキストマネージャ - -この章では、テキストファイルやJSON、CSVファイルの読み書きといった、実践的なファイル操作を学びます。特に、リソース管理を安全かつ簡潔に行うための **`with`** 文(コンテキストマネージャ)は、Pythonプログラミングにおいて必須の知識です。 - -## `open()`関数によるファイルのオープン - -Pythonでファイルを操作するには、まず組み込み関数の **`open()`** を使ってファイルオブジェクトを取得します。`open()` は少なくとも2つの引数、ファイルパスとモードを取ります。 - - * **ファイルパス**: 操作したいファイルへのパス(例: `'data.txt'`)。 - * **モード**: ファイルをどのように開くかを指定する文字列。 - * `'r'`: 読み込み専用(デフォルト) - * `'w'`: 書き込み専用(ファイルが存在すれば上書き) - * `'a'`: 追記(ファイルの末尾に書き足す) - * `'x'`: 新規作成して書き込み(ファイルが存在するとエラー) - * `'+'` を付けると読み書き両用になります(例: `'r+'`, `'w+'`)。 - * `'b'` を付けるとバイナリモードになります(例: `'rb'`, `'wb'`)。 - -```python-repl:1 ->>> # 'w' モードでファイルを開く(または新規作成する) ->>> f = open('spam.txt', 'w', encoding='utf-8') ->>> f -<_io.TextIOWrapper name='spam.txt' mode='w' encoding='utf-8'> ->>> # ファイルを使い終わったら必ず閉じる ->>> f.close() -``` - -**`encoding='utf-8'`** は、特に日本語のような非ASCII文字を扱う際に重要です。文字化けを防ぐため、テキストファイルを扱う際はエンコーディングを明示的に指定することを強く推奨します。ファイルを閉じる **`close()`** メソッドを呼び出すまで、ファイルリソースはプログラムによって確保されたままになります。 - - -## テキストファイルの読み書き - -ファイルオブジェクトのメソッドを使って、ファイルの内容を操作します。 - -### 書き込み - -**`write()`** メソッドは、文字列をファイルに書き込みます。このメソッドは書き込んだ文字数を返します。 - -```python-repl:2 ->>> f = open('test.txt', 'w', encoding='utf-8') ->>> f.write('こんにちは、世界!\n') -9 ->>> f.write('これは2行目です。\n') -9 ->>> f.close() -``` - -`write()` は自動的には改行しないため、必要であれば自分で改行コード `\n` を追加します。 - -```text-readonly:test.txt -こんにちは、世界! -これは2行目です。 -``` - -### 読み込み - -ファイルからデータを読み込むには、いくつかの方法があります。 - - * **`read()`**: ファイルの内容全体を一つの文字列として読み込みます。 - * **`readline()`**: ファイルから1行だけを読み込み、文字列として返します。 - * **`readlines()`**: ファイルのすべての行を読み込み、各行を要素とするリストで返します。 - -```python-repl:3 ->>> # 先ほど書き込んだファイルを読み込む ->>> f = open('test.txt', 'r', encoding='utf-8') ->>> content = f.read() ->>> print(content) -こんにちは、世界! -これは2行目です。 - ->>> f.close() - ->>> # readline() を使って1行ずつ読む ->>> f = open('test.txt', 'r', encoding='utf-8') ->>> f.readline() -'こんにちは、世界!\n' ->>> f.readline() -'これは2行目です。\n' ->>> f.readline() # ファイルの終端に達すると空文字列を返す -'' ->>> f.close() -``` - - -## `with`文による安全なファイル操作(コンテキストマネージャ) - -ファイルを `open()` したら `close()` する必要がありますが、処理中に例外が発生すると `close()` が呼ばれない可能性があります。これを確実に、そして簡潔に書く方法が **`with`** 文です。 - -**`with`** 文のブロックを抜けると、ファイルオブジェクトは自動的に `close()` されます。エラーが発生した場合でも同様です。これは「コンテキストマネージャ」という仕組みによって実現されており、ファイル操作の標準的な方法です。 - -```python-repl:4 ->>> # with文を使った書き込み ->>> with open('spam.txt', 'w', encoding='utf-8') as f: -... f.write('withブロックを使っています。\n') -... f.write('ブロックを抜けると自動で閉じられます。\n') -... ->>> # ブロックの外ではファイルは閉じている ->>> f.closed -True - ->>> # with文を使った読み込み ->>> with open('spam.txt', 'r', encoding='utf-8') as f: -... data = f.read() -... ->>> print(data) -withブロックを使っています。 -ブロックを抜けると自動で閉じられます。 - -``` - -このように、`with` 文を使えば `close()` の呼び出しを忘れる心配がなく、コードもすっきりします。今後は常に `with` 文を使ってファイルを扱うようにしましょう。 - -```text-readonly:spam.txt -withブロックを使っています。 -ブロックを抜けると自動で閉じられます。 -``` - -## `json`モジュールを使ったJSONの操作 - -**JSON (JavaScript Object Notation)** は、データ交換フォーマットとして広く使われています。Pythonの標準ライブラリである **`json`** モジュールを使うと、Pythonのオブジェクト(辞書やリストなど)をJSON形式のデータに、またはその逆に変換できます。 - - * **`json.dump(obj, fp)`**: Pythonオブジェクト `obj` をJSON形式でファイルオブジェクト `fp` に書き込みます。 - * **`json.load(fp)`**: JSON形式のファイルオブジェクト `fp` からデータを読み込み、Pythonオブジェクトに変換します。 - - - -```python-repl:5 ->>> import json - ->>> # 書き込むデータ(Pythonの辞書) ->>> data = { -... "name": "Taro Yamada", -... "age": 30, -... "is_student": False, -... "courses": ["Python", "Machine Learning"] -... } - ->>> # with文を使ってJSONファイルに書き込む ->>> with open('user.json', 'w', encoding='utf-8') as f: -... # ensure_ascii=Falseで日本語をそのまま出力 -... json.dump(data, f, indent=4, ensure_ascii=False) -... - ->>> # JSONファイルから読み込む ->>> with open('user.json', 'r', encoding='utf-8') as f: -... loaded_data = json.load(f) -... ->>> loaded_data -{'name': 'Taro Yamada', 'age': 30, 'is_student': False, 'courses': ['Python', 'Machine Learning']} ->>> loaded_data['name'] -'Taro Yamada' -``` - -`json.dump()` の `indent=4` は、人間が読みやすいように4スペースのインデントを付けて出力するオプションです。`ensure_ascii=False` は、日本語などの非ASCII文字をそのままの文字で出力するために指定します。 - -```json-readonly:user.json -{ - "name": "Taro Yamada", - "age": 30, - "is_student": false, - "courses": [ - "Python", - "Machine Learning" - ] -} -``` - -## `csv`モジュールを使ったCSVの操作 - -**CSV (Comma-Separated Values)** は、スプレッドシートやデータベースでよく使われる表形式のデータを保存するためのフォーマットです。**`csv`** モジュールを使うと、CSVファイルの読み書きが簡単になります。 - -### CSVファイルへの書き込み - -**`csv.writer()`** を使ってライターオブジェクトを作成し、**`writerow()`** (1行) や **`writerows()`** (複数行) メソッドでデータを書き込みます。 - -```python-repl:6 ->>> import csv - ->>> # 書き込むデータ(リストのリスト) ->>> data_to_write = [ -... ["ID", "Name", "Score"], -... [1, "Alice", 95], -... [2, "Bob", 88], -... [3, "Charlie", 76] -... ] - ->>> # newline='' はWindowsでの不要な空行を防ぐためのおまじない ->>> with open('scores.csv', 'w', newline='', encoding='utf-8') as f: -... writer = csv.writer(f) -... writer.writerows(data_to_write) -... -``` - -```csv-readonly:scores.csv -ID,Name,Score -1,Alice,95 -2,Bob,88 -3,Charlie,76 -``` - -### CSVファイルの読み込み - -**`csv.reader()`** を使ってリーダーオブジェクトを作成します。このオブジェクトをループで回すことで、1行ずつリストとしてデータを取得できます。 - -```python-repl:7 ->>> import csv - ->>> with open('scores.csv', 'r', newline='', encoding='-utf-8') as f: -... reader = csv.reader(f) -... # リーダーオブジェクトはイテレータなのでforループで回せる -... for row in reader: -... print(row) -... -['ID', 'Name', 'Score'] -['1', 'Alice', '95'] -['2', 'Bob', '88'] -['3', 'Charlie', '76'] -``` - -注意点として、`csv`モジュールはすべてのデータを文字列として読み込みます。数値として扱いたい場合は、自分で `int()` や `float()` を使って型変換する必要があります。 - - -## この章のまとめ - -この章では、Pythonを使った基本的なファイルの入出力について学びました。 - - - **`open()` 関数**: ファイルを開き、ファイルオブジェクトを取得するための基本です。モード (`'r'`, `'w'`, `'a'`) とエンコーディング (`'utf-8'`) の指定が重要です。 - - **ファイルメソッド**: **`.read()`**, **`.readline()`**, **`.write()`** といったメソッドを使って、ファイルの内容を操作します。 - - **`with` 文**: ファイルを自動的に閉じるための最も安全で推奨される方法です。**コンテキストマネージャ**の仕組みにより、後片付けが確実に行われます。 - - **`json` モジュール**: Pythonの辞書やリストを、広く使われているデータ形式であるJSONとして読み書きするために使用します。**`json.dump()`** と **`json.load()`** が中心的な関数です。 - - **`csv` モジュール**: カンマ区切りの表形式データを扱うためのモジュールです。**`csv.writer()`** と **`csv.reader()`** を使って、行単位での読み書きを簡単に行えます。 - -ファイル操作は、プログラムの設定を保存したり、処理結果を記録したり、他のシステムとデータをやり取りしたりするなど、あらゆるアプリケーションで必要となる基本的なスキルです。 - -### 練習問題1: ユーザー情報の書き出しと読み込み - -1. 以下の情報を持つユーザーのデータを、Pythonの辞書として作成してください。 - * `id`: 101 - * `name`: "Sato Kenji" - * `email`: "kenji.sato@example.com" -2. この辞書を、`with` 文と `json` モジュールを使って `user_profile.json` という名前のファイルに書き出してください。その際、人間が読みやすいようにインデントを付けてください。 -3. 書き出した `user_profile.json` ファイルを読み込み、内容をコンソールに表示してください。 - -```python:practice7_1.py -``` - -```python-exec:practice7_1.py -(出力例) {'id': 101, 'name': 'Sato Kenji', 'email': 'kenji.sato@example.com'} -``` - -```json-readonly:user_profile.json -``` - -### 練習問題2: 売上データのCSV集計 - -あなたは店舗の売上データ(CSV形式)を処理する必要があります。以下の手順でプログラムを作成してください。 - -1. `sales.csv` ファイルを読み込みモードで開きます。 -2. `csv.reader` を使ってデータを1行ずつ読み込み、ヘッダー行(1行目)は無視してください。 -3. 各商品の「価格」と「数量」を掛け合わせ、その行の売上金額を計算します。 -4. すべての商品の合計売上金額を計算し、最後に「合計売上: XXXX円」という形式でコンソールに出力してください。 - -**ヒント:** `csv` モジュールで読み込んだ値はすべて文字列型です。計算する前に `int()` を使って整数型に変換する必要があります。 - -```csv-readonly:sales.csv -商品,価格,数量 -リンゴ,120,3 -バナナ,80,5 -オレンジ,150,2 -ブドウ,300,1 -``` - -```python:practice7_2.py -``` - -```python-exec:practice7_2.py -(出力例) 合計売上: 1360円 -``` diff --git a/public/docs/python-8.md b/public/docs/python-8.md deleted file mode 100644 index 91618b5..0000000 --- a/public/docs/python-8.md +++ /dev/null @@ -1,219 +0,0 @@ -# 第8章: エラーとの付き合い方:例外処理 - -プログラムの実行中に予期せぬ問題が発生すると、Pythonは「例外 (exception)」を送出して処理を中断します。これらのエラーを放置するとプログラムはクラッシュしてしまいますが、「例外処理」の仕組みを使うことで、エラーを優雅に捉えて対処できます。この章では、その方法を学びます。 - -## `try...except`による例外の捕捉 - -他の言語の `try...catch` と同様に、Pythonでは `try...except` ブロックを使います。エラーが発生する可能性のあるコードを `try` ブロックに記述し、エラーが発生した際の処理を `except` ブロックに記述します。 - -例えば、`0` で割り算をすると `ZeroDivisionError` という例外が発生します。 - -```python-repl:1 ->>> 10 / 0 -Traceback (most recent call last): - File "", line 1, in -ZeroDivisionError: division by zero -``` - -このエラーを `try...except` で捕捉してみましょう。 - -```python-repl:2 ->>> try: -... result = 10 / 0 -... except ZeroDivisionError: -... print("エラー: 0で割ることはできません。") -... -エラー: 0で割ることはできません。 -``` - -`try` ブロック内で `ZeroDivisionError` が発生したため、プログラムはクラッシュせずに `except` ブロック内の処理が実行されました。 - -## 複数の例外を処理する方法 - -`try` ブロック内では、複数の種類のエラーが発生する可能性があります。例えば、ユーザーの入力を数値に変換しようとして失敗した場合は `ValueError` が発生します。 - -複数の例外を処理するには、2つの方法があります。 - -**1. `except` ブロックを複数記述する** - -エラーの種類ごとに異なる処理を行いたい場合に適しています。 - -```python-repl:3 ->>> def calculate(a, b): -... try: -... a = int(a) -... b = int(b) -... result = a / b -... print(f"計算結果: {result}") -... except ValueError: -... print("エラー: 数値を入力してください。") -... except ZeroDivisionError: -... print("エラー: 0で割ることはできません。") -... ->>> calculate(10, 2) -計算結果: 5.0 ->>> calculate(10, 0) -エラー: 0で割ることはできません。 ->>> calculate('ten', 2) -エラー: 数値を入力してください。 -``` - -**2. 1つの `except` ブロックでタプルを使ってまとめる** - -複数の例外に対して同じ処理を行いたい場合に便利です。 - -```python-repl:4 ->>> def calculate_v2(a, b): -... try: -... a = int(a) -... b = int(b) -... result = a / b -... print(f"計算結果: {result}") -... except (ValueError, ZeroDivisionError) as e: -... print(f"入力エラーが発生しました: {e}") -... ->>> calculate_v2(20, 0) -入力エラーが発生しました: division by zero ->>> calculate_v2('twenty', 5) -入力エラーが発生しました: invalid literal for int() with base 10: 'twenty' -``` - -`as e` のように書くことで、発生した例外オブジェクトそのものを変数 `e` で受け取ることができます。これにより、具体的なエラーメッセージを表示できます。 - -## 独自例外の送出 (`raise`) - -特定の条件を満たした場合に、意図的に例外を発生させたいことがあります。その場合は `raise` 文を使います。 - -例えば、負の値を受け付けない関数を考えてみましょう。 - -```python-repl:5 ->>> def process_positive_number(num): -... if num < 0: -... raise ValueError("負の値は処理できません。") -... print(f"{num}を処理しました。") -... ->>> process_positive_number(100) -100を処理しました。 ->>> process_positive_number(-5) -Traceback (most recent call last): - File "", line 1, in - File "", line 3, in process_positive_number -ValueError: 負の値は処理できません。 -``` - -このように、`raise` を使うことで、関数の事前条件などを満たさない場合に、プログラムの実行を中断して呼び出し元にエラーを通知できます。 - -## `else`と`finally`節の役割 - -`try...except` ブロックには、`else` と `finally` という2つのオプションの節を追加できます。 - - * **`else` 節**: `try` ブロックで**例外が発生しなかった場合**にのみ実行されます。 - * **`finally` 節**: 例外の有無に**関わらず、必ず最後に**実行されます。ファイルクローズやデータベース接続の切断など、後片付け処理に最適です。 - -すべての節を使った例を見てみましょう。 - -```python-repl:6 ->>> def divider(a, b): -... print(f"--- {a} / {b} の計算を開始します ---") -... try: -... result = a / b -... except ZeroDivisionError: -... print("エラー: 0による除算です。") -... else: -... # 例外が発生しなかった場合に実行 -... print(f"計算成功! 結果: {result}") -... finally: -... # 常に最後に実行 -... print("--- 計算を終了します ---") -... ->>> # 成功するケース ->>> divider(10, 2) ---- 10 / 2 の計算を開始します --- -計算成功! 結果: 5.0 ---- 計算を終了します --- - ->>> # 例外が発生するケース ->>> divider(10, 0) ---- 10 / 0 の計算を開始します --- -エラー: 0による除算です。 ---- 計算を終了します --- -``` - -この例から、実行フローが明確にわかります。 - - * 成功ケースでは `try` -\> `else` -\> `finally` の順に実行されます。 - * 失敗ケースでは `try` -\> `except` -\> `finally` の順に実行されます。 - -`finally` 節は、`try` ブロック内で `return` が実行される場合でも、その `return` の直前に実行されることが保証されています。これにより、リソースの解放漏れなどを防ぐことができます。 - -## この章のまとめ - -この章では、Pythonにおけるエラー処理の基本を学びました。重要なポイントを振り返りましょう。 - - * **例外**: Pythonでは、エラーは「例外」オブジェクトとして扱われます。例外が発生すると、プログラムの実行は中断されます。 - * `try...except`: 例外が発生する可能性のあるコードを `try` ブロックで囲み、`except` ブロックで捕捉することで、プログラムのクラッシュを防ぎ、エラーに応じた処理を実行できます。 - * **複数の例外処理**: `except` ブロックを複数記述したり、タプルでまとめたりすることで、さまざまな種類のエラーに柔軟に対応できます。 - * `raise`: 特定の条件で意図的に例外を発生させ、プログラムに異常な状態を通知します。 - * `else` と `finally`: `try` ブロックが成功した場合の処理を `else` に、成功・失敗にかかわらず必ず実行したい後片付け処理を `finally` に記述することで、より堅牢なコードを書くことができます。 - -例外処理をマスターすることは、予期せぬ入力や状況に強い、安定したプログラムを作成するための重要なステップです。 - -### 練習問題1: 安全なリスト要素の取得 - -リストとインデックスを受け取り、そのインデックスに対応する要素を返す `safe_get(my_list, index)` という関数を作成してください。 - -**要件:** - -1. インデックスがリストの範囲外の場合 (`IndexError`)、「指定されたインデックスは範囲外です。」と表示してください。 -2. インデックスが整数でない場合 (`TypeError`)、「インデックスは整数で指定してください。」と表示してください。 -3. 正常に要素を取得できた場合は、その要素を返してください。 - -```python:practice8_1.py -def safe_get(my_list, index): - - -data = ['apple', 'banana', 'cherry'] -print(safe_get(data, 1)) -print(safe_get(data, 3)) -print(safe_get(data, 'zero')) -``` - -```python-exec:practice8_1.py -(出力例) -banana -指定されたインデックスは範囲外です。 -インデックスは整数で指定してください。 -``` - -### 練習問題2: ユーザー年齢の検証 - -ユーザーの年齢を入力として受け取り、18歳以上であれば「あなたは成人です。」と表示する `check_age(age_str)` という関数を作成してください。 - -**要件:** - -1. 関数内部で、受け取った文字列を整数に変換してください。変換できない場合 (`ValueError`) は、`ValueError` を `raise` して、「有効な数値を入力してください。」というメッセージを伝えてください。 -2. 変換した数値が負の値である場合、`ValueError` を `raise` して、「年齢に負の値は指定できません。」というメッセージを伝えてください。 -3. 年齢が0歳から17歳までの場合は、「あなたは未成年です。」と表示してください。 -4. 関数の呼び出し側で、`raise` された例外も捕捉できるようにしてください。 - -```python:practice8_2.py -def check_age(age_str): - - -# 正常ケース -print(check_age("20")) -print(check_age("15")) - -# 例外ケース -print(check_age("abc")) -print(check_age("-5")) -``` - -```python-exec:practice8_2.py -(出力例) -あなたは成人です。 -あなたは未成年です。 -Traceback (most recent call last): - ... -ValueError: 有効な数値を入力してください。 -``` diff --git a/public/docs/python-9.md b/public/docs/python-9.md deleted file mode 100644 index 11e00cc..0000000 --- a/public/docs/python-9.md +++ /dev/null @@ -1,231 +0,0 @@ -# 第9章: さらに先へ:ジェネレータとデコレータ - -この章では、より高度でPythonらしいコードを書くための強力な機能、**ジェネレータ**と**デコレータ**を学びます。これらの機能を使いこなすことで、メモリ効率の良いデータ処理を実装したり、既存の関数の振る舞いをエレガントに変更したりできるようになります。 - -## イテレータとイテラブル - -Pythonの`for`ループは非常にシンプルで強力ですが、その裏側では**イテレーションプロトコル**という仕組みが動いています。これを理解することが、ジェネレータを学ぶ上での第一歩です。 - - * **イテラブル (Iterable)**: `for`ループで繰り返し処理が可能なオブジェクトのことです。リスト、タプル、辞書、文字列などがこれにあたります。内部に `__iter__()` メソッドを持つオブジェクトと定義されます。 - * **イテレータ (Iterator)**: 「次の値」を返す `__next__()` メソッドを持ち、値を一つずつ取り出すためのオブジェクトです。イテレータは一度最後まで進むと、それ以上値を取り出すことはできません。 - -`for`ループは、まずイテラブルオブジェクトの `__iter__()` を呼び出してイテレータを取得し、次にそのイテレータの `__next__()` を繰り返し呼び出して要素を一つずつ取り出しています。 - -REPLで動きを見てみましょう。`iter()`関数でイテレータを取得し、`next()`関数で要素を取り出します。 - -```python-repl:1 ->>> my_list = [1, 2, 3] ->>> my_iterator = iter(my_list) ->>> type(my_iterator) - - ->>> next(my_iterator) -1 ->>> next(my_iterator) -2 ->>> next(my_iterator) -3 ->>> next(my_iterator) -Traceback (most recent call last): - File "", line 1, in -StopIteration -``` - -最後の`next()`呼び出しで `StopIteration` という例外が発生しているのがわかります。`for`ループはこの例外を検知して、ループを自動的に終了してくれます。 - -## ジェネレータ関数とyieldキーワード - -イテレータを自作するには、クラスに `__iter__()` と `__next__()` を実装する必要がありますが、少し手間がかかります。そこで登場するのが**ジェネレータ**です。ジェネレータは、イテレータを簡単に作成するための特別な関数です。 - -ジェネレータ関数は、通常の関数と似ていますが、値を返すのに`return`の代わりに`yield`を使います。 - - * **`yield`の働き**: `yield`は値を返すだけでなく、その時点で関数の実行を**一時停止**し、関数の状態(ローカル変数など)を保存します。次に`next()`が呼ばれると、停止した場所から処理を再開します。 - -これにより、巨大なデータセットを扱う際に、全てのデータを一度にメモリに読み込む必要がなくなります。必要な時に必要な分だけデータを生成するため、非常にメモリ効率が良いコードが書けます。 - -フィボナッチ数列を生成するジェネレータの例を見てみましょう。 - -```python-repl:2 ->>> def fib_generator(n): -... a, b = 0, 1 -... count = 0 -... while count < n: -... yield a -... a, b = b, a + b -... count += 1 -... ->>> f = fib_generator(5) ->>> type(f) - - ->>> next(f) -0 ->>> next(f) -1 ->>> next(f) -1 ->>> next(f) -2 ->>> next(f) -3 ->>> next(f) -Traceback (most recent call last): - File "", line 1, in -StopIteration - -# ジェネレータはもちろんforループで使うことができます ->>> for num in fib_generator(8): -... print(num, end=' ') -... -0 1 1 2 3 5 8 13 -``` - -## ジェネレータ式 - -リスト内包表記に似た構文で、より簡潔にジェネレータを作成する方法が**ジェネレータ式**です。リスト内包表記の `[]` を `()` に変えるだけで作れます。 - -リスト内包表記はリストオブジェクトを生成するため、要素数が多いとメモリを大量に消費します。一方、ジェネレータ式はジェネレータオブジェクトを返すため、遅延評価(必要になるまで計算しない)が行われ、メモリ使用量を抑えられます。 - -```python-repl:3 -# リスト内包表記 ->>> list_comp = [i * 2 for i in range(5)] ->>> list_comp -[0, 2, 4, 6, 8] ->>> type(list_comp) - - -# ジェネレータ式 ->>> gen_exp = (i * 2 for i in range(5)) ->>> gen_exp - at 0x...> ->>> type(gen_exp) - - ->>> next(gen_exp) -0 ->>> next(gen_exp) -2 ->>> list(gen_exp) # 残りの要素をリストに変換 -[4, 6, 8] -``` - -巨大なファイルの各行を処理する場合など、ジェネレータ式は非常に有効です。 - -## デコレータの概念と基本的な作り方 - -**デコレータ**は、既存の関数のコードを一切変更せずに、その関数に新しい機能を追加(装飾)するための仕組みです。これは、関数を受け取って、新しい関数を返す**高階関数**として実装されます。 - -ログ出力、実行時間の計測、認証チェックなど、複数の関数に共通して適用したい「横断的な関心事」を扱うのに非常に便利です。 - -### 基本的な作り方 - -デコレータの基本的な構造は、関数を入れ子にすることです。 - -1. 外側の関数(デコレータ関数)は、装飾したい対象の関数を引数として受け取ります。 -2. 内側の関数(ラッパー関数)で、受け取った関数を呼び出す前後に、追加したい処理を記述します。 -3. 外側の関数は、この内側の関数を返します。 - -関数の実行前後にメッセージを表示する簡単なデコレータを見てみましょう。 - -```python-repl:4 ->>> def my_decorator(func): -... def wrapper(): -... print("--- 処理を開始します ---") -... func() -... print("--- 処理が完了しました ---") -... return wrapper -... - ->>> def say_hello(): -... print("こんにちは!") -... - -# デコレートされた新しい関数を作成 ->>> decorated_hello = my_decorator(say_hello) ->>> decorated_hello() ---- 処理を開始します --- -こんにちは! ---- 処理が完了しました --- -``` - -この書き方をより簡単にするための構文が `@`(アットマーク)、シンタックスシュガーです。 - -```python-repl:5 ->>> @my_decorator -... def say_goodbye(): -... print("さようなら!") -... - ->>> say_goodbye() ---- 処理を開始します --- -さようなら! ---- 処理が完了しました --- -``` - -`@my_decorator` は、`say_goodbye = my_decorator(say_goodbye)` と同じ意味になります。こちらのほうが直感的で、Pythonのコードで広く使われています。 - -ジェネレータとデコレータは、最初は少し複雑に感じるかもしれませんが、使いこなせばよりクリーンで効率的なPythonコードを書くための強力な武器となります。ぜひ積極的に活用してみてください。 - -はい、承知いたしました。先に作成したチュートリアルの末尾に追加する「この章のまとめ」と「練習問題」を作成します。 - -## この章のまとめ - -この章では、Pythonプログラミングをさらに高いレベルへ引き上げるための2つの強力な概念を学びました。 - - * **ジェネレータ**: `yield`キーワードを使うことで、メモリ効率に優れたイテレータを簡単に作成できることを学びました。ジェネレータ関数やジェネレータ式を使うことで、巨大なデータストリームや無限シーケンスを、必要な分だけ計算しながら扱うことができます。これは、パフォーマンスが重要なアプリケーションにおいて不可欠なテクニックです。 - - * **デコレータ**: `@`シンタックスを用いることで、既存の関数のソースコードを変更することなく、機能を追加・変更できることを学びました。デコレータは、ロギング、実行時間計測、アクセス制御といった横断的な関心事を分離し、コードの再利用性を高め、DRY (Don't Repeat Yourself) の原則を維持するのに役立ちます。 - -これらの機能を使いこなすことは、単に高度な文法を覚えるだけでなく、Pythonの設計思想を理解し、より「Pythonらしい(Pythonic)」コードを書くための重要なステップです。 - -### 練習問題1: カウントダウンジェネレータ - -`countdown(start)` というジェネレータ関数を作成してください。この関数は、引数で与えられた `start` の数値から1まで、1ずつ減っていく数値を順番に `yield` します。例えば `countdown(3)` は、`3`, `2`, `1` の順に値を生成します。 - -```python:practice9_1.py -def countdown(start): - - -# 動作確認 -cd_gen = countdown(5) -for i in cd_gen: - print(i) -``` - -```python-exec:practice9_1.py -(出力例) -5 -4 -3 -2 -1 -``` - -### 問題2: 実行時間計測デコレータ - -関数の実行時間を計測し、"実行時間: X.XXXX秒" のように表示するデコレータ `@measure_time` を作成してください。このデコレータを、少し時間のかかる処理を行う関数に適用して、動作を確認してみましょう。 - -**ヒント**: 時間の計測には `time` モジュールが使えます。処理の開始前と終了後で `time.time()` を呼び出し、その差分を計算します。 - -```python:practice9_2.py -import time - -def measure_time(func): - - -# 動作確認用の時間のかかる関数 -@measure_time -def slow_function(n): - print(f"{n}まで数えます...") - time.sleep(n) # n秒間処理を停止 - print("完了!") - -slow_function(2) -``` - -```python-exec:practice9_2.py -(出力例) -2まで数えます... -完了! -実行時間: 2.0021秒 -``` diff --git a/public/docs/python/0-intro/-intro.md b/public/docs/python/0-intro/-intro.md new file mode 100644 index 0000000..f9d51f6 --- /dev/null +++ b/public/docs/python/0-intro/-intro.md @@ -0,0 +1 @@ +プログラミング経験者であっても、言語ごとのツールや流儀を最初に理解することは重要です。この章では、Pythonの開発環境を整え、基本的なツールの使い方を学びます。 diff --git a/public/docs/python/0-intro/1-0-readability.md b/public/docs/python/0-intro/1-0-readability.md new file mode 100644 index 0000000..ab00575 --- /dev/null +++ b/public/docs/python/0-intro/1-0-readability.md @@ -0,0 +1,20 @@ +--- +id: python-intro-readability +title: 'Pythonの思想と特徴: 「読みやすさ」は最優先' +level: 2 +--- + +## Pythonの思想と特徴: 「読みやすさ」は最優先 + +他の言語(Java, C++, PHPなど)と比較したとき、Pythonが最も重視するのは**コードの可読性(Readability)**です。 + +* **シンプルな文法:** C言語やJavaのような `{}`(波括弧)によるブロックや、行末の `;`(セミコロン)を必要としません。 +* **強制的なインデント:** Pythonは、**インデント(字下げ)**そのものでコードブロックを表現します。これは構文的なルールであり、オプションではありません。これにより、誰が書いても(ある程度)同じような見た目のコードになり、可読性が劇的に向上します。 +* **動的型付け (Dynamic Typing):** JavaやC++のように `int num = 10;` と変数の型を明示的に宣言する必要がありません。`num = 10` と書けば、Pythonが実行時に自動的に型を推論します。(これはJavaScriptやPHPと似ていますが、Pythonは型付けがより厳格(Strong Typing)で、例えば文字列と数値を暗黙的に連結しようとするとエラーになります) +* **豊富な標準ライブラリ**: 「Batteries Included(バッテリー同梱)」という思想のもと、OS操作、ネットワーク、データ処理、JSON、正規表現など、多くの機能が最初から標準ライブラリとして提供されています。 + +**💡 The Zen of Python (Pythonの禅)** Pythonの設計思想は、`import this` というコマンドでいつでも確認できます。 + +* Beautiful is better than ugly. (醜いより美しいほうがいい) +* Explicit is better than implicit. (暗黙的より明示的なほうがいい) +* Simple is better than complex. (複雑であるよりシンプルなほうがいい) diff --git a/public/docs/python/0-intro/2-0-install.md b/public/docs/python/0-intro/2-0-install.md new file mode 100644 index 0000000..8862ef9 --- /dev/null +++ b/public/docs/python/0-intro/2-0-install.md @@ -0,0 +1,22 @@ +--- +id: python-intro-install +title: Pythonのインストール方法 +level: 2 +--- + +## Pythonのインストール方法 + +手元の環境で本格的に開発を進めるために、Pythonのインストール方法を紹介します。 + +WindowsでPythonをインストールするには、主に2つの方法があります。 + +1. **[Python公式インストーラ](https://www.python.org/downloads/)**: Pythonの公式サイトからインストーラをダウンロードする方法が最も一般的です。インストール時に「Add Python to PATH」のチェックを入れると、コマンドプロンプトやPowerShellから `python` コマンドを直接実行できるようになり便利です。 +2. **Microsoft Store**: Microsoft Storeからも手軽にPythonをインストールできます。 + +macOSでは、**Homebrew** というパッケージマネージャを使ってインストールするのが簡単です。 +```bash +brew install python +``` +もちろん、Windowsと同様に公式サイトからインストーラをダウンロードすることも可能です。 + +Linuxでは、多くのディストリビューションには初めからPythonがインストールされていますが、最新版を使いたい場合はディストリビューションのパッケージマネージャ(`apt`, `yum`など)を利用するのが一般的です。 diff --git a/public/docs/python/0-intro/2-3-version-management.md b/public/docs/python/0-intro/2-3-version-management.md new file mode 100644 index 0000000..87ba1c6 --- /dev/null +++ b/public/docs/python/0-intro/2-3-version-management.md @@ -0,0 +1,12 @@ +--- +id: python-intro-version-management +title: バージョン管理と環境管理ツール +level: 3 +--- + +### バージョン管理と環境管理ツール + +より高度な開発や、複数のプロジェクトを並行して進める場合は、バージョン管理ツールや統合的な環境管理ツールの利用が推奨されます。 + + * **[pyenv](https://github.com/pyenv/pyenv)**: 複数のPythonバージョン(例: 3.9と3.11)を一つのPCに共存させ、プロジェクトごとに切り替えるためのツールです。 + * **[Conda](https://docs.conda.io/en/latest/)**: 特にデータサイエンスの分野で人気のあるツールです。**Conda** はPythonのバージョン管理だけでなく、パッケージ管理、仮想環境の管理までを一つでこなせるオールインワンのソリューションです。 diff --git a/public/docs/python/0-intro/3-0-repl.md b/public/docs/python/0-intro/3-0-repl.md new file mode 100644 index 0000000..1b21c3f --- /dev/null +++ b/public/docs/python/0-intro/3-0-repl.md @@ -0,0 +1,21 @@ +--- +id: python-intro-repl +title: 対話モード(REPL)でPythonを体験しよう +level: 2 +--- + +## 対話モード(REPL)でPythonを体験しよう + +**REPL**(Read-Eval-Print Loop)は、入力したコードをその場で実行し、結果をすぐに見ることができる強力な学習・デバッグツールです。 + +このウェブサイト上ではブラウザ上でコードを実行できる環境を埋め込んでおり、以下のように緑枠で囲われたコード例には自由にPythonコードを書いて試すことができます。 + +手元のPCにインストールした環境で実行するには、ターミナル(コマンドプロンプトやPowerShellなど)で `python` と入力すれば、同じ対話モードを起動できます。 + +```python-repl +>>> message = "Hello, Python!" +>>> print(message) +Hello, Python! +>>> 1 + 2 * 3 +7 +``` diff --git a/public/docs/python/0-intro/3-3-repl-usage.md b/public/docs/python/0-intro/3-3-repl-usage.md new file mode 100644 index 0000000..d8d50d3 --- /dev/null +++ b/public/docs/python/0-intro/3-3-repl-usage.md @@ -0,0 +1,32 @@ +--- +id: python-intro-repl-usage +title: REPL の基本的な使い方 +level: 3 +--- + +### REPL の基本的な使い方 + +* **計算:** 数式を直接入力すると、計算結果が返ってきます。 +```python +>>> 10 * 5 + 3 +53 +``` +* **変数と関数の利用:** 変数を定義したり、`print()`のような組み込み関数を呼び出したりできます。 +```python +>>> greeting = "Hi there" +>>> print(greeting) +Hi there +``` +* **ヘルプ機能:** `help()` と入力するとヘルプが表示されます。調べたいモジュールや関数名(例: `str`)を入力するとドキュメントが表示されます。 + * PCのターミナルで起動したREPLでは、対話的なヘルプモードが起動します。ヘルプモードを抜けるには `quit` と入力します。 +```python +>>> help(str) +Help on class str in module builtins: + +class str(object) + | str(object='') -> str + | str(bytes_or_buffer[, encoding[, errors]]) -> str + | ... +``` +* **終了方法:** REPLを終了するには、`exit()` と入力するか、ショートカットキー(macOS/Linuxでは `Ctrl + D`、Windowsでは `Ctrl + Z` を押してからEnter)を使用します。 + * このウェブサイトに埋め込まれているREPLは、終了できません。 diff --git a/public/docs/python/0-intro/4-0-script-run.md b/public/docs/python/0-intro/4-0-script-run.md new file mode 100644 index 0000000..9206dc5 --- /dev/null +++ b/public/docs/python/0-intro/4-0-script-run.md @@ -0,0 +1,21 @@ +--- +id: python-intro-script-run +title: スクリプトの実行方法 +level: 2 +--- + +## スクリプトの実行方法 + +一連の処理をまとめて実行する場合は、`.py` という拡張子を持つファイルにコードを記述します。例えば、`hello.py` というファイルを以下のように作成します。 +REPLでは式を入力するだけでも結果が表示されていましたが、スクリプトで結果を表示するには `print()` 関数を使う必要があります。 + +```python:hello.py +print("Hello from a Python script!") +``` + +このスクリプトを実行するには、ターミナルで `python hello.py` のようにコマンドを入力します。 +このウェブサイト上の実行環境で動かす場合は、以下の実行ボタンをクリックしてください。 + +```python-exec:hello.py +Hello from a Python script! +``` diff --git a/public/docs/python/0-intro/4-1-main-snippet.md b/public/docs/python/0-intro/4-1-main-snippet.md new file mode 100644 index 0000000..32aeee3 --- /dev/null +++ b/public/docs/python/0-intro/4-1-main-snippet.md @@ -0,0 +1,73 @@ +--- +id: python-intro-main-snippet +title: __main__ について +level: 3 +--- + +### `__main__` について + +前述の hello.py のようにファイルの1行目から処理を書いても問題なく動作しますが、一般的には以下のようなお決まりの書き方が用いられます。 + +```python:hello2.py +def main(): + print("Hello from a Python script!") + +if __name__ == "__main__": + main() +``` + +```python-exec:hello2.py +Hello from a Python script! +``` + +なぜわざわざ `if __name__ == "__main__":` を使うのでしょうか? +それは、**書いたコードを「スクリプトとして直接実行する」場合と、「他のファイルから部品(モジュール)として読み込んで使う」場合の両方に対応できるようにするため**です。 + +Pythonでは、ファイルは他のファイルから `import` 文で読み込むことができます。このとき、読み込まれたファイル(モジュール)は上から順に実行されます。 + +`if __name__ == "__main__":` を使うと、**「このファイルがコマンドラインから直接 `python a.py` のように実行された時だけ、このブロックの中の処理を実行してね」** という意味になります。 + +**例:再利用可能な関数を持つスクリプト** + +```python:my_utils.py +def say_hello(name): + """挨拶を返す関数""" + return f"Hello, {name}!" + +# このファイルが直接実行された時だけ、以下のテストコードを実行する +if __name__ == "__main__": + print("--- Running Test ---") + message = say_hello("Alice") + print(message) + print("--- Test Finished ---") +``` + +このファイルを2通りの方法で使ってみます。 + +1. **直接スクリプトとして実行する** + + ```python-exec:my_utils.py + --- Running Test --- + Hello, Alice! + --- Test Finished --- + ``` + +2. **他のファイルからモジュールとして読み込む** + + ```python:main_app.py + # my_utils.py から say_hello 関数だけを読み込む + from my_utils import say_hello + + print("--- Running Main App ---") + greeting = say_hello("Bob") + print(greeting) + ``` + + ```python-exec:main_app.py + --- Running Main App --- + Hello, Bob! + ``` + + `my_utils.py` のテストコード(`--- Running Test ---`など)は実行されず、`say_hello` 関数だけを部品として利用できました。 + +このように、`if __name__ == "__main__":` は、**再利用可能な関数やクラスの定義**と、**そのファイル単体で動かすための処理**をきれいに分離するための、Pythonにおける非常に重要な作法です。 diff --git a/public/docs/python/0-intro/5-0-pip-venv.md b/public/docs/python/0-intro/5-0-pip-venv.md new file mode 100644 index 0000000..eb7fb3b --- /dev/null +++ b/public/docs/python/0-intro/5-0-pip-venv.md @@ -0,0 +1,50 @@ +--- +id: python-intro-pip-venv +title: パッケージ管理ツール pip と仮想環境 venv +level: 2 +--- + +## パッケージ管理ツール `pip` と仮想環境 `venv` + +Pythonの強力なエコシステムは、豊富なサードパーティ製パッケージ(ライブラリ)によって支えられています。これらのパッケージを管理するのが **`pip`** です。 + +しかし、プロジェクトごとに異なるバージョンのパッケージを使いたい場合、依存関係の衝突が問題になります。これを解決するのが **仮想環境** で、Pythonでは **`venv`** モジュールを使って作成するのが標準的です。 + +**仮想環境とは?** 🚧 +プロジェクト専用の独立したPython実行環境です。ここでインストールしたパッケージはシステム全体には影響を与えず、そのプロジェクト内に限定されます。 + +**基本的な流れ:** + +1. **仮想環境の作成**: + + ```bash + # .venvという名前の仮想環境を作成 + python -m venv .venv + ``` + +2. **仮想環境の有効化(Activate)**: + + ```bash + # macOS / Linux + source .venv/bin/activate + + # Windows (PowerShell) + .\.venv\Scripts\Activate.ps1 + ``` + + 有効化すると、ターミナルのプロンプトに `(.venv)` のような表示が付きます。 + +3. **パッケージのインストール**: + 有効化された環境で `pip` を使ってパッケージをインストールします。 + + ```bash + (.venv) $ pip install requests + ``` + +4. **仮想環境の無効化(Deactivate)**: + + ```bash + (.venv) $ deactivate + ``` + +**`pyenv` でPythonバージョンを固定し、`venv` でプロジェクトのパッケージを隔離する** のが、現代的なPython開発の基本スタイルです。(前述の **Conda** は、このPythonバージョン管理と環境・パッケージ管理を両方とも行うことができます。) diff --git a/public/docs/python/1-basics/-intro.md b/public/docs/python/1-basics/-intro.md new file mode 100644 index 0000000..3610046 --- /dev/null +++ b/public/docs/python/1-basics/-intro.md @@ -0,0 +1 @@ +他の言語でのプログラミング経験がある方を対象に、Pythonの基本的な文法と組み込みデータ型を解説します。多くの静的型付け言語(Java, C++, C\#など)との違いを意識しながら、Pythonの特徴を素早く掴んでいきましょう。特に、**動的型付け**は重要なコンセプトです。 diff --git a/public/docs/python/1-basics/1-0-variable.md b/public/docs/python/1-basics/1-0-variable.md new file mode 100644 index 0000000..e564213 --- /dev/null +++ b/public/docs/python/1-basics/1-0-variable.md @@ -0,0 +1,28 @@ +--- +id: python-basics-variable +title: 変数宣言と動的型付け +level: 2 +--- + +## 変数宣言と動的型付け + +Pythonの最も大きな特徴の一つは、変数の型を宣言する必要がないことです。変数への代入が、変数の作成と型の決定を同時に行います。 + +さらに、Pythonは**動的型付け**言語です。これは、一度作成した変数に、異なる型のデータを再代入できることを意味します。`type()`関数を使うと、変数が現在どの型を参照しているかを確認できます。 + +```python-repl +>>> # 変数 x を作成し、整数を代入 +>>> x = 10 +>>> print(x) +10 +>>> type(x) + +>>> # 同じ変数 x に文字列を再代入 +>>> x = "Hello" +>>> print(x) +Hello +>>> type(x) + +``` + +この柔軟性は、静的型付け言語に慣れていると少し奇妙に感じるかもしれませんが、Pythonの簡潔さと書きやすさの源泉となっています。 diff --git a/public/docs/python/1-basics/2-0-types.md b/public/docs/python/1-basics/2-0-types.md new file mode 100644 index 0000000..7c8d8fd --- /dev/null +++ b/public/docs/python/1-basics/2-0-types.md @@ -0,0 +1,9 @@ +--- +id: python-basics-types +title: 基本的なデータ型 +level: 2 +--- + +## 基本的なデータ型 + +Pythonには多くの組み込みデータ型がありますが、ここでは最も基本的なものを紹介します。 diff --git a/public/docs/python/1-basics/2-1-int.md b/public/docs/python/1-basics/2-1-int.md new file mode 100644 index 0000000..a93ae43 --- /dev/null +++ b/public/docs/python/1-basics/2-1-int.md @@ -0,0 +1,32 @@ +--- +id: python-basics-int +title: 数値(int, float) +level: 3 +--- + +### 数値(int, float) + +Pythonは整数 (`int`) と浮動小数点数 (`float`) を区別します。 + +四則演算は直感的に行えます。注意点として、除算 (`/`) は常に `float` を返します。整数除算を行いたい場合は (`//`) を使います。 + +```python-repl +>>> # 整数 (int) +>>> a = 10 +>>> type(a) + +>>> # 浮動小数点数 (float) +>>> b = 3.14 +>>> type(b) + +>>> 10 / 3 +3.3333333333333335 +>>> 10 // 3 +3 +>>> # べき乗 +>>> 2 ** 4 +16 +>>> # 剰余 +>>> 10 % 3 +1 +``` diff --git a/public/docs/python/1-basics/2-2-str.md b/public/docs/python/1-basics/2-2-str.md new file mode 100644 index 0000000..6ab6fcb --- /dev/null +++ b/public/docs/python/1-basics/2-2-str.md @@ -0,0 +1,22 @@ +--- +id: python-basics-str +title: 文字列(str) +level: 3 +--- + +### 文字列(str) + +文字列はシングルクォート (`'`) またはダブルクォート (`"`) で囲んで作成します。 + +文字列の連結は `+` 演算子、繰り返しは `*` 演算子を使います。 + +```python-repl +>>> name = "Guido" +>>> greeting = 'Hello' +>>> full_greeting = greeting + ", " + name + "!" +>>> print(full_greeting) +Hello, Guido! +>>> print("-" * 10) +---------- +``` + diff --git a/public/docs/python/1-basics/2-3-f-string.md b/public/docs/python/1-basics/2-3-f-string.md new file mode 100644 index 0000000..009bf05 --- /dev/null +++ b/public/docs/python/1-basics/2-3-f-string.md @@ -0,0 +1,16 @@ +--- +id: python-basics-f-string +title: f-string (フォーマット済み文字列リテラル) +level: 4 +--- + +#### f-string (フォーマット済み文字列リテラル) +変数の値を文字列に埋め込む際には、**f-string** が非常に便利で推奨されています。文字列の前に `f` を付け、埋め込みたい変数を `{}` で囲みます。 + +```python-repl +>>> name = "Ada" +>>> age = 36 +>>> message = f"My name is {name} and I am {age} years old." +>>> print(message) +My name is Ada and I am 36 years old. +``` diff --git a/public/docs/python/1-basics/2-4-bool.md b/public/docs/python/1-basics/2-4-bool.md new file mode 100644 index 0000000..f06381a --- /dev/null +++ b/public/docs/python/1-basics/2-4-bool.md @@ -0,0 +1,25 @@ +--- +id: python-basics-bool +title: 真偽値(bool) +level: 3 +--- + +### 真偽値(bool) + +真偽値は `True` と `False` の2つの値を持ちます(先頭が大文字であることに注意してください)。論理演算子には `and`, `or`, `not` を使います。 + +```python-repl +>>> is_active = True +>>> has_permission = False +>>> type(is_active) + +>>> # 論理積 (AND) +>>> is_active and has_permission +False +>>> # 論理和 (OR) +>>> is_active or has_permission +True +>>> # 否定 (NOT) +>>> not is_active +False +``` diff --git a/public/docs/python/1-basics/3-0-type-hints.md b/public/docs/python/1-basics/3-0-type-hints.md new file mode 100644 index 0000000..3a8c217 --- /dev/null +++ b/public/docs/python/1-basics/3-0-type-hints.md @@ -0,0 +1,27 @@ +--- +id: python-basics-type-hints +title: 型ヒント(Type Hints)の紹介 +level: 2 +--- + +## 型ヒント(Type Hints)の紹介 + +動的型付けは柔軟ですが、コードが大規模になると変数がどの型を期待しているのかが分かりにくくなることがあります。そこで導入されたのが**型ヒント**です。これは、変数や関数の引数、返り値に「期待される型」を注釈として付与する機能です。 + +`変数名: 型` のように記述します。 + +**重要な注意点:** 型ヒントはあくまで「ヒント」であり、**Pythonの実行エンジンはこれを強制しません**。つまり、型ヒントと異なる型の値を代入してもエラーにはなりません。 + +```python-repl +>>> # 型ヒントを付けた変数宣言 +>>> user_name: str = "Alice" +>>> user_id: int = 123 +>>> type(user_id) + +>>> # int型とヒントを付けたが、文字列を代入できてしまう +>>> user_id = "abc-789" +>>> type(user_id) + +``` + +型ヒントは、コードの可読性を高めたり、MyPyのような静的解析ツールや統合開発環境(IDE)がコードのバグを事前に発見したりするために利用されます。 diff --git a/public/docs/python/1-basics/4-0-summary.md b/public/docs/python/1-basics/4-0-summary.md new file mode 100644 index 0000000..f1d4677 --- /dev/null +++ b/public/docs/python/1-basics/4-0-summary.md @@ -0,0 +1,12 @@ +--- +id: python-basics-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * Pythonでは変数の型宣言は不要で、値の代入によって型が自動的に決まる(**動的型付け**)。 + * 基本的なデータ型として**数値** (`int`, `float`)、**文字列** (`str`)、**真偽値** (`bool`) がある。 + * 文字列に変数を埋め込むには、簡潔で強力な **f-string** を使うのが一般的。 + * **型ヒント** (`変数名: 型`) は、コードの可読性を向上させるための注釈であり、実行時に型の強制力はない。 diff --git a/public/docs/python/1-basics/4-1-practice1.md b/public/docs/python/1-basics/4-1-practice1.md new file mode 100644 index 0000000..293d917 --- /dev/null +++ b/public/docs/python/1-basics/4-1-practice1.md @@ -0,0 +1,16 @@ +--- +id: python-basics-practice1 +title: 練習問題1 +level: 3 +--- + +### 練習問題1 + +`item_name` という変数に商品名(文字列)、`price` という変数に価格(整数)、`stock` という変数に在庫数(整数)をそれぞれ代入してください。その後、f-stringを使って「商品: [商品名], 価格: [価格]円, 在庫: [在庫数]個」という形式の文字列にし、 `print()` で出力するコードを書いてみましょう。 + +```python:practice2_1.py +``` + +```python-exec:practice2_1.py +(出力例) 商品: 高性能マウス, 価格: 4500円, 在庫: 2個 +``` diff --git a/public/docs/python/1-basics/4-2-practice2.md b/public/docs/python/1-basics/4-2-practice2.md new file mode 100644 index 0000000..1d2eba5 --- /dev/null +++ b/public/docs/python/1-basics/4-2-practice2.md @@ -0,0 +1,15 @@ +--- +id: python-basics-practice2 +title: 練習問題2 +level: 3 +--- + +### 練習問題2 + +`is_adult` という変数に `bool` 型の型ヒントを付けて `True` を代入し、`type()` で型を確認してください。その後、同じ変数に数値の `20` を代入し、再度 `type()` で型を確認してください。この結果から、型ヒントが実行時の動作にどのような影響を与える(あるいは与えない)か考察してみましょう。 + +```python:practice2_2.py +``` + +```python-exec:practice2_2.py +``` diff --git a/public/docs/python/2-collections/-intro.md b/public/docs/python/2-collections/-intro.md new file mode 100644 index 0000000..d1d4f6e --- /dev/null +++ b/public/docs/python/2-collections/-intro.md @@ -0,0 +1 @@ +Pythonのプログラミングにおいて、データを効率的に扱う能力は非常に重要です。この章では、Pythonに組み込まれている強力なコレクション(データ構造)である**リスト**、**タプル**、**辞書**、**セット**を学びます。これらのデータ構造は、他の言語における配列、ハッシュマップ、集合などと似ていますが、Pythonならではの便利な特性やメソッドを持っています。これらを理解し、使いこなすことが「Pythonらしい」コードを書くための第一歩です。 diff --git a/public/docs/python/2-collections/1-0-list.md b/public/docs/python/2-collections/1-0-list.md new file mode 100644 index 0000000..428120d --- /dev/null +++ b/public/docs/python/2-collections/1-0-list.md @@ -0,0 +1,45 @@ +--- +id: python-collections-list +title: リスト (List):ミュータブルなシーケンス +level: 2 +--- + +## リスト (List):ミュータブルなシーケンス + +リストは、複数の要素を順序付けて格納できるコレクションです。他の言語における「動的配列」に最も近い存在です。 + + * **ミュータブル (Mutable)**: 作成後に要素の追加、変更、削除が可能です。 + * **順序あり (Ordered)**: 要素は格納された順序を保持します。 + * **多様な要素**: 数値、文字列、さらには他のリストなど、異なるデータ型の要素を混在させることができます。 + +**基本的な使い方 (REPL実行例)** + +```python-repl +>>> # リストの作成 +>>> fruits = ['apple', 'banana', 'cherry'] +>>> fruits +['apple', 'banana', 'cherry'] + +>>> # 要素へのアクセス (インデックスは0から) +>>> fruits[1] +'banana' + +>>> # 要素の変更 +>>> fruits[0] = 'apricot' +>>> fruits +['apricot', 'banana', 'cherry'] + +>>> # 要素の追加 (末尾に) +>>> fruits.append('mango') +>>> fruits +['apricot', 'banana', 'cherry', 'mango'] + +>>> # 要素の削除 (指定したインデックス) +>>> removed_fruit = fruits.pop(1) +>>> removed_fruit +'banana' +>>> fruits +['apricot', 'cherry', 'mango'] +``` + +リストは非常に柔軟性が高く、Pythonで最も頻繁に使われるデータ構造の一つです。 diff --git a/public/docs/python/2-collections/2-0-tuple.md b/public/docs/python/2-collections/2-0-tuple.md new file mode 100644 index 0000000..9cd3d1f --- /dev/null +++ b/public/docs/python/2-collections/2-0-tuple.md @@ -0,0 +1,44 @@ +--- +id: python-collections-tuple +title: タプル (Tuple):イミュータブルなシーケンス +level: 2 +--- + +## タプル (Tuple):イミュータブルなシーケンス + +タプルはリストと非常によく似ていますが、最大の違いは**イミュータブル (Immutable)**、つまり一度作成したら変更できない点です。 + + * **イミュータブル (Immutable)**: 要素の変更、追加、削除はできません。 + * **順序あり (Ordered)**: リスト同様、順序を保持します。 + +**なぜタプルを使うのか?** 🤔 + +1. **安全なデータ**: 変更されたくないデータを安全に保持できます(例: 関数の引数、定数セット)。 +2. **パフォーマンス**: 一般的にリストよりわずかに高速で、メモリ効率が良いとされています。 +3. **辞書のキーとして使用可能**: ミュータブルなリストは辞書のキーになれませんが、イミュータブルなタプルはキーとして使えます。 + +**基本的な使い方 (REPL実行例)** + +```python-repl +>>> # タプルの作成 (丸括弧を使用) +>>> coordinates = (10, 20) +>>> coordinates +(10, 20) + +>>> # 要素へのアクセス +>>> coordinates[0] +10 + +>>> # 変更しようとするとエラーが発生する +>>> coordinates[0] = 5 +Traceback (most recent call last): + File "", line 1, in +TypeError: 'tuple' object does not support item assignment + +>>> # アンパッキング (複数の変数に要素を一度に代入) +>>> x, y = coordinates +>>> x +10 +>>> y +20 +``` diff --git a/public/docs/python/2-collections/3-0-dictionary.md b/public/docs/python/2-collections/3-0-dictionary.md new file mode 100644 index 0000000..f46fdaf --- /dev/null +++ b/public/docs/python/2-collections/3-0-dictionary.md @@ -0,0 +1,41 @@ +--- +id: python-collections-dictionary +title: 辞書 (Dictionary):キーと値のペア +level: 2 +--- + +## 辞書 (Dictionary):キーと値のペア + +辞書は、他の言語の**ハッシュマップ**や**連想配列**に相当します。順序ではなく、一意な「キー」を使って「値」にアクセスします。 + + * **キーと値のペア**: `key: value` の形式でデータを格納します。 + * **一意なキー**: キーは辞書内で重複してはいけません。 + * **順序**: Python 3.7以降では、要素が追加された順序が保持されます。 + * **ミュータブル**: 要素の追加、変更、削除が可能です。 + +**基本的な使い方 (REPL実行例)** + +```python-repl +>>> # 辞書の作成 +>>> person = {'name': 'Taro Yamada', 'age': 30, 'city': 'Tokyo'} +>>> person +{'name': 'Taro Yamada', 'age': 30, 'city': 'Tokyo'} + +>>> # 値へのアクセス (キーを使用) +>>> person['name'] +'Taro Yamada' + +>>> # 値の変更 +>>> person['age'] = 31 +>>> person['age'] +31 + +>>> # 新しいキーと値のペアの追加 +>>> person['job'] = 'Engineer' +>>> person +{'name': 'Taro Yamada', 'age': 31, 'city': 'Tokyo', 'job': 'Engineer'} + +>>> # キーと値のペアをまとめて取得 +>>> person.items() +dict_items([('name', 'Taro Yamada'), ('age', 31), ('city', 'Tokyo'), ('job', 'Engineer')]) +``` diff --git a/public/docs/python/2-collections/4-0-set.md b/public/docs/python/2-collections/4-0-set.md new file mode 100644 index 0000000..5cae74c --- /dev/null +++ b/public/docs/python/2-collections/4-0-set.md @@ -0,0 +1,50 @@ +--- +id: python-collections-set +title: セット (Set):ユニークな要素のコレクション +level: 2 +--- + +## セット (Set):ユニークな要素のコレクション + +セットは、**順序がなく、重複した要素を持たない**コレクションです。数学の「集合」の概念に近く、和集合や積集合といった集合演算を高速に行えます。 + + * **ユニークな要素**: 同じ要素を複数含めることはできません。 + * **順序なし (Unordered)**: 要素の格納順は保証されません。 + * **ミュータブル**: 要素の追加、削除は可能です。 + +**使いどころ** + + * リストなどから重複した要素を効率的に削除したい場合。 + * 二つのコレクションに共通する要素(積集合)や、全ての要素(和集合)を求めたい場合。 + +**基本的な使い方 (REPL実行例)** + +```python-repl +>>> # セットの作成 (重複した4は自動的に無視される) +>>> numbers = {1, 2, 3, 4, 4, 5} +>>> numbers +{1, 2, 3, 4, 5} + +>>> # 要素の追加 +>>> numbers.add(6) +>>> numbers +{1, 2, 3, 4, 5, 6} + +>>> # 重複削除への応用 +>>> my_list = ['a', 'b', 'c', 'a', 'b'] +>>> unique_elements = set(my_list) +>>> unique_elements +{'c', 'a', 'b'} + +>>> # 集合演算 +>>> set_a = {1, 2, 3, 4} +>>> set_b = {3, 4, 5, 6} + +>>> # 和集合 (A ∪ B) +>>> set_a | set_b +{1, 2, 3, 4, 5, 6} + +>>> # 積集合 (A ∩ B) +>>> set_a & set_b +{3, 4} +``` diff --git a/public/docs/python/2-collections/5-0-slicing.md b/public/docs/python/2-collections/5-0-slicing.md new file mode 100644 index 0000000..3d2ef3e --- /dev/null +++ b/public/docs/python/2-collections/5-0-slicing.md @@ -0,0 +1,35 @@ +--- +id: python-collections-slicing +title: スライシングによる部分的な要素の取得 +level: 2 +--- + +## スライシングによる部分的な要素の取得 + +スライシングは、リストやタプルのようなシーケンスから、部分的な要素を効率的に取り出すための非常に強力な機能です。構文は `[start:stop:step]` です。 + +**REPL実行例** + +```python-repl +>>> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + +>>> # インデックス1から4の手前まで +>>> numbers[1:4] +[1, 2, 3] + +>>> # 最初からインデックス5の手前まで +>>> numbers[:5] +[0, 1, 2, 3, 4] + +>>> # インデックス6から最後まで +>>> numbers[6:] +[6, 7, 8, 9] + +>>> # 2つおきに要素を取得 +>>> numbers[::2] +[0, 2, 4, 6, 8] + +>>> # 逆順にする +>>> numbers[::-1] +[9, 8, 7, 6, 5, 4, 3, 2, 1, 0] +``` diff --git a/public/docs/python/2-collections/6-0-comprehensions.md b/public/docs/python/2-collections/6-0-comprehensions.md new file mode 100644 index 0000000..186d5b9 --- /dev/null +++ b/public/docs/python/2-collections/6-0-comprehensions.md @@ -0,0 +1,9 @@ +--- +id: python-collections-comprehensions +title: 内包表記 (Comprehensions)による効率的な生成 +level: 2 +--- + +## 内包表記 (Comprehensions)による効率的な生成 + +内包表記は、既存のイテラブルから新しいリスト、辞書、セットを簡潔かつ効率的に生成するためのPythonらしい構文です。`for`ループを使うよりも短く、可読性が高いコードを書くことができます。 diff --git a/public/docs/python/2-collections/6-1-list-comprehension.md b/public/docs/python/2-collections/6-1-list-comprehension.md new file mode 100644 index 0000000..76d5723 --- /dev/null +++ b/public/docs/python/2-collections/6-1-list-comprehension.md @@ -0,0 +1,29 @@ +--- +id: python-collections-list-comprehension +title: リスト内包表記 +level: 3 +--- + +### リスト内包表記 + +`for`ループで書く場合と、リスト内包表記で書く場合を比較してみましょう。 + +```python-repl +>>> # forループの場合 +>>> squares_loop = [] +>>> for i in range(10): +... squares_loop.append(i * i) +... +>>> squares_loop +[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +>>> # リスト内包表記の場合 (簡潔!) +>>> squares_comp = [i * i for i in range(10)] +>>> squares_comp +[0, 1, 4, 9, 16, 25, 36, 49, 64, 81] + +>>> # 条件付きも可能 (偶数のみ2乗) +>>> even_squares = [i * i for i in range(10) if i % 2 == 0] +>>> even_squares +[0, 4, 16, 36, 64] +``` diff --git a/public/docs/python/2-collections/6-2-dict-comprehension.md b/public/docs/python/2-collections/6-2-dict-comprehension.md new file mode 100644 index 0000000..0b452ea --- /dev/null +++ b/public/docs/python/2-collections/6-2-dict-comprehension.md @@ -0,0 +1,14 @@ +--- +id: python-collections-dict-comprehension +title: 辞書内包表記 +level: 3 +--- + +### 辞書内包表記 + +```python-repl +>>> # 数値をキー、その2乗を値とする辞書を作成 +>>> square_dict = {x: x*x for x in range(5)} +>>> square_dict +{0: 0, 1: 1, 2: 4, 3: 9, 4: 16} +``` diff --git a/public/docs/python/2-collections/6-3-set-comprehension.md b/public/docs/python/2-collections/6-3-set-comprehension.md new file mode 100644 index 0000000..dc9e239 --- /dev/null +++ b/public/docs/python/2-collections/6-3-set-comprehension.md @@ -0,0 +1,15 @@ +--- +id: python-collections-set-comprehension +title: セット内包表記 +level: 3 +--- + +### セット内包表記 + +```python-repl +>>> # リスト内のユニークな数値の2乗のセットを作成 +>>> numbers = [1, 2, 2, 3, 4, 4, 5] +>>> square_set = {x*x for x in numbers} +>>> square_set +{1, 4, 9, 16, 25} +``` diff --git a/public/docs/python/2-collections/7-0-summary.md b/public/docs/python/2-collections/7-0-summary.md new file mode 100644 index 0000000..6b40e8a --- /dev/null +++ b/public/docs/python/2-collections/7-0-summary.md @@ -0,0 +1,18 @@ +--- +id: python-collections-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonでデータを扱うための基本的な4つのコレクションを学びました。それぞれの特性を理解し、状況に応じて適切に使い分けることが重要です。 + +| データ構造 | 構文例 | 変更可能性 | 順序 | 重複 | 主な用途 | +| :--- | :--- | :--- | :--- | :--- | :--- | +| **リスト (List)** | `[1, 'a', 2]` | **可能** (ミュータブル) | **あり** | 許可 | 順序があり、変更が必要な要素の集まり。 | +| **タプル (Tuple)** | `(1, 'a', 2)` | **不可能** (イミュータブル) | **あり** | 許可 | 変更しない(させたくない)データの集まり、辞書のキー。 | +| **辞書 (Dictionary)** | `{'key': 'value'}` | **可能** (ミュータブル) | **あり** (Python 3.7+) | キーは不許可 | キーと値のペアでデータを管理。 | +| **セット (Set)** | `{1, 'a', 2}` | **可能** (ミュータブル) | **なし** | 不許可 | 重複を除き、要素の存在確認や集合演算を高速に行う。 | + +加えて、**スライシング**を使えばシーケンス(リストやタプル)から部分的な要素を柔軟に取得でき、**内包表記**を利用すれば、これらのコレクションを一行で効率的かつPythonらしく生成できます。これらのツールは、あなたのコードをより簡潔で強力なものにしてくれるでしょう。 diff --git a/public/docs/python/2-collections/7-1-practice1.md b/public/docs/python/2-collections/7-1-practice1.md new file mode 100644 index 0000000..9e1b75a --- /dev/null +++ b/public/docs/python/2-collections/7-1-practice1.md @@ -0,0 +1,27 @@ +--- +id: python-collections-practice1 +title: '練習問題1: 商品のフィルタリング' +level: 3 +--- + +### 練習問題1: 商品のフィルタリング + +ある店舗の商品のリストがあります。このリストから、価格が500円以上の商品だけを抽出し、その名前だけを新しいリストに格納してください。 + +**ヒント:** +リスト内包表記と、辞書の値にアクセスする方法 (`product['price']`) を組み合わせ、`if` 条件を追加してみましょう。 + +```python:practice3_1.py +products = [ + {'name': 'Apple', 'price': 150}, + {'name': 'Banana', 'price': 100}, + {'name': 'Melon', 'price': 600}, + {'name': 'Orange', 'price': 120}, + {'name': 'Grape', 'price': 550} +] + +``` + +```python-exec:practice3_1.py +(出力例) ['Melon', 'Grape'] +``` diff --git a/public/docs/python/2-collections/7-2-practice2.md b/public/docs/python/2-collections/7-2-practice2.md new file mode 100644 index 0000000..086dd35 --- /dev/null +++ b/public/docs/python/2-collections/7-2-practice2.md @@ -0,0 +1,29 @@ +--- +id: python-collections-practice2 +title: '練習問題2: クラブ活動のメンバー分析' +level: 3 +--- + +### 練習問題2: クラブ活動のメンバー分析 + +2つのクラブ活動、「数学クラブ」と「科学クラブ」のメンバーリストがあります。セット(集合)の機能を使って、以下のメンバーリストを作成してください。 + +a. 両方のクラブに所属しているメンバー +b. 少なくともどちらか一方のクラブに所属している全メンバー +c. 数学クラブにのみ所属しているメンバー + +**ヒント:** +セットの積集合 (`&`)、和集合 (`|`)、差集合 (`-`) 演算子を使います。 + +```python:practice3_2.py +math_club = {'Alice', 'Bob', 'Charlie', 'David'} +science_club = {'Charlie', 'David', 'Eve', 'Frank'} + +``` + +```python-exec:practice3_2.py +(出力例) +a. {'Charlie', 'David'} +b. {'Alice', 'Bob', 'Charlie', 'David', 'Eve', 'Frank'} +c. {'Alice', 'Bob'} +``` diff --git a/public/docs/python/3-control-functions/-intro.md b/public/docs/python/3-control-functions/-intro.md new file mode 100644 index 0000000..f5335dd --- /dev/null +++ b/public/docs/python/3-control-functions/-intro.md @@ -0,0 +1 @@ +この章では、Pythonの基本的な制御構文(条件分岐、ループ)と関数の定義方法について学びます。他の言語にも同様の機能はありますが、特に`for`ループの振る舞いや、柔軟な引数の渡し方はPythonの大きな特徴です。これらの「Pythonらしい」書き方をマスターすることで、より簡潔で読みやすいコードを書けるようになります。 diff --git a/public/docs/python/3-control-functions/1-0-if.md b/public/docs/python/3-control-functions/1-0-if.md new file mode 100644 index 0000000..0661998 --- /dev/null +++ b/public/docs/python/3-control-functions/1-0-if.md @@ -0,0 +1,31 @@ +--- +id: python-control-functions-if +title: if/elif/elseによる条件分岐 +level: 2 +--- + +## `if`/`elif`/`else`による条件分岐 + +Pythonの条件分岐は`if`、`elif`(else ifの略)、`else`を使って記述します。C言語やJavaのような波括弧`{}`は使わず、**コロン`:`とインデント(通常は半角スペース4つ)**でコードブロックを表現するのが最大の特徴です。 + +条件式に`and`や`or`、`not`といった論理演算子も使用できます。 + +```python-repl +>>> score = 85 +>>> if score >= 90: +... print('優') +... elif score >= 80: +... print('良') +... elif score >= 70: +... print('可') +... else: +... print('不可') +... +良 +>>> temp = 25 +>>> is_sunny = True +>>> if temp > 20 and is_sunny: +... print("お出かけ日和です") +... +お出かけ日和です +``` diff --git a/public/docs/python/3-control-functions/2-0-for.md b/public/docs/python/3-control-functions/2-0-for.md new file mode 100644 index 0000000..a6f7a9d --- /dev/null +++ b/public/docs/python/3-control-functions/2-0-for.md @@ -0,0 +1,19 @@ +--- +id: python-control-functions-for +title: forループ +level: 2 +--- + +## `for`ループ + +Pythonの`for`ループは、他の言語の`for (int i = 0; i < 5; i++)`といったカウンタ変数を使うスタイルとは少し異なります。リストやタプル、文字列などの**イテラブル(反復可能)オブジェクト**から要素を1つずつ取り出して処理を実行します。これは、Javaの拡張for文やC\#の`foreach`に似ています。 + +```python-repl +>>> fruits = ['apple', 'banana', 'cherry'] +>>> for fruit in fruits: +... print(f"I like {fruit}") +... +I like apple +I like banana +I like cherry +``` diff --git a/public/docs/python/3-control-functions/2-1-range.md b/public/docs/python/3-control-functions/2-1-range.md new file mode 100644 index 0000000..f0cb806 --- /dev/null +++ b/public/docs/python/3-control-functions/2-1-range.md @@ -0,0 +1,20 @@ +--- +id: python-control-functions-range +title: range() 関数 +level: 3 +--- + +### `range()` 関数 + +決まった回数のループを実行したい場合は、`range()`関数が便利です。`range(n)`は0からn-1までの連続した数値を生成します。 + +```python-repl +>>> for i in range(5): +... print(i) +... +0 +1 +2 +3 +4 +``` diff --git a/public/docs/python/3-control-functions/2-2-enumerate.md b/public/docs/python/3-control-functions/2-2-enumerate.md new file mode 100644 index 0000000..a32acc3 --- /dev/null +++ b/public/docs/python/3-control-functions/2-2-enumerate.md @@ -0,0 +1,19 @@ +--- +id: python-control-functions-enumerate +title: enumerate() 関数 +level: 3 +--- + +### `enumerate()` 関数 + +ループ処理の中で、要素のインデックス(番号)と値の両方を使いたい場合があります。そのような時は`enumerate()`関数を使うと、コードが非常にスッキリします。これは非常にPythonらしい書き方の一つです。 + +```python-repl +>>> fruits = ['apple', 'banana', 'cherry'] +>>> for i, fruit in enumerate(fruits): +... print(f"Index: {i}, Value: {fruit}") +... +Index: 0, Value: apple +Index: 1, Value: banana +Index: 2, Value: cherry +``` diff --git a/public/docs/python/3-control-functions/3-0-while.md b/public/docs/python/3-control-functions/3-0-while.md new file mode 100644 index 0000000..e62a7d4 --- /dev/null +++ b/public/docs/python/3-control-functions/3-0-while.md @@ -0,0 +1,22 @@ +--- +id: python-control-functions-while +title: whileループ +level: 2 +--- + +## `while`ループ + +`while`ループは、指定された条件が`True`である間、処理を繰り返します。ループを途中で抜けたい場合は`break`を、現在の回の処理をスキップして次の回に進みたい場合は`continue`を使用します。 + +```python-repl +>>> n = 0 +>>> while n < 5: +... print(n) +... n += 1 +... +0 +1 +2 +3 +4 +``` diff --git a/public/docs/python/3-control-functions/4-0-def.md b/public/docs/python/3-control-functions/4-0-def.md new file mode 100644 index 0000000..f7af51e --- /dev/null +++ b/public/docs/python/3-control-functions/4-0-def.md @@ -0,0 +1,29 @@ +--- +id: python-control-functions-def +title: 関数の定義 (def) +level: 2 +--- + +## 関数の定義 (`def`) + +関数は`def`キーワードを使って定義します。ここでもコードブロックはコロン`:`とインデントで示します。値は`return`キーワードで返します。 + +引数と返り値に**型アノテーション(型ヒント)**を付けることもできます。これはコードの可読性を高め、静的解析ツールによるバグの発見を助けますが、実行時の動作に直接影響を与えるものではありません。 +型アノテーションは `引数名: 型` のように記述し、返り値の型は `-> 型:` のように記述します。 + +```python-repl +>>> def greet(name): +... """指定された名前で挨拶を返す関数""" # これはDocstringと呼ばれるドキュメント文字列です +... return f"Hello, {name}!" +... +>>> message = greet("Alice") +>>> print(message) +Hello, Alice! +>>> def greet(name: str) -> str: +... """指定された名前で挨拶を返す関数""" +... return f"Hello, {name}!" +... +>>> message = greet("Alice") +>>> print(message) +Hello, Alice! +``` diff --git a/public/docs/python/3-control-functions/5-0-arguments.md b/public/docs/python/3-control-functions/5-0-arguments.md new file mode 100644 index 0000000..8a3f910 --- /dev/null +++ b/public/docs/python/3-control-functions/5-0-arguments.md @@ -0,0 +1,35 @@ +--- +id: python-control-functions-arguments +title: 引数の渡し方 +level: 2 +--- + +## 引数の渡し方 + +Pythonの関数は、非常に柔軟な引数の渡し方ができます。型アノテーションと組み合わせることで、どのような型の引数を期待しているかがより明確になります。 + + * **位置引数 (Positional Arguments):** 最も基本的な渡し方で、定義された順序で値を渡します。 + * **キーワード引数 (Keyword Arguments):** `引数名=値`の形式で渡します。順序を問わないため、可読性が向上します。 + * **デフォルト引数値 (Default Argument Values):** 関数を定義する際に引数にデフォルト値を設定できます。呼び出し時にその引数が省略されると、デフォルト値が使われます。 + +```python-repl +>>> def describe_pet(animal_type: str, pet_name: str, owner_name: str = "Taro") -> None: +... # この関数は何も値を返さないため、返り値の型は None となります +... print(f"私には {animal_type} がいます。") +... print(f"名前は {pet_name} で、飼い主は {owner_name} です。") +... +>>> # 位置引数のみで呼び出し +>>> describe_pet("ハムスター", "ジャンボ") +私には ハムスター がいます。 +名前は ジャンボ で、飼い主は Taro です。 +>>> # キーワード引数で呼び出し(順序を逆にしてもOK) +>>> describe_pet(pet_name="ポチ", animal_type="犬") +私には 犬 がいます。 +名前は ポチ で、飼い主は Taro です。 +>>> # デフォルト引数を持つ引数を指定して呼び出し +>>> describe_pet("猫", "ミケ", "Hanako") +私には 猫 がいます。 +名前は ミケ で、飼い主は Hanako です。 +``` + +**注意点:** デフォルト引数を持つ引数は、持たない引数の後に定義する必要があります。 diff --git a/public/docs/python/3-control-functions/6-0-var-arg.md b/public/docs/python/3-control-functions/6-0-var-arg.md new file mode 100644 index 0000000..47320e7 --- /dev/null +++ b/public/docs/python/3-control-functions/6-0-var-arg.md @@ -0,0 +1,9 @@ +--- +id: python-control-functions-var-arg +title: '可変長引数 (*args, **kwargs)' +level: 2 +--- + +## 可変長引数 (`*args`, `**kwargs`) + +関数の引数の数が可変である場合に対応するための仕組みです。型アノテーションを使う場合は、`typing`モジュールから`Any`などをインポートすると便利です。 diff --git a/public/docs/python/3-control-functions/6-1-positional-var-arg.md b/public/docs/python/3-control-functions/6-1-positional-var-arg.md new file mode 100644 index 0000000..dcbd8a4 --- /dev/null +++ b/public/docs/python/3-control-functions/6-1-positional-var-arg.md @@ -0,0 +1,25 @@ +--- +id: python-control-functions-positional-var-arg +title: '*args' +level: 3 +--- + +### `*args` + +任意の数の**位置引数**をタプルとして受け取ります。型アノテーションでは `*args: 型` のように表現します。 + +```python-repl +>>> def sum_all(*numbers: int) -> int: +... print(f"受け取ったタプル: {numbers}") +... total = 0 +... for num in numbers: +... total += num +... return total +... +>>> print(sum_all(1, 2, 3)) +受け取ったタプル: (1, 2, 3) +6 +>>> print(sum_all(10, 20, 30, 40, 50)) +受け取ったタプル: (10, 20, 30, 40, 50) +150 +``` diff --git a/public/docs/python/3-control-functions/6-2-keyword-var-arg.md b/public/docs/python/3-control-functions/6-2-keyword-var-arg.md new file mode 100644 index 0000000..70bb94b --- /dev/null +++ b/public/docs/python/3-control-functions/6-2-keyword-var-arg.md @@ -0,0 +1,23 @@ +--- +id: python-control-functions-keyword-var-arg +title: '**kwargs' +level: 3 +--- + +### `**kwargs` + +任意の数の**キーワード引数**を辞書として受け取ります。型アノテーションでは `**kwargs: 型` のように表現します。どのような型の値も受け付ける場合は `Any` を使います。 + +```python-repl +>>> from typing import Any +>>> def print_profile(**user_info: Any) -> None: +... print(f"受け取った辞書: {user_info}") +... for key, value in user_info.items(): +... print(f"{key}: {value}") +... +>>> print_profile(name="Sato", age=28, city="Tokyo") +受け取った辞書: {'name': 'Sato', 'age': 28, 'city': 'Tokyo'} +name: Sato +age: 28 +city: Tokyo +``` diff --git a/public/docs/python/3-control-functions/7-0-lambda.md b/public/docs/python/3-control-functions/7-0-lambda.md new file mode 100644 index 0000000..b12b05f --- /dev/null +++ b/public/docs/python/3-control-functions/7-0-lambda.md @@ -0,0 +1,28 @@ +--- +id: python-control-functions-lambda +title: ラムダ式(Lambda expressions) +level: 2 +--- + +## ラムダ式(Lambda expressions) + +`lambda`キーワードを使うと、名前のない小さな**無名関数**を定義できます。 + +構文: `lambda 引数: 式` + +```python-repl +>>> # 通常の関数で2つの数を足す +>>> def add(x: int, y: int) -> int: +... return x + y +... +>>> # ラムダ式で同じ処理を定義 +>>> add_lambda = lambda x, y: x + y +>>> print(add_lambda(3, 5)) +8 +>>> # sorted関数のキーとして利用する例 +>>> students = [('Taro', 80), ('Jiro', 95), ('Saburo', 75)] +>>> # 成績(タプルの2番目の要素)でソートする +>>> sorted_students = sorted(students, key=lambda student: student[1], reverse=True) +>>> print(sorted_students) +[('Jiro', 95), ('Taro', 80), ('Saburo', 75)] +``` diff --git a/public/docs/python/3-control-functions/8-0-summary.md b/public/docs/python/3-control-functions/8-0-summary.md new file mode 100644 index 0000000..4d94bfc --- /dev/null +++ b/public/docs/python/3-control-functions/8-0-summary.md @@ -0,0 +1,17 @@ +--- +id: python-control-functions-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonの制御構文と関数の基本を学びました。他の言語の経験がある方にとって、特に以下の点はPythonの特徴として重要です。 + + * **インデントが構文の一部**: 波括弧`{}`の代わりにインデントでコードブロックを定義するため、自然と誰が書いても読みやすいコードスタイルになります。 + * **`for`ループはイテラブルを巡る**: `for item in collection:` の形が基本です。インデックスが必要な場合は、`for i, item in enumerate(collection):` のように`enumerate()`を使うのがPythonらしい書き方です。 + * **柔軟な関数引数**: **キーワード引数**、**デフォルト引数値**、そして**可変長引数 (`*args`, `**kwargs`)** を使いこなすことで、非常に柔軟で再利用性の高い関数を作成できます。 + * **型アノテーション**: 引数や返り値に型ヒントを付けることで、関数の意図が明確になり、コードの信頼性が向上します。 + * **ラムダ式**: ちょっとした処理をその場で関数として渡したい場合に、`lambda`はコードを簡潔に保つのに役立ちます。 + +これらの機能を理解し使いこなすことが、より効率的で「Pythonic(パイソニック)」なコードを書くための第一歩となります。 diff --git a/public/docs/python/3-control-functions/8-1-practice1.md b/public/docs/python/3-control-functions/8-1-practice1.md new file mode 100644 index 0000000..988cd67 --- /dev/null +++ b/public/docs/python/3-control-functions/8-1-practice1.md @@ -0,0 +1,27 @@ +--- +id: python-control-functions-practice1 +title: '練習問題1: 偶数とそのインデックスの発見' +level: 3 +--- + +### 練習問題1: 偶数とそのインデックスの発見 + +数値のリストが与えられたとき、そのリストに含まれる**偶数**とその**インデックス(位置番号)**だけを出力するプログラムを書いてください。 + +**ヒント:** + + * `for`ループと`enumerate()`を組み合わせます。 + * 数値が偶数かどうかは、`%`(剰余)演算子を使って、2で割った余りが0になるかで判定できます (`number % 2 == 0`)。 + +```python:practice4_1.py +numbers: list[int] = [8, 15, 22, 37, 40, 51, 68] + +``` + +```python-exec:practice4_1.py +(出力例) +インデックス: 0, 値: 8 +インデックス: 2, 値: 22 +インデックス: 4, 値: 40 +インデックス: 6, 値: 68 +``` diff --git a/public/docs/python/3-control-functions/8-2-practice2.md b/public/docs/python/3-control-functions/8-2-practice2.md new file mode 100644 index 0000000..a43c786 --- /dev/null +++ b/public/docs/python/3-control-functions/8-2-practice2.md @@ -0,0 +1,39 @@ +--- +id: python-control-functions-practice2 +title: '練習問題2: ユーザープロフィール作成関数' +level: 3 +--- + +### 練習問題2: ユーザープロフィール作成関数 + +ユーザーのプロフィール情報を出力する関数 `create_profile` を作成してください。引数と返り値には型アノテーションを付けてください。 + +**要件:** + +1. `name`(名前)は`str`型で、必須の引数とします。 +2. `age`(年齢)と `city`(都市)は`str`型で、キーワード引数として任意に受け取れるようにします。もし指定されなかった場合は、年齢は「秘密」、都市は「不明」と表示されるようにしてください。 +3. この関数は値を返さないものとします。 +4. 関数を呼び出し、異なるパターンでプロフィールが出力されることを確認してください。 + +**ヒント:** + + * `age`と`city`にはデフォルト引数値を設定します。 + * 値を返さない関数の返り値の型アノテーションは `-> None` です。 + +```python:practice4_2.py +def create_profile( +``` + +```python-exec:practice4_2.py +(出力例) +--- プロフィール --- +名前: Tanaka +年齢: 秘密 +都市: 不明 +-------------------- +--- プロフィール --- +名前: Sato +年齢: 32 +都市: Osaka +-------------------- +``` diff --git a/public/docs/python/4-modules/-intro.md b/public/docs/python/4-modules/-intro.md new file mode 100644 index 0000000..1524666 --- /dev/null +++ b/public/docs/python/4-modules/-intro.md @@ -0,0 +1 @@ +プログラミングを進めていくと、コードは必然的に長くなり、一つのファイルで管理するのが難しくなってきます。機能ごとにファイルを分割し、再利用しやすく、メンテナンスしやすい構造にすることが、効率的な開発の鍵となります。この章では、Pythonでコードを整理するための**モジュール**と**パッケージ**という仕組み、そしてPythonの強力な武器である**標準ライブラリ**の活用方法について学びます。 diff --git a/public/docs/python/4-modules/1-0-basic.md b/public/docs/python/4-modules/1-0-basic.md new file mode 100644 index 0000000..1afadc5 --- /dev/null +++ b/public/docs/python/4-modules/1-0-basic.md @@ -0,0 +1,9 @@ +--- +id: python-modules-basic +title: モジュール:コードを部品化する +level: 2 +--- + +## モジュール:コードを部品化する + +Pythonでは、**1つの `.py` ファイルが1つのモジュール**として扱われます。モジュールを使うことで、関連する関数やクラスを一つのファイルにまとめ、他のプログラムから再利用可能な「部品」として扱うことができます。これは、他の言語におけるライブラリやソースファイルのインポート機能に似ています。 diff --git a/public/docs/python/4-modules/1-1-import-math.md b/public/docs/python/4-modules/1-1-import-math.md new file mode 100644 index 0000000..2ca186a --- /dev/null +++ b/public/docs/python/4-modules/1-1-import-math.md @@ -0,0 +1,21 @@ +--- +id: python-modules-import-math +title: import文の基本 +level: 3 +--- + +### `import`文の基本 + +モジュールを利用するには `import` 文を使います。Pythonには多くの便利なモジュールが標準で用意されています(これらを**標準ライブラリ**と呼びます)。例えば、数学的な計算を行う `math` モジュールを使ってみましょう。 + +```python-repl +>>> # mathモジュールをインポート +>>> import math +>>> # mathモジュール内の変数や関数を利用する +>>> print(math.pi) # 円周率π +3.141592653589793 +>>> print(math.sqrt(16)) # 16の平方根 +4.0 +``` + +毎回 `math.` と書くのが面倒な場合は、いくつかの書き方があります。 diff --git a/public/docs/python/4-modules/1-2-import-as.md b/public/docs/python/4-modules/1-2-import-as.md new file mode 100644 index 0000000..d311e8f --- /dev/null +++ b/public/docs/python/4-modules/1-2-import-as.md @@ -0,0 +1,16 @@ +--- +id: python-modules-import-as +title: 'import...as: モジュールに別名をつけて利用する' +level: 4 +--- + +#### `import...as`: モジュールに別名をつけて利用する + +```python-repl +>>> import math as m +>>> +>>> print(m.pi) +3.141592653589793 +>>> print(m.sqrt(16)) +4.0 +``` diff --git a/public/docs/python/4-modules/1-3-from-import.md b/public/docs/python/4-modules/1-3-from-import.md new file mode 100644 index 0000000..afab5ad --- /dev/null +++ b/public/docs/python/4-modules/1-3-from-import.md @@ -0,0 +1,18 @@ +--- +id: python-modules-from-import +title: 'from...import: モジュールから特定の関数や変数だけを取り込む' +level: 4 +--- + +#### `from...import`: モジュールから特定の関数や変数だけを取り込む + +```python-repl +>>> from math import pi, sqrt +>>> +>>> print(pi) # 直接piを参照できる +3.141592653589793 +>>> print(sqrt(16)) # 直接sqrtを参照できる +4.0 +``` + +> **注意** ⚠️: `from math import *` のようにアスタリスク (`*`) を使うと、そのモジュールのすべての名前(関数、変数、クラス)が現在の名前空間にインポートされます。一見便利に見えますが、どの名前がどこから来たのか分からなくなり、意図しない名前の上書きを引き起こす可能性があるため、**特別な理由がない限り避けるべき**です。 diff --git a/public/docs/python/4-modules/1-4-custom-module.md b/public/docs/python/4-modules/1-4-custom-module.md new file mode 100644 index 0000000..1ceabfe --- /dev/null +++ b/public/docs/python/4-modules/1-4-custom-module.md @@ -0,0 +1,66 @@ +--- +id: python-modules-custom-module +title: 自作モジュールの作成と利用 +level: 3 +--- + +### 自作モジュールの作成と利用 + +自分でモジュールを作成するのも簡単です。関連する関数をまとめた `.py` ファイルを作成するだけです。ここからは複数のファイルが必要になるため、再びスクリプトファイルで説明します。 + +1. **`utils.py` の作成**: + まず、便利な関数をまとめた `utils.py` というファイルを作成します。 + + ```python:utils.py + def say_hello(name): + """指定された名前で挨拶を返す""" + return f"Hello, {name}!" + + def get_list_average(numbers): + """数値リストの平均を計算する""" + if not numbers: + return 0 + return sum(numbers) / len(numbers) + + # このファイルが直接実行された場合にのみ以下のコードを実行 + if __name__ == "__main__": + print("This is a utility module.") + print(say_hello("World")) + avg = get_list_average([10, 20, 30]) + print(f"Average: {avg}") + ``` + + ```python-exec:utils.py + This is a utility module. + Hello, World! + Average: 20.0 + ``` + + > **`if __name__ == "__main__":` の重要性** + > この記述はPythonの定型句です。 + + > * スクリプトが**直接実行された**場合、そのスクリプトの `__name__` という特殊変数は `"__main__"` になります。 + > * スクリプトが**モジュールとして `import` された**場合、`__name__` はファイル名(この場合は `"utils"`)になります。 + > これにより、モジュールとしてインポートされた際には実行したくないテストコードやデモコードを記述することができます。他言語経験者にとっては、プログラムの「エントリーポイント」を定義する `main` 関数のような役割と考えると分かりやすいでしょう。 + +2. **`main.py` からの利用**: + 次に、`utils.py` と同じディレクトリに `main.py` を作成し、`utils` モジュールをインポートして使います。 + + ```python:main.py + # 自作のutilsモジュールをインポート + import utils + + greeting = utils.say_hello("Alice") + print(greeting) + + scores = [88, 92, 75, 100] + average_score = utils.get_list_average(scores) + print(f"Your average score is: {average_score}") + ``` + + ```python-exec:main.py + Hello, Alice! + Your average score is: 88.75 + ``` + +このように、機能ごとにファイルを分割することで、コードの見通しが良くなり、再利用も簡単になります。 diff --git a/public/docs/python/4-modules/2-0-package.md b/public/docs/python/4-modules/2-0-package.md new file mode 100644 index 0000000..b921271 --- /dev/null +++ b/public/docs/python/4-modules/2-0-package.md @@ -0,0 +1,9 @@ +--- +id: python-modules-package +title: パッケージ:モジュールをまとめる +level: 2 +--- + +## パッケージ:モジュールをまとめる + +プロジェクトがさらに大きくなると、多数のモジュールを管理する必要が出てきます。**パッケージ**は、複数のモジュールをディレクトリ構造で階層的に管理するための仕組みです。 diff --git a/public/docs/python/4-modules/2-1-init-py.md b/public/docs/python/4-modules/2-1-init-py.md new file mode 100644 index 0000000..dea94ef --- /dev/null +++ b/public/docs/python/4-modules/2-1-init-py.md @@ -0,0 +1,58 @@ +--- +id: python-modules-init-py +title: パッケージの概念と __init__.py +level: 3 +--- + +### パッケージの概念と `__init__.py` + +パッケージは、簡単に言うと**モジュールが入ったディレクトリ**です。Pythonにそのディレクトリをパッケージとして認識させるために、`__init__.py` という名前のファイルを置きます(近年のPythonでは必須ではありませんが、互換性や明示性のために置くのが一般的です)。 + +以下のようなディレクトリ構造を考えてみましょう。 + +``` +my_project/ +├── main.py +└── my_app/ + ├── __init__.py + ├── models.py + └── services.py +``` + + * `my_app` がパッケージ名になります。 + * `__init__.py` は空でも構いません。このファイルが存在することで、`my_app` ディレクトリは単なるフォルダではなく、Pythonのパッケージとして扱われます。 + * `models.py` と `services.py` が、`my_app` パッケージに含まれるモジュールです。 + +`main.py` からこれらのモジュールをインポートするには、`パッケージ名.モジュール名` のように記述します。 + +```python +# パッケージ内のモジュールをインポート +from my_app import services + +# servicesモジュール内の関数を実行 (仮の関数) +# user_data = services.fetch_user_data(user_id=123) +# print(user_data) +``` + +`__init__.py` には、パッケージがインポートされた際の初期化処理を記述することもできます。例えば、特定のモジュールから関数をパッケージのトップレベルにインポートしておくと、利用側でより短い記述でアクセスできるようになります。 + +```python +# my_app/__init__.py + +# servicesモジュールからfetch_user_data関数をインポート +from .services import fetch_user_data + +print("my_app package has been initialized.") +``` + +このようにしておくと、`main.py` から以下のように直接関数をインポートできます。 + +```python +# main.py + +# __init__.pyで設定したおかげで、短いパスでインポートできる +from my_app import fetch_user_data + +user_data = fetch_user_data(user_id=123) +print(user_data) +``` diff --git a/public/docs/python/4-modules/3-0-std-library.md b/public/docs/python/4-modules/3-0-std-library.md new file mode 100644 index 0000000..98481cc --- /dev/null +++ b/public/docs/python/4-modules/3-0-std-library.md @@ -0,0 +1,12 @@ +--- +id: python-modules-std-library +title: 標準ライブラリ:Pythonに備わった強力なツール群 +level: 2 +--- + +## 標準ライブラリ:Pythonに備わった強力なツール群 + +Pythonの大きな魅力の一つは、その「**バッテリー同梱 (Batteries Included)**」という哲学です。これは、Pythonをインストールしただけで、追加のインストールなしにすぐに使える膨大で強力な**標準ライブラリ**が付属していることを意味します。 +何かを実装したいと思ったら、まずは「Python 標準ライブラリ 〇〇」で検索してみると、車輪の再発明を防ぐことができます。 + +ここでは、日常的によく使われる標準ライブラリをいくつか紹介します。 diff --git a/public/docs/python/4-modules/3-1-find-std-library.md b/public/docs/python/4-modules/3-1-find-std-library.md new file mode 100644 index 0000000..4d8b400 --- /dev/null +++ b/public/docs/python/4-modules/3-1-find-std-library.md @@ -0,0 +1,30 @@ +--- +id: python-modules-find-std-library +title: 標準ライブラリの探索 +level: 3 +--- + +### 標準ライブラリの探索 + +どんなライブラリがあるかを知るには、公式ドキュメントが最も信頼できます。 + + * [**The Python Standard Library — Python 3.x documentation**](https://docs.python.org/3/library/index.html) + +また、REPLの `help()` や `dir()` を使うと、モジュールの内容を簡単に確認できます。 + +```python-repl +>>> import datetime +>>> # datetimeモジュールが持つ属性や関数のリストを表示 +>>> dir(datetime) +['MAXYEAR', 'MINYEAR', '__all__', '__builtins__', ..., 'date', 'datetime', 'time', 'timedelta', 'timezone', 'tzinfo'] +>>> +>>> # dateクラスのヘルプドキュメントを表示 +>>> help(datetime.date) +Help on class date in module datetime: + +class date(builtins.object) + | date(year, month, day) --> a date object + | + | Methods defined here: +(ヘルプ情報が続く) ... +``` diff --git a/public/docs/python/4-modules/3-2-os.md b/public/docs/python/4-modules/3-2-os.md new file mode 100644 index 0000000..e0ac629 --- /dev/null +++ b/public/docs/python/4-modules/3-2-os.md @@ -0,0 +1,19 @@ +--- +id: python-modules-os +title: os +level: 3 +--- + +### `os` + +オペレーティングシステムと対話するための機能を提供します。ファイルやディレクトリの操作、環境変数の取得などができます。 + +```python-repl +>>> import os +>>> # カレントディレクトリのファイル一覧を取得 +>>> os.listdir('.') +['hello.py', 'utils.py', 'main.py'] +>>> # OSに依存しない安全なパスの結合 +>>> os.path.join('data', 'file.txt') # Windowsなら 'data\\file.txt' +'data/file.txt' +``` diff --git a/public/docs/python/4-modules/3-3-sys.md b/public/docs/python/4-modules/3-3-sys.md new file mode 100644 index 0000000..b3d5f4c --- /dev/null +++ b/public/docs/python/4-modules/3-3-sys.md @@ -0,0 +1,17 @@ +--- +id: python-modules-sys +title: sys +level: 3 +--- + +### `sys` + +Pythonインタプリタ自体を制御するための機能を提供します。コマンドライン引数の取得や、Pythonの検索パスの確認などができます。 + +```python-repl +>>> import sys +>>> # Pythonのバージョンを表示 +>>> sys.version # 環境により異なります +'3.11.4 (main, Jun 7 2023, 10:13:09) [GCC 12.3.0]' +``` + diff --git a/public/docs/python/4-modules/3-4-datetime.md b/public/docs/python/4-modules/3-4-datetime.md new file mode 100644 index 0000000..5953033 --- /dev/null +++ b/public/docs/python/4-modules/3-4-datetime.md @@ -0,0 +1,20 @@ +--- +id: python-modules-datetime +title: datetime +level: 3 +--- + +### `datetime` + +日付や時刻を扱うための機能を提供します。 + +```python-repl +>>> import datetime +>>> # 現在の日時を取得 (実行時刻による) +>>> now = datetime.datetime.now() +>>> print(now) +2025-08-12 18:26:06.123456 +>>> # 日時をフォーマットして文字列にする +>>> now.strftime('%Y-%m-%d %H:%M:%S') +'2025-08-12 18:26:06' +``` diff --git a/public/docs/python/4-modules/3-5-json.md b/public/docs/python/4-modules/3-5-json.md new file mode 100644 index 0000000..cbad4d2 --- /dev/null +++ b/public/docs/python/4-modules/3-5-json.md @@ -0,0 +1,28 @@ +--- +id: python-modules-json +title: json +level: 3 +--- + +### `json` + +Web APIなどで広く使われているデータ形式であるJSONを扱うための機能を提供します。 + +```python-repl +>>> import json +>>> # Pythonの辞書型データ +>>> user = {"id": 1, "name": "Ken", "email": "ken@example.com"} +>>> # 辞書型をJSON形式の文字列に変換 (dumps: dump string) +>>> json_string = json.dumps(user, indent=2) +>>> print(json_string) +{ + "id": 1, + "name": "Ken", + "email": "ken@example.com" +} +>>> # JSON形式の文字列をPythonの辞書型に変換 (loads: load string) +>>> loaded_user = json.loads(json_string) +>>> loaded_user['name'] +'Ken' +``` + diff --git a/public/docs/python/4-modules/4-0-summary.md b/public/docs/python/4-modules/4-0-summary.md new file mode 100644 index 0000000..aba7634 --- /dev/null +++ b/public/docs/python/4-modules/4-0-summary.md @@ -0,0 +1,13 @@ +--- +id: python-modules-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonのコードが複雑になるにつれて重要性を増す、整理と再利用のテクニックを学びました。ここで学んだ概念は、小さなスクリプトから大規模なアプリケーションまで、あらゆるレベルのPythonプログラミングで役立ちます。 + + * **モジュール**: 1つの `.py` ファイルは1つの**モジュール**です。関連する関数やクラスをモジュールにまとめることで、コードを論理的な単位に分割できます。他のファイルからは `import` 文を使ってその機能を再利用できます。 + * **パッケージ**: **パッケージ**は、複数のモジュールを階層的なディレクトリ構造で管理する仕組みです。これにより、大規模なプロジェクトでも名前の衝突を避け、関連するコードをまとめて整理することができます。 + * **標準ライブラリ**: Pythonには「**バッテリー同梱**」という思想があり、`datetime` (日時)、`os` (OS機能)、`json` (データ形式) など、すぐに使える便利なモジュールが豊富に揃っています。これらを活用することで、開発を大幅に効率化できます。 diff --git a/public/docs/python/4-modules/4-1-practice1.md b/public/docs/python/4-modules/4-1-practice1.md new file mode 100644 index 0000000..65a111d --- /dev/null +++ b/public/docs/python/4-modules/4-1-practice1.md @@ -0,0 +1,34 @@ +--- +id: python-modules-practice1 +title: '練習問題1: 計算モジュールを作ろう 🔢' +level: 3 +--- + +### 練習問題1: 計算モジュールを作ろう 🔢 + +四則演算を行うための自作モジュールを作成し、別のファイルから利用してみましょう。 + +1. `calculator.py` というファイルを作成し、以下の4つの関数を定義してください。 + * `add(a, b)`: aとbの和を返す + * `subtract(a, b)`: aとbの差を返す + * `multiply(a, b)`: aとbの積を返す + * `divide(a, b)`: aをbで割った商を返す。ただし、`b` が `0` の場合は「ゼロで割ることはできません」という文字列を返すようにしてください。 +2. `practice5_1.py` というファイルを作成し、作成した `calculator` モジュールをインポートします。 +3. `practice5_1.py` の中で、`calculator` モジュールの各関数を少なくとも1回ずつ呼び出し、結果を `print` 関数で表示してください。 + +```python:calculator.py +def add(a, b): + +``` + +```python:practice5_1.py +``` + +```python-exec:practice5_1.py +(出力例) +10 + 5 = 15 +10 - 5 = 5 +10 * 5 = 50 +10 / 2 = 5.0 +10 / 0 = ゼロで割ることはできません +``` diff --git a/public/docs/python/4-modules/4-2-practice2.md b/public/docs/python/4-modules/4-2-practice2.md new file mode 100644 index 0000000..bd06556 --- /dev/null +++ b/public/docs/python/4-modules/4-2-practice2.md @@ -0,0 +1,39 @@ +--- +id: python-modules-practice2 +title: 練習問題2:日報データをJSONで作成しよう 📝 +level: 3 +--- + +### 練習問題2:日報データをJSONで作成しよう 📝 + +標準ライブラリの `datetime` と `json` を使って、簡単な日報データを作成するプログラムを書いてみましょう。 + +1. Pythonスクリプトを作成します。 +2. `datetime` モジュールを使って、**現在の日付**を `YYYY-MM-DD` 形式の文字列として取得します。 +3. 以下の情報を含むPythonの辞書を作成します。 + * `author`: あなたの名前 (文字列) + * `date`: 手順2で取得した日付文字列 + * `tasks`: その日に行ったタスクのリスト (例: `["会議", "資料作成", "メール返信"]`) +4. `json` モジュールを使い、手順3で作成した辞書を人間が読みやすい形式 (インデント付き) のJSON文字列に変換します。 +5. 変換後のJSON文字列を `print` 関数で表示してください。 + +**ヒント**: `datetime.datetime.now()` で現在時刻を取得し、`.strftime('%Y-%m-%d')` メソッドで日付をフォーマットできます。`json.dumps()` の `indent` 引数を指定すると、出力がきれになります。 + +```python:practice5_2.py +import datetime +import json +``` + +```python-exec:practice5_2.py +(出力例) +{ + "author": "山田 太郎", + "date": "2025-08-17", + "tasks": [ + "Pythonのモジュール学習", + "練習問題の実装", + "チームミーティング" + ], + "status": "完了" +} +``` diff --git a/public/docs/python/5-oop/-intro.md b/public/docs/python/5-oop/-intro.md new file mode 100644 index 0000000..23e099d --- /dev/null +++ b/public/docs/python/5-oop/-intro.md @@ -0,0 +1 @@ +Pythonのオブジェクト指向プログラミング(OOP)は、他の言語と考え方は似ていますが、よりシンプルで柔軟な構文を持っています。この章では、クラスの定義から継承、そしてPython特有の「マジックメソッド」まで、その基本を学びます。 diff --git a/public/docs/python/5-oop/1-0-class-instance.md b/public/docs/python/5-oop/1-0-class-instance.md new file mode 100644 index 0000000..02ad345 --- /dev/null +++ b/public/docs/python/5-oop/1-0-class-instance.md @@ -0,0 +1,25 @@ +--- +id: python-oop-class-instance +title: classの定義とインスタンス化 +level: 2 +--- + +## `class`の定義とインスタンス化 + +Pythonでは、`class`キーワードを使ってクラスを定義します。JavaやC++のように波括弧`{}`は使わず、インデントでブロックを示します。非常にシンプルです。 + +クラスを定義したら、関数を呼び出すように`クラス名()`と書くことで、そのクラスの**インスタンス**(オブジェクト)を生成できます。 + +```python:dog1.py +class Dog: + pass # passは何もしないことを示す文 + +# Dogクラスのインスタンスを作成 +my_dog = Dog() + +print(my_dog) +``` + +```python-exec:dog1.py +<__main__.Dog object at 0x10e85a4d0> +``` diff --git a/public/docs/python/5-oop/2-0-constructor.md b/public/docs/python/5-oop/2-0-constructor.md new file mode 100644 index 0000000..13870f0 --- /dev/null +++ b/public/docs/python/5-oop/2-0-constructor.md @@ -0,0 +1,35 @@ +--- +id: python-oop-constructor +title: コンストラクタ (__init__) と self +level: 2 +--- + +## コンストラクタ (`__init__`) と `self` + +Pythonのクラスでは、`__init__`という名前の特殊なメソッドがコンストラクタの役割を果たします。このメソッドは、クラスがインスタンス化される際に自動的に呼び出されます。 + +メソッドの最初の引数には`self`を書くのが慣習です。これはインスタンス自身への参照であり、JavaやC++の`this`に相当します。ただし、Pythonでは`self`を明示的に引数として記述する必要があります。 + +```python:dog2.py +class Dog: + # インスタンス生成時に呼び出されるコンストラクタ + def __init__(self, name, breed): + print(f"{name}という名前の犬が作成されました。") + # self.変数名 の形でインスタンス変数を定義 + self.name = name + self.breed = breed + +# インスタンス化する際に__init__のself以外の引数を渡すと、 +# `__init__`メソッドが `self`に`my_dog`インスタンス、`name`に`"ポチ"`、`breed`に`"柴犬"`を受け取って実行される +my_dog = Dog("ポチ", "柴犬") + +# インスタンス変数にアクセス +print(f"名前: {my_dog.name}") +print(f"犬種: {my_dog.breed}") +``` + +```python-exec:dog2.py +ポチという名前の犬が作成されました。 +名前: ポチ +犬種: 柴犬 +``` diff --git a/public/docs/python/5-oop/3-0-class-vars.md b/public/docs/python/5-oop/3-0-class-vars.md new file mode 100644 index 0000000..57b06ac --- /dev/null +++ b/public/docs/python/5-oop/3-0-class-vars.md @@ -0,0 +1,45 @@ +--- +id: python-oop-class-vars +title: インスタンス変数とクラス変数 +level: 2 +--- + +## インスタンス変数とクラス変数 + +Pythonのクラスには、2種類の変数があります。 + + * **インスタンス変数**: `self.変数名`のように`__init__`内などで定義され、**各インスタンスに固有**の値を持ちます。上の例の`name`や`age`がこれにあたります。 + * **クラス変数**: クラス定義の直下に書かれ、そのクラスから作られた**全てのインスタンスで共有**されます。 + +```python:dog3.py +class Dog: + # このクラスから作られるインスタンス全てで共有されるクラス変数 + species = "イヌ科" + + def __init__(self, name): + # このインスタンス固有のインスタンス変数 + self.name = name + +dog1 = Dog("ポチ") +dog2 = Dog("ハチ") + +# インスタンス変数へのアクセス +print(f"{dog1.name}も{dog2.name}も、") + +# クラス変数へのアクセス (インスタンス経由でもクラス経由でも可能) +print(f"種は同じく {dog1.species} です。") +print(f"Dogクラスの種は {Dog.species} です。") + +# クラス変数を変更すると、全てのインスタンスに影響が及ぶ +Dog.species = "ネコ科" +print(f"{dog1.name}の種は {dog1.species}") +print(f"{dog2.name}の種は {dog2.species}") +``` + +```python-exec:dog3.py +ポチもハチも、 +種は同じく イヌ科 です。 +Dogクラスの種は イヌ科 です。 +ポチの種は ネコ科 +ハチの種は ネコ科 +``` diff --git a/public/docs/python/5-oop/4-0-method.md b/public/docs/python/5-oop/4-0-method.md new file mode 100644 index 0000000..dfc26b0 --- /dev/null +++ b/public/docs/python/5-oop/4-0-method.md @@ -0,0 +1,28 @@ +--- +id: python-oop-method +title: メソッドの定義 +level: 2 +--- + +## メソッドの定義 + +クラス内で定義される関数をメソッドと呼びます。インスタンスのデータ(インスタンス変数)を操作するために使用します。 +メソッドを定義する際も、最初の引数には必ず`self`を指定する必要があります。これにより、メソッド内から`self`を通じてインスタンス変数にアクセスできます。 + +```python:dog4.py +class Dog: + def __init__(self, name): + self.name = name + + # barkというメソッドを定義 + # selfを介してインスタンス変数nameにアクセスする + def bark(self): + return f"{self.name}: ワン!" + +my_dog = Dog("ポチ") +print(my_dog.bark()) # メソッドの呼び出し +``` + +```python-exec:dog4.py +ポチ: ワン! +``` diff --git a/public/docs/python/5-oop/4-1-class-type-annotation.md b/public/docs/python/5-oop/4-1-class-type-annotation.md new file mode 100644 index 0000000..4aad0b0 --- /dev/null +++ b/public/docs/python/5-oop/4-1-class-type-annotation.md @@ -0,0 +1,35 @@ +--- +id: python-oop-class-type-annotation +title: クラスメンバーの型アノテーション +level: 3 +--- + +### クラスメンバーの型アノテーション + +型安全性を高めるために、クラス変数やインスタンス変数にも型アノテーション(型ヒント)を付けることができます。 + + * **クラス変数**: `変数名: 型 = 値` のように記述します。 + * **インスタンス変数**: `__init__`内で `self.変数名: 型 = 値` のように記述するか、クラス直下で `変数名: 型` と宣言だけしておくこともできます。 + +```python:dog5.py +class Dog: + # クラス変数の型アノテーション + species: str = "イヌ科" + + # インスタンス変数の型を宣言 + name: str + age: int + + def __init__(self, name: str, age: int): + self.name = name + self.age = age + + # メソッドの戻り値の型アノテーション + def bark(self) -> str: + return f"{self.name}: ワン!" + +my_dog = Dog("ポチ", 3) +``` + +```python-exec:dog5.py +``` diff --git a/public/docs/python/5-oop/5-0-inheritance.md b/public/docs/python/5-oop/5-0-inheritance.md new file mode 100644 index 0000000..b63de6c --- /dev/null +++ b/public/docs/python/5-oop/5-0-inheritance.md @@ -0,0 +1,52 @@ +--- +id: python-oop-inheritance +title: 継承 +level: 2 +--- + +## 継承 + +あるクラスの機能を引き継いだ新しいクラスを作成することを継承と呼びます。Pythonでは、クラス定義の際に`()`内に親クラス(基底クラス)を指定することで継承を行います。 + +子クラス(派生クラス)は親クラスのメソッドや変数を全て利用でき、必要に応じて上書き(オーバーライド)することも可能です。親クラスのメソッドを呼び出したい場合は`super()`を使います。 + +```python:dog6.py +# 親クラス +class Animal: + def __init__(self, name: str): + print("Animalの__init__が呼ばれました") + self.name = name + + def eat(self) -> str: + return f"{self.name}は食事中です。" + + def speak(self) -> str: + return "..." + +# Animalクラスを継承した子クラス +class Dog(Animal): + def __init__(self, name: str, breed: str): + print("Dogの__init__が呼ばれました") + # super()で親クラスの__init__を呼び出し、nameを初期化 + super().__init__(name) + self.breed = breed # Dogクラス独自のインスタンス変数を追加 + + # 親のメソッドをオーバーライド + def speak(self) -> str: + return f"{self.name}: ワン!" + + +dog = Dog("ポチ", "柴犬") + +# 親クラスのメソッドも使える +print(dog.eat()) +# オーバーライドしたメソッドが呼ばれる +print(dog.speak()) +``` + +```python-exec:dog6.py +Dogの__init__が呼ばれました +Animalの__init__が呼ばれました +ポチは食事中です。 +ポチ: ワン! +``` diff --git a/public/docs/python/5-oop/6-0-str-repr.md b/public/docs/python/5-oop/6-0-str-repr.md new file mode 100644 index 0000000..e5e3672 --- /dev/null +++ b/public/docs/python/5-oop/6-0-str-repr.md @@ -0,0 +1,52 @@ +--- +id: python-oop-str-repr +title: 基本的なマジックメソッド (__str__, __repr__) +level: 2 +--- + +## 基本的なマジックメソッド (`__str__`, `__repr__`) + +`__init__`のように、アンダースコア2つで囲まれた特殊なメソッドを**マジックメソッド**(または**ダンダーメソッド**)と呼びます。これらを定義することで、Pythonの組み込み関数の挙動をカスタマイズできます。 + + * `__str__(self)` + * `print()`関数や`str()`でオブジェクトを文字列に変換する際に呼び出されます。 + * 目的は、**人間にとって読みやすい**、非公式な文字列表現を返すことです。 + * `__repr__(self)` + * `repr()`関数で呼び出されるほか、`__str__`が定義されていない場合の`print()`や、インタラクティブシェルでオブジェクトを評価した際に使われます。 + * 目的は、**曖昧さのない**、公式な文字列表現を返すことです。理想的には、その文字列を評価すると同じオブジェクトを再作成できるような表現(例: `MyClass(arg1=1, arg2='B')`)が望ましいです。 + +```python:dog7.py +class Dog: + def __init__(self, name, age): + self.name = name + self.age = age + + # print()で表示したときの振る舞いを定義 + def __str__(self): + return f"名前: {self.name}, 年齢: {self.age}" + + # REPLでの評価やrepr()での振る舞いを定義 + def __repr__(self): + return f"Dog(name='{self.name}', age={self.age})" + +dog = Dog("ポチ", 3) + +# print()は__str__を呼び出す +print(dog) + +# str()も__str__を呼び出す +print(str(dog)) + +# repr()は__repr__を呼び出す +print(repr(dog)) + +# REPLやJupyter Notebookなどで変数をそのまま評価すると__repr__が表示される +# >>> dog +# Dog(name='ポチ', age=3) +``` + +```python-exec:dog7.py +名前: ポチ, 年齢: 3 +名前: ポチ, 年齢: 3 +Dog(name='ポチ', age=3) +``` diff --git a/public/docs/python/5-oop/8-0-summary.md b/public/docs/python/5-oop/8-0-summary.md new file mode 100644 index 0000000..8f8d3a4 --- /dev/null +++ b/public/docs/python/5-oop/8-0-summary.md @@ -0,0 +1,18 @@ +--- +id: python-oop-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonにおけるオブジェクト指向プログラミングの基本を学びました。 + + * **クラスとインスタンス**: `class`キーワードでクラスという「設計図」を定義し、`クラス名()`でインスタンスという「実体」を作成します。 + * **`__init__`と`self`**: `__init__`はインスタンス化の際に呼ばれるコンストラクタです。第一引数の`self`はインスタンス自身を指し、`self.変数名`の形でインスタンスごとにユニークな**インスタンス変数**を定義します。 + * **クラス変数**: クラス直下に定義され、全てのインスタンスで共有される変数です。 + * **メソッド**: クラス内で定義される関数で、インスタンスの振る舞いを表します。メソッドの第一引数も必ず`self`です。 + * **継承**: `class 子クラス(親クラス):`と書くことで、親クラスの機能を引き継いだ新しいクラスを作成できます。`super()`を使うことで、親クラスのメソッドを呼び出せます。 + * **マジックメソッド**: `__str__`や`__repr__`のように`__`で囲まれた特殊なメソッドで、`print()`などの組み込み関数の挙動をカスタマイズできます。 + +PythonのOOPは、JavaやC++に比べてシンプルで直感的な構文が特徴です。しかし、その裏側にある「すべてがオブジェクトである」という思想は一貫しており、非常に強力なプログラミングパラダイムです。 diff --git a/public/docs/python/5-oop/8-1-practice1.md b/public/docs/python/5-oop/8-1-practice1.md new file mode 100644 index 0000000..0a98d9a --- /dev/null +++ b/public/docs/python/5-oop/8-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: python-oop-practice1 +title: '練習問題1: Bookクラスの作成' +level: 3 +--- + +### 練習問題1: `Book`クラスの作成 + +書籍の情報を管理する`Book`クラスを作成してください。 + +**要件:** + +1. インスタンス化する際に、`title`(タイトル)と`author`(著者)を引数で受け取る。 +2. `info()`というメソッドを持ち、呼び出すと`「{タイトル}」- {著者}`という形式の文字列を返す。 +3. `print()`でインスタンスを直接表示した際に、`info()`メソッドと同じ文字列が表示されるようにする。 + +```python:practice6_1.py +class Book: + + +if __name__ == "__main__": + harry_potter = Book("ハリー・ポッターと賢者の石", "J.K. ローリング") + print(harry_potter.info()) + print(harry_potter) +``` + +```python-exec:practice6_1.py +「ハリー・ポッターと賢者の石」- J.K. ローリング +「ハリー・ポッターと賢者の石」- J.K. ローリング +``` diff --git a/public/docs/python/5-oop/8-2-practice2.md b/public/docs/python/5-oop/8-2-practice2.md new file mode 100644 index 0000000..be38625 --- /dev/null +++ b/public/docs/python/5-oop/8-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: python-oop-practice2 +title: '練習問題2: 継承を使ったEBookクラスの作成' +level: 3 +--- + +### 練習問題2: 継承を使った`EBook`クラスの作成 + +問題1で作成した`Book`クラスを継承して、電子書籍を表す`EBook`クラスを作成してください。 + +**要件:** + +1. `Book`クラスを継承する。 +2. インスタンス化の際に、`title`、`author`に加えて`file_size`(ファイルサイズ、MB単位)も引数で受け取る。 +3. `info()`メソッドを**オーバーライド**し、呼び出すと`「{タイトル}」- {著者} (ファイルサイズ: {file_size}MB)`という形式の文字列を返すように変更する。 + +```python:practice6_2.py +from practice6_1 import Book + +class EBook(Book): + + +if __name__ == "__main__": + ebook_version = EBook("Python実践入門", "掌田 津耶乃", 24) + print(ebook_version.info()) +``` + +```python-exec:practice6_2.py +「Python実践入門」- 掌田 津耶乃 (ファイルサイズ: 24MB) +``` diff --git a/public/docs/python/6-file-io/-intro.md b/public/docs/python/6-file-io/-intro.md new file mode 100644 index 0000000..0ac2529 --- /dev/null +++ b/public/docs/python/6-file-io/-intro.md @@ -0,0 +1 @@ +この章では、テキストファイルやJSON、CSVファイルの読み書きといった、実践的なファイル操作を学びます。特に、リソース管理を安全かつ簡潔に行うための **`with`** 文(コンテキストマネージャ)は、Pythonプログラミングにおいて必須の知識です。 diff --git a/public/docs/python/6-file-io/1-0-open.md b/public/docs/python/6-file-io/1-0-open.md new file mode 100644 index 0000000..381723d --- /dev/null +++ b/public/docs/python/6-file-io/1-0-open.md @@ -0,0 +1,29 @@ +--- +id: python-file-io-open +title: open()関数によるファイルのオープン +level: 2 +--- + +## `open()`関数によるファイルのオープン + +Pythonでファイルを操作するには、まず組み込み関数の **`open()`** を使ってファイルオブジェクトを取得します。`open()` は少なくとも2つの引数、ファイルパスとモードを取ります。 + + * **ファイルパス**: 操作したいファイルへのパス(例: `'data.txt'`)。 + * **モード**: ファイルをどのように開くかを指定する文字列。 + * `'r'`: 読み込み専用(デフォルト) + * `'w'`: 書き込み専用(ファイルが存在すれば上書き) + * `'a'`: 追記(ファイルの末尾に書き足す) + * `'x'`: 新規作成して書き込み(ファイルが存在するとエラー) + * `'+'` を付けると読み書き両用になります(例: `'r+'`, `'w+'`)。 + * `'b'` を付けるとバイナリモードになります(例: `'rb'`, `'wb'`)。 + +```python-repl +>>> # 'w' モードでファイルを開く(または新規作成する) +>>> f = open('spam.txt', 'w', encoding='utf-8') +>>> f +<_io.TextIOWrapper name='spam.txt' mode='w' encoding='utf-8'> +>>> # ファイルを使い終わったら必ず閉じる +>>> f.close() +``` + +**`encoding='utf-8'`** は、特に日本語のような非ASCII文字を扱う際に重要です。文字化けを防ぐため、テキストファイルを扱う際はエンコーディングを明示的に指定することを強く推奨します。ファイルを閉じる **`close()`** メソッドを呼び出すまで、ファイルリソースはプログラムによって確保されたままになります。 diff --git a/public/docs/python/6-file-io/2-0-textfile.md b/public/docs/python/6-file-io/2-0-textfile.md new file mode 100644 index 0000000..0dd691f --- /dev/null +++ b/public/docs/python/6-file-io/2-0-textfile.md @@ -0,0 +1,9 @@ +--- +id: python-file-io-textfile +title: テキストファイルの読み書き +level: 2 +--- + +## テキストファイルの読み書き + +ファイルオブジェクトのメソッドを使って、ファイルの内容を操作します。 diff --git a/public/docs/python/6-file-io/2-1-write.md b/public/docs/python/6-file-io/2-1-write.md new file mode 100644 index 0000000..941908e --- /dev/null +++ b/public/docs/python/6-file-io/2-1-write.md @@ -0,0 +1,25 @@ +--- +id: python-file-io-write +title: 書き込み +level: 3 +--- + +### 書き込み + +**`write()`** メソッドは、文字列をファイルに書き込みます。このメソッドは書き込んだ文字数を返します。 + +```python-repl +>>> f = open('test.txt', 'w', encoding='utf-8') +>>> f.write('こんにちは、世界!\n') +9 +>>> f.write('これは2行目です。\n') +9 +>>> f.close() +``` + +`write()` は自動的には改行しないため、必要であれば自分で改行コード `\n` を追加します。 + +```text-readonly:test.txt +こんにちは、世界! +これは2行目です。 +``` diff --git a/public/docs/python/6-file-io/2-2-read.md b/public/docs/python/6-file-io/2-2-read.md new file mode 100644 index 0000000..43d7ea9 --- /dev/null +++ b/public/docs/python/6-file-io/2-2-read.md @@ -0,0 +1,34 @@ +--- +id: python-file-io-read +title: 読み込み +level: 3 +--- + +### 読み込み + +ファイルからデータを読み込むには、いくつかの方法があります。 + + * **`read()`**: ファイルの内容全体を一つの文字列として読み込みます。 + * **`readline()`**: ファイルから1行だけを読み込み、文字列として返します。 + * **`readlines()`**: ファイルのすべての行を読み込み、各行を要素とするリストで返します。 + +```python-repl +>>> # 先ほど書き込んだファイルを読み込む +>>> f = open('test.txt', 'r', encoding='utf-8') +>>> content = f.read() +>>> print(content) +こんにちは、世界! +これは2行目です。 + +>>> f.close() + +>>> # readline() を使って1行ずつ読む +>>> f = open('test.txt', 'r', encoding='utf-8') +>>> f.readline() +'こんにちは、世界!\n' +>>> f.readline() +'これは2行目です。\n' +>>> f.readline() # ファイルの終端に達すると空文字列を返す +'' +>>> f.close() +``` diff --git a/public/docs/python/6-file-io/3-0-with.md b/public/docs/python/6-file-io/3-0-with.md new file mode 100644 index 0000000..11b4849 --- /dev/null +++ b/public/docs/python/6-file-io/3-0-with.md @@ -0,0 +1,38 @@ +--- +id: python-file-io-with +title: with文による安全なファイル操作(コンテキストマネージャ) +level: 2 +--- + +## `with`文による安全なファイル操作(コンテキストマネージャ) + +ファイルを `open()` したら `close()` する必要がありますが、処理中に例外が発生すると `close()` が呼ばれない可能性があります。これを確実に、そして簡潔に書く方法が **`with`** 文です。 + +**`with`** 文のブロックを抜けると、ファイルオブジェクトは自動的に `close()` されます。エラーが発生した場合でも同様です。これは「コンテキストマネージャ」という仕組みによって実現されており、ファイル操作の標準的な方法です。 + +```python-repl +>>> # with文を使った書き込み +>>> with open('spam.txt', 'w', encoding='utf-8') as f: +... f.write('withブロックを使っています。\n') +... f.write('ブロックを抜けると自動で閉じられます。\n') +... +>>> # ブロックの外ではファイルは閉じている +>>> f.closed +True + +>>> # with文を使った読み込み +>>> with open('spam.txt', 'r', encoding='utf-8') as f: +... data = f.read() +... +>>> print(data) +withブロックを使っています。 +ブロックを抜けると自動で閉じられます。 + +``` + +このように、`with` 文を使えば `close()` の呼び出しを忘れる心配がなく、コードもすっきりします。今後は常に `with` 文を使ってファイルを扱うようにしましょう。 + +```text-readonly:spam.txt +withブロックを使っています。 +ブロックを抜けると自動で閉じられます。 +``` diff --git a/public/docs/python/6-file-io/4-0-json-module.md b/public/docs/python/6-file-io/4-0-json-module.md new file mode 100644 index 0000000..a5007c8 --- /dev/null +++ b/public/docs/python/6-file-io/4-0-json-module.md @@ -0,0 +1,55 @@ +--- +id: python-file-io-json-module +title: jsonモジュールを使ったJSONの操作 +level: 2 +--- + +## `json`モジュールを使ったJSONの操作 + +**JSON (JavaScript Object Notation)** は、データ交換フォーマットとして広く使われています。Pythonの標準ライブラリである **`json`** モジュールを使うと、Pythonのオブジェクト(辞書やリストなど)をJSON形式のデータに、またはその逆に変換できます。 + + * **`json.dump(obj, fp)`**: Pythonオブジェクト `obj` をJSON形式でファイルオブジェクト `fp` に書き込みます。 + * **`json.load(fp)`**: JSON形式のファイルオブジェクト `fp` からデータを読み込み、Pythonオブジェクトに変換します。 + + + +```python-repl +>>> import json + +>>> # 書き込むデータ(Pythonの辞書) +>>> data = { +... "name": "Taro Yamada", +... "age": 30, +... "is_student": False, +... "courses": ["Python", "Machine Learning"] +... } + +>>> # with文を使ってJSONファイルに書き込む +>>> with open('user.json', 'w', encoding='utf-8') as f: +... # ensure_ascii=Falseで日本語をそのまま出力 +... json.dump(data, f, indent=4, ensure_ascii=False) +... + +>>> # JSONファイルから読み込む +>>> with open('user.json', 'r', encoding='utf-8') as f: +... loaded_data = json.load(f) +... +>>> loaded_data +{'name': 'Taro Yamada', 'age': 30, 'is_student': False, 'courses': ['Python', 'Machine Learning']} +>>> loaded_data['name'] +'Taro Yamada' +``` + +`json.dump()` の `indent=4` は、人間が読みやすいように4スペースのインデントを付けて出力するオプションです。`ensure_ascii=False` は、日本語などの非ASCII文字をそのままの文字で出力するために指定します。 + +```json-readonly:user.json +{ + "name": "Taro Yamada", + "age": 30, + "is_student": false, + "courses": [ + "Python", + "Machine Learning" + ] +} +``` diff --git a/public/docs/python/6-file-io/5-0-csv-module.md b/public/docs/python/6-file-io/5-0-csv-module.md new file mode 100644 index 0000000..d076aaa --- /dev/null +++ b/public/docs/python/6-file-io/5-0-csv-module.md @@ -0,0 +1,9 @@ +--- +id: python-file-io-csv-module +title: csvモジュールを使ったCSVの操作 +level: 2 +--- + +## `csv`モジュールを使ったCSVの操作 + +**CSV (Comma-Separated Values)** は、スプレッドシートやデータベースでよく使われる表形式のデータを保存するためのフォーマットです。**`csv`** モジュールを使うと、CSVファイルの読み書きが簡単になります。 diff --git a/public/docs/python/6-file-io/5-1-csv-write.md b/public/docs/python/6-file-io/5-1-csv-write.md new file mode 100644 index 0000000..fa0407c --- /dev/null +++ b/public/docs/python/6-file-io/5-1-csv-write.md @@ -0,0 +1,34 @@ +--- +id: python-file-io-csv-write +title: CSVファイルへの書き込み +level: 3 +--- + +### CSVファイルへの書き込み + +**`csv.writer()`** を使ってライターオブジェクトを作成し、**`writerow()`** (1行) や **`writerows()`** (複数行) メソッドでデータを書き込みます。 + +```python-repl +>>> import csv + +>>> # 書き込むデータ(リストのリスト) +>>> data_to_write = [ +... ["ID", "Name", "Score"], +... [1, "Alice", 95], +... [2, "Bob", 88], +... [3, "Charlie", 76] +... ] + +>>> # newline='' はWindowsでの不要な空行を防ぐためのおまじない +>>> with open('scores.csv', 'w', newline='', encoding='utf-8') as f: +... writer = csv.writer(f) +... writer.writerows(data_to_write) +... +``` + +```csv-readonly:scores.csv +ID,Name,Score +1,Alice,95 +2,Bob,88 +3,Charlie,76 +``` diff --git a/public/docs/python/6-file-io/5-2-csv-read.md b/public/docs/python/6-file-io/5-2-csv-read.md new file mode 100644 index 0000000..2ebc61f --- /dev/null +++ b/public/docs/python/6-file-io/5-2-csv-read.md @@ -0,0 +1,26 @@ +--- +id: python-file-io-csv-read +title: CSVファイルの読み込み +level: 3 +--- + +### CSVファイルの読み込み + +**`csv.reader()`** を使ってリーダーオブジェクトを作成します。このオブジェクトをループで回すことで、1行ずつリストとしてデータを取得できます。 + +```python-repl +>>> import csv + +>>> with open('scores.csv', 'r', newline='', encoding='-utf-8') as f: +... reader = csv.reader(f) +... # リーダーオブジェクトはイテレータなのでforループで回せる +... for row in reader: +... print(row) +... +['ID', 'Name', 'Score'] +['1', 'Alice', '95'] +['2', 'Bob', '88'] +['3', 'Charlie', '76'] +``` + +注意点として、`csv`モジュールはすべてのデータを文字列として読み込みます。数値として扱いたい場合は、自分で `int()` や `float()` を使って型変換する必要があります。 diff --git a/public/docs/python/6-file-io/6-0-summary.md b/public/docs/python/6-file-io/6-0-summary.md new file mode 100644 index 0000000..f2ade04 --- /dev/null +++ b/public/docs/python/6-file-io/6-0-summary.md @@ -0,0 +1,17 @@ +--- +id: python-file-io-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonを使った基本的なファイルの入出力について学びました。 + + - **`open()` 関数**: ファイルを開き、ファイルオブジェクトを取得するための基本です。モード (`'r'`, `'w'`, `'a'`) とエンコーディング (`'utf-8'`) の指定が重要です。 + - **ファイルメソッド**: **`.read()`**, **`.readline()`**, **`.write()`** といったメソッドを使って、ファイルの内容を操作します。 + - **`with` 文**: ファイルを自動的に閉じるための最も安全で推奨される方法です。**コンテキストマネージャ**の仕組みにより、後片付けが確実に行われます。 + - **`json` モジュール**: Pythonの辞書やリストを、広く使われているデータ形式であるJSONとして読み書きするために使用します。**`json.dump()`** と **`json.load()`** が中心的な関数です。 + - **`csv` モジュール**: カンマ区切りの表形式データを扱うためのモジュールです。**`csv.writer()`** と **`csv.reader()`** を使って、行単位での読み書きを簡単に行えます。 + +ファイル操作は、プログラムの設定を保存したり、処理結果を記録したり、他のシステムとデータをやり取りしたりするなど、あらゆるアプリケーションで必要となる基本的なスキルです。 diff --git a/public/docs/python/6-file-io/6-1-practice1.md b/public/docs/python/6-file-io/6-1-practice1.md new file mode 100644 index 0000000..f22c6b2 --- /dev/null +++ b/public/docs/python/6-file-io/6-1-practice1.md @@ -0,0 +1,24 @@ +--- +id: python-file-io-practice1 +title: '練習問題1: ユーザー情報の書き出しと読み込み' +level: 3 +--- + +### 練習問題1: ユーザー情報の書き出しと読み込み + +1. 以下の情報を持つユーザーのデータを、Pythonの辞書として作成してください。 + * `id`: 101 + * `name`: "Sato Kenji" + * `email`: "kenji.sato@example.com" +2. この辞書を、`with` 文と `json` モジュールを使って `user_profile.json` という名前のファイルに書き出してください。その際、人間が読みやすいようにインデントを付けてください。 +3. 書き出した `user_profile.json` ファイルを読み込み、内容をコンソールに表示してください。 + +```python:practice7_1.py +``` + +```python-exec:practice7_1.py +(出力例) {'id': 101, 'name': 'Sato Kenji', 'email': 'kenji.sato@example.com'} +``` + +```json-readonly:user_profile.json +``` diff --git a/public/docs/python/6-file-io/6-2-practice2.md b/public/docs/python/6-file-io/6-2-practice2.md new file mode 100644 index 0000000..cf6a2d4 --- /dev/null +++ b/public/docs/python/6-file-io/6-2-practice2.md @@ -0,0 +1,31 @@ +--- +id: python-file-io-practice2 +title: '練習問題2: 売上データのCSV集計' +level: 3 +--- + +### 練習問題2: 売上データのCSV集計 + +あなたは店舗の売上データ(CSV形式)を処理する必要があります。以下の手順でプログラムを作成してください。 + +1. `sales.csv` ファイルを読み込みモードで開きます。 +2. `csv.reader` を使ってデータを1行ずつ読み込み、ヘッダー行(1行目)は無視してください。 +3. 各商品の「価格」と「数量」を掛け合わせ、その行の売上金額を計算します。 +4. すべての商品の合計売上金額を計算し、最後に「合計売上: XXXX円」という形式でコンソールに出力してください。 + +**ヒント:** `csv` モジュールで読み込んだ値はすべて文字列型です。計算する前に `int()` を使って整数型に変換する必要があります。 + +```csv-readonly:sales.csv +商品,価格,数量 +リンゴ,120,3 +バナナ,80,5 +オレンジ,150,2 +ブドウ,300,1 +``` + +```python:practice7_2.py +``` + +```python-exec:practice7_2.py +(出力例) 合計売上: 1360円 +``` diff --git a/public/docs/python/7-exceptions/-intro.md b/public/docs/python/7-exceptions/-intro.md new file mode 100644 index 0000000..66b7ac1 --- /dev/null +++ b/public/docs/python/7-exceptions/-intro.md @@ -0,0 +1 @@ +プログラムの実行中に予期せぬ問題が発生すると、Pythonは「例外 (exception)」を送出して処理を中断します。これらのエラーを放置するとプログラムはクラッシュしてしまいますが、「例外処理」の仕組みを使うことで、エラーを優雅に捉えて対処できます。この章では、その方法を学びます。 diff --git a/public/docs/python/7-exceptions/1-0-try-except.md b/public/docs/python/7-exceptions/1-0-try-except.md new file mode 100644 index 0000000..615dccf --- /dev/null +++ b/public/docs/python/7-exceptions/1-0-try-except.md @@ -0,0 +1,32 @@ +--- +id: python-exceptions-try-except +title: try...exceptによる例外の捕捉 +level: 2 +--- + +## `try...except`による例外の捕捉 + +他の言語の `try...catch` と同様に、Pythonでは `try...except` ブロックを使います。エラーが発生する可能性のあるコードを `try` ブロックに記述し、エラーが発生した際の処理を `except` ブロックに記述します。 + +例えば、`0` で割り算をすると `ZeroDivisionError` という例外が発生します。 + +```python-repl +>>> 10 / 0 +Traceback (most recent call last): + File "", line 1, in +ZeroDivisionError: division by zero +``` + +このエラーを `try...except` で捕捉してみましょう。 + +```python:try-zero-division.py +try: + result = 10 / 0 +except ZeroDivisionError: + print("エラー: 0で割ることはできません。") +``` +```python-exec:try-zero-division.py +エラー: 0で割ることはできません。 +``` + +`try` ブロック内で `ZeroDivisionError` が発生したため、プログラムはクラッシュせずに `except` ブロック内の処理が実行されました。 diff --git a/public/docs/python/7-exceptions/2-0-multiple-error.md b/public/docs/python/7-exceptions/2-0-multiple-error.md new file mode 100644 index 0000000..fe25cc3 --- /dev/null +++ b/public/docs/python/7-exceptions/2-0-multiple-error.md @@ -0,0 +1,11 @@ +--- +id: python-exceptions-multiple-error +title: 複数の例外を処理する方法 +level: 2 +--- + +## 複数の例外を処理する方法 + +`try` ブロック内では、複数の種類のエラーが発生する可能性があります。例えば、ユーザーの入力を数値に変換しようとして失敗した場合は `ValueError` が発生します。 + +複数の例外を処理するには、2つの方法があります。 diff --git a/public/docs/python/7-exceptions/2-1-multiple-except.md b/public/docs/python/7-exceptions/2-1-multiple-except.md new file mode 100644 index 0000000..1516936 --- /dev/null +++ b/public/docs/python/7-exceptions/2-1-multiple-except.md @@ -0,0 +1,31 @@ +--- +id: python-exceptions-multiple-except +title: 1. except ブロックを複数記述する +level: 3 +--- + +### 1\. `except` ブロックを複数記述する + +エラーの種類ごとに異なる処理を行いたい場合に適しています。 + +```python:multiple-except.py +def calculate(a, b): + try: + a = int(a) + b = int(b) + result = a / b + print(f"計算結果: {result}") + except ValueError: + print("エラー: 数値を入力してください。") + except ZeroDivisionError: + print("エラー: 0で割ることはできません。") + +calculate(10, 2) +calculate(10, 0) +calculate('ten', 2) +``` +```python-exec:multiple-except.py +計算結果: 5.0 +エラー: 0で割ることはできません。 +エラー: 数値を入力してください。 +``` diff --git a/public/docs/python/7-exceptions/2-2-tuple-except.md b/public/docs/python/7-exceptions/2-2-tuple-except.md new file mode 100644 index 0000000..2750af4 --- /dev/null +++ b/public/docs/python/7-exceptions/2-2-tuple-except.md @@ -0,0 +1,29 @@ +--- +id: python-exceptions-tuple-except +title: 2. 1つの except ブロックでタプルを使ってまとめる +level: 3 +--- + +### 2\. 1つの `except` ブロックでタプルを使ってまとめる + +複数の例外に対して同じ処理を行いたい場合に便利です。 + +```python:tuple-except.py +def calculate_v2(a, b): + try: + a = int(a) + b = int(b) + result = a / b + print(f"計算結果: {result}") + except (ValueError, ZeroDivisionError) as e: + print(f"入力エラーが発生しました: {e}") + +calculate_v2(20, 0) +calculate_v2('twenty', 5) +``` +```python-exec:tuple-except.py +入力エラーが発生しました: division by zero +入力エラーが発生しました: invalid literal for int() with base 10: 'twenty' +``` + +`as e` のように書くことで、発生した例外オブジェクトそのものを変数 `e` で受け取ることができます。これにより、具体的なエラーメッセージを表示できます。 diff --git a/public/docs/python/7-exceptions/3-0-raise.md b/public/docs/python/7-exceptions/3-0-raise.md new file mode 100644 index 0000000..d05807a --- /dev/null +++ b/public/docs/python/7-exceptions/3-0-raise.md @@ -0,0 +1,28 @@ +--- +id: python-exceptions-raise +title: 独自例外の送出 (raise) +level: 2 +--- + +## 独自例外の送出 (`raise`) + +特定の条件を満たした場合に、意図的に例外を発生させたいことがあります。その場合は `raise` 文を使います。 + +例えば、負の値を受け付けない関数を考えてみましょう。 + +```python-repl +>>> def process_positive_number(num): +... if num < 0: +... raise ValueError("負の値は処理できません。") +... print(f"{num}を処理しました。") +... +>>> process_positive_number(100) +100を処理しました。 +>>> process_positive_number(-5) +Traceback (most recent call last): + File "", line 1, in + File "", line 3, in process_positive_number +ValueError: 負の値は処理できません。 +``` + +このように、`raise` を使うことで、関数の事前条件などを満たさない場合に、プログラムの実行を中断して呼び出し元にエラーを通知できます。 diff --git a/public/docs/python/7-exceptions/4-0-else-finally.md b/public/docs/python/7-exceptions/4-0-else-finally.md new file mode 100644 index 0000000..94033e8 --- /dev/null +++ b/public/docs/python/7-exceptions/4-0-else-finally.md @@ -0,0 +1,48 @@ +--- +id: python-exceptions-else-finally +title: elseとfinally節の役割 +level: 2 +--- + +## `else`と`finally`節の役割 + +`try...except` ブロックには、`else` と `finally` という2つのオプションの節を追加できます。 + + * **`else` 節**: `try` ブロックで**例外が発生しなかった場合**にのみ実行されます。 + * **`finally` 節**: 例外の有無に**関わらず、必ず最後に**実行されます。ファイルクローズやデータベース接続の切断など、後片付け処理に最適です。 + +すべての節を使った例を見てみましょう。 + +```python-repl +>>> def divider(a, b): +... print(f"--- {a} / {b} の計算を開始します ---") +... try: +... result = a / b +... except ZeroDivisionError: +... print("エラー: 0による除算です。") +... else: +... # 例外が発生しなかった場合に実行 +... print(f"計算成功! 結果: {result}") +... finally: +... # 常に最後に実行 +... print("--- 計算を終了します ---") +... +>>> # 成功するケース +>>> divider(10, 2) +--- 10 / 2 の計算を開始します --- +計算成功! 結果: 5.0 +--- 計算を終了します --- + +>>> # 例外が発生するケース +>>> divider(10, 0) +--- 10 / 0 の計算を開始します --- +エラー: 0による除算です。 +--- 計算を終了します --- +``` + +この例から、実行フローが明確にわかります。 + + * 成功ケースでは `try` -\> `else` -\> `finally` の順に実行されます。 + * 失敗ケースでは `try` -\> `except` -\> `finally` の順に実行されます。 + +`finally` 節は、`try` ブロック内で `return` が実行される場合でも、その `return` の直前に実行されることが保証されています。これにより、リソースの解放漏れなどを防ぐことができます。 diff --git a/public/docs/python/7-exceptions/5-0-summary.md b/public/docs/python/7-exceptions/5-0-summary.md new file mode 100644 index 0000000..1fef0f8 --- /dev/null +++ b/public/docs/python/7-exceptions/5-0-summary.md @@ -0,0 +1,17 @@ +--- +id: python-exceptions-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonにおけるエラー処理の基本を学びました。重要なポイントを振り返りましょう。 + + * **例外**: Pythonでは、エラーは「例外」オブジェクトとして扱われます。例外が発生すると、プログラムの実行は中断されます。 + * `try...except`: 例外が発生する可能性のあるコードを `try` ブロックで囲み、`except` ブロックで捕捉することで、プログラムのクラッシュを防ぎ、エラーに応じた処理を実行できます。 + * **複数の例外処理**: `except` ブロックを複数記述したり、タプルでまとめたりすることで、さまざまな種類のエラーに柔軟に対応できます。 + * `raise`: 特定の条件で意図的に例外を発生させ、プログラムに異常な状態を通知します。 + * `else` と `finally`: `try` ブロックが成功した場合の処理を `else` に、成功・失敗にかかわらず必ず実行したい後片付け処理を `finally` に記述することで、より堅牢なコードを書くことができます。 + +例外処理をマスターすることは、予期せぬ入力や状況に強い、安定したプログラムを作成するための重要なステップです。 diff --git a/public/docs/python/7-exceptions/5-1-practice1.md b/public/docs/python/7-exceptions/5-1-practice1.md new file mode 100644 index 0000000..12577f1 --- /dev/null +++ b/public/docs/python/7-exceptions/5-1-practice1.md @@ -0,0 +1,32 @@ +--- +id: python-exceptions-practice1 +title: '練習問題1: 安全なリスト要素の取得' +level: 3 +--- + +### 練習問題1: 安全なリスト要素の取得 + +リストとインデックスを受け取り、そのインデックスに対応する要素を返す `safe_get(my_list, index)` という関数を作成してください。 + +**要件:** + +1. インデックスがリストの範囲外の場合 (`IndexError`)、「指定されたインデックスは範囲外です。」と表示してください。 +2. インデックスが整数でない場合 (`TypeError`)、「インデックスは整数で指定してください。」と表示してください。 +3. 正常に要素を取得できた場合は、その要素を返してください。 + +```python:practice8_1.py +def safe_get(my_list, index): + + +data = ['apple', 'banana', 'cherry'] +print(safe_get(data, 1)) +print(safe_get(data, 3)) +print(safe_get(data, 'zero')) +``` + +```python-exec:practice8_1.py +(出力例) +banana +指定されたインデックスは範囲外です。 +インデックスは整数で指定してください。 +``` diff --git a/public/docs/python/7-exceptions/5-2-practice2.md b/public/docs/python/7-exceptions/5-2-practice2.md new file mode 100644 index 0000000..4616ec2 --- /dev/null +++ b/public/docs/python/7-exceptions/5-2-practice2.md @@ -0,0 +1,38 @@ +--- +id: python-exceptions-practice2 +title: '練習問題2: ユーザー年齢の検証' +level: 3 +--- + +### 練習問題2: ユーザー年齢の検証 + +ユーザーの年齢を入力として受け取り、18歳以上であれば「あなたは成人です。」と表示する `check_age(age_str)` という関数を作成してください。 + +**要件:** + +1. 関数内部で、受け取った文字列を整数に変換してください。変換できない場合 (`ValueError`) は、`ValueError` を `raise` して、「有効な数値を入力してください。」というメッセージを伝えてください。 +2. 変換した数値が負の値である場合、`ValueError` を `raise` して、「年齢に負の値は指定できません。」というメッセージを伝えてください。 +3. 年齢が0歳から17歳までの場合は、「あなたは未成年です。」と表示してください。 +4. 関数の呼び出し側で、`raise` された例外も捕捉できるようにしてください。 + +```python:practice8_2.py +def check_age(age_str): + + +# 正常ケース +print(check_age("20")) +print(check_age("15")) + +# 例外ケース +print(check_age("abc")) +print(check_age("-5")) +``` + +```python-exec:practice8_2.py +(出力例) +あなたは成人です。 +あなたは未成年です。 +Traceback (most recent call last): + ... +ValueError: 有効な数値を入力してください。 +``` diff --git a/public/docs/python/8-generators-decorators/-intro.md b/public/docs/python/8-generators-decorators/-intro.md new file mode 100644 index 0000000..96e2c97 --- /dev/null +++ b/public/docs/python/8-generators-decorators/-intro.md @@ -0,0 +1 @@ +この章では、より高度でPythonらしいコードを書くための強力な機能、**ジェネレータ**と**デコレータ**を学びます。これらの機能を使いこなすことで、メモリ効率の良いデータ処理を実装したり、既存の関数の振る舞いをエレガントに変更したりできるようになります。 diff --git a/public/docs/python/8-generators-decorators/1-0-iterator.md b/public/docs/python/8-generators-decorators/1-0-iterator.md new file mode 100644 index 0000000..72f8e38 --- /dev/null +++ b/public/docs/python/8-generators-decorators/1-0-iterator.md @@ -0,0 +1,36 @@ +--- +id: python-generators-decorators-iterator +title: イテレータとイテラブル +level: 2 +--- + +## イテレータとイテラブル + +Pythonの`for`ループは非常にシンプルで強力ですが、その裏側では**イテレーションプロトコル**という仕組みが動いています。これを理解することが、ジェネレータを学ぶ上での第一歩です。 + + * **イテラブル (Iterable)**: `for`ループで繰り返し処理が可能なオブジェクトのことです。リスト、タプル、辞書、文字列などがこれにあたります。内部に `__iter__()` メソッドを持つオブジェクトと定義されます。 + * **イテレータ (Iterator)**: 「次の値」を返す `__next__()` メソッドを持ち、値を一つずつ取り出すためのオブジェクトです。イテレータは一度最後まで進むと、それ以上値を取り出すことはできません。 + +`for`ループは、まずイテラブルオブジェクトの `__iter__()` を呼び出してイテレータを取得し、次にそのイテレータの `__next__()` を繰り返し呼び出して要素を一つずつ取り出しています。 + +REPLで動きを見てみましょう。`iter()`関数でイテレータを取得し、`next()`関数で要素を取り出します。 + +```python-repl +>>> my_list = [1, 2, 3] +>>> my_iterator = iter(my_list) +>>> type(my_iterator) + + +>>> next(my_iterator) +1 +>>> next(my_iterator) +2 +>>> next(my_iterator) +3 +>>> next(my_iterator) +Traceback (most recent call last): + File "", line 1, in +StopIteration +``` + +最後の`next()`呼び出しで `StopIteration` という例外が発生しているのがわかります。`for`ループはこの例外を検知して、ループを自動的に終了してくれます。 diff --git a/public/docs/python/8-generators-decorators/2-0-generator-yield.md b/public/docs/python/8-generators-decorators/2-0-generator-yield.md new file mode 100644 index 0000000..709b7ed --- /dev/null +++ b/public/docs/python/8-generators-decorators/2-0-generator-yield.md @@ -0,0 +1,52 @@ +--- +id: python-generators-decorators-generator-yield +title: ジェネレータ関数とyieldキーワード +level: 2 +--- + +## ジェネレータ関数とyieldキーワード + +イテレータを自作するには、クラスに `__iter__()` と `__next__()` を実装する必要がありますが、少し手間がかかります。そこで登場するのが**ジェネレータ**です。ジェネレータは、イテレータを簡単に作成するための特別な関数です。 + +ジェネレータ関数は、通常の関数と似ていますが、値を返すのに`return`の代わりに`yield`を使います。 + + * **`yield`の働き**: `yield`は値を返すだけでなく、その時点で関数の実行を**一時停止**し、関数の状態(ローカル変数など)を保存します。次に`next()`が呼ばれると、停止した場所から処理を再開します。 + +これにより、巨大なデータセットを扱う際に、全てのデータを一度にメモリに読み込む必要がなくなります。必要な時に必要な分だけデータを生成するため、非常にメモリ効率が良いコードが書けます。 + +フィボナッチ数列を生成するジェネレータの例を見てみましょう。 + +```python-repl +>>> def fib_generator(n): +... a, b = 0, 1 +... count = 0 +... while count < n: +... yield a +... a, b = b, a + b +... count += 1 +... +>>> f = fib_generator(5) +>>> type(f) + + +>>> next(f) +0 +>>> next(f) +1 +>>> next(f) +1 +>>> next(f) +2 +>>> next(f) +3 +>>> next(f) +Traceback (most recent call last): + File "", line 1, in +StopIteration + +# ジェネレータはもちろんforループで使うことができます +>>> for num in fib_generator(8): +... print(num, end=' ') +... +0 1 1 2 3 5 8 13 +``` diff --git a/public/docs/python/8-generators-decorators/3-0-generator-expr.md b/public/docs/python/8-generators-decorators/3-0-generator-expr.md new file mode 100644 index 0000000..54dcfce --- /dev/null +++ b/public/docs/python/8-generators-decorators/3-0-generator-expr.md @@ -0,0 +1,36 @@ +--- +id: python-generators-decorators-generator-expr +title: ジェネレータ式 +level: 2 +--- + +## ジェネレータ式 + +リスト内包表記に似た構文で、より簡潔にジェネレータを作成する方法が**ジェネレータ式**です。リスト内包表記の `[]` を `()` に変えるだけで作れます。 + +リスト内包表記はリストオブジェクトを生成するため、要素数が多いとメモリを大量に消費します。一方、ジェネレータ式はジェネレータオブジェクトを返すため、遅延評価(必要になるまで計算しない)が行われ、メモリ使用量を抑えられます。 + +```python-repl +# リスト内包表記 +>>> list_comp = [i * 2 for i in range(5)] +>>> list_comp +[0, 2, 4, 6, 8] +>>> type(list_comp) + + +# ジェネレータ式 +>>> gen_exp = (i * 2 for i in range(5)) +>>> gen_exp + at 0x...> +>>> type(gen_exp) + + +>>> next(gen_exp) +0 +>>> next(gen_exp) +2 +>>> list(gen_exp) # 残りの要素をリストに変換 +[4, 6, 8] +``` + +巨大なファイルの各行を処理する場合など、ジェネレータ式は非常に有効です。 diff --git a/public/docs/python/8-generators-decorators/4-0-decorator.md b/public/docs/python/8-generators-decorators/4-0-decorator.md new file mode 100644 index 0000000..ee0f23c --- /dev/null +++ b/public/docs/python/8-generators-decorators/4-0-decorator.md @@ -0,0 +1,41 @@ +--- +id: python-generators-decorators-decorator +title: デコレータの概念と基本的な作り方 +level: 2 +--- + +## デコレータの概念と基本的な作り方 + +**デコレータ**は、既存の関数のコードを一切変更せずに、その関数に新しい機能を追加(装飾)するための仕組みです。これは、関数を受け取って、新しい関数を返す**高階関数**として実装されます。 + +ログ出力、実行時間の計測、認証チェックなど、複数の関数に共通して適用したい「横断的な関心事」を扱うのに非常に便利です。 + +デコレータの基本的な構造は、関数を入れ子にすることです。 + +1. 外側の関数(デコレータ関数)は、装飾したい対象の関数を引数として受け取ります。 +2. 内側の関数(ラッパー関数)で、受け取った関数を呼び出す前後に、追加したい処理を記述します。 +3. 外側の関数は、この内側の関数を返します。 + +関数の実行前後にメッセージを表示する簡単なデコレータを見てみましょう。 + +```python-repl +>>> def my_decorator(func): +... def wrapper(): +... print("--- 処理を開始します ---") +... func() +... print("--- 処理が完了しました ---") +... return wrapper +... + +>>> def say_hello(): +... print("こんにちは!") +... + +# デコレートされた新しい関数を作成 +>>> decorated_hello = my_decorator(say_hello) +>>> decorated_hello() +--- 処理を開始します --- +こんにちは! +--- 処理が完了しました --- +``` + diff --git a/public/docs/python/8-generators-decorators/4-1-at-decorator.md b/public/docs/python/8-generators-decorators/4-1-at-decorator.md new file mode 100644 index 0000000..3d9fd59 --- /dev/null +++ b/public/docs/python/8-generators-decorators/4-1-at-decorator.md @@ -0,0 +1,24 @@ +--- +id: python-generators-decorators-at-decorator +title: '@ 構文' +level: 3 +--- + +### `@` 構文 +この書き方をより簡単にするための構文が `@`(アットマーク)、シンタックスシュガーです。 + +```python-repl +>>> @my_decorator +... def say_goodbye(): +... print("さようなら!") +... + +>>> say_goodbye() +--- 処理を開始します --- +さようなら! +--- 処理が完了しました --- +``` + +`@my_decorator` は、`say_goodbye = my_decorator(say_goodbye)` と同じ意味になります。こちらのほうが直感的で、Pythonのコードで広く使われています。 + +ジェネレータとデコレータは、最初は少し複雑に感じるかもしれませんが、使いこなせばよりクリーンで効率的なPythonコードを書くための強力な武器となります。ぜひ積極的に活用してみてください。 diff --git a/public/docs/python/8-generators-decorators/5-0-summary.md b/public/docs/python/8-generators-decorators/5-0-summary.md new file mode 100644 index 0000000..9395590 --- /dev/null +++ b/public/docs/python/8-generators-decorators/5-0-summary.md @@ -0,0 +1,15 @@ +--- +id: python-generators-decorators-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + +この章では、Pythonプログラミングをさらに高いレベルへ引き上げるための2つの強力な概念を学びました。 + + * **ジェネレータ**: `yield`キーワードを使うことで、メモリ効率に優れたイテレータを簡単に作成できることを学びました。ジェネレータ関数やジェネレータ式を使うことで、巨大なデータストリームや無限シーケンスを、必要な分だけ計算しながら扱うことができます。これは、パフォーマンスが重要なアプリケーションにおいて不可欠なテクニックです。 + + * **デコレータ**: `@`シンタックスを用いることで、既存の関数のソースコードを変更することなく、機能を追加・変更できることを学びました。デコレータは、ロギング、実行時間計測、アクセス制御といった横断的な関心事を分離し、コードの再利用性を高め、DRY (Don't Repeat Yourself) の原則を維持するのに役立ちます。 + +これらの機能を使いこなすことは、単に高度な文法を覚えるだけでなく、Pythonの設計思想を理解し、より「Pythonらしい(Pythonic)」コードを書くための重要なステップです。 diff --git a/public/docs/python/8-generators-decorators/5-1-practice1.md b/public/docs/python/8-generators-decorators/5-1-practice1.md new file mode 100644 index 0000000..ce6d9cf --- /dev/null +++ b/public/docs/python/8-generators-decorators/5-1-practice1.md @@ -0,0 +1,28 @@ +--- +id: python-generators-decorators-practice1 +title: '練習問題1: カウントダウンジェネレータ' +level: 3 +--- + +### 練習問題1: カウントダウンジェネレータ + +`countdown(start)` というジェネレータ関数を作成してください。この関数は、引数で与えられた `start` の数値から1まで、1ずつ減っていく数値を順番に `yield` します。例えば `countdown(3)` は、`3`, `2`, `1` の順に値を生成します。 + +```python:practice9_1.py +def countdown(start): + + +# 動作確認 +cd_gen = countdown(5) +for i in cd_gen: + print(i) +``` + +```python-exec:practice9_1.py +(出力例) +5 +4 +3 +2 +1 +``` diff --git a/public/docs/python/8-generators-decorators/5-2-practice2.md b/public/docs/python/8-generators-decorators/5-2-practice2.md new file mode 100644 index 0000000..0232911 --- /dev/null +++ b/public/docs/python/8-generators-decorators/5-2-practice2.md @@ -0,0 +1,34 @@ +--- +id: python-generators-decorators-practice2 +title: '問題2: 実行時間計測デコレータ' +level: 3 +--- + +### 問題2: 実行時間計測デコレータ + +関数の実行時間を計測し、"実行時間: X.XXXX秒" のように表示するデコレータ `@measure_time` を作成してください。このデコレータを、少し時間のかかる処理を行う関数に適用して、動作を確認してみましょう。 + +**ヒント**: 時間の計測には `time` モジュールが使えます。処理の開始前と終了後で `time.time()` を呼び出し、その差分を計算します。 + +```python:practice9_2.py +import time + +def measure_time(func): + + +# 動作確認用の時間のかかる関数 +@measure_time +def slow_function(n): + print(f"{n}まで数えます...") + time.sleep(n) # n秒間処理を停止 + print("完了!") + +slow_function(2) +``` + +```python-exec:practice9_2.py +(出力例) +2まで数えます... +完了! +実行時間: 2.0021秒 +``` diff --git a/public/docs/python/index.yml b/public/docs/python/index.yml new file mode 100644 index 0000000..9d0a0c9 --- /dev/null +++ b/public/docs/python/index.yml @@ -0,0 +1,30 @@ +name: Python +description: Pythonの基礎から応用までを学べるチュートリアル +pages: +- slug: 0-intro + name: 環境構築と基本思想 + title: Pythonへようこそ:環境構築と基本思想 +- slug: 1-basics + name: 基本構文とデータ型 + title: Pythonの基本構文とデータ型 +- slug: 2-collections + name: リスト、タプル、辞書、セット + title: Pythonを使いこなす:リスト、タプル、辞書、セット +- slug: 3-control-functions + name: 制御構文と関数 + title: 制御構文と関数:Pythonらしい書き方 +- slug: 4-modules + name: モジュールとパッケージ + title: コードの整理術:モジュールとパッケージ +- slug: 5-oop + name: オブジェクト指向プログラミング + title: Pythonicなオブジェクト指向プログラミング +- slug: 6-file-io + name: ファイルの入出力とコンテキストマネージャ + title: ファイルの入出力とコンテキストマネージャ +- slug: 7-exceptions + name: 例外処理 + title: エラーとの付き合い方:例外処理 +- slug: 8-generators-decorators + name: ジェネレータとデコレータ + title: さらに先へ:ジェネレータとデコレータ diff --git a/public/docs/ruby-1.md b/public/docs/ruby-1.md deleted file mode 100644 index 4d10219..0000000 --- a/public/docs/ruby-1.md +++ /dev/null @@ -1,147 +0,0 @@ -# 第1章: Rubyの世界へようこそ - 環境構築と第一歩 - -Rubyへようこそ! 他の言語でのプログラミング経験をお持ちのあなたなら、Rubyの持つ柔軟性と「開発者の楽しさ」をすぐに感じ取れるはずです。この章では、Rubyの基本的な哲学に触れ、あなたのマシンに開発環境をセットアップし、最初のコードを実行します。 - -## Rubyの哲学と特徴 - -Rubyは、まつもとゆきひろ(Matz)氏によって開発された、**シンプルさ**と**生産性**を重視した動的オブジェクト指向言語です。 - - * **すべてがオブジェクト (Everything is an Object)** - JavaやPythonでは`int`や`float`などのプリミティブ型がオブジェクトとは別に存在しますが、Rubyでは**すべてがメソッドを持つオブジェクト**です。`5`のような数値や`"hello"`のような文字列はもちろん、`nil`(nullに相当)や`true`/`false`さえもオブジェクトです。 - - ```ruby-repl:1 - irb(main):001> 5.class - => Integer - irb(main):002> "hello".upcase - => "HELLO" - irb(main):003> nil.nil? - => true - ``` - - * **開発者を楽しませる (MINASWAN)** - Rubyの設計思想の核は、プログラマがストレスなく、楽しくコーディングできることを最適化する点にあります。これはしばしば「**MINASWAN** (Matz Is Nice And So We Are Nice)」というコミュニティの標語にも表れています。言語仕様が厳格さよりも「驚き最小の原則」や表現力を優先することがあります。 - - * **柔軟性と表現力** - Rubyは非常に柔軟で、既存のクラスを後から変更する(オープンクラス)ことや、コードによってコードを操作するメタプログラミングが容易です。これにより、Ruby on Railsのような強力なフレームワークや、RSpecのようなDSL(ドメイン固有言語)が生まれています。 - - -## 他言語との簡単な比較 - -あなたの経験言語とRubyを少し比べてみましょう。 - - * **Pythonistaへ**: - - * インデントは構文的な意味を持ちません(単なる可読性のため)。 - * ブロック(コードのまとまり)は`:`とインデントではなく、`do...end`キーワード、または`{...}`(ブレース)で定義します。 - * メソッド呼び出しの丸括弧`()`は、曖昧さがなければ省略可能です。`print "hello"`のように書けます。 - - * **Java/C\# Developerへ**: - - * Rubyは静的型付けではなく**動的型付け**です。変数の型宣言(`int i = 10`)は不要で、`i = 10`と書くだけです。 - * コンパイルステップは不要です。スクリプトとして直接実行されます。 - * `public`, `private`, `protected`のアクセス修飾子はありますが、Javaなどとは少し動作が異なります(特に`private`)。 - - * **JavaScript Developerへ**: - - * Rubyもクラスベースのオブジェクト指向です(ただし、内部的にはJSのプロトタイプチェーンに似た特異クラスの仕組みも持ちます)。 - * `this`の代わりに`self`を使いますが、`self`のコンテキストはJSの`this`ほど複雑に変化せず、より直感的です。 - * `true`, `false`, `nil` 以外はすべて「Truthy(真)」として扱われます(`0`や空文字列`""`も真です)。 - -## 環境構築:バージョン管理ツールの導入 - -システム(OS)に最初から入っているRubyを直接使うのではなく、**バージョン管理ツール**(`rbenv`や`RVM`)を導入することを強く推奨します。 - -プロジェクトごとに異なるRubyバージョン(例: 2.7.x と 3.3.x)を簡単に切り替えることができ、システムをクリーンに保てます。 - -ここでは`rbenv`を使った一般的な流れを紹介します。(詳細なインストール手順はOSによって異なりますので、`rbenv`のGitHubリポジトリなどを参照してください。) - -1. **rbenv と ruby-build のインストール**: - Homebrew (macOS) や apt/yum (Linux) など、お使いのパッケージマネージャでインストールします。 - -2. **Rubyのインストール**: - - ```bash - # インストール可能なバージョン一覧を確認 - $ rbenv install -l - - # 最新の安定版(例: 3.3.0)をインストール - $ rbenv install 3.3.0 - ``` - -3. **使用するバージョンの設定**: - - ```bash - # このPCでのデフォルトバージョンとして設定 - $ rbenv global 3.3.0 - - # バージョンが切り替わったか確認 - $ ruby -v - ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] - ``` - -## 対話型シェル(irb)の活用 - -Rubyのインストールが完了したら、`irb` (Interactive Ruby) を起動してみましょう。これはRubyのREPL (Read-Eval-Print Loop) で、コード片を試したり、ドキュメント代わりに使ったりするのに非常に便利です。 - -ターミナルで`irb`と入力することで起動できます。 - -このウェブサイトではドキュメント内にRubyの実行環境を埋め込んでいます。 -以下のように青枠で囲われたコード例には自由にRubyコードを書いて試すことができます。 - -```ruby-repl:2 -irb(main):001> 10 * (5 + 3) -=> 80 -irb(main):002> "Ruby".length -=> 4 -irb(main):003> 3.times { puts "Welcome!" } -Welcome! -Welcome! -Welcome! -=> 3 -``` - -`=>` の右側に表示されているのが、式の**評価結果(返り値)**です。 - -`3.times`の例に注目してください。`puts "Welcome!"`が3回実行(出力)されていますが、`=> 3`と表示されています。これは、`3.times`というメソッド自体の返り値が`3`(レシーバである数値)であることを示しています。Rubyではすべての式が値を返すことを覚えておいてください。 - -## "Hello, World\!" とスクリプトの実行 - -最後に、定番の "Hello, World\!" を2通りの方法で実行してみましょう。 - -### irbでの実行 - -`puts`("put string")は、引数を標準出力(ターミナル)に出力し、最後に改行を追加するメソッドです。 - -```ruby-repl:3 -irb(main):001> puts "Hello, World!" -Hello, World! -=> nil -``` - -(`puts`メソッド自体の返り値は、常に`nil`です。) - -### スクリプトファイルでの実行 - -エディタで`hello.rb`という名前のファイルを作成します。 - -```ruby:hello.rb -#!/usr/bin/env ruby -# 1行目はShebang(シーバン)と言い、Unix系OSでスクリプトとして直接実行する際に使われます。 - -# 変数に文字列を代入 -message = "Hello from script file!" - -# 変数の内容を出力 -puts message -``` - -このファイルを実行するには、ターミナルで`ruby`コマンドの引数にファイル名を渡します。 - -このウェブサイト上では以下のように実行ボタンをクリックするとスクリプトの実行結果が表示されます。上の hello.rb のコードを変更して再度実行すると結果も変わるはずです。試してみてください。 - -```ruby-exec:hello.rb -Hello from script file! -``` - -おめでとうございます! これでRubyの世界への第一歩を踏み出しました。 -次の章では、Rubyの基本的な構文、データ型、そして他の言語にはない特徴的な「シンボル」について詳しく学んでいきます。 diff --git a/public/docs/ruby-10.md b/public/docs/ruby-10.md deleted file mode 100644 index d7a994c..0000000 --- a/public/docs/ruby-10.md +++ /dev/null @@ -1,292 +0,0 @@ -# 第10章: 標準ライブラリの活用 - -Rubyの強力な点の一つは、多くの一般的なタスクを処理するための豊富な「標準ライブラリ」が同梱されていることです。これらは "batteries included"(電池付属)とよく表現されます。 - -他の言語で `import` や `include` を使うのと同様に、Rubyでは `require` を使ってこれらのライブラリをロードします。ただし、`File` や `Time`、`Regexp` のようなコア機能の多くは、`require` なしで利用可能です。 - -この章では、特に使用頻度の高い標準ライブラリの機能を見ていきます。 - -## ファイル操作 (File, Dir, Pathname) - -ファイルシステムとのやり取りは、多くのアプリケーションで不可欠です。 - -### Fileクラスによる読み書き - -`File` クラスは、ファイルに対する基本的な読み書き操作を提供します。 - -**ファイルの書き込みと追記:** - -```ruby:file_io_example.rb -# 1. ファイルへの書き込み (上書き) -# シンプルな方法は File.write です -File.write('sample.txt', "Hello, Ruby Standard Library!\n") - -# 2. ファイルへの追記 -# mode: 'a' (append) オプションを指定します -File.write('sample.txt', "This is a second line.\n", mode: 'a') - -puts "File 'sample.txt' created and updated." -``` - -```ruby-exec:file_io_example.rb -File 'sample.txt' created and updated. -``` - -```text-readonly:sample.txt -``` - -**ファイルの読み込み:** - -```ruby:file_read_example.rb -# 'sample.txt' が存在すると仮定 - -# 1. ファイル全体を一度に読み込む -content = File.read('sample.txt') -puts "--- Reading all at once ---" -puts content - -# 2. 1行ずつ処理する (大きなファイルに効率的) -puts "\n--- Reading line by line ---" -File.foreach('sample.txt') do |line| - print "Line: #{line}" -end - -# 3. 処理後にファイルをクリーンアップ -File.delete('sample.txt') -puts "\n\nFile 'sample.txt' deleted." -``` - -```ruby-exec:file_read_example.rb ---- Reading all at once --- -Hello, Ruby Standard Library! -This is a second line. - ---- Reading line by line --- -Line: Hello, Ruby Standard Library! -Line: This is a second line. - -File 'sample.txt' deleted. -``` - -### DirクラスとPathname - -`Dir` クラスはディレクトリの内容を操作するために使われます。特に `Dir.glob` はワイルドカードを使ってファイルやディレクトリを検索するのに便利です。 - -しかし、パスの連結や解析を文字列として扱うのは面倒です。`Pathname` ライブラリは、パスをオブジェクトとして扱うための優れたインターフェースを提供します。 - -```ruby-repl:1 -irb(main):001:0> # Dir.glob はワイルドカードでファイルリストを取得できます -irb(main):002:0> Dir.glob('*.rb') # (irbを実行しているディレクトリによります) -=> [] - -irb(main):003:0> # Pathname を使うには require が必要 -irb(main):004:0> require 'pathname' -=> true - -irb(main):005:0> # 文字列の代わりに Pathname オブジェクトを作成 -irb(main):006:0> base_path = Pathname.new('/usr/local') -=> # - -irb(main):007:0> # + や / 演算子で安全にパスを連結できます -irb(main):008:0> lib_path = base_path + 'lib' -=> # -irb(main):009:0> bin_path = base_path / 'bin' -=> # - -irb(main):010:0> # パスの解析 -irb(main):011:0> file_path = Pathname.new('/var/log/app.log') -=> # -irb(main):012:0> file_path.basename # ファイル名 -=> # -irb(main):013:0> file_path.extname # 拡張子 -=> ".log" -irb(main):014:0> file_path.dirname # 親ディレクトリ -=> # -irb(main):015:0> file_path.absolute? # 絶対パスか? -=> true -``` - -## 日付と時刻 (Time, Date) - -Rubyには `Time`(組み込み)と `Date`(要 `require`)の2つの主要な日時クラスがあります。 - - * **Time:** 時刻(タイムスタンプ)をナノ秒までの精度で扱います。 - * **Date:** 日付(年月日)のみを扱い、カレンダー計算に特化しています。 - -```ruby-repl:2 -irb(main):001:0> # Time (組み込み) -irb(main):002:0> now = Time.now -=> 2025-11-04 11:32:00 +0900 (JST) -irb(main):003:0> now.year -=> 2025 -irb(main):004:0> now.monday? -=> false -irb(main):005:0> now.to_i # UNIXタイムスタンプ -=> 1762309920 - -irb(main):006:0> # strftime (string format time) でフォーマット -irb(main):007:0> now.strftime("%Y-%m-%d %H:%M:%S") -=> "2025-11-04 11:32:00" - -irb(main):008:0> # Date (require が必要) -irb(main):009:0> require 'date' -=> true -irb(main):010:0> today = Date.today -=> # -irb(main):011:0> today.strftime("%A") # 曜日 -=> "Tuesday" - -irb(main):012:0> # 文字列からのパース -irb(main):013:0> christmas = Date.parse("2025-12-25") -=> # -irb(main):014:0> (christmas - today).to_i # あと何日? -=> 51 -``` - -## JSONのパースと生成 (json) - -現代のWeb開発においてJSONの扱いは不可欠です。`json` ライブラリは、JSON文字列とRubyのHash/Arrayを相互に変換する機能を提供します。 - -```ruby-repl:3 -irb(main):001:0> require 'json' -=> true - -irb(main):002:0> # 1. JSON文字列 -> Rubyオブジェクト (Hash) へのパース -irb(main):003:0> json_data = '{"user_id": 123, "name": "Alice", "tags": ["admin", "ruby"]}' -=> "{\"user_id\": 123, \"name\": \"Alice\", \"tags\": [\"admin\", \"ruby\"]}" - -irb(main):004:0> parsed_data = JSON.parse(json_data) -=> {"user_id"=>123, "name"=>"Alice", "tags"=>["admin", "ruby"]} -irb(main):005:0> parsed_data['name'] -=> "Alice" -irb(main):006:0> parsed_data['tags'] -=> ["admin", "ruby"] - -irb(main):007:0> # 2. Rubyオブジェクト (Hash) -> JSON文字列 への生成 -irb(main):008:0> ruby_hash = { -irb(main):009:1* status: "ok", -irb(main):010:1* data: { item_id: 987, price: 1500 } -irb(main):011:1* } -=> {:status=>"ok", :data=>{:item_id=>987, :price=>1500}} - -irb(main):012:0> # .to_json メソッドが便利です -irb(main):013:0> json_output = ruby_hash.to_json -=> "{\"status\":\"ok\",\"data\":{\"item_id\":987,\"price\":1500}}" - -irb(main):014:0> # 人が読みやすいように整形 (pretty generate) -irb(main):015:0> puts JSON.pretty_generate(ruby_hash) -{ - "status": "ok", - "data": { - "item_id": 987, - "price": 1500 - } -} -=> nil -``` - -## 正規表現 (Regexp) と match - -Rubyの正規表現 (Regexp) は、Perl互換の強力なパターンマッチング機能を提供します。`/pattern/` リテラルで記述するのが一般的です。 - -### マッチの確認 (`=~` と `match`) - - * `=~` 演算子: マッチした位置のインデックス(0から始まる)を返すか、マッチしなければ `nil` を返します。 - * `String#match`: `MatchData` オブジェクトを返すか、マッチしなければ `nil` を返します。`MatchData` は、キャプチャグループ(`()`で囲んだ部分)へのアクセスに便利です。 - -```ruby-repl:4 -irb(main):001:0> text = "User: alice@example.com (Alice Smith)" -=> "User: alice@example.com (Alice Smith)" - -irb(main):002:0> # =~ は位置を返す -irb(main):003:0> text =~ /alice/ -=> 6 -irb(main):004:0> text =~ /bob/ -=> nil - -irb(main):005:0> # String#match は MatchData を返す -irb(main):006:0> # パターン: (ユーザー名)@(ドメイン) -irb(main):007:0> match_data = text.match(/(\w+)@([\w\.]+)/) -=> # - -irb(main):008:0> # マッチしたオブジェクトからキャプチャを取得 -irb(main):009:0> match_data[0] # マッチ全体 -=> "alice@example.com" -irb(main):010:0> match_data[1] # 1番目の () -=> "alice" -irb(main):011:0> match_data[2] # 2番目の () -=> "example.com" -``` - -### 検索と置換 (`scan` と `gsub`) - - * `String#scan`: マッチするすべての部分文字列を(キャプチャグループがあればその配列として)返します。 - * `String#gsub`: マッチするすべての部分を置換します (Global SUBstitute)。 - -```ruby-repl:5 -irb(main):001:0> log = "ERROR: code 500. WARNING: code 404. ERROR: code 403." -=> "ERROR: code 500. WARNING: code 404. ERROR: code 403." - -irb(main):002:0> # scan: 'ERROR: code (数字)' にマッチする部分をすべて探す -irb(main):003:0> log.scan(/ERROR: code (\d+)/) -=> [["500"], ["403"]] - -irb(main):004:0> # gsub: 'ERROR' を 'CRITICAL' に置換する -irb(main):005:0> log.gsub("ERROR", "CRITICAL") -=> "CRITICAL: code 500. WARNING: code 404. CRITICAL: code 403." - -irb(main):006:0> # gsub はブロックと正規表現を組み合わせて高度な置換が可能 -irb(main):007:0> # 数字(コード)を [] で囲む -irb(main):008:0> log.gsub(/code (\d+)/) do |match| -irb(main):009:1* # $1 は最後のマッチの1番目のキャプチャグループ -irb(main):010:1* "code [#{$1}]" -irb(main):011:1> end -=> "ERROR: code [500]. WARNING: code [404]. ERROR: code [403]." -``` - -## この章のまとめ - - * Rubyには、`require` でロードできる豊富な**標準ライブラリ**が付属しています。 - * **File**クラスはファイルの読み書きを、**Pathname**はパス操作をオブジェクト指向的に行います。 - * **Time**は時刻を、**Date**は日付を扱います。`strftime` でフォーマットできます。 - * **json**ライブラリは `JSON.parse`(文字列→Hash)と `to_json`(Hash→文字列)を提供します。 - * **Regexp**(`/pattern/`)はパターンマッチングに使います。`String#match` で `MatchData` を取得し、`scan` や `gsub` で検索・置換を行います。 - -これらは標準ライブラリのごく一部です。他にもCSVの処理 (`csv`)、HTTP通信 (`net/http`)、テスト (`minitest`) など、多くの機能が提供されています。 - -### 練習問題1: JSON設定ファイルの読み書き - -1. `config.json` ファイルを読み込み、内容をJSONパースしてRubyのHashに変換してください。 -2. そのHashの `logging` の値を `true` に変更し、さらに `:updated_at` というキーで現在時刻(文字列)を追加してください。 -3. 変更後のHashをJSON文字列に変換し、`config_updated.json` という名前でファイルに保存してください。(読みやすさのために `JSON.pretty_generate` を使っても構いません) - -```json-readonly:config.json -{"app_name": "RubyApp", "version": "1.0", "logging": false} -``` - -```ruby:practice10_1.rb -``` - -```ruby-exec:practice10_1.rb -``` - -```json-readonly:config_updated.json -``` - -### 練習問題2: ログファイルからの情報抽出 - -1. `system.log` というファイルを1行ずつ読み込みます。 -2. 正規表現を使い、`[INFO]` で始まり、かつ `logged in` という文字列を含む行だけを検出してください。 -3. マッチした行から、IPアドレス(`192.168.1.10` のような形式)を正規表現のキャプチャグループを使って抽出し、IPアドレスだけをコンソールに出力してください。 - -```text-readonly:system.log -[INFO] 2025-11-04 User 'admin' logged in from 192.168.1.10 -[WARN] 2025-11-04 Failed login attempt for user 'guest' -[INFO] 2025-11-04 Service 'payment_gateway' started. -``` - -```ruby:practice10_2.rb -``` - -```ruby-exec:practice10_2.rb -``` diff --git a/public/docs/ruby-11.md b/public/docs/ruby-11.md deleted file mode 100644 index 4637bd6..0000000 --- a/public/docs/ruby-11.md +++ /dev/null @@ -1,358 +0,0 @@ -# 第11章: テスト文化入門 - Minitest - -Rubyは動的型付け言語であり、コンパイル時ではなく実行時に型が決まります。これは柔軟で高速な開発を可能にする反面、型の不一致などによる単純なミスが実行時まで検出されにくいという特性も持ちます。 - -そのため、Rubyコミュニティでは「**テストは文化**」と言われるほど、自動化されたテストを書くことが重視されます。テストは、コードが期待通りに動作することを保証するだけでなく、未来の自分や他の開発者がコードをリファクタリング(修正・改善)する際の「安全網」として機能します。 - -この章では、Rubyに標準で添付されているテスティングフレームワーク「Minitest」を使い、テストの基本的な書き方と文化を学びます。 - -## 標準添付のテスティングフレームワーク「Minitest」 - -Minitestは、Rubyに標準で含まれている(=別途インストール不要)軽量かつ高速なテストフレームワークです。 - -Ruby on Railsなどの主要なフレームワークもデフォルトでMinitestを採用しており、Rubyのエコシステムで広く使われています。(RSpecという、よりDSL(ドメイン固有言語)ライクに記述できる人気のフレームワークもありますが、まずは標準のMinitestを理解することが基本となります。) - -Minitestは、`Minitest::Test` を継承する「Unitスタイル」と、`describe` ブロックを使う「Specスタイル」の2種類の書き方を提供しますが、この章では最も基本的なUnitスタイルを学びます。 - -## テストファイルの作成と実行 - -早速、簡単なクラスをテストしてみましょう。 - -### 1\. テスト対象のクラスの作成 - -まず、テスト対象となる簡単な電卓クラスを作成します。 - -```ruby:calculator.rb -# シンプルな電卓クラス -class Calculator - def add(a, b) - a + b - end - - def subtract(a, b) - a - b - end -end -``` - -### 2\. テストファイルの作成 - -Rubyの規約では、テストファイルは `test_` プレフィックス(例: `test_calculator.rb`)または `_test.rb` サフィックス(例: `calculator_test.rb`)で作成するのが一般的です。ここでは `test_calculator.rb` を作成します。 - -テストファイルは、以下の要素で構成されます。 - -1. `require 'minitest/autorun'` - * Minitestライブラリを読み込み、ファイル実行時にテストが自動で走るようにします。 -2. `require_relative 'ファイル名'` - * テスト対象のファイル(今回は `calculator.rb`)を読み込みます。 -3. `class クラス名 < Minitest::Test` - * テストクラスを定義し、`Minitest::Test` を継承します。 -4. `def test_メソッド名` - * `test_` で始まるメソッドを定義します。これが個々のテストケースとなります。 - -```ruby:test_calculator.rb -require 'minitest/autorun' -require_relative 'calculator' - -class CalculatorTest < Minitest::Test - # `test_` で始まるメソッドがテストとして実行される - def test_addition - # テスト対象のインスタンスを作成 - calc = Calculator.new - - # 期待値 (Expected) - expected = 5 - # 実際の結果 (Actual) - actual = calc.add(2, 3) - - # アサーション(後述) - # 期待値と実際の結果が等しいことを検証する - assert_equal(expected, actual) - end - - def test_subtraction - calc = Calculator.new - # アサーションは1行で書くことが多い - assert_equal(1, calc.subtract(4, 3)) - end -end -``` - -### 3\. テストの実行 - -ターミナルで、作成した**テストファイル**を実行します。 - -```ruby-exec:test_calculator.rb -Run options: --seed 51740 - -# Running: - -.. - -Finished in 0.001099s, 1819.8362 runs/s, 1819.8362 assertions/s. - -2 runs, 2 assertions, 0 failures, 0 errors, 0 skips -``` - -実行結果のサマリに注目してください。 - - * `.`(ドット): テストが成功(Pass)したことを示します。 - * `2 runs, 2 assertions`: 2つのテスト(`test_addition` と `test_subtraction`)が実行され、合計2回のアサーション(`assert_equal`)が成功したことを意味します。 - * `0 failures, 0 errors`: 失敗もエラーもありません。 - -もしテストが失敗すると、`F`(Failure)や `E`(Error)が表示され、詳細なレポートが出力されます。 - -## アサーション(assert\_equal, assert 等)の書き方 - -アサーション(Assertion = 表明、断言)は、「この値はこうあるべきだ」と検証するためのメソッドです。Minitestは `Minitest::Test` を継承したクラス内で、様々なアサーションメソッドを提供します。 - -### `assert_equal(expected, actual)` - -最もよく使うアサーションです。「期待値(expected)」と「実際の結果(actual)」が `==` で等しいことを検証します。 - -> **⚠️ 注意:** 引数の順序が重要です。\*\*1番目が「期待値」、2番目が「実際の結果」\*\*です。逆にすると、失敗時のメッセージが非常に分かりにくくなります。 - -```ruby-repl -irb> require 'minitest/assertions' -=> true -irb> include Minitest::Assertions -=> Object -irb> def assert_equal(expected, actual); super; end # irbで使うための設定 -=> :assert_equal - -irb> assert_equal 5, 2 + 3 -=> true - -irb> assert_equal 10, 2 + 3 -# Minitest::Assertion: <--- 失敗(Assertion Failed) -# Expected: 10 -# Actual: 5 -``` - -### `assert(test)` - -`test` が **true**(またはtrueと評価される値)であることを検証します。偽(`false` または `nil`)の場合は失敗します。 - -```ruby-repl -irb> assert "hello".include?("e") -=> true -irb> assert [1, 2, 3].empty? -# Minitest::Assertion: Expected [] to be empty?. -``` - -### `refute(test)` - -`assert` の逆です。`test` が **false** または `nil` であることを検証します。 - -```ruby-repl -irb> refute [1, 2, 3].empty? -=> true -irb> refute "hello".include?("e") -# Minitest::Assertion: Expected "hello".include?("e") to be falsy. -``` - -### `assert_nil(obj)` - -`obj` が `nil` であることを検証します。 - -```ruby-repl -irb> a = nil -=> nil -irb> assert_nil a -=> true -``` - -### `assert_raises(Exception) { ... }` - -ブロック `{ ... }` を実行した結果、指定した例外(`Exception`)が発生することを検証します。 - -これは、意図したエラー処理が正しく動作するかをテストするのに非常に重要です。 - -```ruby:test_calculator_errors.rb -require 'minitest/autorun' - -class Calculator - def divide(a, b) - raise ZeroDivisionError, "Cannot divide by zero" if b == 0 - a / b - end -end - -class CalculatorErrorTest < Minitest::Test - def test_division_by_zero - calc = Calculator.new - - # ブロック内で ZeroDivisionError が発生することを期待する - assert_raises(ZeroDivisionError) do - calc.divide(10, 0) - end - end -end -``` - -```ruby-exec:test_calculator_errors.rb -Run options: --seed 19800 - -# Running: - -. - -Finished in 0.000624s, 1602.5641 runs/s, 1602.5641 assertions/s. - -1 runs, 1 assertions, 0 failures, 0 errors, 0 skips -``` - -## 簡単なTDD(テスト駆動開発)の体験 - -TDD (Test-Driven Development) は、機能のコードを書く前に、**まず失敗するテストコードを書く**開発手法です。TDDは以下の短いサイクルを繰り返します。 - -1. **Red (レッド):** - * これから実装したい機能に対する「失敗するテスト」を書きます。 - * まだ機能が存在しないため、テストは(当然)失敗(Red)します。 -2. **Green (グリーン):** - * そのテストをパスさせるための**最小限の**機能コードを実装します。 - * テストが成功(Green)すればOKです。コードの綺麗さはまだ問いません。 -3. **Refactor (リファクタリング):** - * テストが成功した状態を維持したまま、コードの重複をなくしたり、可読性を上げたりする「リファクタリング」を行います。 - -`Calculator` クラスに、`multiply`(掛け算)メソッドをTDDで追加してみましょう。 - -### 1\. Red: 失敗するテストを書く - -まず、`test_calculator.rb` に `multiply` のテストを追加します。 - -```ruby:calculator.rb -# シンプルな電卓クラス -class Calculator - def add(a, b) - a + b - end - - def subtract(a, b) - a - b - end -end -``` - -```ruby:test_calculator_tdd.rb -require 'minitest/autorun' -require_relative 'calculator' # calculator.rb は add と subtract のみ - -class CalculatorTest < Minitest::Test - def setup - # @calc をインスタンス変数にすると、各テストメソッドで使える - @calc = Calculator.new - end - - def test_addition - assert_equal(5, @calc.add(2, 3)) - end - - def test_subtraction - assert_equal(1, @calc.subtract(4, 3)) - end - - # --- TDDサイクル スタート --- - - # 1. Red: まずテストを書く - def test_multiplication - assert_equal(12, @calc.multiply(3, 4)) - end -end -``` - -この時点で `calculator.rb` に `multiply` メソッドは存在しません。テストを実行します。 - -```ruby-exec:test_calculator_tdd.rb -# (実行結果の抜粋) -... -Error: -CalculatorTest#test_multiplication: -NoMethodError: undefined method `multiply' for # -... -1 runs, 0 assertions, 0 failures, 1 errors, 0 skips -``` - -期待通り、`NoMethodError` でテストが**エラー (E)** になりました。これが「Red」の状態です。(Failure (F) はアサーションが期待と違った場合、Error (E) はコード実行中に例外が発生した場合を指します) - -### 2\. Green: テストを通す最小限のコードを書く - -次に、`calculator.rb` に以下のように `multiply` メソッドを実装し、テストをパス(Green)させます。 - -```ruby -class Calculator - def add(a, b) - a + b - end - - def subtract(a, b) - a - b - end - - # 2. Green: テストを通す最小限の実装 - def multiply(a, b) - a * b - end -end -``` - -`calculator.rb` を編集し、再びテストを実行すると、以下のようにすべてのテストが成功します。「Green」の状態です。 - -```bash -$ ruby test_calculator_tdd.rb -... -Finished in ... -3 runs, 3 assertions, 0 failures, 0 errors, 0 skips -``` - -### 3\. Refactor: リファクタリング - -今回は実装が非常にシンプルなのでリファクタリングの必要はあまりありませんが、もし `multiply` の実装が複雑になったり、他のメソッドとコードが重複したりした場合は、この「Green」の(テストが成功している)状態で安心してコードをクリーンアップします。 - -TDDは、この「Red -\> Green -\> Refactor」のサイクルを高速で回すことにより、バグの少ない、メンテンスしやすいコードを堅実に構築していく手法です。 - -## 📈 この章のまとめ - - * Rubyは動的型付け言語であるため、実行時の動作を保証する**テストが非常に重要**です。 - * **Minitest** はRubyに標準添付された軽量なテスティングフレームワークです。 - * テストファイルは `require 'minitest/autorun'` し、`Minitest::Test` を継承します。 - * テストメソッドは `test_` プレフィックスで定義します。 - * `assert_equal(期待値, 実際の結果)` が最も基本的なアサーションです。 - * `assert` (true検証), `refute` (false検証), `assert_raises` (例外検証) などもよく使われます。 - * **TDD (テスト駆動開発)** は「Red (失敗) -\> Green (成功) -\> Refactor (改善)」のサイクルで開発を進める手法です。 - -### 練習問題1: Stringクラスのテスト - -`Minitest::Test` を使って、Rubyの組み込みクラスである `String` の動作をテストする `test_string.rb` を作成してください。以下の2つのテストメソッドを実装してください。 - - * `test_string_length`: `"hello"` の `length` が `5` であることを `assert_equal` で検証してください。 - * `test_string_uppercase`: `"world"` を `upcase` した結果が `"WORLD"` であることを `assert_equal` で検証してください。 - -```ruby:test_string.rb -require 'minitest/autorun' - - -``` - -```ruby-exec:test_string.rb -``` - -### 練習問題2: TDDでUserクラスを実装 - -TDDの「Red -\> Green」サイクルを体験してください。 - -1. (Red)`User` クラスに `first_name` と `last_name` を渡してインスタンス化し、`full_name` メソッドを呼ぶと `"First Last"` のように連結された文字列が返ることを期待するテスト `test_full_name` を含む `test_user.rb` を先に作成してください。(この時点では `user.rb` は空か、存在しなくても構いません) -2. (Green)テストがパスするように、`user.rb` に `User` クラスを実装してください。(`initialize` で名前を受け取り、`full_name` メソッドで連結します) - - -```ruby:user.rb -``` - -```ruby:test_user.rb -require 'minitest/autorun' -require_relative 'user' - -``` - -```ruby-exec:test_user.rb -``` diff --git a/public/docs/ruby-12.md b/public/docs/ruby-12.md deleted file mode 100644 index 3c9004a..0000000 --- a/public/docs/ruby-12.md +++ /dev/null @@ -1,212 +0,0 @@ -# 第12章: メタプログラミング入門 - -Rubyの最も強力であり、同時に最も特徴的な側面の一つが「メタプログラミング」です。これは「コードがコードを書く(あるいは変更する)」能力を指します。他の言語でコンパイル時やリフレクションAPIを通じて行っていた操作の多くを、Rubyでは実行時に直接、かつ柔軟に行うことができます。 - -## Rubyの動的な性質 - -Rubyは非常に動的な言語です。クラスは実行中に変更可能であり、メソッドの追加や削除、上書きがいつでも行えます。この章では、その動的な性質を利用したメタプログラミングの基本的な手法を学びます。 - - * **オープンクラス**: Rubyでは、既存のクラス(組み込みクラスさえも)を後から「開いて」メソッドを追加・変更できます。 - * **実行時**: 多くの決定がコンパイル時ではなく実行時に行われます。 - -これらの性質が、DRY (Don't Repeat Yourself) の原則を追求し、柔軟なDSL(ドメイン固有言語)を構築するための基盤となります。 - -## send: メソッドを動的に呼び出す - -通常、メソッドは `object.method_name` のようにドット(`.`)を使って呼び出します。しかし、呼び出したいメソッド名が実行時までわからない場合、`send` メソッド(または `public_send`)が役立ちます。 - -`send` は、第1引数にメソッド名を**シンボル**(`:`)または**文字列**で受け取り、残りの引数をそのメソッドに渡して実行します。 - -```ruby-repl -irb(main):001> "hello".send(:upcase) -=> "HELLO" -irb(main):002> "hello".send("length") -=> 5 -irb(main):003> 10.send(:+, 5) # 演算子も内部的にはメソッドです -=> 15 -irb(main):004> -irb(main):004> method_to_call = :reverse -irb(main):005> "Ruby".send(method_to_call) -=> "ybuR" -``` - -> **注意**: `send` は `private` メソッドも呼び出すことができます。意図せず `private` メソッドを呼び出さないように、通常は `public_send` を使う方が安全です。 - -## define\_method: メソッドを動的に定義する - -メソッドを動的に(実行時に)定義したい場合、`define_method` を使用します。これは主にクラスやモジュールの定義内で使われます。 - -`define_method` は、第1引数に定義したいメソッド名(シンボル)を、第2引数にブロック(ProcやLambda)を取ります。このブロックが、新しく定義されるメソッドの本体となります。 - -例えば、似たようなメソッドを多数定義する必要がある場合に非常に便利です。 - -```ruby:dynamic_greeter.rb -class DynamicGreeter - # 定義したい挨拶のリスト - GREETINGS = { - hello: "Hello", - goodbye: "Goodbye", - hi: "Hi" - } - - GREETINGS.each do |name, prefix| - # define_methodを使ってメソッドを動的に定義する - define_method(name) do |target| - puts "#{prefix}, #{target}!" - end - end -end - -greeter = DynamicGreeter.new - -# 動的に定義されたメソッドを呼び出す -greeter.hello("World") -greeter.goodbye("Alice") -greeter.hi("Bob") -``` - -```ruby-exec:dynamic_greeter.rb -Hello, World! -Goodbye, Alice! -Hi, Bob! -``` - -## method\_missing: 存在しないメソッドへの応答 - -オブジェクトに対して定義されていないメソッドが呼び出されると、Rubyは例外(`NoMethodError`)を発生させる前に、`method_missing` という特別なメソッドを呼び出そうと試みます。 - -この `method_missing` を自分でオーバーライドすることで、定義されていないメソッド呼び出しを「キャッチ」し、動的に処理できます。 - -`method_missing` は以下の引数を受け取ります。 - -1. 呼び出されようとしたメソッド名(シンボル) -2. そのメソッドに渡された引数(配列) -3. そのメソッドに渡されたブロック(存在する場合) - -```ruby:ghost_methods.rb -class DynamicLogger - def method_missing(method_name, *args, &block) - # 呼び出されたメソッド名が 'log_' で始まるかチェック - if method_name.to_s.start_with?("log_") - # 'log_' の部分を取り除いてレベル名とする - level = method_name.to_s.delete_prefix("log_") - - # メッセージ(引数)を取得 - message = args.first || "(no message)" - - puts "[#{level.upcase}] #{message}" - else - # 関係ないメソッド呼び出しは、通常通り NoMethodError を発生させる - super - end - end - - # respond_to? が正しく動作するように、respond_to_missing? も定義するのがベストプラクティス - def respond_to_missing?(method_name, include_private = false) - method_name.to_s.start_with?("log_") || super - end -end - -logger = DynamicLogger.new - -logger.log_info("Application started.") -logger.log_warning("Cache is empty.") -logger.log_error("File not found.") - -# respond_to? の動作確認 -puts "Responds to log_info? #{logger.respond_to?(:log_info)}" -puts "Responds to undefined_method? #{logger.respond_to?(:undefined_method)}" - -# 存在しないメソッド(super呼び出し) -# logger.undefined_method # => NoMethodError -``` - -```ruby-exec:ghost_methods.rb -[INFO] Application started. -[WARNING] Cache is empty. -[ERROR] File not found. -Responds to log_info? true -Responds to undefined_method? false -``` - -## Railsなどでの活用例 - -Rubyのメタプログラミングは、Ruby on Railsのようなフレームワークで広く活用されています。これにより、開発者は定型的なコード(ボイラープレート)を大量に書く必要がなくなり、宣言的な記述が可能になります。 - - * **Active Record (ORM)**: - * `method_missing` の典型的な例です。`User.find_by_email("test@example.com")` のようなメソッドは、`User` クラスに明示的に定義されていません。Active Recordは `method_missing` を使って `find_by_` プレフィックスを検出し、`email` カラムで検索するSQLを動的に生成します。 - * **関連付け (Associations)**: - * `has_many :posts` や `belongs_to :user` といった記述。これらは単なる宣言に見えますが、内部では `define_method` を使い、`user.posts` や `post.user` といった便利なメソッドを実行時に定義しています。 - -このように、メタプログラミングはRubyエコシステムの「魔法」の多くを支える技術であり、フレームワークの内部を理解する上で不可欠です。 - -## ⚡ この章のまとめ - - * **メタプログラミング**とは、コードが実行時に自身の構造(クラスやメソッド)を操作する技術です。 - * `send`(または `public_send`)は、メソッド名を文字列やシンボルで指定し、動的にメソッドを呼び出します。 - * `define_method` は、実行時にメソッドを動的に定義します。DRYを保つのに役立ちます。 - * `method_missing` は、定義されていないメソッド呼び出しを捕捉し、柔軟なインターフェース(DSL)を構築するために使われます。 - * メタプログラミングは非常に強力ですが、コードの可読性やデバッグの難易度を上げる可能性もあります。使い所を理解し、**乱用は避ける**ことが重要です。 - -## 練習問題1: 動的アクセサ - -`define_method` を使って、指定された属性名の配列からゲッター(`attr_reader`)とセッター(`attr_writer`)を動的に定義するメソッド `my_attr_accessor` を持つモジュールを作成してください。(ヒント: インスタンス変数 `@name` を読み書きするメソッドを定義します) - -```ruby:practice12_1.rb -module DynamicAccessor - def my_attr_accessor(*attrs) - attrs.each do |attr| - # ゲッターとセッターを動的に定義するコードをここに書く - - - end - end -end - -class Person - extend DynamicAccessor - - my_attr_accessor :name, :age -end -person = Person.new -person.name = "Alice" -person.age = 30 - -puts "Name: #{person.name}, Age: #{person.age}" -``` - -```ruby-exec:practice12_1.rb -Name: Alice, Age: 30 -``` - -### 練習問題2: シンプルな設定オブジェクト - -`method_missing` を使って、ハッシュのように動作する `SimpleConfig` クラスを作成してください。`config.api_key = "12345"` のように値を設定でき、`config.api_key` で値を取得できるようにしてください。設定されていないキーを呼び出した場合は `nil` を返すようにします。 - -```ruby:practice12_2.rb -class SimpleConfig - def initialize - @settings = {} - end - - def method_missing(method_name, *args, &block) - # ここにコードを書いてください - - - end - - def respond_to_missing?(method_name, include_private = false) - true - end -end - -config = SimpleConfig.new -config.api_key = "12345" -puts "API Key: #{config.api_key.inspect}" -puts "Timeout: #{config.timeout.inspect}" # 設定されていないキー -``` - -```ruby-exec:practice12_2.rb -API Key: "12345" -Timeout: nil -``` diff --git a/public/docs/ruby-2.md b/public/docs/ruby-2.md deleted file mode 100644 index 821662b..0000000 --- a/public/docs/ruby-2.md +++ /dev/null @@ -1,212 +0,0 @@ -# 第2章: 基本構文とデータ型 - Rubyの「書き方」 - -Rubyへようこそ!他の言語の経験がある皆さんなら、Rubyの柔軟で読みやすい構文にすぐに慣れるでしょう。この章では、Rubyの基本的な構成要素を見ていきます。 - -## 💎 変数、定数、スコープ - -Rubyの変数は型宣言を必要としませんが、変数の「スコープ(可視範囲)」は名前の付け方によって決まります。これは他の言語と大きく異なる点です。 - - * **ローカル変数**: `my_var` - * 小文字または `_` で始まります。定義されたスコープ(メソッド定義、ブロック、ファイルのトップレベルなど)でのみ有効です。 - * **インスタンス変数**: `@my_var` - * `@` で始まります。特定のオブジェクトのインスタンスに属し、そのオブジェクトのメソッド内からアクセスできます。(クラスの章で詳述します) - * **クラス変数**: `@@my_var` - * `@@` で始まります。クラス全体とそのサブクラスで共有されます。(クラスの章で詳述します) - * **グローバル変数**: `$my_var` - * `$` で始まります。プログラムのどこからでもアクセス可能ですが、グローバルな状態を持つため、使用は最小限に抑えるべきです。 - * **定数**: `MY_CONSTANT` - * 大文字で始まります。一度定義すると変更すべきではない値を示します(技術的には変更可能ですが、Rubyが警告を出します)。 - -```ruby-repl:1 -irb(main):001> local_var = "I am local" -=> "I am local" -irb(main):002> @instance_var = "I belong to an object" -=> "I belong to an object" -irb(main):003> $global_var = "Available everywhere" -=> "Available everywhere" -irb(main):004> MY_CONSTANT = 3.14 -=> 3.14 -irb(main):005> MY_CONSTANT = 3.14159 # 警告が出ます -(irb):5: warning: already initialized constant MY_CONSTANT -(irb):4: warning: previous definition of MY_CONSTANT was here -=> 3.14159 -``` - -## 🔢 Rubyの基本データ型 - -Rubyには多くの組み込みデータ型がありますが、まずは基本的なものを押さえましょう。 - - * **Integer (整数)**: `1`, `100`, `-5`, `1_000_000` ( `_` は読みやすさのためのもので、無視されます) - * **Float (浮動小数点数)**: `1.5`, `3.14`, `-0.001` - * **String (文字列)**: `"Hello"`, `'World'` - * **Boolean (真偽値)**: `true`, `false` - * **NilClass (nil)**: `nil` (何も存在しないことを示す唯一の値) - * **Array (配列)**: `[1, "apple", true]` - * **Hash (ハッシュ)**: `{"key1" => "value1", :key2 => "value2"}` - * **Symbol (シンボル)**: `:my_symbol` (後述します) - -Rubyでは、これらすべてが「オブジェクト」であり、メソッドを持っています。 - -```ruby-repl:2 -irb(main):001> 100.class -=> Integer -irb(main):002> "Hello".class -=> String -irb(main):003> 3.14.class -=> Float -irb(main):004> true.class -=> TrueClass -irb(main):005> nil.class -=> NilClass -irb(main):006> [1, 2].class -=> Array -irb(main):007> {a: 1}.class -=> Hash -irb(main):008> :symbol.class -=> Symbol -``` - -## 🚫 重要: nil と false の扱い - -Rubyの条件分岐(`if`文など)において、**偽 (falsey) として扱われるのは `nil` と `false` の2つだけ**です。 - -これは非常に重要です。C言語やJavaScriptなどの `0`、空文字列 `""`、空配列 `[]` が偽として扱われる言語とは異なります。Rubyでは、これらはすべて**真 (truthy)** として扱われます。 - -```ruby:truthy_check.rb -def check_truthy(label, value) - if value - puts "[#{label}] は真 (truthy) です。" - else - puts "[#{label}] は偽 (falsey) です。" - end -end - -check_truthy("false", false) -check_truthy("nil", nil) -puts "---" -check_truthy("true", true) -check_truthy("0 (Integer)", 0) -check_truthy("1 (Integer)", 1) -check_truthy("空文字列 \"\"", "") -check_truthy("文字列 \"abc\"", "abc") -check_truthy("空配列 []", []) -check_truthy("空ハッシュ {}", {}) -``` - -```ruby-exec:truthy_check.rb -[false] は偽 (falsey) です。 -[nil] は偽 (falsey) です。 ---- -[true] は真 (truthy) です。 -[0 (Integer)] は真 (truthy) です。 -[1 (Integer)] は真 (truthy) です。 -[空文字列 ""] は真 (truthy) です。 -[文字列 "abc"] は真 (truthy) です。 -[空配列 []] は真 (truthy) です。 -[空ハッシュ {}] は真 (truthy) です。 -``` - -## 💬 重要: シンボル (Symbol) とは何か? - -シンボルは、他の言語の経験者にとってRubyの最初の「つまずきポイント」かもしれません。 - -シンボルはコロン ( `:` ) で始まります(例: `:name`, `:status`)。 - -**文字列 (String) とシンボル (Symbol) の違い:** - -1. **イミュータブル (Immutable)**: - * シンボルは一度作成されると変更できません。`"hello"[0] = "H"` は可能ですが、 `:hello` に対してこのような操作はできません。 -2. **一意性 (Identity)**: - * 同じ内容の文字列は、作成されるたびに異なるオブジェクトID(メモリ上の場所)を持つことがあります。 - * 同じ内容のシンボルは、プログラム全体で**常に同一のオブジェクト**を指します。 -3. **パフォーマンス**: - * シンボルは内部的に整数として扱われるため、文字列の比較よりも高速です。 - -**主な用途**: - - * **ハッシュのキー**: パフォーマンスとメモリ効率のため、シンボルはハッシュのキーとして非常によく使われます。 - * `user = { name: "Alice", age: 30 }` (これは `{ :name => "Alice", :age => 30 }` のシンタックスシュガーです) - * **メソッド名や状態の識別子**: `status = :pending`, `status = :completed` のように、固定された「名前」や「状態」を表すのに使われます。 - -```ruby-repl:3 -irb(main):001> "hello".object_id # 実行ごとに変わる -=> 60 -irb(main):002> "hello".object_id # 異なるID -=> 80 -irb(main):003> :hello.object_id # 常に同じ -=> 1084828 -irb(main):004> :hello.object_id # 同一のID -=> 1084828 -irb(main):005> "status".to_sym # 文字列とシンボルの変換 -=> :status -irb(main):006> :status.to_s -=> "status" -``` - -シンボルは「名前」そのもの、文字列は「データ」そのもの、と考えると分かりやすいかもしれません。 - -## 🚀 メソッド呼び出し(括弧の省略記法) - -Rubyでは、メソッドを呼び出す際の括弧 `()` を省略できます(ただし、曖昧さが生じない場合に限ります)。 - -```ruby-repl:4 -irb(main):001> puts("Hello, World!") # 括弧あり (推奨されることが多い) -Hello, World! -=> nil -irb(main):002> puts "Hello, World!" # 括弧なし (DSLや単純な呼び出しでよく使われる) -Hello, World! -=> nil -irb(main):003> 5.+(3) # '+' も実はメソッド呼び出し -=> 8 -irb(main):004> 5 + 3 # これは 5.+(3) のシンタックスシュガー -=> 8 -``` - -括弧を省略するとコードが読みやすくなる場合がありますが、メソッドチェーンが続く場合や、引数が複雑な場合は括弧を付けた方が明確です。 - -## 📜 文字列操作と式展開 - -Rubyの文字列は強力で、特に「式展開」は頻繁に使われます。 - - * **シングルクォート (`'...'`)**: ほぼそのまま文字列として扱います。`\n`(改行)などのエスケープシーケンスや式展開は解釈**されません**(`\'` と `\\` を除く)。 - * **ダブルクォート (`"..."`)**: エスケープシーケンス(`\n`, `\t` など)を解釈し、**式展開 (Interpolation)** を行います。 - -式展開は `#{...}` という構文を使い、`...` の部分でRubyのコードを実行し、その結果を文字列に埋め込みます。 - -```ruby-repl:5 -irb(main):001> name = "Alice" -=> "Alice" -irb(main):002> puts 'Hello, #{name}\nWelcome!' # シングルクォート -Hello, #{name}\nWelcome! -=> nil -irb(main):003> puts "Hello, #{name}\nWelcome!" # ダブルクォート -Hello, Alice -Welcome! -=> nil -irb(main):004> puts "1 + 2 = #{1 + 2}" # 式展開内では計算も可能 -1 + 2 = 3 -=> nil -irb(main):005> "Ruby" + " " + "Rocks" # 文字列の連結と繰り返し -=> "Ruby Rocks" -irb(main):006> "Go! " * 3 -=> "Go! Go! Go! " -``` - -## 📝 この章のまとめ - - * Rubyの変数は、先頭の記号 (`@`, `@@`, `$`) によってスコープが決まる。 - * `false` と `nil` のみが偽 (falsey) であり、`0` や `""` も真 (truthy) として扱われる。 - * シンボル (`:name`) はイミュータブルで一意な「名前」を表し、主にハッシュのキーや識別子として使われる。 - * メソッド呼び出しの括弧は、曖昧さがない限り省略できる。 - * ダブルクォート文字列 (`"..."`) は式展開 `#{...}` をサポートする。 - -### 練習問題1: 式展開とデータ型 - -ユーザーの名前(`name`)と年齢(`age`)を変数に代入してください。 -次に、`"#{...}"`(式展開)を使い、「(名前)さんの年齢は(年齢)歳です。5年後は(5年後の年齢)歳になります。」という文字列を出力するスクリプトを作成してください。 - -```ruby:practice2_1.rb -``` - -```ruby-exec:practice2_1.rb -``` diff --git a/public/docs/ruby-3.md b/public/docs/ruby-3.md deleted file mode 100644 index 9d1e31e..0000000 --- a/public/docs/ruby-3.md +++ /dev/null @@ -1,354 +0,0 @@ -# 第3章: 制御構造とメソッド定義 - -Rubyの制御構造は、他の多くの言語と似ていますが、Rubyの「すべてが式である」という哲学と、読みやすさを重視した構文(`unless`など)に特徴があります。また、メソッド(関数)の定義は非常に柔軟で、強力な引数の扱いや例外処理の仕組みを備えています。 - -## 条件分岐 - -Rubyの条件分岐は、`if`、`unless`、`case`が基本です。`if`や`case`は文(Statement)ではなく**式(Expression)**であるため、それ自体が値を返します。 - -### if, else, elsif - -基本的な構文は他言語と同様ですが、`else if`は `elsif`(`e`が1つ)と綴る点に注意してください。 - -`if`は値を返すため、結果を変数に代入できます。 - -```ruby-repl:1 -irb(main):001:0> score = 85 -=> 85 -irb(main):002:0> grade = if score > 90 -irb(main):003:1* "A" -irb(main):004:1* elsif score > 80 # "else if" ではない -irb(main):005:1* "B" -irb(main):006:1* else -irb(main):007:1* "C" -irb(main):008:1* end -=> "B" -irb(main):009:0> puts "あなたの成績は#{grade}です" -あなたの成績はBです -=> nil -``` - -### unless - -`unless`は `if !`(もし~でなければ)の糖衣構文(Syntactic Sugar)です。条件が**偽 (false)** の場合にブロックが実行されます。 - -```ruby-repl:2 -irb(main):010:0> logged_in = false -=> false -irb(main):011:0> unless logged_in -irb(main):012:1* puts "ログインしてください" -irb(main):013:1* end -ログインしてください -=> nil -``` - -> **補足:** `unless` に `else` を付けることも可能ですが、多くの場合 `if` を使った方が可読性が高くなります。 - -### case - -C言語やJavaの `switch` 文に似ていますが、より強力です。`when` 節では、複数の値、範囲(Range)、正規表現、さらにはクラスを指定することもできます。`break` は不要です。 - -```ruby:case_example.rb -def analyze_input(input) - puts "Input: #{input.inspect}" - result = case input - when 0 - "ゼロ" - when 1..9 - "一桁の数字" - when "admin", "guest" - "特定のユーザー" - when String - "その他の文字列" - when /Error/ - "エラーメッセージ" - else - "不明な型" - end - puts "Result: #{result}" -end - -analyze_input(5) -analyze_input("guest") -analyze_input("Some value") -analyze_input(nil) -``` - -```ruby-exec:case_example.rb -Input: 5 -Result: 一桁の数字 -Input: "guest" -Result: 特定のユーザー -Input: "Some value" -Result: その他の文字列 -Input: nil -Result: 不明な型 -``` - -## 繰り返し処理 - -Rubyでは、後の章で学ぶイテレータ(`each`など)が繰り返し処理の主流ですが、C言語スタイルの `while` や `until` も利用可能です。 - -### while - -条件が**真 (true)** の間、ループを続けます。 - -```ruby-repl:3 -irb(main):001:0> i = 0 -=> 0 -irb(main):002:0> while i < 3 -irb(main):003:1* print i, " " # printは改行しません -irb(main):004:1* i += 1 # Rubyに i++ はありません -irb(main):005:1* end -0 1 2 => nil -``` - -### until - -`while !` と同じです。条件が**偽 (false)** の間、ループを続けます。 - -```ruby-repl:4 -irb(main):006:0> counter = 5 -=> 5 -irb(main):007:0> until counter == 0 -irb(main):008:1* print counter, " " -irb(main):009:1* counter -= 1 -irb(main):010:1* end -5 4 3 2 1 => nil -``` - -## メソッドの定義 (def) - -Rubyでは、`def` キーワードを使ってメソッドを定義します。 - -### 基本的な定義と戻り値(returnの省略) - -Rubyのメソッドは、**最後に評価された式の結果**を暗黙的に返します。`return` キーワードは、メソッドの途中で明示的に値を返したい場合(早期リターン)に使いますが、必須ではありません。 - -```ruby:method_return.rb -# 最後に評価された a + b が自動的に戻り値となる -def add(a, b) - a + b -end - -# 早期リターンで return を使う例 -def check_value(val) - if val < 0 - return "Negative" # ここで処理が中断 - end - - # val >= 0 の場合は、この式が評価され、戻り値となる - "Positive or Zero" -end - -puts add(10, 5) -puts check_value(-10) -puts check_value(10) -``` - -```ruby-exec:method_return.rb -15 -Negative -Positive or Zero -``` - -## 引数の種類 - -Rubyは、デフォルト引数、キーワード引数、可変長引数など、柔軟な引数の定義をサポートしています。 - -### デフォルト引数 - -引数にデフォルト値を設定できます。 - -```ruby-repl:5 -irb(main):001:0> def greet(name = "Guest") -irb(main):002:1* "Hello, #{name}!" -irb(main):003:1* end -=> :greet -irb(main):004:0> greet("Alice") -=> "Hello, Alice!" -irb(main):005:0> greet -=> "Hello, Guest!" -``` - -### キーワード引数 - -Pythonのように、引数名を指定して値を渡すことができます。`:`(コロン)を末尾に付けます。キーワード引数は可読性を大幅に向上させます。 - -```ruby:keyword_arguments.rb -# name: は必須のキーワード引数 -# age: はデフォルト値を持つキーワード引数 -def register_user(name:, age: nil, admin: false) - puts "User: #{name}" - puts "Age: #{age}" if age - puts "Admin: #{admin}" -end - -# 順序を問わない -register_user(admin: true, name: "Taro") - -puts "---" - -# 必須の name を省略すると ArgumentError になる -begin - register_user(age: 30) -rescue ArgumentError => e - puts e.message -end -``` - -```ruby-exec:keyword_arguments.rb -User: Taro -Admin: true ---- -missing keyword: :name -``` - -### 可変長引数 (Splat演算子) - -引数の先頭に `*`(Splat演算子)を付けると、任意の数の引数を配列として受け取ることができます。 - -```ruby-repl:6 -irb(main):006:0> def summarize(*items) -irb(main):007:1* puts "Items count: #{items.length}" -irb(main):008:1* puts "Items: #{items.join(', ')}" -irb(main):009:1* end -=> :summarize -irb(main):010:0> summarize("Apple", "Banana", "Orange") -Items count: 3 -Items: Apple, Banana, Orange -=> nil -irb(main):011:0> summarize("Book") -Items count: 1 -Items: Book -=> nil -irb(main):012:0> summarize -Items count: 0 -Items: -=> nil -``` - -## 例外処理 - -JavaやPythonの `try-catch-finally` に相当する構文として、Rubyは `begin-rescue-ensure` を提供します。 - -### begin, rescue, ensure - - * `begin`: 例外が発生する可能性のある処理を囲みます。 - * `rescue`: 例外を捕捉(catch)します。捕捉する例外クラスを指定できます。 - * `else`: (Optional) `begin` ブロックで例外が発生しなかった場合に実行されます。 - * `ensure`: (Optional) 例外の有無にかかわらず、最後に必ず実行されます(finally)。 - -```ruby:exception_example.rb -def safe_divide(a, b) - begin - # メインの処理 - result = a / b - rescue ZeroDivisionError => e - # ゼロ除算エラーを捕捉 - puts "Error: ゼロで割ることはできません。" - puts "(#{e.class}: #{e.message})" - result = nil - rescue TypeError => e - # 型エラーを捕捉 - puts "Error: 数値以外が使われました。" - puts "(#{e.class}: #{e.message})" - result = nil - else - # 例外が発生しなかった場合 - puts "計算成功: #{result}" - ensure - # 常に実行 - puts "--- 処理終了 ---" - end - - return result -end - -safe_divide(10, 2) -safe_divide(10, 0) -safe_divide(10, "a") -``` - -```ruby-exec:exception_example.rb -計算成功: 5 ---- 処理終了 --- -Error: ゼロで割ることはできません。 -(ZeroDivisionError: divided by 0) ---- 処理終了 --- -Error: 数値以外が使われました。 -(TypeError: String can't be coerced into Integer) ---- 処理終了 --- -``` - -> **補足:** `def` ... `end` のメソッド定義内では、`begin` と `end` は省略可能です。 - -### raise (例外の発生) - -`raise` を使って、意図的に例外を発生(throw)させることができます。 - -```ruby-repl:7 -irb(main):001:0> def check_age(age) -irb(main):002:1* if age < 0 -irb(main):003:2* # raise "エラーメッセージ" -irb(main):004:2* # raise 例外クラス, "エラーメッセージ" -irb(main):005:2* raise ArgumentError, "年齢は負の値にできません" -irb(main):006:2* end -irb(main):007:1* puts "年齢は #{age} 歳です" -irb(main):008:1* end -=> :check_age -irb(main):009:0> check_age(20) -年齢は 20 歳です -=> nil -irb(main):010:0> check_age(-5) -(irb):5:in `check_age': 年齢は負の値にできません (ArgumentError) - from (irb):10:in `
      ' - ... -``` - -## この章のまとめ - - * Rubyの制御構造(`if`, `case`)は**式**であり、値を返します。 - * `if !` の代わりに `unless` を、`while !` の代わりに `until` を使うことで、否定条件を読みやすく記述できます。 - * メソッドの戻り値は、`return` を使わずとも**最後に評価された式**が自動的に返されます。 - * メソッドの引数は、**デフォルト引数**、**キーワード引数** (`name:`), **可変長引数** (`*args`) を駆使することで、非常に柔軟に定義できます。 - * 例外処理は `begin`, `rescue` (catch), `ensure` (finally) で行い、`raise` で意図的に例外を発生させます。 - -### 練習問題1: 評価メソッドの作成 - -生徒の点数(0〜100)を受け取り、以下の基準で評価(文字列)を返すメソッド `evaluate_score(score)` を作成してください。 - - * 90点以上: "A" - * 70点〜89点: "B" - * 50点〜69点: "C" - * 50点未満: "D" - * 0未満または100を超える場合: `ArgumentError` を `raise` してください。 - -`case` 文と `raise` を使用して実装してください。 - -```ruby:practice3_1.rb -``` - -```ruby-exec:practice3_1.rb -``` - -### 練習問題2: 柔軟なログ出力メソッド - -ログメッセージ(必須)と、オプションとしてログレベル(キーワード引数 `level:`)およびタグ(可変長引数 `tags`)を受け取るメソッド `logger` を作成してください。 - - * メソッドシグネチャ: `def logger(message, level: "INFO", *tags)` - * 実行例: - * `logger("Server started")` - * 出力: `[INFO] Server started` - * `logger("User login failed", level: "WARN", "security", "auth")` - * 出力: `[WARN] (security, auth) User login failed` - * `logger("DB connection lost", level: "ERROR", "database")` - * 出力: `[ERROR] (database) DB connection lost` - -(ヒント: タグの配列 `tags` が空でないかを確認し、`join` メソッドを使って整形してください。) - -```ruby:practice3_2.rb -``` - -```ruby-exec:practice3_2.rb -``` diff --git a/public/docs/ruby-4.md b/public/docs/ruby-4.md deleted file mode 100644 index be8605f..0000000 --- a/public/docs/ruby-4.md +++ /dev/null @@ -1,181 +0,0 @@ -# 第4章: すべてがオブジェクト - -Rubyの設計思想における最も重要かつ強力なコンセプトの一つは、「すべてがオブジェクトである」という点です。他の言語、例えばJavaやC++では、数値(int, double)や真偽値(boolean)は「プリミティブ型」として扱われ、オブジェクトとは区別されます。 - -しかしRubyでは、`5` のような数値も、`"hello"` のような文字列も、そして `nil` さえも、すべてがメソッド(振る舞い)を持つオブジェクトです。 - -## 🎯 Rubyの核心: 5.times の衝撃 - -他の言語の経験者がRubyに触れて最初に驚くことの一つが、以下のようなコードが動作することです。 - -```ruby-repl:1 -irb(main):001:0> 5.times do -irb(main):002:1* print "Ruby! " -irb(main):003:1> end -Ruby! Ruby! Ruby! Ruby! Ruby! => 5 -``` - -`5` という数値リテラルが `.times` というメソッドを呼び出しています。これは、`5` が単なる値ではなく、`Integer` クラスのインスタンス(オブジェクト)だからです。 - -同様に、文字列もオブジェクトです。 - -```ruby-repl:2 -irb(main):001:0> "hello, world".upcase -=> "HELLO, WORLD" -irb(main):002:0> "hello, world".length -=> 12 -``` - -`"hello, world"` という `String` オブジェクトが、`upcase` や `length` というメソッド(メッセージ)に応答しています。 - -`.class` メソッドを使うと、そのオブジェクトがどのクラスに属しているかを確認できます。 - -```ruby-repl:3 -irb(main):001:0> 5.class -=> Integer -irb(main):002:0> "hello".class -=> String -irb(main):003:0> 3.14.class -=> Float -``` - -## 👻 nil オブジェクト: 無ですらオブジェクト - -Rubyには「何もない」「無効」な状態を示す `nil` という特別な値があります。これは他の言語における `null` や `None` に相当します。 - -しかし、Rubyの哲学を徹底している点は、この `nil` ですらオブジェクトであるということです。 - -```ruby-repl:4 -irb(main):001:0> nil.class -=> NilClass -``` - -`nil` は `NilClass` という専用クラスの唯一のインスタンスです。オブジェクトであるため、`nil` もメソッドを持ちます。 - -```ruby-repl:5 -irb(main):001:0> nil.nil? -=> true -irb(main):002:0> "hello".nil? -=> false -irb(main):003:0> nil.to_s -=> "" -irb(main):004:0> nil.to_i -=> 0 -``` - -`nil` がメソッドを持つことで、`null` チェックに起因するエラー(例えば `null.someMethod()` のような呼び出しによるエラー)を避けやすくなり、より安全で流暢なコードが書ける場合があります。 - -## 📨 メソッド呼び出しの仕組み: メッセージパッシング - -Rubyのメソッド呼び出し `オブジェクト.メソッド名(引数)` は、厳密には「**メッセージパッシング**」という概念に基づいています。 - -`5.times` というコードは、以下のように解釈されます。 - -1. レシーバ(受信者): `5` という `Integer` オブジェクト -2. メッセージ: `:times` というシンボル(メソッド名) -3. `5` オブジェクトに `:times` というメッセージを送る。 -4. `5` オブジェクト(の所属する `Integer` クラス)は、そのメッセージを解釈し、関連付けられた処理(ブロックを5回実行する)を実行する。 - -この考え方は、オブジェクト指向の「カプセル化(オブジェクトが自身の振る舞いを決定する)」を強力にサポートします。`+` などの演算子でさえ、実際にはメソッド呼び出しのシンタックスシュガー(糖衣構文)です。 - -```ruby-repl:6 -irb(main):001:0> 10 + 3 -=> 13 -irb(main):002:0> 10.+(3) # 内部的にはこれと同じ -=> 13 -``` - -## 🛠️ よく使う組み込みクラスのメソッド - -すべてがオブジェクトであるため、Rubyは基本的なデータ型に対して非常に多くの便利なメソッドを標準で提供しています。 - -### String (文字列) - -`String` クラスには、テキスト操作のための豊富なメソッドが用意されています。 - -```ruby:string_methods.rb -text = " ruby is convenient " - -# 先頭と末尾の空白を除去 -cleaned_text = text.strip -puts "Strip: '#{cleaned_text}'" - -# 先頭の文字を大文字に -puts "Capitalize: #{cleaned_text.capitalize}" - -# "convenient" を "powerful" に置換 -puts "Gsub: #{cleaned_text.gsub("convenient", "powerful")}" - -# "ruby" という文字列で始まっているか? -puts "Start with 'ruby'?: #{cleaned_text.start_with?("ruby")}" - -# 単語に分割 (配列が返る) -words = cleaned_text.split(" ") -p words # p はデバッグ用の表示メソッド -``` - -```ruby-exec:string_methods.rb -Strip: 'ruby is convenient' -Capitalize: Ruby is convenient -Gsub: ruby is powerful -Start with 'ruby'?: true -["ruby", "is", "convenient"] -``` - -### Integer / Float (数値) - -数値クラス (総称して `Numeric`) も便利なメソッドを持っています。 - -```ruby-repl:7 -irb(main):001:0> # Integer -irb(main):002:0> 10.even? -=> true -irb(main):003:0> 10.odd? -=> false -irb(main):004:0> 5.to_s -=> "5" -irb(main):005:0> 5.to_f -=> 5.0 - -irb(main):006:0> # Float -irb(main):007:0> 10.5.round -=> 11 -irb(main):008:0> 10.5.floor # 切り捨て -=> 10 -irb(main):009:0> 10.5.ceil # 切り上げ -=> 11 -irb(main):010:0> (10.5).to_i -=> 10 -``` - -## 📜 この章のまとめ - - * Rubyでは、数値、文字列、`nil` を含むすべてが **オブジェクト** です。 - * すべてのオブジェクトは **クラス** に属しています(例: `5` は `Integer` クラス)。 - * オブジェクトであるため、すべての値は **メソッド** を持つことができます(例: `5.times`, `"hello".upcase`)。 - * メソッド呼び出しは、オブジェクトへの **メッセージパッシング** として理解されます。 - * `nil` も `NilClass` のオブジェクトであり、メソッドを持ちます。 - -### 練習問題1: 文字列の操作 -変数 `sentence = " Welcome to the Ruby World! "` があります。`String` のメソッドを組み合わせて、最終的に `"WELCOME, RUBY"` という文字列をコンソールに出力してください。 - - * ヒント: `strip`, `upcase`, `gsub` (または `sub`), `slice` (またはインデックスアクセス `[]`) などが使えます。 - -```ruby:practice4_1.rb -sentence = " Welcome to the Ruby World! " -``` - -```ruby-exec:practice4_1.rb -``` - - -### 練習問題2: 数値と判定 - -`Float` の値 `123.456` があります。この値を四捨五入して整数(`Integer`)にした後、その整数が偶数(even)か奇数(odd)かを判定して、`"Result is even"` または `"Result is odd"` と出力するコードを書いてください。 - -```ruby:practice4_2.rb -value = 123.456 -``` - -```ruby-exec:practice4_2.rb -``` diff --git a/public/docs/ruby-5.md b/public/docs/ruby-5.md deleted file mode 100644 index 0dadc7d..0000000 --- a/public/docs/ruby-5.md +++ /dev/null @@ -1,206 +0,0 @@ -# 第5章: コレクション (Array, Hash, Range) - -Rubyの強力な機能の一つに、柔軟で直感的なコレクション(データを集めて格納するオブジェクト)があります。他の言語でのListやMap、Dictionaryに相当するものを学びましょう。この章では、`Array`(配列)、`Hash`(ハッシュ)、そして `Range`(範囲)を扱います。 - -## 配列 (Array) - -Rubyの `Array` は、他の言語における動的配列やリストに似ています。順序付けられた要素のコレクションであり、異なるデータ型の要素を混在させることができます。 - -### 生成と操作 - -配列は `[]` (角括弧) を使って生成します。 - -```ruby-repl:1 -irb(main):001:0> numbers = [1, 2, 3, 4, 5] -=> [1, 2, 3, 4, 5] -irb(main):002:0> mixed = [1, "hello", true, 3.14] # 型の混在が可能 -=> [1, "hello", true, 3.14] -irb(main):003:0> empty_array = [] -=> [] -``` - -要素へのアクセスは `[index]` を使います。Rubyのインデックスは0から始まり、**負のインデックス**(末尾からのアクセス)をサポートしているのが特徴です。 - -```ruby-repl:2 -irb(main):004:0> numbers[0] # 最初の要素 -=> 1 -irb(main):005:0> numbers[-1] # 末尾の要素 -=> 5 -irb(main):006:0> numbers[-2] # 末尾から2番目の要素 -=> 4 -``` - -### 要素の追加と削除 - -要素の追加には `<<` (shovel演算子) や `push` メソッドを使います。 `pop` は末尾の要素を削除し、それを返します。 - -```ruby-repl:3 -irb(main):007:0> fruits = ["apple", "banana"] -=> ["apple", "banana"] -irb(main):008:0> fruits << "cherry" # << (shovel) は高速で一般的 -=> ["apple", "banana", "cherry"] -irb(main):009:0> fruits.push("orange") -=> ["apple", "banana", "cherry", "orange"] -irb(main):010:0> last_fruit = fruits.pop -=> "orange" -irb(main):011:0> fruits -=> ["apple", "banana", "cherry"] -``` - -### 便利なメソッド - -`Array` には非常に多くの便利なメソッドが用意されています。 - -```ruby-repl:4 -irb(main):012:0> fruits.length # 要素数 -=> 3 -irb(main):013:0> fruits.include?("banana") # 要素が含まれているか -=> true -irb(main):014:0> fruits.sort # ソートされた新しい配列を返す -=> ["apple", "banana", "cherry"] -irb(main):015:0> fruits.first # 最初の要素 -=> "apple" -irb(main):016:0> fruits.last # 最後の要素 -=> "cherry" -``` - -## ハッシュ (Hash) - -`Hash` は、キーと値のペアを格納するコレクションです。他の言語のMap、Dictionary、連想配列に相当します。 - -### 2種類のシンタックス - -Rubyのハッシュには2つの主要な記法があります。 - -#### 1\. 旧シンタックス (Rocket Syntax) - -`=>`(ハッシュロケット)を使う記法です。キーには**任意のオブジェクト**(文字列、数値、シンボルなど)を使用できます。 - -```ruby-repl:5 -irb(main):001:0> # キーが文字列の場合 -irb(main):002:0> user_profile = { "name" => "Alice", "age" => 30 } -=> {"name"=>"Alice", "age"=>30} -irb(main):003:0> user_profile["name"] -=> "Alice" -``` - -#### 2\. 新シンタックス (JSON-like Syntax) - -Ruby 1.9から導入された、より簡潔な記法です。JavaScriptのオブジェクトリテラルに似ています。 - -> **注意:** この記法を使うと、**キーは自動的にシンボル (Symbol) になります**。 - -```ruby-repl:6 -irb(main):004:0> # 新シンタックス (キーはシンボルになる) -irb(main):005:0> user_profile_new = { name: "Bob", age: 25 } -=> {:name=>"Bob", :age=>25} -irb(main):006:0> # アクセス時もシンボル (:name) を使う -irb(main):007:0> user_profile_new[:name] -=> "Bob" -``` - -現在では、キーが固定されている場合は、シンボルを使った新シンタックスが好まれます。 - -## 範囲 (Range) - -`Range` は、連続する値のシーケンスを表すオブジェクトです。`for` ループや `case` 文での条件分岐によく使われます。 - -範囲の作成には `(start..end)` と `(start...end)` の2つの形式があります。 - -### `..` (終端を含む) - -`..`(ドット2つ)は、終端の値を含む範囲を作成します。 - -```ruby-repl:9 -irb(main):001:0> inclusive_range = (1..10) # 1から10まで (10を含む) -=> 1..10 -irb(main):002:0> inclusive_range.to_a # to_aで配列に変換できる -=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] -irb(main):003:0> inclusive_range.include?(10) -=> true -``` - -### `...` (終端を含まない) - -`...`(ドット3つ)は、終端の値を含まない(未満の)範囲を作成します。 - -```ruby-repl:10 -irb(main):004:0> exclusive_range = (1...10) # 1から10まで (10を含まない) -=> 1...10 -irb(main):005:0> exclusive_range.to_a -=> [1, 2, 3, 4, 5, 6, 7, 8, 9] -irb(main):006:0> exclusive_range.include?(10) -=> false -``` - -### 範囲の活用例 - -`Range` は `case` 文と組み合わせると非常に強力です。 - -```ruby:grade_checker.rb -def assign_grade(score) - case score - when (90..100) - "A" - when (80...90) # 80は含むが90は含まない (80-89) - "B" - when (60...80) - "C" - else - "F" - end -end - -puts "Score 95: #{assign_grade(95)}" -puts "Score 90: #{assign_grade(90)}" -puts "Score 89: #{assign_grade(89)}" -puts "Score 60: #{assign_grade(60)}" -puts "Score 59: #{assign_grade(59)}" -``` - -```ruby-exec:grade_checker.rb -Score 95: A -Score 90: A -Score 89: B -Score 60: C -Score 59: F -``` - -## この章のまとめ - - * **Array**: `[]` で作成する順序付きリスト。`<<` で追加、`pop` で取り出し、`[-1]` で末尾にアクセスできます。 - * **Hash**: `{}` で作成するキー/バリューペア。 - * **Symbol**: `:name` のようにコロンで始まる識別子。イミュータブルで高速なため、ハッシュのキーに最適です。 - * **Hashのシンタックス**: キーがシンボルの場合、`{ key: "value" }` というモダンな記法が使えます。 - * **Range**: `(1..10)`(含む)と `(1...10)`(含まない)があり、連続したシーケンスを表現します。 - -### 練習問題1: ショッピングカートの管理 - -あなたのショッピングカートを表現する配列 `cart` があります。 -`cart` は、商品情報を表すハッシュの配列です。 -以下の操作を行ってください。 - -1. `cart` に `{ name: "Orange", price: 120 }` を追加する。 -2. `cart` の最初の商品の名前 (`"Apple"`) を表示する。 - -```ruby:practice5_1.rb -cart = [{ name: "Apple", price: 100 }, { name: "Banana", price: 80 }] -``` - -```ruby-exec:practice5_1.rb -``` - -### 練習問題2: ハッシュの操作 - -ユーザーの設定を保存するハッシュ `settings` を作成してください。 - -* キーにはシンボルを使用します (`:theme`, `:notifications`)。 -* `:theme` の初期値は `:light`、`:notifications` の初期値は `true` とします。 -* `settings` を作成した後、`:theme` の値を `:dark` に更新してください。 - -```ruby:practice5_2.rb -settings = -``` - -```ruby-exec:practice5_2.rb -``` diff --git a/public/docs/ruby-6.md b/public/docs/ruby-6.md deleted file mode 100644 index 04f17e2..0000000 --- a/public/docs/ruby-6.md +++ /dev/null @@ -1,307 +0,0 @@ -# 第6章: ブロックとイテレータ - Rubyの最重要機能 - -Rubyの学習において、**ブロック (Block)** は最も重要で強力な機能の一つです。他言語の経験者にとって、これはラムダ式や無名関数、クロージャに似た概念ですが、Rubyではこれが言語構文の核に深く組み込まれています。 - -この章では、ブロックの使い方と、ブロックを活用する「イテレータ」と呼ばれるメソッドを学びます。 - -## ブロック構文: do...end と {} - -ブロックとは、メソッド呼び出しに渡すことができる**コードの塊**です。メソッド側は、受け取ったそのコードの塊を好きなタイミングで実行できます。 - -ブロックには2種類の書き方があります。 - -1. **`{ ... }` (波括弧)**: 通常、1行で完結する場合に使われます。 -2. **`do ... end`**: 複数行にわたる処理を書く場合に使われます。 - -どちらも機能的にはほぼ同じです。最も簡単な例は、指定した回数だけブロックを実行する `times` メソッドです。 - -```ruby-repl:1 -irb(main):001:0> 3.times { puts "Hello!" } -Hello! -Hello! -Hello! -=> 3 - -irb(main):002:0> 3.times do -irb(main):003:1* puts "Ruby is fun!" -irb(main):004:1> end -Ruby is fun! -Ruby is fun! -Ruby is fun! -=> 3 -``` - -`3.times` というメソッド呼び出しの後ろに `{ ... }` や `do ... end` で囲まれたコードブロックを渡しています。`times` メソッドは、そのブロックを3回実行します。 - -## 代表的なイテレータ - -Rubyでは、コレクション(配列やハッシュなど)の各要素に対して処理を行うメソッドを**イテレータ (Iterator)** と呼びます。イテレータは通常、ブロックを受け取って動作します。 - -代表的なイテレータを見ていきましょう。 - -### each - -`each` は、コレクションの各要素を順番に取り出してブロックを実行します。他言語の `foreach` ループに最も近いものです。 - -`|n|` の部分は**ブロック引数**と呼ばれ、イテレータが取り出した要素(この場合は配列の各要素)を受け取ります。 - -```ruby-repl:2 -irb(main):001:0> numbers = [1, 2, 3] -=> [1, 2, 3] - -irb(main):002:0> numbers.each do |n| -irb(main):003:1* puts "Current number is #{n}" -irb(main):004:1> end -Current number is 1 -Current number is 2 -Current number is 3 -=> [1, 2, 3] -``` - -> **Note:** `each` メソッドの戻り値は、元の配列 (`[1, 2, 3]`) 自身です。`each` はあくまで「繰り返すこと」が目的であり、ブロックの実行結果は利用しません。 - -### map (collect) - -`map` は、各要素に対してブロックを実行し、その**ブロックの戻り値**を集めた**新しい配列**を返します。 - -```ruby-repl:3 -irb(main):005:0> numbers = [1, 2, 3] -=> [1, 2, 3] - -irb(main):006:0> doubled = numbers.map { |n| n * 2 } -=> [2, 4, 6] - -irb(main):007:0> puts doubled.inspect -[2, 4, 6] -=> nil - -irb(main):008:0> puts numbers.inspect # 元の配列は変更されない -[1, 2, 3] -=> nil -``` - -`map` は、元の配列を変換した新しい配列が欲しい場合に非常に便利です。 - -### select (filter) - -`select` は、各要素に対してブロックを実行し、ブロックの戻り値が**真 (true)** になった要素だけを集めた**新しい配列**を返します。 - -```ruby-repl:4 -irb(main):009:0> numbers = [1, 2, 3, 4, 5, 6] -=> [1, 2, 3, 4, 5, 6] - -irb(main):010:0> evens = numbers.select { |n| n.even? } # n.even? は n % 2 == 0 と同じ -=> [2, 4, 6] -``` - -### find (detect) - -`find` は、ブロックの戻り値が**真 (true)** になった**最初の要素**を返します。見つからなければ `nil` を返します。 - -```ruby-repl:5 -irb(main):011:0> numbers = [1, 2, 3, 4, 5, 6] -=> [1, 2, 3, 4, 5, 6] - -irb(main):012:0> first_even = numbers.find { |n| n.even? } -=> 2 - -irb(main):013:0> over_10 = numbers.find { |n| n > 10 } -=> nil -``` - -## Enumerableモジュール:イテレーションの力 - -`each`, `map`, `select`, `find` といった便利なメソッドは、実は `Enumerable`(エニューメラブル)という**モジュール**によって提供されています。 - -`Enumerable` はRubyの「Mix-in(ミックスイン)」機能の代表例です。これは、クラスに「混ぜ込む」ことで、そのクラスのインスタンスに特定の機能(メソッド群)を追加する仕組みです。 - -`Enumerable` をMix-inするクラス(例えば `Array` や `Hash`, `Range`)が満たすべき契約はただ一つ、**`each` メソッドを実装すること**です。 - -`each` メソッドさえ定義されていれば、`Enumerable` モジュールは `each` を使って `map`, `select`, `find`, `sort`, `count` など、数十もの便利なイテレーションメソッドを自動的に提供してくれます。 - -例えば、`Array` クラスは `each` を持っています。 - -```ruby-repl:6 -irb(main):014:0> numbers = [1, 2, 3] -=> [1, 2, 3] -# numbers (Array) は each を持っているので... -irb(main):015:0> numbers.map { |n| n * 2 } # map が使える -=> [2, 4, 6] -irb(main):016:0> numbers.select { |n| n.odd? } # select が使える -=> [1, 3] -``` - -これは、自分で新しいコレクションクラスを作った場合でも同様です。(`include` については後の「モジュールとMix-in」の章で詳しく学びます) - -```ruby:my_collection.rb -# Enumerableモジュールを include する -class MyCollection - include Enumerable # これがMix-in - - def initialize(items) - @items = items - end - - # Enumerable のために each メソッドを定義する - def each - @items.each do |item| - yield(item) # ブロックに要素を渡す - end - end -end - -collection = MyCollection.new([10, 20, 30]) - -# each を定義しただけで、map が使える! -doubled = collection.map { |x| x * 2 } -puts "Map result: #{doubled.inspect}" - -# select も使える! -selected = collection.select { |x| x > 15 } -puts "Select result: #{selected.inspect}" -``` - -```ruby-exec:my_collection.rb -Map result: [20, 40, 60] -Select result: [20, 30] -``` - -このように、Rubyのイテレータの強力さは `Enumerable` モジュールによって支えられています。Rubyでは、**「`each` メソッドを持つものは、すべて `Enumerable` である(あるいはそう振る舞える)」**という考え方が非常に重要です。 - -## for ループとの比較 - -他言語経験者の方は、`for` ループを使いたくなるかもしれません。 - -```c -// C や Java の for ループ -for (int i = 0; i < 3; i++) { - printf("Hello\n"); -} -``` - -Rubyにも `for` 構文は存在します。 - -```ruby-repl:7 -irb(main):014:0> numbers = [1, 2, 3] -=> [1, 2, 3] - -irb(main):015:0> for num in numbers -irb(main):016:1* puts num -irb(main):017:1> end -1 -2 -3 -=> [1, 2, 3] -``` - -しかし、Rubyの世界では `for` ループは**ほとんど使われません**。なぜなら、`for` は内部的に `each` メソッドを呼び出しているに過ぎないからです。 - -Rubyプログラマは、`for` よりも `each` などのイテレータをブロックと共に使うことを圧倒的に好みます。イテレータの方が、何をしているか(単なる繰り返し、変換、選択など)がメソッド名 (`each`, `map`, `select`) から明確であり、コードが読みやすくなるためです。 - -## ブロック引数とブロックの戻り値 - -すでに出てきたように、ブロックは `| ... |` を使って引数を受け取ることができます。 - -```ruby-repl:8 -irb(main):018:0> ["Alice", "Bob"].each do |name| -irb(main):019:1* puts "Hello, #{name}!" -irb(main):020:1> end -Hello, Alice! -Hello, Bob! -=> ["Alice", "Bob"] -``` - -また、ブロックも(Rubyのすべての式と同様に)戻り値を持ちます。ブロックの戻り値とは、**ブロック内で最後に評価された式の値**です。 - - * `each` はブロックの戻り値を**無視**します。 - * `map` はブロックの戻り値を**集めて新しい配列**にします。 - * `select` はブロックの戻り値が**真か偽か**を判定に使います。 - -```ruby-repl:9 -irb(main):021:0> result = [1, 2].map do |n| -irb(main):022:1* m = n * 10 # mは 10, 20 -irb(main):023:1* m + 5 # ブロックの戻り値 (15, 25) -irb(main):024:1> end -=> [15, 25] -``` - -## yield:ブロックを受け取るメソッド - -では、どうすればブロックを受け取るメソッドを自分で作れるのでしょうか? -それには `yield` というキーワードを使います。 - -メソッド内で `yield` が呼び出されると、そのメソッドに渡されたブロックが実行されます。 - -```ruby:yield_basic.rb -def simple_call - puts "メソッド開始" - yield # ここでブロックが実行される - puts "メソッド終了" -end - -simple_call do - puts "ブロック内の処理です" -end -``` - -```ruby-exec:yield_basic.rb -メソッド開始 -ブロック内の処理です -メソッド終了 -``` - -`yield` はブロックに引数を渡すこともできます。 - -```ruby:yield_with_args.rb -def call_with_name(name) - puts "メソッド開始" - yield(name) # ブロックに "Alice" を渡す - yield("Bob") # ブロックに "Bob" を渡す - puts "メソッド終了" -end - -call_with_name("Alice") do |n| - puts "ブロックが #{n} を受け取りました" -end -``` - -```ruby-exec:yield_with_args.rb -メソッド開始 -ブロックが Alice を受け取りました -ブロックが Bob を受け取りました -メソッド終了 -``` - -`each` や `map` のようなイテレータは、内部でこの `yield` を使って、コレクションの各要素をブロックに渡しながら実行しているのです。 - -## この章のまとめ - - * **ブロック**は、メソッドに渡せるコードの塊で、`{}`(1行)または `do...end`(複数行)で記述します。 - * **イテレータ**は、ブロックを受け取り、要素の繰り返し処理を行うメソッドです(`each`, `map`, `select` など)。 - * **Enumerableモジュール**は、 `each` を実装するクラスに `map` や `select` などの強力なイテレーション機能を提供します。 - * Rubyでは `for` ループよりもイテレータが好まれます。 - * ブロックは `|arg|` で引数を受け取ることができ、ブロックの最後の式の値が戻り値となります。 - * 自作メソッド内で `yield` を使うと、渡されたブロックを実行できます。 - -### 練習問題1 - -数値の配列 `[1, 2, 3, 4, 5]` があります。`map` イテレータとブロックを使って、各要素を文字列に変換し(例: `1` → `"1"`)、 `"1"`, `"2"`, `"3"`, `"4"`, `"5"` という文字列の配列を作成してください。 - -```ruby:practice6_1.rb -array = [1, 2, 3, 4, 5] - -``` - -```ruby-exec:practice6_1.rb -``` - -### 練習問題2 -文字列の配列 `["apple", "banana", "cherry", "date"]` があります。`select` イテレータとブロックを使って、文字数が5文字以上の果物だけを抽出した新しい配列(`["apple", "banana", "cherry"]`)を作成してください。 - -```ruby:practice6_2.rb -array = ["apple", "banana", "cherry", "date"] - -``` - -```ruby-exec:practice6_2.rb -``` diff --git a/public/docs/ruby-7.md b/public/docs/ruby-7.md deleted file mode 100644 index 934ce0d..0000000 --- a/public/docs/ruby-7.md +++ /dev/null @@ -1,381 +0,0 @@ -# 第7章: クラスとオブジェクト(基本) - -Rubyは純粋なオブジェクト指向言語であり、第4章「すべてがオブジェクト」で学んだように、数値や文字列さえもオブジェクトです。この章では、それらのオブジェクトの「設計図」である**クラス**を定義する方法について学びます。 - -他のオブジェクト指向言語(Java, Python, C\#など)の経験があれば、概念は馴染み深いはずです。Ruby特有の構文(`@`や`attr_*`など)に注目してください。 - -## 💴 クラス定義: class, initialize - -Rubyでは、`class`キーワードを使ってクラスを定義します。クラス名は慣習として**大文字**で始めます(例: `MyClass`)。 - -`new`メソッドが呼ばれたときに実行される特別なメソッドが `initialize` です。これは他の言語における**コンストラクタ**に相当し、インスタンスの初期化処理を行います。 - -```ruby:user.rb -# クラス名はアッパーキャメルケース(PascalCase)で記述します -class User - # newが呼ばれた際に自動的に実行される初期化メソッド - def initialize(name, age) - # インスタンス変数は @ で始める - @name = name - @age = age - puts "Userオブジェクトが作成されました!" - end -end - -# クラスからインスタンスを生成 -# User.new は initialize メソッドを呼び出す -user1 = User.new("Alice", 30) -user2 = User.new("Bob", 25) - -p user1 -p user2 -``` - -```ruby-exec:user.rb -Userオブジェクトが作成されました! -Userオブジェクトが作成されました! - - -``` - -## 🏃‍♂️ インスタンス変数 (@var) とインスタンスメソッド - -### インスタンス変数 - -`@`で始まる変数(例: `@name`)は**インスタンス変数**です。 - - * そのクラスのインスタンス(オブジェクト)ごとに個別に保持されます。 - * `initialize`や他のインスタンスメソッド内で定義・参照されます。 - * **デフォルトで外部から直接アクセスすることはできません(カプセル化)**。 - -### インスタンスメソッド - -`def`で定義されたメソッド(`initialize`を除く)が**インスタンスメソッド**です。これらはインスタンスの「振る舞い」を定義し、そのインスタンスのインスタンス変数(`@var`)にアクセスできます。 - -```ruby:user_greet.rb -class User - def initialize(name, age) - @name = name - @age = age - end - - # インスタンスメソッドの定義 - def greet - # メソッド内からインスタンス変数(@name, @age)を参照 - puts "こんにちは、#{@name}さん (#{@age}歳) です。" - end -end - -user1 = User.new("Alice", 30) - -# インスタンスメソッドの呼び出し -user1.greet -``` - -```ruby-exec:user_greet.rb -こんにちは、Aliceさん (30歳) です。 -``` - -## 🔐 アクセサ: attr\_reader, attr\_writer, attr\_accessor - -前述の通り、`@name`のようなインスタンス変数は外部から直接参照・変更できません。 - -```ruby:access_error.rb -class User - def initialize(name) - @name = name - end -end - -user = User.new("Alice") -p user.name #=> NoMethodError -user.name = "Bob" #=> NoMethodError -``` - -```ruby-exec:access_error.rb -NoMethodError (undefined method `name'...) -``` - - -外部からアクセスさせるためには、**アクセサメソッド**(ゲッターとセッター)を定義する必要があります。 - -### 手動での定義 - -JavaやC\#のように、ゲッターとセッターを明示的に書くこともできます。 - -```ruby:manual_accessor.rb -class Product - def initialize(name) - @name = name - end - - # ゲッター (値の読み取り) - def name - @name - end - - # セッター (値の書き込み) - # メソッド名が = で終わるのが特徴 - def name=(new_name) - @name = new_name - end -end - -item = Product.new("Laptop") -puts item.name # ゲッター(item.name)の呼び出し -item.name = "Desktop" # セッター(item.name=)の呼び出し -puts item.name -``` - -```ruby-exec:manual_accessor.rb -Laptop -Desktop -``` - -### `attr_*` による自動定義 - -Rubyでは、上記のような定型的なアクセサメソッドを自動で定義するための便利な「マクロ」が用意されています。これらはクラス定義のトップレベルで使います。 - - * `attr_reader :var` : ゲッター(読み取り専用)を定義します。 - * `attr_writer :var` : セッター(書き込み専用)を定義します。 - * `attr_accessor :var` : ゲッターとセッターの両方を定義します。 - -引数にはインスタンス変数名の`@`を除いた**シンボル**(`:`から始まる名前)を渡します。 - -```ruby:auto_accessor.rb -class Product - # @name のゲッターとセッターを自動定義 - attr_accessor :name - # @price のゲッターのみを自動定義 (読み取り専用) - attr_reader :price - # @stock のセッターのみを自動定義 (書き込み専用) - attr_writer :stock - - def initialize(name, price, stock) - @name = name - @price = price - @stock = stock - end - - def summary - # ゲッターは self.price とも書けるが、 - # クラス内部では @price と直接アクセスするのが一般的 - "商品: #{@name}, 価格: #{@price}円" - end -end - -item = Product.new("Mouse", 3000, 50) - -# attr_accessor -puts item.name # ゲッター -item.name = "Keyboard" # セッター -puts item.name - -# attr_reader -puts item.price # ゲッター -# item.price = 3500 # => NoMethodError (undefined method `price=') - -# attr_writer -# puts item.stock # => NoMethodError (undefined method `stock') -item.stock = 100 # セッター - -puts item.summary -``` - -```ruby-exec:auto_accessor.rb -Mouse -Keyboard -3000 -商品: Keyboard, 価格: 3000円 -``` - -## 🏢 クラス変数 (@@var) とクラスメソッド (self.method\_name) - -### クラス変数 (@@var) - -`@@`で始まる変数(例: `@@count`)は**クラス変数**です。 - - * インスタンスごとではなく、**クラス全体で共有**されます。 - * そのクラスのすべてのインスタンスから参照・変更が可能です。 - * (注意)継承した場合、子クラスとも共有されるため、意図しない動作の原因になることもあり、使用には注意が必要です。 - -### クラスメソッド (self.method\_name) - -インスタンスではなく、**クラス自体から呼び出すメソッド**です。`def self.メソッド名` のように `self.` をつけて定義します。 - - * `User.new` の `new` も、実はクラスメソッドの一種です。 - * インスタンス変数 (`@var`) にはアクセスできません(インスタンスが存在しないため)。 - * クラス変数 (`@@var`) にはアクセスできます。 - * ファクトリメソッド(特定のパターンのインスタンスを生成するメソッド)や、クラス全体に関わる操作(例: 総数のカウント)によく使われます。 - -```ruby:counter.rb -class Counter - # クラス変数(クラス全体で共有) - @@total_count = 0 - - attr_reader :id - - def initialize(id) - @id = id - # インスタンスが作られるたびにクラス変数を増やす - @@total_count += 1 - end - - # クラスメソッド (self. をつける) - # クラス変数を返す - def self.total_count - @@total_count - end - - # インスタンスメソッド - def report_total - # インスタンスメソッドからもクラス変数を参照できる - "私のIDは #{@id} です。総数は #{@@total_count} です。" - end -end - -# クラスメソッドの呼び出し -puts "初期カウント: #{Counter.total_count}" #=> 0 - -c1 = Counter.new(1) -c2 = Counter.new(2) - -# クラスメソッドの呼び出し -puts "最終カウント: #{Counter.total_count}" #=> 2 - -# インスタンスメソッドの呼び出し -puts c1.report_total #=> 私のIDは 1 です。総数は 2 です。 -puts c2.report_total #=> 私のIDは 2 です。総数は 2 です。 - -# c1.total_count #=> NoMethodError (インスタンスからは呼べない) -``` - -```ruby-exec:counter.rb -初期カウント: 0 -最終カウント: 2 -私のIDは 1 です。総数は 2 です。 -私のIDは 2 です。総数は 2 です。 -``` - -## 👪 継承 (\<) と super - -Rubyは**単一継承**をサポートしています。`<` 記号を使って親クラス(スーパークラス)を指定します。 - -子クラス(サブクラス)は、親クラスのメソッドや変数を引き継ぎます。 - -### `super` - -子クラスで親クラスと同じ名前のメソッドを定義(**オーバーライド**)した際、`super`キーワードを使うと、**親クラスの同名メソッドを呼び出す**ことができます。 - -これは特に `initialize` メソッドで、親クラスの初期化処理を呼び出すために必須となります。 - -```ruby:vehicle.rb -# 親クラス (スーパークラス) -class Vehicle - attr_reader :name - - def initialize(name) - @name = name - puts "Vehicleを初期化中: #{@name}" - end - - def move - puts "#{@name} は移動します。" - end -end - -# 子クラス (サブクラス) -# Vehicle クラスを継承 -class Car < Vehicle - def initialize(name, color) - # super で親クラスの initialize を呼び出す - # (name を渡す) - super(name) - @color = color - puts "Carを初期化中: 色は#{@color}" - end - - # move メソッドをオーバーライド (上書き) - def move - # super で親クラスの move メソッドを呼び出す - super - # Car 固有の処理を追加 - puts "車輪が回転します。" - end -end - -my_car = Car.new("MyCar", "Red") -puts "---" -my_car.move -``` - -```ruby-exec:vehicle.rb -Vehicleを初期化中: MyCar -Carを初期化中: 色はRed ---- -MyCar は移動します。 -車輪が回転します。 -``` - -`super` は引数を省略すると、現在のメソッドが受け取った引数をそのまま親メソッドに渡します。`super()` のように `()` をつけると、引数なしで親メソッドを呼び出します。 - -## 📝 この章のまとめ - - * クラスは `class` キーワードで定義し、インスタンスは `.new` で生成します。 - * `initialize` はインスタンス生成時に呼ばれるコンストラクタです。 - * インスタンス変数は `@` で始まり、インスタンスごとに独立し、デフォルトでプライベートです。 - * `attr_reader`, `attr_writer`, `attr_accessor` は、インスタンス変数へのアクセサ(ゲッター/セッター)を自動定義するマクロです。 - * クラス変数は `@@` で始まり、クラスと全インスタンスで共有されます。 - * クラスメソッドは `def self.メソッド名` で定義し、クラス自体から呼び出します。 - * 継承は `<` で行い、`super` で親クラスの同名メソッドを呼び出します。 - -### 練習問題1: `Book` クラスの作成 - -以下の仕様を持つ `Book` クラスを作成してください。 - -1. `initialize` で `title`(タイトル)と `author`(著者)を受け取る。 -2. `title` と `author` は、インスタンス変数(`@title`, `@author`)に格納する。 -3. `title` と `author` は、どちらも外部から読み取り可能(書き換えは不可)にする。 -4. `info` というインスタンスメソッドを持ち、`"タイトル: [title], 著者: [author]"` という形式の文字列を返す。 - -```ruby:practice7_1.rb -# ここにBookクラスの定義を書いてください - - -book = Book.new("Ruby入門", "Sato") -puts book.info -puts book.title -# book.title = "改訂版" #=> エラー (NoMethodError) になるはず -``` - -```ruby-exec:practice7_1.rb -(実行結果例) -タイトル: Ruby入門, 著者: Sato -Ruby入門 -``` - - -### 練習問題2: 継承を使った `EBook` クラスの作成 - -問題1で作成した `Book` クラスを継承して、以下の仕様を持つ `EBook`(電子書籍)クラスを作成してください。 - -1. `initialize` で `title`, `author`, `file_size`(ファイルサイズ, 例: "10MB")を受け取る。 -2. `title` と `author` の初期化は、`Book` クラスの `initialize` を利用する (`super` を使う)。 -3. `file_size` は外部から読み取り可能にする。 -4. `info` メソッドをオーバーライドし、`"タイトル: [title], 著者: [author] (ファイルサイズ: [file_size])"` という形式の文字列を返す。 - * ヒント: 親クラスの `info` メソッドの結果を `super` で利用すると効率的です。 - -```ruby:practice7_2.rb -require './practice7_1.rb' # 7_1のコードを実行してBookの定義を読み込みます - -# ここにEBookクラスの定義を書いてください - -ebook = EBook.new("実践Ruby", "Tanaka", "2.5MB") -puts ebook.info -puts ebook.title -``` - -```ruby-exec:practice7_2.rb -タイトル: 実践Ruby, 著者: Tanaka (ファイルサイズ: 2.5MB) -実践Ruby -``` diff --git a/public/docs/ruby-8.md b/public/docs/ruby-8.md deleted file mode 100644 index 89c2ae3..0000000 --- a/public/docs/ruby-8.md +++ /dev/null @@ -1,306 +0,0 @@ -# 第8章: モジュールとミックスイン(オブジェクト指向の拡張) - -Rubyのオブジェクト指向において、クラスの継承は「is-a」(〜である)関係を表現するのに適しています。しかし、「has-a」(〜を持つ)や「can-do」(〜ができる)といった**振る舞い(ビヘイビア)**を複数の異なるクラス間で共有したい場合があります。 - -他の言語では「インターフェース」や「トレイト」で解決するこの問題を、Rubyは**モジュール (Module)** と **ミックスイン (Mix-in)** という強力な仕組みで解決します。 - -## モジュール (module) の2つの役割 - -`module` キーワードで定義されるモジュールには、大きく分けて2つの主要な役割があります。 - -1. **名前空間 (Namespace):** - 関連するクラス、メソッド、定数を一つのグループにまとめ、名前の衝突(コンフリクト)を防ぎます。 -2. **ミックスイン (Mix-in):** - メソッドの集まりを定義し、それをクラスに `include` することで、インスタンスメソッドとして機能を追加します。これはRubyの「多重継承」の代替手段です。 - -## 名前空間としてのモジュール - -プログラムが大規模になると、異なる目的で同じ名前のクラス(例: `Database::User` と `WebApp::User`)を使いたくなることがあります。モジュールは、これらを区別するための「仕切り」として機能します。 - -名前空間内の要素には、`::` (スコープ解決演算子) を使ってアクセスします。 - -```ruby:module_example.rb -module AppUtilities - VERSION = "1.0.0" - - class Logger - def log(msg) - puts "[App log] #{msg}" - end - end - - # モジュールメソッド (self. をつける) - def self.default_message - "Hello from Utility" - end -end - -# 定数へのアクセス -puts AppUtilities::VERSION - -# モジュールメソッドの呼び出し -puts AppUtilities.default_message - -# モジュール内のクラスのインスタンス化 -logger = AppUtilities::Logger.new -logger.log("Initialized.") -``` - -```ruby-exec:module_example.rb -1.0.0 -Hello from Utility -[App log] Initialized. -``` - -## ミックスインとしてのモジュール (include) - -モジュールの最も強力な機能がミックスインです。これにより、クラスは継承ツリーとは無関係に、モジュールの振る舞い(インスタンスメソッド)を取り込むことができます。 - -`include` を使うと、モジュールはクラスの継承チェーン(祖先チェーン)に挿入されます。具体的には、`include` したクラスのスーパークラスの「直前」に挿入されます。 - -```ruby:mix_in_example.rb -# 「飛ぶ」能力を提供するモジュール -module Flyable - def fly - puts "I'm flying! My speed is #{fly_speed}." - end - - # このモジュールは、include したクラスが - # `fly_speed` メソッドを実装していることを期待している -end - -# 「泳ぐ」能力を提供するモジュール -module Swimmable - def swim - puts "I'm swimming!" - end -end - -class Bird - # fly_speed を実装 - def fly_speed - "10km/h" - end -end - -class Duck < Bird - include Flyable # 飛べる - include Swimmable # 泳げる -end - -class Penguin < Bird - include Swimmable # 泳げる (飛べない) -end - -class Airplane - include Flyable # 飛べる (生物ではない) - - def fly_speed - "800km/h" - end -end - -puts "--- Duck ---" -duck = Duck.new -duck.fly -duck.swim - -puts "--- Penguin ---" -penguin = Penguin.new -# penguin.fly #=> NoMethodError -penguin.swim - -puts "--- Airplane ---" -airplane = Airplane.new -airplane.fly -# airplane.swim #=> NoMethodError -``` - -```ruby-exec:mix_in_example.rb ---- Duck --- -I'm flying! My speed is 10km/h. -I'm swimming! ---- Penguin --- -I'm swimming! ---- Airplane --- -I'm flying! My speed is 800km/h. -``` - -`Duck` と `Airplane` は全く異なるクラス(`Bird` のサブクラスと、`Object` のサブクラス)ですが、`Flyable` モジュールを `include` することで `fly` メソッドを共有できています。 - -## include vs extend - -`include` と `extend` は、モジュールのメソッドをどこに追加するかが異なります。 - - * `include`: モジュールのメソッドを、クラスの**インスタンスメソッド**として追加します。 - * `extend`: モジュールのメソッドを、クラスの**クラスメソッド**(特異メソッド)として追加します。 - -```ruby:extend_example.rb -module HelperMethods - def info - "This is a helper method." - end -end - -# --- include の場合 --- -class IncludedClass - include HelperMethods -end - -obj = IncludedClass.new -obj.info # インスタンスメソッドになる -# IncludedClass.info #=> NoMethodError - -# --- extend の場合 --- -class ExtendedClass - extend HelperMethods -end - -ExtendedClass.info # クラスメソッドになる -obj2 = ExtendedClass.new -# obj2.info #=> NoMethodError -``` - -```ruby-exec:extend_example.rb -"This is a helper method." -"This is a helper method." -``` - -## アクセスコントロール (public, private, protected) - -Rubyのアクセスコントロールは、他の言語と少し異なる振る舞い、特に `private` の動作に特徴があります。 - - * `public` (デフォルト) - - * どこからでも呼び出せます。レシーバ(`object.`)を省略しても、明示しても構いません。 - - * `private` - - * **レシーバを明示して呼び出すことができません**。 - * `self.` を付けずに、クラス内部(またはサブクラス)からのみ呼び出せます。 - * 主にクラス内部の詳細を隠蔽(カプセル化)するために使われます。 - - * `protected` - - * `private` と似ていますが、**同じクラス(またはサブクラス)の他のインスタンスをレシーバとして呼び出すことができます**。 - * オブジェクト同士を比較するメソッドなどで使われます。 - -```ruby:access_control_demo.rb -class Wallet - attr_reader :id - - def initialize(id, amount) - @id = id - @balance = amount # private なインスタンス変数 - end - - # public メソッド (外部インターフェース) - def transfer(other_wallet, amount) - if withdraw(amount) - other_wallet.deposit(amount) - puts "Transferred #{amount} from #{self.id} to #{other_wallet.id}" - else - puts "Transfer failed: Insufficient funds in #{self.id}" - end - end - - # protected メソッド (インスタンス間での連携) - protected - - def deposit(amount) - @balance += amount - end - - # private メソッド (内部処理) - private - - def withdraw(amount) - if @balance >= amount - @balance -= amount - true - else - false - end - end -end - -w1 = Wallet.new("Wallet-A", 100) -w2 = Wallet.new("Wallet-B", 50) - -# public メソッドはどこからでも呼べる -w1.transfer(w2, 70) - -puts "w1 ID: #{w1.id}" -# puts "w1 Balance: #{w1.balance}" #=> NoMethodError (attr_reader がないため) - -# private / protected メソッドは外部から直接呼べない -# w1.deposit(100) #=> NoMethodError: protected method `deposit' called... -# w1.withdraw(10) #=> NoMethodError: private method `withdraw' called... -``` - -```ruby-exec:access_control_demo.rb -Transferred 70 from Wallet-A to Wallet-B -w1 ID: Wallet-A -``` - -この例では、`transfer` (public) が内部で `withdraw` (private) を呼び出し、引数で受け取った `other_wallet` の `deposit` (protected) を呼び出しています。`deposit` は `protected` なので、`other_wallet.` というレシーバを明示しても `Wallet` クラス内からは呼び出せます。 - -## この章のまとめ - - * **モジュール**は `module` キーワードで定義され、**名前空間**と**ミックスイン**の2つの役割を持ちます。 - * 名前空間としては、`::` を使って定数やクラスをグループ化し、名前の衝突を防ぎます。 - * ミックスインとしては、`include` することでモジュールのメソッドを**インスタンスメソッド**としてクラスに追加できます。これは多重継承の代わりとなる強力な機能です。 - * `extend` は、モジュールのメソッドを**クラスメソッド**として追加します。 - * `public`, `private`, `protected` でメソッドの可視性を制御します。 - * Rubyの `private` は「レシーバを指定して呼び出せない」というユニークな制約を持ちます。 - -### 練習問題1: カウンター機能のミックスイン - -`Enumerable` モジュール(第6章で少し触れました)のように、`include` したクラスに便利な機能を追加するモジュールを作成します。 - -1. `Counter` というモジュールを定義してください。 -2. `Counter` モジュールは `count_items(item_to_find)` というメソッドを持つものとします。 -3. このメソッドは、`include` したクラスが `items` という名前の配列(`Array`)を返すインスタンスメソッドを持っていることを前提とします。 -4. `count_items` は、その `items` 配列内に `item_to_find` がいくつ含まれているかを返します。 -5. `ShoppingCart` クラスと `WordList` クラスを作成し、両方で `items` メソッドを実装し、`Counter` モジュールを `include` して `count_items` が動作することを確認してください。 - -```ruby:practice8_1.rb -module Counter - -end - -class ShoppingCart - -end - -class WordList - -end - - - -``` - -```ruby-exec:practice8_1.rb -``` - -### 練習問題2: protected を使った比較 - -`protected` のユースケースを理解するための問題です。 - -1. `Score` クラスを作成します。`initialize` で `@value` (得点)をインスタンス変数として保持します。 -2. `higher_than?(other_score)` という `public` なインスタンスメソッドを定義してください。これは、`other_score` (`Score` の別のインスタンス)より自分の `@value` が高ければ `true` を返します。 -3. `higher_than?` メソッドの実装のために、`value` という `protected` メソッドを作成し、`@value` を返すようにしてください。 -4. `higher_than?` の内部では、`self.value > other_score.value` のように `protected` メソッドを呼び出してください。 -5. 2つの `Score` インスタンスを作成し、`higher_than?` が正しく動作することを確認してください。また、`protected` メソッドである `value` をインスタンスの外部から直接呼び出そうとするとエラーになることも示してください。 - -```ruby:practice8_2.rb -class Score - -end - - -``` - -```ruby-exec:practice8_2.rb -``` diff --git a/public/docs/ruby-9.md b/public/docs/ruby-9.md deleted file mode 100644 index 9f87b2f..0000000 --- a/public/docs/ruby-9.md +++ /dev/null @@ -1,311 +0,0 @@ -# 第9章: Proc, Lambda, クロージャ - -これまでの章で、Rubyの強力な機能である「ブロック」を `each` や `map` などのメソッドと共に使ってきました。しかし、ブロックは常にメソッド呼び出しに付随する形でしか使えませんでした。 - -この章では、そのブロックを「オブジェクト」として扱い、変数に代入したり、メソッドの引数として自由に受け渡したりする方法を学びます。これにより、Rubyの表現力はさらに向上します。 - -## ブロックをオブジェクトとして扱う: Proc クラス - -ブロックは、それ自体ではオブジェクトではありません。しかし、Rubyにはブロックをオブジェクト化するための `Proc` クラスが用意されています。 - -`Proc.new` にブロックを渡すことで、`Proc` オブジェクトを作成できます。 - -```ruby-repl:1 -irb(main):001:0> greeter = Proc.new { |name| puts "Hello, #{name}!" } -irb(main):002:0> greeter -=> # -``` - -作成した `Proc` オブジェクトは、`call` メソッドを使って実行できます。 - -```ruby-repl:2 -irb(main):003:0> greeter.call("Alice") -Hello, Alice! -=> nil -irb(main):004:0> greeter.call("Bob") -Hello, Bob! -=> nil -``` - -`proc` という `Proc.new` のエイリアスメソッドもよく使われます。 - -```ruby-repl:3 -irb(main):005:0> multiplier = proc { |x| x * 2 } -=> # -irb(main):006:0> multiplier.call(10) -=> 20 -``` - -## Proc.new と lambda の違い - -`Proc` オブジェクトを作成するもう一つの方法として `lambda` があります。(`->` というリテラル構文もよく使われます) - -```ruby-repl:4 -irb(main):007:0> adder_lambda = lambda { |a, b| a + b } -=> # -irb(main):008:0> adder_lambda.call(3, 4) -=> 7 - -irb(main):009:0> subtractor_lambda = ->(x, y) { x - y } -=> # -irb(main):010:0> subtractor_lambda.call(10, 3) -=> 7 -``` - -`lambda` で作成されたオブジェクトも `Proc` クラスのインスタンスですが、`Proc.new` (または `proc`) で作成されたものとは、主に以下の2点で挙動が異なります。 - -### 1\. return の挙動 - - * **Proc.new (proc)**: `return` は、Procが定義されたスコープ(通常はメソッド)からリターンします(**ローカルリターン**)。 - * **lambda**: `return` は、`lambda` ブロックの実行からリターンするだけです(**Procからのリターン**)。 - -これは、メソッド内で `Proc` オブジェクトを定義して実行すると、その違いが明確になります。 - -**Proc.new の例:** - -```ruby:proc_return_example.rb -def proc_return_test - # Proc.new で Proc オブジェクトを作成 - my_proc = Proc.new do - puts "Proc: Inside proc" - return "Proc: Returned from proc" # メソッド全体からリターンする - end - - my_proc.call # Proc を実行 - puts "Proc: After proc.call (This will not be printed)" - return "Proc: Returned from method" -end - -puts proc_return_test -``` - -```ruby-exec:proc_return_example.rb -Proc: Inside proc -Proc: Returned from proc -``` - -`proc_return_test` メソッド内の `my_proc.call` が実行された時点で、Proc内の `return` が呼ばれ、メソッド自体が終了していることがわかります。 - -**lambda の例:** - -```ruby:lambda_return_example.rb -def lambda_return_test - # lambda で Proc オブジェクトを作成 - my_lambda = lambda do - puts "Lambda: Inside lambda" - return "Lambda: Returned from lambda" # lambda からリターンするだけ - end - - result = my_lambda.call # lambda を実行 - puts "Lambda: After lambda.call" - puts "Lambda: Result from lambda: #{result}" - return "Lambda: Returned from method" -end - -puts lambda_return_test -``` - -```ruby-exec:lambda_return_example.rb -Lambda: Inside lambda -Lambda: After lambda.call -Lambda: Result from lambda: Lambda: Returned from lambda -Lambda: Returned from method -``` - -`lambda` の場合、`my_lambda.call` 内の `return` は `lambda` の実行を終了させ、その戻り値が `result` 変数に代入されます。メソッドの実行は継続します。 - -### 2\. 引数の厳密さ - - * **Proc.new (proc)**: 引数の数に寛容です。足りない引数は `nil` になり、余分な引数は無視されます。 - * **lambda**: 引数の数を厳密にチェックします。過不足があると `ArgumentError` が発生します。 - -**Proc.new の例:** - -```ruby-repl:5 -irb(main):001:0> my_proc = proc { |a, b| puts "a: #{a.inspect}, b: #{b.inspect}" } -=> # - -irb(main):002:0> my_proc.call(1) # 引数が足りない -a: 1, b: nil -=> nil -irb(main):003:0> my_proc.call(1, 2, 3) # 引数が多い -a: 1, b: 2 -=> nil -``` - -**lambda の例:** - -```ruby-repl:6 -irb(main):004:0> my_lambda = lambda { |a, b| puts "a: #{a.inspect}, b: #{b.inspect}" } -=> # - -irb(main):005:0> my_lambda.call(1) # 引数が足りない -(irb):5:in `block in
      ': wrong number of arguments (given 1, expected 2) (ArgumentError) - from (irb):5:in `call' - from (irb):5:in `
      ' -... -irb(main):006:0> my_lambda.call(1, 2, 3) # 引数が多い -(irb):6:in `block in
      ': wrong number of arguments (given 3, expected 2) (ArgumentError) - from (irb):6:in `call' - from (irb):6:in `
      ' -... -``` - -一般的に、`lambda` の方が通常のメソッド定義に近い(引数が厳密で、`return` がブロックから抜けるだけ)挙動をするため、使い分けが重要です。 - -## & 演算子の役割 - -`&` 演算子は、ブロックと `Proc` オブジェクトを相互に変換する役割を果たします。 - -### 1\. ブロックを Proc として受け取る - -メソッド定義の最後の引数に `&` をつけて引数名(慣習的に `block`)を指定すると、そのメソッド呼び出し時に渡されたブロックが `Proc` オブジェクトに変換され、その変数に束縛されます。 - -```ruby:block_receiver.rb -# &block でブロックを受け取り、Proc オブジェクトとして扱う -def custom_iterator(items, &block) - puts "Got a Proc object: #{block.inspect}" - - # Proc オブジェクトを call で実行 - items.each do |item| - block.call(item.upcase) # Proc を実行 - end -end - -fruits = ["apple", "banana"] - -# ブロックを渡してメソッドを呼び出す -custom_iterator(fruits) do |fruit| - puts "Processing: #{fruit}" -end -``` - -```ruby-exec:block_receiver.rb -Got a Proc object: # -Processing: APPLE -Processing: BANANA -``` - -これにより、受け取ったブロック(`Proc`)を、メソッド内で好きなタイミングで実行したり、他のメソッドに渡したりすることが可能になります。 - -### 2\. Proc をブロックとして渡す - -逆に、メソッドを呼び出す際に、`Proc` オブジェクトを `&` 付きで渡すと、その `Proc` オブジェクトがブロックとしてメソッドに渡されます。 - -`Array#map` メソッドは通常ブロックを受け取りますが、`Proc` オブジェクトを `&` を使って渡すことができます。 - -```ruby-repl:7 -irb(main):001:0> numbers = [1, 2, 3, 4, 5] -=> [1, 2, 3, 4, 5] - -irb(main):002:0> # 2倍にする Proc オブジェクト -irb(main):003:0> doubler = proc { |n| n * 2 } -=> # - -irb(main):004:0> # & を使って Proc をブロックとして map メソッドに渡す -irb(main):005:0> numbers.map(&doubler) -=> [2, 4, 6, 8, 10] -``` - -これは、以下のコードと等価です。 - -```ruby-repl:8 -irb(main):006:0> numbers.map { |n| n * 2 } -=> [2, 4, 6, 8, 10] -``` - -`&` は、`Proc` とブロック(メソッド呼び出しに付随するコード)の間の架け橋となる重要な演算子です。 - -## クロージャ(スコープ)の概念 - -`Proc` オブジェクト(`lambda` も含む)の非常に重要な特性として、**クロージャ (Closure)** があります。 - -クロージャとは、**Proc オブジェクトが、それが定義された時点のスコープ(環境)を記憶し、後で実行される際にもそのスコープ内の変数(ローカル変数など)にアクセスできる**仕組みです。 - -JavaScriptなど、他の言語でクロージャに触れたことがあるかもしれません。Rubyの `Proc` も同様の機能を提供します。 - -```ruby:closure_example.rb -def counter_generator(initial_value) - count = initial_value - - # この lambda は、外側のスコープにある `count` 変数を記憶する - incrementer = lambda do - puts "Current count: #{count}" - count += 1 # 記憶した変数を更新 - puts "New count: #{count}" - end - - return incrementer # Proc オブジェクトを返す -end - -# counter_generator メソッドの実行は終了し、 -# 本来ローカル変数 `count` は消えるはず... -counter1 = counter_generator(10) - -puts "--- First call ---" -counter1.call # => Current count: 10, New count: 11 - -puts "--- Second call ---" -counter1.call # => Current count: 11, New count: 12 - -# 別のスコープを持つカウンターを作成 -counter2 = counter_generator(100) -puts "--- Counter 2 call ---" -counter2.call # => Current count: 100, New count: 101 - -puts "--- Counter 1 call again ---" -counter1.call # => Current count: 12, New count: 13 -``` - -```ruby-exec:closure_example.rb ---- First call --- -Current count: 10 -New count: 11 ---- Second call --- -Current count: 11 -New count: 12 ---- Counter 2 call --- -Current count: 100 -New count: 101 ---- Counter 1 call again --- -Current count: 12 -New count: 13 -``` - -`counter_generator` メソッドが終了した後でも、返された `lambda` オブジェクト(`counter1` や `counter2`)は、それぞれが定義された時点の `count` 変数を保持し続け、`call` されるたびにそれを更新できます。これがクロージャの力です。 - -## ☕ この章のまとめ - - * **Proc**: ブロックをオブジェクト化したもので、`Proc.new` や `proc` で作成できます。 - * **Lambda**: `lambda` または `->` で作成できる `Proc` オブジェクトの一種です。 - * **Proc と Lambda の違い**: - * **return**: `proc` はローカルリターン、`lambda` はProcからのリターン。 - * **引数**: `proc` は寛容、`lambda` は厳密。 - * **& 演算子**: メソッド定義で使うとブロックを `Proc` として受け取り、メソッド呼び出しで使うと `Proc` をブロックとして渡します。 - * **クロージャ**: `Proc` や `lambda` は、定義された時点のスコープ(ローカル変数など)を記憶し、後からでもアクセスできます。 - -### 練習問題1: Lambda の作成 - -引数を2つ取り、その和を返す `lambda` を作成し、`adder` という変数に代入してください。その後、`adder.call(5, 7)` を実行して `12` が返ってくることを確認してください。 - -```ruby:practice9_1.rb -adder = - -puts adder.call(5, 7) -``` - -```ruby-exec:practice9_1.rb -12 -``` - -### 練習問題2: & を使ったメソッド -数値の配列 `numbers` と、`Proc` オブジェクト `processor` を引数として受け取る `apply_proc_to_array` メソッドを定義してください。メソッド内では、配列の各要素に対して `processor` を実行し、その結果を標準出力に出力するようにしてください。 -(ヒント: メソッド呼び出し側では `&` を使って `Proc` をブロックとして渡すか、メソッド定義側で `&` を使ってブロックを受け取るか、両方の方法が考えられます。ここでは `Proc` オブジェクトをそのまま引数として受け取り、`call` で実行してみてください。) - -```ruby:practice9_2.rb - -``` - -```ruby-exec:practice9_2.rb -``` - diff --git a/public/docs/ruby/0-intro/-intro.md b/public/docs/ruby/0-intro/-intro.md new file mode 100644 index 0000000..2c52a60 --- /dev/null +++ b/public/docs/ruby/0-intro/-intro.md @@ -0,0 +1 @@ +Rubyへようこそ! 他の言語でのプログラミング経験をお持ちのあなたなら、Rubyの持つ柔軟性と「開発者の楽しさ」をすぐに感じ取れるはずです。この章では、Rubyの基本的な哲学に触れ、あなたのマシンに開発環境をセットアップし、最初のコードを実行します。 diff --git a/public/docs/ruby/0-intro/1-0-philosophy.md b/public/docs/ruby/0-intro/1-0-philosophy.md new file mode 100644 index 0000000..34bba22 --- /dev/null +++ b/public/docs/ruby/0-intro/1-0-philosophy.md @@ -0,0 +1,27 @@ +--- +id: ruby-intro-philosophy +title: Rubyの哲学と特徴 +level: 2 +--- + +## Rubyの哲学と特徴 + +Rubyは、まつもとゆきひろ(Matz)氏によって開発された、**シンプルさ**と**生産性**を重視した動的オブジェクト指向言語です。 + + * **すべてがオブジェクト (Everything is an Object)** + JavaやPythonでは`int`や`float`などのプリミティブ型がオブジェクトとは別に存在しますが、Rubyでは**すべてがメソッドを持つオブジェクト**です。`5`のような数値や`"hello"`のような文字列はもちろん、`nil`(nullに相当)や`true`/`false`さえもオブジェクトです。 + + ```ruby-repl + irb(main):001> 5.class + => Integer + irb(main):002> "hello".upcase + => "HELLO" + irb(main):003> nil.nil? + => true + ``` + + * **開発者を楽しませる (MINASWAN)** + Rubyの設計思想の核は、プログラマがストレスなく、楽しくコーディングできることを最適化する点にあります。これはしばしば「**MINASWAN** (Matz Is Nice And So We Are Nice)」というコミュニティの標語にも表れています。言語仕様が厳格さよりも「驚き最小の原則」や表現力を優先することがあります。 + + * **柔軟性と表現力** + Rubyは非常に柔軟で、既存のクラスを後から変更する(オープンクラス)ことや、コードによってコードを操作するメタプログラミングが容易です。これにより、Ruby on Railsのような強力なフレームワークや、RSpecのようなDSL(ドメイン固有言語)が生まれています。 diff --git a/public/docs/ruby/0-intro/2-0-comparison.md b/public/docs/ruby/0-intro/2-0-comparison.md new file mode 100644 index 0000000..fee7dc6 --- /dev/null +++ b/public/docs/ruby/0-intro/2-0-comparison.md @@ -0,0 +1,27 @@ +--- +id: ruby-intro-comparison +title: 他言語との簡単な比較 +level: 2 +--- + +## 他言語との簡単な比較 + +あなたの経験言語とRubyを少し比べてみましょう。 + + * **Pythonistaへ**: + + * インデントは構文的な意味を持ちません(単なる可読性のため)。 + * ブロック(コードのまとまり)は`:`とインデントではなく、`do...end`キーワード、または`{...}`(ブレース)で定義します。 + * メソッド呼び出しの丸括弧`()`は、曖昧さがなければ省略可能です。`print "hello"`のように書けます。 + + * **Java/C\# Developerへ**: + + * Rubyは静的型付けではなく**動的型付け**です。変数の型宣言(`int i = 10`)は不要で、`i = 10`と書くだけです。 + * コンパイルステップは不要です。スクリプトとして直接実行されます。 + * `public`, `private`, `protected`のアクセス修飾子はありますが、Javaなどとは少し動作が異なります(特に`private`)。 + + * **JavaScript Developerへ**: + + * Rubyもクラスベースのオブジェクト指向です(ただし、内部的にはJSのプロトタイプチェーンに似た特異クラスの仕組みも持ちます)。 + * `this`の代わりに`self`を使いますが、`self`のコンテキストはJSの`this`ほど複雑に変化せず、より直感的です。 + * `true`, `false`, `nil` 以外はすべて「Truthy(真)」として扱われます(`0`や空文字列`""`も真です)。 diff --git a/public/docs/ruby/0-intro/3-0-rbenv.md b/public/docs/ruby/0-intro/3-0-rbenv.md new file mode 100644 index 0000000..444a81a --- /dev/null +++ b/public/docs/ruby/0-intro/3-0-rbenv.md @@ -0,0 +1,37 @@ +--- +id: ruby-intro-rbenv +title: 環境構築:バージョン管理ツールの導入 +level: 2 +--- + +## 環境構築:バージョン管理ツールの導入 + +システム(OS)に最初から入っているRubyを直接使うのではなく、**バージョン管理ツール**(`rbenv`や`RVM`)を導入することを強く推奨します。 + +プロジェクトごとに異なるRubyバージョン(例: 2.7.x と 3.3.x)を簡単に切り替えることができ、システムをクリーンに保てます。 + +ここでは`rbenv`を使った一般的な流れを紹介します。(詳細なインストール手順はOSによって異なりますので、`rbenv`のGitHubリポジトリなどを参照してください。) + +1. **rbenv と ruby-build のインストール**: + Homebrew (macOS) や apt/yum (Linux) など、お使いのパッケージマネージャでインストールします。 + +2. **Rubyのインストール**: + + ```bash + # インストール可能なバージョン一覧を確認 + $ rbenv install -l + + # 最新の安定版(例: 3.3.0)をインストール + $ rbenv install 3.3.0 + ``` + +3. **使用するバージョンの設定**: + + ```bash + # このPCでのデフォルトバージョンとして設定 + $ rbenv global 3.3.0 + + # バージョンが切り替わったか確認 + $ ruby -v + ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [arm64-darwin23] + ``` diff --git a/public/docs/ruby/0-intro/4-0-irb.md b/public/docs/ruby/0-intro/4-0-irb.md new file mode 100644 index 0000000..0a90730 --- /dev/null +++ b/public/docs/ruby/0-intro/4-0-irb.md @@ -0,0 +1,29 @@ +--- +id: ruby-intro-irb +title: 対話型シェル(irb)の活用 +level: 2 +--- + +## 対話型シェル(irb)の活用 + +Rubyのインストールが完了したら、`irb` (Interactive Ruby) を起動してみましょう。これはRubyのREPL (Read-Eval-Print Loop) で、コード片を試したり、ドキュメント代わりに使ったりするのに非常に便利です。 + +このウェブサイト上ではブラウザ上でコードを実行できる環境を埋め込んでおり、以下のように緑枠で囲われたコード例には自由にRubyコードを書いて試すことができます。 + +手元のPCにインストールした環境で実行するには、ターミナル(コマンドプロンプトやPowerShellなど)で `irb` と入力すれば、同じ対話モードを起動できます。 + +```ruby-repl +irb(main):001> 10 * (5 + 3) +=> 80 +irb(main):002> "Ruby".length +=> 4 +irb(main):003> 3.times { puts "Welcome!" } +Welcome! +Welcome! +Welcome! +=> 3 +``` + +`=>` の右側に表示されているのが、式の**評価結果(返り値)**です。 + +`3.times`の例に注目してください。`puts "Welcome!"`が3回実行(出力)されていますが、`=> 3`と表示されています。これは、`3.times`というメソッド自体の返り値が`3`(レシーバである数値)であることを示しています。Rubyではすべての式が値を返すことを覚えておいてください。 diff --git a/public/docs/ruby/0-intro/5-0-helloworld.md b/public/docs/ruby/0-intro/5-0-helloworld.md new file mode 100644 index 0000000..c364a15 --- /dev/null +++ b/public/docs/ruby/0-intro/5-0-helloworld.md @@ -0,0 +1,9 @@ +--- +id: ruby-intro-helloworld +title: '"Hello, World!" とスクリプトの実行' +level: 2 +--- + +## "Hello, World\!" とスクリプトの実行 + +最後に、定番の "Hello, World\!" を2通りの方法で実行してみましょう。 diff --git a/public/docs/ruby/0-intro/5-1-irb-helloworld.md b/public/docs/ruby/0-intro/5-1-irb-helloworld.md new file mode 100644 index 0000000..845685e --- /dev/null +++ b/public/docs/ruby/0-intro/5-1-irb-helloworld.md @@ -0,0 +1,17 @@ +--- +id: ruby-intro-irb-helloworld +title: irbでの実行 +level: 3 +--- + +### irbでの実行 + +`puts`("put string")は、引数を標準出力(ターミナル)に出力し、最後に改行を追加するメソッドです。 + +```ruby-repl +irb(main):001> puts "Hello, World!" +Hello, World! +=> nil +``` + +(`puts`メソッド自体の返り値は、常に`nil`です。) diff --git a/public/docs/ruby/0-intro/5-2-run.md b/public/docs/ruby/0-intro/5-2-run.md new file mode 100644 index 0000000..b622aaa --- /dev/null +++ b/public/docs/ruby/0-intro/5-2-run.md @@ -0,0 +1,31 @@ +--- +id: ruby-intro-run +title: スクリプトファイルでの実行 +level: 3 +--- + +### スクリプトファイルでの実行 + +エディタで`hello.rb`という名前のファイルを作成します。 + +```ruby:hello.rb +#!/usr/bin/env ruby +# 1行目はShebang(シーバン)と言い、Unix系OSでスクリプトとして直接実行する際に使われます。 + +# 変数に文字列を代入 +message = "Hello from script file!" + +# 変数の内容を出力 +puts message +``` + +このファイルを実行するには、ターミナルで`ruby`コマンドの引数にファイル名を渡します。 + +このウェブサイト上では以下のように実行ボタンをクリックするとスクリプトの実行結果が表示されます。上の hello.rb のコードを変更して再度実行すると結果も変わるはずです。試してみてください。 + +```ruby-exec:hello.rb +Hello from script file! +``` + +おめでとうございます! これでRubyの世界への第一歩を踏み出しました。 +次の章では、Rubyの基本的な構文、データ型、そして他の言語にはない特徴的な「シンボル」について詳しく学んでいきます。 diff --git a/public/docs/ruby/1-basics/-intro.md b/public/docs/ruby/1-basics/-intro.md new file mode 100644 index 0000000..1dfcca7 --- /dev/null +++ b/public/docs/ruby/1-basics/-intro.md @@ -0,0 +1 @@ +Rubyへようこそ!他の言語の経験がある皆さんなら、Rubyの柔軟で読みやすい構文にすぐに慣れるでしょう。この章では、Rubyの基本的な構成要素を見ていきます。 diff --git a/public/docs/ruby/1-basics/1-0-variable.md b/public/docs/ruby/1-basics/1-0-variable.md new file mode 100644 index 0000000..9898979 --- /dev/null +++ b/public/docs/ruby/1-basics/1-0-variable.md @@ -0,0 +1,35 @@ +--- +id: ruby-basics-variable +title: 💎 変数、定数、スコープ +level: 2 +--- + +## 💎 変数、定数、スコープ + +Rubyの変数は型宣言を必要としませんが、変数の「スコープ(可視範囲)」は名前の付け方によって決まります。これは他の言語と大きく異なる点です。 + + * **ローカル変数**: `my_var` + * 小文字または `_` で始まります。定義されたスコープ(メソッド定義、ブロック、ファイルのトップレベルなど)でのみ有効です。 + * **インスタンス変数**: `@my_var` + * `@` で始まります。特定のオブジェクトのインスタンスに属し、そのオブジェクトのメソッド内からアクセスできます。(クラスの章で詳述します) + * **クラス変数**: `@@my_var` + * `@@` で始まります。クラス全体とそのサブクラスで共有されます。(クラスの章で詳述します) + * **グローバル変数**: `$my_var` + * `$` で始まります。プログラムのどこからでもアクセス可能ですが、グローバルな状態を持つため、使用は最小限に抑えるべきです。 + * **定数**: `MY_CONSTANT` + * 大文字で始まります。一度定義すると変更すべきではない値を示します(技術的には変更可能ですが、Rubyが警告を出します)。 + +```ruby-repl +irb(main):001> local_var = "I am local" +=> "I am local" +irb(main):002> @instance_var = "I belong to an object" +=> "I belong to an object" +irb(main):003> $global_var = "Available everywhere" +=> "Available everywhere" +irb(main):004> MY_CONSTANT = 3.14 +=> 3.14 +irb(main):005> MY_CONSTANT = 3.14159 # 警告が出ます +(irb):5: warning: already initialized constant MY_CONSTANT +(irb):4: warning: previous definition of MY_CONSTANT was here +=> 3.14159 +``` diff --git a/public/docs/ruby/1-basics/2-0-types.md b/public/docs/ruby/1-basics/2-0-types.md new file mode 100644 index 0000000..03bde73 --- /dev/null +++ b/public/docs/ruby/1-basics/2-0-types.md @@ -0,0 +1,39 @@ +--- +id: ruby-basics-types +title: 🔢 Rubyの基本データ型 +level: 2 +--- + +## 🔢 Rubyの基本データ型 + +Rubyには多くの組み込みデータ型がありますが、まずは基本的なものを押さえましょう。 + + * **Integer (整数)**: `1`, `100`, `-5`, `1_000_000` ( `_` は読みやすさのためのもので、無視されます) + * **Float (浮動小数点数)**: `1.5`, `3.14`, `-0.001` + * **String (文字列)**: `"Hello"`, `'World'` + * **Boolean (真偽値)**: `true`, `false` + * **NilClass (nil)**: `nil` (何も存在しないことを示す唯一の値) + * **Array (配列)**: `[1, "apple", true]` + * **Hash (ハッシュ)**: `{"key1" => "value1", :key2 => "value2"}` + * **Symbol (シンボル)**: `:my_symbol` (後述します) + +Rubyでは、これらすべてが「オブジェクト」であり、メソッドを持っています。 + +```ruby-repl +irb(main):001> 100.class +=> Integer +irb(main):002> "Hello".class +=> String +irb(main):003> 3.14.class +=> Float +irb(main):004> true.class +=> TrueClass +irb(main):005> nil.class +=> NilClass +irb(main):006> [1, 2].class +=> Array +irb(main):007> {a: 1}.class +=> Hash +irb(main):008> :symbol.class +=> Symbol +``` diff --git a/public/docs/ruby/1-basics/3-0-nil-false.md b/public/docs/ruby/1-basics/3-0-nil-false.md new file mode 100644 index 0000000..9487736 --- /dev/null +++ b/public/docs/ruby/1-basics/3-0-nil-false.md @@ -0,0 +1,45 @@ +--- +id: ruby-basics-nil-false +title: 🚫 重要: nil と false の扱い +level: 2 +--- + +## 🚫 重要: nil と false の扱い + +Rubyの条件分岐(`if`文など)において、**偽 (falsey) として扱われるのは `nil` と `false` の2つだけ**です。 + +これは非常に重要です。C言語やJavaScriptなどの `0`、空文字列 `""`、空配列 `[]` が偽として扱われる言語とは異なります。Rubyでは、これらはすべて**真 (truthy)** として扱われます。 + +```ruby:truthy_check.rb +def check_truthy(label, value) + if value + puts "[#{label}] は真 (truthy) です。" + else + puts "[#{label}] は偽 (falsey) です。" + end +end + +check_truthy("false", false) +check_truthy("nil", nil) +puts "---" +check_truthy("true", true) +check_truthy("0 (Integer)", 0) +check_truthy("1 (Integer)", 1) +check_truthy("空文字列 \"\"", "") +check_truthy("文字列 \"abc\"", "abc") +check_truthy("空配列 []", []) +check_truthy("空ハッシュ {}", {}) +``` + +```ruby-exec:truthy_check.rb +[false] は偽 (falsey) です。 +[nil] は偽 (falsey) です。 +--- +[true] は真 (truthy) です。 +[0 (Integer)] は真 (truthy) です。 +[1 (Integer)] は真 (truthy) です。 +[空文字列 ""] は真 (truthy) です。 +[文字列 "abc"] は真 (truthy) です。 +[空配列 []] は真 (truthy) です。 +[空ハッシュ {}] は真 (truthy) です。 +``` diff --git a/public/docs/ruby/1-basics/4-0-symbol.md b/public/docs/ruby/1-basics/4-0-symbol.md new file mode 100644 index 0000000..1adb3f1 --- /dev/null +++ b/public/docs/ruby/1-basics/4-0-symbol.md @@ -0,0 +1,44 @@ +--- +id: ruby-basics-symbol +title: 💬 重要: シンボル (Symbol) とは何か? +level: 2 +--- + +## 💬 重要: シンボル (Symbol) とは何か? + +シンボルは、他の言語の経験者にとってRubyの最初の「つまずきポイント」かもしれません。 + +シンボルはコロン ( `:` ) で始まります(例: `:name`, `:status`)。 + +**文字列 (String) とシンボル (Symbol) の違い:** + +1. **イミュータブル (Immutable)**: + * シンボルは一度作成されると変更できません。`"hello"[0] = "H"` は可能ですが、 `:hello` に対してこのような操作はできません。 +2. **一意性 (Identity)**: + * 同じ内容の文字列は、作成されるたびに異なるオブジェクトID(メモリ上の場所)を持つことがあります。 + * 同じ内容のシンボルは、プログラム全体で**常に同一のオブジェクト**を指します。 +3. **パフォーマンス**: + * シンボルは内部的に整数として扱われるため、文字列の比較よりも高速です。 + +**主な用途**: + + * **ハッシュのキー**: パフォーマンスとメモリ効率のため、シンボルはハッシュのキーとして非常によく使われます。 + * `user = { name: "Alice", age: 30 }` (これは `{ :name => "Alice", :age => 30 }` のシンタックスシュガーです) + * **メソッド名や状態の識別子**: `status = :pending`, `status = :completed` のように、固定された「名前」や「状態」を表すのに使われます。 + +```ruby-repl +irb(main):001> "hello".object_id # 実行ごとに変わる +=> 60 +irb(main):002> "hello".object_id # 異なるID +=> 80 +irb(main):003> :hello.object_id # 常に同じ +=> 1084828 +irb(main):004> :hello.object_id # 同一のID +=> 1084828 +irb(main):005> "status".to_sym # 文字列とシンボルの変換 +=> :status +irb(main):006> :status.to_s +=> "status" +``` + +シンボルは「名前」そのもの、文字列は「データ」そのもの、と考えると分かりやすいかもしれません。 diff --git a/public/docs/ruby/1-basics/5-0-method-call.md b/public/docs/ruby/1-basics/5-0-method-call.md new file mode 100644 index 0000000..c9cc6d9 --- /dev/null +++ b/public/docs/ruby/1-basics/5-0-method-call.md @@ -0,0 +1,24 @@ +--- +id: ruby-basics-method-call +title: 🚀 メソッド呼び出し(括弧の省略記法) +level: 2 +--- + +## 🚀 メソッド呼び出し(括弧の省略記法) + +Rubyでは、メソッドを呼び出す際の括弧 `()` を省略できます(ただし、曖昧さが生じない場合に限ります)。 + +```ruby-repl +irb(main):001> puts("Hello, World!") # 括弧あり (推奨されることが多い) +Hello, World! +=> nil +irb(main):002> puts "Hello, World!" # 括弧なし (DSLや単純な呼び出しでよく使われる) +Hello, World! +=> nil +irb(main):003> 5.+(3) # '+' も実はメソッド呼び出し +=> 8 +irb(main):004> 5 + 3 # これは 5.+(3) のシンタックスシュガー +=> 8 +``` + +括弧を省略するとコードが読みやすくなる場合がありますが、メソッドチェーンが続く場合や、引数が複雑な場合は括弧を付けた方が明確です。 diff --git a/public/docs/ruby/1-basics/6-0-string-interpolation.md b/public/docs/ruby/1-basics/6-0-string-interpolation.md new file mode 100644 index 0000000..8eb6929 --- /dev/null +++ b/public/docs/ruby/1-basics/6-0-string-interpolation.md @@ -0,0 +1,33 @@ +--- +id: ruby-basics-string-interpolation +title: 📜 文字列操作と式展開 +level: 2 +--- + +## 📜 文字列操作と式展開 + +Rubyの文字列は強力で、特に「式展開」は頻繁に使われます。 + + * **シングルクォート (`'...'`)**: ほぼそのまま文字列として扱います。`\n`(改行)などのエスケープシーケンスや式展開は解釈**されません**(`\'` と `\\` を除く)。 + * **ダブルクォート (`"..."`)**: エスケープシーケンス(`\n`, `\t` など)を解釈し、**式展開 (Interpolation)** を行います。 + +式展開は `#{...}` という構文を使い、`...` の部分でRubyのコードを実行し、その結果を文字列に埋め込みます。 + +```ruby-repl +irb(main):001> name = "Alice" +=> "Alice" +irb(main):002> puts 'Hello, #{name}\nWelcome!' # シングルクォート +Hello, #{name}\nWelcome! +=> nil +irb(main):003> puts "Hello, #{name}\nWelcome!" # ダブルクォート +Hello, Alice +Welcome! +=> nil +irb(main):004> puts "1 + 2 = #{1 + 2}" # 式展開内では計算も可能 +1 + 2 = 3 +=> nil +irb(main):005> "Ruby" + " " + "Rocks" # 文字列の連結と繰り返し +=> "Ruby Rocks" +irb(main):006> "Go! " * 3 +=> "Go! Go! Go! " +``` diff --git a/public/docs/ruby/1-basics/7-0-summary.md b/public/docs/ruby/1-basics/7-0-summary.md new file mode 100644 index 0000000..8b743a5 --- /dev/null +++ b/public/docs/ruby/1-basics/7-0-summary.md @@ -0,0 +1,13 @@ +--- +id: ruby-basics-summary +title: 📝 この章のまとめ +level: 2 +--- + +## 📝 この章のまとめ + + * Rubyの変数は、先頭の記号 (`@`, `@@`, `$`) によってスコープが決まる。 + * `false` と `nil` のみが偽 (falsey) であり、`0` や `""` も真 (truthy) として扱われる。 + * シンボル (`:name`) はイミュータブルで一意な「名前」を表し、主にハッシュのキーや識別子として使われる。 + * メソッド呼び出しの括弧は、曖昧さがない限り省略できる。 + * ダブルクォート文字列 (`"..."`) は式展開 `#{...}` をサポートする。 diff --git a/public/docs/ruby/1-basics/7-1-practice1.md b/public/docs/ruby/1-basics/7-1-practice1.md new file mode 100644 index 0000000..454d4d0 --- /dev/null +++ b/public/docs/ruby/1-basics/7-1-practice1.md @@ -0,0 +1,16 @@ +--- +id: ruby-basics-practice1 +title: '練習問題1: 式展開とデータ型' +level: 3 +--- + +### 練習問題1: 式展開とデータ型 + +ユーザーの名前(`name`)と年齢(`age`)を変数に代入してください。 +次に、`"#{...}"`(式展開)を使い、「(名前)さんの年齢は(年齢)歳です。5年後は(5年後の年齢)歳になります。」という文字列を出力するスクリプトを作成してください。 + +```ruby:practice2_1.rb +``` + +```ruby-exec:practice2_1.rb +``` diff --git a/public/docs/ruby/10-testing/-intro.md b/public/docs/ruby/10-testing/-intro.md new file mode 100644 index 0000000..b2c8a4f --- /dev/null +++ b/public/docs/ruby/10-testing/-intro.md @@ -0,0 +1,5 @@ +Rubyは動的型付け言語であり、コンパイル時ではなく実行時に型が決まります。これは柔軟で高速な開発を可能にする反面、型の不一致などによる単純なミスが実行時まで検出されにくいという特性も持ちます。 + +そのため、Rubyコミュニティでは「**テストは文化**」と言われるほど、自動化されたテストを書くことが重視されます。テストは、コードが期待通りに動作することを保証するだけでなく、未来の自分や他の開発者がコードをリファクタリング(修正・改善)する際の「安全網」として機能します。 + +この章では、Rubyに標準で添付されているテスティングフレームワーク「Minitest」を使い、テストの基本的な書き方と文化を学びます。 diff --git a/public/docs/ruby/10-testing/1-0-minitest.md b/public/docs/ruby/10-testing/1-0-minitest.md new file mode 100644 index 0000000..f7659d9 --- /dev/null +++ b/public/docs/ruby/10-testing/1-0-minitest.md @@ -0,0 +1,13 @@ +--- +id: ruby-testing-minites +title: 標準添付のテスティングフレームワーク「Minitest」 +level: 2 +--- + +## 標準添付のテスティングフレームワーク「Minitest」 + +Minitestは、Rubyに標準で含まれている(=別途インストール不要)軽量かつ高速なテストフレームワークです。 + +Ruby on Railsなどの主要なフレームワークもデフォルトでMinitestを採用しており、Rubyのエコシステムで広く使われています。(RSpecという、よりDSL(ドメイン固有言語)ライクに記述できる人気のフレームワークもありますが、まずは標準のMinitestを理解することが基本となります。) + +Minitestは、`Minitest::Test` を継承する「Unitスタイル」と、`describe` ブロックを使う「Specスタイル」の2種類の書き方を提供しますが、この章では最も基本的なUnitスタイルを学びます。 diff --git a/public/docs/ruby/10-testing/2-0-test-basic.md b/public/docs/ruby/10-testing/2-0-test-basic.md new file mode 100644 index 0000000..642a8e9 --- /dev/null +++ b/public/docs/ruby/10-testing/2-0-test-basic.md @@ -0,0 +1,9 @@ +--- +id: ruby-testing-test-basic +title: テストファイルの作成と実行 +level: 2 +--- + +## テストファイルの作成と実行 + +早速、簡単なクラスをテストしてみましょう。 diff --git a/public/docs/ruby/10-testing/2-1-test-class.md b/public/docs/ruby/10-testing/2-1-test-class.md new file mode 100644 index 0000000..1fe26b7 --- /dev/null +++ b/public/docs/ruby/10-testing/2-1-test-class.md @@ -0,0 +1,22 @@ +--- +id: ruby-testing-test-class +title: 1. テスト対象のクラスの作成 +level: 3 +--- + +### 1\. テスト対象のクラスの作成 + +まず、テスト対象となる簡単な電卓クラスを作成します。 + +```ruby:calculator.rb +# シンプルな電卓クラス +class Calculator + def add(a, b) + a + b + end + + def subtract(a, b) + a - b + end +end +``` diff --git a/public/docs/ruby/10-testing/2-2-test-file.md b/public/docs/ruby/10-testing/2-2-test-file.md new file mode 100644 index 0000000..991af08 --- /dev/null +++ b/public/docs/ruby/10-testing/2-2-test-file.md @@ -0,0 +1,48 @@ +--- +id: ruby-testing-test-file +title: 2. テストファイルの作成 +level: 3 +--- + +### 2\. テストファイルの作成 + +Rubyの規約では、テストファイルは `test_` プレフィックス(例: `test_calculator.rb`)または `_test.rb` サフィックス(例: `calculator_test.rb`)で作成するのが一般的です。ここでは `test_calculator.rb` を作成します。 + +テストファイルは、以下の要素で構成されます。 + +1. `require 'minitest/autorun'` + * Minitestライブラリを読み込み、ファイル実行時にテストが自動で走るようにします。 +2. `require_relative 'ファイル名'` + * テスト対象のファイル(今回は `calculator.rb`)を読み込みます。 +3. `class クラス名 < Minitest::Test` + * テストクラスを定義し、`Minitest::Test` を継承します。 +4. `def test_メソッド名` + * `test_` で始まるメソッドを定義します。これが個々のテストケースとなります。 + +```ruby:test_calculator.rb +require 'minitest/autorun' +require_relative 'calculator' + +class CalculatorTest < Minitest::Test + # `test_` で始まるメソッドがテストとして実行される + def test_addition + # テスト対象のインスタンスを作成 + calc = Calculator.new + + # 期待値 (Expected) + expected = 5 + # 実際の結果 (Actual) + actual = calc.add(2, 3) + + # アサーション(後述) + # 期待値と実際の結果が等しいことを検証する + assert_equal(expected, actual) + end + + def test_subtraction + calc = Calculator.new + # アサーションは1行で書くことが多い + assert_equal(1, calc.subtract(4, 3)) + end +end +``` diff --git a/public/docs/ruby/10-testing/2-3-run.md b/public/docs/ruby/10-testing/2-3-run.md new file mode 100644 index 0000000..f02af47 --- /dev/null +++ b/public/docs/ruby/10-testing/2-3-run.md @@ -0,0 +1,29 @@ +--- +id: ruby-testing-run +title: 3. テストの実行 +level: 3 +--- + +### 3\. テストの実行 + +ターミナルで、作成した**テストファイル**を実行します。 + +```ruby-exec:test_calculator.rb +Run options: --seed 51740 + +# Running: + +.. + +Finished in 0.001099s, 1819.8362 runs/s, 1819.8362 assertions/s. + +2 runs, 2 assertions, 0 failures, 0 errors, 0 skips +``` + +実行結果のサマリに注目してください。 + + * `.`(ドット): テストが成功(Pass)したことを示します。 + * `2 runs, 2 assertions`: 2つのテスト(`test_addition` と `test_subtraction`)が実行され、合計2回のアサーション(`assert_equal`)が成功したことを意味します。 + * `0 failures, 0 errors`: 失敗もエラーもありません。 + +もしテストが失敗すると、`F`(Failure)や `E`(Error)が表示され、詳細なレポートが出力されます。 diff --git a/public/docs/ruby/10-testing/3-0-assertion.md b/public/docs/ruby/10-testing/3-0-assertion.md new file mode 100644 index 0000000..e8ea911 --- /dev/null +++ b/public/docs/ruby/10-testing/3-0-assertion.md @@ -0,0 +1,9 @@ +--- +id: ruby-testing-assertion +title: アサーション(assert_equal, assert 等)の書き方 +level: 2 +--- + +## アサーション(assert\_equal, assert 等)の書き方 + +アサーション(Assertion = 表明、断言)は、「この値はこうあるべきだ」と検証するためのメソッドです。Minitestは `Minitest::Test` を継承したクラス内で、様々なアサーションメソッドを提供します。 diff --git a/public/docs/ruby/10-testing/3-1-assert_equal.md b/public/docs/ruby/10-testing/3-1-assert_equal.md new file mode 100644 index 0000000..4e924ef --- /dev/null +++ b/public/docs/ruby/10-testing/3-1-assert_equal.md @@ -0,0 +1,28 @@ +--- +id: ruby-testing-assert_equal +title: assert_equal(expected, actual) +level: 3 +--- + +### `assert_equal(expected, actual)` + +最もよく使うアサーションです。「期待値(expected)」と「実際の結果(actual)」が `==` で等しいことを検証します。 + +> **⚠️ 注意:** 引数の順序が重要です。\*\*1番目が「期待値」、2番目が「実際の結果」\*\*です。逆にすると、失敗時のメッセージが非常に分かりにくくなります。 + +```ruby-repl +irb> require 'minitest/assertions' +=> true +irb> include Minitest::Assertions +=> Object +irb> def assert_equal(expected, actual); super; end # irbで使うための設定 +=> :assert_equal + +irb> assert_equal 5, 2 + 3 +=> true + +irb> assert_equal 10, 2 + 3 +# Minitest::Assertion: <--- 失敗(Assertion Failed) +# Expected: 10 +# Actual: 5 +``` diff --git a/public/docs/ruby/10-testing/3-2-assert.md b/public/docs/ruby/10-testing/3-2-assert.md new file mode 100644 index 0000000..f997b0c --- /dev/null +++ b/public/docs/ruby/10-testing/3-2-assert.md @@ -0,0 +1,16 @@ +--- +id: ruby-testing-assert +title: assert(test) +level: 3 +--- + +### `assert(test)` + +`test` が **true**(またはtrueと評価される値)であることを検証します。偽(`false` または `nil`)の場合は失敗します。 + +```ruby-repl +irb> assert "hello".include?("e") +=> true +irb> assert [1, 2, 3].empty? +# Minitest::Assertion: Expected [] to be empty?. +``` diff --git a/public/docs/ruby/10-testing/3-3-refute.md b/public/docs/ruby/10-testing/3-3-refute.md new file mode 100644 index 0000000..daa5ef0 --- /dev/null +++ b/public/docs/ruby/10-testing/3-3-refute.md @@ -0,0 +1,16 @@ +--- +id: ruby-testing-refute +title: refute(test) +level: 3 +--- + +### `refute(test)` + +`assert` の逆です。`test` が **false** または `nil` であることを検証します。 + +```ruby-repl +irb> refute [1, 2, 3].empty? +=> true +irb> refute "hello".include?("e") +# Minitest::Assertion: Expected "hello".include?("e") to be falsy. +``` diff --git a/public/docs/ruby/10-testing/3-4-assert_nil.md b/public/docs/ruby/10-testing/3-4-assert_nil.md new file mode 100644 index 0000000..642469b --- /dev/null +++ b/public/docs/ruby/10-testing/3-4-assert_nil.md @@ -0,0 +1,16 @@ +--- +id: ruby-testing-assert_nil +title: assert_nil(obj) +level: 3 +--- + +### `assert_nil(obj)` + +`obj` が `nil` であることを検証します。 + +```ruby-repl +irb> a = nil +=> nil +irb> assert_nil a +=> true +``` diff --git a/public/docs/ruby/10-testing/3-5-assert_raises.md b/public/docs/ruby/10-testing/3-5-assert_raises.md new file mode 100644 index 0000000..a6d869c --- /dev/null +++ b/public/docs/ruby/10-testing/3-5-assert_raises.md @@ -0,0 +1,45 @@ +--- +id: ruby-testing-assert_raises +title: 'assert_raises(Exception) { ... }' +level: 3 +--- + +### `assert_raises(Exception) { ... }` + +ブロック `{ ... }` を実行した結果、指定した例外(`Exception`)が発生することを検証します。 + +これは、意図したエラー処理が正しく動作するかをテストするのに非常に重要です。 + +```ruby:test_calculator_errors.rb +require 'minitest/autorun' + +class Calculator + def divide(a, b) + raise ZeroDivisionError, "Cannot divide by zero" if b == 0 + a / b + end +end + +class CalculatorErrorTest < Minitest::Test + def test_division_by_zero + calc = Calculator.new + + # ブロック内で ZeroDivisionError が発生することを期待する + assert_raises(ZeroDivisionError) do + calc.divide(10, 0) + end + end +end +``` + +```ruby-exec:test_calculator_errors.rb +Run options: --seed 19800 + +# Running: + +. + +Finished in 0.000624s, 1602.5641 runs/s, 1602.5641 assertions/s. + +1 runs, 1 assertions, 0 failures, 0 errors, 0 skips +``` diff --git a/public/docs/ruby/10-testing/4-0-tdd.md b/public/docs/ruby/10-testing/4-0-tdd.md new file mode 100644 index 0000000..c496ee0 --- /dev/null +++ b/public/docs/ruby/10-testing/4-0-tdd.md @@ -0,0 +1,20 @@ +--- +id: ruby-testing-tdd +title: 簡単なTDD(テスト駆動開発)の体験 +level: 2 +--- + +## 簡単なTDD(テスト駆動開発)の体験 + +TDD (Test-Driven Development) は、機能のコードを書く前に、**まず失敗するテストコードを書く**開発手法です。TDDは以下の短いサイクルを繰り返します。 + +1. **Red (レッド):** + * これから実装したい機能に対する「失敗するテスト」を書きます。 + * まだ機能が存在しないため、テストは(当然)失敗(Red)します。 +2. **Green (グリーン):** + * そのテストをパスさせるための**最小限の**機能コードを実装します。 + * テストが成功(Green)すればOKです。コードの綺麗さはまだ問いません。 +3. **Refactor (リファクタリング):** + * テストが成功した状態を維持したまま、コードの重複をなくしたり、可読性を上げたりする「リファクタリング」を行います。 + +`Calculator` クラスに、`multiply`(掛け算)メソッドをTDDで追加してみましょう。 diff --git a/public/docs/ruby/10-testing/4-1-tdd-red.md b/public/docs/ruby/10-testing/4-1-tdd-red.md new file mode 100644 index 0000000..07fdc52 --- /dev/null +++ b/public/docs/ruby/10-testing/4-1-tdd-red.md @@ -0,0 +1,63 @@ +--- +id: ruby-testing-tdd-red +title: '1. Red: 失敗するテストを書く' +level: 3 +--- + +### 1\. Red: 失敗するテストを書く + +まず、`test_calculator.rb` に `multiply` のテストを追加します。 + +```ruby:calculator.rb +# シンプルな電卓クラス +class Calculator + def add(a, b) + a + b + end + + def subtract(a, b) + a - b + end +end +``` + +```ruby:test_calculator_tdd.rb +require 'minitest/autorun' +require_relative 'calculator' # calculator.rb は add と subtract のみ + +class CalculatorTest < Minitest::Test + def setup + # @calc をインスタンス変数にすると、各テストメソッドで使える + @calc = Calculator.new + end + + def test_addition + assert_equal(5, @calc.add(2, 3)) + end + + def test_subtraction + assert_equal(1, @calc.subtract(4, 3)) + end + + # --- TDDサイクル スタート --- + + # 1. Red: まずテストを書く + def test_multiplication + assert_equal(12, @calc.multiply(3, 4)) + end +end +``` + +この時点で `calculator.rb` に `multiply` メソッドは存在しません。テストを実行します。 + +```ruby-exec:test_calculator_tdd.rb +# (実行結果の抜粋) +... +Error: +CalculatorTest#test_multiplication: +NoMethodError: undefined method `multiply' for # +... +1 runs, 0 assertions, 0 failures, 1 errors, 0 skips +``` + +期待通り、`NoMethodError` でテストが**エラー (E)** になりました。これが「Red」の状態です。(Failure (F) はアサーションが期待と違った場合、Error (E) はコード実行中に例外が発生した場合を指します) diff --git a/public/docs/ruby/10-testing/4-2-tdd-green.md b/public/docs/ruby/10-testing/4-2-tdd-green.md new file mode 100644 index 0000000..c4a0dc3 --- /dev/null +++ b/public/docs/ruby/10-testing/4-2-tdd-green.md @@ -0,0 +1,35 @@ +--- +id: ruby-testing-tdd-green +title: '2. Green: テストを通す最小限のコードを書く' +level: 3 +--- + +### 2\. Green: テストを通す最小限のコードを書く + +次に、`calculator.rb` に以下のように `multiply` メソッドを実装し、テストをパス(Green)させます。 + +```ruby +class Calculator + def add(a, b) + a + b + end + + def subtract(a, b) + a - b + end + + # 2. Green: テストを通す最小限の実装 + def multiply(a, b) + a * b + end +end +``` + +`calculator.rb` を編集し、再びテストを実行すると、以下のようにすべてのテストが成功します。「Green」の状態です。 + +```bash +$ ruby test_calculator_tdd.rb +... +Finished in ... +3 runs, 3 assertions, 0 failures, 0 errors, 0 skips +``` diff --git a/public/docs/ruby/10-testing/4-3-tdd-refactor.md b/public/docs/ruby/10-testing/4-3-tdd-refactor.md new file mode 100644 index 0000000..4cc0e5f --- /dev/null +++ b/public/docs/ruby/10-testing/4-3-tdd-refactor.md @@ -0,0 +1,11 @@ +--- +id: ruby-testing-tdd-refactor +title: '3. Refactor: リファクタリング' +level: 3 +--- + +### 3\. Refactor: リファクタリング + +今回は実装が非常にシンプルなのでリファクタリングの必要はあまりありませんが、もし `multiply` の実装が複雑になったり、他のメソッドとコードが重複したりした場合は、この「Green」の(テストが成功している)状態で安心してコードをクリーンアップします。 + +TDDは、この「Red -\> Green -\> Refactor」のサイクルを高速で回すことにより、バグの少ない、メンテンスしやすいコードを堅実に構築していく手法です。 diff --git a/public/docs/ruby/10-testing/5-0-summary.md b/public/docs/ruby/10-testing/5-0-summary.md new file mode 100644 index 0000000..7865ef5 --- /dev/null +++ b/public/docs/ruby/10-testing/5-0-summary.md @@ -0,0 +1,15 @@ +--- +id: ruby-testing-summary +title: 📈 この章のまとめ +level: 2 +--- + +## 📈 この章のまとめ + + * Rubyは動的型付け言語であるため、実行時の動作を保証する**テストが非常に重要**です。 + * **Minitest** はRubyに標準添付された軽量なテスティングフレームワークです。 + * テストファイルは `require 'minitest/autorun'` し、`Minitest::Test` を継承します。 + * テストメソッドは `test_` プレフィックスで定義します。 + * `assert_equal(期待値, 実際の結果)` が最も基本的なアサーションです。 + * `assert` (true検証), `refute` (false検証), `assert_raises` (例外検証) などもよく使われます。 + * **TDD (テスト駆動開発)** は「Red (失敗) -\> Green (成功) -\> Refactor (改善)」のサイクルで開発を進める手法です。 diff --git a/public/docs/ruby/10-testing/5-1-practice1.md b/public/docs/ruby/10-testing/5-1-practice1.md new file mode 100644 index 0000000..ccd0771 --- /dev/null +++ b/public/docs/ruby/10-testing/5-1-practice1.md @@ -0,0 +1,21 @@ +--- +id: ruby-testing-practice1 +title: '練習問題1: Stringクラスのテスト' +level: 3 +--- + +### 練習問題1: Stringクラスのテスト + +`Minitest::Test` を使って、Rubyの組み込みクラスである `String` の動作をテストする `test_string.rb` を作成してください。以下の2つのテストメソッドを実装してください。 + + * `test_string_length`: `"hello"` の `length` が `5` であることを `assert_equal` で検証してください。 + * `test_string_uppercase`: `"world"` を `upcase` した結果が `"WORLD"` であることを `assert_equal` で検証してください。 + +```ruby:test_string.rb +require 'minitest/autorun' + + +``` + +```ruby-exec:test_string.rb +``` diff --git a/public/docs/ruby/10-testing/5-2-practice2.md b/public/docs/ruby/10-testing/5-2-practice2.md new file mode 100644 index 0000000..80248b7 --- /dev/null +++ b/public/docs/ruby/10-testing/5-2-practice2.md @@ -0,0 +1,25 @@ +--- +id: ruby-testing-practice2 +title: '練習問題2: TDDでUserクラスを実装' +level: 3 +--- + +### 練習問題2: TDDでUserクラスを実装 + +TDDの「Red -\> Green」サイクルを体験してください。 + +1. (Red)`User` クラスに `first_name` と `last_name` を渡してインスタンス化し、`full_name` メソッドを呼ぶと `"First Last"` のように連結された文字列が返ることを期待するテスト `test_full_name` を含む `test_user.rb` を先に作成してください。(この時点では `user.rb` は空か、存在しなくても構いません) +2. (Green)テストがパスするように、`user.rb` に `User` クラスを実装してください。(`initialize` で名前を受け取り、`full_name` メソッドで連結します) + + +```ruby:user.rb +``` + +```ruby:test_user.rb +require 'minitest/autorun' +require_relative 'user' + +``` + +```ruby-exec:test_user.rb +``` diff --git a/public/docs/ruby/11-metaprogramming/-intro.md b/public/docs/ruby/11-metaprogramming/-intro.md new file mode 100644 index 0000000..e685fc8 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/-intro.md @@ -0,0 +1 @@ +Rubyの最も強力であり、同時に最も特徴的な側面の一つが「メタプログラミング」です。これは「コードがコードを書く(あるいは変更する)」能力を指します。他の言語でコンパイル時やリフレクションAPIを通じて行っていた操作の多くを、Rubyでは実行時に直接、かつ柔軟に行うことができます。 diff --git a/public/docs/ruby/11-metaprogramming/1-0-dynamic.md b/public/docs/ruby/11-metaprogramming/1-0-dynamic.md new file mode 100644 index 0000000..3c94535 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/1-0-dynamic.md @@ -0,0 +1,14 @@ +--- +id: ruby-metaprogramming-dynamic +title: Rubyの動的な性質 +level: 2 +--- + +## Rubyの動的な性質 + +Rubyは非常に動的な言語です。クラスは実行中に変更可能であり、メソッドの追加や削除、上書きがいつでも行えます。この章では、その動的な性質を利用したメタプログラミングの基本的な手法を学びます。 + + * **オープンクラス**: Rubyでは、既存のクラス(組み込みクラスさえも)を後から「開いて」メソッドを追加・変更できます。 + * **実行時**: 多くの決定がコンパイル時ではなく実行時に行われます。 + +これらの性質が、DRY (Don't Repeat Yourself) の原則を追求し、柔軟なDSL(ドメイン固有言語)を構築するための基盤となります。 diff --git a/public/docs/ruby/11-metaprogramming/2-0-send.md b/public/docs/ruby/11-metaprogramming/2-0-send.md new file mode 100644 index 0000000..5578c3e --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/2-0-send.md @@ -0,0 +1,26 @@ +--- +id: ruby-metaprogramming-send +title: 'send: メソッドを動的に呼び出す' +level: 2 +--- + +## `send`: メソッドを動的に呼び出す + +通常、メソッドは `object.method_name` のようにドット(`.`)を使って呼び出します。しかし、呼び出したいメソッド名が実行時までわからない場合、`send` メソッド(または `public_send`)が役立ちます。 + +`send` は、第1引数にメソッド名を**シンボル**(`:`)または**文字列**で受け取り、残りの引数をそのメソッドに渡して実行します。 + +```ruby-repl +irb(main):001> "hello".send(:upcase) +=> "HELLO" +irb(main):002> "hello".send("length") +=> 5 +irb(main):003> 10.send(:+, 5) # 演算子も内部的にはメソッドです +=> 15 +irb(main):004> +irb(main):004> method_to_call = :reverse +irb(main):005> "Ruby".send(method_to_call) +=> "ybuR" +``` + +> **注意**: `send` は `private` メソッドも呼び出すことができます。意図せず `private` メソッドを呼び出さないように、通常は `public_send` を使う方が安全です。 diff --git a/public/docs/ruby/11-metaprogramming/3-0-define-method.md b/public/docs/ruby/11-metaprogramming/3-0-define-method.md new file mode 100644 index 0000000..88e7413 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/3-0-define-method.md @@ -0,0 +1,44 @@ +--- +id: ruby-metaprogramming-define-method +title: 'define_method: メソッドを動的に定義する' +level: 2 +--- + +## `define_method`: メソッドを動的に定義する + +メソッドを動的に(実行時に)定義したい場合、`define_method` を使用します。これは主にクラスやモジュールの定義内で使われます。 + +`define_method` は、第1引数に定義したいメソッド名(シンボル)を、第2引数にブロック(ProcやLambda)を取ります。このブロックが、新しく定義されるメソッドの本体となります。 + +例えば、似たようなメソッドを多数定義する必要がある場合に非常に便利です。 + +```ruby:dynamic_greeter.rb +class DynamicGreeter + # 定義したい挨拶のリスト + GREETINGS = { + hello: "Hello", + goodbye: "Goodbye", + hi: "Hi" + } + + GREETINGS.each do |name, prefix| + # define_methodを使ってメソッドを動的に定義する + define_method(name) do |target| + puts "#{prefix}, #{target}!" + end + end +end + +greeter = DynamicGreeter.new + +# 動的に定義されたメソッドを呼び出す +greeter.hello("World") +greeter.goodbye("Alice") +greeter.hi("Bob") +``` + +```ruby-exec:dynamic_greeter.rb +Hello, World! +Goodbye, Alice! +Hi, Bob! +``` diff --git a/public/docs/ruby/11-metaprogramming/4-0-method-missing.md b/public/docs/ruby/11-metaprogramming/4-0-method-missing.md new file mode 100644 index 0000000..168e5eb --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/4-0-method-missing.md @@ -0,0 +1,63 @@ +--- +id: ruby-metaprogramming-method-missing +title: 'method_missing: 存在しないメソッドへの応答' +level: 2 +--- + +## `method_missing`: 存在しないメソッドへの応答 + +オブジェクトに対して定義されていないメソッドが呼び出されると、Rubyは例外(`NoMethodError`)を発生させる前に、`method_missing` という特別なメソッドを呼び出そうと試みます。 + +この `method_missing` を自分でオーバーライドすることで、定義されていないメソッド呼び出しを「キャッチ」し、動的に処理できます。 + +`method_missing` は以下の引数を受け取ります。 + +1. 呼び出されようとしたメソッド名(シンボル) +2. そのメソッドに渡された引数(配列) +3. そのメソッドに渡されたブロック(存在する場合) + +```ruby:ghost_methods.rb +class DynamicLogger + def method_missing(method_name, *args, &block) + # 呼び出されたメソッド名が 'log_' で始まるかチェック + if method_name.to_s.start_with?("log_") + # 'log_' の部分を取り除いてレベル名とする + level = method_name.to_s.delete_prefix("log_") + + # メッセージ(引数)を取得 + message = args.first || "(no message)" + + puts "[#{level.upcase}] #{message}" + else + # 関係ないメソッド呼び出しは、通常通り NoMethodError を発生させる + super + end + end + + # respond_to? が正しく動作するように、respond_to_missing? も定義するのがベストプラクティス + def respond_to_missing?(method_name, include_private = false) + method_name.to_s.start_with?("log_") || super + end +end + +logger = DynamicLogger.new + +logger.log_info("Application started.") +logger.log_warning("Cache is empty.") +logger.log_error("File not found.") + +# respond_to? の動作確認 +puts "Responds to log_info? #{logger.respond_to?(:log_info)}" +puts "Responds to undefined_method? #{logger.respond_to?(:undefined_method)}" + +# 存在しないメソッド(super呼び出し) +# logger.undefined_method # => NoMethodError +``` + +```ruby-exec:ghost_methods.rb +[INFO] Application started. +[WARNING] Cache is empty. +[ERROR] File not found. +Responds to log_info? true +Responds to undefined_method? false +``` diff --git a/public/docs/ruby/11-metaprogramming/5-0-rails-example.md b/public/docs/ruby/11-metaprogramming/5-0-rails-example.md new file mode 100644 index 0000000..4918166 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/5-0-rails-example.md @@ -0,0 +1,16 @@ +--- +id: ruby-metaprogramming-rails-example +title: Railsなどでの活用例 +level: 2 +--- + +## Railsなどでの活用例 + +Rubyのメタプログラミングは、Ruby on Railsのようなフレームワークで広く活用されています。これにより、開発者は定型的なコード(ボイラープレート)を大量に書く必要がなくなり、宣言的な記述が可能になります。 + + * **Active Record (ORM)**: + * `method_missing` の典型的な例です。`User.find_by_email("test@example.com")` のようなメソッドは、`User` クラスに明示的に定義されていません。Active Recordは `method_missing` を使って `find_by_` プレフィックスを検出し、`email` カラムで検索するSQLを動的に生成します。 + * **関連付け (Associations)**: + * `has_many :posts` や `belongs_to :user` といった記述。これらは単なる宣言に見えますが、内部では `define_method` を使い、`user.posts` や `post.user` といった便利なメソッドを実行時に定義しています。 + +このように、メタプログラミングはRubyエコシステムの「魔法」の多くを支える技術であり、フレームワークの内部を理解する上で不可欠です。 diff --git a/public/docs/ruby/11-metaprogramming/6-0-summary.md b/public/docs/ruby/11-metaprogramming/6-0-summary.md new file mode 100644 index 0000000..d0810cb --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/6-0-summary.md @@ -0,0 +1,13 @@ +--- +id: ruby-metaprogramming-summary +title: ⚡ この章のまとめ +level: 2 +--- + +## ⚡ この章のまとめ + + * **メタプログラミング**とは、コードが実行時に自身の構造(クラスやメソッド)を操作する技術です。 + * `send`(または `public_send`)は、メソッド名を文字列やシンボルで指定し、動的にメソッドを呼び出します。 + * `define_method` は、実行時にメソッドを動的に定義します。DRYを保つのに役立ちます。 + * `method_missing` は、定義されていないメソッド呼び出しを捕捉し、柔軟なインターフェース(DSL)を構築するために使われます。 + * メタプログラミングは非常に強力ですが、コードの可読性やデバッグの難易度を上げる可能性もあります。使い所を理解し、**乱用は避ける**ことが重要です。 diff --git a/public/docs/ruby/11-metaprogramming/6-1-practice1.md b/public/docs/ruby/11-metaprogramming/6-1-practice1.md new file mode 100644 index 0000000..1ad9014 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/6-1-practice1.md @@ -0,0 +1,36 @@ +--- +id: ruby-metaprogramming-practice1 +title: '練習問題1: 動的アクセサ' +level: 3 +--- + +### 練習問題1: 動的アクセサ + +`define_method` を使って、指定された属性名の配列からゲッター(`attr_reader`)とセッター(`attr_writer`)を動的に定義するメソッド `my_attr_accessor` を持つモジュールを作成してください。(ヒント: インスタンス変数 `@name` を読み書きするメソッドを定義します) + +```ruby:practice12_1.rb +module DynamicAccessor + def my_attr_accessor(*attrs) + attrs.each do |attr| + # ゲッターとセッターを動的に定義するコードをここに書く + + + end + end +end + +class Person + extend DynamicAccessor + + my_attr_accessor :name, :age +end +person = Person.new +person.name = "Alice" +person.age = 30 + +puts "Name: #{person.name}, Age: #{person.age}" +``` + +```ruby-exec:practice12_1.rb +Name: Alice, Age: 30 +``` diff --git a/public/docs/ruby/11-metaprogramming/6-2-practice2.md b/public/docs/ruby/11-metaprogramming/6-2-practice2.md new file mode 100644 index 0000000..83b10c9 --- /dev/null +++ b/public/docs/ruby/11-metaprogramming/6-2-practice2.md @@ -0,0 +1,37 @@ +--- +id: ruby-metaprogramming-practice2 +title: '練習問題2: シンプルな設定オブジェクト' +level: 3 +--- + +### 練習問題2: シンプルな設定オブジェクト + +`method_missing` を使って、ハッシュのように動作する `SimpleConfig` クラスを作成してください。`config.api_key = "12345"` のように値を設定でき、`config.api_key` で値を取得できるようにしてください。設定されていないキーを呼び出した場合は `nil` を返すようにします。 + +```ruby:practice12_2.rb +class SimpleConfig + def initialize + @settings = {} + end + + def method_missing(method_name, *args, &block) + # ここにコードを書いてください + + + end + + def respond_to_missing?(method_name, include_private = false) + true + end +end + +config = SimpleConfig.new +config.api_key = "12345" +puts "API Key: #{config.api_key.inspect}" +puts "Timeout: #{config.timeout.inspect}" # 設定されていないキー +``` + +```ruby-exec:practice12_2.rb +API Key: "12345" +Timeout: nil +``` diff --git a/public/docs/ruby/2-control-methods/-intro.md b/public/docs/ruby/2-control-methods/-intro.md new file mode 100644 index 0000000..a0f17f4 --- /dev/null +++ b/public/docs/ruby/2-control-methods/-intro.md @@ -0,0 +1 @@ +Rubyの制御構造は、他の多くの言語と似ていますが、Rubyの「すべてが式である」という哲学と、読みやすさを重視した構文(`unless`など)に特徴があります。また、メソッド(関数)の定義は非常に柔軟で、強力な引数の扱いや例外処理の仕組みを備えています。 diff --git a/public/docs/ruby/2-control-methods/1-0-conditional.md b/public/docs/ruby/2-control-methods/1-0-conditional.md new file mode 100644 index 0000000..a1b82c8 --- /dev/null +++ b/public/docs/ruby/2-control-methods/1-0-conditional.md @@ -0,0 +1,9 @@ +--- +id: ruby-control-conditional +title: 条件分岐 +level: 2 +--- + +## 条件分岐 + +Rubyの条件分岐は、`if`、`unless`、`case`が基本です。`if`や`case`は文(Statement)ではなく**式(Expression)**であるため、それ自体が値を返します。 diff --git a/public/docs/ruby/2-control-methods/1-1-if.md b/public/docs/ruby/2-control-methods/1-1-if.md new file mode 100644 index 0000000..79b981e --- /dev/null +++ b/public/docs/ruby/2-control-methods/1-1-if.md @@ -0,0 +1,27 @@ +--- +id: ruby-control-if +title: if, else, elsif +level: 3 +--- + +### `if`, `else`, `elsif` + +基本的な構文は他言語と同様ですが、`else if`は `elsif`(`e`が1つ)と綴る点に注意してください。 + +`if`は値を返すため、結果を変数に代入できます。 + +```ruby-repl +irb(main):001:0> score = 85 +=> 85 +irb(main):002:0> grade = if score > 90 +irb(main):003:1* "A" +irb(main):004:1* elsif score > 80 # "else if" ではない +irb(main):005:1* "B" +irb(main):006:1* else +irb(main):007:1* "C" +irb(main):008:1* end +=> "B" +irb(main):009:0> puts "あなたの成績は#{grade}です" +あなたの成績はBです +=> nil +``` diff --git a/public/docs/ruby/2-control-methods/1-2-unless.md b/public/docs/ruby/2-control-methods/1-2-unless.md new file mode 100644 index 0000000..51d9212 --- /dev/null +++ b/public/docs/ruby/2-control-methods/1-2-unless.md @@ -0,0 +1,21 @@ +--- +id: ruby-control-unless +title: unless +level: 3 +--- + +### `unless` + +`unless`は `if !`(もし~でなければ)の糖衣構文(Syntactic Sugar)です。条件が**偽 (false)** の場合にブロックが実行されます。 + +```ruby-repl +irb(main):010:0> logged_in = false +=> false +irb(main):011:0> unless logged_in +irb(main):012:1* puts "ログインしてください" +irb(main):013:1* end +ログインしてください +=> nil +``` + +> **補足:** `unless` に `else` を付けることも可能ですが、多くの場合 `if` を使った方が可読性が高くなります。 diff --git a/public/docs/ruby/2-control-methods/1-3-case.md b/public/docs/ruby/2-control-methods/1-3-case.md new file mode 100644 index 0000000..99b812e --- /dev/null +++ b/public/docs/ruby/2-control-methods/1-3-case.md @@ -0,0 +1,46 @@ +--- +id: ruby-control-case +title: case +level: 3 +--- + +### `case` + +C言語やJavaの `switch` 文に似ていますが、より強力です。`when` 節では、複数の値、範囲(Range)、正規表現、さらにはクラスを指定することもできます。`break` は不要です。 + +```ruby:case_example.rb +def analyze_input(input) + puts "Input: #{input.inspect}" + result = case input + when 0 + "ゼロ" + when 1..9 + "一桁の数字" + when "admin", "guest" + "特定のユーザー" + when String + "その他の文字列" + when /Error/ + "エラーメッセージ" + else + "不明な型" + end + puts "Result: #{result}" +end + +analyze_input(5) +analyze_input("guest") +analyze_input("Some value") +analyze_input(nil) +``` + +```ruby-exec:case_example.rb +Input: 5 +Result: 一桁の数字 +Input: "guest" +Result: 特定のユーザー +Input: "Some value" +Result: その他の文字列 +Input: nil +Result: 不明な型 +``` diff --git a/public/docs/ruby/2-control-methods/2-0-loop.md b/public/docs/ruby/2-control-methods/2-0-loop.md new file mode 100644 index 0000000..764654c --- /dev/null +++ b/public/docs/ruby/2-control-methods/2-0-loop.md @@ -0,0 +1,9 @@ +--- +id: ruby-control-loop +title: 繰り返し処理 +level: 2 +--- + +## 繰り返し処理 + +Rubyでは、後の章で学ぶイテレータ(`each`など)が繰り返し処理の主流ですが、C言語スタイルの `while` や `until` も利用可能です。 diff --git a/public/docs/ruby/2-control-methods/2-1-while.md b/public/docs/ruby/2-control-methods/2-1-while.md new file mode 100644 index 0000000..5716aef --- /dev/null +++ b/public/docs/ruby/2-control-methods/2-1-while.md @@ -0,0 +1,19 @@ +--- +id: ruby-control-while +title: while +level: 3 +--- + +### `while` + +条件が**真 (true)** の間、ループを続けます。 + +```ruby-repl +irb(main):001:0> i = 0 +=> 0 +irb(main):002:0> while i < 3 +irb(main):003:1* print i, " " # printは改行しません +irb(main):004:1* i += 1 # Rubyに i++ はありません +irb(main):005:1* end +0 1 2 => nil +``` diff --git a/public/docs/ruby/2-control-methods/2-2-until.md b/public/docs/ruby/2-control-methods/2-2-until.md new file mode 100644 index 0000000..590bfb7 --- /dev/null +++ b/public/docs/ruby/2-control-methods/2-2-until.md @@ -0,0 +1,19 @@ +--- +id: ruby-control-methods-until +title: until +level: 3 +--- + +### `until` + +`while !` と同じです。条件が**偽 (false)** の間、ループを続けます。 + +```ruby-repl +irb(main):006:0> counter = 5 +=> 5 +irb(main):007:0> until counter == 0 +irb(main):008:1* print counter, " " +irb(main):009:1* counter -= 1 +irb(main):010:1* end +5 4 3 2 1 => nil +``` diff --git a/public/docs/ruby/2-control-methods/3-0-def.md b/public/docs/ruby/2-control-methods/3-0-def.md new file mode 100644 index 0000000..58f7de8 --- /dev/null +++ b/public/docs/ruby/2-control-methods/3-0-def.md @@ -0,0 +1,38 @@ +--- +id: ruby-methods-def +title: メソッドの定義 (def) +level: 2 +--- + +## メソッドの定義 (`def`) + +Rubyでは、`def` キーワードを使ってメソッドを定義します。 + +Rubyのメソッドは、**最後に評価された式の結果**を暗黙的に返します。`return` キーワードは、メソッドの途中で明示的に値を返したい場合(早期リターン)に使いますが、必須ではありません。 + +```ruby:method_return.rb +# 最後に評価された a + b が自動的に戻り値となる +def add(a, b) + a + b +end + +# 早期リターンで return を使う例 +def check_value(val) + if val < 0 + return "Negative" # ここで処理が中断 + end + + # val >= 0 の場合は、この式が評価され、戻り値となる + "Positive or Zero" +end + +puts add(10, 5) +puts check_value(-10) +puts check_value(10) +``` + +```ruby-exec:method_return.rb +15 +Negative +Positive or Zero +``` diff --git a/public/docs/ruby/2-control-methods/4-1-default-arg.md b/public/docs/ruby/2-control-methods/4-1-default-arg.md new file mode 100644 index 0000000..45c4c3d --- /dev/null +++ b/public/docs/ruby/2-control-methods/4-1-default-arg.md @@ -0,0 +1,20 @@ +--- +id: ruby-methods-default-arg +title: デフォルト引数 +level: 3 +--- + +### デフォルト引数 + +引数にデフォルト値を設定できます。 + +```ruby-repl +irb(main):001:0> def greet(name = "Guest") +irb(main):002:1* "Hello, #{name}!" +irb(main):003:1* end +=> :greet +irb(main):004:0> greet("Alice") +=> "Hello, Alice!" +irb(main):005:0> greet +=> "Hello, Guest!" +``` diff --git a/public/docs/ruby/2-control-methods/4-2-keyword-arg.md b/public/docs/ruby/2-control-methods/4-2-keyword-arg.md new file mode 100644 index 0000000..75773dd --- /dev/null +++ b/public/docs/ruby/2-control-methods/4-2-keyword-arg.md @@ -0,0 +1,38 @@ +--- +id: ruby-methods-keyword-arg +title: キーワード引数 +level: 3 +--- + +### キーワード引数 + +Pythonのように、引数名を指定して値を渡すことができます。`:`(コロン)を末尾に付けます。キーワード引数は可読性を大幅に向上させます。 + +```ruby:keyword_arguments.rb +# name: は必須のキーワード引数 +# age: はデフォルト値を持つキーワード引数 +def register_user(name:, age: nil, admin: false) + puts "User: #{name}" + puts "Age: #{age}" if age + puts "Admin: #{admin}" +end + +# 順序を問わない +register_user(admin: true, name: "Taro") + +puts "---" + +# 必須の name を省略すると ArgumentError になる +begin + register_user(age: 30) +rescue ArgumentError => e + puts e.message +end +``` + +```ruby-exec:keyword_arguments.rb +User: Taro +Admin: true +--- +missing keyword: :name +``` diff --git a/public/docs/ruby/2-control-methods/4-3-splat-arg.md b/public/docs/ruby/2-control-methods/4-3-splat-arg.md new file mode 100644 index 0000000..6a77af8 --- /dev/null +++ b/public/docs/ruby/2-control-methods/4-3-splat-arg.md @@ -0,0 +1,29 @@ +--- +id: ruby-methods-splat-arg +title: 可変長引数 (Splat演算子) +level: 3 +--- + +### 可変長引数 (Splat演算子) + +引数の先頭に `*`(Splat演算子)を付けると、任意の数の引数を配列として受け取ることができます。 + +```ruby-repl +irb(main):006:0> def summarize(*items) +irb(main):007:1* puts "Items count: #{items.length}" +irb(main):008:1* puts "Items: #{items.join(', ')}" +irb(main):009:1* end +=> :summarize +irb(main):010:0> summarize("Apple", "Banana", "Orange") +Items count: 3 +Items: Apple, Banana, Orange +=> nil +irb(main):011:0> summarize("Book") +Items count: 1 +Items: Book +=> nil +irb(main):012:0> summarize +Items count: 0 +Items: +=> nil +``` diff --git a/public/docs/ruby/2-control-methods/5-0-exception.md b/public/docs/ruby/2-control-methods/5-0-exception.md new file mode 100644 index 0000000..631d8ee --- /dev/null +++ b/public/docs/ruby/2-control-methods/5-0-exception.md @@ -0,0 +1,58 @@ +--- +id: ruby-exception +title: 例外処理 +level: 2 +--- + +## 例外処理 + +JavaやPythonの `try-catch-finally` に相当する構文として、Rubyは `begin-rescue-ensure` を提供します。 + + * `begin`: 例外が発生する可能性のある処理を囲みます。 + * `rescue`: 例外を捕捉(catch)します。捕捉する例外クラスを指定できます。 + * `else`: (Optional) `begin` ブロックで例外が発生しなかった場合に実行されます。 + * `ensure`: (Optional) 例外の有無にかかわらず、最後に必ず実行されます(finally)。 + +```ruby:exception_example.rb +def safe_divide(a, b) + begin + # メインの処理 + result = a / b + rescue ZeroDivisionError => e + # ゼロ除算エラーを捕捉 + puts "Error: ゼロで割ることはできません。" + puts "(#{e.class}: #{e.message})" + result = nil + rescue TypeError => e + # 型エラーを捕捉 + puts "Error: 数値以外が使われました。" + puts "(#{e.class}: #{e.message})" + result = nil + else + # 例外が発生しなかった場合 + puts "計算成功: #{result}" + ensure + # 常に実行 + puts "--- 処理終了 ---" + end + + return result +end + +safe_divide(10, 2) +safe_divide(10, 0) +safe_divide(10, "a") +``` + +```ruby-exec:exception_example.rb +計算成功: 5 +--- 処理終了 --- +Error: ゼロで割ることはできません。 +(ZeroDivisionError: divided by 0) +--- 処理終了 --- +Error: 数値以外が使われました。 +(TypeError: String can't be coerced into Integer) +--- 処理終了 --- +``` + +> **補足:** `def` ... `end` のメソッド定義内では、`begin` と `end` は省略可能です。 diff --git a/public/docs/ruby/2-control-methods/5-2-raise.md b/public/docs/ruby/2-control-methods/5-2-raise.md new file mode 100644 index 0000000..03904d0 --- /dev/null +++ b/public/docs/ruby/2-control-methods/5-2-raise.md @@ -0,0 +1,28 @@ +--- +id: ruby-exception-raise +title: raise (例外の発生) +level: 3 +--- + +### `raise` (例外の発生) + +`raise` を使って、意図的に例外を発生(throw)させることができます。 + +```ruby-repl +irb(main):001:0> def check_age(age) +irb(main):002:1* if age < 0 +irb(main):003:2* # raise "エラーメッセージ" +irb(main):004:2* # raise 例外クラス, "エラーメッセージ" +irb(main):005:2* raise ArgumentError, "年齢は負の値にできません" +irb(main):006:2* end +irb(main):007:1* puts "年齢は #{age} 歳です" +irb(main):008:1* end +=> :check_age +irb(main):009:0> check_age(20) +年齢は 20 歳です +=> nil +irb(main):010:0> check_age(-5) +(irb):5:in `check_age': 年齢は負の値にできません (ArgumentError) + from (irb):10:in `
      ' + ... +``` diff --git a/public/docs/ruby/2-control-methods/6-0-summary.md b/public/docs/ruby/2-control-methods/6-0-summary.md new file mode 100644 index 0000000..806aa4c --- /dev/null +++ b/public/docs/ruby/2-control-methods/6-0-summary.md @@ -0,0 +1,13 @@ +--- +id: ruby-control-methods-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * Rubyの制御構造(`if`, `case`)は**式**であり、値を返します。 + * `if !` の代わりに `unless` を、`while !` の代わりに `until` を使うことで、否定条件を読みやすく記述できます。 + * メソッドの戻り値は、`return` を使わずとも**最後に評価された式**が自動的に返されます。 + * メソッドの引数は、**デフォルト引数**、**キーワード引数** (`name:`), **可変長引数** (`*args`) を駆使することで、非常に柔軟に定義できます。 + * 例外処理は `begin`, `rescue` (catch), `ensure` (finally) で行い、`raise` で意図的に例外を発生させます。 diff --git a/public/docs/ruby/2-control-methods/6-1-practice1.md b/public/docs/ruby/2-control-methods/6-1-practice1.md new file mode 100644 index 0000000..2108093 --- /dev/null +++ b/public/docs/ruby/2-control-methods/6-1-practice1.md @@ -0,0 +1,23 @@ +--- +id: ruby-control-methods-practice1 +title: '練習問題1: 評価メソッドの作成' +level: 3 +--- + +### 練習問題1: 評価メソッドの作成 + +生徒の点数(0〜100)を受け取り、以下の基準で評価(文字列)を返すメソッド `evaluate_score(score)` を作成してください。 + + * 90点以上: "A" + * 70点〜89点: "B" + * 50点〜69点: "C" + * 50点未満: "D" + * 0未満または100を超える場合: `ArgumentError` を `raise` してください。 + +`case` 文と `raise` を使用して実装してください。 + +```ruby:practice3_1.rb +``` + +```ruby-exec:practice3_1.rb +``` diff --git a/public/docs/ruby/2-control-methods/6-2-practice2.md b/public/docs/ruby/2-control-methods/6-2-practice2.md new file mode 100644 index 0000000..e3c7eb0 --- /dev/null +++ b/public/docs/ruby/2-control-methods/6-2-practice2.md @@ -0,0 +1,26 @@ +--- +id: ruby-control-methods-practice2 +title: '練習問題2: 柔軟なログ出力メソッド' +level: 3 +--- + +### 練習問題2: 柔軟なログ出力メソッド + +ログメッセージ(必須)と、オプションとしてログレベル(キーワード引数 `level:`)およびタグ(可変長引数 `tags`)を受け取るメソッド `logger` を作成してください。 + + * メソッドシグネチャ: `def logger(message, level: "INFO", *tags)` + * 実行例: + * `logger("Server started")` + * 出力: `[INFO] Server started` + * `logger("User login failed", level: "WARN", "security", "auth")` + * 出力: `[WARN] (security, auth) User login failed` + * `logger("DB connection lost", level: "ERROR", "database")` + * 出力: `[ERROR] (database) DB connection lost` + +(ヒント: タグの配列 `tags` が空でないかを確認し、`join` メソッドを使って整形してください。) + +```ruby:practice3_2.rb +``` + +```ruby-exec:practice3_2.rb +``` diff --git a/public/docs/ruby/3-everything-object/-intro.md b/public/docs/ruby/3-everything-object/-intro.md new file mode 100644 index 0000000..7aa0944 --- /dev/null +++ b/public/docs/ruby/3-everything-object/-intro.md @@ -0,0 +1,3 @@ +Rubyの設計思想における最も重要かつ強力なコンセプトの一つは、「すべてがオブジェクトである」という点です。他の言語、例えばJavaやC++では、数値(int, double)や真偽値(boolean)は「プリミティブ型」として扱われ、オブジェクトとは区別されます。 + +しかしRubyでは、`5` のような数値も、`"hello"` のような文字列も、そして `nil` さえも、すべてがメソッド(振る舞い)を持つオブジェクトです。 diff --git a/public/docs/ruby/3-everything-object/1-0-literal.md b/public/docs/ruby/3-everything-object/1-0-literal.md new file mode 100644 index 0000000..bbbad5f --- /dev/null +++ b/public/docs/ruby/3-everything-object/1-0-literal.md @@ -0,0 +1,40 @@ +--- +id: ruby-everything-object-literal +title: '🎯 Rubyの核心: 5.times の衝撃' +level: 2 +--- + +## 🎯 Rubyの核心: 5.times の衝撃 + +他の言語の経験者がRubyに触れて最初に驚くことの一つが、以下のようなコードが動作することです。 + +```ruby +5.times do + print "Ruby! " +end +# Ruby! Ruby! Ruby! Ruby! Ruby! が出力される +``` + +`5` という数値リテラルが `.times` というメソッドを呼び出しています。これは、`5` が単なる値ではなく、`Integer` クラスのインスタンス(オブジェクト)だからです。 + +同様に、文字列もオブジェクトです。 + +```ruby +"hello, world".upcase +# => "HELLO, WORLD" +"hello, world".length +# => 12 +``` + +`"hello, world"` という `String` オブジェクトが、`upcase` や `length` というメソッド(メッセージ)に応答しています。 + +`.class` メソッドを使うと、そのオブジェクトがどのクラスに属しているかを確認できます。 + +```ruby-repl +irb(main):001:0> 5.class +=> Integer +irb(main):002:0> "hello".class +=> String +irb(main):003:0> 3.14.class +=> Float +``` diff --git a/public/docs/ruby/3-everything-object/2-0-nil.md b/public/docs/ruby/3-everything-object/2-0-nil.md new file mode 100644 index 0000000..4220fdb --- /dev/null +++ b/public/docs/ruby/3-everything-object/2-0-nil.md @@ -0,0 +1,28 @@ +--- +id: ruby-everything-object-nil +title: '👻 nil オブジェクト: 無ですらオブジェクト' +level: 2 +--- + +## 👻 nil オブジェクト: 無ですらオブジェクト + +Rubyには「何もない」「無効」な状態を示す `nil` という特別な値があります。これは他の言語における `null` や `None` に相当します。 + +しかし、Rubyの哲学を徹底している点は、この `nil` ですらオブジェクトであるということです。 + +`nil` は `NilClass` という専用クラスの唯一のインスタンスです。オブジェクトであるため、`nil` もメソッドを持ちます。 + +```ruby-repl +irb(main):001:0> nil.class +=> NilClass +irb(main):001:0> nil.nil? +=> true +irb(main):002:0> "hello".nil? +=> false +irb(main):003:0> nil.to_s +=> "" +irb(main):004:0> nil.to_i +=> 0 +``` + +`nil` がメソッドを持つことで、`null` チェックに起因するエラー(例えば `null.someMethod()` のような呼び出しによるエラー)を避けやすくなり、より安全で流暢なコードが書ける場合があります。 diff --git a/public/docs/ruby/3-everything-object/3-0-message-passing.md b/public/docs/ruby/3-everything-object/3-0-message-passing.md new file mode 100644 index 0000000..65575f9 --- /dev/null +++ b/public/docs/ruby/3-everything-object/3-0-message-passing.md @@ -0,0 +1,25 @@ +--- +id: ruby-everything-object-message-passing +title: '📨 メソッド呼び出しの仕組み: メッセージパッシング' +level: 2 +--- + +## 📨 メソッド呼び出しの仕組み: メッセージパッシング + +Rubyのメソッド呼び出し `オブジェクト.メソッド名(引数)` は、厳密には「**メッセージパッシング**」という概念に基づいています。 + +`5.times` というコードは、以下のように解釈されます。 + +1. レシーバ(受信者): `5` という `Integer` オブジェクト +2. メッセージ: `:times` というシンボル(メソッド名) +3. `5` オブジェクトに `:times` というメッセージを送る。 +4. `5` オブジェクト(の所属する `Integer` クラス)は、そのメッセージを解釈し、関連付けられた処理(ブロックを5回実行する)を実行する。 + +この考え方は、オブジェクト指向の「カプセル化(オブジェクトが自身の振る舞いを決定する)」を強力にサポートします。`+` などの演算子でさえ、実際にはメソッド呼び出しのシンタックスシュガー(糖衣構文)です。 + +```ruby-repl +irb(main):001:0> 10 + 3 +=> 13 +irb(main):002:0> 10.+(3) # 内部的にはこれと同じ +=> 13 +``` diff --git a/public/docs/ruby/3-everything-object/4-0-basic-method.md b/public/docs/ruby/3-everything-object/4-0-basic-method.md new file mode 100644 index 0000000..6b82e75 --- /dev/null +++ b/public/docs/ruby/3-everything-object/4-0-basic-method.md @@ -0,0 +1,9 @@ +--- +id: ruby-everything-object-basic-method +title: 🛠️ よく使う組み込みクラスのメソッド +level: 2 +--- + +## 🛠️ よく使う組み込みクラスのメソッド + +すべてがオブジェクトであるため、Rubyは基本的なデータ型に対して非常に多くの便利なメソッドを標準で提供しています。 diff --git a/public/docs/ruby/3-everything-object/4-1-string.md b/public/docs/ruby/3-everything-object/4-1-string.md new file mode 100644 index 0000000..7d71c8a --- /dev/null +++ b/public/docs/ruby/3-everything-object/4-1-string.md @@ -0,0 +1,38 @@ +--- +id: ruby-everything-object-string +title: String (文字列) +level: 3 +--- + +### String (文字列) + +`String` クラスには、テキスト操作のための豊富なメソッドが用意されています。 + +```ruby:string_methods.rb +text = " ruby is convenient " + +# 先頭と末尾の空白を除去 +cleaned_text = text.strip +puts "Strip: '#{cleaned_text}'" + +# 先頭の文字を大文字に +puts "Capitalize: #{cleaned_text.capitalize}" + +# "convenient" を "powerful" に置換 +puts "Gsub: #{cleaned_text.gsub("convenient", "powerful")}" + +# "ruby" という文字列で始まっているか? +puts "Start with 'ruby'?: #{cleaned_text.start_with?("ruby")}" + +# 単語に分割 (配列が返る) +words = cleaned_text.split(" ") +p words # p はデバッグ用の表示メソッド +``` + +```ruby-exec:string_methods.rb +Strip: 'ruby is convenient' +Capitalize: Ruby is convenient +Gsub: ruby is powerful +Start with 'ruby'?: true +["ruby", "is", "convenient"] +``` diff --git a/public/docs/ruby/3-everything-object/4-2-integer.md b/public/docs/ruby/3-everything-object/4-2-integer.md new file mode 100644 index 0000000..e6f15e0 --- /dev/null +++ b/public/docs/ruby/3-everything-object/4-2-integer.md @@ -0,0 +1,31 @@ +--- +id: ruby-everything-object-integer +title: Integer / Float (数値) +level: 3 +--- + +### Integer / Float (数値) + +数値クラス (総称して `Numeric`) も便利なメソッドを持っています。 + +```ruby-repl +irb(main):001:0> # Integer +irb(main):002:0> 10.even? +=> true +irb(main):003:0> 10.odd? +=> false +irb(main):004:0> 5.to_s +=> "5" +irb(main):005:0> 5.to_f +=> 5.0 + +irb(main):006:0> # Float +irb(main):007:0> 10.5.round +=> 11 +irb(main):008:0> 10.5.floor # 切り捨て +=> 10 +irb(main):009:0> 10.5.ceil # 切り上げ +=> 11 +irb(main):010:0> (10.5).to_i +=> 10 +``` diff --git a/public/docs/ruby/3-everything-object/5-0-summary.md b/public/docs/ruby/3-everything-object/5-0-summary.md new file mode 100644 index 0000000..02311ac --- /dev/null +++ b/public/docs/ruby/3-everything-object/5-0-summary.md @@ -0,0 +1,13 @@ +--- +id: ruby-everything-object-summary +title: 📜 この章のまとめ +level: 2 +--- + +## 📜 この章のまとめ + + * Rubyでは、数値、文字列、`nil` を含むすべてが **オブジェクト** です。 + * すべてのオブジェクトは **クラス** に属しています(例: `5` は `Integer` クラス)。 + * オブジェクトであるため、すべての値は **メソッド** を持つことができます(例: `5.times`, `"hello".upcase`)。 + * メソッド呼び出しは、オブジェクトへの **メッセージパッシング** として理解されます。 + * `nil` も `NilClass` のオブジェクトであり、メソッドを持ちます。 diff --git a/public/docs/ruby/3-everything-object/5-1-practice1.md b/public/docs/ruby/3-everything-object/5-1-practice1.md new file mode 100644 index 0000000..fedc6ef --- /dev/null +++ b/public/docs/ruby/3-everything-object/5-1-practice1.md @@ -0,0 +1,17 @@ +--- +id: ruby-everything-object-practice1 +title: '練習問題1: 文字列の操作' +level: 3 +--- + +### 練習問題1: 文字列の操作 +変数 `sentence = " Welcome to the Ruby World! "` があります。`String` のメソッドを組み合わせて、最終的に `"WELCOME, RUBY"` という文字列をコンソールに出力してください。 + + * ヒント: `strip`, `upcase`, `gsub` (または `sub`), `slice` (またはインデックスアクセス `[]`) などが使えます。 + +```ruby:practice4_1.rb +sentence = " Welcome to the Ruby World! " +``` + +```ruby-exec:practice4_1.rb +``` diff --git a/public/docs/ruby/3-everything-object/5-2-practice2.md b/public/docs/ruby/3-everything-object/5-2-practice2.md new file mode 100644 index 0000000..572305b --- /dev/null +++ b/public/docs/ruby/3-everything-object/5-2-practice2.md @@ -0,0 +1,16 @@ +--- +id: ruby-everything-object-practice2 +title: '練習問題2: 数値と判定' +level: 3 +--- + +### 練習問題2: 数値と判定 + +`Float` の値 `123.456` があります。この値を四捨五入して整数(`Integer`)にした後、その整数が偶数(even)か奇数(odd)かを判定して、`"Result is even"` または `"Result is odd"` と出力するコードを書いてください。 + +```ruby:practice4_2.rb +value = 123.456 +``` + +```ruby-exec:practice4_2.rb +``` diff --git a/public/docs/ruby/4-collections/-intro.md b/public/docs/ruby/4-collections/-intro.md new file mode 100644 index 0000000..e955929 --- /dev/null +++ b/public/docs/ruby/4-collections/-intro.md @@ -0,0 +1 @@ +Rubyの強力な機能の一つに、柔軟で直感的なコレクション(データを集めて格納するオブジェクト)があります。他の言語でのListやMap、Dictionaryに相当するものを学びましょう。この章では、`Array`(配列)、`Hash`(ハッシュ)、そして `Range`(範囲)を扱います。 diff --git a/public/docs/ruby/4-collections/1-0-array.md b/public/docs/ruby/4-collections/1-0-array.md new file mode 100644 index 0000000..ec33ae8 --- /dev/null +++ b/public/docs/ruby/4-collections/1-0-array.md @@ -0,0 +1,28 @@ +--- +id: ruby-collections-array +title: 配列 (Array) +level: 2 +--- + +## 配列 (Array) + +Rubyの `Array` は、他の言語における動的配列やリストに似ています。順序付けられた要素のコレクションであり、異なるデータ型の要素を混在させることができます。 + +配列は `[]` (角括弧) を使って生成します。 + +要素へのアクセスは `[index]` を使います。Rubyのインデックスは0から始まり、**負のインデックス**(末尾からのアクセス)をサポートしているのが特徴です。 + +```ruby-repl +irb(main):001:0> numbers = [1, 2, 3, 4, 5] +=> [1, 2, 3, 4, 5] +irb(main):002:0> numbers[0] # 最初の要素 +=> 1 +irb(main):003:0> numbers[-1] # 末尾の要素 +=> 5 +irb(main):004:0> numbers[-2] # 末尾から2番目の要素 +=> 4 +irb(main):005:0> mixed = [1, "hello", true, 3.14] # 型の混在が可能 +=> [1, "hello", true, 3.14] +irb(main):006:0> empty_array = [] +=> [] +``` diff --git a/public/docs/ruby/4-collections/1-2-array-push-pop.md b/public/docs/ruby/4-collections/1-2-array-push-pop.md new file mode 100644 index 0000000..14eae9e --- /dev/null +++ b/public/docs/ruby/4-collections/1-2-array-push-pop.md @@ -0,0 +1,22 @@ +--- +id: ruby-collections-array-push-pop +title: 要素の追加と削除 +level: 3 +--- + +### 要素の追加と削除 + +要素の追加には `<<` (shovel演算子) や `push` メソッドを使います。 `pop` は末尾の要素を削除し、それを返します。 + +```ruby-repl +irb(main):007:0> fruits = ["apple", "banana"] +=> ["apple", "banana"] +irb(main):008:0> fruits << "cherry" # << (shovel) は高速で一般的 +=> ["apple", "banana", "cherry"] +irb(main):009:0> fruits.push("orange") +=> ["apple", "banana", "cherry", "orange"] +irb(main):010:0> last_fruit = fruits.pop +=> "orange" +irb(main):011:0> fruits +=> ["apple", "banana", "cherry"] +``` diff --git a/public/docs/ruby/4-collections/1-3-array-method.md b/public/docs/ruby/4-collections/1-3-array-method.md new file mode 100644 index 0000000..4e3c3da --- /dev/null +++ b/public/docs/ruby/4-collections/1-3-array-method.md @@ -0,0 +1,22 @@ +--- +id: ruby-collections-array-method +title: 便利なメソッド +level: 3 +--- + +### 便利なメソッド + +`Array` には非常に多くの便利なメソッドが用意されています。 + +```ruby-repl +irb(main):012:0> fruits.length # 要素数 +=> 3 +irb(main):013:0> fruits.include?("banana") # 要素が含まれているか +=> true +irb(main):014:0> fruits.sort # ソートされた新しい配列を返す +=> ["apple", "banana", "cherry"] +irb(main):015:0> fruits.first # 最初の要素 +=> "apple" +irb(main):016:0> fruits.last # 最後の要素 +=> "cherry" +``` diff --git a/public/docs/ruby/4-collections/2-0-hash.md b/public/docs/ruby/4-collections/2-0-hash.md new file mode 100644 index 0000000..760fdf7 --- /dev/null +++ b/public/docs/ruby/4-collections/2-0-hash.md @@ -0,0 +1,11 @@ +--- +id: ruby-collections-hash +title: ハッシュ (Hash) +level: 2 +--- + +## ハッシュ (Hash) + +`Hash` は、キーと値のペアを格納するコレクションです。他の言語のMap、Dictionary、連想配列に相当します。 + +Rubyのハッシュには2つの主要な記法があります。 diff --git a/public/docs/ruby/4-collections/2-1-hash-rocket.md b/public/docs/ruby/4-collections/2-1-hash-rocket.md new file mode 100644 index 0000000..3800ec7 --- /dev/null +++ b/public/docs/ruby/4-collections/2-1-hash-rocket.md @@ -0,0 +1,17 @@ +--- +id: ruby-collections-hash-rocket +title: '1. 旧シンタックス (Rocket Syntax)' +level: 3 +--- + +### 1\. 旧シンタックス (Rocket Syntax) + +`=>`(ハッシュロケット)を使う記法です。キーには**任意のオブジェクト**(文字列、数値、シンボルなど)を使用できます。 + +```ruby-repl +irb(main):001:0> # キーが文字列の場合 +irb(main):002:0> user_profile = { "name" => "Alice", "age" => 30 } +=> {"name"=>"Alice", "age"=>30} +irb(main):003:0> user_profile["name"] +=> "Alice" +``` diff --git a/public/docs/ruby/4-collections/2-2-hash-json-like.md b/public/docs/ruby/4-collections/2-2-hash-json-like.md new file mode 100644 index 0000000..3dda204 --- /dev/null +++ b/public/docs/ruby/4-collections/2-2-hash-json-like.md @@ -0,0 +1,22 @@ +--- +id: ruby-collections-hash-json-like +title: '2. 新シンタックス (JSON-like Syntax)' +level: 3 +--- + +#### 2\. 新シンタックス (JSON-like Syntax) + +Ruby 1.9から導入された、より簡潔な記法です。JavaScriptのオブジェクトリテラルに似ています。 + +> **注意:** この記法を使うと、**キーは自動的にシンボル (Symbol) になります**。 + +```ruby-repl +irb(main):004:0> # 新シンタックス (キーはシンボルになる) +irb(main):005:0> user_profile_new = { name: "Bob", age: 25 } +=> {:name=>"Bob", :age=>25} +irb(main):006:0> # アクセス時もシンボル (:name) を使う +irb(main):007:0> user_profile_new[:name] +=> "Bob" +``` + +現在では、キーが固定されている場合は、シンボルを使った新シンタックスが好まれます。 diff --git a/public/docs/ruby/4-collections/3-0-range.md b/public/docs/ruby/4-collections/3-0-range.md new file mode 100644 index 0000000..220c895 --- /dev/null +++ b/public/docs/ruby/4-collections/3-0-range.md @@ -0,0 +1,11 @@ +--- +id: ruby-collections-range +title: 範囲 (Range) +level: 2 +--- + +## 範囲 (Range) + +`Range` は、連続する値のシーケンスを表すオブジェクトです。`for` ループや `case` 文での条件分岐によく使われます。 + +範囲の作成には `(start..end)` と `(start...end)` の2つの形式があります。 diff --git a/public/docs/ruby/4-collections/3-1-range-2.md b/public/docs/ruby/4-collections/3-1-range-2.md new file mode 100644 index 0000000..3abbab3 --- /dev/null +++ b/public/docs/ruby/4-collections/3-1-range-2.md @@ -0,0 +1,18 @@ +--- +id: ruby-collections-range-2 +title: .. (終端を含む) +level: 3 +--- + +### `..` (終端を含む) + +`..`(ドット2つ)は、終端の値を含む範囲を作成します。 + +```ruby-repl +irb(main):001:0> inclusive_range = (1..10) # 1から10まで (10を含む) +=> 1..10 +irb(main):002:0> inclusive_range.to_a # to_aで配列に変換できる +=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] +irb(main):003:0> inclusive_range.include?(10) +=> true +``` diff --git a/public/docs/ruby/4-collections/3-2-range-3.md b/public/docs/ruby/4-collections/3-2-range-3.md new file mode 100644 index 0000000..1732e27 --- /dev/null +++ b/public/docs/ruby/4-collections/3-2-range-3.md @@ -0,0 +1,18 @@ +--- +id: ruby-collections-range-3 +title: ... (終端を含まない) +level: 3 +--- + +### `...` (終端を含まない) + +`...`(ドット3つ)は、終端の値を含まない(未満の)範囲を作成します。 + +```ruby-repl +irb(main):004:0> exclusive_range = (1...10) # 1から10まで (10を含まない) +=> 1...10 +irb(main):005:0> exclusive_range.to_a +=> [1, 2, 3, 4, 5, 6, 7, 8, 9] +irb(main):006:0> exclusive_range.include?(10) +=> false +``` diff --git a/public/docs/ruby/4-collections/3-3-range-case.md b/public/docs/ruby/4-collections/3-3-range-case.md new file mode 100644 index 0000000..879e61e --- /dev/null +++ b/public/docs/ruby/4-collections/3-3-range-case.md @@ -0,0 +1,38 @@ +--- +id: ruby-collections-range-case +title: 範囲の活用例 +level: 3 +--- + +### 範囲の活用例 + +`Range` は `case` 文と組み合わせると非常に強力です。 + +```ruby:grade_checker.rb +def assign_grade(score) + case score + when (90..100) + "A" + when (80...90) # 80は含むが90は含まない (80-89) + "B" + when (60...80) + "C" + else + "F" + end +end + +puts "Score 95: #{assign_grade(95)}" +puts "Score 90: #{assign_grade(90)}" +puts "Score 89: #{assign_grade(89)}" +puts "Score 60: #{assign_grade(60)}" +puts "Score 59: #{assign_grade(59)}" +``` + +```ruby-exec:grade_checker.rb +Score 95: A +Score 90: A +Score 89: B +Score 60: C +Score 59: F +``` diff --git a/public/docs/ruby/4-collections/4-0-summary.md b/public/docs/ruby/4-collections/4-0-summary.md new file mode 100644 index 0000000..6edbd3f --- /dev/null +++ b/public/docs/ruby/4-collections/4-0-summary.md @@ -0,0 +1,13 @@ +--- +id: ruby-collections-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **Array**: `[]` で作成する順序付きリスト。`<<` で追加、`pop` で取り出し、`[-1]` で末尾にアクセスできます。 + * **Hash**: `{}` で作成するキー/バリューペア。 + * **Symbol**: `:name` のようにコロンで始まる識別子。イミュータブルで高速なため、ハッシュのキーに最適です。 + * **Hashのシンタックス**: キーがシンボルの場合、`{ key: "value" }` というモダンな記法が使えます。 + * **Range**: `(1..10)`(含む)と `(1...10)`(含まない)があり、連続したシーケンスを表現します。 diff --git a/public/docs/ruby/4-collections/4-1-practice1.md b/public/docs/ruby/4-collections/4-1-practice1.md new file mode 100644 index 0000000..a98dbb5 --- /dev/null +++ b/public/docs/ruby/4-collections/4-1-practice1.md @@ -0,0 +1,21 @@ +--- +id: ruby-collections-practice1 +title: '練習問題1: ショッピングカートの管理' +level: 3 +--- + +### 練習問題1: ショッピングカートの管理 + +あなたのショッピングカートを表現する配列 `cart` があります。 +`cart` は、商品情報を表すハッシュの配列です。 +以下の操作を行ってください。 + +1. `cart` に `{ name: "Orange", price: 120 }` を追加する。 +2. `cart` の最初の商品の名前 (`"Apple"`) を表示する。 + +```ruby:practice5_1.rb +cart = [{ name: "Apple", price: 100 }, { name: "Banana", price: 80 }] +``` + +```ruby-exec:practice5_1.rb +``` diff --git a/public/docs/ruby/4-collections/4-2-practice2.md b/public/docs/ruby/4-collections/4-2-practice2.md new file mode 100644 index 0000000..26afc5a --- /dev/null +++ b/public/docs/ruby/4-collections/4-2-practice2.md @@ -0,0 +1,20 @@ +--- +id: ruby-collections-practice2 +title: '練習問題2: ハッシュの操作' +level: 3 +--- + +### 練習問題2: ハッシュの操作 + +ユーザーの設定を保存するハッシュ `settings` を作成してください。 + +* キーにはシンボルを使用します (`:theme`, `:notifications`)。 +* `:theme` の初期値は `:light`、`:notifications` の初期値は `true` とします。 +* `settings` を作成した後、`:theme` の値を `:dark` に更新してください。 + +```ruby:practice5_2.rb +settings = +``` + +```ruby-exec:practice5_2.rb +``` diff --git a/public/docs/ruby/5-blocks-iterators/-intro.md b/public/docs/ruby/5-blocks-iterators/-intro.md new file mode 100644 index 0000000..8257213 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/-intro.md @@ -0,0 +1,3 @@ +Rubyの学習において、**ブロック (Block)** は最も重要で強力な機能の一つです。他言語の経験者にとって、これはラムダ式や無名関数、クロージャに似た概念ですが、Rubyではこれが言語構文の核に深く組み込まれています。 + +この章では、ブロックの使い方と、ブロックを活用する「イテレータ」と呼ばれるメソッドを学びます。 diff --git a/public/docs/ruby/5-blocks-iterators/1-0-block.md b/public/docs/ruby/5-blocks-iterators/1-0-block.md new file mode 100644 index 0000000..60a68d0 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/1-0-block.md @@ -0,0 +1,34 @@ +--- +id: ruby-blocks +title: 'ブロック構文: do...end と {}' +level: 2 +--- + +## ブロック構文: `do...end` と `{}` + +ブロックとは、メソッド呼び出しに渡すことができる**コードの塊**です。メソッド側は、受け取ったそのコードの塊を好きなタイミングで実行できます。 + +ブロックには2種類の書き方があります。 + +1. **`{ ... }` (波括弧)**: 通常、1行で完結する場合に使われます。 +2. **`do ... end`**: 複数行にわたる処理を書く場合に使われます。 + +どちらも機能的にはほぼ同じです。最も簡単な例は、指定した回数だけブロックを実行する `times` メソッドです。 + +```ruby-repl +irb(main):001:0> 3.times { puts "Hello!" } +Hello! +Hello! +Hello! +=> 3 + +irb(main):002:0> 3.times do +irb(main):003:1* puts "Ruby is fun!" +irb(main):004:1> end +Ruby is fun! +Ruby is fun! +Ruby is fun! +=> 3 +``` + +`3.times` というメソッド呼び出しの後ろに `{ ... }` や `do ... end` で囲まれたコードブロックを渡しています。`times` メソッドは、そのブロックを3回実行します。 diff --git a/public/docs/ruby/5-blocks-iterators/1-1-block-arg.md b/public/docs/ruby/5-blocks-iterators/1-1-block-arg.md new file mode 100644 index 0000000..3aae678 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/1-1-block-arg.md @@ -0,0 +1,18 @@ +--- +id: ruby-blocks-arg +title: ブロック引数 +level: 3 +--- + +### ブロック引数 + +ブロックは `| ... |` を使って引数を受け取ることができます。 + +```ruby-repl +irb(main):018:0> ["Alice", "Bob"].each do |name| +irb(main):019:1* puts "Hello, #{name}!" +irb(main):020:1> end +Hello, Alice! +Hello, Bob! +=> ["Alice", "Bob"] +``` diff --git a/public/docs/ruby/5-blocks-iterators/1-2-block-return.md b/public/docs/ruby/5-blocks-iterators/1-2-block-return.md new file mode 100644 index 0000000..4e5ed3c --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/1-2-block-return.md @@ -0,0 +1,17 @@ +--- +id: ruby-blocks-return +title: ブロックの戻り値 +level: 3 +--- + +### ブロックの戻り値 + +(Rubyのすべての式と同様に)ブロックも戻り値を持ちます。ブロックの戻り値とは、**ブロック内で最後に評価された式の値**です。 + +```ruby-repl +irb(main):021:0> result = [1, 2].map do |n| +irb(main):022:1* m = n * 10 # mは 10, 20 +irb(main):023:1* m + 5 # ブロックの戻り値 (15, 25) +irb(main):024:1> end +=> [15, 25] +``` diff --git a/public/docs/ruby/5-blocks-iterators/2-0-iterator.md b/public/docs/ruby/5-blocks-iterators/2-0-iterator.md new file mode 100644 index 0000000..1cf11af --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/2-0-iterator.md @@ -0,0 +1,11 @@ +--- +id: ruby-iterators +title: 代表的なイテレータ +level: 2 +--- + +## 代表的なイテレータ + +Rubyでは、コレクション(配列やハッシュなど)の各要素に対して処理を行うメソッドを**イテレータ (Iterator)** と呼びます。イテレータは通常、ブロックを受け取って動作します。 + +代表的なイテレータを見ていきましょう。 diff --git a/public/docs/ruby/5-blocks-iterators/2-1-each.md b/public/docs/ruby/5-blocks-iterators/2-1-each.md new file mode 100644 index 0000000..8257eec --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/2-1-each.md @@ -0,0 +1,26 @@ +--- +id: ruby-iterators-each +title: each +level: 3 +--- + +### `each` + +`each` は、コレクションの各要素を順番に取り出してブロックを実行します。他言語の `foreach` ループに最も近いものです。 + +`|n|` の部分は**ブロック引数**と呼ばれ、イテレータが取り出した要素(この場合は配列の各要素)を受け取ります。 + +```ruby-repl +irb(main):001:0> numbers = [1, 2, 3] +=> [1, 2, 3] + +irb(main):002:0> numbers.each do |n| +irb(main):003:1* puts "Current number is #{n}" +irb(main):004:1> end +Current number is 1 +Current number is 2 +Current number is 3 +=> [1, 2, 3] +``` + +> **Note:** `each` メソッドの戻り値は、元の配列 (`[1, 2, 3]`) 自身です。`each` はあくまで「繰り返すこと」が目的であり、ブロックの実行結果は利用しません。 diff --git a/public/docs/ruby/5-blocks-iterators/2-2-map.md b/public/docs/ruby/5-blocks-iterators/2-2-map.md new file mode 100644 index 0000000..013a942 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/2-2-map.md @@ -0,0 +1,27 @@ +--- +id: ruby-iterators-map +title: map (collect) +level: 3 +--- + +### `map` (`collect`) + +`map` は、各要素に対してブロックを実行し、その**ブロックの戻り値**を集めた**新しい配列**を返します。 + +```ruby-repl +irb(main):005:0> numbers = [1, 2, 3] +=> [1, 2, 3] + +irb(main):006:0> doubled = numbers.map { |n| n * 2 } +=> [2, 4, 6] + +irb(main):007:0> puts doubled.inspect +[2, 4, 6] +=> nil + +irb(main):008:0> puts numbers.inspect # 元の配列は変更されない +[1, 2, 3] +=> nil +``` + +`map` は、元の配列を変換した新しい配列が欲しい場合に非常に便利です。 diff --git a/public/docs/ruby/5-blocks-iterators/2-3-select.md b/public/docs/ruby/5-blocks-iterators/2-3-select.md new file mode 100644 index 0000000..61af1a1 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/2-3-select.md @@ -0,0 +1,17 @@ +--- +id: ruby-iterators-select +title: select (filter) +level: 3 +--- + +### `select` (`filter`) + +`select` は、各要素に対してブロックを実行し、ブロックの戻り値が**真 (true)** になった要素だけを集めた**新しい配列**を返します。 + +```ruby-repl +irb(main):009:0> numbers = [1, 2, 3, 4, 5, 6] +=> [1, 2, 3, 4, 5, 6] + +irb(main):010:0> evens = numbers.select { |n| n.even? } # n.even? は n % 2 == 0 と同じ +=> [2, 4, 6] +``` diff --git a/public/docs/ruby/5-blocks-iterators/2-4-find.md b/public/docs/ruby/5-blocks-iterators/2-4-find.md new file mode 100644 index 0000000..1f0bfa8 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/2-4-find.md @@ -0,0 +1,20 @@ +--- +id: ruby-iterators-find +title: find (detect) +level: 3 +--- + +### `find` (`detect`) + +`find` は、ブロックの戻り値が**真 (true)** になった**最初の要素**を返します。見つからなければ `nil` を返します。 + +```ruby-repl +irb(main):011:0> numbers = [1, 2, 3, 4, 5, 6] +=> [1, 2, 3, 4, 5, 6] + +irb(main):012:0> first_even = numbers.find { |n| n.even? } +=> 2 + +irb(main):013:0> over_10 = numbers.find { |n| n > 10 } +=> nil +``` diff --git a/public/docs/ruby/5-blocks-iterators/3-0-enumerable.md b/public/docs/ruby/5-blocks-iterators/3-0-enumerable.md new file mode 100644 index 0000000..3253613 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/3-0-enumerable.md @@ -0,0 +1,64 @@ +--- +id: ruby-blocks-iterators-enumerable +title: Enumerableモジュール:イテレーションの力 +level: 2 +--- + +## Enumerableモジュール:イテレーションの力 + +`each`, `map`, `select`, `find` といった便利なメソッドは、実は `Enumerable`(エニューメラブル)という**モジュール**によって提供されています。 + +`Enumerable` はRubyの「Mix-in(ミックスイン)」機能の代表例です。これは、クラスに「混ぜ込む」ことで、そのクラスのインスタンスに特定の機能(メソッド群)を追加する仕組みです。 + +`Enumerable` をMix-inするクラス(例えば `Array` や `Hash`, `Range`)が満たすべき契約はただ一つ、**`each` メソッドを実装すること**です。 + +`each` メソッドさえ定義されていれば、`Enumerable` モジュールは `each` を使って `map`, `select`, `find`, `sort`, `count` など、数十もの便利なイテレーションメソッドを自動的に提供してくれます。 + +例えば、`Array` クラスは `each` を持っています。 + +```ruby-repl +irb(main):014:0> numbers = [1, 2, 3] +=> [1, 2, 3] +# numbers (Array) は each を持っているので... +irb(main):015:0> numbers.map { |n| n * 2 } # map が使える +=> [2, 4, 6] +irb(main):016:0> numbers.select { |n| n.odd? } # select が使える +=> [1, 3] +``` + +これは、自分で新しいコレクションクラスを作った場合でも同様です。(`include` については後の「モジュールとMix-in」の章で詳しく学びます) + +```ruby:my_collection.rb +# Enumerableモジュールを include する +class MyCollection + include Enumerable # これがMix-in + + def initialize(items) + @items = items + end + + # Enumerable のために each メソッドを定義する + def each + @items.each do |item| + yield(item) # ブロックに要素を渡す + end + end +end + +collection = MyCollection.new([10, 20, 30]) + +# each を定義しただけで、map が使える! +doubled = collection.map { |x| x * 2 } +puts "Map result: #{doubled.inspect}" + +# select も使える! +selected = collection.select { |x| x > 15 } +puts "Select result: #{selected.inspect}" +``` + +```ruby-exec:my_collection.rb +Map result: [20, 40, 60] +Select result: [20, 30] +``` + +このように、Rubyのイテレータの強力さは `Enumerable` モジュールによって支えられています。Rubyでは、**「`each` メソッドを持つものは、すべて `Enumerable` である(あるいはそう振る舞える)」**という考え方が非常に重要です。 diff --git a/public/docs/ruby/5-blocks-iterators/4-0-for.md b/public/docs/ruby/5-blocks-iterators/4-0-for.md new file mode 100644 index 0000000..3b77b9f --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/4-0-for.md @@ -0,0 +1,35 @@ +--- +id: ruby-blocks-iterators-for +title: for ループとの比較 +level: 2 +--- + +## `for` ループとの比較 + +他言語経験者の方は、`for` ループを使いたくなるかもしれません。 + +```c +// C や Java の for ループ +for (int i = 0; i < 3; i++) { + printf("Hello\n"); +} +``` + +Rubyにも `for` 構文は存在します。 + +```ruby-repl +irb(main):014:0> numbers = [1, 2, 3] +=> [1, 2, 3] + +irb(main):015:0> for num in numbers +irb(main):016:1* puts num +irb(main):017:1> end +1 +2 +3 +=> [1, 2, 3] +``` + +しかし、Rubyの世界では `for` ループは**ほとんど使われません**。なぜなら、`for` は内部的に `each` メソッドを呼び出しているに過ぎないからです。 + +Rubyプログラマは、`for` よりも `each` などのイテレータをブロックと共に使うことを圧倒的に好みます。イテレータの方が、何をしているか(単なる繰り返し、変換、選択など)がメソッド名 (`each`, `map`, `select`) から明確であり、コードが読みやすくなるためです。 diff --git a/public/docs/ruby/5-blocks-iterators/6-0-yield.md b/public/docs/ruby/5-blocks-iterators/6-0-yield.md new file mode 100644 index 0000000..3e6fbc0 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/6-0-yield.md @@ -0,0 +1,54 @@ +--- +id: ruby-blocks-iterators-yield +title: yield:ブロックを受け取るメソッド +level: 2 +--- + +## `yield`:ブロックを受け取るメソッド + +では、どうすればブロックを受け取るメソッドを自分で作れるのでしょうか? +それには `yield` というキーワードを使います。 + +メソッド内で `yield` が呼び出されると、そのメソッドに渡されたブロックが実行されます。 + +```ruby:yield_basic.rb +def simple_call + puts "メソッド開始" + yield # ここでブロックが実行される + puts "メソッド終了" +end + +simple_call do + puts "ブロック内の処理です" +end +``` + +```ruby-exec:yield_basic.rb +メソッド開始 +ブロック内の処理です +メソッド終了 +``` + +`yield` はブロックに引数を渡すこともできます。 + +```ruby:yield_with_args.rb +def call_with_name(name) + puts "メソッド開始" + yield(name) # ブロックに "Alice" を渡す + yield("Bob") # ブロックに "Bob" を渡す + puts "メソッド終了" +end + +call_with_name("Alice") do |n| + puts "ブロックが #{n} を受け取りました" +end +``` + +```ruby-exec:yield_with_args.rb +メソッド開始 +ブロックが Alice を受け取りました +ブロックが Bob を受け取りました +メソッド終了 +``` + +`each` や `map` のようなイテレータは、内部でこの `yield` を使って、コレクションの各要素をブロックに渡しながら実行しているのです。 diff --git a/public/docs/ruby/5-blocks-iterators/7-0-summary.md b/public/docs/ruby/5-blocks-iterators/7-0-summary.md new file mode 100644 index 0000000..44c21f5 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/7-0-summary.md @@ -0,0 +1,14 @@ +--- +id: ruby-blocks-iterators-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **ブロック**は、メソッドに渡せるコードの塊で、`{}`(1行)または `do...end`(複数行)で記述します。 + * **イテレータ**は、ブロックを受け取り、要素の繰り返し処理を行うメソッドです(`each`, `map`, `select` など)。 + * **Enumerableモジュール**は、 `each` を実装するクラスに `map` や `select` などの強力なイテレーション機能を提供します。 + * Rubyでは `for` ループよりもイテレータが好まれます。 + * ブロックは `|arg|` で引数を受け取ることができ、ブロックの最後の式の値が戻り値となります。 + * 自作メソッド内で `yield` を使うと、渡されたブロックを実行できます。 diff --git a/public/docs/ruby/5-blocks-iterators/7-1-practice1.md b/public/docs/ruby/5-blocks-iterators/7-1-practice1.md new file mode 100644 index 0000000..39c0e1d --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/7-1-practice1.md @@ -0,0 +1,17 @@ +--- +id: ruby-blocks-iterators-practice1 +title: 練習問題1 +level: 3 +--- + +### 練習問題1 + +数値の配列 `[1, 2, 3, 4, 5]` があります。`map` イテレータとブロックを使って、各要素を文字列に変換し(例: `1` → `"1"`)、 `"1"`, `"2"`, `"3"`, `"4"`, `"5"` という文字列の配列を作成してください。 + +```ruby:practice6_1.rb +array = [1, 2, 3, 4, 5] + +``` + +```ruby-exec:practice6_1.rb +``` diff --git a/public/docs/ruby/5-blocks-iterators/7-2-practice2.md b/public/docs/ruby/5-blocks-iterators/7-2-practice2.md new file mode 100644 index 0000000..17d6a30 --- /dev/null +++ b/public/docs/ruby/5-blocks-iterators/7-2-practice2.md @@ -0,0 +1,16 @@ +--- +id: ruby-blocks-iterators-practice2 +title: 練習問題2 +level: 3 +--- + +### 練習問題2 +文字列の配列 `["apple", "banana", "cherry", "date"]` があります。`select` イテレータとブロックを使って、文字数が5文字以上の果物だけを抽出した新しい配列(`["apple", "banana", "cherry"]`)を作成してください。 + +```ruby:practice6_2.rb +array = ["apple", "banana", "cherry", "date"] + +``` + +```ruby-exec:practice6_2.rb +``` diff --git a/public/docs/ruby/6-classes/-intro.md b/public/docs/ruby/6-classes/-intro.md new file mode 100644 index 0000000..482aab4 --- /dev/null +++ b/public/docs/ruby/6-classes/-intro.md @@ -0,0 +1,3 @@ +Rubyは純粋なオブジェクト指向言語であり、第4章「すべてがオブジェクト」で学んだように、数値や文字列さえもオブジェクトです。この章では、それらのオブジェクトの「設計図」である**クラス**を定義する方法について学びます。 + +他のオブジェクト指向言語(Java, Python, C\#など)の経験があれば、概念は馴染み深いはずです。Ruby特有の構文(`@`や`attr_*`など)に注目してください。 diff --git a/public/docs/ruby/6-classes/1-0-initialize.md b/public/docs/ruby/6-classes/1-0-initialize.md new file mode 100644 index 0000000..c19efe0 --- /dev/null +++ b/public/docs/ruby/6-classes/1-0-initialize.md @@ -0,0 +1,39 @@ +--- +id: ruby-classes-initialize +title: '💴 クラス定義: class, initialize' +level: 2 +--- + +## 💴 クラス定義: class, initialize + +Rubyでは、`class`キーワードを使ってクラスを定義します。クラス名は慣習として**大文字**で始めます(例: `MyClass`)。 + +`new`メソッドが呼ばれたときに実行される特別なメソッドが `initialize` です。これは他の言語における**コンストラクタ**に相当し、インスタンスの初期化処理を行います。 + +```ruby:user.rb +# クラス名はアッパーキャメルケース(PascalCase)で記述します +class User + # newが呼ばれた際に自動的に実行される初期化メソッド + def initialize(name, age) + # インスタンス変数は @ で始める + @name = name + @age = age + puts "Userオブジェクトが作成されました!" + end +end + +# クラスからインスタンスを生成 +# User.new は initialize メソッドを呼び出す +user1 = User.new("Alice", 30) +user2 = User.new("Bob", 25) + +p user1 +p user2 +``` + +```ruby-exec:user.rb +Userオブジェクトが作成されました! +Userオブジェクトが作成されました! + + +``` diff --git a/public/docs/ruby/6-classes/2-0-instance-var-method.md b/public/docs/ruby/6-classes/2-0-instance-var-method.md new file mode 100644 index 0000000..2dfccc2 --- /dev/null +++ b/public/docs/ruby/6-classes/2-0-instance-var-method.md @@ -0,0 +1,39 @@ +--- +id: ruby-classes-instance-var-method +title: 🏃‍♂️ インスタンス変数 (@var) とインスタンスメソッド +level: 2 +--- + +## 🏃‍♂️ インスタンス変数 (`@var`) とインスタンスメソッド + +`@`で始まる変数(例: `@name`)は**インスタンス変数**です。 + + * そのクラスのインスタンス(オブジェクト)ごとに個別に保持されます。 + * `initialize`や他のインスタンスメソッド内で定義・参照されます。 + * **デフォルトで外部から直接アクセスすることはできません(カプセル化)**。 + +`def`で定義されたメソッド(`initialize`を除く)が**インスタンスメソッド**です。これらはインスタンスの「振る舞い」を定義し、そのインスタンスのインスタンス変数(`@var`)にアクセスできます。 + +```ruby:user_greet.rb +class User + def initialize(name, age) + @name = name + @age = age + end + + # インスタンスメソッドの定義 + def greet + # メソッド内からインスタンス変数(@name, @age)を参照 + puts "こんにちは、#{@name}さん (#{@age}歳) です。" + end +end + +user1 = User.new("Alice", 30) + +# インスタンスメソッドの呼び出し +user1.greet +``` + +```ruby-exec:user_greet.rb +こんにちは、Aliceさん (30歳) です。 +``` diff --git a/public/docs/ruby/6-classes/3-0-accessor.md b/public/docs/ruby/6-classes/3-0-accessor.md new file mode 100644 index 0000000..bde47b3 --- /dev/null +++ b/public/docs/ruby/6-classes/3-0-accessor.md @@ -0,0 +1,28 @@ +--- +id: ruby-classes-accessor +title: '🔐 アクセサメソッド +level: 2 +--- + +## 🔐 アクセサメソッド + +前述の通り、`@name`のようなインスタンス変数は外部から直接参照・変更できません。 + +```ruby:access_error.rb +class User + def initialize(name) + @name = name + end +end + +user = User.new("Alice") +p user.name #=> NoMethodError +user.name = "Bob" #=> NoMethodError +``` + +```ruby-exec:access_error.rb +NoMethodError (undefined method `name'...) +``` + + +外部からアクセスさせるためには、**アクセサメソッド**(ゲッターとセッター)を定義する必要があります。 diff --git a/public/docs/ruby/6-classes/3-1-accessor-manual.md b/public/docs/ruby/6-classes/3-1-accessor-manual.md new file mode 100644 index 0000000..1d69203 --- /dev/null +++ b/public/docs/ruby/6-classes/3-1-accessor-manual.md @@ -0,0 +1,38 @@ +--- +id: ruby-classes-accessor-manual +title: 手動での定義 +level: 3 +--- + +### 手動での定義 + +JavaやC\#のように、ゲッターとセッターを明示的に書くこともできます。 + +```ruby:manual_accessor.rb +class Product + def initialize(name) + @name = name + end + + # ゲッター (値の読み取り) + def name + @name + end + + # セッター (値の書き込み) + # メソッド名が = で終わるのが特徴 + def name=(new_name) + @name = new_name + end +end + +item = Product.new("Laptop") +puts item.name # ゲッター(item.name)の呼び出し +item.name = "Desktop" # セッター(item.name=)の呼び出し +puts item.name +``` + +```ruby-exec:manual_accessor.rb +Laptop +Desktop +``` diff --git a/public/docs/ruby/6-classes/3-2-accessor-attr.md b/public/docs/ruby/6-classes/3-2-accessor-attr.md new file mode 100644 index 0000000..3b29ea5 --- /dev/null +++ b/public/docs/ruby/6-classes/3-2-accessor-attr.md @@ -0,0 +1,62 @@ +--- +id: ruby-classes-accessor-attr +title: attr_* による自動定義 +level: 3 +--- + +### `attr_*` による自動定義 + +Rubyでは、上記のような定型的なアクセサメソッドを自動で定義するための便利な「マクロ」が用意されています。これらはクラス定義のトップレベルで使います。 + + * `attr_reader :var` : ゲッター(読み取り専用)を定義します。 + * `attr_writer :var` : セッター(書き込み専用)を定義します。 + * `attr_accessor :var` : ゲッターとセッターの両方を定義します。 + +引数にはインスタンス変数名の`@`を除いた**シンボル**(`:`から始まる名前)を渡します。 + +```ruby:auto_accessor.rb +class Product + # @name のゲッターとセッターを自動定義 + attr_accessor :name + # @price のゲッターのみを自動定義 (読み取り専用) + attr_reader :price + # @stock のセッターのみを自動定義 (書き込み専用) + attr_writer :stock + + def initialize(name, price, stock) + @name = name + @price = price + @stock = stock + end + + def summary + # ゲッターは self.price とも書けるが、 + # クラス内部では @price と直接アクセスするのが一般的 + "商品: #{@name}, 価格: #{@price}円" + end +end + +item = Product.new("Mouse", 3000, 50) + +# attr_accessor +puts item.name # ゲッター +item.name = "Keyboard" # セッター +puts item.name + +# attr_reader +puts item.price # ゲッター +# item.price = 3500 # => NoMethodError (undefined method `price=') + +# attr_writer +# puts item.stock # => NoMethodError (undefined method `stock') +item.stock = 100 # セッター + +puts item.summary +``` + +```ruby-exec:auto_accessor.rb +Mouse +Keyboard +3000 +商品: Keyboard, 価格: 3000円 +``` diff --git a/public/docs/ruby/6-classes/4-0-class-var-method.md b/public/docs/ruby/6-classes/4-0-class-var-method.md new file mode 100644 index 0000000..495b036 --- /dev/null +++ b/public/docs/ruby/6-classes/4-0-class-var-method.md @@ -0,0 +1,69 @@ +--- +id: ruby-classes-class-var-method +title: 🏢 クラス変数 (@@var) とクラスメソッド (self.method_name) +level: 2 +--- + +## 🏢 クラス変数 (`@@var`) とクラスメソッド (`self.method_name`) + +`@@`で始まる変数(例: `@@count`)は**クラス変数**です。 + + * インスタンスごとではなく、**クラス全体で共有**されます。 + * そのクラスのすべてのインスタンスから参照・変更が可能です。 + * (注意)継承した場合、子クラスとも共有されるため、意図しない動作の原因になることもあり、使用には注意が必要です。 + +**クラスメソッド**は、インスタンスではなく、**クラス自体から呼び出すメソッド**です。`def self.メソッド名` のように `self.` をつけて定義します。 + + * `User.new` の `new` も、実はクラスメソッドの一種です。 + * インスタンス変数 (`@var`) にはアクセスできません(インスタンスが存在しないため)。 + * クラス変数 (`@@var`) にはアクセスできます。 + * ファクトリメソッド(特定のパターンのインスタンスを生成するメソッド)や、クラス全体に関わる操作(例: 総数のカウント)によく使われます。 + +```ruby:counter.rb +class Counter + # クラス変数(クラス全体で共有) + @@total_count = 0 + + attr_reader :id + + def initialize(id) + @id = id + # インスタンスが作られるたびにクラス変数を増やす + @@total_count += 1 + end + + # クラスメソッド (self. をつける) + # クラス変数を返す + def self.total_count + @@total_count + end + + # インスタンスメソッド + def report_total + # インスタンスメソッドからもクラス変数を参照できる + "私のIDは #{@id} です。総数は #{@@total_count} です。" + end +end + +# クラスメソッドの呼び出し +puts "初期カウント: #{Counter.total_count}" #=> 0 + +c1 = Counter.new(1) +c2 = Counter.new(2) + +# クラスメソッドの呼び出し +puts "最終カウント: #{Counter.total_count}" #=> 2 + +# インスタンスメソッドの呼び出し +puts c1.report_total #=> 私のIDは 1 です。総数は 2 です。 +puts c2.report_total #=> 私のIDは 2 です。総数は 2 です。 + +# c1.total_count #=> NoMethodError (インスタンスからは呼べない) +``` + +```ruby-exec:counter.rb +初期カウント: 0 +最終カウント: 2 +私のIDは 1 です。総数は 2 です。 +私のIDは 2 です。総数は 2 です。 +``` diff --git a/public/docs/ruby/6-classes/5-0-inheritance.md b/public/docs/ruby/6-classes/5-0-inheritance.md new file mode 100644 index 0000000..0cd59af --- /dev/null +++ b/public/docs/ruby/6-classes/5-0-inheritance.md @@ -0,0 +1,65 @@ +--- +id: ruby-classes-inheritance +title: 👪 継承 (<) と super +level: 2 +--- + +## 👪 継承 (`<`) と `super` + +Rubyは**単一継承**をサポートしています。`<` 記号を使って親クラス(スーパークラス)を指定します。 + +子クラス(サブクラス)は、親クラスのメソッドや変数を引き継ぎます。 + +子クラスで親クラスと同じ名前のメソッドを定義(**オーバーライド**)した際、`super`キーワードを使うと、**親クラスの同名メソッドを呼び出す**ことができます。 + +これは特に `initialize` メソッドで、親クラスの初期化処理を呼び出すために必須となります。 + +```ruby:vehicle.rb +# 親クラス (スーパークラス) +class Vehicle + attr_reader :name + + def initialize(name) + @name = name + puts "Vehicleを初期化中: #{@name}" + end + + def move + puts "#{@name} は移動します。" + end +end + +# 子クラス (サブクラス) +# Vehicle クラスを継承 +class Car < Vehicle + def initialize(name, color) + # super で親クラスの initialize を呼び出す + # (name を渡す) + super(name) + @color = color + puts "Carを初期化中: 色は#{@color}" + end + + # move メソッドをオーバーライド (上書き) + def move + # super で親クラスの move メソッドを呼び出す + super + # Car 固有の処理を追加 + puts "車輪が回転します。" + end +end + +my_car = Car.new("MyCar", "Red") +puts "---" +my_car.move +``` + +```ruby-exec:vehicle.rb +Vehicleを初期化中: MyCar +Carを初期化中: 色はRed +--- +MyCar は移動します。 +車輪が回転します。 +``` + +`super` は引数を省略すると、現在のメソッドが受け取った引数をそのまま親メソッドに渡します。`super()` のように `()` をつけると、引数なしで親メソッドを呼び出します。 diff --git a/public/docs/ruby/6-classes/6-0-summary.md b/public/docs/ruby/6-classes/6-0-summary.md new file mode 100644 index 0000000..d923006 --- /dev/null +++ b/public/docs/ruby/6-classes/6-0-summary.md @@ -0,0 +1,15 @@ +--- +id: ruby-classes-summary +title: 📝 この章のまとめ +level: 2 +--- + +## 📝 この章のまとめ + + * クラスは `class` キーワードで定義し、インスタンスは `.new` で生成します。 + * `initialize` はインスタンス生成時に呼ばれるコンストラクタです。 + * インスタンス変数は `@` で始まり、インスタンスごとに独立し、デフォルトでプライベートです。 + * `attr_reader`, `attr_writer`, `attr_accessor` は、インスタンス変数へのアクセサ(ゲッター/セッター)を自動定義するマクロです。 + * クラス変数は `@@` で始まり、クラスと全インスタンスで共有されます。 + * クラスメソッドは `def self.メソッド名` で定義し、クラス自体から呼び出します。 + * 継承は `<` で行い、`super` で親クラスの同名メソッドを呼び出します。 diff --git a/public/docs/ruby/6-classes/6-1-practice1.md b/public/docs/ruby/6-classes/6-1-practice1.md new file mode 100644 index 0000000..b9f3f12 --- /dev/null +++ b/public/docs/ruby/6-classes/6-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: ruby-classes-practice1 +title: '練習問題1: Book クラスの作成' +level: 3 +--- + +### 練習問題1: `Book` クラスの作成 + +以下の仕様を持つ `Book` クラスを作成してください。 + +1. `initialize` で `title`(タイトル)と `author`(著者)を受け取る。 +2. `title` と `author` は、インスタンス変数(`@title`, `@author`)に格納する。 +3. `title` と `author` は、どちらも外部から読み取り可能(書き換えは不可)にする。 +4. `info` というインスタンスメソッドを持ち、`"タイトル: [title], 著者: [author]"` という形式の文字列を返す。 + +```ruby:practice7_1.rb +# ここにBookクラスの定義を書いてください + + +book = Book.new("Ruby入門", "Sato") +puts book.info +puts book.title +# book.title = "改訂版" #=> エラー (NoMethodError) になるはず +``` + +```ruby-exec:practice7_1.rb +(実行結果例) +タイトル: Ruby入門, 著者: Sato +Ruby入門 +``` diff --git a/public/docs/ruby/6-classes/6-2-practice2.md b/public/docs/ruby/6-classes/6-2-practice2.md new file mode 100644 index 0000000..9e1352e --- /dev/null +++ b/public/docs/ruby/6-classes/6-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: ruby-classes-practice2 +title: '練習問題2: 継承を使った EBook クラスの作成' +level: 3 +--- + +### 練習問題2: 継承を使った `EBook` クラスの作成 + +問題1で作成した `Book` クラスを継承して、以下の仕様を持つ `EBook`(電子書籍)クラスを作成してください。 + +1. `initialize` で `title`, `author`, `file_size`(ファイルサイズ, 例: "10MB")を受け取る。 +2. `title` と `author` の初期化は、`Book` クラスの `initialize` を利用する (`super` を使う)。 +3. `file_size` は外部から読み取り可能にする。 +4. `info` メソッドをオーバーライドし、`"タイトル: [title], 著者: [author] (ファイルサイズ: [file_size])"` という形式の文字列を返す。 + * ヒント: 親クラスの `info` メソッドの結果を `super` で利用すると効率的です。 + +```ruby:practice7_2.rb +require './practice7_1.rb' # 7_1のコードを実行してBookの定義を読み込みます + +# ここにEBookクラスの定義を書いてください + +ebook = EBook.new("実践Ruby", "Tanaka", "2.5MB") +puts ebook.info +puts ebook.title +``` + +```ruby-exec:practice7_2.rb +タイトル: 実践Ruby, 著者: Tanaka (ファイルサイズ: 2.5MB) +実践Ruby +``` diff --git a/public/docs/ruby/7-modules/-intro.md b/public/docs/ruby/7-modules/-intro.md new file mode 100644 index 0000000..a3adfc9 --- /dev/null +++ b/public/docs/ruby/7-modules/-intro.md @@ -0,0 +1,3 @@ +Rubyのオブジェクト指向において、クラスの継承は「is-a」(〜である)関係を表現するのに適しています。しかし、「has-a」(〜を持つ)や「can-do」(〜ができる)といった**振る舞い(ビヘイビア)**を複数の異なるクラス間で共有したい場合があります。 + +他の言語では「インターフェース」や「トレイト」で解決するこの問題を、Rubyは**モジュール (Module)** と **ミックスイン (Mix-in)** という強力な仕組みで解決します。 diff --git a/public/docs/ruby/7-modules/1-0-basic.md b/public/docs/ruby/7-modules/1-0-basic.md new file mode 100644 index 0000000..16a6893 --- /dev/null +++ b/public/docs/ruby/7-modules/1-0-basic.md @@ -0,0 +1,14 @@ +--- +id: ruby-modules-basic +title: モジュール (module) の2つの役割 +level: 2 +--- + +## モジュール (module) の2つの役割 + +`module` キーワードで定義されるモジュールには、大きく分けて2つの主要な役割があります。 + +1. **名前空間 (Namespace):** + 関連するクラス、メソッド、定数を一つのグループにまとめ、名前の衝突(コンフリクト)を防ぎます。 +2. **ミックスイン (Mix-in):** + メソッドの集まりを定義し、それをクラスに `include` することで、インスタンスメソッドとして機能を追加します。これはRubyの「多重継承」の代替手段です。 diff --git a/public/docs/ruby/7-modules/2-0-namespace.md b/public/docs/ruby/7-modules/2-0-namespace.md new file mode 100644 index 0000000..7714a82 --- /dev/null +++ b/public/docs/ruby/7-modules/2-0-namespace.md @@ -0,0 +1,44 @@ +--- +id: ruby-modules-namespace +title: 名前空間としてのモジュール +level: 2 +--- + +## 名前空間としてのモジュール + +プログラムが大規模になると、異なる目的で同じ名前のクラス(例: `Database::User` と `WebApp::User`)を使いたくなることがあります。モジュールは、これらを区別するための「仕切り」として機能します。 + +名前空間内の要素には、`::` (スコープ解決演算子) を使ってアクセスします。 + +```ruby:module_example.rb +module AppUtilities + VERSION = "1.0.0" + + class Logger + def log(msg) + puts "[App log] #{msg}" + end + end + + # モジュールメソッド (self. をつける) + def self.default_message + "Hello from Utility" + end +end + +# 定数へのアクセス +puts AppUtilities::VERSION + +# モジュールメソッドの呼び出し +puts AppUtilities.default_message + +# モジュール内のクラスのインスタンス化 +logger = AppUtilities::Logger.new +logger.log("Initialized.") +``` + +```ruby-exec:module_example.rb +1.0.0 +Hello from Utility +[App log] Initialized. +``` diff --git a/public/docs/ruby/7-modules/3-0-include.md b/public/docs/ruby/7-modules/3-0-include.md new file mode 100644 index 0000000..6a93e12 --- /dev/null +++ b/public/docs/ruby/7-modules/3-0-include.md @@ -0,0 +1,81 @@ +--- +id: ruby-modules-include +title: ミックスインとしてのモジュール (include) +level: 2 +--- + +## ミックスインとしてのモジュール (`include`) + +モジュールの最も強力な機能がミックスインです。これにより、クラスは継承ツリーとは無関係に、モジュールの振る舞い(インスタンスメソッド)を取り込むことができます。 + +`include` を使うと、モジュールはクラスの継承チェーン(祖先チェーン)に挿入されます。具体的には、`include` したクラスのスーパークラスの「直前」に挿入されます。 + +```ruby:mix_in_example.rb +# 「飛ぶ」能力を提供するモジュール +module Flyable + def fly + puts "I'm flying! My speed is #{fly_speed}." + end + + # このモジュールは、include したクラスが + # `fly_speed` メソッドを実装していることを期待している +end + +# 「泳ぐ」能力を提供するモジュール +module Swimmable + def swim + puts "I'm swimming!" + end +end + +class Bird + # fly_speed を実装 + def fly_speed + "10km/h" + end +end + +class Duck < Bird + include Flyable # 飛べる + include Swimmable # 泳げる +end + +class Penguin < Bird + include Swimmable # 泳げる (飛べない) +end + +class Airplane + include Flyable # 飛べる (生物ではない) + + def fly_speed + "800km/h" + end +end + +puts "--- Duck ---" +duck = Duck.new +duck.fly +duck.swim + +puts "--- Penguin ---" +penguin = Penguin.new +# penguin.fly #=> NoMethodError +penguin.swim + +puts "--- Airplane ---" +airplane = Airplane.new +airplane.fly +# airplane.swim #=> NoMethodError +``` + +```ruby-exec:mix_in_example.rb +--- Duck --- +I'm flying! My speed is 10km/h. +I'm swimming! +--- Penguin --- +I'm swimming! +--- Airplane --- +I'm flying! My speed is 800km/h. +``` + +`Duck` と `Airplane` は全く異なるクラス(`Bird` のサブクラスと、`Object` のサブクラス)ですが、`Flyable` モジュールを `include` することで `fly` メソッドを共有できています。 diff --git a/public/docs/ruby/7-modules/4-0-include-extend.md b/public/docs/ruby/7-modules/4-0-include-extend.md new file mode 100644 index 0000000..2729c29 --- /dev/null +++ b/public/docs/ruby/7-modules/4-0-include-extend.md @@ -0,0 +1,43 @@ +--- +id: ruby-modules-include-extend +title: include vs extend +level: 2 +--- + +## `include` vs `extend` + +`include` と `extend` は、モジュールのメソッドをどこに追加するかが異なります。 + + * `include`: モジュールのメソッドを、クラスの**インスタンスメソッド**として追加します。 + * `extend`: モジュールのメソッドを、クラスの**クラスメソッド**(特異メソッド)として追加します。 + +```ruby:extend_example.rb +module HelperMethods + def info + "This is a helper method." + end +end + +# --- include の場合 --- +class IncludedClass + include HelperMethods +end + +obj = IncludedClass.new +obj.info # インスタンスメソッドになる +# IncludedClass.info #=> NoMethodError + +# --- extend の場合 --- +class ExtendedClass + extend HelperMethods +end + +ExtendedClass.info # クラスメソッドになる +obj2 = ExtendedClass.new +# obj2.info #=> NoMethodError +``` + +```ruby-exec:extend_example.rb +"This is a helper method." +"This is a helper method." +``` diff --git a/public/docs/ruby/7-modules/5-0-access-control.md b/public/docs/ruby/7-modules/5-0-access-control.md new file mode 100644 index 0000000..d1fd3e6 --- /dev/null +++ b/public/docs/ruby/7-modules/5-0-access-control.md @@ -0,0 +1,84 @@ +--- +id: ruby-modules-access-control +title: アクセスコントロール (public, private, protected) +level: 2 +--- + +## アクセスコントロール (`public`, `private`, `protected`) + +Rubyのアクセスコントロールは、他の言語と少し異なる振る舞い、特に `private` の動作に特徴があります。 + + * `public` (デフォルト) + + * どこからでも呼び出せます。レシーバ(`object.`)を省略しても、明示しても構いません。 + + * `private` + + * **レシーバを明示して呼び出すことができません**。 + * `self.` を付けずに、クラス内部(またはサブクラス)からのみ呼び出せます。 + * 主にクラス内部の詳細を隠蔽(カプセル化)するために使われます。 + + * `protected` + + * `private` と似ていますが、**同じクラス(またはサブクラス)の他のインスタンスをレシーバとして呼び出すことができます**。 + * オブジェクト同士を比較するメソッドなどで使われます。 + +```ruby:access_control_demo.rb +class Wallet + attr_reader :id + + def initialize(id, amount) + @id = id + @balance = amount # private なインスタンス変数 + end + + # public メソッド (外部インターフェース) + def transfer(other_wallet, amount) + if withdraw(amount) + other_wallet.deposit(amount) + puts "Transferred #{amount} from #{self.id} to #{other_wallet.id}" + else + puts "Transfer failed: Insufficient funds in #{self.id}" + end + end + + # protected メソッド (インスタンス間での連携) + protected + + def deposit(amount) + @balance += amount + end + + # private メソッド (内部処理) + private + + def withdraw(amount) + if @balance >= amount + @balance -= amount + true + else + false + end + end +end + +w1 = Wallet.new("Wallet-A", 100) +w2 = Wallet.new("Wallet-B", 50) + +# public メソッドはどこからでも呼べる +w1.transfer(w2, 70) + +puts "w1 ID: #{w1.id}" +# puts "w1 Balance: #{w1.balance}" #=> NoMethodError (attr_reader がないため) + +# private / protected メソッドは外部から直接呼べない +# w1.deposit(100) #=> NoMethodError: protected method `deposit' called... +# w1.withdraw(10) #=> NoMethodError: private method `withdraw' called... +``` + +```ruby-exec:access_control_demo.rb +Transferred 70 from Wallet-A to Wallet-B +w1 ID: Wallet-A +``` + +この例では、`transfer` (public) が内部で `withdraw` (private) を呼び出し、引数で受け取った `other_wallet` の `deposit` (protected) を呼び出しています。`deposit` は `protected` なので、`other_wallet.` というレシーバを明示しても `Wallet` クラス内からは呼び出せます。 diff --git a/public/docs/ruby/7-modules/6-0-summary.md b/public/docs/ruby/7-modules/6-0-summary.md new file mode 100644 index 0000000..72e4c91 --- /dev/null +++ b/public/docs/ruby/7-modules/6-0-summary.md @@ -0,0 +1,14 @@ +--- +id: ruby-modules-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **モジュール**は `module` キーワードで定義され、**名前空間**と**ミックスイン**の2つの役割を持ちます。 + * 名前空間としては、`::` を使って定数やクラスをグループ化し、名前の衝突を防ぎます。 + * ミックスインとしては、`include` することでモジュールのメソッドを**インスタンスメソッド**としてクラスに追加できます。これは多重継承の代わりとなる強力な機能です。 + * `extend` は、モジュールのメソッドを**クラスメソッド**として追加します。 + * `public`, `private`, `protected` でメソッドの可視性を制御します。 + * Rubyの `private` は「レシーバを指定して呼び出せない」というユニークな制約を持ちます。 diff --git a/public/docs/ruby/7-modules/6-1-practice1.md b/public/docs/ruby/7-modules/6-1-practice1.md new file mode 100644 index 0000000..ccbfea1 --- /dev/null +++ b/public/docs/ruby/7-modules/6-1-practice1.md @@ -0,0 +1,35 @@ +--- +id: ruby-modules-practice1 +title: '練習問題1: カウンター機能のミックスイン' +level: 3 +--- + +### 練習問題1: カウンター機能のミックスイン + +`Enumerable` モジュール(第6章で少し触れました)のように、`include` したクラスに便利な機能を追加するモジュールを作成します。 + +1. `Counter` というモジュールを定義してください。 +2. `Counter` モジュールは `count_items(item_to_find)` というメソッドを持つものとします。 +3. このメソッドは、`include` したクラスが `items` という名前の配列(`Array`)を返すインスタンスメソッドを持っていることを前提とします。 +4. `count_items` は、その `items` 配列内に `item_to_find` がいくつ含まれているかを返します。 +5. `ShoppingCart` クラスと `WordList` クラスを作成し、両方で `items` メソッドを実装し、`Counter` モジュールを `include` して `count_items` が動作することを確認してください。 + +```ruby:practice8_1.rb +module Counter + +end + +class ShoppingCart + +end + +class WordList + +end + + + +``` + +```ruby-exec:practice8_1.rb +``` diff --git a/public/docs/ruby/7-modules/6-2-practice2.md b/public/docs/ruby/7-modules/6-2-practice2.md new file mode 100644 index 0000000..74c71e6 --- /dev/null +++ b/public/docs/ruby/7-modules/6-2-practice2.md @@ -0,0 +1,26 @@ +--- +id: ruby-modules-practice2 +title: '練習問題2: protected を使った比較' +level: 3 +--- + +### 練習問題2: protected を使った比較 + +`protected` のユースケースを理解するための問題です。 + +1. `Score` クラスを作成します。`initialize` で `@value` (得点)をインスタンス変数として保持します。 +2. `higher_than?(other_score)` という `public` なインスタンスメソッドを定義してください。これは、`other_score` (`Score` の別のインスタンス)より自分の `@value` が高ければ `true` を返します。 +3. `higher_than?` メソッドの実装のために、`value` という `protected` メソッドを作成し、`@value` を返すようにしてください。 +4. `higher_than?` の内部では、`self.value > other_score.value` のように `protected` メソッドを呼び出してください。 +5. 2つの `Score` インスタンスを作成し、`higher_than?` が正しく動作することを確認してください。また、`protected` メソッドである `value` をインスタンスの外部から直接呼び出そうとするとエラーになることも示してください。 + +```ruby:practice8_2.rb +class Score + +end + + +``` + +```ruby-exec:practice8_2.rb +``` diff --git a/public/docs/ruby/8-proc-lambda/-intro.md b/public/docs/ruby/8-proc-lambda/-intro.md new file mode 100644 index 0000000..c99e360 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/-intro.md @@ -0,0 +1,3 @@ +これまでの章で、Rubyの強力な機能である「ブロック」を `each` や `map` などのメソッドと共に使ってきました。しかし、ブロックは常にメソッド呼び出しに付随する形でしか使えませんでした。 + +この章では、そのブロックを「オブジェクト」として扱い、変数に代入したり、メソッドの引数として自由に受け渡したりする方法を学びます。これにより、Rubyの表現力はさらに向上します。 diff --git a/public/docs/ruby/8-proc-lambda/1-0-proc.md b/public/docs/ruby/8-proc-lambda/1-0-proc.md new file mode 100644 index 0000000..4e7db4c --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/1-0-proc.md @@ -0,0 +1,31 @@ +--- +id: ruby-proc +title: 'ブロックをオブジェクトとして扱う: Proc クラス' +level: 2 +--- + +## ブロックをオブジェクトとして扱う: `Proc` クラス + +ブロックは、それ自体ではオブジェクトではありません。しかし、Rubyにはブロックをオブジェクト化するための `Proc` クラスが用意されています。 + +`Proc.new` にブロックを渡すことで、`Proc` オブジェクトを作成できます。 + +作成した `Proc` オブジェクトは、`call` メソッドを使って実行できます。 + +```ruby-repl +irb(main):001:0> greeter = Proc.new { |name| puts "Hello, #{name}!" } +irb(main):002:0> greeter +=> # +irb(main):003:0> greeter.call("Alice") +Hello, Alice! +=> nil +irb(main):004:0> greeter.call("Bob") +Hello, Bob! +=> nil +``` + +`proc` という `Proc.new` のエイリアスメソッドもよく使われます。 + +```ruby +greeter = proc { |name| puts "Hello, #{name}!" } +``` diff --git a/public/docs/ruby/8-proc-lambda/2-0-proc-new-lambda.md b/public/docs/ruby/8-proc-lambda/2-0-proc-new-lambda.md new file mode 100644 index 0000000..082a148 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/2-0-proc-new-lambda.md @@ -0,0 +1,23 @@ +--- +id: ruby-proc-new-lambda +title: Proc.new と lambda の違い +level: 2 +--- + +## `Proc.new` と `lambda` の違い + +`Proc` オブジェクトを作成するもう一つの方法として `lambda` があります。(`->` というリテラル構文もよく使われます) + +```ruby-repl +irb(main):007:0> adder_lambda = lambda { |a, b| a + b } +=> # +irb(main):008:0> adder_lambda.call(3, 4) +=> 7 + +irb(main):009:0> subtractor_lambda = ->(x, y) { x - y } +=> # +irb(main):010:0> subtractor_lambda.call(10, 3) +=> 7 +``` + +`lambda` で作成されたオブジェクトも `Proc` クラスのインスタンスですが、`Proc.new` (または `proc`) で作成されたものとは、主に以下の2点で挙動が異なります。 diff --git a/public/docs/ruby/8-proc-lambda/2-1-lambda-return.md b/public/docs/ruby/8-proc-lambda/2-1-lambda-return.md new file mode 100644 index 0000000..c7b5c5b --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/2-1-lambda-return.md @@ -0,0 +1,65 @@ +--- +id: ruby-proc-lambda-return +title: 1. return の挙動 +level: 3 +--- + +### 1\. `return` の挙動 + + * **Proc.new (proc)**: `return` は、Procが定義されたスコープ(通常はメソッド)からリターンします(**ローカルリターン**)。 + * **lambda**: `return` は、`lambda` ブロックの実行からリターンするだけです(**Procからのリターン**)。 + +これは、メソッド内で `Proc` オブジェクトを定義して実行すると、その違いが明確になります。 + +**Proc.new の例:** + +```ruby:proc_return_example.rb +def proc_return_test + # Proc.new で Proc オブジェクトを作成 + my_proc = Proc.new do + puts "Proc: Inside proc" + return "Proc: Returned from proc" # メソッド全体からリターンする + end + + my_proc.call # Proc を実行 + puts "Proc: After proc.call (This will not be printed)" + return "Proc: Returned from method" +end + +puts proc_return_test +``` + +```ruby-exec:proc_return_example.rb +Proc: Inside proc +Proc: Returned from proc +``` + +`proc_return_test` メソッド内の `my_proc.call` が実行された時点で、Proc内の `return` が呼ばれ、メソッド自体が終了していることがわかります。 + +**lambda の例:** + +```ruby:lambda_return_example.rb +def lambda_return_test + # lambda で Proc オブジェクトを作成 + my_lambda = lambda do + puts "Lambda: Inside lambda" + return "Lambda: Returned from lambda" # lambda からリターンするだけ + end + + result = my_lambda.call # lambda を実行 + puts "Lambda: After lambda.call" + puts "Lambda: Result from lambda: #{result}" + return "Lambda: Returned from method" +end + +puts lambda_return_test +``` + +```ruby-exec:lambda_return_example.rb +Lambda: Inside lambda +Lambda: After lambda.call +Lambda: Result from lambda: Lambda: Returned from lambda +Lambda: Returned from method +``` + +`lambda` の場合、`my_lambda.call` 内の `return` は `lambda` の実行を終了させ、その戻り値が `result` 変数に代入されます。メソッドの実行は継続します。 diff --git a/public/docs/ruby/8-proc-lambda/2-2-args.md b/public/docs/ruby/8-proc-lambda/2-2-args.md new file mode 100644 index 0000000..efc0562 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/2-2-args.md @@ -0,0 +1,37 @@ +--- +id: ruby-proc-lambda-args +title: 2. 引数の厳密さ +level: 3 +--- + +### 2\. 引数の厳密さ + + * **Proc.new (proc)**: 引数の数に寛容です。足りない引数は `nil` になり、余分な引数は無視されます。 + * **lambda**: 引数の数を厳密にチェックします。過不足があると `ArgumentError` が発生します。 + +```ruby-repl +irb(main):001:0> # Proc.newの例: +=> nil +irb(main):001:0> my_proc = proc { |a, b| puts "a: #{a.inspect}, b: #{b.inspect}" } +=> # +irb(main):002:0> my_proc.call(1) # 引数が足りない +a: 1, b: nil +=> nil +irb(main):003:0> my_proc.call(1, 2, 3) # 引数が多い +a: 1, b: 2 +=> nil +irb(main):001:0> # Lambdaの例: +=> nil +irb(main):004:0> my_lambda = lambda { |a, b| puts "a: #{a.inspect}, b: #{b.inspect}" } +=> # +irb(main):005:0> my_lambda.call(1) # 引数が足りない +(irb):5:in `block in
      ': wrong number of arguments (given 1, expected 2) (ArgumentError) + from (irb):5:in `call' + from (irb):5:in `
      ' +irb(main):006:0> my_lambda.call(1, 2, 3) # 引数が多い +(irb):6:in `block in
      ': wrong number of arguments (given 3, expected 2) (ArgumentError) + from (irb):6:in `call' + from (irb):6:in `
      ' +``` + +一般的に、`lambda` の方が通常のメソッド定義に近い(引数が厳密で、`return` がブロックから抜けるだけ)挙動をするため、使い分けが重要です。 diff --git a/public/docs/ruby/8-proc-lambda/3-0-amp-operator.md b/public/docs/ruby/8-proc-lambda/3-0-amp-operator.md new file mode 100644 index 0000000..cbb4713 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/3-0-amp-operator.md @@ -0,0 +1,9 @@ +--- +id: ruby-proc-lambda-amp-operator +title: '& 演算子の役割' +level: 2 +--- + +## `&` 演算子の役割 + +`&` 演算子は、ブロックと `Proc` オブジェクトを相互に変換する役割を果たします。 diff --git a/public/docs/ruby/8-proc-lambda/3-1-block-as-proc.md b/public/docs/ruby/8-proc-lambda/3-1-block-as-proc.md new file mode 100644 index 0000000..032f05e --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/3-1-block-as-proc.md @@ -0,0 +1,36 @@ +--- +id: ruby-proc-lambda-block-as-proc +title: 1. ブロックを Proc として受け取る +level: 3 +--- + +### 1\. ブロックを `Proc` として受け取る + +メソッド定義の最後の引数に `&` をつけて引数名(慣習的に `block`)を指定すると、そのメソッド呼び出し時に渡されたブロックが `Proc` オブジェクトに変換され、その変数に束縛されます。 + +```ruby:block_receiver.rb +# &block でブロックを受け取り、Proc オブジェクトとして扱う +def custom_iterator(items, &block) + puts "Got a Proc object: #{block.inspect}" + + # Proc オブジェクトを call で実行 + items.each do |item| + block.call(item.upcase) # Proc を実行 + end +end + +fruits = ["apple", "banana"] + +# ブロックを渡してメソッドを呼び出す +custom_iterator(fruits) do |fruit| + puts "Processing: #{fruit}" +end +``` + +```ruby-exec:block_receiver.rb +Got a Proc object: # +Processing: APPLE +Processing: BANANA +``` + +これにより、受け取ったブロック(`Proc`)を、メソッド内で好きなタイミングで実行したり、他のメソッドに渡したりすることが可能になります。 diff --git a/public/docs/ruby/8-proc-lambda/3-2-proc-as-block.md b/public/docs/ruby/8-proc-lambda/3-2-proc-as-block.md new file mode 100644 index 0000000..4477a25 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/3-2-proc-as-block.md @@ -0,0 +1,28 @@ +--- +id: ruby-proc-lambda-proc-as-block +title: 2. Proc をブロックとして渡す +level: 3 +--- + +### 2\. `Proc` をブロックとして渡す + +逆に、メソッドを呼び出す際に、`Proc` オブジェクトを `&` 付きで渡すと、その `Proc` オブジェクトがブロックとしてメソッドに渡されます。 + +`Array#map` メソッドは通常ブロックを受け取りますが、`Proc` オブジェクトを `&` を使って渡すことができます。 + +```ruby-repl +irb(main):001:0> numbers = [1, 2, 3, 4, 5] +=> [1, 2, 3, 4, 5] + +irb(main):002:0> # 2倍にする Proc オブジェクト +irb(main):003:0> doubler = proc { |n| n * 2 } +=> # + +irb(main):004:0> # & を使って Proc をブロックとして map メソッドに渡す +irb(main):005:0> numbers.map(&doubler) +=> [2, 4, 6, 8, 10] +irb(main):006:0> numbers.map { |n| n * 2 } # これはこのようにブロックを渡すのと等価です。 +=> [2, 4, 6, 8, 10] +``` + +`&` は、`Proc` とブロック(メソッド呼び出しに付随するコード)の間の架け橋となる重要な演算子です。 diff --git a/public/docs/ruby/8-proc-lambda/4-0-closure.md b/public/docs/ruby/8-proc-lambda/4-0-closure.md new file mode 100644 index 0000000..fe6da0a --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/4-0-closure.md @@ -0,0 +1,63 @@ +--- +id: ruby-proc-lambda-closure +title: クロージャ(スコープ)の概念 +level: 2 +--- + +## クロージャ(スコープ)の概念 + +`Proc` オブジェクト(`lambda` も含む)の非常に重要な特性として、**クロージャ (Closure)** があります。 + +クロージャとは、**Proc オブジェクトが、それが定義された時点のスコープ(環境)を記憶し、後で実行される際にもそのスコープ内の変数(ローカル変数など)にアクセスできる**仕組みです。 + +JavaScriptなど、他の言語でクロージャに触れたことがあるかもしれません。Rubyの `Proc` も同様の機能を提供します。 + +```ruby:closure_example.rb +def counter_generator(initial_value) + count = initial_value + + # この lambda は、外側のスコープにある `count` 変数を記憶する + incrementer = lambda do + puts "Current count: #{count}" + count += 1 # 記憶した変数を更新 + puts "New count: #{count}" + end + + return incrementer # Proc オブジェクトを返す +end + +# counter_generator メソッドの実行は終了し、 +# 本来ローカル変数 `count` は消えるはず... +counter1 = counter_generator(10) + +puts "--- First call ---" +counter1.call # => Current count: 10, New count: 11 + +puts "--- Second call ---" +counter1.call # => Current count: 11, New count: 12 + +# 別のスコープを持つカウンターを作成 +counter2 = counter_generator(100) +puts "--- Counter 2 call ---" +counter2.call # => Current count: 100, New count: 101 + +puts "--- Counter 1 call again ---" +counter1.call # => Current count: 12, New count: 13 +``` + +```ruby-exec:closure_example.rb +--- First call --- +Current count: 10 +New count: 11 +--- Second call --- +Current count: 11 +New count: 12 +--- Counter 2 call --- +Current count: 100 +New count: 101 +--- Counter 1 call again --- +Current count: 12 +New count: 13 +``` + +`counter_generator` メソッドが終了した後でも、返された `lambda` オブジェクト(`counter1` や `counter2`)は、それぞれが定義された時点の `count` 変数を保持し続け、`call` されるたびにそれを更新できます。これがクロージャの力です。 diff --git a/public/docs/ruby/8-proc-lambda/5-0-summary.md b/public/docs/ruby/8-proc-lambda/5-0-summary.md new file mode 100644 index 0000000..65cb462 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/5-0-summary.md @@ -0,0 +1,15 @@ +--- +id: ruby-proc-lambda-summary +title: ☕ この章のまとめ +level: 2 +--- + +## ☕ この章のまとめ + + * **Proc**: ブロックをオブジェクト化したもので、`Proc.new` や `proc` で作成できます。 + * **Lambda**: `lambda` または `->` で作成できる `Proc` オブジェクトの一種です。 + * **Proc と Lambda の違い**: + * **return**: `proc` はローカルリターン、`lambda` はProcからのリターン。 + * **引数**: `proc` は寛容、`lambda` は厳密。 + * **& 演算子**: メソッド定義で使うとブロックを `Proc` として受け取り、メソッド呼び出しで使うと `Proc` をブロックとして渡します。 + * **クロージャ**: `Proc` や `lambda` は、定義された時点のスコープ(ローカル変数など)を記憶し、後からでもアクセスできます。 diff --git a/public/docs/ruby/8-proc-lambda/5-1-practice1.md b/public/docs/ruby/8-proc-lambda/5-1-practice1.md new file mode 100644 index 0000000..7290722 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/5-1-practice1.md @@ -0,0 +1,19 @@ +--- +id: ruby-proc-lambda-practice1 +title: '練習問題1: Lambda の作成' +level: 3 +--- + +### 練習問題1: Lambda の作成 + +引数を2つ取り、その和を返す `lambda` を作成し、`adder` という変数に代入してください。その後、`adder.call(5, 7)` を実行して `12` が返ってくることを確認してください。 + +```ruby:practice9_1.rb +adder = + +puts adder.call(5, 7) +``` + +```ruby-exec:practice9_1.rb +12 +``` diff --git a/public/docs/ruby/8-proc-lambda/5-2-practice2.md b/public/docs/ruby/8-proc-lambda/5-2-practice2.md new file mode 100644 index 0000000..4b300d8 --- /dev/null +++ b/public/docs/ruby/8-proc-lambda/5-2-practice2.md @@ -0,0 +1,16 @@ +--- +id: ruby-proc-lambda-practice2 +title: '練習問題2: & を使ったメソッド' +level: 3 +--- + +### 練習問題2: & を使ったメソッド +数値の配列 `numbers` と、`Proc` オブジェクト `processor` を引数として受け取る `apply_proc_to_array` メソッドを定義してください。メソッド内では、配列の各要素に対して `processor` を実行し、その結果を標準出力に出力するようにしてください。 +(ヒント: メソッド呼び出し側では `&` を使って `Proc` をブロックとして渡すか、メソッド定義側で `&` を使ってブロックを受け取るか、両方の方法が考えられます。ここでは `Proc` オブジェクトをそのまま引数として受け取り、`call` で実行してみてください。) + +```ruby:practice9_2.rb + +``` + +```ruby-exec:practice9_2.rb +``` diff --git a/public/docs/ruby/9-stdlib/-intro.md b/public/docs/ruby/9-stdlib/-intro.md new file mode 100644 index 0000000..54016cf --- /dev/null +++ b/public/docs/ruby/9-stdlib/-intro.md @@ -0,0 +1,5 @@ +Rubyの強力な点の一つは、多くの一般的なタスクを処理するための豊富な「標準ライブラリ」が同梱されていることです。これらは "batteries included"(電池付属)とよく表現されます。 + +他の言語で `import` や `include` を使うのと同様に、Rubyでは `require` を使ってこれらのライブラリをロードします。ただし、`File` や `Time`、`Regexp` のようなコア機能の多くは、`require` なしで利用可能です。 + +この章では、特に使用頻度の高い標準ライブラリの機能を見ていきます。 diff --git a/public/docs/ruby/9-stdlib/1-0-filesystem.md b/public/docs/ruby/9-stdlib/1-0-filesystem.md new file mode 100644 index 0000000..4dd6b07 --- /dev/null +++ b/public/docs/ruby/9-stdlib/1-0-filesystem.md @@ -0,0 +1,9 @@ +--- +id: ruby-stdlib-filesystem +title: ファイル操作 (File, Dir, Pathname) +level: 2 +--- + +## ファイル操作 (`File`, `Dir`, `Pathname`) + +ファイルシステムとのやり取りは、多くのアプリケーションで不可欠です。 diff --git a/public/docs/ruby/9-stdlib/1-1-file-read-write.md b/public/docs/ruby/9-stdlib/1-1-file-read-write.md new file mode 100644 index 0000000..47ca86f --- /dev/null +++ b/public/docs/ruby/9-stdlib/1-1-file-read-write.md @@ -0,0 +1,63 @@ +--- +id: ruby-stdlib-file-read-write +title: Fileクラスによる読み書き +level: 3 +--- + +### `File`クラスによる読み書き + +`File` クラスは、ファイルに対する基本的な読み書き操作を提供します。 + +**ファイルの書き込みと追記:** + +```ruby:file_io_example.rb +# 1. ファイルへの書き込み (上書き) +# シンプルな方法は File.write です +File.write('sample.txt', "Hello, Ruby Standard Library!\n") + +# 2. ファイルへの追記 +# mode: 'a' (append) オプションを指定します +File.write('sample.txt', "This is a second line.\n", mode: 'a') + +puts "File 'sample.txt' created and updated." +``` + +```ruby-exec:file_io_example.rb +File 'sample.txt' created and updated. +``` + +```text-readonly:sample.txt +``` + +**ファイルの読み込み:** + +```ruby:file_read_example.rb +# 'sample.txt' が存在すると仮定 + +# 1. ファイル全体を一度に読み込む +content = File.read('sample.txt') +puts "--- Reading all at once ---" +puts content + +# 2. 1行ずつ処理する (大きなファイルに効率的) +puts "\n--- Reading line by line ---" +File.foreach('sample.txt') do |line| + print "Line: #{line}" +end + +# 3. 処理後にファイルをクリーンアップ +File.delete('sample.txt') +puts "\n\nFile 'sample.txt' deleted." +``` + +```ruby-exec:file_read_example.rb +--- Reading all at once --- +Hello, Ruby Standard Library! +This is a second line. + +--- Reading line by line --- +Line: Hello, Ruby Standard Library! +Line: This is a second line. + +File 'sample.txt' deleted. +``` diff --git a/public/docs/ruby/9-stdlib/1-2-dir-pathname.md b/public/docs/ruby/9-stdlib/1-2-dir-pathname.md new file mode 100644 index 0000000..38c466c --- /dev/null +++ b/public/docs/ruby/9-stdlib/1-2-dir-pathname.md @@ -0,0 +1,43 @@ +--- +id: ruby-stdlib-dir-pathname +title: DirクラスとPathname +level: 3 +--- + +### `Dir`クラスと`Pathname` + +`Dir` クラスはディレクトリの内容を操作するために使われます。特に `Dir.glob` はワイルドカードを使ってファイルやディレクトリを検索するのに便利です。 + +しかし、パスの連結や解析を文字列として扱うのは面倒です。`Pathname` ライブラリは、パスをオブジェクトとして扱うための優れたインターフェースを提供します。 + +```ruby-repl +irb(main):001:0> # Dir.glob はワイルドカードでファイルリストを取得できます +irb(main):002:0> Dir.glob('*.rb') # (irbを実行しているディレクトリによります) +=> [] + +irb(main):003:0> # Pathname を使うには require が必要 +irb(main):004:0> require 'pathname' +=> true + +irb(main):005:0> # 文字列の代わりに Pathname オブジェクトを作成 +irb(main):006:0> base_path = Pathname.new('/usr/local') +=> # + +irb(main):007:0> # + や / 演算子で安全にパスを連結できます +irb(main):008:0> lib_path = base_path + 'lib' +=> # +irb(main):009:0> bin_path = base_path / 'bin' +=> # + +irb(main):010:0> # パスの解析 +irb(main):011:0> file_path = Pathname.new('/var/log/app.log') +=> # +irb(main):012:0> file_path.basename # ファイル名 +=> # +irb(main):013:0> file_path.extname # 拡張子 +=> ".log" +irb(main):014:0> file_path.dirname # 親ディレクトリ +=> # +irb(main):015:0> file_path.absolute? # 絶対パスか? +=> true +``` diff --git a/public/docs/ruby/9-stdlib/2-0-time-date.md b/public/docs/ruby/9-stdlib/2-0-time-date.md new file mode 100644 index 0000000..a068f84 --- /dev/null +++ b/public/docs/ruby/9-stdlib/2-0-time-date.md @@ -0,0 +1,42 @@ +--- +id: ruby-stdlib-time-date +title: 日付と時刻 (Time, Date) +level: 2 +--- + +## 日付と時刻 (`Time`, `Date`) + +Rubyには `Time`(組み込み)と `Date`(要 `require`)の2つの主要な日時クラスがあります。 + + * **Time:** 時刻(タイムスタンプ)をナノ秒までの精度で扱います。 + * **Date:** 日付(年月日)のみを扱い、カレンダー計算に特化しています。 + +```ruby-repl +irb(main):001:0> # Time (組み込み) +irb(main):002:0> now = Time.now +=> 2025-11-04 11:32:00 +0900 (JST) +irb(main):003:0> now.year +=> 2025 +irb(main):004:0> now.monday? +=> false +irb(main):005:0> now.to_i # UNIXタイムスタンプ +=> 1762309920 + +irb(main):006:0> # strftime (string format time) でフォーマット +irb(main):007:0> now.strftime("%Y-%m-%d %H:%M:%S") +=> "2025-11-04 11:32:00" + +irb(main):008:0> # Date (require が必要) +irb(main):009:0> require 'date' +=> true +irb(main):010:0> today = Date.today +=> # +irb(main):011:0> today.strftime("%A") # 曜日 +=> "Tuesday" + +irb(main):012:0> # 文字列からのパース +irb(main):013:0> christmas = Date.parse("2025-12-25") +=> # +irb(main):014:0> (christmas - today).to_i # あと何日? +=> 51 +``` diff --git a/public/docs/ruby/9-stdlib/3-0-json.md b/public/docs/ruby/9-stdlib/3-0-json.md new file mode 100644 index 0000000..7465d51 --- /dev/null +++ b/public/docs/ruby/9-stdlib/3-0-json.md @@ -0,0 +1,47 @@ +--- +id: ruby-stdlib-json +title: JSONのパースと生成 (json) +level: 2 +--- + +## JSONのパースと生成 (`json`) + +現代のWeb開発においてJSONの扱いは不可欠です。`json` ライブラリは、JSON文字列とRubyのHash/Arrayを相互に変換する機能を提供します。 + +```ruby-repl +irb(main):001:0> require 'json' +=> true + +irb(main):002:0> # 1. JSON文字列 -> Rubyオブジェクト (Hash) へのパース +irb(main):003:0> json_data = '{"user_id": 123, "name": "Alice", "tags": ["admin", "ruby"]}' +=> "{\"user_id\": 123, \"name\": \"Alice\", \"tags\": [\"admin\", \"ruby\"]}" + +irb(main):004:0> parsed_data = JSON.parse(json_data) +=> {"user_id"=>123, "name"=>"Alice", "tags"=>["admin", "ruby"]} +irb(main):005:0> parsed_data['name'] +=> "Alice" +irb(main):006:0> parsed_data['tags'] +=> ["admin", "ruby"] + +irb(main):007:0> # 2. Rubyオブジェクト (Hash) -> JSON文字列 への生成 +irb(main):008:0> ruby_hash = { +irb(main):009:1* status: "ok", +irb(main):010:1* data: { item_id: 987, price: 1500 } +irb(main):011:1* } +=> {:status=>"ok", :data=>{:item_id=>987, :price=>1500}} + +irb(main):012:0> # .to_json メソッドが便利です +irb(main):013:0> json_output = ruby_hash.to_json +=> "{\"status\":\"ok\",\"data\":{\"item_id\":987,\"price\":1500}}" + +irb(main):014:0> # 人が読みやすいように整形 (pretty generate) +irb(main):015:0> puts JSON.pretty_generate(ruby_hash) +{ + "status": "ok", + "data": { + "item_id": 987, + "price": 1500 + } +} +=> nil +``` diff --git a/public/docs/ruby/9-stdlib/4-0-regexp.md b/public/docs/ruby/9-stdlib/4-0-regexp.md new file mode 100644 index 0000000..11bb25c --- /dev/null +++ b/public/docs/ruby/9-stdlib/4-0-regexp.md @@ -0,0 +1,9 @@ +--- +id: ruby-stdlib-regexp +title: 正規表現 (Regexp) +level: 2 +--- + +## 正規表現 (`Regexp`) + +Rubyの正規表現 (Regexp) は、Perl互換の強力なパターンマッチング機能を提供します。`/pattern/` リテラルで記述するのが一般的です。 diff --git a/public/docs/ruby/9-stdlib/4-1-match.md b/public/docs/ruby/9-stdlib/4-1-match.md new file mode 100644 index 0000000..ca76fdb --- /dev/null +++ b/public/docs/ruby/9-stdlib/4-1-match.md @@ -0,0 +1,34 @@ +--- +id: ruby-stdlib-match +title: マッチの確認 (=~ と match) +level: 3 +--- + +### マッチの確認 (`=~` と `match`) + + * `=~` 演算子: マッチした位置のインデックス(0から始まる)を返すか、マッチしなければ `nil` を返します。 + * `String#match`: `MatchData` オブジェクトを返すか、マッチしなければ `nil` を返します。`MatchData` は、キャプチャグループ(`()`で囲んだ部分)へのアクセスに便利です。 + +```ruby-repl +irb(main):001:0> text = "User: alice@example.com (Alice Smith)" +=> "User: alice@example.com (Alice Smith)" + +irb(main):002:0> # =~ は位置を返す +irb(main):003:0> text =~ /alice/ +=> 6 +irb(main):004:0> text =~ /bob/ +=> nil + +irb(main):005:0> # String#match は MatchData を返す +irb(main):006:0> # パターン: (ユーザー名)@(ドメイン) +irb(main):007:0> match_data = text.match(/(\w+)@([\w\.]+)/) +=> # + +irb(main):008:0> # マッチしたオブジェクトからキャプチャを取得 +irb(main):009:0> match_data[0] # マッチ全体 +=> "alice@example.com" +irb(main):010:0> match_data[1] # 1番目の () +=> "alice" +irb(main):011:0> match_data[2] # 2番目の () +=> "example.com" +``` diff --git a/public/docs/ruby/9-stdlib/4-2-scan-gsub.md b/public/docs/ruby/9-stdlib/4-2-scan-gsub.md new file mode 100644 index 0000000..4db00d5 --- /dev/null +++ b/public/docs/ruby/9-stdlib/4-2-scan-gsub.md @@ -0,0 +1,31 @@ +--- +id: ruby-stdlib-scan-gsub +title: 検索と置換 (scan と gsub) +level: 3 +--- + +### 検索と置換 (`scan` と `gsub`) + + * `String#scan`: マッチするすべての部分文字列を(キャプチャグループがあればその配列として)返します。 + * `String#gsub`: マッチするすべての部分を置換します (Global SUBstitute)。 + +```ruby-repl +irb(main):001:0> log = "ERROR: code 500. WARNING: code 404. ERROR: code 403." +=> "ERROR: code 500. WARNING: code 404. ERROR: code 403." + +irb(main):002:0> # scan: 'ERROR: code (数字)' にマッチする部分をすべて探す +irb(main):003:0> log.scan(/ERROR: code (\d+)/) +=> [["500"], ["403"]] + +irb(main):004:0> # gsub: 'ERROR' を 'CRITICAL' に置換する +irb(main):005:0> log.gsub("ERROR", "CRITICAL") +=> "CRITICAL: code 500. WARNING: code 404. CRITICAL: code 403." + +irb(main):006:0> # gsub はブロックと正規表現を組み合わせて高度な置換が可能 +irb(main):007:0> # 数字(コード)を [] で囲む +irb(main):008:0> log.gsub(/code (\d+)/) do |match| +irb(main):009:1* # $1 は最後のマッチの1番目のキャプチャグループ +irb(main):010:1* "code [#{$1}]" +irb(main):011:1> end +=> "ERROR: code [500]. WARNING: code [404]. ERROR: code [403]." +``` diff --git a/public/docs/ruby/9-stdlib/5-0-summary.md b/public/docs/ruby/9-stdlib/5-0-summary.md new file mode 100644 index 0000000..5a15483 --- /dev/null +++ b/public/docs/ruby/9-stdlib/5-0-summary.md @@ -0,0 +1,15 @@ +--- +id: ruby-stdlib-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * Rubyには、`require` でロードできる豊富な**標準ライブラリ**が付属しています。 + * **File**クラスはファイルの読み書きを、**Pathname**はパス操作をオブジェクト指向的に行います。 + * **Time**は時刻を、**Date**は日付を扱います。`strftime` でフォーマットできます。 + * **json**ライブラリは `JSON.parse`(文字列→Hash)と `to_json`(Hash→文字列)を提供します。 + * **Regexp**(`/pattern/`)はパターンマッチングに使います。`String#match` で `MatchData` を取得し、`scan` や `gsub` で検索・置換を行います。 + +これらは標準ライブラリのごく一部です。他にもCSVの処理 (`csv`)、HTTP通信 (`net/http`)、テスト (`minitest`) など、多くの機能が提供されています。 diff --git a/public/docs/ruby/9-stdlib/5-1-practice1.md b/public/docs/ruby/9-stdlib/5-1-practice1.md new file mode 100644 index 0000000..4de6317 --- /dev/null +++ b/public/docs/ruby/9-stdlib/5-1-practice1.md @@ -0,0 +1,24 @@ +--- +id: ruby-stdlib-practice1 +title: '練習問題1: JSON設定ファイルの読み書き' +level: 3 +--- + +### 練習問題1: JSON設定ファイルの読み書き + +1. `config.json` ファイルを読み込み、内容をJSONパースしてRubyのHashに変換してください。 +2. そのHashの `logging` の値を `true` に変更し、さらに `:updated_at` というキーで現在時刻(文字列)を追加してください。 +3. 変更後のHashをJSON文字列に変換し、`config_updated.json` という名前でファイルに保存してください。(読みやすさのために `JSON.pretty_generate` を使っても構いません) + +```json-readonly:config.json +{"app_name": "RubyApp", "version": "1.0", "logging": false} +``` + +```ruby:practice10_1.rb +``` + +```ruby-exec:practice10_1.rb +``` + +```json-readonly:config_updated.json +``` diff --git a/public/docs/ruby/9-stdlib/5-2-practice2.md b/public/docs/ruby/9-stdlib/5-2-practice2.md new file mode 100644 index 0000000..7d54cfa --- /dev/null +++ b/public/docs/ruby/9-stdlib/5-2-practice2.md @@ -0,0 +1,23 @@ +--- +id: ruby-stdlib-practice2 +title: '練習問題2: ログファイルからの情報抽出' +level: 3 +--- + +### 練習問題2: ログファイルからの情報抽出 + +1. `system.log` というファイルを1行ずつ読み込みます。 +2. 正規表現を使い、`[INFO]` で始まり、かつ `logged in` という文字列を含む行だけを検出してください。 +3. マッチした行から、IPアドレス(`192.168.1.10` のような形式)を正規表現のキャプチャグループを使って抽出し、IPアドレスだけをコンソールに出力してください。 + +```text-readonly:system.log +[INFO] 2025-11-04 User 'admin' logged in from 192.168.1.10 +[WARN] 2025-11-04 Failed login attempt for user 'guest' +[INFO] 2025-11-04 Service 'payment_gateway' started. +``` + +```ruby:practice10_2.rb +``` + +```ruby-exec:practice10_2.rb +``` diff --git a/public/docs/ruby/index.yml b/public/docs/ruby/index.yml new file mode 100644 index 0000000..8c2e3a5 --- /dev/null +++ b/public/docs/ruby/index.yml @@ -0,0 +1,39 @@ +name: Ruby +description: hoge +pages: +- slug: 0-intro + name: rubyの世界へようこそ + title: Rubyの世界へようこそ - 環境構築と第一歩 +- slug: 1-basics + name: 基本構文とデータ型 + title: 基本構文とデータ型 - Rubyの「書き方」 +- slug: 2-control-methods + name: 制御構造とメソッド定義 + title: 制御構造とメソッド定義 +- slug: 3-everything-object + name: すべてがオブジェクト + title: すべてがオブジェクト +- slug: 4-collections + name: コレクション (Array, Hash, Range) + title: コレクション (Array, Hash, Range) +- slug: 5-blocks-iterators + name: ブロックとイテレータ + title: ブロックとイテレータ - Rubyの最重要機能 +- slug: 6-classes + name: クラスとオブジェクト + title: クラスとオブジェクト(基本) +- slug: 7-modules + name: モジュールとMix-in + title: モジュールとミックスイン(オブジェクト指向の拡張) +- slug: 8-proc-lambda + name: Proc, Lambda, クロージャ + title: Proc, Lambda, クロージャ +- slug: 9-stdlib + name: 標準ライブラリの活用 + title: 標準ライブラリの活用 +- slug: 10-testing + name: テスト文化入門 + title: テスト文化入門 - Minitest +- slug: 11-metaprogramming + name: メタプログラミング入門 + title: メタプログラミング入門 diff --git a/public/docs/rust-1.md b/public/docs/rust-1.md deleted file mode 100644 index 72ace66..0000000 --- a/public/docs/rust-1.md +++ /dev/null @@ -1,148 +0,0 @@ -# 第1章: Rustの世界へようこそ - -Rustは、Mozillaによって開始され、現在はRust Foundationによって管理されているオープンソースのシステムプログラミング言語です。Stack OverflowのDeveloper Surveyで長年「最も愛されている言語」に選ばれ続けているのには理由があります。 - -## Rustの特徴:なぜ学ぶのか? - -経験豊富なプログラマにとって、Rustは「トレードオフを解消する」言語として映るはずです。 - -### 1\. メモリ安全性(Memory Safety) - -C/C++ではプログラマの責任であったメモリ管理を、Rustは**所有権(Ownership)**というコンパイル時のシステムで保証します。 - - * ガベージコレクタ(GC)は**存在しません**。 - * ランタイムコストなしに、ダングリングポインタや二重解放、バッファオーバーフローをコンパイル段階で防ぎます。 - -### 2\. ゼロコスト抽象化(Zero-cost Abstractions) - -「使わないものにはコストを払わない。使うものについては、手書きのコード以上のコストをかけない」というC++の哲学を継承しています。イテレータや高階関数を使っても、最適化された低レベルコードと同等のパフォーマンスが得られます。 - -### 3\. 安全な並行性(Fearless Concurrency) - -多くの言語で並行処理はバグの温床(データ競合など)ですが、Rustではコンパイラがデータ競合を検知し、コンパイルエラーとして報告します。「コンパイルが通れば、並行性のバグを含んでいる可能性は低い」という安心感を持ってコーディングできます。 - -## 開発環境の構築 - -Rustの開発環境は非常にモダンで、バージョン管理やパッケージ管理が統合されています。 - -### `rustup` のインストール - -Rustのバージョンマネージャである `rustup` を使用してインストールするのが標準的です。 - -macOS / Linux / WSL (Windows Subsystem for Linux) の場合: - -```bash -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -``` - -Windowsの場合: -公式サイト(rust-lang.org)から `rustup-init.exe` をダウンロードして実行します(C++ビルドツールが必要になる場合があります)。 - -インストール後、以下のコマンドでバージョンが表示されれば成功です。 - -```bash -rustc --version -cargo --version -``` - -## Hello World (`rustc` を直接使う) - -まずは、ビルドシステムを使わずにコンパイラ `rustc` を直接叩いて、Rustプログラムの最小単位を見てみましょう。 - -以下のコードを記述します。 - -```rust:hello.rs -fn main() { - // !がついているのは関数ではなく「マクロ」の呼び出しです - println!("Hello, world from rustc!"); -} -``` - -コンパイルと実行は以下の手順で行います。 - -1. コンパイル: `rustc hello.rs` - * これにより、バイナリファイル(Windowsなら`.exe`)が生成されます。 -2. 実行: `./hello` (Windowsなら `.\hello.exe`) - - - -```rust-exec:hello.rs -Hello, world from rustc! -``` - -### ポイント - - * **`fn main() { ... }`**: エントリポイントです。引数や戻り値がない場合、このように記述します。 - * **`println!`**: 末尾に `!` がついているのは**マクロ**であることを示しています。Rustでは可変長引数を取る機能などをマクロとして実装しています。 - * **セミコロン `;`**: 文の終わりには必須です。 - -## Cargo:Rustのビルドシステムとパッケージマネージャ - -実際の開発では `rustc` を直接使うことは稀です。公式のビルドシステム兼パッケージマネージャである **Cargo** を使用します。Node.jsにおける `npm`、Pythonにおける `pip` + `venv` のような存在ですが、それ以上にプロジェクトのライフサイクル全体を管理します。 - -### プロジェクトの作成 (`cargo new`) - -新しいプロジェクトを作成します。 - -```bash -cargo new hello_cargo -cd hello_cargo -``` - -このコマンドにより、以下のディレクトリ構造が生成されます。 - - * **`Cargo.toml`**: パッケージのマニフェストファイル(依存関係やメタデータを記述)。 - * **`src/main.rs`**: ソースコード。 - * **`.gitignore`**: Gitの設定ファイルも自動生成されます。 - -### Cargoの主要コマンド - -生成されたプロジェクトで、以下のコマンドを試してみましょう。 - -1. **`cargo check`** - - * **重要**: コンパイルが可能かどうかのチェックだけを行い、実行ファイルは生成しません。高速に動作するため、コーディング中の構文チェックとして頻繁に使用します。 - -2. **`cargo build`** - - * デバッグビルドを行います。成果物は `target/debug/` に生成されます。コンパイル速度重視で、最適化は行われません。 - -3. **`cargo run`** - - * ビルドと実行を一度に行います。開発中はこれが最も使われます。 - -4. **`cargo build --release`** - - * リリースビルドを行います。`target/release/` に生成されます。コンパイル時間は長くなりますが、強力な最適化がかかり、実行速度が劇的に向上します。 - -`src/main.rs` の中身は以下のようになっています(生成時デフォルト)。 - -```rust -fn main() { - println!("Hello, world!"); -} -``` - -## フォーマッタとリンタ - -Rustは「公式のスタイル」を重視する言語です。議論の余地をなくすために強力なツールが標準で用意されています。 - -### `rustfmt` (コードフォーマッタ) - -Go言語の `gofmt` のように、コードを自動整形します。 - -```bash -cargo fmt -``` - -多くのエディタ(VS Codeなど)では保存時に自動実行されるよう設定するのが一般的です。 - -### `clippy` (リンタ) - -単なるスタイルチェックだけでなく、パフォーマンスの改善案や、Rustらしい書き方(Idiomatic Rust)を提案してくれます。 - -```bash -cargo clippy -``` - -例えば、無駄な計算や非推奨なAPIの使用などを指摘してくれるため、学習中はこのコマンドの警告に従うだけでRustの理解が深まります。 diff --git a/public/docs/rust-10.md b/public/docs/rust-10.md deleted file mode 100644 index 15bfacd..0000000 --- a/public/docs/rust-10.md +++ /dev/null @@ -1,337 +0,0 @@ -# 第10章: エラーハンドリング - -ようこそ、第10章へ。ここまでRustの所有権や型システムについて学んできましたが、この章では実用的なアプリケーション開発において避けては通れない「エラーハンドリング」について解説します。 - -他の多くの言語(Java, Python, C++など)とRustが最も大きく異なる点の一つが、**「例外(Exception)」が存在しない**ことです。 - -Rustでは、エラーは「誰かがキャッチしてくれることを祈って投げるもの」ではなく、**「戻り値として明示的に処理すべき値」**として扱われます。この設計思想により、予期せぬクラッシュを防ぎ、堅牢なソフトウェアを構築することができます。 - -## エラーの分類 - -Rustでは、エラーを大きく2つのカテゴリーに分類します。 - -1. **回復不可能なエラー (Unrecoverable Errors):** - * バグ、配列の範囲外アクセス、メモリ不足など。 - * プログラムは即座に停止すべき状況。 - * 手段: `panic!` -2. **回復可能なエラー (Recoverable Errors):** - * ファイルが見つからない、パースの失敗、ネットワーク切断など。 - * 呼び出し元で対処(リトライやエラーメッセージ表示)が可能な状況。 - * 手段: `Result` - -## 回復不可能なエラー (`panic!`) - -プログラムが続行不可能な状態に陥った場合、Rustはパニック(panic)を起こします。これはデフォルトでスタックを巻き戻し(unwind)、データを掃除してからプログラムを終了させます。 - -もっとも単純な方法は `panic!` マクロを呼ぶことです。 - -```rust:panic_demo.rs -fn main() { - println!("処理を開始します..."); - - // 何か致命的なことが起きたと仮定 - panic!("ここで致命的なエラーが発生しました!"); - - // 以下の行は実行されません - println!("この行は表示されません"); -} -``` - -```rust-exec:panic_demo.rs -処理を開始します... -thread 'main' panicked at panic_demo.rs:5:5: -ここで致命的なエラーが発生しました! -``` - -**ポイント:** - - * `panic!` は、基本的に「プログラムのバグ」や「どうしようもない状況」でのみ使用します。 - * 通常の制御フロー(入力値のバリデーション失敗など)には使用しません。 - -## 回復可能なエラー (`Result`) - -Rustのエラーハンドリングの主役は `Result` 列挙型です。以前の章で学んだ `Option` に似ていますが、失敗した場合に「なぜ失敗したか(エラー内容)」を持つ点が異なります。 - -定義は以下のようになっています(標準ライブラリに含まれています)。 - -```rust -enum Result { - Ok(T), // 成功時:値 T を含む - Err(E), // 失敗時:エラー E を含む -} -``` - -### 基本的な `Result` の処理 - -他の言語での `try-catch` の代わりに、Rustでは `match` 式を使って成功と失敗を分岐させるのが基本です。 - -```rust:result_basic.rs -fn divide(numerator: f64, denominator: f64) -> Result { - if denominator == 0.0 { - // 失敗時は Err でラップして返す - return Err(String::from("0で割ることはできません")); - } - // 成功時は Ok でラップして返す - Ok(numerator / denominator) -} - -fn main() { - let inputs = vec![(10.0, 2.0), (5.0, 0.0)]; - - for (num, den) in inputs { - let result = divide(num, den); - - match result { - Ok(val) => println!("{} / {} = {}", num, den, val), - Err(e) => println!("エラー: {}", e), - } - } -} -``` - -```rust-exec:result_basic.rs -10 / 2 = 5 -エラー: 0で割ることはできません -``` - -この明示的な分岐により、プログラマはエラー処理を「忘れる」ことができなくなります(コンパイラが `Result` を無視すると警告を出したり、使おうとすると型エラーになるため)。 - -## `unwrap` と `expect` の使い所 - -毎回 `match` で分岐を書くのは冗長な場合があります。「失敗したらプログラムをクラッシュさせていい」という場合や、「ここでは絶対に失敗しない」と確信がある場合のために、ヘルパーメソッドが用意されています。 - -### `unwrap` - -`Result` が `Ok` なら中身を返し、`Err` なら即座に `panic!` します。手っ取り早いですが、エラーメッセージは一般的で詳細が含まれません。 - -### `expect` - -`unwrap` と同じ挙動ですが、パニック時に表示するメッセージを指定できます。**デバッグのしやすさから、通常は `unwrap` よりも `expect` が推奨されます。** - -```rust:unwrap_expect.rs -fn main() { - let valid_str = "100"; - let invalid_str = "hello"; - - // 1. unwrap: 成功時は値を返す - let n: i32 = valid_str.parse().unwrap(); - println!("パース成功: {}", n); - - // 2. expect: 失敗時は指定したメッセージと共に panic! する - // 以下の行を実行するとクラッシュします - let _m: i32 = invalid_str.parse().expect("数値のパースに失敗しました"); -} -``` - -```rust-exec:unwrap_expect.rs -thread 'main' panicked at unwrap_expect.rs:11:35: -数値のパースに失敗しました: ParseIntError { kind: InvalidDigit } -note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace -``` - -**使い所のヒント:** - - * **プロトタイピング・実験:** `unwrap` を多用してロジックを素早く組む。 - * **テストコード:** テスト中に失敗したらテスト自体を落としたいので `unwrap` が有用。 - * **「絶対に失敗しない」ロジック:** 理論上エラーにならない場合でも、型合わせのために `unwrap` が必要な場合があります。 - -## エラーの伝播(`?` 演算子) - -関数内でエラーが発生した際、その場で処理せずに呼び出し元へエラーを返したいことがよくあります。これを「エラーの伝播(propagation)」と呼びます。 - -Rustにはこれを劇的に短く書くための **`?` 演算子** があります。 - - * `Result` 値の後ろに `?` を置く。 - * 値が `Ok` なら、中身を取り出して処理を続行。 - * 値が `Err` なら、**その関数から即座に `return Err(...)` する。** - - - -```rust:error_propagation.rs -use std::num::ParseIntError; - -// 文字列を受け取り、最初の文字を切り出して数値に変換し、10倍して返す -fn get_first_digit_scaled(text: &str) -> Result { - // 1. 文字列が空の場合のエラー処理 - let first_char = text.chars().next().ok_or("空の文字列です".to_string())?; - - // 2. 文字を数値にパース(失敗したらエラー伝播) - // ParseIntError を String に変換するために map_err を使用しています - // (?演算子はFromトレイトを使って自動変換を行いますが、ここでは単純化のため手動変換します) - let num: i32 = first_char.to_string() - .parse() - .map_err(|_| format!("'{}' は数値ではありません", first_char))?; - - Ok(num * 10) -} - -fn main() { - match get_first_digit_scaled("5 apples") { - Ok(v) => println!("計算結果: {}", v), - Err(e) => println!("エラー発生: {}", e), - } - - match get_first_digit_scaled("banana") { - Ok(v) => println!("計算結果: {}", v), - Err(e) => println!("エラー発生: {}", e), - } -} -``` - -```rust-exec:error_propagation.rs -計算結果: 50 -エラー発生: 'b' は数値ではありません -``` - -`?` 演算子を使うことで、`match` のネスト地獄(右方向へのドリフト)を防ぎ、コードの流れを「成功ルート」を中心に記述できます。 - -## カスタムエラー型の定義 - -ライブラリや大規模なアプリケーションを作る場合、`String` 型のエラーでは情報が不足します。Rustでは `std::error::Error` トレイトを実装した独自の型(通常は Enum)を定義するのが一般的です。 - -Rustのエコシステムでは、ボイラープレート(定型コード)を減らすために **`thiserror`** というクレートが非常に人気ですが、ここでは仕組みを理解するために標準機能だけで実装してみます。 - -```rust:custom_error.rs -use std::fmt; - -// 1. 独自のエラー型を定義 -#[derive(Debug)] -enum MyToolError { - IoError(String), - ParseError(String), - LogicError, -} - -// 2. Display トレイトの実装(ユーザー向けのエラーメッセージ) -impl fmt::Display for MyToolError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - MyToolError::IoError(msg) => write!(f, "IOエラー: {}", msg), - MyToolError::ParseError(msg) => write!(f, "解析エラー: {}", msg), - MyToolError::LogicError => write!(f, "ロジックエラーが発生しました"), - } - } -} - -// 3. Error トレイトの実装(これを実装することで、Rustのエラーエコシステムと統合される) -impl std::error::Error for MyToolError {} - -// 使用例 -fn dangerous_operation(input: i32) -> Result { - match input { - 0 => Err(MyToolError::IoError("ディスク書き込み失敗".into())), - 1 => Err(MyToolError::ParseError("不正なヘッダ".into())), - 2 => Err(MyToolError::LogicError), - _ => Ok("成功!".into()), - } -} - -fn main() { - let results = [dangerous_operation(0), dangerous_operation(3)]; - - for res in results { - match res { - Ok(s) => println!("結果: {}", s), - Err(e) => println!("失敗: {}", e), // Displayの実装が使われる - } - } -} -``` - -```rust-exec:custom_error.rs -失敗: IOエラー: ディスク書き込み失敗 -結果: 成功! -``` - -**補足:** `thiserror` クレートを使うと、上記の `impl fmt::Display` などをマクロで自動生成でき、以下のように簡潔に書けます(参考情報)。 - -```rust -// thiserrorを使った場合のイメージ -#[derive(thiserror::Error, Debug)] -enum MyToolError { - #[error("IOエラー: {0}")] - IoError(String), - // ... -} -``` - -## この章のまとめ - - * **例外はない**: Rustは戻り値 `Result` でエラーを表現する。 - * **Panic**: 回復不可能なエラーには `panic!` を使うが、乱用しない。 - * **Result処理**: 基本は `match` で処理する。 - * **便利メソッド**: `unwrap` は強制取り出し(失敗時パニック)、`expect` はメッセージ付きパニック。 - * **?演算子**: エラーが発生したら即座に呼び出し元へ `Err` を返すショートカット。 - * **型安全性**: コンパイラがエラー処理の漏れを指摘してくれるため、堅牢なコードになる。 - -### 練習問題 1: 安全な割り算 - -2つの `f64` を受け取り、割り算の結果を返す関数 `safe_div` を作成してください。 - - * 戻り値は `Result` としてください。 - * 0で割ろうとした場合は、「Division by zero」というエラーメッセージを含む `Err` を返してください。 - * `main` 関数で、正常なケースとエラーになるケースの両方を呼び出し、結果を表示してください。 - - - -```rust:practice10_1.rs -fn safe_div(a: f64, b: f64) -> Result { - // ここにコードを書いてください - -} - -fn main() { - let test_cases = vec![(10.0, 2.0), (5.0, 0.0)]; - - for (a, b) in test_cases { - match safe_div(a, b) { - Ok(result) => println!("{} / {} = {}", a, b, result), - Err(e) => println!("エラー: {}", e), - } - } -} -``` - -```rust-exec:practice10_1.rs -10 / 2 = 5 -エラー: Division by zero -``` - -### 練習問題 2: データ処理チェーン(エラー伝播) - -文字列形式の数値(例: "10,20,30")を受け取り、カンマ区切りの最初の数値を2倍にして返す関数 `process_csv_data` を作成してください。 - - * 以下の手順を行い、途中でエラーがあれば `?` 演算子などを使って伝播させてください。 - 1. 文字列をカンマ `,` で分割し( `split` メソッド)、最初の要素を取得する(要素がない場合はエラー)。 - 2. 取得した文字列を `i32` にパースする( `parse` メソッド)(パース失敗はエラー)。 - 3. 数値を2倍して返す。 - * 関数の戻り値は `Result` とします(エラー型の変換が必要な場合は `map_err` を活用してください)。 - - - -```rust:practice10_2.rs -fn process_csv_data(csv: &str) -> Result { - - -} - -fn main() { - let inputs = ["10,20,30", "abc,20", "", " ,50"]; - - for input in inputs { - print!("Input: {:<10} => ", format!("\"{}\"", input)); - match process_csv_data(input) { - Ok(n) => println!("結果: {}", n), - Err(e) => println!("エラー: {}", e), - } - } -} -``` - -```rust-exec:practice10_2.rs -Input: "10,20,30" => 結果: 20 -Input: "abc,20" => エラー: 'abc' は数値ではありません -Input: "" => エラー: 要素が空です -Input: " ,50" => エラー: 要素が空です -``` - diff --git a/public/docs/rust-11.md b/public/docs/rust-11.md deleted file mode 100644 index a9ee48e..0000000 --- a/public/docs/rust-11.md +++ /dev/null @@ -1,327 +0,0 @@ -# 第11章: ジェネリクスとトレイト - -Rustチュートリアルの第11章へようこそ。 -この章では、Rustにおける抽象化とコード再利用の核心である「ジェネリクス」と「トレイト」について解説します。 - -他のプログラミング言語での経験がある方にとって、ジェネリクスは馴染み深い概念かもしれませんが、トレイトはクラス継承とは異なるアプローチをとります。これらを理解することで、柔軟かつ高速なRustコードが書けるようになります。 - -## 他言語との違い:Rustのアプローチ - -Rustのジェネリクスとトレイトは、C++のテンプレートやHaskellの型クラスに近い性質を持っています。JavaやPythonなどのオブジェクト指向言語(OOP)出身の方が特に意識すべき違いは以下の通りです。 - -1. **継承の欠如:** Rustにはクラス継承(`extends`)がありません。代わりに**トレイト(Trait)**を使用して共通の振る舞いを定義し、構造体(Struct)や列挙型(Enum)に実装します。これは「継承よりコンポジション(構成)」を好む現代的な設計思想を言語レベルで強制するものです。 -2. **ダックタイピングとの違い:** Pythonなどの動的型付け言語では「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」というダックタイピングが一般的ですが、Rustではコンパイル時に厳密に型チェックを行います。「アヒルのように鳴く」能力があることを**トレイト境界**で明示する必要があります。 -3. **静的ディスパッチと単相化(Monomorphization):** Rustのジェネリクスは、コンパイル時に具体的な型ごとにコードを生成(展開)します。これを単相化と呼びます。 - * **メリット:** 実行時のオーバーヘッドがゼロ(C++のテンプレートと同様)。仮想関数テーブル(vtable)を参照する動的ディスパッチのようなコストがかかりません。 - * **デメリット:** バイナリサイズが若干大きくなる可能性があります。 - -## ジェネリックなデータ型と関数 - -ジェネリクスを使用すると、具体的なデータ型に依存しないコードを書くことができます。Rustでは慣習として `T` (Typeの略)などの短い大文字識別子を使用します。 - -### ジェネリックな関数 - -もっとも単純な例として、型 `T` の引数をそのまま返す関数を考えてみましょう。 - -```rust:generic_function.rs -fn inspect(value: T) { - // 実際にはここで何か処理を行うが、 - // Tが何であるか(DisplayやDebug等)を知らないと - // プリントすらできないため、ここでは単純にスコープを抜ける -} - -fn main() { - inspect(10); // i32 - inspect(3.14); // f64 - inspect("Hello"); // &str - println!("Compilation successful."); -} -``` - -```rust-exec:generic_function.rs -Compilation successful. -``` - -これだけではあまり役に立ちませんが、構文としては `fn 関数名<型パラメータ>(引数)` という形になります。 - -### ジェネリックな構造体 - -構造体のフィールドの型をジェネリックにすることも可能です。 - -```rust:generic_struct.rs -// T型のxとyを持つPoint構造体 -struct Point { - x: T, - y: T, -} - -// 異なる型を持たせたい場合は複数のパラメータを使う -struct MixedPoint { - x: T, - y: U, -} - -fn main() { - let integer = Point { x: 5, y: 10 }; - let float = Point { x: 1.0, y: 4.0 }; - - // 以下の行はコンパイルエラーになる(xとyが同じTである必要があるため) - // let error = Point { x: 5, y: 4.0 }; - - let mixed = MixedPoint { x: 5, y: 4.0 }; - - println!("Int Point: x = {}, y = {}", integer.x, integer.y); - println!("Mixed Point: x = {}, y = {}", mixed.x, mixed.y); -} -``` - -```rust-exec:generic_struct.rs -Int Point: x = 5, y = 10 -Mixed Point: x = 5, y = 4 -``` - -## トレイトの定義と実装 - -トレイトは、**「特定の型がどのような機能を持っているか」**を定義するものです。JavaやC\#の「インターフェース」に非常に近い概念です。 - -### トレイトの定義 - -ここでは、「情報を要約できる」という振る舞いを表す `Summary` トレイトを定義してみましょう。 - -```rust -pub trait Summary { - fn summarize(&self) -> String; // メソッドのシグネチャのみ定義 -} -``` - -### トレイトの実装 - -定義したトレイトを具体的な型に実装するには、`impl トレイト名 for 型名` ブロックを使用します。 - -```rust:trait_impl.rs -// トレイトの定義 -trait Summary { - fn summarize(&self) -> String; - - // デフォルト実装を持たせることも可能 - fn greeting(&self) -> String { - String::from("(Read more below...)") - } -} - -struct NewsArticle { - headline: String, - location: String, - author: String, -} - -// NewsArticleにSummaryトレイトを実装 -impl Summary for NewsArticle { - fn summarize(&self) -> String { - format!("{}, by {} ({})", self.headline, self.author, self.location) - } -} - -struct Tweet { - username: String, - content: String, -} - -// TweetにSummaryトレイトを実装 -impl Summary for Tweet { - fn summarize(&self) -> String { - format!("{}: {}", self.username, self.content) - } - // greetingはデフォルト実装を使用するため記述しない -} - -fn main() { - let article = NewsArticle { - headline: String::from("Rust 1.0 Released"), - location: String::from("Internet"), - author: String::from("Core Team"), - }; - - let tweet = Tweet { - username: String::from("horse_ebooks"), - content: String::from("of course, as you probably already know, people"), - }; - - println!("Article: {}", article.summarize()); - println!("Tweet: {} {}", tweet.summarize(), tweet.greeting()); -} -``` - -```rust-exec:trait_impl.rs -Article: Rust 1.0 Released, by Core Team (Internet) -Tweet: horse_ebooks: of course, as you probably already know, people (Read more below...) -``` - -## トレイト境界(Trait Bounds) - -ジェネリック関数を作る際、型 `T` に対して「どんな型でもいい」のではなく、「特定の機能(トレイト)を持っている型だけ受け付けたい」という場合がほとんどです。これを制約するのが**トレイト境界**です。 - -### 基本的な構文 - -以下の関数は、引数 `item` が `Summary` トレイトを実装していることを要求します。 - -```rust -// 糖衣構文(Syntactic Sugar) -fn notify(item: &impl Summary) { - println!("Breaking news! {}", item.summarize()); -} - -// 正式なトレイト境界の構文 -fn notify_formal(item: &T) { - println!("Breaking news! {}", item.summarize()); -} -``` - -### 複数のトレイト境界と `where` 句 - -複数のトレイトが必要な場合(例えば「表示可能」かつ「要約可能」であってほしい場合)、`+` でつなぎます。制約が多くなりシグネチャが長くなる場合は、`where` 句を使って整理できます。 - -```rust:trait_bounds.rs -use std::fmt::Display; - -trait Summary { - fn summarize(&self) -> String; -} - -struct Book { - title: String, - author: String, -} - -impl Summary for Book { - fn summarize(&self) -> String { - format!("{} by {}", self.title, self.author) - } -} - -// Displayトレイトは標準ライブラリで定義されている(println!等で使用) -impl Display for Book { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "Book({})", self.title) - } -} - -// itemはSummaryとDisplayの両方を実装している必要がある -fn notify(item: &T) -where - T: Summary + Display, -{ - println!("Notify: {}", item.summarize()); - println!("Display format: {}", item); -} - -fn main() { - let b = Book { - title: String::from("The Rust Book"), - author: String::from("Steve Klabnik"), - }; - - notify(&b); -} -``` - -```rust-exec:trait_bounds.rs -Notify: The Rust Book by Steve Klabnik -Display format: Book(The Rust Book) -``` - -## 代表的な標準トレイト - -Rustには、すべてのRustプログラマが知っておくべき標準トレイトがいくつかあります。これらはしばしば `#[derive(...)]` 属性を使って自動的に実装されます。 - -1. **`Debug`**: `{:?}` でフォーマット出力するためのトレイト。開発中のデバッグ出力に必須です。 -2. **`Display`**: `{}` でフォーマット出力するためのトレイト。ユーザー向けの表示に使います。自動導出(derive)はできず、手動実装が必要です。 -3. **`Clone`**: `.clone()` メソッドで明示的にディープコピー(またはそれに準ずる複製)を作成するためのトレイト。 -4. **`Copy`**: 値がビット単位のコピーで複製できることを示すマーカートレイト。これが実装されている型(`i32`など)は、代入しても所有権が移動(Move)せず、コピーされます。 - - - -```rust:std_traits.rs -// Debug, Clone, Copyを自動導出 -#[derive(Debug, Clone, Copy)] -struct Point { - x: i32, - y: i32, -} - -fn main() { - let p1 = Point { x: 10, y: 20 }; - - // Copyトレイトがあるので、p1はmoveされない。コピーされる。 - let p2 = p1; - - // Debugトレイトがあるので {:?} が使える - println!("p1: {:?}", p1); - println!("p2: {:?}", p2); - - // Cloneトレイトがあるので明示的に呼ぶこともできる(Copyがある場合、動作はCopyと同じになることが多い) - let p3 = p1.clone(); - println!("p3: {:?}", p3); -} -``` - -```rust-exec:std_traits.rs -p1: Point { x: 10, y: 20 } -p2: Point { x: 10, y: 20 } -p3: Point { x: 10, y: 20 } -``` - -> **注意:** `String` や `Vec` などのヒープ領域へのポインタを持つ型は、所有権のルール上、安易に `Copy` を実装できません(二重解放エラーになるため)。それらは `Clone` のみを実装します。 - -# この章のまとめ - - * **ジェネリクス**: 型をパラメータ化し、コードの重複を減らします。コンパイル時に単相化されるため、実行時コストがかかりません。 - * **トレイト**: 共通の振る舞いを定義します。インターフェースに似ていますが、継承ではなくコンポジションを促進します。 - * **トレイト境界**: ジェネリクス型 `T` に対して、「特定のトレイトを実装している型のみ」を受け入れるよう制約を課します。 - * **標準トレイト**: `Debug`, `Display`, `Clone`, `Copy` など、Rustの基本動作を支える重要なトレイトが存在します。 - -これらを使いこなすことで、Rustコンパイラに安全性を保証させつつ、再利用性の高いライブラリのようなコードを書くことができるようになります。 - -### 練習問題 1: ジェネリックなペア - -2つの異なる型 `T` と `U` を保持できる構造体 `Pair` を作成してください。 -そして、その構造体にメソッド `new` (インスタンス作成)と、デバッグ出力をするメソッド `print_pair` を実装してください。 -(ヒント:`print_pair` 内で `T` と `U` を表示するには、それぞれの型に `Debug` トレイトの制約が必要です) - -```rust:practice11_1.rs - - - -fn main() { - let pair = Pair::new(10, "Hello"); - pair.print_pair(); -} -``` -```rust-exec:practice11_1.rs -10 and "Hello" -``` - -### 問題 2: 最大値を探す - -ジェネリックなスライス `&[T]` を受け取り、その中の最大値を返す関数 `largest` を作成してください。 -比較を行うためには `T` にどのようなトレイト境界が必要か考えてください(ヒント:比較には `std::cmp::PartialOrd` が必要です。また、スライスから値を取り出して返すには `Copy` があると簡単です)。 - -```rust:practice11_2.rs - - - -fn main() { - let number_list = vec![34, 50, 25, 100, 65]; - let result = largest(&number_list); - println!("The largest number is {}", result); - - let char_list = vec!['y', 'm', 'a', 'q']; - let result = largest(&char_list); - println!("The largest char is {}", result); -} -``` - -```rust-exec:practice_solutions.rs -The largest number is 100 -The largest char is y -``` diff --git a/public/docs/rust-12.md b/public/docs/rust-12.md deleted file mode 100644 index 37de42b..0000000 --- a/public/docs/rust-12.md +++ /dev/null @@ -1,252 +0,0 @@ -# 第12章: ライフタイム(Lifetimes) - -ようこそ、Rustの学習における「最難関」とも呼ばれる章へ。 -これまで所有権や借用(第4章、第5章)を学んできましたが、**ライフタイム(Lifetimes)** はそれらを支えるコンパイラのロジックそのものです。 - -他の言語(C/C++)では、メモリが解放された後にその場所を指し続ける「ダングリングポインタ」がバグの温床でした。JavaやPythonのようなガベージコレクション(GC)を持つ言語では、これを自動で管理しますが、パフォーマンスのコストがかかります。 - -Rustは**「コンパイル時に参照の有効性を厳密にチェックする」**ことで、GCなしでメモリ安全性を保証します。そのための仕組みがライフタイムです。 - -## ライフタイムとは何か - -ライフタイムとは、簡単に言えば**「その参照が有効である期間(スコープ)」**のことです。 - -実は、これまでの章でもあなたは無意識にライフタイムを使用してきました。通常、コンパイラが自動的に推論してくれるため、明示する必要がなかっただけです。しかし、コンパイラが「参照の有効期間が不明瞭だ」と判断した場合、プログラマが明示的に注釈(アノテーション)を加える必要があります。 - -### ダングリング参照を防ぐ - -ライフタイムの主な目的は、無効なデータを指す参照を作らせないことです。 - -以下のコードを見てください(これはコンパイルエラーになります)。 - -```rust -{ - let r; // ---------+-- rのライフタイム - // | - { // | - let x = 5; // -+-- xのライフタイム - r = &x; // | | - } // -+ | - // | - println!("r: {}", r); // | -} // ---------+ -``` - -ここで、`r` は `x` を参照しようとしています。しかし、内側のブロック `{}` が終わった時点で `x` は破棄されます。その後に `r` を使おうとすると、`r` は「解放されたメモリ」を指していることになります。 - -Rustのコンパイラ(**借用チェッカー**)は、このスコープのズレを検知し、「`x` の寿命が短すぎる」としてエラーを出します。 - -## 関数のライフタイム注釈 - -最も頻繁にライフタイム注釈が必要になるのは、**「引数として参照を受け取り、戻り値として参照を返す関数」**です。 - -2つの文字列スライスを受け取り、長い方を返す関数 `longest` を考えてみましょう。 - -```rust:longest_fail.rs -fn longest(x: &str, y: &str) -> &str { - if x.len() > y.len() { - x - } else { - y - } -} - -fn main() { - let string1 = String::from("abcd"); - let string2 = "xyz"; - - let result = longest(string1.as_str(), string2); - println!("The longest string is {}", result); -} -``` - -このコードをコンパイルしようとすると、以下のようなエラーが出ます。 - -```rust-exec:longest_fail.rs -error[E0106]: missing lifetime specifier - | -1 | fn longest(x: &str, y: &str) -> &str { - | ---- ---- ^ expected named lifetime parameter - | - = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` -``` - -**なぜエラーになるのか?** -コンパイラには、`longest` 関数が `x` を返すのか `y` を返すのか実行時まで分かりません。そのため、**戻り値の参照がいつまで有効であれば安全なのか(xの寿命に合わせるべきか、yの寿命に合わせるべきか)** を判断できないのです。 - -### ライフタイム注釈の構文 - -ここで**ジェネリックなライフタイム注釈**が登場します。 -構文は `'a` のようにアポストロフィから始まる名前を使います。通常は `'a`(a, b, c...)が使われます。 - -注釈のルールは以下の通りです: - - * 関数名の後に `<'a>` でライフタイムパラメータを宣言する。 - * 引数と戻り値の参照に `&'a str` のように付与する。 - -修正したコードがこちらです。 - -```rust:longest_success.rs -// 'a というライフタイムを宣言し、 -// 「引数x、引数y、そして戻り値は、すべて少なくとも 'a と同じ期間だけ生きている」 -// という制約をコンパイラに伝える。 -fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { - if x.len() > y.len() { - x - } else { - y - } -} - -fn main() { - let string1 = String::from("long string is long"); - let result; - { - let string2 = String::from("xyz"); - // resultのライフタイムは、string1とstring2のうち「短い方」の寿命に制約される - result = longest(string1.as_str(), string2.as_str()); - println!("The longest string is '{}'", result); - } - // ここで string2 がドロップされるため、result も無効になる。 - // もしここで result を使おうとするとコンパイルエラーになる(安全!)。 - // println!("The longest string is '{}'", result); -} -``` - -```rust-exec:longest_success.rs -The longest string is 'long string is long' -``` - -**重要なポイント:** -ライフタイム注釈 `'a` は、変数の寿命を**延ばすものではありません**。 -「複数の参照の寿命の関係性」をコンパイラに説明し、**「渡された参照の中で最も寿命が短いもの」** に戻り値の寿命を合わせるように制約するものです。 - -## ライフタイム省略ルール - -「待ってください。第4章で書いた `fn first_word(s: &str) -> &str` は注釈なしで動きましたよ?」 - -鋭い質問です。初期のRustではすべての参照に明示的なライフタイムが必要でした。しかし、あまりにも頻出するパターン(例えば「引数が1つなら、戻り値のライフタイムもそれと同じ」など)があったため、Rustチームはそれらを自動推論する**「ライフタイム省略ルール(Lifetime Elision Rules)」**をコンパイラに組み込みました。 - -コンパイラは以下の3つのルールを順番に適用します。それでもライフタイムが決まらない場合のみ、エラーを出して人間に注釈を求めます。 - -1. **各引数に独自のライフタイムを割り当てる** - * `fn foo(x: &str, y: &str)` → `fn foo<'a, 'b>(x: &'a str, y: &'b str)` -2. **入力ライフタイムが1つだけなら、そのライフタイムをすべての出力(戻り値)に割り当てる** - * `fn foo<'a>(x: &'a str) -> &'a str` - * これが `first_word` 関数で注釈が不要だった理由です。 -3. **メソッド(`&self` または `&mut self` を含む)の場合、`self` のライフタイムをすべての出力に割り当てる** - * これにより、構造体のメソッドを書く際にいちいち注釈を書かずに済みます。 - -## 構造体定義におけるライフタイム注釈 - -これまでの章では、構造体には `String` や `i32` などの「所有される型」を持たせてきました。 -しかし、構造体に**参照**を持たせたい場合もあります。その場合、**「構造体そのものよりも、中の参照先が長生き(あるいは同等の寿命)である」**ことを保証しなければなりません。 - -```rust:struct_lifetime.rs -// ImportantExcerpt構造体は、'a という期間だけ生きる文字列スライスを保持する -struct ImportantExcerpt<'a> { - part: &'a str, -} - -fn main() { - let novel = String::from("Call me Ishmael. Some years ago..."); - - // 最初の文(ピリオドまで)を取得 - let first_sentence = novel.split('.').next().expect("Could not find a '.'"); - - // 構造体のインスタンスを作成 - // part は novel の一部を参照している。 - // novel が有効である限り、i も有効であることが保証される。 - let i = ImportantExcerpt { - part: first_sentence, - }; - - println!("Novel start: {}", i.part); -} -``` - -```rust-exec:struct_lifetime.rs -Novel start: Call me Ishmael -``` - -もし `User` 構造体のような定義で `<'a>` を忘れると、「参照を持たせるならライフタイムを指定せよ」というエラーになります。これは、構造体が生きている間に参照先のデータが消えてしまうのを防ぐためです。 - -## 静的ライフタイム ('static) - -特別なライフタイムとして `'static` があります。 -これは、**「参照がプログラムの実行期間全体にわたって有効である」**ことを意味します。 - -すべての文字列リテラルは `'static` ライフタイムを持っています。なぜなら、それらはプログラムのバイナリ自体に埋め込まれており、メモリ上の位置が固定されているからです。 - -```rust -let s: &'static str = "I have a static lifetime."; -``` - -**注意点:** -エラーメッセージで「`'static` が必要です」と提案されることがありますが、安易に `'static` を使って解決しようとしないでください。多くの場合、それは「参照ではなく所有権を持つべき」か「ライフタイムの関係を正しく記述すべき」場面であり、本当にプログラム終了までデータを保持し続けたいケースは稀です。 - -## この章のまとめ - - * **ライフタイム**は、参照が有効なスコープの長さを表す概念です。 - * Rustの**借用チェッカー**は、ライフタイムを比較してダングリング参照(無効なメモリへのアクセス)を防ぎます。 - * 関数で複数の参照を扱い、戻り値がそれらに依存する場合、`<'a>` のような**ジェネリックライフタイム注釈**が必要です。 - * **構造体**に参照を持たせる場合も、ライフタイム注釈が必要です。 - * **省略ルール**のおかげで、一般的なケースでは注釈を省略できます。 - -ライフタイムの記法は最初は「ノイズ」に見えるかもしれませんが、これは「メモリ安全性をコンパイラとプログラマが対話するための言語」です。これを理解すれば、C++のような複雑なメモリ管理の落とし穴を完全に回避できます。 - -### 練習問題 1: 参照を持つ構造体とメソッド - -以下の要件を満たすコードを作成してください。 - -1. `Book` という構造体を定義してください。 -2. この構造体は `title` というフィールドを持ち、それは `String` ではなく文字列スライス `&str` です(ライフタイム注釈が必要です)。 -3. `main` 関数で `String` 型の変数(例: `"The Rust Programming Language"`)を作成し、`Book` のインスタンスにその参照を渡してください。 -4. `Book` のインスタンスを表示してください(`Debug` トレイを導出(`#[derive(Debug)]`)して構いません)。 - -```rust:practice12_1.rs -// ここにBookの定義を書いてください - - -fn main() { - let book_title = String::from("The Rust Programming Language"); - let my_book = Book { - title: &book_title, - }; - - println!("Book details: {:?}", my_book); -} -``` - -```rust-exec:practice12_1.rs -Book details: Book { title: "The Rust Programming Language" } -``` - -### 練習問題 2: 最初の単語を返す関数(ライフタイム付き) - -以下の要件を満たすコードを作成してください。 - -1. 2つの文字列スライス `str1`, `str2` を受け取る関数 `first_word_of_longer` を作成してください。 -2. この関数は、2つの文字列のうち**長い方**を選び、その文字列の**最初の単語**(スペースまで)をスライスとして返します。 - * 例: "Hello World" と "Hi" なら、"Hello" を返す。 -3. 引数と戻り値に適切なライフタイム注釈 `'a` を付けてください。 -4. `main` 関数で動作確認をしてください。 - -*(ヒント: 単語の切り出しは `s.split_whitespace().next()` などが使えますが、戻り値のライフタイムが引数と紐付いていることが重要です)* - -```rust:practice12_2.rs -// ここにfirst_word_of_longer関数を書いてください - - -fn main() { - let string1 = String::from("Hello World from Rust"); - let string2 = String::from("Hi"); - - let first_word = first_word_of_longer(string1.as_str(), string2.as_str()); - println!("The first word of the longer string is: '{}'", first_word); -} -``` - -```rust-exec:practice12_2.rs -The first word of the longer string is: 'Hello' -``` diff --git a/public/docs/rust-2.md b/public/docs/rust-2.md deleted file mode 100644 index baab9e7..0000000 --- a/public/docs/rust-2.md +++ /dev/null @@ -1,209 +0,0 @@ -# 第2章: 基本構文と「不変性」の哲学 - -他の言語での開発経験がある方にとって、Rustの学習で最初に直面するカルチャーショックが「変数の扱い」です。 - -C++やJava、Pythonなどでは「変数はデフォルトで書き換え可能(Mutable)」であることが一般的ですが、Rustでは**「デフォルトで不変(Immutable)」**です。この設計こそが、Rustがコンパイル時に多くのバグを排除できる理由の根幹にあります。 - -第2章では、Rustの構文の基礎と、その背後にある哲学を学びます。 - -## 変数と可変性(let vs let mut) - -Rustでは変数を宣言するために `let` キーワードを使用します。しかし、単に `let` で宣言された変数は、値を一度代入すると二度と変更できません。 - -### 不変変数(Immutable) - -まず、以下のコードを見てください。これは意図的にコンパイルエラーになるように書かれています。 - -```rust:immutability_error.rs -fn main() { - let x = 5; - println!("xの値は: {}", x); - - // 値を再代入しようとする(コンパイルエラーになる) - x = 6; - println!("xの値は: {}", x); -} -``` - -```rust-exec:immutability_error.rs -error[E0384]: cannot assign twice to immutable variable `x` - --> immutability_error.rs:6:5 - | -2 | let x = 5; - | - first assignment to `x` -... -6 | x = 6; - | ^^^^^ cannot assign twice to immutable variable - | - -``` - -これをコンパイルしようとすると、Rustコンパイラは「不変変数 `x` に二度代入することはできない」と強く叱ってくれます。 - -### 可変変数(Mutable) - -値を変更したい場合は、`mut`(mutableの略)キーワードを明示的に付ける必要があります。これにより、「この変数は値が変わる可能性がある」とコードの読み手やコンパイラに宣言します。 - -```rust:mutability_demo.rs -fn main() { - // mut をつけることで可変になる - let mut x = 5; - println!("xの値は: {}", x); - - x = 6; - println!("xの値は: {}", x); -} -``` - -```rust-exec:mutability_demo.rs -xの値は: 5 -xの値は: 6 -``` - -> **なぜデフォルトが不変なのか?** -> 大規模なシステムや並行処理において、「いつの間にか値が変わっている」ことはバグの主要な原因です。Rustは「変更が必要な箇所だけを明示的にする」ことで、コードの予測可能性と安全性を高めています。 - -## シャドーイング(Shadowing) - -Rustには、他の言語ではあまり見られない**シャドーイング(Shadowing)という強力な機能があります。これは、同じ名前の変数を `let` を使って再宣言**することで、前の変数を「覆い隠す」機能です。 - -`mut` との違いは2点あります: - -1. **型を変更できる**: `mut` は値の変更しかできませんが、シャドーイングは新しい変数を宣言しているのと同じなので、型を変えることができます。 -2. **不変に戻る**: 処理が終わった後、その変数は再び不変(immutable)になります。 - - - -```rust:shadowing.rs -fn main() { - let x = 5; - - // 最初のシャドーイング: 値を加工する - let x = x + 1; - - { - // 内側のスコープでのシャドーイング - let x = x * 2; - println!("内側のスコープでのx: {}", x); - } - - // スコープを抜けると、外側のxの値が参照される - println!("外側のスコープでのx: {}", x); - - // 型の変更を伴うシャドーイングの例 - let spaces = " "; // 文字列スライス型 - let spaces = spaces.len(); // usize型(整数) - - println!("スペースの数: {}", spaces); -} -``` - -```rust-exec:shadowing.rs -内側のスコープでのx: 12 -外側のスコープでのx: 6 -スペースの数: 3 -``` - -他言語では `spaces_str`、`spaces_num` のように変数名を変える場面でも、Rustでは変数の意味が変わらなければ同じ名前を再利用してコードをすっきり保つことができます。 - -## 基本的なデータ型 - -Rustは静的型付け言語ですが、型推論が強力なため、多くの場合に型注釈は不要です。しかし、ここでは明示的な理解のために型を見ていきます。 - -### スカラー型(単一の値を表す) - -1. **整数型**: - * `i32` (デフォルト), `i64`, `u32`, `u8` など。 - * アーキテクチャ依存の `isize`, `usize` (インデックス用によく使われる)。 -2. **浮動小数点型**: - * `f64` (デフォルト, 倍精度), `f32` (単精度)。 -3. **論理値型**: - * `bool` (`true` / `false`)。 -4. **文字型**: - * `char`。Rustのcharは4バイトで、Unicodeスカラー値を扱います(ASCIIだけでなく、漢字や絵文字も1文字として扱えます)。シングルクォート `'` で囲みます。 - -### 複合型(複数の値をまとめる) - -1. **タプル (Tuple)**: - * 異なる型をまとめる固定長のリストです。 -2. **配列 (Array)**: - * **同じ型**の要素をまとめる**固定長**のリストです。 - * ※ 長さが可変のリストが必要な場合は、後の章で学ぶ「ベクタ (`Vec`)」を使います。配列はスタック上に確保されるため、高速ですがサイズ変更はできません。 - - - -```rust:data_types.rs -fn main() { - // --- スカラー型 --- - let int_val: i32 = -10; - let float_val: f64 = 2.5; - let char_val: char = 'あ'; // Unicode文字 - let emoji: char = '🦀'; - - println!("Scalar: {}, {}, {}, {}", int_val, float_val, char_val, emoji); - - // --- 複合型: タプル --- - // 型が異なっていてもOK - let tup: (i32, f64, u8) = (500, 6.4, 1); - - // 分解(Destructuring)して値を取り出す - let (x, y, z) = tup; - println!("Tuple分解: yの値は {}", y); - - // ドット記法でアクセス - println!("Tupleアクセス: 最初の値は {}", tup.0); - - // --- 複合型: 配列 --- - // 型と長さを指定: [型; 長さ] - let arr: [i32; 5] = [1, 2, 3, 4, 5]; - - // 同じ値で初期化する記法 let a = [3; 5] は [3, 3, 3, 3, 3] - let months = ["Jan", "Feb", "Mar"]; - - println!("Array: 2番目の月は {}", months[1]); -} -``` - -```rust-exec:data_types.rs -Scalar: -10, 2.5, あ, 🦀 -Tuple分解: yの値は 6.4 -Tupleアクセス: 最初の値は 500 -Array: 2番目の月は Feb -``` - -## この章のまとめ - - * **不変性がデフォルト**: 変数は `mut` をつけない限り変更できない。これにより安全性が担保される。 - * **シャドーイング**: `let` を重ねることで変数の再定義が可能。値の加工や型の変更に便利。 - * **データ型**: 整数、浮動小数点、文字(Unicode対応)、タプル、配列(固定長)などの基本型がある。 - -他の言語では「定数」として扱われるような使い方が、Rustでは「デフォルトの変数」になります。最初は窮屈に感じるかもしれませんが、「変化するものは目立つようにする」というRustの哲学に慣れると、コードの流れが追いやすくなることに気づくはずです。 - -### 練習問題 1: 計算とシャドーイング - -以下の手順に従ってコードを書いてください。 - -1. 変数 `x` を定義し、文字列 `"100"` を代入する。 -2. シャドーイングを使って、`x` を数値の `100` (文字列からパースする)に変換する。 - * ヒント: `"100".parse().expect("Not a number")` で数値に変換できます。 -3. さらにシャドーイングを使って、`x` に `50` を足した値にする。 -4. 最終的な `x` の値を表示する。 - -```rust:practice2_1.rs -``` - -```rust-exec:practice2_1.rs -``` - -### 練習問題 2: 配列とタプルの操作 - -1. 浮動小数点型の配列 `data` を定義し、`[1.1, 2.2, 3.3]` で初期化する。 -2. タプル `result` を定義し、配列 `data` の最初の要素と最後の要素を格納する。 -3. タプルを分解(デストラクチャリング)して、それぞれの値を表示する。 -4. この一連の操作で `mut` が必要ない理由を考える。 - -```rust:practice2_2.rs -``` - -```rust-exec:practice2_2.rs -``` diff --git a/public/docs/rust-3.md b/public/docs/rust-3.md deleted file mode 100644 index f58ca74..0000000 --- a/public/docs/rust-3.md +++ /dev/null @@ -1,219 +0,0 @@ -# 第3章: 関数と制御フロー - -他の言語での経験がある皆さんなら、関数やループの基本的な概念はすでにご存知でしょう。しかし、Rustには「すべてが式である」という強力な設計思想があり、これが制御フローの書き方にも大きく影響しています。 - -この章では、Rust特有の「文(Statement)と式(Expression)」の違いを理解し、それを踏まえた関数の定義方法と、柔軟な制御フローについて学びます。 - -## 関数と「式」指向 - -Rustの関数定義は `fn` キーワードを使用し、変数や関数の命名規則にはスネークケース(`snake_case`)を採用するのが慣例です。 - -### 引数と戻り値 - -Rustは静的型付け言語であるため、関数の定義では**各引数の型を必ず宣言**する必要があります。戻り値がある場合は、矢印 `->` の後に型を記述します。 - -### 文(Statement)と式(Expression) - -Rustを理解する上で最も重要な概念の一つがこれです。 - - * **文 (Statement):** 何らかの操作を行い、値を返さないもの(例: `let y = 6;`)。 - * **式 (Expression):** 評価されて結果の値を返すもの(例: `5 + 6`、`{ code_block }`、関数呼び出し)。 - -他の多くの言語と異なり、Rustでは**ブロックの最後の式がそのブロックの戻り値**になります。`return` キーワードを使うこともできますが、一般的には「最後の行のセミコロンを省略する」スタイルが好まれます。 - -以下のコードで確認してみましょう。 - -```rust:functions_demo.rs -fn main() { - let x = 5; - let y = 10; - - // 値を返す関数呼び出し - let result = add(x, y); - println!("{} + {} = {}", x, y, result); - - // 文と式の違いを示すブロック - let z = { - let a = 3; - a + 1 // セミコロンがないので、この式が評価されzに代入される - }; - - println!("zの値: {}", z); -} - -// 引数を取り、i32型を返す関数 -fn add(a: i32, b: i32) -> i32 { - // 最後の行にセミコロンがないため、この式の計算結果が戻り値となる - // "return a + b;" と書くのと同じ意味だが、こちらがRustらしい書き方 - a + b -} -``` - -```rust-exec:functions_demo.rs -5 + 10 = 15 -zの値: 4 -``` - -> **注意:** もし `a + 1` の後ろにセミコロンをつけて `a + 1;` とすると、それは「文」となり、値を返さなくなります(正確には空のタプル `()` を返します)。コンパイルエラーの原因になりやすいため注意してください。 - -## 制御フロー - -Rustの制御フロー(`if`, `loop`, `while`, `for`)もまた「式」として扱うことができます。 - -### if 式 - -Rustの `if` は式です。つまり、条件分岐の結果を変数に代入することができます。 -三項演算子(`condition ? a : b`)のような専用の構文はRustにはありませんが、`if` がその役割を果たします。 - -重要なルールとして、**条件式は必ず `bool` 型でなければなりません**。JavaScriptやC++のように、数値を自動的に `true/false` に変換することはありません。 - -```rust:if_expression.rs -fn main() { - let condition = true; - - // ifの結果を変数に束縛できる - // ifブロックとelseブロックが返す型は同じである必要がある - let number = if condition { - 5 - } else { - 6 - }; - - println!("numberの値: {}", number); -} -``` - -```rust-exec:if_expression.rs -numberの値: 5 -``` - -### ループ構造 - -Rustには3種類のループがあります。 - -1. `loop`: 無限ループ -2. `while`: 条件付きループ -3. `for`: コレクションや範囲に対するループ - -#### loop と値の戻り - -`loop` キーワードは、明示的に `break` するまで永遠に繰り返します。面白い機能として、`break` の後に値を置くことで、ループ全体の結果としてその値を返すことができます。 - -```rust:loop_return.rs -fn main() { - let mut counter = 0; - - // ループの結果を変数resultに代入 - let result = loop { - counter += 1; - - if counter == 10 { - // カウンタが10になったら、counter * 2 の値を返して終了 - break counter * 2; - } - }; - - println!("ループの結果: {}", result); -} -``` - -```rust-exec:loop_return.rs -ループの結果: 20 -``` - -#### while - -`while` は他の言語とほぼ同じです。条件が真である限り実行されます。 - -```rust -// 例(実行不要) -let mut number = 3; -while number != 0 { - println!("{}!", number); - number -= 1; -} -``` - -#### for ループ - -Rustで最も安全かつ頻繁に使用されるのが `for` ループです。配列の要素を走査したり、特定の回数だけ処理を行ったりする場合、インデックス管理が不要な `for` が推奨されます。 - -数値の範囲を指定する場合は `Range` 型(`start..end`)を使用します。 - -```rust:for_loop.rs -fn main() { - let a = [10, 20, 30, 40, 50]; - - // 配列のイテレータを使ったループ - println!("--- 配列の走査 ---"); - for element in a.iter() { - println!("値: {}", element); - } - - // Rangeを使ったループ (1から3まで。4は含まない) - println!("--- 範囲指定 ---"); - for number in 1..4 { - println!("カウント: {}", number); - } -} -``` - -```rust-exec:for_loop.rs ---- 配列の走査 --- -値: 10 -値: 20 -値: 30 -値: 40 -値: 50 ---- 範囲指定 --- -カウント: 1 -カウント: 2 -カウント: 3 -``` - -## この章のまとめ - - * **関数:** パラメータの型と戻り値の型を明示する。 - * **文と式:** Rustは式指向であり、セミコロンのない最後の式がブロックの戻り値となる。 - * **if式:** `if` は値を返すことができる。条件は必ず `bool` でなければならない。 - * **ループ:** `loop`(無限・値返し可能)、`while`(条件付き)、`for`(イテレータ・範囲)があり、特に `for` が推奨される。 - -## 練習問題 1: 摂氏・華氏変換 - -摂氏(Celsius)を華氏(Fahrenheit)に変換する関数 `c_to_f` を作成してください。 -公式: F = C × 1.8 + 32 - -```rust:practice3_1.rs -fn main() { - let celsius_temp = 25.0; - let fahrenheit_temp = c_to_f(celsius_temp); - println!("{}°C は {}°F です", celsius_temp, fahrenheit_temp); -} - -// ここに関数を実装してください - -``` - -```rust-exec:practice3_1.rs -25°C は 77°F です -``` - -### 練習問題 2: フィボナッチ数列 - -第 `n` 番目のフィボナッチ数を求める関数 `fib` を実装してください。 -制御フロー(`if` または ループ)を使用してください。 -(定義: 第0項=0, 第1項=1, 第n項 = 第n-1項 + 第n-2項) - -```rust:practice3_2.rs -fn main() { - let n = 10; - println!("フィボナッチ数列の第 {} 項は {} です", n, fib(n)); -} - -// ここに関数を実装してください - -``` - -```rust-exec:practice3_2.rs -フィボナッチ数列の第 10 項は 55 です -``` diff --git a/public/docs/rust-4.md b/public/docs/rust-4.md deleted file mode 100644 index 8332572..0000000 --- a/public/docs/rust-4.md +++ /dev/null @@ -1,242 +0,0 @@ -# 第4章: 所有権(Ownership)システム - -Rustへようこそ。ここからがRustの学習における**最大の山場**であり、同時に**最大の特徴**である「所有権(Ownership)」システムについて解説します。 - -他のプログラミング言語(Python, Java, C++など)の経験がある方にとって、この概念は最も馴染みがなく、直感に反する場合があるかもしれません。しかし、所有権こそがガベージコレクション(GC)なしでメモリ安全性を保証するRustの魔法の源です。 - -本章では、参照(借用)の概念に入る前に、基礎となる「所有権の移動(ムーブ)」とメモリ管理の仕組みを理解します。 - -## スタックとヒープのメモリ管理 - -所有権を理解するには、計算機科学の基礎である「スタック(Stack)」と「ヒープ(Heap)」の違いを意識する必要があります。多くの高水準言語ではこれを意識しなくてもコードが書けますが、システムプログラミング言語であるRustでは、値がどこに配置されるかが言語の挙動に直結します。 - -### スタック(Stack) - - * **特徴:** Last In, First Out (LIFO)。高速。 - * **データ:** コンパイル時にサイズが既知のデータ(`i32`, `bool`, 固定長配列など)が置かれます。 - * **動作:** 関数呼び出し時にローカル変数がプッシュされ、関数終了時にポップされます。 - -### ヒープ(Heap) - - * **特徴:** 任意の順序で確保・解放が可能。スタックより低速(ポインタ経由のアクセスが必要)。 - * **データ:** コンパイル時にサイズが不明、または可変長のデータ(`String`, `Vec`など)が置かれます。 - * **動作:** メモリ管理が必要。OSにメモリを要求し、そのアドレス(ポインタ)を受け取ります。ポインタ自体はスタックに置かれます。 - -**Rustの所有権システムは、主にこの「ヒープデータの管理」を自動化・安全化するためのルールセットです。** - -## 所有権の3つのルール - -Rustのコンパイラは、以下の厳格なルールに基づいてメモリ管理を行います。これを破るとコンパイルエラーになります。 - -> **所有権のルール** -> -> 1. Rustの各値は、**所有者(Owner)**と呼ばれる変数を持つ。 -> 2. いかなる時も、所有者は**一人だけ**である。 -> 3. 所有者がスコープから外れると、値は**破棄(ドロップ)**される。 - -### 変数のスコープ - -まずは単純なスコープの例を見てみましょう。これは他の言語とほぼ同じです。 - -```rust:scope_example.rs -fn main() { - { // s はここで宣言されていないので無効 - let s = "hello"; // s はここから有効になる - println!("{}", s); // s を使用できる - } // ここでスコープ終了。s は無効になる -} -``` - -```rust-exec:scope_example.rs -hello -``` - -ここで重要なのは、スコープを抜けた瞬間にRustが自動的にメモリを解放する処理(`drop`関数)を呼び出すという点です。これはC++のRAII (Resource Acquisition Is Initialization) パターンと同様です。 - -## ムーブセマンティクス(Move vs Copy) - -ここからがRust独自の挙動です。データ型によって、「代入」の意味が変わります。 - -### Copyトレイト:スタックのみのデータ - -整数型のような単純な値は、サイズが固定でスタック上にあります。この場合、変数を代入すると**値がコピー**されます。 - -```rust -let x = 5; -let y = x; // xの値(5)がコピーされてyに入る -// ここでは x も y も両方有効 -``` - -### Move(移動):ヒープデータの場合 - -`String`型のようにヒープにメモリを確保する型を見てみましょう。 - -```rust -let s1 = String::from("hello"); -let s2 = s1; -``` - -C++などの経験があると、これは「ポインタのコピー(浅いコピー)」あるいは「ディープコピー」のどちらかだと思うかもしれません。 -しかしRustでは、これは**所有権の移動(Move)**とみなされます。 - -1. `s1` はヒープ上の "hello" を指すポインタ、長さ、容量をスタックに持っています。 -2. `s2 = s1` を実行すると、スタック上のデータ(ポインタ等)のみが `s2` にコピーされます。 -3. **重要:** この瞬間、Rustは `s1` を**無効**とみなします。 - -なぜなら、もし `s1` も有効なままだと、スコープを抜けた時に `s1` と `s2` が同じヒープメモリを2回解放しようとしてしまう(二重解放エラー)からです。 - -以下のコードを実行して確認してみましょう。 - -```rust:move_error_demo.rs -fn main() { - let s1 = String::from("hello"); - let s2 = s1; // 所有権が s1 から s2 へ移動(ムーブ) - - // s1 はもう無効なので、以下の行はコンパイルエラーになる - // println!("{}, world!", s1); - - println!("s1 is moved."); - println!("s2 is: {}", s2); -} -``` - -```rust-exec:move_error_demo.rs -s1 is moved. -s2 is: hello -``` - -もし `println!("{}", s1)` のコメントアウトを外すと、`value borrowed here after move` という有名なコンパイルエラーが発生します。 - -### Clone:ディープコピー - -もしヒープ上のデータも含めて完全にコピーしたい場合は、明示的に `.clone()` メソッドを使用します。 - -```rust:clone_example.rs -fn main() { - let s1 = String::from("hello"); - let s2 = s1.clone(); // ヒープデータごとコピーする(コストは高い) - - println!("s1 = {}, s2 = {}", s1, s2); -} -``` - -```rust-exec:clone_example.rs -s1 = hello, s2 = hello -``` - -## 所有権と関数 - -関数に変数を渡す動作も、代入と同様に機能します。つまり、**関数へ値を渡すと所有権が移動します**(Copy型を除く)。 - -```rust:function_ownership.rs -fn main() { - let s = String::from("hello"); // s がスコープに入る - - takes_ownership(s); // s の値が関数にムーブされる - // ここで s はもう有効ではない! - - let x = 5; // x がスコープに入る - makes_copy(x); // x も関数に移動するが、 - // i32はCopyトレイトを持つので、 - // この後も x を使って問題ない - -} // ここで x がスコープアウト。s もスコープアウトだが、 - // 所有権は既に移動しているので何も起きない。 - -fn takes_ownership(some_string: String) { // some_string に所有権が移る - println!("{}", some_string); -} // ここで some_string がスコープアウトし、`drop` が呼ばれる。メモリ解放。 - -fn makes_copy(some_integer: i32) { // some_integer に値がコピーされる - println!("{}", some_integer); -} // ここで some_integer がスコープアウト。何も起きない。 -``` - -```rust-exec:function_ownership.rs -hello -5 -``` - -### 戻り値と所有権 - -関数から値を返すことで、所有権を呼び出し元に戻すことができます。 - -```rust:return_ownership.rs -fn main() { - let s1 = gives_ownership(); // 戻り値の所有権が s1 に移動 - let s2 = String::from("hello"); // s2 スコープイン - let s3 = takes_and_gives_back(s2); // s2 は関数にムーブされ、 - // 戻り値が s3 にムーブされる - println!("s1: {}, s3: {}", s1, s3); -} - -fn gives_ownership() -> String { - let some_string = String::from("yours"); - some_string // 所有権を呼び出し元に返す -} - -// 文字列を受け取り、それをそのまま返す関数 -fn takes_and_gives_back(a_string: String) -> String { - a_string // 所有権を返す -} -``` - -```rust-exec:return_ownership.rs -s1: yours, s3: hello -``` - -## この章のまとめ - - * **所有権のルール:** 値の所有者は常に一人。所有者がスコープを抜けると値は破棄される。 - * **スタック vs ヒープ:** サイズ固定のデータはスタック(高速)、可変長データはヒープ(管理が必要)に置かれる。 - * **Move(ムーブ):** ヒープデータを変数に代入(または関数渡し)すると、所有権が移動し、元の変数は使用不能になる。 - * **Copy:** 整数などの単純な型は、ムーブではなく自動的にコピーされる。 - * **Clone:** ヒープデータをディープコピーしたい場合は `clone()` を使う。 - -このシステムのおかげで、Rustは実行時のガベージコレクションによる停止を避けつつ、ダングリングポインタ(無効なメモリを指すポインタ)や二重解放のバグをコンパイル時に完全に防ぐことができます。 - -しかし、「関数に値を渡すたびに所有権がなくなってしまい、使えなくなる」のは不便だと感じたでしょう。値を一時的に関数に使わせたいだけなのに、いちいち所有権を返してもらうのは面倒です。 - -次章の**「借用(Borrowing)」**では、所有権を渡さずに値を参照する方法を学びます。 - -### 練習問題 1: ムーブの回避 - -以下のコードは、`s1` の所有権が `s2` に移動してしまったためコンパイルエラーになります。`s1` と `s2` の両方を表示できるように修正してください(`clone`を使用する方法と、新しい文字列を作る方法のどちらでも構いません)。 - -```rust:practice4_1.rs -fn main() { - let s1 = String::from("Rust"); - let s2 = s1; - - println!("Original: {}", s1); // ここでエラー - println!("New: {}", s2); -} -``` - -```rust-exec:practice4_1.rs -``` - -### 練習問題 2: 所有権のリレー - -以下の `process_string` 関数は文字列を受け取って長さを出力しますが、戻り値がないため、呼び出し元で元の文字列が使えなくなってしまいます。 -`process_string` を修正して、受け取った文字列の所有権を呼び出し元に返すようにし、`main` 関数でその後の処理ができるようにしてください。 - -```rust:practice4_2.rs -fn main() { - let s = String::from("ownership"); - - // ここで s を渡して、処理後に戻ってきた所有権を new_s で受け取るように修正する - process_string(s); - - // 修正後は以下のコメントを解除しても動作するようにする - // println!("String is still valid: {}", new_s); -} - -// 戻り値の型と実装を修正してください -fn process_string(input: String) { - println!("Length is: {}", input.len()); -} -``` - -```rust-exec:practice4_2.rs -``` diff --git a/public/docs/rust-5.md b/public/docs/rust-5.md deleted file mode 100644 index 14b3f16..0000000 --- a/public/docs/rust-5.md +++ /dev/null @@ -1,288 +0,0 @@ -# 第5章: 借用(Borrowing)とスライス - -前章では「所有権(Ownership)」というRust独自のメモリ管理システムについて学びました。「所有権は一度に1つの変数しか持てない」というルールは強力ですが、関数に値を渡すたびに所有権が移動(ムーブ)してしまうと、毎回値を返り値として受け取らなければならず不便です。 - -そこで登場するのが**「参照(Reference)」**と**「借用(Borrowing)」**です。これを使うことで、所有権を渡さずに値にアクセスすることが可能になります。 - -## 5.1 参照と借用(& と \&mut) - -**参照(Reference)**とは、所有権を持たずにデータへアクセスするためのポインタのようなものです。他の言語のポインタと似ていますが、Rustの参照は「常に有効なデータを指していること」が保証されています。 - -関数の引数として参照を渡すことを、Rustでは**「借用(Borrowing)」**と呼びます。誰かから本を借りても、それを捨てたり(メモリ解放)、転売したり(所有権の譲渡)できないのと同じです。 - -### 不変参照(Immutable Reference) - -デフォルトでは、参照は**不変**です。借りた値を読むことはできますが、変更することはできません。参照を作成するには `&` を使います。 - -```rust:calculate_length.rs -fn main() { - let s1 = String::from("hello"); - - // &s1 で s1 への参照を渡す(所有権は移動しない) - let len = calculate_length(&s1); - - // 所有権は移動していないので、ここで s1 をまだ使える! - println!("The length of '{}' is {}.", s1, len); -} - -// 引数の型が &String になっていることに注目 -fn calculate_length(s: &String) -> usize { - s.len() -} // ここで s がスコープを抜けるが、所有権を持っていないのでメモリは解放されない -``` - -```rust-exec:calculate_length.rs -The length of 'hello' is 5. -``` - -### 可変参照(Mutable Reference) - -借りた値を変更したい場合は、**可変参照**を使用します。これには `&mut` を使い、元の変数も `mut` である必要があります。 - -```rust:mutable_borrow.rs -fn main() { - let mut s = String::from("hello"); - - change(&mut s); // 可変参照を渡す - - println!("Result: {}", s); -} - -fn change(some_string: &mut String) { - some_string.push_str(", world"); -} -``` - -```rust-exec:mutable_borrow.rs -Result: hello, world -``` - -## 5.2 借用のルール - -Rustには、メモリ安全性とデータ競合(Data Race)を防ぐための非常に重要なルールがあります。これを**「借用のルール」**と呼びます。 - -> **【借用の鉄則】** -> ある特定のスコープにおいて、以下の**どちらか一方**しか満たすことはできません: -> -> 1. **1つの**可変参照(`&mut T`)を持つ。 -> 2. **任意の数の**不変参照(`&T`)を持つ。 - -### なぜ可変参照は1つだけなのか? - -もし「同じデータに対する可変参照」が同時に2つ存在したらどうなるでしょうか? -Aさんがデータを書き換えている最中に、Bさんも書き換えようとすると、データが破損する可能性があります。Rustはこの可能性をコンパイル時に排除します。 - -### 不変参照と可変参照の共存禁止 - -同様に、「誰かがデータを読んでいる最中(不変参照)に、誰かがデータを書き換える(可変参照)」ことも禁止されています。読んでいる最中にデータが変わると困るからです。 - -以下のコードはコンパイルエラーになります(概念を示すための例です)。 - -```rust -let mut s = String::from("hello"); - -let r1 = &s; // 問題なし -let r2 = &s; // 問題なし -let r3 = &mut s; // 大問題!不変参照が存在している間に可変参照は作れない - -println!("{}, {}, and {}", r1, r2, r3); -``` - -しかし、**スコープ**を利用すれば、この制限をクリアできます。直前の参照が使用されなくなった(スコープを抜けた)後であれば、新しい参照を作ることができます。 - -```rust:borrow_scope.rs -fn main() { - let mut s = String::from("hello"); - - { - let r1 = &mut s; - r1.push_str(" world"); - // r1 はここでスコープを抜ける - } - - // r1 はもういないので、新しい不変参照を作れる - let r2 = &s; - println!("Final string: {}", r2); -} -``` - -```rust-exec:borrow_scope.rs -Final string: hello world -``` - -## 5.3 ダングリングポインタの防止 - -C++などの言語では、メモリが解放された後の場所を指し続けるポインタ(ダングリングポインタ)を作ってしまうことがあり、これが重大なバグの原因になります。 -Rustでは、**コンパイラがこれを絶対に許可しません**。 - -以下のコードは、「関数内で作成した変数の参照」を返そうとしていますが、これはエラーになります。 - -```rust -// コンパイルエラーになる例 -fn dangle() -> &String { - let s = String::from("hello"); - &s // sの参照を返す -} // ここでsはスコープを抜け、メモリが解放される。参照先が消滅する! -``` - -Rustコンパイラは「データのライフタイム(生存期間)が、その参照よりも短い」と判断し、エラーを出してくれます。この場合は、参照ではなく値を返して所有権を移動させるのが正解です。 - -## 5.4 スライス型(Slice) - -参照の特殊な形として**「スライス(Slice)」**があります。スライスは、コレクション(配列や文字列など)の**一部分**への参照です。所有権を持たない点は通常の参照と同じです。 - -### 文字列スライス(`&str`) - -特に重要なのが文字列スライスです。`String` の一部を切り出して参照します。 -書き方は `[開始インデックス..終了インデックス]` です。 - -```rust:string_slices.rs -fn main() { - let s = String::from("Hello Rust World"); - - let hello = &s[0..5]; // 0番目から4番目まで(5は含まない) - let rust = &s[6..10]; // 6番目から9番目まで - - println!("Slice 1: {}", hello); - println!("Slice 2: {}", rust); - - // 省略記法 - let len = s.len(); - let start = &s[0..5]; - let start_short = &s[..5]; // 0は省略可能 - - let end = &s[6..len]; - let end_short = &s[6..]; // 末尾も省略可能 - - let all = &s[..]; // 全体 - - println!("Full: {}", all); -} -``` - -```rust-exec:string_slices.rs -Slice 1: Hello -Slice 2: Rust -Full: Hello Rust World -``` - -### 文字列リテラルはスライスである - -これまで何気なく使っていた文字列リテラルですが、実はこれこそがスライスです。 - -```rust -let s = "Hello, world!"; -``` - -ここで `s` の型は `&str` です。これはバイナリの静的領域に格納されている文字列データへのスライス(参照)なのです。 - -### 引数としての `&str` - -関数で文字列を受け取る際、`&String` よりも `&str` を使う方が柔軟性が高まります。なぜなら、`&str` を引数にすれば、`String` も `&str`(リテラルなど)も両方受け取れるからです。 - -```rust -// この定義の方が汎用的 -fn first_word(s: &str) -> &str { - // 実装... - &s[..] // 仮の実装 -} -``` - -## 5.5 その他のスライス - -スライスは文字列だけでなく、配列に対しても使えます。 - -```rust:array_slice.rs -fn main() { - let a = [10, 20, 30, 40, 50]; - - // 配列の一部を借用する - let slice = &a[1..3]; // [20, 30] - - // assert_eq! は値が等しいか確認するマクロ - assert_eq!(slice, &[20, 30]); - - println!("Slice elements: {:?}", slice); -} -``` - -```rust-exec:array_slice.rs -Slice elements: [20, 30] -``` - -## この章のまとめ - - * **参照(`&`)**: 所有権を移動させずに値にアクセスする仕組み。 - * **借用**: 関数の引数として参照を渡すこと。 - * **借用のルール**: - 1. 不変参照(`&T`)は同時にいくつでも作れる。 - 2. 可変参照(`&mut T`)は同時に1つしか作れない。 - 3. 不変参照と可変参照は同時に存在できない。 - * **スライス**: コレクションの一部分を参照するビュー。`&str` は文字列の一部への不変参照。 - -Rustのコンパイラ(ボローチェッカー)は厳格ですが、それはバグのない安全なコードを書くための強力なパートナーです。慣れてくれば、コンパイルが通った時点でロジックの正しさに自信が持てるようになります。 - -## 練習問題 1: 参照渡しへの書き換え - -以下のコードは動作しますが、`calculate_area` 関数が所有権を奪ってしまうため、再度 `rect1` を使おうとするとエラーになります。 -`calculate_area` が `Rectangle` の**不変参照**を受け取るように修正し、`main` 関数で `rect1` を再利用できるようにしてください。 - -```rust:practice5_1.rs -struct Rectangle { - width: u32, - height: u32, -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - - let area = calculate_area(rect1); // ここを修正 - - // 現在のコードだと、ここで rect1 を使うとエラーになる - // println!("rect1 is {:?}", rect1); - - println!("The area is {}", area); -} - -fn calculate_area(rect: Rectangle) -> u32 { // ここを修正 - rect.width * rect.height -} -``` - -```rust-exec:practice5_1.rs -``` - -### 問題 2: スライスの活用 - -Eメールアドレスを表す文字列を受け取り、**「@」より前のユーザー名部分だけを文字列スライス(`&str`)として返す**関数 `extract_username` を作成してください。もし「@」が含まれていない場合は、文字列全体を返してください。 - -ヒント: 文字列をバイト配列として扱い、ループで `@` を探します。 - -```rust:practice5_2.rs -fn main() { - let email = String::from("user@example.com"); - let username = extract_username(&email); - println!("Username: {}", username); // "user" と出力されるべき - - let simple = String::from("admin"); - let simple_name = extract_username(&simple); - println!("Username: {}", simple_name); // "admin" と出力されるべき -} - -fn extract_username(s: &str) -> &str { - let bytes = s.as_bytes(); - - for (i, &item) in bytes.iter().enumerate() { - // ここにコードを書く - - } - - // 「@」が含まれていない場合は、文字列全体を返す - -} -``` - -```rust-exec:practice5_2.rs -Username: user -Username: admin -``` diff --git a/public/docs/rust-6.md b/public/docs/rust-6.md deleted file mode 100644 index 1d63002..0000000 --- a/public/docs/rust-6.md +++ /dev/null @@ -1,288 +0,0 @@ -# 第6章: 構造体とメソッド構文 - -他のオブジェクト指向言語(C++、Java、Pythonなど)の経験がある方にとって、Rustの**構造体(Struct)**は「クラス」に似ているように見えますが、決定的な違いがいくつかあります。 - -最も大きな違いは以下の2点です: - -1. **クラス継承が存在しない**: Rustには`extends`や親クラスという概念がありません(コードの再利用には、後述する「トレイト」や「コンポジション」を使用します)。 -2. **データと振る舞いの分離**: データ定義(`struct`)とメソッド定義(`impl`)は明確に分かれています。 - -この章では、関連するデータをグループ化し、それに対する操作を定義する方法を学びます。 - -## 構造体の定義とインスタンス化 - -構造体は、異なる型の値を一つにまとめて名前を付けたカスタムデータ型です。 - -### 基本的な定義 - -C言語の `struct` や、メソッドを持たないクラスのようなものです。フィールド名と型を定義します。 - -```rust:user_struct.rs -struct User { - username: String, - email: String, - sign_in_count: u64, - active: bool, -} - -fn main() { - // インスタンス化 - // フィールドの順番は定義と異なっても構いません - let mut user1 = User { - email: String::from("someone@example.com"), - username: String::from("someusername123"), - active: true, - sign_in_count: 1, - }; - - // ドット記法でフィールドにアクセス - user1.email = String::from("another@example.com"); - - println!("User: {}, Email: {}", user1.username, user1.email); -} -``` - -```rust-exec:user_struct.rs -User: someusername123, Email: another@example.com -``` - -> **注意**: Rustでは、インスタンス全体が可変(`mut`)か不変かのどちらかになります。特定のフィールドだけを可変(`mut`)にすることはできません。 - -### フィールド初期化省略記法 - -関数引数や変数の名前がフィールド名と同じ場合、記述を省略できます。これはJavaScriptのオブジェクト定義に似ています。 - -```rust -fn build_user(email: String, username: String) -> User { - User { - email, // email: email と同じ - username, // username: username と同じ - active: true, - sign_in_count: 1, - } -} -``` - -### 構造体更新記法 - -既存のインスタンスの値を元に、一部だけ変更した新しいインスタンスを作成する場合、`..` 構文を使用できます。 - -```rust -// user1のデータを元に、emailだけ変更したuser2を作成 -let user2 = User { - email: String::from("another@example.com"), - ..user1 // 残りのフィールドはuser1と同じ値が入る -}; -``` - -## タプル構造体とユニット様構造体 - -名前付きフィールドを持たない構造体も定義できます。 - -### タプル構造体 (Tuple Structs) - -フィールドに名前がなく、型だけが並んでいる構造体です。「型」として区別したい場合に便利です。例えば、同じ `(i32, i32, i32)` でも、「色」と「座標」は計算上混ぜるべきではありません。 - -```rust:tuple_structs.rs -struct Color(i32, i32, i32); -struct Point(i32, i32, i32); - -fn main() { - let black = Color(0, 0, 0); - let origin = Point(0, 0, 0); - - // black と origin は構造が同じでも、型としては別物なので - // 関数に渡す際などにコンパイラが区別してくれます。 - - // 中身へのアクセスはタプル同様にインデックスを使用 - println!("Origin X: {}", origin.0); -} -``` - -```rust-exec:tuple_structs.rs -Origin X: 0 -``` - -### ユニット様構造体 (Unit-like Structs) - -フィールドを全く持たない構造体です。`struct AlwaysEqual;` のように定義します。これらは、データを持たずに振る舞い(トレイト)だけを実装したい場合に役立ちますが、詳細は後の章で扱います。 - -## 所有権と構造体 - -構造体における所有権の扱いは非常に重要です。 - -1. **所有するデータ**: `String`や`Vec`のような所有権を持つ型をフィールドにすると、その構造体のインスタンスがデータの所有者になります。構造体がドロップされると、フィールドのデータもドロップされます。 -2. **参照**: 構造体に「参照(\&strなど)」を持たせることも可能ですが、これには**ライフタイム(第12章)の指定が必要になります。そのため、現段階では構造体には(参照ではなく)`String`などの所有権のある型**を使うことを推奨します。 - - - -```rust -// 推奨(現段階) -struct User { - username: String, // Userがデータを所有する -} - -// 非推奨(ライフタイムの知識が必要になるためコンパイルエラーになる) -// struct User { -// username: &str, -// } -``` - -## impl ブロック:メソッドと関連関数 - -ここが、他言語のクラスにおける「メソッド定義」に相当する部分です。データ(`struct`)とは別に、`impl`(implementation)ブロックを使って振る舞いを定義します。 - -### メソッドの定義 - -メソッドは、最初の引数が必ず `self` (自分自身のインスタンス)になる関数です。 - - * `&self`: データを読み取るだけ(借用・不変)。最も一般的。 - * `&mut self`: データを書き換える(借用・可変)。 - * `self`: 所有権を奪う(ムーブ)。メソッド呼び出し後に元の変数は使えなくなる。変換処理などで使う。 - - - -```rust:rectangle.rs -#[derive(Debug)] // print!で{:?}を使ってデバッグ表示するために必要 -struct Rectangle { - width: u32, - height: u32, -} - -// Rectangle型に関連する関数やメソッドを定義 -impl Rectangle { - // メソッド:面積を計算する(読み取りのみなので &self) - fn area(&self) -> u32 { - self.width * self.height - } - - // メソッド:他の四角形を含めるか判定する - fn can_hold(&self, other: &Rectangle) -> bool { - self.width > other.width && self.height > other.height - } -} - -fn main() { - let rect1 = Rectangle { width: 30, height: 50 }; - let rect2 = Rectangle { width: 10, height: 40 }; - - // メソッド呼び出し(自動参照外し機能により、rect1.area() と書ける) - println!("The area of the rectangle is {} pixels.", rect1.area()); - - if rect1.can_hold(&rect2) { - println!("rect1 can hold rect2"); - } -} -``` - -```rust-exec:rectangle.rs -The area of the rectangle is 1500 pixels. -rect1 can hold rect2 -``` - -### 関連関数 (Associated Functions) - -`impl`ブロックの中で、第1引数に `self` を取らない関数も定義できます。これらはインスタンスではなく、型そのものに関連付けられた関数です。 -他言語での「静的メソッド(Static Method)」に相当します。 - -最も一般的な用途は、コンストラクタのような役割を果たす初期化関数の作成です。Rustには `new` というキーワードはありませんが、慣習として `new` という名前の関連関数をよく作ります。 - -```rust:associated_fn.rs -#[derive(Debug)] -struct Circle { - radius: f64, -} - -impl Circle { - // 関連関数(selfがない) - // コンストラクタのように振る舞う - fn new(radius: f64) -> Circle { - Circle { radius } - } - - // メソッド(selfがある) - fn area(&self) -> f64 { - 3.14159 * self.radius * self.radius - } -} - -fn main() { - // 関連関数の呼び出しは :: を使う - let c = Circle::new(2.0); - - println!("Circle radius: {}, area: {}", c.radius, c.area()); -} -``` - -```rust-exec:associated_fn.rs -Circle radius: 2, area: 12.56636 -``` - -## この章のまとめ - - * **構造体 (`struct`)** は関連するデータをまとめるカスタム型です。 - * **タプル構造体** は名前付きフィールドを持たない構造体で、特定の型を区別するのに便利です。 - * **メソッド** は `impl` ブロック内に定義し、第1引数に `self` を取ります。 - * **関連関数** は `self` を取らず、`型名::関数名` で呼び出します(コンストラクタ `new` など)。 - * Rustには継承がないため、データ構造とメソッドの組み合わせのみでオブジェクト指向的な設計を行います。 - -次章では、Rustの強力な機能の一つである「列挙型(Enum)」と、フロー制御の要である「パターンマッチ」について学びます。 - -### 練習問題1: RPGのキャラクター - -以下の要件を満たす `Character` 構造体と `impl` ブロックを作成してください。 - -1. フィールド: - * `name`: キャラクター名 (`String`) - * `hp`: 現在のヒットポイント (`i32`) - * `attack_power`: 攻撃力 (`i32`) -2. 関連関数 `new`: - * 名前を受け取り、hpを100、attack\_powerを10で初期化したインスタンスを返す。 -3. メソッド `take_damage`: - * ダメージ量 (`i32`) を受け取り、`hp` から引く。ただし、`hp` は0未満にならないようにする(0で止める)。 - * このメソッドは `hp` を変更するため、`&mut self` が必要です。 - -```rust:practice6_1.rs -// ここにCharacter構造体とimplブロックを実装してください - - -fn main() { - let mut hero = Character::new(String::from("Hero")); - println!("{} has {} HP.", hero.name, hero.hp); - - hero.take_damage(30); - println!("After taking damage, {} has {} HP.", hero.name, hero.hp); - - hero.take_damage(80); - println!("After taking more damage, {} has {} HP.", hero.name, hero.hp); -} -``` -```rust-exec:practice6_1.rs -Hero has 100 HP. -After taking damage, Hero has 70 HP. -After taking more damage, Hero has 0 HP. -``` - -### 練習問題2: 座標計算 - -2次元座標を表すタプル構造体 `Point(f64, f64)` を作成し、以下を実装してください。 - -1. 関連関数 `origin`: 原点 `(0.0, 0.0)` を持つ `Point` を返す。 -2. メソッド `distance_to`: 別の `Point` への参照を受け取り、2点間の距離を計算して返す `f64`。 - * ヒント: 距離の公式は sqrt((x₂ - x₁)² + (y₂ - y₁)²) です。平方根は `f64`型の値に対して `.sqrt()` メソッドで計算できます。 - -```rust:practice6_2.rs -// ここにPointタプル構造体とimplブロックを実装してください - - -fn main() { - let p1 = Point::origin(); - let p2 = Point(3.0, 4.0); - - let distance = p1.distance_to(&p2); - println!("Distance from origin to p2 is {}", distance); -} -``` -```rust-exec:practice6_2.rs -Distance from origin to p2 is 5.0 -``` diff --git a/public/docs/rust-7.md b/public/docs/rust-7.md deleted file mode 100644 index eb1fc66..0000000 --- a/public/docs/rust-7.md +++ /dev/null @@ -1,326 +0,0 @@ -# 第7章: 列挙型(Enum)とパターンマッチ - -Rustチュートリアル第7章へようこそ。 -これまでの章では、構造体を使って関連するデータをまとめる方法を学びました。この章では、**列挙型(Enum)と、それに関連する強力な制御フロー構造であるパターンマッチ**について学びます。 - -他の言語(C、C++、Javaなど)でのEnumは、単に「名前付き定数のリスト」であることが多いですが、RustのEnumは「代数的データ型(Algebraic Data Types)」に近い性質を持っており、はるかに強力です。 - -## Enumの定義と値の保持 - -最も基本的なEnumの使い方は、C言語などと同様に「ありうる値の列挙」です。しかし、RustのEnumの真価は、**各バリアント(選択肢)にデータを持たせることができる**点にあります。 - -例えば、IPアドレスを表現する場合を考えてみましょう。IPアドレスにはV4とV6があり、それぞれ異なる形式のデータを持ちます。 - -```rust:ip_address.rs -#[derive(Debug)] -enum IpAddr { - V4(u8, u8, u8, u8), // 4つのu8を持つ - V6(String), // Stringを持つ -} - -fn main() { - let home = IpAddr::V4(127, 0, 0, 1); - let loopback = IpAddr::V6(String::from("::1")); - - println!("Home: {:?}", home); - println!("Loopback: {:?}", loopback); -} -``` - -```rust-exec:ip_address.rs -Home: V4(127, 0, 0, 1) -Loopback: V6("::1") -``` - -### 構造体との違い - -これを構造体で実装しようとすると、「種類(kind)」フィールドと「データ」フィールドを持つ必要がありますが、V4の場合にV6用のフィールドが無駄になったり、型安全性が下がったりします。 -RustのEnumを使うと、**「V4なら必ず4つの数値がある」「V6なら文字列がある」**ということが型レベルで保証されます。 - -### あらゆる種類のデータを埋め込める - -Enumの各バリアントには、名前付きフィールドを持つ構造体のような形や、タプルのような形など、自由に定義できます。 - -```rust:message_enum.rs -#[derive(Debug)] -enum Message { - Quit, // データなし - Move { x: i32, y: i32 }, // 名前付きフィールド(構造体風) - Write(String), // 単一のString(タプル風) - ChangeColor(i32, i32, i32), // 3つのi32(タプル風) -} - -impl Message { - // Enumにもメソッドを定義できる - fn call(&self) { - println!("メッセージを処理します: {:?}", self); - } -} - -fn main() { - let m1 = Message::Write(String::from("hello")); - let m2 = Message::Move { x: 10, y: 20 }; - let m3 = Message::Quit; - - m1.call(); - m2.call(); - m3.call(); -} -``` - -```rust-exec:message_enum.rs -メッセージを処理します: Write("hello") -メッセージを処理します: Move { x: 10, y: 20 } -メッセージを処理します: Quit -``` - -## Option\ 型(Null安全性の核心) - -Rustには、他の多くの言語にある **Null(ヌル)が存在しません**。 -その代わり、標準ライブラリで定義された `Option` というEnumを使用します。これは「値が存在するかもしれないし、しないかもしれない」ことを表現します。 - -`Option` は以下のように定義されています(概念図): - -```rust -enum Option { - Some(T), // 値がある場合。Tは任意の型。 - None, // 値がない場合。 -} -``` - -### なぜこれが安全なのか? - -`Option` 型と `T` 型(例えば `i32`)は異なる型です。そのため、**「値がないかもしれないもの」を、チェックせずにそのまま計算に使うことがコンパイラレベルで禁止されます。** - -```rust:option_intro.rs -fn main() { - let some_number = Some(5); - let some_string = Some("a string"); - - // Noneの場合は型推論できないため、明示的に型を指定する必要がある - let absent_number: Option = None; - - let x: i8 = 5; - let y: Option = Some(5); - - // 以下の行はコンパイルエラーになります。 - // i8 と Option は足し算できません。 - // let sum = x + y; - - println!("x: {}", x); - // 値を取り出すには明示的な処理が必要(後述のmatchなどを使う) - println!("y is: {:?}", y); - println!("absent is: {:?}", absent_number); -} -``` - -```rust-exec:option_intro.rs -x: 5 -y is: Some(5) -absent is: None -``` - -値を使うためには、`Option` から `T` を取り出す処理(Nullチェックに相当)を必ず書かなければなりません。これにより、「うっかりNullを参照してクラッシュ」という事故を防げます。 - -## match フロー制御演算子 - -`match` は、Enumの値を処理するための最も強力なツールです。C言語やJavaの `switch` に似ていますが、より表現力が高く、コンパイラによる**網羅性チェック(Exhaustiveness Check)**があります。 - -### 網羅性チェック - -`match` は、あり得るすべてのパターンをカバーしなければなりません。一つでも漏れているとコンパイルエラーになります。 - -```rust:match_shapes.rs -enum Shape { - Circle(f64), // 半径 - Rectangle(f64, f64), // 幅, 高さ - Triangle(f64, f64, f64), // 3辺の長さ(簡略化のためヘロンの公式用) -} - -fn calculate_area(shape: Shape) -> f64 { - match shape { - // パターンマッチで中のデータを取り出す(バインディング) - Shape::Circle(radius) => { - std::f64::consts::PI * radius * radius - }, - Shape::Rectangle(w, h) => { - w * h - }, - Shape::Triangle(a, b, c) => { - let s = (a + b + c) / 2.0; - (s * (s - a) * (s - b) * (s - c)).sqrt() - } - } -} - -fn main() { - let c = Shape::Circle(10.0); - let r = Shape::Rectangle(3.0, 4.0); - - println!("円の面積: {:.2}", calculate_area(c)); - println!("長方形の面積: {:.2}", calculate_area(r)); -} -``` - -```rust-exec:match_shapes.rs -円の面積: 314.16 -長方形の面積: 12.00 -``` - -### Option\ と match - -`Option` の中身を取り出す際も `match` がよく使われます。 - -```rust:match_option.rs -fn plus_one(x: Option) -> Option { - match x { - None => None, - Some(i) => Some(i + 1), - } -} - -fn main() { - let five = Some(5); - let six = plus_one(five); - let none = plus_one(None); - - println!("Five: {:?}", five); - println!("Six: {:?}", six); - println!("None: {:?}", none); -} -``` - -```rust-exec:match_option.rs -Five: Some(5) -Six: Some(6) -None: None -``` - -### `_` プレースホルダー - -全ての値を個別に書きたくない場合、`_`(アンダースコア)を使って「その他すべて」にマッチさせることができます。これは `switch` 文の `default` に相当します。 - -```rust -let dice_roll = 9; -match dice_roll { - 3 => println!("3が出ました"), - 7 => println!("7が出ました"), - _ => println!("それ以外が出ました"), // 3, 7 以外はここに来る -} -``` - -## if let 記法 - -`match` は強力ですが、**「ある1つのパターンだけ処理して、他は全部無視したい」**という場合には記述が長くなりがちです。 -そのような場合に `if let` が便利です。 - -これは以下の `match` のシンタックスシュガー(糖衣構文)です。 - -```rust -// matchを使う場合(冗長) -let config = Some("config_value"); -match config { - Some(val) => println!("設定値: {}", val), - _ => (), // 何もしない -} -``` - -これと同じことを `if let` で書くと以下のようになります。 - -```rust:if_let_demo.rs -fn main() { - let config = Some("config_value"); - let missing: Option<&str> = None; - - // 「もし config が Some(val) というパターンにマッチするならブロックを実行」 - if let Some(val) = config { - println!("設定値があります: {}", val); - } - - // else も使えます - if let Some(val) = missing { - println!("設定値: {}", val); - } else { - println!("設定値がありません"); - } -} -``` - -```rust-exec:if_let_demo.rs -設定値があります: config_value -設定値がありません -``` - -`if let` を使うとコードが短くなりますが、`match` が強制する「網羅性チェック」の恩恵は失われます。状況に応じて使い分けましょう。 - -## この章のまとめ - - * **Enum(列挙型)**: RustのEnumは、異なる型や量のデータを各バリアントに持たせることができる(代数的データ型)。 - * **Option\**: `Some(T)` と `None` によって、Null安全を実現する。値を使うには `Option` の皮を剥く処理が必須となる。 - * **match**: パターンマッチングを行う制御フロー。コンパイラが全てのケースを網羅しているかチェックしてくれる。 - * **if let**: 単一のパターンだけを扱いたい場合の簡潔な記法。 - -### 練習問題 1: コインの分類機 - -アメリカの硬貨を表すEnum `Coin` を定義してください。 - - * バリアント: `Penny` (1セント), `Nickel` (5セント), `Dime` (10セント), `Quarter` (25セント) - * `Quarter` バリアントには、`UsState` というEnum(各州の名前を持つ)をデータとして持たせてください(例: `Quarter(UsState::Alaska)`)。 - * `Coin` を受け取り、その価値(セント単位)を文字列で返す関数 `value_in_cents` を `match` を使って実装してください。Quarterの場合は、その州の名前も同時に返してください。 - -```rust:practice7_1.rs -// UsState, Coin, value_in_cents を作成してください - -fn main() { - let coin1 = Coin::Penny; - let coin2 = Coin::Quarter(UsState::California); - - println!("Coin1 value: {}", value_in_cents(coin1)); - println!("Coin2 value: {}", value_in_cents(coin2)); -} -``` - -```rust-exec:practice7_1.rs -Coin1 value: 1 cent -Coin2 value: 25 cents from California -``` - -### 練習問題 2: 簡易計算機 - -2つの数値に対する操作を表すEnum `Operation` を定義し、計算を行ってください。 - -1. Enum `Operation` を定義します。 - * `Add`: 2つの `i32` を持つ - * `Subtract`: 2つの `i32` を持つ - * `Multiply`: 2つの `i32` を持つ - * `Divide`: 2つの `i32` を持つ -2. 関数 `calculate(op: Operation) -> Option` を実装してください。 - * `match` を使用して計算結果を返します。 - * 割り算の場合、0での除算(ゼロ除算)を防ぐため、分母が0なら `None` を返し、計算できるなら `Some(結果)` を返してください。他の演算は常に `Some` で返します。 -3. `main` 関数でいくつかのパターンを試し、結果を表示してください。 - -```rust:practice7_2.rs -// Operation enum と calculate 関数を実装してください - -fn main() { - let add = Operation::Add(5, 3); - let subtract = Operation::Subtract(10, 4); - let multiply = Operation::Multiply(6, 7); - let divide = Operation::Divide(20, 4); - let divide_by_zero = Operation::Divide(10, 0); - - println!("5 + 3 = {:?}", calculate(add)); - println!("10 - 4 = {:?}", calculate(subtract)); - println!("6 * 7 = {:?}", calculate(multiply)); - println!("20 / 4 = {:?}", calculate(divide)); - println!("10 / 0 = {:?}", calculate(divide_by_zero)); -} -``` - -```rust-exec:practice7_2.rs -5 + 3 = Some(8) -10 - 4 = Some(6) -6 * 7 = Some(42) -20 / 4 = Some(5) -10 / 0 = None -``` diff --git a/public/docs/rust-8.md b/public/docs/rust-8.md deleted file mode 100644 index 37ddb68..0000000 --- a/public/docs/rust-8.md +++ /dev/null @@ -1,312 +0,0 @@ -# 第8章: モジュールシステムとパッケージ管理 - -これまでの章では、関数や構造体を使ってコードを整理する方法を学びました。プログラムの規模が大きくなると、コードをさらに大きな単位で整理し、詳細を隠蔽(カプセル化)し、再利用性を高める必要が出てきます。 - -他の言語(C++のnamespace、Javaのpackage、Pythonのmoduleなど)での経験があれば、Rustのモジュールシステムも直感的に理解できる部分が多いですが、**「デフォルトで非公開(private)」**というRust特有の哲学や、ファイルシステムとの対応関係には注意が必要です。 - -この章では、Rustのコード整理の仕組みである「モジュールシステム」と、外部ライブラリを活用するための「パッケージ管理」について学びます。 - -## パッケージ、クレート、モジュール - -Rustのモジュールシステムは、以下の3つの概念で構成されています。 - -1. **パッケージ (Package):** `Cargo.toml` ファイルを持ち、1つ以上のクレートをビルドするための機能です。通常 `cargo new` で作成されるプロジェクト全体を指します。 -2. **クレート (Crate):** 木構造状のモジュール群からなるコンパイル単位です。 - * **バイナリクレート:** 実行可能な形式(`src/main.rs` がルート)。 - * **ライブラリクレート:** 他のプロジェクトから利用される形式(`src/lib.rs` がルート)。 -3. **モジュール (Module):** クレート内のコードをグループ化し、可視性(公開/非公開)を制御する仕組みです。 - -### モジュールの基本定義 (`mod`) - -モジュールは `mod` キーワードを使って定義します。モジュールの中に、さらにモジュール(サブモジュール)を入れることも可能です。 - -まずは、1つのファイル内でモジュールを定義して、その構造を見てみましょう。 - -```rust:simple_module.rs -// "restaurant" という名前のモジュールを定義 -mod restaurant { - // モジュール内に関数を定義 - // 注意: デフォルトでは親モジュールからアクセスできません(後述) - fn make_coffee() { - println!("コーヒーを淹れます"); - } - - // サブモジュール - mod front_of_house { - fn add_to_waitlist() { - println!("順番待ちリストに追加しました"); - } - } -} - -fn main() { - // restaurant::make_coffee(); - // restaurant::front_of_house::add_to_waitlist(); -} -``` - -```rust-exec:simple_module.rs -``` - -このコードはコンパイルに通りますが、`main` 関数から `make_coffee` などを呼び出そうとするとエラーになります。それは**可視性**の問題があるからです。 - -## 可視性と `pub` キーワード - -Rustのモジュールシステムの最大の特徴は、**「すべてのアイテム(関数、構造体、モジュールなど)は、デフォルトで非公開(private)」**であるという点です。 - - * **非公開:** 定義されたモジュール自身と、その子モジュールからのみアクセス可能。親モジュールからは見えません。 - * **公開 (`pub`):** 親モジュールや外部からアクセス可能になります。 - -親モジュール(この場合は `main` 関数がいるルート)から子モジュールの中身を使うには、明示的に `pub` をつける必要があります。 - -```rust:simple_module_with_pub.rs -mod restaurant { - // pubがないので、restaurantモジュール内からしか呼べない - fn make_coffee() { - println!("コーヒーを淹れます"); - } - - // pubをつけてサブモジュールも公開 - pub mod front_of_house { - // ここも公開関数にする - pub fn add_to_waitlist() { - println!("順番待ちリストに追加しました"); - } - } -} -fn main() { - // これで呼び出せるようになる - restaurant::front_of_house::add_to_waitlist(); - - // restaurant::make_coffee(); -} -``` - -```rust-exec:simple_module_with_pub.rs -順番待ちリストに追加しました -``` - -### 構造体の可視性 - -構造体に `pub` をつけた場合、**構造体そのものは公開されますが、フィールドはデフォルトで非公開のまま**です。フィールドごとに `pub` を決める必要があります。 - -```rust:struct_visibility.rs -mod kitchen { - pub struct Breakfast { - pub toast: String, // 公開フィールド - seasonal_fruit: String, // 非公開フィールド - } - - impl Breakfast { - // コンストラクタ(これがないと外部からインスタンスを作れない) - pub fn summer(toast: &str) -> Breakfast { - Breakfast { - toast: String::from(toast), - seasonal_fruit: String::from("peaches"), - } - } - } -} - -fn main() { - // コンストラクタ経由でインスタンス作成 - let mut meal = kitchen::Breakfast::summer("ライ麦パン"); - - // 公開フィールドは変更・参照可能 - meal.toast = String::from("食パン"); - println!("トースト: {}", meal.toast); - - // 非公開フィールドにはアクセスできない - // meal.seasonal_fruit = String::from("blueberries"); // エラー! -} -``` - -```rust-exec:struct_visibility.rs -トースト: 食パン -``` - -## use キーワードとパス - -モジュールの階層が深くなると、毎回 `restaurant::front_of_house::add_to_waitlist()` のようにフルパスを書くのは面倒です。 -`use` キーワードを使うと、パスをスコープに持ち込み、短い名前で呼び出せるようになります。これは他言語の `import` に相当します。 - -### 絶対パスと相対パス - -パスの指定方法には2種類あります。 - -1. **絶対パス:** クレートのルート(`crate`)から始まるパス。 -2. **相対パス:** 現在のモジュール(`self`)や親モジュール(`super`)から始まるパス。 - -```rust:use_paths.rs -mod sound { - pub mod instrument { - pub fn clarinet() { - println!("クラリネットの音色♪"); - } - } -} - -// 絶対パスで持ち込む -use crate::sound::instrument; - -// 相対パスの場合(このファイル内であれば以下も同じ意味) -// use self::sound::instrument; - -fn main() { - // useのおかげで、直接 instrument を使える - instrument::clarinet(); - instrument::clarinet(); -} -``` - -```rust-exec:use_paths.rs -クラリネットの音色♪ -``` - -> **慣習:** 関数を持ち込むときは、親モジュールまでを `use` して `親::関数()` と呼び出すのが一般的です(関数の出処が明確になるため)。一方、構造体やEnumは完全なパスを指定して直接名前だけで使えるようにすることが多いです。 - -## モジュールのファイル分割 - -これまでは説明のために1つのファイルにすべてのモジュールを書いてきましたが、実際の開発ではファイルを分割します。 - -Rustでは**「ファイルシステム上の構造」**と**「モジュール階層」**が対応します。 - -例えば、`main.rs` と `front_of_house.rs` がある場合: - -```rust:main.rs -// ファイルの中身をモジュールとして宣言 -// これにより、コンパイラは front_of_house.rs を探しに行きます -mod front_of_house; - -pub use crate::front_of_house::hosting; - -fn main() { - hosting::add_to_waitlist(); -} -``` - -```rust:front_of_house.rs -// ここには "mod front_of_house { ... }" の枠は書かない -pub mod hosting { - pub fn add_to_waitlist() { - println!("リストに追加しました"); - } -} -``` - -```rust-exec:main.rs -リストに追加しました -``` - -さらに `front_of_house` の中にサブモジュールを作りたい場合は、ディレクトリを作成します。 - - * `src/main.rs` - * `src/front_of_house/` (ディレクトリ) - * `mod.rs` (または `front_of_house.rs` と同義。ディレクトリのエントリーポイント) - * `hosting.rs` - -このように、Rustはファイルやディレクトリの存在だけで自動的にモジュールを認識するのではなく、**親となるファイルで `mod xxx;` と宣言されたものだけ**をコンパイル対象として認識します。これがC\#やJavaなどの「フォルダにあるものは全部パッケージに含まれる」言語との大きな違いです。 - -## 外部クレートの利用 - -Rustのパッケージ管理システムであるCargoを使うと、外部ライブラリ(クレート)を簡単に利用できます。 - -### Cargo.toml への追加 - -`Cargo.toml` の `[dependencies]` セクションに、使いたいクレートの名前とバージョンを記述します。例えば、乱数を生成する `rand` クレートを使う場合: - -```toml -[dependencies] -rand = "0.8.5" -``` - -### コードでの利用 - -外部クレートも、プロジェクト内のモジュールと同じように `use` でスコープに持ち込んで使用します。 - -```rust -use std::collections::HashMap; // 標準ライブラリも 'std' という外部クレートのような扱い - -// 外部クレート rand を使用する想定のコード -use rand::Rng; - -fn main() { - let mut scores = HashMap::new(); - scores.insert("Blue", 10); - scores.insert("Yellow", 50); - - println!("スコア: {:?}", scores); - - let secret_number = rand::thread_rng().gen_range(1..101); - println!("乱数: {}", secret_number); -} -``` - -標準ライブラリ(`std`)はデフォルトで利用可能ですが、それ以外のクレートは crates.io (Rustの公式パッケージレジストリ)から自動的にダウンロード・ビルドされます。 - -> 注: my.code(); のオンライン実行環境では外部クレートは使用できません。 - -## 第8章のまとめ - - * **パッケージとクレート:** `cargo new` で作るのがパッケージ、生成されるバイナリやライブラリがクレートです。 - * **モジュール:** コードを整理する箱です。`mod` で定義します。 - * **可視性:** すべてのアイテムはデフォルトで**非公開 (private)** です。公開するには `pub` をつけます。 - * **パスとuse:** `use` キーワードでモジュールへのパスを省略(インポート)できます。絶対パス(`crate::`)と相対パス(`self::`, `super::`)があります。 - * **ファイル分割:** `mod filename;` と宣言することで、別ファイルのコードをサブモジュールとして読み込みます。 - -この章の内容を理解することで、大規模なアプリケーション開発への準備が整いました。 - -### 練習問題1:ライブラリの設計 - -以下の仕様に従って、架空の図書館システムモジュールを作成してください。 - -1. `library` という親モジュールを作成する。 -2. その中に `books` というサブモジュールを作成する。 -3. `books` モジュールの中に `Book` 構造体を作成する。フィールドは `title` (String, 公開) と `isbn` (String, 非公開) とする。 -4. `Book` 構造体に、新しい本を作成するコンストラクタ `new(title: &str)` を実装する(ISBNは内部で適当な文字列を設定する)。 -5. `main` 関数から `library::books::Book` を使って本を作成し、タイトルを表示するコードを書く。 - - - -```rust:practice8_1.rs - -fn main() { - let my_book = library::books::Book::new("Rust入門"); - println!("本のタイトル: {}", my_book.title); -} -``` -```rust:library/mod.rs -``` -```rust:library/books.rs -``` - -```rust-exec:practice8_1.rs -本のタイトル: Rust入門 -``` - -### 練習問題2:パスと可視性の修正 - -以下のコードは可視性の設定とパスの指定が誤っているためコンパイルできません。修正して正常に「ネットワーク接続完了」と表示されるようにしてください。 - -```rust:practice8_2.rs -mod network { - fn connect() { - println!("ネットワーク接続完了"); - } - - mod server { - fn start() { - // 親モジュールのconnectを呼びたい - connect(); // ここが間違っている - } - } -} - -fn main() { - // ネットワークモジュールのconnectを呼びたい - connect(); // ここも間違っている -} -``` - -```rust-exec:practice8_2.rs -``` diff --git a/public/docs/rust-9.md b/public/docs/rust-9.md deleted file mode 100644 index 0854d86..0000000 --- a/public/docs/rust-9.md +++ /dev/null @@ -1,323 +0,0 @@ -# 第9章: 一般的なコレクションと文字列 - -これまでの章では、配列やタプルといった固定長のデータ構造を扱ってきました。これらはスタックに格納されるため高速ですが、コンパイル時にサイズが決まっている必要があります。 - -本章では、Rustの標準ライブラリが提供する、**ヒープ領域**にデータを格納する動的なコレクションについて学びます。これらは実行時にサイズを変更可能です。特に、他の言語経験者が躓きやすい「Rustにおける文字列(UTF-8)の扱い」には重点を置いて解説します。 - -主に以下の3つを扱います。 - -1. **ベクタ (`Vec`)**: 可変長のリスト。 -2. **文字列 (`String`)**: UTF-8エンコードされたテキスト。 -3. **ハッシュマップ (`HashMap`)**: キーと値のペア。 - -## ベクタ (`Vec`):可変長配列 - -ベクタは、同じ型の値をメモリ上に連続して配置するデータ構造です。C++の `std::vector` や Javaの `ArrayList`、Pythonのリストに近いものです。 - -### ベクタの作成と更新 - -`Vec::new()` 関数または `vec!` マクロを使用して作成します。要素を追加するには `push` メソッドを使いますが、ベクタを変更するためには `mut` で可変にする必要があります。 - -```rust:vector_basics.rs -fn main() { - // 空のベクタを作成(型注釈が必要な場合がある) - let mut v: Vec = Vec::new(); - v.push(5); - v.push(6); - v.push(7); - - // vec!マクロを使うと型推論が効くため記述が楽 - let mut v2 = vec![1, 2, 3]; - v2.push(4); - - println!("v: {:?}", v); - println!("v2: {:?}", v2); - - // popで末尾の要素を削除して取得(Optionを返す) - let last = v2.pop(); - println!("Popped: {:?}", last); - println!("v2 after pop: {:?}", v2); -} -``` - -```rust-exec:vector_basics.rs -v: [5, 6, 7] -v2: [1, 2, 3, 4] -Popped: Some(4) -v2 after pop: [1, 2, 3] -``` - -### 要素へのアクセス - -要素へのアクセスには「インデックス記法 `[]`」と「`get` メソッド」の2通りの方法があります。安全性において大きな違いがあります。 - - * `&v[i]`: 存在しないインデックスにアクセスすると**パニック**を起こします。 - * `v.get(i)`: `Option<&T>` を返します。範囲外の場合は `None` になるため、安全に処理できます。 - - - -```rust:vector_access.rs -fn main() { - let v = vec![10, 20, 30, 40, 50]; - - // 方法1: インデックス(確実に存在するとわかっている場合に使用) - let third: &i32 = &v[2]; - println!("3番目の要素は {}", third); - - // 方法2: getメソッド(範囲外の可能性がある場合に使用) - match v.get(100) { - Some(third) => println!("101番目の要素は {}", third), - None => println!("101番目の要素はありません"), - } - - // イテレーション - // &v とすることで所有権を移動させずに参照でループする - print!("要素: "); - for i in &v { - print!("{} ", i); - } - println!(); - - // 値を変更しながらイテレーション - let mut v_mut = vec![1, 2, 3]; - for i in &mut v_mut { - *i += 50; // 参照外し演算子(*)を使って値を書き換える - } - println!("変更後: {:?}", v_mut); -} -``` - -```rust-exec:vector_access.rs -3番目の要素は 30 -101番目の要素はありません -要素: 10 20 30 40 50 -変更後: [51, 52, 53] -``` - -## 文字列 (`String`) と UTF-8 - -Rustにおける文字列は、他の言語経験者にとって最も混乱しやすい部分の一つです。 -Rustの文字列は、**UTF-8エンコードされたバイトのコレクション**として実装されています。 - -### `String` と `&str` の違い(復習) - - * **`String`**: 所有権を持つ、伸長可能な、ヒープ上の文字列(`Vec` のラッパー)。 - * **`&str` (文字列スライス)**: どこか(バイナリ領域やヒープ領域)にある文字列データへの参照。 - -### 文字列の操作 - -`String` は `Vec` と同様に `push_str` や `+` 演算子で結合できます。 - -```rust:string_ops.rs -fn main() { - let mut s = String::from("foo"); - s.push_str("bar"); // 文字列スライスを追加 - s.push('!'); // 1文字追加 - println!("{}", s); - - let s1 = String::from("Hello, "); - let s2 = String::from("World!"); - - // + 演算子を使用。 - // s1はムーブされ、以降使用できなくなることに注意 - // シグネチャは fn add(self, s: &str) -> String に近いため - let s3 = s1 + &s2; - - println!("{}", s3); - // println!("{}", s1); // コンパイルエラー:s1はムーブ済み - - // format!マクロを使うと所有権を奪わず、読みやすく結合できる - let s4 = String::from("tic"); - let s5 = String::from("tac"); - let s6 = String::from("toe"); - - let s_all = format!("{}-{}-{}", s4, s5, s6); - println!("{}", s_all); -} -``` - -```rust-exec:string_ops.rs -foobar! -Hello, World! -tic-tac-toe -``` - -### なぜインデックスアクセスができないのか? - -多くの言語では `s[0]` で1文字目を取得できますが、Rustでは**コンパイルエラー**になります。 - -Rustの文字列はUTF-8です。ASCII文字は1バイトですが、日本語のような文字は3バイト(またはそれ以上)を使用します。 - - * `"A"` -\> `[0x41]` (1バイト) - * `"あ"` -\> `[0xE3, 0x81, 0x82]` (3バイト) - -もし `"あ"` という文字列に対して `s[0]` で1バイト目を取得できたとしても、それは `0xE3` という意味のないバイト値であり、プログラマが期待する「あ」ではありません。Rustはこの誤解を防ぐために、インデックスアクセスを禁止しています。 - -文字列の中身を見るには、「バイトとして見る」か「文字(スカラ値)として見る」かを明示する必要があります。 - -```rust:string_utf8.rs -fn main() { - let s = "こんにちは"; // UTF-8で各文字3バイト - - // NG: s[0] はコンパイルエラー - - // 文字(char)として反復処理 - // RustのcharはUnicodeスカラ値(4バイト) - print!("Chars: "); - for c in s.chars() { - print!("{} ", c); - } - println!(); - - // バイトとして反復処理 - print!("Bytes: "); - for b in s.bytes() { - print!("{:x} ", b); // 16進数で表示 - } - println!(); - - // 部分文字列(スライス)の取得には範囲指定が必要 - // ただし、文字の境界に合わないバイトを指定すると実行時にパニックする - let s_slice = &s[0..3]; // 最初の3バイト=「こ」 - println!("Slice: {}", s_slice); -} -``` - -```rust-exec:string_utf8.rs -Chars: こ ん に ち は -Bytes: e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 8a -Slice: こ -``` - -## ハッシュマップ (`HashMap`) - -ハッシュマップは、キーと値をマッピングしてデータを格納します。Pythonの `dict`、JavaScriptの `Map` やオブジェクト、Rubyの `Hash` に相当します。標準ライブラリの `std::collections` モジュールからインポートする必要があります。 - -### 基本的な操作 - -```rust:hashmap_demo.rs -use std::collections::HashMap; - -fn main() { - let mut scores = HashMap::new(); - - // 挿入 - scores.insert(String::from("Blue"), 10); - scores.insert(String::from("Yellow"), 50); - - // 値の取得(getはOption<&V>を返す) - let team_name = String::from("Blue"); - if let Some(score) = scores.get(&team_name) { - println!("{}: {}", team_name, score); - } - - // 反復処理(順序は保証されない) - for (key, value) in &scores { - println!("{}: {}", key, value); - } -} -``` - -```rust-exec:hashmap_demo.rs -Blue: 10 -Yellow: 50 -Blue: 10 -``` - -### 所有権の移動 - -`HashMap` にキーや値を挿入すると、`String` のような所有権を持つ型はマップ内に**ムーブ**されます(`i32` のような `Copy` トレイトを持つ型はコピーされます)。挿入後に元の変数を使おうとするとエラーになります。 - -### エントリ API による更新 - -「キーが存在しなければ値を挿入し、存在すれば何もしない(あるいは値を更新する)」というパターンは非常に一般的です。Rustでは `entry` APIを使うとこれを簡潔に書けます。 - -```rust:hashmap_update.rs -use std::collections::HashMap; - -fn main() { - let mut scores = HashMap::new(); - scores.insert(String::from("Blue"), 10); - - // 上書き(同じキーでinsertすると値は上書きされる) - scores.insert(String::from("Blue"), 25); - println!("Blue updated: {:?}", scores); - - // キーがない場合のみ挿入 (or_insert) - scores.entry(String::from("Yellow")).or_insert(50); - scores.entry(String::from("Blue")).or_insert(50); // 既に25があるので無視される - println!("Entry check: {:?}", scores); - - // 既存の値に基づいて更新(単語の出現回数カウントなど) - let text = "hello world wonderful world"; - let mut map = HashMap::new(); - - for word in text.split_whitespace() { - // or_insertは挿入された値への可変参照(&mut V)を返す - let count = map.entry(word).or_insert(0); - *count += 1; // 参照外ししてインクリメント - } - - println!("Word count: {:?}", map); -} -``` - -```rust-exec:hashmap_update.rs -Blue updated: {"Blue": 25} -Entry check: {"Blue": 25, "Yellow": 50} -Word count: {"world": 2, "hello": 1, "wonderful": 1} -``` - -## この章のまとめ - - * **`Vec`**: 同じ型の要素を可変長で保持します。範囲外アクセスには注意し、必要なら `get` メソッドを使用します。 - * **`String`**: UTF-8エンコードされたバイト列のラッパーです。インデックス `[i]` によるアクセスは禁止されており、文字として扱うには `.chars()` を、バイトとして扱うには `.bytes()` を使用します。 - * **`HashMap`**: キーバリューストアです。`entry` APIを使用すると、「存在確認してから挿入・更新」という処理を効率的かつ安全に記述できます。 - -### 練習問題1:整数のリスト分析 - -整数のベクタ `vec![1, 10, 5, 2, 10, 5, 20, 5]` が与えられたとき、以下の3つを計算して表示するプログラムを作成してください。 - -1. **平均値 (Mean)** -2. **中央値 (Median)**: リストをソートしたときに真ん中に来る値。 -3. **最頻値 (Mode)**: 最も頻繁に出現する値(ヒント:ハッシュマップを使って出現回数を数えます)。 - -```rust:practice9_1.rs -fn main() { - let numbers = vec![1, 10, 5, 2, 10, 5, 20, 5]; - - -} -``` -```rust-exec:practice9_1.rs -平均値: 7.25 -中央値: 5 -最頻値: 5 -``` - -### 問題2:ピッグ・ラテン (Pig Latin) 変換 - -文字列を「ピッグ・ラテン」と呼ばれる言葉遊びに変換する関数を作成してください。ルールは以下の通りです。 - -1. 単語が**母音** (a, i, u, e, o) で始まる場合、単語のお尻に `-hay` を追加します。 - * 例: `apple` -\> `apple-hay` -2. 単語が**子音**で始まる場合、最初の文字を単語のお尻に移動し、`-ay` を追加します。 - * 例: `first` -\> `irst-fay` - -アルファベットのみ、小文字のみの想定で構いません。 - -```rust:practice9_2.rs -fn pig_latin(word: &str) -> String { - // ここに変換ロジックを実装 - - -} -fn main() { - println!("{}", pig_latin("apple")); - println!("{}", pig_latin("first")); -} -``` -```rust-exec:practice9_2.rs -apple-hay -irst-fay -``` diff --git a/public/docs/rust/0-intro/-intro.md b/public/docs/rust/0-intro/-intro.md new file mode 100644 index 0000000..b907c62 --- /dev/null +++ b/public/docs/rust/0-intro/-intro.md @@ -0,0 +1 @@ +Rustは、Mozillaによって開始され、現在はRust Foundationによって管理されているオープンソースのシステムプログラミング言語です。Stack OverflowのDeveloper Surveyで長年「最も愛されている言語」に選ばれ続けているのには理由があります。 diff --git a/public/docs/rust/0-intro/1-0-features.md b/public/docs/rust/0-intro/1-0-features.md new file mode 100644 index 0000000..cdc292e --- /dev/null +++ b/public/docs/rust/0-intro/1-0-features.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-features +title: Rustの特徴:なぜ学ぶのか? +level: 2 +--- + +## Rustの特徴:なぜ学ぶのか? + +経験豊富なプログラマにとって、Rustは「トレードオフを解消する」言語として映るはずです。 diff --git a/public/docs/rust/0-intro/1-1-memory-safety.md b/public/docs/rust/0-intro/1-1-memory-safety.md new file mode 100644 index 0000000..f1cc490 --- /dev/null +++ b/public/docs/rust/0-intro/1-1-memory-safety.md @@ -0,0 +1,12 @@ +--- +id: rust-intro-memory-safety +title: 1. メモリ安全性(Memory Safety) +level: 3 +--- + +### 1\. メモリ安全性(Memory Safety) + +C/C++ではプログラマの責任であったメモリ管理を、Rustは**所有権(Ownership)**というコンパイル時のシステムで保証します。 + + * ガベージコレクタ(GC)は**存在しません**。 + * ランタイムコストなしに、ダングリングポインタや二重解放、バッファオーバーフローをコンパイル段階で防ぎます。 diff --git a/public/docs/rust/0-intro/1-2-zero-cost.md b/public/docs/rust/0-intro/1-2-zero-cost.md new file mode 100644 index 0000000..02dc55d --- /dev/null +++ b/public/docs/rust/0-intro/1-2-zero-cost.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-zero-cost +title: 2. ゼロコスト抽象化(Zero-cost Abstractions) +level: 3 +--- + +### 2\. ゼロコスト抽象化(Zero-cost Abstractions) + +「使わないものにはコストを払わない。使うものについては、手書きのコード以上のコストをかけない」というC++の哲学を継承しています。イテレータや高階関数を使っても、最適化された低レベルコードと同等のパフォーマンスが得られます。 diff --git a/public/docs/rust/0-intro/1-3-concurrency.md b/public/docs/rust/0-intro/1-3-concurrency.md new file mode 100644 index 0000000..6df2408 --- /dev/null +++ b/public/docs/rust/0-intro/1-3-concurrency.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-concurrency +title: 3. 安全な並行性(Fearless Concurrency) +level: 3 +--- + +### 3\. 安全な並行性(Fearless Concurrency) + +多くの言語で並行処理はバグの温床(データ競合など)ですが、Rustではコンパイラがデータ競合を検知し、コンパイルエラーとして報告します。「コンパイルが通れば、並行性のバグを含んでいる可能性は低い」という安心感を持ってコーディングできます。 diff --git a/public/docs/rust/0-intro/2-0-setup.md b/public/docs/rust/0-intro/2-0-setup.md new file mode 100644 index 0000000..4c0a3e9 --- /dev/null +++ b/public/docs/rust/0-intro/2-0-setup.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-setup +title: 開発環境の構築 +level: 2 +--- + +## 開発環境の構築 + +Rustの開発環境は非常にモダンで、バージョン管理やパッケージ管理が統合されています。 diff --git a/public/docs/rust/0-intro/2-1-rustup.md b/public/docs/rust/0-intro/2-1-rustup.md new file mode 100644 index 0000000..23edef4 --- /dev/null +++ b/public/docs/rust/0-intro/2-1-rustup.md @@ -0,0 +1,25 @@ +--- +id: rust-intro-rustup +title: rustup のインストール +level: 3 +--- + +### `rustup` のインストール + +Rustのバージョンマネージャである `rustup` を使用してインストールするのが標準的です。 + +macOS / Linux / WSL (Windows Subsystem for Linux) の場合: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Windowsの場合: +公式サイト(rust-lang.org)から `rustup-init.exe` をダウンロードして実行します(C++ビルドツールが必要になる場合があります)。 + +インストール後、以下のコマンドでバージョンが表示されれば成功です。 + +```bash +rustc --version +cargo --version +``` diff --git a/public/docs/rust/0-intro/3-0-hello-world.md b/public/docs/rust/0-intro/3-0-hello-world.md new file mode 100644 index 0000000..ed07d23 --- /dev/null +++ b/public/docs/rust/0-intro/3-0-hello-world.md @@ -0,0 +1,30 @@ +--- +id: rust-intro-hello-world +title: Hello World (rustc を直接使う) +level: 2 +--- + +## Hello World (`rustc` を直接使う) + +まずは、ビルドシステムを使わずにコンパイラ `rustc` を直接叩いて、Rustプログラムの最小単位を見てみましょう。 + +以下のコードを記述します。 + +```rust:hello.rs +fn main() { + // !がついているのは関数ではなく「マクロ」の呼び出しです + println!("Hello, world from rustc!"); +} +``` + +コンパイルと実行は以下の手順で行います。 + +1. コンパイル: `rustc hello.rs` + * これにより、バイナリファイル(Windowsなら`.exe`)が生成されます。 +2. 実行: `./hello` (Windowsなら `.\hello.exe`) + + + +```rust-exec:hello.rs +Hello, world from rustc! +``` diff --git a/public/docs/rust/0-intro/3-1-points.md b/public/docs/rust/0-intro/3-1-points.md new file mode 100644 index 0000000..7fa141c --- /dev/null +++ b/public/docs/rust/0-intro/3-1-points.md @@ -0,0 +1,11 @@ +--- +id: rust-intro-points +title: ポイント +level: 3 +--- + +### ポイント + + * **`fn main() { ... }`**: エントリポイントです。引数や戻り値がない場合、このように記述します。 + * **`println!`**: 末尾に `!` がついているのは**マクロ**であることを示しています。Rustでは可変長引数を取る機能などをマクロとして実装しています。 + * **セミコロン `;`**: 文の終わりには必須です。 diff --git a/public/docs/rust/0-intro/4-0-cargo.md b/public/docs/rust/0-intro/4-0-cargo.md new file mode 100644 index 0000000..b4572cf --- /dev/null +++ b/public/docs/rust/0-intro/4-0-cargo.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-cargo +title: Cargo:Rustのビルドシステムとパッケージマネージャ +level: 2 +--- + +## Cargo:Rustのビルドシステムとパッケージマネージャ + +実際の開発では `rustc` を直接使うことは稀です。公式のビルドシステム兼パッケージマネージャである **Cargo** を使用します。Node.jsにおける `npm`、Pythonにおける `pip` + `venv` のような存在ですが、それ以上にプロジェクトのライフサイクル全体を管理します。 diff --git a/public/docs/rust/0-intro/4-1-cargo-new.md b/public/docs/rust/0-intro/4-1-cargo-new.md new file mode 100644 index 0000000..da3d758 --- /dev/null +++ b/public/docs/rust/0-intro/4-1-cargo-new.md @@ -0,0 +1,20 @@ +--- +id: rust-intro-cargo-new +title: プロジェクトの作成 (cargo new) +level: 3 +--- + +### プロジェクトの作成 (`cargo new`) + +新しいプロジェクトを作成します。 + +```bash +cargo new hello_cargo +cd hello_cargo +``` + +このコマンドにより、以下のディレクトリ構造が生成されます。 + + * **`Cargo.toml`**: パッケージのマニフェストファイル(依存関係やメタデータを記述)。 + * **`src/main.rs`**: ソースコード。 + * **`.gitignore`**: Gitの設定ファイルも自動生成されます。 diff --git a/public/docs/rust/0-intro/4-2-cargo-commands.md b/public/docs/rust/0-intro/4-2-cargo-commands.md new file mode 100644 index 0000000..a841c3d --- /dev/null +++ b/public/docs/rust/0-intro/4-2-cargo-commands.md @@ -0,0 +1,33 @@ +--- +id: rust-intro-cargo-commands +title: Cargoの主要コマンド +level: 3 +--- + +### Cargoの主要コマンド + +生成されたプロジェクトで、以下のコマンドを試してみましょう。 + +1. **`cargo check`** + + * **重要**: コンパイルが可能かどうかのチェックだけを行い、実行ファイルは生成しません。高速に動作するため、コーディング中の構文チェックとして頻繁に使用します。 + +2. **`cargo build`** + + * デバッグビルドを行います。成果物は `target/debug/` に生成されます。コンパイル速度重視で、最適化は行われません。 + +3. **`cargo run`** + + * ビルドと実行を一度に行います。開発中はこれが最も使われます。 + +4. **`cargo build --release`** + + * リリースビルドを行います。`target/release/` に生成されます。コンパイル時間は長くなりますが、強力な最適化がかかり、実行速度が劇的に向上します。 + +`src/main.rs` の中身は以下のようになっています(生成時デフォルト)。 + +```rust +fn main() { + println!("Hello, world!"); +} +``` diff --git a/public/docs/rust/0-intro/5-0-fmt-lint.md b/public/docs/rust/0-intro/5-0-fmt-lint.md new file mode 100644 index 0000000..3e5e685 --- /dev/null +++ b/public/docs/rust/0-intro/5-0-fmt-lint.md @@ -0,0 +1,9 @@ +--- +id: rust-intro-fmt-lint +title: フォーマッタとリンタ +level: 2 +--- + +## フォーマッタとリンタ + +Rustは「公式のスタイル」を重視する言語です。議論の余地をなくすために強力なツールが標準で用意されています。 diff --git a/public/docs/rust/0-intro/5-1-rustfmt.md b/public/docs/rust/0-intro/5-1-rustfmt.md new file mode 100644 index 0000000..367c70b --- /dev/null +++ b/public/docs/rust/0-intro/5-1-rustfmt.md @@ -0,0 +1,15 @@ +--- +id: rust-intro-rustfmt +title: rustfmt (コードフォーマッタ) +level: 3 +--- + +### `rustfmt` (コードフォーマッタ) + +Go言語の `gofmt` のように、コードを自動整形します。 + +```bash +cargo fmt +``` + +多くのエディタ(VS Codeなど)では保存時に自動実行されるよう設定するのが一般的です。 diff --git a/public/docs/rust/0-intro/5-2-clippy.md b/public/docs/rust/0-intro/5-2-clippy.md new file mode 100644 index 0000000..940ee04 --- /dev/null +++ b/public/docs/rust/0-intro/5-2-clippy.md @@ -0,0 +1,15 @@ +--- +id: rust-intro-clippy +title: clippy (リンタ) +level: 3 +--- + +### `clippy` (リンタ) + +単なるスタイルチェックだけでなく、パフォーマンスの改善案や、Rustらしい書き方(Idiomatic Rust)を提案してくれます。 + +```bash +cargo clippy +``` + +例えば、無駄な計算や非推奨なAPIの使用などを指摘してくれるため、学習中はこのコマンドの警告に従うだけでRustの理解が深まります。 diff --git a/public/docs/rust/1-basics/-intro.md b/public/docs/rust/1-basics/-intro.md new file mode 100644 index 0000000..cd2dbba --- /dev/null +++ b/public/docs/rust/1-basics/-intro.md @@ -0,0 +1,5 @@ +他の言語での開発経験がある方にとって、Rustの学習で最初に直面するカルチャーショックが「変数の扱い」です。 + +C++やJava、Pythonなどでは「変数はデフォルトで書き換え可能(Mutable)」であることが一般的ですが、Rustでは**「デフォルトで不変(Immutable)」**です。この設計こそが、Rustがコンパイル時に多くのバグを排除できる理由の根幹にあります。 + +第2章では、Rustの構文の基礎と、その背後にある哲学を学びます。 diff --git a/public/docs/rust/1-basics/1-0-let-mut.md b/public/docs/rust/1-basics/1-0-let-mut.md new file mode 100644 index 0000000..5da7763 --- /dev/null +++ b/public/docs/rust/1-basics/1-0-let-mut.md @@ -0,0 +1,9 @@ +--- +id: rust-basics-let-mut +title: 変数と可変性(let vs let mut) +level: 2 +--- + +## 変数と可変性(let vs let mut) + +Rustでは変数を宣言するために `let` キーワードを使用します。しかし、単に `let` で宣言された変数は、値を一度代入すると二度と変更できません。 diff --git a/public/docs/rust/1-basics/1-1-immutable.md b/public/docs/rust/1-basics/1-1-immutable.md new file mode 100644 index 0000000..663593f --- /dev/null +++ b/public/docs/rust/1-basics/1-1-immutable.md @@ -0,0 +1,35 @@ +--- +id: rust-basics-immutable +title: 不変変数(Immutable) +level: 3 +--- + +### 不変変数(Immutable) + +まず、以下のコードを見てください。これは意図的にコンパイルエラーになるように書かれています。 + +```rust:immutability_error.rs +fn main() { + let x = 5; + println!("xの値は: {}", x); + + // 値を再代入しようとする(コンパイルエラーになる) + x = 6; + println!("xの値は: {}", x); +} +``` + +```rust-exec:immutability_error.rs +error[E0384]: cannot assign twice to immutable variable `x` + --> immutability_error.rs:6:5 + | +2 | let x = 5; + | - first assignment to `x` +... +6 | x = 6; + | ^^^^^ cannot assign twice to immutable variable + | + +``` + +これをコンパイルしようとすると、Rustコンパイラは「不変変数 `x` に二度代入することはできない」と強く叱ってくれます。 diff --git a/public/docs/rust/1-basics/1-2-mutable.md b/public/docs/rust/1-basics/1-2-mutable.md new file mode 100644 index 0000000..e8f8321 --- /dev/null +++ b/public/docs/rust/1-basics/1-2-mutable.md @@ -0,0 +1,28 @@ +--- +id: rust-basics-mutable +title: 可変変数(Mutable) +level: 3 +--- + +### 可変変数(Mutable) + +値を変更したい場合は、`mut`(mutableの略)キーワードを明示的に付ける必要があります。これにより、「この変数は値が変わる可能性がある」とコードの読み手やコンパイラに宣言します。 + +```rust:mutability_demo.rs +fn main() { + // mut をつけることで可変になる + let mut x = 5; + println!("xの値は: {}", x); + + x = 6; + println!("xの値は: {}", x); +} +``` + +```rust-exec:mutability_demo.rs +xの値は: 5 +xの値は: 6 +``` + +> **なぜデフォルトが不変なのか?** +> 大規模なシステムや並行処理において、「いつの間にか値が変わっている」ことはバグの主要な原因です。Rustは「変更が必要な箇所だけを明示的にする」ことで、コードの予測可能性と安全性を高めています。 diff --git a/public/docs/rust/1-basics/2-0-shadowing.md b/public/docs/rust/1-basics/2-0-shadowing.md new file mode 100644 index 0000000..cf89412 --- /dev/null +++ b/public/docs/rust/1-basics/2-0-shadowing.md @@ -0,0 +1,48 @@ +--- +id: rust-basics-shadowing +title: シャドーイング(Shadowing) +level: 2 +--- + +## シャドーイング(Shadowing) + +Rustには、他の言語ではあまり見られない**シャドーイング(Shadowing)という強力な機能があります。これは、同じ名前の変数を `let` を使って再宣言**することで、前の変数を「覆い隠す」機能です。 + +`mut` との違いは2点あります: + +1. **型を変更できる**: `mut` は値の変更しかできませんが、シャドーイングは新しい変数を宣言しているのと同じなので、型を変えることができます。 +2. **不変に戻る**: 処理が終わった後、その変数は再び不変(immutable)になります。 + + + +```rust:shadowing.rs +fn main() { + let x = 5; + + // 最初のシャドーイング: 値を加工する + let x = x + 1; + + { + // 内側のスコープでのシャドーイング + let x = x * 2; + println!("内側のスコープでのx: {}", x); + } + + // スコープを抜けると、外側のxの値が参照される + println!("外側のスコープでのx: {}", x); + + // 型の変更を伴うシャドーイングの例 + let spaces = " "; // 文字列スライス型 + let spaces = spaces.len(); // usize型(整数) + + println!("スペースの数: {}", spaces); +} +``` + +```rust-exec:shadowing.rs +内側のスコープでのx: 12 +外側のスコープでのx: 6 +スペースの数: 3 +``` + +他言語では `spaces_str`、`spaces_num` のように変数名を変える場面でも、Rustでは変数の意味が変わらなければ同じ名前を再利用してコードをすっきり保つことができます。 diff --git a/public/docs/rust/1-basics/3-0-data-types.md b/public/docs/rust/1-basics/3-0-data-types.md new file mode 100644 index 0000000..c9dad4f --- /dev/null +++ b/public/docs/rust/1-basics/3-0-data-types.md @@ -0,0 +1,9 @@ +--- +id: rust-basics-data-types +title: 基本的なデータ型 +level: 2 +--- + +## 基本的なデータ型 + +Rustは静的型付け言語ですが、型推論が強力なため、多くの場合に型注釈は不要です。しかし、ここでは明示的な理解のために型を見ていきます。 diff --git a/public/docs/rust/1-basics/3-1-scalar.md b/public/docs/rust/1-basics/3-1-scalar.md new file mode 100644 index 0000000..f33b80a --- /dev/null +++ b/public/docs/rust/1-basics/3-1-scalar.md @@ -0,0 +1,17 @@ +--- +id: rust-basics-scalar +title: スカラー型(単一の値を表す) +level: 3 +--- + +### スカラー型(単一の値を表す) + +1. **整数型**: + * `i32` (デフォルト), `i64`, `u32`, `u8` など。 + * アーキテクチャ依存の `isize`, `usize` (インデックス用によく使われる)。 +2. **浮動小数点型**: + * `f64` (デフォルト, 倍精度), `f32` (単精度)。 +3. **論理値型**: + * `bool` (`true` / `false`)。 +4. **文字型**: + * `char`。Rustのcharは4バイトで、Unicodeスカラー値を扱います(ASCIIだけでなく、漢字や絵文字も1文字として扱えます)。シングルクォート `'` で囲みます。 diff --git a/public/docs/rust/1-basics/3-2-compound.md b/public/docs/rust/1-basics/3-2-compound.md new file mode 100644 index 0000000..2a6f96f --- /dev/null +++ b/public/docs/rust/1-basics/3-2-compound.md @@ -0,0 +1,54 @@ +--- +id: rust-basics-compound +title: 複合型(複数の値をまとめる) +level: 3 +--- + +### 複合型(複数の値をまとめる) + +1. **タプル (Tuple)**: + * 異なる型をまとめる固定長のリストです。 +2. **配列 (Array)**: + * **同じ型**の要素をまとめる**固定長**のリストです。 + * ※ 長さが可変のリストが必要な場合は、後の章で学ぶ「ベクタ (`Vec`)」を使います。配列はスタック上に確保されるため、高速ですがサイズ変更はできません。 + + + +```rust:data_types.rs +fn main() { + // --- スカラー型 --- + let int_val: i32 = -10; + let float_val: f64 = 2.5; + let char_val: char = 'あ'; // Unicode文字 + let emoji: char = '🦀'; + + println!("Scalar: {}, {}, {}, {}", int_val, float_val, char_val, emoji); + + // --- 複合型: タプル --- + // 型が異なっていてもOK + let tup: (i32, f64, u8) = (500, 6.4, 1); + + // 分解(Destructuring)して値を取り出す + let (x, y, z) = tup; + println!("Tuple分解: yの値は {}", y); + + // ドット記法でアクセス + println!("Tupleアクセス: 最初の値は {}", tup.0); + + // --- 複合型: 配列 --- + // 型と長さを指定: [型; 長さ] + let arr: [i32; 5] = [1, 2, 3, 4, 5]; + + // 同じ値で初期化する記法 let a = [3; 5] は [3, 3, 3, 3, 3] + let months = ["Jan", "Feb", "Mar"]; + + println!("Array: 2番目の月は {}", months[1]); +} +``` + +```rust-exec:data_types.rs +Scalar: -10, 2.5, あ, 🦀 +Tuple分解: yの値は 6.4 +Tupleアクセス: 最初の値は 500 +Array: 2番目の月は Feb +``` diff --git a/public/docs/rust/1-basics/4-0-summary.md b/public/docs/rust/1-basics/4-0-summary.md new file mode 100644 index 0000000..2c21fd9 --- /dev/null +++ b/public/docs/rust/1-basics/4-0-summary.md @@ -0,0 +1,13 @@ +--- +id: rust-basics-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **不変性がデフォルト**: 変数は `mut` をつけない限り変更できない。これにより安全性が担保される。 + * **シャドーイング**: `let` を重ねることで変数の再定義が可能。値の加工や型の変更に便利。 + * **データ型**: 整数、浮動小数点、文字(Unicode対応)、タプル、配列(固定長)などの基本型がある。 + +他の言語では「定数」として扱われるような使い方が、Rustでは「デフォルトの変数」になります。最初は窮屈に感じるかもしれませんが、「変化するものは目立つようにする」というRustの哲学に慣れると、コードの流れが追いやすくなることに気づくはずです。 diff --git a/public/docs/rust/1-basics/4-1-practice1.md b/public/docs/rust/1-basics/4-1-practice1.md new file mode 100644 index 0000000..8d04207 --- /dev/null +++ b/public/docs/rust/1-basics/4-1-practice1.md @@ -0,0 +1,21 @@ +--- +id: rust-basics-practice1 +title: '練習問題 1: 計算とシャドーイング' +level: 3 +--- + +### 練習問題 1: 計算とシャドーイング + +以下の手順に従ってコードを書いてください。 + +1. 変数 `x` を定義し、文字列 `"100"` を代入する。 +2. シャドーイングを使って、`x` を数値の `100` (文字列からパースする)に変換する。 + * ヒント: `"100".parse().expect("Not a number")` で数値に変換できます。 +3. さらにシャドーイングを使って、`x` に `50` を足した値にする。 +4. 最終的な `x` の値を表示する。 + +```rust:practice2_1.rs +``` + +```rust-exec:practice2_1.rs +``` diff --git a/public/docs/rust/1-basics/4-2-practice2.md b/public/docs/rust/1-basics/4-2-practice2.md new file mode 100644 index 0000000..eb7bfd2 --- /dev/null +++ b/public/docs/rust/1-basics/4-2-practice2.md @@ -0,0 +1,18 @@ +--- +id: rust-basics-practice2 +title: '練習問題 2: 配列とタプルの操作' +level: 3 +--- + +### 練習問題 2: 配列とタプルの操作 + +1. 浮動小数点型の配列 `data` を定義し、`[1.1, 2.2, 3.3]` で初期化する。 +2. タプル `result` を定義し、配列 `data` の最初の要素と最後の要素を格納する。 +3. タプルを分解(デストラクチャリング)して、それぞれの値を表示する。 +4. この一連の操作で `mut` が必要ない理由を考える。 + +```rust:practice2_2.rs +``` + +```rust-exec:practice2_2.rs +``` diff --git a/public/docs/rust/10-generics-traits/-intro.md b/public/docs/rust/10-generics-traits/-intro.md new file mode 100644 index 0000000..4fc4aab --- /dev/null +++ b/public/docs/rust/10-generics-traits/-intro.md @@ -0,0 +1,4 @@ +Rustチュートリアルの第11章へようこそ。 +この章では、Rustにおける抽象化とコード再利用の核心である「ジェネリクス」と「トレイト」について解説します。 + +他のプログラミング言語での経験がある方にとって、ジェネリクスは馴染み深い概念かもしれませんが、トレイトはクラス継承とは異なるアプローチをとります。これらを理解することで、柔軟かつ高速なRustコードが書けるようになります。 diff --git a/public/docs/rust/10-generics-traits/1-0-rust-approach.md b/public/docs/rust/10-generics-traits/1-0-rust-approach.md new file mode 100644 index 0000000..96cf030 --- /dev/null +++ b/public/docs/rust/10-generics-traits/1-0-rust-approach.md @@ -0,0 +1,15 @@ +--- +id: rust-generics-traits-rust-approach +title: 他言語との違い:Rustのアプローチ +level: 2 +--- + +## 他言語との違い:Rustのアプローチ + +Rustのジェネリクスとトレイトは、C++のテンプレートやHaskellの型クラスに近い性質を持っています。JavaやPythonなどのオブジェクト指向言語(OOP)出身の方が特に意識すべき違いは以下の通りです。 + +1. **継承の欠如:** Rustにはクラス継承(`extends`)がありません。代わりに**トレイト(Trait)**を使用して共通の振る舞いを定義し、構造体(Struct)や列挙型(Enum)に実装します。これは「継承よりコンポジション(構成)」を好む現代的な設計思想を言語レベルで強制するものです。 +2. **ダックタイピングとの違い:** Pythonなどの動的型付け言語では「アヒルのように歩き、アヒルのように鳴くなら、それはアヒルだ」というダックタイピングが一般的ですが、Rustではコンパイル時に厳密に型チェックを行います。「アヒルのように鳴く」能力があることを**トレイト境界**で明示する必要があります。 +3. **静的ディスパッチと単相化(Monomorphization):** Rustのジェネリクスは、コンパイル時に具体的な型ごとにコードを生成(展開)します。これを単相化と呼びます。 + * **メリット:** 実行時のオーバーヘッドがゼロ(C++のテンプレートと同様)。仮想関数テーブル(vtable)を参照する動的ディスパッチのようなコストがかかりません。 + * **デメリット:** バイナリサイズが若干大きくなる可能性があります。 diff --git a/public/docs/rust/10-generics-traits/2-0-generics.md b/public/docs/rust/10-generics-traits/2-0-generics.md new file mode 100644 index 0000000..3447065 --- /dev/null +++ b/public/docs/rust/10-generics-traits/2-0-generics.md @@ -0,0 +1,9 @@ +--- +id: rust-generics-traits-generics +title: ジェネリックなデータ型と関数 +level: 2 +--- + +## ジェネリックなデータ型と関数 + +ジェネリクスを使用すると、具体的なデータ型に依存しないコードを書くことができます。Rustでは慣習として `T` (Typeの略)などの短い大文字識別子を使用します。 diff --git a/public/docs/rust/10-generics-traits/2-1-generic-func.md b/public/docs/rust/10-generics-traits/2-1-generic-func.md new file mode 100644 index 0000000..caa2434 --- /dev/null +++ b/public/docs/rust/10-generics-traits/2-1-generic-func.md @@ -0,0 +1,30 @@ +--- +id: rust-generics-traits-generic-func +title: ジェネリックな関数 +level: 3 +--- + +### ジェネリックな関数 + +もっとも単純な例として、型 `T` の引数をそのまま返す関数を考えてみましょう。 + +```rust:generic_function.rs +fn inspect(value: T) { + // 実際にはここで何か処理を行うが、 + // Tが何であるか(DisplayやDebug等)を知らないと + // プリントすらできないため、ここでは単純にスコープを抜ける +} + +fn main() { + inspect(10); // i32 + inspect(3.14); // f64 + inspect("Hello"); // &str + println!("Compilation successful."); +} +``` + +```rust-exec:generic_function.rs +Compilation successful. +``` + +これだけではあまり役に立ちませんが、構文としては `fn 関数名<型パラメータ>(引数)` という形になります。 diff --git a/public/docs/rust/10-generics-traits/2-2-generic-struct.md b/public/docs/rust/10-generics-traits/2-2-generic-struct.md new file mode 100644 index 0000000..4db2995 --- /dev/null +++ b/public/docs/rust/10-generics-traits/2-2-generic-struct.md @@ -0,0 +1,41 @@ +--- +id: rust-generics-traits-generic-struct +title: ジェネリックな構造体 +level: 3 +--- + +### ジェネリックな構造体 + +構造体のフィールドの型をジェネリックにすることも可能です。 + +```rust:generic_struct.rs +// T型のxとyを持つPoint構造体 +struct Point { + x: T, + y: T, +} + +// 異なる型を持たせたい場合は複数のパラメータを使う +struct MixedPoint { + x: T, + y: U, +} + +fn main() { + let integer = Point { x: 5, y: 10 }; + let float = Point { x: 1.0, y: 4.0 }; + + // 以下の行はコンパイルエラーになる(xとyが同じTである必要があるため) + // let error = Point { x: 5, y: 4.0 }; + + let mixed = MixedPoint { x: 5, y: 4.0 }; + + println!("Int Point: x = {}, y = {}", integer.x, integer.y); + println!("Mixed Point: x = {}, y = {}", mixed.x, mixed.y); +} +``` + +```rust-exec:generic_struct.rs +Int Point: x = 5, y = 10 +Mixed Point: x = 5, y = 4 +``` diff --git a/public/docs/rust/10-generics-traits/3-0-traits.md b/public/docs/rust/10-generics-traits/3-0-traits.md new file mode 100644 index 0000000..3e8920a --- /dev/null +++ b/public/docs/rust/10-generics-traits/3-0-traits.md @@ -0,0 +1,9 @@ +--- +id: rust-generics-traits-traits +title: トレイトの定義と実装 +level: 2 +--- + +## トレイトの定義と実装 + +トレイトは、**「特定の型がどのような機能を持っているか」**を定義するものです。JavaやC\#の「インターフェース」に非常に近い概念です。 diff --git a/public/docs/rust/10-generics-traits/3-1-trait-def.md b/public/docs/rust/10-generics-traits/3-1-trait-def.md new file mode 100644 index 0000000..a85332e --- /dev/null +++ b/public/docs/rust/10-generics-traits/3-1-trait-def.md @@ -0,0 +1,15 @@ +--- +id: rust-generics-traits-trait-def +title: トレイトの定義 +level: 3 +--- + +### トレイトの定義 + +ここでは、「情報を要約できる」という振る舞いを表す `Summary` トレイトを定義してみましょう。 + +```rust +pub trait Summary { + fn summarize(&self) -> String; // メソッドのシグネチャのみ定義 +} +``` diff --git a/public/docs/rust/10-generics-traits/3-2-trait-impl.md b/public/docs/rust/10-generics-traits/3-2-trait-impl.md new file mode 100644 index 0000000..8118a79 --- /dev/null +++ b/public/docs/rust/10-generics-traits/3-2-trait-impl.md @@ -0,0 +1,68 @@ +--- +id: rust-generics-traits-trait-impl +title: トレイトの実装 +level: 3 +--- + +### トレイトの実装 + +定義したトレイトを具体的な型に実装するには、`impl トレイト名 for 型名` ブロックを使用します。 + +```rust:trait_impl.rs +// トレイトの定義 +trait Summary { + fn summarize(&self) -> String; + + // デフォルト実装を持たせることも可能 + fn greeting(&self) -> String { + String::from("(Read more below...)") + } +} + +struct NewsArticle { + headline: String, + location: String, + author: String, +} + +// NewsArticleにSummaryトレイトを実装 +impl Summary for NewsArticle { + fn summarize(&self) -> String { + format!("{}, by {} ({})", self.headline, self.author, self.location) + } +} + +struct Tweet { + username: String, + content: String, +} + +// TweetにSummaryトレイトを実装 +impl Summary for Tweet { + fn summarize(&self) -> String { + format!("{}: {}", self.username, self.content) + } + // greetingはデフォルト実装を使用するため記述しない +} + +fn main() { + let article = NewsArticle { + headline: String::from("Rust 1.0 Released"), + location: String::from("Internet"), + author: String::from("Core Team"), + }; + + let tweet = Tweet { + username: String::from("horse_ebooks"), + content: String::from("of course, as you probably already know, people"), + }; + + println!("Article: {}", article.summarize()); + println!("Tweet: {} {}", tweet.summarize(), tweet.greeting()); +} +``` + +```rust-exec:trait_impl.rs +Article: Rust 1.0 Released, by Core Team (Internet) +Tweet: horse_ebooks: of course, as you probably already know, people (Read more below...) +``` diff --git a/public/docs/rust/10-generics-traits/4-0-trait-bounds.md b/public/docs/rust/10-generics-traits/4-0-trait-bounds.md new file mode 100644 index 0000000..ec9c740 --- /dev/null +++ b/public/docs/rust/10-generics-traits/4-0-trait-bounds.md @@ -0,0 +1,9 @@ +--- +id: rust-generics-traits-trait-bounds +title: トレイト境界(Trait Bounds) +level: 2 +--- + +## トレイト境界(Trait Bounds) + +ジェネリック関数を作る際、型 `T` に対して「どんな型でもいい」のではなく、「特定の機能(トレイト)を持っている型だけ受け付けたい」という場合がほとんどです。これを制約するのが**トレイト境界**です。 diff --git a/public/docs/rust/10-generics-traits/4-1-basic-syntax.md b/public/docs/rust/10-generics-traits/4-1-basic-syntax.md new file mode 100644 index 0000000..60cdb2f --- /dev/null +++ b/public/docs/rust/10-generics-traits/4-1-basic-syntax.md @@ -0,0 +1,21 @@ +--- +id: rust-generics-traits-basic-syntax +title: 基本的な構文 +level: 3 +--- + +### 基本的な構文 + +以下の関数は、引数 `item` が `Summary` トレイトを実装していることを要求します。 + +```rust +// 糖衣構文(Syntactic Sugar) +fn notify(item: &impl Summary) { + println!("Breaking news! {}", item.summarize()); +} + +// 正式なトレイト境界の構文 +fn notify_formal(item: &T) { + println!("Breaking news! {}", item.summarize()); +} +``` diff --git a/public/docs/rust/10-generics-traits/4-2-where-clause.md b/public/docs/rust/10-generics-traits/4-2-where-clause.md new file mode 100644 index 0000000..4997eaf --- /dev/null +++ b/public/docs/rust/10-generics-traits/4-2-where-clause.md @@ -0,0 +1,58 @@ +--- +id: rust-generics-traits-where-clause +title: 複数のトレイト境界と where 句 +level: 3 +--- + +### 複数のトレイト境界と `where` 句 + +複数のトレイトが必要な場合(例えば「表示可能」かつ「要約可能」であってほしい場合)、`+` でつなぎます。制約が多くなりシグネチャが長くなる場合は、`where` 句を使って整理できます。 + +```rust:trait_bounds.rs +use std::fmt::Display; + +trait Summary { + fn summarize(&self) -> String; +} + +struct Book { + title: String, + author: String, +} + +impl Summary for Book { + fn summarize(&self) -> String { + format!("{} by {}", self.title, self.author) + } +} + +// Displayトレイトは標準ライブラリで定義されている(println!等で使用) +impl Display for Book { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Book({})", self.title) + } +} + +// itemはSummaryとDisplayの両方を実装している必要がある +fn notify(item: &T) +where + T: Summary + Display, +{ + println!("Notify: {}", item.summarize()); + println!("Display format: {}", item); +} + +fn main() { + let b = Book { + title: String::from("The Rust Book"), + author: String::from("Steve Klabnik"), + }; + + notify(&b); +} +``` + +```rust-exec:trait_bounds.rs +Notify: The Rust Book by Steve Klabnik +Display format: Book(The Rust Book) +``` diff --git a/public/docs/rust/10-generics-traits/5-0-std-traits.md b/public/docs/rust/10-generics-traits/5-0-std-traits.md new file mode 100644 index 0000000..f05730b --- /dev/null +++ b/public/docs/rust/10-generics-traits/5-0-std-traits.md @@ -0,0 +1,48 @@ +--- +id: rust-generics-traits-std-traits +title: 代表的な標準トレイト +level: 2 +--- + +## 代表的な標準トレイト + +Rustには、すべてのRustプログラマが知っておくべき標準トレイトがいくつかあります。これらはしばしば `#[derive(...)]` 属性を使って自動的に実装されます。 + +1. **`Debug`**: `{:?}` でフォーマット出力するためのトレイト。開発中のデバッグ出力に必須です。 +2. **`Display`**: `{}` でフォーマット出力するためのトレイト。ユーザー向けの表示に使います。自動導出(derive)はできず、手動実装が必要です。 +3. **`Clone`**: `.clone()` メソッドで明示的にディープコピー(またはそれに準ずる複製)を作成するためのトレイト。 +4. **`Copy`**: 値がビット単位のコピーで複製できることを示すマーカートレイト。これが実装されている型(`i32`など)は、代入しても所有権が移動(Move)せず、コピーされます。 + + + +```rust:std_traits.rs +// Debug, Clone, Copyを自動導出 +#[derive(Debug, Clone, Copy)] +struct Point { + x: i32, + y: i32, +} + +fn main() { + let p1 = Point { x: 10, y: 20 }; + + // Copyトレイトがあるので、p1はmoveされない。コピーされる。 + let p2 = p1; + + // Debugトレイトがあるので {:?} が使える + println!("p1: {:?}", p1); + println!("p2: {:?}", p2); + + // Cloneトレイトがあるので明示的に呼ぶこともできる(Copyがある場合、動作はCopyと同じになることが多い) + let p3 = p1.clone(); + println!("p3: {:?}", p3); +} +``` + +```rust-exec:std_traits.rs +p1: Point { x: 10, y: 20 } +p2: Point { x: 10, y: 20 } +p3: Point { x: 10, y: 20 } +``` + +> **注意:** `String` や `Vec` などのヒープ領域へのポインタを持つ型は、所有権のルール上、安易に `Copy` を実装できません(二重解放エラーになるため)。それらは `Clone` のみを実装します。 diff --git a/public/docs/rust/10-generics-traits/6-0-summary.md b/public/docs/rust/10-generics-traits/6-0-summary.md new file mode 100644 index 0000000..9d89061 --- /dev/null +++ b/public/docs/rust/10-generics-traits/6-0-summary.md @@ -0,0 +1,14 @@ +--- +id: rust-generics-traits-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **ジェネリクス**: 型をパラメータ化し、コードの重複を減らします。コンパイル時に単相化されるため、実行時コストがかかりません。 + * **トレイト**: 共通の振る舞いを定義します。インターフェースに似ていますが、継承ではなくコンポジションを促進します。 + * **トレイト境界**: ジェネリクス型 `T` に対して、「特定のトレイトを実装している型のみ」を受け入れるよう制約を課します。 + * **標準トレイト**: `Debug`, `Display`, `Clone`, `Copy` など、Rustの基本動作を支える重要なトレイトが存在します。 + +これらを使いこなすことで、Rustコンパイラに安全性を保証させつつ、再利用性の高いライブラリのようなコードを書くことができるようになります。 diff --git a/public/docs/rust/10-generics-traits/6-1-practice1.md b/public/docs/rust/10-generics-traits/6-1-practice1.md new file mode 100644 index 0000000..daea580 --- /dev/null +++ b/public/docs/rust/10-generics-traits/6-1-practice1.md @@ -0,0 +1,24 @@ +--- +id: rust-generics-traits-practice1 +title: '練習問題 1: ジェネリックなペア' +level: 3 +--- + +### 練習問題 1: ジェネリックなペア + +2つの異なる型 `T` と `U` を保持できる構造体 `Pair` を作成してください。 +そして、その構造体にメソッド `new` (インスタンス作成)と、デバッグ出力をするメソッド `print_pair` を実装してください。 +(ヒント:`print_pair` 内で `T` と `U` を表示するには、それぞれの型に `Debug` トレイトの制約が必要です) + +```rust:practice11_1.rs + + + +fn main() { + let pair = Pair::new(10, "Hello"); + pair.print_pair(); +} +``` +```rust-exec:practice11_1.rs +10 and "Hello" +``` diff --git a/public/docs/rust/10-generics-traits/6-2-practice2.md b/public/docs/rust/10-generics-traits/6-2-practice2.md new file mode 100644 index 0000000..15ed180 --- /dev/null +++ b/public/docs/rust/10-generics-traits/6-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: rust-generics-traits-practice2 +title: '問題 2: 最大値を探す' +level: 3 +--- + +### 問題 2: 最大値を探す + +ジェネリックなスライス `&[T]` を受け取り、その中の最大値を返す関数 `largest` を作成してください。 +比較を行うためには `T` にどのようなトレイト境界が必要か考えてください(ヒント:比較には `std::cmp::PartialOrd` が必要です。また、スライスから値を取り出して返すには `Copy` があると簡単です)。 + +```rust:practice11_2.rs + + + +fn main() { + let number_list = vec![34, 50, 25, 100, 65]; + let result = largest(&number_list); + println!("The largest number is {}", result); + + let char_list = vec!['y', 'm', 'a', 'q']; + let result = largest(&char_list); + println!("The largest char is {}", result); +} +``` + +```rust-exec:practice_solutions.rs +The largest number is 100 +The largest char is y +``` diff --git a/public/docs/rust/11-lifetimes/-intro.md b/public/docs/rust/11-lifetimes/-intro.md new file mode 100644 index 0000000..5c494b4 --- /dev/null +++ b/public/docs/rust/11-lifetimes/-intro.md @@ -0,0 +1,6 @@ +ようこそ、Rustの学習における「最難関」とも呼ばれる章へ。 +これまで所有権や借用(第4章、第5章)を学んできましたが、**ライフタイム(Lifetimes)** はそれらを支えるコンパイラのロジックそのものです。 + +他の言語(C/C++)では、メモリが解放された後にその場所を指し続ける「ダングリングポインタ」がバグの温床でした。JavaやPythonのようなガベージコレクション(GC)を持つ言語では、これを自動で管理しますが、パフォーマンスのコストがかかります。 + +Rustは**「コンパイル時に参照の有効性を厳密にチェックする」**ことで、GCなしでメモリ安全性を保証します。そのための仕組みがライフタイムです。 diff --git a/public/docs/rust/11-lifetimes/1-0-what-is-lifetime.md b/public/docs/rust/11-lifetimes/1-0-what-is-lifetime.md new file mode 100644 index 0000000..96c6dfd --- /dev/null +++ b/public/docs/rust/11-lifetimes/1-0-what-is-lifetime.md @@ -0,0 +1,11 @@ +--- +id: rust-lifetimes-what-is-lifetime +title: ライフタイムとは何か +level: 2 +--- + +## ライフタイムとは何か + +ライフタイムとは、簡単に言えば**「その参照が有効である期間(スコープ)」**のことです。 + +実は、これまでの章でもあなたは無意識にライフタイムを使用してきました。通常、コンパイラが自動的に推論してくれるため、明示する必要がなかっただけです。しかし、コンパイラが「参照の有効期間が不明瞭だ」と判断した場合、プログラマが明示的に注釈(アノテーション)を加える必要があります。 diff --git a/public/docs/rust/11-lifetimes/1-1-dangling-ref.md b/public/docs/rust/11-lifetimes/1-1-dangling-ref.md new file mode 100644 index 0000000..fcc3c80 --- /dev/null +++ b/public/docs/rust/11-lifetimes/1-1-dangling-ref.md @@ -0,0 +1,28 @@ +--- +id: rust-lifetimes-dangling-ref +title: ダングリング参照を防ぐ +level: 3 +--- + +### ダングリング参照を防ぐ + +ライフタイムの主な目的は、無効なデータを指す参照を作らせないことです。 + +以下のコードを見てください(これはコンパイルエラーになります)。 + +```rust +{ + let r; // ---------+-- rのライフタイム + // | + { // | + let x = 5; // -+-- xのライフタイム + r = &x; // | | + } // -+ | + // | + println!("r: {}", r); // | +} // ---------+ +``` + +ここで、`r` は `x` を参照しようとしています。しかし、内側のブロック `{}` が終わった時点で `x` は破棄されます。その後に `r` を使おうとすると、`r` は「解放されたメモリ」を指していることになります。 + +Rustのコンパイラ(**借用チェッカー**)は、このスコープのズレを検知し、「`x` の寿命が短すぎる」としてエラーを出します。 diff --git a/public/docs/rust/11-lifetimes/2-0-func-lifetime.md b/public/docs/rust/11-lifetimes/2-0-func-lifetime.md new file mode 100644 index 0000000..1602aac --- /dev/null +++ b/public/docs/rust/11-lifetimes/2-0-func-lifetime.md @@ -0,0 +1,43 @@ +--- +id: rust-lifetimes-func-lifetime +title: 関数のライフタイム注釈 +level: 2 +--- + +## 関数のライフタイム注釈 + +最も頻繁にライフタイム注釈が必要になるのは、**「引数として参照を受け取り、戻り値として参照を返す関数」**です。 + +2つの文字列スライスを受け取り、長い方を返す関数 `longest` を考えてみましょう。 + +```rust:longest_fail.rs +fn longest(x: &str, y: &str) -> &str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() { + let string1 = String::from("abcd"); + let string2 = "xyz"; + + let result = longest(string1.as_str(), string2); + println!("The longest string is {}", result); +} +``` + +このコードをコンパイルしようとすると、以下のようなエラーが出ます。 + +```rust-exec:longest_fail.rs +error[E0106]: missing lifetime specifier + | +1 | fn longest(x: &str, y: &str) -> &str { + | ---- ---- ^ expected named lifetime parameter + | + = help: this function's return type contains a borrowed value, but the signature does not say whether it is borrowed from `x` or `y` +``` + +**なぜエラーになるのか?** +コンパイラには、`longest` 関数が `x` を返すのか `y` を返すのか実行時まで分かりません。そのため、**戻り値の参照がいつまで有効であれば安全なのか(xの寿命に合わせるべきか、yの寿命に合わせるべきか)** を判断できないのです。 diff --git a/public/docs/rust/11-lifetimes/2-1-lifetime-syntax.md b/public/docs/rust/11-lifetimes/2-1-lifetime-syntax.md new file mode 100644 index 0000000..5bf31d8 --- /dev/null +++ b/public/docs/rust/11-lifetimes/2-1-lifetime-syntax.md @@ -0,0 +1,52 @@ +--- +id: rust-lifetimes-lifetime-syntax +title: ライフタイム注釈の構文 +level: 3 +--- + +### ライフタイム注釈の構文 + +ここで**ジェネリックなライフタイム注釈**が登場します。 +構文は `'a` のようにアポストロフィから始まる名前を使います。通常は `'a`(a, b, c...)が使われます。 + +注釈のルールは以下の通りです: + + * 関数名の後に `<'a>` でライフタイムパラメータを宣言する。 + * 引数と戻り値の参照に `&'a str` のように付与する。 + +修正したコードがこちらです。 + +```rust:longest_success.rs +// 'a というライフタイムを宣言し、 +// 「引数x、引数y、そして戻り値は、すべて少なくとも 'a と同じ期間だけ生きている」 +// という制約をコンパイラに伝える。 +fn longest<'a>(x: &'a str, y: &'a str) -> &'a str { + if x.len() > y.len() { + x + } else { + y + } +} + +fn main() { + let string1 = String::from("long string is long"); + let result; + { + let string2 = String::from("xyz"); + // resultのライフタイムは、string1とstring2のうち「短い方」の寿命に制約される + result = longest(string1.as_str(), string2.as_str()); + println!("The longest string is '{}'", result); + } + // ここで string2 がドロップされるため、result も無効になる。 + // もしここで result を使おうとするとコンパイルエラーになる(安全!)。 + // println!("The longest string is '{}'", result); +} +``` + +```rust-exec:longest_success.rs +The longest string is 'long string is long' +``` + +**重要なポイント:** +ライフタイム注釈 `'a` は、変数の寿命を**延ばすものではありません**。 +「複数の参照の寿命の関係性」をコンパイラに説明し、**「渡された参照の中で最も寿命が短いもの」** に戻り値の寿命を合わせるように制約するものです。 diff --git a/public/docs/rust/11-lifetimes/3-0-elision.md b/public/docs/rust/11-lifetimes/3-0-elision.md new file mode 100644 index 0000000..e4d2c23 --- /dev/null +++ b/public/docs/rust/11-lifetimes/3-0-elision.md @@ -0,0 +1,21 @@ +--- +id: rust-lifetimes-elision +title: ライフタイム省略ルール +level: 2 +--- + +## ライフタイム省略ルール + +「待ってください。第4章で書いた `fn first_word(s: &str) -> &str` は注釈なしで動きましたよ?」 + +鋭い質問です。初期のRustではすべての参照に明示的なライフタイムが必要でした。しかし、あまりにも頻出するパターン(例えば「引数が1つなら、戻り値のライフタイムもそれと同じ」など)があったため、Rustチームはそれらを自動推論する**「ライフタイム省略ルール(Lifetime Elision Rules)」**をコンパイラに組み込みました。 + +コンパイラは以下の3つのルールを順番に適用します。それでもライフタイムが決まらない場合のみ、エラーを出して人間に注釈を求めます。 + +1. **各引数に独自のライフタイムを割り当てる** + * `fn foo(x: &str, y: &str)` → `fn foo<'a, 'b>(x: &'a str, y: &'b str)` +2. **入力ライフタイムが1つだけなら、そのライフタイムをすべての出力(戻り値)に割り当てる** + * `fn foo<'a>(x: &'a str) -> &'a str` + * これが `first_word` 関数で注釈が不要だった理由です。 +3. **メソッド(`&self` または `&mut self` を含む)の場合、`self` のライフタイムをすべての出力に割り当てる** + * これにより、構造体のメソッドを書く際にいちいち注釈を書かずに済みます。 diff --git a/public/docs/rust/11-lifetimes/4-0-struct-lifetime.md b/public/docs/rust/11-lifetimes/4-0-struct-lifetime.md new file mode 100644 index 0000000..3210d8e --- /dev/null +++ b/public/docs/rust/11-lifetimes/4-0-struct-lifetime.md @@ -0,0 +1,39 @@ +--- +id: rust-lifetimes-struct-lifetime +title: 構造体定義におけるライフタイム注釈 +level: 2 +--- + +## 構造体定義におけるライフタイム注釈 + +これまでの章では、構造体には `String` や `i32` などの「所有される型」を持たせてきました。 +しかし、構造体に**参照**を持たせたい場合もあります。その場合、**「構造体そのものよりも、中の参照先が長生き(あるいは同等の寿命)である」**ことを保証しなければなりません。 + +```rust:struct_lifetime.rs +// ImportantExcerpt構造体は、'a という期間だけ生きる文字列スライスを保持する +struct ImportantExcerpt<'a> { + part: &'a str, +} + +fn main() { + let novel = String::from("Call me Ishmael. Some years ago..."); + + // 最初の文(ピリオドまで)を取得 + let first_sentence = novel.split('.').next().expect("Could not find a '.'"); + + // 構造体のインスタンスを作成 + // part は novel の一部を参照している。 + // novel が有効である限り、i も有効であることが保証される。 + let i = ImportantExcerpt { + part: first_sentence, + }; + + println!("Novel start: {}", i.part); +} +``` + +```rust-exec:struct_lifetime.rs +Novel start: Call me Ishmael +``` + +もし `User` 構造体のような定義で `<'a>` を忘れると、「参照を持たせるならライフタイムを指定せよ」というエラーになります。これは、構造体が生きている間に参照先のデータが消えてしまうのを防ぐためです。 diff --git a/public/docs/rust/11-lifetimes/5-0-static-lifetime.md b/public/docs/rust/11-lifetimes/5-0-static-lifetime.md new file mode 100644 index 0000000..35b2676 --- /dev/null +++ b/public/docs/rust/11-lifetimes/5-0-static-lifetime.md @@ -0,0 +1,19 @@ +--- +id: rust-lifetimes-static-lifetime +title: "静的ライフタイム ('static)" +level: 2 +--- + +## 静的ライフタイム ('static) + +特別なライフタイムとして `'static` があります。 +これは、**「参照がプログラムの実行期間全体にわたって有効である」**ことを意味します。 + +すべての文字列リテラルは `'static` ライフタイムを持っています。なぜなら、それらはプログラムのバイナリ自体に埋め込まれており、メモリ上の位置が固定されているからです。 + +```rust +let s: &'static str = "I have a static lifetime."; +``` + +**注意点:** +エラーメッセージで「`'static` が必要です」と提案されることがありますが、安易に `'static` を使って解決しようとしないでください。多くの場合、それは「参照ではなく所有権を持つべき」か「ライフタイムの関係を正しく記述すべき」場面であり、本当にプログラム終了までデータを保持し続けたいケースは稀です。 diff --git a/public/docs/rust/11-lifetimes/6-0-summary.md b/public/docs/rust/11-lifetimes/6-0-summary.md new file mode 100644 index 0000000..0cbbe58 --- /dev/null +++ b/public/docs/rust/11-lifetimes/6-0-summary.md @@ -0,0 +1,15 @@ +--- +id: rust-lifetimes-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **ライフタイム**は、参照が有効なスコープの長さを表す概念です。 + * Rustの**借用チェッカー**は、ライフタイムを比較してダングリング参照(無効なメモリへのアクセス)を防ぎます。 + * 関数で複数の参照を扱い、戻り値がそれらに依存する場合、`<'a>` のような**ジェネリックライフタイム注釈**が必要です。 + * **構造体**に参照を持たせる場合も、ライフタイム注釈が必要です。 + * **省略ルール**のおかげで、一般的なケースでは注釈を省略できます。 + +ライフタイムの記法は最初は「ノイズ」に見えるかもしれませんが、これは「メモリ安全性をコンパイラとプログラマが対話するための言語」です。これを理解すれば、C++のような複雑なメモリ管理の落とし穴を完全に回避できます。 diff --git a/public/docs/rust/11-lifetimes/6-1-practice1.md b/public/docs/rust/11-lifetimes/6-1-practice1.md new file mode 100644 index 0000000..c348cd9 --- /dev/null +++ b/public/docs/rust/11-lifetimes/6-1-practice1.md @@ -0,0 +1,32 @@ +--- +id: rust-lifetimes-practice1 +title: '練習問題 1: 参照を持つ構造体とメソッド' +level: 3 +--- + +### 練習問題 1: 参照を持つ構造体とメソッド + +以下の要件を満たすコードを作成してください。 + +1. `Book` という構造体を定義してください。 +2. この構造体は `title` というフィールドを持ち、それは `String` ではなく文字列スライス `&str` です(ライフタイム注釈が必要です)。 +3. `main` 関数で `String` 型の変数(例: `"The Rust Programming Language"`)を作成し、`Book` のインスタンスにその参照を渡してください。 +4. `Book` のインスタンスを表示してください(`Debug` トレイを導出(`#[derive(Debug)]`)して構いません)。 + +```rust:practice12_1.rs +// ここにBookの定義を書いてください + + +fn main() { + let book_title = String::from("The Rust Programming Language"); + let my_book = Book { + title: &book_title, + }; + + println!("Book details: {:?}", my_book); +} +``` + +```rust-exec:practice12_1.rs +Book details: Book { title: "The Rust Programming Language" } +``` diff --git a/public/docs/rust/11-lifetimes/6-2-practice2.md b/public/docs/rust/11-lifetimes/6-2-practice2.md new file mode 100644 index 0000000..6295882 --- /dev/null +++ b/public/docs/rust/11-lifetimes/6-2-practice2.md @@ -0,0 +1,34 @@ +--- +id: rust-lifetimes-practice2 +title: '練習問題 2: 最初の単語を返す関数(ライフタイム付き)' +level: 3 +--- + +### 練習問題 2: 最初の単語を返す関数(ライフタイム付き) + +以下の要件を満たすコードを作成してください。 + +1. 2つの文字列スライス `str1`, `str2` を受け取る関数 `first_word_of_longer` を作成してください。 +2. この関数は、2つの文字列のうち**長い方**を選び、その文字列の**最初の単語**(スペースまで)をスライスとして返します。 + * 例: "Hello World" と "Hi" なら、"Hello" を返す。 +3. 引数と戻り値に適切なライフタイム注釈 `'a` を付けてください。 +4. `main` 関数で動作確認をしてください。 + +*(ヒント: 単語の切り出しは `s.split_whitespace().next()` などが使えますが、戻り値のライフタイムが引数と紐付いていることが重要です)* + +```rust:practice12_2.rs +// ここにfirst_word_of_longer関数を書いてください + + +fn main() { + let string1 = String::from("Hello World from Rust"); + let string2 = String::from("Hi"); + + let first_word = first_word_of_longer(string1.as_str(), string2.as_str()); + println!("The first word of the longer string is: '{}'", first_word); +} +``` + +```rust-exec:practice12_2.rs +The first word of the longer string is: 'Hello' +``` diff --git a/public/docs/rust/2-functions-control/-intro.md b/public/docs/rust/2-functions-control/-intro.md new file mode 100644 index 0000000..34dc385 --- /dev/null +++ b/public/docs/rust/2-functions-control/-intro.md @@ -0,0 +1,3 @@ +他の言語での経験がある皆さんなら、関数やループの基本的な概念はすでにご存知でしょう。しかし、Rustには「すべてが式である」という強力な設計思想があり、これが制御フローの書き方にも大きく影響しています。 + +この章では、Rust特有の「文(Statement)と式(Expression)」の違いを理解し、それを踏まえた関数の定義方法と、柔軟な制御フローについて学びます。 diff --git a/public/docs/rust/2-functions-control/1-0-functions-expr.md b/public/docs/rust/2-functions-control/1-0-functions-expr.md new file mode 100644 index 0000000..1538d23 --- /dev/null +++ b/public/docs/rust/2-functions-control/1-0-functions-expr.md @@ -0,0 +1,9 @@ +--- +id: rust-functions-control-functions-expr +title: 関数と「式」指向 +level: 2 +--- + +## 関数と「式」指向 + +Rustの関数定義は `fn` キーワードを使用し、変数や関数の命名規則にはスネークケース(`snake_case`)を採用するのが慣例です。 diff --git a/public/docs/rust/2-functions-control/1-1-params-return.md b/public/docs/rust/2-functions-control/1-1-params-return.md new file mode 100644 index 0000000..19c4865 --- /dev/null +++ b/public/docs/rust/2-functions-control/1-1-params-return.md @@ -0,0 +1,9 @@ +--- +id: rust-functions-control-params-return +title: 引数と戻り値 +level: 3 +--- + +### 引数と戻り値 + +Rustは静的型付け言語であるため、関数の定義では**各引数の型を必ず宣言**する必要があります。戻り値がある場合は、矢印 `->` の後に型を記述します。 diff --git a/public/docs/rust/2-functions-control/1-2-stmt-expr.md b/public/docs/rust/2-functions-control/1-2-stmt-expr.md new file mode 100644 index 0000000..a9d3d3e --- /dev/null +++ b/public/docs/rust/2-functions-control/1-2-stmt-expr.md @@ -0,0 +1,49 @@ +--- +id: rust-functions-control-stmt-expr +title: 文(Statement)と式(Expression) +level: 3 +--- + +### 文(Statement)と式(Expression) + +Rustを理解する上で最も重要な概念の一つがこれです。 + + * **文 (Statement):** 何らかの操作を行い、値を返さないもの(例: `let y = 6;`)。 + * **式 (Expression):** 評価されて結果の値を返すもの(例: `5 + 6`、`{ code_block }`、関数呼び出し)。 + +他の多くの言語と異なり、Rustでは**ブロックの最後の式がそのブロックの戻り値**になります。`return` キーワードを使うこともできますが、一般的には「最後の行のセミコロンを省略する」スタイルが好まれます。 + +以下のコードで確認してみましょう。 + +```rust:functions_demo.rs +fn main() { + let x = 5; + let y = 10; + + // 値を返す関数呼び出し + let result = add(x, y); + println!("{} + {} = {}", x, y, result); + + // 文と式の違いを示すブロック + let z = { + let a = 3; + a + 1 // セミコロンがないので、この式が評価されzに代入される + }; + + println!("zの値: {}", z); +} + +// 引数を取り、i32型を返す関数 +fn add(a: i32, b: i32) -> i32 { + // 最後の行にセミコロンがないため、この式の計算結果が戻り値となる + // "return a + b;" と書くのと同じ意味だが、こちらがRustらしい書き方 + a + b +} +``` + +```rust-exec:functions_demo.rs +5 + 10 = 15 +zの値: 4 +``` + +> **注意:** もし `a + 1` の後ろにセミコロンをつけて `a + 1;` とすると、それは「文」となり、値を返さなくなります(正確には空のタプル `()` を返します)。コンパイルエラーの原因になりやすいため注意してください。 diff --git a/public/docs/rust/2-functions-control/2-0-control-flow.md b/public/docs/rust/2-functions-control/2-0-control-flow.md new file mode 100644 index 0000000..891733f --- /dev/null +++ b/public/docs/rust/2-functions-control/2-0-control-flow.md @@ -0,0 +1,9 @@ +--- +id: rust-functions-control-control-flow +title: 制御フロー +level: 2 +--- + +## 制御フロー + +Rustの制御フロー(`if`, `loop`, `while`, `for`)もまた「式」として扱うことができます。 diff --git a/public/docs/rust/2-functions-control/2-1-if.md b/public/docs/rust/2-functions-control/2-1-if.md new file mode 100644 index 0000000..c9b6541 --- /dev/null +++ b/public/docs/rust/2-functions-control/2-1-if.md @@ -0,0 +1,32 @@ +--- +id: rust-functions-control-if +title: if 式 +level: 3 +--- + +### if 式 + +Rustの `if` は式です。つまり、条件分岐の結果を変数に代入することができます。 +三項演算子(`condition ? a : b`)のような専用の構文はRustにはありませんが、`if` がその役割を果たします。 + +重要なルールとして、**条件式は必ず `bool` 型でなければなりません**。JavaScriptやC++のように、数値を自動的に `true/false` に変換することはありません。 + +```rust:if_expression.rs +fn main() { + let condition = true; + + // ifの結果を変数に束縛できる + // ifブロックとelseブロックが返す型は同じである必要がある + let number = if condition { + 5 + } else { + 6 + }; + + println!("numberの値: {}", number); +} +``` + +```rust-exec:if_expression.rs +numberの値: 5 +``` diff --git a/public/docs/rust/2-functions-control/2-2-loops.md b/public/docs/rust/2-functions-control/2-2-loops.md new file mode 100644 index 0000000..41f8371 --- /dev/null +++ b/public/docs/rust/2-functions-control/2-2-loops.md @@ -0,0 +1,13 @@ +--- +id: rust-functions-control-loops +title: ループ構造 +level: 3 +--- + +### ループ構造 + +Rustには3種類のループがあります。 + +1. `loop`: 無限ループ +2. `while`: 条件付きループ +3. `for`: コレクションや範囲に対するループ diff --git a/public/docs/rust/2-functions-control/2-3-loop-return.md b/public/docs/rust/2-functions-control/2-3-loop-return.md new file mode 100644 index 0000000..4c795e0 --- /dev/null +++ b/public/docs/rust/2-functions-control/2-3-loop-return.md @@ -0,0 +1,31 @@ +--- +id: rust-functions-control-loop-return +title: loop と値の戻り +level: 4 +--- + +#### loop と値の戻り + +`loop` キーワードは、明示的に `break` するまで永遠に繰り返します。面白い機能として、`break` の後に値を置くことで、ループ全体の結果としてその値を返すことができます。 + +```rust:loop_return.rs +fn main() { + let mut counter = 0; + + // ループの結果を変数resultに代入 + let result = loop { + counter += 1; + + if counter == 10 { + // カウンタが10になったら、counter * 2 の値を返して終了 + break counter * 2; + } + }; + + println!("ループの結果: {}", result); +} +``` + +```rust-exec:loop_return.rs +ループの結果: 20 +``` diff --git a/public/docs/rust/2-functions-control/2-4-while.md b/public/docs/rust/2-functions-control/2-4-while.md new file mode 100644 index 0000000..003c1b1 --- /dev/null +++ b/public/docs/rust/2-functions-control/2-4-while.md @@ -0,0 +1,18 @@ +--- +id: rust-functions-control-while +title: while +level: 4 +--- + +#### while + +`while` は他の言語とほぼ同じです。条件が真である限り実行されます。 + +```rust +// 例(実行不要) +let mut number = 3; +while number != 0 { + println!("{}!", number); + number -= 1; +} +``` diff --git a/public/docs/rust/2-functions-control/2-5-for.md b/public/docs/rust/2-functions-control/2-5-for.md new file mode 100644 index 0000000..b8916b2 --- /dev/null +++ b/public/docs/rust/2-functions-control/2-5-for.md @@ -0,0 +1,42 @@ +--- +id: rust-functions-control-for +title: for ループ +level: 4 +--- + +#### for ループ + +Rustで最も安全かつ頻繁に使用されるのが `for` ループです。配列の要素を走査したり、特定の回数だけ処理を行ったりする場合、インデックス管理が不要な `for` が推奨されます。 + +数値の範囲を指定する場合は `Range` 型(`start..end`)を使用します。 + +```rust:for_loop.rs +fn main() { + let a = [10, 20, 30, 40, 50]; + + // 配列のイテレータを使ったループ + println!("--- 配列の走査 ---"); + for element in a.iter() { + println!("値: {}", element); + } + + // Rangeを使ったループ (1から3まで。4は含まない) + println!("--- 範囲指定 ---"); + for number in 1..4 { + println!("カウント: {}", number); + } +} +``` + +```rust-exec:for_loop.rs +--- 配列の走査 --- +値: 10 +値: 20 +値: 30 +値: 40 +値: 50 +--- 範囲指定 --- +カウント: 1 +カウント: 2 +カウント: 3 +``` diff --git a/public/docs/rust/2-functions-control/3-0-summary.md b/public/docs/rust/2-functions-control/3-0-summary.md new file mode 100644 index 0000000..b521955 --- /dev/null +++ b/public/docs/rust/2-functions-control/3-0-summary.md @@ -0,0 +1,12 @@ +--- +id: rust-functions-control-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **関数:** パラメータの型と戻り値の型を明示する。 + * **文と式:** Rustは式指向であり、セミコロンのない最後の式がブロックの戻り値となる。 + * **if式:** `if` は値を返すことができる。条件は必ず `bool` でなければならない。 + * **ループ:** `loop`(無限・値返し可能)、`while`(条件付き)、`for`(イテレータ・範囲)があり、特に `for` が推奨される。 diff --git a/public/docs/rust/2-functions-control/4-0-practice1.md b/public/docs/rust/2-functions-control/4-0-practice1.md new file mode 100644 index 0000000..6ef89a5 --- /dev/null +++ b/public/docs/rust/2-functions-control/4-0-practice1.md @@ -0,0 +1,25 @@ +--- +id: rust-functions-control-practice1 +title: '練習問題 1: 摂氏・華氏変換' +level: 2 +--- + +## 練習問題 1: 摂氏・華氏変換 + +摂氏(Celsius)を華氏(Fahrenheit)に変換する関数 `c_to_f` を作成してください。 +公式: F = C × 1.8 + 32 + +```rust:practice3_1.rs +fn main() { + let celsius_temp = 25.0; + let fahrenheit_temp = c_to_f(celsius_temp); + println!("{}°C は {}°F です", celsius_temp, fahrenheit_temp); +} + +// ここに関数を実装してください + +``` + +```rust-exec:practice3_1.rs +25°C は 77°F です +``` diff --git a/public/docs/rust/2-functions-control/4-1-practice2.md b/public/docs/rust/2-functions-control/4-1-practice2.md new file mode 100644 index 0000000..bb54a9d --- /dev/null +++ b/public/docs/rust/2-functions-control/4-1-practice2.md @@ -0,0 +1,25 @@ +--- +id: rust-functions-control-practice2 +title: '練習問題 2: フィボナッチ数列' +level: 3 +--- + +### 練習問題 2: フィボナッチ数列 + +第 `n` 番目のフィボナッチ数を求める関数 `fib` を実装してください。 +制御フロー(`if` または ループ)を使用してください。 +(定義: 第0項=0, 第1項=1, 第n項 = 第n-1項 + 第n-2項) + +```rust:practice3_2.rs +fn main() { + let n = 10; + println!("フィボナッチ数列の第 {} 項は {} です", n, fib(n)); +} + +// ここに関数を実装してください + +``` + +```rust-exec:practice3_2.rs +フィボナッチ数列の第 10 項は 55 です +``` diff --git a/public/docs/rust/3-ownership/-intro.md b/public/docs/rust/3-ownership/-intro.md new file mode 100644 index 0000000..36f2cd5 --- /dev/null +++ b/public/docs/rust/3-ownership/-intro.md @@ -0,0 +1,5 @@ +Rustへようこそ。ここからがRustの学習における**最大の山場**であり、同時に**最大の特徴**である「所有権(Ownership)」システムについて解説します。 + +他のプログラミング言語(Python, Java, C++など)の経験がある方にとって、この概念は最も馴染みがなく、直感に反する場合があるかもしれません。しかし、所有権こそがガベージコレクション(GC)なしでメモリ安全性を保証するRustの魔法の源です。 + +本章では、参照(借用)の概念に入る前に、基礎となる「所有権の移動(ムーブ)」とメモリ管理の仕組みを理解します。 diff --git a/public/docs/rust/3-ownership/1-0-stack-heap.md b/public/docs/rust/3-ownership/1-0-stack-heap.md new file mode 100644 index 0000000..d3b58ff --- /dev/null +++ b/public/docs/rust/3-ownership/1-0-stack-heap.md @@ -0,0 +1,9 @@ +--- +id: rust-ownership-stack-heap +title: スタックとヒープのメモリ管理 +level: 2 +--- + +## スタックとヒープのメモリ管理 + +所有権を理解するには、計算機科学の基礎である「スタック(Stack)」と「ヒープ(Heap)」の違いを意識する必要があります。多くの高水準言語ではこれを意識しなくてもコードが書けますが、システムプログラミング言語であるRustでは、値がどこに配置されるかが言語の挙動に直結します。 diff --git a/public/docs/rust/3-ownership/1-1-stack.md b/public/docs/rust/3-ownership/1-1-stack.md new file mode 100644 index 0000000..ef448b6 --- /dev/null +++ b/public/docs/rust/3-ownership/1-1-stack.md @@ -0,0 +1,11 @@ +--- +id: rust-ownership-stack +title: スタック(Stack) +level: 3 +--- + +### スタック(Stack) + + * **特徴:** Last In, First Out (LIFO)。高速。 + * **データ:** コンパイル時にサイズが既知のデータ(`i32`, `bool`, 固定長配列など)が置かれます。 + * **動作:** 関数呼び出し時にローカル変数がプッシュされ、関数終了時にポップされます。 diff --git a/public/docs/rust/3-ownership/1-2-heap.md b/public/docs/rust/3-ownership/1-2-heap.md new file mode 100644 index 0000000..0a56345 --- /dev/null +++ b/public/docs/rust/3-ownership/1-2-heap.md @@ -0,0 +1,13 @@ +--- +id: rust-ownership-heap +title: ヒープ(Heap) +level: 3 +--- + +### ヒープ(Heap) + + * **特徴:** 任意の順序で確保・解放が可能。スタックより低速(ポインタ経由のアクセスが必要)。 + * **データ:** コンパイル時にサイズが不明、または可変長のデータ(`String`, `Vec`など)が置かれます。 + * **動作:** メモリ管理が必要。OSにメモリを要求し、そのアドレス(ポインタ)を受け取ります。ポインタ自体はスタックに置かれます。 + +**Rustの所有権システムは、主にこの「ヒープデータの管理」を自動化・安全化するためのルールセットです。** diff --git a/public/docs/rust/3-ownership/2-0-ownership-rules.md b/public/docs/rust/3-ownership/2-0-ownership-rules.md new file mode 100644 index 0000000..b9be030 --- /dev/null +++ b/public/docs/rust/3-ownership/2-0-ownership-rules.md @@ -0,0 +1,15 @@ +--- +id: rust-ownership-ownership-rules +title: 所有権の3つのルール +level: 2 +--- + +## 所有権の3つのルール + +Rustのコンパイラは、以下の厳格なルールに基づいてメモリ管理を行います。これを破るとコンパイルエラーになります。 + +> **所有権のルール** +> +> 1. Rustの各値は、**所有者(Owner)**と呼ばれる変数を持つ。 +> 2. いかなる時も、所有者は**一人だけ**である。 +> 3. 所有者がスコープから外れると、値は**破棄(ドロップ)**される。 diff --git a/public/docs/rust/3-ownership/2-1-scope.md b/public/docs/rust/3-ownership/2-1-scope.md new file mode 100644 index 0000000..7f0350a --- /dev/null +++ b/public/docs/rust/3-ownership/2-1-scope.md @@ -0,0 +1,24 @@ +--- +id: rust-ownership-scope +title: 変数のスコープ +level: 3 +--- + +### 変数のスコープ + +まずは単純なスコープの例を見てみましょう。これは他の言語とほぼ同じです。 + +```rust:scope_example.rs +fn main() { + { // s はここで宣言されていないので無効 + let s = "hello"; // s はここから有効になる + println!("{}", s); // s を使用できる + } // ここでスコープ終了。s は無効になる +} +``` + +```rust-exec:scope_example.rs +hello +``` + +ここで重要なのは、スコープを抜けた瞬間にRustが自動的にメモリを解放する処理(`drop`関数)を呼び出すという点です。これはC++のRAII (Resource Acquisition Is Initialization) パターンと同様です。 diff --git a/public/docs/rust/3-ownership/3-0-move-copy.md b/public/docs/rust/3-ownership/3-0-move-copy.md new file mode 100644 index 0000000..1e6956b --- /dev/null +++ b/public/docs/rust/3-ownership/3-0-move-copy.md @@ -0,0 +1,9 @@ +--- +id: rust-ownership-move-copy +title: ムーブセマンティクス(Move vs Copy) +level: 2 +--- + +## ムーブセマンティクス(Move vs Copy) + +ここからがRust独自の挙動です。データ型によって、「代入」の意味が変わります。 diff --git a/public/docs/rust/3-ownership/3-1-copy.md b/public/docs/rust/3-ownership/3-1-copy.md new file mode 100644 index 0000000..19f87ba --- /dev/null +++ b/public/docs/rust/3-ownership/3-1-copy.md @@ -0,0 +1,15 @@ +--- +id: rust-ownership-copy +title: Copyトレイト:スタックのみのデータ +level: 3 +--- + +### Copyトレイト:スタックのみのデータ + +整数型のような単純な値は、サイズが固定でスタック上にあります。この場合、変数を代入すると**値がコピー**されます。 + +```rust +let x = 5; +let y = x; // xの値(5)がコピーされてyに入る +// ここでは x も y も両方有効 +``` diff --git a/public/docs/rust/3-ownership/3-2-move.md b/public/docs/rust/3-ownership/3-2-move.md new file mode 100644 index 0000000..4d6ae3b --- /dev/null +++ b/public/docs/rust/3-ownership/3-2-move.md @@ -0,0 +1,45 @@ +--- +id: rust-ownership-move +title: Move(移動):ヒープデータの場合 +level: 3 +--- + +### Move(移動):ヒープデータの場合 + +`String`型のようにヒープにメモリを確保する型を見てみましょう。 + +```rust +let s1 = String::from("hello"); +let s2 = s1; +``` + +C++などの経験があると、これは「ポインタのコピー(浅いコピー)」あるいは「ディープコピー」のどちらかだと思うかもしれません。 +しかしRustでは、これは**所有権の移動(Move)**とみなされます。 + +1. `s1` はヒープ上の "hello" を指すポインタ、長さ、容量をスタックに持っています。 +2. `s2 = s1` を実行すると、スタック上のデータ(ポインタ等)のみが `s2` にコピーされます。 +3. **重要:** この瞬間、Rustは `s1` を**無効**とみなします。 + +なぜなら、もし `s1` も有効なままだと、スコープを抜けた時に `s1` と `s2` が同じヒープメモリを2回解放しようとしてしまう(二重解放エラー)からです。 + +以下のコードを実行して確認してみましょう。 + +```rust:move_error_demo.rs +fn main() { + let s1 = String::from("hello"); + let s2 = s1; // 所有権が s1 から s2 へ移動(ムーブ) + + // s1 はもう無効なので、以下の行はコンパイルエラーになる + // println!("{}, world!", s1); + + println!("s1 is moved."); + println!("s2 is: {}", s2); +} +``` + +```rust-exec:move_error_demo.rs +s1 is moved. +s2 is: hello +``` + +もし `println!("{}", s1)` のコメントアウトを外すと、`value borrowed here after move` という有名なコンパイルエラーが発生します。 diff --git a/public/docs/rust/3-ownership/3-3-clone.md b/public/docs/rust/3-ownership/3-3-clone.md new file mode 100644 index 0000000..d28f9fa --- /dev/null +++ b/public/docs/rust/3-ownership/3-3-clone.md @@ -0,0 +1,22 @@ +--- +id: rust-ownership-clone +title: Clone:ディープコピー +level: 3 +--- + +### Clone:ディープコピー + +もしヒープ上のデータも含めて完全にコピーしたい場合は、明示的に `.clone()` メソッドを使用します。 + +```rust:clone_example.rs +fn main() { + let s1 = String::from("hello"); + let s2 = s1.clone(); // ヒープデータごとコピーする(コストは高い) + + println!("s1 = {}, s2 = {}", s1, s2); +} +``` + +```rust-exec:clone_example.rs +s1 = hello, s2 = hello +``` diff --git a/public/docs/rust/3-ownership/4-0-ownership-func.md b/public/docs/rust/3-ownership/4-0-ownership-func.md new file mode 100644 index 0000000..56fb8ea --- /dev/null +++ b/public/docs/rust/3-ownership/4-0-ownership-func.md @@ -0,0 +1,38 @@ +--- +id: rust-ownership-ownership-func +title: 所有権と関数 +level: 2 +--- + +## 所有権と関数 + +関数に変数を渡す動作も、代入と同様に機能します。つまり、**関数へ値を渡すと所有権が移動します**(Copy型を除く)。 + +```rust:function_ownership.rs +fn main() { + let s = String::from("hello"); // s がスコープに入る + + takes_ownership(s); // s の値が関数にムーブされる + // ここで s はもう有効ではない! + + let x = 5; // x がスコープに入る + makes_copy(x); // x も関数に移動するが、 + // i32はCopyトレイトを持つので、 + // この後も x を使って問題ない + +} // ここで x がスコープアウト。s もスコープアウトだが、 + // 所有権は既に移動しているので何も起きない。 + +fn takes_ownership(some_string: String) { // some_string に所有権が移る + println!("{}", some_string); +} // ここで some_string がスコープアウトし、`drop` が呼ばれる。メモリ解放。 + +fn makes_copy(some_integer: i32) { // some_integer に値がコピーされる + println!("{}", some_integer); +} // ここで some_integer がスコープアウト。何も起きない。 +``` + +```rust-exec:function_ownership.rs +hello +5 +``` diff --git a/public/docs/rust/3-ownership/4-1-return-ownership.md b/public/docs/rust/3-ownership/4-1-return-ownership.md new file mode 100644 index 0000000..145521e --- /dev/null +++ b/public/docs/rust/3-ownership/4-1-return-ownership.md @@ -0,0 +1,33 @@ +--- +id: rust-ownership-return-ownership +title: 戻り値と所有権 +level: 3 +--- + +### 戻り値と所有権 + +関数から値を返すことで、所有権を呼び出し元に戻すことができます。 + +```rust:return_ownership.rs +fn main() { + let s1 = gives_ownership(); // 戻り値の所有権が s1 に移動 + let s2 = String::from("hello"); // s2 スコープイン + let s3 = takes_and_gives_back(s2); // s2 は関数にムーブされ、 + // 戻り値が s3 にムーブされる + println!("s1: {}, s3: {}", s1, s3); +} + +fn gives_ownership() -> String { + let some_string = String::from("yours"); + some_string // 所有権を呼び出し元に返す +} + +// 文字列を受け取り、それをそのまま返す関数 +fn takes_and_gives_back(a_string: String) -> String { + a_string // 所有権を返す +} +``` + +```rust-exec:return_ownership.rs +s1: yours, s3: hello +``` diff --git a/public/docs/rust/3-ownership/5-0-summary.md b/public/docs/rust/3-ownership/5-0-summary.md new file mode 100644 index 0000000..afd3d64 --- /dev/null +++ b/public/docs/rust/3-ownership/5-0-summary.md @@ -0,0 +1,19 @@ +--- +id: rust-ownership-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **所有権のルール:** 値の所有者は常に一人。所有者がスコープを抜けると値は破棄される。 + * **スタック vs ヒープ:** サイズ固定のデータはスタック(高速)、可変長データはヒープ(管理が必要)に置かれる。 + * **Move(ムーブ):** ヒープデータを変数に代入(または関数渡し)すると、所有権が移動し、元の変数は使用不能になる。 + * **Copy:** 整数などの単純な型は、ムーブではなく自動的にコピーされる。 + * **Clone:** ヒープデータをディープコピーしたい場合は `clone()` を使う。 + +このシステムのおかげで、Rustは実行時のガベージコレクションによる停止を避けつつ、ダングリングポインタ(無効なメモリを指すポインタ)や二重解放のバグをコンパイル時に完全に防ぐことができます。 + +しかし、「関数に値を渡すたびに所有権がなくなってしまい、使えなくなる」のは不便だと感じたでしょう。値を一時的に関数に使わせたいだけなのに、いちいち所有権を返してもらうのは面倒です。 + +次章の**「借用(Borrowing)」**では、所有権を渡さずに値を参照する方法を学びます。 diff --git a/public/docs/rust/3-ownership/5-1-practice1.md b/public/docs/rust/3-ownership/5-1-practice1.md new file mode 100644 index 0000000..595498e --- /dev/null +++ b/public/docs/rust/3-ownership/5-1-practice1.md @@ -0,0 +1,22 @@ +--- +id: rust-ownership-practice1 +title: '練習問題 1: ムーブの回避' +level: 3 +--- + +### 練習問題 1: ムーブの回避 + +以下のコードは、`s1` の所有権が `s2` に移動してしまったためコンパイルエラーになります。`s1` と `s2` の両方を表示できるように修正してください(`clone`を使用する方法と、新しい文字列を作る方法のどちらでも構いません)。 + +```rust:practice4_1.rs +fn main() { + let s1 = String::from("Rust"); + let s2 = s1; + + println!("Original: {}", s1); // ここでエラー + println!("New: {}", s2); +} +``` + +```rust-exec:practice4_1.rs +``` diff --git a/public/docs/rust/3-ownership/5-2-practice2.md b/public/docs/rust/3-ownership/5-2-practice2.md new file mode 100644 index 0000000..8f0b316 --- /dev/null +++ b/public/docs/rust/3-ownership/5-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: rust-ownership-practice2 +title: '練習問題 2: 所有権のリレー' +level: 3 +--- + +### 練習問題 2: 所有権のリレー + +以下の `process_string` 関数は文字列を受け取って長さを出力しますが、戻り値がないため、呼び出し元で元の文字列が使えなくなってしまいます。 +`process_string` を修正して、受け取った文字列の所有権を呼び出し元に返すようにし、`main` 関数でその後の処理ができるようにしてください。 + +```rust:practice4_2.rs +fn main() { + let s = String::from("ownership"); + + // ここで s を渡して、処理後に戻ってきた所有権を new_s で受け取るように修正する + process_string(s); + + // 修正後は以下のコメントを解除しても動作するようにする + // println!("String is still valid: {}", new_s); +} + +// 戻り値の型と実装を修正してください +fn process_string(input: String) { + println!("Length is: {}", input.len()); +} +``` + +```rust-exec:practice4_2.rs +``` diff --git a/public/docs/rust/4-borrowing-slices/-intro.md b/public/docs/rust/4-borrowing-slices/-intro.md new file mode 100644 index 0000000..85a2f9d --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/-intro.md @@ -0,0 +1,3 @@ +前章では「所有権(Ownership)」というRust独自のメモリ管理システムについて学びました。「所有権は一度に1つの変数しか持てない」というルールは強力ですが、関数に値を渡すたびに所有権が移動(ムーブ)してしまうと、毎回値を返り値として受け取らなければならず不便です。 + +そこで登場するのが**「参照(Reference)」**と**「借用(Borrowing)」**です。これを使うことで、所有権を渡さずに値にアクセスすることが可能になります。 diff --git a/public/docs/rust/4-borrowing-slices/1-0-ref-borrow.md b/public/docs/rust/4-borrowing-slices/1-0-ref-borrow.md new file mode 100644 index 0000000..bee77be --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/1-0-ref-borrow.md @@ -0,0 +1,11 @@ +--- +id: rust-borrowing-slices-ref-borrow +title: 5.1 参照と借用(& と &mut) +level: 2 +--- + +## 5.1 参照と借用(& と \&mut) + +**参照(Reference)**とは、所有権を持たずにデータへアクセスするためのポインタのようなものです。他の言語のポインタと似ていますが、Rustの参照は「常に有効なデータを指していること」が保証されています。 + +関数の引数として参照を渡すことを、Rustでは**「借用(Borrowing)」**と呼びます。誰かから本を借りても、それを捨てたり(メモリ解放)、転売したり(所有権の譲渡)できないのと同じです。 diff --git a/public/docs/rust/4-borrowing-slices/1-1-immutable-ref.md b/public/docs/rust/4-borrowing-slices/1-1-immutable-ref.md new file mode 100644 index 0000000..219bf83 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/1-1-immutable-ref.md @@ -0,0 +1,30 @@ +--- +id: rust-borrowing-slices-immutable-ref +title: 不変参照(Immutable Reference) +level: 3 +--- + +### 不変参照(Immutable Reference) + +デフォルトでは、参照は**不変**です。借りた値を読むことはできますが、変更することはできません。参照を作成するには `&` を使います。 + +```rust:calculate_length.rs +fn main() { + let s1 = String::from("hello"); + + // &s1 で s1 への参照を渡す(所有権は移動しない) + let len = calculate_length(&s1); + + // 所有権は移動していないので、ここで s1 をまだ使える! + println!("The length of '{}' is {}.", s1, len); +} + +// 引数の型が &String になっていることに注目 +fn calculate_length(s: &String) -> usize { + s.len() +} // ここで s がスコープを抜けるが、所有権を持っていないのでメモリは解放されない +``` + +```rust-exec:calculate_length.rs +The length of 'hello' is 5. +``` diff --git a/public/docs/rust/4-borrowing-slices/1-2-mutable-ref.md b/public/docs/rust/4-borrowing-slices/1-2-mutable-ref.md new file mode 100644 index 0000000..911071a --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/1-2-mutable-ref.md @@ -0,0 +1,27 @@ +--- +id: rust-borrowing-slices-mutable-ref +title: 可変参照(Mutable Reference) +level: 3 +--- + +### 可変参照(Mutable Reference) + +借りた値を変更したい場合は、**可変参照**を使用します。これには `&mut` を使い、元の変数も `mut` である必要があります。 + +```rust:mutable_borrow.rs +fn main() { + let mut s = String::from("hello"); + + change(&mut s); // 可変参照を渡す + + println!("Result: {}", s); +} + +fn change(some_string: &mut String) { + some_string.push_str(", world"); +} +``` + +```rust-exec:mutable_borrow.rs +Result: hello, world +``` diff --git a/public/docs/rust/4-borrowing-slices/2-0-borrow-rules.md b/public/docs/rust/4-borrowing-slices/2-0-borrow-rules.md new file mode 100644 index 0000000..e7c0ea5 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/2-0-borrow-rules.md @@ -0,0 +1,15 @@ +--- +id: rust-borrowing-slices-borrow-rules +title: 5.2 借用のルール +level: 2 +--- + +## 5.2 借用のルール + +Rustには、メモリ安全性とデータ競合(Data Race)を防ぐための非常に重要なルールがあります。これを**「借用のルール」**と呼びます。 + +> **【借用の鉄則】** +> ある特定のスコープにおいて、以下の**どちらか一方**しか満たすことはできません: +> +> 1. **1つの**可変参照(`&mut T`)を持つ。 +> 2. **任意の数の**不変参照(`&T`)を持つ。 diff --git a/public/docs/rust/4-borrowing-slices/2-1-single-mut-ref.md b/public/docs/rust/4-borrowing-slices/2-1-single-mut-ref.md new file mode 100644 index 0000000..e520670 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/2-1-single-mut-ref.md @@ -0,0 +1,10 @@ +--- +id: rust-borrowing-slices-single-mut-ref +title: なぜ可変参照は1つだけなのか? +level: 3 +--- + +### なぜ可変参照は1つだけなのか? + +もし「同じデータに対する可変参照」が同時に2つ存在したらどうなるでしょうか? +Aさんがデータを書き換えている最中に、Bさんも書き換えようとすると、データが破損する可能性があります。Rustはこの可能性をコンパイル時に排除します。 diff --git a/public/docs/rust/4-borrowing-slices/2-2-no-mixed-refs.md b/public/docs/rust/4-borrowing-slices/2-2-no-mixed-refs.md new file mode 100644 index 0000000..ab834ff --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/2-2-no-mixed-refs.md @@ -0,0 +1,43 @@ +--- +id: rust-borrowing-slices-no-mixed-refs +title: 不変参照と可変参照の共存禁止 +level: 3 +--- + +### 不変参照と可変参照の共存禁止 + +同様に、「誰かがデータを読んでいる最中(不変参照)に、誰かがデータを書き換える(可変参照)」ことも禁止されています。読んでいる最中にデータが変わると困るからです。 + +以下のコードはコンパイルエラーになります(概念を示すための例です)。 + +```rust +let mut s = String::from("hello"); + +let r1 = &s; // 問題なし +let r2 = &s; // 問題なし +let r3 = &mut s; // 大問題!不変参照が存在している間に可変参照は作れない + +println!("{}, {}, and {}", r1, r2, r3); +``` + +しかし、**スコープ**を利用すれば、この制限をクリアできます。直前の参照が使用されなくなった(スコープを抜けた)後であれば、新しい参照を作ることができます。 + +```rust:borrow_scope.rs +fn main() { + let mut s = String::from("hello"); + + { + let r1 = &mut s; + r1.push_str(" world"); + // r1 はここでスコープを抜ける + } + + // r1 はもういないので、新しい不変参照を作れる + let r2 = &s; + println!("Final string: {}", r2); +} +``` + +```rust-exec:borrow_scope.rs +Final string: hello world +``` diff --git a/public/docs/rust/4-borrowing-slices/3-0-dangling-ptr.md b/public/docs/rust/4-borrowing-slices/3-0-dangling-ptr.md new file mode 100644 index 0000000..f22b010 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/3-0-dangling-ptr.md @@ -0,0 +1,22 @@ +--- +id: rust-borrowing-slices-dangling-ptr +title: 5.3 ダングリングポインタの防止 +level: 2 +--- + +## 5.3 ダングリングポインタの防止 + +C++などの言語では、メモリが解放された後の場所を指し続けるポインタ(ダングリングポインタ)を作ってしまうことがあり、これが重大なバグの原因になります。 +Rustでは、**コンパイラがこれを絶対に許可しません**。 + +以下のコードは、「関数内で作成した変数の参照」を返そうとしていますが、これはエラーになります。 + +```rust +// コンパイルエラーになる例 +fn dangle() -> &String { + let s = String::from("hello"); + &s // sの参照を返す +} // ここでsはスコープを抜け、メモリが解放される。参照先が消滅する! +``` + +Rustコンパイラは「データのライフタイム(生存期間)が、その参照よりも短い」と判断し、エラーを出してくれます。この場合は、参照ではなく値を返して所有権を移動させるのが正解です。 diff --git a/public/docs/rust/4-borrowing-slices/4-0-slice.md b/public/docs/rust/4-borrowing-slices/4-0-slice.md new file mode 100644 index 0000000..0bb02bf --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/4-0-slice.md @@ -0,0 +1,9 @@ +--- +id: rust-borrowing-slices-slice +title: 5.4 スライス型(Slice) +level: 2 +--- + +## 5.4 スライス型(Slice) + +参照の特殊な形として**「スライス(Slice)」**があります。スライスは、コレクション(配列や文字列など)の**一部分**への参照です。所有権を持たない点は通常の参照と同じです。 diff --git a/public/docs/rust/4-borrowing-slices/4-1-str-slice.md b/public/docs/rust/4-borrowing-slices/4-1-str-slice.md new file mode 100644 index 0000000..3303efa --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/4-1-str-slice.md @@ -0,0 +1,40 @@ +--- +id: rust-borrowing-slices-str-slice +title: 文字列スライス(&str) +level: 3 +--- + +### 文字列スライス(`&str`) + +特に重要なのが文字列スライスです。`String` の一部を切り出して参照します。 +書き方は `[開始インデックス..終了インデックス]` です。 + +```rust:string_slices.rs +fn main() { + let s = String::from("Hello Rust World"); + + let hello = &s[0..5]; // 0番目から4番目まで(5は含まない) + let rust = &s[6..10]; // 6番目から9番目まで + + println!("Slice 1: {}", hello); + println!("Slice 2: {}", rust); + + // 省略記法 + let len = s.len(); + let start = &s[0..5]; + let start_short = &s[..5]; // 0は省略可能 + + let end = &s[6..len]; + let end_short = &s[6..]; // 末尾も省略可能 + + let all = &s[..]; // 全体 + + println!("Full: {}", all); +} +``` + +```rust-exec:string_slices.rs +Slice 1: Hello +Slice 2: Rust +Full: Hello Rust World +``` diff --git a/public/docs/rust/4-borrowing-slices/4-2-str-literal.md b/public/docs/rust/4-borrowing-slices/4-2-str-literal.md new file mode 100644 index 0000000..2e28ea0 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/4-2-str-literal.md @@ -0,0 +1,15 @@ +--- +id: rust-borrowing-slices-str-literal +title: 文字列リテラルはスライスである +level: 3 +--- + +### 文字列リテラルはスライスである + +これまで何気なく使っていた文字列リテラルですが、実はこれこそがスライスです。 + +```rust +let s = "Hello, world!"; +``` + +ここで `s` の型は `&str` です。これはバイナリの静的領域に格納されている文字列データへのスライス(参照)なのです。 diff --git a/public/docs/rust/4-borrowing-slices/4-3-str-param.md b/public/docs/rust/4-borrowing-slices/4-3-str-param.md new file mode 100644 index 0000000..4b5aa64 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/4-3-str-param.md @@ -0,0 +1,17 @@ +--- +id: rust-borrowing-slices-str-param +title: 引数としての &str +level: 3 +--- + +### 引数としての `&str` + +関数で文字列を受け取る際、`&String` よりも `&str` を使う方が柔軟性が高まります。なぜなら、`&str` を引数にすれば、`String` も `&str`(リテラルなど)も両方受け取れるからです。 + +```rust +// この定義の方が汎用的 +fn first_word(s: &str) -> &str { + // 実装... + &s[..] // 仮の実装 +} +``` diff --git a/public/docs/rust/4-borrowing-slices/5-0-other-slices.md b/public/docs/rust/4-borrowing-slices/5-0-other-slices.md new file mode 100644 index 0000000..5d10d58 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/5-0-other-slices.md @@ -0,0 +1,27 @@ +--- +id: rust-borrowing-slices-other-slices +title: 5.5 その他のスライス +level: 2 +--- + +## 5.5 その他のスライス + +スライスは文字列だけでなく、配列に対しても使えます。 + +```rust:array_slice.rs +fn main() { + let a = [10, 20, 30, 40, 50]; + + // 配列の一部を借用する + let slice = &a[1..3]; // [20, 30] + + // assert_eq! は値が等しいか確認するマクロ + assert_eq!(slice, &[20, 30]); + + println!("Slice elements: {:?}", slice); +} +``` + +```rust-exec:array_slice.rs +Slice elements: [20, 30] +``` diff --git a/public/docs/rust/4-borrowing-slices/6-0-summary.md b/public/docs/rust/4-borrowing-slices/6-0-summary.md new file mode 100644 index 0000000..3909a15 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/6-0-summary.md @@ -0,0 +1,17 @@ +--- +id: rust-borrowing-slices-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **参照(`&`)**: 所有権を移動させずに値にアクセスする仕組み。 + * **借用**: 関数の引数として参照を渡すこと。 + * **借用のルール**: + 1. 不変参照(`&T`)は同時にいくつでも作れる。 + 2. 可変参照(`&mut T`)は同時に1つしか作れない。 + 3. 不変参照と可変参照は同時に存在できない。 + * **スライス**: コレクションの一部分を参照するビュー。`&str` は文字列の一部への不変参照。 + +Rustのコンパイラ(ボローチェッカー)は厳格ですが、それはバグのない安全なコードを書くための強力なパートナーです。慣れてくれば、コンパイルが通った時点でロジックの正しさに自信が持てるようになります。 diff --git a/public/docs/rust/4-borrowing-slices/7-0-practice1.md b/public/docs/rust/4-borrowing-slices/7-0-practice1.md new file mode 100644 index 0000000..e64c296 --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/7-0-practice1.md @@ -0,0 +1,35 @@ +--- +id: rust-borrowing-slices-practice1 +title: '練習問題 1: 参照渡しへの書き換え' +level: 2 +--- + +## 練習問題 1: 参照渡しへの書き換え + +以下のコードは動作しますが、`calculate_area` 関数が所有権を奪ってしまうため、再度 `rect1` を使おうとするとエラーになります。 +`calculate_area` が `Rectangle` の**不変参照**を受け取るように修正し、`main` 関数で `rect1` を再利用できるようにしてください。 + +```rust:practice5_1.rs +struct Rectangle { + width: u32, + height: u32, +} + +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + + let area = calculate_area(rect1); // ここを修正 + + // 現在のコードだと、ここで rect1 を使うとエラーになる + // println!("rect1 is {:?}", rect1); + + println!("The area is {}", area); +} + +fn calculate_area(rect: Rectangle) -> u32 { // ここを修正 + rect.width * rect.height +} +``` + +```rust-exec:practice5_1.rs +``` diff --git a/public/docs/rust/4-borrowing-slices/7-1-practice2.md b/public/docs/rust/4-borrowing-slices/7-1-practice2.md new file mode 100644 index 0000000..075783b --- /dev/null +++ b/public/docs/rust/4-borrowing-slices/7-1-practice2.md @@ -0,0 +1,40 @@ +--- +id: rust-borrowing-slices-practice2 +title: '問題 2: スライスの活用' +level: 3 +--- + +### 問題 2: スライスの活用 + +Eメールアドレスを表す文字列を受け取り、**「@」より前のユーザー名部分だけを文字列スライス(`&str`)として返す**関数 `extract_username` を作成してください。もし「@」が含まれていない場合は、文字列全体を返してください。 + +ヒント: 文字列をバイト配列として扱い、ループで `@` を探します。 + +```rust:practice5_2.rs +fn main() { + let email = String::from("user@example.com"); + let username = extract_username(&email); + println!("Username: {}", username); // "user" と出力されるべき + + let simple = String::from("admin"); + let simple_name = extract_username(&simple); + println!("Username: {}", simple_name); // "admin" と出力されるべき +} + +fn extract_username(s: &str) -> &str { + let bytes = s.as_bytes(); + + for (i, &item) in bytes.iter().enumerate() { + // ここにコードを書く + + } + + // 「@」が含まれていない場合は、文字列全体を返す + +} +``` + +```rust-exec:practice5_2.rs +Username: user +Username: admin +``` diff --git a/public/docs/rust/5-structs-methods/-intro.md b/public/docs/rust/5-structs-methods/-intro.md new file mode 100644 index 0000000..3ff16b4 --- /dev/null +++ b/public/docs/rust/5-structs-methods/-intro.md @@ -0,0 +1,8 @@ +他のオブジェクト指向言語(C++、Java、Pythonなど)の経験がある方にとって、Rustの**構造体(Struct)**は「クラス」に似ているように見えますが、決定的な違いがいくつかあります。 + +最も大きな違いは以下の2点です: + +1. **クラス継承が存在しない**: Rustには`extends`や親クラスという概念がありません(コードの再利用には、後述する「トレイト」や「コンポジション」を使用します)。 +2. **データと振る舞いの分離**: データ定義(`struct`)とメソッド定義(`impl`)は明確に分かれています。 + +この章では、関連するデータをグループ化し、それに対する操作を定義する方法を学びます。 diff --git a/public/docs/rust/5-structs-methods/1-0-struct-def.md b/public/docs/rust/5-structs-methods/1-0-struct-def.md new file mode 100644 index 0000000..bb21de4 --- /dev/null +++ b/public/docs/rust/5-structs-methods/1-0-struct-def.md @@ -0,0 +1,9 @@ +--- +id: rust-structs-methods-struct-def +title: 構造体の定義とインスタンス化 +level: 2 +--- + +## 構造体の定義とインスタンス化 + +構造体は、異なる型の値を一つにまとめて名前を付けたカスタムデータ型です。 diff --git a/public/docs/rust/5-structs-methods/1-1-basic-def.md b/public/docs/rust/5-structs-methods/1-1-basic-def.md new file mode 100644 index 0000000..54e8e0e --- /dev/null +++ b/public/docs/rust/5-structs-methods/1-1-basic-def.md @@ -0,0 +1,40 @@ +--- +id: rust-structs-methods-basic-def +title: 基本的な定義 +level: 3 +--- + +### 基本的な定義 + +C言語の `struct` や、メソッドを持たないクラスのようなものです。フィールド名と型を定義します。 + +```rust:user_struct.rs +struct User { + username: String, + email: String, + sign_in_count: u64, + active: bool, +} + +fn main() { + // インスタンス化 + // フィールドの順番は定義と異なっても構いません + let mut user1 = User { + email: String::from("someone@example.com"), + username: String::from("someusername123"), + active: true, + sign_in_count: 1, + }; + + // ドット記法でフィールドにアクセス + user1.email = String::from("another@example.com"); + + println!("User: {}, Email: {}", user1.username, user1.email); +} +``` + +```rust-exec:user_struct.rs +User: someusername123, Email: another@example.com +``` + +> **注意**: Rustでは、インスタンス全体が可変(`mut`)か不変かのどちらかになります。特定のフィールドだけを可変(`mut`)にすることはできません。 diff --git a/public/docs/rust/5-structs-methods/1-2-field-shorthand.md b/public/docs/rust/5-structs-methods/1-2-field-shorthand.md new file mode 100644 index 0000000..8bcec64 --- /dev/null +++ b/public/docs/rust/5-structs-methods/1-2-field-shorthand.md @@ -0,0 +1,20 @@ +--- +id: rust-structs-methods-field-shorthand +title: フィールド初期化省略記法 +level: 3 +--- + +### フィールド初期化省略記法 + +関数引数や変数の名前がフィールド名と同じ場合、記述を省略できます。これはJavaScriptのオブジェクト定義に似ています。 + +```rust +fn build_user(email: String, username: String) -> User { + User { + email, // email: email と同じ + username, // username: username と同じ + active: true, + sign_in_count: 1, + } +} +``` diff --git a/public/docs/rust/5-structs-methods/1-3-struct-update.md b/public/docs/rust/5-structs-methods/1-3-struct-update.md new file mode 100644 index 0000000..7e52d6e --- /dev/null +++ b/public/docs/rust/5-structs-methods/1-3-struct-update.md @@ -0,0 +1,17 @@ +--- +id: rust-structs-methods-struct-update +title: 構造体更新記法 +level: 3 +--- + +### 構造体更新記法 + +既存のインスタンスの値を元に、一部だけ変更した新しいインスタンスを作成する場合、`..` 構文を使用できます。 + +```rust +// user1のデータを元に、emailだけ変更したuser2を作成 +let user2 = User { + email: String::from("another@example.com"), + ..user1 // 残りのフィールドはuser1と同じ値が入る +}; +``` diff --git a/public/docs/rust/5-structs-methods/2-0-tuple-unit-structs.md b/public/docs/rust/5-structs-methods/2-0-tuple-unit-structs.md new file mode 100644 index 0000000..1d5c5e0 --- /dev/null +++ b/public/docs/rust/5-structs-methods/2-0-tuple-unit-structs.md @@ -0,0 +1,9 @@ +--- +id: rust-structs-methods-tuple-unit-structs +title: タプル構造体とユニット様構造体 +level: 2 +--- + +## タプル構造体とユニット様構造体 + +名前付きフィールドを持たない構造体も定義できます。 diff --git a/public/docs/rust/5-structs-methods/2-1-tuple-structs.md b/public/docs/rust/5-structs-methods/2-1-tuple-structs.md new file mode 100644 index 0000000..031d7e8 --- /dev/null +++ b/public/docs/rust/5-structs-methods/2-1-tuple-structs.md @@ -0,0 +1,29 @@ +--- +id: rust-structs-methods-tuple-structs +title: タプル構造体 (Tuple Structs) +level: 3 +--- + +### タプル構造体 (Tuple Structs) + +フィールドに名前がなく、型だけが並んでいる構造体です。「型」として区別したい場合に便利です。例えば、同じ `(i32, i32, i32)` でも、「色」と「座標」は計算上混ぜるべきではありません。 + +```rust:tuple_structs.rs +struct Color(i32, i32, i32); +struct Point(i32, i32, i32); + +fn main() { + let black = Color(0, 0, 0); + let origin = Point(0, 0, 0); + + // black と origin は構造が同じでも、型としては別物なので + // 関数に渡す際などにコンパイラが区別してくれます。 + + // 中身へのアクセスはタプル同様にインデックスを使用 + println!("Origin X: {}", origin.0); +} +``` + +```rust-exec:tuple_structs.rs +Origin X: 0 +``` diff --git a/public/docs/rust/5-structs-methods/2-2-unit-structs.md b/public/docs/rust/5-structs-methods/2-2-unit-structs.md new file mode 100644 index 0000000..1164b32 --- /dev/null +++ b/public/docs/rust/5-structs-methods/2-2-unit-structs.md @@ -0,0 +1,9 @@ +--- +id: rust-structs-methods-unit-structs +title: ユニット様構造体 (Unit-like Structs) +level: 3 +--- + +### ユニット様構造体 (Unit-like Structs) + +フィールドを全く持たない構造体です。`struct AlwaysEqual;` のように定義します。これらは、データを持たずに振る舞い(トレイト)だけを実装したい場合に役立ちますが、詳細は後の章で扱います。 diff --git a/public/docs/rust/5-structs-methods/3-0-ownership-structs.md b/public/docs/rust/5-structs-methods/3-0-ownership-structs.md new file mode 100644 index 0000000..be23592 --- /dev/null +++ b/public/docs/rust/5-structs-methods/3-0-ownership-structs.md @@ -0,0 +1,26 @@ +--- +id: rust-structs-methods-ownership-structs +title: 所有権と構造体 +level: 2 +--- + +## 所有権と構造体 + +構造体における所有権の扱いは非常に重要です。 + +1. **所有するデータ**: `String`や`Vec`のような所有権を持つ型をフィールドにすると、その構造体のインスタンスがデータの所有者になります。構造体がドロップされると、フィールドのデータもドロップされます。 +2. **参照**: 構造体に「参照(\&strなど)」を持たせることも可能ですが、これには**ライフタイム(第12章)の指定が必要になります。そのため、現段階では構造体には(参照ではなく)`String`などの所有権のある型**を使うことを推奨します。 + + + +```rust +// 推奨(現段階) +struct User { + username: String, // Userがデータを所有する +} + +// 非推奨(ライフタイムの知識が必要になるためコンパイルエラーになる) +// struct User { +// username: &str, +// } +``` diff --git a/public/docs/rust/5-structs-methods/4-0-impl.md b/public/docs/rust/5-structs-methods/4-0-impl.md new file mode 100644 index 0000000..ab61122 --- /dev/null +++ b/public/docs/rust/5-structs-methods/4-0-impl.md @@ -0,0 +1,9 @@ +--- +id: rust-structs-methods-impl +title: impl ブロック:メソッドと関連関数 +level: 2 +--- + +## impl ブロック:メソッドと関連関数 + +ここが、他言語のクラスにおける「メソッド定義」に相当する部分です。データ(`struct`)とは別に、`impl`(implementation)ブロックを使って振る舞いを定義します。 diff --git a/public/docs/rust/5-structs-methods/4-1-methods.md b/public/docs/rust/5-structs-methods/4-1-methods.md new file mode 100644 index 0000000..20562e1 --- /dev/null +++ b/public/docs/rust/5-structs-methods/4-1-methods.md @@ -0,0 +1,53 @@ +--- +id: rust-structs-methods-methods +title: メソッドの定義 +level: 3 +--- + +### メソッドの定義 + +メソッドは、最初の引数が必ず `self` (自分自身のインスタンス)になる関数です。 + + * `&self`: データを読み取るだけ(借用・不変)。最も一般的。 + * `&mut self`: データを書き換える(借用・可変)。 + * `self`: 所有権を奪う(ムーブ)。メソッド呼び出し後に元の変数は使えなくなる。変換処理などで使う。 + + + +```rust:rectangle.rs +#[derive(Debug)] // print!で{:?}を使ってデバッグ表示するために必要 +struct Rectangle { + width: u32, + height: u32, +} + +// Rectangle型に関連する関数やメソッドを定義 +impl Rectangle { + // メソッド:面積を計算する(読み取りのみなので &self) + fn area(&self) -> u32 { + self.width * self.height + } + + // メソッド:他の四角形を含めるか判定する + fn can_hold(&self, other: &Rectangle) -> bool { + self.width > other.width && self.height > other.height + } +} + +fn main() { + let rect1 = Rectangle { width: 30, height: 50 }; + let rect2 = Rectangle { width: 10, height: 40 }; + + // メソッド呼び出し(自動参照外し機能により、rect1.area() と書ける) + println!("The area of the rectangle is {} pixels.", rect1.area()); + + if rect1.can_hold(&rect2) { + println!("rect1 can hold rect2"); + } +} +``` + +```rust-exec:rectangle.rs +The area of the rectangle is 1500 pixels. +rect1 can hold rect2 +``` diff --git a/public/docs/rust/5-structs-methods/4-2-assoc-funcs.md b/public/docs/rust/5-structs-methods/4-2-assoc-funcs.md new file mode 100644 index 0000000..1bfa205 --- /dev/null +++ b/public/docs/rust/5-structs-methods/4-2-assoc-funcs.md @@ -0,0 +1,43 @@ +--- +id: rust-structs-methods-assoc-funcs +title: 関連関数 (Associated Functions) +level: 3 +--- + +### 関連関数 (Associated Functions) + +`impl`ブロックの中で、第1引数に `self` を取らない関数も定義できます。これらはインスタンスではなく、型そのものに関連付けられた関数です。 +他言語での「静的メソッド(Static Method)」に相当します。 + +最も一般的な用途は、コンストラクタのような役割を果たす初期化関数の作成です。Rustには `new` というキーワードはありませんが、慣習として `new` という名前の関連関数をよく作ります。 + +```rust:associated_fn.rs +#[derive(Debug)] +struct Circle { + radius: f64, +} + +impl Circle { + // 関連関数(selfがない) + // コンストラクタのように振る舞う + fn new(radius: f64) -> Circle { + Circle { radius } + } + + // メソッド(selfがある) + fn area(&self) -> f64 { + 3.14159 * self.radius * self.radius + } +} + +fn main() { + // 関連関数の呼び出しは :: を使う + let c = Circle::new(2.0); + + println!("Circle radius: {}, area: {}", c.radius, c.area()); +} +``` + +```rust-exec:associated_fn.rs +Circle radius: 2, area: 12.56636 +``` diff --git a/public/docs/rust/5-structs-methods/5-0-summary.md b/public/docs/rust/5-structs-methods/5-0-summary.md new file mode 100644 index 0000000..dd87e25 --- /dev/null +++ b/public/docs/rust/5-structs-methods/5-0-summary.md @@ -0,0 +1,15 @@ +--- +id: rust-structs-methods-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **構造体 (`struct`)** は関連するデータをまとめるカスタム型です。 + * **タプル構造体** は名前付きフィールドを持たない構造体で、特定の型を区別するのに便利です。 + * **メソッド** は `impl` ブロック内に定義し、第1引数に `self` を取ります。 + * **関連関数** は `self` を取らず、`型名::関数名` で呼び出します(コンストラクタ `new` など)。 + * Rustには継承がないため、データ構造とメソッドの組み合わせのみでオブジェクト指向的な設計を行います。 + +次章では、Rustの強力な機能の一つである「列挙型(Enum)」と、フロー制御の要である「パターンマッチ」について学びます。 diff --git a/public/docs/rust/5-structs-methods/5-1-practice1.md b/public/docs/rust/5-structs-methods/5-1-practice1.md new file mode 100644 index 0000000..8660844 --- /dev/null +++ b/public/docs/rust/5-structs-methods/5-1-practice1.md @@ -0,0 +1,40 @@ +--- +id: rust-structs-methods-practice1 +title: '練習問題1: RPGのキャラクター' +level: 3 +--- + +### 練習問題1: RPGのキャラクター + +以下の要件を満たす `Character` 構造体と `impl` ブロックを作成してください。 + +1. フィールド: + * `name`: キャラクター名 (`String`) + * `hp`: 現在のヒットポイント (`i32`) + * `attack_power`: 攻撃力 (`i32`) +2. 関連関数 `new`: + * 名前を受け取り、hpを100、attack\_powerを10で初期化したインスタンスを返す。 +3. メソッド `take_damage`: + * ダメージ量 (`i32`) を受け取り、`hp` から引く。ただし、`hp` は0未満にならないようにする(0で止める)。 + * このメソッドは `hp` を変更するため、`&mut self` が必要です。 + +```rust:practice6_1.rs +// ここにCharacter構造体とimplブロックを実装してください + + +fn main() { + let mut hero = Character::new(String::from("Hero")); + println!("{} has {} HP.", hero.name, hero.hp); + + hero.take_damage(30); + println!("After taking damage, {} has {} HP.", hero.name, hero.hp); + + hero.take_damage(80); + println!("After taking more damage, {} has {} HP.", hero.name, hero.hp); +} +``` +```rust-exec:practice6_1.rs +Hero has 100 HP. +After taking damage, Hero has 70 HP. +After taking more damage, Hero has 0 HP. +``` diff --git a/public/docs/rust/5-structs-methods/5-2-practice2.md b/public/docs/rust/5-structs-methods/5-2-practice2.md new file mode 100644 index 0000000..60b9917 --- /dev/null +++ b/public/docs/rust/5-structs-methods/5-2-practice2.md @@ -0,0 +1,29 @@ +--- +id: rust-structs-methods-practice2 +title: '練習問題2: 座標計算' +level: 3 +--- + +### 練習問題2: 座標計算 + +2次元座標を表すタプル構造体 `Point(f64, f64)` を作成し、以下を実装してください。 + +1. 関連関数 `origin`: 原点 `(0.0, 0.0)` を持つ `Point` を返す。 +2. メソッド `distance_to`: 別の `Point` への参照を受け取り、2点間の距離を計算して返す `f64`。 + * ヒント: 距離の公式は sqrt((x₂ - x₁)² + (y₂ - y₁)²) です。平方根は `f64`型の値に対して `.sqrt()` メソッドで計算できます。 + +```rust:practice6_2.rs +// ここにPointタプル構造体とimplブロックを実装してください + + +fn main() { + let p1 = Point::origin(); + let p2 = Point(3.0, 4.0); + + let distance = p1.distance_to(&p2); + println!("Distance from origin to p2 is {}", distance); +} +``` +```rust-exec:practice6_2.rs +Distance from origin to p2 is 5.0 +``` diff --git a/public/docs/rust/6-enums-pattern/-intro.md b/public/docs/rust/6-enums-pattern/-intro.md new file mode 100644 index 0000000..a972d12 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/-intro.md @@ -0,0 +1,4 @@ +Rustチュートリアル第7章へようこそ。 +これまでの章では、構造体を使って関連するデータをまとめる方法を学びました。この章では、**列挙型(Enum)と、それに関連する強力な制御フロー構造であるパターンマッチ**について学びます。 + +他の言語(C、C++、Javaなど)でのEnumは、単に「名前付き定数のリスト」であることが多いですが、RustのEnumは「代数的データ型(Algebraic Data Types)」に近い性質を持っており、はるかに強力です。 diff --git a/public/docs/rust/6-enums-pattern/1-0-enum-def.md b/public/docs/rust/6-enums-pattern/1-0-enum-def.md new file mode 100644 index 0000000..e24f351 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/1-0-enum-def.md @@ -0,0 +1,32 @@ +--- +id: rust-enums-pattern-enum-def +title: Enumの定義と値の保持 +level: 2 +--- + +## Enumの定義と値の保持 + +最も基本的なEnumの使い方は、C言語などと同様に「ありうる値の列挙」です。しかし、RustのEnumの真価は、**各バリアント(選択肢)にデータを持たせることができる**点にあります。 + +例えば、IPアドレスを表現する場合を考えてみましょう。IPアドレスにはV4とV6があり、それぞれ異なる形式のデータを持ちます。 + +```rust:ip_address.rs +#[derive(Debug)] +enum IpAddr { + V4(u8, u8, u8, u8), // 4つのu8を持つ + V6(String), // Stringを持つ +} + +fn main() { + let home = IpAddr::V4(127, 0, 0, 1); + let loopback = IpAddr::V6(String::from("::1")); + + println!("Home: {:?}", home); + println!("Loopback: {:?}", loopback); +} +``` + +```rust-exec:ip_address.rs +Home: V4(127, 0, 0, 1) +Loopback: V6("::1") +``` diff --git a/public/docs/rust/6-enums-pattern/1-1-enum-vs-struct.md b/public/docs/rust/6-enums-pattern/1-1-enum-vs-struct.md new file mode 100644 index 0000000..66ac32a --- /dev/null +++ b/public/docs/rust/6-enums-pattern/1-1-enum-vs-struct.md @@ -0,0 +1,10 @@ +--- +id: rust-enums-pattern-enum-vs-struct +title: 構造体との違い +level: 3 +--- + +### 構造体との違い + +これを構造体で実装しようとすると、「種類(kind)」フィールドと「データ」フィールドを持つ必要がありますが、V4の場合にV6用のフィールドが無駄になったり、型安全性が下がったりします。 +RustのEnumを使うと、**「V4なら必ず4つの数値がある」「V6なら文字列がある」**ということが型レベルで保証されます。 diff --git a/public/docs/rust/6-enums-pattern/1-2-enum-data.md b/public/docs/rust/6-enums-pattern/1-2-enum-data.md new file mode 100644 index 0000000..98cbb89 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/1-2-enum-data.md @@ -0,0 +1,42 @@ +--- +id: rust-enums-pattern-enum-data +title: あらゆる種類のデータを埋め込める +level: 3 +--- + +### あらゆる種類のデータを埋め込める + +Enumの各バリアントには、名前付きフィールドを持つ構造体のような形や、タプルのような形など、自由に定義できます。 + +```rust:message_enum.rs +#[derive(Debug)] +enum Message { + Quit, // データなし + Move { x: i32, y: i32 }, // 名前付きフィールド(構造体風) + Write(String), // 単一のString(タプル風) + ChangeColor(i32, i32, i32), // 3つのi32(タプル風) +} + +impl Message { + // Enumにもメソッドを定義できる + fn call(&self) { + println!("メッセージを処理します: {:?}", self); + } +} + +fn main() { + let m1 = Message::Write(String::from("hello")); + let m2 = Message::Move { x: 10, y: 20 }; + let m3 = Message::Quit; + + m1.call(); + m2.call(); + m3.call(); +} +``` + +```rust-exec:message_enum.rs +メッセージを処理します: Write("hello") +メッセージを処理します: Move { x: 10, y: 20 } +メッセージを処理します: Quit +``` diff --git a/public/docs/rust/6-enums-pattern/2-0-option.md b/public/docs/rust/6-enums-pattern/2-0-option.md new file mode 100644 index 0000000..888b08c --- /dev/null +++ b/public/docs/rust/6-enums-pattern/2-0-option.md @@ -0,0 +1,19 @@ +--- +id: rust-enums-pattern-option +title: 'Option 型(Null安全性の核心)' +level: 2 +--- + +## Option\ 型(Null安全性の核心) + +Rustには、他の多くの言語にある **Null(ヌル)が存在しません**。 +その代わり、標準ライブラリで定義された `Option` というEnumを使用します。これは「値が存在するかもしれないし、しないかもしれない」ことを表現します。 + +`Option` は以下のように定義されています(概念図): + +```rust +enum Option { + Some(T), // 値がある場合。Tは任意の型。 + None, // 値がない場合。 +} +``` diff --git a/public/docs/rust/6-enums-pattern/2-1-why-safe.md b/public/docs/rust/6-enums-pattern/2-1-why-safe.md new file mode 100644 index 0000000..e93f77a --- /dev/null +++ b/public/docs/rust/6-enums-pattern/2-1-why-safe.md @@ -0,0 +1,39 @@ +--- +id: rust-enums-pattern-why-safe +title: なぜこれが安全なのか? +level: 3 +--- + +### なぜこれが安全なのか? + +`Option` 型と `T` 型(例えば `i32`)は異なる型です。そのため、**「値がないかもしれないもの」を、チェックせずにそのまま計算に使うことがコンパイラレベルで禁止されます。** + +```rust:option_intro.rs +fn main() { + let some_number = Some(5); + let some_string = Some("a string"); + + // Noneの場合は型推論できないため、明示的に型を指定する必要がある + let absent_number: Option = None; + + let x: i8 = 5; + let y: Option = Some(5); + + // 以下の行はコンパイルエラーになります。 + // i8 と Option は足し算できません。 + // let sum = x + y; + + println!("x: {}", x); + // 値を取り出すには明示的な処理が必要(後述のmatchなどを使う) + println!("y is: {:?}", y); + println!("absent is: {:?}", absent_number); +} +``` + +```rust-exec:option_intro.rs +x: 5 +y is: Some(5) +absent is: None +``` + +値を使うためには、`Option` から `T` を取り出す処理(Nullチェックに相当)を必ず書かなければなりません。これにより、「うっかりNullを参照してクラッシュ」という事故を防げます。 diff --git a/public/docs/rust/6-enums-pattern/3-0-match.md b/public/docs/rust/6-enums-pattern/3-0-match.md new file mode 100644 index 0000000..d0f8027 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/3-0-match.md @@ -0,0 +1,9 @@ +--- +id: rust-enums-pattern-match +title: match フロー制御演算子 +level: 2 +--- + +## match フロー制御演算子 + +`match` は、Enumの値を処理するための最も強力なツールです。C言語やJavaの `switch` に似ていますが、より表現力が高く、コンパイラによる**網羅性チェック(Exhaustiveness Check)**があります。 diff --git a/public/docs/rust/6-enums-pattern/3-1-exhaustive.md b/public/docs/rust/6-enums-pattern/3-1-exhaustive.md new file mode 100644 index 0000000..5e99161 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/3-1-exhaustive.md @@ -0,0 +1,46 @@ +--- +id: rust-enums-pattern-exhaustive +title: 網羅性チェック +level: 3 +--- + +### 網羅性チェック + +`match` は、あり得るすべてのパターンをカバーしなければなりません。一つでも漏れているとコンパイルエラーになります。 + +```rust:match_shapes.rs +enum Shape { + Circle(f64), // 半径 + Rectangle(f64, f64), // 幅, 高さ + Triangle(f64, f64, f64), // 3辺の長さ(簡略化のためヘロンの公式用) +} + +fn calculate_area(shape: Shape) -> f64 { + match shape { + // パターンマッチで中のデータを取り出す(バインディング) + Shape::Circle(radius) => { + std::f64::consts::PI * radius * radius + }, + Shape::Rectangle(w, h) => { + w * h + }, + Shape::Triangle(a, b, c) => { + let s = (a + b + c) / 2.0; + (s * (s - a) * (s - b) * (s - c)).sqrt() + } + } +} + +fn main() { + let c = Shape::Circle(10.0); + let r = Shape::Rectangle(3.0, 4.0); + + println!("円の面積: {:.2}", calculate_area(c)); + println!("長方形の面積: {:.2}", calculate_area(r)); +} +``` + +```rust-exec:match_shapes.rs +円の面積: 314.16 +長方形の面積: 12.00 +``` diff --git a/public/docs/rust/6-enums-pattern/3-2-option-match.md b/public/docs/rust/6-enums-pattern/3-2-option-match.md new file mode 100644 index 0000000..0324851 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/3-2-option-match.md @@ -0,0 +1,34 @@ +--- +id: rust-enums-pattern-option-match +title: 'Option と match' +level: 3 +--- + +### Option\ と match + +`Option` の中身を取り出す際も `match` がよく使われます。 + +```rust:match_option.rs +fn plus_one(x: Option) -> Option { + match x { + None => None, + Some(i) => Some(i + 1), + } +} + +fn main() { + let five = Some(5); + let six = plus_one(five); + let none = plus_one(None); + + println!("Five: {:?}", five); + println!("Six: {:?}", six); + println!("None: {:?}", none); +} +``` + +```rust-exec:match_option.rs +Five: Some(5) +Six: Some(6) +None: None +``` diff --git a/public/docs/rust/6-enums-pattern/3-3-placeholder.md b/public/docs/rust/6-enums-pattern/3-3-placeholder.md new file mode 100644 index 0000000..91389b4 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/3-3-placeholder.md @@ -0,0 +1,18 @@ +--- +id: rust-enums-pattern-placeholder +title: _ プレースホルダー +level: 3 +--- + +### `_` プレースホルダー + +全ての値を個別に書きたくない場合、`_`(アンダースコア)を使って「その他すべて」にマッチさせることができます。これは `switch` 文の `default` に相当します。 + +```rust +let dice_roll = 9; +match dice_roll { + 3 => println!("3が出ました"), + 7 => println!("7が出ました"), + _ => println!("それ以外が出ました"), // 3, 7 以外はここに来る +} +``` diff --git a/public/docs/rust/6-enums-pattern/4-0-if-let.md b/public/docs/rust/6-enums-pattern/4-0-if-let.md new file mode 100644 index 0000000..63c8ea7 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/4-0-if-let.md @@ -0,0 +1,49 @@ +--- +id: rust-enums-pattern-if-let +title: if let 記法 +level: 2 +--- + +## if let 記法 + +`match` は強力ですが、**「ある1つのパターンだけ処理して、他は全部無視したい」**という場合には記述が長くなりがちです。 +そのような場合に `if let` が便利です。 + +これは以下の `match` のシンタックスシュガー(糖衣構文)です。 + +```rust +// matchを使う場合(冗長) +let config = Some("config_value"); +match config { + Some(val) => println!("設定値: {}", val), + _ => (), // 何もしない +} +``` + +これと同じことを `if let` で書くと以下のようになります。 + +```rust:if_let_demo.rs +fn main() { + let config = Some("config_value"); + let missing: Option<&str> = None; + + // 「もし config が Some(val) というパターンにマッチするならブロックを実行」 + if let Some(val) = config { + println!("設定値があります: {}", val); + } + + // else も使えます + if let Some(val) = missing { + println!("設定値: {}", val); + } else { + println!("設定値がありません"); + } +} +``` + +```rust-exec:if_let_demo.rs +設定値があります: config_value +設定値がありません +``` + +`if let` を使うとコードが短くなりますが、`match` が強制する「網羅性チェック」の恩恵は失われます。状況に応じて使い分けましょう。 diff --git a/public/docs/rust/6-enums-pattern/5-0-summary.md b/public/docs/rust/6-enums-pattern/5-0-summary.md new file mode 100644 index 0000000..661069b --- /dev/null +++ b/public/docs/rust/6-enums-pattern/5-0-summary.md @@ -0,0 +1,12 @@ +--- +id: rust-enums-pattern-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **Enum(列挙型)**: RustのEnumは、異なる型や量のデータを各バリアントに持たせることができる(代数的データ型)。 + * **Option\**: `Some(T)` と `None` によって、Null安全を実現する。値を使うには `Option` の皮を剥く処理が必須となる。 + * **match**: パターンマッチングを行う制御フロー。コンパイラが全てのケースを網羅しているかチェックしてくれる。 + * **if let**: 単一のパターンだけを扱いたい場合の簡潔な記法。 diff --git a/public/docs/rust/6-enums-pattern/5-1-practice1.md b/public/docs/rust/6-enums-pattern/5-1-practice1.md new file mode 100644 index 0000000..62cb846 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/5-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: rust-enums-pattern-practice1 +title: '練習問題 1: コインの分類機' +level: 3 +--- + +### 練習問題 1: コインの分類機 + +アメリカの硬貨を表すEnum `Coin` を定義してください。 + + * バリアント: `Penny` (1セント), `Nickel` (5セント), `Dime` (10セント), `Quarter` (25セント) + * `Quarter` バリアントには、`UsState` というEnum(各州の名前を持つ)をデータとして持たせてください(例: `Quarter(UsState::Alaska)`)。 + * `Coin` を受け取り、その価値(セント単位)を文字列で返す関数 `value_in_cents` を `match` を使って実装してください。Quarterの場合は、その州の名前も同時に返してください。 + +```rust:practice7_1.rs +// UsState, Coin, value_in_cents を作成してください + +fn main() { + let coin1 = Coin::Penny; + let coin2 = Coin::Quarter(UsState::California); + + println!("Coin1 value: {}", value_in_cents(coin1)); + println!("Coin2 value: {}", value_in_cents(coin2)); +} +``` + +```rust-exec:practice7_1.rs +Coin1 value: 1 cent +Coin2 value: 25 cents from California +``` diff --git a/public/docs/rust/6-enums-pattern/5-2-practice2.md b/public/docs/rust/6-enums-pattern/5-2-practice2.md new file mode 100644 index 0000000..1270ae3 --- /dev/null +++ b/public/docs/rust/6-enums-pattern/5-2-practice2.md @@ -0,0 +1,45 @@ +--- +id: rust-enums-pattern-practice2 +title: '練習問題 2: 簡易計算機' +level: 3 +--- + +### 練習問題 2: 簡易計算機 + +2つの数値に対する操作を表すEnum `Operation` を定義し、計算を行ってください。 + +1. Enum `Operation` を定義します。 + * `Add`: 2つの `i32` を持つ + * `Subtract`: 2つの `i32` を持つ + * `Multiply`: 2つの `i32` を持つ + * `Divide`: 2つの `i32` を持つ +2. 関数 `calculate(op: Operation) -> Option` を実装してください。 + * `match` を使用して計算結果を返します。 + * 割り算の場合、0での除算(ゼロ除算)を防ぐため、分母が0なら `None` を返し、計算できるなら `Some(結果)` を返してください。他の演算は常に `Some` で返します。 +3. `main` 関数でいくつかのパターンを試し、結果を表示してください。 + +```rust:practice7_2.rs +// Operation enum と calculate 関数を実装してください + +fn main() { + let add = Operation::Add(5, 3); + let subtract = Operation::Subtract(10, 4); + let multiply = Operation::Multiply(6, 7); + let divide = Operation::Divide(20, 4); + let divide_by_zero = Operation::Divide(10, 0); + + println!("5 + 3 = {:?}", calculate(add)); + println!("10 - 4 = {:?}", calculate(subtract)); + println!("6 * 7 = {:?}", calculate(multiply)); + println!("20 / 4 = {:?}", calculate(divide)); + println!("10 / 0 = {:?}", calculate(divide_by_zero)); +} +``` + +```rust-exec:practice7_2.rs +5 + 3 = Some(8) +10 - 4 = Some(6) +6 * 7 = Some(42) +20 / 4 = Some(5) +10 / 0 = None +``` diff --git a/public/docs/rust/7-modules/-intro.md b/public/docs/rust/7-modules/-intro.md new file mode 100644 index 0000000..27829f2 --- /dev/null +++ b/public/docs/rust/7-modules/-intro.md @@ -0,0 +1,5 @@ +これまでの章では、関数や構造体を使ってコードを整理する方法を学びました。プログラムの規模が大きくなると、コードをさらに大きな単位で整理し、詳細を隠蔽(カプセル化)し、再利用性を高める必要が出てきます。 + +他の言語(C++のnamespace、Javaのpackage、Pythonのmoduleなど)での経験があれば、Rustのモジュールシステムも直感的に理解できる部分が多いですが、**「デフォルトで非公開(private)」**というRust特有の哲学や、ファイルシステムとの対応関係には注意が必要です。 + +この章では、Rustのコード整理の仕組みである「モジュールシステム」と、外部ライブラリを活用するための「パッケージ管理」について学びます。 diff --git a/public/docs/rust/7-modules/1-0-pkg-crate-module.md b/public/docs/rust/7-modules/1-0-pkg-crate-module.md new file mode 100644 index 0000000..aa99c17 --- /dev/null +++ b/public/docs/rust/7-modules/1-0-pkg-crate-module.md @@ -0,0 +1,15 @@ +--- +id: rust-modules-pkg-crate-module +title: パッケージ、クレート、モジュール +level: 2 +--- + +## パッケージ、クレート、モジュール + +Rustのモジュールシステムは、以下の3つの概念で構成されています。 + +1. **パッケージ (Package):** `Cargo.toml` ファイルを持ち、1つ以上のクレートをビルドするための機能です。通常 `cargo new` で作成されるプロジェクト全体を指します。 +2. **クレート (Crate):** 木構造状のモジュール群からなるコンパイル単位です。 + * **バイナリクレート:** 実行可能な形式(`src/main.rs` がルート)。 + * **ライブラリクレート:** 他のプロジェクトから利用される形式(`src/lib.rs` がルート)。 +3. **モジュール (Module):** クレート内のコードをグループ化し、可視性(公開/非公開)を制御する仕組みです。 diff --git a/public/docs/rust/7-modules/1-1-mod-def.md b/public/docs/rust/7-modules/1-1-mod-def.md new file mode 100644 index 0000000..a0532ca --- /dev/null +++ b/public/docs/rust/7-modules/1-1-mod-def.md @@ -0,0 +1,39 @@ +--- +id: rust-modules-mod-def +title: モジュールの基本定義 (mod) +level: 3 +--- + +### モジュールの基本定義 (`mod`) + +モジュールは `mod` キーワードを使って定義します。モジュールの中に、さらにモジュール(サブモジュール)を入れることも可能です。 + +まずは、1つのファイル内でモジュールを定義して、その構造を見てみましょう。 + +```rust:simple_module.rs +// "restaurant" という名前のモジュールを定義 +mod restaurant { + // モジュール内に関数を定義 + // 注意: デフォルトでは親モジュールからアクセスできません(後述) + fn make_coffee() { + println!("コーヒーを淹れます"); + } + + // サブモジュール + mod front_of_house { + fn add_to_waitlist() { + println!("順番待ちリストに追加しました"); + } + } +} + +fn main() { + // restaurant::make_coffee(); + // restaurant::front_of_house::add_to_waitlist(); +} +``` + +```rust-exec:simple_module.rs +``` + +このコードはコンパイルに通りますが、`main` 関数から `make_coffee` などを呼び出そうとするとエラーになります。それは**可視性**の問題があるからです。 diff --git a/public/docs/rust/7-modules/2-0-visibility.md b/public/docs/rust/7-modules/2-0-visibility.md new file mode 100644 index 0000000..83aca34 --- /dev/null +++ b/public/docs/rust/7-modules/2-0-visibility.md @@ -0,0 +1,41 @@ +--- +id: rust-modules-visibility +title: 可視性と pub キーワード +level: 2 +--- + +## 可視性と `pub` キーワード + +Rustのモジュールシステムの最大の特徴は、**「すべてのアイテム(関数、構造体、モジュールなど)は、デフォルトで非公開(private)」**であるという点です。 + + * **非公開:** 定義されたモジュール自身と、その子モジュールからのみアクセス可能。親モジュールからは見えません。 + * **公開 (`pub`):** 親モジュールや外部からアクセス可能になります。 + +親モジュール(この場合は `main` 関数がいるルート)から子モジュールの中身を使うには、明示的に `pub` をつける必要があります。 + +```rust:simple_module_with_pub.rs +mod restaurant { + // pubがないので、restaurantモジュール内からしか呼べない + fn make_coffee() { + println!("コーヒーを淹れます"); + } + + // pubをつけてサブモジュールも公開 + pub mod front_of_house { + // ここも公開関数にする + pub fn add_to_waitlist() { + println!("順番待ちリストに追加しました"); + } + } +} +fn main() { + // これで呼び出せるようになる + restaurant::front_of_house::add_to_waitlist(); + + // restaurant::make_coffee(); +} +``` + +```rust-exec:simple_module_with_pub.rs +順番待ちリストに追加しました +``` diff --git a/public/docs/rust/7-modules/2-1-struct-visibility.md b/public/docs/rust/7-modules/2-1-struct-visibility.md new file mode 100644 index 0000000..f38700a --- /dev/null +++ b/public/docs/rust/7-modules/2-1-struct-visibility.md @@ -0,0 +1,44 @@ +--- +id: rust-modules-struct-visibility +title: 構造体の可視性 +level: 3 +--- + +### 構造体の可視性 + +構造体に `pub` をつけた場合、**構造体そのものは公開されますが、フィールドはデフォルトで非公開のまま**です。フィールドごとに `pub` を決める必要があります。 + +```rust:struct_visibility.rs +mod kitchen { + pub struct Breakfast { + pub toast: String, // 公開フィールド + seasonal_fruit: String, // 非公開フィールド + } + + impl Breakfast { + // コンストラクタ(これがないと外部からインスタンスを作れない) + pub fn summer(toast: &str) -> Breakfast { + Breakfast { + toast: String::from(toast), + seasonal_fruit: String::from("peaches"), + } + } + } +} + +fn main() { + // コンストラクタ経由でインスタンス作成 + let mut meal = kitchen::Breakfast::summer("ライ麦パン"); + + // 公開フィールドは変更・参照可能 + meal.toast = String::from("食パン"); + println!("トースト: {}", meal.toast); + + // 非公開フィールドにはアクセスできない + // meal.seasonal_fruit = String::from("blueberries"); // エラー! +} +``` + +```rust-exec:struct_visibility.rs +トースト: 食パン +``` diff --git a/public/docs/rust/7-modules/3-0-use-paths.md b/public/docs/rust/7-modules/3-0-use-paths.md new file mode 100644 index 0000000..a950bf7 --- /dev/null +++ b/public/docs/rust/7-modules/3-0-use-paths.md @@ -0,0 +1,10 @@ +--- +id: rust-modules-use-paths +title: use キーワードとパス +level: 2 +--- + +## use キーワードとパス + +モジュールの階層が深くなると、毎回 `restaurant::front_of_house::add_to_waitlist()` のようにフルパスを書くのは面倒です。 +`use` キーワードを使うと、パスをスコープに持ち込み、短い名前で呼び出せるようになります。これは他言語の `import` に相当します。 diff --git a/public/docs/rust/7-modules/3-1-abs-rel-paths.md b/public/docs/rust/7-modules/3-1-abs-rel-paths.md new file mode 100644 index 0000000..6f60d69 --- /dev/null +++ b/public/docs/rust/7-modules/3-1-abs-rel-paths.md @@ -0,0 +1,40 @@ +--- +id: rust-modules-abs-rel-paths +title: 絶対パスと相対パス +level: 3 +--- + +### 絶対パスと相対パス + +パスの指定方法には2種類あります。 + +1. **絶対パス:** クレートのルート(`crate`)から始まるパス。 +2. **相対パス:** 現在のモジュール(`self`)や親モジュール(`super`)から始まるパス。 + +```rust:use_paths.rs +mod sound { + pub mod instrument { + pub fn clarinet() { + println!("クラリネットの音色♪"); + } + } +} + +// 絶対パスで持ち込む +use crate::sound::instrument; + +// 相対パスの場合(このファイル内であれば以下も同じ意味) +// use self::sound::instrument; + +fn main() { + // useのおかげで、直接 instrument を使える + instrument::clarinet(); + instrument::clarinet(); +} +``` + +```rust-exec:use_paths.rs +クラリネットの音色♪ +``` + +> **慣習:** 関数を持ち込むときは、親モジュールまでを `use` して `親::関数()` と呼び出すのが一般的です(関数の出処が明確になるため)。一方、構造体やEnumは完全なパスを指定して直接名前だけで使えるようにすることが多いです。 diff --git a/public/docs/rust/7-modules/4-0-file-split.md b/public/docs/rust/7-modules/4-0-file-split.md new file mode 100644 index 0000000..1e6dbd1 --- /dev/null +++ b/public/docs/rust/7-modules/4-0-file-split.md @@ -0,0 +1,47 @@ +--- +id: rust-modules-file-split +title: モジュールのファイル分割 +level: 2 +--- + +## モジュールのファイル分割 + +これまでは説明のために1つのファイルにすべてのモジュールを書いてきましたが、実際の開発ではファイルを分割します。 + +Rustでは**「ファイルシステム上の構造」**と**「モジュール階層」**が対応します。 + +例えば、`main.rs` と `front_of_house.rs` がある場合: + +```rust:main.rs +// ファイルの中身をモジュールとして宣言 +// これにより、コンパイラは front_of_house.rs を探しに行きます +mod front_of_house; + +pub use crate::front_of_house::hosting; + +fn main() { + hosting::add_to_waitlist(); +} +``` + +```rust:front_of_house.rs +// ここには "mod front_of_house { ... }" の枠は書かない +pub mod hosting { + pub fn add_to_waitlist() { + println!("リストに追加しました"); + } +} +``` + +```rust-exec:main.rs +リストに追加しました +``` + +さらに `front_of_house` の中にサブモジュールを作りたい場合は、ディレクトリを作成します。 + + * `src/main.rs` + * `src/front_of_house/` (ディレクトリ) + * `mod.rs` (または `front_of_house.rs` と同義。ディレクトリのエントリーポイント) + * `hosting.rs` + +このように、Rustはファイルやディレクトリの存在だけで自動的にモジュールを認識するのではなく、**親となるファイルで `mod xxx;` と宣言されたものだけ**をコンパイル対象として認識します。これがC\#やJavaなどの「フォルダにあるものは全部パッケージに含まれる」言語との大きな違いです。 diff --git a/public/docs/rust/7-modules/5-0-external-crates.md b/public/docs/rust/7-modules/5-0-external-crates.md new file mode 100644 index 0000000..9557242 --- /dev/null +++ b/public/docs/rust/7-modules/5-0-external-crates.md @@ -0,0 +1,9 @@ +--- +id: rust-modules-external-crates +title: 外部クレートの利用 +level: 2 +--- + +## 外部クレートの利用 + +Rustのパッケージ管理システムであるCargoを使うと、外部ライブラリ(クレート)を簡単に利用できます。 diff --git a/public/docs/rust/7-modules/5-1-cargotoml.md b/public/docs/rust/7-modules/5-1-cargotoml.md new file mode 100644 index 0000000..0a57ea3 --- /dev/null +++ b/public/docs/rust/7-modules/5-1-cargotoml.md @@ -0,0 +1,14 @@ +--- +id: rust-modules-cargotoml +title: Cargo.toml への追加 +level: 3 +--- + +### Cargo.toml への追加 + +`Cargo.toml` の `[dependencies]` セクションに、使いたいクレートの名前とバージョンを記述します。例えば、乱数を生成する `rand` クレートを使う場合: + +```toml +[dependencies] +rand = "0.8.5" +``` diff --git a/public/docs/rust/7-modules/5-2-usage.md b/public/docs/rust/7-modules/5-2-usage.md new file mode 100644 index 0000000..d91c40a --- /dev/null +++ b/public/docs/rust/7-modules/5-2-usage.md @@ -0,0 +1,31 @@ +--- +id: rust-modules-usage +title: コードでの利用 +level: 3 +--- + +### コードでの利用 + +外部クレートも、プロジェクト内のモジュールと同じように `use` でスコープに持ち込んで使用します。 + +```rust +use std::collections::HashMap; // 標準ライブラリも 'std' という外部クレートのような扱い + +// 外部クレート rand を使用する想定のコード +use rand::Rng; + +fn main() { + let mut scores = HashMap::new(); + scores.insert("Blue", 10); + scores.insert("Yellow", 50); + + println!("スコア: {:?}", scores); + + let secret_number = rand::thread_rng().gen_range(1..101); + println!("乱数: {}", secret_number); +} +``` + +標準ライブラリ(`std`)はデフォルトで利用可能ですが、それ以外のクレートは crates.io (Rustの公式パッケージレジストリ)から自動的にダウンロード・ビルドされます。 + +> 注: my.code(); のオンライン実行環境では外部クレートは使用できません。 diff --git a/public/docs/rust/7-modules/6-0-summary.md b/public/docs/rust/7-modules/6-0-summary.md new file mode 100644 index 0000000..1b37da0 --- /dev/null +++ b/public/docs/rust/7-modules/6-0-summary.md @@ -0,0 +1,15 @@ +--- +id: rust-modules-summary +title: 第8章のまとめ +level: 2 +--- + +## 第8章のまとめ + + * **パッケージとクレート:** `cargo new` で作るのがパッケージ、生成されるバイナリやライブラリがクレートです。 + * **モジュール:** コードを整理する箱です。`mod` で定義します。 + * **可視性:** すべてのアイテムはデフォルトで**非公開 (private)** です。公開するには `pub` をつけます。 + * **パスとuse:** `use` キーワードでモジュールへのパスを省略(インポート)できます。絶対パス(`crate::`)と相対パス(`self::`, `super::`)があります。 + * **ファイル分割:** `mod filename;` と宣言することで、別ファイルのコードをサブモジュールとして読み込みます。 + +この章の内容を理解することで、大規模なアプリケーション開発への準備が整いました。 diff --git a/public/docs/rust/7-modules/6-1-practice1.md b/public/docs/rust/7-modules/6-1-practice1.md new file mode 100644 index 0000000..d0aebea --- /dev/null +++ b/public/docs/rust/7-modules/6-1-practice1.md @@ -0,0 +1,33 @@ +--- +id: rust-modules-practice1 +title: 練習問題1:ライブラリの設計 +level: 3 +--- + +### 練習問題1:ライブラリの設計 + +以下の仕様に従って、架空の図書館システムモジュールを作成してください。 + +1. `library` という親モジュールを作成する。 +2. その中に `books` というサブモジュールを作成する。 +3. `books` モジュールの中に `Book` 構造体を作成する。フィールドは `title` (String, 公開) と `isbn` (String, 非公開) とする。 +4. `Book` 構造体に、新しい本を作成するコンストラクタ `new(title: &str)` を実装する(ISBNは内部で適当な文字列を設定する)。 +5. `main` 関数から `library::books::Book` を使って本を作成し、タイトルを表示するコードを書く。 + + + +```rust:practice8_1.rs + +fn main() { + let my_book = library::books::Book::new("Rust入門"); + println!("本のタイトル: {}", my_book.title); +} +``` +```rust:library/mod.rs +``` +```rust:library/books.rs +``` + +```rust-exec:practice8_1.rs +本のタイトル: Rust入門 +``` diff --git a/public/docs/rust/7-modules/6-2-practice2.md b/public/docs/rust/7-modules/6-2-practice2.md new file mode 100644 index 0000000..e8f7dd0 --- /dev/null +++ b/public/docs/rust/7-modules/6-2-practice2.md @@ -0,0 +1,32 @@ +--- +id: rust-modules-practice2 +title: 練習問題2:パスと可視性の修正 +level: 3 +--- + +### 練習問題2:パスと可視性の修正 + +以下のコードは可視性の設定とパスの指定が誤っているためコンパイルできません。修正して正常に「ネットワーク接続完了」と表示されるようにしてください。 + +```rust:practice8_2.rs +mod network { + fn connect() { + println!("ネットワーク接続完了"); + } + + mod server { + fn start() { + // 親モジュールのconnectを呼びたい + connect(); // ここが間違っている + } + } +} + +fn main() { + // ネットワークモジュールのconnectを呼びたい + connect(); // ここも間違っている +} +``` + +```rust-exec:practice8_2.rs +``` diff --git a/public/docs/rust/8-collections-strings/-intro.md b/public/docs/rust/8-collections-strings/-intro.md new file mode 100644 index 0000000..eb3b8fb --- /dev/null +++ b/public/docs/rust/8-collections-strings/-intro.md @@ -0,0 +1,9 @@ +これまでの章では、配列やタプルといった固定長のデータ構造を扱ってきました。これらはスタックに格納されるため高速ですが、コンパイル時にサイズが決まっている必要があります。 + +本章では、Rustの標準ライブラリが提供する、**ヒープ領域**にデータを格納する動的なコレクションについて学びます。これらは実行時にサイズを変更可能です。特に、他の言語経験者が躓きやすい「Rustにおける文字列(UTF-8)の扱い」には重点を置いて解説します。 + +主に以下の3つを扱います。 + +1. **ベクタ (`Vec`)**: 可変長のリスト。 +2. **文字列 (`String`)**: UTF-8エンコードされたテキスト。 +3. **ハッシュマップ (`HashMap`)**: キーと値のペア。 diff --git a/public/docs/rust/8-collections-strings/1-0-vec.md b/public/docs/rust/8-collections-strings/1-0-vec.md new file mode 100644 index 0000000..246fb83 --- /dev/null +++ b/public/docs/rust/8-collections-strings/1-0-vec.md @@ -0,0 +1,9 @@ +--- +id: rust-collections-strings-vec +title: 'ベクタ (Vec):可変長配列' +level: 2 +--- + +## ベクタ (`Vec`):可変長配列 + +ベクタは、同じ型の値をメモリ上に連続して配置するデータ構造です。C++の `std::vector` や Javaの `ArrayList`、Pythonのリストに近いものです。 diff --git a/public/docs/rust/8-collections-strings/1-1-vec-create.md b/public/docs/rust/8-collections-strings/1-1-vec-create.md new file mode 100644 index 0000000..9d34f6a --- /dev/null +++ b/public/docs/rust/8-collections-strings/1-1-vec-create.md @@ -0,0 +1,38 @@ +--- +id: rust-collections-strings-vec-create +title: ベクタの作成と更新 +level: 3 +--- + +### ベクタの作成と更新 + +`Vec::new()` 関数または `vec!` マクロを使用して作成します。要素を追加するには `push` メソッドを使いますが、ベクタを変更するためには `mut` で可変にする必要があります。 + +```rust:vector_basics.rs +fn main() { + // 空のベクタを作成(型注釈が必要な場合がある) + let mut v: Vec = Vec::new(); + v.push(5); + v.push(6); + v.push(7); + + // vec!マクロを使うと型推論が効くため記述が楽 + let mut v2 = vec![1, 2, 3]; + v2.push(4); + + println!("v: {:?}", v); + println!("v2: {:?}", v2); + + // popで末尾の要素を削除して取得(Optionを返す) + let last = v2.pop(); + println!("Popped: {:?}", last); + println!("v2 after pop: {:?}", v2); +} +``` + +```rust-exec:vector_basics.rs +v: [5, 6, 7] +v2: [1, 2, 3, 4] +Popped: Some(4) +v2 after pop: [1, 2, 3] +``` diff --git a/public/docs/rust/8-collections-strings/1-2-vec-access.md b/public/docs/rust/8-collections-strings/1-2-vec-access.md new file mode 100644 index 0000000..c37b7cd --- /dev/null +++ b/public/docs/rust/8-collections-strings/1-2-vec-access.md @@ -0,0 +1,52 @@ +--- +id: rust-collections-strings-vec-access +title: 要素へのアクセス +level: 3 +--- + +### 要素へのアクセス + +要素へのアクセスには「インデックス記法 `[]`」と「`get` メソッド」の2通りの方法があります。安全性において大きな違いがあります。 + + * `&v[i]`: 存在しないインデックスにアクセスすると**パニック**を起こします。 + * `v.get(i)`: `Option<&T>` を返します。範囲外の場合は `None` になるため、安全に処理できます。 + + + +```rust:vector_access.rs +fn main() { + let v = vec![10, 20, 30, 40, 50]; + + // 方法1: インデックス(確実に存在するとわかっている場合に使用) + let third: &i32 = &v[2]; + println!("3番目の要素は {}", third); + + // 方法2: getメソッド(範囲外の可能性がある場合に使用) + match v.get(100) { + Some(third) => println!("101番目の要素は {}", third), + None => println!("101番目の要素はありません"), + } + + // イテレーション + // &v とすることで所有権を移動させずに参照でループする + print!("要素: "); + for i in &v { + print!("{} ", i); + } + println!(); + + // 値を変更しながらイテレーション + let mut v_mut = vec![1, 2, 3]; + for i in &mut v_mut { + *i += 50; // 参照外し演算子(*)を使って値を書き換える + } + println!("変更後: {:?}", v_mut); +} +``` + +```rust-exec:vector_access.rs +3番目の要素は 30 +101番目の要素はありません +要素: 10 20 30 40 50 +変更後: [51, 52, 53] +``` diff --git a/public/docs/rust/8-collections-strings/2-0-string-utf8.md b/public/docs/rust/8-collections-strings/2-0-string-utf8.md new file mode 100644 index 0000000..ba3670d --- /dev/null +++ b/public/docs/rust/8-collections-strings/2-0-string-utf8.md @@ -0,0 +1,10 @@ +--- +id: rust-collections-strings-string-utf8 +title: 文字列 (String) と UTF-8 +level: 2 +--- + +## 文字列 (`String`) と UTF-8 + +Rustにおける文字列は、他の言語経験者にとって最も混乱しやすい部分の一つです。 +Rustの文字列は、**UTF-8エンコードされたバイトのコレクション**として実装されています。 diff --git a/public/docs/rust/8-collections-strings/2-1-string-str.md b/public/docs/rust/8-collections-strings/2-1-string-str.md new file mode 100644 index 0000000..6008f8a --- /dev/null +++ b/public/docs/rust/8-collections-strings/2-1-string-str.md @@ -0,0 +1,10 @@ +--- +id: rust-collections-strings-string-str +title: String と &str の違い(復習) +level: 3 +--- + +### `String` と `&str` の違い(復習) + + * **`String`**: 所有権を持つ、伸長可能な、ヒープ上の文字列(`Vec` のラッパー)。 + * **`&str` (文字列スライス)**: どこか(バイナリ領域やヒープ領域)にある文字列データへの参照。 diff --git a/public/docs/rust/8-collections-strings/2-2-string-ops.md b/public/docs/rust/8-collections-strings/2-2-string-ops.md new file mode 100644 index 0000000..63773dd --- /dev/null +++ b/public/docs/rust/8-collections-strings/2-2-string-ops.md @@ -0,0 +1,43 @@ +--- +id: rust-collections-strings-string-ops +title: 文字列の操作 +level: 3 +--- + +### 文字列の操作 + +`String` は `Vec` と同様に `push_str` や `+` 演算子で結合できます。 + +```rust:string_ops.rs +fn main() { + let mut s = String::from("foo"); + s.push_str("bar"); // 文字列スライスを追加 + s.push('!'); // 1文字追加 + println!("{}", s); + + let s1 = String::from("Hello, "); + let s2 = String::from("World!"); + + // + 演算子を使用。 + // s1はムーブされ、以降使用できなくなることに注意 + // シグネチャは fn add(self, s: &str) -> String に近いため + let s3 = s1 + &s2; + + println!("{}", s3); + // println!("{}", s1); // コンパイルエラー:s1はムーブ済み + + // format!マクロを使うと所有権を奪わず、読みやすく結合できる + let s4 = String::from("tic"); + let s5 = String::from("tac"); + let s6 = String::from("toe"); + + let s_all = format!("{}-{}-{}", s4, s5, s6); + println!("{}", s_all); +} +``` + +```rust-exec:string_ops.rs +foobar! +Hello, World! +tic-tac-toe +``` diff --git a/public/docs/rust/8-collections-strings/2-3-no-indexing.md b/public/docs/rust/8-collections-strings/2-3-no-indexing.md new file mode 100644 index 0000000..5972615 --- /dev/null +++ b/public/docs/rust/8-collections-strings/2-3-no-indexing.md @@ -0,0 +1,52 @@ +--- +id: rust-collections-strings-no-indexing +title: なぜインデックスアクセスができないのか? +level: 3 +--- + +### なぜインデックスアクセスができないのか? + +多くの言語では `s[0]` で1文字目を取得できますが、Rustでは**コンパイルエラー**になります。 + +Rustの文字列はUTF-8です。ASCII文字は1バイトですが、日本語のような文字は3バイト(またはそれ以上)を使用します。 + + * `"A"` -\> `[0x41]` (1バイト) + * `"あ"` -\> `[0xE3, 0x81, 0x82]` (3バイト) + +もし `"あ"` という文字列に対して `s[0]` で1バイト目を取得できたとしても、それは `0xE3` という意味のないバイト値であり、プログラマが期待する「あ」ではありません。Rustはこの誤解を防ぐために、インデックスアクセスを禁止しています。 + +文字列の中身を見るには、「バイトとして見る」か「文字(スカラ値)として見る」かを明示する必要があります。 + +```rust:string_utf8.rs +fn main() { + let s = "こんにちは"; // UTF-8で各文字3バイト + + // NG: s[0] はコンパイルエラー + + // 文字(char)として反復処理 + // RustのcharはUnicodeスカラ値(4バイト) + print!("Chars: "); + for c in s.chars() { + print!("{} ", c); + } + println!(); + + // バイトとして反復処理 + print!("Bytes: "); + for b in s.bytes() { + print!("{:x} ", b); // 16進数で表示 + } + println!(); + + // 部分文字列(スライス)の取得には範囲指定が必要 + // ただし、文字の境界に合わないバイトを指定すると実行時にパニックする + let s_slice = &s[0..3]; // 最初の3バイト=「こ」 + println!("Slice: {}", s_slice); +} +``` + +```rust-exec:string_utf8.rs +Chars: こ ん に ち は +Bytes: e3 81 93 e3 82 93 e3 81 ab e3 81 a1 e3 81 8a +Slice: こ +``` diff --git a/public/docs/rust/8-collections-strings/3-0-hashmap.md b/public/docs/rust/8-collections-strings/3-0-hashmap.md new file mode 100644 index 0000000..6b4da14 --- /dev/null +++ b/public/docs/rust/8-collections-strings/3-0-hashmap.md @@ -0,0 +1,9 @@ +--- +id: rust-collections-strings-hashmap +title: 'ハッシュマップ (HashMap)' +level: 2 +--- + +## ハッシュマップ (`HashMap`) + +ハッシュマップは、キーと値をマッピングしてデータを格納します。Pythonの `dict`、JavaScriptの `Map` やオブジェクト、Rubyの `Hash` に相当します。標準ライブラリの `std::collections` モジュールからインポートする必要があります。 diff --git a/public/docs/rust/8-collections-strings/3-1-hashmap-basic.md b/public/docs/rust/8-collections-strings/3-1-hashmap-basic.md new file mode 100644 index 0000000..04c3489 --- /dev/null +++ b/public/docs/rust/8-collections-strings/3-1-hashmap-basic.md @@ -0,0 +1,36 @@ +--- +id: rust-collections-strings-hashmap-basic +title: 基本的な操作 +level: 3 +--- + +### 基本的な操作 + +```rust:hashmap_demo.rs +use std::collections::HashMap; + +fn main() { + let mut scores = HashMap::new(); + + // 挿入 + scores.insert(String::from("Blue"), 10); + scores.insert(String::from("Yellow"), 50); + + // 値の取得(getはOption<&V>を返す) + let team_name = String::from("Blue"); + if let Some(score) = scores.get(&team_name) { + println!("{}: {}", team_name, score); + } + + // 反復処理(順序は保証されない) + for (key, value) in &scores { + println!("{}: {}", key, value); + } +} +``` + +```rust-exec:hashmap_demo.rs +Blue: 10 +Yellow: 50 +Blue: 10 +``` diff --git a/public/docs/rust/8-collections-strings/3-2-ownership.md b/public/docs/rust/8-collections-strings/3-2-ownership.md new file mode 100644 index 0000000..501b24c --- /dev/null +++ b/public/docs/rust/8-collections-strings/3-2-ownership.md @@ -0,0 +1,9 @@ +--- +id: rust-collections-strings-ownership +title: 所有権の移動 +level: 3 +--- + +### 所有権の移動 + +`HashMap` にキーや値を挿入すると、`String` のような所有権を持つ型はマップ内に**ムーブ**されます(`i32` のような `Copy` トレイトを持つ型はコピーされます)。挿入後に元の変数を使おうとするとエラーになります。 diff --git a/public/docs/rust/8-collections-strings/3-3-entry-api.md b/public/docs/rust/8-collections-strings/3-3-entry-api.md new file mode 100644 index 0000000..674027a --- /dev/null +++ b/public/docs/rust/8-collections-strings/3-3-entry-api.md @@ -0,0 +1,45 @@ +--- +id: rust-collections-strings-entry-api +title: エントリ API による更新 +level: 3 +--- + +### エントリ API による更新 + +「キーが存在しなければ値を挿入し、存在すれば何もしない(あるいは値を更新する)」というパターンは非常に一般的です。Rustでは `entry` APIを使うとこれを簡潔に書けます。 + +```rust:hashmap_update.rs +use std::collections::HashMap; + +fn main() { + let mut scores = HashMap::new(); + scores.insert(String::from("Blue"), 10); + + // 上書き(同じキーでinsertすると値は上書きされる) + scores.insert(String::from("Blue"), 25); + println!("Blue updated: {:?}", scores); + + // キーがない場合のみ挿入 (or_insert) + scores.entry(String::from("Yellow")).or_insert(50); + scores.entry(String::from("Blue")).or_insert(50); // 既に25があるので無視される + println!("Entry check: {:?}", scores); + + // 既存の値に基づいて更新(単語の出現回数カウントなど) + let text = "hello world wonderful world"; + let mut map = HashMap::new(); + + for word in text.split_whitespace() { + // or_insertは挿入された値への可変参照(&mut V)を返す + let count = map.entry(word).or_insert(0); + *count += 1; // 参照外ししてインクリメント + } + + println!("Word count: {:?}", map); +} +``` + +```rust-exec:hashmap_update.rs +Blue updated: {"Blue": 25} +Entry check: {"Blue": 25, "Yellow": 50} +Word count: {"world": 2, "hello": 1, "wonderful": 1} +``` diff --git a/public/docs/rust/8-collections-strings/4-0-summary.md b/public/docs/rust/8-collections-strings/4-0-summary.md new file mode 100644 index 0000000..87d143b --- /dev/null +++ b/public/docs/rust/8-collections-strings/4-0-summary.md @@ -0,0 +1,11 @@ +--- +id: rust-collections-strings-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **`Vec`**: 同じ型の要素を可変長で保持します。範囲外アクセスには注意し、必要なら `get` メソッドを使用します。 + * **`String`**: UTF-8エンコードされたバイト列のラッパーです。インデックス `[i]` によるアクセスは禁止されており、文字として扱うには `.chars()` を、バイトとして扱うには `.bytes()` を使用します。 + * **`HashMap`**: キーバリューストアです。`entry` APIを使用すると、「存在確認してから挿入・更新」という処理を効率的かつ安全に記述できます。 diff --git a/public/docs/rust/8-collections-strings/4-1-practice1.md b/public/docs/rust/8-collections-strings/4-1-practice1.md new file mode 100644 index 0000000..325e7b5 --- /dev/null +++ b/public/docs/rust/8-collections-strings/4-1-practice1.md @@ -0,0 +1,26 @@ +--- +id: rust-collections-strings-practice1 +title: 練習問題1:整数のリスト分析 +level: 3 +--- + +### 練習問題1:整数のリスト分析 + +整数のベクタ `vec![1, 10, 5, 2, 10, 5, 20, 5]` が与えられたとき、以下の3つを計算して表示するプログラムを作成してください。 + +1. **平均値 (Mean)** +2. **中央値 (Median)**: リストをソートしたときに真ん中に来る値。 +3. **最頻値 (Mode)**: 最も頻繁に出現する値(ヒント:ハッシュマップを使って出現回数を数えます)。 + +```rust:practice9_1.rs +fn main() { + let numbers = vec![1, 10, 5, 2, 10, 5, 20, 5]; + + +} +``` +```rust-exec:practice9_1.rs +平均値: 7.25 +中央値: 5 +最頻値: 5 +``` diff --git a/public/docs/rust/8-collections-strings/4-2-practice2.md b/public/docs/rust/8-collections-strings/4-2-practice2.md new file mode 100644 index 0000000..e8656f9 --- /dev/null +++ b/public/docs/rust/8-collections-strings/4-2-practice2.md @@ -0,0 +1,32 @@ +--- +id: rust-collections-strings-practice2 +title: 問題2:ピッグ・ラテン (Pig Latin) 変換 +level: 3 +--- + +### 問題2:ピッグ・ラテン (Pig Latin) 変換 + +文字列を「ピッグ・ラテン」と呼ばれる言葉遊びに変換する関数を作成してください。ルールは以下の通りです。 + +1. 単語が**母音** (a, i, u, e, o) で始まる場合、単語のお尻に `-hay` を追加します。 + * 例: `apple` -\> `apple-hay` +2. 単語が**子音**で始まる場合、最初の文字を単語のお尻に移動し、`-ay` を追加します。 + * 例: `first` -\> `irst-fay` + +アルファベットのみ、小文字のみの想定で構いません。 + +```rust:practice9_2.rs +fn pig_latin(word: &str) -> String { + // ここに変換ロジックを実装 + + +} +fn main() { + println!("{}", pig_latin("apple")); + println!("{}", pig_latin("first")); +} +``` +```rust-exec:practice9_2.rs +apple-hay +irst-fay +``` diff --git a/public/docs/rust/9-error-handling/-intro.md b/public/docs/rust/9-error-handling/-intro.md new file mode 100644 index 0000000..2b44120 --- /dev/null +++ b/public/docs/rust/9-error-handling/-intro.md @@ -0,0 +1,5 @@ +ようこそ、第10章へ。ここまでRustの所有権や型システムについて学んできましたが、この章では実用的なアプリケーション開発において避けては通れない「エラーハンドリング」について解説します。 + +他の多くの言語(Java, Python, C++など)とRustが最も大きく異なる点の一つが、**「例外(Exception)」が存在しない**ことです。 + +Rustでは、エラーは「誰かがキャッチしてくれることを祈って投げるもの」ではなく、**「戻り値として明示的に処理すべき値」**として扱われます。この設計思想により、予期せぬクラッシュを防ぎ、堅牢なソフトウェアを構築することができます。 diff --git a/public/docs/rust/9-error-handling/1-0-error-types.md b/public/docs/rust/9-error-handling/1-0-error-types.md new file mode 100644 index 0000000..5636b65 --- /dev/null +++ b/public/docs/rust/9-error-handling/1-0-error-types.md @@ -0,0 +1,18 @@ +--- +id: rust-error-handling-error-types +title: エラーの分類 +level: 2 +--- + +## エラーの分類 + +Rustでは、エラーを大きく2つのカテゴリーに分類します。 + +1. **回復不可能なエラー (Unrecoverable Errors):** + * バグ、配列の範囲外アクセス、メモリ不足など。 + * プログラムは即座に停止すべき状況。 + * 手段: `panic!` +2. **回復可能なエラー (Recoverable Errors):** + * ファイルが見つからない、パースの失敗、ネットワーク切断など。 + * 呼び出し元で対処(リトライやエラーメッセージ表示)が可能な状況。 + * 手段: `Result` diff --git a/public/docs/rust/9-error-handling/2-0-panic.md b/public/docs/rust/9-error-handling/2-0-panic.md new file mode 100644 index 0000000..7f29bfb --- /dev/null +++ b/public/docs/rust/9-error-handling/2-0-panic.md @@ -0,0 +1,34 @@ +--- +id: rust-error-handling-panic +title: '回復不可能なエラー (panic!)' +level: 2 +--- + +## 回復不可能なエラー (`panic!`) + +プログラムが続行不可能な状態に陥った場合、Rustはパニック(panic)を起こします。これはデフォルトでスタックを巻き戻し(unwind)、データを掃除してからプログラムを終了させます。 + +もっとも単純な方法は `panic!` マクロを呼ぶことです。 + +```rust:panic_demo.rs +fn main() { + println!("処理を開始します..."); + + // 何か致命的なことが起きたと仮定 + panic!("ここで致命的なエラーが発生しました!"); + + // 以下の行は実行されません + println!("この行は表示されません"); +} +``` + +```rust-exec:panic_demo.rs +処理を開始します... +thread 'main' panicked at panic_demo.rs:5:5: +ここで致命的なエラーが発生しました! +``` + +**ポイント:** + + * `panic!` は、基本的に「プログラムのバグ」や「どうしようもない状況」でのみ使用します。 + * 通常の制御フロー(入力値のバリデーション失敗など)には使用しません。 diff --git a/public/docs/rust/9-error-handling/3-0-result.md b/public/docs/rust/9-error-handling/3-0-result.md new file mode 100644 index 0000000..5ea6eba --- /dev/null +++ b/public/docs/rust/9-error-handling/3-0-result.md @@ -0,0 +1,18 @@ +--- +id: rust-error-handling-result +title: '回復可能なエラー (Result)' +level: 2 +--- + +## 回復可能なエラー (`Result`) + +Rustのエラーハンドリングの主役は `Result` 列挙型です。以前の章で学んだ `Option` に似ていますが、失敗した場合に「なぜ失敗したか(エラー内容)」を持つ点が異なります。 + +定義は以下のようになっています(標準ライブラリに含まれています)。 + +```rust +enum Result { + Ok(T), // 成功時:値 T を含む + Err(E), // 失敗時:エラー E を含む +} +``` diff --git a/public/docs/rust/9-error-handling/3-1-result-basic.md b/public/docs/rust/9-error-handling/3-1-result-basic.md new file mode 100644 index 0000000..4acb05e --- /dev/null +++ b/public/docs/rust/9-error-handling/3-1-result-basic.md @@ -0,0 +1,40 @@ +--- +id: rust-error-handling-result-basic +title: 基本的な Result の処理 +level: 3 +--- + +### 基本的な `Result` の処理 + +他の言語での `try-catch` の代わりに、Rustでは `match` 式を使って成功と失敗を分岐させるのが基本です。 + +```rust:result_basic.rs +fn divide(numerator: f64, denominator: f64) -> Result { + if denominator == 0.0 { + // 失敗時は Err でラップして返す + return Err(String::from("0で割ることはできません")); + } + // 成功時は Ok でラップして返す + Ok(numerator / denominator) +} + +fn main() { + let inputs = vec![(10.0, 2.0), (5.0, 0.0)]; + + for (num, den) in inputs { + let result = divide(num, den); + + match result { + Ok(val) => println!("{} / {} = {}", num, den, val), + Err(e) => println!("エラー: {}", e), + } + } +} +``` + +```rust-exec:result_basic.rs +10 / 2 = 5 +エラー: 0で割ることはできません +``` + +この明示的な分岐により、プログラマはエラー処理を「忘れる」ことができなくなります(コンパイラが `Result` を無視すると警告を出したり、使おうとすると型エラーになるため)。 diff --git a/public/docs/rust/9-error-handling/4-0-unwrap-expect.md b/public/docs/rust/9-error-handling/4-0-unwrap-expect.md new file mode 100644 index 0000000..c298af6 --- /dev/null +++ b/public/docs/rust/9-error-handling/4-0-unwrap-expect.md @@ -0,0 +1,9 @@ +--- +id: rust-error-handling-unwrap-expect +title: unwrap と expect の使い所 +level: 2 +--- + +## `unwrap` と `expect` の使い所 + +毎回 `match` で分岐を書くのは冗長な場合があります。「失敗したらプログラムをクラッシュさせていい」という場合や、「ここでは絶対に失敗しない」と確信がある場合のために、ヘルパーメソッドが用意されています。 diff --git a/public/docs/rust/9-error-handling/4-1-unwrap.md b/public/docs/rust/9-error-handling/4-1-unwrap.md new file mode 100644 index 0000000..2c02f89 --- /dev/null +++ b/public/docs/rust/9-error-handling/4-1-unwrap.md @@ -0,0 +1,9 @@ +--- +id: rust-error-handling-unwrap +title: unwrap +level: 3 +--- + +### `unwrap` + +`Result` が `Ok` なら中身を返し、`Err` なら即座に `panic!` します。手っ取り早いですが、エラーメッセージは一般的で詳細が含まれません。 diff --git a/public/docs/rust/9-error-handling/4-2-expect.md b/public/docs/rust/9-error-handling/4-2-expect.md new file mode 100644 index 0000000..29b2b1c --- /dev/null +++ b/public/docs/rust/9-error-handling/4-2-expect.md @@ -0,0 +1,36 @@ +--- +id: rust-error-handling-expect +title: expect +level: 3 +--- + +### `expect` + +`unwrap` と同じ挙動ですが、パニック時に表示するメッセージを指定できます。**デバッグのしやすさから、通常は `unwrap` よりも `expect` が推奨されます。** + +```rust:unwrap_expect.rs +fn main() { + let valid_str = "100"; + let invalid_str = "hello"; + + // 1. unwrap: 成功時は値を返す + let n: i32 = valid_str.parse().unwrap(); + println!("パース成功: {}", n); + + // 2. expect: 失敗時は指定したメッセージと共に panic! する + // 以下の行を実行するとクラッシュします + let _m: i32 = invalid_str.parse().expect("数値のパースに失敗しました"); +} +``` + +```rust-exec:unwrap_expect.rs +thread 'main' panicked at unwrap_expect.rs:11:35: +数値のパースに失敗しました: ParseIntError { kind: InvalidDigit } +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +**使い所のヒント:** + + * **プロトタイピング・実験:** `unwrap` を多用してロジックを素早く組む。 + * **テストコード:** テスト中に失敗したらテスト自体を落としたいので `unwrap` が有用。 + * **「絶対に失敗しない」ロジック:** 理論上エラーにならない場合でも、型合わせのために `unwrap` が必要な場合があります。 diff --git a/public/docs/rust/9-error-handling/5-0-propagation.md b/public/docs/rust/9-error-handling/5-0-propagation.md new file mode 100644 index 0000000..19456df --- /dev/null +++ b/public/docs/rust/9-error-handling/5-0-propagation.md @@ -0,0 +1,55 @@ +--- +id: rust-error-handling-propagation +title: エラーの伝播(? 演算子) +level: 2 +--- + +## エラーの伝播(`?` 演算子) + +関数内でエラーが発生した際、その場で処理せずに呼び出し元へエラーを返したいことがよくあります。これを「エラーの伝播(propagation)」と呼びます。 + +Rustにはこれを劇的に短く書くための **`?` 演算子** があります。 + + * `Result` 値の後ろに `?` を置く。 + * 値が `Ok` なら、中身を取り出して処理を続行。 + * 値が `Err` なら、**その関数から即座に `return Err(...)` する。** + + + +```rust:error_propagation.rs +use std::num::ParseIntError; + +// 文字列を受け取り、最初の文字を切り出して数値に変換し、10倍して返す +fn get_first_digit_scaled(text: &str) -> Result { + // 1. 文字列が空の場合のエラー処理 + let first_char = text.chars().next().ok_or("空の文字列です".to_string())?; + + // 2. 文字を数値にパース(失敗したらエラー伝播) + // ParseIntError を String に変換するために map_err を使用しています + // (?演算子はFromトレイトを使って自動変換を行いますが、ここでは単純化のため手動変換します) + let num: i32 = first_char.to_string() + .parse() + .map_err(|_| format!("'{}' は数値ではありません", first_char))?; + + Ok(num * 10) +} + +fn main() { + match get_first_digit_scaled("5 apples") { + Ok(v) => println!("計算結果: {}", v), + Err(e) => println!("エラー発生: {}", e), + } + + match get_first_digit_scaled("banana") { + Ok(v) => println!("計算結果: {}", v), + Err(e) => println!("エラー発生: {}", e), + } +} +``` + +```rust-exec:error_propagation.rs +計算結果: 50 +エラー発生: 'b' は数値ではありません +``` + +`?` 演算子を使うことで、`match` のネスト地獄(右方向へのドリフト)を防ぎ、コードの流れを「成功ルート」を中心に記述できます。 diff --git a/public/docs/rust/9-error-handling/6-0-custom-error.md b/public/docs/rust/9-error-handling/6-0-custom-error.md new file mode 100644 index 0000000..5ca04eb --- /dev/null +++ b/public/docs/rust/9-error-handling/6-0-custom-error.md @@ -0,0 +1,75 @@ +--- +id: rust-error-handling-custom-error +title: カスタムエラー型の定義 +level: 2 +--- + +## カスタムエラー型の定義 + +ライブラリや大規模なアプリケーションを作る場合、`String` 型のエラーでは情報が不足します。Rustでは `std::error::Error` トレイトを実装した独自の型(通常は Enum)を定義するのが一般的です。 + +Rustのエコシステムでは、ボイラープレート(定型コード)を減らすために **`thiserror`** というクレートが非常に人気ですが、ここでは仕組みを理解するために標準機能だけで実装してみます。 + +```rust:custom_error.rs +use std::fmt; + +// 1. 独自のエラー型を定義 +#[derive(Debug)] +enum MyToolError { + IoError(String), + ParseError(String), + LogicError, +} + +// 2. Display トレイトの実装(ユーザー向けのエラーメッセージ) +impl fmt::Display for MyToolError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + MyToolError::IoError(msg) => write!(f, "IOエラー: {}", msg), + MyToolError::ParseError(msg) => write!(f, "解析エラー: {}", msg), + MyToolError::LogicError => write!(f, "ロジックエラーが発生しました"), + } + } +} + +// 3. Error トレイトの実装(これを実装することで、Rustのエラーエコシステムと統合される) +impl std::error::Error for MyToolError {} + +// 使用例 +fn dangerous_operation(input: i32) -> Result { + match input { + 0 => Err(MyToolError::IoError("ディスク書き込み失敗".into())), + 1 => Err(MyToolError::ParseError("不正なヘッダ".into())), + 2 => Err(MyToolError::LogicError), + _ => Ok("成功!".into()), + } +} + +fn main() { + let results = [dangerous_operation(0), dangerous_operation(3)]; + + for res in results { + match res { + Ok(s) => println!("結果: {}", s), + Err(e) => println!("失敗: {}", e), // Displayの実装が使われる + } + } +} +``` + +```rust-exec:custom_error.rs +失敗: IOエラー: ディスク書き込み失敗 +結果: 成功! +``` + +**補足:** `thiserror` クレートを使うと、上記の `impl fmt::Display` などをマクロで自動生成でき、以下のように簡潔に書けます(参考情報)。 + +```rust +// thiserrorを使った場合のイメージ +#[derive(thiserror::Error, Debug)] +enum MyToolError { + #[error("IOエラー: {0}")] + IoError(String), + // ... +} +``` diff --git a/public/docs/rust/9-error-handling/7-0-summary.md b/public/docs/rust/9-error-handling/7-0-summary.md new file mode 100644 index 0000000..074be5d --- /dev/null +++ b/public/docs/rust/9-error-handling/7-0-summary.md @@ -0,0 +1,14 @@ +--- +id: rust-error-handling-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **例外はない**: Rustは戻り値 `Result` でエラーを表現する。 + * **Panic**: 回復不可能なエラーには `panic!` を使うが、乱用しない。 + * **Result処理**: 基本は `match` で処理する。 + * **便利メソッド**: `unwrap` は強制取り出し(失敗時パニック)、`expect` はメッセージ付きパニック。 + * **?演算子**: エラーが発生したら即座に呼び出し元へ `Err` を返すショートカット。 + * **型安全性**: コンパイラがエラー処理の漏れを指摘してくれるため、堅牢なコードになる。 diff --git a/public/docs/rust/9-error-handling/7-1-practice1.md b/public/docs/rust/9-error-handling/7-1-practice1.md new file mode 100644 index 0000000..fa76300 --- /dev/null +++ b/public/docs/rust/9-error-handling/7-1-practice1.md @@ -0,0 +1,38 @@ +--- +id: rust-error-handling-practice1 +title: '練習問題 1: 安全な割り算' +level: 3 +--- + +### 練習問題 1: 安全な割り算 + +2つの `f64` を受け取り、割り算の結果を返す関数 `safe_div` を作成してください。 + + * 戻り値は `Result` としてください。 + * 0で割ろうとした場合は、「Division by zero」というエラーメッセージを含む `Err` を返してください。 + * `main` 関数で、正常なケースとエラーになるケースの両方を呼び出し、結果を表示してください。 + + + +```rust:practice10_1.rs +fn safe_div(a: f64, b: f64) -> Result { + // ここにコードを書いてください + +} + +fn main() { + let test_cases = vec![(10.0, 2.0), (5.0, 0.0)]; + + for (a, b) in test_cases { + match safe_div(a, b) { + Ok(result) => println!("{} / {} = {}", a, b, result), + Err(e) => println!("エラー: {}", e), + } + } +} +``` + +```rust-exec:practice10_1.rs +10 / 2 = 5 +エラー: Division by zero +``` diff --git a/public/docs/rust/9-error-handling/7-2-practice2.md b/public/docs/rust/9-error-handling/7-2-practice2.md new file mode 100644 index 0000000..c52a600 --- /dev/null +++ b/public/docs/rust/9-error-handling/7-2-practice2.md @@ -0,0 +1,43 @@ +--- +id: rust-error-handling-practice2 +title: '練習問題 2: データ処理チェーン(エラー伝播)' +level: 3 +--- + +### 練習問題 2: データ処理チェーン(エラー伝播) + +文字列形式の数値(例: "10,20,30")を受け取り、カンマ区切りの最初の数値を2倍にして返す関数 `process_csv_data` を作成してください。 + + * 以下の手順を行い、途中でエラーがあれば `?` 演算子などを使って伝播させてください。 + 1. 文字列をカンマ `,` で分割し( `split` メソッド)、最初の要素を取得する(要素がない場合はエラー)。 + 2. 取得した文字列を `i32` にパースする( `parse` メソッド)(パース失敗はエラー)。 + 3. 数値を2倍して返す。 + * 関数の戻り値は `Result` とします(エラー型の変換が必要な場合は `map_err` を活用してください)。 + + + +```rust:practice10_2.rs +fn process_csv_data(csv: &str) -> Result { + + +} + +fn main() { + let inputs = ["10,20,30", "abc,20", "", " ,50"]; + + for input in inputs { + print!("Input: {:<10} => ", format!("\"{}\"", input)); + match process_csv_data(input) { + Ok(n) => println!("結果: {}", n), + Err(e) => println!("エラー: {}", e), + } + } +} +``` + +```rust-exec:practice10_2.rs +Input: "10,20,30" => 結果: 20 +Input: "abc,20" => エラー: 'abc' は数値ではありません +Input: "" => エラー: 要素が空です +Input: " ,50" => エラー: 要素が空です +``` diff --git a/public/docs/rust/index.yml b/public/docs/rust/index.yml new file mode 100644 index 0000000..27d17df --- /dev/null +++ b/public/docs/rust/index.yml @@ -0,0 +1,39 @@ +name: Rust +description: a +pages: +- slug: 0-intro + name: Rustの世界へようこそ + title: Rustの世界へようこそ +- slug: 1-basics + name: 基本構文と「不変性」 + title: 基本構文と「不変性」の哲学 +- slug: 2-functions-control + name: 関数と制御フロー + title: 関数と制御フロー +- slug: 3-ownership + name: 所有権 + title: 所有権(Ownership)システム +- slug: 4-borrowing-slices + name: 借用とスライス + title: 借用(Borrowing)とスライス +- slug: 5-structs-methods + name: 構造体とメソッド構文 + title: 構造体とメソッド構文 +- slug: 6-enums-pattern + name: 列挙型とパターンマッチ + title: 列挙型(Enum)とパターンマッチ +- slug: 7-modules + name: モジュールシステムとパッケージ管理 + title: モジュールシステムとパッケージ管理 +- slug: 8-collections-strings + name: コレクションと文字列 + title: 一般的なコレクションと文字列 +- slug: 9-error-handling + name: エラーハンドリング + title: エラーハンドリング +- slug: 10-generics-traits + name: ジェネリクスとトレイト + title: ジェネリクスとトレイト +- slug: 11-lifetimes + name: ライフタイム + title: ライフタイム(Lifetimes) diff --git a/public/docs/typescript-1.md b/public/docs/typescript-1.md deleted file mode 100644 index bd9b73c..0000000 --- a/public/docs/typescript-1.md +++ /dev/null @@ -1,136 +0,0 @@ -# 第1章: TypeScriptへようこそ - -JavaScriptの経験がある皆さん、TypeScriptの世界へようこそ。 -この章では、TypeScriptがどのような言語であるか、なぜ現代のWeb開発のスタンダードとなっているのかを理解し、実際に開発環境を整えて最初のコードを実行するところまでを学びます。 - -## TypeScriptとは? - -TypeScriptは、Microsoftによって開発されているオープンソースのプログラミング言語です。一言で言えば、**「型(Type)を持ったJavaScript」**です。 - -重要な特徴は以下の通りです: - - * **JavaScriptのスーパーセット(上位互換):** すべての有効なJavaScriptコードは、有効なTypeScriptコードでもあります。つまり、今日から既存のJS知識をそのまま活かせます。 - * **静的型付け:** JavaScriptは実行時に変数の型が決まる「動的型付け言語」ですが、TypeScriptはコンパイル時(コードを書いている途中やビルド時)に型をチェックする「静的型付け言語」としての性質を持ちます。 - * **コンパイル(トランスパイル):** ブラウザやNode.jsはTypeScriptを直接理解できません。TypeScriptコンパイラ(`tsc`)を使って、標準的なJavaScriptファイルに変換してから実行します。 - -## なぜTypeScriptか? - -「わざわざ型を書くのは面倒だ」と感じるかもしれません。しかし、中〜大規模な開発においてTypeScriptは以下の強力なメリットを提供します。 - -1. **型安全性(バグの早期発見):** - `undefined` のプロパティを読み取ろうとしたり、数値を期待する関数に文字列を渡したりするミスを、コードを実行する前にエディタ上で警告してくれます。 -2. **強力なエディタサポート:** - VS Codeなどのエディタでは、型情報に基づいた正確なコード補完(IntelliSense)が効きます。APIの仕様をドキュメントで調べなくても、ドット`.`を打つだけで利用可能なメソッドが表示されます。 -3. **リファクタリングの容易さ:** - 変数名や関数名を変更する際、型情報があるおかげで、影響範囲を自動的に特定し、安全に一括置換できます。 - -## 環境構築 - -それでは、実際にTypeScriptを動かす環境を作りましょう。 - -### プロジェクトの作成とTypeScriptのインストール - -今回はローカル環境にTypeScriptをインストールする方法を採用します。適当なディレクトリを作成し、ターミナルで以下のコマンドを実行してください。 - -※あらかじめ [Node.js](https://nodejs.org/) がインストールされていることを前提とします。 - -```bash -# プロジェクトフォルダの作成と移動 -mkdir ts-tutorial -cd ts-tutorial - -# package.jsonの初期化 -npm init -y - -# TypeScriptのインストール(開発用依存関係として) -npm install --save-dev typescript -``` - -インストールが完了したら、バージョンを確認してみましょう。 - -```bash -npx tsc --version -# Output: Version 5.x.x (バージョンは時期によります) -``` - -## 最初のTypeScript - -いよいよ最初のTypeScriptコードを書いてみましょう。 - -### コードの記述 - -エディタで `hello.ts` というファイルを作成し、以下のコードを記述します。 -JavaScriptと似ていますが、変数宣言の後ろに `: string` という「型注釈(Type Annotation)」が付いている点に注目してください。 - -```ts:hello.ts -// 変数messageにstring型(文字列)を指定 -const message: string = "Hello, TypeScript World!"; - -// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説) -console.log(message); -``` - -### コンパイルと実行 - -このままではNode.jsで実行できないため、JavaScriptにコンパイルします。 - -```bash -npx tsc hello.ts -``` - -エラーが出なければ、同じフォルダに `hello.js` というファイルが生成されています。中身を確認すると、型注釈が取り除かれた普通のJavaScriptになっているはずです。 - -生成されたJSファイルをNode.jsで実行します。 - -```ts-exec:hello.ts -Hello, TypeScript World! -``` - -これがTypeScript開発の基本的なサイクル(記述 → コンパイル → 実行)です。 - -このウェブサイトでは上のようにコードを編集して実行ボタンを押すとコンパイルと実行を行うことができる環境を埋め込んでいます。 - -またコンパイル後のjsファイルの内容も以下のように確認できます。 - -```js-readonly:hello.js -"use strict"; -// 変数messageにstring型(文字列)を指定 -const message = "Hello, TypeScript World!"; -// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説) -console.log(message); -``` - -## tsconfig.json: コンパイラの設定 - -毎回 `npx tsc hello.ts` のようにファイル名を指定するのは手間ですし、プロジェクト全体の設定も管理しづらくなります。そこで、`tsconfig.json` という設定ファイルを使用します。 - -以下のコマンドで初期設定ファイルを生成します。 - -```bash -npx tsc --init -``` - -生成された `tsconfig.json` には多くの設定項目がありますが、基本として以下の設定が有効(コメントアウトされていない状態)になっているか確認してください。 - -```json -{ - "compilerOptions": { - "target": "es2016", /* コンパイル後のJSのバージョン */ - "module": "commonjs", /* モジュールシステム */ - "strict": true, /* 厳格な型チェックを有効にする(重要) */ - "esModuleInterop": true, /* CommonJSモジュールとの互換性 */ - "forceConsistentCasingInFileNames": true, /* ファイル名の大文字小文字を区別 */ - "skipLibCheck": true /* 定義ファイルのチェックをスキップ */ - } -} -``` - -### 設定ファイルを使ったコンパイル - -`tsconfig.json` があるディレクトリでは、ファイル名を指定せずに以下のコマンドだけで、ディレクトリ内のすべてのTypeScriptファイルが設定に基づいてコンパイルされます。 - -```bash -npx tsc -``` - -> **Note:** `strict: true` はTypeScriptの恩恵を最大限に受けるために非常に重要です。このチュートリアルでは常にこの設定が有効であることを前提に進めます。 diff --git a/public/docs/typescript-2.md b/public/docs/typescript-2.md deleted file mode 100644 index 6eeb2bf..0000000 --- a/public/docs/typescript-2.md +++ /dev/null @@ -1,231 +0,0 @@ -# 第2章: 基本的な型と型推論 - -JavaScriptでの開発経験がある皆様、TypeScriptの世界へようこそ。 -第1章では環境構築を行いましたが、本章からいよいよ具体的なコードを書いていきます。 - -TypeScriptの最大の武器は**「型(Type)」**です。しかし、すべてのコードに手動で型を書く必要はありません。TypeScriptは非常に賢い「型推論」という機能を持っており、JavaScriptを書く感覚のまま、安全性を享受できる場面も多々あります。 - -この章では、基礎となるプリミティブ型、TypeScriptならではの配列やタプルの扱い、そして「何でもあり」な状態をどう制御するかについて学びます。 - -## 2.1 型注釈の構文 (Type Annotations) - -変数を宣言する際、その変数がどのような種類のデータを扱うかを明示することを「型注釈(Type Annotation)」と呼びます。 -構文は非常にシンプルで、変数名の後ろに `: 型名` を記述します。 - -```ts:annotation.ts -// 文字列型の変数を宣言 -let message: string = "Hello, TypeScript!"; - -// 数値型の定数を宣言 -const userId: number = 1001; - -// コンソールに出力 -console.log(message); -console.log(`User ID: ${userId}`); - -// エラーになる例(コメントアウトを外すとエディタ上で赤線が出ます) -// message = 123; // Error: Type 'number' is not assignable to type 'string'. -``` - -```ts-exec:annotation.ts -Hello, TypeScript! -User ID: 1001 -``` -```js-readonly:annotation.js -``` - -> **ポイント:** JavaScriptでは変数にどんな値でも再代入できましたが、TypeScriptでは宣言された型と異なる値を代入しようとすると、コンパイルエラー(またはエディタ上の警告)が発生します。これがバグを未然に防ぐ第一の砦です。 - -## 2.2 主要なプリミティブ型 - -JavaScriptでおなじみのプリミティブ型は、TypeScriptでもそのまま使用できます。 - - * **string**: 文字列 (`"hello"`, `'world'`, \`template\`) - * **number**: 数値 (整数、浮動小数点数、`NaN`, `Infinity` すべて含む) - * **boolean**: 真偽値 (`true`, `false`) - -注意点として、`Number`や`String`(大文字始まり)はラッパーオブジェクト型を指すため、通常は**小文字**の`number`, `string`を使用してください。 - -```ts:primitives.ts -let isDone: boolean = false; -let decimal: number = 6; -let hex: number = 0xf00d; -let color: string = "blue"; - -// テンプレートリテラルもstring型として扱われます -let summary: string = `Color is ${color} and Hex is ${hex}`; - -console.log("Is Done:", isDone); -console.log(summary); -``` - -```ts-exec:primitives.ts -Is Done: false -Color is blue and Hex is 61453 -``` -```js-readonly:primitives.js -``` - -## 2.3 型推論 (Type Inference) - -ここがJavaScript経験者にとって嬉しいポイントです。 -変数の初期化と同時に値を代入する場合、**型注釈を省略してもTypeScriptが自動的に型を判別**してくれます。これを「型推論」と呼びます。 - -```ts:inference.ts -// 型注釈がないが、"TypeScript"という文字列から string型 と推論される -let techName = "TypeScript"; - -// 数値が入っているため、count は number型 と推論される -let count = 42; - -console.log(`Technology: ${techName}, Count: ${count}`); - -// 推論された型と違う値を入れようとするとエラーになる -// count = "Forty-Two"; // Error! -``` - -```ts-exec:inference.ts -Technology: TypeScript, Count: 42 -``` -```js-readonly:inference.js -``` - -> **ベストプラクティス:** 初期値がある場合、わざわざ `: string` などを書く必要はありません。コードが冗長になるのを防ぐため、明示的な型注釈は「初期値がない場合」や「推論される型とは別の型として扱いたい場合」に使用するのが一般的です。 - -## 2.4 特殊な型: any, unknown, never - -TypeScriptには「特定のデータ型」ではない特殊な型が存在します。これらは安全性に大きく関わるため、違いを理解することが重要です。 - -### any: 危険な「何でもあり」 - -`any` 型は、型チェックを無効にする型です。JavaScriptと同じ挙動になりますが、TypeScriptを使うメリットが失われるため、**可能な限り使用を避けてください**。 - -### unknown: 安全な「正体不明」 - -「何が入ってくるかわからない」場合(例:外部APIのレスポンスなど)は、`any`の代わりに`unknown`を使います。`unknown`型の変数は、**「型の絞り込み(Type Narrowing)」を行わない限り、プロパティへのアクセスやメソッドの呼び出しができません**。 - -### never: 決して発生しない - -`never` は「値を持たない」ことを意味します。常に例外を投げる関数や、無限ループなど「終了しない関数」の戻り値として使われます。 - -```ts:special_types.ts -// --- any の例 --- -let looseVariable: any = 4; -looseVariable = "Maybe a string instead"; -looseVariable = false; // エラーにならない(危険!) -console.log("Any:", looseVariable); - -// --- unknown の例 --- -let uncertainValue: unknown = "I am actually a string"; - -// uncertainValue.toUpperCase(); // エラー: Object is of type 'unknown'. - -// 型チェック(絞り込み)を行うと使用可能になる -if (typeof uncertainValue === "string") { - console.log("Unknown (checked):", uncertainValue.toUpperCase()); -} - -// --- never の例 --- -function throwError(message: string): never { - throw new Error(message); -} - -try { - // この関数は決して正常に戻らない - throwError("Something went wrong"); -} catch (e) { - console.log("Error caught"); -} -``` - -```ts-exec:special_types.ts -Any: false -Unknown (checked): I AM ACTUALLY A STRING -Error caught -``` -```js-readonly:special_types.js -``` - -## 2.5 配列とタプル - -データの集合を扱う方法を見ていきましょう。 - -### 配列 (Array) - -配列の型定義には2通りの書き方があります。 - -1. `型[]` (推奨:シンプル) -2. `Array<型>` (ジェネリクス記法) - -### タプル (Tuple) - -配列に似ていますが、**「要素の数が固定」**で、**「各要素の型が決まっている」**ものをタプルと呼びます。CSVの1行や、座標`(x, y)`などを表現するのに便利です。 - -```ts:arrays_tuples.ts -// --- 配列 --- -// 数値の配列 -let fibonacci: number[] = [1, 1, 2, 3, 5]; - -// 文字列の配列(Array記法) -let frameworkList: Array = ["React", "Vue", "Angular"]; - -// --- タプル --- -// [名前, 年齢, 有効フラグ] の順序と型を守る必要がある -let userTuple: [string, number, boolean]; - -userTuple = ["Alice", 30, true]; -// userTuple = [30, "Alice", true]; // エラー: 型の順序が違う - -console.log("First Framework:", frameworkList[0]); -console.log(`User: ${userTuple[0]}, Age: ${userTuple[1]}`); - -// fibonacci.push("8"); // エラー: number[] に string は追加できない -fibonacci.push(8); // OK -console.log("Next Fib:", fibonacci[fibonacci.length - 1]); -``` - -```ts-exec:arrays_tuples.ts -First Framework: React -User: Alice, Age: 30 -Next Fib: 8 -``` -```js-readonly:arrays_tuples.js -``` - -## この章のまとめ - - * 変数宣言時に `: 型名` で型注釈をつけることができる。 - * 初期値がある場合、TypeScriptは自動的に型を推測する(**型推論**)。 - * プリミティブ型は `string`, `number`, `boolean` を小文字で使う。 - * `any` は型チェックを無効にするため避け、不明な値には `unknown` を使う。 - * **配列**は同じ型の集まり、**タプル**は位置と型が固定された配列である。 - -次回は、より複雑なデータ構造を扱うための「オブジェクト、インターフェース、型エイリアス」について学びます。 - -### 練習問題 1: タプルと配列の操作 - -1. 「商品名(string)」と「価格(number)」を持つ**タプル**型の変数 `product` を定義し、`["Keyboard", 5000]` を代入してください。 -2. 文字列の**配列** `tags` を定義し、型推論を使って `["IT", "Gadget"]` で初期化してください。 -3. `tags` に新しいタグ `"Sale"` を追加してください。 -4. それぞれの値をコンソールに出力してください。 - -```ts:practice2_1.ts -``` -```ts-exec:practice2_1.ts -``` -```js-readonly:practice2_1.js -``` - -### 練習問題 2: unknown型の安全な利用 - -1. `unknown` 型の引数 `input` を受け取る関数 `printLength` を作成してください。 -2. 関数内で、`input` が `string` 型である場合のみ、その文字列の長さをコンソールに出力してください(`input.length`)。 -3. `input` が `string` 以外の場合は、「Not a string」と出力してください。 -4. この関数に 文字列 `"TypeScript"` と 数値 `100` を渡して実行してください。 - -```ts:practice2_2.ts -``` -```ts-exec:practice2_2.ts -``` -```js-readonly:practice2_2.js -``` diff --git a/public/docs/typescript-3.md b/public/docs/typescript-3.md deleted file mode 100644 index 4d9e361..0000000 --- a/public/docs/typescript-3.md +++ /dev/null @@ -1,221 +0,0 @@ -# 第3章: オブジェクト、インターフェース、型エイリアス - -JavaScriptでは、オブジェクトはデータを扱うための中心的な存在です。TypeScriptにおいてもそれは変わりませんが、JavaScriptの自由度に「型」という制約を加えることで、開発時の安全性を劇的に高めることができます。 - -この章では、オブジェクトの形状(Shape)を定義するための主要な方法である**型エイリアス(type)**と**インターフェース(interface)**について学びます。 - -## オブジェクトの型付け: インラインでの定義 - -最も基本的な方法は、変数宣言時に直接オブジェクトの構造(型)を記述する方法です。これを「インラインの型定義」や「オブジェクトリテラル型」と呼びます。 - -```ts:inline-object.ts -// 変数名の後ろに : { プロパティ名: 型; ... } を記述します -const book: { title: string; price: number; isPublished: boolean } = { - title: "TypeScript入門", - price: 2500, - isPublished: true, -}; - -console.log(`Title: ${book.title}, Price: ${book.price}`); -``` - -```ts-exec:inline-object.ts -Title: TypeScript入門, Price: 2500 -``` -```js-readonly:inline-object.js -``` - -この方法はシンプルですが、同じ構造を持つオブジェクトを複数作成する場合、毎回型定義を書く必要があり、コードが冗長になります。そこで登場するのが「型に名前を付ける」機能です。 - -## 型エイリアス (type): 型に名前を付ける - -**型エイリアス(Type Alias)**を使用すると、特定の型定義に名前を付け、それを再利用することができます。JavaScriptの経験がある方にとって、これは「型の変数」を作るようなものだとイメージしてください。 - -キーワードは `type` です。慣習として型名には **PascalCase**(大文字始まり)を使用します。 - -```ts:type-alias.ts -// User型を定義 -type User = { - name: string; - age: number; - email: string; -}; - -// 定義したUser型を使用 -const user1: User = { - name: "Tanaka", - age: 28, - email: "tanaka@example.com", -}; - -const user2: User = { - name: "Suzuki", - age: 34, - email: "suzuki@example.com", -}; - -// 関数の引数としても利用可能 -function greet(user: User): string { - return `Hello, ${user.name}!`; -} - -console.log(greet(user1)); -``` - -```ts-exec:type-alias.ts -Hello, Tanaka! -``` - -```js-readonly:type-alias.js -``` - -## インターフェース (interface): オブジェクトの「形状」を定義する - -オブジェクトの構造を定義するもう一つの代表的な方法が **インターフェース(interface)** です。 -JavaやC\#などの言語経験がある方には馴染み深いキーワードですが、TypeScriptのインターフェースは「クラスのための契約」だけでなく、「純粋なオブジェクトの形状定義」としても頻繁に使用されます。 - -```ts:interface-basic.ts -// interfaceキーワードを使用(= は不要) -interface Car { - maker: string; - model: string; - year: number; -} - -const myCar: Car = { - maker: "Toyota", - model: "Prius", - year: 2023, -}; - -console.log(`${myCar.maker} ${myCar.model} (${myCar.year})`); -``` - -```ts-exec:interface-basic.ts -Toyota Prius (2023) -``` -```js-readonly:interface-basic.js -``` - -## type vs interface: 使い分けの基本的なガイドライン - -「`type` と `interface` のどちらを使うべきか?」は、TypeScriptにおける最大の論点の一つです。 -現在のTypeScriptでは機能的な差は非常に少なくなっていますが、基本的な使い分けのガイドラインは以下の通りです。 - -| 特徴 | type (型エイリアス) | interface (インターフェース) | -| :--- | :--- | :--- | -| **主な用途** | プリミティブ、ユニオン型(第5章で解説)、タプル、関数の型など、**あらゆる型**に名前を付ける。 | **オブジェクトの構造**やクラスの実装ルールを定義する。 | -| **拡張性** | 交差型 (`&`) を使って拡張する。 | `extends` キーワードで継承できる。また、同名のinterfaceを定義すると自動でマージされる(Declaration Merging)。 | -| **推奨シーン** | アプリケーション開発全般、複雑な型の組み合わせ。 | ライブラリ開発(拡張性を残すため)、オブジェクト指向的な設計。 | - -**結論としての指針:** -初心者のうちは、**「オブジェクトの定義には `interface`、それ以外(単純な型や複雑な型の合成)には `type`」** というルールで始めるのが無難です。 -あるいは、最近のトレンドとして「一貫して `type` を使う」というチームも増えています。重要なのは**プロジェクト内で統一すること**です。 - -## オプショナルなプロパティ (?) - -オブジェクトによっては、特定のプロパティが存在しない(省略可能である)場合があります。 -プロパティ名の後ろに `?` を付けることで、そのプロパティを **オプショナル(任意)** に設定できます。 - -```ts:optional-properties.ts -interface UserProfile { - username: string; - avatarUrl?: string; // ? があるので、このプロパティはなくてもエラーにならない -} - -const profileA: UserProfile = { - username: "user_a", - avatarUrl: "https://example.com/a.png", -}; - -const profileB: UserProfile = { - username: "user_b", - // avatarUrl は省略可能 -}; - -console.log(profileA); -console.log(profileB); -``` - -```ts-exec:optional-properties.ts -{ username: 'user_a', avatarUrl: 'https://example.com/a.png' } -{ username: 'user_b' } -``` -```js-readonly:optional-properties.js -``` - -この場合、`avatarUrl` の型は実質的に `string | undefined`(文字列 または undefined)として扱われます。 - -## 読み取り専用プロパティ (readonly) - -オブジェクトのプロパティを初期化した後に変更されたくない場合、`readonly` 修飾子を使用します。これは特に、IDや設定値など、不変であるべきデータを扱う際に有用です。 - -```ts:readonly-properties.ts -type Product = { - readonly id: number; // 書き換え不可 - name: string; // 書き換え可能 - price: number; -}; - -const item: Product = { - id: 101, - name: "Laptop", - price: 98000 -}; - -item.price = 95000; // OK: 通常のプロパティは変更可能 - -// 以下の行はコンパイルエラーになります -// item.id = 102; // Error: Cannot assign to 'id' because it is a read-only property. - -console.log(item); -``` - -```ts-exec:readonly-properties.ts -{ id: 101, name: 'Laptop', price: 95000 } -``` -```js-readonly:readonly-properties.js -``` - -注意点として、`readonly` はあくまで TypeScript のコンパイル時のチェックです。実行時の JavaScript コードでは通常のオブジェクトとして振る舞うため、無理やり書き換えるコードが混入すると防げない場合があります(ただし、TSを使っている限りはその前にエラーで気づけます)。 - -## この章のまとめ - - * **インライン定義**: `{ key: type }` でその場限りの型定義が可能。 - * **型エイリアス (`type`)**: 型定義に名前を付けて再利用しやすくする。柔軟性が高い。 - * **インターフェース (`interface`)**: オブジェクトの構造を定義することに特化している。 - * **オプショナル (`?`)**: プロパティを必須ではなく任意にする。 - * **読み取り専用 (`readonly`)**: プロパティの再代入を禁止し、不変性を保つ。 - -### 練習問題 1: 商品在庫管理 - -以下の条件を満たす `Item` インターフェースを定義し、そのオブジェクトを作成してください。 - -1. `id` は数値で、読み取り専用 (`readonly`) であること。 -2. `name` は文字列であること。 -3. `price` は数値であること。 -4. `description` は文字列だが、省略可能 (`?`) であること。 -5. 作成したオブジェクトの `price` を変更し、コンソールに出力してください。 - -```ts:practice3_1.ts -``` -```ts-exec:practice3_1.ts -``` -```js-readonly:practice3_1.js -``` - -### 練習問題 2: ユーザー情報の統合 - -以下の2つの型エイリアスを定義してください。 - -1. `Contact`: `email` (string) と `phone` (string) を持つ。 -2. `Employee`: `id` (number), `name` (string), `contact` (`Contact`型) を持つ。 - * つまり、`Employee` の中に `Contact` 型がネスト(入れ子)されている状態です。 -3. この `Employee` 型を使って、あなたの情報を表現する変数を作成してください。 - -```ts:practice3_2.ts -``` -```ts-exec:practice3_2.ts -``` -```js-readonly:practice3_2.js -``` diff --git a/public/docs/typescript-4.md b/public/docs/typescript-4.md deleted file mode 100644 index 7c8a993..0000000 --- a/public/docs/typescript-4.md +++ /dev/null @@ -1,279 +0,0 @@ -# 第4章: 関数の型定義 - -JavaScript開発者にとって、関数はロジックの中心的な構成要素です。JavaScriptでは引数の数や型が柔軟(あるいはルーズ)ですが、TypeScriptではここを厳密に管理することで、実行時エラーの大半を防ぐことができます。 - -この章では、TypeScriptにおける関数の型定義の基本から、モダンなJavaScript開発で必須となるアロー関数、そして高度なオーバーロードまでを学習します。 - -## 引数と戻り値の型 - -TypeScriptの関数定義において最も基本的なルールは、「引数」と「戻り値」に型を付けることです。 - - * **引数**: 変数名の後ろに `: 型` を記述します。 - * **戻り値**: 引数リストの閉じ括弧 `)` の後ろに `: 型` を記述します。 - -戻り値の型は型推論(Chapter 2参照)によって省略可能ですが、関数の意図を明確にするために明示的に書くことが推奨されます。戻り値がない場合は `void` を使用します。 - -```ts:basic_math.ts -// 基本的な関数宣言 -function add(a: number, b: number): number { - return a + b; -} - -// 戻り値がない関数 -function logMessage(message: string): void { - console.log(`LOG: ${message}`); -} - -const result = add(10, 5); -logMessage(`Result is ${result}`); - -// エラー例(コメントアウトを外すとエラーになります) -// add(10, "5"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'. -``` - -```ts-exec:basic_math.ts -LOG: Result is 15 -``` -```js-readonly:basic_math.js -``` - -## オプショナル引数とデフォルト引数 - -JavaScriptでは引数を省略すると `undefined` になりますが、TypeScriptでは定義された引数は**必須**とみなされます。引数を省略可能にするには、特別な構文が必要です。 - -### オプショナル引数 (`?`) - -引数名の後ろに `?` を付けることで、その引数を省略可能(オプショナル)にできます。省略された場合の値は `undefined` です。 - -> **注意:** オプショナル引数は、必ず必須引数の**後ろ**に配置する必要があります。 - -### デフォルト引数 (`=`) - -ES6(JavaScript)と同様に、引数にデフォルト値を指定できます。デフォルト値がある場合、TypeScriptはその引数を「型推論」し、かつ「省略可能」として扱います。 - -```ts:optional_default.ts -// titleは省略可能 -function greet(name: string, title?: string): string { - if (title) { - return `Hello, ${title} ${name}!`; - } - return `Hello, ${name}!`; -} - -// powerのデフォルト値は2 -// 戻り値の型はnumberと推論されるため省略可能 -function exponent(base: number, power: number = 2) { - return base ** power; -} - -console.log(greet("Tanaka")); -console.log(greet("Sato", "Dr.")); -console.log(`2^2 = ${exponent(2)}`); -console.log(`2^3 = ${exponent(2, 3)}`); -``` - -```ts-exec:optional_default.ts -Hello, Tanaka! -Hello, Dr. Sato! -2^2 = 4 -2^3 = 8 -``` -```js-readonly:optional_default.js -``` - -## アロー関数と `this` - -### アロー関数の型定義 - -アロー関数を変数に代入する場合、引数と戻り値の記述場所は通常の関数と同様です。 - -```ts:arrow_func.ts -const multiply = (x: number, y: number): number => { - return x * y; -}; - -// 1行で書く場合(暗黙のreturn) -const subtract = (x: number, y: number): number => x - y; - -console.log(multiply(4, 5)); -console.log(subtract(10, 3)); -``` - -```ts-exec:arrow_func.ts -20 -7 -``` -```js-readonly:arrow_func.js -``` - -### `this` の型指定 - -JavaScriptにおいて `this` の挙動は複雑ですが、TypeScriptでは `this` が何を指すかを明示的に型定義できます。 -これを行うには、関数の**最初の引数**として `this` という名前の「偽の引数」を定義します。これはコンパイル後のJavaScriptには出力されません。 - -```ts:this_context.ts -interface User { - name: string; - count: number; -} - -function counter(this: User) { - this.count += 1; - console.log(`${this.name}: ${this.count}`); -} - -const userA: User = { name: "Alice", count: 0 }; - -// callメソッドを使ってthisコンテキストを指定して実行 -counter.call(userA); -counter.call(userA); - -// アロー関数はthisを持たないため、この構文は使いません -``` - -```ts-exec:this_context.ts -Alice: 1 -Alice: 2 -``` -```js-readonly:this_context.js -``` - -## 関数のオーバーロード - -JavaScriptでは「引数の型や数によって挙動が変わる関数」をよく書きます。TypeScriptでこれを表現するには**オーバーロード**を使用します。 - -オーバーロードは以下の2つの部分で構成されます: - -1. **オーバーロードシグネチャ**: 関数の呼び出しパターンを定義(複数可)。実装は書きません。 -2. **実装シグネチャ**: 実際の関数の処理。外部からは直接見えません。 - -```ts:overload.ts -// 1. オーバーロードシグネチャ(呼び出し可能なパターン) -function double(value: number): number; -function double(value: string): string; - -// 2. 実装シグネチャ(すべてのパターンを網羅できる型定義にする) -function double(value: number | string): number | string { - if (typeof value === 'number') { - return value * 2; - } else { - return value.repeat(2); - } -} - -const numResult = double(10); // 型は number として推論される -const strResult = double("Hi"); // 型は string として推論される - -console.log(numResult); -console.log(strResult); - -// double(true); // エラー: booleanを受け入れるオーバーロードはありません -``` - -```ts-exec:overload.ts -20 -HiHi -``` -```js-readonly:overload.js -``` - - -> **ポイント:** 実装シグネチャ(`number | string` の部分)は直接呼び出せません。必ず上で定義したシグネチャ(`number` または `string`)に一致する必要があります。 - -## 残余引数 (Rest Parameters) - -引数の数が可変である場合(可変長引数)、JavaScriptと同様に `...args` 構文を使用します。 -TypeScriptでは、この `args` は必ず**配列の型**である必要があります。 - -```ts:rest_params.ts -// 数値を好きなだけ受け取り、合計を返す -function sumAll(...numbers: number[]): number { - return numbers.reduce((total, num) => total + num, 0); -} - -// 文字列を結合する -function joinStrings(separator: string, ...words: string[]): string { - return words.join(separator); -} - -console.log(sumAll(1, 2, 3, 4, 5)); -console.log(joinStrings("-", "TypeScript", "is", "fun")); -``` - -```ts-exec:rest_params.ts -15 -TypeScript-is-fun -``` -```js-readonly:rest_params.js -``` - -## 関数の型エイリアス - -コールバック関数を引数に取る場合など、関数の型定義が長くなりがちです。 -第3章で学んだ `type`(型エイリアス)を使って、関数のシグネチャそのものに名前を付けることができます。 - -構文: `type 型名 = (引数: 型) => 戻り値の型;` - -```ts:func_alias.ts -// 関数の型定義を作成 -type MathOperation = (x: number, y: number) => number; - -// 作成した型を適用 -const addition: MathOperation = (a, b) => a + b; -const multiplication: MathOperation = (a, b) => a * b; - -// 高階関数での利用例(関数を受け取る関数) -function compute(x: number, y: number, op: MathOperation): number { - return op(x, y); -} - -console.log(compute(10, 2, addition)); -console.log(compute(10, 2, multiplication)); -``` - -```ts-exec:func_alias.ts -12 -20 -``` -```js-readonly:func_alias.js -``` - - -## この章のまとめ - - * 関数定義では、引数と戻り値に型を明記するのが基本です。 - * `?` でオプショナル引数、`=` でデフォルト引数を定義できます。 - * アロー関数や `this` の型付けもサポートされており、コンテキストミスを防げます。 - * **オーバーロード**を使うことで、引数によって戻り値の型が変わる柔軟な関数を定義できます。 - * **型エイリアス**を使うことで、複雑な関数シグネチャを再利用可能なパーツとして定義できます。 - -### 練習問題 1: ユーザー検索関数 - -以下の要件を満たす `findUser` 関数をアロー関数として作成してください。 - -1. 引数 `id` (number) と `name` (string) を受け取る。 -2. `name` はオプショナル引数とする。 -3. 戻り値は「検索中: [id] [name]」という文字列(nameがない場合は「検索中: [id] ゲスト」)とする。 -4. 関数の型定義(Type Alias)を `SearchFunc` として先に定義し、それを適用すること。 - -```ts:practice4_1.ts -``` -```ts-exec:practice4_1.ts -``` -```js-readonly:practice4_1.js -``` - -### 練習問題 2: データ変換のオーバーロード - -以下の要件を満たす `convert` 関数を `function` キーワードで作成してください。 - -1. 引数が `number` の場合、それを `string` に変換して返す(例: `100` -\> `"100"`)。 -2. 引数が `string` の場合、それを `number` に変換して返す(例: `"100"` -\> `100`)。 -3. 適切なオーバーロードシグネチャを2つ定義すること。 - -```ts:practice4_2.ts -``` -```ts-exec:practice4_2.ts -``` -```js-readonly:practice4_2.js -``` \ No newline at end of file diff --git a/public/docs/typescript-5.md b/public/docs/typescript-5.md deleted file mode 100644 index 8d4fd62..0000000 --- a/public/docs/typescript-5.md +++ /dev/null @@ -1,299 +0,0 @@ -# 第5章: 型を組み合わせる - -これまでの章では、`string` や `number`、あるいは特定のオブジェクトの形といった「単一の型」を扱ってきました。しかし、現実のアプリケーション開発(特にJavaScriptの世界)では、「IDは数値かもしれないし、文字列かもしれない」「成功時はデータを返すが、失敗時はエラーメッセージを返す」といった柔軟なデータ構造が頻繁に登場します。 - -この章では、既存の型をパズルのように組み合わせて、より複雑で柔軟な状況を表現する方法を学びます。 - -## Union型 (共用体型) - -Union型(共用体型)は、**「A または B」**という状態を表現します。パイプ記号 `|` を使用して記述します。 - -JavaScriptでは変数の型が動的であるため、1つの変数に異なる型の値が入ることがよくありますが、TypeScriptではUnion型を使ってこれを安全に定義できます。 - -```ts:union-basic.ts -// idは数値、または文字列を許容する -let id: number | string; - -id = 101; // OK -id = "user-a"; // OK -// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'. - -function printId(id: number | string) { - console.log(`Your ID is: ${id}`); -} - -printId(123); -printId("ABC"); -``` - -```ts-exec:union-basic.ts -Your ID is: 123 -Your ID is: ABC -``` -```js-readonly:union-basic.js -``` - -> **注意点:** Union型を使用している変数は、その時点では「どの型か確定していない」ため、**すべての候補に共通するプロパティやメソッド**しか操作できません。特定の型として扱いたい場合は、後述する「型ガード」を使用します。 - -## Literal型 (リテラル型) - -`string` や `number` は「あらゆる文字列」や「あらゆる数値」を受け入れますが、**「特定の値だけ」**を許可したい場合があります。これをLiteral型(リテラル型)と呼びます。 - -通常、Literal型は単独で使うよりも、Union型と組み合わせて**「決まった選択肢のいずれか」**を表現するのによく使われます(Enumの代わりとしてもよく利用されます)。 - -```ts:literal-types.ts -// 文字列リテラル型とUnion型の組み合わせ -type TrafficLight = 'red' | 'yellow' | 'green'; - -let currentLight: TrafficLight = 'red'; - -// currentLight = 'blue'; // Error: Type '"blue"' is not assignable to type 'TrafficLight'. - -// 数値リテラルも可能 -type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6; -let dice: DiceRoll = 3; - -console.log(`Light: ${currentLight}, Dice: ${dice}`); -``` - -```ts-exec:literal-types.ts -Light: red, Dice: 3 -``` -```js-readonly:literal-types.js -``` - -## Intersection型 (交差型) - -Intersection型(交差型)は、**「A かつ B」**を表します。アンパサンド `&` を使用します。 -これは主にオブジェクトの型定義を合成(マージ)して、**「複数の型のすべてのプロパティを持つ新しい型」**を作る際によく使用されます。 - -```ts:intersection-types.ts -type Person = { - name: string; -}; - -type Employee = { - employeeId: number; - department: string; -}; - -// Person かつ Employee の特徴を持つ型 -type CompanyMember = Person & Employee; - -const member: CompanyMember = { - name: "Suzuki", - employeeId: 5001, - department: "Engineering" - // どれか一つでも欠けるとエラーになります -}; - -console.log(member); -``` - -```ts-exec:intersection-types.ts -{ name: 'Suzuki', employeeId: 5001, department: 'Engineering' } -``` -```js-readonly:intersection-types.js -``` - -> **補足:** プリミティブ型同士で `string & number` のように交差させると、両方を満たす値は存在しないため、型は `never`(ありえない値)になります。Intersection型は主にオブジェクト型の合成に使われます。 - -## null と undefined - -TypeScriptには `null` 型と `undefined` 型が存在します。 -`tsconfig.json` の設定で `strictNullChecks: true`(推奨設定)になっている場合、これらは他の型(stringなど)には代入できません。 - -値が存在しない可能性がある場合は、Union型を使って明示的に `null` や `undefined` を許可します。 - -```ts:nullable.ts -// string または null を許容する -let userName: string | null = "Tanaka"; - -userName = null; // OK - -// オプショナルなプロパティ(?)は 「型 | undefined」 の糖衣構文に近い動きをします -type UserConfig = { - theme: string; - notification?: boolean; // boolean | undefined -}; - -const config: UserConfig = { - theme: "dark" - // notification は省略可能 (undefined) -}; - -console.log(`User: ${userName}, Theme: ${config.theme}`); -``` - -```ts-exec:nullable.ts -User: null, Theme: dark -``` -```js-readonly:nullable.js -``` - -## 型ガード (Type Guards) - -Union型 (`string | number`) の変数があるとき、プログラムの中で「今は `string` なのか `number` なのか」を区別して処理を分けたい場合があります。これを**型の絞り込み(Narrowing)**と言います。 - -TypeScriptのコンパイラが「このブロック内ではこの変数はこの型だ」と認識できるようにするチェック処理を**型ガード**と呼びます。 - -### typeof 演算子 - -プリミティブ型(string, number, boolean, symbol, undefined)の判定に使います。 - -```ts:type-guard-typeof.ts -function formatPrice(price: number | string) { - // ここでは price は number | string - - if (typeof price === 'string') { - // このブロック内では price は 'string' 型として扱われる - return parseInt(price).toLocaleString(); - } else { - // このブロック内では price は 'number' 型として扱われる - return price.toLocaleString(); - } -} - -console.log(formatPrice(10000)); -console.log(formatPrice("20000")); -``` - -```ts-exec:type-guard-typeof.ts -10,000 -20,000 -``` -```js-readonly:type-guard-typeof.js -``` - -### in 演算子 - -オブジェクトが特定のプロパティを持っているかどうかで型を絞り込みます。 - -```ts:type-guard-in.ts -type Fish = { swim: () => void }; -type Bird = { fly: () => void }; - -function move(animal: Fish | Bird) { - if ('swim' in animal) { - // ここでは Fish 型 - animal.swim(); - } else { - // ここでは Bird 型 - animal.fly(); - } -} - -const fish: Fish = { swim: () => console.log("Swimming...") }; -move(fish); -``` - -```ts-exec:type-guard-in.ts -Swimming... -``` -```js-readonly:type-guard-in.js -``` - -### instanceof 演算子 - -クラスのインスタンスかどうかを判定します(第7章のクラスで詳しく扱いますが、Dateなどの組み込みオブジェクトでも有効です)。 - -```ts:type-guard-instanceof.ts -function logDate(value: string | Date) { - if (value instanceof Date) { - console.log(value.toISOString()); - } else { - console.log(value); - } -} -``` -```js-readonly:type-guard-instanceof.js -``` - -## 型アサーション (Type Assertions) - -時に、プログラマがTypeScriptコンパイラよりも型の詳細を知っている場合があります。例えば、外部APIからのレスポンスや、DOM要素の取得などです。 - -`as` キーワードを使うと、コンパイラに対して「この変数はこの型であるとして扱ってくれ」と強制できます。 - -```ts:assertion.ts -// unknown型は何でも入るが、そのままでは操作できない型 -let someValue: unknown = "This is a string"; - -// コンパイラに「これはstringだからlengthを使わせて」と伝える -let strLength: number = (someValue as string).length; - -console.log(strLength); - -// 注意: 全く互換性のない型への変換はエラーになりますが、 -// unknownを経由すると無理やり変換できてしまうため、乱用は避けてください。 -// let wrong = (123 as string); // Error -// let dangerous = (123 as unknown as string); // OKだが実行時にバグの元 -``` - -```ts-exec:assertion.ts -16 -``` -```js-readonly:assertion.js -``` - -> **注意:** 型アサーションはあくまで「コンパイル時の型チェックを黙らせる」機能であり、実行時の型変換を行うわけではありません。実行時に値が想定と違う場合、クラッシュの原因になります。可能な限り、型ガードを使って安全に絞り込むことを推奨します。 - -## この章のまとめ - - * **Union型 (`|`)**: 複数の型のうち「いずれか」を表す。 - * **Literal型**: 特定の値のみを許容する型。Union型と組み合わせて列挙型のように使える。 - * **Intersection型 (`&`)**: 複数の型を「合成」して、すべてのプロパティを持つ型を作る。 - * **null / undefined**: `strictNullChecks` 環境下では、Union型を使って明示的に許容する必要がある。 - * **型ガード**: `typeof`, `in`, `instanceof` などを使って、Union型から特定の型へ絞り込む。 - * **型アサーション (`as`)**: 型を強制的に指定するが、安全性のために使用は慎重に行う。 - -### 練習問題1: 結果の型定義 - -APIリクエストの結果を表す `Result` 型を定義してください。 - - * 成功時は `success: true` と `data: string` を持ちます。 - * 失敗時は `success: false` と `error: string` を持ちます。 - * `handleResult` 関数内で型ガードを使い、成功ならデータを、失敗ならエラーメッセージをログ出力してください。 - -```ts:practice5_1.ts -// ここに SuccessResult, FailureResult, Result 型を定義してください -// type Result = ... - -function handleResult(result: Result) { - // ここに処理を実装してください -} - -// テスト用 -handleResult({ success: true, data: "Data loaded" }); -handleResult({ success: false, error: "Network error" }); -``` -```ts-exec:practice5_1.ts -``` -```js-readonly:practice5_1.js -``` - - -### 練習問題2: 図形の面積計算 - -`Circle` 型と `Square` 型を定義し、それらのUnion型である `Shape` を定義してください。 - - * `Circle` は `kind: 'circle'` と `radius: number` を持ちます。 - * `Square` は `kind: 'square'` と `sideLength: number` を持ちます。 - * `getArea` 関数で、渡された図形に応じて面積を計算して返してください(円周率は `Math.PI` を使用)。 - -```ts:practice5_2.ts -// ここに型を定義 - -function getArea(shape: Shape): number { - // ここに実装 (switch文やif文で kind プロパティによる絞り込みを行う) - return 0; -} - -// テスト用 -console.log(getArea({ kind: 'circle', radius: 10 })); -console.log(getArea({ kind: 'square', sideLength: 5 })); -``` -```ts-exec:practice5_2.ts -``` -```js-readonly:practice5_2.js -``` diff --git a/public/docs/typescript-6.md b/public/docs/typescript-6.md deleted file mode 100644 index 955ce5f..0000000 --- a/public/docs/typescript-6.md +++ /dev/null @@ -1,272 +0,0 @@ -# 第6章: ジェネリクス (Generics) - -第6章では、TypeScriptを使いこなす上で非常に強力な機能である**ジェネリクス (Generics)** について学びます。JavaやC\#などの言語経験がある方には馴染み深い概念かもしれませんが、JavaScriptの世界から来た方にとっては少し抽象的に感じるかもしれません。しかし、これを理解することで、**「柔軟性」と「安全性」を両立したコード**が書けるようになります。 - -## Genericsの必要性: 型を引数のように扱う - -プログラミングをしていると、「処理内容は同じだが、扱うデータの型だけが違う」という場面によく遭遇します。 - -例えば、「引数をそのまま返す関数」を考えてみましょう。 - -```ts -// 数値を受け取って数値を返す -function returnNumber(arg: number): number { - return arg; -} - -// 文字列を受け取って文字列を返す -function returnString(arg: string): string { - return arg; -} - -// どんな型でも受け取れるが、戻り値の型情報が失われる(any) -function returnAny(arg: any): any { - return arg; -} -``` - -`returnNumber` と `returnString` はロジックが完全に重複しています。一方、`returnAny` は重複を防げますが、TypeScriptの利点である型チェックが無効になってしまいます。 - -ここで登場するのが **ジェネリクス** です。ジェネリクスを使うと、**「型そのもの」を引数のように受け取る**ことができます。 - -## Generics関数 - -ジェネリクスを使った関数の定義を見てみましょう。 -型変数は慣習として `T` (Typeの頭文字) がよく使われます。 - -```ts:identity_func.ts -// は「この関数内で T という名前の型変数を使います」という宣言 -function identity(arg: T): T { - console.log(`引数の型: ${typeof arg}, 値: ${arg}`); - return arg; -} - -// 使用例1: 明示的に型を指定する -const output1 = identity("Hello Generics"); - -// 使用例2: 型推論に任せる (推奨) -// 引数が数値なので、T は number に自動的に推論される -const output2 = identity(100); - -// output1は string型、output2は number型 として扱われるため安全 -// output1.toFixed(2); // エラー: string型にtoFixedは存在しない -``` - -```ts-exec:identity_func.ts -引数の型: string, 値: Hello Generics -引数の型: number, 値: 100 -``` -```js-readonly:identity_func.js -``` - -このように、`identity` 関数は定義時点では型を固定せず、**呼び出す瞬間に型が決まる**という柔軟な性質を持ちます。 - -## Genericsインターフェース - -関数だけでなく、インターフェースもジェネリクスにできます。これにより、再利用性の高いデータ構造を定義できます。 -例えば、「何かを入れる箱 (Box)」のような汎用的な型を作る場合に便利です。 - -```ts:generic_box.ts -// T型の値を持つ value プロパティがあるインターフェース -interface Box { - value: T; -} - -// 文字列を入れる箱 -const stringBox: Box = { - value: "TypeScript" -}; - -// 数値を入れる箱 -const numberBox: Box = { - value: 42 -}; - -console.log(stringBox.value.toUpperCase()); // 文字列のメソッドが使える -console.log(numberBox.value.toFixed(1)); // 数値のメソッドが使える -``` - -```ts-exec:generic_box.ts -TYPESCRIPT -42.0 -``` -```js-readonly:generic_box.js -``` - -JavaScriptでは特に意識せずオブジェクトに様々な型の値を入れていましたが、TypeScriptではこのようにジェネリクスを使うことで、「中身が何かわからない」状態を防ぎつつ、どんな型でも許容する構造を作れます。 - -## Genericsクラス - -クラスでも同様にジェネリクスを使用できます。リストやキュー、スタックなどのデータ構造を実装する際によく使われます。 - -ここではシンプルな「スタック(後入れ先出し)」クラスを作ってみましょう。 - -```ts:simple_stack.ts -class SimpleStack { - private items: T[] = []; - - // データを追加する - push(item: T): void { - this.items.push(item); - } - - // データを取り出す - pop(): T | undefined { - return this.items.pop(); - } - - // 現在の中身を表示(デバッグ用) - print(): void { - console.log(this.items); - } -} - -// 数値専用のスタック -const numberStack = new SimpleStack(); -numberStack.push(10); -numberStack.push(20); -// numberStack.push("30"); // エラー: number以外は入れられない -console.log("Pop:", numberStack.pop()); - -// 文字列専用のスタック -const stringStack = new SimpleStack(); -stringStack.push("A"); -stringStack.push("B"); -stringStack.print(); -``` - -```ts-exec:simple_stack.ts -Pop: 20 -[ 'A', 'B' ] -``` -```js-readonly:simple_stack.js -``` - -もしジェネリクスを使わずにこれを実装しようとすると、`NumberStack`クラスと`StringStack`クラスを個別に作るか、`any`を使って安全性を犠牲にするしかありません。ジェネリクスを使えば、1つのクラス定義で安全に様々な型に対応できます。 - -## 型制約 (extends): Generics型に制約を設ける - -ジェネリクスは「どんな型でも受け入れられる」のが基本ですが、時には「ある特定の条件を満たす型だけを受け入れたい」という場合があります。 - -例えば、引数の `.length` プロパティにアクセスしたい場合を考えてみましょう。 - -```ts:without_constraints.ts -function logLength(arg: T): void { - console.log(arg.length); // エラー! Tがlengthを持っているとは限らない -} -``` -```ts-exec:without_constraints.ts -without_constraints.ts:2:19 - error TS2339: Property 'length' does not exist on type 'T'. - -2 console.log(arg.length); // エラー! Tがlengthを持っているとは限らない - ~~~~~~ -``` -```js-readonly:without_constraints.js -``` - -すべての型が `length` を持っているわけではない(例: `number`型にはない)ため、TypeScriptはエラーを出します。 -これを解決するために、`extends` キーワードを使って **「T は少なくともこの型を継承(適合)していなければならない」** という制約(Constraint)を設けます。 - -```ts:constraints.ts -// lengthプロパティを持つ型を定義 -interface Lengthy { - length: number; -} - -// T は Lengthy インターフェースを満たす型でなければならない -function logLength(arg: T): void { - console.log(`値: ${JSON.stringify(arg)}, 長さ: ${arg.length}`); -} - -// 配列は length を持つのでOK -logLength([1, 2, 3]); - -// 文字列も length を持つのでOK -logLength("Hello"); - -// オブジェクトも length プロパティがあればOK -logLength({ length: 10, value: "something" }); - -// 数値は length を持たないのでエラーになる -// logLength(100); -``` - -```ts-exec:constraints.ts -値: [1,2,3], 長さ: 3 -値: "Hello", 長さ: 5 -値: {"length":10,"value":"something"}, 長さ: 10 -``` -```js-readonly:constraints.js -``` - -このように `extends` を使うことで、ジェネリクスの柔軟性を保ちつつ、関数内で安全に特定のプロパティやメソッドを利用することができます。 - -## この章のまとめ - - * **ジェネリクス (Generics)** は、型を引数のように扱い、コードの再利用性と型安全性を両立させる機能です。 - * **``** のように型変数を宣言して使用します。 - * **関数、インターフェース、クラス** などで利用可能です。 - * **`extends`** キーワードを使用することで、受け入れる型に制約(「最低限このプロパティを持っていること」など)を与えることができます。 - -ジェネリクスを理解すると、ライブラリの型定義ファイル(`.d.ts`)も読みやすくなり、TypeScriptでの開発力が一気に向上します。 - -### 練習問題 1: ペアを作成する関数 - -2つの引数を受け取り、それらを配列(タプル)にして返すジェネリクス関数 `createPair` を作成してください。 -第1引数と第2引数は異なる型でも構いません。 - -**要件:** - - * 型引数を2つ(例: `T`, `U`)使用すること。 - * 戻り値の型は `[T, U]` となること。 - -```ts:practice6_1.ts -// ここに関数を定義してください -function createPair(first: T, second: U): [T, U] { - // 実装 - return [first, second]; -} - -// 実行例 -const pair1 = createPair("score", 100); -console.log(pair1); // ["score", 100] - -const pair2 = createPair(true, "valid"); -console.log(pair2); // [true, "valid"] -``` -```ts-exec:practice6_1.ts -``` -```js-readonly:practice6_1.js -``` - -### 練習問題 2: 制約付きジェネリクス - -`id` プロパティ(型は `number` または `string`)を持つオブジェクトのみを受け取り、その `id` を表示する関数 `showId` を作成してください。 - -**要件:** - - * `extends` を使用して型パラメータに制約をかけること。 - * `id` プロパティを持たないオブジェクトを渡すとコンパイルエラーになること。 - -```ts:practice6_2.ts -// 制約用のインターフェース -interface HasId { - id: number | string; -} - -// ここに関数を定義してください -function showId(item: T): void { - console.log(`ID is: ${item.id}`); -} - -// 実行例 -showId({ id: 1, name: "UserA" }); // OK -showId({ id: "abc-123", active: true }); // OK - -// 以下のコードはエラーになるはずです -// showId({ name: "NoIdUser" }); -``` -```ts-exec:practice6_2.ts -``` -```js-readonly:practice6_2.js -``` diff --git a/public/docs/typescript-7.md b/public/docs/typescript-7.md deleted file mode 100644 index 20be426..0000000 --- a/public/docs/typescript-7.md +++ /dev/null @@ -1,306 +0,0 @@ -# 第7章: クラスとアクセス修飾子 - -JavaScript(ES6以降)に慣れ親しんでいる方であれば、`class`構文自体はすでにご存知かと思います。TypeScriptにおけるクラスは、JavaScriptのクラス機能をベースにしつつ、**型安全性**と**アクセス制御(カプセル化)**を強化するための機能が追加されています。 - -本章では、TypeScript特有のクラスの書き方、特にプロパティの定義、アクセス修飾子、そしてインターフェースとの連携について学びます。 - -## JSのクラス構文の復習: constructor, extends - -まずは、基本的なJavaScriptのクラス構文をTypeScriptのファイルとして書いてみましょう。TypeScriptはJavaScriptのスーパーセット(上位互換)であるため、標準的なJSの書き方もほぼそのまま動作しますが、少しだけ「型」の意識が必要です。 - -```ts:basic-animal.ts -class Animal { - // TypeScriptでは、ここでプロパティ(フィールド)を宣言するのが一般的ですが、 - // JSのようにconstructor内でthis.name = nameするだけだとエラーになることがあります。 - // (詳しくは次のセクションで解説します) - name: string; - - constructor(name: string) { - this.name = name; - } - - move(distanceInMeters: number = 0) { - console.log(`${this.name} moved ${distanceInMeters}m.`); - } -} - -class Snake extends Animal { - constructor(name: string) { - // 派生クラスのコンストラクタでは super() の呼び出しが必須 - super(name); - } - - move(distanceInMeters: number = 5) { - console.log("Slithering..."); - super.move(distanceInMeters); - } -} - -const sam = new Snake("Sammy the Python"); -sam.move(); -``` - -```ts-exec:basic-animal.ts -Slithering... -Sammy the Python moved 5m. -``` -```js-readonly:basic-animal.js -``` - -基本構造はJSと同じですが、引数に型注釈(`: string`, `: number`)が付いている点が異なります。 - -## TypeScriptのクラス: プロパティの型定義 - -JavaScriptでは、コンストラクタ内で `this.x = 10` と書くだけでプロパティを追加できましたが、TypeScriptでは**クラスの直下(ボディ)でプロパティとその型を宣言する**必要があります。 - -これを省略すると、「プロパティ 'x' は型 'ClassName' に存在しません」というエラーになります。 - -```ts:property-definition.ts -class Product { - // プロパティの宣言(必須) - id: number; - name: string; - price: number; - - constructor(id: number, name: string, price: number) { - this.id = id; - this.name = name; - this.price = price; - } - - getDetail(): string { - return `ID:${this.id} ${this.name} (${this.price}円)`; - } -} - -const item = new Product(1, "TypeScript入門書", 2500); -console.log(item.getDetail()); -``` - -```ts-exec:property-definition.ts -ID:1 TypeScript入門書 (2500円) -``` -```js-readonly:property-definition.js -``` - -> **注意:** `strictPropertyInitialization` 設定(tsconfig.json)が有効な場合、プロパティを宣言したもののコンストラクタで初期化していないとエラーになります。初期化を後で行うことが確実な場合は `name!: string;` のように `!` を付けて警告を抑制することもあります。 - -## アクセス修飾子: public, private, protected - -TypeScriptには、クラスのメンバー(プロパティやメソッド)へのアクセスを制御するための3つの修飾子があります。これはJavaやC\#などの言語と同様の概念です。 - -1. **`public` (デフォルト)**: どこからでもアクセス可能。 -2. **`private`**: 定義されたクラスの内部からのみアクセス可能。 -3. **`protected`**: 定義されたクラス、およびそのサブクラス(継承先)からアクセス可能。 - -### 従来の書き方と省略記法(パラメータプロパティ) - -TypeScriptには、コンストラクタの引数にアクセス修飾子を付けることで、**「プロパティ宣言」と「代入」を同時に行う省略記法(パラメータプロパティ)**があります。実務ではこの書き方が非常によく使われます。 - -```ts:access-modifiers.ts -class User { - // 通常の書き方 - public name: string; - private _age: number; // 慣習的にprivateフィールドには_をつけることがあります - - // 省略記法(パラメータプロパティ) - // constructor引数に修飾子をつけることで、自動的にプロパティとして定義・代入される - constructor(name: string, age: number, protected email: string) { - this.name = name; - this._age = age; - // this.email = email; // 自動で行われるため記述不要 - } - - public getProfile(): string { - // privateやprotectedはクラス内部ではアクセス可能 - return `${this.name} (${this._age}) - ${this.email}`; - } -} - -const user = new User("Alice", 30, "alice@example.com"); - -console.log(user.name); // OK (public) -console.log(user.getProfile()); // OK (public) - -// 以下の行はコンパイルエラーになります -// console.log(user._age); // Error: Property '_age' is private... -// console.log(user.email); // Error: Property 'email' is protected... -``` - -```ts-exec:access-modifiers.ts -Alice -Alice (30) - alice@example.com -``` - -```js-readonly:access-modifiers.js -``` - -> **Note:** TypeScriptの `private` はあくまでコンパイル時のチェックです。JavaScriptにトランスパイルされると単なるプロパティになるため、実行時にはアクセスしようと思えばできてしまいます。厳密な実行時プライベートが必要な場合は、JavaScript標準の `#` (例: `#field`) を使用してください。 - -## readonly修飾子: クラスプロパティへの適用 - -`readonly` 修飾子を付けると、そのプロパティは**読み取り専用**になります。 -値の代入は「プロパティ宣言時」または「コンストラクタ内」でのみ許可されます。 - -```ts:readonly-modifier.ts -class Configuration { - // 宣言時に初期化 - readonly version: string = "1.0.0"; - readonly apiKey: string; - - constructor(apiKey: string) { - // コンストラクタ内での代入はOK - this.apiKey = apiKey; - } - - updateConfig() { - // エラー: 読み取り専用プロパティに代入しようとしています - // this.version = "2.0.0"; - } -} - -const config = new Configuration("xyz-123"); -console.log(`Version: ${config.version}, Key: ${config.apiKey}`); - -// エラー: クラスの外からも変更不可 -// config.apiKey = "abc-999"; -``` - -```ts-exec:readonly-modifier.ts -Version: 1.0.0, Key: xyz-123 -``` -```js-readonly:readonly-modifier.js -``` - -## implements: インターフェースによるクラスの形状の強制 - -第3章で学んだインターフェースは、オブジェクトの型定義だけでなく、**クラスが特定の実装を持っていることを保証する(契約を結ぶ)**ためにも使われます。これを `implements` と呼びます。 - -```ts:implements-interface.ts -interface Printable { - print(): void; -} - -interface Loggable { - log(message: string): void; -} - -// 複数のインターフェースを実装可能 -class DocumentFile implements Printable, Loggable { - constructor(private title: string) {} - - // Printableの実装 - print() { - console.log(`Printing document: ${this.title}...`); - } - - // Loggableの実装 - log(message: string) { - console.log(`[LOG]: ${message}`); - } -} - -const doc = new DocumentFile("ProjectPlan.pdf"); -doc.print(); -doc.log("Print job started"); -``` - -```ts-exec:implements-interface.ts -Printing document: ProjectPlan.pdf... -[LOG]: Print job started -``` -```js-readonly:implements-interface.js -``` - -もし `print()` メソッドを実装し忘れると、TypeScriptコンパイラは即座にエラーを出します。これにより、大規模開発での実装漏れを防げます。 - -## 抽象クラス (abstract): 継承専用の基底クラス - -「インスタンス化はさせたくないが、共通の機能を継承させたい」場合や、「メソッドの名前だけ決めておいて、具体的な処理はサブクラスに任せたい」場合に **抽象クラス (`abstract class`)** を使用します。 - - * `abstract` クラス: `new` で直接インスタンス化できません。 - * `abstract` メソッド: 実装(中身)を持ちません。継承先のクラスで必ず実装する必要があります。 - -```ts:abstract-class.ts -abstract class Shape { - constructor(protected color: string) {} - - // 具体的な実装を持つメソッド - describe(): void { - console.log(`This is a ${this.color} shape.`); - } - - // 抽象メソッド(署名のみ定義) - // サブクラスで必ず getArea を実装しなければならない - abstract getArea(): number; -} - -class Circle extends Shape { - constructor(color: string, private radius: number) { - super(color); - } - - // 抽象メソッドの実装 - getArea(): number { - return Math.PI * this.radius ** 2; - } -} - -// const shape = new Shape("red"); // エラー: 抽象クラスはインスタンス化できない - -const circle = new Circle("blue", 5); -circle.describe(); // 親クラスのメソッド -console.log(`Area: ${circle.getArea().toFixed(2)}`); // 実装したメソッド -``` - -```ts-exec:abstract-class.ts -This is a blue shape. -Area: 78.54 -``` -```js-readonly:abstract-class.js -``` - -## この章のまとめ - - * **プロパティ定義:** TypeScriptではクラスボディ内でプロパティの宣言が必要です。 - * **アクセス修飾子:** `public`, `private`, `protected` でカプセル化を制御します。 - * **パラメータプロパティ:** コンストラクタ引数に修飾子をつけることで、宣言と初期化を簡潔に書けます。 - * **readonly:** プロパティを不変(読み取り専用)にします。 - * **implements:** クラスが特定のインターフェースの仕様を満たすことを強制します。 - * **abstract:** インスタンス化できない基底クラスや、実装を強制する抽象メソッドを定義します。 - -### 練習問題 1: 従業員クラスの作成 - -以下の要件を満たす `Employee` クラスを作成し、動作確認コードを書いてください。 - -1. **プロパティ**: - * `name` (string): パブリック - * `id` (number): 読み取り専用 - * `salary` (number): プライベート -2. **コンストラクタ**: 省略記法(パラメータプロパティ)を使ってこれらを初期化してください。 -3. **メソッド**: - * `getSalaryInfo()`: "従業員 [name] の給与は [salary] です" と出力するメソッド(クラス内部からは `salary` にアクセスできることを確認)。 - -```ts:practice7_1.ts -``` -```ts-exec:practice7_1.ts -``` -```js-readonly:practice7_1.js -``` - -### 練習問題 2: 図形クラスの継承 - -以下の要件でコードを書いてください。 - -1. **インターフェース `AreaCalculator`**: `calculateArea(): number` メソッドを持つ。 -2. **クラス `Rectangle`**: `AreaCalculator` を実装(`implements`)する。 - * プロパティ: `width` (number), `height` (number) - * メソッド: `calculateArea` を実装して面積を返す。 -3. `Rectangle` のインスタンスを作成し、面積をコンソールに出力してください。 - -```ts:practice7_2.ts -``` -```ts-exec:practice7_2.ts -``` -```js-readonly:practice7_2.js -``` \ No newline at end of file diff --git a/public/docs/typescript-8.md b/public/docs/typescript-8.md deleted file mode 100644 index 8a39192..0000000 --- a/public/docs/typescript-8.md +++ /dev/null @@ -1,285 +0,0 @@ -# 第8章: 非同期処理とユーティリティ型 - -JavaScriptにおいて `Promise` や `async/await` は日常的に使用しますが、TypeScriptでは「将来どのような値が返ってくるか」を明示する必要があります。 -また、既存の型を再利用して新しい型を作る「ユーティリティ型」を学ぶことで、コードの重複を劇的に減らすことができます。 - -## 非同期処理の型: Promise と async/await - -JavaScriptでは、非同期関数の戻り値は常に `Promise` オブジェクトです。TypeScriptでは、このPromiseが**解決(Resolve)されたときに持つ値の型**をジェネリクスを使って `Promise` の形式で表現します。 - -### 基本的な定義 - - * 戻り値が文字列の場合: `Promise` - * 戻り値が数値の場合: `Promise` - * 戻り値がない(void)場合: `Promise` - -`async` キーワードがついた関数は、自動的に戻り値が `Promise` でラップされます。 - -```ts:async-fetch.ts -type User = { - id: number; - name: string; - email: string; -}; - -// 擬似的なAPIコール関数 -// 戻り値の型として Promise を指定します -const fetchUser = async (userId: number): Promise => { - // 実際はfetchなどを行いますが、ここでは擬似的に遅延させて値を返します - return new Promise((resolve) => { - setTimeout(() => { - resolve({ - id: userId, - name: "Yamada Taro", - email: "taro@example.com", - }); - }, 500); - }); -}; - -const main = async () => { - console.log("Fetching data..."); - - // awaitを使うことで、user変数の型は自動的に User 型(Promiseが解けた状態)になります - const user = await fetchUser(1); - - console.log(`ID: ${user.id}`); - console.log(`Name: ${user.name}`); -}; - -main(); -``` - -```ts-exec:async-fetch.ts -Fetching data... -ID: 1 -Name: Yamada Taro -``` -```js-readonly:async-fetch.js -``` - -### エラーハンドリングと型 - -Promiseが拒否(Reject)される場合のエラー型は、現状のTypeScriptではデフォルトで `any` または `unknown` として扱われます(`try-catch` ブロックの `error` オブジェクトなど)。 - -## ユーティリティ型 (Utility Types) - -TypeScriptには、既存の型定義を変換して新しい型を生成するための便利な型が標準で用意されています。これらを使うと、「一部のプロパティだけ変更したい」「全てオプショナルにしたい」といった場合に、いちいち新しい型を定義し直す必要がなくなります。 - -ここでは、特によく使われる4つのユーティリティ型を紹介します。 - -### ベースとなる型 - -以下の `Product` 型を例に使用します。 - -```ts -interface Product { - id: number; - name: string; - price: number; - description: string; -} -``` - -### 1\. Partial\: 全てをオプショナルにする - -`Partial` は、型 `T` のすべてのプロパティを「必須」から「任意(Optional / `?`付き)」に変更します。データの更新処理(パッチ)などで、一部のフィールドだけ送信したい場合に便利です。 - -```ts:utility-partial.ts -interface Product { - id: number; - name: string; - price: number; - description: string; -} - -// プロパティの一部だけを更新する関数 -// updateDataは { name?: string; price?: number; ... } のようになります -function updateProduct(id: number, updateData: Partial) { - console.log(`Updating product ${id} with:`, updateData); -} - -// nameとpriceだけ更新(descriptionやidがなくてもエラーにならない) -updateProduct(100, { - name: "New Product Name", - price: 5000 -}); -``` - -```ts-exec:utility-partial.ts -Updating product 100 with: { name: 'New Product Name', price: 5000 } -``` -```js-readonly:utility-partial.js -``` - -### 2\. Readonly\: 全てを読み取り専用にする - -`Readonly` は、型 `T` のすべてのプロパティを書き換え不可(readonly)にします。関数内でオブジェクトを変更されたくない場合や、ReactのState管理などで役立ちます。 - -```ts:utility-readonly.ts -interface Product { - id: number; - name: string; - price: number; -} - -const originalProduct: Product = { id: 1, name: "Pen", price: 100 }; - -// 変更不可のオブジェクトとして扱う -const frozenProduct: Readonly = originalProduct; - -// 読み取りはOK -console.log(frozenProduct.name); - -// コンパイルエラー: 値の代入はできません -// frozenProduct.price = 200; -``` - -```ts-exec:utility-readonly.ts -Pen -``` -```js-readonly:utility-readonly.js -``` - -### 3\. Pick\: 特定のキーだけ抜き出す - -`Pick` は、型 `T` から `K` で指定したプロパティのみを抽出して新しい型を作ります。 -「ユーザー情報全体から、表示用の名前と画像URLだけ欲しい」といった場合に使います。 - -```ts:utility-pick.ts -interface Product { - id: number; - name: string; - price: number; - description: string; - stock: number; -} - -// 商品一覧表示用に、IDと名前と価格だけが必要な型を作る -type ProductPreview = Pick; - -const item: ProductPreview = { - id: 1, - name: "Laptop", - price: 120000, - // description: "..." // エラー: ProductPreviewにはdescriptionは存在しません -}; - -console.log(item); -``` - -```ts-exec:utility-pick.ts -{ id: 1, name: 'Laptop', price: 120000 } -``` -```js-readonly:utility-pick.js -``` - -### 4\. Omit\: 特定のキーだけ除外する - -`Omit` は `Pick` の逆で、指定したプロパティを除外します。 -「データベースのモデルから、機密情報や内部管理用のIDを除外してクライアントに返したい」といった場合に有用です。 - -```ts:utility-omit.ts -interface Product { - id: number; - name: string; - price: number; - secretCode: string; // 外部に出したくない情報 - internalId: string; // 外部に出したくない情報 -} - -// 外部公開用の型(secretCodeとinternalIdを除外) -type PublicProduct = Omit; - -const publicItem: PublicProduct = { - id: 1, - name: "Mouse", - price: 3000 -}; - -console.log(publicItem); -``` - -```ts-exec:utility-omit.ts -{ id: 1, name: 'Mouse', price: 3000 } -``` -```js-readonly:utility-omit.js -``` - -## 高度な型操作(概要) - -ここでは詳細な文法までは踏み込みませんが、ライブラリの型定義などを読む際に遭遇する高度な概念を紹介します。これらは上記のユーティリティ型の内部実装にも使われています。 - -### Mapped Types (マップ型) - -既存の型のプロパティをループ処理して、新しい型を作る機能です。配列の `.map()` の型バージョンと考えると分かりやすいでしょう。 - -```ts -type Item = { a: string; b: number }; - -// 既存のItemのキー(P)をすべて boolean 型に変換する -type BooleanItem = { - [P in keyof Item]: boolean; -}; -// 結果: { a: boolean; b: boolean; } と等価 -``` - -### Conditional Types (条件付き型) - -型の三項演算子のようなものです。「もし型Tが型Uを継承しているならX型、そうでなければY型」という条件分岐を定義できます。 - -```ts -// Tがstringなら number[] を、それ以外なら T[] を返す型 -type StringArrayOrGeneric = T extends string ? number[] : T[]; - -type A = StringArrayOrGeneric; // number[] になる -type B = StringArrayOrGeneric; // boolean[] になる -``` - -## この章のまとめ - - * **非同期処理**: `async` 関数の戻り値は `Promise` で定義する。 - * **Utility Types**: TypeScriptには型の再利用性を高める便利な型が組み込まれている。 - * `Partial`: 全プロパティを任意にする。 - * `Readonly`: 全プロパティを読み取り専用にする。 - * `Pick`: 必要なプロパティだけ抽出する。 - * `Omit`: 不要なプロパティを除外する。 - * **高度な型**: `Mapped Types` や `Conditional Types` を使うことで、動的で柔軟な型定義が可能になる。 - -JavaScriptの柔軟性を保ちつつ、堅牢さを加えるためにこれらの機能は非常に重要です。特にユーティリティ型は、冗長なコードを減らす即戦力の機能ですので、ぜひ活用してください。 - -### 練習問題1: 非同期データの取得 - -1. `Post` というインターフェースを定義してください(`id: number`, `title: string`, `body: string`)。 -2. `fetchPost` という `async` 関数を作成してください。この関数は引数に `id` (number) を受け取り、戻り値として `Promise` を返します。 -3. 関数内部では、引数で受け取ったデータをそのまま含むオブジェクトを返してください(`setTimeout`などは不要です)。 -4. 作成した関数を実行し、結果をコンソールに表示してください。 - -```ts:practice8_1.ts -``` -```ts-exec:practice8_1.ts -``` -```js-readonly:practice8_1.js -``` - -### 練習問題2: ユーティリティ型の活用 - -アプリケーションの設定を表す `AppConfig` インターフェースがあります。 -以下の要件を満たす新しい型と変数を定義してください。 - -1. `AppConfig` から `debugMode` を**除外**した型 `ProductionConfig` を定義してください (`Omit`を使用)。 -2. `AppConfig` のすべてのプロパティを**任意(Optional)**にした型 `OptionalConfig` を定義してください (`Partial`を使用)。 -3. `ProductionConfig` 型を持つ変数 `prodConfig` を定義し、適切な値を代入してください。 - -```ts:practice8_2.ts -interface AppConfig { - apiUrl: string; - retryCount: number; - timeout: number; - debugMode: boolean; -} -``` -```ts-exec:practice8_2.ts -``` -```js-readonly:practice8_2.js -``` diff --git a/public/docs/typescript/0-intro/-intro.md b/public/docs/typescript/0-intro/-intro.md new file mode 100644 index 0000000..1f222ff --- /dev/null +++ b/public/docs/typescript/0-intro/-intro.md @@ -0,0 +1,2 @@ +JavaScriptの経験がある皆さん、TypeScriptの世界へようこそ。 +この章では、TypeScriptがどのような言語であるか、なぜ現代のWeb開発のスタンダードとなっているのかを理解し、実際に開発環境を整えて最初のコードを実行するところまでを学びます。 diff --git a/public/docs/typescript/0-intro/1-0-about.md b/public/docs/typescript/0-intro/1-0-about.md new file mode 100644 index 0000000..0ddde51 --- /dev/null +++ b/public/docs/typescript/0-intro/1-0-about.md @@ -0,0 +1,15 @@ +--- +id: typescript-intro-about +title: TypeScriptとは? +level: 2 +--- + +## TypeScriptとは? + +TypeScriptは、Microsoftによって開発されているオープンソースのプログラミング言語です。一言で言えば、**「型(Type)を持ったJavaScript」**です。 + +重要な特徴は以下の通りです: + + * **JavaScriptのスーパーセット(上位互換):** すべての有効なJavaScriptコードは、有効なTypeScriptコードでもあります。つまり、今日から既存のJS知識をそのまま活かせます。 + * **静的型付け:** JavaScriptは実行時に変数の型が決まる「動的型付け言語」ですが、TypeScriptはコンパイル時(コードを書いている途中やビルド時)に型をチェックする「静的型付け言語」としての性質を持ちます。 + * **コンパイル(トランスパイル):** ブラウザやNode.jsはTypeScriptを直接理解できません。TypeScriptコンパイラ(`tsc`)を使って、標準的なJavaScriptファイルに変換してから実行します。 diff --git a/public/docs/typescript/0-intro/2-0-why.md b/public/docs/typescript/0-intro/2-0-why.md new file mode 100644 index 0000000..c36063d --- /dev/null +++ b/public/docs/typescript/0-intro/2-0-why.md @@ -0,0 +1,16 @@ +--- +id: typescript-intro-why +title: なぜTypeScriptか? +level: 2 +--- + +## なぜTypeScriptか? + +「わざわざ型を書くのは面倒だ」と感じるかもしれません。しかし、中〜大規模な開発においてTypeScriptは以下の強力なメリットを提供します。 + +1. **型安全性(バグの早期発見):** + `undefined` のプロパティを読み取ろうとしたり、数値を期待する関数に文字列を渡したりするミスを、コードを実行する前にエディタ上で警告してくれます。 +2. **強力なエディタサポート:** + VS Codeなどのエディタでは、型情報に基づいた正確なコード補完(IntelliSense)が効きます。APIの仕様をドキュメントで調べなくても、ドット`.`を打つだけで利用可能なメソッドが表示されます。 +3. **リファクタリングの容易さ:** + 変数名や関数名を変更する際、型情報があるおかげで、影響範囲を自動的に特定し、安全に一括置換できます。 diff --git a/public/docs/typescript/0-intro/3-0-setup.md b/public/docs/typescript/0-intro/3-0-setup.md new file mode 100644 index 0000000..58a4df4 --- /dev/null +++ b/public/docs/typescript/0-intro/3-0-setup.md @@ -0,0 +1,9 @@ +--- +id: typescript-intro-setup +title: 環境構築 +level: 2 +--- + +## 環境構築 + +それでは、実際にTypeScriptを動かす環境を作りましょう。 diff --git a/public/docs/typescript/0-intro/3-1-create-install.md b/public/docs/typescript/0-intro/3-1-create-install.md new file mode 100644 index 0000000..33169f1 --- /dev/null +++ b/public/docs/typescript/0-intro/3-1-create-install.md @@ -0,0 +1,30 @@ +--- +id: typescript-intro-create-install +title: プロジェクトの作成とTypeScriptのインストール +level: 3 +--- + +### プロジェクトの作成とTypeScriptのインストール + +今回はローカル環境にTypeScriptをインストールする方法を採用します。適当なディレクトリを作成し、ターミナルで以下のコマンドを実行してください。 + +※あらかじめ [Node.js](https://nodejs.org/) がインストールされていることを前提とします。 + +```bash +# プロジェクトフォルダの作成と移動 +mkdir ts-tutorial +cd ts-tutorial + +# package.jsonの初期化 +npm init -y + +# TypeScriptのインストール(開発用依存関係として) +npm install --save-dev typescript +``` + +インストールが完了したら、バージョンを確認してみましょう。 + +```bash +npx tsc --version +# Output: Version 5.x.x (バージョンは時期によります) +``` diff --git a/public/docs/typescript/0-intro/4-0-first-ts.md b/public/docs/typescript/0-intro/4-0-first-ts.md new file mode 100644 index 0000000..920e75a --- /dev/null +++ b/public/docs/typescript/0-intro/4-0-first-ts.md @@ -0,0 +1,9 @@ +--- +id: typescript-intro-first-ts +title: 最初のTypeScript +level: 2 +--- + +## 最初のTypeScript + +いよいよ最初のTypeScriptコードを書いてみましょう。 diff --git a/public/docs/typescript/0-intro/4-1-code.md b/public/docs/typescript/0-intro/4-1-code.md new file mode 100644 index 0000000..78db6f7 --- /dev/null +++ b/public/docs/typescript/0-intro/4-1-code.md @@ -0,0 +1,18 @@ +--- +id: typescript-intro-code +title: コードの記述 +level: 3 +--- + +### コードの記述 + +エディタで `hello.ts` というファイルを作成し、以下のコードを記述します。 +JavaScriptと似ていますが、変数宣言の後ろに `: string` という「型注釈(Type Annotation)」が付いている点に注目してください。 + +```ts:hello.ts +// 変数messageにstring型(文字列)を指定 +const message: string = "Hello, TypeScript World!"; + +// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説) +console.log(message); +``` diff --git a/public/docs/typescript/0-intro/4-2-compile-run.md b/public/docs/typescript/0-intro/4-2-compile-run.md new file mode 100644 index 0000000..af42ac9 --- /dev/null +++ b/public/docs/typescript/0-intro/4-2-compile-run.md @@ -0,0 +1,35 @@ +--- +id: typescript-intro-compile-run +title: コンパイルと実行 +level: 3 +--- + +### コンパイルと実行 + +このままではNode.jsで実行できないため、JavaScriptにコンパイルします。 + +```bash +npx tsc hello.ts +``` + +エラーが出なければ、同じフォルダに `hello.js` というファイルが生成されています。中身を確認すると、型注釈が取り除かれた普通のJavaScriptになっているはずです。 + +生成されたJSファイルをNode.jsで実行します。 + +```ts-exec:hello.ts +Hello, TypeScript World! +``` + +これがTypeScript開発の基本的なサイクル(記述 → コンパイル → 実行)です。 + +このウェブサイトでは上のようにコードを編集して実行ボタンを押すとコンパイルと実行を行うことができる環境を埋め込んでいます。 + +またコンパイル後のjsファイルの内容も以下のように確認できます。 + +```js-readonly:hello.js +"use strict"; +// 変数messageにstring型(文字列)を指定 +const message = "Hello, TypeScript World!"; +// 数値を渡そうとするとエディタ上でエラーになります(後ほど解説) +console.log(message); +``` diff --git a/public/docs/typescript/0-intro/5-0-tsconfig.md b/public/docs/typescript/0-intro/5-0-tsconfig.md new file mode 100644 index 0000000..4696389 --- /dev/null +++ b/public/docs/typescript/0-intro/5-0-tsconfig.md @@ -0,0 +1,30 @@ +--- +id: typescript-intro-tsconfig +title: 'tsconfig.json: コンパイラの設定' +level: 2 +--- + +## tsconfig.json: コンパイラの設定 + +毎回 `npx tsc hello.ts` のようにファイル名を指定するのは手間ですし、プロジェクト全体の設定も管理しづらくなります。そこで、`tsconfig.json` という設定ファイルを使用します。 + +以下のコマンドで初期設定ファイルを生成します。 + +```bash +npx tsc --init +``` + +生成された `tsconfig.json` には多くの設定項目がありますが、基本として以下の設定が有効(コメントアウトされていない状態)になっているか確認してください。 + +```json +{ + "compilerOptions": { + "target": "es2016", /* コンパイル後のJSのバージョン */ + "module": "commonjs", /* モジュールシステム */ + "strict": true, /* 厳格な型チェックを有効にする(重要) */ + "esModuleInterop": true, /* CommonJSモジュールとの互換性 */ + "forceConsistentCasingInFileNames": true, /* ファイル名の大文字小文字を区別 */ + "skipLibCheck": true /* 定義ファイルのチェックをスキップ */ + } +} +``` diff --git a/public/docs/typescript/0-intro/5-1-compile-config.md b/public/docs/typescript/0-intro/5-1-compile-config.md new file mode 100644 index 0000000..c3578b7 --- /dev/null +++ b/public/docs/typescript/0-intro/5-1-compile-config.md @@ -0,0 +1,15 @@ +--- +id: typescript-intro-compile-config +title: 設定ファイルを使ったコンパイル +level: 3 +--- + +### 設定ファイルを使ったコンパイル + +`tsconfig.json` があるディレクトリでは、ファイル名を指定せずに以下のコマンドだけで、ディレクトリ内のすべてのTypeScriptファイルが設定に基づいてコンパイルされます。 + +```bash +npx tsc +``` + +> **Note:** `strict: true` はTypeScriptの恩恵を最大限に受けるために非常に重要です。このチュートリアルでは常にこの設定が有効であることを前提に進めます。 diff --git a/public/docs/typescript/1-types/-intro.md b/public/docs/typescript/1-types/-intro.md new file mode 100644 index 0000000..b7c8704 --- /dev/null +++ b/public/docs/typescript/1-types/-intro.md @@ -0,0 +1,6 @@ +JavaScriptでの開発経験がある皆様、TypeScriptの世界へようこそ。 +第1章では環境構築を行いましたが、本章からいよいよ具体的なコードを書いていきます。 + +TypeScriptの最大の武器は**「型(Type)」**です。しかし、すべてのコードに手動で型を書く必要はありません。TypeScriptは非常に賢い「型推論」という機能を持っており、JavaScriptを書く感覚のまま、安全性を享受できる場面も多々あります。 + +この章では、基礎となるプリミティブ型、TypeScriptならではの配列やタプルの扱い、そして「何でもあり」な状態をどう制御するかについて学びます。 diff --git a/public/docs/typescript/1-types/1-0-type-annotations.md b/public/docs/typescript/1-types/1-0-type-annotations.md new file mode 100644 index 0000000..0c7d672 --- /dev/null +++ b/public/docs/typescript/1-types/1-0-type-annotations.md @@ -0,0 +1,34 @@ +--- +id: typescript-types-type-annotations +title: 2.1 型注釈の構文 (Type Annotations) +level: 2 +--- + +## 2.1 型注釈の構文 (Type Annotations) + +変数を宣言する際、その変数がどのような種類のデータを扱うかを明示することを「型注釈(Type Annotation)」と呼びます。 +構文は非常にシンプルで、変数名の後ろに `: 型名` を記述します。 + +```ts:annotation.ts +// 文字列型の変数を宣言 +let message: string = "Hello, TypeScript!"; + +// 数値型の定数を宣言 +const userId: number = 1001; + +// コンソールに出力 +console.log(message); +console.log(`User ID: ${userId}`); + +// エラーになる例(コメントアウトを外すとエディタ上で赤線が出ます) +// message = 123; // Error: Type 'number' is not assignable to type 'string'. +``` + +```ts-exec:annotation.ts +Hello, TypeScript! +User ID: 1001 +``` +```js-readonly:annotation.js +``` + +> **ポイント:** JavaScriptでは変数にどんな値でも再代入できましたが、TypeScriptでは宣言された型と異なる値を代入しようとすると、コンパイルエラー(またはエディタ上の警告)が発生します。これがバグを未然に防ぐ第一の砦です。 diff --git a/public/docs/typescript/1-types/2-0-primitive-types.md b/public/docs/typescript/1-types/2-0-primitive-types.md new file mode 100644 index 0000000..64bf0b9 --- /dev/null +++ b/public/docs/typescript/1-types/2-0-primitive-types.md @@ -0,0 +1,35 @@ +--- +id: typescript-types-primitive-types +title: 2.2 主要なプリミティブ型 +level: 2 +--- + +## 2.2 主要なプリミティブ型 + +JavaScriptでおなじみのプリミティブ型は、TypeScriptでもそのまま使用できます。 + + * **string**: 文字列 (`"hello"`, `'world'`, \`template\`) + * **number**: 数値 (整数、浮動小数点数、`NaN`, `Infinity` すべて含む) + * **boolean**: 真偽値 (`true`, `false`) + +注意点として、`Number`や`String`(大文字始まり)はラッパーオブジェクト型を指すため、通常は**小文字**の`number`, `string`を使用してください。 + +```ts:primitives.ts +let isDone: boolean = false; +let decimal: number = 6; +let hex: number = 0xf00d; +let color: string = "blue"; + +// テンプレートリテラルもstring型として扱われます +let summary: string = `Color is ${color} and Hex is ${hex}`; + +console.log("Is Done:", isDone); +console.log(summary); +``` + +```ts-exec:primitives.ts +Is Done: false +Color is blue and Hex is 61453 +``` +```js-readonly:primitives.js +``` diff --git a/public/docs/typescript/1-types/3-0-type-inference.md b/public/docs/typescript/1-types/3-0-type-inference.md new file mode 100644 index 0000000..5531974 --- /dev/null +++ b/public/docs/typescript/1-types/3-0-type-inference.md @@ -0,0 +1,31 @@ +--- +id: typescript-types-type-inference +title: 2.3 型推論 (Type Inference) +level: 2 +--- + +## 2.3 型推論 (Type Inference) + +ここがJavaScript経験者にとって嬉しいポイントです。 +変数の初期化と同時に値を代入する場合、**型注釈を省略してもTypeScriptが自動的に型を判別**してくれます。これを「型推論」と呼びます。 + +```ts:inference.ts +// 型注釈がないが、"TypeScript"という文字列から string型 と推論される +let techName = "TypeScript"; + +// 数値が入っているため、count は number型 と推論される +let count = 42; + +console.log(`Technology: ${techName}, Count: ${count}`); + +// 推論された型と違う値を入れようとするとエラーになる +// count = "Forty-Two"; // Error! +``` + +```ts-exec:inference.ts +Technology: TypeScript, Count: 42 +``` +```js-readonly:inference.js +``` + +> **ベストプラクティス:** 初期値がある場合、わざわざ `: string` などを書く必要はありません。コードが冗長になるのを防ぐため、明示的な型注釈は「初期値がない場合」や「推論される型とは別の型として扱いたい場合」に使用するのが一般的です。 diff --git a/public/docs/typescript/1-types/4-0-special-types.md b/public/docs/typescript/1-types/4-0-special-types.md new file mode 100644 index 0000000..2f053e5 --- /dev/null +++ b/public/docs/typescript/1-types/4-0-special-types.md @@ -0,0 +1,9 @@ +--- +id: typescript-types-special-types +title: '2.4 特殊な型: any, unknown, never' +level: 2 +--- + +## 2.4 特殊な型: any, unknown, never + +TypeScriptには「特定のデータ型」ではない特殊な型が存在します。これらは安全性に大きく関わるため、違いを理解することが重要です。 diff --git a/public/docs/typescript/1-types/4-1-any.md b/public/docs/typescript/1-types/4-1-any.md new file mode 100644 index 0000000..476a03f --- /dev/null +++ b/public/docs/typescript/1-types/4-1-any.md @@ -0,0 +1,9 @@ +--- +id: typescript-types-any +title: 'any: 危険な「何でもあり」' +level: 3 +--- + +### any: 危険な「何でもあり」 + +`any` 型は、型チェックを無効にする型です。JavaScriptと同じ挙動になりますが、TypeScriptを使うメリットが失われるため、**可能な限り使用を避けてください**。 diff --git a/public/docs/typescript/1-types/4-2-unknown.md b/public/docs/typescript/1-types/4-2-unknown.md new file mode 100644 index 0000000..fcda88b --- /dev/null +++ b/public/docs/typescript/1-types/4-2-unknown.md @@ -0,0 +1,9 @@ +--- +id: typescript-types-unknown +title: 'unknown: 安全な「正体不明」' +level: 3 +--- + +### unknown: 安全な「正体不明」 + +「何が入ってくるかわからない」場合(例:外部APIのレスポンスなど)は、`any`の代わりに`unknown`を使います。`unknown`型の変数は、**「型の絞り込み(Type Narrowing)」を行わない限り、プロパティへのアクセスやメソッドの呼び出しができません**。 diff --git a/public/docs/typescript/1-types/4-3-never.md b/public/docs/typescript/1-types/4-3-never.md new file mode 100644 index 0000000..52f99ab --- /dev/null +++ b/public/docs/typescript/1-types/4-3-never.md @@ -0,0 +1,47 @@ +--- +id: typescript-types-never +title: 'never: 決して発生しない' +level: 3 +--- + +### never: 決して発生しない + +`never` は「値を持たない」ことを意味します。常に例外を投げる関数や、無限ループなど「終了しない関数」の戻り値として使われます。 + +```ts:special_types.ts +// --- any の例 --- +let looseVariable: any = 4; +looseVariable = "Maybe a string instead"; +looseVariable = false; // エラーにならない(危険!) +console.log("Any:", looseVariable); + +// --- unknown の例 --- +let uncertainValue: unknown = "I am actually a string"; + +// uncertainValue.toUpperCase(); // エラー: Object is of type 'unknown'. + +// 型チェック(絞り込み)を行うと使用可能になる +if (typeof uncertainValue === "string") { + console.log("Unknown (checked):", uncertainValue.toUpperCase()); +} + +// --- never の例 --- +function throwError(message: string): never { + throw new Error(message); +} + +try { + // この関数は決して正常に戻らない + throwError("Something went wrong"); +} catch (e) { + console.log("Error caught"); +} +``` + +```ts-exec:special_types.ts +Any: false +Unknown (checked): I AM ACTUALLY A STRING +Error caught +``` +```js-readonly:special_types.js +``` diff --git a/public/docs/typescript/1-types/5-0-array-tuple.md b/public/docs/typescript/1-types/5-0-array-tuple.md new file mode 100644 index 0000000..b536361 --- /dev/null +++ b/public/docs/typescript/1-types/5-0-array-tuple.md @@ -0,0 +1,9 @@ +--- +id: typescript-types-array-tuple +title: 2.5 配列とタプル +level: 2 +--- + +## 2.5 配列とタプル + +データの集合を扱う方法を見ていきましょう。 diff --git a/public/docs/typescript/1-types/5-1-array.md b/public/docs/typescript/1-types/5-1-array.md new file mode 100644 index 0000000..9c95c17 --- /dev/null +++ b/public/docs/typescript/1-types/5-1-array.md @@ -0,0 +1,12 @@ +--- +id: typescript-types-array +title: 配列 (Array) +level: 3 +--- + +### 配列 (Array) + +配列の型定義には2通りの書き方があります。 + +1. `型[]` (推奨:シンプル) +2. `Array<型>` (ジェネリクス記法) diff --git a/public/docs/typescript/1-types/5-2-tuple.md b/public/docs/typescript/1-types/5-2-tuple.md new file mode 100644 index 0000000..56075bd --- /dev/null +++ b/public/docs/typescript/1-types/5-2-tuple.md @@ -0,0 +1,40 @@ +--- +id: typescript-types-tuple +title: タプル (Tuple) +level: 3 +--- + +### タプル (Tuple) + +配列に似ていますが、**「要素の数が固定」**で、**「各要素の型が決まっている」**ものをタプルと呼びます。CSVの1行や、座標`(x, y)`などを表現するのに便利です。 + +```ts:arrays_tuples.ts +// --- 配列 --- +// 数値の配列 +let fibonacci: number[] = [1, 1, 2, 3, 5]; + +// 文字列の配列(Array記法) +let frameworkList: Array = ["React", "Vue", "Angular"]; + +// --- タプル --- +// [名前, 年齢, 有効フラグ] の順序と型を守る必要がある +let userTuple: [string, number, boolean]; + +userTuple = ["Alice", 30, true]; +// userTuple = [30, "Alice", true]; // エラー: 型の順序が違う + +console.log("First Framework:", frameworkList[0]); +console.log(`User: ${userTuple[0]}, Age: ${userTuple[1]}`); + +// fibonacci.push("8"); // エラー: number[] に string は追加できない +fibonacci.push(8); // OK +console.log("Next Fib:", fibonacci[fibonacci.length - 1]); +``` + +```ts-exec:arrays_tuples.ts +First Framework: React +User: Alice, Age: 30 +Next Fib: 8 +``` +```js-readonly:arrays_tuples.js +``` diff --git a/public/docs/typescript/1-types/6-0-summary.md b/public/docs/typescript/1-types/6-0-summary.md new file mode 100644 index 0000000..dfcc680 --- /dev/null +++ b/public/docs/typescript/1-types/6-0-summary.md @@ -0,0 +1,15 @@ +--- +id: typescript-types-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * 変数宣言時に `: 型名` で型注釈をつけることができる。 + * 初期値がある場合、TypeScriptは自動的に型を推測する(**型推論**)。 + * プリミティブ型は `string`, `number`, `boolean` を小文字で使う。 + * `any` は型チェックを無効にするため避け、不明な値には `unknown` を使う。 + * **配列**は同じ型の集まり、**タプル**は位置と型が固定された配列である。 + +次回は、より複雑なデータ構造を扱うための「オブジェクト、インターフェース、型エイリアス」について学びます。 diff --git a/public/docs/typescript/1-types/6-1-practice1.md b/public/docs/typescript/1-types/6-1-practice1.md new file mode 100644 index 0000000..c3ca2fb --- /dev/null +++ b/public/docs/typescript/1-types/6-1-practice1.md @@ -0,0 +1,19 @@ +--- +id: typescript-types-practice1 +title: '練習問題 1: タプルと配列の操作' +level: 3 +--- + +### 練習問題 1: タプルと配列の操作 + +1. 「商品名(string)」と「価格(number)」を持つ**タプル**型の変数 `product` を定義し、`["Keyboard", 5000]` を代入してください。 +2. 文字列の**配列** `tags` を定義し、型推論を使って `["IT", "Gadget"]` で初期化してください。 +3. `tags` に新しいタグ `"Sale"` を追加してください。 +4. それぞれの値をコンソールに出力してください。 + +```ts:practice2_1.ts +``` +```ts-exec:practice2_1.ts +``` +```js-readonly:practice2_1.js +``` diff --git a/public/docs/typescript/1-types/6-2-practice2.md b/public/docs/typescript/1-types/6-2-practice2.md new file mode 100644 index 0000000..f6715f8 --- /dev/null +++ b/public/docs/typescript/1-types/6-2-practice2.md @@ -0,0 +1,19 @@ +--- +id: typescript-types-practice2 +title: '練習問題 2: unknown型の安全な利用' +level: 3 +--- + +### 練習問題 2: unknown型の安全な利用 + +1. `unknown` 型の引数 `input` を受け取る関数 `printLength` を作成してください。 +2. 関数内で、`input` が `string` 型である場合のみ、その文字列の長さをコンソールに出力してください(`input.length`)。 +3. `input` が `string` 以外の場合は、「Not a string」と出力してください。 +4. この関数に 文字列 `"TypeScript"` と 数値 `100` を渡して実行してください。 + +```ts:practice2_2.ts +``` +```ts-exec:practice2_2.ts +``` +```js-readonly:practice2_2.js +``` diff --git a/public/docs/typescript/2-objects-interfaces/-intro.md b/public/docs/typescript/2-objects-interfaces/-intro.md new file mode 100644 index 0000000..43da311 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/-intro.md @@ -0,0 +1,3 @@ +JavaScriptでは、オブジェクトはデータを扱うための中心的な存在です。TypeScriptにおいてもそれは変わりませんが、JavaScriptの自由度に「型」という制約を加えることで、開発時の安全性を劇的に高めることができます。 + +この章では、オブジェクトの形状(Shape)を定義するための主要な方法である**型エイリアス(type)**と**インターフェース(interface)**について学びます。 diff --git a/public/docs/typescript/2-objects-interfaces/1-0-object-types.md b/public/docs/typescript/2-objects-interfaces/1-0-object-types.md new file mode 100644 index 0000000..29336bb --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/1-0-object-types.md @@ -0,0 +1,28 @@ +--- +id: typescript-objects-interfaces-object-types +title: 'オブジェクトの型付け: インラインでの定義' +level: 2 +--- + +## オブジェクトの型付け: インラインでの定義 + +最も基本的な方法は、変数宣言時に直接オブジェクトの構造(型)を記述する方法です。これを「インラインの型定義」や「オブジェクトリテラル型」と呼びます。 + +```ts:inline-object.ts +// 変数名の後ろに : { プロパティ名: 型; ... } を記述します +const book: { title: string; price: number; isPublished: boolean } = { + title: "TypeScript入門", + price: 2500, + isPublished: true, +}; + +console.log(`Title: ${book.title}, Price: ${book.price}`); +``` + +```ts-exec:inline-object.ts +Title: TypeScript入門, Price: 2500 +``` +```js-readonly:inline-object.js +``` + +この方法はシンプルですが、同じ構造を持つオブジェクトを複数作成する場合、毎回型定義を書く必要があり、コードが冗長になります。そこで登場するのが「型に名前を付ける」機能です。 diff --git a/public/docs/typescript/2-objects-interfaces/2-0-type-alias.md b/public/docs/typescript/2-objects-interfaces/2-0-type-alias.md new file mode 100644 index 0000000..1e8b5c3 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/2-0-type-alias.md @@ -0,0 +1,47 @@ +--- +id: typescript-objects-interfaces-type-alias +title: '型エイリアス (type): 型に名前を付ける' +level: 2 +--- + +## 型エイリアス (type): 型に名前を付ける + +**型エイリアス(Type Alias)**を使用すると、特定の型定義に名前を付け、それを再利用することができます。JavaScriptの経験がある方にとって、これは「型の変数」を作るようなものだとイメージしてください。 + +キーワードは `type` です。慣習として型名には **PascalCase**(大文字始まり)を使用します。 + +```ts:type-alias.ts +// User型を定義 +type User = { + name: string; + age: number; + email: string; +}; + +// 定義したUser型を使用 +const user1: User = { + name: "Tanaka", + age: 28, + email: "tanaka@example.com", +}; + +const user2: User = { + name: "Suzuki", + age: 34, + email: "suzuki@example.com", +}; + +// 関数の引数としても利用可能 +function greet(user: User): string { + return `Hello, ${user.name}!`; +} + +console.log(greet(user1)); +``` + +```ts-exec:type-alias.ts +Hello, Tanaka! +``` + +```js-readonly:type-alias.js +``` diff --git a/public/docs/typescript/2-objects-interfaces/3-0-interface.md b/public/docs/typescript/2-objects-interfaces/3-0-interface.md new file mode 100644 index 0000000..c1018f4 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/3-0-interface.md @@ -0,0 +1,33 @@ +--- +id: typescript-objects-interfaces-interface +title: 'インターフェース (interface): オブジェクトの「形状」を定義する' +level: 2 +--- + +## インターフェース (interface): オブジェクトの「形状」を定義する + +オブジェクトの構造を定義するもう一つの代表的な方法が **インターフェース(interface)** です。 +JavaやC\#などの言語経験がある方には馴染み深いキーワードですが、TypeScriptのインターフェースは「クラスのための契約」だけでなく、「純粋なオブジェクトの形状定義」としても頻繁に使用されます。 + +```ts:interface-basic.ts +// interfaceキーワードを使用(= は不要) +interface Car { + maker: string; + model: string; + year: number; +} + +const myCar: Car = { + maker: "Toyota", + model: "Prius", + year: 2023, +}; + +console.log(`${myCar.maker} ${myCar.model} (${myCar.year})`); +``` + +```ts-exec:interface-basic.ts +Toyota Prius (2023) +``` +```js-readonly:interface-basic.js +``` diff --git a/public/docs/typescript/2-objects-interfaces/4-0-type-vs-interface.md b/public/docs/typescript/2-objects-interfaces/4-0-type-vs-interface.md new file mode 100644 index 0000000..ac42311 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/4-0-type-vs-interface.md @@ -0,0 +1,20 @@ +--- +id: typescript-objects-interfaces-type-vs-interface +title: 'type vs interface: 使い分けの基本的なガイドライン' +level: 2 +--- + +## type vs interface: 使い分けの基本的なガイドライン + +「`type` と `interface` のどちらを使うべきか?」は、TypeScriptにおける最大の論点の一つです。 +現在のTypeScriptでは機能的な差は非常に少なくなっていますが、基本的な使い分けのガイドラインは以下の通りです。 + +| 特徴 | type (型エイリアス) | interface (インターフェース) | +| :--- | :--- | :--- | +| **主な用途** | プリミティブ、ユニオン型(第5章で解説)、タプル、関数の型など、**あらゆる型**に名前を付ける。 | **オブジェクトの構造**やクラスの実装ルールを定義する。 | +| **拡張性** | 交差型 (`&`) を使って拡張する。 | `extends` キーワードで継承できる。また、同名のinterfaceを定義すると自動でマージされる(Declaration Merging)。 | +| **推奨シーン** | アプリケーション開発全般、複雑な型の組み合わせ。 | ライブラリ開発(拡張性を残すため)、オブジェクト指向的な設計。 | + +**結論としての指針:** +初心者のうちは、**「オブジェクトの定義には `interface`、それ以外(単純な型や複雑な型の合成)には `type`」** というルールで始めるのが無難です。 +あるいは、最近のトレンドとして「一貫して `type` を使う」というチームも増えています。重要なのは**プロジェクト内で統一すること**です。 diff --git a/public/docs/typescript/2-objects-interfaces/5-0-optional-props.md b/public/docs/typescript/2-objects-interfaces/5-0-optional-props.md new file mode 100644 index 0000000..8d491d2 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/5-0-optional-props.md @@ -0,0 +1,39 @@ +--- +id: typescript-objects-interfaces-optional-props +title: オプショナルなプロパティ (?) +level: 2 +--- + +## オプショナルなプロパティ (?) + +オブジェクトによっては、特定のプロパティが存在しない(省略可能である)場合があります。 +プロパティ名の後ろに `?` を付けることで、そのプロパティを **オプショナル(任意)** に設定できます。 + +```ts:optional-properties.ts +interface UserProfile { + username: string; + avatarUrl?: string; // ? があるので、このプロパティはなくてもエラーにならない +} + +const profileA: UserProfile = { + username: "user_a", + avatarUrl: "https://example.com/a.png", +}; + +const profileB: UserProfile = { + username: "user_b", + // avatarUrl は省略可能 +}; + +console.log(profileA); +console.log(profileB); +``` + +```ts-exec:optional-properties.ts +{ username: 'user_a', avatarUrl: 'https://example.com/a.png' } +{ username: 'user_b' } +``` +```js-readonly:optional-properties.js +``` + +この場合、`avatarUrl` の型は実質的に `string | undefined`(文字列 または undefined)として扱われます。 diff --git a/public/docs/typescript/2-objects-interfaces/6-0-readonly.md b/public/docs/typescript/2-objects-interfaces/6-0-readonly.md new file mode 100644 index 0000000..1ece3e3 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/6-0-readonly.md @@ -0,0 +1,38 @@ +--- +id: typescript-objects-interfaces-readonly +title: 読み取り専用プロパティ (readonly) +level: 2 +--- + +## 読み取り専用プロパティ (readonly) + +オブジェクトのプロパティを初期化した後に変更されたくない場合、`readonly` 修飾子を使用します。これは特に、IDや設定値など、不変であるべきデータを扱う際に有用です。 + +```ts:readonly-properties.ts +type Product = { + readonly id: number; // 書き換え不可 + name: string; // 書き換え可能 + price: number; +}; + +const item: Product = { + id: 101, + name: "Laptop", + price: 98000 +}; + +item.price = 95000; // OK: 通常のプロパティは変更可能 + +// 以下の行はコンパイルエラーになります +// item.id = 102; // Error: Cannot assign to 'id' because it is a read-only property. + +console.log(item); +``` + +```ts-exec:readonly-properties.ts +{ id: 101, name: 'Laptop', price: 95000 } +``` +```js-readonly:readonly-properties.js +``` + +注意点として、`readonly` はあくまで TypeScript のコンパイル時のチェックです。実行時の JavaScript コードでは通常のオブジェクトとして振る舞うため、無理やり書き換えるコードが混入すると防げない場合があります(ただし、TSを使っている限りはその前にエラーで気づけます)。 diff --git a/public/docs/typescript/2-objects-interfaces/7-0-summary.md b/public/docs/typescript/2-objects-interfaces/7-0-summary.md new file mode 100644 index 0000000..263bd89 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/7-0-summary.md @@ -0,0 +1,13 @@ +--- +id: typescript-objects-interfaces-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **インライン定義**: `{ key: type }` でその場限りの型定義が可能。 + * **型エイリアス (`type`)**: 型定義に名前を付けて再利用しやすくする。柔軟性が高い。 + * **インターフェース (`interface`)**: オブジェクトの構造を定義することに特化している。 + * **オプショナル (`?`)**: プロパティを必須ではなく任意にする。 + * **読み取り専用 (`readonly`)**: プロパティの再代入を禁止し、不変性を保つ。 diff --git a/public/docs/typescript/2-objects-interfaces/7-1-practice1.md b/public/docs/typescript/2-objects-interfaces/7-1-practice1.md new file mode 100644 index 0000000..bdd44b2 --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/7-1-practice1.md @@ -0,0 +1,22 @@ +--- +id: typescript-objects-interfaces-practice1 +title: '練習問題 1: 商品在庫管理' +level: 3 +--- + +### 練習問題 1: 商品在庫管理 + +以下の条件を満たす `Item` インターフェースを定義し、そのオブジェクトを作成してください。 + +1. `id` は数値で、読み取り専用 (`readonly`) であること。 +2. `name` は文字列であること。 +3. `price` は数値であること。 +4. `description` は文字列だが、省略可能 (`?`) であること。 +5. 作成したオブジェクトの `price` を変更し、コンソールに出力してください。 + +```ts:practice3_1.ts +``` +```ts-exec:practice3_1.ts +``` +```js-readonly:practice3_1.js +``` diff --git a/public/docs/typescript/2-objects-interfaces/7-2-practice2.md b/public/docs/typescript/2-objects-interfaces/7-2-practice2.md new file mode 100644 index 0000000..bc49fbe --- /dev/null +++ b/public/docs/typescript/2-objects-interfaces/7-2-practice2.md @@ -0,0 +1,21 @@ +--- +id: typescript-objects-interfaces-practice2 +title: '練習問題 2: ユーザー情報の統合' +level: 3 +--- + +### 練習問題 2: ユーザー情報の統合 + +以下の2つの型エイリアスを定義してください。 + +1. `Contact`: `email` (string) と `phone` (string) を持つ。 +2. `Employee`: `id` (number), `name` (string), `contact` (`Contact`型) を持つ。 + * つまり、`Employee` の中に `Contact` 型がネスト(入れ子)されている状態です。 +3. この `Employee` 型を使って、あなたの情報を表現する変数を作成してください。 + +```ts:practice3_2.ts +``` +```ts-exec:practice3_2.ts +``` +```js-readonly:practice3_2.js +``` diff --git a/public/docs/typescript/3-function-types/-intro.md b/public/docs/typescript/3-function-types/-intro.md new file mode 100644 index 0000000..a092c4c --- /dev/null +++ b/public/docs/typescript/3-function-types/-intro.md @@ -0,0 +1,3 @@ +JavaScript開発者にとって、関数はロジックの中心的な構成要素です。JavaScriptでは引数の数や型が柔軟(あるいはルーズ)ですが、TypeScriptではここを厳密に管理することで、実行時エラーの大半を防ぐことができます。 + +この章では、TypeScriptにおける関数の型定義の基本から、モダンなJavaScript開発で必須となるアロー関数、そして高度なオーバーロードまでを学習します。 diff --git a/public/docs/typescript/3-function-types/1-0-params-return.md b/public/docs/typescript/3-function-types/1-0-params-return.md new file mode 100644 index 0000000..8b44b7c --- /dev/null +++ b/public/docs/typescript/3-function-types/1-0-params-return.md @@ -0,0 +1,38 @@ +--- +id: typescript-function-types-params-return +title: 引数と戻り値の型 +level: 2 +--- + +## 引数と戻り値の型 + +TypeScriptの関数定義において最も基本的なルールは、「引数」と「戻り値」に型を付けることです。 + + * **引数**: 変数名の後ろに `: 型` を記述します。 + * **戻り値**: 引数リストの閉じ括弧 `)` の後ろに `: 型` を記述します。 + +戻り値の型は型推論(Chapter 2参照)によって省略可能ですが、関数の意図を明確にするために明示的に書くことが推奨されます。戻り値がない場合は `void` を使用します。 + +```ts:basic_math.ts +// 基本的な関数宣言 +function add(a: number, b: number): number { + return a + b; +} + +// 戻り値がない関数 +function logMessage(message: string): void { + console.log(`LOG: ${message}`); +} + +const result = add(10, 5); +logMessage(`Result is ${result}`); + +// エラー例(コメントアウトを外すとエラーになります) +// add(10, "5"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'. +``` + +```ts-exec:basic_math.ts +LOG: Result is 15 +``` +```js-readonly:basic_math.js +``` diff --git a/public/docs/typescript/3-function-types/2-0-optional-default.md b/public/docs/typescript/3-function-types/2-0-optional-default.md new file mode 100644 index 0000000..f2e756d --- /dev/null +++ b/public/docs/typescript/3-function-types/2-0-optional-default.md @@ -0,0 +1,9 @@ +--- +id: typescript-function-types-optional-default +title: オプショナル引数とデフォルト引数 +level: 2 +--- + +## オプショナル引数とデフォルト引数 + +JavaScriptでは引数を省略すると `undefined` になりますが、TypeScriptでは定義された引数は**必須**とみなされます。引数を省略可能にするには、特別な構文が必要です。 diff --git a/public/docs/typescript/3-function-types/2-1-optional.md b/public/docs/typescript/3-function-types/2-1-optional.md new file mode 100644 index 0000000..e31d1d7 --- /dev/null +++ b/public/docs/typescript/3-function-types/2-1-optional.md @@ -0,0 +1,11 @@ +--- +id: typescript-function-types-optional +title: オプショナル引数 (?) +level: 3 +--- + +### オプショナル引数 (`?`) + +引数名の後ろに `?` を付けることで、その引数を省略可能(オプショナル)にできます。省略された場合の値は `undefined` です。 + +> **注意:** オプショナル引数は、必ず必須引数の**後ろ**に配置する必要があります。 diff --git a/public/docs/typescript/3-function-types/2-2-default.md b/public/docs/typescript/3-function-types/2-2-default.md new file mode 100644 index 0000000..717e696 --- /dev/null +++ b/public/docs/typescript/3-function-types/2-2-default.md @@ -0,0 +1,39 @@ +--- +id: typescript-function-types-default +title: デフォルト引数 (=) +level: 3 +--- + +### デフォルト引数 (`=`) + +ES6(JavaScript)と同様に、引数にデフォルト値を指定できます。デフォルト値がある場合、TypeScriptはその引数を「型推論」し、かつ「省略可能」として扱います。 + +```ts:optional_default.ts +// titleは省略可能 +function greet(name: string, title?: string): string { + if (title) { + return `Hello, ${title} ${name}!`; + } + return `Hello, ${name}!`; +} + +// powerのデフォルト値は2 +// 戻り値の型はnumberと推論されるため省略可能 +function exponent(base: number, power: number = 2) { + return base ** power; +} + +console.log(greet("Tanaka")); +console.log(greet("Sato", "Dr.")); +console.log(`2^2 = ${exponent(2)}`); +console.log(`2^3 = ${exponent(2, 3)}`); +``` + +```ts-exec:optional_default.ts +Hello, Tanaka! +Hello, Dr. Sato! +2^2 = 4 +2^3 = 8 +``` +```js-readonly:optional_default.js +``` diff --git a/public/docs/typescript/3-function-types/3-0-arrow-this.md b/public/docs/typescript/3-function-types/3-0-arrow-this.md new file mode 100644 index 0000000..3fc7e2b --- /dev/null +++ b/public/docs/typescript/3-function-types/3-0-arrow-this.md @@ -0,0 +1,7 @@ +--- +id: typescript-function-types-arrow-this +title: アロー関数と this +level: 2 +--- + +## アロー関数と `this` diff --git a/public/docs/typescript/3-function-types/3-1-arrow-type.md b/public/docs/typescript/3-function-types/3-1-arrow-type.md new file mode 100644 index 0000000..e492592 --- /dev/null +++ b/public/docs/typescript/3-function-types/3-1-arrow-type.md @@ -0,0 +1,28 @@ +--- +id: typescript-function-types-arrow-type +title: アロー関数の型定義 +level: 3 +--- + +### アロー関数の型定義 + +アロー関数を変数に代入する場合、引数と戻り値の記述場所は通常の関数と同様です。 + +```ts:arrow_func.ts +const multiply = (x: number, y: number): number => { + return x * y; +}; + +// 1行で書く場合(暗黙のreturn) +const subtract = (x: number, y: number): number => x - y; + +console.log(multiply(4, 5)); +console.log(subtract(10, 3)); +``` + +```ts-exec:arrow_func.ts +20 +7 +``` +```js-readonly:arrow_func.js +``` diff --git a/public/docs/typescript/3-function-types/3-2-this-type.md b/public/docs/typescript/3-function-types/3-2-this-type.md new file mode 100644 index 0000000..7cc3185 --- /dev/null +++ b/public/docs/typescript/3-function-types/3-2-this-type.md @@ -0,0 +1,37 @@ +--- +id: typescript-function-types-this-type +title: this の型指定 +level: 3 +--- + +### `this` の型指定 + +JavaScriptにおいて `this` の挙動は複雑ですが、TypeScriptでは `this` が何を指すかを明示的に型定義できます。 +これを行うには、関数の**最初の引数**として `this` という名前の「偽の引数」を定義します。これはコンパイル後のJavaScriptには出力されません。 + +```ts:this_context.ts +interface User { + name: string; + count: number; +} + +function counter(this: User) { + this.count += 1; + console.log(`${this.name}: ${this.count}`); +} + +const userA: User = { name: "Alice", count: 0 }; + +// callメソッドを使ってthisコンテキストを指定して実行 +counter.call(userA); +counter.call(userA); + +// アロー関数はthisを持たないため、この構文は使いません +``` + +```ts-exec:this_context.ts +Alice: 1 +Alice: 2 +``` +```js-readonly:this_context.js +``` diff --git a/public/docs/typescript/3-function-types/4-0-overload.md b/public/docs/typescript/3-function-types/4-0-overload.md new file mode 100644 index 0000000..1799eba --- /dev/null +++ b/public/docs/typescript/3-function-types/4-0-overload.md @@ -0,0 +1,47 @@ +--- +id: typescript-function-types-overload +title: 関数のオーバーロード +level: 2 +--- + +## 関数のオーバーロード + +JavaScriptでは「引数の型や数によって挙動が変わる関数」をよく書きます。TypeScriptでこれを表現するには**オーバーロード**を使用します。 + +オーバーロードは以下の2つの部分で構成されます: + +1. **オーバーロードシグネチャ**: 関数の呼び出しパターンを定義(複数可)。実装は書きません。 +2. **実装シグネチャ**: 実際の関数の処理。外部からは直接見えません。 + +```ts:overload.ts +// 1. オーバーロードシグネチャ(呼び出し可能なパターン) +function double(value: number): number; +function double(value: string): string; + +// 2. 実装シグネチャ(すべてのパターンを網羅できる型定義にする) +function double(value: number | string): number | string { + if (typeof value === 'number') { + return value * 2; + } else { + return value.repeat(2); + } +} + +const numResult = double(10); // 型は number として推論される +const strResult = double("Hi"); // 型は string として推論される + +console.log(numResult); +console.log(strResult); + +// double(true); // エラー: booleanを受け入れるオーバーロードはありません +``` + +```ts-exec:overload.ts +20 +HiHi +``` +```js-readonly:overload.js +``` + + +> **ポイント:** 実装シグネチャ(`number | string` の部分)は直接呼び出せません。必ず上で定義したシグネチャ(`number` または `string`)に一致する必要があります。 diff --git a/public/docs/typescript/3-function-types/5-0-rest-params.md b/public/docs/typescript/3-function-types/5-0-rest-params.md new file mode 100644 index 0000000..6fb6460 --- /dev/null +++ b/public/docs/typescript/3-function-types/5-0-rest-params.md @@ -0,0 +1,32 @@ +--- +id: typescript-function-types-rest-params +title: 残余引数 (Rest Parameters) +level: 2 +--- + +## 残余引数 (Rest Parameters) + +引数の数が可変である場合(可変長引数)、JavaScriptと同様に `...args` 構文を使用します。 +TypeScriptでは、この `args` は必ず**配列の型**である必要があります。 + +```ts:rest_params.ts +// 数値を好きなだけ受け取り、合計を返す +function sumAll(...numbers: number[]): number { + return numbers.reduce((total, num) => total + num, 0); +} + +// 文字列を結合する +function joinStrings(separator: string, ...words: string[]): string { + return words.join(separator); +} + +console.log(sumAll(1, 2, 3, 4, 5)); +console.log(joinStrings("-", "TypeScript", "is", "fun")); +``` + +```ts-exec:rest_params.ts +15 +TypeScript-is-fun +``` +```js-readonly:rest_params.js +``` diff --git a/public/docs/typescript/3-function-types/6-0-type-alias.md b/public/docs/typescript/3-function-types/6-0-type-alias.md new file mode 100644 index 0000000..69204a0 --- /dev/null +++ b/public/docs/typescript/3-function-types/6-0-type-alias.md @@ -0,0 +1,36 @@ +--- +id: typescript-function-types-type-alias +title: 関数の型エイリアス +level: 2 +--- + +## 関数の型エイリアス + +コールバック関数を引数に取る場合など、関数の型定義が長くなりがちです。 +第3章で学んだ `type`(型エイリアス)を使って、関数のシグネチャそのものに名前を付けることができます。 + +構文: `type 型名 = (引数: 型) => 戻り値の型;` + +```ts:func_alias.ts +// 関数の型定義を作成 +type MathOperation = (x: number, y: number) => number; + +// 作成した型を適用 +const addition: MathOperation = (a, b) => a + b; +const multiplication: MathOperation = (a, b) => a * b; + +// 高階関数での利用例(関数を受け取る関数) +function compute(x: number, y: number, op: MathOperation): number { + return op(x, y); +} + +console.log(compute(10, 2, addition)); +console.log(compute(10, 2, multiplication)); +``` + +```ts-exec:func_alias.ts +12 +20 +``` +```js-readonly:func_alias.js +``` diff --git a/public/docs/typescript/3-function-types/7-0-summary.md b/public/docs/typescript/3-function-types/7-0-summary.md new file mode 100644 index 0000000..d99b861 --- /dev/null +++ b/public/docs/typescript/3-function-types/7-0-summary.md @@ -0,0 +1,13 @@ +--- +id: typescript-function-types-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * 関数定義では、引数と戻り値に型を明記するのが基本です。 + * `?` でオプショナル引数、`=` でデフォルト引数を定義できます。 + * アロー関数や `this` の型付けもサポートされており、コンテキストミスを防げます。 + * **オーバーロード**を使うことで、引数によって戻り値の型が変わる柔軟な関数を定義できます。 + * **型エイリアス**を使うことで、複雑な関数シグネチャを再利用可能なパーツとして定義できます。 diff --git a/public/docs/typescript/3-function-types/7-1-practice1.md b/public/docs/typescript/3-function-types/7-1-practice1.md new file mode 100644 index 0000000..34ebd28 --- /dev/null +++ b/public/docs/typescript/3-function-types/7-1-practice1.md @@ -0,0 +1,21 @@ +--- +id: typescript-function-types-practice1 +title: '練習問題 1: ユーザー検索関数' +level: 3 +--- + +### 練習問題 1: ユーザー検索関数 + +以下の要件を満たす `findUser` 関数をアロー関数として作成してください。 + +1. 引数 `id` (number) と `name` (string) を受け取る。 +2. `name` はオプショナル引数とする。 +3. 戻り値は「検索中: [id] [name]」という文字列(nameがない場合は「検索中: [id] ゲスト」)とする。 +4. 関数の型定義(Type Alias)を `SearchFunc` として先に定義し、それを適用すること。 + +```ts:practice4_1.ts +``` +```ts-exec:practice4_1.ts +``` +```js-readonly:practice4_1.js +``` diff --git a/public/docs/typescript/3-function-types/7-2-practice2.md b/public/docs/typescript/3-function-types/7-2-practice2.md new file mode 100644 index 0000000..aa60188 --- /dev/null +++ b/public/docs/typescript/3-function-types/7-2-practice2.md @@ -0,0 +1,20 @@ +--- +id: typescript-function-types-practice2 +title: '練習問題 2: データ変換のオーバーロード' +level: 3 +--- + +### 練習問題 2: データ変換のオーバーロード + +以下の要件を満たす `convert` 関数を `function` キーワードで作成してください。 + +1. 引数が `number` の場合、それを `string` に変換して返す(例: `100` -\> `"100"`)。 +2. 引数が `string` の場合、それを `number` に変換して返す(例: `"100"` -\> `100`)。 +3. 適切なオーバーロードシグネチャを2つ定義すること。 + +```ts:practice4_2.ts +``` +```ts-exec:practice4_2.ts +``` +```js-readonly:practice4_2.js +``` diff --git a/public/docs/typescript/4-combining-types/-intro.md b/public/docs/typescript/4-combining-types/-intro.md new file mode 100644 index 0000000..db91ce2 --- /dev/null +++ b/public/docs/typescript/4-combining-types/-intro.md @@ -0,0 +1,3 @@ +これまでの章では、`string` や `number`、あるいは特定のオブジェクトの形といった「単一の型」を扱ってきました。しかし、現実のアプリケーション開発(特にJavaScriptの世界)では、「IDは数値かもしれないし、文字列かもしれない」「成功時はデータを返すが、失敗時はエラーメッセージを返す」といった柔軟なデータ構造が頻繁に登場します。 + +この章では、既存の型をパズルのように組み合わせて、より複雑で柔軟な状況を表現する方法を学びます。 diff --git a/public/docs/typescript/4-combining-types/1-0-union.md b/public/docs/typescript/4-combining-types/1-0-union.md new file mode 100644 index 0000000..22631f1 --- /dev/null +++ b/public/docs/typescript/4-combining-types/1-0-union.md @@ -0,0 +1,36 @@ +--- +id: typescript-combining-types-union +title: Union型 (共用体型) +level: 2 +--- + +## Union型 (共用体型) + +Union型(共用体型)は、**「A または B」**という状態を表現します。パイプ記号 `|` を使用して記述します。 + +JavaScriptでは変数の型が動的であるため、1つの変数に異なる型の値が入ることがよくありますが、TypeScriptではUnion型を使ってこれを安全に定義できます。 + +```ts:union-basic.ts +// idは数値、または文字列を許容する +let id: number | string; + +id = 101; // OK +id = "user-a"; // OK +// id = true; // Error: Type 'boolean' is not assignable to type 'string | number'. + +function printId(id: number | string) { + console.log(`Your ID is: ${id}`); +} + +printId(123); +printId("ABC"); +``` + +```ts-exec:union-basic.ts +Your ID is: 123 +Your ID is: ABC +``` +```js-readonly:union-basic.js +``` + +> **注意点:** Union型を使用している変数は、その時点では「どの型か確定していない」ため、**すべての候補に共通するプロパティやメソッド**しか操作できません。特定の型として扱いたい場合は、後述する「型ガード」を使用します。 diff --git a/public/docs/typescript/4-combining-types/2-0-literal.md b/public/docs/typescript/4-combining-types/2-0-literal.md new file mode 100644 index 0000000..bca7426 --- /dev/null +++ b/public/docs/typescript/4-combining-types/2-0-literal.md @@ -0,0 +1,32 @@ +--- +id: typescript-combining-types-literal +title: Literal型 (リテラル型) +level: 2 +--- + +## Literal型 (リテラル型) + +`string` や `number` は「あらゆる文字列」や「あらゆる数値」を受け入れますが、**「特定の値だけ」**を許可したい場合があります。これをLiteral型(リテラル型)と呼びます。 + +通常、Literal型は単独で使うよりも、Union型と組み合わせて**「決まった選択肢のいずれか」**を表現するのによく使われます(Enumの代わりとしてもよく利用されます)。 + +```ts:literal-types.ts +// 文字列リテラル型とUnion型の組み合わせ +type TrafficLight = 'red' | 'yellow' | 'green'; + +let currentLight: TrafficLight = 'red'; + +// currentLight = 'blue'; // Error: Type '"blue"' is not assignable to type 'TrafficLight'. + +// 数値リテラルも可能 +type DiceRoll = 1 | 2 | 3 | 4 | 5 | 6; +let dice: DiceRoll = 3; + +console.log(`Light: ${currentLight}, Dice: ${dice}`); +``` + +```ts-exec:literal-types.ts +Light: red, Dice: 3 +``` +```js-readonly:literal-types.js +``` diff --git a/public/docs/typescript/4-combining-types/3-0-intersection.md b/public/docs/typescript/4-combining-types/3-0-intersection.md new file mode 100644 index 0000000..292843f --- /dev/null +++ b/public/docs/typescript/4-combining-types/3-0-intersection.md @@ -0,0 +1,41 @@ +--- +id: typescript-combining-types-intersection +title: Intersection型 (交差型) +level: 2 +--- + +## Intersection型 (交差型) + +Intersection型(交差型)は、**「A かつ B」**を表します。アンパサンド `&` を使用します。 +これは主にオブジェクトの型定義を合成(マージ)して、**「複数の型のすべてのプロパティを持つ新しい型」**を作る際によく使用されます。 + +```ts:intersection-types.ts +type Person = { + name: string; +}; + +type Employee = { + employeeId: number; + department: string; +}; + +// Person かつ Employee の特徴を持つ型 +type CompanyMember = Person & Employee; + +const member: CompanyMember = { + name: "Suzuki", + employeeId: 5001, + department: "Engineering" + // どれか一つでも欠けるとエラーになります +}; + +console.log(member); +``` + +```ts-exec:intersection-types.ts +{ name: 'Suzuki', employeeId: 5001, department: 'Engineering' } +``` +```js-readonly:intersection-types.js +``` + +> **補足:** プリミティブ型同士で `string & number` のように交差させると、両方を満たす値は存在しないため、型は `never`(ありえない値)になります。Intersection型は主にオブジェクト型の合成に使われます。 diff --git a/public/docs/typescript/4-combining-types/4-0-null-undefined.md b/public/docs/typescript/4-combining-types/4-0-null-undefined.md new file mode 100644 index 0000000..1bb53fa --- /dev/null +++ b/public/docs/typescript/4-combining-types/4-0-null-undefined.md @@ -0,0 +1,38 @@ +--- +id: typescript-combining-types-null-undefined +title: null と undefined +level: 2 +--- + +## null と undefined + +TypeScriptには `null` 型と `undefined` 型が存在します。 +`tsconfig.json` の設定で `strictNullChecks: true`(推奨設定)になっている場合、これらは他の型(stringなど)には代入できません。 + +値が存在しない可能性がある場合は、Union型を使って明示的に `null` や `undefined` を許可します。 + +```ts:nullable.ts +// string または null を許容する +let userName: string | null = "Tanaka"; + +userName = null; // OK + +// オプショナルなプロパティ(?)は 「型 | undefined」 の糖衣構文に近い動きをします +type UserConfig = { + theme: string; + notification?: boolean; // boolean | undefined +}; + +const config: UserConfig = { + theme: "dark" + // notification は省略可能 (undefined) +}; + +console.log(`User: ${userName}, Theme: ${config.theme}`); +``` + +```ts-exec:nullable.ts +User: null, Theme: dark +``` +```js-readonly:nullable.js +``` diff --git a/public/docs/typescript/4-combining-types/5-0-type-guards.md b/public/docs/typescript/4-combining-types/5-0-type-guards.md new file mode 100644 index 0000000..59dc6be --- /dev/null +++ b/public/docs/typescript/4-combining-types/5-0-type-guards.md @@ -0,0 +1,11 @@ +--- +id: typescript-combining-types-type-guards +title: 型ガード (Type Guards) +level: 2 +--- + +## 型ガード (Type Guards) + +Union型 (`string | number`) の変数があるとき、プログラムの中で「今は `string` なのか `number` なのか」を区別して処理を分けたい場合があります。これを**型の絞り込み(Narrowing)**と言います。 + +TypeScriptのコンパイラが「このブロック内ではこの変数はこの型だ」と認識できるようにするチェック処理を**型ガード**と呼びます。 diff --git a/public/docs/typescript/4-combining-types/5-1-typeof.md b/public/docs/typescript/4-combining-types/5-1-typeof.md new file mode 100644 index 0000000..9764810 --- /dev/null +++ b/public/docs/typescript/4-combining-types/5-1-typeof.md @@ -0,0 +1,33 @@ +--- +id: typescript-combining-types-typeof +title: typeof 演算子 +level: 3 +--- + +### typeof 演算子 + +プリミティブ型(string, number, boolean, symbol, undefined)の判定に使います。 + +```ts:type-guard-typeof.ts +function formatPrice(price: number | string) { + // ここでは price は number | string + + if (typeof price === 'string') { + // このブロック内では price は 'string' 型として扱われる + return parseInt(price).toLocaleString(); + } else { + // このブロック内では price は 'number' 型として扱われる + return price.toLocaleString(); + } +} + +console.log(formatPrice(10000)); +console.log(formatPrice("20000")); +``` + +```ts-exec:type-guard-typeof.ts +10,000 +20,000 +``` +```js-readonly:type-guard-typeof.js +``` diff --git a/public/docs/typescript/4-combining-types/5-2-in.md b/public/docs/typescript/4-combining-types/5-2-in.md new file mode 100644 index 0000000..3bddb9d --- /dev/null +++ b/public/docs/typescript/4-combining-types/5-2-in.md @@ -0,0 +1,33 @@ +--- +id: typescript-combining-types-in +title: in 演算子 +level: 3 +--- + +### in 演算子 + +オブジェクトが特定のプロパティを持っているかどうかで型を絞り込みます。 + +```ts:type-guard-in.ts +type Fish = { swim: () => void }; +type Bird = { fly: () => void }; + +function move(animal: Fish | Bird) { + if ('swim' in animal) { + // ここでは Fish 型 + animal.swim(); + } else { + // ここでは Bird 型 + animal.fly(); + } +} + +const fish: Fish = { swim: () => console.log("Swimming...") }; +move(fish); +``` + +```ts-exec:type-guard-in.ts +Swimming... +``` +```js-readonly:type-guard-in.js +``` diff --git a/public/docs/typescript/4-combining-types/5-3-instanceof.md b/public/docs/typescript/4-combining-types/5-3-instanceof.md new file mode 100644 index 0000000..fc8f613 --- /dev/null +++ b/public/docs/typescript/4-combining-types/5-3-instanceof.md @@ -0,0 +1,21 @@ +--- +id: typescript-combining-types-instanceof +title: instanceof 演算子 +level: 3 +--- + +### instanceof 演算子 + +クラスのインスタンスかどうかを判定します(第7章のクラスで詳しく扱いますが、Dateなどの組み込みオブジェクトでも有効です)。 + +```ts:type-guard-instanceof.ts +function logDate(value: string | Date) { + if (value instanceof Date) { + console.log(value.toISOString()); + } else { + console.log(value); + } +} +``` +```js-readonly:type-guard-instanceof.js +``` diff --git a/public/docs/typescript/4-combining-types/6-0-type-assertions.md b/public/docs/typescript/4-combining-types/6-0-type-assertions.md new file mode 100644 index 0000000..15c9acf --- /dev/null +++ b/public/docs/typescript/4-combining-types/6-0-type-assertions.md @@ -0,0 +1,34 @@ +--- +id: typescript-combining-types-type-assertions +title: 型アサーション (Type Assertions) +level: 2 +--- + +## 型アサーション (Type Assertions) + +時に、プログラマがTypeScriptコンパイラよりも型の詳細を知っている場合があります。例えば、外部APIからのレスポンスや、DOM要素の取得などです。 + +`as` キーワードを使うと、コンパイラに対して「この変数はこの型であるとして扱ってくれ」と強制できます。 + +```ts:assertion.ts +// unknown型は何でも入るが、そのままでは操作できない型 +let someValue: unknown = "This is a string"; + +// コンパイラに「これはstringだからlengthを使わせて」と伝える +let strLength: number = (someValue as string).length; + +console.log(strLength); + +// 注意: 全く互換性のない型への変換はエラーになりますが、 +// unknownを経由すると無理やり変換できてしまうため、乱用は避けてください。 +// let wrong = (123 as string); // Error +// let dangerous = (123 as unknown as string); // OKだが実行時にバグの元 +``` + +```ts-exec:assertion.ts +16 +``` +```js-readonly:assertion.js +``` + +> **注意:** 型アサーションはあくまで「コンパイル時の型チェックを黙らせる」機能であり、実行時の型変換を行うわけではありません。実行時に値が想定と違う場合、クラッシュの原因になります。可能な限り、型ガードを使って安全に絞り込むことを推奨します。 diff --git a/public/docs/typescript/4-combining-types/7-0-summary.md b/public/docs/typescript/4-combining-types/7-0-summary.md new file mode 100644 index 0000000..20c7dc1 --- /dev/null +++ b/public/docs/typescript/4-combining-types/7-0-summary.md @@ -0,0 +1,14 @@ +--- +id: typescript-combining-types-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **Union型 (`|`)**: 複数の型のうち「いずれか」を表す。 + * **Literal型**: 特定の値のみを許容する型。Union型と組み合わせて列挙型のように使える。 + * **Intersection型 (`&`)**: 複数の型を「合成」して、すべてのプロパティを持つ型を作る。 + * **null / undefined**: `strictNullChecks` 環境下では、Union型を使って明示的に許容する必要がある。 + * **型ガード**: `typeof`, `in`, `instanceof` などを使って、Union型から特定の型へ絞り込む。 + * **型アサーション (`as`)**: 型を強制的に指定するが、安全性のために使用は慎重に行う。 diff --git a/public/docs/typescript/4-combining-types/7-1-practice1.md b/public/docs/typescript/4-combining-types/7-1-practice1.md new file mode 100644 index 0000000..2b15efe --- /dev/null +++ b/public/docs/typescript/4-combining-types/7-1-practice1.md @@ -0,0 +1,30 @@ +--- +id: typescript-combining-types-practice1 +title: '練習問題1: 結果の型定義' +level: 3 +--- + +### 練習問題1: 結果の型定義 + +APIリクエストの結果を表す `Result` 型を定義してください。 + + * 成功時は `success: true` と `data: string` を持ちます。 + * 失敗時は `success: false` と `error: string` を持ちます。 + * `handleResult` 関数内で型ガードを使い、成功ならデータを、失敗ならエラーメッセージをログ出力してください。 + +```ts:practice5_1.ts +// ここに SuccessResult, FailureResult, Result 型を定義してください +// type Result = ... + +function handleResult(result: Result) { + // ここに処理を実装してください +} + +// テスト用 +handleResult({ success: true, data: "Data loaded" }); +handleResult({ success: false, error: "Network error" }); +``` +```ts-exec:practice5_1.ts +``` +```js-readonly:practice5_1.js +``` diff --git a/public/docs/typescript/4-combining-types/7-2-practice2.md b/public/docs/typescript/4-combining-types/7-2-practice2.md new file mode 100644 index 0000000..25dd721 --- /dev/null +++ b/public/docs/typescript/4-combining-types/7-2-practice2.md @@ -0,0 +1,30 @@ +--- +id: typescript-combining-types-practice2 +title: '練習問題2: 図形の面積計算' +level: 3 +--- + +### 練習問題2: 図形の面積計算 + +`Circle` 型と `Square` 型を定義し、それらのUnion型である `Shape` を定義してください。 + + * `Circle` は `kind: 'circle'` と `radius: number` を持ちます。 + * `Square` は `kind: 'square'` と `sideLength: number` を持ちます。 + * `getArea` 関数で、渡された図形に応じて面積を計算して返してください(円周率は `Math.PI` を使用)。 + +```ts:practice5_2.ts +// ここに型を定義 + +function getArea(shape: Shape): number { + // ここに実装 (switch文やif文で kind プロパティによる絞り込みを行う) + return 0; +} + +// テスト用 +console.log(getArea({ kind: 'circle', radius: 10 })); +console.log(getArea({ kind: 'square', sideLength: 5 })); +``` +```ts-exec:practice5_2.ts +``` +```js-readonly:practice5_2.js +``` diff --git a/public/docs/typescript/5-generics/-intro.md b/public/docs/typescript/5-generics/-intro.md new file mode 100644 index 0000000..409eb29 --- /dev/null +++ b/public/docs/typescript/5-generics/-intro.md @@ -0,0 +1 @@ +第6章では、TypeScriptを使いこなす上で非常に強力な機能である**ジェネリクス (Generics)** について学びます。JavaやC\#などの言語経験がある方には馴染み深い概念かもしれませんが、JavaScriptの世界から来た方にとっては少し抽象的に感じるかもしれません。しかし、これを理解することで、**「柔軟性」と「安全性」を両立したコード**が書けるようになります。 diff --git a/public/docs/typescript/5-generics/1-0-generics-intro.md b/public/docs/typescript/5-generics/1-0-generics-intro.md new file mode 100644 index 0000000..10249e9 --- /dev/null +++ b/public/docs/typescript/5-generics/1-0-generics-intro.md @@ -0,0 +1,32 @@ +--- +id: typescript-generics-generics-intro +title: 'Genericsの必要性: 型を引数のように扱う' +level: 2 +--- + +## Genericsの必要性: 型を引数のように扱う + +プログラミングをしていると、「処理内容は同じだが、扱うデータの型だけが違う」という場面によく遭遇します。 + +例えば、「引数をそのまま返す関数」を考えてみましょう。 + +```ts +// 数値を受け取って数値を返す +function returnNumber(arg: number): number { + return arg; +} + +// 文字列を受け取って文字列を返す +function returnString(arg: string): string { + return arg; +} + +// どんな型でも受け取れるが、戻り値の型情報が失われる(any) +function returnAny(arg: any): any { + return arg; +} +``` + +`returnNumber` と `returnString` はロジックが完全に重複しています。一方、`returnAny` は重複を防げますが、TypeScriptの利点である型チェックが無効になってしまいます。 + +ここで登場するのが **ジェネリクス** です。ジェネリクスを使うと、**「型そのもの」を引数のように受け取る**ことができます。 diff --git a/public/docs/typescript/5-generics/2-0-generics-func.md b/public/docs/typescript/5-generics/2-0-generics-func.md new file mode 100644 index 0000000..519ee39 --- /dev/null +++ b/public/docs/typescript/5-generics/2-0-generics-func.md @@ -0,0 +1,37 @@ +--- +id: typescript-generics-generics-func +title: Generics関数 +level: 2 +--- + +## Generics関数 + +ジェネリクスを使った関数の定義を見てみましょう。 +型変数は慣習として `T` (Typeの頭文字) がよく使われます。 + +```ts:identity_func.ts +// は「この関数内で T という名前の型変数を使います」という宣言 +function identity(arg: T): T { + console.log(`引数の型: ${typeof arg}, 値: ${arg}`); + return arg; +} + +// 使用例1: 明示的に型を指定する +const output1 = identity("Hello Generics"); + +// 使用例2: 型推論に任せる (推奨) +// 引数が数値なので、T は number に自動的に推論される +const output2 = identity(100); + +// output1は string型、output2は number型 として扱われるため安全 +// output1.toFixed(2); // エラー: string型にtoFixedは存在しない +``` + +```ts-exec:identity_func.ts +引数の型: string, 値: Hello Generics +引数の型: number, 値: 100 +``` +```js-readonly:identity_func.js +``` + +このように、`identity` 関数は定義時点では型を固定せず、**呼び出す瞬間に型が決まる**という柔軟な性質を持ちます。 diff --git a/public/docs/typescript/5-generics/3-0-generics-interface.md b/public/docs/typescript/5-generics/3-0-generics-interface.md new file mode 100644 index 0000000..f62ccc3 --- /dev/null +++ b/public/docs/typescript/5-generics/3-0-generics-interface.md @@ -0,0 +1,39 @@ +--- +id: typescript-generics-generics-interface +title: Genericsインターフェース +level: 2 +--- + +## Genericsインターフェース + +関数だけでなく、インターフェースもジェネリクスにできます。これにより、再利用性の高いデータ構造を定義できます。 +例えば、「何かを入れる箱 (Box)」のような汎用的な型を作る場合に便利です。 + +```ts:generic_box.ts +// T型の値を持つ value プロパティがあるインターフェース +interface Box { + value: T; +} + +// 文字列を入れる箱 +const stringBox: Box = { + value: "TypeScript" +}; + +// 数値を入れる箱 +const numberBox: Box = { + value: 42 +}; + +console.log(stringBox.value.toUpperCase()); // 文字列のメソッドが使える +console.log(numberBox.value.toFixed(1)); // 数値のメソッドが使える +``` + +```ts-exec:generic_box.ts +TYPESCRIPT +42.0 +``` +```js-readonly:generic_box.js +``` + +JavaScriptでは特に意識せずオブジェクトに様々な型の値を入れていましたが、TypeScriptではこのようにジェネリクスを使うことで、「中身が何かわからない」状態を防ぎつつ、どんな型でも許容する構造を作れます。 diff --git a/public/docs/typescript/5-generics/4-0-generics-class.md b/public/docs/typescript/5-generics/4-0-generics-class.md new file mode 100644 index 0000000..628993c --- /dev/null +++ b/public/docs/typescript/5-generics/4-0-generics-class.md @@ -0,0 +1,54 @@ +--- +id: typescript-generics-generics-class +title: Genericsクラス +level: 2 +--- + +## Genericsクラス + +クラスでも同様にジェネリクスを使用できます。リストやキュー、スタックなどのデータ構造を実装する際によく使われます。 + +ここではシンプルな「スタック(後入れ先出し)」クラスを作ってみましょう。 + +```ts:simple_stack.ts +class SimpleStack { + private items: T[] = []; + + // データを追加する + push(item: T): void { + this.items.push(item); + } + + // データを取り出す + pop(): T | undefined { + return this.items.pop(); + } + + // 現在の中身を表示(デバッグ用) + print(): void { + console.log(this.items); + } +} + +// 数値専用のスタック +const numberStack = new SimpleStack(); +numberStack.push(10); +numberStack.push(20); +// numberStack.push("30"); // エラー: number以外は入れられない +console.log("Pop:", numberStack.pop()); + +// 文字列専用のスタック +const stringStack = new SimpleStack(); +stringStack.push("A"); +stringStack.push("B"); +stringStack.print(); +``` + +```ts-exec:simple_stack.ts +Pop: 20 +[ 'A', 'B' ] +``` +```js-readonly:simple_stack.js +``` + +もしジェネリクスを使わずにこれを実装しようとすると、`NumberStack`クラスと`StringStack`クラスを個別に作るか、`any`を使って安全性を犠牲にするしかありません。ジェネリクスを使えば、1つのクラス定義で安全に様々な型に対応できます。 diff --git a/public/docs/typescript/5-generics/5-0-type-constraint.md b/public/docs/typescript/5-generics/5-0-type-constraint.md new file mode 100644 index 0000000..7e81bfb --- /dev/null +++ b/public/docs/typescript/5-generics/5-0-type-constraint.md @@ -0,0 +1,62 @@ +--- +id: typescript-generics-type-constraint +title: '型制約 (extends): Generics型に制約を設ける' +level: 2 +--- + +## 型制約 (extends): Generics型に制約を設ける + +ジェネリクスは「どんな型でも受け入れられる」のが基本ですが、時には「ある特定の条件を満たす型だけを受け入れたい」という場合があります。 + +例えば、引数の `.length` プロパティにアクセスしたい場合を考えてみましょう。 + +```ts:without_constraints.ts +function logLength(arg: T): void { + console.log(arg.length); // エラー! Tがlengthを持っているとは限らない +} +``` +```ts-exec:without_constraints.ts +without_constraints.ts:2:19 - error TS2339: Property 'length' does not exist on type 'T'. + +2 console.log(arg.length); // エラー! Tがlengthを持っているとは限らない + ~~~~~~ +``` +```js-readonly:without_constraints.js +``` + +すべての型が `length` を持っているわけではない(例: `number`型にはない)ため、TypeScriptはエラーを出します。 +これを解決するために、`extends` キーワードを使って **「T は少なくともこの型を継承(適合)していなければならない」** という制約(Constraint)を設けます。 + +```ts:constraints.ts +// lengthプロパティを持つ型を定義 +interface Lengthy { + length: number; +} + +// T は Lengthy インターフェースを満たす型でなければならない +function logLength(arg: T): void { + console.log(`値: ${JSON.stringify(arg)}, 長さ: ${arg.length}`); +} + +// 配列は length を持つのでOK +logLength([1, 2, 3]); + +// 文字列も length を持つのでOK +logLength("Hello"); + +// オブジェクトも length プロパティがあればOK +logLength({ length: 10, value: "something" }); + +// 数値は length を持たないのでエラーになる +// logLength(100); +``` + +```ts-exec:constraints.ts +値: [1,2,3], 長さ: 3 +値: "Hello", 長さ: 5 +値: {"length":10,"value":"something"}, 長さ: 10 +``` +```js-readonly:constraints.js +``` + +このように `extends` を使うことで、ジェネリクスの柔軟性を保ちつつ、関数内で安全に特定のプロパティやメソッドを利用することができます。 diff --git a/public/docs/typescript/5-generics/6-0-summary.md b/public/docs/typescript/5-generics/6-0-summary.md new file mode 100644 index 0000000..324df7d --- /dev/null +++ b/public/docs/typescript/5-generics/6-0-summary.md @@ -0,0 +1,14 @@ +--- +id: typescript-generics-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **ジェネリクス (Generics)** は、型を引数のように扱い、コードの再利用性と型安全性を両立させる機能です。 + * **``** のように型変数を宣言して使用します。 + * **関数、インターフェース、クラス** などで利用可能です。 + * **`extends`** キーワードを使用することで、受け入れる型に制約(「最低限このプロパティを持っていること」など)を与えることができます。 + +ジェネリクスを理解すると、ライブラリの型定義ファイル(`.d.ts`)も読みやすくなり、TypeScriptでの開発力が一気に向上します。 diff --git a/public/docs/typescript/5-generics/6-1-practice1.md b/public/docs/typescript/5-generics/6-1-practice1.md new file mode 100644 index 0000000..afab8c1 --- /dev/null +++ b/public/docs/typescript/5-generics/6-1-practice1.md @@ -0,0 +1,34 @@ +--- +id: typescript-generics-practice1 +title: '練習問題 1: ペアを作成する関数' +level: 3 +--- + +### 練習問題 1: ペアを作成する関数 + +2つの引数を受け取り、それらを配列(タプル)にして返すジェネリクス関数 `createPair` を作成してください。 +第1引数と第2引数は異なる型でも構いません。 + +**要件:** + + * 型引数を2つ(例: `T`, `U`)使用すること。 + * 戻り値の型は `[T, U]` となること。 + +```ts:practice6_1.ts +// ここに関数を定義してください +function createPair(first: T, second: U): [T, U] { + // 実装 + return [first, second]; +} + +// 実行例 +const pair1 = createPair("score", 100); +console.log(pair1); // ["score", 100] + +const pair2 = createPair(true, "valid"); +console.log(pair2); // [true, "valid"] +``` +```ts-exec:practice6_1.ts +``` +```js-readonly:practice6_1.js +``` diff --git a/public/docs/typescript/5-generics/6-2-practice2.md b/public/docs/typescript/5-generics/6-2-practice2.md new file mode 100644 index 0000000..ebbf920 --- /dev/null +++ b/public/docs/typescript/5-generics/6-2-practice2.md @@ -0,0 +1,37 @@ +--- +id: typescript-generics-practice2 +title: '練習問題 2: 制約付きジェネリクス' +level: 3 +--- + +### 練習問題 2: 制約付きジェネリクス + +`id` プロパティ(型は `number` または `string`)を持つオブジェクトのみを受け取り、その `id` を表示する関数 `showId` を作成してください。 + +**要件:** + + * `extends` を使用して型パラメータに制約をかけること。 + * `id` プロパティを持たないオブジェクトを渡すとコンパイルエラーになること。 + +```ts:practice6_2.ts +// 制約用のインターフェース +interface HasId { + id: number | string; +} + +// ここに関数を定義してください +function showId(item: T): void { + console.log(`ID is: ${item.id}`); +} + +// 実行例 +showId({ id: 1, name: "UserA" }); // OK +showId({ id: "abc-123", active: true }); // OK + +// 以下のコードはエラーになるはずです +// showId({ name: "NoIdUser" }); +``` +```ts-exec:practice6_2.ts +``` +```js-readonly:practice6_2.js +``` diff --git a/public/docs/typescript/6-classes/-intro.md b/public/docs/typescript/6-classes/-intro.md new file mode 100644 index 0000000..f7d3913 --- /dev/null +++ b/public/docs/typescript/6-classes/-intro.md @@ -0,0 +1,3 @@ +JavaScript(ES6以降)に慣れ親しんでいる方であれば、`class`構文自体はすでにご存知かと思います。TypeScriptにおけるクラスは、JavaScriptのクラス機能をベースにしつつ、**型安全性**と**アクセス制御(カプセル化)**を強化するための機能が追加されています。 + +本章では、TypeScript特有のクラスの書き方、特にプロパティの定義、アクセス修飾子、そしてインターフェースとの連携について学びます。 diff --git a/public/docs/typescript/6-classes/1-0-js-class-review.md b/public/docs/typescript/6-classes/1-0-js-class-review.md new file mode 100644 index 0000000..117d603 --- /dev/null +++ b/public/docs/typescript/6-classes/1-0-js-class-review.md @@ -0,0 +1,50 @@ +--- +id: typescript-classes-js-class-review +title: 'JSのクラス構文の復習: constructor, extends' +level: 2 +--- + +## JSのクラス構文の復習: constructor, extends + +まずは、基本的なJavaScriptのクラス構文をTypeScriptのファイルとして書いてみましょう。TypeScriptはJavaScriptのスーパーセット(上位互換)であるため、標準的なJSの書き方もほぼそのまま動作しますが、少しだけ「型」の意識が必要です。 + +```ts:basic-animal.ts +class Animal { + // TypeScriptでは、ここでプロパティ(フィールド)を宣言するのが一般的ですが、 + // JSのようにconstructor内でthis.name = nameするだけだとエラーになることがあります。 + // (詳しくは次のセクションで解説します) + name: string; + + constructor(name: string) { + this.name = name; + } + + move(distanceInMeters: number = 0) { + console.log(`${this.name} moved ${distanceInMeters}m.`); + } +} + +class Snake extends Animal { + constructor(name: string) { + // 派生クラスのコンストラクタでは super() の呼び出しが必須 + super(name); + } + + move(distanceInMeters: number = 5) { + console.log("Slithering..."); + super.move(distanceInMeters); + } +} + +const sam = new Snake("Sammy the Python"); +sam.move(); +``` + +```ts-exec:basic-animal.ts +Slithering... +Sammy the Python moved 5m. +``` +```js-readonly:basic-animal.js +``` + +基本構造はJSと同じですが、引数に型注釈(`: string`, `: number`)が付いている点が異なります。 diff --git a/public/docs/typescript/6-classes/2-0-ts-class.md b/public/docs/typescript/6-classes/2-0-ts-class.md new file mode 100644 index 0000000..d4fda79 --- /dev/null +++ b/public/docs/typescript/6-classes/2-0-ts-class.md @@ -0,0 +1,41 @@ +--- +id: typescript-classes-ts-class +title: 'TypeScriptのクラス: プロパティの型定義' +level: 2 +--- + +## TypeScriptのクラス: プロパティの型定義 + +JavaScriptでは、コンストラクタ内で `this.x = 10` と書くだけでプロパティを追加できましたが、TypeScriptでは**クラスの直下(ボディ)でプロパティとその型を宣言する**必要があります。 + +これを省略すると、「プロパティ 'x' は型 'ClassName' に存在しません」というエラーになります。 + +```ts:property-definition.ts +class Product { + // プロパティの宣言(必須) + id: number; + name: string; + price: number; + + constructor(id: number, name: string, price: number) { + this.id = id; + this.name = name; + this.price = price; + } + + getDetail(): string { + return `ID:${this.id} ${this.name} (${this.price}円)`; + } +} + +const item = new Product(1, "TypeScript入門書", 2500); +console.log(item.getDetail()); +``` + +```ts-exec:property-definition.ts +ID:1 TypeScript入門書 (2500円) +``` +```js-readonly:property-definition.js +``` + +> **注意:** `strictPropertyInitialization` 設定(tsconfig.json)が有効な場合、プロパティを宣言したもののコンストラクタで初期化していないとエラーになります。初期化を後で行うことが確実な場合は `name!: string;` のように `!` を付けて警告を抑制することもあります。 diff --git a/public/docs/typescript/6-classes/3-0-access-modifiers.md b/public/docs/typescript/6-classes/3-0-access-modifiers.md new file mode 100644 index 0000000..6eb20ce --- /dev/null +++ b/public/docs/typescript/6-classes/3-0-access-modifiers.md @@ -0,0 +1,13 @@ +--- +id: typescript-classes-access-modifiers +title: 'アクセス修飾子: public, private, protected' +level: 2 +--- + +## アクセス修飾子: public, private, protected + +TypeScriptには、クラスのメンバー(プロパティやメソッド)へのアクセスを制御するための3つの修飾子があります。これはJavaやC\#などの言語と同様の概念です。 + +1. **`public` (デフォルト)**: どこからでもアクセス可能。 +2. **`private`**: 定義されたクラスの内部からのみアクセス可能。 +3. **`protected`**: 定義されたクラス、およびそのサブクラス(継承先)からアクセス可能。 diff --git a/public/docs/typescript/6-classes/3-1-param-props.md b/public/docs/typescript/6-classes/3-1-param-props.md new file mode 100644 index 0000000..4f5cae6 --- /dev/null +++ b/public/docs/typescript/6-classes/3-1-param-props.md @@ -0,0 +1,49 @@ +--- +id: typescript-classes-param-props +title: 従来の書き方と省略記法(パラメータプロパティ) +level: 3 +--- + +### 従来の書き方と省略記法(パラメータプロパティ) + +TypeScriptには、コンストラクタの引数にアクセス修飾子を付けることで、**「プロパティ宣言」と「代入」を同時に行う省略記法(パラメータプロパティ)**があります。実務ではこの書き方が非常によく使われます。 + +```ts:access-modifiers.ts +class User { + // 通常の書き方 + public name: string; + private _age: number; // 慣習的にprivateフィールドには_をつけることがあります + + // 省略記法(パラメータプロパティ) + // constructor引数に修飾子をつけることで、自動的にプロパティとして定義・代入される + constructor(name: string, age: number, protected email: string) { + this.name = name; + this._age = age; + // this.email = email; // 自動で行われるため記述不要 + } + + public getProfile(): string { + // privateやprotectedはクラス内部ではアクセス可能 + return `${this.name} (${this._age}) - ${this.email}`; + } +} + +const user = new User("Alice", 30, "alice@example.com"); + +console.log(user.name); // OK (public) +console.log(user.getProfile()); // OK (public) + +// 以下の行はコンパイルエラーになります +// console.log(user._age); // Error: Property '_age' is private... +// console.log(user.email); // Error: Property 'email' is protected... +``` + +```ts-exec:access-modifiers.ts +Alice +Alice (30) - alice@example.com +``` + +```js-readonly:access-modifiers.js +``` + +> **Note:** TypeScriptの `private` はあくまでコンパイル時のチェックです。JavaScriptにトランスパイルされると単なるプロパティになるため、実行時にはアクセスしようと思えばできてしまいます。厳密な実行時プライベートが必要な場合は、JavaScript標準の `#` (例: `#field`) を使用してください。 diff --git a/public/docs/typescript/6-classes/4-0-readonly.md b/public/docs/typescript/6-classes/4-0-readonly.md new file mode 100644 index 0000000..573cf77 --- /dev/null +++ b/public/docs/typescript/6-classes/4-0-readonly.md @@ -0,0 +1,40 @@ +--- +id: typescript-classes-readonly +title: 'readonly修飾子: クラスプロパティへの適用' +level: 2 +--- + +## readonly修飾子: クラスプロパティへの適用 + +`readonly` 修飾子を付けると、そのプロパティは**読み取り専用**になります。 +値の代入は「プロパティ宣言時」または「コンストラクタ内」でのみ許可されます。 + +```ts:readonly-modifier.ts +class Configuration { + // 宣言時に初期化 + readonly version: string = "1.0.0"; + readonly apiKey: string; + + constructor(apiKey: string) { + // コンストラクタ内での代入はOK + this.apiKey = apiKey; + } + + updateConfig() { + // エラー: 読み取り専用プロパティに代入しようとしています + // this.version = "2.0.0"; + } +} + +const config = new Configuration("xyz-123"); +console.log(`Version: ${config.version}, Key: ${config.apiKey}`); + +// エラー: クラスの外からも変更不可 +// config.apiKey = "abc-999"; +``` + +```ts-exec:readonly-modifier.ts +Version: 1.0.0, Key: xyz-123 +``` +```js-readonly:readonly-modifier.js +``` diff --git a/public/docs/typescript/6-classes/5-0-implements.md b/public/docs/typescript/6-classes/5-0-implements.md new file mode 100644 index 0000000..6cc5c1f --- /dev/null +++ b/public/docs/typescript/6-classes/5-0-implements.md @@ -0,0 +1,47 @@ +--- +id: typescript-classes-implements +title: 'implements: インターフェースによるクラスの形状の強制' +level: 2 +--- + +## implements: インターフェースによるクラスの形状の強制 + +第3章で学んだインターフェースは、オブジェクトの型定義だけでなく、**クラスが特定の実装を持っていることを保証する(契約を結ぶ)**ためにも使われます。これを `implements` と呼びます。 + +```ts:implements-interface.ts +interface Printable { + print(): void; +} + +interface Loggable { + log(message: string): void; +} + +// 複数のインターフェースを実装可能 +class DocumentFile implements Printable, Loggable { + constructor(private title: string) {} + + // Printableの実装 + print() { + console.log(`Printing document: ${this.title}...`); + } + + // Loggableの実装 + log(message: string) { + console.log(`[LOG]: ${message}`); + } +} + +const doc = new DocumentFile("ProjectPlan.pdf"); +doc.print(); +doc.log("Print job started"); +``` + +```ts-exec:implements-interface.ts +Printing document: ProjectPlan.pdf... +[LOG]: Print job started +``` +```js-readonly:implements-interface.js +``` + +もし `print()` メソッドを実装し忘れると、TypeScriptコンパイラは即座にエラーを出します。これにより、大規模開発での実装漏れを防げます。 diff --git a/public/docs/typescript/6-classes/6-0-abstract.md b/public/docs/typescript/6-classes/6-0-abstract.md new file mode 100644 index 0000000..67204dd --- /dev/null +++ b/public/docs/typescript/6-classes/6-0-abstract.md @@ -0,0 +1,51 @@ +--- +id: typescript-classes-abstract +title: '抽象クラス (abstract): 継承専用の基底クラス' +level: 2 +--- + +## 抽象クラス (abstract): 継承専用の基底クラス + +「インスタンス化はさせたくないが、共通の機能を継承させたい」場合や、「メソッドの名前だけ決めておいて、具体的な処理はサブクラスに任せたい」場合に **抽象クラス (`abstract class`)** を使用します。 + + * `abstract` クラス: `new` で直接インスタンス化できません。 + * `abstract` メソッド: 実装(中身)を持ちません。継承先のクラスで必ず実装する必要があります。 + +```ts:abstract-class.ts +abstract class Shape { + constructor(protected color: string) {} + + // 具体的な実装を持つメソッド + describe(): void { + console.log(`This is a ${this.color} shape.`); + } + + // 抽象メソッド(署名のみ定義) + // サブクラスで必ず getArea を実装しなければならない + abstract getArea(): number; +} + +class Circle extends Shape { + constructor(color: string, private radius: number) { + super(color); + } + + // 抽象メソッドの実装 + getArea(): number { + return Math.PI * this.radius ** 2; + } +} + +// const shape = new Shape("red"); // エラー: 抽象クラスはインスタンス化できない + +const circle = new Circle("blue", 5); +circle.describe(); // 親クラスのメソッド +console.log(`Area: ${circle.getArea().toFixed(2)}`); // 実装したメソッド +``` + +```ts-exec:abstract-class.ts +This is a blue shape. +Area: 78.54 +``` +```js-readonly:abstract-class.js +``` diff --git a/public/docs/typescript/6-classes/7-0-summary.md b/public/docs/typescript/6-classes/7-0-summary.md new file mode 100644 index 0000000..89b60d9 --- /dev/null +++ b/public/docs/typescript/6-classes/7-0-summary.md @@ -0,0 +1,14 @@ +--- +id: typescript-classes-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **プロパティ定義:** TypeScriptではクラスボディ内でプロパティの宣言が必要です。 + * **アクセス修飾子:** `public`, `private`, `protected` でカプセル化を制御します。 + * **パラメータプロパティ:** コンストラクタ引数に修飾子をつけることで、宣言と初期化を簡潔に書けます。 + * **readonly:** プロパティを不変(読み取り専用)にします。 + * **implements:** クラスが特定のインターフェースの仕様を満たすことを強制します。 + * **abstract:** インスタンス化できない基底クラスや、実装を強制する抽象メソッドを定義します。 diff --git a/public/docs/typescript/6-classes/7-1-practice1.md b/public/docs/typescript/6-classes/7-1-practice1.md new file mode 100644 index 0000000..6519b3c --- /dev/null +++ b/public/docs/typescript/6-classes/7-1-practice1.md @@ -0,0 +1,24 @@ +--- +id: typescript-classes-practice1 +title: '練習問題 1: 従業員クラスの作成' +level: 3 +--- + +### 練習問題 1: 従業員クラスの作成 + +以下の要件を満たす `Employee` クラスを作成し、動作確認コードを書いてください。 + +1. **プロパティ**: + * `name` (string): パブリック + * `id` (number): 読み取り専用 + * `salary` (number): プライベート +2. **コンストラクタ**: 省略記法(パラメータプロパティ)を使ってこれらを初期化してください。 +3. **メソッド**: + * `getSalaryInfo()`: "従業員 [name] の給与は [salary] です" と出力するメソッド(クラス内部からは `salary` にアクセスできることを確認)。 + +```ts:practice7_1.ts +``` +```ts-exec:practice7_1.ts +``` +```js-readonly:practice7_1.js +``` diff --git a/public/docs/typescript/6-classes/7-2-practice2.md b/public/docs/typescript/6-classes/7-2-practice2.md new file mode 100644 index 0000000..567a06b --- /dev/null +++ b/public/docs/typescript/6-classes/7-2-practice2.md @@ -0,0 +1,22 @@ +--- +id: typescript-classes-practice2 +title: '練習問題 2: 図形クラスの継承' +level: 3 +--- + +### 練習問題 2: 図形クラスの継承 + +以下の要件でコードを書いてください。 + +1. **インターフェース `AreaCalculator`**: `calculateArea(): number` メソッドを持つ。 +2. **クラス `Rectangle`**: `AreaCalculator` を実装(`implements`)する。 + * プロパティ: `width` (number), `height` (number) + * メソッド: `calculateArea` を実装して面積を返す。 +3. `Rectangle` のインスタンスを作成し、面積をコンソールに出力してください。 + +```ts:practice7_2.ts +``` +```ts-exec:practice7_2.ts +``` +```js-readonly:practice7_2.js +``` diff --git a/public/docs/typescript/7-async-utilities/-intro.md b/public/docs/typescript/7-async-utilities/-intro.md new file mode 100644 index 0000000..473dc44 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/-intro.md @@ -0,0 +1,2 @@ +JavaScriptにおいて `Promise` や `async/await` は日常的に使用しますが、TypeScriptでは「将来どのような値が返ってくるか」を明示する必要があります。 +また、既存の型を再利用して新しい型を作る「ユーティリティ型」を学ぶことで、コードの重複を劇的に減らすことができます。 diff --git a/public/docs/typescript/7-async-utilities/1-0-async-types.md b/public/docs/typescript/7-async-utilities/1-0-async-types.md new file mode 100644 index 0000000..3b9bcb0 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/1-0-async-types.md @@ -0,0 +1,9 @@ +--- +id: typescript-async-utilities-async-types +title: '非同期処理の型: Promise と async/await' +level: 2 +--- + +## 非同期処理の型: Promise と async/await + +JavaScriptでは、非同期関数の戻り値は常に `Promise` オブジェクトです。TypeScriptでは、このPromiseが**解決(Resolve)されたときに持つ値の型**をジェネリクスを使って `Promise` の形式で表現します。 diff --git a/public/docs/typescript/7-async-utilities/1-1-basic-def.md b/public/docs/typescript/7-async-utilities/1-1-basic-def.md new file mode 100644 index 0000000..595ab86 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/1-1-basic-def.md @@ -0,0 +1,56 @@ +--- +id: typescript-async-utilities-basic-def +title: 基本的な定義 +level: 3 +--- + +### 基本的な定義 + + * 戻り値が文字列の場合: `Promise` + * 戻り値が数値の場合: `Promise` + * 戻り値がない(void)場合: `Promise` + +`async` キーワードがついた関数は、自動的に戻り値が `Promise` でラップされます。 + +```ts:async-fetch.ts +type User = { + id: number; + name: string; + email: string; +}; + +// 擬似的なAPIコール関数 +// 戻り値の型として Promise を指定します +const fetchUser = async (userId: number): Promise => { + // 実際はfetchなどを行いますが、ここでは擬似的に遅延させて値を返します + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + id: userId, + name: "Yamada Taro", + email: "taro@example.com", + }); + }, 500); + }); +}; + +const main = async () => { + console.log("Fetching data..."); + + // awaitを使うことで、user変数の型は自動的に User 型(Promiseが解けた状態)になります + const user = await fetchUser(1); + + console.log(`ID: ${user.id}`); + console.log(`Name: ${user.name}`); +}; + +main(); +``` + +```ts-exec:async-fetch.ts +Fetching data... +ID: 1 +Name: Yamada Taro +``` +```js-readonly:async-fetch.js +``` diff --git a/public/docs/typescript/7-async-utilities/1-2-error-handling.md b/public/docs/typescript/7-async-utilities/1-2-error-handling.md new file mode 100644 index 0000000..7d3ce7f --- /dev/null +++ b/public/docs/typescript/7-async-utilities/1-2-error-handling.md @@ -0,0 +1,9 @@ +--- +id: typescript-async-utilities-error-handling +title: エラーハンドリングと型 +level: 3 +--- + +### エラーハンドリングと型 + +Promiseが拒否(Reject)される場合のエラー型は、現状のTypeScriptではデフォルトで `any` または `unknown` として扱われます(`try-catch` ブロックの `error` オブジェクトなど)。 diff --git a/public/docs/typescript/7-async-utilities/2-0-utility-types.md b/public/docs/typescript/7-async-utilities/2-0-utility-types.md new file mode 100644 index 0000000..b29f781 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-0-utility-types.md @@ -0,0 +1,11 @@ +--- +id: typescript-async-utilities-utility-types +title: ユーティリティ型 (Utility Types) +level: 2 +--- + +## ユーティリティ型 (Utility Types) + +TypeScriptには、既存の型定義を変換して新しい型を生成するための便利な型が標準で用意されています。これらを使うと、「一部のプロパティだけ変更したい」「全てオプショナルにしたい」といった場合に、いちいち新しい型を定義し直す必要がなくなります。 + +ここでは、特によく使われる4つのユーティリティ型を紹介します。 diff --git a/public/docs/typescript/7-async-utilities/2-1-base-types.md b/public/docs/typescript/7-async-utilities/2-1-base-types.md new file mode 100644 index 0000000..a95face --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-1-base-types.md @@ -0,0 +1,18 @@ +--- +id: typescript-async-utilities-base-types +title: ベースとなる型 +level: 3 +--- + +### ベースとなる型 + +以下の `Product` 型を例に使用します。 + +```ts +interface Product { + id: number; + name: string; + price: number; + description: string; +} +``` diff --git a/public/docs/typescript/7-async-utilities/2-2-partial.md b/public/docs/typescript/7-async-utilities/2-2-partial.md new file mode 100644 index 0000000..2a4a965 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-2-partial.md @@ -0,0 +1,36 @@ +--- +id: typescript-async-utilities-partial +title: '1. Partial: 全てをオプショナルにする' +level: 3 +--- + +### 1\. Partial\: 全てをオプショナルにする + +`Partial` は、型 `T` のすべてのプロパティを「必須」から「任意(Optional / `?`付き)」に変更します。データの更新処理(パッチ)などで、一部のフィールドだけ送信したい場合に便利です。 + +```ts:utility-partial.ts +interface Product { + id: number; + name: string; + price: number; + description: string; +} + +// プロパティの一部だけを更新する関数 +// updateDataは { name?: string; price?: number; ... } のようになります +function updateProduct(id: number, updateData: Partial) { + console.log(`Updating product ${id} with:`, updateData); +} + +// nameとpriceだけ更新(descriptionやidがなくてもエラーにならない) +updateProduct(100, { + name: "New Product Name", + price: 5000 +}); +``` + +```ts-exec:utility-partial.ts +Updating product 100 with: { name: 'New Product Name', price: 5000 } +``` +```js-readonly:utility-partial.js +``` diff --git a/public/docs/typescript/7-async-utilities/2-3-readonly.md b/public/docs/typescript/7-async-utilities/2-3-readonly.md new file mode 100644 index 0000000..b618f29 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-3-readonly.md @@ -0,0 +1,34 @@ +--- +id: typescript-async-utilities-readonly +title: '2. Readonly: 全てを読み取り専用にする' +level: 3 +--- + +### 2\. Readonly\: 全てを読み取り専用にする + +`Readonly` は、型 `T` のすべてのプロパティを書き換え不可(readonly)にします。関数内でオブジェクトを変更されたくない場合や、ReactのState管理などで役立ちます。 + +```ts:utility-readonly.ts +interface Product { + id: number; + name: string; + price: number; +} + +const originalProduct: Product = { id: 1, name: "Pen", price: 100 }; + +// 変更不可のオブジェクトとして扱う +const frozenProduct: Readonly = originalProduct; + +// 読み取りはOK +console.log(frozenProduct.name); + +// コンパイルエラー: 値の代入はできません +// frozenProduct.price = 200; +``` + +```ts-exec:utility-readonly.ts +Pen +``` +```js-readonly:utility-readonly.js +``` diff --git a/public/docs/typescript/7-async-utilities/2-4-pick.md b/public/docs/typescript/7-async-utilities/2-4-pick.md new file mode 100644 index 0000000..967f2fa --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-4-pick.md @@ -0,0 +1,38 @@ +--- +id: typescript-async-utilities-pick +title: '3. Pick: 特定のキーだけ抜き出す' +level: 3 +--- + +### 3\. Pick\: 特定のキーだけ抜き出す + +`Pick` は、型 `T` から `K` で指定したプロパティのみを抽出して新しい型を作ります。 +「ユーザー情報全体から、表示用の名前と画像URLだけ欲しい」といった場合に使います。 + +```ts:utility-pick.ts +interface Product { + id: number; + name: string; + price: number; + description: string; + stock: number; +} + +// 商品一覧表示用に、IDと名前と価格だけが必要な型を作る +type ProductPreview = Pick; + +const item: ProductPreview = { + id: 1, + name: "Laptop", + price: 120000, + // description: "..." // エラー: ProductPreviewにはdescriptionは存在しません +}; + +console.log(item); +``` + +```ts-exec:utility-pick.ts +{ id: 1, name: 'Laptop', price: 120000 } +``` +```js-readonly:utility-pick.js +``` diff --git a/public/docs/typescript/7-async-utilities/2-5-omit.md b/public/docs/typescript/7-async-utilities/2-5-omit.md new file mode 100644 index 0000000..28b8e2a --- /dev/null +++ b/public/docs/typescript/7-async-utilities/2-5-omit.md @@ -0,0 +1,37 @@ +--- +id: typescript-async-utilities-omit +title: '4. Omit: 特定のキーだけ除外する' +level: 3 +--- + +### 4\. Omit\: 特定のキーだけ除外する + +`Omit` は `Pick` の逆で、指定したプロパティを除外します。 +「データベースのモデルから、機密情報や内部管理用のIDを除外してクライアントに返したい」といった場合に有用です。 + +```ts:utility-omit.ts +interface Product { + id: number; + name: string; + price: number; + secretCode: string; // 外部に出したくない情報 + internalId: string; // 外部に出したくない情報 +} + +// 外部公開用の型(secretCodeとinternalIdを除外) +type PublicProduct = Omit; + +const publicItem: PublicProduct = { + id: 1, + name: "Mouse", + price: 3000 +}; + +console.log(publicItem); +``` + +```ts-exec:utility-omit.ts +{ id: 1, name: 'Mouse', price: 3000 } +``` +```js-readonly:utility-omit.js +``` diff --git a/public/docs/typescript/7-async-utilities/3-0-advanced-types.md b/public/docs/typescript/7-async-utilities/3-0-advanced-types.md new file mode 100644 index 0000000..e099de8 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/3-0-advanced-types.md @@ -0,0 +1,9 @@ +--- +id: typescript-async-utilities-advanced-types +title: 高度な型操作(概要) +level: 2 +--- + +## 高度な型操作(概要) + +ここでは詳細な文法までは踏み込みませんが、ライブラリの型定義などを読む際に遭遇する高度な概念を紹介します。これらは上記のユーティリティ型の内部実装にも使われています。 diff --git a/public/docs/typescript/7-async-utilities/3-1-mapped-types.md b/public/docs/typescript/7-async-utilities/3-1-mapped-types.md new file mode 100644 index 0000000..4be2138 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/3-1-mapped-types.md @@ -0,0 +1,19 @@ +--- +id: typescript-async-utilities-mapped-types +title: Mapped Types (マップ型) +level: 3 +--- + +### Mapped Types (マップ型) + +既存の型のプロパティをループ処理して、新しい型を作る機能です。配列の `.map()` の型バージョンと考えると分かりやすいでしょう。 + +```ts +type Item = { a: string; b: number }; + +// 既存のItemのキー(P)をすべて boolean 型に変換する +type BooleanItem = { + [P in keyof Item]: boolean; +}; +// 結果: { a: boolean; b: boolean; } と等価 +``` diff --git a/public/docs/typescript/7-async-utilities/3-2-conditional-types.md b/public/docs/typescript/7-async-utilities/3-2-conditional-types.md new file mode 100644 index 0000000..79d84ce --- /dev/null +++ b/public/docs/typescript/7-async-utilities/3-2-conditional-types.md @@ -0,0 +1,17 @@ +--- +id: typescript-async-utilities-conditional-types +title: Conditional Types (条件付き型) +level: 3 +--- + +### Conditional Types (条件付き型) + +型の三項演算子のようなものです。「もし型Tが型Uを継承しているならX型、そうでなければY型」という条件分岐を定義できます。 + +```ts +// Tがstringなら number[] を、それ以外なら T[] を返す型 +type StringArrayOrGeneric = T extends string ? number[] : T[]; + +type A = StringArrayOrGeneric; // number[] になる +type B = StringArrayOrGeneric; // boolean[] になる +``` diff --git a/public/docs/typescript/7-async-utilities/4-0-summary.md b/public/docs/typescript/7-async-utilities/4-0-summary.md new file mode 100644 index 0000000..7e484cf --- /dev/null +++ b/public/docs/typescript/7-async-utilities/4-0-summary.md @@ -0,0 +1,17 @@ +--- +id: typescript-async-utilities-summary +title: この章のまとめ +level: 2 +--- + +## この章のまとめ + + * **非同期処理**: `async` 関数の戻り値は `Promise` で定義する。 + * **Utility Types**: TypeScriptには型の再利用性を高める便利な型が組み込まれている。 + * `Partial`: 全プロパティを任意にする。 + * `Readonly`: 全プロパティを読み取り専用にする。 + * `Pick`: 必要なプロパティだけ抽出する。 + * `Omit`: 不要なプロパティを除外する。 + * **高度な型**: `Mapped Types` や `Conditional Types` を使うことで、動的で柔軟な型定義が可能になる。 + +JavaScriptの柔軟性を保ちつつ、堅牢さを加えるためにこれらの機能は非常に重要です。特にユーティリティ型は、冗長なコードを減らす即戦力の機能ですので、ぜひ活用してください。 diff --git a/public/docs/typescript/7-async-utilities/4-1-practice1.md b/public/docs/typescript/7-async-utilities/4-1-practice1.md new file mode 100644 index 0000000..a5a5461 --- /dev/null +++ b/public/docs/typescript/7-async-utilities/4-1-practice1.md @@ -0,0 +1,19 @@ +--- +id: typescript-async-utilities-practice1 +title: '練習問題1: 非同期データの取得' +level: 3 +--- + +### 練習問題1: 非同期データの取得 + +1. `Post` というインターフェースを定義してください(`id: number`, `title: string`, `body: string`)。 +2. `fetchPost` という `async` 関数を作成してください。この関数は引数に `id` (number) を受け取り、戻り値として `Promise` を返します。 +3. 関数内部では、引数で受け取ったデータをそのまま含むオブジェクトを返してください(`setTimeout`などは不要です)。 +4. 作成した関数を実行し、結果をコンソールに表示してください。 + +```ts:practice8_1.ts +``` +```ts-exec:practice8_1.ts +``` +```js-readonly:practice8_1.js +``` diff --git a/public/docs/typescript/7-async-utilities/4-2-practice2.md b/public/docs/typescript/7-async-utilities/4-2-practice2.md new file mode 100644 index 0000000..eecb1da --- /dev/null +++ b/public/docs/typescript/7-async-utilities/4-2-practice2.md @@ -0,0 +1,27 @@ +--- +id: typescript-async-utilities-practice2 +title: '練習問題2: ユーティリティ型の活用' +level: 3 +--- + +### 練習問題2: ユーティリティ型の活用 + +アプリケーションの設定を表す `AppConfig` インターフェースがあります。 +以下の要件を満たす新しい型と変数を定義してください。 + +1. `AppConfig` から `debugMode` を**除外**した型 `ProductionConfig` を定義してください (`Omit`を使用)。 +2. `AppConfig` のすべてのプロパティを**任意(Optional)**にした型 `OptionalConfig` を定義してください (`Partial`を使用)。 +3. `ProductionConfig` 型を持つ変数 `prodConfig` を定義し、適切な値を代入してください。 + +```ts:practice8_2.ts +interface AppConfig { + apiUrl: string; + retryCount: number; + timeout: number; + debugMode: boolean; +} +``` +```ts-exec:practice8_2.ts +``` +```js-readonly:practice8_2.js +``` diff --git a/public/docs/typescript/index.yml b/public/docs/typescript/index.yml new file mode 100644 index 0000000..f5687be --- /dev/null +++ b/public/docs/typescript/index.yml @@ -0,0 +1,27 @@ +name: TypeScript +description: にゃー +pages: +- slug: 0-intro + name: TypeScriptへようこそ + title: TypeScriptへようこそ +- slug: 1-types + name: 基本的な型と型推論 + title: 基本的な型と型推論 +- slug: 2-objects-interfaces + name: オブジェクト、インターフェース、型エイリアス + title: オブジェクト、インターフェース、型エイリアス +- slug: 3-function-types + name: 関数の型定義 + title: 関数の型定義 +- slug: 4-combining-types + name: 型を組み合わせる + title: 型を組み合わせる +- slug: 5-generics + name: ジェネリクス + title: ジェネリクス (Generics) +- slug: 6-classes + name: クラスとアクセス修飾子 + title: クラスとアクセス修飾子 +- slug: 7-async-utilities + name: 非同期処理とユーティリティ型 + title: 非同期処理とユーティリティ型 diff --git a/scripts/generateLanguagesList.ts b/scripts/generateLanguagesList.ts new file mode 100644 index 0000000..7359ef7 --- /dev/null +++ b/scripts/generateLanguagesList.ts @@ -0,0 +1,21 @@ +// Generates public/docs/languages.yml listing all language directories. + +import { readdir, writeFile, stat } from "node:fs/promises"; +import { join } from "node:path"; +import yaml from "js-yaml"; + +const docsDir = join(process.cwd(), "public", "docs"); + +const entries = await readdir(docsDir); +const langIds: string[] = []; +for (const entry of entries) { + const entryPath = join(docsDir, entry); + if ((await stat(entryPath)).isDirectory()) { + langIds.push(entry); + } +} +langIds.sort(); + +const yamlContent = yaml.dump(langIds); +await writeFile(join(docsDir, "languages.yml"), yamlContent, "utf-8"); +console.log(`Generated languages.yml (${langIds.length} languages: ${langIds.join(", ")})`); diff --git a/scripts/generateSectionsList.ts b/scripts/generateSectionsList.ts new file mode 100644 index 0000000..6b294b3 --- /dev/null +++ b/scripts/generateSectionsList.ts @@ -0,0 +1,47 @@ +// Generates public/docs/{lang}/{pageId}/sections.yml for each page directory. +// Each sections.yml lists the .md files in that directory in display order. + +import { readdir, writeFile, stat } from "node:fs/promises"; +import { join } from "node:path"; +import yaml from "js-yaml"; + +const docsDir = join(process.cwd(), "public", "docs"); + +function naturalSortMdFiles(a: string, b: string): number { + // -intro.md always comes first + if (a === "-intro.md") return -1; + if (b === "-intro.md") return 1; + // Sort numerically by leading N1-N2 prefix + const aMatch = a.match(/^(\d+)-(\d+)/); + const bMatch = b.match(/^(\d+)-(\d+)/); + if (aMatch && bMatch) { + const n1Diff = parseInt(aMatch[1]) - parseInt(bMatch[1]); + if (n1Diff !== 0) return n1Diff; + return parseInt(aMatch[2]) - parseInt(bMatch[2]); + } + return a.localeCompare(b); +} + +const langEntries = await readdir(docsDir); +for (const langId of langEntries) { + const langDir = join(docsDir, langId); + if (!(await stat(langDir)).isDirectory()) continue; + + const pageEntries = await readdir(langDir); + for (const pageId of pageEntries) { + // Only process page directories (start with a digit to skip index.yml and other metadata files) + if (!/^\d/.test(pageId)) continue; + const pageDir = join(langDir, pageId); + if (!(await stat(pageDir)).isDirectory()) continue; + + const files = (await readdir(pageDir)) + .filter((f) => f.endsWith(".md")) + .sort(naturalSortMdFiles); + + const yamlContent = yaml.dump(files); + await writeFile(join(pageDir, "sections.yml"), yamlContent, "utf-8"); + console.log( + `Generated ${langId}/${pageId}/sections.yml (${files.length} files)` + ); + } +}