高級(jí)語言入門_第1頁(yè)
高級(jí)語言入門_第2頁(yè)
高級(jí)語言入門_第3頁(yè)
高級(jí)語言入門_第4頁(yè)
高級(jí)語言入門_第5頁(yè)
已閱讀5頁(yè),還剩135頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

傳智播客C語言入門教程(6)講師:尹成QQ:77025077博客/yincheng01微博/yincheng8848網(wǎng)址C語言C++語言傳智播客高薪就業(yè)傳智播客C語言入門教程(6)大綱1.窮人時(shí)代如何節(jié)約內(nèi)存——位運(yùn)算不僅僅局限于內(nèi)存——文件靈活卻難以理解——指針進(jìn)階更深入的理解——函數(shù)進(jìn)階生存期、作用域與可見域.初學(xué)者的疑難解答偉大的比爾蓋茨在DOS時(shí)代曾經(jīng)有過這樣的說法“640K的內(nèi)存對(duì)一般人來說已經(jīng)足夠”,可現(xiàn)在的電腦內(nèi)存動(dòng)輒就是1G、2G,且不評(píng)判上述那句話的正誤,但從日新月異的硬件發(fā)展來看,我們就能猜出二三十年前的程序員是如何在內(nèi)存使用上精打細(xì)算的,與之相比,現(xiàn)代程序員好像少了很多煩惱,內(nèi)存似乎永遠(yuǎn)用不完,但內(nèi)存再大,也要精打細(xì)算,從位運(yùn)算的角度看一下如何合理節(jié)約內(nèi)存的使用。從開始到現(xiàn)在,經(jīng)常出現(xiàn)的一個(gè)詞是內(nèi)存單元,即1B,我們說char型占1個(gè)內(nèi)存單元(1B),而short型占2個(gè)內(nèi)存單元(2B)。1B被當(dāng)成整體來看,但不要忘記有下面的等式成立:1

B

=

8

bits1個(gè)字節(jié)有8個(gè)位,每個(gè)位有0、1兩個(gè)取值,從這個(gè)角度上說,1個(gè)字節(jié)所能包含的意義似乎比預(yù)想的要大的多。舉例來說,房間里有8盞燈,為了控制每盞燈的亮滅,可以聲明8個(gè)Byte變量,變量為0代表燈滅,變量非0代表燈亮,這完全行得通,而且看起來很有效率。能否換種角度思考,能不能只有1個(gè)字節(jié)的8個(gè)位來控制8盞燈?該位為0代表燈滅,該位為1代表燈亮,答案是“可以”,比較兩種方法,發(fā)現(xiàn)使用位操作的方式有效節(jié)省了內(nèi)存,如所示:假設(shè)某個(gè)時(shí)刻,燈1和燈2亮,而其他燈都滅,此時(shí)控制字為

11000000,想變換下狀態(tài),讓燈1和燈3亮,其他燈滅,目標(biāo)控制字為10100000,只要賦值給該單元即可。問題又來了,如果原來不知道哪幾盞燈亮哪幾盞燈滅,現(xiàn)在還是想讓第3盞燈亮起來,賦值操作看來是行不通了,要如何做呢?在這個(gè)背景下,位運(yùn)算的概念被提出,即能否只改變控制第

3盞燈的那一位的狀態(tài)?本章的剩余章節(jié)將結(jié)合開燈關(guān)燈這一場(chǎng)景講述位運(yùn)算的內(nèi)容??傮w來說,C語言中的位運(yùn)算符有以下兩類:位邏輯運(yùn)算符:&(位“與”)、^(位“異或”)、|(位“或”)、~(位“取反”)。移位運(yùn)算符:<<(左移)、>>(右移)大家現(xiàn)在對(duì)邏輯運(yùn)算已經(jīng)不陌生了,位邏輯運(yùn)算的原理與普通邏輯運(yùn)算基本一致,不同在于普通的邏輯運(yùn)算以變量為單位,而位邏輯運(yùn)算以位(bit)為單位,先從最簡(jiǎn)單的位取反運(yùn)算說起。假設(shè)不知道哪幾盞燈亮著哪幾盞燈滅著,但想進(jìn)行一個(gè)操作:讓亮著的燈滅掉,讓滅著的燈亮起來,這時(shí)就要用到位取反操作。位取反的操作符為“~”,如果A為10101010,那么~A返回的結(jié)果為01010101,即每位都取反,0變成1,1變成0,需要注意的是,位取反運(yùn)算并不改變操作數(shù)的值。假設(shè)字節(jié)A控制著8盞燈的亮滅,那么下述操作可實(shí)現(xiàn)預(yù)想的功能:A=~A:重申,位取反運(yùn)算并不改變操作數(shù)的值,是賦值運(yùn)算改變了A的值。位與運(yùn)算的操作符為&,將對(duì)兩個(gè)操作數(shù)的每一位進(jìn)行與運(yùn)算,位“與”運(yùn)算的準(zhǔn)則如下:1

&

1=1 1

&

0=0 0

&

1=0 0

&

0=0不知道哪幾盞燈亮著哪幾盞燈滅著,想讓第3盞燈滅掉,使用位與就能實(shí)現(xiàn),想想看,要如何來做呢?只要在當(dāng)前狀態(tài)的基礎(chǔ)上位與“11011111”即可,和1位與并不會(huì)改變?cè)瓉砦坏臓顟B(tài),而和0位與,無論原來是0還是1,都會(huì)變成0,燈滅。位或運(yùn)算的操作符為|,將對(duì)兩個(gè)操作數(shù)的每一位進(jìn)行或運(yùn)算,位“或”運(yùn)算的準(zhǔn)則如下:1

|

1=1 1

|

0=1 0

|

1=1 0

|

0=0不知道哪幾盞燈亮著哪幾盞燈滅著,想讓第3盞燈亮起來,使用位或就能實(shí)現(xiàn),只要在當(dāng)前狀態(tài)的基礎(chǔ)上位或“00100000”即可,和0位或并不會(huì)改變?cè)瓉砦坏臓顟B(tài),而和1位或,無論原來是

0還是1,都會(huì)變成1,燈亮。位或運(yùn)算的操作符為^,將對(duì)兩個(gè)操作數(shù)的每一位進(jìn)行異或運(yùn)算。通俗地講,如果位“異或”運(yùn)算的兩個(gè)位相同(同為0或同為1),結(jié)果為0,若兩個(gè)位不同(一個(gè)為0,另一個(gè)為1),結(jié)果為1,對(duì)應(yīng)的準(zhǔn)則為:1

^

1=0 1

^

0=1 0

^

1=1 0

^

0=0位“反”、位“與”、位“或”和位“異或”運(yùn)算符不關(guān)心操作數(shù)的符號(hào),只是按操作數(shù)所在字節(jié)的0、1序列進(jìn)行比對(duì),符號(hào)位會(huì)被當(dāng)成普通的0或1進(jìn)行處理。/*使用printf要包含的頭文件*//*主函#include

<stdio.h>#include

<stdlib.h>void

main(void)數(shù)*/{short

czs1=1234;short

czs2=123;/*聲明操作數(shù)czs1,初始化*//*聲明操作數(shù)czs2,初始化*/short

ResAnd,ResOr,ResNot,Res;/*聲明幾個(gè)變量用以保存結(jié)果*/ResAnd=czs1&czs2;/*位與運(yùn)算*/ResOr=czs1|czs2;/*位或運(yùn)算*/ResNot=~czs1;/*位取反運(yùn)算*/Res=czs1^czs2;/*位異或運(yùn)算*/printf("位與:czs1&czs2是%d\n",ResAnd);/*輸出*/printf("位或:czs1|czs2是%d\n",ResOr);printf("位取反:~czs1是%d\n",ResNot);printf("位異或:czs1^czs2是%d\n",Res);}getchar();

/*等待,按任意鍵繼續(xù)*/顧名思義,移位運(yùn)算就是將某個(gè)量中的bits向左或向右移動(dòng),該量的值也會(huì)相應(yīng)發(fā)生變化,在開燈關(guān)燈這個(gè)場(chǎng)景中,移位運(yùn)算的一個(gè)重要應(yīng)用是實(shí)現(xiàn)流水燈效果,即按從1到8或從8到1的順序每次只亮一個(gè)燈。移位運(yùn)算表達(dá)式的基本形式為:/*左移*/A

<<

n;或A

>>

n;/*右移*/A稱為操作數(shù),其必須為數(shù)字型變量或數(shù)字型常量,此處的數(shù)字型包括整型、浮點(diǎn)型和char型,

A中存儲(chǔ)的0、1序列向左或右移動(dòng)n位,移動(dòng)后的值作為整個(gè)表達(dá)式的輸出,執(zhí)行移位運(yùn)算并不改變操作數(shù)A的值。/*使用printf要包含的頭文件*//*主函數(shù)*/#include

<stdio.h>#include

<stdlib.h>void

main(void){unsigned

short

czs=1889;

/*聲明操作數(shù)czs,初始化*/unsigned

short

ls=0,rs=0;/*聲明兩個(gè)結(jié)果變量,保存左移右移的結(jié)果*/rs=czs>>4;ls=czs<<4;/*左移操作*//*右移操作*/printf("19889左移4位結(jié)果是%d\n",ls);printf("19889右移4位結(jié)果是%d",rs);/*輸出ls*//*輸出rs*//*等待,按任意鍵繼續(xù)*/getchar();}盡管內(nèi)存資源已不如早期程序設(shè)計(jì)時(shí)那么緊張,但位運(yùn)算在很多方面有著獨(dú)到的應(yīng)用,位運(yùn)算主要分為位邏輯運(yùn)算和移位運(yùn)算兩大類,位邏輯運(yùn)算主要有位取反運(yùn)算、位或運(yùn)算、位與運(yùn)算和位異或運(yùn)算,使用時(shí)應(yīng)注意和普通變量的邏輯運(yùn)算區(qū)分。移位運(yùn)算分為向左移動(dòng)和向右移動(dòng)兩類,對(duì)無符號(hào)數(shù)或有符號(hào)正數(shù)來說,編譯器會(huì)自動(dòng)為空白位補(bǔ)0,對(duì)有符號(hào)負(fù)數(shù)來說,當(dāng)填充的空白位牽扯到符號(hào)位時(shí),編譯器會(huì)對(duì)符號(hào)位進(jìn)行特殊處理。輸出整數(shù)的補(bǔ)碼與原碼1輸入unsignedshort類型的數(shù)據(jù),在內(nèi)存里面改變二進(jìn)制,將上半部分全部變?yōu)?,下半部分不變,打印輸出2輸入unsigned

short類型的數(shù)據(jù),在內(nèi)存里面改變二進(jìn)制,將上半部分全部變?yōu)?,下半部分不變,打印輸出3求函數(shù)返回值,輸入x=9999;int

func(x){

int

countx=0;while(x){

countx

++;

x

=

x&(x-1);

}returncountx;}4計(jì)算機(jī)是人類的優(yōu)秀輔助工具,人依靠輸入輸出與其交互,在前面章節(jié)中,輸入輸出都是由

printf函數(shù)和scanf函數(shù)來完成,完成的也只是極其簡(jiǎn)單的任務(wù),所有的變量和數(shù)字什么的都是放在內(nèi)存中,一旦斷電,所有的數(shù)據(jù)都會(huì)丟失,有時(shí),希望能將結(jié)果保存起來,下次開機(jī)時(shí)再使用,這就要用到文件。首先解決一個(gè)問題,“什么是外部介質(zhì)”,外部介質(zhì)的概念是針對(duì)內(nèi)存來說的,首先想到的外部介質(zhì)是硬盤和光盤等,但外部介質(zhì)的概念比這寬泛得多,還包括一些輸入輸出設(shè)備,比如鍵盤、顯示器以及打印機(jī)等。計(jì)算機(jī)操作外部設(shè)備,包括驅(qū)動(dòng)程序,都是讀寫文件的模式交換信息的。文件分類按文件的邏輯結(jié)構(gòu):記錄文件:由具有一定結(jié)構(gòu)的記錄組成(定長(zhǎng)和不定長(zhǎng))流式文件:由一個(gè)個(gè)字符(字節(jié))數(shù)據(jù)順序組成按存儲(chǔ)介質(zhì):普通文件:存儲(chǔ)介質(zhì)文件(磁盤、磁帶等)設(shè)備文件:非存儲(chǔ)介質(zhì)(鍵盤、顯示器、打印機(jī)等)按數(shù)據(jù)的組織形式:文本文件:ASCII文件,每個(gè)字節(jié)存放一個(gè)字符的ASCII碼二進(jìn)制文件:數(shù)據(jù)按其在內(nèi)存中的存儲(chǔ)形式原樣存放文件:存儲(chǔ)在外部介質(zhì)上數(shù)據(jù)的集合,是操作系統(tǒng)數(shù)據(jù)管理的單位使用數(shù)據(jù)文件的目的1、數(shù)據(jù)文件的改動(dòng)不引起程序的改動(dòng)——程序與數(shù)據(jù)分離2、不同程序可以訪問同一數(shù)據(jù)文件中的數(shù)據(jù)——數(shù)據(jù)共享3、能保存程序運(yùn)行的中間數(shù)據(jù)或結(jié)果數(shù)據(jù)00110011每個(gè)文件都以文件名為標(biāo)識(shí),I/O設(shè)備的文件名是系統(tǒng)定義的,如:COM1或AUX——第一串行口,附加設(shè)備COM2——第二串行口,此外,還可能有COM3、COM4等CON——控制臺(tái)(console),鍵盤(輸入用)或顯示器(輸出用)LPT1或PRN——第一并行口或打印機(jī)LPT2——第二并行口,還可能有LPT3等NUL——空設(shè)備磁盤文件可以由用戶自己命名,但上述被系統(tǒng)(windows和dos下均是如此)保留的設(shè)備名字不能用作文件名,如不能把一個(gè)文件命名為CON(不帶擴(kuò)展名)或CON.TXT(不帶擴(kuò)展名)。流是一個(gè)動(dòng)態(tài)的概念,可以將一個(gè)字節(jié)形象地比喻成一滴水,字節(jié)在設(shè)備、文件和程序之間的傳輸就是流,類似于水在管道中的傳輸,可以看出,流是對(duì)輸入輸出源的一種抽象,也是對(duì)傳輸信息的一種抽象。通過對(duì)輸入輸出源的抽象,屏蔽了設(shè)備之間的差異,使程序員能以一種通用的方式進(jìn)行存儲(chǔ)操作,通過對(duì)傳輸信息的抽象,使得所有信息都轉(zhuǎn)化為字節(jié)流的形式傳輸,信息解讀的過程與傳輸過程分離。C語言中,I/O操作可以簡(jiǎn)單地看作是從程序移進(jìn)或移出字節(jié),這種搬運(yùn)的過程便稱為流(stream)。程序只需要關(guān)心是否正確地輸出了字節(jié)數(shù)據(jù),以及是否正確地輸入了要讀取字節(jié)數(shù)據(jù),特定I/O設(shè)備的細(xì)節(jié)對(duì)程序員是隱藏的。文件處理方法緩沖文件系統(tǒng):高級(jí)文件系統(tǒng),系統(tǒng)自動(dòng)為正在使用的文件開辟內(nèi)存緩沖區(qū)非緩沖文件系統(tǒng):低級(jí)文件系統(tǒng),由用戶在程序中為每個(gè)文件設(shè)定緩沖區(qū)重定向是由操作系統(tǒng)來完成的,一般來說,標(biāo)準(zhǔn)的輸出和輸入設(shè)備通常指的是顯示器和鍵盤,在支持重定向的操作系統(tǒng)中,標(biāo)準(zhǔn)輸入輸出能被替換,以DOS系統(tǒng)為例,看一段代碼:#include

<stdio.h>/*使用printf要包含的頭文件*/void

main(void)

/*主函數(shù)*/{printf("本段文字用來測(cè)試重定向");

/*輸出提示信息*/getchar();

/*等待,按任意鍵繼續(xù)*/}Hello.exe<輸入,>輸出*fp;指針變量說明:FILE用法:文件打開時(shí),系統(tǒng)自動(dòng)建立文件結(jié)構(gòu)體,并把指向它的指針返回來,程序通過這個(gè)指針獲得文件信息,訪問文件文件關(guān)閉后,它的文件結(jié)構(gòu)體被釋放使用printf函數(shù)時(shí),輸出設(shè)備默認(rèn)為標(biāo)準(zhǔn)輸出設(shè)備(一般是顯示器),因此,不需要告訴printf函數(shù)顯示器在哪。但如果想從文件中讀取輸入,情況就不同了,系統(tǒng)中有不同的磁盤,每個(gè)磁盤又有成

千上萬的文件,到底應(yīng)該從哪個(gè)讀呢?要想對(duì)文件進(jìn)行操作,系統(tǒng)需要很多控制信息,包括文件名,文件當(dāng)前讀寫位置,緩沖區(qū)位置和大小等,為此,C語言提供了“文件型結(jié)構(gòu)來標(biāo)示記錄待操作文件的信息,該結(jié)構(gòu)定義于頭文件stdio.h中,其形式為:struct

_iobuf{char

*_ptr;//當(dāng)前緩沖區(qū)內(nèi)容指針int _cnt;//緩沖區(qū)還有多少個(gè)字符char

*_base;//緩沖區(qū)的起始地址int _flag;//文件流的狀態(tài),是否錯(cuò)誤或者結(jié)束int _file;//文件描述符int _charbuf;//雙字節(jié)緩沖,緩沖2個(gè)字節(jié)int _bufsiz;//緩沖區(qū)大小char

*_tmpfname;//臨時(shí)文件名};typede

f

struct

_iobuf

FILE;C語言程序在進(jìn)行文件操作時(shí)遵循如下操作步驟:打開讀寫操作關(guān)閉,通俗地說,打開是獲取文件結(jié)構(gòu)、系統(tǒng)為文件分配緩沖區(qū)的過程,不打開文件就不能對(duì)其進(jìn)行讀寫,關(guān)閉是釋放緩沖區(qū)和其他資源的過程,不關(guān)閉文件就會(huì)慢慢耗光系統(tǒng)資源,。在進(jìn)行文件操作時(shí),系統(tǒng)自動(dòng)與3個(gè)標(biāo)準(zhǔn)設(shè)備文件聯(lián)系,這3個(gè)文件無需打開和關(guān)閉,它們的文件指針分別是:stdin:標(biāo)準(zhǔn)輸入文件指針,系統(tǒng)分配為鍵盤。stdout:標(biāo)準(zhǔn)輸出文件指針,系統(tǒng)分配為顯示器。stderr:標(biāo)準(zhǔn)錯(cuò)誤輸出文件指針,系統(tǒng)分配為顯示器。舉例來說,從文件輸入和向文件輸出有兩個(gè)對(duì)應(yīng)函數(shù)fscanf和fprintf,兩個(gè)函數(shù)的原型分別為:int

fprintf(FILE*

ofp,控制字符串,參數(shù)表);int

fscanf(FILE*

ifp,控制字符串,參數(shù)表);參數(shù)表中參數(shù)的個(gè)數(shù)同樣是任意的,fprintf函數(shù)用于將轉(zhuǎn)換后的控制字符串寫出到ofp指向的文件中,fscanf用于從ifp指向的文件中讀取字節(jié)信息為參數(shù)表中的參數(shù)賦值。前面章節(jié)中用到的標(biāo)準(zhǔn)輸入輸出函數(shù)printf和scanf實(shí)際上等價(jià)于:fprintf(stdout,

控制字符串,參數(shù)表)fscanf(stdin,

控制字符串,參數(shù)表)C文件操作用庫(kù)函數(shù)實(shí)現(xiàn),包含在stdio.h閉文自動(dòng)打開和關(guān)閉三個(gè)標(biāo)準(zhǔn)文件準(zhǔn)輸入------鍵盤

stdi準(zhǔn)輸出------顯示器

stdo準(zhǔn)出錯(cuò)輸出-----顯示器

stderr函數(shù)原型:

FILE

*fopen(char *name,char

*mode)功能:按指定方式打開文件返值:正常打開,為指向文件結(jié)構(gòu)體的指針;打開失敗,為NULL要打開的文件名式標(biāo)

{ printf(“File

open

error!\n”);

ut標(biāo)

exit(0);標(biāo)

if(fp==NULL)

n例

FILE

*fp;fp=

fopen

(“c:\\fengyi\\bkc\\test.dat”,”r”);}文件使用方式:打開文件-->文件讀/寫-->關(guān)件

文件打開與測(cè)試FILE

*fp;系統(tǒng)

fp=fopen(“aa.c”,“w”);

FILE

*fp;

char

*filename=“c:\\fengyi\\bkc\\test.dat”

fp=

fopen(filename,”r”);

打開文件fopen

使用文件方“r+/rb+”(讀寫)“a/ab”(追加)“w/wb”(只寫)“r/rb”(只讀)“w+/wb+”(讀寫)為輸入打開一個(gè)文本/二進(jìn)制文件為輸出打開或建立一個(gè)文本/二進(jìn)制文件為讀/寫打開一個(gè)文本/二進(jìn)制文件為讀/寫建立一個(gè)文本/二進(jìn)制文件“a+/ab+”

(讀寫)

為讀/寫打開或建立一個(gè)文本/二進(jìn)制文件向文本/二進(jìn)制文件尾追加數(shù)據(jù)文件使用方式含義int

fclose(FILE

*fp)文件打開時(shí)返回的文件類型指針不關(guān)閉文件可能會(huì)丟失數(shù)據(jù)fgetc(fp)fp為文件句柄,函數(shù)值為得到的字符。fputc(ch,fp)ch為字符變量,fp為句柄。成功函數(shù)返回相應(yīng)字符;失敗返回EOF。按照文本的方式讀取字符,以及寫入字符。feof函數(shù)原型:

int

feof(FILE

*fp)功能:判斷文件是否結(jié)束返值:文件結(jié)束,返回真(非0);文件未結(jié)束,返回0函數(shù)原型:char

*fgets(char *s,int

n,FILE

*fp)int

fputs(char

*s,FILE

*fp)功能:從fp指向的文件讀/寫一個(gè)字符串返值:

fgets正常時(shí)返回讀取字符串的首地址;出錯(cuò)或文件尾,返回NULL

fputs正常時(shí)返回寫入的最后一個(gè)字符;出錯(cuò)為EOFfgets從fp所指文件讀n-1個(gè)字符送入s指向的內(nèi)存并在最后加一個(gè)‘\0’(若讀入n-1個(gè)字符前遇換行符或文件尾(EOF)即結(jié)束)fputs把s指向的字符串寫入fp指向的文件區(qū),數(shù)據(jù)塊I/O:fread與fwrite函數(shù)原型:size_tsize_tcount,FILEcount,FILE*fp)*fp)fread(void

*buffer,size_t size,

size_tfwrite(void

*buffer,size_t size,

size_t功能:讀/寫數(shù)據(jù)塊返值:成功,返回讀/寫的塊數(shù);出錯(cuò)或文件尾,返回0說明:typedef

unsigned

size_t;buffer:

指向要輸入/輸出數(shù)據(jù)塊的首地址的指針size:

每個(gè)要讀/寫的數(shù)據(jù)塊的大?。ㄗ止?jié)數(shù))count:

要讀/寫的數(shù)據(jù)塊的個(gè)數(shù)fp:

要讀/寫的文件指針fread與fwrite

一般用于二進(jìn)制文件的輸入/輸出格式化I/O:fprintf與fscanf函數(shù)原型:intfprintf(FILE*fp,const

char*format[,argument,…])intfscanf(FILE*fp,const

char*format[,address,…])功能:按格式對(duì)文件進(jìn)行I/O操作返值:成功,返回I/O的個(gè)數(shù);出錯(cuò)或文件尾,返回EOF例

fprintf(fp,“%d,%6.2f”,i,t);fscanf(fp,“%d,%f”,&i,&t);//將i和t按%d,%6.2f格式輸出到fp文件//若文件中有3,4.5

,則將3送入i,

4.5送入t出錯(cuò)的檢測(cè)ferror函數(shù)函數(shù)原型:int

ferror(FILE

*fp)功能:測(cè)試文件是否出現(xiàn)錯(cuò)誤返值:未出錯(cuò),0;出錯(cuò),非0說明每次調(diào)用文件輸入輸出函數(shù),均產(chǎn)生一個(gè)新的ferror函數(shù)值,所以應(yīng)及時(shí)測(cè)試fopen打開文件時(shí),ferror函數(shù)初值自動(dòng)置為0前面介紹的文件讀寫是針對(duì)順序讀寫的情況,實(shí)際上,文件的讀寫方式有兩種,一是順序讀寫,位置指針按字節(jié)順序從頭到尾移動(dòng),另一種是隨機(jī)讀寫,位置指針按需要移動(dòng)到任意位置,隨機(jī)形式多用于二進(jìn)制文件的讀寫。如果要對(duì)文件進(jìn)行隨機(jī)讀寫,就需要控制文件位置指針的值,這就是文件定位,與文件定位有關(guān)的函數(shù)是rewind函數(shù),fseek函數(shù)和ftell函數(shù),本節(jié)來看一下這些函數(shù)的用法。rewind函數(shù)沒有返回值,其調(diào)用形式為;rewind(FILE*

fp);該函數(shù)使得文件位置指針返回文件開頭。隨機(jī)形式允許文件位置指針跳來跳去,為得到文件指針的當(dāng)前位置,C語言標(biāo)準(zhǔn)庫(kù)提供了ftell函數(shù),其原型為:long

ftell(FILE

*);執(zhí)行成功時(shí),返回當(dāng)前文件指針到文件頭有多少個(gè)字節(jié),否則,返回-1。文件定位中最重要的一個(gè)函數(shù)是fseek,用以控制、調(diào)整文件指針的值,從而改變下一次讀寫操作的位置,其函數(shù)原型為;int

fseek(FILE

*

fp,

long

offset,

int

startPos);其中,fp是文件指針,startPos是起始點(diǎn),offset是目標(biāo)位置相對(duì)起始點(diǎn)的偏移量,可以為負(fù)數(shù),如果函數(shù)操作執(zhí)行成功,文件位置指針將被設(shè)定為“起始點(diǎn)+offset”,起始點(diǎn)并不是任意設(shè)定的,C語言給出了3中起始點(diǎn)方式,如所示:函數(shù)名功能編程實(shí)現(xiàn)統(tǒng)計(jì)英文文檔中大小寫字母數(shù)字的個(gè)數(shù)。學(xué)習(xí)下漢字標(biāo)準(zhǔn),并實(shí)現(xiàn)統(tǒng)計(jì)中文文檔。1實(shí)現(xiàn)文件的增刪查改。2實(shí)現(xiàn)文件的批量修改,在文件末位添加一段話。3實(shí)現(xiàn)根據(jù)密碼加密解密一個(gè)文件。4指針函數(shù)函數(shù)變量變量數(shù)組數(shù)組指針結(jié)構(gòu)體指針 結(jié)構(gòu)體地址指針地址數(shù)組名是一種常指針(不能修改),其值等于數(shù)組占據(jù)內(nèi)存單元的首地址,但其類型取決于數(shù)組的維數(shù)。二維數(shù)組a[i][j]大家認(rèn)為&a,*a,a區(qū)別在哪里作為指針存儲(chǔ)的地址都是一樣,是否意味著指針都一樣呢指針不僅僅有大小,也也有數(shù)據(jù)類型,類型決定了大小數(shù)組與指針關(guān)系密切,數(shù)組元素除了可以使用下標(biāo)來訪問,還可

用指針形式表示。數(shù)組元素可以很方便地用數(shù)組名常指針來表示,以3維int型數(shù)組A舉例,其中的元素A[i][j][k]可用下述形式表示:(1)*(A[i][j]+k)A[i][j]是int型指針,其值為&A[i][j][0],因此,A[i][j][k]可表述為*(A[i][j]+k)。(2)*(*(A[i]+j)+k)和第一種形式比較,不難發(fā)現(xiàn)A[i][j]=*(A[i]+j),A[i]是二級(jí)指針,其值為&A[i][0]。(3)*(*(*(A+i)+j)+k)將第2種形式的A[i]替換成了*(A+i),此處A是三級(jí)指針,其值為&A[0]。此處以3維數(shù)組舉例,還可進(jìn)一步推廣到更高維的情況。大家練習(xí)一下,創(chuàng)建一個(gè)四維數(shù)組,循環(huán)遍歷一下,查找一下,一個(gè)變量是否在這個(gè)數(shù)組之中。指針也可作為數(shù)組中的元素,將一個(gè)個(gè)指針用數(shù)組形式組織起來,就構(gòu)成了指針數(shù)組。一個(gè)數(shù)組,若其元素均為指針類型數(shù)據(jù),稱為指針數(shù)組,也就是說,指針數(shù)組中的每一個(gè)元素都存放一個(gè)地址,相當(dāng)于一個(gè)指針變量。定義一維指針數(shù)組的一般形式為 類型名*數(shù)組名[數(shù)組長(zhǎng)度]; int

*p[4];有一個(gè)指針數(shù)組,其元素分別指向一個(gè)整型數(shù)組的元素,用指向指針數(shù)據(jù)的指針變量,輸出整型數(shù)組各元素的值。然后從小到大顯示。#include

<stdio.h>int

main(){int

a[5]={1,3,5,7,9};int

*num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};int

**p,i;p=num;for(i=0;i<5;i++){ printf("%d

",**p);p++;}printf("\n"); return

0;}&a[0]&a[1]&a[2]&a[3]&a[4]p12345將若干字符串按字母順序(由小到大)輸出。解題思路:定義一個(gè)指針數(shù)組,用各字符串對(duì)

它進(jìn)行初始化,然后排序,但不是移動(dòng)字符串,而是改變指針數(shù)組的各元素的指向。二維數(shù)組存儲(chǔ)空間固定指針數(shù)組元素的作用相當(dāng)于二維數(shù)組的行名但指針數(shù)組中元素是指針變量字符指針數(shù)組相當(dāng)于可變列長(zhǎng)的二維數(shù)組二維數(shù)組的行名是地址常量Follow\0Great\0FORTRAN\0Computer\0name[0]name[1]name[2]name[3]在了解了指針數(shù)組的基礎(chǔ)上,需要了解指向指針數(shù)據(jù)的指針變量,簡(jiǎn)稱為指向指針的指針。namepchar

**pFollow\0Great\0FORTRAN\0Computer\0name[0]name[1]name[2]name[3]namep指針數(shù)組的一個(gè)重要應(yīng)用是作為main函數(shù)的形參。在以往的程序中,main函數(shù)的第一行一般寫成以下形式:intmain()

或int

main(void)表示main函數(shù)沒有參數(shù),調(diào)用main函數(shù)時(shí)不必給出實(shí)參。這是一般程序常采用的形式。實(shí)際上,在某些情況下,main函數(shù)可以有參數(shù),例如:int

main(int

argc,char

*argv[])其中,argc和argv就是main函數(shù)的形參,它們是程序的“命令行參數(shù)”。argv是*char指針數(shù)組,數(shù)組中每一個(gè)元素(其值為指針)指向命令行中的一個(gè)字符串。通常main函數(shù)和其他函數(shù)組成一個(gè)文件模塊,有一個(gè)文件名。對(duì)這個(gè)文件進(jìn)行編譯和連接,得到可執(zhí)行文件(后綴為.exe)。用戶執(zhí)行這個(gè)可執(zhí)行文件,操作系統(tǒng)就調(diào)用main函數(shù),然后由

main函數(shù)調(diào)用其他函數(shù),從而完成程序的功能。#include

<stdio.h>int

main(int

argc,char

*argv[]){while(argc>1){

++argv;printf(“%s\n”,

*argv);--argc;}return

0;}普通數(shù)組名可以看成是“指向數(shù)組元素首地址的常指針”,結(jié)構(gòu)體數(shù)組名同樣可以看成是指向結(jié)構(gòu)體數(shù)組元素首地址的常指針,也可以聲明一個(gè)結(jié)構(gòu)指針變量,使其指向數(shù)組元素首地址,這兩種方式都能實(shí)現(xiàn)通過指針訪問數(shù)組元素,我們來親自動(dòng)手實(shí)踐一下。函數(shù)被載入內(nèi)存,函數(shù)必然有一個(gè)地址是函數(shù)的入口,我們用這個(gè)地址來調(diào)用,函數(shù)名也是指向函數(shù)入口點(diǎn)的指針,我們可以通過函數(shù)名找到函數(shù)的執(zhí)行入口。同時(shí)C語言的編譯器(無論VC或者GCC)都有這樣的規(guī)則。針對(duì)函數(shù)void run(),函數(shù)名run

解析為函數(shù)的地址,run,&run,*run都解析為run的入口地址,即為&run函數(shù)的首地址。而且函數(shù)名不可以用sizeof操作符。先復(fù)習(xí)下指針數(shù)組的概念,當(dāng)數(shù)組元素都是同種類型的指針時(shí),該數(shù)組稱為指針數(shù)組,如“int*

A[3];”即聲明了一個(gè)指針數(shù)組A,大小為3,其中每個(gè)元素都是int型指針。如果數(shù)組元素都是指向同型函數(shù)(返回值類型相同,參數(shù)類型相同)的指針,該數(shù)組稱為函數(shù)指針數(shù)組,來看一個(gè)例子:double

(*f[5])(

);f是一個(gè)數(shù)組,有5個(gè)元素,元素都是函數(shù)指針,指向的函數(shù)類型是沒有參數(shù)且返回double類型的函數(shù)。函數(shù)指針數(shù)組的使用方式和普通數(shù)組完全一致,我們來親自演練一下。再來看下述語句:double

(*f[5])(

);已經(jīng)知道,數(shù)組名可作為指向數(shù)組首元素起始地址的常指針,那函數(shù)指針數(shù)組的數(shù)組名是什么呢?類推得出,函數(shù)指針數(shù)組名,對(duì)應(yīng)上面語句中的f,是指向函數(shù)指針的常指針,下述代碼聲明了一個(gè)指向函數(shù)指針的指針變量p,并用f為其初始化:double

(**p)(

)=f;做個(gè)小測(cè)試,用變量名p給出下列有關(guān)指針的定義:(1)一個(gè)指向整型數(shù)的指針。(2)一個(gè)指向整型數(shù)指針的指針(3)一個(gè)有10個(gè)整型指針的數(shù)組(4)一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針。(5)一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù),并返回一個(gè)整型數(shù)。(6)一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù),并返回一個(gè)整型數(shù)。(7)一個(gè)指向函數(shù)指針的指針,所指向的函數(shù)有一個(gè)整型參數(shù),并返回一個(gè)整型數(shù)。C語言中,內(nèi)置類型,如int型,其指針(int*)可以看成種新的類型,那有沒有函數(shù)指針類型呢?借助前面介紹的typedef,能容易地將函數(shù)指針類型化。在結(jié)構(gòu)體和共用體一節(jié)中介紹了typedef和#define的基本用法,對(duì)比了以下兩個(gè)語句:typedef

double*

DP;DP

pDouble1,

pDouble2;與#define

DP

double*DP

pDouble1,

pDouble2;不知大家是否還記得兩者的不同,通俗地說,#define是種字面替換,而typedef卻是引入一個(gè)新的助記符號(hào),這么說稍顯枯燥,下面給出一個(gè)簡(jiǎn)單的理解方式:試著將上面語句中的typedef和#define去掉試試看。對(duì)typedef語句“typedef

double*

DP;”來說,去掉typedef后,其仍然是條完整的C語句“double*

DP;”,該語句用以聲明一個(gè)double類型的指針變量DP。由此可以理解:typedef的作用是將變量名作為(或說定義為)該變量所屬類型的別名(或說助記符)。#define不具備這種特點(diǎn),去掉#define后,“DP

double*”并不是一條合法的C語句。如何用define與typedef定義函數(shù)指針?本節(jié)從更深層次幫助大家理解函數(shù)。主要是函數(shù)的參數(shù)的傳遞兩種形式,傳值與傳地址。函數(shù)的輸入-參數(shù),函數(shù)的輸出-返回值不僅可以是int,double等等也可以是數(shù)組,結(jié)構(gòu)體等等。如果將函數(shù)比作劇本,那形參和實(shí)參的關(guān)系相當(dāng)于角色和演員的關(guān)系,函數(shù)的參數(shù)傳遞有傳值和傳地址兩種方式。傳值調(diào)用時(shí),在函數(shù)內(nèi)對(duì)形參的改變都不會(huì)影響實(shí)參,要想在函數(shù)內(nèi)對(duì)實(shí)參進(jìn)行操作,必須采用傳地址調(diào)用的方式。這是形象化的理解,從本質(zhì)上說,這是由參數(shù)傳遞的副本機(jī)制決定的。所謂副本機(jī)制,是指copy(拷貝)的思想,不論是傳值調(diào)用還是傳址調(diào)用,編譯器都要為每個(gè)參數(shù)制作臨時(shí)副本,或稱拷貝,函數(shù)體中對(duì)參數(shù)的修改都是對(duì)副本的修改,下面具體分析之。相比傳值調(diào)用,傳址調(diào)用似乎要復(fù)雜一點(diǎn),但只要知道,傳址調(diào)用也是通過副本機(jī)制,便能很好地理解傳址調(diào)用的機(jī)理./*局部變量c*//*返回*//*函數(shù)調(diào)用*/函數(shù)返回的副本機(jī)制很好地解釋了為什么return一個(gè)局部變量是合法的,來看一段簡(jiǎn)單的求和函數(shù)代碼:int

sum(int

a,int

b)/*函數(shù)定義*/{intc=a+b;return

c;}……intd=sum(1,2);來看語句“intd=sum(1,2);”,該語句先執(zhí)行函數(shù)sum,sum函數(shù)執(zhí)行完畢后將結(jié)果賦值給int型變量d,如果從字面上理解,是將c賦值給d,但實(shí)際上,在執(zhí)行賦值操作時(shí),由于函數(shù)sum已經(jīng)執(zhí)行完畢返回,函數(shù)中的局部變量c已被撤銷,不存在了。實(shí)際上,在c被撤銷前,函數(shù)已經(jīng)為返回值c創(chuàng)建了副本,保存在特定的位置上,賦值操作是由該位置處的副本完成的,形象的示意如

所示。如果要細(xì)分,函數(shù)返回也可以認(rèn)為存在傳值和傳址兩種方式。函數(shù)返回同樣也是根據(jù)副本機(jī)制來處理的,首先來回顧下函數(shù)返回的流程:當(dāng)執(zhí)行到return語句時(shí),return的值被復(fù)制到某個(gè)內(nèi)存單元或寄存器中,其地址是由編譯器來維護(hù)的,程序員無法直接訪問該地址,也就是說,在函數(shù)執(zhí)行完畢,相關(guān)現(xiàn)場(chǎng)被撤銷前,返回的值被復(fù)制保存到了某個(gè)地方,編譯器訪問該位置即可知道函數(shù)的返回值。該位置即可看成是函數(shù)中返回值的副本。對(duì)函數(shù)返回取地址是不合法的,即假設(shè)存在如下函數(shù):int

A(int

b,int

c);不允許使用如下形式的語句:&A(3,4);下面來看一下如何通過返回指針在函數(shù)中動(dòng)態(tài)申請(qǐng)內(nèi)存,感受下#include

<stdio.h> /*使用printf要包含的頭文件*/char*

GetMemory(int

num)/*定義函數(shù)GetMemory*/{char*

p=(char*)malloc(sizeof(char)*num);/*動(dòng)態(tài)申請(qǐng)內(nèi)存*/returnp;}void

main(void)

/*主函數(shù)*/{char*

str=NULL;

/*聲明一char型指針str并初始化為NULL*/str=GetMemory(10);/*調(diào)用函數(shù)GetMemory*/if(str!=NULL)/*判斷動(dòng)態(tài)內(nèi)存是否申請(qǐng)成功*/strcpy(str,"Hello,C");/*將一個(gè)C風(fēng)格字符串復(fù)制給str*/printf("%s",str);free(str);/*釋放動(dòng)態(tài)申請(qǐng)的內(nèi)存*/getchar();/*等待,按任意鍵結(jié)束*/}如果將中的GetMemory函數(shù)修改如下,會(huì)怎樣?char*

GetMemory(void) /*定義函數(shù)GetMemory*/{char*

p="Hello,C";return

p; /*返回局部指針*/}"Hello,C"作為常量字符串,位于程序的只讀存儲(chǔ)區(qū)(.rodata),此時(shí),返回指向只讀存儲(chǔ)區(qū)的指針p并沒有問題,但該指針只能用于輸出,而不能用于輸入改寫。結(jié)構(gòu)體可以看成一種數(shù)據(jù)組織方式,將很多不同類型的相關(guān)數(shù)據(jù)打包,構(gòu)成一種新的類型,從這種意義上說,結(jié)構(gòu)體變量完全可以當(dāng)成是一種普通類型的變量來使用。結(jié)構(gòu)體變量作函數(shù)參數(shù)時(shí),也有傳值和傳址兩種方式,函數(shù)返回亦是如此,既可以返回結(jié)構(gòu)體變量,也可以返回指向非局部結(jié)構(gòu)體變量的指針。采用值傳遞時(shí),在函數(shù)內(nèi)將生成實(shí)參的“復(fù)制品”,如果參數(shù)多是像int,char之類的簡(jiǎn)單變量,這些變量占用的內(nèi)存并不多,復(fù)制也快。但結(jié)構(gòu)或共用體變量往往由多個(gè)成員變量組成,占用內(nèi)存大,如果復(fù)制一份,會(huì)造成時(shí)間和空間雙重浪費(fèi)。采用地址傳遞不會(huì)造成時(shí)空浪費(fèi),因?yàn)椴还苁嵌嗝磸?fù)雜的結(jié)構(gòu)類型,指針參數(shù)只占4個(gè)內(nèi)存字節(jié)。結(jié)構(gòu)體變量的數(shù)據(jù)成員作函數(shù)實(shí)參時(shí),結(jié)構(gòu)體變量的數(shù)據(jù)成員可以當(dāng)成是普通變量來使用,同樣存在傳值和傳址兩種函數(shù)調(diào)用方式.如果函數(shù)的返回值是某個(gè)結(jié)構(gòu)體變量,常稱該函數(shù)為結(jié)構(gòu)體型函數(shù),結(jié)構(gòu)體型函數(shù)定義的基本格式為:struct

結(jié)構(gòu)體名函數(shù)名(形參列表){函數(shù)體;}聲明一個(gè)結(jié)構(gòu)體型函數(shù)時(shí)也不要遺漏struct,如下:struct

結(jié)構(gòu)體名函數(shù)名(形參列表);結(jié)構(gòu)體指針同樣可以作為函數(shù)的返回類型,前提是該指針不是指向棧內(nèi)存的,換句話說,該指針不是指向局部結(jié)構(gòu)體變量的。與返回結(jié)構(gòu)體變量相比,返回結(jié)構(gòu)體指針大大節(jié)省了函數(shù)返回時(shí)創(chuàng)建副本的時(shí)空開銷,有較高的效率。數(shù)組是種使用廣泛的數(shù)據(jù)結(jié)構(gòu),數(shù)組名和數(shù)組元素都

可以作為函數(shù)的參數(shù),實(shí)現(xiàn)函數(shù)間的數(shù)據(jù)傳遞和共享。此外,由于數(shù)組名和指針的對(duì)應(yīng)關(guān)系,在一些需要指

針型參數(shù)的場(chǎng)合,可以用數(shù)組名(即常指針)作函數(shù)

參數(shù)。把數(shù)組的元素作實(shí)參

int

a[1]={1,2,3}fun1(a[2])把數(shù)組名作實(shí)參

int

a[]={1,2,3}fun2(a)void

main(){float

s[5]={80,82,70,90,68};float

a1=f1(s[0],s[1],s[2],s[3],s[4]);//數(shù)組元素作實(shí)參

float

a2=f2(s,5);//數(shù)組名(數(shù)組首地址)作實(shí)參}float

a1(floata,float

b,float

c,float

d,floate){return

(a+b+c+d+e)/5;}在調(diào)用效率低函數(shù)時(shí)需要傳遞多個(gè)實(shí)參,、通用性差float

a2(floatscore[],intn){float

result=0;int

i;for

(i=0;i<n;i++)result+=score[i];return

result/n;}在調(diào)用函數(shù)時(shí)只需傳遞兩個(gè)實(shí)參,效率高、通用性強(qiáng)當(dāng)用數(shù)組名作為函數(shù)實(shí)參時(shí),函數(shù)形參會(huì)接收到此數(shù)組的首地址。這就意味著函數(shù)形參數(shù)組首地址等于實(shí)參數(shù)組首地址。在函數(shù)中對(duì)形參數(shù)組的操作直接作用于實(shí)參數(shù)組。void

main(){int

a[3]={4,5,6};f(a);//數(shù)組名作實(shí)參}void

f(int

b[3]){b[0]=8;b[1]=9;b[2]=10;}76011

952

473實(shí)參與形參均用數(shù)組void

inv(int x[],

int

n){ int

t,i,j,m=(n-1)/2;for(i=0;i<=m;i++){ j=n-1-i;t=x[i];

x[i]=x[j];

x[j]=t;}}main(){ int

i,a[10]={3,7,9,11,0,6,7,5,4,2};inv(a,10);printf("The

array

has

beenreverted:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");}void

inv(int *x,

int

n){ int

t,*p,*i,*j,m=(n-1)/2;i=x; j=x+n-1;

p=x+m;for(;i<=p;i++,j--){

t=*i;

*i=*j; *j=t;

}}main(){ int

i,a[10]={3,7,9,11,0,6,7,5,4,2};inv(a,10);printf("The

array

has

beenreverted:\n");for(i=0;i<10;i++)printf("%d,",a[i]);printf("\n");}24576011973void

inv(int

*x,

int

n){ int

t,*i,*j,*p,m=(n-1)/2;i=x; j=x+n-1;

p=x+m;for(;i<=p;i++,j--){

t=*i;

*i=*j; *j=t;

}}main(){ int

i,a[10],*p=a;for(i=0;i<10;i++,p++)scanf("%d",p);p=a;

inv(p,10);printf("The

array

has

been

reverted:\n");for(p=a;p<a+10;p++)printf("%d",*p);}實(shí)參與形參均用指針變量void

inv(int x[],

int

n){ int

t,i,j,m=(n-1)/2;for(i=0;i<=m;i++){ j=n-1-i;t=x[i];

x[i]=x[j];x[j]=t;}}main(){ int

i,a[10],*p=a;for(i=0;i<10;i++,p++)scanf("%d",p);p=a;

inv(p,10);printf("The

array

has

beenreverted:\n");for(p=arr;p<arr+10;p++)printf("%d

",*p);}遞歸要成功,離不開兩點(diǎn),一是遞歸遞進(jìn)機(jī)制,二是遞歸終止條件,遞進(jìn)機(jī)制應(yīng)保證每次調(diào)用都向調(diào)用終止靠近一步,中,f(5)調(diào)用

f(4),f(4)調(diào)用f(3),……,一步步靠近f(1)這一終止條件。這保證了遞歸調(diào)用正常結(jié)束,不至于出現(xiàn)無限循環(huán),導(dǎo)致系統(tǒng)內(nèi)存耗盡而

崩潰,通常用參數(shù)對(duì)調(diào)用過程進(jìn)行判斷,以

便在合適的時(shí)候切斷調(diào)用鏈,如中的

“if(n==1){……}”結(jié)構(gòu)。Hanoi(漢諾)塔問題。古代有一個(gè)梵塔,塔內(nèi)有3個(gè)座A、B、C,開始時(shí)A座上有64個(gè)盤子,盤子大小不等,大的在下,小的在上。有一個(gè)老和尚想把這64個(gè)盤子從A座移到C座,但規(guī)定每次只允許移動(dòng)一個(gè)盤,且在移動(dòng)過程中在3個(gè)座上都始終保持大盤在下,小盤在上。在移動(dòng)過程中可以利用B座。要求編程序輸出移動(dòng)一盤子的步驟。據(jù)說移動(dòng)完成就是世界的末日。解題思路:要把64個(gè)盤子從A座移動(dòng)到C座,需要移動(dòng)大約264

次盤子。一般人是不可能直接確定移動(dòng)盤子的每一個(gè)具體步驟的聯(lián)想一下,最簡(jiǎn)單的情況,3個(gè)盤子的情況,4個(gè)盤子的情況,#include

<stdio.h>int

main(){ void

hanoi(int

n,char

one,char

two,char

three);int

m;printf(“the

number

of

diskes:");scanf("%d",&m);printf("move

%d

diskes:\n",m);hanoi(m,'A','B','C');}void

hanoi(int

n,char

one,char

two,char

three){ void

move(char

x,char

y);if(n==1)move(one,three);else{ hanoi(n-1,one,three,two);move(one,three);hanoi(n-1,two,one,three);}}遞歸調(diào)用中,每次函數(shù)調(diào)用都有一定的堆棧操作,相比循環(huán)方式,時(shí)空開銷交到,因此,遞歸函數(shù)的效率總比功能相同的循環(huán)結(jié)構(gòu)略低,遞歸編寫的函數(shù)盡量用循環(huán)代替,以提高效率。但是,遞歸帶來的好處也是顯而易見的:一是程序的可讀性比較好,易于修改和維護(hù),二是在不斷的函數(shù)調(diào)用中,函數(shù)的規(guī)模在減小,在求解階乘例子中,不斷用復(fù)雜度為n-1的問題來描述復(fù)雜度為n的問題,直到問題可以直接求解為止(參數(shù)為1)。如果用循環(huán)語句實(shí)現(xiàn)的算法比使用遞歸復(fù)雜得多,有必要考慮復(fù)雜度和效率的折衷,此時(shí)建議優(yōu)先采用遞歸方式來解決問題。C語言的函數(shù)參數(shù)傳遞有傳值和傳址兩種方式,不論采用哪種方式,函數(shù)都會(huì)為實(shí)參在堆棧中開辟副本,所有的操作都是針對(duì)副本進(jìn)行的,因此,傳值調(diào)用不會(huì)修改實(shí)參,但傳址調(diào)用時(shí),因?yàn)橹羔橀g接操作的關(guān)系,會(huì)影響實(shí)參所在的內(nèi)存區(qū)域。函數(shù)返回同樣有值和地址之分,但應(yīng)注意,不要返回指向局部變量,即棧內(nèi)存的指針。函數(shù)是C語言的核心所在,只有全面掌握了函數(shù)的機(jī)制,才能讀懂高質(zhì)量代碼,寫出自己的高質(zhì)量代碼。作為兩種有效組織數(shù)據(jù)的手段,結(jié)構(gòu)和數(shù)組在函數(shù)中有著廣泛的應(yīng)用,結(jié)構(gòu)體變量本質(zhì)上可以當(dāng)成是普通變量來使用,而用數(shù)組名作參數(shù)傳遞給函數(shù)時(shí),其退化成一個(gè)指針,數(shù)組的大小等需要另外顯式傳遞給函數(shù)。最后說明了一種常用的編程機(jī)制—遞歸,使用遞歸應(yīng)注意終止條件的書寫,防止出現(xiàn)無限調(diào)用循環(huán),造成程序崩潰。實(shí)現(xiàn)一個(gè)函數(shù),傳入數(shù)組,對(duì)數(shù)組進(jìn)行排序.1實(shí)現(xiàn)一個(gè)函數(shù),修改一個(gè)指針變量。2創(chuàng)建一個(gè)函數(shù),進(jìn)行遞歸運(yùn)算實(shí)現(xiàn)1

+(1×2)+(1×2×3)+~~~~~31+2+3+4+~~`n分別用遞歸與非遞歸兩種函數(shù)實(shí)現(xiàn),4有時(shí)興致勃勃地寫完一大頁(yè)的程序,編譯鏈接卻跳出了很多奇奇怪怪的錯(cuò)誤,比如某某變量未定義,某某函數(shù)找不到,你可能在嘀咕,這個(gè)函數(shù)明明在這里啊,那不是某某變量么?是不是編譯器有問題?。孔x完本章你就會(huì)發(fā)現(xiàn),編譯器沒有問題,是函數(shù)、變量的作用域、生存期與可見域在作怪??赡苌婕暗降某绦蛞赜校鹤兞浚ㄆ胀愋?/p>

的變量、結(jié)構(gòu)體變量和共用體變量的統(tǒng)稱),常量、函數(shù)以及結(jié)構(gòu)體、共用體的定義等。變量名、函數(shù)名等都對(duì)應(yīng)著內(nèi)存中的一塊區(qū)域,那這些實(shí)體在內(nèi)存中是如何存放的呢,程序又是如何使用這些變量的,首先從C程序內(nèi)存分配入手,一步步回答這些問題。代碼數(shù)據(jù)載入內(nèi)存靜態(tài)動(dòng)態(tài)內(nèi)存分配一個(gè)由C編譯的程序占用的內(nèi)存大致分為以下幾部分:棧區(qū)(stack):由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量的值等。堆區(qū)(heap):一般由程序員分配釋放(動(dòng)態(tài)內(nèi)存申請(qǐng)與釋放),若程序員不釋放,程序結(jié)束時(shí)可能由操作系統(tǒng)回收。全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域,該區(qū)域在程序結(jié)束后由操作系統(tǒng)釋放。程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼,字符串常量和其他常量的存儲(chǔ)位置,程序結(jié)束后由操作系統(tǒng)釋放。。按作用域分,變量可分為局部變量和全局變量,所謂局部變量,是指在函數(shù)內(nèi)部定義的變量,局部變量?jī)H在定義它的函數(shù)內(nèi)才能有效使用,其作用域僅限在函數(shù)內(nèi),即從變量定義的位置開始,到函數(shù)體結(jié)束。通常,編譯器不為局部變量分配內(nèi)存單元,而是在程序運(yùn)行中,當(dāng)局部變量所在的函數(shù)被調(diào)用時(shí),系統(tǒng)根據(jù)需要臨時(shí)為其分配內(nèi)存。當(dāng)函數(shù)執(zhí)行結(jié)束時(shí),局部變量被撤銷,占用內(nèi)存被收回。在函數(shù)外定義的變量稱為全局變量,也稱外部變量,全局變量的

作用域較廣,全局變量不屬于任何一個(gè)函數(shù),理論上可被其作用

域中的所有函數(shù)訪問,因此,提供了一個(gè)不同函數(shù)間聯(lián)系的途徑,使函數(shù)間的數(shù)據(jù)聯(lián)系不只局限于參數(shù)傳遞和return語句。全局變量一經(jīng)定義,編譯器會(huì)為其分配固定的內(nèi)存單元,在程序運(yùn)行期間,這塊內(nèi)存單元始終有效,一直到程序執(zhí)行完畢才由操作系統(tǒng)收回該塊內(nèi)存。在函數(shù)內(nèi)定義的變量,其作用范圍局限于此函數(shù)體內(nèi)部,這種變量叫局部變量,函數(shù)的形參、在main函數(shù)中定義的變量也是局部變量。局部變量在函數(shù)被調(diào)用時(shí)由系統(tǒng)分配存儲(chǔ)區(qū),在不同的函數(shù)中同名的變量實(shí)際上在內(nèi)存中占不同的單元,因此在不同的函數(shù)中可以定義相同名字的局部變量。#include

“stdio.h”void

fun(int

x,

int

y){int

z=10;x+=10;

y+=10;if(z){int

z=20;printf(“x=%d

y=%d

z=%d\n”,

x,

y,

z);}printf(“x=%d

y=%d

z=%d\n”,

x,

y,

z);}int

main(){int

x=10,

y=10,

z=10;fun(x,

y);printf(“x=%d

y=%d

z=%d”,

x,

y,

z);return

0;}程序的運(yùn)行結(jié)果是:

x=20

y=20

z=20x=20

y=20

z=10x=10

y=10

z=10在函數(shù)括號(hào)外面定義的變量叫全局變量,其作用范圍是從定義處直到文件末尾。由于全局變量作用范圍很大,若在某個(gè)函數(shù)中修改了全局變量的值,在其它函數(shù)中讀出的就是這個(gè)新值。建議:用大寫字母命名全局變量,便于區(qū)分局部變量。//x=8+20+10//y=2+20-10//x=8//y=2//x=38,

y=12#include

“stdio.h”int

x,y;voidfun(){int

a=20,

b=10;x=x+a+b;y=y+a-b;}int

main(){int

a=5,b=3;x=a+b;y=a-b;fun();printf(“%d,

%d”,x,y);return

0;}通俗地講:生存期指的是在程序運(yùn)行過程中,變量從創(chuàng)建到撤銷的一段時(shí)間。生存期的長(zhǎng)短取決于前面所講的存儲(chǔ)方式,對(duì)于自動(dòng)分配(棧分配),變量與其所在的代碼塊共存亡;對(duì)于靜態(tài)分配(編譯器預(yù)分配),變量與程序共存亡,程序開始執(zhí)行時(shí)即已存在,一致到程序運(yùn)行完畢退出后才撤銷;對(duì)于動(dòng)態(tài)存儲(chǔ)的內(nèi)存塊(注意:不是指向該內(nèi)存塊的指針),由程序員決定其生存期。對(duì)程序代碼區(qū)的函數(shù)、常量區(qū)的字符串常量和其他常量等、結(jié)構(gòu)體和共用體的定義等來說,生存期的討論沒有意義,因?yàn)樗鼈兌际桥c程序共存亡的。在程序代碼中,變量有效的范圍(源程序區(qū)域)稱為作用域,能對(duì)變量、標(biāo)識(shí)符進(jìn)行合法的訪問的范圍(源程序區(qū)域)稱為可見域,可以這樣說,作用域是變量理論上有效的區(qū)域,而可見域是變量實(shí)際有效的區(qū)域,可見域是作用域的子集??梢詫語言的作用域分為以下幾類:(1)塊作用域自動(dòng)變量(auto、register)和內(nèi)部靜態(tài)變量(static)具有塊作用域,在一個(gè)塊內(nèi)聲明的變量,其作用域從聲明點(diǎn)開始,到該塊結(jié)束為止。函數(shù)定義中聲明的形參,其作用域限定在該函數(shù)體內(nèi).(2)文件作用域外部靜態(tài)變量(static)具有文件作用域,從聲明點(diǎn)開始到文件末尾,此處所指的文件是編譯基本單位—c文件。(3)全局(程序)作用域全局變量(extern)具有全局作用域,只要在使用前對(duì)其進(jìn)行聲明,便可在程序(由若干個(gè)文件組成)的任意位置使用全局變量。void

fun(int

z){int

y;if

(x>0){int

x;…}}成對(duì)的大括號(hào)及內(nèi)部的語句組成了程序塊。函數(shù)體、選擇和循環(huán)結(jié)構(gòu)中都要用大括號(hào)表示邊界。大括號(hào)內(nèi)定義的變量,必須緊跟在左大括號(hào)后面,其作用范圍是從定義處直到配對(duì)的右大括號(hào)之前。若多層大括號(hào)中出現(xiàn)重名變量,外層同名變量在內(nèi)層中被屏蔽不起作用。z作用域x作用域y作用域變量可以在程序中三個(gè)地方說明:函數(shù)的參數(shù)定義部分函數(shù)內(nèi)部函數(shù)外部形式參數(shù)局部變量全局變量函數(shù)的形參及代碼塊中定義的變量都屬于auto變量,這是C語言中應(yīng)用最廣的一種變量,這類變量是棧分配的,是動(dòng)態(tài)分配存儲(chǔ)

空間的。舉函數(shù)形參為例,當(dāng)調(diào)用該函數(shù)時(shí),為形參分配存儲(chǔ)空間,當(dāng)函數(shù)調(diào)用結(jié)束時(shí),

就自動(dòng)釋放這些存儲(chǔ)空間。對(duì)代碼塊中定義

的變量(包含函數(shù)中定義的變量),當(dāng)執(zhí)行

到變量聲明語句時(shí),系統(tǒng)為這些auto變量分配空間,當(dāng)程序流程離開代碼塊時(shí),這些變量被自動(dòng)撤銷,其占用的內(nèi)存空間被釋放。自動(dòng)變量的定義格式為[auto]數(shù)據(jù)類型變量1[=初始化表達(dá)式],變量2[=初始化表達(dá)式]……;其中,方括號(hào)表示可以省略,此處變量不僅指普通內(nèi)置類型的變量,還包括數(shù)組、結(jié)構(gòu)體和指針等復(fù)合結(jié)構(gòu)。C語言默認(rèn)所定義的變量是auto變量,在以前所舉例子中,函數(shù)和代碼塊中的局部變量并沒有使用關(guān)鍵字auto,這實(shí)際上是遵循了C語言的默認(rèn)規(guī)定,舉個(gè)例子來說,在一個(gè)函數(shù)中,如下定義:int

a;float

b:自動(dòng)被C編譯器解釋為:auto

int

a;auto

float

b;int

a,b,c;

/*等價(jià)于auto

int

a,b,c;

*/……}int

y,z;/*等價(jià)于auto

int

y,z;

*/……return

0;auto變量的作用域和生存期都局限在定義它的代碼塊中,所謂代碼塊,是指用兩個(gè)花括號(hào)包裹起來的代碼行,函數(shù)只是代碼塊的一種,常見的代碼塊還有if結(jié)構(gòu)、for結(jié)構(gòu)等等,哪怕只是兩個(gè)成對(duì)花括號(hào),也能構(gòu)成一個(gè)獨(dú)立代碼塊。此外,結(jié)合“先聲明,后使用”的原則,可知,auto變量的作用域和生存期對(duì)應(yīng)著從定義到所在代碼塊結(jié)束這塊時(shí)空區(qū)域。來看下面的函數(shù):int

func(int

m,int

n){intx;/*等價(jià)于auto

int

x;*/{}代碼塊可以嵌套應(yīng)用形成一定的層次結(jié)構(gòu),那內(nèi)外層代碼塊中可否定義同名變量呢?如果可以,這些同名變量有什么關(guān)系呢?/*此處記為A行,聲明3個(gè)自動(dòng)變量,作用域和生存期為從A行到main函數(shù)執(zhí)行結(jié)束*/int

x=1,y=2,z=3;printf("x

is

%d,y

is

%d,z

is%d\n",x,y,z);{/*#1*//*此處記為B行,聲明的y,作用域和生存期為從B行到#4,屏蔽了A行定義的y*/inty=6;printf("x

is

%d,y

is

%d,z

is%d\n",x,y,z);{/*#2*//*此處記為C行,聲明的y和z,作用域和生存期為從C行到#3*//*屏蔽了B行定義的y和A行定義的z*/int

y=8,z=9;printf("x

is

%d,y

is

%d,z

is%d\n",x,y,z);}/*#3*/printf("x

is

%d,y

is

%d,z

is%d\n",x,y,z);}auto變量不能重復(fù)定義,所謂重復(fù),是指在同一代碼塊中,出現(xiàn)兩個(gè)同名變量。此處所指的同一代碼塊,不包括屏蔽的情況。下面的代碼就犯了重復(fù)定義的錯(cuò)誤:if(……){int

x,y;double

x,y;}并列層次的代碼塊中可以出現(xiàn)同名變量而不會(huì)引起混淆,最普遍的一個(gè)例子就是函數(shù),由于所有的函數(shù)都是在外部定義的,包括main函數(shù)在內(nèi)的所有函數(shù)都是并列的,因此,函數(shù)A內(nèi)定義的auto變量在函數(shù)B內(nèi)是完全不可見的,即使兩個(gè)函數(shù)中定義了同名變量,編譯器也能很好地將其區(qū)分開,這大大方便了函數(shù)的編寫。某些編譯器并不會(huì)自動(dòng)為auto變量初始化,這項(xiàng)工作必須在變量定義時(shí)由程序員顯式完成,否則,變量的值是隨機(jī)不確定的。不論是指針還是普通變量,時(shí)刻提醒自己注意初

始化,能有效防止一些稀奇古怪錯(cuò)誤的發(fā)生。一般來說,CPU訪問內(nèi)部寄存器的速度大大高于訪問內(nèi)存的速度,因此,有人提議,能否將一些應(yīng)用頻繁的變量放在CPU的通用寄存器中,這樣,在使用該變量時(shí)便不必再訪問內(nèi)存,直

接從寄存器中取,將大大提高程序運(yùn)行的效率,因此,C語言引入了register變量,稱為寄存器變量。extern變量又稱全局變量,放在靜態(tài)存儲(chǔ)區(qū),所謂全局,是說該變量可以在程序的任意位置使用,其作用域是整個(gè)程序代碼范圍內(nèi),和auto變量不同的是,extern變量有定義和聲明之分。寄存器變量的定義格式為:register數(shù)據(jù)類型變量1[=初始化表達(dá)式],變量2[=初始化表達(dá)式]……;和auto變量一樣,register變量也屬于局部變量。只能在函數(shù)體內(nèi)定義register變量,CPU使用寄存器中數(shù)據(jù)的速度要遠(yuǎn)遠(yuǎn)快于使用內(nèi)存中的

數(shù)據(jù)速度,因此,應(yīng)用好CPU內(nèi)的寄存器可以大大提高

程序的運(yùn)行效率和速度。但是,CPU中寄存器的數(shù)量有

限,所以,通常是把使用頻繁的變量定義為寄存器變量。VC編譯器會(huì)自動(dòng)優(yōu)化設(shè)置程序運(yùn)行時(shí)變量的存放位置,用戶不必做此聲明全局變量定義的基本格式為:extern

類型變量名=初始化表達(dá)式;此時(shí),初始化表達(dá)式不可省略,此指令通知編譯器在靜態(tài)存儲(chǔ)區(qū)中開辟一塊指定類型大小的內(nèi)存區(qū)域,用于存儲(chǔ)該變量。下列語句創(chuàng)建了一個(gè)初始值為100的int型全局變量m:extern

int

m=100;C語言規(guī)定,只要是在外部,即不是在任何一個(gè)函數(shù)內(nèi)定義的變量,編譯器就將其當(dāng)作全局變量,無論變量定義前是否有extern說明符。也就是說,只要在函數(shù)外部書寫下述形式即可:intm=100;當(dāng)全局變量定義時(shí),當(dāng)且僅當(dāng)省略了extern時(shí),初始化表達(dá)式才可省略,系統(tǒng)默認(rèn)將其初始化為0,對(duì)于定義的全局?jǐn)?shù)組或

結(jié)構(gòu),編譯器將其中的每個(gè)元素或成員的所有位都初始化為0。一般為了敘述方便,把建立存儲(chǔ)空間的變量聲明稱定義,而把不需要建立存儲(chǔ)空間的聲明稱為聲明在函數(shù)中出現(xiàn)的對(duì)變量的聲明(除了用extern聲明的以外)都是定義extern

intx;

//提升全局變量x作用范圍至此

void

main(){x++;}int

x=8;void

f1(){x--;}若在全局變量定義處之前想要訪問這個(gè)變量,可以在相應(yīng)的地方用“extern”關(guān)鍵字提前聲明此變量,從而將在后面定義的全局變量作用范圍擴(kuò)展到前面來。x原來的作用范圍x現(xiàn)在的作用范圍voidnum(){extern

int

x,y; /*將全局變量x,y作用域擴(kuò)展到此處*/int

a=15,b=10;x=a-b;y=a+b;}int

x,y; /*

全局變量x,y

*/int

main(){int

a=7,b=5;x=a+b;/*

x=7+5

*/程序的運(yùn)行結(jié)果是:5,25}y=a-b;num();printf(“%d,%d”,x,y);return

0;/*

y=7-5

*/全局變量的作用范圍還可以擴(kuò)展至其它C程序文件,只需要在其它C程序文件中使用extern關(guān)鍵字說明一下即可。int

A;void

printValue();//聲明函數(shù)

int

main(){A=0;printValue();return

0;}extern

int

A;//聲明外部變量AvoidprintValue(){printf(“%d”,

A);A++;}全局變量是與程序共存亡的,因此,全局變量的生存期不是關(guān)心的重點(diǎn),經(jīng)常討論的是其作用域與可見域。全局變量的作用域是整個(gè)程序,不論該程序由幾個(gè)文件組成,理論上,可以在程序的任意位置使用定義的全局變量,但在特定位置處,全局變量是否可見取決于是否對(duì)其進(jìn)行了合理聲明。不進(jìn)行任何聲明時(shí),全局變量的可見域?yàn)閺亩x到本文件結(jié)束,來看一段示例代碼:/*主函數(shù)*/voidmain(void){{extern

intx;/*聲明全局變量x*/printf("x

is%d",x); /*可以訪問*/}printf("x

is

%d",x);/*錯(cuò)誤,找不到x*/}int

x=5;給定b的值,輸入a,求a*b的值。解題思路:分別編寫兩個(gè)文件模塊,其中文件file1包含主函數(shù),另一個(gè)文件file2包含求a*b的函數(shù)。在file1文件中定義外部變量A,在file2中用

extern聲明外部變量A,把A的作用域擴(kuò)展到

file2文件。在auto變量一節(jié)中提到,內(nèi)層代碼塊中聲明的變量將屏蔽外層代碼塊中聲明的同名變量,屏蔽準(zhǔn)則對(duì)全局變量同樣適用extern

int

x=10;double

y=30;void

main(){extern

float

z;z=5;void

print(void);/*全局變量x定義,帶初始表達(dá)式*//*extern可省略,只要定義在外部即可*//*主函數(shù)*//*聲明全局變量z*//*對(duì)全局變量的操作*//*print函數(shù)聲明*/print(); /*print函數(shù)調(diào)用*/printf("x

is%d,y

is%f,z

is

%f\n",x,y,z); /*輸出全局變量*/}float

z;/*全局變量z定義,默認(rèn)初始化為0*/void

print() /*print函數(shù)*/{int

x=1; /*局部變量屏蔽全局變量*/double

y=3;printf("x

is

%d

,y

is

%f

,z

is

%f\n",x,y,z); /*輸出局部變量*/}全局變量帶來的好處是顯而易見的:(1)為函數(shù)間數(shù)據(jù)傳遞提供了新的途徑,函數(shù)返回值僅僅只能有1個(gè),很多情況下,這不能滿足要求,而全局變量可用于更多處理結(jié)果。(2)利用全局變量可以減少形參和實(shí)參的個(gè)數(shù),省去函數(shù)調(diào)用時(shí)的時(shí)空開銷,提高程序運(yùn)行的效率。但是,全局變量在程序執(zhí)行期間都有效,一直占據(jù)著存儲(chǔ)單元,不像局部變量等在調(diào)用執(zhí)行期間臨時(shí)占用內(nèi)存,退出函數(shù)時(shí)便將其釋放。最大的問題是降低了函數(shù)的封裝性和通用性,由于函數(shù)中存在全局變量,因此,如果想把函數(shù)復(fù)用在其他文件中,必須連所涉及的全局變量一塊移植過去,容易引發(fā)各種問題,造成程序不可靠。全局變量使得函數(shù)間獨(dú)立性下降,耦合度上升,可移植性和可靠性變差。為了代碼的可移植,在可以不使用全局變量的情況下應(yīng)盡量避免使用全局變量。static變量的定義格式為:static數(shù)據(jù)類型變量1[=初始化表達(dá)式],變量2[=初始化表達(dá)式]……;與extern變量都是全局變量不同,static變量有靜態(tài)全局變量和靜態(tài)局部變量之分:靜態(tài)局部變量,除了生存期是整個(gè)程序執(zhí)行期間(與程序共存

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

評(píng)論

0/150

提交評(píng)論