欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-24 18:49:11
...

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<iostream>
#include<cstring>

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<iostream>

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<iostream>

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<iostream>

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<iostream>
    
    
    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<iostream>
    
    
    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<iostream>
    
    
    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<iostream>

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<iostream>

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<iostream>

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 两种模式下,表现是不一样的。