程序设计基础第五~六周答疑要点整理

函数的return语句的意义:表示函数结束

对一个函数来说,当执行到return语句的时候,表示这个函数要直接结束,返回到函数外面调用这个函数的地方继续运行。即使return套在多重循环里,而循环可能还剩很多趟要跑,也会直接“撒手不管”,不会去执行执行顺序在return后面的语句。

关于函数单向传参问题

有些同学想使用如下函数将传入函数的两个局部变量的值交换,是一种很常见的错误写法:

1
2
3
4
5
6
//错误写法
void swap(int a, int b){
int tmp = a;
a = b;
b = tmp;
}

大家在学习函数的时候都知道,函数传参是单向的。函数在被调用时,会在内存中开辟一块自己的内存空间。此时,作为函数参数的变量,它们的值会被相应地拷贝一份,存在属于函数自己的内存空间中。当在函数内“交换”变量ab的时候,事实上是把函数自己的内存空间中保存的ab变量值的一个副本进行了交换,而外面真正的变量ab的值并没有受到影响。

如果需要通过在函数中的操作修改外部变量的值,一种可选的方法是使用全局变量。此时,因为函数可以直接访问全局变量,所以不需要将全局变量的值作为参数传入函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a, b;

void swap(){ //函数内可以直接访问全局变量a和b
int tmp = a;
a = b;
b = tmp;
}

int main(){
scanf("%d%d", &a, &b);
swap(); //swap函数调用结束后,全局变量a和b的值发生了交换

return 0;
}

另外一种方法是将待交换变量的指针作为函数参数传入函数,下次课学过指针后会有详细讲解。

字符串字面值和字符字面值的区别

字符字面值是由一对单引号''括起来的单个字符(转义字符的反斜杠\和后面的字符合起来算作一个字符),一个字符字面值存在一个字节(char)的内存空间中。

字符串字面值是由一对双引号""括起来的任意个字符,在内存中,字符串中的每个字符都各自存在一个字节(char)的内存空间中,这些字节按字符在字符串中的顺序连续排列,最后以一个存有结束符'\0'的字节作为结尾。

1
2
3
4
5
char c1 = 'a';	//c1是一个字符,存入的值是字符字面量'a'
char str1[] = "a"; //str1是一个长度为1的字符串,str1[0]为'a',str1[1]为结束符'\0'
char str2[] = "this is a string"; //一个合法的字符串
char c2 = 'this is a string'; //错误写法!单引号不能用来括住多个字符
char c3 = '\n'; //正确写法,c3是一个字符,存入的值是换行符'\n'

字符串在数组中的存储

当使用scanf("%s", str);gets(str);读取长度为len的字符串,并存入字符数组str时,字符串中的字符在数组中存储的下标范围是0 ~ len-1,下标为len的位置上则是一个标识字符串结束位置的'\0'。在遍历字符串时需要注意下标范围。

与此同时,也需要注意:字符串里的'\0'也是需要一个位置来存储的。所以声明数组时,请适量地把长度开大一点。

字符数组的推荐初始化方法

在声明字符数组时,推荐将字符数组的各位置初始化为全0

1
char str[100] = {};

初始化为全0后,字符数组的每个位置上的字符都是'\0',即标识字符串结束的字符。万一遇到需要自己手动给字符数组的某个位置赋值'\0'、标识字符串的结尾但是不小心忘记了的情况,也能使字符串能够结尾。

字符串操作函数的传入参数

strcpystrcatstrcmpstrlenstrstr等诸多字符串操作函数,当需要对一维字符数组执行操作时,需要传入这些函数的参数是数组名。

1
2
3
4
char a[105] = {}, b[105] = {};
scanf("%s%s", a, b);
strcpy(a, b); //正确
strcpy(a[105], b[105]); //错误,a[105]和b[105]是访问数组a,b中下标为105的位置上的单个字符值,且访问操作发生了越界

事实上,传入的参数需要是指向一维char数组的指针类型,下次课学到指针后会有详细讲解。

遍历字符串时一个常见的易超时写法

在需要遍历字符串的题目中,经常出现一种比较典型的写法:

1
2
3
4
5
char str[100] = {};
scanf("%s", str);
for (int i = 0; i < strlen(str); i++){
//...
}

当不会在循环中修改字符串str的长度时,这种写法的效率是十分低的。

因为由for循环中语句的执行顺序可知:每跑完一趟循环,都要去执行for后面括号里中间的判断条件i < strlen(str),来判断循环要继续还是结束。而strlen是通过遍历字符数组,寻找第一次出现的'\0'结束符来计算字符串长度的。当字符串长度固定时,strlen函数返回的值也是固定的值。如果每次循环都要重新计算一次这个值,那就要白白地多遍历字符串strlen(str)趟,显然是不必要的操作。

因此,可以在遍历字符串前,先声明一个变量待存字符串的长度,使用一次strlen函数计算长度并存入变量中,然后在遍历字符串时,使用这个变量的值作为循环的判断条件。这样就能减少不必要的操作,提高代码效率:

1
2
3
4
5
6
char str[100] = {};
scanf("%s", str);
int len = strlen(str);
for (int i = 0; i < len; i++){
//...
}

当然,如果字符串str的长度在每次循环中都可能发生变化时,使用i < strlen(str)作为判断条件就没什么问题了。

逻辑表达式的短路求值性质在访问字符数组中的应用

我们知道:逻辑表达式具有短路求值的性质。逻辑表达式从左往右执行,对两个以逻辑与运算&&连接的关系语句,当左面的关系语句为假(值为0)时,右面的关系语句不需要计算,可以直接得出整个逻辑表达式为假(值为0)的结果。

因此,当要在一个if条件里使用逻辑与运算&&,实现 判断数组下标是否越界、在不越界时取出该下标位置中的元素判断该元素是否满足某条件 时,最好将判断下标是否越界的关系语句写在左面。这样,当下标越界时左面的语句值为假,就不会执行右面的语句访问越界的位置,有助于培养大家对访问数组前先检查下标是否越界的好习惯:

1
2
3
4
5
6
7
8
9
char str[100] = {};
scanf("%s", str);
int len = strlen(str);
int i = 0;
if ((0<=i&&i<len) && str[i]>='A'){
//将
//...
}


作者:逐月的游星、梁秋月


程序设计基础第五~六周答疑要点整理
https://suzumiyaakizuki.github.io/2023/11/02/C5-E6答疑要点整理/
作者
SuzumiyaAkizuki
发布于
2023年11月2日
许可协议