[C和指针] ch08. 数组

本文是《C和指针》第八章数组的读书笔记,包含本章总结、课后习题解答、随笔和疑问。总结了数组与指针的关系、数组名特性、下标引用与间接访问等知识;解答习题时指出常见错误和坑点;随笔强调数组和指针结合的重点;疑问涉及指针数组概念对比、矩阵乘法实现等。

第八章:数组

GitHub 链接:ch08. 数组

本章总结及注意点

在这里插入图片描述


部分课后习题解答

8.7 问题

  1. 问题是个好问题,但是,ints 数组下标 0 对应的是 10,1 对应的是 20…大意了啊。参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UGUAX8jD-1609067461424)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221103938.png)]

  2. 运算符优先级不同。第二个等价于 array[j] + i

  3. 不能。因为指针指向数组左边界的前一个位置了,数组越界,赋值是非法的。

  4. 不想写这个转换了,不过回文字符串的判断还是蛮重要的。参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2odFThF3-1609067461427)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221104742.png)]

  5. 绝大多数都是下标…参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jAFaipPs-1609067461429)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221104921.png)]

  6. 书上讲解的很清楚了,关于汇编不做实验了。

  7. 和机器性能及编译器都有关。关于及时可以使用相关库函数。

  8. 警告!!!重点坑点!!!

    第一个赋值中。答案给的解释看不懂,甚至一度认为这样的写法是正确的。有一说一,答案给的解释是真不行!这个问题应该归结到 extern 数组与 extern 指针中去。如果在 main 文件中使用 extern char *data, 编译不会出错,先让 char data[100] = {10};,然后打印 data 的值是 10, 所以猜测 main 文件中 data 的值是 test 文件 data 指针指向地址内容,继续验证让 char data[100] = {0, 0, 0, 0,1};,打印的值是 0,再让 char data[100] = {0, 0, 0,1};,打印的值是 16777216(就是小端模式下的 0x1000000),所以引用方式是错误,编译器把数组的内容解释为指针值(地址),只取数组的前4个字节(32位模式)。应该保持类型匹配,使用 extern char data[],编译器就会把 data 值设置为数组首地址。第二个赋值中。 编译器认为 b 是个数组名,故将 12 加到 b 的存储地址,然后间接访问获取值。然而 b 只是一个指针变量,它的存储位置和数组的起始位置没有任何关系,从内存中提取到后面的 3 个整形位置实际上是从任意变量中取得的,并非从数组的首地址开始向后偏移 3 个整形大小取到数组的第三个元素。所以都是未知,随机的。很不错的一个问题! 参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Us3r14Cf-1609067461431)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221113046.png)]

    这个问题在 《C专家编程》中也提及到了。

    重点参考博文:

  9. int coin_values[] = { 1, 5, 10, 25, 50, 100 };

  10. &array[1][2] 是真滴恶心,看着都已经越界了。但数组的连续存储特性,其就等价于 &array[2][0]。涨知识了!参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36ayh7LE-1609067461432)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221114913.png)]

  11. 关于 array[1][0] + 1 手误算错了。参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MOlnpQZE-1609067461434)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221115502.png)]

  12. 不会…参考答案:当执行“按照元素在内存中出现的顺序对元素进行访问”的操作时…内存中就是按照行主序来进行存储的。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aWAQYV16-1609067461435)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221115708.png)]

  13. 注意 *( *( *array + 1 ) + 2 ) array[0][1][2] 还有 **( *array + 1 ) array[0][1][0]。参考答案:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bRywWJt0-1609067461436)(https://raw.githubusercontent.com/Y-puyu/picture/main/images/20201221120107.png)]

  14. 显然,i 声明为整形指针变量的时候不会报错。

  15. 第二个有意义啊。双指针算法中经常都是这样,&& 有运算符截断的特性,所以前者判断越界后后者就不再进行检查,就避免了越界错误。写在后面…啥也不是。

  16. array1 其作为函数参数,实际上是一个指针变量。指向作为实际参数传递的数组,该指针变量的值可以被修改,同时数组作为参数并没有给其分配内存空间。而 array 是指针常量,因此其值不能更改,且在这个函数中给其分配了 10 个整数的空间。相关代码见 demo02.c,这个写的很抽象,个人理解用 hh…

  17. 第一个参数是一个标量,写不写 const 意义都不是很大。对这份拷贝的修改并不会影响原来的参数,const 关键字的作用并不是防止原来的参数被修改。第二个参数实际上是一个指向整形的指针,传递给函数的是指针的拷贝,对其进行修改并不会影响到指针参数本身,但是可以通过该指针执行间接访问来修改指向的值,const 关键字在此用于防止这种修改。这种 const 关键字的应用是非常广泛的。

  18. void function( int array[][2][5] );

    void function( int (*array)[2][5] );

  19. 在其末尾附加一个空串即可。

    for( kwp = keyword_table; **kwp != '\0'; kwp++ )

8.8 编程练习

  1. 虽然花括号确实需要锻炼,不过这也太硬核了吧…我寻思直接针对下标进行赋值它不香吗?要求静态初始化,而不使用赋值语句,意味着数组必须在静态内存中,还要记得下标从 0 开始,且数据范围大多在 0~255 之间,故采用 unsigned char 类型即可。 demo03.c

  2. 就是个数组值的一一对应的关系。完全可以 if-else 嵌套搞定,但是针对数据量大的时候显然是不合适的。循环是一种非常优雅的解决方案!见 demo04.c,注意初始化数组时,给首项挖个空留着。

  3. 判断 a[i][i] == 1 即可。见 demo05.c。然而答案给出了一个两重循环的写法…但我觉得针对当下问题而言是没必要的。

  4. 思路和上面一样,不重复写了,终究还是知道了矩阵大小。

  5. 模拟矩阵乘法可还行。见 demo06.c。矩阵相关运算是一个很好练习二维、多维数组的题目!矩阵快速幂、矩阵斐波拉切数列等等。

  6. 看不懂题,在这写作文呢?

  7. 同上。

  8. 经典的 dfs 问题啊。我很喜欢!

    请参考我的博文:[dfs] n 皇后(模板+经典)

随笔

无疑是重点中的重点,数组和指针的结合,C 语言接近汇编的效率,在本章终于领略到了!

  1. 数组名的值是一个指针常量,指向内存中数组的起始位置。程序经过编译、链接,链接完成后内存中数组的位置就是固定的,当程序运行时不可移动数组,即不可再修改数组名这个常量值。

  2. 数组名做 sizeof 操作符、左 & 的操作数时不以指针常量来表示。其中,sizeof 返回整个数组的长度,而不是指向数组的指针长度。取址符将产生一个指向数组的指针,而不是指向某个常量值的指针。

  3. 除优先级外,下标引用和间接访问完全相同。负值作为数组下标,从指针间接访问方面很好理解。2[array] = *(2 + (array))

  4. 8.1.4 节,层层优化一个简单的数组拷贝程序,让 C 语言的代码真正可以和汇编代码媲美!使用指针,优化掉计数器,使用寄存器…NP!

  5. 数组和指针。声明定长数组,编译器会为数组元素保留内存空间,然后创建数组,数组名为常量指针,指向这段空间的起始地址。而声明一个指针变量,编译器只为指针变量保存空间,不为其指向的任何值分配空间,且指针变量未被初始化指向任何的内存空间。P150 精华。

  6. 所有的参数都是通过传值方式传递的,数组的传值调用行为是传递给函数的是参数的一份拷贝,即指向数组起始位置的指针的拷贝,函数可以自由地操作其形参指针而不用担心修改对应的作为实参的指针。const 在函数参数中的灵活运用。

  7. 数组形参只是传递的指向数组第一个元素的指针,函数并不为数组参数分配空间。存储与静态内存的数组只初始化一次,默认初始化值为 0。局部变量由于在堆栈上,内存并不相同,在缺省情况下是未初始化的,若在声明中给出了初始值,其实是以一条隐式的赋值语句来初始化。

  8. 字符数组与字符串常量的区别。

  9. 多维数组和指针的联系。一维数组名是 指向元素类型的指针,多维数组以低维数组做元素,即 a[3][10],其数组名是一个指向一个包含 10 个整形元素的数组的指针。

  10. 下标引用的优先级高于间接访问。数组指针和指针数组的区别。指针指向数组,数组内元素是指针。

  11. 多维指针第一维元素大小不重要,编译器可以推导。但是绝不能全部省略第二维及以后各维的长度,若省略则编译器无法对各下标进行求值,第一维在下标求值中也用不到,所以可以省略。一个常见错误就是将二维数组和二级指针混为一谈,相互的错误传参。

  12. 指针数组。int *a[10],下标引用优先级高于间接访问,故在此先执行下标引用,故 a 是某类型的数组,取得数组元素后随即执行间接访问,那么这个元素就是个指针,最终的结果是一个整型值。故 a 中的元素类型就是指向整形的指针。这点经常在字符串指针数组中常用。

疑问

  1. 指针数组本章讲的非常少。另外,指针数组和数组指针的概念其并没有做系统性对比。

  2. 关于数组名是否分配空间问题,是不会。数组名&数组名,都是同一个值,均为该数组的首地址。

  3. 关于 问题 8 那个 extern 天坑问题,将数组名声明为指针,该指针存储的是数组首元素的值,而不是数组的首地址。

  4. 关于矩阵乘法,这是个很重要的一点。并且代码采用了行主序+指针的方式实现,很是简单,且添加 register 优化是一个极大提升效率的做法!这个得参考答案,我并没有实现。

  5. 针对编程 6、7 实在不想看这么多的文字…头大。

<think> 我们正在讨论C语言中指针函数是否能返回数组的问题。根据用户提供的两个引用,我们可以分析两种情况:返回指向栈内存的指针返回指向堆内存的指针。 首先,在引用[1]中,函数`get_c`尝试返回一个指向局部数组`ch`的指针。由于`ch`是函数内的局部变量(位于栈上),当函数返回时,栈空间会被释放,因此返回的指针将指向无效的内存区域。在`main`函数中试图使用这个指针会导致未定义行为(通常程序会崩溃或输出乱码)。 其次,在引用[2]中,函数`paf`使用`malloc`在堆上分配内存,并返回一个指向该内存的指针。堆内存不会在函数返回时自动释放,因此返回的指针是有效的,但调用者必须负责在适当的时候使用`free`来释放内存,否则会造成内存泄漏。 因此,我们可以得出结论:指针函数可以返回数组,但必须注意数组所在的内存区域。返回指向栈内存的数组指针是危险的,因为栈内存会随着函数返回而自动回收。而返回指向堆内存的数组指针是安全的,但需要调用者管理内存。 下面我们分别用两个示例来说明这两种情况: ### 示例1:返回指向栈内存的指针(错误示例) ```c #include <stdio.h> int* get_array() { int arr[3] = {1, 2, 3}; // 栈上分配的数组 return arr; // 警告:返回局部变量的地址 } int main() { int* ptr = get_array(); printf("%d\n", ptr[0]); // 未定义行为,可能输出错误值或崩溃 return 0; } ``` ### 示例2:返回指向堆内存的指针(正确示例) ```c #include <stdio.h> #include <stdlib.h> int* create_array(int size) { int* arr = (int*)malloc(size * sizeof(int)); if (arr != NULL) { for (int i = 0; i < size; i++) { arr[i] = i + 1; } } return arr; // 返回堆上分配的数组指针 } int main() { int size = 5; int* ptr = create_array(size); if (ptr == NULL) { printf("Memory allocation failed.\n"); return 1; } for (int i = 0; i < size; i++) { printf("%d ", ptr[i]); // 正确输出:1 2 3 4 5 } free(ptr); // 必须释放内存 return 0; } ``` ### 注意事项 1. 返回指向栈内存的指针会导致未定义行为,应避免。 2. 返回指向堆内存的指针是安全的,但调用者必须记得释放内存,否则会导致内存泄漏。 3. 另一种方法是使用静态数组(static),但这样会使得数组在程序运行期间一直存在,且函数不可重入(非线程安全),一般不推荐。 ### 总结 指针函数可以返回数组,但必须确保数组内存的有效性。通常,我们通过动态内存分配(堆内存)来实现,并约定调用者负责释放内存。 §§相关问题§§ 1. 在C语言中,如何避免返回局部数组指针导致的错误? 2. 除了动态分配内存,还有哪些方法可以让函数返回一个数组? 3. 在C语言中,函数返回结构体包含数组与返回数组指针有什么区别? </think> 在C语言中,指针函数可以返回数组,但需要特别注意**内存管理****作用域**问题。以下是关键要点示例: ### 一、指针函数返回数组的两种方式 1. **返回指向堆内存的指针(安全)** ```c #include <stdlib.h> int* createArray(int size) { int* arr = (int*)malloc(size * sizeof(int)); // 堆内存 for(int i=0; i<size; i++) arr[i] = i; return arr; // 返回堆内存地址 } ``` - ✅ 优点:函数退出后内存仍有效 - ❗ 注意:调用者必须手动`free()`释放内存 2. **返回指向静态数组指针(谨慎使用)** ```c int* getStaticArray() { static int arr[3] = {1,2,3}; // static修饰 return arr; // 返回静态存储区地址 } ``` - ✅ 优点:无需手动释放 - ⚠️ 缺点:非线程安全,内容会被后续调用覆盖 --- ### 二、错误示例:返回局部数组(危险!) ```c int* invalidReturn() { int localArr[3] = {1,2,3}; // 栈内存 return localArr; // 错误!函数退出后内存失效 } ``` - ❌ 后果:访问时出现**未定义行为**(崩溃/乱码) - 🔍 根本原因:栈内存随函数退出自动释放 --- ### 三、推荐实践(带完整示例) ```c #include <stdio.h> #include <stdlib.h> // 返回堆内存数组 int* createIntArray(int size) { int* arr = malloc(size * sizeof(int)); for(int i=0; i<size; i++) arr[i] = i*10; return arr; } int main() { int* myArr = createIntArray(5); // 使用数组 for(int i=0; i<5; i++) printf("%d ", myArr[i]); // 输出:0 10 20 30 40 free(myArr); // 必须释放内存! return 0; } ``` --- ### 四、关键注意事项 1. **内存所有权**:明确由调用者负责释放堆内存 2. **数组长度传递**:需额外返回数组长度(因`sizeof`对指针失效) 3. **替代方案**:通过参数传入数组指针(更安全) ```c void fillArray(int* arr, int size) { for(int i=0; i<size; i++) arr[i] = i; } ``` --- ### 总结 | 返回方式 | 内存区域 | 是否需要释放 | 安全性 | |----------------|----------|--------------|--------| | 堆内存(malloc) | 堆 | 是 | ✅ 高 | | 静态数组 | 数据段 | 否 | ⚠️ 中 | | 局部数组 | 栈 | - | ❌ 危险 | > 📌 最佳实践:优先使用**动态内存分配+调用者释放**的组合,或通过参数传递数组指针[^1][^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值