1. 程式人生 > >深度探索C++物件模型-----第一章筆記

深度探索C++物件模型-----第一章筆記

第一章:關於物件

1.1 C++模式
 

關於操作符過載的問題

type& operator[](int index)
{
    assert(index < dim && index >= 0);
    return _coords[index];
}
type operator[](int index)const
{
    assert(index < dim && index >= 0);
    return _coords[index];
}

一個類過載operator[]()函式時要過載operator[]兩次,一個是const的,一個是非const的。
因為const型別只能訪問const約束的函式,而non-const的會優先訪問non-const的,如果沒有non-const則呼叫const函式。所以每次過載時定義兩個可以保護避免修改需要的變數。

 

關於友元過載的問題:首先流操作符因為要與cin、cout類互動所以使用friend。

// 宣告在函式內部,可以與兩個引數互動
friend A operator+(const A &t1, const A &t2);

// 具體實現
A operator+(const A &t1, const A &t2)
{   
    A t;    
    t.a = t1.a + t2.a;    
    t.b = t1.a + t2.b;    
    return t;
}


//非友元過載,只能與一個引數互動,另一個引數則是this指標。
A operator+(A & tmpa)
{
    A t;
    t.a = this->a + tmpa.a;
    t.b = this->b + tmpa.b;
    return t;
}
//實際上,按下面的方式對定義進行修改(交換乘法運算元的順序),可以將這個友元函式編寫為非友元函式:
class1 operator*(double m,const class1 &t)
{
  return t*m;//use t.operator*(m)
}

上圖取自別人的部落格,大概用法與上述的方法相同。

 

關於const的問題

#include <iostream>
#include <cassert>
#include <string>
using namespace std;
template <class type, int dim>
class Point
{
public:
Point();
Point(type coords[dim])
{
for (int index = 0; index < dim; index++)
{
_coords[index] = coords[index];
}
}
type& operator[](int index)
{
assert(index < dim && index >= 0);
return _coords[index];
}
type operator[](int index)const
/*這裡要注意如果用了operator&會報錯const string無法轉化為string &形式,具體原因是無法將const傳遞給普通引用,改法就是const type& operator[](int index)const。這種形式也就是所謂的const *type const this*/
{
assert(index < dim && index >= 0);
return _coords[index];
}
private:
type _coords[dim];
};
template <class type, int dim>
ostream& operator<<(ostream &os, const Point<type, dim> &pt)
{
os << "(";
for (int ix = 0; ix < dim - 1; ix++)
os << pt[ix] << ", ";
os << pt[dim - 1];
os << ")";
return os;
}

 

const成員函式

classStack
{
public:
    void Push(int elem);
    int Pop(void);
    intGetCount(void) const; // const 成員函式
private:
    intm_num;
    int m_data[100];
};
int Stack::GetCount(void)const
{
    ++ m_num; // 編譯錯誤,企圖修改資料成員m_num
    Pop();// 編譯錯誤,企圖呼叫非const函式
    returnm_num;
}

a.const物件只能訪問const成員函式,而非const物件可以訪問任意的成員函式,包括const成員函式.
b.const物件的成員是不可修改的,然而const物件通過指標維護的物件卻是可以修改的.
c.const成員函式不可以修改物件的資料,不管物件是否具有const性質.它在編譯時,以是否修改成員資料為依
據,進行檢查.
d.然而加上mutable修飾符的資料成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函式是可
以修改它的

 

static的問題:存在靜態儲存區,不能直接呼叫而是可以採用類名.成員的方式呼叫。

public class Foo{
    int i=0;
    static{
        int j=i+1;//這裡不能呼叫非static的變數i
    }
    static{
        Foo f = new Foo();
        int j=f.i+1;//通過物件點可以呼叫
    }
}

linux段管理分為:
BSS段(儲存未初始化的全域性變數,不佔據磁碟空間,儲存在其他表中只有當執行時才轉為DATA,屬於靜態記憶體分配)
DATA段(儲存初始化的全域性變數)
TEXT段(用於存放程式程式碼,處理各個原始檔之間的函式引用以及處理各個段之間的函式引用問題)
RODATA段(常量區,用於存放常量資料,有時候該區域為節省空間是多個程序共享的,包括const修飾的全域性變數等)
STACK段(由系統申請和釋放,用於儲存引數變數和區域性變數,可以處理遞迴)
HEAP段(由使用者申請和釋放)
可用nm指令檢視一個c檔案的記憶體情況。

class Point{
public:
    Point(float xval);//class object外
    virtual ~Point();//vtbl裡
    float x() const;//class object外
    static int PointCount();//class object外

protected:
    virtual ostream& print(ostream &os) const;//vtbl裡
    float _x;//class object裡
    static int _point_count;//class object外
};
//因此可知class object裡只有nonstatic data member和vptr兩個,如果此時需要重新增刪改_x這個變數,這個類需要重新編譯。

虛繼承的一個優勢在於只有一個虛基類的例項,其他儲存的是指標及偏移量。

class A  //大小為4
{
public:
    int a;
};
class B :virtual public A  //大小為12,變數a,b共8位元組,虛基類表指標4
{
public:
    int b;
};
class C :virtual public A //與B一樣12
{
public:
    int c;
};
class D :public B, public C //24,變數a,b,c,d共16,B的虛基類指標4,C的虛基類指標4
{
public:
    int d;
};

 

關於靜態與非靜態的問題:在常用的方法下建議使用靜態,靜態沒有例項物件new出來的開銷,只有一份方法。一切不需要例項化的有確定行為的方法都應該設計為靜態的。靜態方法只能呼叫靜態成員變數。對於用不到this指標的類適合用靜態方法。對於Singleton以及工廠模式以及資料庫SQLHelper那種模式的,都適合用靜態方式。
C++記憶體5大儲存區是堆區、棧區、全域性區或靜態區、字元常量區、程式程式碼區。
C++記憶體分配有三種:靜態儲存區分配、棧上建立、堆上分配。
棧上儲存資料會比堆要來的快,堆是尋找匹配記憶體的因此會產生碎片而棧並不會產生碎片。

Java也有5個,分別是程式計數器、堆、Java棧和本地方法棧、方法區。
Java靜態方法儲存在方法區(方法區中有一個常量池,用來儲存編譯期間生成的各種字面量和符號引用,字串也儲存在其中,但是new出來的字串在堆中,所有new出來的全部在堆中),C++的靜態方法儲存在靜態儲存區。

傳值與傳址:如果不改變實參則傳值,如果改變了實參則傳址,當呼叫陣列且不修改實參時建議使用const+指標傳遞,如果是結構或類則建議用引用傳遞。建議去看:https://blog.csdn.net/songjunyan/article/details/20576043https://www.cnblogs.com/MATU/p/3819829.html講得很好。
使用函式進行記憶體分配時注意:
有時候需要使用二級指標,即指標的指標,例如:

MemAllocate(char *a){
    a=(char *)malloc(sizeof(char));//錯誤
}

當呼叫此函式進行記憶體分配時,發現不能分配記憶體不能成功,因為此時對於a來說,形參改變了,但實參並不
會改變,他們對應於不同的記憶體單元。正確的寫法應該是:

MemAllocate(char **a){
    *a=(char *)malloc(sizeof(char));//正確
}


1.引用是實參的別名,儲存在實參的記憶體中,如果不使用引用而是一般變數時要給形參分配記憶體單元也就是實參的副本,如果實參是物件還將呼叫拷貝建構函式,因此一般而言引用比一般變數傳遞引數的效率和空間更好。常引用既可以用引用提高程式的效率,又能保證傳遞給函式的資料值不被改變。

2.引用與多型的關係:
靜下心來看完可以收穫很多:https://blog.csdn.net/u011939264/article/details/52175504
 

Class A; 
Class B : Class A{...}; 
B b; 
A& ref = b;//1.3節那裡詳細介紹了三種多型方式


總結下來是基類指標和引用可以指向派生類物件,但是無法訪問不存在於基類而只存在於派生類的元素。(因此我們需要虛擬函式和純虛擬函式)。如果要引用派生類裡的成員則需要強制進行派生類轉化使其靜態繫結為派生
類。或者在動態繫結時讓派生類重寫基類的成員函式就可以訪問到派生類了。而動態繫結需要基於虛擬函式來實
現。
靜態型別表明一個物件能執行的所有動作(成員函式)+它的自有屬性(資料成員)。
而動態繫結可以修改動態型別為派生類。
只有虛擬函式會實現動態繫結,而多型也是基於動態繫結從而實現的,也就是class A裡的函式為虛擬函式,這樣
基類才可以訪問到派生類裡的成員。

3.流操作符一定要使用引用為返回值
只有引用才可以實現級聯操作,因為如果不使用引用,兩次<<操作符產生的結果實際上是針對不同物件的,如
果不是引用,程式返回時就會生成新的臨時物件。
ostream& operator<<(ostream& out, const A& a) { out<<A.x<<' '<<A.y<<endl; return out;}
而+-*/不能返回引用,如果返回引用那麼引用源會沒有。也就是sum會沒有。

A operator+(A a,A b) 
{
    A sum;   
    sum.x=a.x+b.x; 
    sum.y=a.y+b.y;   
    return sum; 
}
A& operator+(A& a,A &b);
//也是可以的,但是如果是A& operator+(A& a,B &b);則可能會報錯因為如果堆疊已被破壞就會報錯。
//注:返回一個引用不能使用臨時變數
int & fun() 
{ 
    int a; 
    a=10; 
    return a; 
}//這樣是不行的,因為a會在fun退出時被銷燬,這時返回的a的引用是無效的。
//這種情況下,如果fun的返回型別不是int & 而是int就沒有問題了。
//函式返回時如果不是返回引用那麼一定會產生一個臨時變數。

 

#include <iostream>
using namespace std;
class Complex
{
   public:
   friend ostream& operator << (ostream&,Complex&); //宣告過載運算子“<<”,以友元形式是因為最後是以cin、cout形式輸出的,而cin、cout與Complex是不同的類,因此讓cin、cout可以訪問Complex。
   friend istream& operator >> (istream&,Complex&); //宣告過載運算子“>>”
   private:
   double real;
   double imag;
};
ostream& operator << (ostream& output,Complex& c)
{
   output<<"("<<c.real;
   if(c.imag>=0) output<<"+";//虛部為正數時,在虛部前加“+”號
   output<<c.imag<<"i)"<<endl;  //虛部為負數時,在虛部前不加“+”號
   return output;
}
istream& operator >> (istream& input,Complex& c)  //定義過載運算子“>>”
{
   cout<<"input real part and imaginary part of complex number:";
   input>>c.real>>c.imag;
   return input;
}
int main( )
{
   Complex c1,c2;
   cin>>c1>>c2;
   cout<<"c1="<<c1<<endl;
   cout<<"c2="<<c2<<endl;
   return 0;
}

4.友元函式是在類中用關鍵字friend修飾的非成員函式,它並不是本類的成員函式但卻可以通過物件名訪問類的私有成員和保護成員。友元類是在別的類中宣告我是你的friend,可以獲取那個類中的私有和保護成員。是去別人那裡宣告,然後訪問別人的成員。

 

 

1.2關鍵詞帶來的差異

主要講了struct和class,裡面的模板:
template<typename T>
inline T const& max(T const& a,T const& b)
{    
    return a<b?a:b;
}

1.3物件的差異

重點:

class Parent{};
class Child:public Parent{};
Child c;
Parent p=c;//派生類的物件可以直接賦值給基類物件
Parent &rc=c;//基類物件的引用可以引用一個派生物件
Parent *pc=&c;//基類物件的指標可以指向一個派生物件

對於多繼承會產生二義性的問題,因為多繼承會保留有多套基類的資料,因此呼叫要使Obj.A::next=0去訪問
,而虛繼承只保留有一套基類的資料。

class L
{
public:
    int next;
};
class A:public L{};
class B:public L{};
class C:public A,public B
{
public:
    void f(){next=0;}
};

C Obj;
Obj.next=0;//會產生二義性的錯誤
Obj.A::next=0;//可以正常訪問。如果是虛繼承的話就不用這麼麻煩。
zooAnimal za("Zoey");
zooAnimal *pza=&za;//pza是指向za指標。