目次
7. 関数ポインターの配列へのポインター (理解のためだけに)
この記事には、関数ポインターの配列、関数ポインターの配列へのポインター、コールバック関数などの知識ポイントの概要が 含まれますが、これらに限定されません。上記ポインタを継承してアドバンスト(1)ナレッジポイントのまとめ、ポータル --> http://t.csdn.cn/mgVGJ
ポインター上級 (3): ポインターおよび配列の筆記試験問題の分析と概要、ポータル --> http://t.csdn.cn/aKVsj
間違い等ありましたらご指摘・修正をお願い致します、ご訪問ありがとうございます!
6.関数ポインタの配列
int * arr [ 10 ];//配列の各要素は int*
次に、関数のアドレスを配列に格納すると、この配列は関数ポインタの配列と呼ばれます。関数ポインタの配列を定義するにはどうすればよいですか?
int ( * parr1 [ 10 ])();//1int * parr2 [ 10 ]();//2int ( * )() parr3 [ 10 ];//3
答えは次のとおりです: parr1 //1
Parr1は最初に [] と結合されており 、parr1が配列であることを示しています。配列の内容は何ですか?int (*)()型の関数ポインタです。--> 変数の名前を削除する場合、それはその型です。
int my_strlen(const char* str)
{
return 0;
}
int main()
{
//指针数组
char* arr[10];
//数组指针
int arr2[5] = { 0 };
int(*p)[5] = &arr2;//p是一个指向数组的指针变量
//函数指针
int (*pf)(const char*) = my_strlen;//pf是一个指向函数的函数指针变量
//函数指针数组-存放函数指针的数组
int (*pfArr[10])(const char*);
//1.pf先与[10]结合,代表这是一个数组,数组有10个元素;
//2.去掉pf[10],剩余int (*)(int,int)为数组每个元素的类型--函数指针类型
return 0;
}
次の 3 つの記述方法はすべて同等です
1. *(*pf)("abcdef");
2. pf ("abcdef");
3. my_strlen("abcdef");
6.1 簡単な計算機
私たちの通常の考え方によれば、整数の加算、減算、乗算、除算の関数を実現する簡単な計算機を作成するには、大まかに次のコードのように記述する必要があります。
次に、それを実行して、どうなるかを確認します。
上記の問題は、0 を入力した後、すぐに「計算機の終了」を出力してプログラムを終了するのではなく、情報を出力する前に 2 つのオペランドを入力し続け、さらに 6 を出力することです。ところで、次の問題があるはずです。このコードを少し改良してみましょう。
//写一个计算器能完成整数的+ - * /
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do {
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Add(x, y);
printf("%d\n", ret);
break;
case 2:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Sub(x, y);
printf("%d\n", ret);
break;
case 3:
printf("请输入两个操作数:>");
scanf("%d %d",&x,&y);
ret = Mul(x, y);
printf("%d\n", ret);
break;
case 4:
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = Div(x, y);
printf("%d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default :
printf("选择错误\n");
break;
}
}while(input);
return 0;
}
コードを実行してテストを続行します。
問題は解決しましたが、新たな問題が見つかりました。
上記のコードには switch case ステートメントが多すぎます。このカウンタに << >> & | ^ && || などの関数を追加すると、case ステートメントがどんどん増えてしまいます。どのように修正すればよいですか? ?
このとき、関数ポインタの配列が使用されます。
例:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
//存放函数指针的数组 - 函数指针数组
int (*pf[4])(int, int) = {Add,Sub,Mul,Div };
// 0 1 2 3
int i = 0;
for (i = 0; i < 4; i++)
{
int ret = pf[i](8, 4);
printf("%d\n", ret);
}
return 0;
}
このコードは、関数ポインターの配列を使用し、4 つの演算関数のアドレスを配列に格納し、ループを通じて配列を走査し、4 つの演算関数を順番に呼び出して計算を実行し、結果を出力します。このアプローチにより、コードの重複が減り、コードの保守性が向上します。
6.2 電卓を実現する関数ポインタ配列
//函数指针数组实现计算器
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
void menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;//选择菜单的功能
int x = 0;
int y = 0;
int ret = 0;//用于接收值
//转移表 - 函数指针的数组
int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
// 0 1 2 3 4
//NULL的作用是为了占用0下标,然后让函数指针数组的元素下标对应上菜单的功能
do {
//菜单
menu();
//进行选择
printf("请选择:>");
scanf("%d", &input);
//用函数指针数组代替switch
if (input == 0)
{
printf("退出计算器\n");
return 0;
}
else if (input >= 1 && input <= 4)
{
printf("请输入两个操作数:>");
scanf("%d %d",&x,&y);
ret = (***pfArr[input])(x, y);//其实比函数指针的用法就多了一个[input]
//跟函数指针一样,调用函数的时候 * 是没有任何意义的
printf("%d\n",ret);
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
関数ポインタの配列は、要素が関数ポインタである配列です。各関数ポインタは特定の関数を指しており、対応する関数は関数ポインタを通じて呼び出すことができます。関数ポインター配列を使用すると、コードの冗長性の問題を解決でき、さまざまな関数を簡単に横断して呼び出すことができるため、コードの構造とロジックが簡素化され、コードの保守性とスケーラビリティが向上します。
関数ポインタの配列の目的:転送テーブル
7.関数ポインターの配列へのポインター (理解のためだけに)
//指向函数指针数组的指针
int Add(int a, int b)
{
return a + b;
}
int Sub(int a, int b)
{
return a - b;
}
int main()
{ //函数指针
int(*pf)(int, int) = Add;
//函数指针数组
int (*pfArr[4])(int, int) = { Add,Sub };
//指向函数指针数组的指针
int (*(*ppfArr)[4])(int,int) = &pfArr;
return 0;
}
関数ポインターの配列へのポインターを理解する方法:
8. コールバック関数
コンセプト
コールバック関数は、関数ポインターを通じて呼び出される関数です。関数ポインター (アドレス) をパラメーターとして別の関数に渡す場合、このポインターがそれが指す関数を呼び出すために使用されるとき、これをコールバック関数と呼びます。コールバック関数は、関数の実装者によって直接呼び出されるのではなく、特定のイベントまたは条件が発生したときに別の当事者によって呼び出され、イベントまたは条件に応答するために使用されます。
同じロジックと冗長なコードを含む部分が多数あります。
現時点では、コールバック関数を使用して問題を解決する必要があります。
8.1 コールバック関数の理解
8.1.1 コールバックを使用した単純な計算機の改善
#include<stdio.h>
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
//将冗余重复部分用Calc函数封装(只用了一个函数),通过这个函数里面传入的函数指针调用计算器函数
void Calc(int(*pf)(int, int))//pf函数指针,传过来哪个函数地址就用哪种类型计算
{
int x = 0;
int y = 0;
int ret = 0;
printf("请输入两个操作数:>");
scanf("%d %d", &x, &y);
ret = pf(x, y);
printf("%d\n", ret);
}
void menu()
{
printf("*************************\n");
printf("*** 1.Add 2.Sub **\n");
printf("*** 3.Mul 4.Div **\n");
printf("*** 0.Exit **\n");
printf("*************************\n");
}
int main()
{
int input = 0;
int ret = 0;
do{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
Calc(Add);
break;
case 2:
Calc(Sub);
break;
case 3:
Calc(Mul);
break;
case 4:
Calc(Div);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("选择错误\n");
break;
}
} while (input);
return 0;
}
要約:
コールバック関数: パラメータとして渡される関数。Add、Sub、Mul、Div の 4 つの関数がコールバック関数です。
Calc 機能はツールです
8.2 qsortライブラリ関数の使用
8.2.1 バブルソート
バブルソートのプロセスを確認してください。
バブルソートの考え方: 昇順に並べられていると仮定して、隣接する 2 つの要素を比較します。
バブルソートのコードの書き方:
①要素数で回数を決める②トリップ回数による比較対数
③2つの要素をペアで交換し昇順に並べる
//冒泡排序
void bubble_sort(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz - 1; i++)//趟数
{
int j = 0;
for (j = 0; j < sz - 1 - i; j++)//比较对数
{
if (arr[j] > arr[j + 1])
{
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0};
//排序
//使用
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz);
print_arr(arr, sz);
return 0;
}
不十分:
ただし、このバブル ソートの欠点も明らかです。つまり、整数 int のみをソートできるということです。
浮動小数点や構造体などのデータが登場した場合、それらを配置することはできません。どうすれば解決できますか?
このとき、qsortを使用します。
8.2.2 qsortの概念
qsort -- クイックソート
これはライブラリ関数であり、クイックソートメソッドを使用してライブラリ関数をソートするために使用されます。
qsort の利点は次のとおりです。
1. 既製品
2. あらゆる種類のデータを並べ替えることができます
qsort はあらゆるタイプのデータをソートできます
1. 2 つの整数の大きさを比較します。 > < ==
//qsort函数的使用者提供这个函数
//qsort 默认是升序
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
//排成倒序的话,什么都不用动,只需要return *(int*)p2 - *(int*)p1;//调换顺序即可
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
test1()
{
int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
//使用qsort来排序整型数组,这里就要提供一个比较函数,这个比较函数能够比较2个整数的大小
qsort(arr, sz, sizeof(arr[0]), cmp_int);
print_arr(arr, sz);
}
int main()
{
test1();
}
2. 2 つの文字列を比較します (strcmp) --> 文字列の知識を確認します: http://t.csdn.cn/V7E9a
3. 2 つの構造データを比較 (生徒: Zhang San、Li Si) 比較基準を指定
構造体オブジェクトのアクセス メンバーに関する知識を確認します: http://t.csdn.cn/DVEVj
//测试qsort 排序结构体数据
struct Stu {
char name[20];
int age;
};
void print_arr1(struct Stu* arr, int sz)//打印年龄
{
int i = 0;
for (i = 0; i < sz; i++)
{
//printf("%d ", (*(arr + i)).age);
//printf("%d ", (arr+i)->age);
printf("%d ", arr[i].age);
}
printf("\n");
}
void print_arr2(struct Stu*arr, int sz)//打印姓名
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", (arr+i)->name);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
//按照年龄来比较
int cmp_stu_by_age(const void*p1,const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void test2()
{
struct Stu s[] = { {"zhangsan",30},{"lisi",25},{"wangwu",50} };
int sz = sizeof(s) / sizeof(s[0]);
//测试按照年龄来排序
print_arr1(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
print_arr1(s, sz);
//测试按照名字来排序
/*print_arr2(s, sz);
qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
print_arr2(s, sz);*/
}
印刷年齢:
性別を印刷:
8.3 qsortを実現するためのバブルソートのアイデア
バブルソートの考え方を利用して、qsortの関数と同様のバブルソート関数 bubble_sort() を実装します。
各パラメータは、base、num、width、cmp 関数、elem1 および elem2で構成されます。
次に、qsort の各パラメータの設計上の考え方を説明します。
①なぜbaseはvoid*型として定義されているのでしょうか?
void* はC 言語の一般的なポインタ型で、あらゆる型のデータを指すことができます。qsort 関数では、さまざまなタイプのデータをソートする必要があるため、さまざまなタイプのデータを受け入れるために void* 型のポインターを使用する必要があります。base パラメーターは、ソートされる配列の最初の要素へのポインターです。任意の型のデータを指すため、 void* を使用するのが最も一般的です。
例:
int a = 10;
void* p = &a;
アドバンテージ:
ポインタには特定の種類がないため、あらゆる種類のアドレスを受け取ることができます。
欠点:
*p //--> void*的指针不能解引用操作符
p++ //--> 因为不知道是什么数据类型,不能直接++
正しい使い方:
*(int*)p;//也可以转化成其它类型
②なぜnumをsize_tとして設計する必要があるのでしょうか?
- size_t は、C 言語の符号なし整数型で、サイズ、長さ、インデックス値を表すために使用され、配列の要素数を表すために使用するのに適しています。
配列要素の数は負ではない値であり、負の数を避けるために符号なし型 size_t を使用する方が合理的です。
- 特殊な size_t 型を使用すると、単純な unsigned int よりもこのパラメーターのセマンティクスが明確になります。これは、整数ではなくサイズまたは長さを表します。
③幅をsize_tで設計する理由
qsort 関数では、width パラメータは各配列要素のサイズ (バイト単位) を示します。
width は「サイズ」の概念を表します。size_t 型を使用すると、このパラメーターのセマンティクスをより明確に表現できます。
width の単位はバイトで、size_t は通常符号なし整数型であり、負の数がないため、正の値を表すのに適しています。
width には、あらゆるデータ型のサイズを表すのに十分な大きさの範囲が必要です。size_t タイプはプラットフォームに依存しますが、通常はマシンワードサイズであり、ほとんどのデータ要素のサイズ要件を満たします。
配列要素にアクセスする場合、アドレス オフセットを取得するには、インデックス位置に要素サイズを乗算する必要があります。インデックスは size_t 型であるため、オーバーフローの問題を避けるために、2 つを乗算した結果も size_t 型になる必要があります。
④ なぜ関数ポインタ cmp を設計するのか?
qsort 自体は一般的なソート関数であり、ソート対象の要素のタイプを知ることができないため、2 つの要素を直接比較することはできません。関数ポインターを使用すると、比較演算の実装をユーザーに任せることができます。
関数ポインターを介して、ユーザーは独自のデータ型の比較関数を実装し、特定の比較ロジックをカプセル化できます。これにより、qsort の多用途性と柔軟性が向上します。
データ型が異なると、比較演算のロジックも異なる場合があります。関数ポインターを使用すると、qsort で多くの条件付き分岐ロジックを作成する必要がなくなります。
関数ポインターは拡張メカニズムを提供します。ユーザーが比較演算のロジックを変更する必要がある場合、qsort 関数自体を変更することなく、新しい関数ポインターを渡すだけで済みます。
⑤elem1型とelem2型はなぜconst void*なのでしょうか?
qsort はあらゆるタイプのデータをソートするため、比較関数はあらゆるタイプの要素を処理できる必要があります。void* を使用すると、あらゆるタイプのデータを指すことができ、const は、ポインターが指す内容を変更してはならないことを示すために使用されます。
qsort はあらゆるタイプのデータをソートするため、比較関数はあらゆるタイプの要素を処理できる必要があります。void* を使用すると、あらゆるタイプのデータを指すことができ、const は、ポインターが指す内容を変更してはならないことを示すために使用されます。
qsort を呼び出すとき、配列要素のアドレスを const void* 型に直接変換し、それを比較関数に渡すことで呼び出しを簡素化できます。
qsort 関数自体は要素の特定の型を理解する必要はなく、 void* ポインターを比較関数に渡すだけで、比較関数はポインターの内容を変換して解釈します。
シミュレーションの実装を開始します。大きな質問をバブルソートするというアイデアは変更する必要はありません。ソート方法のみを変更する必要があります。
int cmp_int(const void* p1, const void* p2)
{
return *(int*)p1 - *(int*)p2;
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
int (*cmp)(const void* p1, const void* p2))
{
//冒泡排序大题思路不需要改变,只需要对排序方式进行即可
//要确定趟数
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
int flag = 0;//假设已经有序了
//一趟冒泡排序的过程
size_t j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//两个相邻的元素比较
//arr[j] arr[j+1]
if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
{
//交换
flag = 0;
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void print_arr(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test3()
{
int arr[] = { 3,1,5,2,4,9,8,6,5,7 };
int sz = sizeof(arr) / sizeof(arr[0]);
printf("排序前:");
print_arr(arr, sz);
bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
printf("排序后:");
print_arr(arr, sz);
}
qsort によるコードの分析:
データを交換する方法:
なぜここはchar*と書かれるのでしょうか?
bubble_sort 関数では、base が指すデータ型が不明なため、ポインタのステップ サイズが 1 になるように char* 型にキャストする必要があります。このようにして、ポインタを加算および減算するときに、幅パラメータに従ってポインタのステップ サイズを制御し、任意のデータ型のソートを実現できます。
注: width は char の場合は 1 バイトを意味しますが、他のタイプでは異なります。幅はさまざまなパラメータに従ってさまざまなデータ型を設定し、ポインタのステップ サイズを制御します。
交換プロセス: 1、2、3、4 は順序を表します
コードの全体的な流れは次のとおりです。
並べ替え:
新しく定義されたバブルソートで年齢と名前を並べ替えます
struct Stu {
char name[20];
int age;
};
int cmp_stu_by_age(const void*p1,const void* p2)
{
return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}
int cmp_stu_by_name(const void* p1, const void* p2)
{
return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}
void Swap(char* buf1, char* buf2, int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char tmp = *buf1;
*buf1 = *buf2;
*buf2 = tmp;
buf1++;
buf2++;
}
}
//假设排序为升序
//希望这个bubble_sort函数可以排序任意类型的数据
void bubble_sort(void* base, size_t num, size_t width,
int (*cmp)(const void* p1, const void* p2))
{
//要确定趟数
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
int flag = 0;//假设已经有序了
//一趟冒泡排序的过程
size_t j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//两个相邻的元素比较
//arr[j] arr[j+1]
if (cmp((char*)base + j * width, (char*)base+(j+1)*width)>0)
{
//交换
flag = 0;
Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
}
}
}
}
void print_arr3(struct Stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", (s + i)->age);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
void print_arr4(struct Stu* s, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%s ", (s+i)->name);
//printf("%s ", (*(arr+i)).name);
//printf("%s ", arr[i].name);
}
printf("\n");
}
void test4()
{
struct Stu s[] = { {"zhangsan",30} ,{"lisi",25},{"wangwu",50}};
int sz = sizeof(s) / sizeof(s[0]);
//测试按年龄来排序
/*print_arr3(s, sz);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
print_arr3(s, sz);*/
//测试按照名字来排序
print_arr4(s, sz);
bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
print_arr4(s, sz);
}
名前順:
年齢順に並べ替える
この記事はここまでです。間違いがあれば修正してください。その後、sizeof と strlen:、ポインターと配列のペン テストの問題分析の概要、ポータル --> http://t.csdn に関する詳細な概要に到達します。 cn/aKVsj
皆様のご訪問を歓迎します。