黑马程序员C语言基础(第七天)内存管理
黑馬程序員C語(yǔ)言基礎(chǔ)(第一天)
黑馬程序員C語(yǔ)言基礎(chǔ)(第二天)
黑馬程序員C語(yǔ)言基礎(chǔ)(第三天)
黑馬程序員C語(yǔ)言基礎(chǔ)(第四天)數(shù)據(jù)類型
黑馬程序員C語(yǔ)言基礎(chǔ)(第五天)運(yùn)算符與表達(dá)式、程序流程結(jié)構(gòu)、數(shù)組和字符串、函數(shù)
黑馬程序員C語(yǔ)言基礎(chǔ)(第六天)指針
黑馬程序員C語(yǔ)言基礎(chǔ)(第七天)內(nèi)存管理
視頻地址
文章目錄
- 內(nèi)存管理
- 作用域
- 8.1.1 局部變量(auto自動(dòng)變量)
- 8.1.2 靜態(tài)(static)局部變量
- 普通局部變量和靜態(tài)(static)局部變量的區(qū)別
- 8.1.3 全局變量
- 跨文件使用全局變量示例(沒用頭文件)
- 跨文件使用全局變量示例(用頭文件)
- 8.1.4 靜態(tài)(static)全局變量
- 8.1.5 extern全局變量聲明
- 8.1.6 全局函數(shù)(普通函數(shù))和靜態(tài)函數(shù)(static函數(shù))
- 8.1.7 總結(jié)
- 8.2 內(nèi)存布局
- 8.2.1 內(nèi)存分區(qū)(text代碼區(qū)、data區(qū)、bss區(qū)、棧區(qū)、堆區(qū))
- 8.2.2 存儲(chǔ)類型總結(jié)(有用!)
- 棧越界:指分配的內(nèi)存空間超出了棧的范圍(ulimit -a)
- 8.2.3 存儲(chǔ)類型總結(jié)內(nèi)存操作函數(shù)
- 1) memset()(將s的內(nèi)存區(qū)域的前n個(gè)字節(jié)以字符c填入)(注意是以字節(jié)為單位填入,一個(gè)字節(jié)填入一個(gè)字符的ascii碼)
- 2) memcpy()(拷貝src所指的內(nèi)存內(nèi)容的前n個(gè)字節(jié)到dest所值的內(nèi)存地址上)(即使遇到 \0 也能拷貝)
- 3) memmove()(內(nèi)存重疊時(shí)代替memcpy()使用)(內(nèi)存重疊:使用memcpy拷貝的過程中原數(shù)組值發(fā)生變化,使得結(jié)果不正確)(踩內(nèi)存)
- 4) memcmp()(主要用來(lái)比較兩個(gè)地址前n個(gè)字節(jié)是否相等)
- 8.2.4 堆區(qū)內(nèi)存分配和釋放
- 1)malloc()
- 2)free()
- 8.3 內(nèi)存分區(qū)代碼分析(在Linux下測(cè)試)
- 1) 返回棧區(qū)地址
- 2) 返回data區(qū)地址
- 3) 值傳遞1
- 4) 值傳遞2
- 5) 返回堆區(qū)地址(較上面兩種完美)
內(nèi)存管理
作用域
C語(yǔ)言變量的作用域分為:
- 代碼塊作用域(代碼塊是{}之間的一段代碼)
- 函數(shù)作用域
- 文件作用域
8.1.1 局部變量(auto自動(dòng)變量)
局部變量也叫auto自動(dòng)變量(auto可寫可不寫),一般情況下代碼塊{}內(nèi)部定義的變量都是自動(dòng)變量,它有如下特點(diǎn):
- 在一個(gè)函數(shù)內(nèi)定義,只在函數(shù)范圍內(nèi)有效
- 在復(fù)合語(yǔ)句中定義,只在復(fù)合語(yǔ)句中有效
- 隨著函數(shù)調(diào)用的結(jié)束或復(fù)合語(yǔ)句的結(jié)束局部變量的聲明聲明周期也結(jié)束
- 如果沒有賦初值,內(nèi)容為隨機(jī)
注意:auto關(guān)鍵字在c++ .cpp文件下不能識(shí)別
#include <stdio.h>void test() {//auto寫不寫是一樣的//auto只能出現(xiàn)在{}內(nèi)部auto int b = 10; }int main(void) {//b = 100; //err, 在main作用域中沒有bif (1){//在復(fù)合語(yǔ)句中定義,只在復(fù)合語(yǔ)句中有效int a = 10;printf("a = %d\n", a);}//a = 10; //err離開if()的復(fù)合語(yǔ)句,a已經(jīng)不存在return 0; }8.1.2 靜態(tài)(static)局部變量
- static局部變量的作用域也是在定義的函數(shù)內(nèi)有效
- static局部變量的生命周期和程序運(yùn)行周期一樣,同時(shí)staitc局部變量的值只初始化一次(如:static int i =10;只能運(yùn)行一次),但可以賦值多次
- static局部變量若未賦以初值,則由系統(tǒng)自動(dòng)賦值:數(shù)值型變量自動(dòng)賦初值0,字符型變量賦空字符
結(jié)果:
i = 1 i = 1 a = 1 a = 2普通局部變量和靜態(tài)(static)局部變量的區(qū)別
8.1.3 全局變量
聲明用extern關(guān)鍵字聲明
跨文件使用全局變量也同樣要先聲明
- 在函數(shù)外定義,可被本文件及其它文件中的函數(shù)所共用,若其它文件中的函數(shù)調(diào)用此變量,須用extern聲明
- 全局變量的生命周期和程序運(yùn)行周期一樣
- 不同文件的全局變量不可重名
跨文件使用全局變量示例(沒用頭文件)
main.cpp
#include <stdio.h>int main(void) {extern void test();test();extern int a;extern int b;a = 111;b = 222;printf("a = %d, b= %d\n", a, b);return 0; }fun.cpp
#include <stdio.h> #include <stdlib.h> #include <String.h>int a = 10; int b = 5; void test() {a = 1;b = 1; }運(yùn)行結(jié)果:
a = 111, b= 222跨文件使用全局變量示例(用頭文件)
main.cpp
#include <stdio.h> #include "test.h"int main(void) {test();a = 111;b = 222;printf("a = %d, b= %d\n", a, b);return 0; }fun.cpp
#include <stdio.h> #include <stdlib.h> #include <String.h>int a = 10; int b = 5; void test() {a = 1;b = 1; }test.h
#pragma once extern void test(); extern int a; extern int b;運(yùn)行結(jié)果:
a = 111, b= 2228.1.4 靜態(tài)(static)全局變量
- 在函數(shù)外定義,作用范圍被限制在所定義的文件中
- 不同文件靜態(tài)全局變量可以重名,但作用域不沖突
- static全局變量的生命周期和程序運(yùn)行周期一樣,同時(shí)staitc全局變量的值只初始化一次
8.1.5 extern全局變量聲明
extern int a;聲明一個(gè)變量,這個(gè)變量在別的文件中已經(jīng)定義了,這里只是聲明,而不是定義。
8.1.6 全局函數(shù)(普通函數(shù))和靜態(tài)函數(shù)(static函數(shù))
在C語(yǔ)言中函數(shù)默認(rèn)都是全局的,使用關(guān)鍵字static可以將函數(shù)聲明為靜態(tài),函數(shù)定義為static就意味著這個(gè)函數(shù)只能在定義這個(gè)函數(shù)的文件中使用,在其他文件中不能調(diào)用,即使在其他文件中聲明這個(gè)函數(shù)都沒用。
對(duì)于不同文件中的staitc函數(shù)名字可以相同。
注意:
- 允許在不同的函數(shù)中使用相同的變量名,它們代表不同的對(duì)象,分配不同的單元,互不干擾。
- 同一源文件中,允許全局變量和局部變量同名,在局部變量的作用域內(nèi),全局變量不起作用。
- 所有的函數(shù)默認(rèn)都是全局的,意味著所有的函數(shù)都不能重名,但如果是staitc函數(shù),那么作用域是文件級(jí)的,所以不同的文件static函數(shù)名是可以相同的。
8.1.7 總結(jié)
auto變量就是局部變量(普通變量),extern變量就是全局變量,extern函數(shù)就是普通函數(shù)
8.2 內(nèi)存布局
size ./out
可以看區(qū)?
還真可以!
dontla@dontla-virtual-machine:~/桌面/test$ size testtext data bss dec hex filename1820 608 8 2436 984 test8.2.1 內(nèi)存分區(qū)(text代碼區(qū)、data區(qū)、bss區(qū)、棧區(qū)、堆區(qū))
C代碼經(jīng)過預(yù)處理、編譯、匯編、鏈接4步后生成一個(gè)可執(zhí)行程序。
在 Linux 下,程序是一個(gè)普通的可執(zhí)行文件,以下列出一個(gè)二進(jìn)制可執(zhí)行文件的基本情況:
通過上圖可以得知,在沒有運(yùn)行程序前,也就是說(shuō)程序沒有加載到內(nèi)存前,可執(zhí)行程序內(nèi)部已經(jīng)分好3段信息,分別為代碼區(qū)(text)、數(shù)據(jù)區(qū)(data)和未初始化數(shù)據(jù)區(qū)(bss)3 個(gè)部分(有些人直接把data和bss合起來(lái)叫做靜態(tài)區(qū)或全局區(qū))。
-
代碼區(qū)
存放 CPU 執(zhí)行的機(jī)器指令。通常代碼區(qū)是可共享的(即另外的執(zhí)行程序可以調(diào)用它),使其可共享的目的是對(duì)于頻繁被執(zhí)行的程序,只需要在內(nèi)存中有一份代碼即可。代碼區(qū)通常是只讀的,使其只讀的原因是防止程序意外地修改了它的指令。另外,代碼區(qū)還規(guī)劃了局部變量的相關(guān)信息。 -
全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(data段)
該區(qū)包含了在程序中明確被初始化的全局變量、已經(jīng)初始化的靜態(tài)變量(包括全局靜態(tài)變量和局部靜態(tài)變量)和常量數(shù)據(jù)(如字符串常量)。 -
未初始化數(shù)據(jù)區(qū)(又叫 bss 區(qū))
存入的是全局未初始化變量和未初始化靜態(tài)變量。未初始化數(shù)據(jù)區(qū)的數(shù)據(jù)在程序開始執(zhí)行之前被內(nèi)核初始化為 0 或者空(NULL)。
程序在加載到內(nèi)存前,代碼區(qū)和全局區(qū)(data和bss)的大小就是固定的,程序運(yùn)行期間不能改變。然后,運(yùn)行可執(zhí)行程序,系統(tǒng)把程序加載到內(nèi)存,除了根據(jù)可執(zhí)行程序的信息分出代碼區(qū)(text)、數(shù)據(jù)區(qū)(data)和未初始化數(shù)據(jù)區(qū)(bss)之外,還額外增加了棧區(qū)、堆區(qū)。
-
代碼區(qū)(text segment)
加載的是可執(zhí)行文件代碼段,所有的可執(zhí)行代碼都加載到代碼區(qū),這塊內(nèi)存是不可以在運(yùn)行期間修改的。 -
未初始化數(shù)據(jù)區(qū)(BSS)(Block Started by Symbol)
加載的是可執(zhí)行文件BSS段,位置可以分開亦可以緊靠數(shù)據(jù)段,存儲(chǔ)于數(shù)據(jù)段的數(shù)據(jù)(全局未初始化,靜態(tài)未初始化數(shù)據(jù))的生存周期為整個(gè)程序運(yùn)行過程。 -
全局初始化數(shù)據(jù)區(qū)/靜態(tài)數(shù)據(jù)區(qū)(data segment)
加載的是可執(zhí)行文件數(shù)據(jù)段,存儲(chǔ)于數(shù)據(jù)段(全局初始化,靜態(tài)初始化數(shù)據(jù),文字常量(只讀))的數(shù)據(jù)的生存周期為整個(gè)程序運(yùn)行過程。 -
棧區(qū)(stack)
棧是一種先進(jìn)后出的內(nèi)存結(jié)構(gòu),由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值、返回值、局部變量等。在程序運(yùn)行過程中實(shí)時(shí)加載和釋放,因此,局部變量的生存周期為申請(qǐng)到釋放該段??臻g。 -
堆區(qū)(heap)
堆是一個(gè)大容器,它的容量要遠(yuǎn)遠(yuǎn)大于棧,但沒有棧那樣先進(jìn)后出的順序。用于動(dòng)態(tài)內(nèi)存分配。堆在內(nèi)存中位于BSS區(qū)和棧區(qū)之間。一般由程序員分配和釋放,若程序員不釋放,程序結(jié)束時(shí)由操作系統(tǒng)回收。
8.2.2 存儲(chǔ)類型總結(jié)(有用!)
#include <stdio.h> #include <stdlib.h>int e; static int f; int g = 10; static int h = 10; int main() {int a;int b = 10;static int c;static int d = 10;char *i = "test";char *k = NULL;printf("&a\t %p\t //局部未初始化變量\n", &a);printf("&b\t %p\t //局部初始化變量\n", &b);printf("&c\t %p\t //靜態(tài)局部未初始化變量\n", &c);printf("&d\t %p\t //靜態(tài)局部初始化變量\n", &d);printf("&e\t %p\t //全局未初始化變量\n", &e);printf("&f\t %p\t //全局靜態(tài)未初始化變量\n", &f);printf("&g\t %p\t //全局初始化變量\n", &g);printf("&h\t %p\t //全局靜態(tài)初始化變量\n", &h);printf("i\t %p\t //只讀數(shù)據(jù)(文字常量區(qū))\n", i);k = (char *)malloc(10);printf("k\t %p\t //動(dòng)態(tài)分配的內(nèi)存\n", k);return 0; } &a 00D3FA00 //局部未初始化變量 &b 00D3F9F4 //局部初始化變量 &c 0118A18C //靜態(tài)局部未初始化變量 &d 0118A048 //靜態(tài)局部初始化變量 &e 0118A184 //全局未初始化變量 &f 0118A188 //全局靜態(tài)未初始化變量 &g 0118A040 //全局初始化變量 &h 0118A044 //全局靜態(tài)初始化變量 i 01187B30 //只讀數(shù)據(jù)(文字常量區(qū)) k 00FF04D0 //動(dòng)態(tài)分配的內(nèi)存棧越界:指分配的內(nèi)存空間超出了棧的范圍(ulimit -a)
在linux下用ulimit -a指令查看各個(gè)空間大小:
可以看到棧的空間為8192kbytes,也就是8Mb
dontla@dontla-virtual-machine:~/桌面/test$ ulimit -a core file size (blocks, -c) 0 data seg size (kbytes, -d) unlimited scheduling priority (-e) 0 file size (blocks, -f) unlimited pending signals (-i) 111644 max locked memory (kbytes, -l) 65536 max memory size (kbytes, -m) unlimited open files (-n) 1024 pipe size (512 bytes, -p) 8 POSIX message queues (bytes, -q) 819200 real-time priority (-r) 0 stack size (kbytes, -s) 8192 cpu time (seconds, -t) unlimited max user processes (-u) 111644 virtual memory (kbytes, -v) unlimited file locks (-x) unlimited放visual studio上也會(huì)報(bào)錯(cuò):(但大小好像不太一樣?)
8.2.3 存儲(chǔ)類型總結(jié)內(nèi)存操作函數(shù)
1) memset()(將s的內(nèi)存區(qū)域的前n個(gè)字節(jié)以字符c填入)(注意是以字節(jié)為單位填入,一個(gè)字節(jié)填入一個(gè)字符的ascii碼)
#include <string.h> void *memset(void *s, int c, size_t n); 功能:將s的內(nèi)存區(qū)域的前n個(gè)字節(jié)以參數(shù)c填入 參數(shù):s:需要操作內(nèi)存s的首地址c:填充的字符,c雖然參數(shù)為int,但必須是unsigned char , 范圍為0~255n:指定需要設(shè)置的大小 返回值:s的首地址 #include <stdio.h> #include <String.h>int main() {int a[10];memset(a, 0, sizeof(a));memset(a, 97, sizeof(a));//memset(a, 'a', sizeof(a));//都可以int i = 0;for (i = 0; i < 10; i++){printf("%c\n", a[i]);}return 0; }結(jié)果:
a a a a a a a a a a示例:以字節(jié)為單位初始化
2) memcpy()(拷貝src所指的內(nèi)存內(nèi)容的前n個(gè)字節(jié)到dest所值的內(nèi)存地址上)(即使遇到 \0 也能拷貝)
#include <string.h> void *memcpy(void *dest, const void *src, size_t n); 功能:拷貝src所指的內(nèi)存內(nèi)容的前n個(gè)字節(jié)到dest所值的內(nèi)存地址上。 參數(shù):dest:目的內(nèi)存首地址src:源內(nèi)存首地址,注意:dest和src所指的內(nèi)存空間不可重疊n:需要拷貝的字節(jié)數(shù) 返回值:dest的首地址示例1:
#include "cal.h" #include<stdio.h> int main() { int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10] = {0};memcpy(b, a, sizeof(a));for (int i = 0; i < 10; i++){printf("%d, ",*(b+i));}printf("\n");//memcpy(&a[3], a, 5 * sizeof(int)); //err, 內(nèi)存重疊 }結(jié)果:
1, 2, 3, 4, 5, 6, 7, 8, 9, 10,示例2:
#include <stdio.h> #include <String.h>int main() {char p[] = "adfsfdsf\0assfs";//printf("%s\n", p);//adfsfdsf//printf("%d\n",sizeof(p));//15char buf[100];strncpy_s(buf, p, sizeof(p));printf("%s\n", buf);//adfsfdsfprintf("%s\n", buf + strlen("adfsfdsf") + 1);//無(wú)(說(shuō)明strncpy沒法將\0后面的字符拷貝過去)memset(buf, 0, sizeof(buf));memcpy(buf, p, sizeof(p));printf("%s\n", buf);//adfsfdsfprintf("%s\n", buf + strlen("adfsfdsf") + 1);//assfsreturn 0; }3) memmove()(內(nèi)存重疊時(shí)代替memcpy()使用)(內(nèi)存重疊:使用memcpy拷貝的過程中原數(shù)組值發(fā)生變化,使得結(jié)果不正確)(踩內(nèi)存)
memmove()功能用法和memcpy()一樣,區(qū)別在于:dest和src所指的內(nèi)存空間重疊時(shí),memmove()仍然能處理,不過執(zhí)行效率比memcpy()低些。
4) memcmp()(主要用來(lái)比較兩個(gè)地址前n個(gè)字節(jié)是否相等)
#include <string.h> int memcmp(const void *s1, const void *s2, size_t n); 功能:比較s1和s2所指向內(nèi)存區(qū)域的前n個(gè)字節(jié) 參數(shù):s1:內(nèi)存首地址1s2:內(nèi)存首地址2n:需比較的前n個(gè)字節(jié) 返回值:相等:=0大于:>0小于:<0 #include <stdio.h> #include <String.h>int main() {int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int b[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };int flag = memcmp(a, b, sizeof(a));printf("flag = %d\n", flag);//flag = 0 }8.2.4 堆區(qū)內(nèi)存分配和釋放
1)malloc()
#include <stdlib.h> void *malloc(size_t size); 功能:在內(nèi)存的動(dòng)態(tài)存儲(chǔ)區(qū)(堆區(qū))中分配一塊長(zhǎng)度為size字節(jié)的連續(xù)區(qū)域,用來(lái)存放類型說(shuō)明符指定的類型。分配的內(nèi)存空間內(nèi)容不確定,一般使用memset初始化。 參數(shù):size:需要分配內(nèi)存大小(單位:字節(jié)) 返回值: 成功:分配空間的起始地址 失敗:NULL #include <stdlib.h> #include <stdio.h> #include <string.h>int main() {int count, * array, n;printf("請(qǐng)輸入要申請(qǐng)數(shù)組的個(gè)數(shù):\n");scanf_s("%d", &n);array = (int*)malloc(n * sizeof(int));if (array == NULL){printf("申請(qǐng)空間失敗!\n");return -1;}//將申請(qǐng)到空間清0memset(array, 0, sizeof(int) * n);for (count = 0; count < n; count++) /*給數(shù)組賦值*///array[count] = count;//提示波浪線?*(array + count) = count;//不提示for (count = 0; count < n; count++) /*打印數(shù)組元素*/printf("%2d", *(array+count));free(array);return 0; }結(jié)果:
請(qǐng)輸入要申請(qǐng)數(shù)組的個(gè)數(shù): 60 1 2 3 4 5注意,。melloc(0)運(yùn)行時(shí)不會(huì)報(bào)錯(cuò),但調(diào)試最后free空間時(shí)會(huì)報(bào)這樣的錯(cuò)誤:
2)free()
#include <stdlib.h> void free(void *ptr); 功能:釋放ptr所指向的一塊內(nèi)存空間,ptr是一個(gè)任意類型的指針變量,指向被釋放區(qū)域的首地址。對(duì)同一內(nèi)存空間多次釋放會(huì)出錯(cuò)。 參數(shù): ptr:需要釋放空間的首地址,被釋放區(qū)應(yīng)是由malloc函數(shù)所分配的區(qū)域。 返回值:無(wú)示例:
#include <stdio.h> #include <String.h> #include<stdlib.h> int main() {int* address = (int*)malloc(sizeof(int));if (address==NULL) {printf("分配失敗!\n");return -1;}else if (address!=NULL) {*address = 11;}printf("%d\n", *address);//11if (NULL!=address) {free(address);address = NULL;}//printf("%d\n", *address);//運(yùn)行時(shí)不報(bào)錯(cuò),調(diào)試的時(shí)候報(bào)錯(cuò),address是空指針//*address = 12;//運(yùn)行時(shí)不報(bào)錯(cuò),調(diào)試的時(shí)候報(bào)錯(cuò) }8.3 內(nèi)存分區(qū)代碼分析(在Linux下測(cè)試)
1) 返回棧區(qū)地址
#include <stdio.h>int* fun() {int a = 10;return &a;//函數(shù)調(diào)用完畢,a釋放//通過visual studio調(diào)試發(fā)現(xiàn),雖然a沒了,但是原先a指向的內(nèi)存里的值還在,所以不會(huì)報(bào)錯(cuò),//但在linux,報(bào)錯(cuò)顯示不能返回局部變量的地址return &a }int main(int argc, char* argv[]) {int* p = NULL;p = fun();*p = 100;printf("%d\n",*p);//100return 0; }2) 返回data區(qū)地址
#include <stdio.h>int* fun() {static int a = 10;return &a; //函數(shù)調(diào)用完畢,a不釋放 }int main(int argc, char* argv[]) {int* p = NULL;p = fun();*p = 100; //okprintf("*p = %d\n", *p);//*p = 100return 0; }3) 值傳遞1
示例1:值傳遞,形參修改不會(huì)影響實(shí)參
#include <stdio.h> #include <stdlib.h>void fun(int* tmp) {tmp = (int*)malloc(sizeof(int));if (NULL!=tmp) {*tmp = 100;} }int main(int argc, char* argv[]) {int a = 1;int* p = &a;fun(p); //值傳遞,形參修改不會(huì)影響實(shí)參printf("*p = %d\n", *p);//err,操作空指針指向的內(nèi)存return 0; }示例2:
4) 值傳遞2
#include <stdio.h> #include <stdlib.h>void fun(int* tmp) {*tmp = 100; }int main(int argc, char* argv[]) {int* p = NULL;p = (int*)malloc(sizeof(int));fun(p); //值傳遞if (NULL!=p) {printf("*p = %d\n", *p); //ok,*p為100}return 0; }5) 返回堆區(qū)地址(較上面兩種完美)
#include <stdio.h> #include <stdlib.h>int* fun() {int* tmp = NULL;tmp = (int*)malloc(sizeof(int));if (NULL!=tmp) {*tmp = 100;}return tmp;//返回堆區(qū)地址,函數(shù)調(diào)用完畢,不釋放 }int main(int argc, char* argv[]) {int* p = NULL;p = fun();printf("*p = %d\n", *p);//ok//堆區(qū)空間,使用完畢,手動(dòng)釋放if (p != NULL){free(p);p = NULL;}return 0; }https://www.bilibili.com/video/BV1jW411K7z8/?p=42&spm_id_from=pageDriver
總結(jié)
以上是生活随笔為你收集整理的黑马程序员C语言基础(第七天)内存管理的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 注意:C语言结构体里不能赋初始值!
- 下一篇: 黑马程序员C语言基础(第八天)复合类型(