ばとらの部屋

プログラミング演習I

作成日 2023年12月5日 / 最終更新日 2024年3月3日

グローバル変数とローカル変数

  • グローバル変数:関数外に宣言した変数、どこからでも呼び出せる。
  • ローカル変数:関数内に宣言した変数、宣言した関数内でのみ呼び出せる。

同名の変数が存在する場合、呼び出し箇所と近い方の変数が優先される。 グローバル変数は代入せずに宣言すると0を格納する。

#include <stdio.h>
int a;
int main(void){
    printf("%d\n",a); // 0
    return 0;
}

print関数の変換指定子

%[フラグ][最小フィールド幅][.精度][修飾子]変換指定子

変換指定子変数の型概要
%cchar文字
%shar *文字列
%dint10進整数
%hdshort int半分の精度の10進整数
%ldlong int倍精度の10進整数
%uunsigned int符号なし10進整数
%huunsigned short int符号なし半分の精度の10進整数
%luunsigned long int符号なし倍精度の10進整数
%oint8進整数
%xint16進整数
%ffloat実数
%lfdouble倍精度の実数
%efloat実数の指数表示
%gfloat実数の最適表示

桁数指定

printf("%f"  , 3.14); // 3.140000(デフォルト)
printf("%.2f", 3.14); // 3.14    (下二桁)
printf("%.0f", 3.14); // 3       (小数点以下切り捨て)
printf("%.f" , 3.14); // 3       (小数点以下切り捨て)
 
printf("%5.2f", 3.14);   // " 3.14"(右詰め)(空白が追加される)
printf("%.2f", 0.0 / 0); // "nan"  (NaN)
printf("%.2f", 1.0 / 0); // "inf"  (Infinity)

前置演算と後置演算

前置演算は「先に演算してから代入」

int i = 10;
int j = ++i; //i = 11 j = 11;

後置演算は「先に代入してから演算

int i = 10;
int j = i++; //i = 11 j = 10;

値渡し・ポインタ渡し・参照渡し

  • 値渡し:その値のコピーを関数に渡すため、関数の内部でその値を変更しても、関数を抜けた後にその影響が残らない。
  • ポインタ渡し:値のポインタ(メモリ上の住所)を関数に渡すため、関数の内部でそのメモリ上の値を変更すると、関数を抜けた後にはその影響が残ったままとなる。
  • 参照渡し(C++):ポインタ渡しと同様に、関数内部で値を変更すると、関数を抜けた後にもその影響が残る。
値渡しポインタ渡し参照渡し
var*var&var

値渡し

#include <stdio.h>
 
void add1(int x) {
  x += 1;
}
 
int main(void){
    int a = 0;
    add1(a);
    printf("%d\n", a); // 0
    return 0;
}

参照渡し

#include <stdio.h>
 
void add1(int *x) {
  *x += 1;
}
 
int main(void){
    int a = 0;
    add1(&a);
    printf("%d\n", a); // 1
    return 0;
}

ポインタと配列

変数の前に&をつけるとその変数のアドレスを取得する。

char x = 'A';
printf("%p\n", &x); // xのアドレスを表示
printf("%c\n", &x); // A
 
char *p;
p = &x; // ポインタpにxのアドレスを代入
printf("%p\n", p); // アドレスを表示

ポインタに間接演算子*をつけるとアドレスに格納された値を取得する。

char x = 'A';
char *p;
p = &x; // ポインタpにxのアドレスを代入
printf("%c\n", *p); // A

以下のようにポインタの指す値の変更をすることができる。ただし、ポインタで変数を指していない場合はセグフォになる。

char x = 'A'; // メモリに値を格納
char *p = &x; // pにxのアドレスを代入
*p = 'B'; // pのアドレスの値を'B'に変更
printf("%c\n", *p); // pのアドレスの値を表示

このように間接演算子*を使うことでポインタに格納されたアドレスのデータにアクセスできる。

ポインタで配列を指すと、配列の先頭を指す。

char arr[] = {'A', 'B', 'C', 'D', 'E'};
char *p;
p = arr;
printf("%c\n", *p); // A

次のように書いても同義で、先頭を指す。

p = &(arr[0]);

ポインタが配列を指しているとき、インデックスを指定して各要素にアクセスすることができる。

char arr[] = {'A', 'B', 'C', 'D', 'E'};
char *p;
p = arr;
printf("%c\n",*p); // A
p[0] = 'F'; // AをFに書き換え
p[3] = 'G'; // DをGに書き換え

このときp = 0*pは配列の0番目にアクセスしている。

parr[1]を指しているため、pは1番目を配列の先頭としてみなす。

char arr[] = {'A', 'B', 'C', 'D', 'E'};
char *p;
p = &(arr[1]);
// 以下は基準を配列の1番目とする
p[0] = 'F'; // BをFに書き換え
p[3] = 'G'; // EをGに書き換え

アドレスそのものを加減算で変化させることで、配列の各要素にアクセスすることもできる。

char arr[] = {'A', 'B', 'C', 'D', 'E'};
char *p;
p = arr;
*(p + 3) = 'H'; // CをHに書き換え

*をつけることでそのアドレスに格納している値にアクセスしていることがわかる。

模擬期末試験の解説

#include <stdio.h>
 
int main(void){
    int data[]={8,3,2,9,6,7,1,5,4};
    int *ip, *iq;
 
    ip = data; // ip = 0, ポインタに配列の先頭アドレスを代入
    printf("%d\n", *ip + data[1]); // 8 + 3 = 11
 
    iq = ip+3; // iq = 3, iqは配列の先頭+3番目のアドレスを代入
    ip++; // ポインタに配列の先頭+1番目のアドレスを代入
    printf("%d\n", *ip + *iq); // 9 + 3 = 12
 
    ip = data; // ip = 0, ポインタは配列の先頭アドレスを指す
    printf("%d\n", *(ip + 6)); // 先頭+6番目のアドレスを指す // 1
 
    ip = data + 4; // ポインタは配列の先頭+4番目のアドレスを代入
    iq = ip - 3; // iqは配列のip(先頭+4番目)-3番目のアドレスを指す
 
    // iq + data[*ip] = *(1 + data[6]) = *(1 + 1) = 2
    printf("%d\n", *(iq + data[*ip])); // 2
 
    return 0;
}

構造体とアロー演算子

宣言方法

#include <stdio.h>
 
struct data {
    int x;
    char str[7];
};
 
int main(void) {
    struct data d = {0, "Hello!"};
    d.x = 5; // 構造体dのメンバxに5を格納
    printf("%d\n",d.x); // 5
    printf("%s\n",d.str); // Hello!
}

アロー演算子->は、*.をひとつにまとめた演算子である。 ポインタから構造体のメンバへアクセスする演算子である。

struct data d1, d2;
struct data *p = &d1; // ポインタpを宣言して構造体d1のアドレスを代入
(*p).x = 10; // ポインタを使ってd1.xに10を代入
p->y = 20; // アロー演算子を使うパターン
 
p = &d2; // ポインタpは構造体d2を指す
(*p).x = 30; // d2.x = 30;
p->y = 40; // d2.y = 40;

(*構造体ポインタ).(メンバ名)構造体ポインタ->メンバ名と書くことができる。

switch文

switch文においてcaseブロックにbreak文にない場合は次のcaseブロックに進む。

#include <stdio.h>
 
int main(void){
  int k = 0; int i = 0;
  switch (k){
  case 0: i++; //break;がないので次のcaseに進む
  case 1: i = 5; break; //break;があるのでswitch文を抜ける
  case 2: i = 10; break;
  default: i = 100;
  }
  printf("i=%d\n", i); // 5
}

文字列

C言語において文字列を扱う場合はcharを使う。char型の変数のサイズは1バイトであり、1変数に1文字しか保存できない。 複数の文字を扱う場合はchar型の配列を使う。基本的にchar型の配列の最後の文字に終端文字(ヌル文字)を入れる必要がある。''で囲むとヌル文字\0が必要で、""で囲むと必要ない。

#include <stdio.h>
 
int main(void) {
    char str1[] = "ABC"; // ""で囲むと文字列型で終端文字が自動で付加される
    char str2[] = {'D', 'E', 'F', '\0'}; // ''で囲むと文字型で終端文字が必要
    printf("str1 = %s\n", str1);
    printf("str2 = %s\n", str2);
    return 0;
}

要素数 = 配列全体のメモリサイズ / 配列の要素1つのメモリサイズ

bit演算

printf("%d\n",1 << 3);   // 左シフト 1*2^3
printf("%d\n",5 >> 1);   // 右シフト 5/2^1(切り捨て)
printf("%d\n",5 & 3);    // AND演算 0101 & 0011 = 0001 = 1
printf("%d\n",5 | 3);    // OR演算 0101 | 0011 = 0111 = 7
printf("%d\n",5 ^ 3);    // XOR演算 0101 ^ 0011 = 0110 = 6
printf("%d\n",~5);       // NOT演算 ~0101 = 1010 = -6