04737-C++程序設計

7 minute read

Published:

序言

第一章

C++ 語言特點

  • 可運行多種平台
  • 加入了面向對象;

輸入輸出流

  • P.33
# include <iostream> //輸入輸出流
# include <fstream>  //文件流
# include <string>   // 處理字符串處理函數
# include <cmath>    // 標準數學函數
# include "e:\MYDIR\EX1.h"
using namespace std; // 消除同命引起的歧義
using std::cin;
using std::count;
using std:string;
using std:endl;
count<<"請輸入 \
以空格或ENTER分開"<<endl;
cin>>oneInt1>>twoInt2

類型轉換

  • P.34
# double-> int 下面四個都可以
oneInt2=static_cast<int>(oneDouble);
oneInt2=int(oneDouble);
oneInt2=(int)oneDouble;
oneInt2=oneDouble;
int a=1;
const int *p =&a;
// 去除const int *,轉變為 int *
q=const_cast<int *>p;
*q=30;//此時地址a的值改為30

引用

  • this是指向类或者结构体本身的指针,并且不可以改变。
  • *this就是指向本身的类或结构体的变量。
  • int *p=&a p是指標變量,可以存地址, &a 是a 的地址
  • p是地址, *p是該地址的值
  • int *p=new int()在這段程式中,new 運算子會配置 int 需要的空間,並傳回該空間的位址,可以使用指標 p 來儲存位址
int a1;
// b的類型為int & , 為a的引用,不會為b分配地址
int &b=a;
const int & c=a;
c=2; // error ,常引用不能改變a的值
  • const與指針
int a1=1;
// 值為常量
int const * a4=&a1;
*a4=10; //這會出錯,因為是常量,不能變


// 指針為常量, 不能改地址
int *const a5=&a1;
*a5=10 //這是是正確的;

內聯函數

  • P.44

  • 減少程序代碼, 實現程序代碼的共享, 從而提高程序的開發效率和可維護性
  • 平時調用函數時,需要主調函數的現象和返回地址
  • 編譯器在編譯時並不生成函數調用,內聯函數的調用表達式直接用該內聯函數的函數體進行替換
  • 空間消耗節省時間開銷
  • 代碼少才用
inline 返回值類型 函數名(形參表)
{
  函數體
}

重載函數

  • 指在程序的同一範圍內聲明幾個功能類似的同名函數
  • 參數表中對應的參數類型不同
  • 參數表中參數個數不同
  • 因不知函數輸出是什麽, 所以只有函數輸出是不同類型, 是不能重載
  • 以下函數可以用同一個對象作為實參,所以不能重載
void print(int);
void print(int&); 
  • 函數時進行必要的類型提升
double bigger(double x, double y)
{
  // 內容
}
int x1=10;
float y1=20.0;
bigger(x1,y1);// 這個可以

指針與動態分配內存

  • 使用new進行動態分配
int *q;
int *p;
int i=5;
p=new int[i*20];
p[0]=20;
p[99]=30;
int p=new int[3];
int q=new int;
delete q;
detete []p;
int s=1;
int *r=&s;
delet s // error, 不是動態分配;

string

include <string>
string name[]={"aaaa","bbbb"}
string tmp=name[0]+name[1] //aaaabbbb
bool k=name[0]<name[1] // true
char astr[]="hello";
string tmp=name[0]+astr; //aaaahello
  • find(),size(),empty();
string str="";
if (str.empty())
{
  cout<<"empty"<<endl;
}
str=str.append("abc");//str=abc

string s5="test";

// c_str() 是string 轉去const char *
const char *p= s5.c_str();

char cpyTest[20];
strcpy(cpyTest,s5.c_str())// 將s5轉為const char *

int len=str.length();
int size=str.size();

// find 從0 開始找b這字符串,找到的idx
str.find("b",0) //1;

// insert() 在2位置插入"123"
str.insert(2,"123"); // ab123c

C++程序結構

/*
注 
*/
// 文檔結尾為 .cpp

第二章

結構化程多設計

  • 結構化程多設計是將複雜的功能模塊化繁為簡

面向對象

  • 面向對象技術把問題看成是相互作用的事物的集合,也就是對象的集合, 對象具有兩種特性:
    • 狀態
    • 行為
  • 面向對象的程序設計有以下特點
    • 抽象:將同一類事物的共同特點概括起來
    • 封裝:將對象屬性和操作結合在一起,當作封裝;對象的屬性及實現的細節隱藏起來,公開的竹信息成為與外界交互的接口
    • 繼承:新的類時,以舊的類為基礎。新的類是從舊的類派生而來
    • 多態:不同種類的對象都具有相同的行為,具體行為的實現方式卻有不同
  • 對象是描述客觀事的一個實體

  • 對象的特點
    • 屬性:描述靜態特徵的數據項
    • 操作:描述對象動態特徵:如加薪

  • private:類的成員函數
  • public:任意
  • protected:子類或類的成員函數
  • 類中不能賦初值, 可成為實體時賦初值
  • 在類外聲名的函數用以下方式
 class 類名
 {
   inline 返回值類型 成員函數名(參數列表);
   返回值類型 成員函數名(參數列表);

 }


返回值類型 類名:成員函數名(參數列表)
{
  成員函數的函數體
}
  • 類並不占用空間
  • 實例化對象時,只會分配成員變量的空間, 而不會分配成員函數的空間
  • 類的成員函數不一定由類調用
  • 普通的成員函數由類的對象調用,類的靜態成員函數由類來調用
  • 設置私有成員的機制叫隱藏,隱藏的一個目的就是強制對私有成員變量的訪問一定要通過公有成員函數進行
  • 在設計類時,應盡量隱藏使用者不需要知道的實現細節,只留下必要的接口
Student ss;
Student *sp=&ss;
Student &sy=ss;

sp->printStudent();
sy.printStudent();

標識符的作用域與可見性

  • 在聲明函數原型時形參的作用范圍就是函數原型的作用域
  • 程序中用一對大括號括起來的一段程序稱為塊
  • 標識符要聲明在前,引用在後
  • 同一作用域中,不能聲明同名的標識符
  • 在內層聲明與外層同名的標識符,則在內層中會隐藏外層的標識符,稱為隱藏規則

第三章

構造函數

myDate::myDate(int y,int m,int d)
{
  year=y;
  month=m;
  day=d;
}

myDate::myDate:year(y),month(m),day(d){}
  • 沒有加括號是允許的, 系統只為成員變量分配內存空間,但不進行內的初始化,成員變量的值是隨機值
  • 加括號,分配內存空間時, 初始值為0;

析構函數

  • 沒有參數,沒有返回植
  • 有且只有一個
  • 不會有重載的析構函數
  • 析構函數的調用執行順序與構造函數剛好相反
myDate::myDate()::year(1970),month(1),day(1)
{
  
}
Student:Student():name("a"),birthday(MyDate())
  • 以上是執行順序myDate(),Student(),~Student,~myDate()

靜態變量

  • 函數:
    • static 修飾的全局變量是靜態全局變量,僅在定義該變量的源文件內有效, 項目中的其化源文件中不能使用它
    • 在函體中可見,程序結束時釋放占用的空間,所以若在函數內定義, 出了函數就消失
    • 在函數有static int fs=1,下次再進入該函數會繼續叠加
    • 靜態成員都只有一份保存在公用內存中
    • 必須在類體外定義靜態成員變量的初值
    • 類的靜態成員函數沒有this指針
    • 靜態成員函數井不作用某個具體對象,不會改變對象中非靜態成員變量的值
    • 不能在靜態成員函數內訪問非靜態的成員
類型 類名::靜態成員變量=初值;

變量和對象的生存期和作用域

  • new , 直到delete運算符釋放或程序結束
  • 不同作用域內的同名標識符互不沖突
  • 程序作用域也稱為多文件作用域,
  • extern 說明外部變量和外部函數
  • 進入子范圍后,將屏蔽其父范圍的名字

常量成員和常引用成員

  • 常量成員變量就不允許修改,只可讀其值
  • 對於常量對象,只能調用常用函數,
  • 常量對象一旦初始化后, 其值就再也不能更改
  • 不允許通過常量調用普通成員函數
  • 普通成員對象既可以調用常量成員函數, 也可以調用普通成員函數
  • 靜態成員函數井不作用某個具體對象,不會改變對象中非靜態成員變量的值,所以常數成員函數和非常量成員函數內部都可以調用靜態成員變量的值
  • const成員變量必須在構造函數的初始化列表中進行初始化
const 類據類型 常量名=表達式;
類型說明符 函數名(參數表) const;

成員對象和封閉類

  • 一個類的成員變量如果是另一個類的對象, 則該成員變量稱為”成員對象”,有包括關係,包含成員對象的類叫作封閉類,即父稱為封閉類
  • 先執行子類的構造函數

友元

  • 友元函數的目的是能夠訪問類的私有成員
  • 有助數據共享,提高程序執行的效率
  • 類外不能訪問類的私有成員變量,須透過公有成員
  • 友元函數不是類的成員函數
  • 友元函數體中訪問對象成員,必須使用 “對象名.對象成員名”
  • 調用友元函數時不通過類對象來調用
  • 調用成員函數時, 需要通過對象來實現
  • 友元類
    • B是A的友元類,不等於A是B的友員類
    • B是A的友元類,C是B的友元類,不等於C是A的友員類
    • B是A的友元類,即B中所有函數都是A的友元函數
class A{

friend 返回值類型 函數名(參數表);
friend 返回值類型 A::A的成員函數名(參數表);
}

this 指針

  • 當調用一個成員函數時,系統自動向它傳遞一個隱含的參數,該參數是一個指向調用該函數的對象指針,稱為this 指針
myDate::myDate(int year,int month, int day)
{
  this->year=year;
  this->month=month;
  this->day=day;
}

myComplex::AddRealOne()
{
  this->real++;
  return *this; //返回對象本身
}

第四章

  • 運算符可以被重載為全局函數,也可以重載為類的成員函數
  • 若重載為全局,則對於二元運算符,需要二個參數
  • 若重載為成員,則對於二元運算符,需要一個參數
  • 有時重載為成員函數滿足不了需要,就重載為該類的友元函數
class mycomplex
{
  public :
  mycomplex operator-(const myComplex &c)
  {
    // 函數內容
  }

  // 友元時
  friend mycomplex operator+(const myComplex &c)
  {
    // 函數內容
  }
};

//在外面才定義時
mycomplex mycomplex::openator-(const myComplex &c)
{
  // 函數內容
}

mycomplex operator+(const myComplex &c)
{
  // 函數內容
}
  • 重載時規則
    • 重載后應符合原有的用法習慣
    • 不能改變運算符原有的語義,包括運算符的優先級和結合性
    • 不能改變操作符操作的個數和語法結構
    • 不能創新的運算符
    • (),[],->, =,只能重載為成員函數,不能全局
    • 不能改變運算符用於基本數據類型對象的含義
s1=s2;
//等價於
s1.operator=(s2);

深淺拷貝

  • 淺拷貝, 本來int* p已生成新的內存, 但是p=A.p;賦值後p 都指向A.p相同地址
class A
{
  public:
    int *p;
    A(const A &aa)
    {
      p=A.p;
    }
}
  • 深拷貝
class A
{
  public:
    int *p;
    A(const A &aa)
    {
        *p=*aa.p;
    }
    A &operation=(const A &aa)
{
      delete this->p;
      this->p=new int(*aa.p);
      return *this;
}
}


重載流插入和流提取

class complex
{
  public:
  ostream &operator<<(ostream & output )
  {
      //內容
  }

  friend istream & operator>>(istream & input , complex & c1)
  {
    //內容
  }
}

ostream & complex:: operator<<(ostream & output )
{
  //內容
}
instream & operaotor>>(istream & input , complex & c1)
{
  //內容
}

自增、自減運算符

class cdemo
{
  public:
  cdemo &operator++(); //++x
  cdemo operator++(int); //x++
  friend cdemo & operator-- (cdemo &); //++x
  friend cdemo operator-- (cdemo & ,int ); //x++
};

// 成員函數在外面定義
cdemo & cdemo::operator++()
{
  //內容
}
cdemo  cdemo::operator++( int n)
{
  //內容
}

//友元在外面定義
cdemo & operator--(cdemo & c)
{
  //內容
}
cdemo operator-- ( cdemo &c , int n)
{
  //內容
}

第五章

  • 基類有友元類或友元函數,派生類不會因繼承關係而也有此友元類或友元函數。
  • 基類是某類的友元, 是會被繼承
  • 靜態變量是會被繼承
  • 派生類在內層,基類在外層,派生成員隱藏了外層同名成員
Class CB
{
  public: 
    int a;
    CB(int x)
    {
      a=x
    } 
}


Class CD:public CB
{
  public:
    int a;
    CD(int x, int y):CB(x)
    {
      a=y;
    } 

}

CB b=CB(12);
CD d=CD(48,999);
// d.a=999,d.CB::a=48
  • 除基類的構造函數和析構函數外,派生類可以繼承基類的全部成員變量和成員函數

公有派生,私有派生和保護派生

  • 公有
    • 派生類的對象可以賦值給基類對象,基類對象=派生類對象
    • 派生類對象可以用來初始化基類引用
    • 派生類對象地址可以賦值給基類指針
  • 私有
    • 當繼承方式是私有派生,基類的公有成員和私有成員都以私有成員身份出現
    • 不加說明, 默認繼承方法是private
  • 保護
    • 當繼承方式是私保護派生,基類的公有成員和私有成員都以保護成員身份出現

派生類的構造函數和析構函數

  • 派生類都會使用無參對基類進行構造函數, 再進行派生類的構造函數
  • 次序
    • 調用基類構造函數,調用順序按照它們被繼承時聲明的順序, 從左到右
    • 對派生類新增的成員變量初始化,調用順序按照他們在類中聲明的順序
    • 執行派生類的構造函數中的內容

類之間的關係

  • 已有類編寫新的類有兩種方式:繼承和組合
  • has a ,即一個類A以另一類的對象B作為成員變量,包含關係,A稱封閉類,B稱內嵌類
  • 繼承: is a, 基類集合包含派生類集合,具有傳遞性

第六章

多態與虛函數

  • P.245
  • 多態為了接口复用
  • 多態前不能提前知曉指針指向,函數調用與代碼入口地址需要綁定時才能確定,為動態綁定
  • 早綁定是靜態綁定

滿足動態綁定的2個條件

  • 必須聲明虛函數
  • 通過基類類型指針或引用調用虛函數

虛函數:

  • 通常虛函數不是內聯函數
  • 實現多態是派生和基類的同名函數,參數列表,返回值都一樣
  • 基類定義了虛,派生也是虛
  • 靜態成員函數和友元函數不能為虛
  • 虛函數定義在類外,只而聲明時加 virtal ,定義時不用
  • 構造函數不能是虛函數,operator=最好也不要是虛函數
  • 構造和析構最好不要調用虛函數

不能聲明為虛函數:

  • 全局函數不能是虛
  • 構造不能是虛
  • 靜態
  • 內聯成員函數
  • 友元

多態的使用

class cbase()
{
  public:
  void fun1(){
    fun2();
    fun3();
  };
  virtual fun2(){};
  void fun3(){};
}

class CDerived:public cbase
{
  public :
    virtual fun2(){};
    void fun3(){};
}

int maint()
{
  Cdervied d;
  d.fun1();
  //結果是運行cbase.fun1()
  // CDervied.fun2
  // CBase.fun3
}

  • 在構造函數和析構函數中使用虛函數, 因為在編譯時就決定使用哪個版本, 所以不會有虛函數的特徵,在B類中使用某函數, 就在B的函數, 在B沒有, 就找B的基類A
  • 所以在構造函數和析構函數中使用虛函數不會實現多態
  • 多態必須滿足的修件: 使用基類指針或引用來調用基類中聲明的虛函數

虛析構函數

  • p.263
  • 沒有返回值
  • 沒有參數
  • 對象消忘時實現多態
int main()
{
  ABase *p=new Derived();
  delete p; return 0;
}
  • 若基類的析構函數是虛,delete p就是先執行Derived析構函數 , 後執行base析構函數
  • 若基類的析構函數不是虛,delete p就是執行base析構函數

純虛函數

  • p.266
  • 不可創純虛函數的對象,但可引用或指針。
virtual 函數類型 函數名(參數表)=0;
  • 是虛函數就是

虛基類

  • A是基
  • B,C是A的派生
  • D,是B和C的派生
  • 排除二義性
class B :virtual public A
{};
class C : virtual public A
{};
class D:public B,public C
{};

第七章

  • P.277
  • iomanip包括了格式化I/O帶參數操縱符
  • cerr非緩沖輸出流
  • clog緩沖輸出流
if (cin.eof()){}

控制I/O操縱符

  • iostream
    • endl:新行符,清空流(O)
    • ends:(O)
    • flush:清空緩沖區(O)
    • dec*:10進制(I/O)
    • hex:16進制(I/O)
    • oct:8進制(I/O)
    • ws:提取空白符(O)
  • iomainip
    • p.284
    • fixed:小數記數
    • left:向左靠
    • right*:向右靠
    • scientific:科學記數
    • setbase():數字的基
    • setw():輸出的寬
    • setfill():空的位置的填滿符
    • setprecision:小數或科學的小數後準確度,若不是fixed或scientific,就是從頭的數字數起,而不是小數後數起
double x=1.23456789
cout<<setprecision(x); // 1.2346
cout<<fixed<<setprecision(x); // 1.23457
cout<<scientific<<setprecision(x); // 1.23457e+000
  • 標志字
    • setiosflags 是持久的,直到resetiosflag
cout<<setiosflags(ios::scienfitic|ios::showpos);
cout<<resetiosflags(ios::scienfitic)

cout

  • P.289
cout.fill("*");
cout.width();
cout.precision();
cout.setf(ios::scienfitic);
cout.unsetf(ios::scienfitic);
  • 字符插入
ostream &put(char c);
ostream & write(const char *pch, int nCount); // pch 指向長度為nCount的字符

cin

while(ch=cin.get()!=EOF)
{ }
istream & getline(char *buf ,int bufSize) // 讀bufSize-1個字符到緩沖區或讀到"\n",結尾自動加"\0"
istream & getline(char *buf, int bufSize,char delim);// 讀bufSize-1個字符到緩沖區或讀到delim,結尾自動加"\0"
istream & ignore(int n=1, int delim=EOF)// 跳過頭n個字符或delim前的字符
bool eof()
peek() //返回輸入流的當前字符,但不會從輸入流中取出,即看一眼
cin.getline();
cin.eof();
cin.ignore();
cin.peek();

第八章

p.305

#讀入
int main()
{
  int a,b,c;
  ifstream  inFile;
  inFile.open("data.txt" ,ios::in);
  while(inFile>>a>>b>>c)
  {

  }
  inFile.close();
  return 0;
}

寫出
#int main()
{
  int a,b,c;
  ofstream  onFile;
  onFile.open("data.txt" ,ios::out);
  onFile<<a<<b<<endl;
  onFIle.close();
  return 0;
}

二進制

// buffer 指向長度nCount寫入
ostream &write(char * buffer ,int nCount);
// buffer 指向長度nCount讀入
istream &read(char * buffer ,int nCount);
Cstudent stu; // a class
while (inFile.read( (char *) &stu , sizeof (stu)) )
{
` cout<<stu.id<<stu.name<<endl;
}
// read 成功讀取的字節數
int gcount;
//每次寫入一個字節
ostream & put(char ch ) ;
// 每次取一個字節
instream &get (char *pch , int nCount, char delim="lim"  );

隨機存取

// 將指針設置為pos, seek_dir方向移動pos個字節
istream & seekg(long pos ios::seek_dir dir);
long tellg();
ostream & seekp(long pos ios::seek_dir dir);
long tellp();
inFile.clear();

第九章

template <typename T>
T abs(Tx){}
template <class T1>
void Swap(T &x, T&y)
{
  T tmp=x;
  x=y;
  y=tmp;
}

int main()
{
  int n=5;
  int m=4;
  cout<<abs(5)<<end;
  cout<<swap<int>(m,n)<<endl;
}
  • 函數模板的使用次序
    • 先找到參數匹配的普通函數
    • 再找到參數匹配的模板函數
    • 實參自動轉換匹配的普通函數
    • 以上都找不到,報錯

類模板

template <class T>
class TestClass
{
  public:
  T buffer[10];
  T getData[int j];
};
template <class T>
T TestClass <T>::getData(int j)
{
  return *(buffer+j);
}