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


一、類和對象的基本概念


1. 類成員的可訪問范圍

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

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

定義一個類

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

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

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

在類的成員函數內部,能夠訪問:

– 當前對象的全部屬性、函數;

– 同類其它對象的全部屬性、函數。

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

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"的唯一區別,就是未說明是公有還是私有的成員,就是公有

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

2. 設置私有成員的機制,叫“隱藏”

“隱藏”的目的

  強制對成員變量的訪問一定要通過成員函數進行,那么以后成員變量的類型等屬性修改后,只需要更改成員函數即可。

否則,所有直接訪問成員變量的語句都需要修改。

“隱藏”的作用

  如果將上面的程序移植到內存空間緊張的手持設備上,希望將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

二、構造函數


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

三、復制構造函數


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);  

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


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

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


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


免責聲明!

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



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