杭州网站seo,免费网站建设,网站制作心得体会200字,怎么自己做网站版面设计目录
一. 字符指针变量
二. 数组指针变量
三. 二维数组传参
3.1 二维数组的本质
3.2 访问方式与地址计算
3.3 二维数组的传参方式
3.4 深入解析 *(*(arri)j) 与 arr[i][j] 的等价性
四. 函数指针变量
4.1 函数指针变量的创建
4.2 函数指针变量的使用
4.3 两段…目录
一. 字符指针变量
二. 数组指针变量
三. 二维数组传参
3.1 二维数组的本质
3.2 访问方式与地址计算
3.3 二维数组的传参方式
3.4 深入解析 *(*(arri)j) 与 arr[i][j] 的等价性
四. 函数指针变量
4.1 函数指针变量的创建
4.2 函数指针变量的使用
4.3 两段有趣代码的讲解
4.3.1 解析 (*(void(*)())0)();
4.3.2 解析void(* signal(int, void(*)(int)))(int);
4.4 typedef关键字讲解
4.4.1 基本语法
4.4.2 常见用法
4.4.3 结合 typedef 解析 signal 函数
4.4.4 typedef 的优点
4.4.5 总结
五. 函数指针数组
5.1 基本概念
5.2 详细用法
5.3 实际应用示例
六. 转移表 一. 字符指针变量
什么是字符指针变量呢?
字符指针变量是指向字符类型数据的指针在C/C中通常用于处理字符和字符串和字符数组。
例如以下代码 处理字符 int main()
{char ch w;char* pc ch; ///字符指针变量return 0;
} pc存放的是字符w的地址 例如以下代码 处理字符串 int main()
{const char* pc Hello, World!;//常量字符串 不可被修改printf( %c\n, *pc);printf( %s, pc); //注意: 打印字符串的时候 需要的参数是字符串的起始地址return 0;
} pc存放的是首字符H的地址 打印验证结果如下 注意: 打印字符串的时候 需要的参数是字符串的起始地址 注意:pc此时指向的是常量字符串 即*pc现在是一个左值 无法被修改 由于*pc现在是一个左值 无法被修改 使用我们使用const来修饰它 例如以下代码 处理字符数组 int main()
{char arr[] ABCDE;char* pc arr;printf( %c\n, *pc);printf( %s, pc); //注意: 打印字符串的时候 需要的参数是字符串的起始地址return 0;
} pc存放的是数组第一个元素a的地址 同理 运行结果如下 注意:pc此时指向的是数组 与指向字符串的区别是*pc现在可以被修改 现在让我们来认真阅读以下代码 运行结果会是什么呢?
int main()
{char str1[] Hello world;char str2[] Hello world;const char* str3 Hello world;const char* str4 Hello world;if (str1 str2)printf(str1str2\n);elseprintf(str1!str2\n);if (str3 str4)printf(str3str4\n);elseprintf(str3!str4\n);return 0;
}
运行结果如下 可以看出str1与str2不相等 而str3和str4却相等 这是为什么呢?
原因分析 str1 和 str2 的比较 (str1 str2) str1 和 str2 是两个独立的字符数组分别存储 Hello world。数组名在比较时会被转换为指向数组首元素的指针即 str1[0] 和 str2[0]。由于 str1 和 str2 是两个不同的数组它们的地址不同所以 str1 str2 为 false输出 str1!str2。 str3 和 str4 的比较 (str3 str4) str3 和 str4 是指向字符串常量的指针且它们的值都是 Hello world。编译器会对相同的字符串常量进行优化称为 字符串池化String Interning即多个相同的字符串常量在内存中只存储一份。因此str3 和 str4 实际上指向同一个内存地址所以 str3 str4 为 true输出 str3str4。 关键区别 字符数组 (char[]) 会分配独立的内存空间即使内容相同地址也不同。字符串常量 (const char*) 可能被优化为共享同一内存因此相同内容的字符串常量可能指向同一地址。 而如果想比较字符串的内容是否相同应该使用 strcmp 函数而不是直接比较指针
if (strcmp(str1, str2) 0) // 比较内容是否相同printf(str1 和 str2 内容相同\n);
elseprintf(str1 和 str2 内容不同\n);
strcmp函数简要功能如下 具体了解请访问strcmp - C Reference
二. 数组指针变量
在学习数组指针前 让我们来回顾一下 字符指针 整型指针 字符指针: char* p ----指向字符的指针 存放的是字符的地址 char *pch; 整形指针: int* p ----指向整型的指针 存放的是整形的地址 int a10; pa; 数组指针: ----指向数组的指针 存放的是数组的地址 即数组指针变量是指向数组的指针且在C/C中用于处理多维数组和动态数组操作。
而数组的地址我们也曾经遇见过 同学们不妨通过以下代码回想一下
int main()
{int arr[10] { 0 };arr; //首元素的地址arr[0];arr;//取出的是整个数组的地址---数组的地址return 0;
}
相信已经有同学分不清了 现在让我们来区别一下指针数组和数组指针
特性数组指针 (int (*p)[N])指针数组 (int *p[N])本质一个指针指向整个数组一个数组元素全是指针声明方式int (*p)[5];int *p[5];内存占用指针大小通常8字节N个指针的大小如5个指针40字节存储内容存储数组的首地址存储多个指针地址典型用途处理二维数组存储多个字符串/动态数组sizeof结果sizeof(p)指针大小sizeof(p)N×指针大小
直观理解:
数组指针 → 指向数组的指针
int arr[3][4];
int (*p)[4] arr; // p指向arr的第一行一个包含4个int的数组
p1会跳过整个子数组移动4*sizeof(int)字节
指针数组 → 存放指针的数组
char *strs[3] {Hello, World, !};
strs[1]返回第二个字符串的地址World的首地址 关键区别:
操作数组指针 (int (*p)[4])指针数组 (int *p[4])定义指向int[4]的指针包含4个int*的数组p1的偏移量16字节假设int4字节8字节指针大小
记忆口诀 “星号括起来是指针星号不括是数组” int (*p)[N] → 星号被括号括住强调是指针int *p[N] → 星号没被括强调是数组 如何使用数组指针来打印数组里的值呢? 如下
#define _CRT_SECURE_NO_WARNINGS
#include stdio.h
int main()
{int arr[] {1,2,3,4,5,6,7,8,9};int (*p)[9] arr;//(*p)得到arr的地址 [i]表示调用arr里的第几个元素for (int i 0;i 9;i){printf(%d , (*p)[i]);}
}
但这种写法似乎更加复杂了 并没有什么优势
但其实我们并不会在这种情况使用数组指针 下面让我们继续深入学习
三. 二维数组传参
3.1 二维数组的本质
二维数组是 “数组的数组”在内存中仍然是连续存储的线性结构。例如
int arr[3][4] {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}
};内存布局
[1][2][3][4] [5][6][7][8] [9][10][11][12]每行 arr[i] 是一个一维数组类型是 int[4]arr 本身是 “指向int[4]的指针”即 int (*)[4]
3.2 访问方式与地址计算
arr[i][j] 的地址arr[0][0] i * 4 j 假设 int 占4字节4是列数行指针 arr[i]等价于 *(arr i)元素 arr[i][j]等价于 *(*(arr i) j) 3.3 二维数组的传参方式
(1) 标准方式必须指定列数
void print(int arr[][4], int rows){for(int i0; irows; i){for(int j0; j4; j){printf(%d , arr[i][j]);}printf(\n);}
}本质arr[][4] 会被编译器转换为 int (*)[4]
(2) 数组指针方式
void print(int (*arr)[4], int rows) {// 与上述代码完全等价
}关键点
arr1 会跳过 16字节4个int必须指定列数否则无法计算步长
(3) 错误方式
void print(int **arr, int rows, int cols) { // 错误静态二维数组不是二级指针
}
下面让我们具体运行来观察一下二维数组传参
#define _CRT_SECURE_NO_WARNINGS
#include stdio.h
text(int(*arr)[5], int r,int c)
{for (int i 0;i r;i){for (int j 0;j c;j){printf(%d , *(*(arr i)j));}}
}
int main()
{int arr[3][5] {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};text(arr, 3, 5);return 0;
} 注意:
*(arri) arr[i] 因为二维数组的数组名表示第一行的地址 i表示跳过 i 行 并且*(*(arri)j) arr[ i ][ j ]
二维数组传参本质上也是传递了地址 传递的是第一行这个一维数组的地址
3.4 深入解析 *(*(arri)j) 与 arr[i][j] 的等价性
1. 关键概念拆解
表达式类型含义arrint (*)[3]指向第一行({1,2,3})的指针arr iint (*)[3]指向第i行的指针*(arr i)int *第i行的首元素地址退化成一维*(arri) jint *第i行第j个元素的地址*(*(arri)j)int第i行第j个元素的值
2. 与下标访问的对应关系
arr[i][j] ≡ *(*(arr i) j)编译器实际处理 所有 arr[i][j] 最终都会被转换为指针运算形式。
3. 为什么需要列数
arr i 的步长取决于列数sizeof(int[N])若未指定列数如 int arr[][]编译器无法计算 arr i 的偏移量
4. 典型考题示例
题目以下代码输出什么
int arr[2][3] {{1,2,3}, {4,5,6}};
printf(%d\n, *(*(arr 1) 2));答案6 解析 *(arr 1) 指向第二行 {4,5,6}*(arr 1) 2 指向 6解引用后得到值 6。
掌握这个核心等价关系就能彻底理解二维数组的指针运算
四. 函数指针变量
4.1 函数指针变量的创建
让我们回忆一下之前的指针内容 字符指针: 存放的是字符的地址 指向的就是字符变量 整型指针: 存放的是整型的地址 指向的是整行变量 数组指针: 存放的是数组的地址 指向的是数组 函数指针: 存放的是函数的地址 指向的是函数 那函数的地址怎么得到的呢?
我们知道数组的地址是通过数组名得到的 那函数的地址是通过函数名得到的吗?
答案是 是的 函数的地址就是通过函数名获得 可以看到 地区打印了函数的地址
那函数和数组一样吗? 数组名代表了数组首元素的地址 那函数名代表什么呢? 让我们来试试 可以看到 打印出来的一模一样 那函数名是首函数的地址吗? 显然没有这个说法
他们俩所得到的都是函数的地址 并没有什么区别
那什么是函数指针变量呢?
函数指针变量是一个指向函数的指针它存储了函数的地址可以通过该指针间接调用函数。函数指针的类型由函数的返回类型和参数列表决定。
1. 函数指针的声明
函数指针的声明语法如下
返回类型 (*指针变量名)(参数类型1, 参数类型2, ...);示例
#define _CRT_SECURE_NO_WARNINGS
#includestdio.h
int Add(int x, int y)
{return x y;
}
int main()
{int(*pf)(int,int) Add;//pf就是函数指针变量return 0;
}
那存放下面这个函数的函数指针变量该如何写呢?
int* text(int n, char* p)
{}
通过类比 不难写出
int* (*pf)(int, char*)text;//pf就是函数指针变量
4.2 函数指针变量的使用
我们知道通过解引用操作符 可以通过地址来找到存放的变量 那解引用函数指针变量是否就可以使用函数了呢? 答案是 是的 例子如下 可以看到 我通过*pf 实现了对函数的调用 并且传入参数( 3 , 5 )用r来接受 因为函数返回值是int类型 所以r也是int类型
其次 我们知道我们可以通过函数名来调用函数 即Add( 3 , 5 ) 那函数指针变量存放的又是函数名
那我们是否可以直接通过函数指针变量来使用函数呢? 不妨让我们试试 可以看到 无论是函数名调用 还是函数指针变量调用 还是解引用函数指针变量调用 都可以实现对函数的使用
4.3 两段有趣代码的讲解
那让我们来思考思考下面这段代码的含义是什么呢?
(*(void(*)())0)();
相信大家看了之后都会感觉 浑身不自在吧 现在让我们一起来解读一下这段代码
4.3.1 解析 (*(void(*)())0)();
这个表达式看起来复杂但它实际上是一个 函数指针强制转换 调用 的典型例子。我们可以一步步拆解它的含义。 1. 表达式拆解
(*(void(*)())0)();可以分解为
(void(*)())0将 0 强制转换为一个 函数指针。*(void(*)())0解引用 这个函数指针得到函数本身。(*(void(*)())0)();调用 这个函数。 2. 详细分析
(1) void(*)() 是什么
void(*)() 是一个 函数指针类型表示 返回类型void无返回值参数列表()无参数 所以void(*)() 是一个 指向无参无返回值函数的指针。
(2) (void(*)())0将 0 强制转换为函数指针
0 是一个整数代表 内存地址 0x0NULL 指针。(void(*)())0 表示 把 0 强制转换为一个函数指针即 void (*func_ptr)() (void(*)())0; // 现在 func_ptr 指向地址 0(3) *(void(*)())0解引用函数指针
*(void(*)())0 相当于 void (*func_ptr)() (void(*)())0;
*func_ptr; // 解引用得到函数本身在 C 语言中函数指针解引用后仍然是函数所以 *func_ptr 和 func_ptr 是等价的见上一节分析。
(4) (*(void(*)())0)();调用这个函数
最终(*(void(*)())0)(); 相当于 void (*func_ptr)() (void(*)())0;
(*func_ptr)(); // 调用地址 0 处的函数或者更简单的写法因为 func_ptr() 和 (*func_ptr)() 等价 ((void(*)())0)(); // 直接调用
4.3.2 解析void(* signal(int, void(*)(int)))(int);
这个声明 void(* signal(int, void(*)(int)))(int); 是一个函数声明它定义了一个名为 signal 的函数。为了理解这个声明我们可以逐步解析它 最内层部分 void(*)(int): 这是一个函数指针类型指向一个接受 int 参数并返回 void 的函数。 例如void handler(int sig); 这样的函数可以匹配这个指针类型。 中间部分 signal(int, void(*)(int)): signal 是一个函数它接受两个参数 第一个参数是 int 类型。 第二个参数是 void(*)(int) 类型即上述的函数指针。 因此signal 的函数原型可以理解为 void (*signal(int sig, void (*handler)(int)))(int);最外层部分 void(* ... )(int): signal 函数的返回值也是一个函数指针类型为 void(*)(int)。 也就是说signal 函数返回一个指向“接受 int 参数并返回 void 的函数”的指针。
简化理解
signal 是一个函数它接受一个 int 和一个函数指针并返回一个同类型的函数指针。
总结
void(* signal(int, void(*)(int)))(int); 声明了一个函数 signal它
接受两个参数int 和 void(*)(int)函数指针。返回一个 void(*)(int) 类型的函数指针。
这种写法在 C 语言中很常见尤其是在处理回调函数或函数指针时。
4.4 typedef关键字讲解
typedef 关键字解释
typedef 是 C/C 中的一个关键字用于为现有的数据类型包括基本类型、结构体、联合体、枚举、函数指针等定义一个新的别名使代码更易读、更简洁。 4.4.1 基本语法
typedef 原类型 新别名;原类型可以是 int、float、char、struct、union、enum 或函数指针等。新别名你给这个类型取的新名字。 4.4.2 常见用法
(1) 为基本类型定义别名
typedef unsigned int uint; // 定义 uint 代替 unsigned int
typedef float real; // 定义 real 代替 floatuint age 25; // 等同于 unsigned int age 25;
real weight 65.5f; // 等同于 float weight 65.5f;(2) 为结构体定义别名
传统写法需要 struct 关键字
struct Point {int x;int y;
};struct Point p1; // 必须写 struct Point使用 typedef 简化
typedef struct {int x;int y;
} Point; // 定义 Point 代替 struct { ... }Point p1; // 直接使用 Point不需要写 struct(3) 为指针类型定义别名
typedef int* IntPtr; // IntPtr 是 int* 的别名int a 10;
IntPtr p a; // 等同于 int* p a;(4) 为函数指针定义别名
原始写法复杂
void (*funcPtr)(int); // funcPtr 是一个指向 void(int) 函数的指针使用 typedef 简化
typedef void (*FuncPtr)(int); // FuncPtr 是 void(*)(int) 的别名void foo(int x) { printf(%d\n, x); }FuncPtr fp foo; // 等同于 void (*fp)(int) foo;
fp(10); // 调用 foo(10)4.4.3 结合 typedef 解析 signal 函数
原声明
void (*signal(int, void(*)(int)))(int);使用 typedef 简化
typedef void (*SignalHandler)(int); // 定义 SignalHandler 代替 void(*)(int)SignalHandler signal(int sig, SignalHandler handler); // 更清晰的声明SignalHandler 是一个函数指针类型指向 void(int) 函数。signal 是一个函数接受 int 和 SignalHandler并返回 SignalHandler。 4.4.4 typedef 的优点
提高可读性复杂的类型如函数指针可以用更直观的名字表示。减少重复代码避免反复写冗长的类型声明。便于维护修改类型时只需改 typedef 定义而不需要修改所有使用的地方。 4.4.5 总结
用途示例基本类型别名typedef int Int32;结构体别名typedef struct { ... } Point;指针别名typedef int* IntPtr;函数指针别名typedef void (*FuncPtr)(int);
typedef 是 C/C 中非常重要的关键字能显著提升代码的可读性和可维护性尤其是在处理复杂类型如函数指针时非常有用。
五. 函数指针数组
首先我们要明白什么是函数指针数组
5.1 基本概念
1. 函数指针
函数指针是指向函数的指针变量。声明一个函数指针需要指定它指向的函数的返回类型和参数类型。
// 函数原型
int add(int a, int b);
int subtract(int a, int b);// 函数指针声明
int (*funcPtr)(int, int);// 指向add函数
funcPtr add;2. 函数指针数组
函数指针数组是存储多个函数指针的数组。
// 声明一个包含两个函数指针的数组
int (*funcArray[2])(int, int);// 初始化数组
funcArray[0] add;
funcArray[1] subtract;5.2 详细用法
1. 声明函数指针数组
// 返回类型 (*数组名[数组大小])(参数列表)
double (*operations[4])(double, double);2. 初始化函数指针数组
double add(double a, double b) { return a b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div(double a, double b) { return a / b; }// 初始化数组
operations[0] add;
operations[1] sub;
operations[2] mul;
operations[3] div;3. 使用函数指针数组
double result;
int choice 2; // 假设用户选择乘法
double x 5.0, y 3.0;// 通过索引调用函数
result operations[choice](x, y);
printf(结果: %.2f\n, result); // 输出: 15.005.3 实际应用示例
1. 计算器实现
#include stdio.h// 定义运算函数
double add(double a, double b) { return a b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div(double a, double b) { return a / b; }int main() {// 声明并初始化函数指针数组double (*ops[4])(double, double) {add, sub, mul, div};int choice;double x, y;printf(选择运算:\n0. 加\n1. 减\n2. 乘\n3. 除\n);scanf(%d, choice);printf(输入两个数字: );scanf(%lf %lf, x, y);// 调用选中的函数double result ops[choice](x, y);printf(结果: %.2f\n, result);return 0;
}
运行结果如下: 六. 转移表
转移表Jump Table
转移表也称为跳转表是一种使用函数指针数组来实现多路分支的技术它比传统的switch-case语句更高效、更灵活。
基本概念
转移表本质上是一个函数指针数组通过数组索引来选择和调用不同的函数避免了冗长的条件判断。
转移表 vs switch-case
switch-case实现
void handleCommand(int cmd) {switch(cmd) {case 0: cmd0(); break;case 1: cmd1(); break;case 2: cmd2(); break;// ...default: defaultHandler();}
}转移表实现
// 定义命令处理函数
void cmd0() { /* ... */ }
void cmd1() { /* ... */ }
void cmd2() { /* ... */ }
void defaultHandler() { /* ... */ }// 创建转移表
typedef void (*CommandHandler)(void);
CommandHandler jumpTable[] {cmd0, cmd1, cmd2};void handleCommand(int cmd) {if (cmd 0 cmd sizeof(jumpTable)/sizeof(jumpTable[0])) {jumpTable[cmd]();} else {defaultHandler();}
}转移表优势 效率更高直接通过索引访问时间复杂度O(1)而switch-case可能需要多次比较 代码更简洁特别是当分支很多时 更易维护添加新功能只需扩展数组不需要修改逻辑结构 动态性可以在运行时修改函数指针
实际应用示例
1. 简单计算器
#include stdio.h// 运算函数
double add(double a, double b) { return a b; }
double sub(double a, double b) { return a - b; }
double mul(double a, double b) { return a * b; }
double div(double a, double b) { return a / b; }// 定义转移表
typedef double (*Operation)(double, double);
Operation operations[] {add, sub, mul, div};int main() {int choice;double x, y;printf(选择运算(0-3): );scanf(%d, choice);printf(输入两个数字: );scanf(%lf %lf, x, y);if (choice 0 choice sizeof(operations)/sizeof(operations[0])) {double result operations[choice](x, y);printf(结果: %.2f\n, result);} else {printf(无效选择\n);}return 0;
}以上就是本篇内容 希望能对你有所帮助