Cpp Primer Plus Part4


  1. 类的访问控制

通过类的对象即可访问类的公有成员,而只能通过类对象的公有成员函数(或友元函数)来访问对象的保护和私有成员。(类的默认控制权为私有的,而结构则是公有的)

  1. 类声明中的短小成员函数会被作为内联函数来使用。

  2. 类的构造函数不可以有返回值,其原型应该位于类声明的公有部分。
    构造函数中的参数列表不能和成员变量同名,因为参数列表并不是类的成员,常见的区分方法是在成员变量前加m_前缀或_后缀来作为构造函数的形参。

    Stock food = Stock("World Cabbage", 250, 1.25);
    Stock garment("Furry Mason", 50, 2.5);
    //Stock garment = Stock("Furry Mason", 50, 2.5);
    Stock *pstock = new Stock("Electroshock Games", 18, 19.0);

    如果类中没有定义构造函数,则编译器会自动加上默认构造函数(毫不作为,不会对成员进行初始化),而当存在构造函数时必须手动加上默认构造函数

  3. 析构函数负责完成类对象所占据的内存的回收,即销毁类对象。其函数名为构造函数前加上~,同样也不允许有返回值。
    析构函数不应该在代码中显式地进行调用。因为如果类对象是静态存储类对象时,程序结束后会自动调用析构函数;为自动存储类对象时则会在代码块结束时自行调用;而当为通过new创建的类对象时,只需通过delete即可自动调用析构函数;而当类对象为临时的副本对象时,程序结束时也会自动调用析构函数。

  4. const成员函数:一般而言,定义了一个const类对象,并通过该对象访问类的成员函数时,编译器将会拒绝这一行为,因为无法确保该函数不会对类对象进行修改,因此需要定义const成员函数,通过在函数后加上const来表征:void stock::show() const. 只要类方法不修改调用对象,就应声明为const。参数列表中的const表明不会修改参数列表的值,而成员函数成名为const表明不会修改当前对象的值。

  5. this指针

    假设我们希望对比两种股票,输出股价更高的那一支,则可以通过引用的方式,将其中一支股票的引用传入到另一支股票的成员函数中,然后成员函数返回股价较高的那支股票的引用,如将该函数定义为:
    const Stock& topval(const Stock & s) const;
    则通过下面的命令即可实现以上功能:
    top=stock1.topval(stock2);
    top=stock2.topval(stock1);
    但存在一个问题,当执行命令top=stock1.topval(stock2);时,如果stock2的股价更高则直接返回stock2即可,那么当stock1的股价更高,如何返回呢?答案是返回*this
    this指针指向用来调用成员函数的对象,所有类方法的this指针都设置为调用它的对象的地址,也就是说,this指向的是类对象本身的地址。因此,上边的topval方法可以写成:

    const Stock& topval(const Stock& s) const {
        if(s.total_val > total_val)
            return s;
        return *this;
    }
  6. 在类内定义的成员变量和成员函数的作用域都是整个类,意味着无法直接从类外访问类内成员(即使是公有成员),必须通过类对象来进行访问。若要定义作用域为类时,不能直接通过使用const修饰成员变量,因为没创建类之前,不应该有对应的存储值的空间,如果直接定义一个const变量,那么每个类都将会有属于自己的const变量了。可以采用的方法:

  • 通过在类中声明一个枚举
  • static const修饰:此时该变量并没有存储在类中,而是和其他静态变量一样存储在静态存储区。
  1. 作用域内枚举(enum后加上struct或class), 不能隐式转换为整型。

  2. 运算符重载:operator op(argument list)

    Time Time::Sum(const Time & t) const {
        Time sum;
        sum.minutes = minutes + t.minutes;
        sum.hours = hours + t.hours + sum.minutes/60;
        sum.minutes %= 60;
        return sum;
    }
    Time Time::operator+(const Time & t) const {    // 重载运算符+
        Time sum;
        sum.minutes = minutes + t.minutes;
        sum.hours = hours + t.hours + sum.minutes/60;
        sum.minutes %= 60;
        return sum;
    }
    
    int main(void) {
        ...
        Time time1{...};
        Time time2{...};
        // 下边三种都是等价的
        Time sum = time1.Sum(time2);
        Time sum1 = time1.operator+(time2); // 采用重载运算符+
        Time sum2 = time1 + time2;          // 采用重载运算符+
        ...
    }

    重载运算符的限制:

    • 重载后的运算符的操作数至少有一个时用户定义的类型
    • 使用运算符不能违反其原本的句法规则,同时也无法修改运算符的优先级
    • 不能新建运算符
    • 不能重载的运算符:sizeof..*::?:typeidconst_castdynamic_castreinterpret_casestatic_cast.
    • 必须通过成员函数进行重载的运算符:=()[]->
  3. 友元包括三种:友元函数、友元类和友元成员函数。为类重载二元运算符时通常需要友元。 比如:

    Time operator*(double multi) {
        Time result;
        long t = hours*multi*60+minutes*multi;
        result.hours = t/60;
        result.minutes = t%60;
        return result;
    }

    通过重载运算符*,可以实现两种不同类型的相乘,但只能通过obj.operator*(1.23)obj*1.23,而不能1.23*obj,因为这个*是属于类对象,需要通过调用类的实例来访问,而1.23并不是类的实例,无法调用该重载运算符。为解决这个问题,需要借助友元这一概念。
    创建友元的方法:首先将原型放在类声明中,并在原型声明前加上friend;然后编写函数定义,注意函数定义不要使用friend

    友元函数不是类的成员函数,因而不能通过类的实例来调用,但它与成员函数的访问权限相同。友元与OOP并不矛盾,因为只有类声明中才能决定哪一个函数是友元,即类依然对哪些函数可以访问类的私有成员保持绝对控制权。

  4. 重载运算符<<:为实现类似整型打印那样直接将某个类进行打印,即cout << time;,需要对<<进行重载(通过友元):

    class Time {
        ...
    public:
        ...
        friend void operator<<(ostream &os, const Time & t);
        ...
    }
    void operator<<(ostream &os, const Time& t) {
        os << t.hours << " hours," << t.minutes << " minutes";
    }

    然而,上边的重载方式并不能解析类似cout << time1 << time2;的语句,因为它要求<<左边必须为ostream类对象,而上边的语句实际上等同于(cout << time1) << time2;,在第二个<<左边并不存在ostream对象,因此可以进行第二种重载:

    ostream & operator << (ostream &os, const time &t) {
        os << t.hours << " hours," << t.minutes << " minutes";
        return os;
    }
  5. 实际上,作为成员函数的运算符重载和友元函数的运算符重载都可以实现所需功能,不过友元函数提供了一种更符合用户思考的方式罢了,比如前边重载<<,用成员函数的方式重载时调用的方式是time << cout,和原本的ostream的使用相比过于怪异。其中,成员函数的操作数比操作符少1,因为另一个操作是通过隐式传递调用该运算符的对象;而友元则操作数需要等于运算符的操作数,即操作数作为友元的参数传递。

  6. 注意,如果以成员函数的方式重载了二元运算符,比如重载-那么time1 - time2实际上就等同于time1.operator-(time2),注意这个顺序是需要严格遵守的。

  7. Cpp中不自动对类型不兼容的类型进行转换,如数值类型到指针类型。

  8. 只有接受一个参数的构造函数才能作为转换函数,或者为后续参数提供默认参数。通过关键字explicit可以关闭这种特性。

class Stone {
    ...
public:
    Stone() {}
    Stone(double n) {}
    ~Stone();
    ...
}
int main(void) {
    Stone s;
    s = 1.25;   // valid, but invalid when `explict stone(double)`
    s = 1// valid;
}
  1. 第15点中实现了数值到类对象的转换,那么如何实现类对象到数值的转换呢?可以采用C++中特殊的运算符函数,即转换函数来实现。其声明为:

                   operator typeName();
    转换函数必须是类方法,既不能指定返回类型,也不能有参数。typeName为所要转换成的类型,返回值为直接对数值进行转换后的结果,如returun (double)s;. 为避免隐式转换带来不必要的错误,可以将转换函数声明为explict的。


文章作者: Vyron Su
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Vyron Su !