2, Class declaration and function
In my opinion, a class relative to a structure is just like a structure relative to an array. The operability range and depth of the realization of logical functions are higher. In C + +, the use of classes is a bit like the structure. Now we will introduce C + + classes with the structure.
Example 7-1: adding data to the end of the array
arr.h
#ifndef _ARR_ #define _ARR_ typedef struct arr{ int data[100]; int tail; }ARR; void init(ARR *arr); void addtail(ARR *arr, int data); void show(ARR *arr); #endif
arr.c
#include "arr.h" #include <stdio.h> void init(ARR *arr) { arr->tail = 0; } void addtail(ARR *arr, int data) { arr->data[arr->tail++] = data; } void show(ARR *arr) { int i = 0; for(;i<arr->tail; i++) printf("%d, ", arr->data[i]); printf("\n"); }
main.c
#include "arr.h" int main() { ARR arr; init(&arr); int n = 10; while(n--) addtail(&arr, n); show(&arr); int i = 0; for(;i<10; i++) addtail(&arr, i); show(&arr); }
result:
@ubuntu:/mnt/hgfs/ub2/ARR1$ ls arr.c arr.h main.c @ubuntu:/mnt/hgfs/ub2/ARR1$ gcc *.c @ubuntu:/mnt/hgfs/ub2/ARR1$ ./a.out 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, @ubuntu:/mnt/hgfs/ub2/ARR1$
Example 7-2: adding data to the end of the array (optimized version)
void addtail(ARR *arr, int data) in example 7-1; And void show(ARR *arr); It is not restricted and can be accessed directly from the outside. In this way, there is a risk that the data will be tampered with inadvertently. In other words, I hope these functions are bound together and can only be used after initialization, so as to improve the security of the code. Many high-level official codes are like this.
Friends who have played stm32 MCU and used the code generated by STM32CubeMX will certainly be familiar with this. On the other hand, we must face these problems in the development of embedded MCU
arr.h
#ifndef _ARR_ #define _ARR_ typedef struct arr{ int data[100]; int tail; void (*addtail)(struct arr *arr, int data); void (*show)(struct arr *arr); }ARR; void init(struct arr *arr); #endif
arr.c
#include "arr.h" #include <stdio.h> static void addtail(ARR *arr, int data) { arr->data[arr->tail++] = data; } static void show(ARR *arr) { int i = 0; for(;i<arr->tail; i++) printf("%d, ", arr->data[i]); printf("\n"); } void init(ARR *arr) { arr->tail = 0; arr->addtail = addtail; arr->show = show; }
main.c
#include "arr.h" int main() { ARR arr; init(&arr); int n = 10; while(n--) arr.addtail(&arr, n); arr.show(&arr); int i = 0; for(;i<10; i++) arr.addtail(&arr, i); arr.show(&arr); }
result:
@ubuntu:/mnt/hgfs/ub2/ARR2$ ls arr.c arr.h main.c @ubuntu:/mnt/hgfs/ub2/ARR2$ gcc *.c @ubuntu:/mnt/hgfs/ub2/ARR2$ ./a.out 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, @ubuntu:/mnt/hgfs/ub2/ARR2$
The function pointer is used in the structure of example 7-2 above, so that the pointed function becomes a subordinate of the structure (it has to be written as arr.addtail() and arr.show() when calling), which fully shows the idea of encapsulation and the concept of object-oriented. It also embodies the idea of callback (I believe friends who understand stm32 development should have a deeper understanding of callback functions here).
typedef struct arr{ int data[100]; int tail; //Two function pointers void (*addtail)(struct arr *arr, int data); void (*show)(struct arr *arr); }ARR;
1. Class declaration
Example 7-3: adding data to the end of the array (C + + version)
arr.h
#ifndef _ARR_ #define _ARR_ class ARR{ public: void addtail(int data); void show(void); private: int data[100]; int tail; }; #endif
arr.cpp
#include "arr.h" #include <stdio.h> void ARR::addtail(int data) { this->data[tail++] = data; //this keyword identifies the member in the class ARR (int data[100]; data), which is distinguished from the passed parameter name (void ARR::addtail(int data)) } void ARR::show(void) { int i = 0; for(;i<tail; i++) printf("%d, ", data[i]); printf("\n"); }
main.cpp
#include "arr.h" int main() { ARR arr; int n = 10; while(n--) arr.addtail(n); arr.show(); int i = 0; for(;i<10; i++) arr.addtail(i); arr.show(); }
result:
@ubuntu:/mnt/hgfs/ub2/ARR3$ ls arr.cpp arr.h main.cpp @ubuntu:/mnt/hgfs/ub2/ARR3$ g++ *.cpp @ubuntu:/mnt/hgfs/ub2/ARR3$ ./a.out 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, @ubuntu:/mnt/hgfs/ub2/ARR3$
2. Member function of class
- Constructor (a)
- Destructor ~ A()
- Ordinary member function
Example 8-1. Existence of implicit construction - > create several objects based on destructor judgment
First declare class A, and then debug by printing destructor information.
#include <stdio.h> class A{ public: ~A(){ printf("A~~~~~~~~~~~~~\n"); } }; int main() { A x ; A y = x; }
result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_1.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ @ubuntu:/mnt/hgfs/ub2/class$
It is not difficult to find from the results: the destructor is executed twice, so it is concluded that two objects are created.
It can be determined that the constructor can be executed by default. Then, does the constructor execute twice? You can write some constructor content to test.
#include <stdio.h> class A{ public: A(){ printf("A\n"); } ~A(){ printf("A~~~~~~~~~~~~~\n"); } }; int main() { A x ; A y = x; }
result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_2.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ @ubuntu:/mnt/hgfs/ub2/class$
From the results, we can find that there is only one constructor. Is there one less constructor? Not at all. It's not that one constructor is missing here, but that constructor is implicitly constructed, which is executed when A y = x. here, the copy constructor is used. The copy constructor executes the default creation object of the system, and ignores the content printf("A\n") of the custom parameterless constructor
The above is a parameterless constructor. But many times, functions need to pass parameters, so we need to use a parametric constructor.
Example 8-2. Parameterized constructor
When using a parameterless constructor, if you need to have a parameterless constructor by default, you need to write a parameterless constructor.
#include <stdio.h> class A{ public: A(){ printf("A\n"); } A(int data) { printf("A(int data)\n"); } ~A(){ printf("A~~~~~~~~~~~~~\n"); } }; int main() { A x = 10; A y; }
result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_3.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A(int data) A A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ @ubuntu:/mnt/hgfs/ub2/class$
Here A x = 10; And A x(10); It's all about spreading the meaning of participation.
If the default is not written, an error will be reported, as follows:
#include <stdio.h> class A{ public: A(int data) { printf("A(int data)\n"); } ~A(){ printf("A~~~~~~~~~~~~~\n"); } }; int main() { A x = 10; A y; }
Error result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_4.cpp class_construct.cpp: In function 'int main()': class_construct.cpp:17:5: error: no matching function for call to 'A::A()' A y; ^ class_construct.cpp:5:2: note: candidate: A::A(int) A(int data) ^ class_construct.cpp:5:2: note: candidate expects 1 argument, 0 provided class_construct.cpp:3:7: note: candidate: A::A(const A&) class A{ ^ class_construct.cpp:3:7: note: candidate expects 1 argument, 0 provided @ubuntu:/mnt/hgfs/ub2/class$
Example 8-3. Deep copy and shallow copy
After understanding the parametric constructor, let's discuss the copy constructor in example 8-1.
#include <stdio.h> class A{ public: A(){ printf("A\n"); } A(int data) { printf("A(int data)\n"); } ~A(){ printf("A~~~~~~~~~~~~~\n"); } }; int main() { A w(10); A x = 10; A y = x; A z; }
result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_5.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A(int data) A(int data) A A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ A~~~~~~~~~~~~~ @ubuntu:/mnt/hgfs/ub2/class$
According to the results, A y = x; Instead of executing the custom constructor content printf("A\n"), execute the system default blank constructor, and then copy the data of object x as y's own data.
As the saying goes: the custom created object x has data, and the created object z is also user-defined, but the object y is created by the system and then uses the data of the custom object x, which is equivalent to the slag data 10 in the example. After having the object x and then dealing with the object y. That's a problem, right. Because one day, object x finds that the slag has object y and then kills the slag (object x releases the data). If the slag is killed, it will certainly affect object y. This is the shallow copy.
And deep copy is the real sense of data split into two, twins. The elder brother has an object x and the younger brother has an object y. One day, the elder brother was drained by object x, but the younger brother was energetic and object y was not affected at all.
Here are the error prone shallow copies:
#include <stdio.h> class A{ public: A() { printf("A()\n"); p = new char[10]; } ~A() { printf("~A()\n"); delete [] p; } private: char *p; }; int main() { A x; A y = x; }
Error result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_6.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A() ~A() ~A() *** Error in `./a.out': double free or corruption (fasttop): 0x00000000006e7030 *** ======= Backtrace: ========= /lib/x86_64-linux-gnu/libc.so.6(+0x777f5)[0x7f8211ee97f5] /lib/x86_64-linux-gnu/libc.so.6(+0x8038a)[0x7f8211ef238a] /lib/x86_64-linux-gnu/libc.so.6(cfree+0x4c)[0x7f8211ef658c] ./a.out[0x4008d5] ./a.out[0x40083a] /lib/x86_64-linux-gnu/libc.so.6(__libc_start_main+0xf0)[0x7f8211e92840] ./a.out[0x400729] ======= Memory map: ======== 00400000-00401000 r-xp 00000000 00:2d 75348 /mnt/hgfs/ub2/level14/1day/class/a.out 00600000-00601000 r--p 00000000 00:2d 75348 /mnt/hgfs/ub2/level14/1day/class/a.out 00601000-00602000 rw-p 00001000 00:2d 75348 /mnt/hgfs/ub2/level14/1day/class/a.out 006d5000-00707000 rw-p 00000000 00:00 0 [heap] 7f820c000000-7f820c021000 rw-p 00000000 00:00 0 7f820c021000-7f8210000000 ---p 00000000 00:00 0 7f8211b69000-7f8211c71000 r-xp 00000000 08:01 669534 /lib/x86_64-linux-gnu/libm-2.23.so 7f8211c71000-7f8211e70000 ---p 00108000 08:01 669534 /lib/x86_64-linux-gnu/libm-2.23.so 7f8211e70000-7f8211e71000 r--p 00107000 08:01 669534 /lib/x86_64-linux-gnu/libm-2.23.so 7f8211e71000-7f8211e72000 rw-p 00108000 08:01 669534 /lib/x86_64-linux-gnu/libm-2.23.so 7f8211e72000-7f8212032000 r-xp 00000000 08:01 669539 /lib/x86_64-linux-gnu/libc-2.23.so 7f8212032000-7f8212232000 ---p 001c0000 08:01 669539 /lib/x86_64-linux-gnu/libc-2.23.so 7f8212232000-7f8212236000 r--p 001c0000 08:01 669539 /lib/x86_64-linux-gnu/libc-2.23.so 7f8212236000-7f8212238000 rw-p 001c4000 08:01 669539 /lib/x86_64-linux-gnu/libc-2.23.so 7f8212238000-7f821223c000 rw-p 00000000 00:00 0 7f821223c000-7f8212252000 r-xp 00000000 08:01 658907 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8212252000-7f8212451000 ---p 00016000 08:01 658907 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8212451000-7f8212452000 rw-p 00015000 08:01 658907 /lib/x86_64-linux-gnu/libgcc_s.so.1 7f8212452000-7f82125c4000 r-xp 00000000 08:01 1057509 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f82125c4000-7f82127c4000 ---p 00172000 08:01 1057509 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f82127c4000-7f82127ce000 r--p 00172000 08:01 1057509 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f82127ce000-7f82127d0000 rw-p 0017c000 08:01 1057509 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21 7f82127d0000-7f82127d4000 rw-p 00000000 00:00 0 7f82127d4000-7f82127fa000 r-xp 00000000 08:01 667754 /lib/x86_64-linux-gnu/ld-2.23.so 7f82129d9000-7f82129df000 rw-p 00000000 00:00 0 7f82129f8000-7f82129f9000 rw-p 00000000 00:00 0 7f82129f9000-7f82129fa000 r--p 00025000 08:01 667754 /lib/x86_64-linux-gnu/ld-2.23.so 7f82129fa000-7f82129fb000 rw-p 00026000 08:01 667754 /lib/x86_64-linux-gnu/ld-2.23.so 7f82129fb000-7f82129fc000 rw-p 00000000 00:00 0 7ffc6e46b000-7ffc6e48c000 rw-p 00000000 00:00 0 [stack] 7ffc6e573000-7ffc6e576000 r--p 00000000 00:00 0 [vvar] 7ffc6e576000-7ffc6e578000 r-xp 00000000 00:00 0 [vdso] ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall] Aborted (core dumped) @ubuntu:/mnt/hgfs/ub2/class$
The above error message contains double free or corruption, which indicates the problem. Object x applies for a piece of memory space, and object y uses the applied memory space. Because the destructor is executed twice, that is, releasing memory twice, but actually only one piece is applied, an error double free is reported.
The following is a deep copy:
#include <stdio.h> #include <string.h> class A{ public: A() { printf("A()\n"); p = new char[10]; strcpy(p, "hello"); } A(const A &x) { printf("const A()\n"); p = new char[10]; strcpy(p, x.p); } ~A() { printf("~A()\n"); delete [] p; } private: char *p; }; int main() { A x; A y = x; }
result:
@ubuntu:/mnt/hgfs/ub2/class$ g++ class_7.cpp @ubuntu:/mnt/hgfs/ub2/class$ ./a.out A() const A() ~A() ~A() @ubuntu:/mnt/hgfs/ub2/class$
From the perspective of code security, deep copy is very necessary. In fact, deep and shallow copy has something in common with what we copy and paste. Shallow copy is equivalent to copying a shortcut, while deep copy requires copying the whole file package.