30.C++復習篇


 

本章學習內容:

  • 1.const
  • 2.指針const
  • 3.inline內聯函數
  • 4.函數重載
  • 5.extern “C”
  • 6.new/delete聲明與釋放
  • 7.namespace命名空間
  • 8.C++中的4種轉換
  • 9.拷貝構造函數
  • 10.構造函數初始化列表
  • 11.析構函數
  • 12.const成員函數
  • 13.const對象
  • 14.棧、堆、靜態存儲區的區別
  • 15.靜態成員變量/靜態成員函數
  • 16.友元friend
  • 17.operator操作符重載函數
  • 18. 通過()操作符重載實現:函數對象
  • 19. 操作符重載實現:類型轉換函數
  • 20.explicit顯式調用(用來阻止隱式轉換)
  • 21.父類和子類中的同名成員/函數
  • 22.子類對象初始化父類對象
  • 23.父類對象初始化子類對象
  • 24.純虛函數vertual
  • 25.泛型函數模板(兼容不同類型)
  • 26.泛型類模板(兼容不同類型)
  • 27.數值型函數模板和數值型類模板(兼容不同數值)
  • 28.C++智能指針
  • 29.Qt中的智能指針

 

1.const

const和define宏區別

  • const常量:       由編譯器處理,它會對const常量進行類型檢查和作用域檢查
  • define宏定義:  由預處理器處理,直接進行文本替換,不會進行各種檢查

const在C++中為真正常量.示例:

   const int c = 0;             //const局部變量

    int* p = (int*)&c;        //會給p重新分配空間,而c還是處於常量符號表中

    *p = 5;                  //此時修改的值是新的地址上,對於c而言,依舊為0

    printf("c = %d,*p=%d\n", c,*p);  //打印: c = 0, *p=5

 

2.指針const
1) 底層const(位於*左側)
const int *p : const修飾*p為常量,也就是說該指針指向的對象內容是個常量,只能改變指向的地址.但是可以通過其他方式修改對象內容
例如:

int a=1,a=2; 
const int *p = &a;
*p = 2;         //error, 不能直接修改
a=2;          //right,通過對象本身修改內容
*p = &b;        //right,可以指向其它地址

2) 頂層const(位於*右側)
int * const p : const修飾指針p是個常量,也就是說p指向的地址是個常量
例如:

int a=1,a=2;
int * const p = &a;
p = &b;            //error,p指向的地址是常量,永遠為a地址,不能修改

注意:頂層const變量可以替代mutable變量

 

3.inline內聯函數

示例如下:

inline int MAX(int a, int b)
{
  return a > b ? a : b ;
}

 

  • 普通函數:每次調用前,CPU都會保存現場(入棧),調用完后還要恢復現場(出棧)等額外開銷.
  • 內聯函數:就會在每次調用的地方,將內聯函數里的代碼段”內聯地”展開,所以省去了額外的開銷

注意:當內聯函數里的代碼過多,且流程復雜時,編譯器可能會拒絕該函數的內聯請求,從而變成普通函數


4.函數重載
參數表不同主要有以下幾種

  • 1) 參數個數不同
  • 2) 參數類型不同
  • 3) 參數順序不同

注意:

  • 重載函數需要避免使用參數默認值
  • 調用重載函數時,只會匹配函數參數表,與函數返回值無關
  • 函數重載必須發生在同一個作用域
  • 重載函數的入口地址,不能直接通過函數名來獲取


5.extern “C”
可以實現調用C庫代碼.
示例:

#ifdef __cplusplus
extern "C"       //通過C方式來編譯add.h,也就是add()函數
{
#include "add.h"
}
#endif

 

6.new/delete聲明與釋放

示例如下:

int *p = new int();  //默認值為0

int *p1= new int(1); //動態分配一個int空間給p1,並賦值為1

float *p2=new float(2.0f); //2.0后面加f,表示2.0是個float類型

int *p3 = new int; //默認值為隨機值

string *p4 = new string[10];

delete p;
delete p1;
delete p2;
delete p3;
delete[] p4;

注意:
• 釋放數組的空間時,必須使用delete[],而不是delete,避免內存泄漏

 

7.namespace命名空間

示例:

#include <stdio.h>

namespace First //定義First命名空間
{
  int i = 0;
}

namespace Second //定義Second命名空間
{
  int i = 1;namespace Internal   //在Second里,再次定義一個Internal空間(實現嵌套)
  {
    struct Position
    {
      int x;
      int y;
    };
  }
}

int main()
{
  using namespace First;         //使用First整個命名空間,成為該main()的默認空間
  using Second::Internal::Position; //使用Second->Internal空間里的Position結構體

  printf("First::i = %d\n", i);

  printf("Second::i = %d\n", Second::i);

  Position p = {2, 3}; 
  printf("p.x = %d\n", p.x);
  printf("p.y = %d\n", p.y);

  return 0;
}

輸出結果:

First::i = 0
Second::i = 1
p.x = 2
p.y = 3

 


8.C++中的4種轉換
static_cast(靜態類型轉換)
用於變量和對象之間的轉換,比如(bool,char,int等)
用於有繼承關系的類對象指針轉換,可以通過父類對象去初始化子類對象(注意只能初始化父類的那部分)

const_cast(去常類型轉換)
常用於去除const類對象的只讀屬性
強制轉換的類型必須是指針*或者引用&

示例-去除const對象的只讀屬性:

class Test 
{
public:
        int mval; 
        Test():mval(10)
        {
            
        }
}; 

int main()
{
     const Test n1;
     
     //n1.mval = 100;  //error,不能直接修改常量對象的成員
     
     Test *n2 =  const_cast<Test *>(&n1);    //通過指針*轉換 
     Test &n3 =  const_cast<Test &>(n1);    //通過引用&轉換 
      
     n2->mval = 20;
     cout<<n1.mval<<endl;        //打印20
     
     n3.mval = 30;
     cout<<n1.mval<<endl;        //打印30
}   

dynamic_cast(動態類型轉換)
只能用在有虛函數的類中,一般在多重繼承下用的比較多,比如:

class BaseA
{
public:
  virtual void funcA()
  {
    cout<<"BaseA: funcA()"<<endl;
  }
};

class BaseB
{
public:
  virtual void funcB()
  {
    cout<<"BaseB: funcB()"<<endl;
  }
};

class Derived : public BaseA,public BaseB
{ 
};

int main()
{
  Derived d;
  BaseA *pa=&d; 
  
  pa->funcA(); //打印 BaseA: funcA()

  /*通過強制轉換執行*/
  BaseB *pb=(BaseB *)pa; 
  pb->funcB(); //還是打印BaseA: funcA(), 因為pb還是指向pa,執行的還是pa的虛函數表 


  /*通過dynamic_cast執行*/
  pb = dynamic_cast<BaseB *>(pa); 
  pb->funcB(); //打印 BaseB: funcB()
  //編譯器會去檢測pa所在的地址,發現有多個虛函數表,然后根據 <BaseB *>來修正指針pb 
  return 0; 
}

 

reinterpret_cast(解讀類型轉換)
對要轉換的數據重新進行解讀,適用於所有指針的強制轉換

 

9.拷貝構造函數
一般用於當類對象中有成員指針時,才會自己寫拷貝構造函數,因為編譯器自帶的默認拷貝構造函數只支持淺拷貝

class Test
{
   //... ...
    Test(const Test& t)
    {
      //copy... ...
    }
};

 

10.構造函數初始化列表

  • 當類中有const成員變量時,則必須要用初始化列表進行初始化.
  • 對於其它普通變量如果不初始化的話則為隨機值.
  • 初始化列表位於構造函數名右側,以一個冒號開始,接着便是需要初始化的變量,以逗號隔開

 示例如下:

class Example
{
private:
    int i;
    float j;
    const int ci;
    int *p;
public:
     Test(): j(1.5),i(2),ci(10),p(new int(3))    //初始化i=2,j=1.5,ci=10 *p=3
    {
    }
};

 


11.析構函數
注意:

  • 在類里,當定義了析構函數,編譯器就不會提供默認的構造函數了,所以還要自己定義一個構造函數
  • 使用new創建的對象變量,在不使用時,需要使用delete,才能調用析構函數

構造函數的調用順序

  • 1. 首先判斷父類是否存在,若存在則調用父類構造函數
  • 2. 其次判斷該對象的是否有類成員,若有則調用類成員的構造函數(調用順序按照聲明的順序來構造)
  • 3. 最后調用對象本身的構造函數

 

12.const成員函數

  • cosnt成員函數里只能調用const成員函數
  • const成員函數中不能直接修改成員變量的值
  • 只有對mutable成員變量或者頂層const成員是可以修改的
  • 如果用const修飾的函數,那么該函數一定是類的成員函數

13.const對象

  • const對象的成員變量不允許被改變,
  • const對象只能調用const成員函數,而非const對象可以訪問const成員函數
  • const對象是編譯階段的概念,運行時無效
  • const對象可以通過const_cast強制轉換來實現改變其中成員變量的值

14.棧、堆、靜態存儲區的區別

用來存放函數里的局部變量,當調用某個函數時(執行某個代碼段),會將該函數的變量(從數據段讀出)入棧,然后退出函數的時候,會將該局部變量出棧進行銷毀.
一般如果局部變量未初始化的話,都是隨機值

堆由程序員分配釋放new/delete,所以需要注意內存泄漏問題
一般new分配的對象變量,其成員都是隨機值
靜態存儲區
用來存放全局變量,一直會存在的,一般編譯器為自動將未賦值的全局變量進行一次清0

15.靜態成員變量/靜態成員函數

  •  在類里定義時直接通過static關鍵字修飾
  •  靜態成員變量需要在類外單獨分配空間,而靜態成員函數則不需要
  •  靜態成員變量在程序內部位於靜態存儲區
  •  對於public公有的靜態成員變量/函數時,可以直接通過類名進行直接訪問
  •  靜態成員函數中不能訪問非靜態成員變量,因為它屬於整個類的,沒有隱含this指針

示例如下:

class Test{

private:
  static int mval;

public: 
  Test()
  {
    print(); 
  } 

  static int print()    //靜態成員函數是存在代碼段中,所以不在類外定義也可以 
  {
    cout<<"mval="<<mval<<endl; 
  } 
}; 
int Test::mval=4; //靜態成員變量存在靜態存儲區中,所以需要在類外定義 int main() {   Test::print(); //通過類名直接訪問靜態成員函數,打印: mval=4 }

 


16.友元friend

  • 友元的好處在於,方便快捷.可以通過friend函數來訪問這個類的私有成員
  • 友元的壞處在於,破壞了面向對象的封裝性,在現代已經逐漸被遺棄

示例:

#include "stdio.h"

class Test{
private:
  static int n;
  int x;
  int y;

public:
  Test(int x,int y)
  {
    this->x = x;
    this->y = y;
  } 

  friend void f_func(const Test& t); //聲明Test的友元是f_func()函數

};

int Test::n = 3; 

void f_func(const Test& t)
{
  printf("t.x=%d\n",t.x); 
  printf("t.y=%d\n",t.y);
  printf("t.n=%d\n",t.n); //訪問私有靜態成員變量
}

int main()
{
  Test t1(1,2);
  f_func(t1);
  return 0;
} 

 

17.operator操作符重載函數

使'+,-,*,/'等操作符擁有了重載能力,能夠實現對象之間的操作,而不再單純的普通變量之間的操作了.

示例如下,實現一個加法類:

class Add
{
    double mval;
public:
    explicit Add(double t=0)
    {
        mval =  t; 
    }
     
    Add& operator +(const Add& t) //實現相同類對象相加
    {
         this->mval += t.mval;
         cout<<"operator +(const Add& t)"<<endl;
         return *this;            //返回該對象,表示可以重復使用 
    }
Add
& operator +(int i) //實現int型對象相加 { this->mval += i; cout<<"operator +(int i)"<<endl; return *this; } Add& operator +(double d) //實現double型對象相加 { this->mval += d; cout<<"operator +(double d)"<<endl; return *this; } Add& operator = (const Add& t) //重載賦值操作符 { cout<<"operator =(const Add& t)"<<endl; if(this!=&t) { mval = t.mval; } return *this; } double val() { return mval; } };
int main() { Add a1(11.5); Add a2(1.25); a1=a1+a2; //相當於調用兩步: a1.operator =(a1.operator +(a2)); cout<< a1.val() <<endl; }

運行打印:


18.通過()操作符重載實現:函數對象

  • 函數對象是指該對象具備函數的行為
  • 函數對象,是通過()調用操作符聲明得到的,然后便能通過函數方式來調用該對象了.
  • ()調用操作符可以定義不同參數的多個重載函數
  • ()調用操作符只能通過類的成員函數重載(不能通過全局函數)

示例:

class Test{
public:
  void operator () (void) //通過()重載操作符,來使對象具備函數的行為
  {
    cout<<"hello"<<endl;
  }
};

int main()
{
  Test t;
  t(); //來調用t這個函數對象打印"hello"
} 

PS:好處在於可以封裝自己的成員以及其它函數,所以能夠更好的面向對象.

19.操作符重載實現:類型轉換函數
示例如下:

class Test{

  int mValue;

public:
  Test(int i=0)
  {
    mValue=i;
  }

  operator int()   //重載int類型
  {
    return mValue;
  }
};

int main()
{
  Test t(1000);
  int i=t;     //等價於: i=t.operator int();
  cout<<i<<endl; //i=1000
}

 

20.explicit顯式調用(用來阻止隱式轉換)
示例:

class Test{
public:
  explicit Test(unsigned int i)
  {
    cout<<"unsigned i= "<<i<<endl;
  } 
};

int main()
{ 
  short num=3;
  //Test t1=num; //Error,因為explicit阻止short類型 轉換為unsigned int 類型

  /*只能有以下3個方法實現*/
  Test t2=(Test)num;         //C方式強制轉換,不推薦
  Test t3=static_cast<Test>(num); //C++方式強制轉換
  Test t4(num);            //手工調用構造函數
  return 0;
}

 

21.父類和子類中的同名成員/函數

  • 子類可以定義父類中的同名成員和同名函數
  • 子類中的成員變量和函數將會隱藏父類的同名成員變量和函數
  • 父類中的同名成員變量和函數依然存在子類中
  • 通過作用域分辨符(::)才可以訪問父類中的同名成員變量和函數

示例1-通過子類訪問父類同名函數和同名成員:

class Parent{

public:

  int mval;

  Parent():mval(100)
  { }

  void print()
  {
  cout<<"Parent: mval="<<mval<<endl;
  }

};

class Child :public Parent
{
  public:
  int mval;

  Child():mval(20)
  { }

  void print()
  {
  cout<<"Child: mval="<<mval<<endl;
  }    
};


int main()
{
  Child c;
  c.Parent::print();          //調用父類的同名成員函數 
  cout<<c.mval<<endl;
  cout<<c.Parent::mval<<endl;    //打印父類的同名成員變量
}

 

22.子類對象初始化父類對象
以上示例的Parent類Child類為例,在編譯器中,可以將子類對象退化為父類對象,從而實現子類來初始化父類,比如:

Parent p1(Child());    //Child()構造函數會返回一個臨時對象,從而通過子類初始化父類
Child c;
Parent & p2 = c ;    //定義p2是C對象的別名

23.父類對象初始化子類對象
只能使用static_cast或者C方式轉換,以上示例的Parent類和Child類為例:

Parent p;
Child *c = static_cast<Child *>(&p);

 

24.純虛函數vertual

  • 在父類中用virtual聲明的成員函數便為虛函數
  • 虛函數的作用在於,能夠正確調用某個同名函數是哪個類的對象
  • 比如:當某個子類被強制轉換為父類時,則父類的虛函數也會被替代為子類的,從而實現程序靈活性

一個典型的示例,如下所示:

class Base    //父類
{
public:
  virtual void func()    //聲明func為虛函數
  {
    cout<<"Base: func()"<<endl;
  }
};

class BaseA : public Base //子類A
{
public:
  void func()
  {
    cout<<"BaseA: funcA()"<<endl;
  }
};

class BaseB : public Base //子類B
{
public:
  void func()
  {
    cout<<"BaseB: funcB()"<<endl;
  }
};

void print(class Base& b)
{
  b.func();
}

int main()
{
  BaseA bA;
  BaseB bB;
  print(bA);    
  print(bB);
  return 0; 
}

運行打印:

如上圖可以看到,我們以print(bA)為例:
再調用print()函數時,會將BaseA bA轉換為父類Base,由於父類Base有個func()虛函數,所以會被動態替換為bA子類的func()函數.所以會打印funcA()

如果將上面代碼virtual void func()改為void func()重新編譯運行后,打印:

如上圖可以看到,沒有虛函數后,整個代碼都變得沒有靈活性,不適合類的擴展.

 

PS:在QT中,virtual用的非常多,比如QWidget的showEvent函數:

virtual void    showEvent ( QShowEvent * event );

假如我們需要在窗口顯示時加點特效時,只需要重寫它即可,而QT庫只需要根據vertual特性來自動調用我們重寫的函數,非常靈活.

 

25.泛型函數模板(兼容不同類型)


函數模板是C++中重要的代碼復用方式, 可通過不同類型進行調用

  • 通過template關鍵字來聲明使用模板
  • 通過typename關鍵字來定義模板類型

示例:

template <typename T> //聲明使用模板,並定義T是一個模板類型
void Swap(T& a, T& b) //緊接着使用T
{
  T c = a;
  a = b;
  b = c;
} 
int main() {   int a=0;   int b=1;   Swap(a,b);       //自動調用,編譯器根據a和b的類型來推導   float c=0;   float d=1;   Swap<float>(c,d); //顯示調用,指定T是float類型 }

為什么函數模板能夠執行不同的類型參數?
答:

  • 其實編譯器對函數模板進行了兩次編譯
  • 第一次編譯時,首先去檢查函數模板本身有沒有語法錯誤
  • 第二次編譯時,會去找調用函數模板的代碼,然后通過代碼的真正參數,來生成真正的函數。
  • 所以函數模板,其實只是一個模具,當我們調用它時,編譯器就會給我們生成真正的函數.

函數模板也支持多參數,示例如下(如果定義了返回值模板,則必須要顯示指定返回值類型,因為編譯器不知道到底返回什么類型):

#include <iostream>

using namespace std;

template<typename T1,typename T2,typename T3> 
T1 Add(T2 a,T3 b)
{
return static_cast<T1>(a+b); 
}

int main()
{
// int a = add(1,1.5);       //該行編譯出錯,沒有指定返回值類型
int a = Add<int>(1,1.5);      //指定T1為int形    
cout<<a<<endl;           //打印2

float b = Add<float,int,float>(1,1.5); //指定T1,T2,T3類型
cout<<b<<endl;           //2.5

return 0;
}

 

26.泛型類模板(兼容不同類型)
類模板和函數模板一樣,都是進行2次編譯,需要注意的是定義對象必須顯示指定所有類型
示例:

template<typename t1,typename t2,typename t3>
class Operator{

public:
  t1 add(t2 num1,t3 num2)
  {
    return num1+num2;
  }
};
int main() {   Operator<float,int,float>t;   cout<<t.add(11,11.5)<<endl; //11+11.5 = 22.5   return 0; }

 

27.數值型函數模板和數值型類模板(兼容不同數值)
數值型和泛型類似,但是數值型模板必須在編譯時被唯一確定

示例1-數值型函數模板:

template <typename T,int N >    //定義一個泛型值T,還有個int型的數值 
void func()
{
  T arr[N];    //使用模板參數T和N定義局部數組 
}

int main()
{
  func<int,10>(); //相當於實現 int arr[10] 
}

示例2-數值型類模板(實現1+2+3+....+N值):

template < int N >
class Sum
{
public:
  static const int VALUE = Sum<N-1>::VALUE + N; //通過Sum<N-1>::VALUE實現遞歸調用,並返回該臨時對象 
};

template < >          //完全特化,因為我們知道N為1,所以不需要寫< int N >    
class Sum < 1 >        //重載Sum類(類似於函數重載),當N==1時調用該類 
{
public:
  static const int VALUE = 1;
};

int main()
{
  cout << "1 + 2 + 3 + ... + 10 = " << Sum<10>::VALUE << endl;
  cout << "1 + 2 + 3 + ... + 100 = " << Sum<100>::VALUE << endl;
  return 0;
}

 

28.C++智能指針
頭文件<memory>
1)auto_ptr

  • 生命周期結束時,自動摧毀指向的內存空間
  • 不能指向堆數組(因為auto_ptr的析構函數刪除指針用的是delete,而不是delete[])
  • auto_ptr的構造函數為explicit類型,所以只能顯示初始化
  • 提供get()成員函數,可以用來查看類里的指針地址
  • 一個堆空間永遠只屬於一個對象(比如auto_ptr被拷貝/賦值,則自身的指針指向的地址會被搶占)

示例如下:

#include <iostream>
#include <memory>

using namespace std;

class Test{
  int mvalue;
public:
  Test(int i=0)
  {
    mvalue = i ;
    cout<< "Test("<<mvalue<<")"<<endl;    
  }
  ~Test()
  {
    cout<< "~Test("<<mvalue<<")"<<endl;    
  }
};

int main()
{
  auto_ptr<Test> p1(new Test(1));
  auto_ptr<Test> p2(new Test(2));

  cout<<"p1: addr="<<p1.get()<<endl;    
  cout<<"p2: addr="<<p2.get()<<endl;    

  p2 = p1;
  cout<<"p1: addr="<<p1.get()<<endl;    
  cout<<"p2: addr="<<p2.get()<<endl;     
  return 0;
}

運行打印:

如上圖所示,當我們執行p2=p1后,便執行了p2的析構函數進行自動釋放了.並且p1.get()=0,所以auto_ptr具備自動釋放功能以及同塊堆空間下只能有一個指針對象特性

2) shared_ptr (需要C++11支持)

  • 帶有引用計數機制,支持多個指針對象指向同一片內存(實現共享)
  • 提供swap()成員函數,用來交換兩個相同類型的對象指針地址
  • 提供unique()成員函數, 判斷該指針對象地址是否被其它指針對象引用
  • 提供get()成員函數,用來獲取指針對象指向的地址
  • 提供reset()成員函數,將自身指針對象地址設為NULL,並將引用計數-1(當計數為0,會自動去delete內存)
  • 提供use_count()成員函數,可以用來查看引用計數個數

示例如下所示:

class Test{

public:
  int mvalue;
  Test(int i=0)
  {
    mvalue = i ;
    cout<< "Test("<<mvalue<<")"<<endl;    
  }
  ~Test()
  {
    cout<< "~Test("<<mvalue<<")"<<endl;    
  }
};

int main()
{
  shared_ptr<Test> p1(new Test(1));
  shared_ptr<Test> p2(new Test(2));

  cout<<"p1: addr="<<p1.get()<<endl;    
  cout<<"p2: addr="<<p2.get()<<endl;    

  p1.swap(p2);    //互換p1和p2指針指向的地址 

  cout<<"p1: addr="<<p1.get()<<endl;    
  cout<<"p2: addr="<<p2.get()<<endl;

  p1 = p2;    //使p1指向p2指向的地址,並且釋放p1之前指向的地址 
  cout<<"p1:addr="<<p1.get()<<", p2:addr="<<p2.get()<<endl;    
  cout<<"p1: unique="<<p1.unique()<<endl;    //p1和p2指向同一片內存,所以為0 
  cout<<"p1: count="<<p1.use_count()<<endl;
  return 0;
}

運行打印:

 

29.Qt中的智能指針
-QPointer

  • 當其指向的對象被銷毀時,本身會自動賦值為NULL(從而避免被多次釋放和野指針)
  • 缺點在於,該模板類析構時,不會自動摧毀所指向的對象(需要手工delete)

-QSharedPointer

  • 帶有引用計數機制,支持多個指針對象指向同一片內存(實現共享)
  • 可以被自由地拷貝和賦值
  • 當引用計數為0(最后一個指針被摧毀)時,才刪除指向的對象(和shared_ptr類似)

-QScopedPointer

  • 優點在於生命期結束后會自動刪除它所指的對象(不需要手工delete)
  • 不支持多個QScopedPointer指針對象指向同一片內存(不能共享)

示例:

QScopedPointer<QPushButton> p1(new QPushButton);

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM