OpenFOAM基础 - c++ note1

c++20的学习 -- 碎碎念01

这篇文章并不是深入学习C++的总结,只是为了记录在学习C++过程中的练习和代码,熟悉C++20的一些新特性与编程方法。对于C++而言,最重要的是设计模式,类的构思,将大型项目化整为零的能力。

静态链接库和动态链接库

静态链接库和动态链接库都是C++函数的具体实现,一般要配合相应的库头文件使用。这种做法维护了函数的具体实现不被外界获知,也使开发者不用去纠结函数的具体实现而是专注在上层结构的开发中,便于使程序整体清晰、解耦。但是静态链接库和动态链接库在使用方法上还是有很大区别:

  • 静态链接库在编译时被链接到程序中,因此也被称为静态库。当程序被编译链接时,静态链接库的代码会被全部复制到生成的可执行文件中。优点是程序的运行不依赖于外部的库文件,可以在没有额外依赖的情况下在不同的环境中运行。缺点:每个使用该静态库的程序都会包含一份静态库的副本,导致可执行文件的大小增加,并且占用更多的内存空间。
  • 动态链接库在程序运行时被加载到内存中,因此也被称为共享库。程序在运行时会动态地链接到动态链接库中的函数和资源。优点是减小了可执行文件的大小,节约了内存空间,而且如果多个程序使用同一个动态链接库,那么系统只需要加载一份该库的副本,节约了系统资源。缺点是程序的运行依赖于外部的库文件,如果某个动态链接库被删除或损坏,那么依赖于它的程序将无法正常运行。

如何在Mac中使用库文件来构建程序

首先给出一个基本的程序,这个程序是调用了外部的函数,所以需要将外部函数生成链接库的形式:

hello.hpp

1
2
#include<iostream>
#include"add.hpp" //外部函数头文件

hello.cpp

1
2
3
4
5
6
7
8
9
#include"hello.hpp"
using namespace std;
int main()
{
int a,b;
cin>>a>>b;
cout<<add(a,b)<<endl;
return 0;
}

外部函数add.hpp头文件:

1
int add(int a, int b);

外部函数add.cpp头文件:

1
2
3
4
5
#include"add.hpp"
int add(int a, int b)
{
return a+b;
}

首先使用clang++将外部函数编译成函数链接库:

1
clang++ -std=c++20 -shared -o libadd.dylib add.cpp

其中,-shared指令表示生成共享链接库,就是动态链接库,如果是静态链接库,就是-static,然后就是编译过程中引用外部链接库:

1
clang++ -std=c++20 -I ./ hello.cpp -L./ -ladd -o hello

注意,这其中-I、-L、-l的作用有差异,作用如下(chatgpt):

在使用 g++ 编译器时,可以通过不同的选项来指定库文件和头文件的位置,其中 -L-l-I 是常用的选项,它们分别表示如下意思:

  1. -L 选项:
    • -L 选项用于指定库文件的搜索路径,告诉编译器在哪里寻找要链接的库文件。
    • 例如,如果你的库文件位于 /path/to/lib 目录下,你可以使用 -L/path/to/lib 来告诉编译器在该目录下寻找库文件。
  2. -l 选项:
    • -l 选项用于指定要链接的库文件的名称,通常是去掉开头的 lib 前缀和文件扩展名的部分。
    • 例如,如果你要链接的库文件为 libexample.alibexample.so,你可以使用 -lexample 来告诉编译器链接这个库文件。
  3. -I 选项:
    • -I 选项用于指定头文件的搜索路径,告诉编译器在哪里寻找包含文件(头文件)。
    • 例如,如果你的头文件位于 /path/to/include 目录下,你可以使用 -I/path/to/include 来告诉编译器在该目录下寻找头文件。

综上所述,-L 用于指定库文件的搜索路径,-l 用于指定要链接的库文件的名称,而 -I 用于指定头文件的搜索路径。这些选项可以帮助编译器正确地找到并链接需要的库文件和头文件,从而顺利完成程序的编译过程。

函数重载

函数重载是重要内容,在C++中应用广泛。声明一个函数需要返回值类型 + 函数名 + 参数列表,由于C++中每一个变量都必须说明变量的类型,是浮点型、整数型还是字符类型等。所以对应不同的输入参数,如果函数要实现的功能又一致的话,就需要进行函数重载。函数重载有三条限制:

  • 被重载的函数具有相同的函数名
  • 参数列表不同:被重载的函数必须具有不同的参数列表。参数列表可以通过参数的类型、参数的顺序或参数的个数来区分。
  • 返回类型不同不足以进行重载:函数的返回类型不会被视为函数重载的一部分。如果两个函数具有相同的参数列表,但是返回类型不同,将无法进行重载。

以下为具体实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
using namespace std;
int add(int a, int b)
{
return a+b;
}
double add(double a, double b)
{
return a+b;
}
// void add(int a, int b) 不构成函数重载
// {
// int c = a+b;
// }
int main()
{
int a,b;
double a1,b1;
cin>>a>>b;
cin>>a1>>b1;
cout<<add(a,b)<<endl;
cout<<add(a1,b1)<<endl;
return 0;
}
程序的输入与输出:
1
2
3
4
5
6
in:
10 10
98.5 1.5
out:
20
100

函数模板

如果每一个不同类型的输入,就要反复重载函数的话,也确实太过于繁琐,因此,采用函数模板可以简化程序,避免大量冗余的代码。通过模板,C++可以根据输入值的类型来反推函数参数的类型,然后将这个函数实例化,变成可以实用的函数。引入模板就已经开始初步涉入泛型编程了。

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
#include<iostream>
using namespace std;
template <typename T>
T maximum(T a,T b,T c)
{
T temp=a;
if(a<b)
temp = b;
if(temp<c)
temp = c;
return temp;
}

template<typename T1, typename T2> //多个参数类型
void print(T1 a, T2 b)
{
cout<<a<<" "<<b<<endl;
}
int main()
{
int a = 10,b=11,c=12;
cout<<maximum(a,b,c)<<endl;
double a1 = 10.1, b1 = 11.1, c1 = 12.1;
cout<<maximum(a1,b1,c1)<<endl;

int c2 = 100;
double c3 = 10.1;
print(c2,c3);
return 0;
}

数组

在c语言中,数组实质是指针,开辟一片连续的内存空间,都需要预先申请内存。C++中,可以使用一些新玩法,新的数据结构。一个是array,一个是vector。其中array是固定数组,类似于在c语言中看到的数组,使用需要引用头文件array。vector是可变向量,使用需要引用头文件vector。 无论是array还是vector都是类模板,需要在指定类型后,才能成为一个实际的类。数组的使用,无非是声明,遍历,赋值及修改值,查找和排序。首先以array为例: ### 数组的声明及初始化/赋值

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
39
#include<iostream>
#include<array>
using namespace std;
void test0()
{
array<int,5> a; //输入int表示这是一个整数类型指针,5表示有5个元素
array b{1,2,3,4,5}; //同样,对于简单数据类型,也可以直接声明,让array去猜去判断数据类型到底是什么
//接下来是循环遍历
//第一种方案,类似于普通数组的用法
for(int i = 0;i<a.size();i++) //a.size()返回数组元素个数
{
a[i] = i+1;
cout<<a[i]<<endl;
}
cout<<"above is the first method"<<endl;

//第二种方案,使用迭代器
for(auto it = a.begin(); it!= a.end();it++) //auto关键字一般使用来自动判断数据类型的,auto可以简化迭代器的声明
{
cout<<*it<<endl; //这里的it更像是指针
*it = *it * 2;
cout<<"after: "<<*it<<endl;
}
cout<<"above is the second method"<<endl;
//前面两种,没办法直接判断数组越界
for(int i = 0;i<a.size();i++)
{
cout<<a.at(i)<<endl;
a.at(i) = a.at(i)*2;
cout<<"after: "<<a.at(i)<<endl;
//a.at(5)就会抛出异常,在工程代码中非常有用
}
cout<<"above is the third method"<<endl;
}
int main()
{
test0();
return 0;
}
### 数组的查找和排序 数组array作为模板类,本身也提供了一系列查找方法,可以很方便对数组进行一些操作。在Algorithm库中,有sort函数可以使用,需要给出数组的首尾数据,默认是升序排列。如果要改变排序的方法,可以自己定义排序函数。
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
39
40
41
42
43
44
45
46
47
48
49
50
#include<iostream>
#include<array>
#include<algorithm>
using namespace std;
void test1()
{
array b{6,7,3,9,10}; //初始化一个数组
std::sort(b.begin(),b.end()); //默认升序排列
for(int &item : b)
{
std::cout<<item<<endl;
}
cout<<"after the sorting"<<endl;
}
bool cmp(int a,int b)
{
return a>b; //降序排列函数
}
void test2()
{
array b{6,7,3,9,10}; //初始化一个数组
std::sort(b.begin(),b.end(),cmp); //默认升序排列
for(int &item : b)
{
std::cout<<item<<endl;
}
cout<<"after the sorting: decend"<<endl;
}
void test3()
{
array b{6,7,3,9,10}; //初始化一个数组
std::sort(b.begin(),b.end()); //默认升序排列
for(int & item : b)
{
std::cout<<item<<endl;
}
cout << std::binary_search(b.begin(),b.end(),6)<<endl;
bool flag{std::binary_search(b.begin(),b.end(),10)}; //注意,实测降序无法使用二分查找
if(flag)
cout<<"I find it"<<endl;
else
cout<<"Sorry I cannot find it"<<endl;
}
int main()
{
//test1();
//test2();
test3();
return 0;
}
其实还有一个点是多维数组,多维数组本质上是多个降一维度的数组迭加,有一个嵌套的使用在:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <array>

int main() {
// 创建一个二维数组,3行四列,注意这里声明的时候,行列的位置是反的
std::array<std::array<int, 4>, 3> myArray = {{
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
}};
// std::array<std::array<int, 4>, 3> myArray{std::array{1, 2, 3, 4},std::array{5, 6, 7, 8},std::array{9, 10, 11, 12}};
// 访问并输出数组元素
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 4; ++j) {
std::cout << myArray[i][j] << " ";
}
std::cout << std::endl;
}

return 0;
}

函数式编程

其实函数本身也可以作为“参数”传递给其他函数使用,函数式编程也具有“增量更新”的特性。原本在面向过程的开发流场中,稍微改动一个函数的功能,就要修改变量,可能会引入非常多的错误。C++中的函数编程是指利用函数作为一等公民(First-Class Citizen)来编写程序,这意味着函数可以像其他类型的数据一样被传递、赋值、返回和操作。函数式编程强调函数的纯粹性和不可变性,鼓励使用无副作用的函数和避免共享状态。

函数可以作为参量被调用,也可以和函数模板配合:

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
39
40
41
#include <iostream>

// // 定义一个需要传入函数作为参数的函数
// void performOperation(int x, int y, int (*operation)(int, int)) {
// int result = operation(x, y);
// std::cout << "The result is: " << result << std::endl;
// }

// 定义一个函数,用于相加
// int add(int a, int b) {
// return a + b;
// }

// 定义一个函数,用于相乘
int multiply(int a, int b) {
return a * b;
}

template<class T>
T add(T a, T b)
{
return a+b;
}
template<class T1> //函数模板,不指定参数类型
void performOperation(T1 x, T1 y, T1 (*operation)(T1, T1)) { //operation为别名
T1 result = operation(x, y);
std::cout << "The result is: " << result << std::endl;
}

int main() {
// 将相加函数作为参数传递给 performOperation 函数
performOperation(3, 4, add);

performOperation(3.5, 4.6, add);

// 将相乘函数作为参数传递给 performOperation 函数
performOperation(3, 4, multiply);

return 0;
}

函数指针的声明和调用

在C++中,函数指针是指向函数的指针变量。它存储了函数的地址,可以通过函数指针调用相应的函数。

函数指针的声明和使用方式如下:

  1. 声明函数指针:
1
returnType (*ptrName)(parameterTypes);

这里的 returnType 是函数的返回类型,parameterTypes 是函数的参数列表,ptrName 是函数指针的名称。

  1. 将函数的地址赋给函数指针:

    1
    ptrName = functionName;
    这里的 functionName 是函数的名称,可以直接将函数名赋给函数指针。

  2. 通过函数指针调用函数:

    1
    returnType result = (*ptrName)(arguments);
    或者简写为:
    1
    returnType result = ptrName(arguments);
    这里的 arguments 是函数的参数。 举一个实例:
    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
    #include<iostream>
    using namespace std;
    //template<class T>
    //using op = T(*) (T , T );
    using Operation = int(*)(int, int);
    int add(int a, int b)
    {
    return a+b;
    }
    int multiply(int a,int b)
    {
    return a*b;
    }
    double multiply(double a,double b)
    {
    return a*b;
    }
    int main()
    {
    int a = 10, b = 10;
    double a1 = 11.1, b1 = 12.1;
    Operation operation;
    operation = add;
    cout<<operation(a,b)<<endl;
    operation = multiply;
    cout<<operation(a,b)<<endl;
    return 0;
    }

同样的,可以结合函数模板和函数指针,实现类似多态的效果,调用不同返回类型的函数:

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
#include <iostream>

// 定义一个函数模板
template <typename T>
void process(T value, void (*func)(T)) {
func(value);
}

// 定义两个不同的函数
void printInt(int value) {
std::cout << "Integer value: " << value << std::endl;
}

void printDouble(double value) {
std::cout << "Double value: " << value << std::endl;
}

int main() {
// 使用函数模板和函数指针实现多态效果
process(5, &printInt); // 调用 printInt 函数
process(3.14, &printDouble); // 调用 printDouble 函数

return 0;
}


OpenFOAM基础 - c++ note1
http://example.com/2023/11/01/post04/
作者
Biheng Xie
发布于
2023年11月1日
许可协议