C++筆記(6)

1.相關數據結構成員方法的調用

class Stack
{
public:
     Stack()
     {
          cout << this << endl;
          cout << "Stack()" << endl;
          mpstack = new int[10];
          mtop = -1;
          msize = 10;
     }
     //explicit:你明確的調用了這個構造函數,我纔給你構造對象;否則
     //編譯錯誤   explicit:防止隱式產生臨時對象用的!
     //explicit Stack(int size)
     Stack(int size)
     {
          cout << this << endl;
          cout << "Stack(int)" << endl;
          mpstack = new int[size];
          mtop = -1;
          msize = size;
     }
     Stack(int size, int len, int initVal)
     {
          cout << this << endl;
          cout << "Stack(int, int, int)" << endl;
          mpstack = new int[size];
          msize = size;
          mtop = -1;
          for (int i = 0; i < len; ++i)
          {
              mpstack[++mtop] = initVal;
          }
     }
     ~Stack()
     {
          cout << this << endl;
          cout << "~Stack()" << endl;
          delete[]mpstack;
          mpstack = NULL;
     }
     Stack(const Stack &src)
     {
          cout << &src << "=>" <<this << endl;
          cout << "Stack(const Stack &src)" << endl;
          mpstack = new int[src.msize];
          msize = src.msize;
          mtop = src.mtop;
          for (int i = 0; i <= mtop; ++i)
          {
              mpstack[i] = src.mpstack[i];
          }
     }
     void operator=(const Stack &src)
     {
          cout << &src << "=>" << this << endl;
          cout << "void operator=(const Stack &src)" << endl;
          if (this == &src)
              return;

          delete[]mpstack;

          mpstack = new int[src.msize];
          msize = src.msize;
          mtop = src.mtop;
          for (int i = 0; i <= mtop; ++i)
          {
              mpstack[i] = src.mpstack[i];
          }
     }
private:
     int *mpstack;
     int mtop;
     int msize;
};
在main()函數裏:
Stack s1;//調用默認的構造函數
Stack s2(20);//調用帶有一個整型參的構造函數
Stack s3(10,20,15);//調用帶有三個整型參的構造函數
Stack s4(s3);//Stack s4=s3;//調用自定義的拷貝構造函數
s2=s4;//調用自定義的賦值運算符的重載函數
1)Stack s5=Stack(20);//Stack s5=20;//Stack s5(20);//直接調用帶有一個整型參的構造函數
     Stack s6=Stack(20,15,15);//直接調用帶有三個整型參的構造函數
     //告訴編譯器,幫我產生一個臨時對象【這裏指定了類名稱,叫做顯示產生臨時對象】
     臨時對象的生存週期:語句結束,週期就到了。
/*
C++編譯器的編譯規則:
用臨時對象[顯示產生的臨時對象]拷貝構造函數時,臨時對象就被優化掉了,直接構造左值對象就行
*/
2)Stack s7=(Stack)(20,15,30);//Stack s7=(Stack)30;//Stack s7=Stack(30);
                                 //此處是逗號表達式,最終值爲30
     //調用帶有一個整型參的構造函數
3)s7=20;//s7.operator=(20);//此處相當於隱式產生臨時對象
     此句話相當於做了三件事:
     1.Stack(int)==>20,即隱式產生臨時對象;
     2.operator=(const Stack &src(src引用的就是臨時對象)),即調用賦值運算符的重載函數;
     3.析構臨時對象.
問題:
隱式產生的臨時對象爲什麼不優化?
一般隱式的臨時對象不是在新對象的創建的情況下產生的,而是在對已存在的對象進行賦值時產生的,而C++的編譯規則中臨時對象被優化的前提是用臨時對象拷貝構造新對象,所以隱式產生的臨時對象的創建是不能被優化從而省略掉的,而是在等號賦值之後會被析構。
運算符explicit:意爲明確的,確定的
                          即你明確的調用了這個構造函數,我纔給你構造,否則編譯報錯。
                          只能修飾構造函數,是防止隱式產生臨時對象用的。
                          eg:s7=20;此時隱式產生了臨時對象,並且調用的是帶有一個整型參的構                                   造函數,所以我們就可以給帶有一個整型參的構造函數之前加上                                           explicit運算符,此處就不會隱式產生臨時對象了。
                                 即:explicit Stack(int size);
4)Stack *p1=new Stack();
     Stack *p2=new Stack(10);
     Stack *p3=new Stack(10,5,60);
     //在堆上構造新對象,必須自己delete,否則永遠不會析構
     delete p1;
     delete p2;
     delete p3;
     異常:
     delete (int *)p1;
     delete (int *)p2;
     delete (int *)p3;
     //指針的類型會影響析構的調用,指針調用什麼類型,析構什麼類型,而這裏delete時起始地址沒變,所以會釋放p1/p2/p3所佔用的整塊內存,但是不會調用類類型的析構,所以會將p1/p2/p3佔用的外部資源弄丟了

     Stack *p4=new Stack[3];
     //在堆上創建一個對象數組,那麼每個對象就只能調用默認構造函數,此處調用三次
     delete []p4;
     //delete三次
     異常:
     delete p4;
     //整塊內存都釋放了,但卻只釋放了Stack[0]上的外部資源,沒有釋放Stack[1],Stack[2]上的外部資源
     new:除了開闢內存,還能調用對象的構造函數
     delete:會把堆上的對象先析構,再釋放其內存
5)Stack *p5=&Stack(10);//棧上構造
     //用指針指向臨時對象,語句結束,臨時對象就析構了
     Stack &ref1=Stack(20);//Stack ref1(20);//棧上構造
     //用引用來引用臨時對象,相當於起別名,即使語句結束,也不會析構
2.例題:請給出下面對象創建過程中涉及的方法打印
class Test
{
public:
     Test(int a = 5, int b = 5) :ma(a), mb(b)
     {
          cout << "Test(int, int)" << endl;
     }
     ~Test()
     {
          cout << "~Test()" << endl;
     }
     Test(const Test &src) :ma(src.ma), mb(src.mb)
     {
          cout << "Test(const Test&)" << endl;
     }
     void operator=(const Test &src)
     {
          ma = src.ma; mb = src.mb; cout << "operator=" << endl;
     }
private:
     int ma;
     int mb;
};
Test t1(10, 10);//程序運行時開闢,調用帶有兩個整型參的構造函數
int main()
{
     Test t2(20, 20);//調用帶有兩個整型參的構造函數
     Test t3 = t2;//調用自定義的拷貝構造函數
     static Test t4 = Test(30, 30);//t4起初開闢內存,在運行時調用拷貝構造函數
     t2 = Test(40, 40);
     //1.調用帶有兩個整型參(20,20)的構造函數構造臨時對象  2.賦值運算符的重載函數  3.析構臨時對象
     t2 = (Test)(50, 50);//相當於強轉逗號表達式,相當於t2=Test(50,5)[5是默認大小]
     //1.調用帶有兩個整型參(20,20)的構造函數構造臨時對象  2.賦值運算符的重載函數  3.析構臨時對象
     t2 = 60;//相當於隱式產生一個臨時對象,等價於t2=Stack(60,5)
     //1.調用帶有兩個整型參(20,20)的構造函數構造臨時對象  2.賦值運算符的重載函數  3.析構臨時對象
     Test *p1 = new Test(70, 70);//動態開闢對象的內存,調用帶有兩個整型參的構造函數
     Test *p2 = new Test[2];//動態開闢對象的內存,調用帶有兩個默認的構造函數
     //堆上動態開闢的內存必須手動delete,否則永遠不會析構
     Test *p3 = &Test(80, 80);
     //指針:1.調用帶有兩個整型參的構造函數構造一個臨時對象  2.語句結束臨時對象析構
     Test &p4 = Test(90, 90);
     //引用:調用帶有兩個整型參的構造函數構造一個臨時對象,並給這個臨時對象起了個別名p4,所以語句結束臨時對象不會析構
     delete p1;//手動釋放p1所佔用的外部資源
     delete[]p2;//手動釋放p2所佔用的外部資源
}
Test t5(100, 100);//程序運行時開闢,調用帶有兩個整型參的構造函數
//釋放的順序是:p4,t3,t2,t4,t5,t1

運行結果爲:
3.問題:
   C語言中空struct結構體的sizeof大小是0,而C++中空class類的sizeof大小是1個字節?
   struct結構體裏只算變量的大小,存在變量,按照字節對齊原理計算字節大小,當結構爲空時,就是沒有變量存在,所以sizeof(struct)=0;
   而在類class中,計算的是一個類的實例化對象所佔空間的大小。每個實例在內存中都有獨一無二的地址,爲了達到這個目的,編譯器往往會給空類隱含的加一個字節,這樣空類在實例化後在內存中得到了獨一無二的地址,所以空類所佔的內存大小是1個字節。
   /*
        類的大小:
        1.爲類的非靜態成員數據的類型大小之和;
        2.由編譯器額外加入的成員變量的大小,用來支持語言的某些特性(如:指向虛函數的指針);
        3.爲了優化存取效率,進行的邊緣調整(字節對齊);
        4.與類中的構造函數,析構函數以及其他的成員函數無關。
   */
4.關於函數代碼優化:
class Test
{
public:
     Test(int a = 5) :ma(a)
     {
          cout << "Test(int=5)" << endl;
     }
     ~Test()
     {
          cout << "~Test()" << endl;
     }
     Test(const Test &src) :ma(src.ma)
     {
          cout << "Test(const Test&)" << endl;
     }
     void operator=(const Test &src)
     {
          ma = src.ma;
          cout << "operator=" << endl;
     }
     int getValue(){ return ma; }
private:
     int ma;
};  

第一種優化:
如果用值傳遞相當於產生了一個臨時對象temp,Test a=temp;會調用拷貝構造函數,而在函數結束後還要析構臨時對象,如果引用接收對象,就只是給本來的對象起了個別名,不會產生臨時對象,更不用析構,提高代碼效率。
優化一:函數參數傳遞過程中,要用引用接收對象,不要用值接受對象。
第二種優化:
如果返回已經存在的對象,還要在之前調用拷貝構造新函數先構造這個對象再返回它,函數結束之後再析構這個對象,如果直接返回臨時對象,就不存在拷貝構造和析構的過程了,提高代碼效率。
優化二:當函數返回一個對象時,要返回臨時對象,不要返回已經存在的對象。
第三種優化:
以初始化的方式接收的話就直接調用構造函數構造t2,不會有臨時對象的產生,此時代碼效率達到最優。
優化三:以初始化的方式接收返回類型爲對象的函數調用。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章