3時間でc言語を覚える方法(基礎編)

記事
IT・テクノロジー
いきなり矛盾してますが、飲み込みが早い人と遅い人がいるので、そこは保証できかねます。


(最初のほうからゆっくり分かるまでやるのでは時間がかかりすぎるので、ある程度分かったらさっさと次の章に移ってソースを入力してコンパイルしてみてこのテキスト全体を通して何週も何週も学習してください。その方がよっぽど早く覚えられます。)


大丈夫です。辛抱強く勉強すれば誰でも基本的なコーディング自体は出来るようになります。
まず、これです。Hello,Worldからです。これがプログラミングの世界では、
ごく基本中の基本なプログラムとされているようです。
VisualStudioのVisualC++でコンパイルして実行ファイルを作成する
前提で話を進めます。

#include<stdio.h>
void main()
{
        printf("Hello,World\n");
}

つづけて、改行記号と\nについてです。
例えば、次のようなプログラム結果を行うにはどうすればいいかといいますと、
実行結果-------------
Hell
o,World

実行終了-------------

こういうプログラムがされています。
#include<stdio.h>
void main()
{
        printf("Hell\no,World\n");
}

つまり、printf(プリントエフ)という関数の"と"に囲まれ文字列の中に
\nと入れると改行されるという仕組みです。


第2章です。
こんどはキーボードからの数値の入力についてです。

キーボードからリアルタイムではなくて、Enterが押されるまで
画面が動かない数値の入力ですが、
scanf(スキャンエフ)という関数を使います。

では、次のようなプログラムはどう動くでしょうか?
#include<stdio.h>
void main()
{
        int num;
        scanf("%d",&num);
        printf("%dが入力されました\n",num);
}
まず、値が変わる数値を扱うためには変数というものが
必要です。で、c言語では、変数を使うには関数(今回はmain関数)の
冒頭で変数のタイプ(整数型、小数型、文字型)を宣言しなければ
使えません。

それぞれの変数の型の宣言方法を見てみましょう。ちなみに、
すでに使われている命令や同じ関数内などにある変数名は使えないですが
それ以外の名前なら、"_"やアルファベットが先頭にくれば、
変数名の後ろは英数字の組み合わせで何でも使えます。
(記号は_以外変数名に使えないみたいです)

・型の宣言方法
整数型
 int num;
小数型
 double weight;
文字型
 char moji;
です。
で、ここで疑問が発生します。さっき文字列を扱ったのに
文字列に関しての型が無いではないか?という指摘です。

これに関しては、実は文字型の連続、すなわち文字型の配列で
文字列を扱うことができます。つまり文字列の宣言はこうなります。
char 文字列の変数名[配列のサイズ];

char str[256];

で、それぞれの型にscanfとprintfの扱いについて書いていませんでした。
それぞれ、

整数型
int num;
scanf("%d",&num);
printf("%d\n",num);

小数型
double weight;
scanf("%lf",&weight);
printf("%lf\n",weight);

文字型
char c;
scanf("%c",&c);
printf("%c\n",c);

文字列型
char str[10];
scanf("%c",str);
printf("%s\n",str);

とこんな感じですが、
ここでも疑問が発生します。
何故、文字列型だけscanf("%c",&str);じゃないのか?
という疑問です。

これは、実は、c言語初心者の最大の壁と言われる
変数のアドレスのポインタを使っているからです。


つまり、配列名はアドレスを表すと考えてください。
他の配列ではなく単体の変数(整数、小数、文字)の型のアドレスを
表すには、変数名の前に&をつける決まりがあります。

ちなみに何度も言いますが、変数名は基本的に自由です。

では、ここまでで、プリントエフと変数の使い方について
学んだので、問題をといてください。
1.AくんとBくんの身長の高さの小数型の変数を入力して
Aくんは身長%lfメートルです
Bくんは身長%lfメートルです
と出力してください。

答えは、後で書きます。

2.
A:%dが入力されました。
B:%dが入力されました。
AにBを足すと%dです。
AからBを引くと%dです。
AにBを掛けると%dです。
AからBで割ると%dです。
ヒント:+,-,*,/演算子を使ってください。
それぞれプラス、マイナス、掛け算、割り算です。

と出力してください。

答えです。
1.は
#include<stdio.h>
void main()
{
        double A,B;
        scanf("%lf",&A);
        scanf("%lf",&B);
        printf("Aくんは身長%lfメートルです。\n",A);
        printf("Bくんは身長%lfメートルです。\n",B); 
}
2.は
#include<stdio.h>
void main()
{
        int A,B;
        scanf("%d",&A);
        scanf("%d",&B);
        printf("A:%dが入力されました\n",A);
        printf("B:%dが入力されました\n",B);
        printf("AにBを足すと%dです。",A+B);
        printf("AからBを引くと%dです。",A-B);
        printf("AにBを掛けると%dです。",A*B);
        printf("AからBで割ると%dです。",A/B);
}


第3章です。
ループを学びます。ループとは、繰り返し行う処理の事です。

c言語のループには3種類あります。
for(初期値;ループ条件;増分)

while(ループ条件)

do{
}while(ループ条件);

ここでは一番汎用性の高い
forだけみていきます。

まず、1から100まで足すプログラムを書いて

下さいと言われたらどうでしょうか?

printf("%d\n",1+2+3+4+...99+100);とか、
公式を使って、printf("%d\n",(100*101)/2);
いままでのやり方だけではこれしか出来ないと思いますが、
ループを上手く使うとかなり簡単に書く事ができます。

例えば、こんな感じです。
#include<stdio.h>
void main()
{
        int sum=0;int i;
        for(i=1;i<=100;i++)
        {
               sum+=i; 
        }
        printf("1から100を足すと%dです。\n",sum);
}

int sum=0;としているのは、変数を宣言しただけでは、c言語の場合値がゴミデータとして不定の値になるのでこういう風にループ内で加算するような変数は0にセットしないとデータがものすごく大きなものになったり、少し前のWindowsだとブルースクリーンの原因にもなります。

で、初期値に1を代入してあるのは、いいとして、i<=100というのは、変数iが100以下ならループを続けるという意味です。i++というのは、i=i+1やi+=1;と同じで変数iを1ずつ加算するという意味です。

でループ内でやはり、変数sumにiを足した値をsumに代入しています。

では、問題です。
1.自分が入力した整数の数だけ入力した値を加算するプログラムはどうなるか試してみましょう。
2.入力した角度をラジアンに変換するプログラムを書いてください。
 ヒント:角度*3.14/180でラジアンになります。




答え:
1.
#include<stdio.h>
void main()
{
        int sum=0;int addcount;
        int input;

        scanf("%d",&addcount);
        for(i=0;i<addcount;i++)
        {
                scanf("%d",&input);
                sum+=input;
        }
        printf("合計は%dです。\n",sum);
}

2.
#include<stdio.h>
void main()
{
        int deg;double rad;
        scanf("%d",&deg);
        rad=deg*3.14/180;
        printf("%d度は%lfラジアンです。\n",deg,rad);
}

演算子には、他にも+,-,*,/の他にもありますが、ここでは述べません。
ちなみに、暗黙のキャストと自明のキャストとかありますが、混乱を避けるため、ここでは述べません。

if文のやり方
if(条件A){
     //条件Aが成り立つならば
}else if(条件B){
     //条件Bが成り立つならば
}else{
     //条件AもBも成り立たなければ
}
ところで、条件って何かといいますと、
A=51;B=9;の場合、
if(A>B)はAのほうが大きいので条件が真となり、
これがif文の条件ならば成り立ちます。
逆に
C=100;D=256;の場合、
if(C>D)の場合、Dのほうが大きいので条件は成り立ちません。

で、条件に使える演算子があります。
それが、==(同じ)、!=(違う)、a<=b(以下),a<b(未満)、
a>=b(以上)、a>b(より大きい)!a(aの否定)です。

では、複数の条件を一気に行う方法があります。それが&&と||です。
if(条件A && 条件B){
     //条件が両方とも成り立つとき
}
if(条件A || 条件B){
     //条件が片方でも成り立つとき
}
では次の3つの条件はどうなるでしょう。
A=0;
...
if(A=2){
     //Aは2を代入されるため、0以下を代入されない限り
    //条件は真
}

A=0;
...
if(!A)
{
     //Aが0の否定なので1と見なされるので条件は真
}
A=100;
B=256;
if(A!=B)
{
     //AとBが違う場合
}
全て成り立ちます。

#define GameMain  0
#define PAUSE 1
#define TITLE 2

switch case文
switch(Mode)
{
        case TITLE://変数ModeがTITLEならば
                break;
        case PAUSE://変数ModeがPAUSEならば
                break;
        case GameMain://変数ModeがGameMainならば
                break;
        default://それでもなければ
                break;
}
それぞれのswitchの分岐にbreakを忘れないようにした方が
いいと思います。
break;忘れるとどうなるかというと、例でやってみます。
#include<stdio.h>
void main()
{
    int a;
    int tbl[3]={0};

    srand(GetTickCount());

    a=rand()%3;

    const int A=0;
    const int B=1;
    const int C=2;

    tbl[A]=A;
    tbl[B]=B;
    tbl[C]=C;

    switch(tbl[a])
    {
        case A:
            printf("%dです。\n",A);
        case B:
            //ここでAの後ろにbreakを忘れた為、AとBは同じ処理を
            //するはめになる
            printf("%dです。\n",B);
            break;
        case C:
            printf("%dです。\n",C);
            break;
        default:
            printf("%dです。\n",-1);
            break;
    }
}



第4章
配列とポインタです。

基本的には、配列とポインタは似ているようで違いますが、
配列名がポインタになる理由は、配列の先頭アドレスが配列名で
表せる事が理由のようです。

もしも、ポインタが分からなくなったらポインタを使わずに配列で
実装した方がいいかもしれないです。

例えば、この式はポインタだと成り立ちますが、配列だと成り立ちません。

#include<stdio.h>
void main()
{
    char strA[]="stone";
    char *strB="apple";
    //strA="explosion";//できない
    strB="juice";
    printf("A=%s,B=%s\n",strA,strB);
}

なので、文字列をポインタとして代用できる場合とそうでない場合がありそうです。

では、文字列strAをどうすれば変更できるのかというと、
#include<stdio.h>
#include<string.h>
void main()
{
    char strA[]="stone";
    char *strB="apple";
    //strA="explosion";//できない
    strcpy(strA,"explosion"); //これなら書き換えられる
    strB="juice";
    printf("A=%s,B=%s\n",strA,strB);
}

ポインタによる文字列操作を試してもらうために
試しにポインタで文字列の長さを求めてもらう関数と
ポインタで文字列の指定した文字の数を数える関数を
作ってもらいます。

ポインタの操作を書いていませんでした。

ポインタに変数のアドレスを代入するには
int A=365;
int *po=&A;
なので、printf("%d",*po);とやると
365が出力されるはずです。

で、ポインタのアドレスを+1するのには、
char *str="baka";
str++;です。

ポインタの値を取り出すには、
*ポインタ;です。なので、

int B=512;
int *A;
A=&512;
*A+=32;
printf("%d",*A);
です。


やってみましょう。
#include<stdio.h>

void main()
{
    int A=365;
    int *Po=&A;

    printf("%d",*Po);
}
実行結果----------------------
365
-------------------------------
きちんとなりましたね。

おっと、その型のポインタの宣言は
型名 *変数名です。
C++によく似て非なる参照というやり方がありますが、
あれは完全なミラーです。

で、関数の作り方ですが、
型名 関数名(引数の型 引数の変数名, 続きがあるなら同様に引数の宣言を続ける)
{
    return 返り値;
}
です。
なお、型名がvoidの場合のreturnはreturn;です。

#include<stdio.h>
#include<string.h>
int mojiLen(char *str)
{
    int i=0;
    for(;*str;)
    {
        str++;
        i++;
    }
    return i;
}
int SameCountStr(char *str,char ch)
{
    int i=0;
    for(;*str;)
    {
        if(*str==ch)
            i++;
        str++;
    }
 return i;
}
void main()
{
    char strA[100];
    char same;
    gets(strA);
    printf("%d",mojiLen(strA));
    scanf("%c",&same);
    printf("%cと同じ文字は%sの中に%d個ありました。\n",
    same,strA,SameCountStr(strA,same));
}
実行結果-------------------------------------------------
IwanttoeatApple.
16a
aと同じ文字はIwanttoeatApple.の中に2個ありました。
----------------------------------------------------------

第5章です。
計算、配列、ループ、ポインタ、関数までやったので、
(ここまでついて来れなかった方は「やさしいc」という本を
おススメします。本当にやさしく簡単にマスターできるように
書かれています。)

構造体とクラスについて書くつもりです。
C++の場合、構造体もクラスとして見なすようなのでC++を
学ぶ際にも重要です。
構造体の宣言は例えばこんな感じです。
typedef struct{
     int age;
     char *name;
}構造体名;

例えば、GameMachineという構造体を作ってみます。
typedef struct{
     int year;//販売開始年度
     char name[15];//ゲーム機の名前
     int MemoryAmount;//メモリの量(byte単位)
     char CPUName[20];//CPUの名前
}GameMachine;

でグローバル変数(関数の外に宣言した変数)のように
構造体を宣言します。

GameMachine NES;
NES.year=1985;
strcpy(NES.name,"NES");
NES.MemoryAmount=2048;
strcpy(NES.CPUName,"6502");


構造体をプログラマによってグローバルにするか、それとも関数mainに置くかどうかは、よく知らないのですが、あまりにもグローバル変数や構造体のデータを置くのはcの作法としてはダメみたいです。

では構造体のサンプルソースです。

#include<stdio.h>
#include<string.h>

void main()
{
    typedef struct{
     int year;//販売開始年度
     char name[80];//ゲーム機の名前
     int MemoryAmount;//メモリの量(byte単位)
     char CPUName[20];//CPUの名前
    }GameMachine;


    GameMachine NES,SNES;
    NES.year=1985;
    strcpy(NES.name,"ファミリーコンピューター");
    NES.MemoryAmount=2048;
    strcpy(NES.CPUName,"6502");

    SNES.year=1990;
    strcpy(SNES.name,"スーパーファミコン");
    SNES.MemoryAmount=-1;
    strcpy(SNES.CPUName,"65816");


    printf("販売開始年度=%d\n",NES.year);
    printf("ゲーム機の名前=%s\n",NES.name);
    printf("メモリの容量=%d\n",NES.MemoryAmount);
    printf("CPUの名前%s\n",NES.CPUName);

    NES=SNES;

    printf("販売開始年度=%d\n",NES.year);
    printf("ゲーム機の名前=%s\n",NES.name);
    printf("メモリの容量=%d\n",NES.MemoryAmount);
    printf("CPUの名前%s\n",NES.CPUName);
}

途中でSNESをNESに代入していますが、こんな事も出来るという一例です。

実行結果------------------------------------------------
販売開始年度=1985
ゲーム機の名前=ファミリーコンピューター
メモリの容量=2048
CPUの名前6502
販売開始年度=1990
ゲーム機の名前=スーパーファミコン
メモリの容量=-1
CPUの名前65816
----------------------------------------------------------



ビット演算もファミコンみたいなゲーム作るなら必須です。

ビット演算の演算子は、<<,>>,|,&,!,^,といった感じです。
ビット演算は次のような感じです。
int num=64;
num>>=1;//右シフトなので2^-1倍、2で割ったのと同じ
num=48;
num<<=3;//左シフトなので2^3倍、8倍したのと同じ=384と同じ

&演算子は、2の累乗の割り算の余りを求めるのに使われます。
他にも&演算子はある特定のビットを0にするのにもつかわれます。
例えば、
A=2;
B=A & 1;
のように、2の累乗の割り算の余りを求める際に
2の累乗の-1した値を&します。
これで、高速に値を求める事が出来ます。

|は、OR演算子と呼ばれていて、変数の2進数で考えた場合、
ある特定のビットを1にするのに使われます。

例えば、とある1桁の数値をASCIIで文字にしたい
場合、
if(num>=0 && num<=9){
        num|=0x30;
}
となりますが、
3桁の場合、
#include<stdio.h>
void main()
{
    char B[4]="532";
    char i=0;
    while(i<3)
    {
        printf("%c",B[i]|0x30);
        i++;
    }
}

^演算子はXORです。
XORはビットを反転させるのに使われます。
例えばこんな処理です。XORを使うなら特許料を
払え!!という無茶苦茶な儲け方をした会社があったそうです。

static int n=0;
n^=1;

#include<stdio.h>

void main()
{
    char n=0;
    char i=0;

    while(i<10)
    {
        printf("%d,",n^=1);
        i++;
    }
}

これの特許はもうさすがに過ぎていますが、
これを回避する方法として
さきほどの&演算子が使えます。

static int n=0;
n=(n+1)&1;

これも同様に動きます。
#include<stdio.h>

void main()
{
    char n=0;
    char i=0;

    while(i<10)
    {
        n=(n+1)&1;
        printf("%d,",n);
        i++;
    }
}
実行結果--------------------------
1,0,1,0,1,0,1,0,1,0,
-----------------------------------
ビット演算するうえで2進数で表示する方法無いの~!?
って20年位前の自分ならそう言ってたかもしれないですが、
2進数で指定する方法と2進数を0と1で表示する方法は似ているようで
少し違います。

#include<stdio.h>
#include<math.h>

int strlen2(char *str)
{
    int i=0;
    for(;*str;){
        str++;
        i++;
    }
    return i;
}

int pow2(int p,int jo)
{
    int i;
    int n=1;
    for(i=1;i<=jo;i++)
    {
        n*=p;
    }
    return n;
}
int _0b(char *str)
{
    int num=0;
    int size=strlen2(str)-1;
    for(int i=size;i>=0;i--)
    {
        if(str[i]!='0' && str[i]!='1') return -1;
        num+=pow2(2,size-i)*(str[i]=='0'?0:1);
    }
    return num;
}
void ShowBin(int num)
{
    int i;
    int tbl[100];
    printf("DEC:(%d) ",num);
    for(i=0;i<8;i++)
    {
        tbl[i]=num&1;//amari
        num>>=1;//div 2
    }
    printf("(");
    for(i=7;i>=0;i--)
    {
        printf("%d",tbl[i]);//7 to 0--
    }
    puts(")");
    //printf("\n");
}
void main()
{
    unsigned char n=0;
    char i=0;
    int size=0;
    n=_0b("10100000");
    ShowBin(n);
}

これをコンパイルして実行すると、
実行結果-----------------------------------
DEC:(160) (10100000)
--------------------------------------------
となります。

で、この2進数の表示で何が出来るのかというと、
ビット演算の結果がテキストベースで分かりやすい
ということです。

次の章ではビット演算についてみていきます。

ビット演算を行う方法はわかりました。

では、ヒントをあげるので問題にチャレンジしてください。

1.ビット演算で00110011をビットをすべて反転してください。
ソースはすぐ前の章のやつを流用していいので.
hint.xor 255

2.ビット演算で00101010を10進数で表示した際に一度代入されて
表示されたのに、符号を反転させてください。
hint.xor,+1,signed char

3.ビット演算で右側から0bit目、1bit目と数えるばあい、"11111111"
0bit目を0にしてください。
hint.and ,0xFE

4.ビット演算で右側から0bit目、1bit目と数えるばあい、"01011100"
の0ビット目を1にしてください。
hint.or,0x01








1.の回答です。ソースを全部上げていたらきりがないのでmain関数内のみです。

void main()
{
    unsigned char n=0;
    char i=0;
    int size=0;
    n=_0b("00110011");
    ShowBin(n);
    n^=0xFF;
    ShowBin(n);
}
実行結果-----------------------
DEC:(51) (00110011)
DEC:(204) (11001100)
--------------------------------

2.の回答です。
void main()
{
    signed char n=0;


    n=_0b("00101010");
    ShowBin(n);
    n^=0xFF;
    n+=1;
    ShowBin(n);
}
実行結果-------------------------
DEC:(42) (00101010)
DEC:(-42) (11010110)
----------------------------------

3.の回答です。
void main()
{
    unsigned char n=0;
    char i=0;
    int size=0;
    n=_0b("11111111");
    ShowBin(n);
    n&=0xFE;
    ShowBin(n);
}
DEC:(255) (11111111)
DEC:(254) (11111110)

4.の回答です。
void main()
{
    unsigned char n=0;
    char i=0;
    int size=0;

    n=_0b("01011100");
    ShowBin(n);
    n|=0x01;
    ShowBin(n);
}
DEC:(92) (01011100)
DEC:(93) (01011101)

以上で基礎編は終わりです。
次回以降はファイル処理とゲーム作りに移ります。

以上、NesMania1985でした。

サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す