<<c专家编程>>笔记

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 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;
    }


    转载请注明出处

目录
相关文章
|
4月前
|
存储 Java 开发工具
探索安卓应用开发:从新手到专家的旅程
【8月更文挑战第30天】在数字时代,掌握安卓应用开发的技能不仅是职业发展的利器,也是个人技术成长的象征。本文将带你了解如何从零基础开始,逐步深入安卓开发的奥秘,最终成为一名能够独立开发高质量应用的专家。通过实际的代码示例和清晰的步骤指导,我们将一起构建你的第一个安卓应用程序,并探讨如何提升至更高水平。无论你是编程新手还是希望提高现有技能的开发者,这篇文章都将为你提供宝贵的知识和启发。
|
3月前
|
算法 前端开发 Java
从新手到专家:开发者的成长之路
软件开发充满挑战与机遇,从新手成长为专家是许多开发者的梦想。本文详细介绍了这一过程,包括基础知识学习与实践经验积累;持续技能提升与新技术探索;深入专研特定领域并分享知识;以及保持积极开放的心态面对挑战。成为专家需要时间与努力,但正确的路径与态度将助你一臂之力。
|
4月前
|
机器学习/深度学习 前端开发 程序员
探索编程之道:从小白到专家的旅程
【8月更文挑战第29天】本文将引导读者踏上一段从编程新手到资深开发者的旅程。我们将通过一个实际的项目案例,探讨如何从零开始学习编程,逐步掌握关键技能,并最终成长为一名能够独立解决复杂问题的专家。在这个过程中,我们将分享实用的学习资源、策略和心态调整方法,帮助读者在编程之路上不断前进。
|
2月前
|
人工智能 算法 大数据
技术之旅:从新手到专家的蜕变####
【10月更文挑战第20天】 本文探讨了个人在技术领域的成长历程,通过真实案例与深刻感悟,揭示了持续学习、实践积累与心态调整的重要性,为读者提供了一条从技术新手迈向专家的可行路径。 ####
33 8
|
3月前
|
算法 安全 小程序
编程之旅:从小白到专家的蜕变之路
【9月更文挑战第8天】本文是一篇个人技术成长历程的分享,通过作者的亲身经历,展示了一个编程新手如何逐步成长为一名技术专家。文章不仅包含了编程学习的心得体会,还探讨了持续学习、实践和社区参与对于技术提升的重要性。适合所有对编程感兴趣的读者,尤其是那些正在寻找学习路线和动力的初学者。
58 7
|
4月前
|
开发者
探索编程之旅:从新手到专家的心路历程
【8月更文挑战第15天】编程,一个充满挑战与创新的领域,吸引着无数人投身其中。本文将带你领略编程世界的奇妙之处,分享我从一名初学者逐步成长为资深开发者的心得体会。在这个过程中,我们不仅会探讨技术层面的成长,还会深入理解编程背后的哲学思考。无论你是刚开始接触编程的新手,还是已经有一定经验的开发者,这篇文章都会给你带来新的启示和思考。让我们一起踏上这段探索编程之旅,感受编程带来的无限可能。
|
5月前
|
算法 程序员 开发者
探索代码之美:从新手到专家的技术成长之旅
【7月更文挑战第12天】编程,一个充满逻辑与创造的奇妙世界。本文将带你走进程序员的内心世界,感受从初学者到资深开发者的成长历程。我们将一同探讨编程基础的重要性,学习如何通过实践和反思来提升技能,以及如何在技术日新月异的环境中保持持续学习和适应的能力。你将发现,编程不仅仅是一门技术,更是一种艺术,一种生活方式。
|
6月前
|
设计模式 存储 Swift
探索iOS应用开发:从新手到专家
【6月更文挑战第28天】在这篇文章中,我们将深入探讨iOS应用开发的各个方面。无论你是刚开始学习iOS开发的新手,还是已经有一定经验的开发者,这篇文章都将为你提供有价值的信息和指导。我们将从基础知识开始,逐步深入到更复杂的主题,包括设计模式、性能优化和最新的iOS技术趋势。通过这篇文章,你将获得从新手到专家的全面指导。
|
存储 搜索推荐 数据管理
码上公益低代码开发训练营 — 课时4:《低代码开发师中级认证课程》1-3章 企业管理系统综合实践
传统合同录入存在归档困难,查询繁琐耗时,审批效率低下等问题。钉钉宜搭使用关联表单组件以及高级流程设计的强大功能,使得合同管理在线化,贯穿合同管理全生命周期。本章将通过合同管理系统的实践掌握相关知识的使用。
码上公益低代码开发训练营 — 课时4:《低代码开发师中级认证课程》1-3章 企业管理系统综合实践
|
安全 小程序
在阿里云平台的学习与成长
创建网站的方法:服务器+域名+工具+源码
169 0