这是我一直想做的整理之一。
数值方法,我大概了解了一下, 包括了矩阵消元、解方程、抽样、PDE等,基本是把大学的概率论和线性代数有效地结合。同时,无论是实习的面试过程还是在实际的运用中,也都碰到过这些问题。
去年这个时候(2019-09),我也选修了大数据学院的《数据科学中的计算机基础》这门课,被贺老师带入了数值方法的神圣世界。当时学的不是很好,因为有趣的知识太多了,而我的能力有限。因此,我决定开始重新把这部分内容进行学习和深入地挖掘。
考察了相关教材,我主要参考了清华大学出版社出版的《数值方法(C++描述)》。主要是程序比较好懂。书中的代码,我也尝试了一下,能够跑通。
我主要的运行环境是Windows10中的Ubuntu。我用的不是虚拟机,而是微软store自带的Ubuntu。
安装教程我已经写过了,如下。
No.2 Windows10环境中的Ubuntu
那么,我已经迫不及待地想要进行学习了。
一、一个例子
1. 第一次接触
书上第一个C++例子如下。书上的代码,存在一些问题,因为我的运行环境是C++11,因此我做了小小的调整。
首先,在Ubuntu中新建一个c1.cpp的文件——touch c1.cpp,然后通过emacs进行编辑(emacs的安装也在上面的教程中)。
danielshen@DESKTOP-VOSNAQJ:~/nr$ touch c1.cppdanielshen@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个数据类型,优先级依次增大
气体分子的平均速度能够通过公式得到,T和M是用户指定,R和
是常量;
代码
#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的时候
因此,++放在前面,是先+1再使用;当到后面,是先使用再+1。y = ++i; // y=2,i=2 x = j++; // x=1,j=2
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项;当连续的两个黄金比例的差距的绝对值小于
,那么程序终止。
- 代码
```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; }
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的值。
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)
- 构造函数
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;
}
