目次
0 はじめに
誰もが知っているように、ポインタは C 言語の魂であるため、この本 ( 「C とポインタ」 ) では、ポインタに関連するトピックにさらに多くの労力を費やします。この章では、ポインタの応用例をさらに詳しく見て、それらについて詳しく説明します。 C言語ならではの魅力。
この号の内容の概要:
1 ポインターへのポインターのさらなる調査
ポインタを学べば、ポインタ対ポインタ、つまり、現在のポインタが指すアドレスにポインタが存在し、そのポインタが指すアドレスにアクセスする必要がある値が保持されている、ということを理解することは難しくありません。
簡単な例でそれを確認できます。
#include <stdio.h>
int main()
{
int i = 10;
int *pi = &i;
int **ppi = π
printf("i = %d\n",i);
printf("*pi = %d\n", *pi);
printf("**ppi = %d\n", **ppi);
}
実行して出力を印刷します。
結果が私たちの期待と一致していることがわかります。
2 高度なステートメント
この本には、高度なポインター宣言の問題がいくつかリストされていますが、特定の宣言方法が本の残りの部分にリストされているため、すべてを理解したい場合以外は、本の残りの部分を読むだけで済みます。この部分にはさらに詳しい説明があります。
3 関数ポインタ
関数ポインタは、その名前が示すように、関数へのポインタであり、本質的には通常のポインタと大きな違いはなく、形式がいくつか異なるだけで、使い方がより興味深いものになります。
3.1 コールバック関数
平たく言えば、コールバック関数は関数ポインタをパラメータとして他の関数に渡し、ユーザーの関数を「コールバック」します。パラメータとして渡された関数が呼び出されます折り返し電話。
本の中にこんな記述があります。
この手法は、異なる時間に異なる種類の作業を実行できる必要がある関数、または関数の呼び出し元のみが定義できる作業を実行できる関数を作成するときにいつでも使用できます。多くのウィンドウ システムでは、コールバック関数を使用して、マウスのドラッグやボタンのクリックなどの複数のアクションを接続し、ユーザーのプログラム内の特定の関数を指定します。
少し複雑に聞こえますが、具体的には、インターフェイス (関数) を設定したものの、必要な特定の部分を事前に予測できない場合、この時点で、特定のタスクを実行するために後続の関数をコールバックする必要があります。つまり、具体的な実装は、特定の状況に基づいて検討する必要があります。
たとえば、C言語stdlib
ライブラリでは、クイックソートアルゴリズムを実装した関数がカプセル化されておりqsort
、呼び出す際にはソート関数を自分で記述する必要があります。
クイックソートこれは非常に古典的で一般的な並べ替えアルゴリズムです。並べ替えアルゴリズムの具体的な実装原理に興味がある場合は、「
トップ 10 古典的な並べ替えアルゴリズム (C 言語で実装)」
に詳細な説明を参照してください。
まず単純な配列のソートを実装しましょう。
int a[] = {
1,3,5,11,2};
qsort(a, 5, sizeof(int), compare);
for (int i = 0; i < 5; i++)
{
printf("a[%d] is %d\n", i, a[i]);
}
比較関数は次のように実装されます。
int compare(const void *a, const void *b)
{
int *p1 = (int *)a, *p2 = (int *)b;
if (*p1 > *p2)
{
return 1;
}
else if (*p1 == *p2)
{
return 0;
}
else if (*p1 < *p2)
{
return -1;
}
}
並べ替え関数がカプセル化されたので、比較関数も作成してみませんか?
これは、並べ替え関数では、比較する必要があるデータ型が整数なのか、浮動小数点型なのか、それとも構造体メンバーなのかを事前に予測できないためです。したがって、並べ替えを実現するには、比較関数を自分で完成させる必要があります。この本で紹介されている例はまさにこんな感じです。もちろん、コールバック関数の応用はそれをはるかに超えています。
3.2 転送テーブル
転送テーブル (または変換テーブル) は関数ポインターの配列です。このようにして、同じ型の関数の呼び出しが非常に簡単になり、関連する関数は配列の添字を通じて直接見つけることができます。
小さな計算機 (加算、減算、乗算、除算の計算を実行できる) を設計する必要があるとします。従来の方法を使用する場合は、次のようなプログラムを作成します。
enum OPER
{
ADD = 1,
SUB,
MUL,
DIV
};
double cal_add(double a, double b)
{
return a + b;
}
double cal_sub(double a, double b)
{
return a - b;
}
double cal_mul(double a, double b)
{
return a * b;
}
double cal_div(double a, double b)
{
return a / b;
}
//使用传统方法
//使用传统方法
double cal_all_1(int oper, double op1, double op2)
{
double result = 0;
switch (oper)
{
case ADD: result = cal_add(op1, op2); break;
case SUB: result = cal_sub(op1, op2); break;
case MUL: result = cal_mul(op1, op2); break;
case DIV: result = cal_div(op1, op2); break;
default:
break;
}
return result;
}
次に、転送テーブルを使用して同じ機能を実装します。
まず次の転送テーブルを定義します
double(*oper_func[])(double, double) = {
cal_add, cal_sub, cal_mul, cal_div };
アルゴリズムの実行関数は次のようになります。
//使用函数指针数组
double cal_all_2(int oper, double op1, double op2)
{
double result = 0;
if (oper <= 4)
result = oper_func[oper - 1](op1, op2);
else
exit();
return result;
}
もちろん、2 番目の方法の方がはるかに簡単で、関連する操作を実行する場合は、配列の添え字にインデックスを付けて、実際のパラメータを渡すだけです。main 関数の記述を見てみましょう。
#include <stdio.h>
#include <stdlib.h>
#include "transfor_table.h"
int main()
{
double res1 = 0,res2 = 0;
res1 = cal_all_1(ADD, 10, 10);
printf("fun_1:10 + 10 = %f\n", res1);
res2 = cal_all_2(ADD, 10, 10);
printf("fun_2:10 + 10 = %f\n", res2);
system("pause");
return 0;
}
次の内容を実行して印刷します。
2 つの実行は同じ結果を生成することがわかりますが、この種のテストは厳密ではなく、それをサポートするにはより多くのテスト ケースが必要です。問題を説明するために、ここでは単純な比較を示します。
注: この本にあるように除算関数 (つまり、div()) を記述しないでください。これは、C 言語ライブラリの関数名と競合し、コンパイル エラーが発生し、名前の変更を求めるプロンプトが表示されます。
4 コマンドラインパラメータ
一部の C プログラムでは、main
この関数は特殊であり、次のような 2 つのパラメーターを受け入れます。
int main(int argc, char **argv)
これら 2 つの仮パラメータは、コマンド ラインから渡されたパラメータを受け入れます。
4.1 コマンドラインパラメータの受け渡し
上記の main 関数では、最初のパラメータはコマンド ライン パラメータの数を表す argc と呼ばれ、2 番目のパラメータは argv と呼ばれ、パラメータ値のセットを指します ( と の省略形であると推定されますargument counter
) argument vector
。
このプログラムのコンパイルと実行は少し面倒なので、具体的な操作方法については、開発者コマンド プロンプトのコマンド ラインを使用して C++ コードをコンパイルおよび実行する VS を参照してください。
たとえば、次のプログラムを作成します。
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
printf("%d\n", argc);
while(*++argv != NULL)
printf("%s\n", *argv);
return EXIT_SUCCESS;
}
4.2 コマンドラインパラメータの処理
上記のプログラムをコンパイルして実行すると、出力は次のようになります:
最初のパラメーターは、入力した文字列パラメーターの数を自動的にカウントし、 と表示されます3
。1 つカウントするには Enter をクリックする必要がありますか? すると、入力した内容が正しく印刷されます。
5 文字列定数
単純な文字列は、その最初の要素へのポインタも表します。これは、配列が配列名であることを除いて、配列と比較できます。まず例を見てみましょう。
void string_basic()
{
printf("%c\n",*("xyz" + 1));
printf("%c\n", "xyz"[2]);
}
出力が最初の要素へのポインタである
場合、それは 1 つの要素のアドレスを後方に移動し、間接的にそれにアクセスして 2 番目の要素を取得することを意味します。"xyz"
+1
y
もちろん、配列と同様に、文字列も添字を介して要素にアクセスできるため、文字を自然に取得できますz
。
さらに、この本では、ミステリー機能、その謎の機能は何なのか、その答えは明かされていません。見に行きましょう。
関数は次のように定義されます。
//参数是一个0~100的值
void mystery(int n)
{
n += 5;
n /= 10;
printf("%s\n", "**********" + 10 - n);
}
最初の 2 つのステートメントは理解しやすく、整数を丸めてから 10 の位の値を求めます。最後のprintf
ステートメントでは文字列定数を理解する必要がありますが、分析後に出力される * 記号の数は、計算されたばかりの 10 の位の値です。実験を行うことができます。
for(int i = 0; i < 10; i++)
mystery(i*10);
次に、出力を印刷します。
10 の整数倍でない場合も、同様の出力を得ることができ、処理方法は同じです。
この本の最後の部分では、バイナリ値を文字列に変換する別の例を追加しています。
void binary_to_ascii(unsigned int value)
{
unsigned int quotient;
quotient = value / 10;
if (quotient != 0)
binary_to_ascii(quotient);
putchar(value % 10 + '0');
}
ここで再帰が使用されていることに注意してください。これは理解するのが難しくありません。main 関数で次のように呼び出されます。
binary_to_ascii(1001100);
印刷結果:
アルゴリズムに問題がないことがわかります。
次に、この本では、特定の 16 進数を文字列に変換する方法が説明されています。
putchar("0123456789ABCDEF"[value % 16]);
同様に、上記の例に従い、16 進数を文字列に変換する関数を作成できます。
void hexadecimal_to_ascii(unsigned int value)
{
unsigned int quotient;
quotient = value / 16;
if (quotient != 0)
hexadecimal_to_ascii(quotient);
putchar("0123456789ABCDEF"[value % 16]);
}
次に、それを main 関数で呼び出します。
hexadecimal_to_ascii(0xF5);
プリントアウト:
ご覧のとおり、正解が得られました。
6 まとめ
コールバック関数と転送テーブルは、その作業プロセスと主要なアイデアを理解するだけでなく、実際の開発でそれらを使用する方法を知ることがさらに重要です。
実行環境がコマンド ライン パラメータを実装している場合、これらのパラメータは 2 つの仮パラメータを通じて main 関数に渡されます。これら 2 つの仮パラメータは通常、argc
sumと呼ばれますargv
。
詳細情報:ポインター関数と関数ポインター