JNI之C++语言四

这是在JNI之前的最后一篇关于基础知识介绍的博客,从下篇开始就可以正式进入JNI的介绍篇章了。本文介绍C++中剩余的部分点,主要包括C++中的四种显式类型转换符、运算符重载以及模板。显示类型转换符以及模板是我们需要着重理解的部分,特别是模板部分,它跟Java中泛型机制很相似,在C++中也应用场景也非常多。

类型转换

在Java中如果高数据类型向低数据类型转换需要进行强制转换,,如double类型转换为int类型,有可能导致数据精度丢失,只保留整数部分。但是在C或者C++中,基本数据类型高数据类型和低数据类型相互转换都可以进行隐式转换,这样容易造成一个问题,就是代码可读性会降低,稍有不注意,在数据转换时导致精度丢失引发异常。

double m = 10.23;
int x = m;

int a = 10;
double d = a;

在C++中提供了四种类型转换符:static_cast、dynamic_cast、const_cast和reinterpret_cast。在涉及到类型转换时通过使用这四种类型转换符,可以提高代码可读性,让潜在隐式转化风险可视化,利于程序后期更新维护。

关键字 说明
static_cast 用于良性转换,一般不会导致意外发生,风险很低。
const_cast 用于const与非 const、volatile 与非 volatile 之间的转换。
reinterpret_cast 高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。
dynamic_cast 借助 RTTI,用于类型安全的向下转型(Downcasting)。

注:RTTI(Run-Time Type Identification),通过运行时类型信息程序能够使用基类的指针或引用来检查这些指针或引用所指的对象的实际派生类型。

class Base{
public:
	virtual void print();
};
class Child :public Base{
public:
	void print();
};

void Base::print(){
	cout << "base print" << endl;
}
void Child::print(){
	cout << "child print" << endl;
}

static_cast

static_cast类似传统形式上的强制转换,但没有运行时类型检查来保证转换的安全性。

static_cast主要有如下几种方式用法:

  • 用于类类型指针或者引用转换,当然了也可以用于子类父类之间对象类型转换。
  • 基本数据类型之间的转换。
  • 将空指针转换为目标类型的空指针。
double m = 10.23;
int x = static_cast<int>(m);
cout << x << endl;  //10

double *dp = new double;
void *vd = static_cast<void*>(dp);

Child child;
Base base = static_cast<Base>(child);
base.print();

Base base01;
//Child child01 = static_cast<Child>(base01);//报错

// 注意:如果使用dynamic_cast转换则会输出cp is NULL
Base *bp = new Base();
Child *cp = static_cast<Child *>(bp);
if (cp == NULL){
	cout << "cp is NULL" << endl;
}else{
	cp->print();   //base print
}

上述最后一个示例中输出结果是“base print”,所以上文说了static_cast没有运行类型检查保证转换的安全性。如果使用的是dynamic_cast进行转换,转换后的cp将会是一个NULL指针。

const_cast

const_cast中的类型必须是指针或者引用,一般将常量指针或者引用转换为非常量指针或者引用。

const int i = 10;
int *j = const_cast<int*>(&i);

const int m = 20;
int &n = const_cast<int &>(m);

dynamic_cast

dynamic_cast中的类型也必须是指针或者引用,而且必须是类类型的指针或者引用。

在执行子类父类之间转换时,dynamic_cast和static_cast效果是一样的。但是dynamic_cast具有类型检查的功能,比static_cast更安全。

Base *bp = new Base();
Child *cp = dynamic_cast<Child *>(bp);
if (cp == NULL){
	cout << "cp is NULL" << endl;  // cp is NULL
}
else{
	cp->print();
}

reinterpret_cast

reinterpret_cast中的类型必须是指针或者引用。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针。

reinterpret_cast执行取决于编译器,这也就表示它不可移植。

void func01(){
	cout << "func01" << endl;
}

char *func02(){
	cout << "func02" << endl;
	return "hello";
}

typedef void(*fp)();

void func004(){
	fp array[5];
	array[0] = func01;

	array[1] = reinterpret_cast<fp>(func02);
	array[1]();
	
	int i = 10;
	array[2] = reinterpret_cast<fp>(&i);
	array[2]();//运行后异常崩溃
}

const特性

在C语言已经介绍了部分const特性,所以这里是补充的一些其它点。

  • const在定义对象时,关键字const需在类名称的前面,其实这里跟定义一般的基础数据类型变量很相似。
  • 如果声明const成员函数时,const必须放在函数体之前,参数列表之后。
  • 如果一个对象被声明为const,则这时候不能再调用该对象的非const成员函数,因为非const成员函数有修改该对象的数据成员的可能。但是非const对象既可以调用const成员函数,也可以调用非const成员函数。
  • 在函数调用时,const成员函数不允许调用非const成员函数。
class Circle {
public:
	Circle(double a = 0.0, double b = 0.0, double c = 1.0){
		x = a;
		y = b;
		r = c;
	}
	void show() const;
	void print();
private:
	double x, y;//圆心坐标
	double r;//半径
};
void Circle::show() const{
	cout << "x:" << x << "  y:" << y << "  r:" << r << endl;
}
void Circle::print(){
	cout << "x:" << x << "  y:" << y << "  r:" << r << endl;
}

void main(){
	Circle circle(0, 0, 2.5);
	circle.show();
	circle.print();

	const Circle csn(0, 0, 2.5);
	csn.show();
	//csn.print();报错
}

如果在类中有些数据成员被声明为了const变量,则这些变量仅允许在构造函数和析构函数中修改。其实这里跟Java中final变量有些类似,在Java中如果一个变量被声明为了final类型,则该变量要么在声明时直接初始化,要么在构造方法中初始化。

在上述示例中,如果我们将r定义为const类型的变量,这时候就只能在构造函数初始化成员列表中进行初始化,上面示例中使用的方式叫做赋值,所以在编译时会提示错误,这里可以参看前面介绍的构造函数初始化列表的相关内容。

public:
	Circle(double a = 0.0, double b = 0.0, double c = 1.0) :r(c){
		x = a;
		y = b;
	}
	void show() const;
	void print();
private:
	double x, y;//圆心坐标
	const double r;//const 半径
};

运算符重载

无论是Java还是C++中,函数重载实现都是一致的,函数名相同,参数类型、个数或者顺序不同,这就是函数重载。不过在C++中还支持另外一种形式的重载-运算符重载。运算符就是如“+-%|&<<>>”等等的操作符。

在C++的第一个示例中我们就已经接触了运算符重载了,运算符lt;<和cout结合使用,运算符lt;<既可以作为移位运算符,也可以和对象结合作为流插入运算符。 运算符重载可以实现的功能,函数也可以实现,所以开发过程中不是所有的情形下都建议使用运算符重载的,如果可以是程序变得清晰、简单,能用运算符重载时尽量使用,否则不建议使用运算符重载。

在C++不可以定义新的运算符,所以我们只能使用已有的运算符进行重载,当然了,也不是所有的运算符都支持重载的。

运算符重载必须通过函数实现完成,函数可以定义许多实现方式,所以运算符重载必须遵循特定的格式才可以。重载运算符的函数名是operator加运算符

我们知道在C++中是不支持对象相加的,接下来我们写一个简单示例,两个对象相加达到两个对象对应的属性分别相加生成一个新对象,以此达到对象可以相加的目的。

class Point{
public:
	Point(int x, int y){
		this->x = x;
		this->y = y;
	}
	void print(){
		cout << "x:" << this->x << "  y:" << this->y << endl;
	}
public:
	int x;
	int y;
};

Point operator +(Point &p1, Point &p2){
	return Point(p1.x + p2.x, p1.y + p2.y);
}

void main(){

	Point p1(10, 20);
	Point p2(15, 25);
	Point p = p1 + p2;  //运算符重载实现
	p.print();  //x:25  y : 45

	int x = 10;
	int y = 20;
	int z = x + y;  //验证+不影响正常计算
	cout << z << endl; //30
	
}

模板

Java中可以使用泛型,泛型的使用使得Java代码减少了许多,不仅可以使代码具有更好的重用性,而且还可以在一定程度上面确保数据类型的安全性。C++中也提供了一套类似Java泛型的机制-模板,模板分为函数模板和类模板,在函数中使用的模板就是函数模板,在类中的就叫做类模板。

C++模板同Java泛型类似,它们都是编译时语法。

定义模板时一般以大写字母开头,建议采用驼峰命名法。使用关键字template定义模板,类型参数可以使用class或者typename指明,二者意义相同。

一般模板定义方式如下:

template <class T>
template <class ElementType>
template <class T1,class T2>

函数模板

template <typename T>
void mySwap(T &t01, T &t02){
	T t = t01;
	t01 = t02;
	t02 = t;
}
void main(){
	int i = 10;
	int j = 20;
	mySwap<int>(i, j);
	cout << i << ":" << j << endl;

	char x = 'x';
	char y = 'y';
	mySwap(x, y);
	cout << x << ":" << y<< endl;
}

类模板

类模板在使用时需要注意,在类定义时需要在类的前面声明类模板,而且后续在类外部定义函数时,每一个成员函数前面也需要声明类模板。

template <typename K, typename V>
class Pair{
public:
	Pair(K k, V v);
	void print();
private:
	K k;
	V v;
};
template <typename K, typename V>//这里必须进行声明
Pair<K, V>::Pair(K k, V v){
	this->k = k;
	this->v = v;
}
template <typename K, typename V>//这里必须进行声明
void Pair<K, V>::print(){
	cout << "k:" << this->k << "   v:" << this->v << endl;
}
void main(){
	Pair<int, int> pairInt(10, 20);
	pairInt.print();

	Pair<char*, char*> pairStr("this is k", "this is v");
	pairStr.print();
}

小结

本文介绍了C++中的四种常用的显式类型转换符以及各自的应用场景,在编程中使用显式类型转换符是一种良好的编程习惯,它可以有效降低因类型转换而引发的精度丢失或者类型转换异常。另外介绍了另外一种有别于函数重载的方式-运算符重载,如果可以是运算更清晰简单时可以使用运算符重载。最后介绍了模板,它跟Java中泛型很相似,需要着重掌握的部分。

从下篇文章开始,我们就可以进入JNI部分了,部分遗漏点会在介绍JNI时补充上来。

评论

您确定要删除吗?删除之后不可恢复