天天看點

【C++】侯捷C++面向對象進階程式設計(上)

C++面向對象進階程式設計

  • ​​前言​​
  • ​​C++ Programs代碼基本形式​​
  • ​​檔案類型​​
  • ​​頭檔案寫法​​
  • ​​頭檔案布局​​
  • ​​class1——complex​​
  • ​​類的聲明​​
  • ​​inline——内聯函數​​
  • ​​class通路級别(access level)​​
  • ​​構造函數(ctors)​​
  • ​​構造函數特有文法​​
  • ​​構造函數放到private?​​
  • ​​常量成員函數const member functions​​
  • ​​參數傳遞​​
  • ​​傳回值​​
  • ​​友元friend​​
  • ​​同一個class的各個object互為友元​​
  • ​​操作符重載operator overloading(成員函數)​​
  • ​​操作符重載operator overloading(非成員函數)​​
  • ​​complex類實作過程​​
  • ​​class2——string​​
  • ​​class with pointer members​​
  • ​​深&淺拷貝ctor function​​
  • ​​copy assignment operator——拷貝指派函數​​
  • ​​string類的實作過程​​
  • ​​堆、棧、記憶體管理​​
  • ​​Stack​​
  • ​​Heap​​
  • ​​New​​
  • ​​Delete​​
  • ​​arry new​​
  • ​​static_靜态​​
  • ​​cout補充​​
  • ​​class template_類模闆​​
  • ​​function template_函數模闆​​
  • ​​namespace​​
  • ​​class Composition_複合​​
  • ​​class Delegation_委托​​
  • ​​Inheritance_繼承​​
  • ​​Inheritance with virtual functions​​
  • ​​Inheritance + Composition下的構造和析構​​
  • ​​Delegation + Inheritance 委托+繼承​​

前言

面向對象,就是将資料和處理這些資料的函數包在一起。資料隻有這個函數能夠看到,不會和其他的混雜在一起。

c++ class ->  c struct + 更多的特性      

C++的結構幾乎等同于class。

C++ Programs代碼基本形式

檔案類型

.h(header files)
.cpp      

延伸檔案名(extension file name)不一定是.h Or .cpp,也可能是.hpp或者其他甚至無擴充名。

頭檔案寫法

防止頭檔案重複包含

【C++】侯捷C++面向對象進階程式設計(上)
#ifndef _XXX
#define _XXX_      

頭檔案布局

前置聲明
類——聲明
類——定義-功能實作      

class1——complex

類的聲明

【C++】侯捷C++面向對象進階程式設計(上)

inline——内聯函數

增加了 inline 關鍵字的函數稱為“内聯函數”。内聯函數和普通函數的差別在于:當編譯器處理調用内聯函數的語句時,不會将該語句編譯成函數調用的指令,而是直接将整個函數體的代碼插人調用語句處,就像整個函數體在調用處被重寫了一遍一樣。(​​連結​​)

inline是C++關鍵字,在函數聲明或定義中,函數傳回類型前加上關鍵字inline,即可以把函數指定為内聯函數。這樣可以解決一些頻繁調用的函數大量消耗棧空間(棧記憶體)的問題。關鍵字inline必須與函數定義放在一起才能使函數成為内聯函數,僅僅将inline放在函數聲明前面不起任何作用。inline是一種“用于實作”的關鍵字,而不是一種“用于聲明”的關鍵字。(​​連結​​)

inline 函數傳回值類型 函數名()
{
    
}      

class通路級别(access level)

public——放所有函數(幾乎)
private——資料
protected——受保護的(暫時不說)      

資料一定要通過類中的函數來傳遞出去(或者被設定)。除非這些資料是public,但我們要避免的就是這種。

【C++】侯捷C++面向對象進階程式設計(上)

構造函數(ctors)

  1. 與類名相同
  2. 可以有預設參數
  3. 沒有傳回類型

構造函數特有文法

(充分運用特殊寫法)

注意:括号中要有接收參數double r ,double i

class complex
{
public:
    //一個變量的指派使用有兩個階段,
    //1.初始化
    //2.指派,使用
    //如果不用構造函數的特殊寫法,就相當于跳過了初始化,直接在函數中指派了
      complex(double r = 0,double i = 0):re(r),im(i)
          //傳進來的兩個數賦到類内變量中
    {
    }
private:
    double re,im;
};      

例:

#include<iostream>
using namespace std;
class Test
{
public:
  Test(int c,int d):a(c),b(d)
  {

  }
  int FanHui()
  {
    return a;
  }
private:
  int a, b;
};
int main(void)
{
  Test s1(1, 2);
  cout << s1.FanHui() << endl;
  return 0;
}      

與構造函數對應的析構函數,不帶指針的類,大多不用寫析構函數。

構造函數可以有很多個——overloading(重載),同名的函數可以同時存在(在編譯器看來其實不同名),函數重載長長發生在構造函數上,但是這種不行

class complex
{
public:
    complex(double r = 0,double i = 0):re(r),im(i)
    {
    }
    complex():re(0),im(0)
    {
    }
private:
    double re,im;
};      

構造函數放到private?

什麼情況會這樣做?

不允許外接建立對象,那這個類有什麼用呢?‘

例:設計模式中的Singleton(單體)

class A
{
public:
    static A& gentInstance();
    setup(){......}
private:
    A();
    A(const A& rhs);
    ...
};
A& A::getInstance()
{
    static A a;
    return a;
}      

常量成員函數const member functions

class裡面的函數分為會改變資料内容的,和不會改變資料内容的,不會改變資料的内容的函數加const。

加不加const看傳進來的參數經過這一系列運算會不會發生改變。(全局函數同理)

double real()const{    return re;}      

該加const的位置一定要加const——建立類對象前面加了const

【C++】侯捷C++面向對象進階程式設計(上)

參數傳遞

首先考慮傳引用,注意看是否可以傳引用。

pass by value

正常參數傳遞 類型 名稱      

pass by reference (to const)

就是傳引用,相當于傳指針。效果也同樣相同    const complex&   ————傳引用加const防止被修改      

傳回值

首先考慮傳引用,注意看是否可以傳引用。

return by value

類型      

return by reference (to const)

引用      

什麼時候不能傳回引用

如果新得到的結果放到了已經有的空間位置上,就OK。(如圖)

但是,将兩個已有的資料加在一起,不能放到原來已經有的位置上,這時候就需要在函數中建立一個新的變量用來接收的這個新得到的值,這時候不能傳回這個新建立的變量,因為局部變量( local變量)在函數結束之後就消失了。

【C++】侯捷C++面向對象進階程式設計(上)

友元friend

【C++】侯捷C++面向對象進階程式設計(上)

同一個class的各個object互為友元

【C++】侯捷C++面向對象進階程式設計(上)

操作符重載operator overloading(成員函數)

操作符重載通過成員函數或者非成員函數實作。

c++中操作符就是一種函數,因為它可以重新定義。

所有的成員函數都帶着一個隐藏dispointer,指向調用者。

傳遞者無需知道接收者是以reference形式接收

【C++】侯捷C++面向對象進階程式設計(上)

操作符重載operator overloading(非成員函數)

為了應對client_使用者的不同用法,我們這裡給出3種寫法。

注意到:運算的數寫的位置不同,所重載的版本不同。

并且這幾個絕對不可return by reference,因為他們傳回的必定是local object,不是指派給了已經存在的空間位置上,而是從這個函數中建立出一個complex,然後将它傳回。

是從哪裡建立的呢?

這裡有個特殊文法:

typename();——建立臨時對象,它的生命到下一行就結束了。

【C++】侯捷C++面向對象進階程式設計(上)

重載傳回值的特殊情況:

注意到連用情況,在本次重載<<運算符中,如果client_user按照标準庫中的cout使用方式連用,那麼我們重載所設定的傳回值就還得是個ostream類型,因為它從左向右運算,完成第一個之後,得到的類型還得是ostream類型才能接受這個<<。

但是,如果client_user不連用,隻是cout<<xxx;那麼本次運算之後的傳回值是什麼就無所謂了,我們可以填個void,并且注意,沒有return。

【C++】侯捷C++面向對象進階程式設計(上)

complex類實作過程

注意:成員函數實作重載,作用在運算符左邊。傳進來的參數是另一個。

簡單實作:

MyComplex.h

#ifndef mycomplex
#define mycomplex
#include<iostream>
using namespace std;

class MyComplex
{
public:
  MyComplex(double r = 0, double i = 0) :re(r), im(i) {}
  double real()const//得到實部
  {
    return re;
  }
  double imag()const//得到虛部
  {
    return im;
  }
  MyComplex& operator += (const MyComplex& c);//一個複數加到另一個複數上

  bool operator == (const MyComplex& c);//判斷兩個複數是否相等
private:
  double re, im;
  //在函數前加friend變為友元函數,寫在這裡,這個函數就能取到這個類的私有資料了。
  
  friend MyComplex& GetBody1(MyComplex* ths, const MyComplex& tempC);
};

//将一個虛數的實部和虛部加到另一個上
inline MyComplex& GetBody1(MyComplex* ths, const MyComplex& tempC)
{
  ths->re += tempC.re;
  ths->im += tempC.im;
  return *ths;
}
//兩個虛部進行相加
/*為什麼要把對兩個複數的運算操作寫成成員函數?
因為可以利用到this指針,書寫簡單*/
inline MyComplex& MyComplex::operator+=(const MyComplex& c1)
{
  return GetBody1(this, c1);//注意到this是個指向調用者的指針,是以上面的GetBody1的參數1類型要寫MyComplex*
}



//重載<<運算符,在螢幕上列印輸出負數
inline ostream& operator << (ostream & os,const MyComplex& c1)
{
  return os << '(' << c1.real() << ',' << c1.imag() << ')';
}

//兩個複數類的相加運算,并且加到一個新的複數類上
inline MyComplex operator + (const  MyComplex& c1,const MyComplex& c2)
{
  return MyComplex(c1.real() + c2.real(), c1.imag() + c2.imag());
}

//一個複數和一個double數進行運算
inline MyComplex operator + (MyComplex& c1, double x)
{
  return  MyComplex(c1.real() + x, c1.imag());
}

//對這個複數進行取反
inline MyComplex operator - (MyComplex& c1)
{
  return MyComplex(-c1.real(), -c1.imag());
}

//判斷兩個複數是否相等
inline bool MyComplex::operator == (const MyComplex& c)
{
  return this->im == c.im && this->re == c.re;
}

//得到一個複數的共轭複數
inline MyComplex GetRatherImag(const MyComplex& c)
{
  return MyComplex(c.real(),-c.imag());
}
  

#endif      

MyComplex.cpp

#include "mycomplex.h"
#include<iostream>
using namespace std;

int main(void)
{
  MyComplex c1(1, 2);
  MyComplex c2(-1, -2);
  MyComplex cTemp1();


  
  cout << c1 << endl;
  cout << c1 + c2 << endl;
  cout << c1 << c2 << endl;
  cout << c1 + 1 << endl;
  cout << -c1 << endl;
  c1 += c2;
  cout << c1 << endl;
  cout << (c1 == c2) << endl;
  
  cout << GetRatherImag(c2) << endl;
  c1 = c2;
  cout << c1 << endl;

  return 0;
}      

class2——string

class帶指針,我們就不能用預設的拷貝構造函數,應該自己寫。

因為拷貝了指針,這兩個指針指向的是同一塊内容空間。

【C++】侯捷C++面向對象進階程式設計(上)

class with pointer members

隻要了類帶着指針,就要寫拷貝構造和拷貝指派,析構函數。——Big Three三個特殊函數。

class裡面有指針,多半是要動态記憶體配置設定。

因為傳遞的是指針,建立的這兩個class中的data就是一個指針,如果就使用編譯器的否拷貝構造函數。

那麼就會使得這兩個指針指向的是同一塊記憶體空間。

并且會導緻另一塊沒有指針指向的記憶體空間引起memory leak(記憶體洩漏)。

——這就是我們所說的淺拷貝。

【C++】侯捷C++面向對象進階程式設計(上)

深&淺拷貝ctor function

引出——深拷貝,我們coder所要寫的這個(copy ctor)拷貝構造函數。

因為它的名字和類名相同,是以他是構造函數,并且它的參數傳遞的是它本身這種類型,是以叫做copy ctor。

拷貝構造應該做什麼?

為傳進來的這個藍本建立足夠的空間。

inline 
String::String(const String& str)
{
    m_data = new char[strlen(str.m_data)+1];
    strcpy(m_data,str.m_data);
}      
String s1("Hello");
//以下兩行代碼意思完全相同
String s2(s1);
String s2 = s1;      

copy assignment operator——拷貝指派函數

将原來的内容清空,開辟一塊與另一塊記憶體一樣大的空間,然後将另一塊的内容複制到這塊來。

self assignment——檢測自我指派

【C++】侯捷C++面向對象進階程式設計(上)

string類的實作過程

(不管什麼函數就都加inline就完事了,編譯器會做決策)

MyString.h

#ifndef myString
#define myString//注意防衛式聲明的名字不能和類名相同!

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<cstring>
using namespace std;

class MyString
{
public:
  MyString(const char* cstr = 0);//接收什麼樣的初值
  //拷貝構造
  MyString(const MyString& str);

  //拷貝指派
  MyString& operator = (const MyString& str); //看函數傳回出來的結果要放到什麼地方去,來決定是否可以Return by reference

  //(指針指向c字元的字元串是C語言風格的字元串)

  //傳回字元串丢到cout
  char* get_str()const
  {
    return m_data;
  }

  //析構函數
  ~MyString();

private:
  char* m_data;//放一根指針,動态配置設定
};

inline MyString::MyString(const char* cstr)
{
  if (cstr != 0)
  {
    m_data = new char[strlen(cstr) + 1];
    strcpy(m_data, cstr);//将傳進來的内容拷貝到新配置設定到的空間中
  }
  else
  {
    //沒有初值也要來一個空間
    //放/0,就是——所謂空字元串也要有一個最後的結束符
    m_data = new char[1];
    *m_data = '\0';
  }
}

inline MyString::~MyString()
{
  delete[] m_data;//arry new 配 arry delete
}

inline MyString::MyString(const MyString& str)
{
  m_data = new char[strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);
}

inline MyString& MyString::operator = (const MyString& str)
{
  //注意參數中的& 和if中str前的& ,他們兩個的意義是不同的。

  //考慮到是否進行自我指派
  //來源端和目的端是否相同

  if (this == &str)//取位址得到的是一根指針
  {
    return *this;
  }


  delete m_data;
  //清理掉之後重新配置設定一個夠大的空間
  m_data = new char[strlen(str.m_data) + 1];
  strcpy(m_data, str.m_data);

  //考慮到連串情況(類似于之前MyComplex中cout<<連用的情況)
  //是以傳回MySring& 
  return *this;
}

#endif // !MyString      

MyString.cpp

#include"MyString.h"
#include<iostream>
using namespace std;
int main(void)
{
  //MyString s1("hello");
  //cout << s1.get_str();
  string stemp = {};
  MyString s2("");
  cout << s2.get_str() << endl;;
  cout << sizeof(s2.get_str()) << endl;
  cout << sizeof(stemp) << endl;
  //cout << "1" << endl;

  MyString s3("在");
  s2 = s3;
  cout << s2.get_str() << endl;


  return 0;
}      

堆、棧、記憶體管理

Stack

是存在于某個作用域的一塊記憶體空間memory space。

調用函數,就會形成一個stack,用來存放它的參數,以及傳回位址。

Stack object的生命周期在作用域結束之後就結束了。 會被自動清理。

Heap

即——System Heap,是指由作業系統提供的一個global記憶體空間,由coder負責配置設定。

complex c1(1,2);complex *p =  new complex(3);      

**Static object **在作用域結束之後仍然存在,直到整個程式結束 ,析構函數才被調用。

New

new完記得Delete。

調用New,先配置設定一塊記憶體空間,然後再調用構造函數。

例如:

調用
MyComplex *pc = new MyComplex(1,2);
之後
我們可以了解為
實際上編譯器轉化為了3條語句
    
    配置設定記憶體-相當于調用malloc(n)
void* temp = operator new(sozeof(MyComplex));
pc = static_cast<MyComplex*>(temp);//轉型
pc->MyComplex::MyComplex(1,2);//調用構造函數      

Delete

**先調用析構函數,再釋放記憶體。**與New相反

delete pc;

Complex::~Complex(pc);
operator delete(pc);
        (即 調用free)      

arry new

arry new一定要搭配arry delete

String *str = new String[3];
...
delete[] p;//調用三次dtor      

否則會造成記憶體洩漏。

注意洩漏的是哪個位置。

【C++】侯捷C++面向對象進階程式設計(上)

static_靜态

C與C++的static有兩種用法:​​面向過程​​​程式設計中的static和​​面向對象程式設計​​​中的static。前者應用于普通變量和函數,不涉及類;後者主要說明static在類中的作用。——​​連結​​

在有的情況,例如銀行的利率,我們就可以将它設定為靜态static類型,因為每個人看到的利率都是一樣的,隻有一份。

靜态函數沒有dispointer,是以它隻能處理靜态的資料。

靜态的資料一定要在class外定義。

class Account
{
public:
  static double m_rate;
  static void set_rate(const double &x)
  {
      m_rate = x;
  }
};
double Account::m_rate = 8.0;      

調用靜态函數有兩種方式

  1. 通過object對象調用
  2. 通過class name調用

有時我們的class隻希望建立一個對象

隻有有人調用到這個函數,這個對象才會誕生。

沒人用,這個唯一的對象就不會産生。only one forever

class A
{
public:
    static A& getInstance();
    setup(){};             
private:
    A();
    A(const A& rhs);
    ...
};
A& A::getInstance()
{
    static A a;
    return a;
}      

cout補充

【C++】侯捷C++面向對象進階程式設計(上)

class template_類模闆

【C++】侯捷C++面向對象進階程式設計(上)

function template_函數模闆

【C++】侯捷C++面向對象進階程式設計(上)

函數模闆不必明确的指出來傳入的類型,編譯器會進行推導。

與運算符重載互相搭配。

(C++标準庫裡的算法都是function template形式)

namespace

namespace std
{
    
}      

使用方式

  1. using directive_打開全部封鎖
  2. using declaration_一條一條聲明
  3. 啥也不寫,每條都得std::xx

class Composition_複合

a has b

(标注庫中的很多容器就是複合)

例如:将deque容器改裝成 queue——adapter_改造

【C++】侯捷C++面向對象進階程式設計(上)

**構造由内而外——**包餃子

析構由内而外——剝桔子

class Delegation_委托

Composition by reference

編譯防火牆

【C++】侯捷C++面向對象進階程式設計(上)

Inheritance_繼承

is a

**構造由内而外——**包餃子

析構由内而外——剝桔子

繼承——子類會有父類的part

主要搭配virtual用

Inheritance with virtual functions

在成員函數前+virtual。

資料和函數都可以被繼承下來,隻不過函數繼承的是調用權。

成員函數分為三種:

  1. non-virtual 函數:你不希望子類(dericed class) 重新定義(override覆寫——之能用在虛函數被重新定義)
  2. virtual函數:你希望被dericed class override,并且它已有預設定義。
  3. pure virtual函數:你希望dericed class一定要override,且它沒有預設定義
【C++】侯捷C++面向對象進階程式設計(上)

子類對象可以調用父類函數

【C++】侯捷C++面向對象進階程式設計(上)

(例如MFC架構)

隻有應用程式本身知道如何讀取自己的檔案(格式)

Inheritance + Composition下的構造和析構

當一個類繼承了另一個類,并且又複合了一個類,那麼他們的構造函數和析構函數的調用順序是什麼樣的呢?

test.h

這裡面的Person3繼承了Person1,複合了Person2

#ifndef Test
#define Test
#include<iostream>
using namespace std;

class Person1
{
public:
  Person1(int r = 0):m_age(r)
  {
    cout << "Person 1的預設構造函數" << endl;
  }
  ~Person1()
  {
    cout << "Person 1的析構函數" << endl;
  }
private:
  int m_age;

};

class Person2
{
public:
  Person2(int r = 0):m_age(r)
  {
    cout << "Person 2的預設構造函數" << endl;
  }
  ~Person2()
  {
    cout << "Person 2的析構函數" << endl;
  }
private:
  int m_age;
};

class Person3:Person1
{
public:
  Person3(int r = 0):m_age(r)
  {
    cout << "Person 3的預設構造函數" << endl;
  }
  ~Person3()
  {
    cout << "Person 3的析構函數" << endl;
  }
private:
  int m_age;
  Person2 p2;
};



#endif // !Test      

test.cpp

#include"test.h"
#include<iostream>
using namespace std;
int main(void)
{
  Person3 testTemp;
  
  return 0;
}      

結果:

【C++】侯捷C++面向對象進階程式設計(上)

如果一個類繼承了一個父類,并且這個父類中複合了一個類呢,那麼他的構造函數和析構函數的調用順序是如何的呢?

這裡的Person1繼承了Person3,Person3複合了Person2

test.h

#ifndef Test
#define Test
#include<iostream>
using namespace std;



class Person2
{
public:
  Person2(int r = 0):m_age(r)
  {
    cout << "Person 2的預設構造函數" << endl;
  }
  ~Person2()
  {
    cout << "Person 2的析構函數" << endl;
  }
private:
  int m_age;
};

class Person3
{
public:
  Person3(int r = 0):m_age(r)
  {
    cout << "Person 3的預設構造函數" << endl;
  }
  ~Person3()
  {
    cout << "Person 3的析構函數" << endl;
  }
private:
  int m_age;
  Person2 p2;
};

class Person1 :public Person3
{
public:
  Person1(int r = 0) :m_age(r)
  {
    cout << "Person 1的預設構造函數" << endl;
  }
  ~Person1()
  {
    cout << "Person 1的析構函數" << endl;
  }
private:
  int m_age;

};

#endif // !Test      

test.cpp

#include"test.h"
#include<iostream>
using namespace std;
int main(void)
{
  Person1 tempTEST;
  return 0;
}      

結果如圖:

【C++】侯捷C++面向對象進階程式設計(上)

Delegation + Inheritance 委托+繼承

【C++】侯捷C++面向對象進階程式設計(上)

(面向對象問題——準備class來解決問題)

Composite

【C++】侯捷C++面向對象進階程式設計(上)

繼續閱讀