音视频学习之路--C语言(2)

419 阅读8分钟

前言

C和C++作为学习音视频技术首要具备的语言基础,所以十分必要学习和复习一下之前学习的C语言基础。

正文

前面有一篇文章已经介绍了不少关于C的知识点,下面我们继续。

结构体

不论是C还是Java,都不能只有那几种基本数据类型,当然也需要一种类的概念,在Java中是面向对象,也就是类,在C中我们需要使用结构体。

结构体允许C语言创建一种自定义的数据类型,使用struct关键字,这个也非常容易理解,代码如下:

#include <time.h>
#include <stdlib.h>

struct Book{
    char title[50];
    char author[50];
    char subject[50];
    int book_id;
};

int main() {
    struct Book androidBook;
    strcpy(androidBook.title,"第一行代码");
    strcpy(androidBook.author,"郭霖");
    strcpy(androidBook.subject,"android");
    androidBook.book_id = 100;

    printf("book info : title = %s \n "
           "author = %s \n "
           "subject = %s \n "
           "id = %d \n",
           androidBook.title,androidBook.author,androidBook.subject,androidBook.book_id);
    return 0;
}

这里注意如果中文显示不出来,需要设置IDE的编码,可以设置未UTF-16,默认是UTF-8,上面代码打印是:

image.png

由于结构体不像Java类可以定义什么get/set函数,所以这里赋值就直接使用strcpy或者直接赋值,在获取结构体成员时使用.调用。

结构体指针

既然结构体属于自定义的类型,那一定就可以定义指向这种类型的结构体指针了,这里也非常简单,主要点就是通过指针获取结构体的成员可以使用箭头 -> 来进行,当然也可以先取值,用点 . 是一样的,下面是代码:

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

struct Book{
    char title[50];
    char author[50];
    char subject[50];
    int book_id;
};

int main() {
    struct Book androidBook;
    strcpy(androidBook.title,"第一行代码");
    strcpy(androidBook.author,"郭霖");
    strcpy(androidBook.subject,"android");
    androidBook.book_id = 100;

    printf("book info : title = %s \n "
           "author = %s \n "
           "subject = %s \n "
           "id = %d \n",
           androidBook.title,androidBook.author,androidBook.subject,androidBook.book_id);
    
    //使用结构体指针
    struct Book *pBook;
    pBook = &androidBook;
    printf("book info : title = %s \n "
           "author = %s \n "
           "subject = %s \n "
           "id = %d \n",
           pBook -> title,
           (*pBook).author,
           pBook -> subject,
           pBook -> book_id);
    return 0;
}

这里使用pBook指针,打印如下:

image.png

位域

不得不说,C的内存使用还是讲究啊,就比如这个位域,在结构体中有些信息在存储时并不需要一个完整的字节,也就是8个二进制位,这时就可以按二进制来保存信息,减小内存使用。比如存放一个开关变量,只需要0和1即可,比如下面代码:

struct Bean{
    unsigned a:1;
    //这里空7位,可以不定义成员直接空着
    int :7;
    unsigned b:6;   //范围是0到63
    //一个成员变量不会存储到2个字节中,所以这里默认也是空2位
    unsigned c:7;   //范围是0到127
};

int main() {

    struct Bean bean;
    struct Bean *pBean;
    bean.a = 0;
    //64无法正确保存到b中,b会全是0
    bean.b = 64;
    bean.c = 100;
    printf("bean的值分别是 %d %d %d \n",bean.a,bean.b,bean.c);

    pBean = &bean;
    pBean -> a = 1;
    pBean -> b = 62;
    pBean -> c = 129;
    printf("bean的值分别是 %d %d %d \n",pBean -> a,pBean ->b,pBean -> c);

    return 0;
}

上面注释已经写的很清楚了,所以打印可以预知如下:

image.png

所以这里的位域只对保存信息比较小的时候有用,也就是小于8个字节,给拆开的情况。

共用体

这个共用体感觉有点奇怪又合理,它是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型,但是可以定义一个带多成员的共用体,任何时候只有一个成员带有值,直接看示例代码就明白:

union Data{
    int i;
    float f;
    char str[20];
};

int main() {

    union Data data;

    //这种访问共用体是错误的
    data.i = 10;
    data.f = 2.9f;
    strcpy(data.str,"android");
    printf("data.i : %d \n",data.i);
    printf("data.f :%f \n",data.f);
    printf("data.str : %s \n",data.str);

    //这种才是正确的
    data.i = 10;
    printf("data.i : %d \n",data.i);
    data.f = 2.9f;
    printf("data.f :%f \n",data.f);
    strcpy(data.str,"android");
    printf("data.str : %s \n",data.str);

    return 0;
}

上面代码定义了共用体以及错误和正确的访问方式,看一下打印:

image.png

由于共用体只能由一个成员带值,所以第一种访问肯定是不对的,很容易理解。

typedef

这个很容易理解,从名字就看的出来类型定义,也就是给类型取一个新名字。但是也有一个关键字也可以实现,那就是#define,这个其实是预处理指令,它俩还是有区别的:

  • typedef仅仅用于类型符号的别名,#define不仅可以为类型起别名,也可以为数值,比如定义Π为3.14。

  • typedef是由编译器执行解释的,#define语句是由预编译器进行处理的。

输入和输出

这里先说输入和输出是键盘和屏幕,其中涉及3类方法,代码如下:

int main() {

    //scanf和printf
    float f;
    printf("输入一个float值 \n");
    scanf("%f",&f);
    printf("输入的值是 %f \n",f);

    //getchar和putchar
    int c;
    printf("输入一个char值 \n");
    c = getchar();
    putchar(c);

    //gets和puts
    char str[100];
    printf("输入一个字符串");
    gets_s(str,10);
    puts(str);

    return 0;
}
  • scanf()和printf(),用于从标准键盘读取并且格式化,标准输出到屏幕。

  • getchar()和putchar(),用于读取一个字符和输出一个字符。

  • gets()和puts(),用于读取和输出字符串。

文件读写

其实文件读写和上面说的输入和输出是一样的,包括API设计思想也是类似,这里主要是有个FILE指针就是用来控制文件的,直接看代码即可:

int main() {

    //文件写入
    FILE *fp = NULL;
    //返回一个FILE指针
    fp = fopen("C:\Users\wayee\CLionProjects\Ctest\test.txt","a+");
    //通过fprintf写文件,其中fp的第一个参数
    fprintf(fp,"fprintf添加 \n");
    //通过fputs写文件,其中fp是第二个参数
    fputs("fputs添加\n",fp);
    fclose(fp);

    //文件读取
    FILE  *p = NULL;
    p = fopen("C:\Users\wayee\CLionProjects\Ctest\test.txt","r");
    //需要一个缓冲区
    char buffer[255];
    //使用fscanf读取文件
    fscanf(p,"%s",buffer);
    printf("通过fscanf读取 : %s \n",buffer);
    //通过fgets读取文件
    fgets(buffer,255,p);
    printf("通过fgets读取 : %s \n",buffer);
    fgets(buffer,255,p);
    printf("通过fgets读取 : %s \n",buffer);
    fclose(p);

    return 0;
}

其中txt文件如下:

image.png

打印如下:

image.png

其中主要就是通过fscanf和fgets这2个函数来读取文件。

预处理器

C语言有预处理器,这个还是比较特殊的,至少在Java中没有这个概念,说编译就编译了,那这个预处理器是啥意思呢,其实就是一个文本替换工具而已。

C的预处理器也就是CPP,会在实际编译器完成处理,所有预处理命令都是以#开头。

预处理命令.png

其实看着多,都非常好理解,不外乎就是判断某个宏是否定义了,或者条件判断,预处理在C语言代码中编译有着很重要的作用。

除了上面的几个,还有一些预处理运算符在代码中也非常有用,

宏运算符.png

下面是简单的示例代码,加强记忆:


#include <stdio.h>
#include <time.h>
#include <stdlib.h>

//使用宏延续运算符
#define message_for(a,b) \
    printf(#a " and " #b ": love \n")
//使用粘贴##,把token和n给粘贴为一个标记
#define tokenPaster(n) printf("token"#n" = %d \n",token##n)
//参数化的宏,来定义一个x*x的函数
#define square(x) ((x) * (x))

int main() {
    //使用字符串常量化运算符
    message_for(Carole,Debra);
    //粘贴
    int token34 = 40;
    tokenPaster(34);
    //参数化的宏
    int j = square(5);
    printf("j = %d",j);
    return 0;
}

打印结果是:

image.png

头文件

在C语言中有头文件的概念,是以.h为扩展名,这类文件在Java中是不存在的,所以为什么在C语言中要搞一个头文件的概念呢?

我查阅了相关文章,其实如果你不要这个头文件也可以,学过Java的都知道这个#include其实和import是一样的功能,但是Java中一般是导入一个类或者变量等,而#include是导入头文件,当然#include也可以导入方法、变量,那为什么不直接用#include来导入方法或者变量呢,就不用定义头文件了。

原因还是C中有了头文件可以更方便的判断编译,如果没有头文件的概念,那条件编译会有很多判断,所以我们来看一下要把什么东西放到头文件中:

头文件.png

当然这里就不细说了,等后面具体代码再讨论,头文件主要就是为了让代码文件结构更清晰和条件编译。

可变参数

可变参数这个在kotlin中用的很多,尤其是数组arrayOf类似的函数,但是在C语言中这个可变参数是如何定义和解析的呢?

其实这个还是比较复杂的,我们直接看代码和注释即可:

//多参数 其中num是多参数的个数,...表示参数
double average(int num,...){
    //先定义一个va_list变量
    va_list  vaList;
    double sum = 0.0;
    //开始解析多参数,解析num个,放入vaList中
    va_start(vaList,num);
    for (int j = 0; j < num; ++j) {
        //使用va_arg获取多参数的每个参数值
        sum += va_arg(vaList,int );
    }
    //结束解析
    va_end(vaList);
    return sum/num;
}

int main() {
    printf("average of 2,3,4,5 = %f \n", average(4,2,3,4,5));
    return 0;
}

这里代码是求2,3,4,5这4个数的平均数,打印如下:

image.png

总结

C学习大概先到这里,等后面学习具体项目再进行补充,这2篇C语言的文章只是复习一些C语言的基础知识,上一篇文章是:

# 音视频学习之路--C语言(1)

可以结合一起看,用来复习一下C语言。