:::tips 本章习题均要求用指针方法处理。本质上和前面的重复,无非就是多出几个 * 而已,真正指针用途还是对于动态内存方面比较适合。也就是指针的引入完全是因为函数设计模式(变量的拷贝问题)节约内存的一种做法。 :::

1、输入 3 个整数,按由小到大的顺序输出。

解题思路:主要考察对函数传入指针(地址)而改变原来的值,即形参依然是拷贝实参的值,但是它的变量类型特殊就在于它是记录实参指针的内存的地址。

  1. #include <stdio.h>
  2. void swap(int *a, int *b) {
  3. int tmp = *a;
  4. *a = *b;
  5. *b = tmp;
  6. }
  7. int main() {
  8. int n1, n2, n3, *p1 = &n1, *p2 = &n2, *p3 = &n3;
  9. printf("Please input three numbers:");
  10. scanf("%d %d %d", &n1, &n2, &n3);
  11. if (n1 > n2) { // 将最小值给 n1
  12. swap(p1, p2);
  13. }
  14. if (n1 > n3) { // 将最小值给 n1
  15. swap(p1, p3);
  16. }
  17. if (n2 > n3) { // 将最大值给 n3
  18. swap(p2, p3);
  19. }
  20. printf("ascending order: %d %d %d \n", n1, n2, n3);
  21. return 0;
  22. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/threeSort.c -o ./bin/threeSort
b12@PC:~/chapter8$ ./bin/threeSort
Please input three numbers:5 9 7
ascending order: 5 7 9
b12@PC:~/chapter8$ ./bin/threeSort
Please input three numbers:-8 4 465
ascending order: -8 4 465 :::

2、输入 3 个字符串,按由小到大的顺序输出。

解题思路:本体同上面一样,只不过换成字符数组而已,本质上没改变。注意两两交换不能像上面数字交换,因为它是一个容器,数组地址是左值无法修改,因此只能将容器内所有的值一一交换。

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define N 50
  4. char tmp[N];
  5. void swap(char *a, char *b) {
  6. strcpy(tmp, a);
  7. strcpy(a, b);
  8. strcpy(b, tmp);
  9. }
  10. int main() {
  11. char str1[N], str2[N], str3[N], *p1 = str1, *p2 = str2, *p3 = str3;
  12. printf("Please input three strings:\n");
  13. fgets(str1, N, stdin);
  14. fgets(str2, N, stdin);
  15. fgets(str3, N, stdin);
  16. if (strcmp(str1, str2) > 0) { // 将最小值给 str1
  17. swap(p1, p2);
  18. }
  19. if (strcmp(str1, str3) > 0) { // 将最小值给 str1
  20. swap(p1, p3);
  21. }
  22. if (strcmp(str2, str3) > 0) { // 将最大值给 n3
  23. swap(p2, p3);
  24. }
  25. printf("ascending order:\n%s%s%s", str1, str2, str3); // 自带换行符
  26. return 0;
  27. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/threeStrSort.c -o ./bin/threeStrSort
b12@PC:~/chapter8$ ./bin/threeStrSort
Please input three strings:
zcxger arwea
fsdfafa asfa
adsa gdsg
ascending order:
adsa gdsg
fsdfafa asfa
zcxger arwea :::

3、输入 10 个整数,将其中最小的数与第一个数对换,把最大的数与最后一个数对换。写 3 个函数:

  • 输入 10 个数
  • 进行处理
  • 输出 10 个数

解题思路:之前分析过,涉及到两次搬动,需要注意搬动的东西即最值是否因为搬动而交换。
一次循环比较,然后记录最大最小值地址(前面实现的是索引,原理是相同的)

  1. 使用两个指针 int *pMax, *Min = arr 先假设数组中最大值和最小值都是第一个(当然也可以额外判断)
  2. 先将最小的搬动到最前面即 arr[0], *pMin = *pMin, arr[0] ,但是此举可能会有纰漏
    1. *pMax = arr[0] ,那么将会导致 arr[0] 元素被搬到 pMin 所指向的地址去,因此此时最大值位置就变为 pMin 所指向的地址
    2. *pMax != arr[0] ,最大值位置就变为 pMax 所指向的地址 ```c

      include

      define N 10

void solve(int arr[]) { // 1.更新最大最小指针所指向地址 int pMin = arr, pMax = arr; for (int i = 1; i < N; i++) { if (pMin > arr[i]) { pMin = arr + i; } if (pMax < arr[i]) { pMax = arr + i; } } // 2.交换最小值到最前面 int tmp = pMin; pMin = arr[0]; arr[0] = tmp; // 3.交换最大值到后面 if (arr == pMax) { // 如果第一个是最大值,被交换到 pMin 位置了 pMax = pMin; } tmp = pMax; pMax = arr[N-1]; arr[N-1] = tmp; }

int main() { int arr[N]; printf(“Please input %d numbers:”, N); for (int i = 0; i < N; i++) { scanf(“%d”, arr + i); } solve(arr); for (int i = 0; i < N; i++) { printf(“%d “, arr[i]); } printf(“\n”); return 0; }

  1. **编译运行:**
  2. :::success
  3. b12@PC:~/chapter7$ gcc -Wall ./src/minMaxSwap.c -o ./bin/minMaxSwap<br />b12@PC:~/chapter7$ ./bin/minMaxSwap<br />Please input 10 numbers:32 24 56 78 1 98 36 44 29 6<br />1 24 56 78 32 6 36 44 29 98<br />b12@PC:~/chapter7$ ./bin/minMaxSwap<br />Please input 10 numbers:98 24 56 78 1 32 36 44 29 6<br />1 24 56 78 6 32 36 44 29 98
  4. :::
  5. <a name="Oyyza"></a>
  6. # 4、有 n 个整数,使前面各数顺序向右移动 m 个位置,最后 m 个数变成前面 m 个数。协议函数实现以上功能,在主函数中输入 n 个整数和输出调整后的 n 个数。
  7. ![image.png](https://cdn.nlark.com/yuque/0/2020/png/1438957/1595403606362-80e05c18-07e2-4197-a9d1-c746396ba0f4.png#height=109&id=nLZET&margin=%5Bobject%20Object%5D&name=image.png&originHeight=218&originWidth=834&originalType=binary&size=11059&status=done&style=shadow&width=417)<br />[189.旋转数组(中等)](https://www.yuque.com/u1190503/lsb24f/aom10i?view=doc_embed)
  8. <a name="Nc5SI"></a>
  9. # 5、有 n 个人围成一圈,顺序排号。从第 1 个人开始报数(从 1 到 3 报数),凡报到 3 的人退出圈子,问最后留下的是原来的第几号的那位。
  10. [约瑟夫环](https://www.yuque.com/u1190503/lsb24f/bho2b0?view=doc_embed)<br />强调一点就是上面标号是从 `0` 开始,而不是题意中的 `1` ,因此需要进行结果的 `+1` 处理。
  11. ```c
  12. #include <stdio.h>
  13. int lastRemaining(int n, int m){
  14. int ans = 0;
  15. for (int i = 2; i <= n; i++) {
  16. ans = (ans + m) % i;
  17. }
  18. return ans;
  19. }
  20. int main() {
  21. printf("Please input n, m:");
  22. int n, m;
  23. scanf("%d %d", &n, &m);
  24. printf("The lastRemaining is %d\n", lastRemaining(n, m) + 1); // 别忘+1
  25. return 0;
  26. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/circle.c -o ./bin/circle
b12@PC:~/chapter8$ ./bin/circle
Please input n, m:8 3
The lastRemaining is 7 :::

6、写一函数,求一个字符串的长度。在 main 函数中输入字符串,并输出其长度。

解题思路:本题关键在于 根据字符数组的性质,在字符结束位置有 '\0' 垫底。但是需要注意 IO 输入问题, scanf("%s") 无法解决输入含有空格的字符串!

  1. #include <stdio.h>
  2. int length(char *str) {
  3. int len = 0;
  4. while (*str != '\n' && *str != '\0') { // 因为可能有\n
  5. len++; // 长度+1
  6. str++; // 指针移动
  7. }
  8. return len;
  9. }
  10. int main () {
  11. char str[20];
  12. printf("Please input string:");
  13. fgets(str, 20, stdin); // 防止溢出+\n
  14. printf("The length of '%s' is %d\n", str, length(str));
  15. return 0;
  16. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/strlen.c -o ./bin/strlen
b12@PC:~/chapter8$ ./bin/strlen
Please input string:I am come from Sichuan province.
The length of ‘I am come from Sich’ is 19
b12@PC:~/chapter8$ ./bin/strlen
Please input string:I am Chinese!
The length of ‘I am Chinese!
‘ is 13 ::: 另一种方式就是直接使用 scanf("%[^\n]s") 的正则输入判断,但是这种会遇到的 \n 字符会在缓冲区内。(见下一题

7、有一字符串,包含 n 个字符。写一函数,将此字符串中从第 m 个 字符开始的全部字符复制成为另一个字符串。

解题思路

  1. 对于字符串的 IO ,作者在书上也改用 gets() 函数,可能输出的字符串含有空格。
  2. 先要验证输入 m 是否合法,即 课后习题 - 图7 才可以!

下面将会使用两种方式进行处理 IO 输入问题:

  1. 使用正则表达式的 scanf("%[^\n]s") ,将会把 \n 放入缓冲区,若此时进行输入字符 scanf("%c") 将会导致意想不到的错误。但是如果是输入数字scanf("%d") 呢? ```c

    include

    include

    define MAXSIZE 20

void copystr(char dest, char src, int m) { // 书本上先让 src 指针加到 src + m,属实牛逼 src += m - 1; // 传参是指针(右值),不是数组(左值),因此可以修改 while (‘\0’ != src) { dest = src; dest++, src++; } dest = ‘\0’; // 如果该块内存没初始化,一定要加上 }

int main() { char str1[MAXSIZE], str2[MAXSIZE]; printf(“Please input string:”); scanf(“%[^\n]s”, str2); int m, n = strlen(str2); printf(“Please input the place to copy:”); scanf(“%d”, &m); if (m > n) { printf(“Invalid Input\n”); } else { copystr(str1, str2, m); printf(“res:%s\n”, str1); } return 0; }

  1. **编译运行:很明显看到缓冲区内的 **`**\n**` 不会影响 `scanf("%d", &m);` 的输入
  2. :::success
  3. b12@PC:~/chapter8$ gcc -Wall ./src/copystr.c -o ./bin/copystr<br />b12@PC:~/chapter8$ ./bin/copystr<br />Please input string:reading_room<br />Please input the place to copy:9<br />res:room
  4. :::
  5. 2. 使用 `fgets(buffer, N, stdin)` 进行字符串的 `IO` 。当输入字符长度(**包含最后看不见的换行符**) ![](https://cdn.nlark.com/yuque/__latex/318e1938b29164eb9f3fae32c748a826.svg#card=math&code=%5Cge%20N&height=16&id=Ap0aM) 时,实际上接受 `N - 1` 个,然后 `buffer[N-1] = '\0'`;若输入字符长度 (**包含最后看不见的换行符**)![](https://cdn.nlark.com/yuque/__latex/8cb4728e96b53b3286d47e0489fd9fb9.svg#card=math&code=%5Clt%20N&height=16&id=Hu3sf) 时,实际上接受 `x + 1` 个,其中 `x` 是“显示”输入的字符个数, `+1` 是“隐式”的 `\n` ,因而有`buffer[x] = '\n'`;最后默认加上 `buffer[x+1] = '\0'`
  6. 这个非常麻烦,需要判断上面输入字符真实长度.因此可能判断很多.通常偷懒使用 `gets` 函数(会警告)或者向上面一样进行正则输入.
  7. <a name="4pd4F"></a>
  8. # 8、输入一行文字,找出其中大写字母、小写字母、空格、数字以及其他字符各有多少。
  9. **解题思路**:同之前一样,没有任何区别,无非就是把数组的索引形式 `a[i]` 变为指针的加法形式 `*(p + i)` 。其实在进行编译时,会将前者转化为后者进行,也就是最终执行效率都是一样。但是前者可读性更好!
  10. 而对于最烦的 `IO` 来看,可以输入数组判断也可以直接使用 `getchar();` 函数进行。本例使用 `fgets()` 其类似书中以 `\n` 进行判断。仍然注意最终 `buffer` 倒数第二个位置是不是 `\n` 问题!因此通过条件 `while (*p != '\n' && *p != '\0')` 进行遍历。
  11. ```c
  12. #include <stdio.h>
  13. #include <string.h>
  14. #define MAXSIZE 21
  15. int main() {
  16. char str[MAXSIZE], *p = str;
  17. printf("Please input string(<=%d):", MAXSIZE - 1);
  18. fgets(str, MAXSIZE, stdin);
  19. // 1.遍历
  20. int upper = 0, lower = 0, blank = 0, digit = 0, other = 0;
  21. while (*p != '\n' && *p != '\0') { // 注意条件
  22. if ('A' <= *p && *p <= 'Z') {
  23. upper++;
  24. } else if ('a' <= *p && *p <= 'z') {
  25. lower++;
  26. } else if ('0' <= *p && *p <= '9') {
  27. digit++;
  28. } else if (' ' == *p) {
  29. blank++;
  30. } else {
  31. other++;
  32. }
  33. p++;
  34. }
  35. // 2.输出结果
  36. printf("upper = %d, lower = %d, blank = %d, digit = %d, other = %d\n",
  37. upper, lower, blank, digit, other);
  38. return 0;
  39. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/countChar.c -o ./bin/countChar
b12@PC:~/chapter8$ ./bin/countChar
Please input string(<=20):Today is 2008/8/8
upper = 1, lower = 6, blank = 2, digit = 6, other = 2 :::

9、写一函数、将一个 3×3 的整型矩阵转置。

解题思路:明确二维数组也是储存也是连续的即可。想要实现“跳跃一行”,必定要跨完该行所有元素(“列”大小)然后到下一行的第 0 列。
image.png
另外就是转置就是 课后习题 - 图9,用 int (*p)[colSize] 指针实现就是 课后习题 - 图10,用 int *p 指针实现就是 课后习题 - 图11

  1. #include <stdio.h>
  2. #define N 3
  3. void printOut(int p[][N]) {
  4. for (int row = 0; row < N; row++) {
  5. for (int col = 0; col < N; col++) {
  6. printf("%d ", p[row][col]);
  7. }
  8. printf("\n");
  9. }
  10. }
  11. void move1(int *p) {
  12. for (int i = 0; i < N; i++) {
  13. for (int j = i; j < N; j++) { // 一行交换 N - i
  14. int tmp = *(p + i * N + j);
  15. *(p + i * N + j) = *(p + j * N + i);
  16. *(p + j * N + i) = tmp;
  17. }
  18. }
  19. }
  20. void move2(int (*p)[N]) {
  21. for (int i = 0; i < N; i++) {
  22. for (int j = i; j < N; j++) { // 一行交换 N - i
  23. int tmp = *(*(p + i) + j);
  24. *(*(p + i) + j) = *(*(p + j) + i);
  25. *(*(p + j) + i) = tmp;
  26. }
  27. }
  28. }
  29. int main () {
  30. int matrix[N][N];
  31. printf("Please input %d numbers: ", N * N);
  32. // 1.IO输入
  33. for (int row = 0; row < N; row++) {
  34. for (int col = 0; col < N; col++) {
  35. scanf("%d", &matrix[row][col]);
  36. }
  37. }
  38. // 2.两种方式旋转
  39. move1(matrix[0]); // &matrix[0][0]
  40. printf("int *p\n");
  41. printOut(matrix);
  42. move2(matrix); // matrix
  43. printf("int int (*p)[N]\n");
  44. printOut(matrix);
  45. return 0;
  46. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/matrixInvert.c -o ./bin/matrixInvert
b12@PC:~/chapter8$ ./bin/matrixInvert
Please input 9 numbers: 1 2 3 4 5 6 7 8 9
int p
1 4 7
2 5 8
3 6 9
int int (
p)[N]
1 2 3
4 5 6
7 8 9 ::: 注意:千万不要用 int **p 以为它就是可以指向二维数组,是错的!因为其指针移动跨步是 sizeof(int *) 而不是 sizeof(int) * colSize。因此会出现访问出错。

10、将一个 5×5 的举证最大的元素放在中心,4 个角分别放在 4 个最小的元素(顺序为从左到右,从上到下依次从小到大存放),写一函数实现之。用 main 函数调用。

解题思路:其实就交换和找最值操作。

  1. 找到最大值并将其放在中心:课后习题 - 图12
  2. 找到最小值并将其放在左上角课后习题 - 图13
  3. 找到第二小值并将其放在右上角课后习题 - 图14
  4. 找到第三小值并将其放在左下角课后习题 - 图15
  5. 找到第四小值并将其放在右下角课后习题 - 图16

关键就是怎么比较后面四个,如何防止重复?

  • 指针位置不重叠:这种写法很不直接,易错!
  • 不再看已确定矩阵位置:下标法进行定位去除。搞好判断关系是非已知点(条件或) :::tips 书本上代码是有错误的,因为作者是一次遍历找到最大最小值的地址,然后交换。同本章第 3 题一样,当出现左上角为最大值时即 int *pMax = &matrix[0][0] 若此时最小值恰好是正中间即 int *pMin = &matrix[2][2],交换最大值到正中央后,由于 int *pMin = &matrix[2][2] 那么这个时候 matrix[2][2] 是最大值!而后再进行交换就是错误的!
    解决办法很简单:
  1. 找到最大值后搬动再找最小值(如下方翻转1中实现“中间大四周小”)
  2. 同时找到最大最小值,然后搬动过程中一定要确保被替换的那个数是否是另一个最值(即兼顾两者,如下方翻转2中实现“中间小四周大”) ::: ```c

    include

    define N 5

void printOut(int p[][N]) { for (int row = 0; row < N; row++) { for (int col = 0; col < N; col++) { printf(“%d “, p[row][col]); } printf(“\n”); } }

void change1(int p) { // 1.找到矩阵最大值位置,并把它放中间 int pMax = p, pMin = p; // 初始化比较方便 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (pMax < (p + 5 i + j)) { // 值比较 pMax = p + 5 i + j; // 地址赋值 } } } int tmp = (p + 2 N + 2); (p + 2 N + 2) = pMax; pMax = tmp; // 2.找到矩阵最小值位置,并把它放左上角 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (pMin > (p + 5 i + j)) { // 值比较 pMin = p + 5 i + j; // 地址赋值 } } } tmp = (p + 0 N + 0); (p + 0 N + 0) = pMin; pMin = tmp; // 3.找第二小值并交换到右上角 pMin = p + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不等于最小值且比当前值小 if (p + 5 i + j != p && pMin > (p + 5 i + j)) {
pMin = p + 5
i + j; // 第二小的地址赋值 } } } tmp = (p + 0 N + N - 1); (p + 0 N + N - 1) = pMin; pMin = tmp; // 4.找第三小值并交换到左下角 pMin = p + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不等于 min,second 且比当前值小 if (p + 5 i + j != p && p + 5 i + j != p + 0 N + N - 1 && pMin > (p + 5 i + j)) {
pMin = p + 5 i + j; // 第三小的地址赋值 } } } tmp = (p + (N - 1) N + 0); (p + (N - 1) N + 0) = pMin; pMin = tmp; // 5.找第四小值并交换到右下角 pMin = p + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不等于 min,second,third 且比当前值小 if (p + 5 i + j != p && p + 5 i + j != p + 0 N + N - 1 && p + 5 i + j != p + (N - 1) N + 0 && pMin > (p + 5 i + j)) {
pMin = p + 5
i + j; // 第四小的地址赋值 } } } tmp = (p + (N - 1) N + N - 1); (p + (N - 1) N + N - 1) = pMin; pMin = tmp; }

void change2(int (p)[N]) { // 1.找到矩阵最大小值位置 int pMax = p[0], pMin = p[0]; // 初始化比较方便 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { if (pMax < ((p + i) + j)) { // 值比较 pMax = (p + i) + j; // 地址赋值 } if (pMin > ((p + i) + j)) { // 值比较 pMin = (p + i) + j; // 地址赋值 } } } // 2.将最小值放中间,最大值放左上角 int tmp = ((p + 2) + 2); ((p + 2) + 2) = pMin; pMin = tmp; if ((p + 2) + 2 == pMax) { // 如果最大值被换走 pMax = pMin; } tmp = ((p + 0) + 0); ((p + 0) + 0) = pMax; pMax = tmp; // 3.找第二大值并交换到右上角 pMax = (p + 0) + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不遍历确定的位置更新当前较大值 if (i == 0 && j == 0) continue; if (pMax < ((p + i) + j)) {
pMax = (p + i) + j; // 第二大的地址赋值 } } } tmp = ((p + 0) + N - 1); ((p + 0) + N - 1) = pMax; pMax = tmp; // 4.找第三大值并交换到左下角 pMax = (p + 0) + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不遍历确定的位置更新当前较大值 if ((i == 0 && j == 0) || (i == 0 && j == N - 1)) continue; if (pMax < ((p + i) + j)) {
pMax =
(p + i) + j; // 第三大的地址赋值 } } } tmp = ((p + N - 1) + 0); ((p + N - 1) + 0) = pMax; pMax = tmp; // 5.找第四大值并交换到右下角 pMax = (p + 0) + 1; // 注意此时比较基准是 a[0][1] 元素 for (int i = 0; i < N; i++) { for (int j = 0; j < N; j++) { // 不遍历确定的位置更新当前较大值 if ((i == 0 && j == 0) || (i == 0 && j == N - 1) || (i == N - 1 && j == 0)) continue; if (pMax < ((p + i) + j)) {
pMax = (p + i) + j; // 第四大的地址赋值 } } } tmp = ((p + N - 1) + N - 1); ((p + N - 1) + N - 1) = pMax; *pMax = tmp; }

int main () { int matrix[N][N]; printf(“Please input %d numbers: “, N N); // 1.IO输入 for (int row = 0; row < N; row++) { for (int col = 0; col < N; col++) { scanf(“%d”, &matrix[row][col]); } } // 2.两种方式交换 change1(matrix[0]); // &matrix[0][0] printf(“int p\n”); printOut(matrix); change2(matrix); // matrix printf(“\nint (*p)[N]\n”); printOut(matrix); return 0; }

  1. **编译运行**:
  2. :::success
  3. b12@PC:~/chapter8$ gcc -Wall ./src/matrixExchange.c -o ./bin/matrixExchange<br />b12@PC:~/chapter8$ ./bin/matrixExchange<br />Please input 25 numbers: 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11<br />int *p<br />11 34 33 32 12<br />30 29 28 27 26<br />25 24 35 22 21<br />20 19 18 17 16<br />13 23 15 31 14
  4. int (*p)[N]<br />35 12 13 14 34<br />30 29 28 27 26<br />25 24 11 22 21<br />20 19 18 17 16<br />33 23 15 31 32
  5. :::
  6. <a name="GlgZl"></a>
  7. # 11、在主函数中输入 `10` 个等长的字符串。用另一函数对它们排序。然后在主函数输出这 `10` 个已排好序的字符串。
  8. **解题思路**:包含字符串 `IO` 问题和排序问题(冒泡/选择排序交换过程中**需要进行逐个字符数组的拷贝**,而非整型变量交换。原因是数组是多个元素,当然要一个个进行复制交换啊)
  9. 1. 字符串 `IO` 问题,建议使用 `fgets()` ,因为所有都是等长,最后的 `\n` 字符要么都是一起存在,不影响排序大小。
  10. 1. 指针传参也会使用两种方式: `char *` `char (*)p[N]` 处理二维数组问题
  11. ```c
  12. #include <stdio.h>
  13. #include <string.h>
  14. #define ROW 10
  15. #define COL 21
  16. void bubbleSort(char p[][COL]) {
  17. char tmp[COL]; // 交换中转站
  18. for (int i = 0; i < ROW - 1; i++) { // 一共 n-1 轮
  19. for (int j = 0; j < ROW - 1 - i; j++) { // 一轮 n-1-i 趟比较
  20. if (strcmp(p[j], p[j+1]) > 0) { // ascending
  21. strcpy(tmp, p[j+1]); // (1)先把str[j]放在tmp
  22. strcpy(p[j+1], p[j]); // (2)str[j+1]交换到str[j]去
  23. strcpy(p[j], tmp); // (3)tmp交换到str[j+1]去
  24. }
  25. }
  26. }
  27. }
  28. void selectSort(char (*p)[COL]) {
  29. char tmp[COL]; // 交换中转站
  30. for (int i = 0; i < ROW - 1; i++) { // 一共 n-1 轮
  31. int idx = i; // 此轮最大值下标
  32. for (int j = i + 1; j < ROW; j++) { // 在[i+1:]区间找最大值
  33. if (strcmp(p[idx], p[j]) < 0) { // descending
  34. idx = j; // 更新最大下标,减少频繁地交换字符数组
  35. }
  36. }
  37. if (idx != i) { // 把最大值整到对应位置去
  38. strcpy(tmp, p[i]); // (1)先把p[i]放在tmp
  39. strcpy(p[i], p[idx]); // (2)p[idx]交换到p[i]去
  40. strcpy(p[idx], tmp); // (3)tmp交换到p[idx]去
  41. }
  42. }
  43. }
  44. int main () {
  45. char str[ROW][COL];
  46. // 1.字符 IO
  47. printf("Please input %d string(<=%d)\n", ROW, COL - 1);
  48. for (int i = 0; i < ROW; i++) {
  49. scanf("%[^\n]s", str[i]);
  50. getchar(); // 注意必须!
  51. }
  52. // 2.排序1:升序,传参方式:char *p
  53. bubbleSort(str);
  54. printf("\nAscending order:\n");
  55. printf("%s", str[0]);
  56. for (int i = 1; i < ROW; i++) {
  57. printf(" < %s", str[i]);
  58. }
  59. // 3.排序2:降序,传参方式:char (*p)[COL]
  60. selectSort(str);
  61. printf("\nDecending order:\n");
  62. for (int i = 0; i < ROW - 1; i++) {
  63. printf("%s > ", str[i]);
  64. }
  65. printf("%s\n", str[ROW-1]);
  66. return 0;
  67. }

编译运行:(注意这里的连续间隔字符输出方式,单独处理第一个或者最后一个即可) :::success b12@PC:~/chapter8$ gcc -Wall ./src/strSort.c -o ./bin/strSort
b12@PC:~/chapter8$ ./bin/strSort
Please input 10 string(<=20)
China
Japan
Yeman
Pakistan
Mexico
Brazil
Iceland
Canada
Mongolia
Korea
Ascending order:
Brazil < Canada < China < Iceland < Japan < Korea < Mexico < Mongolia < Pakistan < Yeman
Decending order:
Yeman > Pakistan > Mongolia > Mexico > Korea > Japan > Iceland > China > Canada > Brazil :::

12、用指针数组处理上一题目,字符串不等长。

解题思路:其实本题就是考察指针数组和内存之间的关系。很多人以为指针数组啊,数组内的元素就是指针啊,可以指向一个字符串啊。确实没问题,但是前提是这个字符是开辟储存空间的,这一点尤其是在进行 IO 输入最容易忘记。

  1. 静态储存空间:系统自动帮你配置好了。

    1. #include <stdio.h>
    2. char *Month[] = {"", "January", "February", "March", "April", "May", "June",
    3. "July", "August", "September", "October", "November", "December"};
    4. int main() {
    5. printf("Please input number:(1~12):");
    6. int mon;
    7. scanf("%d", &mon);
    8. printf("%s\n", Month[mon]);
    9. return 0;
    10. }

    如上是一个简单的月份判断程序,使用到字符指针数组。字符串常量都是存储在静态储存区内的!(read-only) :::success b12@PC:~/chapter8$ gcc -Wall ./src/test.c -o ./bin/test
    b12@PC:~/chapter8$ ./bin/test
    Please input number:(1~12):12
    December
    b12@PC:~/chapter8$ ./bin/test
    Please input number:(1~12):4
    April :::

  2. 想自定义输入字符串?

例如另外一个易错的用法就是以为定义了字符指针数组,就认为开辟了存储空间。随便 IO 输入。

  1. #include <stdio.h>
  2. int main() {
  3. char *str[12];
  4. // 1.字符 IO
  5. printf("Please input 12 string\n");
  6. for (int i = 0; i < 12; i++) {
  7. scanf("%[^\n]s", str[i]);
  8. getchar(); // 注意必须!
  9. // fgets(str[i], COL, stdin);
  10. }
  11. for (int i = 0; i < 12; i++) {
  12. printf("%s ", str[i]);
  13. }
  14. return 0;
  15. }

编译运行: :::danger b12@PC:~/chapter8$ gcc -Wall ./src/test.c -o ./bin/test
b12@PC:~/chapter8$ ./bin/test
Please input 12 string
Xiaoming
Segmentation fault (core dumped) ::: 执行失败了,为什么呢?也是因为野指针,字符指针数组内所指向的地址位置,你就敢往里面放字符??不内存错误就怪了。

因此本题的关键就是明白,字符指针数组只是存地址,不是开辟内存,它就想一个索引表一样,只是方便操作,而实际存储字符是用二维数组进行。(其就类似简单粗暴的哈希表,如下所示)
image.png
因此排序也只是对这个字符指针数组内的元素进行排序,而二维指针数组的顺序是没有改变的!!这一点非常关键。
image.png

  1. #include <stdio.h>
  2. #include <string.h>
  3. #define ROW 10
  4. #define COL 21
  5. void insertSort(char *p[ROW]) {
  6. // 使用插入排序进行,从后往前插入 n-1 次
  7. for (int i = 1; i < ROW; i++) {
  8. char *idx = p[i]; // 先把它挖出来
  9. int j = i;
  10. while (j > 0 && strcmp(p[j], p[j-1]) > 0) { // 把大的放前面
  11. char *tmp = p[j];
  12. p[j] = p[j-1];
  13. p[j-1] = tmp;
  14. j--;
  15. }
  16. p[j] = idx; // 此时 j 停留在该插入地方
  17. }
  18. }
  19. int main () {
  20. char str[ROW][COL], *p[ROW];
  21. // 1.字符 IO
  22. printf("Please input %d string(<=%d)\n", ROW, COL - 1);
  23. for (int i = 0; i < ROW; i++) {
  24. scanf("%[^\n]s", str[i]);
  25. getchar(); // 注意必须!
  26. p[i] = str[i]; // 赋值地址
  27. }
  28. // 2.排序:升序,传参方式:char *p[ROW]
  29. insertSort(p);
  30. printf("\nid\t\tDescend\t\tid\t\tOriginal\n");
  31. for (int i = 0; i < ROW; i++) {
  32. printf("%p\t%s\t%p\t\t%s\n", p[i], p[i], str[i], str[i]);
  33. }
  34. return 0;
  35. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/strSort.c -o ./bin/strSort
b12@PC:~/chapter8$ ./bin/strSort
Please input 10 string(<=20)
China
Japan
Yeman
Pakistan
Mexico
Korea
Brazil
Iceland
Canada
Mongolia ::: 终端的制表符对齐不了,换成如下形式就明白字符指针数组原理了。

id Descend: char *p[10] id Original: arr[10][20]
0x7fffe54d87ca Yeman 0x7fffe54d87a0 China
0x7fffe54d87df Pakistan 0x7fffe54d87b5 Japan
0x7fffe54d885d Mongolia 0x7fffe54d87ca Yeman
0x7fffe54d87f4 Mexico 0x7fffe54d87df Pakistan
0x7fffe54d8809 Korea 0x7fffe54d87f4 Mexico
0x7fffe54d87b5 Japan 0x7fffe54d8809 Korea
0x7fffe54d8833 Iceland 0x7fffe54d881e Brazil
0x7fffe54d87a0 China 0x7fffe54d8833 Iceland
0x7fffe54d8848 Canada 0x7fffe54d8848 Canada
0x7fffe54d881e Brazil 0x7fffe54d885d Mongolia

20、用指向指针的指针的方法对 5 个字符串排序并输出。

解题思路:如果明白上面的道理,那么本题指向指针的指针(数组名字就是地址),即二级指针也是轻而易举。即此时二级指针是指向这个指针数组的起始地址。(换汤不换药,并且还有点啰嗦。本质上就是 int arr[10], *p = arr 的原理,这里只不过就是用二级指针替代指针数组实现排序而已)
image.png
char *p[ROW], **pp = p 等价于 p[0] = (*pp) 进行排序即可。
image.png
编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/strPtr2Ptr.c -o ./bin/strPtr2Ptr
b12@PC:~/chapter8$ ./bin/strPtr2Ptr
Please input 5 string(<=20)
China
America
India
Philippines
Canada ::: 输出结果用表格展示:

id Ascend: char *p[10] id Original: arr[5][20]
0x7fffe2c75665 America 0x7fffe2c75650 China
0x7fffe2c756a4 Canada 0x7fffe2c75665 America
0x7fffe2c75650 China 0x7fffe2c7567a India
0x7fffe2c7567a India 0x7fffe2c7568f Philippines
0x7fffe2c7568f Philippines 0x7fffe2c756a4 Canada

13、写一个用矩形法求定积分的通用函数,分别求:课后习题 - 图21课后习题 - 图22课后习题 - 图23(其中 sin , cosexp 在 math.h 库中实现了,直接使用 #include <math.h> 即可)

解题思路:微积分的应用,化圆为方,当 dx 无限小的时候,就会用矩形面积和求解。
课后习题 - 图24
同时我们也学过手动求解定积分来验算结果。例如上述分别对应
课后习题 - 图25
最主要的就是实现一个通用的“梯形法求解定积分”函数,即无论对于任意函数,只需要知道积分上下限就可以得到结果。 double integral(double (*fx)(), double a, double b, int n) 通用函数首部。要求传入积分上下限 a, b 然后传入对应函数即可。

  • 参数 n 表示将区间 [a, b] 分为几等分分别积分,即 课后习题 - 图26dx 越小,结果越准确。
  • double (*fx)() :表示 fx 是指针,它指向一个函数,该函数返回值为 double 。 ``` COS(3) Linux Programmer’s Manual COS(3)

NAME cos, cosf, cosl - cosine function

SYNOPSIS

  1. #include <math.h>
  2. double cos(double x);
  3. float cosf(float x);
  4. long double cosl(long double x);
  5. Link with -lm.
  1. 因为默认数学函数都是返回高精度的,因此将书本上的** **`**float**` 改为 `**double**` 类型。
  2. ```c
  3. #include <stdio.h>
  4. #include <math.h>
  5. double integral(double (*fx)(double), double a, double b, int n) {
  6. double dx = (b - a) / n, s = 0.0;
  7. for (int i = 0; i < n; i++) { // n 等份计算
  8. a = a + dx; // 矩形右侧 x 值
  9. s += (*fx)(a) * dx;
  10. }
  11. return s;
  12. }
  13. int main () {
  14. double a1, b1, a2, b2, a3, b3;
  15. int n1, n2, n3;
  16. printf("Please input a1, b1, n1:");
  17. scanf("%lf %lf %d", &a1, &b1, &n1);
  18. printf("%f\t%f\n", integral(sin, a1, b1, n1),
  19. -cos(b1) - -cos(a1));
  20. printf("Please input a2, b2, n2:");
  21. scanf("%lf %lf %d", &a2, &b2, &n2);
  22. printf("%f\t%f\n", integral(cos, a2, b2, n2),
  23. sin(b2) - sin(a2));
  24. printf("Please input a3, b3, n3:");
  25. scanf("%lf %lf %d", &a3, &b3, &n3);
  26. printf("%f\t%f\n", integral(exp, a3, b3, n3),
  27. exp(b3) - exp(a3));
  28. return 0;
  29. }

编译运行: :::success b12@PC:~/chapter8$ ./bin/integral
Please input a1, b1, n1:0 1 10
0.501388 0.459698
Please input a2, b2, n2:-1 1 10
1.677328 1.682942
Please input a3, b3, n3:0 2 10
7.049244 6.389056

b12@PC:~/chapter8$ ./bin/integral
Please input a1, b1, n1:0 1 100
0.463901 0.459698
Please input a2, b2, n2:-1 1 100
1.682886 1.682942
Please input a3, b3, n3:0 2 100
6.453160 6.389056

b12@PC:~/chapter8$ ./bin/integral
Please input a1, b1, n1:0 1 1000
0.460118 0.459698
Please input a2, b2, n2:-1 1 1000
1.682941 1.682942
Please input a3, b3, n3:0 2 1000
6.395447 6.389056 ::: 从结果可以看出:随着划分份数增大,其越来越精确。

14、将 n 个数按输入时的逆序排列,用函数实现。

课后习题

15、有一个班有 4 个学生,5 门课程。

  • 求第 1 门课程的平均分
  • 找出有两门以上课程不及格的学生,输出他们的学号和全部课程成绩及平均成绩
  • 找出平均成绩在 90 分以上或全部课程成绩在 85 分以上的学生。

分别用 3 个函数实现上面 3 个要求。
解题思路:(书本上搞得很复杂,实现起来真的很容易),本质上本题只需要一个实型二维数组 score 存储学生的成绩。数组共 4 行代表 4 名学生。,一行有 5 列代表 5 门课程。

  1. 科目名称输入处理,即给每个课程搞一个名字。(用一个字符型二维数组存起来)
  2. 学号处理:可以使用单独一个数组 id[4] 或者直接在score 多增加一列存储学号。
  3. 每名学生 5 门科目 IO 处理,放入 score[i] 内即可。

image.png
整题的所有存储之间的索引如上所示,接下来就是搬砖的问题。

  1. #include <stdio.h>
  2. #include <math.h>
  3. #define N 4 // 4名学生
  4. #define M 5 // 5门课程
  5. #define MAXSIZE 10 // 字符串最大长度
  6. float subject1average(float *scorePtr) {
  7. // 传参分别是指二维成绩表列的指针
  8. float sum = 0.0; // 第一列总成绩
  9. for (int i = 0; i < N; i++) {
  10. // 注意静态二维数组和指针指向等价关系是静态数组内存地址连续
  11. sum += *(scorePtr + M * i + 0);
  12. }
  13. return sum / N;
  14. }
  15. void average(float *scorePtr, float *averPtr) {
  16. // 传参分别是指二维成绩表列的指针和指向平均分数组的指针
  17. for (int i = 0; i < N; i++) {
  18. float sum = 0.0; // 每个学生总成绩
  19. for (int j = 0; j < M; j++) {
  20. sum += *(scorePtr + M * i + j);
  21. }
  22. *(averPtr + i) = sum / M; // *averPtr++ = sum / M;
  23. }
  24. }
  25. void fail2subject(float *scorePtr, int *idPtr, float *averPtr) {
  26. for (int i = 0; i < N; i++) {
  27. int failCnt = 0;
  28. for (int j = 0; j < M; j++) {
  29. failCnt += *(scorePtr + M * i + j) < 60.0;
  30. if (2 == failCnt) { // 这可以直接2
  31. printf("%d", idPtr[i]); // 输出学号
  32. for (int k = 0; k < M; k++) { // 五门成绩
  33. printf("%11.2f", *(scorePtr + M * i + k));
  34. }
  35. printf("%11.2f\n", averPtr[i]); // 挂科学生总成绩平均分
  36. break; // 在循环内要break
  37. }
  38. }
  39. }
  40. }
  41. void good(float *scorePtr, int *idPtr, float *averPtr) {
  42. for (int i = 0; i < N; i++) {
  43. int ge85cnt = 0;
  44. for (int j = 0; j < M; j++) {
  45. ge85cnt += *(scorePtr + M * i + j) >= 85;
  46. }
  47. if (M == ge85cnt || averPtr[i] >= 90) { // 其实这里可以直接2
  48. printf("%d", idPtr[i]); // 输出学号
  49. for (int k = 0; k < M; k++) { // 五门成绩
  50. printf("%11.2f", *(scorePtr + M * i + k));
  51. }
  52. printf("%11.2f\n", averPtr[i]); // 挂科学生总成绩平均分
  53. }
  54. }
  55. }
  56. int main () {
  57. // 1.输入科目名称处理
  58. char subject[M][MAXSIZE];
  59. printf("Please input %d subjects:\n", M);
  60. for (int i = 0; i < M; i++) {
  61. scanf("%s", subject[i]);
  62. }
  63. // 2.学号和该学生5门课程成绩处理
  64. int id[N], *idPtr = id; // 学号表
  65. float score[N][M], *scorePtr = score[0]; // 成绩表
  66. printf("Please input %d students' id and %d subjects' score\n", N, M);
  67. for (int i = 0; i < N; i++) {
  68. scanf("%d", idPtr + i); // 先收取学号
  69. for (int j = 0; j < M; j++) {
  70. scanf("%f", scorePtr + M * i + j); // 再收取该名学生5门成绩
  71. }
  72. }
  73. // 3.用函数计算第一门课的平均分,将score第一列相加求平均值
  74. printf("The first subject is %s, and its average is %.2f\n",
  75. subject[0], subject1average(scorePtr));
  76. // 4.用函数计算每个学生所有成绩的平均分
  77. float aver[N], *averPtr = aver; // N 名学生所有成绩的平均分数组
  78. average(scorePtr, averPtr);
  79. // 5.找到挂科2门的学生
  80. printf(" ===============Student who is fail in two subjects=============== \n");
  81. printf("NO. ");
  82. for (int i = 0; i < M; i++) {
  83. printf("%11s", subject[i]);
  84. }
  85. printf(" average\n");
  86. fail2subject(scorePtr, idPtr, averPtr);
  87. // 6.找到好学生:平均分>=90或者5门课都>=85
  88. printf(" ===============Student whose score is good=============== \n");
  89. printf("NO. ");
  90. for (int i = 0; i < M; i++) {
  91. printf("%11s", subject[i]);
  92. }
  93. printf(" average\n");
  94. good(scorePtr, idPtr, averPtr);
  95. return 0;
  96. }

编译运行:写代码调试真难! :::success b12@PC:~/chapter8$ gcc -Wall ./src/studentScore.c -o ./bin/studentScore
b12@PC:~/chapter8$ ./bin/studentScore
Please input 5 subjects:
English
Computer
Math
Physics
Chemistry
Please input 4 students’ id and 5 subjects’ score
101 34 56 88 99 89
102 27 88 99 67 78
103 99 90 87 86 89
104 78 89 99 56 77
The first subject is English, and its average is -24761295154441252503552.00
===============Student who is fail in two subjects===============
NO. English Computer Math Physics Chemistry average
101 34.00 56.00 88.00 99.00 89.00 73.20
===============Student whose score is good===============
NO. English Computer Math Physics Chemistry average
103 99.00 90.00 87.00 86.00 89.00 90.20 :::

16、输入一个字符串,内有数字和非数字字符,例如: A123x456 17960? 302tab5876 ,将其中连续的数字作为一个整数,依次存放到一维数组 a 中。例如课后习题 - 图28统计共有多少整数并输出这些数。

解题思路:此题非常常见的问题就是后效性问题,即处理最有一个满足条件的字符!!非常容易忘记。
处理方式有两种:多向后看一位(遍历到 \0 字符);单独处理判断最后一次,将结果添加进去。

  1. IO 问题:题目明显输入字符串含有空格,优先选择正则的 scanf 输入。
  2. 处理数字和数组存储关系。边遇到数字边进行位权的相乘(非参考书上找完所有数字区间然后再开始相乘!) ```c

    include

    include

    define MAXSIZE 50

int main() { char str[MAXSIZE], p = str; printf(“Please input string:”); fgets(str, MAXSIZE, stdin); // scanf(“%[^\n]s”, str); // 1.初始化和开始找数字 int n = strlen(str), nums[MAXSIZE], idx = 0, product = 0; int isNum = 0; // 防止 product 初始化和’0’情况 while (p <= str + n) { // 走到\0! if (‘0’ <= p && p <= ‘9’) { isNum = 1; // 标记遇到数字 product = product 10 + (*p - ‘0’); // 防止溢出写法 } else if (isNum) { nums[idx++] = product; product = isNum = 0; // 重置状态 } p++; } // 2.输出结果 for (int i = 0; i < idx; i++) { printf(“%d “, nums[i]); } printf(“\n”); return 0; }

  1. **编译运行**:
  2. :::success
  3. b12@PC:~/chapter8$ ./bin/parseInt<br />Please input string:A123x456 17960? 302tab5876<br />123 456 17960 302 5876
  4. :::
  5. 拓展延伸:其实上述过程还有很多提升,例如有符号,判断溢出问题等等!远不止书本上这么简单。如果用正则表达式一行就可以解决问题!还可以自己实现有限状态机。有兴趣的可以参考[这里](https://leetcode-cn.com/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/)。
  6. <a name="dqTLF"></a>
  7. # 17、写一函数,实现两个字符串的比较。即自己写一个 strcmp 函数,函数原型为 `strcmp(char *p1, char *p2);` 设 `p1` 指向字符串 `s1` , `p2` 指向字符串 `s2` 。
  8. - `s1 = s2` 时返回值为 0
  9. - `s1 != s2` 时返回它们二至第 1 个不同字符的 ASCII 码差值(如“BOY”与“BAD”,第 2 个字母不同, `O` `A` 之差为 `79-65=14` )。
  10. - `s1 > s2` 返回正值。
  11. - `s1 < s2` 返回负值。
  12. **解题思路**:无非就是实现 `<string.h` 中的 `strcmp` 函数,如果当前字符相等,移动指针,直到**结束**或者**两者不同**就是返回结果的时候。
  13. ```c
  14. #include <stdio.h>
  15. #include <string.h>
  16. #define N 20
  17. int myStrCmp(char *a, char *b) {
  18. while (*a && *b && *a == *b) {
  19. a++;
  20. b++;
  21. }
  22. return (int)(*a - *b);
  23. }
  24. int main () {
  25. // 1.字符串IO:正则
  26. char str1[N], str2[N];
  27. printf("Please input two strings:\n");
  28. scanf("%[^\n]s", str1);
  29. getchar();
  30. scanf("%[^\n]s", str2);
  31. getchar();
  32. printf("myStrCmp(%s,%s):%d, strcmp(%s,%s):%d\n",
  33. str1, str2, myStrCmp(str1, str2), str1, str2, strcmp(str1, str2));
  34. return 0;
  35. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/myStrCmp.c -o ./bin/myStrCmp
b12@PC:~/chapter8$ ./bin/myStrCmp
Please input two strings:
CHINA
CHEN
myStrCmp(CHINA,CHEN):4, strcmp(CHINA,CHEN):4
b12@PC:~/chapter8$ ./bin/myStrCmp
Please input two strings:
CHINA
Chen
myStrCmp(CHINA,Chen):-32, strcmp(CHINA,Chen):-32
b12@PC:~/chapter8$ ./bin/myStrCmp
Please input two strings:
hello!
hello!
myStrCmp(hello!,hello!):0, strcmp(hello!,hello!):0 :::

18、编一程序,输入月份号,输出该月的英文月名。例如输入 3,则输出“March”,要求使用指针数组处理。

解题思路:只要明白什么是(字符型)指针数组。它就是一个数组,数组内元素全是指针类型,该指针类型指向一个字符串的起始地址。另外就是字符串常量是存储在静态存储区内,属性是只读的!
PTA函数—指针

19、实现 mallocfree 函数

  1. 编写一个函数 new ,对 n 个字符开辟连续的存储空间,此函数应该返回一个指针(地址),指向字符串开始的空间。 new(n) 表示分配 n 个字节的内存空间。
  2. 写一函数 free ,将前面用 new 函数占用的空间进行释放。 free(p) 表示将 p (地址)指向的单元以后的内存段释放。

解题思路:标准库函数中的实现方式是和操作系统打交道的,申请的都是堆内存,也就是动态内存。

  • 如果当前的存储容量不够了,向操作系统申请一个更大的,然后把原本的小容量内的元素全部复制过去,因此其与静态内存(规定容量)相比,超过容量后赋值很麻烦的。
  • 由于操作系统管理很多进程,因此它对每个进程分配的空间是有记录的。当某个程序分走一块内存,另一个就无法使用。因此若某个程序不断地向操作系统索要 malloc 内存而不返还 free 就会出现“内存泄漏”(内存不够了,爆炸)

本例只是简单的模拟这个过程,即不涉及太深内容,只是将一超大容量的静态内存来模拟这两个函数。

  1. 在每次处理申请内存请求时,需要查看手头上是否还存在内存可分配。如果不够,那就返回 NULL ,否则将固定大小的内存块的起始地址返回。(指针的最大作用之一:动态内存的指向
  2. 记录每次分配给某个人的位置:本例只是简单地顺序实现,并没有考虑很多。例如:有 a, b, c 三个人先后申请内存,然后 b, a, c 先后归还内存,这个时候实现起来就非常麻烦。但是归还顺序是 c, b, a 实现起来就是非常容易。(虽然本题不太符合实际,只是为了解这两个函数最简单原理而设定的)。
  3. free 请求要判断是否符合,若原来指针没有分配过内存就是不能乱来。(就是为什么不可以对一个指针进行两次 free ,因为第一次释放了,第二次它变成野指针)

实现方式:使用全局静态数组 buffer 作为超大内存,容量为 MAXSIZE ,用一个指针记录此时 buffer 使用情况(指向未使用内存的起始位置)。

  1. malloc 函数实现:当 buffer 尚有容量且够申请者要 n 大小的内存区时,进行分配并更新 buffer 使用情况。否则够就申请失败!

free模拟.png

  1. free 函数实现:由于只是简单地实现“先申请后归还,后申请先返回”类似栈数据结构的实现方式很简单。需要注意对非法指针的释放问题(实际上并非如下代码中简单判断)。

malloc模拟.png
下面代码实现用户输入“变长”数组(长度由 IO 决定),然后登记其基本信息;再进行第二个用户等等。。最后打印两个用户的输入信息。

  1. #include <stdio.h>
  2. #define MAXSIZE 50
  3. int buffer[MAXSIZE], *anchor = buffer;
  4. int *new(int n) {
  5. if (anchor + n <= buffer + MAXSIZE) { // 指针比较:不超过容量最大地址
  6. anchor += n;
  7. return anchor - n; // 返回该片内存起始地址
  8. }
  9. return NULL; // 申请失败
  10. }
  11. void free(int *p) {
  12. if (buffer <= p && p <= buffer + MAXSIZE) {
  13. anchor = p; // anchor回退到该块内存起始地址表示释放
  14. } else {
  15. printf("Error\n");
  16. }
  17. }
  18. int *input(int *size) {
  19. printf("Please input array size:");
  20. scanf("%d", size);
  21. int *p = new(*size);
  22. if (p) {
  23. printf("Please input %d numbers:\n", *size);
  24. for (int i = 0; i < *size; i++) {
  25. scanf("%d", p + i);
  26. }
  27. } else {
  28. printf("Sorry, there is no enough size for you\n");
  29. }
  30. return p;
  31. }
  32. void output(int n, int user, int *p) {
  33. if (!p) return;
  34. printf("User %d info:\n", user);
  35. for (int i = 0; i < n; i++) {
  36. printf("%d ", p[i]);
  37. }
  38. printf("\n");
  39. free(p);
  40. }
  41. int main () {
  42. // 1.输入两人信息
  43. printf("anchor:%p\n\n", anchor);
  44. int n1, *p1 = input(&n1);
  45. printf("malloc(p1) anchor:%p\n\n", anchor);
  46. int n2, *p2 = input(&n2);
  47. printf("malloc(p2) anchor:%p\n\n", anchor);
  48. // 2.输出两人信息,先输入后释放
  49. output(n2, 2, p2);
  50. printf("free(p2) anchor:%p\n\n", anchor);
  51. output(n1, 1, p1);
  52. printf("free(p1) anchor:%p\n\n", anchor);
  53. // 3.输出全局数组内容
  54. for (int i = 0; i < MAXSIZE; i++) {
  55. printf("%02d ", buffer[i]);
  56. if ((i + 1) % 10 == 0) {
  57. printf("\n");
  58. }
  59. }
  60. return 0;
  61. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/mallocFree.c -o ./bin/mallocFree
b12@PC:~/chapter8$ ./bin/mallocFree
anchor:0x7f852235a040

Please input array size:20
Please input 20 numbers:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
malloc(p1) anchor:0x7f852235a090

Please input array size:10
Please input 10 numbers:
1 2 3 4 5 6 7 8 9 10
malloc(p2) anchor:0x7f852235a0b8

User 2 info:
1 2 3 4 5 6 7 8 9 10
free(p2) anchor:0x7f852235a090

User 1 info:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
free(p1) anchor:0x7f852235a040

01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00

b12@PC:~/chapter8$ ./bin/mallocFree
anchor:0x7f88c6577040

Please input array size:30
Please input 30 numbers:
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
1 2 3 4 5 6 7 8 9 10
malloc(p1) anchor:0x7f88c65770b8

Please input array size:21
Sorry, there is no enough size for you
malloc(p2) anchor:0x7f88c65770b8

free(p2) anchor:0x7f88c65770b8

User 1 info:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1 2 3 4 5 6 7 8 9 10
free(p1) anchor:0x7f88c6577040

01 02 03 04 05 06 07 08 09 10
11 12 13 14 15 16 17 18 19 20
01 02 03 04 05 06 07 08 09 10
00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 :::

21、用指向指针的指针的方法对 n 个整数排序并输出。要求将排序单独写成一个函数。n 个整数在主函数中输入,最后在主函数中输出。

解题思路:本题和 20 题一致,都是索引表的排序,而不是原数组 data 位置的搬动。指向指针的指针即“二级指针”它指向的就是整型指针数组 pstr ,这个指针数组内存储着 data 每个元素的地址
image.png

  1. #include <stdio.h>
  2. #define N 20
  3. void selectSort(int **p, int n) {
  4. for (int i = 0; i < n - 1; i++) {
  5. int min = i; // 选择此时为最小
  6. for (int j = i + 1; j < n; j++) {
  7. if (**(p + j) < **(p + min)) {
  8. min = j;
  9. }
  10. }
  11. if (min != i) {
  12. int tmp = **(p + i);
  13. **(p + i) = **(p + min);
  14. **(p + min) = tmp;
  15. }
  16. }
  17. }
  18. int main () {
  19. // 1.输入数据
  20. int data[N], *pstr[N], **p = pstr;
  21. printf("Please input %d number\n", N);
  22. for (int i = 0; i < N; i++) {
  23. scanf("%d", data + i);
  24. pstr[i] = data + i; // 将data元素地址赋值到指针数组pstr内
  25. }
  26. // 2.传入二级指针:指向指针数组的指针进行排序
  27. selectSort(p, N);
  28. // 3.打印升序数组
  29. for (int i = 0; i < N; i++) {
  30. printf("%d ", *pstr[i]);
  31. }
  32. printf("\n");
  33. return 0;
  34. }

编译运行: :::success b12@PC:~/chapter8$ gcc -Wall ./src/intPtr2Ptr.c -o ./bin/intPtr2Ptr
b12@PC:~/chapter8$ ./bin/intPtr2Ptr
Please input 20 number
13 15 8 1 0 16 18 10 6 14 12 4 19 3 5 9 7 11 2 17
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 :::