Linux C語言結構體-學習筆記

Linux C語言結構體簡介

前面學習了c語言的基本語法特性,本節進行更深刻的學習。編程

  • 預處理程序。 編譯指令: 預處理, 宏定義,
  • 創建本身的數據類型:結構體,聯合體,動態數據結構
  • c語言表達式工具 邏輯運算符: & | ^ ~ << >>
  • 函數的遞歸調用方法

什麼是預處理

vim helloworld.c

helloworld.c:vim

#include <stdio.h>

int main()
{
    printf("hello,world!\n");
    return 0;
}

編譯的目的:數組

從c語言 .c源文件變成可執行文件
gcc helloworld.c -o helloworld.out
./helloworld.out

編譯的四個步驟:數據結構

.c文件-> .i文件-> .s文件-> .o文件->可執行文件(可運行)
    1. 預處理
    1. 編譯
    1. 彙編
    1. 連接

記憶法: ISO三步走戰略。框架

下面咱們來查看預處理中要作的事情:函數

gcc -o helloworld.i helloworld.c -E

-E表示只讓gcc執行預處理。工具

// 查看helloworld.i文件
cat helloworld.i

vim跳到整個文檔底部,命令: :$學習

mark

能夠看到代碼的底端是咱們的main函數spa

對比一下.i文件和.c文件的區別3d

首先:它們都是c的語法。其次.c文件main函數上面是 #include <stdio.h>

.i 文件中這行代碼不見了,變成了上面這些東西。

因此預處理所作的第一件事情就是展開頭文件

#include <stdio.h>stdio.h展開,將未註釋的內容直接寫入.i文件。

在預處理步驟中,除了展開頭文件,還要進行宏替換。

宏是什麼

c語言常量分爲直接常量和符號常量:

#define 標識符 常量值 (注意:沒有分號)

helloMacro.c源代碼:

#include <stdio.h>
#define R 10

int main()
{
    int a =R;
    printf("a=%d\n");
    printf("hello,world!\n");
    return 0;
}
gcc -o helloMacro.i helloMacro.c -E

預處理過以後的代碼

# 4 "helloworld.c"
int main()
{
    int a =10;
    printf("a=%d\n");
    printf("hello,world!\n");
    return 0;
}

mark

能夠看到10是直接當作一個字符串來替換本來的宏定義R。

宏的本質是發生在預處理階段單純的字符串替換(宏替換), 在預處理階段,宏不考慮語法;

示例代碼2:
vim helloMacro2.c

#include <stdio.h>
#define R 10
#define M int main(

M){
    printf("hello,world!\n");
    return 0;
}
gcc helloMacro2.c -o helloMacro2.out
./helloMacro2.out

預處理是沒有問題的,能夠成功的編譯執行。宏不考慮C語言的語法。它很單純,字符串替換。

mark

  • 宏用於大量反覆使用的常量、數組buffer的大小,爲了便於修改定義成宏。

一般定義數組咱們這樣寫:

int a[10];
int b[10];

定義兩個相同大小的數組,這裏咱們就能夠改成下面代碼。

#define R 10
int a[R];
int b[R];
一次修改,能夠修改兩份。

宏也是能夠傳遞參數的,能夠作一些函數能夠作的事情

宏函數

vim helloMacroFunction.c
源代碼:

#include <stdio.h>
#define R 10
#define M int main(
#define N(n) n*10



M){
    int a = R;
    int b = N(a);
    printf("b = %d\n",b);
    printf("a =%d\n",a);
    printf("hello,world!\n");
    return 0;
}
gcc helloMacroFunction.c -o helloMacroFunction.out
./helloMacroFunction.out

mark

這裏的處理過程: 首先將參數a替換到上面的宏中,上面就變成了 N(a) a*10,以後再用 a*10替換下面的 N(a)
int b = N(a); //變成了 int b =a*10;
gcc -o helloMacroFunction.i helloMacroFunction.c -E

預處理以後:

# 8 "hello.c"
int main(){
    int a = 10;
    int b =a*10;
    printf("b = %d\n",b);
    printf("a =%d\n",a);
    printf("hello,world!\n");
    return 0;
}

mark

先不考慮宏實現,先來寫一個正常的求和函數。

vim helloAdd.c
#include <stdio.h>
#define R 20
#define M int main(
#define N(n) n*10

int add(int a,int b){
    return a+b;
}


M){
    int a = R;
    printf("a =%d\n",a);
    printf("hello,world!\n");

    int b =N(a);
    printf("b = %d\n",b);
    
    int c =add(a,b);
    printf("c =%d\n",c);

    return 0;
}
gcc helloAdd.c -o helloAdd.out
./helloAdd.out

mark

使用宏函數實現求和。

vim helloAddMacro.c
#include <stdio.h>
#define R 20
#define M int main(
#define N(n) n*10
#define ADD(a,b) a+b
int add(int a,int b){
    return a+b;
}


M){
    int a = R;
    printf("a =%d\n",a);
    printf("hello,world!\n");

    int b =N(a);
    printf("b = %d\n",b);
    
    int c =add(a,b);
    printf("c =%d\n",c);

    int d =ADD(a,b);
    printf("d =%d\n",d);

    return 0;
}
gcc helloAddMacro.c -o helloAddMacro.out
./helloAddMacro.out

mark

能夠看到使用宏函數和普通函數的求和效果是一致的。結果與簡單的字符串替換一致。

ADD(a,b) 被替換成 a+b 所以式子變成int d = a+b;

gcc -o helloAddMacro.i helloAddMacro.c -E
vim helloAddMacro.i

mark

版本3,宏定義中優先級問題。

#include <stdio.h>
#define R 20
#define M int main(
#define N(n) n*10
#define ADD(a,b) a+b
int add(int a,int b){
    return a+b;
}


M){
    int a = R;
    printf("a =%d\n",a);
    printf("hello,world!\n");

    int b =N(a);
    printf("b = %d\n",b);
    
    int c =add(a,b);
    printf("c =%d\n",c);

    int d =ADD(a,b);
    printf("d =%d\n",d);

    int e =ADD(a,b) * ADD(a,b);
    printf("e =%d\n",e);

    return 0;
}

預測一下e的輸出爲: a+b*a+b ab先乘起來,a=20,b=200,ab=4000,而後加上a,b:獲得結果(4220)

gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence.out
./helloAddMacroPrecedence.out

mark

運算是等咱們編譯完了,執行的時候纔會運行的。預處理階段不會進行運算操做。
  • 宏定義時因爲本質是字符串的替換

真正運算的時候,會按照運算符號的優先級來進行

解決方案:

#define ADD(a,b) (a+b)
gcc helloAddMacroPrecedence.c -o helloAddMacroPrecedence2.out
./helloAddMacroPrecedence2.out
加個括號,保證優先級更高一點。

mark

宏函數和正常函數的優點?

正常的add函數須要返回值類型,須要傳遞進來的參數有類型要求。

講傳入的a,b 類型進行改變,如變爲兩個浮點型數,程序就會自動類型轉換

可是宏函數就沒有這種要求能夠不用考慮輸入值的類型,這與普通的函數定義不一樣。

int c =add(10.5,20.4);
printf("c =%d\n",c);

float d =ADD(10.5,20.4);
printf("d =%f\n",d);
gcc helloAddMacroPrecedenceCompare.c -o helloAddMacroPrecedenceCompare.out
./helloAddMacroPrecedenceCompare.out

mark

普通函數例如 int add(int a,int b)除了在開頭要聲明值的類型,還要設置返回值,所以在定義過程與調用過程相對複雜。若能用宏定義實現的狀況應優先考慮宏定義.

宏是不考慮數據類型,不考慮c語言的語法的。只是簡單的字符串的處理。

預處理階段,除了宏以外,還提供了一個叫作mtianyan:條件編譯的功能。

能夠按照不一樣的條件,編譯不一樣的程序部分,從而產生不一樣的目標代碼文件。對於程序的移植和調試都是頗有用的。

下集預告: 和宏比較相近的功能,typedef

Linux C預處理之typedef

嚴格來說,typedef和預處理是沒多大關係的,放在這裏是由於容易搞混淆。

  • typedef做用是給一個變量類型起別名。

簡記: 取別名; 可是屬於C語法, 結束要加分號, 與#define不一樣, 它不須要加分號。

typedef int tni;

inttni代替,在以後的int定義可直接寫爲:

tni a;

咱們以前定義指針的狀況:

int *p;
// 若是咱們加上typedef
typedef int *p;
typedef int *p; 是給 int * 這個數據類型(還記得大明湖畔咱們說過的, typedef做用是給一個變量類型起別名)起了別名 p
pq=null
// 根據上面的typedef等價於
int* q=null

將int類型換成tni:

typedef int tni;

M){
    tni a = R;
gcc helloAddMacroPrecedenceCompareTypedef.c -o helloAddMacroPrecedenceCompareTypedef.out
./helloAddMacroPrecedenceCompareTypedef.out

mark

能夠看到程序能夠成功的運行,注意, 真名能夠和別名共存

以前咱們已經試驗過了宏是會在預處理階段被替換的,而tni不會在.i文件中被替換。

gcc -o helloAddMacroPrecedenceCompareTypedef.i helloAddMacroPrecedenceCompareTypedef.c -E
vim helloAddMacroPrecedenceCompareTypedef.i

mark

咱們一般在使用typedef的時候,一般都是給本身自定義的數據類型起別名。

好比size_t就是系統給咱們定義的一個類型的別名。

typedef unsigned long size_t;

// 定義一個結構體
struct stu{
};

// 使用結構體的時候
struct stu xxx;

// 給結構體起別名
typedef struct stu{
} stu_t;
// 起了別名以後使用結構體
stu_t xxx;

區別2: 宏定義開頭聲明,全局任何做用域均可以直接使用。

注意typedef若是寫在方法體內,則只可做用於該做用域的花括號內。

下節課: 結構體

結構體的聲明與定義

以前的學習中,使用的變量類型大可能是一些比較簡單的變量類型。

好比:

int a = 0;
float b =0.0;
這些變量之間沒有什麼聯繫,然而不少時候咱們存儲的數據比較複雜,不能經過簡單的類型進行描述。

好比咱們須要存儲一個武器的信息:

mark

它會包含武器的不少信息。以前的全部數據類型,都不足以描述一個武器的多種信息。

數組也不行,由於數組只能存放同類型的數據,武器的名字和價格類型不一樣。

結構體是不一樣類型變量的集合 & 數組是相同類型變量的集合

vim Cstructweapon.c
#include <stdio.h>
struct weapon{
    // 武器名字
    char name[20];
    // 攻擊力
    int atk;
    // 價格
    int price;
};

int main()
{
   int a =0;
   float b =0.0;

   struct weapon weapon_1;
   return 0;
}
struct 結構體類型名{},只是建立了一個結構體類型。

可是咱們可使用這一結構體類型struct weapon來聲明變量

struct weapon weapon_1;
這種定義方法是聲明和定義分離的形式。

第二種直接在聲明時去定義。

struct weapon{
    // 武器名字
    char name[20];
    // 攻擊力
    int atk;
    // 價格
    int price;
}weapon_1;
至關於定義了一個全局變量,變量名爲weapon_1,類型是 struct weapon。簡單程序能夠運用

第三種方法:

struct{
    // 武器名字
    char name[20];
    // 攻擊力
    int atk;
    // 價格
    int price;
}weapon_1;
這種方法就不能用這個結構體類型去定義其餘的變量。匿名結構體類型。

下節課預告: 如何進行結構體的初始化與引用

結構體的初始化與引用。

定義告終構體以後: 咱們要知道如何初始化,以及如何訪問結構體成員

在對結構體進行初始化的時候,能夠等於一個初始化列表。經過一個花括號將常量括起來,
依次賦值給結構體成員。

struct weapon weapon_1 = {"weapon_name",100,200};
// 訪問結構體裏的成員,使用點
printf("%s\n,%d\n",weapon_1.name,++weapon_1.price);
#include <stdio.h>
struct weapon{
    // 武器名字
    char name[20];
    // 攻擊力
    int atk;
    // 價格
    int price;
};

int main()
{
   int a =0;
   float b =0.0;

   struct weapon weapon_1 = {"weapon_name",100,200};
   // 訪問結構體裏的成員,使用點
   printf("%s\n%d\n",weapon_1.name,++weapon_1.price);
}
gcc Cstructweapon.c -o Cstructweapon.out
./Cstructweapon.out

mark

結構體成員變量能夠和普通變量同樣進行操做。weapon_1.name應該被咱們視爲一個總體

a++;
++weapon_1.price //能夠看出它能夠像普通變量同樣操做

結構體數組

若是咱們須要十個武器的數據,咱們須要使用數組,結構體數組。

  • .(點)運算符是一個成員運算符,在全部運算符中運算級最高,可用.運算符訪問結構體內部成員.
結構體數組和普通數組,差異不大。
int a[2]; // 包含兩個int類型的元素

//結構體數組裏包含兩個結構體類型的元素,每一個元素有結構體裏的三個成員
struct weapon weapon_2[2]; // 每一個元素都是一個結構體類型的數據

vim CstructweaponArray.c(在原基礎上添加代碼)

struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}};
printf("%s\n%d\n",weapon_2[0].name,weapon_2[1].atk);
gcc CstructweaponArray.c -o CstructweaponArray.out
./CstructweaponArray.out

mark

兩種結構體數組的初始化方式:

// 直觀式
struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}};
// 通常式
struct weapon weapon_2[2]={"weapon_name1",50,100,"weapon_name2",99,200};

兩種方式均可以成功的初始化。

結構體指針

瞭解: 什麼是指向結構體變量的指針變量,以及如何去使用這個變量。

struct weapon *w ;  //定義一個指向weapon的w結構體指針
w=&weapon_1;       //具體指向weapon_1的內存地址

(*w).name             //左右兩側的括號不可省略,由於.運算符優先級高於星
w->name          // 箭頭叫作指向運算符
weapon_1.name   //這3種訪問類型都是同樣效果
vim CstructweaponArrayPointer.c
struct weapon * w;
w = &weapon_1;
printf("---------------------------------\n");
printf("name=%s\nname=%s\nname=%s\n",(*w).name,w->name,weapon_1.name);
gcc CstructweaponArrayPointer.c -o CstructweaponArrayPointer.out
./CstructweaponArrayPointer.out

mark

結構體數組指針

cp CstructweaponArrayPointer.c CstructweaponArrayPointer2.c
vim CstructweaponArrayPointer2.c

結構體數組指針,不用取地址符&

數組的名字表明瞭這個數組的內存首地址, 數組括號內的長度表明了數組的單元數,數據類型是int的話就按照int類型(32位系統上是4個字節)乘以單元數的長度,若是數據類型是結構體的話就按照結構體的長度乘以單元的長度。

p++,不是內存位置右移了一個字節,而是右移了一個單位長度的結構體weapon的內存長度。因此就不難理解爲何右移到了第二個結構體實例的首地址上了。

struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",99,200}};
struct weapon *p;
p = weapon_2; //數組的名字對應着數組第一個元素的起始地址

// 取成員值: p->name 至關於 weapon_2[0].name
printf("%s\n",p->name);
printf("---------------------------------\n");
p++; // 等價於 weapon_2+1 讓其指向weapon_2[1]
printf("%s\n",p->name);
gcc CstructweaponArrayPointer2.c -o CstructweaponArrayPointer2.out
./CstructweaponArrayPointer2.out

mark

Linux C 公用體

公用體類型也被稱之爲聯合體,建立公用體類型可使用關鍵字union

理解起來很簡單,讓幾個不一樣類型的變量共享同一塊內存地址
vim union.c
#include <stdio.h>
union data{
    int a;
    char b;
    int c;
}; // 表明a,b,c會只在同一個內存空間。

int main()
{
   union data data_1;
   data_1.b ='C';
   data_1.a =10; // 後面被賦值的成員纔是真正起做用的成員
   return 0;
}
優勢是節省一部分的開銷。缺點: 同一時刻只能存儲一個成員。

好比,對b進行賦值,對a進行賦值。以最後一個賦值爲準,由於全部成員共享一塊內存。
(後賦值的會把前面的覆蓋掉)

共用體類型的長度是全部成員中最長的那個成員的長度。好比上面的例子中int有四個字節,char只有一個字節。int就是最長的,所以這個共用體類型就佔四個字節。
union data data_1 = {10};
所以在對公用體或聯合體賦值的時候,只能給一個變量。

對於結構體來講,這個是不同的

struct data{
    int a;
    char b;
    int c;
};
可能會下意識的覺得struct佔用的內存空間大小是這幾個成員變量的大小之和。 4+1+4=9

結構體的內存大小佔用,涉及字節對齊,是一種計算機用空間換取時間的方式。

大小 = 最後一個成員的偏移量+最後一個成員的大小+(可選)末尾填充字節數。
偏移量就是某一個成員的實際地址和結構體首地址之間的距離。(偏移量,實際地址和結構體首地址之間的距離)
  • 對於成員a來講,a的地址就是結構體的首地址,偏移量0;
  • b的偏移量4,自身大小爲1;
  • 結構體字節對齊準則: 每一個結構體相對於首地址的偏移量都得是當前成員所佔內存大小的整數倍,不然就會加上填充字節。
  • c的偏移量爲5,c自身大小爲4.由於5不是4的整數倍,字節對齊時要補齊爲8.而後加上c自身的4。

因此整個佔用12。

  • 而後再判斷這個大小12是否是結構體中最寬的基本類型成員的整數倍。
例子中最寬的是int,4能夠被12整除。 若是不能,c以後也要填充末尾填充字節數。

union共用體是多種不一樣數據類型的合集,所佔字節按照共用體裏面所佔內存空間最大的類型決定。

例若有char(1字節)型和int(四字節)型,按照int型來計,整個共用體所佔四個字節;
此外,共同體裏面全部數據的地址是相同的,這個決定了它在有多種數據類型的時候,有且只能存放這些數據類型裏面的一種數據。以最後一次賦值爲準。

struct結構體也是不一樣數據類型的合集,所佔字節按照裏面全部數據類型的長度來決定.

例如:char(1字節)和int(4字節),struct所佔空間是8個字節,不是5個字節,緣由是涉及到字節對齊,字節對齊是爲了方便計算機快速的讀取數據。
vim struct_size.c
#include <stdio.h>
struct data{
    int a;
    char b;
    int c;
};
int main(){
   // union data data_1;
   //data_1.b ='C';
   // data_1.a =10;
   printf("%lu\n",sizeof(struct data));

   return 0;
}
gcc struct_size.c -o struct_size.out

mark

這裏的12是這樣計算來的,a是第一個成員,偏移量爲0.b偏移量爲4,b自身大小爲1,能夠整除,不用補字節,c偏移量爲5,自身大小爲4,字節對齊爲8,加上自身大小4,12了。

而後判斷12是否是結構體中最寬的基本類型成員的整數倍。

  • %p表示輸出這個指針
  • %d表示後面的輸出類型爲有符號的10進制整形,
  • %u表示無符號10進制整型,
  • %lu表示輸出無符號長整型整數(long unsigned)
vim union_address.c
#include <stdio.h>
union data{
    int a;
    char b;
    int c;
}; // 表明a,b,c會只在同一個內存空間。

int main()
{
    union data data_1;
    data_1.b ='C';
    data_1.a =10;
    printf("%p\n%p\n%p\n",&data_1.a,&data_1.b,&data_1.c);
    return 0;
}
gcc union_address.c -o union_address.out
./union_address.out

mark

共用體中成員變量的地址所有相同。

Linux C 動態數據結構-靜態鏈表

(以前使用到的都是)靜態數據結構:

  • 如:整型、浮點型、數組。
  • 特色: 系統分配固定大小的存儲空間

以後程序運行的時候,空間的位置和容量都不會再改變了。

好比咱們在使用數組的時候,數組的長度就必須事先定義好。可是不少時候咱們不知道。

須要一個能夠動態存儲分配的數據結構。也就是可變大小的數據結構。

鏈表:

  • 有一個頭指針變量head,存放地址,地址指向第一個元素。沒有頭指針鏈表沒法訪問
  • 鏈表中的每個元素都是一個節點。
  • 每一個節點裏包含兩部分,包括用戶須要的數據和下一個節點的地址,各個元素的地址不必定是連續的(與數組的區別)

mark

指向爲空時鏈表結束。 在鏈表裏訪問某一個元素,必須經過上一個元素提供的下一個元素的地址才行。

想訪問B元素,得找到a元素中存放的b元素的地址才能訪問到b元素。

靜態鏈表:

(全部節點都是在程序中定義的,而不是臨時開闢的)

【由三個武器信息的節點組成,因此用結構體類型做爲節點元素】

vim struct_weapon_link.c
#include <stdio.h>

struct weapon{
    int price;
    int atk; // 節點的數據部分

    struct weapon * next; // 下一節點的地址
};

int main()
{
   struct weapon a,b,c,*head; // 除過三個武器,還要定義一個頭指針
   a.price =100;
   a.atk = 100;
   b.price =200;
   b.atk =200;
   c.price =300;
   c.atk =300;

   // 鏈接成鏈表
   head = &a;
   a.next =&b;
   b.next =&c;
   c.next = NULL;

   struct weapon *p;
   p =head;
   while(p!=NULL){
     printf("%d,%d\n",p->atk,p->price);
     p=p->next;
   }
}
gcc struct_weapon_link.c -o struct_weapon_link.out
./struct_weapon_link.out

mark

靜態鏈表,全部的節點都是在程序中去定義的,而不是臨時開闢的。

鏈表有一個頭指針和尾指針,每一個指針指向的是鏈表下一個數據的地址。

在結構體裏面加入指針就構成鏈表,此時指針結構體包括兩個部分,一個是信息域(數據域),另外一個是指針域。

Linux C 動態數據結構-動態鏈表

鏈表:能夠用malloc來動態分配所需的內存,而且須要用free手動釋放在堆裏面申請的內存。

程序執行過程當中從無到有的創建起一個鏈表,也就是說須要一個一個的開闢新節點,輸入新節點的數據,而後創建起先後相連的關係。

創建武器信息的單向動態鏈表:

vim dynamic_linked_list.c
#include <stdio.h>
#include <malloc.h>

struct weapon{
  int price;
  int atk;
  struct weapon * next;
};

// 須要一個建立鏈表的函數,返回值是鏈表的頭指針,返回類型是struct weapon *
struct weapon * create(){
   struct weapon *head;
   struct weapon *p1,*p2; // 3個指針都用來指向struct weapon類型數據,p1,p2分別指向當前新建立的節點和上一個節點。
   int n=0;// 臨時變量,記錄當前鏈表中節點個數
   // malloc分配內存塊的函數,sizeof判斷數據類型長度符
   // (int)malloc(16) 分配16個int型內存塊
   p1=p2=(struct weapon*)malloc(sizeof(struct weapon));

   // 從鍵盤輸入數據,給第一個節點。
   scanf("%d,%d",&p1->price,&p1->atk);
   head = NULL;// 一開始鏈表不存在,置空

   while(p1->price!=0){ // 約定price爲0時中止輸入
     n++;
     if(n==1) head=p1;
     else p2->next=p1;

     p2=p1; // 保留p1當前所指向的的地址,保留上一個節點。

     //須要開闢一個新的動態存儲區,把這個的地址載給p1
     p1=(struct weapon*)malloc(sizeof(struct weapon));
     scanf("%d,%d",&p1->price,&p1->atk);//開闢後輸入數據
}
p2->next=NULL;
return (head);
}//p1,p2一個用來指向鏈表新創立的節點,一個用來指向下一個節點
int main()
{
   struct weapon *p;
   p=create(); // p成爲鏈表的頭指針
   printf("%d,%d\n",p->price,p->atk);//打印第一個節點的信息
   return 0;
}
gcc dynamic_linked_list.c -o dynamic_linked_list.out
./dynamic_linked_list.out

mark

while(p!=NULL){
   printf("%d,%d\n",p->atk,p->price);
   p=p->next;
   }

mark

C語言中的位運算符:按位與、按位或、按位異或、左移和右移

LInux C 位運算之按位與

  • 位:指二進制數中的一位 0 false 1 true
  • 古老的微處理器速度:比加減運算快一些,比乘除法快不少

如今的框架中:一般與加減運算快相同,比乘除法快不少

六種位運算符

  • & 按位與
  • | 按位或
  • ^按位異或
  • ~按位取反
  • <<左移
  • 右移
vim and_operation.c
  • 位運算就是將參與運算的兩個數據按照對應的二進制數逐位進行邏輯與運算。
  • 參與運算的兩個數必須是整型或者是字符型。
  • 並且參與運算的數必需要以補碼的方式出現。
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a = 4; //0100 int佔用4字節,32位補碼。
   int b = 7; //0111

   int c = a&b; //0100 邏輯與運算

   printf("%d\n",c);
   return 0;
}
gcc and_operation.c -o and_operation.out
./and_operation.out

mark

按位與的應用

  • 迅速清零(對於一個數中爲1的位,讓另外一個數的相應位爲0);

任何一個數和0作按位與結果必定是0

  • 保留指定位置(對另外一個數的相應位置1); 取a的低8位,將b的低八位所有置爲一,按位與便可。
  • 奇偶判斷(a和1作與運算)[a&1 //獲得結果1爲奇數,獲得0爲偶數]
vim odd_even.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =4;//0100
   int b =7;//0111

   int c =a&1;//0100

   int d =b&1;//0100

   printf("%d\n",c);
   printf("%d\n",d);
   return 0;
}

輸出爲0,1; 輸出爲0是偶數,輸出爲1是奇數。

由於除過最後一位前面的都必定是2的倍數了。最後一位爲1,表示是奇數。
最後一位爲0,表示是偶數。
gcc odd_even.c -o odd_even.out
./odd_even.out

按位或運算。

將參與運算的兩個數據按位進行邏輯或(有一個1的時候結果就是1)運算

vim bit_or.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =9; //1001
   int b =5; //0101

   int c =a|b;//1101

   printf("%d\n",c);
   return 0;
}
gcc bit_or.c -o bit_or.out
./bit_or.out

mark

輸出爲1101(13)

按位或的做用:

設定數據的指定位,與255(0xFF)作按位或運算

好比咱們想讓9低八位的數據位設置爲1.

a = a | 0xFF; 設定數據a的指定二進制數後8位置爲1
vim bit_or_8.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =9; //1001
   int b =5; //0101

   int c =a|b;//1101

   printf("%d\n",c);

   int a =a|0xff;

   printf("%d\n",a);
   return 0;
}
gcc bit_or_8.c -o bit_or_8.out
./bit_or_8.out

mark

按位異或^

將參與運算的兩個數據按對應的二進制數逐位進行邏輯異或運算.只有對應位結果互斥,結果才爲真。

vim bitwise_xor.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =9; //1001
   int b =5; //0101

   int c =a^b;//1100

   printf("%d\n",c);
   return 0;
}
gcc bitwise_xor.c -o bitwise_xor.out
./bitwise_xor.out

mark

  • 定位反轉 讓a中全部的0變成1,1變成0。
a^0xff;
  • 數值交換
a= a^b;
b= b^a;
a= a^b;
vim bitwise_xor_change.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =9; //1001
   int b =5; //0101

   a= a^b;
   b= b^a;
   a= a^b;
   printf("%d\n%d\n",a,b);
   return 0;
}
gcc bitwise_xor_change.c -o bitwise_xor_change.out
./bitwise_xor_change.out
成功的將a,b進行了調換

mark

涉及到的運算原理能夠具體以下闡述:(下面的= 是等於的意思 而非賦值號)

P0. x^x=0
P1. a^0=a
P2. c=a^x --> a=c^x (a=a^x^x=a^0=a)

故有交換 解釋:

a^=b; 起先 a被賦值爲 a^b
b^=a; 此時 b被賦值成最開始的a值,而a保持上一步驟中的結果值不變
a^=b; 此時 a被賦值成最開始的b值,而b保持上一步驟中的結果值不變

按位取反

惟一的一個單目運算符,右結合性。把零換成1,1換成0

~1000 = 0111

左移:高速乘以2; 右移:高速除以2

左移:將數據對應的二進制值逐位左移若干位;

高位會被捨棄掉,低位補零; 至關於乘以2的n次方

左移須要注意的是int是一個有符號的類型,移動過程當中把符號位移出去了會溢出

右移:將數據對應的二進制值逐位右移若干位

mtianyan: 低位丟棄,無符號數補零,有符號數,高位補0或1(根據符號位判斷,就是正數數補0,負數補1); 至關於除以2的n次方。

vim left_right_shift.c
#include <stdio.h>

int main()
{
   // & | ^ ~ << >>

   int a =3; //0000 0011
   a = a<<4; //0011 0000 32+16=48

   printf("a=%d\n",a);

   int i =1; // 0000 0001
   i = i<<33; // 至關於i<<1
   printf("i=%d\n",i);
   // 當移位位數超過該數值類型的最大位數時,編譯器會用移位位數去模該類型位數,而後按照餘數進行移位。 

   int b = 4;
   b = b>>1;

   printf("b=%d\n",b);
   return 0;
}
gcc left_right_shift.c -o left_right_shift.out
./left_right_shift.out

mark

能夠看到會報出warning

遞歸函數之遞歸調用

遞歸調用就是在調用函數的過程當中,被調用的函數調用它自己的過程。

遞歸調用有時候會犧牲效率

vim recursiv_call.c
#include <stdio.h>
void func(){
  printf("1\n");
  func();
  }

int main()
{
  func();
  return 0;
}
gcc recursiv_call.c -o recursiv_call.out
./recursiv_call.out

mark

無盡地輸出1,而後爆出段錯誤,核心已轉儲

所以咱們的遞歸是要加上條件的。適當的時候選擇是否遞歸,由於遞歸在某些時候會犧牲效率。

使用遞歸求階乘

vim recursiv_call_factorial.c
#include <stdio.h>
int func(int n)
{
    int r =0;
    if(n<0){
      printf("error");
    }else if(n==0 || n==1){
    return 1;
    }else{
        r =n *func(n-1);
        return r;
    }
 }

int main()
{
   int n =0;
   printf("please input the num:");
   scanf("%d",&n);
   // 使用func求階乘
   int result = func(n);
   printf("the result is %d\n",r);
   return 0;
}
gcc recursiv_call_factorial.c -o recursiv_call_factorial.out
./recursiv_call_factorial.out

mark

注意這裏遞歸的整型溢出

遞歸函數之遞歸原理

遞歸是一種編程技巧,函數調用過程當中函數體內又調用了它本身

函數調用的過程:

mark

函數A調用函數B,main函數中調用A,A中調用了B,B有兩個參數,a和b被稱爲形參。

函數沒有被調用的時候,形參是不會被分配內存單元的。在A中調用了B,A就被稱爲主調函數。

主調函數中調用一個函數的時候,這個函數裏的參數被稱爲實參。

mark

函數調用所須要作的第一件事:

爲被調用的函數(B)的形參分配一個臨時的內存單元而後才能把兩個對應的實參的值傳遞進來。

同時還須要傳遞的是主調函數(A)的返回地址。(俗稱保護現場)

之因此要把A的返回地址保存起來,是由於被調函數(B)執行完了還須要繼續執行主調函數(A)後面的代碼。

經過return語句將函數計算後的值帶回到主調函數。

保護現場時傳遞的返回地址,參數,函數調用結束的返回值。這些數據都保存在棧中。

遞歸是調用自身,但自身也是函數調用,本質就是函數的調用。

mark

求階乘的函數,因爲遞歸也是符合函數調用原則的,因此全部被調用的函數都會建立一個副本,爲各自的調用者服務,不受其餘函數影響。遞歸函數被調用多少次,就會建立多少個它自身的副本。

這個副本就好像是一個新的函數同樣。系統會用棧來管理它的內存。它是一個獨立的存在,徹底能夠理解爲這已是一個新的func了。它被分配了獨立的內存單元。

mark

遞歸:大規模——>化簡——>小規模,直到問題可求。
遞歸函數同時必須有 遞歸條件和遞歸表達式,不然會進入死循環。
遞推(for):則是由小問題的解逐步代入大問題並求出解。

mark

總結

  • 編譯預處理:展開頭文件、宏替換
  • 自定義數據類型:結構體、聯合體、鏈表實現,靜態動態鏈表。結構體涉及字節對齊,共同體全部成員共用一個地址, 結構體內存佔用計算方法。
  • 邏輯運算符位運算

mark

  • 遞歸調用,遞歸思想和遞推
相關文章
相關標籤/搜索