当前位置: 首页 > news >正文

网站后台登陆口网站建设与管理实训报告总结

网站后台登陆口,网站建设与管理实训报告总结,宁波网站建设开发,怎么建立一个网站让外国人浏览1. 回调函数 回调函数的基本思想是#xff0c;将函数指针作为参数传递给另一个函数#xff0c;并在需要时通过这个函数指针调用对应的函数。这种方式允许一个函数对执行的内容进行控制#xff0c;而不需要知道具体的实现细节。 回调函数在以下场景中尤为有用#xff1a; …1. 回调函数 回调函数的基本思想是将函数指针作为参数传递给另一个函数并在需要时通过这个函数指针调用对应的函数。这种方式允许一个函数对执行的内容进行控制而不需要知道具体的实现细节。 回调函数在以下场景中尤为有用 事件驱动编程例如处理按键事件或鼠标点击。排序或过滤可以将比较函数作为参数传递给排序函数。异步操作例如定时器到时后调用某个处理函数。 1.1 实现回调函数的步骤 实现回调函数主要有以下几个步骤 定义一个回调函数编写一个可以被回调的函数这个函数的签名需要与回调机制所期望的签名一致。定义函数指针定义一个可以指向回调函数的函数指针。调用函数并传递回调函数指针在需要执行回调函数的地方通过传递回调函数的地址来调用它。 1.2 回调函数实例 上一节我们讲解了转移表来改善我们的简易计算器那我们能不能通过回调函数来实现呢 1.2.1 改造前的代码 //使用回调函数改造前 #include stdio.h int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } int main() {int x, y;int input 1;int ret 0;do{printf(*************************\n);printf( 1:add2:sub \n);printf( 3:mul4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);switch (input){case 1:printf(输入操作数);scanf(%d %d, x, y);ret add(x, y);printf(ret %d\n, ret);break;case 2:printf(输入操作数);scanf(%d %d, x, y);ret sub(x, y);printf(ret %d\n, ret);break;case 3:printf(输入操作数);scanf(%d %d, x, y);ret mul(x, y);printf(ret %d\n, ret);break;case 4:printf(输入操作数);scanf(%d %d, x, y);ret div(x, y);printf(ret %d\n, ret);break;case 0:printf(退出程序\n);break;default:printf(选择错误\n);break;}} while (input);return 0; }我们发现每一个case中的代码有很大一部分都是重复的那么这一部分可以用回调函数来改造一下 1.2.2 改造后的代码 //使用回到函数改造后 #include stdio.h int add(int a, int b) {return a b; } int sub(int a, int b) {return a - b; } int mul(int a, int b) {return a * b; } int div(int a, int b) {return a / b; } void calc(int(*pf)(int, int)) {int ret 0;int x, y;printf(输入操作数);scanf(%d %d, x, y);ret pf(x, y);printf(ret %d\n, ret); } int main() {int input 1;do{printf(*************************\n);printf( 1:add2:sub \n);printf( 3:mul4:div \n);printf(*************************\n);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; }1.2.3 代码解析 这段代码的核心在于使用回调函数 calc 统一处理不同的运算操作从而简化了 main 函数中的逻辑。 1.2.3.1 定义 calc 回调函数 calc 函数使用了函数指针 pf可以接收任意符合 int (int, int) 签名的函数。calc 函数的作用是通用地处理不同的运算逻辑而不直接关心运算的具体实现这使得代码更加灵活。 void calc(int (*pf)(int, int)) {int ret 0;int x, y;printf(输入操作数);scanf(%d %d, x, y);ret pf(x, y); // 调用传入的回调函数printf(ret %d\n, ret); }输入操作数提示用户输入两个操作数 x 和 y。调用回调函数通过 pf(x, y) 调用传入的运算函数。输出结果将结果 ret 输出给用户。 1.2.3.2 主函数 main main 函数的作用是提供用户交互界面根据用户的选择调用对应的运算函数。 int main() {int input 1;do {printf(*************************\n);printf( 1:add2:sub \n);printf( 3:mul4:div \n);printf(*************************\n);printf(请选择);scanf(%d, input);switch (input) {case 1:calc(add); // 将加法函数指针传递给 calcbreak;case 2:calc(sub); // 将减法函数指针传递给 calcbreak;case 3:calc(mul); // 将乘法函数指针传递给 calcbreak;case 4:calc(div); // 将除法函数指针传递给 calcbreak;case 0:printf(退出程序\n);break;default:printf(选择错误\n);break;}} while (input);return 0; }菜单显示每次循环都会显示运算选项菜单用户可以选择对应的运算。用户输入获取用户的选择并存储在 input 中。调用 calc 函数根据 input 的值通过 calc 调用对应的运算函数。退出程序当 input 为 0 时程序输出退出提示并结束循环。 1.3 回调函数的优点 代码复用通过传递不同的回调函数calc 函数可以实现不同的功能。灵活性可以在程序运行时动态地选择调用哪个函数。模块化设计使代码逻辑更加清晰、可维护。 1.4 回调函数的应用场景 排序算法在 qsort 等函数中用户可以自定义比较函数实现各种排序逻辑。(下面会讲)事件驱动的GUI程序响应用户的点击、输入等操作。异步任务处理在异步操作完成时通过回调函数通知调用者任务完成。 2. qsort 使用 qsort是C语言标准库stdlib.h中的一个通用排序函数它使用快速排序算法来对数组进行排序。qsort的强大之处在于它可以用于各种数据类型的排序包括基本数据类型和复杂的自定义数据结构。这里将探讨qsort的使用方法并通过详细的实例演示如何对不同类型的数据进行排序。 2.1 qsort函数原型详解 void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))参数说明 base指向要排序的数组的第一个元素的指针。它是一个void指针可以接受任何类型的数组。nitems数组中元素的个数。通常使用sizeof(array) / sizeof(array[0])来计算。size数组中每个元素的大小以字节为单位。通常使用sizeof(array[0])来获取。compar用于比较两个元素的函数指针。这个函数决定了排序的顺序和标准。 2.2 比较函数的设计 比较函数是qsort的核心它决定了排序的方式。比较函数的原型如下 int compare(const void *a, const void *b)比较函数应该遵循以下规则 如果 *a *b返回负数通常是-1如果 *a *b返回0如果 *a *b返回正数通常是1 注意比较函数接收的是void指针需要在函数内部进行适当的类型转换。 2.3 整数排序详解 下面是一个使用qsort对整数数组进行排序的详细例子 #include stdio.h #include stdlib.h// 升序排序的比较函数 int compare_ints_asc(const void* a, const void* b) {return (*(int*)a - *(int*)b); }// 降序排序的比较函数 int compare_ints_desc(const void* a, const void* b) {return (*(int*)b - *(int*)a); }int main() {int arr[] {64, 34, 25, 12, 22, 11, 90};int n sizeof(arr) / sizeof(arr[0]);// 升序排序qsort(arr, n, sizeof(int), compare_ints_asc);printf(升序排序后的数组\n);for (int i 0; i n; i)printf(%d , arr[i]);printf(\n);// 降序排序qsort(arr, n, sizeof(int), compare_ints_desc);printf(降序排序后的数组\n);for (int i 0; i n; i)printf(%d , arr[i]);printf(\n);return 0; }在这个例子中我们定义了两个比较函数一个用于升序排序另一个用于降序排序。通过更改传递给qsort的比较函数我们可以轻松地改变排序的顺序。 2.4 字符串排序详解 对字符串数组进行排序需要特别注意因为我们处理的是指向字符串的指针数组。以下是一个详细的例子 #include stdio.h #include stdlib.h #include string.h// 升序排序的比较函数 int compare_strings_asc(const void* a, const void* b) {return strcmp(*(const char**)a, *(const char**)b); }// 降序排序的比较函数 int compare_strings_desc(const void* a, const void* b) {return strcmp(*(const char**)b, *(const char**)a); }int main() {const char* arr[] {banana, apple, cherry, date, elderberry};int n sizeof(arr) / sizeof(arr[0]);// 升序排序qsort(arr, n, sizeof(const char*), compare_strings_asc);printf(升序排序后的字符串数组\n);for (int i 0; i n; i)printf(%s\n, arr[i]);// 降序排序qsort(arr, n, sizeof(const char*), compare_strings_desc);printf(\n降序排序后的字符串数组\n);for (int i 0; i n; i)printf(%s\n, arr[i]);return 0; }在这个例子中我们使用strcmp函数来比较字符串。注意比较函数中的双重指针*(const char**)a 用于获取实际的字符串指针。 2.5 结构体排序详解 对结构体数组进行排序是qsort最强大的应用之一。我们可以根据结构体的不同成员进行排序。以下是一个详细的例子 #include stdio.h #include stdlib.h #include string.htypedef struct {char name[50];int age;float height; } Person;// 按年龄升序排序 int compare_age_asc(const void* a, const void* b) {Person* personA (Person*)a;Person* personB (Person*)b;return personA-age - personB-age; }// 按身高降序排序 int compare_height_desc(const void* a, const void* b) {Person* personA (Person*)a;Person* personB (Person*)b;if (personA-height personB-height) return 1;if (personA-height personB-height) return -1;return 0; }// 按姓名字母顺序排序 int compare_name(const void* a, const void* b) {Person* personA (Person*)a;Person* personB (Person*)b;return strcmp(personA-name, personB-name); }int main() {Person people[] {{Alice, 30, 165.5},{Bob, 25, 180.0},{Charlie, 35, 175.5},{David, 28, 170.0},{Eve, 22, 160.0}};int n sizeof(people) / sizeof(people[0]);// 按年龄升序排序qsort(people, n, sizeof(Person), compare_age_asc);printf(按年龄升序排序\n);for (int i 0; i n; i)printf(%s: %d岁, %.1fcm\n, people[i].name, people[i].age, people[i].height);// 按身高降序排序qsort(people, n, sizeof(Person), compare_height_desc);printf(\n按身高降序排序\n);for (int i 0; i n; i)printf(%s: %d岁, %.1fcm\n, people[i].name, people[i].age, people[i].height);// 按姓名字母顺序排序qsort(people, n, sizeof(Person), compare_name);printf(\n按姓名字母顺序排序\n);for (int i 0; i n; i)printf(%s: %d岁, %.1fcm\n, people[i].name, people[i].age, people[i].height);return 0; }在这个例子中我们定义了三个不同的比较函数分别按年龄、身高和姓名进行排序。这展示了qsort在处理复杂数据结构时的灵活性。 2.6 qsort的高级应用 多级排序当主要排序标准相同时可以在比较函数中添加次要排序标准。 int compare_multi(const void* a, const void* b) {Person* personA (Person*)a;Person* personB (Person*)b;if (personA-age ! personB-age)return personA-age - personB-age; // 主要标准年龄return strcmp(personA-name, personB-name); // 次要标准姓名 }稳定性处理qsort本身不保证稳定性相等元素的相对顺序可能改变。如果需要稳定排序可以在比较函数中加入原始索引比较。 自定义排序规则比较函数可以实现复杂的自定义排序逻辑如按字符串长度排序、按特定规则排序等。 2.7 qsort的性能考虑 qsort的时间复杂度平均为O(n log n)但在最坏情况下可能达到O(n^2)。对于小数组通常少于10-20个元素插入排序等简单算法可能更快。比较函数的效率直接影响排序的整体性能应尽可能简化比较函数。 2.8 结论 qsort函数是C语言中一个强大而灵活的排序工具。通过合理设计比较函数它可以适应各种复杂的排序需求。掌握qsort的使用不仅可以提高编程效率还能帮助深入理解指针、函数指针和回调函数等C语言的重要概念。在实际应用中合理使用qsort可以大大简化代码结构提高程序的可读性和可维护性。 3. qsort函数的模拟实现 我们采用 冒泡排序 来实现并用回调函数来定义比较规则这样我们的排序函数可以对不同数据类型进行排序。 3.1 基本思路 冒泡排序冒泡排序是一种简单的排序算法它通过重复地交换相邻的元素来将最大或最小的元素逐步移动到数组的末尾或开头。回调函数qsort 使用一个回调函数来比较元素的大小。这个回调函数返回负值、零或正值分别表示第一个元素小于、等于或大于第二个元素。通用性为了实现一个通用的排序函数函数接收一个 void* 类型的数组指针并通过额外的参数指定元素的大小和数组的长度。通过这样的设置可以排序任意类型的数组。 3.2 模拟实现 qsort 的代码 以下代码展示了如何使用回调函数模拟 qsort 功能采用冒泡排序的方式来实现排序。 #include stdio.h int int_cmp(const void * p1, const void * p2) {return (*( int *)p1 - *(int *) p2); } void _swap(void *p1, void * p2, int size) {int i 0;for (i 0; i size; i){char tmp *((char *)p1 i);*(( char *)p1 i) *((char *) p2 i);*(( char *)p2 i) tmp;} } void bubble(void *base, int count , int size, int(*cmp )(void *, void *)) {int i 0;int j 0;for (i 0; i count - 1; i){for (j 0; jcount-i-1; j){if (cmp ((char *) base j*size , (char *)base (j 1)*size) 0){_swap(( char *)base j*size, (char *)base (j 1)*size,size);}}} } int main() {int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof (int), int_cmp);for (i 0; i sizeof(arr) / sizeof(arr[0]); i){printf( %d , arr[i]);}printf(\n);return 0; }3.3 代码解析 3.3.1 整数比较函数 int_cmp int int_cmp(const void * p1, const void * p2) {return (*(int *)p1 - *(int *)p2); }功能int_cmp 是一个比较函数用于比较两个整数的大小。它接受两个 const void* 类型的指针 p1 和 p2。实现将 p1 和 p2 转换为 int*解引用后得到两个整数并返回它们的差值。差值的正负代表 p1 和 p2 的相对大小大于零表示 p1 p2小于零表示 p1 p2。 3.3.2 交换函数 _swap void _swap(void *p1, void * p2, int size) {int i 0;for (i 0; i size; i) {char tmp *((char *)p1 i);*((char *)p1 i) *((char *)p2 i);*((char *)p2 i) tmp;} }功能_swap 函数用于交换两个内存位置的数据。这在排序中非常重要因为我们需要不断交换位置来排列数组元素。实现它通过字节级别的拷贝实现交换操作。函数参数 size 决定了每次交换的数据块大小这样可以适用于任意数据类型。细节通过 char* 类型指针对每个字节进行交换使用循环将 p1 和 p2 对应位置的数据逐字节交换。 3.3.3 冒泡排序函数 bubble void bubble(void *base, int count , int size, int(*cmp)(void *, void *)) {int i 0;int j 0;for (i 0; i count - 1; i) {for (j 0; j count - i - 1; j) {if (cmp((char *)base j * size, (char *)base (j 1) * size) 0) {_swap((char *)base j * size, (char *)base (j 1) * size, size);}}} }功能bubble 函数是一个通用的冒泡排序函数可以排序任何类型的数据。参数 base待排序数组的起始地址。count数组中的元素个数。size每个元素的大小字节数。cmp用于比较两个元素的函数指针。 实现 使用两层嵌套循环实现冒泡排序。外层循环控制排序轮数内层循环每次将最大或最小的元素移动到数组的末尾。cmp 函数用于比较相邻两个元素的大小。如果 cmp 返回值大于 0表示前一个元素大于后一个元素则调用 _swap 函数交换它们的位置。 3.3.4 主函数 main int main() {int arr[] { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };int i 0;bubble(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);for (i 0; i sizeof(arr) / sizeof(arr[0]); i) {printf(%d , arr[i]);}printf(\n);return 0; }功能定义一个整型数组 arr调用 bubble 函数对其进行排序然后输出排序后的结果。实现 sizeof(arr) / sizeof(arr[0]) 计算数组中元素的个数。sizeof(int) 表示每个元素的大小字节数。将 int_cmp 作为参数传递给 bubble 函数指定排序时使用的比较逻辑。 3.4 总结 通用性bubble 函数使用了 void* 指针和 cmp 比较函数指针因此可以排序任意类型的数据只需传入适当的比较函数。灵活性通过 int_cmp 函数我们定义了整数的比较方式可以方便地替换为其他比较方式比如降序排序或不同数据类型的排序。冒泡排序排序逻辑基于冒泡排序复杂度为 O(n^2)适用于小规模数据。 这段代码展示了使用回调函数实现通用排序的思想是 C 语言中实现灵活算法的重要方法。 4. sizeof和strlen的对比 4.1 sizeof()的作用和用法 功能sizeof() 是一个编译时运算符用于返回变量或数据类型的大小以字节为单位。 返回值返回值的类型为 size_t它是一个无符号整数表示所占的字节数。 用法 sizeof(变量名)返回变量所占的内存大小。sizeof(数据类型)返回指定数据类型所占的内存大小。 示例: int a 10; printf(sizeof(int): %zu\n, sizeof(int)); // 输出4假设int为4字节 printf(sizeof(a): %zu\n, sizeof(a)); // 输出4使用场景: 计算基本数据类型或结构体的大小例如 sizeof(int)、sizeof(double) 等。确定数组大小特别是当数组的大小在编译时已知时很有用。 4.2 strlen()的作用和用法 功能strlen() 是一个库函数定义在 string.h 中用于计算字符串的长度即字符数不包括字符串末尾的 \0 终止符。 返回值返回值的类型为 size_t表示字符串中字符的数量。 用法 strlen(字符串)计算字符串的长度字符串必须以 \0 结尾。 示例: char str[] Hello; printf(strlen(str): %zu\n, strlen(str)); // 输出5不包括\0使用场景: 常用于操作字符串时获取其长度通常配合 char 数组或 char 指针。适合在运行时处理动态长度的字符串。 4.3 sizeof() 和 strlen() 的主要区别 特性sizeof()strlen()定义位置编译时运算符运行时库函数功能计算变量或数据类型的内存大小计算字符串的字符长度适用类型任意数据类型仅适用于字符串返回值是否包含 \0包含对于 char 数组不包含字符串末尾的 \0使用限制适用于任何类型编译时计算仅限以 \0 结尾的字符串运行时计算 4.4 sizeof() 与 strlen() 的常见应用与误区 数组和字符串的区别 对于字符数组sizeof 会返回数组总大小包括 \0 而 strlen 仅返回字符串长度不包含 \0。 char str[10] Hello; printf(sizeof(str): %zu\n, sizeof(str)); // 输出10包含未使用的字符空间 printf(strlen(str): %zu\n, strlen(str)); // 输出5只计算到Hello的长度指针与数组的区别 对于字符指针sizeof 返回指针的大小通常是 4 或 8 字节取决于系统而 strlen 返回字符串的实际长度。 char *str_ptr Hello; printf(sizeof(str_ptr): %zu\n, sizeof(str_ptr)); // 输出指针大小4或8字节 printf(strlen(str_ptr): %zu\n, strlen(str_ptr)); // 输出5字符串的长度字符串常量 对于字符串常量sizeof 会计算字符串的字节数并包含 \0而 strlen 则不包括。 printf(sizeof(Hello): %zu\n, sizeof(Hello)); // 输出6包含\0 printf(strlen(Hello): %zu\n, strlen(Hello)); // 输出5不包含\04.5 总结 sizeof() 是一个运算符用于在编译时确定数据的总内存大小适用于任意类型。对数组而言它返回整个数组的大小对指针而言它返回指针本身的大小。strlen() 适用于字符串类型只在运行时计算字符数不包含终止符 \0。 掌握 sizeof() 和 strlen() 的差异可以有效避免数组和字符串处理中的常见错误。在字符串操作时记得区分 sizeof 和 strlen 的用途以免出现意外的结果。 5. 数组和指针笔试题解析 5.1 一维数组 int a[] {1,2,3,4}; printf(%zd\n,sizeof(a)); printf(%zd\n,sizeof(a0)); printf(%zd\n,sizeof(*a)); printf(%zd\n,sizeof(a1)); printf(%zd\n,sizeof(a[1])); printf(%zd\n,sizeof(a)); printf(%zd\n,sizeof(*a)); printf(%zd\n,sizeof(a1)); printf(%zd\n,sizeof(a[0])); printf(%zd\n,sizeof(a[0]1));5.1.1 代码及解释 int a[] {1,2,3,4};声明了一个整型数组 a包含 4 个元素。数组 a 的内存大小为 4 * sizeof(int) 16 字节。 5.1.1.1 sizeof(a) printf(%zd\n, sizeof(a));解释a 是一个数组名作为 sizeof 的操作数时表示整个数组的大小。结果返回整个数组的大小即 4 * sizeof(int) 16 字节。 5.1.1.2 sizeof(a0) printf(%zd\n, sizeof(a0));解释a0 表示将数组 a 转换为指向第一个元素的指针然后再加上偏移量 0。因此 a0 实际上是一个指向 int 的指针。结果返回指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.1.3 sizeof(*a) printf(%zd\n, sizeof(*a));解释*a 表示解引用 a即获取数组第一个元素的值。第一个元素是一个 int 类型所以 sizeof(*a) 实际上是 sizeof(int)。结果返回 int 的大小即 4 字节。 5.1.1.4 sizeof(a1) printf(%zd\n, sizeof(a1));解释a1 是数组名 a 加上偏移量 1表示指向数组第二个元素的指针。a1 的类型是指针类型。结果返回指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.1.5 sizeof(a[1]) printf(%zd\n, sizeof(a[1]));解释a[1] 表示数组的第二个元素类型为 int。结果返回 int 的大小即 4 字节。 5.1.1.6 sizeof(a) printf(%zd\n, sizeof(a));解释a 是整个数组 a 的地址它的类型是 int (*)[4]即指向一个包含 4 个 int 的数组的指针。结果返回一个指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.1.7 sizeof(*a) printf(%zd\n, sizeof(*a));解释*a 先取 a 的地址然后再解引用回到原来的数组 a。因此*a 的类型是 int[4]代表整个数组。结果返回数组 a 的大小即 16 字节。 5.1.1.8 sizeof(a1) printf(%zd\n, sizeof(a1));解释a1 表示数组 a 的地址加上 1。由于 a 是一个 int (*)[4] 类型指针指向包含 4 个 int 的数组a1 指向的是下一个同类型数组的地址。结果返回一个指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.1.9 sizeof(a[0]) printf(%zd\n, sizeof(a[0]));解释a[0] 是第一个元素的地址即一个指向 int 的指针。结果返回一个指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.1.10 sizeof(a[0]1) printf(%zd\n, sizeof(a[0]1));解释a[0]1 是第一个元素的地址加上 1结果是一个指向 a[1] 的指针即一个指向 int 的指针。结果返回指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。 5.1.2 总结 表达式解释返回值字节sizeof(a)数组 a 的大小16sizeof(a0)指向 a 的第一个元素的指针的大小4/8sizeof(*a)a[0] 的大小即 int 的大小4sizeof(a1)指向 a 的第二个元素的指针的大小4/8sizeof(a[1])数组第二个元素的大小即 int 的大小4sizeof(a)数组 a 的地址的大小4/8sizeof(*a)数组 a 的大小16sizeof(a1)指向下一个数组的地址的大小4/8sizeof(a[0])a[0] 的地址的大小4/8sizeof(a[0]1)a[1] 的地址的大小4/8 在这里sizeof 操作数是数组时返回整个数组的大小而是指针时则返回指针的大小。这些差异在理解 C 语言中的指针和数组时非常重要。 5.2 字符数组 5.2.1 代码1 char arr[] {a,b,c,d,e,f}; printf(%zd\n, sizeof(arr)); printf(%zd\n, sizeof(arr0)); printf(%zd\n, sizeof(*arr)); printf(%zd\n, sizeof(arr[1])); printf(%zd\n, sizeof(arr)); printf(%zd\n, sizeof(arr1)); printf(%zd\n, sizeof(arr[0]1));5.2.1.1 sizeof(arr) printf(%zd\n, sizeof(arr));解释arr 是一个数组名在 sizeof 中使用时它表示整个数组的大小。结果数组 arr 的总大小为 6 个字节因为它包含 6 个 char 元素每个 char 是 1 字节。输出6 5.2.1.2 sizeof(arr0) printf(%zd\n, sizeof(arr0));解释arr0 表示将数组名 arr 转换为指向数组第一个元素的指针然后再加上偏移量 0。结果是一个 char* 指针。结果返回指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.1.3 sizeof(*arr) printf(%zd\n, sizeof(*arr));解释*arr 表示解引用数组 arr即获取第一个元素的值。第一个元素是一个 char 类型所以 sizeof(*arr) 实际上是 sizeof(char)。结果返回 char 的大小即 1 字节。输出1 5.2.1.4 sizeof(arr[1]) printf(%zd\n, sizeof(arr[1]));解释arr[1] 表示数组中的第二个元素即字符 b类型为 char。结果返回 char 的大小即 1 字节。输出1 5.2.1.5 sizeof(arr) printf(%zd\n, sizeof(arr));解释arr 是整个数组 arr 的地址它的类型是 char (*)[6]即指向包含 6 个 char 的数组的指针。结果返回指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.1.6 sizeof(arr1) printf(%zd\n, sizeof(arr1));解释arr1 表示数组 arr 的地址加上 1。由于 arr 是一个 char (*)[6] 类型指针指向包含 6 个 char 的数组所以 arr1 指向的是下一个同类型数组的地址。结果返回指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.1.7 sizeof(arr[0]1) printf(%zd\n, sizeof(arr[0]1));解释arr[0] 是第一个元素的地址即一个指向 char 的指针。arr[0]1 表示指针加一指向数组的第二个元素。结果返回指针的大小在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.1.8 总结 表达式解释返回值字节输出32 位输出64 位sizeof(arr)数组 arr 的大小666sizeof(arr0)arr 的第一个元素的指针的大小4或 848sizeof(*arr)arr[0] 的大小即 char 的大小111sizeof(arr[1])数组第二个元素的大小即 char 的大小111sizeof(arr)数组 arr 的地址的大小4或 848sizeof(arr1)下一个数组的地址的大小4或 848sizeof(arr[0]1)arr[1] 的地址的大小4或 848 这道题展示了 sizeof 的不同应用。使用 sizeof 对数组名、指针和指向数组的指针时会有不同的结果。在数组名作为 sizeof 操作数时返回整个数组的大小而在指针表达式时返回的是指针的大小。 5.2.2 代码2 char arr[] {a,b,c,d,e,f}; printf(%zd\n, strlen(arr)); printf(%zd\n, strlen(arr0)); printf(%zd\n, strlen(*arr)); printf(%zd\n, strlen(arr[1])); printf(%zd\n, strlen(arr)); printf(%zd\n, strlen(arr1)); printf(%zd\n, strlen(arr[0]1));5.2.2.1 strlen(arr) printf(%zd\n, strlen(arr));解释arr 是一个字符数组的首地址strlen(arr) 试图从 arr 开始计算字符长度直到找到 \0。问题由于 arr 没有 \0 终止符因此 strlen 将继续访问内存直到遇到偶然的 \0这会导致未定义的行为可能返回一个不确定的长度甚至引发程序崩溃。建议如果想要使用 strlen应当定义数组时添加一个 \0 终止符如 char arr[] abcdef;。 5.2.2.2 strlen(arr0) printf(%zd\n, strlen(arr0));解释arr0 是一个指向 arr 第一个元素的指针因此 strlen(arr0) 等价于 strlen(arr)。问题与 strlen(arr) 相同arr 没有以 \0 结尾因此 strlen(arr0) 会导致未定义行为。建议应确保 arr 是一个以 \0 结尾的字符串才能使用 strlen。 5.2.2.3 strlen(*arr) printf(%zd\n, strlen(*arr));解释*arr 表示解引用 arr即 arr[0]它的值是字符 a。问题strlen 的参数应为指向 char 的指针而 *arr 是一个 chara 的 ASCII 值是 97。传递非指针类型给 strlen 会导致编译错误因为 strlen 期望一个 char* 指针。错误信息编译器通常会提示类型不兼容错误。 5.2.2.4 strlen(arr[1]) printf(%zd\n, strlen(arr[1]));解释arr[1] 是数组的第二个元素值为字符 b。问题与上一行类似strlen 需要一个 char* 类型的参数而 arr[1] 是一个 char会导致类型不兼容错误。错误信息编译器通常会提示类型不兼容错误。 5.2.2.5 strlen(arr) printf(%zd\n, strlen(arr));解释arr 是整个数组的地址类型是 char (*)[6]指向包含 6 个字符的数组。问题strlen 期望一个 char* 参数而 arr 是一个 char (*)[6] 类型的指针这与 char* 类型不兼容。虽然 arr 和 arr 的值相同但类型不同会导致未定义行为。错误信息编译器可能会报类型不兼容的警告或错误运行时可能导致错误。 5.2.2.6 strlen(arr1) printf(%zd\n, strlen(arr1));解释arr1 是 arr 数组的地址加上 1跳过整个 arr 数组的大小因此指向数组 arr 之后的内存。问题strlen 期望一个指向 char 的指针而 arr1 是 char (*)[6] 类型的指针类型不兼容。此外它指向 arr 之后的内存区域而不是有效的字符串起始地址访问会导致未定义行为。错误信息编译器可能会报类型不兼容的警告运行时可能导致程序崩溃。 5.2.2.7 strlen(arr[0]1) printf(%zd\n, strlen(arr[0]1));解释arr[0] 是 arr 的第一个元素的地址即一个 char* 指针。arr[0] 1 指向数组的第二个元素即 arr[1]。问题由于 arr 不是以 \0 结束的字符串strlen(arr[0] 1) 仍会继续读取直到遇到一个偶然的 \0导致未定义行为。建议如果想要计算从 arr[1] 开始的有效字符串长度应确保 arr 是以 \0 结尾的字符串。 5.2.2.8 总结 由于 arr 不是一个以 \0 结尾的字符串因此不能直接使用 strlen 计算其长度。在 C 语言中strlen 只能用来处理以 \0 结束的字符串否则会出现未定义行为。正确的方式是确保数组以 \0 结束或者使用 sizeof(arr) 来计算数组的总大小。 5.2.3 代码3 char arr[] abcdef; printf(%zd\n, sizeof(arr)); printf(%zd\n, sizeof(arr0)); printf(%zd\n, sizeof(*arr)); printf(%zd\n, sizeof(arr[1])); printf(%zd\n, sizeof(arr)); printf(%zd\n, sizeof(arr1)); printf(%zd\n, sizeof(arr[0]1));5.2.3.1 sizeof(arr) printf(%zd\n, sizeof(arr));解释arr 是一个字符数组的名字在 sizeof 中使用时它表示整个数组的大小。结果数组 arr 的总大小为 7 个字节因为它包含 abcdef 和一个 \0 终止符。输出7 5.2.3.2 sizeof(arr0) printf(%zd\n, sizeof(arr0));解释arr0 是一个指向数组第一个元素的指针即 char* 指针因为数组名 arr 在表达式中会衰减为指针。sizeof(arr0) 返回指针的大小而不是数组的大小。结果返回指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.3.3 sizeof(*arr) printf(%zd\n, sizeof(*arr));解释*arr 是解引用 arr即获取数组的第一个元素的值类型为 char。结果返回 char 的大小即 1 字节。输出1 5.2.3.4 sizeof(arr[1]) printf(%zd\n, sizeof(arr[1]));解释arr[1] 是数组的第二个元素类型为 char。结果返回 char 的大小即 1 字节。输出1 5.2.3.5 sizeof(arr) printf(%zd\n, sizeof(arr));解释arr 是数组 arr 的地址它的类型是 char (*)[7]即指向包含 7 个字符的数组的指针。结果返回指向数组的指针的大小而不是数组的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.3.6 sizeof(arr1) printf(%zd\n, sizeof(arr1));解释arr1 是 arr 数组的地址加上 1。这会跳过整个数组的大小指向下一个 char[7] 类型的数组的地址。arr1 的类型仍然是 char (*)[7]。结果返回指向数组的指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.3.7 sizeof(arr[0]1) printf(%zd\n, sizeof(arr[0]1));解释arr[0] 是数组第一个元素的地址即一个 char* 指针。arr[0] 1 指向数组的第二个元素即 arr[1]。结果sizeof(arr[0]1) 返回 char* 指针的大小因为 arr[0]1 是一个指向 char 的指针。输出432 位系统或 864 位系统 5.2.4 代码4 char arr[] abcdef; printf(%zd\n, strlen(arr)); printf(%zd\n, strlen(arr0)); printf(%zd\n, strlen(*arr)); printf(%zd\n, strlen(arr[1])); printf(%zd\n, strlen(arr)); printf(%zd\n, strlen(arr1)); printf(%zd\n, strlen(arr[0]1));5.2.4.1 strlen(arr) printf(%zd\n, strlen(arr));解释arr 是字符数组的名字它在表达式中会退化为指向第一个字符的指针。结果strlen 从 arr 开始逐字符计算直到遇到 \0 终止符。因此strlen(arr) 返回字符串 abcdef 的长度即 6。输出6 5.2.4.2 strlen(arr0) printf(%zd\n, strlen(arr0));解释arr0 是数组名 arr 加上偏移量 0相当于指向第一个字符的指针。因此 strlen(arr0) 和 strlen(arr) 的效果相同。结果返回字符串 abcdef 的长度即 6。输出6 5.2.4.3 strlen(*arr) printf(%zd\n, strlen(*arr));解释*arr 是解引用 arr即获取 arr 的第一个字符 arr[0] 的值也就是字符 a。strlen 的参数必须是一个 char* 类型的指针而 *arr 是一个 char 类型字符 a 的 ASCII 值。问题将 char 类型作为 strlen 的参数会导致编译错误因为 strlen 需要一个指向字符数组的指针而不是一个单个字符。错误信息编译器会提示类型不兼容错误。 5.2.4.4 strlen(arr[1]) printf(%zd\n, strlen(arr[1]));解释arr[1] 是数组的第二个元素其值为字符 b。与 *arr 类似这里传递了一个 char 类型的参数即 b而不是指向字符数组的指针。问题同样地将 char 类型作为 strlen 的参数会导致编译错误因为 strlen 需要的是一个 char* 类型的指针而不是单个字符。错误信息编译器会提示类型不兼容错误。 5.2.4.5 strlen(arr) printf(%zd\n, strlen(arr));解释arr 是整个数组 arr 的地址其类型是 char (*)[7]指向一个包含 7 个字符的数组。虽然 arr 和 arr 的值相同都是数组的起始地址但是类型不同。问题strlen 需要的是一个 char*而 arr 的类型是 char (*)[7]。虽然它指向同一块内存但类型不同可能会导致未定义行为。运行结果编译器可能会警告类型不兼容。运行时可能输出正确的字符串长度6但不推荐使用这种写法。 5.2.4.6 strlen(arr1) printf(%zd\n, strlen(arr1));解释arr1 是数组 arr 的地址加 1它跳过整个数组的大小因此指向数组 arr 之后的内存区域。问题arr1 指向的并不是有效的字符串地址传递它给 strlen 会导致未定义行为因为它指向的是 arr 数组末尾之后的位置访问将导致未定义的结果。运行结果可能会导致程序崩溃或产生不确定的结果。编译器也可能会警告类型不兼容。 5.2.4.7 strlen(arr[0]1) printf(%zd\n, strlen(arr[0]1));解释arr[0] 是数组第一个元素的地址即一个 char* 指针指向字符 a。arr[0] 1 指向数组的第二个元素即字符 b。结果strlen(arr[0] 1) 从 arr[1] 开始计算长度返回从字符 b 到字符串结尾的长度即 5。输出5 5.2.4.8 总结 表达式解释返回值字符数输出strlen(arr)从 arr 开始计算字符串长度66strlen(arr0)等价于 strlen(arr)从 arr 开始计算长度66strlen(*arr)*arr 是第一个字符 a类型错误错误编译错误strlen(arr[1])arr[1] 是第二个字符 b类型错误错误编译错误strlen(arr)arr 是数组地址类型不兼容可能导致错误未定义未定义可能是 6strlen(arr1)指向数组结束后的位置未定义行为未定义未定义可能崩溃strlen(arr[0]1)从 arr[1] 开始计算字符串长度55 注意strlen 只能用于以 \0 结尾的字符串对于其他情况或不适当的类型传递会导致编译错误或未定义行为。 5.2.5 代码5 char *p abcdef; printf(%zd\n, sizeof(p)); printf(%zd\n, sizeof(p1)); printf(%zd\n, sizeof(*p)); printf(%zd\n, sizeof(p[0])); printf(%zd\n, sizeof(p)); printf(%zd\n, sizeof(p1)); printf(%zd\n, sizeof(p[0]1));5.2.5.1 sizeof(p) printf(%zd\n, sizeof(p));解释p 是一个指向字符的指针类型是 char*。结果sizeof(p) 返回指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.5.2 sizeof(p1) printf(%zd\n, sizeof(p1));解释p1 表示指针 p 向后移动一个位置指向字符串的第二个字符即 b。p1 的类型仍然是 char*只是指向的内存位置发生了变化。结果sizeof(p1) 返回指针的大小与 sizeof(p) 相同。输出432 位系统或 864 位系统 5.2.5.3 sizeof(*p) printf(%zd\n, sizeof(*p));解释*p 是解引用指针 p即获取指针所指向的第一个字符的值。*p 的类型是 char。结果sizeof(*p) 返回 char 的大小通常为 1 字节。输出1 5.2.5.4 sizeof(p[0]) printf(%zd\n, sizeof(p[0]));解释p[0] 等价于 *p表示指针 p 所指向的第一个字符类型为 char。结果sizeof(p[0]) 返回 char 的大小通常为 1 字节。输出1 5.2.5.5 sizeof(p) printf(%zd\n, sizeof(p));解释p 是指针 p 的地址类型是 char**即指向 char* 的指针。结果sizeof(p) 返回指向指针的指针的大小。在 32 位系统上通常是 4 字节在 64 位系统上通常是 8 字节。输出432 位系统或 864 位系统 5.2.5.6 sizeof(p1) printf(%zd\n, sizeof(p1));解释p1 是指针 p 的地址加上 1。p 的类型是 char**因此 p1 指向的是下一个 char* 指针的地址。结果sizeof(p1) 返回指向指针的指针的大小与 sizeof(p) 相同。输出432 位系统或 864 位系统 5.2.5.7 sizeof(p[0]1) printf(%zd\n, sizeof(p[0]1));解释p[0] 是 p 所指向的第一个字符的地址即字符串 abcdef 的起始地址。p[0] 的类型是 char*所以 p[0] 1 表示这个地址向后移动一个字符指向字符串的第二个字符即 b。结果sizeof(p[0]1) 返回指针的大小与 sizeof(p) 相同。输出432 位系统或 864 位系统 5.2.5.8 总结 表达式解释返回值字节输出32 位输出64 位sizeof(p)p 是一个 char* 指针返回指针大小4或 848sizeof(p1)p1 仍是 char* 指针返回指针大小4或 848sizeof(*p)*p 是指向的第一个字符类型为 char111sizeof(p[0])等同于 *p返回第一个字符的大小111sizeof(p)p 是 char** 类型返回指向指针的指针的大小4或 848sizeof(p1)p1 是 char** 类型返回指向指针的指针的大小4或 848sizeof(p[0]1)p[0]1 是 char* 类型返回指针大小4或 848 这段代码展示了 sizeof 运算符在处理指针和指针的地址时的不同效果。主要区别在于 sizeof(p) 返回 p 本身的大小而 sizeof(*p) 返回 p 所指向的数据的大小。对于 p 和 p1它们的类型是 char**因为它们指向的是指针 p 的地址。 5.2.6 代码6 char *p abcdef; printf(%zd\n, strlen(p)); printf(%zd\n, strlen(p1)); printf(%zd\n, strlen(*p)); printf(%zd\n, strlen(p[0])); printf(%zd\n, strlen(p)); printf(%zd\n, strlen(p1)); printf(%zd\n, strlen(p[0]1));5.2.6.1 strlen(p) printf(%zd\n, strlen(p));解释p 是一个指向字符串 abcdef 的指针。结果strlen 从 p 开始计算字符数直到遇到 \0。因此strlen(p) 返回字符串 abcdef 的长度即 6。输出6 5.2.6.2 strlen(p1) printf(%zd\n, strlen(p1));解释p1 表示指针 p 向后移动一个字符的位置因此指向字符串的第二个字符 b。结果strlen(p1) 从 p1 开始计算返回从字符 b 到字符串结尾的长度即 5。输出5 5.2.6.3 strlen(*p) printf(%zd\n, strlen(*p));解释*p 是对指针 p 的解引用得到 p 所指向的第一个字符的值 a。*p 的类型是 char而不是 char*。问题strlen 需要一个 char* 参数但 *p 是一个 char会导致编译错误因为类型不匹配。错误信息编译器会提示类型不兼容的错误。 5.2.6.4 strlen(p[0]) printf(%zd\n, strlen(p[0]));解释p[0] 等价于 *p表示 p 所指向的第一个字符 p[0] 的值 a类型为 char。问题与上面相同strlen 需要 char* 参数而 p[0] 是 char 类型导致类型不兼容错误。错误信息编译器会提示类型不兼容的错误。 5.2.6.5 strlen(p) printf(%zd\n, strlen(p));解释p 是指针 p 的地址类型是 char**即指向 char* 的指针。问题strlen 需要一个 char* 类型的参数而 p 是 char**类型不匹配可能导致未定义行为。运行结果编译器可能会发出类型不兼容的警告。运行时可能产生不可预期的结果或导致崩溃。 5.2.6.6 strlen(p1) printf(%zd\n, strlen(p1));解释p1 是指向指针 p 的地址再加 1因此它指向 p 之后的内存地址类型为 char**。问题strlen 期望一个 char* 参数而 p1 是 char**类型不匹配可能导致未定义行为。运行结果编译器可能会发出类型不兼容的警告运行时可能会导致崩溃。 5.2.6.7 strlen(p[0]1) printf(%zd\n, strlen(p[0]1));解释p[0] 是字符串第一个字符的地址即 p 本身。p[0] 1 表示从 p 向后移动一个字符的位置指向字符串的第二个字符 b。结果strlen(p[0] 1) 从 p[1]即字符 b开始计算字符串长度返回 5。输出5 5.2.6.8 总结 表达式解释返回值字符数输出strlen(p)从 p 开始计算字符串长度66strlen(p1)从 p1即字符 b开始计算长度55strlen(*p)*p 是字符 a类型错误错误编译错误strlen(p[0])p[0] 是字符 a类型错误错误编译错误strlen(p)p 是 char**类型不匹配未定义行为未定义可能崩溃或错误strlen(p1)p1 是 char**类型不匹配未定义行为未定义可能崩溃或错误strlen(p[0]1)从 p[1]即字符 b开始计算长度55 注意strlen 只能用于以 \0 结尾的字符串对于其他数据类型或错误类型传递会导致编译错误或未定义行为。正确使用时应确保传递的是指向以 \0 结束的 char 字符串的指针。 5.3 二维数组 int a[3][4] {0}; printf(%zd\n,sizeof(a)); printf(%zd\n,sizeof(a[0][0])); printf(%zd\n,sizeof(a[0])); printf(%zd\n,sizeof(a[0]1)); printf(%zd\n,sizeof(*(a[0]1))); printf(%zd\n,sizeof(a1)); printf(%zd\n,sizeof(*(a1))); printf(%zd\n,sizeof(a[0]1)); printf(%zd\n,sizeof(*(a[0]1))); printf(%zd\n,sizeof(*a)); printf(%zd\n,sizeof(a[3]));5.3.1. sizeof(a) printf(%zd\n, sizeof(a));解释a 是一个二维数组sizeof(a) 返回整个数组的大小。结果整个数组有 3 行 4 列每个元素是 4 字节因此 sizeof(a) 3 * 4 * 4 48 字节。输出48 5.3.2 sizeof(a[0][0]) printf(%zd\n, sizeof(a[0][0]));解释a[0][0] 是数组中的第一个元素类型是 int。结果sizeof(a[0][0]) 返回 int 的大小即 4 字节。输出4 5.3.3 sizeof(a[0]) printf(%zd\n, sizeof(a[0]));解释a[0] 表示二维数组的第一行类型是 int[4]即一个包含 4 个整数的一维数组。结果sizeof(a[0]) 返回第一行的大小即 4 * sizeof(int) 16 字节。输出16 5.3.4 sizeof(a[0]1) printf(%zd\n, sizeof(a[0]1));解释a[0] 是第一行的起始地址即一个 int* 指针a[0] 1 表示将该指针偏移到 a[0][1] 的位置类型仍为 int*。结果sizeof(a[0]1) 返回指针的大小。在 32 位系统上通常为 4 字节在 64 位系统上通常为 8 字节。输出432 位系统或 864 位系统 5.3.5 sizeof(*(a[0]1)) printf(%zd\n, sizeof(*(a[0]1)));解释a[0] 1 是指向 a[0][1] 的指针*(a[0] 1) 解引用该指针得到 a[0][1] 的值类型为 int。结果sizeof(*(a[0]1)) 返回 int 的大小即 4 字节。输出4 5.3.6 sizeof(a1) printf(%zd\n, sizeof(a1));解释a 是一个二维数组表达式 a1 会将 a 视为指向 int[4] 的指针即指向第一行a1 表示指向下一行的地址类型为 int (*)[4]。结果sizeof(a1) 返回指针的大小。在 32 位系统上通常为 4 字节在 64 位系统上通常为 8 字节。输出432 位系统或 864 位系统 5.3.7 sizeof(*(a1)) printf(%zd\n, sizeof(*(a1)));解释a1 是指向第二行的指针类型为 int (*)[4]*(a1) 解引用得到 a[1]类型是 int[4]即一个包含 4 个 int 的一维数组。结果sizeof(*(a1)) 返回第二行的大小即 4 * sizeof(int) 16 字节。输出16 5.3.8 sizeof(a[0]1) printf(%zd\n, sizeof(a[0]1));解释a[0] 是第一行的地址类型为 int (*)[4]。a[0] 1 指向第二行的地址类型仍为 int (*)[4]。结果sizeof(a[0]1) 返回指向数组一行的指针的大小。在 32 位系统上通常为 4 字节在 64 位系统上通常为 8 字节。输出432 位系统或 864 位系统 5.3.9 sizeof(*(a[0]1)) printf(%zd\n, sizeof(*(a[0]1)));解释a[0] 1 是指向第二行的地址类型为 int (*)[4]*(a[0]1) 解引用得到第二行即 a[1]类型为 int[4]。结果sizeof(*(a[0]1)) 返回 a[1] 的大小即 4 * sizeof(int) 16 字节。输出16 5.3.10 sizeof(*a) printf(%zd\n, sizeof(*a));解释a 是一个指向第一行的指针类型为 int (*)[4]*a 解引用得到第一行的内容即 a[0]类型为 int[4]。结果sizeof(*a) 返回第一行的大小即 4 * sizeof(int) 16 字节。输出16 5.3.11 sizeof(a[3]) printf(%zd\n, sizeof(a[3]));解释虽然 a[3] 超出了数组的范围但在 sizeof 中不会引发越界错误。a[3] 类型被视为 int[4]与 a[0] 相同。结果sizeof(a[3]) 返回 int[4] 的大小即 4 * sizeof(int) 16 字节。输出16 5.3.12 总结 表达式解释返回值字节输出假设 int 为 4 字节sizeof(a)返回整个二维数组的大小4848sizeof(a[0][0])返回第一个元素int的大小44sizeof(a[0])返回第一行的大小1616sizeof(a[0]1)返回指向 a[0][1] 的指针的大小4或 84 或 8sizeof(*(a[0]1))返回 a[0][1]int的大小44sizeof(a1)返回指向 a[1] 的指针的大小4或 84 或 8sizeof(*(a1))返回第二行的大小1616sizeof(a[0]1)返回指向 a[1] 的指针的大小4或 84 或 8sizeof(*(a[0]1))返回 a[1]一行的大小1616sizeof(*a)返回 a[0]第一行的大小1616sizeof(a[3])返回 int[4] 的大小1616 这些表达式展示了 sizeof 在二维数组和指针上下文中的应用。对于数组sizeof 返回总大小对于指针返回指针的大小。 数组名的意义 sizeof(数组名)这里的数组名表示整个数组计算的是整个数组的大小。数组名这里的数组名表示整个数组取出的是整个数组的地址。除此之外所有的数组名都表示首元素的地址。 6. 指针运算笔试题解析 6.1 题目1 #include stdio.h int main() {int a[5] { 1, 2, 3, 4, 5 };int *ptr (int *)(a 1);printf( %d,%d, *(a 1), *(ptr - 1));return 0; } //程序的结果是什么6.1.1 代码分析 6.1.1.1 初始化数组和指针 int a[5] { 1, 2, 3, 4, 5 };定义了一个包含 5 个元素的数组 a 数组 a 的元素依次是1, 2, 3, 4, 5数组在内存中是连续存储的。 假设数组 a 的起始地址为 0x100则其布局如下每个元素占用 4 个字节 地址值0x10010x10420x10830x10C40x1105 指针的声明 int *ptr (int *)(a 1);这里分两步解析 a 表示 数组 a 的地址其类型是 int (*)[5]即指向一个包含 5 个 int 的数组。 假设 a 的值是 0x100则 a 1 跳过整个数组占用的内存大小是 5 * sizeof(int)即 20 字节。所以a 1 的值是 0x114。 (int *)(a 1) 将 a 1 强制转换为 int * 类型。 ptr 最终指向地址 0x114。 6.1.1.2 输出语句解析 printf(%d,%d, *(a 1), *(ptr - 1));这段代码输出两个值分别是 *(a 1) a 是一个数组名等价于指向数组首元素的指针其类型是 int *。a 1 是指向数组 a 的第二个元素地址 0x104。*(a 1) 的值是该地址存储的值即 2。 *(ptr - 1) ptr 当前指向地址 0x114。ptr - 1 是向后移动一个 int 类型的地址4 字节即 0x110。*(ptr - 1) 的值是 0x110 地址存储的值即 5。 6.1.2 程序输出 printf(%d,%d, *(a 1), *(ptr - 1));根据上面的分析 *(a 1) 的值是 2。*(ptr - 1) 的值是 5。 所以程序的输出是 2,56.1.3 关键点总结 数组名的行为 数组名 a 可以被视为指向数组首元素的指针。通过 a i 可以访问数组第 i 个元素。 a 和 a 的区别 a 是指向数组首元素的指针类型是 int *。a 是整个数组的地址类型是 int (*)[5]。 指针运算和强制类型转换 a 1 会跳过整个数组。将其转换为 int * 类型后可以访问数组后的内存区域。 内存布局 理解数组和指针的内存地址是关键程序中利用了数组边界之后的地址。 6.2 题目2 //在X86环境下 //假设结构体的大小是20个字节 //程序输出的结果是啥 struct Test {int Num;char *pcName;short sDate;char cha[2];short sBa[4]; }*p (struct Test*)0x100000; int main() {printf(%p\n, p 0x1);printf(%p\n, (unsigned long)p 0x1);printf(%p\n, (unsigned int*)p 0x1);return 0; }6.2.1 代码分析 以下代码涉及指针运算、类型转换以及结构体的内存对齐规则。在 X8632 位环境下指针大小为 4 字节指针运算会依据指针的类型进行偏移。 6.2.1.1 结构体对齐与大小 struct Test {int Num; // 4 字节char *pcName; // 4 字节short sDate; // 2 字节char cha[2]; // 2 字节short sBa[4]; // 8 字节 } *p (struct Test*)0x100000; // 假设结构体起始地址为 0x100000 // 假设结构体的总大小为 20 字节成员对齐 成员 int Num 占 4 字节地址偏移为 0。成员 char *pcName 占 4 字节地址偏移为 4。成员 short sDate 占 2 字节地址偏移为 8。成员 char cha[2] 占 2 字节地址偏移为 10。成员 short sBa[4] 占 8 字节地址偏移为 12 至 20。 结构体对齐 按最大成员对齐4 字节总大小为 20 字节。 6.2.1.2 指针初始化 struct Test *p (struct Test*)0x100000;指针 p 指向地址 0x100000表示该地址处存储一个 struct Test 结构体。 6.2.1.3 指针运算与输出分析 第一条语句p 0x1 printf(%p\n, p 0x1);p 是一个指向 struct Test 的指针。p 1的含义是指向下一个 struct Test根据结构体大小跳过 20 字节 新地址 当前地址 sizeof(struct Test) 0x100000 20 0x100014。 输出 0x100014第二条语句(unsigned long)p 0x1 printf(%p\n, (unsigned long)p 0x1);(unsigned long)p将 p强制转换为无符号长整型 指针地址本身不变转换后为整数类型 0x100000。 加法运算是直接对整数进行运算 结果 0x100000 0x1 0x100001。 输出 0x100001第三条语句(unsigned int*)p 0x1 printf(%p\n, (unsigned int*)p 0x1);(unsigned int*)p 将 p 强制转换为 unsigned int* 类型。unsigned int占用 4 字节因此指针加法跳过 4 字节 新地址 当前地址 sizeof(unsigned int) 0x100000 4 0x100004。 输出 0x100004程序输出 综合上述分析程序依次输出 0x100014 0x100001 0x100004关键点总结 指针类型与运算规则 p 1 的偏移取决于指针类型指向的数据大小sizeof(数据类型)。 强制类型转换 (unsigned long)p将指针当作整数进行运算结果直接加上偏移值。(unsigned int*)p改变指针类型指针运算规则改为基于新的类型大小。 结构体对齐和大小 结构体大小受内存对齐规则影响成员偏移和对齐方式决定结构体的总大小。 内存模型与架构 在 X86 架构下指针和 int 类型为 4 字节。 这段代码巧妙利用了指针运算和类型转换的特性使输出产生不同的结果。 6.3 题目3 #include stdio.h int main() {int a[3][2] { (0, 1), (2, 3), (4, 5) };int *p;p a[0];printf( %d, p[0]);return 0; }6.3.1 代码分析 这段代码考察了数组的初始化方式、二维数组的指针使用、以及逗号表达式的行为。 6.3.1.1 二维数组初始化 int a[3][2] { (0, 1), (2, 3), (4, 5) };a 是一个 3×2 的二维数组。 初始化使用了 逗号表达式 (x, y) 在 C 中逗号表达式 (x, y) 的值是表达式 y 的值。例如(0, 1) 的结果是 1(2, 3) 的结果是 3(4, 5) 的结果是 5。 因此数组 a 被初始化为 a[0][0] 1, a[0][1] 3 a[1][0] 5, a[1][1] 未定义值(因为初始化的逗号表达式个数不足填满数组所以未完全指定的元素值是未定义行为) 6.3.1.2 指针赋值 int *p; p a[0];a[0] 是 a 的第一行的地址其类型是 int*表示指向第一行的首元素。赋值后指针 p 指向 a[0][0]。 假设 a 在内存中的布局如下地址以 0x1000 为例 地址值0x100010x100430x100850x100C未定义值0x1010未定义值0x1014未定义值 6.3.1.3 输出操作 printf(%d, p[0]);p[0] 等价于 *(p 0)指向 a[0][0] 的值。根据初始化的结果a[0][0] 1因此输出 1。 6.3.2 程序输出 16.3.3 关键点总结 逗号表达式 (x, y) 返回 y 的值这会影响数组的初始化。 二维数组与指针 a[0] 是第一行的地址类型为 int*。 指针与数组关系 一维数组的指针可以直接用于元素访问例如 p[0]。 未完全初始化的数组 如果初始化的值不足剩余的元素未定义如果全初始化了会被填充为 0。在本例中可能存在未定义值的行为。 6.4 题目4 //假设环境是x86环境程序输出的结果是啥 #include stdio.h int main() {int a[5][5];int(*p)[4];p a;printf( %p,%d\n, p[4][2] - a[4][2], p[4][2] - a[4][2]);return 0; }6.4.1 代码分析 6.4.1.1 定义二维数组 a int a[5][5];a 是一个 5x5 的二维数组包含 5 行每行 5 个 int。在 x86 环境下每个 int 占用 4 字节因此整个数组 a 的总大小是 5 * 5 * 4 100 字节。 6.4.1.2 定义指针 p int (*p)[4];p 是一个指向 int[4] 类型数组的指针。换句话说p 是一个指向长度为 4 的 int 数组的指针。这意味着在通过 p 进行指针运算时它会认为每行是 4 个整数而不是 a 实际的每行 5 个整数。 6.4.1.3 指针赋值 p a;a 是一个 int[5][5] 类型的数组可以隐式转换为 int (*)[5] 类型即指向 int[5] 的指针。尽管 p 的类型是 int (*)[4]编译器在这里允许将 a 赋值给 p但可能会给出警告因为类型并不完全匹配。赋值完成后p 指向数组 a 的起始地址即 a[0][0] 的地址。 6.4.1.4 计算并输出地址差 printf(%p,%d\n, p[4][2] - a[4][2], p[4][2] - a[4][2]);这行代码中的表达式 p[4][2] - a[4][2] 用于计算 p[4][2] 和 a[4][2] 之间的差异。我们逐个解析 p[4][2] 的含义 p 被解释为一个指向 int[4] 的指针。p[4] 表示 p 所指向的“数组”的第五行。p[4][2] 指的是 p[4] 的第三个元素即第 2 个索引位置的地址。因为 p 被解释为一个每行包含 4 个整数的指针p[4] 实际上指向的是数组 a 中的第 4 * 4 16 个整数。所以 p[4][2] 实际上是 a[16 2]即 a[18] 的地址。 a[4][2] 的含义 a 是一个 5x5 的二维数组。a[4][2] 指的是 a 的第五行的第三个元素的地址。在数组 a 中a[4][2] 实际上是 a 中的第 4 * 5 2 22 个元素的地址。 指针差值计算 p[4][2] - a[4][2] 计算的是 a 中第 18 个元素地址和第 22 个元素地址的差值。结果是 18 - 22 -4。 程序输出 printf(%p,%d\n, p[4][2] - a[4][2], p[4][2] - a[4][2]);根据上述分析程序的输出将是 -4, -4关键点总结 二维数组的指针类型 a 是 int[5][5] 类型实际上每行有 5 个整数。p 的类型是 int (*)[4]它将每行视为 4 个整数的数组指针。 指针差值计算 p[4][2] - a[4][2] 计算了两个不同类型指针之间的地址差这个差值是以 int 为单位的。 输出的结果 由于 p 和 a 的类型差异导致了计算结果的偏移输出为 -4。 6.5 题目5 #include stdio.h int main() {int aa[2][5] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int *ptr1 (int *)(aa 1);int *ptr2 (int *)(*(aa 1));printf( %d,%d, *(ptr1 - 1), *(ptr2 - 1));return 0; }6.5.1 代码逐行分析 int aa[2][5] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };6.5.1.1 定义二维数组 aa aa 是一个 2x5 的二维数组即包含 2 行每行有 5 个整数。数组 aa 被初始化为 { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }所以 aa 的内容如下 aa[0][0] 1, aa[0][1] 2, aa[0][2] 3, aa[0][3] 4, aa[0][4] 5 aa[1][0] 6, aa[1][1] 7, aa[1][2] 8, aa[1][3] 9, aa[1][4] 10假设数组 aa 的起始地址为 0x1000则内存布局为每个整数占 4 字节 地址值0x100010x100420x100830x100C40x101050x101460x101870x101C80x102090x102410 int *ptr1 (int *)(aa 1);6.5.1.2 指针 ptr1 的计算 aa 是数组 aa 的地址它的类型是 int (*)[2][5]即指向一个 2x5 整数数组的指针。aa 1 使指针跳过整个 aa 数组。因为 aa 包含 2 行每行 5 个 int所以 aa 的大小是 2 * 5 * sizeof(int) 40 字节。因此aa 1 指向 aa 之后的内存位置即 0x1000 40 0x1028。(int *)(aa 1) 将 aa 1 强制转换为 int* 类型所以 ptr1 最终指向地址 0x1028。 int *ptr2 (int *)(*(aa 1));6.5.1.3 指针 ptr2 的计算 aa 是一个指向 int[5] 的数组指针即 aa 是一个指向第 0 行的指针。aa 1 表示数组的第 1 行的首地址即 aa[1] 的地址。*(aa 1) 解引用得到 aa[1] 的首地址即 aa[1][0]也就是 6 的地址假设为 0x1014。将 *(aa 1) 强制转换为 int*所以 ptr2 最终指向 aa[1][0] 的地址即 0x1014。 printf(%d,%d, *(ptr1 - 1), *(ptr2 - 1));6.5.1.4 输出操作 *(ptr1 - 1) ptr1 指向地址 0x1028ptr1 - 1 指向 0x1024。0x1024 是 aa[1][4] 的地址对应的值是 10。因此*(ptr1 - 1) 的值是 10。 *(ptr2 - 1) ptr2 指向地址 0x1014ptr2 - 1 指向 0x1010。0x1010 是 aa[0][4] 的地址对应的值是 5。因此*(ptr2 - 1) 的值是 5。 6.5.2 程序输出 根据上述分析程序的输出为 10,56.5.3 关键点总结 二维数组的地址计算 aa 是整个数组的地址跳过数组时需要考虑整个数组的大小。aa 1 指向数组的下一行。 指针运算与类型转换 将 aa 1 转换为 int * 使得指针可以按单个 int 进行减法操作。ptr1 - 1 和 ptr2 - 1 的结果取决于二维数组内存布局。 内存布局理解 通过理解二维数组在内存中的排布能够正确解析指针的指向位置。 6.6 题目6 #include stdio.h int main() {char *a[] {work,at,alibaba};char**pa a;pa;printf(%s\n, *pa);return 0; }6.6.1 代码分析 这段代码涉及到字符指针数组和指针运算。让我们逐行分析代码的工作原理和输出。 char *a[] {work, at, alibaba};6.6.1.1 定义字符指针数组 a a 是一个字符指针数组包含 3 个字符串work、at、alibaba。 每个元素 a[i] 都是一个指向字符串的指针因此 a 的类型是 char *[3]。 内存布局如下假设字符串地址为 0x1000、0x1005、和 0x1008具体地址只是举例 a[0]指向字符串 work地址假设为 0x1000a[1]指向字符串 at地址假设为 0x1005a[2]指向字符串 alibaba地址假设为 0x1008 char **pa a;6.6.1.2 定义字符指针的指针 pa 并初始化 pa 是一个指向指针的指针类型是 char **。 pa 被初始化为 a即指向 a 数组的第一个元素 a[0] 的地址。 此时pa 的指向关系如下 pa - a[0] - workpa;6.6.1.3 指针运算 pa 使 pa 移动到数组 a 的下一个元素。 由于 pa 是一个 char** 类型的指针pa 会使 pa 指向 a[1]即 a 中的第二个元素指向字符串 at 的指针。 现在pa 的指向关系如下 pa - a[1] - atprintf(%s\n, *pa);6.6.1.4 打印字符串 *pa 解引用 pa即得到 a[1]指向字符串 at 的指针。printf(%s\n, *pa); 打印 *pa 指向的字符串即 at。 6.6.2 程序输出 根据以上分析程序的输出为 at6.6.3 关键点总结 字符指针数组 a 是一个字符指针数组每个元素都指向一个字符串常量。a 的类型是 char *[3]包含 3 个指针分别指向 work、at、alibaba。 指针的偏移 pa 是一个 char** 类型的指针最初指向 a[0]。pa 使 pa 指向 a[1]即第二个字符串 at 的地址。 指针解引用 *pa 解引用得到 a[1]指向字符串 at 的指针。打印 *pa 输出 at。 通过这些分析程序最终输出 at。 6.7 题目7 #include stdio.h int main() {char *c[] {ENTER,NEW,POINT,FIRST};char**cp[] {c3,c2,c1,c};char***cpp cp;printf(%s\n, **cpp);printf(%s\n, *--*cpp3);printf(%s\n, *cpp[-2]3);printf(%s\n, cpp[-1][-1]1);return 0; }6.7.1 代码分析 char *c[] {ENTER,NEW,POINT,FIRST};6.7.1.1 定义字符指针数组 c c 是一个字符指针数组包含 4 个字符串ENTER、NEW、POINT、FIRST。 每个元素 c[i] 都是一个指向字符串的指针。 假设 c 数组的内存布局如下具体地址只是示例 c[0]指向字符串 ENTERc[1]指向字符串 NEWc[2]指向字符串 POINTc[3]指向字符串 FIRST char** cp[] {c3, c2, c1, c};6.7.1.2 定义字符指针的指针数组 cp cp 是一个数组包含 4 个 char** 类型的元素。 每个元素 cp[i] 是一个指向 c 数组中元素的指针。 cp 的内容为{ c3, c2, c1, c } c3 是指向 c[3] 的指针指向 FIRST;c2 是指向 c[2] 的指针指向 POINT;c1 是指向 c[1] 的指针指向 NEW;c 是指向 c[0] 的指针指向 ENTER。 所以cp 数组的内容布局如下 cp[0]指向 c[3]即指向 FIRSTcp[1]指向 c[2]即指向 POINTcp[2]指向 c[1]即指向 NEWcp[3]指向 c[0]即指向 ENTER char*** cpp cp;6.7.1.3 定义三重指针 cpp cpp 是一个 char*** 类型的指针初始化为 cp。因此cpp 最初指向 cp[0]即 c3也就是指向字符串 FIRST 的指针。 输出语句逐行分析 printf(%s\n, **cpp);6.7.1.4 第一个输出**cpp cpp将 cpp向前移动一个位置从 cp[0]移动到 cp[1]。 此时cpp 指向 cp[1]即 c2。 **cpp解引用两次即 *(*cpp)。 *cpp 是 c2即指向 POINT。**cpp 解引用 c2得到字符串 POINT。 输出 POINTprintf(%s\n, *--*cpp3);6.7.1.5 第二个输出*--*cpp3 cpp将 cpp向前移动一个位置从cp[1]移动到cp[2]。 此时cpp 指向 cp[2]即 c1。 *cpp解引用一次得到 c1即指向 NEW 的指针。--*cpp对 *cpp 进行递减操作即从 c1 移动到 c现在指向 ENTER。*--*cpp现在 *cpp 指向 ENTER解引用得到字符串 ENTER。*--*cpp 3对字符串 ENTER 进行偏移 3 个字符得到 ER。 输出 ERprintf(%s\n, *cpp[-2]3);6.7.1.6 第三个输出*cpp[-2]3 cpp[-2]cpp 当前指向 cp[2]因此 cpp[-2] 指向 cp[0]即 c3。*cpp[-2]解引用 cpp[-2]即 c3指向字符串 FIRST。*cpp[-2] 3对字符串 FIRST 偏移 3 个字符得到 ST。 输出 STprintf(%s\n, cpp[-1][-1]1);6.7.1.7 第四个输出cpp[-1][-1]1 cpp[-1]cpp 当前指向 cp[2]所以 cpp[-1] 指向 cp[1]即 c2。cpp[-1][-1] cpp[-1] 是 c2即指向 POINT。cpp[-1][-1] 相当于 c[1]即 NEW。 cpp[-1][-1] 1对字符串 NEW 偏移 1 个字符得到 EW。 输出 EW6.7.2 程序的最终输出 POINT ER ST EW6.7.3 关键点总结 多重指针解引用 代码通过多层指针操作和数组下标引用来访问字符数组。解引用的顺序和操作顺序如 、--会影响最终的指向结果。 字符数组偏移 偏移操作如 3用于获取字符串的子串。 指针算术运算 cpp[-2]、cpp[-1][-1] 等复杂的指针运算是代码的核心需要清晰理解多级指针之间的关系。 —完—
http://www.hkea.cn/news/14466718/

相关文章:

  • 慈溪市网站开发如何下载音乐到wordpress
  • 优购物官方网站app分销微信小程序开发
  • 池州做网站培训山东平台网站建设价位
  • 网站定制开发微信运营吉林省 网站建设
  • 招聘类网站该怎么做网站建设商务代表工作总结
  • 乐成高端网站建设开发网站赚钱
  • 图片预览网站 末班万网注册域名查询官方网站
  • 网站规划与建设模板哪里网站海报做的比较好
  • wordpress手机端导航栏站长工具seo综合查询adc
  • 西安响应式网站建设公司门头沟做网站公司
  • 设计公司网站设计核心关键词和长尾关键词
  • 网站制作效果好国外有哪些做服装的网站有哪些
  • 麻章网站建设公司桂林市区旅游景点
  • 网站企业地图专业网站定制平台
  • 吉林网站建设设计网页设计适合女生吗
  • 做的很不好的网站seo黑帽教程视频
  • 官方网站建设属于什么科目wordpress自动审核
  • wordpress 站群WordPress 错误记录
  • 欧美风的网站设计制作网站登录
  • 主要网站域名不错的网站建设公
  • 扬州做企业网站登录功能网站怎么做的
  • 网站建设 话术旅游做网站
  • 网站备案完成通知百度搜索引擎
  • 企业网站推广多少钱首信建设网站
  • 化妆品商城网站建设策划方案深圳专业极速网站建设
  • wordpress站群主题地方农产品网站建设
  • 知己图书网站建设策划书上线了小程序怎么收费
  • 桥头做网站wordpress插件的选择
  • 网站建设产品中心专业网站制作公司是如何处理一个优秀网站的
  • 19寸 网站做多大app推广渠道有哪些