c 常见问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_27087571/article/details/79431753

c语言容易忽视的问题

关键字

  1. static

    作用:改变作用域,改变生存期
    修饰函数,全局变量,局部变量。值得注意的是:
    修饰局部变量时,由于被 static 修饰的变量总是存在内存的静态区,所以即使这个函数运行结束,这个静态
    变量的值还是不会被销毁,函数下次使用时仍然能用到这个值。*局部变量不会随着函数结束而结束,但是只
    有在函数里面才可以访问到这个变量,这就是和修饰全局变量的区别。*

    static int j;
    void fun1(void)
    {
        static int i = 0;
        i ++;
        printf("i :%d\n",i);
    }
    void fun2(void)
    {
        j = 0;
        j++;
        printf("j :%d\n",j);
    }
    int main()
    {
        for(int k=0; k<10; k++)
        {
            fun1();
            fun2();
        }
    
        return 0;
    }
    

以上的结果是i的值会一直增加,并且只会被初始化一次。
static 与 extern 是不兼容的。使用static修饰的变量没有办法使用extern开放出去。

  1. const

    • const什么时候和volatile同时使用:

      可能发生在该变量是只读寄存器,度与

    • volatile 使用的地方:

      并行设备的硬件寄存器(如:状态寄存器)
      一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
      多线程应用中被几个任务共享的变量

  2. union

  3. 位操作:

    • 清零赋值

      a &=~ b 等价为 a = a & (~b)
      常用 :&=~ 清零,|= 置位,& 查看
      例0x1234,共16位,每个数字4位,共2个bit(字节),每个bit有8位

    • 假设从0位开始

      查看x位 reg & (0x1 << x)
      将某x位置1 reg |= (0x1 << x)
      将某x位置0 reg &=~ (0x1 << x)
      查看3到7位 reg & (0x1f << 3)
      将3到7位置0 reg &=~ (0x1f << 3)
      将3到7位置x reg &=~ (0x1f << 3) ; reg |= (x << 3) 先清零后置位

    • 除法向上取整
      (x+y-1)/y

    • 对其

      4对其:(x >> 2 << 2)
      或者是:x += (4-1) ; x &=~ 4

    • arrribtte的使用

      对于struct结构体在编译过程中会自动优化对其的。默认4字节对其的。
      手动改变结构体成员的顺序可以改变结构体的大小,式结构变得紧凑。
      使用__attribute__((packed))取消对其,或者使用__attribute__((alligned(n)))指定n个字节对其。

预处理

  1. 多语句宏的书写方式
    多语句宏的书写使用do while(0),这样不仅是为了增加代码的可读性更是实际的需要.

如果不使用的话

#define DOSOMETHING()\
               foo1();\
               foo2();

这样的话会将后面的两个函数宏替换

if(a>0)
    DOSOMETHING();

展开替换为

if(a>0)
    foo1();
foo2();

这样的话不论怎样都会执行第二个函数,因此不加肯定是有问题的。
如果使用{}

    #define DOSOMETHING()\
    {\
        foo1();\
        foo2();\
    }

展开之后就是

if(a>0)
{
    foo1();
    foo2();
};

这样就会有语法错误

因此这样使用

    #define DOSOMETHING()\
                do{\
                    foo1();\
                    foo2();\
                }while(0)

这样的话宏替换就不会出现问题了。

  • 使用宏函数的优点和缺点:
    优点:提高执行效率,没有调用函数的开销。
    缺点:大量使用宏函数会增加代码的体积,执行的文件变大。

    1. 头文件的相互调用

    为了避免出现头文件相互调用的时候出现重复定义,使用以下的技巧

    #ifndef HFILENAME_USED
    #define HFILENAME_USED
        ... 头文件内容 ...
    #endif

指针和数组

  1. 数组指针变量

    定义一个指针数组变量

    char (*buff)[10];
    

    //当成 char (*)[10] buffer,就好理解了,但是不能这么写

  2. 数组名a作为左值和右值的区别

    左值:修改变量的值
    右值:读取变量的值
    数组名只能作为右值,不能作为左值,作为右值的时候表示&a[0](数组元素的首地址),而不是&a(数组的首
    地址)

    int a[100];
    
    int main()
    {
        printf("&a[1]:%x,a+1:%x, &a+1:%x\n", &a[1], a+1, &a+1);
        return 0;
    }
    

结果:&a[1]:601064,a+1:601064, &a+1:6011f0
也可以总结为:数组名只有在使用sizeof的时候代表数组,其他的时候都只是数组元素的首地址.

因此当数组作为函数的行参数就没办法使用sizeof知道数组的大小,必须另外传进来。

  1. 大端模式:高字节放在低地址中,低字节放在高地址中。
    小段模式:低字节放在高地址中,高字节放在低地址中。

  2. 二维数组作为函数参数传递

    数组蜕为指针的规则是不能递归使用,也就是说二位数组的传递不能使用指针的指针
    行参必须使用二位数组或者是数组指针。因为如果不是这样的话就没有办法,确定每一维数组的长度了。

  3. 柔性数组

    技巧就是,在结构体里面使用长度为0的数组,结构体分配内存大小时算上要分配的数组的大小,这样的话
    可以使用这个数组了,并且数组的内存就在结构体后面。释放的时候只用释放结构体的内存就可以一起释放了。
    主要的优点是,数组的指针和内存是连续的。方便使用管理。

        typedef struct a {
            int size;
            int array[0];
        }A;
        A *a;
        a = (A*)malloc(sizeof(A)+sizeof(int)*10);

结构体

  1. 结构体之间的直接赋值
    _同种类型的结构体之间可以直接赋值,如果结构体里面有指针变量,那么两个结构体里的指针指向的是同一
    个块内存,并没有重新分配一块内存。_

  2. 结构体初始化的三种方式

        typedef struct a {
            int a;
            int (*fun1)(int a);
            int (*fun2)(char b);
            int b;
        }A;

        int fun1(int a)
        {
            printf("1\n");
            return 0;
        }

        int fun2(char b)
        {
            printf("2\n");
            return 0;
        }

        int main()
        {
            A a1 = {
                .a    = 1,
                .fun1 = fun1,
                .fun2 = fun2,//通常使用这种方式
            };
            A a2 = {
                a   : 2,
                fun1 : fun1,
                fun2 : fun2,
            };
            A a3 = {
                1,
                fun1,
                fun2,
            };
        }

表达式

  1. ?:
    这个三目运算符,返回值是一个右值,是不能被赋值的,另外类型强制转换之后也是右值。

    int a,b,c;
    (a==0?b:c) = 10;//这样写是错误的
    *(a==0?&b:&c) = 10;//这样写就可以了

函数

  1. 函数指针的类型

    定义一个函数指针变量 int(*Fun)(char *a);

    typedef int(*Fun)(char *a);//这里Fun不是函数指针,指的是函数指针变量。
    (*fun(0x0))(x);//或者是(*(int(*)(char *)0x0))(x);
    
  2. 函数当作另一个行参(主要用作回调函数)

        typedef int(*Fun)(int a);

        int fun1(int a)
        {
            a++;
            printf("a:%d\n", a);
            return 0;
        }
        void fun2(int b, int fun(int a))
        {
            fun(b);
        }

        void fun3(int b, int (*fun)(int a))
        {
            fun(b);
        }

        void fun4(int b, Fun fun)
        {
            fun(b);
        }

        int main()
        {
            fun2(1,fun1);
            fun3(2,fun1);
            fun4(3,fun1);

            return 0;
        }
> 结果2,3,4
  1. 函数指针数组

    定义一个指针数组变量

    int(*Fun[10])(char *a);
    //当成int(*)(char*a) Fun[10];就很好理解了,但是不能这么写。

  2. 函数参数传递

    只有参数是数组的时候是双向值传递,其他的时候都是单向值传递
    当传进函数的参数只用来读的时候,传值就可以了。当传进来的参数要写的时候就要传地址,(相对于该变
    量来说,也可以说是双向值传递)
    这个完全适用于指针变量。当我们传递内存的时候传进来指针变量。因为我们要改变这个指针变量的值,
    因此要传递它的指针,也就是二级指针。或者使用返回值的方式。

  3. 可变参数函数

标准库函数

  1. itoa, sprintf, strtok

  2. 文件操作函数

内存使用

  • 内存池

    为了避免出现内存碎片,将内存统一管理。提前将内存malloc成很多不同大小的块。每次分配的时候使用提前分
    配好的固定大小的内存。

  • 内存越界检查

    一般在内存的结尾加上固定长度的字符串,程序运行时检查该字符串有没有被别人冲掉

猜你喜欢

转载自blog.csdn.net/qq_27087571/article/details/79431753