1. 程式人生 > >C/C++面試知識點總結(一)

C/C++面試知識點總結(一)

目錄:

一、基礎知識

    1.C/C++

    2.STL

    6.資料庫

一、基礎知識

1.C/C++

(1).struct大小的確定

由於記憶體對齊的原則,在32位機器上,記憶體是4位元組對齊,也就是說,不夠4個位元組的按 4位元組來算。同理,在32位機器上,記憶體是8位元組對齊。
例子:

struct test
{
    int a;
    char b;
    int c;
}TEST;

其大小為4+4+4 = 12b

struct test
{
    int a;
    char b;
    char d;
    char
e; char f; int c; }TEST;

其大小為4+(1+1+1+1)+4 = 12b

struct test
{
    int a;
    char b;
    int c;
    char d;
}TEST;

其大小為4+4+4+4 = 16b

struct test
{
    char a;
}TEST;

其大小為1b

struct test
{
    char a;
    char b;
}TEST;

其大小為2b,說明如果前面的位元組+後面的位元組不超過記憶體對齊所需要的位元組,是不會用0填充的。實際多少個位元組就是多少個位元組。

(2).strlen、strcpy的實現

int _strlen(const char* str)
{
    if (!str || *str == 0) return 0;
    int len = 0;
    while (*str++)++len;
    return len;
}

char* _strcpy(char* d, const char* s)
{
    if (!s || *s == 0) return d ? &(*d = 0) : NULL; //防止d未初始化亂碼
    char* td = d;
    if (d) while (*d++ = *s++); //防止d為NULL
return td; }

(3).memcpy的實現以及如何防止記憶體重疊造成的錯誤。

我沒用過未定義的memcpy,我在VC、VS2013、VS2015測試的時候,memcpy已經對記憶體重疊進行檢測了。所以,這裡就寫一個未檢測記憶體重疊的和檢測記憶體重疊的memcpy。

void* Memcpy(void* des, const void* src, size_t count)
{
    if (!src || count <= 0) return des;
    char* td = (char*)des;
    const char* ts = (const char*) src;
    while (count--) *td++ = *ts++;
    return des;
}

例子如下:

    char* p = new char[6];
    memcpy(p, "12345", 10);
    printf("%s\n", p);
    Memcpy(p + 1, p, 4);
    printf("%s\n", p);

void* Memmove(void* des,const void* src,size_t count)
{
    if (!src || count <= 0) return des;
    char* td = (char*)des;
    const char* ts = (char*)src;
    //如果源地址 + count 小於目的地址,說明,記憶體無重疊,進行正向拷貝
    if (ts + count < td)
    {
        while (count--) *td++ = *ts++;
    }
    //否則有記憶體重疊,進行逆向拷貝
    else
    {
        char* ttd = td + count - 1;
        const char* tts = ts + count - 1;
        while (count--) *ttd-- = *tts--;
    }
    return des;
}

例子如下:

    char* p = new char[6];
    memcpy(p, "12345", 10);
    printf("%s\n", p);
    Memmove(p + 1, p, 4);
    printf("%s\n", p);

附上一張記憶體重疊示意圖:

(4).全域性變數、區域性變數、靜態變數的作用域以及存放的記憶體區域。

(5).static關鍵字的作用

a.在函式體內部定義變數,該變數從程式開始到結束只會分配一次記憶體,當再次進入該函式的時候,其值不變,仍為上次退出時的值
例子:

int f()
{
    static int i = 0;
    return ++i;
}

int main()
{
    cout << f() << f() << f() << endl;
    return 0;
}

結果是321,為什麼是321這也是一個點,下面會有解釋。
b.模組內的static定義的變數和函式不能被外部引用,唯一的辦法是通過一個間接變數或者函式來引用才行。
例子:

A.cpp
static char C = 'A';
char c = C;
static int F()
{
    return 5;
}
int ff()
{
    return F();
}

B.cpp
int main()
{
    extern int ff();
    extern char c;
    cout << ff() << endl;
    cout << c << endl;
    return 0;
}

c.類中定義的static變數屬於整個類,即類成員變數,與物件無關,只會在執行的時候建立一次。

d.類中定義的static函式屬於整個類,及類成員函式,與物件無關,所以不能在靜態函式裡面使用this指標,這個是物件獨有的。

例子:
class TEST
{
public:
    static int m_i;
    static int f() { return m_i; } //靜態成員函式只能引用靜態成員變數
};
int TEST::m_i = 6;
int main()
{
    cout << TEST::f() << endl;
    return 0;
}

(6).const關鍵字的作用
a.定義普通變數的時候,只能初始化一次,以後不可再修改其值。
b.定義指標變數時,再型別前,則值不能改,再型別後,則其地址不能改,若兩個都有,則兩者都不能改。
例子:

int a = 2, b = 3;
const int* p1 = &a;
*p1 = b; //值不可以修改
p1 = &b; //地址可以修改
int* const p2 = &a;
*p2 = b; //值可以修改
p2 = &b; //地址不可以修改

c.在函式形參宣告中,使用const,可以防止傳進來的引數被修改。
d.在類中使用const定義的函式,在函式內部不能修改成員變數的值,但是可以修改傳進來的形參值,但是一般不這麼用。

(7).sizeof是運算子而不是函式,計算變數或者結構體大小的時候可以不加括號,但是計算型別一定要加,所以計算什麼都加就對了。

(8).陣列地址問題
例子:

int a[] = { 1,2,3,4,5 };
cout << "a[0]: " << &a[0] << endl;
cout << "a[4]: " << endl;
cout << "(a[0] + 1): " <<&a[0] + 1 << endl;
cout << "(a + 1): " << a + 1 << endl;
cout << "&a + 1: " << &a + 1 << endl;

可以看出來,a和&a的值一樣,概念不一樣,a表示陣列的首地址,而&a表示的是整個物件的首地址,所以當它+1的時候,就會跳出陣列的邊界。

(9).如何將浮點數分解為整數和小數部分

a.強轉為int即為整數部分,然後再減去整數部分即可。

double Modf(double x, int* y)
{
    *y = (int)x;
    return x - *y;
}

b.直接使用庫函式,double modf(double x,double* y);

(10).其它一樣,只改變一樣,不能使函式過載的是

返回型別。

(11).C的結構體和C++的結構體的區別

a.C結構體內部不允許有函式存在,C++允許
b.內部成員變數許可權不同,C的只能是public,C++的可以有三種。
c.C結構體不可以繼承,C++可以繼承結構體或者類

(12).淺拷貝和深拷貝的原理

其實這兩個概念很簡單,淺拷貝就是兩個物件共享一塊記憶體,其缺點就是當析構一個物件的時候,另一個物件也不存在了,如果再使用它就會發生錯誤。深拷貝就是完完全全的複製出一個物件,兩者在記憶體上無任何關係。

(13).不能過載的5個運算子

.(成員訪問運算子)
->(成員指標運算子)
::(作用域解析運算子)
?(條件運算子)
sizeof運算子

(14)常見的不能宣告為虛擬函式的有哪些?

普通函式(非成員函式)
靜態成員函式
內聯成員函式
建構函式
友元函式。

(15)C++的靜態多型和動態多型

所謂靜態多型就是執行前確定型別或者函式呼叫(也就是編譯後確定),其採用的是函式過載和泛型(例如,模板。
所謂動態多型就是執行後確定型別或者函式呼叫,其採用虛擬函式實現。

(16)C++虛擬函式的原理

虛擬函式由虛表管理,這張表存放著所有虛擬函式的地址,而類的例項物件維護著一個虛表指標,指向這個表。執行的時候,根據new的物件裡面的虛表指標,確定呼叫的函式。如下例子所示:

class A
{
public:
    virtual void f1() { cout << "A...f1()" << endl; }
};

class B :public A
{
public:
    void f1() { cout << "B...f1()" << endl; }
};

int main()
{

    A* a = new A;   //因為不是抽象類,所以可以例項化
    B* b = new B;   //直接用子類自己例項化
    A* pB = new B;  //用基類指標指向子類

    return 0;
}

這就是為什麼能在執行時確定呼叫哪個函式的原因了,當new B返回B的物件後,裡面會有一張B類的虛表,然後根據B的虛表呼叫B的函式。

(17)C++虛擬函式佔用類的大小

因為只需要維護一個指向虛表的指標,所以大小為4或8個位元組
靜態成員和函式不計入sizeof中。

(18)new與malloc的區別

(19)C++中有哪幾種資料儲存區?

棧、堆、自由儲存區、全域性/靜態儲存區、常量儲存區

(20)什麼是棧溢位?哪些情況下比較容易出現棧溢位?

棧溢位泛指系統維護的棧溢位,因資料壓不下去了,導致溢位,此時程式會崩潰。

一般遞迴深度過大、建立普通陣列過大(就是區域性變數佔用的空間大於棧了就會溢位,new的是堆,不算)。

(21)“#include”後面跟引號與尖括號的區別?

引號編譯器會搜尋當前工作目錄下的標頭檔案,尖括號會搜尋安裝目錄下的標頭檔案。

(22)gcc和g++的區別

(23)類成員函式的過載、覆蓋和重寫區別

過載和普通的函式過載一樣。
覆蓋則基類的函式要加virtual (這就是多型的實現)
如果派生類的函式與基類的函式同名,但是引數不同。此時,不論有無virtual關鍵字,基類的函式將被隱藏,子類就重寫了這個函式
如果派生類的函式與基類的函式同名,並且引數也相同,但是基類函式沒有virtual關鍵字。此時,基類的函式被隱藏,子類就重寫了這個函式

(24)建構函式為什麼不能是虛擬函式

虛擬函式存在於虛表中,而虛表要靠虛表指標維護,而只有例項化後的物件才有虛表指標,而例項化物件就必須呼叫建構函式初始化物件,所以衝突了,結果就是建構函式不能是虛擬函式。

(25)printf(“%d,%d\n”,i++,i++),若i=0,則結果輸出什麼。

這裡有一個點就是,printf和cout輸出的時候都是從右至左讀取值,所以結果應該是1,0。