c2p6(c primer plus 6 edition) 14


p.s. 從十四章寫起,前面的一些章節(jié)不想補(二周目再說)
因為是基于書上的東西,我會做一些筆記,還有一些自己的理解(畢竟不是看原著只是看中文譯本),略過的部分讀者自行看書,有些是比較簡單讀者看書就懂,我就不再贅述。絕對不是懶[微笑]。

14.2 建立結(jié)構(gòu)聲明
14.3定義結(jié)構(gòu)變量
 14.3.1初始化結(jié)構(gòu)
 14.3.2訪問結(jié)構(gòu)體成員
 14.3.3結(jié)構(gòu)體初始化器
14.4結(jié)構(gòu)體數(shù)組
14.5嵌套結(jié)構(gòu)
14.6指向結(jié)構(gòu)的指針
 14.6.1聲明和初始化結(jié)構(gòu)指針
  14.6.2用指針訪問成員

14.7.1傳遞結(jié)構(gòu)成員
 14.7.2傳遞結(jié)構(gòu)的地址
 14.7.3傳遞結(jié)構(gòu)
 14.7.4其他結(jié)構(gòu)特性
 14.7.5 結(jié)構(gòu)和結(jié)構(gòu)指針的選擇
 14.7.6結(jié)構(gòu)中的字符數(shù)組和字符指針
14.8把結(jié)構(gòu)內(nèi)容保存到文件中
14.10聯(lián)合介紹(union)

14.2(定義)
  結(jié)構(gòu)的定義語法,struct可以不跟名字(不推薦),然后是變量(成員)的定義,變量的定義和平常我們的定義變量沒有區(qū)別。

struct name{
  data_type value_name;
};
struct {  //不推薦
  data_type value_name;
};

14.3(結(jié)構(gòu)體變量)
這是兩種定義的方法,在實際情況下我們我們會把struct放在.h里定義變量并不是一個好習(xí)慣,我們應(yīng)該在使用的時候再定義,并注意其它的生存周期。

struct name{  //可以沒有name 對照上面
      data_type value_name;
}joe;

struct name joe,*Moon,stu[10];
不貼內(nèi)存圖很不pro

14.3.1(初始化)
數(shù)值,字符或字符串之間,只需要用逗號隔開即可,如果是更復(fù)雜的結(jié)構(gòu)我們通過換行來提高閱讀性。(ANSI之前,不能用自動變量初始化結(jié)構(gòu):ANSI之后可以同任意存儲類型),初始化與初始化數(shù)組類似。

struct  rectangle{
     float length;
     float width;
 };
     
struct rectangle r1={4.0,2.0};

注意 初始化結(jié)構(gòu)體和類型存儲期
  第12章中提到,如果初始化一個具有靜態(tài)存儲時期的變量,只能使用常量。這條規(guī)則同樣使用與結(jié)構(gòu)體。如果存儲時期是自動的,列表的值就不必是常量了。

14.3.2(訪問結(jié)構(gòu)成員)
結(jié)構(gòu)本身類似于本質(zhì)上是一個超級數(shù)組,我們可以這樣訪問成員(變量)

r1.length=2;
printf("%f\n",r1.length);
scanf("%f",&r1.width);

這里由于 . 的運算優(yōu)先級比 & 更高所以&r1.width等同于&(r1.width)

14.3.3(結(jié)構(gòu)體初始化器)

#include "stdio.h"

 struct man{
    int length;
    int width;
    int weight;
};

int main(void){
    struct man p1={.weight=10,
                   .length=21,
                   .width=2,
                   3
};
    printf("%d%2d%2d\n",p1.length,p1.width,p1.weight);
}

最后一次賦值才是它真正的值,但賦值其實是按順序的賦值是根據(jù)前一個指向的成員的位置確認(rèn)下一個成員即便沒有成員名。

    struct man p1={
                   .length=21,
                   .width=2,
                   .weight=10,
                   3
};

這樣的一個寫法是3只會警告為無效,而不會出錯,由此可見編譯器是按照指向去初始化的,如果只是簡單的struct man p1={21,2,10}初始化編譯器會順序初始化,而我們使用騷操作也是可行的

struct man p1={.length=21,
               .width=2,
               .weight=10,
               .width=3,
               3,
               .length=10,
               9
};
//為騷而騷了,實際上并不會這么使用

14.4結(jié)構(gòu)體數(shù)組
struct man people[100]這不是一個合適的做法,因為是自動存儲類別變量,其中的信息會存到棧中,如果數(shù)量過大可能會導(dǎo)致溢出,這時可以設(shè)置編譯器中棧的大小,又或者可以創(chuàng)建靜態(tài)或者外部數(shù)組。(我們并不建議過大的數(shù)組)。

這里是沒看懂也沒遇過這種情況,雖說是優(yōu)化這是個缺陷,暫時mark(根本沒想過偷懶)

代碼14.2(部分代碼)
//只是大概情況,絕對沒偷懶[微笑]
#define len 40
struct book{
  char title[len];
  char author[len];
  float value;
};
int main(){
  ......
  scanf("%f",&library[count++].value);
  ......
  return 0;
}

14.5嵌套結(jié)構(gòu)
結(jié)構(gòu)嵌結(jié)構(gòu)其實是十分常見的,不過既然是結(jié)構(gòu)體,那自然可以套用結(jié)構(gòu)體聲明的規(guī)則了。同時我們用 . 或者->來訪問結(jié)構(gòu)體變量的結(jié)構(gòu)體成員的成員(該用哪個我們視情況而定)。

#define len 30
struct name{
  char first_name[len];
  char last_name[len];
};
struct book{
  char title[len];
  struct name author;
  float value;
};
int main(){
  struct book mybook={"c語言從入門到放棄",
                      {"蝦說","吳"},
                      54.32
};
  printf("hello %s%s",mybook.author.last_name,mybook.author.first_name);
  return 0;
}

14.6(指向結(jié)構(gòu)的指針)
喜歡使用指針的人如果得知能夠最使用指向結(jié)構(gòu)的指針會十分高興。原因有四
第一: 就像指向數(shù)組的指針比數(shù)組本身更容易操作(排序中會很明顯),指向結(jié)構(gòu)的指針通常比結(jié)構(gòu)本身更容易操作。
第二: 在早期的c實現(xiàn)中,結(jié)構(gòu)不能作為參數(shù)被傳遞給函數(shù)。但指向結(jié)構(gòu)的指針可以。
第三:即便可以傳遞結(jié)構(gòu),傳遞指針也更有效率。
第四:許多的數(shù)據(jù)表示,都使用了包含指向其他結(jié)構(gòu)的指針的結(jié)構(gòu)。

14.6.1(聲明和初始化結(jié)構(gòu)指針)

 struct book mybook;
 struct book papers[10];
 struct book *p=NULL;  //置空是個好習(xí)慣
 p=&mybook;
 p=&papers[0];

book結(jié)構(gòu)的大小的94(假設(shè)float是4)字節(jié),但是在一些系統(tǒng)中,一個結(jié)構(gòu)的大小可能比各成員大小的總和還大一點,這是因為系統(tǒng)對數(shù)據(jù)進行校準(zhǔn)過程中產(chǎn)生了“間隙”,例如有些系統(tǒng)必須把每個成員放在偶數(shù)地址上,或是4的倍數(shù)的地址上。

14.6.2(用指針訪問成員)
常見的訪問方式.->,這兩者區(qū)別,我們可以通俗的理解,一個實際的變量如 papers[0],*p我們用.訪問成員,而一些地址如p, papers, (&mybook)我們使用->來訪問成員。

p=&mybook;
mybook.value == p->value ==(*p).value ==(&mybook).value;
//*的優(yōu)先級低于.,必須先用括號,同理&也要加括號

14.7.1-4(傳遞成員、地址、結(jié)構(gòu)和其他特性)

char* sum1(char *,char *);  //傳遞成員
struct book *sum2(struct book *);  //傳遞地址
struct book sum3(struct book);  //傳遞結(jié)構(gòu)
  struct book mybook={"c語言從入門到放棄",
                      {"蝦說","吳"},
                      54.32
};
full_name1(mybook.names.first_name,mybook.names,last_name);
full_name2(&mybook);
full_name3(mybook);
struct book youbook=mybook; 

現(xiàn)在的c(第六版的出版年)包括ANIS C,允許結(jié)構(gòu)作為參數(shù)傳遞,還能作為參數(shù)傳遞。這里還是講究數(shù)據(jù)類型和函數(shù)的內(nèi)容

14.7.5(結(jié)構(gòu)和結(jié)構(gòu)指針的選擇)
  把指針作為參數(shù)有兩個優(yōu)點:任何C都可以實現(xiàn),執(zhí)行速度快,只要地址。缺點是無法保護數(shù)據(jù)在調(diào)用函數(shù)的某些操作可能使數(shù)據(jù)發(fā)生改變,ANSI C用const解決了這個問題??梢哉f是克服了缺點但是實際應(yīng)用難免修改某個成員的值。
  而傳遞結(jié)構(gòu)的優(yōu)點是,只是在修改副本,保護了原始數(shù)據(jù),當(dāng)然也更易閱讀。缺點是增加了內(nèi)存空間和傳遞時間。當(dāng)他只使用少數(shù)的參數(shù)時會顯得特別不討好。
  這樣說來,指針樣樣都好,但是程序的可閱讀性和維護性是你應(yīng)該想的(可能你在寫一個簡單的算法和demo時想不到這些因素)。

14.7.6結(jié)構(gòu)的字符數(shù)組和指針
如果編譯器比較寬容的話,代碼在運行時不會出錯。我用的是gcc 版本 6.4.1 2017 07 27 (Red Hat 6.4.1-1) (GCC)

#define len 30
struct name1{
  char first_name[len];
  char last_name[len];
};
struct name2 {
  char *first;
  char *last;
};
struct name1 programer;
name2 coder;
scanf("%s",programer.first_name);
scanf("%s",coder.first);
printf("pro first %s in %p\n",programer.first_name,&programer.first_name);
printf("cod first %s in %p\n",coder.first,coder.first);
寬容的編譯器順利的運行成功(運行時我用vs0215就失敗了)

如果你知道字符常量,我們簡單的測了一下就出了問題

int main() {
    char *str="abc";
    struct name1 programer;
    struct name2 coder;
    scanf("%s", programer.first_name);
    scanf("%s", coder.first);
    printf("str %s in %p\n",str,str);
    printf("pro first %s in %p\n",programer.first_name,&programer.first_name);
    printf("cod first %s in %p\n",coder.first,coder.first);
    return 0;
}

coder.first是空的

指針太騷還是要慎用又或是我們用coder.first=(char *)malloc(···);給他一塊專屬內(nèi)存,不需要的時候freefree(coder.first)。當(dāng)然指針管理數(shù)據(jù)還是十分收到青睞。
記得include"malloc.h",不要忽視警告往往就是bug出現(xiàn)的原因

14.7.8(復(fù)合型字面量 c99 )
  這相當(dāng)于即用即定義,和尋常的變量一樣,但是它沒有名字并不一個好事。如果它是全局變量則是靜態(tài)存儲期,在塊中則是自動存儲期。復(fù)合字面量和普通初始化列表采取一樣的規(guī)則,騷操作指定初始化器一樣可以使用。

14.8(把結(jié)構(gòu)內(nèi)容保存到文件中)

以二進制表示法儲存數(shù)據(jù)的缺點是,不同的系統(tǒng)可能使不同的二進制表示法,所以數(shù)據(jù)文件可能不具有移值性。甚至在同一個系統(tǒng),不同編譯器設(shè)置也可能導(dǎo)致不同的二進制布局。

14.10(聯(lián)合簡介)

#include "stdio.h"
union all {
    int a;
    _Bool b;
    char c;
};

int main() {
    union all test;
    scanf("%d", &test.a);
    printf("%d,%d,%c\n", test.a, test.b, test.c);
    scanf("%d", &test.b);
    printf("%d,%d,%c\n", test.a, test.b, test.c);
    scanf("%d", &test.c);
    printf("%d,%d,%c\n", test.a, test.b, test.c);
}

從結(jié)果可以看到union的值為最后一次賦值,他們同時使用同一塊內(nèi)存

&test==&test.a==&test.b==&test.c
使用初始化結(jié)構(gòu)器的情況,有區(qū)別于結(jié)構(gòu)體的初始化(沒騷起來…)

int main() {
    union all test = {.b=3,89,.a=77,0};
    printf("%d",test);
    return 0;
}
不像結(jié)構(gòu)體一樣會繼續(xù)初始化,編譯警告,0這個值無效
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容