实现矩阵的加、减、乘、除和逆运算
最编程
2024-05-22 15:46:24
...
1 #include<stdio.h>
2 #include<stdlib.h>
3 #define col 3
4 #define row 3
5 class matrix//类的定义
6 {
7 private:
8 double m[col][row];//矩阵设置为私有的,
9 public:
10 matrix(){}//无参数的构造函数
11 matrix(double a[col][row]);//有参数的构造函数
12 matrix Add(matrix &b);//加法运算声明
13 matrix Sub(matrix &b);//减法运算声明
14 matrix Mul(matrix &b);//乘法运算声明
15 matrix Div(matrix &b);//除法运算声明
16 matrix Inverse();//求逆运算声明
17 ~matrix();//析构函数声明
18 void display();//显示函数声明
19 };
20 matrix::matrix(double a[col][row])//构造函数的定义
21 {
22 int i,j;
23 for(i=0;i<col;i++)
24 for(j=0;j<row;j++)
25 m[i][j]=a[i][j];
26 }
27 matrix matrix::Add(matrix &b)//加法运算
28 {
29 int i,j;
30 matrix*c=(matrix*)malloc(sizeof(matrix));
31 for(i=0;i<col;i++)
32 for(j=0;j<row;j++)
33 c->m[i][j]=m[i][j]+b.m[i][j];
34 return(*c);
35 }
36 matrix matrix::Sub(matrix &b)//减法运算
37 {
38 int i,j;
39 matrix*c=(matrix*)malloc(sizeof(matrix));
40 for(i=0;i<col;i++)
41 for(j=0;j<row;j++)
42 c->m[i][j]=m[i][j]-b.m[i][j];
43 return *c;
44 }
45 matrix matrix::Mul(matrix &b)//乘法运算
46 {
47 int i,j,k;
48 double sum=0;
49 matrix*c=(matrix*)malloc(sizeof(matrix));
50 for(i=0;i<col;i++)
51 {
52 for(j=0;j<row;j++)
53 {
54 for(k=0;k<row;k++)
55 sum+=m[i][k]*(b.m[k][j]);
56 c->m[i][j]=sum;
57 sum=0;
58 }
59 }
60 return(*c);
61 }
62 matrix matrix::Div(matrix &b)//除法运算
63 {
64 //除法直接求解,参见主函数
65 matrix c;
66 return(c);
67 }
68 matrix matrix::Inverse()//求逆运算
69 { //参考博客:http://www.cnblogs.com/rollenholt/articles/2050662.html
70 int i,j,k,M=col,N=2*col;
71 double b[col][col*2];
72 matrix*c=(matrix*)malloc(sizeof(matrix));
73 for(i=0;i<M;i++) //赋值
74 for(j=0;j<M;j++)
75 b[i][j]=m[i][j];
76 for(i=0;i<M;i++) //扩展
77 for(j=M;j<N;j++)
78 {
79 if(i==(j-M))
80 b[i][j]=1;
81 else
82 b[i][j]=0;
83 }
84 /***************下面进行求逆运算*********/
85 for(i=0;i<M;i++)
86 {
87 if(b[i][i]==0)
88 {
89 for(k=i;k<M;k++)
90 {
91 if(b[k][i]!=0) //作者的博客里面此处为b[k][k],貌似是不正确的,
92 //因为这对比如说是{0,0,1,1,0,1,0,1,1}的矩阵就会判断为不可逆,
93 { //而实际上该矩阵是可逆的,这里应该是作者笔误,待进一步求证
94 for(int j=0;j<N;j++)
95 {
96 double temp;
97 temp=b[i][j];
98 b[i][j]=b[k][j];
99 b[k][j]=temp;
100 }
101 break;
102 }
103 }
104 if(k==M)
105 {
106 printf("该矩阵不可逆!\n");
107 exit(0);
108 }
109 }
110 for(j=N-1;j>=i;j--)
111 b[i][j]/=b[i][i];
112
113 for(k=0;k<M;k++)
114 {
115 if(k!=i)
116 {
117 double temp=b[k][i];
118 for(j=0;j<N;j++)
119 b[k][j]-=temp*b[i][j];
120 }
121 }
122 }
123 /**********************导出结果******************/
124 for(i=0;i<M;i++)
125 for(j=3;j<N;j++)
126 c->m[i][j-3]=b[i][j];
127 return (*c);
128 }
129
130 matrix::~matrix()
131 {}
132 void matrix::display()
133 {
134 int i,j;
135 for(i=0;i<col;i++)
136 {
137 for(j=0;j<row;j++)
138 printf("%f ",m[i][j]);
139 printf("\n");
140 }
141 }
142 void main()
143 {
144 double a[3][3]={{1,0,1},{0,1,1},{0,3,1}};
145 double b[3][3]={{0,0,1},{1,0,1},{0,1,0}};
146 matrix ma(a),mb(b),mc;
147 int flag;
148 printf("----------------------------------------------------\n请选择要进行的操作:\n1、打印\t2、加法");
149 printf("\t3、减法\n4、乘法\t5、除法\t6、求逆\n7、退出\n");
150 printf("-----------------------------------------------------\n");
151 scanf("%d",&flag);
152 while((flag==1)||(flag==2)||(flag==3)||(flag==4)||(flag==5)||(flag==6)||(flag==7))
153 {
154 if(flag==1)
155 {
156 printf("矩阵a为:\n");
157 ma.display();
158 printf("矩阵b为:\n");
159 mb.display();
160 }
161 if(flag==2)//矩阵加法运算
162 {
163 printf("矩阵加法运算结果:\n");
164 mc=ma.Add(mb);
165 mc.display();
166 }
167 else if(flag==3)//矩阵减法运算
168 {
169 printf("矩阵减法运算结果:\n");
170 mc=ma.Sub(mb);
171 mc.display();
172 }
173 else if(flag==4)//矩阵乘法运算
174 {
175 printf("矩阵乘法运算结果:\n");
176 mc=ma.Mul(mb);
177 mc.display();
178 }
179 else if(flag==5)//矩阵除法运算
180 {
181 printf("矩阵除法运算结果:\n");
182 printf("矩阵的除法分成两类:\n 1、A\\B=inverse(A)*B \n 2、B/A=B*inverse(A)\n");
183 printf("采用第1类,则a\\b的结果为:\n");
184 mc=ma.Inverse();
185 mc=mc.Mul(mb);
186 mc.display();
187 printf("采用第2类,则a/b的结果为:\n");
188 mc=mb.Inverse();
189 mc=ma.Mul(mc);
190 mc.display();
191 }
192 else if (flag==6)//矩阵求逆运算
193 {
194 printf("矩阵a求逆运算结果为:\n");
195 mc=ma.Inverse();
196 mc.display();
197
198 printf("矩阵b求逆运算结果为:\n");
199 mc=mb.Inverse();
200 mc.display();
201 }
202 else {exit(0);}
203 printf("----------------------------------------------------\n请选择要进行的操作:\n1、打印\t2、加法");
204 printf("\t3、减法\n4、乘法\t5、除法\t6、求逆\n7、退出\n");
205 printf("-----------------------------------------------------\n");
206 scanf("%d",&flag);
207 }
208 }
推荐阅读
-
用 Java 实现两个输入数的加、减、乘、除运算
-
JavaFX15.4 ( 创建一个简单的计算器 ) 编写一个执行加、减、乘、除运算的程序。
-
opencv 中的常见矩阵运算(加、减、乘、逆、平均值、标准偏差)
-
编写了一个简单的矩阵类,可用于加、减、乘、求行列式等。
-
实现矩阵的加、减、乘、除和逆运算
-
OpenCV 矩阵加、减、乘、除、移位等摘要
-
复数的加、减、乘、除和建模,找出共轭复数
-
复数的加、减、乘、除和建模,找出共轭复数
-
数的机器码表示:原码、反码、补码、变形补码、移码和浮点数编码-数学定义:例:+111的原码为0111,-101的原码为1101 (2) 纯小数的原码表示 纯小数的原码首位同样为符号位,后面的数值则表示小数的尾数,纯小数的整数位为默认为0无需表示。 例:+0.111的原码为0111,-0.101的原码为1101 可以看到,+111和+0.111的原码同为0111,这是因为约定的小数点位置不同,整数的原码的小数点约定在末尾,纯小数的原码的小数点约定在数值的最前面,这样通过约定小数点的位置来表示数的方法就称为定点数表示法,约定小数点位置实际上就是约定编码中每一位的权重。 二、反码 正数的反码与其原码相同。 负数的反码是其对应原码的符号位不变,数值位按位取反。 数学定义:例: 真值 +111 -101 +0.111 -0.101 原码 0111 1101 0111 1101 反码 0111 1010 0111 1010 三、补码 原码虽然转换很简单,但是在做减法时操作很复杂(减不够还要借位),因此计算机在做加负数操作时会先将负数的原码转换为补码再做加法。 先举个栗子,假设时钟现在是9点钟,我把时针往回拨3个小时是6点钟,或者顺时针往后拨9个小时还是6点钟,也就是说9-3的结果等同于9+9(mod 12),对于模数12,-3的补码为+9,这就引申出了一种将减法转换为加法的思想,把减去一个正数视为加上一个负数(例如9+(-3)),再将负数转换为对应的补码,最后就可以和补码做加法了,若结果超出了模数则丢弃一个模数即可。 如图所示:9减去灰色的部分(-3)就等同于加上蓝色的部分,即-3的补码即为蓝色部分的长度9(mod 12)。即补码=模数+真值(超出模数则舍弃一个模数) (1) 整数的补码表示 对于一个n位的二进制真值x,则取模数为2^(n+1),若x为正数则补码和原码相同(加上一个模数又需舍弃一个模数 故相同),若为负数则补码为模数加上x。相对于原码,补码这里的首位就不仅代表原数真值的符号了,也是补码自己的一个数值位。 取模数为2^(n+1)是因为在需要舍弃模数时只需要舍弃运算结果(二进制数)的最高位即可,这在计算机中很容易实现 数学定义:例:三位二进制数的模数2^4就是10000,故+111的补码为0111(即10000 + 111 = 0111 (舍弃模数位)),-101的补码为1011(即10000 - 101 = 1011) 补码运算示例:那么+111 - 101 = +111 + (-101) = 0111 + 1011 = 10010,运算结果只保留后四位(即舍弃模数位),故计算结果为0010。这样就通过加法实现了减法运算。 补码可表示数据范围:由数学定义可知,n位二进制补码可表示的数据范围为 -2n-1~2n-1-1。以8位的byte类型数为例,可表示的数据范围为 -27~27-1,即-128至+127,最小负数-128(补码:1000 0000),最大负数-1(补码:1111 1111),0(补码:0000 0000),最小正数1(补码:0000 0001),最大正数127(补码:0111 1111)。 由补码求真值:正数的补码即为原码即为真值,负数的真值由计算规则可知 负数真值= - (模数 - 补码),以补码1111 1111为例,其真值 = - (1 0000 0000 - 1111 1111) = - 0000 0001 = -1 (2) 纯小数的补码表示 对于一个纯小数x,则取模数为2^1,正数的补码和原码相同,负数的补码为模数2加上x。同样补码的首位不仅代表原数真值的符号,也是补码的数值位。 数学定义:例:纯小数的模数2就是10,故+0.111的补码为0111,-0.101的补码为1011(小数点约定在符号位后) 计算机中求补码的规则 可以注意到求负数的补码时还是要做减法,这在计算机中就很不方便了,但是通过其数学定义可以看到无论是整数还是纯小数,负数的补码都等于反码的末尾加1,而这又等同于原码数值位从右向左遇到第一个1后,这个1左边的数值位都按位取反,故实际计算机中求补码的规则如下:正数的补码等于原码负数的补码等于原码的数值位从右向左的第一个1左边的所有数值位按位取反(例:byte类型值-6的原码为1000 0110,则其补码为1111 1010) 四、变形补码 两个补码在运算时可能会溢出从而产生错误的结果,比如0111+0101 = 1100,两个正数相加反而得到了一个负数,那么在计算机中要如何判断运算结果是否溢出了呢,这就引申出了变形补码。从直观上看,相对于补码来说变形补码就是用两位来表示符号位,00表示正数,11表示负数。运算结果符号位为01表示正溢出,10表示负溢出。
-
【Netty】「萌新入门」(七)ByteBuf 的性能优化-堆内存的分配和释放都是由 Java 虚拟机自动管理的,这意味着它们可以快速地被分配和释放,但是也会产生一些开销。 直接内存需要手动分配和释放,因为它由操作系统管理,这使得分配和释放的速度更快,但是也需要更多的系统资源。 另外,直接内存可以映射到本地文件中,这对于需要频繁读写文件的应用程序非常有用。 此外,直接内存还可以避免在使用 NIO 进行网络传输时发生数据拷贝的情况。在使用传统的 I/O 时,数据必须先从文件或网络中读取到堆内存中,然后再从堆内存中复制到直接缓冲区中,最后再通过 SocketChannel 发送到网络中。而使用直接缓冲区时,数据可以直接从文件或网络中读取到直接缓冲区中,并且可以直接从直接缓冲区中发送到网络中,避免了不必要的数据拷贝和内存分配。 通过 ByteBufAllocator.DEFAULT.directBuffer 方法来创建基于直接内存的 ByteBuf: ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); 通过 ByteBufAllocator.DEFAULT.heapBuffer 方法来创建基于堆内存的 ByteBuf: ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); 注意: 直接内存是一种特殊的内存分配方式,可以通过在堆外申请内存来避免 JVM 堆内存的限制,从而提高读写性能和降低 GC 压力。但是,直接内存的创建和销毁代价昂贵,因此需要慎重使用。 此外,由于直接内存不受 JVM 垃圾回收的管理,我们需要主动释放这部分内存,否则会造成内存泄漏。通常情况下,可以使用 ByteBuffer.clear 方法来释放直接内存中的数据,或者使用 ByteBuffer.cleaner 方法来手动释放直接内存空间。 测试代码: public static void testCreateByteBuf { ByteBuf buf = ByteBufAllocator.DEFAULT.buffer(16); System.out.println(buf.getClass); ByteBuf heapBuf = ByteBufAllocator.DEFAULT.heapBuffer(16); System.out.println(heapBuf.getClass); ByteBuf directBuf = ByteBufAllocator.DEFAULT.directBuffer(16); System.out.println(directBuf.getClass); } 运行结果: class io.netty.buffer.PooledUnsafeDirectByteBuf class io.netty.buffer.PooledUnsafeHeapByteBuf class io.netty.buffer.PooledUnsafeDirectByteBuf 池化技术 在 Netty 中,池化技术指的是通过对象池来重用已经创建的对象,从而避免了频繁地创建和销毁对象,这种技术可以提高系统的性能和可伸缩性。 通过设置 VM options,来决定池化功能是否开启: -Dio.netty.allocator.type={unpooled|pooled} 在 Netty 4.1 版本以后,非 Android 平台默认启用池化实现,Android 平台启用非池化实现; 这里我们使用非池化功能进行测试,依旧使用的是上面的测试代码 testCreateByteBuf,运行结果如下所示: class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeHeapByteBuf class io.netty.buffer.UnpooledByteBufAllocator$InstrumentedUnpooledUnsafeDirectByteBuf 可以看到,ByteBuf 类由 PooledUnsafeDirectByteBuf 变成了 UnpooledUnsafeDirectByteBuf; 在没有池化的情况下,每次使用都需要创建新的 ByteBuf 实例,这个操作会涉及到内存的分配和初始化,如果是直接内存则代价更为昂贵,而且频繁的内存分配也可能导致内存碎片问题,增加 GC 压力。 使用池化技术可以避免频繁内存分配带来的开销,并且重用池中的 ByteBuf 实例,减少了内存占用和内存碎片问题。另外,池化技术还可以采用类似 jemalloc 的内存分配算法,进一步提升分配效率。 在高并发环境下,池化技术的优点更加明显,因为内存的分配和释放都是比较耗时的操作,频繁的内存分配和释放会导致系统性能下降,甚至可能出现内存溢出的风险。使用池化技术可以将内存分配和释放的操作集中到预先分配的池中,从而有效地降低系统的内存开销和风险。 内存释放 当在 Netty 中使用 ByteBuf 来处理数据时,需要特别注意内存回收问题。 Netty 提供了不同类型的 ByteBuf 实现,包括堆内存(JVM 内存)实现 UnpooledHeapByteBuf 和堆外内存(直接内存)实现 UnpooledDirectByteBuf,以及池化技术实现的 PooledByteBuf 及其子类。 UnpooledHeapByteBuf:通过 Java 的垃圾回收机制来自动回收内存; UnpooledDirectByteBuf:由于 JVM 的垃圾回收机制无法管理这些内存,因此需要手动调用 release 方法来释放内存; PooledByteBuf:使用了池化机制,需要更复杂的规则来回收内存; 由于池化技术的特殊性质,释放 PooledByteBuf 对象所使用的内存并不是立即被回收的,而是被放入一个内存池中,待下次分配内存时再次使用。因此,释放 PooledByteBuf 对象的内存可能会延迟到后续的某个时间点。为了避免内存泄漏和占用过多内存,我们需要根据实际情况来设置池化技术的相关参数,以便及时回收内存; Netty 采用了引用计数法来控制 ByteBuf 对象的内存回收,在博文 「源码解析」ByteBuf 的引用计数机制 中将会通过解读源码的形式对 ByteBuf 的引用计数法进行深入理解; 每个 ByteBuf 对象被创建时,都会初始化为1,表示该对象的初始计数为1。 在使用 ByteBuf 对象过程中,如果当前 handler 已经使用完该对象,需要通过调用 release 方法将计数减1,当计数为0时,底层内存会被回收,该对象也就被销毁了。此时即使 ByteBuf 对象还在,其各个方法均无法正常使用。 但是,如果当前 handler 还需要继续使用该对象,可以通过调用 retain 方法将计数加1,这样即使其他 handler 已经调用了 release 方法,该对象的内存仍然不会被回收。这种机制可以有效地避免了内存泄漏和意外访问已经释放的内存的情况。 一般来说,应该尽可能地保证 retain 和 release 方法成对出现,以确保计数正确。