本节涉及到函数的调用,形参和实参等知识
下面是一个有问题的代码,聪明的你肯定知道哪里出了问题
#include <stdio.h> //有错误,不能进行实际的变量交换 void swap(int a, int b) { int t = a; a = b; b = t; }
下面解释一下
第一步: 在main函数中,a = 3, b = 4,所以swap(a, b)等价于swap(3, 4), 而这里的参数3,4称为实参(实际参数)
第二步: 把实参赋值给函数中的形参a, b,swap函数中,a = 3, b =4
第三步: 形参a , b的值进行交换了,所以现在是a = 4, b =3;
注意:函数的形参和在该函数中定义的变量都称为该函数的局部变量(local variable),不同函数的局部变量是相互独立的,局部变量的存储空间是临时分配的,函数执行完毕时,
局部变量的空间将被释放。而全局变量(global variable),此变量在函数外声明,可以在任何时候,由任何函数访问。
调用栈(Call stack)描述的是函数之间的调用关系,它由多个栈帧(stack frame)组成,每个栈帧对应着一个未运行完的函数。
下面我们使用gcc来调试我们的程序。
gcc swap.c -std=c99 -g
gcc命令是编译程序,-g告诉编译器生成调试信息,编译选项-std=c99 告诉编译器按c99标准来编译代码
gdb a.exe
这就是输入以上命令会出现的情况,我们输入 l可以查看源码
(gdb) l 3 // 4 void swap(int a, int b) 5 { 6 int t = a; 7 a = b; 8 b = t; 9 } 10 int main() 11 { 12 int a = 3, b = 4; (gdb)
12 int a = 3, b = 4; (gdb) Quit (expect signal SIGINT when the program is resumed) (gdb) b 9 Breakpoint 1 at 0x4013cf: file 4.2.1.c, line 9. (gdb) r Starting program: H:\2\4.2.1_a.exe [New Thread 5904.0xb50] Breakpoint 1, swap (a=4, b=3) at 4.2.1.c:9 9 } (gdb)
上面通过b 9来在程序的第9行增加一个断点,r运行该程序,然后显示了a和b的值,现在a = 4, b = 3
(gdb) bt #0 swap (a=4, b=3) at 4.2.1.c:9 #1 0x00401403 in main () at 4.2.1.c:13 (gdb) p a $1 = 4 (gdb) p b $2 = 3 (gdb) up #1 0x00401403 in main () at 4.2.1.c:13 13 swap(a, b); (gdb) p a $3 = 3 (gdb) p b $4 = 4 (gdb) q A debugging session is active. Inferior 1 [process 5904] will be killed. Quit anyway? (y or n) y
我们使用bt命令调用栈中包含的两个栈帧#0,#1,#0是当前栈帧swap函数,#1是上一栈帧main函数
p命令可以打印变量的值,up命令可以选择上一个栈帧q退出gdb,在这里可以很清楚的看到
在main函数中的变量值是没有改变的,还是a = 3,b = 4.下面给出正确代码:
#include <stdio.h> void swap(int *a, int *b) { int t = *a; *a = *b; *b = t; } int main() { int a = 3, b = 4; swap(&a, &b); printf("%d %d ", a, b); getchar(); return 0; }
再次使用以上方式将上面代码进行调试
(gdb) b 7 Breakpoint 1 at 0x4013d7: file swap.c, line 7. (gdb) r Starting program: H:\2\a.exe [New Thread 2468.0x1110] Breakpoint 1, swap (a=0x28ff1c, b=0x28ff18) at swap.c:7 7 } (gdb) bt #0 swap (a=0x28ff1c, b=0x28ff18) at swap.c:7 #1 0x0040140b in main () at swap.c:11 (gdb) p a $1 = (int *) 0x28ff1c (gdb) p b $2 = (int *) 0x28ff18 (gdb) p *a $3 = 4 (gdb) p *b $4 = 3 (gdb) up #1 0x0040140b in main () at swap.c:11 11 swap(&a, &b); (gdb) p a $5 = 4 (gdb) p b $6 = 3 (gdb) p &a $7 = (int *) 0x28ff1c (gdb) p &b $8 = (int *) 0x28ff18 (gdb)
注意: 千万不能滥用指针,这不仅会把自己搞糊涂,还会让程序产生各种奇怪的错误。
- 数组作为参数
- 代码
#include <stdio.h> int sum(int *a, int n) { int ans = 0; for(int i = 0; i < n; i++) ans+=a[i]; return ans; } int main() { int a[100],i; int index = 0; while( scanf("%d", &i) == 1 && i) { a[index++] = i; } int ans = sum(a, index);//此时Index刚好是数组个数 printf("%d ", ans); return 0; }
注:不能直接将a[]传递过来,因为int a[]等价于int *a,在只知道地址信息的情况下,是无法知道数组里有多少个元素的,所以要加一个数组参数。
- 代码
效果
- 古老的密码问题
-
Ancient Cipher
给定两个长度相同且不超过100的字符串,判断是否能把其中一个字符串的各个字母重排,
然后对26个字母做一个一一映射,使得两个字符串相同,例如,JWPUDJSTVP重排后可以得到
WJDUPSJPVT,然后把每个字母映射到它的前一个字母(B->A...),得到VICTORIOUS,
输入两个字符串,
输出YES 或者NO - 代码
//古老的密码 2017-8-26 #include<stdio.h> #include<stdlib.h> // qsort #include<string.h> // strlen int cmp(const void *a, const void *b){ return *(int *)a - *(int *)b; } int main() { char s1[200], s2[200]; while(scanf("%s%s", s1, s2) == 2){ int n = strlen(s1); int cnt1[26] = {0}, cnt2[26] = {0}; for(int i = 0; i < n; i++) cnt1[s1[i] - 'A']++; for(int j = 0; j < n; j++) cnt2[s2[j] - 'A']++; qsort(cnt1, 26, sizeof(int), cmp); qsort(cnt2, 26, sizeof(int), cmp); int ok = 1; for(int k = 0; k < 26; k++) if(cnt1[k] != cnt2[k]) ok = 0; if(ok) printf("YES ");else printf("NO "); } return 0; }
分析:既然可以重排,则每个字母的位置并不重要,重要的是每个字母出现的次数。这样可以先统计出两个字符串中的各个字母的出现次数,得到两个数组cnt1[26],cnt2[26],只要两个数组排序之后的结果相同,输入的两个串就可以通过重排和一一映射变得相同。
- 注:在c语言中stdlib.h中有个叫qsort的库函数,实现了著名的快速排序算法。
- void qsort(void *base, size_t num, size_t size, int (*comparator)(const void *, const void *));
- 这里的const void *,他可以通过强制类型转化变成任意类型的指针。
-
今天就到这吧!
能力决定价值!