软微科协 2012 c++ 組招新试题
命题人: 科协 c++ 組所有成员
本文件包括试题以及答案
1 选择题
1.1 下面代码中有问题的是哪个?
A. char* const p; B. char const *p; C. const char *p; D. const *char p;
- answers
- A. 本身是 const 的 char 指针
- B. 指向 const char 的指针
- C. 同 B
- D. 不正确,没有这种写法。
选 D
1.2 下面哪一项定义一个返回值为int,参数为char*的函数指针
A. *foo int (char*)
B. typedef int (*foo) (char*)
C. int (*foo) (char*)
D. typedef int (*) (char*) foo
- answers
选 C, 注意 B 中定义的是 foo 这个类型,而不是函数指针的变量。
1.3 现有如下函数
1: int foo(int a, int b) 2: { 3: return a + b; 4: }
则调用 foo((1,2),(3,4)) 的结果为
A. 4 B. 10 C. 7 D. 6
- answers 1
- 选 D
逗号运算符返回最后的一个表达式的值顺便扩展一下最近发现的一个奇葩东西。。。
delete x, y;
上面这句话完全正确,而且 c++ 是这么看待这个式子的
(delete x), y;
即,把 x 删了,返回 y 的值。。。
1.4 下面的代码输出是什么?
1: #include <stdio.h> 2: int main() 3: { 4: unsigned int a = 6; 5: int b = -20; 6: (a+b > 6)? puts("> 6"): puts("<= 6"); 7: return 0; 8: }
A. > 6 B. < 6 C. >= 6 D. <= 6
- answers
选 A 当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。 即 b 变成了一个超级大的正数 (2 ^ 32 - 20) 即使加上 a 也不会溢出,仍是超大的正数
1.5 malloc()申请内存处于
A. 静态存储区 B. 栈 (stack) C. 堆 (heap) D. 代码区 (text)
- answers 1
选 C
2 解答题
2.1 请讲一下你对头文件的理解,头文件里面应该都放些什么。
- answers 1
- 头文件与一般的文本文件没有区别,在编译的时候与 .c .cpp 之类的是同等对待。但 是头文件有特殊的使命,头文件是通过 #include 完全复制到 .cpp 文件之后才发 挥作用,头文件里面一般没有实质的执行的代码,有的只是一些声明,通过头文件就可 以方便的得到统一的声明。
- answers 2
- 头文件里面可以有的东西
- 变量声明
- 函数声明
- 类声明
下面是几个特殊的定义,为了确保不会重复定义,都会要求定义必须完全一致
- 有名字的名字空间
- 类型定义
- 模板定义
- 内联函数定义
- 枚举
- 宏定义
- 条件宏
- 用 字面值 初始化的 const 常量
- 头文件里面可以有的东西
2.2 设有
int var1; double var2;
请分别给出 var1, var2 与 0 比较的判断语句
要求,如果为 0 就执行条件体 示例: 如果 bool var3; 则 if (!var3){…}
- answers 1
- int : if (var2 == 0){…}
- double : if (var3 >= -1e-6 && var3 <= 1e-6){…}
需要注意有两个等于号 double 是不精确的,不可以直接判断是否相等。
2.3 在 32 位的操作系统下,请计算以下的 sizeof,并解释一下原因
- 1
1: int f(char str[100]) 2: { 3: return sizeof(str); // = ? 4: }
- 2
1: char *str = (char*)malloc(100); 2: sizeof(str); // = ? 3: sizeof(*str); // = ?
- 3
1: char str[100]; 2: sizeof(str); // = ?
- answers 1
sizeof(str) = 4 注意数组类型的东西在作为形式参数时会退化为指针,而 32 位下所有指针都是4个字 节。
- answers 2
sizeof(str) = 4 32 位下所有指针都是4个字节。 sizeof(*str) = 1 char 类型所以 1 个字节
- answers 3
sizeof(str) = 100 数组的 sizeof 相当于 ”类型大小 * 元素个数“
3 阅读程序
3.1 阅读下面的程序
1: void test1() 2: { 3: char string[10]; 4: char* str1 = "0123456789"; 5: strcpy( string, str1 ); 6: }
- 请写出 strcpy 的函数声明
- 找出程序的错误
- answers 1
1: char* strcpy(char *dest, const char *src);
- answers 2
- c 字符串需要一个 '\0' 作为结尾,string 只有 10 个位置不够放
- str1 应该声明为 const char *str1 ,因为字符串常量 "0123456789" 是不可修改的。如果声明为 const 则可以提前到编译期就,发现程序的错误
- 使用 strcpy 复制字符串时经常会出现位置不够的情况,而且很多时候源 字符串的长度不可得知,其实都应该用下面来代替。
1: char* strncpy(char *dest, const char *src, size_t n);
3.2 阅读下面程序
1: void foo(int *a, int *b) 2: { 3: *a = *a + *b; 4: *b = *a - *b; 5: *a = *a - *b; 6: }
- 请写出这个程序是干什么的
- 这个程序有什么问题?
- answers 1
交换两个变量的值
- answers 1
如果使用同一个变量会出错 foo(&a, &a);
3.3 请写出以下程序片段的输出
1: #include <iostream> 2: using namespace std; 3: 4: class Base { 5: public: 6: void printA() 7: { 8: cout << "Base printA." << endl; 9: } 10: virtual void printB() 11: { 12: cout << "Base printB." << endl; 13: } 14: }; 15: 16: class Derived : public Base{ 17: public: 18: void printA() 19: { 20: cout << "Derived printA." << endl; 21: } 22: virtual void printB() 23: { 24: cout << "Derived printB." << endl; 25: } 26: }; 27: 28: int main() 29: { 30: Base *p_1 = new Base(); 31: Derived *p_2 = new Derived(); 32: Base *p_3 = new Derived(); 33: 34: p_1->printA(); 35: p_2->printA(); 36: p_3->printA(); 37: cout << endl; 38: 39: p_1->printB(); 40: p_2->printB(); 41: p_3->printB(); 42: return 0; 43: } 44:
- answers 1
结果:
Base printA.
Derived printA.
Base printA.
Base printB.
Derived printB.
Derived printB.
多态。
4 编程解释题
4.1 写一个函数实现字符串的左循环
void leftRotate(char *str, int n);
例如:
leftRotate("abcdefg", 3); "abcdefg" => "defgabc"
- answers 1
本题没有要求时间和空间复杂度,很多同学都是用了新开辟的一个同样大小的数组再复制,其实可以实现O(n)的时间,O(1)的空间
1: #include <stdio.h> 2: #include <string.h> 3: #include <math.h> 4: 5: void leftRotate(char *str, int n) 6: { 7: int length = strlen(str), 8: current = 0, 9: next = 0, 10: floorNum = 0; 11: 12: char temp = str[0]; 13: for (int i = 0; i != length - 1; ++i){ 14: next = (current + n) % length; 15: 16: // floorNum = floor(double(current + n) / length); 17: // next = (current + n) - length * floorNum; 18: 19: str[current] = str[next]; 20: current = next; 21: } 22: str[current] = temp; 23: } 24: 25: int main() 26: { 27: char str[] = "abcdefg"; 28: leftRotate(str, 2); 29: puts(str); 30: return 0; 31: }
-
如果想要实现参数为负数的时候会造成右旋。
就把 for 语句中第一句注释掉
把原来注释的两行恢复
- 下面讲解
例如: abcdefg 左旋 2 变成 cdefgab
0 1 2 3 4 5 6 src a b c d e f g dest c d e f g a b - 发现有如下替换
dest[0] = src[2] dest[1] = src[3] dest[2] = src[4] ... dest[x] = src[y]
x 与 y 的关系就是
y = (x + 2) % length
也就是
y = (x + n) % length
但是按照这样的顺序赋值会 丢失信息
如第一句就丢失 dest[0 ] 的内容
所以调整复制的顺序
int temp = dest[0]; dest[0] = src[2] dest[2] = src[4] dest[4] = src[6] dest[6] = src[1] dest[1] = src[3] dest[3] = src[5] // 上面赋值了 length - 1 次,于是最后 dest[5] = temp
-
上面程序中的 x, y 就是最终程序中的 current, next
- 下面附加讨论当 n 为负数的时候变成右旋。
- 先扩展一下有关于负数除法的小知识
-1 / 5 = ?
计算机程序在计算带有负数的正数除法的时候有两种结果
首先除出小数 -0.2
这时 如果答案为向 0 趋近的整数就叫做 truncate 除 (结果为 0 )
如果答案为向 负无穷 趋近的整数就叫做 floor 除 (结果为 -1)
除法结果不同导致取余结果也不同,利用下面式子推余数
a / b = x —— y
a = b * x + y
c++ 平台相关,但实际实验之后得到为 truncate 除
数学的模运算,python 使用 floor 除
- 返回题目
我们期望的结果是
例如: abcdefg 左旋 -2 变成 fgabcde
0 1 2 3 4 5 6 src a b c d e f g dest f g a b c d e 即
int temp = dest[1]; dest[0] = src[5] dest[5] = src[3] dest[3] = src[1] dest[1] = src[6] dest[6] = src[4] dest[4] = src[2] // 上面赋值了 length - 1 次,于是最后 dest[2] = temp
-
同样是上面的赋值,同样是
y = (x + n) % length
就看第一项,若直接使用 c++ 的 truncate 除
则 y = (0 - 2) / 7 = 0 —— -2
但如果是 floor 除就刚好可以得出需要的结果
y = (0 - 2) % 7 = -1 —— 5
再看一下原来注释的代码就知道了 其实是在模拟 floor 除的方法 …
4.2 给定单链表如下
1: typedef struct Node Node; 2: struct Node{ 3: int data; 4: Node *next; 5: }; 6: Node *head;
- 试判断链表有没有环,(即该单链表最后一个指针指向前面的元素) 另外可以保证如果没有环则最后一个指针指向 NULL (此题可以只说明方法)
- answers 1
- 可以采用龟兔赛跑的方法,定义快指针与慢指针,每次循环块指针走两步,慢指针走一 步。如果两指针相遇则是有环,如果快指针到达了链表尾,则说明没有环。
bool HasCycle(List list) { // 用快的迭代器去“追”慢的迭代器 if (!list->head) return false; Link slow = list->head, fast = list->head->next; while (fast && fast->next) { // 到头了,没环路 if (fast == slow || fast->next == slow) return true; // “追”上了,有环路 slow = slow->next; fast = fast->next->next; } return false; }
- answers 2
- 下面的讨论与本次考试题目无关,只是我的一点心得
- 如果链表想要很好的析构掉,必须要得到是否是环的信息,如果是环还必须 要知道节点在哪里才可以,不然删除到尾巴时会造成删除已经删除过的节点。
- 所以问题变得更加复杂,即需要找到交点的位置。请参考下图
假设链表进入环之前的长度为 x ,环本身的长度为 y 。假设交点为 A ,快慢点相 遇点为 B 。
- 先讲一下大概方法,同样是两个节点一块一慢,相遇到 B 之后,就把一个节点放到 链表首,然后两个节点同时一次走一步,最后两个节点相遇之时就是节点 A 。
- 下面进行证明
- 慢点无法在环内绕圈
即使极限情况,慢点与快点在环内一开始处于重合状态,即快点需要追一整个圈才 能追上慢点,快点也可以在慢点刚好走完一圈的时候追上。
- 设慢点走了 x + m
则快点走了 2 * (x + m) = x + m + k * y (k 为快点在相遇之前在环内转的圈 数)
可以推出 m + x = k * y
- 后来把一点放回起始点之后就同步前进,这意味着两点所走路程之差始终是 m + x 即 k * y 。所以当放回起点的点到达 A 时,必然另一点也到达 A 。
- 慢点无法在环内绕圈
5 其他
5.1 请说出你看过的技术相关书籍名称,以及看完之后的感想。
5.2 请说出 linux 的根目录底下文件有哪些,你知道的 linux 发行版名称。
5.3 随便说说你将来的理想,打算搞哪方面的技术,如果加入 c++ 小组,最希望学到的知识?
Date: 2012-03-05 一