第一篇:編程知識(shí)總結(jié)C難點(diǎn)總結(jié)
《編程知識(shí)總結(jié)》—C#難點(diǎn)總結(jié)
1.編寫菜單中對(duì)應(yīng)子菜單項(xiàng)的消息響應(yīng)函數(shù),考慮的方面很多,例 如,當(dāng)前打開一個(gè)文件,此文件已經(jīng)被修改,如果用戶需要新建 或者打開另外一個(gè)文件,程序要詢問用戶是否保存當(dāng)前的文件。
問題的解決:先用紙張寫一份詳細(xì)的業(yè)務(wù)流程圖,在之后的編寫 過程中,按照業(yè)務(wù)流程的規(guī)定進(jìn)行功能的逐步實(shí)現(xiàn)。
2.編寫判斷是否要提醒用戶保存的程序,判斷是否提醒保存的根據(jù) 是什么。我的判斷根據(jù)驗(yàn)證文件內(nèi)容是否被修改,如果被修改,提醒用戶保存。如果被保存的文件沒有文件路徑,調(diào)用另存為的 響應(yīng)函數(shù)進(jìn)行執(zhí)行。
3.無法關(guān)閉窗體,當(dāng)我響應(yīng)文件菜單下退出菜單項(xiàng)的時(shí)候,如果使 用 Close()函數(shù)的話,可以就直接退出,但是當(dāng)我需要以同樣的 方式響應(yīng)右上角關(guān)閉按鈕的話,事件信息就會(huì)進(jìn)入死循環(huán),而且 永遠(yuǎn)不會(huì)結(jié)束。所以,在關(guān)閉窗體的時(shí)候,目前我用的就是 Application.exit();這個(gè)函數(shù),強(qiáng)制性終結(jié)這個(gè)窗口
4.保存文件,我們打開文件和直接輸入信息的文件的情況,對(duì)于保 存來說是不一樣的,因?yàn)榇蜷_的時(shí)候,應(yīng)該是直接保存到指定的 文件中,而直接輸入的應(yīng)該提示保存,通過另存為的方式保存。5.文件的刪除。C#的 textBox 沒有直接為我們提供文本刪除的函數(shù),我弄了許久,后來,無意間突然發(fā)現(xiàn)通過已經(jīng)選擇的字符串的下 標(biāo)來操作,就可以刪除文件了,具體操作就是,先獲取被選擇文 件的前半部分,在獲取后半部分,而略過中間被選中的部分即可。6.開始的時(shí)候沒有使用 Using System.IO,系統(tǒng)報(bào)錯(cuò);
7.由于在 MessageBox.Show 后沒有寫 return 導(dǎo)致錯(cuò)誤;
8.初步創(chuàng)建簡易記事本后點(diǎn)擊打開文件選項(xiàng)并不顯示文件的內(nèi)容,且編譯器報(bào)錯(cuò)了,是由于沒有判斷文件名為空,且文件的完整路 徑名獲取的不準(zhǔn)確,將文件名為空的情況及其文件的完整路徑名 獲取之后,讀取文件正確;
9.首先,我在添加單擊“新建”響應(yīng)的代碼時(shí),單擊進(jìn)去,結(jié)果代 碼全部不能運(yùn)行,必須是雙擊。
10.獲取文件名時(shí),文件名不需要特別處理,程序內(nèi)部直接處理好的,我在這里畫蛇添足了。
11.操作對(duì)象必須指代明確,不如要出錯(cuò)。
12.在用 WinForm 做文本文檔的時(shí)候,在“新建文本文檔”項(xiàng)的代碼 中,需要在項(xiàng)目中新建一個(gè) new 窗體作為“新建文本文檔”,在 form1 的代碼中寫如下代碼: Form frm = new NewForm();frm.Show();this.Hide();然后在 Form2 中下如同樣的代碼即可。
13.還發(fā)現(xiàn)“保存”與“另存為”項(xiàng)的代碼編寫沒有任何差異,考慮 并修改程序無果。代碼如下: private void 保存SToolStripMenuItem_Click(object sender, EventArgs e)// { DialogResult save = saveFileD
ialog1.ShowDialog();
if
(save
== DialogResult.OK){ string filepath = saveFileDialog1.FileName;//FileInfo fi = new FileInfo(filepath);string content = richTextBox1.Text;File.WriteAllText(filepath, content, Encoding.Default);} } 14.在保存的時(shí)候必須輸入保存路徑才能正常執(zhí)行,沒有實(shí)現(xiàn)文本文 檔的基本保存功能。并且在打開一個(gè)原本有內(nèi)容的文本文檔之后,加以修改再保存就不能保存新的內(nèi)容。
15.獲取 TextBox 中獲取一行的值,使用 TextBox 的 Lines 屬性來獲 取
16.做刪除操作時(shí),由 MessageBoxv.show 來彈出一個(gè)確認(rèn)窗體,把返 回的值賦給一個(gè) DialogResult 類型的變量
17.在復(fù)制文件時(shí),CopyTo 函數(shù)將第二個(gè)參數(shù)置為 TRUE 則在復(fù)制時(shí),如果文件存在會(huì)被覆蓋掉。
18.在向文件寫入數(shù)據(jù)時(shí),使用 Encoding 的 Default 的屬性可避免輸 入中文亂碼問題。19.關(guān)于上午的文件編程,剛開始對(duì)程序要執(zhí)行的具體操作不夠明確,在編程時(shí),功能實(shí)現(xiàn)的矛盾性問題很多,其問題在于編程前分析 不夠到位;
20.文件編程中起先將打開文件定位為文件夾,導(dǎo)致彈出對(duì)話框顯示 “不存在此文件”; 21.File.WriteAllLines(),無法將臨時(shí)修改后的文件信息進(jìn)行正 確保存,文本沒有實(shí)現(xiàn)正確的回車換行;File.WriteAllText()能實(shí)現(xiàn);
22.this.Close()無法實(shí)現(xiàn)程序的結(jié)束 Close()方法是關(guān)閉 Form 23.當(dāng)創(chuàng)建了控件的響應(yīng)事件后,修改控件的 name 屬性,響應(yīng)事件 名為改變,但并不影響進(jìn)程的進(jìn)行,要修改方法名應(yīng)刪除該響應(yīng) 方法,雙擊控件,重新獲得響應(yīng)事件。
24.代碼經(jīng)常會(huì)出現(xiàn)命名不規(guī)范的情況,同時(shí)沒有寫注釋的習(xí)慣
25.經(jīng)常會(huì)丟掉方法后的括號(hào),如 savedialog1.showdialog()26.一 些 方 法 中 的 細(xì) 微 差 別 如 File.Write.AllLine 27.對(duì)于異常的處理經(jīng)常忽略,導(dǎo)致程序的容錯(cuò)處理比較差。
28.當(dāng)編輯框改變時(shí)有兩種情況,一種是打開,一種是編輯,當(dāng)打開 時(shí)不應(yīng)該記錄改變標(biāo)志。解決的辦法:做一個(gè)打開標(biāo)志,當(dāng)打開操作時(shí),賦值為 true,在 文本框的改變函數(shù)中判斷,如果是打開操作,將打開操作之位 false,同時(shí)文本改變標(biāo)志值為 false
29.此處如何把長整形變量賦值給一個(gè) string 類型? 使用 ToString()30.如何在文本框上添加滾動(dòng)條。設(shè)置 TextBox.ScrollBars
31.textbox 控件大小跟窗體一起變化:剛開始想通過相應(yīng)窗體大小 改變事件的辦法解決,后來通過其他同學(xué)得知可以通過更改 textbox 的 Dock 屬性(改為 Fill)來解決。
32.當(dāng) textbox 空間的文本改變時(shí),進(jìn)行新建、打開、退出等操作時(shí) 應(yīng)提示用戶是否保存更改,通過設(shè)置文件更改標(biāo)志(flag)并相 File.WriteAllText 和 響 textbox 的 textchanged 事件來解決。
33.由于需要提示用戶保存更改,剛
開始時(shí)代碼控制結(jié)構(gòu)寫的很復(fù)雜,連自己也有點(diǎn)難理解。后來用調(diào)用“保存”事件的響應(yīng)函數(shù)(剛 開始一位不能這樣)后才解決這一問題。
34.由于剛開始做,命名方面不是很規(guī)范,導(dǎo)致了變量、方法和類的 名字不易區(qū)分,不能一目了然,間接增加了編寫程序所需的時(shí)間。解決方法是在編寫程序中時(shí)刻遵守命名規(guī)范。
35.在對(duì)文件操作時(shí),文件的路徑經(jīng)常出錯(cuò),原因是少了轉(zhuǎn)換為絕對(duì) 路徑的@。
36.對(duì)文件注釋太少,經(jīng)常導(dǎo)致前面編過的代碼過一段時(shí)間就完全看 不懂了,所以,在編程中也要養(yǎng)成隨時(shí)加注釋的習(xí)慣,其實(shí)這樣 做表面上是增加了時(shí)間,統(tǒng)籌來看,這樣做既方便了自己日后修 改程序,又為閱讀我們程序的人提供了便利。
37.文件操作經(jīng)常出錯(cuò),程序無法運(yùn)行,要因此要異常處理,比如說 用 try,catch 對(duì)異常處理。
38.在昨天上午的文件操作中,第一個(gè)問題就是路徑的合法性檢測(cè)。由于不知道 c#是否存在像 c++ access()一樣的方法。最后使用 OpenFileDialog 來限定用戶輸入??梢允褂谜齽t表達(dá)式驗(yàn)證 39.再有就是文件操作異常處理。C#的異常處理還是很方便的,昨天 的經(jīng)驗(yàn)是只要使用 try catch 語句,在 catch 中不作任何處理程 序也不會(huì)當(dāng)?shù)簟?/p>
40.MessageBox.Show(“你還沒有保存上次的修改,是否保存修改?”, “保存”, MessageBoxButtons.YesNoCancel);選項(xiàng)的判斷 問 同 學(xué) 得 : 返 回 值 為 DialogResult.No DialogResult.Yes DialogResult.Cancel 41.2 . private
void
Form1_FormClosing(object
sender, FormClosingEventArgs e)中如何取消關(guān)閉 問同學(xué)得:e.Cancel = true;42.如何實(shí)現(xiàn)修改后 “新建”“打開”等提示保存 點(diǎn)打開保存后記 錄存儲(chǔ)路徑
43.新建 2 個(gè)成員記錄:private string Pathname;private bool savetxt=true;
44.如何在兩個(gè)窗體間傳遞值。通過向同學(xué)和老師詢問,知道傳值有 很多種方法,我用的就是調(diào)用構(gòu)造函數(shù),可是老師也說了如果值 比較多的時(shí)候,這種方法還是有缺陷的。使用構(gòu)造函數(shù)可以,只是也用對(duì)象會(huì)好一些。
45.當(dāng)漢字改成英文時(shí),函數(shù)中仍然是漢字,如果直接在 program.cs 中改容易出問題,怎么改? 利用 VS 的代碼重構(gòu)
46.如何改窗體的默認(rèn)主題樣式 可以使用第三方控件完成
47.TextBox 中文本值改變時(shí)的響應(yīng)事件(可以添加事件響應(yīng)函數(shù))。
48.文 件 打 開 中 將 文 件 寫 到 textbox 中 : 定 義 string[] 調(diào) 用 File.ReadAllLines 函數(shù)將文件內(nèi)容逐行讀入到字符串?dāng)?shù)組中,然后利用 for 循環(huán)將文件內(nèi)容逐行寫出到控件中。
49.填寫郵箱和文件路徑的時(shí)候需要有正確的規(guī)格,因?yàn)橹敖佑|過 c#不知道該如何完成,后來在經(jīng)過詢問他人和網(wǎng)上資料查詢得知 用正則表達(dá)式來規(guī)范正確
的格式,便避免了這方面因格式而導(dǎo)致 的問題。
50.當(dāng)輸入多行字符串,保存過后進(jìn)行查看時(shí),當(dāng)有換行就會(huì)出現(xiàn)黑 乎乎的正方形。換行:rn
51.假設(shè)某個(gè)事件已經(jīng)做好,可以作為一個(gè)模塊使用,那么如何在其 他代碼中調(diào)用此模塊
52.記事本的查找、替換模塊不知道如何實(shí)現(xiàn) 需要使用字符串函數(shù)
53.打開 txt 文件的時(shí)候中文沒有亂碼但是保持了之后在打開就有了 亂碼 實(shí)現(xiàn):原先是因?yàn)楸3值臅r(shí)候沒有使用 encoding.default 54.保存時(shí)文件不能自動(dòng)保存文件 ?自動(dòng)保存文件
55.在做簡易記事本的途中,創(chuàng)建快捷鍵的時(shí)候遇到一點(diǎn)問題,當(dāng)在 雜項(xiàng)的 Shortcutkeys 中選擇 shift 的時(shí)候,它會(huì)報(bào)屬性值無效的 錯(cuò)誤,后來無奈之下選擇了 Ctrl,居然就正確了
56.還有在 TextBox 的屬性 Dock 選擇了 fill 的時(shí)候,依然沒有變成 全部填充,真是百思不得其解!需要將 TextBox 設(shè)置為多行文本
57.發(fā)現(xiàn)了一種比較簡便的方法: 已經(jīng)做完的某個(gè)控件出發(fā)的事件,可以作為一個(gè)法方來使用。例: private void SaveMenu_Click(object sender, EventArgs e){ //判斷文件是否已有路徑,若無,選定路徑,若有,保存在最新操作的文件路徑下 if(path == string.Empty){ if(SolgSave.ShowDialog()== DialogResult.OK){ path = SolgSave.FileName;File.AppendAllText(path, txtEdit.Text, Encoding.Default);} } else { File.AppendAllText(path, txtEdit.Text, Encoding.Default);} } private void ExitMenu_Click(object sender, EventArgs e){ if(txtEdit.Text!= string.Empty){ DialogResult dr = MessageBox.Show(“是否保存當(dāng)前文本至
”+path,“
記
事
本
”, MessageBoxButtons.OKCancel,MessageBoxIcon.Warning);if(dr == DialogResult.OK){ //使用保存文本方法 this.SaveMenu_Click(sender,e);//SaveMenu 的單擊事件 } } //退出程序 Application.Exit();}
58.RichTextBox 與 TextBox 的換行區(qū)別問題。在 TextBox 中是以rn 為換行標(biāo)志,而在 RichTextBox 中是以n 為換行標(biāo)志。我在 C#程序中使用 RichTextBox 將其文本內(nèi)容保存為文本文件,即(*.txt)類型的文件時(shí),文本不能正常的換行,且出現(xiàn)一些亂碼。這是由于文本文件是以rn 作為換行符,當(dāng)我改用 TextBox 時(shí),就 沒有這樣的問題了。
59.文本的選中問題。SelectedText 這一屬性可以指向當(dāng)前控件中選中的文本,60.通常我們會(huì)發(fā)現(xiàn),保存操作在文本沒有保存的情況下時(shí)才會(huì)出現(xiàn) 保存文件對(duì)話框,當(dāng)文本已經(jīng)保存過時(shí),新增的文本會(huì)保存到原 文件中,這兩個(gè)操作需要對(duì)文本是否保存過進(jìn)行判斷。我采用了 判斷路徑的方法對(duì)我文本是否保存過進(jìn)行了判斷,當(dāng)文本路徑存 在時(shí),表明文本已經(jīng)保存過,反之,則沒有保存過。
第二篇:《C專家編程》總結(jié)
《C專家編程》總結(jié)
開始讀《C專家編程》之前,有一個(gè)很擔(dān)心的問題:94年出的講語言的書,在現(xiàn)在(2012)還有多少是適用的。因此,一邊讀,一邊用VS2010做實(shí)驗(yàn)。最后發(fā)現(xiàn)大部分內(nèi)容都還在用。讀完后,覺得最精彩的部分有二:一是講解如何理解聲明,二是深入地講解數(shù)組名與指針。下文是將看書過程中所做的筆記進(jìn)行的整理。
p.s: 以下代碼均在VS2010測(cè)試過
1.使用無符號(hào)數(shù)時(shí)要特別注意(不推薦使用無符號(hào)數(shù))當(dāng)無符號(hào)數(shù)與有符號(hào)數(shù)在同一條表達(dá)式中出現(xiàn)時(shí),有符號(hào)數(shù)會(huì)被轉(zhuǎn)換為無符號(hào)數(shù)。e.g:
int feng =-1;unsigned int yan = 5;bool result =(feng < yan)? true : false;//最后的結(jié)果會(huì)是false 原因是C語言在計(jì)算含有不同類型的表達(dá)式時(shí),會(huì)將類型向上提升。在本例中,int被提升了unsigned int,從而使-1的補(bǔ)碼被解析為很大的整數(shù) 2.相鄰的字符串常量會(huì)被自動(dòng)合并成一個(gè)字符串 e.g:
char *str[] = {“feng” “yan”, “zero”};//“feng”和“yan”被合并成一個(gè)了:“fengyan” 3.易出錯(cuò)的優(yōu)先級(jí)
.高于* e.g: *p.f 正確的理解:*(p.f)
[]高于* e.g: int *ap[] 正確的理解:int *(ap[])ap是個(gè)數(shù)組,其元素為int* 函數(shù)()高于* e.g: int *fp()正確的理解:int* fp()fp是返回int*的函數(shù)
==和!=高于位操作符 e.g: val & mask!= 0 正確的理解:val &(mask!= 0)==和!=高于賦值符 e.g: c = getchar()!= EOF 正確的理解:c =(getchar()!= EOF)
算術(shù)運(yùn)算高于移位運(yùn)算 e.g: msb<<4 + lsb 正確的理解:msb <<(4 + lsb)逗號(hào)運(yùn)算符優(yōu)先級(jí)最低 e.g: i = 1, 2 正確的理解:(i = 1), 2 4.理解聲明,定義,typedef語句的步驟
a.找標(biāo)識(shí)符
b.找被括號(hào)括起來的部分
c.找后綴操作符,如果是(),則表示是函數(shù);如果是[],則表示是數(shù)組 d.找前綴操作符,如果是*,則表示“指向XX的指針”
e.找const和volatile,如果const,volatile后面緊跟類型(如int,long),那么它作用于類型,其它情況下,作用于它左邊緊鄰的項(xiàng) e.g:
int const * zero;//zero是一個(gè)指針,指向一個(gè)常量整形
char(*zero)[20];//zero是一個(gè)指針,指向一個(gè)有20個(gè)char元素的數(shù)組
typedef void(*ptr_to_func)(int);//ptr_to_func是新類型名,這種類型是一個(gè)函數(shù)指針,指向接收一個(gè)int參數(shù),并返回void的函數(shù)
char* const *(*zero)();//zero是一個(gè)函數(shù)指針,該函數(shù)無參數(shù),并返回一個(gè)指針,返回的指針指向一個(gè)常量指針
char*(*zero[10])(int **p);//zero是一個(gè)數(shù)組,元素是函數(shù)指針,其指向的函數(shù)授受一個(gè)二維指針,并返回一個(gè)指向char的指針 void(*signal(int sig, void(*func)(int)))(int);//signal是一個(gè)函數(shù),該函數(shù)接收一個(gè)int,一個(gè)函數(shù)指針,并返回一個(gè)函數(shù)指針
5.左值與右值
左值通常表示存儲(chǔ)結(jié)果的地方(地址),其值在編譯時(shí)可知 右值通常表示地址的內(nèi)容,其值通常要到運(yùn)行時(shí)才知道
6.指針與數(shù)組名不等同的情況(定義為數(shù)組,卻聲明為指針,或者反過來)前提知識(shí)(假設(shè)有定義:int array[10], *ptr;):
a.使用數(shù)組名下標(biāo)訪問(如:array[1]),會(huì)直接將數(shù)組名的地址加上偏移值作為變量的地址(即array[1]的地址)
b.使用指針下標(biāo)訪問(如:ptr[1]),會(huì)先取指針指向的內(nèi)容,然后將這個(gè)內(nèi)容加上偏移值作為變量的地址(即ptr[1]的地址)
不等同的原因:
當(dāng)定義為數(shù)組,卻聲明為指針時(shí),相當(dāng)于用指針下標(biāo)訪問的方法來解析一個(gè)數(shù)組名下標(biāo),即先取數(shù)組第0號(hào)元素的內(nèi)容,然后將這個(gè)內(nèi)容加上偏移值作為變量的地址,從而訪問了不該訪問的東西。反之亦然。7.指針與數(shù)組等同的情況
a.編譯器將表達(dá)式中的數(shù)組名當(dāng)作指向該數(shù)組第0號(hào)元素的指針,下標(biāo)當(dāng)作指針的偏移量,即array[i]會(huì)被當(dāng)作*(array + i)
b.編譯器將函數(shù)參數(shù)聲明中的數(shù)組名當(dāng)作指向該數(shù)組第0號(hào)元素的指針,即在函數(shù)內(nèi)部得到的是指針,而不是數(shù)組名
基于a情況,可知這條謠言是假的(至少在一維數(shù)組中一定不成立): 用指針迭代數(shù)組比用下標(biāo)迭代數(shù)組更快
基于b情況,可解釋為什么在傳遞數(shù)組后,不能用以下方法計(jì)算數(shù)組長度
int ArrayLength(int arr[]){ return sizeof(arr)/ sizeof(arr[0]);//返回值必定是1,因?yàn)榇藭r(shí)的arr是一個(gè)指針,而不是數(shù)組名 } 注意b情況的將數(shù)組改寫為指針并不是遞歸定義的,e.g:
實(shí)參 char zero[10][10] 被改寫為 char(*zero)[10],這里將數(shù)組的數(shù)組改寫為數(shù)組的指針
實(shí)參 char *zero[10] 被改寫為 char **zero,這里將指針數(shù)組改寫為指針的指針
實(shí)參 cahr(*zero)[10] 不改變,因?yàn)榇藭r(shí)的zero是指針,而不是數(shù)組 8.interposition interposition指用戶定義的函數(shù)取代庫中聲明完全相同的函數(shù),注意這不是指重載,而是指下面這種:
void zero();//user defined function void zero();//library function 出現(xiàn)interposition時(shí),要特別注意以下情況:
void zero();//user defined function int main(){ zero();//調(diào)用用戶定義的函數(shù)zero,而不是庫函數(shù)zero
FengYan();//假設(shè)這是另一個(gè)庫函數(shù),并且函數(shù)內(nèi)調(diào)用庫函數(shù)zero,此時(shí)由于interposition,變成調(diào)用用戶定義的zero return 0;} 備注:
出現(xiàn)interposition時(shí),在VS2010會(huì)出現(xiàn)warning: inconsistent dll linkage 9.堆棧段作用
a.存儲(chǔ)局部變量
b.函數(shù)調(diào)用時(shí),存儲(chǔ)有關(guān)的維護(hù)信息
c.用作暫時(shí)存儲(chǔ)區(qū)。e.g: 計(jì)算一個(gè)很長的表達(dá)式時(shí),會(huì)把部分結(jié)果先壓到堆棧中
第三篇:C語言編程自我總結(jié)
1.編譯器選擇8級(jí)優(yōu)化時(shí),可能會(huì)出現(xiàn)錯(cuò)誤。剛寫好的程序,建議先用0級(jí)優(yōu)化看能否正常運(yùn)行,再用更高的優(yōu)化等級(jí)進(jìn)行優(yōu)化。
2.a、寫中斷程序一定要用using語句指定寄存器組。第1、2、3組都可以,不能是0,否則可能會(huì)main()函數(shù)沖突。從一個(gè)中斷程序中調(diào)用函數(shù)必須和中斷使用相同的寄存器組(摘自《Keil Cx51 編譯器用戶手冊(cè)中文版》P129)。建議把原本中斷函數(shù)需要調(diào)用的函數(shù)直接寫在中斷函數(shù)里,無須調(diào)用。
b、51單片機(jī)的中斷有兩個(gè)優(yōu)先級(jí)。一個(gè)中斷不會(huì)打斷另一個(gè)相同優(yōu)先級(jí)的中斷。這樣相同級(jí)別中斷可以使用同一個(gè)組。比如:低優(yōu)先級(jí)的中斷函數(shù)都用 using 1,高優(yōu)先級(jí)的中斷都用 using 2。這樣不會(huì)沖突。
3.C語言無符號(hào)數(shù)容易犯的錯(cuò)誤。若定義成有符號(hào)數(shù)char,則不會(huì)陷入死循環(huán)。
main(){ unsigned char i;for(i = 2;i>=0;i--){ printf(“%d”,i);} }
4.C51忌諱使用絕對(duì)定位_at_,因?yàn)橹灰x變量和變量的作用域,編譯器就會(huì)把一個(gè)固定地址給這個(gè)變量,無須人工將其絕對(duì)定位,這樣可能引發(fā)其他問題。
5.bit與sbit的區(qū)別:bit定義的位標(biāo)量的地址是隨機(jī)的,而sbit定義的位標(biāo)量的地址是確定的。bit只能訪問芯片內(nèi)部RAM中的可尋址位20H-2FH,而sbit可以訪問芯片內(nèi)部RAM中的可尋址位和特殊功能寄存器中的可尋址位。注意不能直接在程序里用P1^0等位變量,需要經(jīng)過sbit定義才可以使用。例如:
bit
tem;sbit led=P1^0;tem的地址是隨機(jī)分配的,而led的地址則固定為0x90.0。sbit變量后面需要跟等號(hào)=。6.為了避免由于使用參數(shù)宏而帶來意外的錯(cuò)誤,需要注意以下幾點(diǎn):
6.1 宏的參數(shù)必須帶括號(hào),例如 #define CIRCLE_SQUARE(R)3.141*(R)*(R)6.2 對(duì)所使用的參數(shù)宏進(jìn)行簡單地展開檢查;
6.3 使用簡單表達(dá)式、對(duì)參數(shù)加括號(hào)、避免節(jié)外生枝的使用方式(例如“++”、“--”一類都屬于不必要的附件運(yùn)算);
6.4 在參數(shù)宏定義時(shí),對(duì)于運(yùn)算順序通過括號(hào)進(jìn)行明確的限定,只要遵循以上幾點(diǎn),就可以避免大多數(shù)應(yīng)用場(chǎng)合的意外錯(cuò)誤。
手把手教你寫程序
內(nèi)容:從最簡單的程序入手,手把手教你寫程序,讓同學(xué)們拿到一個(gè)復(fù)雜的程序或者任務(wù),能快速找到切入點(diǎn),寫出程序,再在此基礎(chǔ)上優(yōu)化程序。當(dāng)拿到一個(gè)單片機(jī)任務(wù)時(shí),不要急于動(dòng)手寫程序,先仔細(xì)分析它的以下幾個(gè)點(diǎn):
1、它要單片機(jī)整體實(shí)現(xiàn)什么功能
2、功能細(xì)分(模塊化),先干什么,再干什么,最后干什么
3、畫初步流程圖,(把幾個(gè)模塊畫出即可)
4、模塊之間的分析:一個(gè)模塊到另一個(gè)模塊之間,怎么變換,怎么連接(優(yōu)化流程圖)
5、單個(gè)模塊分析:每個(gè)模塊要做什么(流程圖細(xì)化)
6、所有模塊結(jié)合連接,細(xì)化所有流程圖
7、分析單個(gè)模塊每步要用到的方法或者指令
8、總流程圖定型
9、紙上寫程序,對(duì)照流程圖分析其可行性,若不可行則返回
10、上機(jī)調(diào)試,加注釋
11、從小到大,一個(gè)功能一個(gè)功能地調(diào)試;
以上十一步,缺一不可(小程序例外)切記:流程圖的確定很重要,需反復(fù)修改
大忌:拿到任務(wù),不仔細(xì)分析就寫程序。即使是小程序,我們也要養(yǎng)成良好的編程習(xí)慣,不要一味的追求結(jié)果。寫小程序可能比別人快,若是大程序,一旦出現(xiàn)思維混亂,或者出現(xiàn)程序調(diào)試不出結(jié)果,那么你花在調(diào)試上的時(shí)間,要比別人的多。?。?!磨刀不誤砍柴工?。?!程序的優(yōu)化:屬于后期工作,只有調(diào)試出來后,才去優(yōu)化,如果一開始優(yōu)化和寫程序同時(shí)進(jìn)行,一是加重你的思考量,二是出現(xiàn)問題無從下手。無疑增加了寫程序的難度。對(duì)于一個(gè)初學(xué)者,寫一個(gè)程序,本身頭腦就處于緊張的狀態(tài),思考的問題就很多,如果此時(shí)把優(yōu)化程序也考慮進(jìn)去,你腦袋的負(fù)荷無疑加重,若你頭腦精明,你可以把優(yōu)化的地方,先在紙上記下來,等到調(diào)試結(jié)果正常,再把你想到的,優(yōu)化的地方加進(jìn)去。
7、如果在中斷程序中改變了多字節(jié)類型的變量,那么中斷程序以外的程序中(主程序,子函數(shù))要使用該多字節(jié)類型變量的話,讀寫前要關(guān)中斷,讀寫后再開中斷。否則會(huì)導(dǎo)致偶爾讀寫錯(cuò)誤。(實(shí)質(zhì)為資源沖突)舉一反三:
其他的數(shù)據(jù)類型也可能有這種影響。例如:長整型、浮點(diǎn)型。例如:
unsigned int ms_counter;void T0(){ //定時(shí)器程序每100毫秒中斷一次,程序略 if(ms_counter<1000)ms_counter++;} void main(void){ //初始化定時(shí)器程序每100毫秒中斷一次,程序略 unsigned char tt;ms_counter=0;tt=0;//用tt控制只響一次 while(1){ if(ms_counter<400){ if(tt==0){ tt=1;Sound_on();
} } else { Sound_off();} //其他程序 } }
8、sbit變量不能使用extern關(guān)鍵字,使其在不同的文件中被使用,如要在led.c和main.c文件中使用同一個(gè)變量led0,有以下下兩種辦法:
1.在各種文件中重復(fù)定義變量,如在led.c中定義sbit led0=P1^0;同樣在main.c中定義sbit led0=P1^0;這樣,led0就變成了全局變量,可以在兩個(gè)文件中使用。
2.將sbit led0=P1^0定義到led.h頭文件中,均在led.c和main.c中包含led.h這個(gè)頭文件。
9、在多文件的程序中聲明外部變量(extern和)
如果一個(gè)程序包含兩個(gè)文件,在兩個(gè)文件中都要用到同一個(gè)外部變量Num,不能分別在兩個(gè)文件中各自定義一個(gè)外部變量Num,否則在進(jìn)行程序的連接時(shí)會(huì)出現(xiàn)“重復(fù)定義”的 錯(cuò)誤。正確的做法是:在任一個(gè)文件中定義外部變量Num,而在另一個(gè)文件中用extern對(duì)Num作“外部變量聲明”。即extern Num;注意若Num為uchar類型,應(yīng)當(dāng)寫為“extern uchar Num”,否則會(huì)當(dāng)為int,而導(dǎo)致出錯(cuò)。
當(dāng)使用static聲明變量和函數(shù)時(shí),需要在定義變量和函數(shù)的基礎(chǔ)上加上此關(guān)鍵字,而不能單獨(dú)使用。例如:
static int a;//定義性聲明,需要時(shí),直接使用變量a即可 a = 0x01;
static int funA(int a, int b);//聲明,且static不起作用 int funA(int a ,int b)//定義,即使funA有static關(guān)鍵字修飾,但由于static不能單獨(dú)使用,//故funA仍為外部函數(shù)。
{ …… } extern對(duì)變量進(jìn)行聲明時(shí),如沒有初始化,則為引用性聲明,不含定義,如需使用此變量,需要進(jìn)行定義。例如:
extern int a;//引用性聲明,不含定義
extern int a = 0x01;//定義性聲明,需要時(shí),直接使用變量a即可 int a;//定義
extern對(duì)函數(shù)進(jìn)行聲明時(shí),如沒有函數(shù)體,則為引用性聲明,不含定義。
extern int funB(int a ,int b);//引用性聲明,不含定義,且extern聲明可以省略
extern int funC(int a, int b)//定義性聲明 { …… }
10、一般的,要盡量減少中斷服務(wù)程序的內(nèi)容和長度。因?yàn)樵谥鞒绦蛑锌梢赃€需要隨時(shí)響應(yīng)其他的中斷或事件。如果一個(gè)中斷服務(wù)程序過程,很可能會(huì)影響到主程序?qū)ν獠啃盘?hào)的檢測(cè)和響應(yīng)。通常,在中斷程序中只是改變一些變量或標(biāo)志位,在主程序中再根據(jù)變量或標(biāo)志位的值進(jìn)行判斷,處理相應(yīng)的事件。
11、在A/D和D/A轉(zhuǎn)換電路中,電源電壓和基準(zhǔn)電壓的穩(wěn)定性,對(duì)轉(zhuǎn)換的精度影響很大。另外,A/D和D/A轉(zhuǎn)換電路中要特別注意地線的正確連接,否則轉(zhuǎn)換結(jié)果將是不正確的,干擾影響將很嚴(yán)重。
12、根據(jù)C語言標(biāo)準(zhǔn),左移“<<”和右移“>>”運(yùn)算要求操作數(shù)至少是int,如果不滿int,自動(dòng)轉(zhuǎn)換成int(C語言整型提升)。因此 uchar a=0x01;a<<8;實(shí)際運(yùn)算,并不是8位數(shù)左移8位,而是int型左移8位。
13、在中斷里調(diào)用其他函數(shù),且要進(jìn)行參數(shù)傳遞時(shí),必須保證被調(diào)用函數(shù)所使用的寄存器組與中斷函數(shù)一樣,否則會(huì)產(chǎn)生不正確的結(jié)果。為了保證被調(diào)用的函數(shù)與中斷函數(shù)使用的寄存器一致,可對(duì)被調(diào)用函數(shù)使用using,不過此函數(shù)只能被中斷函數(shù)調(diào)用。
14、函數(shù)不使用using 時(shí),所使用寄存器組保持與此函數(shù)被調(diào)用前相同,不對(duì)RS0和RS1的值進(jìn)行修改;當(dāng)使用了using 關(guān)鍵字后,此函數(shù)所使用的寄存器組與using所定義的一樣。
15、當(dāng)指定中斷程序的工作寄存器組時(shí),保護(hù)工作寄存器的工作就可以被省略。使用關(guān)鍵 字using 后跟一個(gè)0 到3 的數(shù)對(duì)應(yīng)著4 組工作寄存器當(dāng)指定工作寄存器組的時(shí)候默 認(rèn)的工作寄存器組就不會(huì)被推入堆棧這將節(jié)省32 個(gè)處理周期,因?yàn)槿霔:统鰲6夹枰? 個(gè)處理周期。為中斷程序指定工作寄存器組的缺點(diǎn)是所有被中斷調(diào)用的過程都必須使用 同一個(gè)寄存器組否則參數(shù)傳遞會(huì)發(fā)生錯(cuò)誤。
16、如何使用pdata 類型的變量?當(dāng)要使用到pdata 類型的變量,如下: void main(void){ uchar pdata a;a=0x01;}
則需要進(jìn)行如下設(shè)置,否則pdata 的變量a則會(huì)無效。
a、修改STARTUP.A51的內(nèi)容。默認(rèn)時(shí),PPAGEENALBE為0,表示不允許pdata類型的變量,須將其值改為1;PPAGE表示pdata類型的變量存儲(chǔ)在哪一頁,01H表示存放在外部存儲(chǔ)器的第1頁,地址范圍100H至1FFH,此時(shí)P2經(jīng)STARTUP.A51處理后的值為0x01;此項(xiàng)設(shè)置需和BL51連接器的設(shè)置一致。
b、修改BL51連接器。根據(jù)STARTUP.A51中PPAGE所設(shè)置的值來填寫Pdata的值,如下圖。圖中Pdata的值可以填寫100H至1FFH中任意一個(gè),表示pdata類型的變量從所填
寫的值開始存儲(chǔ)。例如,當(dāng)Pdata填寫的值為108H時(shí),表示pdata類型的變量從108H開始存儲(chǔ),因此,存儲(chǔ)范圍變?yōu)榱?08H至1FFH。
另外,存儲(chǔ)模式Compact的作用是將沒有指定存儲(chǔ)類型的變量定義為pdata類型,對(duì)uchar pdata a;變量的定義沒有影響,但對(duì)uchar a;則有影響。
17、XBYTE的用法。XBYTE存在于#include
XBYTE[0x000F]=data; // 此語句表示將data寫到外部RAM中的0x000F data=XBYTE[0x000F] // 此語句表示讀取外部RAM中0x000F的數(shù)據(jù) 以下語句與上面的語句等效:
#define EX_RAM XBYTE[0x000F] //將EX_RAM定義為外部RAM的地址0x000F EX_RAM=data;// 此語句表示將data寫到外部RAM中的0x000F data=EX_RAM // 此語句表示讀取外部RAM中0x000F的數(shù)據(jù)
18、如何在keil中用匯編實(shí)現(xiàn)51中沒有的指令
部分MCU與8051兼容,但會(huì)增加8051中沒有的指令,如華邦的W77E58和N79E352等芯片,具有8051中沒有的指令DEC DPTR。如何才Keil中實(shí)現(xiàn)此指令呢? 方法1:
在需要執(zhí)行該指令的地方放置相應(yīng)的機(jī)器碼 MAIN:
MOV DPTR,#02H DB 0A5H;由于從數(shù)據(jù)手冊(cè)上得知,DEC DPTR的機(jī)器碼為0A5H,故此處相當(dāng)于執(zhí)行了DEC DPTR指令。
AJMP $ END
方法2:
使用宏定義的方法
/*宏定義,表示用DEC_DPTR代替MACRO與ENDM之間的內(nèi)容*/ DEC_DPTR MACRO
DB 0A5H;此處不能與MACRO同一行 ENDM
MAIN: MOV DPTR,#02H DEC_DPTR;放置機(jī)器碼0A5H,相當(dāng)于執(zhí)行DEC DPTR AJMP $ END
通過將以上兩種方法生成的hex文件調(diào)入到編程器中,發(fā)現(xiàn)代碼一樣。經(jīng)測(cè)試,同樣可以用以上兩種方法代替8051中已有的指令。
例如,從數(shù)據(jù)手冊(cè)可知,MOV A,#0FH的長度為2字節(jié),機(jī)器碼的值為74H,0FH。因此,經(jīng)驗(yàn)證,以下三個(gè)程序等效,產(chǎn)生的HEX文件一樣 MAIN: MOV A,#55H DB 74H DB 0FH MOV P1,A AJMP $ END
MAIN: MOV A,#55H MOV A,#0FH MOV P1,A AJMP $ END
TEST MACRO DB 74H DB 0FH ENDM MAIN: MOV A,#55H TEST MOV P1,A AJMP $ END
18、匯編中包含頭步驟:
例如,T2CON為定時(shí)器2的特殊功能寄存器,地址為0C8H,要對(duì)此寄存器賦值01H,除了
MOV 0C8H,#01H 和
T2CON EQU 0C8H MOV T2CON,#01H 外,還有用包含頭文件的方法 #include
19、指針
C51 提供一個(gè)3 字節(jié)的通用存儲(chǔ)器指針。通用指針的頭一個(gè)字節(jié)表明指針?biāo)傅拇鎯?chǔ) 區(qū)空間,另外兩個(gè)字節(jié)存儲(chǔ)16 位偏移量。對(duì)于DATA IDATA 和PDATA 段只需要8 位偏移量。Keil 允許使用者規(guī)定指針指向的存儲(chǔ)段,這種指針叫具體指針。使用具體指針的好處是節(jié)省了存儲(chǔ)空間編譯器不用為存儲(chǔ)器選擇和決定正確的存儲(chǔ)器操作指令產(chǎn)生代碼這樣就使代碼更加簡短但你必須保證指針不指向你所聲明的存儲(chǔ)區(qū)以外的地方否則會(huì)產(chǎn)生錯(cuò)誤而且很難調(diào)試。
由于使用具體指針能夠節(jié)省不少時(shí)間所以我們一般都不使用通用指針。
20、EEPROM存放開關(guān)機(jī)(復(fù)位)次數(shù)方法:每次開機(jī)(復(fù)位)讀取EEPROM存放開關(guān)機(jī)的數(shù)據(jù),并加1后重新寫入EEPROM。
21、C51中,將printf函數(shù)與串口輸出結(jié)合注意事項(xiàng):
a、關(guān)串口中斷;
b、初始化串口,并使TI=1;
c、KEIL里擴(kuò)展出了b(8位),h(16位),l(32位)來對(duì)輸入字節(jié)寬的設(shè)置
在Keil C51中用printf輸出一個(gè)單字節(jié)變量時(shí)要使用%bd,若使用%d,則默認(rèn)為雙字節(jié)寬度,輸出可能會(huì)出錯(cuò)。如
unsigned char counter;printf(“Current count: %bdn”, counter);而在標(biāo)準(zhǔn)C語言中都是使用%d: printf(“Current count: %dn”, counter);d、輸出數(shù)據(jù)類型的長度應(yīng)與定義的數(shù)據(jù)類型長度一致,如:
uint tem2=97;
printf(“%c,%bdn”,tem2,tem2);第一個(gè)輸出會(huì)出錯(cuò)。
22、我一般不刻意的注意這個(gè),都是從軟件自身找問題的。
我寫程序時(shí)對(duì)于軟件抗干擾都是在程序狀態(tài)上考慮意外情況的,例如:
if(a == 1){...} else if(a == 2){....} else{//這個(gè)else 一定得加的,即使自己認(rèn)為不可能出現(xiàn)的情況也要加上
..//經(jīng)過好多程序走飛的情況發(fā)現(xiàn):大多情況都是缺少這個(gè)語句條件的,這 //語句可以寫成重新初始化a } 還有程序出現(xiàn)堆棧比較深的運(yùn)算(例如浮點(diǎn)乘除法后)或中斷比較深,我加2個(gè)_nop_();
23、STC12C5410AD外部RAM使用方法:
a.在Keil中設(shè)置外部RAM的起始地址和大小,如下圖
b.將變量定義為xdata即可。
24、中斷嵌套
當(dāng)有外部中斷0時(shí),中斷標(biāo)志位IE0由硬件自動(dòng)置1,進(jìn)入中斷服務(wù)程序后,IE0被自動(dòng)清0。若外部中斷0觸發(fā)信號(hào)在執(zhí)行完中斷服務(wù)程序后仍沒有撤除,就會(huì)再次使已經(jīng)變0的中斷標(biāo)志位IE0置1,再次進(jìn)入中斷服務(wù)程序;若在響應(yīng)中斷服務(wù)程序期間,再次產(chǎn)生外部中斷0觸發(fā)信號(hào)時(shí),此中斷不能被識(shí)別,因?yàn)镃PU在響應(yīng)中斷時(shí)會(huì)自動(dòng)關(guān)閉同一中斷。
如果外部中斷0比外部中斷1的優(yōu)先級(jí)高,當(dāng)在響應(yīng)外部中斷0期間產(chǎn)生外部中斷1時(shí),如果執(zhí)行完外部中斷0后,外部中斷1的中斷請(qǐng)求標(biāo)志位IE1仍沒有清除的話,將會(huì)響應(yīng)外部中斷1的請(qǐng)求;但是如果在響應(yīng)外部中斷0期間,外部中斷1的觸發(fā)信號(hào)產(chǎn)生后又撤除的話,IE1也會(huì)自動(dòng)清除,也就是說,執(zhí)行完外部中斷0后,不會(huì)去響應(yīng)外部中斷1。
當(dāng)多個(gè)中斷源同時(shí)向CPU請(qǐng)求中斷時(shí),CPU就可以通過中斷優(yōu)先權(quán)電路率先響應(yīng)中斷優(yōu)先權(quán)高的中斷請(qǐng)求,而把中斷優(yōu)先權(quán)低的中斷請(qǐng)求暫時(shí)擱置起來,等到處理完優(yōu)先權(quán)高的中斷請(qǐng)求后再來響應(yīng)優(yōu)先權(quán)低的中斷。
如果某一中斷源提出中斷請(qǐng)求后,CPU不能立即響應(yīng),只要該中斷請(qǐng)求標(biāo)志位不被軟件人為清除,中斷請(qǐng)求的狀態(tài)就將一直保持,直到CPU響應(yīng)中斷為止。但是對(duì)于串行口中斷,即使CPU響應(yīng)了中斷,其中斷標(biāo)志位RI/TI也不會(huì)自動(dòng)清零,而必須在中斷服務(wù)程序中設(shè)置
清除RI/TI的指令后,才會(huì)再一次地提出中斷請(qǐng)求。
25、在滿足應(yīng)用要求的前提下,選擇配較低的單片機(jī),較小的RAM/ROM、較低的ADC分辨率、較低的ADC速率,較少的IO管腳都可以降低單片機(jī)的整體功耗。當(dāng)然了,這個(gè)得能滿足你產(chǎn)品需求的前提下。
26、對(duì)于一個(gè)數(shù)字系統(tǒng)而言,其功耗大致滿足公式:P=CV2f。其中C為系統(tǒng)的負(fù)載電容,V為電源電壓,f為系統(tǒng)工作頻率[2]。功耗與電源電壓的平方成正比,因此電源電壓對(duì)系統(tǒng)的功耗影響最大,其次是工作頻率,再次就是負(fù)載電容。負(fù)載電容對(duì)設(shè)計(jì)人員而言,一般是不可控的,因此設(shè)計(jì)一個(gè)低功耗系統(tǒng),在不影響系統(tǒng)性能的前提下,盡可能地降低電源的電壓和工作頻率。對(duì)于大多數(shù)低功耗單片機(jī)來說,工作頻率越低,意味著消耗的電流也越小,但是不能認(rèn)為頻率越低,系統(tǒng)整體功耗越小,因?yàn)楣ぷ黝l率降低,意味著需要更長的處理時(shí)間,其他外圍電路消耗的電能就越多。目前有很多單片機(jī)都允許有兩個(gè)或者兩個(gè)以上的時(shí)鐘源,低頻時(shí)鐘作為如UART、定時(shí)器等外圍功能器件的時(shí)鐘源,高頻時(shí)鐘作為系統(tǒng)的主時(shí)鐘。在不需要高速運(yùn)行的場(chǎng)合下,低頻時(shí)鐘也可以作為系統(tǒng)主時(shí)鐘使用。對(duì)于需要在工作狀態(tài)與空閑狀態(tài)之間頻繁切換的應(yīng)用,在考慮單片機(jī)本身低功耗的同時(shí),應(yīng)該考慮切換時(shí)間和切換電流??紤]到有些場(chǎng)合單片機(jī)的工作特點(diǎn),選擇單片機(jī)不光要關(guān)注工作電流,更應(yīng)該關(guān)注單片機(jī)休眠時(shí)的靜態(tài)電流。單片機(jī)豐富的低功耗模式和極低的靜態(tài)電流,在滿足特定應(yīng)用功能的同時(shí),有效降低系統(tǒng)的功耗。盡量關(guān)閉MCU內(nèi)部不用的資源,比如ATmega8內(nèi)部的模擬比較器,默認(rèn)是開著的,還有ATmega88內(nèi)部的大多數(shù)資源都可以在不用的時(shí)候用軟件關(guān)閉。
27、定時(shí)/ 計(jì)數(shù)器的實(shí)時(shí)性
定時(shí)/ 計(jì)數(shù)器啟動(dòng)計(jì)數(shù)后,當(dāng)計(jì)滿回0 溢出向主機(jī)請(qǐng)求中斷處理,由內(nèi)部硬件自動(dòng)進(jìn)行。但從回0 溢出請(qǐng)求中斷到主機(jī)響應(yīng)中斷并作出處理存在時(shí)間延遲,且這種延時(shí)隨中斷請(qǐng)求時(shí)的現(xiàn)場(chǎng)環(huán)境的不同而不同,一般需延時(shí)3 個(gè)機(jī)器周期以上,這就給實(shí)時(shí)處理帶來誤差差。大多數(shù)應(yīng)用場(chǎng)合可忽略不計(jì),但對(duì)某些要求實(shí)時(shí)性苛刻的場(chǎng)合,可采用動(dòng)態(tài)補(bǔ)償措施。
所謂動(dòng)態(tài)補(bǔ)償,即在中斷服務(wù)程序中對(duì)THx、TLx 重新置計(jì)數(shù)初值時(shí),應(yīng)將THx、TLx 從回0 溢出又重新從0 開始繼續(xù)計(jì)數(shù)的值讀出,并補(bǔ)償?shù)皆?jì)數(shù)初值中去進(jìn)行重新設(shè)置??煽紤]如下補(bǔ)償方法: CLR EA ;禁止中斷
MOV A,T L x ;讀TLx 中已計(jì)數(shù)值 ADD A,#LOW ;LOW 為原低字節(jié)計(jì)數(shù)初值 MOV T L x,A ;設(shè)置低字節(jié)計(jì)數(shù)初值 MOV A,#HIGH ;原高字節(jié)計(jì)數(shù)初值送A ADDC A,T H x ;高字節(jié)計(jì)數(shù)初值補(bǔ)償 MOV T H x,A ;置高字節(jié)計(jì)數(shù)初值 SETB EA ;開中斷
28、動(dòng)態(tài)讀取運(yùn)行中的定時(shí)器/計(jì)數(shù)值
在動(dòng)態(tài)讀取運(yùn)行中的定時(shí)/ 計(jì)數(shù)器的計(jì)數(shù)值時(shí),如果不加注意,就可能出錯(cuò)。這是因?yàn)椴豢赡茉谕粫r(shí)刻同時(shí)讀取THx 和TLx 中的計(jì)數(shù)值。比如,先讀TLx 后讀THx,因?yàn)槎〞r(shí)/ 計(jì)數(shù)器處于運(yùn)行狀態(tài),在讀TLx 時(shí)尚未產(chǎn)生向THx 進(jìn)位,而在讀THx 前已產(chǎn)生進(jìn)位,這時(shí)讀得的THx 就不對(duì)了;同樣,先讀THx 后讀TLx 也可能出錯(cuò)。
一種可避免讀錯(cuò)的方法是:先讀THx,后讀TLx,將兩次讀得的THx 進(jìn)行比較;若兩次讀得的值相等,則可確定讀的值是正確的,否則重復(fù)上述過程,重復(fù)讀得的值一般不會(huì)再錯(cuò)。此法的軟件編程如下:
RDTM: MOV A,THx ;讀取THx 存A 中 MOV R0,TLx ; 讀取TLx 存R0 中
CJNE A,THx,RDTM ;比較兩次THx 值,若相等,則讀得的值正確,否則重讀 MOV R1,A ;將THx 存于R1 中
29、掉電及空閑模式
掉電方式
當(dāng)PCON中的第二位PD為1時(shí),進(jìn)入掉電模式,不會(huì)執(zhí)行任何指令,外部時(shí)鐘停振,片內(nèi)所有功能部件停止工作,如定時(shí)器,串行口,外部中斷(部分增強(qiáng)型8051的外部中斷可以工作),但片內(nèi)RAM和SFR的內(nèi)容保持不變。標(biāo)準(zhǔn)8051從掉電狀態(tài)退出的惟一方法是硬件復(fù)位(部分增強(qiáng)型8051還可以通過外部中斷來退出掉電狀態(tài)),復(fù)位后,SFR被重新初始化,但RAM的內(nèi)容不變。因此,若要使得8051在供電恢復(fù)正常后繼續(xù)執(zhí)行掉電前的程序,那就必須在掉電前預(yù)先把SFR中的內(nèi)容保護(hù)到片內(nèi)RAM,并在供電恢復(fù)正常后為SFR恢復(fù)到掉電前的狀態(tài)。
當(dāng)PCON的第一位IDEL為1時(shí),進(jìn)入空閑模式,CPU停止工作,不會(huì)執(zhí)行任何指令,但中斷、串行口和定時(shí)器可以繼續(xù)工作。此時(shí),CPU現(xiàn)場(chǎng)(即SP、PC、PSW和ACC等)、片內(nèi)RAM和SFR中其他寄存器內(nèi)容均維持不變。退出空閑模式有兩種方法:
一、被允許中斷的中斷源發(fā)出中斷請(qǐng)求;
二、硬件復(fù)位。30、看門狗應(yīng)用
將喂狗操作(取反指令,如 CPL P1.0)分成兩步,放在主程序和中斷里執(zhí)行。如將SETB P1.0放在主程序中,將CLR P1.0放在中斷里,這樣可以避免主程序跑飛,中斷功能正?;蛘咧鞒绦蛘?,而中斷跑飛的情況導(dǎo)致看門狗失效。
31、volatile作用
如果將將變量加上volatile修飾,則編譯器保證對(duì)此變量的讀寫操作都不會(huì)被優(yōu)化(肯定執(zhí)行)。此例中i也應(yīng)該如此說明。
一般說來,volatile用在如下的幾個(gè)地方:
1、中斷服務(wù)程序中修改的供其它程序檢測(cè)的變量需要加volatile;
2、多任務(wù)環(huán)境下各任務(wù)間共享的標(biāo)志應(yīng)該加volatile;
3、存儲(chǔ)器映射的硬件寄存器通常也要加volatile說明,因?yàn)槊看螌?duì)它的讀寫都可能由不同意義;
另外,以上這幾種情況經(jīng)常還要同時(shí)考慮數(shù)據(jù)的完整性(相互關(guān)聯(lián)的幾個(gè)標(biāo)志讀了一半被打斷了重寫),在1中可以通過關(guān)中斷來實(shí)現(xiàn),2中可以禁止任務(wù)調(diào)度,3中則只能依靠硬件的良好設(shè)計(jì)了。32、51準(zhǔn)雙向口讀?。?/p>
只有1條指令:
MOV A,P1為讀端口寄存器 有兩條指令: MOV A,#0FFH MOV A,P1為讀引腳
33、采用C語言和匯編語言混合編程是最佳的選擇;
34、系統(tǒng)投入運(yùn)行的最初時(shí)刻,應(yīng)對(duì)系統(tǒng)進(jìn)行自檢和初始化。開機(jī)自檢在初始化前執(zhí)行,如果自檢無誤,則對(duì)系統(tǒng)進(jìn)行正常初始化,通常包括硬件初始化和軟件初始化兩個(gè)部分。硬件初始化是指對(duì)系統(tǒng)中的各種硬件資源設(shè)定明確的初始狀態(tài),如對(duì)各種可編程芯片進(jìn)行編程、對(duì)各I/O端口設(shè)定初始狀態(tài)和為單片機(jī)的硬件資源分配任務(wù)等。軟件初始化包括,對(duì)中斷的安排、對(duì)堆棧的安排、狀態(tài)變量的初始化、各種軟件標(biāo)志的初始化、系統(tǒng)時(shí)鐘的初始化和各
種變量儲(chǔ)存單元的初始化等。
35、按鍵連擊速度一般為3-4次/s。
36、復(fù)合鍵利用兩個(gè)以上按鍵同時(shí)按下時(shí)產(chǎn)生的按鍵效果,但實(shí)際情況下,不可能做到真正的“同時(shí)按下”,它們的時(shí)間差可以長達(dá)50ms左右,故當(dāng)檢測(cè)到有KEY1按鍵按下時(shí),還要等待超過50ms,再判斷是否還有其他按鍵按下,再解析按鍵。
37、數(shù)碼管顯示閃爍效果時(shí),一般閃爍速度為1-4次/s。
38、大電流和高電壓設(shè)備的啟動(dòng)和關(guān)閉都是由軟件指令來完成,這些指令執(zhí)行后,必然引起強(qiáng)烈的干擾,這些干擾不能算隨機(jī)干擾,它們與軟件完全相關(guān);可以在最后才執(zhí)行這些可能引起強(qiáng)烈干擾的I/O操作,之后立即進(jìn)入睡眠狀態(tài),這樣就不會(huì)干擾到CPU,等CPU醒來后,干擾的高峰也基本過去。
39、用積分時(shí)間為20ms整數(shù)倍的雙積分型A/D轉(zhuǎn)換方式,能有效地抑制50Hz工頻干擾。40、掉電檢測(cè)電路必須在電壓下降到CPU最低工作電壓之前就提出中斷申請(qǐng),提前時(shí)間為幾百us到數(shù)ms,以便掉電中斷程序進(jìn)行掉電保護(hù)。
41、用定時(shí)器作看門狗:當(dāng)為專職看門狗時(shí),在主程序中周期性清0定時(shí)器計(jì)數(shù)值,以使定時(shí)器中斷不能產(chǎn)生,當(dāng)產(chǎn)生定時(shí)器中斷時(shí),表明看門狗溢出,此時(shí)應(yīng)執(zhí)行出錯(cuò)處理程序或者進(jìn)行復(fù)位。當(dāng)為兼職看門狗時(shí),可以在定時(shí)器中斷程序?qū)τ?jì)數(shù)值進(jìn)行加1,若計(jì)數(shù)值大于某值時(shí),表明看門狗溢出,而主程序中應(yīng)周期性地對(duì)計(jì)數(shù)值進(jìn)行清0。
42、中斷中,沖突發(fā)生的條件:
1)某一資源為中斷程序和主程序所使用;該資源可以為1個(gè)變量,也可以為1個(gè)數(shù)組或者1個(gè)緩沖區(qū)。
2)中斷程序或主程序?qū)υ撡Y源進(jìn)行了寫操作;
3)主程序不能用一條指令對(duì)資源完成讀或者寫操作。(這條不對(duì),參考深入淺出AVR單片機(jī)P100的例子)
當(dāng)這三個(gè)條件均滿足時(shí),即有可能發(fā)生資源沖突,導(dǎo)致程序偶然運(yùn)行不正常。為了避免發(fā)生沖突,可以在主程序中先關(guān)中斷,再對(duì)資源進(jìn)行讀或?qū)?,結(jié)束后再開中斷。
當(dāng)主程序?qū)Y源的訪問比較費(fèi)時(shí),長期關(guān)中斷可能影響系統(tǒng)的實(shí)時(shí)性;解決的辦法是盡可能縮短關(guān)中斷的時(shí)間,將一邊訪問,一邊處理的工作方式改為集中訪問,分批處理。如果是讀該資源,則關(guān)中斷迅速將該資源的內(nèi)容轉(zhuǎn)移到緩沖區(qū),再開中斷,然后再對(duì)緩沖區(qū)中的信息進(jìn)行處理;如果是寫該資源,則先邊運(yùn)算邊寫緩沖區(qū),全部寫好后再關(guān)中斷,然后迅速將緩沖區(qū)中的內(nèi)容復(fù)制到該資源中,邊可以開中斷了。
43、A/B*C的運(yùn)算方案不如(A*C)/B的運(yùn)算方案精度高。因此,應(yīng)盡可能將出現(xiàn)偏差的運(yùn)算往后排,先進(jìn)行無偏差或偏差小的運(yùn)算。在定點(diǎn)運(yùn)算系統(tǒng)中,加減法只要不超限,是沒有偏差的,乘法運(yùn)算的結(jié)果會(huì)使字長增加,如雙字節(jié)乘雙字節(jié),積為四字節(jié),如果保留全部結(jié)果,則沒有偏差的。乘法運(yùn)算的結(jié)果會(huì)使字長增加,如雙字節(jié)乘雙字節(jié),積為四字節(jié),如果保留全部結(jié)果,則沒有偏差;如果受字長限制,則要舍去低位字節(jié),從而產(chǎn)生舍入偏差。除法幾乎都是有偏差的能夠剛好整除的情況是很少的。在浮點(diǎn)運(yùn)算系統(tǒng)中,加減法由于要進(jìn)行對(duì)階操作,當(dāng)兩操作數(shù)的階碼相差較大時(shí),絕對(duì)值大的數(shù)有可能將絕對(duì)值小的數(shù)淹沒,使運(yùn)算的結(jié)果仍為絕對(duì)值大的數(shù),一點(diǎn)兒也看不出絕對(duì)值小的數(shù)對(duì)結(jié)果的影響。相比之下,浮點(diǎn)乘法和浮點(diǎn)除法引起的偏差就比較小,它們能夠保持一個(gè)比較穩(wěn)定的運(yùn)算精度。另外,不管在定點(diǎn)系統(tǒng)中還是在浮點(diǎn)系統(tǒng)中,都要盡可能避免兩個(gè)數(shù)值相近的數(shù)過早相減,因?yàn)樗麄兌伎赡苁墙浦?,相減以后,差的有效數(shù)值大大減少,必然帶來更大的相對(duì)誤差。經(jīng)過后續(xù)運(yùn)算之后,結(jié)果可能離真實(shí)值相差甚遠(yuǎn)。再有,盡可能不要用絕對(duì)值小的數(shù)作分母,否則引起的誤差也是很大的。
44、要對(duì)軟件標(biāo)志位的使用進(jìn)行說明;對(duì)于全局定義的軟件標(biāo)志,它有惟一的定義;對(duì)于局
部定義的軟件標(biāo)志,必須注明其有效范圍。
45、軟件理論已經(jīng)證明:任何一個(gè)程序(除某些短小的子程序外)都存在錯(cuò)誤(缺陷),人們可以通過合理的測(cè)試來證明它仍然存在錯(cuò)誤,卻無法證明它已經(jīng)沒有錯(cuò)誤。軟件測(cè)試應(yīng)該把發(fā)現(xiàn)錯(cuò)誤作為目的,而不能把“程序調(diào)通”作為目的。
1.P0口能驅(qū)動(dòng)8個(gè)TTL電路意思: 8051單片機(jī)P0口驅(qū)動(dòng)8個(gè)TTL電路的意思,TTL電路輸入懸浮時(shí)相當(dāng)于輸入高電平,因此P0口輸出高電平驅(qū)動(dòng)TTL電路幾乎不需輸出電流。TTL電路輸入為低電平時(shí)最少要釋放1mA電流,因此P0口輸出低電平時(shí)吸收的電流大于8mA。TTL輸出高電平最大1.6mA,輸出低電平時(shí)吸收的最大電流 16mA。51輸出最好用低電平有效,推動(dòng)PNP管,因?yàn)?1復(fù)位后IO為高電平,如果用高電平有效推N管的話上電復(fù)位后會(huì)先讓外部電路動(dòng)做。
2.在51里,有一條指令沒有寫進(jìn)書本,機(jī)器碼為A5,執(zhí)行操作:將下一個(gè)字節(jié)跳過而不管它是單字節(jié)指令還是雙字節(jié)或三字節(jié)指令的一部分.如果反匯編工具不識(shí)別A5指令的話,你在A5以后的程序反匯編后就錯(cuò)亂無章.當(dāng)成個(gè)數(shù)據(jù),用db a5 即可
3.有些51系統(tǒng)容易復(fù)位,一般是電路設(shè)計(jì)上的問題。很多電路介紹的復(fù)位電路都是10u和8.2k,但是在實(shí)踐過程中我們發(fā)現(xiàn)該電路在電源不穩(wěn)時(shí)很容易復(fù)位,特別是附近有大干擾時(shí),如繼電器動(dòng)作等。我建議使用22u和1k的復(fù)位電路,有許多電路改為該數(shù)值后就工作穩(wěn)定了。當(dāng)然,最好的辦法還是使用專用復(fù)位電路或三極管電路,但是那樣要增加成本和體積。
4.電路中的濾波電容一定要注意加上,最好每個(gè)芯片都再加一個(gè)約0.1uf的電容,這樣對(duì)電路的穩(wěn)定性很有好處。如果使用了看門狗電路,就有可能是軟件問題,程序工作到某些環(huán)節(jié)時(shí)忘記了復(fù)位看門狗,結(jié)果計(jì)數(shù)滿了就復(fù)位了。
5.如果在中斷程序中改變了多字節(jié)類型的變量,那么中斷程序以外的程序中(主程序,子函數(shù)),讀寫前要關(guān)中斷,讀寫后再開中斷。舉一反三:
其他的數(shù)據(jù)類型也可能有這種影響。例如:長整型、浮點(diǎn)型。
上面的例子是中斷里寫,主程序中讀。相反主程序?qū)懀袛嗬镒x也可能出錯(cuò)。
6.教你一招,別說我損。。
寫一個(gè)測(cè)試代碼,反復(fù)向EEPROM中的某幾個(gè)不用的空位字節(jié)寫入0x55,直到把它干到壽命終結(jié)不能寫為止,如果按照10MS寫一個(gè)字節(jié)計(jì)算的話,大約只需要20分鐘就能干掉它。然后向這個(gè)芯片中燒入你的正常代碼,當(dāng)然了,這個(gè)代碼中應(yīng)該有一段上電檢測(cè)EEPROM這幾個(gè)字節(jié)的代碼,先嘗試向它寫入0Xaa,然后再讀出來看看是否寫入成功,如果沒寫入則再來兩次,如果始終不能寫入,這當(dāng)作檢查通過,如果就判斷為檢查失敗,這個(gè)時(shí)候代碼要裝著‘不知情’繼續(xù)執(zhí)行正確代碼,下面的‘破壞’行為應(yīng)該如何做就不要我講了把? 破壞行為要裝的掩蔽一點(diǎn),例如調(diào)一段代碼檫除FLASH的代碼,嘿嘿,那對(duì)方肯定以為CHIP質(zhì)量不好容易出現(xiàn)FLASH數(shù)據(jù)丟失,如果對(duì)方使用了AD什么的,可以偶爾人為讓它波動(dòng)大一點(diǎn),這樣對(duì)方一般只會(huì)懷疑PCB和硬件電路弄的不好,而不會(huì)想到是代碼動(dòng)手腳了,長久以后他的用戶肯定也會(huì)認(rèn)為他們的產(chǎn)品質(zhì)量不好,你這個(gè)時(shí)候就可以向他的客戶推廣你的產(chǎn)品了。。
上電寫EEPROM的次數(shù)要在你自己的產(chǎn)品質(zhì)量承諾的壽命時(shí)間之內(nèi),否則你自己的產(chǎn)品也
可能增加維修。。
這個(gè)方法特別適合在外接單掙錢的工程師,你可能給了對(duì)方幾個(gè)CHIP做測(cè)試,對(duì)方測(cè)試通過偏說不行,就是不給你余款,然后把CHIP拿去CRACK,妄想省掉這個(gè)錢,NND,讓他們見鬼去把,俺這招已經(jīng)對(duì)付了不少不良分子。。
7.AD鍵盤
8.防解密高招
高招, 解密
使用一些帶內(nèi)部晶振和內(nèi)部EEPROM的單片機(jī),如PIC16F913和ATMEGA8等,帶內(nèi)部晶振的單片機(jī)有一個(gè)寄存器OSCTUNE(或OSCCAL),這個(gè)是芯片廠家用來校準(zhǔn)內(nèi)部晶振的,范圍從0-31,出廠時(shí)同型號(hào)的單片機(jī)這個(gè)寄存器的值是不一樣.我們可以利用一些隱藏功能,將OSCTUNE寄存器的值存入內(nèi)部的EEPROM中,開機(jī)時(shí)讀取EEPROM的值,再與OSCTUNE的值相比較, 若二者相同系統(tǒng)正常工作,若不
相同則不正常工作.解密者將解密的程序燒寫進(jìn)單片機(jī)中后,會(huì)發(fā)大部分的芯片不能正常工作,因?yàn)樗麄儾恢肋@個(gè)隱藏的功能.舉例說明: 芯片為PIC16F913,這個(gè)廠品有4個(gè)按鍵(KEY0、KEY1、KEY2、KEY3),內(nèi)部我們可以設(shè)定這樣子一個(gè)隱藏的功能,如果KEY0與KEY1同時(shí)按下3秒鐘以上,會(huì)將OSCTUNE寄存器存入單片機(jī)的EEPROM中。
開機(jī)復(fù)位后,讀取EEPROM中的數(shù)據(jù),與OSCTUNE寄存器相比較,若二者相同系統(tǒng)正常工作,若不相同則不正常工作。以上有三個(gè)重點(diǎn):
1、對(duì)于OSCTUNE寄存器不要進(jìn)行寫的操作,只進(jìn)行讀的操作,因?yàn)閷懥艘淮我院?,就一直是你寫的這個(gè)數(shù)據(jù)的。
2、剛才介紹的KEY0、KEY1同時(shí)按下3秒鐘這個(gè)功能,可不能讓解密者(包括產(chǎn)品的用戶)知道,當(dāng)然大家可以用別的隱藏的功能。
3、單片機(jī)中的OSCTUNE寄存器(或OSCCAL)的值,同一種型號(hào)的單片機(jī)不是每一個(gè)都是一樣的,有32個(gè)數(shù)據(jù),也就是說32個(gè)芯片中有一個(gè)是與解密的單片機(jī)是一樣的。這樣子造成的后果是:解密者解密了你的程序以后,卻發(fā)現(xiàn)有些單片機(jī)可以正常工作,可有些單片機(jī)不能正常工作,可以說是大部分的單片機(jī)不能正常工作。
不過需要注意一下:要是遇到強(qiáng)干擾把EEPROM中的數(shù)據(jù)改變了看客戶怎么收拾你!
9.PIC16F887A中,要求SLEEP指令后的下一條指令為NOP;不知51和AVR的芯片是否需要注意這一點(diǎn)。經(jīng)查,AVR的datasheet無此要求,可能是其喚醒時(shí),存在啟動(dòng)延時(shí)。
10.中斷隨時(shí)隨刻都有可能產(chǎn)生,故編寫程序時(shí),需要時(shí)刻注意中斷的影響。
11.注意以下語句在某些編譯器下,結(jié)果可能出錯(cuò):
unsigned char a,b;
unsigned int sum;
a=0x80;
b=0x80;
sum=a+b;
12.編程序最重要是好維護(hù)。幾個(gè)執(zhí)行時(shí)間和程序的可讀性比,和開發(fā)時(shí)間比,我認(rèn)為是不用考慮的。為了幾個(gè)機(jī)器周期而把程序搞得很復(fù)雜,是非常愚蠢的行為??墒呛芏嗳硕鄻反瞬黄0???傮w系統(tǒng)的算法是要考慮優(yōu)化的問題的,這點(diǎn)我是贊同的。天天在技術(shù)上對(duì)著幾行程序去優(yōu)化,而導(dǎo)致開發(fā)速度減慢,是非常愚蠢的行為。
13.串口通信協(xié)議:引導(dǎo)碼/識(shí)別碼+長度+命令字+data+校驗(yàn)
通過引導(dǎo)碼/識(shí)別碼、長度、校驗(yàn)三步檢測(cè) 每當(dāng)出錯(cuò)則丟棄當(dāng)前數(shù)據(jù)并還原接收狀態(tài)和空間…………
14.當(dāng)準(zhǔn)備調(diào)試一塊板的時(shí)候,一定要先認(rèn)真的做好目視檢查,檢查在焊接的過程中是否有可見的短路和管腳搭錫等故障,檢查是否有元器件型號(hào)放置錯(cuò)誤,第
一腳放置錯(cuò)誤,漏裝配等問題,然后用萬用表測(cè)量各個(gè)電源到地的電阻,以檢查是否有短路,這個(gè)好習(xí)慣可以避免貿(mào)然上電后損壞單板。調(diào)試的過程中要有平和的心態(tài),遇見問題是非常正常的,要做的就是多做比較和分析,逐步的排除可能的原因,要堅(jiān)信“凡事都是有辦法解決的”和“問題出現(xiàn)一定有它的原因”,這樣最后一定能調(diào)試成功。
做一個(gè)硬件設(shè)計(jì)人員要鍛煉出良好的溝通能力,面對(duì)壓力的調(diào)節(jié)能力,同一時(shí)間處理多個(gè)事務(wù)的協(xié)調(diào)和決斷能力和良好平和的心態(tài),還有細(xì)心和認(rèn)真等等。
對(duì)初學(xué)者來說,在新的領(lǐng)域面前一窮二白,要學(xué)的東西太多太多,一味機(jī)械地試圖學(xué)習(xí)這些“高深的語法”和“看不懂的技巧”,胡亂模仿些別人的“優(yōu)秀風(fēng)格”,不能說完全無所得,只能說會(huì)學(xué)得很累,而且往往事倍功半---久而久之,信心喪失殆盡,半途而廢。學(xué)習(xí)編程,做自己便好。學(xué)習(xí)眼前能夠看懂的內(nèi)容,多寫自己會(huì)寫的程序。對(duì)于已經(jīng)學(xué)到的東西,仔細(xì)地體會(huì)、思考,發(fā)掘其中的發(fā)展;學(xué)會(huì)用一種研究的心態(tài)去考察你的每一個(gè)疑問;不可以輕易地人云亦云,在網(wǎng)上看了別人無責(zé)任的經(jīng)驗(yàn),甚至是寫錯(cuò)了,丟在一邊,懶得改的東西之后,就放棄了自己的探索;要學(xué)會(huì)堅(jiān)持自我,遇到別人先進(jìn)的做法,在自己還沒有體會(huì)到自己當(dāng)前這種做法的劣勢(shì)之前,不要輕易盲從;同樣,使用了先進(jìn)的方法,在沒有同時(shí)理解這種做法的優(yōu)點(diǎn)和缺點(diǎn)之前,請(qǐng)不要輕易地感嘆“這種方法好,跟帖頂一下!”“大師高手寧有種乎?”堅(jiān)持“體會(huì)到了才是學(xué)到了”的態(tài)度,最終形成自己的風(fēng)格,形成自己的技巧。
第四篇:多線程編程知識(shí)總結(jié)
多線程編程
一、問題的提出
1.1問題的引出
編寫一個(gè)耗時(shí)的單線程程序:
新建一個(gè)基于對(duì)話框的應(yīng)用程序SingleThread,在主對(duì)話框IDD_SINGLETHREAD_DIALOG添加一個(gè)按鈕,ID為IDC_SLEEP_SIX_SECOND,標(biāo)題為“延時(shí)6秒”,添加按鈕的響應(yīng)函數(shù),代碼如下:
void CSingleThreadDlg::OnSleepSixSecond(){ Sleep(6000);//延時(shí)6秒 } 編譯并運(yùn)行應(yīng)用程序,單擊“延時(shí)6秒”按鈕,你就會(huì)發(fā)現(xiàn)在這6秒期間程序就象“死機(jī)”一樣,不在響應(yīng)其它消息。為了更好地處理這種耗時(shí)的操作,我們有必要學(xué)習(xí)——多線程編程。
1.2多線程概述
進(jìn)程和線程都是操作系統(tǒng)的概念。進(jìn)程是應(yīng)用程序的執(zhí)行實(shí)例,每個(gè)進(jìn)程是由私有的虛擬地址空間、代碼、數(shù)據(jù)和其它各種系統(tǒng)資源組成,進(jìn)程在運(yùn)行過程中創(chuàng)建的資源隨著進(jìn)程的終止而被銷毀,所使用的系統(tǒng)資源在進(jìn)程終止時(shí)被釋放或關(guān)閉。
線程是進(jìn)程內(nèi)部的一個(gè)執(zhí)行單元。系統(tǒng)創(chuàng)建好進(jìn)程后,實(shí)際上就啟動(dòng)執(zhí)行了該進(jìn)程的主執(zhí)行線程,主執(zhí)行線程以函數(shù)地址形式,比如說main或WinMain函數(shù),將程序的啟動(dòng)點(diǎn)提供給Windows系統(tǒng)。主執(zhí)行線程終止了,進(jìn)程也就隨之終止。
每一個(gè)進(jìn)程至少有一個(gè)主執(zhí)行線程,它無需由用戶去主動(dòng)創(chuàng)建,是由系統(tǒng)自動(dòng)創(chuàng)建的。用戶根據(jù)需要在應(yīng)用程序中創(chuàng)建其它線程,多個(gè)線程并發(fā)地運(yùn)行于同一個(gè)進(jìn)程中。一個(gè)進(jìn)程中的所有線程都在該進(jìn)程的虛擬地址空間中,共同使用這些虛擬地址空間、全局變量和系統(tǒng)資源,所以線程間的通訊非常方便,多線程技術(shù)的應(yīng)用也較為廣泛。
多線程可以實(shí)現(xiàn)并行處理,避免了某項(xiàng)任務(wù)長時(shí)間占用CPU時(shí)間。要說明的一點(diǎn)是,對(duì)于單處理器(CPU)的,為了運(yùn)行所有這些線程,操作系統(tǒng)為每個(gè)獨(dú)立線程安排一些CPU時(shí)間,操作系統(tǒng)以輪換方式向線程提供時(shí)間片,這就給人一種假象,好象這些線程都在同時(shí)運(yùn)行。由此可見,如果兩個(gè)非?;钴S的線程為了搶奪對(duì)CPU的控制權(quán),在線程切換時(shí)會(huì)消耗很多的CPU資源,反而會(huì)降低系統(tǒng)的性能。這一點(diǎn)在多線程編程時(shí)應(yīng)該注意。
Win32 SDK函數(shù)支持進(jìn)行多線程的程序設(shè)計(jì),并提供了操作系統(tǒng)原理中的各種同步、互斥和臨界區(qū)等操作。Visual C++中,使用MFC類庫也實(shí)現(xiàn)了多線程的程序設(shè)計(jì),使得多線程編程更加方便。1.3 Win32 API對(duì)多線程編程的支持
Win32 提供了一系列的API函數(shù)來完成線程的創(chuàng)建、掛起、恢復(fù)、終結(jié)以及通信等工作。下面將選取其中的一些重要函數(shù)進(jìn)行說明。
1、HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes,DWORD dwStackSize,LPTHREAD_START_ROUTINE lpStartAddress,LPVOID lpParameter,DWORD dwCreationFlags,LPDWORD lpThreadId);該函數(shù)在其調(diào)用進(jìn)程的進(jìn)程空間里創(chuàng)建一個(gè)新的線程,并返回已建線程的句柄,其中各參數(shù)說明如下:
lpThreadAttributes:指向一個(gè) SECURITY_ATTRIBUTES 結(jié)構(gòu)的指針,該結(jié)構(gòu)決定了線程的安全屬性,一般置為 NULL;
dwStackSize:指定了線程的堆棧深度,一般都設(shè)置為0;
lpStartAddress:表示新線程開始執(zhí)行時(shí)代碼所在函數(shù)的地址,即線程的起始地址。一般情況為(LPTHREAD_START_ROUTINE)ThreadFunc,ThreadFunc 是線程函數(shù)名;
lpParameter:指定了線程執(zhí)行時(shí)傳送給線程的32位參數(shù),即線程函數(shù)的參數(shù);
dwCreationFlags:控制線程創(chuàng)建的附加標(biāo)志,可以取兩種值。如果該參數(shù)為0,線程在被創(chuàng)建后就會(huì)立即開始執(zhí)行;如果該參數(shù)為CREATE_SUSPENDED,則系統(tǒng)產(chǎn)生線程后,該線程處于掛起狀態(tài),并不馬上執(zhí)行,直至函數(shù)ResumeThread被調(diào)用;
lpThreadId:該參數(shù)返回所創(chuàng)建線程的ID;
如果創(chuàng)建成功則返回線程的句柄,否則返回NULL。
2、DWORD SuspendThread(HANDLE hThread);該函數(shù)用于掛起指定的線程,如果函數(shù)執(zhí)行成功,則線程的執(zhí)行被終止。
3、DWORD ResumeThread(HANDLE hThread);該函數(shù)用于結(jié)束線程的掛起狀態(tài),執(zhí)行線程。
4、VOID ExitThread(DWORD dwExitCode);該函數(shù)用于線程終結(jié)自身的執(zhí)行,主要在線程的執(zhí)行函數(shù)中被調(diào)用。其中參數(shù)dwExitCode用來設(shè)置線程的退出碼。
5、BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
一般情況下,線程運(yùn)行結(jié)束之后,線程函數(shù)正常返回,但是應(yīng)用程序可以調(diào)用TerminateThread強(qiáng)行終止某一線程的執(zhí)行。各參數(shù)含義如下: hThread:將被終結(jié)的線程的句柄;
dwExitCode:用于指定線程的退出碼。
使用TerminateThread()終止某個(gè)線程的執(zhí)行是不安全的,可能會(huì)引起系統(tǒng)不穩(wěn)定;雖然該函數(shù)立即終止線程的執(zhí)行,但并不釋放線程所占用的資源。因此,一般不建議使用該函數(shù)。
6、BOOL PostThreadMessage(DWORD idThread,UINT Msg,WPARAM wParam,LPARAM lParam);該函數(shù)將一條消息放入到指定線程的消息隊(duì)列中,并且不等到消息被該線程處理時(shí)便返回。idThread:將接收消息的線程的ID;
Msg:指定用來發(fā)送的消息;
wParam:同消息有關(guān)的字參數(shù);
lParam:同消息有關(guān)的長參數(shù);
調(diào)用該函數(shù)時(shí),如果即將接收消息的線程沒有創(chuàng)建消息循環(huán),則該函數(shù)執(zhí)行失敗。
1.4.Win32 API多線程編程例程
例程1 [MultiThread1] 一個(gè)簡單的線程。注意事項(xiàng):
? Volatile:關(guān)鍵字:
volatile是要求C++編譯器不要自作聰明的把變量緩沖在寄存器里.因?yàn)樵撟兞靠赡軙?huì)被意外的修改。(多個(gè)線程或其他原因)
如從串口讀數(shù)據(jù)的場(chǎng)合,把變量緩沖在寄存器里,下次去讀寄存器就沒有意義了.因?yàn)榇诘臄?shù)據(jù)可能隨時(shí)會(huì)改變的.加鎖訪問用于多個(gè)線程的場(chǎng)合.在進(jìn)入臨界區(qū)時(shí)是肯定要加鎖的.volatile也加上,以保證從內(nèi)存中讀取變量的值.? 終止線程:
Windows終止線程運(yùn)行的四種方法 終止線程運(yùn)行
若要終止線程的運(yùn)行,可以使用下面的方法:
? 線程函數(shù)返回(最好使用這種方法)。
? 通過調(diào)用 ExitThread 函數(shù),線程將自行撤消(最好不要使用這種方法)。
? 同一個(gè)進(jìn)程或另一個(gè)進(jìn)程中的線程調(diào)用 TerminateThread 函數(shù)(應(yīng)該避免使用這種方法)。
? 包含線程的進(jìn)程終止運(yùn)行(應(yīng)該避免使用這種方法)。
下面將介紹終止線程運(yùn)行的方法,并且說明線程終止運(yùn)行時(shí)會(huì)出現(xiàn)什么情況。
? 線程函數(shù)返回
始終都應(yīng)該將線程設(shè)計(jì)成這樣的形式,即當(dāng)想要線程終止運(yùn)行時(shí),它們就能夠返回。這是確保所有線程資源被正確地清除的唯一辦法。
如果線程能夠返回,就可以確保下列事項(xiàng)的實(shí)現(xiàn):
? 在線程函數(shù)中創(chuàng)建的所有 C++ 對(duì)象均將通過它們的撤消函數(shù)正確地撤消。
? 操作系統(tǒng)將正確地釋放線程堆棧使用的內(nèi)存。
? 系統(tǒng)將線程的退出代碼(在線程的內(nèi)核對(duì)象中維護(hù))設(shè)置為線程函數(shù)的返回值。
? 系統(tǒng)將遞減線程內(nèi)核對(duì)象的使用計(jì)數(shù)。? 使用 ExitThread 函數(shù)
可以讓線程調(diào)用 ExitThread 函數(shù),以便強(qiáng)制線程終止運(yùn)行:
VOID ExitThread(DWORD dwExitCode);
該函數(shù)將終止線程的運(yùn)行,并導(dǎo)致操作系統(tǒng)清除該線程使用的所有操作系統(tǒng)資源。但是,C++ 資源(如 C++ 類對(duì)象)將不被撤消。由于這個(gè)原因,最好從線程函數(shù)返回,而不是通過調(diào)用 ExitThread 來返回。
當(dāng)然,可以使用 ExitThread 的 dwExitThread 參數(shù)告訴系統(tǒng)將線程的退出代碼設(shè)置為什么。ExitThread 函數(shù)并不返回任何值,因?yàn)榫€程已經(jīng)終止運(yùn)行,不能執(zhí)行更多的代碼。? 使用 TerminateThread 函數(shù)
調(diào)用 TerminateThread 函數(shù)也能夠終止線程的運(yùn)行:
BOOL TerminateThread(HANDLE hThread, DWORD dwExitCode);
與 ExitThread 不同,ExitThread 總是撤消調(diào)用的線程,而 TerminateThread 能夠撤消任何線程。hThread 參數(shù)用于標(biāo)識(shí)被終止運(yùn)行的線程的句柄。當(dāng)線程終止運(yùn)行時(shí),它的退出代碼成為你作為 dwExitCode 參數(shù)傳遞的值。同時(shí),線程的內(nèi)核對(duì)象的使用計(jì)數(shù)也被遞減。
注意 TerminateThread 函數(shù)是異步運(yùn)行的函數(shù),也就是說,它告訴系統(tǒng)你想要線程終止運(yùn)行,但是,當(dāng)函數(shù)返回時(shí),不能保證線程被撤消。如果需要確切地知道該線程已經(jīng)終止運(yùn)行,必須調(diào)用 WaitForSingleObject 或者類似的函數(shù),傳遞線程的句柄。
設(shè)計(jì)良好的應(yīng)用程序從來不使用這個(gè)函數(shù),因?yàn)楸唤K止運(yùn)行的線程收不到它被撤消的通知。線程不能正確地清除,并且不能防止自己被撤消。
注意 當(dāng)使用返回或調(diào)用 ExitThread 的方法撤消線程時(shí),該線程的內(nèi)存堆棧也被撤消。但是,如果使用 TerminateThread,那么在擁有線程的進(jìn)程終止運(yùn)行之前,系統(tǒng)不撤消該線程的堆棧。Microsoft故意用這種方法來實(shí)現(xiàn) TerminateThread。如果其他仍然正在執(zhí)行的線程要引用強(qiáng)制撤消的線程堆棧上的值,那么其他的線程就會(huì)出現(xiàn)訪問違規(guī)的問題。如果將已經(jīng)撤消的線程的堆棧留在內(nèi)存中,那么其他線程就可以繼續(xù)很好地運(yùn)行。
此外,當(dāng)線程終止運(yùn)行時(shí),DLL 通常接收通知。如果使用 TerminateThread 強(qiáng)迫線程終止,DLL 就不接收通知,這能阻止適當(dāng)?shù)那宄谶M(jìn)程終止運(yùn)行時(shí)撤消線程。當(dāng)線程終止運(yùn)行時(shí),會(huì)發(fā)生下列操作:
? 線程擁有的所有用戶對(duì)象均被釋放。在 Windows 中,大多數(shù)對(duì)象是由包含創(chuàng)建這些對(duì)象的線程的進(jìn)程擁有的。但是一個(gè)線程擁有兩個(gè)用戶對(duì)象,即窗口和掛鉤。當(dāng)線程終止運(yùn)行時(shí),系統(tǒng)會(huì)自動(dòng)撤消任何窗口,并且卸載線程創(chuàng)建的或安裝的任何掛鉤。其他對(duì)象只有在擁有線程的進(jìn)程終止運(yùn)行時(shí)才被撤消。
? 線程的退出代碼從 STILL_ACTIVE 改為傳遞給 ExitThread 或 TerminateThread 的代碼。
? 線程內(nèi)核對(duì)象的狀態(tài)變?yōu)橐淹ㄖ?/p>
? 如果線程是進(jìn)程中最后一個(gè)活動(dòng)線程,系統(tǒng)也將進(jìn)程視為已經(jīng)終止運(yùn)行。
? 線程內(nèi)核對(duì)象的使用計(jì)數(shù)遞減 1。
當(dāng)一個(gè)線程終止運(yùn)行時(shí),在與它相關(guān)聯(lián)的線程內(nèi)核對(duì)象的所有未結(jié)束的引用關(guān)閉之前,該內(nèi)核對(duì)象不會(huì)自動(dòng)被釋放。
一旦線程不再運(yùn)行,系統(tǒng)中就沒有別的線程能夠處理該線程的句柄。然而別的線程可以調(diào)用 GetExitcodeThread 來檢查由 hThread 標(biāo)識(shí)的線程是否已經(jīng)終止運(yùn)行。如果它已經(jīng)終止運(yùn)行,則確定它的退出代碼:
BOOL GetExitCodeThread(HANDLE hThread, PDOWRD pdwExitCode);退出代碼的值在 pdwExitCode 指向的 DWORD 中返回。如果調(diào)用 GetExitCodeThread 時(shí)線程尚未終止運(yùn)行,該函數(shù)就用 STILL_ACTIVE 標(biāo)識(shí)符(定義為 0x103)填入 DWORD。如果該函數(shù)運(yùn)行成功,便返回 TRUE。
? 線程的定義:
例程2[MultiThread2] 傳送一個(gè)一個(gè)整型的參數(shù)到一個(gè)線程中,以及如何等待一個(gè)線程完成處理。
DWORD WaitForSingleObject(HANDLE hHandle,DWORD dwMilliseconds);
hHandle:為要監(jiān)視的對(duì)象(一般為同步對(duì)象,也可以是線程)的句柄;
dwMilliseconds:為hHandle對(duì)象所設(shè)置的超時(shí)值,單位為毫秒;
當(dāng)在某一線程中調(diào)用該函數(shù)時(shí),線程暫時(shí)掛起,系統(tǒng)監(jiān)視hHandle所指向的對(duì)象的狀態(tài)。如果在掛起的dwMilliseconds毫秒內(nèi),線程所等待的對(duì)象變?yōu)橛行盘?hào)狀態(tài),則該函數(shù)立即返回;如果超時(shí)時(shí)間已經(jīng)到達(dá)dwMilliseconds毫秒,但hHandle所指向的對(duì)象還沒有變成有信號(hào)狀態(tài),函數(shù)照樣返回。參數(shù)dwMilliseconds有兩個(gè)具有特殊意義的值:0和INFINITE。若為0,則該函數(shù)立即返回;若為INFINITE,則線程一直被掛起,直到hHandle所指向的對(duì)象變?yōu)橛行盘?hào)狀態(tài)時(shí)為止。
例程3[MultiThread3] 傳送一個(gè)結(jié)構(gòu)體給一個(gè)線程函數(shù),可以通過傳送一個(gè)指向結(jié)構(gòu)體的指針參數(shù)來完成。補(bǔ)充一點(diǎn):如果你在void CMultiThread3Dlg::OnStart()函數(shù)中添加/* */語句,編譯運(yùn)行你就會(huì)發(fā)現(xiàn)進(jìn)度條不進(jìn)行刷新,主線程也停止了反應(yīng)。什么原因呢?這是因?yàn)閃aitForSingleObject函數(shù)等待子線程(ThreadFunc)結(jié)束時(shí),導(dǎo)致了線程死鎖。因?yàn)閃aitForSingleObject函數(shù)會(huì)將主線程掛起(任何消息都得不到處理),而子線程ThreadFunc正在設(shè)置進(jìn)度條,一直在等待主線程將刷新消息處理完畢返回才會(huì)檢測(cè)通知事件。這樣兩個(gè)線程都在互相等待,死鎖發(fā)生了,編程時(shí)應(yīng)注意避免。
例程4[MultiThread4] 測(cè)試在Windows下最多可創(chuàng)建線程的數(shù)目。
二、MFC中的多線程開發(fā)
2.1 MFC對(duì)多線程編程的支持
MFC中有兩類線程,分別稱之為工作者線程和用戶界面線程。二者的主要區(qū)別在于工作者線程沒有消息循環(huán),而用戶界面線程有自己的消息隊(duì)列和消息循環(huán)。
工作者線程沒有消息機(jī)制,通常用來執(zhí)行后臺(tái)計(jì)算和維護(hù)任務(wù),如冗長的計(jì)算過程,打印機(jī)的后臺(tái)打印等。用戶界面線程一般用于處理獨(dú)立于其他線程執(zhí)行之外的用戶輸入,響應(yīng)用戶及系統(tǒng)所產(chǎn)生的事件和消息等。但對(duì)于Win32的API編程而言,這兩種線程是沒有區(qū)別的,它們都只需線程的啟動(dòng)地址即可啟動(dòng)線程來執(zhí)行任務(wù)。
在MFC中,一般用全局函數(shù)AfxBeginThread()來創(chuàng)建并初始化一個(gè)線程的運(yùn)行,該函數(shù)有兩種重載形式,分別用于創(chuàng)建工作者線程和用戶界面線程。兩種重載函數(shù)原型和參數(shù)分別說明如下:
(1)CWinThread* AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
PfnThreadProc:指向工作者線程的執(zhí)行函數(shù)的指針,線程函數(shù)原型必須聲明如下: UINT ExecutingFunction(LPVOID pParam);請(qǐng)注意,ExecutingFunction()應(yīng)返回一個(gè)UINT類型的值,用以指明該函數(shù)結(jié)束的原因。一般情況下,返回0表明執(zhí)行成功。
pParam:傳遞給線程函數(shù)的一個(gè)32位參數(shù),執(zhí)行函數(shù)將用某種方式解釋該值。它可以是數(shù)值,或是指向一個(gè)結(jié)構(gòu)的指針,甚至可以被忽略;
nPriority:線程的優(yōu)先級(jí)。如果為0,則線程與其父線程具有相同的優(yōu)先級(jí);
nStackSize:線程為自己分配堆棧的大小,其單位為字節(jié)。如果nStackSize被設(shè)為0,則線程的堆棧被設(shè)置成與父線程堆棧相同大小; dwCreateFlags:如果為0,則線程在創(chuàng)建后立刻開始執(zhí)行。如果為CREATE_SUSPEND,則線程在創(chuàng)建后立刻被掛起;
lpSecurityAttrs:線程的安全屬性指針,一般為NULL;
(2)CWinThread* AfxBeginThread(CRuntimeClass* pThreadClass,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
pThreadClass 是指向 CWinThread 的一個(gè)導(dǎo)出類的運(yùn)行時(shí)類對(duì)象的指針,該導(dǎo)出類定義了被創(chuàng)建的用戶界面線程的啟動(dòng)、退出等;其它參數(shù)的意義同形式1。使用函數(shù)的這個(gè)原型生成的線程也有消息機(jī)制,在以后的例子中我們將發(fā)現(xiàn)同主線程的機(jī)制幾乎一樣。下面對(duì)CWinThread類的數(shù)據(jù)成員及常用函數(shù)進(jìn)行簡要說明。
? ? ? m_hThread:當(dāng)前線程的句柄;
m_nThreadID:當(dāng)前線程的ID;
m_pMainWnd:指向應(yīng)用程序主窗口的指針
virtual BOOL CWinThread::InitInstance();重載該函數(shù)以控制用戶界面線程實(shí)例的初始化。初始化成功則返回非0值,否則返回0。用戶界面線程經(jīng)常重載該函數(shù),工作者線程一般不使用InitInstance()。
virtual int CWinThread::ExitInstance();在線程終結(jié)前重載該函數(shù)進(jìn)行一些必要的清理工作。該函數(shù)返回線程的退出碼,0表示執(zhí)行成功,非0值用來標(biāo)識(shí)各種錯(cuò)誤。同InitInstance()成員函數(shù)一樣,該函數(shù)也只適用于用戶界面線程。
2.2 MFC多線程編程實(shí)例
例程5 MultiThread5 為了與Win32 API對(duì)照,使用MFC 類庫編程實(shí)現(xiàn)例程3 MultiThread3。
例程6 MultiThread6[用戶界面線程] ? 創(chuàng)建用戶界面線程的步驟:
1.使用ClassWizard創(chuàng)建類CWinThread的派生類(以CUIThread類為例)class CUIThread : public CWinThread { DECLARE_DYNCREATE(CUIThread)protected: CUIThread();// protected constructor used by dynamic creation
// Attributes public: // Operations public:
// Overrides // ClassWizard generated virtual function overrides //{{AFX_VIRTUAL(CUIThread)public: virtual BOOL InitInstance();virtual int ExitInstance();//}}AFX_VIRTUAL // Implementation protected: virtual ~CUIThread();// Generated message map functions //{{AFX_MSG(CUIThread)
// NOTE-the ClassWizard will add and remove member functions here.//}}AFX_MSG
DECLARE_MESSAGE_MAP()};
2.重載函數(shù)InitInstance()和ExitInstance()。BOOL CUIThread::InitInstance(){ CFrameWnd* wnd=new CFrameWnd;wnd->Create(NULL,“UI Thread Window”);wnd->ShowWindow(SW_SHOW);wnd->UpdateWindow();m_pMainWnd=wnd;return TRUE;}
3.創(chuàng)建新的用戶界面線程 void CUIThreadDlg::OnButton1(){
}
請(qǐng)注意以下兩點(diǎn):
A、在UIThreadDlg.cpp的開頭加入語句: #include “UIThread.h” B、把UIThread.h中類CUIThread()的構(gòu)造函數(shù)的特性由 protected 改為 public。CUIThread* pThread=new CUIThread();pThread->CreateThread();
用戶界面線程的執(zhí)行次序與應(yīng)用程序主線程相同,首先調(diào)用用戶界面線程類的InitInstance()函數(shù),如果返回TRUE,繼續(xù)調(diào)用線程的Run()函數(shù),該函數(shù)的作用是運(yùn)行一個(gè)標(biāo)準(zhǔn)的消息循環(huán),并且當(dāng)收到WM_QUIT消息后中斷,在消息循環(huán)過程中,Run()函數(shù)檢測(cè)到線程空閑時(shí)(沒有消息),也將調(diào)用OnIdle()函數(shù),最后Run()函數(shù)返回,MFC調(diào)用ExitInstance()函數(shù)清理資源。
你可以創(chuàng)建一個(gè)沒有界面而有消息循環(huán)的線程,例如:你可以從CWinThread派生一個(gè)新類,在InitInstance函數(shù)中完成某項(xiàng)任務(wù)并返回FALSE,這表示僅執(zhí)行InitInstance函數(shù)中的任務(wù)而不執(zhí)行消息循環(huán),你可以通過這種方法,完成一個(gè)工作者線程的功能。
三、線程間通訊
3.1通訊方式
一般而言,應(yīng)用程序中的一個(gè)次要線程總是為主線程執(zhí)行特定的任務(wù),這樣,主線程和次要線程間必定有一個(gè)信息傳遞的渠道,也就是主線程和次要線程間要進(jìn)行通信。這種線程間的通信不但是難以避免的,而且在多線程編程中也是復(fù)雜和頻繁的,下面將進(jìn)行說明。
3.1.1使用全局變量進(jìn)行通信
由于屬于同一個(gè)進(jìn)程的各個(gè)線程共享操作系統(tǒng)分配該進(jìn)程的資源,故解決線程間通信最簡單的一種方法是使用全局變量。對(duì)于標(biāo)準(zhǔn)類型的全局變量,建議使用volatile 修飾符,它告訴編譯器無需對(duì)該變量作任何的優(yōu)化,即無需將它放到一個(gè)寄存器中,并且該值可被外部改變。如果線程間所需傳遞的信息較復(fù)雜,可以定義一個(gè)結(jié)構(gòu),通過傳遞指向該結(jié)構(gòu)的指針進(jìn)行傳遞信息。
3.1.2使用自定義消息
可以在一個(gè)線程的執(zhí)行函數(shù)中向另一個(gè)線程發(fā)送自定義的消息來達(dá)到通信的目的。一個(gè)線程向另外一個(gè)線程發(fā)送消息是通過操作系統(tǒng)實(shí)現(xiàn)的。利用Windows操作系統(tǒng)的消息驅(qū)動(dòng)機(jī)制,當(dāng)一個(gè)線程發(fā)出一條消息時(shí),操作系統(tǒng)首先接收到該消息,然后把該消息轉(zhuǎn)發(fā)給目標(biāo)線程,接收消息的線程必須已經(jīng)建立了消息循環(huán)。
3.2例程
例程GlobalObjectTest 該例程演示了如何利用全局變量進(jìn)行通信
例程7[MultiThread7] 該例程演示了如何使用自定義消息進(jìn)行線程間通信。首先,主線程向CCalculateThread線程發(fā)送消息WM_CALCULATE,CCalculateThread線程收到消息后進(jìn)行計(jì)算,再向主線程發(fā)送WM_DISPLAY消息,主線程收到該消息后顯示計(jì)算結(jié)果。步驟:
四、線程的同步
4.1基本概念
雖然多線程能給我們帶來好處,但是也有不少問題需要解決。例如,對(duì)于像磁盤驅(qū)動(dòng)器這樣獨(dú)占性系統(tǒng)資源,由于線程可以執(zhí)行進(jìn)程的任何代碼段,且線程的運(yùn)行是由系統(tǒng)調(diào)度自動(dòng)完成的,具有一定的不確定性,因此就有可能出現(xiàn)兩個(gè)線程同時(shí)對(duì)磁盤驅(qū)動(dòng)器進(jìn)行操作,從而出現(xiàn)操作錯(cuò)誤;又例如,對(duì)于銀行系統(tǒng)的計(jì)算機(jī)來說,可能使用一個(gè)線程來更新其用戶數(shù)據(jù)庫,而用另外一個(gè)線程來讀取數(shù)據(jù)庫以響應(yīng)儲(chǔ)戶的需要,極有可能讀數(shù)據(jù)庫的線程讀取的是未完全更新的數(shù)據(jù)庫,因?yàn)榭赡茉谧x的時(shí)候只有一部分?jǐn)?shù)據(jù)被更新過。
使隸屬于同一進(jìn)程的各線程協(xié)調(diào)一致地工作稱為線程的同步。MFC提供了多種同步對(duì)象,下面只介紹最常用的四種:
臨界區(qū)(CCriticalSection)
事件(CEvent)
互斥量(CMutex)
信號(hào)量(CSemaphore)
通過這些類,可以比較容易地做到線程同步。
4.2使用 CCriticalSection 類
當(dāng)多個(gè)線程訪問一個(gè)獨(dú)占性共享資源時(shí),可以使用“臨界區(qū)”對(duì)象。任一時(shí)刻只有一個(gè)線程可以擁有臨界區(qū)對(duì)象,擁有臨界區(qū)的線程可以訪問被保護(hù)起來的資源或代碼段,其他希望進(jìn)入臨界區(qū)的線程將被掛起等待,直到擁有臨界區(qū)的線程放棄臨界區(qū)時(shí)為止,這樣就保證了不會(huì)在同一時(shí)刻出現(xiàn)多個(gè)線程訪問共享資源。
CCriticalSection類的用法非常簡單,步驟如下:
1.定義CCriticalSection類的一個(gè)全局對(duì)象(以使各個(gè)線程均能訪問),如CCriticalSection critical_section;
2.在訪問需要保護(hù)的資源或代碼之前,調(diào)用CCriticalSection類的成員Lock()獲得臨界區(qū)對(duì)象: critical_section.Lock();3.在線程中調(diào)用該函數(shù)來使線程獲得它所請(qǐng)求的臨界區(qū)。如果此時(shí)沒有其它線程占有臨界區(qū)對(duì)象,則調(diào)用Lock()的線程獲得臨界區(qū);否則,線程將被掛起,并放入到一個(gè)系統(tǒng)隊(duì)列中等待,直到當(dāng)前擁有臨界區(qū)的線程釋放了臨界區(qū)時(shí)為止。
4.訪問臨界區(qū)完畢后,使用CCriticalSection的成員函數(shù)Unlock()來釋放臨界區(qū):critical_section.Unlock();通俗講,就是線程A執(zhí)行到critical_section.Lock();語句時(shí),如果其它線程(B)正在執(zhí)行critical_section.Lock();語句后且critical_section.Unlock();語句前的語句時(shí),線程A就會(huì)等待,直到線程B執(zhí)行完critical_section.Unlock();語句,線程A才會(huì)繼續(xù)執(zhí)行。
例程8 MultiThread8 4.3使用 CEvent 類
CEvent 類提供了對(duì)事件的支持。事件是一個(gè)允許一個(gè)線程在某種情況發(fā)生時(shí),喚醒另外一個(gè)線程的同步對(duì)象。例如在某些網(wǎng)絡(luò)應(yīng)用程序中,一個(gè)線程(記為A)負(fù)責(zé)監(jiān)聽通訊端口,另外一個(gè)線程(記為B)負(fù)責(zé)更新用戶數(shù)據(jù)。通過使用CEvent 類,線程A可以通知線程B何時(shí)更新用戶數(shù)據(jù)。每一個(gè)CEvent 對(duì)象可以有兩種狀態(tài):有信號(hào)狀態(tài)和無信號(hào)狀態(tài)。線程監(jiān)視位于其中的CEvent 類對(duì)象的狀態(tài),并在相應(yīng)的時(shí)候采取相應(yīng)的操作。
在MFC中,CEvent 類對(duì)象有兩種類型:人工事件和自動(dòng)事件。一個(gè)自動(dòng)CEvent 對(duì)象在被至少一個(gè)線程釋放后會(huì)自動(dòng)返回到無信號(hào)狀態(tài);而人工事件對(duì)象獲得信號(hào)后,釋放可利用線程,但直到調(diào)用成員函數(shù)ReSetEvent()才將其設(shè)置為無信號(hào)狀態(tài)。在創(chuàng)建CEvent 類的對(duì)象時(shí),默認(rèn)創(chuàng)建的是自動(dòng)事件。CEvent 類的各成員函數(shù)的原型和參數(shù)說明如下:
1、CEvent(BOOL bInitiallyOwn=FALSE,BOOL bManualReset=FALSE,LPCTSTR lpszName=NULL,LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);bInitiallyOwn:指定事件對(duì)象初始化狀態(tài),TRUE為有信號(hào),F(xiàn)ALSE為無信號(hào);
bManualReset:指定要?jiǎng)?chuàng)建的事件是屬于人工事件還是自動(dòng)事件。TRUE為人工事件,F(xiàn)ALSE為自動(dòng)事件;
后兩個(gè)參數(shù)一般設(shè)為NULL,在此不作過多說明。
2、BOOL CEvent::SetEvent();
將 CEvent 類對(duì)象的狀態(tài)設(shè)置為有信號(hào)狀態(tài)。如果事件是人工事件,則 CEvent 類對(duì)象保持為有信號(hào)狀態(tài),直到調(diào)用成員函數(shù)ResetEvent()將 其重新設(shè)為無信號(hào)狀態(tài)時(shí)為止。如果CEvent 類對(duì)象為自動(dòng)事件,則在SetEvent()將事件設(shè)置為有信號(hào)狀態(tài)后,CEvent 類對(duì)象由系統(tǒng)自動(dòng)重置為無信號(hào)狀態(tài)。
如果該函數(shù)執(zhí)行成功,則返回非零值,否則返回零。
3、BOOL CEvent::ResetEvent();
該函數(shù)將事件的狀態(tài)設(shè)置為無信號(hào)狀態(tài),并保持該狀態(tài)直至SetEvent()被調(diào)用時(shí)為止。由于自動(dòng)事件是由系統(tǒng)自動(dòng)重置,故自動(dòng)事件不需要調(diào)用該函數(shù)。如果該函數(shù)執(zhí)行成功,返回非零值,否則返回零。一般通過調(diào)用WaitForSingleObject函數(shù)來監(jiān)視事件狀態(tài)。前面已經(jīng)介紹了該函數(shù)。由于語言描述的原因,CEvent 類的理解確實(shí)有些難度,只要通過下面例程,多看幾遍就可理解。例程9 MultiThread9 仔細(xì)分析這兩個(gè)線程函數(shù), 就會(huì)正確理解CEvent 類。線程WriteD執(zhí)行到 WaitForSingleObject(eventWriteD.m_hObject,INFINITE);處等待,直到事件eventWriteD為有信號(hào)該線程才往下執(zhí)行,因?yàn)閑ventWriteD對(duì)象是自動(dòng)事件,則當(dāng)WaitForSingleObject()返回時(shí),系統(tǒng)自動(dòng)把eventWriteD對(duì)象重置為無信號(hào)狀態(tài)。
4.4使用CMutex 類
互斥對(duì)象與臨界區(qū)對(duì)象很像.互斥對(duì)象與臨界區(qū)對(duì)象的不同在于:互斥對(duì)象可以在進(jìn)程間使用,而臨界區(qū)對(duì)象只能在同一進(jìn)程的各線程間使用。當(dāng)然,互斥對(duì)象也可以用于同一進(jìn)程的各個(gè)線程間,但是在這種情況下,使用臨界區(qū)會(huì)更節(jié)省系統(tǒng)資源,更有效率。
4.5使用CSemaphore 類
當(dāng)需要一個(gè)計(jì)數(shù)器來限制可以使用某個(gè)線程的數(shù)目時(shí),可以使用“信號(hào)量”對(duì)象。CSemaphore 類的對(duì)象保存了對(duì)當(dāng)前訪問某一指定資源的線程的計(jì)數(shù)值,該計(jì)數(shù)值是當(dāng)前還可以使用該資源的線程的數(shù)目。如果這個(gè)計(jì)數(shù)達(dá)到了零,則所有對(duì)這個(gè)CSemaphore 類對(duì)象所控制的資源的訪問嘗試都被放入到一個(gè)隊(duì)列中等待,直到超時(shí)或計(jì)數(shù)值不為零時(shí)為止。一個(gè)線程被釋放已訪問了被保護(hù)的資源時(shí),計(jì)數(shù)值減1;一個(gè)線程完成了對(duì)被控共享資源的訪問時(shí),計(jì)數(shù)值增1。這個(gè)被CSemaphore 類對(duì)象所控制的資源可以同時(shí)接受訪問的最大線程數(shù)在該對(duì)象的構(gòu)建函數(shù)中指定。
CSemaphore 類的構(gòu)造函數(shù)原型及參數(shù)說明如下:
CSemaphore(LONG lInitialCount=1,LONG lMaxCount=1,LPCTSTR pstrName=NULL,LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);lInitialCount:信號(hào)量對(duì)象的初始計(jì)數(shù)值,即可訪問線程數(shù)目的初始值;
lMaxCount:信號(hào)量對(duì)象計(jì)數(shù)值的最大值,該參數(shù)決定了同一時(shí)刻可訪問由信號(hào)量保護(hù)的資源的線程最大數(shù)目;
后兩個(gè)參數(shù)在同一進(jìn)程中使用一般為NULL,不作過多討論;
在用CSemaphore 類的構(gòu)造函數(shù)創(chuàng)建信號(hào)量對(duì)象時(shí)要同時(shí)指出允許的最大資源計(jì)數(shù)和當(dāng)前可用資源計(jì)數(shù)。一般是將當(dāng)前可用資源計(jì)數(shù)設(shè)置為最大資源計(jì)數(shù),每增加一個(gè)線程對(duì)共享資源的訪問,當(dāng)前可用資源計(jì)數(shù)就會(huì)減1,只要當(dāng)前可用資源計(jì)數(shù)是大于0的,就可以發(fā)出信號(hào)量信號(hào)。但是當(dāng)前可用計(jì)數(shù)減小到0時(shí),則說明當(dāng)前占用資源的線程數(shù)已經(jīng)達(dá)到了所允許的最大數(shù)目,不能再允許其它線程的進(jìn)入,此時(shí)的信號(hào)量信號(hào)將無法發(fā)出。線程在處理完共享資源后,應(yīng)在離開的同時(shí)通過ReleaseSemaphore()函數(shù)將當(dāng)前可用資源數(shù)加1。例程10 MultiThread10 為了文件中能夠正確使用同步類,在文件開頭添加: #include “afxmt.h” 定義信號(hào)量對(duì)象和一個(gè)字符數(shù)組,為了能夠在不同線程間使用,定義為全局變量:CSemaphore semaphoreWrite(2,2);//資源最多訪問線程2個(gè),當(dāng)前可訪問線程數(shù)2個(gè)
在信號(hào)量對(duì)象有信號(hào)的狀態(tài)下,線程執(zhí)行到WaitForSingleObject語句處繼續(xù)執(zhí)行,同時(shí)可用線程數(shù)減1;若線程執(zhí)行到WaitForSingleObject語句時(shí)信號(hào)量對(duì)象無信號(hào),線程就在這里等待,直到信號(hào)量對(duì)象有信號(hào)線程才往下執(zhí)行。
第五篇:C++編程知識(shí)總結(jié)
1.數(shù)組
1.1數(shù)組定義時(shí)的注意點(diǎn)
1在C++中不提供可變化大小的數(shù)組,○即數(shù)組定義中的常量表達(dá)式不能包含變量。(來源:C++書6.1.1)
int n;cin>>n;float t[n];上例在定義數(shù)組t時(shí),變量n沒有確定的值,即在程序執(zhí)行之前,無法知道數(shù)組t的元素個(gè)數(shù),所以這種聲明不被允許。但是可以用new動(dòng)態(tài)分配,如: int n;cin>>n;float *t;t=new float[n];
2在定義數(shù)組時(shí),可以不直接指定數(shù)組的大小,由C++編譯器根據(jù)初值表中元素的個(gè)數(shù)來自○動(dòng)確定數(shù)組元素的個(gè)數(shù)。例如: int z[]={0,1,2,3,4,5,6,7,8} 3C++語言規(guī)定只能對(duì)數(shù)組中的元素進(jìn)行賦值或引用,不能把整個(gè)數(shù)組作為一個(gè)整體進(jìn)行賦○值或引用。(2.3是一個(gè)實(shí)例)(來源:C++書4同類型的數(shù)組之間不能相互賦值 ○如int a[5],b[5];a=b;//錯(cuò)誤
strcpy(b,a);//正確
6.1.1)
1.2數(shù)組和指針的關(guān)系(來源:C++書8.2節(jié)8.2.1)
char s[5];在C++中說明了一個(gè)數(shù)組后,數(shù)組名可以作為一個(gè)指針來使用,因此s可作為一個(gè)指針使用(但它不同于指針,不能賦值運(yùn)算、算術(shù)運(yùn)算等)。
2.字符數(shù)組
2.1輸入字符數(shù)據(jù) char c;cin>>c;// cin不能將輸入的空格賦給字符型變量。
cin.get();//可獲得鍵盤上輸入的每一個(gè)字符,包括空格和回車鍵。
2.2字符數(shù)組的輸入/輸出(來源:C++書6.2.4)2.2.1逐個(gè)字符輸入 char c[10];for(int i=0;i<10;i++)cin>>c[i];2.2.2字符串輸入 方法1 char c[10];cin>>c;//即在輸入輸出時(shí)只給數(shù)組名
此法在輸入字符串時(shí),遇到空格和回車就認(rèn)為一個(gè)字符結(jié)束。方法2 cin.getline(字符數(shù)組名,允許輸入的最大字符個(gè)數(shù))此法可把輸入的一行作為一個(gè)字符串送到字符數(shù)組中。
2.3字符數(shù)組和字符指針的初始化 2.3.1字符數(shù)組初始化 char tx[5]=“";2.3.2字符指針初始化 char *ptx=new char[5];ptx[0]='