互聯網公司筆試常見陷阱

        關於2014年阿里、騰訊的實習生招聘,有些題目是陷阱。覺得有必要總結一下哈,在此感謝一下參與討論的筒子們,小芳,靜兒

陷阱分類:

(1)指針與字符串

7.   閱讀下面代碼,程序會打印出來的值是(D------------------------------(騰訊2014實習生筆試)

void f(char **p)
{
	*p += 2;
}
void main()
{
	char *a[] = { “123”,”abc”,”456”};
	f(a);
	printf(“%s\r\n”,*a);
}

A.123   B.   abc  C.  456  D.  3

 

void fun1(char **p)
{
    *p+=2;
    cout<<(int)(*p)<<endl;//輸出*p保存的地址
}

void fun2(char *p)
{
    p+=2;
}

int main()
{   
    char *r[]={"123","234","456"};
    fun1(r);
    cout<<int(*r)<<endl; //輸出*r保存的地址
     char *r1="1234";
    fun2(r1);
    cout<<*r<<endl<<r1; 
    return 0;
}

      圖1
從上圖1的紅框中可看出,*p和*r保存的地址相同,也就*p和*r指向同一地址,如圖2所示,*p和*r也就是p1

圖2

 

void fun1(char **p)
{
	p++;
	*p+=2;
}	
int main()
{	
	char *r[]={"123","234","456"};
	fun11(r);
	cout<<*r<<endl<<*(r+1);
	system("pause");
	return 0;
}

       運行結果爲圖3, 結合上面的程序和圖2所示,P++後,p指向p2,*p+=2後,p2指向4,在主函數中,r就是p1,p1未變所以第一行輸出123,(r+1)指向p2,p2已被更新,指向4,所以第二行輸出4。


圖3

8 下面C++程序的輸出是(B//---------------------------------------阿里2014實習生筆試

void f1(char *p)
{
	p++;
	*p='a';
}
int main()
{
	char a[sizeof("hello")];
	strcpy(a,"hello");
	char *a1=a;
	f1(a);
	cout<<a<<endl;
	return 0;
}

A hello   B hallo  C allo   D以上都不是

         如圖4所示程序中的a用圖4中的r表示,p其實只是a的一個副本,p++後,p指向e,將e用a覆蓋。但r任然指向H。所以cout<<a,結果是hallo。

 

圖4

       若將p改成指針的引用,程序如下所示,則p和r是同一變量,p只不過是r的別名,對p操作即是對r操作。所以有圖5的結果。

void f1(char *p)
{
	p++;
	*p='a';
}
void f2(char *&p)
{
	p++;
	*p='a';
}
int main()
{
	char a[sizeof("hello")];
	strcpy(a,"hello");
	char *a1=a;
	f1(a);
	cout<<a<<endl;
	strcpy(a,"hello");
	a1=a;
	f2(a1);
	cout<<a1<<endl;
	system("pause");
	return 0;
}

圖5

     另外,若主函數改成如下形式,則,運行出錯,無輸出,因爲指針指向的字符串是常量,不可更改。

int main()
{
	char *a="hello";
	f1(a);
	cout<<a<<endl;
	return 0;
}<span style="font-size:18px;"></span>

無輸出錯誤爲圖6:

圖6

 

9.   現在有以下兩個函數,調用test的結果是(B  //-----------------------騰訊2014實習生筆試

char* getMem(void)
{      
	Char * p = “hello world ”;
	P[5] = 0x0;
	Return p;
}
void test(void) 
{      
	char *s = 0x0;
	s = getMem();
	Printf(s);
}

A.  hello   B. 無輸出  C.  Hello0world   D.  不確定

 此題也將出現圖6的結果,因爲指針指向的字符串,是常量。只讀不可寫。

(2)虛函數與多態性、構造函數與析構函數

9 32位環境下,以上程序的輸出結果是(2014//-----------------------騰訊2014實習生筆試

class Base
{
public:
	virtual int foo(int x){return x*10;}
	int foo(char x[14]){return sizeof(x)+10;}
};
class Derived:public Base
{
	int foo(int x){return x*20;}
	virtual int foo(char x[10]){return sizeof (x)+20;}
};
int main(void)
{
	Derived  stDerived;
	Base * pstBase=& stDerived;
	char x[10];
	printf(“%d\n”,pstBase->foo(100)+pstBase->foo(x));
	return 0;
}

 

--------------------------------------------2014騰訊實習生筆試題

class Base
{
public:
	Base(){cout << "Base Construction"<<endl;}
	~Base(){cout << "Base Destruction"<<endl;}//(2)
};
class Derived:public Base
{
public:
	Derived(){ cout << "Derived Construction"<<endl;}
	~Derived(){cout << "Derived Destruction"<<endl;}(1)
};
int main()
{	
	Base *base = new Derived();
	delete base;
	return 0;
}

A先調用(1)再調用(2)  B先調用(2)再調用(1     C只調用(2   D只調用(1

程序運行結果爲圖7,因此只調用了基類析構函數。如果析構函數不被聲明成虛函數,則編譯器實施靜態綁定,在刪除基類指針時,只會調用基類的析構函數而不調用派生類析構函數,這樣就會造成派生類對象析構不完全。

圖7

若主函數改成如下程序,則運行結果爲圖8,派生類的指針,delete時,先調用派生類析構函數,再調用基類析構函數。

int main()
{	
	Base *base = new Derived();
	delete base;
	Derived *base1 = new Derived();
	delete base1;
	return 0;
}

 

圖8

若將派生類析構函數改爲虛函數,則結果爲圖9,此時,delete基類指針時,先調用派生類析構函數,再調用基類析構函數。

class Base
{
public:
	Base(){cout << "Base Construction"<<endl;}
	virtual ~Base(){cout << "Base Destruction"<<endl;}//(2)
};

圖9

           若將派生類的析構函數改爲虛函數,基類的函數爲一般析構函數,運行時卡在一個地方。說是內存泄露,不是很明白原因。結果爲圖10.

class Derived:public Base
{
public:
	Derived(){ cout << "Derived Construction"<<endl;}
	virtual ~Derived()
	{cout << "Derived Destruction"<<endl;}
};

圖10 

         但若再基類加一個虛函數。運行又不卡住,不知是何原因,望求高人指點。結果爲圖11

class Base
{
public:
	Base(){cout << "Base Construction"<<endl;}
	~Base(){cout << "Base Destruction"<<endl;}
	virtual void foo(){cout<<"Base foo"<<endl;}
};
class Derived:public Base
{
public:
	Derived(){ cout << "Derived Construction"<<endl;}
	virtual ~Derived(){cout << "Derived Destruction"<<endl;}
};

圖11


-------------------------------------阿里筆試題

class Base
{
public:
	int bar(char x)
	{
		return (int)(x);
	}
	virtual int bar(int x)
	{
		return 2*x;
	}
};
class Derive:public Base
{ 
public:
	virtual	int bar(char x)
	{
		return (int)(-x);
	}
	int bar(int x)
	{
		return x/2;
	}
};
class Derive2:public Derive
{ 
public:
	int bar(char x)
	{
		return (int)(-2*x);
	}
	int bar(int x)
	{
		return x/4;
	}
};

運行結果爲圖12,是不是有些出乎意料啊,哈哈

圖12

(3)變量所佔字節數及存儲位置

13.請看一下這一段C++代碼,如果編譯後程序在windows下運行,則一下說話正確的是(AC)-------------騰訊2014實習生筆試

Char*p1 = “123456”;

Char*p2 = (char*)malloc(10);

A.  P1 p2都存在棧中

B.   P2指向的10個字節內存在棧中

C.  堆和棧在內存中的生長方向是相反的

D.  “123456”6個字符存儲在棧

 看了圖12,你就懂了。。。。

圖13

8.   Char p1[] = “Tencent”, void *p2 = malloc((10)32位機器上sizeof(p1)sizeof(p2)對應的值是(C)  //--------騰訊2014實習生筆試

A.  80   B.   410   C.  8 D.  44

 

2 . 64位系統上,定義的變量int *a[2][3]佔據(D)字節  //-------------------阿里2014實習生筆試

A 4    B 12    C 24   D 48

 

(4)時間複雜度

2.   假設函數f1的時間複雜度O(n),那麼f1*f1的時間複雜度爲(A)

//----------------------------騰訊2014實習生筆試

A.  O(n)   B. O(n*n)   C. O(n*log(n))   D.  以上都不對

這個題真的比較坑爹啊,這裏的*是f1的結果相稱,不是複雜度相乘,f1就調用了2次,哎。。。。>....<

 

11. 在一臺主流配置的PC機上,調用f(35)所需要的時間大概是(C)

//---------------------------------------阿里2014實習生筆試

unsigned long long  cnt=0;
int f(int x)
{
	int s=0;
	cnt++;
	while(x--)
		s+=f(x);
	return max(s,1);
}


A 幾毫秒   B幾秒  C幾分鐘   D幾小時

分析如下:

f(0)=1,f(1)=1+f(0)=2f(0),f(2)=1+f(1)+f(0)=4f(0),f(3)=1+f(2)+f(1)+f(0)=8f(0),f(n)=2^nf(0),f函數執行2^n次,分析結果正如圖14所示。圖14所示爲:cout<<n<<":   "<<cnt<<"  "<<sum<<"  time:  "<<GetTickCount()-start<<endl;

輸出依次爲:n的取值、f()的執行次數cntsum的值、程序運行時間(單位ms

圖14

圖15

實際n=35是,執行次數2^35=34359738368,運行時間爲1368031ms約22.8分鐘,PC配置爲CPU 2G 22

目前主流PC配置爲主頻3.5G 4核。設指令週期2-5f()執行一次,要執行10條指令左右。因爲是4核的,1s鍾估測運算3.5G條指令。3.5*10^9/10=100s。這樣算,大概只需要2分鐘,當然,計算機不可能不做其他事。CPU也不可能100%使用,時間肯定大於2分鐘。

(5)輸出流執行順序

圖16的結果是不是讓你震驚啊,輸出流是從右向左運算的。和函數調用參數的運算順序相同也是從右向左運算的。

 

圖16

下面代碼的輸出,也讓人奇怪,後加很好理解,輸出流從右向左運算。前加就有點莫名其妙了。結果爲圖17,反彙編,有點長,不分析了哈。

	int n=10;
	cout<<n<<" "<<n++<<" "<<n++<<endl;
	cout<<n<<" "<<n++<<" "<<n++<<" "<<++n<<" "<<++n<<endl;

圖17


(6)函數參數執行順序

void canshutest(int a,int b)
{
	cout<<a<<endl<<b<<endl;
}

int f1(int a)
{
  cout<<"a="<<a<<endl;
  return a;
}

int f2(int b)
{
	cout<<"b="<<b<<endl;
	return b;
}
int main()
{	
	int n=10;
	canshutest(f1(++n),f2(++n));	
	canshutest(n++,n++);
	canshutest(++n,++n);
	return 0;
}


圖18

圖19說明如下:

將變量n放入寄存器eax

然後eax+1

再將eax放入變量n

變量n再賦給ecx

ecx+1

ecx再賦給變量n

然後變量n賦給寄存器edx

edx壓棧

調用f2

圖19

 

圖20

 

圖21

 

看完以上3張圖片,自然就明白了

(7)函數返回值

有以下程序,其執行結果是(B

int test(char a,char b)
{
	if (a)
		return b;
}
int main()
{
	int a='0',b='1',c='2';
	printf("%c\n",test(test(a,b),test(b,c)));
	system("pause");
}

A 函數調用出錯   B 2   C 0   D 1

 

int test(char a,char b)
{
	if (a)
		return b;
}

雖然編譯是給出警告,若a0時,函數返回0;並非出錯。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章