c言語もよく忘れる言語なので忘れガチなところだけチートシートを作ります。
char *a[]の意味するところ
最も基本ですが、メモリのイメージがしっかりできていないとエラーが出まくったりします。
どこで使われているか?
Cの教科書なんかによくでてくる、微妙にわかったようなわからないようなmainの引数でもつかわれています
#include <stdio.h>
int main(int argc,char *argv[]) {
printf("argc=%d \n",argc);
for (int i=0;i<argc;i++){
printf("argv[%d]=%s\n",i,argv[i]);
}
return 0;
}
実行結果
ターミナルやコンソールで
./main a b c d
とすると
argc=5
argv[0]=./main
argv[1]=a
argv[2]=b
argv[3]=c
argv[4]=d
となります。
char *a[]={"abc","xy"} の分解
①a まずは変数名aが中心にきます
②a[] *と[]では[]のほうが優先度が高いので、変数aは配列です
③*a[] 配列の中身はポインタです
④char *a[] ポインタの先は文字型です
例えば配列aが100番地、文字列"abc"が1000番地、'xy'が1100番地に割り付けられているとしたら
シンプルなプログラムで実際のメモリイメージを確認してみましょう。
#include <stdio.h>
int main(void) {
const char *a[]={"abc","xy"} ;
printf("%p %p %p\n",a,a[0],&a[0]);
printf("%p %p %p\n",a,a[1],&a[1]);
return 0;
}
replitでの結果です。
https://replit.com/@bkh4149/memoriPei-Zhi-a
ただし実際のアドレスは実行ごとに毎回変わります
アクセス方法
配列はポインタ(配列の先頭アドレス)なので*や**を使ってもアクセスします
先程のコードを少し変化させると
#include <stdio.h>
int main(void) {
const char *a[]={"abc","xy"} ;
printf("%p %p %p\n",a,a[0],&a[0]);
printf("%p %p %p\n",a,a[1],&a[1]);
printf("%p\n",*a); //←ここ追加
printf("%x\n",**a); //←ここ追加
return 0;
}
こうなります。(アドレスは毎回変わります)
0x7ffd7e375710 0x402004 0x7ffd7e375710
0x7ffd7e375710 0x402008 0x7ffd7e375718
0x402004
61
ちょっとくどいかもしれませんが、以下のイメージです。
aは配列なので、単にaとだけ書くと&a[0]のアドレスになっています。その中身が*aで値はここでは0x402004です。
そのポインタが指し示す先(**a)に0x61(アスキーコードの'a')が入っています。
const char* 型
以下のパターンも突然出てくるとドギマギしてしまいがちなのではないでしょうか?
#include <stdio.h>
const char* getMessage() {
return "Hello from WebAssembly!";
}
int main(void) {
const char *c;
c=getMessage();
printf("%s\n", c);
return 0;
}
以下の問題にすぐに答えられたら、スルーしてください
①なぜ char *c;ではなくて const char *c;なのか
②getMessage()は何をかえすのか
回答
①getMessage()関数の中で文字列リテラルを使っています。文字列リテラルはデータセグメントに格納され、実行時に変更することはできません。そのため、返されるポインタは const char* 型であり、これは変更不可能な文字列を指すことを意味します。
char *c ではなく const char *c を使用するのは、この不変性を尊重するためです。constを付けることで、このポインタを通じて文字列を変更することができないことを明示しています。
② getMessage() は const char* を返し、これは文字列リテラル "Hello from WebAssembly!" の先頭アドレスを指しています。const char* が変更不可能な文字へのポインタである理由は、関数が文字列リテラルを直接変更することを防ぐためです。
#include <stdio.h>
const char* getMessage() {
return "Hello from WebAssembly!";//文字列の先頭アドレスを返す
}
int main(void) {
const char *c;
c=getMessage(); // 先頭アドレスを cに代入
printf("%s\n", c); // 文字列を出力
return 0;
}
フォーマット指定方法
printf()関数やscanf()関数の中で使用されます。
以下にいくつかの基本的なフォーマット指定子を示します。
整数:
%d : 符号付き10進数整数
%i : %dと同じ
%u : 符号無し10進数整数
%o : 符号無し8進数整数
%x : 符号無し16進数整数 (小文字)
%X : 符号無し16進数整数 (大文字)
浮動小数点数:
%f : 小数点形式
%e : 指数形式 (小文字)
%E : 指数形式 (大文字)
%g : %fと%eの中から短い方
%G : %fと%Eの中から短い方
文字と文字列:
%c : 単一文字
%s : 文字列
ポインタ:
%p : ポインタ
これらのフォーマット指定子の前には幅、精度、フラグを指定することができます。たとえば、%08dは8桁の10進数を0でパディングします。
また、printf()関数では可変引数を使って複数の変数を出力することができます。以下に一例を示します。
int a = 5;
float b = 3.14f;
printf("整数: %d, 浮動小数点数: %.2f\n", a, b); // 整数: 5, 浮動小数点数: 3.14
以上がC言語の基本的なフォーマット指定方法になります。他にも細かいオプションがありますので、必要に応じてマニュアルやドキュメンテーションを参照してください。
フォーマット指定自体は文字列なのでこんな書き方もできます
#include <stdio.h>
int main(void) {
char a[]="%d\n"; //文字列配列
int b=3;
printf(a,b); //ここに注目!
return 0;
}
型について
型は値の範囲とメモリ使用量を決定します。
整数型:
型 | bit | 概要 | 符号 |
---|---|---|---|
char | 8 | キャラクタを格納 | 符号付きまたは符号なし |
int | 32 | 整数 | 符号付き |
short | 16 | intよりも小さい整数 | 符号付き |
long | 32/64 | intよりも大きな整数 | 符号付き |
long long | 64 | 非常に大きな整数 | 符号付き |
これらの型は、"unsigned"キーワードを前置することで、符号無し(つまり、非負)のバリエーションを作成できます。
浮動小数点型:
float: 単精度浮動小数点数を格納します。通常、32ビットで6-7桁の精度です。
double: 倍精度浮動小数点数を格納します。通常、64ビットで15-16桁の精度です。
long double: 拡張精度浮動小数点数を格納します。大抵のシステムでは80ビット以上です。
その他の型:
void: 値を持たず、戻り値または引数のない関数の型指定に使われます。
_Bool: 論理値(真または偽)を格納します。
ポインタ型:
メモリアドレスを格納します。
すべてのポインタ型は同じサイズですが、指しているデータの型は異なります。
また、C言語ではこれらの基本型を組み合わせて構造体(struct)、共用体(union)、列挙型(enum)などの複合データ型を定義することもできます。
C言語のプログラムエリア
主なメモリ領域は4つ
コード領域
データ領域
ヒープ領域
スタック領域
テキスト領域(またはコード領域):
実行コード(コンパイルされたマシンコード)が保存されます。読み取り専用。
データ領域:初期化済み
グローバル変数や静的変数で、初期値が設定されているものがここに保存されます。
データ領域: BSS:
初期値が設定されていないグローバル変数や静的変数がここに保存されます。これらの変数はデフォルトで0に初期化されます。
ヒープ領域:
動的に確保されるメモリ(malloc、calloc、reallocなどで確保)。ヒープはメモリの低位アドレスから高位アドレスへ向かって成長します。
スタック領域:
関数の呼び出しやローカル変数の格納に使用されるメモリ領域です。スタックは高位アドレスから低位アドレスへ向かって成長します。
strの7つ道具 string.h
関数とポインタ
ポインタと配列
コンパイルのオプション
fgetsとscanf
Makeの基本
スタックとキューの実現方法
超シンプル版ゼロから考えた原人的スタック
苦手意識高い系な人間が、書いたコードです
#include <stdio.h>
struct Stack{
int top;
int stk[30];//スタックは30個分
};
void showStk(struct Stack S){
int top=S.top;
printf("@showStk top=%d \n",top);
for (int i=top-1;i>=0;i--){
printf("i=%d stack=%d\n",i,S.stk[i]);
}
}
int main(void) {
int pop;
struct Stack S1;//push
S1.top=0;
showStk(S1);
S1.stk[S1.top]=11;//push
S1.top +=1;
showStk(S1);
S1.stk[S1.top]=22;//push
S1.top +=1;
showStk(S1);
S1.stk[S1.top]=33;//push
S1.top +=1;
showStk(S1);
S1.top -=1; //pop
pop = S1.stk[S1.top];
showStk(S1);
S1.stk[S1.top]=44;//push
S1.top +=1;
showStk(S1);
S1.top -=1; //pop
pop = S1.stk[S1.top];
showStk(S1);
printf("end\n");
return 0;
}
chatGPTによる修正版
上をchatGPTにコードレビューしてもらったところ、大量の修正点をあげていただきました。pushやpopを関数化して、スタックはポインタ渡しに変更してくれました。
#include <stdio.h>
#define MAX_SIZE 30 // Define stack size as a constant
struct Stack{
int top;
int stk[MAX_SIZE];
};
void push(struct Stack *S, int value){
if(S->top == MAX_SIZE){
printf("Stack overflow\n");
return;
}
S->stk[S->top] = value;
S->top += 1;
}
int pop(struct Stack *S){
if(S->top == 0){
printf("Stack underflow\n");
return -1; // return -1 or another specific value to indicate underflow
}
S->top -= 1;
return S->stk[S->top];
}
void showStk(struct Stack S){
int top = S.top;
printf("@showStk top=%d\n", top);
for (int i = top-1; i >= 0; i--){
printf("i=%d stack=%d\n", i, S.stk[i]);
}
}
int main(void) {
int popValue;
struct Stack S1;
S1.top = 0;
push(&S1, 11);
showStk(S1);
push(&S1, 22);
showStk(S1);
push(&S1, 33);
showStk(S1);
popValue = pop(&S1);
showStk(S1);
push(&S1, 44);
showStk(S1);
popValue = pop(&S1);
showStk(S1);
printf("end\n");
return 0;
}
さすがきれい、まあでもスタックの仕組みは捉えたきがします。
キューの実現 リングバッファ使用 chatGPT版
#include <stdio.h>
#define MAX_SIZE 5 // Define queue size as a constant
struct CircularQueue {
int items[MAX_SIZE];
int front, rear;
};
// Initializing the queue
void initializeQueue(struct CircularQueue *q) {
q->front = q->rear = -1;
}
// Check if the queue is full
int isFull(struct CircularQueue *q) {
if ((q->rear + 1) % MAX_SIZE == q->front) {
return 1;
}
return 0;
}
// Check if the queue is empty
int isEmpty(struct CircularQueue *q) {
if (q->front == -1) {
return 1;
}
return 0;
}
// Adding elements to the queue
void enqueue(struct CircularQueue *q, int element) {
if (isFull(q)) {
printf("Queue is full!\n");
return;
}
if (isEmpty(q)) {
q->front = q->rear = 0;
} else {
q->rear = (q->rear + 1) % MAX_SIZE;
}
q->items[q->rear] = element;
}
// Removing elements from the queue
int dequeue(struct CircularQueue *q) {
int data;
if (isEmpty(q)) {
printf("Queue is empty!\n");
return -1;
}
data = q->items[q->front];
if (q->front == q->rear) {
q->front = q->rear = -1;
} else {
q->front = (q->front + 1) % MAX_SIZE;
}
return data;
}
// Function to display the elements of queue
void display(struct CircularQueue *q) {
int i = q->front;
while (i != q->rear) {
printf("%d ", q->items[i]);
i = (i + 1) % MAX_SIZE;
}
printf("%d\n", q->items[i]);
}
int main() {
struct CircularQueue q;
initializeQueue(&q);
enqueue(&q, 1);
enqueue(&q, 2);
enqueue(&q, 3);
enqueue(&q, 4);
display(&q);
dequeue(&q);
display(&q);
enqueue(&q, 5);
display(&q);
dequeue(&q);
display(&q);
dequeue(&q);
display(&q);
dequeue(&q);
display(&q);
enqueue(&q, 6);
display(&q);
enqueue(&q, 7);
display(&q);
enqueue(&q, 8);
display(&q);
enqueue(&q, 9);
display(&q);
enqueue(&q, 10);
display(&q);
return 0;
}
実行結果は以下の通り、これもきれいなコードですなー
1 2 3 4
2 3 4
2 3 4 5
3 4 5
4 5
5
5 6
5 6 7
5 6 7 8
5 6 7 8 9
Queue is full!
5 6 7 8 9