19、混淆字符数组与字符指针的区别

错误示范:非常常见的错误!

  1. #include <stdio.h>
  2. int main () {
  3. char str[20];
  4. str = "C programming";
  5. printf("%s\n", str);
  6. return 0;
  7. }

编译运行: :::danger (base) b12@PC:~/chapter13$ gcc ./src/strArray.c -o ./bin/strArray
./src/strArray.c: In function ‘main’:
./src/strArray.c:5:6: error: assignment to expression with array type
5 | str = “C programming”;
| ^ ::: 错误原因:同上一题类似,主要原因是数组定义后就是常量值了,想要初始化其内的元素,必须通过循环方式一个个赋值。否则就在定义的同时进行初始化!

解决方法1:定义字符数组的同时进行初始化

  1. #include <stdio.h>
  2. int main () {
  3. char str[] = "C programming"; // 可加括号或不加
  4. printf("%s\n", str);
  5. return 0;
  6. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/strArray.c -o ./bin/strArray
(base) b12@PC:~/chapter13$ ./bin/strArray
C programming ::: 解决方法2:字符指针变量赋值常量字符串地址

  1. #include <stdio.h>
  2. int main () {
  3. char *p = "C programming"; // 可加括号或不加
  4. printf("%s\n", p);
  5. return 0;
  6. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/strArray.c -o ./bin/strArray
(base) b12@PC:~/chapter13$ ./bin/strArray
C programming ::: 分析:要明白“定义式初始化”和赋值的区别,后者没有开辟额外空间,是将符串常量在 静态存储区 内地址赋值给指针变量,因此使得这个指针变量就指向该常量字符串!

31、混淆数组名与指针变量的区别

错误示范:数组名是 lvalue 而指针变量rvalue

  1. #include <stdio.h>
  2. #define N 5
  3. int main () {
  4. int nums[N];
  5. printf("Please input %d numbers:", N);
  6. for (int i = 0; i < N; i++) {
  7. scanf("%d", a++); // Error
  8. }
  9. printf("Ok\n");
  10. return 0;
  11. }

编译运行: :::danger (base) b12@PC:~/chapter13$ gcc ./src/arrayPointer.c -o ./bin/arrayPointer
./src/arrayPointer.c: In function ‘main’:
./src/arrayPointer.c:8:15: error: ‘a’ undeclared (first use in this function)
8 | scanf(“%d”, a++); // Error
| ^
./src/arrayPointer.c:8:15: note: each undeclared identifier is reported only once for each function it appears in ::: 出错原因:数组名和指针变量是两码事,数组名是常量(不可修改),是第一个元素的起始地址。而 x++ 后缀自增运算符是会改变 x 的值!所以矛盾。应该使用指针变量替换。

  1. #include <stdio.h>
  2. #define N 5
  3. int main () {
  4. int nums[N], p = nums;
  5. printf("Please input %d numbers:", N);
  6. for (int i = 0; i < N; i++) {
  7. scanf("%d", p++);
  8. }
  9. printf("Ok\n");
  10. return 0;
  11. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/arrayPointer.c -o ./bin/arrayPointer
(base) b12@PC:~/chapter13$ ./bin/arrayPointer
Please input 5 numbers:5 4 3 2 1
Ok ::: tips:指针变量的加减就是地址的加减,地址根据该类型,比如此时其基类型 int ,则其偏移量 offset = sizeof(int) ,表示一次加减跳过 sizeof(int) 字节。为什么可以这样,还是因为静态数组在内存中是连续存储的!这也是为什么静态的二维数组也可以用 int *p 进行遍历。但是如果二维数组是 malloc 出来的,那么由于每行的地址不一定连续,因此 p++ 不一定跨越到下一行!

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/arrayPointer.c -o ./bin/arrayPointer
(base) b12@PC:~/chapter13$ ./bin/arrayPointer
static array
0x7f78072b3040 0x7f78072b3044 0x7f78072b3048 0x7f78072b304c 0x7f78072b3050
0x7f78072b3054 0x7f78072b3058 0x7f78072b305c 0x7f78072b3060 0x7f78072b3064
0x7f78072b3068 0x7f78072b306c 0x7f78072b3070 0x7f78072b3074 0x7f78072b3078
0x7f78072b307c 0x7f78072b3080 0x7f78072b3084 0x7f78072b3088 0x7f78072b308c
0x7f78072b3090 0x7f78072b3094 0x7f78072b3098 0x7f78072b309c 0x7f78072b30a0
Dynamic array
0x7fffb853d2e0 0x7fffb853d2e4 0x7fffb853d2e8 0x7fffb853d2ec 0x7fffb853d2f0
0x7fffb853d300 0x7fffb853d304 0x7fffb853d308 0x7fffb853d30c 0x7fffb853d310
0x7fffb853d320 0x7fffb853d324 0x7fffb853d328 0x7fffb853d32c 0x7fffb853d330
0x7fffb853d340 0x7fffb853d344 0x7fffb853d348 0x7fffb853d34c 0x7fffb853d350
0x7fffb853d360 0x7fffb853d364 0x7fffb853d368 0x7fffb853d36c 0x7fffb853d370 ::: 从上面看到静态数组,即数组大小固定时,不管是否换行其内存元素都是连续存在的( int *p 能遍历的原因),但是对于动态数组,即通过 malloc 获得的堆内存数组,由于 malloc 申请也是一块连续内存,因此列元素是相距 sizeof(int) ,但是行与上一行末尾元素地址却不一定相距 sizeof(int). 这一点关系到库函数 strcpymemcpy 等这类连续拷贝元素的函数。

20、在引用指针变最之前没有对它赋予确定的值

错误示范:常见野指针错误,未分配内存就引用

  1. #include <stdio.h>
  2. int main () {
  3. char *p; // 没初始化
  4. printf("Please input your name:");
  5. scanf("%s", p); // Error
  6. printf("Hello %s, :)\n", p);
  7. return 0;
  8. }

编译运行: :::success b12@PC:~/chapter13$ gcc ./src/wildPointer.c -o ./bin/wildPointer
b12@PC:~/chapter13$ ./bin/wildPointer
Please input your name:Cao
Hello (null), :) ::: 在 Linux 上很奇怪,居然不警告还不出现内存错误。然后我在 Windows 上就出现了
image.png
出错原因:指针变量 p 没有给它赋值(初始化是随机的值),因而会从这个地址往后不断写入输入的字符。可能这个地址是非常重要的,因而会覆盖原有信息,倒置程序炸了。
修改方法:指向一个特定内存区域,尤其注意这和上面的字符串常量地址区别。

  1. #include <stdio.h>
  2. #define N 10
  3. int main () {
  4. char str[N], *p = str; // 没初始化
  5. printf("Please input your name:");
  6. fgets(p, N, stdin);
  7. // 不建议用 scanf("%s", p);
  8. printf("Hello %s", p);
  9. return 0;
  10. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/wildPointer.c -o ./bin/wildPointer
(base) b12@PC:~/chapter13$ ./bin/wildPointer
Please input your name:CaoBenYi123456
Hello CaoBenYi1 :::

22、混淆字符和字符串的表示形式

  1. #include <stdio.h>
  2. int main () {
  3. char sex = "M";
  4. char *yes = 'Y';
  5. printf("%c %s\n", sex, yes);
  6. return 0;
  7. }

编译运行: :::warning (base) b12@PC:~/chapter13$ gcc ./src/charString.c -o ./bin/charString
./src/charString.c: In function ‘main’:
./src/charString.c:4:13: warning: initialization of ‘char’ from ‘char ’ makes integer from pointer without a cast [-Wint-conversion]
4 | char sex = “M”;
| ^~~
./src/charString.c:5:14: warning: initialization of ‘char
’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
5 | char yes = ‘Y’;
| ^~~
(base) b12@PC:~/chapter13$ ./bin/charString
Segmentation fault (core dumped) ::: 错误原因:C 语言中规定字符'' 作为分隔,并不代表字符“字面上”就是一个长度,还有很多转义符 '\n' 等。C 语言中没有字符串,取而代之的是表示用*字符数组
,字符数组和普通整型数组类似。
它们的关系是:字符数组 > 字符串 > 字符

  • 字符数组:没有要求必须有 \0 字符,比如 char name[3] = {'A', 'B', 'C'}。表示三个字符组成的数组
  • 字符串:用字符数组表示,要求必须含有一个 \0 字符,比如 char name[4] = {'A', 'B', 'C', '\0'};
  • 字符:由单引号 '' 包裹起来的有效长度为 **1** 的字符(有效长度必须是 1 ,转义字符 '\x41' 有效长度也是 1 ),即组成字符数组的元素,比如 char sex = '\0' 。它同整型变量一样。

即 C 语言中只存在字符字符数组,字符串不是基本类型,而是为了方便使用字符数组表示在“最后”(通常是最后,但是 printf 输出元组是从左到右遇到第一个 '\0' 就停止输出)添加 '\0' 字符作为标记。

因此所有的字符串都可以使用 while + putchar() 输出的!

  1. #include <stdio.h>
  2. int main () {
  3. char alphabet = '\x41';
  4. char *name = "I am a string";
  5. printf("%c | %s\n", alphabet, name);
  6. return 0;
  7. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/charString.c -o ./bin/charString
(base) b12@PC:~/chapter13$ ./bin/charString
A | I am a string :::

23、使用自加(++)和自减(—)运算符时容易出的错误

错误示范:搞不懂 增/减量 运算符和 * 运算符的关系。

  1. #include <stdio.h>
  2. void printOut(int *nums, int numsSize) {
  3. for (int i = 0; i < numsSize; i++) {
  4. printf("%d ", nums[i]);
  5. }
  6. printf("\n");
  7. }
  8. int main () {
  9. int nums[] = {1, 2, 3, 4, 5}, *p = nums;
  10. // 1. * operand ++/-- = *(operand ++/--)
  11. printf("%d\n", *p++);
  12. printf("%d\n", *p--);
  13. // 2. * ++/-- operand = *(++/-- operand)
  14. printf("%d\n", *(++p)); // *++p(2)
  15. printf("%d\n", *(--p)); // *--p(1)
  16. // 3.++/-- * operand = ++/--(*operand)
  17. printOut(nums, 5);
  18. printf("%d\n", ++*p);
  19. printOut(nums, 5);
  20. printf("%d\n", --*p);
  21. printOut(nums, 5);
  22. return 0;
  23. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/asterisk2increase.c -o ./bin/asterisk2increase
(base) b12@PC:~/chapter13$ ./bin/asterisk2increase
1
2
2
1
1 2 3 4 5
2
2 2 3 4 5
1
1 2 3 4 5 ::: 输出分析:首先 增/减量 运算符很花哨,再次 * 的运算符优先级较小。
**a = 5**

  • 前缀
    • --a 先让 a 自减 1 ,因此此时 a = 4 ,整个表达式返回 4 ,即原来 a 自减 1 后的结果。
    • ++a 先让 a 自加 1 ,因此此时 a = 5 ,整个表达式返回 5 ,即原来 a 自加 1 后的结果。
  • 后缀
    • a-- 表达式先返回 a 的值,即原来 a 的值 5 ,然后 a 自减 1 ,最终 a = 4
    • a++ 表达式先返回 a 的值,即原来 a 的值 5 ,然后 a 自增 1 ,最终 a = 6

现在将两个运算符连接起来,两者都是单目运算符,只要一个操作数。我们枚举所有 函数/数组/指针相关问题 - 图2 的情况:操作数一定在不能在 * 前面!

  1. // 不可能存在操作数在 * 前
  2. operand * ++/--
  3. operand ++/-- *
  4. * operand ++/-- = *(operand ++/--)
  5. * ++/-- operand = *(++/-- operand)
  6. ++/-- operand * // 不可能存在操作数在 *
  7. ++/-- * operand = ++/--(*operand)

当两运算符相遇时,谁的优先级高,先听谁的!因此不管这两个运算符怎么组合。对上面程序流程进一步分析:

行数 执行前:nums 操作 解释 执行后:nums
13 [1, 2, 3, 4, 5]
^
printf(“%d\n”, *p++); *(p++) :表达式 p++ 先返回 p 地址,然后与 * 结合得到值 1 ,最后 p 自增 1 ,最终时 p = nums + 1 [1, 2, 3, 4, 5]
^
14 [1, 2, 3, 4, 5]
^
printf(“%d\n”, *p—); *(p--) :表达式 p-- 先返回 p 地址,然后与 * 结合得到值 2 ,最后 p 自减 1 ,最终时 p = nums [1, 2, 3, 4, 5]
^
16 [1, 2, 3, 4, 5]
^
printf(“%d\n”, *(++p)); *(++p) :表达式 ++p 先让 p 自增 1,最终时 p = nums + 1,最后回 p 地址,然后与 * 结合得到值 2 [1, 2, 3, 4, 5]
^
17 [1, 2, 3, 4, 5]
^
printf(“%d\n”, *(—p)); *(--p) :表达式 --p 先让 p 自减 1,最终时 p = nums,最后回 p 地址,然后与 * 结合得到值 1 [1, 2, 3, 4, 5]
^
20 [1, 2, 3, 4, 5]
^
printf(“%d\n”, ++*p); ++*p :由于单目运算符 * 先和指针变量 p 结合,获取其内值(访问+修改),然后变成 ++lvalue ,先对该地址内的 lvalue 自增 1 ,因此 nums[0] = 2 ,然后返回自增后的结果,即 2 [2, 2, 3, 4, 5]
^
22 [2, 2, 3, 4, 5]
^
printf(“%d\n”, —*p); --*p :由于单目运算符 * 先和指针变量 p 结合,获取其内值(访问+修改),然后变成 --lvalue ,先对该地址内的 lvalue 自减去 1 ,因此 nums[0] = 1 ,然后返回自减后的结果,即 1 [1, 2, 3, 4, 5]
^

如上,关键要搞清楚具体执行顺序,如果实在不懂,那就抛弃这种装逼的写法,老老实实地用 () 表明优先级。代码可读性非常重要!

24、忘记对所调用的函数进行函数原型声明

错误示范:函数未声明就调用

  1. #include <stdio.h>
  2. int main () {
  3. int nums[] = {1, 2, 3, 4, 5};
  4. printOut(nums, 5);
  5. return 0;
  6. }
  7. void printOut(int *nums, int numsSize) {
  8. for (int i = 0; i < numsSize; i++) {
  9. printf("%d ", nums[i]);
  10. }
  11. printf("\n");
  12. }

编译运行: :::warning (base) b12@PC:~/chapter13$ gcc ./src/funcDeclare.c -o ./bin/funcDeclare
./src/funcDeclare.c: In function ‘main’:
./src/funcDeclare.c:5:2: warning: implicit declaration of function ‘printOut’; did you mean ‘printf’? [-Wimplicit-function-declaration]
5 | printOut(nums, 5);
| ^~
| printf
./src/funcDeclare.c: At top level:
./src/funcDeclare.c:9:6: warning: conflicting types for ‘printOut’
9 | void printOut(int *nums, int numsSize) {
| ^
~
./src/funcDeclare.c:5:2: note: previous implicit declaration of ‘printOut’ was here
5 | printOut(nums, 5);
| ^~~~
(base) b12@PC:~/chapter13$ ./bin/funcDeclare
1 2 3 4 5 ::: 警告原因:编译器发出警告说你没有对 printOut 进行函数声明,但是实际上编译器知道去哪里找这个函数。如强制运行还是得到结果。
解决警告方法:实际上就是需要提前告知编译器这个函数原型是什么。声明不等于定义,它“一般”不消耗内存空间,类似给编译器提个醒。因为编译器是自上而下进行读取文件,因此有两种方式告诉它这个函数调用是怎么一回事。

  1. 将定义函数放在调用语句前(对于单个文件,本人习惯这样,因为可以不写声明) ```c

    include

void printOut(int *nums, int numsSize) { for (int i = 0; i < numsSize; i++) { printf(“%d “, nums[i]); } printf(“\n”); }

int main () { int nums[] = {1, 2, 3, 4, 5}; printOut(nums, 5); return 0; }

  1. 2. 定义函数处在调用语句之后,需要在调用前声明函数原型(推荐:因为以后写头文件必备!)
  2. ```c
  3. #include <stdio.h>
  4. int main () {
  5. int nums[] = {1, 2, 3, 4, 5};
  6. void printOut(int *nums, int numsSize); // 直接把定义函数花括前复制过来
  7. /*
  8. * void printOut(int *, int);
  9. * 还可以省略形参名字
  10. */
  11. printOut(nums, 5);
  12. return 0;
  13. }
  14. void printOut(int *nums, int numsSize) {
  15. for (int i = 0; i < numsSize; i++) {
  16. printf("%d ", nums[i]);
  17. }
  18. printf("\n");
  19. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/funcDeclare.c -o ./bin/funcDeclare
(base) b12@PC:~/chapter13$ ./bin/funcDeclare
1 2 3 4 5 :::

25、对函数声明与函数定义不匹配

错误示范:张冠李戴

  1. #include <stdio.h>
  2. int main () {
  3. int nums[] = {1, 2, 3, 4, 5};
  4. void printOut(int *nums, int numsSize);
  5. /*
  6. * 形参类型错误(也可能是顺序错误):
  7. * void printOut(int nums, int numsSize);
  8. * void printOut(int *nums, float numsSize);
  9. * 形参缺少错误:
  10. * void printOut(int numsSize);
  11. * void printOut(int *nums);
  12. * 参数顺序错误:导致形参类型错误
  13. * void printOut(int numsSize, int *nums);
  14. * 漏写函数返回类型:即使是 void 也要
  15. * printOut(int *nums, int numsSize);
  16. */
  17. printOut(nums, 5);
  18. return 0;
  19. }
  20. void printOut(int *nums, int numsSize) {
  21. for (int i = 0; i < numsSize; i++) {
  22. printf("%d ", nums[i]);
  23. }
  24. printf("\n");
  25. }

综上:编译器对声明还是非常严格的,函数首部信息就包含上面所有错误类型。最简单的方式就是直接拷贝定义函数时的 {} 前的东西。同时由于函数调用时才分配空间(栈),因此形参名的地址是随时分配,因此也不要求声明时必须有形参名,但是必须要有类型。(只管造房子,不管谁来住)因此下面都是等价的。

  • void printOut(int *nums, int numsSize);
  • void printOut(int *p, int n);
  • void printOut(int *, int);

注意:函数声明和调用不是一回事,声明不占内存空间,类似对编译器的 tips,而函数调用就是指令,必须执行。两者会对形参数量、类型一一检测。但是函数调用不同声明就是获得返回值

28、函数的实参和形参类型不一致

与上面函数声明形参对比,此处是函数调用时的检测“虚实结合”的正确性
错误示范:

  1. #include <stdio.h>
  2. float sum(int x, int y) {
  3. return x + y; // implicit type conversion
  4. }
  5. int main () {
  6. float a = 6.6, b = 9.9, c;
  7. /*
  8. * sum(int x, int y)
  9. * 1.形参类型(错误)(但不警告):sum(a, b)
  10. * 2.形参缺少错误:sum(a)
  11. * 3.参数顺序错误:导致形参类型错误(不警告)
  12. * 函数返回是否接受:不要求,具体视作用而定
  13. * c = sum(a, b);
  14. * sum(a, b);
  15. */
  16. c = sum(a, b);
  17. printf("%.1f + %.1f = %.1f\n", a, b, c);
  18. return 0;
  19. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/typeConvert.c -o ./bin/typeConvert
(base) b12@PC:~/chapter13$ ./bin/typeConvert
6.6 + 9.9 = 15.0 ::: 与预期不同的原因:编译器不给你发出警告,而是直接隐式类型转换,让你不知道 BUG 在哪里。这种最为致命!因此调用函数之前必须阅读相关用法。这一点在使用他人设计的函数时需要非常注意,比如使用标准库函数时,一定要去了解函数首部和其相关作用!

26、在需要加头文件时没有用 #include 指令去包含头文件

错误示范:容易忘记

  1. int main () {
  2. printf("Hello World\n");
  3. return 0;
  4. }

编译运行: :::warning (base) b12@PC:~/chapter13$ gcc ./src/header.c -o ./bin/header
./src/header.c: In function ‘main’:
./src/header.c:2:2: warning: implicit declaration of function ‘printf’ [-Wimplicit-function-declaration]
2 | printf(“Hello World\n”);
| ^~
./src/header.c:2:2: warning: incompatible implicit declaration of built-in function ‘printf’
./src/header.c:1:1: note: include ‘’ or provide a declaration of ‘printf’
+++ |+#include
1 | int main () {
(base) b12@PC:~/chapter13$ ./bin/header
Hello World ::: 警告原因:如上可见对于标准库函数,即使编译器发出警告,它也似乎在努力在容忍你的犯错(到标准库里面找),最终找到了,提醒你要导入头文件<stdio.h>。但是对于非标准库函数,我瞎写的一个呢?

  1. #include <stdio.h>
  2. /*void printOut(int *nums, int numsSize) {
  3. for (int i = 0; i < numsSize; i++) {
  4. printf("%d ", nums[i]);
  5. }
  6. printf("\n");
  7. }*/
  8. int main () {
  9. int nums[] = {1, 2, 3, 4, 5};
  10. printOut(nums, 5);
  11. return 0;
  12. }

编译运行: :::danger (base) b12@PC:~/chapter13$ gcc ./src/header.c -o ./bin/header
./src/header.c: In function ‘main’:
./src/header.c:6:2: warning: implicit declaration of function ‘printOut’; did you mean ‘printf’? [-Wimplicit-function-declaration]
6 | printOut(nums, 5);
| ^~~~
| printf
/usr/bin/ld: /tmp/ccN6Bu3r.o: in function main':<br />header.c:(.text+0x50): undefined reference toprintOut’
collect2: error: ld returned 1 exit status ::: 这次编译器直接检测到没有定义函数了,但是依然在提示你是否是想用 printf 函数。

因此在使用库函数或者自定义的其它函数时,都要为编译器指明寻找它的方向。

27、误认为函数形参值的改变会影响实参的值

错误示范

  1. #include <stdio.h>
  2. void swap(int a, int b) {
  3. printf("")
  4. int tmp = a;
  5. a = b;
  6. b = tmp;
  7. }
  8. int main () {
  9. int a = 5, b = 1;
  10. if (a > b) {
  11. swap(a, b);
  12. }
  13. printf("a=%d,b=%d\n", a, b);
  14. return 0;
  15. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/swap.c -o ./bin/swap
(base) b12@PC:~/chapter13$ ./bin/swap
swap(5, 1)
swap(1, 5)
a=5,b=1 ::: 与预期不同原因:函数设计初衷就是避免程序中重复写实现代码,同时也是为了保护变量不受改变而另设作用域。因此本质上传参就是传“值”拷贝。这里的“值”就是变量内存的数据,直接 copy 一份。因此上面只是对 copy 的形参进行交换,对实参没任何改变!
修改方式:传指针变量。本质上形参是指针变量,即拷贝来自实参变量内的值。而指针变量的内存的值是存储地址,依然是“值”拷贝。只不过就是在函数内部通过该地址去修改原来的值而达到交换目的(我认为指针出现可能原因之一,即弥补函数传参拷贝消耗内存问题)

  1. #include <stdio.h>
  2. void swap(int *a, int *b) {
  3. printf("swap:address a=%p, b=%p\n", a, b);
  4. printf("swap(%d, %d)\n", *a, *b);
  5. int tmp = *a;
  6. *a = *b;
  7. *b = tmp;
  8. printf("swap:address a=%p, b=%p\n", a, b);
  9. printf("swap(%d, %d)\n", *a, *b);
  10. }
  11. int main () {
  12. int a = 5, b = 1;
  13. printf("Address a:%p, b:%p\n", &a, &b);
  14. if (a > b) {
  15. swap(&a, &b);
  16. }
  17. printf("a=%d,b=%d\n", a, b);
  18. return 0;
  19. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/swap.c -o ./bin/swap
(base) b12@PC:~/chapter13$ ./bin/swap
Address a:0x7fffcb9ef9f0, b:0x7fffcb9ef9f4
swap:address a=0x7fffcb9ef9f0, b=0x7fffcb9ef9f4
swap(5, 1)
swap:address a=0x7fffcb9ef9f0, b=0x7fffcb9ef9f4
swap(1, 5)
a=1,b=5 ::: 综上:指针变量的出现改变函数设计的一些弊端:

  1. 传参拷贝可能消耗超大内存,因此可以传指针(但是万一不小心改变了呢?还有 const ** const 啊)
  2. 解决函数返回值只能是“一个”类型。比如 python 中一次传回 n 个值!这也可以用 type * 指针实现呀。甚至可以直接把需要改的变量当形参指针变量解决(如上 swap 函数)。

    29、不同类型的指针混用

    错误示范:张冠李戴 ```c

    include

int main () { int i = 9, p1 = &i; float f = 6.6f, p2 = &f; printf(“i=%d, f=%.2f\n”, p1[0], p2); p2 = &i; // int -> float p1 = &f; // float -> int printf(“i=%d, f=%.2f\n”, p2[0], p1); return 0; }

  1. **编译运行**:
  2. :::warning
  3. (base) b12@PC:~/chapter13$ gcc ./src/pointerType.c -o ./bin/pointerType<br />./src/pointerType.c: In function main’:<br />./src/pointerType.c:7:5: warning: assignment to float *’ from incompatible pointer type int *’ [-Wincompatible-pointer-types]<br /> 7 | p2 = &i; // int * -> float *<br /> | ^<br />./src/pointerType.c:8:5: warning: assignment to ‘int *’ from incompatible pointer type ‘float *’ [-Wincompatible-pointer-types]<br /> 8 | p1 = &f; // float * -> int *<br /> | ^<br />./src/pointerType.c:9:13: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]<br /> 9 | printf("i=%d, f=%.2f\n", p2[0], *p1);<br /> | ~^ ~~~~~<br /> | | |<br /> | int double<br /> | %f<br />./src/pointerType.c:9:21: warning: format ‘%f’ expects argument of type ‘double’, but argument 3 has type ‘int’ [-Wformat=]<br /> 9 | printf("i=%d, f=%.2f\n", p2[0], *p1);<br /> | ~~~^ ~~~<br /> | | |<br /> | double int<br /> | %.2d<br />(base) b12@PC:~/chapter13$ ./bin/pointerType<br />i=9, f=6.60<br />i=1087583027, f=0.00
  4. :::
  5. **与预期不同的原因**:与上面的 [printf格式输出类型不一致](#x0ibx) 类似。
  6. 1. 即使强制用地址去赋值,改指针变量的类型决定**以什么类型方式**去访问指针变量所指向地址内的值,即 `ptr[0]` 读取内存值得方式是以 `ptr` 基类型决定。
  7. 1. 由于原因 `1` 导致读取数据类型和 `printf` 格式输出类型不一致,再次发生以 `%d` 方式读取 `float` 类型问题。
  8. 综上两种原因导致最后的结果是无法预知的。一定要和编译器的警告作斗争!
  9. 另外就是 `void *` 类型, `void` 称为无类型(“万能类型”是不对的),也就是用该类型定义的指针变量,其值就是纯地址!通常用在 `malloc()` 返回值上。
  10. - 理论上必须进行**强制类型转换**即 `char *s = (char *)malloc(sizeof(char) * N);`
  11. - 但是现在的编译器基本上都有**隐式类型转换**,有时候可以简写为 `char *s = malloc(sizeof(char) * N);`
  12. ```c
  13. #include <stdio.h>
  14. int main () {
  15. int i = 9, *p1 = &i;
  16. float f = 6.6f, *p2 = &f;
  17. printf("i=%d, f=%.2f\n", p1[0], *p2);
  18. void *p = p1;
  19. printf("i=%d, f=%.2f\n", *(int *)p, *p2);
  20. return 0;
  21. }

总之,只要知道你返回的具体类型有改变即可,像如上代码不进行类型转换会发出警告 dereferencing ‘void *’ pointer ,因此引用时若不存在隐式类型转换时就要手动强制类型转换

30、没有注意系统对函数参数的求值顺序的处理方法

错误示范:典型转牛角尖,代码不规范的 undefined behavior

  1. #include <stdio.h>
  2. int main () {
  3. int i = 7;
  4. printf("%d,%d,%d\n", i, ++i, ++i);
  5. return 0;
  6. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/undefinebehavior.c -o ./bin/undefinebehavior
(base) b12@PC:~/chapter13$ ./bin/undefinebehavior
9,9,9 ::: 可见上面 i 自增两次,但是最后全部输出都是 9 都很奇怪。对于这种与其纠结解释出现的原因,不如把代码写规范,别给自己挖坑,非常不建议这样写!!代码可读性很重要!读者只需要知道 使用自加(++)和自减(—)运算符时容易出的错误 即可。
替代方法:用变量解决!

  1. #include <stdio.h>
  2. int main () {
  3. int i = 7;
  4. int j = i + 1, k = j + 1;
  5. printf("%d,%d,%d\n", i, j, k);
  6. return 0;
  7. }

编译运行: :::success (base) b12@PC:~/chapter13$ gcc ./src/undefinebehavior.c -o ./bin/undefinebehavior
(base) b12@PC:~/chapter13$ ./bin/undefinebehavior
7,8,9 :::

32、混淆结构体类型与结构体变量的区别,对一个结构体类型赋值

错误示范:混淆变量类型(不分配空间)和变量(分配空间)关系

  1. #include <stdio.h>
  2. #include <string.h>
  3. struct Student {
  4. int id;
  5. char name[10];
  6. char sex;
  7. int age;
  8. }; // struct type
  9. int main () {
  10. Student.id = 007;
  11. strcpy(Student.name, "Zhang san");
  12. Student.sex = 'F';
  13. Student.age = 15;
  14. printf("name:%s\n", Student.name);
  15. printf("id:%d\n", Student.id);
  16. printf("sex:%c\n", Student.sex);
  17. printf("age:%d\n", Student.age);
  18. return 0;
  19. }

编译运行: :::danger (base) b12@PC:~/chapter13$ gcc ./src/structType.c -o ./bin/structType
./src/structType.c: In function ‘main’:
./src/structType.c:12:2: error: ‘Student’ undeclared (first use in this function)
12 | Student.id = 007;
| ^~~
./src/structType.c:12:2: note: each undeclared identifier is reported only once for each function it appears in ::: 错误原因:提示说标识符 Student 未定义。结构体类型是数据类型,而不是实体存在,它就好比人类的概念,但是不是真实的肉体的人,而用它定义才是变量。数据类型是抽象的,不占储存空间,而变量是占储存空间。变量的内存空间大小数据类型决定

  1. #include <stdio.h>
  2. #include <string.h>
  3. struct Student {
  4. int id;
  5. char name[10];
  6. char sex;
  7. int age;
  8. }; // struct type
  9. int main () {
  10. struct Student stu = {007, "Zhang san", 'F', 15}; // 定义式初始化
  11. printf("name:%s\n", stu.name);
  12. printf("id:%d\n", stu.id);
  13. printf("sex:%c\n", stu.sex);
  14. printf("age:%d\n", stu.age);
  15. return 0;
  16. }

编译运行:(注意:上面如果不是定义式初始化,那么必须每个成员单独赋值 :::success (base) b12@PC:~/chapter13$ gcc ./src/structType.c -o ./bin/structType
(base) b12@PC:~/chapter13$ ./bin/structType
name:Zhang san
id:7
sex:F
age:15 :::