精华内容
下载资源
问答
  • 常量表达式值作为编译条件;作为编译条件两种形式;作为编译条件的应用举例;
  • 条件编译应用举例-AT89S52单片机C语言应用100例
  • 文章目录条件编译条件编译在头文件包含中的应用结构体结构体成员的引用于赋值结构体指针及其引用使用结构体封装俄罗斯方块举例 条件编译 条件编译属于预处理命令,它可以根据宏是否定义,来决定某段代码是否需要...

    条件编译

    条件编译属于预处理命令,它可以根据宏是否定义,来决定某段代码是否需要编译。
    假设一个场景,我们写的程序,可能在windows和Linux上编译,在Linux上,因为种种原因,不能得到全面支持。

    #ifdef ARCH_WIN
     /*代码段1*/ 
    int main(int argc, char* argv[])
     { 
     printf("windows, all functions work well\r\n"); 
     return 0; 
    } 
    #else 
    /*代码段2*/ 
    int main(int argc, char* argv[])
     { 
     printf("Linux, bye bye...\r\n");
     return 0;
     } 
    #endif
    

    以上代码:如果定义了ARCH_WIN这个宏,那么编译代码段1,否则,编译代码段2. 条件编译在工程中的应用非常多。比如我们所谓的Debug版本,和Release版本其实 就是条件编译控制的。
    不仅可以通过define指令去设置宏,还可以通过VS设置宏:

    vs设置宏的原理,是直接通过编译器的编译选项设置。所以对于所有的cpp文件全部 生效。

    条件编译在头文件包含中的应用

    赤裸裸的头文件包含,容易产生重复包含的问题。
    准备三个文件:
    mybase.h

    extern int nValue;
    

    mybase2.h
    ···
    #include “mybase.h”

    void myfun();
    ···
    main.cpp

    #include "mybase.h" 
    #include "mybase2.h"
    int main(int argc, char* argv[]) 
    {
     return 0; 
    }
    

    依据include的知识,最后main.cpp

    extern int nValue; 
    extern int nValue; 
    void myfun();
    int main(int argc, char* argv[]) 
    {
     return 0; 
    }
    

    这使得有些头文件被重复包含了。轻则降低效率,重则编译出错。
    我们可以使用条件编译,解决这个问题。
    解决方案,是按照以下模板去写头文件

    #ifndef 非常特殊的宏 
    #define 非常特殊的宏 
    你的头文件的内容…… 
    #endif
    

    比如修改mybase.h

    #ifndef 20191212_NR1001_C12
    #define 20191212_NR1001_C12
     extern int nValue; 
    #endif
    

    mybase2.h

    #ifndef 20191212_MYBASE2 
    #define 20191212_MYBASE2
     #include "mybase.h"
    void myfun();
     #endif
    

    这种套路,被更新的指令

    #pragam once
    

    所取代

    结构体

    他是非基本数据类型,顾名思义,不是编译器天生知道的类型。是程序员发明的类型:

    • 程序员先发明新类型(非基本数据类型)
    • 然后再用新类型,去定义新变量
      即将学习的结构体(struct),是C语言中最重要的非基本数据类型。 它的发明 目的,是为了复合信息
      举例,如果我们要写一个学生管理系统,那么当然要保存学生的信息,比如:
    • 姓名, char[12]
    • 性别, int
    • 学号, int
      如果是你,如何来存储一个学生的信息:
    char szName1[12] = "路人甲" 
    int nGender1=1;
     int nStuID1 = 201912001;
    

    如果有两个学生:

    char szName1[12] = "路人甲"
     int nGender1=1;
     int nStuID1 = 201912001;
     char szName2[12] = "路人乙"
     int nGender2=1;
     int nStuID2 = 201912002;
    

    很明显,当需要记录的学生越来越多时,容易出错。因为姓名、ID、学号没有被很好 的封装起来。
    结构体,就是为了解决封装问题而诞生的语法。
    ** 定义结构体类型**

    struct tagStudent 
    { 
     char szName[8]; 
     int nGender; 
     int nStuID; 
    };
    

    使用结构体定义新的结构体变量
    纯正的C89语法:

    struct tagStudent stu1;
    

    不过,之后的C++以及较新的C编译器,都允许省略struct来定义结构体变量

    tagStudent stu1;
    

    结构体成员的引用于赋值

    从结构体类型的定义就可以看出,结构体中封装了多种变量。 我们可以通过"."运算符,去引用到结构体内部的变量(结构体成员)。

    struct tagStudent
    {
        char szStudentName[15];
        int nStudentGender;
        int nStrdentID;
    
    };
    
    int main(int argc, char* argv[])
    {
        struct tagStudent stu1;
        struct tagStudent stu2;
        strcpy(stu1.szStudentName, "同学甲");
        stu1.nStudentGender = 1;
        stu1.nStrdentID = 20222888;
        strcpy(stu2.szStudentName, "同学乙");
        stu2.nStudentGender = 0;
        stu2.nStrdentID = 20222889;
        printf("姓名:%s\r\n性别:%d\r\n学号:%d\r\n", stu1.szStudentName, stu1.nStudentGender, stu1.nStrdentID);
        printf("姓名:%s\r\n性别:%d\r\n学号:%d\r\n", stu2.szStudentName, stu2.nStudentGender, stu2.nStrdentID);
    
        return 0;
    }
    

    结构体指针及其引用

    如果需要将结构体作为参数传递
    强烈不推荐使用值传递的方法传递结构体。
    而强烈推荐使用指针传递结构体。
    因为以值传递的方式传递结构体,结构体会整体被拷贝到栈帧并随函数返回释放。这 常常意味着较大的消耗资源。
    甚至可能造成栈帧的崩溃。

    //结构体作为值传递
    struct tagStudent
    {
        char szStudentName[2000];
        int nStudentGender;
        int nStudentID;
    };//这个应该先定义(在调用这个结构体函数的定义之前),要不然报错
    
    void ShowMessage(tagStudent stu)
    {
        printf("姓名:%s\r\n性别:%d\r\n学号:%d\r\n", stu.szStudentName, stu.nStudentGender, stu.nStudentID);
    };
    
    
    
    struct tagStudent g_stu1;
    
    int main(int argc, char* argv[])
    {
        strcpy(g_stu1.szStudentName, "st1");
        g_stu1.nStudentGender = 1;
        g_stu1.nStudentID = 20221888;
        ShowMessage(g_stu1);
        return 0;
    }
    

    综上所述,我们推荐使用地址的方式(结构体指针)传递结构体。
    结构体指针的定义

    tagStudent* pStu = &g_stu1;
    

    使用结构体指针引用结构体成员

    //用结构体指针引用结构体成员
    struct tagStudent
    {
        char szStudentName[2000];
        int nStudentGender;
        int nStudentID;
    };
    
    void Show(tagStudent *pStu)
    {
        printf("%s\r\n%d\r\n%d\r\n", pStu->szStudentName, pStu->nStudentGender, pStu->nStudentID);
    }
    
    struct tagStudent g_stu1;
    
    int main(int argc, char* argv[])
    {
        strcpy(g_stu1.szStudentName, "路人甲");
        g_stu1.nStudentGender = 0;
        g_stu1.nStudentID = 20221888;
        Show(&g_stu1);//注意此处指针&
        return 0;
    }
    

    使用结构体封装俄罗斯方块举例

    结构体入门,主要是封装

    struct tagBrick 
    { 
        char chBrick[4][4]; 
        int nShape = 0; //是长条还是方块,系数为16
        int nRotate = 0; //朝向,系数为4 
        int nRow = 0; 
        int nCol = 0; 
    };
    extern tagBrick g_Brick; 
    extern tagBrick g_BrickNext;
    
    展开全文
  • 本磕碜全面介绍C语言精华内容,如何利用C语言进行程序设计的方法,触及各种常用C语言语法,详细解释了理论和实践结合,举例恰到好处,坑点全部强调到位 任务作业: 若有如下语句: int *po...

    立即学习:https://edu.csdn.net/course/play/8088/175768?utm_source=blogtoedu

    1. 文件包含

    将另一个文件的内容包含到本文件中,一般通过#include来实现

    一般形式:#include "文件名"

    说明:

    (1)很多公共修改都可以放到这个.h文件中进行了。但是你一旦修改了这个.h,也相当于修改了#inlcude这个.h的所有.cpp文件,那这些.cpp文件显然也得重新编译了

    (2)一个#inlcude只能包含一个文件,如果要#include多个文件,那就使用多个#include

    (3)文件包含是可以嵌套的。一个头文件中可以#include其他头文件

    (4)自己写的.h头文件用#include".h",标准的头文件使用#include<.h">

     

    2. 条件编译

    条件编译几种形式:

    (1)形式1:

    #ifdef 标识符

        程序段1(一堆代码)

    #else

        程序段2

    #endif

    作用:当标识符被定义过(#define来定义),则对程序段1进行编译,否则对程序段2进行编译。#else部分可以没有

    (2)形式2:

    形式2:

    #ifndef 标识符

        程序段1(一堆代码)

    #else

        程序段2

    #endif

    作用:若标识符未被定义,则编译程序段1,否则编译程序段2;与形式1正好相反。#else部分可以没有

    (3)形式3:

    #if 表达式

        程序段1

    #else

        程序段2

    #endif

    作用:当指定的表达式值为真(非0)时就编译程序段1,否则编译程序段2,那我们事先给定一定的条件,就可以使程序在不同条件下执行不同的功能。

    (4)形式4:

    #if 表达式1

        程序段1

    #elif  表达式2

        程序段2

    #else

        程序段3

    #endif

    作用:当表达式1值为真(非0)时就编译程序段1,否则当表达式2的值为真(非0)则编译程序段2,否则编译程序段3

     

    条件编译的好处:

    (1)条件编译可以减少目标程序长度

    (2)解决跨平台开发问题,增加程序的可移植性,增加程序的灵活性,如:

    #if _WIN32

        //windows专用函数

        //WaitForSingleObject()

    #elif __linux__

        //linux专用函数

        //epoll()

    #else

    #endif

    展开全文
  • ;回顾一下编译链接过程;C语言程序的编译链接过程;编译指令;宏的分类;无参宏;无参宏的使用;无参宏的使用;无参宏的使用;有参宏;有参宏的使用;有参宏与函数的区别;文件包含;文件包含成为同一个...文件包含举例;条件编译
  • 首先三种形式的命令:宏定义,文件包含,条件编译命令。 1、宏定义主要是:#define,#undef 如下: #define PI 3.1415926 /*不带参数的宏定义*/ #define Max(a,b) a>b?a:b /*带参数的宏定义*/ 说明:宏定义在...
     
    

    首先三种形式的命令:宏定义,文件包含,条件编译命令。

    1、宏定义主要是:#define,#undef

    如下:

    #define PI 3.1415926                    /*不带参数的宏定义*/

    #define Max(a,b)  a>b?a:b                    /*带参数的宏定义*/

    说明:宏定义在C语言与C++语言中是相通的。

    下面举例说明定义宏FAILED用于检测数据的正确性。

    ①#define的应用:
    #define FAILED(Status) ((Status)<0)
    #include "stdio.h"
    void main()
    {
    int d;
    printf ("Please input a integer number(n>0)\n");
    do
    {
    scanf("%d" ,&d);
    }while(FAILED(d));
    }
    其中while(FAILED(d))在编译之前被无条件替换为while(d<0)。
    宏定义和调用在形式与函数比较相似,但是原理是不同。
    ②#undef的应用:

    #include "stdio.h"
    void Test();
    int main(int argc, char* argv[])
    {
    #define CONST_NAME1 "CONST_NAME1"
    printf("%s\n",CONST_NAME1);
    #undef CONST_NAME1
    printf("%s\n",CONST_NAME1); /*错误,CONST_NAME1的定义已经取消*/
    {
    #define CONST_NAME2 "CONST_NAME2"
    printf("%s\n",CONST_NAME2);
    }
    printf("%s\n",CONST_NAME2);
    return 0;
    }
    void Test()
    {
    printf("%s\n",CONST_NAME2);
    }
    在程序的编译的时候,系统提示如下信息
    error C2065: 'CONST_NAME1' : undeclared identifier
    出现上述编译错误的原因是,在第二次应用符号常量CONST_NAME1时,此符号常量已经被取消定义。

    2、文件包含:

    #include <文件名> ,这种属于标准方式,用于编译系统指定的文件。

    #include “文件名”,这种属于用户方式,查找用户当前工作的文件夹中的文件,如果不存在则再按照标准方式查询。

    3、条件编译(常见的三种形式):

    ①第一种形式:

    #if defined(或者是ifdef)<标识符>

    <程序段1>

    [#else

    <程序段2>]

    #endif

    ②第二种形式:

    #if !defined(或者是ifndef)<标识符>

    <程序段1>

    [#else

    <程序段2>]

    #endif

    ③第三种形式常用与C++编译器中。

    #ifdef …

    [#elif … ]

    [#elif …]

    #else …

     #endif

    4、其他条件编译命令:

    ①#error:

    语法格式如下:
    #error token-sequence
    其主要的作用是在编译的时候输出编译错误信息token-sequence,从方便程序员检查程序中出现的错误。例如下面的程序
    #include "stdio.h"
    int main(int argc, char* argv[])
    {
    #define CONST_NAME1 "CONST_NAME1"
      printf("%s\n",CONST_NAME1);
    #undef CONST_NAME1
    #ifndef CONST_NAME1
    #error No defined Constant Symbol CONST_NAME1
    #endif
    {
    #define CONST_NAME2 "CONST_NAME2"
       printf("%s\n",CONST_NAME2);
    }
       printf("%s\n",CONST_NAME2);
       return 0;
    }
    在编译的时候输出如编译信息
    fatal error C1189: #error : No defined Constant Symbol CONST_NAME1

    # pragma
    在编写程序的时候,我们经常要用到#pragma 指令来设定编译器的状态或者是指示编译器完成一些特定的动作.

    下面介绍了一下该指令的一些常用参数,希望对大家有所帮助!

    一般格式:#pragma para

    一、  message 参数。

    message

    它能够在编译信息输出窗口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:

    #pragma message(“消息文本”)

    当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

    当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条

    指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

    #ifdef _X86

    #pragma message(“_X86 macro activated!”)

    #endif

    当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了

    二、  另一个使用得比较多的#pragma参数是code_seg。格式如:

    #pragma code_seg( [ [ { push | pop}, ] [ identifier, ] ] [ "segment-name" [, "segment-class" ] )

    该指令用来指定函数在.obj文件中存放的节,观察OBJ文件可以使用VC自带的dumpbin命令行程序,函数在.obj文件中默认的存放节为.text节。

    如果code_seg没有带参数的话,则函数存放在.text节中 push (可选参数) 将一个记录放到内部编译器的堆栈中,可选参数可以为一个标识符或者节名 pop(可选参数) 将一个记录从堆栈顶端弹出,该记录可以为一个标识符或者节名

    identifier (可选参数) 当使用push指令时,为压入堆栈的记录指派的一个标识符,当该标识符被删除的时候和其相关的堆栈中的记录将被弹出堆栈

    "segment-name" (可选参数) 表示函数存放的节名

    例如:

    //默认情况下,函数被存放在.text节中

    void func1() { // stored in .text

    }

    //将函数存放在.my_data1节中

    #pragma code_seg(".my_data1")

    void func2() { // stored in my_data1

    }

    //r1为标识符,将函数放入.my_data2节中

    #pragma code_seg(push, r1, ".my_data2")

    void func3() { // stored in my_data2

    }

    int main() {

    }

    三、   #pragma once (比较常用)

     

    这是一个比较常用的指令,只要在头文件的最开始加入这条指令就能够保证头文件被编译一次

    四、   #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。

    BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。

    有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,

    如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

    五、   #pragma warning指令

    该指令允许有选择性的修改编译器的警告消息的行为

    指令格式如下:

    #pragma warning( warning-specifier : warning-number-list [; warning-specifier : warning-number-list...]

    #pragma warning( push[ ,n ] )

    #pragma warning( pop )

    主要用到的警告表示有如下几个:

    once:只显示一次(警告/错误等)消息

    default:重置编译器的警告行为到默认状态

    1,2,3,4:四个警告级别

    disable:禁止指定的警告信息

    error:将指定的警告信息作为错误报告

    如果大家对上面的解释不是很理解,可以参考一下下面的例子及说明

    #pragma warning( disable : 4507 34; once : 4385; error : 164 )

    等价于:

    #pragma warning(disable:4507 34) // 不显示4507和34号警告信息

    #pragma warning(once:4385) // 4385号警告信息仅报告一次

    #pragma warning(error:164) // 把164号警告信息作为一个错误。

    同时这个pragma warning 也支持如下格式:

    #pragma warning( push [ ,n ] )

    #pragma warning( pop )

    这里n代表一个警告等级(1---4)。

    #pragma warning( push )保存所有警告信息的现有的警告状态。

    #pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告 等级设定为n。

    #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的 一切改动取消。例如:

    #pragma warning( push )

    #pragma warning( disable : 4705 )

    #pragma warning( disable : 4706 )

    #pragma warning( disable : 4707 )

    #pragma warning( pop )

    在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)

    在使用标准C++进行编程的时候经常会得到很多的警告信息,而这些警告信息都是不必要的提示, 所以我们可以使用#pragma warning(disable:4786)来禁止该类型的警告。

    在vc中使用ADO的时候也会得到不必要的警告信息,这个时候我们可以通过

    #pragma warning(disable:4146)来消除该类型的警告信息

    六、  pragma comment(...)

    该指令的格式为

    #pragma comment( "comment-type" [, commentstring] )

    该指令将一个注释记录放入一个对象文件或可执行文件中,

    comment-type(注释类型):可以指定为五种预定义的标识符的其中一种

    五种预定义的标识符为:

    compiler:将编译器的版本号和名称放入目标文件中,本条注释记录将被编译器忽略 如果你为该记录类型提供了commentstring参数,编译器将会产生一个警告

    例如:#pragma comment( compiler )

    exestr:将commentstring参数放入目标文件中,在链接的时候这个字符串将被放入到可执行文件中, 当操作系统加载可执行文件的时候,该参数字符串不会被加载到内存中.但是,该字符串可以被 dumpbin之类的程序查找出并打印出来,你可以用这个标识符将版本号码之类的信息嵌入到可执行文件中!

    lib:这是一个非常常用的关键字,用来将一个库文件链接到目标文件中

    常用的lib关键字,可以帮我们连入一个库文件。

    例如:

    #pragma comment(lib, "user32.lib")

    该指令用来将user32.lib库文件加入到本工程中

    linker:将一个链接选项放入目标文件中,你可以使用这个指令来代替由命令行传入的或者在开发环境中

    设置的链接选项,你可以指定/include选项来强制包含某个对象,例如:

    #pragma comment(linker, "/include:__mySymbol")

    你可以在程序中设置下列链接选项

    /DEFAULTLIB

    /EXPORT

    /INCLUDE

    /MERGE

    /SECTION

    这些选项在这里就不一一说明了,详细信息请看msdn!

    user:将一般的注释信息放入目标文件中commentstring参数包含注释的文本信息,这个注释记录将被链接器忽略

    例如:

    #pragma comment( user, "Compiled on " __DATE__ " at " __TIME__ )

    补充一个

    #pragma pack(n)

    控制对齐 如

    #pragma pack(push)

    #pragma pack(1)

    struct s_1{

    char szname[1];

    int a;

    };

    #pragma pack(pop)

    struct s_2{

    char szname[1];

    int a;

    };

    printf("s_1 size : %d\n", sizeof(struct s_1));

    printf("s_2 size : %d\n", sizeof(struct s_2));

    得到5,8。

    七、  #pragma的用法

    #pragma是一个C语言中的预处理指令,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。

    其格式一般为: #pragma Para

    其中Para 为参数,下面来看一些常用的参数。

    (1) message 参数。

    Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗

    口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为:

    #Pragma message(“消息文本”)

    当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。

    当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法

    #ifdef _X86

    #Pragma message(“_X86 macro activated!”)

    #endif

    当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_

    X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了

    (2) 另一个使用得比较多的pragma参数是code_seg。格式如:

    #pragma code_seg( ["section-name"[,"section-class"] ] )

    它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。

    (3) #pragma once (比较常用)

    只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。

    (4) #pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。

    有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。

    (5) #pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体

    外观的定义。

    (6) #pragma warning( disable : 4507 34; once : 4385; error : 164 )

    等价于:

    #pragma warning(disable:4507 34) // 不显示4507和34号警告信息

    #pragma warning(once:4385) // 4385号警告信息仅报告一次

    #pragma warning(error:164) // 把164号警告信息作为一个错误。

    同时这个pragma warning 也支持如下格式:

    #pragma warning( push [ ,n ] )

    #pragma warning( pop )

    这里n代表一个警告等级(1---4)。

    #pragma warning( push )保存所有警告信息的现有的警告状态。

    #pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告

    等级设定为n。

    #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的

    一切改动取消。例如:

    #pragma warning( push )

    #pragma warning( disable : 4705 )

    #pragma warning( disable : 4706 )

    #pragma warning( disable : 4707 )

    //.......

    #pragma warning( pop )

    在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。

    (7) pragma comment(...)

    该指令将一个注释记录放入一个对象文件或可执行文件中。

    常用的lib关键字,可以帮我们连入一个库文件。

    (8) progma pack(n)

    指定结构体对齐方式!#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条 件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

      否则必须为n的倍数。下面举例说明其用法。

    #pragma pack(push) //保存对齐状态

    #pragma pack(4)//设定为4字节对齐

    struct test

    {

    char m1;

    double m4;

    int m3;

    };

    #pragma pack(pop)//恢复对齐状态

    为测试该功能,可以使用sizeof()测试结构体的长度!

    ③#line

    此命令主要是为强制编译器按指定的行号,开始对源程序的代码重新编号,在调试的时候,可以按此规定输出错误代码的准确位置。
    形式1
    语法格式如下:
    # line constant “filename”:其作用是使得其后的源代码从指定的行号constant重新开始编号,并将当前文件的名命名为filename。例如下面的程序如下:
    #include "stdio.h"
    void Test();
    #line 10 "Hello.c"
    int main(int argc, char* argv[])
    {
    #define CONST_NAME1 "CONST_NAME1"
    printf("%s\n",CONST_NAME1);
    #undef CONST_NAME1
    printf("%s\n",CONST_NAME1);
    {
    #define CONST_NAME2 "CONST_NAME2"
    printf("%s\n",CONST_NAME2);
    }
    printf("%s\n",CONST_NAME2);
    return 0;
    }
    void Test()
    {
    printf("%s\n",CONST_NAME2);
    }
    提示如下的编译信息:Hello.c(15) : error C2065: 'CONST_NAME1' : undeclared identifier
    表示当前文件的名称被认为是Hello.c, #line 10 "Hello.c"所在的行被认为是第10行,因此提示第15行出错。
    形式2语法格式如下:

    # line constant:其作用在于编译的时候,准确输出出错代码所在的位置(行号),而在源程序中并不出现行号,从而方便程序员准确定位。

    ④运算符#和##
    在ANSI C中为预编译指令定义了两个运算符——#和##。
    # 的作用是实现文本替换,例如
    #define HI(x) printf("Hi,"#x"\n");
    void main()
    {
        HI(John);
    }
    程序的运行结果:Hi,John
    在预编译处理的时候, "#x"的作用是将x替换为所代表的字符序列。在本程序中x为John,所以构建新串“Hi,John”。
    ##的作用是串连接。例如
    #define CONNECT(x,y) x##y
    void main()
    {
    int a1,a2,a3;
    CONNECT(a,1)=0;
    CONNECT(a,2)=12;
    a3=4;
    printf("a1=%d\ta2=%d\ta3=%d",a1,a2,a3);
    }
    程序的运行结果为:a1=0 a2=12 a3=4
    在编译之前, CONNECT(a,1)被翻译为a1, CONNECT(a,2)被翻译为a2。

    ⑤预定义常量
    __LINE__:当前源代码的行号,为整型常量

    __FILE__:当前编译程序文件的名称,为字符串

    __DATE__:编译程序文件日期,为字符串(”MM DD YYYY"形式,如” Jan 19 1993”)

    __TIME__:编译程序文件时间,为字符串("hh:mm:ss"形式,如”08:30:23”)

    __STDC__:ANSI C标志,整型常量1,说明此程序兼容ANSI C标准。

    展开全文
  • 嵌入式开发--C语言之语法概述(1.2)

    千次阅读 多人点赞 2020-05-19 21:01:15
    C语言之语法概述1.GCC介绍和使用1.1介绍1.2gcc概述1.3C语言编译过程介绍1.4C语言编译常见错误举例1.5C语言预处理介绍1.6条件预处理的应用1.7宏展开下的#、##使用2.C语言常用关键字及运算符操作2.1C语言常用关键字及...

    1.GCC介绍和使用

    1.1介绍

    今天我们就进入到嵌入式c语言高级部分的学习了,我们先要明确一个基本目标。众所周知,c语言作为我们嵌入式开发中的一个核心工具,那么对c语言的掌握程度越高 ,对于后面我们遇到的一些问题,你会更加容易理解。我们作为一个嵌入式工程师来说,在以后遇到问题,我们解决问题的能力就会非常的强大。所以对c语言的学习呢,我们更多的是希望大家通过语法去了解到c语言为什么会这么设计。以及呢,它在设计中还蕴含了一些什么样的设计思想。

    对于要想学好一门程序设计语言,我们实际上可以认为它是分两个部分来走的。第一个部分就是我们所说的程序设计思想的学习。其实就是大部分书上大量篇幅中讲到的if的介绍、while的介绍,for的介绍啊,然后如何用if、for、while,然后用一些我们的关键字,对于某些数据进行什么排序啊查找啊等等的实现,那这些呢,我们认为它只能叫做程序设计思想,但是我们说你如果要想真正掌握一门语言的话啊,你不仅需要有基本的程序设计思想,你还需要了解这个语言工具的一些特性。

    我们更多的是希望大家去理解c语言在嵌入式开发中,它的一些工具特性,c语言为什么作为我们最早最老的一种一门语言,到现在还在使用啊?其原因就在于它操作底层的性能,或者叫操作底层的便易性 ,应该是其他语言没法比拟的,所以说它一直有他存在的道理,这个道理自然而然也就是c语言最大的特性,就是它操作底层,操作什么内存类型资源的这种方式,应该说这种操作方式它是独一无二的啊,准确来说,即便不是独一无二的,但是至少是最合理、最合适的一门语言。

    最典型的一个例子就是指针啊,很多同学可能在学校中所学的什么冒泡法啊,什么插入排序啊,等等这些这些算法啊,更多的我们认为是一种程序设计的思想,即就是该如何去写一个简单程序,但是一旦让你使用指针,利用指针去操作的话,你会发现你就没有办法快速的写出合适的程序。那原因就是对于这个工具的特性,你并没有了解。那么程序设计思想呢,应该说这个是学不来的,这个需要练的,就是你不要不断的通过一些例题和一些你程序的一些积累才能练出来。但是工具特性的学习呢 ,因为它的特点非常明显,所以我们通过一定的培训或者一定的辅导过后,你应该能够很快的掌握,所以说呢,我们希望大家去学习c语言了,更多的还是先学习这样一种特性。

    可以自己回答三个问题。我们讲很多 ,比如关键字 int char 甚至来说我们的指针等等,后面的课程中你可以去总结一下。当我们想用它了,哎,int是什么时候用的?应该怎么用呢?当然我们说对于c语言来说啊,这个工具怎么用,应该是非常的简单的,因为它的基本语法这个肯定是要先掌握的,然后什么时候用?大家可以总结一下。又在什么场景下出现,它为什么会出现这个东西啊,那么他肯定有它的意义,比如说我写int而不写char,他肯定有区别。那为什么我一定要写int而不写char,或者他俩能不能交换呢。对不对?哎,大家都带着这样的疑问去学习的话呢,可能对c语言的很多理解会更加深入啊,其实最后一个问题就是为什么要这样设计。这个内容就相对来讲就比较复杂了,可能不是我们这一门课程能解决的。可能需要大家后期学到arm开发,一些我们驱动程序的一些开发过后,你才能去理解到c语言在这样设计中有它的一个怎样的想法在里面。

    知其然知其所以然啊,这是我们的基本定位和想法,那么这是一种思路和方法,希望大家呢尽量的按照这个思路慢慢的去学啊,我们还是希望偏向于嵌入式的一些实际工程开发,然后给大家进行相应的举例,看举例来学习, 比如说像我们c语言中啊,大家可能了解过的,比如说那个运算符的优先级,这样的知识点呢,我们可能就不想给大家介绍了,因为我们都知道,什么加减乘除位运算啊,我们的逻辑运算式在写成一个表达式的时候呢,一般它有一个优先级的一个一个执行过程,对不对,哎,但是呢,我们说实际上我们确实没有必要记忆优先级的高低。因为在我们实际开发中有这么一个万能的钥匙,就是圆括号,那么利用这样一个圆括号呢,可以帮我们人为地把一些什么优先级给它定义出来,并且这样的话,还不会产生歧义,别人也能更好的看懂你的代码。

    1.2gcc概述

    今天我们就进入到嵌入式c语言高级编程中的第一个内容。首先我们先看看这个阶段我们需要掌握的重点及难点。那么首先重点我们希望大家能够掌握在c语言中是如何将逻辑语言c变成机器指令的过程。因为这个概念的掌握对于我们后期学习arm开发有帮助。作为一个嵌入式开发人员的话,直接关系到我们后面在学习linux驱动,学arm体系结构的学习效率。

    来看看第一个我们需要给大家介绍的概念----gcc。我们为什么要谈gcc或者为什么有的gcc这个东西?实际上计算机是一个非常傻瓜化的东西,因为它只认识高低电平。可是如果我们让它来处理人的逻辑,让大家更早的去接触高低电平这样一种非常枯燥的资源,我们实在不能接受。所以那些之前的大牛们就把它设计成了一个叫做高级语言的一个东西。所以说呢,其实我们所谓的计算机编程,其实就是一个翻译的过程。把我们人能理解的一套语言(c语言)翻译成机器能识别的语言(高低电平)。那gcc是什么呢?就是翻译官,用来翻译的。它的具体解释如下:

    GUN:开源社区的标杆。

    gcc----起源: GUN c compile (只支持c)

    --------发展: GUN compiler collection (后来可以翻译更多的语言)

    gcc -o output 1.c

    -o必须和output一起,输入文件必须有后缀。区别不同后缀名。

    gcc -v -o output 1.c
    加-v看见使用到哪些“翻译官”

    1.3C语言编译过程介绍

    预处理 (替换)
    cpp -o a.i 001.c
    【gcc -E】

    编译 (关键字是编译器处理的)
    /usr/lib/gcc/i486-linux-gnu/4.4.3/cc1 -o a.s 001.c
    【gcc -S】

    汇编
    as -o a.o a.s
    【gcc -c】

    链接
    /usr/lib/gcc/i486-linux-gnu/4.4.3/collect2 -o build a.o+…
    【gcc -o】
    gcc -o build 001.c

    【问题】
    defineinclude是关键字吗?
    不是关键字,因为关键字是编译器处理的,而这两个是预处理进行替换的。

    1.4C语言编译常见错误举例

    预处理错误
    #include “name”
    #include < name >
    not find
    gcc -I(大写)跟查找头文件的目录

    编译错误
    语法错误 ; { }等忘记写了。

    链接错误
    1.原材料不够
    undefined reference to ‘fun’
    2.寻找标签是否实现了,链接时是否加入一起链接
    3.原材料多了,只保留一个标签实现就好
    multiple definition of `fun’

    1.5C语言预处理介绍

    #include 包含头文件

    #define 宏 替换,不进行语法检查,编译过程中才会进行语法检查
    #define 宏名 宏体 请对宏体加括号,安全
    #define ABC 5+3
    printf(“the %d\n”,ABC5); 5+35
    #define ABC (5+3)
    #define ABC(x) (5+(x)) 宏函数。安全起见,参数也加括号

    #ifdef #else #endif

    预定义宏 (系统定义的宏)
    FUNCTION : 函数名
    LINE : 行号
    FILE : 文件名

    1.6条件预处理的应用

    一套代码,维护多个版本
    调试版本
    发行版本
    #ifdef ABC
    #endif
    gcc -D :
    gcc -DABC1 === #define ABC1

    1.7宏展开下的#、##使用

    #字符串化
    ##连接符号
    #define ABC(x) #x 即字符串x
    printf(ABC(abc\n)); 就会打印abc

    #define MYDAY(x) day##x
    int day1=10;
    printf(“the day is %d\n”,MYDAY(1)); 打印10

    2.C语言常用关键字及运算符操作

    									+-char
    									+-int,long,short
    					------数据类-----+-unsigned,signed
    					-				+-float,double
    					- 			  	+-void
    					-
    					-				+-struct
    					-				+-union
    			-------自定义数据类型----+-enum
    			-						+-typedef
    			-
    			-
    			-		       +-if,else
      关键字----------逻辑结构--+-switch,case,default
    	        -			   +-do,while,for
    	        -		       +-continue,break,goto
    	        -
    	        -
    	        -					+-auto
    	        -					+-register
    	        ------类型修饰符-----+-static
    	        -					+-const
    	        -					+-extern	
    	        - 					+-volatile
    	        -
    	        -
    	        -------杂项-----+-return
    					        +-sizeof
    

    2.1C语言常用关键字及运算符课程介绍

    【重点】
    掌握C语言的常用关键字及其应用场景,使用技巧
    掌握位运算的典型操作 (硬件的操作单位是bit,但是实际定义的最小单位是char,8位)
    掌握常用的逻辑操作
    【难点】
    when to do? how to do? why to do?

    2.2关键字概念及sizeof、return

    所谓的关键字是其实就是我们所谓的c语言编译器。他预先已经给我们定义了一定意义的一个字符串。它实际上对我们的一些字符串的一些特殊意义进行了一次预先展开。
    32个关键字。就是字符串,被编译器赋予了一定的意义。

    sizeof是关键字,在编译的时候就使用了。
    int a;
    printf(“the a is %d\n”,sizeof(a)); -------->>实际上返回值用%lu打印,才是标准的。
    sizeof : 编译器给我们查看内存空间容量的一个工具

    2.3数据类型关键字介绍及char类型

    那么首先我们先看一下,为什么会提出数据类型这个概念呢?那么说到这个问题,我们先看第一个问题,大家觉得c语言的操作对象是什么? 或者说c语言最终的操作目标是什么呢?答案是:我们在这个地方给一个非常宽泛的概念---->>资源。怎么理解这个资源呢?众所周知,所谓的计算机系统,无外乎由cpu作为我们的核心处理器,外围有一堆资源,然后我们通过所谓的地址总线和数据总线和它们进行访问,最后把得到的数据内容交由cpu进行处理。那么c语言呢,作为我们cpu执行指令的一个下达者,实际上就是要考虑在这些资源中,如何去找到和访问它。那么c语言的资源里头包含什么呢,最典型的其实就是我们大家非常熟悉的内存。这里的内存指的是所有内存类型的资源,不仅仅是内存条的资源。即lcd,led,显卡,一些i2c设备等等都算。

    也就说c语言操作最终的对象就是内存,那么我们如何去描述这些内存?既然最终的目的是操作它。那么我们是不是要让c语言编译器去描述它或者说让我们的程序员能够描述它,然后这样的话呢,我们人和人之间就利用这样的一个概念进行交流,然后c语言编译器再把我们人和人交流的这个逻辑结构转换成对应的硬件结构。那么也就产生了第二个问题,c语言如何描述这些资源的属性呢?那么我们说那么这些资源 ,首先它的属性非常复杂 ,比如说我们的led、lcd它可能又都有不同的类型,但是不管怎么说,我们首先先考虑第一个问题就是资源中属性的大小问题,所以c语言在设计的时候,就考虑一个问题。我要想描述内存的资源,那么自然而然我就需要考虑这个资源的大小怎么去定义? 好,那么由我们第一组关键字就是数据类型关键字,它来帮我们来解决大小限制的问题。所以说我们可以把数据类型关键字当成是我们限制内存大小的一组特殊意义的字符串。

    这里有个一点,那就是面试官问你数据类型的大小,这个问题的答案是:由编译器决定的,没有固定的值。

    那么, 有了以上的想法过后,我们先看第一个关键词,这个关键字的产生并不是平白无故的,char类型应该是我们整个数据类型产生的第一个类型。因为从技术角度来讲,我们最终的目的是操作硬件芯片,那么硬件芯片可操作的最小单位是多少呢? 那这个单位我们称之为比特(bit),它实际上只有一个高和低这两种状态, 也就是硬件所说的高低电平,那么在软件中我们把它称之为位,那么1位就代表高电平或者低电平,如果说c语言有这样一个关键字bit那岂不是就可以直接通过软件控制硬件了吗?但是实际上在软件开发上,在计算机发展的历史过程中,我们并没有这样做,因为如果这样做的话,我们软件开发的成本也就是开发的量会非常的庞大,所以我们的软件采用了一个集合方式,即我们软件操作最小单位是一组bit–>> 8个bit等于一个(字节)Byte。所以说我们在软件概念中经常会听到bit和Byte的概念(即大B和小b的关系问题)。无论怎样理解,我们必须明白的概念是------>>bit代表硬件规范,Byte代表软件规范。

    例如下面定义两个数组:
    char buf[xxx]----一般硬件相关,可以用这个。
    int buf[xxx]

    2.4数据类型之int、long、short.

    大小:
    根据编译器来决定
    编译器最优的处理大小:
    系统一个周期,所能接受的最大处理单位,int
    32bit 4B int
    16bit 2B int
    int a; char a;

    整型常量
    8bit的最大值是256,如果你定义一个char a=300,a++得到的结果并不是301。
    char a = 300; 300l 300L
    2B 65535
    int a = 66535;
    进制表示(十进制是给人看的。8进制和16进制是二进制的衍生版,是给机器看的)
    10
    十进制 八进制 十六进制 二进制
    3bit 8进制
    111 0x7
    1000 0x8 int a = 010; //8
    12 001 010
    4bit 16进制 int a = 0x10 //16

    以上是我们嵌入式开发中应该必备的一个基本概念。了解之后,我们接着把后面出来long和short看一下,其实它们就是在补充int和char带来的一个短板。那么short,我们更多的时候,并不是说我们会定义short a或int a,如果我们在没有什么特殊要求的情况下,我们一般都写int不会写short,除非说我们这个空间恰好有一个非常严格的限制,就是它对于长度必须满足16个bit,不能超过,那这个时候,我们可以用short来作为我们32位中的16比特来处理,而long类型,它是一个我们c语言可扩的一个类型,它可以帮我们把c语言不断的去累加,比如说我们随着计算机的位数增加,它可以long long这样一些类型,来表示64等等,当然这些不管怎么变,最终都要看你的编译器是否支持,所以说对于这个部分,这三个类型我们都可以称之为是整型类型的一个,整型类型的组合它主要是考察大家在我们内存中如何分配的问题。所以这几个关键字大家一定要对它的大小有个清晰的认识,以及我们在使用的场景上,在以后我们在随着代码深入和我们以后的硬件体系的建立那么你们会慢慢看到,为什么用char而不用int,或者用char和int的一些好处。

    2.5数据类型之符号数、浮点类型

    我们再看两个关键字,这两个关键字呢,也可以称之为数据类型的一个限制标志,那么它们就是unsigned和signed。我们称之为无符号数和有符号数。
    那么这两个关键字呢,在我们平时定义的时候呢,好像并没有经常去书写,比如我们一般写int a,char a,这种写法,实际上是我们程序员的一个偷懒的行为。系统编译器实际上会把int a展开,它的解释代码会帮我们解释这样的数据是signed的,也就说如果我们没有进行任何显式的声明数据为signed,一般情况下我们定义的int、char、long、short编译器自动定义成有符号数。
    而如果是无符号数,那么必须在前面用unsigned定义,这是它的一个简单语法使用。那么这两个关键字到底有什么意义呢?首先我们先说它们的最大区别其实就是这段内存的最高字节,我们究竟把它当成符号位还是当成数据位。那么有符号数,我们更多的时候是把它利用在一种数字的概念,比如说我们是把它作为加减乘除使用的这样一个标识或者这样一个物理意义的时候,那么我们就不加思索地把这样的空间当成有符号的数字来使用。而无符号数,更多的时候是指的是数据,什么是数据呢?就比如说我们摄像头采集或者是显卡产生的数据,那这样的数据我们都使用无符号数。
    在我们在实际工程开发的过程中呢,有些人可能没有进行区别,直接拿过来用, 好像代码没有什么太大问题,但是一旦我们出现了像我们嵌入式中经常会用到的位运算,涉及到的是一个右移操作。那么就会出现一些问题,我们来举个例子,我们定义一个char a =-1;让a>>1不断的右移,最终会不会变成0?如果是unsigned char会不会变成0?

    好,所以说呢,在我们的嵌入式开发中,我们无符号数的利用,更多的时候是面向于数据的采集和数据的申请,所以这点大家一定要清晰。比如说如果我们定义的这段内存空间它的意义更多的在于数据那么这个时候我们建议大家尽量的用unsigned去定义而不是偷懒直接写成int a,因为这样的话会导致我们后期大型程序维护的时候可能会出现一些稀奇古怪的问题啊,这样偷懒对于代码的可读性也不是太好,因为别人一看到unsigned立刻想到,哦,原来是你代表的是数据的意义。如果只是定义一个普通的char a那么我们就 认为你是一种带有数学意义的一个数字啊或有加正负号的数字的概念。

    最后我们再看这个float和double,
    我们在c语言中提供的两种浮点运算,所谓的浮点运算呢,我们认为它其实就是小数,前面呢我们给大家介绍的这几个关键字啊其实都代表是整数。那么什么是浮点数、什么是整数呢?浮点数和整数的重点不同在于它们在内存中的表达形式不一样,那你看比如说我们说整数0x10,那么我们都知道它在内存中表示的二进制是 0001 0000,就是十进制来的16,平时看到的内容和我们在内存中所表达的内容实际上是一致的。 但是如果现在有个数字1.0,那么是不是我们在内存中描述成是这样的就是0001 0000?这样描述的话那么这个岂不是就是16,这样肯定是不对的。我们说1.0这个浮点数要想在内存中描述,并不是我们想象的这么简单,至于为什么,暂时不需要理解,在这个地方我们现在只需要记住。浮点数 在内存中的表示形式跟我们整形是完全不一致的。那么要想把一段内存当成浮点数来使用,那么我们必须按照特殊的float或double的形式让我们编译器或让我们的cpu进行这样内存的一个处理。
    那么这个地方到底1.0怎样表示呢,我们不要求大家去记或者是了解啊,因为不同的计算机组成原理中它对于浮点数的具体表示方法是不一致的。
    double a=1.0;
    float b =1.0f;
    1.0在我们内存中是八个字节,所以说浮点数是非常耗内存空间的啊,所以说在某些场合下如果我们能不用小数呢就尽量不要用小数。那么当然用了小数过后,它有些的算法可能会更加精确。但是这个时候我觉得double已经太大了啊,没有必要搞得这么精度这么深啊,这个时候呢,我们还有一个方法就是1.0后面加上一个小f,那么这样的话呢就告诉我们编码器1.0不再使用八个字节而使用float就是四个字节。关于浮点数呢,我们不要求大家能够掌握得多么深,因为它其实就是一些算法中可能会用到,那么在我们实际开发中呢,可能涉及到的不会太多。

    最后一个数据类型,是void,这是一个称之为不限制内存大小的一个关键字。也就说void a那么这个实际上是没有什么意义的,因为这个a并没有告诉你它所管辖的空间的大小范围。所以这种写法呢 更多的时候是一种占位标志,它更多的意义是告诉我们申请一种变量名,那具体这个变量是什么。需要进行强制类型转换等等操做。void是声明标志而不是使用标志,比如你定义void a,再a++;这样写法是错误的。

    2.6自定义数据类型struct、union

    前面的讲解钟我们只会发现其实int等数据类型只是我们描述外界事物的一个最小单位。我们如果说想让c语言去描述更复杂的数据类型。光用基本数据类型肯定满足不了我们的要求。C语言又不可能全部把我们所需要的数据类型全部定义完,所以说给我们提供了一个新的概念叫自定义数据类型。那么自定义数据类型,我们重点放在前两个也就struct和union。那么这两个关键字应该是使用最频繁的。typedef我们不能称之为自定义数据类型我们可以称之为别名。这种别称更多的时候就有点像给别人起个外号而已啊,但这个外号呢肯定不是为了去取笑别人啊 ,而是一个让我们程序员或者说我们人和人之间交流的时候更有相应的交流意义而已,啊好这是我们关于自定义数据类型的一个定义,所以我们可以这样认为虽然编c语言编译器他的默认定义的数据类型的内存分配不符合实际资源的需求。所以我们通过基本元素的集合来定义我们的 新的数据结构。

    定义:
    struct myabc{
    unsigned int a;
    unsigned int b;
    unsigned int c;
    unsigned int d;
    };

    使用:
    struct myabc mybuf;

    比如我们操作一个原理图中的关于寄存器设置的表,那么这个寄存器描述组,这样一个资源组我们该如何定义呢?我们的想法当然是把整个表定义成一个对象,那么很显然我们就可以使用struct来定义,如上操作,需要注意的是因为是数据,而不是数学含义,所以用unsigned类型。

    那共用体呢,相对而言跟结构体呢它有一个本质的区别是什么呢,结构体是一个元素和一个元素之间递增的关系,就是说你定义完这个元素过后下一个元素一定是在这个元素的结尾往后增,也就是说他每一个变量的起始地址都是上一个结束地址。而共用体就是共用大家的起始地址然后申请的内存空间。所以这样的话呢 那么有什么意义呢,那么这个意义一般来说它并不代表我们去描述什么呢。去描述我们真实的物理 情况 ,因为我真实的物理情况是每一个方格应该有他自己的一套空间去维护它,因为一般的情况啊那么共同体更多的是一种技巧性的代码。 共同体呢我们不要求大家去灵活运运用啊,但是呢能够通过共用体这种思想慢慢去读啊或者说去理解在内核中或者是我们应用编程中他的一些技巧性的代码啊能够理解到就可以啊,所以这是我们说共同体的概念。

    定义:
    union myabc{
    unsigned char a;
    unsigned int b;
    };

    使用:
    union myabc mybuf;

    2.7自定义数据类型enum

    这个enum关键词是一个地位很低的关键词.
    不是强制符号,而是人与人之间交流用的集合的表示方法。

    是缩写enumerate 一一列举
    被命名的整型常数的集合
    #define MON 0
    #define TUE 1
    #define WED 2
    enum abc{ MOD = 0,TUE,WED }
    enum 枚举名称{ 常量列表 };
    enum week{
    Monday = 0 ,Tuesday =1 ,Wednesday = 2,
    Thursday,Friday,
    Saturday,Sunday
    };

    2.8自定义数据类型typedef

    含义:数据类型的别名

    int a =170;
    int b = 3600;
    以上写法,对于a和b的理解不太清楚。

    len_t a = 170;
    time_t b = 3600;
    以上写法,能猜出a和b的用法

    int a; a是一个int类型的变量
    typedef int a_t; a_t是一个int类型的外号

    2.9逻辑结构关键字

    熟悉语法就可,其他没什么好说的。

    swtich(整形数字)
    float a;
    switch(a){
    case 1.0:
    break;
    case 2.0:
    }

    goto在函数内部使用,应该问题不大。其他场景下,若非必然情况,尽量少用。

    2.10类型修饰符(一)_register

    类型修饰符其实就是对资源存放位置的一个限定

    auto int a; 就是普通的可读可写区域的int a
    register int a;
    限制变量定义在寄存器上的修饰符
    定义一些快速访问的变量
    但是并不绝对在寄存器上,编译器会尽量的安排CPU的寄存器去存放这个a,如果寄存器不足时,a还是放在存储器中。cpu的寄存器越多,cpu越强。
    &这个符号对register不起作用(取地址)

    内存(存储器) 寄存器
    0x100 R0,R2

    2.11类型修饰符(二)_static_const

    static应用场景:
    修饰3种数据:
    1)、函数内部的变量
    int fun()
    {
    int a; ===> static int a;
    }
    2)、函数外部的变量
    int a; ====> static int a;
    int fun()
    {
    }
    3)、函数的修饰符
    int fun(); ===> static int fun();

    extern外部申明,这里暂时不做说明,放到工程中看。

    const应该说c语言的一个软肋,也是c语言的一个中看不中用的关键字,有点像register。原本初衷是好的,希望定义一个不能更改的常量。但是最终是一个只读的变量。C语言考虑并没有特别深,就是他还站在内存思想来考虑的。我们还是可以通过某些方法----->>指针来改变它。那么其实我们在说到这里也再提一个新的概念叫内存泄漏。内存泄漏是非常恐怖的事情,甚至来说可能会得到一些意想不到的结果,这个意想不到应该是成为黑客的必经之路。

    2.12类型修饰符(三)_volatile

    它的操作对象是编译器,而不是内存。
    告知编译器编译方法的关键字,不优化编译
    修饰变量的值的修改,不仅仅可以通过软件,也可以通过其他方式(硬件外部的用户)

    c语言代码:
    int a = 100;
    while( a==100 );
    mylcd();

    汇编的伪代码:
    [a] : a的地址
    f1: LDR R0,[a]
    f2: CMP R0,#100
    f3: JMPeq f1 ----> JMPEQ f2(打开优化了)
    f4: mylcd();

    但是a是由外部改变的(比如外部键盘输入等等),这个时候肯定是不能优化的。

    3.C语言运算符操作

    			------算术操作运算符--(+、-)
    			-				   --(*、/、%)
    			- 			  	
    			-
    			-				--(||、&&)
    			-				--(>、>=、<、<=)
    			-------逻辑运算----(!)
    			-				--(?:)
    			-
    			-
    			-		       --(<<、>>)
      运算符----------位运算------(&、|)
    	        -			   --(^、~)
    	        -		       
    	        -
    	        -
    	        -					
    	        -					
    	        ------赋值运算---------(=)
    	        -					--(+=、-=、&=、...)
    	        -					
    	        - 					
    	        -                      --(())
    	        -                      --([])
    	        -------内存访问符号-------({})
    					               --(->、.)
                                       --(&、*)
    

    3.1常用运算符(一)_mod

    语法应该都知道。熟练运用括号就行。
    乘和除在大部分CPU中是不支持的。

    int a = b*10; CPU可能多个周期,甚至要利用软件的模拟方法去实现乘法
    int a = b+10; CPU 一个周期可以处理

    %:
    0 % 3 = 0 1%3 = 1 2%3=2 3%3=0 4%3=1 … …
    n % m = res [0 - m-1]
    使用场景:
    取一个范围的数:
    eg. 给一个任意的数字,得到一个1到100以内的数字?
    (m% 100)+1 ===> res;
    得到M进制的一个个位数
    循环数据结构的下标

    3.2常用运算符(二)_逻辑运算符

    非0为真,0为假
    返回结果就是1 0
    int a = -1;
    if(a)

    A || B ===不等价 B || A (A为真的时候B还执行吗?同理如下)
    A && B ===不等价 B && A

    3.3位运算符(一)移位运算符

    左移 : 乘法 2 二进制下的移位
    m<<1; m
    2
    m<<n; m2^n(2的n次方)
    4: 0 0 1 0 0
    8: 0 1 0 0 0
    int a = b
    32; ===> b<<5
    [数据、数字]
    -1 *2 =-2:
    8bit
    1 0 0 0 0 0 0 1 1 0 0 0 0 0 1 0
    1 1 1 1 1 1 1 0 符号位取反 1 1 1 1 1 1 0 1
    1 1 1 1 1 1 1 1 最后位加1,得到内存中的状态 ==== -1 1 1 1 1 1 1 1 0=-2

    左移对数据或者数字不影响。

    右移:除以2
    m >> n m/2^n
    符号变量有关
    int a; a>>n
    unsigned int a a>>n
    死循环:
    int a = xxx;
    while(a){
    a = a>>1;
    }
    printf("-------------\n");

    3.4位运算符(二)与或运算符

    & 、 |
    A & 0 ----> 0
    &作用1: 屏蔽
    int a = 0x1234;
    a & 0xff00; 屏蔽低8bit,取出高8bit
    A & 1 —> A
    &作用2:取出
    &:在硬件中是清零器 clr

    | :
    A | 0 === A
    保留
    A | 1 === 1
    设置为高电平的方法,设置set
    设置一个资源的bit5(即第6位)为高电平,其他位不变
    int a;
    a =a| 1 0 0 0 0 0
    即a=a| (0x1<<5) ========>>a|(0x1<<n)

    题目:清除第五位,其它位置不变。
    int a;
    a = a & 0 1 1 1 1 1 31 即是a & 31 但是31是int型,是32bit,前面的位数也要考虑到
    ~(0x1<<5)
    a = a & ~(0x1<<5) ==========a = a & (~(0x1<<n)

    我们想资源456bit设置为101?

    3.5位运算符(三)取反异或运算符

    ^ :相同位0,不同为一
    1 ^ 1 = 0 0 ^ 0 = 0
    1 ^ 0 = 1
    主要用于算法(加密算法等等) AES SHA1

    交换两个数:
    int fun()
    {
    int a = 20;
    int b = 30;
    int c; c = a;
    xxxx-----> a = b; b = c;

    }
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;

    ~取反要逐位取反,看系统是多少位。
    0xf0 ~ 0xffff ff0f

    3.6常用运算符(三)_内存访问符

    展开全文
  • C语言预处理命令

    2020-08-09 14:17:02
    首先三种形式的命令:宏定义,文件包含,条件编译命令 宏定义主要是:#define,#undef #define PI 3.1415926 /*不带参数的宏定义*/ #define Max(a,b) a>b?a:b /*带参数的宏定义*/ 下面举例说明定义宏...
  • 单片机c语言应用100例

    2016-06-08 11:13:38
    4.8.4实例41:条件编译应用举例 习题与实验 第5章单片机的定时器/计数器 5.1定时器,计数器的基本概念 5.2定时器/计数器的结构及工作原理 5.2.1定时器/计数器的结构 5.2.2定时器,计数器的工作原理 5.3定时器,...
  • C语言钻石段位测试题

    2020-05-23 16:27:06
    C语言钻石段位测试题 满分100分的测试题,你能做多少分?答案见链接。 常数123ul,123.45l中的ul、l代表什么含义?...如果编译时,不满足某些条件,用户如何向编译器抛出警告或者错误。(2分) 谈谈函数指针的用途
  • 谭浩强c语言程序设计

    2011-03-18 09:58:19
    9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • 谭浩强c语言word版

    2011-04-25 10:40:43
    9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • 9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • C语言程序设计.rar

    2009-09-28 13:44:30
    4.7.5 条件编译命令 70 4.7.6 #undef 72 4.7.7 #line 73 4.7.8 #pragma 73 4.7.9 预定义的宏名 73 4.7.10 注释 73 4.8 程序应用举例 74 第5章 数组 78 5.1 一维数组 78 5.1.1 向函数传递一维数组 78 5.1.2 字符串...
  • C语言程序设计(高清PDF)

    千次下载 热门讨论 2010-12-27 16:54:09
    4.7.5 条件编译命令 70 4.7.6 #undef 72 4.7.7 #line 73 4.7.8 #pragma 73 4.7.9 预定义的宏名 73 4.7.10 注释 73 4.8 程序应用举例 74 第5章 数组 78 5.1 一维数组 78 5.1.1 向函数传递一维数组 78 5.1.2 字符串...
  • 2.4.6 条件运算符和条件表达式 2.4.7 逗号运算符和逗号表达式 2.4.8 运算符的优先级 2.5 不同类型数据之间的转换 2.5.1 隐式转换 2.5.2 显式转换 2.6 数据的输入Input 2.6.1 使用scanf()函数实现数据输入 2.6.2 一个...
  • c语言编写单片机技巧

    2009-04-19 12:15:17
    MCU来说,其内部ROM、RAM、STACK等资源都有限,如果使用C语言编写,一条C语言指令编译后,会变成很多条机器码,很容易出现ROM空间不够、堆栈溢出等问题。而且一些单片机厂家也不一定能提供C编译器。而汇编语言,一...
  • 4.7.5 条件编译命令 70 4.7.6 #undef 72 4.7.7 #line 73 4.7.8 #pragma 73 4.7.9 预定义的宏名 73 4.7.10 注释 73 4.8 程序应用举例 74 第5章 数组 78 5.1 一维数组 78 5.1.1 向函数传递一维数组 78 5.1.2 字符串...
  • 9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • 谭浩强C语言设计第三版

    热门讨论 2010-11-03 05:11:03
    9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • 谭浩强c语言程序设计(第3版)电子书

    千次下载 热门讨论 2010-04-05 21:22:26
     9.4 条件编译 130  9.5 本章小结  10 指针  10.1 地址指针的基本概念 131  10.2 变量的指针和指向变量的指针变量 132  10.2.1 定义一个指针变量 133  10.2.2 指针变量的引用 133  10.2.3 指针变量作为函数...
  • 谭浩强c语言PDF版.pdf

    2010-04-27 01:40:26
    9.4 条件编译 130 9.5 本章小结 10 指针 10.1 地址指针的基本概念 131 10.2 变量的指针和指向变量的指针变量 132 10.2.1 定义一个指针变量 133 10.2.2 指针变量的引用 133 10.2.3 指针变量作为函数参数 137 10.2.4 ...
  • 内容简介: 本书从3个不同的角度深入浅出地向读者介绍了C语言的知识,帮助读者提高C程序...5.6.1 条件编译命令的一般形式 5.6.2 条件编译的应用 5.7 本章小结与 第2部分 C库函数 第3部分 经典C编程实例与常见试题解析
  • 9.4 条件编译 9 9.5 本章小结 11 10 指针 1 10.1 地址指针的基本概念 1 10.2 变量的指针和指向变量的指针变量 2 10.2.1 定义一个指针变量 3 10.2.2 指针变量的引用 3 10.2.3 指针变量作为函数参数 7 10.2.4 指针...
  • 23 <br/>9 预处理命令 1 9.1 概述 1 9.2 宏定义 1 9.2.1 无参宏定义 1 9.2.2 带参宏定义 4 9.3 文件包含 8 9.4 条件编译 9 9.5 本章小结 11 <br/>10 指针 1 10.1 地址指针...
  • 9.4 条件编译 9 9.5 本章小结 11 10 指针 1 10.1 地址指针的基本概念 1 10.2 变量的指针和指向变量的指针变量 2 10.2.1 定义一个指针变量 3 10.2.2 指针变量的引用 3 10.2.3 指针变量作为函数参数 7 10.2.4 指针变量...
  • 4.7.5 条件编译命令 70 4.7.6 #undef 72 4.7.7 #line 73 4.7.8 #pragma 73 4.7.9 预定义的宏名 73 4.7.10 注释 73 4.8 程序应用举例 74 第5章 数组 78 5.1 一维数组 78 5.1.1 向函数传递一维数组 78 5.1.2 字符串...
  • 程序流程设计C语句概述选择型程序设计if语句(条件选择语句)if语句的三种形式:语句嵌套switch语句(开关分支语句)循环型程序设计C语言可实现循环的语句:goto语句while语句do~while语句for语句循环的嵌套辅助控制...
  • (1) 条件判断语句  if语句,switch语句 (2) 循环执行语句  do while语句,while语句,for语句 (3) 转向语句  break语句,goto语句,continue语句,return语句 4.复合语句 把多个语句用括号{}括起来组成的一个...
  • 谭浩强C语言设计第三版.pdf

    热门讨论 2008-12-16 11:06:25
     3.3.4 条件表达式  3.4 循环型程序设计  3.4.1 迭代与穷举算法  3.4.2 while结构  3.4.3 dowhile结构  3.4.4 for结构  3.4.5 循环结构的中途退出与重复周期的中途结束  习题三 第4章 模块化程序设计  4.1 ...

空空如也

空空如也

1 2 3 4
收藏数 77
精华内容 30
关键字:

c语言条件编译举例

c语言 订阅