JNI之C++语言一

由于C++完全兼容C语言的实现,所以C++中类似C语言实现的内容在本文就不做介绍了,如基本数据类型、指针、数组、字符串、预处理指令、宏、结构体、联合体、动态内存分配malloc、文件读写以及sizeof和typedef等等,更多可以参看前面C语言内容:

有了C语言的基础,学习C++就很容易上手了,再者,如果学习过Java,那些Java中面向对象的特性同样适用于C++,如封装、继承和多态。在理解了Java中实现的基础上,学习C++时,基本可以照搬面向对象的那套流程,不同的是现在使用的是C++的语法和结构,不仅如此,还可以对比与Java实现方式上的异同,加深理解,甚至可以达到事半功倍的效果。

如下是一个C++简单示例。

#include<iostream>
using namespace std;
void main(){
	char name[20];
	cout << "请输入姓名:";
	cin >> name;
	cout << "你好:"<<name<<endl;
}

输入输出流

在C语言中,如果我们向屏幕输出数据可以使用scanf()和printf()函数,但在C++中我们还可以使用另外一种方式,就是使用cin>>和cout<<的方式,cin和cout分别是输入流istream和输出流ostream对象,然后结合运算符就可以输入输出数据到屏幕上了。换行符我们可以使用换行符"\n",上述示例使用了一个叫做流操作算子endl实现了换行功能。

在C语言中,我们可以使用fopen()、fclose()、fputs()、fgets()对文件操作,在C++中引入对象操作文件。如果需要操作文件需要导入<fstream>头文件,类ifstream是文件输入流,ofstream是文件输出流,这里的ifstream和ofstream分别是istream和ostream的子类。

如下是一个简单示例,用户输入数据到文件后再从文件读取数据到显示屏。

#include<iostream>
#include <fstream>
using namespace std;
void main(){
	char data[100];

	// 以写模式打开文件
	ofstream outfile;
	outfile.open("test.txt");

	cout << "姓名: ";
	cin.getline(data, 100);

	// 向文件写入用户输入的数据
	outfile << data << endl;

	cout << "年龄: ";
	cin >> data;
	cin.ignore();//忽略掉之前读语句留下的多余字符

	// 再次向文件写入用户输入的数据
	outfile << data << endl;

	// 关闭打开的文件
	outfile.close();

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

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

	// 在屏幕上写入数据
	cout << data << endl;

	// 再次从文件读取数据,并显示它
	infile >> data;
	cout << data << endl;

	// 关闭打开的文件
	infile.close();

}

命名空间

命名空间是为了区分不同库相同的类、函数以及变量等,其实可以理解为命名空间是定义了一个上下文,而在这个命名空间中的所有类、函数以及变量只在该上下文有效。

在C++中定义命名空间使用关键字namespace,如果以前有接触过xml的话,其实xml中也有命名空间的说法,目的都是一样的,就是为了解决相同名称使用冲突的问题。

定义命名空间使用如下格式,一个关键字namespace加上一个命名空间名称。

namespace space_name{
	//代码
}

当调用命名空间中的函数或者变量时,需要使用命名空间名称作为前缀。如果所有的函数或者变量都是前缀,这样代码让人看上去有些混乱,不够简洁,这时候我们可以在使用某个命名空间中函数或者变量之前使用using namespace指令,后续再使用命名空间中代码时就可以省略该命名空间名称了,如上述的示例中使用的cin和cout,这两个对象都是定义在std命名空间下的。

#include<iostream>
using namespace std;
namespace first_space{
	int i = 100;
	void func(){
		cout << "first space function" << endl;
	}
}
namespace second_space{
	int i = 102;
	void func(){
		cout << "second space function" << endl;
	}
}

void main(){
	first_space::func();
	cout << "first space variable:" << first_space::i << endl;
	second_space::func();
	cout << "second space variable:" << second_space::i << endl;

	using namespace second_space;
	func();//second space function
	cout << "space variable:" << i << endl;//102
}

命名空间也可以嵌套,当访问嵌套的命名空间中函数或者变量时,可以使用::运算符来访问嵌套的命名空间中的成员。

namespace first_space{
	namespace second_space{
		int i = 102;
		void func(){
			cout << "second space function" << endl;
		}
	}
}

using namespace first_space::second_space;
void main(){
	func();//second space function
	cout << "space variable:" << i << endl;//102
}

命名空间可以定义在不同的地方或者不同文件中,也就是说一个命名空间可以由几个单独定义的部分组成,示例这里就不展示了。

引用与结构体

引用

在C语言中参数的传递只有传值一种方式,但是在C++中又引入了一种传引用的方式。定义引用使用&符号,引用中&符号需要在参数名称的前面,如果在变量等号的右侧,这时候是一个取地址符,所以一定要区分是定义的引用,还是取变量地址值。

定义一个引用后,相当于目标变量有了两个别名,即原名称和引用名称,所以定义引用后,并不是定义了一个新的变量,它仅是表示该引用名是目标变量的一个别名。引用本身不占用存储单元。

void main(){

	int i;
	double d;

	int& r = i;
	double& s = d;

	i = 5;
	cout << i << endl;//5
	cout << r << endl;//5
	cout << &i << ":" << &r << endl;//00BFFDC0:00BFFDC0
	r = 8;
	cout << i << endl;//8

	d = 10.5;
	cout << d << endl;//10.5
	cout << s << endl;//10.5
	cout << &d << ":" << &s << endl;//00BFFDB0:00BFFDB0

	int m;
	int *p = &m;
	m = 9;
	cout << *p << endl;//9
	*p = 20;
	cout << m << endl;//20

}

通过上面的示例代码可以看出,使用引用和指针有很多相似之处。

  • 引用不可以为空,但是指针可以为空,引用在定义时必须初始化。
  • 引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针(地址)本身的大小,32位系统下,一般为4个字节。
  • 引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。

引用的一个重要作用就是作为函数参数的使用。使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

void swap(int &x, int &y){
	int temp;
	temp = x;
	x = y;
	y = temp;
}
void main(){
	int x = 10, y = 20;
	swap(x, y);
	printf("x=%d,y=%d\n", x, y);//x=20,y=10
	system("pause");
}

在使用引用传参时,函数声明中形参可以省略别名,如swap()函数可以这样声明void swap(int &, int &)

结构体

结构体在C中已经介绍过了,我们这里就举一个简单的示例,看一下C++与C中在结构体的使用上的不同。

struct student
{
	int num;
	char *name;
	float score;
}
void main(){
	student stu;
	stu.num = 10;
	stu.name = "mike";
	cout << stu.num << ":" << stu.name << endl;
}

在C语言中,如果需要定义结构体变量,前面还需要添加一个struct关键字,当然了也可以使用typedef定义一个跟结构体名称相同的新类型代替,但是C++可以省略struct关键字。

可变参数与默认值参数

可变参数

可变参数在C或者C++中都支持,Java中也有可变参数方法。其实在学习C时就一直在使用可变参数函数,典型输出函数printf(),其函数原型为:

int printf(const char* format, ...);

如果使用可变参数定义函数时,需要导入头文件。

在定义不定长参数时,不定长参数必须放在参数列表末尾,然后倒数第2位是不定长参数的长度,再前面才是可选的参数。与Java不同的地方是Java中不需要定义参数长度,但是C或者C++中必须添加一个不定长参数长度的参数。另外,Java中输出遍历不定长参数时可以使用数组形式输出,但是C或者C++中相对比较复杂,示例代码如下:

void simple01(int num, ...){
	va_list args_p;
	va_start(args_p, num);//为num个参数初始化args_p
	int a = va_arg(args_p, int);
	int b = va_arg(args_p, int);
	int c = va_arg(args_p, int);
	char d = va_arg(args_p, char);

	cout << a << ":" << b << ":" << c << ":" << d << endl;
	va_end(args_p);//清理为args_p保留的内存
}
void simple02(float f, int num, ...){
	va_list args_p;
	va_start(args_p, num);
	char* a = va_arg(args_p, char*);
	char* b = va_arg(args_p, char*);
	char* c = va_arg(args_p, char*);
	char* d = va_arg(args_p, char*);
	cout << f << ":" << a << ":" << b << ":" << c << ":" << d << endl;
	va_end(args_p);
}
void simple03(int num, ...){
	va_list args_p;
	va_start(args_p, num);
	for (int i = 0; i < num; i++){
		char* str = va_arg(args_p, char*);
		cout << str << " ";
	}
	cout << endl;
	va_end(args_p);
	system("pause");
}

默认值参数

在C或者Java中都不支持默认值参数的写法,但是C++支持,而且这种写法可以有效减少函数个数,目前Google推崇的Kotlin语言也支持这种写法。如果是做Android开发人员,在自定义View时,一般我们都要重写至少3个构造方法,但是如果是使用Kotlin默认值参数写法,只需要写一个构造方法即可满足要求。

如果一个函数中有默认值参数,则所有的带有默认值的参数列表必须位于参数列表尾部,不带默认值参数必须位于默认值参数的前面,否则编译时就会报错。

如果一个函数先声明后定义的话,则默认值必须在声明时给出,否则,如果在定义函数时使用默认值,程序运行时会出错。当然了如果函数是直接定义的,没有给出函数声明,这时候就可以在定义函数时给出必要的默认参数值。

在调用默认值函数式,如果调用的函数没有默认值参数列表,则函数中未明确传入的参数使用的都是默认值。

void func(int a, int b = 1, int c = 2);

//void func(int a=0, int b , int c = 2);//error

void main(){
	func(10);      //10:1:2
	func(10, 2);   //10:2:2
	func(10, 2, 5);//10:2:5
}

void func(int a, int b, int c){
	cout << a << ":" << b << ":" << c << endl;
}

动态分配内存

在C语言中我们可以使用malloc()等函数动态分配内存,然后使用free()释放已经申请内存。在C++中新增了new和delete方式进行动态分配和释放内存,并且该种方式也是推荐的方式管理内存,因为在C++中新增了面向对象的特性,在使用new创建一个新对象时,会自动调用该类的构造函数,在使用delete释放内存时,会自动调用析构函数,构造函数和析构函数在下文类和对象时介绍。

new申请内存跟malloc类似,返回的也是所申请类型的指针,意味着如果申请成功了返回的是内存的首地址。

delete与free类似,在指针指向的空间通过delete释放后,指针本身的值并未改变,如果再对该指针使用delete运算,则会引发异常,另外当指针被释放后,应该养成良好的习惯将其赋值为NULL。在对数组delete时,一定不要忘记前面的[],对多维数据delete时,需要遍历多维数组然后采用一维数组释放内存的方式使用delete。

这里就可以发现一个规律了,在动态分配内存时无论是malloc和free还是new和delete,其实最终操作的都是一个指针,即一个内存地址。

在C++使用new时有一点与Java有很大的不同,就是在C++中new后面可以跟基本数据类型,但是在Java中new后面一定是一个对象类型。

另外需要注意,动态申请的内存都是在堆中分配的,全局变量和静态变量是分配中静态存储区的,非静态局部变量是分配在栈中的

int *ip = new int;
*ip = 12;
cout << *ip << endl;
delete ip;
cout << ip << endl;//00008123
ip = NULL;
cout << ip << endl;//00000000

float *fp = new float(12.5f);//申请一个float空间,初始值为12.5
cout << *fp << endl;
delete fp;
fp = NULL;

int *array = new int[20];
delete []array;//数组释放时需要加上[]
array = NULL;

小结

本文介绍了部分C++在语法上与C实现不同的知识点,包括如何使用文件流写入或者读取文件,如何定义与使用命名空间,引用和指针的对比使用示例,以及它们各自的优缺点。在函数使用上面C++支持默认值参数,其实这里的默认值参数规则对后续构造函数仍然适用,这种规则在C或者Java中都不支持,但是在C++中确实功能强大而且灵活性高,代码看上去也清晰,可以有效减少代码冗余。最后介绍了C++中有别于C语言方式的动态内存分配和释放,在C++中可以借助于new和delete对内存进行动态申请和释放,在使用上面比malloc和free更加简洁,而且在后续涉及到面向对象功能时,基本都是借助于new和delete动态管理内存,这也是推荐的方式,因为new和delete执行时会调用构造函数和析构函数。

有关面向对象的一些特性,后续继续讲解,仅仅是有关类和对象的内容估计后续就要介绍不止一篇博文,其实强调的点倒不是很多,有Java基础这部分很容易理解,主要是介绍时需要借助示例,一旦涉及到类,代码示例比较长。

评论

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