C語言歸納八-結構體等各種結構

目錄

一、C語言結構體指針

1.1 定義:

1.2 獲取結構體成員:

1.3 結構體指針作爲函數參數

二、C語言枚舉類型enum用法詳解

三、C語言共用體類型union用法詳解

四、大端小端以及判別方式

定義:

爲什麼會有大小端之分?

五、C語言位域(位段)詳解

5.1 位域的存儲

六、C語言位運算(按位與運算、或運算、異或運算、左移運算、右移運算)

6.1 位運算應用

七、typedef用法

八、const用法

九、隨機數


一、C語言結構體指針

1.1 定義:

struct stu *pstu = &stu1;

1.2 獲取結構體成員:

通過結構體指針可以獲取結構體成員,一般形式爲:

(*pointer).memberName

或者:

pointer->memberName

粗糙代碼借用:

#include <stdio.h>
struct stu{
    char *name;  //姓名
    int num;  //學號
    int age;  //年齡
    char group;  //所在小組
    float score;  //成績
}stus[] = {
    {"Zhou ping", 5, 18, 'C', 145.0},
    {"Zhang ping", 4, 19, 'A', 130.5},
    {"Liu fang", 1, 18, 'A', 148.5},
    {"Cheng ling", 2, 17, 'F', 139.0},
    {"Wang ming", 3, 17, 'B', 144.5}
}, *ps;
int main(){
    //求數組長度
    int len = sizeof(stus) / sizeof(struct stu);
    printf("Name\t\tNum\tAge\tGroup\tScore\t\n");
    for(ps=stus; ps<stus+len; ps++){
        printf("%s\t%d\t%d\t%c\t%.1f\n", ps->name, ps->num, ps->age, ps->group, ps->score);
    }
    return 0;
}

1.3 結構體指針作爲函數參數

結構體就和int數據類型一樣,沒什麼特別的。

二、C語言枚舉類型enum用法詳解

#include <stdio.h>
#define Mon 1
#define Tues 2
#define Wed 3
#define Thurs 4
#define Fri 5
#define Sat 6
#define Sun 7
int main(){
    int day;
    scanf("%d", &day);
    switch(day){
        case Mon: puts("Monday"); break;
        case Tues: puts("Tuesday"); break;
        case Wed: puts("Wednesday"); break;
        case Thurs: puts("Thursday"); break;
        case Fri: puts("Friday"); break;
        case Sat: puts("Saturday"); break;
        case Sun: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

運行結果:
5↙
Friday

出一個星期有幾天:

enum week{ Mon, Tues, Wed, Thurs, Fri, Sat, Sun };

枚舉值默認從 0 開始,往後逐個加 1(遞增);也就是說,week 中的 Mon、Tues ...... Sun 對應的值分別爲 0、1 ...... 6。

我們也可以給每個名字都指定一個值:
enum week{ Mon = 1, Tues = 2, Wed = 3, Thurs = 4, Fri = 5, Sat = 6, Sun = 7 };
更爲簡單的方法是隻給第一個名字指定值:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
這樣枚舉值就從 1 開始遞增,跟上面的寫法是等效的。

定義枚舉變量

枚舉是一種類型,通過它可以定義枚舉變量:
enum week a, b, c;
也可以在定義枚舉類型的同時定義變量:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } a, b, c;
有了枚舉變量,就可以把列表中的值賦給它:
enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun };
enum week a = Mon, b = Wed, c = Sat;
#include <stdio.h>
int main(){
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day;
    scanf("%d", &day);
    switch(day){
        case Mon: puts("Monday"); break;
        case Tues: puts("Tuesday"); break;
        case Wed: puts("Wednesday"); break;
        case Thurs: puts("Thursday"); break;
        case Fri: puts("Friday"); break;
        case Sat: puts("Saturday"); break;
        case Sun: puts("Sunday"); break;
        default: puts("Error!");
    }
    return 0;
}

需要注意的兩點是:
1) 枚舉列表中的 Mon、Tues、Wed 這些標識符的作用範圍是全局的(嚴格來說是 main() 函數內部),不能再定義與它們名字相同的變量。

2) Mon、Tues、Wed 等都是常量,不能對它們賦值,只能將它們的值賦給其他的變量。

枚舉類型變量需要存放的是一個整數,我猜測它的長度和 int 應該相同,下面來驗證一下:

#include <stdio.h>
int main(){
    enum week{ Mon = 1, Tues, Wed, Thurs, Fri, Sat, Sun } day = Mon;
    printf("%d, %d, %d, %d, %d\n", sizeof(enum week), sizeof(day), sizeof(Mon), sizeof(Wed), sizeof(int) );
    return 0;
}
運行結果:
4, 4, 4, 4, 4

三、C語言共用體類型union用法詳解

定義:

union 共用體名{
    成員列表
};

共用體也是一種自定義類型,可以通過它來創建變量,例如:
union data{
    int n;
    char ch;
    double f;
};
union data a, b, c;

共用體 data 中,成員 f 佔用的內存最多,爲 8 個字節,所以 data 類型的變量(也就是 a、b、c)也佔用 8 個字節的內存。

請看下面的演示:

#include <stdio.h>
union data{
    int n;
    char ch;
    short m;
};
int main(){
    union data a;
    printf("%d, %d\n", sizeof(a), sizeof(union data) );
    a.n = 0x40;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.ch = '9';
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.m = 0x2059;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
    a.n = 0x3E25AD54;
    printf("%X, %c, %hX\n", a.n, a.ch, a.m);
   
    return 0;
}
運行結果:
4, 4
40, @, 40
39, 9, 39
2059, Y, 2059
3E25AD54, T, AD54

要想理解上面的輸出結果,弄清成員之間究竟是如何相互影響的,就得了解各個成員在內存中的分佈。以上面的 data 爲例,各個成員在內存中的分佈如下:

上圖是在絕大多數 PC 機上的內存分佈情況,要確定是大端還是小端。

接下來有個例子看一下union的應用。

Name

Num

Sex

Profession

Score / Course

HanXiaoXiao

501

f

s

89.5

YanWeiMin

1011

m

t

math

LiuZhenTao

109

f

t

English

ZhaoFeiYan

982

m

s

95.0

#define TOTAL 4  //人員總數
struct{
    char name[20];
    int num;
    char sex;
    char profession;
    union{
        float score;
        char course[20];
    } sc;
} bodys[TOTAL];

四、大端小端以及判別方式

定義:

大端和小端是指數據在內存中的存儲模式,它由 CPU 決定:

1) 大端模式(Big-endian)是指將數據的低位(比如 1234 中的 34 就是低位)放在內存的高地址上,而數據的高位(比如 1234 中的 12 就是高位)放在內存的低地址上。這種存儲模式有點兒類似於把數據當作字符串順序處理,地址由小到大增加,而數據從高位往低位

2) 小端模式(Little-endian)是指將數據的低位放在內存的低地址上,而數據的高位放在內存的高地址上。這種存儲模式將地址的高低和數據的大小結合起來,高地址存放數值較大的部分,低地址存放數值較小的部分,這和我們的思維習慣是一致,比較容易理解。

爲什麼會有大小端之分?

計算機中的數據是以字節(Byte)爲單位存儲的,每個字節都有不同的地址。現代 CPU 的位數(可以理解爲一次能處理的數據的位數)都超過了 8 位(一個字節),PC機、服務器的 CPU 基本都是 64 位的,嵌入式系統或單片機系統仍然在使用 32 位和 16 位的 CPU。

對於一次能處理多個字節的CPU,必然存在着如何安排多個字節的問題,也就是大端和小端模式。以 int 類型的 0x12345678 爲例,它佔用 4 個字節,如果是小端模式(Little-endian),那麼在內存中的分佈情況爲(假設從地址 0x 4000 開始存放):

內存地址 0x4000 0x4001 0x4002 0x4003
存放內容 0x78 0x56 0x34 0x12

如果是大端模式(Big-endian),那麼分佈情況正好相反:

內存地址 0x4000 0x4001 0x4002 0x4003
存放內容 0x12 0x34 0x56 0x78

個人感覺,小端模式的存儲類似於棧,棧頂是高地址和高位。大端模式的棧頂是高地質和低位。

佔用內存都是從低地址開始的,這就可以解釋共用體的上面的佔用了。

代碼驗證如下:

藉助共用體,我們可以檢測 CPU 是大端模式還是小端模式,請看代碼:
#include <stdio.h>
int main(){
    union{
        int n;
        char ch;
    } data;
    data.n = 0x00000001;  //也可以直接寫作 data.n = 1;
    if(data.ch == 1){
        printf("Little-endian\n");
    }else{
        printf("Big-endian\n");
    }
    return 0;
}
在PC機上的運行結果:
Little-endian

我們的 PC 機上使用的是 X86 結構的 CPU,它是小端模式;51 單片機是大端模式;很多 ARM、DSP 也是小端模式(部分 ARM 處理器還可以由硬件來選擇是大端模式還是小端模式)。

五、C語言位域(位段)詳解

在結構體定義時,我們可以指定某個成員變量所佔用的二進制位數(Bit),這就是位域。請看下面的例子:

struct bs{
    unsigned m;
    unsigned n: 4;
    unsigned char ch: 6;
};

:後面的數字用來限定成員變量佔用的位數。成員 m 沒有限制,根據數據類型即可推算出它佔用 4 個字節(Byte)的內存。成員 n、ch 被:後面的數字限制,不能再根據數據類型計算長度,它們分別佔用 4、6 位(Bit)的內存。

n、ch 的取值範圍非常有限,數據稍微大些就會發生溢出,請看下面的例子:

#include <stdio.h>
int main(){
    struct bs{
        unsigned m;
        unsigned n: 4;
        unsigned char ch: 6;
    } a = { 0xad, 0xE, '$'};
    //第一次輸出
    printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
    //更改值後再次輸出
    a.m = 0xb8901c;
    a.n = 0x2d;
    a.ch = 'z';
    printf("%#x, %#x, %c\n", a.m, a.n, a.ch);
    return 0;
}

運行結果:
0xad, 0xe, $
0xb8901c, 0xd, :

C語言標準規定,位域的寬度不能超過它所依附的數據類型的長度。通俗地講,成員變量都是有類型的,這個類型限制了成員變量的最大長度,:後面的數字不能超過這個長度。

例如上面的 bs,n 的類型是 unsigned int,長度爲 4 個字節,共計 32 位,那麼 n 後面的數字就不能超過 32;ch 的類型是 unsigned char,長度爲 1 個字節,共計 8 位,那麼 ch 後面的數字就不能超過 8。

C語言標準還規定,只有有限的幾種數據類型可以用於位域。在 ANSI C 中,這幾種數據類型是 int、signed int 和 unsigned int(int 默認就是 signed int);到了 C99,_Bool 也被支持了。

但編譯器在具體實現時都進行了擴展,額外支持了 char、signed char、unsigned char 以及 enum 類型,所以上面的代碼雖然不符合C語言標準,但它依然能夠被編譯器支持。

5.1 位域的存儲

位域的具體存儲規則如下:
1) 當相鄰成員的類型相同時,如果它們的位寬之和小於類型的 sizeof 大小,那麼後面的成員緊鄰前一個成員存儲,直到不能容納爲止;如果它們的位寬之和大於類型的 sizeof 大小,那麼後面的成員將從新的存儲單元開始,其偏移量爲類型大小的整數倍。

以下面的位域 bs 爲例:

#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 6;
        unsigned n: 12;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));
    return 0;
}
運行結果:
4

m、n、p 的類型都是 unsigned int,sizeof 的結果爲 4 個字節(Byte),也即 32 個位(Bit)。m、n、p 的位寬之和爲 6+12+4 = 22,小於 32,所以它們會挨着存儲,中間沒有縫隙。

sizeof(struct bs) 的大小之所以爲 4,而不是 3,是因爲要將內存對齊到 4 個字節,以便提高存取效率。
如果將成員 m 的位寬改爲 22,那麼輸出結果將會是 8,因爲 22+12 = 34,大於 32,n 會從新的位置開始存儲,相對 m 的偏移量是 sizeof(unsigned int),也即 4 個字節。

如果再將成員 p 的位寬也改爲 22,那麼輸出結果將會是 12,三個成員都不會挨着存儲。

2) 當相鄰成員的類型不同時,不同的編譯器有不同的實現方案,GCC 會壓縮存儲,而 VC/VS 不會。
#include <stdio.h>
int main(){
    struct bs{
        unsigned m: 12;
        unsigned char ch: 4;
        unsigned p: 4;
    };
    printf("%d\n", sizeof(struct bs));
    return 0;
}
在 GCC 下的運行結果爲 4,三個成員挨着存儲;在 VC/VS 下的運行結果爲 12,三個成員按照各自的類型存儲(與不指定位寬時的存儲方式相同)。

3) 如果成員之間穿插着非位域成員,那麼不會進行壓縮。例如對於下面的 bs:
struct bs{
    unsigned m: 12;
    unsigned ch;
    unsigned p: 4;
};
在各個編譯器下 sizeof 的結果都是 12。

六、C語言位運算(按位與運算、或運算、異或運算、左移運算、右移運算)

運算符 & | ^ ~ << >>
說明 按位與 按位或 按位異或   0^1爲1,0^0爲0,1^1爲0。 取反,~9的結果爲 -10 左移。高位丟棄,低位補0。 右移,低位丟棄,高位補 0 或 1。如果數據的最高位是 0,那麼就補 0;如果最高位是 1,那麼就補 1。

&是根據內存中的二進制位進行運算的,而不是數據的二進制形式;其他位運算符也一樣。其實就是補碼格式。

如果被丟棄的低位不包含 1,那麼右移 n 位相當於除以 2 的 n 次方(但被移除的位中經常會包含 1)。

如果數據較小,被丟棄的高位不包含 1,那麼左移 n 位相當於乘以 2 的 n 次方。

具體看鏈接:http://c.biancheng.net/view/2038.html

6.1 位運算應用

#include <stdio.h>
#include <stdlib.h>
int main(){
    char plaintext = 'a';  // 明文
    char secretkey = '!';  // 密鑰
    char ciphertext = plaintext ^ secretkey;  // 密文
    char decodetext = ciphertext ^ secretkey;  // 解密後的字符
    char buffer[9];
    printf("            char    ASCII\n");
    // itoa()用來將數字轉換爲字符串,可以設定轉換時的進制(基數)
    // 這裏將字符對應的ascii碼轉換爲二進制
    printf(" plaintext   %c     %7s\n", plaintext, itoa(plaintext, buffer, 2));
    printf(" secretkey   %c     %7s\n", secretkey, itoa(secretkey, buffer, 2));
    printf("ciphertext   %c     %7s\n", ciphertext, itoa(ciphertext, buffer, 2));
    printf("decodetext   %c     %7s\n", decodetext, itoa(decodetext, buffer, 2));
    return 0;
}

程序中的 itoa() 位於 stdlib.h 頭文件,它並不是一個標準的C函數,只有Windows下有。

itoa並不是一個標準的C函數,它是Windows特有的,如果要寫跨平臺的程序,請用sprintf。是Windows平臺下擴展的,標準庫中有sprintf,功能比這個更強,用法跟printf類似:
 

char str[255];
sprintf(str, "%x", 100); //將100轉爲16進製表示的字符串。

七、typedef用法

typedef  oldName  newName;


1.typedef int INTEGER;

2.typedef char ARRAY20[20];
表示 ARRAY20 是類型char [20]的別名。它是一個長度爲 20 的數組類型。
接着可以用 ARRAY20 定義數組:
ARRAY20 a1, a2, s1, s2;
它等價於:
char a1[20], a2[20], s1[20], s2[20];

3.typedef struct stu{
    char name[20];
    int age;
    char sex;
} STU;
STU body1,body2;

4.typedef int (*PTR_TO_ARR)[4];
表示 PTR_TO_ARR 是類型int * [4]的別名,它是一個二維數組指針類型。接着可以使用 PTR_TO_ARR 定義二維數組指針:
PTR_TO_ARR p1, p2;

5.函數指針別名
typedef int (*PTR_TO_FUNC)(int, int);
PTR_TO_FUNC pfunc;
#include <stdio.h>

typedef char (*PTR_TO_ARR)[30];//數組指針別名
typedef int (*PTR_TO_FUNC)(int, int);//函數指針別名

int max(int a, int b){
    return a>b ? a : b;
}
char str[3][30] = {
    "http://c.biancheng.net",
    "C語言中文網",
    "C-Language"
};

int main(){
    PTR_TO_ARR parr = str;
    PTR_TO_FUNC pfunc = max;
    int i;
   
    printf("max: %d\n", (*pfunc)(10, 20));
    for(i=0; i<3; i++){
        printf("str[%d]: %s\n", i, *(parr+i));
    }
    return 0;
}
運行結果:
max: 20
str[0]: http://c.biancheng.net
str[1]: C語言中文網
str[2]: C-Language

還有些我覺得不需要講,自己可以判斷出來,#define宏替換純替換,typedef是數據類型別名定義。

八、const用法

const type name = value;

建議將常量名的首字母大寫,以提醒程序員這是個常量。

http://c.biancheng.net/view/2041.html

九、隨機數

http://c.biancheng.net/view/2043.html

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章