一、命名

在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只能储存一个字符, 需要使用数组来保存字符串. 因此在使用上characterstring更类似

对于字符串的连接, 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++数组主要的区别:

  1. 索引起始位置:在Fortran中, 数组的索引默认从1开始, 并且可以自定义索引序号. 而在C++中, 数组的索引始终从0开始.
  2. 内存布局:Fortran使用列优先(column-major)顺序存储多维数组, 而C++(和大多数其他C风格的语言)使用行优先(row-major)顺序存储多维数组.
  3. 动态数组:在Fortran中, 可以很容易地创建动态数组, 而在C++中, 创建动态数组需要更多的工作. 不过, C++提供了std::vector这样的容器类, 可以方便地创建和管理动态数组.
  4. 数组作为函数参数:在Fortran中, 当数组作为函数参数时, 会传递数组的引用, 而不是数组本身. 这意味着, 如果在函数内部修改了数组, 那么在函数外部的数组也会被修改. 在C++中, 如果你直接将数组作为函数参数, 那么实际上传递的是数组的指针, 如果你想要达到和Fortran相同的效果, 你需要显式地使用引用或者指针.
  5. 数组大小:在Fortran中, 数组本身知道其大小, 而在C++中, 原生数组不知道其大小, 你需要自己保持对数组大小的跟踪. 不过, C++的std::arraystd::vector等容器类知道它们的大小.
  • 数组的运算: 在Fortran中, 对于数组可以直接使用+ - * / **等运算符号, 其意义为数组中对应位置的元素相加, 减, 赋值.

例如:

1
2
3
4
5
integer:: i(3),j(3)
i = 1
i = i + 1
j = i
i = i - j

在这段代码中, 定义了两个长度为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
2
3
4
5
6
7
8
9
10
double cos(double); //返回弧度角的余弦
double sin(double); //正弦
double tan(double); //正切
double log(double); //返回输入参数的自然对数-ln
double pow(double, double); //返回第一个参数的第二个参数次方
double hypot(double, double); //返回两个参数平方和的平方根
double sqrt(double); //返回参数的平方根
int abs(int); //返回整数的绝对值
double fabs(double); //返回浮点数的绝对值
double floor(double); //返回小于或等于传入的参数的最大整数

四、控制结构

1. 循环结构

在Fortran中, 循环结构有两种dodo while. 分别对应着c++中的forwhile. 除此之外, c++还有一种结构do ... while. 它与while的区别在于: while是先判断后执行, do ... while是先执行后判断.

循环控制语句对应关系:

  • exit: 在c++中为break
  • cycle: 在c++中为continue

此外, 在c++中还有一种控制语句goto即跳转到某一被标记的语句执行. 但是, 在程序中不建议使用该语句!

2. 判断语句

在Fortran中, 判断语句有if-then, if-then-elseif-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++中, whileswitch都使用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
2
3
4
5
6
7
8
9
subroutine sum100(sum_)
implicit none
integer,intent(out)::sum_
integer :: i
sum_ = 0
do i=1,100
sum_ = sum_ +i
endo
end subroutine sum100

这个程序会计算1~100的和并输出到sum这个量

1
call sum100(sum1)

转换成c++

1
2
3
4
5
6
7
8
9
int sum = 0;
void sum100 (int &sum_)
{
for (int i=1; i<=100; i++) //在循环体中Fortran判断结束的条件默认是小于等于
{
sum_ += i
}
}
sum100(sum);

这类子程序的特点: 有明确的输入的输出, 全部工作都在子程序内完成, 不牵扯其他的module或子程序.


关于参数的传递

在Fortran的子程序中, 参数的传递默认按照引用来传递. 即, 如果你在子程序内改变了这一变量(未规定为in类型), 外部的变量也会改变. 这与C++中使用&符号传递参数的作用相同.

有些子程序, 在编写时, 未规定传入参数的类型为输入(in)还是输出(out). 在转换为C++时, 需要注意分辨是否需要改变外部的变量.

在转换时, in类型的变量在子程序中是无法改变的, 因此我们在写c++的函数时, 不需要加&. 而对于out类型的变量, 必须要加上&, 否则不能改变外部的变量.

对于全局变量而言, 不需要作为传入参数. 可以直接使用并改变该变量的值.

六、模块(Module)

对于这类文件, 可以使用头文件+全局变量+源文件的模式

对于全局常量和结构体, 只需要定义在头文件内即可. 在头文件中, 结构体的定义和常量的定义不使用extern声明.

在编写代码时, 可以使用namespace对不同文件的参数作区分, 防止出现命名上的冲突 (可不使用). 在c++中, 还可以使用class来更好的组织和封装数据及函数.

如果原模块没有私有变量, 可直接使用namespace.

以下是GPT对两种方式的优缺点的判断:

使用namespace的优点:

  1. namespace可以用来避免命名冲突. 你可以在不同的namespace中定义同名的函数或变量, 而不会产生冲突.
  2. namespace可以嵌套, 这使得你可以更好地组织你的代码.
  3. namespace可以在多个文件中定义, 这使得你可以将相关的代码和数据分散在多个文件中.

使用namespace的缺点:

  1. namespace不能用来封装数据. 在namespace中定义的所有函数和变量都是公开的, 任何人都可以访问.
  2. namespace不能用来实现面向对象编程的特性, 如继承和多态.

使用class的优点:

  1. class可以用来封装数据和函数. 你可以控制哪些数据和函数是公开的(public), 哪些是私有的(private).
  2. class可以用来实现面向对象编程的特性, 如继承和多态.
  3. class可以用来创建对象, 这使得你可以在运行时动态地创建和销毁数据和函数.

使用class的缺点:

  1. class不能像namespace那样用来避免命名冲突. 在同一个namespace中, 不能有两个同名的class.
  2. class不能在多个文件中定义. 一个class的所有成员都必须在同一个文件中定义.

欢迎补充和指正