一、類和對象的基本概念
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
