MATLAB:你不知道的12个基础知识

  之前的文章,可能过于晦涩难懂。再加上我写的也不好,解释的不够清楚,导致最后看的人不多。这次来点简单轻松的,争取大家都能看懂。
  下面都是我自己个人总结的,未经本人许可,禁止转载(长文预警,能仔细看完的,看完还不主动点赞收藏的,我觉得应该没有。)

1.

  我们知道, 是一个小数点后无限位的无理数,计算机是无法精确表示的。所以,在MATLAB中,pi只是一个近似值,3.141592653589793,精确到小数点后15位。

pi  ==  3.141592653589793  
% ans = 1

2. sin(pi)≠0

  因为,导致的一个问题是,sin(pi)并不精确的等于0。事实上,所有的sin(n*pi)都不为0,且互不相等。

sin(pi)  
% ans = 1.224646799147353e-16  
sin(2*pi)  
% ans = -2.449293598294706e-16  
sin(3*pi)  
% ans = 3.673940397442059e-16

甚至可以看到,当n足够大时,sin(n*pi)会接近于±1.

sin(4e16*pi)  
% ans = -0.999478704149319

正确地计算的方法是,利用sinpi函数。这是一个R2018b推出的新函数,能够精确计算形如的值。

sinpi(1)  ==  0  
% ans = 1  
sinpi(4e16)  ==  0  
% ans = 1  
sinpi(1/4)  ==  sqrt(2)/2  
% ans = 1

如果是早期版本的MATLAB,就只能将弧度制转换成角度制后,用sind函数计算了。

sind(180)  ==  0  
% ans = 1  
sind(180*4e16)  ==  0  
% ans = 1  
sind(45)  ==  sqrt(2)/2  
% ans = 1

3. 0.7/0.1≠7 但0.2/0.1=2

0.7/0.1  ==  7  
% ans =0

  这是由于浮点数的表示机制决定的,计算机是二进制的,在表示某些小数时,并不是精确表示的。由于二进制的限制,计算机无法精确的表示0.7。

format long 
0.7/0.1  
% ans = 6.999999999999999

  事实上,计算机中,所有的数不是连续的,而是离散的,每个浮点数和下一个更大的浮点数之间都存在一个较小的间隔,这个间隔可以用eps函数来计算。
  例如,1234567890.0123456789最后一位的9是无效的,因为即使是双精度double也无法精确到最后一位。

1234567890.0123456789  ==  1234567890.012345678  
% ans = 1  
% 左边的数比右边的数多了最后一位的9,但仍然是相等的。

那么,如果遇到类似的问题时,如何输出正确的判断结果呢?一种可行的方法是全部转换为整数

(0.7*10)/(0.1*10)  ==  7  
% ans = 1

或者,求两个数的差值,看差值的绝对值是否足够小。如果精度要求不高的话,可以自己设定一个较小的阈值

abs(0.7/0.1  -  7)  <=  1e-6  
% ans = 1

如果精度要求高的话,可借助于eps函数

abs(0.7/0.1  -  7)  <=  eps(7)  
% ans = 1

4. 矩阵转置.' 与' 的区别

  大多数教程、网上博客、甚至书籍里面都会提到,MATLAB的矩阵转置运算符'。事实上,这是一个很大的误区。A'是矩阵A的共轭转置,只不过大多数情况下,A虚部为0,无法体现出共轭。

A =  [1,  2;  3,  4]; 
A'  
% ans = [1, 3; 2, 4]

但是,如果A是复数矩阵,就会出现共轭。

A =  [1+2i,  3+4i;  5+6i,  7+8i];
A'  
% ans = [1-2i,5-6i; 3-4i,7-8i];

正确的转置运算符是.'

A =  [1+2i,  3+4i;  5+6i,  7+8i]; 
A.'  
% ans = [1+2i,5+6i; 3+4i,7+8i];

5. 每次生成相同的随机数

  在知乎上面有很多类似的问题,如何保证每次生成的随机数相同。我看到很多回答都是说,将生成的随机数save成本地mat文件,然后在下次要用的时候,用load函数读取mat文件中的值。这种方法也不是不可以,但是效率比较低,文件读写可能会花费大量的时间。
  事实上,MATLAB生成的随机数都是伪随机数,也就是说,按照某种特定的算法生成的。这意味着,随机数的生成过程是可以复现的,可以通过控制随机数生成器的"种子",确保每次生成相同的随机数。
  rng函数用于控制随机数的生成。

% 保存当前随机数生成器的状态("种子")
scurr =  rng();  
% 生成随机数 
A =  randn([1,4])  
% A = [-0.4611 0.1132 1.3442 -0.4842];  

% 每次想要生成相同的随机数时  
% 先用rng函数将生成器的状态置为上次保存的
scurr  rng(scurr); 
A =  randn([1,4])  
% A = [-0.4611 0.1132 1.3442 -0.4842];

6. 如何生成类似于A1,A2,....,A100的变量

  在知乎上也有很多类似的问题,纷纷表示用手一个一个的敲进去太累了。大部分的回答是,用evalfeval之类的函数。但是eval函数的效率很低,而且代码的可读性会很差。
  事实是,绝大部分情况下,并不需要生成A1,A2,...,A100这样的变量。真正需要做的是,改变自己的编程思路,给数组A增加一个额外的维度,用这个维度来表示A1到A100。

% 例如A1到A100,  
% An每个变量的值为[n,n+1,...,n+99]  
% 用eval函数可以完成上述赋值  
for ii =  1:100  
    eval(['A'  num2str(ii)  '='  num2str(ii)  ':'  num2str(ii+99)]);  
end  

% 事实上,只需要给A多一个维度就可以了  
% 这样当你想要使用A1变量时,只需要调用A(1,:)就可以了 
for ii =  1:100  
    A(ii,:)  = ii:ii+99;  
end

如果A1,A2,...A100每个变量的长度不一样,不能放到一个矩阵里面怎么办?改用cell数组

% 例如A1到A100,  
% An每个变量的值为[n,n+1,...,100]  
% 用eval函数可以生成完成上述赋值  
for ii =  1:100  
    eval(['A'  num2str(ii)  '='  num2str(ii)  ':'  num2str(100)]);  
end

% 使用cell元胞数组完成类似的赋值  
% 当你想使用A1变量时,只需要调用A{1}  
for ii =  1:100 
    A{ii}  = ii:100;  
end

7. i 和j都是MATLAB内置函数(built-in function)

  在上面的例子,我在for循环里面的循环变量用的ii,而不是常用的i,这是为什么呢?因为在MATLAB中,i是一个内置函数,代表的是虚数单位(j也是),用于输入复数。

% 确保当前工作区没有i,j变量 
clear i  j  
i  ==  j  
% ans = 1

  当然可以将ij覆盖为变量,但是覆盖内置函数不是一个好的编程习惯,同时也会带来运行速度上的降低。而且,一旦程序中涉及输入复数,就可能会出现错误。

% 下面的代码中,想实现的是复数1+2i  
% 但实际上,A = [3,5,7,9,11] 
a =  1; 
b =  2;  
for  i  =  1:5  
    A(i)  = a + b*i;  
end

那么,为了避免这个问题,一种良好的编程习惯就是,将循环变量i,j改成ii,jj。在写虚数单位时,用1i,1j代替i,j.

for ii =  1:5 
    A(ii)  = a + b*1i;  
end

8. dbstop if error真的很好用

  当我作为一个MATLAB新手首次接触这个命令时,感觉仿佛迎来了春天,给我带来的震撼不亚于第一次接触bsxfun
  在调试MATLAB程序时,如果有错误,我们经常需要在出错的那一行加上断点,然后重新运行脚本或函数。这样程序会运行到断点前,我们就可以观察出错前各个变量的值,进而找到bug。这一过程需要经历运行,出错,加断点,再运行这一过程。
  如果在MATLAB命令行输入dbstop if error,然后再运行程序,这样程序会自动停在出错的那一行,这时可以直接观察各个变量的值,省去了自己加断点的过程。而且,不需要每次运行程序前都输入dbstop if error,只需要输入一次就OK。
如果想要在每次warning前暂停程序,也可以在命令行输入dbstop if warning

9. 调试程序时在不同的函数之间传递值

  我们知道,如果想在想让一个值在各个函数都使用,可以将其设为全局变量(global),此时声明了该全局变量的函数,可以互相传递这个值。
  那么,如果是在调试时,程序已经运行了一半,停在了某个断点前,想将函数内当前的某个值传递到上一个函数,或者直接传递到基础工作区,用于后续对变量值的仔细分析,怎么办?(基础工作区就是,MATLAB的主函数空间,当程序运行完后的工作区。)
  解决方法是使用assignin函数。
  assignin函数能够在基础工作区(base),或者调用当前函数的函数工作区(caller)内,产生一个新的变量,该变量的值等于当前函数空间内的某个值。(比较绕口,仔细看下面例子吧)
  例如,下面的函数myfun, 函数本身是用于画图的,不会返回任何值。

function  myfun() 
a =  randn([1,1e4]);  
plot(a);  
end

第3行前有一个断点,程序停在断点前。这时,我们可以看到a的值,但我们希望将a的值导入到基础工作区,进行后续的分析。(一旦我们退出调试,或者继续运行程序,都无法得到a的值。当然,可以修改myfun,给它添加一个返回值a,然后重新运行程序。但是,如果是一个复杂的程序,花了很多时间程序才运行到这个位置,重新开始意味着前功尽弃。)
  这时,我们可以在调试时,在MATLAB命令行中输入下列命令:

% 在基础工作区生成一个变量a_debug  
% a_debug的值与a的值相等  
assignin('base','a_debug',a)

这样,在退出当前调试后,能够在基础工作区看到一个a_debug变量,它的值等于myfun函数中a的值。

10.退出MATLAB时自动保存当前工作空间的变量

  如果当前路径中存在finish.m文件,matlab在退出前,会自动运行finish.m文件。通过编写finish文件,即可实现对当前工作空间变量的保存。

save 'C:\Users\matlab.mat'  

新建脚本文件,将上述命令复制过去,保存为finish.m文件。这样当你退出MATLAB时,就会自动将当前工作区内的变量全部保存到C:\Users\matlab.mat文件中。

注意:

  1. 如果想要当前文件夹位于任何文件夹时均能触发finish.m脚本的运行,将其保存在MATLABPATH文件夹中,具体请使用path命令查看。
  2. 退出matlab时,右上角的"×",命令行输入quit, exit均能自动触发finish.m脚本的运行。
  3. 命令行输入quit force时,强制退出matlab,不会执行finish.m脚本。

11. 启动MATLAB时自动读取上次保存的工作空间变量

  在MATLAB默认的工作文件夹,编写startup.m脚本,MATLAB在启动时会自动运行该脚本。

load 'C:\Users\matlab.mat'  

startup.m文件必须位于MATLAB启动后的默认文件夹内
注意:

  • MATLAB的默认文件夹可以更改

12. 使用pcode对MATLAB代码进行模糊处理

  有时,我们希望自己的代码别人能够使用,但不能看到源代码,这是可以通过pcode命令简单实现这一操作。
  MATLAB在运行.m程序文件时,事实上是先将.m文件(文本文件)转换成.p文件(二进制文件)。p文件一般是隐藏文件,在文件夹中是无法看到的。我们可以通过pcode命令将m文件转换成p文件。将p文件转给第三方使用,但对方无法轻易看到源代码。

% 下列命令会在当前文件夹内生成myfun.p  
% myfun.p与myfun.m具有相同的功能 
pcode myfun

注意:

  1. p文件的优先级高于m文件,如果函数名相同,matlab会优先使用p文件
  2. p文件不够安全,不能将其视为加密,只是一个简单的模糊处理
  3. p文件是可以被逆向的,至少MathWorks就可以。
  4. 网上流传过p文件逆向的小工具,这些工具要么被删,要么已不适用现在的版本。
  5. 上古版本的MATLAB可以对p文件进行断点调试,某种程度上可以推测出源码。
  6. MATLAB软件本身也使用了很多p文件,说明MathWorks认为p文件没有那么容易被破解。
  7. 如果代码涉及知识产权或商业机密,p文件是不安全的,妥善的方法是将代码部署在远程服务器上。

结束语

你都看到这里了,还不点赞收藏?

  • 本文由本人首发于知乎:易夕-MATLAB Tricks 专栏,现由本人转发于-易夕奂,未得到本人许可,禁止转载。

你可能感兴趣的:(MATLAB:你不知道的12个基础知识)