C語言學習筆記(五)——複合類型

1.結構體

#include <stdio.h>
#include<string.h>
#include<stdlib.h>
struct student
{
    char name[100];
    int age;
};//注意這裏有;

int main()
{
    struct student st;//定義了一個student類型的結構體變量,名字叫st
    st.age=20;
    strcpy(st.name,"劉某");
    printf("name=%s,age=%d\n",st.name,st.age);
    return 0;

}

初始化方法

#include <stdio.h>
#include<string.h>
#include<stdlib.h>
struct student
{
    char name[100];
    int age;
};//注意這裏有;
int main()
{
    //struct student st={"王某",50};//定義結構變量的時候,同時初始化成員變量的值,很像數組
    //struct student st={"王某"};//定義結構變量的時候,同時初始化成員變量的值,如果沒輸入,默認零
    //struct student st={0};//定義結構變量的時候,同時初始化成員變量的值,字符串空,年齡是0
    //struct student st={.age=30, .name="孫某"};//定義結構變量的時候,同時初始化成員變量的值,可以用這種方式打亂順序
    printf("name=%s,age=%d\n",st.name,st.age);
    return 0;
}

1.1結構體的對齊

一個結構體變量成員總是以最大的那個元素作爲對齊單位

#include <stdio.h>
#include<string.h>
#include<stdlib.h>
struct A
{
    char a1;
    char a2;//如果不考慮a3,增加一個成員,下面輸出a佔用的內存增加一個字節
    int a3;//這裏a佔用的內存爲八字節,因爲要以int對齊,總是以int爲單位來變化
};//注意這裏有;
struct B
{
    char a1;
    short a2;
    char a4;//在這裏放char的時候,short已經把內存佔了,沒地方放char了,所以結構體B佔用總爲12個字節。
    int a3;//上邊兩個加起來沒一個int大,所以,爲八個字節,但是不知道short的位置
};

int main()
struct A a;
struct B b={1,2,3};
printf("%u\n",sizeof(a));//輸出a佔用的內存爲一個字節
printf("%p\n",&a);//在這裏做一個斷點,然後單步調試,選擇調試,內存,查看內存內容
return 0;

圖形說明上述程序:
這裏寫圖片描述
對於structB
這裏寫圖片描述
加了char a4的情況爲:

這裏寫圖片描述
此時,非常簡單的優化方法是把a4和a3換個位置,減少內存浪費。

#include <stdio.h>
#include<string.h>
#include<stdlib.h>
struct D
{
    char a[10];

    int b;//此時輸出d的佔用內存大小爲16,可以肯定它沒有按照char a[10]對齊,否則應該是十的倍數,其實它還是按照int對齊的,是4的倍數,如果結構體成員出現數組,那麼是以數組的具體每個成員作爲對齊標準。
    //如果int b改成char b,那麼這個結構體中的成員都是一種類型,那麼這個結構變量在內存中就基本和一個數組類似。可以用char *s=&d;結構體本身是個變量,不能直接當數組名用,所以要取地址。甚至可以寫成s[0]=1;s[10]=2;
};//注意這裏有;
struct E 
{
    char a1;
    short a2;//有一個字節被浪費了
    int a3;
};
struct F
{
    char a1;
    short a2;
    int a3;
    short a4;
    char a5;//此時,a1a2a3a4的內存位置都已知,a5是處於緊挨着short後面的下一個地址還是填充最後一個字節呢?結果是緊挨着的那個地址,最後一個字節是空的。
};
int main()
{
    struct D d;
    printf("%u\n",sizeof(d));
    return 0;
    printf("%p,%p\n",&d,d.a);//d.a是一個數組名,這裏要弄清楚,這倆地址是一樣的,結構體變量的地址,就是這個結構體首元素的地址
    struct E e={1,2,3};
    s=&e;
    s[1]=10;//可以通過指針,變相的訪問那個被浪費的字節。
}

1.2結構體的位字段

#include<stdio.h>
struct A 
{
    char a : 2;//a只有兩個比特(一個char是八個)
    char b : 4;//b是四個比特
};
struct B
{
    unsigned char a1 : 1;
    unsigned char a2 : 1;
    unsigned char a3 : 1;
    unsigned char a4 : 1;
    unsigned char a5 : 1;
    unsigned char a6 : 1;
    unsigned char a7 : 1;
    unsigned char a8 : 1;//LED燈控制,按位控制,分別給每一位賦值0、1,此時b佔用內存爲一字節

};
struct C 
{
    char a1 : 1;
    int a2 : 1;//雖然只佔了兩個比特,但是這個結構佔8個字節
};
int main()
{
    struct A a;
    a.a=5;//此時輸出的a.a爲1,因爲只有兩位
    printf("%x\n",a.a)
    return 0;
}

1.3結構體數組

#include <stdio.h>
#include <string.h>
#pragma warning (disable:4996)
struct student
{
char name [20];
unsigned  char age;
int sex;//cpu處理int的效率是最高的,int比char要多佔內存而已
};
int main()
{   int i;
    //struct student st[3]={{"abc",30,1}{"cxe",20,0}{"asfka",50,0}};//定義一個結構體數組,有三個成員,每個成員都是struct student
    struct student st[]={{}{}{}{}{}};//對於這種情況,要使用sizeof(st)/sizeof(st[0])來構造循環
    //for(i=0;i<3;i++)
    //{
        //scanf("%s\n",st[i].name);
        //scanf("%d\n",&st[i].age);
        //scanf("%d\n",&st[i].sex);
    //}
    for(i=0;i<3;i++)
        printf("%s,%d,%d\n",st[i].name,st[i].age,st[i].sex);
}

那麼對於一個結構數組,如何對其按年齡排序呢?
這裏假設有五個成員的結構數組

void swap_str (char *a,char *b)
{
    char temp[20]={0};
    strcpy(tmp,a);
    strcpy(a,b);
    strcpy(b,tmp)
}
void swap_int (int *a,int *b)
{
    int temp=*a;
    *a=*b;
    *b=temp
}

for(i=0;i<5;i++){
    for (j=1;j<5-i;j++){
        if(st[j].age<st[j-1].age){
        swap_str(st[j].name,st[j-1].name);
        swap_int(st[j].age,st[j-1].age);
        swap_str(st[j].sex,st[j-1].sex);}}}

1.4結構體嵌套

#include <stdio.h>
struct A 
{
    char a1;
};
struct B
{
    struct A a;
    int a1;
}
//struct D
{};//這種不行,至少要有一個成員,這個語法在C++是合法的。
struct A1
{
    int a1;
    char a2;
}
struct A2
{
    struct A1 a1;//這裏是一個結構體的嵌套
    char a2;//上面結構體變量作爲一個整體存在,不可能把a2補到a1的後面去,所以a2一定是一個單獨的對齊單位。具體在內存中的分佈見下圖。
    int a3;
}
int main()
{
    struct B b;
    printf("%u\n",sizeof(b));//和一個結構體類似,以int對齊,把A當一個char來用了
    b.a.a1=0;//通過這種方法使用A的成員
    return 0;
}

這裏寫圖片描述

注意結構體變量的賦值
簡單的用僞代碼表示:

struct student st1={"abc",30};
struct student st2;
st2=st1;//可以通過這種方式給結構體變量賦值,賦值就是內存拷貝
//mencpy(&st2,&st1,sizeof(st1));//由於賦值是內存拷貝,所以可以用這個

1.5指向結構體的指針

->操作符
(*p).a等同於p->a

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma warning(disable:4996)
struct student
{
    char name[20];
    int age;
};
int main()
{
    struct student st1 = {"abc", 30};
    struct student st2;
    st2 = st1;//結構體變量的賦值,賦值就是內存拷貝
    //memcpy(&st2, &st1, sizeof(st1));
    printf("%s, %d\n", st2.name, st2.age);

    struct student *p;
    p = &st1;
    //strcpy((*p).name, "hello");//加括號是因爲.的優先級比*高,所以要加括號
    //(*p).age = 50;
    strcpy(p->name, "hello");
    p->age = 50;
    printf("%s, %d\n", st1.name, st1.age);
    return 0;
}

結構體指針和數組的關係

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma warning(disable:4996)
struct student
{
    char name[20];
    int age;
};
struct man
{
    char *name;
    int age;
};
int main()
{
    struct student st[3]={{"張三"34}{"李四",}{"王二",}}
    struct student *p=st;
    p->age=100;//修改的是張三的年齡
    p++;
    p->age=50;//此時p不再指向張三了,所以輸出會錯,所以要p--;
    p--;
    int i;
    for (i=0;i<3;i++)
    {
    printf("%s,%d",p[i].name,p[i].age)
    }
}

結構中的數組成員和指針成員

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma warning(disable:4996)
struct student
{
    char name[20];
    int age;
};


struct man
{
    char *name;
    int age;
};
int main()
{
    struct student st = { 0 };
    struct student st1 = { 0 };
    strcpy(st.name, "劉德華");
    st.age = 30;
    st1 = st;//結構體變量賦值
    printf("%s, %d\n", st1.name, st1.age);
    struct man m = { 0 };//m裏面的name是什麼?是空指針,指向一個無效地址,那麼後面的strcpy無法操作
    struct man m1 = { 0 };
    m.name = calloc(20, sizeof(char));//用堆內存,避免上面的問題
    strcpy(m.name, "張學友");
    m.age = 40;
    //m1 = m;//淺拷貝
    //free(m.name);//此時後面輸出的m1就會出現亂碼,原因見下圖
    m1.name = calloc(20, sizeof(char));
    memcpy(m1.name, m.name, 20);//深拷貝
    m1.age = m.age;
    free(m.name);
    printf("%s, %d\n", m1.name, m1.age);
    free(m1.name);
    return 0;
}

賦值後,都指向了同一個堆空間,但是堆空間被free了,m1就指向一個無效的空間了,m1就成爲了野指針。
這裏寫圖片描述
堆中創建結構體變量

```
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#pragma warning(disable:4996)
struct student
{
    char name[20];
    int age;
};
struct man
{
    char *name;
    int age;
};
int main()
{
    struct student st;//請問st.name在堆裏面還是在棧裏面(在棧裏)
    struct student *p = malloc(sizeof(struct student));//請問p->name在堆裏面,還是在棧裏面(在堆裏)
    free(p);
    struct man *p1 = malloc(sizeof(struct man));//申請一個堆空間,p1->name在哪裏?在堆裏,但是是一個野指針,也就是這個堆裏第一個成員是個char* 是個野指針
    p1->name = malloc(20);
    strcpy(p1->name, "蒼老師");
    p1->age = 20;
    free(p1->name);
    free(p1);
    //free(p1->name);//name在堆中已經被釋放了
    return 0;
}

函數的參數爲結構體變量

#include<stdio.h>
#include<string.h>
struct student 
{
    char name[20];
    int age;
};
void print_student(struct student st)//在調用函數的時候,在棧裏有個淺拷貝的過程st=s
{
    printf("%s,%d\n",st.name,st.age);
}
void print_student_new(struct student *st)//st=&s;地址編號賦值,其效率遠遠高於上一個操作。要保護的話,可以加const struct
{
    printf("%s,%d\n",st->name,st->age);
}
void set_student1(struct student *st)
{
    strcpy(st->name,"123456");
    st->age=0;
}
void set_student(struct student st)
{
    strcpy(st.name,"123456");
    st.age=0;
}
int main ()
{
    struct student s={"abc",40};
    //set_student(&s);//s的值在函數調用後不會改變,st是形參
    set_student1;//這樣纔會改變
    print_student(&s);
    return 0;
}

2.聯合體

聯合union是一個能在同一個存儲空間存儲不同類型數據的類型。
聯合體所佔的內存長度等於其最長成員的長度,也有叫做共用體。
聯合體雖然可以有多個成員,但同一時間只能存放其中一種。

#include<stdio>
union A
{
    int a1;
    short a2;
    char a3;
    char *p;
};
int main()
{
    union A a;
    a.a1=1printf("%u\n",sizeof(a));//此時輸出4
    a.a3=10;
    a.a1=0;
    printf("%d\n",a.a3);//此時輸出0,也就是三個成員互相影響,他們共用一個內存,輸出a1,a2,a3的地址是相同的。
    a.a1=0x12345678;
    a.a3=0;//此時輸出a1的值爲0x12345600因爲a3只佔一個字節,只能影響a1的最後一個字節。
    a.a2=0;//此時輸出0x12340000
    a.p=malloc(10);//假設這塊堆內存的編號爲0x12345
    a.a1=0;//此時導致p的值也成了0了。內存泄露了
    free(a.p);
    return 0;
}

3.枚舉

可以使用枚舉(enumerated type)聲明代表整數常量的符號名稱,關鍵字enum創建一個新的枚舉類型。
實際上,enum常量是int類型的。
增加代碼可讀性。

#include<stdio>
struct man
{
    char *name;
    int age;
    int sex;
};
enum spectrum {red,yellow,green,blue,white,black};
//enum spectrum {red=100,yellow,green,blue,white,black};//可以改變默認值,此時爲100,101,102...
//enum spectrum {red=100,yellow=10,green,blue,white,black};//後面順着10順延。
enum sex{man,woman};
int main()
{
    struct man m;
    strcpy(m.name,"tom");
    m.age=20;
    //enum sex s;
    //s=man;
    //m.sex=man;
    m.sex=man;在c語言中可以直接這樣用,因爲man women是整形的常量,常量不能取地址。
    int *p=&red;//錯誤,和下面的100一樣,100是系統內由CPU產生的立即數
    int *p=100;
    int a=100;//CPU生成一個立即數100,在棧中分配4個BYTE的空間,然後把這個空間設置爲100。
    const char *p="hello";//"hello"在內存的常量區裏面
    //red=5;//錯誤。常量不能做左值。
    //m.sex=1;//可能會導致忘記0/1的意義,可以用#define MAN 1 那麼可以改成m.sex=MAN;但是如果量不止0/1就會很麻煩,就需要用到枚舉。
    enum spectrum color;
    color=red;
}

4 typedef

typedef是一種高級數據特性,它能使某一類型創建自己的名字
1與#define不同,typedef僅限於數據類型,而不是能是表達式或具體的值
2typedef是編譯器處理的,而不是預編譯指令
3typedef比#define更靈活
直接看typedef好像沒什麼用處,使用BYTE定義一個unsigned char。使用typedef可以增加程序的可移植性。

#include<stdio.h>
typedef unsigned char BYTE;//多了一種數據類型叫BYTE
struct man
{
    char name [20];
    //unsigned char age;
    BYTE age;
};
typedef struct man M;//M就類似於int,就是一種數據類型了。
int main()
{
    //struct man m;
    M m;
    m.age=20;
    BYTE a=0;

    return 0;
}

假設一種情況,定義了許多short
short a1;
short a2;
short a3;
short a4;
short a5;
short a6;
假如需要把short改成int就很麻煩。
那麼可以用 typedef short SHORT 增加程序的可維護性
另外,微軟常用的做法是用typedef定義所有的類型,這樣的話,如果不同的語言之間可以進行移植。
例如:

#define UNICODE
#ifdef UNICODE//是否define了UNICODE ,是寬碼操作系統,在寬碼操作系統內部處理字符串的時候一般用wchar_t
typeof wchar_t TCHAR;
#else
typeof char TCHAR;
#endif

5.習題

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
struct man
{
    //char name[20];//不好確定名字長度。通過堆解決此問題
    char *name;
    int age;
    int classid;
};
int main()
{
    //struct man *p=calloc(10,sizeof(struct man));//在堆中分配了10個man,如果輸入的數目也不確定,該如何修改呢?
    struct man *p=NULL;

    int i = 1;
    //for (i=0;i<10;i++)//把這個改成while
    while(1)
    {
        printf("please input name:");
        char tmp[1024]={0};
//如果用戶的名字等於exit,我們就退出循環
        int len=strlen(tmp);
        if(strcmp(tmp,"exit")==0);
        break;
        p=realloc(p,sizeof(struct man)*i);

        p[i-1].name=malloc(len+1);
        strcpy(p[i-1].name,tmp);
        printf("please input age");
        scanf("%d",&p[i-1].age);
        printf("please input classid");
        scanf("%d",&p[i-1].classid);
        i++;
    }
    int a ;
    for (a=0;a<i;a++)
    {
        printf("%s,%d,%d\n",p[a].name,p[a].age,p[a].classid);
    }
    for (a=0;a<i;a++)
    {
        free(p[a].name);//分別釋放離散堆空間
    }
    free(p);//釋放堆中連續的空間,就是堆中的數組
    return 0;
}

這種用指針的方法相對於數組的優點在於,直接在數組中添加刪除元素代價非常大,而指針可以非常迅速的處理。
這裏寫圖片描述

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