30道C++ 基礎高頻題整理(附答案背誦版)

1. C和C++有什麼區別?

C++是C語言的超集(我看網上很多文章說這是不對的),這意味着幾乎所有的C程序都可以在C++編譯器中編譯和運行。然而,C++引入了許多新的概念和特性,使得兩種語言在一些關鍵點上有顯著的區別。

以下是C和C++的一些主要區別:

  1. 面向對象編程:C++支持面向對象編程(OOP),包括類、對象、繼承、封裝、多態等特性。這使得C++更適合大型軟件項目,因爲OOP可以提高代碼的重用性和可讀性。C語言是一種過程性語言,沒有這些特性。
  2. STL(Standard Template Library):C++提供了STL,這是一套強大的模板類和函數的庫,包括列表、向量、隊列、棧、關聯數組等。這些可以大大提高開發效率。C語言沒有內置的數據結構庫。
  3. 異常處理:C++提供了異常處理機制,可以更優雅地處理錯誤情況。C語言處理錯誤通常依賴於函數返回值。
  4. 構造函數和析構函數:C++支持構造函數和析構函數,這些特殊的函數允許對象在創建和銷燬時執行特定的代碼。C語言沒有這個概念。
  5. 運算符重載:C++允許運算符重載,這意味着開發者可以更改已有運算符的行爲,或者爲用戶自定義類型添加新的運算符。C語言不支持運算符重載。

例如,如果我們要創建一個複數類並對其進行算術運算,C++的面向對象和運算符重載特性就非常有用。我們可以定義一個複數類,然後重載+、-和*運算符以執行復數的加法、減法和乘法。這樣,我們就可以像處理內置類型一樣處理複數對象。反觀C語言,我們需要定義結構體來存儲複數,並且需要寫一堆函數來處理複數的加法、減法和乘法。

2. C語言的結構體和C++的有什麼區別

C語言的結構體和C++的結構體在基本的使用上是相似的,都是用來封裝多個不同或相同類型的數據。然而,C++中的結構體繼承了C++面向對象的特性,與C語言中的結構體有一些關鍵性的區別:

  1. 成員函數:C++的結構體可以包含成員函數(包括構造函數和析構函數),而C語言的結構體不能。
  2. 訪問控制:C++的結構體支持公有(public)、保護(protected)和私有(private)三種訪問控制級別,而C語言的結構體中的所有成員都是公有的。
  3. 繼承:C++的結構體可以從其他結構體或類繼承,而C語言的結構體不能繼承。

舉個例子,假設我們需要創建一個表示日期的結構體,包含年、月、日這三個字段,並且需要一個函數來檢查日期是否有效。在C語言中,我們需要定義一個結構體和一個獨立的函數:

struct Date {
    int year;
    int month;
    int day;
};

// 獨立函數
int is_valid_date(struct Date date) {
    // 驗證日期的邏輯
}

在C++中,我們可以將這個函數作爲結構體的成員函數:

struct Date {
    int year;
    int month;
    int day;

    bool is_valid() const {
        // 驗證日期的邏輯
    }
};

這樣,在C++中使用時,我們可以直接調用成員函數 is_valid(),代碼更加清晰和易於維護。

Date date;
//...
if (date.is_valid()) {
    //...
}

3. C 語言的關鍵字 static 和 C++ 的關鍵字 static 有什麼區別

在C和C++中,static關鍵字有三個主要的用途,但其在C++中的用法更加豐富:

  1. 在函數內部:在C和C++中,static關鍵字可用於函數內部變量。此時,此變量的生命週期將貫穿整個程序,即使函數執行結束,這個變量也不會被銷燬。每次調用這個函數時,它都不會重新初始化。這可以用於實現一些需要保持狀態的函數。
  2. 在函數外部或類內部:在C和C++中,static關鍵字可以用於全局變量或函數。此時,此變量或函數的作用域被限制在定義它的文件內,無法在其他文件中訪問。這可以防止命名衝突或不必要的訪問。
  3. 在類內部:只有C++支持此用法。在C++中,static關鍵字可以用於類的成員變量或成員函數。對於靜態成員變量,無論創建多少個類的實例,都只有一份靜態成員變量的副本。靜態成員函數則可以直接通過類名調用,而無需創建類的實例。

以下是一個C++中使用static的例子:

class MyClass {
public:
    static int count; // 靜態成員變量,所有實例共享一份

    MyClass() {
        count++; // 每次創建實例,計數加1
    }

    static int getCount() { // 靜態成員函數,通過類名直接調用
        return count;
    }
};

int MyClass::count = 0; // 靜態成員變量的初始化

int main() {
    MyClass a;
    MyClass b;
    MyClass c;

    std::cout << MyClass::getCount(); // 輸出3,因爲創建了3個實例
}

這個例子中,我們創建了一個名爲MyClass的類,它有一個靜態成員變量count和一個靜態成員函數getCount()。每創建一個MyClass的實例,count就會增加1。我們可以直接通過類名調用getCount()來獲取count的值,而無需創建類的實例。

4. C++ 和 Java有什麼核心區別?

  1. C++和Java都是廣泛使用的編程語言,但它們在設計理念、功能和用途上有很大的不同。以下是C++和Java的幾個核心區別:
  2. 運行環境:Java是一種解釋型語言,它的代碼在JVM(Java虛擬機)上運行,這使得Java程序可以在任何安裝有JVM的平臺上運行,實現了“一次編寫,到處運行”的理念。而C++是一種編譯型語言,其代碼直接編譯成目標機器的機器碼運行,因此需要針對特定平臺編譯。
  3. 內存管理:Java有自動內存管理和垃圾回收機制,程序員不需要直接管理內存。而在C++中,程序員需要手動進行內存的分配和釋放,這提供了更大的控制力,但同時也增加了內存泄漏的風險。
  4. 面向對象編程:Java是一種純面向對象的編程語言,所有的代碼都需要包含在類中。與此不同,C++支持面向對象編程,但它也允許過程式編程。
  5. 錯誤處理:Java使用異常處理機制進行錯誤處理,而C++既支持異常處理,也支持通過返回值進行錯誤處理。
  6. 多線程:Java內置了對多線程的支持,而C++在C++11標準之後引入了對多線程的支持。
  7. 性能:因爲C++的代碼直接編譯爲機器碼,所以它通常比Java程序運行得更快。但是,Java的跨平臺能力和內置的垃圾回收機制使其在開發大型企業級應用時更具優勢。

例如,如果你正在開發一個需要直接訪問硬件,或者需要高性能數學計算的應用(比如遊戲,圖形渲染,科學計算),C++可能是一個更好的選擇。而如果你正在開發一個大型的企業級web應用,Java的跨平臺能力,內置的垃圾回收和強大的類庫可能會更有優勢。

5. C++中,a和&a有什麼區別?

在C++中,a和&a表示的是兩種完全不同的概念:

  1. a:當你在代碼中寫a時,你正在引用變量a的值。例如,如果你之前寫的int a = 10;,那麼a的值就是10。
  2. &a:&是一個地址運算符,它給出了變量a在內存中的位置。這被稱作a的引用或者是指向a的指針。例如,如果你寫int* p = &a;,那麼p就是一個指向a的指針,你可以通過*p來訪問或修改a的值。

這是C++中的一種基礎概念,被稱爲指針和引用。通過指針和引用,你可以直接操作內存,這在很多情況下都非常有用,例如,動態內存分配,函數參數傳遞,數據結構(如鏈表和樹)等等。

6. C++中,static關鍵字有什麼作用?

在C++中,static關鍵字有多個用途,它的作用主要取決於它在哪裏被使用:

  1. 在函數內部:如果static被用於函數內部的變量,那麼它會改變該變量的生命週期,使其在程序的整個運行期間都存在,而不是在每次函數調用結束時被銷燬。這意味着,這個變量的值在函數調用之間是保持的。
  2. 在函數外部:如果static被用於函數外部的全局變量或函數,那麼它會將這個變量或函數的鏈接範圍限制在它被定義的文件內。換句話說,這個變量或函數不能在其他文件中被直接訪問。這可以幫助減少命名衝突,而且能提供一種控制變量和函數可見性的方式。
  3. 在類中:如果static被用於類的成員變量,那麼該變量將會成爲這個類的所有實例共享的變量,也就是說,類的每個實例都能訪問到這個同樣的變量。如果static被用於類的成員函數,那麼這個函數可以直接通過類來調用,而不需要創建類的實例。

以下是一個C++中使用static的例子:

class MyClass {
public:
    static int count; // 靜態成員變量,所有實例共享一份

    MyClass() {
        count++; // 每次創建實例,計數加1
    }

    static int getCount() { // 靜態成員函數,通過類名直接調用
        return count;
    }
};

int MyClass::count = 0; // 靜態成員變量的初始化

int main() {
    MyClass a;
    MyClass b;
    MyClass c;

    std::cout << MyClass::getCount(); // 輸出3,因爲創建了3個實例
}

在這個例子中,MyClass有一個靜態成員變量count和一個靜態成員函數getCount()。每次創建一個MyClass的實例,count就會增加1。我們可以直接通過類名調用getCount()來獲取count的值,而無需創建類的實例。

7. C++中,#define和const有什麼區別?

#define和const都可以用來定義常量,但它們在實現方式和使用上有一些區別。

  1. 預處理器與編譯器:#define是預處理器指令,在編譯前會被預處理器替換,它只是簡單的文本替換,不進行類型檢查,也不會分配內存。而const是編譯器處理的,它會在編譯時進行類型檢查,確保你不會意外地改變它的值。
  2. 作用域:#define沒有作用域的概念,一旦定義,到文件結束都有效。而const常量有作用域,它的作用範圍限制在定義它的塊或者文件中。
  3. 調試:在調試時,#define定義的宏常量無法查看,因爲在預處理階段就已經被替換掉了。而const定義的常量在調試過程中是可以查看的。

例如,考慮以下的代碼:

#define PI 3.14
const double Pi = 3.14;

double area1 = PI * r * r; // 使用#define定義的常量
double area2 = Pi * r * r; // 使用const定義的常量

在這個例子中,PI是一個預處理器定義的宏,而Pi是一個const定義的常量。兩者都可以用來計算圓的面積,但Pi在編譯時進行類型檢查,並且在調試過程中可以查看其值。

9. 靜態鏈接和動態鏈接有什麼區別?

靜態鏈接和動態鏈接是兩種不同的程序鏈接方式,它們主要的區別在於鏈接的時間和方式。

  1. 靜態鏈接:在靜態鏈接中,所有代碼(包括程序本身的代碼和它依賴的庫的代碼)都會在編譯時期被合併爲一個單一的可執行文件。這個可執行文件包含了程序運行所需的所有信息,因此它不依賴於任何外部的庫文件。靜態鏈接的優點是部署簡單,因爲不需要額外的依賴,只需要一個文件就可以運行。缺點是可執行文件通常會比動態鏈接的大,因爲它包含了所有需要的代碼,而且如果庫更新,程序需要重新編譯和鏈接。
  2. 動態鏈接:在動態鏈接中,程序的代碼和它依賴的庫的代碼被分開。程序的可執行文件只包含了程序本身的代碼和一些標記,這些標記表示程序在運行時需要鏈接到哪些庫。當程序運行時,操作系統會負責加載這些庫並進行鏈接。動態鏈接的優點是可執行文件更小,因爲它不包含庫的代碼,而且多個程序可以共享同一份庫,節省內存。此外,如果庫更新,只需要替換庫文件,程序無需重新編譯和鏈接。缺點是部署稍微複雜一些,因爲需要確保運行環境中有所需的庫文件。

例如,假設我們有一個程序,它使用了一個數學庫。如果我們靜態鏈接這個庫,那麼所有的數學函數都會被包含在我們的可執行文件中,我們可以將這個文件複製到任何地方運行。如果我們動態鏈接這個庫,那麼我們的可執行文件就會小得多,但如果我們想在另一臺機器上運行這個程序,我們就需要確保那臺機器上也安裝了這個數學庫。

10. 變量的聲明和定義有什麼區別

在C++中,變量的聲明和定義是兩個不同的概念。

聲明是告訴編譯器某個變量的存在,以及它的類型。聲明並不分配存儲空間。例如,外部變量的聲明extern int a;,這裏只是告訴編譯器有一個類型爲int的變量a存在,具體的a在哪裏定義的,編譯器此時並不知道。

定義是聲明的延伸,除了聲明變量的存在和類型以外,還分配了存儲空間。例如,int a;就是一個定義,編譯器在這裏爲a分配了足夠的存儲空間來存儲一個整數。

在C++中,一個變量可以被聲明多次,但只能被定義一次。例如,我們可以在多個文件中聲明同一個變量,但只能在一個文件中定義它。如果在多個地方定義同一個變量,編譯器會報錯。

舉個例子,假設我們正在編寫一個大型程序,這個程序有一個全局變量需要在多個文件中使用。我們可以在一個文件中定義這個變量,然後在其他需要使用這個變量的文件中聲明它。這樣,所有的文件都可以訪問到這個變量,但只有一個文件負責管理它的存儲空間。

11. typedef 和define 有什麼區別

typedef和#define都是C++中用於定義別名的關鍵字,但它們的用途和行爲有所不同。

typedef是C++的一個關鍵字,用於爲現有的類型創建一個新的名稱(別名)。例如,如果我們想要爲unsigned long int創建一個更簡單的別名,我們可以寫typedef unsigned long int ulong;,然後在代碼中就可以使用ulong來代替unsigned long int。typedef只能爲類型定義別名,不能爲值定義別名。

#define是預處理器的一個指令,用於創建宏。宏可以是一個值,也可以是一段代碼。例如,#define PI 3.14159就定義了一個名爲PI的宏,它的值是3.14159。#define的作用範圍更廣,它不僅可以爲類型定義別名,也可以爲值定義別名,甚至可以定義一段代碼。

兩者的主要區別在於:

  1. typedef僅作用於類型,而#define可以定義類型、值或者代碼。
  2. typedef是由編譯器解析的,而#define是由預處理器處理的。因此,typedef的作用範圍是局部的,只在定義它的文件或作用域內有效,而#define的作用範圍是全局的,一旦定義即在整個源代碼中有效。
  3. typedef定義的別名會受到類型檢查,而#define定義的宏不會。例如,如果你試圖使用typedef爲一個函數類型定義別名,然後使用這個別名定義一個整數,編譯器會報錯。但是如果你使用#define定義一個函數類型的宏,然後使用這個宏定義一個整數,預處理器會默默地接受。
  4. typedef可以處理模板化的類型,而#define不能。例如,typedef std::vector int_vector;是合法的,但是使用#define來做同樣的事情就會出現問題。
  5. typedef定義的別名在調試時更友好。因爲它是編譯器處理的,所以在調試時可以看到別名。而#define定義的宏在預處理階段就被替換掉了,所以在調試時看不到宏的名稱,只能看到宏的值。

12. final和override關鍵字

final和override是C++11引入的兩個關鍵字,主要用於類的繼承和虛函數的覆蓋。

  1. final:如果一個類被聲明爲final,那麼它不能被繼承。例如,class Base final { ... };,此時任何試圖繼承Base的類都會導致編譯錯誤。此外,如果一個虛函數被聲明爲final,那麼它不能在派生類中被覆蓋。例如,virtual void fun() final;,此時任何派生類試圖覆蓋fun()函數都會導致編譯錯誤。
  2. override:如果一個虛函數被聲明爲override,那麼編譯器會檢查這個函數是否真的覆蓋了基類中的一個虛函數。如果沒有,編譯器會報錯。這個關鍵字可以幫助我們避免因爲拼寫錯誤或者函數簽名錯誤而導致的錯誤。例如,void fun() override;,如果基類中沒有一個函數的簽名和fun()完全匹配,那麼編譯器就會報錯。

例如,假設我們有一個基類Animal和一個派生類Dog。Animal有一個虛函數make_sound(),Dog需要覆蓋這個函數。如果我們在Dog的make_sound()函數聲明中加上了override關鍵字,那麼如果我們不小心將函數名拼寫成了mkae_sound(),編譯器就會因爲找不到對應的基類函數而報錯,幫助我們及時發現錯誤。

13. 宏定義和函數有何區別?

宏定義(#define)和函數是兩種常見的在C++中編寫代碼的方式,但它們有一些重要的區別:

  1. 編譯階段:宏定義是在預處理階段展開的,而函數是在編譯階段處理的。這意味着使用宏定義的代碼在編譯前就已經被預處理器替換掉了,而函數在編譯階段會生成對應的函數調用。
  2. 類型檢查:函數在編譯時會進行類型檢查,而宏定義不會。這可能會導致宏定義在使用時出現錯誤,而在編譯階段並不會被發現。
  3. 效率:由於宏定義在預處理階段就被替換,因此它沒有函數調用的開銷(如堆棧操作),所以在某些情況下可能更快。然而,過度使用宏定義可能會導致編譯後的代碼體積增大,因爲每次使用宏都會插入一份宏的代碼副本。
  4. 封裝:函數提供了更好的封裝,使得代碼更易於閱讀和維護。而宏定義由於其替換性質,可能會在複雜的表達式中產生不易察覺的錯誤。

例如,考慮一個簡單的宏定義和函數,它們都用於計算兩個值的最大值:

#define MAX(a, b) ((a) > (b) ? (a) : (b))

int max(int a, int b) {
    return a > b ? a : b;
}

如果我們使用MAX宏定義來計算MAX(i++, j++),由於宏展開,i和j可能會增加兩次,這是一個副作用,可能導致不可預見的結果。而使用函數max(i++, j++),則不會有這個問題,因爲函數參數的求值順序是確定的,i和j只會增加一次。

14. sizeof 和strlen 的區別

sizeof和strlen是兩個在C++中常用的函數,但它們的功能和用途有所不同:

  1. sizeof 是一個編譯時操作符,它返回一個對象或者類型所佔用的字節數。例如,sizeof(int)將返回4(在大多數現代系統中),sizeof(char)將返回1。如果你對一個數組使用sizeof,它將返回整個數組的大小,而不是數組中的元素個數。例如,int arr[10]; sizeof(arr);將返回40(在大多數現代系統中,因爲一個int通常佔用4個字節,所以10個int佔用40個字節)。
  2. strlen 是一個運行時函數,它返回一個以空字符終止的字符串的長度。該長度不包括終止的空字符。例如,strlen("hello")將返回5。注意,strlen只能用於字符數組,且該字符數組必須以空字符('\0')終止,否則strlen會繼續讀取內存,直到遇到一個空字符,這可能會導致未定義的行爲。

舉個例子,如果我們有一個字符數組char arr[10] = "hello";,那麼sizeof(arr)將返回10(因爲arr是一個10個字符的數組,每個字符佔用1個字節),而strlen(arr)將返回5(因爲字符串"hello"的長度是5,不包括終止的空字符)。

15. 簡述strcpy、sprintf 與memcpy 的區別

strcpy, sprintf, 和 memcpy 都是 C/C++ 標準庫中的函數,用於處理字符串和內存,但它們的作用是不同的:

  1. strcpy:這個函數用於將源字符串複製到目標字符串。它會複製源字符串的所有字符,直到遇到終止的空字符,並在目標字符串的末尾添加一個空字符。例如,char dest[10]; strcpy(dest, "hello");,這將會把字符串 "hello"(包括終止的空字符)複製到 dest 中。需要注意的是,如果源字符串的長度(包括終止的空字符)超過了目標字符串的大小,那麼會導致緩衝區溢出,這可能會引發安全問題。
  2. sprintf:這個函數用於將格式化的數據寫入字符串。它可以接受多個輸入參數,並按照指定的格式將這些參數格式化爲一個字符串,然後將這個字符串寫入目標字符串。例如,char str[50]; sprintf(str, "Hello, %s!", "world");,這將會把 "Hello, world!" 寫入 str。同樣,如果格式化後的字符串長度(包括終止的空字符)超過了目標字符串的大小,那麼會導致緩衝區溢出。
  3. memcpy:這個函數用於複製內存區域。它會從源內存區域複製指定數量的字節到目標內存區域。例如,char dest[10]; char src[10] = "hello"; memcpy(dest, src, 6);,這將會把 src 的前6個字節(包括終止的空字符)複製到 dest。memcpy 不會因爲遇到空字符而停止複製,它總是複製指定的字節數,因此,如果指定的字節數超過了目標內存區域的大小,那麼會導致緩衝區溢出。

總的來說,strcpy 和 sprintf 是處理以空字符終止的字符串的函數,而 memcpy 是處理內存的函數。在使用這些函數時,需要特別注意緩衝區溢出的問題。

由於內容太多,更多內容以鏈接形勢給大家,點擊進去就是答案了

16. volatile有什麼作用

17. 一個參數可以既是const又是volatile嗎

18. 全局變量和局部變量有什麼區別?操作系統和編譯器是怎麼知道的?

19. 什麼是C++中的指針和引用?它們有什麼區別?

20. 數組名和指針(這裏爲指向數組首元素的指針)區別?

21. 一個指針佔用多少字節?

22. 什麼是智能指針?智能指針有什麼作用?分爲哪幾種?各自有什麼樣的特點?

23. shared_ptr是如何實現的?

24. 右值引用有什麼作用?

25. 懸掛指針與野指針有什麼區別?

26. 指針常量與常量指針區別

27. 如何避免“野指針”

28. 句柄和指針的區別和聯繫是什麼?

29. 說一說extern“C”

30. 對c++中的smart pointer四個智能指針:shared_ptr,unique_ptr,weak_ptr,auto_ptr的理解

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