ホーム » 「C言語」タグがついた投稿

タグアーカイブ: C言語

2021年10月
 12
3456789
10111213141516
17181920212223
24252627282930
31  

最新の投稿(電子情報)

アーカイブ

カテゴリー

C言語での入出力処理のおさらい

テストのプログラム作成の問題で、入力処理の書き方が適切でないものが多いので、基本とテクニックの解説。

scanf()の使い方

// scanf( "フォーマット" , 引数... ) ;
// データの型とフォーマット
// int       %d (10進数として入力) - Digit
//           %o (8進数として入力)  - Octal digit
//           %x (16進数として入力) - heXa-decimal digit
//        - short int %hd - half-size digit
//        - long  int %ld - long-size digit
//
// float     %f
// double    %lf - long-size float
//
// char[]    %s
// char      %c

// 基本 scanf() はポインタ渡し。文字列(char配列)は配列の先頭アドレスを渡す
int x ;
scanf( "%d" , &x ) ;  // ポインタを渡し、xの場所に書き込んでもらう。
char str[ 10 ] ;
scanf( "%s" , str ) ; // strは配列の先頭の場所。 & は不要。

通常のscanfの%d,%sなどは、データ区切りは空白や改行となる。
%s で “tohru saitoh”といった空白の入った文字列を入力するには、一工夫が必要。

printf()の使い方

// printf( "フォーマット" , 引数... ) ;
// データの型とフォーマット
// 基本は、scanf() と同じ。
// float 型の出力
//       %f 固定小数点表示 1.23 のような出力
//       %e 指数表示 1.23e+10 のような出力
//       %g 固定小数点%fと指数表示%eのどちらか
// 桁数指定
//       %5d   - 必ず5桁で出力         "  123"
//       %05d  - 5桁の空白部は0で埋める "00123"
//       %5.2f - 全体5桁、小数点以下2桁 " 1.23"
//       %5s   - 文字列を5桁で出力     "  abc"
// 左寄せ
//       %-5s  - 文字列を左寄せ5桁で出力"abc  "

scanf(),printf()は成功した項目数を返す

scanf(),printf() は、返り値を使わないように思うけど、実際は入力に成功した項目件数、出力に成功した項目件数を返す。

int x ;
char str[ 10 ] ;
int ans ;

ans = scanf( "%d%s" , &x , str ) ;
printf( "%d¥n" , ans ) ; // 入力に成功すれば2。
ans = printf( "%d%s¥n" , x , str ) ;
printf( "%d¥n" , ans ) ; // 出力に成功すれば2。

特に、ファイルからの入力であれば、途中でデータがなくなって、これ以上データが入力できない時には、scanfの返り値を用いる。

char name[ 100 ] ;
int  age ;
while( scanf( "%s%d" , name , &age ) == 2 ) {
   printf( "%s %d¥n" , name , age ) ;
}

Microsoft の scanf_s() は安全な入力関数

C言語の標準関数 scanf() の %s では、バッファオーバーフローを検出できない。Microsoft の Visual Studio の C言語では、こういった危険な scanf(“%s”) は危険なので、scanf_s() を使うようになっている。ただし、他のC言語では使えない場合が多いので要注意。

char name[ 10 ] ;
scanf( "%s" , name ) ; // バッファオーバーフロー

scanf_s( "%s" , name , sizeof( name ) ) ;
// バッファオーバーフローの心配がない

fget()とsscanf()を使った安全な入力

C言語では、1行のデータを読む場合には、gets() 関数がある。しかし、配列サイズを指定できないので、バッファオーバーフローの危険がある。一方、ファイルからの入力関数 fgets() は、入力するデータサイズを指定できるため、fgets を使うべき。

char buff[ 1000 ] ;
gets( buff ) ; // バッファオーバーフローの危険性

fgets( buff , sizeof( buff ) , stdin ) ;
// 入力に失敗すると NULLを返す

一方、入力した文字列のデータから、scanf() のようにデータを抽出する、sscanf() という関数がある。

char  string[] = "123 tohru 1.23" ;
int   x ;
char  str[ 10 ] ;
double y ;

sscanf( string , "%d%s%lf" , &x , str , &y ) ;
// x = 123 , str = "tohru" , y = 1.23 
// scanfと同様に成功した項目数3が返る。

これらを踏まえ、1行に名前と年齢のデータが記録されていて、データが入力できるだけ繰り返す処理を fgets + sscanf で書くと以下のようになる。

char buff[ 1000 ] ;
char name[ 1000 ] ;
int  age ;

while( fgets( buff , sizeof( buff ) , stdin ) != NULL ) {
   if ( sscanf( buff , "%s%d" , name , &age ) == 2 ) {
      // buffには必ず1000文字以下なので、nameが1000文字を超えることはない
      printf( "%s %d¥n" , name , age ) ;
   }
}

なお、このプログラムを動かす場合、これ以上データがない場合には、Windowsであれば “Ctrl-Z” を入力。unixやmacOSであれば”Ctrl-D”を入力すること。

その他の気づいた点

文末の「;」を忘れないで

JavaScript では、単純式の末尾の「;」は、忘れてもそれなりに正しく動く。しかし、C言語では「;」を忘れると、文法エラーになるので要注意。

1: struct A {
2:    :
3: } ;          ←この行のセミコロンを忘れると、5行目で文法エラーの表示となる。
4:
5: for( ... ) { ←この行を見ても文法エラーの原因はわからない。3行目が原因。
6:    :
7: }

局所変数やヒープメモリにはゴミデータが入っている

void foo() {
   int sum ;  ←初期化忘れ
   int array[ 3 ] = { 11 , 22 , 33 } ;
   for( int i = 0 ; i < 3 ; i++ ) {
      sum += array[ i ] ;
   }
}

局所変数やヒープメモリは、関数に入って確保やmallocで確保されるメモリ領域だけど、このメモリ領域には以前の処理で使われていたデータが残っている場合がある。こういった初期化されていないメモリ領域は、悪意を持ったプログラムがデータを盗むために使われる場合もある。必要に応じてちゃんと初期化が必要。

また、大域変数(グローバル変数)は、C言語では 0 で初期化される。(ポインタなら NULL が入っている。)

プログラム言語(C言語)の基礎

学際科目の情報制御基礎において、学科間でプログラミングの初歩の理解差があるので、簡単なC言語プログラミングの基礎の説明。

Hello World

“Hello World”と表示するだけのC言語プログラムは以下のようになる。

// コメントの書き方1              // "//"で始まる行は、プログラムの説明(コメント)
/* コメントの書き方2 */           // "/*"から"*/"で囲まれる範囲もコメント
#include <stdio.h>             // #で始まる行はプリプロセッサ行
                               // stdio.h には、入出力関数の説明が書いてある
int main() {                   // 一連の処理の塊を関数と呼ぶ。
                               // C言語では main() 関数を最初に実行する。
   printf( "Hello World\n" ) ; // printf() は、以下の物を表示する関数。
                               // "\n"は、文字を出力して改行するための特殊文字
   return 0 ;                  // main() 関数が、正常終了したことを意味する
}                              // 0 を返り値として返す。

“#include <…>”のプリプロセッサ行は、最初のうちは解りにくいので、「これを書かないとダメ…」と思っていればいい。

#include <stdio.h> は、別ファイル(ヘッダファイル) stdio.h に記載されているプログラムリストを読み込む機能。
stdio.h には、printf() や scanf() などの基本的な関数や定数などの情報が記載されている。

C言語の基本的な命令(文)は、”;”で終わる。(単文)
複数の処理をまとめる場合には、”{“から”}”の中に、複数の文を書き並べる。(複文)

関数とは、複数の処理をひとまとめにした、処理の「かたまり」と思えばいい。

関数の型 関数名( 仮引数 ... ) {
   処理1 ... ;
   処理2 ... ;
}

printf() の 文字列中の”\n”(あるいは”¥n”)は、改行を意味する。
「\:バックスラッシュ」は、日本語環境では「¥:円記号」で入力・表示することが多い。

変数と代入

#include <stdio.h>
#include <math.h>            // 数学関数を使う 平方根 sqrt() を使っている
int main() {
   // 変数の宣言
   int    i ;                // 符号付き32bit変数 i の宣言
   int    a = 123 , j ;      // a を 123 で初期化 , j も整数型
   float  x ;                // 単精度実数の x を宣言
   double y = 1.234 , z ;    // 倍精度実数の y を宣言し 1.234 で初期化,
                             // z も倍精度実数
   // 変数への代入
   i = 1 ;                   // i に 1 を代入
   i = 12 + 2 * a ;          // 12+2*a を代入 a は123なので、
                             //   iには、258 が入る。
   x = sqrt( 2.0 ) ;         // x に 2.0 の平方根(1.4142)を代入
   z = y * 2.0 + x * 3.0 ;   // y*2+x*3をzに代入

   // 変数の内容の表示
   printf( "%d\n" , i ) ;    // 整数型(%d)で、    i の値を表示
   printf( "%f\n" , x ) ;    // 単精度実数(%f) で、x の値を表示
   printf( "%lf\n" , z ) ;   // 倍精度実数(%lf)で、z の値を表示

   printf( "iの値は%d,xの値は%lfです。\n" , i , x ) ;

   return 0 ;                // 正常終了 0 を返す
}

変数(計算結果を格納する入れ物)を使う場合は、変数を宣言する。
変数名には、何が入っているのか理解しやすいように、名前をつければいい。(英字で始まり、英数字が続くもの,_が入ってもいい)

変数に値を記憶する時は、”変数名=式 ;”の様に書くと、代入演算子”=” の右辺を計算し、その計算結果が左辺の変数に保存される。

変数の内容を表示する時には、printf() の文字列の中に、%d,%f,%lf などの表示したい式の型に応じたものを書いておく。
式の値が、その %.. の部分に書き込まれて、出力される。

繰り返しの制御命令

最も基礎的な繰り返し命令として、for() 文を説明。

#include <stdio.h>
int main() {
   int i ;
   for( i = 1 ; i <= 10 ; i++ ) {     // iを1から10まで変化させる。
      printf( "%d %d\n" , i , i*i ) ; // i と iの二乗を表示
   }
   return 0 ;
}

for文の意味を説明するために、対応するフローチャートを示す。

先のプログラムをフローチャートで示し、その命令の実行順序と、その変数の変化を下図に示す。

練習問題1

簡単なプログラミングの練習として、前回講義の練習問題をC言語で書いてみよう。

  • 電気電子工学科,電子情報工学科の学生は、出席番号が奇数は処理C,偶数は処理Dについて回答せよ。
  • それ以外の学科の学生は、出席番号が奇数は処理A,偶数は処理Bの結果について回答せよ。

制御構文とフローチャート

構文の入れ子

文と複文

C言語の文法で、{,} は複数の処理をまとめる複文とよばれる。

これに対して、a = 123 ; といったセミコロンで終わる「処理 ;」は単文という。

制御構文は、「if ( 条件) 文」で文となる。このため、文が単文であれば、{,} は不要である。

if ( 条件 ) {
   a = 123 ;
}
if ( 条件 )
   a = 123 ; // 単文なら中括弧は不要

同じように、「while(条件) 文」、「for(A,B,C) 文」、「do 文 while(条件) ;」も、それぞれ文を構成する。
{,} の複文は、{ 文 文 文… } のように、一連の文を実行し、それを1つの文として扱うための機能である。

練習問題2

プログラムの制御構造の確認として、以下の3つ(No.1,No.2,No.3)の問題から、
M科,C科,B科の学生は((自分の出席番号+1) % 2)+1 の問題、E科,EI科の学生は、((自分の出席番号+1) % 3)+1について、プログラムのフローチャートを描き、その処理がどのように進むのか答えよ。

レポートには、以下の点を記載すること。

  • フローチャート
  • 実行順序
  • 変数の変化がわかる内容
  • (できれば、実際にプログラムを動かし、正しいことを検証)
// No.1
#include <stdio.h>
int main() {
   int i , j ;
   for( i = 1 ; i <= 4 ; i++ ) {
      if ( i % 2 == 0 ) {  // i%2 は2で割った余り,i%2==0ならば偶数のとき
         for( j = 1 ; j <= 2 ; j++ )
            printf( "%d %d\n" , i , j ) ;
      }
   }
   return 0 ;
}
// No.2
#include <stdio.h>
int main() {
   int x = 10 , y = 7 , s = 0 ;
   while( x > 0 ) {
      if ( x % 2 != 0 )
         s = s + y ;
      y = y * 2 ;
      x = x / 2 ;  // 注意: xは整数型      
   }
   printf( "%d\n" , s ) ;
   return 0 ;
}

// No.3
#include <stdio.h>
int a[ 6 ] = { 2 , 3 , 5 , 8 , 13 , 21 } ; 
int main() {
   int left = 0 , right = 6 , mid ;
   int key = 13 ;
   while( right - left > 0 ) {
      mid = (left + right) / 2 ; // 整数型で計算
      printf( "%d\n" , a[ mid ] ) ;
      if ( a[ mid ] == key )
         break ;
      else if ( a[ mid ] > key )
         right = mid ;
      else
         left = mid + 1 ;
   }
   return 0 ;
}

 

 

C言語の基礎(part1)

学際科目の情報制御基礎において、学科間でプログラミングの初歩の理解差があるので、簡単なC言語プログラミングの基礎の説明。

Hello World

“Hello World”と表示するだけのC言語プログラムは以下のようになる。

// コメントの書き方1              // "//"で始まる行は、プログラムの説明(コメント)
/* コメントの書き方2 */           // "/*"から"*/"で囲まれる範囲もコメント
#include <stdio.h>             // #で始まる行はプリプロセッサ行
                               // stdio.h には、入出力関数の説明が書いてある
int main() {                   // 一連の処理の塊を関数と呼ぶ。
                               // C言語では main() 関数を最初に実行する。
   printf( "Hello World\n" ) ; // printf() は、以下の物を表示する関数。
                               // "\n"は、文字を出力して改行するための特殊文字
   return 0 ;                  // main() 関数が、正常終了したことを意味する
}                              // 0 を返り値として返す。

“#include <>”のプリプロセッサ行は、最初のうちは解りにくいので、これを書かないとダメ…と思っていればいい。

#include <stdio.h> は、別ファイル(ヘッダファイル) stdio.h に記載されているプログラムを読み込む機能。
stdio.h には、printf() とか scanf() とかの関数や定数などの情報が記載されている。

C言語の基本的な命令(文)は、”;”で終わる。(単文)
複数の処理をまとめる場合には、”{“から”}”の中に、複数の文を書き並べる。(複文)

関数とは、複数の処理をひとまとめにした、処理の「かたまり」と思えばいい。

関数の型 関数名( 仮引数 ... ) {
   処理1 ... ;
   処理2 ... ;
}

printf() の 文字列中の”\n”(あるいは”¥n”)は、改行を意味する。
「\:バックスラッシュ」は、日本語環境では「¥:円記号」で入力・表示することが多い。

変数と代入

#include <stdio.h>
#include <math.h>            // 数学関数を使う 平方根 sqrt() を使っている
int main() {
   // 変数の宣言
   int    i ;                // 符号付き32bit変数 i の宣言
   int    a = 123 , j ;      // a を 123 で初期化 , j も整数型
   float  x ;                // 単精度実数の x を宣言
   double y = 1.234 , z ;    // 倍精度実数の y を宣言し 1.234 で初期化,
                             // z も倍精度実数
   // 変数への代入
   i = 1 ;                   // i に 1 を代入
   i = 12 + 2 * a ;          // 12+2*a を代入 a は123なので、
                             //   iには、258 が入る。
   x = sqrt( 2.0 ) ;         // x に 2.0 の平方根(1.4142)を代入
   z = y * 2.0 + x * 3.0 ;   // y*2+x*3をzに代入

   // 変数の内容の表示
   printf( "%d\n" , i ) ;    // 整数型(%d)で、    i の値を表示
   printf( "%f\n" , x ) ;    // 単精度実数(%f) で、x の値を表示
   printf( "%lf\n" , z ) ;   // 倍精度実数(%lf)で、z の値を表示

   printf( "iの値は%d,xの値は%lfです。\n" , i , x ) ;

   return 0 ;                // 正常終了 0 を返す
}

変数(計算結果を格納する入れ物)を使う場合は、変数を宣言する。
変数名には、何が入っているのか理解しやすいように、名前をつければいい。(英字で始まり、英数字が続くもの,_が入ってもいい)

変数に値を記憶する時は、”変数名=式 ;”の様に書くと、代入演算子”=” の右辺を計算し、その計算結果が左辺の変数に保存される。

変数の内容を表示する時には、printf() の文字列の中に、%d,%f,%lf などの表示したい式の型に応じたものを書いておく。
式の値が、その %.. の部分に書き込まれて、出力される。

繰り返しの制御命令

最も基礎的な繰り返し命令として、for() 文を説明。

#include <stdio.h>
int main() {
   int i ;
   for( i = 1 ; i <= 10 ; i++ ) {     // iを1から10まで変化させる。
      printf( "%d %d\n" , i , i*i ) ; // i と iの二乗を表示
   }
   return 0 ;
}

for文の意味を説明するために、対応するフローチャートを示す。

先のプログラムをフローチャートで示し、その命令の実行順序と、その変数の変化を下図に示す。

理解度確認

以下のプログラムの実行順序と、最終結果で表示される内容を答えよ。

 

局所変数配列をreturn

情報構造論の前期期末試験で、局所変数返しの解答が多かったので、メモ。

JavaScript でプログラムを書いていると、動くネタだけど、C言語では初心者がよく間違って書いてしまう定番であり、それなりに動いたりするから、誤解が増えてしまう可能性もある。

スタック変数返し

char* foo() {
   char str[ 20 ] ;
   strcpy( str , "Hello World" ) ;
   return str ;
}
char* bar() {
   char baz[ 20 ] ;
   memset( baz , 'A' , sizeof( baz ) ) ;
   return NULL ;
}
void main() {
   char* ans = foo() ;
   printf( "%s¥n" , ans ) ; // Hello World
   // 一見正しく動いているように思うかも。

   bar() ; // 局所変数を触るだけの処理
   printf( "%s¥n" , ans ) ; // AAAAAAAAAAA
   // ans の中身が壊れている。
}

静的局所変数返し

上記のスタック変数を返す問題点を示すと、じゃあ静的局所変数でいいじゃん….という話がでてくる。

char* foo() {
   static char str[20] ;
   strcpy( str , "Hello World" ) ;
   return str ;
}

この方式なら、スタック領域ではなく静的変数領域なので、関数呼び出しで破壊されることはない。
しかし、この方式は「スレッドセーフ」ではない。foo() の処理後に、スレッドを起動した場合、スレッドではメモリ空間が共有されるため、foo() を保持していて、別スレッドがそのメモリを書き換えると、他方は中身が変わってしまう。
(参考「スレッドセーフではないCライブラリ関数」)

実数と整数の変換

情報制御基礎で出題した問題で、5点の移動平均の処理の一部に、以下のコードがあった場合の間違い説明の問題。

int avg() {
   int s = .... ;
   return (1/5) * s ;
}

の間違いを修正せよ….の答えだけど、return 0.2 * s ; とか return s/5 ; が答えだけど、小数点以下が切り捨てになるのか、四捨五入になるのか気になってきた。

for( double x = 0.0 ; x <= 2.0 ; x += 0.1 )
   printf( "%ld %d\n" , x , (int)x ) ;

で確認してみたら、(int)実数は、小数点以下切り捨てだな。となると、四捨五入したいなら、return (int)( 0.2 * s + 0.5 ) ; とか、return (s+2)/5 ; とか書いてほしい…とか微妙な話になるな。

採点は、切り捨て・四捨五入の誤差については問わないで、0.2*s , s/5 を◯でいこう。