Android的NDK开发中,C语言需要掌握到什么程度?要学哪些?

Android这门学问很深,远远不是“用轮子进行组装”那么简单。他不仅仅有宽度还有广度,比如NDK 开发模块,我们还需要深入底层,学习C语言。

笔者的唠叨

“学C语言?”
“是的。”

或许这听起来很吃力,但是,付出与收获是成正比的,现在的市场上,NDK 开发者是十分吃香的,大家可以看看下面的招聘信息。

看到这里是不是觉得还算物有所值?这是一个值得一试的方向。

音视频开发该怎样学习?

其实也没什么太多的取巧,只是学习这件事,必须得有体系,有条理,循序渐进,这样才能完全的掌握这些重要的知识点和技能。

1、首先是要了解NDK基础知识体系,在脑子里有一个基本的学习路线和框架,主要有下面的这些关键点:

  • Linux环境搭建,系统管理,权限系统和工具使用(vim等)

  • Shell脚本编程

  • Native开发工具
    image

  • JNI开发

  • C与C++
    image

2、底层图片处理
image

3、最重要的是要进行音视频开发实战训练。光说不练假把式,下面以斗鱼直播为例
image

对NDK方向感兴趣的朋友可以(直接点击我)加入我们Android NDK 技术交流圈,一起成长,共同进步。

下面就带大家学一波C语言吧。


C语言,走起!

数据类型

C 语言中数据类型分有符号和无符号,默认是有符号的。

类型 同义词 存储空间
signed char 1 字节
int signed int、signed 2 或 4 字节
short signed short、short int、signed short int 2 字节
long signed long、long int、signed long int 4 字节
long long (C99) signed long long、long long int、signed long long int 8 字节
类型 同义词 存储空间
unsigned char 1 字节
unsigned int unsigned 2 或 4 字节
unsigned short unsigned short int 2 字节
unsigned long unsigned long int 4 字节
unsigned long long (C99) unsigned long long int 8 字节

C 语言定义的 int 长度是不比 short 短,不比 long 长。具体长度取决于编译时指定的目标平台。

是「名称 - 替换文本」的映射,预处理时会将源码中出现宏名称的地方展开为指定的替换文本;

宏定义:
#define ARRAY_SIZE 100

使用宏:
double data[ARRAY_SIZE]
复制代码

带参数的宏 注意宏名称和左括号之间不能有空格,否则会变成无参数的宏。

#define DISTANCE(x, y) ((x)>=(y) ? (x)-(y) : (y)-(x))
int d = DISTANCE(1,2
复制代码

变量

变量类型 作用域 生命周期 内存区域
局部变量 函数内部 函数 栈区
全局变量 整个项目 进程 数据区
静态局部变量 函数内部 进程 数据区
静态全局变量 源文件内部 进程 数据区

函数

函数类型 作用域 存储位置
全局函数 整个项目 代码区
静态函数 源文件内部 代码区
  • 函数声明 必须放在函数调用之前,函数声明可以省略形参声明,这依赖于 C语言没有函数重载(C++有函数重载)。函数定义 不一定要放在函数调用之前。
  • printf 占位符
占位符 含义
%d、%i 有符号整数
%u 无符号整数,最高位当作值的一部分
%o 有符号整数(八进制)
%x 有符号整数(八进制)
%X 有符号整数(十六进制),字母按大写显示
%c 字符
%s 字符串
  • inline 内联函数: 函数体在编译时直接嵌入函数调用位置,运行时不会再执行函数调用指令,省去了函数调用的开销。内联是以代码膨胀为代价的,因为每一处内联函数的调用的位置都复制了一份代码,程序总代码量增大,程序会占用更大的内存空间。所以内联函数只适合简短的函数,另外内联函数不能直接递归调用。
inline void fun(); // 无效
void fun() {
}
--------------------------
void fun();
inline void fun() { // 有效
}
复制代码

注意:inline 需要应用在 函数定义 才有效,应用在 函数定义 是无效的。

关键词

static 关键字的作用

  • 静态存储类型: 用于修饰变量,使之成为静态局部变量或静态全局变量;
  • 静态函数: 用于修饰函数,使之成为静态函数。

const 关键字的作用

  • 修饰变量 / 形参: 变量值不能修改
const int a = 1;
a = 2; (X)// Cannot assign to variable 'a' with const-qualified type 'const int'

注意:const 和 类型 int 可以互换位置,以下两条语句是等价的。
const int a = 1;
int const a = 1;
复制代码
  • 修饰指针: const 可以配合指针变量使用,记忆方法如下:
    • 1、const 离变量名近是修饰指针变量本身,指针指向不可变;
    • 2、const 离变量名远是修饰指针指向的数据,指针指向的数据不可变;
const int * p; // const 离变量名远
int const * p; // const 离变量名远
int * const p; // const 离变量名近

const int * const p; // 指针和指针指向的数据都只读
int const * const p; // 指针和指针指向的数据都只

int a = 1;
const int * p = &a;
*p = 2; (X)// Read-only variable is not assignable
复制代码
  • 修饰函数返回值: 一般用于返回指针的函数,表示不允许修改返回指针指向的数据
const * int func(); // const 离变量名远,上面提到的 const int * p 的情况。
复制代码

const 和 define 的对比

const 和 define 有类似之处,它们都定义了一个不允许修改的值,如果只从功能上看,它们都是 “常量”。但是从实现原理上看,它们并不是常量。

  • define 是预编译指令,define 定义的宏在预处理阶段就展开为指定的文本,define 定义的宏在编译后就不存在了。
  • const 是变量修饰符,本质上是一个 只读变量,一般所谓 “const 常量”,是从功能上对 const 的描述。

extern 关键字的作用

  • 修饰变量或函数: 表示该变量或函数是在外部文件中定义的全局变量或全局函数,提示编译器在外部文件中查找。

main.c

// 如果不使用 extern 修饰,会提示找不到变量。
extern int g ;
g = 1;

extern int g = 1; (X)// 这种写法不允许:'extern' variable cannot have an initializer
复制代码

other.c

int g;
复制代码
  • extern “C” : 实现 C++ 代码调用 C 语言代码,该代码将按照 C 语言的方式进行编译链接。
#ifdef __cplusplus
extern "C" {
#endif

// all of your legacy C code here

#ifdef __cplusplus
}
#endif
复制代码

volatile 关键字的作用

用于修饰变量,提醒编译器该变量随时可能发生改变,程序每次需要读取变量值的时候,都需要直接从变量地址中读取,而不应该使用寄存器中的缓存。

volatile int a;
复制代码

include 关键字的作用

#include <系统资源>
#include "项目资源"
复制代码

数组

字符串

  • 字符串本质上是以\0为结束符的字符数组,C 风格字符串自动在数组末尾增加\0结束符。
char *p = "xurui";
p[2] = '1'; 不能修改全局区域
printf("%s", p);
复制代码
  • 遍历字符串
char *str = "xurui";
while (*str) {
    ...
    str++
}
复制代码
  • 字符串转换

atoi: 将字符串转换为长整型

函数原型:参数是指向字符串的指针,返回值是长整形
int atoi(const char *str)

调用函数:
#include <stdlib.h>
int result = atoi("1");

类似函数:atol、atof、strtod
复制代码

怎么判断转换成功?

  • 字符串比较

strcmp: 比较两个字符串 strncmp: 比较两个字符串,最多比较前 n 个字符

函数原型:参数是两个字符串指针
int strcmp(const char *str1, const char *str2)
int strncmp(const char *str1, const char *str2, size_t n)

该函数返回值如下:
- 如果返回值小于 0,则表示 str1 小于 str2。
- 如果返回值大于 0,则表示 str1 大于 str2。
- 如果返回值等于 0,则表示 str1 等于 str2。

调用函数:
#include <string.h>
int result = strcmp("1", "2")
复制代码
  • 字符查找

strstr: 查找第一次出现的位置

函数原型:
char *strstr(const char *haystack, const char *needle)

调用函数:
char *text = "peng xurui";
char *subtext = "x";

char *pop = strstr(text, subtext);
printf("%s\n", pop); // 输出 xurui

int index = pop - text;
printf("索引:%d", index); // 输出 5
复制代码
  • 字符串拼接

strcat: 拼接两个字符串

函数原型:把 src 字符串追加到 dest 字符串的结尾
char *strcat(char *dest, const char *src)
复制代码
  • 大小写转换

tolower: 转换为小写字符 toupper :转换为大写字符

函数原型:把 c 字符转换为小写字符
int tolower(int c);

调用函数:
char c = 'A';
tolower(c);
复制代码
函数原型:把 c 字符转换为大写字符
int toupper(int c);

调用函数:
char c = 'a';
tolower(c);
复制代码

指针

  • & 取地址,* 取地址中的值,%p 地址的占位符。
  • 指针存放的是内存地址,不过指针本身也有内存地址。
  • 指针的大小 与指向的地址存储的变量类型无关,只与操作系统的字长有关。sizeof(int*) 在32 位系统中指针为 4 字节,64 位系统中指针为 8 字节。
  • 野指针 是指向不可用内存地址的指针,例如指针指向超出数组范围的地址。野指针不是空指针。
  • 函数指针 是指向函数的指针。函数的地址是函数存储空间的首地址。函数指针的定义方式为:*函数返回值类型 (指针变量名) (函数参数列表);
int Func(int x); 声明一个函数
int (*p) (int x); 定义一个函数指针
p = Func; 将 Func 函数的首地址赋给指针变量 p
(*p)(9); 调用函数
(p)(9); 可以省略 *,这里是函数指针特例
复制代码
  • 行指针
  • 列指针

堆内存管理

  • malloc: 在堆上分配一块新的连续空间
函数原型:参数是分配的字节数
void *malloc(sizt_t size);

调用函数:
#include <stdlib.h>

int *p = (int *) malloc(sizeof int);
if (!p) {
    printf("申请失败");
}
复制代码
  • calloc: 在堆上分配一块新的连续空间,并初始化为0
函数原型:参数是元素个数 + 元素占用字节数
void* calloc(size_t num_elements,size_t element_size);

调用函数:
#include <stdlib.h>

int *p = (int *) calloc(5, sizeof(int));
if (!p) {
    printf("申请失败");
}
复制代码
  • realloc: 调整一块已经分配的内存空间(扩大或缩小)
函数原型:参数是原地址的指针 + 新的内存大小

void realloc(void *ptr, size_t new_size);

调用函数:
#include <stdlib.h>

int *p = (int *) malloc(20 * sizeof(int));
int *p1 = (int *) realloc(p, 21 * sizeof(int));
int *p2 = (int *) realloc(p, 2000 * sizeof(int));

printf("%x\n", p); // 输出 ce4054c0
printf("%x\n", p1); // 输出 ce4054c0
printf("%x\n", p2); // 输出 ce809800(新开辟了一块内存空间)
复制代码

如果是将分配的内存扩大,则有以下情况: 1)如果当前内存段后面有足够内存空间,则直接扩展这段内存空间,realloc() 返回原指针; 2)否则,在堆中新开辟一块内存,并释放原来的内存块,返回新内存块地址; 2)否则,分配失败返回 NULL。

  • free: 释放动态分配的内存块
函数原型:参数是内存块的指针
free(void* pointer);

调用函数:
#include <stdlib.h>

if (p) {
    free(*p);
    p = NULL; // 防止存现悬空指针
}
复制代码

1)使用 malloc,calloc,realloc 分配的内存,必须通过 free 释放; 2)不能重复释放同一个内存块,因为释放后操作系统可能会把该空间分配给其他用途。

结构体

  • 声明结构体
struct Student {
    char *name;
    int age;
    char sex;
}; // 必须加分号
复制代码
  • 初始化结构体
// 方式 1:
struct Student mark;
mark.name = "mark";
mark.age = 10;
mark.sex = '1';

// 方式 2:
struct Student mark = {"mari", 10, '1'};

复制代码

注意:结构体中的成员变量初始化为系统值,而不像 Java 会给成员变量初始化为零值。

  • 声明时初始化
struct Student {
    char name[10];
    int age;
    char sex;
} mark = {"mark", 10, '1'},
tom, amy;
复制代码
  • 结构体嵌套
声明结构体:
struct Student {
    char name[10];
    int age;
    char sex;

    struct Sport {
        char *name;
    } sport;
};

使用结构体:
struct Student mark = {"mark", 10, '1'};
struct Student tom = {"tom", 1, '1'};

mark.sport.name = "basketball";
tom.sport.name = "football";

printf("%s\n", mark.sport.name); // basketball
printf("%s\n", tom.sport.name); // football
复制代码

注意: 嵌套的 sport 结构体是独立的两个结构体,是独立的两块内存。

  • 结构体指针

结构体指针是指向结构体的指针。

struct Student *p = &mark;
printf("%s\n", p->name); // mark
复制代码

其中,->是调用指针的一级成员变量。

  • 结构体的静态开辟和动态开辟
静态开辟:栈
struct Student mark;
mark.name = "mark";

动态开辟:堆
struct Student *markP = malloc(sizeof(struct Student));
markP->name = "mark";

printf("%s\n", markP->name); // mark
复制代码
  • 结构体数组

结构体数组是元素为结构体的数组。

静态开辟:栈
struct Student student[3] = {
            {"mark", 10, '1'},
            {"tom",  1,  '1'},
            {},
};
struct Student *p = &student;

printf("%s\n", student[0].name); // mark
printf("%s\n", (p + 1)->name); // tom

动态开辟:堆
struct Student *p = malloc(sizeof(struct Student) * 3);
p->name = "mark";
(p + 1)->name = "tom";

printf("%s\n", p->name); // mark
printf("%s\n", (p + 1)->name); // tom

free(p);
复制代码
  • 结构体别名
定义别名:
typedef struct Student Student_;
typedef struct Student * pStudent;

使用别名:
Student *p = malloc(sizeof(Student) * 3);
pStudent p = malloc(sizeof(Student) * 3);
复制代码

这里需要注意 Clion 和 VS 两个编辑器的差异化:

Clion 要求使用 struct Student
struct Student *p = malloc(sizeof(struct Student) * 3);
free(p);

VS 可以省略 struct 关键字
Student *p = malloc(sizeof(Student) * 3);
free(p);
复制代码

为了避免源码在不同编译器不统一,可以定义一个相同的别名:

定义一个相同的别名
typedef struct Student Student;

使用别名:
Student *p = malloc(sizeof(Student) * 3);
复制代码

提示: 你可以在源码中看到很多结构体都定义了别名,就是为了保持代码统一。C 语言枚举也有类似的问题。

  • 匿名结构体
struct { // 匿名结构体
    char *name;
}; // 不能引用,没有任何意义

struct { // 匿名结构体
    char *name;
} a1, a2, a3;
复制代码

文件操作

  • fopen :打开文件
函数原型:使用指定 mode 打开文件,返回 FILE 指针
FILE *fopen(const char *filename, const char *mode)

模式:
1)r:读取
2)w:写入
3)a:在文件后追加
4)r+
5)w+
6)a+

使用函数:
FILE *file = fopen("文件名", "r");
if (!file) {
    // 文件打开失败
}

char buffer[10];
while (fgets(buffer, 10, file) {
    printf("%s", buffer);
}
复制代码
  • fputs: 写入文件
函数原型:
int fputs(const char *str, FILE *stream)

使用函数:
FILE *file = fopen("文件名", "");
if (!file) {
    // 文件打开失败
}

fputs("xurui", file);
fclose(file);
复制代码
  • EOF: 文件的末尾(End of File)

内存区域

  • 从低地址到高地址,依次为:代码区、数据区、堆区和栈区。
  • 代码区: 可执行文件的二进制代码
  • 数据区:
    • data 段: 已经初始化的静态变量和全局变量
    • bss 段: 未初始化的静态变量和全局变量
    • rodata 段: 常量值(如字符串常量)
  • 栈区: 存储函数调用对应的栈帧
  • 堆区: 动态分配的内存

—— 图片引用自网络


音视频开发高效学习资料

这里给大家分享一位大佬整理的音视频开发高效学习资料。他把理论和产品结合起来,根据实际业务需求和上层来配合进行讲解。

所以这份资料不仅写逻辑,还有数据和其他UI展示,还会解说在做应用层的一些音视频相关逻辑,一线经验+实例代码,这样配合起来学习就会事半功倍。
获取文中完整资料(直接点击我)即可领取~

大家学习之后如果发现缺漏,还请多多指正。整理这份资料还要感谢很多博客大佬的帮助,这里就不赘言一一点名致谢了。

资料总目录
image

知识点

1、NDK 模块开发

  • C++与 C#数据类型总结
  • C 与 C++之内存结构与管理
  • C 与 C++之预处理命令与用 typedef 命名已有类型
  • C 与 C++之结构体、共用体
  • C 与 C++之指针
  • C/C++多线程操作说明
  • C/C++ 之函数与初始化列表
  • ……

image

2、JNI 模块

  • JNI 开发之 静态注册与动态注册(一)
  • JNI 开发之方法签名与 Java 通信(二)
  • JNI 开发之局部引用、全局引用和弱全局引用(三)
  • ……
    image

3、Native 开发工具

  • 十大最受欢迎的 React Native 应用开发编辑器
  • react-native 打包流程
  • 静态库和动态库
  • ABI 管理
  • 处理 CPU 功能
  • NEON 支持
  • ……
    image

4、Linux 编程

  • Linux 环境搭建,系统管理,权限系统和工具使用(vim 等)
  • Linux 系统管理操作(25 个命令)
  • Shell 脚本
  • 流程控制语句
  • 计划任务服务程序
  • ……
    image

5、底层图片处理

  • PNG/JPEG/WEBP 图像处理与压缩
  • 微信图片压缩
  • GIF 合成原理与实现
  • ……
    image

6、音视频开发

  • 多媒体系统
  • FFmpeg
  • 流媒体协议
  • OpenGL ES 滤镜开发之美颜效果
  • 抖音视频效果分析与实现
  • ……
    image

7、机器学习

  • Opencv
  • 图像预处理
  • 腐蚀与膨胀
  • 人脸检测
  • ID识别
  • ……
    image

笔者感想

从物种的起源开始,这个世界一直都是物竞天择,适者生存。人类的社会也一直在进步,如果不能保持学习和前进,就会成为“落后挨打”的那个。

回顾历史,2G让移动互联网走进我们的视野,让百度搜狐异军崛起;3G开启了即时通信,网上购物,成就了腾讯,京东等互联网巨头;4G 带来了短视频的兴起,成就了字节跳动、快手等黑马。

5G的风口又在什么地方呢?至少对于我们Android开发来说,音视频这个风口还在刮风,把握机会吧,北上广深很多年限上50w-70w的音视频岗位,常年还招不到人。别犹豫了,趁现在,提升自己!

获取文中完整资料(直接点击我)即可领取~

image

猜你喜欢

转载自blog.csdn.net/BUGgogogo/article/details/114807242
今日推荐