Array
Array is a common data structure in development. Of course, we know that array is equivalent to a container, but it can not only store values and characters, but it can also store the entry address of functions and structures. data.
typedef struct _value { int val_1; int val_2; }VALUE; typedef struct _table { char index; char data[100]; int (*UniSetFunc)(VALUE*); }TABLE; int add(VALUE *val ) { int temp = 0; temp = val->val_1 + val->val_2; return temp; } TABLE page[10] { {“ First section ”, “abcdefghijklmn”, add}, {“Second section”, “opqrstuvwxyz”, NULL} }; int main() { VALUE AddValue; AddValue.val_1 = 2; AddValue.val_2 = 4; int result = 0; result = page[0]-> UniSetFunc(&AddValue); printf(“The Result of add is %d\n”, result); return 0; }
At this time, the array is converted into a structure similar to a dictionary in the Python language, which facilitates subsequent development and utilization, as well as additional upgrades and maintenance.
Code analysis: First of all, we know that the name of the function can be used as the entry address of the function (similar to the name of an array representing the address of the array), so in the TABLE structure we define a member function int (*UniSetFunc) (VALUE *); Here UniSetFunc serves as the entry parameter of the function, and VALUE* represents the formal parameter type of the function; the TABLE type array page[10] contains the data of the structure and the entry address of the function, which can be called page[0]- > UniSetFunc(&AddValue) to indirectly call the add function and implement the summation operation of the two numbers AddValue.val_1 and AddValue.val_1 in AddValue.
Memory hit rate problem:
In order to improve the efficiency of code operation, the memory space should be fully utilized and the memory area should be accessed continuously.
We know that the array is stored in a whole area in memory and is stored continuously. For the defined array array[2][2], assume that the address of array[0][0] is 0x04030 , then the addresses of array[0][1], array[1][0], and array[1][1] are 0x04031, 0x04032, 0x04033 respectively; to improve the memory hit rate, the memory should be accessed as continuously as possible Space area, rather than jump access; next let's look at a matrix multiplication problem.
for (int i = 0; i < 2; ++i) { for (int j = 0; j < 2; ++j) { for (int k = 0; k < 3; ++k) { matrix[i][j] += matrix1[i][k] * matrix2[k][j]; } } }
The above code is a commonly used method to multiply matrix matrix1 and matrix2 and then assign it to matrix. That is, use matrix1 matrix to get the row vector multiplied by the column vector of matrix matrix2, and then assign it to matrix. In this way, due to the structure of matrix storage in memory , we can clearly know that the continuous access method is not used when accessing matrix2, so the memory hit rate is low. Next we look at a method with high memory hit rate.
for (int i = 0; i < 2; ++i) { for (int k = 0; k < 3; ++k) { for (int j = 0; j < 2; ++j) { matrix[i][j] += matrix1[i][k] * matrix2[k][j]; } } }
It can be seen that the code only exchanges the positions of the second for loop and the third for loop, while the other parts have not changed. However, the memory hit rate has been greatly improved. We use the matrix1 and matrix2 matrices The internal elements are multiplied sequentially and then accumulated to perform matrix multiplication. In this way, no memory miss occurs when accessing the matrix1 and matrix2 matrices, thereby increasing the probability of a memory hit.
The relationship between volatile, const and static:
The const keyword is a constant keyword, the amount it acts on is a constant, and the program is not allowed to change the value of the constant , such as const int value = 12; this constant value does not allow the program to change it. The const keyword is often used during the development process. In order to prevent the program from accidentally changing a fixed constant, we should add it in time. Use the const keyword; in addition, when the const keyword acts on a constant, it must be initialized directly, because it is not allowed to be changed during the entire program, so it must be initialized immediately. For example: const int value = 12 is correct, The syntax of const int value; value = 12; is wrong! Next, let's study a slightly more difficult issue, namely constant pointers and pointer constants. Let’s look at a piece of code first:
#define SWITCH 1 int main() { int val_1 = 5; int val_2 = 10; const int *p1 = &val_1; int const *p2 = &val_1; int *const p3 = &val_1; #ifdef SWITCH // This is a switch *p1 = 20; *p2 = 21; *p3 = 22; #endif #ifndef SWITCH p1 = &val_2; p2 = &val_2; p3 = &val_2; #endif printf("%d\n", *p1); printf("%d\n", *p2); printf("%d\n", *p3); return 0; }
Executed under the cygwin compiler, we can see this error:
From the picture we can clearly see that pointers p1 and p2 You can only read the value in val_1 as a pointer constant, that is, you cannot change the content of the variable it points to, so *p1 = 20; *p2 = 21; these two commands are wrong! (#ifdef SWITCH ... #endif is conditional compilation, which is a macro switch). Then we comment out the #define SWITCH 1 statement, and the second block of code will be run at this time, and the result is as follows:
From the error you can It can be seen that p3 is a constant pointer. It can only point to a fixed address and cannot change the direction it points to. Therefore, the operation of p3 = &val_2; is wrong, so the correct code is as follows:
int main() { int val_1 = 5; int val_2 = 10; const int *p1 = &val_1; int const *p2 = &val_1; int *const p3 = &val_1; printf("Frist\n"); printf("%d\n", *p1); printf("%d\n", *p2); printf("%d\n", *p3); p1 = &val_2; p2 = &val_2; *p3 = 22; printf("Second\n"); printf("%d\n", *p1); printf("%d\n", *p2); printf("%d\n", *p3); return 0; }
The result is:
Final end: constant pointer (const int *p or int const *p) means that the pointer p cannot change the value stored in the address it points to, but can change the value it points to. Address; pointer constant (int *const p) means that pointer p cannot change the address it points to, that is, the pointer cannot change the location it points to, but it can change the content in the location it points to. If you want the pointer to neither change the position it points to nor the content there, then you can define it like this:
const int * const p = &a;或int const *const p = &a; 在定义函数的时候,若该入口参数在程序执行的过程中不希望被改变,则一定要将该形参用const来修饰,一来这样可以防止该段程序将其改变,二来对于形参而言,一个无论是否是const修饰的实参都可以将其传入const形的形参,而一个const形的实参是无法传入非const形的形参中,所以为了使编译不出错在定义函数的时候,一定要将不希望被改变的量用const关键字来修饰。
Static关键字为静态关键字,它的作用是将作用的变量存入内存中,而非存入寄存器中(即将变量存入堆中而非栈中),并且该作用的变量仅保存最近一次获取的值。接下来我们来看一段代码。
void countfun () { static int count = 0; ++count; printf(“This is %d number, enter into this function !\n”, count ); } int main() { for (int i = 0; i < 5; ++i) { countfun(); } return 0; }
这段代码的运行结果如下:
而若将除去static关键字,则运行的结果如下:
由此我们可以清楚的看出,static作用的变量count只会存入当前的结果,因此循环调用countfun( )函数的时候并没有从新将count变量置为0,而是保存了前一次的值。
Static关键字在项目中的应用是很广泛的,它不仅仅有上述所介绍的特点,同时若想要定义的全局变量在整个工程中仅在当前.C文件中有效时,也应该将这个全局变量用static来修饰,这样在其他的文件中是无法访问这个变量,从而降低了模块间的耦合度,提高了模块的内聚性,防止其他文件将其改变,从而更加的安全。
volatile关键字在嵌入式领域中是十分重要的一个关键字,尤其是在与硬件相关或多线程的编程中更为重要。volatile关键字修饰的变量说明它是可以随时发生改变的,我们不希望编译器去优化某些代码的时候,需要将这个变量用volatile关键字来修饰,从而程序每次访问该变量的时候是直接从内存中提取出来,而不是从临时的寄存器中将该变量的副本给提取出来利用!例如当我们想要实现某个中断处理时,其用来做判断条件的标记位则应该用volatile来修饰,这样当这个中断在别的地方被触发的时候就可以被实时的检测到,不至于由于优化而忽略中断。接下来我们看一段代码:
int main() { volatile int i = 10; int a = i; printf(“i = %d\n”, a); __asm { mov dword ptr[ebp-4], 0x10 } int b = i; printf(“i = %d\n”, b); return 0; }
此程序输出结果为i = 10;i = 16; 若将volatile关键字去掉,则结果为i = 10;i = 10;
即不加关键字会将汇编代码忽略掉,所以为了防止代码优化以及可以及时检测到外部程序对该变量的改变,我们必须将该变量加上volatile关键字。我们知道volatile关键字表征该量是易变的,const关键字代表该量是常量不能改变,那么volatile与const是否可以一起修饰同一个量呢,是肯定的,例如在硬件编程中ROM所存储的数据是不允许用户改变的,即指向该数据的指针必须为常量指针(const int *p = &ram_data),然而开发商却可以将其意外的改变,为了防止ROM的内容被意外的改变时,而用户程序没有及时的发现,必须将该量用volatile修饰,所以应这样定义该指针(volatile const int *p = &rom_data)。
位运算
在数字解码与编码的过程中,位运算的操作是司空见惯的事,同时位运算在提高程序的性能方面也独占鳌头,因此位运算操作是必需要深入了解的问题。
在乘法以及除法的操作中我可以使用未运行来提高代码的质量,例如:a = a * 16;这种操作完全可以替换为:a = a << 4;我们知道左移一位相当于将原数乘以2,左移N位则相当于乘以2^N,前提是在没有发生溢出的情况下;故上例即相当于将数a左移4位,对于某些乘以非2的整数幂情况,如 a = a * 9;则可以改写为a = (a << 3) + a; 同理右移相当于除以2的整数幂,当然以上所有情况都是在没有发生数据溢出的情况下,因此位运算操作要格外的小心,否则极有可能发生出错的情况。
在数据类型转换的过程中也需要做位运算操作,例如我们想将一个unsigned short类型的数据存入unsigned char类型的数组中,就需要进行位运算,首先分析知道unsigned short占用16个字节,unsigned char占用8个字节,想要将大字节的数据存入小字节,必须要对大字节进行分割,即将高8位与低8为分离开来分别存放,来看实现代码:
unsigned char * DrawCompo_Pj_BT_Change(unsigned short *subarray) { unsigned char temp[500]; (void)_sys_memset(&temp, 0x00, sizeof(temp) ); unsigned short i = 0; while (subarray[i] != 0x0000) { if( (subarray[i] & 0xff00) == 0x0000) { temp[i++] = (unsigned char)(subarray[i] & 0x00ff); } else { temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8); temp[i++] = (unsigned char)(subarray[i] & 0x00ff); } } temp[i] = '\0'; return temp; }
temp[i] = (unsigned char)( (subarray[i] & 0xff00) >> 8);即取subarray[i]数据的高8位,temp[i++] = (unsigned char)(subarray[i] & 0x00ff);取低8位。这样就可以实现将高字节的数据完整的存入到低字节中。
位运算还可以用来判断变量的符号,我们知道对于一个有符号的变量,其最高位为其符号位,故检查改变的最高位即可知道该变量为正还是为负。看一段代码:
int main() { short test_data = -12; if (test_data & 0xF000) { printf("This number is negative "); } else { printf("This number is positive "); } return 0; }
对于想要交换两个数的值的时候,通常我们的做法如下:
void swap(int &data1, int &data2) { int temp = 0; temp = data1; data1 = data2; data2 = temp; }
这样的代码比较简单易懂,然而美中不足的是它会产生一个临时变量temp,接下来我们用位运算来重写这个程序;
void swap(int &data1, int &data2) { data1 = data1 ^ data2; data2 = data1 ^ data2; data1 = data1 ^ data2; }
从上面的代码我们可以看出少了一个临时变量,同时也加快了代码的运行效率。
尾递归:
递归调用给我们带来了很多方便,也简化了代码,使程序看起来更加的简洁和明了,但递归调用也通常伴随着一个潜在的危险:出栈,接下来我们来看一个常见的递归。
int factorial(int n) { if (n < 1) { return 1; } else { return factorial(n-1)*n; } }
通常在求一个数的阶乘的时候我们习惯于采用上述方法,然而分析来看当输入的n值较大时,factorial(n-1)*n所计算的值会不断的压入堆栈,生成很多的临时变量,等待下一个的值的确定才得以计算,然而在内存中堆栈的大小是固定的,当输入的n值很大时,极有可能产生堆栈溢出!因此,有一个好的方法解决这种问题即尾递归调用。接下来我们来看这种强大的算法。
int factorial(int n, int m) { if (n < 2) { return m; } else { factorial(n-1, n*m); } }
从代码中可以看出,通过引入一个新的参数m来存放每次递归所产生的值,这样就避免了每次递归都要进行压栈操作,也就不会产生堆栈溢出的现象,而且普通的递归每次递归只能等待下一次递归得到的结果后才能继续运算,而尾递归每次执行都会进行运算,一次循环执行完毕即可得到结果,其时间复杂度为O(n);