标准C语言

标准C语言

gcc操作

计算机只能识别0和1

源文件 --> 预处理 --> 编译 --> 汇编 --> 链接 --> 可执行文件

  • -o 可指定得到的文件名称

  • 预处理主要进行头文件替换

    gcc -E hello.c -o hello.i

  • 编译对hello.i文件进行处理,得到汇编文件

    gcc -S hello.i -o hello.s

  • 汇编对hello.s文件进行处理,得到二进制文件

    gcc -c hello.s -o hello.o

  • 链接,链接库生成可执行文件

    gcc hello.o -o hello

符号含义

  • #include 预处理指令,stdio.h 头文件
    • #include<stdio.h> 到系统目录中找
    • #include"stdio.h" 先在当前目录找,再到系统目录找
  • 注释
    1. 多行注释:/* … */
    2. 单行注释:// …
  • 反斜杠
    • \b 含义:退格
    • \r 含义:到当前行首
    • \n 含义:到下一行
    • \t 含义:tab
    • \’ 含义:’
    • \" 含义:"
    • \\ 含义:\
    • %% 含义:%

运算符

  • 功能划分:算数运算符 逻辑运算符 位运算符 赋值运算符
    • 算数运算符:+ - * / % ++(自增运算符) --(自减运算符)
      • a++表示先赋值再自增;++a表示先自增再赋值
    • 逻辑运算符:> >= <= < != == &&与 ||或 !非
      • c语言中只有0表示false,其他数字表示真true
      • 逻辑与或非短路特性:当前一个逻辑表达式的结果可以决定整体的表达结果,编译器会忽略后一个逻辑表达式
  • 数量划分:单目运算符 双目运算符 三目运算符
    • 三目运算符
      • 格式:布尔值 ? 表达式一 : 表达式二
      • 布尔值为真使用表达式一,布尔值为假使用表达式二
      • 不要在问号后使用赋值运算符

位运算符

  • & 与运算:都为1才会1,其余为0
  • | 或运算:都为0才为0,其余为1
  • ^ 异或运算:相同为0,不同为1
  • >> 右移位:有符号数:左补符号位;无符号数:左补0
  • << 左移位:右补0

变量

程序中所涉及到的数据可以存到变量中,变量需要先定义再使用

  • 命名规则

    1. 只能由字母,数字和下划线构成
    2. 数字不能做开头
    3. 不能和关键字同名
    4. 变量名区分大小写
    5. 最好做到见名知意
  • &获取变量存储区的地址

作用域

作用域指的是一个变量在程序中定义后所能被访问的范围

  • 局部变量:从定义的语句开始到函数结束,所有语句都可以使用
  • 全局变量:从定义的语句开始向下,所有语句都可以使用
  • 块级变量:从定义的语句开始到语句块结束前的所有语句都可使用

生命周期

生命周期是指变量在程序中存在的时间

  • 局部变量:从定义变量时分配存储区,函数调用结束释放存储区
  • 全局变量:整个程序执行时间

静态变量

  • 生命静态变量时需要使用static关键字
  • 静态变量的生命周期是整个程序的执行时间
  • 未初始化的静态变量自动初始化为0
  • 静态局部变量的作用域仍然只包含函数里的所有语句
  • 静态变量的初始化只在程序开始的时候执行一次,不论初始语句写在什么地方
  • 静态全局变量的生命周期还是整个程序的执行时间,但是它的作用域只包含声明它的文件里的所有语句(不可以跨文件使用静态全局变量)
  • 静态全局变量和静态函数只能在当前文件使用

关键字

具有特殊的功能和作用

常用关键字

  • return:结束函数执行;返回结果,传递数据

  • sizeof:计算内存大小,返回长整型

  • extern:外部声明

  • const:常量化,被修饰的变量只能读不能改

  • struct:声明结构体

  • union:声明联合体

数据类型

  1. char 字符 1字节 -128 ~ 127
  2. unsigned char 字符 (无符号) 1字节 0 ~ 255
  3. short 短整型 2字节 -32768 ~ 32767
  4. unsigned short 短整型 (无符号)2字节 0~ 65535
  5. int 整数 4字节 -2^31 ~ 2^31 - 1
  6. unsigned int 整数 (无符号) 4字节 0 ~ 2^32 - 1
  7. long 长整型 4字节(32位) 8字节(64位)
  8. unsigned long 长整型 (无符号) 4字节(32位) 8字节(64位)
  9. 数据类型默认有符号,想使用无符号数字在后面加u,例如3u
  10. float 单精度浮点类型 4字节 1.2 * 10^-38 ~ 3.4 * 10^38
  11. double 双精度浮点类型 8字节 2.2 * 10^-308 ~ 1.8 * 10^308
  12. 在程序中见到浮点数默认为double类型,例如:5.6;若使用float类型需在后面加f,例如:5.6f

占位符

  1. %hhd(输出数字) %hhu(无符号)或 %c(输出字符):字符类型。注意:键盘输入内容需与占位符一致
  2. %hd %hu(无符号):短整型
  3. %d %u(无符号):整数类型
  4. %ld %lu(无符号):长整型
  5. %f(保留末尾0) %g:(不保留末尾0)单精度浮点类型
  6. %lf(保留末尾0) %lg:(不保留末尾0)双精度浮点类型
  7. %p:地址类型
  8. %lu:sizeof占位符
  9. %[#]o:八进制占位符
  10. %[#]x/X:十六进制占位符
  11. %s:字符串的占位符

字符输出方式

//char c = 65;
char c = 'A';
printf("%hhd\n",c);//65
printf("%c\n",c);//A

c = c + 1;
printf("%hhd\n",c);//66
printf("%c\n",c);//B

函数

一堆语句的集合(语句块),为了使某些功能具有独立性和通用性,将功能封装到函数中可以降低开发的工作量,一次写好,到处使用

  • 函数使用顺序:声明 实现 使用
  • 函数的声明
    • 在使用函数时需要现在开头对其进行声明
    • 返回值类型 函数名(形参表);
    • int main(int argc,char* argv[])
      • argc表示命令行参数的个数,无需手动输入,argv[]表示命令行参数的内容
  • 编译顺序:自上而下;执行顺序:从main函数开始
  • 递归函数:自己调用自己

常用函数

  • main 主函数

    • c程序中有且只有一个main函数,main函数是程序执行的入口
    • 只能返回整数型数据
  • printf 输出函数

    • \n表示换行
    • 在printf函数中想要输出%,需要打两个%%
  • scanf 输入函数

    int var,a,b;
    scanf("%d",&var);
    //通过scanf函数一次获取两个数据,占位符间不加空格
    scanf("%d%d",&a,&b);
    
  • rand 随机数函数

    • 需要引入<stdlib.h>头文件
    • RAND_MAX产生的随机数最大值
  • srand 随机数函数

    • 通过改变随机数种子的数值来产生一系列随机数,不同的种子产生的随机数不同
  • time 时间函数

    • 需要引入<time.h>头文件
    • 参数 0:返回秒值
  • atoi 将字符串转换为数字

    • 需要引入头文件<stdlib.h>
    • 必需以数字开头,直到非数字停止;若字符串开头不为数字则返回0
  • abs 获取绝对值

流程控制

  • 流程控制关键字
    1. break 结束循环
    2. continue 结束本次循环,继续下次循环

分支

  • if

    if(condition){
        content;
    }
    
  • if-else

    if(condition){
        content;
    }else{
        content;
    }
    
  • if-else if-else

    if(condition1){
        content;
    }else if(condition2){
        content;
    }else{
        content;
    }
    
  • switch-case

    int a;
    scanf("%d",&a);
    switch(a){
        case 1:
            printf("1\n");
            break;
        case 2:
            printf("2\n");
            break;
        default:
            printf("o\n");
            break;
    }
    

    注意:switch只能做整数判断

循环

  • for

    //expression1:循环开始
    //expression2:循环结束
    //expression3循环的变化
    for(expression1;expression2;expression3){
        content;
    }
    
    //expression1和expression3可省略
    int j =0
    for(;j<3;j++){
        content;
    }
    
  • while

    while(expression){
        content;
    }
    

数组

可以通过数组一次性分配若干个同类型的存储区,这些存储区在内存中连续

  • 定义数组

    • 类型 数组名[元素个数];int arr[5];
    • 定义数组时可以不给定元素个数
    • 数组中每个元素通过下标来表示,第一个元素从下标0开始,数组中元素地址连续
    • 定义数组时可以直接赋值初始化,如果给的数据个数少于元素个数,则未被赋值的元素默认给0值进行初始化
  • 使用数组

    • 数字名表示的地址为数组第一个元素的地址,使用sizeof(数组名)时计算的是数组的总大小

    • 两个元素地址值相减得到相差的元素个数

    • 元素地址加一表示在该地址处加上一个数组同类型的字节大小

      int arr[5] = {1,2,3,4,5};
      printf("%ld\n",sizeof(arr);//20
             
      printf("%ld\n",&arr[4]-&arr[1]);//输出结果为3
      
      printf("%p\n",&arr[1]);
      printf("%p\n",&arr[1]+1);//输出结果比上一条语句多一个int类型的大小
      

变长(动态)数组

  • 声明变长数组的时候用变量表示数组里的存储区个数

    int n;
    scanf("%d",&n);
    int a[n];
    
  • 变长数组不可以初始化

  • 变长数组里包含的存储区个数在某一次程序执行过程中不会变化

二维数组

  • 定义数组
    • 类型 数组名[元素个数][元素个数];int arr[5][3];
    • 组下标:每组的下标从0开始,到分组数减一
    • 组内下标:一组的每个元素的下标从0开始,到每组个数减一为止
    • 一维数组和二维数组不能出现重名

进制转换

  • 进制转换

    1. 十进制非负数转二进制数:除2取余,逆序排列
    2. 十进制负数转二进制数:去负转二,取反加一
    3. 二进制数转十进制负数:取反加一,转十添负
  • 单位转换

    1. 1byte = 8bit;1word = 2byte
    2. 1kb = 1024 bytes
    3. 1mb = 1024kb
    4. 1gb = 1024mb
  • 有符号类型的数字(不加unsigned修饰)才有符号位,二进制数最左边的位叫符号位。0表示正,1表示负

  • 进制表示

    1. 八进制数前面加一个0表示
    2. 十六进制前面加0x/0X表示,小写x表示的数都用小写,大写X表示的数都用大写
  • 类型转换规则:整转浮,有转无,小转大

  • 输出二进制数

    #include<stido.h>
    
    int main(void){
        char a = 54;
        unsigned int mask =128;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        printf("%d",a & mask ? 1 : 0);
        mask = mask >>1;
        
        return 0;
    }
    

指针

  • 指针用来存储地址,定义时用*表示指针类型

  • *解引用,通过指针访问变量的存储区

  • 32位系统,指针在内存中占4字节;64位系统,指针在内存中占8字节

  • 没有记录有效地址的指针分为两类

    1. 空指针里固定记录空地址(用NULL表示,数值就是数字0)
    2. 所有其他没有记录有效地址的指针都叫做野指针
    3. 程序里禁止出现野指针
    4. C语言里所有指针都必须记得进行初始化
  • 指针的类型决定了对内存的访问能力,对指针做加一,相当于加上一个指针类型的大小

  • 小端字节序:数据的低位存在低地址,数据的高位存在高地址,绝大部分计算机都是以小端字节序存储数据;取出数据时同理

  • 指针指向数组时 int* p =arr;

    1. 大部分情况下可以使用p来代替arr
    2. 计算数组长度的时候不可以使用p来代替arr
    3. 数组名不可以做加减,可以使用p++,不能使用arr++
    4. 指针可以通过下标访问元素,p[1]表示访问数组第二个元素
  • 函数也可以有指针修饰,可以返回变量的地址

    int* fun(void){
        //用static改变函数内部变量的生命周期
        static int a=10;
        return &a;
    }
    

const修饰指针

  • 常量指针
    • 不可以通过这种指针对它指向的存储区做赋值,但是可以对指针本身做赋值
    • const int* p = &val;(或者int const* p)
  • 指针常量
    • 不可以对指针本身赋值,但是可以对它指向的存储区赋值
    • int* const p = &val;
  • 常量指针常量
    • 不可以对指针本身和它指向的存储区赋值
    • const int* const p = &val;

泛型指针

定义指针变量的时候可以使用void作为类型名称,这种指针叫做泛型指针,它可以指向任意类型变量的存储区

  • 别名:泛化指针,无类型指针

  • 无法通过这种指针知道它所指向的存储区类型

  • 不能在泛型指针前直接使*运算符,泛型指针必须先强制类型转换成有类型的指针后才能使用

    int a = 10;
    void* p =&a;
    printf("%d\n",*(int*)p);
    

高级指针

  • 高级指针可以用来表示低级的指针的数组

  • 二级指针示例

    #include<stdio.h>
    
    int main(void){
        int a=10;
        int* p = &a;
        int** pp = &p;
        
        printf("a = %d,&a = %p\n",a,&a);
        printf("p = %p,&p = %p,*p = %d\n",p,&p,*p);
        printf("pp = %p,&pp = %p,*pp = %p,**pp = %d\n",pp,&pp,*pp,**pp);
        
        *p = 11;
        printf("a = %d\n",a);
        **pp = 12;
        printf("a = %d\n",a);
        
        return 0;
    }
    

函数指针

  • c语言里函数也有地址,函数名称可以用来表示函数地址

  • 函数指针用来记录函数的地址,函数指针也需要先声明再使用

  • 函数指针的声明需要根据函数声明变化得到

  • 函数指针也分类型,不同类型的函数指针适合与不同类型的函数捆绑

  • 可以像通过函数名一样通过函数指针调用函数

  • 由近及远,先右后左,括号优先

    int* a[3];指针数组
    int (*a)[3];数组指针
    int a(int,int);函数
    int (*a)(int,int);函数指针
    int* a(int,int);返回值为int*的函数
    
  • 函数指针用法示例

    #include<stdio.h>
    //pfunc_t 是 void (*)(void) 类型的别名,起别名方便使用
    typedef void(*pfunc_t)(void);
    
    void f1(void){
        printf("f1\n");
    }
    
    void f2(void){
        printf("f2\n");
    }
    
    void cmd(pfunc_t p){
        p();
    }
    
    int main(void){
        cmd(f1);
        return 0;
    }
    

字符串

用来记录文字信息

  • 字符串匹配:一串连续的字符 + ‘\0’
  • 定义:字符串由一组连续的字符组成,使用""括起来,字符串必须以\0结尾
  • “s” 表示字符串,'s’表示字符
  • '\0’表示字符串的结束,对应的ASCII值为0
  • 编译器会自动给字符串常量添加’\0’字符;“abcd"等价"abcd\0”
  • 调用printf函数时,可以用%s作为占位符来输出字符串,%s表示字符串首元素地址,一直输出到’\0’,也可用来输出数组内容
  • 编译器会把字符串当作其中第一个字符所在存储区的地址
  • 字符串“hello” 既表示字符串内容本身,也表示内存中首字节的地址,可以被指针指向,如char p = “hello”;*
  • 代码中相邻的若干个小串在编译时,编译器自动合成一个大串,如printf(“he”“llo!”“world\n”);的输出结果为hello!world
  • 字符串的输入和输出不建议使用puts和gets函数

字符串操作函数

  • 导入string.h头文件使用
  • strlen:计算一个字符串的长度,计算长度不包含最后的空字符\0,返回无符号型的整数类型,strlen所需的参数为字符串的地址
  • strcat:将一个字符串内容连接到另一个字符串的末尾,后面的练到前面的
  • strcpy:将一个字符串复制到另一个字符串中,后面的复制到前面,会造成覆盖
  • strcmp:比较两个字符串的大小
    • int strcmp(const char* str1,const char* str2)
    • 如果str1大于str2,则返回一个正数,值为ASCII码的差值,否则返回负数
    • 比较规则:从两个字符串的第一个字符开始比较,若不同则返回差值,若相同则继续向后比较,直到字符不同
    • 注意:不是比较两个字符串的长短

预处理指令

在预处理阶段会处理的指令,以#开头的都是预处理指令

  • #define预处理指令用来定义宏
  • 宏可以用来给数字起别名,定义宏的时候把名称写前面,宏代表的数字写后面,如:#define PI (3.14)
  • 编译器在编译时会把程序里的宏名称替换为它代表的值
  • 命名规则
    1. 宏名称通常由大写英文字母构成
    2. 宏名称里不可以包含空格
    3. 用宏给数字起名字的时候不可以使用赋值运算符

宏运算符

  • 编写宏的时候可以使用一些特殊的符号,它们叫做宏运算符
  • #是一个宏运算符,它可以把宏的参数转换成字符串,如#a预处理后会替换为"a"
  • ##可以把一个代表标识符的参数和其他内容连接成为一个新的标识符

有参宏-宏函数

  • 宏可以给表达式起名字

  • 表达式里包含未知数字,宏的参数可以用来表示表达式的未知数字

  • 格式:#define 宏名称(参数列表) (宏值表达式)

  • 编译时可以使用-D选项指定宏所代表的数字,如gcc -DSIZE=10,指定SIZE的值为10

    #include<stdio.h>
    //SIZE是宏,该宏的值在编译时指定
    int main(void){
        int arr[SIZE] = {};
        for(int i = 0;i < SIZE;i++){
            arr[i] = i + 100;
            printf("%d",arr[i])
        }
        return 0;
    }
    
  • 注意

    1. 使用宏参数不要忘记加(),否则可能导致运算顺序不符合预期,引起副作用,如:#define SQUARE(x) (x*x) SQUARE(2+3)的返回结果不是25而是11
    2. 有参宏只检查参数个数,不检查参数类型

预定义宏

  • 编译器默认已定义好的宏,直接使用即可

    占位符含义
    __FILE__%s所在文件名
    __LINE__%d所在行号
    __FUNCTION__%s所在函数名
    __func__%s所在函数名
    __DATE__%s编译该文件日期
    __TIME__%s编译该文件时间

    注意:前后都是两个下划线

条件编译

  • 条件编译可以在编译的时候从几组语句里选择一组编译而忽略其他组;条件成立就编译,条件不成立就不编译

  • 使用方式

    • #if 表达式:如果表达式为真,该部分代码会被编译,否则不编译
    • #ifdef 宏:如果定义了某个宏,该部分代码会被编译,否则不编译
    • #ifndef 宏:如果没定义某个宏,该部分代码会被编译,否则不编译
    • #elif
    • #else
    • #endif
  • 条件编译可以让一套代码可以适应不同的硬件平台

多文件编程

  • 单文件程序改造成多文件程序的步骤

    1. 把所有函数分散在多个不同的源文件里(通常主函数单独占一个文件 main.c)
    2. 为每个源文件编写配对的以.h作为拓展名的头文件(主函数所在的源文件不需要配对的头文件)
    3. 在所有源文件里使用#include预处理指令包含必要的头文件(配对头文件是必要头文件,如果源文件里使用了某个头文件里声明的函数则这个头文件也是必要头文件)
  • 头文件卫士示例:a.h文件

    #ifndef _A_H
    #define _A_H
    int a = 100;
    #endif
    /*头文件卫士写法
    #ifndef 宏名称
    #define 宏名称
    头文件内容
    #endif
    */
    

Makefile

  • 可以把多文件程序的编译步骤记录在Makefile文件里,然后使用make工具按照Makefile文件里记录的步骤完成编译

  • Makefile文件里每个命令前都不可以使用空格而应使用键

  • Makefile是一个文本文件

  • 写法示例

    #目标main,依赖于main.o calc.o
    #最终要得到的文件要写在第一行
    main:main.o calc.o
    	gcc main.o calc.o -o main
    #目标main.o,依赖于main.c
    main.o:main.c
    	gcc -c mian.c -o main.o
    #目标calc.o,依赖于calc.c
    calc.o:calc.c
    	gcc -c calc.c -o calc.o
    

结构体

c语言里可以在一个存储区里记录多个数字,这种存储区的类型叫做结构体,这种类型需要程序员创建出来才能使用

  • 结构体存储区里不同子存储区的类型可以不同

  • 子存储区也可以是结构体类型的存储区

  • 使用struct关键字声明结构体,结构体声明语句用来创建结构体类型,类型的声明,不涉及内存的分配

  • 示例

    #include<stdio.h>
    /*struct 结构体名称{
        类型1 成员1;
        类型2 成员2;
        类型3 成员3;
        ...
    };
    //类型的声明,不涉及内存的分配
    //struct 结构体名称 就是一个新的数据类型
    //struct 结构体名称 变量名;*/
    
    struct stu{
        int age;
        char name[32];
        float score;
    };
    typedef struct stu stu_t;//起别名
    
    /*
    另一种写法
    typedef struct stu{
    	int age;
        char name[32];
        float score;
    }stu_t;
    */
    
    int main(void){
        struct stu a = {18,"bob",99};
        struct stu b = {.score=77,.age=17,.name="timi",};
        stu_t arr[2] = {{18,"bob",99},{16,"john",89}};
        //arr[0] <==> *pa <==> pa[0]
        stu_t* pa = arr;
        
        //结构体指针
        //通过变量自身表示成员 a.name
        //通过指针表示变量成员 p->name (*p).name
        struct stu* p = &a;
        printf("name= %s\n",p->name);
        
        printf("name= %s\n",arr[0].name);
        
        return 0;
    }
    
  • 还可以直接定义结构体变量

    #include<stdio.h>
    struct {
        int a;
        int b;
    }s = {3,4};
    
    /*
    struct {
        int a;
        int b;
    }s = {3,4},x={8,9};
    */
    int main(void){
        printf("%d,%d\n",s.a,s.b);
        s.a = 5;
        printf("%d,%d\n",s.a,s.b);
        return 0;
    }
    
  • 嵌套结构体

    #include<stdio.h>
    //结构体,描述坐标点
    typedef struct{
        int x;
        int y;
    }pt_t;
    //结构体,描述矩形
    typedef struct{
        pt_t p1;
        pt_t p2;
    }rect_t;
    
    int main(void){
        rect_t r;
        printf("请输入两个点的横纵坐标:");
        scanf("%d%d%d%d",&r.p1.x,&r.p1.y,&r.p2.x,&r.p2.y);
        
        return 0;
    }
    
  • 内存对齐补齐

    • 在c语言中,内存对齐和补齐是为了优化内存访问速度和处理器对数据的读取而进行的一种内存布局优化技术。当你定义一个结构体或变量时,编译器会根据特定规则对其进行对齐和补齐,以确保数据按照规定的边界对齐存储

    • 内存对齐指的是将数据存储在特定的内存地址上,以使其起始地址能够被特定字节大小整除。常见的对齐边界包括1字节、2字节、4字节、8字节等。例如,当一个4字节的整型变量按照4字节对齐时,它的起始地址应该是4字节的倍数

    • 内存补齐指的是在数据结构中添加额外的填充字节,以使结构体或变量的大小满足对齐要求。这样做是为了提高数据访问的效率,因为许多计算机体系结构对于未按照对齐安求存储的数据的访问效率较低。填充字节在结构体的成员之间插入,以确保每个成员都按照规定的对齐要求存储

    • 示例

      #include<stdio.h>
      
      typedef struct{
          char c;//1字节
          short s;//2字节
          int i;//4字节
      }a_t;
      
      typedef struct{
          char c;
          int i;
          short s;
      }b_t;
      //指定按多少个字节大小进行对齐补齐
      #pragma pack(1)//对下方所有代码有效
      typedef struct{
          char c;
          int i;
          short s;
      }c_t;
      int main(void){
          printf("a_t类型大小:%lu\n",sizeof(a_t));//8
          printf("b_t类型大小:%lu\n",sizeof(b_t));//12
          printf("c_t类型大小:%lu\n",sizeof(c_t));//7
          return 0;
      }
      

联合体

  • 联合也可以用来创建新的数据类型,联合类型也需要先声明然后才能使用,声明联合类型的时候需要使用union关键字

  • 联合成员变量对应的存储区在内存里互相重叠,共用一块存储区

  • 联合里所有成员变量的开始地址都一样

  • 联合变量的大小就是它占地最大成员变量的大小

  • 联合变量可以当做多种不同类型的变量使用

  • 示例

    #include<stdio.h>
    typedef union {
        char c;
        short s;
        int i;
    }u_t;
    
    int main(void){
        u_t u = {.i = 0x12345678};
        printf("sizeof(u_t) = %lu\n",sizeof(u_t));
        printf("u.c = %hhx\n",u.c);//78
        printf("u.s = %hx\n",u.s);//5678
        printf("u.i = %x\n",u.i);//12345678
        
        return 0;
    }
    
  • 数据传输场景

    #include<stdio.h>
    union msg{
        unsigned char buf[48];
        struct cmd{
            unsigned char head[4];
            unsigned char data[40];
            unsigned char tail[4];
        }cmd_info;
    };
    

枚举

  • 枚举是一个有限整型常量的列表,每个枚举值都是一个符号常量
  • 默认从0开始,向后依次加1
  • 可以通过赋值改变枚举的值,没有赋值的枚举值=上一个枚举值+1
  • 格式:enum 枚举类型 {枚举值列表}

动态分配内存

可以在程序运行的时候临时决定需要分配的存储区个数,这种分配方式叫做动态内存分配

  • 使用管理动态分配内存的标准函数需要引入头文件stdlib.h

  • malloc函数可以动态分配一组连续的存储区

    • 需要一个整数类型的参数表示希望分配的字节个数
    • 返回值表示分配好的第一个字节的地址
    • 内存分配失败返回NULL
    • 返回值记录在无类型指针存储区里,需要先强制类型转换成有类型指针后使用,但不写编译器也会进行隐式类型转换
  • free函数用来释放动态分配的内存

    • 计算机不会主动回收动态分配的内存,当程序不需要动态分配内存后就应该主动释放,否则会造成内存泄漏
    • 需要第一个字节的地址作为参数
    • 如果使用指针作为参数调用free函数则函数结束后必须把指针设置成空指针
  • calloc函数也可动态分配内存

    • 第一个参数是想要分配存储区的个数,第二个参数是每个分配存储区的大小
    • 和malloc的区别:calloc函数分配的存储区全部初始化为0,malloc函数分配的存储区没有初始化,为随机数
  • 示例

    #include<stdio.h>
    
    int main(void){
        int* p = NULL;
        p = calloc(5,sizeof(int));
        if(p == NULL){
            printf("calloc分配失败\n");
            return -1;
        }
        for(int i = 0;i < 5;i++){
            p[i] = 100 + i;
            printf("p[%d] = %d\n",i,p[i]);
        }
        
        free(p);
        p = NULL;
        
        return 0;
    }
    
  • realloc函数重新分配内存

    • 需要两个参数,第一个参数:首地址(malloc函数返回值),第二个参数:从首地址上调整后的字节个数
    • 返回值是重新分配后的地址,一般就是原来的首地址
    • 原有内容不会发生改变

文件操作

文件是数据的集合

  • 内存掉电会丢失,闪存(磁盘)掉电不丢失
  • 程序在内存中运行,运行产生的数据保存在内存中,掉电数据即丢失,将数据保存在闪存中可实现长时间保留
  • 要将数据存储到闪存中,以文件的方式将数据保存
  • 文件操作基本步骤
    1. 打开文件 -fopen
    2. 操作文件 -fwrite/fread
    3. 关闭文件 -fclose
  • fopen的第一个参数是文件路劲,第二个参数是打开方式,返回值是FILE*类型
  • 文件打开方式
    • r -只读,文件必须存在,从头开始读
    • w -只写,文件不存在就创建,存在就清空,从头开始写
    • a -追加,文件不存在就创建,存在不清空,从尾开始写
    • r+ -读写,文件必须存在,从头开始写
    • w+ -读写,文件不存在就创建,存在就清空,从头开始读写
    • a+ -追读,文件不存在就创建,存在不清空,从头开始读,从尾开始写
  • fwirte函数的功能是将内存中的数据写入到文件所在的硬盘中
    • 参数一:要向文件中写入的数据的存储区首地址
    • 参数二:要写入的元素占据的字节数
    • 参数三:要写入的元素个数
    • 参数四:要写入的文件的文件指针
    • 返回值:实际写入的元素个数

文件读写位置

  • 计算机里为每个打开的文件保留一个整数,这个整数表示下一次文件读写操作的开始位置
  • 这个整数就是文件头到这个位置之间包含的字节个数
  • 这个整数叫做文件的位置指针,每当从文件里获得n个字节或向文件里写入n个字节以后位置指针都会向后移动n个字节
  • ftell函数可以用来获得位置指针的数字
  • rewind函数可以把位置指针移动到文件开头
  • fseek函数可以把位置指针移动到文件里的任何位置
    • fseek函数里需要指定一个基准位置以及目标位置到基准位置之间的距离
    • 参数一:文件指针,表示要操作的文件
    • 参数二:偏移量,整数向后移动,负数向前移动。偏移量是元素的大小,如三个int型数据的偏移量为12
    • 参数三:基准位置
      1. SEEK_SET 0 把文件头作为基准位置
      2. SEEK_CUR 1 把当前位置作为基准位置
      3. SEEK_END 2 把文件尾作为基准位置
  • fprintf函数可以直接把数字记录到文件中
    • 第一个参数是文件指针,后面的参数是printf函数的参数 fprintf(fp,“%d\n”,a);
  • fscanf函数可以从文本文件里获得数字并记录到存储区
    • 第一个参数是文件指针,后面的参数是scanf函数的参数 fscanf(fp,“%d”,&a);
  • fprintf和fscanf执行效率较低,不适合处理大数据量的文件
  • 三个标准文件指针
    • 标准输入 stdin 键盘 printf(“%d\n”,a); == fprintf(stdout,“%d\n”,a);
    • 标准输出 stdout 终端窗口 scanf(“%d”,&a); == fscanf(stdin,“%d”,&a);
    • 标准错误 stderr 终端窗口 直接输出到显示器
  • 23
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值