C語(yǔ)言總結(jié)(數(shù)組,函數(shù),指針,結(jié)構(gòu)體,預(yù)處理)

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

  2. 數(shù)組

    • 數(shù)組申請(qǐng)
      int m ;
      int a[m];
      可以運(yùn)行,但在運(yùn)行的時(shí)候m必須確定

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

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

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

    • 二維數(shù)組

      • 二維數(shù)組初始化:
        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}; 錯(cuò)誤

  1. 指針
    • 為什么要用指針?
      計(jì)算機(jī)是按字節(jié)編址的,使用指針是為了方便程序員操作者內(nèi)存

    • 打印指針地址 使用 p%

    • 數(shù)組,指針,函數(shù)關(guān)注點(diǎn)

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

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

      解讀方法:首先從標(biāo)示符開始閱讀,然后往右讀,每遇到圓括號(hào)就調(diào)轉(zhuǎn)閱讀方向。重復(fù)這個(gè)過(guò)程直到整個(gè)聲明解析完畢。需要注意的是,已經(jīng)讀過(guò)的部分在后續(xù)作為一個(gè)整體來(lái)看。

           函數(shù)指針:
           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;
           }
      
           數(shù)組指針:
           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;
           }
      
           指針數(shù)組:
           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");
           }
      
           函數(shù)指針數(shù)組:
           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]

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

        1. a代表什么?
          a是整個(gè)數(shù)組的名字,當(dāng)a作為右值時(shí),它代表首元素的首地址,等價(jià)于&a[0]
        2. &a代表什么?
          &a取a的地址,即整個(gè)數(shù)組的地址
        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 指向 數(shù)組末尾
      3. a[-1]是否合法?如何理解?
      • 程序驗(yàn)證

          #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;
          }
        
      • 分析&&結(jié)論

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

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

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

        4. &a[0]代表數(shù)組第一個(gè)元素的地址。

        5. a[-1]是什么含義呢?其實(shí)編譯器內(nèi)部是按照指針加偏移的方式來(lái)處理數(shù)組下標(biāo)的。比如a[i],編譯器會(huì)解析為* (a+i),所以a[-1]其實(shí)就是*(a-1)的值。根據(jù)這個(gè)規(guī)則我們還會(huì)發(fā)現(xiàn)其實(shí)a[i]和i[a]都是合法的,且指向的是同一個(gè)值

        6. 當(dāng)不知道數(shù)組大小時(shí),獲取數(shù)組最后一個(gè)元素。

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

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

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

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

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

    • 指針和二維數(shù)組

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

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

      3. 行數(shù)組指針:
        定義:type (* var_name) [cols]
        動(dòng)態(tài)分配內(nèi)存:

         int m, n;
         int (*p)[m];// 動(dòng)態(tài)初始化一個(gè)n*m的數(shù)組
         p = (int(*)[m])malloc(n * m * sizeof(int));  
        
  • const
    1. 定義非指針類型的常量

      const int a;
      a = 5;  // 錯(cuò)誤
      const int b = 6;
    
    1. 函數(shù)參數(shù)

       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;
       }
      

      函數(shù)參數(shù)中使用const是為了防止函數(shù)內(nèi)部對(duì)函數(shù)進(jìn)行更改

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

               int a[3] = {1, 2, 3};
               int b[3] = {4, 5, 6};
               int * const pi = a; 
               pi[1] = 2;
               pi = b; // 錯(cuò)誤
      
       2. 常量指針:指向常量的指針,指針?biāo)赶虻膬?nèi)容不可修改,但指針本身可修改(比如,指向其他地址)  
      
               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; // 錯(cuò)誤
               pi = b;
      
       3. 區(qū)分方法
           去掉類型名,右側(cè)是指針變量名,就是指針常量。
      
    3. 指針和引用

      1. 指針:指針是一個(gè)變量,只不過(guò)這個(gè)變量存儲(chǔ)的是一個(gè)地址,指向內(nèi)存的一個(gè)存儲(chǔ)單元;而引用跟原來(lái)的變量實(shí)質(zhì)上是同一個(gè)東西,只不過(guò)是原變量的一個(gè)別名而已。如:

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

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

        而下面2句定義了一個(gè)整形變量a和這個(gè)整形a的引用b,事實(shí)上a和b是同一個(gè)東西,在內(nèi)存占有同一個(gè)存儲(chǔ)單元。

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

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

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

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

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

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

      8. 指針和引用作為函數(shù)參數(shù)進(jìn)行傳遞時(shí)的區(qū)別

        1. 指針作為參數(shù)進(jìn)行傳遞

           #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;
           }
          

          輸出結(jié)果: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;
           }
          

          運(yùn)行結(jié)果為:
          0x22ff44 1
          指針p為NULL

          在main函數(shù)中聲明了一個(gè)指針p,并賦值為NULL,當(dāng)調(diào)用test函數(shù)時(shí),事實(shí)上傳遞的也是地址,只不過(guò)傳遞的是指地址。也就是說(shuō)將指針作為參數(shù)進(jìn)行傳遞時(shí),事實(shí)上也是值傳遞,只不過(guò)傳遞的是地址。當(dāng)把指針作為參數(shù)進(jìn)行傳遞時(shí),也是將實(shí)參的一個(gè)拷貝傳遞給形參,即上面程序main函數(shù)中的p何test函數(shù)中使用的p不是同一個(gè)變量,存儲(chǔ)2個(gè)變量p的單元也不相同(只是2個(gè)p指向同一個(gè)存儲(chǔ)單元),那么在test函數(shù)中對(duì)p進(jìn)行修改,并不會(huì)影響到main函數(shù)中的p的值。

          3.將引用作為函數(shù)的參數(shù)進(jìn)行傳遞。

           #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;
           }
          

        輸出結(jié)果為:
        0x22ff44 1
        0x22ff44 1
        引用作為函數(shù)參數(shù)進(jìn)行傳遞時(shí),實(shí)質(zhì)上傳遞的是實(shí)參本身,即傳遞進(jìn)來(lái)的不是實(shí)參的一個(gè)拷貝,因此對(duì)形參的修改其實(shí)是對(duì)實(shí)參的修改,所以在用引用進(jìn)行參數(shù)傳遞時(shí),不僅節(jié)約時(shí)間,而且可以節(jié)約空間。

  1. 預(yù)處理

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

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

      • 使用時(shí)必須要使用足夠多的括號(hào)來(lái)保證宏展開后以正確的順序進(jìn)行結(jié)合和運(yùn)算。
      • 不要在宏中使用增量或減量運(yùn)算符。比如++和--。
    • typedef用途總結(jié)

      • 用途1:定義一種類型的別名,而不只是簡(jiǎn)單的宏替換。可以用作同時(shí)聲明指針型的多個(gè)對(duì)象。比如注意一下三種定義的區(qū)別與聯(lián)系:

          char* pa,pb; //等價(jià)于  char *pa ; char pb;
          typedef char* PCHAR;
          PCHAR pa,pb; //等價(jià)于char *pa,*pb;
        
      • 用途2:定義與平臺(tái)無(wú)關(guān)的類型。比如定義一個(gè)叫 REAL 的浮點(diǎn)類型,在目標(biāo)平臺(tái)一上,讓它表示最高精度的類型為:typedef long double REAL; 在不支持long double的平臺(tái)二上,改為:typedef double REAL; 在連double都不支持的平臺(tái)三上,改為:typedef float REAL; 也就是說(shuō),當(dāng)跨平臺(tái)時(shí),只要改下typedef本身就行,不用對(duì)其他源碼做任何修改。標(biāo)準(zhǔn)庫(kù)就廣泛使用了這個(gè)技巧,比如size_t。另外,因?yàn)閠ypedef是定義了一種類型的新別名,不是簡(jiǎn)單的字符串替換,所以它比宏來(lái)得穩(wěn)健(雖然用宏有時(shí)也可以完成以上的用途)。

      • 用途3:在舊的C代碼中,定義一個(gè)結(jié)構(gòu)體類型變量的時(shí)候,需要寫上struct,即struct 結(jié)構(gòu)名 變量名 。而在C++或新的C中,可以省去struct關(guān)鍵字,直接定義變量。所以在舊的C標(biāo)準(zhǔn)中,我們可以用typedef來(lái)實(shí)現(xiàn)新的C中的功能。

    • typedef注意點(diǎn)

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

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

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

      • typedef只是給類型定義別名(準(zhǔn)確說(shuō)是鏈接),但是#define還可以給常量“定義”別名;
      • typedef的定義是由編譯器處理的,而#define是在預(yù)處理器處理的
      • typedef是定義了一種新的類型,而#define只是文本替換;
      • typedef遵循作用域規(guī)則,而#define沒有;
      • typedef定義需要以封號(hào)結(jié)尾,而#define不需要;
  1. 結(jié)構(gòu)體
    字節(jié)對(duì)齊

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

使用#pragma pack更改了字節(jié)對(duì)齊值

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

字節(jié)對(duì)齊 按最長(zhǎng)的對(duì)齊 ,如果剩下的不夠放 再開辟按最長(zhǎng)空間

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

下圖為struct tagS1在內(nèi)存中的分布圖:

image

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容