<<c专家编程>>笔记

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: C专家编程摘录c操作符的优先级有时一些c操作符有时并不会像你想象的那样工作。下方表格将说明这个问题:优先级问题表达式期望的情况实际情况. 优先级高于**p.f(*p).

C专家编程摘录

  • c操作符的优先级

    有时一些c操作符有时并不会像你想象的那样工作。
    下方表格将说明这个问题:
优先级问题 表达式 期望的情况 实际情况
. 优先级高于* *p.f (*p).f *(p.f)
[ ]优先级高于* int *ap[ ] int (*ap)[ ] int *(ap[ ])
函数()优先级高于* int *fp() int (*fp)(), fp是 int型函数的指针 int *(fp( )),fp是返回int型指针的函数
==和!=优先级高于位操作 (val & mask != 0) (val &mask) != 0 val & (mask != 0)
==和!=优先级高于= c=getchar() != EOF (c=getchar()) != EOF c=(getchar() != EOF)
算数运算符高于位移运算符 msb << 4 + lsb (msb << 4) + lsb msb << (4 + lsb)
, 优先级最低 i = 1,2; i = (1,2); (i = 1),2;

对于上表做出补充:

1.表中第二条。指向数组的指针(ptr to array of ints)和指针数组(array of ptrs-to-int)的区别.
指向数组的指针可以认为是二维数组,即int (*ap)[ ] 等价于 int ap[ ][ ].ap指针及每个ap(即ap+i)偏移指针指向一个一维数组(一串数)。
而指针数组是由连续多个int指针组成的数组,每个数组元素指向一个int 变量。
对于指针数组要多提一点,虽然声明一个指针数组并不会被编译器报错,但是这是不安全的。 首先考虑这样一种情况,声明一个int 数组

int Na[5];  

Na数组中的5个值都是随机值。当数组元素为指针时,情况也是相同的,即申请了一堆野指针。这将会导致运行时错误。
实测图:
img_d24df870807e2d3d6e15a85277d865cd.png
可以看到,a[1]是一个空指针(nil),当用户使用这个指针指向的内存时,将会导致程序异常终止。

2. 一个非常妥善的对策是使用小括号将表达式包含起来(小括号优先级很高)。




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

  • c操作符的结合性

    每个操作符都有优先级和“左”或“右”的结合性。当操作符优先级不同时,求值顺序取决于操作符优先级,而当操作符优先级相同时,这时将使用结合性。
    1.除“位与”和“位或”外的所有操作符都遵守从右到左的结合性。(前者从左到右)
    2.结合性目的在于表达式中操作符优先级相同情况下给出标准的运算顺序。
    3.当操作符的优先级和结合性组合情况较为复杂时,可以采用拆分表达式或者使用小括号。

eg:
int a,b=1,c=2;
a = b = c;

上述两行代码a,b,c结果为: a = 2,b = 2,c = 2;




---

  • c中的空格

    c中的空格并非人们普遍认为的那样不重要,可以随意增加或者减少。有时候空格会在根本上改变程序的原意。


    1.反斜杠可以用来跳过一些字符,包括跳过一个新行(跳过回车符)。一个被反斜杠跳过回车的新行和他的上方未结束的行被认为是逻辑上的一行。但是如果上述情况下在反斜杠后面多打了一个空格(space),情况就不一样了。(反斜杠跳过了空格,而不是回车)。
    如图:
    img_0eb6f4cf47fa6bb5783edeb1da834640.png



    2.现在这个问题主要由于ANSI 标准c中的“最大一口策略(maximal munch strategy)”。 考虑下述代码:

int z = y+++x;
z = y + ++x;?还是 z = y++ + x;?

根据最大一口策略,答案是前者。
(注:最大一口策略:如果表达式下一个符号(token)有超过一种语义的可能性,编译器将更愿意咬掉更长的字符序列)
考虑下述代码:
int z = y+++++x;
编译器如何解析?根据maximal munch strategy,结果为 :
z = y++ ++ +x;
但不妙的是,编译器不认识“++”,所以程序编译错误。


3.第三种情况发生在类似这样的一行代码中:
int ratio = *x/*y;
编译器如何处理这条语句?
结果如图:
img_a1f9afba619aa89655d85b65a3b0af61.png

即“/*”被认为是一段注释的开始,与我们期望的大相径庭。而15行在加入空格(space)后则解决了这个问题。


4.其他情况(上述三种情况并不代表全部)。



----

  • C声明中的优先级规则

    c语言的声明表达对于编译器来说并没什么难度去解析,但对于学习C语言的程序员来说这种复杂性会造成一定的困扰。
    举个复杂声明的例子:
    char* const (next)();
    下面介绍几个较为有用的运算符优先级,并提供一个系统的面向于人的解析c复杂声明的方法,最后附加一个c语言小程序,用于实现一个声明的自动解析。   



    1.较为有用的优先级  

    声明从名字开始解析,然后按照优先级顺序继续读取下一个词汇单元。

    优先级顺序:(从高到低)
    1、小括号包含起来的声明。
    2、后继操作符“()”表示一个函数,后继操作符“[]”表示一个数组。
    3、前驱操作符“*”表示一个“指向...的指针”。
    4、如果const或volatile在类型标识符(e.g.int,long,etc)旁,则它与类型标识符匹配。否则与它直接的左边的星号匹配。


    2.系统的方法

    处理顺序:
     对于整个表达式,采取从右向左的顺序逐次擦除掉记号(token)。当所有记号被擦除,声明的解读过程也就完成了。
         <--------
    cha* const *(*next)();
    
    step1:找到最左端的标识符;
    读作:“标识符 是...”;
    
    step2:如果右边下一个记号是方括号“[possible-size]”;
    读作:“...的数组”;
    
    step3:如果右边下一个记号是一个开放的小括号“(possible-parameter)”;
    处理:一直读到最近的右小括号匹配。
    读作:”返回...的函数“;
    
    step4:如果记号是左小括号 “(”;
    处理:这个记号是当一个开放的小括号内记号被处理完后向左查询到的。这时左右小括号里的记号已经被擦除,继续读取右到小括号匹配,擦除,返回step2;
    
    step5:如果左记号是"const,volatile,*"三者中的一者
    处理:继续向左查询记号,知道不再出现这三者中任何一个记号为止。从step4重新开始。
    读作:const:“常...”, volatile: “非常...”,* : “...指针”;
    
    step6:当记号是基础的类型说明符时。
    处理:结束
    读作:相应类型名。

    下面以一个例子帮助理解:
    (处理中的记号使用粗体)

待处理声明 下一步处理 结果
char* const *(*next) ( ) step1 称"next是...”
char * const *(*  ) ( ) step2,step3 右小括号不匹配,下一步
char *const *(*    ) ( ) step4 *不匹配,下一步
char *const *( *   ) ( ) step5 ”*“ 匹配,称”...的指针“,应用step4
char *const *(    ) ( ) step4 "(" 和 “)”匹配,应用step2
char *const *    ( ) step2 不匹配,下一步
char *const *      ( ) step3 称”返回...的函数“
char *const * step4 不匹配,下一步
char *const * step5 称”...的指针“
char *const step5 称”常...“
char * step5 称”...的指针“
char step6 称“字符类型”

最后连在一起表达:next是一个指向返回指向常字符指针的函数的指针。:)  

  • c中的指针和数组的关系

    首先,如果你认为在c中指针和数组是等价的,那么你很有必要向下看。

    数组和指针本质是不同的,数组名代表的是一个地址,可以认为是一个只读的整数(const int),而指针是一个地址的地址(一个保存地址的变量)。但是由于在很多情况下,数组可以转换为指向数组首元素地址的指针,只不过这种转换是隐式的,但正因如此,才造成了我们的困惑。
    在下面三种情况下,数组将转换为指针:
    1.数组作为函数的参数,将以指针的形式传递。
    eg:
    void foo(int *arr) { ... }

    main {
    int arr[...];
    foo(a);
    }

    2.数组作为表达式一部份参与求值。

    eg:
    int c = a[i];
    //隐式转换:
    int c = (a+i); //此时a是一个指针

    3.使用下标的数组名将会被转换成一个指针加上偏移量。
    eg:
    a[i] --->
    (a+i) ;

    注意:指针不会转换成数组。
    附:简单的c语言声明语义解析程序

    点击此行显示代码



    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <ctype.h>
    #define MAXTOKEN 100
    #define MAXTOKENLEN 64
    #define pop stack[top--]
    #define push(s) stack[++top]=s;
    typedef enum type_taag { IDENTIFIER,QUALIFIER,TYPE} tyag;
    struct token
    {
    char type;
    char string[MAXTOKENLEN];
    };
    int top = -1;
    struct token stack[MAXTOKEN]; //save tokens as a structure contians string
    struct token this_; //global structure contains string and type
    tyag classify_string(void)
    {
    char s = this_.string;
    if(!strcmp(s,"const")) {
    strcpy(s,"read only ");
    return QUALIFIER;
    }
    if(!strcmp(s,"volatitle")) return QUALIFIER;
    if(!strcmp(s,"signed")) return TYPE;
    if(!strcmp(s,"char")) return TYPE;
    if(!strcmp(s,"unsigned")) return TYPE;
    if(!strcmp(s,"short")) return TYPE;
    if(!strcmp(s,"int")) return TYPE;
    if(!strcmp(s,"long")) return TYPE;
    if(!strcmp(s,"float")) return TYPE;
    if(!strcmp(s,"double")) return TYPE;
    if(!strcmp(s,"struct"))return TYPE;
    if(!strcmp(s,"union")) return TYPE;
    if(!strcmp(s,"enum")) return TYPE;
    return IDENTIFIER;
    }
    void gettoken(void)
    {
    char
    p = this_.string;
    while((p = getchar()) == ' ') ;
    if(isalnum(
    p)) { // A-z, 0-9
    while(isalnum(++p = getchar())) ;
    ungetc(
    p,stdin);
    p = '\0';
    this_.type = classify_string();
    return ;
    }
    if(
    p == '') {
    strcpy(this_.string,"pointer to");
    this_.type = '
    ';
    return ;
    }
    this_.string[1] = '\0'; //'['']'(')'
    this_.type = p;
    return ;
    }
    void read_to_first_identifier(void)
    {
    gettoken();
    while(this_.type != IDENTIFIER) {
    push(this_);
    gettoken();
    }
    printf("%s is ", this_.string);
    gettoken(); //read next token after identifier
    }
    void deal_with_array(void)
    {
    while(this_.type == '[') {
    printf("array ");
    gettoken(); //read a number or ']'
    if(isdigit(this_.string[0])) {
    printf("0...%d ",atoi(this_.string)-1);
    gettoken(); //eat the ']'
    }
    gettoken(); //get next token past ']'
    printf("of ");
    }
    }
    void deal_with_function(void)
    {
    while(this_.type != ')') {
    gettoken(); //ignore the parameters of function
    }
    gettoken(); //eat the ')'
    printf("function returnning ");
    }
    void deal_with_pointer(void)
    {
    while(stack[top].type == '
    ') {
    printf("%s ", pop.string);
    }
    }
    void deal_with_declarator(void)
    {
    switch(this_.type) {
    case'[':deal_with_array();break;
    case'(':deal_with_function();break;
    }
    deal_with_pointer();
    //process the tokens which still in the stack
    while(top >= 0)
    {
    if(stack[top].type == '(') {
    pop;
    gettoken();
    deal_with_declarator();
    }
    else
    printf("%s ", pop.string);
    }
    }
    int main(void)
    {
    read_to_first_identifier();
    deal_with_declarator();
    printf("\n");
    return 0;
    }


    转载请注明出处

目录
相关文章
|
2月前
|
人工智能 算法 大数据
技术之旅:从新手到专家的蜕变####
【10月更文挑战第20天】 本文探讨了个人在技术领域的成长历程,通过真实案例与深刻感悟,揭示了持续学习、实践积累与心态调整的重要性,为读者提供了一条从技术新手迈向专家的可行路径。 ####
33 8
|
4月前
|
数据采集 JavaScript 前端开发
代码之旅:从小白到专家的蜕变之路
在数字时代的浪潮中,编程技能成为了一项宝贵的资产。本文将带领读者回顾我从一位对代码一无所知的新手,逐步成长为能够独立解决问题的专家级程序员的过程。通过分享我的学习经历、遇到的挑战以及克服困难的策略,旨在为同样渴望在编程世界中留下自己足迹的朋友们提供一份实用的指南和鼓励。
38 0
|
4月前
|
程序员 开发者
技术成长之旅:从新手到专家的蜕变之路
【8月更文挑战第8天】在技术的海洋中,每一位程序员都曾是一名初学者。本文将通过个人经历和行业观察,探讨如何从技术新手成长为领域专家的过程。我们将深入讨论持续学习、实践、反思、交流与创新的重要性,并分享一些实用的成长策略。无论你是刚刚起步的技术爱好者,还是正在追求更高成就的资深开发者,这篇文章都将为你提供宝贵的启示和指导。
|
项目管理
PMP备考之路 - 汪博士第九章(项目资源管理)(下)
PMP备考之路 - 汪博士第九章(项目资源管理)(下)
62 1
|
监控 数据挖掘 项目管理
PMP备考之路 - 汪博士第九章(项目资源管理)(上)
PMP备考之路 - 汪博士第九章(项目资源管理)
83 1
|
持续交付 项目管理
PMP备考之路 - 敏捷实践第二讲(敏捷概述)
PMP备考之路 - 敏捷实践第二讲(敏捷概述)
82 0
|
存储 搜索推荐 数据管理
码上公益低代码开发训练营 — 课时4:《低代码开发师中级认证课程》1-3章 企业管理系统综合实践
传统合同录入存在归档困难,查询繁琐耗时,审批效率低下等问题。钉钉宜搭使用关联表单组件以及高级流程设计的强大功能,使得合同管理在线化,贯穿合同管理全生命周期。本章将通过合同管理系统的实践掌握相关知识的使用。
码上公益低代码开发训练营 — 课时4:《低代码开发师中级认证课程》1-3章 企业管理系统综合实践
|
数据可视化 数据管理 BI
码上公益低代码开发训练营 — 课时3:《低代码开发师初级认证课程》第4章 招聘管理系统综合实践
通过上一章节--请假申请实践案例的学习,我们已掌握了低代码平台的基础功能,能够使用表单、流程和报表从空白搭建应用。本章课程将通过一个完整的招聘管理系统的案例,学习如何将Excel的招聘管理表格升级成招聘管理系统,并掌握关联表单组件和节点提交规则的进阶功能,同时具备初步的系统需求分析的思维能力。
码上公益低代码开发训练营 — 课时3:《低代码开发师初级认证课程》第4章 招聘管理系统综合实践
|
数据挖掘 BI 项目管理
第二期“码上公益营·低代码挑战”评审结果通知
感谢各位爱心极客与公益组织的共同努力,第二期“码上公益营·低代码挑战”活动取得了丰硕的成果,经小组展示以及三方测评,现公布评审结果。
479 0
第二期“码上公益营·低代码挑战”评审结果通知
|
运维 Kubernetes Cloud Native
技术人如何成长?阿里的专家给出了这些建议 | 开发者必读(176期)
如何走出焦虑,在工作中快速成长?关于高效沟通和认知升级这些事,看看阿里的专家都怎么说~
1454 0
下一篇
DataWorks