Fortran转C++的一些注意事项
一、命名
在Fortran中, 所有的字符都是不区分大小写
的. 在编译时, 全部使用小写字符. 但是, 在C++中是会区分大小写
的. 因此, 在转换时要注意统一大小写.
二、数据类型
1. 可直接转换的参数类型
- 整型:
integer
=int
- 浮点数:
real
=float
- 双浮点数:
real(8)
=double
- 逻辑:
logical
=bool
对于科学计数法, Fortran中
d
表示双精度浮点数, 如1.2d3
. 在C++中并不能直接识别, 但是C++中默认使用双精度浮点数. 因此可直接使用1.2e3
来代替.
2. 可代替的参数类型
- 字符串
character
: 可使用std::string
代替 (须#indlude <string>
)
在c++中
char
只能储存一个字符, 需要使用数组来保存字符串. 因此在使用上character
和string
更类似对于字符串的连接, Fortran中使用
//
, C++中使用+
- 复数
complex
: 可使用std::complex
代替 (须#include <complex>
)
C++的
std::complex
内提供了一些用于复数运算的函数,例如:
std::real
(获取复数的实部)
std::imag
(获取复数的虚部)
std::abs
(获取复数的模)
std::arg
(获取复数的辐角)
std::norm
(获取复数的模的平方)
std::conj
(获取复数的共轭复数)
- 自定义类型
type
: 可使用结构体struct
代替
对于参数的访问, 在Fortran中使用
%
在c++中使用.
- 动态数组
allocatable
: 可使用向量vector
代替 (须#include <vector>
)
对于结构体类型的向量, 使用
vecname.assign(quantity, vecname());
来调整向量的大小. 但是注意这会清除向量中原有的内容对于数值类型的向量, 使用
vecname.resize()
来调整大小使用
vecname.size()
可以得到向量当前的大小
- 动态
type
数组: 使用std::vector<structname>
来定义
对于动态type数组这类变量, 在Fortran中的初始化:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 !在Fortran2003以后的标准中, type可以直接给定默认值
type material
real :: mass = 0.0
real :: speed = 0.0
real :: Vx(3) = 0.0, Vy(3) = 0.0, Vz(3)=0.0
type(material),allocatable::material_list(:)
allocate(material_list(3))
!对于之前的标准, 或者没有定义默认值, 再或者需要再次初始化, 可以使用以下方式
type material
real mass
real speed
real Vx(3), Vy(3), Vz(3)
type(material),allocatable::material_list(:)
allocate(material_list(3))
do i=1,3
material_list(i)%mass = 0.0
material_list(i)%speed = 0.0
material_list(i)%Vx = (/0.0, 0.0, 0.0/)
material_list(i)%Vy = (/0.0, 0.0, 0.0/)
material_list(i)%Vz = (/0.0, 0.0, 0.0/)
end do
! 特别的,在Fortran中,不加索引号会对数组内的全部元素赋值
type material
real mass
real speed
real Vx(3), Vy(3), Vz(3)
type(material),allocatable::material_list(:)
allocate(material_list(3))
material_list%mass = 0.0
material_list%speed = 0.0
material_list%Vx = (/0.0, 0.0, 0.0/)
material_list%Vy = (/0.0, 0.0, 0.0/)
material_list%Vz = (/0.0, 0.0, 0.0/)
!与上面带索引号的方式,效果相同, C++中不能这样做在c++中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 //在C++11以后的标准中可以直接对结构体设置默认值
struct material //定义结构体
{
float mass = 0.0f; //0.0默认是double类型, 浮点数的0.0要加上f
float speed = 0.0f;
float Vx[3] = {0.0f}, Vy[3] = {0.0f}, Vz[3] = {0.0f};//此处c++会将数组里的其他值也初始化为0.0f
};
std::vector<material> material_list; //创建结构体向量
material_list.assign(3,material()); //修改向量大小
//对于之前的标准或者没有定义默认值, 再或者想要填充为其他值
material temp;
temp.mass = 0.0;
temp.speed = 0.0;
for (int i = 1; i < 3; i++ )
{
temp.Vx[i] = 0.0;
temp.Vy[i] = 0.0;
temp.Vz[i] = 0.0;
};
material_list.assign(3,temp);
//assign函数会将material的大小调整为3, 并将所有值填充为temp
3. 需要注意的参数类型
- 数组: 在Fortran中,数组的索引序号默认是从
1
开始, 而在C++中序号是从0
开始. 这要求我们在使用循环操作数组时要注意索引序号. 其次, 在存储数据时, Fortran是使用列主序, 即一个二维数组(3,4)表示的是3列4行. 而在c++中二维数组[3,4]表示的是3行4列
Fortran和C++数组主要的区别:
- 索引起始位置:在Fortran中, 数组的索引默认从1开始, 并且可以自定义索引序号. 而在C++中, 数组的索引始终从0开始.
- 内存布局:Fortran使用列优先(column-major)顺序存储多维数组, 而C++(和大多数其他C风格的语言)使用行优先(row-major)顺序存储多维数组.
- 动态数组:在Fortran中, 可以很容易地创建动态数组, 而在C++中, 创建动态数组需要更多的工作. 不过, C++提供了
std::vector
这样的容器类, 可以方便地创建和管理动态数组.- 数组作为函数参数:在Fortran中, 当数组作为函数参数时, 会传递数组的引用, 而不是数组本身. 这意味着, 如果在函数内部修改了数组, 那么在函数外部的数组也会被修改. 在C++中, 如果你直接将数组作为函数参数, 那么实际上传递的是数组的指针, 如果你想要达到和Fortran相同的效果, 你需要显式地使用引用或者指针.
- 数组大小:在Fortran中, 数组本身知道其大小, 而在C++中, 原生数组不知道其大小, 你需要自己保持对数组大小的跟踪. 不过, C++的
std::array
和std::vector
等容器类知道它们的大小.
- 数组的运算: 在Fortran中, 对于
数组
可以直接使用+ - * / **
等运算符号, 其意义为数组中对应位置的元素相加, 减, 赋值.
例如:
1 | integer:: i(3),j(3) |
在这段代码中, 定义了两个长度为3的一维数组.
第2行, 将
i
中的元素全部赋值为1
.第3行, 将
i
中的每一个元素都加1
,i
中所有元素变为2
.第4行, 将
i
中元素的值,对应赋值给j
数组,j
中元素也都变为了2
.第5行, 将
i
中的元素对应减去j
中的元素并赋值给i
, 这时,i
中的元素全部变为了1
.
这些在C++中是无法直接做到的, 可以借助一些库或者使用循环或者STL中的一些标准函数来完成这些操作.
copy
从一个数组复制到另一个数组 (std::copy(first,last,dest)前两个参数控制复制到数组的开始和结尾, 第三个为要复制的数组)fill
填充数组,用于数组的初始化 (std::fill(first,last,init)前两个控制范围,最后一个为填充值)transform
转换transform
算法接受一个或两个输入范围, 以及一个输出范围, 并根据提供的一元函数对象或二元函数对象对”输入范围内的元素” 进行转换;InputIt first1
参数: 输入容器的起始迭代器(包含);InputIt last1
参数: 输入容器的终止迭代器(不包含);InputIt2 first2
参数: 第二个输入容器的起始迭代器(包含);OutputIt d_first
参数: 输出容器的开始迭代器, 输出元素个数根据输入元素的范围确定,transform会将变换结果存储到输出容器中;UnaryOperation unary_op
参数: 一元函数对象, 将输入容器的每个元素输入到该一元函数对象中, 将计算结果输出到输出容器中;
三、运算符号
+
-
*
/
都可以直接转换, 除了上面提到的应用在数组的计算上时**
幂乘运算在c++中可使用#include <cmath>
库中的pow
来代替
用法:
std::pow(底数,指数)
- 求余
mod()
在c++中为%
- 在c++中, 还有
--
和++
等自减和自增运算, 注意其在使用时前缀和后缀的区别: 可以理解为前缀既算值又变, 后缀只算值不变
. 当然这句话仅对其所在的一个语句生效, 无论是自增还是自减, 在这条语句结束后, 数值都发生了改变.
例如:
1
2
3
4
5 int a=10;
int b;
b = ++a; //前缀自增a变为了11, 其参与计算的值也变为11, b=11, a=11
b = a++; //后缀自增a从上面的11变为了12, 但在这条语句中a参与计算的值还是11, b=11, a=11, 结束后a=12
b = a; //在上面的语句结束后a参与计算的值变为了12, b=12, a=12在上面的基础上,
b=++a--;
中的a b参与计算的值分别为多少? 该语句结束后a b的值又是多少?
1
2 b=13 a=13;
b=13 a=12;同时这两个运算符的优先级是要高于其它的
1 b = 1 + a--; //先计算a--再计算+1三个运算符相连时
1
2 b = a++-1; //等价于b=(a++)-1
b = a+++1; //等价于b=(a++)+1, 但不建议这样做
C++中内置了(cmath)一些数学运算函数, 下面是常用的一些函数:
1 | double cos(double); //返回弧度角的余弦 |
四、控制结构
1. 循环结构
在Fortran中, 循环结构有两种do
和do while
. 分别对应着c++中的for
和while
. 除此之外, c++还有一种结构do ... while
. 它与while
的区别在于: while
是先判断后执行, do ... while
是先执行后判断.
循环控制语句对应关系:
exit
: 在c++中为break
cycle
: 在c++中为continue
此外, 在c++中还有一种控制语句
goto
即跳转到某一被标记的语句执行. 但是, 在程序中不建议
使用该语句!
2. 判断语句
在Fortran中, 判断语句有if-then
, if-then-else
和if-else if-else
. 这与c++中的if
, if-else
以及if-else if-else
是一一对应的. 他们的用法相同.
需要注意的是, 在Fortran中的select case
语句与c++中的switch
语句有一些使用上的差别.
select case
语句在运行选择的case
后会结束select
也就是只进行一次选择. 而在c++的switch
语句中, 如果不使用break
语句, 会一直进行switch
.相当于Fortran的选择语句中, 所有的
case
都带有一个break
.如果将
do while(.true.)
与select case()
语句结合, 当某个case中有一个exit
语句, 它会结束do while的循环. 而在c++中,while
和switch
都使用break
来打断, 所以case中的break
只会结束switch而不会结束while(true). 因此, 在转换时需要增加while结束的条件.例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 do while(.true.)
key = Keyord(kw,nbkw)
select case(key)
case(1)
exit
case(2)
call GetString(Title)
case(3)
nb_particle = GetInt()
write(*,"(a,i12)") 'Number of particles = ',nb_particle
write(iomsg,"(a,i12)") 'Number of particles = ',nb_particle
allocate(particle_list(nb_particle))
call InitParticle()
case default
stop 'STOP - Error encountered in reading data'
end select
end do改为c++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 while (true)
{
key = KeyWord(kw, nbkw);
switch (key + 1)
{
case 1:
break;
case 2:
Title = GetString();
break;
case 3:
nb_particle = GetInt();
cout << "Number of particles = " << nb_particle << endl;
outfile << "Number of particles = " << nb_particle << endl;
particle_list.assign(nb_particle, Particle());
break;
default:
cout << "STOP - Error encountered in reading data" << endl;
exit(1);
}
if (key == 0) break;
}或者使用
do...while
1
2
3
4
5
6
7
8 do
{
key = KeyWord(kw, nbkw);
switch (key + 1)
{
//...
}
}while(key != 0);
五、子程序和函数子程序
对于独立的子程序 (不引用其他的module) 可直接使用void
函数替代. 对于function
函数子程序可以对应类型的函数替代, 即有返回值的函数.
例子:
1 | subroutine sum100(sum_) |
这个程序会计算1~100的和并输出到sum这个量
1 | call sum100(sum1) |
转换成c++
1 | int sum = 0; |
这类子程序的特点: 有明确的输入的输出, 全部工作都在子程序内完成, 不牵扯其他的module或子程序.
关于参数的传递
在Fortran的子程序中, 参数的传递默认按照引用
来传递. 即, 如果你在子程序内改变了这一变量(未规定为in
类型), 外部的变量也会改变. 这与C++中使用&
符号传递参数的作用相同.
有些子程序, 在编写时, 未规定传入参数的类型为输入(in
)还是输出(out
). 在转换为C++时, 需要注意分辨是否需要改变外部的变量.
在转换时,
in
类型的变量在子程序中是无法改变的, 因此我们在写c++的函数时, 不需要加&
. 而对于out
类型的变量, 必须要加上&
, 否则不能改变外部的变量.对于全局变量而言, 不需要作为传入参数. 可以直接使用并改变该变量的值.
六、模块(Module)
对于这类文件, 可以使用头文件
+全局变量
+源文件
的模式
对于全局常量和结构体
, 只需要定义在头文件
内即可. 在头文件中, 结构体的定义和常量的定义不使用extern
声明.
在编写代码时, 可以使用namespace
对不同文件的参数作区分, 防止出现命名上的冲突 (可不使用). 在c++中, 还可以使用class
来更好的组织和封装数据及函数.
如果原模块没有私有变量, 可直接使用namespace
.
以下是GPT对两种方式的优缺点的判断:
使用
namespace
的优点:
namespace
可以用来避免命名冲突. 你可以在不同的namespace
中定义同名的函数或变量, 而不会产生冲突.namespace
可以嵌套, 这使得你可以更好地组织你的代码.namespace
可以在多个文件中定义, 这使得你可以将相关的代码和数据分散在多个文件中.使用
namespace
的缺点:
namespace
不能用来封装数据. 在namespace
中定义的所有函数和变量都是公开的, 任何人都可以访问.namespace
不能用来实现面向对象编程的特性, 如继承和多态.使用
class
的优点:
class
可以用来封装数据和函数. 你可以控制哪些数据和函数是公开的(public
), 哪些是私有的(private
).class
可以用来实现面向对象编程的特性, 如继承和多态.class
可以用来创建对象, 这使得你可以在运行时动态地创建和销毁数据和函数.使用
class
的缺点:
class
不能像namespace
那样用来避免命名冲突. 在同一个namespace
中, 不能有两个同名的class
.class
不能在多个文件中定义. 一个class
的所有成员都必须在同一个文件中定义.
欢迎补充和指正