音视频学习之路--C语言(2)
前言
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,上面代码打印是:
由于结构体不像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指针,打印如下:
位域
不得不说,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; }复制代码
上面注释已经写的很清楚了,所以打印可以预知如下:
所以这里的位域只对保存信息比较小的时候有用,也就是小于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; }复制代码
上面代码定义了共用体以及错误和正确的访问方式,看一下打印:
由于共用体只能由一个成员带值,所以第一种访问肯定是不对的,很容易理解。
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文件如下:
打印如下:
其中主要就是通过fscanf和fgets这2个函数来读取文件。
预处理器
C语言有预处理器,这个还是比较特殊的,至少在Java中没有这个概念,说编译就编译了,那这个预处理器是啥意思呢,其实就是一个文本替换工具而已。
C的预处理器也就是CPP,会在实际编译器完成处理,所有预处理命令都是以#开头。
其实看着多,都非常好理解,不外乎就是判断某个宏是否定义了,或者条件判断,预处理在C语言代码中编译有着很重要的作用。
除了上面的几个,还有一些预处理运算符在代码中也非常有用,
下面是简单的示例代码,加强记忆:
#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; }复制代码
打印结果是:
头文件
在C语言中有头文件的概念,是以.h为扩展名,这类文件在Java中是不存在的,所以为什么在C语言中要搞一个头文件的概念呢?
我查阅了相关文章,其实如果你不要这个头文件也可以,学过Java的都知道这个#include其实和import是一样的功能,但是Java中一般是导入一个类或者变量等,而#include是导入头文件,当然#include也可以导入方法、变量,那为什么不直接用#include来导入方法或者变量呢,就不用定义头文件了。
原因还是C中有了头文件可以更方便的判断编译,如果没有头文件的概念,那条件编译会有很多判断,所以我们来看一下要把什么东西放到头文件中:
当然这里就不细说了,等后面具体代码再讨论,头文件主要就是为了让代码文件结构更清晰和条件编译。
可变参数
可变参数这个在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个数的平均数,打印如下:
总结
C学习大概先到这里,等后面学习具体项目再进行补充,这2篇C语言的文章只是复习一些C语言的基础知识
作者:元浩875
链接:https://juejin.cn/post/7020979685332877320