前言
C和C++作为学习音视频技术首要具备的语言基础,所以十分必要学习和复习一下之前学习的C语言基础。
视频教程:音视频开发全系列教程_哔哩哔哩_bilibili
正文
前面有一篇文章已经介绍了不少关于C的知识点,下面我们继续。
结构体
不论是C还是Java,都不能只有那几种基本数据类型,当然也需要一种类的概念,在Java中是面向对象,也就是类,在C中我们需要使用结构体。
结构体允许C语言创建一种自定义的数据类型,使用struct关键字,这个也非常容易理解,代码如下:
#include
#include
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
#include
#include
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
#include
#include
//使用宏延续运算符
#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语言的基础知识,上一篇文章是:
音视频开发学习之路--C语言(一) - (jianshu.com)
视频教程:音视频开发全系列教程_哔哩哔哩_bilibili
可以结合一起看,用来复习一下C语言。
本文转自 https://juejin.cn/post/7020979685332877320,如有侵权,请联系删除。