C/C++練習題(三)


1、對下面兩個文件編譯后,運行會輸出什么?

// 第一個文件a.c
#include <stdio.h>
extern char p[];
extern void f();

int main()
{
	f();
	printf("a.c: %s\n", p);
	return 0;
}

// 第二個文件b.c
char* p = "Hello World";
void f()
{
	printf("b.c: %s\n", p);
}

打印結果:

b.c: Hello World
a.c: ل€¤※@_

分析:在我們看來,雖然使用字符數組和字符指針差不多,printf都可以打印出字符串出來,但是編譯器對他們的處理完全不同。 **對於字符指針,編譯器看到后,會把里邊保存的值取出來,然后在去這個地址值處,將字符串取出來(進行一次尋址);對於字符數組,編譯器直接到數組首地址處打印字符串。** 在這里b.c定義的是字符指針,也就是說**p的地址**不是“helloworld”的地址,**p中保存的**才是“helloworld”的地址。但是到了a.c里面,卻聲明成了數組,所以編譯的代碼就不會進行尋址了,直接把p的地址當成了“helloworld”的地址打印出來。

2、下面程序輸出什么?

#include <stdio.h>
int main()
{
	int a[5] = {1, 2, 3, 4, 5};
	int* p1 = (int*)(&a + 1);
	int* p2 = (int*)((int)a + 1);
	int* p3 = (int*)(a + 1);
	printf("%d, %d, %d\n", p1[-1], p2[0], p3[1]);
	return 0;
}

打印結果:

5, 33554432(0x2000000), 3

分析:p1和p3沒有問題,關鍵是p2,為什么是0x2000000呢?
我們來看a的地址分布:a[0] -> a[4] ==>低地址 -> 高地址
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
然后(int)a+1指向的地址如下:
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
然后p2就指向這個地址了,之后將這個地址當成整形輸出,就是輸出下面的東西:
01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00 05 00 00 00
組合成十六進制的形式就是0x02000000。


1、下面的代碼輸出什么?為什么?

#include <stdio.h>

#define f(a,b) a##b
#define g(a) #a
#define h(a) g(a)

int main()
{
	printf("%s\n", h(f(1, 2)));
	printf("%s\n", g(f(1, 2)));
	return 0;
}
結果:12 f(1, 2) 分析: 1:#在宏中表示將后面的符號變成C語言字符串;##表示將兩個符號連接成一個新的符號。 2:宏和函數的調用不同,當宏定義中還使用了其他宏的時候會先擴展嵌套的宏,否則就直接進行宏替換。如h(f(1, 2))會先擴展f(1, 2)是12,然后在h(12) = "12"

2、下面的代碼輸出什么?為什么?

#include <stdio.h>
int main()
{
	int i = 1;
	printf("%d, %d\n", sizeof(i++), i);
	return 0;
}

這個題和編譯器無關,輸出一定是 4,1

分析:sizeof是編譯器的武器,編譯器用它只看結果不會計算的。sizeof(i++)中i++肯定是int類型,所以在編譯期間這個語句就變成4了,i++沒有執行。

3、下面的代碼輸出什么?為什么?

#include <stdio.h>    

#define PrintInt(expr) printf("%s: %d\n", #expr, (expr))

int FiveTimes(int a)
{
    int t;
    t = a << 2 + a;
    return t;
}

int main()  
{
	int a = 1;
    PrintInt(FiveTimes(a));
    return 0;
} 
結果:FiveTimes(a): 8 考點有兩個:1、還是之前的#運算符的作用; 2、也是產品中最容易出現bug的地方,位運算的優先級比算術優先級要低。 對於軟件開發來說,bug的原因很多時候都是基礎不扎實,如果不想多加班,就多把基礎搞搞。

4、char * * string = "abcdefg";   * (*string)++; 這個表達式的值是什么?


分析:string是一個二級指針,卻保存着“abcdefg”的地址。 * string代表到string指向的內存中取一個大小為sizeof( * string)的數據。string指向“abcdefg”,那么假設取的4字節數據就是0x12345678,之后是 * (0x12345678)++,意思是到0x12345678中取值,誰知道運行時0x12345678地址的值是什么呢???

1、用二進制來編碼字符串“abcdabaa”,需要能夠根據編碼,解碼回原來的字符串,那么最少需要多長的二進制字符串?(原google筆試題)

思路:考慮 霍夫曼樹


2、有20個數組,每個數組里面有500個數組,降序排列,每個數字都是32位的uint,求出這10000個數字中最大的500個數?(原百度筆試題)

思路:這個問題是考對歸並排序的理解,這些數組都是排序好的,只需要直接歸並即可。還可以考慮到競爭樹,在效率上基本上同級別。


3、有個數組a[100]存放了100個數,這100個數取自1-99,且只有兩個相同的數,剩下的98個數都是互不相同的,寫出一個搜索算法找出相同的那個數?

思路:根據題目的意思,我們只需要掃描數組一遍就OK了,不需要輔助空間。只需要把所有的數組元素相加,然后減掉1+2+···+99就可以了。


1、死鎖發生的必要條件是(ABCD)

A:互斥條件
B:請求和保持
C:不可剝奪
D:循環等待

思路:這個問題是考對歸並排序的理解,這些數組都是排序好的,只需要直接歸並即可。還可以考慮到競爭樹,在效率上基本上同級別。



2、Android 內核和 Linux 內核的區別?

Android 是執行於 Linux kernel 之上,但並不是 GNU/Linux。因為在一般 GNU/Linux 里支持的功能,Android 大都沒有支持,包括 Cairo、X11、 Alsa、 FFmpeg、 GTK、 Pango 及Glibc 等都被移除掉了。
Android又以 bionic 取代 Glibc、以 Skia 取代 Cairo、再以 opencore 取代FFmpeg 等等。
Android 為了達到商業應用,必須移除被 GNU GPL授權證所約束的部份,例如 Android 將驅動程序移到 userspace,使得 Linux driver 與 Linux kernel 徹底分開。bionic/libc/kernel/ 並非標准的 kernel header files。Android 的 kernel header 是利用工具由Linux kernel header 所產生的。
Android 對 Linux 內核的主要剪裁部分:
Goldfish
YAFFS2
藍牙
調度器
Android 新增驅動
電源管理
以及新增的額外調試功能、鍵盤背光控制、TCP 網絡管理等。



3、下面代碼輸出什么?為什么?

int main()
{
	char a[1000];
	int i;
	for(i=0; i<1000; i++)
	{
		a[i] = (char)(-1 - i);
	}
	printf("%d\n", strlen(a));
	return 0;
}
輸出結果為 255 分析: 我們來逐一計算 a 中元素的值: a[0] = -1, a[1] = -2, a[2] = -3, … , a[126] = -127, a[127] = -128, a[128]= 127, a[129] = 126, … , a[255] = 0; …… 在 C 語言中 char 的取值范圍是[-128, 127],所以當 i=127 的時候 a中的元素 a[127]將存儲 char 類型所能存儲的最小值-128;當 i=128的時候(-1 - 128)的值為-129,這個時候 char 類型將無法表示這個數值,於是產生了溢出(為何溢出后的值為 127,請參考《計算機組成原理》的相關內容),其結果反轉為 char 所能表示的最大值 127,以此類推,當 i=255 的時候,a 數組中出現第一個 0 值(就是'\0')。所以,將數組 a 作為參數調用 strlen 得到的結果為 255。

1、逆波蘭表達式

正常的表達式稱為中綴表達式,運算符在中間,主要是給人閱讀的,機器求解並不方便。 例如: 3 + 5 * (2 + 6) - 1 而且,常常需要用括號來改變運算次序。 相反,如果使用逆波蘭表達式(前綴表達式)表示,上面的算式則表示為: - + 3 * 5 + 2 6 1 不再需要括號,機器可以用遞歸的方法很方便地求解。

2、實現下面函數

寫一個函數,它的原形是 int continumax(char *outputstr,char *intputstr)
功能:
在字符串中找出連續最長的數字串,並把這個串的長度返回,並把這個最長數字串付給其中一個函數參數 outputstr 所指內存。
例如: "abcd12345ed125ss123456789"的首地址傳給 intputstr 后,函數將返回 9。outputstr 所指的值為 123456789。

提示:這個問題可以在遍歷一般字符串后得到答案,就是一個統計的問題。 ``` #include #include using namespace std;

int continumax(char *outputstr, char *inputstr);
int main()
{
char *s = "abcd12345ed125ss123456789";
char ss[256];
int n = continumax(ss,s);
return 0;
}
int continumax(char *outputstr, char inputstr)
{
int n = 0;
int maxs = 0;
int len = 0;
char tmp;
char poi;
while (
inputstr != '\0')
{
if ((
inputstr >= '0')&&(
inputstr <= '9'))
{
tmp = inputstr;
++n;
inputstr++;

		while (*inputstr != '\0')
		{
			if ((*inputstr >= '0')&&(*inputstr <= '9'))
			{
				++n;
			}
			else
			{
				break;
			}
			inputstr++;
		}
		if (n > maxs)
		{
			maxs = n;
			poi = tmp;
		}
	}
	else
	{
		n = 0;
	}
	
	inputstr++;
}
for (int i = 0; i < maxs; ++i)
{
	*outputstr++ = *poi++;
}
*outputstr = '\0';
return maxs;

}



---


**1、下面程序有沒有問題,為什么?**

int main()
{
int i = 10;
int array[i];
return 0;
}

<font color=#90c>
在 C99 規范之后 C 語言可以定義變量數組,也就是定義時下標用變量來定義的數組。而 GNU 一直都緊跟規范,所以在最新的 linux 版本中自帶的 gcc 和 g++都支持變長數組了。
然而,事實上的 C 語言規范是 C89 ,目前絕大多數公司的產品都是用 C89 編譯器的,極少有公司使用 C99,對於 C++ 它的存在有一個使命 就是兼容 C 語言,所以 C99 支持變長數組后
C++也支持變長數組。因此 我們在做軟件時以 C89 為規范寫代碼。這其實很簡單的,規范和實際的工程有不同的,目前好多硬件生產廠商是很不願意去升級自己提供的編譯器。C89 用得好好的,為什么要大力氣去支持
C99 呢?費時費力但對財政報表沒有幫助,而軟件廠商會覺得自己的大多數代碼都是 C89 寫的,為什么要吃螃蟹呢?所以很多規范制定得好,但是執行得不一定好。

---

---

**2、下面程序應該輸出什么呢?**
```int main()
{
    int a = 10;
    int b = a;
    char c;
    int d = b;
    int& array[2] = {a, d};
    printf("%d\n", &array[1] - &array[0]);
    return 0;
}
從 C 語言的角度 這個程序應該輸出 1 原因很簡單 因為這個就是一個典型的 指針運算(兩個數組的下標差) 我們假設 C++支持這么寫 那么從 C++的角度來說 應是計算變量 a 和變量 d 之間的地址差 因此輸出絕對不會是 1 那么大家想想 這樣子 是不是就改變了 C 語言本來的語義 也就是說如果支持這么寫 那么就沒有兼容 C 語言的特性了 **大家知道 C++不支持引用數組的原因了嗎** 一旦支持那么將改變原有的 C 語言語義 兼容性被破壞。

1、下面程序輸出什么?

#include <stdio.h>
int main()
{
	int a[5][5];
	int (*p)[4];
	p = a[0];
	printf("%d\n", &p[3][3] - &a[3][3]);
	return 0;
}
答案是 -3,而不是 0,為什么呢? 數組 int a[5][5]; 可以等價於 typedef int(AA)[5]; AA a[5]; a[3][3]的地址是: (unsigned int)a + 3 * sizeof(AA) + 3 * sizeof(int) ==> (unsigned int)a + 3 * **20** + 3 * 4 p[3][3]的地址: (unsgined int)a + 3 * **16** + 3 * 4 於是:&p[3][3] - &a[3][3]就等於: (3 * 20 - 3 * 16)/4 = -3

2、編寫一個函數,該函數的參數是一個長整型數,返回值是長整型,且函數的返回值是參數中各個十進制位的從大到小排序的整數(性能要求:以最少的空間和時間完成)如:原型: long f(long n);調用: long num = f(1302181);函數返回后 num 的值為 8321110

主要思想: **十進制數的每一位出現的數字只可能為 0-9,因此可以先統計各個位上的數字出現的次數,然后根據這些統計信息重新組合為一個符合要求的十進制數返回。** 實現代碼如下: ``` long f(long n) { int ret = 0; int i = 0; int fact = 1; int s[10] = {0}; // 輔助空間 int sign = (n > 0) ? 1 : -1;
n = (n > 0) ? n : -n;

while(n)
{
	s[n%10]++;
	n /= 10;
}
for(i=1; i<10; i++)
{
	while( s[i] )
	{
		ret += i * fact;
		fact *= 10;
		s[i]--;
	}
}
while( s[0] )
{
	ret *= 10;
	s[0]--;
}
return ret * sign;

}

<font color=#f0f>
在這個算法中,循環次數為 2n,所需要的執行時間與問題規模 n 成線性關系,算法復雜度為 O(n)。



---



###1、下面程序輸出結果是什么?為什么?

int main()
{
int i;
int a[5];

for(i=0; i<=5; i++)
{
	a[i] = -i;
	printf("%d, %d\n", i, a[i]);
}
return 0;

}

<font color=#f00>
答案:死循環
<font color=#00f>
相信大家一眼就可以看出本題的主要問題是數組 a 只有 5 個元素,訪問其元素的下標分別是0, 1, 2, 3, 4。當循環變量 i 的值為 5 的時候將訪問 a[5],這個時候產生了一個數組越界的錯誤。但問題是,為什么會產生死循環,當 i 的值為 5 的時候,程序究竟做了什么?在 C語言中產生死循環只有一個原因,就是循環條件一直為真。在本題中也就是 i<=5 將一直成立。可問題是,循環變量 i 在每次循環結束后都做了 i++。理論上,不可能產生死循環。為了弄清楚問題的本質,我們先弄清楚 a[5]究竟代表什么?在 C 中 a[5]其實等價於(a+5),也就是說當 i 的值為 5 的時候,將對 a+5 這個內存空間進行賦值。<font color=#f00>很不幸,在本題中 a+5 這個內存空間正好就是 i 的地址</font>,也就是說當 i 的值為 5 的時候,在 for 循環中的賦值語句其實
等價於 i=-i,即將 i 賦值為-5,因此 i<=5 永遠滿足。更進一步,為什么 i 正好在 a+5 這個位置呢?

<font color=#f0f>
據推測,PC機采用的是減棧。

>**空棧**:棧指針指向空位,每次存入直接存入然后棧指針移動一格,每次取出則先移動一個才能取出
 
>**滿棧**:棧指針指向棧中最后一格數據,每次存入需要先移動棧指針一格再存入,取出直接取出,然后在移動棧指針
 
>**增棧**:棧指針移動時向地址增加的方向移動的棧
 
>**減棧**:棧指針移動時向地址減少的方向移動的棧
 
 <font color=#ff4500>
**在分配棧空間的時候,先分配i的地址(處於棧的高地址),然后分配a[5]的棧地址(處於棧的低地址),但是在a[5]內部的訪問從a[0] -> a[4]卻是從低地址 -> 高地址的,這樣如果越界,就會與i的地址沖突。**

我們看下面的例子:

int main()
{
int i;
int a[5];

printf("%X\n", &i);
printf("%X\n", a+4);
printf("%X\n", a+3);
printf("%X\n", a+2);
printf("%X\n", a+1);
printf("%X\n", a);

}


最后得到的結果是:
BFA5F240
BFA5F23C
BFA5F238
BFA5F234
BFA5F230
BFA5F22C
從運行結果可以知道:局部變量 i 確實緊跟着數組中的第 4 個元素 a[4]在棧上分配空間,因此 a[5]就是 i。
 <font color=#f0f>
(類似的還有之前《[C/C++練習1](http://blog.csdn.net/lvonve/article/details/53286916)》的第二小題,也是一樣的道理。)



---



**1、進程和線程的描述,哪些是對的 (C )**
A. 操作系統的一個程序必須有一個進程,但是不必須有一個線程
B. 進程有自己的棧空間,而線程只共享父進程的棧空間
C. 線程必從屬於一個進程
D. 線程可以更改從屬的進程

<font color=#90c>
程序並不能單獨運行,只有將程序裝載到內存中,系統為它分配資源才能運行,而這種執行的程序就稱之為進程。程序和進程的區別就在於:程序是指令的集合,它是進程運行的靜態描述文本;進程是程序的一次執行活動,屬於動態概念。
<font color=#90c>
進程和線程的主要差別在於它們是不同的操作系統資源管理方式。進程有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產生影響,而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等於整個進程死掉,所以多進程的程序要比多線程的程序 健壯,但在進程切換時,耗費資源較大,效率要差一些。但對於一些要求同時進行並且又要共享某些變量的並發操作,只能用線程,不能用進程。
<font color=#90c>
A,創建進程的時候就會有一個線程存在 B,線程有自己的棧空間
老師分析:
程序是一個可執行的文件,進程是一個可執行文件執行后在內存中的實體。<font color=#f00>進程是操作系統分配資源的基本單位</font>,如,分配內存,分配 CPU 周期等資源,<font color=#f00>而線程是執
行流的基本單元</font>因此,每個進程必然有一個線程才可能運行得起來,否則就只有一堆資源,沒有執行過程了。
按照分析 顯然 A D 就錯了
D 錯的原因是 線程執行需要資源 因此必然屬於一個進程而不能更改
B 這個最容易搞錯 線程被創建后必須有自己的棧空間 而不能共享 因為多個線程同時運行后 CPU 的調度需要知道線程的執行流 共享的話 無法多線程了

---

---

**2、下面哪些是穩定排序? (AD )**
A. 冒泡兒排序
B. 快速排序
C. 堆排序
D. 歸並排序
E. 選擇排序

<font color=#90c>
排序的穩定性指的是,如果兩個數據元素的值相同,那么排序后原來排在前面的總是排在前面,否則就不穩定。**(冒泡 歸並 插入 都是穩定的)**

---

---

**3、下面程序輸出什么?**

int main()
{
int i = 43;
int n = printf("%d\n", i);
printf("%d\n", n);
return 0;
}

><font color=#90c>
>好,大家深入考慮一下,為什么返回是3 。這背后有什么鮮為人知的秘密,到底是C語言離奇的規定,還是深思熟慮后的決定?
><font color=#90c>
>相信大家都在學習驅動的時候應該知道有一種字符設備驅動。在 linux 中一切東東都是文件,外設也是文件,也就是說顯示器也是文件,那么 printf 的實現其實就是調用顯示器的驅動程序往這種外設寫入數據,所以我們來考慮一下,顯示器屬於什么設備呢,字符型設備。所以<font color=#f00> printf 返回的其實不應該是輸出的字符個數,准確的說應該是向字符設備寫入的數據的字節數。</font>因為 char 占用一個字節,所以碰巧“printf 返回輸出字符的個數”,這個說法正確了。

---

---

**4、#define中用到了array,但是array在后面才定義的,合法嗎?為什么?最后程序輸出什么?**

include <stdio.h>

define TOTAL_ELEMENTS (sizeof(array) / sizeof(array[0]))

int array[] = {23,34,12,17,204,99,16};
int main()
{
int d;
for(d=-1;d <= (TOTAL_ELEMENTS-2);d++)
printf("%d\n",array[d+1]);
return 0;
}

<font color=#90c>
第一個: #define 中用到了 array, 但是 array 在后面才定義的,合法嗎?為什么?
其實合法的,在編譯之前是預編譯,預編譯會處理#define之流的東東,在編譯時這個define就沒了。
第二個:程序輸出什么?
程序不會輸出任何東西。因為 int 和 unsigned int 比較時會被轉換為無符號的,因此-1就直
接被看成 0xFFFFFFFF 了,這樣 d 不可能小於條件中的表達式。自然 for 不會執行。 <font color=#f00>注意一點 sizeof 是編譯的工具,它的計算結果是無符號的。</font>

---

---

**5、char **p, a[16][8];問:p=a 是否會導致程序在以后出現問題?為什么?**

<font color=#00f>
p[1][2]和 a[1][2]代表一樣的意思嗎?
我們應該知道 二維數組在內存中也是一維排布的
所以 a[1][2] 代表 第 11 個元素
我們再來看 p[1][2] 因為 p 的類型為 char**p 所以 p[1][2] 代表的意思 *(*(p+1) + 2) ,p指向一個 char* 所以 p+1 向前移動 4 個字節 后面+2 再向前移動 2 個字節 一共 6 個字節。所以說 不一樣。

---

---

**5、int d[3][5] = {{1}, {2}, {3}}; (*p)[5] = d; 下面表達式中值不為0的是?為什么?**
A. *&d[1][2] B. p[1][2] C. *(p+1*5+2) D. *(*(p+1)+2)

<font color=#00f>B和D統一,C是野指針了。

---

---

**6、下面函數有沒有問題?如果有,如何修改?**

int square(int* ptr)
{
return *ptr * *ptr;
}

<font color=#90c>
對於這個題目主要的問題是 ptr 所指向的地址內容很可能被意想不到的改變 而 ptr 就可能反映不出這個改變了。所以 square 就必須解決這個,因此,改變的第一步就應該是
int square(<font color=#f00>volatile</font> int *ptr)
{
    return *ptr * *ptr;
}
但是,這樣改也有問題,如果 ptr 真的被意想不到的改變了,那么 square 就顯得不會是某個數的平方。所以最后的方案應該是:
int square(volatile int *ptr)
{
  int a = *ptr;
    return a * a;
}




---


**1、下面程序有沒有問題,為什么?**

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("/nArea = %f", area);
return area;
}


<font color=#90c>
中斷是嵌入式系統中重要的組成部分,這導致了很多編譯開發商提供一種擴展—讓標准 C支持中斷。具代表事實是,產生了一個新的關鍵字__interrupt。上面的代碼就使用了__interrupt關鍵字去定義了一個中斷服務子程序(ISR)。
這個是經典的面試題,它的問題如下:
**1.ISR 不能返回一個值。**
**2.ISR 不能傳遞參數。**
**3.由於重效率問題不要在 ISR 中做浮點運算。**
**4.不要在 ISR 中做 IO 操作。**
(printf 這個函數是一個 IO 函數 很多 IO 函數都是不可重入的。也就是說如果在執行 printf 的時候這個中斷產生了,那么是不是 printf 又被調用了?
另外的原因就是 printf 實在太慢了( printf 的實現其實還調用了 sprintf 和 write 系統調用))
下面就有一個問題了,既然 ISR 有那么多限制,那么如果我們真的需要做這些事,那么怎么辦呢?
好,下面就要提一下系統設計的一些技巧。在一些大型系統中 ISR 一般只是做一些投遞消息的操作。比如發生了中斷,然后就只是向一個隊列中插入一個中斷號,這樣就可以在其他的線程中處理這個中斷,這樣就繞開了限制。

---

---

**2、編譯期和運行期的疑問?**
**).一個程序哪些東西可以在編譯期就確定?**
**2).一個程序哪些東西要等到運行期才確定?**
**3).比如:int x = 100; x是在什么時間被賦值的?**

<font color=#90c>
1、靜態的賦值,比如 int a = 2; sizof的計算等。編譯器會盡自己最大的努力在編譯階段確定足夠多的東西
2、實在是在編譯階段無法確定的東西,比如指針的多態表現,對內存的動態分配等
3、int x = 100 顯然是編譯期
編譯期分配內存並不是說在編譯期就把程序所需要的空間在內存里面分配好,而是說在程序生成的代碼里面產生一些指令,由這些指令控制程序在運行的時候把內存分配好。不過分配的大小在編譯的時候就是知道的。
而運行期分配內存則是說在運行期確定分配的大小,存放的位置也是在運行期才知道的。

---

---

**3、為什么ABC不行?**

define ABC #pragma message("aaa")

int main()
{
//#pragma message("aaa") //這樣是可以的
ABC //這樣就不行
return 0;
}


<font color=#90c>當然不行,#define 中,不能嵌套其它預編譯的代碼。

---

---

**4、題目:實現一個袖珍型計算器,假設已經從界面編輯框讀入兩個數 op1 和 op2,和一個操作符 per( +, -, *, /) ,可以用 switch 來實現,代碼如下:**
**switch(per)**
**{**
    **case ADD:**
        **result = add(op1,opt2);**
        **break;**
    **case SUB:**
        **result = sub(op1,op2);**
        **break;**
    **……**
**}**
**如果對於一個新奇的有上百個操作符的計算器,這個 switch 將會很長。有沒有什么辦法可以縮短這個代碼量,看起來簡潔明了呢?**

<font color=#90c>
大家想想如何做 這個確實在大型軟件系統中常用。
老師:
第 0 步:
定義操作函數

int add(int p1, int p2)
{
return p1 + p2;
}
int sub(int p1, int p2)
{
return p1 - p2;
}

第1步:
定義函數指針

typedef int(*pFunc)(int, int);

第2步
定義結構體

struct Operation
{
int op;
pFunc func;
};

第3步
定義操作列表

static struct Operation OpArray[] =
{
{ '+', add },
{ '-', sub },
};

第四步
定義 run 函數

int run(int op, int p1, int p2)
{
int ret = 0;
int i = 0;
for(i=0; i<sizeof(OpArray)/sizeof(*OpArray); i++)
{
if( OpArray[i].op == op )
{
ret = OpArray[i].func(p1, p2);
}
}
return ret;
}

<font color=#f00>如果你現在要增加一個新的操作乘法,你會如何修改上面的代碼呢?只需要修改第0和3步就可以了。這樣來寫代碼是不是對於后面的擴展很有好處了,只需要多定義一個操作函數 和 把操作函數添加到操作列表中就可以了。</font>
<font color=#90c>
其他的解法:
1、申明並實現單個功能函數:

Type add(Type, Type);
Type sub(Type, Type);
Type mul(Type, Type);
……

2、初始化函數指針數組:

Type (*oper_func[])(Type,Type) = {add,sub,mul,...};

3、求計算結果:

result = oper_funcadd/sub/mul;





---



###1、關於 int a[10]; 問下面哪些不可以表示 a[1] 的地址?

A. a+sizeof(int)
B. &a[0]+1
C. (int)&a+1
D. (int
)((char*)&a+sizeof(int))



**分析:**
A. a+sizeof(int) 
// No, 在32位機器上相當於指針運算 a + 4
B. &a[0]+1 
// Yes,數組首元素地址加1,根據指針運算就是a[1]的地址
C. (int*)&a+1 
// Yes,數組地址被強制類型轉換為int*,然后加1,這樣和B表示的一個意思
D. (int*)((char*)&a+sizeof(int))
// Yes,數據地址先被轉換為char*,然后加4,根據指針運算公式,向前移動4 * sizeof(char),之后被轉換為int*,顯然是a[1]的地址

---

###2、基於比較的排序的時間復雜度下限是多少?

A. O(n)
B. O(n^2)
C. O(nlogn)
D. O(1)

>**大家記住這個結論就好  在當前 計算機科學界 對於基於比較的排序  最快只是O(n*logn)**


---

###3、完成字符串拷貝可以使用 sprintf、strcpy 及memcpy 函數,請問這些函數有什么區別,你喜歡使用哪個,為什么?

>**strcpy**是最初C庫中的字符串處理函數,只能用於以0結束的字符串,甚至不能用於字符數組的處理,因為strcpy不帶長度信息因此是不安全的函數,很多黑客都是從這個函數入手做很多事。
>
**memcpy**是內存拷貝函數,可以拷貝任意的內存,帶長度信息,屬於安全的函數。
>
**sprintf**是字符串格式化函數,可以將幾乎任意的基礎類型值格式化為字符串,因為不帶長度信息,所以這個函數也是不安全的.
>
這三個函數都可以做字符串的拷貝,但是三個函數的設計出發點不同,這個題目主要是考察面試者的對C標准庫的熟悉程度。


---

###4、是地址之差還是地址下標之差?

int* p = 0;
printf("%d\n", &(p[9]));
printf("%d\n", &(p[9])-p);

**打印:36, 9 **
>地址相減則為下標差。



---



###1、進程和線程的描述,哪些是對的( C )
A. 操作系統的一個程序必須有一個進程,但是不必須有一個線程
B. 進程有自己的棧空間,而線程只共享父進程的棧空間
C. 線程必從屬於一個進程
D. 線程可以更改從屬的進程

>我們先來看看程序和進程的區別:
程序是一個可執行的文件,進程是一個可執行文件執行后在內存中的實體
進程是操作系統分配資源的基本單位,如,分配內存,分配CPU周期等資源,而線程是執行流的基本單元。因此,每個進程必然有一個線程才可能運行得起來,否則就只有一堆資源,沒有執行過程了。
按照我剛才分析 顯然 A D就錯了  
>
D錯的原因是 線程執行需要資源 因此必然屬於一個進程而不能更改
>
B這個最容易搞錯,線程被創建后必須有自己的棧空間而不能共享。因為多個線程同時運行后,CPU的調度需要知道線程的執行流。共享的話無法多線程了。線程切換后怎么切換回來呢?如果每個線程沒有自己的存儲執行流的棧的話,是無法實現線程切換的。

---

###2、下面程序選什么?(D)

int main()
{
int* p;
int a = 10;
p = a;
printf("
p = %d\n", *p);
return 0;
}

A. 10
B. a的地址值
C. 編譯錯誤
D. 運行異常

>**分析:**
>1、p為野指針;
>2、int* p = NULL;也是錯誤的,p未指向任何地方,卻被賦值10,錯誤;
>3、改為p = &a;
>
>
>野指針的成因主要有三種:
>1. 指針變量沒有被初始化,任何指針變量剛被創建的時候不會自動成為NULL,它的缺省值是隨機的,它會亂指一氣。
>2. 指針被free或者delete之后,沒有置為NULL,讓人誤以為是合法的指針。
>3. 指針操作超越了變量的作用范圍。比如不要返回指向棧內存的指針或者引用,因為棧內存在函數調用結束時會被釋放。

---

###3、下面代碼輸出的結果為?

define a 10

void foo();
int main()
{
printf("%d.", a);
foo();
printf("%d", a);
}
void foo()
{
#undef a
#define a 50
}

>**答案:10.10**
>分析:#undef 是在后面取消以前定義的宏定義。define在預處理階段就把main中的a全部替換為10了.
>另外,不管是在某個函數內,還是在函數外,define都是從定義開始知道文件結尾,所以如果把foo函數放到main上面的話,則結果會是50 ,50

---


###1、下面程序在80x86架構下,輸出什么值?

union Test
{
char a[4];
short b;
}
Test t;
t.a[0] = 256;
t.a[1] = 255;
t.a[2] = 254;
t.a[3] = 253;
printf("%d\n", t.b);

>**答案: -256 **
>分析:
>char類型的取值范圍是-128~127,unsigned char的取值范圍是0~256
>這里a[0]=256,出現了正溢出,將其轉換到取值范圍內就是0,即a[0]=0;
>同理,a[1]=-1, a[2]=-2, a[3]=-3,在C語言標准里面,用補碼表示有符號數,故其在計算機中的表示形式如下:
>a[0]=0,     0000 0000
>a[1]=-1,    1111 1111(反碼+1 = -1)
>a[2]=-2,    1111 1110
>a[3]=-3,    1111 1101
>short是2字節(a[0]和a[1]),由於80X86是小端模式,即數據的低位保存在內存的低地址中,而數據的高位保存在內存的高地址中,在本例中,a[0]中存放的是b的低位,a[1]中存放的是b的高位,即b的二進制表示是:1111 1111 0000 0000,表示-256



---

###1、宏的使用

define NDEBUG

define PRINTF(s)

else

define PRINTF(s) printf("%s\n", #s); s


---

###2、下面程序輸出什么?

int main()
{
int a;
int p;
p = &a;
p = 0x500;
a = (int )(
(&p));
a = (int )(&(
p));
if(a == (int)p)
printf("equal !\n");
else
printf("not equal !\n");
return 0;
}


**答案:equal !**
> a = (int )(*(&p));和a = (int )(&(*p));打印的都是p的地址。

---

###3、什么是可重入函數?C語言中寫可重入函數,應注意的事項?
>答案:可重入函數是指能夠被多個線程“同時”調用的函數,並且能保證函數結果的正確性的函數。
>在編寫可重入函數時通常要注意如下的一些問題:
>1、盡量不要使用全局變量,靜態變量,如果使用了應該注意對變量訪問的互斥。通常可以根據具體的情況采用:信號量機制,關調度機制,關中斷機制等方式來保證函數的可重入性。
>2、不要調用不可重入的函數,調用了不可重入的函數會使該函數也變為不可重入的函數。
>3、注意對系統中的臨界資源,互斥資源的訪問方式,防止使函數成為不可重入的函數。
>4、一般驅動程序都是不可重入的函數,因此在編寫驅動程序時一定要注意重入的問題。



---


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM