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

[C語言]進階|程序結構,進階

編輯:關於C語言

[C語言]進階|程序結構,進階


------------------------------------------------------------------------------------

全局變量:

//  main.c
//  Created by weichen on 15/7/14.
//  Copyright (c) 2015年 weichen. All rights reserved.

#include <stdio.h>

int gAll;

// int g2 = gAll; 編譯不通過;如果是 const int gAll = 10;int g2 = gAll;是可以的,但是不推薦這麼寫

void f(int a);

void t(void);


int main(int argc, const char * argv[]) {
    /*
     1. 全局變量
    
     定義在函數外面的變量是全局變量
     全局變量具有全局的生存期和作用域
        它們與任何函數無關
        在任何函數內部都可以使用它們
     
     2. 全局變量初始化
     
     沒有做初始化的全局變量會得到0值,編譯器會加上;本地變量是內存裡有什麼就得到什麼
        指針沒有初始化會得到NULL值
     只能用編譯時刻已知的值來初始化全局變量
     它們的初始化發生在main函數之前
     
     3. 靜態本地變量
     
     在本地變量定義時加上static修飾符就成為靜態本地變量
     當函數離開的時候,靜態本地變量會繼續存在並保持其值
     靜態本地變量的初始化只會在第一次進入這個函數時做,以後進入函數時會保持上次離開時的值
     
     靜態本地變量實際上是特殊的全局變量
        它們位於相同的內存區域;&gAll和&all相差正好是一個int的大小,而實際的本地變量放在另外的地方
     靜態變量具有全局的生存期,函數內的局部作用域
        static在這裡的意思是局部作用域(本地可訪問)
     
     4. 返回指針的函數
    
     返回本地變量的地址是危險的;因為離開函數,本地變量就不存在了
     返回全局變量或靜態本地變量的地址是安全的
     返回在函數內malloc的內存是安全的,但是容易造成問題
     最好的做法是返回傳入的指針
     
     5. Tips
     
     不要使用全局變量來在函數間傳遞參數和結果
     盡量避免使用全局變量
        豐田汽車的案子
     使用全局變量和靜態本地變量的函數是線程不安全的
     
     */
    
    printf("in %s is %d\n", __func__, gAll); // in main is 0
    
    f(gAll);
    
    printf("again in %s is %d\n", __func__, gAll); // again in main is 2
    
    t(); // all is 6
    t(); // all is 7 , 沒有被重新初始化為5,使用上次得到的變量值
    t(); // all is 8

    return 0;
}

// 全局變量
void f(int a)
{
    printf("in %s is %d\n", __func__, a); // in f is 0

    gAll += 2;
    
    printf("again in %s is %d\n", __func__, gAll); // again in f is 2

    int gAll = 1; // 重新聲明一個與全局變量同名的本地變量,此時全局變量gAll被隱藏
    
    printf("last in %s is %d\n", __func__, gAll); // last in f is 1
}

// 靜態本地變量
void t(void)
{
    static int all = 5;
    
    int k = 0;
    
    all += 1;
    
    printf("all is %d\n", all);

    printf("&gAll = %p\n", &gAll); // &gAll = 0x10000101c
    printf("&all = %p\n", &all); // &all = 0x100001018
    printf("&k = %p\n", &k); //  &k = 0x7fff5fbff80c
}

/*
int* g(void)
{
    int x = 10;
    return &x; // 返回本地變量的地址,編譯要麼不通過要麼提示warning
}
*/

 

編譯預處理:

//  main.c
//  Created by weichen on 15/7/15.
//  Copyright (c) 2015年 weichen. All rights reserved.

#include <stdio.h>

// const double PI = 3.14159; // C99可以使用的方式
#define PI 3.14159 // C99之前使用的方式,#define是編譯預處理指令

#define FORMAT "%f\n"

#define PI2 2*PI // pi * 2

#define PRT printf("%f ", PI);\
            printf("%f\n", PI2)

#define cube(x) ( (x) * (x) * (x) )

#define RADTODEG1(x) (x * 57.3)
#define RADTODEG2(x) (x) * 57.3

int main(int argc, const char * argv[]) {
    /*
     編譯預處理指令
     
        #開頭的是編譯預處理指令
        它們不是C語言的成分,但是C語言程序離不開它們
        #define用來定義一個宏
     
            如何看到編譯這個過程(保存編譯過程中的臨時文件,加--save-temps選項):
            終端下 gcc main.c -o 1.out --save-temps 【 main.c -> main.i(預處理後的文件) -> main.s(匯編代碼)-> main.o(目標代碼文件) 1.out(可執行文件) 】
            tail main.i 看到後面幾行中PI被替換成值
     
     宏
     
        #define <名字> <值>
        注意結尾不能加分號,因為不是C的語句
        名字必須是一個單詞,值可以是各種東西
        在C語言的編譯器開始編譯之前,編譯預處理程序(cpp)會把程序中的名字換成值
            完全的文本替換
        gcc --save-temps
     
        如果一個宏的值中有其他的宏的名字,也是會被再次替換
        如果一個宏的值超過一行,最後一行之前的行末需要加\
        宏的值後面出現的注釋不會被當做宏的值得一部分,C語言的注釋有效
     
     沒有值的宏
     
        #define _DEBUG
        這類宏是用於條件編譯的,後面有其他的編譯預處理指令來檢查這個宏是否已經被定義過了(如果定義了執行這一部分代碼,沒有定義執行另一部分代碼)
     
     預定義的宏
     
        __LINE__    // 當前代碼行號
        __FILE__    // 源代碼包含路徑的文件名
        __DATE__    // 編譯時日期
        __TIME__    // 編譯時時間
        __STDC__    // 當要求程序嚴格遵循ANSIC標准時,標識符__STDC__就會被賦值為1
     
     帶參數的宏
        
        #define cube(x) ( (x) * (x) * (x) )
        宏可以帶參數
        可以帶多個參數
            #define MIN(a, b) ((a) > (b) ? (b) : (a))
        也可以組合(嵌套)使用其它宏
     
        在大型程序的代碼中使用非常普遍(在代替函數時運行效率比函數高,但是代碼大小比函數大)
        可以非常復雜,如“產生”函數
            在#和##這兩個運算符的幫助下
        存在中西方文化差異(國外使用宏的項目更多)
        部分宏會被inline函數替代(加上inline就代表這個函數是聲明而不是定義,使用inline時,相當於把當前調用替換成函數裡的代碼,增加了代碼但是減少了函數調用的額外開銷,是以空間換時間的做法;什麼時候用inline:函數2-3行很小或在循環裡頻繁調用的函數,什麼時候不用inline:超過20行的函數或遞歸的函數)
     
     錯誤定義的宏
     
        #define RADTODEG1(x) (x * 57)
        #define RADTODEG2(x) (x) * 57
     
     帶參數的宏的原則
     
        一切都要括號
            整個值要括號
            參數出現的每個地方都要括號
        #define RADTODEG(x) ((x) * 57.3)
     
     其它編譯預處理指令
        
        條件編譯
        error
        ...
     
     */
    printf("%f\n", 2*PI); // 6.283180
    
    printf(FORMAT, PI2); // 6.283180
    
    PRT; // 3.141590 6.283180
    
    printf("%s:%d\n", __FILE__, __LINE__); // Users/weichen/.../main.c:66  #通常用於調試
    printf("%s:%s\n", __DATE__, __TIME__); // Jul 15 2015:01:47:36         #區分程序版本
    
    if (__STDC__ == 1) {
        printf("ANSIC\n");
    } else {
        printf("Not ANSIC\n");
    }
    
    
    int i = 2;
    printf("%d\n", cube(5)); // 125
    printf("%d\n", cube(i)); // 8
    printf("%d\n", cube(i+2)); // 64
    
    printf("%f\n", RADTODEG1(5+2)); // 119.600000
    printf("%f\n", 180/RADTODEG2(1)); // 10314.000000  #我們希望的是180/57.3, 而實際卻不是
    
    return 0;
}

 

大程序結構:

//  main.c
//  Created by weichen on 15/7/15.
//  Copyright (c) 2015年 weichen. All rights reserved.

#include <stdio.h>

int main(int argc, const char * argv[]) {
    /*
     大程序結構
     
        main()裡地代碼太長了適合分成幾個函數
        一個源代碼.c文件太長了適合分成幾個文件
        兩個獨立的源代碼文件不能編譯形成可執行的程序
     
     項目
     
        在DevC++中新建一個項目,然後把幾個源代碼文件加入進去
        對於項目,DevC++的編譯會把一個項目中所有的源代碼文件都編譯後,鏈接起來
        有得IDE有分開的編譯和構建兩個按鈕,前者是對單個源代碼文件編譯,後者是對整個項目做鏈接
     
     編譯單元
        
        一個.c文件是一個編譯單元
        編譯器每次編譯只處理一個編譯單元,編譯形成.o文件,由鏈接器鏈接它們
     
     頭文件
        
        如果main裡面調用的函數沒有進行聲明,C語言會將函數的所有參數和返回值默認當做int對待,這種情況下不能保證函數被正確使用
        把函數原型放到一個頭文件(以.h結尾)中,在需要調用這個函數的源代碼(.c文件)中#include這個頭文件,就能讓編譯器在編譯的時候知道函數的原型
     
        #include是一個編譯預處理指令,和宏一樣,在編譯之前就處理了
        它把那個文件的全部文本內容原封不動的插入到它所在的地方
            所以也不是一定要在.c文件的最前面#include
        #include有兩種形式來指出要插入的文件
            " "要求編譯器首先在當前目錄(.c文件所在的目錄)尋找這個文件,如果沒有,到編譯器指定的目錄去找
            < >讓編譯器只在指定的系統目錄去找(/usr/include)
            編譯器自己知道自己的標准庫的頭文件在哪裡
            環境變量和編譯器命令行參數也可以指定尋找頭文件的目錄
        
        在使用和定義這個函數的地方都應該#include這個頭文件
        一般的做法是除了main之外的任何.c都有對應的同名的.h, 把所有對外公開的函數的原型和全局變量的聲明都放進去
     
     #include的誤區
        
        #include不是用來引入庫的
        stdio.h裡只有printf的原型,printf的代碼在另外的地方,某個.lib(windows)或.a(Unix)中
        現在的C語言編譯器默認會引入所有的標准庫
        #include <stdio.h>只是為了讓編譯器知道printf函數的原型,保證你調用時給出的參數值是正確地類型
     
     不對外公開的函數
     
        在函數前面加上static就使得它成為只能在所在的編譯單元中被使用的函數(函數不希望別人用,僅當前文件中能使用)
        在全局變量前面加上static就使得它成為只能在所在的編譯單元中被使用的全局變量(僅當前文件中能使用的全局變量)
     
     變量定義和聲明的區別
     
        int i; 是變量的定義
        extern int i; 是變量的聲明,不能初始化,放在頭文件中;用來告訴編譯器,其它用到的變量i是存在的
     
        聲明是不產生代碼的東西
            函數原型
            變量聲明
            結構聲明
            宏聲明
            枚舉聲明
            類型聲明
            inline聲明
        定義是產生代碼的東西
            函數
            全局變量
     
        只有聲明可以被放在頭文件中(是規則不是法律)
        否則會造成一個項目中多個編譯單元裡有重名的實體
            某些編譯器允許幾個編譯單元中存在同名的函數,或者用weak修飾符來強調這種存在
     
        同一個編譯單元裡,同名的結構不能被重復聲明
        如果你的頭文件裡有結構的聲明,很難這個頭文件不會在一個編譯單元裡被#include多次
        所以需要"標准頭文件結構",運用條件編譯和宏,保證這個頭文件在一個編譯單元裡只會被#include一次
            #ifndef _MAX_H_
            #define _MAX_H_
            
            .....
            
            #endif
        #pragma once也能起到相同的作用,但不是所有的編譯器都支持
        
    */return 0;
}

 

Link:http://www.cnblogs.com/farwish/p/4647044.html

@黑眼詩人 <www.farwish.com>

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