C++&Qt踩坑与经验总结

前言

C++基础语法部分

(1) 重写基类的纯虚函数,返回值必须是基类的指针或者引用,否则报错

(2)以下代码(其实与第一条一致)

//存在一个抽象模板类类:template<class NodeType> MyIterator
 //存在一个继承自MyIterator的派生类:UPItertaor
 //基类中有一个函数:
 ​
 virtual bool operator== (const MyIterator<NodeType>& iter)=0;   // MyIterator
 // 这个函数是没有办法通过协变在派生类中实现的,如果强行按以下的方法书写:
 virtual bool operator== (const UPIterator<NodeType>& iter)=0;   // UPIterator
 // 这个函数只能算是operator==的重载而不是基类函数的实现
 

(3) 模板类的友元函数最好在命名空间中实现(如果有命名空间的话)

(4) 定义命名空间中声明的类(即写出class{public: ......})必须要添加命名空间的限定符

namespace test{
     class hello;
 }
 ​
 class hello{
     public:
     .......
 }   // 这个类与test中的class hello 不是同一个类,(想想这个错误真的很蠢)

(5) std::map 的迭代器使用时 map<type_1,type_2>::iterator出错,如果想要声明一个包含模板嵌套的容器的迭代器,应当用auto 关键字指定迭代器,或者用typename关键字指定出迭代器:

参考链接:c++ iterator with template - Stack Overflow

 
map<char,double> map_1;
 map<char,double>::iterator iter = map_1.begin();                    // 有具体的模板时这么写是正确的
 ​
 template<class char_type>
 void func(){
     map<char_type,double> map;
     map<char_type,double>::iterator iter_1 = map.begin();           // 声明是可以的
     cout<<iter_1 == map.end();                                      // 模板是实例化的时候确定的,实例化之前编译器无法知道 map<char_type,double>::iterator是个什么东西(可能是类型名也有可能是变量名),这时可以通过手动指定或者让编译器自行推断两种方法解决
     /*
     auto iter_1 = map.begin();                                      
     // 让编译器等实例化后自己推断
     typename map<char_type,double>::iterator iter_1 = map.begin();  
     // 手工指定map<char_type,double>::iterator为一种变量类型
     */
 }
 ​
 // 如果需要用到迭代器模板参数最好用typename指定出来,可以在template<typename char_type>就进行指定
 // 不过需要注意的是不是任何情况都要用typename指定模板,当出现以下情况:
 /*
 template<class Type>
 class A{
 ​
 // typename Type func();
 // 报错:expected nested-name-specifier before 'Type'
 Type func();
 // 这样写ok
 }
 ​
 */
 ​

(6) 函数的返回值变量传递过程

int func(int a,int b){
     return a+b;
 }
 ​
 int main(){
     int& c = func(1,1);         // 会报错,函数的返回值是一个右值,右值的生命周期只在包含它的语句中,左值引用只能引用左值
 }

(7) 右值引用的地址

 int&& a = 12;                       // &a: 0x77207ffaf0
 int&& b = 12;                       // &b: 0x77207ffaf4
 // 两次引用指向的地址不相同

(8) 移动语义与右值引用

  • 参考:

  • 以下是一个使用右值引用优化左值复制构造函数为右值移动构造函数的示例

// 这里我们定义了一个管理大型内存(数组)的一个类
 #include <iostream>
 using namespace std;
 ​
 class MyVector{
 public:
     int* vec_list;
     int length;
     MyVector():vec_list(nullptr){};
     MyVector(int* a,int len){
         this->length =len;
         this->vec_list = new int[len];
         for(int i = 0;i<len;i++){
             this->vec_list[i] = a[i];
         }
     };
     MyVector(const MyVector& left_quate){
         // 对于左值引用我们可以开辟新的内存来存储,因为对于左值引用来说它的生命周期不确定
         this->length = left_quate.length;
         this->vec_list = new int[this->length];
         for(int i = 0;i<this->length;i++){
             this->vec_list[i] = left_quate.vec_list[i];
         }
     }
     MyVector(MyVector&& right_quate){
         // 对于右值引用可以直接将右值的堆内存拿过来使用,因为右值的生命周期在所在表达式中,表达式结束以后数组会随着右值被释放
         this->vec_list = right_quate.vec_list;
         right_quate.length = 0;
         right_quate.vec_list = nullptr;
     }
     ~MyVector(){
         if(this->vec_list){
             delete this->vec_list;
         }
     }
     friend MyVector operator+(const MyVector& vec_1,const MyVector& vec_2){
         // 重载了加法运算符,将两个MyVector对象合成为一个
         MyVector temp;
         temp.length = vec_1.length+vec_2.length;
         temp.vec_list = new int[temp.length];
         int index_temp = 0;
         for(int i = 0;i<vec_1.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_1.vec_list[i];
         }
         for(int i = 0;i<vec_2.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_2.vec_list[i];
         }
         return temp;
     }
 };
 ​
 int main(){
 int a[3] = {1,2,3};
 MyVector list_1(a,3);
 MyVector list_2(a,3);
     // 以下的list_3的构造函数调用的是左值引用构造函数,开辟新内存构造
 MyVector list_3(list_1);    
     // 以下的list_4的构造函数调用的是右值引用构造函数,因为list_1+list_2的值是一个右值
 MyVector list_4(list_1+list_2);
     // 以下list_5调用了MyVector的隐式右值引用构造
 MyVector list_5 = list_1+list_2;
 for(int i = 0;i<list_4.length;i++){
     cout<<list_4.vec_list[i]<<" ";
 }
     // 1 2 3 1 2 3
 cout<<endl;
 for(int i = 0;i<list_5.length;i++){
     cout<<list_5.vec_list[i]<<" ";
 }
     // 1 2 3 1 2 3
 }
  • 添加右值引用构造可以避免不必要的内存开辟:如上述的示例,如果没有右值引用复制构造函数,一些编译器会自动进行优化,它不会调用任何构造函数,但是会替我们完成移动构造这个操作

#include <iostream>
 using namespace std;
 ​
 class MyVector{
 public:
     int* vec_list;
     int length;
     MyVector():vec_list(nullptr){};
     MyVector(int* a,int len){
         this->length =len;
         this->vec_list = new int[len];
         cout<<"调用了数组构造函数,分配了内存"<<endl;
         for(int i = 0;i<len;i++){
             this->vec_list[i] = a[i];
         }
     };
     MyVector(const MyVector& left_quate){
         // 对于左值引用我们可以开辟新的内存来存储,因为对于左值引用来说它的生命周期不确定
         this->length = left_quate.length;
         this->vec_list = new int[this->length];
         cout<<"调用左值构造函数,分配了内存"<<endl;
         for(int i = 0;i<this->length;i++){
             this->vec_list[i] = left_quate.vec_list[i];
         }
     }
     // MyVector(MyVector&& right_quate){
     //     // 对于右值引用可以直接将右值的堆内存拿过来使用,因为右值的生命周期在所在表达式中,表达式结束以后数组会随着右值被释放
     //     this->vec_list = right_quate.vec_list;
     //     right_quate.length = 0;
     //     right_quate.vec_list = nullptr;
     // }
     ~MyVector(){
         if(this->vec_list){
             delete this->vec_list;
             cout<<"析构函数被调用,删除数组"<<endl;
         }
     }
     friend MyVector operator+(const MyVector& vec_1,const MyVector& vec_2){
         MyVector temp;
         temp.length = vec_1.length+vec_2.length;
         temp.vec_list = new int[temp.length];
         int index_temp = 0;
         for(int i = 0;i<vec_1.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_1.vec_list[i];
         }
         for(int i = 0;i<vec_2.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_2.vec_list[i];
         }
         return temp;
     }
 };
 ​
 int main(){
 int a[3] = {1,2,3};
 MyVector list_1(a,3);
 MyVector list_2(a,3);
 MyVector list_3(list_1);
 MyVector list_4(list_1+list_2);
     // 以下list_5调用了MyVector的隐式右值引用构造,如果不使用设置右值构造,是会报错的,因为右值是即将消亡的,左值引用不可指向右值
 MyVector list_5 = list_1+list_2;
 cout<<"list_1:数组地址:"<<list_1.vec_list<<endl;
 cout<<"list_2:数组地址:"<<list_2.vec_list<<endl;
 cout<<"list_3:数组地址:"<<list_3.vec_list<<endl;
 cout<<"list_4:数组地址:"<<list_4.vec_list<<endl;
 cout<<"list_5:数组地址:"<<list_5.vec_list<<endl;
 for(int i = 0;i<list_4.length;i++){
     cout<<list_4.vec_list[i]<<" ";
 }
 cout<<endl;
 for(int i = 0;i<list_5.length;i++){
     cout<<list_5.vec_list[i]<<" ";
 }
 cout<<endl;
 ​
     
     
     return 0;
 }
 /*输出
 ​
 调用了数组构造函数,分配了内存         //      MyVector list_1(a,3);
 调用了数组构造函数,分配了内存         //      MyVector list_1(a,3);
 调用左值构造函数,分配了内存          //      MyVector list_3(list_1);下和下下一步list1+list2调用的是默认移动构造
 list_1:数组地址:0x207772d6130
 list_2:数组地址:0x207772d6150
 list_3:数组地址:0x207772d64a0
 list_4:数组地址:0x207772d64c0
 list_5:数组地址:0x207772d64e0
 1 2 3 1 2 3 
 1 2 3 1 2 3
 析构函数被调用,删除数组
 析构函数被调用,删除数组
 析构函数被调用,删除数组
 析构函数被调用,删除数组
 析构函数被调用,删除数组
 ​
 */
  • 可以看到左值构造函数只调用了一次,与我们的代码中完全一致,而右值完全没有(我们的右值移动构造函数已经被注释掉了),并且右值生命周期结束的时候没有使用我们自己的析构函数,可以推断出编译器一定进行了某些优化

  • 总结:虽然有时编译器会优化这个过程,如果没有优化可能会产生额外的两份等于右值的拷贝(一份是右值而另一份存储右值的拷贝(左值引用绑定右值,因此需要一个中间变量),传给左值构造函数进行构造),所以最好还是将右值移动构造函数定义出来(这样的话构造细节上的一切才能尽可能掌握再程序员手里)

(9) 强制移动语义(应用在赋值的情况)

  • 当出现以下情况

#include <iostream>
 using namespace std;
 ​
 class MyVector{
 public:
     int* vec_list;
     int length;
     MyVector():vec_list(nullptr){};
     MyVector(int* a,int len){
         this->length =len;
         this->vec_list = new int[len];
         cout<<"调用了数组构造函数,分配了内存"<<endl;
         for(int i = 0;i<len;i++){
             this->vec_list[i] = a[i];
         }
     };
     MyVector(const MyVector& left_quote){
         // 对于左值引用我们可以开辟新的内存来存储,因为对于左值引用来说它的生命周期不确定
         this->length = left_quote.length;
         this->vec_list = new int[this->length];
         cout<<"调用左值构造函数,分配了内存"<<endl;
         for(int i = 0;i<this->length;i++){
             this->vec_list[i] = left_quote.vec_list[i];
         }
     }
     MyVector(MyVector&& right_quote){
         // 对于右值引用可以直接将右值的堆内存拿过来使用,因为右值的生命周期在所在表达式中,表达式结束以后数组会随着右值被释放
         cout<<"调用了右值移动构造函数"<<endl;
         this->vec_list = right_quote.vec_list;
         right_quote.length = 0;
         right_quote.vec_list = nullptr;
     }
     ~MyVector(){
         cout<<"析构函数被调用";
         if(this->vec_list){
             delete this->vec_list;
             cout<<"删除了数组"<<endl;
         }
         else{
             cout<<"没有删除数组"<<endl;
         }
     }
     friend MyVector operator+(const MyVector& vec_1,const MyVector& vec_2){
         MyVector temp;
         temp.length = vec_1.length+vec_2.length;
         temp.vec_list = new int[temp.length];
         int index_temp = 0;
         for(int i = 0;i<vec_1.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_1.vec_list[i];
         }
         for(int i = 0;i<vec_2.length;i++,index_temp++){
             temp.vec_list[index_temp] = vec_2.vec_list[i];
         }
         return temp;
     }
     MyVector operator=(const MyVector& left_quote){
         // 左值,复制复制运算符
         delete[] this->vec_list;
         this->length = left_quote.length;
         this->vec_list = new int[this->length];
         cout<<"调用左值复制赋值构造函数,分配了内存"<<endl;
         for(int i = 0;i<this->length;i++){
             this->vec_list[i] = left_quote.vec_list[i];
         }
         return *this;
     }
     MyVector operator=(MyVector&& right_quote){
         cout<<"调用了右值移动赋值运算"<<endl;
         delete[] this->vec_list;
         this->vec_list = right_quote.vec_list;
         right_quote.length = 0;
         right_quote.vec_list = nullptr;
         // 对于赋值运算符来说,不管是左值复制还是右值移动都最好是返回一份拷贝而不是没有内存的右值引用
         return *this;
     }
 };
 ​
 int main(){
     int lis[3] = {1,2,3};
     int lis_1[2] = {4,5};
     MyVector a(lis,3);      // 这里调用了基于数组的构造函数,其中开辟了新内存
     MyVector b(lis_1,2);
     // 将a转换成右值
     // 将b = std::move(a)的拷贝设置为右值,此时会调用右值移动赋值函数
     MyVector c = std::move(b = std::move(a));
     // 实际上只申请了三份内存,一份是a初始时的,一份是b初始时的,另一份是b,a赋值时返回的
     return 0;
 }
 ​
 // 注意构造的顺序是按照栈的形式执行的,因此先构造的后析构
 ​
 /*
 调用了数组构造函数,分配了内存                 // MyVector a(lis,3);
 调用了数组构造函数,分配了内存                 // MyVector b(lis_1,2);
 调用了右值移动赋值运算                       // b = std::move(a) 
 调用左值构造函数,分配了内存                  // b = std::move(a) 返回值
 调用了右值移动构造函数                       // MyVector c = std::move(b = std::move(a));
 析构函数被调用没有删除数组                   //  b = std::move(a) 的返回值 已经被右值赋值处理过            
 析构函数被调用删除了数组                    // c 的析构函数
 析构函数被调用删除了数组                    // b 的析构函数
 析构函数被调用没有删除数组                   // a 的析构函数 已经被右值赋值处理过
 */
 // 加上赋值中删掉的b的数组,一共申请了三次内存,删除了三次数组
 ​
 ​
 // 如果不使用std::move()
 int main(){
     int lis[3] = {1,2,3};
     int lis_1[2] = {4,5};
     MyVector a(lis,3);      // 这里调用了基于数组的构造函数,其中开辟了新内存
     MyVector b(lis_1,2);
     // 将a转换成右值
     // 将b = std::move(a)的拷贝设置为右值,此时会调用右值移动赋值函数
     MyVector c = (b = a);
     // 实际上只申请了三份内存,一份是a初始时的,一份是b初始时的,另一份是b,a赋值时的
     return 0;
 }
 /*
 调用了数组构造函数,分配了内存                 // MyVector a(lis,3);
 调用了数组构造函数,分配了内存                 // MyVector b(lis_1,2);
 调用左值复制赋值构造函数,分配了内存            // b = a 内部删除了b原来的数组
 调用左值构造函数,分配了内存                  // b = a 返回值给了c:MyVector c = (b = a);
 析构函数被调用删除了数组                     // c的析构
 析构函数被调用删除了数组                     // b的析构
 析构函数被调用删除了数组                     // a的析构
 */
 // 加上b=a赋值过程中删掉的数组,一共申请了四次内存,删除了四次数组
 ​
 // 因此,在某些情况下适当使用move()函数可以减少程序对内存得开销
  • 注意std::move()函数起到的作用是将一个值强制转化成右值,至于值的移动是交由对应得右值构造函数和赋值函数完成得,std::move()本身不参与值得移动,它只起到类型转换得作用

(10) 模板友元类的声明

  • 一定要在发出友元声明的模板类之前写出被友元声明的模板类的类声明,否则会报出非模板类的错误

// 以下是一个盆友写的泛型链式栈
 #include <iostream>
 #include <assert.h>
 using namespace std;
 ​
 // 一定要在发出友元声明的模板类之前写出被友元声明的模板类的类声明,否则会报出非模板类的错误
 template<class T>
 class LinkedStack;
 ​
 template <class T>
 class StackNode
 {
 private:
     T data;
     StackNode<T>* link;
 ​
 public:
     StackNode(T data)
     {
         this->data = data;
         this->link = NULL;
     }
     StackNode()
     {
         this->link = NULL;
     }
     // 声明友元模板
     friend class LinkedStack<T>;
 };
 ​
 template <class T>
 class LinkedStack
 {
 private:
     StackNode<T>* top;
 ​
 public:
     LinkedStack()
     {
         top = NULL;
     }
     ~LinkedStack()
     {
         makeEmpty();
     }
     void Push(const T& x)
     {
         //先创建一个节点newStnde
         StackNode<T>* newStnde = new StackNode<T>(x);
         //让tmp指向top
         newStnde->link = top;
         //修改top的值,让top=newStnde
         top = newStnde;
         assert(top != NULL);
     }
     bool Pop(T& x)
     {
         if (IsEmpty() == true)
         {
             return false;
         }
         StackNode<T>* p = top;
         top = top->link;
         x = p->data;
         delete p;
         return 1;
     }
     bool getTop(T& x)
     {
         if (IsEmpty() == 1)
         {
             return 0;
         }
         x = top->data;
         return 1;
     }
     int getSize()
     {
         StackNode<T>* p = top;
         int k = 0;
         for (p; p != NULL; p = p->link)
         {
             k++;
         }
         return k;
     }
     void makeEmpty()
     {
         StackNode <T>* p;
         while (top != NULL)
         {
             p = top;
             top = top->link;
             delete p;
         }
     }
     bool IsEmpty()
     {
         return (top == NULL) ? true : false;
     }
     // 注意这个运算符重载函数是LinkedStack的友元但是不是StackNode的友元,因此无法访问StackNode的私有成员
     friend ostream& operator<<(ostream& out, LinkedStack<T>& s)
     {
         out << "the number of linkedstack: " << s.getSize() << endl;
         StackNode<T>* p = s.top;
         int i = 0;
         while (p != NULL)
         {
             out << ++i << ":" << p->data << endl;   // 报错,无法访问StackNode的私有成员
             p = p->link;// 报错,无法访问StackNode的私有成员
             return out;
         }
 ​
     }
 };
 int main()
 {
     LinkedStack<int> s;
     for (int i = 0; i <= 10; i++)
     {
         s.Push(i);
     }
     cout << s << endl;
     //取栈顶元素
     int n;
     s.getTop(n);
     cout << "the top is: " << n << endl;
     s.Pop(n);
     cout << "after Pop " << s << endl;
     s.makeEmpty();
     n = s.getSize();
     cout << "after makeEmpty,the size of LinKedStack" << n << endl;
     cout << s << endl;
     return 0;
 }

(11) 类的静态常量初始化办法

 // 不管是不是常量,只要是静态成员的初始化都应该在类外进行
 // main.cpp
 class A{
 public:
     static const string type;
 }
 const string A::type = "A";

(12) 虚析构函数的必要性

  • 如果不声明虚析构函数,在对象生命周期结束时,只会调用对应指针类型的析构函数

 
#include <iostream>
 using namespace std;
 ​
 class Base{
 private:
     int* value;
 public:
     Base(){
         cout<<"Base构造函数调用了"<<endl;
         this->value = new int(6);
     }
     ~Base(){
         cout<<"Base析构函数调用了"<<endl;
         delete this->value;
     }
 };
 ​
 class Child:public Base{
 public:
     Child():Base(){
         cout<<"Child构造函数调用了"<<endl;
     };
     ~Child(){
         cout<<"Child析构函数调用了"<<endl;
     }
 };
 int main(){
     Child* c1 = new Child;
     delete c1;
     /*
         Base构造函数调用了
         Child构造函数调用了
         Child析构函数调用了
         Base析构函数
         // 可以发现基类的析构函数也被调用了,正常运行
     */
     Base* c = new Child;
     delete c;
     /*
         Base构造函数调用了
         Child构造函数调用了
         Base析构函数调用了
         
         // 只能调用基类的析构函数,子类未析构(这样的话在子类中的析构操作无法实现)
     */
 }
// 将析构函数改为虚函数可以解决这个问题
 #include <iostream>
 using namespace std;
 ​
 class Base{
 private:
     int* value;
 public:
     Base(){
         cout<<"Base构造函数调用了"<<endl;
         this->value = new int(6);
     }
     virtual ~Base(){
         cout<<"Base析构函数调用了"<<endl;
         delete this->value;
     }
 };
 ​
 class Child:public Base{
 public:
     Child():Base(){
         cout<<"Child构造函数调用了"<<endl;
     };
     virtual ~Child()override{
         cout<<"Child析构函数调用了"<<endl;
     };
 };
 int main(){
     Base* c = new Child;
     delete c;
     /*
         Base构造函数调用了
         Child构造函数调用了
         Child析构函数调用了
         Base析构函数调用了
     */
 }

  • 基类的虚析构函数必须定义否则会报错:

    • [build] collect2.exe: error: ld returned 1 exit status

(13) C++子类与父类的访问控制

  • 可以在子类将父类中的公有成员声明为保护成员,然后可以通过兄弟类的公有函数访问

    •  #include <iostream>
       using namespace std;
       class A{
       public:
           A(){};
           virtual void show(){
               cout<<"hello A"<<endl;
           };
       };
       class B:public A{
       protected:
           virtual void show()override{
               cout<<"hello B"<<endl;
           }
       };
       class C:public A{
       protected:
           virtual void show()override{
               this->a_ptr->show();
           }
       public:
           A* a_ptr;
           C(A* a_ptr):a_ptr(a_ptr){};
           void show_public(){
               this->show();
           }
           ~C(){
               if(this->a_ptr != nullptr){
                   delete this->a_ptr;
               }
               
           }
       };
       int main(){
           A a;
           a.show();
           B b;
           A* b_ptr = &b;          // 不可绑定
           // b.show();            // 不可访问
           C c(new B);
           c.show_public();        // 可以通过同兄弟子类的公有函数访问
           return 0;
       }

  • 对成员变量重新声明:将公有变量声明为私有变量或者仍然保持公有,此时变量跟随指针类型

    • 这里成员变量实际上是存储了两份,基类一份,派生类一份,就算两个同名变量的访问控制属性相同,二者都不是同一个变量,因此不存在将基类的公有成员在派生类中声明为私有或保护的写法

    •  
      #include <iostream>
       #include <string>
       using namespace std;
       class A{
       public:
           std::string value;
           A():value("A"){};
           
       ​
       };
       class B:public A{
       ​
       public:
           std::string value;
           B():value("B"){};
           
       };
       int main(){
           B* b = new B;
           A* a = b;
           cout<<b->value<<endl;
           cout<<a->value;
       ​
           return 0;
       }
       // 两个指针指向的是同一个对象,但是因为指针类型不同输出结果也不同
       /*
       输出结果
           B
           A
       ​
       这说明了子类的成员与基类的成员变量是独立存储的(如果在子类重新声明成员的话),因此避免在子类重新声明同名成员,这样会覆盖基类的成员变量
       ​
       */

(14) 模板类的派生

  • 模板类派生时,一定要指出基类的模板类型

  • template<typename type_MEMBERTYPE>
     class MyVectorA:public std::vector<type_MEMBERTYPE>{
         
     }
     class MyVectorB:public std::vector<int>{
         
     }
     class MyVectorC:public std::vector{
         // 会报错
     }

(15) 泛型与容器类(以STL的vetcor为例)

  • 以std::vector为例它添加元素的函数是push_back,其接收的参数是左值引用或者右值引用,由于引用可以发生协变,因此std::vector<A>可以接收元素B(class B:public A),但是在删除元素或者vector析构的情况下是无法执行B的析构函数的(不管A的析构函数是否被声明为虚函数)

  •  
    #include <vector>
     #include <iostream>
     using namespace std;
     class A{
     public:
         A(){
             cout<<"A构造了"<<endl;
         }
         A(A&& other){
             cout<<"发生A的右值引用构造"<<endl;
         }
         virtual ~A(){
             cout<<"析构A了"<<endl;
         }
          virtual void out(){
             cout<<"我是A"<<endl;
         }
     };
     class B:public A{
     public:
         B(){
             cout<<"B构造了"<<endl;
         }
         B(B&& other){
             cout<<"发生B的右值引用构造"<<endl;
         }
         virtual ~B()override{
             cout<<"B析构了"<<endl;
         }
         virtual void out()override{
             cout<<"我是B"<<endl;
         }
     };
     int main(){
         vector<A> vec;
         vec.push_back(B());
         vec.back().out();
         cout<<endl;
     /*
     ​
     A构造了            在vec.push_back(B())中调用B的构造函数首先要调用基类构造函数
     B构造了            在vec.push_back(B())中调用B的构造函数
     发生A的右值引用构造  在vec.push_back(B())中由vector调用A的移动构造函数(因为vector的模板参数是A)
     B析构了            在vec.push_back(B())中由B产生的临时变量析构
     析构A了            临时变量调用基类析构函数
     我是A             虽然vector的函数back()返回的是基类A的引用,但是由于我们之前调用的是A的移动构造函数,此时存储的变量                    实际上是A,因此并没有发生协变
     ​
     析构A了            main函数结束,vec析构的同时依次调用其元素(类型为A)的析构
     ​
     */
         return 0;
     }

  • 由此可见,如果想要用vector(或者数组)来实现泛型存储,其中保存的类型应该是基类的指针而不是基类(至于引用是否可以,可以参考以下链接:11.4 引用作为模板参数 - 知乎 (zhihu.com))

Qt使用部分

(1) 用代码设置窗口时将setupUi()函数在代码设置的后面调用,导致代码设置的窗口被覆盖

(2) 尝试在QWidget的派生类中将QPainter对象作为成员对象创建,并使用

函数文档截图

 

说明

  1. painter只能在paintEvent虚函数中使用

  2. 是允许将painter创建为成员变量的,只不过只能在绘图事件中调用

(3) Qt中有许多类都有setFlags函数用于设置类的状态,为什么可以通过按位或运算符连接要选取的枚举值,从而达到设置状态的目的

(4) Qt中命名空间UI中的的类MainWindow和主窗口类MainWindow不是同一个类,UI::MainWindow是一个从文件ui_mainwindow.h中继承自Ui_MainWindow的类

(5) QGraphicsItem的继承类没有继承QObject而使用了宏Q_OBJECT,通过查看Qt文档可以发现,QGraphicsItem类是没有继承QObject类的,如果一定要使用信号与槽机制,要使用多继承(优先继承QObject其次继承QGraphicsItem)

(6) 编写Qt程序的时候,发现事件函数中可以通过Parent::function()的形式调用被重写前的基类函数,其实function并不是静态函数,其完整写法应该是:this->Parent::function(),实际上这个机制与指针或引用的向上强制转化(upcast)类似是相反的应用,向上强制转化可以通过基类指针访问派生类方法,而这里this是派生类的指针,通过添加域运算符,让派生类指针能够指向基类方法(当然在类外可以使用派生类指针实现上述操作)

参考:qt - How does C++ knows child class calls a parent method? - Stack Overflow

 #include <iostream>
 using namespace std;
 class A{
 public:
     A(){};
     virtual void out(){
         cout<<"hi"<<endl;
     };
     void func(){
         cout<<0;
     }
 };
 class B:public A{
     public:
     B(){};
     virtual void out()override;
 };
 void B::out(){
     cout<<"hello";
     this->A::out();         // A::out()     // 应用在虚函数中
     this->A::func();        // A::func()    // 同理可以通过这个办法用派生类指针访问基类函数
 }
 int main(){
     B b;
     b.out();                // 输出: hellohi
     
 ​
     return 0;
 }
 ​
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学艺不精的Антон

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值