几个面试题,大家都来答一下
3、在一副除去大小王扑克牌中,抽出五张牌,得到一手四带一牌型(例如AAAA2这种牌型)的几率为多少;如果把五张牌换成五颗骰子,摇出四带一的几率又是多大呢?
7、请把下面A列中的词用线与B列中和其相关的词连接起来,并分别解释连起来后的名词
AB
标识符生命周期
变量作用域
11、有10个编号从1-10的瓶子里,分别装着100粒药丸,已知其中一个瓶子中的所有药丸都比其他瓶子里正常的药丸重0.01g,现给你一把电子秤,只准称一次,请问如何可以知道哪个瓶子中的药丸是不符合标准的。
17、请问通过什么技术可以在C++里通过同一个指针,调用没有继承关系的两个不同对象的同名方法?
[解决办法]
3. answer: 13*48/ (5,52) = 0.00024009603841536616
7??
11.as upper.
17.*p = A::*fun();
*p= B::*fun();
[解决办法]
3. 1/(51*50*49) 1/(6*6*6)
7. 标识符与变量,作用域与生命周期 是两个很近似的概念,这道题考查的是对它们区别的理解。
个人认为变量是标示符的一种;
作用域体现的是空间作用范围,生命周期体现的是时间作用范围,
无论怎么连线都从这个角度解释就行了,
应该 重点强调const static 全局变量 栈存储空间 堆存储空间 静态数据区 等概念
[解决办法]
3. 扑克牌(54-4)*13/(54*53*52*51*50/1/2/3/4/5) = 0.0002055
筛子:5*6*5/6^5=0.019
[解决办法]
3、
扑克牌:C(13,1)*C(48,1)/C(52,5),其中C(x,y)表示从x中取y的组合数,即4楼的答案。
骰子:(6*5*5)/(6^5) = (5^2)/6^4),即11楼的答案。
7、
标识符 连 作用域:这是针对代码而言的。在作用域范围内,标识符有效,即,可以通过名字查找到该标识符。
变量 连 生命周期:这是针对运行时而言的。在生命周期范围内,变量有效,即,可以正常访问该变量。
11、
这个需要再仔细想一下,因为并不知道正常药丸的重量。
17、
这个目前想不到好办法。我理解的题义是该指针与某个对象组合就可访问该对象的函数,即指针“指向的”应该是函数而非对象。
如果两个类之间不存在继承关系,成员函数指针应该是不行的。还需仔细思考。
[解决办法]
[解决办法]
是这样的。
对于foo1中的foo,此时是可以正常访问变量a的。18楼所谓“正常访问”,是指在运行期的正常访问,即当你访问时,a是正常的;不是指编译期从语法上看,访问是否合法。
foo函数体内部不属于a的作用域,因而你无法查找到a,从而实际上没有访问过a。这正好说明了作用域和生命周期的区别。
编译期能否合法地访问,这取决于标识符的作用域;运行期访问时它是否正常,这取决于变量的生命周期。
比如,修改foo()为 foo(int& b)。
然后foo1中改为 foo(a)。
可以看到foo()函数体内部仍不属于a的作用域,但你访问由引用a的b时,它是正常的;此外b也显示了标识符和变量的区别。
后一个例子也类似。只有在foo中使用"a"这个名字(标识符)时,指代的是foo中的这个a(变量),但是它可以(不是必须)被传递到foo的外部,在运行期,不管在foo的内部还是外部,访问该变量都是正常的。
[解决办法]
关于17题,
好吧,不用“函数”这个词,我的意思是该指针代表的信息应该是关于这个同名方法的,而非关于对象的。
比如我们都知道成员函数指针。如果存在一类指针,是非固定类型的成员函数指针,比如:
void (any::*p)() = &A::fun | &B::fun; // 假想的语法
然后这个p可以这样使用:
A a; (a.*p)(); // 它应该等价于 a.fun();
B b; (b.*p)(); // 它应该等价于 b.fun();
那么这个p就满足我们的要求了。
但是C++本身的语法目前不存在这样的指针。需要我们去构造它。
这是我理解的题义。
[解决办法]
当然是有用的。
比如:
int* p = new int;
// pos1: 此时可以访问p所指的变量
delete p;
// pos2: 此时仍然可以使用p,但你不能正常访问p所指的变量了
我们知道,pos1是p所指的变量的生命周期范围内。那么,你可以在这里增加对*p的访问。但是在pos2时,就不可以了。
并且,还可以在pos1中,增加其他东西,如:
int& r = *p;
但是我们应该知道,在pos2里,r也不能被正常访问了。
生命周期告诉你,哪里是可以正常访问的(而不管你现在是否真的访问它);这样的话,你就可以在将来,在变量的生命周期内,任意的添加访问它的代码,而不必担心会出现异常情况。
而作用域则是语法有效的范围,如果你希望代码通过编译(或在需要时报一个错),那么你应该救助于作用域。
[解决办法]
关于11题。
给出一个部分解决方案,抛砖引玉。
假设正常药丸的重量为x,不符合标准的瓶子为y,则
1、首先对每个瓶子k,选取2k-1个药丸,即分别为1,3,5,...,19.
此时它们的总重量为 100x + 0.01(2y-1)
2、每个瓶子选取80个药丸。
它们的总重量为 80(10x+0.01)= 800x + 8
3、合并1和2,称重得W。
W = 900x + 8 + 0.01(2y-1)
4、设 w 为 W的小数部分,则
y = (100w+1)/2 // w必为1-19中的某个奇数。
这个方案成立的一个充分条件是x为0.01的整数倍(应用在第4步)。如果不是这样,就需要更好的方法。
在目前这个方案中,第2步好象是不必要的,原因就是它是不完整的。完整的方案会使用到这些药丸。
思路就是通过计算,从所称得的重量W中分离出x和y,毕竟W是你唯一可以得到的变量。在完整的方案中,针对任意的x和y,应该存在一种数学过程通过W(比如通过整数部分和两位以后的小数部分)计算出x,然后再计算出y.而构造这个过程就是该题关键所在。
[解决办法]
17我们可以取巧一下:
struct{
void (A::*p1)();
void (B::*p2)();
} *p;
然后通过p->p1和p->p2调用
另外,不知道C++语法是否允许将两个不同类成员指针类型放在一个Union内部。
[解决办法]
一个正常访问过程:
1.被访问的对象是存在的
(被访问对象在其生命周期内,主要表示在运行时(编译时有可能对其中一部分检查到))
2.能够通过某个标识符关联到这个对象在的内存
(涉及标识符的作用域,标识符的解析,主要表示在编译时(在运行时主要是动态类型解析))
3.标识符本身的访问权限
(访问过程的权限的解析,主要表示在编译时(大多数是编译时做的,对于只读权限的放在只读内存里可以进一步保证))
从上面可以看出访问不仅和生命周期(实体的存在性)相关
而是多种因素决定的,所以只将"正常访问","非法访问"和生命周期相关是不恰当的.
生命周期只是保证了一个最基本的要素:被访问者的存在性.
[解决办法]
11:分别从10个瓶子中取1,2,3,4,5,6,7,8,9,10颗药。在称这些取出药的总重量,比正常的多了多少克就是第几号瓶子。
[解决办法]
是这样的。
a.~A();之后,旧a的生命周期就结束了。在它和 new(&a) A(1); 之前的这段时间里,并没有新a存在。
当然 new(&a) A(1); 也会导致旧a的生命周期结束,如果之前没有调用过a.~A();的话。
“变量存在”,所谓存在确实是一个值得推敲的概念。何为变量存在?一种定义是指内存的存在(对应storage duration),还有一个定义就是指生命周期(对应lifetime)。前面几贴我都是使用第一种定义。
如果是第二种定义,即由生命周期来定义变量存在,则使用存在去描述生命周期是trivial的(属于循环定义)。
这就好比要找到“x满足性质P的充分必要条件”,回答:“x满足性质P是x满足性质P的一个充分必要条件”。
[解决办法]
第17题,按照28楼的思路,目前可以想到的方案是:
- C/C++ code
#include <iostream>#include <cstdlib>using std::cout;using std::endl;using std::system;class A {public: void func(){ cout << "A::func" << endl; }};class B{public: void func(){ cout << "B::func" << endl; }};template<typename T1, typename T2>class multi_func_ptr{public: typedef void (T1::*func1)(); typedef void (T2::*func2)(); operator func1() { return f1_; } operator func2() { return f2_; } func1 operator()(T1&) { return f1_; } func2 operator()(T2&) { return f2_; } void invoke(T1& t) { (t.*f1_)(); } void invoke(T2& t) { (t.*f2_)(); } multi_func_ptr(func1 f1, func2 f2) : f1_(f1), f2_(f2) {} multi_func_ptr& operator=(func1 f1) { f1_ = f1; return *this; } multi_func_ptr& operator=(func2 f2) { f2_ = f2; return *this; }private: func1 f1_; func2 f2_;};int main(){ multi_func_ptr<A, B> p(&A::func, &B::func); A a; (a.*p(a))(); // 无法做到 (a.*p)(); B b; (b.*p(b))(); p = &A::func; p = &B::func; void (A::*pp)() = p; // 或 multi_func_ptr<A, B>::func1 pp = p; (a.*pp)(); void (B::*ppp)() = p; // 或 multi_func_ptr<A, B>::func2 ppp = p; (b.*ppp)(); p.invoke(a); p.invoke(b); system("pause");}
[解决办法]
3、 在一副除去大小王扑克牌中,抽出五张牌,得到一手四带一牌型(例如AAAA2这种牌型)的几率为多少;如果把五张牌换成五颗骰子,摇出四带一的几率又是多大呢?
个人认为:(1) 1/13 * 1/13 * 1/13 * 1/13 = 0.00003501
(2) 1/6 * 1/6 * 1/6 * 1/6 * 5/6 = 0.000634
7、 还没想好
11、 一次从编号为1~10的10个瓶子中取1,2,3,。。。,10个,正常的每个重量为x,则这些球的总重量为
x *(1+10)* 10 / 2 = 55x, 电子秤称的总重量为y,则(y - 55x)/ 0.01即得非正常药丸的编号。
[解决办法]
17
把同名函数抽取为纯虚类
class Parrent
{
public:
virtual void func()=0;
};
class C1 : public Parent
{
public:
virtual void func(){};
};
class C2 : public Parent
{
public:
virtual void func(){}
};
只有这样才可以正确运行的
C1和C2没有继承关系,不知道是否满足楼主的条件
------解决方案--------------------
3.1 在一副除去大小王扑克牌中,抽出五张牌,得到一手四带一牌型(例如AAAA2这种牌型)的几率为多少;
第1张牌抽到A的概率是 4 / 50
保证前面的牌都抽到A,第2张牌也抽到A的概率是 3 / 49
保证前面的牌都抽到A,第3张牌也抽到A的概率是 2 / 48
保证前面的牌都抽到A,第4张牌也抽到A的概率是 1 / 47
再抽到一个 2 的概率是 4 / 46
上面的概率全部乘起来就是所求概率。
3.2 五颗骰子,摇出四带一的几率又是多大呢?
第1颗骰子随意! 取其数值所需值。
第2颗到4颗摇到所需值的概率都是 1/6,
第5颗摇到非所需值的概率是 5/6。
所以 (1/6)*(1/6)*(1/6)*(5/6) 就是所求概率
[解决办法]
愚见:
标识符----作用域
标识符 是 标识 一个实体的名字,比如我们用函数名标识函数,用变量名表示变量,用文件描述符标识打开的文件
只有声明了这个标识符的区域才可以知道这个标识符的存在,所以它表示了作用域。比如你要使用某些别人定义的函数,你必须现声明它的标识,
变量----生命周期
变量是一个实体,变量名是它的标识符。变量从定义开始它的生命周期。
前者是空间概念,后者是时间概念。
[解决办法]
关于第17题。
多继承我也考虑过。不过没能想到合适的方案。因为派生类的成员函数指针不能用于基类的对象。
另外,请注意题目是“调用没有继承关系的两个不同对象的同名方法”,是“对象”而不是“类”,所以多继承不合适。
如果原题是“调用没有继承关系的两个不同类的同名方法”,那么65楼的方法就可以了。(或许原题有瑕疵也说不定,呵呵)
52楼的方案是一个参考,没有很好地切合“指针”这个要求。模板、操作符重载、隐式类型转换甚至宏等等都可能是备选技术,如何设计还需斟酌。
至于函数原型,就成员函数指针而言这是没有办法的,必须有原型声明。当然更复杂的设计可以用模板参数来设定,这个方向暂不考虑。
第7题讨论得差不多了。第11题需要巧妙的数学构造(或者就是题目缺条件,呵呵),再说一下第3题吧。
之前大部分都是给答案,没有说过程。71楼开了一个好头。我也说一下思路吧:
3.1 在一副除去大小王扑克牌中,抽出五张牌,得到一手四带一牌型(例如AAAA2这种牌型)的几率为多少?
一个概率题,首先要找到一个等概率事件。在本题中,等概率事件就是任取五张牌的不同结果。可以按一定的顺序取,也可以不考虑顺序。前者有些复杂,造成71楼答案的瑕疵。鉴于每张牌都是不同的,这里使用后者,可以很方便地得出结果。
首先这个等概率事件拥有C(52,5)种可能,所以分母就是C(52,5).
然后构造四带一牌型。四张套按照点数,有C(13,1)种可能,花色无须考虑,因为必须全部包含。剩下的一张,可以在剩余52-4张中任取,即C(48,1)。根据乘法原理,总共是C(13,1)*C(48,1)种可能.
所以,答案就是C(13,1)*C(48,1)/C(52,5),其中C(x,y)表示从x中取y的组合数,即4楼的答案。
3.2 如果把五张牌换成五颗骰子,摇出四带一的几率又是多大呢?
等概率事件是五颗骰子分别投出的不同结果。注意与上题的区别,在本题中,每颗骰子是独立的。因此,考虑到骰子的点数之间没有区别,不能只看组合的结果,它们不是等概率的(例如12345和12222不是等概率的,所以不能把它们都只看作一种可能),还需要考虑排列。
首先这个等概率事件拥有6^5种可能,所以分母就是6^5.
然后构造四带一。第一步(步骤是独立的,可以不按这个顺序来)选择一个四颗的点数,它有6种可能;第二步考虑该点数的位置,在五颗骰子中位置有C(5,4)种可能,即5种;第三步考虑余下的一个骰子的点数,它有5种可能(我相信五颗同点不算是四带一)。总共是6*5*5种可能。
所以,答案是(6*5*5)/(6^5) = (5^2)/6^4),即11楼的答案。
[解决办法]
17、 请问通过什么技术可以在C++里通过同一个指针,调用没有继承关系的两个不同对象的同名方法?
你可以试一下用组合类的方法.但是楼主用的时候必须小心区别,调用.这种同名的用通过同一个指针调用很容易出错.