輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 傳智播客C提高講義 傳智掃地僧 1程序內存模型 1.1就業班引言 1.1.1問題引出 企業需要能干活的人 C學到什么程度可以找工作
對於C/C++初級開發者怎么達到企業的用人標准
就業問題
問 老師有沒有一個框框
有沒有一個標准啊
我們學什么哪 C工程開發需要什么培養什么能力 成熟的、商業化的信息系統在分區、分層 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
信息系統的技術模型在分層 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 找出對我們初學者最近的那一層哪些能力是你入行前必須要掌握的 C項目開發的套路一套接口 //socket_client pool api 設計與實現
int sckClient_poolinit(void **handle);
int sckClient_getConnet(void *handle, void **hConnect);
int sckClient_sendData(void *hConnect, unsigned char *data, int dataLen);
int sckClient_getData(void *hConnect, unsigned char **data, int *dataLen);
int sckClient_getData_Free(void *hConnect, unsigned char *data);
int sckClient_putConnet(void *handle, void **hConnect);
int sckClient_pooldestory(void **handle);
總結尋找到學習的標准 培養兩種能力 接口的封裝和設計功能抽象和封裝
接口api的使用能力
接口api的查找能力快速上手 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 接口api的實現能力
建立正確程序運行內存布局圖印象圖
內存四區模型圖
函數調用模型圖 1.1.2總體課程安排 課程大綱 C提高
C++
數據結構
總體時間1個月 實用專題 總輕松入門實戰應用
形式1專題的形式錄制話題集中便於初學者學習
形式2知識點分段錄制、細致講解從根本上提高初學者水平
項目開發中的重要點做剖析
指針鐵律1 2 3 4 5 6 7 8 9 10===》企業用人標准
1.1.3學員要求 資料時間空間管理
工作經驗記錄和積累
臨界點
事物認知規律
挑戰 *p**p, ***p
提高課堂效率
課堂例子當堂運行。
錄制視頻說明不來看視頻 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 C/C++學習特點
Java學習、應用、做項目
C學習、理解、應用、做項目
多動手
不動手永遠學不會
關鍵點、關鍵時候進行強化訓練和考試 1.1.4小結 建立信心
接口的封裝和設計
指針教學多年實踐檢驗
心態放輕松了
分析有效時間
尊重事物認知規律、給自己一次機會 1.2學員聽課的標准 C語言學到什么程度就可以聽懂傳智播客就業班第一階段的課程了。
有沒有一個標准
選擇法或者冒泡法排序
在一個函數內排序
通過函數調用的方式排序
數組做函數參數的技術盲點和推演 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 1.3內存四區專題講座
1.3.1數據類型本質分析 數據類型概念 “類型”是對數據的抽象
類型相同的數據有相同的表示形式、存儲格式以及相關的操作
程序中使用的所有數據都必定屬於某一種數據類型 數據類型的本質思考 思考數據類型和內存有關系嗎
C/C++為什么會引入數據類型 數據類型的本質 數據類型可理解為創建變量的模具模子是固定內存大小的別名。 數據類型的作用編譯器預算對象變量分配的內存空間大小
程序舉例如何求數據類型的大小sizeof(int *)
請問數據類型可以有別名嗎數據類型可以自定義嗎 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 數據類型大小 int main()
{
int a = 10;
int b[10] ;
printf("int a:%d \n", sizeof(a));
printf("int a:%d \n", sizeof(int *));
printf("int b:%d \n", sizeof(b));
printf("int b:%d \n", sizeof(b[0]));
printf("int b:%d \n", sizeof(*b));
printf("hello.....\n");
getchar();
return 0;
}
sizeof是操作符不是函數sizeof測量的實體大小為編譯期間就已確定
數據類型別名 數據類型可以理解為固定大小內存塊的別名請問數據類型可以起別名嗎
int main()
{
//Teacher t1;
printf("Teacher:%d \n", sizeof(Teacher));
printf("u32:%d \n", sizeof(u32));
printf("u8:%d \n", sizeof(u8));
printf("hello.....\n");
getchar();
return 0;
} 數據類型的封裝 1、void的字面意思是“無類型”void *則為“無類型指針”void *可以
指向任何類型的數據。
2、用法1數據類型的封裝
int InitHardEnv(void **handle);
典型的如內存操作函數memcpy和memset的函數原型分別為 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
3、用法2void修飾函數返回值和參數僅表示無。
如果函數沒有返回值那么應該將其聲明為void型
如果函數沒有參數應該聲明其參數為void
int function(void)
{return 1;}
4、void指針的意義
C語言規定只有相同類型的指針才可以相互賦值
void*指針作為左值用於“接收”任意類型的指針
void*指針作為右值賦值給其它指針時需要強制類型轉換
int *p1 = NULL;
char *p2 = (char *)malloc(sizoeof(char)*20);
5、不存在void類型的變量
C語言沒有定義void究竟是多大內存的別名
6、擴展閱讀《void類型詳解.doc》 數據類型總結與擴展 1、數據類型本質是固定內存大小的別名是個模具c語言規定通過
數據類型定義變量。
2、數據類型大小計算sizeof
3、可以給已存在的數據類型起別名typedef
4、數據類型封裝概念void 萬能類型
思考1
C一維數組、二維數組有數據類型嗎int array[10]。
若有數組類型又如何表達又如定義
若沒有也請說明原因。
拋磚數組類型壓死初學者的三座大山 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 1、數組類型
2、數組指針
3、數組類型和數組指針的關系
思考2
C語言中函數是可以看做一種數據類型嗎
a)若是請說明原因
並進一步思考函數這種數據類型能再重定義嗎
b)若不是也請說明原因。
拋磚 1.3.2變量本質分析 變量概念 概念既能讀又能寫的內存對象稱為變量若一旦初始化后不能修改的對象則稱
為常量。
變量定義形式類型標識符, 標識符, … , 標識符;
例如
int x ;
int wordCut , Radius , Height ;
double FlightTime , Mileage , Speed ;
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 變量本質 1、程序通過變量來申請和命名內存空間int a = 0
2、通過變量名訪問內存空間
一段連續內存空間的別名是一個門牌號
3、 修改變量有幾種方法
1、直接
2、間接。內存有地址編號拿到地址編號也可以修改內存於是橫空出世
了編程案例
3、內存空間可以再取給別名嗎
4、數據類型和變量的關系 通過數據類型定義變量 5、總結及思考題
1 對內存可讀可寫2通過變量往內存讀寫數據3 不是向變量讀寫數據
而是向變量所代表的內存空間中寫數據。問變量跑哪去了
思考1變量三要素(名稱、大小、作用域)變量的生命周期
思考2C++編譯器是如何管理函數1函數2變量之間的關系的
====》引出兩個重要話題
內存四區模型
函數調用模型 重要實驗
int main333()
{
//
//2種方法通過變量直接操作內存
// 通過內存編號操作內存
int i = 0;
printf("&i:%d\n", &i);
*((int *)(1245024)) = 10;
printf("i:%d", i); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 printf("hello....\n");
getchar();
return 0;
} 1.3.3程序的內存四區模型 內存四區的建立流程
流程說明
1、操作系統把物理硬盤代碼load到內存
2、操作系統把c代碼分成四個區
3、操作系統找到main函數入口執行 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 各區元素分析 1.4函數調用模型
1.4.1基本原理
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
1.4.2內存四區模型和函數調用模型變量傳遞分析 1、一個主程序有n函數組成c++編譯器會建立有幾個堆區有幾個棧區
2、函數嵌套調用時實參地址傳給形參后C++編譯器如何管理變量的生命周期
分析函數A調用函數B通過參數傳遞的變量內存空間能用嗎
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 1.4.3提示學好C語言的關鍵 1.4.4如何建立正確的程序運行內存布局圖 內存四區模型&函數調用模型
函數內元素
深入理解數據類型和變量“內存”屬性
一級指針內存布局圖(int *,char*)
二級指針內存布局圖(int ** char **)
函數間
主調函數分配內存還是被調用函數分配內存
主調函數如何使用被調用函數分配的內存技術關鍵點指針做函
數參數
======》學習指針的技術路線圖 1.5內存四區強化訓練 01全局區訓練
char *p1= “abcdefg”;
02 堆棧區生命周期訓練
Char p1[]= “abcdefg”;
返回基本類型 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 返回非基本類型
03堆棧屬性訓練
測試heap生長方向
測試stack生長方向
Heap、stack生長方向和內存存放方向是兩個不同概念
野指針
Malloc得到指針釋放問題測試
free(p)
free(p+1)深入理解
1.6作業強化
訓練1划出內存四區 void main26()
{
char buf[100];
//byte b1 = new byte[100]; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 int a = 10; //分配4個字節的內存棧區也叫臨時區
int *p;//分配4個字節的內存
p = &a; //cpu執行的代碼放在代碼區
*p = 20; //
{
char *p = NULL; //分配4個字節的內存棧區也叫臨時區
p = (char *)malloc(100); //內存泄露概念
if (p != NULL)
{
free(p);
}
}
system("pause");
}
全局區代碼測試
char * getstring1()
{
char *p1 = "abcde";
return p1;
}
char * getstring2()
{
char *p2 = "abcde";
return p2; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 }
void main()
{
int i= 0;
//指針指向誰就把誰的地址賦給指針變量。
char *p1 = getstring1();
char *p2 = getstring2();
char ******* p3 = NULL; //p3 是個變量
//指針變量和它所執行的內存空間變量是兩個不同的概念
strcmp(p1, p2);
system("pause");
}
訓練2 划出內存四區 void main01()
{
char buf[100];
//byte b1 = new byte[100];
int a = 10; //分配4個字節的內存棧區也叫臨時區
int *p;//分配4個字節的內存
p = &a; //cpu執行的代碼放在代碼區
*p = 20; // 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
{
char *p2 = NULL; //分配4個字節的內存棧區也叫臨時區
p2 = (char *)malloc(100); //內存泄露概念
if (p2 != NULL)
{
free(p2);
//p2 = NULL;若不寫,實驗效果,分析原因
}
if (p2 != NULL)
{
free(p2);
}
}
system("pause");
}
2指針知識體系搭建 2.1前言 先從整體上把握指針的知識體系。然后突破1級指針、二級指針、多級指針。 2.2指針強化
鐵律1指針是一種數據類型 1 指針也是一種變量占有內存空間用來保存內存地址 測試指針變量占有內存空間大小 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 2*p操作內存 在指針聲明時*號表示所聲明的變量為指針
在指針使用時*號表示操作指針所指向的內存空間中的值
*p相當於通過地址(p變量的值)找到一塊內存然后操作內存
*p放在等號的左邊賦值給內存賦值
*p放在等號的右邊取值從內存獲取值 3指針變量和它指向的內存塊是兩個不同的概念 //含義1 給p賦值p=0x1111; 只會改變指針變量值不會改變所指的內容p = p +1;
//p++
//含義2 給*p賦值*p='a'; 不會改變指針變量的值只會改變所指的內存塊的值
//含義3 =左邊*p 表示給內存賦值 =右邊*p 表示取值含義不同切結
//含義4 =左邊char *p
//含義5 保證所指的內存塊能修改 4指針是一種數據類型是指它指向的內存空間的數據類型 含義1指針步長p++根據所致內存空間的數據類型來確定
p++=(unsigned char )p+sizeof(a);
結論指針的步長根據所指內存空間類型來定。
注意 建立指針指向誰就把把誰的地址賦值給指針。圖和代碼和二為一。
不斷的給指針變量賦值就是不斷的改變指針變量和所指向內存空間沒有任何關
系。 鐵律2間接賦值*p是指針存在的最大意義 1兩碼事指針變量和它指向的內存塊變量
2條件反射指針指向某個變量就是把某個變量地址否給指針
3*p間接賦值成立條件3個條件
a)2個變量通常一個實參一個形參
b) 建立關系實參取地址賦給形參指針
c) *p形參去間接修改實參的值 I
nt iNum = 0; //實參
int *p = NULL;
p = &iNum;
iNum = 1;
*p =2 ; //通過*形參 == 間接地改變實參的值
*p成立的三個條件 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
4引申函數調用時,用n指針形參改變n-1指針實參的值。
//改變0級指針int iNum = 1的值有2種方式
//改變1級指針eg char *p = 0x1111 的值有2種方式
//改變2級指針的eg char **pp1 = 0x1111 的值有2種方式
//函數調用時形參傳給實參用實參取地址傳給形參在被調用函數里面用
*p來改變實參把運算結果傳出來。
//指針作為函數參數的精髓。 鐵律3理解指針必須和內存四區概念相結合 1主調函數被調函數
a) 主調函數可把堆區、棧區、全局數據內存地址傳給被調用函數
b) 被調用函數只能返回堆區、全局數據
2內存分配方式
a) 指針做函數參數是有輸入和輸出特性的。 鐵律4應用指針必須和函數調用相結合指針做函數參數 編號
指針函數參數
內存分配方式級別+堆棧
主調函數
實參
被調函數
形參
備注
01
1級指針
做輸入
堆 分配 使用 一般應用禁
用
棧 分配 使用 常用
Int showbuf(char *p);
int showArray(int *array, int iNum)
02
1級指針
做輸出
棧 使用 結果傳出 常用
int geLen(char *pFileName, int *pfileLen);
03
2級指針
做輸入
堆 分配 使用 一般應用禁
用
棧 分配 使用 常用
int main(int arc ,char *arg[]); 指針數組
int shouMatrix(int [3][4], int iLine);二維字符串數組
04
2級指針
做輸出
堆 使用 分配 常用但不
建議用轉
化成02
int getData(char **data, int *dataLen);
Int getData_Free(void *data);
Int getData_Free(void **data); //避免野指針
05
3級指針
做輸出
堆 使用 分配 不常用
int getFileAllLine(char ***content, int *pLine); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 int getFileAllLine_Free(char ***content, int *pLine);
指針做函數參數問題的實質不是指針而是看內存塊內存塊是1維、2維。
1如果基礎類int變量不需要用指針
2若內存塊是1維、2維。 鐵律5一級指針典型用法指針做函數參數 一級指針做輸入
int showbuf(char *p)
int showArray(int *array, int iNum)
一級指針做輸出
int geLen(char *pFileName, int *pfileLen);
理解
主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存 鐵律6二級指針典型用法指針做函數參數 二級指針做輸入
int main(int arc ,char *arg[]); 字符串數組
int shouMatrix(int [3][4], int iLine);
二級指針做輸出
int Demo64_GetTeacher(Teacher **ppTeacher);
int Demo65_GetTeacher_Free(Teacher **ppTeacher);
int getData(char **data, int *dataLen);
Int getData_Free(void *data);
Int getData_Free2(void **data); //避免野指針
理解
主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存 鐵律7三級指針輸出典型用法 三級指針做輸出
int getFileAllLine(char ***content, int *pLine);
int getFileAllLine_Free(char ***content, int *pLine);
理解 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存
鐵律8雜項指針用法幾點擴充 1野指針 2種free形式
int getData(char **data, int *dataLen);
int getData_Free(void *data);
int getData_Free2(void **data);
22次調用
主調函數第一次調用被調用函數求長度根據長度分配內存調用被調用
函數。
3返回值char */int/char **
4C程序書寫結構
商業軟件每一個出錯的地方都要有日志日志級別
鐵律9一般應用禁用malloc/new
2.3接口封裝設計思想引導 基於socketclient客戶端接口設計與實現仿真模擬 2.4附錄 【王保明老師經典語錄】
1指針也是一種數據類型指針的數據類型是指它所指向內存空間的數據類型
2間接賦值*p是指針存在的最大意義
3理解指針必須和內存四區概念相結合
4應用指針必須和函數調用相結合指針做函數參數
指針是子彈函數是槍管子彈只有沿着槍管發射才能顯示它的威力指針的學習重點不言
而喻了吧。接口的封裝和設計、模塊的划分、解決實際應用問題它是你的工具。 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 5指針指向誰就把誰的地址賦給指針
6指針指向誰就把誰的地址賦給指針用它對付鏈表輕松加愉快
7鏈表入門的關鍵是分清楚鏈表操作和輔助指針變量乊間的邏輯關系
8C/C++語言有它自己的學習特點若java語言的學習特點是學習、應用、上項目那
么C/C++語言的學習特點是學習、理解、應用、上項目。多了一個步驟吧。
9學好指針才學會了C語言的半壁江山另外半壁江山在哪里呢你猜精彩剖析在課堂。
10) 理解指針關鍵在內存沒有內存哪來的內存首地址沒有內存首地址哪來的指針啊。 3字符串和一級指針內存模型專題 3.1字符串基本操作
字符數組初始化方法 int main11()
{
//1 大{}號法初始化列表
//數組初始化有2種方法默認元素個數、指定元素個數
char buf1[] = {'a', 'b', 'c', 'd', 'e'}; //若沒有指定長度默認不分配零
//若指定長度不夠報錯buf長度多於初始化個數會自動補充零
char buf2[6] = {'a', 'b', 'c', 'd', 'e'};
char buf3[6] = {'a', 'b', 'c', 'd', 'e'};
//char buf4[5] = {'a', 'b', 'c', 'd', 'e'};
printf("buf3:%s", buf3);
system("pause");
}
在C語言中使用字符數組來模擬字符串
C語言中的字符串是以’\0’結束的字符數組
C語言中的字符串可以分配於棧空間堆空間或者只讀存儲區
//在C語言中使用字符數組來模擬字符串
//C語言中的字符串是以’\0’結束的字符數組
//C語言中的字符串可以分配於棧空間堆空間或者只讀存儲區 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 int main12()
{
//1 用字符串來初始化數組
char buf2[] = {'a', 'b','c','d','\0'};
//2 字符串常量初始化一個字符數組
char buf3[] = {"abcde"}; //結論會補充零
char buf4[] = "abcde";
char buf5[100] = "abcde";
printf(" strlen(buf5) :%d \n", strlen(buf5));
printf(" sizeof(buf4) :%d \n", sizeof(buf5));
printf(" sizeof(buf4) :%d \n", sizeof(buf4));
}
//strlen()求字符串的長度注意字符串的長度不包含\0
//sizeof(類型)字符串類型的大小包括\0
02Sizeof與strlen的區別 數組法和指針法操作字符串 03
字符串操作
數組法下標法
字符數組名是個指針是個常量指針
字符數組名代表字符數組首元素的地址不代表整個數組的。
如果代表這個數組那需要數組數據類型的知識
下期分解
//字符串操作方法數組下標法指針法
int main13()
{
int i = 0;
char buf5[100] = "abcde";
char *p = NULL;
//下標法
for (i=0; i<100; i++)
{
printf("%c", buf5[i]);
}
printf("\n");
//指針法1
for (i=0; i<100; i++)
{ 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 printf("%c", *(buf5+i));
}
//buf5是個指針是個常量指針
//指針法2
printf("\n");
p = buf5;
for (i=0; i<100; i++)
{
printf("%c", *(p+i));
}
//buf5是個指針是個常量指針
}
推演過程為i變0+I, 去[]號加*號
//其實本質指針*p間接尋址操作內存
//[] 編譯器為我們做了*p操作而已
3.2字符串做函數參數 深入理解指針。。。。。。。。。。。的關鍵是什么
注意
指針和數組的巨大區別
char *p = “abcdefg”;
Char *buf = “abcdefg”;
一維字符串內存模型兩種
void copy_str01(char *from, char *to)
{
for (; *from!='\0'; from++, to++)
{
*to = *from;
}
*to = '\0';
}
void copy_str02(char *from, char *to)
{
while(*from!='\0')
{
*to++ = *from++;
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 *to = '\0';
}
void copy_str03(char *from, char *to)
{
while( (*to=*from) !='\0')
{
to++;
from++;
}
}
void copy_str04(char *from, char *to)
{
while( (*to++=*from++) !='\0')
{
;
}
}
int copy_str05_good(const char *from, char *to)
{
if (from==NULL || to==NULL)
{
printf("func copy_str05_good() err. (from==NULL || to==NULL)\n");
return -1;
}
while( (*to++=*from++) !='\0')
{
;
}
return 0;
}
典型錯誤知多少
char *str_cnct(char *x, char* y) /*簡化算法*/
{
char str3[80];
char *z=str3; /*指針z指向數組str3*/
while(*z++=*x++);
z--; /*去掉串尾結束標志*/
while(*z++=*y++);
z=str3; /*將str3地址賦給指針變量z*/
return(z); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 }
修改字符常量結果會如何
Char *p = “abcdefg”;
Modify p[1] = ‘1’;
04字符串操作易錯
//你往哪里輸入數據
int main()
{
char buf[2000];
char *p = NULL;
p = buf;
printf("\n請輸入一個字符串:");
scanf("%s", p);
printf("%s", p);
getchar();
getchar();
return 0;
} 3.3庫函數api 快速的上手
api是一種能力
建立正確的程序運行示意圖內存四區及函數調用堆棧圖是根本保障
int main31()
{
char buf1[100];
char buf2[200];
strcpy(buf1, "111");
printf("%s", strcat(buf1, "222"));
getchar();
return 0;
}
int main32()
{
char *string1 = "1234567890";
char *string2 = "747DC8";
int length;
//在字符str1中查找與str2中任意字符有公共交集的位置
length = strcspn(string1, string2);
printf("Character where strings intersect is at position %d\n", length);
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 getchar();
return 0;
}
//strnset函數有錯誤
//測試程序修改如下
int main33()
{
char string[] = "abcdefghijklmnopqrstuvwxyz";
char letter = 'x';
printf("string before strnset: %s\n", string);
strnset(string, letter, 13);
printf("string after strnset: %s\n", string);
getchar();
return 0;
}
int main44()
{
char *string1 = "abcdefghijklmnopqrstuvwxyz";
char *string2 = "onm";
char *ptr;
ptr = strpbrk(string1, string2);
if (ptr)
printf("strpbrk found first character: %c\n", *ptr);
else
printf("strpbrk didn't find character in set\n");
getchar();
return 0;
}
int main55()
{
char input[16] = "abc,d";
char *p;
/* strtok places a NULL terminator
in front of the token, if found */
p = strtok(input, ",");
if (p) printf("%s\n", p);
/* A second call to strtok using a NULL
as the first parameter returns a pointer
to the character following the token */
p = strtok(NULL, ","); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 if (p) printf("%s\n", p);
getchar();
return 0;
}
//典型的狀態函數
int main()
{
char str[] = "now # is the time for all # good men to come to the # aid of their country";
//char delims[] = "#";
char *delims = "#";
char *result = NULL;
result = strtok( str, delims );
while( result != NULL ) {
printf( "result is \"%s\"\n", result );
result = strtok( NULL, delims );
}
printf("----------==========----------\n");
printf("%s", str);
getchar();
return 0;
}
3.4字符串相關一級指針內存模型 void main()
{
char buf[20]= "aaaa";
char buf2[] = "bbbb";
char *p1 = "111111";
char *p2 = malloc(100); strcpy(p2, "3333");
system("pause");
return ;
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
3.5項目開發字符串模型 strstr-whiledowhile模型 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 兩頭堵模型 字符串反轉模型
3.6一級指針(char *)易錯模型分析 01char *字符串做函數參數出錯模型分析 建立一個思想是主調函數分配內存還是被調用函數分配內存
//不要相信主調函數給你傳的內存空間你可以寫。。。。。。一級指針你懂了。
但是二級指針你就不一定懂。。。拋出。。。。。。。。。
void copy_str21(char *from, char *to)
{
if (*NULL= '\0' || *to!=’\0’)
{
Printf(“func copy_str21() err\n”);
return; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 }
for (; *from!='\0'; from++, to++)
{
*to = *from;
}
*to = '\0';
}
//字符串逆序
int main()
{
//char p[1024] ={0};
char *p ={0}; p = NULL;
char to[100];
copy_str21(p, to);
C語言中沒有你不知道的只有你不會調
Java語言中沒有你不會調的只有你不知道 不斷修改內存指針變量
02越界 越界語法級別的越界 char buf[3] = "abc";
03不斷修改指針變量的值 越界
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 void copy_str_err(char *from, char *to)
{
for (; *from!='\0'; from++, to++)
{
*to = *from;
}
*to = '\0';
printf("to:%s", to);
printf("from:%s", from);
}
04你向外面傳遞什么 1
、臨時str3內存空間
// char *str_cnct(x,y) /*簡化算法*/
// char *x,*y;
char *str_cnct(char *x, char* y) /*簡化算法*/
{
char str3[80];
char *z=str3; /*指針z指向數組str3*/
while(*z++=*x++);
z--; /*去掉串尾結束標志*/ 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 while(*z++=*y++);
z=str3; /*將str3地址賦給指針變量z*/
return(z);
}
2、經驗要學習
while(*z++=*x++);
z--; /*去掉串尾結束標志*/
char *str_cnct(char *x, char* y) /*簡化算法*/
{
char * str3= (char *)malloc(80)
char *z=str3; /*指針z指向數組str3*/
while(*z++=*x++);
z--; /*去掉串尾結束標志*/
while(*z++=*y++);
z=str3; /*將str3地址賦給指針變量z*/
return(z);
}
char *str_cnct(char *x, char* y) /*簡化算法*/
{
If (x == NULL)
{
Return NULL;
}
char * str3= (char *)malloc(80)
char *z=str3; /*指針z指向數組str3*/
while(*z++=*x++);
z--; /*去掉串尾結束標志*/
while(*z++=*y++);
z=str3; /*將str3地址賦給指針變量z*/ note:
return(z);
}
Main ()
{
Char *p = str_cnct(“abcd”, “ddeee”);
If (p != NULL) {Free(p) ;p = NULL}//yezhizhen
}
int getKeyByValude(char *keyvaluebuf, char *keybuf, char *valuebuf, int *
valuebuflen)
{
int result = 0;
char *getbuf = new char[100]; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 memset(getbuf, 0, sizeof(getbuf));
char *trimbuf = new char[100];
memset(trimbuf, 0, sizeof(trimbuf));
int destlen = strlen(keyvaluebuf);
if (keybuf == NULL || keyvaluebuf == NULL || valuebuf == NULL/* || valuebuflen
== NULL*/)
{
result = -1;
return result;
}
if (strstr(keyvaluebuf, keybuf) == NULL)
{
result = -1;
return result;
}
else
{
for (int i = 0; i < destlen; i++)
{
if (*keyvaluebuf == '=')
{
*keyvaluebuf++;
break;
}
keyvaluebuf++;
}
while(*keyvaluebuf != '\0')
{
*valuebuf = *keyvaluebuf;
valuebuf++;
keyvaluebuf++;
}
*valuebuf = '\0';
}
int len = strlen(valuebuf);
return result;
}
//char *p = "abcd11111abcd2222abcdqqqqq"; //
字符串中"abcd"出現的次數。 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 //要求你自己寫一個函數接口並且寫出測試用例。
//完成功能為求出“abcd”字串出現的次數
//輸入
int getSubCount(char *str, char *substr, int * mycount)
{
int ret = 0;
char *p = str;
char *sub = substr;
int count = 0;
if (str==NULL || substr==NULL || mycount == NULL)
{
ret = -1;
return ret;
}
//char *p = "abcd11111abcd2222abcdqqqqqabcd";
//char *p2 = NULL;
//p2 = p;
do
{
p = strstr(p, sub);
if (p!= NULL)
{
count++;
//++后綴操作符優先級高所以先執行*p操作然后地址++
*mycount++;
p = p + strlen(sub);
}
else
{
break;
}
} while (*p != '\0');
//printf("count:%d \n", count);
//mycount是實參的地址 *實參的地址
*mycount = count;
return ret;
}
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 05 看圖
06重復的錯誤何時休 #include "stdio.h"
#include "stdlib.h"
#include "string.h"
void copy_str21_modify(char *from, char *to)
{
int i = 0;
if (*from != '\0')
{
printf("ddddd");
}
for (; *from!='\0'; from++, to++)
{
*to = *from;
}
*to = '\0';
printf("to:%s", to);
printf("from:%s", from);
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
void copy_str_err(char *from, char *to)
{
for (; *from!='\0'; from++, to++)
{
*to = *from;
}
*to = '\0';
printf("to:%s", to);
printf("from:%s", from);
}
//字符串逆序
int mainaaaa()
{
char buf1[100] = "abcdefg";
char to[100];
copy_str_err(buf1, to);
}
//越界場景
int main00000000000()
{
char from[5] = "abcde";
printf("\n %s",from);
getchar();
return 0;
}
3.7const專題 1、 const基礎知識用法、含義、好處、擴展
int main()
{
const int a; //
int const b;
const char *c;
char * const d; char buf[100]
const char *const e ; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
return 0;
}
Int func1(const )
初級理解const是定義常量==》const意味着只讀
含義
//第一個第二個意思一樣代表一個常整形數
//第三個 c是一個指向常整形數的指針(所指向的內存數據不能被修改但是本身可以修改)
//第四個 d 常指針指針變量不能被修改但是它所指向內存空間可以被修改
//第五個 e一個指向常整形的常指針指針和它所指向的內存空間均不能被修改
Const好處
//合理的利用const
//1指針做函數參數可以有效的提高代碼可讀性減少bug
//2清楚的分清參數的輸入和輸出特性
結論
//指針變量和它所指向的內存空間變量是兩個不同的概念。。。。。。
//看const 是放在*的左邊還是右邊看const是修飾指針變量還是修飾所指向的內存空變量 3.8考試強化訓練 1、有一個字符串開頭或結尾含有n個空格” abcdefgdddd ”欲去掉前后空格
返回一個新字符串。
要求1請自己定義一個接口函數並實現功能70分
要求2編寫測試用例。30分
int trimSpace(char *inbuf, char *outbuf);
2、有一個字符串”1a2b3d4z”,
要求寫一個函數實現如下功能
功能1把偶數位字符挑選出來組成一個字符串1。valude20分
功能2把奇數位字符挑選出來組成一個字符串2valude 20
功能3把字符串1和字符串2通過函數參數傳送給main並打印。
功能4主函數能測試通過。
int getStr1Str2(char *souce, char *buf1, char *buf2);
3、鍵值對”key = valude”字符串在開發中經常使用
要求1請自己定義一個接口實現根據key獲取valude40分
要求2編寫測試用例。30分
要求3鍵值對中間可能有n多空格請去除空格30分
注意鍵值對字符串格式可能如下
“key1 = valude1”
“key2 = valude2 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 “key3 = valude3”
“key4 = valude4”
“key5 = “
“key6 =“
“key7 = “
int getKeyByValude(char *keyvaluebuf, char *keybuf, char *valuebuf, int
*valuebuflen);
int main()
{
getKeyByValude(“key1 = valude1”, ”key1”, buf, &len);
} 4二級指針和多級指針專題 4.1二級指針的三種內存模型
4.1.1二級指針輸入和輸出模型
4.1.2二級指針三種內存模型
思路先把二級指針的所用內存模型練一遍然后我們再探究它的內存模型及本質。
工程開發中二級指針的典型用法
二級指針的第一種內存模型
二級指針的第二種內存模型
二級指針的第三種內存模型
4.1.3強化兩個輔助指針變量挖字符串 思想分享強化訓練到極致 眼高手低練習到極致高屋建瓴
一看都會一練習都錯
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 眼高手低練習到極致高屋建瓴 4.1.4二級指針內存模型建立 void main2()
{
int i = 0;
//指針數組
char * p1[] = {"123", "456", "789"};
//二維數組
char p2[3][4] = {"123", "456", "789"};
//手工二維內存
char **p3 = (char **)malloc(3 * sizeof(char *)); //int array[3];
for (i=0; i<3; i++)
{
p3[i] = (char *)malloc(10*sizeof(char)); //char buf[10]
sprintf(p3[i], "%d%d%d", i, i, i);
}
}
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 4.2數組類型和多維數組本質
4.2.1數組概念 概念 1元素類型角度數組是相同類型的變量的有序集合測試指針變量占有內存空間大
小
2內存角度聯系的一大片內存空間
數組初始化 //數組元素的個數可以顯示或隱式指定
//分析數組初始化{0}與memset比較
int main()
{
int i = 0;
int a[10] = {1,2}; //其他初始化為0
int b[] = {1, 2};
int c[20] = {0};
for (i=0; i<10; i++)
{
printf("%d ", a[i]);
}
memset(a, 0, sizeof(a));
getchar();
return 0;
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 數組名的技術盲點 1數組首元素的地址和數組地址是兩個不同的概念
2數組名代表數組首元素的地址它是個常量。
解釋如下變量本質是內存空間的別名一定義數組就分配內存
內存就固定了。所以數組名起名以后就不能被修改了。
3數組首元素的地址和數組的地址值相等
4、怎么樣得到整個一維數組的地址 C語言規定:
Int a[10];
printf("得到整個數組的地址a: %d \n", &a);
printf("數組的首元素的地址a: %d \n", a); 怎么樣表達int a[10]這種數據類型那 4.2.2數組類型、數組指針類型、數組指針類型變量 數組類型 1數據類型分為基礎、非基礎思考角度應該發生變化
2 C語言中的數組有自己特定的類型
數組的類型由元素類型和數組大小共同決定
例int array[5]的類型為int[5] /*
typedef int(MYINT5)[5]; //int
typedef float(MYFLOAT10)[10];
數組定義
MYINT5i Array; int array[5];
MYFLOAT10fArray
*/ 3定義數組類型並用數組類型定義變量
int main()
{
typedef int(MYINT5)[5];
int i = 0;
MYINT5 array;
for (i=0; i<5; i++)
{
array[i] = i; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 }
for (i=0; i<5; i++)
{
printf("%d ", array[i]);
}
getchar();
return 0;
} 數組指針類型 數組指針用於指向一個數組
int a[10]
數組名是數組首元素的起始地址但並不是數組的起始地址
通過將取地址符&作用於數組名可以得到整個數組的起始地址
//定義數組指針有兩種
1通過數組類型定義數組指針:
typedef int(ArrayType)[5]; int *a
ArrayType* pointer;
2) 聲明一個數組指針類型 typedef int (*MyPointer)[5];
MyPointer myPoint;
3直接定義int (*pointer)[n];
pointer 為數組指針變量名
type 為指向的數組的類型
n 為指向的數組的大小
注意這個地方是type類型比如int *pointer[10]
數組指針用數組類型加*定義一個數組指針
//1
{
int a[5];
//聲明一個數組類型
typedef int(MYINT5)[5]; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 //用數組類型加*定義一個數組指針變量
MYINT5 *array;
array = &a;
for (i=0; i<5; i++)
{
(*array)[i] = i;
}
//
for (i=0; i<5; i++)
{
printf("\n%d %d", a[i], (*array)[i]);
}
}
數組指針定義一個數組指針類型然后用類型定義變量
{
int b[5];
//聲明一個數組指針類型
typedef int (*MyPointer)[5];
//用數組指針類型去定義一個變量
MyPointer mypoint ;
mypoint= &b;
for (i=0; i<5; i++)
{
(*mypoint)[i] = i;
}
//
for (i=0; i<5; i++)
{
printf("\n%d %d", b[i], (*mypoint)[i]);
}
}
//3數組指針直接定義一個數組指針變量
{
int c[5];
//直接聲明一個數組指針變量
int (*pointer)[5] = &c;
for (i=0; i<5; i++)
{
(*pointer)[i] = i;
}
for (i=0; i<5; i++) 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 {
printf("\n%d %d", c[i], (*pointer)[i]);
}
} 4.2.3多維數組本質技術推演 int a[10];
char myarray[3][5] PK int (*p)[5]
myarray名稱到底是什么
多維數組char a[i][j] ==> *(*(a+i)+j)轉換技巧分析
void main222()
{
int a[3][5];
int c[5]; //&c + 1;
int b[10]; //b代表數組首元素的地址&b代表這個數組的地址&b+1相當於指針后移4*10
個單位
//a代表什么什么那a是一個數組指針指向低維數組的指針
//a +1;
printf("a:%d, a+1:%d \n", a, a+1); //4*5
{
int i=0, j = 0, tmp = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
a[i][j] = ++tmp;
}
}
printf("\n");
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
printf("%d \n", a[i][j]);
}
}
}
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 //a的本質是一個數組指針每次往后跳一維的維數
{
int i = 0, j = 0;
//定義了一個數組指針變量
int (*myArrayPoint)[5] ; //告訴編譯給我開辟四個字節內存
myArrayPoint = a;
printf("\n");
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
//myArrayPoint[i][j] = ++tmp;
printf("%d \n", myArrayPoint[i][j]);
}
}
}
/*
char cbuf[30]; // cbuf1級指針代表數組首元素的地址。。。&cbuf二級指針代表整
個數組的地址
char array[10][30]; //array是二級指針
(array+i) //相當於整個第i行的數組地址 //二級指針&cbuf
*(array+i)//一維數組的首地址 cbuf
*(array+i)+j //相當於第i行第j列的地址了把。。。。&array[i][j]
*(*(array+i)+j) //相當於第i行第j列的地址了把。。。。<====>array[i][j]
*/
system("pause");
}
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
結論a是一個指向int myarray[5]的數組指針 a+1 向后跳5*4跳一行。 4.2.4多維數組做函數參數退化原因大剖析
//證明一下多維數組的線性存儲
//線性打印
void printfArray411(int *array, int num)
{
int i = 0;
for (i=0; i<num ; i++)
{
printf("%d ", array[i]);
}
}
void printfArray412(int (*array)[5], int num)
{
return ;
}
void printfArrr333(int c[3][4][5])
{
return ;
}
void main()
{
int a[3][5]; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 int c[3][4][5];
int i , j = 0;
int tmp = 0;
for (i=0; i<3; i++)
{
for (j=0; j<5; j++)
{
a[i][j] = tmp ++;
}
}
printfArray411((int *)a, 15);
system("pause");
}
4.2.5多維數組做函數參數技術推演 1
、 C語言中只會以機械式的值拷貝的方式傳遞參數實參把值傳給形參
int fun(char a[20], size_t b)
{
printf("%d\t%d",b,sizeof(a));
}
原因1高效
原因2
C語言處理a[n]的時候它沒有辦法知道n是幾它只知道&n[0]是多少它的值作為參數傳
遞進去了
雖然c語言可以做到直接int fun(char a[20])然后函數能得到20這個數字但是C沒有這
么做。
2、二維數組參數同樣存在退化的問題
二維數組可以看做是一維數組
二維數組中的每個元素是一維數組
二維數組參數中第一維的參數可以省略
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]);
3、等價關系
數組參數 等效的指針參數
一維數組 char a[30] 指針 char* 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 指針數組 char *a[30] 指針的指針 char **a
二維數組 char a[10][30] 數組的指針 char(*a)[30]
4.3指針數組的應用場景 指針數組的兩種用法菜單命令行
操作系統拉起應用在框架下干活
字符數組自我結束標志
// NULL 0 '\0'
4.4強化訓練 課堂考試“上黑板”
int sort(char *p[], int count, char **p, int *ncount);
int sort(char *p[], int count, char (*p)[30], int *ncount);
int sort(char (*p)[30], int ncount, char **p, int *ncount);
//把第一種內存模型第二種內存模型結果copy到第三種內存模型中並排序打印
char ** sort(char **p1, int num1, char (*p)[30], int num2, int *num3 );
//
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
int getArray3_Free(char **p3, int p3num)
{
int i;
if (p3 == NULL)
{
return -1;
}
for (i=0; i<p3num; i++)
{
if (p3[i]!=NULL)
{
free(p3[i]);
}
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 free(p3);
}
int getArray3_Free2(char ***p3, int p3num)
{
int i;
char **tmp = NULL;
if (p3 == NULL)
{
return -1;
}
tmp = *p3;
for (i=0; i<p3num; i++)
{
if (tmp[i]!=NULL)
{
free(tmp[i]);
}
}
free(tmp);
*p3 = NULL; //通過間接賦值去間接的修改實參的值成0
}
int getArray3_2(char **myp1, int num1, char (*myp2)[30], int num2, char *** myp3,
int *num3)
{
int ret = 0;
int i,j;
int tmpNum3 = 0;
char **tmpp3 = NULL;
char *temp;
/*
printf("111111111");
if (*myp3 ==NULL )
{
printf("222222222");
} */
printf("33333"); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 if (myp1==NULL || myp2==NULL ||num3==NULL || myp3==NULL)
{
ret = -1;
return ret;
}
//准備內存
tmpNum3 = num1 + num2;
//分配第一維
tmpp3 = (char **)malloc(tmpNum3 * sizeof(char *));
if (tmpp3 == NULL)
{
return NULL;
}
//分配第二維把第一種內存模型數據和第二種內存模型數據copy到第3中內存模型中
for (i=0; i<num1; i++)
{
tmpp3[i] = (char *)malloc(strlen(myp1[i])+1);
if (tmpp3[i]==NULL)
{
puts("out of space");
return NULL;
}
strcpy(tmpp3[i],myp1[i]);
}
for (j=0;j<num2;j++,i++)
{
tmpp3[i]=(char *)malloc(strlen(myp2[j]) + 1); //note modify
if (tmpp3[i]==NULL)
{
puts("out of space");
return NULL;
}
strcpy(tmpp3[i],myp2[j]);
}
//排序
for (i=0;i<tmpNum3;i++)
{
for (j=i+1;j<tmpNum3;j++)
{
if (strcmp(tmpp3[i],tmpp3[j])>0)
{
temp=tmpp3[i]; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 tmpp3[i]=tmpp3[j];
tmpp3[j]=temp;
}
}
}
//通過間接賦值把結果甩給實參
*num3=tmpNum3;
*myp3 = tmpp3; //*0 = 100;
return ret;
}
char **getArray3(char **myp1, int num1, char (*myp2)[30], int num2, int *num3)
{
int i,j;
int tmpNum3 = 0;
char **tmpp3 = NULL;
char *temp;
if (myp1==NULL || myp2==NULL ||num3==NULL )
{
return NULL;
}
//准備內存
tmpNum3 = num1 + num2;
//分配第一維
tmpp3 = (char **)malloc(tmpNum3 * sizeof(char *));
if (tmpp3 == NULL)
{
return NULL;
}
//分配第二維把第一種內存模型數據和第二種內存模型數據copy到第3中內存模型中
for (i=0; i<num1; i++)
{
tmpp3[i] = (char *)malloc(strlen(myp1[i])+1);
if (tmpp3[i]==NULL)
{
puts("out of space");
return NULL;
}
strcpy(tmpp3[i],myp1[i]);
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 for (j=0;j<num2;j++,i++)
{
tmpp3[i]=(char *)malloc(strlen(myp2[j]) + 1); //note
if (tmpp3[i]==NULL)
{
puts("out of space");
return NULL;
}
strcpy(tmpp3[i],myp2[j]);
}
//排序
for (i=0;i<tmpNum3;i++)
{
for (j=i+1;j<tmpNum3;j++)
{
if (strcmp(tmpp3[i],tmpp3[j])>0)
{
temp=tmpp3[i];
tmpp3[i]=tmpp3[j];
tmpp3[j]=temp;
}
}
}
*num3=tmpNum3;
return tmpp3;
}
void main()
{
int num3 = 0, i = 0;
int ret = 0;
char *p1[] = {"222222", "1111111", "33333333"};
char p2[4][30] = {"bbbbb", "aaaaa", "zzzzzz", "ccccccc"};
char **p3 = NULL;
char ***myerrp3 = NULL;
//p3 = getArray3(p1, 3, p2, 4, &num3);
//ret = getArray3_2(p1,3, p2, 4, &p3, &num3);
ret = getArray3_2(p1,3, p2, 4, 0, &num3); //錯誤做法
if (ret != 0)
{
return ; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 }
for (i=0; i<num3; i++)
{
printf("%s \n", p3[i]);
}
//getArray3_Free(p3, num3);
// p3=NULL;
getArray3_Free2(&p3, num3);
printf("p3:%d \n", p3);
system("pause");
}
5結構體專題 5.1大綱 01、結構體類型定義及結構體變量定義
char c1,char c2, char name[62]; int age
char name[62]; int age,char c1,char c2
結構體變量的引用 .
結構體變量的指針 ->
02、結構體做函數參數
結構體賦值編譯器行為研究
結構體變量做函數參數 PK 結構體指針做函數參數
結構體做函數參數//結構體賦值和實參形參賦值行為研究
內存四區調用圖畫法
//從鍵盤獲取數據給結構體變量初始化並排序打印結構體
stack上分配結構數組和heap上分配結構體數組
03、工程開發中結構體開發的常見模型及典型錯誤用法
結構體嵌套一級指針
結構體嵌套二級指針
04、結構體中的深拷貝淺拷貝
問題拋出
解決方法 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 5.2結構體類型定義及變量定義 /*
//結構體類型定義及結構體變量定義
結構體是一種構造數據類型
用途把不同類型的數據組合成一個整體-------自定義數據類型
結構體類型定義
*/
//聲明一個結構體類型
struct _Teacher
{
char name[32];
char tile[32];
int age;
char addr[128];
};
//定義結構體變量的方法
/*
1)定義類型用類型定義變量
2)定義類型的同時定義變量
3直接定義結構體變量
*/
struct _Student
{
char name[32];
char tile[32];
int age;
char addr[128];
}s1, s2; //定義類型的同時定義變量
struct
{
char name[32];
char tile[32];
int age;
char addr[128];
}s3,s4; //直接定義結構體變量
//初始化結構體變量的幾種方法
//1)
struct _Teacher t4 = {"name2", "tile2", 2, "addr2"}; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 //2)
struct Dog1
{
char name[32];
char tile[32];
int age;
char addr[128];
}d5 = {"dog", "gongzhu", 1, "ddd"};
//3)
struct
{
char name[32];
char tile[32];
int age;
char addr[128];
}d6 = {"dog", "gongzhu", 1, "ddd"};
//結構體變量的引用
int main11()
{
//struct _Teacher t1, t2;
//定義同時初始化
{
struct _Teacher t3 = {"name2", "tile2", 2, "addr2"};
printf("%s\n", t3.name);
printf("%s\n", t3.tile);
}
//用指針法和變量法分別操作結構體
{
struct _Teacher t4;
struct _Teacher *pTeacher = NULL;
pTeacher = &t4;
strcpy(t4.name, "wangbaoming");
strcpy(pTeacher->addr, "ddddd");
printf("t4.name:%s\n", t4.name);
}
printf("hello....\n");
getchar(); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 return 0;
}
5.3結構體做函數參數及結構體數組 //
測試兩個結構體變量之間可以copy數據嗎
//t2 = t1;
//測試實參傳給形參編譯器的行為
//結果很出人意外
//聲明一個結構體類型
struct _MyTeacher
{
char name[32];
char tile[32];
int age;
char addr[128];
};
void printfMyteach01(struct _MyTeacher t)
{
printf("\nt.name:%s", t.name);
}
void printfMyteach02(struct _MyTeacher *t)
{
printf("\nt->name:%s", t->name);
}
//結構體賦值和實參形參賦值行為研究
int main21()
{
struct _MyTeacher t1, t2;
memset(&t1, 0, sizeof(t1));
strcpy(t1.name, "name");
strcpy(t1.addr, "addr");
strcpy(t1.tile, "addr");
t1.age = 1;
//測試兩個結構體變量之間可以copy數據嗎
//t2 = t1;
//測試實參傳給形參編譯器的行為
//結果很出人意外 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 printfMyteach01(t1);
printfMyteach02(&t1);
getchar();
return 0;
}
//定義結構體數組
int main22()
{
int i = 0;
struct _MyTeacher teaArray[3];
struct _MyTeacher *tmp = NULL;
for (i=0; i<3; i++)
{
strcpy(teaArray[i].name, "aaaaa");
//printf("%s", teaArray[i].name);
tmp = &teaArray[i];
printf("%s", tmp->name);
}
getchar();
return 0;
}
例子
從鍵盤接受數據。。。。並排序
int printfArray(struct _MyTeacher *teaArray, int count)
{
int i = 0;
//打印
for (i=0; i<count; i++)
{
printf("\n教師名字");
printf("%s", teaArray[i].name);
printf("\n教師年齡");
printf("%d", teaArray[i].age);
}
}
int main23()
{
int i = 0, j = 0; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 struct _MyTeacher teaArray[3];
struct _MyTeacher tmp;
for (i=0; i<3; i++)
{
printf("\n請鍵入教師名字");
scanf("%s", teaArray[i].name);
printf("\n請鍵入教師年齡");
scanf("%d", &teaArray[i].age);
}
for (i=0; i<3; i++)
{
for (j=i+1; j<3; j++)
{
if (teaArray[i].age >teaArray[j].age)
{
tmp = teaArray[i];
teaArray[i] = teaArray[j];
teaArray[j] = tmp;
}
}
}
//打印
for (i=0; i<3; i++)
{
printf("\n教師名字");
printf("%s", teaArray[i].name);
printf("\n教師年齡");
printf("%d", teaArray[i].age);
}
printf("ddddd\n");
printfArray(teaArray, 3);
system("pause");
}
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 5.4結構體在工程開發中的應用 //測試輸入
//測試打印
//測試malloc
//測試typdef用法
//定義結構體數組
struct _Student
{
char name[32];
char tile[32];
};
//聲明一個結構體類型
struct _itTeacher
{
char name[32];
char tile[32];
int age;
char addr[128];
};
struct _itAdvTeacher
{
char *name;
char *tile;
int age;
char *addr;
char *p1;
char **p2;
};
//測試輸入
//測試打印
//測試malloc
//測試typdef用法
//定義結構體數組
int main()
{
int i = 0; 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 struct _itTeacher teaArray[3];
struct _itTeacher *tmp = NULL;
for (i=0; i<3; i++)
{
strcpy(teaArray[i].name, "aaaaa");
//printf("%s", teaArray[i].name);
tmp = &teaArray[i];
printf("%s", tmp->name);
}
getchar();
return 0;
}
//內存四字節對齊
//
結構體實參傳給形參也是一個值copy相當於t1 = t2;
//兩個結構體變量之間確實是可以copy這個是編譯器的行為我們需要順從 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 6文件操作專題 6.1 c語言文件讀寫概念
文件分類
按文件的邏輯結構
記錄文件由具有一定結構的記錄組成定長和不定長
流式文件由一個個字符字節數據順序組成
按存儲介質
普通文件存儲介質文件磁盤、磁帶等
設備文件非存儲介質鍵盤、顯示器、打印機等
按數據的組織形式
文本文件ASCII文件每個字節存放一個字符的ASCII碼
二進制文件數據按其在內存中的存儲形式原樣存放
每個文件都以文件名為標識I/O設備的文件名是系統定義的如
COM1或AUX——第一串行口附加設備
COM2——第二串行口此外還可能有COM3、COM4等
CON——控制台console鍵盤輸入用或顯示器輸出用
LPT1或PRN——第一並行口或打印機
LPT2——第二並行口還可能有LPT3等
NUL——空設備
磁盤文件可以由用戶自己命名但上述被系統windows和dos下均是如此保留的設
備名字不能用作文件名如不能把一個文件命名為CON不帶擴展名或CON.TXT不
帶擴展名。
流概念
流是一個動態的概念可以將一個字節形象地比喻成一滴水字節在設備、文件和程序
之間的傳輸就是流類似於水在管道中的傳輸可以看出流是對輸入輸出源的一種抽
象也是對傳輸信息的一種抽象。通過對輸入輸出源的抽象屏蔽了設備之間的差異
使程序員能以一種通用的方式進行存儲操作通過對傳輸信息的抽象使得所有信息都
轉化為字節流的形式傳輸信息解讀的過程與傳輸過程分離。
C語言中I/O操作可以簡單地看作是從程序移進或移出字節這種搬運的過程便稱為流
(stream)。程序只需要關心是否正確地輸出了字節數據以及是否正確地輸入了要讀取輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 字節數據特定I/O設備的細節對程序員是隱藏的。
文件處理方法
緩沖文件系統高級文件系統系統自動為正在使用的文件開辟內存緩沖區
非緩沖文件系統低級文件系統由用戶在程序中為每個文件設定緩沖區
緩沖文件系統理解文件句柄
6.2 文件操作API
6.2.1文件api的分類 01文件讀寫api
fgetc fputc 按照字符讀寫文件
fputs fgets 按照行讀寫文件讀寫配置文件
fread fwirte 按照塊讀寫文件大數據塊遷移
fprintf 按照格式化進行讀寫文件
fprintf(fp, "%s = %s\n", pKey, pValue);
02文件控制api
文件是否結束
文件指針的定位、跳轉 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 fseek(fp, 0L, SEEK_END); //把文件指針從0位置開始移動到文件末尾
//獲取文件長度;
length = ftell(fp);
fseek(fp, 0L, SEEK_SET);
03api做項目 6.2.2標准文件的讀寫 1.文件的打開fopen()
文件的打開操作表示將給用戶指定的文件在內存分配一個FILE結構區並將該結構的
指針返回給用戶程序以后用戶程序就可用此FILE指針來實現對指定文件的存取操作了。
當使用打開函數時必須給出文件名、文件操作方式(讀、寫或讀寫),如果該文件名不存在
就意味着建立(只對寫文件而言對讀文件則出錯)並將文件指針指向文件開頭。若已有一
個同名文件存在則刪除該文件若無同名文件則建立該文件並將文件指針指向文件開
頭。
fopen(char *filename,char *type);
其中*filename是要打開文件的文件名指針一般用雙引號括起來的文件名表示也可
使用雙反斜杠隔開的路徑名。而*type參數表示了對打開文件的操作方式。其可采用的操作
方式如下
方式含義
"r" 打開只讀
"w" 打開文件指針指到頭只寫
"a" 打開指向文件尾在已存在文件中追加
"rb" 打開一個二進制文件只讀
"wb" 打開一個二進制文件只寫
"ab" 打開一個二進制文件進行追加
"r+" 以讀/寫方式打開一個已存在的文件
"w+" 以讀/寫方式建立一個新的文本文件
"a+" 以讀/寫方式打開一個文件文件進行追加
"rb+" 以讀/寫方式打開一個二進制文件
"wb+" 以讀/寫方式建立一個新的二進制文件
"ab+" 以讀/寫方式打開一個二進制文件進行追加
當用fopen(0成功的打開一個文件時該函數將返回一個FILE指針如果文件打開失敗
將返回一個NULL指針。如想打開test文件進行寫
FILE *fp;
if((fp=fopen("test","w"))==NULL)
{
printf("File cannot be opened\n");
exit();
} 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 else
printf("File opened for writing\n");
……
fclose(fp);
DOS操作系統對同時打開的文件數目是有限制的缺省值為5可以通過修改CONFIG.SYS
文件改變這個設置。
2.關閉文件函數fclose()
文件操作完成后必須要用fclose()函數進行關閉這是因為對打開的文件進行寫入時
若文件緩沖區的空間未被寫入的內容填滿這些內容不會寫到打開的文件中去而丟失。只有
對打開的文件進行關閉操作時停留在文件緩沖區的內容才能寫到該文件中去從而使文件
完整。再者一旦關閉了文件該文件對應的FILE結構將被釋放從而使關閉的文件得到保
護因為這時對該文件的存取操作將不會進行。文件的關閉也意味着釋放了該文件的緩沖區。
int fclose(FILE *stream);
它表示該函數將關閉FILE指針對應的文件並返回一個整數值。若成功地關閉了文件
則返回一個0值否則返回一個非0值。常用以下方法進行測試
if(fclose(fp)!=0)
{
printf("File cannot be closed\n");
exit(1);
}
else
printf("File is now closed\n");
當打開多個文件進行操作而又要同時關閉時可采用fcloseall()函數它將關閉所有
在程序中打開的文件。
int fcloseall();
該函數將關閉所有已打開的文件將各文件緩沖區未裝滿的內容寫到相應的文件中去
接着釋放這些緩沖區並返回關閉文件的數目。如關閉了4個文件則當執行
n=fcloseall();時n應為4。
3.文件的讀寫
(1).讀寫文件中字符的函數(一次只讀寫文件中的一個字符)
int fgetc(FILE *stream);
int fgetchar(void);
int fputc(int ch,FILE *stream);
int fputchar(int ch);
int getc(FILE *stream);
int putc(int ch,FILE *stream);
其中fgetc()函數將把由流指針指向的文件中的一個字符讀出例如
ch=fgetc(fp); 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 將把流指針fp指向的文件中的一個字符讀出並賦給ch當執行fgetc()函數時若當
時文件指針指到文件尾即遇到文件結束標志EOF(其對應值為-1)該函數返回一個-1給ch
在程序中常用檢查該函數返回值是否為-1來判斷是否已讀到文件尾從而決定是否繼續。
#include "stdio.h"
main()
{
FILE *fp;
ch ch;
if((fp=fopen("myfile.tex","r"))==NULL)
{
printf("file cannot be opened\n");
exit(1);
}
while((ch=fgetc(fp))!=EOF) fputc(ch,stdout);
fclose(fp);
}
該程序以只讀方式打開myfile.txt文件在執行while循環時文件指針每循環一次后移
一個字符位置。用fgetc()函數將文件指針指定的字符讀到ch變量中然后用fputc()函數在
屏幕上顯示當讀到文件結束標志EOF時變關閉該文件。
上面的程序用到了fputc()函數該函數將字符變量ch的值寫到流指針指定的文件中去
由於流指針用的是標准輸出(顯示器)的FILE指針stdout故讀出的字符將在顯示器上顯示。
又比如
putc(ch,fp);
該函數執行結構將把ch表示的字符送到流指針fp指向的文件中去。
在TC中putc()等價於fput(),getc()等價於fgetc()。
putchar(c)相當於fputc(c,stdout)getchar()相當於fgetc(stdin)。
注意這里使用char ch,其實是不科學的因為最后判斷結束標志時是看ch!=EOF,而
EOF的值為-1這顯然和char是不能比較的。所以某些使用我們都定義成int ch。
(2).讀寫文件中字符串的函數
char *fgets(char *string,int n,FILE *stream);
char *gets(char *s);
int fprintf(FILE *stream,char *format,variable-list);
int fputs(char *string,FILE *stream);
int fscanf(FILE *stream,char *format,variable-list);
其中fgets()函數將把由流指針指定的文件中n-1個字符讀到由指針stream指向的字符
數組中去例如
fgets(buffer,9,fp);
將把fp指向的文件中的8個字符讀到buffer內存區buffer可以是定義的字符數組也
可以是動態分配的內存區。
注意fgets()函數讀到'\n'就停止而不管是否達到數目要求。同時在讀取字符串的最后
加上'\0'。 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 fgets()函數執行完以后返回一個指向該串的指針。如果讀到文件尾或出錯則均返回
一個空指針NULL所以長用feof()函數來測定是否到了文件尾或者是ferror()函數來測試是否
出錯例如下面的程序用fgets()函數讀test.txt文件中的第一行並顯示出來
#include "stdio.h"
main()
{
FILE *fp;
char str[128];
if((fp=fopen("test.txt","r"))==NULL)
{
printf("cannot open file\n");
exit(1);
}
while(!feof(fp))
{
if(fgets(str,128,fp)!=NULL) printf("%s",str);
}
fclose(fp);
}
gets()函數執行時只要未遇到換行符或文件結束標志將一直讀下去。因此讀到什么
時候為止需要用戶進行控制否則可能造成存儲區的溢出。
fputs()函數想指定文件寫入一個由string指向的字符串'\0'不寫入文件。
fprintf()和fscanf()同printf()和scanf()函數類似不同之處就是printf()函數是想顯示器輸
出fprintf()則是向流指針指向的文件輸出fscanf()是從文件輸入。
下面程序是向文件test.dat里輸入一些字符
#include<stdio.h>
main()
{
char *s="That's good news";
int i=617;
FILE *fp;
fp=fopne("test.dat", "w");
fputs("Your score of TOEFLis",fp);
fputc(':', fp);
fprintf(fp, "%d\n", i);
fprintf(fp, "%s", s);
fclose(fp);
}
用DOS的TYPE命令顯示TEST.DAT的內容如下所示:
屏幕顯示 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 Your score of TOEFL is: 617
That's good news
下面的程序是把上面的文件test.dat里的內容在屏幕上顯示出來
#include<stdio.h>
main()
{
char *s, m[20];
int i;
FILE *fp;
fp=fopen("test.dat", "r");
fgets(s, 24, fp);
printf("%s", s);
fscanf(fp, "%d", &i);
printf("%d", i);
putchar(fgetc(fp));
fgets(m, 17, fp);
puts(m);
fclose(fp);
getch();
}
運行后屏幕顯示:
Your score of TOEFL is: 617
That's good news
4.清除和設置文件緩沖區
(1).清除文件緩沖區函數
int fflush(FILE *stream);
int flushall();
fflush()函數將清除由stream指向的文件緩沖區里的內容常用於寫完一些數據后立即用
該函數清除緩沖區以免誤操作時破壞原來的數據。
flushall()將清除所有打開文件所對應的文件緩沖區。
(2).設置文件緩沖區函數
void setbuf(FILE *stream,char *buf);
void setvbuf(FILE *stream,char *buf,int type,unsigned size);
這兩個函數將使得打開文件后用戶可建立自己的文件緩沖區而不使用fopen()函數
打開文件設定的默認緩沖區。
對於setbuf()函數buf指出的緩沖區長度由頭文件stdio.h中定義的宏BUFSIZE的值決
定缺省值為512字節。當選定buf為空時setbuf函數將使的文件I/O不帶緩沖。而對setvbuf
函數則由malloc函數來分配緩沖區。參數size指明了緩沖區的長度(必須大於0),而參數type
則表示了緩沖的類型其值可以取如下值
type 值含義 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 _IOFBF 文件全部緩沖即緩沖區裝滿后才能對文件讀寫
_IOLBF 文件行緩沖即緩沖區接收到一個換行符時才能對文件讀寫
_IONBF 文件不緩沖此時忽略buf,size的值直接讀寫文件不再經過文件緩
沖區緩沖
5.文件的隨機讀寫函數
前面介紹的文件的字符/字符串讀寫均是進行文件的順序讀寫即總是從文件的開頭
開始進行讀寫。這顯然不能滿足我們的要求C語言提供了移動文件指針和隨機讀寫的函數
它們是
(1).移動文件指針函數
long ftell(FILE *stream);
int rewind(FILE *stream);
fseek(FILE *stream,long offset,int origin);
函數ftell()用來得到文件指針離文件開頭的偏移量。當返回值是-1時表示出錯。
rewind()函數用於文件指針移到文件的開頭當移動成功時返回0否則返回一個非0
值。
fseek()函數用於把文件指針以origin為起點移動offset個字節其中origin指出的位置
可有以下幾種
origin 數值代表的具體位置
SEEK_SET 0 文件開頭
SEEK_CUR 1 文件指針當前位置
SEEK_END 2 文件尾
例如
fseek(fp,10L,0);
把文件指針從文件開頭移到第10字節處由於offset參數要求是長整型數故其數后
帶L。
fseek(fp,-15L,2);
把文件指針從文件尾向前移動15字節。
(2).文件隨機讀寫函數
int fread(void *ptr,int size,int nitems,FILE *stream);
int fwrite(void *ptr,int size,int nitems,FILE *stream);
fread()函數從流指針指定的文件中讀取nitems個數據項每個數據項的長度為size個字
節讀取的nitems數據項存入由ptr指針指向的內存緩沖區中
在執行fread()函數時文件指針隨着讀取的字節數而向后移動最后移動結束的位置等於實
際讀出的字節數。該函數執行結束后將返回實際讀出的數據項數
這個數據項數不一定等於設置的nitems因為若文件中沒有足夠的數據項或讀中間出錯
都會導致返回的數據項數少於設置的nitems。當返回數不等於nitems時 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 可以用feof()或ferror()函數進行檢查。
fwrite()函數從ptr指向的緩沖區中取出長度為size字節的nitems個數據項寫入到流指
針stream指向的文件中執行該操作后文件指針將向后移動
移動的字節數等於寫入文件的字節數目。該函數操作完成后也將返回寫入的數據項數。
6.2.3非標准文件的讀寫 這類函數最早用於UNIX操作系統,ANSI標准未定義,但有時也經常用到,DOS 3.0以上版本
支持這些函數。它們的頭文件為io.h。
由於我們不常用這些函數所以在這里就簡單說一下。
1.文件的打開和關閉
open()函數的作用是打開文件,其調用格式為:
int open(char *filename, int access);
該函數表示按access的要求打開名為filename的文件,返回值為文件描述字,其中access
有兩部分內容:
基本模式和修飾符, 兩者用" "("或")方式連接。修飾符可以有多個, 但基本模式只能有一
個。
access的規定
--------------------------------------------------------
基本模式含義修飾符含義
--------------------------------------------------------
O_RDONLY 只讀 O_APPEND 文件指針指向末尾
O_WRONLY 只寫 O_CREAT 文件不存在時創建文件, 屬性按基本模式屬性
O_RDWR 讀寫 O_TRUNC 若文件存在, 將其長度縮為0, 屬性不變
O_BINARY 打開一個二進制文件
O_TEXT 打開一個文字文件
---------------------------------------------------------
open()函數打開成功, 返回值就是文件描述字的值(非負值), 否則返回-1。
close()函數的作用是關閉由open()函數打開的文件, 其調用格式為:
int close(int handle);
該函數關閉文件描述字handle相連的文件。
2.讀寫函數
int read(int handle, void *buf, int count);
read()函數從handle(文件描述字)相連的文件中, 讀取count個字節放到buf所指
的緩沖區中,
返回值為實際所讀字節數, 返回-1表示出錯。返回0 表示文件結束。
write()函數的調用格式為:
int write(int handle, void *buf, int count);
write()函數把count個字節從buf指向的緩沖區寫入與handle相連的文件中, 返輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 回值為實際寫入的字節數。
3.隨機定位函數
lseek()函數的調用格式為:
int lseek(int handle, long offset, int fromwhere);
該函數對與handle相連的文件位置指針進行定位,功能和用法與fseek()函數相同。
tell()函數的調用格式為:
long tell(int handle);
該函數返回與handle相連的文件現生位置指針, 功能和用法與ftell()相同 6.2.4注意點 文本文件
ASCII文件每個字節存放一個字符的ASCII碼
二進制文件數據按其在內存中的存儲形式原樣存放
項目開發中參考fgets
函數的實現方法 fgets(buf, bufMaxLen, fp);
對fgets函數來說n必須是個正整數表示從文件按中讀出的字符數不超過n-1存儲
到字符數組str中並在末尾加上結束標志’\0’換言之n代表了字符數組的長度
即sizeof(str)。如果讀取過程中遇到換行符或文件結束標志讀取操作結束。若正常
讀取返回指向str代表字符串的指針否則返回NULL空指針。
輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程
6.3文件操作案例-配置文件讀寫 配置文件讀寫案例實現分析
1、 功能划分
a) 界面測試功能集成
自己動手規划接口模型。
b) 配置文件讀寫
i. 配置文件讀根據key讀取valude
ii. 配置文件寫輸入key、valude
iii. 配置文件修改輸入key、valude
iv. 優化 ===》接口要求緊模塊要求松
2、 實現及代碼講解
3、 測試。 6.4文件操作案例-大文件加解密 功能實現分析
1、數據加密解密接口測試
2、數據加密過程分析
文件數據的movecopy + 數據加密
3、數據加解密功能集成
數據加密和解密分為兩個版本打padding 和不打padding
數據加密解密原理 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 7C接口的封裝和設計專題 Win32環境下動態鏈接庫(DLL)編程原理
比較大的應用程序都由很多模塊組成這些模塊分別完成相對獨立的功能它們彼此協
作來完成整個軟件系統的工作。其中可能存在一些模塊的功能較為通用在構造其它軟件系
統時仍會被使用。在構造軟件系統時如果將所有模塊的源代碼都靜態編譯到整個應用程序
EXE文件中會產生一些問題一個缺點是增加了應用程序的大小它會占用更多的磁盤空
間程序運行時也會消耗較大的內存空間造成系統資源的浪費另一個缺點是在編寫大
的EXE程序時在每次修改重建時都必須調整編譯所有源代碼增加了編譯過程的復雜性
也不利於階段性的單元測試。
Windows系統平台上提供了一種完全不同的較有效的編程和運行環境你可以將獨立的
程序模塊創建為較小的DLL(Dynamic Linkable Library)文件並可對它們單獨編譯和測試。在
運行時只有當EXE程序確實要調用這些DLL模塊的情況下系統才會將它們裝載到內存空
間中。這種方式不僅減少了EXE文件的大小和對內存空間的需求而且使這些DLL模塊可以
同時被多個應用程序使用。Microsoft Windows自己就將一些主要的系統功能以DLL模塊的
形式實現。例如IE中的一些基本功能就是由DLL文件實現的它可以被其它應用程序調用
和集成。
一般來說DLL是一種磁盤文件通常帶有DLL擴展名它由全局數據、服務函數和
資源組成在運行時被系統加載到進程的虛擬空間中成為調用進程的一部分。如果與其它
DLL之間沒有沖突該文件通常映射到進程虛擬空間的同一地址上。DLL模塊中包含各種導
出函數用於向外界提供服務。Windows 在加載DLL模塊時將進程函數調用與DLL文件的
導出函數相匹配。
在Win32環境中每個進程都復制了自己的讀/寫全局變量。如果想要與其它進程共享輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 內存必須使用內存映射文件或者聲明一個共享數據段。DLL模塊需要的堆棧內存都是從運
行進程的堆棧中分配出來的。
DLL現在越來越容易編寫。Win32已經大大簡化了其編程模式並有許多來自AppWizard
和MFC類庫的支持。
一、導出和導入函數的匹配
DLL文件中包含一個導出函數表。這些導出函數由它們的符號名和稱為標識號的整數與
外界聯系起來。函數表中還包含了DLL中函數的地址。當應用程序加載 DLL模塊時時它
並不知道調用函數的實際地址但它知道函數的符號名和標識號。動態鏈接過程在加載的
DLL模塊時動態建立一個函數調用與函數地址的對應表。如果重新編譯和重建DLL文件並
不需要修改應用程序除非你改變了導出函數的符號名和參數序列。
簡單的DLL文件只為應用程序提供導出函數比較復雜的DLL文件除了提供導出函數以
外還調用其它DLL文件中的函數。這樣一個特殊的DLL可以既有導入函數又有導入函
數。這並不是一個問題因為動態鏈接過程可以處理交叉相關的情況。
在DLL代碼中必須像下面這樣明確聲明導出函數
__declspec(dllexport) int MyFunction(int n);
但也可以在模塊定義(DEF)文件中列出導出函數不過這樣做常常引起更多的麻煩。在
應用程序方面要求像下面這樣明確聲明相應的輸入函數
__declspec(dllimport) int MyFuncition(int n);
僅有導入和導出聲明並不能使應用程序內部的函數調用鏈接到相應的DLL文件上。應用
程序的項目必須為鏈接程序指定所需的輸入庫LIB文件。而且應用程序事實上必須至少包
含一個對DLL函數的調用。
二、與DLL模塊建立鏈接
應用程序導入函數與DLL文件中的導出函數進行鏈接有兩種方式隱式鏈接和顯式鏈接。
所謂的隱式鏈接是指在應用程序中不需指明DLL文件的實際存儲路徑程序員不需關心DLL
文件的實際裝載。而顯式鏈接與此相反。
采用隱式鏈接方式程序員在建立一個DLL文件時鏈接程序會自動生成一個與之對應
的LIB導入文件。該文件包含了每一個DLL導出函數的符號名和可選的標識號但是並不含
有實際的代碼。LIB文件作為DLL的替代文件被編譯到應用程序項目中。當程序員通過靜態
鏈接方式編譯生成應用程序時應用程序中的調用函數與LIB文件中導出符號相匹配這些
符號或標識號進入到生成的EXE文件中。LIB文件中也包含了對應的DLL文件名但不是完
全的路徑名鏈接程序將其存儲在EXE文件內部。當應用程序運行過程中需要加載DLL文
件時Windows根據這些信息發現並加載DLL然后通過符號名或標識號實現對DLL函數的輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 動態鏈接。
顯式鏈接方式對於集成化的開發語言例如VB比較適合。有了顯式鏈接程序員就
不必再使用導入文件而是直接調用Win32 的LoadLibary函數並指定DLL的路徑作為參
數。LoadLibary返回HINSTANCE參數應用程序在調用 GetProcAddress函數時使用這一參數。
GetProcAddress函數將符號名或標識號轉換為DLL內部的地址。假設有一個導出如下函數的
DLL文件
extern "C" __declspec(dllexport) double SquareRoot(double d);
下面是應用程序對該導出函數的顯式鏈接的例子
c====》應用
win/linux系統編程
api
typedef double(SQRTPROC)(double);
HINSTANCE hInstance;
SQRTPROC* pFunction;
VERIFY(hInstance=::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));
VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot"));
double d=(*pFunction)(81.0);//調用該DLL函數
在隱式鏈接方式中所有被應用程序調用的DLL文件都會在應用程序EXE文件加載時被
加載在到內存中但如果采用顯式鏈接方式程序員可以決定DLL文件何時加載或不加載。
顯式鏈接在運行時決定加載哪個DLL文件。例如可以將一個帶有字符串資源的DLL模塊以
英語加載而另一個以西班牙語加載。應用程序在用戶選擇了合適的語種后再加載與之對應
的DLL文件。
三、使用符號名鏈接與標識號鏈接
在Win16環境中符號名鏈接效率較低所有那時標識號鏈接是主要的鏈接方式。在
Win32環境中符號名鏈接的效率得到了改善。Microsoft 現在推薦使用符號名鏈接。但在
MFC庫中的DLL版本仍然采用的是標識號鏈接。一個典型的MFC程序可能會鏈接到數百個
MFC DLL函數上。采用標識號鏈接的應用程序的EXE文件體相對較小因為它不必包含導入
函數的長字符串符號名。
四、編寫DllMain函數
DllMain函數是DLL模塊的默認入口點。當Windows加載 DLL模塊時調用這一函數。系
統首先調用全局對象的構造函數然后調用全局函數DLLMain。DLLMain函數不僅在將DLL
鏈接加載到進程時被調用在DLL模塊與進程分離時以及其它時候也被調用。下面是一
個框架DLLMain函數的例子。
HINSTANCE g_hInstance;
extern "C" int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved) 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 {
if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("EX22A.DLL Initializing!\n");
//在這里進行初始化
}
else if(dwReason=DLL_PROCESS_DETACH)
{
TRACE0("EX22A.DLL Terminating!\n");
//在這里進行清除工作
}
return 1;//成功
}
如果程序員沒有為DLL模塊編寫一個DLLMain函數系統會從其它運行庫中引入一個不
做任何操作的缺省DLLMain函數版本。在單個線程啟動和終止時DLLMain函數也被調用。
正如由dwReason參數所表明的那樣。
五、模塊句柄
進程中的每個DLL模塊被全局唯一的32字節的HINSTANCE句柄標識。進程自己還有一
個HINSTANCE句柄。所有這些模塊句柄都只有在特定的進程內部有效它們代表了DLL或
EXE模塊在進程虛擬空間中的起始地址。在Win32中HINSTANCE和HMODULE的值是相同
的這個兩種類型可以替換使用。進程模塊句柄幾乎總是等於0x400000而DLL模塊的加
載地址的缺省句柄是0x10000000。如果程序同時使用了幾個DLL模塊每一個都會有不同
的HINSTANCE值。這是因為在創建DLL文件時指定了不同的基地址或者是因為加載程序
對DLL代碼進行了重定位。
模塊句柄對於加載資源特別重要。Win32 的FindResource函數中帶有一個HINSTANCE參數。
EXE和DLL都有其自己的資源。如果應用程序需要來自於DLL的資源就將此參數指定為DLL
的模塊句柄。如果需要EXE文件中包含的資源就指定EXE的模塊句柄。
但是在使用這些句柄之前存在一個問題你怎樣得到它們呢如果需要得到EXE模塊句
柄調用帶有Null參數的Win32函數GetModuleHandle如果需要DLL模塊句柄就調用以
DLL文件名為參數的Win32函數GetModuleHandle。
六、應用程序怎樣找到DLL文件
如果應用程序使用LoadLibrary顯式鏈接那么在這個函數的參數中可以指定DLL文件
的完整路徑。如果不指定路徑或是進行隱式鏈接Windows將遵循下面的搜索順序來定位
DLL
1包含EXE文件的目錄
2進程的當前工作目錄
3 Windows系統目錄 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 4 Windows目錄
5列在Path環境變量中的一系列目錄。
這里有一個很容易發生錯誤的陷阱。如果你使用VC進行項目開發並且為DLL模
塊專門創建了一個項目然后將生成的DLL文件拷貝到系統目錄下從應用程序中調用DLL
模塊。到目前為止一切正常。接下來對DLL模塊做了一些修改后重新生成了新的DLL文件
但你忘記將新的DLL文件拷貝到系統目錄下。下一次當你運行應用程序時它仍加載了老版
本的DLL文件這可要當心
七、調試DLL程序
Microsoft的VC是開發和測試DLL 的有效工具只需從DLL項目中運行調試程序即
可。當你第一次這樣操作時調試程序會向你詢問EXE文件的路徑。此后每次在調試程序中
運行DLL時調試程序會自動加載該EXE文件。然后該EXE文件用上面的搜索序列發現DLL
文件這意味着你必須設置Path環境變量讓其包含DLL文件的磁盤路徑或者也可以將DLL
文件拷貝到搜索序列中的目錄路徑下。
DLL分配的內存如何在EXE里面釋放 總結下面幾個要點 1. 保證內存分配和清除的統一性如果一個DLL提供一個能夠分配內存的函數那么這個DLL
同時應該提供一個函數釋放這些內存。數據的創建和清除應該在同一個層次上。 曾經遇到過這樣的例子在dll中分配了一塊內存,通過PostMessage將其地址傳給應用。然后
應用去釋放它,結果總是報異常。 2.如果exe用 MFC Appwizard方式生成 dll用win32方式生成則運行時會出現錯誤。進一步用單步跟蹤發現mfc方式和win32方式下的new操作符是用不同方式實現的源程序分別在
VC目錄的文件 Afxmem.cpp和new.cpp中。有興趣的話可以自已跟蹤一下。 因為dll輸出函數后並不知道是哪一個模擬調用它因此new和delete配對時最好在一個文
件中這樣可以保證一致性。 3. 問題主要在於DLL和EXE主程序中分配內存的堆不一樣你可以不用new和delete而是用 1 ::HeapAlloc(::GetProcessHeap(),...)和::HeapFree(::GetProcessHeap(),...) 2 ::GlobalAlloc()和::GlobalFree() 這兩對API這樣無論在DLL中還是在主程序中都是在進程默認堆中分配就不會出錯了。 輕松入門實戰應用傳智播客C++學院就業班第一階段C提高課程 4. 還有一個辦法就是把dll的Settings的C/C++選項卡的Code Generation的Use Run-time
liberary改成Debug Multithreaded DLL在Release版本中改成Multithreaded DLL就可以
直接使用new和delete了。不過MFC就不能用Shared模式了。