这是我一直想做的整理之一。
数值方法,我大概了解了一下, 包括了矩阵消元、解方程、抽样、PDE等,基本是把大学的概率论和线性代数有效地结合。同时,无论是实习的面试过程还是在实际的运用中,也都碰到过这些问题。
去年这个时候(2019-09),我也选修了大数据学院的《数据科学中的计算机基础》这门课,被贺老师带入了数值方法的神圣世界。当时学的不是很好,因为有趣的知识太多了,而我的能力有限。因此,我决定开始重新把这部分内容进行学习和深入地挖掘。
考察了相关教材,我主要参考了清华大学出版社出版的《数值方法(C++描述)》。主要是程序比较好懂。书中的代码,我也尝试了一下,能够跑通。
我主要的运行环境是Windows10中的Ubuntu。我用的不是虚拟机,而是微软store自带的Ubuntu。
image.png
安装教程我已经写过了,如下。
No.2 Windows10环境中的Ubuntu

那么,我已经迫不及待地想要进行学习了。

一、一个例子

1. 第一次接触

书上第一个C++例子如下。书上的代码,存在一些问题,因为我的运行环境是C++11,因此我做了小小的调整。
首先,在Ubuntu中新建一个c1.cpp的文件——touch c1.cpp,然后通过emacs进行编辑(emacs的安装也在上面的教程中)。

  1. danielshen@DESKTOP-VOSNAQJ:~/nr$ touch c1.cpp
  2. danielshen@DESKTOP-VOSNAQJ:~/nr$ emacs c1.cpp

进入emacs中,内部编辑如下

#include<iostream> //告诉编译器在程序中添加两个头文件。注释采用//开启。
#include<math.h>
using namespace std;

//定义了一个program1的类;
//变量在private中,只能够在program1中被访问;
//函数在public中,能够在program1外被使用。
class program1
{
    private:
        double a, result; //定义double型数据。
    public:
        void square_root() //该函数没有任何返回值,因此是void类型。
        {
            cout << "\n Enter your number : "; //在屏幕中打印
            cin >> a; //输入的值放入a中。
            result = sqrt(a); //sqrt来自math.h
            cout << "\n square root is " << result << endl;
        }
}; //注意;不可以省略。

// int是指我们返回的值是0,因此是int整数型。
int main()
{
    program1 p1;
    p1.square_root(); //.表示调用这个类的方法,括号不省略。
    return 0;
}

然后在Ubuntu中进行编译(g++的安装也在上面的教程中)

danielshen@DESKTOP-VOSNAQJ:~/nr$ g++ c1.cpp -o c1 -std=c++11
danielshen@DESKTOP-VOSNAQJ:~/nr$ ./c2

 Enter your number : 9

 square root is 3

2. C++数据类型

  • C++主要有6个数据类型,优先级依次增大

数值方法No.1-复习C - 图2

  • 常量声明

    • const + type + name

      const float pi = 3.14159;
      
    • define [标识符名] [被替代文本]

      #define R 3.14159
      
  • 例子

气体分子的平均速度能够通过公式数值方法No.1-复习C - 图3得到,T和M是用户指定,R和数值方法No.1-复习C - 图4是常量;
代码

#include<iostream>
#include<math.h>
using namespace std;

#define PI 3.14159
const double R = 8.314e7;

class program2
{
private:
  double v, M, T;
public:
  void av_velocity()
  {
    cout << "\n Enter temperature and relative particle quality : ";
    cin >> T >> M;
    v = sqrt(8 * R * T / (PI * M));
    cout << "v = " << v << endl;
  }
};

int main()
{
  program2 p2;
  p2.av_velocity();
  return 0;
}

3. 运算符号

  • ++前后的问题:i=1,j=1的时候
    y = ++i; // y=2,i=2
    x = j++; // x=1,j=2
    
    因此,++放在前面,是先+1再使用;当到后面,是先使用再+1。

4. 循环结构

  • C++中存在三种循环结构:for,while,do while

for

for(初值; 表达式-满足了就停止; 增量表达)
{
    do something;
}

while

满足某个条件的时候执行循环,循环次数未知;需要在循环体中写入表达式,从而在某点终止;

while(表达式满足就执行[不满足就停止])
{
    do something;
}

do … while 循环

最少执行一次循环,测试条件放在循环结束的地方,如果不满足,那么循环不再执行;

do
{
    do something;
}
while(满足就执行[不满足就停止]);
  • 例子,序列为[1 1 2 3 5 8 13 ……],每一项是前两项的和,确定黄金比例=倒数第2项/倒数第1项;当连续的两个黄金比例的差距的绝对值小于数值方法No.1-复习C - 图5,那么程序终止。
  • 代码 ```cpp

    include

    include

    using namespace std;

int main() { double first=0.0, second=1.0, middle, old_r, new_r=0.0, difference; do { middle = first + second; first = second; second = middle; old_r = new_r; new_r = first / second; difference = fabs(old_r - new_r); } while(difference > 1e-12); cout << “Golden ratio = “ << new_r << endl; return 0; }


<a name="fsnML"></a>
### 5. 判断语句
for...else和switch...case。
<a name="LOGuW"></a>
#### if...else...
除了基本的用法之外,还有exit(0)的用法,也就是省略了else之后的语句,表示程序成功退出。
```cpp
#include<iostream>
#include<math.h>

using namespace std;

int main()
{
  double a, result;
  cout << "Enter a number : ";
  cin >> a;
  if(a <= 0)
    {
      cout << "Wrong." << endl;
      exit(0); //如果输入非正数,那么只运行这句话就结束了;表示程序成功退出
    }
  result = log(a);
  cout << "log a = " << result << endl;
  return 0; 
}

这里运行的时候,如果我输入0,那么只运行了if内部的内容,并不会运行result=log(a)及以下的内容;输入整数,不会运行if内部的内容,而是运行result之后的内容。

switch…case

如果是一个长的判断树,那么可以通过switch…case。

switch(int or char x)
{
    case x1: //如果x=x1
        do something1;
        break;

    case x2: //如果x=x2
        do something2;
        break;        

    default: //否则
        do something;    
}

例子:输入1,执行“a”;输入2,执行“b”;否则执行“c”。

#include<iostream>
#include<math.h>
using namespace std;

class program
{
private:
  int choice;
public:
  void switch_example()
  {    
    cout << "Enter a number of 1 or 2 : " << endl;
    cin >> choice;
    switch(choice)
      {
      case 1:
        cout << "a" << endl;
        break;

      case 2:
        cout << "b" << endl;
        break;

      default:
        cout << "c" << endl;    
      }
  }
};

int main()
{
  program p1;
  p1.switch_example();
  return 0;
}

二、函数

  • 在类的内部定义函数:当函数较小的时候定义;
  • 在类的外部定义函数,需要通过 :: 将函数所属的类关联。:: 称为作用域限定运算符,用来标识类和成员函数的联系。
  • 例子 ```cpp

    include

    include

    using namespace std;

class program { private: double a, x, f, res; public: double func(double ); // 在函数内部声明 void result(); };

// 在函数外部定义,直接使用类内部的变量。 double program::func(double x) { f = exp(-x * x / 2); return f; }

// 直接使用类内部的函数 void program::result() { a = 1.2; res = func(a); cout << “result is “ << res << endl; }

int main() { program p1; p1.result(); return 0; }


- 按引用传递&——传递的是复本,而不会修改原始值。当原始数据不应该被修改的时候,采用这种方法。

<a name="P8ZTs"></a>
## 三、C++文件处理?(待补充)

- 当程序需要大量的数据时,需要从外部读取数据。为了处理文件,必须要包含头文件**fstream**。

<a name="wqeb4"></a>
## 四、数组

- 数组包含了一些同类的数据项。**矩阵和向量**是重要的应用。矩阵的元素可以储存为数组元素。
   - 数组的定义:
```cpp
{数据类型} {数组名} [{数组大小}];

// 向量
double a [5];

// 矩阵
double A[行数][列数];
  • a是一个含有5个元素的数组,所有的元素的类型都是双精度型double,方括号中的数值指明了数组的大小。
    • 元素是a[0],a[1],a[2],a[3],a[4],方括号中的数值叫做数组下标,下标从0开始。
  • 赋值

    double a[] = {1.1, 2.2, 3.3, 4.4, 5.5};
    double b[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
    
  • 循环输出

    double a[5];
    for(int i=0; i<5; i++)
    {
    cout << a[i] << endl;
    }
    
  • 例子, ```cpp

    include

    using namespace std;

class program { private: int i, j; double A[3][5];

public: void matrix(); };

void program::matrix() { for(i=0; i<3; i++) { for(j=0; j<5; j++) { A[i][j] = i + j; } }

cout << “A = “ << endl; for(i=0; i<3; i++) { for(j=0; j<5; j++) { cout << A[i][j] << “ “; } cout << endl; } cout << endl;
}

int main() { program p1; p1.matrix(); return 0; }

结果

A = 0 1 2 3 4 1 2 3 4 5 2 3 4 5 6


- 通过指针访问数组:a[i] = *(a+i)。
```cpp
double y, value, * x;
y = 1.1;
x = &y; //指针x指向y,x是y的地址;
value = *x; //value是y的值。
  • *(array + i)和(array+i)的区别: ```cpp

    include

    using namespace std;

int main() { double b[3]; int i; for(i=0; i<3; i++) { cout << “address : “ << (b+i) << endl; //输出地址 cout << “value : “ << *(b+i) << endl; //输出值 } return 0; }


- 动态分配储存空间。通过new动态分配存储空间,使用delete释放。
```cpp
#include<iostream>
using namespace std;

class program
{
private:
  int i, j, m, n;
  double **a, *b; //a用来储存矩阵,b用来储存向量。
public:
  void dynamic1();
  void dynamic2();  
};

void program::dynamic1()
{
  cout << "Enter the number of one-directional vector: ";
  cin >> m;
  b = new double [m]; //分配内存
  for(i=0; i<m; i++){
    cout << "Enter b[" << i << "] = " ;
    cin >> b[i];
  }
  cout << endl << "b is " << endl;
  for(i=0; i<m; i++){
    cout << b[i] << " ";
  }
  cout << endl;
  delete [] b; //释放内存
}

void program::dynamic2()
{
  cout << "Enter the row of matrix : ";
  cin >> m;
  cout << endl;
  cout << "Enter the column of matrix : ";
  cin >> n;
  cout << endl;
  a = new double *[m]; //分配内存,每个元素依然是n维向量。
  for(i=0; i<m; i++)
    {
      a[i] = new double [n]; //每个元素是n维的向量。
    }
  for(i=0; i<m; i++)
    {
      for(j=0; j<n; j++)
    {
      cout << "Enter a[" << i << "][" << j << "]=";
      cin >> a[i][j];
    }
    }
  cout << endl << "matrix is " << endl;
  for(i=0; i<m; i++)
    {
      for(j=0; j<n; j++)
    {
      cout << a[i][j] << " ";
    }
      cout << endl;
    }
  cout << endl;
  delete [] a; //释放内存。
}

int main()
{
  program p1;
  p1.dynamic1();
  p1.dynamic2();
  return 0;
}
  • 注意二维数组是通过一维数据产生;同时在定义a的时候,注意是*a,以及内个元素是new double [m]。

五、构造函数constructor和析构函数(destrcutor)

  • 构造函数
    • 作用:当创建该类新的对象的时候,该函数就能够被自动调用,从而初始化成员变量或者分配储存空间
    • 使用:构造器名称和类一样,同名函数;
    • 没有任何返回类型(void,因此构造器之前的void省略)。 ```cpp

      include

      using namespace std;

class Rectangle{ private: int w, h; public: Rectangle(int, int); //构造器 int area(){return w*h;}; };

Rectangle::Rectangle(int a, int b){ //构造器 w = a; h = b; }

int main(){ Rectangle rect1(2,3), rect2(4,5); cout << “rect1 area : “ << rect1.area() << endl; cout << “rect2 area : “ << rect2.area() << endl; return 0; }


   - 这样,就**省略了void set_values(int, int )的操作**,就能够在生成对象的时候,顺便把参数一起传进去-——**构造器完成初始化**。
- 析构函数(destructor)
   - 作用:释放对象;当一个对象结束了生存期(例如自己定义的函数中变量在调用之后就结束),系统自动调用析构函数,释放该对象,从而释放内存空间。
   - 在类中,是成员函数的一种。
   - 和类同名,但是函数前加上~,表示功能和构造器相反。
   - no 参数,不能重载,no 返回值。
   - 在对象生命期即将结束时,被系统自动调用。
   - 可以是虚函数(啥事虚函数。。。)
   - 变量消除顺序是:后进先出,由最后的往最开始的消除。
```cpp
#include<iostream>
using namespace std;

class program
{
private:
  int i, n;
  double * a;
public:
  void allocate();

  ~program(){
    delete [] a;  //析构函数:在执行完allocate韩束之后,析构函数释放了数组a的内存。
  }
};

void program::allocate()
{
  cout << "Enter the number of vector : ";
  cin >> n;
  a = new double [n];
  for(i=0 ; i<n; i++)
    {
      cout << "a[" << i << "]=";
      cin >> a[i];
    }
  cout << "a[" << n << "]=" << endl;
  for(i=0; i<n; i++)
    {
      cout << a[i] << " ";
    }
  cout << endl;
}

int main()
{
  program p1;
  p1.allocate();
  return 0;
}