初心者向けのc言語練習問題集です。他のプログラム言語の経験が無く、c言語からプログラミングを始める方を対象としています。変数、分岐、繰り返し等、プログラミングの基本的な要素の習得に重点を … マツダ CX-5を、価格.comに集まるこだわり派ユーザーが、エクステリア・インテリア・エンジン性能など気になる項目別に徹底評価!実際のユーザーが書き込む生の声は何にも代えがたい情報源です。 調べて見たらC言語の問題集サイトが意外と少なかったので作ってみました。C言語の勉強・復習・理解度チェック等好きな用途で使用してください。, 一通りC言語を学んだ型向けの問題が多く、基本的にちょっとつまづきやすいような問題や知っておくと役に立つ知識が身につくような応用問題にしています。, 本の問題集と違ってコメントしていただければ分からない点をお答えすることができますのでぜひコメント欄も活用していただければと思います。, の構成で成り立っています。ヒントや解答は初期状態だと見えませんが、クリックすれば中身が見れるようにしています。, 下記プログラムで /* ここから不要 */ から /* ここまで不要 */ の部分が不要になったのでコメントアウトしたのですが、うまくコメントアウトができずコンパイルエラーになってしまいました。, コンパイルエラーになる理由はなんでしょう?どのようにすれば簡単にこの不要部分をコメントアウトできるでしょうか?, 様々な方法があると思いますが、プリプロセッサを利用すれば簡単にコメントアウトすることができます。, コメントは /* から最初の */ 部分となります。ですので、上のソースコードのようにコメントを入れ子にすると途中までしかコメントとして扱われません。, 下記のようにディレクティブ #if を使用すれば「#if 0 〜 #endif」の部分を簡単にコメントアウトすることが可能です。, ディレクティブとはプリプロセッサへの指示です。「#if 条件文」と書けば「条件文」が成立しない場合、「#endif」までの部分がコメントアウトされることになります。, #include なんかもディレクティブです。ディレクティブ・プリプロセッサについては下記で解説していますので、興味があれば読んでみてください。, 1から10の整数の足し算を行うプログラムを作成しました。実行してみたところ、計算結果が55になりません!それどころか実行するたびに結果がバラバラになってしまいます。どのようにプログラムを修正すれば良いでしょうか?, 変数宣言して作成した変数には基本的に不定値が格納されます。そのためプログラム実行ごとに異なる値が格納され、異なる結果となっているというわけです。不定値というのは何が入るか分からない値です。ランダムな値みたいな感じで考えて良いです。, なので、不定値を自分の意図する値で初期化してやればこの問題は解決します。具体的には変数宣言部分を下記のように変更すれば良いです。, 初歩的な問題ですが、実際プログラミングしていると陥りやすい問題です。変数宣言時には必ず初期化を行うクセをつけると良いと思います(私もできてない…)。, どうやらカウントアップが上手くできていないようです。なぜ上手くカウントアップできていないのでしょうか?カウントアップするためにどのようにソースコードを変更すれば良いでしょう?, 関数内で変数宣言された変数は特に指定がない場合は自動変数として作成されます。自動変数ではなく静的変数で count 変数を作成するためには・・・?, ヒントで述べたように count は自動変数として作成されています。自動変数は下記のような特徴を持ちます。, ですので、自動変数を関数内でカウントアップしても、次の関数呼び出し時にはまた count は 0 に初期化されてしまいます。, つまり、カウントアップが行われない理由は count 変数が自動変数であるためです。, 解決方法は count 変数を自動変数ではなく、静的変数として宣言する事です。下記のように count の型に static をつけて変数宣言すれば静的変数として作成できます。, 作成・初期化・削除されるのはプログラム実行からプログラム終了までで一度だけであり、関数実行時に初期化は行われなくなります。, 日にちの入力を受付け、2018年7月だと何曜日になるかを表示するプログラムを作成せよ, ありえないくらいに結果が小さい値になってしまいます。どう考えてもおかしい…。この計算で結果がおかしくなる原因はなんでしょう?どのようにソースコードを変更すればうまく計算できるようになるでしょうか?, int 型変数 * int 型変数の計算結果は int 型になります。int 型の最大値は 2147483647 ですので、この値を超えた場合は桁あふれが発生して 2147483648 引いた数が計算結果となってしまいます。, 解決方法は計算結果が int 型よりも大きい値が扱える型である long long 型になるように、計算に使用する変数をlong long 型にキャストすることです。long long 型の変数 * long log 型の変数の計算結果は long long 型ですので、int 型の最大値を超えたとしても計算結果を正しく出力することが可能です。, C言語だけでなくプログラミングにおいてキャストは非常に重要です。下のページでキャストについて解説していますので、この問題が分からなかった型やもっとキャストについて知りたい方は是非読んでみてください。, 負の値まで正の値と判定されているようです。この理由はなんでしょう?うまく負の値を判定するためにはどのようにすれば変更すれば良いでしょうか?, int 型変数と unsigned int 変数の比較は、両方 unsigned int 型にキャストされた後に比較されます。, int 型変数と unsigned int 型変数で比較を行った場合、int 型変数は unsigned int 型に変換された後に比較が行われます。つまり int 型変数は全て正の値に変換されるのです。ですので問題文の条件分岐の判定は必ず成立するため、必ず正の値として判断されてしまいます。, これを防ぐためには unsigned int 型の変数に対して明示的な型変換(キャスト)を行えば良いです。int 型同士の比較であれば暗黙の型変換が行われないため、意図した通りの比較を行うことが可能です。, 先ほどの問題同様にこちらもキャストが大きく関わる問題です。プログラミングにおいてキャストは非常に重要な概念になります。キャストに関しては下のページで解説していますのでしっかり理解しておきましょう!, 入力された2つの整数 x , y の x / y の計算結果を小数点以下で切り捨て・切り上げ・四捨五入するプログラムを作成してください, 切り捨て切り上げ四捨五入する関数は存在します。ですが、今回はその関数は使用せずに自力で計算してください。, ここも実はキャストが深く関わってきます。キャストを利用した計算を身につけましょう。, 下記により1つ目の式で切り捨て、2つ目の式で切り上げ、3つ目の式で切り上げが行われます。, なぜこれで切り捨て切り上げ四捨五入ができるかは下のページで解説していますので、問題が分からなかった方は是非読んでみてください。, char 型で扱える値の最大値は 127 です。これは覚えている人も多いでしょう。では int 型の最大値はいくつでしょう?計算するプログラムを作成してください。, OSや開発環境によっては int 型の最大値は「limits.h」のファイルで「INT_MAX」として定義されています。が、今回はこれを使わずに自分で計算してみましょう。, 型のサイズ、サイズの取得の仕方、ビットシフト演算について理解していただくことが狙いです。, 符号付きの整数型では最上位ビットが符号部になっており、そのほかが値を表現するビットになっています。, また、型のサイズは下記のように型名を引数として sizeof 関数を実行することで取得することが可能です。ただし取得されるサイズの単位はバイトです。, 1 を符号部以外のビット数分だけ左にシフトすれば最上位ビットのみが 1 のデータが作成できます。, これから 1 を引いてやれば、符号部以外のビットが全て 1 となり、これがその型の最大値となります。, 用意した二次元配列を時計回りに90度、180度、270度それぞれの角度で回転させた結果を出力してください, 例えば90度回転して表示する場合は配列にアクセスする順序が下の矢印の順になります。, 下記のような行数3、列数6の二次元データに対し、指定された座標(x, y)の値を取得して表示するプログラムを作成することを考えます。, 二次元データを二次元配列に格納した場合は、プログラムのソースコードは下記のようになります。, 二次元データを下記のように一次元配列に格納した場合に、同様に指定された座標(x, y)の値を表示するプログラムを作成してください。, 二次元データの列数がYであるとき、二次元座標の(x, y)を一次元座標で考えると(y * M + x)にマッピングできます, 問題文で示した一次元の配列を変数宣言した場合、printf 関数部分を下記のように書き換えれば2次元配列の時と同様の動きをさせることが可能です。, このように1次元配列でも2次元データを扱うことが可能です。ですので2次元データを扱うからといって必ず2次元配列を使用しなければいけないということもありません。個人的には2次元配列嫌いなので、このやり方で2次元データを1次元配列で扱うことが多いです。, 基本的にC言語で文字列を扱う関数は、文字列の最後にヌル文字( ‘\0’ )が格納されていることを期待しています。ヌル文字までを有効な文字列と考えて処理します。例えば printf 関数だと、ヌル文字までの文字列を出力する作りになっています。, また文字列を作成したり変更するような関数(例えば strcpy や sprintf などなど)は文字列の最後にヌル文字を格納するようになっています。, これは上記の文字列を格納する変数の初期化においても同じで、文字列の最後にヌル文字が格納されます。, ですが、上記のコードだと11文字分の配列に対して11文字を格納する初期化を行なっているため、ヌル文字を格納する事ができません。, ヌル文字がないと、文字列を扱う関数(上記のソースコードの場合は printf 関数)が文字列の終端が分からないため、余分に文字列を出力してしまう可能性があります。, ですので、文字列を格納する配列を作成する場合は、配列の大きさを「文字列の長さ+1文字分」の大きさにしてやる必要があります。, 下記のように変数宣言を書き換えれば、必ず初期化した文字列分のみが出力されるようになります。, 入力された日本語文字列から「特定の日本語の文字」が含まれているかを判断するプログラムを作成してください, malloc 関数によって動的に確保したメモリは、メモリリークを防ぐために使い終わった後は free 関数で解放することが必要です。, さらに二重 free や解放済みのメモリにアクセスしないためには、free で解放したアドレスを指していたポインタには、free 関数直後に NULL を指させること(NULLを代入すること)が重要です。, この辺りの話は下のページで解説していますので ポインタや free についてさらに詳しく知りたい方は読んでみてください。, とりあえず言いたいのは free 関数直後に NULL 代入することが重要だと言うことです。, free 関数後に NULL 代入することが大事なのは理解したとして、この NULL 代入することをつい忘れてしまう人も多いと思います。, つまり free と NULL 代入を行う関数です。この safe_free 関数を使うことによりメモリ解放後に、そのアドレスを指していたポインタには必ず NULL が代入されます。素晴らしい!, 早速下記のようにプログラムを作成し、safe_free 関数実行前後のポインタが指すアドレスを表示してみました。, 実行後のアドレスが 0x0 になっていれば成功なのですが、なぜか safe_free 関数実行前後でアドレスが変わっていません!!, 実行前後で表示されるアドレスが変わらない理由はなんでしょうか?作ろうとしていた safe_free 関数を実現するためにはどうすれば良いでしょうか?, 関数に引数として渡される変数は、関数呼び出し元の変数とは別のものです。ただし変数に格納されている値はコピーされています。違う箱に同じ値が格納されるイメージです。つまりメイン関数の変数 pointer と safe_free 関数の変数 p は別の変数だけど同じアドレスを指す事になります。, ですので、 p には free すべきアドレスがコピーされてきており、free 関数での解放処理は意図通りに行われています。, しかし safe_free 関数に渡された p に NULL を代入しても、p は単なるコピーの変数ですので、関数呼び出し元の変数である pointer には何の影響もありません。, 注目すべき点は、関数に渡されたアドレスの先は変更することが可能ということです(この場合は渡されたアドレスの先を解放している)。, つまり、pointer に格納されている値(ポインタの指す先、アドレス)を変更するためには、pointer 変数が存在するアドレスを safe_free 関数に引数として渡せば良いということです。, pointer はポインタですので、pointer の存在するアドレスを引数として指定するためには…?, 下記のソースコードであれば前述した safe_free 関数を実現することが可能です。, 結果も safe_free 関数実行直後に変数 pointer の指すアドレスが 0x0 になっていることが確認できます。, ポイントはポインタ変数である pointer が存在するアドレスを safe_free 関数に指定して関数を実行することです。これにより pointer が存在するアドレス(仮にA番地としましょう)を safe_freeに渡すことができます。, ポインタ変数のアドレスが渡されてくるので、safe_free の引数 p の型はダブルポインタ(ポインタのポインタ)にする必要があります。, p はダブルポインタですので、ポインタ変数が存在するアドレスを格納することが可能です(まさにポインタのポインタ)。, したがって *p を free 関数で解放してやれば、pointer の指すアドレスの領域を解放することができます。, さらに *p に NULL を代入すれば、*p の指すアドレス(A番地)にある変数 pointer に NULL が代入される事になります。, 注目して欲しいのはダブルポインタを使えば関数呼び出し元のポインタに格納される値を関数側で変更できる点ですね。関数内で呼び出し元のポインタを変更したい場合は、ダブルポインタを使うと便利です。, 説明がよく分からなかった方は、下記でダブルポインタについて解説していますのでこちらも参考にしてみてください。, ポインタ head がリストの先頭を指し、list 構造体の next メンバが次のリスト構造体を指すことで各データが繋がっています。, ではここからが問題です。上のソースコードではリストの削除(malloc でメモリ確保したものの解放)が行われていません。リストを削除するソースコードを追加してください。, 先頭のデータから順に削除してください。また削除されたことが確認できるように、free関数で解放する前に、解放しようとしているデータの number メンバの値を表示するようにしてください。, head はリストの先頭のデータを指しています。head に次のデータを指すためには下記を実行すれば良いです。, ここまで聞くとリストを先頭から辿りながらデータを解放していくコードで最初に思い浮かぶのは下記ではないでしょうか。, しかし、これをやると先頭のデータは解放できますが、他のデータが解放できません。というか4行目でNULLアクセスをして落ちる可能性も高いです。, 結論的には、この問題の場合、データを指すポインタは head の1つだけではリストのデータ全てを解放することは無理です。もう一つポインタを用いて上手く解放させる方法を考えてみましょう。, node に削除するデータのアドレスを退避するところがポイントです。これにより head に次のデータを指させても、node が削除するデータを指していますので、削除すべきデータのアドレスを見失わずに解放することが可能になります。, この問題ではリストの削除のみに関するものになりますが、リストの全体像やプログラムについては下記のページで解説しています。詳しく知りたい方は是非読んでみてください。, calc_sum 関数は、1 から n までの整数の和を「1 から n – 1 までの整数の和 + n」の計算により求める関数です。1 から n – 1 までの整数の和は calc_sum 関数により求めますので再帰呼び出しになっています。, 引数に 10 を指定して calc_sum 関数が実行されると、次に引数 9 を指定して calc_sum 関数が実行されます。さらにその中で引数 8 を指定して calc_sum 関数が実行され、これが引数 1 になるまで繰り返し行われることになります。ここまで計算処理は行われません。, 引数 1 で実行した場合は、1 から 1 までの整数の和を求めるので、単純に 1 をリターンします。そうすると、引数 2 で実行した calc_sum 関数に戻り、そこでそれに対して 2 を足した結果をリターンし(つまり 1 から 2 までの整数の和)、引数 3 で実行した calc_sum に戻ります。この calc_num では calc_num(2) の戻り値に 3 を足した結果(つまり 1 から 3 までの整数の和)をリターンします。, これを最後まで繰り返すことにより 1 から 10 までの整数の和を求めることができます。, ポイントは 再帰呼び出しをストップするタイミングですね。この問題であれば引数 n が 1 の時に、それ以上再帰呼び出しをする必要がないということで再帰呼び出しは行わずに関数を終了しています。, 再帰呼び出しを行う関数は以下の3つ処理に分けられます。再帰再帰呼び出しなので、呼び出す関数は自分自身の関数ですね。, ポイントは先に実行されれば「関数呼び出し前の処理」は早く実行されるが「関数呼び出し後の処理」は遅く実行されるところですね。関数呼び出し前の処理は再帰呼び出しの折り返し前に行われますが、関数呼び出し後の処理は再帰呼び出しの折り返し後に実行される感じです。, ポイントは print_recursive 関数の中で、print_recursive 関数呼び出し前と print_recursive 関数呼び出し後両方で同じ出力を行なっている点です。同じ出力を同じ関数の中で行なっていますが、表示が行われるタイミングは再帰呼び出しの折り返し前と折り返し後で異なるため、かなり離れて出力されていることが確認できると思います。, また、再帰呼び出しはどこで再帰呼び出しをストップするか(自身の関数の呼び出しを行わないようにするか)も重要なポイントです。このソースコードの場合は下記で再帰呼び出しを続けるかどうかの判断を行なっています。, リストの解放の問題をリストの解放1で出題しましたが、その問題ではリストの先頭からデータの解放を行うことを条件としていました。, 今回はその逆で、リストの最後尾からデータの解放を行うプログラムを作成してください。, 最後尾のデータから順に削除してください。また削除されたことが確認できるように、free 関数で解放する前に、解放しようとしているデータの number メンバの値を表示するようにしてください。, 下記の関数を引数にリストの先頭へのポインタを指定して呼び出すことでリストを最後尾から順に削除することができます。, deleteList 関数の中で deleteList 関数に node->next を指定して実行することで再帰呼び出しを行なっており、これによりリストの最後尾(node->next == NULL となる node)までたどることができます。, そこで再帰呼び出しがストップするので、そこからは最後尾から順にデータを free して関数終了する処理がリストの先頭まで実行されます。, 画像ファイルを読み込み、その画像を90度回転させたものを画像ファイルとして保存するプログラムを作成してください, lib Jpegを用いてJPEGファイルを扱う方法を下記で説明していますのでこちらもご参考に。, だえうホームページのプライバシーポリシー・免責事項についてはこちらに記載しております。.