少女祈祷中...

一些关键字

  1. volatile关键字
    使用volatile关键字声明变量,目的是为了告诉编译器不要对其进行‘优化’。那什么是‘优化’呢?,举个例子如下:

    1
    2
    3
    4
    5
    int a = 1,b,c; //为a b c申请内存并初始化
    b = a; //内存(&a) -> CPU
    //CPU -> 内存(&b)
    c = a; //内存(&a) -> CPU
    //CPU -> 内存(&c)

    以上代码可以帮助我们更好理解寄存器(CPU)读取规则:在初始化完成,寄存器会从a的内存中读取值,然后再将这存到b的内存中;同理,接下来寄存器会从a的内存中读取值,然后再将这存到c的内存中。其中内存(&a) -> CPU 这一步进行了俩次,但是第二次明显多多余了,于是编译器便对其进行了‘优化’:直接将上一次寄存器(CPU)中存储的a的值存入C的内存中,这样便减少了一次指令的执行。
    但是在这个过程中,如果a的值在其他地方发生变化,那么寄存器(CPU)中存储的值就不能直接存入c的内存,这个时候就需要使用volatile声明变量a,告诉编译器,这个变量会经常改变,必须从它的内存中取读取数据。
    在以下三种情况应该使用volatile关键字:

    • 并行设备的硬件寄存器。它们的寄存器状态随时会因为自身硬件而改变,因此,对存储器映射的硬件寄存器应该使用vlatile关键字。
    • 中断服务程序(ISR)与主程序的共享变量
    • 多线程中的共享变量
  2. static 关键字
    static是被声明的静态类型的变量,存储在静态区(全局区)中,它的生命周期是整个程序的生命周期。static声明的变量如果没有初始化,那么默认初始化为0。static声明的全局变量,作用域为当前文件,声明的局部变量则是当前{}
    static 声明的变量初始化后,都会保存在内存区域中,由于它的生命周期相当于整个程序的生命周期,因此它不会被销毁。

  3. const 关键字
    定义变量为常量(即不可被改变)。
    当const声明指针时,有以下几种情况:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
       const int *p;
    int const *p;
    int* const p;
    const int* const p;
    ```
    12种情况完全相同,但是常量指针:p都是指向一个不可变的常量,但是p本身的值可以改变;
    3种是指针常量:可以通过p更改p指向的值(即*p=4),但不能更改p的值;
    4种指向常量的常指针:即指针是常量,指针所指向的值也是常量,即不能更改指针的值,也不能通过该指针更改其指向的值。

    const如果声明函数传参时,即函数内不能改变该参数的值;
    const如果声明函数返回值时:分两种情况,如果返回值是指针,那么指针指向的内容是不可改变的,并且获取该函数的返回值时必须也用同样有const修饰的对应类型的变量;如果返回值是其他,那么由于函数执行的return时,它的生命周期结束了,用const修饰的变量也被销毁了,即使用const毫无意义。
    4. typedef 和 define
    define 是c语言中定义的语法,是预处理指令。它的作用是在预处理的时候,进行简单的机械字符串替换,无论正确与否,即不做正确性检查,只有当编译已被展开的源码时才会发现错误。而typedef是关键字,在编译时处理,因此它具有类型检查的功能。它的作用是,在自己的作用域内,为已经存在的类型增加一个别名,以便与理解,但他不能再函数内使用。

    define没有作用域的限制,只有之前预定义过的宏,在之后的程序中都可以使用,而typedef有作用域的限制
    对指针操作也不同,例如在以下代码中:
    ```c
    #define INTP1 int*
    typedef INTP2 int*
    INTP1 p1,p2;
    INTP2 p3,p4;

    INTP1 p1,p2;经过字符串替换后为 int* p1,p2,即声明一个int指针和int类型的变量,而INTP2 p3,p4; 由于 INTP2是由含义的:指向整型的指针。因此后面的p3,p4都是指向整型的指针(如果不能理解你可以把 int* 想成 char,那p3,p4就都是char类型)。

变量

  1. 定义常量谁更好?# define还是 const?
  2. 全局变量和局部变量的区别是什么?
  3. 全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?
  4. 局部变量能否和全局变量重名?

指针

数组与指针

  1. 数组指针,是一个指向数组的指针,例如 int (*p)[4]; 声明了一个指针,该指针指向一个有4个int类型元素的数组。(我看的能个链接好像有问题,没怎么看懂,后面补一个新的例题)
  2. 指针数组,是一个数组,其内容都是由指针构成。如
    1
    2
    3
    4
    5
    int *p[3];
    int a[3];
    p[0] = &a[0];
    p[1] = &a[1];
    p[2] = &a[2];
    这样是合理的数组p里面存储的都是int类型的指针。
  3. 数组所占存储空间的大小:sizeof(数组名)
    数组大小:sizeof(数组名)/sizeof(数据类型)
    sizeof(指针名),无论该指针是什么类型,在32位系统下结果位4,64位系统下结果为8。
  4. 指针进行强制类型转换后与地址进行加法运算,结果是什么?

函数与指针

函数指针。在程序中声明了一个函数后,系统会为这个函数分配空间,这段存储空间的首地址便是这个函数的地址,同时,函数名表示的就是这个地址。因此,可以用一个指针来存储该地址,这样的指针变量就叫做函数指针,其形式类似于int (*p)(int,int);
因为它是一个指针变量,所以是*p,后面两个int说明该指针指向的函数需要两个int类型的参数。指向函数的指针没有++或者–等操作。
另外由于*的优先级比括号低,如果不加括号int *p(int,int); ,这样则是一个返回类型为int*,传入参数为两个int的函数声明。
函数指针的使用案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int max(int,int); //函数声明
void main(void)
{
int (*p)(int,int) = max;
int a=1,b=2;

printf("a b max is %d\n",(*p)(a,b));
}

int max(int a,int b)
{
return a>b?a:b;
}

内存

C语言函数参数压栈顺序是怎样的?

从右向左,主要原因是为了方便支持可变长参数。例如printf(const char* format,…),采用从右向左压栈后,固定参数 format会位于栈顶附件,可以用指针直接访问,然后通过指针偏移访问其他参数。示例:可变长参数函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdarg.h>
#include <stdio.h>

void print_numbers(int count, ...) {
va_list ap;
va_start(ap, count); // ap指向count之后的第一个可变参数
for (int i = 0; i < count; i++) {
int num = va_arg(ap, int); // 依次访问可变参数
printf("%d ", num);
}
va_end(ap);
}

int main() {
print_numbers(3, 10, 20, 30); // 输出:10 20 30
return 0;
}

预处理

  1. #error。当编译程序时,当遇到一个#error时,就会生成一个编译错误的提示信息。例如
    1
    2
    3
    #ifdef xxx
    #error have define xxx
    #endif
  2. 使用 #intclude<filename.h>#intclude"filename.h"的区别?
    区别是当使用第一个是,系统编译器会先从标准库的路径搜索filename.h,使得系统文件调用较快。而第二个则先从用户的工作路径去查找文件,使得自定义文件较快。
  3. 在头文件中定义静态变量是否可行,为什么?
    不可行,在头文件中定义静态变量,会造成资源浪费的问题。因为按照编译的步骤,如果头文件中存在一个静态变量,那么每一个使用该头文件的文件都会创建一个新的静态变量,从而引起空间浪费或者程序错误。
  4. # 和## 的使用
    #可以把宏参数转换成其对于的字符串,例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include<stdio.h>
    #define CONVERT(a) #a

    void main(void)
    {
    int num = 0;

    printf(CONVERT(num)); //其输出结果为num
    }
    ##则是连接操作符,把多个形参转换成一个实际的参数名,例如:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    #include<stdio.h>
    #define CONNECT(a,b) a##b

    void main(void)
    {
    int lastnum = 5;

    printf("%d\n",CONNECT(last,num)); //其输出结果为5
    }

其他

  1. strlen(“\0”) =? sizeof(“\0”)=? 两者结果与区别
    strlen(“\0”) =0 sizeof(“\0”)=2。strlen是从字符串开始计数直到’\0’结束;而sizof则是计算括号内所占的空间大小。
  2. C语言中 struct与 union的区别是什么?
    结构体是所有成员所占空间是它们之和,所有成员都是存在的;而联合体是所有成员共用同一块空间,联合体内只存放了其中一个成员,其变量长度为最长的成员长度。(两者的长度都要考虑字节对齐)。
  3. 什么是大端和小端?
    大端:高地址存低字节,低地址存高字节。
    小端:低地址存低字节,高地址存高字节