程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C語言02基礎深入理解(二)

C語言02基礎深入理解(二)

編輯:關於C語言

C語言02基礎深入理解(二)


指針基礎


變量回顧
既然程序中的變量只是一段存儲空間的別名 , 那麼是不
是必須通過這個別名才能使用這段存儲空間 ?

指針本質
? 指針在本質上也是一個變量
? 指針需要占用一定的內存空間
? 指針用於保存內存地址的值

* 號的意義
? 在指針聲明時 ,* 號表示所聲明的變量為指針
? 在指針使用時 ,* 號表示取指針所指向的內存空間中的值
// 指針聲明 :
int i = 0;
int j = 0;
int* p = &i;
// 取值 :
j = *p;
p: 0xAABBCCD0 i: 0
0xAABBCCD0
* 號類似一把鑰匙 , 通過這把鑰匙可以
打開內存 , 讀取內存中的值 。

1. 指針占用的內存空間
2. 指針的地址
3. 通過* 號寫內存

傳值調用與傳址調用
? 指針是變量 , 因此可以聲明指針參數
? 當一個函數體內部需要改變實參的值 , 則需要使用指針參數
? 函數調用時實參值將復制到形參
? 指針適用於復雜數據類型作為參數的函數中

常量與指針(判斷const修飾的是p還是*p)
? const int* p; //p 可變 ,p 指向的內容不可變
? int const* p; //p 可變 ,p 指向的內容不可變
? int* const p; //p 不可變 ,p 指向的內容可變
? const int* const p; //p 和p 指向的內容都不可變
口訣 : 左數右指
當const 出現在* * 號左邊時指針指向的數據為常量
當const 出現在* * 後右邊時指針本身為常量
www.enjoylinux.cn

指針小結
? 指針是C 語言中一種特別的變量
? 指針所保存的值是內存的地址
? 可以通過指針修改內存中的任意地址內容
int* p
0x00eaff00 10 …… …… ……
0x00eaff00

數組基礎

數組的概念
數組是相同類型的變量的有序集合
int a[5];

a代表數組第一個元素的起始地址。


這20個字節空間的名字為
a。a[0], a[1]等都是a中的
元素,並非元素的名字。數
組中的元素沒有名字。
每個元素都是int型數據

數組的大小
? 數組在一片連續的內存空間中存儲元素
? 數組元素的個數可以顯示或隱式指定

數組地址與數組名
? 數組名代表數組首元素的地址,是個常量(整數),因此,不能作為左值
? 數組的地址需要用取地址符 & 才能得到
? 數組首元素的地址值與數組的地址值相同
? 數組首元素的地址與數組的地址是兩個不同的概念
你家所在的樓房和你家的 GPS 地址是
相同的 , 但意義相同嗎 ?

數組名的盲點
? 數組名可以看做一個常量指針
? 數組名“ 指向” 的是內存中數組首元素的起始位置
? 在表達式中數組名只能作為右值使用


? 只有在下列場合中數組名不能看做常量指針
? 數組名作為sizeof 操作符的參數
? 數組名作為& 運算符的參數


數組小結
? 數組是一片連續的內存空間
? 數組的地址和數組首元素的地址意義不同
? 數組名在大多數情況下被當成常量指針處理
? 數組名其實並不是指針 , 在外部聲明時不能混淆
概念的混淆是 BUG 的根源之一 !

數組與指針分析

數組的本質
? 數組是一段連續的內存空間
? 數組的空間大小為sizeof(array_type) * array_size
? 數組名可看做指向數組第一個元素的常量指針

指針的運算
? 指針是一種特殊的變量 , 與整數的運算規則為
p + n; ?? (unsigned int)p + n*sizeof(*p);
結論 :
當指針p 指向一個同類型的數組的元素時 : p+1 將指向
當前元素的下一個元素 ; p- 1 將指向當前元素的上一
個元素 。

指針的運算
? 指針之間只支持減法運算 , 且必須參與運算的指針類型必
須相同
p1 – p2; ( (unsigned int)p1 - (unsigned int)p2) / sizeof(type);
注意 :
? 只有當兩個指針指向同一個數組中的元素時 , 指針
相減才有意義 , 其意義為指針所指元素的下標差
? 當兩個指針指向的元素不在同一個數組中時 , 結果
未定義

指針的比較
? 指針也可以進行關系運算
< <= > >=
? 指針關系運算的前提是同時指向同一個數組中的元素
? 任意兩個指針之間的比較運算(==, !=) 無限制

數組的訪問
? 以 下標的形式 訪問數組中的元素
? 以 指針的形式 訪問數組中的元素

標 下標 VS 指針
? 從理論上而言 , 當指針以固定增量在數組中移動時 , 其效
率高於下標產生的代碼
? 當指針增量為1 且硬件具有硬件增量模型時 , 表現更佳
注意 :
現代編譯器的生成代碼優化率已大大提高 , 在固定增
量時 , 下標形式的效率已經和指針形式相當 ; 但從可
讀性和代碼維護的角度來看 , 下標形式更優 。

a 和&a 的區別
?a 為數組是數組首元素的地址
?&a 為整個數組的地址
?a 和&a 的意義不同其區別在於指針運算
a + 1 (unsigned int)a + sizeof(*a)
&a + 1 (unsigned int)(&a) + sizeof(*&a)

數組參數
?C 語言中 , 數組作為函數參數時 , 編譯器將
其編譯成對應的指針
void f(int a[]); ?? void f(int* a);
void f(int a[5]); ?? void f(int* a);
結論 :
一般情況下 , 當定義的函數中有數組參數時 , 需要定
義另一個參數來標示數組的大小 。

指針和數組的對比
? 數組聲明時編譯器自動分配一片連續內存空間

? 指針聲明時只分配了用於容納指針的4 字節空間

? 在作為函數參數時 , 數組參數和指針參數等價

? 數組名在多數情況可以看做常量指針 , 其值不能改變

? 指針的本質是變量 , 保存的值被看做內存中的地址

C 語言中的字符串

? 從概念上講 ,C 語言中沒有字符串數據類型
? 在C 語言中使用字符數組來模擬字符串
? C 語言中的字符串是以’\0’ 結束的字符數組
? C 語言中的字符串可以分配於棧空間 , 堆空間或者只讀存儲區

字符串長度
? 字符串的長度就是字符串所包含字符的個數
? C 語言中的字符串長度指的是第一個’\ 0 ’ 字符前出現的字符個數
? C 語言中通過’\0’ 結束符來確定字符串的長度

strlen 的返回值是用無符號數定義的 , 因此
相減不可能產生負數 , 以上的語句不等價 。

? 一般情況下 , 千萬千萬不要自行編寫C 標准庫已經提供的函數 。
? 標准庫有時會使用匯編語言實現 , 目的就是為了充分利用機器所提供的特殊指令以追求最大的速度 。
? 復用已經存在的函數庫會更高效 。

不受限制的字符串函數
? 不受限制的字符串函數是通過尋找字符串的結束符
’\0’ 來判斷長度
字符串復制: :
char* strcpy(char* dst, const char* src);


字符串連接: :
char* strcat(char* dst, const char* src);


字符串比較 :
int strcmp(const char* s1, const char* s2);

? 不受限制的字符串函數都是以‘ \0 ’ 作為結尾標記來進
行的 , 因此輸入參數中必須包含’ \0’ 。

? strcpy 和strcat 必須保證目標字符數組的剩余空間足
以保存整個源字符串 。

? strcmp 以0 值表示兩個字符串相等
? 第一個字符串 大於 第二個字符串的時候返回值 大於0
? 第一個字符串 小於 第二個字符串的時候返回值 小於0
? strcmp 不會修改參數值 , 但依然以’\ \0 0’ 作為結束符


長度受限的字符串函數
? 長度受限的字符串函數接收一個顯示的長度參數用於
限定操作的字符數
字符串復制: :
char* strncpy(char* dst, const char* src, size_t len);
字符串連接: :
char* strncat(char* dst, const char* src , size_t len);
字符串比較 :
int strncmp(const char* s1, const char*s2 , size_t len);

? strncpy 只復制len 個字符到目標字符串
? 當源字符串的長度小於len 時 , 剩余的空間以’\0’ 填充 。
? 當源字符串的長度大於len 時 , 只有len 個字符會被復制 , 且
它將不會以’\0’ 結束 。
? strncat 最多從源字符串中復制len 個字符到目標串中
? strncat 總是在結果字符串後面添加’\0’
? strncat 不會用’\0’ 填充目標串中的剩余空間
? strncmp 只比較len 個字符是否相等

指針數組和數組指針分析


array 代表數組首元素的地址 , 那麼matrix 代表什麼 ?
array 和&array 的地址值相同 , 但是意義不同 , 那麼指向它們的
指針類型相同嗎 ?

? C 語言中通過typedef 為數組類型重命名
typedef type(name)[size];
? 數組類型 :
typedef int(AINT5)[5];
typedef float(AFLOAT10)[10];
? 數組定義 :
AINT5 iArray;
AFLOAT10 fArray;

數組指針
? 數組指針用於指向一個數組
? 數組名是數組首元素的起始地址 , 但並不是數組的起始地址
? 通過將取地址符& 作用於數組名可以得到數組的起始地址
? 可通過數組類型定義數組指針 : ArrayType* pointer;
? 也可以直接定義 :type (*pointer)[n];
? pointer 為數組指針變量名
? type 為指向的數組的類型

指針數組
? 指針數組是一個普通的數組
? 指針數組中每個元素為一個指針
? 指針數組的定義 :type* pArray[n];
? type* 為數組中每個元素的類型
? pArray 為數組名
? n 為數組大小
float* a[3]
float*
float*
float*

main 函數的參數
int main()
int main(int argc)
int main(int argc, char *argv[])
int main(int argc, char *argv[], char *env[])
argc – 命令行參數個數
argv – 命令行參數數組
env – 環境變量數組
? main 函數可以理解為操作系統調用的函數
? 在執行程序的時候可以向main 函數傳遞參數

小結
? 數組指針本質上是一個指針
? 數組指針指向的值是數組的地址

 

? 指針數組本質上是一個數組
? 指針數組中每個元素的類型是指針

 

多維數組和多維指針

指向指針的指針

? 指針變量在內存中會占用一定的空間

? 可以定義指針來保存指針變量的地址值

指向指針的指針
? 為什麼需要指向指針的指針 ?
? 指針在本質上也是變量
? 對於指針也同樣存在傳值調用與傳址調用

二維數組與二級指針
? 二維數組在內存中以一維的方式排布
? 二維數組中的第一維是一維數組
? 二維數組中的第二維才是具體的值
? 二維數組的數組名可看做常量指針
int a[3][3]
a[0]
a[1]
a[2]

數組名
? 一維數組名代表數組首元素的地址
int a[5] ? a 的類型為int*
? 二維數組名同樣代表數組首元素的地址
int m[2][5] ? m 的類型為int(*)[5]
結論 :
. 1. 二維數組名可以看做是指向數組的常量指針
. 2. 二維數組可以看做是一維數組
. 3. 二維數組中的每個元素都是同類型的一維數組

如何動態申請二維數組
以二維指針模擬

小結
C 語言中只有一維數組 , 而且數組大小必須在
編譯期就作為常數確定
C 語言中的數組元素可是任何類型的數據 , 即
數組的元素可以是另一個數組
C 語言中只有數組的大小和數組首元素的地址
是編譯器直接確定的

 

 

數組參數和指針參數分析


退化的意義
C 語言中只會以值拷貝的方式傳遞參數
? 當向函數傳遞數組時
? 將整個數組拷貝一份傳入函數
? 將數組名看做常量指針傳數組首元素地址
C 語言以高效為最初設計目標 , 在函數傳遞的時
候如果拷貝整個數組執行效率將大大下降 。

二維數組參數
? 二維數組參數同樣存在退化的問題
? 二維數組可以看做是一維數組
? 二維數組中的每個元素是一維數組
? 二維數組參數中第一維的參數可以省略
void f(int a[5]); ?? void f(int a[]); ?? void f(int* a);
void g(int a[3][3]); ?? void g(int a[][3]); ?? void g(int (*a)[3]);

等價關系
數組的指針 :char (*a)[4] 二維數組 :char a[3][4]

 

指針的指針 :int** a 指針數組 :int* a[5]

指針 :float* a 一維數組 :float a[5]

等效的指針參數 數組參數

注意事項
C 語言中無法向一個函數傳遞任意的多維數組
為了提供正確的指針運算 , 必須提供除第一維之外的
所有維長度限制
一維數組參數 – 必須提供一個標示數組結束位置的長度信息
二維數組參數 – 不能直接傳遞給函數
三維或更多維數組參數 – 無法使用

 

函數與指針分析

函數類型
? C C 語言中的函數有自己特定的類型
? 函數的類型由 返回值 , 參數類型 和 參數個數 共同決定
例 :int add(int i, int j) 的類型為int(int, int)
? C 語言中通過typedef 為函數類型重命名
typedef type name(parameter list)
? 例 :
typedef int f(int, int);
typedef void p(int);

函數指針
? 函數指針用於指向一個函數
? 函數名是執行函數體的入口地址
? 可通過函數類型定義函數指針 : FuncType* pointer;
? 也可以直接定義 :type (*pointer)(parameter list);
? pointer 為函數指針變量名
? type 為指向函數的返回值類型
? parameter list 為指向函數的參數類型列表

回調函數

? 回調函數是利用函數指針實現的一種調用機制
? 回調機制原理
? 調用者不知道具體事件發生的時候需要調用的具體函數
? 被調函數不知道何時被調用 , 只知道被調用後需要完成的任務
? 當具體事件發生時 , 調用者通過函數指針調用具體函數
? 回調機制的將調用者和被調函數分開 , 兩者互不依賴

指針閱讀技巧解析

? 右左法則
1. 從最裡層的圓括號中未定義的標示符看起
2. 首先往右看 , 再往左看
3. 當遇到圓括號或者方括號時可以確定部分類型 ,
並調轉方向
4. 重復2,3 步驟 , 直到閱讀結束

 

#include
int main()
{
int (*p2)(int*, int (*f)(int*));

int (*p3[5])(int*);

int (*(*p4)[5])(int*);

int (*(*p5)(int*))[5];
}

 

動態內存分配

為什麼使用動態內存分配
C 語言中的一切操作都是基於內存的
變量和數組都是內存的別名 , 如何分配這些內存由編
譯器在編譯期間決定
定義數組的時候必須指定數組長度
而數組長度是在編譯期就必須決定的

malloc 和free
? malloc 和free 用於執行動態內存分配和釋放

? malloc 所分配的是一塊連續的內存 , 以字節為單位 ,
並且不帶任何的類型信息
? free 用於將動態內存歸還系統
void* malloc(size_t size);
void free(void* pointer);
注意 :
? malloc 實際分配的內存可能會比請求的稍微多一點 , 但是不
能依賴於編譯器的這個行為
? 當請求的動態內存無法滿足時malloc 返回NULL
? 當free 的參數為NULL 時 , 函數直接返回

calloc 和realloc
? 你認識malloc 的兄弟嗎 ?
void* calloc(size_t num, size_t size);
void* realloc(void* pointer, size_t new_size);
? calloc 的參數代表所返回內存的類型信息
? calloc 會將返回的內存初始化為0
? realloc 用於修改一個原先已經分配的內存塊大小
? 在使用realloc 之後應該使用其返回值
? 當pointer 的第一個參數為NULL 時 , 等價於malloc

小結
? 動態內存分配是 C 語言中的強大功能
? 程序能夠在需要的時候有機會使用更多的內存
? malloc 單純的從系統中申請固定字節大小的內存
? calloc 能以類型大小為單位申請內存並初始化為0
? realloc 用於重置內存大小
程序中的棧
? 棧是現代計算機程序裡最為重要的概念之一
? 棧在程序中用於維護函數調用上下文 , 沒有棧就沒有
函數 , 沒有局部變量

? 棧保存了一個函數調用所需的維護信息
? 函數參數 , 函數返回地址
? 局部變量
? 函數調用上下文

程序中的堆
? 為什麼有了棧還需要堆 ?
? 棧上的數據在函數返回後就會被釋放掉 , 無法傳遞到函數外
部 , 如 : 局部數組
? 堆是程序中一塊巨大的內存空間 , 可由程序自由使用
? 堆中被程序申請使用的內存在程序主動釋放前將一直有效
堆空間通過申請才能獲得 !

? 系統對堆空間的管理方式
? 空閒鏈表法 , 位圖法 , 對象池法等等

程序中的靜態存儲區
? 程序靜態存儲區隨著程序的運行而分配空間 , 直到程
序運行結束
? 在程序的編譯期靜態存儲區的大小就已經確定
? 程序的靜態存儲區主要用於保存程序中的全局變量和
靜態變量
? 與棧和堆不同 , 靜態存儲區的信息最終會保存到可執
行程序中

小結
? 棧 , 堆和靜態存儲區是C語言程序常涉及的三個基本內存區
? 棧區主要用於函數調用的使用
? 堆區主要是用於內存的動態申請和歸還
? 靜態存儲區用於保存全局變量和靜態變量

野指針和內存操作分析

初識野指針
? 野指針通常是因為指針變量中保存的值不是一個合法的內存地址而造成的
? 野指針不是NULL 指針 , 是指向不可用內存的指針
? NULL 指針不容易用錯 , 因為if 語句很好判斷一個指針
是不是NULL
C 語言中沒有任何手段可以判斷一個指針是否為野指針 !

? 局部指針變量沒有被初始化

 

? 使用已經釋放過後的指針

? 指針所指向的變量在指針之前被銷毀


非法內存操作分析
? 結構體成員指針未初始化
? 沒有為結構體指針分配足夠的內存

內存初始化分析
? 內存分配成功 , 但並未初始化

內存越界分析
? 數組越界

內存洩露分析

多次指針釋放

使用已釋放的指針

? 用malloc 申請了內存之後 , 應該立即檢查指針值是否
為NULL , 防止使用值為NULL 的指針

? 牢記數組的長度 , 防止數組越界操作 , 考慮使用柔性
數組

? 動態申請操作必須和釋放操作匹配 , 防止內存洩露和
多次釋放

? free 指針之後必須立即賦值為NULL

認清函數的真面目

? 函數的由來
序 程序 = 據 數據 + 算法
C 序 程序 = 據 數據 + 函數

函數的意義
? 模塊化程序設計

面向過程的程序設計
? 面向過程是一種以過程為中心的編程思想
? 首先將復雜的問題分解為一個個容易解決的問題
? 分解過後的問題可以按照步驟一步步完成
? 函數是面向過程在C 語言中的體現
? 解決問題的每個步驟可以用函數來實現

聲明和定義
? 程序中的聲明可理解為預先告訴編譯器實體的存
在 , 如 : 變量 , 函數 , 等等
? 程序中的定義明確指示編譯器實體的意義
聲明和定義並不相同 !!!


函數參數

函數參數在本質上與局部變量相同 , 都是在棧上分配空間

? 函數參數的初始值是函數調用時的實參值

函數參數的求值順序依賴於編譯器的實現 !!

C 語言中大多數運算符對其操作數求值的順
的 序都是依賴於編譯器的實現的 !!!
int i = f() * g();
 

程序中的順序點

? 程序中存在一定的順序點
? 順序點指的是執行過程中修改變量值的最晚時刻
? 在程序達到順序點的時候 , 之前所做的一切操作必須反映
到後續的訪問中

? 每個完整表達式結束時
? &&, ||, ?:, 以及逗號表達式的每個運算對象計算之後
? 函數調用中對所有實際參數的求值完成之後( 進入函數體之前)

函數的缺省認定
? C 語言會默認沒有類型的函數參數為int

小結
? C 語言是一種面向過程的語言
? 函數可理解為解決問題的步驟
? 函數的實參並沒有固定的計算次序
? 順序點是C 語言中變量改變的最晚時機
? 函數定義時參數和返回值的缺省類型為int

函數遞歸與函數設計技巧

遞歸概述
? 遞歸是數學領域中概念在程序設計中的應用
? 遞歸是一種強有力的程序設計方法
? 遞歸的本質為函數內部在適當的時候調用自身

遞歸函數
?C 遞歸函數有兩個主要的組成部分 :
? 點 遞歸點 – 以不同參數調用自身
? 出口– 不在遞歸調用
f(x) =
1; x = 1
x * f(x-1); x > 1

小結
? C 語言中的遞歸函數必然會使用判斷語句
? 遞歸函數在需要編寫的時候定義函數的出口 , 否則
棧會溢出
? 遞歸函數是一種分而治之的思想

函數設計技巧
? 不要在函數中使用全局變量 , 盡量讓函數從意義上是一個
獨立的功能模塊
? 參數名要能夠體現參數的意義
void str_copy (char *str1, char *str2);
void str_copy (char *str_dest, char *str_src);

函數設計技巧
? 如果參數是指針 , 且僅作輸入參數用 , 則應在類型前加
const , 以防止該指針在函數體內被意外修改
void str_copy (char *str_dest, const char *str_src);

函數設計技巧
? 不要省略返回值的類型 , 如果函數沒有返回值 , 那麼
應聲明為void 類型

 

? 在函數體的“ 入口處” , 對參數的有效性進行檢查 , 對
指針的檢查尤為重要

? 語句不可返回指向“ 棧內存” 的“ 指針” , 因為該內存在
函數體結束時被自動銷毀

函數設計技巧
? 函數體的規模要小 , 盡量控制在80 行代碼之內
? 相同的輸入應當產生相同的輸出 , 盡量避免函數帶有“ 記
憶” 功能
? 避免函數有太多的參數 , 參數個數盡量控制在4 個以內

函數設計技巧
? 有時候函數不需要返回值 , 但為了增加靈活性 , 如支持鏈式表達 , 可
以附加返回值
char s[64];
int len = strlen(strcpy(s, “android”));
函數名與返回值類型在語義上不可沖突
char c;
c = getchar();
if(EOF == c)
{
//…
}

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved