C語(yǔ)言08- 位運(yùn)算己儒,宏定義崎岂,遞歸

16:位運(yùn)算

16.1:位運(yùn)算概述

二進(jìn)制與位運(yùn)算

程序中的數(shù)據(jù)在內(nèi)存中,都是以二進(jìn)制的形式存在的闪湾。內(nèi)存中的數(shù)據(jù)都是0和1組成的序列冲甘。

位運(yùn)算:是直接對(duì)整數(shù)在內(nèi)存中的二進(jìn)制位進(jìn)行操作;直接對(duì)二進(jìn)制位進(jìn)行操作途样,使得位運(yùn)算比普通的運(yùn)算操作效率要高

學(xué)習(xí)位運(yùn)算江醇,首先要學(xué)習(xí)什么是二進(jìn)制位?
1. byte字節(jié)
2. bit位

位運(yùn)算操作符:
1. 與(and):&
2. 或(or):|
3. 取反(not):~
4. 異或(xor):^
5. 左移(shl):<<
6. 右移(shr/sar):>>

16.2:與(and):&

與運(yùn)算:只有當(dāng)2個(gè)數(shù)對(duì)應(yīng)的位都為1何暇,該位運(yùn)算結(jié)果為1嫁审,否則運(yùn)算結(jié)果為0。

int main(void) {
    int a = 15;//1111
    int b = 10;//1010
    int c = 15&10;//1010
    printf(“a&b=%d\n”, c);//a&b=10
    
    return 0;
}

&應(yīng)用:

//1. 獲取一個(gè)整數(shù)的后n位:(如現(xiàn)實(shí)中子網(wǎng)掩碼)
獲取一個(gè)整數(shù)的后3位:
int a=100;
a&7(7的二進(jìn)制碼為0111)

2. 使用與運(yùn)算與取反和移位運(yùn)算相結(jié)合赖晶,將整數(shù)的某位置零:
a&(~(1<<n))//即可將x第n位置為零
#define CLEARFLAG(a, n) ((a) &= ~(1<<(n)))//把整數(shù)a中第n位置為0

3. 判斷a中第n位是否為1:
#define FLAGON(a,n) ((a)&(1<<(n)))//判斷整數(shù)a中第n位是否為1

4. 請(qǐng)出整數(shù)a二進(jìn)制中最右邊的1:
a&(a-1)
//計(jì)算整數(shù)有幾個(gè)二進(jìn)制1
int count_ones(int x) {
    int count = 0;
    
    while(x) {
        count++;
        x &= (x-1);
    }
    
    return count
}

16.3:或(or):|

|:只有當(dāng)2個(gè)數(shù)對(duì)應(yīng)的位都為0,該位運(yùn)算結(jié)果為0辐烂,否則運(yùn)算結(jié)果為1遏插。

int main(void) {
    int a = 15;//1111
    int b = 10;//1010
    int c = 15|10;//1111
    printf(“a|b=%d\n”, c);//a|b=15
    
    return 0;
}

|應(yīng)用:
//把整數(shù)a中的第n位置為1
#define SETFLAG(a,n)  ((a) |= (1<<(n)))

16.4:取反(not):~

~:?jiǎn)文窟\(yùn)算符;是將一個(gè)整數(shù)中位為1的變成0纠修,位為0的變成1胳嘲。

int main(void) {
    int a = 10;//1010
    int b = ~10;//0101
    printf(“~10=%d\n”, b);//5
    
    return 0;
}

~應(yīng)用:

1.
//取反運(yùn)算同與運(yùn)算結(jié)合,可以將一個(gè)整數(shù)的某位置0扣草,比如:
#define CLEARFLAG(a,n) ((a) &= ~(1<<(n)))//把整數(shù)a中第n位置為0

16.5:異或(xor):^

^:相同為0了牛,不同為1颜屠。

int main(void) {
    int a = 15;//1111
    int b = 10;//1010
    int c = 15^10;//0101
    printf(“a^b=%d\n”, c);//a^b=5
    
    return 0;
}

//異或運(yùn)算:
1. 置零
a^0=a
a^a=0//xor eax,eax

2. 兩數(shù)交換
#define SWAP(a,b) \
do{\
a=a^b;\
b=a^b;\
a=a^b;\
}while(0)
/*
證明:
假設(shè):a和b的初始值:a=a0,b=b0;
第一句:a=a^b即a為a0^b0
第二句:b=a^b即(a0^b0)^b0=》a0^(b0^b0)=》a0^0=》a0
第三句:a=a^b即a0^b0^a0=》a0^a0^b0=》0^b0=》b0
因此,通過(guò)上面的推導(dǎo)鹰祸,實(shí)現(xiàn)了a與b值的交換甫窟。
*/

3. 單指針實(shí)現(xiàn)雙鏈表
/*
可以用單個(gè)指針域來(lái)表示雙向鏈表:
有任意3個(gè)相鄰結(jié)點(diǎn):PL, P蛙婴, PR
那么P->next = PL^PR
對(duì)于頭結(jié)點(diǎn)來(lái)說(shuō):P沒(méi)有左邊的結(jié)點(diǎn)粗井,所以左結(jié)點(diǎn)為NULL
所以Head->next = NULL^PR

對(duì)于尾結(jié)點(diǎn)來(lái)說(shuō):
尾結(jié)點(diǎn)沒(méi)有右邊的結(jié)點(diǎn),所以PR為NULL
Tail->next = PL^NULL

按照上述規(guī)則生成鏈表之后街图,遍歷方法如下:
從左往右遍歷鏈表:
pl=NULL;
p=Head;

while(p!=Tail) {
    pr=pl^(p->next);
    pl=p;
    p=pr;
}

從右往左遍歷鏈表:
pr=NULL;
p=Tail;

while(p!=Head) {
    pl=pr^(p->next);
    pr=p;
    p=pl;
}
*/

單指針雙鏈表

image.png

16.6:移位運(yùn)算左移(shl):<<

<<:左移運(yùn)算符浇衬;將一個(gè)數(shù)a向左移動(dòng)n位記為:a<<n

將一個(gè)數(shù)左移N位相當(dāng)于將一個(gè)數(shù)乘以2^N

int main(void) {
       int a = 10;
       int b = a<<2;
       printf(“b=%d\n”, b);//40

       return 0;

}

16.7:移位運(yùn)算右移(shr/sar):>>

>>:右移運(yùn)算符;將一個(gè)數(shù)a向右移動(dòng)n位記為:a>>n餐济。

右移動(dòng)運(yùn)算分為兩種右移:

  1. 邏輯右移耘擂,在移動(dòng)過(guò)程中,左邊位用0填充絮姆。(有符號(hào)數(shù):10000011醉冤,對(duì)于邏輯右移,向右移動(dòng)3位滚朵,那么左邊用0填充冤灾,變成了:00010000。)
  2. 算術(shù)右移辕近,在移動(dòng)過(guò)程中韵吨,左邊用符號(hào)位來(lái)填充。(算術(shù)右移移宅,向右移動(dòng)3位归粉,那么左邊用1(1為符號(hào)位)填充,變成了11110000漏峰。而對(duì)于01000011糠悼,算術(shù)右移3位,那么左邊用0(0為符號(hào)位)填充浅乔,變成了00001000倔喂。)

在C語(yǔ)言中,右移運(yùn)算符為算術(shù)右移運(yùn)算符靖苇,即左邊用符號(hào)位來(lái)填充席噩。對(duì)于無(wú)符號(hào)數(shù),而將一個(gè)數(shù)右移N位相當(dāng)于將這個(gè)數(shù)除以2^N

int main(void) {
    int a = -3;
    int b = 10;
    int c = a>>2;//
    int d = b>>1;//10/2^1=5
    printf(“a>>2=%d, b>>1=%d\n”, c, d);
    
    return 0; 
}
//證明:
/*
    int a = -3;
    //-3原碼10000000 00000000 00000000 00000011
    //-3反碼11111111 11111111 11111111 11111100
    //-3補(bǔ)碼11111111 11111111 11111111 11111101(加一)
    printf("a的16進(jìn)制:%x\n", a);//0Xfffffffd
    int b = 10;
    int c = a>>2;//
    //-3補(bǔ)碼11111111 11111111 11111111 11111101>>2
    //c補(bǔ)碼11111111 11111111 11111111 11111111
    //c反碼11111111 11111111 11111111 11111110(減一)
    //c原碼10000000 00000000 00000000 00000001(即-1)
    printf("c的16進(jìn)制:%x\n", c);//0Xffffffff
    int d = b>>1;//10/2^1=5
    printf("a>>2=%d, b>>1=%d\n", c, d);
*/

16.8:位運(yùn)算綜合應(yīng)用:


1. 將第n位置1或者清零(n是從零開(kāi)始計(jì)算的):
//把整數(shù)a中的第n位置為1
#define SETFLAGE(a, n) ((a)|(1<<(n)))
//把整數(shù)a中的第n位置為0
#define CLEARFLAG(a, n) ((a)&(~(1<<(n))))
//判斷整數(shù)a中第n位是否為1
#define FLAGON(a, n) ((a)&(1<<(n)))

#define BIT(n) (1<<n)
//置1:
a |= BIT(n);
//置0:
a &= ~BIT
//判斷:
a & BIT(n)

2. 對(duì)稱加密:XOR
異或性質(zhì):A^0==A, A^A==0
A為明文贤壁,B為密文
加密:B=A^key
解密:A=B^key

void func(char *data, int datalen, char *key, int keylen) {
    int i = 0;
    
    for (i=0; i<datalen; i++) {
        data[i] = (char)(data[i] ^ key[i%keylen])
    }
}

常見(jiàn)的位運(yùn)算:


image.png

位運(yùn)算在實(shí)際項(xiàng)目中的運(yùn)用

//先將該整數(shù)右移24位悼枢,與0xFF做與運(yùn)算,即獲得該8位的值脾拆。高8位記錄了對(duì)文件的修改標(biāo)記:
ulDisposition = (lpIrpStack->Parameters.Create.Options >> 24) & 0xFF;

//清除或者判斷某些標(biāo)志:
#define FlagOn(_F, _SF) ((_F)&(_SF))//查看_F是否包含_SF

mov eax, CRO
add eax, 0FFFEFFFFh//相當(dāng)于CR0 &= 0xFFFEFFFF;
mov CRO, eax

mov eax, CRO
or  eax, NOT OFFFEFFFFh//CR0 |= ~0xFFFEFFFF
mov CRO, eax 

17:宏定義

預(yù)處理->編譯->鏈接->可執(zhí)行文件(預(yù)處理將由預(yù)處理程序負(fù)責(zé)完成)馒索。

程序在編譯預(yù)處理的階段莹妒,不僅提供了宏定義的功能,還提供了文件包含以及條件編譯的功能绰上。

宏在編譯前預(yù)處理階段旨怠,就已經(jīng)完成了替換

17.1:宏的定義

  1. 宏定義:指用一個(gè)標(biāo)志符代表一個(gè)字符串,該標(biāo)志符就稱為宏名渔期。
  2. 宏展開(kāi):在程序編譯預(yù)處理階段运吓,所有宏名將會(huì)被替換為宏定義中的字符串的操作。

其定義格式如下(/其標(biāo)識(shí)符疯趟,稱為宏名):

//1. 不帶參數(shù)格式:#define 標(biāo)識(shí)符 字符串
//2. 帶參數(shù)的格式:#define 標(biāo)識(shí)符(參數(shù)表) 字符串

//例子:
#define PI 3.14
float calccirclearea(float r) {
       return PI*r*r;
}

#define MAX(X, Y) (X) > (Y) ? (X) : (Y)
void main(void) {
       int a = 10;
       int b = 11;
       int c = MAX(a, b);

       printf(“max = %d\n”, c);
}

宏定義的優(yōu)缺點(diǎn):

#define PI 3.14
1. 有意義
2. 減少修改
3. 無(wú)類型檢查
4. 無(wú)法調(diào)試
(宏不是變量拘哨,也沒(méi)有類型,不占內(nèi)存)

const float pi=3.14f;
1. 有意義
2. 減少修改
3. 有類型
4. 可調(diào)試

帶參數(shù)的宏與函數(shù)的優(yōu)缺點(diǎn)比較:

1. 宏的效率要高(inline)信峻,沒(méi)有了函數(shù)調(diào)用過(guò)程中的進(jìn)棧出棧傳參拷貝和出棧棧平衡倦青;
2. 宏無(wú)法調(diào)試
3. 宏無(wú)法做到類型檢查
4. 傳參的計(jì)算也不同,宏是簡(jiǎn)單的替換盹舞;函數(shù)先計(jì)算产镐,再傳參。

如:
#define MAX(a, b) ((a)>(b)?(a):(b))

int getMax(int x, int y) {
    return x>y?x:y;
}

MAX(1+2, value)-->((1+2)>(value)?(1+2):(value));
getMax(1+2, value);

17.2:宏的應(yīng)用與注意事項(xiàng)

  1. 宏名一般用大寫
  2. 使用宏可提高程序的通用性和易讀性踢步,減少不一致性癣亚,減少輸入錯(cuò)誤和便于修改。例如:數(shù)組大小常用宏定義
  3. 預(yù)處理是在編譯之前的處理获印,而編譯工作的任務(wù)之一就是語(yǔ)法檢查述雾,預(yù)處理不做語(yǔ)法檢查。
  4. 定義末尾不加分號(hào)兼丰;
  5. 宏定義寫在函數(shù)的花括號(hào)外邊玻孟,作用域?yàn)槠浜蟮某绦颍ǔT谖募淖铋_(kāi)頭鳍征。
  6. 可以用#undef命令終止宏定義的作用域
  7. 宏定義允許嵌套
  8. 字符串" "中永遠(yuǎn)不包含宏
  9. 宏定義不分配內(nèi)存黍翎,變量定義分配內(nèi)存。
  10. 宏定義不存在類型問(wèn)題艳丛,它的參數(shù)也是無(wú)類型的匣掸。
1. #,宏把#的內(nèi)容當(dāng)做一個(gè)字符串替換
#define CAT(c) "123"#c ---> CAT(abc) "123""abc" ===> "123abc"
#define FDR(c) #c ---> FDR(a) ===> "a"

2. ##氮双,用于把兩側(cè)的參數(shù)合并為一個(gè)符號(hào)
#define combine(a, b, c) a##b##c ---> combine(1, 2, 3) ===> "123"
#define WIDE(str) L##str ---> WIDE("adb") ===> L"abc"

17.3:條件編譯

條件編譯:只有在符合條件下旺聚,代碼才能編譯進(jìn)程序。

預(yù)編譯格式:

1.
#ifdef  標(biāo)識(shí)符
  程序段1
#else
  程序段2
#endif

2.
#ifndef  標(biāo)識(shí)符
  程序段1
#else
  程序段2
#endif

3.
#if 常量表達(dá)式
    程序段1
#else 
    程序段2
#endif

4.
#if 表達(dá)式1
    語(yǔ)句段1
#elif 表達(dá)式2
    語(yǔ)句段2
#else
    語(yǔ)句段3
#endif

18:遞歸

遞歸:指某個(gè)函數(shù)直接或間接的調(diào)用自身眶蕉;

18.1:遞歸定義

如果確定遞歸:

  1. 遞歸首先需要有一個(gè)或者多個(gè)遞歸出口(即遞歸終止的條件) ,也就是最小子問(wèn)題的求解
  2. 遞歸需要一個(gè)遞歸式唧躲,這個(gè)遞歸式規(guī)定如何將原問(wèn)題劃分成子問(wèn)題(子問(wèn)題解決的對(duì)象和原問(wèn)題解決的對(duì)象性質(zhì)一樣)
//階乘
int fact(unsigned int  n) {
    if(n==0)
        return 1;//遞歸出口
    return n*fact(n-1);//n*Fact(n-1)就是遞歸式,將求n的階乘造挽,轉(zhuǎn)化為子問(wèn)題求n-1的階乘
}

int main(void) {
     printf("10!=%d\n", fact(10));
     return 0;
}

//斐波那契數(shù)列
//斐波那契數(shù)列指的是這樣一個(gè)數(shù)列  1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, ...這個(gè)數(shù)列從第三項(xiàng)開(kāi)始碱璃,每一項(xiàng)都等于前兩項(xiàng)之和。
因此可以寫出它的遞歸函數(shù)與遞歸出口:
/*
f(1) = 1;
f(2) = 1;
f(n) = f(n-1) + f(n-2) n > 2
最簡(jiǎn)子問(wèn)題(遞歸出口):f(1),f(2)
子問(wèn)題轉(zhuǎn)化:f(n)=f(n-1)+f(n-2) n>2
*/

unsigned long feibo(unsigned int n) {
    if (n==1 || n==2) {
        return 1;
    } else {
        return feibo(n-1) + feibo(n-2);
    }
}

遞歸的優(yōu)缺點(diǎn):

  1. 遞歸的時(shí)間復(fù)雜度和對(duì)應(yīng)的非遞歸差不多饭入,但遞歸的效率是低不少(主要原因:反復(fù)函數(shù)調(diào)用和進(jìn)棧出棧嵌器,各種終端等機(jī)制)。遞歸嵌套過(guò)甚過(guò)多消耗棧內(nèi)存谐丢,回造成棧溢出爽航。
  2. 內(nèi)核開(kāi)發(fā)不能使用遞歸,因?yàn)闂V挥袔譑

18.2:遞歸的應(yīng)用

//1. 不允許使用任何全局或局部變量編寫 int strlen(char *s)乾忱,計(jì)算字符串的長(zhǎng)度
size_t strlen(const char *s) {
    if(s==NULL || *s==’\0’) {
        return 0;
    }

    return 1+strlen(s+1);
}

size_t strlen(const char* s) {
    return (s==NULL||*s==’\0’)?0:1+strlen(s+1);
}

//2. 請(qǐng)反向的輸出一個(gè)字符串:比如”hello, world!”讥珍,打印出來(lái)是:”!dlrow, olleh”。
void inverse_print(char *s) {
    if(*s=='\0'||s==NULL )
         return;

    inverse_print(s+1);//先遞歸反向打印s的子串s+1

    printf( "%c", *s);
}

//3. 字符串逆置
void reverse_str(char *str窄瘟,size_t len) {
    if(str==NULL || len==1||len==0)
        return;

    reverse_str(str+1,len-2);//先逆置子串
    char tmp=*str;//再交換主串最左邊與最右邊的字符
    *str=*(str+len-1)
    *(st+len-1)=tmp;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末衷佃,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蹄葱,更是在濱河造成了極大的恐慌氏义,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件图云,死亡現(xiàn)場(chǎng)離奇詭異惯悠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竣况,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門克婶,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人帕翻,你說(shuō)我怎么就攤上這事鸠补。” “怎么了嘀掸?”我有些...
    開(kāi)封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵紫岩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我睬塌,道長(zhǎng)泉蝌,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任揩晴,我火速辦了婚禮勋陪,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘硫兰。我一直安慰自己诅愚,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開(kāi)白布劫映。 她就那樣靜靜地躺著违孝,像睡著了一般刹前。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雌桑,一...
    開(kāi)封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天喇喉,我揣著相機(jī)與錄音,去河邊找鬼校坑。 笑死拣技,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耍目。 我是一名探鬼主播膏斤,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼制妄!你這毒婦竟也來(lái)了掸绞?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤耕捞,失蹤者是張志新(化名)和其女友劉穎衔掸,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體俺抽,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡敞映,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了磷斧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片振愿。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖弛饭,靈堂內(nèi)的尸體忽然破棺而出冕末,到底是詐尸還是另有隱情,我是刑警寧澤侣颂,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布档桃,位于F島的核電站,受9級(jí)特大地震影響憔晒,放射性物質(zhì)發(fā)生泄漏藻肄。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一拒担、第九天 我趴在偏房一處隱蔽的房頂上張望嘹屯。 院中可真熱鬧,春花似錦从撼、人聲如沸州弟。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)呆馁。三九已至桐经,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間浙滤,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工气堕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留纺腊,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓茎芭,卻偏偏與公主長(zhǎng)得像揖膜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梅桩,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容