zoukankan      html  css  js  c++  java
  • C语言学习笔记

    更新:因为开始写PA2了,因此决定重回这个坑,顺便记录一下解决一些C问题的方法,不一定和语法相关


    现在才知道 oi 用的是 C-with-STL... 感觉自己对这个看起来很单纯的语言仍然不够了解

    打算仔仔细细地写一写自己不太会的东西,包括与 C++ 不同的语法、宏(macro)的技巧、指针的技巧等等

    语法

    enum

    可以利用enum的特点来引入最大下标开数组

    enum {PC,R1,R2,...,R7,R_SIZE};
    
    

    那么这里的R_SIZE天然就是最大下标

    数组初始化

    简单的就不说了。通常会有这样的情形:我们需要一个lookup-table,并且希望这是const的

    dom的范围很大,但是实际上有用的lookup项很少(比如说为每个二元运算分配一个优先级)
    那么就可以这么写

    enum TOKENS {
    	// blah blah blah....
    } ;
    
    // priority for _binary operators_
    static const int priority[512] = {
      [TK_OR] = 4,
      
      [TK_AND] = 5,
      
      [TK_NEQ] = 9,
      [TK_EQ] = 9,
      
      [TK_LEQ] = 10,
      [TK_GEQ] = 10,
      [TK_LT] = 10,
      [TK_GT] = 10,
      
      ['+'] = 12,
      ['-'] = 12,
      
      ['*'] = 13,
      ['/'] = 13,
      ['%'] = 13,
    };
    

    struct

    struct里面不能有函数(好严格..)

    C语言中的结构体有两种定义方式

    struct MyStruct {
          int tmp;
    } ;
    
    struct MyStruct a;
    

    或者可以

    typedef struct tmpMyStruct {
        tmpMyStruct *p;
        int tmp;
    } MyStruct;
    
    MyStruct a;
    

    这两者的区别在于下面的方法把struct定义为一个类型,并且引入了一个中间名字tmpStruct,这使得我们可以在结构体中定义结构体变量(考虑一个指针节点的组成)

    malloc/free

    C++用的更多的是newdelete,C里面用的是mallocfree
    假设有这么一个

    typedef struct tmpNode {
          struct tmpNode *next;
          int value;
    } Node;
    
    Node *head;
    

    我们要给head指针分配内存空间,用的就是

    head = (Node*) malloc(sizeof(Node));
    

    这个可以类比给Nodenew了一个对象,不带初始化函数
    这里分配的内存不包括结构体内部指针指向的地址的内存,这个还需要在自己写的初始化函数里再malloc一次

    一个我常用的小技巧是把初始化函数自己实现一遍,返回一个对象的指针

    类似的,在free的时候需要自己实现对象的析构:比如说free一棵红黑树,比如说free一个链表。通常递归free掉所有节点就够了

    const 数组下标

    C语言中const修饰的int变量不能定义数组大小,因为这是一个只读变量而不是常量,通常我们会用#define

    bool

    C里面的bool是在C99以后才有的,如果要用true和false需要引用stdbool.h,或者自己define

    逗号表达式

    逗号的优先级最低,一段逗号表达式按顺序从左到右求值,一整段逗号表达式的值由最右边的式子决定
    比如说(a = 3, a *= 2)的值就是6

    static静态变量

    分为全局静态变量和局部静态变量。静态变量放在.bss.data字段中

    全局静态变量在链接的时候不能被其他.o文件引用,可以看成是一种private封装
    局部静态变量不在栈上,作用域和局部变量一致,生命周期却和全局变量一致

    volatile关键字

    这个是看CSAPP学到的姿势

    对于多线程共同访问一个全局变量的情况,可以使用volatile来告诉编译器对于该变量只从内存中取值

    这可以防止编译器过度优化使得无锁并行的程序出错

    macro

    未定义macro的初值

    考虑如下代码

    #include <stdio.h>
    int main(void) {
    	#if aa==bb
    		puts("YES");
    	#else
    		puts("NO");
    	#endif
    	return 0;
    }
    

    输出是YES
    原因在于未定义的macro默认值都是0

    同时 #if 后跟着的表达式必须是常亮表达式。很好理解,因为是编译期行为

    #ifndef

    在多文件编程的时候我们会include若干.h文件。.h文件的include原理就是复制粘贴,因此如果多次include会出现奇奇怪怪的错
    所以我们需要在.h前后加上

    #ifndef _SOMETHINGSPECIAL_
    #define _SOMETHINGSPECIAL_
    // do something ...
    #endif
    

    其中_SOMETHINGSPECIAL_是不同头文件不同的一个变量名,这个东西的意思是如果没有定义过这个宏就执行内部内容,否则就跳过。而内部代码定义了这样一个宏,就保证了只会被执行一次(即使被include多次)

    stringify和concatenation

    假设要实现floatint的最大值函数

    #define CONCAT_TMP(X, Y) X ## Y
    #define CONCAT(X, Y) CONCAT_TMP(X, Y)
    
    #define DEF_MAX 
    	int		CONCAT(max_, int)(int x,  int y) { return x > y ? x : y;} 
    	float	CONCAT(max_, float)(int x,int y) { return x > y ? x : y;}
    

    多行macro&&任意参数

    很多时候一行宏定义不够用,于是我们就可以通过在行末加''符号定义多行macro
    如果出现了参数不明确但是操作一致的情况,我们还可以在定义的时候采用...作为形参,使用的时候用__VA_ARGS__这个宏

    #define IS_DEBUG true
    #define DEBUG(...) {
    if (IS_DEBUG) 
    printf(__VA_ARGS__);
    }
    
    

    while(0)

    一个很怪的用法,大概可以写成下面这样:

    #define CHECK(EXP) do {
      if (EXP) printf("WARNING! " #EXP " CHECK FAILED!
    "); } while (0)
    

    这个宏函数会检查一个表达式EXP,但是这里把主体语句用一个do while(0)括起来了
    好处大概有这么几个:

    1. 可以包裹多条语句,在展开之后可以保证这些语句在一个大括号内,这样可以保证操作对于if then else的整体性
    2. (新增)这样做的话宏的内部相对独立,就可以随便开临时变量了

    X-macro

    这个用于解决相关联的表项数据分布于不同文件时,如何方便修改的问题

    一个比较烂的的例子是抄来的:

    enum color {RED,GREEN,BLUE};
    char *str[] = {"RED","GREEN","BLUE"};
    

    实际代码中可能不止两处,可能相隔很远

    现在如果要在红色和绿色之间加入黄色,那么所有硬编码的地方都需要修改

    所谓 X macro就是这样一类解决多处硬编码的修改问题

    #define COLOR(X) 
    	X("RED",RED) 
    	X("BLUE",BLUE) 
    	X("GREEN",GREEN)
    
    #define X(a,b) b,
    	enum color {COLOR(X)};
    #undef X
    
    #define X(a,b) a,
    	char *str[] = {COLOR(X)};
    #undef X
    

    怎么说呢,有点像call back function的感觉

    指针

    特殊指针

    NULL指针在C中的定义是(void *)0

    常量指针const int *pint const *p:指向常量的指针(a pointer that points to a const)

    指针常量int *const p:一个自己是常量的指针(a const pointer)

    上面两类还可以复合/套娃....类比就行

    指针变量的阅读

    具体可以看这篇文章

    链接

    符号表

    这里的符号表和编译原理的符号表又不太一样

    编译的时候可以多个编译单元编译成.o文件,最后合并成一个可执行文件(elf),这个合并的过程就是链接

    在链接的时候,需要维护一些跨编译单元的1. 函数调用2. 全局变量引用,符号表就是用来给链接器提供这个信息的

    根据这个角度,就可以知道为什么局部变量和宏不会出现在符号表中了,因为它们在链接时1. 不会被引用和修改 2. 已经被展开了

    staticinline

    inline表示建议编译器内联这段函数,但仅含有inline的函数定义不是一个函数声明,因此不会出现在符号表中

    在GCC开启-O0级别优化的时候,就会出错

    所以有两种方法解决:

    1. inline定义后加一个函数声明
    2. inline关键字前加static关键字

    而对于仅含有static的函数,如果它未被调用,则开启-Wall-Werror的时候就会报Function defined but not used错,这是因为static函数只可能被当前.c文件调用,而查手册可以发现-Wunused-function恰好会指出这种情况。解决的方法可以是用static inline代替static,或者删掉这个函数定义和声明

    杂项

    编译器选项

    感觉-Wall那个应该放这里的

    64位下,对于开启-O1及以上优化级别的程序,截止当前版本的GCC默认会省略%rbp寄存器的保存(压栈、记录栈顶),这样既可以变快,又可以多一个通用寄存器。在做ICS Labs的时候要写一个自己的setjmp()longjmp(),然后我就卡在了获得ret地址这一步,原因就是这个东西破坏了调用规定,导致栈的行为不确定了....

    解决方法很简单,直接自己写一整个的函数,而不是在函数内部内联汇编,这样callee就是自己维护的了,想怎么做就怎么做

    本文来自博客园,作者:jjppp。本博客所有文章除特别声明外,均采用CC BY-SA 4.0 协议

  • 相关阅读:
    AGC037F Counting of Subarrays
    AGC025F Addition and Andition
    CF506C Mr. Kitayuta vs. Bamboos
    AGC032D Rotation Sort
    ARC101F Robots and Exits
    AGC032E Modulo Pairing
    CF559E Gerald and Path
    CF685C Optimal Point
    聊聊Mysql索引和redis跳表
    什么是线程安全
  • 原文地址:https://www.cnblogs.com/jjppp/p/14195067.html
Copyright © 2011-2022 走看看