慕課網-LinuxC語言結構體-學習筆記

天涯明月笙發表於2017-08-22

Linux C語言結構體

  • 編譯指令:預處理,巨集定義,
  • 建立自己的資料型別:結構體,聯合體,動態資料結構
  • 邏輯運算子:& | ^ ~ << >>
  • 遞迴函式的呼叫

什麼是預處理

.c檔案->.i檔案->.s檔案->.o檔案->可執行檔案

  • 預處理
  • 編譯
  • 彙編
  • 連結

gcc -o helloworld.i helloworld.c -E
-E表示只讓gcc執行預處理。

vim跳到整個文件底部,命令::$

  • 展開標頭檔案。
    #include <stdio.h>展開

巨集是什麼

c語言常量分為直接常量和符號常量:
#define 識別符號 常量值(沒有分號)

hello.c原始碼:

#include <stdio.h>
#define R 10

int main()
{
    int a =R;
    printf("a=%d
");
    printf("hello,world!
");
    return 0;
}

預處理過之後的程式碼

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

10是直接當做一個字串來替換。
巨集的本質是在預處理階段發生的單純的字串替換(巨集替換);
在預處理階段,巨集不考慮語法;

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

M){
    printf("hello,world!
");
    return 0;
}

預處理是沒有問題的。可以編譯執行。

  • 巨集用於大量使用的常量、陣列buffer中,便於修改。

巨集函式

原始碼:

#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
",b);
    printf("a =%d
",a);
    printf("hello,world!
");
    return 0;
}

預處理之後:

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

巨集替換中直接替換的優先順序問題。

#include <stdio.h>
#define R 10
#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;
    int b =N(a);
    int c =add(a,b);
    int d =ADD(a,b);
    int e = ADD(a,b) * ADD(a,b);

    printf("e = %d
",e);
    printf("b = %d
",b);
    printf("a =%d
",a);
    printf("c =%d
",c);
    printf("d =%d
",d);
    printf("hello,world!
");
    return 0;
}
  • 巨集定義時由於是看作字串的替換,因此在設計函式時利用巨集可以不用考慮輸入值的型別,這與普通的函式定義不同。
  • #define ADD(a,b) (a+b)
  • 這裡後面的括號是為了防止呼叫多次時優先順序出錯:
  • main 函式種。ADD(2,3)*ADD(2,3)如果定義時沒有括號則是2+3*2+3其與(2+3)*(2+3)不同。因為預處理階段沒有函式運算,定義的東西只會被當作字串。但在呼叫後可以實現其功能。
  • 而普通函式例如int add(int a,int b)除了在開頭要宣告值的型別,還要設定返回值,因此在定義過程與呼叫過程相對複雜。若能用巨集定義實現的情況應優先考慮巨集定義.
float d = ADD(10.5,20.0)

巨集是不考慮資料型別等的。

條件編譯。

typedef是怎麼回事

  • typedef 簡記:取別名;但是屬於C語法,結束要加分號,與#define 不同。

  • typedef int tni;即將int 用tni代替,在之後的int定義可直接寫為:tni i;

  • typedef int *p;給int *起了別名:p,

  • 在指標定義中int * q=null即可等價為pq=null

  • 巨集是會在預處理階段被替換的,而tni不會

  • typedef struct stu{ }stu_t; 給結構體起別名

  • typedef unsigned long size_t

注意typedef如果寫在方法體內則只可作用於該作用域內{},#define一直全域性。

結構體的宣告與定義

#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 結構體型別名 變數名(前兩部分效果類似定義 int 變數 中的int)

結構體的初始化與引用。

struct weapon weapon_1 = {"weapon_name",100,200};
   printf("%s
,%d
",weapon_1.name,++weapon_1.price);

結構體元素可以和普通變數一樣進行操作。

結構體陣列

  • 運算子是一個成員運算子,在所有運算子中運算級最高,可用.運算子訪問結構體內部成員
  • //陣列裡包含兩個結構體元素,每個元素有結構體裡的三個成員
struct weapon weapon_2[2]={{"weapon_name1",50,100},{"weapon_name2",100,200}};
printf("%s
,%d
",weapon_2[0].name,weapon_2[1].atk);

結構體指標

struct weapon *w ;  //定義一個指向weapon的w結構體指標
w=&weapon_1;       //具體指向weapon_1的記憶體地址
(*w).name
w->name
weapon_1.name           //這3種訪問型別都是一樣效果
   struct weapon *w;
   w = &weapon_1;
   printf("---------------------------------
");
   printf("name=%s
,name=%s
,name=%s
",(*w).name,w->name,weapon_1.name);
   return 0;

結構體陣列指標。不用取地址符&:陣列的名字代表了這個陣列的記憶體首地址,陣列括號內的長度代表了陣列的單元數,資料型別是int的話就按照int型別(32位系統上是4個位元組)乘以單元數的長度,如果資料型別是結構體的話就按照結構體的長度乘以單元的長度。
p++,不是記憶體位置右移了一個位元組,而是右移了一個單元長度的結構體weapon的記憶體長度。所以就不難理解為什麼右移到了第二個結構體例項的首地址上了

   struct weapon *p;
   p = weapon_2; //p->name weapon_2[0].name
   printf("%s
",p->name);
   printf("---------------------------------
");
   p++;// weapon_2 +1 weapon_2[1]
   printf("%s
",p->name);
   return 0;

共用體型別是怎麼定義的以及怎麼使用的

  • union共用體是多種不同資料型別的合集,所佔位元組按照共用體裡面型別所佔最長位元組決定,
  • 例如有char(1位元組)型和int(4位元組)型,按照int型來計,整個共用體所佔四個位元組;此外,共同體裡面所有資料的地址是相同的,這個決定了它在有多種資料型別的時候,有且只能存放這些資料型別裡面的一種資料。以最後一次賦值為準。
  • struct結構體也是不同資料型別的合集,所佔位元組按照裡面所有資料型別的長度來決定,
  • 例如:char(1位元組)和int(4位元組),struct所佔空間是8個位元組,不是5個位元組,原因是涉及到位元組對齊,位元組對其是方便計算機快速的讀取資料。
  • 大小 = 最後一個成員的偏移量+最後一個成員的大小+末尾填充位元組數。
  • 偏移量,實際地址和結構體首地址之間的距離。
#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",sizeof(struct data));
    return 0;

}

a的偏移量0;b的偏移量4,自身大小為1。偏移量是大小的整數倍,不會填充空間;c的偏移量為5,c自身大小為4.要補齊為8.然後加上c自身的4。
所以整個佔用12.然後再判斷這個大小12是不是最寬成員的整數倍。

  • %p表示輸出這個指標
  • %d表示後面的輸出型別為有符號的10進位制整形,
  • %u表示無符號10進位制整型,
  • %lu表示輸出無符號長整型整數(long unsigned)
#include <stdio.h>
union data{
    int a;
    char b;
    int c;
};

int main()
{
    union data data_1;
    data_1.b =`C`;
    data_1.a =10;
    printf("%p
,%p
,%p
",&data_1.a,&data_1.b,&data_1.c);
    return 0;

}

共用體地址全部相同。

動態資料結構-靜態連結串列

靜態資料結構:

  • 如:整型、浮點型、陣列。
  • 系統分配固定大小的儲存空間

連結串列:

  • 有頭指標,存放地址,地址指向第一個元素。沒有頭指標連結串列無法訪問
  • 連結串列中的每一個元素都是一個節點。
  • 節點裡包括使用者需要的資料和下一個節點的地址,各個元素的地址不一定是連續的

靜態連結串列;(所有節點都是在程式中定義的,而不是臨時開闢的)
【由三個武器資訊的節點組成,所以用結構體型別作為節點元素】

#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
",p->atk,p->price);
    p=p->next;

   }
}

  • 連結串列:可以用malloc來動態分配所需的記憶體,並且需要用free手動釋放在堆裡面申請的記憶體。連結串列有一個頭指標和尾指標,每個指標指向的是連結串列下一個資料的地址。在結構體裡面加入指標就構成連結串列,此時指標結構體包括兩個部分,一個是資訊域,另一個是指標域。

動態連結串列

程式執行過程中從無到有的建立起一個連結串列,也就是說需要一個一個的開闢新節點,輸入新節點的資料,然後建立起前後相連的關係。
建立武器資訊的單向動態連結串列:

#include <stdio.h>
#include <malloc.h>
struct weapon{
  int price;
  int atk;
  struct weapon * next;
};
//【需要一個建立連結串列的函式,返回值是連結串列的頭指標】
struct weapon * create(){
   struct weapon *head;
   struct weapon *p1,*p2;//3個指標都用來指向struct weapon型別資料,p1,p2分別指向當前新建立和上一個。
   int n=0;//記錄當前節點個數
   //malloc分配記憶體塊的函式,sizeof判斷資料型別長度符
   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",p->price,p->atk);//列印第一個節點的資訊
   return 0;
}

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

按位與

  • 位:指二進位制數中的一位 0false 1true
  • 古老的微處理器速度:比加減運算快一些,比乘除法快很多
    現在的框架中:通常與加減運算快相同,比乘除法快很多

六種位運算子

  • & 按位與
  • | 按位或
  • ^按位異或
  • ~按位取反
  • <<左移
  • 右移

#include <stdio.h>

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

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

  int c =a&b;//0100

   printf("%d",c);
   return 0;
}
  • 位與運算就是將參與運算的兩個資料按照對應的二進位制數逐位進行邏輯與運算。
  • 參與運算的兩個數必須是整型或者是字元型。
  • 參與運算的數必須要以補碼的方式出現。

按位與的應用

  • 迅速清零(對於一個數中為1的位,讓另一個數的相應位為0);
  • 保留指定位置(對另一個數的相應位置1);
  • 奇偶判斷(和1做與運算)[a&1//得到結果1位奇數,得到0為偶數]
#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
",c);
   printf("%d
",d);
   return 0;
}

輸出為0,1.

按位或運算。

#include <stdio.h>

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

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

   int c =a|b;//1101

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

輸出為1101:13

按位或的作用:
設定資料的指定位,與255(0xFF)做或運算;

b = b| 0xFF,能設定資料b的指定二進位制數後8位置為1
即b的十進位制等於255

按位異或

將參與運算的兩個資料按對應的二進位制數逐位進行邏輯異或運算

  • 定位反轉 a^0xff;
  • 數值交換 a = a^b;b= b^a; a=a^b;

按位取反

~1000 = 0111

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

左移:將資料對應的二進位制值逐位左移若干位;高位丟棄,低位補零;乘以2的n次方
右移:將資料對應的二進位制值逐位右移若干位:低位丟棄,高位補0或1(根據符號位判斷,就是正數數補0,負數補1);除以2的n次方
高位丟棄,低位補零

遞迴呼叫

遞迴呼叫有時候會犧牲效率

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

}

int main()
{
   int a =0;
   printf("please input the num:");
   scanf("%d",&a);
   int r = func(a);
   printf("the result is %d
",r);
   return 0;
}

遞迴的工作原理。

  • 函式呼叫需要做的第一件事是為被呼叫的函式的形參分配一個臨時的記憶體單元然後才能把實參的值傳遞過來。
  • 同時還需要傳遞的是主調函式的返回地址。(保護現場)
  • 被調函式執行完了還需要繼續執行主調函式後面的程式碼。
  • 資料儲存到棧裡面。
  • 遞迴:大規模——化簡——>小規模,直到問題可求。
  • 遞迴函式同時必須有 遞迴條件和遞迴表示式,否則會進入死迴圈。
  • 遞推(for):則是由小問題的解逐步代入大問題並求出解。

總結

  • 編譯預處理:標頭檔案、巨集替換

  • 自定義資料型別:結構體、聯合體、連結串列宣告定義等,結構體涉及位元組對齊,共同體所有成員公用結構體記憶體地址

  • 邏輯運算子位運算

  • 遞迴和遞推


相關文章