C++學習_類和對象基礎

1、類和對象的基本概念


1. 類成員的可訪問範圍

在類的定義中,用下列訪問範圍關鍵字來講明類成員ios

可被訪問的範圍:
– private: 私有成員,只能在成員函數內訪問
– public : 公有成員,能夠在任何地方訪問
– protected: 保護成員,之後再說
以上三種關鍵字出現的次數和前後次序都沒有限制。

定義一個類數組

class className {
private:       // 說明類成員的可訪問範圍
私有屬性和函數
public:
公有屬性和函數
protected:
保護屬性和函數
};

如過某個成員前面沒有上述關鍵字,則缺省地被認爲是私有成員。函數

class Man {
  int nAge; // 私有成員
  char szName[20]; // 私有成員
public:
  void SetName(char * szName){
  strcpy( Man::szName,szName);
}
};

在類的成員函數內部,可以訪問:優化

– 當前對象的所有屬性、函數;spa

– 同類其它對象的所有屬性、函數。對象

在類的成員函數之外的地方,只可以訪問該類對象的公有成員。blog

class CEmployee {
  private:
    char szName[30]; // 名字
  public :
    int salary; // 工資
    void setName(char * name);
    void getName(char * name);
    void averageSalary(CEmployee e1,CEmployee e2);
};
void CEmployee::setName( char * name) {
  strcpy( szName, name); //ok
}
void CEmployee::getName( char * name) {
  strcpy( name,szName); //ok
}
void CEmployee::averageSalary(CEmployee e1,CEmployee e2){
  cout << e1.szName; //ok ,訪問同類其餘對象私有成員
  salary = (e1.salary + e2.salary )/2;
}
int main()
{
  CEmployee e;
  strcpy(e.szName,"Tom1234567889"); // 編譯錯,不能訪問私有成員
  e.setName( "Tom"); // ok
  e.salary = 5000; //ok
  return 0;
}

用struct定義類內存

  和用"class"的惟一區別,就是未說明是公有仍是私有的成員,就是公有ci

struct CEmployee {
  char szName[30]; // 公有!!
  public :
    int salary; // 工資
    void setName(char * name);
    void getName(char * name);
    void averageSalary(CEmployee e1,CEmployee e2);
};

2. 設置私有成員的機制,叫「隱藏」

「隱藏」的目的get

  強制對成員變量的訪問必定要經過成員函數進行,那麼之後成員變量的類型等屬性修改後,只須要更改爲員函數便可。

不然,全部直接訪問成員變量的語句都須要修改。

「隱藏」的做用

  若是將上面的程序移植到內存空間緊張的手持設備上,但願將szName 改成 char szName[5],若szName不是私有,

那麼就要找出全部相似 strcpy(e.szName,"Tom1234567889");這樣的語句進行修改,以防止數組越界。這樣作很麻煩。

  若是將szName變爲私有,那麼程序中就不可能出現(除非在類的內部)strcpy(e.szName,"Tom1234567889");

這樣的語句,全部對 szName的訪問都是經過成員函數來進行,好比:e.setName( 「Tom12345678909887」);

那麼,就算szName改短了,上面的語句也不須要找出來修改,只要改 setName成員函數,在裏面確保不越界就能夠了。

3. 成員函數的重載及參數缺省

成員函數也能夠重載
成員函數能夠帶缺省參數。

#include <iostream>
using namespace std;
class Location {
  private :
    int x, y;
  public:
    void init( int x=0 , int y = 0 );
    void valueX( int val ) { x = val ;}
    int valueX() { return x; }
};

void Location::init( int X, int Y)
{
  x = X;
  y = Y;
}
int main() 
{
  Location A,B;
  A.init(5);
  A.valueX(5);
  cout << A.valueX();  // 輸出:5
  return 0;
}

使用缺省參數要注意避免有函數重載時的二義性

class Location {
  private :
    int x, y;
  public:
    void init( int x =0, int y = 0 );
    void valueX( int val = 0) { x = val; }
    int valueX() { return x; }
};
  Location A;
  A.valueX(); // 錯誤,編譯器沒法判斷調用哪一個valueX

2、構造函數


1. 基本概念

成員函數的一種
1).名字與類名相同,能夠有參數,不能有返回值(void也不行)
2).做用是對對象進行初始化,如給成員變量賦初值
3).若是定義類時沒寫構造函數,則編譯器生成一個默認的無參數的構造函數
4).默認構造函數無參數,不作任何操做
5).若是定義了構造函數,則編譯器不生成默認的無參數的構造函數
6).對象生成時構造函數自動被調用。對象一旦生成,就不再能在其上執行構造函數
7).一個類能夠有多個構造函數

爲何須要構造函數:
1) 構造函數執行必要的初始化工做,有了構造函數,就不必專門再寫初始化函數,也不用擔憂忘記調用初始化函數。
2) 有時對象沒被初始化就使用,會致使程序出錯。

A.默認無參數構造函數

class Complex {
		private :
				double real, imag;
		public:
				void Set( double r, double i);
}; //編譯器自動生成默認構造函數
Complex c1; //默認構造函數被調用
Complex * pc = new Complex; //默認構造函數被調用

B.有參數的構造函數

class Complex {
		private :
				double real, imag;
		public:
				Complex( double r, double i = 0);
};
Complex::Complex( double r, double i) {
real = r; imag = i;
}
Complex c1; // error, 缺乏構造函數的參數
Complex * pc = new Complex; // error, 沒有參數
Complex c1(2); // OK
Complex c1(2,4), c2(3,5);
Complex * pc = new Complex(3,4);

C.能夠有多個構造函數,參數個數或類型不一樣

class Complex {
		private :
				double real, imag;
		public:
				void Set( double r, double i );
				Complex(double r, double i );
				Complex(double r );
				Complex(Complex c1, Complex c2);
};
Complex::Complex(double r, double i)
{
real = r; imag = i;
}

Complex::Complex(double r)
{
real = r; imag = 0;
}
Complex::Complex (Complex c1, Complex c2);
{
real = c1.real+c2.real;
imag = c1.imag+c2.imag;
}
Complex c1(3) , c2 (1,0), c3(c1,c2);
// c1 = {3, 0}, c2 = {1, 0}, c3 = {4, 0};

D.構造函數最好是public的,private構造函數不能直接用來初始化對象

class CSample{
		private:
			CSample() { }
};
int main(){
		CSample Obj; //err. 惟一構造函數是private
		return 0;
}

課堂例題:

有類A以下定義:
class A {
int v;
public:
A ( int n) { v = n; }
};
下面哪條語句是編譯不會出錯的?
A) A a1(3);
B) A a2;
C) A * p = new A();
D) A * a(3)

2. 構造函數在數組中的使用

class CSample {
		int x;
public:
		CSample() {
				cout << "Constructor 1 Called" << endl;
		}
		CSample(int n) {
				x = n;
				cout << "Constructor 2 Called" << endl;
		}
};
int main(){
		CSample array1[2];
		cout << "step1"<<endl;
		CSample array2[2] = {4,5};
		cout << "step2"<<endl;
		CSample array3[2] = {3};
		cout << "step3"<<endl;
		CSample * array4 = new CSample[2];
		delete []array4;
		return 0;
}

輸出:

Constructor 1 Called
Constructor 1 Called
step1
Constructor 2 Called
Constructor 2 Called
step2
Constructor 2 Called
Constructor 1 Called
step3
Constructor 1 Called
Constructor 1 Called

 代碼示例:

class Test {
  public:
    Test( int n) { } //(1)
    Test( int n, int m) { } //(2)
    Test() { } //(3)
};
Test array1[3] = { 1, Test(1,2) };
// 三個元素分別用(1),(2),(3)初始化
Test array2[3] = { Test(2,3), Test(1,2) , 1};
// 三個元素分別用(2),(2),(1)初始化
Test * pArray[3] = { new Test(4), new Test(1,2) };
//兩個元素分別用(1),(2) 初始化

課堂例題:  

假設 A 是一個類的名字,下面的語句生成
了幾個類A的對象?
A * arr[4] = { new A(), NULL,new A() };
A) 1
B) 4
C) 2

3、複製構造函數


1. 基本概念

1.1). 只有一個參數,即對同類對象的引用。
   形如 X::X( X& )或X::X(const X &), 兩者選一,後者能以常量對象做爲參數
1.2). 若是沒有定義複製構造函數,那麼編譯器生成默認複製構造函數。默認的複製      構造函數完成複製功能。

class Complex {
			private :
					double real,imag;
};
Complex c1; //調用缺省無參構造函數
Complex c2(c1);//調用缺省的複製構造函數,將 c2 初始化成和c1同樣

 1.3). 若是定義的本身的複製構造函數,則默認的複製構造函數不存在。

class Complex {
			public :
			double real,imag;
			Complex(){ }
			Complex( const Complex & c ) {
			real = c.real;
			imag = c.imag;
			cout << 「Copy Constructor called」;
}
};
Complex c1;
Complex c2(c1);//調用本身定義的複製構造函數,輸出 Copy Constructor called

 1.4). 不容許有形如 X::X( X )的構造函數。  

class CSample {
			CSample( CSample c ) {
			} //錯,不容許這樣的構造函數
};

2. 複製構造函數起做用的三種狀況

1)當用一個對象去初始化同類的另外一個對象時。

Complex c2(c1);
Complex c2 = c1; //初始化語句,非賦值語句

2)若是某函數有一個參數是類 A 的對象,那麼該函數被調用時,類A的複製構造函數將被調用。

class A
{
		public:
			A() { };
			A( A & a) {
			cout << "Copy constructor called" <<endl;
		}
};

void Func(A a1){ }
int main(){
		A a2;
		Func(a2);
		return 0;
}
// 程序輸出結果爲: Copy constructor called

3) 若是函數的返回值是類A的對象時,則函數返回時,A的複製構造函數被調用:

class A
{
		public:
			int v;
			A(int n) { v = n; };
			A( const A & a) {
				v = a.v;
				cout << "Copy constructor called" <<endl;
			}
};
A Func() {
			A b(4);
			return b;
}
int main() {
			cout << Func().v << endl; return 0;
}
輸出結果:
Copy constructor called
4

注意:對象間賦值並不致使複製構造函數被調用  

class CMyclass {
			public:
				int n;
				CMyclass() {};
				CMyclass( CMyclass & c) { n = 2 * c.n ; }
};
int main() {
			CMyclass c1,c2;
			c1.n = 5; c2 = c1; CMyclass c3(c1);
			cout <<"c2.n=" << c2.n << ",";
			cout <<"c3.n=" << c3.n << endl;
			return 0;
}
輸出: c2.n=5,c3.n=10

3. 常量引用參數的使用

void fun(CMyclass obj_ ) {
			cout << "fun" << endl;
}

  這樣的函數,調用時生成形參會引起復制構造函數調用,開銷比較大。

因此能夠考慮使用 CMyclass & 引用類型做爲參數。若是但願確保實參的值在函數中不該被改變,那麼能夠加上const 關鍵字:

void fun(const CMyclass & obj) {
//變 函數中任何試圖改變 obj 值的語句都將是變成非法
}

課堂習題:

假設A 是一個類的名字,下面哪段程序不會用到A的複製構造函數?
A) A a1,a2; a1 = a2;
B) void func( A a) { cout << "good" << endl; }
C) A func( ) { A tmp; return tmp; }
D) A a1; A a2(a1);  

 4、類型轉換構造函數和析構函數


1.類型轉換構造函數

1.1 什麼是類型轉換構造函數
1. 定義轉換構造函數的目的是實現類型的自動轉換。
2. 只有一個參數,並且不是複製構造函數的構造函數,通常就能夠看做是轉換構造函數。
3. 當須要的時候,編譯系統會自動調用轉換構造函數,創建一個無名的臨時對象(或臨時變量)。

1.2 類型轉換構造函數實例

class Complex {
	public:
		double real, imag;
		Complex( int i) {// 類型轉換構造函數
			cout << "IntConstructor called" << endl;
			real = i; imag = 0;
		}
		Complex(double r,double i) {real = r; imag = i; }
};
int main ()
{
		Complex c1(7,8);
		Complex c2 = 12;
		c1 = 9; // 9 被自動轉換成一個臨時Complex 對象
		cout << c1.real << "," << c1.imag << endl;
		return 0;
}  

顯式類型轉換構造函數

class Complex {
		public:
				double real, imag;
				explicit Complex( int i) {//顯式類型轉換構造函數
						cout << "IntConstructor called" << endl;
						real = i; imag = 0;
				}
				Complex(double r,double i) {real = r; imag = i; }
};
int main () {
		Complex c1(7,8);
		Complex c2 = Complex(12);
		c1 = 9; // error, 9不能被自動轉換成一個臨時Complex對象
		c1 = Complex(9) //ok
		cout << c1.real << "," << c1.imag << endl;
		return 0;
}

課堂習題

類A定義以下:
class A {
		int v;
		public:
				A(int i) { v = i; }
				A() { }
};
下面段程序不會引起類型轉換構造函數被調用?
A) A a1(4)
B) A a2 = 4;
C) A a3; a3 = 9;
D) A a1,a2; a1 = a2;

2.析構函數

2.1 什麼是析構函數
1. 名字與類名相同,在前面加‘~’, 沒有參數和返回值,一個類最多隻能有一個析構函數。
2. 析構函數對象消亡時即自動被調用。能夠定義析構函數來在對象消亡前作善後工做,好比釋放分配的空間等。
3. 若是定義類時沒寫析構函數,則編譯器生成缺省析構函數。缺省析構函數什麼也不作。
4. 若是定義了析構函數,則編譯器不生成缺省析構函數。

2.2 析構函數實例

class String{
		private :
				char * p;
		public:
				String () {
						p = new char[10];
				}
				~ String () ;
};
String ::~ String()
{
		delete [] p;
}

2.3 析構函數和數組 

class Ctest {
		public:
				~Ctest() { cout<< "destructor called" << endl; }
};
int main () {
		Ctest array[2];
		cout << "End Main" << endl;
		return 0;
}

對象數組生命期結束時,對象數組的每一個元素的析構函數都會被調用。
輸出:
End Main
destructor called
destructor called

2.4 析構函數和運算符 delete  

// delete 運算致使析構函數調用。
Ctest * pTest;
pTest = new Ctest; // 構造函數調用
delete pTest; // 析構函數調用
---------------------------------------------------------
pTest = new Ctest[3]; // 構造函數調用3次 次
delete [] pTest; // 析構函數調用3次 次
若new一個對象數組,那麼用delete釋放時應該寫 []。不然只delete一個對象(調用一次析構函數)

2.5 析構函數在對象做爲函數返回值返回後被調用  

class CMyclass {
		public:
				~CMyclass() { cout << "destructor" << endl; }
};
CMyclass obj;
CMyclass fun(CMyclass sobj ) { // 參數對象消亡也會致使析
															 // 構函數被調用
		return sobj; // 函數調用返回時生成臨時對象返回
}
int main(){
		obj = fun(obj); // 函數調用的返回值(臨時對象)被
		return 0; // 用事後,該臨時對象析構函數被調用
}
輸出:
destructor
destructor
destructor

 5、構造函數和析構函數調用時機


1. 構造函數和析構函數何時被調用?

class Demo {
		int id;
		public:
				Demo(int i) {
						id = i;
						cout << "id=" << id << " constructed" << endl;
				}
				~Demo() {
						cout << "id=" << id << " destructed" << endl;
				}
};

Demo d1(1);
void Func()
{
		static Demo d2(2);
		Demo d3(3);
		cout << "func" << endl;
}

int main () {
		Demo d4(4);
		d4 = 6; // 臨時對象
		d4 = 7;
		cout << "main" << endl;
		{Demo d5(5);} //局部對象,不加{}則在 main ends後釋放
		Func();
		cout << "main ends" << endl;
		return 0;
}

輸出結果
id=1 constructed
id=4 constructed
id=6 constructed
id=6 destructed
id=7 constructed
id=7 destructed
main
id=5 constructed
id=5 destructed
id=2 constructed
id=3 constructed
func
id=3 destructed
main ends
id=7 destructed // Demo d4(4); 調用析構函數
id=2 destructed
id=1 destructed

課堂習題 

假設A是一個類的名字,下面的程序片斷會調用類A的析構函數幾回?
int main() {
		A * p = new A[2];
		A * p2 = new A; // new 出來的對象只有delete纔會消亡
		A a;
		delete [] p;
}
A) 1
B) 2
C) 3
D) 4

2. 複製構造函數和析構函數

#include <iostream>
using namespace std;
class CMyclass {
		public:
				CMyclass() {};
				CMyclass( CMyclass & c)
				{
						cout << "copy constructor" << endl;
				}
				~CMyclass() { cout << "destructor" << endl; }
};

void fun(CMyclass obj_ )
{
		cout << "fun" << endl;	
}
CMyclass c;
CMyclass Test( )
{
		cout << "test" << endl;
		return c;
}
int main(){
		CMyclass c1;
		fun(c1);
		Test();
		return 0;
}

輸出結果
copy constructor
fun
destructor //參數消亡
test
copy constructor
destructor // 返回值臨時對象消亡
destructor // 局部變量消亡
destructor // 全局變量消亡

3. 複製構造函數在不一樣編譯器中的表現

class A {
		public:
				int x;
				A(int x_):x(x_)
				{ cout << x << " constructor called" << endl; }
				A(const A & a ) { // 本例中dev 須要此const 其餘編譯器不要
						x = 2 + a.x;
						cout << "copy called" << endl;
				}
				~A() { cout << x << " destructor called" << endl; }
};
A f( ){ A b(10); return b; }
int main( ){
		A a(1);
		a = f(); // 複製構造函數初始化
		return 0;
}

Visual Studio輸出
結果:
1 constructor called
10 constructor called
10 destructor called
copy called
12 destructor called
12 destructor called

dev C++輸出結果:
1 constructor called
10 constructor called
10 destructor called
10 destructor called

說明dev出於優化目的並未生成返回值臨時對象。VS無此問題 

 RRR

相關文章
相關標籤/搜索