總時長大概在一個小時,主要提問C++、操作系統、計網以及數據庫等方面,最后兩個算法編程題。
一上來先介紹項目
Linux下的mybash命令處理器和內存池
mybash可以再總結歸納一下,一上來有點緊張沒有條理
內存池是用邊界標識法寫的,大概介紹了一下,問了知不知道伙伴系統,講一下
提問了linux下的內存管理
Linux 下的內存管理是一個復雜而重要的系統功能,以下是其相關介紹:
- 內核空間:
- 內核空間是操作系統內核運行的區域,它擁有最高的權限,可以訪問系統的所有資源。
- 內核空間占用了一部分物理內存,用于存儲內核代碼、數據結構以及內核態下的各種進程和線程。
- 內核空間中的內存管理由內核負責,它通過一系列的數據結構和算法來分配、釋放和管理內存,以確保內核的穩定運行和系統資源的有效利用。
- 用戶空間:
- 用戶空間是用戶進程運行的區域,每個用戶進程都有自己獨立的用戶空間。
- 用戶空間的大小通常由操作系統和硬件平臺決定,例如,在 32 位的 Linux 系統中,用戶空間一般為 3GB,而在 64 位系統中,用戶空間則可以達到更大的范圍。
- 用戶空間中的內存管理由用戶進程自己負責,進程可以通過系統調用請求操作系統分配內存,也可以使用標準的 C 庫函數如
malloc
、free
等來管理內存。
內存管理的主要功能
- 內存分配與回收:負責為進程分配內存空間,確保進程有足夠的內存來運行。當進程結束或不再需要某些內存時,及時回收這些內存,以便重新分配給其他進程,提高內存利用率。
- 地址轉換:將進程使用的虛擬地址轉換為物理地址。每個進程都有自己獨立的虛擬地址空間,通過地址轉換機制,使得不同進程的虛擬地址可以映射到不同的物理內存區域,保證進程之間的內存相互隔離,提高系統的穩定性和安全性。
- 內存保護:防止進程非法訪問或修改其他進程的內存空間,以及保護操作系統自身的內存區域不被用戶進程隨意篡改。通過設置內存訪問權限,如只讀、可寫、可執行等,確保內存的正確使用。
- 內存共享:允許不同進程共享同一塊物理內存區域,以實現進程間通信和數據共享,提高內存使用效率,減少內存浪費。
- 緩存管理:利用內存作為緩存,加速對磁盤等慢速存儲設備的數據訪問。例如,將經常訪問的文件數據緩存到內存中,下次訪問時可以直接從內存中讀取,大大提高了訪問速度。
內存管理的實現方式
- 虛擬內存:Linux 通過虛擬內存技術為每個進程提供了一個獨立的、連續的虛擬地址空間。虛擬地址空間被劃分為多個頁,每頁大小通常為 4KB 或更大。物理內存也被劃分為同樣大小的頁幀。通過頁表等數據結構,將虛擬頁映射到物理頁幀上。當進程訪問的虛擬地址不在物理內存中時,會觸發缺頁中斷,操作系統將相應的頁面從磁盤交換到物理內存中。
- 內存分配算法:Linux 采用了多種內存分配算法,如伙伴系統算法和 slab 分配器。伙伴系統算法主要用于管理較大的內存塊分配,它將內存空間按照不同的大小級別進行劃分,當需要分配內存時,從合適的級別中查找空閑塊并進行分配。slab 分配器則主要用于為內核對象分配小內存,它預先分配一些內存緩沖區,并將其劃分為多個小的對象空間,當需要創建內核對象時,直接從相應的 slab 緩沖區中分配空間,提高了分配效率。
- 內存回收機制:當物理內存不足時,Linux 會啟動內存回收機制。通過掃描內存中的頁面,將不常用的頁面交換到磁盤上的交換空間中,以釋放物理內存供其他進程使用。同時,也會根據頁面的使用頻率和最近訪問時間等因素,選擇合適的頁面進行回收。
內存管理相關的命令和工具
- free 命令:用于查看系統內存的使用情況,包括總內存、已使用內存、空閑內存、緩沖區和緩存使用的內存等信息。
- top 命令:可以實時顯示系統中各個進程的資源占用情況,包括內存占用、CPU 使用率等,幫助用戶了解系統中內存消耗較大的進程。
- vmstat 命令:提供了關于系統虛擬內存、進程、CPU 等方面的統計信息,對于分析內存性能和系統整體運行狀況有很大幫助。
- strace 命令:可以跟蹤進程的系統調用,查看進程對內存的分配、釋放等操作,有助于調試內存相關的問題。
C/C++
1.C和C++的區別
C 和 C++ 均為廣泛應用的編程語言,二者存在諸多不同,下面為你詳細介紹:
1. 語言范式
- C 語言:屬于面向過程的編程語言,其核心在于對過程的抽象,把大問題分解成若干小問題,每個小問題由一個函數來解決。程序的執行過程就是一系列函數的調用過程。
c
#include <stdio.h>// 函數用于計算兩個整數的和
int add(int a, int b) {return a + b;
}int main() {int result = add(3, 5);printf("The result is: %d\n", result);return 0;
}
- C++ 語言:是一種支持多種編程范式的語言,涵蓋面向過程、面向對象、泛型編程等。面向對象編程將數據和操作數據的方法封裝成類,通過類的實例(對象)來實現程序功能。
cpp
#include <iostream>// 定義一個類
class Calculator {
public:int add(int a, int b) {return a + b;}
};int main() {Calculator calc;int result = calc.add(3, 5);std::cout << "The result is: " << result << std::endl;return 0;
}
2. 輸入輸出方式
- C 語言:主要使用標準輸入輸出函數,如
scanf
和printf
。
c
#include <stdio.h>int main() {int num;printf("Please enter a number: ");scanf("%d", &num);printf("You entered: %d\n", num);return 0;
}
- C++ 語言:采用流對象
cin
和cout
進行輸入輸出操作,使用起來更加方便,且類型安全。
cpp
#include <iostream>int main() {int num;std::cout << "Please enter a number: ";std::cin >> num;std::cout << "You entered: " << num << std::endl;return 0;
}
3. 內存管理
- C 語言:使用
malloc
、calloc
、realloc
進行內存分配,使用free
釋放內存。
c
#include <stdio.h>
#include <stdlib.h>int main() {int *arr = (int *)malloc(5 * sizeof(int));if (arr == NULL) {printf("Memory allocation failed\n");return 1;}for (int i = 0; i < 5; i++) {arr[i] = i;}for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}free(arr);return 0;
}
- C++ 語言:除了可以使用 C 語言的內存管理方式,還引入了
new
和delete
運算符。new
用于動態分配內存,delete
用于釋放內存。
cpp
#include <iostream>int main() {int *arr = new int[5];for (int i = 0; i < 5; i++) {arr[i] = i;}for (int i = 0; i < 5; i++) {std::cout << arr[i] << " ";}delete[] arr;return 0;
}
4. 函數重載
- C 語言:不支持函數重載,即不能定義同名但參數不同的函數。
- C++ 語言:支持函數重載,允許定義多個同名但參數列表不同的函數,編譯器會根據調用時的實參類型和數量來選擇合適的函數。
cpp
#include <iostream>// 函數重載示例
int add(int a, int b) {return a + b;
}double add(double a, double b) {return a + b;
}int main() {int intResult = add(3, 5);double doubleResult = add(3.2, 5.6);std::cout << "Integer result: " << intResult << std::endl;std::cout << "Double result: " << doubleResult << std::endl;return 0;
}
5. 類和對象
- C 語言:沒有類和對象的概念,主要通過結構體來組織數據。結構體只能包含數據成員,不能包含成員函數。
c
#include <stdio.h>// 定義一個結構體
struct Point {int x;int y;
};void printPoint(struct Point p) {printf("(%d, %d)\n", p.x, p.y);
}int main() {struct Point p = {3, 5};printPoint(p);return 0;
}
- C++ 語言:引入了類和對象的概念,類是一種用戶自定義的數據類型,它可以包含數據成員和成員函數。
cpp
#include <iostream>// 定義一個類
class Point {
private:int x;int y;
public:Point(int x, int y) : x(x), y(y) {}void print() {std::cout << "(" << x << ", " << y << ")" << std::endl;}
};int main() {Point p(3, 5);p.print();return 0;
}
6. 異常處理
- C 語言:沒有內置的異常處理機制,通常通過返回錯誤碼來處理錯誤。
c
#include <stdio.h>
#include <stdlib.h>int divide(int a, int b, int *result) {if (b == 0) {return -1; // 返回錯誤碼}*result = a / b;return 0;
}int main() {int a = 10, b = 0, result;if (divide(a, b, &result) == -1) {printf("Error: division by zero\n");} else {printf("Result: %d\n", result);}return 0;
}
- C++ 語言:提供了異常處理機制,使用
try
、catch
和throw
關鍵字來捕獲和處理異常。
cpp
#include <iostream>int divide(int a, int b) {if (b == 0) {throw "Division by zero!"; // 拋出異常}return a / b;
}int main() {int a = 10, b = 0;try {int result = divide(a, b);std::cout << "Result: " << result << std::endl;} catch (const char* msg) {std::cerr << "Error: " << msg << std::endl;}return 0;
}
7. 模板
- C 語言:不支持模板。
- C++ 語言:支持模板,模板是泛型編程的基礎,它允許編寫與類型無關的代碼。例如,標準模板庫(STL)就是基于模板實現的。
cpp
#include <iostream>// 模板函數示例
template <typename T>
T max(T a, T b) {return (a > b) ? a : b;
}int main() {int intMax = max(3, 5);double doubleMax = max(3.2, 5.6);std::cout << "Integer max: " << intMax << std::endl;std::cout << "Double max: " << doubleMax << std::endl;return 0;
}
綜上所述,C++ 是 C 語言的擴展,在 C 語言的基礎上增加了許多新特性,使得代碼更加靈活、可維護和可擴展。但 C 語言由于其簡潔性和高效性,在嵌入式系統、操作系統等領域仍然有廣泛的應用。
2.C++優勢體現在哪些方面
性能高效
- 接近硬件:C++ 能直接操作內存、寄存器等硬件資源,通過指針可對內存進行精確控制,減少不必要的內存開銷。例如在開發操作系統內核時,需要直接管理內存分配和釋放,C++ 可以滿足這種對硬件的直接操作需求,從而實現高效的系統性能。
- 執行速度快:C++ 代碼編譯后生成的機器碼執行效率高,在需要處理大量數據和復雜計算的場景中,如金融領域的高頻交易系統,需要在短時間內完成大量的交易數據處理和計算,C++ 的高性能可以保證系統的實時性和響應速度。
功能強大
- 支持多種編程范式:C++ 支持面向過程、面向對象、泛型編程和函數式編程等多種編程范式。例如,在開發一個游戲引擎時,可以使用面向對象編程來設計游戲中的角色、場景等類,使用泛型編程來實現通用的數據結構和算法,提高代碼的復用性和可維護性。
- 豐富的標準庫:C++ 標準庫提供了豐富的容器(如?
vector
、map
?等)、算法(如?sort
、find
?等)和輸入輸出流等功能,開發者可以直接使用這些庫來提高開發效率。例如,在開發一個文件管理系統時,可以使用標準庫中的文件流來進行文件的讀寫操作,使用容器來存儲文件信息。
可移植性強
- 跨平臺開發:C++ 代碼可以在不同的操作系統(如 Windows、Linux、macOS 等)和硬件平臺上進行編譯和運行,只需要根據不同的平臺進行適當的調整和優化。例如,一款跨平臺的辦公軟件可以使用 C++ 開發,在不同的操作系統上都能提供一致的用戶體驗。
- 代碼復用:C++ 的代碼可以在不同的項目中進行復用,通過將通用的功能封裝成庫,可以在多個項目中共享和使用,減少開發成本和時間。
應用廣泛
- 系統軟件:C++ 常用于開發操作系統、編譯器、數據庫管理系統等系統軟件。例如,Windows 操作系統、GCC 編譯器等都是使用 C++ 開發的。
- 游戲開發:許多大型游戲引擎(如 Unreal Engine、Unity 等)都使用 C++ 作為主要的開發語言,因為 C++ 可以提供高性能的圖形渲染和物理模擬等功能。
- 嵌入式系統:在嵌入式系統開發中,C++ 可以直接操作硬件資源,滿足嵌入式系統對性能和實時性的要求。例如,智能家居設備、汽車電子系統等都廣泛使用 C++ 進行開發。
社區支持和生態系統
- 龐大的開發者社區:C++ 擁有龐大的開發者社區,開發者可以在社區中分享經驗、交流技術、解決問題。例如,Stack Overflow 等技術論壇上有大量關于 C++ 的問題和解決方案。
- 豐富的第三方庫和工具:有許多優秀的第三方庫和工具可供 C++ 開發者使用,如 Boost 庫、OpenCV 庫等,這些庫可以擴展 C++ 的功能,提高開發效率。
3.C++引用和指針的區別
在C++ 中,引用和指針有以下區別:
定義與性質
- 引用:是一個已存在變量的別名,它在定義時必須初始化,并且一旦初始化后就不能再引用其他變量。例如,?int a = 5; int& ref = a;?,?ref?就是?a?的引用。
- 指針:是一個變量,其值是另一個變量的地址。指針可以在定義后再賦值,也可以指向不同的變量。例如,?int a = 5; int* ptr = &a;?,?ptr?指向?a?的地址,之后可以讓?ptr?指向其他?int?類型的變量。
操作符
- 引用:使用時就像使用普通變量一樣,直接訪問其引用的變量的值,不需要使用特殊的操作符來解引用。例如,?ref = 10;? 就是直接修改?a?的值。
- 指針:訪問指針所指向的變量的值時,需要使用?*?操作符進行解引用。例如,?*ptr = 10;? 才能修改?a?的值。
內存分配
- 引用:不占用額外的內存空間來存儲地址信息,它只是被引用變量的一個別名。
- 指針:本身需要占用一定的內存空間來存儲所指向變量的地址,在32位系統中通常占4個字節,在64位系統中通常占8個字節。
空值
- 引用:不能指向空值,必須始終引用一個有效的對象。
- 指針:可以賦值為?nullptr?,表示不指向任何有效的對象。在使用指針之前,通常需要檢查它是否為?nullptr?,以避免空指針引用錯誤。
應用場景
- 引用:常用于函數參數傳遞和返回值,以避免對象的拷貝,提高效率。例如,函數?void func(int& num)?,傳遞的是變量的引用,在函數中對?num?的修改會影響到原變量。
- 指針:在需要動態分配內存、實現數據結構(如鏈表、樹)以及進行底層內存操作等場景中廣泛使用。例如,?int* arr = new int[10];? 通過指針動態分配了一個包含10個?int?類型元素的數組。
4.指針和數組的區別
概念
- 指針:是一個變量,用于存儲內存地址,通過該地址可以間接訪問和操作所指向的內存空間中的數據。
- 數組:是一種復合數據類型,它由多個相同類型的元素組成,這些元素在內存中連續存儲,通過數組名和下標來訪問各個元素。
定義與初始化
- 指針:定義時需要指定所指向的數據類型,如?int* p;?。初始化可以將其指向已存在的變量,如?int a = 5; int* p = &a;?,也可以初始化為?nullptr?,即?int* p = nullptr;?。
- 數組:定義時需要指定數組的類型和大小,如?int arr[5];?。初始化可以采用靜態初始化,如?int arr[5] = {1, 2, 3, 4, 5};?,也可以在定義后逐個元素賦值。
內存分配
- 指針:本身占用一定的內存空間來存儲地址,其指向的內存空間可以通過?new?運算符動態分配,如?int* p = new int;?,也可以指向靜態分配或棧上的變量。
- 數組:數組的內存是在定義時靜態分配在棧上,或使用?new?動態分配在堆上。例如,?int* arr = new int[10];?在堆上分配了一個包含10個?int?類型元素的數組。
訪問方式
- 指針:通過解引用操作符?*?來訪問所指向的變量,如?*p = 10;?。也可以通過指針的算術運算來訪問連續內存空間中的數據,如?p++?指向下一個同類型的內存地址。
- 數組:通過數組名和下標來訪問元素,如?arr[2] = 10;?。數組名在表達式中會隱式轉換為指向數組首元素的指針,因此也可以使用指針的方式訪問數組元素,如?*(arr + 2) = 10;?。
sizeof 操作符
- 指針:在32位系統中,?sizeof?一個指針通常為4字節,在64位系統中通常為8字節,與指針所指向的數據類型無關。
- 數組:?sizeof?數組得到的是整個數組所占用的內存空間大小,單位是字節。例如,?int arr[5];?,?sizeof(arr)?的值為?20?(假設?int?類型占4字節)。
可變性
- 指針:指針的值(即所指向的地址)可以改變,使其指向不同的內存空間。例如,?int a = 5, b = 6; int* p = &a; p = &b;?,指針?p?先指向?a?,后指向?b?。
- 數組:數組名代表數組的首地址,是一個常量指針,其值不能被修改。例如,?int arr[5]; arr++;?是錯誤的操作。
雖然在某些情況下指針和數組有相似的表現,但它們在概念、內存管理、操作特性等方面存在明顯的區別。
5.內聯函數和普通函數的區別
在C++ 中,內聯函數和普通函數有以下區別:
函數調用機制
- 普通函數:調用時會產生函數調用的開銷,包括保存現場、傳遞參數、跳轉到函數地址執行代碼以及返回結果后恢復現場等操作。
- 內聯函數:在編譯時,編譯器會將內聯函數的代碼直接嵌入到調用處,避免了函數調用的開銷,提高了程序的執行效率。
代碼膨脹
- 普通函數:無論被調用多少次,在目標代碼中只有一份函數代碼。
- 內聯函數:由于代碼會被嵌入到每個調用處,如果內聯函數體較大且被頻繁調用,可能會導致目標代碼體積增大,即代碼膨脹。
定義位置
- 普通函數:可以在源文件的任何位置定義,只要在調用前進行聲明即可。
- 內聯函數:通常建議將內聯函數的定義放在頭文件中,因為編譯器需要在編譯調用該函數的代碼時看到內聯函數的定義,以便進行代碼嵌入。
適用場景
- 普通函數:適用于代碼邏輯復雜、函數體較大、不頻繁調用的情況。
- 內聯函數:適用于函數體簡單、執行速度要求高、被頻繁調用的情況,如一些簡單的訪問器函數、小型的輔助函數等。
編譯器處理
- 普通函數:編譯器按照正常的函數調用規則進行編譯和鏈接。
- 內聯函數:編譯器會根據內聯函數的定義和編譯優化策略,決定是否將其真正展開為內聯代碼。即使函數被聲明為內聯,編譯器也可能因為某些原因(如函數體過于復雜)而不進行內聯展開。
6.內聯函數和普通函數的使用場景有哪些
C++中內聯函數和普通函數的使用場景分別如下:
內聯函數
- 頻繁調用的小函數:例如獲取對象屬性的訪問器函數,像?getX()? ?getY()?等,將它們定義為內聯函數可以避免函數調用開銷,提高程序執行效率。
- 用于替代宏定義:宏定義存在一些問題,如沒有類型檢查、可能導致代碼可讀性差等。內聯函數可以在提供類似宏的代碼嵌入效果的同時,進行類型檢查,增強代碼的安全性和可讀性。
普通函數
- 復雜邏輯函數:當函數體包含大量的代碼邏輯、循環、條件判斷等復雜操作時,使用普通函數可以使代碼結構更清晰,避免內聯帶來的代碼膨脹問題。
- 不頻繁調用的函數:對于不經常被調用的函數,函數調用的開銷相對來說影響較小,使用普通函數可以保持代碼的簡潔性和可維護性。
- 需要動態鏈接的函數:在一些動態鏈接庫的場景中,普通函數更適合,因為內聯函數的代碼嵌入特性可能會導致在不同模塊間的鏈接出現問題。
7.C++的多態以及優缺點
C++ 多態是指在不同的情況下,一個函數或對象可以表現出不同的行為。C++ 多態主要有以下兩種形式:
編譯時多態(靜態多態)
- 函數重載:在同一個作用域內,可以有多個同名函數,但函數的參數列表(參數個數、類型或順序)不同。編譯器會根據調用時傳遞的參數類型和個數來確定調用哪個函數。例如:
int add(int a, int b) {
????return a + b;
}
double add(double a, double b) {
????return a + b;
}
- 模板:通過模板可以定義通用的函數或類,在編譯時根據實際使用的類型生成具體的代碼。例如,下面是一個模板函數,用于交換兩個變量的值:
template <typename T>
void swap(T& a, T& b) {
????T temp = a;
????a = b;
????b = temp;
}
運行時多態(動態多態)
- 虛函數:在基類中使用?virtual?關鍵字聲明的函數,在派生類中可以重寫。通過基類指針或引用調用虛函數時,會根據對象的實際類型來決定調用哪個派生類的重寫版本。例如:
class Shape {
public:
????virtual double area() const {
????????return 0.0;
????}
};
class Circle : public Shape {
public:
????Circle(double r) : radius(r) {}
????double area() const override {
????????return 3.14159 * radius * radius;
????}
private:
????double radius;
};
在上述代碼中,?Shape?類中的?area?函數是虛函數,?Circle?類重寫了?area?函數。當使用?Shape?類的指針或引用調用?area?函數時,如果指向的是?Circle?對象,就會調用?Circle?類中重寫的?area?函數,實現運行時多態。
優點
- 提高代碼的可維護性和可擴展性:當需要添加新的功能或修改現有功能時,只需要在相應的派生類中進行修改或添加,而不需要修改大量的調用代碼。例如,在圖形繪制系統中,添加新的圖形類型,只需增加一個派生類并重寫相關的繪制函數,不影響其他圖形的繪制代碼。
- 增強代碼的復用性:通過多態,不同的派生類可以共享基類的接口,實現代碼的復用。如多個不同類型的文件操作類,都可以繼承自一個基類,共享一些通用的文件打開、關閉等操作代碼,同時各自實現特定的讀寫功能。
- 實現靈活的程序設計:多態允許在運行時根據對象的實際類型來決定執行的操作,使程序具有更高的靈活性和動態性。例如,在游戲開發中,根據不同的角色類型,在運行時調用不同的行為函數。
缺點
- 增加程序的復雜性:多態涉及到虛函數、繼承等概念,使得程序的結構和執行流程變得復雜,增加了理解和調試的難度。例如,在多層繼承結構中,查找虛函數的調用路徑可能會比較復雜。
- 可能導致性能開銷:運行時多態通過虛函數表來實現,在調用虛函數時需要額外的查找虛函數表的操作,會帶來一定的性能開銷。雖然現代編譯器會對此進行一些優化,但在一些對性能要求極高的場景下,可能會產生影響。
- 對代碼可讀性有一定影響:多態使得代碼中的函數調用可能在運行時才能確定具體執行的代碼,這可能會影響代碼的可讀性,尤其是對于不熟悉多態機制的開發者來說。
8.純虛函數
純虛函數是在基類中聲明的虛函數,它沒有函數體,只有函數聲明,用于規范派生類的行為。以下是關于純虛函數的詳細介紹:
定義格式
在函數聲明后面加上?= 0?,例如:
class AbstractClass {
public:
????virtual void pureVirtualFunction() = 0;
};
特點
- 必須在派生類中實現:包含純虛函數的類是抽象類,不能實例化對象。派生類必須實現基類中的純虛函數,否則派生類也會成為抽象類。
- 作為接口規范:純虛函數定義了一組派生類必須遵循的接口,使得不同派生類具有統一的行為模式。
作用
- 實現多態性:通過在派生類中重寫純虛函數,實現運行時多態。當使用基類指針或引用調用純虛函數時,會根據對象的實際類型調用相應派生類的實現。
- 設計抽象基類:用于定義抽象基類,抽象基類提供了一種通用的類型框架,強調派生類應具備的功能,而不關注具體實現。
例如,在一個圖形繪制系統中,可以定義一個抽象基類?Shape?,其中包含純虛函數?draw?,不同的圖形類如?Circle?、?Rectangle?等作為派生類,各自實現?draw?函數來繪制具體的圖形。這樣,通過基類指針或引用就可以根據實際對象類型調用不同圖形的繪制函數,實現多態性。
9.什么函數不能是虛函數
以下幾種函數不能是虛函數:
- 非類成員函數:虛函數是類的成員函數,用于實現類的多態性,普通的全局函數或靜態函數不屬于類的成員,不能定義為虛函數。
- 構造函數:構造函數用于創建對象并初始化其成員變量,在對象創建時被調用。虛函數的調用需要通過對象的虛函數表來實現,而在構造函數執行時,對象還未完全初始化,虛函數表也未正確設置,所以構造函數不能是虛函數。
- 靜態成員函數:靜態成員函數屬于類,而不屬于類的某個對象,它不依賴于對象的狀態,沒有?this?指針。虛函數的調用與對象的動態類型相關,所以靜態成員函數不能是虛函數。
- 內聯函數:內聯函數是在編譯時將函數體插入到調用處,以減少函數調用開銷。虛函數是在運行時根據對象的實際類型來確定調用的函數版本,這與內聯函數的編譯時展開機制相沖突。不過,內聯函數可以調用虛函數。
- 友元函數:友元函數不是類的成員函數,它可以訪問類的私有和保護成員,但不具有多態性的特點,因此不能定義為虛函數。
10.普通函數可以是虛函數嗎
普通函數不能是虛函數。虛函數是類的成員函數,具有特定的多態性機制,需要通過類的對象或指針來調用,并且在運行時根據對象的實際類型確定具體調用的函數版本。而普通函數不屬于類的成員,沒有關聯的類對象和虛函數表等多態實現機制,因此不能被聲明為虛函數。
11.普通函數和虛函數的區別是什么
普通函數和虛函數有以下區別:
定義與聲明
- 普通函數:在類內或類外直接定義,在類外定義時需指明所屬類(如果是類成員函數)。
- 虛函數:在基類中使用?virtual?關鍵字聲明,派生類中重寫時也可加?virtual?(非必需,但建議加上)。
調用機制
- 普通函數:根據函數名和參數在編譯時確定調用的具體函數,屬于靜態綁定。
- 虛函數:通過基類指針或引用調用時,在運行時根據對象的實際類型確定調用的函數版本,是動態綁定。
多態性
- 普通函數:不支持多態,同名函數僅靠函數重載實現不同功能,但本質上是不同的函數,根據參數類型和個數選擇調用。
- 虛函數:是實現多態的關鍵,派生類可重寫虛函數,以實現不同的行為,在運行時根據對象的動態類型來調用相應的函數。
函數覆蓋與隱藏
- 普通函數:在派生類中定義與基類同名但參數不同的函數,會隱藏基類中的函數,而不是覆蓋。
- 虛函數:派生類中重寫虛函數要求函數名、參數列表、返回值類型(協變返回類型除外)都相同,否則編譯錯誤。
性能
- 普通函數:調用速度快,因為編譯時就確定了調用地址,無額外開銷。
- 虛函數:因運行時要通過虛函數表查找函數地址,有一定性能開銷,但現代編譯器會進行優化。
應用場景
- 普通函數:適用于功能明確且固定,不需要在運行時根據不同情況改變行為的場景。
- 虛函數:在設計具有多種不同實現的抽象行為,需要根據對象的實際類型動態選擇行為時使用。如圖形繪制系統中不同圖形的繪制方法。
操作系統
1.linux多線程和多進程的區別
Linux中多進程和多線程有以下區別:
資源分配
- 多進程:每個進程都有獨立的地址空間、代碼段、數據段和堆棧等資源,進程間的資源相互隔離,一個進程的崩潰一般不會影響其他進程。
- 多線程:同一進程內的多個線程共享進程的地址空間、代碼段、數據段等資源,但每個線程有自己獨立的堆棧和寄存器。
通信方式
- 多進程:通信相對復雜,需要使用專門的進程間通信(IPC)機制,如管道、消息隊列、共享內存、信號量等。
- 多線程:通信較為方便,可直接通過共享變量進行數據交換,但需注意線程同步問題,以避免數據沖突。
切換開銷
- 多進程:進程切換時,需要保存和恢復進程的上下文信息,包括寄存器的值、內存映射等,開銷較大。
- 多線程:線程切換只需保存和恢復少量的寄存器和堆棧信息,切換開銷相對較小,能更高效地進行并發執行。
編程難度
- 多進程:編程相對簡單,各進程獨立性強,邏輯清晰,但實現進程間通信和同步較為復雜。
- 多線程:編程時要考慮線程安全問題,避免多個線程同時訪問共享資源導致數據不一致,編程難度較大。
適用場景
- 多進程:適用于需要高度獨立性和穩定性的任務,如服務器守護進程、數據庫管理系統等,每個子進程可以獨立處理不同的請求,互不干擾。
- 多線程:適合于對響應速度要求高、需要頻繁切換任務的場景,如圖形界面應用程序、網絡服務器中的并發處理等,能充分利用多核處理器的優勢,提高程序的運行效率。
2.線程的公有和私有
在Linux線程中,有些資源是線程私有的,而有些則是線程間公有的(共享的),以下是具體介紹:
線程私有資源
- 線程ID:每個線程都有唯一的標識符,用于在進程內區分不同的線程。
- 棧空間:線程有自己獨立的棧,用于存儲局部變量、函數調用參數和返回地址等,不同線程的棧空間相互獨立,不會相互干擾。
- 寄存器:包括程序計數器、棧指針等,保存線程的運行狀態和上下文信息,保證線程被調度時能正確恢復執行。
- errno變量:用于記錄系統調用的錯誤碼,每個線程都有自己的errno,避免一個線程的錯誤影響其他線程對錯誤的判斷。
線程公有(共享)資源
- 進程地址空間:包括代碼段、數據段、堆等,所有線程都在同一個進程地址空間內運行,可以訪問相同的程序代碼和全局數據。
- 文件描述符:進程打開的文件描述符在所有線程間共享,多個線程可以操作同一個文件。
- 信號處理:進程的信號處理機制是共享的,一個線程對信號的處理可能會影響到其他線程。
- 共享庫:進程加載的共享庫在所有線程間共享,節省內存空間,提高程序運行效率。
3.進程通信方式有哪些以及優缺點
Linux中進程通信方式主要有以下幾種:
管道
- 匿名管道:是一種半雙工的通信方式,只能在具有親緣關系的進程之間使用,通常用于父子進程間通信。數據只能單向流動,一端寫入,一端讀出。
- 命名管道:FIFO,有名管道,它可以在不相關的進程之間進行通信。在文件系統中有對應的節點,通過文件路徑來標識,克服了匿名管道只能在親緣進程間通信的限制。
信號
信號是一種用于通知進程發生了某種事件的異步通信機制。進程可以通過信號來響應系統事件、用戶操作或其他進程發送的信號,如?SIGINT?(中斷信號)、?SIGKILL?(終止信號)等。
消息隊列
消息隊列是一個消息的鏈表,存放在內核中,由消息隊列標識符來標識。進程可以向消息隊列中發送消息,也可以從消息隊列中讀取消息,不同進程通過消息隊列進行數據交換。
共享內存
共享內存允許不同進程訪問同一塊內存區域,是最快的進程間通信方式。多個進程可以直接讀寫共享內存中的數據,無需進行數據復制,但需要通過信號量等機制來實現同步和互斥訪問。
信號量
信號量主要用于進程間的同步和互斥,它是一個計數器,用于控制多個進程對共享資源的訪問。通過對信號量的操作(P操作和V操作)來實現進程的同步和互斥控制。
套接字
套接字可用于不同主機間的進程通信,也可用于同一主機上不同進程間的通信。它提供了一種通用的網絡編程接口,支持多種協議,如TCP和UDP。
用戶:
優缺點
豆包:
以下是Linux中各種進程通信方式的優缺點:
管道
- 優點:簡單易用,對于具有親緣關系的進程間通信方便,內核自動進行同步管理。
- 缺點:匿名管道只能用于親緣進程,半雙工通信限制了數據傳輸方向,且管道容量有限。
信號
- 優點:可以快速響應系統事件或異常情況,實現異步通知,開銷較小。
- 缺點:信號能傳遞的信息有限,通常只是一個信號值,難以傳遞復雜數據,且處理信號的代碼可能會打斷正常的程序流程,導致程序邏輯復雜。
消息隊列
- 優點:消息有類型,接收方可以根據類型有選擇地接收消息,適用于不同類型數據的異步通信。
- 缺點:消息的發送和接收有一定的開銷,且消息隊列的大小有限制,若處理不及時可能導致消息丟失。
共享內存
- 優點:通信效率高,數據傳輸速度快,因為進程直接訪問共享內存,無需數據復制。
- 缺點:需要程序員自己處理同步和互斥問題,否則容易出現數據不一致的情況,編程復雜度較高。
信號量
- 優點:能有效實現進程間的同步和互斥,控制對共享資源的訪問,確保數據的一致性。
- 缺點:只能進行簡單的計數和同步控制,不能直接傳輸數據,且使用不當容易導致死鎖。
套接字
- 優點:功能強大,可用于不同主機間的進程通信,支持多種網絡協議,適用于分布式系統。
- 缺點:對于本地進程通信,相比其他一些方式,開銷較大,編程相對復雜,需要處理網絡連接、數據傳輸的可靠性等問題。
4.線程同步方式
在Linux中,線程同步的方式有多種,以下是一些常見的方式:
互斥鎖
- 原理:通過對共享資源進行鎖定,確保在同一時刻只有一個線程能夠訪問該資源。
- 使用方法:使用?pthread_mutex_t?類型的變量來表示互斥鎖,通過?pthread_mutex_lock?函數來獲取鎖,?pthread_mutex_unlock?函數來釋放鎖。
- 優點:簡單高效,適用于對共享資源的短時間獨占訪問。
- 缺點:如果一個線程在持有鎖的情況下發生阻塞,可能會導致其他線程長時間等待。
條件變量
- 原理:允許線程在滿足特定條件時才進行相應的操作,通常與互斥鎖配合使用。
- 使用方法:定義?pthread_cond_t?類型的條件變量,使用?pthread_cond_wait?函數讓線程等待條件滿足,?pthread_cond_signal?或?pthread_cond_broadcast?函數用于喚醒等待的線程。
- 優點:能讓線程在條件不滿足時阻塞,避免無效的忙等待,提高資源利用率。
- 缺點:需要正確地處理條件判斷和信號發送,否則可能導致線程無法正確喚醒或出現競態條件。
讀寫鎖
- 原理:允許多個線程同時讀取共享資源,但在寫操作時需要獨占資源。
- 使用方法:使用?pthread_rwlock_t?類型的變量表示讀寫鎖,通過?pthread_rwlock_rdlock?獲取讀鎖,?pthread_rwlock_wrlock?獲取寫鎖,?pthread_rwlock_unlock?釋放鎖。
- 優點:適合于讀操作遠多于寫操作的場景,能提高并發訪問效率。
- 缺點:實現相對復雜,對讀寫操作的順序和時機有一定要求。
信號量
- 原理:通過一個計數器來控制對共享資源的訪問,當計數器大于0時,線程可以獲取資源并將計數器減1;當計數器為0時,線程需要等待。
- 使用方法:使用?sem_t?類型的變量表示信號量,通過?sem_wait?函數獲取信號量,?sem_post?函數釋放信號量。
- 優點:可以控制多個線程對多個共享資源的訪問,具有較高的靈活性。
- 缺點:如果信號量的初始值設置不當或操作不正確,可能導致死鎖或資源浪費。
5.死鎖
死鎖是指多個進程或線程在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去。以下是關于死鎖的一些關鍵信息:
產生死鎖的原因
- 資源競爭:多個進程或線程同時請求有限的資源,如打印機、內存等,當資源分配不當時,就可能導致死鎖。
- 進程推進順序不當:進程的執行順序不合理,也可能引發死鎖。例如,進程A先獲取了資源R1,然后請求資源R2,而進程B先獲取了資源R2,又請求資源R1,此時兩個進程就會相互等待對方釋放資源,從而產生死鎖。
產生死鎖的必要條件
- 互斥條件:資源在某一時刻只能被一個進程或線程所占用。
- 請求與保持條件:進程已經保持了至少一個資源,但又提出了新的資源請求,而新請求的資源被其他進程占用,導致該進程阻塞,但它仍保持著已獲得的資源。
- 不可剝奪條件:進程所獲得的資源在未使用完之前,不能被其他進程強行剝奪,只能由該進程自己釋放。
- 循環等待條件:存在一個進程或線程的循環鏈,鏈中的每個進程或線程都在等待下一個進程或線程所占用的資源。
死鎖的預防
- 破壞互斥條件:使資源可同時被多個進程訪問,但有些資源本身不支持這種方式,如打印機,所以該方法可行性有限。
- 破壞請求與保持條件:要求進程在開始執行前一次性申請所需的全部資源,或者在申請新資源時,先釋放已持有的資源。
- 破壞不可剝奪條件:允許系統對資源進行剝奪,當一個進程申請的資源被其他進程占用時,可將占用資源的進程暫停,剝奪其資源分配給請求進程。
- 破壞循環等待條件:對資源進行編號,規定進程必須按照編號遞增的順序申請資源,這樣就不會形成循環等待。
死鎖的避免
- 銀行家算法是一種經典的死鎖避免算法。該算法通過對系統資源的分配情況進行監控和預測,判斷每個進程的資源請求是否安全,只有在系統處于安全狀態時才滿足進程的資源請求,從而避免死鎖的發生。
死鎖的檢測與解除
- 檢測:通過資源分配圖等方法來檢測系統是否存在死鎖。資源分配圖中節點表示進程和資源,邊表示進程對資源的請求和分配關系,若圖中存在環,則可能存在死鎖。
- 解除:檢測到死鎖后,可通過撤銷某些進程、剝奪某些進程的資源或讓進程回退到某個安全狀態等方法來解除死鎖。
計算機網絡
1.IO復用
網絡I/O復用是一種在網絡編程中用于同時監控多個文件描述符(通常是套接字)狀態變化的技術,以實現高效的網絡通信。以下是關于它的詳細介紹:
基本概念
- 應用程序通過系統調用,將多個文件描述符注冊到內核中的I/O復用模塊,然后阻塞等待這些文件描述符上有事件發生,如可讀、可寫或異常等。當有事件發生時,內核會通知應用程序,應用程序再對相應的文件描述符進行處理。
常用的I/O復用函數
- select:能同時監控多個文件描述符集合的讀寫和異常事件。它通過?fd_set?結構體來表示文件描述符集合,應用程序需要先將感興趣的文件描述符添加到集合中,然后調用?select?函數。?select?函數會阻塞直到有文件描述符上有事件發生或超時。其缺點是能監控的文件描述符數量有限,通常受限于系統的?FD_SETSIZE?常量,而且每次調用?select?都需要將文件描述符集合從用戶空間復制到內核空間,效率較低。
- poll:與?select?類似,但使用?pollfd?結構體數組來表示文件描述符集合,它沒有文件描述符數量的限制。不過,?poll?同樣需要在每次調用時將數組從用戶空間復制到內核空間,并且當文件描述符數量較多時,遍歷數組判斷事件的開銷也較大。
- epoll:是Linux下高效的I/O復用機制。它通過?epoll_create?創建一個?epoll?實例,然后使用?epoll_ctl?函數向?epoll?實例中添加、修改或刪除要監控的文件描述符及其感興趣的事件。當調用?epoll_wait?函數時,它會阻塞等待事件發生,內核會將發生事件的文件描述符列表返回給應用程序,應用程序只需處理這些有事件的文件描述符即可,大大提高了效率。?epoll?適用于處理大量并發連接的場景。
優點
- 提高資源利用率:可以在一個線程中同時處理多個網絡連接,避免為每個連接創建一個單獨的線程,減少了線程創建和上下文切換的開銷。
- 實現并發處理:能同時監聽多個文件描述符的事件,及時響應不同連接上的讀寫請求,實現對多個網絡連接的并發處理,提高服務器的并發處理能力。
應用場景
- 網絡服務器:廣泛應用于各種網絡服務器程序,如Web服務器、郵件服務器等,用于處理大量客戶端的連接請求和數據傳輸。
- 實時應用:在一些對實時性要求較高的應用中,如即時通訊、在線游戲等,能夠及時響應多個客戶端的操作,保證應用的實時性和流暢性。
2.IO復用的優缺點是什么
網絡I/O復用除了上述提到的提高資源利用率和實現并發處理等優點外,還有一些其他優點和缺點,具體如下:
優點
- 響應性好:能及時響應多個文件描述符上的事件,對于有實時數據處理需求的場景,如實時監控系統,可快速處理接收到的數據,提高系統的響應速度。
- 編程相對簡單:相比于多線程或多進程模型,使用I/O復用可以在一個線程或進程中處理多個連接,減少了多線程編程中復雜的同步和資源管理問題,使代碼結構更清晰,更易于理解和維護。
缺點
- 事件處理復雜:當有大量文件描述符同時產生事件時,處理這些事件的邏輯可能會變得復雜,需要仔細處理不同類型的事件和對應的操作,否則容易出現邏輯錯誤。
- 不適用于所有場景:對于一些特定的場景,如每個連接都需要進行長時間的復雜計算,使用I/O復用可能無法充分發揮其優勢,因為單個線程在處理這些計算時可能會阻塞,導致其他連接的事件不能及時處理。此時,結合多線程或多進程模型可能更合適。
- 可移植性問題:不同操作系統對I/O復用的實現和支持有所不同,例如?epoll?是Linux特有的機制,在其他操作系統上可能需要使用不同的技術或進行適配,這會影響代碼的可移植性。
3.tcp的滑動窗口
TCP的滑動窗口是一種流量控制和數據傳輸優化機制,以下是其相關介紹:
基本原理
- 發送方和接收方都有一個緩存區用于存儲數據,同時各自維護一個滑動窗口。窗口大小表示在無需等待對方確認的情況下,發送方可以發送的數據量。接收方通過在確認消息中告知發送方自己的窗口大小,來控制發送方的發送速度,確保接收方有足夠的緩存空間來處理數據,避免數據丟失。
窗口的滑動
- 當發送方收到接收方對已發送數據的確認信息后,發送窗口會向前滑動,允許發送更多的數據。例如,發送方發送了序號為1 - 1000的字節數據,接收方成功接收并確認后,發送方的窗口就可以向前滑動,假設窗口大小為2000,那么就可以接著發送序號為1001 - 3000的字節數據。
流量控制
- 接收方根據自身的緩存情況和處理能力,動態調整窗口大小并告知發送方。如果接收方處理速度變慢,緩存空間快滿了,就會減小窗口大小,讓發送方降低發送速度;當接收方處理完數據,緩存空間空閑增多時,又會增大窗口大小,允許發送方發送更多數據。
與擁塞控制的關系
- 滑動窗口主要用于流量控制,關注接收方的接收能力;而擁塞控制則主要考慮網絡的擁塞狀況,防止網絡過載。在實際的TCP傳輸中,二者通常結合使用。發送方的發送窗口大小會取擁塞窗口和接收方通告窗口中的較小值,以同時適應網絡狀況和接收方的接收能力。
優點
- 提高傳輸效率,允許發送方在等待確認的同時繼續發送多個數據包,充分利用網絡帶寬。
- 實現流量控制,避免接收方因處理能力不足而丟失數據,保證數據傳輸的可靠性。
缺點
- 窗口大小的設置需要根據網絡和接收方的情況進行合理調整,否則可能導致性能下降。如果窗口設置過大,可能會加重網絡擁塞;設置過小,則無法充分利用網絡帶寬。
- 增加了協議的復雜性,需要額外的機制來維護窗口的狀態、處理確認消息等,增加了TCP實現和維護的難度。
4.tcp的滑動窗口是如何進行流量控制的
TCP滑動窗口通過以下方式進行流量控制:
接收方通告窗口大小
接收方在發送給發送方的確認(ACK)報文中,會包含一個字段來告知發送方自己當前的接收窗口大小,這個窗口大小表示接收方還有多少緩存空間可以用來接收數據。例如,接收方的緩存總大小為1000字節,已經接收并處理了200字節的數據,那么此時它可以在ACK報文中通告窗口大小為800字節,告訴發送方最多可以再發送800字節的數據。
發送方根據通告窗口調整發送速度
發送方接收到接收方通告的窗口大小后,會根據這個值來控制自己的發送速度。發送方不會發送超過接收方通告窗口大小的數據量。比如接收方通告窗口大小為500字節,發送方當前有1000字節的數據要發送,那么發送方只會發送前500字節的數據,然后等待接收方的進一步確認和窗口更新信息。
窗口的動態調整
在數據傳輸過程中,接收方會根據自身緩存的使用情況和數據處理速度,動態地調整通告窗口的大小。如果接收方處理數據的速度加快,緩存空間變得更充裕,它會增大通告窗口,允許發送方發送更多的數據;反之,如果接收方處理速度變慢,緩存快滿了,就會減小通告窗口,讓發送方降低發送速度。例如,接收方原本通告窗口大小為500字節,在處理完一部分數據后,緩存空間增加,于是在下次ACK報文中將通告窗口大小調整為800字節,發送方收到后就可以根據新的窗口大小發送更多數據。
通過這種接收方反饋窗口大小,發送方根據反饋調整發送數據量的機制,TCP滑動窗口能夠有效地實現流量控制,確保數據發送速度與接收方的處理能力相匹配,避免數據丟失和網絡擁塞。
5.擁塞控制的方法
常見的TCP擁塞控制方法有以下幾種:
慢啟動
- 發送方初始時將擁塞窗口大小設置為一個較小的值,通常為一個最大段長度(MSS)。然后,每收到一個對新數據的確認,就將擁塞窗口大小增加一個MSS。這樣,擁塞窗口會以指數增長的方式快速增加,以探測網絡的可用帶寬。
擁塞避免
- 當擁塞窗口大小達到慢啟動閾值時,進入擁塞避免階段。此時,發送方每收到一個確認,不再是成倍增加擁塞窗口,而是線性增加,通常每次增加一個MSS除以擁塞窗口大小的值。通過這種較為緩慢的增長方式,避免網絡出現擁塞。
快重傳
- 接收方如果發現有失序的數據包,會立即發送重復確認給發送方,而不是等到自己的緩存填滿或者定時器超時。發送方只要收到三個相同的重復確認,就認為相應的數據包丟失了,會立即重傳該數據包,而不需要等到定時器超時,從而加快了數據包的重傳速度,提高了網絡的吞吐量。
快恢復
- 快重傳后,執行快恢復算法。發送方將慢啟動閾值設置為當前擁塞窗口大小的一半,同時將擁塞窗口大小設置為慢啟動閾值加上3倍的MSS。然后進入擁塞避免階段,繼續線性增加擁塞窗口,而不是像慢啟動那樣從較小的值開始重新增長,這樣可以更快地恢復網絡傳輸能力,減少數據傳輸的延遲。
數據庫
1.關系型數據庫和非關系型數據庫
關系型數據庫和非關系型數據庫是兩種不同類型的數據庫管理系統,它們在數據模型、存儲結構、查詢方式等方面存在差異,以下是具體介紹:
數據模型
- 關系型數據庫:以表格形式存儲數據,由行和列組成,支持事務處理和約束,如外鍵關聯等,通過SQL語言進行操作,數據一致性高。
- 非關系型數據庫:數據模型更加靈活多樣,包括鍵值對、文檔、列族、圖等形式,支持高并發讀寫,易于橫向擴展,適用于處理非結構化或半結構化數據。
存儲結構
- 關系型數據庫:數據通常存儲在固定格式的表中,表之間通過外鍵等關系進行關聯。數據存儲在硬盤等存儲設備上,以頁為單位進行讀寫,索引用于加快數據的查詢速度。
- 非關系型數據庫:根據不同的類型有不同的存儲方式。例如,鍵值對數據庫將數據以鍵值對的形式存儲在內存或硬盤中;文檔數據庫以文檔的形式存儲數據,數據格式通常為JSON或BSON等。
查詢方式
- 關系型數據庫:使用SQL語言進行查詢,具有強大的查詢功能,可以進行復雜的關聯查詢、分組查詢等。
- 非關系型數據庫:查詢方式因類型而異。例如,鍵值對數據庫通過鍵來獲取值;文檔數據庫可以使用特定的查詢語言或API來查詢文檔中的數據。
適用場景
- 關系型數據庫:適用于對數據一致性要求高、有復雜事務處理需求、數據結構固定且關系明確的場景,如銀行系統、電子商務系統中的訂單處理等。
- 非關系型數據庫:適用于高并發讀寫、數據量巨大、數據結構不固定或半結構化的場景,如社交媒體平臺的用戶生成內容存儲、實時數據處理等。
代表產品
- 關系型數據庫:有MySQL、Oracle、SQL Server等。
- 非關系型數據庫:包括Redis(鍵值對數據庫)、MongoDB(文檔數據庫)、Cassandra(列族數據庫)、Neo4j(圖數據庫)等。
2.什么是事務
在數據庫管理系統中,事務是一個不可分割的工作單元,它包含了一系列的數據庫操作,這些操作要么全部成功執行,要么全部不執行,以保證數據庫的一致性。例如,在銀行轉賬操作中,從一個賬戶扣除一定金額并將相同金額存入另一個賬戶,這兩個操作必須作為一個事務來處理,以確保資金的準確性和一致性。
事務的特性
- 原子性:事務中的所有操作要么全部執行成功,要么全部回滾,就像一個原子一樣不可分割。
- 一致性:事務執行前后,數據庫的完整性約束不會被破壞,數據從一個一致狀態轉換到另一個一致狀態。
- 隔離性:多個事務并發執行時,每個事務都感覺不到其他事務的存在,它們之間相互隔離,互不干擾。
- 持久性:一旦事務被提交,它對數據庫所做的修改就會永久保存,即使系統出現故障也不會丟失。
事務的操作
- 開始事務:標志著事務的開始,后續的數據庫操作將被納入該事務的范圍。
- 提交事務:表示事務中的所有操作都已成功完成,將對數據庫的修改永久保存。
- 回滾事務:當事務中的某個操作出現錯誤或滿足特定條件時,將事務回滾到初始狀態,撤銷事務中已經執行的操作,使數據庫恢復到事務開始前的狀態。
3.事務的acid特性
事務的ACID特性是指原子性(Atomicity)、一致性(Consistency)、隔離性(Isolation)和持久性(Durability),以下是具體解釋:
- 原子性:事務是一個不可分割的工作單元,事務中的所有操作要么全部成功執行,要么全部不執行。例如,在一個銀行轉賬事務中,從賬戶A扣除金額和向賬戶B添加金額這兩個操作必須同時成功或者同時失敗,不能出現只執行了其中一個操作的情況。
- 一致性:事務執行前后,數據庫的完整性約束保持不變,數據從一個一致狀態轉換到另一個一致狀態。例如,在轉賬事務中,轉賬前后,兩個賬戶的總金額應該保持不變,不能因為轉賬操作導致數據不一致。
- 隔離性:多個事務并發執行時,每個事務都感覺不到其他事務的存在,它們之間相互隔離,互不干擾。例如,當多個用戶同時進行轉賬操作時,每個用戶的事務應該是相互獨立的,不會出現一個事務的操作影響到另一個事務的情況。
- 持久性:一旦事務被提交,它對數據庫所做的修改就會永久保存,即使系統出現故障也不會丟失。例如,在轉賬事務提交后,即使數據庫服務器突然斷電,重新啟動后,轉賬的結果也應該是有效的,賬戶的余額已經被正確更新。
4.MySQL鎖機制
MySQL的鎖機制是用于控制并發訪問數據庫時的數據一致性和完整性的重要手段,主要有以下幾種類型:
按鎖的粒度分類
- 表級鎖:對整個表進行鎖定。開銷小,加鎖快,但并發度低。例如,當使用 ?LOCK TABLES table_name READ? 語句對表進行讀鎖定后,其他事務可以對該表進行并發讀,但不能進行寫操作;若使用 ?LOCK TABLES table_name WRITE? 進行寫鎖定,那么其他事務對該表的讀和寫操作都將被阻塞。
- 行級鎖:僅對表中的某一行數據進行鎖定。并發度高,但開銷大,加鎖慢。InnoDB存儲引擎支持行級鎖,如執行 ?UPDATE table_name SET column = value WHERE condition? 語句時,如果 ?condition? 條件能精準定位到某一行,MySQL會使用行級鎖,只鎖定被更新的行,不影響其他行的并發操作。
- 頁級鎖:介于表級鎖和行級鎖之間,鎖定粒度為數據頁。其并發度和開銷也介于兩者之間。
按鎖的類型分類
- 共享鎖(讀鎖):允許事務對數據進行讀取操作,多個事務可以同時獲取同一數據的共享鎖,實現并發讀。例如,一個事務執行 ?SELECT ... LOCK IN SHARE MODE? 語句獲取共享鎖后,其他事務也可以對相同數據獲取共享鎖進行讀取,但不能獲取排他鎖進行寫入操作。
- 排他鎖(寫鎖):只允許一個事務對數據進行寫入操作,并且在排他鎖釋放之前,其他事務不能對該數據獲取任何類型的鎖。例如,執行 ?UPDATE? ?DELETE? 等寫操作時,MySQL會自動獲取排他鎖,以保證數據的一致性和完整性。
MySQL通過鎖機制來協調并發事務對數據的訪問,不同的存儲引擎支持的鎖機制有所不同,用戶可以根據具體的業務需求選擇合適的鎖類型和粒度,以平衡并發性能和數據一致性。
5.數據庫的索引
索引是數據庫中一種用于提高數據查詢效率的數據結構。以下是關于索引的詳細介紹:
索引的作用
- 快速定位數據:就像書籍的目錄,能讓數據庫快速找到所需數據的位置,減少數據查詢時的磁盤 I/O 操作,從而顯著提高查詢性能。例如在一個有大量用戶記錄的表中,通過對“姓名”字段建立索引,查詢特定姓名的用戶時,數據庫可以直接根據索引快速定位到相關記錄,而無需全表掃描。
索引的類型
- B樹索引:是最常見的索引類型,適用于全鍵值、鍵值范圍和鍵前綴查找。其結構能有效平衡樹的高度,保證查詢效率穩定。
- 哈希索引:基于哈希函數實現,能快速定位數據,但只支持精確匹配查詢,不適合范圍查詢。
索引的創建與維護
- 創建索引:使用 ?CREATE INDEX? 語句,如 ?CREATE INDEX index_name ON table_name (column_name)?,可以在指定表的特定列上創建索引。
- 維護索引:數據庫會自動維護索引,但在數據插入、更新和刪除時,索引也需要相應更新,這會帶來一定的性能開銷。因此,要根據實際業務需求合理創建索引,避免過多索引導致維護成本過高。
雖然索引能提高查詢性能,但也會占用額外的存儲空間,并且會影響數據的插入、更新和刪除操作的速度。所以在設計索引時,需要綜合考慮數據庫的性能需求和空間成本等因素。
6.為什么常用b+樹做索引
B+樹是一種常用于數據庫索引的數據結構,它具有以下特點和優勢:
結構特點
- 節點類型:B+樹由根節點、內部節點和葉子節點組成。所有數據都存儲在葉子節點中,葉子節點通過指針連接成有序鏈表,便于范圍查詢。
- 鍵值存儲:內部節點只存儲鍵值和指向子節點的指針,不存儲數據記錄,這樣可以增大每個節點能存儲的鍵值數量,從而減少樹的高度,提高查詢效率。
作為索引的優勢
- 高效的范圍查詢:由于葉子節點是有序鏈表,在進行范圍查詢時,只需找到范圍的起始和結束位置,然后沿著鏈表順序讀取即可,避免了大量的隨機磁盤 I/O。
- 穩定性好:B+樹通過動態調整節點的分裂和合并,保持樹的平衡,使得查詢時間復雜度穩定為 O(log_{n}),n為節點數。
- 支持并發操作:B+樹的結構有利于實現并發控制,多個事務可以同時對不同的節點進行操作,提高數據庫的并發性能。
在數據庫中,使用B+樹作為索引可以大大提高數據的查詢效率,特別是對于范圍查詢和頻繁的等值查詢場景,能夠快速定位到所需數據,減少磁盤訪問次數,提升數據庫的整體性能。
7.b+樹和紅黑樹的區別
B+樹和紅黑樹都是常見的數據結構,在數據庫索引等場景中有著廣泛應用,它們有以下區別:
結構特點
- B+樹:是一種多路平衡查找樹,節點可以有多個子節點,所有數據都在葉子節點,葉子節點之間通過鏈表相連。
- 紅黑樹:是一種二叉查找樹,每個節點只有左右兩個子節點,通過對節點進行紅黑著色來保證樹的平衡。
數據存儲
- B+樹:數據全部存儲在葉子節點,非葉子節點僅用于索引,因此能存儲更多的索引項,樹的高度相對較低。
- 紅黑樹:每個節點都可以存儲數據和鍵值,數據分布在整個樹中。
查找效率
- B+樹:由于樹的高度較低,且葉子節點有序,在進行范圍查詢和等值查詢時用戶:
和紅黑用戶:
和紅黑樹的區別
豆包:
B+樹和紅黑樹都是常見的數據結構,在數據庫索引等場景中有著廣泛應用,它們有以下區別:
結構特點
- B+樹:是一種多路平衡查找樹,節點可以有多個子節點,所有數據都在葉子節點,葉子節點之間通過鏈表相連。
- 紅黑樹:是一種二叉查找樹,每個節點只有左右兩個子節點,通過對節點進行紅黑著色來保證樹的平衡。
數據存儲
- B+樹:數據全部存儲在葉子節點,非葉子節點僅用于索引,因此能存儲更多的索引項,樹的高度相對較低。
- 紅黑樹:每個節點都可以存儲數據和鍵值,數據分布在整個樹中。
查找效率
- B+樹:由于樹的高度較低,且葉子節點有序,在進行范圍查詢和等值查詢時,通常具有更高的效率,查詢時間復雜度為O(log_{n}),n為節點數。
- 紅黑樹:查找效率也比較高,時間復雜度為O(log_{n}),但在范圍查詢上相對B+樹不夠高效,需要遍歷更多的節點。
應用場景
- B+樹:適合用于數據庫索引,特別是在大規模數據存儲和頻繁的范圍查詢、等值查詢場景中表現出色。
- 紅黑樹:常用于內存中的數據結構,如Java的TreeMap、C++ 的map等,在需要快速插入、刪除和查找的場景中應用廣泛。
算法編程
1.刪除字符串中出現的空格,包含首尾
//刪除字符串中出現的空格,包含首尾
#include<iostream>
#include<string>
using namespace std;
string removesp(const string &str)
{string result = "";for (char c :str ){if (c != ' '){result += c;}}return result;}int main()
{string str = " du xin ran ";string newstr = removesp(str);cout << newstr << endl;return 0;
}
2.輸入一個鏈表的頭結點,從尾到頭反過來打印出每個結點的值。?
鏈表結點定義如下:?
不帶頭結點的單鏈表
struct ListNode
{
?? ?int m_nKey;
?? ?ListNode* m_pNext;
};
使用棧
#include<iostream>
#include<stack>
using namespace std;
struct ListNode
{int m_nKey;ListNode* m_pNext;
};
void printLN(ListNode* head)
{stack<ListNode*>s;ListNode* p = head;//存入while (p != nullptr){s.push(p);p = p->m_pNext;}//打印while (!s.empty()){ListNode* node = s.top();cout << node->m_nKey << " ";s.pop();}
}
int main()
{ListNode* head = new ListNode{ 1,new ListNode{2,new ListNode{3,nullptr}} };printLN(head);return 0;
}