跟我一起学NDK-C++基础知识

前言

NDK 采用 C/C++ 语言进行开发,所以学习 NDK 开发之前我们先来补充下必要的 C/C++ 知识。C++ 是对 C 语言的升级,C++ 程序设计语言可以归结为 C 程序设计语言的语法 + 面向对象程序设计,因此本课程只介绍 C++ 程序设计语言。相信 Android 应用层开发的同学对于 Java 语言能够得心应手,本课程对于 C++ 程序设计语言的不少知识会用 Java 类推

基准语法

  • 注释,和 Java 一致

单行

1
//  我是单行注释

多行

1
2
3
/**
* 我是多行注释
*/
  • 语句之间用 ; 分隔,和 Java 一致

  • 代码块包含在 {} 内,和 Java 一致

  • 大小写敏感

数据类型

基本数据类型

基本数据类型的大小在不同机器上会有所不同

类型 关键字 大小
布尔型 bool 1个字节
字符型 char 1个字节
整型 int 4个字节
浮点型 float 4个字节
双浮点型 double 8个字节
无类型 void /
宽字符型 wchar_t 2字节

sizeof() 函数可以获取各数据类型所占字节数

类型修饰符

一些基本类型可以使用一个或多个类型修饰符进行修饰,修饰符修饰会改变所占内存大小

  • signed
  • unsigned
  • short
  • long
类型 大小 范围
unsigned char 1个字节 0 到 255
signed char 1个字节 -128 到 127
unsigned int 4个字节 0 到 4294967295
signed int 4个字节 -2147483648 到 2147483647
short int 2个字节 -32768 到 32767
long int 8个字节 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807
long double 16个字节 +/- 1.7e +/- 308 (~15 个数字)

定义新类型

关键字:typedef

语法:typedef type newname;

1
2
typedef int feet;
feet age;

定义了新类型 feet,使用和 int 一致

其实 wchar_t 类型是这样来的

1
typedef short int wchar_t

变量

语法:数据类型 变量名1,变量名2;

1
2
int age; // 只定义
int diatance = 1200; // 定义并赋值

常量

语法:const 数据类型 常量名 = 常量值;

常量名一般用大写,运行时无法改变其数值,如果申明时并未设置初始值,那么之后无法再设置数值

1
const float PI = 3.1415;

也可以使用宏定义的方式

1
#define PI 3.1415

运算

包括赋值运算、算术运算、比较运算、逻辑运算、递增递减运算以及位运算,这些运算都和 Java 一致。浮点数的余数运算用 fmod(a,b) 函数

数据类型转换

自动转换

数值范围大的作为优先转换的对象,转换顺序如下:

double <- float <- unsigned long <- long <- unsigned int <- int

强制转换

语法:(强制转换类型名称) 表达式或变量;

1
2
int a, b, avg;
avg = (float)(a + b) / 2;//将 a+b 的值转换为浮点数再除以 2

转型运算符

运算符 说明
static_cast 转换数据类型
const_cast 转换指针或引用的常数类型
dynamic_cast 转换类继承体系中的对象指针或引用
reinterpret_cast 转换无关联的数据类型

如下,用 static_cast 将变量 two 从 double 类型转换为 int 类型

1
2
3
int one = 9;
double two = 7.6;
one = one + static_cast<int>(two);

流程控制结构

包括顺序结构、选择结构(if else)、循环结构(for、while/do while),都和 Java 一致

数组

数组定义

语法:数据类型 数组名[数组大小]; 或 数据类型 数组名[] = {值1,值2,…};

1
2
3
4
5
6
int score[3];
score[0] = 69;
score[1] = 71;
score[2] = 88;

int scores[3] = {69, 71, 88};

数组赋值取值

语法:数组名[下标] = 值;

1
scores[0] = 89;

字符串

数组形式

语法:char 字符串变量[] = “初始字符串”;

1
char name[] = "Jone";

String 类

语法:string 字符串变量 = “初始字符串”;

1
2
#include<string> //包含字符串头文件
string name = "Jone";

String 类成员函数:

运算符 功能 用法
append() 串接字符串 str.append(str2)
assign() 字符串赋值 str.assign(str2)
compare() 比较两个字符串 str.compare(str2)
replace() 替换字符串 str.replace(开始位置,长度,str2)
insert() 插入字符串 str.insert(开始位置,str2)
erase() 清除字符串的部分内容 str.erase(开始位置,清除字符数)
length() 获取字符串的长度 str.length();
size() 获取字符串大小 str.size();
find() 寻找字符串 str.find(str2);
substr() 获取字符串的部分子串 str.substr(开始位置,长度);
empty() 判断是否为空字符串 str.empty();
at() 获取指定位置字符 str.at(n);//n为第n个字符

函数

语法:

1
2
3
4
返回数据类型 函数名(参数 = 默认值){
程序语句块;
return 返回值;
}

如:

1
2
3
int add(int a, int b = 3){
return a + b;
}

有默认值的参数务必放置在参数行的尾端

C++ 从上往下编译程序代码,如果在前面调用了后定义的函数,则必须在调用前加声明

预处理指令

C++ 程序在开始进行编译前会先进行一项预处理操作,将程序中那些以 # 符号开头的预处理指令进行特别的处理。预处理指令不会翻译为机器码,只在编译之前起作用。因为预处理指令不是 C++ 语句,所以并不需要以分号 “;” 结束

  • #define 宏定义指令
    #define 是一种替换指令,可以用来定义宏名称,并且替换程序中的数值、字符串、程序语句或者函数

语法如下:

1
2
3
4
#define 宏名称 常数值
#define 宏名称 "字符串"
#define 宏名称 程序语句
#define 宏名称 函数名称

宏函数是一种可以传递参数来替换简单函数功能的宏,参数不需要指定类型

语法如下:

1
#define 宏函数名称(参数行) 函数表达式
1
#define RESULT(r1, r2, h) (r1+r2)*h/2.0

如果想要取消 #define 所声明的宏时,只要使用下面的语法声明即可。取消之后不可以再使用

1
#undef 宏名称

标准预处理宏

宏名称 说明
LINE 正在编译的行在源文件的行号
FILE 被编译的源文件的文件名
DATE 存储编译开始的日期
TIME 存储编译开始的时间
  • #include 指令

#include 指令也是一种宏命令,可以将指定的头文件包含到源文件中(类似于 Java 的 import),实际上是用头文件中的声明替换,有两种指定方式

  1. #include <文件名>
    编译程序将到默认的系统目录中查找指定的头文件,一般为系统头文件
1
#include <cmath>
  1. #inclide “”
    编译程序将到当前程序文件的工作目录中查找指定的头文件,一般为自定义头文件
1
#include "people/"
  • 条件编译指令

有几个指令可以用来有选择地对部分程序源代码进行编译,这个过程被称为条件编译。条件预处理器的结构与 if 选择结构很像。共有六种:#if、#else、#elif、#endif、#ifdef、#ifndef

#if 指令类似于 if 条件语句,#else 指令类似于 else 条件语句,#elif 类似于 else if 条件语句,#endif 条件编译结束标识

语法如下:

1
2
3
4
5
6
7
#if 条件语句
程序块1
#elif 条件语句
程序块2
#else
程序块3
#endif

#ifdef 如果定义了某宏定义标识符,#ifndef 如果没有定义某宏定义标识符

语法如下:

1
2
3
#ifdef 宏名称
程序块
#endif
1
2
3
#ifndef 宏名称
程序块
#endif

头文件

通常在一个 C++ 程序中,包含两类文件——.cpp 文件和 .h 文件。.cpp 文件被称为 C++ 源文件,.h 文件被称为 C++ 头文件

C++ 语言支持“分别编译”,b.cpp 中想要调用 a.cpp 中定义的函数,并不需要知道 a.cpp 的存在,只需要添加 a.cpp 函数相对应的声明。定义只能定义一次,声明可以多次声明

如 a.cpp 中定义个一个 add 的函数

1
2
3
int add(int a, int b){
return a + b;
}

在 b.cpp 中可以这样使用

1
2
int add(int a, int b); //先声明
add(2, 3);

源文件中维护大量的声明特别麻烦,同时会带来多次声明的重复工作。一旦定义发生改变,需要修改的地方众多

所以 C++ 语言提出了头文件的概念。你只需要在头文件中声明一次,在实现文件中定义一次。在所有需要用的文件中,就只需要引用这个头文件,相当于每个文件都包含了一个声明

头文件中应该包含下面内容:

  • 类的声明(包含类里面的成员和方法)
  • 函数原型
  • #define 常量

命名空间

因为 C++ 中不存在 Java 的 package 概念,对于不用库相同名称的函数、变量等无法区分,所以引入了命名空间的概念

语法如下:

1
2
3
4
namespace namespace_name{
// 程序块
code
}

为了调用命名空间的函数或变量,需要在前面加上命名空间的名称

1
namespace_name::code

您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称

1
2
using namespace namespace_name;
code;

extern 关键字

extern可以置于变量或者函数前,以标示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义,表明当前只是声明

当 extern 和 “C” 一起使用的使用有其他含义,如: extern “C” void fun(int a, int b)。告诉编译器在编译 fun 这个函数名时按着 C 的规则去翻译相应的函数名而不是 C++ 的,C++ 的规则在翻译这个函数名时会把 fun 这个名字变得面目全非

异常处理

跟 Java 类似,语法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
try
{
// 保护代码
}catch( ExceptionName e1 )
{
// catch 块
}catch( ExceptionName e2 )
{
// catch 块
}catch( ExceptionName eN )
{
// catch 块
}