目录
  1. 1. 基础
    1. 1.1. 函数
    2. 1.2. Linux 内存布局原理
      1. 1.2.1. 内存
      2. 1.2.2. 内存单位
      3. 1.2.3. 内存编址
      4. 1.2.4. 内存地址
      5. 1.2.5. 内存组成
        1. 1.2.5.1. Android 内存组成
        2. 1.2.5.2. C 语言内存组成
    3. 1.3. 共用体
    4. 1.4. 结构体
    5. 1.5. 内存对齐
    6. 1.6. 结构体大小
    7. 1.7. 结构体存储原则
    8. 1.8. so 动态库与编译
      1. 1.8.1. 库的概念
      2. 1.8.2. 库存在的意义
      3. 1.8.3. 库的种类
        1. 1.8.3.1. 静态库
        2. 1.8.3.2. 动态库
      4. 1.8.4. 动态库与静态库的区别
      5. 1.8.5. 交叉编译 Vs 本地编译
      6. 1.8.6. 交叉编译链
  2. 2. 基础进阶
    1. 2.1. 类型转换
      1. 2.1.1. const_cast
      2. 2.1.2. static_cast
      3. 2.1.3. dynamic_cast
      4. 2.1.4. reinterpret_cast
      5. 2.1.5. char*与int转换
    2. 2.2. 异常
    3. 2.3. 文件与流操作
  3. 3. C/C++ 文件编译过程
高级Android-【C/C++理论实战技术】C C++基础篇

基础

函数

函数定义: 函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main(),所有简单的程序都可以定义其他额外的函数。

return_type function_name(parameter list){
// body of the dunction
}

指针函数: 指带指针的函数,即本质是一个函数。函数返回类型是某一类型的指针。

如:类型标识符* 函数名(参数表)

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进制的数据表示,指向内存中某一块区域。

内存分配规则: 内存分配规则是连续的,一个挨着一个。

内存地址分配规则.png

内存对象 Vs 内存地址

指针指向的内存区域能存储不同的类型。

基本数据类型

基本数据类型.png

内存组成

Android 内存组成

Android内存组成.png

C 语言内存组成

C内存组成.png

共用体

定义: 共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。并且可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。

语法规则如下:

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

【注意】 共用体占用的内存应足够存储共用体中最大的成员。例如,上述示例,Data将占用20个字节的内存空间,因为在各个成员中,字符串所占用的空间是最大的。

结构体

类似于 Java 中的的 JavaBean,和共同体一样,都是一种数据类型的集合,但是结构体需要更大的内存开销。

语法规则如下:

// Student 相当于类名
// student 和 a 可以不定义,表示结构变量,也就是 Student 类型的变量
struct Student {
char name[50];
int age;
}student,a;

// 使用 typedef 定义
typedef struct{
char name[50];
int age;
}Student;

内存对齐

在 C 执行的过程中,特别是给对象、结构体分配内存的时候,是遵循内存对齐规则的。

定义: 对齐跟数据在内存中的位置有关。如果一个变量的内存地址正好位于它长度的整数倍,他就被称作自然对齐。比如在32位CPU下,假设一个整型变量的地址为 0x00000004,那他就是自然对齐的。

结构体大小

定义: 当结构体需要内存过大,使用动态内存申请。结构体占用字节数和结构体内字段无关,指针占用内存就是4/8字节,因此传指针比传值效率更高。

结构体存储原则

  • (1)结构体变量中成员的偏移量必须是成员大小的整数倍(0是被认为是任何数的整数倍)

  • (2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。

示例

// Student 相当于类名
// student 和 a 可以不定义,表示结构变量,也就是 Student 类型的变量
struct Student {
// 占一个字节
char c1;
// 占四个字节
int i;
// 占一个字节
char c2;
}student,a;

按常理来说内存会占据 6个字节,但是使用内存对齐后就会占据 12 个字节。

为什么?

因为在32位操作系统(虽然64位操作系统,但是为了保证兼容性,编程仍然只考虑32位)中,数据总线是32位,地址总线是32位。

地址总线是32位,意味着寻址空间是按4步长递增的;同时数据总线32位意味着一次可读写4byte。

图分析如下:

左图:同样一个int类型变量,要通过两次寻址才可以访问完全,但是右图不会

结构体内存对齐.png

so 动态库与编译

库的概念

定义: 在 windows 平台和 Linux 平台下都大量存在着库。Android 中也存在库,顾名思义,库就是指一个容器文件,里面装载的就是函数。由于 windows 和 Linux 的平台不同(主要是编译器、汇编器以及连接器的不同),因此二者库的二进制是不兼容的。

库存在的意义

定义: 库是别人写好的现有的,成熟的,可以复用的代码。现实中每个程序都需要依赖很多基础的底层库,不可能每个人的代码都从零开始,因此库的存在意义非同寻常。

库的种类

分为动态库静态库,在windows下,动态库的文件格式是 .dll,而静态库则是 .lib,在 Linux 下动态库的文件格式则是 .so,静态库是 .a

静态库

  • 通常情况下,对函数库的链接是放在编译时期(compile time)完成的。所有相关的对象文件(object file)与牵涉到的函数库(library)被链接合成一个可执行的文件(executable file)。程序在运行时,与函数库再无瓜葛,因为所有需要的函数已拷贝到自己门下。所以这些函数库被称为静态库(static library),通常文件名为 “libXXX.a” 的形式

动态库

  • 把对一些库函数的链接推迟到程序运行的时期(runtime);

  • 可以实现进程之间的资源共享;

  • 将一些程序升级变得简单;

  • 甚至可以真正做到链接载入完全由程序猿在程序代码中控制。

那么如何编译动态库?如下图所示:

如何编译动态库.png

动态库编译需要 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;
char *b = const_cast<char*>(a);

char *a;
const char *b = const_cast<const char*>(a);

static_cast

  1. 基础类型之间互转。如:float转成int、int转成unsigned int等
  2. 指针与void之间互转。如:float*转成void*、Bean*转成void*、函数指针转成void*等
  3. 子类指针/引用与 父类指针/引用 转换。
class Parent {
public:
void test() {
cout << "p" << endl;
}
};
class Child :public Parent{
public:
void test() {
cout << "c" << endl;
}
};
Parent *p = new Parent;
Child *c = static_cast<Child*>(p);
//输出c
c->test();

//Parent test加上 virtual 输出 p

dynamic_cast

主要 将基类指针、引用 安全地转为派生类.

在运行期对可疑的转型操作进行安全检查,仅对多态有效

//基类至少有一个虚函数
//对指针转换失败的得到NULL,对引用失败 抛出bad_cast异常
Parent *p = new Parent;
Child *c = dynamic_cast<Child*>(p);
if (!c) {
cout << "转换失败" << endl;
}


Parent *p = new Child;
Child *c = dynamic_cast<Child*>(p);
if (c) {
cout << "转换成功" << endl;
}

reinterpret_cast

对指针、引用进行原始转换

float i = 10;

//&i float指针,指向一个地址,转换为int类型,j就是这个地址
int j = reinterpret_cast<int>(&i);
cout << hex << &i << endl;
cout << hex << j << endl;

cout<<hex<<i<<endl; //输出十六进制数
cout<<oct<<i<<endl; //输出八进制数
cout<<dec<<i<<endl; //输出十进制数

char*与int转换

//char* 转int float
int i = atoi("1");
float f = atof("1.1f");
cout << i << endl;
cout << f << endl;

//int 转 char*
char c[10];
//10进制
itoa(100, c,10);
cout << c << endl;

//int 转 char*
char c1[10];
sprintf(c1, "%d", 100);
cout << c1 << endl;

异常

void test1()
{
throw "测试!";
}

void test2()
{
throw exception("测试");
}

try {
test1();
}
catch (const char *m) {
cout << m << endl;
}
try {
test2();
}
catch (exception &e) {
cout << e.what() << endl;
}

//自定义
class MyException : public exception
{
public:
virtual char const* what() const
{
return "myexception";
}
};

//随便抛出一个对象都可以

文件与流操作

C 语言的文件读写操作

头文件:stdio.h

函数原型:FILE fopen(const char path, const char * mode);

path: 操作的文件路径

mode:模式

模式 描述
r 打开一个已有的文本文件,允许读取文件。
w 打开一个文本文件,允许写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会从文件的开头写入内容。如果文件存在,则该会被截断为零长度,重新写入。
a 打开一个文本文件,以追加模式写入文件。如果文件不存在,则会创建一个新文件。在这里,您的程序会在已有的文件内容中追加内容。
r+ 打开一个文本文件,允许读写文件。
w+ 打开一个文本文件,允许读写文件。如果文件已存在,则文件会被截断为零长度,如果文件不存在,则会创建一个新文件。
a+ 打开一个文本文件,允许读写文件。如果文件不存在,则会创建一个新文件。读取会从文件的开头开始,写入则只能是追加模式。
//========================================================================
FILE *f = fopen("xxxx\\t.txt","w");
//写入单个字符
fputc('a', f);
fclose(f);


FILE *f = fopen("xxxx\\t.txt","w");
char *txt = "123456";
//写入以 null 结尾的字符数组
fputs(txt, f);
//格式化并输出
fprintf(f,"%s",txt);
fclose(f);

//========================================================================
fgetc(f); //读取一个字符

char buff[255];
FILE *f = fopen("xxxx\\t.txt", "r");
//读取 遇到第一个空格字符停止
fscanf(f, "%s", buff);
printf("1: %s\n", buff);

//最大读取 255-1 个字符
fgets(buff, 255, f);
printf("2: %s\n", buff);
fclose(f);

//二进制 I/O 函数
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
//1、写入/读取数据缓存区
//2、每个数据项的大小
//3、多少个数据项
//4、流
//如:图片、视频等以二进制操作:
//写入buffer 有 1024个字节
fwrite(buffer,1024,1,f);

C++ 文件读写操作

\ 和 \

数据类型 描述
ofstream 输出文件流,创建文件并向文件写入信息。
ifstream 输入文件流,从文件读取信息。
fstream 文件流,且同时具有 ofstream 和 ifstream 两种功能。
char data[100];
// 以写模式打开文件
ofstream outfile;
outfile.open("XXX\\f.txt");
cout << "输入你的名字: ";
//cin 接收终端的输入
cin >> data;
// 向文件写入用户输入的数据
outfile << data << endl;
// 关闭打开的文件
outfile.close();

// 以读模式打开文件
ifstream infile;
infile.open("XXX\\f.txt");

cout << "读取文件" << endl;
infile >> data;
cout << data << endl;

// 关闭
infile.close();

C/C++ 文件编译过程

  • 预处理

    • 预处理

      • 预处理阶段主要处理include和define等。它把#include包含进来的.h文件插入到#include所在的位置,把源程序中使用到的用#define定义的宏用实际的字符串代替。
  • 编译

    • 编译阶段

      • 编译器检查代码的规范性、语法错误等,检查无误后,编译器把代码翻译成汇编语言。
  • 汇编

    • 汇编阶段

      • 把 .s文件翻译成二进制机器指令文件 .o,这个阶段接收 .c、.i、.s的文件都没有问题。
  • 链接

    • 链接阶段

      • 链接的是其余的函数库,比如我们自己编写的c/c++文件中用到了三方的函数库,在链接阶段就需要链接三方函数库,如果链接不到就会报错。
打赏
  • 微信
  • 支付宝

评论