《程序設(shè)計(jì)與C語言》課件第10章_第1頁
《程序設(shè)計(jì)與C語言》課件第10章_第2頁
《程序設(shè)計(jì)與C語言》課件第10章_第3頁
《程序設(shè)計(jì)與C語言》課件第10章_第4頁
《程序設(shè)計(jì)與C語言》課件第10章_第5頁
已閱讀5頁,還剩82頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

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

文檔簡介

第10章編譯預(yù)處理與位運(yùn)算10.1宏定義10.2文件包含10.3條件編譯10.4位運(yùn)算習(xí)題1010.1宏定義宏定義就是把一長串字符序列用一個簡短的名字去代替,這個名字稱為宏名,被代替的長串序列就稱為宏值。宏定義的過程是通過#define命令實(shí)現(xiàn)的。其一般格式為: #define〈宏名〉〈宏值〉

〈宏名〉可以出現(xiàn)在程序中,在程序被編譯以前,預(yù)處理程序會先把〈宏名〉原封不動地替換成〈宏值〉(這稱為宏代換),然后再去進(jìn)行編譯。

〈宏名〉可以是一個標(biāo)識符,這稱為簡單宏;也可以帶有參數(shù),這稱為帶參數(shù)的宏。

1.簡單宏簡單宏的定義如下: #define〈標(biāo)識符〉〈字符序列〉例如: #definePI3.14159265則PI為宏名,3.14159265為宏值,如果程序中有語句:

area=PI*r*r;則預(yù)處理程序先把它代換成:

area=3.14159265*r*r;然后再交給編譯器去編譯。簡單宏在定義時應(yīng)注意以下幾個問題:

(1)宏名往往用大寫字母書寫,以突出和其他標(biāo)識符的區(qū)別,但小寫也是可以的。

(2)如果宏值過長,可在換行前加一個續(xù)行符“\”,如:#defineLONG-STRING″thisisalong\

stringthatisusedintheprogram″

(3)當(dāng)宏值為表達(dá)式時,最好用圓括號括起來,以避免引起誤解。如: #defineA3+2程序員的原意是想讓A代表3+2這個整體,但在宏代換中,比如:

e=5/A;就會被替換成:

e=5/3+2;根據(jù)算術(shù)運(yùn)算符的優(yōu)先級,得e=3而非e=1這個預(yù)期的正確結(jié)果。這不是預(yù)處理程序誤解了你的意思,而是你誤解了預(yù)處理程序。因?yàn)楹甏鷵Q是機(jī)械地、沒有智能地去做簡單的替換工作,它不管替換后的語法和語義正確與否,因此保證宏代換正確性的責(zé)任必須由程序員承擔(dān)。如果你熟知了宏代換的操作規(guī)程,那么在宏定義時對宏值加一圓括號,就不會出現(xiàn)這樣的問題了。如定義成 #defineA(3+2)這樣不管怎樣使用A,都不會出現(xiàn)任何問題。

(4)宏定義可以嵌套,即前面已經(jīng)定義過的宏名又可以用到下面的宏值中去,如 #definePI3.14159265

#defineTWOPI(2*PI)則對語句

c=TOWPI*r;預(yù)處理程序會替換成:

c=(2*3.14159265)*r;

(5)若宏名出現(xiàn)在字符串中,則字符串中的宏名不作代換。如: #defineYES1則語句

printf(“YES=%d\n”,YES);被處理成:

printf(“YES=%d\n”,1);即格式控制串中的YES不作代換。

(6)宏名的有效范圍是從定義點(diǎn)起至其所在文件末尾,或遇到取消宏定義的指令#undef為止。對終止點(diǎn)以后的宏名還可以再定義其他宏值。如: #defineA100

#undefA

#defineA10

A代表100的有效范圍

【例10-1】宏名的有效范圍一。#include<stdio.h>

#defineA100main(){inti=2;printf(″i+A=%d\n″,i+A);#undefA#defineA10printf(″i+A=%d\n″,i+A);return0;}運(yùn)行輸出:

i+A=102i+A=12可以看出,A在不同的范圍內(nèi)被代換成不同的宏值。一個函數(shù)中定義的宏名只要不被取消命令取消,就仍然可以被它后面定義的函數(shù)體使用。該函數(shù)體中使用的宏名是作用范圍直到文件尾的宏名,而不是在#undef截止范圍內(nèi)的宏名。

【例10-2】宏名的有效范圍二。#include<stdio.h>

#defineA100mian(){inti=2,j=3;intmax(int,int);printf(″i+A=%d\n″,i+A);printf(″max=%d\n″,max(i,j));#undefA#defineA10printf(″i+A=%d/n″,i+A);printf(″max=%d\n″,max(i,j));return0;}

intmax(inta,intb){returna>b?a+A:b+A;}運(yùn)行輸出:

i+A=102max=13i+A=12max=13可見在max函數(shù)中用到的A是第二次定義的可作用到文件尾的A(A代表10),而第一個A未出main函數(shù)就被取消了。

(7)宏名常用在以下方面:①用一個有意義的名字去代替含義不清的一串?dāng)?shù)字。比如用PI代表圓周率,用PAGESIZE代表每頁打印的行數(shù)等,如#definePI3.14159265358979

#definePAGESIZE66這樣在程序中使用PI和PAGESIZE比用3.14159265358979和66的含義明確多了,既簡單又清楚,既便于修改又可避免出錯。如果圓周率的精度要變動或每頁打印的行數(shù)要改變,只需在宏定義處修改即可,不必遍查程序去修改這兩個數(shù),這樣就避免了如果在某一處忽略而造成大錯的可能。②用一個短的名字去代替較長的名字。如: #defineSTUstructstudent則

STUstud1,stud2;即等價于

structstudentstud1,stud2;這與用typedef定義類型名的作用相似,如

typedefstructstudentSTU;但這兩個其實(shí)是不相同的:宏名是在編譯預(yù)處理階段處理的,定義時不帶分號;而新類型名是在編譯階段處理的,定義時后面有分號。

(8)程序設(shè)計(jì)中常見的錯誤是宏定義時在宏值的后面加分號。如: #definePI3.14159;則對

s=2*PI*r會替換成

s=2*3.14159;*r;這會產(chǎn)生編譯錯誤。

2.帶參數(shù)的宏宏定義的第二種情況是宏名后面可以帶有參數(shù),因而在宏值中也有相同的參數(shù)。帶參數(shù)宏定義的一般格式為: #define〈宏名〉(〈參數(shù)表〉)〈含有參數(shù)的字符序列〉如 #defineS(x)5*x其中,S為宏名,x為參數(shù),5*x是帶參數(shù)的字符序列。對帶有參數(shù)的宏的處理方法是:先用替換字符去替換參數(shù),然后再把宏展開。比如S(5)會被處理成5*5(但不是25),即先用5去替換參數(shù)x,再把S(5)展開成5*5。定義和使用帶參數(shù)的宏時應(yīng)注意的問題有:

(1)為了不致引起誤解,宏值中間的參數(shù)要用圓括號括起來。例如,定義一個求圓面積的帶參數(shù)的宏:#definePI3.1416

#defineCIRCLE-AREA(x)(PI*(x)*(x))如果有語句:

area=CIRCLE-AREA(4)則會被預(yù)處理程序展開成:

area=(3.1416*(4)*(4));因?yàn)楸磉_(dá)式只由常量組成,所以在編譯時就能計(jì)算出該表達(dá)式的值并賦給area。用圓括號把參數(shù)括起來是為了在參數(shù)是表達(dá)式時迫使編譯器以正確的順序計(jì)算表達(dá)式的值。例如,

area=CIRCLE-AREA(3+c);則被展開成:

area=(3.1416*(3+c)*(3+c));其中圓括號使表達(dá)式以正確的順序進(jìn)行計(jì)算。如果去掉圓括號,宏會被展開成

area=3.1416*3+c*3+c;按算術(shù)運(yùn)算符的優(yōu)先級,右邊的表達(dá)式將不正確地按下式計(jì)算:

area=(3.1416*3)+(c*3)+c;

(2)宏名和后面的圓括號之間不能有空格,如有 #defineCUBE(a)a*a*a則預(yù)處理程序會把CUBE作為無參的宏名,而把“(a)a*a*a”作為宏值。

(3)參數(shù)表中的參數(shù)和字符序列中的參數(shù)必須一一對應(yīng),即參數(shù)表中的參數(shù)必須全部在字符序列中出現(xiàn);反過來,字符序列中的參數(shù)也都必須出現(xiàn)在參數(shù)表中。如 #defines(a,b)3*a+6

#definies(a)a+b都是錯誤的。第一種情況是參數(shù)表中的參數(shù)b在后面未用到,因此它在參數(shù)表中的出現(xiàn)毫無意義。第二種情況是字符序列中的b未在參數(shù)表中出現(xiàn),運(yùn)行時會出現(xiàn)錯誤。

(4)有副作用的表達(dá)式(如修改變量的值)不應(yīng)該傳遞給宏,因?yàn)楹甑膮?shù)可能會被計(jì)算多次而產(chǎn)生意想不到的結(jié)果。例如, #defineSQUARE(a)a*a如i=3,以++i代替參數(shù)a,則被展開成++i*++i,本來想求4的平方(16),結(jié)果卻得到20。

(5)當(dāng)一個宏名要在另一個宏定義的宏值中出現(xiàn)時,最好將它的宏值部分括起來,以避免發(fā)生意外。

【例10-3】用求兩個數(shù)的最小值的宏來定義求三個數(shù)的最小值的宏。#definemin2(a,b)((a)<(b)?(a):(b))#definemin3(a,b)min2(a,b)<(c)?min2(a,b):(c)main(){inti,j,k,n;printf(″Input3integers:\n″);scanf(″%d%d%d″,&i,&j,&k);n=min3(i,j,k);printf(″Theminimumof(%d,%d,%d)is%d\n″,i,j,k,n);return0;}運(yùn)行輸出:Input3integers:564Theminimumof(5,6,4)is4宏定義min3(a,b,c)的宏值中用到了min2(a,b)。在對min3(a,b,c)求值時,并不是先計(jì)算出min2(a,b)的值,而是將min2(a,b)的宏值原封不動地進(jìn)行替換,這樣如果min2(a,b)的宏值中沒有最外邊的括號,則在求min3(a,b,c)的值時就會被替換成:

(a)<(b)?(a):(b)<(c)?(a)<(b)?(a):(b):(c)那么當(dāng)輸入為5,6,4時,輸出的最小值就會是5而不是4。這就是程序設(shè)計(jì)中的漏洞。當(dāng)將min2(a,b)的宏值整個地用括號括起來后,就不會出現(xiàn)這樣的問題了。

(6)用帶參的宏可以代替某些簡單的帶參函數(shù)。例如,可以用 #defineCIRCLE-AREA(x)(PI*(x)*(x))去代替函數(shù):

floatcirclearea(floatx) {return3.14159*x*x;}既然兩者都能完成同樣的工作,那么帶參宏和帶參函數(shù)之間有何區(qū)別?它們各有哪些優(yōu)缺點(diǎn)呢?下面具體說明。①函數(shù)調(diào)用時先對參數(shù)表達(dá)式進(jìn)行計(jì)算,把計(jì)算的結(jié)果作為一個值去代替形參,而宏代換時不對參數(shù)求值,如

area=circlearea(5+8);會被處理成

area=circlearea(13);而

area=CIRCLE-AREA(5+8);則被處理成

area=PI*(5+8)*(5+8);②函數(shù)有形參和實(shí)參的類型匹配之說,而宏的參數(shù)沒有類型。③函數(shù)調(diào)用是在運(yùn)行時處理的,要占用運(yùn)行時間,而宏是在編譯預(yù)處理階段處理的,不占用運(yùn)行時間。④函數(shù)調(diào)用需要一些額外開銷,而宏直接把代碼插入到程序中,不會增加額外的開銷,并保持了程序的可讀性,因此對一些小的、調(diào)用次數(shù)頻繁的函數(shù),最好用帶參的宏去代替。10.2文件包含如果在一個文件中用到了另一個文件中的某些內(nèi)容,則不必把該文件全部重復(fù)輸入到自己的文件中,只要用一個文件包含指令就可以了。預(yù)處理指令#include可以完成這一任務(wù)。#include指令的功能是用指定文件的一份拷貝來取代這條預(yù)處理指令。#include指令有如下兩種格式:#include<文件名>

#include″文件名″這兩種格式的差別在于預(yù)處理程序查找被包含文件的路徑不同。如果用雙引號括起文件名,則預(yù)處理程序就在當(dāng)前正編譯的程序所在的目錄中查找被包含文件,該方法通常用來包含程序員定義的頭文件;而對于用尖括號括起來的文件名(多用來查找標(biāo)準(zhǔn)庫頭文件),預(yù)處理程序就用與實(shí)現(xiàn)無關(guān)的方式查找被包含文件,通常是在預(yù)指定的目錄中查找。 #include指令應(yīng)出現(xiàn)在文件的開頭。文件包含的語義如圖10-1所示。即file1.c把file2.c的拷貝包含到自己的內(nèi)部,成為一個大文件,然后再一塊編譯,所以被包含文件file2.c應(yīng)當(dāng)是源文件,不應(yīng)當(dāng)是目標(biāo)文件(.obj)。頭文件不能單獨(dú)編譯,它只能和C源程序文件一起進(jìn)行編譯。圖10-1文件包含的語義使用文件包含指令時應(yīng)注意以下幾個問題:

(1)一個#include指令只能包含一個文件,要包含多個文件就要用多個#include指令。

(2)文件包含可以嵌套,比如文件file1中含有指令: #include″f2.c″而在文件f2.c中又有: #include<math.h>

#include″f3.c″則file1也把f2.c中包含的文件全部包含進(jìn)來。

(3)被包含文件中的全局變量也是包含文件中的全局變量,因此在包含文件中對這些量不必再加extern說明即可加以引用。

(4)被包含文件的擴(kuò)展名一般為.h(head),表示是在文件開頭加進(jìn)來的,其內(nèi)容可以是程序文件或數(shù)據(jù)文件,也可以是宏定義、全局變量聲明等。這些數(shù)據(jù)有相對的獨(dú)立性,可被多個文件使用,不必在多個文件中都去定義,而只需在一個文件中定義,其他文件中包含這個定義文件即可。

【例10-4】利用隨機(jī)數(shù)求圓周率π。編程思路:設(shè)有一個邊長為1的正方形ABCD,則其面積為S=1×1=1。以A為圓心,以1為半徑畫弧,得一扇形ABD,也就是四分之一圓,如圖10-2所示。扇形的面積S1=π×1×1/4=π/4。正方形面積S和該扇形面積S1相比:S/S1=4/π?,F(xiàn)在在正方形中均勻地撒上M粒沙子,其中落在扇形區(qū)域內(nèi)的有N粒,則沙粒數(shù)量之比應(yīng)該和面積之比相當(dāng):M∶N≈S∶S1=4∶π可推出:π≈4×N/M利用隨機(jī)數(shù)函數(shù)rand()產(chǎn)生沙粒的坐標(biāo)(x,y),則0≤x,y<1。若,則沙粒落在扇形內(nèi)。在產(chǎn)生了M個沙粒之后,就可以求出π的近似值。圖10-2程序如下:#include<stdio.h>

#include<math.h>

#include<stdlib.h>

#defineMOD32768main(){intm,n=0,i;doublex,y,pi;printf(″Input:M=?\n″);scanf(″%d″,&m);for(i=0;i<m;i++)

{x=(double)rand()/MOD;y=(double)rand()/MOD;if(sqrt(x*x+y*y)<=1.0)n++;}pi=(double)4*n/m;printf(″n=%d,PI=%f/n″,n,pi);return0;}運(yùn)行輸出:

Input:M=?10000↙

n=7897,PI=3.158800再運(yùn)行:

Input:M=?20000↙n=15776,PI=3.155200再運(yùn)行:

Input:M=?30000↙n=23637,PI=3.151600由運(yùn)行結(jié)果可見,隨著測試數(shù)目的增加,落在1/4圓內(nèi)的比例越來越向某個定值方向趨近,所求π的近似值越來越精確。程序中因用到標(biāo)準(zhǔn)輸入/輸出函數(shù),所以應(yīng)把頭文件<stdio.h>包含進(jìn)來;用到了數(shù)學(xué)函數(shù)sqrt,所以應(yīng)把頭文件<math.h>包含進(jìn)來;而隨機(jī)函數(shù)rand的原型說明在<stdlib.h>,所以也把這個頭文件包含進(jìn)來。10.3條件編譯一個程序中的所有語句并不一定要全部編譯執(zhí)行,根據(jù)一定的條件可以對其中的一部分進(jìn)行編譯。能夠控制編譯范圍的指令就是C語言提供的條件編譯指令。條件編譯指令的結(jié)構(gòu)與if選擇結(jié)構(gòu)非常類似,例如,#if!define(NULL)

#defineNULL0

#endif這段描述的意思是:檢查NULL是否被定義過,如沒有被定義過,則表達(dá)式define(NULL)的結(jié)果為0,而!define(NULL)就是1,條件為真,從而執(zhí)行下面的宏定義,把NULL定義為0;如果已作過NULL的定義,則條件為假,就不執(zhí)行#define指令。每一個#if結(jié)構(gòu)都是以#endif結(jié)束的??梢园眩fdefine()縮寫為#ifdef,把#if!define()縮寫為#ifndef。條件編譯的形式主要有三種。

1.#if…#endif形式這種形式為:#if〈常整數(shù)表達(dá)式〉〈程序段〉

#endif其語義是:如〈常整數(shù)表達(dá)式〉的值為真,則編譯〈程序段〉。在有多個部分的條件編譯結(jié)構(gòu)中可以用#elif和#else指令作為中間嵌套判斷,從而形成如下結(jié)構(gòu):#if〈常整數(shù)表達(dá)式1〉 〈S1〉

#elif〈常整數(shù)表達(dá)式2〉 〈S2〉

#elif〈常整數(shù)表達(dá)式3〉 〈S3〉

#else 〈Sn〉

#endif預(yù)處理程序在處理上述條件編譯命令時,先對#if之后的表達(dá)式求值,結(jié)果是一個邏輯值,然后再根據(jù)結(jié)果是否為0來決定對某段程序是否進(jìn)行編譯。elif即elseif的縮寫。在上述各常整數(shù)表達(dá)式中不可包含強(qiáng)制轉(zhuǎn)換運(yùn)算符,因?yàn)轭A(yù)處理程序不理解這些屬于核心語言的構(gòu)件。常整數(shù)表達(dá)式中只能出現(xiàn)預(yù)定義的常量標(biāo)識符,或整常數(shù),或字符常數(shù)等。

2.#ifdef…#endif形式這種形式為:#ifdef〈標(biāo)識符〉 〈程序段〉

#endif其語義是:如果〈標(biāo)識符〉用#define命令定義過,則條件為真,接著編譯〈程序段〉,如〈標(biāo)識符〉未定義過,則忽略下面的〈程序段〉。

3.#ifndef…#endif形式這種形式為:#ifndef〈標(biāo)識符〉〈程序段〉

#endif其語義是:如果〈標(biāo)識符〉未被定義過,則條件為真,接下來編譯〈程序段〉,否則忽略。如前面的例子可以寫為:#ifndefNULL

#defineNULL0

#endif條件編譯主要有以下用途:

(1)忽略程序的某一部分。在程序開發(fā)過程中,程序員經(jīng)常發(fā)現(xiàn)需要把一大段代碼變?yōu)樽⑨?,以防止編譯器對這段代碼進(jìn)行編譯。如果這段代碼中已含有注釋,就不能再用注釋符號“/*”和“*/”來忽略這段代碼了。這時可以使用如下預(yù)處理結(jié)構(gòu):#if0〈不編譯的代碼段〉

#endif因?yàn)?代表假,所以就不編譯這段代碼,如果想讓編譯器編譯這段代碼,把0改為1就可以了。

(2)幫助程序調(diào)試。調(diào)試主要是隨時跟蹤了解變量的取值情況,看是否有異?,F(xiàn)象發(fā)生。雖然不少系統(tǒng)提供了調(diào)試程序(debugger),但通常難以理解和使用,所以常通過使用printf語句打印變量的值來測試控制流的正確性。這些printf語句可以放在條件編譯指令間,在調(diào)試的時候編譯這些語句。例如:#ifdefDEBUGprintf(″Variablex=%d\n″,x);

#endif若前面已有符號常量DEBUG的定義(即有指令#defineDEBUG),則編譯執(zhí)行printf語句,顯示變量x當(dāng)前的值。在調(diào)試完成后就不需再顯示x的值了,此時只需去掉前面的#defineDEBUG指令就可以了,這樣在編譯過程中就會忽略為調(diào)試而加入的printf語句,不必在程序中一一地把這些調(diào)試語句去掉。在大型程序中可能要定義多個符號常量,用以控制對源文件中某些部分的條件編譯。10.4位運(yùn)算位運(yùn)算是體現(xiàn)C語言具有低級語言功能的最重要的特點(diǎn)之一。計(jì)算機(jī)在用于檢測和控制領(lǐng)域時都要用到位運(yùn)算的知識,而且位運(yùn)算也是計(jì)算機(jī)的基本操作。我們知道,計(jì)算機(jī)中的存儲和運(yùn)算都是以二進(jìn)制方式進(jìn)行的,而位運(yùn)算正是提供了對二進(jìn)制位進(jìn)行操作的手段,因此合理地使用位運(yùn)算可使程序的執(zhí)行效率得到提高。10.4.1位運(yùn)算符

C語言提供了6個位運(yùn)算符:&、|、~、∧、、。它們的功能分別是:按位與、按位或、取反、按位異或、左移和右移。前4個運(yùn)算符具有邏輯運(yùn)算的特點(diǎn),故也稱為字位邏輯運(yùn)算符;后兩個具有移位的功能,故稱為字位移位運(yùn)算符。位運(yùn)算符僅作用于非浮點(diǎn)類型。

1.字位邏輯運(yùn)算符二進(jìn)制的位只有兩個值:0和1,字位邏輯運(yùn)算符的操作對象也就是0或1,運(yùn)算的結(jié)果也是0或1。另外,字位邏輯運(yùn)算還有一個特點(diǎn):它只對相關(guān)的兩位二進(jìn)制位進(jìn)行運(yùn)算,不產(chǎn)生進(jìn)位,這是邏輯運(yùn)算和算術(shù)運(yùn)算的一個重要區(qū)別,因?yàn)樗阈g(shù)運(yùn)算有進(jìn)位問題。

(1)按位與運(yùn)算符&是雙目運(yùn)算符,其兩個運(yùn)算對象均是二進(jìn)制位,結(jié)果也是二進(jìn)制位。其功能為:

0&0=0,0&1=0,1&0=0,1&1=1即只有在兩個運(yùn)算對象全為1時,結(jié)果才為1,其余情況下結(jié)果均為0。

(2)按位或運(yùn)算符|也是一個雙目運(yùn)算符,其功能為:

0|0=0,0|1=1,1|0=1,1|1=1即兩個運(yùn)算對象中只要有一個為1,結(jié)果就是1。

(3)按位取反運(yùn)算符~是單目運(yùn)算符,其功能為:

~0=1,~1=0即對原來的位值進(jìn)行翻轉(zhuǎn)。

(4)按位異或運(yùn)算符∧是雙目運(yùn)算符,其功能是:

0∧0=0,0∧1=1,1∧0=1,1∧1=0即當(dāng)兩個運(yùn)算對象的值相反時結(jié)果為1,相同時結(jié)果為0。它用來檢測兩個運(yùn)算對象是否不相同,不相同時為真,相同時為假。

【例10-5】設(shè)a=(9)10=(1001)2,b=(10)10=(1010)2,求a&b、a|b及a∧b。

a&b: a|b: a∧b: 1001 1001 1001 &1010 |1010 ∧1010 1000 1011 0011結(jié)果為:a&b=1000,a|b=1011,a∧b=0011。

注意:①位運(yùn)算符和邏輯運(yùn)算符的區(qū)別。&和&&,|和||,雖然它們的真值表是相同的,但&和|是對兩個數(shù)按二進(jìn)制位進(jìn)行操作,其結(jié)果是一個數(shù),而邏輯運(yùn)算符&&和||是把兩個數(shù)作為整體進(jìn)行操作的,其結(jié)果是邏輯值。例如:

9&10=(1000)2=(8)10而9&&10=1(真) 9|10=(1011)2=(11)10而9‖10=1(真)②任何一個數(shù)和它自身的異或永遠(yuǎn)是0,而任何一個數(shù)和0異或的結(jié)果是其自身。例如,若a=1010,則:

1010 1010 ∧1010 ∧0000 0000 1010即a∧a=0,a∧0=a。按位異或運(yùn)算有個有趣的特性:對一個數(shù)據(jù)用另一個數(shù)據(jù)進(jìn)行兩次異或操作后,將恢復(fù)這個對象原來的值。即∧(∧(a,b),b)=a其中,∧(a,b)=a∧b,因此上式也就是(a∧b)∧ba∧(b∧b)=a∧0=a結(jié)合律例如,設(shè)a=1001,b=1010,則

1001 (a) 1001 (c) ∧1010 (b) ∧1010 (b) 0011 (c) 1001 (a)前一操作稱為加密過程,將a用b進(jìn)行異或運(yùn)算,結(jié)果為c;后一操作稱為解密過程,即將加密后的值c再用b進(jìn)行異或運(yùn)算,結(jié)果又恢復(fù)為a。利用這個特性,可以在不要第三變量的情況下將兩個變量進(jìn)行交換,這時只需進(jìn)行三次異或操作即可:

a=a0∧b0;(1)b=a∧b0;(2)a=a∧b;(3)從形式上看,三個賦值表達(dá)式右邊的表達(dá)式形式完全一樣(下標(biāo)0僅表示其初始值),但是隨著賦值的進(jìn)行,它們的內(nèi)容卻在發(fā)生著變化,前面賦值運(yùn)算的結(jié)果用于后面的表達(dá)式中??勺魅缦峦茖?dǎo):由(2)式推導(dǎo):

b=a∧b0(a0∧b0)∧b0=a0∧(b0∧b0)=a0∧0=a0(4)由(3)式推導(dǎo):a=a∧b(a0∧b0)∧b(a0∧b0)∧a0(a0∧a0)∧b0=0∧b0=b0即b的終值為a的初值a0,a的終值為b的初值b0。a0和a,b0和b都用同一內(nèi)存單元,只是表示不同時間內(nèi)的不同值而已。(1)式(1)式(4)式結(jié)合按位邏輯運(yùn)算主要用在以下幾個方面:①按位與運(yùn)算&用于選取一個數(shù)的某些二進(jìn)制位。這里又有兩種情況:一是只取一個數(shù)的某幾位,其他位都不要;二是只保留一個數(shù)中某幾位的原始值,其他位全是0。要達(dá)到這樣的目的,關(guān)鍵是設(shè)計(jì)一個“掩碼”。對于第一種情況,掩碼取所需要的位數(shù),且全部置1;對于第二種情況,掩碼和原來的數(shù)一樣長,只在需要保留的位上置1,其余全置0。掩碼確定之后,再與原數(shù)進(jìn)行適當(dāng)?shù)陌次慌c運(yùn)算,即可得到所要的結(jié)果。在一個16位的系統(tǒng)中(即一個字由兩個字節(jié)組成,共有16位二進(jìn)制位),前8位和后8位全為1時表示的數(shù)的情況為:即:(11111111)2=(377)8 (1111111100000000)2=(177400)8因此要取一個數(shù)n的低8位,可取(377)8為掩碼:(377)8 n=n&(0377)/*八進(jìn)制數(shù)以0開頭*/同理,要取其高8位,可用(177400)8為掩碼:

n=n&(0177400)1111111111111111(377)8(177400)8

【例10-6】判斷數(shù)的奇偶性。

編程思路:一般來說,要判斷一個數(shù)的奇偶,只需用該數(shù)對2進(jìn)行求模(%)運(yùn)算,若結(jié)果為1則為奇數(shù),若結(jié)果為0則為偶數(shù),但如果用位運(yùn)算則速度會更快??勺屧摂?shù)與數(shù)1進(jìn)行與運(yùn)算&。奇數(shù)的最低位為1,和1與的結(jié)果為1,代表奇數(shù);偶數(shù)最低位為0,和1與的結(jié)果為0,代表偶數(shù)。由此可設(shè)計(jì)如下程序:

#include<stdio.h>main(){intn;printf(″Inputaninteger:\n″);scanf(″%d″,&n);if(n&1)printf(″%disodd!\n″,n);elseprintf(″%diseven!\n″,n);return0;}運(yùn)行輸出:

Inputaninteger: 9↙

9isaodd!②按位或運(yùn)算|用于將一個數(shù)指定的位置成1,其他位保持不變。例如,將3244的第4至第7位置成1,先找出所需要的掩碼(360)8(找的方法是在16位表示的整數(shù)中,令第4至第7位全為1,其余位全為0即為所求),再與3244作按位或運(yùn)算:

n=n|03600000110010101100 (3244)|0000000011110000 (0360) 0000110011111100 (3324)

【例10-7】利用位運(yùn)算實(shí)現(xiàn)大、小寫字母的轉(zhuǎn)換。編程思路:大、小寫字母的ASCII碼的差值為32=25=(100000)2,即′A′+32=′a′,′A′和′a′的編碼分別是:

′A′:01000001′a′:01100001它們的差別在右起第6位,在大寫字母的該位上置1即變?yōu)閷?yīng)的小寫字母,在小寫字母的該位上置0則變?yōu)橄鄳?yīng)的大寫字母。對于大寫字母,可設(shè)計(jì)掩碼為(00100000)2=(32)10,然后將其與大寫字母的ASCII碼進(jìn)行按位或運(yùn)算,即可得到對應(yīng)的小寫字母;對于小寫字母,可設(shè)計(jì)掩碼為(11011111)2=(df)16=0xdf,然后將其與小寫字母的ASCII碼進(jìn)行按位與運(yùn)算,即可得到對應(yīng)的大寫字母。

【例10-8】大、小寫字母轉(zhuǎn)換。#include<stdio.h>main{inticharc[80];printf(″Inputastring\n″);scanf(″%s″,c);for(i=0;i<80;i++){if(′A′<=c[i]&&c[i]<=′Z′)printf(″%c″,c[i]|32);elseif(′a′<=c[i]&c[i]<=′z′)printf(″%c″,c[i]&0xdf);if(c[i]==′\0′)break;}return0;}運(yùn)行輸出:

InputastringSTRINGstring↙

stringSTRING③按位異或運(yùn)算∧可用于將一個數(shù)的指定位翻轉(zhuǎn),其他位不變。方法同樣是先找出該數(shù)所需要的掩碼,再將原數(shù)與掩碼進(jìn)行異或運(yùn)算。掩碼的設(shè)計(jì)方法是:在和需要翻轉(zhuǎn)位相應(yīng)的位置1,其余位置0。例如,對3244的低字節(jié)的高4位翻轉(zhuǎn)。掩碼為:0000000011110000(360)8,則n=n∧0360為:

0000110010101100 3244∧0000000011110000 (360)8

0000110001011100 3164④取反運(yùn)算常用于掩碼的設(shè)計(jì)。如欲設(shè)計(jì)一掩碼,其最低位為0,其余位皆為1。對16位系統(tǒng)而言,其形式為:

1111111111111110對32位系統(tǒng)而言,其形式為:

11111111111111111111111111111110把它們用八進(jìn)制表示,分別是0177776和037777777776。這種由二進(jìn)制到八進(jìn)制的轉(zhuǎn)換過程,也需要一定的計(jì)算量,但如果用~1表示則相當(dāng)簡單。所以不管對16位還是32位系統(tǒng),要對數(shù)值n的末位置0,都可以寫成下面的形式:

n=n&~1注意:~1≠-1。如對16位系統(tǒng)而言:

~1=1111111111111110 -1=1111111111111111(補(bǔ)碼表示)

~運(yùn)算符的優(yōu)先級高于算術(shù)運(yùn)算符、邏輯運(yùn)算符和其他的位運(yùn)算符,如~b&a等價于(~b)&a。

2.字位移位運(yùn)算符<<和>>這兩個運(yùn)算符的使用格式是:

〈數(shù)值〉<<〈左移位數(shù)〉〈數(shù)值〉>>〈右移位數(shù)〉例如:

a=a<<3;表示將a的二進(jìn)制表示向左移3位。結(jié)果把a(bǔ)最左邊的3位擠掉,右邊補(bǔ)3個0。若a=16,其二進(jìn)制為00010000,則a<<3之后變?yōu)?0000000,即十進(jìn)制的128,而128=16×23。由此可知,左移運(yùn)算時,若移掉的位中無1,則左移n位后的值是原值的2n倍。運(yùn)算 a>>3;是把a(bǔ)向右移3位,則移掉了a最右邊的3位,左邊補(bǔ)3個0,為00000010,即十進(jìn)制的2,它等于16除以23。由此可知,若右移n位,則移動后的值是原值的1/2n。上述規(guī)則適用于正數(shù)及無符號數(shù)。若移動的數(shù)為負(fù)數(shù),則在向左移時,符號位上的數(shù)字會發(fā)生變化,可能為1,也可能為0,因此結(jié)果是不確定的;而右移時,對符號位有不同的處理辦法:若該位置成0則稱邏輯右移,若該位置成1則稱算術(shù)右移。例如:a:1001011111101101a>>1:0100101111110110 (邏輯右移)a>>1:1100101111110110 (算術(shù)右移)采用算術(shù)右移能保證數(shù)值的正負(fù)性不變。TurboC采用算術(shù)右移。10.4.2與位運(yùn)算有關(guān)的復(fù)合賦值運(yùn)算符位運(yùn)算符和賦值運(yùn)算符結(jié)合起來可構(gòu)成如下的復(fù)合賦值運(yùn)算符:

&=、|=、>>=、<<=、∧=復(fù)合賦值運(yùn)算符的左邊必須是變量,右邊是表達(dá)式。例如:

a<<=2;等價于

a=a<<2;又如:

b|=c;等價于

b=b|c;對兩個長度不等的數(shù)進(jìn)行位運(yùn)算時,右邊對齊,短的數(shù)的左邊或添加0(對正數(shù)或無符號數(shù)),或添加1(對負(fù)數(shù)),添加到兩個數(shù)等長后再進(jìn)行位運(yùn)算。

【例10-9】取出一個整數(shù)從右邊第m位開始的右n位。

編程思路:從右邊第m位開始的右n位距最右端的距離是m-n,所以向右移m-n位后再與適當(dāng)?shù)难诖a進(jìn)行與運(yùn)算,即可得到所需的位。這里的掩碼應(yīng)該是右邊n位為1,其余位皆為0。實(shí)現(xiàn)的方法是把~0左移n位后再取反,即~(~0<<n)。因?yàn)椋簙0=1111111111111111~0<<n=1111111111110000(設(shè)n=4)~(~0<<n)=0000000000001111于是可編程如下:#include<stdio.h>main(){unsignedinta,m,n;printf(″Inputanunsignednumber\n″);scanf(″%u″,&a);printf(″m=?n=?\n″);scanf(″%d%d″,&m,&n);a=a>>(m-n)&~(~0<<n);printf(″result=%u\n″,a);return0;}運(yùn)行輸出:

Inputanunsignednumber57↙

m=?n=?63↙result=7

57的二進(jìn)制表示為00111001,取從右端開始的第6位起的右3位,即求中間的111。

【例10-10】用位運(yùn)算把十進(jìn)制整數(shù)轉(zhuǎn)換成二進(jìn)制和八進(jìn)制數(shù)。編程思路:首先把十進(jìn)制數(shù)轉(zhuǎn)換成二進(jìn)制數(shù),再在此基礎(chǔ)上把二進(jìn)制數(shù)轉(zhuǎn)換成八進(jìn)制數(shù)。如何把十進(jìn)制數(shù)轉(zhuǎn)換成二進(jìn)制數(shù)呢?可以從最高位開始,不斷求出二進(jìn)制數(shù)的各位。欲求最高位,應(yīng)找出一個合適的掩碼使得只能取最高位,這個掩碼可用1<<15來得到。在數(shù)值與該掩碼進(jìn)行一次按位與運(yùn)算之后,即可求出最高位;然后把數(shù)值左移一位,使次高位處于最高位的位置,再與掩碼進(jìn)行與運(yùn)算。如此循環(huán),直到數(shù)的各位均被取出為止。如何轉(zhuǎn)換成八進(jìn)制數(shù)?可對得到的二進(jìn)制數(shù)從右向左三位一組地進(jìn)行分節(jié),每節(jié)可轉(zhuǎn)換成一個八進(jìn)制數(shù)碼。因一個整數(shù)用16位二進(jìn)制表示,所以可分為6節(jié),最左一節(jié)1位,其他各節(jié)均3位??梢钥紤]設(shè)計(jì)一個只取數(shù)中三位的掩碼,讓它和被轉(zhuǎn)換數(shù)值的最右端的3位進(jìn)行與運(yùn)算,這就等于取出數(shù)值的低3位,轉(zhuǎn)換成八進(jìn)制數(shù)碼;然后把數(shù)值右移3位,使下一節(jié)再與掩碼進(jìn)行與運(yùn)算。每進(jìn)行一次可得到一個八進(jìn)制數(shù)碼。掩碼如何設(shè)計(jì)?因?yàn)橐?位,故可令掩碼mask=(111)2=7。用一個有6個元素的數(shù)組把在轉(zhuǎn)換過程中得到的八進(jìn)制數(shù)碼收集起來,由轉(zhuǎn)換過程可知該數(shù)組中各元素的值是按八進(jìn)制由低到高的順序排列的,因此最先生成的是最低位。要想得到正確的八進(jìn)制數(shù),還必須把這個數(shù)組顛倒一下。另外應(yīng)考慮,倒置以后的數(shù)組很可能由于數(shù)值不大致使高位為0,因此應(yīng)去掉位于高端的無用0,直接輸出有意義的八進(jìn)制數(shù)碼。程序如下:#include<stdio.h>voidconvtenTo2(unsigned);voidrev(unsigned[],int);main(){unsignedi,j,m,mm,a[6],mask=7;printf(″Inputanunsignedintegernumber:\n″);scanf(″%u″,&m);mm=m; /*保留輸入值*/convtenTo2(m);for(j=0;i<=5;j++){a[j]=m&mask;m>>=3;}printf(″\n″);rev(a,6);printf(″The%u′soctalnumberis:″,mm);for(i=0;i<=5;i++)printf(″%o″,a[i]); /*輸出6位八進(jìn)制數(shù)*/printf(″Theequalis:\n(%u)10=″,mm);j=0;while(a[j]==0) /*去掉前導(dǎo)0*/j++;printf(″(″);for(i=j;i<=5;i++)printf(″%o″,a[i]);printf(″)8\n″);return0;}voidrev(unsignedb[],intn){inti,j=n/2,t;for(i=0;i<j;i++,n--){t=b[i];/*數(shù)組兩端元素對調(diào)*/b[i]=b[n-1];

b[n-1]=t;

}}voidconvtenTo2(unsigneda){unsignedmask,i;mask=1<<15;printf(″\n%u′sbinarynumberis:\n(%u)10=″,a,a);printf(″(″);for(i=1;i<=16;i++){printf(″%c″,a&mask?′1′:′0′);a<<=1;if(i%8==0)putchar(′′);}printf(″)2″);putchar(′\n′);

}

溫馨提示

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

評論

0/150

提交評論