switch存在的问题
缺少default语句
switch
语句可以包含一个可选的default
语句,用于处理没有与任何case
标签匹配的情况。如果没有default
语句,并且没有与表达式匹配的case
标签,程序将不会执行任何操作。
除了case
switch包含的大括号中间可以变量声明、赋值等操作,但是这些操作无法执行,因为switch只执行case匹配的语句
穿透
当某个case
块的末尾没有break
语句时,程序会继续执行下一个case
块的代码,直到遇到break
语句或者switch
语句的末尾。这种穿透特性在某些情况下是非常有用的,它允许你共享代码块而不需要重复编写相同的代码。然而,它也容易导致逻辑错误,特别是当你不小心忘记添加break
语句时。
下面是一个使用switch
穿透特性的例子:
int main() {int num = 2;switch (num) {case 1:printf("Case 1\n");// 没有break,会穿透到case 2case 2:printf("Case 2\n");// 没有break,会穿透到case 3case 3:printf("Case 3\n");break; // 在这里停止穿透default:printf("Default case\n");break;}return 0;
}
在这个例子中,当num
为2时,输出将是:
Case 2
Case 3
这是因为case 2
的末尾没有break
语句,所以程序继续执行case 3
的代码。然而,这种穿透特性也可能导致意外的行为,特别是当你不希望它发生时。因此,在使用switch
语句时,你应该清楚地知道何时使用穿透特性,何时需要添加break
语句来防止穿透。
关于break跳到了那里
大家来看下面的例子
switch(line){case THING1: doit1();break;case THING2:if(x == STUFF){do_first();if(y == OTHER)break;}//程序员想让程序跳到这里default: .......//各种业务
}//实际跳到这里use_modes();
原本想从if语句中跳出,但是break实际跳出了最近的switch语句,程序跳出了switch语句,执行了use_modes(),这样给程序埋下了隐患。
关于书写格式
在C语言中,如果两个字符串常量在代码中相邻出现,并且它们之间仅由空白字符(如空格、制表符或换行符)分隔,或者根本没有分隔符,那么它们会被编译器自动合并成一个字符串。这是C语言的一个特性,允许程序员在编写代码时以更灵活的方式处理字符串。
以下是一个简单的示例,展示了C语言中相邻字符串常量被自动合并的特性:
#include <stdio.h>int main() {printf("这是第一个字符串""这是第二个字符串\n");return 0;
}
然而这种自动合并意味着在字符串初始化时漏掉一个逗号,编译器不会发出任何错误信息,悄悄的把两个字符串合并在一起
int main() {char strArray[][40] = {"Hello""World","C Programming","is fun but hard" };int numRows = sizeof(strArray) / sizeof(strArray[0]);for (int i = 0; i < numRows; i++) {printf("%s\n", strArray[i]);}return 0;
}
这样二维数组从四行变为了三行。字符串的数目比预期少了一个。当程序想修改第二行数组字符串时却修改了别的字符串。
缺省带来的问题
在C语言中,如果你定义了一个函数而没有指定其作用域(即使用static
关键字),那么该函数默认具有全局可见性。这意味着该函数可以在整个程序中被访问和调用。虽然这种默认的全局可见性在某些情况下可能很方便,但它也可能带来一些问题:
1、命名冲突:当多个文件或模块中定义了相同名称的函数时,就会发生命名冲突。这可能导致链接错误或不可预测的行为,因为编译器不知道应该使用哪个函数的定义。
2、代码维护困难:全局可见的函数使得代码更难以维护和理解。因为任何文件都可以调用这些函数,所以很难跟踪函数的调用关系和依赖关系。
3、潜在的错误:全局可见的函数更容易被误用或滥用。其他开发者可能会在不了解函数用途或副作用的情况下调用它们,从而导致程序错误或不稳定。
为了避免这些问题,你可以采取以下措施:
1、使用static
关键字:在函数定义前加上static
关键字,可以将函数的可见性限制在定义它的文件内。这有助于避免命名冲突和减少代码之间的依赖关系。
2、模块化设计:将程序划分为多个模块,每个模块只暴露必要的接口给其他模块。这可以通过头文件和源文件分离来实现,以及使用static
函数来隐藏模块内部的实现细节。
3、命名规范:采用一致的命名规范来减少命名冲突的可能性。例如,可以使用前缀或后缀来标识函数所属的模块或库。
符号重载
不同的关键字在不同的环境中具有不同的意义,下表描述了部分关键字的重载情况
符号 | 意义 |
static | 在函数内部,表示变量的值在各个调用间一直保持延续性 在函数这一级,表示该函数只对本文件可见 |
extern | 用于函数定义,表示全局可见 用于变量,表示在其他地方定义 |
void | 作为函数的返回类型,表示不返回任何值 在指针声明中,表示通用指针的类型 位于参数列表中,表示没有参数 |
* | 乘法运算符 用于指针,间接引用符 声明中,表示指针 |
& | AND操作符 取地址操作符 |
= | 赋值 |
== | 比较运算符 |
<= <<= | 小于等于运算符 左移复合赋值运算符 |
< | 小于运算符 #include指令的左定界符 |
( ) | 在函数定义中,包围形式参数 调用一个函数 改变表达式的运算次序 将值转换为其他类型(强制类型转换) 定义带参数的宏 包围sizeof操作符的操作数 |
还有些奇怪的例子让大家恼火
if(x>>4)
这是啥意思,是x远远大于4
p = N * sizeof * q;
能不能马上判断出,这个乘号是一个还是两个
apple = sizeof(int) * p;
这代表设么意思,是int的长度乘p,或者把p强制转换为int,然后进行sizeof操作。
运算优先级带来的问题
运算符优先级是指不同运算符在表达式中按照一定的顺序进行运算的规则。这个规则决定了在没有括号明确指定顺序时,运算的执行顺序。具有高优先级的运算符会先于低优先级的运算符执行。然而,运算符优先级在使用中也存在一些问题,容易让程序员产生误解或错误
1、sizeof运算符的特殊性:
当sizeof的操作数是一个类型名时,两边必须加上括号。这容易让人误以为sizeof是一个函数。
例如:sizeof(int)
是正确的,而 sizeof int
是错误的。
2、点运算符(.)和星号运算符(*)的优先级:
点运算符(.)的优先级高于星号运算符(*)。
例如:表达式 *p.f
的实际结果是 *(p.f)
,而不是 (*p).f
。
3、方括号运算符([])和星号运算符(*)的优先级:
方括号运算符([ ])的优先级高于星号运算符(*)。
例如:表达式 int* ap[]
的实际结果是 int*(ap[])
,而不是 int(*ap)[]
。
4、函数运算符()和星号运算符(*)的优先级:
函数运算符()的优先级高于星号运算符(*)。
例如:表达式 int* fp()
的实际结果是 int*(fp())
,而不是 int(*fp)()
。
5、比较运算符和位运算符的优先级:
比较运算符(如 ==
和 !=
)的优先级高于位运算符。
例如:表达式 val & mask != 0
的实际结果是 val & (mask != 0)
,而不是 (val & mask) != 0
。
比较运算符和赋值运算符的优先级:
6、比较运算符的优先级也高于赋值运算符。
例如:表达式 c = getchar() != EOF
的实际结果是 c = (getchar() != EOF)
,而不是 (c = getchar()) != EOF
。
7、算术运算符和移位运算符的优先级:
算术运算符的优先级高于移位运算符。
例如:表达式 msb << 4 + lsb
的实际结果是 msb << (4 + lsb)
,而不是 (msb << 4) + lsb
。
8、逗号运算符的优先级:
逗号运算符在所有运算符中优先级最低。
例如:表达式 i = 1, 2;
的实际结果是 (i = 1), 2;
,即 i
被赋值为 1
,然后整个表达式的结果是 2
(但通常不会使用这种写法,因为它可能导致代码难以理解和维护)。
为了避免这些问题,程序员需要熟悉C语言中各种运算符的优先级规则,并在编写代码时谨慎使用括号来明确表达式的运算顺序。同时,保持代码的清晰和可读性也是非常重要的,这有助于减少因运算符优先级问题而导致的错误。