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=1;
printf("%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;
}
這種用指針的方法相對於數組的優點在於,直接在數組中添加刪除元素代價非常大,而指針可以非常迅速的處理。