C语言学习笔记

风尘

文章目录

  1. 1. 数据类型
  2. 2. 常量
  3. 3. 变量
  4. 4. 运算符
  5. 5. 流程控制
  6. 6. 函数
  7. 7. 预处理器
  8. 8. 指针

数据类型

  • char 字符型,点一个字节。
  • int 整型,通常代表机器中整数的自然长度。
  • short int 短整型,通常为16位(int可以省略)。
  • long int 长整型,通常为32位(int可以省略)。
  • float 单精度浮点型
  • double 双精度浮点型
  • long double 高精度的浮点数

signedunsigned用于限定char类型和任何整型,unsigned类型的数值总是正值或0。

常量

语法:#define 常量名 常量值

  • 整数常量
    包含intlong类型常量。long类型以lL结尾。
    当一个整数无法用int表示时,也被当作long类型处理。
    无符号常量以uU结尾,无符号长整型使用ulUL结尾。
    前缀0表示八进制形式,0x表示十六进制形式。

  • 浮点数常量
    没有后缀的常量为double类型。
    后缀加fF表示float类型。
    后缀加lL表示long double类型。

  • 字符常量
    一个字符常量是一个整数,如'0'值为48,它与数值0无关。
    转义字符通常只表示一个字符,如'\013'
    字符常量'\0'表示值为0的字符,即空字符(null)。

  • 字符串常量
    与字符常量的区别是字符串常量用" "双引号括起来。其实就是字符数组,内部使用空字符('\0')作为结尾。

  • 枚举常量
    语法:enum 枚举名 {枚举列表}
    枚举常量是另外一种类型常量,是一个常量整型值的列表,如:

    1
    enum boolean {NO, YES};

    未显示声明的枚举,第一个枚举名的值为0,第二值为1,依此类推。
    如果指定部分枚举值,未指定枚举值将向后递增。

    1
    2
    3
    4
    enum boolean {WHITE=0,BLACK,RED,YELLOW}
    BLACK-->1
    RED-->2
    YELLOW-->3

常量表达式是仅仅包含常量的表达式。这种表达式只在编译时求值,而不在运行时求值。

变量

变量必须先声明再使用,一个变量声明只能指定一种类型,后面可以有一个或多个该类型变量。如:int lower,upper…;
任何

  • 外部变量
    定义在函数之外的变量叫做外部变量。由于定义在函数之外,因此可以在所有函数中使用。由于C语言不允许在一个函数中定义其它函数,因此函数本身是“外部的”。变量都可以使用const限定符限定为不可被修改变量。

    如果要在外部变量定义之前使用变量,或者外部变量的定义与变量的使用不在同一个源文件中,必须在相应变量声明中强制使用关键字extern。外部变量的定义中数组必须指定长度,但extern声明不一定要指定数组长度。

    文件a.c
    1
    2
    3
    #define MAXSIZE 10;
    int a;
    double b[MAXSIZE];
    文件b.c
    1
    2
    extern int a; //使用a.c文件中的变量a
    extern double b[]; //使用a.c文件中的b省略了数组大小
  • 自动变量
    定义在函数内的变量叫做“局部变量”,也叫“自动变量”。由于定义在函数之内,因此只可以函数内使用,多次调用函数不保留前次调用时的赋值。

  • 静态变量
    static修饰的变量,叫做静态变量。静态变量的存储方式与全局变量相同,都是静态存储方式。全局变量的作用域是整个源程序,即源程序源的所有文件中有效。静态变量作用域则是只在当前变量所在源文件中可以使用,其次静态变量的值在函数调用后一直保持不会消失。即使在函数中声明的,每次调用函数,其值都会保存上一次调用后值。

  • 寄存器变量
    使用register关键字声明的变量,叫做寄存器变量。register变量放在机器的寄存器中,这样可以使程序更小,执行速度更快。register声明只适用于自动变量或函数的形式参数形式:

    1
    2
    3
    4
    5
    test(register variA,register variB)
    {
    register int variC;
    ...
    }

    实际上,底层硬件环境对寄存器变量的使用会有一些限制。每个函数中只有很少的变量可以保存在寄存器中,且只允许某些变量类型的变量。编译器可以忽略过量的或不支持的寄存器变量声明,因此过量的寄存器变量声明并没有什么害处。但是注意,无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的。不同的机器,对寄存器变量的限制不同。

运算符

  • 自述运算符
    +-*/%
    c/c++java语言中取模运算(%)就是取余运算,而python则有些不同

  • 关系运算符
    >>=<<===!=

  • 逻辑运算符
    ||&&!

  • 按位运算符
    & 按位与(AND)
    | 按位或(OR)
    ^ 按位异或(XOR)
    <<左位移
    >>右位移
    ~按位求反(一元运算符)

  • 自增运算符
    ++ 可以作为前缀运算符,表示先作自增,后赋值;也可以作为后缀运算符,表示先赋值,再作自增。

    1
    2
    3
    4
    int x, n;
    n = 1;
    x = ++n; //x值为2,n为2
    x = n++; //x值为2,n为3
  • 自减运算符
    -- 用法同自增运算符

  • 三元运算符
    表达式 ? 表达式 : 表达式

流程控制

  • if…else 语句

    1
    2
    3
    4
    if (表达式)
    语句
    else
    语句
  • switch 语句

    1
    2
    3
    4
    5
    switch (表达式) {
    case 常量表达式:语句
    case 常量表达式:语句
    default:语句
    }

    注意,case后必须为整数值常量或常量表达式。

  • while 循环

    1
    2
    while(表达式)
    语句

    如果希望while循环体至少被执行一次可以使用do...while循环:

    1
    2
    3
    do 
    语句
    while (表达式);
  • for 循环

    1
    2
    for(表达式1;表达式2;表达式3)
    语句
  • break / continue 语句
    用于继续或结束循环语句。

  • goto 语句

    1
    2
    3
    4
    5
    6
    for ( ... )
    for ( ... ) {
    if (disaster) goto error;
    }

    error:

    大多数情况,使用goto语句比不使用goto语句程序段要难以理解和维护,少数情况除外。尽管该问题不太严重,但还是建议尽可能少的使用。

函数

函数定义:
返回值类型 函数名(参数列表){ 函数体 }

函数定义可以不带有返回类型,默认返回int类型。函数在源文件中出现的次序可以是任意的,只要保证一个函数不被分离到多个文件中即可。被调用函数通过return 表达式向调用者返回值,return后面表达式可以省略。

预处理器

预处理器是编译过程中单独执行的第一个步骤,最常用的预处理器指令是#include#define

  • 文件包含
    文件包含指令(#include)用于在编译期间把指定文件的内容包含进当前文件中。形式如下:
    #include "文件名"

    #include <文件名>

    当文件名用引号引起来(通常用于包含程序目录中的文件),则在源文件所在位置查找该文件;如果该位置没有该文件或者文件名用尖括号括起来(通常用于包含编译器的类库路径里面的头文件),则根据相应规则查找该文件,该规则同具体实现有关。如果某个包含文件内容发生了变化,那么所有依赖于该包含的文件的源文件都必须重新编译。

  • 宏替换
    宏替换指令(#define)用于用任意字符序列替代一个标记。形式如下:
    #define 标识符 记号序列

    替换文本前后空格会被忽略,两次定义同一标识符是错误的,除非两次记号序列相同(所有空白分割符被认为是相同的)。
    该定义的名字作用域从其定义点开始,到被编译的源文件末尾处结束。定义超过一行使用反斜杠(\)换行。

    替换的文本可以是任意的,如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //为无限循环定义一个名字
    #define forever for(;;)

    //定义带参数宏
    #define max(A,B) ((A) > (B) ? < (A) : (B))
    #define square(x) x * x

    main(){
    int i, z;

    i = z = 2;
    max(++i,i++); // 结果为4
    square(z+1); // 结果为5
    }

    宏定义也有一些缺陷,如上面max,它对每个参数执行两次自增操作。square没有增加括号而导致计算次序错误。

    可以通过#undef取消名字的宏定义:

    1
    #undef max

    #运算符可以使得宏定义的实际参数替换为带引号的字符串:

    1
    2
    3
    #define dprint(expr) printf(#expr + "=%d")
    调用结果x = 4,y = 2:
    dprint(x/y) --> x/y=2

    ##运算符可以使得宏定义的实际参数相连接:

    1
    2
    3
    #define paste(x,y) x ## y
    调用结果:
    paste(1,2) --> 12
  • 条件编译

    语法 
    1
    2
    3
    4
    5
    6
    7
    #if 常量表达式
     文本
    #elif 常量表达式
     文本
    #else
     文本
    #endif

    当预处理器检测到常量表达式值为非0时,对相应表达式下面文本进行编译,后续表达式及文本将会被抛弃。常量表达式可以使用define 标识符define(标识符)表达式,当标识符已经定义时,其值为1,否则为0。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //检测HDR标识符,没有定义时将其定义
    #if !defined(HDR)
    #define HDR
    #endif

    等价于

    #ifdef HDR
    #define HDR
    #endif

    如上所示,可以使用#ifdef 标识符#ifndef 标识符控制指令替换#if define 标识符

  • 预定义标识符

    标识符 说明
    __LINE__ 当前所在源文件行数的十进制常量
    __FILE__ 被编译的源文件名字的字符串
    __DATE__ 被编译的源文件编译日期的字符串,格式:”Mmm dd yyyy”
    __TIME__ 被编译的源文件编译时间的字符串,格式:”hh:mm:ss”
    __STDC__ 整型常量1(只有在遵循标准的实现中该标识符才被定义为1)
  • 其他预处理指令
    #line 常量 "文件名" 以十进制整型常量的形式定义下一行源代码的行号。其中"文件名"可以省略,表示设置当前编译的源文件。
    #error 信息 当预处理器遇到此指令时停止编译并输出定义的错误消息。通常与#if...#endif等指令一起使用。
    #pragam 记号序列 使处理器执行一个与具体实现相关的操作。无法识别的pragma(编译指示)将被忽略掉。
    # 空指令。预处理器行不执行任何操作。

指针

为了便于记忆,指针的声明形式是在变量声明的基础上加一个*间接寻址或间接引用运算符:

1
int *p; //声明一个int类型的指针*p

通过一元运算符&获取一个对象的地址:

1
2
3
int x = 1;
p = &x;
printf("%d",*p); --> 打印1

一元运算符*&的优先级比算术运算符优先级高,因此在进行算术运算时不需要加括号:

1
2
3
4
5
*p += 1;

++*p;

(*p)++;

语句(*p)++中的圆括号是必需的,否则,表达式将对p进行加一运算,而不是对ip指向的对象进行加一运算,原因在于一元运算符表达式遵循从右到左的顺序。

由于指针也是变量,所以可以直接使用,而不必通过间接引用的方法使用:

1
2
int *pp;
pp = ip; //通过变量的形式将指针pp指向指针ip指向的对象
  • 指针与函数参数
    C语言是以传值的方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量的值。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void swap(int x,int y){
    int temp;

    temp = x;
    x = y;
    y = temp;
    }
    调用: 
    swap(x,y);

    由于参数传递是传值方式,所以上述函数无法成功交换变量。可以通过将交换的变量的指针传递给被调用函数的方法实现该功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    void swap(int *px,int *py){
    int temp;

    temp = *px;
    *px = * py;
    *py = temp;
    }
    调用: 
    swap(&x,&y);
  • 指针与数组
    数组其实是由N个对象组成的集合,这些对象存储在相邻的内在区域中。因此可以将指针变量指向数组的每个对象。

    1
    2
    3
    4
    int a[10];
    int *pa;

    pa = &a[0]; //将pa指向数组第0个元素

    根据指针运算的定义,pa+1指向数组下一个对象,pa+i指向pa所指向数组对象之后的第i个对象,pa-i指向pa所指向数组对象之前的第i个元素。
    由于数组名所代表的就是该数组最开始的一个元素的地址,因此下面两等式作用相同:

    1
    2
    3
    pa = &a[0];

    pa = a;

    由上面等式,对数组元素a[i]的引用也可以写成*(a+i)形式。实际上,在C语言计算a[i]元素时就是先将其转换成*(a+i)的形式,然后再求值。

    数组名和指针的不同之处在于,指针是一个变量,数组名却不是变量。因此语句pa=apa++是合法的,而a=paa++形式是非法的。