基础
函数
函数定义: 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main(),所有简单的程序都可以定义其他额外的函数。
return_type function_name(parameter list){ |
指针函数: 指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针。
如:类型标识符* 函数名(参数表)
int* func(x, y)函数指针: 是指向函数的指针变量,即本质是一个指针变量。
如:int (*f)(int x); /* 声明一个函数指针 */
func; /\* 将func函数的首地址赋给指针f \*/指针函数 和 函数指针 最大的区别在于: 后者是一个变量的声明,只是这个变量是一个方法体;而前者则是一个指针。
Linux 内存布局原理
内存
硬件角度: 内存是计算机必不可少的一个组成部分,是与 CPU 沟通的桥梁,计算机中所有的程序都是运行在内存中的。
逻辑角度: 内存是一块具备随机访问能力,支持读写操作,用来存放程序及程序运行中产生数据的区域。
内存单位
每一块内存都是有大小的,其衡量单位为:
位(bit):电子计算机中最小的单位,每一位的状态只能是0或者1。
字节(Byte): 1Byte = 8bit,是内存基本的计量单位。
KB:1KB = 1024Byte,也就是1024个字节。
MB:1MB = 1024KB,当然更大的还有 GB、TB。
内存编址
计算机中的内存按字节编址,每个地址的存储单元可以存放一个字节(8个bit)的数据,CPU 通过内存地址获取指令和数据,并不关心这个地址所代表的空间具体在什么位置、怎么分布,因为硬件的设计保证一个地址对应着一个固定的空间,所以说:内存地址和地址指向的空间共同构成了一个内存单元。
内存地址
通常用16进制的数据表示,指向内存中某一块区域。
内存分配规则: 内存分配规则是连续的,一个挨着一个。
内存对象 Vs 内存地址
指针指向的内存区域能存储不同的类型。
基本数据类型
内存组成
Android 内存组成
C 语言内存组成
共用体
定义: 共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。并且可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。
语法规则如下:
union Data |
【注意】 共用体占用的内存应足够存储共用体中最大的成员。例如,上述示例,Data将占用20个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。
结构体
类似于 Java 中的的 JavaBean,和共同体一样,都是一种数据类型的集合,但是结构体需要更大的内存开销。
语法规则如下:
// Student 相当于类名 |
内存对齐
在 C 执行的过程中,特别是给对象、结构体分配内存的时候,是遵循内存对齐规则的。
定义: 对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称作自然对齐。比如在32位CPU下,假设一个整型变量的地址为 0x00000004,那他就是自然对齐的。
结构体大小
定义: 当结构体需要内存过大,使用动态内存申请。结构体占用字节数和结构体内字段无关,指针占用内存就是4/8字节,因此传指针比传值效率更高。
结构体存储原则
(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0是被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。
示例
// Student 相当于类名 |
按常理来说内存会占据 6个字节,但是使用内存对齐后就会占据 12 个字节。
为什么?
因为在32位操作系统(虽然64位操作系统,但是为了保证兼容性,编程仍然只考虑32位)中,数据总线是32位,地址总线是32位。
地址总线是32位,意味着寻址空间是按4步长递增的;同时数据总线32位意味着一次可读写4byte。
图分析如下:
左图:同样一个int类型变量,要通过两次寻址才可以访问完全,但是右图不会
so 动态库与编译
库的概念
定义: 在 windows 平台和 Linux 平台下都大量存在着库。Android 中也存在库,顾名思义,库就是指一个容器文件,里面装载的就是函数。由于 windows 和 Linux 的平台不同(主要是编译器、汇编器以及连接器的不同),因此二者库的二进制是不兼容的。
库存在的意义
定义: 库是别人写好的现有的,成熟的,可以复用的代码。现实中每个程序都需要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。
库的种类
分为动态库和静态库,在windows下,动态库的文件格式是 .dll,而静态库则是 .lib,在 Linux 下动态库的文件格式则是 .so,静态库是 .a
静态库
- 通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行的文件(executable file)。程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被称为静态库(static library),通常文件名为 “libXXX.a” 的形式
动态库
把对一些库函数的链接推迟到程序运行的时期(runtime);
可以实现进程之间的资源共享;
将一些程序升级变得简单;
甚至可以真正做到链接载入完全由程序猿在程序代码中控制。
那么如何编译动态库?如下图所示:
动态库编译需要 gcc 因此Android开发需要 NDK
编译 .so 命令:gcc test.c -fPIC -shared/static -o test.so
动态库与静态库的区别
静态库文件比较大,动态库一般都比较小;
静态库需要在编译时 被链接在目标代码中,动态库则是在运行时才会被加载到目标代码;
静态库类似于 Android 中的 Module,一旦打包APK则需要重新进行编译;
动态库类似于 jar 包,打包是不需要重新进行编译的。
交叉编译 Vs 本地编译
本地编译
- 在当前编译平台下,编译出来的程序只能放到当前平台下运行,比如,我们在 x86 平台上,编写程序并编译成可执行程序。这种方式下,我们使用 x86平台下上的工具,开发针对 x86 平台本身的可执行程序,这个编译过程称为本地编译。
交叉编译
- 在当前编译平台下,编译出来的程序能运行在体系结构不同的另一种目标平台上,但是编译平台本身却不能运行该程序。比如,我们在 x86平台上,编写程序并运行在 ARM 平台的程序,编译得到的程序在 x86平台上是不能运行的,必须放到 ARM 平台上才能运行。
交叉编译链
交叉编译器
- 只交叉编译的gcc,即在一种计算机环境中运行的编译程序,能编译出在另外一种环境下运行的代码。
交叉编译链的命名规则
arm-none-linux-gnueabi-gcc
arm-cortex-a8-linux-gnueabi-gcc
mips-malta-linux-gnu-gcc
arch-core-kernel-system
arch:用于哪个目标平台
core:使用的是哪个CPU Core,如Cortex A8
kernel:所运行的OS,比如Linux、uclinux、bare(无OS)
system:交叉编译链所选择的库函数和目标映像的规范,如 gnu,gnueabi等。其中 gnu 等价于 glibc + oabi;gnueabi 等价于 glibc + eabi。
基础进阶
类型转换
除了能使用c语言的强制类型转换外,还有:转换操作符 (新式转换)
const_cast
修改类型的const或volatile属性
const char *a; |
static_cast
- 基础类型之间互转。如:float转成int、int转成unsigned int等
- 指针与void之间互转。如:float*转成void*、Bean*转成void*、函数指针转成void*等
- 子类指针/引用与 父类指针/引用 转换。
class Parent { |
dynamic_cast
主要 将基类指针、引用 安全地转为派生类.
在运行期对可疑的转型操作进行安全检查,仅对多态有效
//基类至少有一个虚函数 |
reinterpret_cast
对指针、引用进行原始转换
float i = 10; |
char*与int转换
//char* 转int float |
异常
void test1() |
文件与流操作
C 语言的文件读写操作
头文件:stdio.h
函数原型:FILE fopen(const char path, const char * mode);
path: 操作的文件路径
mode:模式
模式 | 描述 |
---|---|
r | 打开一个已有的文本文件,允许读取文件。 |
w | 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。 |
a | 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。 |
r+ | 打开一个文本文件,允许读写文件。 |
w+ | 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。 |
a+ | 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。 |
//======================================================================== |
C++ 文件读写操作
\
和 \
数据类型 | 描述 |
---|---|
ofstream | 输出文件流,创建文件并向文件写入信息。 |
ifstream | 输入文件流,从文件读取信息。 |
fstream | 文件流,且同时具有 ofstream 和 ifstream 两种功能。 |
char data[100]; |
C/C++ 文件编译过程
预处理
预处理
- 预处理阶段主要处理include和define等。它把#include包含进来的.h文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替。
编译
编译阶段
- 编译器检查代码的规范性、语法错误等,检查无误后,编译器把代码翻译成汇编语言。
汇编
汇编阶段
- 把 .s文件翻译成二进制机器指令文件 .o,这个阶段接收 .c、.i、.s的文件都没有问题。
链接
链接阶段
- 链接的是其余的函数库,比如我们自己编写的c/c++文件中用到了三方的函数库,在链接阶段就需要链接三方函数库,如果链接不到就会报错。