《程序设计实习》之【类和对象进阶】

复制构造函数

基本概念

  • 只有一个参数,即对同类对象的引用。
  • 形如X::X( X& )X::X(const X &), 二者选一,后者能以常量对象作为参数。
  • 如果没有定义复制构造函数,那么编译器生成默认复制构造函数,默认的复制构造函数完成复制功能。
class Complex {
private :
    double real,imag;
};
Complex c1;     //调用缺省无参构造函数
Complex c2(c1); //调用缺省的复制构造函数,将 c2 初始化成和c1一样
  • 如果定义的自己的复制构造函数, 则默认的复制构造函数不存在。
class Complex { 
public :
    double real,imag; 
    Complex( const Complex & c ) { 
        real = c.real; 
        imag = c.imag; 
        cout << “Copy Constructor called”; 
    }
}; 
Complex c1; 
Complex c2(c1);   //调用自己定义的复制构造函数,输出 Copy Constructor called
  • 不允许有形如 X::X( X )的构造函数。
class CSample { 
    CSample( CSample c ) { } //错,不允许这样的构造函数 
};

复制构造函数起作用的三种情况

  • 当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
  • 如果某函数有一个参数是类 A 的对象, 那么该函数被调用时,类A的复制构造函数将被调用。
class A {
public:
    A() { }; 
    A( A & a) { 
        cout << "Copy constructor called" <<endl; 
    }
};
void Func(A a1){ }
int main(){
    A a2;
    Func(a2);    // 调用了复制构造函数,用a2初始化a
    return 0;
}
// 程序输出结果为: Copy constructor called
  • 如果函数的返回值是类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;    // 把返回值b复制给一个临时对象A
    return 0;
}
/* 
输出结果:
Copy constructor called
4
*/

类型转换构造函数

  • 目的
    • 实现类型的自动转换
  • 特点
    • 只有一个参数
    • 不是复制构造函数
  • 编译系统会自动调用->转换构造函数
    • 建立一个临时对象/临时变量
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;  // 12作为参数传递给Complex( int i )来初始化c2,而不会生成临时变量
    c1 = 9;   // 9被自动转换成一个临时Complex对象,对c1进行赋值
    cout << c1.real << "," << c1.imag << endl;
    return 0;
}
/*
输出:
IntConstructor called   // Complex c2 = 12;  
IntConstructor called   // c1 = 9;
9,0
*/

析构函数

  • 成员函数的一种
    • 名字与类名相同
    • 在前面加~
    • 没有参数和返回值
    • 一个类最多只有一个析构函数
  • 对象消亡时->自动被调用
    • 在对象消亡前做善后工作
      • 释放分配的空间等
  • 定义类时没写析构函数, 则编译器生成缺省析构函数
    • 不涉及释放用户申请的内存释放等清理工作
  • 定义了析构函数, 则编译器不生成缺省析构函数
class String { 
private :
    char * p;
public:
    String () {
        p = new char[10];
    }
    ~ String ();
};
String ::~ String() {
    delete [] p;
}

析构函数和数组

  • 对象数组生命期结束时
    • 对象数组的每个元素的析构函数都会被调用
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
*/
}

析构函数和运算符 delete

delete 运算导致析构函数调用

Ctest * pTest;
pTest = new Ctest;
delete pTest;   //构造函数调用

pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest;   //析构函数调用3次

构造函数和析构函数调用时机的例题

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;
    cout << “main” << endl;
    { Demo d5(5); }
    Func();
    cout << “main ends” << endl;
    return 0;
}
/*
输出:
id=1 Constructed   // Demo d1(1)全局变量
id=4 Constructed   // Demo d4(4)
id=6 Constructed   // d4 = 6;  类型转换构造函数,生成了一个临时变量
id=6 Destructed    // 临时变量消亡
main
id=5 Constructed   // Demo d5(5) 
id=5 Destructed    // 花括号表示作用域,离开了该作用域,对象消亡
id=2 Constructed   // static Demo d2(2);
id=3 Constructed  
Func
id=3 Destructed    // 离开Func函数作用域,d3消亡,d2是静态的,静态变量的消亡要等到整个程序结束的时候
main ends
id=6 Destructed
id=2 Destructed
id=1 Destructed
*/

静态成员变量和静态成员函数

基本概念

静态成员:在说明前面加了static关键字的成员。

class CRectangle {
private:
    int w, h; 
    static int nTotalArea; //静态成员变量 
    static int nTotalNumber; 
public:
    CRectangle(int w_,int h_); 
    ~CRectangle(); 
    static void PrintTotal(); //静态成员函数
};
  • 普通成员变量每个对象有各自的一份,而静态成员变 量一共就一份,为所有对象共享
// sizeof 运算符不会计算静态成员变量,因为静态成员变量不属于任何一个对象。
class CMyclass {
    int n;
    static int s;
};
// 则 sizeof( CMyclass ) 等于 4
  • 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用与某个对象
  • 因此静态成员不需要通过对象就能访问。
  • 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
  • 静态成员函数本质上是全局函数。
  • 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。

如何访问静态成员

  • 类名::成员名
CRectangle::PrintTotal();
  • 对象名.成员名
CRectangle r; 
r.PrintTotal();   // 只是可以通过对象访问静态成员,不代表静态成员函数作用于该对象上
  • 指针->成员名
CRectangle * p = &r;

p->PrintTotal();
  • 引用.成员名
CRectangle & ref = r; 
int n = ref.nTotalNumber;

静态成员示例

考虑一个需要随时知道矩形总数和总面积的图形 处理程序 可以用全局变量来记录总数和总面积 用静态成员将这两个变量封装进类中,就更容易理解和维护。

class CRectangle {
private:
    int w, h;
    static int nTotalArea;
    static int nTotalNumber;
public:
    CRectangle(int w_,int h_);
    ~CRectangle();
    static void PrintTotal();
};
CRectangle::CRectangle(int w_,int h_) {
    w = w_;
    h = h_;
    nTotalNumber ++;
    nTotalArea += w * h; 
} 
CRectangle::~CRectangle() {
    nTotalNumber --;
    nTotalArea -= w * h; 
} 
void CRectangle::PrintTotal() {
    cout << nTotalNumber << "," << nTotalArea << endl; 
}
int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;
// 必须在定义类的文件中对静态成员变量进行一次说明
// 或初始化。否则编译能通过,链接不能通过。

int main() {
    CRectangle r1(3,3), r2(2,2);
    //cout << CRectangle::nTotalNumber;  // Wrong , 私有
    CRectangle::PrintTotal();
    r1.PrintTotal();
    return 0;
}
/*
输出结果:
2,13
2,13
*/

此CRectangle类写法,有何缺陷?

  • 在使用CRectangle类时,有时会调用复制构造函数生成临时的隐藏的CRectangle对象
    • 调用一个以CRectangle类对象作为参数的函数时
    • 调用一个以CRectangle类对象作为返回值的函数时
  • 临时对象在消亡时会调用析构函数,减少nTotalNumber 和 nTotalArea的值,可是这些临时对象在生成时却没有增加 nTotalNumber 和 nTotalArea的值。

解决办法:为CRectangle类写一个复制构造函数。

CRectangle :: CRectangle(CRectangle & r ) {
    w = r.w; h = r.h;
    nTotalNumber ++;
    nTotalArea += w * h; 
}

注意事项

  • 在静态成员函数中,不能访问非静态成员变量, 也不能调用非静态成员函数。
void CRectangle::PrintTotal() { 
    cout << w << "," << nTotalNumber << "," << nTotalArea << endl; //wrong 
}
CRetangle::PrintTotal();  //解释不通,w 到底是属于那个对象的?

成员对象和封闭类概念

  • 成员对象: 一个类的成员变量是另一个类的对象
  • 包含成员对象的类叫封闭类 (Enclosing)
class CTyre { //轮胎类
private:
    int radius;  //半径
    int width;   //宽度
public:
    CTyre(int r, int w):radius(r), width(w) {
    }
};
class CEngine { //引擎类
};

class CCar {  // 汽车类  “封闭类”
private:
    int price; //价格 
    CTyre tyre; 
    CEngine engine; 
public:
    CCar(int p, int tr, int tw) {
    }; 
    CCar::CCar(int p, int tr, int w):price(p), tyre(tr, w){
    }; 

int main(){
    CCar car(20000,17,225);
    return 0; 
}
  • 如果 CCar 类不定义构造函数, 则
CCar car; // error  编译出错
  • 编译器不知道 car.tyre 该如何初始化
  • car.engine 的初始化没有问题: 用默认构造函数
  • 生成封闭类对象的语句 -> 明确 “对象中的成员对象”-> 如何初始化

封闭类构造函数的初始化列表

  • 定义封闭类的构造函数时, 添加初始化列表:
类名::构造函数(参数表):成员变量1(参数表), 成员变量2(参数表), …
{
    …
}
  • 成员对象初始化列表中的参数
    • 任意复杂的表达式
    • 函数 / 变量/ 表达式中的函数,变量有定义

调用顺序

  • 当封闭类对象生成时,
    • S1: 执行所有成员对象的构造函数
    • S2: 执行封闭类的构造函数
  • 成员对象的构造函数调用顺序
    • 和成员对象在类中的说明顺序一致
    • 与在成员初始化列表中出现的顺序无关
  • 当封闭类的对象消亡时
    • S1: 先执行 封闭类 的析构函数
    • S2: 执行 成员对象 的析构函数
  • 析构函数顺序和构造函数的调用顺序相反
class CTyre {
public:
    CTyre() { cout << "CTyre contructor" << endl; }
    ~CTyre() { cout << "CTyre destructor" << endl; }
};

class CEngine {
public:
    CEngine() { cout << "CEngine contructor" << endl; }
    ~CEngine() { cout << "CEngine destructor" << endl; }
};

class CCar {
private:
    CEngine engine;
    CTyre tyre;
public:
    CCar( ) { cout << “CCar contructor” << endl; }
    ~CCar() { cout << "CCar destructor" << endl; }
};
int main(){
    CCar car;
    return 0;
}

/*
输出:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructor
*/

友元

友元函数

一个类的友元函数可以访问该类的私有成员

class CCar; //提前声明 CCar类, 以便后面CDriver类使用
class CDriver {
public:
    void ModifyCar( CCar * pCar);  //改装汽车
};
class CCar {
private:
    int price;
    friend int MostExpensiveCar( CCar cars[], int total);  //声明友元
    friend void CDriver::ModifyCar(CCar * pCar);   //声明友元
};

void CDriver::ModifyCar( CCar * pCar) {
    pCar->price += 1000; //汽车改装后价值增加
}
int MostExpensiveCar( CCar cars[], int total) {  //求最贵汽车的价格
    int tmpMax = -1;
    for( int i = 0; i < total; ++i )
        if( cars[i].price > tmpMax)
            tmpMax = cars[i].price;
        return tmpMax;
}

int main() {
    return 0;
}

友元类

A是B的友元类 ,那么A的成员函数可以访问B的私有成员

class CCar {
private:
    int price;
    friend class CDriver;   //声明CDriver为友元类
};

class CDriver {
public:
    CCar myCar;

    void ModifyCar() {    //改装汽车
        myCar.price += 1000;   // CDriver是CCar的友元类可以访问其私有成员
    }
};

int main() { 
    return 0; 
}

注意
友元类之间的关系不能传递, 不能继承

`this`指针

C++程序到C程序的翻译

class CCar {
public:
    int price;
    void SetPrice(int p);
};
void CCar::SetPrice(int p) {
    price = p;
}
int main() {
    CCar car;
    car.SetPrice(20000);
    return 0;
}   

翻译成的C程序:

struct CCar {
    int price;
};
void SetPrice(struct CCar* this, int p) {
    this->price = p;
}
int main() {
    struct CCar car;
    SetPrice(&car, 20000);
    return 0;
}

this指针作用

其作用就是指向成员函数所作用的对象

非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针。

class Complex {
public:
    double real, imag;
    void Print() { 
        cout << real << "," << imag; 
    }
    Complex(double r,double i):real(r),imag(i) { }
    Complex AddOne() {
        this->real ++;  //等价于 real ++;
        this->Print();  //等价于 Print
        return * this;
    }
};

int main() {
    Complex c1(1,1), c2(0,0);
    c2 = c1.AddOne();
    return 0;
} // 输出 2, 1
class A {
    int i;
public:
    void Hello() { 
        cout << "hello" << endl; 
    }
};

void Hello(A * this ) { 
    cout << "hello" << endl; 
}

int main() {
    A * p = NULL;
    p->Hello();      // 在编译器中翻译成 Hello(p);
}  // 输出:hello
class A {
    int i;
public:
    void Hello() { 
        cout << i << "hello" << endl; 

    }
    /* 在编译器中翻译成:
    void Hello(A * this ) { cout << this->i << "hello" << endl;} 
    //this若为NULL,则出错!!
    */
};  

int main() {
    A * p = NULL;
    p->Hello(); // 在翻译器中翻译成 Hello(p);
} 

this指针和静态成员函数

静态成员函数中不能使用this指针,因为静态成员函数并不具体作用于某个对象,因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数。

常量对象、常量成员函数和常引用

常量对象

如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。

class Demo{
private :
    int value;
public:
    void SetValue() { }

};

const Demo Obj;  // 常量对象

常量成员函数

在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
常量成员函数执行期间不应修改其所作用的对象 。因此,在常量成员函数中不能修改成员变量的值 (静态成员变量除外,因为静态成员变量部署于特定的对象),也不能调用同类的非常量成员函数(静态成员函数除外)。

class Sample {
public:
    int value;
    void GetValue() const;
    void func() { };
    Sample() { }
};

void Sample::GetValue() const {
    value = 0;    // wrong,不能更改成员变量的值
    func();       //wrong,非常量成员函数中可能修改成员变量的值,所以不能调用
}
int main() {
    const Sample o;
    o.value = 100; //err.常量对象不可被修改
    o.func(); //err.常量对象上面不能执行非常量成员函数
    o.GetValue(); //ok,常量对象上可以执行常量成员函数
    return 0;
} //在Dev C++中,要为Sample类编写无参构造函数才可以,Visual Studio 2010中不需要

常量成员函数的重载

两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载。

class CTest {
private :
    int n;
public:
    CTest() { n = 1 ; }
    int GetValue() const { return n ; }
    int GetValue() { return 2 * n ; }
};

int main() {
    const CTest objTest1;
    CTest objTest2;
    cout << objTest1.GetValue() << "," << objTest2.GetValue();
    // 编译器检查objTest1是常量对象,所以执行常量成员函数
    // 编译器检查objTest2是非常量成员函数,所以执行非常量成员函数
    return 0;
}

常引用

引用前面可以加const关键字,成为常引用。不能通过常引用,修改其引用的变量。

const int & r = n;
r = 5; //error
n = 4; //ok

对象引用作为函数的参数有一定风险性,若函数中不小心修改了形参o,则实参也跟着变,可以用对象的常引用作为参数,这样函数中就能确保不会出现无意中更改o值的 语句了。

class Sample {
    ……
};

void PrintfObj( const Sample & o) {
    ……
}

猜你喜欢

转载自blog.csdn.net/beashaper_/article/details/80556547
今日推荐