- 批量存儲(chǔ)數(shù)據(jù)
- 存儲(chǔ)不同類型的數(shù)據(jù)
- 支持嵌套
結(jié)構(gòu)體的聲明與定義
聲明
結(jié)構(gòu)體的聲明使用struct關(guān)鍵字,如果我們想要把我們的學(xué)籍信息組織一下的話,可以這樣表示:
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
unsigned int year;//入學(xué)年份,用無符號(hào)整數(shù)表示
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
}
這樣,我們就相當(dāng)于描繪好了一個(gè)框架,以后要用的話直接定義一個(gè)這種類型的變量就好了。
定義
我們剛剛申請(qǐng)了一個(gè)名叫Info的結(jié)構(gòu)體類型,那么理論上我們可以像聲明其他變量的操作一樣,去聲明我們的結(jié)構(gòu)體操作,但是C語言中規(guī)定,聲明結(jié)構(gòu)體變量的時(shí)候,struct關(guān)鍵字是不可少的。struct 結(jié)構(gòu)體類型名 結(jié)構(gòu)體變量名不過,你可以在某個(gè)函數(shù)里面定義:
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
unsigned int year;//入學(xué)年份,用無符號(hào)整數(shù)表示
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
};
int main(void)
{
/**
*在main函數(shù)中聲明結(jié)構(gòu)體變量
*結(jié)構(gòu)體變量名叫info
*struct關(guān)鍵字不能丟
*/
struct Info info;
...
}
也可以在聲明的時(shí)候就把變量名定義下來(此時(shí)這個(gè)變量是全局變量):
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
unsigned int year;//入學(xué)年份,用無符號(hào)整數(shù)表示
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
} info;
/**
*此時(shí)直接定義了變量
*該變量是全局變量
*變量名叫info
*/
int main(void)
{
...
}
訪問結(jié)構(gòu)體成員
結(jié)構(gòu)體成員的訪問有點(diǎn)不同于以往的任何變量,它是采用點(diǎn)號(hào)運(yùn)算符.來訪問成員的。比如,info.name就是引用info結(jié)構(gòu)體的name成員,是一個(gè)字符數(shù)組,而info.year則可以查到入學(xué)年份,是個(gè)無符號(hào)整型。
比如,下面開始錄入學(xué)生的信息:
//Example 01
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
unsigned int year;//入學(xué)年份,用無符號(hào)整數(shù)表示
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
};
int main(void)
{
struct Info info;
printf("請(qǐng)輸入學(xué)生的學(xué)號(hào):");
scanf("%d", &info.identifier);
printf("請(qǐng)輸入學(xué)生的姓名:");
scanf("%s", info.name);
printf("請(qǐng)輸入學(xué)生的入學(xué)年份:");
scanf("%d", &info.year);
printf("請(qǐng)輸入學(xué)生的學(xué)制:");
scanf("%d", &info.years);
printf("
數(shù)據(jù)錄入完畢
");
printf("學(xué)號(hào):%d
姓名:%s
入學(xué)年份:%d
學(xué)制:%d
畢業(yè)時(shí)間:%d
",
info.identifier, info.name, info.year, info.years, info.year + info.years);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 01
請(qǐng)輸入學(xué)生的學(xué)號(hào):20191101
請(qǐng)輸入學(xué)生的姓名:Harris
請(qǐng)輸入學(xué)生的入學(xué)年份:2019
請(qǐng)輸入學(xué)生的學(xué)制:4
數(shù)據(jù)錄入完畢
學(xué)號(hào):20191101
姓名:Harris
入學(xué)年份:2019
學(xué)制:4
畢業(yè)時(shí)間:2023
初始化結(jié)構(gòu)體
像數(shù)組一樣,結(jié)構(gòu)體也可以在定義的時(shí)候初始化,方法也幾乎一樣:
struct Info info = {
20191101,
"Harris",
2019,
4
};
在C99標(biāo)準(zhǔn)中,還支持給指定元素賦值(就像數(shù)組一樣):
struct Info info = {
.name = "Harris",
.year = 2019
};
對(duì)于沒有被初始化的成員,則「數(shù)值型」成員初始化為0,「字符型」成員初始化為‘’。
對(duì)齊
下面這個(gè)代碼,大家來看看會(huì)發(fā)生什么:
//EXample 02 V1
int main(void)
{
struct A
{
char a;
int b;
char c;
} a = {'a', 10, 'o'};
printf("size of a = %d
", sizeof(a));
return 0;
}
我們之前學(xué)過,char類型的變量占1字節(jié),int類型的變量占4字節(jié),那么這么一算,一個(gè)結(jié)構(gòu)體A型的變量應(yīng)該就是6字節(jié)了。別急,我們看運(yùn)行結(jié)果:
//COnsequence 02 V1
size of a = 12
怎么變成12了呢?標(biāo)準(zhǔn)更新了?老師教錯(cuò)了?都不是。我們把代碼改一下:
//EXample 02 V2
int main(void)
{
struct A
{
char a;
char c;
int b;
} a = {'a', 'o', 10};
printf("size of a = %d
", sizeof(a));
return 0;
}
結(jié)果:
//Consequence 02 V2
size of a = 8
實(shí)際上,這是編譯器對(duì)我們程序的一種優(yōu)化——內(nèi)存對(duì)齊。在第一個(gè)例子中,第一個(gè)和第三個(gè)成員是char類型是1個(gè)字節(jié),而中間的int卻有4個(gè)字節(jié),為了對(duì)齊,兩個(gè)char也占用了4個(gè)字節(jié),于是就是12個(gè)字節(jié)。
而在第二個(gè)例子里面,前兩個(gè)都是char,最后一個(gè)是int,那么前兩個(gè)可以一起占用4個(gè)字節(jié)(實(shí)際只用2個(gè),第一個(gè)例子也同理,只是為了訪問速度更快,而不是為了擴(kuò)展),最后的int占用4字節(jié),合起來就是8個(gè)字節(jié)。
結(jié)構(gòu)體嵌套
在學(xué)籍里面,如果我們的日期想要更加詳細(xì)一些,精確到day,這時(shí)候就可以使用結(jié)構(gòu)體嵌套來完成:
struct Date
{
unsigned int year;
unsigned int month;
unsigned int day;
};
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
struct Date date;/*---入學(xué)日期,用結(jié)構(gòu)體Date表示---*/
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
};
int main(void)
{
...
}
如此一來,比我們單獨(dú)聲明普通變量快多了。
不過,這樣訪問變量,就必須用點(diǎn)號(hào)一層層往下訪問。比如要訪問day這個(gè)成員,那就只能info.date.day而不能直接info.date或者info,day。
//Example 03
struct Date
{
unsigned int year;
unsigned int month;
unsigned int day;
};
struct Info
{
unsigned long identifier;//學(xué)號(hào),用無符號(hào)長整數(shù)表示
char name[20];//名字,用字符數(shù)組表示
struct Date date;/*---入學(xué)日期,用結(jié)構(gòu)體Date表示---*/
unsigned int years;//學(xué)制,用無符號(hào)整數(shù)表示
};
int main(void)
{
struct Info info;
printf("請(qǐng)輸入學(xué)生的學(xué)號(hào):");
scanf("%d", &info.identifier);
printf("請(qǐng)輸入學(xué)生的姓名:");
scanf("%s", info.name);
printf("請(qǐng)輸入學(xué)生的入學(xué)年份:");
scanf("%d", &info.date.year);
printf("請(qǐng)輸入學(xué)生的入學(xué)月份:");
scanf("%d", &info.date.month);
printf("請(qǐng)輸入學(xué)生的入學(xué)日期:");
scanf("%d", &info.date.day);
printf("請(qǐng)輸入學(xué)生的學(xué)制:");
scanf("%d", &info.years);
printf("
數(shù)據(jù)錄入完畢
");
printf("學(xué)號(hào):%d
姓名:%s
入學(xué)時(shí)間:%d/%d/%d
學(xué)制:%d
畢業(yè)時(shí)間:%d
",
info.identifier, info.name,
info.date.year, info.date.month, info.date.day,
info.years, info.date.year + info.years);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 03
請(qǐng)輸入學(xué)生的學(xué)號(hào):20191101
請(qǐng)輸入學(xué)生的姓名:Harris
請(qǐng)輸入學(xué)生的入學(xué)年份:2019
請(qǐng)輸入學(xué)生的入學(xué)月份:9
請(qǐng)輸入學(xué)生的入學(xué)日期:7
請(qǐng)輸入學(xué)生的學(xué)制:4
數(shù)據(jù)錄入完畢
學(xué)號(hào):20191101
姓名:Harris
入學(xué)時(shí)間:2019/9/7
學(xué)制:4
畢業(yè)時(shí)間:2023
結(jié)構(gòu)體數(shù)組
剛剛我們演示了存儲(chǔ)一個(gè)學(xué)生的學(xué)籍信息的時(shí)候,使用結(jié)構(gòu)體的例子。那么,如果要錄入一批學(xué)生,這時(shí)候我們就可以沿用之前的思路,使用結(jié)構(gòu)體數(shù)組。我們知道,數(shù)組的定義,就是存放一堆相同類型的數(shù)據(jù)的容器。而結(jié)構(gòu)體一旦被我們聲明,那么你就可以把它看作一個(gè)類型,只不過是你自己定義的罷了。定義結(jié)構(gòu)體數(shù)組也很簡單:
struct 結(jié)構(gòu)體類型
{
成員;
} 數(shù)組名[長度];
/****或者這樣****/
struct 結(jié)構(gòu)體類型
{
成員;
};
struct 結(jié)構(gòu)體類型 數(shù)組名[長度];
結(jié)構(gòu)體指針
既然我們可以把結(jié)構(gòu)體看作一個(gè)類型,那么也就必然有對(duì)應(yīng)的指針變量。
struct Info* pinfo;
但是在指針這里,結(jié)構(gòu)體和數(shù)組就不一樣了。我們知道,數(shù)組名實(shí)際上就是指向這個(gè)數(shù)組第一個(gè)元素的地址,所以可以將數(shù)組名直接賦值給指針。而結(jié)構(gòu)體的變量名并不是指向該結(jié)構(gòu)體的地址,所以要使用取地址運(yùn)算符&才能獲取地址:
pinfo = &info;
通過結(jié)構(gòu)體指針來訪問結(jié)構(gòu)體有以下兩種方法:
- (*結(jié)構(gòu)體指針).成員名
- 結(jié)構(gòu)體指針->成員名
第一種方法:
...
int main(void)
{
struct Info *p;
p = &info;
printf("學(xué)號(hào):
", (*p).identifier);
printf("姓名:
", (*p).name);
printf("入學(xué)時(shí)間:%d/%d/%d
", (*p).date.year, (*p).date.month, (*p).date.day);
printf("學(xué)制:
", (*p).years);
return 0;
}
第二種方法:
...
int main(void)
{
struct Info *p;
p = &info;
printf("學(xué)號(hào):
", p -> identifier);
printf("姓名:
", p -> name);
printf("入學(xué)時(shí)間:%d/%d/%d
", p -> date.year, p -> date.month, p -> date.day);
printf("學(xué)制:
", p -> years);
return 0;
}
傳遞結(jié)構(gòu)體信息
傳遞結(jié)構(gòu)體變量
我們先來看看下面的代碼:運(yùn)行結(jié)果如下:
//Consequence 04
t2.x = 3, t2.y = 4
這么看來,結(jié)構(gòu)體是可以直接賦值的。那么既然這樣,作為函數(shù)的參數(shù)和返回值也自然是沒問題的了。
先來試試作為參數(shù):
//Example 05
struct Date
{
unsigned int year;
unsigned int month;
unsigned int day;
};
struct Info
{
unsigned long identifier;
char name[20];
struct Date date;
unsigned int years;
};
struct Info getInput(struct Info info);
void printInfo(struct Info info);
struct Info getInput(struct Info info)
{
printf("請(qǐng)輸入學(xué)號(hào):");
scanf("%d", &info.identifier);
printf("請(qǐng)輸入姓名:");
scanf("%s", info.name);
printf("請(qǐng)輸入入學(xué)年份:");
scanf("%d", &info.date.year);
printf("請(qǐng)輸入月份:");
scanf("%d", &info.date.month);
printf("請(qǐng)輸入日期:");
scanf("%d", &info.date.day);
printf("請(qǐng)輸入學(xué)制:");
scanf("%d", &info.years);
return info;
}
void printInfo(struct Info info)
{
printf("學(xué)號(hào):%d
姓名:%s
入學(xué)時(shí)間:%d/%d/%d
學(xué)制:%d
畢業(yè)時(shí)間:%d
",
info.identifier, info.name,
info.date.year, info.date.month, info.date.day,
info.years, info.date.year + info.years);
}
int main(void)
{
struct Info i1 = {};
struct Info i2 = {};
printf("請(qǐng)錄入第一個(gè)同學(xué)的信息...
");
i1 = getInput(i1);
putchar('
');
printf("請(qǐng)錄入第二個(gè)學(xué)生的信息...
");
i2 = getInput(i2);
printf("
錄入完畢,現(xiàn)在開始打印...
");
printf("打印第一個(gè)學(xué)生的信息...
");
printInfo(i1);
putchar('
');
printf("打印第二個(gè)學(xué)生的信息...
");
printInfo(i2);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 05
請(qǐng)錄入第一個(gè)同學(xué)的信息...
請(qǐng)輸入學(xué)號(hào):20191101
請(qǐng)輸入姓名:Harris
請(qǐng)輸入入學(xué)年份:2019
請(qǐng)輸入月份:9
請(qǐng)輸入日期:7
請(qǐng)輸入學(xué)制:4
請(qǐng)錄入第二個(gè)學(xué)生的信息...
請(qǐng)輸入學(xué)號(hào):20191102
請(qǐng)輸入姓名:Joy
請(qǐng)輸入入學(xué)年份:2019
請(qǐng)輸入月份:9
請(qǐng)輸入日期:8
請(qǐng)輸入學(xué)制:5
錄入完畢,現(xiàn)在開始打印...
打印第一個(gè)學(xué)生的信息...
學(xué)號(hào):20191101
姓名:Harris
入學(xué)時(shí)間:2019/9/7
學(xué)制:4
畢業(yè)時(shí)間:2023
打印第二個(gè)學(xué)生的信息...
學(xué)號(hào):20191102
姓名:Joy
入學(xué)時(shí)間:2019/9/8
學(xué)制:5
畢業(yè)時(shí)間:2024
傳遞指向結(jié)構(gòu)體變量的指針
早期的C語言是不允許直接將結(jié)構(gòu)體作為參數(shù)直接傳遞進(jìn)去的。主要是考慮到如果結(jié)構(gòu)體的內(nèi)存占用太大,那么整個(gè)程序的內(nèi)存開銷就會(huì)爆炸。不過現(xiàn)在的C語言已經(jīng)放開了這方面的限制。不過,作為一名合格的開發(fā)者,我們應(yīng)該要去珍惜硬件資源。那么,傳遞指針就是一個(gè)很好的辦法。將剛才的代碼修改一下:
//Example 06
struct Date
{
unsigned int year;
unsigned int month;
unsigned int day;
};
struct Info
{
unsigned long identifier;
char name[20];
struct Date date;
unsigned int years;
};
void getInput(struct Info *info);
void printInfo(struct Info *info);
void getInput(struct Info *info)
{
printf("請(qǐng)輸入學(xué)號(hào):");
scanf("%d", &info->identifier);
printf("請(qǐng)輸入姓名:");
scanf("%s", info->name);
printf("請(qǐng)輸入入學(xué)年份:");
scanf("%d", &info->date.year);
printf("請(qǐng)輸入月份:");
scanf("%d", &info->date.month);
printf("請(qǐng)輸入日期:");
scanf("%d", &info->date.day);
printf("請(qǐng)輸入學(xué)制:");
scanf("%d", &info->years);
}
void printInfo(struct Info *info)
{
printf("學(xué)號(hào):%d
姓名:%s
入學(xué)時(shí)間:%d/%d/%d
學(xué)制:%d
畢業(yè)時(shí)間:%d
",
info->identifier, info->name,
info->date.year, info->date.month, info->date.day,
info->years, info->date.year + info->years);
}
int main(void)
{
struct Info i1 = {};
struct Info i2 = {};
printf("請(qǐng)錄入第一個(gè)同學(xué)的信息...
");
getInput(&i1);
putchar('
');
printf("請(qǐng)錄入第二個(gè)學(xué)生的信息...
");
getInput(&i2);
printf("
錄入完畢,現(xiàn)在開始打印...
");
printf("打印第一個(gè)學(xué)生的信息...
");
printInfo(&i1);
putchar('
');
printf("打印第二個(gè)學(xué)生的信息...
");
printInfo(&i2);
return 0;
}
此時(shí)傳遞的就是一個(gè)指針,而不是一個(gè)龐大的結(jié)構(gòu)體。
動(dòng)態(tài)申請(qǐng)結(jié)構(gòu)體
結(jié)構(gòu)體也可以在堆里面動(dòng)態(tài)申請(qǐng):
//Example 01
...
int main(void)
{
struct Info *i1;
struct Info *i2;
i1 = (struct Info *)malloc(sizeof(struct Info));
i2 = (struct Info *)malloc(sizeof(struct Info));
if (i1 == NULL || i2 == NULL)
{
printf("內(nèi)存分配失??!
");
exit(1);
}
printf("請(qǐng)錄入第一個(gè)同學(xué)的信息...
");
getInput(i1);
putchar('
');
printf("請(qǐng)錄入第二個(gè)學(xué)生的信息...
");
getInput(i2);
printf("
錄入完畢,現(xiàn)在開始打印...
");
printf("打印第一個(gè)學(xué)生的信息...
");
printInfo(i1);
putchar('
');
printf("打印第二個(gè)學(xué)生的信息...
");
printInfo(i2);
free(i1);
free(i2);
return 0;
}
實(shí)戰(zhàn):建立一個(gè)圖書館數(shù)據(jù)庫
實(shí)際上,我們建立的數(shù)組可以是指向結(jié)構(gòu)體指針的數(shù)組。代碼實(shí)現(xiàn)如下:
//Example 02
struct Date
{
int year;
int month;
int day;
};
struct Book
{
char title[128];
char author[48];
float price;
struct Date date;
char publisher[48];
};
void getInput(struct Book* book);//錄入數(shù)據(jù)
void printBook(struct Book* book);//打印數(shù)據(jù)
void initLibrary(struct Book* lib[]);//初始化結(jié)構(gòu)體
void printLibrary(struct Book* lib[]);//打印單本書數(shù)據(jù)
void releaseLibrary(struct Book* lib[]);//釋放內(nèi)存
void getInput(struct Book* book)
{
printf("請(qǐng)輸入書名:");
scanf("%s", book->title);
printf("請(qǐng)輸入作者:");
scanf("%s", book->author);
printf("請(qǐng)輸入售價(jià):");
scanf("%f", &book->price);
printf("請(qǐng)輸入出版日期:");
scanf("%d-%d-%d", &book->date.year, &book->date.month, &book->date.day);
printf("請(qǐng)輸入出版社:");
scanf("%s", book->publisher);
}
void printBook(struct Book* book)
{
printf("書名:%s
", book->title);
printf("作者:%s
", book->author);
printf("售價(jià):%.2f
", book->price);
printf("出版日期:%d-%d-%d
", book->date.year, book->date.month, book->date.day);
printf("出版社:%s
", book->publisher);
}
void initLibrary(struct Book* lib[])
{
for (int i = 0; i < MAX_SIZE; i++)
{
lib[i] = NULL;
}
}
void printLibrary(struct Book* lib[])
{
for (int i = 0; i < MAX_SIZE; i++)
{
if (lib[i] != NULL)
{
printBook(lib[i]);
putchar('
');
}
}
}
void releaseLibrary(struct Book* lib[])
{
for (int i = 0; i < MAX_SIZE; i++)
{
if (lib[i] != NULL)
{
free(lib[i]);
}
}
}
int main(void)
{
struct Book* lib[MAX_SIZE];
struct Book* p = NULL;
int ch, index = 0;
initLibrary(lib);
while (1)
{
printf("請(qǐng)問是否要錄入圖書信息(Y/N):");
do
{
ch = getchar();
} while (ch != 'Y' && ch != 'N');
if (ch == 'Y')
{
if (index < MAX_SIZE)
{
p = (struct Book*)malloc(sizeof(struct Book));
getInput(p);
lib[index] = p;
index++;
putchar('
');
}
else
{
printf("數(shù)據(jù)庫已滿!
");
break;
}
}
else
{
break;
}
}
printf("
數(shù)據(jù)錄入完畢,開始打印驗(yàn)證...
");
printLibrary(lib);
releaseLibrary(lib);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 02
請(qǐng)問是否要錄入圖書信息(Y/N):Y
請(qǐng)輸入書名:人類簡史
請(qǐng)輸入作者:尤瓦爾·赫拉利
請(qǐng)輸入售價(jià):32.25
請(qǐng)輸入出版日期:2016-3-4
請(qǐng)輸入出版社:中信出版集團(tuán)
請(qǐng)問是否要錄入圖書信息(Y/N):N
數(shù)據(jù)錄入完畢,開始打印驗(yàn)證...
書名:人類簡史
作者:尤瓦爾·赫拉利
售價(jià):32.25
出版日期:2016-3-4
出版社:中信出版集團(tuán)
單鏈表
我們知道,數(shù)組變量在內(nèi)存中,是連續(xù)的,而且不可拓展。顯然在一些情況下,這種數(shù)據(jù)結(jié)構(gòu)擁有很大的局限性。比如移動(dòng)數(shù)據(jù)的時(shí)候,會(huì)牽一發(fā)而動(dòng)全身,尤其是反轉(zhuǎn)這種操作更加令人窒息。那么,需要需要一種數(shù)據(jù)結(jié)構(gòu)來弄出一種更加靈活的“數(shù)組”,那么這,就是「鏈表」。本節(jié)我們只講講單鏈表。所謂鏈表,就是由一個(gè)個(gè)「結(jié)點(diǎn)」組成的一個(gè)數(shù)據(jù)結(jié)構(gòu)。每個(gè)結(jié)點(diǎn)都有「數(shù)據(jù)域」和「指針域」組成。其中數(shù)據(jù)域用來存儲(chǔ)你想要存儲(chǔ)的信息,而指針域用來存儲(chǔ)下一個(gè)結(jié)點(diǎn)的地址。如圖:![29106356-3042-11ee-9e74-dac502259ad0.png](https://file1.elecfans.com//web2/M00/9F/86/wKgZomToOYqAa-KaAAAvFa2uDN0147.png)
因此對(duì)于學(xué)籍?dāng)?shù)據(jù)庫來說,我們只需要在Info結(jié)構(gòu)體中加上一個(gè)指向自身類型的成員即可:
struct Info
{
unsigned long identifier;
char name[20];
struct Date date;
unsigned int years;
struct Info* next;
};
在單鏈表中插入元素
頭插法
這種每次都將數(shù)據(jù)插入單鏈表的頭部(頭指針后面)的插入法就叫頭插法。如果要把學(xué)生信息加入到單鏈表,可以這么寫:
void addInfo(struct Info** students)//students是頭指針
{
struct Info* info, *temp;
info = (struct Info*)malloc(sizeof(struct Info));
if (info == NULL)
{
printf("內(nèi)存分配失?。?");
exit(1);
}
getInput(info);
if (*students != NULL)
{
temp = *students;
*students = info;
info->next = temp;
}
else
{
*students = info;
info->next = NULL;
}
}
?
由于students存放的是頭指針,因此我們需要傳入它的地址傳遞給函數(shù),才能夠改變它本身的值。而students本身又是一個(gè)指向Info結(jié)構(gòu)體的指針,所以參數(shù)的類型應(yīng)該就是struct Info**。
?往單鏈表里面添加一個(gè)結(jié)點(diǎn),也就是先申請(qǐng)一個(gè)結(jié)點(diǎn),然后判斷鏈表是否為空。如果為空,那么直接將頭指針指向它,然后next成員指向NULL。若不為空,那么先將next指向頭指針原本指向的結(jié)點(diǎn),然后將頭指針指向新結(jié)點(diǎn)即可。
那么,打印鏈表也變得很簡單:
void printStu(struct Info* students)
{
struct Info* info;
int count = 1;
info = students;
while (book != NULL)
{
printf("Student%d:
", count);
printf("姓名:%s
", info->name);
printf("學(xué)號(hào):%d
", info->identifier);
info = info->next;
count++;
}
}
想要讀取單鏈表里面的數(shù)據(jù),只需要迭代單鏈表中的每一個(gè)結(jié)點(diǎn),直到next成員為NULL,即表示單鏈表的結(jié)束。
最后,當(dāng)然還是別忘了釋放空間:
void releaseStu(struct Info** students)
{
struct Info* temp;
while (*students != NULL)
{
temp = *students;
*students = (*students)->next;
free(temp);
}
}
尾插法
與頭插法類似,尾插法就是把每一個(gè)數(shù)據(jù)都插入到鏈表的末尾。
void addInfo(struct Info** students)
{
struct Info* info, *temp;
info = (struct Info*)malloc(sizeof(struct Info));
if (info == NULL)
{
printf("內(nèi)存分配失??!
");
exit(1);
}
getInput(info);
if (*students != NULL)
{
temp = *students;
*students = info;
//定位到鏈表的末尾的位置
while (temp->next != NULL)
{
temp = temp->next;
}
//插入數(shù)據(jù)
temp->next = info;
info->next = temp;
}
else
{
*students = info;
info->next = NULL;
}
}
這么一來,程序執(zhí)行的效率難免要降低很多,因?yàn)槊看尾迦霐?shù)據(jù),都要先遍歷一次鏈表。如果鏈表很長,那么對(duì)于插入數(shù)據(jù)來說就是一次災(zāi)難。不過,我們可以給程序添加一個(gè)指針,讓它永遠(yuǎn)都指向鏈表的尾部,這樣一來,就可以用很少的空間換取很高的程序執(zhí)行效率。
代碼更改如下:
void addInfo(struct Info** students)
{
struct Info* info, *temp;
static struct Info* tail;//設(shè)置靜態(tài)指針
info = (struct Info*)malloc(sizeof(struct Info));
if (info == NULL)
{
printf("內(nèi)存分配失?。?");
exit(1);
}
getInput(info);
if (*students != NULL)
{
tail->next = info;
info->next = NULL;
}
else
{
*students = info;
info->next = NULL;
}
}
搜索單鏈表
單鏈表是我們用來存儲(chǔ)數(shù)據(jù)的一個(gè)容器,那么有時(shí)候需要快速查找信息就需要開發(fā)相關(guān)搜索的功能。比如說輸入學(xué)號(hào),查找同學(xué)的所有信息。
struct Info *searchInfo(struct Info* students, long* target)
{
struct Info* info;
info = students;
while (info != NULL)
{
if (info->identifier == target)
{
break;
}
info = info->next;
}
return book;
};
void printInfo(struct Info* info)
{
...
}
...
int main(void)
{
...
printf("
請(qǐng)輸入學(xué)生學(xué)號(hào):");
scanf("%d", input);
info = searchInfo(students, input);
if (info == NULL)
{
printf("抱歉,未找到相關(guān)結(jié)果!
");
}
else
{
do
{
printf("相關(guān)結(jié)果如下:
");
printInfo(book);
} while ((info = searchInfo(info->next, input)) != NULL);
}
releaseInfo(...);
return 0;
}
插入結(jié)點(diǎn)到指定位置
到了這里,才體現(xiàn)出鏈表真正的優(yōu)勢(shì)。設(shè)想一下,如果有一個(gè)有序數(shù)組,現(xiàn)在要求你去插入一個(gè)數(shù)字,插入完成之后,數(shù)組依然保持有序。你會(huì)怎么做?沒錯(cuò),你應(yīng)該會(huì)挨個(gè)去比較,然后找到合適的位置(當(dāng)然這里也可以使用二分法,比較節(jié)省算力),把這個(gè)位置后面的所有數(shù)都往后移動(dòng)一個(gè)位置,然后將我們要插入的數(shù)字放入剛剛我們騰出來的空間里面。你會(huì)發(fā)現(xiàn),這樣的處理方法,經(jīng)常需要移動(dòng)大量的數(shù)據(jù),對(duì)于程序的執(zhí)行效率來說,是一個(gè)不利因素。那么鏈表,就無所謂。反正在內(nèi)存中,鏈表的存儲(chǔ)毫無邏輯,我們只需要改變指針的值就可以實(shí)現(xiàn)鏈表的中間插入。
//Example 03
struct Node
{
int value;
struct Node* next;
};
void insNode(struct Node** head, int value)
{
struct Node* pre;
struct Node* cur;
struct Node* New;
cur = *head;
pre = NULL;
while (cur != NULL && cur->value < value)
{
pre = cur;
cur = cur->next;
}
New = (struct Node*)malloc(sizeof(struct Node));
if (New == NULL)
{
printf("內(nèi)存分配失?。?");
exit(1);
}
New->value = value;
New->next = cur;
if (pre == NULL)
{
*head = New;
}
else
{
pre->next = New;
}
}
void printNode(struct Node* head)
{
struct Node* cur;
cur = head;
while (cur != NULL)
{
printf("%d ", cur->value);
cur = cur->next;
}
putchar('
');
}
int main(void)
{
struct Node* head = NULL;
int input;
printf("開始插入整數(shù)...
");
while (1)
{
printf("請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:");
scanf("%d", &input);
if (input == -1)
{
break;
}
insNode(&head, input);
printNode(head);
}
return 0;
}
運(yùn)行結(jié)果如下:
03
開始插入整數(shù)...
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:4
4
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:5
4 5
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:3
3 4 5
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:6
3 4 5 6
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:2
2 3 4 5 6
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:5
2 3 4 5 5 6
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:1
1 2 3 4 5 5 6
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:7
1 2 3 4 5 5 6 7
請(qǐng)輸入一個(gè)整數(shù),輸入-1表示結(jié)束:-1
刪除結(jié)點(diǎn)
刪除結(jié)點(diǎn)的思路也差不多,首先修改待刪除的結(jié)點(diǎn)的上一個(gè)結(jié)點(diǎn)的指針,將其指向待刪除結(jié)點(diǎn)的下一個(gè)結(jié)點(diǎn)。然后釋放待刪除結(jié)點(diǎn)的空間。
...
void delNode(struct Node** head, int value)
{
struct Node* pre;
struct Node* cur;
cur = *head;
pre = NULL;
while (cur != NULL && cur->value != value)
{
pre = cur;
cur = cur->next;
}
if (cur == NULL)
{
printf("未找到匹配項(xiàng)!
");
return ;
}
else
{
if (pre == NULL)
{
*head = cur->next;
}
else
{
pre->next = cur->next;
}
free(cur);
}
}
內(nèi)存池
C語言的內(nèi)存管理,從來都是一個(gè)讓人頭禿的問題。要想更自由地管理內(nèi)存,就必須去堆中申請(qǐng),然后還需要考慮何時(shí)釋放,萬一釋放不當(dāng),或者沒有及時(shí)釋放,造成的后果都是難以估量的。當(dāng)然如果就這些,那倒也還不算什么。問題就在于,如果大量地使用malloc和free函數(shù)來申請(qǐng)內(nèi)存,首先使要經(jīng)歷一個(gè)從應(yīng)用層切入系統(tǒng)內(nèi)核層,調(diào)用完成之后,再返回應(yīng)用層的一系列步驟,實(shí)際上使非常浪費(fèi)時(shí)間的。更重要的是,還會(huì)產(chǎn)生大量的內(nèi)存碎片。比如,先申請(qǐng)了一個(gè)1KB的空間,緊接著又申請(qǐng)了一個(gè)8KB的空間。而后,這個(gè)1KB使用完了,被釋放,但是這個(gè)空間卻只有等到下一次有剛好1KB的空間申請(qǐng),才能夠被重新調(diào)用。這么一來,極限情況下,整個(gè)堆有可能被弄得支離破碎,最終導(dǎo)致大量內(nèi)存浪費(fèi)。那么這種情況下,我們解決這類問題的思路,就是創(chuàng)建一個(gè)內(nèi)存池。內(nèi)存池,實(shí)際上就是我們讓程序創(chuàng)建出來的一塊額外的緩存區(qū)域,如果有需要釋放內(nèi)存,先不必使用free函數(shù),如果內(nèi)存池有空,那么直接放入內(nèi)存池。同樣的道理,下一次程序申請(qǐng)空間的時(shí)候,先檢查下內(nèi)存池里面有沒有合適的內(nèi)存,如果有,則直接拿出來調(diào)用,如果沒有,那么再使用malloc。其實(shí)內(nèi)存池我們就可以使用單鏈表來進(jìn)行維護(hù),下面通過一個(gè)通訊錄的程序來說明內(nèi)存池的運(yùn)用。普通的版本:
//Example 04 V1
struct Person
{
char name[40];
char phone[20];
struct Person* next;
};
void getInput(struct Person* person);
void printPerson(struct Person* person);
void addPerson(struct Person** contects);
void changePerson(struct Person* contacts);
void delPerson(struct Person** contacts);
struct Person* findPerson(struct Person* contacts);
void displayContacts(struct Person* contacts);
void releaseContacts(struct Person** contacts);
void getInput(struct Person* person)
{
printf("請(qǐng)輸入姓名:");
scanf("%s", person->name);
printf("請(qǐng)輸入電話:");
scanf("%s", person->phone);
}
void addPerson(struct Person** contacts)
{
struct Person* person;
struct Person* temp;
person = (struct Person*)malloc(sizeof(struct Person));
if (person == NULL)
{
printf("內(nèi)存分配失?。?");
exit(1);
}
getInput(person);
//將person添加到通訊錄中
if (*contacts != NULL)
{
temp = *contacts;
*contacts = person;
person->next = temp;
}
else
{
*contacts = person;
person->next = NULL;
}
}
void printPerson(struct Person* person)
{
printf("聯(lián)系人:%s
", person->name);
printf("電話:%s
", person->phone);
}
struct Person* findPerson(struct Person* contacts)
{
struct Person* current;
char input[40];
printf("請(qǐng)輸入聯(lián)系人:");
scanf("%s", input);
current = contacts;
while (current != NULL && strcmp(current->name, input))
{
current = current->next;
}
return current;
}
void changePerson(struct Person* contacts)
{
struct Person* person;
person = findPerson(contacts);
if (person == NULL)
{
printf("找不到聯(lián)系人!
");
}
else
{
printf("請(qǐng)輸入聯(lián)系電話:");
scanf("%s", person->phone);
}
}
void delPerson(struct Person** contacts)
{
struct Person* person;
struct Person* current;
struct Person* previous;
//先找到待刪除的節(jié)點(diǎn)的指針
person = findPerson(*contacts);
if (person == NULL)
{
printf("找不到該聯(lián)系人!
");
}
else
{
current = *contacts;
previous = NULL;
//將current定位到待刪除的節(jié)點(diǎn)
while (current != NULL && current != person)
{
previous = current;
current = current->next;
}
if (previous == NULL)
{
//若待刪除的是第一個(gè)節(jié)點(diǎn)
*contacts = current->next;
}
else
{
//若待刪除的不是第一個(gè)節(jié)點(diǎn)
previous->next = current->next;
}
free(person);//將內(nèi)存空間釋放
}
}
void displayContacts(struct Person* contacts)
{
struct Person* current;
current = contacts;
while (current != NULL)
{
printPerson(current);
current = current->next;
}
}
void releaseContacts(struct Person** contacts)
{
struct Person* temp;
while (*contacts != NULL)
{
temp = *contacts;
*contacts = (*contacts)->next;
free(temp);
}
}
int main(void)
{
int code;
struct Person* contacts = NULL;
struct Person* person;
printf("| 歡迎使用通訊錄管理程序 |
");
printf("|--- 1:插入新的聯(lián)系人 ---|
");
printf("|--- 2:查找現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 3:更改現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 4:刪除現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 5:顯示當(dāng)前通訊錄 ---|
");
printf("|--- 6:退出通訊錄程序 ---|
");
while (1)
{
printf("
請(qǐng)輸入指令代碼:");
scanf("%d", &code);
switch (code)
{
case 1:addPerson(&contacts); break;
case 2:person = findPerson(contacts);
if (person == NULL)
{
printf("找不到該聯(lián)系人!
");
}
else
{
printPerson(person);
}
break;
case 3:changePerson(contacts); break;
case 4:delPerson(&contacts); break;
case 5:displayContacts(contacts); break;
case 6:goto END;
}
}
END://此處直接跳出恒循環(huán)
releaseContacts(&contacts);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 04 V1
| 歡迎使用通訊錄管理程序 |
|--- 1:插入新的聯(lián)系人 ---|
|--- 2:查找現(xiàn)有聯(lián)系人 ---|
|--- 3:更改現(xiàn)有聯(lián)系人 ---|
|--- 4:刪除現(xiàn)有聯(lián)系人 ---|
|--- 5:顯示當(dāng)前通訊錄 ---|
|--- 6:退出通訊錄程序 ---|
請(qǐng)輸入指令代碼:1
請(qǐng)輸入姓名:HarrisWilde
請(qǐng)輸入電話:0101111
請(qǐng)輸入指令代碼:1
請(qǐng)輸入姓名:Jack
請(qǐng)輸入電話:0101112
請(qǐng)輸入指令代碼:1
請(qǐng)輸入姓名:Rose
請(qǐng)輸入電話:0101113
請(qǐng)輸入指令代碼:2
請(qǐng)輸入聯(lián)系人:HarrisWilde
聯(lián)系人:HarrisWilde
電話:0101111
請(qǐng)輸入指令代碼:2
請(qǐng)輸入聯(lián)系人:Mike
找不到該聯(lián)系人!
請(qǐng)輸入指令代碼:5
聯(lián)系人:Rose
電話:0101113
聯(lián)系人:Jack
電話:0101112
聯(lián)系人:HarrisWilde
電話:0101111
請(qǐng)輸入指令代碼:3
請(qǐng)輸入聯(lián)系人:HarrisWilde
請(qǐng)輸入聯(lián)系電話:0101234
請(qǐng)輸入指令代碼:5
聯(lián)系人:Rose
電話:0101113
聯(lián)系人:Jack
電話:0101112
聯(lián)系人:HarrisWilde
電話:0101234
請(qǐng)輸入指令代碼:6
下面加入內(nèi)存池:
//Example 04 V2
struct Person
{
char name[40];
char phone[20];
struct Person* next;
};
struct Person* pool = NULL;
int count;
void getInput(struct Person* person);
void printPerson(struct Person* person);
void addPerson(struct Person** contects);
void changePerson(struct Person* contacts);
void delPerson(struct Person** contacts);
struct Person* findPerson(struct Person* contacts);
void displayContacts(struct Person* contacts);
void releaseContacts(struct Person** contacts);
void releasePool(void);
void getInput(struct Person* person)
{
printf("請(qǐng)輸入姓名:");
scanf("%s", person->name);
printf("請(qǐng)輸入電話:");
scanf("%s", person->phone);
}
void addPerson(struct Person** contacts)
{
struct Person* person;
struct Person* temp;
//如果內(nèi)存池不是空的,那么首先從里面獲取空間
if (pool != NULL)
{
person = pool;
pool = pool->next;
count--;
}
//內(nèi)存池為空,則直接申請(qǐng)
else
{
person = (struct Person*)malloc(sizeof(struct Person));
if (person == NULL)
{
printf("內(nèi)存分配失?。?");
exit(1);
}
}
getInput(person);
//將person添加到通訊錄中
if (*contacts != NULL)
{
temp = *contacts;
*contacts = person;
person->next = temp;
}
else
{
*contacts = person;
person->next = NULL;
}
}
void printPerson(struct Person* person)
{
printf("聯(lián)系人:%s
", person->name);
printf("電話:%s
", person->phone);
}
struct Person* findPerson(struct Person* contacts)
{
struct Person* current;
char input[40];
printf("請(qǐng)輸入聯(lián)系人:");
scanf("%s", input);
current = contacts;
while (current != NULL && strcmp(current->name, input))
{
current = current->next;
}
return current;
}
void changePerson(struct Person* contacts)
{
struct Person* person;
person = findPerson(contacts);
if (person == NULL)
{
printf("找不到聯(lián)系人!
");
}
else
{
printf("請(qǐng)輸入聯(lián)系電話:");
scanf("%s", person->phone);
}
}
void delPerson(struct Person** contacts)
{
struct Person* person;
struct Person* current;
struct Person* previous;
struct Person* temp;
{
};
//先找到待刪除的節(jié)點(diǎn)的指針
person = findPerson(*contacts);
if (person == NULL)
{
printf("找不到該聯(lián)系人!
");
}
else
{
current = *contacts;
previous = NULL;
//將current定位到待刪除的節(jié)點(diǎn)
while (current != NULL && current != person)
{
previous = current;
current = current->next;
}
if (previous == NULL)
{
//若待刪除的是第一個(gè)節(jié)點(diǎn)
*contacts = current->next;
}
else
{
//若待刪除的不是第一個(gè)節(jié)點(diǎn)
previous->next = current->next;
}
//判斷內(nèi)存池中有沒有空位
if (count < MAX)
{
//使用頭插法將person指向的空間插入內(nèi)存池中
if (pool != NULL)
{
temp = pool;
pool = person;
person->next = temp;
}
else
{
pool = person;
person->next = NULL;
}
count++;
}
//沒有空位,直接釋放
else
{
free(person);//將內(nèi)存空間釋放
}
}
}
void displayContacts(struct Person* contacts)
{
struct Person* current;
current = contacts;
while (current != NULL)
{
printPerson(current);
current = current->next;
}
}
void releaseContacts(struct Person** contacts)
{
struct Person* temp;
while (*contacts != NULL)
{
temp = *contacts;
*contacts = (*contacts)->next;
free(temp);
}
}
void releasePool(void)
{
struct Person* temp;
while (pool != NULL)
{
temp = pool;
pool = pool->next;
free(temp);
}
}
int main(void)
{
int code;
struct Person* contacts = NULL;
struct Person* person;
printf("| 歡迎使用通訊錄管理程序 |
");
printf("|--- 1:插入新的聯(lián)系人 ---|
");
printf("|--- 2:查找現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 3:更改現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 4:刪除現(xiàn)有聯(lián)系人 ---|
");
printf("|--- 5:顯示當(dāng)前通訊錄 ---|
");
printf("|--- 6:退出通訊錄程序 ---|
");
while (1)
{
printf("
請(qǐng)輸入指令代碼:");
scanf("%d", &code);
switch (code)
{
case 1:addPerson(&contacts); break;
case 2:person = findPerson(contacts);
if (person == NULL)
{
printf("找不到該聯(lián)系人!
");
}
else
{
printPerson(person);
}
break;
case 3:changePerson(contacts); break;
case 4:delPerson(&contacts); break;
case 5:displayContacts(contacts); break;
case 6:goto END;
}
}
END://此處直接跳出恒循環(huán)
releaseContacts(&contacts);
releasePool();
return 0;
}
typedef
給數(shù)據(jù)類型起別名
C語言是一門古老的語言,它是在1969至1973年間,由兩位天才丹尼斯·里奇和肯·湯普遜在貝爾實(shí)驗(yàn)室以B語言為基礎(chǔ)開發(fā)出來的,用于他們的重寫UNIX計(jì)劃(這也為后來UNIX系統(tǒng)的可移植性打下了基礎(chǔ),之前的UNIX是使用匯編語言編寫的,當(dāng)然也是這兩位為了玩一個(gè)自己設(shè)計(jì)的游戲而編寫的)。天才就是和咱常人不一樣,不過他倆的故事,在這篇里面不多啰嗦,我們回到話題。雖然C語言誕生的很早,但是卻依舊不是最早的高級(jí)編程語言。目前公認(rèn)的最早的高級(jí)編程語言,是IBM公司于1957年開發(fā)的FORTRAN語言。C語言誕生之時(shí),F(xiàn)ORTRAN已經(jīng)統(tǒng)領(lǐng)行業(yè)數(shù)十年之久。因此,C語言要想快速吸納FORTRAN中的潛在用戶,就必須做出一些妥協(xié)。我們知道,不同的語言的語法,一般來說是不同的,甚至還有較大的差距。比如:C:
int a, b, c;
float i, j, k;
而FORTRAN語言是這樣的:
integer :: a, b, c;
real :: i, j, k;
如果讓FORTRAN用戶使用原來的變量名稱進(jìn)行使用,那么就能夠快速遷移到C語言上面來,這就是typedef的用處之一。
我們使用FORTRAN語言的類型名,那就這么辦:
typedef int integer;
typedef float real;
integer a, b, c;
real i, j, k;
結(jié)構(gòu)體的搭檔
雖然結(jié)構(gòu)體的出現(xiàn)能夠讓我們有一個(gè)更科學(xué)的數(shù)據(jù)結(jié)構(gòu)來管理數(shù)據(jù),但是每次使用結(jié)構(gòu)體都需要struct...,未免顯得有些冗長和麻煩。有了typedef的助攻,我們就可以很輕松地給結(jié)構(gòu)體類型起一個(gè)容易理解的名字:
typedef struct date
{
int year;
int month;
int day;
} DATE;//為了區(qū)分,一般用全大寫
int main(void)
{
DATE* date;
...
}
甚至還可以順便給它的指針也定義一個(gè)別名:
typedef struct date
{
int year;
int month;
int day;
} DATE, *PDATE;
進(jìn)階
我們還可以利用typedef來簡化一些比較復(fù)雜的命令。比如:
int (*ptr) [5];
我們知道這是一個(gè)數(shù)組指針,指向一個(gè)5元素的數(shù)組。那么我們可以改寫成這樣:
typedef int(*PTR_TO_ARRAY)[3];
這樣就可以把很復(fù)雜的聲明變得很簡單:
PTR_TO_ARRAY a = &array;
取名的時(shí)候要盡量使用容易理解的名字,這樣才能達(dá)到使用typedef的最終目的。
共用體
共用體也稱聯(lián)合體。聲明
和結(jié)構(gòu)體還是有點(diǎn)像:
union 共用體名稱
{
成員1;
成員2;
成員3;
};
但是兩者有本質(zhì)的不同。共用體的每一個(gè)成員共用一段內(nèi)存,那么這也就意味著它們不可能同時(shí)被正確地訪問。如:
//Example 05
union Test
{
int i;
double pi;
char str[9];
};
int main(void)
{
union Test test;
test.i = 10;
test.pi = 3.14;
strcpy(test.str, "TechZone");
printf("test.i: %d
", test.i);
printf("test.pi: %.2f
", test.pi);
printf("test.str: %s
", test.str);
return 0;
}
執(zhí)行結(jié)果如下:
//Consequence 05
test.i: 1751344468
test.pi: 3946574856045802736197446431383475413237648487838717723111623714247921409395495328582015991082102150186282825269379326297769425957893182570875995348588904500564659454087397032067072.00
test.str: TechZone
可以看到,共用體只能正確地展示出最后一次被賦值的成員。共用體的內(nèi)存應(yīng)該要能夠滿足最大的成員能夠正常存儲(chǔ)。但是并不一定等于最大的成員的尺寸,因?yàn)檫€要考慮內(nèi)存對(duì)齊的問題。
共用體可以類似結(jié)構(gòu)體一樣來定義和聲明,但是共用體還可以允許不帶名字:
union
{
int i;
char ch;
float f;
} a, b;
初始化
共用體不能在同一時(shí)間存放多個(gè)成員,所以不能批量初始化
union data
{
int i;
char ch;
float f;
};
union data a = {520}; //初始化第一個(gè)成員
union data b = a; //直接使用一個(gè)共用體初始化另一個(gè)共用體
union data c = {.ch = 'C'}; //C99的特性,指定初始化成員
枚舉
枚舉是一個(gè)基本的數(shù)據(jù)類型,它可以讓數(shù)據(jù)更簡潔。如果寫一個(gè)判斷星期的文章,我們當(dāng)然可以使用宏定義來使代碼更加易懂,不過:
這樣的寫法有點(diǎn)費(fèi)鍵盤。那么枚舉就簡單多了:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
?
**注意:**第一個(gè)枚舉成員的默認(rèn)值為整型的 0,后續(xù)枚舉成員的值在前一個(gè)成員上加 1。我們?cè)谶@個(gè)實(shí)例中把第一個(gè)枚舉成員的值定義為 1,第二個(gè)就為 2,以此類推。
?枚舉變量的定義和聲明方法和共用體一樣,也可以省略枚舉名,直接聲明變量名。
//Example 06
int main()
{
enum color { red = 1, green, blue };
enum color favorite_color;
printf("請(qǐng)輸入你喜歡的顏色: (1. red, 2. green, 3. blue): ");
scanf("%d", &favorite_color);
//輸出結(jié)果
switch (favorite_color)
{
case red:
printf("你喜歡的顏色是紅色");
break;
case green:
printf("你喜歡的顏色是綠色");
break;
case blue:
printf("你喜歡的顏色是藍(lán)色");
break;
default:
printf("你沒有選擇你喜歡的顏色");
}
return 0;
}
執(zhí)行結(jié)果如下:
//Consequence 06
請(qǐng)輸入你喜歡的顏色: (1. red, 2. green, 3. blue): 3
你喜歡的顏色是藍(lán)色
也可以把整數(shù)轉(zhuǎn)換為枚舉類型:
//Example 07
int main()
{
enum day
{
saturday,
sunday,
monday,
tuesday,
wednesday,
thursday,
friday
} workday;
int a = 1;
enum day weekend;
weekend = (enum day) a; //使用強(qiáng)制類型轉(zhuǎn)換
//weekend = a; //錯(cuò)誤
printf("weekend:%d", weekend);
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 07
weekend:1
位域
C語言除了開發(fā)桌面應(yīng)用等,還有一個(gè)很重要的領(lǐng)域,那就是「單片機(jī)」開發(fā)。單片機(jī)上的硬件資源十分有限,容不得我們?nèi)ニ烈鈸]灑。單片機(jī)使一種集成電路芯片,使采用超大規(guī)模集成電路技術(shù)把具有數(shù)據(jù)處理能力的CPU、RAM、ROM、I/O、中斷系統(tǒng)、定時(shí)器/計(jì)數(shù)器等功能(有的還包括顯示驅(qū)動(dòng)電路、脈寬調(diào)制電路、模擬多路轉(zhuǎn)換器、A/D轉(zhuǎn)換器等電路)集成到一塊硅片上構(gòu)成的一個(gè)小而完善的微型計(jì)算機(jī)系統(tǒng),在工控領(lǐng)域使用廣泛。對(duì)于這樣的設(shè)備,通常內(nèi)存只有256B,那么能夠給我們利用的資源就十分珍貴了。在這種情況下,如果我們只需要定義一個(gè)變量來存放布爾值,一般就申請(qǐng)一個(gè)整型變量,通過1和0來間接存儲(chǔ)。但是,顯然1和0只用1個(gè)bit就能夠放完,而一個(gè)整型卻是4個(gè)字節(jié),也就是32bit。這就造成了內(nèi)存的浪費(fèi)。好在,C語言為我們提供了一種數(shù)據(jù)結(jié)構(gòu),稱為「位域」(也叫位端、位字段)。也就是把一個(gè)字節(jié)中的二進(jìn)制位劃分,并且你能夠指定每個(gè)區(qū)域的位數(shù)。每個(gè)域有一個(gè)域名,并允許程序中按域名進(jìn)行單獨(dú)操作。使用位域的做法是在結(jié)構(gòu)體定義的時(shí)候,在結(jié)構(gòu)體成員后面使用冒號(hào)(:)和數(shù)字來表示該成員所占的位數(shù)。
//Example 08
int main(void)
{
struct Test
{
unsigned int a : 1;
unsigned int b : 1;
unsigned int c : 2;
} test;
test.a = 0;
test.b = 1;
test.c = 2;
printf("a = %d, b = %d, c = %d
", test.a, test.b, test.c);
printf("size of test = %d
", sizeof(test));
return 0;
}
運(yùn)行結(jié)果如下:
//Consequence 08
a = 0, b = 1, c = 2
size of test = 4
如此一來,結(jié)構(gòu)體test只用了4bit,卻存放下了0、1、2三個(gè)整數(shù)。但是由于2在二進(jìn)制中是10,因此占了2個(gè)bit。如果把test.b賦值為2,那么:
//Consequence 08 V2
a = 0, b = 0, c = 2
size of test = 4
可以看到,b中的10溢出了,只剩下0。
當(dāng)然,位域的寬度不能夠超過本身類型的長度,比如:
unsigned int a : 100;
那么就會(huì)報(bào)錯(cuò):
錯(cuò)誤 C2034 “main::a”: 位域類型對(duì)位數(shù)太小
位域成員也可以沒有名稱,只要給出類型和寬度即可:
struct Test
{
unsigned int x : 1;
unsigned int y : 2;
unsigned int z : 3;
unsigned int : 26;
};
無名位域一般用來作為填充或者調(diào)整成員的位置,因?yàn)闆]有名稱,所以無名位域并不能夠拿來使用。
?C語言的標(biāo)準(zhǔn)只說明unsigned int和signed int支持位域,然后C99增加了_Bool類型也支持位域,其他數(shù)據(jù)類型理論上是不支持的。不過大多數(shù)編譯器在具體實(shí)現(xiàn)時(shí)都進(jìn)行了擴(kuò)展,額外支持了signed char、unsigned char以及枚舉類型,所以如果對(duì)char類型的結(jié)構(gòu)體成員使用位域,基本上也沒什么問題。但如果考慮到程序的可移植性,就需要謹(jǐn)慎對(duì)待了。另外,由于內(nèi)存的基本單位是字節(jié),而位域只是字節(jié)的一部分,所以并不能對(duì)位域進(jìn)行取地址運(yùn)算。
?雖然科技發(fā)展日新月異,但是秉承著節(jié)約成本這個(gè)放之四海而皆準(zhǔn)的原則,還是要注意使用!畢竟5毛錢可能是小錢,但是乘以5000萬呢?
-
C語言
+關(guān)注
關(guān)注
180文章
7614瀏覽量
137800 -
編譯器
+關(guān)注
關(guān)注
1文章
1642瀏覽量
49311 -
數(shù)據(jù)集
+關(guān)注
關(guān)注
4文章
1209瀏覽量
24845
原文標(biāo)題:C語言結(jié)構(gòu)體完全筆記
文章出處:【微信號(hào):c-stm32,微信公眾號(hào):STM32嵌入式開發(fā)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
C語言結(jié)構(gòu)體對(duì)齊介紹
![<b class='flag-5'>C</b><b class='flag-5'>語言</b><b class='flag-5'>結(jié)構(gòu)</b><b class='flag-5'>體</b>對(duì)齊介紹](https://file1.elecfans.com/web2/M00/8C/76/wKgaomSs0U6ABrukAAEdoWhlZq4120.jpg)
C語言中數(shù)組和結(jié)構(gòu)體的內(nèi)存表示和布局
關(guān)于學(xué)習(xí)stm32 C語言編程結(jié)構(gòu)體的使用
C++筆記004:C++類通俗點(diǎn)說—— C結(jié)構(gòu)體復(fù)習(xí)
漫談C語言結(jié)構(gòu)體
單片機(jī)C語言 -- 基于結(jié)構(gòu)體的面向?qū)ο缶幊碳记?/a>
C語言的結(jié)構(gòu)體在單片機(jī)中的應(yīng)用有哪些?
如何寫單片機(jī)的C語言結(jié)構(gòu)體
C語言入門教程-指向結(jié)構(gòu)體的指針
如何學(xué)習(xí)c語言?C語言學(xué)習(xí)筆記資料免費(fèi)下載
C語言之結(jié)構(gòu)體的聲明與定義
![<b class='flag-5'>C</b><b class='flag-5'>語言</b>之<b class='flag-5'>結(jié)構(gòu)</b><b class='flag-5'>體</b>的聲明與定義](https://file.elecfans.com/web1/M00/C0/4F/o4YBAF8GbIOAH9YcAAAH975MCPw260.jpg)
評(píng)論