C/C++核心知識點詳解

C/C++核心知識點詳解

1. 變量的聲明與定義:內存分配的本質區別

核心概念

在C/C++中,變量的聲明定義是兩個完全不同的概念:

  • 聲明(Declaration):告訴編譯器變量的名稱和類型,但不分配內存空間
  • 定義(Definition):不僅聲明變量,還為其分配實際的內存空間

實際應用示例

// 變量聲明:使用extern關鍵字,不分配內存
extern int global_var;  // 聲明一個整型變量,告訴編譯器這個變量在別處定義// 變量定義:分配內存空間
int global_var = 100;   // 定義變量并分配內存,可以初始化// 函數聲明
int add(int a, int b);  // 函數聲明,不包含函數體// 函數定義
int add(int a, int b) { // 函數定義,包含完整實現return a + b;
}

重要規則

  • 一個變量可以在多個地方聲明,但只能在一個地方定義
  • 聲明可以重復,定義不能重復
  • 使用變量前必須先聲明,使用時才需要定義

2. 不同數據類型與零值比較的標準寫法

為什么要規范比較寫法?

不同數據類型與零值比較時,寫法不當可能導致邏輯錯誤或程序崩潰。

標準比較方式

bool類型:直接判斷真假
bool is_valid = true;// 正確寫法:直接使用布爾值
if (is_valid) {// 條件為真時執行printf("數據有效\n");
} else {// 條件為假時執行printf("數據無效\n");
}
int類型:與0比較
int count = 10;// 正確寫法:將常量放在左邊(防御性編程)
if (0 != count) {// count不等于0時執行printf("計數值為:%d\n", count);
} else {// count等于0時執行printf("計數為零\n");
}
指針類型:與NULL比較
int* ptr = nullptr;// 正確寫法:將NULL放在左邊
if (NULL == ptr) {// 指針為空時執行printf("指針為空\n");
} else {// 指針不為空時執行printf("指針指向的值:%d\n", *ptr);
}
float類型:使用精度范圍比較
float value = 0.0001f;
const float EPSILON = 1e-6f;  // 定義精度閾值// 正確寫法:判斷是否在精度范圍內
if ((value >= -EPSILON) && (value <= EPSILON)) {// 認為等于零printf("浮點數接近零\n");
} else {// 不等于零printf("浮點數值:%f\n", value);
}

防御性編程技巧

將常量放在比較運算符左邊的好處:

// 錯誤示例:容易寫錯
if (count = 0) {  // 誤將==寫成=,編譯通過但邏輯錯誤// 永遠不會執行
}// 正確示例:編譯器會報錯
if (0 = count) {  // 編譯錯誤,無法給常量賦值// 編譯器直接報錯,避免邏輯錯誤
}

3. sizeof與strlen:編譯時計算 vs 運行時計算

本質區別分析

sizeof:編譯時操作符
// sizeof是操作符,不是函數
int arr[10];
char str[] = "Hello";// 編譯時就確定結果
size_t arr_size = sizeof(arr);      // 結果:40字節(10個int)
size_t str_size = sizeof(str);      // 結果:6字節(包含'\0')
size_t int_size = sizeof(int);      // 結果:4字節(平臺相關)
strlen:運行時庫函數
#include <string.h>char str[] = "Hello";
char* ptr = "World";// 運行時計算字符串長度
size_t len1 = strlen(str);  // 結果:5(不包含'\0')
size_t len2 = strlen(ptr);  // 結果:5(不包含'\0')

數組退化現象

void test_array(char arr[]) {// 數組作為參數時退化為指針printf("sizeof(arr) = %zu\n", sizeof(arr));    // 輸出指針大小(8字節)printf("strlen(arr) = %zu\n", strlen(arr));    // 輸出字符串長度
}int main() {char str[] = "Hello";printf("sizeof(str) = %zu\n", sizeof(str));    // 輸出:6printf("strlen(str) = %zu\n", strlen(str));    // 輸出:5test_array(str);  // 數組退化為指針return 0;
}

性能對比

  • sizeof:編譯時確定,零運行時開銷
  • strlen:需要遍歷字符串,時間復雜度O(n)

4. static關鍵字:C語言 vs C++的功能擴展

C語言中的static

// 1. 局部靜態變量:函數調用間保持值
void counter() {static int count = 0;  // 只初始化一次count++;printf("調用次數:%d\n", count);
}// 2. 全局靜態變量:限制作用域在當前文件
static int file_global = 100;  // 只在當前文件可見// 3. 靜態函數:限制函數作用域在當前文件
static void helper_function() {printf("這是一個靜態函數\n");
}

C++中的static擴展功能

class MyClass {
private:static int class_count;     // 靜態成員變量:所有對象共享int instance_id;            // 實例成員變量:每個對象獨有public:MyClass() {instance_id = ++class_count;  // 每創建一個對象,計數加1}// 靜態成員函數:不依賴具體對象實例static int get_count() {return class_count;     // 只能訪問靜態成員// return instance_id;  // 錯誤:無法訪問非靜態成員}
};// 靜態成員變量必須在類外定義
int MyClass::class_count = 0;// 使用示例
int main() {MyClass obj1, obj2, obj3;printf("創建的對象數量:%d\n", MyClass::get_count());  // 輸出:3return 0;
}

靜態變量的內存特點

  • 存儲在靜態存儲區,程序結束時才銷毀
  • 只初始化一次,后續調用保持上次的值
  • 可以在不同函數調用間傳遞信息

5. malloc vs new:C風格 vs C++風格的內存管理

基本區別對比

malloc/free:C語言風格
#include <stdlib.h>// 分配內存
int* ptr = (int*)malloc(sizeof(int) * 10);  // 分配10個int的空間
if (ptr == NULL) {printf("內存分配失敗\n");return -1;
}// 使用內存
for (int i = 0; i < 10; i++) {ptr[i] = i * i;  // 需要手動初始化
}// 釋放內存
free(ptr);      // 只釋放內存,不調用析構函數
ptr = NULL;     // 防止懸空指針
new/delete:C++風格
// 分配單個對象
int* single_ptr = new int(42);          // 分配并初始化
delete single_ptr;                      // 釋放單個對象// 分配數組
int* array_ptr = new int[10];           // 分配數組
delete[] array_ptr;                     // 釋放數組(注意使用delete[])// 分配類對象
class Person {
public:Person(const char* name) {printf("構造函數:創建 %s\n", name);}~Person() {printf("析構函數:銷毀對象\n");}
};Person* person = new Person("張三");     // 自動調用構造函數
delete person;                          // 自動調用析構函數

構造函數與析構函數的區別

class TestClass {
public:TestClass() { printf("對象被構造\n"); data = new int[100];  // 分配資源}~TestClass() { printf("對象被析構\n"); delete[] data;        // 釋放資源}
private:int* data;
};// malloc方式:不會調用構造/析構函數
TestClass* obj1 = (TestClass*)malloc(sizeof(TestClass));
// 沒有輸出"對象被構造"
free(obj1);  // 沒有輸出"對象被析構",可能導致內存泄漏// new方式:自動調用構造/析構函數
TestClass* obj2 = new TestClass();  // 輸出"對象被構造"
delete obj2;                        // 輸出"對象被析構"

混用的危險性

// 錯誤示例:不要混用
int* ptr1 = (int*)malloc(sizeof(int));
delete ptr1;    // 錯誤:malloc的內存不能用delete釋放int* ptr2 = new int;
free(ptr2);     // 錯誤:new的內存不能用free釋放

6. 宏定義的陷阱:副作用問題

標準MIN宏的實現

#define MIN(a, b) ((a) <= (b) ? (a) : (b))// 基本使用
int x = 5, y = 3;
int min_val = MIN(x, y);  // 結果:3

宏的副作用問題

#define MIN(a, b) ((a) <= (b) ? (a) : (b))int main() {int x = 5;int* p = &x;// 危險的調用方式int result = MIN(++(*p), 10);// 宏展開后變成:// int result = ((++(*p)) <= (10) ? (++(*p)) : (10));// ++(*p)被執行了兩次!printf("x = %d\n", x);      // x可能是7而不是6printf("result = %d\n", result);return 0;
}

更安全的實現方式

// 使用內聯函數替代宏(C++推薦)
inline int min_safe(int a, int b) {return (a <= b) ? a : b;
}// 或者使用臨時變量的宏(C語言)
#define MIN_SAFE(a, b) ({ \typeof(a) _a = (a); \typeof(b) _b = (b); \(_a <= _b) ? _a : _b; \
})

7. volatile關鍵字:處理不可預測的變量變化

volatile的作用機制

volatile告訴編譯器:這個變量可能被程序外部因素修改,不要進行優化。

典型應用場景

中斷服務程序
volatile int interrupt_flag = 0;  // 中斷標志// 中斷服務函數
void interrupt_handler() {interrupt_flag = 1;  // 中斷發生時設置標志
}// 主程序
int main() {while (interrupt_flag == 0) {// 等待中斷發生// 如果沒有volatile,編譯器可能優化成死循環}printf("中斷已處理\n");return 0;
}
硬件寄存器訪問
// 硬件寄存器地址
volatile unsigned int* const HARDWARE_REG = (unsigned int*)0x40000000;void read_sensor() {unsigned int value = *HARDWARE_REG;  // 每次都從硬件讀取printf("傳感器值:%u\n", value);
}
多線程共享變量
volatile bool thread_running = true;void worker_thread() {while (thread_running) {  // 確保每次都檢查最新值// 執行工作printf("線程運行中...\n");sleep(1);}printf("線程退出\n");
}void stop_thread() {thread_running = false;  // 通知線程停止
}

volatile指針的不同含義

int value = 100;// 指向volatile變量的指針
volatile int* ptr1 = &value;        // 指向的內容是volatile的
*ptr1 = 200;                        // 每次寫入都不會被優化// volatile指針指向普通變量
int* volatile ptr2 = &value;        // 指針本身是volatile的
ptr2 = &another_value;              // 指針的修改不會被優化// volatile指針指向volatile變量
volatile int* volatile ptr3 = &value; // 指針和內容都是volatile的

8. 數組名與數組地址:a vs &a的本質區別

概念解析

  • a:數組名,表示數組首元素的地址
  • &a:數組的地址,表示整個數組的地址

代碼分析實例

#include <stdio.h>int main() {int a[5] = {1, 2, 3, 4, 5};printf("a = %p\n", a);          // 數組首元素地址printf("&a = %p\n", &a);        // 整個數組的地址printf("a+1 = %p\n", a+1);      // 下一個元素地址(+4字節)printf("&a+1 = %p\n", &a+1);    // 下一個數組地址(+20字節)// 關鍵代碼分析int* ptr = (int*)(&a + 1);      // 指向數組后面的位置printf("*(a+1) = %d\n", *(a+1));    // 輸出:2(第二個元素)printf("*(ptr-1) = %d\n", *(ptr-1)); // 輸出:5(最后一個元素)return 0;
}

內存布局圖解

內存地址:  1000   1004   1008   1012   1016   1020
數組內容:  [ 1 ]  [ 2 ]  [ 3 ]  [ 4 ]  [ 5 ]↑                              ↑      ↑a                              |      &a+1&a                             |a+4或&a[4]

指針運算的區別

int a[5] = {1, 2, 3, 4, 5};// a+1:移動一個int的大小(4字節)
int* p1 = a + 1;        // 指向a[1]// &a+1:移動整個數組的大小(20字節)
int* p2 = (int*)(&a + 1); // 指向數組后面的位置// 驗證
printf("p1指向的值:%d\n", *p1);      // 輸出:2
printf("p2-1指向的值:%d\n", *(p2-1)); // 輸出:5

9. C/C++程序內存布局:五大存儲區域詳解

內存分區概述

C/C++程序運行時,內存被劃分為5個主要區域,每個區域有不同的特點和用途。

1. 程序代碼區(Text Segment)

// 存儲編譯后的機器代碼
void function1() {printf("這個函數的代碼存儲在代碼區\n");
}int main() {printf("main函數的代碼也在代碼區\n");return 0;
}

特點:只讀、共享、程序加載時確定大小

2. 全局/靜態存儲區(Data Segment)

// 已初始化的全局變量
int global_var = 100;           // 存儲在已初始化數據區// 未初始化的全局變量
int uninitialized_global;       // 存儲在BSS區(自動初始化為0)// 靜態變量
static int static_var = 200;    // 存儲在已初始化數據區void function() {static int local_static;    // 存儲在BSS區
}

特點:程序運行期間一直存在、自動初始化為0(BSS區)

3. 棧區(Stack)

void stack_demo() {int local_var = 10;         // 局部變量,存儲在棧上char buffer[1024];          // 局部數組,存儲在棧上printf("local_var地址:%p\n", &local_var);printf("buffer地址:%p\n", buffer);// 函數結束時,這些變量自動銷毀
}void recursive_function(int n) {int local = n;              // 每次遞歸調用都在棧上分配if (n > 0) {recursive_function(n - 1);}
}

特點

  • 自動管理(函數結束自動釋放)
  • 訪問速度快
  • 大小有限(通常幾MB)
  • 后進先出(LIFO)

4. 堆區(Heap)

void heap_demo() {// 動態分配內存int* heap_ptr = (int*)malloc(sizeof(int) * 100);if (heap_ptr == NULL) {printf("內存分配失敗\n");return;}// 使用堆內存for (int i = 0; i < 100; i++) {heap_ptr[i] = i;}// 必須手動釋放free(heap_ptr);heap_ptr = NULL;  // 防止懸空指針
}void cpp_heap_demo() {// C++風格的堆內存管理int* ptr = new int[100];    // 分配// 使用內存...delete[] ptr;               // 釋放
}

特點

  • 手動管理(程序員負責分配和釋放)
  • 大小靈活
  • 訪問速度相對較慢
  • 容易產生內存泄漏和碎片

5. 文字常量區(String Literal Pool)

void string_demo() {char* str1 = "Hello World";     // 字符串存儲在常量區char* str2 = "Hello World";     // 可能與str1指向同一地址char arr[] = "Hello World";     // 字符串復制到棧上printf("str1地址:%p\n", str1);printf("str2地址:%p\n", str2);printf("arr地址:%p\n", arr);// str1[0] = 'h';  // 錯誤:不能修改常量區內容arr[0] = 'h';      // 正確:可以修改棧上的副本
}

內存布局示意圖

高地址
┌─────────────────┐
│     棧區        │ ← 向下增長
│   (局部變量)     │
├─────────────────┤
│       ↓         │
│                 │
│       ↑         │
├─────────────────┤
│     堆區        │ ← 向上增長
│   (動態分配)     │
├─────────────────┤
│   未初始化數據   │
│    (BSS段)      │
├─────────────────┤
│   已初始化數據   │
│   (Data段)      │
├─────────────────┤
│   文字常量區     │
├─────────────────┤
│    程序代碼區    │
└─────────────────┘
低地址

10. 字符串操作函數對比:strcpy、sprintf、memcpy

功能定位分析

strcpy:字符串到字符串的復制
#include <string.h>void strcpy_demo() {char source[] = "Hello World";char destination[20];// 復制字符串(包括結尾的'\0')strcpy(destination, source);printf("復制結果:%s\n", destination);// 注意:不檢查目標緩沖區大小,可能溢出// 更安全的版本:strncpystrncpy(destination, source, sizeof(destination) - 1);destination[sizeof(destination) - 1] = '\0';  // 確保以'\0'結尾
}
sprintf:格式化輸出到字符串
#include <stdio.h>void sprintf_demo() {char buffer[100];int age = 25;float height = 175.5f;char name[] = "張三";// 將多種數據類型格式化為字符串sprintf(buffer, "姓名:%s,年齡:%d,身高:%.1f厘米", name, age, height);printf("格式化結果:%s\n", buffer);// 更安全的版本:snprintfsnprintf(buffer, sizeof(buffer), "安全的格式化:%s", name);
}
memcpy:內存塊到內存塊的復制
#include <string.h>void memcpy_demo() {// 復制整數數組int source[] = {1, 2, 3, 4, 5};int destination[5];memcpy(destination, source, sizeof(source));// 復制結構體struct Person {char name[20];int age;};struct Person p1 = {"李四", 30};struct Person p2;memcpy(&p2, &p1, sizeof(struct Person));printf("復制的結構體:%s, %d\n", p2.name, p2.age);// 復制部分內存char str1[] = "Hello World";char str2[20];memcpy(str2, str1, 5);  // 只復制前5個字符str2[5] = '\0';         // 手動添加結束符printf("部分復制:%s\n", str2);  // 輸出:Hello
}

性能對比測試

#include <time.h>void performance_test() {const int TEST_SIZE = 1000000;char source[1000];char destination[1000];clock_t start, end;// 初始化源數據memset(source, 'A', sizeof(source) - 1);source[sizeof(source) - 1] = '\0';// 測試memcpy性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {memcpy(destination, source, sizeof(source));}end = clock();printf("memcpy耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 測試strcpy性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {strcpy(destination, source);}end = clock();printf("strcpy耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 測試sprintf性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {sprintf(destination, "%s", source);}end = clock();printf("sprintf耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
}

使用場景選擇指南

  • strcpy:純字符串復制,需要自動處理’\0’結尾
  • sprintf:需要格式化多種數據類型為字符串
  • memcpy:原始內存復制,最高效,適合大塊數據

11. 直接內存操作:指定地址賦值技術

基本概念

在嵌入式開發或系統編程中,經常需要直接操作特定內存地址的數據。

實現方法

void memory_operation_demo() {// 將整數值0xaa66寫入地址0x67a9int* ptr;                    // 聲明整型指針ptr = (int*)0x67a9;         // 將地址強制轉換為整型指針*ptr = 0xaa66;              // 向該地址寫入數據// 讀取驗證printf("地址0x67a9的值:0x%x\n", *ptr);
}

實際應用場景

硬件寄存器操作
// 定義硬件寄存器地址
#define GPIO_BASE_ADDR    0x40020000
#define GPIO_OUTPUT_REG   (GPIO_BASE_ADDR + 0x14)
#define GPIO_INPUT_REG    (GPIO_BASE_ADDR + 0x10)void gpio_control() {// 控制GPIO輸出volatile unsigned int* gpio_output = (volatile unsigned int*)GPIO_OUTPUT_REG;*gpio_output = 0xFF;  // 設置所有引腳為高電平// 讀取GPIO輸入volatile unsigned int* gpio_input = (volatile unsigned int*)GPIO_INPUT_REG;unsigned int input_value = *gpio_input;printf("GPIO輸入值:0x%x\n", input_value);
}
內存映射文件操作
#include <sys/mman.h>
#include <fcntl.h>void memory_mapped_file() {int fd = open("data.bin", O_RDWR);if (fd == -1) return;// 將文件映射到內存void* mapped_addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped_addr != MAP_FAILED) {// 直接操作內存就是操作文件int* data_ptr = (int*)mapped_addr;*data_ptr = 0x12345678;  // 寫入數據到文件// 解除映射munmap(mapped_addr, 1024);}close(fd);
}

安全注意事項

void safe_memory_access() {// 1. 檢查地址有效性void* addr = (void*)0x67a9;if (addr == NULL) {printf("無效地址\n");return;}// 2. 使用volatile防止編譯器優化volatile int* ptr = (volatile int*)addr;// 3. 異常處理(在支持的系統上)try {*ptr = 0xaa66;} catch (...) {printf("內存訪問異常\n");}
}

12. 面向對象三大特征深度解析

1. 封裝性(Encapsulation):數據隱藏與接口設計

基本概念

封裝是將數據和操作數據的方法組合在一起,通過訪問控制來隱藏內部實現細節。

class BankAccount {
private:double balance;          // 私有數據:外部無法直接訪問string account_number;   // 私有數據:賬戶安全信息public:// 公有接口:提供安全的訪問方式BankAccount(string acc_num, double initial_balance) {account_number = acc_num;balance = initial_balance;}// 存款操作:控制數據修改方式bool deposit(double amount) {if (amount > 0) {balance += amount;return true;}return false;  // 拒絕無效操作}// 取款操作:包含業務邏輯驗證bool withdraw(double amount) {if (amount > 0 && amount <= balance) {balance -= amount;return true;}return false;  // 余額不足或金額無效}// 查詢余額:只讀訪問double get_balance() const {return balance;}
};
訪問控制級別
class AccessDemo {
private:int private_data;        // 只有類內部可以訪問protected:int protected_data;      // 類內部和子類可以訪問public:int public_data;         // 任何地方都可以訪問void demo_access() {private_data = 1;    // 正確:類內部訪問protected_data = 2;  // 正確:類內部訪問public_data = 3;     // 正確:類內部訪問}
};class DerivedClass : public AccessDemo {
public:void test_access() {// private_data = 1;     // 錯誤:無法訪問私有成員protected_data = 2;      // 正確:子類可以訪問保護成員public_data = 3;         // 正確:公有成員任何地方可訪問}
};

2. 繼承性(Inheritance):代碼復用與層次結構

基本繼承概念
// 基類:動物
class Animal {
protected:string name;int age;public:Animal(string n, int a) : name(n), age(a) {}// 虛函數:允許子類重寫virtual void make_sound() {cout << name << "發出聲音" << endl;}// 普通成員函數void eat() {cout << name << "正在吃東西" << endl;}virtual ~Animal() {}  // 虛析構函數
};// 派生類:狗
class Dog : public Animal {
private:string breed;  // 狗特有的屬性public:Dog(string n, int a, string b) : Animal(n, a), breed(b) {}// 重寫基類的虛函數virtual void make_sound() override {cout << name << "汪汪叫" << endl;}// 狗特有的行為void wag_tail() {cout << name << "搖尾巴" << endl;}
};// 派生類:貓
class Cat : public Animal {
public:Cat(string n, int a) : Animal(n, a) {}virtual void make_sound() override {cout << name << "喵喵叫" << endl;}void climb_tree() {cout << name << "爬樹" << endl;}
};
繼承的三種方式
class Base {
public:    int pub_member;
protected: int prot_member;
private:   int priv_member;
};// 公有繼承:保持訪問級別
class PublicDerived : public Base {// pub_member  -> public// prot_member -> protected// priv_member -> 不可訪問
};// 保護繼承:公有成員變為保護
class ProtectedDerived : protected Base {// pub_member  -> protected// prot_member -> protected// priv_member -> 不可訪問
};// 私有繼承:所有成員變為私有
class PrivateDerived : private Base {// pub_member  -> private// prot_member -> private// priv_member -> 不可訪問
};

3. 多態性(Polymorphism):一個接口多種實現

運行時多態(動態多態)
void demonstrate_polymorphism() {// 創建不同類型的動物對象Animal* animals[] = {new Dog("旺財", 3, "金毛"),new Cat("咪咪", 2),new Dog("小黑", 5, "土狗")};// 多態調用:同一接口,不同實現for (int i = 0; i < 3; i++) {animals[i]->make_sound();  // 根據實際對象類型調用相應函數animals[i]->eat();         // 調用基類函數}// 清理內存for (int i = 0; i < 3; i++) {delete animals[i];}
}
編譯時多態(靜態多態)
class Calculator {
public:// 函數重載:同名函數,不同參數int add(int a, int b) {return a + b;}double add(double a, double b) {return a + b;}int add(int a, int b, int c) {return a + b + c;}
};// 模板實現編譯時多態
template<typename T>
T generic_add(T a, T b) {return a + b;
}void polymorphism_demo() {Calculator calc;// 編譯器根據參數類型選擇合適的函數cout << calc.add(1, 2) << endl;        // 調用int版本cout << calc.add(1.5, 2.5) << endl;    // 調用double版本cout << calc.add(1, 2, 3) << endl;     // 調用三參數版本// 模板多態cout << generic_add(10, 20) << endl;      // int版本cout << generic_add(1.1, 2.2) << endl;   // double版本
}

13. C++空類的隱式成員函數

編譯器自動生成的六個函數

當定義一個空類時,編譯器會自動生成以下成員函數:

class EmptyClass {// 編譯器自動生成以下函數:// 1. 默認構造函數// EmptyClass() {}// 2. 拷貝構造函數// EmptyClass(const EmptyClass& other) {}// 3. 析構函數// ~EmptyClass() {}// 4. 賦值運算符// EmptyClass& operator=(const EmptyClass& other) { return *this; }// 5. 取址運算符// EmptyClass* operator&() { return this; }// 6. const取址運算符// const EmptyClass* operator&() const { return this; }
};

實際驗證示例

void test_empty_class() {EmptyClass obj1;              // 調用默認構造函數EmptyClass obj2(obj1);        // 調用拷貝構造函數EmptyClass obj3;obj3 = obj1;                  // 調用賦值運算符EmptyClass* ptr1 = &obj1;     // 調用取址運算符const EmptyClass* ptr2 = &obj1; // 調用const取址運算符// 析構函數在對象生命周期結束時自動調用
}

何時需要自定義這些函數

class ResourceClass {
private:int* data;size_t size;public:// 必須自定義構造函數ResourceClass(size_t s) : size(s) {data = new int[size];cout << "構造函數:分配了 " << size << " 個整數的內存" << endl;}// 必須自定義拷貝構造函數(深拷貝)ResourceClass(const ResourceClass& other) : size(other.size) {data = new int[size];memcpy(data, other.data, size * sizeof(int));cout << "拷貝構造函數:深拷貝" << endl;}// 必須自定義賦值運算符ResourceClass& operator=(const ResourceClass& other) {if (this != &other) {  // 防止自賦值delete[] data;     // 釋放原有資源size = other.size;data = new int[size];memcpy(data, other.data, size * sizeof(int));cout << "賦值運算符:深拷貝" << endl;}return *this;}// 必須自定義析構函數~ResourceClass() {delete[] data;cout << "析構函數:釋放內存" << endl;}
};

14. 拷貝構造函數 vs 賦值運算符

本質區別分析

調用時機不同
class TestClass {
public:int value;TestClass(int v) : value(v) {cout << "構造函數:創建對象,值=" << value << endl;}TestClass(const TestClass& other) : value(other.value) {cout << "拷貝構造函數:從現有對象創建新對象,值=" << value << endl;}TestClass& operator=(const TestClass& other) {cout << "賦值運算符:修改現有對象,從" << value << "改為" << other.value << endl;value = other.value;return *this;}
};void copy_vs_assignment_demo() {TestClass obj1(10);           // 調用構造函數TestClass obj2(obj1);         // 調用拷貝構造函數(創建新對象)TestClass obj3 = obj1;        // 調用拷貝構造函數(不是賦值!)TestClass obj4(20);           // 調用構造函數obj4 = obj1;                  // 調用賦值運算符(修改現有對象)
}
內存管理的區別
class StringClass {
private:char* str;size_t length;public:StringClass(const char* s) {length = strlen(s);str = new char[length + 1];strcpy(str, s);cout << "構造:" << str << endl;}// 拷貝構造函數:為新對象分配內存StringClass(const StringClass& other) {length = other.length;str = new char[length + 1];      // 分配新內存strcpy(str, other.str);cout << "拷貝構造:" << str << endl;}// 賦值運算符:需要處理現有內存StringClass& operator=(const StringClass& other) {if (this != &other) {            // 防止自賦值delete[] str;                // 釋放原有內存length = other.length;str = new char[length + 1];  // 分配新內存strcpy(str, other.str);cout << "賦值:" << str << endl;}return *this;}~StringClass() {cout << "析構:" << str << endl;delete[] str;}
};

自賦值問題的處理

StringClass& operator=(const StringClass& other) {// 方法1:檢查自賦值if (this == &other) {return *this;}// 方法2:異常安全的實現char* temp = new char[other.length + 1];  // 先分配新內存strcpy(temp, other.str);delete[] str;        // 釋放原內存str = temp;          // 指向新內存length = other.length;return *this;
}

15. 設計不可繼承的類

使用模板和友元的方法

template <typename T>
class NonInheritable {friend T;  // 只有T類型可以訪問私有構造函數private:NonInheritable() {}   // 私有構造函數~NonInheritable() {}  // 私有析構函數
};// 可以實例化的類
class FinalClass : virtual public NonInheritable<FinalClass> {
public:FinalClass() {}   // 可以調用基類的私有構造函數(因為是友元)~FinalClass() {}
};// 嘗試繼承會失敗的類
class AttemptInherit : public FinalClass {
public:AttemptInherit() {}  // 編譯錯誤:無法訪問NonInheritable的構造函數~AttemptInherit() {}
};void test_inheritance() {FinalClass obj;      // 正確:可以創建對象// AttemptInherit obj2; // 編譯錯誤:無法繼承
}

C++11的final關鍵字(推薦方法)

// 現代C++的簡單方法
class FinalClass final {  // final關鍵字阻止繼承
public:FinalClass() {cout << "FinalClass構造函數" << endl;}void do_something() {cout << "執行某些操作" << endl;}
};// 編譯錯誤:無法繼承final類
// class DerivedClass : public FinalClass {};void modern_final_demo() {FinalClass obj;obj.do_something();
}

16. 虛函數表機制深度解析

虛函數表的工作原理

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }void func3() { cout << "Base::func3" << endl; }  // 非虛函數
};class Derived : public Base {
public:virtual void func1() override { cout << "Derived::func1" << endl; }virtual void func4() { cout << "Derived::func4" << endl; }
};

內存布局分析

Base對象內存布局:
┌─────────────────┐
│   vptr (8字節)   │ ──→ Base虛函數表
└─────────────────┘     ┌─────────────────┐│ &Base::func1    ││ &Base::func2    │└─────────────────┘Derived對象內存布局:
┌─────────────────┐
│   vptr (8字節)   │ ──→ Derived虛函數表
└─────────────────┘     ┌─────────────────┐│ &Derived::func1 │ (重寫)│ &Base::func2    │ (繼承)│ &Derived::func4 │ (新增)└─────────────────┘

虛函數調用過程演示

void virtual_function_demo() {Base* ptr1 = new Base();Base* ptr2 = new Derived();// 虛函數調用:通過虛函數表ptr1->func1();  // 1. 獲取ptr1的vptr// 2. 在虛函數表中查找func1// 3. 調用Base::func1ptr2->func1();  // 1. 獲取ptr2的vptr// 2. 在虛函數表中查找func1// 3. 調用Derived::func1// 非虛函數調用:編譯時確定ptr1->func3();  // 直接調用Base::func3ptr2->func3();  // 直接調用Base::func3delete ptr1;delete ptr2;
}

訪問虛函數表的技巧(僅用于理解原理)

void access_vtable() {Derived obj;// 獲取對象的虛函數表指針void** vtable = *(void***)&obj;// 調用虛函數表中的函數typedef void(*FuncPtr)();for (int i = 0; i < 3; i++) {FuncPtr func = (FuncPtr)vtable[i];cout << "調用虛函數表第" << i << "個函數:";// 注意:這種方式調用需要傳遞this指針,實際實現更復雜}
}

17. 函數重寫、重載、隱藏的區別

重載(Overloading):同一作用域內的函數多態

class Calculator {
public:// 函數重載:函數名相同,參數不同int add(int a, int b) {cout << "兩個整數相加" << endl;return a + b;}double add(double a, double b) {cout << "兩個浮點數相加" << endl;return a + b;}int add(int a, int b, int c) {cout << "三個整數相加" << endl;return a + b + c;}// 編譯錯誤:僅返回類型不同不能重載// double add(int a, int b) { return a + b; }
};

重寫(Override):繼承關系中的虛函數替換

class Shape {
public:virtual double area() {  // 虛函數cout << "Shape::area()" << endl;return 0.0;}virtual void draw() {    // 虛函數cout << "Shape::draw()" << endl;}
};class Circle : public Shape {
private:double radius;public:Circle(double r) : radius(r) {}// 重寫基類虛函數virtual double area() override {cout << "Circle::area()" << endl;return 3.14159 * radius * radius;}virtual void draw() override {cout << "Circle::draw()" << endl;}
};

隱藏(Hiding):派生類函數隱藏基類同名函數

class Base {
public:void func() {cout << "Base::func()" << endl;}void func(int x) {cout << "Base::func(int)" << endl;}virtual void virtual_func() {cout << "Base::virtual_func()" << endl;}
};class Derived : public Base {
public:// 隱藏基類的所有同名函數(包括重載版本)void func(double x) {cout << "Derived::func(double)" << endl;}// 隱藏基類虛函數(參數不同,不是重寫)void virtual_func(int x) {cout << "Derived::virtual_func(int)" << endl;}
};void hiding_demo() {Derived obj;obj.func(1.5);      // 調用Derived::func(double)// obj.func();      // 編譯錯誤:Base::func()被隱藏// obj.func(1);     // 編譯錯誤:Base::func(int)被隱藏// 使用作用域解析符訪問被隱藏的函數obj.Base::func();   // 調用Base::func()obj.Base::func(1);  // 調用Base::func(int)
}

三者對比總結表

特征重載(Overloading)重寫(Override)隱藏(Hiding)
作用域同一類中基類和派生類基類和派生類
函數名相同相同相同
參數列表必須不同必須相同可同可不同
virtual關鍵字可有可無基類必須有可有可無
綁定時機編譯時運行時編譯時
多態性靜態多態動態多態無多態

18. 多態實現原理:虛函數表詳解

虛函數表的創建時機

class Animal {
public:Animal() {cout << "Animal構造函數:虛函數表指針已設置" << endl;// 此時vptr指向Animal的虛函數表}virtual void speak() {cout << "Animal發出聲音" << endl;}virtual ~Animal() {cout << "Animal析構函數" << endl;}
};class Dog : public Animal {
public:Dog() {cout << "Dog構造函數:虛函數表指針已更新" << endl;// 此時vptr指向Dog的虛函數表}virtual void speak() override {cout << "狗汪汪叫" << endl;}virtual ~Dog() {cout << "Dog析構函數" << endl;}
};

動態綁定的實現過程

void polymorphism_mechanism() {cout << "=== 多態機制演示 ===" << endl;Animal* animals[] = {new Animal(),new Dog()};for (int i = 0; i < 2; i++) {cout << "\n調用第" << i+1 << "個對象的speak()方法:" << endl;// 編譯器生成的代碼等價于:// 1. 獲取對象的虛函數表指針// 2. 在虛函數表中查找speak函數的地址// 3. 調用該地址對應的函數animals[i]->speak();}// 清理內存for (int i = 0; i < 2; i++) {delete animals[i];  // 虛析構函數確保正確析構}
}

虛函數的性能開銷

class PerformanceTest {
public:// 普通函數調用void normal_function() {// 直接函數調用,無額外開銷}// 虛函數調用virtual void virtual_function() {// 需要通過虛函數表間接調用,有輕微開銷}
};void performance_comparison() {const int ITERATIONS = 10000000;PerformanceTest obj;PerformanceTest* ptr = &obj;// 測試普通函數調用性能auto start = chrono::high_resolution_clock::now();for (int i = 0; i < ITERATIONS; i++) {obj.normal_function();}auto end = chrono::high_resolution_clock::now();auto normal_time = chrono::duration_cast<chrono::microseconds>(end - start);// 測試虛函數調用性能start = chrono::high_resolution_clock::now();for (int i = 0; i < ITERATIONS; i++) {ptr->virtual_function();}end = chrono::high_resolution_clock::now();auto virtual_time = chrono::duration_cast<chrono::microseconds>(end - start);cout << "普通函數調用時間:" << normal_time.count() << "微秒" << endl;cout << "虛函數調用時間:" << virtual_time.count() << "微秒" << endl;
}

19. 數組 vs 鏈表:數據結構選擇指南

內存布局對比

數組的連續存儲
void array_memory_layout() {int arr[5] = {10, 20, 30, 40, 50};cout << "數組內存布局:" << endl;for (int i = 0; i < 5; i++) {cout << "arr[" << i << "] = " << arr[i] << ", 地址:" << &arr[i] << endl;}// 地址連續,相鄰元素地址差為sizeof(int)
}
鏈表的分散存儲
struct ListNode {int data;           // 數據域ListNode* next;     // 指針域ListNode(int val) : data(val), next(nullptr) {}
};class LinkedList {
private:ListNode* head;public:LinkedList() : head(nullptr) {}void insert(int val) {ListNode* new_node = new ListNode(val);  // 動態分配,地址不連續new_node->next = head;head = new_node;}void print_addresses() {cout << "鏈表節點地址:" << endl;ListNode* current = head;int index = 0;while (current) {cout << "節點" << index << ": 數據=" << current->data << ", 地址=" << current << endl;current = current->next;index++;}}~LinkedList() {while (head) {ListNode* temp = head;head = head->next;delete temp;}}
};

操作性能對比

隨機訪問性能
void random_access_test() {const int SIZE = 100000;// 數組隨機訪問:O(1)vector<int> arr(SIZE);for (int i = 0; i < SIZE; i++) {arr[i] = i;}auto start = chrono::high_resolution_clock::now();for (int i = 0; i < 10000; i++) {int index = rand() % SIZE;int value = arr[index];  // 直接通過索引訪問}auto end = chrono::high_resolution_clock::now();auto array_time = chrono::duration_cast<chrono::microseconds>(end - start);// 鏈表隨機訪問:O(n)LinkedList list;for (int i = 0; i < SIZE; i++) {list.insert(i);}start = chrono::high_resolution_clock::now();for (int i = 0; i < 100; i++) {  // 減少測試次數,因為鏈表訪問很慢int index = rand() % SIZE;// 需要從頭遍歷到指定位置ListNode* current = list.head;for (int j = 0; j < index && current; j++) {current = current->next;}}end = chrono::high_resolution_clock::now();auto list_time = chrono::duration_cast<chrono::microseconds>(end - start);cout << "數組隨機訪問時間:" << array_time.count() << "微秒" << endl;cout << "鏈表隨機訪問時間:" << list_time.count() << "微秒" << endl;
}

C/C++面試核心知識點詳解

1. 變量的聲明與定義:內存分配的本質區別

核心概念

在C/C++中,變量的聲明定義是兩個完全不同的概念:

  • 聲明(Declaration):告訴編譯器變量的名稱和類型,但不分配內存空間
  • 定義(Definition):不僅聲明變量,還為其分配實際的內存空間

實際應用示例

// 變量聲明:使用extern關鍵字,不分配內存
extern int global_var;  // 聲明一個整型變量,告訴編譯器這個變量在別處定義// 變量定義:分配內存空間
int global_var = 100;   // 定義變量并分配內存,可以初始化// 函數聲明
int add(int a, int b);  // 函數聲明,不包含函數體// 函數定義
int add(int a, int b) { // 函數定義,包含完整實現return a + b;
}

重要規則

  • 一個變量可以在多個地方聲明,但只能在一個地方定義
  • 聲明可以重復,定義不能重復
  • 使用變量前必須先聲明,使用時才需要定義

2. 不同數據類型與零值比較的標準寫法

為什么要規范比較寫法?

不同數據類型與零值比較時,寫法不當可能導致邏輯錯誤或程序崩潰。

標準比較方式

bool類型:直接判斷真假
bool is_valid = true;// 正確寫法:直接使用布爾值
if (is_valid) {// 條件為真時執行printf("數據有效\n");
} else {// 條件為假時執行printf("數據無效\n");
}
int類型:與0比較
int count = 10;// 正確寫法:將常量放在左邊(防御性編程)
if (0 != count) {// count不等于0時執行printf("計數值為:%d\n", count);
} else {// count等于0時執行printf("計數為零\n");
}
指針類型:與NULL比較
int* ptr = nullptr;// 正確寫法:將NULL放在左邊
if (NULL == ptr) {// 指針為空時執行printf("指針為空\n");
} else {// 指針不為空時執行printf("指針指向的值:%d\n", *ptr);
}
float類型:使用精度范圍比較
float value = 0.0001f;
const float EPSILON = 1e-6f;  // 定義精度閾值// 正確寫法:判斷是否在精度范圍內
if ((value >= -EPSILON) && (value <= EPSILON)) {// 認為等于零printf("浮點數接近零\n");
} else {// 不等于零printf("浮點數值:%f\n", value);
}

防御性編程技巧

將常量放在比較運算符左邊的好處:

// 錯誤示例:容易寫錯
if (count = 0) {  // 誤將==寫成=,編譯通過但邏輯錯誤// 永遠不會執行
}// 正確示例:編譯器會報錯
if (0 = count) {  // 編譯錯誤,無法給常量賦值// 編譯器直接報錯,避免邏輯錯誤
}

3. sizeof與strlen:編譯時計算 vs 運行時計算

本質區別分析

sizeof:編譯時操作符
// sizeof是操作符,不是函數
int arr[10];
char str[] = "Hello";// 編譯時就確定結果
size_t arr_size = sizeof(arr);      // 結果:40字節(10個int)
size_t str_size = sizeof(str);      // 結果:6字節(包含'\0')
size_t int_size = sizeof(int);      // 結果:4字節(平臺相關)
strlen:運行時庫函數
#include <string.h>char str[] = "Hello";
char* ptr = "World";// 運行時計算字符串長度
size_t len1 = strlen(str);  // 結果:5(不包含'\0')
size_t len2 = strlen(ptr);  // 結果:5(不包含'\0')

數組退化現象

void test_array(char arr[]) {// 數組作為參數時退化為指針printf("sizeof(arr) = %zu\n", sizeof(arr));    // 輸出指針大小(8字節)printf("strlen(arr) = %zu\n", strlen(arr));    // 輸出字符串長度
}int main() {char str[] = "Hello";printf("sizeof(str) = %zu\n", sizeof(str));    // 輸出:6printf("strlen(str) = %zu\n", strlen(str));    // 輸出:5test_array(str);  // 數組退化為指針return 0;
}

性能對比

  • sizeof:編譯時確定,零運行時開銷
  • strlen:需要遍歷字符串,時間復雜度O(n)

4. static關鍵字:C語言 vs C++的功能擴展

C語言中的static

// 1. 局部靜態變量:函數調用間保持值
void counter() {static int count = 0;  // 只初始化一次count++;printf("調用次數:%d\n", count);
}// 2. 全局靜態變量:限制作用域在當前文件
static int file_global = 100;  // 只在當前文件可見// 3. 靜態函數:限制函數作用域在當前文件
static void helper_function() {printf("這是一個靜態函數\n");
}

C++中的static擴展功能

class MyClass {
private:static int class_count;     // 靜態成員變量:所有對象共享int instance_id;            // 實例成員變量:每個對象獨有public:MyClass() {instance_id = ++class_count;  // 每創建一個對象,計數加1}// 靜態成員函數:不依賴具體對象實例static int get_count() {return class_count;     // 只能訪問靜態成員// return instance_id;  // 錯誤:無法訪問非靜態成員}
};// 靜態成員變量必須在類外定義
int MyClass::class_count = 0;// 使用示例
int main() {MyClass obj1, obj2, obj3;printf("創建的對象數量:%d\n", MyClass::get_count());  // 輸出:3return 0;
}

靜態變量的內存特點

  • 存儲在靜態存儲區,程序結束時才銷毀
  • 只初始化一次,后續調用保持上次的值
  • 可以在不同函數調用間傳遞信息

5. malloc vs new:C風格 vs C++風格的內存管理

基本區別對比

malloc/free:C語言風格
#include <stdlib.h>// 分配內存
int* ptr = (int*)malloc(sizeof(int) * 10);  // 分配10個int的空間
if (ptr == NULL) {printf("內存分配失敗\n");return -1;
}// 使用內存
for (int i = 0; i < 10; i++) {ptr[i] = i * i;  // 需要手動初始化
}// 釋放內存
free(ptr);      // 只釋放內存,不調用析構函數
ptr = NULL;     // 防止懸空指針
new/delete:C++風格
// 分配單個對象
int* single_ptr = new int(42);          // 分配并初始化
delete single_ptr;                      // 釋放單個對象// 分配數組
int* array_ptr = new int[10];           // 分配數組
delete[] array_ptr;                     // 釋放數組(注意使用delete[])// 分配類對象
class Person {
public:Person(const char* name) {printf("構造函數:創建 %s\n", name);}~Person() {printf("析構函數:銷毀對象\n");}
};Person* person = new Person("張三");     // 自動調用構造函數
delete person;                          // 自動調用析構函數

構造函數與析構函數的區別

class TestClass {
public:TestClass() { printf("對象被構造\n"); data = new int[100];  // 分配資源}~TestClass() { printf("對象被析構\n"); delete[] data;        // 釋放資源}
private:int* data;
};// malloc方式:不會調用構造/析構函數
TestClass* obj1 = (TestClass*)malloc(sizeof(TestClass));
// 沒有輸出"對象被構造"
free(obj1);  // 沒有輸出"對象被析構",可能導致內存泄漏// new方式:自動調用構造/析構函數
TestClass* obj2 = new TestClass();  // 輸出"對象被構造"
delete obj2;                        // 輸出"對象被析構"

混用的危險性

// 錯誤示例:不要混用
int* ptr1 = (int*)malloc(sizeof(int));
delete ptr1;    // 錯誤:malloc的內存不能用delete釋放int* ptr2 = new int;
free(ptr2);     // 錯誤:new的內存不能用free釋放

6. 宏定義的陷阱:副作用問題

標準MIN宏的實現

#define MIN(a, b) ((a) <= (b) ? (a) : (b))// 基本使用
int x = 5, y = 3;
int min_val = MIN(x, y);  // 結果:3

宏的副作用問題

#define MIN(a, b) ((a) <= (b) ? (a) : (b))int main() {int x = 5;int* p = &x;// 危險的調用方式int result = MIN(++(*p), 10);// 宏展開后變成:// int result = ((++(*p)) <= (10) ? (++(*p)) : (10));// ++(*p)被執行了兩次!printf("x = %d\n", x);      // x可能是7而不是6printf("result = %d\n", result);return 0;
}

更安全的實現方式

// 使用內聯函數替代宏(C++推薦)
inline int min_safe(int a, int b) {return (a <= b) ? a : b;
}// 或者使用臨時變量的宏(C語言)
#define MIN_SAFE(a, b) ({ \typeof(a) _a = (a); \typeof(b) _b = (b); \(_a <= _b) ? _a : _b; \
})

7. volatile關鍵字:處理不可預測的變量變化

volatile的作用機制

volatile告訴編譯器:這個變量可能被程序外部因素修改,不要進行優化。

典型應用場景

中斷服務程序
volatile int interrupt_flag = 0;  // 中斷標志// 中斷服務函數
void interrupt_handler() {interrupt_flag = 1;  // 中斷發生時設置標志
}// 主程序
int main() {while (interrupt_flag == 0) {// 等待中斷發生// 如果沒有volatile,編譯器可能優化成死循環}printf("中斷已處理\n");return 0;
}
硬件寄存器訪問
// 硬件寄存器地址
volatile unsigned int* const HARDWARE_REG = (unsigned int*)0x40000000;void read_sensor() {unsigned int value = *HARDWARE_REG;  // 每次都從硬件讀取printf("傳感器值:%u\n", value);
}
多線程共享變量
volatile bool thread_running = true;void worker_thread() {while (thread_running) {  // 確保每次都檢查最新值// 執行工作printf("線程運行中...\n");sleep(1);}printf("線程退出\n");
}void stop_thread() {thread_running = false;  // 通知線程停止
}

volatile指針的不同含義

int value = 100;// 指向volatile變量的指針
volatile int* ptr1 = &value;        // 指向的內容是volatile的
*ptr1 = 200;                        // 每次寫入都不會被優化// volatile指針指向普通變量
int* volatile ptr2 = &value;        // 指針本身是volatile的
ptr2 = &another_value;              // 指針的修改不會被優化// volatile指針指向volatile變量
volatile int* volatile ptr3 = &value; // 指針和內容都是volatile的

8. 數組名與數組地址:a vs &a的本質區別

概念解析

  • a:數組名,表示數組首元素的地址
  • &a:數組的地址,表示整個數組的地址

代碼分析實例

#include <stdio.h>int main() {int a[5] = {1, 2, 3, 4, 5};printf("a = %p\n", a);          // 數組首元素地址printf("&a = %p\n", &a);        // 整個數組的地址printf("a+1 = %p\n", a+1);      // 下一個元素地址(+4字節)printf("&a+1 = %p\n", &a+1);    // 下一個數組地址(+20字節)// 關鍵代碼分析int* ptr = (int*)(&a + 1);      // 指向數組后面的位置printf("*(a+1) = %d\n", *(a+1));    // 輸出:2(第二個元素)printf("*(ptr-1) = %d\n", *(ptr-1)); // 輸出:5(最后一個元素)return 0;
}

內存布局圖解

內存地址:  1000   1004   1008   1012   1016   1020
數組內容:  [ 1 ]  [ 2 ]  [ 3 ]  [ 4 ]  [ 5 ]↑                              ↑      ↑a                              |      &a+1&a                             |a+4或&a[4]

指針運算的區別

int a[5] = {1, 2, 3, 4, 5};// a+1:移動一個int的大小(4字節)
int* p1 = a + 1;        // 指向a[1]// &a+1:移動整個數組的大小(20字節)
int* p2 = (int*)(&a + 1); // 指向數組后面的位置// 驗證
printf("p1指向的值:%d\n", *p1);      // 輸出:2
printf("p2-1指向的值:%d\n", *(p2-1)); // 輸出:5

9. C/C++程序內存布局:五大存儲區域詳解

內存分區概述

C/C++程序運行時,內存被劃分為5個主要區域,每個區域有不同的特點和用途。

1. 程序代碼區(Text Segment)

// 存儲編譯后的機器代碼
void function1() {printf("這個函數的代碼存儲在代碼區\n");
}int main() {printf("main函數的代碼也在代碼區\n");return 0;
}

特點:只讀、共享、程序加載時確定大小

2. 全局/靜態存儲區(Data Segment)

// 已初始化的全局變量
int global_var = 100;           // 存儲在已初始化數據區// 未初始化的全局變量
int uninitialized_global;       // 存儲在BSS區(自動初始化為0)// 靜態變量
static int static_var = 200;    // 存儲在已初始化數據區void function() {static int local_static;    // 存儲在BSS區
}

特點:程序運行期間一直存在、自動初始化為0(BSS區)

3. 棧區(Stack)

void stack_demo() {int local_var = 10;         // 局部變量,存儲在棧上char buffer[1024];          // 局部數組,存儲在棧上printf("local_var地址:%p\n", &local_var);printf("buffer地址:%p\n", buffer);// 函數結束時,這些變量自動銷毀
}void recursive_function(int n) {int local = n;              // 每次遞歸調用都在棧上分配if (n > 0) {recursive_function(n - 1);}
}

特點

  • 自動管理(函數結束自動釋放)
  • 訪問速度快
  • 大小有限(通常幾MB)
  • 后進先出(LIFO)

4. 堆區(Heap)

void heap_demo() {// 動態分配內存int* heap_ptr = (int*)malloc(sizeof(int) * 100);if (heap_ptr == NULL) {printf("內存分配失敗\n");return;}// 使用堆內存for (int i = 0; i < 100; i++) {heap_ptr[i] = i;}// 必須手動釋放free(heap_ptr);heap_ptr = NULL;  // 防止懸空指針
}void cpp_heap_demo() {// C++風格的堆內存管理int* ptr = new int[100];    // 分配// 使用內存...delete[] ptr;               // 釋放
}

特點

  • 手動管理(程序員負責分配和釋放)
  • 大小靈活
  • 訪問速度相對較慢
  • 容易產生內存泄漏和碎片

5. 文字常量區(String Literal Pool)

void string_demo() {char* str1 = "Hello World";     // 字符串存儲在常量區char* str2 = "Hello World";     // 可能與str1指向同一地址char arr[] = "Hello World";     // 字符串復制到棧上printf("str1地址:%p\n", str1);printf("str2地址:%p\n", str2);printf("arr地址:%p\n", arr);// str1[0] = 'h';  // 錯誤:不能修改常量區內容arr[0] = 'h';      // 正確:可以修改棧上的副本
}

內存布局示意圖

高地址
┌─────────────────┐
│     棧區        │ ← 向下增長
│   (局部變量)     │
├─────────────────┤
│       ↓         │
│                 │
│       ↑         │
├─────────────────┤
│     堆區        │ ← 向上增長
│   (動態分配)     │
├─────────────────┤
│   未初始化數據   │
│    (BSS段)      │
├─────────────────┤
│   已初始化數據   │
│   (Data段)      │
├─────────────────┤
│   文字常量區     │
├─────────────────┤
│    程序代碼區    │
└─────────────────┘
低地址

10. 字符串操作函數對比:strcpy、sprintf、memcpy

功能定位分析

strcpy:字符串到字符串的復制
#include <string.h>void strcpy_demo() {char source[] = "Hello World";char destination[20];// 復制字符串(包括結尾的'\0')strcpy(destination, source);printf("復制結果:%s\n", destination);// 注意:不檢查目標緩沖區大小,可能溢出// 更安全的版本:strncpystrncpy(destination, source, sizeof(destination) - 1);destination[sizeof(destination) - 1] = '\0';  // 確保以'\0'結尾
}
sprintf:格式化輸出到字符串
#include <stdio.h>void sprintf_demo() {char buffer[100];int age = 25;float height = 175.5f;char name[] = "張三";// 將多種數據類型格式化為字符串sprintf(buffer, "姓名:%s,年齡:%d,身高:%.1f厘米", name, age, height);printf("格式化結果:%s\n", buffer);// 更安全的版本:snprintfsnprintf(buffer, sizeof(buffer), "安全的格式化:%s", name);
}
memcpy:內存塊到內存塊的復制
#include <string.h>void memcpy_demo() {// 復制整數數組int source[] = {1, 2, 3, 4, 5};int destination[5];memcpy(destination, source, sizeof(source));// 復制結構體struct Person {char name[20];int age;};struct Person p1 = {"李四", 30};struct Person p2;memcpy(&p2, &p1, sizeof(struct Person));printf("復制的結構體:%s, %d\n", p2.name, p2.age);// 復制部分內存char str1[] = "Hello World";char str2[20];memcpy(str2, str1, 5);  // 只復制前5個字符str2[5] = '\0';         // 手動添加結束符printf("部分復制:%s\n", str2);  // 輸出:Hello
}

性能對比測試

#include <time.h>void performance_test() {const int TEST_SIZE = 1000000;char source[1000];char destination[1000];clock_t start, end;// 初始化源數據memset(source, 'A', sizeof(source) - 1);source[sizeof(source) - 1] = '\0';// 測試memcpy性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {memcpy(destination, source, sizeof(source));}end = clock();printf("memcpy耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 測試strcpy性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {strcpy(destination, source);}end = clock();printf("strcpy耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);// 測試sprintf性能start = clock();for (int i = 0; i < TEST_SIZE; i++) {sprintf(destination, "%s", source);}end = clock();printf("sprintf耗時:%f秒\n", (double)(end - start) / CLOCKS_PER_SEC);
}

使用場景選擇指南

  • strcpy:純字符串復制,需要自動處理’\0’結尾
  • sprintf:需要格式化多種數據類型為字符串
  • memcpy:原始內存復制,最高效,適合大塊數據

11. 直接內存操作:指定地址賦值技術

基本概念

在嵌入式開發或系統編程中,經常需要直接操作特定內存地址的數據。

實現方法

void memory_operation_demo() {// 將整數值0xaa66寫入地址0x67a9int* ptr;                    // 聲明整型指針ptr = (int*)0x67a9;         // 將地址強制轉換為整型指針*ptr = 0xaa66;              // 向該地址寫入數據// 讀取驗證printf("地址0x67a9的值:0x%x\n", *ptr);
}

實際應用場景

硬件寄存器操作
// 定義硬件寄存器地址
#define GPIO_BASE_ADDR    0x40020000
#define GPIO_OUTPUT_REG   (GPIO_BASE_ADDR + 0x14)
#define GPIO_INPUT_REG    (GPIO_BASE_ADDR + 0x10)void gpio_control() {// 控制GPIO輸出volatile unsigned int* gpio_output = (volatile unsigned int*)GPIO_OUTPUT_REG;*gpio_output = 0xFF;  // 設置所有引腳為高電平// 讀取GPIO輸入volatile unsigned int* gpio_input = (volatile unsigned int*)GPIO_INPUT_REG;unsigned int input_value = *gpio_input;printf("GPIO輸入值:0x%x\n", input_value);
}
內存映射文件操作
#include <sys/mman.h>
#include <fcntl.h>void memory_mapped_file() {int fd = open("data.bin", O_RDWR);if (fd == -1) return;// 將文件映射到內存void* mapped_addr = mmap(NULL, 1024, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (mapped_addr != MAP_FAILED) {// 直接操作內存就是操作文件int* data_ptr = (int*)mapped_addr;*data_ptr = 0x12345678;  // 寫入數據到文件// 解除映射munmap(mapped_addr, 1024);}close(fd);
}

安全注意事項

void safe_memory_access() {// 1. 檢查地址有效性void* addr = (void*)0x67a9;if (addr == NULL) {printf("無效地址\n");return;}// 2. 使用volatile防止編譯器優化volatile int* ptr = (volatile int*)addr;// 3. 異常處理(在支持的系統上)try {*ptr = 0xaa66;} catch (...) {printf("內存訪問異常\n");}
}

12. 面向對象三大特征深度解析

1. 封裝性(Encapsulation):數據隱藏與接口設計

基本概念

封裝是將數據和操作數據的方法組合在一起,通過訪問控制來隱藏內部實現細節。

class BankAccount {
private:double balance;          // 私有數據:外部無法直接訪問string account_number;   // 私有數據:賬戶安全信息public:// 公有接口:提供安全的訪問方式BankAccount(string acc_num, double initial_balance) {account_number = acc_num;balance = initial_balance;}// 存款操作:控制數據修改方式bool deposit(double amount) {if (amount > 0) {balance += amount;return true;}return false;  // 拒絕無效操作}// 取款操作:包含業務邏輯驗證bool withdraw(double amount) {if (amount > 0 && amount <= balance) {balance -= amount;return true;}return false;  // 余額不足或金額無效}// 查詢余額:只讀訪問double get_balance() const {return balance;}
};

2. 繼承性(Inheritance):代碼復用與層次結構

// 基類:動物
class Animal {
protected:string name;int age;public:Animal(string n, int a) : name(n), age(a) {}// 虛函數:允許子類重寫virtual void make_sound() {cout << name << "發出聲音" << endl;}virtual ~Animal() {}  // 虛析構函數
};// 派生類:狗
class Dog : public Animal {
public:Dog(string n, int a) : Animal(n, a) {}// 重寫基類的虛函數virtual void make_sound() override {cout << name << "汪汪叫" << endl;}
};

3. 多態性(Polymorphism):一個接口多種實現

void demonstrate_polymorphism() {// 創建不同類型的動物對象Animal* animals[] = {new Dog("旺財", 3),new Animal("未知動物", 2)};// 多態調用:同一接口,不同實現for (int i = 0; i < 2; i++) {animals[i]->make_sound();  // 根據實際對象類型調用相應函數}// 清理內存for (int i = 0; i < 2; i++) {delete animals[i];}
}

13. C++空類的隱式成員函數

編譯器自動生成的六個函數

class EmptyClass {// 編譯器自動生成以下函數:// 1. 默認構造函數// 2. 拷貝構造函數// 3. 析構函數// 4. 賦值運算符// 5. 取址運算符// 6. const取址運算符
};

14. 拷貝構造函數 vs 賦值運算符

調用時機不同

class TestClass {
public:int value;TestClass(int v) : value(v) {}TestClass(const TestClass& other) : value(other.value) {cout << "拷貝構造函數調用" << endl;}TestClass& operator=(const TestClass& other) {cout << "賦值運算符調用" << endl;value = other.value;return *this;}
};void demo() {TestClass obj1(10);           // 構造函數TestClass obj2(obj1);         // 拷貝構造函數TestClass obj3 = obj1;        // 拷貝構造函數(不是賦值!)TestClass obj4(20);           // 構造函數obj4 = obj1;                  // 賦值運算符
}

15. 設計不可繼承的類

C++11的final關鍵字(推薦方法)

class FinalClass final {  // final關鍵字阻止繼承
public:FinalClass() {cout << "FinalClass構造函數" << endl;}void do_something() {cout << "執行某些操作" << endl;}
};// 編譯錯誤:無法繼承final類
// class DerivedClass : public FinalClass {};

16. 虛函數表機制深度解析

虛函數表的工作原理

class Base {
public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }
};class Derived : public Base {
public:virtual void func1() override { cout << "Derived::func1" << endl; }
};void virtual_demo() {Base* ptr = new Derived();ptr->func1();  // 通過虛函數表調用Derived::func1delete ptr;
}

17. 函數重寫、重載、隱藏的區別

重載(Overloading):同一作用域內的函數多態

class Calculator {
public:int add(int a, int b) { return a + b; }           // 整數版本double add(double a, double b) { return a + b; }  // 浮點版本int add(int a, int b, int c) { return a + b + c; } // 三參數版本
};

重寫(Override):繼承關系中的虛函數替換

class Shape {
public:virtual double area() { return 0.0; }  // 基類虛函數
};class Circle : public Shape {
public:virtual double area() override {       // 重寫基類虛函數return 3.14159 * radius * radius;}
private:double radius;
};

18. 多態實現原理:虛函數表詳解

動態綁定的實現過程

void polymorphism_mechanism() {Shape* shapes[] = {new Circle(5.0),new Rectangle(4.0, 6.0)};for (int i = 0; i < 2; i++) {// 編譯器生成的代碼:// 1. 獲取對象的虛函數表指針// 2. 在虛函數表中查找area函數的地址// 3. 調用該地址對應的函數cout << "面積:" << shapes[i]->area() << endl;}for (int i = 0; i < 2; i++) {delete shapes[i];}
}

19. 數組 vs 鏈表:數據結構選擇指南

性能對比分析

數組的優勢
void array_advantages() {int arr[1000];// 1. 隨機訪問:O(1)時間復雜度int value = arr[500];  // 直接通過索引訪問// 2. 內存連續,緩存友好for (int i = 0; i < 1000; i++) {arr[i] = i * i;  // 順序訪問,緩存命中率高}// 3. 空間效率高:只存儲數據,無額外指針開銷
}
鏈表的優勢
struct ListNode {int data;ListNode* next;ListNode(int val) : data(val), next(nullptr) {}
};class LinkedList {
private:ListNode* head;public:LinkedList() : head(nullptr) {}// 1. 插入操作:O(1)時間復雜度(在已知位置)void insert_at_head(int val) {ListNode* new_node = new ListNode(val);new_node->next = head;head = new_node;}// 2. 刪除操作:O(1)時間復雜度(在已知位置)void delete_node(ListNode* prev, ListNode* current) {if (prev) {prev->next = current->next;} else {head = current->next;}delete current;}// 3. 動態大小:可以根據需要增長或縮小void dynamic_resize() {// 鏈表大小可以動態變化,不需要預先分配固定大小}
};

使用場景選擇指南

  • 數組適用場景

    • 需要頻繁隨機訪問元素
    • 數據大小相對固定
    • 對內存使用效率要求高
    • 需要利用CPU緩存優化性能
  • 鏈表適用場景

    • 需要頻繁插入和刪除操作
    • 數據大小變化很大
    • 不需要隨機訪問元素
    • 內存分配需要靈活性

20. 單鏈表反轉算法實現

迭代算法實現

struct ListNode {int val;ListNode* next;ListNode(int x) : val(x), next(nullptr) {}
};// 迭代方法反轉鏈表
ListNode* reverse_iterative(ListNode* head) {if (!head) {                    // 判斷鏈表是否為空return head;}ListNode* prev = nullptr;       // 前一個節點指針ListNode* current = head;       // 當前節點指針ListNode* next = nullptr;       // 下一個節點指針while (current != nullptr) {    // 遍歷整個鏈表next = current->next;       // 保存下一個節點current->next = prev;       // 反轉當前節點的指針prev = current;             // 移動prev指針current = next;             // 移動current指針}return prev;                    // prev現在指向新的頭節點
}

遞歸算法實現

// 遞歸方法反轉鏈表
ListNode* reverse_recursive(ListNode* head) {// 基礎情況:空鏈表或只有一個節點if (!head || !head->next) {return head;}// 遞歸反轉剩余部分ListNode* new_head = reverse_recursive(head->next);// 反轉當前連接head->next->next = head;        // 將下一個節點指向當前節點head->next = nullptr;           // 當前節點指向空return new_head;                // 返回新的頭節點
}

完整測試示例

// 創建鏈表的輔助函數
ListNode* create_list(vector<int>& values) {if (values.empty()) return nullptr;ListNode* head = new ListNode(values[0]);ListNode* current = head;for (int i = 1; i < values.size(); i++) {current->next = new ListNode(values[i]);current = current->next;}return head;
}// 打印鏈表的輔助函數
void print_list(ListNode* head) {ListNode* current = head;while (current) {cout << current->val;if (current->next) cout << " -> ";current = current->next;}cout << " -> NULL" << endl;
}// 測試函數
void test_reverse() {vector<int> values = {1, 2, 3, 4, 5};// 測試迭代方法ListNode* list1 = create_list(values);cout << "原始鏈表:";print_list(list1);ListNode* reversed1 = reverse_iterative(list1);cout << "迭代反轉后:";print_list(reversed1);// 測試遞歸方法ListNode* list2 = create_list(values);ListNode* reversed2 = reverse_recursive(list2);cout << "遞歸反轉后:";print_list(reversed2);
}

算法復雜度分析

  • 時間復雜度:O(n),需要遍歷鏈表中的每個節點一次
  • 空間復雜度
    • 迭代方法:O(1),只使用常數額外空間
    • 遞歸方法:O(n),遞歸調用棧的深度為n

實際應用場景

  1. 數據結構操作:在實現棧、隊列等數據結構時需要反轉操作
  2. 算法題解決:許多算法問題需要鏈表反轉作為子問題
  3. 系統設計:在某些系統中需要反轉數據流或操作序列

總結

本文詳細介紹了C/C++20個核心知識點,涵蓋了從基礎語法到高級特性的各個方面:

  1. 基礎概念:變量聲明定義、數據類型比較、sizeof/strlen區別
  2. 內存管理:static關鍵字、malloc/new區別、內存布局
  3. 面向對象:三大特征、虛函數機制、多態實現
  4. 數據結構:數組鏈表對比、鏈表反轉算法
  5. 編程技巧:宏定義陷阱、volatile關鍵字、防御性編程

掌握這些知識點不僅能幫助你在面試中脫穎而出,更重要的是能提升你的編程能力和代碼質量。建議讀者結合實際項目練習,加深對這些概念的理解和應用。

記住:理論知識要與實踐相結合,多寫代碼、多調試、多思考,才能真正掌握C/C++編程的精髓。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/91364.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/91364.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/91364.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

物聯網發展:從概念到應用的演變歷程

物聯網的發展歷程是一部技術革新與社會需求共同驅動的進化史&#xff0c;其演變可劃分為概念萌芽、技術積累、應用拓展和智能融合四個階段&#xff0c;每個階段均以關鍵技術突破或社會需求變革為標志&#xff0c;最終形成萬物互聯的智能生態。以下是具體演變歷程&#xff1a;一…

一個人開發一個App(數據庫)

后端要保存數據&#xff0c;我還是選擇了關系型數據庫Mysql, 因為其它的不熟悉。 flutter端這次我選擇的是ObjectBox&#xff0c;以前都是直接用的sqlite3&#xff0c;看對比ObjectBox效率比sqlite3高許多&#xff0c;這次前端為了用戶體驗&#xff0c;我需要緩存數據&#xff…

天銘科技×藍卓 | “1+2+N”打造AI驅動的汽車零部件行業智能工廠

7月24日&#xff0c;杭州天銘科技股份有限公司&#xff08;簡稱 “天銘科技”&#xff09;與藍卓數字科技有限公司&#xff08;簡稱 “藍卓”&#xff09;簽訂全面戰略合作協議。天銘科技董事長張松、副總經理艾鴻冰&#xff0c;藍卓副董事長譚彰等領導出席簽約儀式&#xff0c…

技術復盤報告:Vue表格中多行文本字段數據保存丟失問題

1. 問題背景 在一個基于 Vue 2.0 和 ElementUI 的復雜數據維護頁面中&#xff0c;用戶報告了一個偶發但嚴重的問題&#xff1a;在表格中編輯一個多行文本&#xff08;textarea&#xff09;字段時&#xff0c;輸入的內容有時會在點擊“保存”后丟失。 具體表現&#xff1a; 前端…

#C語言——學習攻略:深挖指針路線(四)--字符指針變量,數組指針變量,二維數組傳參的本質,函數指針變量,函數指針數組

&#x1f31f;菜鳥主頁&#xff1a;晨非辰的主頁 &#x1f440;學習專欄&#xff1a;《C語言學習》 &#x1f4aa;學習階段&#xff1a;C語言方向初學者 ?名言欣賞&#xff1a;"暴力解法是上帝給的&#xff0c;優化解法是魔鬼教的。" 目錄 1. 字符指針變量 1.1 使…

SpringBoot收尾+myBatis plus

一、數據傳遞返回值為:字符串package com.apesource.springboot_web_04.controller;import com.apesource.springboot_web_04.pojo.Emp; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping;/*** 返回值為:字符…

基于 Spring Boot 實現動態路由加載:從數據庫到前端菜單的完整方案

在后臺管理系統中&#xff0c;不同用戶角色往往擁有不同的操作權限&#xff0c;對應的菜單展示也需動態調整。動態路由加載正是解決這一問題的核心方案 —— 根據登錄用戶的權限&#xff0c;從數據庫查詢其可訪問的菜單&#xff0c;封裝成前端所需的路由結構并返回。本文將詳細…

VitePress學習-自定義主題

VitePress-自定義主題 代碼倉庫 基礎了解 初始化項目的時候選擇 custom theme 運行后會發現頁面挺丑的。 如果想要用默認主題怎么辦呢&#xff0c;修改Layout。 使用默認主題的Layout <script setup lang"ts"> import { useData } from vitepress; impo…

【GEO從入門到精通】生成式引擎與其他 AI 技術的關系

2.1.3 生成式引擎與其他 AI 技術的關系生成式引擎作為人工智能領域的創新力量&#xff0c;與其他 AI 技術緊密相連&#xff0c;相互促進&#xff0c;共同推動 生成式引擎優化&#xff08;GEO&#xff09; 的發展。這些技術使生成式引擎能夠為消費者提供更加個性化和精準的內容。…

JAVAEE--4.多線程案例

設計模式1.單例模式1.1餓漢模式1.2懶漢模式(單線程版)1.3懶漢模式(多線程版本)1.4懶漢模式(多線程版本進階版)2.阻塞隊列3.定時器4.線程池1.單例模式設計模式是"軟性約束",不是強制的,可以遵守也可以不遵守,按照設計模式寫代碼使代碼不會太差框架是"硬性約束&qu…

量化感知訓練(QAT)流程

WHAT&#xff1a;量化感知訓練&#xff08;Quantization-Aware Training, QAT&#xff09; 是一種在模型訓練階段引入量化誤差的技術。它的核心思想是&#xff1a;通過在前向傳播時插入“偽量化節點”引入量化誤差&#xff0c;將權重和激活模擬為低精度&#xff08;如 int8&…

docker 用于將鏡像打包為 tar 文件

docker save 是 Docker 中用于將鏡像打包為 tar 文件的命令&#xff0c;常用于鏡像的備份、遷移或離線傳輸。以下是其核心用法和注意事項&#xff1a;一、基本語法bashdocker save [選項] IMAGE [IMAGE...] > 文件名.tar # 或 docker save -o 文件名.tar IMAGE [IMAGE...]IM…

設計模式(六)創建型:單例模式詳解

設計模式&#xff08;六&#xff09;創建型&#xff1a;單例模式詳解單例模式&#xff08;Singleton Pattern&#xff09;是 GoF 23 種設計模式中最簡單卻最常被誤用的創建型模式。其核心價值在于確保一個類在整個應用程序生命周期中僅存在一個實例&#xff0c;并提供一個全局訪…

PostgreSQL AND OR 操作符詳解

PostgreSQL AND & OR 操作符詳解 在數據庫查詢中,AND 和 OR 是兩種常見的邏輯操作符,用于組合多個查詢條件。PostgreSQL 作為一款功能強大的開源關系型數據庫管理系統,同樣支持這些操作符。本文將詳細介紹 PostgreSQL 中的 AND 和 OR 操作符,并探討它們在查詢中的應用…

RabbiteMQ安裝-ubuntu

Ubuntu 1.安裝Erlang RabbitMQ需要Erlang語言的支持&#xff0c;在安裝RabbitMQ之前需要安裝Erlang #更新軟件包 sudo apt-get update#安裝erlang sudo apt-get install erlang查看erlang版本 roothcss-ecs-027f:/# erl Erlang/OTP 24 [erts-12.2.1] [source] [64-bit] [sm…

Linux驅動20 --- FFMPEG視頻API

目錄 一、FFMPEG 視頻 API 的使用 1.1 介紹 1.2 整體編程過程 獲取核心上下文指針 打開輸入流文件 獲取輸入流 獲取編碼器 初始化解碼器 申請輸出流指針 獲取顯示數據空間大小 申請輸出顯示空間 綁定輸出流和輸出顯示空間 申請格式轉換上下文 申請輸入流指針 讀取一幀數據 發…

OpenBayes 一周速覽丨Self Forcing 實現亞秒級延遲實時流視頻生成;邊緣AI新秀,LFM2-1.2B采用創新性架構超越傳統模型

公共資源速遞 This Weekly Snapshots &#xff01; 5 個公共數據集&#xff1a; * AF-Chat 音頻對話文本數據集 * ArtVIP 機器交互式圖像數據集 * Updesh 印度語合成文本數據集 * Medical Information 藥品信息數據集 * Nemotron-Math-HumanReasoning 數學推理數據集…

[NOIP2002 提高組] 均分紙牌

題目描述有N堆紙牌&#xff0c;編號分別為 1,2,…,N。每堆上有若干張&#xff0c;但紙牌總數必為N的倍數。可以在任一堆上取若干張紙牌&#xff0c;然后移動。移牌規則為&#xff1a;在編號為1堆上取的紙牌&#xff0c;只能移到編號為2的堆上&#xff1b;在編號為N的堆上取的紙…

【音視頻】WebRTC-Web 音視頻采集與播放

一、打開攝像頭 打開攝像頭首先需要有一個html的video標簽&#xff1a; id "local-video"&#xff0c;是為了后續的js腳本調用這個對象autoplay是設置打開后自動播放&#xff0c;playsinline則是為了兼容移動端 <video id "local-video" autoplay p…

數據治理平臺如何選?深度解析國產化全棧方案與行業落地實踐

“數據治理平臺廠商有哪些&#xff1f;”國內主流廠商包括阿里云、華為、百分點科技等&#xff0c;各有所長。其中&#xff0c;百分點科技憑借在應急管理、智慧公安及央國企數字化領域的深度實踐&#xff0c;打造了行業特色鮮明的數據治理解決方案。百分點科技的數據治理解決方…