[C语言]进阶|程序结构

通过llama.cpp与羊驼聊天的网页界面- 详解 Serge 的启动使用

 

全局变量:

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

 

Refer:C语言如何进阶

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

posted on 2015-07-15 22:35  ercom  阅读(1438)  评论(0编辑  收藏  举报