Memo

メモ > 技術 > プログラミング言語: C/C++

■概要
■C 主に以下の書籍をもとに、LinuxでのC言語を勉強したときのメモ ふつうのLinuxプログラミング 第2版 サポートサイト http://i.loveruby.net/stdlinux2/ GitHub - aamine/stdlinux2-source: ふつうのLinuxプログラミング第2版ソースコード https://github.com/aamine/stdlinux2-source Dropbox\技術\C Dropbox\サーバ\Unixコマンド Dropbox\サーバ\Etcetera.txt ソースコードからインストール さらに、以下も参考にC言語の基本を改めて勉強中 C言語入門 http://wisdom.sakura.ne.jp/programming/c/index.html ■C++ C++は、主に以下を参考に進めている途中 C++入門 http://wisdom.sakura.ne.jp/programming/cpp/ ゼロから学ぶ C++ https://rinatz.github.io/cpp-book/ ロベールのC++教室 http://www7b.biglobe.ne.jp/~robe/cpphtml/ 以下も参考になりそう おっさんのためのModernC++入門ガイド(草稿) - dec9ue's diary https://dec9ue.hatenablog.com/entry/2021/03/13/124818 Windows 10で始めるC言語開発(22) Windows 10でC言語開発をしよう! Windows APIで行く【Microsoft C/C++】 (1) | TECH+ https://news.mynavi.jp/techplus/article/c-for-windows-22/ C++で実装するなら(2023年時点での考え) - Qiita https://qiita.com/nonbiri15/items/f0b955fcb431c319c205 ■Windowsプログラミング 以下などを参考に、基本的なことは ポップアップだけの単純な Windows プログラムのビルド - C/C++ による Windows プログラミング入門講座 - C/C++ 入門 https://c.keicode.com/windows/windows-programming-08.php
■Vagrant + Ansible で環境構築
■起動
cd C:\Users\refirio\Vagrant\centos7 vagrant up
■終了
cd C:\Users\refirio\Vagrant\centos7 vagrant halt
■Ansible 以下のPlaybookで構築 refirio/ansible-apache_php7_mariadb https://github.com/refirio/ansible-apache_php7_mariadb
$ cd /var/www/ansible-develop $ ansible-playbook site.yml --connection=local
http://192.168.33.10/ ■C言語のプログラムをコンパイル C:\Users\refirio\Vagrant\centos7\code\c
$ gcc -dumpversion 4.8.5 $ cd /var/www/c/hello $ vi hello.c
#include <stdio.h> int main() { printf("Hello, world.\n"); return 0; }
$ gcc -o hello hello.c $ ./hello Hello, world.
■C++ ※ついでにC++のコンパイルも試す 上記手順で環境構築していれば、「gcc」を「g++」に変えることでコンパイルできるみたい
$ cd /var/www/cpp/hello $ vi hello.cpp
#include <iostream> int main() { std::cout << "Hello, World!" << std::endl; return 0; }
$ g++ -o hello hello.cpp $ ./hello Hello, world.
■C: 基本的なプログラム
■変数
#include <stdio.h> int main() { int num = 123; float pi = 3.1415; char a = 'A'; char *str1 = "C言語の世界へ"; char *str2 = "ようこそ!"; printf("num = %d\n", num); printf("num(5桁のゼロ詰め) = %05d\n", num); printf("pi(小数点第2位まで) = %.2f\n", pi); printf("a = %c\n", a); printf("str1 str2 = %s %s\n", str1, str2); return 0; }
■条件分岐
#include <stdio.h> int main() { int sample = 10; if (sample > 5) { printf("変数sampleの内容は5より大きいです。\n"); } else { printf("変数sampleの内容は5より小さいです。\n"); } printf("%s\n" , sample ? "5より大きい" : "5より小さい"); sample = 2; switch (sample) { case 1: printf("sampleの内容は1です。\n"); break; case 2: printf("sampleの内容は2です。\n"); break; case 3: printf("sampleの内容は3です。\n"); break; default: printf("sampleの内容は1〜3以外です。\n"); } return 0; }
■繰り返し
#include <stdio.h> int main() { int count; for (count = 1; count <= 10; count++) { printf("繰り返しのテスト%d。\n", count); } count = 1; while (count <= 10) { printf("繰り返しのテスト%d。\n", count); count++; } return 0; }
■配列
#include <stdio.h> int main() { int ary1[3]; ary1[0] = 10; ary1[1] = 100; ary1[2] = 1000; printf("%d\n%d\n%d\n", ary1[0] , ary1[1] , ary1[2]); int ary2[2][2]; ary2[0][0] = 10; ary2[0][1] = 100; ary2[1][0] = 1000; ary2[1][1] = 10000; printf("%d\n%d\n" , ary2[0][0] , ary2[0][1]); printf("%d\n%d\n" , ary2[1][0] , ary2[1][1]); return 0; }
■文字列
#include <stdio.h> int main() { // 配列で文字列 char str1[5]; int count; str1[0] = 'T'; str1[1] = 'e'; str1[2] = 's'; str1[3] = 't'; str1[4] = 0; for (count = 0; count <= 4; count++) { printf("%c" , str1[count]); } printf("\n"); printf("%s\n", str1); // まとめて初期化 char str2[6] = {'H', 'e', 'l', 'l', 'o', '!'}; for (count = 0; count <= 5; count++) { printf("%c",str2[count]); } printf("\n"); // 文字列として初期化 char str3[6] = "Hello!"; printf("%s\n",str3); // サイズの指定を省略 char str4[] = "Hello! World."; printf("%s\n",str4); return 0; }
■ポインタ
#include <stdio.h> int main() { int var, *p; var = 123; p = &var; printf("変数に格納されている内容 = %d\n" , var); printf("変数のアドレス = %x\n" , &var); printf("ポインタに格納されているアドレス = %x\n" , p); printf("ポインタに格納されている内容 = %d\n" , *p); return 0; }
実行すると、以下のように表示される(プログラムのファイルは「pointer.c」とする)
$ gcc -o pointer pointer.c $ ./pointer 変数に格納されている内容 = 123 変数のアドレス = 4de5c384 ポインタに格納されているアドレス = 4de5c384 ポインタに格納されている内容 = 123
文字列ポインタについても整理して記載する 文字列の配列 http://wisdom.sakura.ne.jp/programming/c/c19.html 文字列ポインタ http://wisdom.sakura.ne.jp/programming/c/c23.html ■関数
#include <stdio.h> int test1() { printf("test1\n"); return 1; } int test2() { printf("test2\n"); return 1; } int main() { printf("main\n"); test1(); test2(); return 0; }
■構造体
#include <stdio.h> struct { char *name; int age; } taro, hanako; int main() { taro.name = "山田太郎"; taro.age = 16; hanako.name = "山田花子"; hanako.age = 15; printf("名前\t\t年齢\n"); printf("%s\t%d\n", taro.name, taro.age); printf("%s\t%d\n", hanako.name, hanako.age); return 0; }
構造体名に任意のものを指定できるように
#include <stdio.h> struct MEMBER { char *name; int age; }; int main() { struct MEMBER taro, hanako; taro.name = "山田太郎"; taro.age = 16; hanako.name = "山田花子"; hanako.age = 15; printf("名前\t\t年齢\n"); printf("%s\t%d\n", taro.name, taro.age); printf("%s\t%d\n", hanako.name, hanako.age); return 0; }
■プリプロセッサ コンパイル処理に先立って行われる処理(プリプロセッサの実行後にコンパイルが行われる) includeによってファイルを取り込んだり、defineで値を置換したりできる
#include <stdio.h> #define LOOP 5 #define MESSAGE "テスト" int main() { int count; for (count = 1; count <= LOOP; count++) { printf("繰り返しの%s%d。\n", MESSAGE, count); } return 0; }
■メモリの動的確保 プログラムの実行中に、必要に応じてメモリを確保する C言語では malloc() でメモリを動的に確保できる
#include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char *po; po = (char *)malloc(sizeof(char) * 128); if (po == NULL) exit(1); strcpy(po , "Hello, World!\n"); printf("%s" , po); return 0; }
■make main.c
#include <stdio.h> #include <stdlib.h> extern int test1(void); extern int test2(void); int main() { printf("main\n"); test1(); test2(); return 0; }
test1.c
#include <stdio.h> int test1() { printf("test1\n"); return 1; }
test2.c
#include <stdio.h> int test2() { printf("test2\n"); return 1; }
makeを使わずにビルドする場合 毎回適切にビルドを実行する必要がある
$ gcc -c -o main.o main.c $ gcc -c -o test1.o test1.c $ gcc -c -o test2.o test2.c $ gcc -o test main.o test1.o test2.o $ ./test
makeを使ってビルドする場合 Makefileというファイルに処理条件を書いておくと、makeと書くだけで適切にビルドが実行される
$ vi Makefile
#@range_begin(rules) CC = gcc CFLAGS = -g -Wall OBJS = main.o test1.o test2.o test: $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) main.o: main.c $(CC) -c $(CFLAGS) -o $@ $< test1.o: test1.c $(CC) -c $(CFLAGS) -o $@ $< test2.o: test2.c $(CC) -c $(CFLAGS) -o $@ $< #@range_end(rules)
$ make $ ./test
./configure;make;make installにはどんな意味がある? - ITmedia エンタープライズ http://www.itmedia.co.jp/help/tips/linux/l0302.html configure, make, make install とは何か - Qiita https://qiita.com/chihiro/items/f270744d7e09c58a50a5 Makefileの書き方 - $ cat /var/log/shin http://shin.hateblo.jp/entry/2012/05/26/231036 makeを使ってソフトウェアをビルドしてみよう:仕事で使える魔法のLAMP(8) - @IT https://www.atmarkit.co.jp/ait/articles/1106/07/news131.html Make - ゼロから学ぶ C++ https://rinatz.github.io/cpp-book/make-make/ ヘッダファイルを設ける場合、ファイルの分割は以下のようになる ビルド方法やMakefileの内容などは同じ main.c
#include <stdio.h> #include <stdlib.h> #include "test1.h" #include "test2.h" int main(int argc, char **argv) { printf("main\n"); test1(); test2(); exit(0); }
test1.h
#ifndef __TEST1_H_INCLUDED__ #define __TEST1_H_INCLUDED__ int test1(void); #endif
test1.c
#include <stdio.h> #include "test1.h" int test1(void) { printf("test1\n"); return 1; }
test2.h
#ifndef __TEST2_H_INCLUDED__ #define __TEST2_H_INCLUDED__ int test2(void); #endif
test2.c
#include <stdio.h> #include "test2.h" int test2(void) { printf("test1\n"); return 1; }
■C: Linuxでの基本的なプログラム
■コマンドライン引数の表示
$ vi args.c
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; printf("argc=%d\n", argc); for (i = 0; i < argc; i++) { printf("argc[%d]=%s\n", i, argv[i]); } exit(0); }
$ gcc -o args args.c $ ./args argc=1 argc[0]=./args $ ./args x y z argc=4 argc[0]=./args argc[1]=x argc[2]=y argc[3]=z
■標準Cライブラリの場所
$ ls /lib64/libc.so.6 /lib64/libc.so.6 $ ls -l /lib64/libc.so.6 lrwxrwxrwx. 1 root root 12 12月 27 02:19 /lib64/libc.so.6 -> libc-2.17.so
■catコマンドを作る
$ vi cat.c
#include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) { FILE *f; int c; f = fopen(argv[i], "r"); if (!f) { perror(argv[i]); exit(1); } while ((c = fgetc(f)) != EOF) { if (putchar(c) < 0) exit(1); //if (fputc(c, stdout) < 0) exit(1); } fclose(f); } exit(0); }
$ gcc -o cat cat.c $ ./cat $ ./cat cat.c
■headコマンドを作る(処理対象は標準出力のみ)
$ vi head.c
#include <stdio.h> #include <stdlib.h> static void do_head(FILE *f, long nlines); int main(int argc, char *argv[]) { if (argc != 2) { fprintf(stderr, "Usage: %s n\n", argv[0]); exit(1); } do_head(stdin, atol(argv[1])); exit(0); } static void do_head(FILE *f, long nlines) { int c; if (nlines <= 0) return; while ((c = getc(f)) != EOF) { if (putchar(c) < 0) exit(1); if (c == '\n') { nlines--; if (nlines == 0) return; } } }
$ gcc -o head head.c $ ./head $ cat head.c | ./head 5
■headコマンドを作る(引数でファイルを指定)
$ vi head.c
#include <stdio.h> #include <stdlib.h> static void do_head(FILE *f, long nlines); int main(int argc, char *argv[]) { long nlines; if (argc < 2) { fprintf(stderr, "Usage: %s n [file file...]\n", argv[0]); exit(1); } nlines = atol(argv[1]); if (argc == 2) { do_head(stdin, atol(argv[1])); } else { int i; for (i = 2; i < argc; i++) { FILE *f; f = fopen(argv[i], "r"); if (!f) { perror(argv[i]); exit(1); } do_head(f, nlines); fclose(f); } } exit(0); } static void do_head(FILE *f, long nlines) { int c; if (nlines <= 0) return; while ((c = getc(f)) != EOF) { if (putchar(c) < 0) exit(1); if (c == '\n') { nlines--; if (nlines == 0) return; } } }
$ gcc -o head head.c $ ./head $ cat head.c | ./head 5 $ ./head 5 head.c
■headコマンドを作る(オプションを扱う)
$ vi head.c
#include <stdio.h> #include <stdlib.h> /* getopt_long() のプロトタイプ宣言を取り込む */ #define _GNU_SOURCE #include <getopt.h> static void do_head(FILE *f, long nlines); #define DEFAULT_N_LINES 10 /* ロングオプションの仕様を定義する */ static struct option longopts[] = { {"lines", required_argument, NULL, 'n'}, {"help", no_argument, NULL, 'h'}, {0, 0, 0, 0 } }; int main(int argc, char *argv[]) { int opt; long nlines; /* オプションを解析するループ */ while ((opt = getopt_long(argc, argv, "n:", longopts, NULL)) != -1) { switch (opt) { case 'n': nlines = atol(optarg); break; case 'h': fprintf(stdout, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]); exit(0); case '?': fprintf(stderr, "Usage: %s [-n LINES] [FILE ...]\n", argv[0]); exit(1); } } /* * optind は現在処理中のオプションのargvでのインデックスが入るグローバル変数 * この場合、オプションではない最初の引数のインデックスを指している */ if (optind == argc) { do_head(stdin, nlines); } else { int i; for (i = optind; i < argc; i++) { FILE *f; f = fopen(argv[i], "r"); if (!f) { perror(argv[i]); exit(1); } do_head(f, nlines); fclose(f); } } exit(0); } static void do_head(FILE *f, long nlines) { int c; if (nlines <= 0) return; while ((c = getc(f)) != EOF) { if (putchar(c) < 0) exit(1); if (c == '\n') { nlines--; if (nlines == 0) return; } } }
$ gcc -o head head.c $ ./head -n 5 head.c $ cat head.c | ./head -n 5
■grepコマンドを作る
$ vi grep.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <regex.h> static void do_grep(FILE *f, regex_t *pat); int main(int argc, char *argv[]) { regex_t pat; int err; if (argc < 2) { fputs("no pattern\n", stderr); exit(1); } /* 文字列で表現されている正規表現パターンを、専用のデータ型(regex_t)に変換する */ err = regcomp(&pat, argv[1], REG_EXTENDED | REG_NOSUB | REG_NEWLINE); if (err != 0) { char buf[1024]; /* regcomp() のエラーコードをエラーメッセージに変換する */ regerror(err, &pat, buf, sizeof buf); puts(buf); exit(1); } if (argc == 2) { do_grep(stdin, &pat); } else { int i; for (i = 2; i < argc; i++) { FILE *f; f = fopen(argv[i], "r"); if (!f) { perror(argv[i]); exit(1); } do_grep(f, &pat); fclose(f); } } /* regcomp() で確保したメモリを開放する */ regfree(&pat); exit(0); } static void do_grep(FILE *f, regex_t *pat) { char buf[4096]; /* 正規表現に適合するか調べるために、バイト単位ではなく行単位で読み込む */ while (fgets(buf, sizeof buf, f)) { /* 正規表現に適合する行のみを出力する */ if (regexec(pat, buf, 0, NULL, 0) == 0) { fputs(buf, stdout); } } }
$ gcc -o grep grep.c $ ./grep pat grep.c
■lsコマンドを作る
$ vi ls.c
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <dirent.h> static void do_ls(char *path); int main(int argc, char *argv[]) { int i; if (argc < 2) { fprintf(stderr, "%s: no arguments\n", argv[0]); exit(1); } for (i = 1; i < argc; i++) { do_ls(argv[1]); } exit(0); } static void do_ls(char *path) { DIR *d; struct dirent *ent; d = opendir(path); if (!d) { perror(path); exit(1); } while (ent =readdir(d)) { printf("%s\n", ent->d_name); } closedir(d); }
$ gcc -o ls ls.c $ ./ls .
■mkdirコマンドを作る
$ vi mkdir.c
#include <stdio.h> #include <stdlib.h> #include <sys/stat.h> #include <sys/types.h> int main(int argc, char *argv[]) { int i; if (argc < 2) { fprintf(stderr, "%s: no arguments\n", argv[0]); exit(1); } for (i = 1; i < argc; i++) { if (mkdir(argv[i], 0777) < 0) { perror(argv[i]); exit(1); } } exit(0); }
$ gcc -o mkdir mkdir.c $ ./mkdir test
■rmdirコマンドを作る
$ vi mkdir.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int i; if (argc < 2) { fprintf(stderr, "%s: no arguments\n", argv[0]); exit(1); } for (i = 1; i < argc; i++) { if (rmdir(argv[i]) < 0) { perror(argv[i]); exit(1); } } exit(0); }
$ gcc -o rmdir rmdir.c $ ./rmdir test
■unlinkコマンドを作る
$ vi mkdir.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int i; if (argc < 2) { fprintf(stderr, "%s: no arguments\n", argv[0]); exit(1); } for (i = 1; i < argc; i++) { if (unlink(argv[i]) < 0) { perror(argv[i]); exit(1); } } exit(0); }
$ gcc -o unlink unlink.c $ ./unlink test.txt
■mvコマンドを作る
$ vi mv.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { int i; if (argc != 3) { fprintf(stderr, "%s: wrong arguments\n", argv[0]); exit(1); } if (rename(argv[1], argv[2]) < 0) { perror(argv[1]); exit(1); } exit(0); }
$ gcc -o mv mv.c $ ./mv test1.txt test2.txt
■C: プロセスを扱うプログラム
■プログラムの実行
$ echo OK $ /bin/echo OK $ vi spawn.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> int main(int argc, char *argv[]) { pid_t pid; if (argc != 3) { fprintf(stderr, "Usage: %s <command> <arg>\n", argv[0]); exit(1); } /* 自プロセスを複製して、新しいプロセスを作る */ pid = fork(); if (pid < 0) { fprintf(stderr, "fork(2) failed\n"); exit(1); } if (pid == 0) { /* 子プロセス */ /* 自プロセスを新しいプログラムで上書きする */ execl(argv[1], argv[1], argv[2], NULL); /* execl() は成功すると呼び出しから戻らないので、呼び出しから戻ったら失敗 */ perror(argv[1]); exit(99); } else { /* 親プロセス */ int status; /* 複製したプロセスの終了を待つ */ waitpid(pid, &status, 0); printf("child (PID=%d) finished; ", pid); if (WIFEXITED(status)) { /* exit で終了した */ printf("exit, status=%d\n", WEXITSTATUS(status)); /* exit で終了したときの終了コード */ } else if (WIFSIGNALED(status)) { /* シグナルで終了した */ printf("signal, sig=%d\n", WTERMSIG(status)); /* シグナルで終了したときのシグナル番号 */ } else { printf("abnormal exit\n"); } } exit(0); }
$ gcc -o spawn spawn.c $ ./spawn /bin/echo OK OK child (PID=4455) finished; exit, status=0
■カレントディレクトリの取得(推奨されない古い方法) ※楽に取得できるが、PATH_MAX では足りない可能性がある
$ vi getcwd.c
#include <stdio.h> #include <unistd.h> #include <limits.h> int main() { char buf[PATH_MAX]; printf("PATH_MAX=%d\n", PATH_MAX); if (getcwd(buf, sizeof buf)) { printf("buf=%s\n", buf); } else { printf("error\n"); } return 0; }
$ gcc -o getcwd getcwd.c $ ./getcwd PATH_MAX=4096 buf=/var/www/c/process
■カレントディレクトリの取得(推奨される方法)
$ vi getcwd.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #define INIT_BUFSIZE 1024 char* my_getcwd(void); int main() { char *path; path = my_getcwd(); if (path) { printf("path=%s\n", path); } else { printf("error\n"); } return 0; } char* my_getcwd(void) { char *buf, *tmp; size_t size = INIT_BUFSIZE; buf = malloc(size); if (!buf) { return NULL; } for (;;) { errno = 0; if (getcwd(buf, size)) { return buf; } if (errno != ERANGE) { break; } size *= 2; tmp = realloc(buf, size); if (!tmp) { break; } buf = tmp; } return NULL; }
$ gcc -o getcwd getcwd.c $ ./getcwd path=/var/www/c/process
■環境変数を表示する
$ vi env.c
#include <stdio.h> #include <stdlib.h> extern char **environ; int main() { char **p; for (p = environ; *p; p++) { printf("%s\n", *p); } exit(0); }
$ gcc -o env env.c $ ./env XDG_SESSION_ID=3 HOSTNAME=localhost.localdomain TERM=xterm SHELL=/bin/bash HISTSIZE=1000 SSH_CLIENT=10.0.2.2 50919 22 OLDPWD=/var/www/c SSH_TTY=/dev/pts/0 USER=vagrant LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36: MAIL=/var/spool/mail/vagrant PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/vagrant/.local/bin:/home/vagrant/bin PWD=/var/www/c/process LANG=ja_JP.UTF-8 HISTCONTROL=ignoredups SHLVL=1 HOME=/home/vagrant LOGNAME=vagrant SSH_CONNECTION=10.0.2.2 50919 10.0.2.15 22 LESSOPEN=||/usr/bin/lesspipe.sh %s XDG_RUNTIME_DIR=/run/user/1000 _=./env
■C: ネットワークを扱うプログラム
■daytimeクライアントを作る
# yum install xinetd # vi /etc/xinetd.d/daytime-stream
disable = yes ↓ disable = no
# systemctl start xinetd # netstat --tcp --listen | grep daytime tcp6 0 0 [::]:daytime [::]:* LISTEN $ vi daytime.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> static int open_connection(char *host, char *service); int main(int argc, char *argv[]) { int sock; FILE *f; char buf[1024]; sock = open_connection((argc>1 ? argv[1] : "localhost"), "daytime"); /* 「"daytime"」は「"13"」でも接続できる(13番ポート) */ f = fdopen(sock, "r"); if (!f) { perror("fdopen(3)"); exit(1); } fgets(buf, sizeof buf, f); fclose(f); fputs(buf, stdout); exit(0); } static int open_connection(char *host, char *service) { int sock; struct addrinfo hints, *res, *ai; int err; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; /* * 接続先とポートを渡すと、1つ以上のaddrinfo構造体を返す * その構造体には、bindやconnectを呼び出す際に指定できるインターネットアドレスが格納されている */ if ((err = getaddrinfo(host, service, &hints, &res)) != 0) { fprintf(stderr, "getaddrinfo(3): %s\n", gai_strerror(err)); exit(1); } /* * getaddrinfoの戻り値はアドレス候補のリスト * 候補に次々に接続を試し、最初に成功したアドレスを試す * 例えばIPv4のアドレスとIPv6のアドレスを返してきた場合に、接続できる方を使用する */ for (ai = res; ai; ai = ai->ai_next) { sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); if (sock < 0) { continue; } if (connect(sock, ai->ai_addr, ai->ai_addrlen) < 0) { close(sock); continue; } /* 接続に成功 */ freeaddrinfo(res); return sock; } fprintf(stderr, "socket(2)/connect(2) failed"); freeaddrinfo(res); exit(1); }
$ gcc -o daytime daytime.c $ ./daytime 22 FEB 2020 17:21:10 JST $ ./daytime localhost 22 FEB 2020 17:21:16 JST
■httpを処理する
$ vi httpd.c
プログラムの内容は以下を参照 https://github.com/aamine/stdlinux2-source/blob/master/httpd.c
$ vi test.txt
Hello! This is a test file.
$ gcc -o httpd httpd.c $ ./httpd … オプションを指定していないので使い方を表示 Usage: ./httpd <docroot> $ ./httpd . … カレントディレクトリをドキュメントルートとする GET /test.txt HTTP/1.0 … リクエストを送る … 空行を送るとリクエスト終了 HTTP/1.0 200 OK … レスポンスが返る Date: Sat, 22 Feb 2020 09:24:55 GMT Server: LittleHTTP/1.0 Connection: close Content-Length: 30 Content-Type: text/plain Hello! This is a test file. $ ./httpd . GET /xxx.txt HTTP/1.0 … 存在しないファイルを指定してみる HTTP/1.0 404 Not Found … 404エラーが返る Date: Sat, 22 Feb 2020 09:29:43 GMT Server: LittleHTTP/1.0 Connection: close Content-Type: text/html <html> <header><title>Not Found</title><header> <body><p>File not found</p></body> </html>
■httpサーバを作る
$ vi httpd2.c
プログラムの内容は以下を参照 https://github.com/aamine/stdlinux2-source/blob/master/httpd2.c
$ vi test.txt
Hello! This is a test file.
$ gcc -o httpd2 httpd2.c $ ./httpd2 … オプションを指定していないので使い方を表示 Usage: ./httpd2 [--port=n] [--chroot --user=u --group=g] [--debug] <docroot> $ sudo su - # ./httpd2 --port=8081 --chroot --user=vagrant --group=vagrant . … カレントディレクトリをドキュメントルートとする
ブラウザから以下にアクセスすると、テキストファイルの内容が表示される http://192.168.33.10:8081/test.txt デーモンとはバックグラウンドプロセスのことで、厳密には制御端末を持たないプロセスのこと サーバはずっと起動させておく必要があるため、プロセスを起動させた人がログアウトしても動き続けるようにする デーモンになるには、プログラム内で以下3つの作業を行う(もしくはこれらを行う daemon(3) を使う) ・ルートディレクトリに chdir() する プロセスが動作しているディレクトリはアンマウントできないので、 長時間動作するデーモンはできるだけルートディレクトリに移動させておく ・標準入出力を /dev/null につなぐ プログラムからうっかり標準入出力を使ってしまってエラーが起きないようにする ・制御端末を切り離す fork() してから親プロセスを終了すると、制御端末を持たないプロセスになる 子プロセスで setsid() で制御端末を持たない新しいセッションを作成する
■C: メモ
■入出力関数 以下のような関数がある gets や scanf といった、使用が推奨されない関数も残っているので注意 fgetc(f) ... バイト単位の入力 fputc(c, stdout) ... バイト単位の出力 getc(f) ... 意味は fgetc() と同じ。マクロとして実装されている putc(c, stdout) ... 意味は fputc() と同じ。マクロとして実装されている getchar() ... 意味は getc(stdin) と同じ。入力元が固定されている putchar(c) ... 意味は putc(c, stdout) と同じ。出力先が固定されている fgets ... 行単位の入力。ただし1行読んで止まったのかバッファいっぱいまで書き込んで止まったのか区別できないので、扱いには注意が必要 fputs ... 任意の文字列を出力。行を出力するとは限らない。fgets() と対になるような名前だが、効果は対ではない gets ... バッファオーバーフローの問題を起こす欠陥があるので使ってはならない puts ... 文字列を標準出力に出力し、その後 '\n' を出力する scanf ... バッファオーバーフローの問題を起こす可能性があるので推奨されない ■文字列を出力
printf("[ %s ]\n", argv[i]);
■入出力のプログラム例
#define _CRT_SECURE_NO_WARNINGS 1 #include <stdlib.h> #include <stdio.h> #include <string.h> #include <stdbool.h> #include <ctype.h> bool isnumber(char *s) { bool flag = true; while (*s != '\0') { if (isdigit(*s) == 0 && *s != '.') { flag = false; break; } s++; } return flag; } int main() { char buffer[1024]; char name[64]; char price[64]; char tax[64]; double price_number; double tax_number; double result_number; /* 商品名を入力 */ printf("商品名を入力してください>\n"); while (true) { /* 標準入力から読み込む */ if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "エラーが発生しました。プログラムを終了します。\n"); exit(0); } else { /* 改行文字がなければバッファサイズを超えているとみなし、入力ストリームを空読みする */ if (strchr(buffer, '\n') == NULL) { while (getchar() != '\n'); } /* 改行文字があれば文字列の終わりとみなす */ else { buffer[strlen(buffer) - 1] = '\0'; } /* 文字サイズ確認 */ if ((int)strlen(buffer) > 64) { fprintf(stderr, "商品名は64バイト以下で入力してください>\n"); continue; } /* 入力 */ else if (sscanf(buffer, "%s", name) == EOF) { fprintf(stderr, "商品名を入力できませんでした>\n"); continue; } } break; } /* 価格を入力 */ printf("価格を入力してください>\n"); while (true) { if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "エラーが発生しました。プログラムを終了します。\n"); exit(0); } else { if (strchr(buffer, '\n') == NULL) { while (getchar() != '\n'); } else { buffer[strlen(buffer) - 1] = '\0'; } if ((int)strlen(buffer) > 8) { fprintf(stderr, "価格は8バイト以下で入力してください>\n"); continue; } else if (sscanf(buffer, "%s", price) == EOF) { fprintf(stderr, "価格を入力できませんでした>\n"); continue; } else if (isnumber(price) == false) { fprintf(stderr, "価格は数値で入力してください>\n"); continue; } } break; } /* 税率を入力 */ printf("税率を入力してください>\n"); while (true) { if (fgets(buffer, sizeof(buffer), stdin) == NULL) { fprintf(stderr, "エラーが発生しました。プログラムを終了します。\n"); exit(0); } else { if (strchr(buffer, '\n') == NULL) { while (getchar() != '\n'); } else { buffer[strlen(buffer) - 1] = '\0'; } if ((int)strlen(buffer) > 4) { fprintf(stderr, "税率は4バイト以下で入力してください>\n"); continue; } else if (sscanf(buffer, "%s", tax) == EOF) { fprintf(stderr, "税率を入力できませんでした>\n"); continue; } else if (isnumber(tax) == false) { fprintf(stderr, "税率は数値で入力してください>\n"); continue; } } break; } /* 計算 */ price_number = atof(price); tax_number = atof(tax); result_number = price_number + price_number * tax_number; /* 結果を表示 */ printf("商品名は「%s」です\n", name); printf("価格は「%s」です\n", price); printf("税率は「%s」です\n", tax); printf("税込価格は%.0f円です\n", result_number); return 0; }
■C++: 基本的なプログラム
■名前空間を指定
#include <iostream> using namespace std; int main() { cout << "Hello, World!" << endl; return 0; }
「std::cout」のような命令を「cout」と書くことができるようになる ■出力
#include <iostream> using namespace std; int main() { cout << 7 << " : " << 3.14 << endl; cout << "Hello " << "C " << "Lang " << "World!" << endl; return 0; }
「iostream」で定義された「cout」に出力演算子「<<」で値を与えると、画面に表示できる 「cout」はC言語と同様に、「sprinf」などでも出力できる 「 << endl;」部分はC言語と同様に、「 << "\n";」でも改行できる ■改行 std::endlと\nの違い|C++のHelloWorldを理解するための本 https://zenn.dev/masahiro_toba/books/a7d0c3d685a209/viewer/208ab9 C++で、std::endl と \n はどっちを使ったほうがいいですか? - Quora https://jp.quora.com/C-%E3%81%A7-std-endl-%E3%81%A8-n-%E3%81%AF%E3%81%A9%E3%81%A3%E3%81%A1%E3%82%92%... ■入出力
#include <iostream> using namespace std; int main() { char str[128]; cout << "名前を入力してください>"; cin >> str; cout << "ようこそ!" << str << "さん。" << endl; return 0; }
実行すると以下のようになる
$ ./input 名前を入力してください>test ようこそ!testさん。
「iostream」で定義された「cin」から出力演算子「>>」で値を受け取ると、変数に代入できる ホワイトスペース(改行や半角スペースなど)が入力の区切りとみなされる 以下のようにすると、複数の値を受け取ることができる
#include <iostream> using namespace std; int main() { int x, y; cout << "2つの値を加算します>"; cin >> x >> y; cout << x + y << endl; return 0; }
実行すると以下のようになる
$ ./input2 2つの値を加算します>2 3 5
■ファイル入出力 以下でファイルを読み込むことができる あらかじめ file.txt を作成し、適当な内容を記述しておく
#include <fstream> #include <iostream> #include <string> int main() { // 読み込むファイルのパスを指定 std::ifstream file("file.txt"); std::string line; // 1行ずつ読み込む while (std::getline(file, line)) { std::cout << line << std::endl; } // ファイルを閉じる file.close(); return 0; }
以下でファイルに書き込むことができる あらかじめ fruits.txt を作成しておく(内容はカラでいい)
#include <fstream> #include <iostream> #include <string> #include <vector> int main() { // 書き出すファイルのパスを指定 std::ofstream file("fruits.txt"); std::vector<std::string> fruits = { "apple", "strawberry", "pear", "grape" }; // 書き出し for (const auto fruit : fruits) { file << fruit << std::endl; } // ファイルを閉じる file.close(); return 0; }
■テンプレート関数 異なるデータ型の値を受け取り、同じ処理を行うことができる
#include <iostream> using namespace std; template <typename X> void println(X out) { cout << out << endl; } int main() { println("Hello, World!"); println(10); println(3.14); return 0; }
以下のようにすれば、複数の値を受け取ることもできる
#include <iostream> using namespace std; template <typename X1, typename X2> void println(X1 out1, X2 out2) { cout << out1 << " : " << out2 << endl; } int main() { println("Hello, World!", 10); println("Hello, World!", 3.14); return 0; }
■メモリの動的確保 C言語では malloc() でメモリを動的に確保できるが、C++ではあまり推奨されない C++の場合、new で動的にメモリを確保できる 確保したメモリは delete で開放できる
#include <iostream> using namespace std; int main() { int *p; p = new int; *p = 100; cout << "動的に割り当てたメモリの内容: " << *p << endl; delete p; return 0; }
動的メモリの割り当て http://wisdom.sakura.ne.jp/programming/cpp/cpp29.html
■C++: クラスを使ったプログラム
■基本的なクラス クラスの作成例 メンバ変数(プロパティ)に値を代入して表示している
#include <iostream> using namespace std; class Hello { public: //char *str; const char* str; } obj; int main() { obj.str = "Hello, Class!"; cout << obj.str << endl; return 0; }
「char *str」部分で警告が表示されたので、「const char *str」とした [C++] ダブルクォーテーションで囲った文字列を関数に渡すことで警告が出るときの理由と対処 - Qiita https://qiita.com/limb/items/c1f4e921b0911dfce3dc メンバ関数(メソッド)を定義し、コンストラクタも定義し、 オブジェクト名も任意のものを指定できるように
#include <iostream> using namespace std; // クラスの定義 class Hello { // 外部からアクセスできない変数と関数 private: int point; // 外部からアクセスできる変数と関数 public: // メンバ変数 const char* str; // コンストラクタ Hello(const char* str); // デストラクタ ~Hello(); // メンバ関数 void print(); }; Hello::Hello(const char* str) { this->point = 0; this->str = str; cout << "[constructor: " << this->str << "]" << this->point << endl; } Hello::~Hello() { this->point++; cout << "[destructor]" << this->point << endl; } void Hello::print() { this->point++; cout << "[" << this->str << "]" << this->point << endl; cout << "[" << Hello::str << "]" << Hello::point << endl; // 「this->str」は「Hello::str」と書くこともできる cout << "[" << str << "]" << point << endl; // メンバ変数名だけでも変数を参照できる } int main() { // クラスの利用 Hello obj("Hello, Class."); obj.print(); obj.str = "Hello, Class!"; cout << obj.str << endl; // 以下の書き方でもクラスを利用できる Hello obj2 = Hello("Hello, Class 2."); obj2.print(); obj2.str = "Hello, Class 2!"; cout << obj2.str << endl; // 以下の書き方でもクラスを利用できる(メモリの動的確保) Hello *obj3 = new Hello("Hello, Class 3."); obj3->print(); obj3->str = "Hello, Class 3!"; cout << obj3->str << endl; delete obj3; return 0; }
C++ クラス 入門 http://vivi.dyndns.org/tech/cpp/class-basic.html ロベールのC++教室 - 第4章 引数付きの構築 - http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02004.html ロベールのC++教室 - 第19章 動的オブジェクト - http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02019.html ■オーバーライド 同名のメンバ関数を定義し、引数によって処理先を変える
#include <iostream> using namespace std; class Hello { public: void print(); void print(const char* str); }; void Hello::print() { cout << "[print]" << endl; } void Hello::print(const char* str) { cout << "[" << str << "]" << endl; } int main() { Hello obj; obj.print(); obj.print("Hello!"); return 0; }
■継承 クラスの機能を引き継いで新しいクラスを作る 「class Bye : public Hello」を「class Bye : private Hello」にすると、Helloクラス内の命令はByeクラス内でのみ使用できるようになる
#include <iostream> using namespace std; class Hello { public: void print(); void say(); }; void Hello::print() { cout << "[print]" << endl; } void Hello::say() { cout << "[Hello!]" << endl; } class Bye : public Hello { public: void say(); void test(); }; void Bye::say() { cout << "[Bye!]" << endl; } void Bye::test() { cout << "[TEST]" << endl; } int main() { Hello hello; Bye bye; hello.print(); hello.say(); bye.print(); bye.say(); bye.test(); return 0; }
■多重継承 ClassA と ClassB を継承して ClassC を作る のように、複数のクラスを継承できる
#include <iostream> using namespace std; class ClassA { public: ClassA(); }; ClassA::ClassA() { cout << "[ClassA]" << endl; } class ClassB { public: ClassB(); }; ClassB::ClassB() { cout << "[ClassB]" << endl; } class ClassC : public ClassA, public ClassB { public: ClassC(); }; ClassC::ClassC() { cout << "[ClassC]" << endl; } int main() { ClassA a; ClassB b; ClassC c; return 0; }
■多重継承での仮想基本クラス Base を継承して ClassA と ClassB を作り、 その ClassA と ClassB を継承して ClassC を作り、 その ClassC のメンバ変数を扱う場合、複数の Base を継承しているために変数が曖昧な状態となる 具体的には、c.name にアクセスしようとすると「request for member ‘name’ is ambiguous」というエラーになる この場合、以下のように「virtual public Base」と宣言すると仮想基本クラスとなり、Baseのコピーは一つしか持たなくなる
#include <iostream> using namespace std; class Base { public: const char* name; Base(); }; Base::Base() { cout << "[Base]" << endl; } class ClassA : virtual public Base { public: ClassA(); }; ClassA::ClassA() { cout << "[ClassA]" << endl; } class ClassB : virtual public Base { public: ClassB(); }; ClassB::ClassB() { cout << "[ClassB]" << endl; } class ClassC : public ClassA, public ClassB { public: ClassC(); }; ClassC::ClassC() { cout << "[ClassC]" << endl; } int main() { ClassA a; ClassB b; ClassC c; c.name = "Taro"; cout << c.name << endl; return 0; }
■抽象クラス 「void print()」という宣言を「virtual void print() = 0」のようにすると純粋仮想関数となり、子クラスで再定義しなければ継承できなくなる 純粋仮想関数を持つクラスを抽象クラスと呼び、抽象クラスから直接オブジェクトを作ることはできない 継承されて初めて使うことができるようになる
#include <iostream> using namespace std; class ClassA { public: ClassA(); virtual void print() = 0; }; ClassA::ClassA() { cout << "[ClassA]" << endl; } class ClassB : public ClassA { public: ClassB(); void print(); }; ClassB::ClassB() { cout << "[ClassB]" << endl; } void ClassB::print() { cout << "[print]" << endl; } int main() { //ClassA a; ClassB b; return 0; }
■テンプレートクラス テンプレート関数のクラス版
#include <iostream> using namespace std; template <typename X> class Stack { X box[64]; int index; public: bool push(X var) { if (index > 63) return false; box[index] = var; index++; return true; } X pop() { if (index == 0) return NULL; return box[--index]; } Stack() { index = 0; } }; int main() { Stack<int> i_obj; i_obj.push(10); i_obj.push(100); cout << i_obj.pop() << endl; cout << i_obj.pop() << endl; Stack<const char*> s_obj; s_obj.push("Hello!"); s_obj.push("Template!"); cout << s_obj.pop() << endl; cout << s_obj.pop() << endl; return 0; }
テンプレート関数とは異なり、テンプレートクラスの場合は 「Stack<int> i_obj」や「Stack<const char*> s_obj」のように、扱う型を明示しないとエラーになるので注意 C++テンプレートことはじめ - Qiita https://qiita.com/a-beco/items/7709ab80a05ea2d09ea8 ■例外処理
#include <iostream> using namespace std; int main() { try { throw "Exception!"; cout << "Hello, World!" << endl; } catch (const char *str) { cout << str << endl; } cout << "Complete!" << endl; return 0; }
#include <iostream> using namespace std; class Hello { public: void print(int num); }; void Hello::print(int num) { if (num < 1) { throw "Exception!"; } cout << "[" << num << "]" << endl; } int main() { try { Hello obj; obj.print(2); } catch (const char *str) { cout << str << endl; } try { Hello obj; obj.print(-1); } catch (const char *str) { cout << str << endl; } return 0; }
■make クラスの宣言をヘッダファイルに書き、クラスの実装をソースコードに書く クラスを使いたいときは、ヘッダファイルをインクルードする 二重定義を防ぐために、ヘッダファイルには二重定義防止コードを書いておく
$ vi main.cpp
#include <iostream> #include "Test1.h" #include "Test2.h" using namespace std; int main() { Test1 a; a.func(); Test2 b; b.func(); cout << "Hello, World!" << endl; return 0; }
$ vi Test1.h
#ifndef __TEST1_H_INCLUDED__ #define __TEST1_H_INCLUDED__ class Test1 { public: void func(); }; #endif
$ vi Test1.cpp
#include <iostream> #include "Test1.h" using namespace std; void Test1::func() { cout << "Test1::func" << endl; }
$ vi Test2.h
#ifndef __TEST2_H_INCLUDED__ #define __TEST2_H_INCLUDED__ class Test2 { public: void func(); }; #endif
$ vi Test2.cpp
#include <iostream> #include "Test2.h" using namespace std; void Test2::func() { cout << "Test2::func" << endl; }
makeを使わずにビルドする場合 毎回適切にビルドを実行する必要がある
$ g++ -c -o main.o main.cpp $ g++ -c -o Test1.o Test1.cpp $ g++ -c -o Test2.o Test2.cpp $ g++ -o main main.o Test1.o Test2.o $ ./main
makeを使ってビルドする場合 Makefileというファイルに処理条件を書いておくと、makeと書くだけで適切にビルドが実行される
$ vi Makefile
#@range_begin(rules) CC = g++ CFLAGS = -g -Wall OBJS = main.o Test1.o Test2.o main: $(OBJS) $(CC) $(CFLAGS) -o $@ $(OBJS) main.o: main.cpp $(CC) -c $(CFLAGS) -o $@ $< Test1.o: Test1.cpp $(CC) -c $(CFLAGS) -o $@ $< Test2.o: Test2.cpp $(CC) -c $(CFLAGS) -o $@ $< #@range_end(rules)
$ make $ ./test
ロベールのC++教室 - 第7章 ファイルを分けよう - http://www7b.biglobe.ne.jp/~robe/cpphtml/html02/cpp02007.html ロベールのC++教室 - 第70章 仰山のファイル - http://www7b.biglobe.ne.jp/~robe/cpphtml/html01/cpp01070.html ヘッダーと.cppを作ってコンパイル http://marupeke296.sakura.ne.jp/IKDADV_PI_CPP2.html

Advertisement