吾生有涯 学海无涯
析模有界 知识无界

OpenFOAM|02 C++扫盲

内容纲要

本文介绍入门OpenFOAM所必须的C++语言基础。

注:

本文内容取自Wolf Dynamics公司的培训教材《C++: A Crash introduction》。

1 C++程序基本结构

先来看一个最简单的C++程序示例。

在任意目录下创建一个cpp扩展名的文件(如demo.cpp),利用任何文本编辑器打开,输入下面的内容:

#include 
using namespace std;

int main()
{
cout << "hello,world!" << endl;
return 0;
}

保存文件后在终端输入:

g++ demo.cpp -o demo
./demo

终端输出如下图所示。

关于最简单的CPP代码:

  • 程序第1行利用预处理命令#include包含了一个名为iostream的头文件
  • 程序第2行表示使用std命名空间,C++中使用命名空间对函数作用范围进行限制
  • 定义了一个名为main的入口函数。所有的C++程序都必须有main函数
  • 在main函数中利用标准输出cout实现了输出信息“hello,world!”到终端的功能

CPP代码中可以使用两种形式的注释,采用/* *///。其中/**/用于多行注释,而//用于单行注释。一个简单的注释示例如下所示。

/*
这里定义了一个最简单的C++程序,
其主要功能是向屏幕打印一行信息。
*/

#include
using namespace std;

//main()函数是C++程序的入口程序
//所有C++程序中都必须包含有main()函数
int main()
{
cout << "hello,world!" << endl; //打印信息到屏幕上
return 0; //返回空值
}

2 数据类型

C++中的标准数据类型如表所示。

数据类型 关键字
布尔型 bool
字符型 char
整型 int
浮点型 float
双精度浮点型 double
无值型 void
宽字符型 wchar_t

C++允许char、int和Double数据类型前面有修饰符。修饰符用于改变基类型的含义,使其更符合实际需要。一些常用的数据类型修饰符如signed、unsigned、long、short等。

修饰符signed、unsigned、long和short可以应用于整数基类型。此外,有符号和无符号可以应用于char,long可以应用于double。如下所示。

unsigned int y;  //4字节。范围从0到4294967295
signed int y; //4字节。范围从-2147483648到-2147483647

3 变量与常量

3.1 变量声明与初始化

C++中的变量在使用之前需要先进行声明,建议在声明变量的同时对其初始化。

如下一些变量声明与初始化的示例:

int a , b , c;       //声明了三个变量a,b,c
int aa = 5; //声明了一个变量aa,并将其初始化为5
int b (2); //声明变量b,设置其初始值为2
float pi; //声明一个浮点值变量pi
pi = 3.1415926; //为变量pi赋初始值

3.2 变量作用域

定义的变量可以是全局范围,也可以是局部范围。全局变量是在所有函数之外的源代码主体中声明的变量,而局部变量是在函数体或块内部声明的变量。

如下面的示例代码,定义了一个全局作用域变量a,在程序的所有位置都可以直接调用。

#include 
using namespace std;

int a = 4; //全局作用域变量
int main()
{
int b = 4; //局部作用域变量
int result = 0;
result = a * b;
cout << result << endl; //输出结果
return 0;
}

上面代码的输出结果为16。

3.3 常量

C++中的常量定义的是确定不变的值。

#include 
using namespace std;

#define PI 3.1415926 //全局常量
int main()
{
const float pi = 3.1415926; //局部常量
float r = 1.0;
double area1 = pi * r * r;
double area2 = PI * r * r;
cout << "area1 = " << area1 << endl;
cout << "area2 = " << area2 << endl;
return 0;
}

程序输出结果如下图所示。

4 运算符

运算符是告诉编译器执行特定数学或逻辑操作的符号。C++具有丰富的内置运算符:

  • 赋值运算符:=
  • 算术运算符:+,-,,*,%
  • 增加/减少:++,--
  • 关系运算符:==,!=,>,<=
  • 逻辑运算符:&&,||,!
  • 按位运算符:&,|,^,~,<<,>>
  • 赋值运算符:+=,-=,*=,/=,%=,<<=,>>=,&=,^=,|=
  • 其他运算符:?,逗号(,),箭头(->),Sizeof()

5 控制结构

控制结构是程序代码的一部分,其根据条件以某种方式执行这些语句。通常有两种类型:条件与循环。

5.1 条件控制

C++中的条件结构采用if来实现条件结构:

if(condition) statement

下面是一个简单的if结构:

if(x < 7)
{
cout << "x is:" << x << endl;
}

if可以与else配合构成更复杂的判断分支结构,如下面的示例:

if(x == 7)
{
cout << "x is 7" << endl;
}
else
{
cout << "x is not 7" << endl;
}

如果有多个分支,还可以使用else if,如下面的示例:

if (x > 0)
cout << "x is positive";
else if (x < 0)
cout << "x is negative";
else
cout << " x is 0";

除了if之外,分支结构还可以使用switch

5.2 循环控制

c++中的循环主要有whiledo while以及for

如下面的代码示例:

#include 
using namespace std;

int main()
{
int n = 0;
while (n < 10)
{
cout << n << endl;
n++;
}
return 0;
}

程序输出结果如下图所示。

相同的代码可以改写为:

#include 
using namespace std;

int main()
{
int n = 0;
do
{
cout << n << endl;
n++;
} while (n < 10);
return 0;
}

相同功能的代码还可以改写为:

#include 
using namespace std;

int main()
{
for (int n = 0; n < 10; n++)
{
cout << n << endl;
}
return 0;
}

可以看到利用for循环的程序代码比较简洁。不过在使用for循环时,注意圆括号中必须有两个分号。

C++的判断与循环结构都可以嵌套。

6 函数

函数是一组共同执行任务的语句。每个C++程序至少有一个函数,即main()函数。在程序设计过程中可以将代码划分为不同的函数。函数声明告诉编译器函数的名称、返回值类型和函数参数。函数定义提供函数的实际主体。C++标准库中提供了大量可以直接调用的内置函数。例如,函数strcat()连接两个字符串,函数memcpy()将一个内存位置复制到另一个位置。

C++中的函数定义形式如下所示:

type name(parameter1, parameter2, ...){statements}

这里:

  • type:为函数返回值的数据类型,如int,float等
  • name:为函数名称,可以是C++语言能够允许的任意字符组合
  • parameters:允许在调用函数时将参数传递给函数。不同的参数用逗号分隔。每个参数都由一个数据类型说明符和一个标识符组成,就像任何常规变量声明一样(例如:intx)。
  • statements:函数的主体。通常为花括号括起来的语句块

如下面的示例代码:

#include 
using namespace std;

int add(int a, int b)
{
int c = a + b;
return c;
}

int main()
{
int result = add(1, 3);
cout << "result is:" << result << endl;

return 0;
}

程序输出结果如下图所示。

上面的代码还可以改写成下面的形式(先声明后实现):

#include 
using namespace std;

int add(int, int)

int main()
{
int result = add(1, 3);
cout << "result is:" << result << endl;

return 0;
}
int add(int a, int b)
{
int c = a + b;
return c;
}

关于C++中的函数:

  • 在函数或任何其他内部块中声明的变量的作用域只是它们自己的函数或块,不能在它们的外部使用。因此,局部变量的作用域被限制在声明它们时所在的同一块级别。尽管如此,我们也可以声明全局变量;这些变量从代码的任何位置都是可见的,无论是在所有函数内部还是外部。
  • 函数没有返回值,则应声明为void函数。

只有参数类型或参数个数不同时,两个不同的函数才能同名,这种做法称为重载函数,如下面的代码示例:

#include 
using namespace std;

int add(int a, int b)
{
int c = a + b;
return c;
}

double add(double a, double b)
{
return a + b;
}

int main()
{
int res1 = add(1, 3);
double res2 = add(1.2, 3.4);
cout << "res1 is:" << res1 << endl;
cout << "res2 is:" << res2 << endl;

return 0;
}

程序运行结果如下图所示。

C++中的函数参数可以按值或按引用调用。按值传递变量时,我们传递给函数的是其值的副本,而不是变量本身,当按值传递变量时,对函数内部传递的变量的任何修改都不会对函数外部变量的值产生任何影响。当通过引用传递变量时,我们不是在传递其值的副本,而是以某种方式将变量本身传递给函数,并且我们对局部变量所做的任何修改都将影响在调用函数时作为参数传递的对应变量。引用传递也是允许函数返回多个值的有效方式。

如下面的示例代码:

#include 
using namespace std;

void func1(int a, int b)
{
a++;
b++;
}

void func2(int &a, int &b)
{
a++;
b++;
}

int main()
{
int a = 1, b = 2;
func1(a, b);
cout << "a=" << a << ",b=" << b << endl;
func2(a, b);
cout << "a=" << a << ",b=" << b << endl;
return 0;
}

运行结果如下图所示,可以看到按照传值调用的方式,函数内的参数改变并不会影响原参数;而采用传址调用的方式,函数体内对参数的任何操作都会影响到原参数。

7 数组

数组是顺序存储在内存中的固定数量的相同类型的元素。因此,整数数组包含一定数量的整数,字符数组包含一定数量的字符,依此类推。数组的大小称为其维数。

7.1 数组的定义与初始化

要在C++中声明数组,可以编写以下代码:

type arrayName[dimension];

这里type指的是数组元素的类型(int,float,double等);arrayName为有效的标识符;dimension为数组的维数。

与普通变量一样,数组的元素必须先初始化之后才能使用,否则在程序中可能会得到意想不到的结果。有几种初始化数组的方法。

一种方法是声明数组,然后初始化部分或全部元素,如下面的代码示例:

int coor[4];
coor[0] = 1;
coor[1] = 0;
coor[2] = 3;
coor[3] = 7;

也可以在定义数组时直接给出所有的元素值,如下所示:

int coor[4] = {1, 0, 3, 7}

有时省略数组的大小会更方便,让编译器根据提供的元素数量来确定数组的大小:

int coor [ ] = { 1, 0, 3, 7, 2, 5, 2, 11 };

C++还支持通过添加多组括号来创建多维数组。因此,可以按如下方式创建二维数组:

type arrayName[dimension1][dimension2]

二维数组的初始化方式与一维数组类似,如下面提供了三种等效的二维数组初始化方式。

    int coor2[2][4];
coor[0][0] = 1;
coor[0][1] = 0;
coor[0][2] = 3;
coor[0][3] = 7;
coor[1][0] = 2;
coor[1][1] = 5;
coor[1][2] = 2;
coor[1][3] = 11;

int coor2[2][4] = {1, 0, 3, 7, 2, 5, 2, 11};
int coor3[2][4] = {{1, 0, 3, 7}, {2, 5, 2, 11}};

请注意,初始化多维数组时必须始终提供维度,否则编译器不可能确定预期的元素如何切分。多维数组只是程序员的抽象,因为数组中的所有元素在内存中都是连续的。声明int coor[2][4]与声明int coor[8]是等效的。

若要为所有数组指定同一初始值,可以采用下面的方式。如下面的代码分别为二维数组和一维数组指定其中所有元素值为0。

int coor[2][30] = {0};
int coor[60] = {0};

数组初始化也可以利用循环来实现,如下面的代码:

int coor[2][20];
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 20; j++)
{
coor[i][j] = 0;
}
}

7.2 数组的访问

数组可以使用元素索引进行访问,注意C++中数组元素索引是从0开始的。

int coor[4] = {1, 0, 3, 7};
a = coor[2];

这里变量a的值为3。

7.3 数组作为函数参数

数组作为函数参数时为传址调用,函数体内对于数组元素的改变会影响到原数组的内容。在函数定义时,只需要提供参数名以及一对方括号即可。如下所示。

void func(int param[])
{

}

如下面的示例代码:

#include 
using namespace std;

void printArray(int arg[], int length)
{
for (int i = 0; i < length; i++)
{
cout << arg[i] << endl;
}
cout << endl;
}

int main()
{
int array[4] = {1, 2, 3, 4};
printArray(array, 4);
return 0;
}

其输出结果如下图所示。

在函数声明中,也可以包含多维数组。对于二维数组,格式为:

void func(int param[][4])

注意,第一个括号留空,而后面几个不留空。因为编译器必须能够在函数内确定每个附加维度的大小。

8 指针

指针是C++语言最强大和最令人困惑的方面之一。有些C++任务使用指针更容易执行,而有些C++任务,如动态内存分配,没有指针就无法执行。

指针是保存变量地址的变量。与常规变量一样,我们必须先声明一个指针,然后才能使用它。指针变量声明的一般形式为:

type *var-name;

其中,type为指针基础类型,其必须为有效的C++类型;var-name为指针变量的名称。

int *ip;    //指向整型双精度*dp的指针
double *dp; //指向双精度浮点型的指针
float *fp; //指向浮点型字符的指针
char *ch; //指向字符的指针

指针只保存变量的内存地址,因此在为指针赋值时,其值必须是地址。要获得变量的地址,可用使用与号(&)运算符,其表示内存中的地址。

如下面的代码:

#include 
using namespace std;
int main()
{
int value = 5;
int *ptr = &value;
cout << &value << endl;
cout << ptr << endl;
return 0;
}

运行结果如下图所示。

指针也可以作为函数参数,其作为函数参数时为传址调用,函数体内对指针变量的任何改变都会影响到原参数值。如下面的代码示例:

#include 
using namespace std;

void add(int *a, int *b)
{
*a = *a + *b;
*b = *a + *b;
}

int main()
{
int a = 5;
int b = 4;

add(&a, &b);
cout << a << endl;
cout << b << endl;
return 0;
}

运行结果如下图所示,可以看到函数体内操作改变了参数值。

指针也可以用于替代数组,通过将数组名赋值给指针变量,之后就可以利用指针访问数组元素。如下面的示例代码:

#include 
using namespace std;

int main()
{
int arr[5] = {1, 2, 3, 4, 5};
int *ptr = arr;
for (int i = 0; i < 5; i++)
{
cout << *(ptr + i) << endl;
}
return 0;
}

运行结果如下图所示。

9 命名空间

命名空间解决了不同代码段之间的命名冲突问题。例如,我们可能正在编写一些具有名为myFunction()的函数的代码。如果我们决定使用第三方库,它也有一个myFunction()函数。编译器无法知道我们在代码中引用的是哪个版本的myFunction()。我们不能更改库的函数名,更改您自己的函数名可能会很痛苦。此时命名空间就是为克服这一困难而设计的,用于区分相似的函数、类、变量等,这些函数、类、变量等在不同的库中具有相同的名称。实际上命名空间定义了一个作用域。

如下面的示例程序。

#include 
using namespace std;

namespace first_space
{
void func()
{
cout << "inside first_space." << endl;
}
} // namespace first_space

namespace second_space
{
void func()
{
cout << "inside second_space." << endl;
}
} // namespace second_space

int main()
{
first_space::func();
second_space::func();
return 0;
}

程序运行结果如下图所示。

可以看到,通过使用命名空间将具有相同名称的函数给分割开,此时可以根据命名空间来调用不同的函数了。

10 定义数据类型

C++允许基于其他现有数据类型定义新的数据类型。我们可以使用关键字typedef来实现这一点,其格式为:

typedef existing_type new_type_name;

其中existing_type是C++基本类型或复合类型,而new_type_name是新类型的名称。

例如定义新的数据类型:

typedef Vector doubleVector;

此时下面的语句是等效的:

vector a(8);
doubleVector a(8);

注意,typedef并不会创建新的数据类型,其只是将现有类型换个形式进行包装而已,方便程序的编写。

11 类

类(class)只不过是用户定义的数据类型,可以将其视为用户子定义的数据类型。

11.1 类定义

类用于指定对象的形式,它将数据表示和用于操作该数据的方法组合到一个整齐的包中。也就是说,在一个类中,可以声明变量类型以及想要对这些变量(函数)做什么。类中的数据和函数称为类的成员。类成员可以拥有私有、保护或公共访问权限。这些访问说明符指定成员如何访问,默认情况下所有的类成员以私有的形式进行访问。

类通常使用关键字class进行定义,其形式为:

class class_name {
access_specifier_1:
member1;
access_specifier_2:
member2;
...
} object_names;

其中class_Name为类名称标识符;object_names为该类对象的可选名称列表。access_specifier为成员访问形式,可以是private、public、protected。关于类访问权限:

  • private:类的私有成员只能从同一类的其他成员或其朋友访问。
  • protected:受保护成员可从其同一类的成员和其友元访问,也可从其派生类的成员访问。
  • public:可以从对象可见的任何位置访问公共成员。

默认情况下,使用class关键字声明的类,其所有成员的访问权限均为private。

下面是一个类的定义示例。

#include 
using namespace std;

class CRectangle
{

private:
int x, y;

public:
void setValues(int, int);
int area()
{
return x * y;
}
};

void CRectangle::setValues(int a, int b)
{
x = a;
y = b;
}

int main()
{
CRectangle rect;
rect.setValues(3, 4);
cout << "area: " << rect.area() << endl;
return 0;
}

程序运行结果如下图所示。

在示例代码中:

  • 定义了一个名为CRectangle的类,该类有两个成员变量xy
  • 类CRectangle包含两个成员函数:setValues与area
  • 函数setValues用于初始化成员变量,函数area用于实现某一功能

11.2 构造函数与析构函数

上面定义的类利用函数setValue来进行成员变量的初始化,实际上并不需要如此去做,在C++的类定义中,可以使用构造函数来实现此功能。

#include 
using namespace std;

class CRectangle
{

private:
int x, y;

public:
//定义构造函数,用于成员变量的初始化
CRectangle(int a, int b)
{
x = a;
y = b;
}
int area()
{
return x * y;
}
};

int main()
{
CRectangle rect(3, 4);
cout << "area: " << rect.area() << endl;
return 0;
}

上面的代码运行结果如下图所示。

构造函数必须与类同名,并且不能有任何返回类型,甚至不能是void。不能将构造函数作为常规成员函数显式调用。构造函数仅在创建该类的新对象时自动执行。

除了构造函数外,C++类还提供了析构函数用于类销毁时清理内存,释放动态创建的对象。析构函数的形式与构造函数类似,只不过在函数前面加上符号~

#include 
using namespace std;

class CRectangle
{

private:
int x, y;
public:
//定义构造函数
CRectangle(int a, int b)
{
x = a;
y = b;
}
//定义析构函数
~CRectangle()
{
//delete x;
//delete y;
}
int area()
{
return x * y;
}
};

int main()
{
CRectangle rect(3, 4);
cout << "area: " << rect.area() << endl;
return 0;
}

如果没有在类定义中声明任何构造函数和析构函数,编译器会假定该类有一个没有参数的默认构造函数和析构函数。

创建指向类的指针是完全有效的。我们只需考虑,一旦声明,类就成为有效的数据类型,因此可以使用类名作为指针的类型。例如语句CRectangle *prest;定义了一个指向CRectangle类的对象的指针prect,此时想要引用指针指向的对象的成员,可以使用间接箭头操作符(->)。

12 模板

模板是泛型编程的基础。泛型编程涉及以独立于任何特定类型的方式编写代码。

模板是用于创建泛型类或函数的蓝图或形式:

  • FunctioN templates:函数模板是可以操作泛型类型的特殊函数。其允许创建函数模板,其功能可以适应多个类型或类,而不需要为每种类型重复完整的代码
  • Class templates:还可以编写类模板,这样一个类就可以拥有使用模板参数作为类型的成员。

12.1 模板函数

通用模板的定义形式如下所示:

template <class identifier> identifier function_name(parameter list)
{

//函数体
}

如下面一个简单的代码示例:

template <class T> T addval(T a, T b)
{

return (a+b);
}

这里创建了一个模板函数,其标识符为T。模板标识符表示尚未指定的类型。函数addval返回此仍未定义类型的两个参数的相加。使用此模板函数可以采用这样的形式:

int x,y;
addval<int> (x,y);

当编译器遇到对模板函数的调用时,它使用模板自动生成一个函数,将T替换为作为实际模板参数传递的类型(在本例中为int),然后调用它。此过程由编译器自动执行,并且对程序员是不可见的。

下面是一个模板函数的详细示例:

#include 
using namespace std;

template <class T>
T addval(T a, T b)
{

T result = a + b;
return result;
}

int main()
{
int k = 6, m = 4, p = 0;
double n = 3.14, r = 12.07, u = 0.0;
p = addval<int>(k, m);
u = addval<double>(n, r);
cout << "result p is:" << p << endl;
cout << "result u is:" << u << endl;
return 0;
}

该示例运行结果如下图所示。

也可以定义包含多个参数的模板函数,如下面的示例代码:

template <class T,class U> T addval(T a, U b)
{

T result = a + b;
return result;
}

调用的时候可以如下所示:

int x = 3;
double y = 4.5;
addval<int,double>(x,y);

12.2 模板类

除了模板函数外,C++还允许定义模板类,如下面的示例代码:

#include 
using namespace std;

template <class T>
class twovals
{

T a, b;

public:
twovals(T first, T second)
{
a = first;
b = second;
}
T getmax();
};

template <class T>
T twovals:
:getmax()
{
T retval = a > b ? a : b;
return retval;
}

int main()
{
twovals<int> intObject(100, 75);
twovals<double> douObject(11.21, 22.32);
cout << intObject.getmax() << endl;
cout << douObject.getmax() << endl;
return 0;
}

运行结果如下图所示。

乍一看,类和函数模板很难理解,但在实际应用中只需要根据其语法直接套用即可。在C++中,模板充当创建其他类似函数或类的模式。

C++中模板背后的基本思想是创建函数和类,而不必指定某些或所有变量的确切类型。相反,使用占位符类型(称为模板类型参数(标识符))定义函数或类。一旦使用这些占位符类型创建了函数,实际上就有效地创建了函数模板或类模板。

13 标准模板库

C++标准模板库(Standard Template Library,STL)是一组功能强大的C++模板类,它们提供通用的模板化类和函数,这些类和函数实现许多常用的算法和数据结构,如向量、列表、队列和堆栈。

C++STL库的核心由三个结构良好的组件组成:

  • 容器:用于管理特定类型的对象集合。有几种不同类型的容器,如双队列、列表、向量等。
  • 算法:作用于容器。它们提供了执行容器内容的初始化、排序、搜索和转换的方法。
  • 迭代器:用于遍历对象集合的元素。

所有这三个组件都有一组丰富的预定义函数,可以帮助我们以非常高效的方式完成复杂的任务。

下面的示例代码演示了一个向量容器(一个C++标准T模板),其类似于一个数组,不同之处在于它会自动处理存储需求。

#include 
#include
using namespace std;

int main()
{
vector<int> vec;
cout << "vector size = " << vec.size() << endl;
for (int i = 0; i < 5; i++)
{
vec.push_back(i);
}
cout << "extended vector size = " << vec.size() << endl;

for (int i = 0; i < 5; i++)
{
cout << "value of vec [" << i << "]= " << vec[i] << endl;
}

vector<int>::iterator v = vec.begin();
while (v != vec.end())
{
cout << "value of v =" << *v << endl;
v++;
}

return 0;
}

程序输出结果如下图所示。

可以看到,利用标准模板类可以实现比数组更强大的功能。


本文只是C++语言的扫盲介绍,更多更详细的内容可参阅《C++ Primer Plus》。C++是一种极其复杂的计算机程序设计语言,想要入门需要花费极多的时间和极大的毅力。

本篇文章来源于微信公众号: CFD之道

赞(1) 打赏
版权声明:未经允许,请勿随意用于商业用途。
文章名称:《OpenFOAM|02 C++扫盲》
文章链接:https://www.topcfd.cn/12059/
本站资源仅供个人学习交流,请于下载后24小时内删除,不允许用于商业用途,否则法律问题自行承担。
分享到

说两句 抢沙发

评论前必须登录!

 

觉得文章有用就打赏一下文章作者吧

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫

微信扫一扫

登录

找回密码

注册