• 《c专家编程》学习笔记


    const 其实并不是真正的常量

     测试: const int two=2;
    switcj(i)
    {
    case 1:...
    case two:;;;;;;;;///产生编译错误,需求常量表达式
    default:
    }
    ================================================================================================================

    case语句至少有257个(ANSI C标准)。8bit+EOF  257个状态;

    ================================================================================================================
    switch-case语句中的break 语句到底中断哪里?
    答:break 语句事实上跳出的是最近的那层循环或switch语句。
    ================================================================================================================
    ANSI c引入的一个新特性是相邻的字符串常量呗自动合并成一个字符串的约定,这种合并带来另一种隐患就是字符串数组在初始化的时候,如果漏掉逗号,编译器不会付出错误信息,而是悄无声息的将字符串合并在一起。
    ================================================================================================================
    C中的符号重载
    static 在函数内部表示该变量在各个调用期间,一直保持延续性,用于函数时,表示该函数只对本文件可见。
    extern 用于函数定义表示全局可见,默认不带也是全局可见。用于变量时表示它在其他地方定义。

    void 作为函数返回类型表示不返回指。用于指针声明表示通用指针类型的;用于参数列表,表示木有参数。

    ================================================================================================================
    sizeof的操作数是一个类型名时必须加上括号,但是操作数是一个变量则不必加括号;
    p=sizeof*q等价于p=sizeof(*q)
    ================================================================================================================
    C语言运算符优先级问题:
    .的优先级高于*,带来的是->用于消除这个问题;例如:*p.f等价于*(p.f);
    []的的优先级高于*  例如:int *ap[]等价于 int *(ap[])表示一个元素为int指针的数组。
    函数()高于*例如:int *fp() 意思是fp是个函数,返回int指针;
    ==和!=高于位操作符:例如(val&mask!=0)等价于val&(mask!=0)
    ==和!=高于赋值符例如c=getchar()!=EOF等价于 c=(getchar()!=EOF)
    算术运算高于移位运算符 例如msb<<4+lsb 等价于msb<<(4+lsb)
    逗号运算符在所有运算符中运算级最低
    ================================================================================================================
    关于结合性的:i
    int a,b=1,c=2;
    a=b=c;
    所有的赋值符均有右结合性,也就是说,该表达式中从最右边操作开始执行,C先赋值给b,然后b在赋值给a,最终a的值是2.
    类似的,具有左结合性的操作符(&和|)则是从左至右依次执行。
    ================================================================================================================

    使用静态数组可以防止任何人修改这个数组,只有拥有指向该数组的指针的函数,才能修改这个静态数组,但是该函数的下次调用将覆盖这个数组的内容,所以调用者必须在此之前使用或者备份数组的内容。
    ================================================================================================================
    显示分配一些内容,保存返回的值,这个办法具有静态数组的优点,而且每次调用时都创建一个新的缓冲区,所以该函数以后的调用不会覆盖以前的返回值,使用多线程的代码,缺点在于程序员必须承担内存管理的责任。如果内存尚在使用就释放,或者出现“内存泄露”(不在使用的内存未回收),就会产生bug。
    ================================================================================================================
    最好的解决方法是要求调用者分配内存来保存函数的返回值。为了提高安全性,调用者应该同时制定缓冲区的大小。程序员就可以在同一个代码块中同时进行“malloc”和“free”操作。
    ================================================================================================================
    软件的经济规律显示:越是在开发周期的早期发现的Bug,修复它所付出的代价就越小。   ps:对于人生何尝不是如此,在年轻的时候能够早早的认识到自己犯的错误,并及时改正,或者能够在年长的人的提醒之下,及时改正那些年少轻狂,是一件多么幸运的事情。因为年轻的我们还有时间承担错误,敢于去尝试。珍惜每一个珍惜帮助过你的人。
    ================================================================================================================
    关于强制类型转换
    char (*j)[20]     //j是一个指向数组的指针,数组内有20个char元素
    j=(char(*)[20])malloc(20)  /* 如果去掉*两边的括号,代码非法
    涉及指针和const的声明可能会出现几种不同的顺序:
    const int *grape    /* 指针指向的对象是只读的*/
    int const *grape /*指针指向的对象是只读的 */
    int *const grape_jeily /* 指针是只读的*/
    =======================================================================================================================
    union一般用来节省空间,因为有些数据项是不可能同时出现的。放入大的结构体之中;
    union的另一个用法是,可以把同一个数据解释成两种不同的东西,而不是把两个不同的数据解释成为同一种东西;
    例如
    union bits32_tag{
    int whole;                /*一个32位的值*/
    struct {char c0,c1,c2,c3;} byte;/*4个8位的值*/
    }
    ============================================================================================
    实际中结构出现的次数远远多于联合,实际中工作,遇见结构的次数将远远多于联合
    =====================================================================================================
    枚举类型 :
        enum sizes { small =7,medium,large=10,very_large};
    ||
    811
    enum可以用#define来代替,枚举类型有一个优点:#define定义的名字一般在编译时呗丢弃,而枚举名字测常在调试器中课件,可以再调试代码是使用它们;
    ====================================================================================================================================char * const*(*next)(); 含义:next是一个函数指针,该函数返回一个char型的常量指针;
    ====================================================================================================================================
    void (*signal(int sig,void(*func)(int)))(int);
    signal是一个函数,此函数返回一个指针,此函数接受两个参数 一个int sig,一个func指示的返回类型为void的函数(形参为int),signal返回的函数指针指向的函数返回类型为void,其形参为int型;
    =================================================================================================================================
    typedef和define的区别
    1,可以用其他类型说明符对哄类型名扩展,但对typedef所定义的类型名却不能这样做
    例如
    #define peach int
    unsigned peach i;    /*no problem*/
    typedef int banana;
    unsigned banana i;/*错误,非法!*/
    2,在连续几个变量的生命,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证
    例如
    #define int_ptr int *
    int_ptr chalk,cheese;
    经过宏扩展,第二行变为
    int *chalk,cheeese; /*chalk是一个指向int的指针,而cheese是一个int */
    typedef char* char_ptr;
    char_ptr Bentley,Eolls_ROyce;/*两个类型相同都是char 指针*/
    ================================================================================================================
    关于动态链接库和静态链接库,如何映射到进程的虚拟地址里的问题还得在仔细探究一下。
    关于unix like 系统的脚本,编译器,环境变量的设置还得加深。
    如何在lib库函数里面查找自己所需要的环境变量的问题还得在学习。
    =============================================================================================================
    准则:不要让程序中的任何符号成为全局的,除非有意让它们成为程序的接口之一。
    C语言的设计哲学:程序员所做的都是对的。编译器也认为这是程序员的意图。
    ======================================================================================
    ld程序是一个复杂的程序,很多选项和约定问题:使用ldd命令列出可执行文件的动态依赖集,这条命令会告诉你动态链接的程序所需要的函数库。
      备注:自学关于ld程序的在线文档
    ============================================================================================================
    在一个可执行文件中运行四则命令时,它会告诉你这个文件的三个段(文本段,数据段,bss段)的大小
    检查可执行文件内容的另一种看法是使用nm或者dump工具


    ============================================================================================================

    BSS  是“Block Started by Symbol”的缩写,意为“以符号开始的块”。

      BSS是Unix链接器产生的未初始化数据段。其他的段分别是包含程序代码的“text”段和包含已初始化数据的“data”段。BSS段的变量只有名称和大小却没有值。此名后来被许多文件格式使用,包括PE。“以符号开始的块”指的是编译器处理未初始化数据的地方。BSS节不包含任何数据,只是简单的维护开始和结束的地址,以便内存区能在运行时被有效地清零。BSS节在应用程序的二进制映象文件中并不存在。
      在采用段式内存管理的架构中(比如intel的80x86系统),bss段(Block Started by Symbol segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域,一般在初始化时bss 段部分将会清零。bss段属于静态内存分配,即程序一开始就将其清零了。
      比如,在C语言之类的程序编译完成之后,已初始化的全局变量保存在.data 段中,未初始化的或初始化为0全局变量保存在.bss 段中。
      text和data段都在可执行文件中(在嵌入式系统里一般是固化在镜像文件中),由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。


    ====================================================================================
    数据段保存在目标文件中;
    BSS段不保存在目标文件中,除了记录BSS段在运行时所需的大小;
    文本段是最容易受优化措施影响的段
    a.out文件的大小受调试状态下编译的影响,但段不受影响。(不太懂到底什么意思)
    ======================================================================================
    堆栈段主要的用途,其中两个跟函数有关,另一个跟表达式运算有关
    1、堆栈为函数内部使用的局部变量提供存储空间。
    2、进行函数调用时堆栈存储于此有关的一些维护信息。比如函数调用地址,任何不适合装入寄存器的参数等等
    3、堆栈可以用作暂时存储区。有时候程序需要一些临时存储,如计算很长的表达式时候,将部分计算结果压到堆栈之中。
    ======================================================================================
    在磁盘中有一个特殊的“交换区”,用于保存从内存中换出的进程。在一台机器中,交换区的大小一般是物理内存的几倍。只有用户进程才会换进换出,OS内核常驻于内存之中。
    ======================================================================================
    calloc函数与malloc类似,但他在返回指针之前先把分配好的内容都清为零。
    realloc函数改变一个指针所指向的内存块的大小,可扩大,可缩小。它经常讲内存拷贝到别的地方然后将指向新地址的指针返回给你。
    ======================================================================================
    有意思的图形显示:
    在C语言中,典型的16*16的黑白图形显示如下:
    unsigned short myarray[]={	0x07C6,	0x1FF7,	0x383B,	0x600C,	0x600C,	0xC0D6,	0xC0D6,	0xDF06,	0xC106,	0xC106,	0x610C,	0x610C,	0x3838,	0x1FFC,	0x07C0,	0xDD00 };

    这些C语言常量并未提供有关图形实际摸样的任何线索;将十六进制转化为二进制显示;之后
    #include <stdio.h>
    
     unsigned short myarray[]={	0x07C6,	0x1FF7,	0x383B,	0x600C,	0x600C,	0xC0D6,	0xC0D6,	0xDF06,	0xC106,	0xC106,	0x610C,	0x610C,	0x3838,	0x1FFC,	0x07C0,	0xDD00 };
    
    void convent(unsigned short a)
    {
    	unsigned int c,d[32]={0};
    	int i=0;
    
    	while(a!=0)
    	{
    		c=a%2;
    		d[i]=c;
    		a=a/2;
    
    		i++;
    	}
    //	printf("%d",i);
    		i=31;
    	while(i>=0)
    	{
    		printf("%d",d[i]);
    		i--;
    	}
    	printf("\n");
    }
    int main()
    {
    	for (int i=0;i<16;i++)
    	{
    		convent(myarray[i]);
    	}
    }


    可以看到显示字幕G;
    这里有一个#define定义的优雅集合,允许程序建立常量使它们看上去更像是屏幕的图形,/定义了它们之后,只要所画的图标,程序会自动创建它们的十六进制模式,使用这些宏定义,程序的自描述能力大大增强;
    #define _ )*2
    #define X )*2+1
    #define S (((((((((((((((( 0
    		
    		unsigned int myarray[]={
    			S _ _ _ _ _ X X X X X _ _ _ X X _ ,
    			S _ _ _ X X X X X X X X X _ X X X,
    			S _ _ X X X _ _ _ _ _ X X X _ X X,
    			S _ X X _ _ _ _ _ _ _ _ _ X X _ _,
    			S _ X X _ _ _ _ _ _ _ _ _ X X _ _,
    			S X X _ _ _ _ _ _ _ _ _ _ _ X X _,
    			S X X _ _ _ _ _ _ _ _ _ _ _ X X _,
    			S X X _ X X X X X _ _ _ _ _ X X _,
    			S X X _ _ _ _ _ X _ _ _ _ _ X X _,
    			S X X _ _ _ _ _ X _ _ _ _ _ X X _,
    			S _ X X _ _ _ _ X _ _ _ _ _ X X _,
    			S _ X X _ _ _ _ X _ _ _ _ _ X X _,
    			S _ X X X _ _ _ X _ _ _ X X X _ _,
    			S _ _ _ X X X X X X X X X X _ _ _,
    			S _ _ _ _ _ X X X X X _ _ _ _ _ _,
    			S _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
    	};


    利用宏和十六进制转为二进制的数学方法,只要定义所画的图标,程序会自动创建它们的十六进制模式,使用这些宏定义,程序的自描述能力大大增强;
    ======================================================================================
    整型提升就是char ,short int和为段类型,以及枚举类型将被提升为int,前提是int能够完整容纳原先数据,否则被转换为 unsigned int。
    另一个发生隐式类型转换的地方就是参数传递。比如 printf函数中 %d能适用于不同的类型,short,char,int,不论实际传递的是上述类型的哪一个,函数从堆栈中取出的参数总是int。
    把所有的操作数转换为统一的长度,极大的简化了代码的生成,这样压到堆栈里的参数都是同一长度的,所以运行时系统只需知道参数的数目,而不需要知道他们的长度。
    =====================================================================================
    函数原型的建立是为了消除一种普通(但是很难发现的)错误,即形参和实参之间类型不匹配,从而是C语言成为一种更加可靠的语言。
    ======================================================================================
    在C中,有好几种方法来表达FSM,但他们绝大对数都是基于函数指针数组。
    示例:
    extern int a(),b(),c(),d();
    int (*stato[])={a,b,c,d};
    可以通过数组的中的指针来调用函数;(*stato[i])();
    弊端:所有的的函数必须接受同样的参数,并返回同种类型的返回值(除非把数组元素做成一个联合。)
    ===================================================================================
    绝大多数调试器都允许从调试器命令行调用寒素,如果拥有十分复杂的数据结构时,他将会非常有用,可以编写并编译一个函数,用于遍历整个数据结构,并把它打印出来.------------------------备注:没用过,找机会试试;
    ==================================================================================
    不要把增量开发(incremental development)和“显式代码调试”(debugging code into existence)混为一谈。
    显式代码调试是程序员新手或者开发时间非常紧的程序员常采用的一种技巧,显式代码调试就是首先匆忙编写一个程序框架,然后在接下来的几周的时间里通过修改无法运行的部分对程序进行连续的完善,知道程序能够工作,同时任何人如果要依赖系统组件,,则会拖拉项目进度;
    可调式性编码意味着把系统分成几个部分先让程序总体结构运行,只有基本程序的能够运行之后,,才为那些复杂的细节完善,性能的调整和算法优化进行编码;
    =====================================================================================
    华丽的散列表(hash)
    hash表是一种广泛使用和严格测试过的表的查找优化的方法,在系统编程中到处有它的踪影:数据库,操作系统,和编译器;




    (未完 p192)












  • 相关阅读:
    弹窗拖拽组件开发应用
    高级事件的运用
    常见排序算法(JS版)
    原生js实现仿window10系统日历效果
    原生js实现吸顶导航和回到顶部特效
    OVN实战---《The OVN Gateway Router》翻译
    OVN实战---《An Introduction to OVN Routing》翻译
    OVN实战---《A Primer on OVN》翻译
    深入理解CNI
    《CNI specification》翻译
  • 原文地址:https://www.cnblogs.com/HuaiNianCiSheng/p/5303277.html
Copyright © 2020-2023  润新知