struct 結構體
struct即結構體,C程序中經常需要用相關的不同類型的數據來描述一個數據對象。例如,描述學生的綜合信息時,需要使用學生的學号、姓名、性别等不同類型的數據時,像這種數據類型總是在一起出現,那麼我們不如把這些變量裝入同一個“文件夾”中,這時用的關鍵字struct聲明的一種數據類型就是表示這個“文件夾”的使用。那麼在說明和使用之前必須先定義它,也就是構造它。如同在說明和調用函數之前要先定義一樣。
結構體是一種集合,它裡面包含了多個變量或數組,它們的類型可以相同,也可以不同,每個這樣的變量或數組都稱為結構體的成員,結構體也是一種數據類型,它由程序員自己定義,可以包含多個其他類型的數據,成員又稱為成員變量,它是結構體所包含的若幹個基本的結構類型,必須用“{}”括起來,并且要以分号結束,每個成員應表明具體的數據類型,成員一般用名字訪問。結構體和數組類似,也是一組數據的集合,整體使用沒有太大的意義。數組使用下标[ ]獲訪問元素,結構體使用點号.訪問單個成員。通過這種方式可以獲取成員的值,也可以給成員賦值
數組:a[0]=10; 結構體:today.day (指針結構體用->訪問)結構體的成員可以包含其他結構體,也可以包含指向自己結構體類型的指針,而通常這種指針的應用是為了實現一些更高級的數據結構如鍊表和樹等。
聲明定義結構:
struct關鍵字 結構體的标志名 大括号裡邊是成員 }後面的聲明此結構變量 末尾分号,一般有這些:
struct week{定義一
int x;
char y;
};
struct week p1,p2;
//聲明變量p1,p2,裡邊都是week的值
//裡邊有x和y的值
//用.訪問 :p1.x p2.x// p1.y, p2.y
struct{定義二
int x;
char y;
}p1,p2;//在這裡聲明變量
//p1和p2都是一種無名結構,
// 裡邊有X和y 訪問一樣用.
struct week {定義三
int x;
int y;
}p1,p2;
//常用的一種結構定義聲明形式
對于第一和第三種形式,都聲明了結構名week,但是第二種沒有聲明結構名,隻是定義了兩個結構變量,
這種叫無名結構
無名結構: 可以定義無名結構體類型的變量。編譯器對無名結構體的處理是随機生成一個不重複的變量名。
無名結構的定義方式就是定義無名結構體時必須定義該結構體類型的至少一個變量。
優點:無名結構體的妙用就是可以避免相同類型的結構體的重複定義,
這樣可以對每一個具體類型的隊列都可以定義一個結構體來管理該隊列的頭尾指針,
即使定義多個相同具體類型的隊列也不會引發重複定義的編譯錯誤。這樣定義了兩個隊列,
其元素類型均為int類型,同時各得到了一個維護隊列頭尾指針的結構體
缺點:這裡定義了一個無名的結構體,同時聲明了三個此種類型的變量。
但是,因為沒有名字,我們在這句之後,無法内再定義與那三種變量相同類型的變量了。
除非你再容次去定義一個這樣的相同的結構體類型。
還有一個重要的原因就是沒有辦法在其他位置定義我們所需要的結構體變量,
每次需要新定義結構體變量的時候都必須要找到最開始結構體代碼書寫的位置才能定義新的結構體
所以實際編程中無名結構并不常用
注意:
1、結構體本身并不會被作為數據而開辟内存,真正作為數據而在内存中存儲的是這種結構體所定義的變量。
2、先聲明結構體類型,再定義該類型的變量,聲明結構體類型,不分配空間定義結構體類型變量,就要分配内存空間
3、量使用占為少的類型,如,在可能的時候使用short代替int,按數據類型本身占用的位置從大到小排
4、除了可以對成員進行逐一賦值,也可以在定義時整體賦值:p1={struct week}{5,10}; 相當于 p1.x=5,p1.y=10;
p1=p2 表示 p1.x=p2.x , p1.y=p2.y; 不過整體賦值僅限于定義結構體變量的時候,在使用過程中隻能對成員逐一賦值
5、結構體變量不能相加,相減,也不能相互乘除,但結構體可以相互賦值,也就是說,可以将一個結構體變量賦值給另一個結構體變量。但是前提是這兩個結構體變量的結構體類型必須相同
結構體的運算:要訪問整個結構,直接用結構變量的名字,對于整個結構,可以做賦值,取地址,也可以傳遞給函數參數
結構體數值嵌套的結構體:
struct week{
int x;
int y;
strcut week at;//在結構體又定義了名為at的一個和week同樣參數的結構體變量
//其中可以用.運算符訪問 see.at.x see.at.y
}see;
但是其實這樣的方式是不建議(非法)的,因為這種聲明實際上是一個無限循環,成員at是一個結構體,
at的内部還會有成員是結構體,依次下去,無線循環。在分配内存的時候,由于無限嵌套,
也無法确定這個結構體的長度,所以這種方式是非法的
正确的方式是使用《結構體指針》,因為指針的長度是确定的:
struct week{
int x;
int y;
strcut week *at;//在結構體内定義了一個指向和week一樣類型的結構指針
}see; 但是注意用指針訪問時要用->運算符 see.at->x
結構體相互引用:
一個結構體A中包含一個或多個與結構體B相關的成員, 且結構體B中也包含一個或多個與結構體A相關的成員稱為結構體的互引用.
但是要注意: 如果已經定義了兩個結構A和B ,在定義結構體A的成員b時,結構體B對A還未可見,故此時編譯器會報數據類型B未定義
解決的辦法是使用不完整聲明:
strcut A;//不完整聲明
strcut B;//不完整聲明
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B a; struct _A b; //在結構B中定義了一個名為b的和A結構一樣類型的結構變量
//其中可以用點訪問 A.a.x B.b.x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結構的錯誤,就是結構體A和B都是直接包含了對方,
正确的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結構B中定義了一個名為b的和A結構一樣類型的結構指針
//其中指針要用->訪問 A.a->x B.b->x
}A; }B;
//但是注意這種方式犯了一個和上面第一個嵌套結構的錯誤,就是結構體A和B都是直接包含了對方,正确的用法還是使用指針:
strcut _A{ strcut _B{
int x; int x;
int y; int y;
struct _B *a; struct _A *b; //在結構B中定義了一個名為b的和A結構一樣類型的結構指針
//其中指針要用->訪問 A.a->x B.b->x
}A; }B;
//所以使用互引用要注意:至少有一個結構必須在另一個結構體中以指針的形式被引用。
結構體函數與函數參數結構體做函數形參:
整個結構可以作為參數的值傳入函數,這時候是在函數内新建一個結構變量,并複制調用者結構的值,也可以返回一個值,這和數組完全不同
用結構體變量作實參時,采取的也是“值傳遞”方式,将 結構體變量所占的内存單元的内容(結構體變量成員列表) 全部順序傳遞給形參,這裡形參也得是結構體變量。
#include<stdio.h>
typedef struct _node {
int n;
char a[100];
}NODE;
void add(NODE a);//這種形式隻是用來做值的傳遞
int main(void) {
//以傳值方式傳遞結構需要對整個結構做一份拷貝
NODE t;
scanf("%d %d", &t.a[0], &t.n);//輸入1 3
printf("1-%d %d\n",t.a[0],t.n);//輸出 1 3
add(t);
printf("3-%d %d\n", t.a[0], t.n);//輸出1 3
//也就是說在add函數裡邊做修改根本就影響不了主函數這邊的值
}
void add(NODE a) {
a.a[0] = 100;//在這裡能接受到NODE結構裡邊的成員
a.n = 666;
printf("2-%d %d\n", a.a[0], a.n);//輸出100 666
}
****//解決辦法是用指針(也是經常用的方式):****
#include<stdio.h>
typedef struct _node {
int n;
char a[100];
}NODE;
int add(NODE a);//這種形式隻是用來做值的傳遞
int main(void) {
//以傳值方式傳遞結構需要對整個結構做一份拷貝
NODE t;
scanf("%d %d", &t.a[0], &t.n);//輸入1 3
printf("1-%d %d\n",t.a[0],t.n);//輸出 1 3
add(&t);//這裡傳進去的是t的地址
printf("3-%d %d\n", t.a[0], t.n);//輸出100 666
//傳進去的是地址,所以就可以達到訪問同一個變量的操作
}
int add(NODE *) {//定義一個結構指針
a.a[0] = 100;//在這裡能接受到NODE結構裡邊的成員
a.n = 666;
printf("2-%d %d\n", a.a[0], a.n);//輸出100 666
return a;//這裡返回的是指針 所以能達到訪問主函數裡邊調用的值
//使用指針才可以用返回值
}
//常用的方式
另一種做法
結構體做函數:
/*上面的第一個的方案,把一個結構傳入了函數,然後在函數中操作,但是沒有返回回去
問題在于傳入函數的是外面那個結構的克隆體,而不是指針,傳入結構和傳入數組是不同的,
解決辦法是在這個輸入函數中,在裡邊創建一個臨時的結構變量,然後把這個結構返回給調用者*/
#include<stdio.h>
typedef struct _node {
int x;
int y;
}NODE;
struct _node add();//定義結構類型的函數
int main(void) {
NODE a;
a.x = 0;
a.y = 0;
printf("1-%d %d\n", a.x, a.y);// 0 0
a = add();//函數調用 /把n的值又返回到a
printf("3-%d %d\n", a.x, a.y);//所以在這裡的時候值已經被改變
return 0;
}
struct _node add() {
NODE n;
scanf("%d", &n.x);//輸入1 3
scanf("%d", &n.y);
printf("2-%d %d\n", n.x, n.y);//在這裡的時候賦值就成功了
//return n;//把n的值帶回出去
}
//這種方法也能達到“改變"的效果,但是往往開銷内存較大,所以一般情況都是使用指針比較方便
用結構體變量名作參數,這種傳遞方式是單向的,如果在執行被調函數期間改變了形參(也是結構體變量)的值,該值不能返回主調函數,這往往造成使用上的不便,因此一般少用這種方法。
和本地變量一樣。在函數内部聲明的結構隻能在函數内部使用,所以通常在函數外部聲明一個結構類型的,這樣就可以被多個函數所使用
//結構做函數參數例子 (輸入今天計算明天)
#include<stdio.h>
#include<stdbool.h>//利用布爾數據類型
struct date {
int year;
int month;
int day;
};
bool If(struct date p);//判斷是否是閏年
int number(struct date c);//判斷是否是此月最後一天
int main(void) {
struct date today,tomorrow;
printf("年-月-日\n");
scanf("%d %d %d", &today.year, &today.month, &today.day);
//前面兩個判斷 是否此月最後一天 是否此年此月最後一天
if (today.day==number(today)&&today.month!=12) {//首月1号
tomorrow.day = 1;
tomorrow.month =today.month 1;
tomorrow.year = today.year;
}
else if (today.day == number(today) && today.month == 12) {//下一年
tomorrow.day = 1;
tomorrow.month = 1;
tomorrow.year =today.year 1;
}
else {
tomorrow.day =today.day 1;
tomorrow.month = today.month;
tomorrow.year = today.year;
}
printf("明天是%d-%d-%d\n", tomorrow.year, tomorrow.month, tomorrow.day);
return 0;
}
int number(struct date c)//這裡的形參接收的today結構體數據
{
int day;
const int a[12] = { 31,28,31,30,31,30,31,31,30,31,30,31 };//這個月最大的天數
if (c.month==22&&If(c)) {//查看是否是二月并且是潤年
day = 29;//是潤年
}
else {
day = a[c.month - 1];
}
return day;
}
bool If(struct date p) {//這裡的形參接收的today結構體數據
//潤年的特點,能被4整除,但不能被100整數,能被100整除,但是不能被400整除
if (p.year % 4 == 0 && p.year / 100 != 0 || p.year % 400 == 0) {
return true;
}
else {
return false;
}
}
//結構體做函數例子 (計算下一秒)
#include<stdio.h>
struct time {
int hour;
int minute;
int second;
};
struct time times(struct time now);//利用結構做函數返回值,形參也是使用結構體做為傳值
int main(void) {
struct time nows[5] = {
{11,50,20},{13,25,59},{12,59,59},{23,59,59},{00,00,00},
};
int i;
for (i = 0; i < 5; i ) {
printf("時間是 %d:%d:%d\n", nows[i].hour, nows[i].minute, nows[i].second);
nows[i] = times(nows[i]);
printf("下一秒是 %d:%d:%d\n", nows[i].hour, nows[i].minute, nows[i].second);
}
return 0;
}
struct time times(struct time now) {
now.second ;
if (now.second == 60) {//60秒
now.minute ;
now.second = 0;
if (now.minute == 60)//60分
{
now.hour ;
now.minute = 0;
now.second = 0;
if (now.hour == 24) {//零點
now.hour=0;
now.minute = 0;
now.second = 0;
}
}
}
return now;//返回類型必須也函數類型一緻,換句話說隻有結構體類型才能返回結構體類型
}
結構體數組結構體數組,是指數組中的每個元素都是一個結構體。在實際應用中,C語言結構體數組常被用來表示一個擁有相同數據結構的群體,比如一個班的學生、一個車間的職工等。結構體可以存儲不同的數據類型,将他們互相聯系起來。結構體數組可以連續存儲多個結構體,和數組作用相似。比如想定義同一個最小外接矩形的四個坐标值,并給予這個矩形一個特征編号。當需要存儲多個最小外接矩形的信息時,就需要動态申請一個結構體數組
定義結構體數組的方法很簡單,同定義結構體變量是一樣的,隻不過将變量改成數組。或者說同前面介紹的普通數組的定義是一模一樣的:struct student tp[10]; 這就定義了一個結構體數組,共有 10 個元素,每個元素都是一個結構體變量,都包含所有的結構體成員。
結構體數組的初始化與前面講的數值型數組的初始化也是一樣的,數值型數組初始化的方法和需要注意的問題在結構體數組的初始化中同樣适用,因為不管是數值型數組還是結構體數組都是數組。
//例子: //尋找學生中 學号最大的
# include <stdio.h>
# include <string.h>
struct STU
{
char name[20];
int age;
char sex[20];
char num[20];
};
void OutputSTU(struct STU stu[]); //函數聲明, 該函數的功能是輸出成績最大的學生信息
int main(void)
{
int i;
struct STU stu[5];
for (i = 0; i < 2; i)
{
printf("請按照名字、年齡、性别、學号(1-9數字)輸入第%d個學生的信息:", i 1);
scanf("%s %d %s %s", stu[i].name, &stu[i].age, stu[i].sex, stu[i].num);/*%c前面要加空格, 不然輸入時會将空格賦給%c*/
}
OutputSTU(stu);
return 0;
}
void OutputSTU(struct STU stu[])
{
struct STU stumax = stu[0];//讓臨時結構stumax保存第一個學生的信息
int j;
for (j = 1; j < 2; j)//第一個學生依次和後面的學生比較
{
if (strcmp(stumax.num, stu[j].num) < 0) //strcmp函數的使用 s1>s2:1 s1<s2:-1
{
stumax = stu[j];//讓臨時結構保存那個學生的信息
}
}
printf("學生姓名:%s 學生年齡:%d 學生性别:%s 學生分數:%s\n", stumax.name, stumax.age, stumax.sex, stumax.num);
}
結構體指針和數組不同,結構變量的名字并不是結構變量的地址,必須使用&運算符 strcut node *tp=&nb; 指針一般用->訪問結構體裡邊的成員
指針變量非常靈活方便,可以指向任一類型的變量 ,若定義指針變量指向結構體類型變量,則可以通過指針來引用結構體類型變量。
#include<stdio.h>
struct node{
int x;
int y;
}my;
int main(void) {
struct node *p = &my;//定義了一個指針p指向了my的結構體
p->x = 11;//這是一種訪問方式(常用的方式)
(*p).x = 12;//這是第二種方式,
printf("%d", p->x);//輸出是12
}
以下 2 種形式是等價的:
(*指針變量).成員名。
指針變量->成員名。
其中第 2 種方式很重要,通常都是使用這種方式,另外兩種方式用得不多。
後面講鍊表的時候用的也都是第 3 種方式。
這裡說明:結構體和結構體變量是兩個不同的概念:結構體是一種數據類型,是一種創建變量的模闆,編譯器不會為它分配内存空間,就像 int、float、char 這些關鍵字本身不占用内存一樣;結構體變量才包含實實在在的數據,才需要内存來存儲。所以用一個結構體去取一個結構體名的地址,這種寫法是錯誤的,也不能将它賦值給其他變量。
#include<stdio.h>
struct point {
int x;
int y;
};
struct point *gt(struct point*p);//結構指針函數
void print(const struct point *p);//結構指針
void out(struct point p);//普通的結構體做函數參數
int main(void) {
struct point y = { 0,0 };//以point結構定義一個y的結構變量
//以下三種調用 等價
//注意gt是一個結構體的指針函數
gt(&y); //這是一個函數的返回結果函數 //取y結構的地址傳入函數
out(y);
out(*gt(&y)); // (裡邊)的都是做為參數 *gt(&y)做為指針返回值 這個函數它的返回用指針表示
print(gt(&y)); //gt(&y)是一個返回值 這樣表示的是利用gt函數的返回值在print函數裡邊操作
//*get(&y) = (struct point){ 1,2 }; //這也可以做的
}
struct point* gt(struct point*p) {// *p要的是&y的地址
scanf("%d", &p->x);
scanf("%d", &p->y);
printf("a=%d,%d \n", p->x, p->y);//用->來訪問指針結構裡邊的成員
return p;// 用完指針後 返回指針
}
void out(struct point p) {
printf("b=%d,%d\n", p.x, p.y);
}
void print(const struct point *p) {//加上const表示不再改動參數
printf("c=%d,%d\n", p->x, p->y);
}
指向結構體數組的指針:
在之前講數值型數組的時候可以将數組名賦給一個指針變量,從而使該指針變量指向數組的首地址,然後用指針訪問數組的元素。結構體數組也是數組,所以同樣可以這麼做。
我們知道,結構體數組的每一個元素都是一個結構體變量。如果定義一個結構體指針變量并把結構體數組的數組名賦給這個指針變量的話,就意味着将結構體數組的第一個元素,即第一個結構體變量的地址,也即第一個結構變量中的第一個成員的地址賦給了這個指針變量
# include <stdio.h>
struct qt
{
char name[5];
int age;
char sex[5];
double scroe;
};
int main(void)
{ //定義了一個student的結構數組
struct qt student[5] = { {"李青", 20, "男", 99}, {"黃歡", 20, "女", 80}, {"七七", 23, "男", 95} };
struct qt *p = student;
int i;
for (i = 0; i < 5; i ) {
printf("%s ", p->name);//利用->可訪問成員(訪問意味着可以讀寫)
printf("%d ", p->age);
printf("%s ", p->sex);
printf("%f ", p->scroe);
}
return 0;
}
當結構體指針變量指向一個結構體變量數組的時候,此時指針變量的值就是結構體數組的首地址,
此時指針變量 p 就指向了結構體數組的第一個元素,即指向 student[0]。我們知道,
當一個指針指向一個數組後,指針就可以通過移動的方式指向數組的其他元素。
這個原則對結構體數組和結構體指針同樣适用,所以 p 1 就指向 student[1] 的首地址;p 2 就指向
student[2] 的首地址……所以隻要利用 for 循環,指針就能一個個地指向結構體數組元素。
同樣需要注意的是,要将一個結構體數組名賦給一個結構體指針變量,那麼它們的結構體類型必須相同。
typedef 别名typedef是在編程語言中用來為複雜的聲明定義簡單的别名,新的名字是某種類型的别名,這樣做改善了程序的可讀性,它與宏定義有些差異。它本身是一種存儲類的關鍵字,與auto、extern、mutable、static、register等關鍵字不能出現在同一個表達式中。
typedef為C語言的關鍵字,功能是用來聲明一個已有的數據類型的新名字,比如 typedef int last ; 這就使得last成為 int 類型的别名 這樣last這個名字就可以代替int出現在變量定義和參數聲明的地方了
typedef也有一個特别的長處:它符合範圍規則,使用typedef定義的變量類型其作用範圍限制在所定義的函數或者文件内(取決于此變量定義的位置),而宏定義則沒有這種特性。
typedef & 複雜的變量聲明
理解複雜聲明可用的“右左法則”:
從變量名看起,先往右,再往左,碰到一個圓括号就調轉閱讀的方向;括号内分析完就跳出括号,
還是按先右後左的順序,如此循環,直到整個聲明分析完。舉例:
int (*func)(int *p);
首 先找到變量名func,外面有一對圓括号,而且左邊是一個*号,這說明func是一個指針;
然後跳出這個圓括号,先看右邊,又遇到圓括号(隻有函數後面才跟形參圓括号),
這說明 (*func)是一個函數,所以func是一個指向這類函數的指針,即函數指針,
這類函數具有int*類型的形參,返回值類型是int,此處就是聲明函數。
int (*func[5])(int *);
func 右邊是一個[]運算符,說明func是具有5個元素的數組;func的左邊有一個*,
說明func的元素是指針(注意這裡的*不是修飾func,而是修飾 func[5]的,原因是[]運算符優先級比*高,
func先跟[]結合)。跳出這個括号,看右邊,又遇到圓括号,說明func數組的元素是函數類型的指 針,
它指向的函數具有int*類型的形參,返回值類型為int。
也可以記住2個模式:
type (*)(....)函數指針
type (*)[]數組指針
結構體的内存對齊方式(存儲空間)結構體内存對齊:一個結構體變量定義完之後,其在内存中的存儲并不等于其所包含元素的寬度之和,元素是按照定義順序一個一個放到内存中去的,但并不是緊密排列的。從結構體存儲的首地址開始,每個元素放置到内存中時,它都會認為内存是按照自己的大小來劃分的,因此元素放置的位置一定會在自己寬度的整數倍上開始。
内存對齊可以大大提升内存訪問速度,是一種用空間換時間的方法。内存不對齊會導緻每次讀取數據都會讀取兩次,使得内存讀取速度減慢。
cpu把内存當成是一塊一塊的,塊的大小可以是2,4,8,16 個字節,因此CPU在讀取内存的時候是一塊一塊進行讀取的,塊的大小稱為内存讀取粒度。
// 内存對齊原則:
// 1、第一個成員的首地址為0.
// 2、每個成員的首地址是自身大小的整數倍
// 3、結構體的總大小,為其成員中所含最大類型的整數倍。
#include<stdio.h>
typedef struct _node {
//char t;//1
//int p; //4
//float y;//8
//char t 要存放的偏移量為0,滿足對齊方式,t占用一個字節,
int p要存儲在下一個可用的地址的偏移量為1.不是sizeof(int)=4的倍數,
需要補足3個字節才能使偏移量變為4(使其滿足對齊方式),因此系統自動填充3個字節使偏移量增加到4,
int p放到此位置,占用4個字節,下一可用的偏移量為8,滿足sizeof(float)=4的對齊,
所以float y直接存放在偏移量為8的位置上,它占用4個字節 總共就是 //1 3 4 4=12
double a;//8
char b;//1
int c;//4
//double a的要存放的偏移量為0,滿足對齊方式直接存儲,占用8個字節,
char b要存儲在下一的可用的地址的偏移量為9,
直接存儲,占用字節為1;int c要存儲在下一個可用的地址的偏移量為9,
9不滿足sizeof(int)=4;所以系統會自動分配3的字節增加到12,12滿足對齊方式,
存儲下去c占用4個字節。總共就是 //8 1 3 4=16
}NODE;
int main(void) {
printf("%d ", sizeof(NODE));
}
如果結構體内存在長度大于處理器位數的元素,那麼就以處理器的倍數為對齊單位;否則,如果結構體内的元素的長度都小于處理器的倍數的時候,便以結構體裡面最長的數據元素為對齊單位。
另外 結構體的内存地址就是它第一個成員變量的地址 isa永遠都是結構體中的第一個成員變量 所以結構體的地址也就是其isa指針的地址
内存對齊簡介由于内存的讀取時間遠遠小于CPU的存儲速度,這裡用設定數據結構的對齊系數,即犧牲空間來換取時間的思想來提高CPU的存儲效率。
内存對齊”應該是編譯器的“管轄範圍”。編譯器為程序中的每個“數據單元”安排在适當的位置上。但是C語言的一個特點就是太靈活,太強大,它允許你幹預“内存對齊”。如果你想了解更加底層的秘密,“内存對齊”對你就不應該再模糊了。這也是一個大小端模式的問題
每個特定平台上的編譯器都有自己的默認“對齊系數”(也叫對齊模數)。程序員可以通過預編譯命令#pragma pack(n)來改變這一系數,其中的n就是你要指定的“對齊系數”。
規則:
1、數據成員對齊規則:結構(struct)(或聯合(union))的數據成員,第一個數據成員放在offset為0的地方,以後每個數據成員的對齊按照#pragma pack指定的數值和這個數據成員自身長度中,比較小的那個進行。
2、結構(或聯合)的整體對齊規則:在數據成員完成各自對齊之後,結構(或聯合)本身也要進行對齊,對齊将按照#pragma pack 指定的數值和結構(或聯合) 最大數據成員長度中,比較小的那個進行對齊。
3、結合1、2可推斷:當#pragma pack的n值等于或超過所有數據成員長度的時候,這個n值的大小将不産生任何效果。
#pragmapack(n) 設定變量以n字節為對齊方式:
作用:指定結構體、聯合以及類成員
語法:#pragmapack( [show] | [push | pop] [, identifier], n )
1,pack提供數據聲明級别的控制,對定義不起作用;
2,調用pack時不指定參數,n将被設成默認值;
n:可選參數;指定packing的數值,以字節為單位;缺省數值是8,合法的數值分别是1、2、4、8、16。
其他參數都是可選的可先不了解
#include<stdio.h>
#pragma pack(2)// 值隻能填1 2 4 8 16 這裡最好是看結構裡邊最小的成員 這裡是char 所以最好是填1 //但是當#pragma pack指定的值等于或者超過所有數據成員長度的時候,這個指定值的大小将不産生任何效果
typedef struct _A {
double x;//4
int y;//4
char p;//1
}NODE;
//8 4 1 1=14按n為2
//8 4 1 3=16按n為4以上或者使用系統自動對齊
#pragma pack(8)//設定為4字節對齊
typedef struct test
{
char m1;
double m4;
int m3;
}NODE2;
//1 1 4 8=14//按n為2
//1 7 8 4=20 不滿足8的倍數 加4等于24滿足 總:1 7 8 4 4=24
int main(void) {
printf("%d\n", sizeof(NODE));
printf("%d", sizeof(NODE2));
}
結構、聯合或者類的數據成員,第一個放在偏移為0的地方;以後每個數據成員的對齊,
按照#pragma pack指定的數值和這個數據成員自身長度兩個中比較小的那個進行;
也就是說,當#pragma pack指定的值等于或者超過所有數據成員長度的時候,
這個指定值的大小将不産生任何效果;
每個成員分别對齊,即每個成員按自己的方式對齊,并最小化長度;規則就是每個成員按其類型的對齊參數(通常是這個類型的大小)和指定對齊參數中較小的一個對齊。
大小端:
如:int 11 22 33 44
在存儲的時候
大端:11 22 33 44
0 1 2 3
低地址----> 高地址
小端:44 33 22 11
0 1 2 3
低地址----> 高地址
大小端的差異在于存放順序不同
常見的操作系統是小端,通訊協議是大端。
//結構體例子:使用尾插法創建鍊表
#include<stdio.h>//單鍊表的創建
typedef struct _node {
int nb;//數值
struct _node *nxte;//定義一個指向下一個的節點的指針
}NODE;
typedef struct _link{//利用這個結構體 封裝 首尾節點
NODE *head;
NODE *qt;
}link;
void add(link *phead, link *qt, int n);//定義函數将 首尾指針傳入
int main(void) {
link head, q;//定義一個結構,連指針都不是的
head.head = q.qt = NULL;//初始化
int n;
for (scanf("%d", &n); n != -1; scanf("%d", &n)) {
add(&head, &q, n);//将地址 值傳入
}
NODE *t;
t = head.head;//利用臨時結構将鍊表輸出
for (; t; t = t->nxte) {
printf("%d ", t->nb);
}
return 0;
}
//尾插法
void add(link *phead, link *qt, int n) {
NODE *p = (NODE*)malloc(sizeof(NODE));//為新結點開辟空間
p->nb = n;
p->nxte = NULL;
if (phead->head == NULL) {//判斷首結點是否為空
phead->head = p;//是空的就讓首結點等于新結點
}
else {//不為空時,讓尾結點依次跑到後面去
qt->qt->nxte = p;
}
qt->qt = p;
}
4、union 共用體(聯合體)在進行某些算法的C語言編程的時候,需要使幾種不同類型的變量存放到同一段内存單元中。也就是使用覆蓋技術,幾個變量互相覆蓋。這種幾個不同的變量共同占用一段内存的結構,在C語言中 以關鍵字union聲明的一種數據結構,這種被稱作“共用體”類型結構,也叫聯合體。
“聯合”與“結構”有一些相似之處。但兩者有本質上的不同。在結構中各成員有各自的内存空間,一個結構體變量的總長度大于等于各成員長度之和。而在“聯合”中,各成員共享一段内存空間,一個聯合變量的長度等于各成員中最長的長度。注意這裡所謂的共享不是指把多個成員同時裝入一個聯合變量内,而是指該聯合變量可被賦予任一成員值,但每次隻能賦一種值,賦入新值則沖去舊值,共用體變量中起作用的成員是最後一次存放的成員,在存入一個新成員後,原有成員就失去作用,共用體變量的地址和它的各成員的地址都是同一地址
一個聯合類型必須經過定義之後,才能把變量說明為該聯合類型:
聯合的定義:
定義一個聯合類型的一般形式為:
union [name](聯合名) name是可選的
{
成員表
};
成員表中含有若幹成員,成員的一般形式為: 類型說明符 成員名
成員名的命名應符合标識符的規定。
union Data
{
int i;
double f;
char str[20];
} data;
現在 Data所有的成員共享一個空間,同一時間隻有一個成員是的值有效的,Data 類型的變量可以存儲一個整數、
一個浮點數,或者一個字符串。這意味着一個變量(相同的内存位置)可以存儲多個多種類型的數據。
您可以根據需要在一個共用體内使用任何内置的或者用戶自定義的數據類型。
共用體占用的内存應足夠存儲共用體中最大的成員。例如,在上面的實例中,
Data 将占用 20 個字節的内存空間,因為在各個成員中,字符串所占用的空間是最大的。
注意:1、不能把共用體變量作為函數參數,也不能是函數帶回共用體變量,但可以使專用指向共用體變量的指針
2、所有成員占用同一段内存,修改一制個成員會影響其餘所有成員。
共用體的訪問:
共用體訪問成員的值時一般使用.運算符,指針時用->運算符(和結構體是一樣的)
typedef union _node {
int a;
double b;
char c;
union _node *p;
}NODE;
int main(void) {
NODE a;//定義變量
NODE t;
a.b;//用.訪問
t.p->a;//指針用->訪問
}
聯合的使用規則幾乎和結構體strtct的規則用法一樣,隻不過是内部表示的不同。
補充:
還有一個是無名聯合體,它是和無名結構體的工作原理是相同的
#include<stdio.h>//簡單的例子
#include<string.h>
typedef union _node{
int a;
double b;
char c[20];
}NODE;
int main(void) {
NODE a;//這裡隻定義一個變量
a.a = 666;
printf("%d\n", a.a);
a.b = 9.99;
printf("%f\n", a.b);
strcpy(a.c, "hello world!");
printf("%s\n", a.c);
//我們看到,三個都被完整的輸出了,因為在同一時刻,隻有一個成員是有效的
}
輸出:
666
9.990000
hellow world!
共用體的作用:
1、節省内存,有兩個很長的數據結構,不會同時使用,比如一個表示老師,一個表示學生,如果要統計教師和學生的情況用結構體的話就有點浪費了!用結構體的話,隻占用最長的那個數據結構所占用的空間,就足夠了!
2、實現不同類型數據之間的類型轉換,遇到各種類型的數據共用存儲空間,很方便的實現了不同數據類型之間的轉換,不需要顯示的強制類型轉換。
其他:
1、确定CPU的模式:大端、小端模式确定
大小端不同,則存儲的方式也存在差别,比如int需要4個字節,而char隻需要1個字節,根據1個字節所在的具體位置即可判定CPU的模式
2、寄存器的定義,實現整體的訪問和單項的訪問
//共用體綜合例子:根據輸入的數據類型輸出需要的相應的數據
#include<stdio.h>
#include<string.h>//數據類型輸出 5*4 m n n的第幾個x
union node {
int a;
double b;
char c[30];
}add[10000];
char p[10000][30]; //保存的字符串數組
int main(void) {
int n, m;
scanf("%d %d", &n, &m);
int x;
double y;
char t[50];
int i, j;
for (i = 0; i < n; i ) {//輸入
scanf("%s", &p[i]);//作為字符串數組,需要取地址
if (strcmp("INT", p[i]) == 0) {//整形
scanf("%d", &x);
add[i].a = x;
}
else if(strcmp("DOUBLE",p[i])==0){//浮點
scanf("%lf", &y);
add[i].b = y;
}
else if (strcmp("STRCING", p[i]) == 0) {//字符串
scanf("%s", t);
strcpy(add[i].c, t);
}
}
for (i = 0; i < m; i ) {//輸出
scanf("%d", &j);
if (strcmp("INT", p[j]) == 0) {
printf("%d\n", add[j].a);
}
else if (strcmp("DOUBLE", p[j]) == 0)
{
printf("%f\n", add[j].b);
}else if(strcmp("STRING",p[j])==0)
{
printf("%s\n", add[j].c);
}
}
return 0;
}
//輸入:
/*
5 4
INT 456
DOUBLE 123.56
DOUBLE 0.476
STRING welcomeToC
STRING LemonTree
0
1
2
4
*/
//輸出:
/*
456
123.56
0.48
LemonTree
*/
希望對你有幫助!
作者:Mr_Li_
對啦對啦!另外的話為了幫助大家,輕松,高效學習C語言/C ,我給大家分享我收集的資源,從最零基礎開始的教程到C語言項目案例,幫助大家在學習C語言的道路上披荊斬棘!可以來我粉絲群領取哦~
編程學習書籍分享:
編程學習視頻分享:
整理分享(多年學習的源碼、項目實戰視頻、項目筆記,基礎入門教程)最重要的是你可以在群裡面交流提問編程問題哦!
對于C/C 感興趣可以關注小編在後台私信我:【編程交流】一起來學習哦!可以領取一些C/C 的項目學習視頻資料哦!已經設置好了關鍵詞自動回複,自動領取就好了!
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!