听课笔记---程序设计与算法(三)C++面向对象程序设计(郭伟老师)---第二周

Week 2

Contents

成员与隐藏

构造函数

析构函数

Thoughts

成员与隐藏

成员函数的定义与调用

  • 成员函数可定义在类内部,与普通函数类似,也可定义在类外部,但其声明总是在类内部,形式如下
class A {
    int Fun();
}

int A::Fun()
{
    ...
}
  • 成员函数必须由对象或指向对象的指针调用

private and public

  • 关键字可以在定义时反复使用,不限顺序
class A {
public:
    ...
private:
    ...
public:
    ...
}
  • 没有关键字的视为private

    class A {
    int a; // private
    int Fun(); //private
    public:
    int b; // public
    };
  • private 成员只能在成员函数内部访问,public 成员可以在任何地方访问

成员函数的可访问范围

  • 当前对象的全部属性,函数
  • 同类对象所有属性及函数(以函数参数传递而来)

其余地带可访问范围

对象的公共成员

“隐藏”机制及其作用

  • 设置私有成员的机制为”隐藏“
  • 强制对成员的访问一定要通过成员函数,以后对成员的属性(如数据类型,返回值类型)进行改变后,只需要改变对应的成员函数中的操作即可,不必修改其余地带直接使用该成员的语句

隐藏机制实例

#include
#include

class CEmployee {
private:
    char szName[30];
    int getSalary();
public:
    int salary;
    void setName(const char * name);
    void getName(char * name);
    void averageSalary(CEmployee e1, CEmployee e2);
};

void CEmployee::setName(const char * name)
{
            strcpy(szName, name);   //直接使用私有成员szName,合法
}

void CEmployee::getName(char * name)
{
            strcpy(name, szName);//直接使用私有成员szName,合法
}

int CEmployee::getSalary()
{
            return salary;
}

int main()
{
    CEmployee A;
    char name[30];
    A.setName("Tom"); // 合法
    A.getSalary(); // 非法,直接在其他地带调用私有函数
    strcpy(name, A.szName); // 非法,直接使用私有变量
    return 0;
}

成员函数的重载及参数缺省

成员函数也可以重载和参数缺省

但是要避免二义性错误, 如

class A {
    void Fun();
    void Fun(int a = 0);
}

int main()
{
    Fun();// ambiguous
}

构造函数

构造函数基本概念

  • 成员函数的一种
  • 名字与类名相同,可以有参数,不能有返回值(void)也不行
  • 负责初始化,不负责分配空间
  • 程序自动生成无参数构造函数,无任何操作。在程序员自己定义了构造函数以后,缺省的构造函数失效,若仍需无参数构造函数需自己写
  • 一个类可以有多个构造函数,各函数之间为重载关系
  • 对象生成时,构造函数会被调用(无论什么形式生成),构造函数执行且仅执行一次
  • 不允许出现A::A(A) 式的构造函数

构造函数的意义

  • 自动初始化对象,不再需要额外初始化对象
  • 避免无初始化导致的未知错误

允许private 构造函数

private 构造函数不能用来直接初始化对象

构造函数使用实例

构造函数基本使用
#include

class Complex {
    double real, imag;  // private
public:
    Complex(double real, double imag);
    Complex(double i);
    Complex(Complex, Complex);
};

Complex::Complex(double r, double i)
{
    real = r;
    imag = i;
}

Complex::Complex(double i)
{
    real = i;
    imag = 0;
}

Complex::Complex(Complex c1, Complex c2)
{
    real = c1.real + c2.real;   
    imag = c1.imag + c2.imag;   // 均合法,使用同类对象私有变量
}

int main()
{
    Complex a;      // 非法,无参缺省构造函数已停用
    Complex c1(4), c2(2, 1); // 自动转化为 double
    Complex c(c1, c2);
    Complex * p = new Complex(1);

    return 0;
}
构造函数与数组
#include

class Test {
public:
    int x;
    Test() 
    {
        std::cout << "Constructor 1 Called." << std::endl;
    }
    Test(int i)
    {
        x = i;
        std::cout << "Constructor 2 Called." << std::endl;
    }
    Test(int a, int b)
    {
        x = a + b;
        std::cout << "Constructor 3 Called." << std::endl;
    }
};


int main()
{
    Test A[2] = {2};        // A[1]只有一个空元素也要调用constructor
    std::cout << "Step 1" << std::endl;
    Test B[2] = {Test(2, 1), 5};
    std::cout << "Step 2" << std::endl;
    Test * P = new Test[2]{1, Test(2,1)};   // 这种初始化方式C++11才有,warning: 额外的初始化,new feature
    std::cout << "Step 3" << std::endl;
    delete []P;
    Test * PA[3] = {new Test(1), new Test(1,2), NULL};  // PA[2]只有一个指针不调用constructor
    delete PA[1];
    delete PA[0];
    std::cout << "Step 4" << std::endl;

    return 0;
}

输出

Constructor 2 Called.
Constructor 1 Called.
Step 1
Constructor 3 Called.
Constructor 2 Called.
Step 2
Constructor 2 Called.
Constructor 3 Called.
Step 3
Constructor 2 Called.
Constructor 3 Called.
Step 4

复制构造函数

复制构造函数基本概念
  • 构造函数中的一种
  • 只有一个参数,即对同类对象的引用(可以是const或非const ,一般使用const
  • 编译器会自动生成复制构造函数,若程序员自己定义,则自动生成的复制构造函数不存在
复制构造函数实例
自动生成的复制构造函数
#include

class A {
public:
    int v;
    A(int i)
    {
        v = i;
    }
};

int main()
{
    A c1(5);
    A c2(c1);   // 缺省的复制构造函数被调用

    std::cout << c1.v << std::endl;
    std::cout << c2.v << std::endl;

    return 0;
}

输出

5
5

三种复制构造函数起作用的情况
  1. 用一个对象初始化另一个同类的对象

    
    #include
    
    
    class A {
    public:
    int v;
    A(int i)
    {
        v = i;
    }
    A(A & a)
    {
        v = a.v;
        std::cout << "Copy constructor called." << std::endl;
    }
    };
    
    int main()
    {
    A c1(5);
    A c2(c1);   // 初始化,复制构造函数被调用
    A c3 = c2;  // 同样是初始化语句,而非赋值语句,复制构造函数被调用
    
    c1 = c2;    // 赋值语句,不调用复制构造函数
    return 0;
    }

    Output

    Copy constructor called.
    Copy constructor called.

  2. 函数参数为类的对象,形参会被复制构造函数初始化

    
    #include
    
    
    class A {
    public:
    int v;
    A(int i)
    {
        v = i;
    }
    A(A & a)
    {
        v = a.v;
        std::cout << "Copy constructor called." << std::endl;
    }
    };
    
    void Fun(A a)
    {
    std::cout << a.v << std::endl;
    }
    
    int main()
    {
    A c1(5);
    
    Fun(c1);    // 复制构造函数被调用形成形参
    return 0;
    }

    Output

    Copy constructor called.
    5

  3. 函数返回值为类的对象,返回时,复制构造函数初始化返回值

    
    #include
    
    
    class A {
    public:
    int v;
    A(int i)
    {
        v = i;
    }
    A(A & a)
    {
        v = a.v;
        std::cout << "Copy constructor called." << std::endl;
    }
    };
    
    A Fun(A a)
    {
    std::cout << a.v << std::endl;
    return a;
    }
    
    int main()
    {
    A c1(5);
    
    std::cout << Fun(c1).v << std::endl;    // 复制构造函数被调用形成形参,再被调用形成返回值
    return 0;
    }

    Output

    Copy constructor called.
    5
    Copy constructor called.
    5

Tips
  • 赋值语句不调用复制构造函数
  • 使用常量引用参数,避免复制构造函数的调用开销,并避免在函数体内改变被引用的对象

类型转换构造函数

类型转换构造函数基本概念
  • 实现类型的自动转换为目的
  • 只有一个参数,不是复制构造函数(即该参数非& A 类型),即可称为类型转换构造函数
  • 构造一个临时变量
类型转换构造函数实例
#include

class Complex {
public:
    double real, imag;
    Complex(int i)  // convert int into Complex
    {
        std::cout << "IntConstructor Called." << std::endl;
        real = i;
        imag = 0;
    }
    Complex(double r, double i)
    {
        real = r;
        imag = i;
    }
};

int main()
{
    Complex aa(4,5);
    Complex a(4);   // 类型转换构造函数被调用,用于初始化a,不产生临时变量

    Complex aaa = 4;    // 同样用于初始化,不产生临时变量

    aaa = 9;        // 类型转换构造函数被调用,先生成Complex(9)初始化的临时变量,再赋给aaa

    return 0;
}

Output:

IntConstructor Called.
IntConstructor Called.
IntConstructor Called.

析构函数

析构函数基本概念

  • 名字与类名相同,在类名前加~ 符号
  • 无参数和返回值
  • 一个类最多拥有一个析构函数
  • 析构函数在对象消亡时被调用
  • 缺省的析构函数几乎不进行操作,程序员自定义后缺省的析构函数便消失

析构函数与数组,指针

对象数组消亡时,每个对象的析构函数都会被调用

指针所指的动态分配的对象,不delete就不会消亡,也不调用析构函数

析构函数与函数返回值

对象在以返回值返回时,先调用赋值构造函数生成临时变量,而后消亡调用析构函数,随后临时变量也会消亡,也调用析构函数。

析构函数实例

析构函数与数组
#include

class A {
    public:
        ~A()
        {
            std::cout << "Destructor Called." << std::endl;
        }
};

int main()
{
    A Test[2];  // 两个对象,消亡时调用两次析构函数
    A * P = new A[3];   //三个对象, 不delete就不调用析构函数

    return 0;
}

Output:

Destructor Called.
Destructor Called.

析构函数与函数调用
#include

class A {
    public:
        int x;
        A(int i) 
        {
            x = i;
        }
        A(A & a)
        {
            x = a.x;
            std::cout << "Copy called" << std::endl;
        }
        ~A()
        {
            std::cout << x << "Destructor Called" << std::endl;
        }
};

A Fun()
{
    A b(4);
    return b;
}

A a(5);

int main()
{
    a = Fun();  //

    std::cout << "main ends" << std::endl;
    return 0;
}

Output:

Copy called
4Destructor Called //b
4Destructor Called //临时变量
main ends

4Destructor Called //a

*编译器中的优化

在许多编译器中,函数的类的对象类型的返回值不会调用复制构造函数生成临时变量,也不会调用析构函数,比如VS在Debug 和 Release 两种模式下,表现是不一样的。

你可能感兴趣的:(听课笔记---程序设计与算法(三)C++面向对象程序设计(郭伟老师)---第二周)