[找工做]程序員面試寶典【筆記】(part 1)

v  題型:前端

數據結構(鏈表,數,圖)、C++編程基礎、數組、遞歸、矩陣、內存管理、時間複雜度、TCP/IP、操做系統、計算機網絡、UML、OOA&OOP、本身的項目程序員

v  swap的幾種寫法:算法

void swap(int* a, int* b)編程

{  int temp;數組

  temp = *a;安全

  *a = *b;網絡

  *b = temp; }數據結構

{  *a = *a + *b;app

   *b = *a - *b;異步

   *a = *a - *b; }

{  *a = *a ^ *b;

   *b = *b ^ *a;

   *a = *a ^ *b; }

v  如何在C++中調用C程序:

C++和C是兩種徹底不一樣的編譯連接處理方式,若是直接在C++裏面調用C函數,會找不到函數體,報連接錯誤。要解決這個問題,就要在 C++文件裏面顯示聲明一下哪些函數是C寫的,要用C的方式來處理。
1.引用頭文件前須要加上 extern 「C」,若是引用多個,那麼就以下所示
extern 「C」
{
#include 「 s.h」
#include 「t.h」
#include 「g.h」
#include 「j.h」
};
而後在調用這些函數以前,須要將函數也所有聲明一遍。
2.C++調用C函數的方法,將用到的函數所有從新聲明一遍
extern 「C」
{
extern void A_app(int);
extern void B_app(int);
extern void C_app(int);
extern void D_app(int);

}

C++程序中調用被c編譯器編譯後的函數,爲何要加extern "C"?

C++語言支持函數重載,C語言不支持函數重載。函數被C++編譯後在庫中的名字與C語言的不一樣。假設某個C 函數的聲明以下:
void foo(int x, int y);
該函數被C 編譯器編譯後在庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字用來支持函數重載和類型安全鏈接。因爲編譯後的名字不一樣,C++程序不能直接調用C 函數。C++提供了一個C 鏈接交換指定符號extern「C」來解決這個問題。例如:
extern 「C」
{
void foo(int x, int y);
// 其它函數
}
或者寫成
extern 「C」
{
#include 「myheader.h」
// 其它C 頭文件
}
這就告訴C++編譯譯器,函數 foo 是個C 鏈接,應該到庫中找名字_foo 而不是找_foo_int_int。C++編譯器開發商已經對C 標準庫的頭文件做了extern「C」處理,因此咱們能夠用#include直接引用這些頭文件。

v  操做符優先級

優先級

運算符

名稱或含義

使用形式

結合方向

說明

1

[]

數組下標

數組名[常量表達式]

左到右

 

()

圓括號

(表達式)/函數名(形參表)

 

.

成員選擇(對象)

對象.成員名

 

->

成員選擇(指針)

對象指針->成員名

 

2

-

負號運算符

-表達式

右到左

單目

(類型)

強制類型轉換

(數據類型)表達式

 

++

自增運算符

++變量名/變量名++

單目

--

自減運算符

--變量名/變量名--

單目

*

取值運算符

*指針變量

單目

&

取地址運算符

&變量名

單目

!

邏輯非運算符

!表達式

單目

~

按位取反運算符

~表達式

單目

sizeof

長度運算符

sizeof(表達式)

 

3

/

表達式/表達式

左到右

雙目

*

表達式*表達式

雙目

%

餘數(取模)

整型表達式/整型表達式

雙目

4

+

表達式+表達式

左到右

雙目

-

表達式-表達式

雙目

5

<< 

左移

變量<<表達式

左到右

雙目

>> 

右移

變量>>表達式

雙目

6

大於

表達式>表達式

左到右

雙目

>=

大於等於

表達式>=表達式

雙目

小於

表達式<表達式

雙目

<=

小於等於

表達式<=表達式

雙目

7

==

等於

表達式==表達式

左到右

雙目

!=

不等於

表達式!= 表達式

雙目

8

&

按位與

表達式&表達式

左到右

雙目

9

^

按位異或

表達式^表達式

左到右

雙目

10

|

按位或

表達式|表達式

左到右

雙目

11

&&

邏輯與

表達式&&表達式

左到右

雙目

12

||

邏輯或

表達式||表達式

左到右

雙目

13

?:

條件運算符

表達式1? 表達式2: 表達式3

右到左

三目

14

=

賦值運算符

變量=表達式

右到左

 

/=

除後賦值

變量/=表達式

 

*=

乘後賦值

變量*=表達式

 

%=

取模後賦值

變量%=表達式

 

+=

加後賦值

變量+=表達式

 

-=

減後賦值

變量-=表達式

 

<<=

左移後賦值

變量<<=表達式

 

>>=

右移後賦值

變量>>=表達式

 

&=

按位與後賦值

變量&=表達式

 

^=

按位異或後賦值

變量^=表達式

 

|=

按位或後賦值

變量|=表達式

 

15

,

逗號運算符

表達式,表達式,…

左到右

從左向右順序運算

v  ::在C++中是什麼意思 

::是運算符中等級最高的,它分爲三種:
1)global scope(全局做用域符),用法(::name)
2)class scope(類做用域符),用法(class::name)
3)namespace scope(命名空間做用域符),用法(namespace::name)
他們都是左關聯(left-associativity)
他們的做用都是爲了更明確的調用你想要的變量,如在程序中的某一處你想調用全局變量a,那麼就寫成::a,若是想調用class A中的成員變量a,那麼就寫成A::a,另一個若是想調用namespace std中的cout成員,你就寫成std::cout(至關於using namespace std;cout)意思是在這裏我想用cout對象是命名空間std中的cout(即就是標準庫裏邊的cout)

v  i++

!x++;     !與++優先級相同,先計算!x;而後計算x++; (原題:P35)

v  指針+1的問題

指針變量加1,即向後移動1 個位置表示指針變量指向下一個數據元素的首地址。而不是在原地址基礎上加1。至於真實的地址加了多少,要看原來指針指向的數據類型是什麼。

p指向的是一個字符,p+1就是移動一個字符大小,一個字符就是一個字節,因此p +1 表明的地址就比 p 表明的地址大1。

p指向的是一個整型,p+1就是移動一個整型大小,即移動4個字節,因此p+1表明的地址比p表明的地址大4.

v  x++與++x

#include <stdio.h>

void main()

{

    int arr[] = { 6, 7, 8, 9, 10 };

    int *ptr = arr;

    *(ptr++) += 123;//equal:*ptr=*ptr+123;ptr++;

    printf("%d,%d\n",*ptr,*(++ptr));//prinft計算參數時從右向左入棧,先執行++ptr;

}

RESULT:8,8

X++先執行運算最後++;++X則先++而後執行其餘運算;

v  隱式類型轉換髮生在下列這些典型的狀況下:

  1. 在混合類型的算術表達式中(最寬的數據類型成爲目標類型)。
  2. 用一種類型的表達式賦值給另外一種類型的對象。
  3. 把一個表達式傳遞給一個函數,調用表達式的類型與形式參數的類型不一樣。
  4. 從一個函數返回一個表達式的類型與返回類型不相同。

v  判斷一個數X是不是2N次方,不可用循環語句:

!(X&(X-1))==0

v  Const

在C程序中,const的用法主要是定義常量、修飾函數參數、修飾函數返回值,在C++中,它還能夠修飾函數的定義體,定義類中某個成員函數爲恆態函數,即不改變類中的數據成員

被Const修飾的東西能夠受到強制保護,預防意外的變更,能提升程序的健壯性

Const與#define相比有什麼不一樣?

Const常量有數據類型,而宏常量沒有數據類型,編譯器能夠對前者進行類型安全檢查,而對後者只能進行字符替換,沒有類型安全檢查,而且在字符替換中可能產生意料不到的錯誤

調試工具能對const常量進行調試,可是沒法對define常量調試

No.

做用

說明

參考代碼

1

能夠定義const常量

 

const int Max = 100; 

2

便於進行類型檢查

const常量有數據類型,而宏常量沒有數據類型。編譯器能夠對前者進行類型安全檢查,而對後者只進行字符替換,沒有類型安全檢查,而且在字符替換時可能會產生意料不到的錯誤

void f(const int i) { .........}
      //對傳入的參數進行類型檢查,不匹配進行提示

3

能夠保護被修飾的東西

防止意外的修改,加強程序的健壯性。

void f(const int i) { i=10;//error! }
      //若是在函數體內修改了i,編譯器就會報錯

4

能夠很方便地進行參數的調整和修改

同宏定義同樣,能夠作到不變則已,一變都變

 

5

爲函數重載提供了一個參考

 

class A
{
           ......
  void f(int i)       {......} //一個函數
  void f(int i) const {......} //上一個函數的重載
           ......
};

6

能夠節省空間,避免沒必要要的內存分配

const定義常量從彙編的角度來看,只是給出了對應的內存地址,而不是象#define同樣給出的是當即數,因此,const定義的常量在程序運行過程當中只有一份拷貝,而#define定義的常量在內存中有若干個拷貝

#define PI 3.14159         //常量宏
const doulbe  Pi=3.14159;  //此時並未將Pi放入ROM中
              ......
double i=Pi;   //此時爲Pi分配內存,之後再也不分配!
double I=PI;  //編譯期間進行宏替換,分配內存
double j=Pi;  //沒有內存分配
double J=PI;  //再進行宏替換,又一次分配內存!

7

 提升了效率

編譯器一般不爲普通const常量分配存儲空間,而是將它們保存在符號表中,這使得它成爲一個編譯期間的常量,沒有了存儲與讀內存的操做,使得它的效率也很高

 

const int *A; 或 int const *A;  //const修飾指向的對象,A可變,A指向的對象不可變
int *const A;               //const修飾指針A, A不可變,A指向的對象可變
const int *const A;           //指針A和A指向的對象都不可變

v  sizeof與Strlen()

sizeof    單位:字節

sizeof(...)是運算符,而不是一個函數。一個簡單的例子:
int a;
cout<<sizeof a<<endl;

在頭文件中typedef爲unsigned int,其值在編譯時即計算好了,參數能夠是數組、指針、類型、對象、函數等。

它的功能是:得到保證能容納實現所創建的最大對象的字節大小

因爲在編譯時計算,所以sizeof不能用來返回動態分配的內存空間的大小。
實際上,用sizeof來返回類型以及靜態分配的對象、結構或數組所佔的空間,返回值跟對象、結構、數組所存儲的內容沒有關係。
具體而言,當參數分別以下時,sizeof返回的值表示的含義以下:數組——編譯時分配的數組空間大小;
指針——存儲該指針所用的空間大小(存儲該指針的地址的長度,是長整型,應該爲4);類型——該類型所佔的空間大小;

對象——對象的實際佔用空間大小;
函數——函數的返回類型所佔的空間大小。函數的返回類型不能是void。
********************************************************************

strlen
strlen(...)是函數,要在運行時才能計算。

參數必須是字符型指針(char*, 且必須是以'\0'結尾的。當數組名做爲參數傳入時,實際上數組就退化成指針了。

int ac[10];

cout<<sizeof(ac)<<endl;
cout<<strlen(ac)<<endl;     (ac至關於一個指針,可是strlen只能接受char*類型,因此編譯時出錯)

它的功能是:返回字符串的長度。該字符串多是本身定義的,也多是內存中隨機的,該函數實際完成的功能是從表明該字符串的第一個地址開始遍歷,直到遇到結束符'\0'。返回的長度大小不包括'\0'。

 

v  結構體長度對齊

在默認狀況下,爲了方便對結構體內元素的訪問和管理,當結構體內的元素長度都小於處理器的位數的時候,便以結構體裏面最長的數據元素爲對其單位,也就是說,結構體的長度必定是最長的數據元素的整數倍;若是結構體內存在長度大於處理器位數的元素,那麼就以處理器的位數爲對齊單位。可是結構體內類型相同的連續元素將在連續的空間內,和數組同樣。

CPU的優化規則大體是這樣:對於n字節的元素(n=2,4,8,…),他的首地址能被n整除,才能得到最好的性能。(我的感受是若是不是這樣,那麼一個元素頗有可能會跨頁存儲,存在兩個不一樣的頁面上,致使訪問速度慢)

v  對其是一種以空間換時間的方法,在訪問內存時,若是地址按4字節對齊,則訪問效率會高不少,這種現象的緣由在於訪問內存的硬件電路。通常狀況下,地址總線老是按照對齊後的地址來訪問的。例如你想獲得0x00000001開始的4字節內容,系統需先以0x00000000讀4個字節,從中取3字節,而後再用0X00000004最爲開始地址,得到下一個四字節,再從中獲得第一個字節,兩次組合出你想要的內容。可是若是地址一開始就是對齊到0x00000000,則系統只需一次內存讀寫便可。

v  對其的自定義設置

#pragma pack(1)

struct aStruct…{…};

#pragma pack()

v  字節對齊是在編譯時決定的,不會再發生變化,在運行時不會再發生變化

v  靜態變量存放在全局數據區,而sizeof計算棧中分配的大小,是不會計算static變量的。

v  數據類型的長度:

short  -------------- 2

int,long,float,指針---4

double -------------- 8

v  sizeof vs strlen

sizeof是算符,strlen是函數

sizeof能夠用類型作參數,strlen只能用char*作參數,且必須是以「\0」結尾的。

數組作sizeof的參數不退化,傳遞給strlen就退化爲了指針

sizeof在編譯時計算,strlen在運算時計算

對函數使用sizeof,在編譯階段會被函數返回值類型取代。

v  sizeof不是函數,也不是一元運算符,他是個相似宏定義的特殊關鍵字,sizeof()括號內的內容是不被編譯的,只是替換,因此a=8;sizeof(a=6);以後a的值仍然爲8。

v  unsigned影響的僅僅是最高位bit的意義(正/負),而不會影響數據長度。

v  空類所佔空間爲1,單一繼承的空類空間也爲1,多重繼承的空類空間仍是1,可是虛繼承涉及到虛表(虛指針),因此空間大小爲4

 

v  內聯函數和宏定義

內聯函數和普通函數相比能夠加快程序運行的速度,由於不須要中斷調用,在編譯的時候內聯函數能夠直接被鑲嵌到目標代碼中,而宏只是一個簡單的替換;內聯函數要作參數類型檢查,這是inline和宏相比的優點;

inline通常只用於以下狀況:

  1. 一個函數不斷被重複調用;
  2. 函數只有簡單的幾行,且函數內不包括for/while/switch語句;

—————————————————————————————————————————————————————————————————————————————————————————

v  指針問題:包括常量指針、數組指針、函數指針、this指針、指針傳值、指向指針的指針等問題

v  指針和引用的區別

非空區別:在任何狀況下都不能使用指向空值的引用;

合法性區別:在使用引用以前不須要測試它的合法性;

可修改區別:指針能夠被從新賦值以指向另外一個不一樣的對象,可是引用則老是指向初始化時被指定的對象,之後不能改變,可是指定的內容能夠改變;

應用區別:使用指針:存在不指向任何對象的可能;須要 在不一樣的時刻指向不一樣的對象;使用引用:老是指向一個對象而且一旦指向一個對象後就不會改變指向。

v  引用的初始化:

聲明一個引用時,必須同時初始化,如同const常量必須聲明的同時初始化。

v  char c[] vs char *c

char c[]分配的是局部數組,而char *c分配的是全局數組

 

v  函數指針

函數指針

void (*f)()

函數返回指針

void* f()

const指針

cons int *

指向const的指針

int * const

指向const的const指針

cons int* const

v  malloc和free是C/C++的標準庫函數,new/delete是C/C++的運算符。他們均可以用於動態申請內存和釋放內存。對於非內部數據類型的對象而言,光用malloc/free沒法知足動態對象的要求。對象在建立的同時要自動執行構造函數,對象在消亡以前要自動執行析構函數。因爲malloc/free是庫函數而不是運算符,不在編譯器控制權限範圍內,不能把執行構造函數和析構函數的任務強加於malloc/free

v  數組指針和指針數組:

數組指針

指針數組

int (*a)[]

int *a[]

 

—————————————————————————————————————————————————————————————————————————————————————————

v  STL模版與容器

_________________

v  STL的優勢:

  • 方便容易的實現搜索數據或對數據排序等一系列的算法
  • 調試程序是更安全和方便
  • 即便是人們用STL在UNIX平臺下寫的代碼,你也能夠很容易理解

v  STL中的一些基本概念

  • 模版(Template)類的宏(macro),正規名:泛型,類的模版-泛型類,函數模版-泛型函數
  • STL標準模版庫,一些聰明人本身寫的一些模版
  • 容器(Container):可容納一些數據的模版類,STL中有vector, set, map, multimap 和 deque等容器
  • 向量(Vector):基本數組模版,是一個容器
  • 遊標(Iterator):它是一個指針,用來指向STL容器中的元素,也能夠指向其餘元素

v  容器向量:標準模板庫是一個基於模版的容器類庫,包括鏈表、列表、隊列和堆棧;標準模板庫還包括許多經常使用的算法,包括排序和查找;可重用;容器是包括其餘對象的對象;標準模版庫類有兩種類型:順序和關聯;不一樣OS間可移植;

v  vector的操做:

標準庫vector類型使用須要的頭文件:#include <vector>。vector 是一個類模板。不是一種數據類型,vector<int>是一種數據類型。Vector的存儲空間是連續的,list不是連續存儲的。

  • 定義和初始化
    vector< typeName > v1;       //默認v1爲空,故下面的賦值是錯誤的v1[0]=5;
    vector<typeName>v2(v1);  或v2=v1 ; 或vector<typeName> v2(v1.begin(), v1.end());//v2是v1的一個副本,若v1.size()>v2.size()則賦值後v2.size()被擴充爲 v1.size()。
    vector< typeName > v3(n,i);//v3包含n個值爲i的typeName類型元素
    vector< typeName > v4(n); //v4含有n個值爲0的元素
    int a[4]={0,1,2,3,3}; vector<int> v5(a,a+5);//v5的size爲5,v5被初始化爲a的5個值。後一個指針要指向將被拷貝的末元素的下一位置。
    vector<int> v6(v5);//v6是v5的拷貝。
    vector< 類型 > 標識符(最大容量,初始全部值)。

 

  • 值初始化
    1>     若是沒有指定元素初始化式,標準庫自行提供一個初始化值進行值初始化。
    2>     若是保存的是含有構造函數的類類型的元素,標準庫使用該類型的構造函數初始化。
    3>     若是保存的是沒有構造函數的類類型的元素,標準庫產生一個帶初始值的對象,使用這個對象進行值初始化。
  • vector對象最重要的幾種操做
    1. v.push_back(t)    在容器的最後添加一個值爲t的數據,容器的size變大。另外list有push_front()函數,在前端插入,後面的元素下標依次增大。
    2. v.size()    返回容器中數據的個數,size返回相應vector類定義的size_type的值。v.resize(2*v.size)或v.resize(2*v.size, 99) 將v的容量翻倍(並把新元素的值初始化爲99)
    3. v.empty()     判斷vector是否爲空
    4. v[n]           返回v中位置爲n的元素
    5. v.insert(pointer,number, content)    向v中pointer指向的位置插入number個content的內容。
     還有v. insert(pointer, content),v.insert(pointer,a[2],a[4])將a[2]到a[4]三個元素插入。
    6. v.pop_back()    刪除容器的末元素,並不返回該元素。
    7.v.erase(pointer1,pointer2) 刪除pointer1到pointer2中間(包括pointer1所指)的元素。vector中刪除一個元素後,此位置之後的元素都須要往前移動一個位置,雖然當前迭代器位置沒有自動加1,可是因爲後續元素的順次前移,也就至關於迭代器的自動指向下一個位置同樣。
    8. v1==v2          判斷v1與v2是否相等。
    9. !=、<、<=、>、>=      保持這些操做符慣有含義。
    10. vector<typeName>::iterator p=v1.begin( ); p初始值指向v1的第一個元素。*p取所指向元素的值。對於const vector<typeName>只能用vector<typeName>::const_iterator類型的指針訪問。
    11.   p=v1.end( ); p指向v1的最後一個元素的下一位置。
    12.v.clear()    刪除容器中的全部元素。

 

  • #include<algorithm>中的泛函算法
    搜索算法:find() 、search() 、count() 、find_if() 、search_if() 、count_if()
    分類排序:sort() 、merge()
    刪除算法:unique() 、remove()
    生成和變異:generate() 、fill() 、transformation() 、copy()
    關係算法:equal() 、min() 、max()
    sort(v1.begin(),vi.begin()+v1.size/2); 對v1的前半段元素排序
    list<char>::iterator pMiddle =find(cList.begin(),cList.end(),'A');找到則返回被查內容第一次出現處指針,不然返回end()。
    vector< typeName >::size_type x ; vector< typeName >類型的計數,可用於循環如同for(int i)

初學C++的程序員可能會認爲vector的下標操做能夠添加元素,其實否則:

vector<int> ivec;   // empty vector

for (vector<int>::size_type ix = 0; ix != 10; ++ix)

     ivec[ix] = ix; // disaster: ivec has no elements

上述程序試圖在ivec中插入10個新元素,元素值依次爲0到9的整數。可是,這裏ivec是空的vector對象,並且下標只能用於獲取已存在的元素。

這個循環的正確寫法應該是:

for (vector<int>::size_type ix = 0; ix != 10; ++ix)

     ivec.push_back(ix); // ok: adds new element with value ix
警告:必須是已存在的元素才能用下標操做符進行索引。經過下標操做進行賦值時,不會添加任何元素,僅能對確知已存在的元素進行下標操做  。

v  泛型編程

何謂泛型編程:STL表明用一致的方式編程是可能的;泛型編程是一種基於發現高效算法的最抽象表示的編程方法;STL是一個泛型編程的例子,C++是咱們能夠實現使人信服的例子的語言。

v  模版

一個函數在編譯時被分配給一個入口地址,這個入口地址就稱爲函數的指針,正如指針是一個變量的地址同樣。(經測試:函數名和對函數名取地址獲得的是同一個值,均可以做爲該函數的指針)。有些地方必須使用函數指針才能完成給定的任務,特別是異步操做的回調和其餘須要匿名回調的結構。另外,想線程的執行和時間的處理,若是缺乏了函數的支持也是很難完成的。

—————————————————————————————————————————————————————————————————————————————————————————————

v  面向對象(Object-Oriented)

編程是在計算機中反應世界

面向對象的優勢:良好的可複用性、易維護、良好的可擴充性

面向對象技術的基本概念是:對象、類和繼承

C++中的空類默認產生的成員函數:

默認構造函數、析構函數、拷貝構造函數、賦值函數

C++中struct也能夠有constructor/destructor及成員函數,它和class的區別是:class的默認訪問控制是private,struct中默認訪問控制是public。

在一個類中,初始化列表的初始化變量順序是根據成員變量的聲明順序來執行的。

MFC庫中,爲何CObject的析構函數是虛函數?

保證在任何狀況下,不會出現因爲析構函數未被調用而致使內存泄漏

析構函數能夠是內聯函數,但內聯函數不能爲virtual

 

多態:容許將子類類型的指針賦值給父類類型的指針,多態在Object Pascal和C++中都是經過虛函數(Virtual Function)實現的。

 

  • overload vs override:
    • overload: 範圍相同,參數不一樣,靜態,函數調用在編譯期間肯定,早綁定,與面向對象無關,與多態無關。
    • override:範圍不一樣,參數相同,子類從新定義父類的虛函數,函數動態調用、運行期綁定。

 

 

實現代碼重用

封裝:隱藏實現細節,使得代碼模塊化

繼承:擴展已存在的代碼模塊(類)

多態:同一操做做用於不一樣對象上,產生不一樣的解釋和執行結果—實現接口重用

 

友元函數:定義在類外部的普通函數,但他須要在類體內說明,爲了與該類的成員函數區別,說明時須要在前面加上friend來區別;他雖然不是成員函數,但卻能夠訪問類的私有變量。

友元還能夠是類,稱爲友元類

 

繼承:可使一個新類得到其父類的操做和數據結構,程序員只需在新類中增長原有類中沒有的成分。

 

爲了避免破壞類的封裝性,全部操做應該經過接口進行溝通。

 

類中的static變量存在數據段,不在類的實例空間中。

相關文章
相關標籤/搜索