程序設計|C語言教學——C語言基礎4:進階

一、預處理指令

預處理指令在編譯前執行,除了#include,還有以下常用指令:

1.?#define?宏定義
  • 無參宏:定義常量或代碼片段,編譯時直接替換(無類型檢查)。

#define PI 3.1415926  // 定義常量
#define MAX(a,b) (a > b ? a : b)  // 定義簡單邏輯(注意括號,避免運算優先級問題)
  • 注意:宏替換是 “文本替換”,可能導致副作用,例如?MAX(a++, b++)?會導致ab多自增一次。

  • 帶參宏與函數的區別

    特性帶參宏函數
    執行時機編譯前替換運行時調用
    開銷無調用開銷(代碼膨脹)有棧幀開銷
    類型檢查
    返回值無(替換結果直接使用)有明確返回值類型
2. 條件編譯

用于控制代碼是否參與編譯,常用于跨平臺開發或調試。

#define DEBUG 1  // 定義宏DEBUG#ifdef DEBUG  // 如果定義了DEBUG,則編譯以下代碼printf("調試信息:變量x的值為%d\n", x);
#else  // 否則編譯以下代碼// 無調試信息
#endif#ifndef _HEADER_H  // 如果未定義_HEADER_H(防止頭文件重復包含)
#define _HEADER_H
// 頭文件內容
#endif

二、數據類型進階

1. 類型修飾符
  • short/long:修飾整數長度(short int?通常 2 字節,long int?通常 4/8 字節,取決于系統)。
  • signed/unsignedsigned int?可表示正負(默認),unsigned int?只表示非負(范圍更大)。
unsigned int num = 10;  // 范圍:0 ~ 4294967295(32位系統)
signed char c = -12;    // 范圍:-128 ~ 127(8位)
2. 自定義類型
  • 結構體(struct:組合不同類型數據,用于描述復雜對象(如學生、坐標)。

// 定義結構體類型,此時的student是一種自定義的新數據類型,像int一樣
struct Student {char name[20];  // 姓名int age;        // 年齡float score;    // 成績
};int main() {// 聲明結構體變量并初始化struct Student stu = {"張三", 18, 90.5f};// 訪問成員(用.運算符)printf("姓名:%s,年齡:%d\n", stu.name, stu.age);// 結構體指針(用->運算符訪問成員)struct Student *p = &stu;p->score = 95.0f;  // 等價于 (*p).score = 95.0freturn 0;
}
  • 枚舉(enum:定義命名的整數常量,提高代碼可讀性。
enum Weekday {MON,  // 默認為0TUE,  // 1WED=5,  // 顯式賦值5THU   // 6(自動遞增)
};int main() {enum Weekday day = WED;printf("%d\n", day);  // 輸出5return 0;
}
  • 共用體(union:所有成員共享同一塊內存(大小為最大成員的大小),用于節省空間。
union Data {int i;float f;char c;
};  // 大小為4字節(float和int通常4字節)int main() {union Data d;d.i = 10;printf("d.i = %d, d.f = %f\n", d.i, d.f);  // f的值會混亂(內存被i覆蓋)return 0;
}
3. 結構體的高級用法
  • 結構體嵌套與自引用(鏈表基礎):結構體可嵌套其他結構體,自引用(包含自身類型的指針)是實現鏈表、樹等數據結構的核心。

示例:單向鏈表節點

#include <stdio.h>
#include <stdlib.h>// 結構體自引用(必須用指針,否則會無限遞歸定義)
struct Node {int data;  // 數據域struct Node *next;  // 指針域:指向 next 節點
};// 創建新節點
struct Node* createNode(int data) {struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));newNode->data = data;newNode->next = NULL;  // 初始指向NULLreturn newNode;
}int main() {// 創建3個節點并鏈接struct Node *n1 = createNode(10);struct Node *n2 = createNode(20);struct Node *n3 = createNode(30);n1->next = n2;  // n1 指向 n2n2->next = n3;  // n2 指向 n3// 遍歷鏈表struct Node *current = n1;while (current != NULL) {printf("%d ", current->data);  // 輸出:10 20 30current = current->next;}// 釋放鏈表內存(從首節點依次釋放)current = n1;while (current != NULL) {struct Node *temp = current;current = current->next;free(temp);}return 0;
}

三、函數與指針深入

1. 函數參數與返回值
  • 傳值調用:函數接收參數的副本,修改副本不影響原變量。

void swap(int a, int b) {int temp = a;a = b;b = temp;  // 僅修改副本,原變量不變
}
  • 傳址調用:通過指針傳遞變量地址,函數可修改原變量。
void swap(int *a, int *b) {int temp = *a;*a = *b;*b = temp;  // 直接修改原變量
}int main() {int x=3, y=5;swap(&x, &y);  // 傳遞地址printf("x=%d, y=%d\n", x, y);  // 輸出x=5, y=3return 0;
}
  • 指針函數(返回指針的函數):返回值為指針(需注意:不能返回局部變量的地址,因其生命周期隨函數結束而結束)。
int* getStaticPtr() {static int num = 10;  // static變量生命周期為整個程序,地址有效return &num;
}
  • 函數指針(指向函數的指針):函數指針存儲函數的地址,可實現 “回調函數”(將函數作為參數傳遞),是 C 語言實現多態的重要方式。
#include <stdio.h>// 加法函數
int add(int a, int b) { return a + b; }// 乘法函數
int multiply(int a, int b) { return a * b; }// 函數指針作為參數(回調函數)
int calculate(int a, int b, int (*func)(int, int)) {return func(a, b);  // 調用傳入的函數
}int main() {int x=3, y=4;// 用函數指針調用不同函數,實現不同邏輯printf("加法結果:%d\n", calculate(x, y, add));       // 7printf("乘法結果:%d\n", calculate(x, y, multiply)); // 12return 0;
}
2. 函數遞歸

函數自身調用自身,需滿足終止條件遞歸關系(如階乘、斐波那契數列)。

// 計算n的階乘:n! = n * (n-1)!,終止條件n=1時返回1
int factorial(int n) {if (n == 1) return 1;  // 終止條件return n * factorial(n-1);  // 遞歸關系
}

注意:遞歸深度過大會導致棧溢出(棧內存有限),復雜場景建議用循環替代。

3. 指針深入
  • 二級指針(指針的指針):用于處理 “指針的集合”(如動態二維數組、指針數組的修改)。

示例:動態創建二維數組(用二級指針)

#include <stdio.h>
#include <stdlib.h>int main() {int rows = 2, cols = 3;int **arr;  // 二級指針:指向int*類型的指針// 第一步:分配存放指針的內存(rows個int*)arr = (int**)malloc(rows * sizeof(int*));// 第二步:為每個指針分配數組內存for (int i=0; i<rows; i++) {arr[i] = (int*)malloc(cols * sizeof(int));}// 賦值arr[0][0] = 1; arr[0][1] = 2; arr[0][2] = 3;arr[1][0] = 4; arr[1][1] = 5; arr[1][2] = 6;// 打印for (int i=0; i<rows; i++) {for (int j=0; j<cols; j++) {printf("%d ", arr[i][j]);}printf("\n");}// 釋放內存(先釋放內層,再釋放外層)for (int i=0; i<rows; i++) {free(arr[i]);}free(arr);return 0;
}
  • const 修飾的指針:區分 “指向 const 的指針” 和 “const 指針”,避免意外修改數據。
#include <stdio.h>int main() {int a = 10, b = 20;// 1. 指向const的指針(不能通過指針修改指向的值,但指針可指向其他地址)const int *p1 = &a;// *p1 = 30;  // 錯誤:不能修改指向的值p1 = &b;  // 正確:可指向其他地址// 2. const指針(指針本身不能修改指向,但可修改指向的值)int *const p2 = &a;*p2 = 30;  // 正確:可修改指向的值// p2 = &b;  // 錯誤:指針本身是const,不能改指向// 3. 指向const的const指針(既不能改指向,也不能改值)const int *const p3 = &a;return 0;
}

四、數組與指針深入

1. 數組與指針的關系

數組名本質是首元素地址(常量指針,不可修改),可通過指針訪問數組元素。

int arr[5] = {1,2,3,4,5};
int *p = arr;  // 等價于 p = &arr[0]// 訪問arr[2]的三種方式
printf("%d\n", arr[2]);    // 數組下標
printf("%d\n", *(arr+2));  // 數組名+偏移量
printf("%d\n", *(p+2));    // 指針+偏移量
2. 字符數組與字符串

字符串是以'\0'(ASCII 值 0)結尾的字符數組,printfstrlen等函數通過'\0'判斷結束。

char str1[] = "hello";  // 自動添加'\0',長度為6(h e l l o \0)
char str2[] = {'h','i','\0'};  // 必須顯式添加'\0',否則不是字符串// 字符串處理函數(需包含<string.h>)
printf("長度:%d\n", strlen(str1));  // 輸出5(不包含'\0')
char dest[20];
strcpy(dest, str1);  // 復制字符串(注意dest容量足夠)
strcat(dest, " world");  // 拼接字符串
3. 指針數組與數組指針
  • 指針數組:數組元素是指針(如存儲多個字符串的地址)。

char *strs[] = {"apple", "banana", "cherry"};  // 每個元素是字符串常量的地址
for (int i=0; i<3; i++) {printf("%s\n", strs[i]);  // 輸出三個字符串
}
  • 數組指針:指向整個數組的指針(需指定數組長度)。
int arr[3] = {1,2,3};
int (*p)[3] = &arr;  // p指向包含3個int的數組
printf("%d\n", (*p)[1]);  // 輸出2(訪問數組第2個元素)

也就是說,指針可以指向數組的某個元素,也可以指向整個數組,也可以作為元素本身構成數組。當指針直接指向數組名的時候,存儲的實際上是數組的第一個元素的地址。

4. 多維數組的指針訪問

二維數組在內存中是連續存儲的(按行優先排列),可通過指針靈活訪問,而不僅限于arr[i][j]的形式。

示例:用指針遍歷二維數組

#include <stdio.h>int main() {int arr[2][3] = {{1,2,3}, {4,5,6}};int *p = &arr[0][0];  // 指向首元素的指針(或直接用 int *p = arr[0];)// 遍歷整個二維數組(共2×3=6個元素)for (int i=0; i<2*3; i++) {printf("%d ", *(p+i));  // 輸出:1 2 3 4 5 6}return 0;
}
  • 二維數組名arr數組指針(類型為int (*)[3]),指向第一行的整個數組,arr+1會跳過一整行(3 個 int)。
  • 易錯點:arr[i][j]等價于*(*(arr+i)+j),需注意指針類型匹配。

五、內存管理

C 語言需手動管理內存,通過stdlib.h中的函數操作堆內存(堆內存需手動申請和釋放,否則內存泄漏)。

1. 動態內存分配
  • malloc(size):分配size字節的內存,返回void*(需強制類型轉換),失敗返回NULL
  • calloc(n, size):分配nsize字節的內存,初始化為 0。
  • realloc(ptr, new_size):調整已分配內存的大小,可能移動內存塊。
2. 內存釋放
  • free(ptr):釋放malloc/calloc/realloc分配的內存,釋放后ptr應置為NULL(避免野指針)。

示例:

#include <stdio.h>
#include <stdlib.h>int main() {// 分配10個int的內存(40字節)int *arr = (int*)malloc(10 * sizeof(int));if (arr == NULL) {  // 檢查分配是否成功printf("內存分配失敗\n");return 1;}// 使用內存for (int i=0; i<10; i++) {arr[i] = i;}// 重新分配為20個int的內存int *new_arr = (int*)realloc(arr, 20 * sizeof(int));if (new_arr != NULL) {arr = new_arr;  // 重新分配成功,更新指針}// 釋放內存free(arr);arr = NULL;  // 避免野指針return 0;
}

六、文件操作

通過stdio.h中的函數讀寫文件,核心是文件指針(FILE*

1. 文件打開與關閉
  • fopen(filename, mode):打開文件,mode為打開模式("r"讀,"w"寫,"a"追加等),失敗返回NULL
  • fclose(fp):關閉文件,必須調用(否則可能丟失數據)。
2. 文件讀寫
  • 字符級:fgetc(fp)(讀一個字符)、fputc(c, fp)(寫一個字符)。
  • 字符串級:fgets(buf, size, fp)(讀一行)、fputs(str, fp)(寫字符串)。
  • 格式化:fscanf(fp, format, ...)fprintf(fp, format, ...)
#include <stdio.h>int main() {// 寫文件FILE *fp = fopen("test.txt", "w");  // 以寫模式打開if (fp == NULL) {printf("文件打開失敗\n");return 1;}fprintf(fp, "Hello, File!\n");  // 寫入內容fclose(fp);  // 關閉文件// 讀文件fp = fopen("test.txt", "r");char buf[100];fgets(buf, 100, fp);  // 讀取一行printf("讀取內容:%s", buf);  // 輸出Hello, File!fclose(fp);return 0;
}

七、其他的一些名詞

  • 野指針:訪問已釋放或未初始化的指針(可能崩潰)。
  • 數組越界:訪問超出數組長度的元素(可能修改其他內存)。
  • 內存泄漏:未用free釋放堆內存(長期運行的程序會耗盡內存)。

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

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

相關文章

數據結構之heap算法

文章目錄前言1. heap結構概述2. push_heap3. pop_heap4. sort_heap5. make_heap前言 heap這種數據結構&#xff0c;允許用戶以任何次序將任何數據放入該結構中&#xff0c;但是最后取出數據的時候一定是權值最高&#xff08;或者最低&#xff09;的元素。主要和實現有關&#x…

MCU 軟件斷點調試注意事項!!!

——為什么調試器會在運行中改我的Flash程序&#xff1f;調試單片機時&#xff0c;很多人都有這樣的疑問&#xff1a;明明我在調試前刷進去的固件是好的&#xff0c;為什么加了一個斷點之后&#xff0c;調試器居然去改了 Flash&#xff1f; 如果我拔掉調試器&#xff0c;這個固…

啟發式合并 + 莫隊 戀戀的心跳大冒險

題目來源&#xff1a;2025 Wuhan University of Technology Programming Contest 比賽鏈接&#xff1a;Dashboard - 2025 Wuhan University of Technology Programming Contest - Codeforces 題目大意&#xff1a; Solution&#xff1a; 首先肯定要預處理出以每個節點為起點…

JCTools 無鎖并發隊列基礎:ConcurrentCircularArrayQueue

ConcurrentCircularArrayQueue ConcurrentCircularArrayQueue 是一個抽象類&#xff0c;它為基于數組的并發循環隊列提供了基礎功能。從其命名可以看出幾個關鍵特性&#xff1a;??Concurrent??&#xff1a;常指無鎖并發。??Circular Array??&#xff1a;內部使用循環數…

力扣(LeetCode) ——622. 設計循環隊列(C語言)

題目&#xff1a;622. 設計循環隊列示例1&#xff1a; MyCircularQueue circularQueue new MyCircularQueue(3); // 設置長度為 3 circularQueue.enQueue(1); // 返回 true circularQueue.enQueue(2); // 返回 true circularQueue.enQueue(3); // 返回 true circularQueue.…

在JVM跑JavaScript腳本 | Oracle GraalJS 簡介與實踐

這是2024年初的 GraalVM 系列博文&#xff0c;當時寫了大綱&#xff0c;知道一年半后的現在才得以完成發布&#x1f604; 1、概述 實話說&#xff0c;標題的場景為小眾需求&#xff0c;日常開發基本用不到&#xff0c;我是最近在做一個低代碼輪子玩具 app-meta 需要實現 FaaS&…

基于 EC 數據與大模型技術實現天氣預報:從數據到上線的全棧方法

1. 先校準“EC 數據”與“AI 預報”的語境 EC 數據家族(常用) IFS/HRES:確定性全球模式,水平分辨率約 9 km,常用預報范圍 10 天; IFS/ENS:51 成員集合預報,提供 15 天概率信息; ERA5:再分析數據,小時級、0.25,可追溯至 1940 年,用作訓練/評測黃金基準。 AI 預報…

迭代器模式及優化

迭代器模式&#xff08;Iterator Pattern&#xff09;是一種行為型設計模式&#xff0c;用于提供一種統一的方式遍歷聚合對象&#xff08;如集合、容器&#xff09;中的元素&#xff0c;而無需暴露對象的內部實現細節。它將遍歷邏輯與聚合對象分離&#xff0c;使得遍歷操作可以…

純Qt手撕gb28181協議/gb28181協議服務端/gb28181協議設備端/gb28181設備模擬器/gb28181虛擬監控設備

一、前言說明 搞完onvif設備模擬器&#xff0c;總想著把28181設備模擬也實現&#xff0c;因為之前已經花了大力氣把28181平臺軟件端實現了&#xff0c;為了實現這個組件&#xff0c;頭發掉了一大把&#xff0c;專門把國標文檔看了好幾遍&#xff0c;逐行閱讀&#xff0c;針對需…

【滲透實戰】無下載器環境(curl/wget)下玩轉 Metasploit 自動利用

1. 背景與問題場景 在滲透測試或漏洞利用中&#xff0c;Metasploit&#xff08;MSF&#xff09;是業界最常用的框架之一。 其許多 RCE&#xff08;遠程代碼執行&#xff09;模塊在落地 payload&#xff08;如 Meterpreter 或反彈 shell&#xff09;時&#xff0c;采用了 CMD St…

jd-hotkey探測熱點key

對任意突發性的無法預先感知的熱點數據&#xff0c;包括并不限于熱點數據&#xff08;如突發大量請求同一個商品&#xff09;、熱用戶&#xff08;如惡意爬蟲刷子&#xff09;、熱接口&#xff08;突發海量請求同一個接口&#xff09;等&#xff0c;進行毫秒級精準探測到。然后…

C#WPF實戰出真汁07--【系統設置】--菜品類型設置

1、菜品設置介紹 菜品設置跟餐桌設置的功能目的是相同的&#xff0c;包括了新增&#xff0c;刪除&#xff0c;編輯&#xff0c;分頁&#xff0c;查詢&#xff0c;重置&#xff0c;全選&#xff0c;全消&#xff0c;列表功能&#xff0c;實現流程也是布局設計&#xff0c;后臺邏…

aave v3 存款與借款利息的計算方式

本文只涉及到利率計算的數學原理&#xff0c;不作源碼解析:存款首先我們假設小明在aave里面存了10000usdt&#xff0c;存的時候年化收益率是5%,那么半年后其存款的利息是多少呢?常規的計算方式如下:利息10000*5%*(存款的時長/一年的時長)這么做有什么問題呢&#xff1f;假設現…

Windows MCP.Net:基于.NET的Windows桌面自動化MCP服務器深度解析

&#x1f4cb; 目錄 項目概述 技術架構深度解析 核心功能模塊詳解 代碼實現分析 使用場景與實戰案例 性能優化與最佳實踐 擴展開發指南 總結與展望 項目概述 什么是Windows-MCP.Net&#xff1f; Windows MCP.Net是一個基于.NET 10.0開發的Windows桌面自動化MCP&…

Boost.Asio學習(7):Boost.Beast實現簡易http服務器

namespace beast boost::beast;beast::flat_buffer是一個用于 Boost.Asio 和 Boost.Beast 網絡讀寫的緩沖區實現。專為 一次性順序讀取 / 消費 場景設計&#xff0c;比 std::string 或 std::vector 高效&#xff0c;因為它是扁平內存結構&#xff08;contiguous memory&#x…

深入解析JVM內存區域劃分:從理論到實踐

Java虛擬機&#xff08;JVM&#xff09;是Java程序運行的核心環境&#xff0c;它負責管理內存分配、垃圾回收、字節碼執行等關鍵任務。理解JVM的內存區域劃分&#xff0c;對于優化Java應用性能、排查內存問題&#xff08;如OutOfMemoryError、StackOverflowError&#xff09;至…

滑窗|貪心|?滾動數組

lc17.08pair按身高升序、相同時體重降序排序結果是找體重序列的最長遞增子序列長度核心&#xff1a;轉化為二維最長遞增子序列問題求解vector<int> dp;for (auto& p : hw) {int w p.second;auto it lower_bound(dp.begin(), dp.end(), w);if (it dp.end()) {dp.pu…

深入理解數據庫架構:從原理到實踐的完整指南

一、數據庫存儲架構的多維度分類體系 1.1 基于數據組織方式的存儲架構分類 數據庫的存儲架構從根本上決定了其性能特征、適用場景和擴展能力。理解不同的數據組織方式是選擇合適數據庫技術的基礎&#xff0c;這種分類不僅反映了技術實現的差異&#xff0c;更體現了對不同業務需…

體彩排列三第2025218期號碼分析

大家好&#xff0c;本人蔡楚門來此平臺分享一下本期得經驗和思路&#xff0c;希望能夠給大家帶來好的運氣和靈感&#xff01;體彩排列三第2025218期號碼分析&#xff0c;大小號碼數字分析&#xff0c;上期開出全小號碼最多&#xff0c;最近兩期的開獎號碼全部都是全小號碼最多&…

java設計模式之迪米特法則介紹與說明

一、核心概念與目標 基本定義 迪米特法則的核心思想是&#xff1a;一個對象應該對其他對象盡可能少地了解&#xff0c;僅與直接關聯的對象&#xff08;即“朋友”&#xff09;通信&#xff0c;避免與“陌生人”產生直接交互。 直接朋友&#xff1a;包括當前對象的成員變量、方法…