五萬字長文 C C++ 面試知識總結(上)

C/C++ 面試知識總結

這是一篇五萬字的C/C++面試知識點總結,包括答案:這是上篇,下篇今天也推送了,須要的同窗記得去看看。本文花費了博主大量的時間進行收集、排版:若是你以爲文章對你有幫助幫忙點贊給博主一點鼓勵~~html

目錄java

  • C/C++python

  • STLios

  • 數據結構c++

  • 算法程序員

  • Problems面試

  • 操做系統算法

  • 計算機網絡數據庫

  • 網絡編程express

  • 數據庫

  • 設計模式

  • 連接裝載庫

  • 海量數據處理

  • 音視頻

  • 其餘

  • 書籍

  • 複習刷題網站

  • 招聘時間崗位

  • 面試題目經驗

C/C++

const

做用

  1. 修飾變量,說明該變量不能夠被改變;

  2. 修飾指針,分爲指向常量的指針和指針常量;

  3. 常量引用,常常用於形參類型,即避免了拷貝,又避免了函數對值的修改;

  4. 修飾成員函數,說明該成員函數內不能修改爲員變量。

使用

// 類
class A {
private:
    const int a;                // 常對象成員,只能在初始化列表賦值

public:
    // 構造函數
    A() { };
    A(int x) : a(x) { };        // 初始化列表

    // const可用於對重載函數的區分
    int getValue();             // 普通成員函數
    int getValue() const;       // 常成員函數,不得修改類中的任何數據成員的值
};

void function() {
    // 對象
    A b;                        // 普通對象,能夠調用所有成員函數
    const A a;                  // 常對象,只能調用常成員函數、更新常成員變量
    const A *p = &a;            // 常指針
    const A &q = a;             // 常引用

    // 指針
    char greeting[] = "Hello";
    char* p1 = greeting;                // 指針變量,指向字符數組變量
    const char* p2 = greeting;          // 指針變量,指向字符數組常量
    char* const p3 = greeting;          // 常指針,指向字符數組變量
    const char* const p4 = greeting;    // 常指針,指向字符數組常量
}

// 函數
void function1(const int Var);           // 傳遞過來的參數在函數內不可變
void function2(const char* Var);         // 參數指針所指內容爲常量
void function3(char* const Var);         // 參數指針爲常指針
void function4(const int& Var);          // 引用參數在函數內爲常量

// 函數返回值
const int function5();      // 返回一個常數
const int* function6();     // 返回一個指向常量的指針變量,使用:const int *p = function6();
int* const function7();     // 返回一個指向變量的常指針,使用:int* const p = function7();
複製代碼

static

做用

  1. 修飾普通變量,修改變量的存儲區域和生命週期,使變量存儲在靜態區,在 main 函數運行前就分配了空間,若是有初始值就用初始值初始化它,若是沒有初始值系統用默認值初始化它。

  2. 修飾普通函數,代表函數的做用範圍,僅在定義該函數的文件內才能使用。在多人開發項目時,爲了防止與他人命令函數重名,能夠將函數定位爲 static。

  3. 修飾成員變量,修飾成員變量使全部的對象只保存一個該變量,並且不須要生成對象就能夠訪問該成員。

  4. 修飾成員函數,修飾成員函數使得不須要生成對象就能夠訪問該函數,可是在 static 函數內不能訪問非靜態成員。

this 指針

  1. this 指針是一個隱含於每個非靜態成員函數中的特殊指針。它指向正在被該成員函數操做的那個對象。

  2. 當對一個對象調用成員函數時,編譯程序先將對象的地址賦給 this 指針,而後調用成員函數,每次成員函數存取數據成員時,由隱含使用 this 指針。

  3. 當一個成員函數被調用時,自動向它傳遞一個隱含的參數,該參數是一個指向這個成員函數所在的對象的指針。

  4. this 指針被隱含地聲明爲: ClassName *const this,這意味着不能給 this 指針賦值;在 ClassName 類的 const 成員函數中,this 指針的類型爲:const ClassName* const,這說明不能對 this 指針所指向的這種對象是不可修改的(即不能對這種對象的數據成員進行賦值操做);

  5. this 並非一個常規變量,而是個右值,因此不能取得 this 的地址(不能 &this)。

  6. 在如下場景中,常常須要顯式引用 this 指針:

  7. 爲實現對象的鏈式引用;

  8. 爲避免對同一對象進行賦值操做;

  9. 在實現一些數據結構時,如 list

inline 內聯函數

特徵

  • 至關於把內聯函數裏面的內容寫在調用內聯函數處;

  • 至關於不用執行進入函數的步驟,直接執行函數體;

  • 至關於宏,卻比宏多了類型檢查,真正具備函數特性;

  • 不能包含循環、遞歸、switch 等複雜操做;

  • 在類聲明中定義的函數,除了虛函數的其餘函數都會自動隱式地當成內聯函數。

使用

// 聲明1(加 inline,建議使用)
inline int functionName(int first, int secend,...);

// 聲明2(不加 inline)
int functionName(int first, int secend,...);

// 定義
inline int functionName(int first, int secend,...) {/****/};

// 類內定義,隱式內聯
class A {
    int doA() { return 0; }         // 隱式內聯
}

// 類外定義,須要顯式內聯
class A {
    int doA();
}
inline int A::doA() { return 0; }   // 須要顯式內聯
複製代碼

編譯器對 inline 函數的處理步驟

  1. 將 inline 函數體複製到 inline 函數調用點處;

  2. 爲所用 inline 函數中的局部變量分配內存空間;

  3. 將 inline 函數的的輸入參數和返回值映射到調用方法的局部變量空間中;

  4. 若是 inline 函數有多個返回點,將其轉變爲 inline 函數代碼塊末尾的分支(使用 GOTO)。

優缺點

優勢

  1. 內聯函數同宏函數同樣將在被調用處進行代碼展開,省去了參數壓棧、棧幀開闢與回收,結果返回等,從而提升程序運行速度。

  2. 內聯函數相比宏函數來講,在代碼展開時,會作安全檢查或自動類型轉換(同普通函數),而宏定義則不會。

  3. 在類中聲明同時定義的成員函數,自動轉化爲內聯函數,所以內聯函數能夠訪問類的成員變量,宏定義則不能。

  4. 內聯函數在運行時可調試,而宏定義不能夠。

缺點

  1. 代碼膨脹。內聯是以代碼膨脹(複製)爲代價,消除函數調用帶來的開銷。若是執行函數體內代碼的時間,相比於函數調用的開銷較大,那麼效率的收穫會不多。另外一方面,每一處內聯函數的調用都要複製代碼,將使程序的總代碼量增大,消耗更多的內存空間。

  2. inline 函數沒法隨着函數庫升級而升級。inline函數的改變須要從新編譯,不像 non-inline 能夠直接連接。

  3. 是否內聯,程序員不可控。內聯函數只是對編譯器的建議,是否對函數內聯,決定權在於編譯器。

虛函數(virtual)能夠是內聯函數(inline)嗎?

Are "inline virtual" member functions ever actually "inlined"?

答案:www.cs.technion.ac.il/users/yechi…

  • 虛函數能夠是內聯函數,內聯是能夠修飾虛函數的,可是當虛函數表現多態性的時候不能內聯。

  • 內聯是在編譯器建議編譯器內聯,而虛函數的多態性在運行期,編譯器沒法知道運行期調用哪一個代碼,所以虛函數表現爲多態性時(運行期)不能夠內聯。

  • inline virtual 惟一能夠內聯的時候是:編譯器知道所調用的對象是哪一個類(如 Base::who()),這隻有在編譯器具備實際對象而不是對象的指針或引用時纔會發生。

虛函數內聯使用

#include <iostream> 
using namespace std;
class Base {
public:
    inline virtual void who() {
        cout << "I am Base\n";
    }
    virtual ~Base() {}
};
class Derived : public Base
{
public:
    inline void who() // 不寫inline時隱式內聯 {
        cout << "I am Derived\n";
    }
};

int main() {
    // 此處的虛函數 who(),是經過類(Base)的具體對象(b)來調用的,編譯期間就能肯定了,因此它能夠是內聯的,但最終是否內聯取決於編譯器。 
    Base b;
    b.who();

    // 此處的虛函數是經過指針調用的,呈現多態性,須要在運行時期間才能肯定,因此不能爲內聯。 
    Base *ptr = new Derived();
    ptr->who();

    // 由於Base有虛析構函數(virtual ~Base() {}),因此 delete 時,會先調用派生類(Derived)析構函數,再調用基類(Base)析構函數,防止內存泄漏。
    delete ptr;
    ptr = nullptr;

    system("pause");
    return 0;
} 
複製代碼

assert()

斷言,是宏,而非函數。assert 宏的原型定義在 <assert.h>(C)、<cassert>(C++)中,其做用是若是它的條件返回錯誤,則終止程序執行。能夠經過定義 NDEBUG 來關閉 assert,可是須要在源代碼的開頭,include <assert.h> 以前。

使用

#define NDEBUG // 加上這行,則 assert 不可用
#include <assert.h>

assert( p != NULL );    // assert 不可用
複製代碼

sizeof()

  • sizeof 對數組,獲得整個數組所佔空間大小。

  • sizeof 對指針,獲得指針自己所佔空間大小。

#pragma pack(n)

設定結構體、聯合以及類成員變量以 n 字節方式對齊

使用

#pragma pack(push) // 保存對齊狀態
#pragma pack(4) // 設定爲 4 字節對齊

struct test
{
    char m1;
    double m4;
    int m3;
};

#pragma pack(pop) // 恢復對齊狀態
複製代碼

位域

Bit mode: 2;    // mode 佔 2 位
複製代碼

類能夠將其(非靜態)數據成員定義爲位域(bit-field),在一個位域中含有必定數量的二進制位。當一個程序須要向其餘程序或硬件設備傳遞二進制數據時,一般會用到位域。

  • 位域在內存中的佈局是與機器有關的

  • 位域的類型必須是整型或枚舉類型,帶符號類型中的位域的行爲將因具體實現而定

  • 取地址運算符(&)不能做用於位域,任何指針都沒法指向類的位域

volatile

volatile int i = 10; 
複製代碼
  • volatile 關鍵字是一種類型修飾符,用它聲明的類型變量表示能夠被某些編譯器未知的因素(操做系統、硬件、其它線程等)更改。因此使用 volatile 告訴編譯器不該對這樣的對象進行優化。

  • volatile 關鍵字聲明的變量,每次訪問時都必須從內存中取出值(沒有被 volatile 修飾的變量,可能因爲編譯器的優化,從 CPU 寄存器中取值)

  • const 能夠是 volatile (如只讀的狀態寄存器)

  • 指針能夠是 volatile

extern "C"

  • 被 extern 限定的函數或變量是 extern 類型的

  • 被 extern "C" 修飾的變量和函數是按照 C 語言方式編譯和鏈接的

extern "C" 的做用是讓 C++ 編譯器將 extern "C" 聲明的代碼看成 C 語言代碼處理,能夠避免 C++ 因符號修飾致使代碼不能和C語言庫中的符號進行連接的問題。

"C" 使用

#ifdef __cplusplus
extern "C" {
#endif

void *memset(void *, int, size_t);

#ifdef __cplusplus
}
#endif
複製代碼

struct 和 typedef struct

C 中

// c
typedef struct Student {
    int age; 
} S;
複製代碼

等價於

// c
struct Student { 
    int age; 
};

typedef struct Student S;
複製代碼

此時 S 等價於 struct Student,但兩個標識符名稱空間不相同。

另外還能夠定義與 struct Student 不衝突的 void Student() {}

C++ 中

因爲編譯器定位符號的規則(搜索規則)改變,致使不一樣於C語言。

1、若是在類標識符空間定義了 struct Student {...};,使用 Student me; 時,編譯器將搜索全局標識符表,Student 未找到,則在類標識符內搜索。

即表現爲可使用 Student 也可使用 struct Student,以下:

// cpp
struct Student { 
    int age; 
};

void f( Student me );       // 正確,"struct" 關鍵字可省略
複製代碼

2、若定義了與 Student 同名函數以後,則 Student 只表明函數,不表明結構體,以下:

typedef struct Student { 
    int age; 
} S;

void Student() {}           // 正確,定義後 "Student" 只表明此函數

//void S() {}               // 錯誤,符號 "S" 已經被定義爲一個 "struct Student" 的別名

int main() {
    Student(); 
    struct Student me;      // 或者 "S me";
    return 0;
}
複製代碼

C++ 中 struct 和 class

總的來講,struct 更適合當作是一個數據結構的實現體,class 更適合當作是一個對象的實現體。

區別

  • 最本質的一個區別就是默認的訪問控制
  1. 默認的繼承訪問權限。struct 是 public 的,class 是 private 的。

  2. struct 做爲數據結構的實現體,它默認的數據訪問控制是 public 的,而 class 做爲對象的實現體,它默認的成員變量訪問控制是 private 的。

union 聯合

聯合(union)是一種節省空間的特殊的類,一個 union 能夠有多個數據成員,可是在任意時刻只有一個數據成員能夠有值。當某個成員被賦值後其餘成員變爲未定義狀態。聯合有以下特色:

  • 默認訪問控制符爲 public

  • 能夠含有構造函數、析構函數

  • 不能含有引用類型的成員

  • 不能繼承自其餘類,不能做爲基類

  • 不能含有虛函數

  • 匿名 union 在定義所在做用域可直接訪問 union 成員

  • 匿名 union 不能包含 protected 成員或 private 成員

  • 全局匿名聯合必須是靜態(static)的

    使用

#include<iostream>

union UnionTest {
    UnionTest() : i(10) {};
    int i;
    double d;
};

static union {
    int i;
    double d;
};

int main() {
    UnionTest u;

    union {
        int i;
        double d;
    };

    std::cout << u.i << std::endl;  // 輸出 UnionTest 聯合的 10

    ::i = 20;
    std::cout << ::i << std::endl;  // 輸出全局靜態匿名聯合的 20

    i = 30;
    std::cout << i << std::endl;    // 輸出局部匿名聯合的 30

    return 0;
}
複製代碼

C 實現 C++ 類

C 語言實現封裝、繼承和多態:

dongxicheng.org/cpp/ooc/

explicit(顯式)構造函數

explicit 修飾的構造函數可用來防止隱式轉換

explicit 使用

class Test1 {
public:
    Test1(int n)            // 普通構造函數
    {
        num=n;
    }
private:
    int num;
};

class Test2 {
public:
    explicit Test2(int n) // explicit(顯式)構造函數 {
        num=n;
    }
private:
    int num;
};

int main() {
    Test1 t1=12;            // 隱式調用其構造函數,成功
    Test2 t2=12;            // 編譯錯誤,不能隱式調用其構造函數
    Test2 t2(12);           // 顯式調用成功
    return 0;
}
複製代碼

friend 友元類和友元函數

  • 能訪問私有成員

  • 破壞封裝性

  • 友元關係不可傳遞

  • 友元關係的單向性

  • 友元聲明的形式及數量不受限制

using

using 聲明

一條 using 聲明 語句一次只引入命名空間的一個成員。它使得咱們能夠清楚知道程序中所引用的究竟是哪一個名字。如:

using namespace_name::name;
複製代碼

構造函數的 using 聲明【C++11】

在 C++11 中,派生類可以重用其直接基類定義的構造函數。

class Derived : Base {
public:
    using Base::Base;
    /* ... */
};
複製代碼

如上 using 聲明,對於基類的每一個構造函數,編譯器都生成一個與之對應(形參列表徹底相同)的派生類構造函數。生成以下類型構造函數:

derived(parms) : base(args) { }
複製代碼

using 指示

using 指示 使得某個特定命名空間中全部名字均可見,這樣咱們就無需再爲它們添加任何前綴限定符了。如:

using namespace_name name;
複製代碼

儘可能少使用 using 指示 污染命名空間

通常說來,使用 using 命令比使用 using 編譯命令更安全,這是因爲它只導入了制定的名稱。若是該名稱與局部名稱發生衝突,編譯器將發出指示。using編譯命令導入全部的名稱,包括可能並不須要的名稱。若是與局部名稱發生衝突,則局部名稱將覆蓋名稱空間版本,而編譯器並不會發出警告。另外,名稱空間的開放性意味着名稱空間的名稱可能分散在多個地方,這使得難以準確知道添加了哪些名稱。

using 使用

儘可能少使用 using 指示

using namespace std;
複製代碼

應該多使用 using 聲明

int x;
std::cin >> x ;
std::cout << x << std::endl;
複製代碼

或者

using std::cin;
using std::cout;
using std::endl;
int x;
cin >> x;
cout << x << endl;
複製代碼

:: 範圍解析運算符

分類

  1. 全局做用域符(::name):用於類型名稱(類、類成員、成員函數、變量等)前,表示做用域爲全局命名空間

  2. 類做用域符(class::name):用於表示指定類型的做用域範圍是具體某個類的

  3. 命名空間做用域符(namespace::name):用於表示指定類型的做用域範圍是具體某個命名空間的

:: 使用

int count = 0;        // 全局(::)的 count

class A {
public:
    static int count; // 類 A 的 count(A::count)
};

int main() {
    ::count = 1;      // 設置全局的 count 的值爲 1

    A::count = 2;     // 設置類 A 的 count 爲 2

    int count = 0;    // 局部的 count
    count = 3;        // 設置局部的 count 的值爲 3

    return 0;
}
複製代碼

enum 枚舉類型

限定做用域的枚舉類型

enum class open_modes { input, output, append };
複製代碼

不限定做用域的枚舉類型

enum color { red, yellow, green };
enum { floatPrec = 6, doublePrec = 10 };
複製代碼

decltype

decltype 關鍵字用於檢查實體的聲明類型或表達式的類型及值分類。語法:

decltype ( expression )
複製代碼

使用

// 尾置返回容許咱們在參數列表以後聲明返回類型
template <typename It>
auto fcn(It beg, It end) -> decltype(*beg)
{
    // 處理序列
    return *beg;    // 返回序列中一個元素的引用
}
// 爲了使用模板參數成員,必須用 typename
template <typename It>
auto fcn2(It beg, It end) -> typename remove_reference<decltype(*beg)>::type
{
    // 處理序列
    return *beg;    // 返回序列中一個元素的拷貝
}
複製代碼

引用

左值引用

常規引用,通常表示對象的身份。

右值引用

右值引用就是必須綁定到右值(一個臨時對象、將要銷燬的對象)的引用,通常表示對象的值。

右值引用可實現轉移語義(Move Sementics)和精確傳遞(Perfect Forwarding),它的主要目的有兩個方面:

  • 消除兩個對象交互時沒必要要的對象拷貝,節省運算存儲資源,提升效率。

  • 可以更簡潔明確地定義泛型函數。

引用摺疊

  • X& &、X& &&、X&& & 可摺疊成 X&

  • X&& && 可摺疊成 X&&

  • 宏定義能夠實現相似於函數的功能,可是它終歸不是函數,而宏定義中括弧中的「參數」也不是真的參數,在宏展開的時候對 「參數」 進行的是一對一的替換。

成員初始化列表

好處

  • 更高效:少了一次調用默認構造函數的過程。

  • 有些場合必需要用初始化列表:

  1. 常量成員,由於常量只能初始化不能賦值,因此必須放在初始化列表裏面

  2. 引用類型,引用必須在定義的時候初始化,而且不能從新賦值,因此也要寫在初始化列表裏面

  3. 沒有默認構造函數的類類型,由於使用初始化列表能夠沒必要調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化。

initializer_list 列表初始化【C++11】

用花括號初始化器列表列表初始化一個對象,其中對應構造函數接受一個 std::initializer_list 參數.

initializer_list 使用

#include <iostream>
#include <vector>
#include <initializer_list>

template <class T>
struct S {
    std::vector<T> v;
    S(std::initializer_list<T> l) : v(l) {
         std::cout << "constructed with a " << l.size() << "-element list\n";
    }
    void append(std::initializer_list<T> l) {
        v.insert(v.end(), l.begin(), l.end());
    }
    std::pair<const T*, std::size_t> c_arr() const {
        return {&v[0], v.size()};  // 在 return 語句中複製列表初始化
                                   // 這不使用 std::initializer_list
    }
};

template <typename T>
void templated_fn(T) {}

int main()
{
    S<int> s = {1, 2, 3, 4, 5}; // 複製初始化
    s.append({6, 7, 8});      // 函數調用中的列表初始化

    std::cout << "The vector size is now " << s.c_arr().second << " ints:\n";

    for (auto n : s.v)
        std::cout << n << ' ';
    std::cout << '\n';

    std::cout << "Range-for over brace-init-list: \n";

    for (int x : {-1, -2, -3}) // auto 的規則令此帶範圍 for 工做
        std::cout << x << ' ';
    std::cout << '\n';

    auto al = {10, 11, 12};   // auto 的特殊規則

    std::cout << "The list bound to auto has size() = " << al.size() << '\n';

//    templated_fn({1, 2, 3}); // 編譯錯誤!「 {1, 2, 3} 」不是表達式,
                             // 它無類型,故 T 沒法推導
    templated_fn<std::initializer_list<int>>({1, 2, 3}); // OK
    templated_fn<std::vector<int>>({1, 2, 3});           // 也 OK
}
複製代碼

面向對象

面向對象程序設計(Object-oriented programming,OOP)是種具備對象概念的程序編程典範,同時也是一種程序開發的抽象方針。

面向對象特徵

面向對象三大特徵 —— 封裝、繼承、多態

封裝

  • 把客觀事物封裝成抽象的類,而且類能夠把本身的數據和方法只讓可信的類或者對象操做,對不可信的進行信息隱藏。

  • 關鍵字:public, protected, friendly, private。不寫默認爲 friendly。

關鍵字 當前類 包內 子孫類 包外
public
protected ×
friendly × ×
private × × ×

繼承

  • 基類(父類)——> 派生類(子類)

多態

  • 多態,即多種狀態,在面嚮對象語言中,接口的多種不一樣的實現方式即爲多態。

  • C++ 多態有兩種:靜態多態(早綁定)、動態多態(晚綁定)。靜態多態是經過函數重載實現的;動態多態是經過虛函數實現的。

  • 多態是以封裝和繼承爲基礎的。

靜態多態(早綁定)

函數重載

class A
{
public:
    void do(int a);
    void do(int a, int b);
};
複製代碼

動態多態(晚綁定)

  • 虛函數:用 virtual 修飾成員函數,使其成爲虛函數

注意:

  • 普通函數(非類成員函數)不能是虛函數

  • 靜態函數(static)不能是虛函數

  • 構造函數不能是虛函數(由於在調用構造函數時,虛表指針並無在對象的內存空間中,必需要構造函數調用完成後纔會造成虛表指針)

  • 內聯函數不能是表現多態性時的虛函數,解釋見:虛函數(virtual)能夠是內聯函數(inline)嗎?:t.cn/E4WVXSP

動態多態使用

class Shape                     // 形狀類
{
public:
    virtual double calcArea()
    {
        ...
    }
    virtual ~Shape();
};
class Circle : public Shape     // 圓形類
{
public:
    virtual double calcArea();
    ...
};
class Rect : public Shape       // 矩形類
{
public:
    virtual double calcArea();
    ...
};
int main()
{
    Shape * shape1 = new Circle(4.0);
    Shape * shape2 = new Rect(5.0, 6.0);
    shape1->calcArea();         // 調用圓形類裏面的方法
    shape2->calcArea();         // 調用矩形類裏面的方法
    delete shape1;
    shape1 = nullptr;
    delete shape2;
    shape2 = nullptr;
    return 0;
}
複製代碼

虛析構函數

虛析構函數是爲了解決基類的指針指向派生類對象,並用基類的指針刪除派生類對象。

虛析構函數使用

class Shape
{
public:
    Shape();                    // 構造函數不能是虛函數
    virtual double calcArea();
    virtual ~Shape();           // 虛析構函數
};
class Circle : public Shape     // 圓形類
{
public:
    virtual double calcArea();
    ...
};
int main()
{
    Shape * shape1 = new Circle(4.0);
    shape1->calcArea();    
    delete shape1;  // 由於Shape有虛析構函數,因此delete釋放內存時,先調用子類析構函數,再調用基類析構函數,防止內存泄漏。
    shape1 = NULL;
    return 0;
}
複製代碼

純虛函數

純虛函數是一種特殊的虛函數,在基類中不能對虛函數給出有意義的實現,而把它聲明爲純虛函數,它的實現留給該基類的派生類去作。

virtual int A() = 0;
複製代碼

虛函數、純虛函數

CSDN . C++ 中的虛函數、純虛函數區別和聯繫:t.cn/E4WVQBI

  • 類裏若是聲明瞭虛函數,這個函數是實現的,哪怕是空實現,它的做用就是爲了能讓這個函數在它的子類裏面能夠被覆蓋,這樣的話,這樣編譯器就可使用後期綁定來達到多態了。純虛函數只是一個接口,是個函數的聲明而已,它要留到子類裏去實現。

  • 虛函數在子類裏面也能夠不重載的;但純虛函數必須在子類去實現。

  • 虛函數的類用於 「實做繼承」,繼承接口的同時也繼承了父類的實現。固然你們也能夠完成本身的實現。純虛函數關注的是接口的統一性,實現由子類完成。

  • 帶純虛函數的類叫抽象類,這種類不能直接生成對象,而只有被繼承,並重寫其虛函數後,才能使用。抽象類和你們口頭常說的虛基類仍是有區別的,在 C# 中用 abstract 定義抽象類,而在 C++ 中有抽象類的概念,可是沒有這個關鍵字。抽象類被繼承後,子類能夠繼續是抽象類,也能夠是普通類,而虛基類,是含有純虛函數的類,它若是被繼承,那麼子類就必須實現虛基類裏面的全部純虛函數,其子類不能是抽象類。

虛函數指針、虛函數表

  • 虛函數指針:在含有虛函數類的對象中,指向虛函數表,在運行時肯定。

  • 虛函數表:在程序只讀數據段(.rodata section,見:目標文件存儲結構:t.cn/E4WVBeF),存放…

虛繼承

虛繼承用於解決多繼承條件下的菱形繼承問題(浪費存儲空間、存在二義性)。

底層實現原理與編譯器相關,通常經過虛基類指針虛基類表實現,每一個虛繼承的子類都有一個虛基類指針(佔用一個指針的存儲空間,4字節)和虛基類表(不佔用類對象的存儲空間)(須要強調的是,虛基類依舊會在子類裏面存在拷貝,只是僅僅最多存在一份而已,並非不在子類裏面了);當虛繼承的子類被當作父類繼承時,虛基類指針也會被繼承。

實際上,vbptr 指的是虛基類表指針(virtual base table pointer),該指針指向了一個虛基類表(virtual table),虛表中記錄了虛基類與本類的偏移地址;經過偏移地址,這樣就找到了虛基類成員,而虛繼承也不用像普通多繼承那樣維持着公共基類(虛基類)的兩份一樣的拷貝,節省了存儲空間。

虛繼承、虛函數

  • 相同之處:都利用了虛指針(均佔用類的存儲空間)和虛表(均不佔用類的存儲空間)

  • 不一樣之處:

  • 虛函數不佔用存儲空間

  • 虛函數表存儲的是虛函數地址

  • 虛基類依舊存在繼承類中,只佔用存儲空間

  • 虛基類表存儲的是虛基類相對直接繼承類的偏移

  • 虛繼承

  • 虛函數

模板類、成員模板、虛函數

  • 模板類中可使用虛函數

  • 一個類(不管是普通類仍是類模板)的成員模板(自己是模板的成員函數)不能是虛函數

抽象類、接口類、聚合類

  • 抽象類:含有純虛函數的類

  • 接口類:僅含有純虛函數的抽象類

  • 聚合類:用戶能夠直接訪問其成員,而且具備特殊的初始化語法形式。知足以下特色:

  • 全部成員都是 public

  • 沒有有定於任何構造函數

  • 沒有類內初始化

  • 沒有基類,也沒有 virtual 函數

內存分配和管理

malloc、calloc、realloc、alloca

  1. malloc:申請指定字節數的內存。申請到的內存中的初始值不肯定。

  2. calloc:爲指定長度的對象,分配能容納其指定個數的內存。申請到的內存的每一位(bit)都初始化爲 0。

  3. realloc:更改之前分配的內存長度(增長或減小)。當增長長度時,可能需將之前分配區的內容移到另外一個足夠大的區域,而新增區域內的初始值則不肯定。

  4. alloca:在棧上申請內存。程序在出棧的時候,會自動釋放內存。可是須要注意的是,alloca 不具可移植性, 並且在沒有傳統堆棧的機器上很難實現。alloca 不宜使用在必須普遍移植的程序中。C99 中支持變長數組 (VLA),能夠用來替代 alloca。

malloc、free

用於分配、釋放內存

malloc、free 使用

申請內存,確認是否申請成功

char *str = (char*) malloc(100);
assert(str != nullptr);
複製代碼

釋放內存後指針置空

free(p); 
p = nullptr;
複製代碼

new、delete

  1. new / new[]:完成兩件事,先底層調用 malloc 分了配內存,而後調用構造函數(建立對象)。

  2. delete/delete[]:也完成兩件事,先調用析構函數(清理資源),而後底層調用 free 釋放空間。

  3. new 在申請內存時會自動計算所需字節數,而 malloc 則需咱們本身輸入申請內存空間的字節數。

new、delete 使用

申請內存,確認是否申請成功

int main()
{
    T* t = new T();     // 先內存分配 ,再構造函數
    delete t;           // 先析構函數,再內存釋放
    return 0;
}
複製代碼

定位 new

定位 new(placement new)容許咱們向 new 傳遞額外的參數。

new (palce_address) type
new (palce_address) type (initializers)
new (palce_address) type [size]
new (palce_address) type [size] { braced initializer list }
複製代碼
  • palce_address 是個指針

  • initializers 提供一個(可能爲空的)以逗號分隔的初始值列表

delete this 合法嗎?

Is it legal (and moral) for a member function to say delete this? 答案:t.cn/E4Wfcfl

合法,但:

  1. 必須保證 this 對象是經過 new(不是 new[]、不是 placement new、不是棧上、不是全局、不是其餘對象成員)分配的

  2. 必須保證調用 delete this 的成員函數是最後一個調用 this 的成員函數

  3. 必須保證成員函數的 delete this 後面沒有調用 this 了

  4. 必須保證 delete this 後沒有人使用了

如何定義一個只能在堆上(棧上)生成對象的類?

如何定義一個只能在堆上(棧上)生成對象的類?

答案:t.cn/E4WfDhP

只能在堆上

方法:將析構函數設置爲私有

緣由:C++ 是靜態綁定語言,編譯器管理棧上對象的生命週期,編譯器在爲類對象分配棧空間時,會先檢查類的析構函數的訪問性。若析構函數不可訪問,則不能在棧上建立對象。

只能在棧上

方法:將 new 和 delete 重載爲私有

緣由:在堆上生成對象,使用 new 關鍵詞操做,其過程分爲兩階段:第一階段,使用 new 在堆上尋找可用內存,分配給對象;第二階段,調用構造函數生成對象。將 new 操做設置爲私有,那麼第一階段就沒法完成,就不可以在堆上生成對象。

智能指針

C++ 標準庫(STL)中

頭文件:#include <memory>

C++ 98

std::auto_ptr<std::string> ps (new std::string(str));
複製代碼

受限於文章字數限制,後續部分請看【中篇】。


掃描下方二維碼,及時獲取更多互聯網求職面經javapython爬蟲大數據等技術,和海量資料分享:公衆號後臺回覆「csdn」便可免費領取【csdn】和【百度文庫】下載服務;公衆號後臺回覆「資料」:便可領取5T精品學習資料java面試考點java面經總結,以及幾十個java、大數據項目資料很全,你想找的幾乎都有

掃碼關注,及時獲取更多精彩內容。(博主今日頭條大數據工程師)

推薦閱讀

【微信事業羣】二面面經

相關文章
相關標籤/搜索