程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> 關於C語言 >> C_數據結構與算法(一):C語言基礎,數據結構與算法

C_數據結構與算法(一):C語言基礎,數據結構與算法

編輯:關於C語言

C_數據結構與算法(一):C語言基礎,數據結構與算法


致初學者的我:一切都是由淺入深。

 

每種語言都有每種語言的特性,基本的特性是相同的,下面依照慣例寫hello world,相關編譯後面再介紹。

// C語言用“//”和“/**/”來寫注釋,注釋、空格等不影響語義(程序的意思)的符號會在程序編譯階段會被刪除。
#include<stdio.h>               //#include    預編譯處理,這裡把stdio的C函數庫載入進程序;
main()                     //main()     主程序入口;
{
    printf("hello world!");         //printf()    stdio函數庫中的函數,表示格式化輸出;
    return 0;                 //return 0    返回狀態0,表示程序正常結束。
}

 part1 基本數據類型及表達式

  • 變量可以視為一個無限大的黑箱,你可以往裡面裝你想裝的東西,三個要素:
    • 定義(或聲明),根據數據類型在內存中申請空間,如int age;
    • 賦值:修改變量的值,如 age = 30;
      • 使用:將變量的值從內存中讀出來進行使用,如 printf("%d",age),或運算 age = age + 1;
            int            age            =                23
          數據類型       變量名                       賦予變量的值

         

  • C基本數據類型(計算機中位指的是'0'或‘1’其中之一,而八位稱為一個字節,sizeof()函數可以查看數據類型具有多少個字節):
      • 整數型 
        short n = 10;   //內存中占16位
        int n = 10;     //占32位
        long n = 10;    //占64位
      • 浮點數
        float f = 1.1;  //占32位,有效位數6~7
        double d = 1.1; //占64位,有效位數15~16  
      • 字符型
        char c = 'a'; //占8位  
  • 進制轉換,計算機慣用的進制為:二、八、十、十六
      • 理解進制(以十進制138為例,可以用程序員計算器來幫助日常換算)
        二進制:    138 = 128(即1*2^7)  +  8(1*2^3)  +  2(1*2^1)  ----->10001010
        八進制:    138 = 128(2*8^2)     +  1(1*8^1)  +  2(2*8^0) ----->212
        十進制:    138 = 100(1*10^10) +30(3*10^1)+  8(8*10^0)   ----->138
        十六進制: 138 =  128(8*16^1)  +  10(10*16^0)             ----->8A
        //十六進制中 10-15 分別以A-F來表示
        
  • 自動類型轉換
      • (unsigned ) char、 short、int、long、float、double等類型之間可以互相賦值,系統會進行自動類型轉換:
        short s = -1; 
        unsigned short us = s;  // us = 65535
        
        short s2 = 65;
        char c = s2;   //c = 'A'

精度損失:如果把存儲大的類型賦值給小的類型,會面臨精度丟失的問題。

  • 變量相關信息
    • 命名規則:變量的命名規則可以讓程序更容易理解,個人采用Camel-Case命名法,如:personalInformation
      • 作用域:指包括此變量聲明的最小代碼塊,在這個作用域中,變量聲明後的所有代碼行都可以訪問此變量。變量在其作用域內被創建,離開其作用域時被撤銷。
        {
           int age = 10;
           ...
        
           ....以上這些行都可以訪問age
        }
        這裡不能訪問age
  • 常量
      • 一般我們在代碼中通過符號來代表常量,這個符號被稱為符號常量,通過#define或const定義,例如:
        const double PI = 3.14; //定義圓周率的符號常量
        #define PI 3.14; //用宏定義的方式,分不出數據類型
        printf("PI=%f\n",PI);
        注:盡量使用const來定義符號常量

        注意:盡量避免使用魔法數,指的是在程序中多處出現的常量值,應該使用上述方法將其賦值給一個恆定的變量。

  • 表達式
      • 表達式由操作符和操作數構成,通過操作符將一個或多個操作數連接到一起,如:
        id = 3 //賦值表達式, =是賦值操作符
        1+2   //計算表達式,+是算術運算符
        ++i   //計算表達式,++是算術計算符
        2>1   //邏輯表達式,>是關系運算符符
        (double)a  //類型轉換表達式
        1 && 0  //邏輯表達式,&&是邏輯運算符
        ...
  • 自增自減運算符
      • ++、–表示對變量執行加1(減1)操作,同時參與表達式運算。例如
        int a = i++ + 3; //先讓i參與表達式運算,然後將i+1
        int a = ++i + 3; //先將i+1,然後讓i參與運算
  • 位運算符
      • &:位與運算,將二進制每一位進行與運算,都是1則為1,如
        3  &  5   = 1
        0011 & 0101 = 0001
      • |:位或運算,將二進制每一位進行或運算,都是0則為0,如
        3  |  5   = 7
        0011 | 0101 = 0111
      • ^:位異或運算,將二進制每一位進行異或運算,不相同為1,相同為0,如
        3   ^  5     = 6
        0011 ^  0101  = 0110
      • ~:按位取反,如
        ~3  = -4
      • <<:左位移,將左端擠掉,右端補零,如
        3<<1  = 6
        011  --> 0110
      • >>:右位移,對有符號數,最左邊補原符號數,其它左邊位補0,右邊被擠掉,如
        3>>1 = 1
        011 -->01
  • 賦值表達式
      • 左值與右值:對一個變量(或表達式)來說,有兩個值與其關聯:
        char[] fullName = "Rudolph_Browne";//fullName為左值,Rudolph_Browne為右值
        1)數據:變量的數據值,存儲在地址中,這個值被稱之為右值(rvalue),r是read(可讀)的意思。 2)地址:存儲數據值的內存地址,地址被稱之為左值(lvalue),l是location(可尋址)的意思。

        “每一個表達式要麼是lvalue,要麼是rvalue”。左值指表達式結束之後依然存在的持久對象,而右值指表達式結束之後就不復存在的(臨時)對象。

  • 其他表達式
      • sizeof運算符:返回變量或類型的字節數,如
        sizeof(float);  //4
        sizeof(double); //8
        int n;
        sizeof(n); //4
      • 條件表達式: 條件?值1:值2,條件成立時取值1,否則取值2,如:
        int a = 1?5:10;   //a為5
        int b = (a%2==0)?1:0; //a為偶數時,b=1,否則為0
      • 逗號表達式:由多個表達式以逗號連接而成,如
        a=3, b=a+1, c=b+1; 
        x = (a=3, b=6*3); //逗號表達式的值為最後一個表達式的值
        (a=3,b) = 5;   //最後表達式為左值時,逗號表達式也可為左值

part2  程序結構

  • 基礎輸入輸出函數
      • printf函數概述
        printf("格式控制字符串"[,輸出列表]);
        注意:輸出列表與格式控制字符串之間有一個逗號分隔(如果輸出列表存在的話)。
      • 格式字符串:%[標志][寬度][.精度][h | l]<格式字符>,例如
        printf("%05hd\n",10);  //h表示短類型,l表示長類型
        輸出短整型數據10,寬度為5位,前面補0  //00010
      • 格式字符
        d(i):十進制整數,負數帶-號
        u:無符號十進制整數
        o:無符號八進制整數,無前導字母o
        x(X):無符號十六進制整數,無前導0x,X表示大寫
        f:小數形式的浮點數,默認6位小數
        e(E):指數形式的浮點數
        g(G):自動選擇e和f中寬度較小者,不打印無效的0
        c:單個字符
        s:字符串
    • putchar函數
      • putchar函數用來將單個字符輸出到標准輸出設備,通常是顯示器
        putchar(c); //c可以是字符或整型數字
    • scanf函數
      • scanf函數用於接收輸入,以回車作為一次函數調用結束,格式為
        scanf("格式控制字符串",地址列表);
        scanf("%d",&a);  //&a表示變量a的地址

        注意:通常在用c庫函數輸出提示語句時都會適當地緩存,要輸出緩存時使用下面語句。

        setbuf(stdout,NULL); //這句代碼是為了先輸出提示語句
    • getchar函數
      • getchar函數從鍵盤輸入一個字符,返回該字符的ASCII碼。例如
        char c = getchar();  //等待鍵盤輸入一個字符給變量c
  • 線性結構
      • 線性結構的程序沒有太多的特殊性,程序按定流水線的方式被執行。
  • 選擇結構
      • if-else if-else結構
        if(條件1) //如果條件為真
        {        //那麼
                 //當條件成立時執行這裡的代碼
        }else if(條件2)
        {      //或者
           //當條件2成立時執行這裡的代碼 }else
        { //否則
           //執行這裡的代碼
        }
      • switch結構(注意是等值判斷:必須用int或char的整數值);注意每個case後面有一個break,忽略後面其它case。
        switch(rank){
        case 1:
            //獎勵iphone;
            break;
        case 2:
            //獎勵ipad;
            break;
        case 3:
            //獎勵ipod
            break;
        default:
            //無視
        }
  • 循環結構
      • while語句不斷判斷循環條件,當循環條件為真(非0)的時候,就執行循環操作,一直到循環條件變為假(0)才跳出。
        while(循環條件){
          //循環操作
        }
      • do-while先執行一遍操作,然後持續判斷循環條件,如果條件為真則繼續執行,直到條件為假退出。
        do{
          //執行循環操作
        }while(循環條件);  //注意有分號

        注意:while和do-while是在程序不知道具體停止位置而使用的,如果知道具體位置,請使用for。

      • for循環一般用於循環次數確定的情況,它將條件的初始化、條件判斷、條件變化抽取出來,和循環體分離。
        for(參數初始化;條件判斷;參數更新){
          //循環操作
        }
      • 在while、do-while、for等循環語句中,break語句用來跳出循環從而執行循環後面的語句。
        while(...){
          ....
          break;
          //下面的語句執行不到了,因為break跳出了循環
          printf(...);
        }
        //break以後,執行這個語句
      • continue用於跳過本次循環體剩余語句,進入下一次循環的條件判斷。
        for(int i=0; i< 100; ++i){
           if(如果是奇數){
              continue;
           }
           sum += i;
        }
      • 用break可以跳出一層循環,如果想跳出多重循環,可以使用goto語句。例如
        #include <stdio.h>
        int main(){
          int i,j;
          for(i=0;i<4;++i){
            printf("%d\n",i);
            for(j=0;j<4;++j){
              if(i*j==4){
                goto l1;
              }
              printf("\t%d:\n",j);
            }
          }
          l1:
          printf("循環跳出,i=%d,j=%d\n",i,j);
          return 0;
        }
        //注:在C語言中,由於goto的靈活性,一般不提倡使用goto進行跳轉。

part3  高級數據類型與

  • 數組
      • 數組是一個變量,用來存儲相同類型的一組數據。數組四要素:變量名稱、元素、下標(從0開始)、類型,通過下標訪問數組的元素。
        類型 變量名稱
        int size = 3; int nArray[3] = {1,2,4}; int d1 = nArray[0];//0是下標,nArray[0]是下標為0的元素 //注意下標不能超出數組的長度-1
        for(i=0;i <= size;i++){
        //循環體
        }
    • 數組的存儲
      • 數組在內存裡順序存放,每個元素存放的大小按數組元素類型而定(以字節為最小單位)
        int a[4]; //a[0](假設地址為2000H)、a[1]、a[2]、a[3]
        2000H   2004H  2008H  200CH
        a[0]     a[1]   a[2]   a[3]
      • 下標越界:數組在聲明賦值的時候,編譯器會檢查是否越界,例如
        int a[4]={1,2,3,4,5}; //編譯提示越界

        但在運行時訪問下標的時候,編譯器不會檢查越界,此時程序員要自己注意。例如:

        int a[4]={1,2,3,4};
        printf("%d",a[4]); //a[4]指向2010H地址,內存的值未知
        a[4] = 100; //如果修改了a[4]的值,實際是修改了別人的數據,可能導致程序出錯或崩潰
      • 局部數組變量聲明後,並沒有自動初始化,此時如果2000H~200CH內存地址裡面已經有值,就會被拿來使用,導致程序運行混亂。所以局部數組變量需要進行初始化,例如:
        1)int a[4]={}; 
        2) #include <string.h>
           int a[4]; 
           memset(a, 0, 4*sizeof(int)); //以字節數為單位賦值
        3)for循環將每個元素設置成0

        注:static變量或全局變量自動會初始化成0。

      • 數組地址分配的規則,用&顯示變量的地址,注意結果地址的增加字節數。
        #include <stdio.h>
        int main(){
          int i,a[4];
          for(i=0;i<4;++i){
            printf("a[%d]的地址是:%p\n",i,&a[i]);
          }
          printf("數組變量a的地址是:%p\n",&a);
          return 0;
        }

        輸出結果為:

        =====運行開始======
        a[0]的地址是:0x7fff1d9e5ae0
        a[1]的地址是:0x7fff1d9e5ae4
        a[2]的地址是:0x7fff1d9e5ae8
        a[3]的地址是:0x7fff1d9e5aec
        數組變量a的地址是:0x7fff1d9e5ae0
        
        =====運行結束======
    • 數組值求解
      • 最大最小值:循環數組,將每個元素和max變量比較,如果元素比max大,則將元素賦值給max,循環完成以後max就是全數組的最大值。
        int i, max=arr[0];
        for(i=1;i<len;++i){
           if(max<arr[i]){
              max = arr[i];
           }
        }
        //循環完成以後,得到最大值max
      • 均值:
        int i,sum=0;
        for(i=0;i<len;++i)
           sum += arr[i];
        
        double avg = (double)sum/len;
      • 查找:用下標來判斷是否找到
        int value = 5; //要查找的數
        int i;
        for(i=0;i<len;++i){
           if(arr[i] == value){
              break;
           }
        }
        
        //如果i<len表示有找到,i就是該元素的下標,如果i = len表示沒有找到
      • 刪除:刪除指定下標的元素:將此下標後面的元素依次往前移動一位,最後一位設置為’\0’,例如刪除下標為3的元素。
        for(i=3; i<LEN-1; ++i){
           arr[i] = arr[i+1];  
          //依次往前移動一位,下標3的被4的覆蓋了
        }
        arr[len-1] = '\0'; //最後一位設置為0

        刪除給定值的元素:先通過查找獲得下標,然後執行1。

        int find=0;
          int removeIndex;
          for(removeIndex=0; removeIndex<LEN; ++removeIndex){
            if(arr[removeIndex] == 81){
              find = 1;
              break;
            }
          }
          if(find){
            for(i=removeIndex; i<LEN-1; ++i){
              arr[i] = arr[i+1];
            }
            arr[LEN-1] = '\0';
          }else{
            printf("沒有找到要刪除的元素");
          }
      • 在數組的指定位置插入一個新元素,前提是數組長度足夠。
        算法:將此位置及之後的元素都往後移動一位,然後將此位置的元素設置為新元素的值。例如,在下標2處插入一個10
        int a[6] = {1,2,3,4,5}; //注意數組長度要夠
        for(i=len-1; i>2; --i){ //注意i從大到小
          a[i] = a[i-1]; //依次往後移動,空出a[2]
        }
        a[2] = 10;
      • 拷貝:循環賦值或用memcpy函數。
        for(i=0; i<len; ++i){
           newArr[i] = arr[i];
        }
        //或:memcpy(newArr, a, len*sizeof(a[0]));

        合並:同樣可以用循環賦值或memcpy函數。

        int a[3]={1,2,3}, b[4]={4,5,6,7};
        int c[7];
        for(i=0; i<7; ++i){
           c[i] = (i<3)?a[i]:b[i-3];
        }
        
        //或:memcpy(c, a, 3*4);
        //memcpy(c+3, b, 4*4);
      • 二維數組a[i][j],可以把i視為行,把j視為列:
        int scores[2][4] = {{1,2},{2,3},{3,4,5,6}};
        for(int i=0; i<2; i++){
          for(int j=0; j<4; j++){
             printf("%d",scores[i][j]);
        }
          printf("\n");
        }

        結果為

        =====運行開始======
        1200
        2300
        //因為2行中賦值中有{1,2,(0,0)}和{2,3,(0,0)},即不足的以0來填補。
        =====運行結束======
    • 二維數組的存儲與矩陣的簡單表示
      • 在邏輯上是二維的,其兩個下標在兩個方向變化,但實際的存儲是連續的,C語言中,按照行排列的方式存儲:存放完一行後,再存放下一行的元素,如:
        scores[3][5],三個行如下:
        scores[0] --> 一班的分數:5個元素的一維數組 
        scores[1] --> 二班的分數:5個元素的一維數組
        scores[2] --> 三班的分數:5個元素的一維數組
        
        共15個元素,按照順序存儲,如:
        2000H 2001H 2002H  2003H 2004H 
        00     01    02    03    04  
        2005H 2006H  2007H 2008H 2009H
        10     11    12    13    14
        2010H 2011H  2012H 2013H 2014H
        20     21    22    23    24

        矩陣算法可以使用二維數組進行運算,如a[3][3]:

        1 2 3  --> a[0]
        4 5 6  --> a[1]
        7 8 9  --> a[2]
  • 字符串
      • 概念:在C語言中,將字符串作為字符數組處理,以’\0’作為字符串的結束標志,’\0’占內存空間,但不計入字符串長度。
        "Hello"  ---> {'H','e','l','l','o','\0'}
        //字符串長度為5,數組長度為6

        輸出字符串數組變量時,遇到’\0’結束,’0’是ASCII碼的0,可以通過循環輸出看到,例如

        char a[] = "Hello";
          int len = sizeof(a)/sizeof(a[0]);  //6
          printf("%s\n",a); //Hello
          for(int i=0; i<len; ++i)
            printf("%d ",a[i]);  //--> 最後一個是 0

        字符數組的定義和賦值:

        char a[] = "Hello";
        char a[] = {"Hello"};
        char a[] = {'H','e','l','l','o','\0'}; 
        
        char a[];
        a = "Hello";  //錯誤,定義後就是常量,不能再整句賦值
    • 字符串函數(C語言提供了專門處理字符串的函數庫:#include <string.h>
      • 拷貝函數strcpy:strcpy(目標字符數組, 源字符數組),將源字符數組的內容(包括結束符\0)拷貝到目標字符數組中,函數返回目標字符數組。
        char s[10]
        strcpy(s, "Hello");  //s-->Hello\0

        strcpy的覆蓋性:如果目標字符數組中已經存在內容,將被覆蓋。

        char s[10]="123456789"
        strcpy(s, "Hello");  //s-->Hello\0789
        printf("%s",s)       //Hello

        strncpy:strncpy(目標字符數組,源字符數組,拷貝長度),將源字符數組的前n個字符(不一定包括結束符\0)拷貝到目標字符數組中,函數返回目標字符數組。

        char s[10]="123456789"
        strncpy(s, "Hello",3);  //s-->Hel456789
        printf("%s",s);       //Hel456789

        strcpy的溢出性:如果目標字符數組的長度比源字符數組的長度短,strcpy函數會導致溢出,即目標字符數組地址後面的內存地址會被寫入多出的字符,產生不可預期的後果。

        char a = 'a';
        char s[3];
        strcpy(s,"Hello"); //s-->Hello\0 --> 可能會導致a被修改
      • 連接函數strcat(目標字符數組,源字符數組),將源字符數組的內容連接到目標字符數組後面,第一個連接過去的字符替代了目標字符數組的’\0’,函數返回目標字符數組。
        char s1[20] = "i am";
        char s2[] = "a boy";
        strcat(s1,s2); //s1-->i am a boy\0
        printf("%s",s1); //i am a boy
      • 比較函數:C語言中用strcmp函數對字符串進行比較:strcmp(字符數組1,字符數組2),返回值是:
        字符數組1 = 字符數組2, 返回值=0
        字符數組1 > 字符數組2, 返回值>0
        字符數組1 < 字符數組2, 返回值<0

        當需要判斷兩個字符串是否相等,例如判斷密碼是否為“137000”,代碼如下:

        char pwd[] = "137000";
        int b = strcmp(pwd,"137000");  //b == 0
      • 長度計數函數strlen:strlen(字符數組)返回字符串的長度,計算到\0為止(不包括\0),如
        char s[20] = "hello";
        printf("%d",strlen(s));  //5

        strlen與sizeof的區別

        1)sizeof是編譯時計算,關注分配了多少空間,不關注實際存儲的數據
        2)strlen是運行時計算,關注存儲的數據內容,不關注分配的空間。
        char s[10] = "hello";
        printf("%d",strlen(s));   //5
        printf("%d",sizeof(s));   //10
  • 函數
      • 概念:函數是程序的一個包含單元,用於完成某一特定的任務,比如一類計算、一種操作等等,使用函數對這些任務進行封裝,減少重復編碼,使程序更加模塊化,增強可讀性和可維護性。例如
        int main(){
           //打印三個*行
           int i;
           for(i=0; i<3; ++i)
             printStar();
        }

        函數的調用:函數是一個黑匣子,外部無需知道函數裡面具體怎麼執行的,只需要調用函數,獲取返回的結果即可。

      • 函數定義的語法:返回值類型、函數名、形參列表、函數體,例如
        返回值類型  函數名(參數列表)
        void printStar(int n, char c){
          //在這裡輸出*號組成的行
        }

        函數需要先定義、後調用,即函數的定義應當在主調函數之前,如

        void printStar(int n, char c){}
        int main(){
          printStar(5,'*');
          ...
        }

        有時候我們想先調用函數,之後再定義函數,則需要在調用之前先聲明,這和變量的聲明、使用、賦值類似,如

        int main(){
           void printStar(int n, char c); //聲明,注意沒有函數體
           printStar(5,'*');
        }
        
        void printStar(int n, char c){//定義這裡有函數體}

        調用系統庫函數,要使用#include<庫文件>,在c語言中,庫文件使用*.h文件,例如

        #include <stdio.h>
        #include <stdlib.h>  //使用庫文件
        int main(){
            printf("%d的絕對值是%d", -3,abs(-3));  //輸出絕對值
            return 0;
        }
    • 函數的執行過程
      • 程序的執行入口是main函數,從main函數體的起始處開始執行,main函數體的所有代碼都執行完成後會自動退出程序。
      • 遇到對其它函數的調用,則暫停當前執行,保存當前的狀態現場和返回地址,轉到被調用函數的入口地址進行執行,當遇到return語句或函數執行結束時,恢復之前保存的現場,從返回地址處開始繼續執行。例如
        #include <stdio.h>
        int add(int a, int b){
          printf("\t開始執行add函數,參數%d,%d\n",a,b);
          int sum = a + b;
          printf("\tadd執行完成,准備返回%d\n",sum);
          return sum;
        }
        
        int main(){
          printf("開始執行main函數\n");
          printf("調用add函數\n");
          int a = add(3,5);
          printf("調用add函數返回:%d\n",a);
          
          a = add(1+2,2*4);
          printf("調用add函數返回:%d\n",a);
        
          printf("main函數執行結束\n");
          return 0;
        }
        =====運行開始======
        開始執行main函數
        調用add函數
            開始執行add函數,參數3,5
            add執行完成,准備返回8
        調用add函數返回:8
            開始執行add函數,參數3,8
            add執行完成,准備返回11
        調用add函數返回:11
        main函數執行結束
        
        =====運行結束======
    • 參數列表
      • 形參:在函數定義時,每個參數定義是“類型 變量名”的聲明格式,由逗號分隔。
        int add(int a, int b){
           //在這裡可以使用變量a和b
        }

        參數列表的定義相當於定義了局部變量,其作用域是函數體,在函數沒有被調用時,形參沒有內存分配,當函數調用時被分配內存,函數結束後,內存被釋放。

      • 實參:在函數調用時,傳入與形參類型一致、個數一致的參數列表,如果不相同會執行自動格式轉換。調用時不需要在前面加變量類型。
        int main(){
           int sum = add(3.2, 5); //3.2傳給a(自動轉換),5傳給b
        }
    • 值傳遞
      • 值傳遞:實參傳入數據時,傳遞給形參(函數內局部變量)的是實參的值而不是變量本身(地址),對形參的任何修改不會影響到實參。
        int change(int a){
           return ++a;
        }
        
        int main(){
           int a = 5;
           change(a);
           printf("%d",a); //a還是5,不會變成6
        }
    • 內存和存儲類型
      • C語言中,程序占用的內存分為代碼區、數據區(靜態存儲區)、動態存儲區(又分為棧和堆)。
        1)靜態存儲區:主要存放全局變量、靜態變量(static)、字面常量,其生存期是從程序開始到程序結束;
        2)動態存儲區:主要存放局部變量、參數等,在程序運行期間動態分配,其中棧和堆的使用又有所不同。
      • 自動變量:說明符為auto,凡是函數內未加存儲類型說明的都是auto類型,例如
        {
           int a;  --->  auto int a;
        }

        自動變量屬於動態存儲方式,當函數被調用時才分配存儲,函數結束即自動釋放。作用域是定義它的單元,如函數或代碼塊。

      • 靜態變量:說明符為static,在函數中的static變量屬於靜態存儲方式,程序啟動時被分配,程序結束後釋放。作用域取決於其定義的位置,函數內定義的作用域就是函數體,函數外定義的作用域就是本文件。例如
        {
           static int a;  //函數結束不會釋放,下次還可以繼續用
        }

        static的理解:具備靜態存儲特性,又具備函數的作用域。

        注:全局變量或函數前面加static,表示作用域在本文件內部,不能被其它文件引用。
      • 外部變量:說明符為extern,全局變量的默認方式,當文件中要引用另一文件中的全局變量,或者在全局變量定義之前要引用它時,可以用extern做說明。例如
        file1.cpp           file2.cpp
        int global;  <---  extern int global; //用到file1.cpp
      • 寄存器變量:說明符為register,建議將變量存儲在CPU的寄存器中,效率非常高。例如
        int maain(){
           register int i, sum=0;
        }
    • 函數的嵌套調用和遞歸調用
      • 在函數定義中允許再調用其它函數,即函數可以嵌套調用。
        int sum(int a, int b){return a+b;}
        int avg(int a, in b){return sum(a,b)/2;}
      • 函數內調用自身稱之為遞歸調用,這種函數稱為遞歸函數。
        int sum(int a){return a+sum(a+1);}
      • 棧溢出:當調用函數時,當前狀態現場、返回地址要被保存到“棧”(STACK)裡,等到函數調用結束時恢復現場,從返回地址繼續執行,如果遞歸函數沒有控制,一直遞歸下去,就會導致棧溢出(StackOverflowException),所以遞歸函數切記要考慮使用計數或狀態來終止遞歸循環。
        遞歸的2個條件:
        1)有反復執行的過程(自己調自己)
        2)有跳出反復過程的條件(遞歸出口)
    • 把數組作為函數參數
      • 數組名作為函數的參數,傳遞的是數組的地址,實參和形參指向同一個地址,所以形參元素被修改會影響到實參。如
        //由於傳遞的是地址,不需要指定個數,需要傳入個數的參數
        void changeArr(int a[], int len){
           a[0] = 100;
        }
        
        int main(){
           int a[5] = {1,2,3,4,5};
           changeArr(a,5);  //傳入的是數組名
           printf("%d",a[0]);
        }
      • 由於傳入是地址,數組參數不需要指定個數,需要另外傳入數組的長度參數(防止越界),如計算數組最大的數:
        int max(int a[], int len){
          int i, max = a[0];
          for(i=0; i<len; ++i){
            if(max<a[i])
              max = a[i];
          return max;
        }
  • 結構體
    • 結構體概述
      • 定義:將不同類型的數據組合在一起,構造出一種新的數據類型,這種形式稱為“結構體”,聲明結構體的語法關鍵字是struct:
        struct student //student是新定義的結構名
        {
          int no;    //學號
          char name[10];  //姓名
          int age;     //年齡
          char address[30];  //地址
        };  //注意分號

        結構體體現了數據的封裝性,用結構體變量進行信息傳遞,一方面可以減少參數的數量,另一方面當業務邏輯有變化時,可以很方便的修改結構體的定義,不需要修改傳遞接口(函數定義)。例如:

        struct car {
         ...
        }
        
        //當汽車增加新成員字段時,不需要修改buyCar的函數定義
        void buyCar(struct car c){
           ....
        }

        結構體變量作為函數參數時,采取的是值傳遞的方式,即形參產生了和實參同樣數據的一個內存拷貝。一方面,結構體數據多的時候內存開銷大,另一方面,函數裡面對數據的修改不會影響到實參。如

        void changeCar(struct car c){
           c.price = 200;
        }
        
        struct car c = {"寶馬","X5","3.0","紅色",130};
        changeCar(c);  //在函數裡修改價格
        printf("%d",c.price); //價格還是130

        如果想讓結構體變量在函數中被修改,跟其他類型一樣,可以使用指針,如

        void change2(struct car *c){
          c->price = 200;  //指針引用成員用 -> 運算符
        }
        change2(&c);

        結構體作為返回值:當函數需要返回多個值時,可以用結構體變量。例如

        struct point {
           int x;
           int y;
        };
        struct point createPoint(){
          struct point p = {0,1};
          return p;
        }
    • 聯合體
      • 將不同的數據類型組合起來,並且共享同一內存,這樣的數據類型稱之為聯合體(union),聯合的定義聲明方式和結構體(struct)類似,例如。
        union test {
           int i;
           char c;
           float f;
        } a, b, c;
        

          

        注意:一個聯合體變量一次只能給一個成員賦值,後賦值會覆蓋前面的賦值,只有最後賦值的成員才有意義。

        #include <stdio.h>
        union test{
          int i;
          char c;
          float f;
        };
        int main(){
          union test t ;
          printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
          t.i = 10;
          printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
          t.c = 'a';
          printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
          t.f = 1.0f;
          printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
          
          return 0;
        }
        

         

          

        =====運行開始======
        i=1314011120,c=-16,f=881720320.000000
        i=10,c=10,f=0.000000
        i=97,c=97,f=0.000000
        i=1065353216,c=0,f=1.000000
        
        =====運行結束======
        

          

      • 枚舉:如果一個變量的值只有幾種可能,可以將這些值列舉出來,定義為枚舉類型(enum)。例如:
        enum weekday {sun,mon,tue,wed,thu,fri,sat};
        enum weekday day1 = sun; //變量的值只能是枚舉元素之一
        

          

        #include <stdio.h>
        enum weekday {sun,mon,tue, wed,thu,fri,sat};
        int main(){
          enum weekday day1 = sun, day2;
          printf("day1 = %d\n",day1);
          day2 = (enum weekday)(day1 + 3);
          printf("day2 = %d\n",day2);
          if(day2 == wed)
            printf("星期天過3天是星期三\n");
          
          return 0;
        }
        

          

      • 使用typedef可以為已有類型定義新的類型名,往往可以使用我們自己定義的結構,這樣很好地具現化出真正的對象結構。如
        typedef struct student{
          int no;
          string name;
        } STUDENT;  //定義新的結構體類型STUDENT
        
        STUDENT zhangsan;
  • 指針
    • 概述
      • 變量的內存結構:類型、變量名、地址、數據:
        int n = 10;   
        2001H~2004H   ----> 10
        &n (int有4個字節)

        指針:變量的指針就是變量的地址(內存中的首地址)

      • 指針變量:指針變量是一種特殊的變量,它的數據是內存的一個地址(unsigned long int),通過這個內存可以找到此內存本身存儲的數據。如:
        int a = 10
        int *p; //定義一個指針變量,指針的類型是int
        p = &a; //p是指針變量,p的數據是a的地址
        
         p     p的數據是地址  地址指向的值
        2005H ---> 2001H --> 10
        *p -->  a的地址裡面的數據  --> 10
        

        *是“間接運算符”,用來定義一個指針變量,以及取指針變量指向的數據。賦給指針變量的值必須是地址常量或變量,不能為普通整數(可以為 0表示空指針),此地址中存放的數據類型必須與指針類型相符。如

        1)int *p;   //定義指針變量 p
        int *p = &a; ---> int *p;  p=&a
        2)printf("%d\n",*p);  //取指針變量p指向的數據
        *(&a)  --> a  //取&a這個地址(指針)指向的數據

         

      • 指針修改值
        *p = 5; //把指針的值修改為5,n == 5
      • void類型的指針可以賦予任何類型對象的地址,如
        int n; char c;
        void *p;
        p = &n;
        p = &c;

        注意:void類型指針不能直接輸出或運算,需要轉換成指定類型。

        void *pv;
        int *pint, n;
        pv = &n;
        pint = (int*)pv;  //強制類型轉換

         

    • 指針的算術運算
      • 指針相減:指向同一個數組的兩個指針相減,返回他們之間的數組元素個數。如
        int n[5];
        int *p1=n,*p2=&n[3];
        p1-p2 --> 3

        注:不是同一數組的指針相減沒有意義。

      • 指針與整數的加減:指針變量與整數的加減是一個算術表達式,返回新的指針變量,指向的地址為原指針地址加減指針指向類型的整數倍,例如
        int n, *p=&n;  //int是4字節
        p+1  --> (n的地址 + 4 )的新指針
      • 自增自減:++和--可用於指針運算,、++、--的優先級相同,都是右結合性。例如: p++ –> 先計算*p,再將p指向p+1個類型長度的地址。
        int a=10,b=20,c=30;
        int *p=&b;
        *p --> 20
        *p++ --> 20,但此時p指向 p+1的 地址(4個字節)
    • 指針的關系運算
      • 當兩個指針p和q指向同一數組中的元素時,可以進行比較:
        p<q p指向的元素在q指向的元素之前,返回1,否則0
        p>q p指向的元素在q指向的元素之後,返回1,否則0
        p==q 兩者指向同一元素,返回1,否則0
        p!=q 不指向同一元素,返回1,否則0
      • 與NULL比較:
        p==NULL 空指針返回1,否則0
        p!=NULL 不是空指針返回1,否則0
    • 指針與常量
      • 指向常量的指針:指針變量指向一個常量,此時指針所指向的值不能修改,但指針本身(存儲的地址)可以指向另外的對象,例如
        int a = 3, b = 10;
        const int *p = &a;
        *p = 5;  //報錯,不能修改
        p = &b; //可以修改
      • 指針常量:聲明指針常量,則指針本身不能改變。例如
        int a=3, b=10;
        int *const p = &a;
        p = &b;  //報錯,不能修改
        *p = 5;  //可以修改
        

          

        理解:看const後面是什麼,如果是*p則表示指針的值不能修改,如果是p則表示指針不能更換指向。

    • 指針與結構體
      • 結構體類型的指針變量,定義方式為
        struct 結構體類型 *變量名;
        
        struct student stu, *p;
        p = &stu;
      • 成員的引用:訪問成員有以下三種方式:
        stu.name;
        (*p).name;
        p->name; //指針專用,指向運算符,優先級比單目運算符高
        p->age++; //先獲得age成員,再++
        

          

    • 指針作為函數參數
      • 函數參數是指針變量時,傳遞的是指針存儲的地址,而變量做參數時傳遞的是具體的值(會產生形參的拷貝)。指針做參數時,地址(指針自己的值)不能被修改,但地址指向的值可以被修改。例如
        void change(int *p){
           *p = 20;
        }
        int main(){
           int a=5;
           change(&a);  //傳入指針,在函數裡a的值被修改
        }
        

          

      • 函數指針:回調函數,又稱callback,是一種事件驅動的編程模式,將函數指針變量作為參數傳遞給另外一個函數,函數裡面當某些事件滿足時會調用此函數指針變量指向的函數,例如。
        void print(int n){  //回調函數,打印信息
          printf("回調函數調用:%d\n",n);
        }
        
        void loop(int max, int (*p)(int n)){ 
        //遍歷1..n,如果7的倍數就打印信息
          int i;
          for(i=0;i<max;++i){
             if(i%7==0) p(i);
          }
        }
        

          

         

         

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved