C語言總結(數組,函數,指針,結構體,預處理)

  1. 變量的聲明和定義
    變量聲明(declaration) 可以declaration很多次,不占內存空間,例如 extern int a;
    變量定義(define) 定義只能定義一次 int a;(全局變量0;局部變量為任意值)

  2. 數組

    • 數組申請
      int m ;
      int a[m];
      可以運行,但在運行的時候m必須確定

    • 特殊位置賦值
      int f[5] = [[4]=5]; //f[4] = 5,其他位置為0
      int i[] = {7,[6]=10,100,1000}; 一共有9個元素,第7,8,9位的值是10,100,1000.剩下的值為7

    • 數組傳參
      int fun(int[] a)
      int fun(int b[][5])//第一唯是大小,剩下的是類型。 傳遞一個類型為int[5]的數組b

    • 字符串賦值
      char p1[5]={"Hello"}; 編譯器最后會自動添加'\0'
      char p2[5]={"H","e","l","l","o"}; 沒有‘\0’

    • 二維數組

      • 二維數組初始化:
        int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
        int b[3][2] = {{1, 2}}; = {{1, 2}, {0, 0}, {0, 0}};
        int c[3][2] = {{1}, {2, 3}}; = {{1, 0}, {2, 3}, {0, 0}};

        int d[3][2] = {1, 2, 3, 4, 5, 6}; = {{1, 2}, {3, 4}, {5, 6}};
        int e[3][2] = {1, 2, 3}; = {{1, 2}, {3, 0}, {0, 0}};
        int f[][3] = {1, 2, 3}; = {{1, 2,3}};
        int g[3][] = {1, 2, 3}; 錯誤

  1. 指針
    • 為什么要用指針?
      計算機是按字節編址的,使用指針是為了方便程序員操作者內存

    • 打印指針地址 使用 p%

    • 數組,指針,函數關注點

      • 數組關注:大小+類型
      • 指針:類型
        • 硬件里面操作 基地址+偏移
      • 函數:參數+返回值
    • 指針數組,數組指針,函數指針

           int *p[3]; //p 往右讀是[],則類型是數組,吧p[3]看成一個整體,數組里面存的是int*
           int (*p)[3];//p 遇到‘)’ 向左讀 遇到‘*’,則類型是指針,數組指針,p指向一個一個int[3]的數組。
           int (*p)(int a);//p 遇到‘)’ 向左讀 遇到‘*’,則類型是指針,往右讀遇到'()',p是一個指向參數為int a ,返回值類型為int 的函數。
           int (*p[3])(int a);
      

      解讀方法:首先從標示符開始閱讀,然后往右讀,每遇到圓括號就調轉閱讀方向。重復這個過程直到整個聲明解析完畢。需要注意的是,已經讀過的部分在后續作為一個整體來看。

           函數指針:
           int echo(int a)
           {
               return a;
           }
           int main()
           { 
               int (*p)(int a); 
               p = echo;
               printf("echo(5)=%d\n", echo(5));
               printf("p(5)=%d\n", p(5));
               
               return 0;
           }
      
           數組指針:
           int main()
           { 
               int (*p)[3]; 
               int d[2][3] = {1, 10, 1000, 2, 20, 2000};
               p = d;
           
               for(int i=0; i<2; i++)
               {
                   for(int j=0; j<3; j++)
                       printf("%d\t", *(*(p+i)+j));
                   printf("\n");
               }
               return 0;
           }
      
           指針數組:
           int main()
           { 
               int a, b,c;
               a=b=c=10;
               int *p1[3] = {&a, &b, &c};
               for(int i=0; i < 3; i++)
                   printf("%d\t", *(p1[i]));
               printf("\n");
           }
      
           函數指針數組:
           int function1(int a)
           {
               printf("int function1\n");
               return 0;
           }
           int function2(int a)
           {
               printf("int function2\n");
               return 0;
           }
           int function3(int a)
           {
               printf("int function3\n");
               return 0;
           }
      
           int main()
           { 
               int (*p[3])(int a); 
               p[0] = function1;
               p[1] = function2;
               p[2] = function3;
               p[0](10);
               p[1](10);
               p[2](10);
               return 0;
           }
      
    • a,&a,&a[0]

      • 假設:int a[3] = {1, 2, 3};

        1. a代表什么?
          a是整個數組的名字,當a作為右值時,它代表首元素的首地址,等價于&a[0]
        2. &a代表什么?
          &a取a的地址,即整個數組的地址
        3. &a[0]代表什么?
          代表首元素的首地址
        4. a、&a、&a[0]的值相等嗎?
          相等
        5. sizeof(a)、sizeof(&a)、sizeof(&a[0])相等嗎?分別是多少?
          sizeof(a)=12
          sizeof(&a)=4
          sizeof(&a[0])=4
      1. sizeof(a[4])合法嗎?如果合法,值是多少?為什么?
        sizeof(a[4])=4
      2. a+1、&a+1、&a[0]+1分別是多少?指向的值又是多少?
        a+1 = &a[0]+1 指向 2
        &a+1 指向 數組末尾
      3. a[-1]是否合法?如何理解?
      • 程序驗證

          #include <stdio.h>
          int main(void)
          {
              int a[3] = {1, 2, 3};
              printf("sizeof(int)=%lu\tsizeof(unsigned long)=%lu\n", sizeof(int), sizeof(unsigned long));
              printf("a=%p\t&a=%p\t&a[0]=%p\n", a, &a, &a[0]);
              printf("sizeof(a)=%lu\tsizeof(&a)=%lu\tsizeof(&a[0])=%lu\n", sizeof(a), sizeof(&a), sizeof(&a[0]));
              printf("sizeof(a[4])=%lu\n", sizeof(a[4]));
              printf("a+1=%p\t&a+1=%p\t&a[0]+1=%p\n", a+1, &a+1, &a[0]+1);
              printf("*(a+1)=%d\t*(&a+1)=%d\t*(&a[0]+1)=%d\n", *(a+1), *(&a+1), *(&a[0]+1));
              printf("a[-1]=%d\n", a[-1]);
              return 0;
          }
        
      • 分析&&結論

        1. a、&a、&a[0]雖然含義不同,但它們的值都是一樣的。

        2. a是整個數組的名字,代表整個數組,所以sizeof(a)的值為整個數組的大小。但有一個例外,當a作為右值時,它代表首元素的首地址,即相當于&a[0]。所以a+1和&a[0]+1等價。

        3. &a取a的地址,即整個數組的地址。所以&a+1表示“下一個數組”的地址,而不是下一個數組元素的地址。

        4. &a[0]代表數組第一個元素的地址。

        5. a[-1]是什么含義呢?其實編譯器內部是按照指針加偏移的方式來處理數組下標的。比如a[i],編譯器會解析為* (a+i),所以a[-1]其實就是*(a-1)的值。根據這個規則我們還會發現其實a[i]和i[a]都是合法的,且指向的是同一個值

        6. 當不知道數組大小時,獲取數組最后一個元素。

           int a[5] = {1,2,3,4,5};
           int *p = (int*)(&a + 1);
           *(p-1) 的值是 5
          
    • 指針和數組

      • 指針和數組雖然很相近,我們經常交換使用,但只有在下面兩種情況下,二者才是等價的:

        • 在表達式中,對數組下標的引用總是可以換成指向數組起始地址的指針加偏移量(而且編譯器一般都是這么做的),也就是說用a[i]這樣的形式訪問數組時總是被編譯器改寫成像* (a+1)這樣的指針訪問。比如:

            int a[] = {1, 3, 5, 7, 9};  
            int *p = a;a[2]等效于*(p+2)  
          
        • 在函數參數的聲明中,數組名被編譯器當做指向該數組第一個元素的指針。比如下面對于func函數的定義是完全等效的:

            func(int *a) { … }
            func(int a[]) { … }
            func(int a[100]) { … }
          
        • 除了上面這兩種情況外,數組就是數組,指針就是指針,不要交換使用。

    • 指針和二維數組

      • 假設定義了二維數組a[m][n],且int *p = a[0],則
      1. a+1代表什么?
        a+1 指向 a[1]

      2. 不使用下標的方式,如何通過a來訪問元素a[i][j]?
        a[i][j] = *((p+i)+j)

      3. 行數組指針:
        定義:type (* var_name) [cols]
        動態分配內存:

         int m, n;
         int (*p)[m];// 動態初始化一個n*m的數組
         p = (int(*)[m])malloc(n * m * sizeof(int));  
        
  • const
    1. 定義非指針類型的常量

      const int a;
      a = 5;  // 錯誤
      const int b = 6;
    
    1. 函數參數

       void print_array1(const int a[], int n)
       {
           for(int i = 0; i < n; i++)
               printf("%d\n", a[i]);
       }
       void print_array2(int a[], int n)
       {
           for(int i = 0; i < n; i++)
               printf("%d\n", a[i]++);
       }
       int main()
       { 
           const int a[3] = {1, 2, 3};
           int b[3] = {4, 5, 6};
           print_array1(a, 3);
           print_array1(b, 3); // print_array2(a, 3);
           print_array2(b, 3); 
           return 0;
       }
      

      函數參數中使用const是為了防止函數內部對函數進行更改

    2. 指針與const
      1. 指針常量:指針是常量,即指針只能指向某個固定地方,不能指向其他地方。

               int a[3] = {1, 2, 3};
               int b[3] = {4, 5, 6};
               int * const pi = a; 
               pi[1] = 2;
               pi = b; // 錯誤
      
       2. 常量指針:指向常量的指針,指針所指向的內容不可修改,但指針本身可修改(比如,指向其他地址)  
      
               int a[3] = {1, 2, 3};
               int b[3] = {4, 5, 6};
               const int *pi = a; //或 int const *pi = a;
               a[1] = 10;
               pi[1] = 10; // 錯誤
               pi = b;
      
       3. 區分方法
           去掉類型名,右側是指針變量名,就是指針常量。
      
    3. 指針和引用

      1. 指針:指針是一個變量,只不過這個變量存儲的是一個地址,指向內存的一個存儲單元;而引用跟原來的變量實質上是同一個東西,只不過是原變量的一個別名而已。如:

         int a=1;int *p=&a;
         
         int a=1;int &b=a;
        

        上面定義了一個整形變量和一個指針變量p,該指針變量指向a的存儲單元,即p的值是a存儲單元的地址。

        而下面2句定義了一個整形變量a和這個整形a的引用b,事實上a和b是同一個東西,在內存占有同一個存儲單元。

      2. 可以有const指針,但是沒有const引用;

      3. 指針可以有多級,但是引用只能是一級(int **p;合法 而 int &&a是不合法的)

      4. 指針的值可以為空,但是引用的值不能為NULL,并且引用在定義的時候必須初始化;

      5. 指針的值在初始化后可以改變,即指向其它的存儲單元,而引用在進行初始化后就不會再改變了。

      6. "sizeof引用"得到的是所指向的變量(對象)的大小,而"sizeof指針"得到的是指針本身的大小;

      7. 指針和引用的自增(++)運算意義不一樣;

      8. 指針和引用作為函數參數進行傳遞時的區別

        1. 指針作為參數進行傳遞

           #include<iostream>
           using namespace std;
           
           void swap(int *a,int *b)
           {
             int temp=*a;
             *a=*b;
             *b=temp;
           }
           
           int main(void)
           {
             int a=1,b=2;
             swap(&a,&b);
             cout<<a<<" "<<b<<endl;
             system("pause");
             return 0;
           }
          

          輸出結果:2 1

           #include<iostream>
           using namespace std;
           
           void test(int *p)
           {
             int a=1;
             p=&a;
             cout<<p<<" "<<*p<<endl;
           }
           
           int main(void)
           {
               int *p=NULL;
               test(p);
               if(p==NULL)
               cout<<"指針p為NULL"<<endl;
               system("pause");
               return 0;
           }
          

          運行結果為:
          0x22ff44 1
          指針p為NULL

          在main函數中聲明了一個指針p,并賦值為NULL,當調用test函數時,事實上傳遞的也是地址,只不過傳遞的是指地址。也就是說將指針作為參數進行傳遞時,事實上也是值傳遞,只不過傳遞的是地址。當把指針作為參數進行傳遞時,也是將實參的一個拷貝傳遞給形參,即上面程序main函數中的p何test函數中使用的p不是同一個變量,存儲2個變量p的單元也不相同(只是2個p指向同一個存儲單元),那么在test函數中對p進行修改,并不會影響到main函數中的p的值。

          3.將引用作為函數的參數進行傳遞。

           #include<iostream>
           using namespace std;
           
           void test(int &a)
           {
             cout<<&a<<" "<<a<<endl;
           }
           
           int main(void)
           {
               int a=1;
               cout<<&a<<" "<<a<<endl;
               test(a);
               system("pause");
               return 0;
           }
          

        輸出結果為:
        0x22ff44 1
        0x22ff44 1
        引用作為函數參數進行傳遞時,實質上傳遞的是實參本身,即傳遞進來的不是實參的一個拷貝,因此對形參的修改其實是對實參的修改,所以在用引用進行參數傳遞時,不僅節約時間,而且可以節約空間。

  1. 預處理

    • 預處理器指令從#開始,到其后第一個換行符“\n”為止。也就是說,指令的長度僅限于一行代碼。但是,我們經常會看到我們可以使用反斜線“\”將指令擴展到多個物理行,由多個物理行組成一個邏輯行(但是這個并不是C預處理器的特性,而是C編譯器的特性:在預處理開始前,編譯器會查找反斜線和換行符的組合,并將其刪掉)。

    • #define——“函數”
      #define SQUARE(X) ((X)
      (X))

      • 使用時必須要使用足夠多的括號來保證宏展開后以正確的順序進行結合和運算。
      • 不要在宏中使用增量或減量運算符。比如++和--。
    • typedef用途總結

      • 用途1:定義一種類型的別名,而不只是簡單的宏替換。可以用作同時聲明指針型的多個對象。比如注意一下三種定義的區別與聯系:

          char* pa,pb; //等價于  char *pa ; char pb;
          typedef char* PCHAR;
          PCHAR pa,pb; //等價于char *pa,*pb;
        
      • 用途2:定義與平臺無關的類型。比如定義一個叫 REAL 的浮點類型,在目標平臺一上,讓它表示最高精度的類型為:typedef long double REAL; 在不支持long double的平臺二上,改為:typedef double REAL; 在連double都不支持的平臺三上,改為:typedef float REAL; 也就是說,當跨平臺時,只要改下typedef本身就行,不用對其他源碼做任何修改。標準庫就廣泛使用了這個技巧,比如size_t。另外,因為typedef是定義了一種類型的新別名,不是簡單的字符串替換,所以它比宏來得穩健(雖然用宏有時也可以完成以上的用途)。

      • 用途3:在舊的C代碼中,定義一個結構體類型變量的時候,需要寫上struct,即struct 結構名 變量名 。而在C++或新的C中,可以省去struct關鍵字,直接定義變量。所以在舊的C標準中,我們可以用typedef來實現新的C中的功能。

    • typedef注意點

      • 陷阱1:記住,typedef是定義了一種類型的新別名,不同于宏,它不是簡單的字符串替換。

        • 比如:先定義:typedef char* PSTR; 然后:int mystrcmp(const PSTR, const PSTR);const PSTR實際上相當于const char*
          不是的,它實際上相當于 char* const。原因在于const給予了整個指針本身以常量性,也就是形成了常量指針char* const。簡單來說,記住當const和typedef一起出現時,typedef不會是簡單的字符串替換就行。
      • 陷阱2:typedef在語法上是一個存儲類的關鍵字(如auto、extern、mutable、static、register等一樣),雖然它并不真正影響對象的存儲特性。比如:

          typedet static int INT2;//不可行
        
    • define vs typedef

      • typedef只是給類型定義別名(準確說是鏈接),但是#define還可以給常量“定義”別名;
      • typedef的定義是由編譯器處理的,而#define是在預處理器處理的
      • typedef是定義了一種新的類型,而#define只是文本替換;
      • typedef遵循作用域規則,而#define沒有;
      • typedef定義需要以封號結尾,而#define不需要;
  1. 結構體
    字節對齊

     struct A
     {
        //假設內存地址從0開始...   
        int a;    //0-3
        char b; //4  
        short c;//6-7
     }
     //由于0-7的相加的結果為8...為自對齊4的倍數...
     //所以結果:sizeof(A) = 8
      
     
     struct B
     {
          //假設內存地址從0開始...
         char a;//0
         int b;  //4-7
         short c;//8-10
     }
    

使用#pragma pack更改了字節對齊值

    #pragma pack(2)
    struct C
    {
        //假設從0開始
        char a;//0
        int b;//2-5
        short c;//6-7
    };
    sizeof(C)的答案為8

字節對齊 按最長的對齊 ,如果剩下的不夠放 再開辟按最長空間

    struct tagS1
    {
        
        char a;
        int n;
        long l;
        double t1;
        char sz[22];
    };
    //結果為48

下圖為struct tagS1在內存中的分布圖:

image

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,923評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,740評論 3 420
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,856評論 0 380
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,175評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,931評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,321評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,383評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,533評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,082評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,891評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,618評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,319評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,732評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,987評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,794評論 3 394
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,076評論 2 375

推薦閱讀更多精彩內容